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:
parent
36c2ceca95
commit
93b7b0ec76
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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; }
|
||||
|
@ -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";
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user