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

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