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