Schannel: TLS1.3 support

It's not possible to connect to microsoft.com with Schannel TLS 1.3 for
some reason (also tested with Internet Explorer), but other sites work
fine. Must be something they have to iron out for later.

In my experience this needs a preview release of Windows. One of my
machines is opted into the dev channel of Windows where they enabled TLS
1.3 by default, and it works well in my tests except for the part above.
On my other machine, after enabling TLS 1.3 through the registry, I fail
to complete the handshake with any site. So around March/April next year
is when this code would activate for most people.

MinGW apparently defines NTDDI_VERSION as the one for Windows Server
2003, so it currently doesn't build the new TLS 1.3 code. In Qt (as a
project) we could consider setting this higher, but that's out of scope
for this patch!

Fixes: QTBUG-81294
Change-Id: If329959c3a30ecbfbb8c0d335cc39ccb6d012890
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Mårten Nordheim 2020-09-30 14:44:11 +02:00
parent 844318f54a
commit 51faa0700d
3 changed files with 137 additions and 44 deletions

View File

@ -62,6 +62,12 @@
#define SUPPORTS_ALPN 1
#endif
// Redstone 5/1809 has all the API available, but TLS 1.3 is not enabled until a later version of
// Win 10, checked at runtime in supportsTls13()
#if NTDDI_VERSION >= NTDDI_WIN10_RS5
#define SUPPORTS_TLS13 1
#endif
// Not defined in MinGW
#ifndef SECBUFFER_ALERT
#define SECBUFFER_ALERT 17
@ -211,6 +217,22 @@ QString schannelErrorToString(qint32 status)
}
}
bool supportsTls13()
{
#ifdef SUPPORTS_TLS13
static bool supported = []() {
const auto current = QOperatingSystemVersion::current();
// 20221 just happens to be the preview version I run on my laptop where I tested TLS 1.3.
const auto minimum =
QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 20221);
return current >= minimum;
}();
return supported;
#else
return false;
#endif
}
DWORD toSchannelProtocol(QSsl::SslProtocol protocol)
{
DWORD protocols = SP_PROT_NONE;
@ -224,7 +246,8 @@ DWORD toSchannelProtocol(QSsl::SslProtocol protocol)
return DWORD(-1); // Not supported at the moment (@future)
case QSsl::AnyProtocol:
protocols = SP_PROT_TLS1_0 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2;
// @future Add TLS 1.3 when supported by Windows!
if (supportsTls13())
protocols |= SP_PROT_TLS1_3;
break;
case QSsl::TlsV1_0:
protocols = SP_PROT_TLS1_0;
@ -236,7 +259,7 @@ DWORD toSchannelProtocol(QSsl::SslProtocol protocol)
protocols = SP_PROT_TLS1_2;
break;
case QSsl::TlsV1_3:
if ((false)) // @future[0/1] Replace with version check once it's supported in Windows
if (supportsTls13())
protocols = SP_PROT_TLS1_3;
else
protocols = DWORD(-1);
@ -254,7 +277,7 @@ DWORD toSchannelProtocol(QSsl::SslProtocol protocol)
protocols |= SP_PROT_TLS1_2;
Q_FALLTHROUGH();
case QSsl::TlsV1_3OrLater:
if ((false)) // @future[1/1] Also replace this with a version check
if (supportsTls13())
protocols |= SP_PROT_TLS1_3;
else if (protocol == QSsl::TlsV1_3OrLater)
protocols = DWORD(-1); // if TlsV1_3OrLater was specifically chosen we should fail
@ -263,6 +286,18 @@ DWORD toSchannelProtocol(QSsl::SslProtocol protocol)
return protocols;
}
#ifdef SUPPORTS_TLS13
// In the new API that descended down upon us we are not asked which protocols we want
// but rather which protocols we don't want. So now we have this function to disable
// anything that is not enabled.
DWORD toSchannelProtocolNegated(QSsl::SslProtocol protocol)
{
DWORD protocols = SP_PROT_ALL; // all protocols
protocols &= ~toSchannelProtocol(protocol); // minus the one(s) we want
return protocols;
}
#endif
/*!
\internal
Used when converting the established session's \a protocol back to
@ -676,40 +711,80 @@ bool QSslSocketBackendPrivate::acquireCredentialsHandle()
certsCount = 1;
Q_ASSERT(localCertContext);
}
SCHANNEL_CRED cred{
SCHANNEL_CRED_VERSION, // dwVersion
certsCount, // cCreds
&localCertContext, // paCred (certificate(s) containing a private key for authentication)
nullptr, // hRootStore
0, // cMappers (reserved)
nullptr, // aphMappers (reserved)
0, // cSupportedAlgs
nullptr, // palgSupportedAlgs (nullptr = system default) @future: QSslCipher-related
protocols, // grbitEnabledProtocols
0, // dwMinimumCipherStrength (0 = system default)
0, // dwMaximumCipherStrength (0 = system default)
0, // dwSessionLifespan (0 = schannel default, 10 hours)
SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
| SCH_CRED_NO_DEFAULT_CREDS, // dwFlags
0 // dwCredFormat (must be 0)
void *credentials = nullptr;
#ifdef SUPPORTS_TLS13
TLS_PARAMETERS tlsParameters = {
0,
nullptr,
toSchannelProtocolNegated(configuration.protocol), // what protocols to disable
0,
nullptr,
0
};
if (supportsTls13()) {
SCH_CREDENTIALS *cred = new SCH_CREDENTIALS{
SCH_CREDENTIALS_VERSION,
0,
certsCount,
&localCertContext,
nullptr,
0,
nullptr,
0,
SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
| SCH_CRED_NO_DEFAULT_CREDS,
1,
&tlsParameters
};
credentials = cred;
} else
#endif // SUPPORTS_TLS13
{
SCHANNEL_CRED *cred = new SCHANNEL_CRED{
SCHANNEL_CRED_VERSION, // dwVersion
certsCount, // cCreds
&localCertContext, // paCred (certificate(s) containing a private key for authentication)
nullptr, // hRootStore
0, // cMappers (reserved)
nullptr, // aphMappers (reserved)
0, // cSupportedAlgs
nullptr, // palgSupportedAlgs (nullptr = system default)
protocols, // grbitEnabledProtocols
0, // dwMinimumCipherStrength (0 = system default)
0, // dwMaximumCipherStrength (0 = system default)
0, // dwSessionLifespan (0 = schannel default, 10 hours)
SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
| SCH_CRED_NO_DEFAULT_CREDS, // dwFlags
0 // dwCredFormat (must be 0)
};
credentials = cred;
}
Q_ASSERT(credentials != nullptr);
TimeStamp expiration{};
auto status = AcquireCredentialsHandle(nullptr, // pszPrincipal (unused)
const_cast<wchar_t *>(UNISP_NAME), // pszPackage
isClient ? SECPKG_CRED_OUTBOUND : SECPKG_CRED_INBOUND, // fCredentialUse
nullptr, // pvLogonID (unused)
&cred, // pAuthData
credentials, // pAuthData
nullptr, // pGetKeyFn (unused)
nullptr, // pvGetKeyArgument (unused)
&credentialHandle, // phCredential
&expiration // ptsExpir
);
#ifdef SUPPORTS_TLS13
if (supportsTls13()) {
delete static_cast<SCH_CREDENTIALS *>(credentials);
} else
#endif // SUPPORTS_TLS13
{
delete static_cast<SCHANNEL_CRED *>(credentials);
}
if (status != SEC_E_OK) {
setErrorAndEmit(QAbstractSocket::SslInternalError, schannelErrorToString(status));
return false;
@ -1194,6 +1269,9 @@ bool QSslSocketBackendPrivate::renegotiate()
if (status == SEC_I_CONTINUE_NEEDED) {
schannelState = SchannelState::PerformHandshake;
return sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer);
} else if (status == SEC_E_OK) {
schannelState = SchannelState::PerformHandshake;
return true;
}
setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError,
QSslSocket::tr("Renegotiation was unsuccessful: %1").arg(schannelErrorToString(status)));

View File

@ -58,8 +58,11 @@ QT_REQUIRE_CONFIG(schannel);
#include "qsslsocket_p.h"
#define SECURITY_WIN32
#define SCHANNEL_USE_BLACKLISTS 1
#include <Winternl.h> // needed for UNICODE defines
#include <security.h>
#include <schnlsp.h>
#undef SCHANNEL_USE_BLACKLISTS
#undef SECURITY_WIN32
QT_BEGIN_NAMESPACE

View File

@ -341,6 +341,24 @@ QString httpServerCertChainPath()
#endif // QT_TEST_SERVER
}
bool supportsTls13()
{
#ifdef TLS1_3_VERSION
return true;
#elif QT_CONFIG(schannel)
// Copied from qsslsocket_schannel.cpp #supportsTls13()
static bool supported = []() {
const auto current = QOperatingSystemVersion::current();
const auto minimum =
QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 20221);
return current >= minimum;
}();
return supported;
#else
return false;
#endif
}
} // unnamed namespace
tst_QSslSocket::tst_QSslSocket()
@ -1168,13 +1186,12 @@ void tst_QSslSocket::protocol()
socket->abort();
}
#ifdef TLS1_3_VERSION
{
if (supportsTls13()) {
// qt-test-server probably doesn't allow TLSV1.3
socket->setProtocol(QSsl::TlsV1_3);
QCOMPARE(socket->protocol(), QSsl::TlsV1_3);
socket->connectToHostEncrypted(QtNetworkSettings::httpServerName(), 443);
if (setProxy && !socket->waitForEncrypted())
if (!socket->waitForEncrypted(10'000))
QSKIP("TLS 1.3 is not supported by the test server or the test is flaky - see QTBUG-29941");
QCOMPARE(socket->protocol(), QSsl::TlsV1_3);
socket->abort();
@ -1182,12 +1199,11 @@ void tst_QSslSocket::protocol()
socket->connectToHost(QtNetworkSettings::httpServerName(), 443);
QVERIFY2(socket->waitForConnected(), qPrintable(socket->errorString()));
socket->startClientEncryption();
if (setProxy && !socket->waitForEncrypted())
if (!socket->waitForEncrypted(10'000))
QSKIP("TLS 1.3 is not supported by the test server or the test is flaky - see QTBUG-29941");
QCOMPARE(socket->sessionProtocol(), QSsl::TlsV1_3);
socket->abort();
}
#endif // TLS1_3_VERSION
{
// qt-test-server allows SSLV3, so it allows AnyProtocol.
socket->setProtocol(QSsl::AnyProtocol);
@ -1335,30 +1351,26 @@ void tst_QSslSocket::protocolServerSide_data()
QTest::newRow("tls1.0orlater-tls1.0") << QSsl::TlsV1_0OrLater << QSsl::TlsV1_0 << true;
QTest::newRow("tls1.0orlater-tls1.1") << QSsl::TlsV1_0OrLater << QSsl::TlsV1_1 << true;
QTest::newRow("tls1.0orlater-tls1.2") << QSsl::TlsV1_0OrLater << QSsl::TlsV1_2 << true;
#ifdef TLS1_3_VERSION
QTest::newRow("tls1.0orlater-tls1.3") << QSsl::TlsV1_0OrLater << QSsl::TlsV1_3 << true;
#endif
if (supportsTls13())
QTest::newRow("tls1.0orlater-tls1.3") << QSsl::TlsV1_0OrLater << QSsl::TlsV1_3 << true;
QTest::newRow("tls1.1orlater-tls1.0") << QSsl::TlsV1_1OrLater << QSsl::TlsV1_0 << false;
QTest::newRow("tls1.1orlater-tls1.1") << QSsl::TlsV1_1OrLater << QSsl::TlsV1_1 << true;
QTest::newRow("tls1.1orlater-tls1.2") << QSsl::TlsV1_1OrLater << QSsl::TlsV1_2 << true;
#ifdef TLS1_3_VERSION
QTest::newRow("tls1.1orlater-tls1.3") << QSsl::TlsV1_1OrLater << QSsl::TlsV1_3 << true;
#endif
if (supportsTls13())
QTest::newRow("tls1.1orlater-tls1.3") << QSsl::TlsV1_1OrLater << QSsl::TlsV1_3 << true;
QTest::newRow("tls1.2orlater-tls1.0") << QSsl::TlsV1_2OrLater << QSsl::TlsV1_0 << false;
QTest::newRow("tls1.2orlater-tls1.1") << QSsl::TlsV1_2OrLater << QSsl::TlsV1_1 << false;
QTest::newRow("tls1.2orlater-tls1.2") << QSsl::TlsV1_2OrLater << QSsl::TlsV1_2 << true;
#ifdef TLS1_3_VERSION
QTest::newRow("tls1.2orlater-tls1.3") << QSsl::TlsV1_2OrLater << QSsl::TlsV1_3 << true;
#endif
#ifdef TLS1_3_VERSION
QTest::newRow("tls1.3orlater-tls1.0") << QSsl::TlsV1_3OrLater << QSsl::TlsV1_0 << false;
QTest::newRow("tls1.3orlater-tls1.1") << QSsl::TlsV1_3OrLater << QSsl::TlsV1_1 << false;
QTest::newRow("tls1.3orlater-tls1.2") << QSsl::TlsV1_3OrLater << QSsl::TlsV1_2 << false;
QTest::newRow("tls1.3orlater-tls1.3") << QSsl::TlsV1_3OrLater << QSsl::TlsV1_3 << true;
#endif // TLS1_3_VERSION
if (supportsTls13()) {
QTest::newRow("tls1.2orlater-tls1.3") << QSsl::TlsV1_2OrLater << QSsl::TlsV1_3 << true;
QTest::newRow("tls1.3orlater-tls1.0") << QSsl::TlsV1_3OrLater << QSsl::TlsV1_0 << false;
QTest::newRow("tls1.3orlater-tls1.1") << QSsl::TlsV1_3OrLater << QSsl::TlsV1_1 << false;
QTest::newRow("tls1.3orlater-tls1.2") << QSsl::TlsV1_3OrLater << QSsl::TlsV1_2 << false;
QTest::newRow("tls1.3orlater-tls1.3") << QSsl::TlsV1_3OrLater << QSsl::TlsV1_3 << true;
}
QTest::newRow("any-tls1.0") << QSsl::AnyProtocol << QSsl::TlsV1_0 << true;
QTest::newRow("any-secure") << QSsl::AnyProtocol << QSsl::SecureProtocols << true;