diff options
Diffstat (limited to 'src/network/kernel/qauthenticator.cpp')
-rw-r--r-- | src/network/kernel/qauthenticator.cpp | 461 |
1 files changed, 287 insertions, 174 deletions
diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp index 3ac54605e4..e42450d7e5 100644 --- a/src/network/kernel/qauthenticator.cpp +++ b/src/network/kernel/qauthenticator.cpp @@ -1,45 +1,10 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <qauthenticator.h> #include <qauthenticator_p.h> #include <qdebug.h> +#include <qloggingcategory.h> #include <qhash.h> #include <qbytearray.h> #include <qcryptographichash.h> @@ -49,6 +14,7 @@ #include <qstring.h> #include <qdatetime.h> #include <qrandom.h> +#include <QtNetwork/qhttpheaders.h> #ifdef Q_OS_WIN #include <qmutex.h> @@ -68,18 +34,23 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + +Q_DECLARE_LOGGING_CATEGORY(lcAuthenticator); +Q_LOGGING_CATEGORY(lcAuthenticator, "qt.network.authenticator"); + static QByteArray qNtlmPhase1(); static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data); #if QT_CONFIG(sspi) // SSPI static bool q_SSPI_library_load(); static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method, - const QString& host); + QStringView host); static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method, - const QString& host, const QByteArray& challenge = QByteArray()); + QStringView host, QByteArrayView challenge = {}); #elif QT_CONFIG(gssapi) // GSSAPI -static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, const QString& host); -static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, - const QByteArray& challenge = QByteArray()); +static bool qGssapiTestGetCredentials(QStringView host); +static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, QStringView host); +static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, QByteArrayView challenge = {}); #endif // gssapi /*! @@ -149,7 +120,28 @@ static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, \section2 SPNEGO/Negotiate - This authentication mechanism currently supports no incoming or outgoing options. + \table + \header + \li Option + \li Direction + \li Type + \li Description + \row + \li \tt{spn} + \li Outgoing + \li QString + \li Provides a custom SPN. + \endtable + + This authentication mechanism currently supports no incoming options. + + The \c{spn} property is used on Windows clients when an SSPI library is used. + If the property is not set, a default SPN will be used. The default SPN on + Windows is \c {HTTP/<hostname>}. + + Other operating systems use GSSAPI libraries. For that it is expected that + KDC is set up, and the credentials can be fetched from it. The backend always + uses \c {HTTPS@<hostname>} as an SPN. \sa QSslSocket */ @@ -190,7 +182,7 @@ QAuthenticator &QAuthenticator::operator=(const QAuthenticator &other) if (d == other.d) return *this; - // Do not share the d since challange reponse/based changes + // Do not share the d since challenge response/based changes // could corrupt the internal store and different network requests // can utilize different types of proxies. detach(); @@ -249,9 +241,11 @@ QString QAuthenticator::user() const */ void QAuthenticator::setUser(const QString &user) { - detach(); - d->user = user; - d->updateCredentials(); + if (!d || d->user != user) { + detach(); + d->user = user; + d->updateCredentials(); + } } /*! @@ -269,8 +263,10 @@ QString QAuthenticator::password() const */ void QAuthenticator::setPassword(const QString &password) { - detach(); - d->password = password; + if (!d || d->password != password) { + detach(); + d->password = password; + } } /*! @@ -300,8 +296,10 @@ QString QAuthenticator::realm() const */ void QAuthenticator::setRealm(const QString &realm) { - detach(); - d->realm = realm; + if (!d || d->realm != realm) { + detach(); + d->realm = realm; + } } /*! @@ -341,8 +339,10 @@ QVariantHash QAuthenticator::options() const */ void QAuthenticator::setOption(const QString &opt, const QVariant &value) { - detach(); - d->options.insert(opt, value); + if (option(opt) != value) { + detach(); + d->options.insert(opt, value); + } } @@ -393,7 +393,7 @@ void QAuthenticatorPrivate::updateCredentials() switch (method) { case QAuthenticatorPrivate::Ntlm: - if ((separatorPosn = user.indexOf(QLatin1String("\\"))) != -1) { + if ((separatorPosn = user.indexOf("\\"_L1)) != -1) { //domain name is present realm.clear(); userDomain = user.left(separatorPosn); @@ -410,9 +410,49 @@ void QAuthenticatorPrivate::updateCredentials() } } -void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByteArray> > &values, bool isProxy) +bool QAuthenticatorPrivate::isMethodSupported(QByteArrayView method) +{ + Q_ASSERT(!method.startsWith(' ')); // This should be trimmed during parsing + auto separator = method.indexOf(' '); + if (separator != -1) + method = method.first(separator); + const auto isSupported = [method](QByteArrayView reference) { + return method.compare(reference, Qt::CaseInsensitive) == 0; + }; + static const char methods[][10] = { + "basic", + "ntlm", + "digest", +#if QT_CONFIG(sspi) || QT_CONFIG(gssapi) + "negotiate", +#endif + }; + return std::any_of(methods, methods + std::size(methods), isSupported); +} + +static bool verifyDigestMD5(QByteArrayView value) +{ + auto opts = QAuthenticatorPrivate::parseDigestAuthenticationChallenge(value); + if (auto it = opts.constFind("algorithm"); it != opts.cend()) { + QByteArray alg = it.value(); + if (alg.size() < 3) + return false; + // Just compare the first 3 characters, that way we match other subvariants as well, such as + // "MD5-sess" + auto view = QByteArrayView(alg).first(3); + return view.compare("MD5", Qt::CaseInsensitive) == 0; + } + return true; // assume it's ok if algorithm is not specified +} + +void QAuthenticatorPrivate::parseHttpResponse(const QHttpHeaders &headers, + bool isProxy, QStringView host) { - const char *search = isProxy ? "proxy-authenticate" : "www-authenticate"; +#if !QT_CONFIG(gssapi) + Q_UNUSED(host); +#endif + const auto search = isProxy ? QHttpHeaders::WellKnownHeader::ProxyAuthenticate + : QHttpHeaders::WellKnownHeader::WWWAuthenticate; method = None; /* @@ -425,35 +465,56 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt authentication parameters. */ - QByteArray headerVal; - for (int i = 0; i < values.size(); ++i) { - const QPair<QByteArray, QByteArray> ¤t = values.at(i); - if (current.first.compare(search, Qt::CaseInsensitive) != 0) - continue; - QByteArray str = current.second.toLower(); - if (method < Basic && str.startsWith("basic")) { + QByteArrayView headerVal; + for (const auto ¤t : headers.values(search)) { + const QLatin1StringView str(current); + if (method < Basic && str.startsWith("basic"_L1, Qt::CaseInsensitive)) { method = Basic; - headerVal = current.second.mid(6); - } else if (method < Ntlm && str.startsWith("ntlm")) { + headerVal = QByteArrayView(current).mid(6); + } else if (method < Ntlm && str.startsWith("ntlm"_L1, Qt::CaseInsensitive)) { method = Ntlm; - headerVal = current.second.mid(5); - } else if (method < DigestMd5 && str.startsWith("digest")) { + headerVal = QByteArrayView(current).mid(5); + } else if (method < DigestMd5 && str.startsWith("digest"_L1, Qt::CaseInsensitive)) { + // Make sure the algorithm is actually MD5 before committing to it: + if (!verifyDigestMD5(QByteArrayView(current).sliced(7))) + continue; + method = DigestMd5; - headerVal = current.second.mid(7); - } else if (method < Negotiate && str.startsWith("negotiate")) { + headerVal = QByteArrayView(current).mid(7); + } else if (method < Negotiate && str.startsWith("negotiate"_L1, Qt::CaseInsensitive)) { +#if QT_CONFIG(sspi) || QT_CONFIG(gssapi) // if it's not supported then we shouldn't try to use it +#if QT_CONFIG(gssapi) + // For GSSAPI there needs to be a KDC set up for the host (afaict). + // So let's only conditionally use it if we can fetch the credentials. + // Sadly it's a bit slow because it requires a DNS lookup. + if (!qGssapiTestGetCredentials(host)) + continue; +#endif method = Negotiate; - headerVal = current.second.mid(10); + headerVal = QByteArrayView(current).mid(10); +#endif } } // Reparse credentials since we know the method now updateCredentials(); - challenge = headerVal.trimmed(); + challenge = headerVal.trimmed().toByteArray(); QHash<QByteArray, QByteArray> options = parseDigestAuthenticationChallenge(challenge); + // Sets phase to Start if this updates our realm and sets the two locations where we store + // realm + auto privSetRealm = [this](QString newRealm) { + if (newRealm != realm) { + if (phase == Done) + phase = Start; + realm = newRealm; + this->options["realm"_L1] = realm; + } + }; + switch(method) { case Basic: - this->options[QLatin1String("realm")] = realm = QString::fromLatin1(options.value("realm")); + privSetRealm(QString::fromLatin1(options.value("realm"))); if (user.isEmpty() && password.isEmpty()) phase = Done; break; @@ -462,9 +523,11 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt // work is done in calculateResponse() break; case DigestMd5: { - this->options[QLatin1String("realm")] = realm = QString::fromLatin1(options.value("realm")); - if (options.value("stale").compare("true", Qt::CaseInsensitive) == 0) + privSetRealm(QString::fromLatin1(options.value("realm"))); + if (options.value("stale").compare("true", Qt::CaseInsensitive) == 0) { phase = Start; + nonceCount = 0; + } if (user.isEmpty() && password.isEmpty()) phase = Done; break; @@ -476,22 +539,21 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt } } -QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMethod, const QByteArray &path, const QString& host) +QByteArray QAuthenticatorPrivate::calculateResponse(QByteArrayView requestMethod, + QByteArrayView path, QStringView host) { #if !QT_CONFIG(sspi) && !QT_CONFIG(gssapi) Q_UNUSED(host); #endif QByteArray response; - const char* methodString = nullptr; + QByteArrayView methodString; switch(method) { case QAuthenticatorPrivate::None: - methodString = ""; phase = Done; break; case QAuthenticatorPrivate::Basic: methodString = "Basic"; - response = user.toLatin1() + ':' + password.toLatin1(); - response = response.toBase64(); + response = (user + ':'_L1 + password).toLatin1().toBase64(); phase = Done; break; case QAuthenticatorPrivate::DigestMd5: @@ -556,43 +618,59 @@ QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMet phase = Phase2; } else { phase = Done; + return ""; } } else { QByteArray phase3Token; #if QT_CONFIG(sspi) // SSPI - phase3Token = qSspiContinue(this, method, host, QByteArray::fromBase64(challenge)); + if (sspiWindowsHandles) + phase3Token = qSspiContinue(this, method, host, QByteArray::fromBase64(challenge)); #elif QT_CONFIG(gssapi) // GSSAPI - phase3Token = qGssapiContinue(this, QByteArray::fromBase64(challenge)); + if (gssApiHandles) + phase3Token = qGssapiContinue(this, QByteArray::fromBase64(challenge)); #endif if (!phase3Token.isEmpty()) { response = phase3Token.toBase64(); phase = Done; challenge = ""; + } else { + phase = Done; + return ""; } } break; } - return QByteArray::fromRawData(methodString, qstrlen(methodString)) + ' ' + response; + return methodString + ' ' + response; } // ---------------------------- Digest Md5 code ---------------------------------------- -QHash<QByteArray, QByteArray> QAuthenticatorPrivate::parseDigestAuthenticationChallenge(const QByteArray &challenge) +static bool containsAuth(QByteArrayView data) +{ + for (auto element : QLatin1StringView(data).tokenize(','_L1)) { + if (element == "auth"_L1) + return true; + } + return false; +} + +QHash<QByteArray, QByteArray> +QAuthenticatorPrivate::parseDigestAuthenticationChallenge(QByteArrayView challenge) { QHash<QByteArray, QByteArray> options; // parse the challenge - const char *d = challenge.constData(); - const char *end = d + challenge.length(); + const char *d = challenge.data(); + const char *end = d + challenge.size(); while (d < end) { while (d < end && (*d == ' ' || *d == '\n' || *d == '\r')) ++d; const char *start = d; while (d < end && *d != '=') ++d; - QByteArray key = QByteArray(start, d - start); + QByteArrayView key = QByteArrayView(start, d - start); ++d; if (d >= end) break; @@ -601,7 +679,6 @@ QHash<QByteArray, QByteArray> QAuthenticatorPrivate::parseDigestAuthenticationCh ++d; if (d >= end) break; - start = d; QByteArray value; while (d < end) { bool backslash = false; @@ -624,13 +701,12 @@ QHash<QByteArray, QByteArray> QAuthenticatorPrivate::parseDigestAuthenticationCh while (d < end && *d != ',') ++d; ++d; - options[key] = value; + options[key.toByteArray()] = std::move(value); } QByteArray qop = options.value("qop"); if (!qop.isEmpty()) { - QList<QByteArray> qopoptions = qop.split(','); - if (!qopoptions.contains("auth")) + if (!containsAuth(qop)) return QHash<QByteArray, QByteArray>(); // #### can't do auth-int currently // if (qop.contains("auth-int")) @@ -656,24 +732,24 @@ QHash<QByteArray, QByteArray> QAuthenticatorPrivate::parseDigestAuthenticationCh /* calculate request-digest/response-digest as per HTTP Digest spec */ static QByteArray digestMd5ResponseHelper( - const QByteArray &alg, - const QByteArray &userName, - const QByteArray &realm, - const QByteArray &password, - const QByteArray &nonce, /* nonce from server */ - const QByteArray &nonceCount, /* 8 hex digits */ - const QByteArray &cNonce, /* client nonce */ - const QByteArray &qop, /* qop-value: "", "auth", "auth-int" */ - const QByteArray &method, /* method from the request */ - const QByteArray &digestUri, /* requested URL */ - const QByteArray &hEntity /* H(entity body) if qop="auth-int" */ + QByteArrayView alg, + QByteArrayView userName, + QByteArrayView realm, + QByteArrayView password, + QByteArrayView nonce, /* nonce from server */ + QByteArrayView nonceCount, /* 8 hex digits */ + QByteArrayView cNonce, /* client nonce */ + QByteArrayView qop, /* qop-value: "", "auth", "auth-int" */ + QByteArrayView method, /* method from the request */ + QByteArrayView digestUri, /* requested URL */ + QByteArrayView hEntity /* H(entity body) if qop="auth-int" */ ) { QCryptographicHash hash(QCryptographicHash::Md5); hash.addData(userName); - hash.addData(":", 1); + hash.addData(":"); hash.addData(realm); - hash.addData(":", 1); + hash.addData(":"); hash.addData(password); QByteArray ha1 = hash.result(); if (alg.compare("md5-sess", Qt::CaseInsensitive) == 0) { @@ -683,9 +759,9 @@ static QByteArray digestMd5ResponseHelper( // but according to the errata page at http://www.rfc-editor.org/errata_list.php, ID 1649, it // must be the following line: hash.addData(ha1.toHex()); - hash.addData(":", 1); + hash.addData(":"); hash.addData(nonce); - hash.addData(":", 1); + hash.addData(":"); hash.addData(cNonce); ha1 = hash.result(); }; @@ -694,10 +770,10 @@ static QByteArray digestMd5ResponseHelper( // calculate H(A2) hash.reset(); hash.addData(method); - hash.addData(":", 1); + hash.addData(":"); hash.addData(digestUri); if (qop.compare("auth-int", Qt::CaseInsensitive) == 0) { - hash.addData(":", 1); + hash.addData(":"); hash.addData(hEntity); } QByteArray ha2hex = hash.result().toHex(); @@ -705,28 +781,29 @@ static QByteArray digestMd5ResponseHelper( // calculate response hash.reset(); hash.addData(ha1); - hash.addData(":", 1); + hash.addData(":"); hash.addData(nonce); - hash.addData(":", 1); + hash.addData(":"); if (!qop.isNull()) { hash.addData(nonceCount); - hash.addData(":", 1); + hash.addData(":"); hash.addData(cNonce); - hash.addData(":", 1); + hash.addData(":"); hash.addData(qop); - hash.addData(":", 1); + hash.addData(":"); } hash.addData(ha2hex); return hash.result().toHex(); } -QByteArray QAuthenticatorPrivate::digestMd5Response(const QByteArray &challenge, const QByteArray &method, const QByteArray &path) +QByteArray QAuthenticatorPrivate::digestMd5Response(QByteArrayView challenge, QByteArrayView method, + QByteArrayView path) { QHash<QByteArray,QByteArray> options = parseDigestAuthenticationChallenge(challenge); ++nonceCount; QByteArray nonceCountString = QByteArray::number(nonceCount, 16); - while (nonceCountString.length() < 8) + while (nonceCountString.size() < 8) nonceCountString.prepend('0'); QByteArray nonce = options.value("nonce"); @@ -987,9 +1064,9 @@ static void qStreamNtlmString(QDataStream& ds, const QString& s, bool unicode) qStreamNtlmBuffer(ds, s.toLatin1()); return; } - const ushort *d = s.utf16(); - for (int i = 0; i < s.length(); ++i) - ds << d[i]; + + for (QChar ch : s) + ds << quint16(ch.unicode()); } @@ -1007,7 +1084,7 @@ static int qEncodeNtlmString(QNtlmBuffer& buf, int offset, const QString& s, boo { if (!unicode) return qEncodeNtlmBuffer(buf, offset, s.toLatin1()); - buf.len = 2 * s.length(); + buf.len = 2 * s.size(); buf.maxLen = buf.len; buf.offset = (offset + 1) & ~1; return buf.offset + buf.len; @@ -1129,12 +1206,11 @@ static QByteArray qNtlmPhase1() static QByteArray qStringAsUcs2Le(const QString& src) { - QByteArray rc(2*src.length(), 0); - const unsigned short *s = src.utf16(); + QByteArray rc(2*src.size(), 0); unsigned short *d = (unsigned short*)rc.data(); - for (int i = 0; i < src.length(); ++i) { - d[i] = qToLittleEndian(s[i]); - } + for (QChar ch : src) + *d++ = qToLittleEndian(quint16(ch.unicode())); + return rc; } @@ -1143,7 +1219,7 @@ static QString qStringFromUcs2Le(QByteArray src) { Q_ASSERT(src.size() % 2 == 0); unsigned short *d = (unsigned short*)src.data(); - for (int i = 0; i < src.length() / 2; ++i) { + for (int i = 0; i < src.size() / 2; ++i) { d[i] = qFromLittleEndian(d[i]); } return QString((const QChar *)src.data(), src.size()/2); @@ -1172,13 +1248,12 @@ static QString qStringFromUcs2Le(QByteArray src) * --------------------------------------- * *********************************************************************/ -QByteArray qEncodeHmacMd5(QByteArray &key, const QByteArray &message) +QByteArray qEncodeHmacMd5(QByteArray &key, QByteArrayView message) { Q_ASSERT_X(!(message.isEmpty()),"qEncodeHmacMd5", "Empty message check"); Q_ASSERT_X(!(key.isEmpty()),"qEncodeHmacMd5", "Empty key check"); QCryptographicHash hash(QCryptographicHash::Md5); - QByteArray hMsg; QByteArray iKeyPad(blockSize, 0x36); QByteArray oKeyPad(blockSize, 0x5c); @@ -1186,7 +1261,7 @@ QByteArray qEncodeHmacMd5(QByteArray &key, const QByteArray &message) hash.reset(); // Adjust the key length to blockSize - if(blockSize < key.length()) { + if (blockSize < key.size()) { hash.addData(key); key = hash.result(); //MD5 will always return 16 bytes length output } @@ -1211,7 +1286,7 @@ QByteArray qEncodeHmacMd5(QByteArray &key, const QByteArray &message) hash.reset(); hash.addData(iKeyPad); - hMsg = hash.result(); + QByteArrayView hMsg = hash.resultView(); //Digest gen after pass-1: H((K0 xor ipad)||text) QByteArray hmacDigest; @@ -1238,10 +1313,10 @@ static QByteArray qCreatev2Hash(const QAuthenticatorPrivate *ctx, Q_ASSERT(phase3 != nullptr); // since v2 Hash is need for both NTLMv2 and LMv2 it is calculated // only once and stored and reused - if(phase3->v2Hash.size() == 0) { + if (phase3->v2Hash.size() == 0) { QCryptographicHash md4(QCryptographicHash::Md4); QByteArray passUnicode = qStringAsUcs2Le(ctx->password); - md4.addData(passUnicode.data(), passUnicode.size()); + md4.addData(passUnicode); QByteArray hashKey = md4.result(); Q_ASSERT(hashKey.size() == 16); @@ -1275,7 +1350,7 @@ static QByteArray qExtractServerTime(const QByteArray& targetInfoBuff) ds >> avId; ds >> avLen; while(avId != 0) { - if(avId == AVTIMESTAMP) { + if (avId == AVTIMESTAMP) { timeArray.resize(avLen); //avLen size of QByteArray is allocated ds.readRawData(timeArray.data(), avLen); @@ -1310,13 +1385,13 @@ static QByteArray qEncodeNtlmv2Response(const QAuthenticatorPrivate *ctx, quint64 time = 0; QByteArray timeArray; - if(ch.targetInfo.len) + if (ch.targetInfo.len) { timeArray = qExtractServerTime(ch.targetInfoBuff); } //if server sends time, use it instead of current time - if(timeArray.size()) { + if (timeArray.size()) { ds.writeRawData(timeArray.constData(), timeArray.size()); } else { // number of seconds between 1601 and the epoch (1970) @@ -1400,7 +1475,7 @@ static bool qNtlmDecodePhase2(const QByteArray& data, QNtlmPhase2Block& ch) ds >> ch.targetInfo; if (ch.targetName.len > 0) { - if (ch.targetName.len + ch.targetName.offset > (unsigned)data.size()) + if (qsizetype(ch.targetName.len + ch.targetName.offset) > data.size()) return false; ch.targetNameStr = qStringFromUcs2Le(data.mid(ch.targetName.offset, ch.targetName.len)); @@ -1448,7 +1523,7 @@ static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phas Q_ASSERT(QNtlmPhase3BlockBase::Size == sizeof(QNtlmPhase3BlockBase)); // for kerberos style user@domain logins, NTLM domain string should be left empty - if (ctx->userDomain.isEmpty() && !ctx->extractedUser.contains(QLatin1Char('@'))) { + if (ctx->userDomain.isEmpty() && !ctx->extractedUser.contains(u'@')) { offset = qEncodeNtlmString(pb.domain, offset, ch.targetNameStr, unicode); pb.domainStr = ch.targetNameStr; } else { @@ -1488,27 +1563,16 @@ static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phas // See http://davenport.sourceforge.net/ntlm.html // and libcurl http_ntlm.c -// Handle of secur32.dll -static HMODULE securityDLLHandle = nullptr; // Pointer to SSPI dispatch table -static PSecurityFunctionTable pSecurityFunctionTable = nullptr; +static PSecurityFunctionTableW pSecurityFunctionTable = nullptr; static bool q_SSPI_library_load() { - static QBasicMutex mutex; + Q_CONSTINIT static QBasicMutex mutex; QMutexLocker l(&mutex); - // Initialize security interface - if (pSecurityFunctionTable == nullptr) { - securityDLLHandle = LoadLibrary(L"secur32.dll"); - if (securityDLLHandle != nullptr) { - INIT_SECURITY_INTERFACE pInitSecurityInterface = - reinterpret_cast<INIT_SECURITY_INTERFACE>( - reinterpret_cast<QFunctionPointer>(GetProcAddress(securityDLLHandle, "InitSecurityInterfaceW"))); - if (pInitSecurityInterface != nullptr) - pSecurityFunctionTable = pInitSecurityInterface(); - } - } + if (pSecurityFunctionTable == nullptr) + pSecurityFunctionTable = InitSecurityInterfaceW(); if (pSecurityFunctionTable == nullptr) return false; @@ -1517,7 +1581,7 @@ static bool q_SSPI_library_load() } static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method, - const QString& host) + QStringView host) { if (!q_SSPI_library_load()) return QByteArray(); @@ -1526,14 +1590,28 @@ static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate if (!ctx->sspiWindowsHandles) ctx->sspiWindowsHandles.reset(new QSSPIWindowsHandles); - memset(&ctx->sspiWindowsHandles->credHandle, 0, sizeof(CredHandle)); + SecInvalidateHandle(&ctx->sspiWindowsHandles->credHandle); + SecInvalidateHandle(&ctx->sspiWindowsHandles->ctxHandle); + + SEC_WINNT_AUTH_IDENTITY auth; + auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + bool useAuth = false; + if (method == QAuthenticatorPrivate::Negotiate && !ctx->user.isEmpty()) { + auth.Domain = const_cast<ushort *>(reinterpret_cast<const ushort *>(ctx->userDomain.constData())); + auth.DomainLength = ctx->userDomain.size(); + auth.User = const_cast<ushort *>(reinterpret_cast<const ushort *>(ctx->user.constData())); + auth.UserLength = ctx->user.size(); + auth.Password = const_cast<ushort *>(reinterpret_cast<const ushort *>(ctx->password.constData())); + auth.PasswordLength = ctx->password.size(); + useAuth = true; + } // Acquire our credentials handle SECURITY_STATUS secStatus = pSecurityFunctionTable->AcquireCredentialsHandle( - nullptr, - (SEC_WCHAR*)(method == QAuthenticatorPrivate::Negotiate ? L"Negotiate" : L"NTLM"), - SECPKG_CRED_OUTBOUND, nullptr, nullptr, nullptr, nullptr, - &ctx->sspiWindowsHandles->credHandle, &expiry + nullptr, + (SEC_WCHAR *)(method == QAuthenticatorPrivate::Negotiate ? L"Negotiate" : L"NTLM"), + SECPKG_CRED_OUTBOUND, nullptr, useAuth ? &auth : nullptr, nullptr, nullptr, + &ctx->sspiWindowsHandles->credHandle, &expiry ); if (secStatus != SEC_E_OK) { ctx->sspiWindowsHandles.reset(nullptr); @@ -1544,7 +1622,7 @@ static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate } static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method, - const QString &host, const QByteArray &challenge) + QStringView host, QByteArrayView challenge) { QByteArray result; SecBuffer challengeBuf; @@ -1574,8 +1652,11 @@ static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivat responseBuf.cbBuffer = 0; // Calculate target (SPN for Negotiate, empty for NTLM) - std::wstring targetNameW = (method == QAuthenticatorPrivate::Negotiate - ? QLatin1String("HTTP/") + host : QString()).toStdWString(); + QString targetName = ctx->options.value("spn"_L1).toString(); + if (targetName.isEmpty()) + targetName = "HTTP/"_L1 + host; + const std::wstring targetNameW = (method == QAuthenticatorPrivate::Negotiate + ? targetName : QString()).toStdWString(); // Generate our challenge-response message SECURITY_STATUS secStatus = pSecurityFunctionTable->InitializeSecurityContext( @@ -1622,7 +1703,7 @@ static void q_GSSAPI_error_int(const char *message, OM_uint32 stat, int type) do { gss_display_status(&minStat, stat, type, GSS_C_NO_OID, &msgCtx, &msg); - qDebug() << message << ": " << reinterpret_cast<const char*>(msg.value); + qCDebug(lcAuthenticator) << message << ": " << reinterpret_cast<const char*>(msg.value); gss_release_buffer(&minStat, &msg); } while (msgCtx); } @@ -1637,26 +1718,36 @@ static void q_GSSAPI_error(const char *message, OM_uint32 majStat, OM_uint32 min q_GSSAPI_error_int(message, minStat, GSS_C_MECH_CODE); } -// Send initial GSS authentication token -static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, const QString &host) +static gss_name_t qGSsapiGetServiceName(QStringView host) { - OM_uint32 majStat, minStat; - - if (!ctx->gssApiHandles) - ctx->gssApiHandles.reset(new QGssApiHandles); - - // Convert target name to internal form - QByteArray serviceName = QStringLiteral("HTTPS@%1").arg(host).toLocal8Bit(); + QByteArray serviceName = "HTTPS@" + host.toLocal8Bit(); gss_buffer_desc nameDesc = {static_cast<std::size_t>(serviceName.size()), serviceName.data()}; - majStat = gss_import_name(&minStat, &nameDesc, - GSS_C_NT_HOSTBASED_SERVICE, &ctx->gssApiHandles->targetName); + gss_name_t importedName; + OM_uint32 minStat; + OM_uint32 majStat = gss_import_name(&minStat, &nameDesc, + GSS_C_NT_HOSTBASED_SERVICE, &importedName); if (majStat != GSS_S_COMPLETE) { q_GSSAPI_error("gss_import_name error", majStat, minStat); + return nullptr; + } + return importedName; +} + +// Send initial GSS authentication token +static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, QStringView host) +{ + if (!ctx->gssApiHandles) + ctx->gssApiHandles.reset(new QGssApiHandles); + + // Convert target name to internal form + gss_name_t name = qGSsapiGetServiceName(host); + if (name == nullptr) { ctx->gssApiHandles.reset(nullptr); return QByteArray(); } + ctx->gssApiHandles->targetName = name; // Call qGssapiContinue with GSS_C_NO_CONTEXT to get initial packet ctx->gssApiHandles->gssCtx = GSS_C_NO_CONTEXT; @@ -1664,7 +1755,7 @@ static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, const QString &host } // Continue GSS authentication with next token as needed -static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, const QByteArray& challenge) +static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, QByteArrayView challenge) { OM_uint32 majStat, minStat, ignored; QByteArray result; @@ -1673,7 +1764,7 @@ static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, const QByteArray& if (!challenge.isEmpty()) { inBuf.value = const_cast<char*>(challenge.data()); - inBuf.length = challenge.length(); + inBuf.length = challenge.size(); } majStat = gss_init_sec_context(&minStat, @@ -1710,6 +1801,28 @@ static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, const QByteArray& return result; } +static bool qGssapiTestGetCredentials(QStringView host) +{ + gss_name_t serviceName = qGSsapiGetServiceName(host); + if (!serviceName) + return false; // Something was wrong with the service name, so skip this + OM_uint32 minStat; + gss_cred_id_t cred; + OM_uint32 majStat = gss_acquire_cred(&minStat, serviceName, GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, GSS_C_INITIATE, &cred, nullptr, + nullptr); + + OM_uint32 ignored; + gss_release_name(&ignored, &serviceName); + gss_release_cred(&ignored, &cred); + + if (majStat != GSS_S_COMPLETE) { + q_GSSAPI_error("gss_acquire_cred", majStat, minStat); + return false; + } + return true; +} + // ---------------------------- End of GSSAPI code ---------------------------------------------- #endif // gssapi |