diff options
Diffstat (limited to 'src/network/kernel/qauthenticator.cpp')
-rw-r--r-- | src/network/kernel/qauthenticator.cpp | 162 |
1 files changed, 80 insertions, 82 deletions
diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp index 97f4a2cba4..e42450d7e5 100644 --- a/src/network/kernel/qauthenticator.cpp +++ b/src/network/kernel/qauthenticator.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** 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> @@ -50,6 +14,7 @@ #include <qstring.h> #include <qdatetime.h> #include <qrandom.h> +#include <QtNetwork/qhttpheaders.h> #ifdef Q_OS_WIN #include <qmutex.h> @@ -69,6 +34,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + Q_DECLARE_LOGGING_CATEGORY(lcAuthenticator); Q_LOGGING_CATEGORY(lcAuthenticator, "qt.network.authenticator"); @@ -153,7 +120,28 @@ static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, QByteArrayView cha \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 */ @@ -405,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); @@ -457,13 +445,14 @@ static bool verifyDigestMD5(QByteArrayView value) return true; // assume it's ok if algorithm is not specified } -void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByteArray>> &values, +void QAuthenticatorPrivate::parseHttpResponse(const QHttpHeaders &headers, bool isProxy, QStringView host) { #if !QT_CONFIG(gssapi) Q_UNUSED(host); #endif - const char *search = isProxy ? "proxy-authenticate" : "www-authenticate"; + const auto search = isProxy ? QHttpHeaders::WellKnownHeader::ProxyAuthenticate + : QHttpHeaders::WellKnownHeader::WWWAuthenticate; method = None; /* @@ -476,26 +465,23 @@ 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.second).sliced(7))) + 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). @@ -505,14 +491,14 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt 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 @@ -522,7 +508,7 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt if (phase == Done) phase = Start; realm = newRealm; - this->options[QLatin1String("realm")] = realm; + this->options["realm"_L1] = realm; } }; @@ -560,16 +546,14 @@ QByteArray QAuthenticatorPrivate::calculateResponse(QByteArrayView requestMethod 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: @@ -639,9 +623,11 @@ QByteArray QAuthenticatorPrivate::calculateResponse(QByteArrayView requestMethod } 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(); @@ -656,26 +642,35 @@ QByteArray QAuthenticatorPrivate::calculateResponse(QByteArrayView requestMethod break; } - return QByteArray::fromRawData(methodString, qstrlen(methodString)) + ' ' + response; + return methodString + ' ' + response; } // ---------------------------- Digest Md5 code ---------------------------------------- +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.data(); - const char *end = d + challenge.length(); + 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; @@ -706,13 +701,12 @@ QAuthenticatorPrivate::parseDigestAuthenticationChallenge(QByteArrayView challen 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")) @@ -809,7 +803,7 @@ QByteArray QAuthenticatorPrivate::digestMd5Response(QByteArrayView challenge, QB ++nonceCount; QByteArray nonceCountString = QByteArray::number(nonceCount, 16); - while (nonceCountString.length() < 8) + while (nonceCountString.size() < 8) nonceCountString.prepend('0'); QByteArray nonce = options.value("nonce"); @@ -1090,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; @@ -1212,7 +1206,7 @@ static QByteArray qNtlmPhase1() static QByteArray qStringAsUcs2Le(const QString& src) { - QByteArray rc(2*src.length(), 0); + QByteArray rc(2*src.size(), 0); unsigned short *d = (unsigned short*)rc.data(); for (QChar ch : src) *d++ = qToLittleEndian(quint16(ch.unicode())); @@ -1225,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); @@ -1267,7 +1261,7 @@ QByteArray qEncodeHmacMd5(QByteArray &key, QByteArrayView 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 } @@ -1529,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 { @@ -1574,7 +1568,7 @@ static PSecurityFunctionTableW pSecurityFunctionTable = nullptr; static bool q_SSPI_library_load() { - static QBasicMutex mutex; + Q_CONSTINIT static QBasicMutex mutex; QMutexLocker l(&mutex); if (pSecurityFunctionTable == nullptr) @@ -1596,7 +1590,8 @@ 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; @@ -1657,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( @@ -1766,7 +1764,7 @@ static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, QByteArrayView cha if (!challenge.isEmpty()) { inBuf.value = const_cast<char*>(challenge.data()); - inBuf.length = challenge.length(); + inBuf.length = challenge.size(); } majStat = gss_init_sec_context(&minStat, |