Schannel: Add support for proper listing of ciphers

Reworked the code to list supported ciphers for Schannel
backend to provide the proper cipher names and cipher
properties.
Note: restricting the used ciphers for a TLS connection
is only supported for TLS 1.2 yet!

[ChangeLog][QtNetwork][QSslConfiguration] Add support for
listing supported ciphers with Schannel backend.

Change-Id: Idfc17335ec489315387a3e84b7a76a263be22378
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
bb10
Tobias Koenig 2023-04-28 10:42:04 +02:00
parent ef7d809eaf
commit f7792d2b6d
5 changed files with 389 additions and 55 deletions

View File

@ -550,8 +550,6 @@ void QSslConfiguration::setPrivateKey(const QSslKey &key)
ciphers. You can revert to using the entire set by calling
setCiphers() with the list returned by supportedCiphers().
\note This is not currently supported in the Schannel backend.
\sa setCiphers(), supportedCiphers()
*/
QList<QSslCipher> QSslConfiguration::ciphers() const
@ -567,8 +565,6 @@ QList<QSslCipher> QSslConfiguration::ciphers() const
Restricting the cipher suite must be done before the handshake
phase, where the session cipher is chosen.
\note This is not currently supported in the Schannel backend.
\sa ciphers(), supportedCiphers()
*/
void QSslConfiguration::setCiphers(const QList<QSslCipher> &ciphers)
@ -590,7 +586,8 @@ void QSslConfiguration::setCiphers(const QList<QSslCipher> &ciphers)
must be done before the handshake phase, where the session cipher
is chosen.
\note This is not currently supported in the Schannel backend.
\note With the Schannel backend the order of the ciphers is ignored and Schannel
picks the most secure one during the handshake.
\sa ciphers()
*/

View File

@ -889,20 +889,28 @@ QSslCipher QTlsBackend::createCiphersuite(const QString &suiteName, QSsl::SslPro
/*!
\internal
Auxiliary function. Creates a new QSslCipher from \a name (which is an implementation-specific
string), \a protocol and \a protocolString, e.g.:
Auxiliary function. Creates a new QSslCipher from \a name, \a keyExchangeMethod, \a encryptionMethod,
\a authenticationMethod, \a bits, \a protocol version and \a protocolString.
For example:
\code
createCipher(QStringLiteral("schannel"), QSsl::TlsV1_2, "TLSv1.2"_L1);
createCiphersuite("ECDHE-RSA-AES256-GCM-SHA256"_L1, "ECDH"_L1, "AES"_L1, "RSA"_L1, 256,
QSsl::TlsV1_2, "TLSv1.2"_L1);
\endcode
*/
QSslCipher QTlsBackend::createCipher(const QString &name, QSsl::SslProtocol protocol,
const QString &protocolString)
QSslCipher QTlsBackend::createCiphersuite(const QString &name, const QString &keyExchangeMethod,
const QString &encryptionMethod,
const QString &authenticationMethod,
int bits, QSsl::SslProtocol protocol,
const QString &protocolString)
{
// Note the name 'createCipher' (not 'ciphersuite'): we don't provide
// information about Kx, Au, bits/supported etc.
QSslCipher cipher;
cipher.d->isNull = false;
cipher.d->name = name;
cipher.d->bits = bits;
cipher.d->supportedBits = bits;
cipher.d->keyExchangeMethod = keyExchangeMethod;
cipher.d->encryptionMethod = encryptionMethod;
cipher.d->authenticationMethod = authenticationMethod;
cipher.d->protocol = protocol;
cipher.d->protocolString = protocolString;
return cipher;

View File

@ -346,8 +346,11 @@ public:
static QSslCipher createCiphersuite(const QString &description, int bits, int supportedBits);
static QSslCipher createCiphersuite(const QString &suiteName, QSsl::SslProtocol protocol,
const QString &protocolString);
static QSslCipher createCipher(const QString &name, QSsl::SslProtocol protocol,
const QString &protocolString);
static QSslCipher createCiphersuite(const QString &name, const QString &keyExchangeMethod,
const QString &encryptionMethod,
const QString &authenticationMethod,
int bits, QSsl::SslProtocol protocol,
const QString &protocolString);
// Those statics are implemented using QSslSocketPrivate (which is not exported,
// unlike QTlsBackend).

View File

@ -87,6 +87,12 @@
#ifndef SP_PROT_TLS1_3
#define SP_PROT_TLS1_3 (SP_PROT_TLS1_3_CLIENT | SP_PROT_TLS1_3_SERVER)
#endif
#ifndef BCRYPT_ECDH_ALGORITHM
#define BCRYPT_ECDH_ALGORITHM L"ECDH"
#endif
#ifndef BCRYPT_ECDSA_ALGORITHM
#define BCRYPT_ECDSA_ALGORITHM L"ECDSA"
#endif
/*
@future!:
@ -108,10 +114,6 @@
- Check if SEC_I_INCOMPLETE_CREDENTIALS is still returned for both "missing certificate" and
"missing PSK" when calling InitializeSecurityContext in "performHandshake".
Medium priority:
- Setting cipher-suites (or ALG_ID)
- People have survived without it in WinRT
Low priority:
- Possibly make RAII wrappers for SecBuffer (which I commonly create QScopeGuards for)
@ -127,39 +129,341 @@ Q_LOGGING_CATEGORY(lcTlsBackendSchannel, "qt.tlsbackend.schannel");
QByteArray _q_makePkcs12(const QList<QSslCertificate> &certs, const QSslKey &key,
const QString &passPhrase);
namespace QTlsPrivate {
QList<QSslCipher> defaultCiphers()
{
// Previously the code was in QSslSocketBackendPrivate.
QList<QSslCipher> ciphers;
const QString protocolStrings[] = { QStringLiteral("TLSv1"), QStringLiteral("TLSv1.1"),
QStringLiteral("TLSv1.2"), QStringLiteral("TLSv1.3") };
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
const QSsl::SslProtocol protocols[] = { QSsl::TlsV1_0, QSsl::TlsV1_1,
QSsl::TlsV1_2, QSsl::TlsV1_3 };
QT_WARNING_POP
const int size = ARRAYSIZE(protocols);
static_assert(size == ARRAYSIZE(protocolStrings));
ciphers.reserve(size);
for (int i = 0; i < size; ++i) {
const QSslCipher cipher = QTlsBackend::createCipher(QStringLiteral("Schannel"),
protocols[i], protocolStrings[i]);
ciphers.append(cipher);
}
return ciphers;
}
} // namespace QTlsPrivate
namespace {
bool supportsTls13();
}
namespace QTlsPrivate {
QList<QSslCipher> defaultCiphers();
struct SchannelCipherInfo {
const char *openSslCipherSuite;
const char *schannelCipherSuite;
const char *keyExchangeMethod;
const char *authenticationMethod;
const char *encryptionMethod;
int encryptionBits;
const char *hashMethod;
QList<QSsl::SslProtocol> protocols;
};
// The list of supported ciphers according to
// https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-server-2022
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
std::array<SchannelCipherInfo, 44> schannelCipherInfo = {{
{"TLS_AES_256_GCM_SHA384", "TLS_AES_256_GCM_SHA384", "", "", "AES", 256, "SHA384", {QSsl::TlsV1_3}},
{"TLS_AES_128_GCM_SHA256", "TLS_AES_128_GCM_SHA256", "", "", "AES", 128, "SHA256", {QSsl::TlsV1_3}},
{"ECDHE-ECDSA-AES256-GCM-SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "ECDH", "ECDSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
{"ECDHE-ECDSA-AES128-GCM-SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "ECDH", "ECDSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"ECDHE-RSA-AES256-GCM-SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "ECDH", "RSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
{"ECDHE-RSA-AES128-GCM-SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "ECDH", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"DHE-RSA-AES256-GCM-SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "DH", "RSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
{"DHE-RSA-AES128-GCM-SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "DH", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"ECDHE-ECDSA-AES256-SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "ECDH", "ECDSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
{"ECDHE-ECDSA-AES128-SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "ECDH", "ECDSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"ECDHE-RSA-AES256-SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "ECDH", "RSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
{"ECDHE-RSA-AES128-SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "ECDH", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"ECDHE-ECDSA-AES256-SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "ECDH", "ECDSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"ECDHE-ECDSA-AES128-SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "ECDH", "ECDSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"ECDHE-RSA-AES256-SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "ECDH", "RSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"ECDHE-RSA-AES128-SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "ECDH", "RSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"AES256-GCM-SHA384", "TLS_RSA_WITH_AES_256_GCM_SHA384", "RSA", "RSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
{"AES128-GCM-SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "RSA", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"AES256-SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA256", "RSA", "RSA", "AES", 256, "SHA256", {QSsl::TlsV1_2}},
{"AES128-SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA256", "RSA", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"AES256-SHA", "TLS_RSA_WITH_AES_256_CBC_SHA", "RSA", "RSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"AES128-SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "RSA", "RSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"DES-CBC3-SHA", "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "RSA", "RSA", "3DES", 168, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"NULL-SHA256", "TLS_RSA_WITH_NULL_SHA256", "RSA", "RSA", "", 0, "SHA256", {QSsl::TlsV1_2}},
{"NULL-SHA", "TLS_RSA_WITH_NULL_SHA", "RSA", "RSA", "", 0, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
// the following cipher suites are not enabled by default in schannel provider
{"TLS_CHACHA20_POLY1305_SHA256", "TLS_CHACHA20_POLY1305_SHA256", "", "", "CHACHA20_POLY1305", 0, "", {QSsl::TlsV1_3}},
{"DHE-RSA-AES256-SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "DH", "RSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"DHE-RSA-AES128-SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "DH", "RSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"DHE-DSS-AES256-SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "DH", "DSA", "AES", 256, "SHA256", {QSsl::TlsV1_2}},
{"DHE-DSS-AES128-SHA256", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "DH", "DSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"DHE-DSS-AES256-SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "DH", "DSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"DHE-DSS-AES128-SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "DH", "DSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"EDH-DSS-DES-CBC3-SHA", "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "DH", "DSA", "3DES", 168, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"RC4-SHA", "TLS_RSA_WITH_RC4_128_SHA", "RSA", "RSA", "RC4", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"RC4-MD5", "TLS_RSA_WITH_RC4_128_MD5", "RSA", "RSA", "RC4", 128, "MD5", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"DES-CBC-SHA", "TLS_RSA_WITH_DES_CBC_SHA", "RSA", "RSA", "DES", 56, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"EDH-DSS-DES-CBC-SHA", "TLS_DHE_DSS_WITH_DES_CBC_SHA", "DH", "DSA", "DES", 56, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
{"NULL-MD5", "TLS_RSA_WITH_NULL_MD5", "RSA", "RSA", "", 0, "MD5", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
// PSK cipher suites
{"PSK-AES256-GCM-SHA384", "TLS_PSK_WITH_AES_256_GCM_SHA384", "PSK", "", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
{"PSK-AES128-GCM-SHA256", "TLS_PSK_WITH_AES_128_GCM_SHA256", "PSK", "", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"PSK-AES256-CBC-SHA384", "TLS_PSK_WITH_AES_256_CBC_SHA384", "PSK", "", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
{"PSK-AES128-CBC-SHA256", "TLS_PSK_WITH_AES_128_CBC_SHA256", "PSK", "", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
{"PSK-NULL-SHA384", "TLS_PSK_WITH_NULL_SHA384", "PSK", "", "", 0, "SHA384", {QSsl::TlsV1_2}},
{"PSK-NULL-SHA256", "TLS_PSK_WITH_NULL_SHA256", "PSK", "", "", 0, "SHA256", {QSsl::TlsV1_2}},
}};
QT_WARNING_POP
const SchannelCipherInfo *cipherInfoByOpenSslName(const QString &name)
{
for (const auto &cipherInfo : schannelCipherInfo) {
if (name == QLatin1StringView(cipherInfo.openSslCipherSuite))
return &cipherInfo;
}
return nullptr;
}
UNICODE_STRING cbcChainingMode = {
sizeof(BCRYPT_CHAIN_MODE_CBC) - 2,
sizeof(BCRYPT_CHAIN_MODE_CBC),
const_cast<PWSTR>(BCRYPT_CHAIN_MODE_CBC)
};
UNICODE_STRING gcmChainingMode = {
sizeof(BCRYPT_CHAIN_MODE_GCM) - 2,
sizeof(BCRYPT_CHAIN_MODE_GCM),
const_cast<PWSTR>(BCRYPT_CHAIN_MODE_GCM)
};
/**
Determines which algorithms are not used by the requested ciphers to build
up a black list that can be passed to SCH_CREDENTIALS.
*/
QList<CRYPTO_SETTINGS> cryptoSettingsForCiphers(const QList<QSslCipher> &ciphers)
{
static const QList<QSslCipher> defaultCipherList = defaultCiphers();
if (defaultCipherList == ciphers) {
// the ciphers have not been restricted for this session, so no black listing needed
return {};
}
QList<const SchannelCipherInfo*> cipherInfo;
for (const auto &cipher : ciphers) {
if (cipher.isNull())
continue;
const auto *info = cipherInfoByOpenSslName(cipher.name());
if (!cipherInfo.contains(info))
cipherInfo.append(info);
}
QList<CRYPTO_SETTINGS> cryptoSettings;
const auto assignUnicodeString = [](UNICODE_STRING &unicodeString, const wchar_t *characters) {
unicodeString.Length = static_cast<USHORT>(wcslen(characters) * sizeof(WCHAR));
unicodeString.MaximumLength = unicodeString.Length + sizeof(UNICODE_NULL);
unicodeString.Buffer = const_cast<wchar_t*>(characters);
};
// black list of key exchange algorithms
const auto allKeyExchangeAlgorithms = {BCRYPT_RSA_ALGORITHM,
BCRYPT_ECDH_ALGORITHM,
BCRYPT_DH_ALGORITHM};
for (const auto &algorithm : allKeyExchangeAlgorithms) {
const auto method = QStringView(algorithm);
const auto usesMethod = [method](const SchannelCipherInfo *info) {
return QLatin1StringView(info->keyExchangeMethod) == method;
};
const bool exclude = std::none_of(cipherInfo.cbegin(), cipherInfo.cend(), usesMethod);
if (exclude) {
CRYPTO_SETTINGS settings = {};
settings.eAlgorithmUsage = TlsParametersCngAlgUsageKeyExchange;
assignUnicodeString(settings.strCngAlgId, algorithm);
cryptoSettings.append(settings);
}
}
// black list of authentication algorithms
const auto allAuthenticationAlgorithms = {BCRYPT_RSA_ALGORITHM,
BCRYPT_DSA_ALGORITHM,
BCRYPT_ECDSA_ALGORITHM,
BCRYPT_DH_ALGORITHM};
for (const auto &algorithm : allAuthenticationAlgorithms) {
const auto method = QStringView(algorithm);
const auto usesMethod = [method](const SchannelCipherInfo *info) {
return QLatin1StringView(info->authenticationMethod) == method;
};
const bool exclude = std::none_of(cipherInfo.begin(), cipherInfo.end(), usesMethod);
if (exclude) {
CRYPTO_SETTINGS settings = {};
settings.eAlgorithmUsage = TlsParametersCngAlgUsageSignature;
assignUnicodeString(settings.strCngAlgId, algorithm);
cryptoSettings.append(settings);
}
}
// black list of encryption algorithms
const auto allEncryptionAlgorithms = {BCRYPT_AES_ALGORITHM,
BCRYPT_RC4_ALGORITHM,
BCRYPT_DES_ALGORITHM,
BCRYPT_3DES_ALGORITHM};
for (const auto &algorithm : allEncryptionAlgorithms) {
const auto method = QStringView(algorithm);
if (method == QLatin1StringView("AES")) {
bool uses128Bit = false;
bool uses256Bit = false;
bool usesGcm = false;
bool usesCbc = false;
for (const auto *info : cipherInfo) {
if (QLatin1StringView(info->encryptionMethod) == method) {
uses128Bit = uses128Bit || (info->encryptionBits == 128);
uses256Bit = uses256Bit || (info->encryptionBits == 256);
usesGcm = usesGcm ||
QLatin1StringView(info->schannelCipherSuite).contains("_GCM_"_L1);
usesCbc = usesCbc ||
QLatin1StringView(info->schannelCipherSuite).contains("_CBC_"_L1);
}
}
CRYPTO_SETTINGS settings = {};
settings.eAlgorithmUsage = TlsParametersCngAlgUsageCipher;
assignUnicodeString(settings.strCngAlgId, algorithm);
if (usesGcm && !usesCbc) {
settings.cChainingModes = 1;
settings.rgstrChainingModes = &cbcChainingMode;
} else if (!usesGcm && usesCbc) {
settings.cChainingModes = 1;
settings.rgstrChainingModes = &gcmChainingMode;
}
if (!uses128Bit && uses256Bit) {
settings.dwMinBitLength = 256;
cryptoSettings.append(settings);
} else if (uses128Bit && !uses256Bit) {
settings.dwMaxBitLength = 128;
cryptoSettings.append(settings);
} else if (!uses128Bit && !uses256Bit) {
cryptoSettings.append(settings);
}
} else {
const auto usesMethod = [method](const SchannelCipherInfo *info) {
return QLatin1StringView(info->encryptionMethod) == method;
};
const bool exclude = std::none_of(cipherInfo.begin(), cipherInfo.end(), usesMethod);
if (exclude) {
CRYPTO_SETTINGS settings = {};
settings.eAlgorithmUsage = TlsParametersCngAlgUsageCipher;
assignUnicodeString(settings.strCngAlgId, algorithm);
cryptoSettings.append(settings);
}
}
}
// black list of hash algorithms
const auto allHashAlgorithms = {BCRYPT_MD5_ALGORITHM,
BCRYPT_SHA1_ALGORITHM,
BCRYPT_SHA256_ALGORITHM,
BCRYPT_SHA384_ALGORITHM};
for (const auto &algorithm : allHashAlgorithms) {
const auto method = QStringView(algorithm);
const auto usesMethod = [method](const SchannelCipherInfo *info) {
return QLatin1StringView(info->hashMethod) == method;
};
const bool exclude = std::none_of(cipherInfo.begin(), cipherInfo.end(), usesMethod);
if (exclude) {
CRYPTO_SETTINGS settings = {};
settings.eAlgorithmUsage = TlsParametersCngAlgUsageDigest;
assignUnicodeString(settings.strCngAlgId, algorithm);
cryptoSettings.append(settings);
}
}
return cryptoSettings;
}
QList<QSslCipher> ciphersByName(QStringView schannelSuiteName)
{
QList<QSslCipher> ciphers;
for (const auto &cipher : schannelCipherInfo) {
if (QLatin1StringView(cipher.schannelCipherSuite) == schannelSuiteName) {
for (const auto &protocol : cipher.protocols) {
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
const QString protocolName = (
protocol == QSsl::TlsV1_0 ? QStringLiteral("TLSv1.0") :
protocol == QSsl::TlsV1_1 ? QStringLiteral("TLSv1.1") :
protocol == QSsl::TlsV1_2 ? QStringLiteral("TLSv1.2") :
protocol == QSsl::TlsV1_3 ? QStringLiteral("TLSv1.3") :
QString());
QT_WARNING_POP
ciphers.append(QTlsBackend::createCiphersuite(QLatin1StringView(cipher.openSslCipherSuite),
QLatin1StringView(cipher.keyExchangeMethod),
QLatin1StringView(cipher.encryptionMethod),
QLatin1StringView(cipher.authenticationMethod),
cipher.encryptionBits,
protocol, protocolName));
}
}
}
return ciphers;
}
QList<QSslCipher> defaultCiphers()
{
ULONG contextFunctionsCount = {};
PCRYPT_CONTEXT_FUNCTIONS contextFunctions = {};
const auto status = BCryptEnumContextFunctions(CRYPT_LOCAL, L"SSL", NCRYPT_SCHANNEL_INTERFACE,
&contextFunctionsCount, &contextFunctions);
if (!NT_SUCCESS(status)) {
qCWarning(lcTlsBackendSchannel, "Failed to enumerate ciphers");
return {};
}
const bool supportsV13 = supportsTls13();
QList<QSslCipher> ciphers;
for (ULONG index = 0; index < contextFunctions->cFunctions; ++index) {
const auto suiteName = QStringView(contextFunctions->rgpszFunctions[index]);
const QList<QSslCipher> allCiphers = ciphersByName(suiteName);
for (const auto &cipher : allCiphers) {
if (!supportsV13 && (cipher.protocol() == QSsl::TlsV1_3))
continue;
ciphers.append(cipher);
}
}
BCryptFreeBuffer(contextFunctions);
return ciphers;
}
bool containsTls13Cipher(const QList<QSslCipher> &ciphers)
{
return std::any_of(ciphers.cbegin(), ciphers.cend(),
[](const QSslCipher &cipher) { return cipher.protocol() == QSsl::TlsV1_3; });
}
} // namespace QTlsPrivate
bool QSchannelBackend::s_loadedCiphersAndCerts = false;
Q_GLOBAL_STATIC(QRecursiveMutex, qt_schannel_mutex)
@ -458,10 +762,10 @@ QT_WARNING_POP
// 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 negatedSchannelProtocols(DWORD wantedProtocols)
{
DWORD protocols = SP_PROT_ALL; // all protocols
protocols &= ~toSchannelProtocol(protocol); // minus the one(s) we want
protocols &= ~wantedProtocols; // minus the one(s) we want
return protocols;
}
@ -778,7 +1082,7 @@ bool TlsCryptographSchannel::acquireCredentialsHandle()
Q_ASSERT(schannelState == SchannelState::InitializeHandshake);
const bool isClient = d->tlsMode() == QSslSocket::SslClientMode;
const DWORD protocols = toSchannelProtocol(configuration.protocol());
DWORD protocols = toSchannelProtocol(configuration.protocol());
if (protocols == DWORD(-1)) {
setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError,
QSslSocket::tr("Invalid protocol chosen"));
@ -833,12 +1137,18 @@ bool TlsCryptographSchannel::acquireCredentialsHandle()
Q_ASSERT(localCertContext);
}
const QList<QSslCipher> ciphers = configuration.ciphers();
if (!containsTls13Cipher(ciphers))
protocols &= ~SP_PROT_TLS1_3;
QList<CRYPTO_SETTINGS> cryptoSettings = cryptoSettingsForCiphers(ciphers);
TLS_PARAMETERS tlsParameters = {
0,
nullptr,
toSchannelProtocolNegated(configuration.protocol()), // what protocols to disable
0,
nullptr,
negatedSchannelProtocols(protocols), // what protocols to disable
static_cast<DWORD>(cryptoSettings.size()),
(cryptoSettings.isEmpty() ? nullptr : cryptoSettings.data()),
0
};
@ -1240,6 +1550,11 @@ bool TlsCryptographSchannel::verifyHandshake()
CHECK_STATUS(status);
// Get session cipher info
status = QueryContextAttributes(&contextHandle,
SECPKG_ATTR_CIPHER_INFO,
&cipherInfo);
CHECK_STATUS(status);
status = QueryContextAttributes(&contextHandle,
SECPKG_ATTR_CONNECTION_INFO,
&connectionInfo);
@ -1393,6 +1708,7 @@ void TlsCryptographSchannel::reset()
deallocateContext();
freeCredentialsHandle(); // in case we already had one (@future: session resumption requires re-use)
cipherInfo = {};
connectionInfo = {};
streamSizes = {};
@ -1767,8 +2083,17 @@ QSslCipher TlsCryptographSchannel::sessionCipher() const
Q_ASSERT(q);
if (!q->isEncrypted())
return QSslCipher();
return QSslCipher(QStringLiteral("Schannel"), sessionProtocol());
return {};
const auto sessionProtocol = toQtSslProtocol(connectionInfo.dwProtocol);
const auto ciphers = ciphersByName(QStringView(cipherInfo.szCipherSuite));
for (const auto& cipher : ciphers) {
if (cipher.protocol() == sessionProtocol)
return cipher;
}
return {};
}
QSsl::SslProtocol TlsCryptographSchannel::sessionProtocol() const

View File

@ -93,6 +93,7 @@ private:
QSslSocket *q = nullptr;
QSslSocketPrivate *d = nullptr;
SecPkgContext_CipherInfo cipherInfo = {};
SecPkgContext_ConnectionInfo connectionInfo = {};
SecPkgContext_StreamSizes streamSizes = {};