QAuthenticator: condition using GSSAPI on credentials availability
AFAICT with GSSAPI the normal workflow is to run kinit or similar and authenticate before running programs relying on it. Therefore we can try to get the credentials before we choose whether or not to use Negotiate. Pick-to: 5.15 Task-number: QTBUG-85123 Change-Id: If0478fdd45389b2939ad87c2f582776fe56959bb Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
parent
e6b8eb502e
commit
e0918af700
@ -449,7 +449,14 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket
|
|||||||
if (auth->isNull())
|
if (auth->isNull())
|
||||||
auth->detach();
|
auth->detach();
|
||||||
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
|
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
|
||||||
priv->parseHttpResponse(fields, isProxy);
|
priv->parseHttpResponse(fields, isProxy, reply->url().host());
|
||||||
|
// Update method in case it changed
|
||||||
|
if (priv->method == QAuthenticatorPrivate::None)
|
||||||
|
return false;
|
||||||
|
if (isProxy)
|
||||||
|
channels[i].proxyAuthMethod = priv->method;
|
||||||
|
else
|
||||||
|
channels[i].authMethod = priv->method;
|
||||||
|
|
||||||
if (priv->phase == QAuthenticatorPrivate::Done) {
|
if (priv->phase == QAuthenticatorPrivate::Done) {
|
||||||
pauseConnection();
|
pauseConnection();
|
||||||
|
@ -81,6 +81,7 @@ static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate
|
|||||||
static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
|
static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
|
||||||
const QString& host, const QByteArray& challenge = QByteArray());
|
const QString& host, const QByteArray& challenge = QByteArray());
|
||||||
#elif QT_CONFIG(gssapi) // GSSAPI
|
#elif QT_CONFIG(gssapi) // GSSAPI
|
||||||
|
static bool qGssapiTestGetCredentials(const QString &host);
|
||||||
static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, const QString& host);
|
static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, const QString& host);
|
||||||
static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx,
|
static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx,
|
||||||
const QByteArray& challenge = QByteArray());
|
const QByteArray& challenge = QByteArray());
|
||||||
@ -422,7 +423,7 @@ void QAuthenticatorPrivate::updateCredentials()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByteArray> > &values, bool isProxy)
|
void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByteArray> > &values, bool isProxy, const QString &host)
|
||||||
{
|
{
|
||||||
const char *search = isProxy ? "proxy-authenticate" : "www-authenticate";
|
const char *search = isProxy ? "proxy-authenticate" : "www-authenticate";
|
||||||
|
|
||||||
@ -454,6 +455,13 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt
|
|||||||
headerVal = current.second.mid(7);
|
headerVal = current.second.mid(7);
|
||||||
} else if (method < Negotiate && str.startsWith("negotiate")) {
|
} else if (method < Negotiate && str.startsWith("negotiate")) {
|
||||||
#if QT_CONFIG(sspi) || QT_CONFIG(gssapi) // if it's not supported then we shouldn't try to use it
|
#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;
|
method = Negotiate;
|
||||||
headerVal = current.second.mid(10);
|
headerVal = current.second.mid(10);
|
||||||
#endif
|
#endif
|
||||||
@ -583,6 +591,7 @@ QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMet
|
|||||||
phase = Phase2;
|
phase = Phase2;
|
||||||
} else {
|
} else {
|
||||||
phase = Done;
|
phase = Done;
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QByteArray phase3Token;
|
QByteArray phase3Token;
|
||||||
@ -595,6 +604,9 @@ QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMet
|
|||||||
response = phase3Token.toBase64();
|
response = phase3Token.toBase64();
|
||||||
phase = Done;
|
phase = Done;
|
||||||
challenge = "";
|
challenge = "";
|
||||||
|
} else {
|
||||||
|
phase = Done;
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1677,26 +1689,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);
|
q_GSSAPI_error_int(message, minStat, GSS_C_MECH_CODE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gss_name_t qGSsapiGetServiceName(const QString &host)
|
||||||
|
{
|
||||||
|
QByteArray serviceName = "HTTPS@" + host.toLocal8Bit();
|
||||||
|
gss_buffer_desc nameDesc = {static_cast<std::size_t>(serviceName.size()), serviceName.data()};
|
||||||
|
|
||||||
|
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
|
// Send initial GSS authentication token
|
||||||
static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, const QString &host)
|
static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, const QString &host)
|
||||||
{
|
{
|
||||||
OM_uint32 majStat, minStat;
|
|
||||||
|
|
||||||
if (!ctx->gssApiHandles)
|
if (!ctx->gssApiHandles)
|
||||||
ctx->gssApiHandles.reset(new QGssApiHandles);
|
ctx->gssApiHandles.reset(new QGssApiHandles);
|
||||||
|
|
||||||
// Convert target name to internal form
|
// Convert target name to internal form
|
||||||
QByteArray serviceName = QStringLiteral("HTTPS@%1").arg(host).toLocal8Bit();
|
gss_name_t name = qGSsapiGetServiceName(host);
|
||||||
gss_buffer_desc nameDesc = {static_cast<std::size_t>(serviceName.size()), serviceName.data()};
|
if (name == nullptr) {
|
||||||
|
|
||||||
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);
|
ctx->gssApiHandles.reset(nullptr);
|
||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
|
ctx->gssApiHandles->targetName = name;
|
||||||
|
|
||||||
// Call qGssapiContinue with GSS_C_NO_CONTEXT to get initial packet
|
// Call qGssapiContinue with GSS_C_NO_CONTEXT to get initial packet
|
||||||
ctx->gssApiHandles->gssCtx = GSS_C_NO_CONTEXT;
|
ctx->gssApiHandles->gssCtx = GSS_C_NO_CONTEXT;
|
||||||
@ -1750,6 +1772,28 @@ static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, const QByteArray&
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool qGssapiTestGetCredentials(const QString &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 ----------------------------------------------
|
// ---------------------------- End of GSSAPI code ----------------------------------------------
|
||||||
|
|
||||||
#endif // gssapi
|
#endif // gssapi
|
||||||
|
@ -113,7 +113,7 @@ public:
|
|||||||
QByteArray digestMd5Response(const QByteArray &challenge, const QByteArray &method, const QByteArray &path);
|
QByteArray digestMd5Response(const QByteArray &challenge, const QByteArray &method, const QByteArray &path);
|
||||||
static QHash<QByteArray, QByteArray> parseDigestAuthenticationChallenge(const QByteArray &challenge);
|
static QHash<QByteArray, QByteArray> parseDigestAuthenticationChallenge(const QByteArray &challenge);
|
||||||
|
|
||||||
void parseHttpResponse(const QList<QPair<QByteArray, QByteArray> >&, bool isProxy);
|
void parseHttpResponse(const QList<QPair<QByteArray, QByteArray> >&, bool isProxy, const QString &host);
|
||||||
void updateCredentials();
|
void updateCredentials();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -607,7 +607,7 @@ void QHttpSocketEngine::slotSocketReadNotification()
|
|||||||
priv->hasFailed = true;
|
priv->hasFailed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
priv->parseHttpResponse(d->reply->header(), true);
|
priv->parseHttpResponse(d->reply->header(), true, d->proxy.hostName());
|
||||||
|
|
||||||
if (priv->phase == QAuthenticatorPrivate::Invalid) {
|
if (priv->phase == QAuthenticatorPrivate::Invalid) {
|
||||||
// problem parsing the reply
|
// problem parsing the reply
|
||||||
|
@ -83,7 +83,7 @@ void tst_QAuthenticator::basicAuth()
|
|||||||
|
|
||||||
QList<QPair<QByteArray, QByteArray> > headers;
|
QList<QPair<QByteArray, QByteArray> > headers;
|
||||||
headers << qMakePair(QByteArray("WWW-Authenticate"), "Basic " + data.toUtf8());
|
headers << qMakePair(QByteArray("WWW-Authenticate"), "Basic " + data.toUtf8());
|
||||||
priv->parseHttpResponse(headers, /*isProxy = */ false);
|
priv->parseHttpResponse(headers, /*isProxy = */ false, {});
|
||||||
|
|
||||||
QCOMPARE(auth.realm(), realm);
|
QCOMPARE(auth.realm(), realm);
|
||||||
QCOMPARE(auth.option("realm").toString(), realm);
|
QCOMPARE(auth.option("realm").toString(), realm);
|
||||||
@ -131,7 +131,7 @@ void tst_QAuthenticator::ntlmAuth()
|
|||||||
// Current implementation uses flags:
|
// Current implementation uses flags:
|
||||||
// NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_REQUEST_TARGET
|
// NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_REQUEST_TARGET
|
||||||
headers << qMakePair(QByteArrayLiteral("WWW-Authenticate"), QByteArrayLiteral("NTLM"));
|
headers << qMakePair(QByteArrayLiteral("WWW-Authenticate"), QByteArrayLiteral("NTLM"));
|
||||||
priv->parseHttpResponse(headers, /*isProxy = */ false);
|
priv->parseHttpResponse(headers, /*isProxy = */ false, {});
|
||||||
if (sso)
|
if (sso)
|
||||||
QVERIFY(priv->calculateResponse("GET", "/", "").startsWith("NTLM "));
|
QVERIFY(priv->calculateResponse("GET", "/", "").startsWith("NTLM "));
|
||||||
else
|
else
|
||||||
@ -140,7 +140,7 @@ void tst_QAuthenticator::ntlmAuth()
|
|||||||
// NTLM phase 2: challenge
|
// NTLM phase 2: challenge
|
||||||
headers.clear();
|
headers.clear();
|
||||||
headers << qMakePair(QByteArray("WWW-Authenticate"), "NTLM " + data.toUtf8());
|
headers << qMakePair(QByteArray("WWW-Authenticate"), "NTLM " + data.toUtf8());
|
||||||
priv->parseHttpResponse(headers, /*isProxy = */ false);
|
priv->parseHttpResponse(headers, /*isProxy = */ false, {});
|
||||||
|
|
||||||
QEXPECT_FAIL("with-realm", "NTLM authentication code doesn't extract the realm", Continue);
|
QEXPECT_FAIL("with-realm", "NTLM authentication code doesn't extract the realm", Continue);
|
||||||
QEXPECT_FAIL("with-realm-sso", "NTLM authentication code doesn't extract the realm", Continue);
|
QEXPECT_FAIL("with-realm-sso", "NTLM authentication code doesn't extract the realm", Continue);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user