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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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