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