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