diff options
Diffstat (limited to 'src/plugins/tls/schannel')
-rw-r--r-- | src/plugins/tls/schannel/CMakeLists.txt | 16 | ||||
-rw-r--r-- | src/plugins/tls/schannel/qtls_schannel.cpp | 926 | ||||
-rw-r--r-- | src/plugins/tls/schannel/qtls_schannel_p.h | 41 | ||||
-rw-r--r-- | src/plugins/tls/schannel/qtlsbackend_schannel_p.h | 43 | ||||
-rw-r--r-- | src/plugins/tls/schannel/qtlskey_schannel.cpp | 52 | ||||
-rw-r--r-- | src/plugins/tls/schannel/qtlskey_schannel_p.h | 40 | ||||
-rw-r--r-- | src/plugins/tls/schannel/qx509_schannel.cpp | 210 | ||||
-rw-r--r-- | src/plugins/tls/schannel/qx509_schannel_p.h | 44 |
8 files changed, 830 insertions, 542 deletions
diff --git a/src/plugins/tls/schannel/CMakeLists.txt b/src/plugins/tls/schannel/CMakeLists.txt index b038f6056e..a7f7fcd99f 100644 --- a/src/plugins/tls/schannel/CMakeLists.txt +++ b/src/plugins/tls/schannel/CMakeLists.txt @@ -1,8 +1,11 @@ -qt_internal_add_plugin(QSchannelBackend - OUTPUT_NAME schannelbackend +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_plugin(QSchannelBackendPlugin + OUTPUT_NAME qschannelbackend CLASS_NAME QSchannelBackend - TYPE tls - DEFAULT_IF WINDOWS + PLUGIN_TYPE tls + DEFAULT_IF WIN32 SOURCES ../shared/qtlskey_base_p.h ../shared/qtlskey_base.cpp @@ -21,10 +24,11 @@ qt_internal_add_plugin(QSchannelBackend qtlskey_schannel.cpp qtlskey_schannel_p.h qx509_schannel.cpp qx509_schannel_p.h LIBRARIES + Qt::NetworkPrivate crypt32 secur32 bcrypt ncrypt - PUBLIC_LIBRARIES - Qt::NetworkPrivate + DEFINES + QT_NO_CAST_FROM_ASCII ) diff --git a/src/plugins/tls/schannel/qtls_schannel.cpp b/src/plugins/tls/schannel/qtls_schannel.cpp index 83e9cb1c6c..a244a90ebc 100644 --- a/src/plugins/tls/schannel/qtls_schannel.cpp +++ b/src/plugins/tls/schannel/qtls_schannel.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // #define QSSLSOCKET_DEBUG @@ -62,17 +26,11 @@ #include <security.h> #include <schnlsp.h> -#if NTDDI_VERSION >= NTDDI_WINBLUE && !defined(Q_CC_MINGW) +#if NTDDI_VERSION >= NTDDI_WINBLUE && defined(SECBUFFER_APPLICATION_PROTOCOLS) // ALPN = Application Layer Protocol Negotiation #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 defined(NTDDI_WIN10_RS5) && NTDDI_VERSION >= NTDDI_WIN10_RS5 -#define SUPPORTS_TLS13 1 -#endif - // Not defined in MinGW #ifndef SECBUFFER_ALERT #define SECBUFFER_ALERT 17 @@ -129,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!: @@ -150,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) @@ -161,43 +121,349 @@ QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.schannel"); +using namespace Qt::StringLiterals; + +Q_LOGGING_CATEGORY(lcTlsBackendSchannel, "qt.tlsbackend.schannel"); // Defined in qsslsocket_qt.cpp. QByteArray _q_makePkcs12(const QList<QSslCertificate> &certs, const QSslKey &key, const QString &passPhrase); +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() { - // Previously the code was in QSslSocketBackendPrivate. + 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; - // @temp (I hope), stolen from qsslsocket_winrt.cpp - const QString protocolStrings[] = { QStringLiteral("TLSv1"), QStringLiteral("TLSv1.1"), - QStringLiteral("TLSv1.2"), QStringLiteral("TLSv1.3") }; - const QSsl::SslProtocol protocols[] = { QSsl::TlsV1_0, QSsl::TlsV1_1, - QSsl::TlsV1_2, QSsl::TlsV1_3 }; - 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); + 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 -namespace { -bool supportsTls13(); -} - bool QSchannelBackend::s_loadedCiphersAndCerts = false; Q_GLOBAL_STATIC(QRecursiveMutex, qt_schannel_mutex) @@ -210,7 +476,7 @@ long QSchannelBackend::tlsLibraryVersionNumber() const QString QSchannelBackend::tlsLibraryVersionString() const { const auto os = QOperatingSystemVersion::current(); - return QString::fromLatin1("Secure Channel, %1 %2.%3.%4") + return "Secure Channel, %1 %2.%3.%4"_L1 .arg(os.name(), QString::number(os.majorVersion()), QString::number(os.minorVersion()), @@ -219,16 +485,13 @@ QString QSchannelBackend::tlsLibraryVersionString() const long QSchannelBackend::tlsLibraryBuildVersionNumber() const { - return tlsLibraryVersionNumber(); + return NTDDI_VERSION; } QString QSchannelBackend::tlsLibraryBuildVersionString() const { - const auto os = QOperatingSystemVersion::current(); - return QString::fromLatin1("%1.%2.%3") - .arg(QString::number(os.majorVersion()), - QString::number(os.minorVersion()), - QString::number(os.microVersion())); + return "Secure Channel (NTDDI: 0x%1)"_L1 + .arg(QString::number(NTDDI_VERSION, 16).toUpper()); } void QSchannelBackend::ensureInitialized() const @@ -267,10 +530,13 @@ QList<QSsl::SslProtocol> QSchannelBackend::supportedProtocols() const protocols << QSsl::AnyProtocol; protocols << QSsl::SecureProtocols; +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED protocols << QSsl::TlsV1_0; protocols << QSsl::TlsV1_0OrLater; protocols << QSsl::TlsV1_1; protocols << QSsl::TlsV1_1OrLater; +QT_WARNING_POP protocols << QSsl::TlsV1_2; protocols << QSsl::TlsV1_2OrLater; @@ -286,8 +552,10 @@ QList<QSsl::SupportedFeature> QSchannelBackend::supportedFeatures() const { QList<QSsl::SupportedFeature> features; +#ifdef SUPPORTS_ALPN features << QSsl::SupportedFeature::ClientSideAlpn; features << QSsl::SupportedFeature::ServerSideAlpn; +#endif return features; } @@ -328,7 +596,11 @@ QList<QSslCertificate> QSchannelBackend::systemCaCertificatesImplementation() // Similar to non-Darwin version found in qtlsbackend_openssl.cpp, // QTlsPrivate::systemCaCertificates function. QList<QSslCertificate> systemCerts; - auto hSystemStore = QHCertStorePointer(CertOpenSystemStore(0, L"ROOT")); + + auto hSystemStore = QHCertStorePointer( + CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, + CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT")); + if (hSystemStore) { PCCERT_CONTEXT pc = nullptr; while ((pc = CertFindCertificateInStore(hSystemStore.get(), X509_ASN_ENCODING, 0, @@ -349,6 +621,11 @@ QTlsPrivate::X509DerReaderPtr QSchannelBackend::X509DerReader() const return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; } +QTlsPrivate::X509Pkcs12ReaderPtr QSchannelBackend::X509Pkcs12Reader() const +{ + return QTlsPrivate::X509CertificateSchannel::importPkcs12; +} + namespace { SecBuffer createSecBuffer(void *ptr, unsigned long length, unsigned long bufferType) @@ -413,7 +690,6 @@ 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. @@ -421,10 +697,8 @@ bool supportsTls13() QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 20221); return current >= minimum; }(); + return supported; -#else - return false; -#endif } DWORD toSchannelProtocol(QSsl::SslProtocol protocol) @@ -433,9 +707,12 @@ DWORD toSchannelProtocol(QSsl::SslProtocol protocol) switch (protocol) { case QSsl::UnknownProtocol: return DWORD(-1); +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED case QSsl::DtlsV1_0: - case QSsl::DtlsV1_2: case QSsl::DtlsV1_0OrLater: +QT_WARNING_POP + case QSsl::DtlsV1_2: case QSsl::DtlsV1_2OrLater: return DWORD(-1); // Not supported at the moment (@future) case QSsl::AnyProtocol: @@ -443,12 +720,15 @@ DWORD toSchannelProtocol(QSsl::SslProtocol protocol) if (supportsTls13()) protocols |= SP_PROT_TLS1_3; break; +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED case QSsl::TlsV1_0: protocols = SP_PROT_TLS1_0; break; case QSsl::TlsV1_1: protocols = SP_PROT_TLS1_1; break; +QT_WARNING_POP case QSsl::TlsV1_2: protocols = SP_PROT_TLS1_2; break; @@ -458,7 +738,8 @@ DWORD toSchannelProtocol(QSsl::SslProtocol protocol) else protocols = DWORD(-1); break; - case QSsl::SecureProtocols: // TLS v1.0 and later is currently considered secure +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED case QSsl::TlsV1_0OrLater: // For the "OrLater" protocols we fall through from one to the next, adding all of them // in ascending order @@ -467,6 +748,8 @@ DWORD toSchannelProtocol(QSsl::SslProtocol protocol) case QSsl::TlsV1_1OrLater: protocols |= SP_PROT_TLS1_1; Q_FALLTHROUGH(); +QT_WARNING_POP + case QSsl::SecureProtocols: // TLS v1.2 and later is currently considered secure case QSsl::TlsV1_2OrLater: protocols |= SP_PROT_TLS1_2; Q_FALLTHROUGH(); @@ -480,17 +763,15 @@ 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 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; } -#endif /*! \internal @@ -507,8 +788,11 @@ QSsl::SslProtocol toQtSslProtocol(DWORD protocol) return q_protocol; \ } +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED MAP_PROTOCOL(SP_PROT_TLS1_0, QSsl::TlsV1_0) MAP_PROTOCOL(SP_PROT_TLS1_1, QSsl::TlsV1_1) +QT_WARNING_POP MAP_PROTOCOL(SP_PROT_TLS1_2, QSsl::TlsV1_2) MAP_PROTOCOL(SP_PROT_TLS1_3, QSsl::TlsV1_3) #undef MAP_PROTOCOL @@ -525,8 +809,7 @@ bool netscapeWrongCertType(const QList<QSslCertificateExtension> &extensions, bo const auto netscapeIt = std::find_if( extensions.cbegin(), extensions.cend(), [](const QSslCertificateExtension &extension) { - const auto netscapeCertType = QStringLiteral("2.16.840.1.113730.1.1"); - return extension.oid() == netscapeCertType; + return extension.oid() == u"2.16.840.1.113730.1.1"; }); if (netscapeIt != extensions.cend()) { const QByteArray netscapeCertTypeByte = netscapeIt->value().toByteArray(); @@ -554,11 +837,11 @@ bool isCertificateAuthority(const QList<QSslCertificateExtension> &extensions) { auto it = std::find_if(extensions.cbegin(), extensions.cend(), [](const QSslCertificateExtension &extension) { - return extension.name() == QLatin1String("basicConstraints"); + return extension.name() == "basicConstraints"_L1; }); if (it != extensions.cend()) { QVariantMap basicConstraints = it->value().toMap(); - return basicConstraints.value(QLatin1String("ca"), false).toBool(); + return basicConstraints.value("ca"_L1, false).toBool(); } return false; } @@ -573,7 +856,7 @@ bool matchesContextRequirements(DWORD attributes, DWORD requirements, bool isClient) { #ifdef QSSLSOCKET_DEBUG -#define DEBUG_WARN(message) qCWarning(lcTlsBackend, message) +#define DEBUG_WARN(message) qCWarning(lcTlsBackendSchannel, message) #else #define DEBUG_WARN(message) #endif @@ -617,21 +900,16 @@ Required const_reinterpret_cast(Actual *p) } #ifdef SUPPORTS_ALPN -bool supportsAlpn() -{ - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1; -} - QByteArray createAlpnString(const QByteArrayList &nextAllowedProtocols) { QByteArray alpnString; - if (!nextAllowedProtocols.isEmpty() && supportsAlpn()) { + if (!nextAllowedProtocols.isEmpty()) { const QByteArray names = [&nextAllowedProtocols]() { QByteArray protocolString; for (QByteArray proto : nextAllowedProtocols) { if (proto.size() > 255) { - qCWarning(lcTlsBackend) << "TLS ALPN extension" << proto - << "is too long and will be ignored."; + qCWarning(lcTlsBackendSchannel) + << "TLS ALPN extension" << proto << "is too long and will be ignored."; continue; } else if (proto.isEmpty()) { continue; @@ -684,7 +962,8 @@ void retainExtraData(QByteArray &buffer, const SecBuffer &secBuffer) return; #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "We got SECBUFFER_EXTRA, will retain %lu bytes", secBuffer.cbBuffer); + qCDebug(lcTlsBackendSchannel, "We got SECBUFFER_EXTRA, will retain %lu bytes", + secBuffer.cbBuffer); #endif std::move(buffer.end() - secBuffer.cbBuffer, buffer.end(), buffer.begin()); buffer.resize(secBuffer.cbBuffer); @@ -694,13 +973,17 @@ qint64 checkIncompleteData(const SecBuffer &secBuffer) { if (secBuffer.BufferType == SECBUFFER_MISSING) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "Need %lu more bytes.", secBuffer.cbBuffer); + qCDebug(lcTlsBackendSchannel, "Need %lu more bytes.", secBuffer.cbBuffer); #endif return secBuffer.cbBuffer; } return 0; } +DWORD defaultCredsFlag() +{ + return qEnvironmentVariableIsSet("QT_SCH_DEFAULT_CREDS") ? 0 : SCH_CRED_NO_DEFAULT_CREDS; +} } // anonymous namespace @@ -740,6 +1023,10 @@ bool TlsCryptographSchannel::sendToken(void *token, unsigned long tokenLength, b Q_ASSERT(d); auto *plainSocket = d->plainTcpSocket(); Q_ASSERT(plainSocket); + if (plainSocket->state() == QAbstractSocket::UnconnectedState || !plainSocket->isValid() + || !plainSocket->isOpen()) { + return false; + } const qint64 written = plainSocket->write(static_cast<const char *>(token), tokenLength); if (written != qint64(tokenLength)) { @@ -802,7 +1089,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")); @@ -856,80 +1143,50 @@ bool TlsCryptographSchannel::acquireCredentialsHandle() certsCount = 1; Q_ASSERT(localCertContext); } - void *credentials = nullptr; -#ifdef SUPPORTS_TLS13 + + const QList<QSslCipher> ciphers = configuration.ciphers(); + if (!ciphers.isEmpty() && !containsTls13Cipher(ciphers)) + protocols &= ~SP_PROT_TLS1_3; + + QList<CRYPTO_SETTINGS> cryptoSettings; + if (!ciphers.isEmpty()) + cryptoSettings = cryptoSettingsForCiphers(ciphers); + TLS_PARAMETERS tlsParameters = { 0, nullptr, - toSchannelProtocolNegated(configuration.protocol()), // what protocols to disable + negatedSchannelProtocols(protocols), // what protocols to disable + static_cast<DWORD>(cryptoSettings.size()), + (cryptoSettings.isEmpty() ? nullptr : cryptoSettings.data()), + 0 + }; + + SCH_CREDENTIALS credentials = { + SCH_CREDENTIALS_VERSION, 0, + certsCount, + &localCertContext, nullptr, - 0 + 0, + nullptr, + 0, + SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT | defaultCredsFlag(), + 1, + &tlsParameters }; - 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) - credentials, // 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(d, QAbstractSocket::SslInternalError, schannelErrorToString(status)); return false; @@ -1134,7 +1391,8 @@ bool TlsCryptographSchannel::performHandshake() auto *plainSocket = d->plainTcpSocket(); Q_ASSERT(plainSocket); - if (plainSocket->state() == QAbstractSocket::UnconnectedState) { + if (plainSocket->state() == QAbstractSocket::UnconnectedState || !plainSocket->isValid() + || !plainSocket->isOpen()) { setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError, QSslSocket::tr("The TLS/SSL connection has been closed")); return false; @@ -1144,8 +1402,9 @@ bool TlsCryptographSchannel::performHandshake() Q_ASSERT(schannelState == SchannelState::PerformHandshake); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "Bytes available from socket: %lld", plainSocket->bytesAvailable()); - qCDebug(lcTlsBackend, "intermediateBuffer size: %d", intermediateBuffer.size()); + qCDebug(lcTlsBackendSchannel, "Bytes available from socket: %lld", + plainSocket->bytesAvailable()); + qCDebug(lcTlsBackendSchannel, "intermediateBuffer size: %d", intermediateBuffer.size()); #endif if (missingData > plainSocket->bytesAvailable()) @@ -1302,13 +1561,18 @@ bool TlsCryptographSchannel::verifyHandshake() // Get session cipher info status = QueryContextAttributes(&contextHandle, + SECPKG_ATTR_CIPHER_INFO, + &cipherInfo); + CHECK_STATUS(status); + + status = QueryContextAttributes(&contextHandle, SECPKG_ATTR_CONNECTION_INFO, &connectionInfo); CHECK_STATUS(status); #ifdef SUPPORTS_ALPN const auto allowedProtos = configuration.allowedNextProtocols(); - if (!allowedProtos.isEmpty() && supportsAlpn()) { + if (!allowedProtos.isEmpty()) { SecPkgContext_ApplicationProtocol alpn; status = QueryContextAttributes(&contextHandle, SECPKG_ATTR_APPLICATION_PROTOCOL, @@ -1353,8 +1617,8 @@ bool TlsCryptographSchannel::verifyHandshake() && configuration.peerVerifyMode() != QSslSocket::PeerVerifyMode::QueryPeer)) { if (status != SEC_E_OK) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "Couldn't retrieve peer certificate, status:" - << schannelErrorToString(status); + qCDebug(lcTlsBackendSchannel) << "Couldn't retrieve peer certificate, status:" + << schannelErrorToString(status); #endif const QSslError error{ QSslError::NoPeerCertificate }; sslErrors += error; @@ -1370,7 +1634,7 @@ bool TlsCryptographSchannel::verifyHandshake() if (!checkSslErrors() || q->state() != QAbstractSocket::ConnectedState) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << __func__ << "was unsuccessful. Paused:" << paused; + qCDebug(lcTlsBackendSchannel) << __func__ << "was unsuccessful. Paused:" << d->isPaused(); #endif // If we're paused then checkSslErrors returned false, but it's not an error return d->isPaused() && q->state() == QAbstractSocket::ConnectedState; @@ -1454,6 +1718,7 @@ void TlsCryptographSchannel::reset() deallocateContext(); freeCredentialsHandle(); // in case we already had one (@future: session resumption requires re-use) + cipherInfo = {}; connectionInfo = {}; streamSizes = {}; @@ -1503,8 +1768,10 @@ void TlsCryptographSchannel::transmit() return; // This function should not have been called // Can happen if called through QSslSocket::abort->QSslSocket::close->QSslSocket::flush->here - if (plainSocket->state() == QAbstractSocket::SocketState::UnconnectedState) + if (plainSocket->state() == QAbstractSocket::UnconnectedState || !plainSocket->isValid() + || !plainSocket->isOpen()) { return; + } if (schannelState != SchannelState::Done) { continueHandshake(); @@ -1553,7 +1820,8 @@ void TlsCryptographSchannel::transmit() fullMessage.resize(inputBuffers[0].cbBuffer + inputBuffers[1].cbBuffer + inputBuffers[2].cbBuffer); const qint64 bytesWritten = plainSocket->write(fullMessage); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "Wrote %lld of total %d bytes", bytesWritten, fullMessage.length()); + qCDebug(lcTlsBackendSchannel, "Wrote %lld of total %d bytes", bytesWritten, + fullMessage.length()); #endif if (bytesWritten >= 0) { totalBytesWritten += bytesWritten; @@ -1575,128 +1843,131 @@ void TlsCryptographSchannel::transmit() } } - if (q->isEncrypted()) { // Decrypt data from remote - int totalRead = 0; - bool hadIncompleteData = false; - const auto readBufferMaxSize = d->maxReadBufferSize(); - while (!readBufferMaxSize || buffer.size() < readBufferMaxSize) { - if (missingData > plainSocket->bytesAvailable() - && (!readBufferMaxSize || readBufferMaxSize >= missingData)) { + int totalRead = 0; + bool hadIncompleteData = false; + const auto readBufferMaxSize = d->maxReadBufferSize(); + while (!readBufferMaxSize || buffer.size() < readBufferMaxSize) { + if (missingData > plainSocket->bytesAvailable() + && (!readBufferMaxSize || readBufferMaxSize >= missingData)) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "We're still missing %lld bytes, will check later.", missingData); + qCDebug(lcTlsBackendSchannel, "We're still missing %lld bytes, will check later.", + missingData); #endif - break; - } + break; + } - missingData = 0; - const qint64 bytesRead = readToBuffer(intermediateBuffer, plainSocket); + missingData = 0; + const qint64 bytesRead = readToBuffer(intermediateBuffer, plainSocket); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "Read %lld encrypted bytes from the socket", bytesRead); + qCDebug(lcTlsBackendSchannel, "Read %lld encrypted bytes from the socket", bytesRead); #endif - if (intermediateBuffer.length() == 0 || (hadIncompleteData && bytesRead == 0)) { + if (intermediateBuffer.length() == 0 || (hadIncompleteData && bytesRead == 0)) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, (hadIncompleteData ? "No new data received, leaving loop!" - : "Nothing to decrypt, leaving loop!")); + qCDebug(lcTlsBackendSchannel, + hadIncompleteData ? "No new data received, leaving loop!" + : "Nothing to decrypt, leaving loop!"); #endif - break; - } - hadIncompleteData = false; + break; + } + hadIncompleteData = false; #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "Total amount of bytes to decrypt: %d", intermediateBuffer.length()); + qCDebug(lcTlsBackendSchannel, "Total amount of bytes to decrypt: %d", + intermediateBuffer.length()); #endif - SecBuffer dataBuffer[4]{ - createSecBuffer(intermediateBuffer, SECBUFFER_DATA), - createSecBuffer(nullptr, 0, SECBUFFER_EMPTY), - createSecBuffer(nullptr, 0, SECBUFFER_EMPTY), - createSecBuffer(nullptr, 0, SECBUFFER_EMPTY) - }; - SecBufferDesc message{ - SECBUFFER_VERSION, - ARRAYSIZE(dataBuffer), - dataBuffer - }; - auto status = DecryptMessage(&contextHandle, &message, 0, nullptr); - if (status == SEC_E_OK || status == SEC_I_RENEGOTIATE || status == SEC_I_CONTEXT_EXPIRED) { - // There can still be 0 output even if it succeeds, this is fine - if (dataBuffer[1].cbBuffer > 0) { - // It is always decrypted in-place. - // But [0] is the STREAM_HEADER, [1] is the DATA and [2] is the STREAM_TRAILER. - // The pointers in all of those still point into 'intermediateBuffer'. - buffer.append(static_cast<char *>(dataBuffer[1].pvBuffer), - dataBuffer[1].cbBuffer); - totalRead += dataBuffer[1].cbBuffer; + SecBuffer dataBuffer[4]{ + createSecBuffer(intermediateBuffer, SECBUFFER_DATA), + createSecBuffer(nullptr, 0, SECBUFFER_EMPTY), + createSecBuffer(nullptr, 0, SECBUFFER_EMPTY), + createSecBuffer(nullptr, 0, SECBUFFER_EMPTY) + }; + SecBufferDesc message{ + SECBUFFER_VERSION, + ARRAYSIZE(dataBuffer), + dataBuffer + }; + auto status = DecryptMessage(&contextHandle, &message, 0, nullptr); + if (status == SEC_E_OK || status == SEC_I_RENEGOTIATE || status == SEC_I_CONTEXT_EXPIRED) { + // There can still be 0 output even if it succeeds, this is fine + if (dataBuffer[1].cbBuffer > 0) { + // It is always decrypted in-place. + // But [0] is the STREAM_HEADER, [1] is the DATA and [2] is the STREAM_TRAILER. + // The pointers in all of those still point into 'intermediateBuffer'. + buffer.append(static_cast<char *>(dataBuffer[1].pvBuffer), + dataBuffer[1].cbBuffer); + totalRead += dataBuffer[1].cbBuffer; #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "Decrypted %lu bytes. New read buffer size: %d", - dataBuffer[1].cbBuffer, buffer.size()); + qCDebug(lcTlsBackendSchannel, "Decrypted %lu bytes. New read buffer size: %d", + dataBuffer[1].cbBuffer, buffer.size()); #endif - } - if (dataBuffer[3].BufferType == SECBUFFER_EXTRA) { - // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel - // dataBuffer[3].cbBuffer indicates the amount of bytes _NOT_ processed, - // the rest need to be stored. - retainExtraData(intermediateBuffer, dataBuffer[3]); - } else { - intermediateBuffer.resize(0); - } } + if (dataBuffer[3].BufferType == SECBUFFER_EXTRA) { + // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel + // dataBuffer[3].cbBuffer indicates the amount of bytes _NOT_ processed, + // the rest need to be stored. + retainExtraData(intermediateBuffer, dataBuffer[3]); + } else { + intermediateBuffer.resize(0); + } + } - if (status == SEC_E_INCOMPLETE_MESSAGE) { - missingData = checkIncompleteData(dataBuffer[0]); + if (status == SEC_E_INCOMPLETE_MESSAGE) { + missingData = checkIncompleteData(dataBuffer[0]); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "We didn't have enough data to decrypt anything, will try again!"); + qCDebug(lcTlsBackendSchannel, "We didn't have enough data to decrypt anything, will try again!"); #endif - // We try again, but if we don't get any more data then we leave - hadIncompleteData = true; - } else if (status == SEC_E_INVALID_HANDLE) { - // I don't think this should happen, if it does we're done... - qCWarning(lcTlsBackend, "The internal SSPI handle is invalid!"); - Q_UNREACHABLE(); - } else if (status == SEC_E_INVALID_TOKEN) { - qCWarning(lcTlsBackend, "Got SEC_E_INVALID_TOKEN!"); - Q_UNREACHABLE(); // Happened once due to a bug, but shouldn't generally happen(?) - } else if (status == SEC_E_MESSAGE_ALTERED) { - // The message has been altered, disconnect now. - shutdown = true; // skips sending the shutdown alert - disconnectFromHost(); - setErrorAndEmit(d, QAbstractSocket::SslInternalError, - schannelErrorToString(status)); - break; - } else if (status == SEC_E_OUT_OF_SEQUENCE) { - // @todo: I don't know if this one is actually "fatal".. - // This path might never be hit as it seems this is for connection-oriented connections, - // while SEC_E_MESSAGE_ALTERED is for stream-oriented ones (what we use). - shutdown = true; // skips sending the shutdown alert - disconnectFromHost(); - setErrorAndEmit(d, QAbstractSocket::SslInternalError, - schannelErrorToString(status)); - break; - } else if (status == SEC_I_CONTEXT_EXPIRED) { - // 'remote' has initiated a shutdown - disconnectFromHost(); - setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError, - schannelErrorToString(status)); - break; - } else if (status == SEC_I_RENEGOTIATE) { - // 'remote' wants to renegotiate + // We try again, but if we don't get any more data then we leave + hadIncompleteData = true; + } else if (status == SEC_E_INVALID_HANDLE) { + // I don't think this should happen, if it does we're done... + qCWarning(lcTlsBackendSchannel, "The internal SSPI handle is invalid!"); + Q_UNREACHABLE(); + } else if (status == SEC_E_INVALID_TOKEN) { + // Supposedly we have an invalid token, it's under-documented what + // this means, so to be safe we disconnect. + shutdown = true; + disconnectFromHost(); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, schannelErrorToString(status)); + break; + } else if (status == SEC_E_MESSAGE_ALTERED) { + // The message has been altered, disconnect now. + shutdown = true; // skips sending the shutdown alert + disconnectFromHost(); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + schannelErrorToString(status)); + break; + } else if (status == SEC_E_OUT_OF_SEQUENCE) { + // @todo: I don't know if this one is actually "fatal".. + // This path might never be hit as it seems this is for connection-oriented connections, + // while SEC_E_MESSAGE_ALTERED is for stream-oriented ones (what we use). + shutdown = true; // skips sending the shutdown alert + disconnectFromHost(); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + schannelErrorToString(status)); + break; + } else if (status == SEC_I_CONTEXT_EXPIRED) { + // 'remote' has initiated a shutdown + disconnectFromHost(); + break; + } else if (status == SEC_I_RENEGOTIATE) { + // 'remote' wants to renegotiate #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend, "The peer wants to renegotiate."); + qCDebug(lcTlsBackendSchannel, "The peer wants to renegotiate."); #endif - schannelState = SchannelState::Renegotiate; - renegotiating = true; + schannelState = SchannelState::Renegotiate; + renegotiating = true; - // We need to call 'continueHandshake' or else there's no guarantee it ever gets called - continueHandshake(); - break; - } + // We need to call 'continueHandshake' or else there's no guarantee it ever gets called + continueHandshake(); + break; } + } - if (totalRead) { - if (bool *readyReadEmittedPointer = d->readyReadPointer()) - *readyReadEmittedPointer = true; - emit q->readyRead(); - emit q->channelReadyRead(0); - } + if (totalRead) { + if (bool *readyReadEmittedPointer = d->readyReadPointer()) + *readyReadEmittedPointer = true; + emit q->readyRead(); + emit q->channelReadyRead(0); } } @@ -1716,7 +1987,8 @@ void TlsCryptographSchannel::sendShutdown() if (status != SEC_E_OK) { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "Failed to apply shutdown control token:" << schannelErrorToString(status); + qCDebug(lcTlsBackendSchannel) + << "Failed to apply shutdown control token:" << schannelErrorToString(status); #endif return; } @@ -1774,7 +2046,8 @@ void TlsCryptographSchannel::sendShutdown() } } else { #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "Failed to initialize shutdown:" << schannelErrorToString(status); + qCDebug(lcTlsBackendSchannel) + << "Failed to initialize shutdown:" << schannelErrorToString(status); #endif } } @@ -1789,30 +2062,36 @@ void TlsCryptographSchannel::disconnectFromHost() if (SecIsValidHandle(&contextHandle)) { if (!shutdown) { shutdown = true; - if (plainSocket->state() != QAbstractSocket::UnconnectedState) { - if (q->isEncrypted()) { - // Read as much as possible because this is likely our last chance - qint64 tempMax = d->maxReadBufferSize(); - d->setMaxReadBufferSize(0); - transmit(); - d->setMaxReadBufferSize(tempMax); - sendShutdown(); - } + if (plainSocket->state() != QAbstractSocket::UnconnectedState && q->isEncrypted()) { + sendShutdown(); + transmit(); } } } - if (plainSocket->state() != QAbstractSocket::UnconnectedState) - plainSocket->disconnectFromHost(); + plainSocket->disconnectFromHost(); } void TlsCryptographSchannel::disconnected() { Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + d->setEncrypted(false); shutdown = true; - d->setEncrypted(false); - deallocateContext(); - freeCredentialsHandle(); + if (plainSocket->bytesAvailable() > 0 || hasUndecryptedData()) { + // Read as much as possible because this is likely our last chance + qint64 tempMax = d->maxReadBufferSize(); + d->setMaxReadBufferSize(0); // Unlimited + transmit(); + d->setMaxReadBufferSize(tempMax); + // Since there were bytes still available we don't want to deallocate + // our context yet. It will happen later, when the socket is re-used or + // destroyed. + } else { + deallocateContext(); + freeCredentialsHandle(); + } } QSslCipher TlsCryptographSchannel::sessionCipher() const @@ -1820,8 +2099,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 @@ -1968,7 +2256,7 @@ void TlsCryptographSchannel::initializeCertificateStores() localCertificateStore = createStoreFromCertificateChain(configuration.localCertificateChain(), configuration.privateKey()); if (localCertificateStore == nullptr) - qCWarning(lcTlsBackend, "Failed to load certificate chain!"); + qCWarning(lcTlsBackendSchannel, "Failed to load certificate chain!"); } } @@ -1995,7 +2283,7 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) nullptr)); if (!tempCertCollection) { #ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend, "Failed to create certificate store collection!"); + qCWarning(lcTlsBackendSchannel, "Failed to create certificate store collection!"); #endif return false; } @@ -2005,15 +2293,20 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) // the Ca list, not just included during verification. // That being said, it's not trivial to add the root certificates (if and only if they // came from the system root store). And I don't see this mentioned in our documentation. - auto rootStore = QHCertStorePointer(CertOpenSystemStore(0, L"ROOT")); + auto rootStore = QHCertStorePointer( + CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, + CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT")); + if (!rootStore) { #ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend, "Failed to open the system root CA certificate store!"); + qCWarning(lcTlsBackendSchannel, "Failed to open the system root CA certificate store!"); #endif return false; } else if (!CertAddStoreToCollection(tempCertCollection.get(), rootStore.get(), 0, 1)) { #ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend, "Failed to add the system root CA certificate store to the certificate store collection!"); + qCWarning(lcTlsBackendSchannel, + "Failed to add the system root CA certificate store to the certificate store " + "collection!"); #endif return false; } @@ -2021,7 +2314,9 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) if (caCertificateStore) { if (!CertAddStoreToCollection(tempCertCollection.get(), caCertificateStore.get(), 0, 1)) { #ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend, "Failed to add the user's CA certificate store to the certificate store collection!"); + qCWarning(lcTlsBackendSchannel, + "Failed to add the user's CA certificate store to the certificate store " + "collection!"); #endif return false; } @@ -2029,7 +2324,8 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) if (!CertAddStoreToCollection(tempCertCollection.get(), certContext->hCertStore, 0, 0)) { #ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend, "Failed to add certificate's origin store to the certificate store collection!"); + qCWarning(lcTlsBackendSchannel, + "Failed to add certificate's origin store to the certificate store collection!"); #endif return false; } @@ -2114,18 +2410,48 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) verifyDepth = DWORD(q->peerVerifyDepth()); const auto &caCertificates = q->sslConfiguration().caCertificates(); + + if (!rootCertOnDemandLoadingAllowed() + && !(chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN) + && (q->peerVerifyMode() == QSslSocket::VerifyPeer + || (isClient && q->peerVerifyMode() == QSslSocket::AutoVerifyPeer))) { + // When verifying a peer Windows "helpfully" builds a chain that + // may include roots from the system store. But we don't want that if + // the user has set their own CA certificates. + // Since Windows claims this is not a partial chain the root is included + // and we have to check that it is one of our configured CAs. + CERT_CHAIN_ELEMENT *element = chain->rgpElement[chain->cElement - 1]; + QSslCertificate certificate = getCertificateFromChainElement(element); + if (!caCertificates.contains(certificate)) { + auto error = QSslError(QSslError::CertificateUntrusted, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + QList<QSslCertificate> peerCertificateChain; for (DWORD i = 0; i < verifyDepth; i++) { CERT_CHAIN_ELEMENT *element = chain->rgpElement[i]; QSslCertificate certificate = getCertificateFromChainElement(element); + if (certificate.isNull()) { + const auto &previousCert = !peerCertificateChain.isEmpty() ? peerCertificateChain.last() + : QSslCertificate(); + auto error = QSslError(QSslError::SslError::UnableToGetIssuerCertificate, previousCert); + sslErrors += error; + emit q->peerVerifyError(error); + if (previousCert.isNull() || q->state() != QAbstractSocket::ConnectedState) + return false; + } const QList<QSslCertificateExtension> extensions = certificate.extensions(); #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "issuer:" << certificate.issuerDisplayName() - << "\nsubject:" << certificate.subjectDisplayName() - << "\nQSslCertificate info:" << certificate - << "\nextended error info:" << element->pwszExtendedErrorInfo - << "\nerror status:" << element->TrustStatus.dwErrorStatus; + qCDebug(lcTlsBackendSchannel) << "issuer:" << certificate.issuerDisplayName() + << "\nsubject:" << certificate.subjectDisplayName() + << "\nQSslCertificate info:" << certificate + << "\nextended error info:" << element->pwszExtendedErrorInfo + << "\nerror status:" << element->TrustStatus.dwErrorStatus; #endif peerCertificateChain.append(certificate); @@ -2219,7 +2545,7 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) if (element->TrustStatus.dwErrorStatus & CERT_TRUST_INVALID_BASIC_CONSTRAINTS) { auto it = std::find_if(extensions.cbegin(), extensions.cend(), [](const QSslCertificateExtension &extension) { - return extension.name() == QLatin1String("basicConstraints"); + return extension.name() == "basicConstraints"_L1; }); if (it != extensions.cend()) { // @Note: This is actually one of two errors: @@ -2227,7 +2553,7 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) // or the chain path length has been exceeded." QVariantMap basicConstraints = it->value().toMap(); QSslError error; - if (i > 0 && !basicConstraints.value(QLatin1String("ca"), false).toBool()) + if (i > 0 && !basicConstraints.value("ca"_L1, false).toBool()) error = QSslError(QSslError::InvalidPurpose, certificate); else error = QSslError(QSslError::PathLengthExceeded, certificate); @@ -2265,7 +2591,7 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) } if (!peerCertificateChain.isEmpty()) - QTlsBackend::storePeerCertificate(d, peerCertificateChain.first()); + QTlsBackend::storePeerCertificate(d, peerCertificateChain.constFirst()); const auto &configuration = q->sslConfiguration(); // Probably, updated by QTlsBackend::storePeerCertificate etc. // @Note: Somewhat copied from qsslsocket_mac.cpp diff --git a/src/plugins/tls/schannel/qtls_schannel_p.h b/src/plugins/tls/schannel/qtls_schannel_p.h index ca4f8f09cf..fab8777249 100644 --- a/src/plugins/tls/schannel/qtls_schannel_p.h +++ b/src/plugins/tls/schannel/qtls_schannel_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QTLS_SCHANNEL_P_H #define QTLS_SCHANNEL_P_H @@ -129,6 +93,7 @@ private: QSslSocket *q = nullptr; QSslSocketPrivate *d = nullptr; + SecPkgContext_CipherInfo cipherInfo = {}; SecPkgContext_ConnectionInfo connectionInfo = {}; SecPkgContext_StreamSizes streamSizes = {}; diff --git a/src/plugins/tls/schannel/qtlsbackend_schannel_p.h b/src/plugins/tls/schannel/qtlsbackend_schannel_p.h index e53cf17f13..7c2d675e79 100644 --- a/src/plugins/tls/schannel/qtlsbackend_schannel_p.h +++ b/src/plugins/tls/schannel/qtlsbackend_schannel_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QTLSBACKEND_ST_P_H #define QTLSBACKEND_ST_P_H @@ -93,10 +57,13 @@ private: QTlsPrivate::X509PemReaderPtr X509PemReader() const override; QTlsPrivate::X509DerReaderPtr X509DerReader() const override; + QTlsPrivate::X509Pkcs12ReaderPtr X509Pkcs12Reader() const override; static bool s_loadedCiphersAndCerts; }; +Q_DECLARE_LOGGING_CATEGORY(lcTlsBackendSchannel) + QT_END_NAMESPACE #endif // QTLSBACKEND_ST_P_H diff --git a/src/plugins/tls/schannel/qtlskey_schannel.cpp b/src/plugins/tls/schannel/qtlskey_schannel.cpp index e407da2ed3..eb0a2371ab 100644 --- a/src/plugins/tls/schannel/qtlskey_schannel.cpp +++ b/src/plugins/tls/schannel/qtlskey_schannel.cpp @@ -1,44 +1,9 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <QtNetwork/private/qssl_p.h> +#include "qtlsbackend_schannel_p.h" #include "qtlskey_schannel_p.h" #include "../shared/qwincrypt_p.h" @@ -50,6 +15,7 @@ #include <QtCore/qscopeguard.h> #include <QtCore/qbytearray.h> +#include <QtCore/qvarlengtharray.h> QT_BEGIN_NAMESPACE @@ -81,7 +47,7 @@ BCRYPT_ALG_HANDLE getHandle(QSslKeyPrivate::Cipher cipher) 0 // dwFlags ); if (status < 0) { - qCWarning(lcTlsBackend, "Failed to open algorithm handle (%ld)!", status); + qCWarning(lcTlsBackendSchannel, "Failed to open algorithm handle (%ld)!", status); return nullptr; } @@ -102,7 +68,7 @@ BCRYPT_KEY_HANDLE generateSymmetricKey(BCRYPT_ALG_HANDLE handle, 0 // dwFlags ); if (status < 0) { - qCWarning(lcTlsBackend, "Failed to generate symmetric key (%ld)!", status); + qCWarning(lcTlsBackendSchannel, "Failed to generate symmetric key (%ld)!", status); return nullptr; } @@ -115,7 +81,8 @@ BCRYPT_KEY_HANDLE generateSymmetricKey(BCRYPT_ALG_HANDLE handle, ); if (status < 0) { BCryptDestroyKey(keyHandle); - qCWarning(lcTlsBackend, "Failed to change the symmetric key's chaining mode (%ld)!", status); + qCWarning(lcTlsBackendSchannel, "Failed to change the symmetric key's chaining mode (%ld)!", + status); return nullptr; } return keyHandle; @@ -160,7 +127,8 @@ QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, const QByteArray &data, const BCRYPT_BLOCK_PADDING // dwFlags ); if (status < 0) { - qCWarning(lcTlsBackend, "%s failed (%ld)!", encrypt ? "Encrypt" : "Decrypt", status); + qCWarning(lcTlsBackendSchannel, "%s failed (%ld)!", encrypt ? "Encrypt" : "Decrypt", + status); return {}; } } diff --git a/src/plugins/tls/schannel/qtlskey_schannel_p.h b/src/plugins/tls/schannel/qtlskey_schannel_p.h index 53c3b447ce..14d146dfd7 100644 --- a/src/plugins/tls/schannel/qtlskey_schannel_p.h +++ b/src/plugins/tls/schannel/qtlskey_schannel_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QTLSKEY_SCHANNEL_P_H #define QTLSKEY_SCHANNEL_P_H diff --git a/src/plugins/tls/schannel/qx509_schannel.cpp b/src/plugins/tls/schannel/qx509_schannel.cpp index 01a21c69f5..d9d82dce29 100644 --- a/src/plugins/tls/schannel/qx509_schannel.cpp +++ b/src/plugins/tls/schannel/qx509_schannel.cpp @@ -1,45 +1,11 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +#include "qtlsbackend_schannel_p.h" #include "qtlskey_schannel_p.h" #include "qx509_schannel_p.h" +#include <QtCore/private/qsystemerror_p.h> #include <QtNetwork/private/qsslcertificate_p.h> #include <memory> @@ -75,13 +41,173 @@ QSslCertificate X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(const QByteArray derData = QByteArray((const char *)certificateContext->pbCertEncoded, certificateContext->cbCertEncoded); QSslCertificate certificate(derData, QSsl::Der); - - auto *certBackend = QTlsBackend::backend<X509CertificateSchannel>(certificate); - Q_ASSERT(certBackend); - certBackend->certificateContext = CertDuplicateCertificateContext(certificateContext); + if (!certificate.isNull()) { + auto *certBackend = QTlsBackend::backend<X509CertificateSchannel>(certificate); + Q_ASSERT(certBackend); + certBackend->certificateContext = CertDuplicateCertificateContext(certificateContext); + } return certificate; } +bool X509CertificateSchannel::importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, + QList<QSslCertificate> *caCertificates, + const QByteArray &passPhrase) +{ + // These are required + Q_ASSERT(device); + Q_ASSERT(key); + Q_ASSERT(cert); + + QByteArray pkcs12data = device->readAll(); + if (pkcs12data.size() == 0) + return false; + + CRYPT_DATA_BLOB dataBlob; + dataBlob.cbData = pkcs12data.size(); + dataBlob.pbData = reinterpret_cast<BYTE*>(pkcs12data.data()); + + const auto password = QString::fromUtf8(passPhrase); + + const DWORD flags = (CRYPT_EXPORTABLE | PKCS12_NO_PERSIST_KEY | PKCS12_PREFER_CNG_KSP); + + auto certStore = QHCertStorePointer(PFXImportCertStore(&dataBlob, + reinterpret_cast<LPCWSTR>(password.utf16()), + flags)); + + if (!certStore) { + qCWarning(lcTlsBackendSchannel, "Failed to import PFX data: %s", + qPrintable(QSystemError::windowsString())); + return false; + } + + // first extract the certificate with the private key + const auto certContext = QPCCertContextPointer(CertFindCertificateInStore(certStore.get(), + X509_ASN_ENCODING | + PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_HAS_PRIVATE_KEY, + nullptr, nullptr)); + + if (!certContext) { + qCWarning(lcTlsBackendSchannel, "Failed to find certificate in PFX store: %s", + qPrintable(QSystemError::windowsString())); + return false; + } + + *cert = QSslCertificate_from_CERT_CONTEXT(certContext.get()); + + // retrieve the private key for the certificate + NCRYPT_KEY_HANDLE keyHandle = {}; + DWORD keyHandleSize = sizeof(keyHandle); + if (!CertGetCertificateContextProperty(certContext.get(), CERT_NCRYPT_KEY_HANDLE_PROP_ID, + &keyHandle, &keyHandleSize)) { + qCWarning(lcTlsBackendSchannel, "Failed to find private key handle in certificate context: %s", + qPrintable(QSystemError::windowsString())); + return false; + } + + SECURITY_STATUS securityStatus = ERROR_SUCCESS; + + // we need the 'NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG' to make NCryptExportKey succeed + DWORD policy = (NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG); + DWORD policySize = sizeof(policy); + + securityStatus = NCryptSetProperty(keyHandle, NCRYPT_EXPORT_POLICY_PROPERTY, + reinterpret_cast<BYTE*>(&policy), policySize, 0); + if (securityStatus != ERROR_SUCCESS) { + qCWarning(lcTlsBackendSchannel, "Failed to update export policy of private key: 0x%x", + static_cast<unsigned int>(securityStatus)); + return false; + } + + DWORD blobSize = {}; + securityStatus = NCryptExportKey(keyHandle, {}, BCRYPT_RSAFULLPRIVATE_BLOB, + nullptr, nullptr, 0, &blobSize, 0); + if (securityStatus != ERROR_SUCCESS) { + qCWarning(lcTlsBackendSchannel, "Failed to retrieve private key size: 0x%x", + static_cast<unsigned int>(securityStatus)); + return false; + } + + std::vector<BYTE> blob(blobSize); + securityStatus = NCryptExportKey(keyHandle, {}, BCRYPT_RSAFULLPRIVATE_BLOB, + nullptr, blob.data(), blobSize, &blobSize, 0); + if (securityStatus != ERROR_SUCCESS) { + qCWarning(lcTlsBackendSchannel, "Failed to retrieve private key from certificate: 0x%x", + static_cast<unsigned int>(securityStatus)); + return false; + } + + DWORD privateKeySize = {}; + + if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CNG_RSA_PRIVATE_KEY_BLOB, + blob.data(), nullptr, &privateKeySize)) { + qCWarning(lcTlsBackendSchannel, "Failed to encode private key to key info: %s", + qPrintable(QSystemError::windowsString())); + return false; + } + + std::vector<BYTE> privateKeyData(privateKeySize); + + CRYPT_PRIVATE_KEY_INFO privateKeyInfo = {}; + privateKeyInfo.Algorithm.pszObjId = const_cast<PSTR>(szOID_RSA_RSA); + privateKeyInfo.PrivateKey.cbData = privateKeySize; + privateKeyInfo.PrivateKey.pbData = privateKeyData.data(); + + if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + CNG_RSA_PRIVATE_KEY_BLOB, blob.data(), + privateKeyInfo.PrivateKey.pbData, &privateKeyInfo.PrivateKey.cbData)) { + qCWarning(lcTlsBackendSchannel, "Failed to encode private key to key info: %s", + qPrintable(QSystemError::windowsString())); + return false; + } + + + DWORD derSize = {}; + + if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, + &privateKeyInfo, nullptr, &derSize)) { + qCWarning(lcTlsBackendSchannel, "Failed to encode key info to DER format: %s", + qPrintable(QSystemError::windowsString())); + + return false; + } + + QByteArray derData(derSize, Qt::Uninitialized); + + if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, + &privateKeyInfo, reinterpret_cast<BYTE*>(derData.data()), &derSize)) { + qCWarning(lcTlsBackendSchannel, "Failed to encode key info to DER format: %s", + qPrintable(QSystemError::windowsString())); + + return false; + } + + *key = QSslKey(derData, QSsl::Rsa, QSsl::Der, QSsl::PrivateKey); + if (key->isNull()) { + qCWarning(lcTlsBackendSchannel, "Failed to parse private key from DER format"); + return false; + } + + // fetch all the remaining certificates as CA certificates + if (caCertificates) { + PCCERT_CONTEXT caCertContext = nullptr; + while ((caCertContext = CertFindCertificateInStore(certStore.get(), + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, CERT_FIND_ANY, nullptr, caCertContext))) { + if (CertCompareCertificate(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + certContext->pCertInfo, caCertContext->pCertInfo)) + continue; // ignore the certificate with private key + + auto caCertificate = QSslCertificate_from_CERT_CONTEXT(caCertContext); + + caCertificates->append(caCertificate); + } + } + + return true; +} + } // namespace QTlsPrivate QT_END_NAMESPACE diff --git a/src/plugins/tls/schannel/qx509_schannel_p.h b/src/plugins/tls/schannel/qx509_schannel_p.h index bf39229e96..4625c9584c 100644 --- a/src/plugins/tls/schannel/qx509_schannel_p.h +++ b/src/plugins/tls/schannel/qx509_schannel_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QX509_SCHANNEL_P_H #define QX509_SCHANNEL_P_H @@ -72,6 +36,10 @@ public: Qt::HANDLE handle() const override; static QSslCertificate QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext); + + static bool importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, + QList<QSslCertificate> *caCertificates, + const QByteArray &passPhrase); private: const CERT_CONTEXT *certificateContext = nullptr; |