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

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