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