Schannel: Add ALPN support
[ChangeLog][QtNetwork][SSL] The Schannel backend now supports ALPN and thus HTTP/2. Change-Id: I1819a936ec3c9e0118b9dad12681f791262d4db2 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>bb10
parent
004d7168a3
commit
698078680f
|
|
@ -57,6 +57,11 @@
|
|||
#include <security.h>
|
||||
#include <schnlsp.h>
|
||||
|
||||
#if NTDDI_VERSION >= NTDDI_WINBLUE && !defined(Q_CC_MINGW)
|
||||
// ALPN = Application Layer Protocol Negotiation
|
||||
#define SUPPORTS_ALPN 1
|
||||
#endif
|
||||
|
||||
// Not defined in MinGW
|
||||
#ifndef SECBUFFER_ALERT
|
||||
#define SECBUFFER_ALERT 17
|
||||
|
|
@ -391,6 +396,40 @@ Required const_reinterpret_cast(Actual *p)
|
|||
return Required(p);
|
||||
}
|
||||
|
||||
#ifdef SUPPORTS_ALPN
|
||||
bool supportsAlpn()
|
||||
{
|
||||
return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1;
|
||||
}
|
||||
|
||||
QByteArray createAlpnString(const QByteArrayList &nextAllowedProtocols)
|
||||
{
|
||||
QByteArray alpnString;
|
||||
if (!nextAllowedProtocols.isEmpty() && supportsAlpn()) {
|
||||
const QByteArray names = [&nextAllowedProtocols]() {
|
||||
QByteArray protocolString;
|
||||
for (QByteArray proto : nextAllowedProtocols) {
|
||||
if (proto.size() > 255) {
|
||||
qCWarning(lcSsl) << "TLS ALPN extension" << proto
|
||||
<< "is too long and will be truncated to 255 characters.";
|
||||
proto = proto.left(255);
|
||||
}
|
||||
protocolString += char(proto.length()) + proto;
|
||||
}
|
||||
return protocolString;
|
||||
}();
|
||||
|
||||
const quint16 namesSize = names.size();
|
||||
const quint32 alpnId = SecApplicationProtocolNegotiationExt_ALPN;
|
||||
const quint32 totalSize = sizeof(alpnId) + sizeof(namesSize) + namesSize;
|
||||
alpnString = QByteArray::fromRawData(reinterpret_cast<const char *>(&totalSize), sizeof(totalSize))
|
||||
+ QByteArray::fromRawData(reinterpret_cast<const char *>(&alpnId), sizeof(alpnId))
|
||||
+ QByteArray::fromRawData(reinterpret_cast<const char *>(&namesSize), sizeof(namesSize))
|
||||
+ names;
|
||||
}
|
||||
return alpnString;
|
||||
}
|
||||
#endif // SUPPORTS_ALPN
|
||||
} // anonymous namespace
|
||||
|
||||
bool QSslSocketPrivate::s_loadRootCertsOnDemand = true;
|
||||
|
|
@ -684,13 +723,28 @@ bool QSslSocketBackendPrivate::createContext()
|
|||
|
||||
TimeStamp expiry;
|
||||
|
||||
SecBufferDesc alpnBufferDesc;
|
||||
bool useAlpn = false;
|
||||
#ifdef SUPPORTS_ALPN
|
||||
configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNone;
|
||||
QByteArray alpnString = createAlpnString(configuration.nextAllowedProtocols);
|
||||
useAlpn = !alpnString.isEmpty();
|
||||
SecBuffer alpnBuffers[1];
|
||||
alpnBuffers[0] = createSecBuffer(alpnString, SECBUFFER_APPLICATION_PROTOCOLS);
|
||||
alpnBufferDesc = {
|
||||
SECBUFFER_VERSION,
|
||||
ARRAYSIZE(alpnBuffers),
|
||||
alpnBuffers
|
||||
};
|
||||
#endif
|
||||
|
||||
auto status = InitializeSecurityContext(&credentialHandle, // phCredential
|
||||
nullptr, // phContext
|
||||
const_reinterpret_cast<SEC_WCHAR *>(targetName().utf16()), // pszTargetName
|
||||
contextReq, // fContextReq
|
||||
0, // Reserved1
|
||||
0, // TargetDataRep (unused)
|
||||
nullptr, // pInput (no input at the moment @future: alpn)
|
||||
useAlpn ? &alpnBufferDesc : nullptr, // pInput
|
||||
0, // Reserved2
|
||||
&contextHandle, // phNewContext
|
||||
&outputBufferDesc, // pOutput
|
||||
|
|
@ -725,7 +779,19 @@ bool QSslSocketBackendPrivate::acceptContext()
|
|||
|
||||
SecBuffer inBuffers[2];
|
||||
inBuffers[0] = createSecBuffer(intermediateBuffer, SECBUFFER_TOKEN);
|
||||
inBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY);
|
||||
|
||||
#ifdef SUPPORTS_ALPN
|
||||
configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNone;
|
||||
// The string must be alive when we call AcceptSecurityContext
|
||||
QByteArray alpnString = createAlpnString(configuration.nextAllowedProtocols);
|
||||
if (!alpnString.isEmpty()) {
|
||||
inBuffers[1] = createSecBuffer(alpnString, SECBUFFER_APPLICATION_PROTOCOLS);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
inBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY);
|
||||
}
|
||||
|
||||
SecBufferDesc inputBufferDesc{
|
||||
SECBUFFER_VERSION,
|
||||
ARRAYSIZE(inBuffers),
|
||||
|
|
@ -931,6 +997,31 @@ bool QSslSocketBackendPrivate::verifyHandshake()
|
|||
SECPKG_ATTR_CONNECTION_INFO,
|
||||
&connectionInfo);
|
||||
CHECK_STATUS(status);
|
||||
|
||||
#ifdef SUPPORTS_ALPN
|
||||
if (!configuration.nextAllowedProtocols.isEmpty() && supportsAlpn()) {
|
||||
SecPkgContext_ApplicationProtocol alpn;
|
||||
status = QueryContextAttributes(&contextHandle,
|
||||
SECPKG_ATTR_APPLICATION_PROTOCOL,
|
||||
&alpn);
|
||||
CHECK_STATUS(status);
|
||||
if (alpn.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) {
|
||||
QByteArray negotiatedProto = QByteArray((const char *)alpn.ProtocolId,
|
||||
alpn.ProtocolIdSize);
|
||||
if (!configuration.nextAllowedProtocols.contains(negotiatedProto)) {
|
||||
setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError,
|
||||
QSslSocket::tr("Unwanted protocol was negotiated"));
|
||||
return false;
|
||||
}
|
||||
configuration.nextNegotiatedProtocol = negotiatedProto;
|
||||
configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNegotiated;
|
||||
} else {
|
||||
configuration.nextNegotiatedProtocol = "";
|
||||
configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationUnsupported;
|
||||
}
|
||||
}
|
||||
#endif // supports ALPN
|
||||
|
||||
#undef CHECK_STATUS
|
||||
|
||||
// Verify certificate
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@
|
|||
|
||||
#include "emulationdetector.h"
|
||||
|
||||
#if !defined(QT_NO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT)
|
||||
#if (!defined(QT_NO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT)) \
|
||||
|| QT_CONFIG(schannel)
|
||||
// HTTP/2 over TLS requires ALPN/NPN to negotiate the protocol version.
|
||||
const bool clearTextHTTP2 = false;
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -74,6 +74,16 @@ typedef QSharedPointer<QSslSocket> QSslSocketPtr;
|
|||
#endif
|
||||
#endif // QT_NO_SSL
|
||||
|
||||
// Detect ALPN (Application-Layer Protocol Negotiation) support
|
||||
#undef ALPN_SUPPORTED // Undef the variable first to be safe
|
||||
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT)
|
||||
#define ALPN_SUPPORTED 1
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(schannel) && !defined(Q_CC_MINGW)
|
||||
#define ALPN_SUPPORTED 1
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_HPUX && defined Q_CC_GNU
|
||||
// This error is delivered every time we try to use the fluke CA
|
||||
// certificate. For now we work around this bug. Task 202317.
|
||||
|
|
@ -3416,12 +3426,20 @@ void tst_QSslSocket::setEmptyDefaultConfiguration() // this test should be last,
|
|||
|
||||
void tst_QSslSocket::allowedProtocolNegotiation()
|
||||
{
|
||||
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT)
|
||||
#ifndef ALPN_SUPPORTED
|
||||
QSKIP("ALPN is unsupported, skipping test");
|
||||
#endif
|
||||
|
||||
#if QT_CONFIG(schannel)
|
||||
if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows8_1)
|
||||
QSKIP("ALPN is not supported on this version of Windows using Schannel.");
|
||||
#endif
|
||||
|
||||
QFETCH_GLOBAL(bool, setProxy);
|
||||
if (setProxy)
|
||||
return;
|
||||
|
||||
|
||||
const QByteArray expectedNegotiated("cool-protocol");
|
||||
QList<QByteArray> serverProtos;
|
||||
serverProtos << expectedNegotiated << "not-so-cool-protocol";
|
||||
|
|
@ -3449,8 +3467,6 @@ void tst_QSslSocket::allowedProtocolNegotiation()
|
|||
QVERIFY(server.socket->sslConfiguration().nextNegotiatedProtocol() ==
|
||||
clientSocket.sslConfiguration().nextNegotiatedProtocol());
|
||||
QVERIFY(server.socket->sslConfiguration().nextNegotiatedProtocol() == expectedNegotiated);
|
||||
|
||||
#endif // OPENSSL_VERSION_NUMBER
|
||||
}
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
|
|
|
|||
Loading…
Reference in New Issue