Add support for SPNEGO/Negotiate authentication

This commit adds support for single-sign-on SPNEGO/Negotiate
authentication to QAuthenticator, using SSPI on Windows and GSSAPI on
other platforms (if KRB5 GSSAPI is available).

[ChangeLog][QtNetwork][QAuthenticator] Add support for SPNEGO/Negotiate

Task-number: QTBUG-4117
Change-Id: Ie246b887db3fd6201b7ed30b023feca292cd6530
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Sandro Mani 2018-07-12 10:27:36 +02:00
parent 36c2ceca95
commit 93b7b0ec76
8 changed files with 327 additions and 160 deletions

View File

@ -398,11 +398,12 @@ void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthentica
{
Q_ASSERT(auth);
// NTLM is a multi phase authentication. Copying credentials between authenticators would mess things up.
// NTLM and Negotiate do multi-phase authentication.
// Copying credentialsbetween authenticators would mess things up.
if (fromChannel >= 0) {
if (!isProxy && channels[fromChannel].authMethod == QAuthenticatorPrivate::Ntlm)
return;
if (isProxy && channels[fromChannel].proxyAuthMethod == QAuthenticatorPrivate::Ntlm)
const QHttpNetworkConnectionChannel &channel = channels[fromChannel];
const QAuthenticatorPrivate::Method method = isProxy ? channel.proxyAuthMethod : channel.authMethod;
if (method == QAuthenticatorPrivate::Ntlm || method == QAuthenticatorPrivate::Negotiate)
return;
}
@ -592,7 +593,7 @@ void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket,
if ((channels[i].authMethod != QAuthenticatorPrivate::Ntlm && request.headerField("Authorization").isEmpty()) || channels[i].lastStatus == 401) {
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].authenticator);
if (priv && priv->method != QAuthenticatorPrivate::None) {
QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false));
QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false), request.url().host());
request.setHeaderField("Authorization", response);
channels[i].authenticationCredentialsSent = true;
}
@ -604,7 +605,7 @@ void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket,
if (!(channels[i].proxyAuthMethod == QAuthenticatorPrivate::Ntlm && channels[i].lastStatus != 407)) {
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(channels[i].proxyAuthenticator);
if (priv && priv->method != QAuthenticatorPrivate::None) {
QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false));
QByteArray response = priv->calculateResponse(request.methodName(), request.uri(false), networkProxy.hostName());
request.setHeaderField("Proxy-Authorization", response);
channels[i].proxyCredentialsSent = true;
}

View File

@ -444,6 +444,9 @@ QAuthenticatorPrivate::Method QHttpNetworkReplyPrivate::authenticationMethod(boo
} else if (method < QAuthenticatorPrivate::DigestMd5
&& line.startsWith("digest")) {
method = QAuthenticatorPrivate::DigestMd5;
} else if (method < QAuthenticatorPrivate::Negotiate
&& line.startsWith("negotiate")) {
method = QAuthenticatorPrivate::Negotiate;
}
}
return method;

View File

@ -199,6 +199,15 @@
]
},
"use": "openssl"
},
"gssapi": {
"label": "KRB5 GSSAPI support",
"type": "compile",
"test": {
"include": [ "gssapi/gssapi.h" ],
"main": ["gss_ctx_id_t ctx;"],
"qmake": "LIBS += -lgssapi_krb5"
}
}
},
@ -374,6 +383,20 @@
"purpose": "Provides API for DNS lookups.",
"section": "Networking",
"output": [ "publicFeature" ]
},
"gssapi": {
"label": "GSSAPI",
"purpose": "Enable SPNEGO authentication through GSSAPI",
"section": "Networking",
"condition": "!config.win32 && tests.gssapi",
"output": [ "publicFeature", "feature" ]
},
"sspi": {
"label": "SSPI",
"purpose": "Enable NTLM/SPNEGO authentication through SSPI",
"section": "Networking",
"condition": "config.win32 && !config.winrt",
"output": [ "publicFeature", "feature" ]
}
},
@ -433,7 +456,8 @@ For example:
"dtls",
"ocsp",
"sctp",
"system-proxies"
"system-proxies",
"gssapi"
]
}
]

View File

@ -68,6 +68,8 @@ mac {
!uikit: LIBS_PRIVATE += -framework CoreServices -framework SystemConfiguration
}
qtConfig(gssapi): LIBS_PRIVATE += -lgssapi_krb5
uikit:HEADERS += kernel/qnetworkinterface_uikit_p.h
osx:SOURCES += kernel/qnetworkproxy_mac.cpp
else:win32:!winrt: SOURCES += kernel/qnetworkproxy_win.cpp

View File

@ -54,20 +54,29 @@
#include <qmutex.h>
#include <private/qmutexpool_p.h>
#include <rpc.h>
#ifndef Q_OS_WINRT
#endif
#if QT_CONFIG(sspi) // SSPI
#define SECURITY_WIN32 1
#include <security.h>
#endif
#elif QT_CONFIG(gssapi) // GSSAPI
#include <gssapi/gssapi.h>
#endif
QT_BEGIN_NAMESPACE
static QByteArray qNtlmPhase1();
static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data);
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
static QByteArray qNtlmPhase1_SSPI(QAuthenticatorPrivate *ctx);
static QByteArray qNtlmPhase3_SSPI(QAuthenticatorPrivate *ctx, const QByteArray& phase2data);
#endif
#if QT_CONFIG(sspi) // SSPI
static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
const QString& host);
static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
const QString& host, const QByteArray& challenge = QByteArray());
#elif QT_CONFIG(gssapi) // GSSAPI
static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, const QString& host);
static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx,
const QByteArray& challenge = QByteArray());
#endif // gssapi
/*!
\class QAuthenticator
@ -90,6 +99,7 @@ static QByteArray qNtlmPhase3_SSPI(QAuthenticatorPrivate *ctx, const QByteArray&
\li Basic
\li NTLM version 2
\li Digest-MD5
\li SPNEGO/Negotiate
\endlist
\target qauthenticator-options
@ -133,6 +143,10 @@ static QByteArray qNtlmPhase3_SSPI(QAuthenticatorPrivate *ctx, const QByteArray&
The Digest-MD5 authentication mechanism supports no outgoing options.
\section2 SPNEGO/Negotiate
This authentication mechanism currently supports no incoming or outgoing options.
\sa QSslSocket
*/
@ -187,7 +201,7 @@ QAuthenticator &QAuthenticator::operator=(const QAuthenticator &other)
d->options = other.d->options;
} else if (d->phase == QAuthenticatorPrivate::Start) {
delete d;
d = 0;
d = nullptr;
}
return *this;
}
@ -339,21 +353,25 @@ bool QAuthenticator::isNull() const
return !d;
}
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
class QNtlmWindowsHandles
#if QT_CONFIG(sspi) // SSPI
class QSSPIWindowsHandles
{
public:
CredHandle credHandle;
CtxtHandle ctxHandle;
};
#endif
#elif QT_CONFIG(gssapi) // GSSAPI
class QGssApiHandles
{
public:
gss_ctx_id_t gssCtx = nullptr;
gss_name_t targetName;
};
#endif // gssapi
QAuthenticatorPrivate::QAuthenticatorPrivate()
: method(None)
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
, ntlmWindowsHandles(0)
#endif
, hasFailed(false)
, phase(Start)
, nonceCount(0)
@ -363,13 +381,7 @@ QAuthenticatorPrivate::QAuthenticatorPrivate()
nonceCount = 0;
}
QAuthenticatorPrivate::~QAuthenticatorPrivate()
{
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
if (ntlmWindowsHandles)
delete ntlmWindowsHandles;
#endif
}
QAuthenticatorPrivate::~QAuthenticatorPrivate() = default;
void QAuthenticatorPrivate::updateCredentials()
{
@ -424,6 +436,9 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt
} else if (method < DigestMd5 && str.startsWith("digest")) {
method = DigestMd5;
headerVal = current.second.mid(7);
} else if (method < Negotiate && str.startsWith("negotiate")) {
method = Negotiate;
headerVal = current.second.mid(10);
}
}
@ -439,6 +454,7 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt
phase = Done;
break;
case Ntlm:
case Negotiate:
// work is done in calculateResponse()
break;
case DigestMd5: {
@ -456,33 +472,36 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt
}
}
QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMethod, const QByteArray &path)
QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMethod, const QByteArray &path, const QString& host)
{
#if !QT_CONFIG(sspi) && !QT_CONFIG(gssapi)
Q_UNUSED(host);
#endif
QByteArray response;
const char *methodString = 0;
const char* methodString = nullptr;
switch(method) {
case QAuthenticatorPrivate::None:
methodString = "";
phase = Done;
break;
case QAuthenticatorPrivate::Basic:
methodString = "Basic ";
methodString = "Basic";
response = user.toLatin1() + ':' + password.toLatin1();
response = response.toBase64();
phase = Done;
break;
case QAuthenticatorPrivate::DigestMd5:
methodString = "Digest ";
methodString = "Digest";
response = digestMd5Response(challenge, requestMethod, path);
phase = Done;
break;
case QAuthenticatorPrivate::Ntlm:
methodString = "NTLM ";
methodString = "NTLM";
if (challenge.isEmpty()) {
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
#if QT_CONFIG(sspi) // SSPI
QByteArray phase1Token;
if (user.isEmpty()) // Only pull from system if no user was specified in authenticator
phase1Token = qNtlmPhase1_SSPI(this);
phase1Token = qSspiStartup(this, method, host);
if (!phase1Token.isEmpty()) {
response = phase1Token.toBase64();
phase = Phase2;
@ -496,10 +515,10 @@ QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMet
phase = Phase2;
}
} else {
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
#if QT_CONFIG(sspi) // SSPI
QByteArray phase3Token;
if (ntlmWindowsHandles)
phase3Token = qNtlmPhase3_SSPI(this, QByteArray::fromBase64(challenge));
if (sspiWindowsHandles)
phase3Token = qSspiContinue(this, method, host, QByteArray::fromBase64(challenge));
if (!phase3Token.isEmpty()) {
response = phase3Token.toBase64();
phase = Done;
@ -511,9 +530,40 @@ QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMet
}
}
break;
case QAuthenticatorPrivate::Negotiate:
methodString = "Negotiate";
if (challenge.isEmpty()) {
QByteArray phase1Token;
#if QT_CONFIG(sspi) // SSPI
phase1Token = qSspiStartup(this, method, host);
#elif QT_CONFIG(gssapi) // GSSAPI
phase1Token = qGssapiStartup(this, host);
#endif
if (!phase1Token.isEmpty()) {
response = phase1Token.toBase64();
phase = Phase2;
} else {
phase = Done;
}
} else {
QByteArray phase3Token;
#if QT_CONFIG(sspi) // SSPI
phase3Token = qSspiContinue(this, method, host, QByteArray::fromBase64(challenge));
#elif QT_CONFIG(gssapi) // GSSAPI
phase3Token = qGssapiContinue(this, QByteArray::fromBase64(challenge));
#endif
if (!phase3Token.isEmpty()) {
response = phase3Token.toBase64();
phase = Done;
}
}
break;
}
return QByteArray(methodString) + response;
return QByteArray::fromRawData(methodString, qstrlen(methodString)) + ' ' + response;
}
@ -699,9 +749,10 @@ QByteArray QAuthenticatorPrivate::digestMd5Response(const QByteArray &challenge,
return credentials;
}
// ---------------------------- Digest Md5 code ----------------------------------------
// ---------------------------- End of Digest Md5 code ---------------------------------
// ---------------------------- NTLM code ----------------------------------------------
/*
* NTLM message flags.
@ -1419,156 +1470,237 @@ static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phas
return rc;
}
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
// ---------------------------- End of NTLM code ---------------------------------------
#if QT_CONFIG(sspi) // SSPI
// ---------------------------- SSPI code ----------------------------------------------
// See http://davenport.sourceforge.net/ntlm.html
// and libcurl http_ntlm.c
// Handle of secur32.dll
static HMODULE securityDLLHandle = NULL;
static HMODULE securityDLLHandle = nullptr;
// Pointer to SSPI dispatch table
static PSecurityFunctionTable pSecurityFunctionTable = NULL;
static PSecurityFunctionTable pSecurityFunctionTable = nullptr;
static bool q_NTLM_SSPI_library_load()
static bool q_SSPI_library_load()
{
static QBasicMutex mutex;
QMutexLocker l(&mutex);
// Initialize security interface
if (pSecurityFunctionTable == NULL) {
if (pSecurityFunctionTable == nullptr) {
securityDLLHandle = LoadLibrary(L"secur32.dll");
if (securityDLLHandle != NULL) {
if (securityDLLHandle != nullptr) {
INIT_SECURITY_INTERFACE pInitSecurityInterface =
reinterpret_cast<INIT_SECURITY_INTERFACE>(
reinterpret_cast<QFunctionPointer>(GetProcAddress(securityDLLHandle, "InitSecurityInterfaceW")));
if (pInitSecurityInterface != NULL)
if (pInitSecurityInterface != nullptr)
pSecurityFunctionTable = pInitSecurityInterface();
}
}
if (pSecurityFunctionTable == NULL)
if (pSecurityFunctionTable == nullptr)
return false;
return true;
}
// Phase 1:
static QByteArray qNtlmPhase1_SSPI(QAuthenticatorPrivate *ctx)
static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
const QString& host)
{
QByteArray result;
if (!q_SSPI_library_load())
return QByteArray();
if (!q_NTLM_SSPI_library_load())
return result;
TimeStamp expiry; // For Windows 9x compatibility of SSPI calls
// 1. The client obtains a representation of the credential set
// for the user via the SSPI AcquireCredentialsHandle function.
if (!ctx->ntlmWindowsHandles)
ctx->ntlmWindowsHandles = new QNtlmWindowsHandles;
memset(&ctx->ntlmWindowsHandles->credHandle, 0, sizeof(CredHandle));
TimeStamp tsDummy;
if (!ctx->sspiWindowsHandles)
ctx->sspiWindowsHandles.reset(new QSSPIWindowsHandles);
memset(&ctx->sspiWindowsHandles->credHandle, 0, sizeof(CredHandle));
// Acquire our credentials handle
SECURITY_STATUS secStatus = pSecurityFunctionTable->AcquireCredentialsHandle(
NULL, (SEC_WCHAR*)L"NTLM", SECPKG_CRED_OUTBOUND, NULL, NULL,
NULL, NULL, &ctx->ntlmWindowsHandles->credHandle, &tsDummy);
nullptr,
(SEC_WCHAR*)(method == QAuthenticatorPrivate::Negotiate ? L"Negotiate" : L"NTLM"),
SECPKG_CRED_OUTBOUND, nullptr, nullptr, nullptr, nullptr,
&ctx->sspiWindowsHandles->credHandle, &expiry
);
if (secStatus != SEC_E_OK) {
delete ctx->ntlmWindowsHandles;
ctx->ntlmWindowsHandles = 0;
return result;
ctx->sspiWindowsHandles.reset(nullptr);
return QByteArray();
}
// 2. The client calls the SSPI InitializeSecurityContext function
// to obtain an authentication request token (in our case, a Type 1 message).
// The client sends this token to the server.
SecBufferDesc desc;
SecBuffer buf;
desc.ulVersion = SECBUFFER_VERSION;
desc.cBuffers = 1;
desc.pBuffers = &buf;
buf.cbBuffer = 0;
buf.BufferType = SECBUFFER_TOKEN;
buf.pvBuffer = NULL;
ULONG attrs;
secStatus = pSecurityFunctionTable->InitializeSecurityContext(&ctx->ntlmWindowsHandles->credHandle, NULL,
const_cast<SEC_WCHAR*>(L"") /* host */,
ISC_REQ_ALLOCATE_MEMORY,
0, SECURITY_NETWORK_DREP,
NULL, 0,
&ctx->ntlmWindowsHandles->ctxHandle, &desc,
&attrs, &tsDummy);
if (secStatus == SEC_I_COMPLETE_AND_CONTINUE ||
secStatus == SEC_I_CONTINUE_NEEDED) {
pSecurityFunctionTable->CompleteAuthToken(&ctx->ntlmWindowsHandles->ctxHandle, &desc);
} else if (secStatus != SEC_E_OK) {
if ((const char*)buf.pvBuffer)
pSecurityFunctionTable->FreeContextBuffer(buf.pvBuffer);
pSecurityFunctionTable->FreeCredentialsHandle(&ctx->ntlmWindowsHandles->credHandle);
delete ctx->ntlmWindowsHandles;
ctx->ntlmWindowsHandles = 0;
return result;
}
result = QByteArray((const char*)buf.pvBuffer, buf.cbBuffer);
pSecurityFunctionTable->FreeContextBuffer(buf.pvBuffer);
return result;
return qSspiContinue(ctx, method, host);
}
// Phase 2:
// 3. The server receives the token from the client, and uses it as input to the
// AcceptSecurityContext SSPI function. This creates a local security context on
// the server to represent the client, and yields an authentication response token
// (the Type 2 message), which is sent to the client.
// Phase 3:
static QByteArray qNtlmPhase3_SSPI(QAuthenticatorPrivate *ctx, const QByteArray& phase2data)
static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
const QString &host, const QByteArray &challenge)
{
// 4. The client receives the response token from the server and calls
// InitializeSecurityContext again, passing the server's token as input.
// This provides us with another authentication request token (the Type 3 message).
// The return value indicates that the security context was successfully initialized;
// the token is sent to the server.
QByteArray result;
SecBuffer challengeBuf;
SecBuffer responseBuf;
SecBufferDesc challengeDesc;
SecBufferDesc responseDesc;
unsigned long attrs;
TimeStamp expiry; // For Windows 9x compatibility of SSPI calls
if (pSecurityFunctionTable == NULL)
return result;
SecBuffer type_2, type_3;
SecBufferDesc type_2_desc, type_3_desc;
ULONG attrs;
TimeStamp tsDummy; // For Windows 9x compatibility of SPPI calls
type_2_desc.ulVersion = type_3_desc.ulVersion = SECBUFFER_VERSION;
type_2_desc.cBuffers = type_3_desc.cBuffers = 1;
type_2_desc.pBuffers = &type_2;
type_3_desc.pBuffers = &type_3;
type_2.BufferType = SECBUFFER_TOKEN;
type_2.pvBuffer = (PVOID)phase2data.data();
type_2.cbBuffer = phase2data.length();
type_3.BufferType = SECBUFFER_TOKEN;
type_3.pvBuffer = 0;
type_3.cbBuffer = 0;
SECURITY_STATUS secStatus = pSecurityFunctionTable->InitializeSecurityContext(&ctx->ntlmWindowsHandles->credHandle,
&ctx->ntlmWindowsHandles->ctxHandle,
const_cast<SEC_WCHAR*>(L"") /* host */,
ISC_REQ_ALLOCATE_MEMORY,
0, SECURITY_NETWORK_DREP, &type_2_desc,
0, &ctx->ntlmWindowsHandles->ctxHandle, &type_3_desc,
&attrs, &tsDummy);
if (secStatus == SEC_E_OK && ((const char*)type_3.pvBuffer)) {
result = QByteArray((const char*)type_3.pvBuffer, type_3.cbBuffer);
pSecurityFunctionTable->FreeContextBuffer(type_3.pvBuffer);
if (!challenge.isEmpty())
{
// Setup the challenge "input" security buffer
challengeDesc.ulVersion = SECBUFFER_VERSION;
challengeDesc.cBuffers = 1;
challengeDesc.pBuffers = &challengeBuf;
challengeBuf.BufferType = SECBUFFER_TOKEN;
challengeBuf.pvBuffer = (PVOID)(challenge.data());
challengeBuf.cbBuffer = challenge.length();
}
pSecurityFunctionTable->FreeCredentialsHandle(&ctx->ntlmWindowsHandles->credHandle);
pSecurityFunctionTable->DeleteSecurityContext(&ctx->ntlmWindowsHandles->ctxHandle);
delete ctx->ntlmWindowsHandles;
ctx->ntlmWindowsHandles = 0;
// Setup the response "output" security buffer
responseDesc.ulVersion = SECBUFFER_VERSION;
responseDesc.cBuffers = 1;
responseDesc.pBuffers = &responseBuf;
responseBuf.BufferType = SECBUFFER_TOKEN;
responseBuf.pvBuffer = nullptr;
responseBuf.cbBuffer = 0;
// Calculate target (SPN for Negotiate, empty for NTLM)
std::wstring targetNameW = (method == QAuthenticatorPrivate::Negotiate
? QLatin1String("HTTP/") + host : QString()).toStdWString();
// Generate our challenge-response message
SECURITY_STATUS secStatus = pSecurityFunctionTable->InitializeSecurityContext(
&ctx->sspiWindowsHandles->credHandle,
!challenge.isEmpty() ? &ctx->sspiWindowsHandles->ctxHandle : nullptr,
const_cast<wchar_t*>(targetNameW.data()),
ISC_REQ_ALLOCATE_MEMORY,
0, SECURITY_NATIVE_DREP,
!challenge.isEmpty() ? &challengeDesc : nullptr,
0, &ctx->sspiWindowsHandles->ctxHandle,
&responseDesc, &attrs,
&expiry
);
if (secStatus == SEC_I_COMPLETE_NEEDED || secStatus == SEC_I_COMPLETE_AND_CONTINUE) {
secStatus = pSecurityFunctionTable->CompleteAuthToken(&ctx->sspiWindowsHandles->ctxHandle,
&responseDesc);
}
if (secStatus != SEC_I_COMPLETE_AND_CONTINUE && secStatus != SEC_I_CONTINUE_NEEDED) {
pSecurityFunctionTable->FreeCredentialsHandle(&ctx->sspiWindowsHandles->credHandle);
pSecurityFunctionTable->DeleteSecurityContext(&ctx->sspiWindowsHandles->ctxHandle);
ctx->sspiWindowsHandles.reset(nullptr);
}
result = QByteArray((const char*)responseBuf.pvBuffer, responseBuf.cbBuffer);
pSecurityFunctionTable->FreeContextBuffer(responseBuf.pvBuffer);
return result;
}
#endif // Q_OS_WIN && !Q_OS_WINRT
// ---------------------------- End of SSPI code ---------------------------------------
#elif QT_CONFIG(gssapi) // GSSAPI
// ---------------------------- GSSAPI code ----------------------------------------------
// See postgres src/interfaces/libpq/fe-auth.c
// Fetch all errors of a specific type
static void q_GSSAPI_error_int(const char *message, OM_uint32 stat, int type)
{
OM_uint32 minStat, msgCtx = 0;
gss_buffer_desc msg;
do {
gss_display_status(&minStat, stat, type, GSS_C_NO_OID, &msgCtx, &msg);
qDebug() << message << ": " << reinterpret_cast<const char*>(msg.value);
gss_release_buffer(&minStat, &msg);
} while (msgCtx);
}
// GSSAPI errors contain two parts; extract both
static void q_GSSAPI_error(const char *message, OM_uint32 majStat, OM_uint32 minStat)
{
// Fetch major error codes
q_GSSAPI_error_int(message, majStat, GSS_C_GSS_CODE);
// Add the minor codes as well
q_GSSAPI_error_int(message, minStat, GSS_C_MECH_CODE);
}
// Send initial GSS authentication token
static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, const QString &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();
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);
if (majStat != GSS_S_COMPLETE) {
q_GSSAPI_error("gss_import_name error", majStat, minStat);
ctx->gssApiHandles.reset(nullptr);
return QByteArray();
}
// Call qGssapiContinue with GSS_C_NO_CONTEXT to get initial packet
ctx->gssApiHandles->gssCtx = GSS_C_NO_CONTEXT;
return qGssapiContinue(ctx);
}
// Continue GSS authentication with next token as needed
static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, const QByteArray& challenge)
{
OM_uint32 majStat, minStat, ignored;
QByteArray result;
gss_buffer_desc inBuf = {0, nullptr}; // GSS input token
gss_buffer_desc outBuf; // GSS output token
if (!challenge.isEmpty()) {
inBuf.value = const_cast<char*>(challenge.data());
inBuf.length = challenge.length();
}
majStat = gss_init_sec_context(&minStat,
GSS_C_NO_CREDENTIAL,
&ctx->gssApiHandles->gssCtx,
ctx->gssApiHandles->targetName,
GSS_C_NO_OID,
GSS_C_MUTUAL_FLAG,
0,
GSS_C_NO_CHANNEL_BINDINGS,
challenge.isEmpty() ? GSS_C_NO_BUFFER : &inBuf,
nullptr,
&outBuf,
nullptr,
nullptr);
if (outBuf.length != 0)
result = QByteArray(reinterpret_cast<const char*>(outBuf.value), outBuf.length);
gss_release_buffer(&ignored, &outBuf);
if (majStat != GSS_S_COMPLETE && majStat != GSS_S_CONTINUE_NEEDED) {
q_GSSAPI_error("gss_init_sec_context error", majStat, minStat);
gss_release_name(&ignored, &ctx->gssApiHandles->targetName);
if (ctx->gssApiHandles->gssCtx)
gss_delete_sec_context(&ignored, &ctx->gssApiHandles->gssCtx, GSS_C_NO_BUFFER);
ctx->gssApiHandles.reset(nullptr);
}
if (majStat == GSS_S_COMPLETE) {
gss_release_name(&ignored, &ctx->gssApiHandles->targetName);
ctx->gssApiHandles.reset(nullptr);
}
return result;
}
// ---------------------------- End of GSSAPI code ----------------------------------------------
#endif // gssapi
QT_END_NAMESPACE

View File

@ -54,6 +54,7 @@
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <qhash.h>
#include <qbytearray.h>
#include <qscopedpointer.h>
#include <qstring.h>
#include <qauthenticator.h>
#include <qvariant.h>
@ -61,14 +62,16 @@
QT_BEGIN_NAMESPACE
class QHttpResponseHeader;
#ifdef Q_OS_WIN
class QNtlmWindowsHandles;
#if QT_CONFIG(sspi) // SSPI
class QSSPIWindowsHandles;
#elif QT_CONFIG(gssapi) // GSSAPI
class QGssApiHandles;
#endif
class Q_AUTOTEST_EXPORT QAuthenticatorPrivate
{
public:
enum Method { None, Basic, Ntlm, DigestMd5 };
enum Method { None, Basic, Ntlm, DigestMd5, Negotiate };
QAuthenticatorPrivate();
~QAuthenticatorPrivate();
@ -79,8 +82,10 @@ public:
Method method;
QString realm;
QByteArray challenge;
#ifdef Q_OS_WIN
QNtlmWindowsHandles *ntlmWindowsHandles;
#if QT_CONFIG(sspi) // SSPI
QScopedPointer<QSSPIWindowsHandles> sspiWindowsHandles;
#elif QT_CONFIG(gssapi) // GSSAPI
QScopedPointer<QGssApiHandles> gssApiHandles;
#endif
bool hasFailed; //credentials have been tried but rejected by server.
@ -100,7 +105,7 @@ public:
QString workstation;
QString userDomain;
QByteArray calculateResponse(const QByteArray &method, const QByteArray &path);
QByteArray calculateResponse(const QByteArray &method, const QByteArray &path, const QString& host);
inline static QAuthenticatorPrivate *getPrivate(QAuthenticator &auth) { return auth.d; }
inline static const QAuthenticatorPrivate *getPrivate(const QAuthenticator &auth) { return auth.d; }

View File

@ -524,7 +524,7 @@ void QHttpSocketEngine::slotSocketConnected()
//qDebug() << "slotSocketConnected: priv=" << priv << (priv ? (int)priv->method : -1);
if (priv && priv->method != QAuthenticatorPrivate::None) {
d->credentialsSent = true;
data += "Proxy-Authorization: " + priv->calculateResponse(method, path);
data += "Proxy-Authorization: " + priv->calculateResponse(method, path, d->proxy.hostName());
data += "\r\n";
}
data += "\r\n";

View File

@ -93,7 +93,7 @@ void tst_QAuthenticator::basicAuth()
QCOMPARE(priv->phase, QAuthenticatorPrivate::Start);
QCOMPARE(priv->calculateResponse("GET", "/").constData(), QByteArray("Basic " + expectedReply).constData());
QCOMPARE(priv->calculateResponse("GET", "/", "").constData(), QByteArray("Basic " + expectedReply).constData());
}
void tst_QAuthenticator::ntlmAuth_data()
@ -133,9 +133,9 @@ void tst_QAuthenticator::ntlmAuth()
headers << qMakePair<QByteArray, QByteArray>("WWW-Authenticate", "NTLM");
priv->parseHttpResponse(headers, /*isProxy = */ false);
if (sso)
QVERIFY(priv->calculateResponse("GET", "/").startsWith("NTLM "));
QVERIFY(priv->calculateResponse("GET", "/", "").startsWith("NTLM "));
else
QCOMPARE(priv->calculateResponse("GET", "/").constData(), "NTLM TlRMTVNTUAABAAAABYIIAAAAAAAAAAAAAAAAAAAAAAA=");
QCOMPARE(priv->calculateResponse("GET", "/", "").constData(), "NTLM TlRMTVNTUAABAAAABYIIAAAAAAAAAAAAAAAAAAAAAAA=");
// NTLM phase 2: challenge
headers.clear();
@ -146,7 +146,7 @@ void tst_QAuthenticator::ntlmAuth()
QEXPECT_FAIL("with-realm-sso", "NTLM authentication code doesn't extract the realm", Continue);
QCOMPARE(auth.realm(), realm);
QVERIFY(priv->calculateResponse("GET", "/").startsWith("NTLM "));
QVERIFY(priv->calculateResponse("GET", "/", "").startsWith("NTLM "));
}
void tst_QAuthenticator::equalityOperators()