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

source code of qtbase/src/network/kernel/qauthenticator.cpp