diff options
Diffstat (limited to 'src/plugins/tls')
19 files changed, 697 insertions, 157 deletions
diff --git a/src/plugins/tls/openssl/qdtls_openssl.cpp b/src/plugins/tls/openssl/qdtls_openssl.cpp index 78288d8dd1..fc07a29ec8 100644 --- a/src/plugins/tls/openssl/qdtls_openssl.cpp +++ b/src/plugins/tls/openssl/qdtls_openssl.cpp @@ -1,10 +1,6 @@ // 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 NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - #include <QtNetwork/private/qnativesocketengine_p_p.h> #include "qsslsocket_openssl_symbols_p.h" diff --git a/src/plugins/tls/openssl/qsslcontext_openssl.cpp b/src/plugins/tls/openssl/qsslcontext_openssl.cpp index ef0e63911a..75c192bd01 100644 --- a/src/plugins/tls/openssl/qsslcontext_openssl.cpp +++ b/src/plugins/tls/openssl/qsslcontext_openssl.cpp @@ -697,7 +697,9 @@ QT_WARNING_POP return; } - if (!dhparams.isEmpty()) { + if (dhparams.isEmpty()) { + q_SSL_CTX_set_dh_auto(sslContext->ctx, 1); + } else { #ifndef OPENSSL_NO_DEPRECATED_3_0 const QByteArray ¶ms = dhparams.d->derData; const char *ptr = params.constData(); diff --git a/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp b/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp index 81cbc6a12d..16e31e605f 100644 --- a/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp +++ b/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp @@ -137,8 +137,9 @@ int QTlsBackendOpenSSL::dhParametersFromPem(const QByteArray &pem, QByteArray *d if (isSafeDH(dh)) { char *buf = nullptr; const int len = q_i2d_DHparams(dh, reinterpret_cast<unsigned char **>(&buf)); + const auto freeBuf = qScopeGuard([&] { q_OPENSSL_free(buf); }); if (len > 0) - *data = QByteArray(buf, len); + data->assign({buf, len}); else return DHParams::InvalidInputDataError; } else { diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp b/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp index 746a620d67..4aa9ca6fb1 100644 --- a/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp +++ b/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 The Qt Company Ltd. // Copyright (C) 2014 BlackBerry Limited. All rights reserved. // Copyright (C) 2016 Richard J. Moore <rich@kde.org> // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only @@ -159,7 +159,6 @@ DEFINEFUNC3(int, CRYPTO_memcmp, const void * in_a, in_a, const void * in_b, in_b DEFINEFUNC(long, OpenSSL_version_num, void, DUMMYARG, return 0, return) DEFINEFUNC(const char *, OpenSSL_version, int a, a, return nullptr, return) DEFINEFUNC(unsigned long, SSL_SESSION_get_ticket_lifetime_hint, const SSL_SESSION *session, session, return 0, return) -DEFINEFUNC4(void, DH_get0_pqg, const DH *dh, dh, const BIGNUM **p, p, const BIGNUM **q, q, const BIGNUM **g, g, return, DUMMYARG) #if QT_CONFIG(dtls) DEFINEFUNC2(int, DTLSv1_listen, SSL *s, s, BIO_ADDR *c, c, return -1, return) @@ -263,7 +262,6 @@ DEFINEFUNC4(int, OBJ_obj2txt, char *a, a, int b, b, ASN1_OBJECT *c, c, int d, d, DEFINEFUNC(int, OBJ_obj2nid, const ASN1_OBJECT *a, a, return NID_undef, return) DEFINEFUNC4(EVP_PKEY *, PEM_read_bio_PrivateKey, BIO *a, a, EVP_PKEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) -DEFINEFUNC4(DH *, PEM_read_bio_DHparams, BIO *a, a, DH **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) DEFINEFUNC7(int, PEM_write_bio_PrivateKey, BIO *a, a, EVP_PKEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) DEFINEFUNC7(int, PEM_write_bio_PrivateKey_traditional, BIO *a, a, EVP_PKEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) DEFINEFUNC4(EVP_PKEY *, PEM_read_bio_PUBKEY, BIO *a, a, EVP_PKEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) @@ -429,13 +427,21 @@ DEFINEFUNC2(void *, BIO_get_ex_data, BIO *b, b, int idx, idx, return nullptr, re DEFINEFUNC3(int, BIO_set_ex_data, BIO *b, b, int idx, idx, void *data, data, return -1, return) DEFINEFUNC3(void *, CRYPTO_malloc, size_t num, num, const char *file, file, int line, line, return nullptr, return) + +#ifndef OPENSSL_NO_DEPRECATED_3_0 DEFINEFUNC(DH *, DH_new, DUMMYARG, DUMMYARG, return nullptr, return) DEFINEFUNC(void, DH_free, DH *dh, dh, return, DUMMYARG) +DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return) +DEFINEFUNC4(void, DH_get0_pqg, const DH *dh, dh, const BIGNUM **p, p, const BIGNUM **q, q, const BIGNUM **g, g, return, DUMMYARG) + DEFINEFUNC3(DH *, d2i_DHparams, DH**a, a, const unsigned char **pp, pp, long length, length, return nullptr, return) DEFINEFUNC2(int, i2d_DHparams, DH *a, a, unsigned char **p, p, return -1, return) -DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return) + +DEFINEFUNC4(DH *, PEM_read_bio_DHparams, BIO *a, a, DH **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +#endif DEFINEFUNC3(BIGNUM *, BN_bin2bn, const unsigned char *s, s, int len, len, BIGNUM *ret, ret, return nullptr, return) + #ifndef OPENSSL_NO_EC DEFINEFUNC2(size_t, EC_get_builtin_curves, EC_builtin_curve * r, r, size_t nitems, nitems, return 0, return) DEFINEFUNC(int, EC_curve_nist2nid, const char *name, name, return 0, return) @@ -743,10 +749,22 @@ static LoadedOpenSsl loadOpenSsl() #ifdef Q_OS_OPENBSD libcrypto->setLoadHints(QLibrary::ExportExternalSymbolsHint); #endif -#if defined(SHLIB_VERSION_NUMBER) && !defined(Q_OS_QNX) // on QNX, the libs are always libssl.so and libcrypto.so + +#if !defined(Q_OS_QNX) // on QNX, the libs are always libssl.so and libcrypto.so + +#if defined(OPENSSL_SHLIB_VERSION) + // OpenSSL v.3 does not have SLIB_VERSION_NUMBER but has OPENSSL_SHLIB_VERSION. + // The comment about OPENSSL_SHLIB_VERSION in opensslv.h is a bit troublesome: + // "This is defined in free form." + auto shlibVersion = QString("%1"_L1).arg(OPENSSL_SHLIB_VERSION); + libssl->setFileNameAndVersion("ssl"_L1, shlibVersion); + libcrypto->setFileNameAndVersion("crypto"_L1, shlibVersion); +#elif defined(SHLIB_VERSION_NUMBER) // first attempt: the canonical name is libssl.so.<SHLIB_VERSION_NUMBER> libssl->setFileNameAndVersion("ssl"_L1, SHLIB_VERSION_NUMBER ""_L1); libcrypto->setFileNameAndVersion("crypto"_L1, SHLIB_VERSION_NUMBER ""_L1); +#endif // OPENSSL_SHLIB_VERSION + if (libcrypto->load() && libssl->load()) { // libssl.so.<SHLIB_VERSION_NUMBER> and libcrypto.so.<SHLIB_VERSION_NUMBER> found return result; @@ -754,7 +772,7 @@ static LoadedOpenSsl loadOpenSsl() libssl->unload(); libcrypto->unload(); } -#endif +#endif // !defined(Q_OS_QNX) #ifndef Q_OS_DARWIN // second attempt: find the development files libssl.so and libcrypto.so @@ -856,7 +874,6 @@ bool q_resolveOpenSslSymbols() RESOLVEFUNC(OPENSSL_sk_num) RESOLVEFUNC(OPENSSL_sk_pop_free) RESOLVEFUNC(OPENSSL_sk_value) - RESOLVEFUNC(DH_get0_pqg) RESOLVEFUNC(SSL_CTX_set_options) RESOLVEFUNC(SSL_set_info_callback) RESOLVEFUNC(SSL_alert_type_string) @@ -1015,7 +1032,6 @@ bool q_resolveOpenSslSymbols() RESOLVEFUNC(OBJ_obj2txt) RESOLVEFUNC(OBJ_obj2nid) RESOLVEFUNC(PEM_read_bio_PrivateKey) - RESOLVEFUNC(PEM_read_bio_DHparams) RESOLVEFUNC(PEM_write_bio_PrivateKey) RESOLVEFUNC(PEM_write_bio_PrivateKey_traditional) RESOLVEFUNC(PEM_read_bio_PUBKEY) @@ -1066,6 +1082,16 @@ bool q_resolveOpenSslSymbols() #endif // OPENSSL_VERSION_MAJOR >= 3 #ifndef OPENSSL_NO_DEPRECATED_3_0 + RESOLVEFUNC(DH_new) + RESOLVEFUNC(DH_free) + RESOLVEFUNC(DH_check) + RESOLVEFUNC(DH_get0_pqg) + + RESOLVEFUNC(d2i_DHparams) + RESOLVEFUNC(i2d_DHparams) + + RESOLVEFUNC(PEM_read_bio_DHparams) + RESOLVEFUNC(EVP_PKEY_assign) RESOLVEFUNC(EVP_PKEY_cmp) @@ -1210,11 +1236,6 @@ bool q_resolveOpenSslSymbols() #endif // dtls RESOLVEFUNC(CRYPTO_malloc) - RESOLVEFUNC(DH_new) - RESOLVEFUNC(DH_free) - RESOLVEFUNC(d2i_DHparams) - RESOLVEFUNC(i2d_DHparams) - RESOLVEFUNC(DH_check) RESOLVEFUNC(BN_bin2bn) #ifndef OPENSSL_NO_EC diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h b/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h index d020527476..a93c110b3f 100644 --- a/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h +++ b/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h @@ -233,7 +233,6 @@ void q_X509_STORE_set_verify_cb(X509_STORE *ctx, X509_STORE_CTX_verify_cb verify int q_X509_STORE_set_ex_data(X509_STORE *ctx, int idx, void *data); void *q_X509_STORE_get_ex_data(X509_STORE *r, int idx); STACK_OF(X509) *q_X509_STORE_CTX_get0_chain(X509_STORE_CTX *ctx); -void q_DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); # define q_SSL_load_error_strings() q_OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS \ | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL) @@ -391,7 +390,6 @@ int q_OBJ_obj2nid(const ASN1_OBJECT *a); #define q_EVP_get_digestbynid(a) q_EVP_get_digestbyname(q_OBJ_nid2sn(a)) EVP_PKEY *q_PEM_read_bio_PrivateKey(BIO *a, EVP_PKEY **b, pem_password_cb *c, void *d); -DH *q_PEM_read_bio_DHparams(BIO *a, DH **b, pem_password_cb *c, void *d); int q_PEM_write_bio_PrivateKey(BIO *a, EVP_PKEY *b, const EVP_CIPHER *c, unsigned char *d, int e, pem_password_cb *f, void *g); int q_PEM_write_bio_PrivateKey_traditional(BIO *a, EVP_PKEY *b, const EVP_CIPHER *c, unsigned char *d, @@ -504,14 +502,21 @@ X509 *q_X509_STORE_CTX_get_current_cert(X509_STORE_CTX *ctx); X509_STORE *q_X509_STORE_CTX_get0_store(X509_STORE_CTX *ctx); // Diffie-Hellman support +#ifndef OPENSSL_NO_DEPRECATED_3_0 DH *q_DH_new(); void q_DH_free(DH *dh); +int q_DH_check(DH *dh, int *codes); +void q_DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); + DH *q_d2i_DHparams(DH **a, const unsigned char **pp, long length); int q_i2d_DHparams(DH *a, unsigned char **p); -int q_DH_check(DH *dh, int *codes); + +DH *q_PEM_read_bio_DHparams(BIO *a, DH **b, pem_password_cb *c, void *d); +#endif // OPENSSL_NO_DEPRECATED_3_0 BIGNUM *q_BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); #define q_SSL_CTX_set_tmp_dh(ctx, dh) q_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_TMP_DH, 0, (char *)dh) +#define q_SSL_CTX_set_dh_auto(ctx, onoff) q_SSL_CTX_ctrl(ctx,SSL_CTRL_SET_DH_AUTO,onoff,NULL) #ifndef OPENSSL_NO_EC // EC Diffie-Hellman support diff --git a/src/plugins/tls/openssl/qtls_openssl.cpp b/src/plugins/tls/openssl/qtls_openssl.cpp index 031ccd9d15..57d09a649b 100644 --- a/src/plugins/tls/openssl/qtls_openssl.cpp +++ b/src/plugins/tls/openssl/qtls_openssl.cpp @@ -92,7 +92,7 @@ QSslCertificate findCertificateToFetch(const QList<QSslError> &tlsErrors, bool c if (checkAIA) { const auto extensions = certToFetch.extensions(); for (const auto &ext : extensions) { - if (ext.oid() == QStringLiteral("1.3.6.1.5.5.7.1.1")) // See RFC 4325 + if (ext.oid() == u"1.3.6.1.5.5.7.1.1") // See RFC 4325 return certToFetch; } //The only reason we check this extensions is because an application set trusted diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp index 5ce5f45a5b..d73515724b 100644 --- a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp +++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp @@ -17,7 +17,7 @@ #include <QtNetwork/qssl.h> #include <QtCore/qdir.h> -#include <QtCore/qdiriterator.h> +#include <QtCore/qdirlisting.h> #include <QtCore/qlist.h> #include <QtCore/qmutex.h> #include <QtCore/qscopeguard.h> @@ -204,11 +204,11 @@ void QTlsBackendOpenSSL::ensureCiphersAndCertsLoaded() const #elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) // check whether we can enable on-demand root-cert loading (i.e. check whether the sym links are there) const QList<QByteArray> dirs = QSslSocketPrivate::unixRootCertDirectories(); - QStringList symLinkFilter; - symLinkFilter << "[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].[0-9]"_L1; + const QStringList symLinkFilter{ + u"[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].[0-9]"_s}; for (const auto &dir : dirs) { - QDirIterator iterator(QLatin1StringView(dir), symLinkFilter, QDir::Files); - if (iterator.hasNext()) { + QDirListing dirList(QString::fromLatin1(dir), symLinkFilter, QDir::Files); + if (dirList.cbegin() != dirList.cend()) { // Not empty QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); break; } @@ -363,7 +363,9 @@ QList<QSslCertificate> systemCaCertificates() QList<QSslCertificate> systemCerts; #if defined(Q_OS_WIN) HCERTSTORE hSystemStore; - hSystemStore = CertOpenSystemStoreW(0, L"ROOT"); + hSystemStore = + 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 (1) { @@ -392,10 +394,9 @@ QList<QSslCertificate> systemCaCertificates() currentDir.setNameFilters(QStringList{QStringLiteral("*.pem"), QStringLiteral("*.crt")}); for (const auto &directory : directories) { currentDir.setPath(QLatin1StringView(directory)); - QDirIterator it(currentDir); - while (it.hasNext()) { + for (const auto &dirEntry : QDirListing(currentDir)) { // use canonical path here to not load the same certificate twice if symlinked - certFiles.insert(it.nextFileInfo().canonicalFilePath()); + certFiles.insert(dirEntry.canonicalFilePath()); } } for (const QString& file : std::as_const(certFiles)) diff --git a/src/plugins/tls/openssl/qtlskey_openssl.cpp b/src/plugins/tls/openssl/qtlskey_openssl.cpp index 8f54fda7fa..294fc2ffcd 100644 --- a/src/plugins/tls/openssl/qtlskey_openssl.cpp +++ b/src/plugins/tls/openssl/qtlskey_openssl.cpp @@ -222,7 +222,7 @@ Qt::HANDLE TlsKeyOpenSSL::handle() const #else qCWarning(lcTlsBackend, "This version of OpenSSL disabled direct manipulation with RSA/DSA/DH/EC_KEY structures, consider using QSsl::Opaque instead."); - return Qt::HANDLE(nullptr); + return Qt::HANDLE(genericKey); #endif } diff --git a/src/plugins/tls/openssl/qwindowscarootfetcher.cpp b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp index 82ad3abfd0..a18aae0b71 100644 --- a/src/plugins/tls/openssl/qwindowscarootfetcher.cpp +++ b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp @@ -245,3 +245,5 @@ QHCertStorePointer QWindowsCaRootFetcher::createAdditionalStore() const } QT_END_NAMESPACE + +#include "moc_qwindowscarootfetcher_p.cpp" diff --git a/src/plugins/tls/schannel/CMakeLists.txt b/src/plugins/tls/schannel/CMakeLists.txt index 9a53bf9ee6..a7f7fcd99f 100644 --- a/src/plugins/tls/schannel/CMakeLists.txt +++ b/src/plugins/tls/schannel/CMakeLists.txt @@ -29,4 +29,6 @@ qt_internal_add_plugin(QSchannelBackendPlugin secur32 bcrypt ncrypt + 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 b230f2f787..a244a90ebc 100644 --- a/src/plugins/tls/schannel/qtls_schannel.cpp +++ b/src/plugins/tls/schannel/qtls_schannel.cpp @@ -26,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 @@ -93,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!: @@ -114,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) @@ -133,39 +129,341 @@ Q_LOGGING_CATEGORY(lcTlsBackendSchannel, "qt.tlsbackend.schannel"); QByteArray _q_makePkcs12(const QList<QSslCertificate> &certs, const QSslKey &key, const QString &passPhrase); +namespace { +bool supportsTls13(); +} + namespace QTlsPrivate { -QList<QSslCipher> defaultCiphers() +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) { - // Previously the code was in QSslSocketBackendPrivate. QList<QSslCipher> ciphers; - const QString protocolStrings[] = { QStringLiteral("TLSv1"), QStringLiteral("TLSv1.1"), - QStringLiteral("TLSv1.2"), QStringLiteral("TLSv1.3") }; + + for (const auto &cipher : schannelCipherInfo) { + if (QLatin1StringView(cipher.schannelCipherSuite) == schannelSuiteName) { + for (const auto &protocol : cipher.protocols) { QT_WARNING_PUSH QT_WARNING_DISABLE_DEPRECATED - const QSsl::SslProtocol protocols[] = { QSsl::TlsV1_0, QSsl::TlsV1_1, - QSsl::TlsV1_2, QSsl::TlsV1_3 }; + 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 - 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); + ciphers.append(QTlsBackend::createCiphersuite(QLatin1StringView(cipher.openSslCipherSuite), + QLatin1StringView(cipher.keyExchangeMethod), + QLatin1StringView(cipher.encryptionMethod), + QLatin1StringView(cipher.authenticationMethod), + cipher.encryptionBits, + protocol, protocolName)); + } + } } return ciphers; - } -} // namespace QTlsPrivate +QList<QSslCipher> defaultCiphers() +{ + ULONG contextFunctionsCount = {}; + PCRYPT_CONTEXT_FUNCTIONS contextFunctions = {}; -namespace { -bool supportsTls13(); + 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) @@ -298,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, @@ -319,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) @@ -383,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. @@ -391,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) @@ -459,17 +763,15 @@ QT_WARNING_POP 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 +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(); @@ -722,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)) { @@ -784,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")); @@ -838,78 +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 | defaultCredsFlag(), - 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 | defaultCredsFlag(), // 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; @@ -1114,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; @@ -1283,6 +1561,11 @@ 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); @@ -1435,6 +1718,7 @@ void TlsCryptographSchannel::reset() deallocateContext(); freeCredentialsHandle(); // in case we already had one (@future: session resumption requires re-use) + cipherInfo = {}; connectionInfo = {}; streamSizes = {}; @@ -1484,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(); @@ -1637,8 +1923,12 @@ void TlsCryptographSchannel::transmit() qCWarning(lcTlsBackendSchannel, "The internal SSPI handle is invalid!"); Q_UNREACHABLE(); } else if (status == SEC_E_INVALID_TOKEN) { - qCWarning(lcTlsBackendSchannel, "Got SEC_E_INVALID_TOKEN!"); - Q_UNREACHABLE(); // Happened once due to a bug, but shouldn't generally happen(?) + // 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 @@ -1809,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 @@ -1994,7 +2293,10 @@ 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(lcTlsBackendSchannel, "Failed to open the system root CA certificate store!"); @@ -2108,10 +2410,40 @@ 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 @@ -2259,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 f6bfdbc7cf..fab8777249 100644 --- a/src/plugins/tls/schannel/qtls_schannel_p.h +++ b/src/plugins/tls/schannel/qtls_schannel_p.h @@ -93,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 a70f8922a6..7c2d675e79 100644 --- a/src/plugins/tls/schannel/qtlsbackend_schannel_p.h +++ b/src/plugins/tls/schannel/qtlsbackend_schannel_p.h @@ -57,6 +57,7 @@ private: QTlsPrivate::X509PemReaderPtr X509PemReader() const override; QTlsPrivate::X509DerReaderPtr X509DerReader() const override; + QTlsPrivate::X509Pkcs12ReaderPtr X509Pkcs12Reader() const override; static bool s_loadedCiphersAndCerts; }; diff --git a/src/plugins/tls/schannel/qx509_schannel.cpp b/src/plugins/tls/schannel/qx509_schannel.cpp index 5a5e625268..d9d82dce29 100644 --- a/src/plugins/tls/schannel/qx509_schannel.cpp +++ b/src/plugins/tls/schannel/qx509_schannel.cpp @@ -1,9 +1,11 @@ // 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> @@ -39,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 17b91983f2..4625c9584c 100644 --- a/src/plugins/tls/schannel/qx509_schannel_p.h +++ b/src/plugins/tls/schannel/qx509_schannel_p.h @@ -36,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; diff --git a/src/plugins/tls/securetransport/qtlsbackend_st.cpp b/src/plugins/tls/securetransport/qtlsbackend_st.cpp index 4c385b5af6..54e45d1720 100644 --- a/src/plugins/tls/securetransport/qtlsbackend_st.cpp +++ b/src/plugins/tls/securetransport/qtlsbackend_st.cpp @@ -357,3 +357,5 @@ QTlsPrivate::TlsCryptograph *QSecureTransportBackend::createTlsCryptograph() con QT_END_NAMESPACE + +#include "moc_qtlsbackend_st_p.cpp" diff --git a/src/plugins/tls/shared/qasn1element.cpp b/src/plugins/tls/shared/qasn1element.cpp index 507a3a727a..97be46866d 100644 --- a/src/plugins/tls/shared/qasn1element.cpp +++ b/src/plugins/tls/shared/qasn1element.cpp @@ -6,6 +6,7 @@ #include <QtCore/qdatastream.h> #include <QtCore/qdatetime.h> +#include <QtCore/qtimezone.h> #include <QtCore/qlist.h> #include <QDebug> #include <private/qtools_p.h> @@ -224,31 +225,28 @@ QDateTime QAsn1Element::toDateTime() const return result; if (mType == UtcTimeType && mValue.size() == 13) { - result = QDateTime::fromString(QString::fromLatin1(mValue), - QStringLiteral("yyMMddHHmmsst")); - if (!result.isValid()) - return result; - - Q_ASSERT(result.timeSpec() == Qt::UTC); - - QDate date = result.date(); - // RFC 2459: // Where YY is greater than or equal to 50, the year shall be // interpreted as 19YY; and // // Where YY is less than 50, the year shall be interpreted as 20YY. // - // QDateTime interprets the 'yy' format as 19yy, so we may need to adjust - // the year (bring it in the [1950, 2049] range). - if (date.year() < 1950) - result.setDate(date.addYears(100)); + // so use 1950 as base year. + constexpr int rfc2459CenturyStart = 1950; + const QLatin1StringView inputView(mValue); + QDate date = QDate::fromString(inputView.first(6), u"yyMMdd", rfc2459CenturyStart); + if (!date.isValid()) + return result; + + Q_ASSERT(date.year() >= rfc2459CenturyStart); + Q_ASSERT(date.year() < 100 + rfc2459CenturyStart); - Q_ASSERT(result.date().year() >= 1950); - Q_ASSERT(result.date().year() <= 2049); + QTime time = QTime::fromString(inputView.sliced(6, 6), u"HHmmss"); + if (!time.isValid()) + return result; + result = QDateTime(date, time, QTimeZone::UTC); } else if (mType == GeneralizedTimeType && mValue.size() == 15) { - result = QDateTime::fromString(QString::fromLatin1(mValue), - QStringLiteral("yyyyMMddHHmmsst")); + result = QDateTime::fromString(QString::fromLatin1(mValue), u"yyyyMMddHHmmsst"); } return result; diff --git a/src/plugins/tls/shared/qwincrypt_p.h b/src/plugins/tls/shared/qwincrypt_p.h index 1b1f0f16c0..48ca4247fa 100644 --- a/src/plugins/tls/shared/qwincrypt_p.h +++ b/src/plugins/tls/shared/qwincrypt_p.h @@ -40,6 +40,16 @@ struct QHCertStoreDeleter { // A simple RAII type used by Schannel code and Window CA fetcher class: using QHCertStorePointer = std::unique_ptr<void, QHCertStoreDeleter>; +struct QPCCertContextDeleter { + void operator()(PCCERT_CONTEXT context) const + { + CertFreeCertificateContext(context); + } +}; + +// A simple RAII type used by Schannel code +using QPCCertContextPointer = std::unique_ptr<const CERT_CONTEXT, QPCCertContextDeleter>; + QT_END_NAMESPACE #endif // QWINCRYPT_P_H diff --git a/src/plugins/tls/shared/qx509_generic.cpp b/src/plugins/tls/shared/qx509_generic.cpp index e700552c43..5006db1a72 100644 --- a/src/plugins/tls/shared/qx509_generic.cpp +++ b/src/plugins/tls/shared/qx509_generic.cpp @@ -118,7 +118,7 @@ QList<QSslCertificate> X509CertificateGeneric::certificatesFromPem(const QByteAr QByteArray decoded = QByteArray::fromBase64( QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); - certificates << certificatesFromDer(decoded, 1);; + certificates << certificatesFromDer(decoded, 1); } return certificates; |