Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qauthenticator.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include <qauthenticator.h>
6#include <qauthenticator_p.h>
7#include <qdebug.h>
8#include <qloggingcategory.h>
9#include <qhash.h>
10#include <qbytearray.h>
11#include <qcryptographichash.h>
12#include <qiodevice.h>
13#include <qdatastream.h>
14#include <qendian.h>
15#include <qstring.h>
16#include <qdatetime.h>
17#include <qrandom.h>
18#include <QtNetwork/qhttpheaders.h>
19
20#ifdef Q_OS_WIN
21#include <qmutex.h>
22#include <rpc.h>
23#endif
24
25#if QT_CONFIG(sspi) // SSPI
26#define SECURITY_WIN32 1
27#include <security.h>
28#elif QT_CONFIG(gssapi) // GSSAPI
29#if defined(Q_OS_DARWIN)
30#include <GSS/GSS.h>
31#else
32#include <gssapi/gssapi.h>
33#endif // Q_OS_DARWIN
34#endif // Q_CONFIG(sspi)
35
37
38using namespace Qt::StringLiterals;
39
41Q_LOGGING_CATEGORY(lcAuthenticator, "qt.network.authenticator");
42
43static QByteArray qNtlmPhase1();
44static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data);
45#if QT_CONFIG(sspi) // SSPI
46static bool q_SSPI_library_load();
47static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
48 QStringView host);
49static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
50 QStringView host, QByteArrayView challenge = {});
51#elif QT_CONFIG(gssapi) // GSSAPI
52static bool qGssapiTestGetCredentials(QStringView host);
53static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, QStringView host);
54static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, QByteArrayView challenge = {});
55#endif // gssapi
56
57/*!
58 \class QAuthenticator
59 \brief The QAuthenticator class provides an authentication object.
60 \since 4.3
61
62 \reentrant
63 \ingroup network
64 \inmodule QtNetwork
65
66 The QAuthenticator class is usually used in the
67 \l{QNetworkAccessManager::}{authenticationRequired()} and
68 \l{QNetworkAccessManager::}{proxyAuthenticationRequired()} signals of QNetworkAccessManager and
69 QAbstractSocket. The class provides a way to pass back the required
70 authentication information to the socket when accessing services that
71 require authentication.
72
73 QAuthenticator supports the following authentication methods:
74 \list
75 \li Basic
76 \li NTLM version 2
77 \li Digest-MD5
78 \li SPNEGO/Negotiate
79 \endlist
80
81 \target qauthenticator-options
82 \section1 Options
83
84 In addition to the username and password required for authentication, a
85 QAuthenticator object can also contain additional options. The
86 options() function can be used to query incoming options sent by
87 the server; the setOption() function can
88 be used to set outgoing options, to be processed by the authenticator
89 calculation. The options accepted and provided depend on the authentication
90 type (see method()).
91
92 The following tables list known incoming options as well as accepted
93 outgoing options. The list of incoming options is not exhaustive, since
94 servers may include additional information at any time. The list of
95 outgoing options is exhaustive, however, and no unknown options will be
96 treated or sent back to the server.
97
98 \section2 Basic
99
100 \table
101 \header \li Option \li Direction \li Type \li Description
102 \row \li \tt{realm} \li Incoming \li QString \li Contains the realm of the authentication, the same as realm()
103 \endtable
104
105 The Basic authentication mechanism supports no outgoing options.
106
107 \section2 NTLM version 2
108
109 The NTLM authentication mechanism currently supports no incoming or outgoing options.
110 On Windows, if no \a user has been set, domain\\user credentials will be searched for on the
111 local system to enable Single-Sign-On functionality.
112
113 \section2 Digest-MD5
114
115 \table
116 \header \li Option \li Direction \li Type \li Description
117 \row \li \tt{realm} \li Incoming \li QString \li Contains the realm of the authentication, the same as realm()
118 \endtable
119
120 The Digest-MD5 authentication mechanism supports no outgoing options.
121
122 \section2 SPNEGO/Negotiate
123
124 \table
125 \header
126 \li Option
127 \li Direction
128 \li Type
129 \li Description
130 \row
131 \li \tt{spn}
132 \li Outgoing
133 \li QString
134 \li Provides a custom SPN.
135 \endtable
136
137 This authentication mechanism currently supports no incoming options.
138
139 The \c{spn} property is used on Windows clients when an SSPI library is used.
140 If the property is not set, a default SPN will be used. The default SPN on
141 Windows is \c {HTTP/<hostname>}.
142
143 Other operating systems use GSSAPI libraries. For that it is expected that
144 KDC is set up, and the credentials can be fetched from it. The backend always
145 uses \c {HTTPS@<hostname>} as an SPN.
146
147 \section1 Security Considerations
148
149 QAuthenticator stores credentials such as usernames and passwords
150 internally using general-purpose data types (QString, QByteArray)
151 that do not guarantee secure erasure of their contents from memory
152 on destruction. Credential material may persist in freed heap pages,
153 core dumps, swap files, or process memory after a QAuthenticator
154 object is destroyed or cleared.
155
156 When Basic authentication is negotiated, credentials are transmitted
157 using Base64 encoding, which is trivially reversible. Basic authentication
158 should only be used over TLS-encrypted connections. QAuthenticator does not
159 enforce this requirement; applications are responsible for ensuring
160 transport security when Basic authentication is in use.
161
162 Applications with strict requirements for credential hygiene should
163 take this into account when deciding how and where to use
164 QAuthenticator.
165
166 \sa QSslSocket
167*/
168
169
170/*!
171 Constructs an empty authentication object.
172*/
173QAuthenticator::QAuthenticator()
174 : d(nullptr)
175{
176}
177
178/*!
179 Destructs the object.
180*/
181QAuthenticator::~QAuthenticator()
182{
183 if (d)
184 delete d;
185}
186
187/*!
188 Constructs a copy of \a other.
189*/
190QAuthenticator::QAuthenticator(const QAuthenticator &other)
191 : d(nullptr)
192{
193 if (other.d)
194 *this = other;
195}
196
197/*!
198 Assigns the contents of \a other to this authenticator.
199*/
200QAuthenticator &QAuthenticator::operator=(const QAuthenticator &other)
201{
202 if (d == other.d)
203 return *this;
204
205 // Do not share the d since challenge response/based changes
206 // could corrupt the internal store and different network requests
207 // can utilize different types of proxies.
208 detach();
209 if (other.d) {
210 d->user = other.d->user;
211 d->userDomain = other.d->userDomain;
212 d->workstation = other.d->workstation;
213 d->extractedUser = other.d->extractedUser;
214 d->password = other.d->password;
215 d->realm = other.d->realm;
216 d->method = other.d->method;
217 d->options = other.d->options;
218 } else if (d->phase == QAuthenticatorPrivate::Start) {
219 delete d;
220 d = nullptr;
221 }
222 return *this;
223}
224
225/*!
226 Returns \c true if this authenticator is identical to \a other; otherwise
227 returns \c false.
228*/
229bool QAuthenticator::operator==(const QAuthenticator &other) const
230{
231 if (d == other.d)
232 return true;
233 if (!d || !other.d)
234 return false;
235 return d->user == other.d->user
236 && d->password == other.d->password
237 && d->realm == other.d->realm
238 && d->method == other.d->method
239 && d->options == other.d->options;
240}
241
242/*!
243 \fn bool QAuthenticator::operator!=(const QAuthenticator &other) const
244
245 Returns \c true if this authenticator is different from \a other; otherwise
246 returns \c false.
247*/
248
249/*!
250 Returns the user used for authentication.
251*/
252QString QAuthenticator::user() const
253{
254 return d ? d->user : QString();
255}
256
257/*!
258 Sets the \a user used for authentication.
259
260 \sa QNetworkAccessManager::authenticationRequired()
261*/
262void QAuthenticator::setUser(const QString &user)
263{
264 if (!d || d->user != user) {
265 detach();
266 d->user = user;
267 d->updateCredentials();
268 }
269}
270
271/*!
272 Returns the password used for authentication.
273*/
274QString QAuthenticator::password() const
275{
276 return d ? d->password : QString();
277}
278
279/*!
280 Sets the \a password used for authentication.
281
282 \sa QNetworkAccessManager::authenticationRequired()
283*/
284void QAuthenticator::setPassword(const QString &password)
285{
286 if (!d || d->password != password) {
287 detach();
288 d->password = password;
289 }
290}
291
292/*!
293 \internal
294*/
295void QAuthenticator::detach()
296{
297 if (!d) {
298 d = new QAuthenticatorPrivate;
299 return;
300 }
301
302 if (d->phase == QAuthenticatorPrivate::Done)
303 d->phase = QAuthenticatorPrivate::Start;
304}
305
306/*!
307 Returns the realm requiring authentication.
308*/
309QString QAuthenticator::realm() const
310{
311 return d ? d->realm : QString();
312}
313
314/*!
315 \internal
316*/
317void QAuthenticator::setRealm(const QString &realm)
318{
319 if (!d || d->realm != realm) {
320 detach();
321 d->realm = realm;
322 }
323}
324
325/*!
326 \since 4.7
327 Returns the value related to option \a opt if it was set by the server.
328 See the \l{QAuthenticator#qauthenticator-options}{Options section} for
329 more information on incoming options.
330 If option \a opt isn't found, an invalid QVariant will be returned.
331
332 \sa options(), {QAuthenticator#qauthenticator-options}{QAuthenticator options}
333*/
334QVariant QAuthenticator::option(const QString &opt) const
335{
336 return d ? d->options.value(opt) : QVariant();
337}
338
339/*!
340 \since 4.7
341 Returns all incoming options set in this QAuthenticator object by parsing
342 the server reply. See the \l{QAuthenticator#qauthenticator-options}{Options section}
343 for more information on incoming options.
344
345 \sa option(), {QAuthenticator#qauthenticator-options}{QAuthenticator options}
346*/
347QVariantHash QAuthenticator::options() const
348{
349 return d ? d->options : QVariantHash();
350}
351
352/*!
353 \since 4.7
354
355 Sets the outgoing option \a opt to value \a value.
356 See the \l{QAuthenticator#qauthenticator-options}{Options section} for more information on outgoing options.
357
358 \sa options(), option(), {QAuthenticator#qauthenticator-options}{QAuthenticator options}
359*/
360void QAuthenticator::setOption(const QString &opt, const QVariant &value)
361{
362 if (option(opt) != value) {
363 detach();
364 d->options.insert(opt, value);
365 }
366}
367
368
369/*!
370 Returns \c true if the object has not been initialized. Returns
371 \c false if non-const member functions have been called, or
372 the content was constructed or copied from another initialized
373 QAuthenticator object.
374*/
375bool QAuthenticator::isNull() const
376{
377 return !d;
378}
379
380/*!
381 \since 6.11
382
383 Clears all credentials and resets the object to its default uninitialized
384 state.
385*/
386
387void QAuthenticator::clear()
388{
389 if (!d)
390 d = new QAuthenticatorPrivate;
391 else
392 *d = QAuthenticatorPrivate();
393
394 d->phase = QAuthenticatorPrivate::Done;
395}
396
397#if QT_CONFIG(sspi) // SSPI
398class QSSPIWindowsHandles
399{
400public:
401 CredHandle credHandle;
402 CtxtHandle ctxHandle;
403};
404#elif QT_CONFIG(gssapi) // GSSAPI
405class QGssApiHandles
406{
407public:
408 gss_ctx_id_t gssCtx = nullptr;
409 gss_name_t targetName;
410};
411#endif // gssapi
412
413
414QAuthenticatorPrivate::QAuthenticatorPrivate()
415 : method(None)
416 , hasFailed(false)
417 , phase(Start)
418 , nonceCount(0)
419{
420 cnonce = QCryptographicHash::hash(QByteArray::number(QRandomGenerator::system()->generate64(), 16),
421 QCryptographicHash::Md5).toHex();
422 nonceCount = 0;
423}
424
425QAuthenticatorPrivate::~QAuthenticatorPrivate() = default;
426
427void QAuthenticatorPrivate::updateCredentials()
428{
429 int separatorPosn = 0;
430
431 switch (method) {
432 case QAuthenticatorPrivate::Ntlm:
433 if ((separatorPosn = user.indexOf("\\"_L1)) != -1) {
434 //domain name is present
435 realm.clear();
436 userDomain = user.left(separatorPosn);
437 extractedUser = user.mid(separatorPosn + 1);
438 } else {
439 extractedUser = user;
440 realm.clear();
441 userDomain.clear();
442 }
443 break;
444 default:
445 userDomain.clear();
446 break;
447 }
448}
449
450bool QAuthenticatorPrivate::isMethodSupported(QByteArrayView method)
451{
452 Q_ASSERT(!method.startsWith(' ')); // This should be trimmed during parsing
453 auto separator = method.indexOf(' ');
454 if (separator != -1)
455 method = method.first(separator);
456 const auto isSupported = [method](QByteArrayView reference) {
457 return method.compare(reference, Qt::CaseInsensitive) == 0;
458 };
459 static const char methods[][10] = {
460 "basic",
461 "ntlm",
462 "digest",
463#if QT_CONFIG(sspi) || QT_CONFIG(gssapi)
464 "negotiate",
465#endif
466 };
467 return std::any_of(methods, methods + std::size(methods), isSupported);
468}
469
470static bool verifyDigestMD5(QByteArrayView value)
471{
472 auto opts = QAuthenticatorPrivate::parseDigestAuthenticationChallenge(value);
473 if (auto it = opts.constFind("algorithm"); it != opts.cend()) {
474 QByteArray alg = it.value();
475 if (alg.size() < 3)
476 return false;
477 // Just compare the first 3 characters, that way we match other subvariants as well, such as
478 // "MD5-sess"
479 auto view = QByteArrayView(alg).first(3);
480 return view.compare("MD5", Qt::CaseInsensitive) == 0;
481 }
482 return true; // assume it's ok if algorithm is not specified
483}
484
485void QAuthenticatorPrivate::parseHttpResponse(const QHttpHeaders &headers,
486 bool isProxy, QStringView host)
487{
488#if !QT_CONFIG(gssapi)
489 Q_UNUSED(host);
490#endif
491 const auto search = isProxy ? QHttpHeaders::WellKnownHeader::ProxyAuthenticate
492 : QHttpHeaders::WellKnownHeader::WWWAuthenticate;
493
494 method = None;
495 /*
496 Fun from the HTTP 1.1 specs, that we currently ignore:
497
498 User agents are advised to take special care in parsing the WWW-
499 Authenticate field value as it might contain more than one challenge,
500 or if more than one WWW-Authenticate header field is provided, the
501 contents of a challenge itself can contain a comma-separated list of
502 authentication parameters.
503 */
504
505 QByteArrayView headerVal;
506 for (const auto &current : headers.values(search)) {
507 const QLatin1StringView str(current);
508 if (method < Basic && str.startsWith("basic"_L1, Qt::CaseInsensitive)) {
509 method = Basic;
510 headerVal = QByteArrayView(current).mid(6);
511 } else if (method < Ntlm && str.startsWith("ntlm"_L1, Qt::CaseInsensitive)) {
512 method = Ntlm;
513 headerVal = QByteArrayView(current).mid(5);
514 } else if (method < DigestMd5 && str.startsWith("digest"_L1, Qt::CaseInsensitive)) {
515 // Make sure the algorithm is actually MD5 before committing to it:
516 if (!verifyDigestMD5(QByteArrayView(current).sliced(7)))
517 continue;
518
519 method = DigestMd5;
520 headerVal = QByteArrayView(current).mid(7);
521 } else if (method < Negotiate && str.startsWith("negotiate"_L1, Qt::CaseInsensitive)) {
522#if QT_CONFIG(sspi) || QT_CONFIG(gssapi) // if it's not supported then we shouldn't try to use it
523#if QT_CONFIG(gssapi)
524 // For GSSAPI there needs to be a KDC set up for the host (afaict).
525 // So let's only conditionally use it if we can fetch the credentials.
526 // Sadly it's a bit slow because it requires a DNS lookup.
527 if (!qGssapiTestGetCredentials(host))
528 continue;
529#endif
530 method = Negotiate;
531 headerVal = QByteArrayView(current).mid(10);
532#endif
533 }
534 }
535
536 // Reparse credentials since we know the method now
537 updateCredentials();
538 challenge = headerVal.trimmed().toByteArray();
539 QHash<QByteArray, QByteArray> options = parseDigestAuthenticationChallenge(challenge);
540
541 // Sets phase to Start if this updates our realm and sets the two locations where we store
542 // realm
543 auto privSetRealm = [this](QString newRealm) {
544 if (newRealm != realm) {
545 if (phase == Done)
546 phase = Start;
547 realm = newRealm;
548 this->options["realm"_L1] = realm;
549 }
550 };
551
552 switch(method) {
553 case Basic:
554 privSetRealm(QString::fromLatin1(options.value("realm")));
555 if (user.isEmpty() && password.isEmpty())
556 phase = Done;
557 break;
558 case Ntlm:
559 case Negotiate:
560 // work is done in calculateResponse()
561 break;
562 case DigestMd5: {
563 privSetRealm(QString::fromLatin1(options.value("realm")));
564 if (options.value("stale").compare("true", Qt::CaseInsensitive) == 0) {
565 phase = Start;
566 nonceCount = 0;
567 }
568 if (user.isEmpty() && password.isEmpty())
569 phase = Done;
570 break;
571 }
572 default:
573 realm.clear();
574 challenge = QByteArray();
575 phase = Invalid;
576 }
577}
578
579QByteArray QAuthenticatorPrivate::calculateResponse(QByteArrayView requestMethod,
580 QByteArrayView path, QStringView host)
581{
582#if !QT_CONFIG(sspi) && !QT_CONFIG(gssapi)
583 Q_UNUSED(host);
584#endif
585 QByteArray response;
586 QByteArrayView methodString;
587 switch(method) {
588 case QAuthenticatorPrivate::None:
589 phase = Done;
590 break;
591 case QAuthenticatorPrivate::Basic:
592 methodString = "Basic";
593 response = (user + ':'_L1 + password).toLatin1().toBase64();
594 phase = Done;
595 break;
596 case QAuthenticatorPrivate::DigestMd5:
597 methodString = "Digest";
598 response = digestMd5Response(challenge, requestMethod, path);
599 phase = Done;
600 break;
601 case QAuthenticatorPrivate::Ntlm:
602 methodString = "NTLM";
603 if (challenge.isEmpty()) {
604#if QT_CONFIG(sspi) // SSPI
605 QByteArray phase1Token;
606 if (user.isEmpty()) { // Only pull from system if no user was specified in authenticator
607 phase1Token = qSspiStartup(this, method, host);
608 } else if (!q_SSPI_library_load()) {
609 // Since we're not running qSspiStartup we have to make sure the library is loaded
610 qWarning("Failed to load the SSPI libraries");
611 return "";
612 }
613 if (!phase1Token.isEmpty()) {
614 response = phase1Token.toBase64();
615 phase = Phase2;
616 } else
617#endif
618 {
619 response = qNtlmPhase1().toBase64();
620 if (user.isEmpty())
621 phase = Done;
622 else
623 phase = Phase2;
624 }
625 } else {
626#if QT_CONFIG(sspi) // SSPI
627 QByteArray phase3Token;
628 if (sspiWindowsHandles)
629 phase3Token = qSspiContinue(this, method, host, QByteArray::fromBase64(challenge));
630 if (!phase3Token.isEmpty()) {
631 response = phase3Token.toBase64();
632 phase = Done;
633 } else
634#endif
635 {
636 response = qNtlmPhase3(this, QByteArray::fromBase64(challenge)).toBase64();
637 phase = Done;
638 }
639 challenge = "";
640 }
641
642 break;
643 case QAuthenticatorPrivate::Negotiate:
644 methodString = "Negotiate";
645 if (challenge.isEmpty()) {
646 QByteArray phase1Token;
647#if QT_CONFIG(sspi) // SSPI
648 phase1Token = qSspiStartup(this, method, host);
649#elif QT_CONFIG(gssapi) // GSSAPI
650 phase1Token = qGssapiStartup(this, host);
651#endif
652
653 if (!phase1Token.isEmpty()) {
654 response = phase1Token.toBase64();
655 phase = Phase2;
656 } else {
657 phase = Done;
658 return "";
659 }
660 } else {
661 QByteArray phase3Token;
662#if QT_CONFIG(sspi) // SSPI
663 if (sspiWindowsHandles)
664 phase3Token = qSspiContinue(this, method, host, QByteArray::fromBase64(challenge));
665#elif QT_CONFIG(gssapi) // GSSAPI
666 if (gssApiHandles)
667 phase3Token = qGssapiContinue(this, QByteArray::fromBase64(challenge));
668#endif
669 if (!phase3Token.isEmpty()) {
670 response = phase3Token.toBase64();
671 phase = Done;
672 challenge = "";
673 } else {
674 phase = Done;
675 return "";
676 }
677 }
678
679 break;
680 }
681
682 return methodString + ' ' + response;
683}
684
685
686// ---------------------------- Digest Md5 code ----------------------------------------
687
688static bool containsAuth(QByteArrayView data)
689{
690 for (auto element : QLatin1StringView(data).tokenize(','_L1)) {
691 if (element == "auth"_L1)
692 return true;
693 }
694 return false;
695}
696
697QHash<QByteArray, QByteArray>
698QAuthenticatorPrivate::parseDigestAuthenticationChallenge(QByteArrayView challenge)
699{
700 QHash<QByteArray, QByteArray> options;
701 // parse the challenge
702 const char *d = challenge.data();
703 const char *end = d + challenge.size();
704 while (d < end) {
705 while (d < end && (*d == ' ' || *d == '\n' || *d == '\r'))
706 ++d;
707 const char *start = d;
708 while (d < end && *d != '=')
709 ++d;
710 if (d >= end)
711 break;
712 QByteArrayView key = QByteArrayView(start, d - start);
713 ++d;
714 if (d >= end)
715 break;
716 bool quote = (*d == '"');
717 if (quote)
718 ++d;
719 if (d >= end)
720 break;
721 QByteArray value;
722 while (d < end) {
723 bool backslash = false;
724 if (*d == '\\' && d < end - 1) {
725 ++d;
726 backslash = true;
727 }
728 if (!backslash) {
729 if (quote) {
730 if (*d == '"')
731 break;
732 } else {
733 if (*d == ',')
734 break;
735 }
736 }
737 value += *d;
738 ++d;
739 }
740 while (d < end && *d != ',')
741 ++d;
742 if (d < end)
743 ++d;
744 options[key.toByteArray()] = std::move(value);
745 }
746
747 QByteArray qop = options.value("qop");
748 if (!qop.isEmpty()) {
749 if (!containsAuth(qop))
750 return QHash<QByteArray, QByteArray>();
751 // #### can't do auth-int currently
752// if (qop.contains("auth-int"))
753// qop = "auth-int";
754// else if (qop.contains("auth"))
755// qop = "auth";
756// else
757// qop = QByteArray();
758 options["qop"] = "auth";
759 }
760
761 return options;
762}
763
764/*
765 Digest MD5 implementation
766
767 Code taken from RFC 2617
768
769 Currently we don't support the full SASL authentication mechanism (which includes cyphers)
770*/
771
772
773/* calculate request-digest/response-digest as per HTTP Digest spec */
775 QByteArrayView alg,
776 QByteArrayView userName,
777 QByteArrayView realm,
778 QByteArrayView password,
779 QByteArrayView nonce, /* nonce from server */
780 QByteArrayView nonceCount, /* 8 hex digits */
781 QByteArrayView cNonce, /* client nonce */
782 QByteArrayView qop, /* qop-value: "", "auth", "auth-int" */
783 QByteArrayView method, /* method from the request */
784 QByteArrayView digestUri, /* requested URL */
785 QByteArrayView hEntity /* H(entity body) if qop="auth-int" */
786 )
787{
788 QCryptographicHash hash(QCryptographicHash::Md5);
789 hash.addData(userName);
790 hash.addData(":");
791 hash.addData(realm);
792 hash.addData(":");
793 hash.addData(password);
794 QByteArray ha1 = hash.result();
795 if (alg.compare("md5-sess", Qt::CaseInsensitive) == 0) {
796 hash.reset();
797 // RFC 2617 contains an error, it was:
798 // hash.addData(ha1);
799 // but according to the errata page at http://www.rfc-editor.org/errata_list.php, ID 1649, it
800 // must be the following line:
801 hash.addData(ha1.toHex());
802 hash.addData(":");
803 hash.addData(nonce);
804 hash.addData(":");
805 hash.addData(cNonce);
806 ha1 = hash.result();
807 };
808 ha1 = ha1.toHex();
809
810 // calculate H(A2)
811 hash.reset();
812 hash.addData(method);
813 hash.addData(":");
814 hash.addData(digestUri);
815 if (qop.compare("auth-int", Qt::CaseInsensitive) == 0) {
816 hash.addData(":");
817 hash.addData(hEntity);
818 }
819 QByteArray ha2hex = hash.result().toHex();
820
821 // calculate response
822 hash.reset();
823 hash.addData(ha1);
824 hash.addData(":");
825 hash.addData(nonce);
826 hash.addData(":");
827 if (!qop.isNull()) {
828 hash.addData(nonceCount);
829 hash.addData(":");
830 hash.addData(cNonce);
831 hash.addData(":");
832 hash.addData(qop);
833 hash.addData(":");
834 }
835 hash.addData(ha2hex);
836 return hash.result().toHex();
837}
838
839QByteArray QAuthenticatorPrivate::digestMd5Response(QByteArrayView challenge, QByteArrayView method,
840 QByteArrayView path)
841{
842 QHash<QByteArray,QByteArray> options = parseDigestAuthenticationChallenge(challenge);
843
844 ++nonceCount;
845 QByteArray nonceCountString = QByteArray::number(nonceCount, 16);
846 while (nonceCountString.size() < 8)
847 nonceCountString.prepend('0');
848
849 QByteArray nonce = options.value("nonce");
850 QByteArray opaque = options.value("opaque");
851 QByteArray qop = options.value("qop");
852
853// qDebug() << "calculating digest: method=" << method << "path=" << path;
854 QByteArray response = digestMd5ResponseHelper(options.value("algorithm"), user.toLatin1(),
855 realm.toLatin1(), password.toLatin1(),
856 nonce, nonceCountString,
857 cnonce, qop, method,
858 path, QByteArray());
859
860
861 QByteArray credentials;
862 credentials += "username=\"" + user.toLatin1() + "\", ";
863 credentials += "realm=\"" + realm.toLatin1() + "\", ";
864 credentials += "nonce=\"" + nonce + "\", ";
865 credentials += "uri=\"" + path + "\", ";
866 if (!opaque.isEmpty())
867 credentials += "opaque=\"" + opaque + "\", ";
868 credentials += "response=\"" + response + '"';
869 if (!options.value("algorithm").isEmpty())
870 credentials += ", algorithm=" + options.value("algorithm");
871 if (!options.value("qop").isEmpty()) {
872 credentials += ", qop=" + qop + ", ";
873 credentials += "nc=" + nonceCountString + ", ";
874 credentials += "cnonce=\"" + cnonce + '"';
875 }
876
877 return credentials;
878}
879
880// ---------------------------- End of Digest Md5 code ---------------------------------
881
882
883// ---------------------------- NTLM code ----------------------------------------------
884
885/*
886 * NTLM message flags.
887 *
888 * Copyright (c) 2004 Andrey Panin <pazke@donpac.ru>
889 *
890 * This software is released under the MIT license.
891 */
892
893/*
894 * Indicates that Unicode strings are supported for use in security
895 * buffer data.
896 */
897#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001
898
899/*
900 * Indicates that OEM strings are supported for use in security buffer data.
901 */
902#define NTLMSSP_NEGOTIATE_OEM 0x00000002
903
904/*
905 * Requests that the server's authentication realm be included in the
906 * Type 2 message.
907 */
908#define NTLMSSP_REQUEST_TARGET 0x00000004
909
910/*
911 * Specifies that authenticated communication between the client and server
912 * should carry a digital signature (message integrity).
913 */
914#define NTLMSSP_NEGOTIATE_SIGN 0x00000010
915
916/*
917 * Specifies that authenticated communication between the client and server
918 * should be encrypted (message confidentiality).
919 */
920#define NTLMSSP_NEGOTIATE_SEAL 0x00000020
921
922/*
923 * Indicates that datagram authentication is being used.
924 */
925#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040
926
927/*
928 * Indicates that the LAN Manager session key should be
929 * used for signing and sealing authenticated communications.
930 */
931#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080
932
933/*
934 * Indicates that NTLM authentication is being used.
935 */
936#define NTLMSSP_NEGOTIATE_NTLM 0x00000200
937
938/*
939 * Sent by the client in the Type 1 message to indicate that the name of the
940 * domain in which the client workstation has membership is included in the
941 * message. This is used by the server to determine whether the client is
942 * eligible for local authentication.
943 */
944#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000
945
946/*
947 * Sent by the client in the Type 1 message to indicate that the client
948 * workstation's name is included in the message. This is used by the server
949 * to determine whether the client is eligible for local authentication.
950 */
951#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000
952
953/*
954 * Sent by the server to indicate that the server and client are on the same
955 * machine. Implies that the client may use the established local credentials
956 * for authentication instead of calculating a response to the challenge.
957 */
958#define NTLMSSP_NEGOTIATE_LOCAL_CALL 0x00004000
959
960/*
961 * Indicates that authenticated communication between the client and server
962 * should be signed with a "dummy" signature.
963 */
964#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000
965
966/*
967 * Sent by the server in the Type 2 message to indicate that the target
968 * authentication realm is a domain.
969 */
970#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000
971
972/*
973 * Sent by the server in the Type 2 message to indicate that the target
974 * authentication realm is a server.
975 */
976#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000
977
978/*
979 * Sent by the server in the Type 2 message to indicate that the target
980 * authentication realm is a share. Presumably, this is for share-level
981 * authentication. Usage is unclear.
982 */
983#define NTLMSSP_TARGET_TYPE_SHARE 0x00040000
984
985/*
986 * Indicates that the NTLM2 signing and sealing scheme should be used for
987 * protecting authenticated communications. Note that this refers to a
988 * particular session security scheme, and is not related to the use of
989 * NTLMv2 authentication.
990 */
991#define NTLMSSP_NEGOTIATE_NTLM2 0x00080000
992
993/*
994 * Sent by the server in the Type 2 message to indicate that it is including
995 * a Target Information block in the message. The Target Information block
996 * is used in the calculation of the NTLMv2 response.
997 */
998#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000
999
1000/*
1001 * Indicates that 128-bit encryption is supported.
1002 */
1003#define NTLMSSP_NEGOTIATE_128 0x20000000
1004
1005/*
1006 * Indicates that the client will provide an encrypted master session key in
1007 * the "Session Key" field of the Type 3 message. This is used in signing and
1008 * sealing, and is RC4-encrypted using the previous session key as the
1009 * encryption key.
1010 */
1011#define NTLMSSP_NEGOTIATE_KEY_EXCHANGE 0x40000000
1012
1013/*
1014 * Indicates that 56-bit encryption is supported.
1015 */
1016#define NTLMSSP_NEGOTIATE_56 0x80000000
1017
1018/*
1019 * AvId values
1020 */
1021#define AVTIMESTAMP 7
1022
1023
1024//************************Global variables***************************
1025
1026const int blockSize = 64; //As per RFC2104 Block-size is 512 bits
1029// FILETIME: two 32-bit values = 8 bytes (MS-DTYP section 2.3.3)
1030static constexpr quint16 NtlmFileTimeSize = 8;
1031
1032/* usage:
1033 // fill up ctx with what we know.
1034 QByteArray response = qNtlmPhase1(ctx);
1035 // send response (b64 encoded??)
1036 // get response from server (b64 decode?)
1037 Phase2Block pb;
1038 qNtlmDecodePhase2(response, pb);
1039 response = qNtlmPhase3(ctx, pb);
1040 // send response (b64 encoded??)
1041*/
1042
1044public:
1045 QNtlmBuffer() : len(0), maxLen(0), offset(0) {}
1049 enum { Size = 8 };
1050};
1051
1052static void qStreamNtlmBuffer(QDataStream& ds, const QByteArray& s)
1053{
1054 ds.writeRawData(s.constData(), s.size());
1055}
1056
1057
1058static void qStreamNtlmString(QDataStream& ds, const QString& s, bool unicode)
1059{
1060 if (!unicode) {
1061 qStreamNtlmBuffer(ds, s.toLatin1());
1062 return;
1063 }
1064
1065 for (QChar ch : s)
1066 ds << quint16(ch.unicode());
1067}
1068
1069
1070
1071static int qEncodeNtlmBuffer(QNtlmBuffer& buf, int offset, const QByteArray& s)
1072{
1073 buf.len = s.size();
1074 buf.maxLen = buf.len;
1075 buf.offset = (offset + 1) & ~1;
1076 return buf.offset + buf.len;
1077}
1078
1079
1080static int qEncodeNtlmString(QNtlmBuffer& buf, int offset, const QString& s, bool unicode)
1081{
1082 if (!unicode)
1083 return qEncodeNtlmBuffer(buf, offset, s.toLatin1());
1084 buf.len = 2 * s.size();
1085 buf.maxLen = buf.len;
1086 buf.offset = (offset + 1) & ~1;
1087 return buf.offset + buf.len;
1088}
1089
1090
1091static QDataStream& operator<<(QDataStream& s, const QNtlmBuffer& b)
1092{
1093 s << b.len << b.maxLen << b.offset;
1094 return s;
1095}
1096
1097static QDataStream& operator>>(QDataStream& s, QNtlmBuffer& b)
1098{
1099 s >> b.len >> b.maxLen >> b.offset;
1100 return s;
1101}
1102
1103
1116
1117
1119{ // challenge
1120public:
1121 char magic[8] = {0};
1122 quint32 type = 0xffffffff;
1125 unsigned char challenge[8] = {'\0'};
1126 quint32 context[2] = {0, 0};
1128 enum { Size = 48 };
1129
1130 // extracted
1133};
1134
1135
1136
1156
1157
1158static QDataStream& operator<<(QDataStream& s, const QNtlmPhase1Block& b) {
1159 bool unicode = (b.flags & NTLMSSP_NEGOTIATE_UNICODE);
1160
1161 s.writeRawData(b.magic, sizeof(b.magic));
1162 s << b.type;
1163 s << b.flags;
1164 s << b.domain;
1165 s << b.workstation;
1166 if (!b.domainStr.isEmpty())
1167 qStreamNtlmString(s, b.domainStr, unicode);
1168 if (!b.workstationStr.isEmpty())
1169 qStreamNtlmString(s, b.workstationStr, unicode);
1170 return s;
1171}
1172
1173
1174static QDataStream& operator<<(QDataStream& s, const QNtlmPhase3Block& b) {
1175 bool unicode = (b.flags & NTLMSSP_NEGOTIATE_UNICODE);
1176 s.writeRawData(b.magic, sizeof(b.magic));
1177 s << b.type;
1178 s << b.lmResponse;
1179 s << b.ntlmResponse;
1180 s << b.domain;
1181 s << b.user;
1182 s << b.workstation;
1183 s << b.sessionKey;
1184 s << b.flags;
1185
1186 if (!b.domainStr.isEmpty())
1187 qStreamNtlmString(s, b.domainStr, unicode);
1188
1189 qStreamNtlmString(s, b.userStr, unicode);
1190
1191 if (!b.workstationStr.isEmpty())
1192 qStreamNtlmString(s, b.workstationStr, unicode);
1193
1194 // Send auth info
1195 qStreamNtlmBuffer(s, b.lmResponseBuf);
1196 qStreamNtlmBuffer(s, b.ntlmResponseBuf);
1197
1198
1199 return s;
1200}
1201
1202
1204{
1205 QByteArray rc;
1206 QDataStream ds(&rc, QIODevice::WriteOnly);
1207 ds.setByteOrder(QDataStream::LittleEndian);
1209 ds << pb;
1210 return rc;
1211}
1212
1213
1214static QByteArray qStringAsUcs2Le(const QString& src)
1215{
1216 QByteArray rc(2*src.size(), 0);
1217 unsigned short *d = (unsigned short*)rc.data();
1218 for (QChar ch : src)
1219 *d++ = qToLittleEndian(quint16(ch.unicode()));
1220
1221 return rc;
1222}
1223
1224
1226{
1227 Q_ASSERT(src.size() % 2 == 0);
1228 unsigned short *d = (unsigned short*)src.data();
1229 for (int i = 0; i < src.size() / 2; ++i) {
1230 d[i] = qFromLittleEndian(d[i]);
1231 }
1232 return QString((const QChar *)src.data(), src.size()/2);
1233}
1234
1235
1236/*********************************************************************
1237* Function Name: qEncodeHmacMd5
1238* Params:
1239* key: Type - QByteArray
1240* - It is the Authentication key
1241* message: Type - QByteArray
1242* - This is the actual message which will be encoded
1243* using HMacMd5 hash algorithm
1244*
1245* Return Value:
1246* hmacDigest: Type - QByteArray
1247*
1248* Description:
1249* This function will be used to encode the input message using
1250* HMacMd5 hash algorithm.
1251*
1252* As per the RFC2104 the HMacMd5 algorithm can be specified
1253* ---------------------------------------
1254* MD5(K XOR opad, MD5(K XOR ipad, text))
1255* ---------------------------------------
1256*
1257*********************************************************************/
1258QByteArray qEncodeHmacMd5(QByteArray &key, QByteArrayView message)
1259{
1260 Q_ASSERT_X(!(message.isEmpty()),"qEncodeHmacMd5", "Empty message check");
1261 Q_ASSERT_X(!(key.isEmpty()),"qEncodeHmacMd5", "Empty key check");
1262
1263 QCryptographicHash hash(QCryptographicHash::Md5);
1264
1265 QByteArray iKeyPad(blockSize, 0x36);
1266 QByteArray oKeyPad(blockSize, 0x5c);
1267
1268 hash.reset();
1269 // Adjust the key length to blockSize
1270
1271 if (blockSize < key.size()) {
1272 hash.addData(key);
1273 key = hash.result(); //MD5 will always return 16 bytes length output
1274 }
1275
1276 //Key will be <= 16 or 20 bytes as hash function (MD5 or SHA hash algorithms)
1277 //key size can be max of Block size only
1278 key = key.leftJustified(blockSize,0,true);
1279
1280 //iKeyPad, oKeyPad and key are all of same size "blockSize"
1281
1282 //xor of iKeyPad with Key and store the result into iKeyPad
1283 for(int i = 0; i<key.size();i++) {
1284 iKeyPad[i] = key[i]^iKeyPad[i];
1285 }
1286
1287 //xor of oKeyPad with Key and store the result into oKeyPad
1288 for(int i = 0; i<key.size();i++) {
1289 oKeyPad[i] = key[i]^oKeyPad[i];
1290 }
1291
1292 iKeyPad.append(message); // (K0 xor ipad) || text
1293
1294 hash.reset();
1295 hash.addData(iKeyPad);
1296 QByteArrayView hMsg = hash.resultView();
1297 //Digest gen after pass-1: H((K0 xor ipad)||text)
1298
1299 QByteArray hmacDigest;
1300 oKeyPad.append(hMsg);
1301 hash.reset();
1302 hash.addData(oKeyPad);
1303 hmacDigest = hash.result();
1304 // H((K0 xor opad )|| H((K0 xor ipad) || text))
1305
1306 /*hmacDigest should not be less than half the length of the HMAC output
1307 (to match the birthday attack bound) and not less than 80 bits
1308 (a suitable lower bound on the number of bits that need to be
1309 predicted by an attacker).
1310 Refer RFC 2104 for more details on truncation part */
1311
1312 /*MD5 hash always returns 16 byte digest only and HMAC-MD5 spec
1313 (RFC 2104) also says digest length should be 16 bytes*/
1314 return hmacDigest;
1315}
1316
1317static QByteArray qCreatev2Hash(const QAuthenticatorPrivate *ctx,
1318 QNtlmPhase3Block *phase3)
1319{
1320 Q_ASSERT(phase3 != nullptr);
1321 // since v2 Hash is need for both NTLMv2 and LMv2 it is calculated
1322 // only once and stored and reused
1323 if (phase3->v2Hash.size() == 0) {
1324 QCryptographicHash md4(QCryptographicHash::Md4);
1325 QByteArray passUnicode = qStringAsUcs2Le(ctx->password);
1326 md4.addData(passUnicode);
1327
1328 QByteArray hashKey = md4.result();
1329 Q_ASSERT(hashKey.size() == 16);
1330 // Assuming the user and domain is always unicode in challenge
1331 QByteArray message =
1332 qStringAsUcs2Le(ctx->extractedUser.toUpper()) +
1333 qStringAsUcs2Le(phase3->domainStr);
1334
1335 phase3->v2Hash = qEncodeHmacMd5(hashKey, message);
1336 }
1337 return phase3->v2Hash;
1338}
1339
1340static QByteArray clientChallenge(const QAuthenticatorPrivate *ctx)
1341{
1342 Q_ASSERT(ctx->cnonce.size() >= 8);
1343 QByteArray clientCh = ctx->cnonce.right(8);
1344 return clientCh;
1345}
1346
1347// caller has to ensure a valid targetInfoBuff
1348static QByteArray qExtractServerTime(const QByteArray& targetInfoBuff)
1349{
1350 QByteArray timeArray;
1351 const char *ptr = targetInfoBuff.constBegin();
1352 const char *end = targetInfoBuff.constEnd();
1353 quint16 avId;
1354 quint16 avLen;
1355
1356 while (end - ptr >= 4) {
1357 avId = qFromLittleEndian<quint16>(ptr + 0);
1358 avLen = qFromLittleEndian<quint16>(ptr + 2);
1359 ptr += 4;
1360
1361 if (avId == AVTIMESTAMP) {
1362 if (avLen != NtlmFileTimeSize)
1363 break;
1364 if (end - ptr < NtlmFileTimeSize)
1365 break;
1366
1367 timeArray.assign(ptr, ptr + NtlmFileTimeSize);
1368 break;
1369 }
1370
1371 if (avLen > end - ptr)
1372 break;
1373 ptr += avLen;
1374 }
1375 return timeArray;
1376}
1377
1378static QByteArray qEncodeNtlmv2Response(const QAuthenticatorPrivate *ctx,
1379 const QNtlmPhase2Block& ch,
1380 QNtlmPhase3Block *phase3)
1381{
1382 Q_ASSERT(phase3 != nullptr);
1383 // return value stored in phase3
1384 qCreatev2Hash(ctx, phase3);
1385
1386 QByteArray temp;
1387 QDataStream ds(&temp, QIODevice::WriteOnly);
1388 ds.setByteOrder(QDataStream::LittleEndian);
1389
1390 ds << respversion;
1391 ds << hirespversion;
1392
1393 //Reserved
1394 QByteArray reserved1(6, 0);
1395 ds.writeRawData(reserved1.constData(), reserved1.size());
1396
1397 quint64 time = 0;
1398 QByteArray timeArray;
1399
1400 if (ch.targetInfo.len)
1401 {
1402 timeArray = qExtractServerTime(ch.targetInfoBuff);
1403 }
1404
1405 //if server sends time, use it instead of current time
1406 if (timeArray.size()) {
1407 ds.writeRawData(timeArray.constData(), timeArray.size());
1408 } else {
1409 // number of seconds between 1601 and the epoch (1970)
1410 // 369 years, 89 leap years
1411 // ((369 * 365) + 89) * 24 * 3600 = 11644473600
1412 time = QDateTime::currentSecsSinceEpoch() + 11644473600;
1413
1414 // represented as 100 nano seconds
1415 time = time * Q_UINT64_C(10000000);
1416 ds << time;
1417 }
1418
1419 //8 byte client challenge
1420 QByteArray clientCh = clientChallenge(ctx);
1421 ds.writeRawData(clientCh.constData(), clientCh.size());
1422
1423 //Reserved
1424 QByteArray reserved2(4, 0);
1425 ds.writeRawData(reserved2.constData(), reserved2.size());
1426
1427 if (ch.targetInfo.len > 0) {
1428 ds.writeRawData(ch.targetInfoBuff.constData(),
1429 ch.targetInfoBuff.size());
1430 }
1431
1432 //Reserved
1433 QByteArray reserved3(4, 0);
1434 ds.writeRawData(reserved3.constData(), reserved3.size());
1435
1436 QByteArray message((const char*)ch.challenge, sizeof(ch.challenge));
1437 message.append(temp);
1438
1439 QByteArray ntChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message);
1440 ntChallengeResp.append(temp);
1441
1442 return ntChallengeResp;
1443}
1444
1445static QByteArray qEncodeLmv2Response(const QAuthenticatorPrivate *ctx,
1446 const QNtlmPhase2Block& ch,
1447 QNtlmPhase3Block *phase3)
1448{
1449 Q_ASSERT(phase3 != nullptr);
1450 // return value stored in phase3
1451 qCreatev2Hash(ctx, phase3);
1452
1453 QByteArray message((const char*)ch.challenge, sizeof(ch.challenge));
1454 QByteArray clientCh = clientChallenge(ctx);
1455
1456 message.append(clientCh);
1457
1458 QByteArray lmChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message);
1459 lmChallengeResp.append(clientCh);
1460
1461 return lmChallengeResp;
1462}
1463
1464static bool qNtlmDecodePhase2(const QByteArray& data, QNtlmPhase2Block& ch)
1465{
1466 if (data.size() < QNtlmPhase2Block::Size)
1467 return false;
1468
1469
1470 QDataStream ds(data);
1471 ds.setByteOrder(QDataStream::LittleEndian);
1472 if (ds.readRawData(ch.magic, 8) < 8)
1473 return false;
1474 if (strncmp(ch.magic, "NTLMSSP", 8) != 0)
1475 return false;
1476
1477 ds >> ch.type;
1478 if (ch.type != 2)
1479 return false;
1480
1481 ds >> ch.targetName;
1482 ds >> ch.flags;
1483 if (ds.readRawData((char *)ch.challenge, 8) < 8)
1484 return false;
1485 ds >> ch.context[0] >> ch.context[1];
1486 ds >> ch.targetInfo;
1487
1488 if (ch.targetName.len > 0) {
1489 qsizetype total;
1490 if (qAddOverflow(qsizetype(ch.targetName.offset), qsizetype(ch.targetName.len), &total))
1491 return false;
1492 if (total > data.size())
1493 return false;
1494
1495 ch.targetNameStr = qStringFromUcs2Le(data.mid(ch.targetName.offset, ch.targetName.len));
1496 }
1497
1498 if (ch.targetInfo.len > 0) {
1499 qsizetype total;
1500 if (qAddOverflow(qsizetype(ch.targetInfo.offset), qsizetype(ch.targetInfo.len), &total))
1501 return false;
1502 if (total > data.size())
1503 return false;
1504
1505 ch.targetInfoBuff = data.mid(ch.targetInfo.offset, ch.targetInfo.len);
1506 }
1507
1508 return true;
1509}
1510
1511
1512static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data)
1513{
1515 if (!qNtlmDecodePhase2(phase2data, ch))
1516 return QByteArray();
1517
1518 QByteArray rc;
1519 QDataStream ds(&rc, QIODevice::WriteOnly);
1520 ds.setByteOrder(QDataStream::LittleEndian);
1522
1523 // set NTLMv2
1524 if (ch.flags & NTLMSSP_NEGOTIATE_NTLM2)
1525 pb.flags |= NTLMSSP_NEGOTIATE_NTLM2;
1526
1527 // set Always Sign
1528 if (ch.flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)
1530
1531 bool unicode = ch.flags & NTLMSSP_NEGOTIATE_UNICODE;
1532
1533 if (unicode)
1534 pb.flags |= NTLMSSP_NEGOTIATE_UNICODE;
1535 else
1536 pb.flags |= NTLMSSP_NEGOTIATE_OEM;
1537
1538
1539 int offset = QNtlmPhase3Block::Size;
1540
1541 // for kerberos style user@domain logins, NTLM domain string should be left empty
1542 if (ctx->userDomain.isEmpty() && !ctx->extractedUser.contains(u'@')) {
1543 offset = qEncodeNtlmString(pb.domain, offset, ch.targetNameStr, unicode);
1544 pb.domainStr = ch.targetNameStr;
1545 } else {
1546 offset = qEncodeNtlmString(pb.domain, offset, ctx->userDomain, unicode);
1547 pb.domainStr = ctx->userDomain;
1548 }
1549
1550 offset = qEncodeNtlmString(pb.user, offset, ctx->extractedUser, unicode);
1551 pb.userStr = ctx->extractedUser;
1552
1553 offset = qEncodeNtlmString(pb.workstation, offset, ctx->workstation, unicode);
1554 pb.workstationStr = ctx->workstation;
1555
1556 // Get LM response
1557 if (ch.targetInfo.len > 0) {
1558 pb.lmResponseBuf = QByteArray();
1559 } else {
1560 pb.lmResponseBuf = qEncodeLmv2Response(ctx, ch, &pb);
1561 }
1562 offset = qEncodeNtlmBuffer(pb.lmResponse, offset, pb.lmResponseBuf);
1563
1564 // Get NTLM response
1565 pb.ntlmResponseBuf = qEncodeNtlmv2Response(ctx, ch, &pb);
1566 offset = qEncodeNtlmBuffer(pb.ntlmResponse, offset, pb.ntlmResponseBuf);
1567
1568
1569 // Encode and send
1570 ds << pb;
1571
1572 return rc;
1573}
1574
1575// ---------------------------- End of NTLM code ---------------------------------------
1576
1577#if QT_CONFIG(sspi) // SSPI
1578// ---------------------------- SSPI code ----------------------------------------------
1579// See http://davenport.sourceforge.net/ntlm.html
1580// and libcurl http_ntlm.c
1581
1582// Pointer to SSPI dispatch table
1583static PSecurityFunctionTableW pSecurityFunctionTable = nullptr;
1584
1585static bool q_SSPI_library_load()
1586{
1587 Q_CONSTINIT static QBasicMutex mutex;
1588 QMutexLocker l(&mutex);
1589
1590 if (pSecurityFunctionTable == nullptr)
1591 pSecurityFunctionTable = InitSecurityInterfaceW();
1592
1593 if (pSecurityFunctionTable == nullptr)
1594 return false;
1595
1596 return true;
1597}
1598
1599static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
1600 QStringView host)
1601{
1602 if (!q_SSPI_library_load())
1603 return QByteArray();
1604
1605 TimeStamp expiry; // For Windows 9x compatibility of SSPI calls
1606
1607 if (!ctx->sspiWindowsHandles)
1608 ctx->sspiWindowsHandles.reset(new QSSPIWindowsHandles);
1609 SecInvalidateHandle(&ctx->sspiWindowsHandles->credHandle);
1610 SecInvalidateHandle(&ctx->sspiWindowsHandles->ctxHandle);
1611
1612 SEC_WINNT_AUTH_IDENTITY auth;
1613 auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
1614 bool useAuth = false;
1615 if (method == QAuthenticatorPrivate::Negotiate && !ctx->user.isEmpty()) {
1616 auth.Domain = const_cast<ushort *>(reinterpret_cast<const ushort *>(ctx->userDomain.constData()));
1617 auth.DomainLength = ctx->userDomain.size();
1618 auth.User = const_cast<ushort *>(reinterpret_cast<const ushort *>(ctx->user.constData()));
1619 auth.UserLength = ctx->user.size();
1620 auth.Password = const_cast<ushort *>(reinterpret_cast<const ushort *>(ctx->password.constData()));
1621 auth.PasswordLength = ctx->password.size();
1622 useAuth = true;
1623 }
1624
1625 // Acquire our credentials handle
1626 SECURITY_STATUS secStatus = pSecurityFunctionTable->AcquireCredentialsHandle(
1627 nullptr,
1628 (SEC_WCHAR *)(method == QAuthenticatorPrivate::Negotiate ? L"Negotiate" : L"NTLM"),
1629 SECPKG_CRED_OUTBOUND, nullptr, useAuth ? &auth : nullptr, nullptr, nullptr,
1630 &ctx->sspiWindowsHandles->credHandle, &expiry
1631 );
1632 if (secStatus != SEC_E_OK) {
1633 ctx->sspiWindowsHandles.reset(nullptr);
1634 return QByteArray();
1635 }
1636
1637 return qSspiContinue(ctx, method, host);
1638}
1639
1640static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
1641 QStringView host, QByteArrayView challenge)
1642{
1643 QByteArray result;
1644 SecBuffer challengeBuf;
1645 SecBuffer responseBuf;
1646 SecBufferDesc challengeDesc;
1647 SecBufferDesc responseDesc;
1648 unsigned long attrs;
1649 TimeStamp expiry; // For Windows 9x compatibility of SSPI calls
1650
1651 if (!challenge.isEmpty())
1652 {
1653 // Setup the challenge "input" security buffer
1654 challengeDesc.ulVersion = SECBUFFER_VERSION;
1655 challengeDesc.cBuffers = 1;
1656 challengeDesc.pBuffers = &challengeBuf;
1657 challengeBuf.BufferType = SECBUFFER_TOKEN;
1658 challengeBuf.pvBuffer = (PVOID)(challenge.data());
1659 challengeBuf.cbBuffer = challenge.length();
1660 }
1661
1662 // Setup the response "output" security buffer
1663 responseDesc.ulVersion = SECBUFFER_VERSION;
1664 responseDesc.cBuffers = 1;
1665 responseDesc.pBuffers = &responseBuf;
1666 responseBuf.BufferType = SECBUFFER_TOKEN;
1667 responseBuf.pvBuffer = nullptr;
1668 responseBuf.cbBuffer = 0;
1669
1670 // Calculate target (SPN for Negotiate, empty for NTLM)
1671 QString targetName = ctx->options.value("spn"_L1).toString();
1672 if (targetName.isEmpty())
1673 targetName = "HTTP/"_L1 + host;
1674 const std::wstring targetNameW = (method == QAuthenticatorPrivate::Negotiate
1675 ? targetName : QString()).toStdWString();
1676
1677 // Generate our challenge-response message
1678 SECURITY_STATUS secStatus = pSecurityFunctionTable->InitializeSecurityContext(
1679 &ctx->sspiWindowsHandles->credHandle,
1680 !challenge.isEmpty() ? &ctx->sspiWindowsHandles->ctxHandle : nullptr,
1681 const_cast<wchar_t*>(targetNameW.data()),
1682 ISC_REQ_ALLOCATE_MEMORY,
1683 0, SECURITY_NATIVE_DREP,
1684 !challenge.isEmpty() ? &challengeDesc : nullptr,
1685 0, &ctx->sspiWindowsHandles->ctxHandle,
1686 &responseDesc, &attrs,
1687 &expiry
1688 );
1689
1690 if (secStatus == SEC_I_COMPLETE_NEEDED || secStatus == SEC_I_COMPLETE_AND_CONTINUE) {
1691 secStatus = pSecurityFunctionTable->CompleteAuthToken(&ctx->sspiWindowsHandles->ctxHandle,
1692 &responseDesc);
1693 }
1694
1695 if (secStatus != SEC_I_COMPLETE_AND_CONTINUE && secStatus != SEC_I_CONTINUE_NEEDED) {
1696 pSecurityFunctionTable->FreeCredentialsHandle(&ctx->sspiWindowsHandles->credHandle);
1697 pSecurityFunctionTable->DeleteSecurityContext(&ctx->sspiWindowsHandles->ctxHandle);
1698 ctx->sspiWindowsHandles.reset(nullptr);
1699 }
1700
1701 result = QByteArray((const char*)responseBuf.pvBuffer, responseBuf.cbBuffer);
1702 pSecurityFunctionTable->FreeContextBuffer(responseBuf.pvBuffer);
1703
1704 return result;
1705}
1706
1707// ---------------------------- End of SSPI code ---------------------------------------
1708
1709#elif QT_CONFIG(gssapi) // GSSAPI
1710
1711// ---------------------------- GSSAPI code ----------------------------------------------
1712// See postgres src/interfaces/libpq/fe-auth.c
1713
1714// Fetch all errors of a specific type
1715static void q_GSSAPI_error_int(const char *message, OM_uint32 stat, int type)
1716{
1717 OM_uint32 minStat, msgCtx = 0;
1718 gss_buffer_desc msg;
1719
1720 do {
1721 gss_display_status(&minStat, stat, type, GSS_C_NO_OID, &msgCtx, &msg);
1722 qCDebug(lcAuthenticator) << message << ": " << reinterpret_cast<const char*>(msg.value);
1723 gss_release_buffer(&minStat, &msg);
1724 } while (msgCtx);
1725}
1726
1727// GSSAPI errors contain two parts; extract both
1728static void q_GSSAPI_error(const char *message, OM_uint32 majStat, OM_uint32 minStat)
1729{
1730 // Fetch major error codes
1731 q_GSSAPI_error_int(message, majStat, GSS_C_GSS_CODE);
1732
1733 // Add the minor codes as well
1734 q_GSSAPI_error_int(message, minStat, GSS_C_MECH_CODE);
1735}
1736
1737static gss_name_t qGSsapiGetServiceName(QStringView host)
1738{
1739 QByteArray serviceName = "HTTPS@" + host.toLocal8Bit();
1740 gss_buffer_desc nameDesc = {static_cast<std::size_t>(serviceName.size()), serviceName.data()};
1741
1742 gss_name_t importedName;
1743 OM_uint32 minStat;
1744 OM_uint32 majStat = gss_import_name(&minStat, &nameDesc,
1745 GSS_C_NT_HOSTBASED_SERVICE, &importedName);
1746
1747 if (majStat != GSS_S_COMPLETE) {
1748 q_GSSAPI_error("gss_import_name error", majStat, minStat);
1749 return nullptr;
1750 }
1751 return importedName;
1752}
1753
1754// Send initial GSS authentication token
1755static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, QStringView host)
1756{
1757 if (!ctx->gssApiHandles)
1758 ctx->gssApiHandles.reset(new QGssApiHandles);
1759
1760 // Convert target name to internal form
1761 gss_name_t name = qGSsapiGetServiceName(host);
1762 if (name == nullptr) {
1763 ctx->gssApiHandles.reset(nullptr);
1764 return QByteArray();
1765 }
1766 ctx->gssApiHandles->targetName = name;
1767
1768 // Call qGssapiContinue with GSS_C_NO_CONTEXT to get initial packet
1769 ctx->gssApiHandles->gssCtx = GSS_C_NO_CONTEXT;
1770 return qGssapiContinue(ctx);
1771}
1772
1773// Continue GSS authentication with next token as needed
1774static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, QByteArrayView challenge)
1775{
1776 OM_uint32 majStat, minStat, ignored;
1777 QByteArray result;
1778 gss_buffer_desc inBuf = {0, nullptr}; // GSS input token
1779 gss_buffer_desc outBuf; // GSS output token
1780
1781 if (!challenge.isEmpty()) {
1782 inBuf.value = const_cast<char*>(challenge.data());
1783 inBuf.length = challenge.size();
1784 }
1785
1786 majStat = gss_init_sec_context(&minStat,
1787 GSS_C_NO_CREDENTIAL,
1788 &ctx->gssApiHandles->gssCtx,
1789 ctx->gssApiHandles->targetName,
1790 GSS_C_NO_OID,
1791 GSS_C_MUTUAL_FLAG,
1792 0,
1793 GSS_C_NO_CHANNEL_BINDINGS,
1794 challenge.isEmpty() ? GSS_C_NO_BUFFER : &inBuf,
1795 nullptr,
1796 &outBuf,
1797 nullptr,
1798 nullptr);
1799
1800 if (outBuf.length != 0)
1801 result = QByteArray(reinterpret_cast<const char*>(outBuf.value), outBuf.length);
1802 gss_release_buffer(&ignored, &outBuf);
1803
1804 if (majStat != GSS_S_COMPLETE && majStat != GSS_S_CONTINUE_NEEDED) {
1805 q_GSSAPI_error("gss_init_sec_context error", majStat, minStat);
1806 gss_release_name(&ignored, &ctx->gssApiHandles->targetName);
1807 if (ctx->gssApiHandles->gssCtx)
1808 gss_delete_sec_context(&ignored, &ctx->gssApiHandles->gssCtx, GSS_C_NO_BUFFER);
1809 ctx->gssApiHandles.reset(nullptr);
1810 }
1811
1812 if (majStat == GSS_S_COMPLETE) {
1813 gss_release_name(&ignored, &ctx->gssApiHandles->targetName);
1814 ctx->gssApiHandles.reset(nullptr);
1815 }
1816
1817 return result;
1818}
1819
1820static bool qGssapiTestGetCredentials(QStringView host)
1821{
1822 gss_name_t serviceName = qGSsapiGetServiceName(host);
1823 if (!serviceName)
1824 return false; // Something was wrong with the service name, so skip this
1825 OM_uint32 minStat;
1826 gss_cred_id_t cred;
1827 OM_uint32 majStat = gss_acquire_cred(&minStat, serviceName, GSS_C_INDEFINITE,
1828 GSS_C_NO_OID_SET, GSS_C_INITIATE, &cred, nullptr,
1829 nullptr);
1830
1831 OM_uint32 ignored;
1832 gss_release_name(&ignored, &serviceName);
1833 gss_release_cred(&ignored, &cred);
1834
1835 if (majStat != GSS_S_COMPLETE) {
1836 q_GSSAPI_error("gss_acquire_cred", majStat, minStat);
1837 return false;
1838 }
1839 return true;
1840}
1841
1842// ---------------------------- End of GSSAPI code ----------------------------------------------
1843
1844#endif // gssapi
1845
1846QT_END_NAMESPACE
1847
1848#include "moc_qauthenticator.cpp"
unsigned char challenge[8]
Combined button and popup list for selecting options.
static QByteArray clientChallenge(const QAuthenticatorPrivate *ctx)
const quint8 respversion
static QByteArray qNtlmPhase1()
#define NTLMSSP_NEGOTIATE_NTLM2
#define NTLMSSP_NEGOTIATE_TARGET_INFO
static constexpr quint16 NtlmFileTimeSize
static QByteArray qStringAsUcs2Le(const QString &src)
static QByteArray qEncodeLmv2Response(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block &ch, QNtlmPhase3Block *phase3)
static bool verifyDigestMD5(QByteArrayView value)
static bool containsAuth(QByteArrayView data)
static int qEncodeNtlmString(QNtlmBuffer &buf, int offset, const QString &s, bool unicode)
static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray &phase2data)
QByteArray qEncodeHmacMd5(QByteArray &key, QByteArrayView message)
#define NTLMSSP_NEGOTIATE_OEM
static QByteArray qEncodeNtlmv2Response(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block &ch, QNtlmPhase3Block *phase3)
static QByteArray digestMd5ResponseHelper(QByteArrayView alg, QByteArrayView userName, QByteArrayView realm, QByteArrayView password, QByteArrayView nonce, QByteArrayView nonceCount, QByteArrayView cNonce, QByteArrayView qop, QByteArrayView method, QByteArrayView digestUri, QByteArrayView hEntity)
static QDataStream & operator>>(QDataStream &s, QNtlmBuffer &b)
static QByteArray qCreatev2Hash(const QAuthenticatorPrivate *ctx, QNtlmPhase3Block *phase3)
static void qStreamNtlmBuffer(QDataStream &ds, const QByteArray &s)
#define NTLMSSP_REQUEST_TARGET
static QString qStringFromUcs2Le(QByteArray src)
static void qStreamNtlmString(QDataStream &ds, const QString &s, bool unicode)
#define NTLMSSP_NEGOTIATE_NTLM
static QByteArray qExtractServerTime(const QByteArray &targetInfoBuff)
#define NTLMSSP_NEGOTIATE_UNICODE
static int qEncodeNtlmBuffer(QNtlmBuffer &buf, int offset, const QByteArray &s)
const quint8 hirespversion
const int blockSize
#define AVTIMESTAMP
#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN
static bool qNtlmDecodePhase2(const QByteArray &data, QNtlmPhase2Block &ch)
#define Q_LOGGING_CATEGORY(name,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)