summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMårten Nordheim <marten.nordheim@qt.io>2019-01-09 16:14:42 +0100
committerMårten Nordheim <marten.nordheim@qt.io>2019-01-28 21:35:11 +0000
commit698078680fc5a6870177af285fa50c0d8a7c0bc3 (patch)
tree423ee3741bf58ca86236333eb4c06282060862bc
parent004d7168a335219b19805badae0590b0f5dcfad0 (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.cpp95
-rw-r--r--tests/auto/network/access/http2/tst_http2.cpp3
-rw-r--r--tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp22
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