diff options
author | Mårten Nordheim <marten.nordheim@qt.io> | 2019-01-09 16:14:42 +0100 |
---|---|---|
committer | Mårten Nordheim <marten.nordheim@qt.io> | 2019-01-28 21:35:11 +0000 |
commit | 698078680fc5a6870177af285fa50c0d8a7c0bc3 (patch) | |
tree | 423ee3741bf58ca86236333eb4c06282060862bc | |
parent | 004d7168a335219b19805badae0590b0f5dcfad0 (diff) |
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>
-rw-r--r-- | src/network/ssl/qsslsocket_schannel.cpp | 95 | ||||
-rw-r--r-- | tests/auto/network/access/http2/tst_http2.cpp | 3 | ||||
-rw-r--r-- | tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp | 22 |
3 files changed, 114 insertions, 6 deletions
diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp index e5f31a3fcf..b10bd546c7 100644 --- a/src/network/ssl/qsslsocket_schannel.cpp +++ b/src/network/ssl/qsslsocket_schannel.cpp @@ -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 diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index 235b78c34a..bbb314128b 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -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 diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index f34a0e8665..1c27901844 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -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 |