From d385158d5213ef568b7629e2aa4a818016bbffac Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Thu, 25 Mar 2021 12:41:08 +0100 Subject: Move plugin code from QtNetwork to qtbase/plugins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All TLS (and non-TLS) backends that QSsl classes rely on are now in plugins/tls (as openssl, securetransport, schannel and certonly plugins). For now, I have to disable some tests that were using OpenSSL calls - this to be refactored/re-thought. These include: qsslsocket auto-test (test-case where we work with private keys), qsslkey auto-test (similar to qsslsocket - test-case working with keys using OpenSSL calls). qasn1element moved to plugins too, so its auto-test have to be re-thought. Since now we can have more than one working TLS-backend on a given platform, the presence of OpenSSL also means I force this backend as active before running tests, to make sure features implemented only in OpenSSL-backend are tested. OCSP auto test is disabled for now, since it heavily relies on OpenSSL symbols (to be refactored). [ChangeLog][QtNetwork][QSslSocket] QSslSocket by default prefers 'openssl' backend if it is available. [ChangeLog][QtNetwork][QSslSocket] TLS-backends are not mutually exclusive anymore, depending on a platform, more than one TLS backend can be built. E.g., configuring Qt with -openssl does not prevent SecureTransport or Schannel plugin from being built. Fixes: QTBUG-91928 Change-Id: I4c05e32f10179066bee3a518bdfdd6c4b15320c3 Reviewed-by: Qt CI Bot Reviewed-by: Edward Welbourne Reviewed-by: Mårten Nordheim --- src/network/CMakeLists.txt | 71 +- src/network/configure.cmake | 9 +- src/network/configure.json | 8 +- src/network/ssl/qasn1element.cpp | 400 ---- src/network/ssl/qasn1element_p.h | 188 -- src/network/ssl/qdtls_base.cpp | 112 - src/network/ssl/qdtls_base_p.h | 115 - src/network/ssl/qdtls_openssl.cpp | 1451 ------------ src/network/ssl/qdtls_openssl_p.h | 251 --- src/network/ssl/qopenssl.cpp | 71 - src/network/ssl/qopenssl_p.h | 117 - src/network/ssl/qsslcontext_openssl.cpp | 824 ------- src/network/ssl/qsslcontext_openssl_p.h | 132 -- .../ssl/qssldiffiehellmanparameters_openssl.cpp | 230 -- src/network/ssl/qsslkey_p.cpp | 4 - src/network/ssl/qsslkey_p.h | 4 +- src/network/ssl/qsslsocket.cpp | 2 +- src/network/ssl/qsslsocket_mac_shared.cpp | 148 -- src/network/ssl/qsslsocket_openssl_android.cpp | 93 - src/network/ssl/qsslsocket_openssl_symbols.cpp | 1234 ----------- src/network/ssl/qsslsocket_openssl_symbols_p.h | 761 ------- src/network/ssl/qsslsocket_p.h | 5 +- src/network/ssl/qsslsocket_qt.cpp | 309 --- src/network/ssl/qtls_openssl.cpp | 1837 ---------------- src/network/ssl/qtls_openssl_p.h | 169 -- src/network/ssl/qtls_schannel.cpp | 2322 -------------------- src/network/ssl/qtls_schannel_p.h | 159 -- src/network/ssl/qtls_st.cpp | 1331 ----------- src/network/ssl/qtls_st_p.h | 141 -- src/network/ssl/qtlsbackend.cpp | 47 +- src/network/ssl/qtlsbackend_cert.cpp | 96 - src/network/ssl/qtlsbackend_cert_p.h | 83 - src/network/ssl/qtlsbackend_openssl.cpp | 630 ------ src/network/ssl/qtlsbackend_openssl_p.h | 134 -- src/network/ssl/qtlsbackend_p.h | 6 + src/network/ssl/qtlsbackend_schannel_p.h | 100 - src/network/ssl/qtlsbackend_st.cpp | 341 --- src/network/ssl/qtlsbackend_st_p.h | 94 - src/network/ssl/qtlskey_base.cpp | 133 -- src/network/ssl/qtlskey_base_p.h | 112 - src/network/ssl/qtlskey_generic.cpp | 884 -------- src/network/ssl/qtlskey_generic_p.h | 117 - src/network/ssl/qtlskey_openssl.cpp | 509 ----- src/network/ssl/qtlskey_openssl_p.h | 126 -- src/network/ssl/qtlskey_schannel.cpp | 187 -- src/network/ssl/qtlskey_schannel_p.h | 82 - src/network/ssl/qtlskey_st.cpp | 110 - src/network/ssl/qtlskey_st_p.h | 83 - src/network/ssl/qwincrypt_p.h | 81 - src/network/ssl/qwindowscarootfetcher.cpp | 292 --- src/network/ssl/qwindowscarootfetcher_p.h | 95 - src/network/ssl/qx509_base.cpp | 178 -- src/network/ssl/qx509_base_p.h | 125 -- src/network/ssl/qx509_generic.cpp | 467 ---- src/network/ssl/qx509_generic_p.h | 101 - src/network/ssl/qx509_openssl.cpp | 929 -------- src/network/ssl/qx509_openssl_p.h | 133 -- src/network/ssl/qx509_schannel.cpp | 87 - src/network/ssl/qx509_schannel_p.h | 87 - src/network/ssl/qx509_st.cpp | 61 - src/network/ssl/qx509_st_p.h | 74 - src/plugins/CMakeLists.txt | 1 + src/plugins/tls/CMakeLists.txt | 13 + src/plugins/tls/certonly/CMakeLists.txt | 16 + src/plugins/tls/certonly/qtlsbackend_cert.cpp | 92 + src/plugins/tls/certonly/qtlsbackend_cert_p.h | 81 + src/plugins/tls/openssl/CMakeLists.txt | 51 + src/plugins/tls/openssl/qdtls_openssl.cpp | 1454 ++++++++++++ src/plugins/tls/openssl/qdtls_openssl_p.h | 250 +++ src/plugins/tls/openssl/qopenssl_p.h | 118 + src/plugins/tls/openssl/qsslcontext_openssl.cpp | 827 +++++++ src/plugins/tls/openssl/qsslcontext_openssl_p.h | 134 ++ .../qssldiffiehellmanparameters_openssl.cpp | 231 ++ .../tls/openssl/qsslsocket_openssl_android.cpp | 92 + .../tls/openssl/qsslsocket_openssl_symbols.cpp | 1221 ++++++++++ .../tls/openssl/qsslsocket_openssl_symbols_p.h | 759 +++++++ src/plugins/tls/openssl/qtls_openssl.cpp | 1839 ++++++++++++++++ src/plugins/tls/openssl/qtls_openssl_p.h | 170 ++ src/plugins/tls/openssl/qtlsbackend_openssl.cpp | 637 ++++++ src/plugins/tls/openssl/qtlsbackend_openssl_p.h | 141 ++ src/plugins/tls/openssl/qtlskey_openssl.cpp | 511 +++++ src/plugins/tls/openssl/qtlskey_openssl_p.h | 127 ++ src/plugins/tls/openssl/qwindowscarootfetcher.cpp | 283 +++ src/plugins/tls/openssl/qwindowscarootfetcher_p.h | 95 + src/plugins/tls/openssl/qx509_openssl.cpp | 939 ++++++++ src/plugins/tls/openssl/qx509_openssl_p.h | 124 ++ src/plugins/tls/schannel/CMakeLists.txt | 30 + src/plugins/tls/schannel/qtls_schannel.cpp | 2314 +++++++++++++++++++ src/plugins/tls/schannel/qtls_schannel_p.h | 158 ++ src/plugins/tls/schannel/qtlsbackend_schannel_p.h | 104 + src/plugins/tls/schannel/qtlskey_schannel.cpp | 189 ++ src/plugins/tls/schannel/qtlskey_schannel_p.h | 82 + src/plugins/tls/schannel/qx509_schannel.cpp | 88 + src/plugins/tls/schannel/qx509_schannel_p.h | 85 + src/plugins/tls/securetransport/CMakeLists.txt | 33 + src/plugins/tls/securetransport/qtls_st.cpp | 1322 +++++++++++ src/plugins/tls/securetransport/qtls_st_p.h | 141 ++ src/plugins/tls/securetransport/qtlsbackend_st.cpp | 341 +++ src/plugins/tls/securetransport/qtlsbackend_st_p.h | 98 + src/plugins/tls/securetransport/qtlskey_st.cpp | 111 + src/plugins/tls/securetransport/qtlskey_st_p.h | 83 + src/plugins/tls/securetransport/qx509_st.cpp | 61 + src/plugins/tls/securetransport/qx509_st_p.h | 74 + src/plugins/tls/shared/qasn1element.cpp | 400 ++++ src/plugins/tls/shared/qasn1element_p.h | 188 ++ src/plugins/tls/shared/qdtls_base.cpp | 112 + src/plugins/tls/shared/qdtls_base_p.h | 116 + src/plugins/tls/shared/qsslsocket_mac_shared.cpp | 150 ++ src/plugins/tls/shared/qsslsocket_qt.cpp | 311 +++ src/plugins/tls/shared/qtlskey_base.cpp | 133 ++ src/plugins/tls/shared/qtlskey_base_p.h | 108 + src/plugins/tls/shared/qtlskey_generic.cpp | 885 ++++++++ src/plugins/tls/shared/qtlskey_generic_p.h | 119 + src/plugins/tls/shared/qwincrypt_p.h | 81 + src/plugins/tls/shared/qx509_base.cpp | 178 ++ src/plugins/tls/shared/qx509_base_p.h | 125 ++ src/plugins/tls/shared/qx509_generic.cpp | 468 ++++ src/plugins/tls/shared/qx509_generic_p.h | 101 + 118 files changed, 19046 insertions(+), 19031 deletions(-) delete mode 100644 src/network/ssl/qasn1element.cpp delete mode 100644 src/network/ssl/qasn1element_p.h delete mode 100644 src/network/ssl/qdtls_base.cpp delete mode 100644 src/network/ssl/qdtls_base_p.h delete mode 100644 src/network/ssl/qdtls_openssl.cpp delete mode 100644 src/network/ssl/qdtls_openssl_p.h delete mode 100644 src/network/ssl/qopenssl.cpp delete mode 100644 src/network/ssl/qopenssl_p.h delete mode 100644 src/network/ssl/qsslcontext_openssl.cpp delete mode 100644 src/network/ssl/qsslcontext_openssl_p.h delete mode 100644 src/network/ssl/qssldiffiehellmanparameters_openssl.cpp delete mode 100644 src/network/ssl/qsslsocket_mac_shared.cpp delete mode 100644 src/network/ssl/qsslsocket_openssl_android.cpp delete mode 100644 src/network/ssl/qsslsocket_openssl_symbols.cpp delete mode 100644 src/network/ssl/qsslsocket_openssl_symbols_p.h delete mode 100644 src/network/ssl/qsslsocket_qt.cpp delete mode 100644 src/network/ssl/qtls_openssl.cpp delete mode 100644 src/network/ssl/qtls_openssl_p.h delete mode 100644 src/network/ssl/qtls_schannel.cpp delete mode 100644 src/network/ssl/qtls_schannel_p.h delete mode 100644 src/network/ssl/qtls_st.cpp delete mode 100644 src/network/ssl/qtls_st_p.h delete mode 100644 src/network/ssl/qtlsbackend_cert.cpp delete mode 100644 src/network/ssl/qtlsbackend_cert_p.h delete mode 100644 src/network/ssl/qtlsbackend_openssl.cpp delete mode 100644 src/network/ssl/qtlsbackend_openssl_p.h delete mode 100644 src/network/ssl/qtlsbackend_schannel_p.h delete mode 100644 src/network/ssl/qtlsbackend_st.cpp delete mode 100644 src/network/ssl/qtlsbackend_st_p.h delete mode 100644 src/network/ssl/qtlskey_base.cpp delete mode 100644 src/network/ssl/qtlskey_base_p.h delete mode 100644 src/network/ssl/qtlskey_generic.cpp delete mode 100644 src/network/ssl/qtlskey_generic_p.h delete mode 100644 src/network/ssl/qtlskey_openssl.cpp delete mode 100644 src/network/ssl/qtlskey_openssl_p.h delete mode 100644 src/network/ssl/qtlskey_schannel.cpp delete mode 100644 src/network/ssl/qtlskey_schannel_p.h delete mode 100644 src/network/ssl/qtlskey_st.cpp delete mode 100644 src/network/ssl/qtlskey_st_p.h delete mode 100644 src/network/ssl/qwincrypt_p.h delete mode 100644 src/network/ssl/qwindowscarootfetcher.cpp delete mode 100644 src/network/ssl/qwindowscarootfetcher_p.h delete mode 100644 src/network/ssl/qx509_base.cpp delete mode 100644 src/network/ssl/qx509_base_p.h delete mode 100644 src/network/ssl/qx509_generic.cpp delete mode 100644 src/network/ssl/qx509_generic_p.h delete mode 100644 src/network/ssl/qx509_openssl.cpp delete mode 100644 src/network/ssl/qx509_openssl_p.h delete mode 100644 src/network/ssl/qx509_schannel.cpp delete mode 100644 src/network/ssl/qx509_schannel_p.h delete mode 100644 src/network/ssl/qx509_st.cpp delete mode 100644 src/network/ssl/qx509_st_p.h create mode 100644 src/plugins/tls/CMakeLists.txt create mode 100644 src/plugins/tls/certonly/CMakeLists.txt create mode 100644 src/plugins/tls/certonly/qtlsbackend_cert.cpp create mode 100644 src/plugins/tls/certonly/qtlsbackend_cert_p.h create mode 100644 src/plugins/tls/openssl/CMakeLists.txt create mode 100644 src/plugins/tls/openssl/qdtls_openssl.cpp create mode 100644 src/plugins/tls/openssl/qdtls_openssl_p.h create mode 100644 src/plugins/tls/openssl/qopenssl_p.h create mode 100644 src/plugins/tls/openssl/qsslcontext_openssl.cpp create mode 100644 src/plugins/tls/openssl/qsslcontext_openssl_p.h create mode 100644 src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp create mode 100644 src/plugins/tls/openssl/qsslsocket_openssl_android.cpp create mode 100644 src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp create mode 100644 src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h create mode 100644 src/plugins/tls/openssl/qtls_openssl.cpp create mode 100644 src/plugins/tls/openssl/qtls_openssl_p.h create mode 100644 src/plugins/tls/openssl/qtlsbackend_openssl.cpp create mode 100644 src/plugins/tls/openssl/qtlsbackend_openssl_p.h create mode 100644 src/plugins/tls/openssl/qtlskey_openssl.cpp create mode 100644 src/plugins/tls/openssl/qtlskey_openssl_p.h create mode 100644 src/plugins/tls/openssl/qwindowscarootfetcher.cpp create mode 100644 src/plugins/tls/openssl/qwindowscarootfetcher_p.h create mode 100644 src/plugins/tls/openssl/qx509_openssl.cpp create mode 100644 src/plugins/tls/openssl/qx509_openssl_p.h create mode 100644 src/plugins/tls/schannel/CMakeLists.txt create mode 100644 src/plugins/tls/schannel/qtls_schannel.cpp create mode 100644 src/plugins/tls/schannel/qtls_schannel_p.h create mode 100644 src/plugins/tls/schannel/qtlsbackend_schannel_p.h create mode 100644 src/plugins/tls/schannel/qtlskey_schannel.cpp create mode 100644 src/plugins/tls/schannel/qtlskey_schannel_p.h create mode 100644 src/plugins/tls/schannel/qx509_schannel.cpp create mode 100644 src/plugins/tls/schannel/qx509_schannel_p.h create mode 100644 src/plugins/tls/securetransport/CMakeLists.txt create mode 100644 src/plugins/tls/securetransport/qtls_st.cpp create mode 100644 src/plugins/tls/securetransport/qtls_st_p.h create mode 100644 src/plugins/tls/securetransport/qtlsbackend_st.cpp create mode 100644 src/plugins/tls/securetransport/qtlsbackend_st_p.h create mode 100644 src/plugins/tls/securetransport/qtlskey_st.cpp create mode 100644 src/plugins/tls/securetransport/qtlskey_st_p.h create mode 100644 src/plugins/tls/securetransport/qx509_st.cpp create mode 100644 src/plugins/tls/securetransport/qx509_st_p.h create mode 100644 src/plugins/tls/shared/qasn1element.cpp create mode 100644 src/plugins/tls/shared/qasn1element_p.h create mode 100644 src/plugins/tls/shared/qdtls_base.cpp create mode 100644 src/plugins/tls/shared/qdtls_base_p.h create mode 100644 src/plugins/tls/shared/qsslsocket_mac_shared.cpp create mode 100644 src/plugins/tls/shared/qsslsocket_qt.cpp create mode 100644 src/plugins/tls/shared/qtlskey_base.cpp create mode 100644 src/plugins/tls/shared/qtlskey_base_p.h create mode 100644 src/plugins/tls/shared/qtlskey_generic.cpp create mode 100644 src/plugins/tls/shared/qtlskey_generic_p.h create mode 100644 src/plugins/tls/shared/qwincrypt_p.h create mode 100644 src/plugins/tls/shared/qx509_base.cpp create mode 100644 src/plugins/tls/shared/qx509_base_p.h create mode 100644 src/plugins/tls/shared/qx509_generic.cpp create mode 100644 src/plugins/tls/shared/qx509_generic_p.h (limited to 'src') diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index f3802e37e9..9cdfa729a7 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -5,7 +5,7 @@ ##################################################################### qt_internal_add_module(Network - PLUGIN_TYPES networkaccessbackends networkinformationbackends + PLUGIN_TYPES networkaccessbackends networkinformationbackends tls SOURCES access/qabstractnetworkcache.cpp access/qabstractnetworkcache.h access/qabstractnetworkcache_p.h access/qhsts.cpp access/qhsts_p.h @@ -41,15 +41,11 @@ qt_internal_add_module(Network socket/qtcpserver.cpp socket/qtcpserver.h socket/qtcpserver_p.h socket/qtcpsocket.cpp socket/qtcpsocket.h socket/qtcpsocket_p.h socket/qudpsocket.cpp socket/qudpsocket.h - ssl/qasn1element.cpp ssl/qasn1element_p.h ssl/qpassworddigestor.cpp ssl/qpassworddigestor.h ssl/qssl.cpp ssl/qssl.h ssl/qssl_p.h ssl/qsslcertificate.cpp ssl/qsslcertificate.h ssl/qsslcertificate_p.h ssl/qsslcertificateextension.cpp ssl/qsslcertificateextension.h ssl/qsslcertificateextension_p.h ssl/qtlsbackend.cpp ssl/qtlsbackend_p.h - ssl/qtlsbackend_cert.cpp ssl/qtlsbackend_cert_p.h - ssl/qx509_base.cpp ssl/qx509_base_p.h - ssl/qx509_generic.cpp ssl/qx509_generic_p.h DEFINES QT_NO_FOREACH QT_NO_USING_NAMESPACE @@ -324,59 +320,11 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_ssl ssl/qsslkey.h ssl/qsslkey_p.cpp ssl/qsslkey_p.h ssl/qsslpresharedkeyauthenticator.cpp ssl/qsslpresharedkeyauthenticator.h ssl/qsslpresharedkeyauthenticator_p.h ssl/qsslsocket.cpp ssl/qsslsocket.h ssl/qsslsocket_p.h - ssl/qtlskey_base.cpp ssl/qtlskey_base_p.h -) - -qt_internal_extend_target(Network CONDITION QT_FEATURE_schannel AND QT_FEATURE_ssl - SOURCES - ssl/qsslsocket_qt.cpp - ssl/qwincrypt_p.h - ssl/qtls_schannel.cpp ssl/qtls_schannel_p.h - ssl/qtlsbackend_schannel_p.h - ssl/qtlskey_generic.cpp ssl/qtlskey_generic_p.h - ssl/qtlskey_schannel.cpp ssl/qtlskey_schannel_p.h - ssl/qx509_schannel.cpp ssl/qx509_schannel_p.h - LIBRARIES - Crypt32 - Secur32 - bcrypt - ncrypt -) - -qt_internal_extend_target(Network CONDITION QT_FEATURE_securetransport AND QT_FEATURE_ssl - SOURCES - ssl/qtls_st.cpp ssl/qtls_st_p.h - ssl/qsslsocket_mac_shared.cpp - ssl/qsslsocket_qt.cpp - ssl/qtlskey_generic.cpp ssl/qtlskey_generic_p.h - ssl/qtlskey_st.cpp ssl/qtlskey_st_p.h - ssl/qtlsbackend_st.cpp ssl/qtlsbackend_st_p.h - ssl/qx509_st.cpp ssl/qx509_st_p.h ) qt_internal_extend_target(Network CONDITION QT_FEATURE_dtls AND QT_FEATURE_ssl SOURCES ssl/qdtls.cpp ssl/qdtls.h ssl/qdtls_p.h - ssl/qdtls_base.cpp ssl/qdtls_base_p.h -) - -qt_internal_extend_target(Network CONDITION QT_FEATURE_openssl AND QT_FEATURE_ssl - SOURCES - ssl/qsslcontext_openssl.cpp ssl/qsslcontext_openssl_p.h - ssl/qssldiffiehellmanparameters_openssl.cpp - ssl/qopenssl.cpp ssl/qopenssl_p.h - ssl/qsslsocket_openssl_symbols.cpp ssl/qsslsocket_openssl_symbols_p.h - ssl/qtls_openssl.cpp ssl/qtls_openssl_p.h - ssl/qtlskey_openssl.cpp ssl/qtlskey_openssl_p.h - ssl/qtlsbackend_openssl.cpp ssl/qtlsbackend_openssl_p.h - ssl/qx509_openssl.cpp ssl/qx509_openssl_p.h - DEFINES - OPENSSL_API_COMPAT=0x10100000L -) - -qt_internal_extend_target(Network CONDITION QT_FEATURE_dtls AND QT_FEATURE_openssl AND QT_FEATURE_ssl - SOURCES - ssl/qdtls_openssl.cpp ssl/qdtls_openssl_p.h ) qt_internal_extend_target(Network CONDITION QT_FEATURE_ocsp AND QT_FEATURE_openssl AND QT_FEATURE_ssl @@ -384,16 +332,6 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_ocsp AND QT_FEATURE_opens ssl/qocsp_p.h ) -qt_internal_extend_target(Network CONDITION APPLE AND QT_FEATURE_openssl AND QT_FEATURE_ssl - SOURCES - ssl/qsslsocket_mac_shared.cpp -) - -qt_internal_extend_target(Network CONDITION ANDROID AND QT_FEATURE_openssl AND QT_FEATURE_ssl AND NOT ANDROID_EMBEDDED - SOURCES - ssl/qsslsocket_openssl_android.cpp -) - qt_internal_extend_target(Network CONDITION QT_FEATURE_openssl AND QT_FEATURE_openssl_linked AND QT_FEATURE_ssl LIBRARIES WrapOpenSSL::WrapOpenSSL @@ -408,13 +346,6 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_openssl AND QT_FEATURE_ss WrapOpenSSLHeaders::WrapOpenSSLHeaders ) -qt_internal_extend_target(Network CONDITION QT_FEATURE_openssl AND QT_FEATURE_ssl AND WIN32 - SOURCES - ssl/qwindowscarootfetcher.cpp ssl/qwindowscarootfetcher_p.h - LIBRARIES - crypt32 -) - qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND UNIX AND NOT ANDROID AND NOT INTEGRITY SOURCES kernel/qdnslookup_unix.cpp diff --git a/src/network/configure.cmake b/src/network/configure.cmake index f1885a6ff8..37f17ad4bd 100644 --- a/src/network/configure.cmake +++ b/src/network/configure.cmake @@ -263,27 +263,26 @@ qt_feature_definition("openssl" "QT_NO_OPENSSL" NEGATE) qt_feature_config("openssl" QMAKE_PUBLIC_QT_CONFIG) qt_feature("openssl-runtime" AUTODETECT NOT WASM - CONDITION NOT QT_FEATURE_securetransport AND NOT QT_FEATURE_schannel AND TEST_openssl_headers + CONDITION TEST_openssl_headers ENABLE INPUT_openssl STREQUAL 'yes' OR INPUT_openssl STREQUAL 'runtime' DISABLE INPUT_openssl STREQUAL 'no' OR INPUT_openssl STREQUAL 'linked' OR INPUT_ssl STREQUAL 'no' ) qt_feature("openssl-linked" PRIVATE LABEL " Qt directly linked to OpenSSL" AUTODETECT OFF - CONDITION NOT QT_FEATURE_securetransport AND NOT QT_FEATURE_schannel AND TEST_openssl + CONDITION TEST_openssl ENABLE INPUT_openssl STREQUAL 'linked' ) qt_feature_definition("openssl-linked" "QT_LINKED_OPENSSL") qt_feature("securetransport" PUBLIC LABEL "SecureTransport" - CONDITION APPLE AND ( INPUT_openssl STREQUAL '' OR INPUT_openssl STREQUAL 'no' ) + CONDITION APPLE DISABLE INPUT_ssl STREQUAL 'no' ) qt_feature_definition("securetransport" "QT_SECURETRANSPORT") qt_feature("schannel" PUBLIC LABEL "Schannel" - AUTODETECT OFF - CONDITION WIN32 AND ( INPUT_openssl STREQUAL '' OR INPUT_openssl STREQUAL 'no' ) + CONDITION WIN32 DISABLE INPUT_ssl STREQUAL 'no' ) qt_feature_definition("schannel" "QT_SCHANNEL") diff --git a/src/network/configure.json b/src/network/configure.json index d1ee15d80b..01dd855a1c 100644 --- a/src/network/configure.json +++ b/src/network/configure.json @@ -284,13 +284,13 @@ "autoDetect": "!config.wasm", "enable": "input.openssl == 'yes' || input.openssl == 'runtime'", "disable": "input.openssl == 'no' || input.openssl == 'linked' || input.ssl == 'no'", - "condition": "!features.securetransport && !features.schannel && libs.openssl_headers" + "condition": "libs.openssl_headers" }, "openssl-linked": { "label": " Qt directly linked to OpenSSL", "autoDetect": false, "enable": "input.openssl == 'linked'", - "condition": "!features.securetransport && !features.schannel && libs.openssl", + "condition": "libs.openssl", "output": [ "privateFeature", { "type": "define", "name": "QT_LINKED_OPENSSL" } @@ -299,7 +299,7 @@ "securetransport": { "label": "SecureTransport", "disable": "input.ssl == 'no'", - "condition": "config.darwin && (input.openssl == '' || input.openssl == 'no')", + "condition": "config.darwin", "output": [ "publicFeature", { "type": "define", "name": "QT_SECURETRANSPORT" } @@ -309,7 +309,7 @@ "label": "Schannel", "autoDetect": false, "disable": "input.ssl == 'no'", - "condition": "config.win32 && (input.openssl == '' || input.openssl == 'no')", + "condition": "config.win32", "output": [ "publicFeature", { "type": "define", "name": "QT_SCHANNEL" } diff --git a/src/network/ssl/qasn1element.cpp b/src/network/ssl/qasn1element.cpp deleted file mode 100644 index 3df76c3774..0000000000 --- a/src/network/ssl/qasn1element.cpp +++ /dev/null @@ -1,400 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Jeremy Lainé -** 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$ -** -****************************************************************************/ - - -#include "qasn1element_p.h" - -#include -#include -#include -#include - -#include - -QT_BEGIN_NAMESPACE - -typedef QMap OidNameMap; -static OidNameMap createOidMap() -{ - OidNameMap oids; - // used by unit tests - oids.insert(oids.cend(), QByteArrayLiteral("0.9.2342.19200300.100.1.5"), QByteArrayLiteral("favouriteDrink")); - oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.113549.1.9.1"), QByteArrayLiteral("emailAddress")); - oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.1.1"), QByteArrayLiteral("authorityInfoAccess")); - oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.1"), QByteArrayLiteral("OCSP")); - oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.2"), QByteArrayLiteral("caIssuers")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.14"), QByteArrayLiteral("subjectKeyIdentifier")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.15"), QByteArrayLiteral("keyUsage")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.17"), QByteArrayLiteral("subjectAltName")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.19"), QByteArrayLiteral("basicConstraints")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.35"), QByteArrayLiteral("authorityKeyIdentifier")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.10"), QByteArrayLiteral("O")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.11"), QByteArrayLiteral("OU")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.12"), QByteArrayLiteral("title")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.13"), QByteArrayLiteral("description")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.17"), QByteArrayLiteral("postalCode")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.3"), QByteArrayLiteral("CN")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.4"), QByteArrayLiteral("SN")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.41"), QByteArrayLiteral("name")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.42"), QByteArrayLiteral("GN")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.43"), QByteArrayLiteral("initials")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.46"), QByteArrayLiteral("dnQualifier")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.5"), QByteArrayLiteral("serialNumber")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.6"), QByteArrayLiteral("C")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.7"), QByteArrayLiteral("L")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.8"), QByteArrayLiteral("ST")); - oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.9"), QByteArrayLiteral("street")); - return oids; -} -Q_GLOBAL_STATIC_WITH_ARGS(OidNameMap, oidNameMap, (createOidMap())) - -QAsn1Element::QAsn1Element(quint8 type, const QByteArray &value) - : mType(type) - , mValue(value) -{ -} - -bool QAsn1Element::read(QDataStream &stream) -{ - // type - quint8 tmpType; - stream >> tmpType; - if (!tmpType) - return false; - - // length - quint64 length = 0; - quint8 first; - stream >> first; - if (first & 0x80) { - // long form - const quint8 bytes = (first & 0x7f); - if (bytes > 7) - return false; - - quint8 b; - for (int i = 0; i < bytes; i++) { - stream >> b; - length = (length << 8) | b; - } - } else { - // short form - length = (first & 0x7f); - } - - if (length > quint64(std::numeric_limits::max())) - return false; - - // read value in blocks to avoid being fooled by incorrect length - const int BUFFERSIZE = 4 * 1024; - QByteArray tmpValue; - int remainingLength = length; - while (remainingLength) { - char readBuffer[BUFFERSIZE]; - const int bytesToRead = qMin(remainingLength, BUFFERSIZE); - const int count = stream.readRawData(readBuffer, bytesToRead); - if (count != int(bytesToRead)) - return false; - tmpValue.append(readBuffer, bytesToRead); - remainingLength -= bytesToRead; - } - - mType = tmpType; - mValue.swap(tmpValue); - return true; -} - -bool QAsn1Element::read(const QByteArray &data) -{ - QDataStream stream(data); - return read(stream); -} - -void QAsn1Element::write(QDataStream &stream) const -{ - // type - stream << mType; - - // length - qint64 length = mValue.size(); - if (length >= 128) { - // long form - quint8 encodedLength = 0x80; - QByteArray ba; - while (length) { - ba.prepend(quint8((length & 0xff))); - length >>= 8; - encodedLength += 1; - } - stream << encodedLength; - stream.writeRawData(ba.data(), ba.size()); - } else { - // short form - stream << quint8(length); - } - - // value - stream.writeRawData(mValue.data(), mValue.size()); -} - -QAsn1Element QAsn1Element::fromBool(bool val) -{ - return QAsn1Element(QAsn1Element::BooleanType, - QByteArray(1, val ? 0xff : 0x00)); -} - -QAsn1Element QAsn1Element::fromInteger(unsigned int val) -{ - QAsn1Element elem(QAsn1Element::IntegerType); - while (val > 127) { - elem.mValue.prepend(val & 0xff); - val >>= 8; - } - elem.mValue.prepend(val & 0x7f); - return elem; -} - -QAsn1Element QAsn1Element::fromVector(const QList &items) -{ - QAsn1Element seq; - seq.mType = SequenceType; - QDataStream stream(&seq.mValue, QDataStream::WriteOnly); - for (auto it = items.cbegin(), end = items.cend(); it != end; ++it) - it->write(stream); - return seq; -} - -QAsn1Element QAsn1Element::fromObjectId(const QByteArray &id) -{ - QAsn1Element elem; - elem.mType = ObjectIdentifierType; - const QList bits = id.split('.'); - Q_ASSERT(bits.size() > 2); - elem.mValue += quint8((bits[0].toUInt() * 40 + bits[1].toUInt())); - for (int i = 2; i < bits.size(); ++i) { - char buffer[std::numeric_limits::digits / 7 + 2]; - char *pBuffer = buffer + sizeof(buffer); - *--pBuffer = '\0'; - unsigned int node = bits[i].toUInt(); - *--pBuffer = quint8((node & 0x7f)); - node >>= 7; - while (node) { - *--pBuffer = quint8(((node & 0x7f) | 0x80)); - node >>= 7; - } - elem.mValue += pBuffer; - } - return elem; -} - -bool QAsn1Element::toBool(bool *ok) const -{ - if (*this == fromBool(true)) { - if (ok) - *ok = true; - return true; - } else if (*this == fromBool(false)) { - if (ok) - *ok = true; - return false; - } else { - if (ok) - *ok = false; - return false; - } -} - -QDateTime QAsn1Element::toDateTime() const -{ - QDateTime result; - - if (mValue.size() != 13 && mValue.size() != 15) - return result; - - // QDateTime::fromString is lenient and accepts +- signs in front - // of the year; but ASN.1 doesn't allow them. - const auto isAsciiDigit = [](char c) - { - return c >= '0' && c <= '9'; - }; - - if (!isAsciiDigit(mValue[0])) - return result; - - // Timezone must be present, and UTC - if (mValue.back() != 'Z') - return result; - - // In addition, check that we only have digits representing the - // date/time. This should not really be necessary (there's no such - // thing as negative months/days/etc.); it's a workaround for - // QTBUG-84349. - if (!std::all_of(mValue.begin(), mValue.end() - 1, isAsciiDigit)) - 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)); - - Q_ASSERT(result.date().year() >= 1950); - Q_ASSERT(result.date().year() <= 2049); - } else if (mType == GeneralizedTimeType && mValue.size() == 15) { - result = QDateTime::fromString(QString::fromLatin1(mValue), - QStringLiteral("yyyyMMddHHmmsst")); - } - - return result; -} - -QMultiMap QAsn1Element::toInfo() const -{ - QMultiMap info; - QAsn1Element elem; - QDataStream issuerStream(mValue); - while (elem.read(issuerStream) && elem.mType == QAsn1Element::SetType) { - QAsn1Element issuerElem; - QDataStream setStream(elem.mValue); - if (issuerElem.read(setStream) && issuerElem.mType == QAsn1Element::SequenceType) { - const auto elems = issuerElem.toList(); - if (elems.size() == 2) { - const QByteArray key = elems.front().toObjectName(); - if (!key.isEmpty()) - info.insert(key, elems.back().toString()); - } - } - } - return info; -} - -qint64 QAsn1Element::toInteger(bool *ok) const -{ - if (mType != QAsn1Element::IntegerType || mValue.isEmpty()) { - if (ok) - *ok = false; - return 0; - } - - // NOTE: - negative numbers are not handled - // - greater sizes would overflow - if (mValue.at(0) & 0x80 || mValue.size() > 8) { - if (ok) - *ok = false; - return 0; - } - - qint64 value = mValue.at(0) & 0x7f; - for (int i = 1; i < mValue.size(); ++i) - value = (value << 8) | quint8(mValue.at(i)); - - if (ok) - *ok = true; - return value; -} - -QList QAsn1Element::toList() const -{ - QList items; - if (mType == SequenceType) { - QAsn1Element elem; - QDataStream stream(mValue); - while (elem.read(stream)) - items << elem; - } - return items; -} - -QByteArray QAsn1Element::toObjectId() const -{ - QByteArray key; - if (mType == ObjectIdentifierType && !mValue.isEmpty()) { - quint8 b = mValue.at(0); - key += QByteArray::number(b / 40) + '.' + QByteArray::number (b % 40); - unsigned int val = 0; - for (int i = 1; i < mValue.size(); ++i) { - b = mValue.at(i); - val = (val << 7) | (b & 0x7f); - if (!(b & 0x80)) { - key += '.' + QByteArray::number(val); - val = 0; - } - } - } - return key; -} - -QByteArray QAsn1Element::toObjectName() const -{ - QByteArray key = toObjectId(); - return oidNameMap->value(key, key); -} - -QString QAsn1Element::toString() const -{ - // Detect embedded NULs and reject - if (qstrlen(mValue) < uint(mValue.size())) - return QString(); - - if (mType == PrintableStringType || mType == TeletexStringType - || mType == Rfc822NameType || mType == DnsNameType - || mType == UniformResourceIdentifierType) - return QString::fromLatin1(mValue, mValue.size()); - if (mType == Utf8StringType) - return QString::fromUtf8(mValue, mValue.size()); - - return QString(); -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qasn1element_p.h b/src/network/ssl/qasn1element_p.h deleted file mode 100644 index 48fe45c9a5..0000000000 --- a/src/network/ssl/qasn1element_p.h +++ /dev/null @@ -1,188 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Jeremy Lainé -** 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$ -** -****************************************************************************/ - - -#ifndef QASN1ELEMENT_P_H -#define QASN1ELEMENT_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -// General -#define RSADSI_OID "1.2.840.113549." - -#define RSA_ENCRYPTION_OID QByteArrayLiteral(RSADSI_OID "1.1.1") -#define DSA_ENCRYPTION_OID QByteArrayLiteral("1.2.840.10040.4.1") -#define EC_ENCRYPTION_OID QByteArrayLiteral("1.2.840.10045.2.1") -#define DH_ENCRYPTION_OID QByteArrayLiteral(RSADSI_OID "1.3.1") - -// These are mostly from the RFC for PKCS#5 -// PKCS#5: https://tools.ietf.org/html/rfc8018#appendix-B -#define PKCS5_OID RSADSI_OID "1.5." -// PKCS#12: https://tools.ietf.org/html/rfc7292#appendix-D) -#define PKCS12_OID RSADSI_OID "1.12." - -// -PBES1 -#define PKCS5_MD2_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "1") // Not (yet) implemented -#define PKCS5_MD2_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "4") // Not (yet) implemented -#define PKCS5_MD5_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "3") -#define PKCS5_MD5_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "6") -#define PKCS5_SHA1_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "10") -#define PKCS5_SHA1_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "11") -#define PKCS12_SHA1_RC4_128_OID QByteArrayLiteral(PKCS12_OID "1.1") // Not (yet) implemented -#define PKCS12_SHA1_RC4_40_OID QByteArrayLiteral(PKCS12_OID "1.2") // Not (yet) implemented -#define PKCS12_SHA1_3KEY_3DES_CBC_OID QByteArrayLiteral(PKCS12_OID "1.3") -#define PKCS12_SHA1_2KEY_3DES_CBC_OID QByteArrayLiteral(PKCS12_OID "1.4") -#define PKCS12_SHA1_RC2_128_CBC_OID QByteArrayLiteral(PKCS12_OID "1.5") -#define PKCS12_SHA1_RC2_40_CBC_OID QByteArrayLiteral(PKCS12_OID "1.6") - -// -PBKDF2 -#define PKCS5_PBKDF2_ENCRYPTION_OID QByteArrayLiteral(PKCS5_OID "12") - -// -PBES2 -#define PKCS5_PBES2_ENCRYPTION_OID QByteArrayLiteral(PKCS5_OID "13") - -// Digest -#define DIGEST_ALGORITHM_OID RSADSI_OID "2." -// -HMAC-SHA-1 -#define HMAC_WITH_SHA1 QByteArrayLiteral(DIGEST_ALGORITHM_OID "7") -// -HMAC-SHA-2 -#define HMAC_WITH_SHA224 QByteArrayLiteral(DIGEST_ALGORITHM_OID "8") -#define HMAC_WITH_SHA256 QByteArrayLiteral(DIGEST_ALGORITHM_OID "9") -#define HMAC_WITH_SHA384 QByteArrayLiteral(DIGEST_ALGORITHM_OID "10") -#define HMAC_WITH_SHA512 QByteArrayLiteral(DIGEST_ALGORITHM_OID "11") -#define HMAC_WITH_SHA512_224 QByteArrayLiteral(DIGEST_ALGORITHM_OID "12") -#define HMAC_WITH_SHA512_256 QByteArrayLiteral(DIGEST_ALGORITHM_OID "13") - -// Encryption algorithms -#define ENCRYPTION_ALGORITHM_OID RSADSI_OID "3." -#define DES_CBC_ENCRYPTION_OID QByteArrayLiteral("1.3.14.3.2.7") -#define DES_EDE3_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "7") -#define RC2_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "2") -#define RC5_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "9") // Not (yet) implemented -#define AES_OID "2.16.840.1.101.3.4.1." -#define AES128_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "2") -#define AES192_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "22") // Not (yet) implemented -#define AES256_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "42") // Not (yet) implemented - -class Q_AUTOTEST_EXPORT QAsn1Element -{ -public: - enum ElementType { - // universal - BooleanType = 0x01, - IntegerType = 0x02, - BitStringType = 0x03, - OctetStringType = 0x04, - NullType = 0x05, - ObjectIdentifierType = 0x06, - Utf8StringType = 0x0c, - PrintableStringType = 0x13, - TeletexStringType = 0x14, - UtcTimeType = 0x17, - GeneralizedTimeType = 0x18, - SequenceType = 0x30, - SetType = 0x31, - - // GeneralNameTypes - Rfc822NameType = 0x81, - DnsNameType = 0x82, - UniformResourceIdentifierType = 0x86, - IpAddressType = 0x87, - - // context specific - Context0Type = 0xA0, - Context1Type = 0xA1, - Context3Type = 0xA3 - }; - - explicit QAsn1Element(quint8 type = 0, const QByteArray &value = QByteArray()); - bool read(QDataStream &data); - bool read(const QByteArray &data); - void write(QDataStream &data) const; - - static QAsn1Element fromBool(bool val); - static QAsn1Element fromInteger(unsigned int val); - static QAsn1Element fromVector(const QList &items); - static QAsn1Element fromObjectId(const QByteArray &id); - - bool toBool(bool *ok = nullptr) const; - QDateTime toDateTime() const; - QMultiMap toInfo() const; - qint64 toInteger(bool *ok = nullptr) const; - QList toList() const; - QByteArray toObjectId() const; - QByteArray toObjectName() const; - QString toString() const; - - quint8 type() const { return mType; } - QByteArray value() const { return mValue; } - - friend inline bool operator==(const QAsn1Element &, const QAsn1Element &); - friend inline bool operator!=(const QAsn1Element &, const QAsn1Element &); - -private: - quint8 mType; - QByteArray mValue; -}; -Q_DECLARE_TYPEINFO(QAsn1Element, Q_RELOCATABLE_TYPE); - -inline bool operator==(const QAsn1Element &e1, const QAsn1Element &e2) -{ return e1.mType == e2.mType && e1.mValue == e2.mValue; } - -inline bool operator!=(const QAsn1Element &e1, const QAsn1Element &e2) -{ return e1.mType != e2.mType || e1.mValue != e2.mValue; } - -QT_END_NAMESPACE - -#endif diff --git a/src/network/ssl/qdtls_base.cpp b/src/network/ssl/qdtls_base.cpp deleted file mode 100644 index 6a5979eb9e..0000000000 --- a/src/network/ssl/qdtls_base.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qdtls_base_p.h" - -QT_BEGIN_NAMESPACE - -void QDtlsBasePrivate::setDtlsError(QDtlsError code, const QString &description) -{ - errorCode = code; - errorDescription = description; -} - -QDtlsError QDtlsBasePrivate::error() const -{ - return errorCode; -} - -QString QDtlsBasePrivate::errorString() const -{ - return errorDescription; -} - -void QDtlsBasePrivate::clearDtlsError() -{ - errorCode = QDtlsError::NoError; - errorDescription.clear(); -} - -QSslConfiguration QDtlsBasePrivate::configuration() const -{ - return dtlsConfiguration; -} - -void QDtlsBasePrivate::setConfiguration(const QSslConfiguration &configuration) -{ - dtlsConfiguration = configuration; - clearDtlsError(); -} - -bool QDtlsBasePrivate::setCookieGeneratorParameters(const GenParams ¶ms) -{ - if (!params.secret.size()) { - setDtlsError(QDtlsError::InvalidInputParameters, - QDtls::tr("Invalid (empty) secret")); - return false; - } - - clearDtlsError(); - - hashAlgorithm = params.hash; - secret = params.secret; - - return true; -} - -QDtlsClientVerifier::GeneratorParameters -QDtlsBasePrivate::cookieGeneratorParameters() const -{ - return {hashAlgorithm, secret}; -} - -bool QDtlsBasePrivate::isDtlsProtocol(QSsl::SslProtocol protocol) -{ - switch (protocol) { - case QSsl::DtlsV1_0: - case QSsl::DtlsV1_0OrLater: - case QSsl::DtlsV1_2: - case QSsl::DtlsV1_2OrLater: - return true; - default: - return false; - } -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qdtls_base_p.h b/src/network/ssl/qdtls_base_p.h deleted file mode 100644 index df35f514a2..0000000000 --- a/src/network/ssl/qdtls_base_p.h +++ /dev/null @@ -1,115 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QDTLS_BASE_P_H -#define QDTLS_BASE_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -QT_REQUIRE_CONFIG(dtls); - -#include "qsslconfiguration.h" -#include "qtlsbackend_p.h" -#include "qsslcipher.h" -#include "qsslsocket.h" -#include "qssl.h" - -#include - -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -// This class exists to re-implement the shared error/cookie handling -// for both QDtls and QDtlsClientVerifier classes. Use it if/when -// you need it. Backend neutral. -class QDtlsBasePrivate : virtual public QTlsPrivate::DtlsBase -{ -public: - QDtlsBasePrivate(QSslSocket::SslMode m, const QByteArray &s) : mode(m), secret(s) {} - void setDtlsError(QDtlsError code, const QString &description) override; - QDtlsError error() const override; - QString errorString() const override; - void clearDtlsError() override; - - void setConfiguration(const QSslConfiguration &configuration) override; - QSslConfiguration configuration() const override; - - bool setCookieGeneratorParameters(const GenParams &) override; - GenParams cookieGeneratorParameters() const override; - - static bool isDtlsProtocol(QSsl::SslProtocol protocol); - - QHostAddress remoteAddress; - quint16 remotePort = 0; - quint16 mtuHint = 0; - - QDtlsError errorCode = QDtlsError::NoError; - QString errorDescription; - QSslConfiguration dtlsConfiguration; - QSslSocket::SslMode mode = QSslSocket::SslClientMode; - QSslCipher sessionCipher; - QSsl::SslProtocol sessionProtocol = QSsl::UnknownProtocol; - QString peerVfyName; - QByteArray secret; - -#ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 - QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; -#else - QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha256; -#endif -}; - -QT_END_NAMESPACE - -#endif // QDTLS_BASE_P_H diff --git a/src/network/ssl/qdtls_openssl.cpp b/src/network/ssl/qdtls_openssl.cpp deleted file mode 100644 index fe6c8013c7..0000000000 --- a/src/network/ssl/qdtls_openssl.cpp +++ /dev/null @@ -1,1451 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX -#include "private/qnativesocketengine_p.h" - -#include "qsslpresharedkeyauthenticator_p.h" -#include "qsslsocket_openssl_symbols_p.h" -#include "qsslcertificate_p.h" -#include "qdtls_openssl_p.h" -#include "qx509_openssl_p.h" -#include "qudpsocket.h" -#include "qssl_p.h" - -#include "qmessageauthenticationcode.h" -#include "qcryptographichash.h" - -#include "qdebug.h" - -#include -#include - -QT_BEGIN_NAMESPACE - -#define QT_DTLS_VERBOSE 0 - -#if QT_DTLS_VERBOSE - -#define qDtlsWarning(arg) qWarning(arg) -#define qDtlsDebug(arg) qDebug(arg) - -#else - -#define qDtlsWarning(arg) -#define qDtlsDebug(arg) - -#endif // QT_DTLS_VERBOSE - -namespace dtlsutil -{ - -QByteArray cookie_for_peer(SSL *ssl) -{ - Q_ASSERT(ssl); - - // SSL_get_rbio does not increment the reference count - BIO *readBIO = q_SSL_get_rbio(ssl); - if (!readBIO) { - qCWarning(lcSsl, "No BIO (dgram) found in SSL object"); - return {}; - } - - auto listener = static_cast(q_BIO_get_app_data(readBIO)); - if (!listener) { - qCWarning(lcSsl, "BIO_get_app_data returned invalid (nullptr) value"); - return {}; - } - - const QHostAddress peerAddress(listener->remoteAddress); - const quint16 peerPort(listener->remotePort); - QByteArray peerData; - if (peerAddress.protocol() == QAbstractSocket::IPv6Protocol) { - const Q_IPV6ADDR sin6_addr(peerAddress.toIPv6Address()); - peerData.resize(int(sizeof sin6_addr + sizeof peerPort)); - char *dst = peerData.data(); - std::memcpy(dst, &peerPort, sizeof peerPort); - dst += sizeof peerPort; - std::memcpy(dst, &sin6_addr, sizeof sin6_addr); - } else if (peerAddress.protocol() == QAbstractSocket::IPv4Protocol) { - const quint32 sin_addr(peerAddress.toIPv4Address()); - peerData.resize(int(sizeof sin_addr + sizeof peerPort)); - char *dst = peerData.data(); - std::memcpy(dst, &peerPort, sizeof peerPort); - dst += sizeof peerPort; - std::memcpy(dst, &sin_addr, sizeof sin_addr); - } else { - Q_UNREACHABLE(); - } - - return peerData; -} - -struct FallbackCookieSecret -{ - FallbackCookieSecret() - { - key.resize(32); - const int status = q_RAND_bytes(reinterpret_cast(key.data()), - key.size()); - if (status <= 0) - key.clear(); - } - - QByteArray key; - - Q_DISABLE_COPY_MOVE(FallbackCookieSecret) -}; - -QByteArray fallbackSecret() -{ - static const FallbackCookieSecret generator; - return generator.key; -} - -int next_timeoutMs(SSL *tlsConnection) -{ - Q_ASSERT(tlsConnection); - timeval timeLeft = {}; - q_DTLSv1_get_timeout(tlsConnection, &timeLeft); - return timeLeft.tv_sec * 1000; -} - - -void delete_connection(SSL *ssl) -{ - // The 'deleter' for QSharedPointer. - if (ssl) - q_SSL_free(ssl); -} - -void delete_BIO_ADDR(BIO_ADDR *bio) -{ - // A deleter for QSharedPointer - if (bio) - q_BIO_ADDR_free(bio); -} - -void delete_bio_method(BIO_METHOD *method) -{ - // The 'deleter' for QSharedPointer. - if (method) - q_BIO_meth_free(method); -} - -// The path MTU discovery is non-trivial: it's a mix of getsockopt/setsockopt -// (IP_MTU/IP6_MTU/IP_MTU_DISCOVER) and fallback MTU values. It's not -// supported on all platforms, worse so - imposes specific requirements on -// underlying UDP socket etc. So for now, we either try a user-proposed MTU -// hint or rely on our own fallback value. As a fallback mtu OpenSSL uses 576 -// for IPv4 and 1280 for IPv6 (RFC 791, RFC 2460). To KIS we use 576. This -// rather small MTU value does not affect the size that can be read/written -// by QDtls, only a handshake (which is allowed to fragment). -enum class MtuGuess : long -{ - defaultMtu = 576 -}; - -} // namespace dtlsutil - -namespace dtlscallbacks -{ - -extern "C" int q_generate_cookie_callback(SSL *ssl, unsigned char *dst, - unsigned *cookieLength) -{ - if (!ssl || !dst || !cookieLength) { - qCWarning(lcSsl, - "Failed to generate cookie - invalid (nullptr) parameter(s)"); - return 0; - } - - void *generic = q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData); - if (!generic) { - qCWarning(lcSsl, "SSL_get_ex_data returned nullptr, cannot generate cookie"); - return 0; - } - - *cookieLength = 0; - - auto dtls = static_cast(generic); - if (!dtls->secret.size()) - return 0; - - const QByteArray peerData(dtlsutil::cookie_for_peer(ssl)); - if (!peerData.size()) - return 0; - - QMessageAuthenticationCode hmac(dtls->hashAlgorithm, dtls->secret); - hmac.addData(peerData); - const QByteArray cookie = hmac.result(); - Q_ASSERT(cookie.size() >= 0); - // DTLS1_COOKIE_LENGTH is erroneously 256 bytes long, must be 255 - RFC 6347, 4.2.1. - *cookieLength = qMin(DTLS1_COOKIE_LENGTH - 1, cookie.size()); - std::memcpy(dst, cookie.constData(), *cookieLength); - - return 1; -} - -extern "C" int q_verify_cookie_callback(SSL *ssl, const unsigned char *cookie, - unsigned cookieLength) -{ - if (!ssl || !cookie || !cookieLength) { - qCWarning(lcSsl, "Could not verify cookie, invalid (nullptr or zero) parameters"); - return 0; - } - - unsigned char newCookie[DTLS1_COOKIE_LENGTH] = {}; - unsigned newCookieLength = 0; - if (q_generate_cookie_callback(ssl, newCookie, &newCookieLength) != 1) - return 0; - - return newCookieLength == cookieLength - && !std::memcmp(cookie, newCookie, cookieLength); -} - -extern "C" int q_X509DtlsCallback(int ok, X509_STORE_CTX *ctx) -{ - if (!ok) { - // Store the error and at which depth the error was detected. - SSL *ssl = static_cast(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx())); - if (!ssl) { - qCWarning(lcSsl, "X509_STORE_CTX_get_ex_data returned nullptr, handshake failure"); - return 0; - } - - void *generic = q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData); - if (!generic) { - qCWarning(lcSsl, "SSL_get_ex_data returned nullptr, handshake failure"); - return 0; - } - - auto dtls = static_cast(generic); - dtls->x509Errors.append(QTlsPrivate::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); - } - - // Always return 1 (OK) to allow verification to continue. We handle the - // errors gracefully after collecting all errors, after verification has - // completed. - return 1; -} - -extern "C" unsigned q_PSK_client_callback(SSL *ssl, const char *hint, char *identity, - unsigned max_identity_len, unsigned char *psk, - unsigned max_psk_len) -{ - auto *dtls = static_cast(q_SSL_get_ex_data(ssl, - QTlsBackendOpenSSL::s_indexForSSLExtraData)); - if (!dtls) - return 0; - - Q_ASSERT(dtls->dtlsPrivate); - return dtls->dtlsPrivate->pskClientCallback(hint, identity, max_identity_len, psk, max_psk_len); -} - -extern "C" unsigned q_PSK_server_callback(SSL *ssl, const char *identity, unsigned char *psk, - unsigned max_psk_len) -{ - auto *dtls = static_cast(q_SSL_get_ex_data(ssl, - QTlsBackendOpenSSL::s_indexForSSLExtraData)); - if (!dtls) - return 0; - - Q_ASSERT(dtls->dtlsPrivate); - return dtls->dtlsPrivate->pskServerCallback(identity, psk, max_psk_len); -} - -} // namespace dtlscallbacks - -namespace dtlsbio -{ - -extern "C" int q_dgram_read(BIO *bio, char *dst, int bytesToRead) -{ - if (!bio || !dst || bytesToRead <= 0) { - qCWarning(lcSsl, "invalid input parameter(s)"); - return 0; - } - - q_BIO_clear_retry_flags(bio); - - auto dtls = static_cast(q_BIO_get_app_data(bio)); - // It's us who set data, if OpenSSL does too, the logic here is wrong - // then and we have to use BIO_set_app_data then! - Q_ASSERT(dtls); - int bytesRead = 0; - if (dtls->dgram.size()) { - bytesRead = qMin(dtls->dgram.size(), bytesToRead); - std::memcpy(dst, dtls->dgram.constData(), bytesRead); - - if (!dtls->peeking) - dtls->dgram = dtls->dgram.mid(bytesRead); - } else { - bytesRead = -1; - } - - if (bytesRead <= 0) - q_BIO_set_retry_read(bio); - - return bytesRead; -} - -extern "C" int q_dgram_write(BIO *bio, const char *src, int bytesToWrite) -{ - if (!bio || !src || bytesToWrite <= 0) { - qCWarning(lcSsl, "invalid input parameter(s)"); - return 0; - } - - q_BIO_clear_retry_flags(bio); - - auto dtls = static_cast(q_BIO_get_app_data(bio)); - Q_ASSERT(dtls); - if (dtls->writeSuppressed) { - // See the comment in QDtls::startHandshake. - return bytesToWrite; - } - - QUdpSocket *udpSocket = dtls->udpSocket; - Q_ASSERT(udpSocket); - - const QByteArray dgram(QByteArray::fromRawData(src, bytesToWrite)); - qint64 bytesWritten = -1; - if (udpSocket->state() == QAbstractSocket::ConnectedState) { - bytesWritten = udpSocket->write(dgram); - } else { - bytesWritten = udpSocket->writeDatagram(dgram, dtls->remoteAddress, - dtls->remotePort); - } - - if (bytesWritten <= 0) - q_BIO_set_retry_write(bio); - - Q_ASSERT(bytesWritten <= std::numeric_limits::max()); - return int(bytesWritten); -} - -extern "C" int q_dgram_puts(BIO *bio, const char *src) -{ - if (!bio || !src) { - qCWarning(lcSsl, "invalid input parameter(s)"); - return 0; - } - - return q_dgram_write(bio, src, int(std::strlen(src))); -} - -extern "C" long q_dgram_ctrl(BIO *bio, int cmd, long num, void *ptr) -{ - // This is our custom BIO_ctrl. bio.h defines a lot of BIO_CTRL_* - // and BIO_* constants and BIO_somename macros that expands to BIO_ctrl - // call with one of those constants as argument. What exactly BIO_ctrl - // does - depends on the 'cmd' and the type of BIO (so BIO_ctrl does - // not even have a single well-defined value meaning success or failure). - // We handle only the most generic commands - the ones documented for - // BIO_ctrl - and also DGRAM specific ones. And even for them - in most - // cases we do nothing but report a success or some non-error value. - // Documents also state: "Source/sink BIOs return an 0 if they do not - // recognize the BIO_ctrl() operation." - these are covered by 'default' - // label in the switch-statement below. Debug messages in the switch mean: - // 1) we got a command that is unexpected for dgram BIO, or: - // 2) we do not call any function that would lead to OpenSSL using this - // command. - - if (!bio) { - qDebug(lcSsl, "invalid 'bio' parameter (nullptr)"); - return -1; - } - - auto dtls = static_cast(q_BIO_get_app_data(bio)); - Q_ASSERT(dtls); - - switch (cmd) { - // Let's start from the most generic ones, in the order in which they are - // documented (as BIO_ctrl): - case BIO_CTRL_RESET: - // BIO_reset macro. - // From documentation: - // "BIO_reset() normally returns 1 for success and 0 or -1 for failure. - // File BIOs are an exception, they return 0 for success and -1 for - // failure." - // We have nothing to reset and we are not file BIO. - return 1; - case BIO_C_FILE_SEEK: - case BIO_C_FILE_TELL: - qDtlsWarning("Unexpected cmd (BIO_C_FILE_SEEK/BIO_C_FILE_TELL)"); - // These are for BIO_seek, BIO_tell. We are not a file BIO. - // Non-negative return value means success. - return 0; - case BIO_CTRL_FLUSH: - // BIO_flush, nothing to do, we do not buffer any data. - // 0 or -1 means error, 1 - success. - return 1; - case BIO_CTRL_EOF: - qDtlsWarning("Unexpected cmd (BIO_CTRL_EOF)"); - // BIO_eof, 1 means EOF read. Makes no sense for us. - return 0; - case BIO_CTRL_SET_CLOSE: - // BIO_set_close with BIO_CLOSE/BIO_NOCLOSE flags. Documented as - // always returning 1. - // From the documentation: - // "Typically BIO_CLOSE is used in a source/sink BIO to indicate that - // the underlying I/O stream should be closed when the BIO is freed." - // - // QUdpSocket we work with is not BIO's business, ignoring. - return 1; - case BIO_CTRL_GET_CLOSE: - // BIO_get_close. No, never, see the comment above. - return 0; - case BIO_CTRL_PENDING: - qDtlsWarning("Unexpected cmd (BIO_CTRL_PENDING)"); - // BIO_pending. Not used by DTLS/OpenSSL (we are not buffering). - return 0; - case BIO_CTRL_WPENDING: - // No, we have nothing buffered. - return 0; - // The constants below are not documented as a part BIO_ctrl documentation, - // but they are also not type-specific. - case BIO_CTRL_DUP: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DUP)"); - // BIO_dup_state, not used by DTLS (and socket-related BIOs in general). - // For some very specific BIO type this 'cmd' would copy some state - // from 'bio' to (BIO*)'ptr'. 1 means success. - return 0; - case BIO_CTRL_SET_CALLBACK: - qDtlsWarning("Unexpected cmd (BIO_CTRL_SET_CALLBACK)"); - // BIO_set_info_callback. We never call this, OpenSSL does not do this - // on its own (normally it's used if client code wants to have some - // debug information, for example, dumping handshake state via - // BIO_printf from SSL info_callback). - return 0; - case BIO_CTRL_GET_CALLBACK: - qDtlsWarning("Unexpected cmd (BIO_CTRL_GET_CALLBACK)"); - // BIO_get_info_callback. We never call this. - if (ptr) - *static_cast(ptr) = nullptr; - return 0; - case BIO_CTRL_SET: - case BIO_CTRL_GET: - qDtlsWarning("Unexpected cmd (BIO_CTRL_SET/BIO_CTRL_GET)"); - // Somewhat 'documented' as setting/getting IO type. Not used anywhere - // except BIO_buffer_get_num_lines (which contradics 'get IO type'). - // Ignoring. - return 0; - // DGRAM-specific operation, we have to return some reasonable value - // (so far, I've encountered only peek mode switching, connect). - case BIO_CTRL_DGRAM_CONNECT: - // BIO_ctrl_dgram_connect. Not needed. Our 'dtls' already knows - // the peer's address/port. Report success though. - return 1; - case BIO_CTRL_DGRAM_SET_CONNECTED: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_CONNECTED)"); - // BIO_ctrl_dgram_set_connected. We never call it, OpenSSL does - // not call it on its own (so normally it's done by client code). - // Similar to BIO_CTRL_DGRAM_CONNECT, but it also informs the BIO - // that its UDP socket is connected. We never need it though. - return -1; - case BIO_CTRL_DGRAM_SET_RECV_TIMEOUT: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_RECV_TIMEOUT)"); - // Essentially setsockopt with SO_RCVTIMEO, not needed, our sockets - // are non-blocking. - return -1; - case BIO_CTRL_DGRAM_GET_RECV_TIMEOUT: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_GET_RECV_TIMEOUT)"); - // getsockopt with SO_RCVTIMEO, not needed, our sockets are - // non-blocking. ptr is timeval *. - return -1; - case BIO_CTRL_DGRAM_SET_SEND_TIMEOUT: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_SEND_TIMEOUT)"); - // setsockopt, SO_SNDTIMEO, cannot happen. - return -1; - case BIO_CTRL_DGRAM_GET_SEND_TIMEOUT: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_GET_SEND_TIMEOUT)"); - // getsockopt, SO_SNDTIMEO, cannot happen. - return -1; - case BIO_CTRL_DGRAM_GET_RECV_TIMER_EXP: - // BIO_dgram_recv_timedout. No, we are non-blocking. - return 0; - case BIO_CTRL_DGRAM_GET_SEND_TIMER_EXP: - // BIO_dgram_send_timedout. No, we are non-blocking. - return 0; - case BIO_CTRL_DGRAM_MTU_DISCOVER: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_MTU_DISCOVER)"); - // setsockopt, IP_MTU_DISCOVER/IP6_MTU_DISCOVER, to be done - // in QUdpSocket instead. OpenSSL never calls it, only client - // code. - return 1; - case BIO_CTRL_DGRAM_QUERY_MTU: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_QUERY_MTU)"); - // To be done in QUdpSocket instead. - return 1; - case BIO_CTRL_DGRAM_GET_FALLBACK_MTU: - qDtlsWarning("Unexpected command *BIO_CTRL_DGRAM_GET_FALLBACK_MTU)"); - // Without SSL_OP_NO_QUERY_MTU set on SSL, OpenSSL can request for - // fallback MTU after several re-transmissions. - // Should never happen in our case. - return long(dtlsutil::MtuGuess::defaultMtu); - case BIO_CTRL_DGRAM_GET_MTU: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_GET_MTU)"); - return -1; - case BIO_CTRL_DGRAM_SET_MTU: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_MTU)"); - // Should not happen (we don't call BIO_ctrl with this parameter) - // and set MTU on SSL instead. - return -1; // num is mtu and it's a return value meaning success. - case BIO_CTRL_DGRAM_MTU_EXCEEDED: - qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_MTU_EXCEEDED)"); - return 0; - case BIO_CTRL_DGRAM_GET_PEER: - qDtlsDebug("BIO_CTRL_DGRAM_GET_PEER"); - // BIO_dgram_get_peer. We do not return a real address (DTLS is not - // using this address), but let's pretend a success. - switch (dtls->remoteAddress.protocol()) { - case QAbstractSocket::IPv6Protocol: - return sizeof(sockaddr_in6); - case QAbstractSocket::IPv4Protocol: - return sizeof(sockaddr_in); - default: - return -1; - } - case BIO_CTRL_DGRAM_SET_PEER: - // Similar to BIO_CTRL_DGRAM_CONNECTED. - return 1; - case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT: - // DTLSTODO: I'm not sure yet, how it's used by OpenSSL. - return 1; - case BIO_CTRL_DGRAM_SET_DONT_FRAG: - qDtlsDebug("BIO_CTRL_DGRAM_SET_DONT_FRAG"); - // To be done in QUdpSocket, it's about IP_DONTFRAG etc. - return 1; - case BIO_CTRL_DGRAM_GET_MTU_OVERHEAD: - // AFAIK it's 28 for IPv4 and 48 for IPv6, but let's pretend it's 0 - // so that OpenSSL does not start suddenly fragmenting the first - // client hello (which will result in DTLSv1_listen rejecting it). - return 0; - case BIO_CTRL_DGRAM_SET_PEEK_MODE: - dtls->peeking = num; - return 1; - default:; -#if QT_DTLS_VERBOSE - qWarning() << "Unexpected cmd (" << cmd << ")"; -#endif - } - - return 0; -} - -extern "C" int q_dgram_create(BIO *bio) -{ - - q_BIO_set_init(bio, 1); - // With a custom BIO you'd normally allocate some implementation-specific - // data and append it to this new BIO using BIO_set_data. We don't need - // it and thus q_dgram_destroy below is a noop. - return 1; -} - -extern "C" int q_dgram_destroy(BIO *bio) -{ - Q_UNUSED(bio); - return 1; -} - -const char * const qdtlsMethodName = "qdtlsbio"; - -} // namespace dtlsbio - -namespace dtlsopenssl -{ - -bool DtlsState::init(QDtlsBasePrivate *dtlsBase, QUdpSocket *socket, - const QHostAddress &remote, quint16 port, - const QByteArray &receivedMessage) -{ - Q_ASSERT(dtlsBase); - Q_ASSERT(socket); - - if (!tlsContext.data() && !initTls(dtlsBase)) - return false; - - udpSocket = socket; - - setLinkMtu(dtlsBase); - - dgram = receivedMessage; - remoteAddress = remote; - remotePort = port; - - // SSL_get_rbio does not increment a reference count. - BIO *bio = q_SSL_get_rbio(tlsConnection.data()); - Q_ASSERT(bio); - q_BIO_set_app_data(bio, this); - - return true; -} - -void DtlsState::reset() -{ - tlsConnection.reset(); - tlsContext.reset(); -} - -bool DtlsState::initTls(QDtlsBasePrivate *dtlsBase) -{ - if (tlsContext.data()) - return true; - - if (!QSslSocket::supportsSsl()) - return false; - - if (!initCtxAndConnection(dtlsBase)) - return false; - - if (!initBIO(dtlsBase)) { - tlsConnection.reset(); - tlsContext.reset(); - return false; - } - - return true; -} - -static QString msgFunctionFailed(const char *function) -{ - //: %1: Some function - return QDtls::tr("%1 failed").arg(QLatin1String(function)); -} - -bool DtlsState::initCtxAndConnection(QDtlsBasePrivate *dtlsBase) -{ - Q_ASSERT(dtlsBase); - Q_ASSERT(QSslSocket::supportsSsl()); - - if (dtlsBase->mode == QSslSocket::UnencryptedMode) { - dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, - QDtls::tr("Invalid SslMode, SslServerMode or SslClientMode expected")); - return false; - } - - if (!QDtlsBasePrivate::isDtlsProtocol(dtlsBase->dtlsConfiguration.protocol())) { - dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, - QDtls::tr("Invalid protocol version, DTLS protocol expected")); - return false; - } - - const bool rootsOnDemand = QTlsBackend::rootLoadingOnDemandAllowed(dtlsBase->dtlsConfiguration); - TlsContext newContext(QSslContext::sharedFromConfiguration(dtlsBase->mode, dtlsBase->dtlsConfiguration, - rootsOnDemand)); - - if (newContext->error() != QSslError::NoError) { - dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, newContext->errorString()); - return false; - } - - TlsConnection newConnection(newContext->createSsl(), dtlsutil::delete_connection); - if (!newConnection.data()) { - dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, - msgFunctionFailed("SSL_new")); - return false; - } - - const int set = q_SSL_set_ex_data(newConnection.data(), - QTlsBackendOpenSSL::s_indexForSSLExtraData, - this); - - if (set != 1 && dtlsBase->dtlsConfiguration.peerVerifyMode() != QSslSocket::VerifyNone) { - dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, - msgFunctionFailed("SSL_set_ex_data")); - return false; - } - - if (dtlsBase->mode == QSslSocket::SslServerMode) { - if (dtlsBase->dtlsConfiguration.dtlsCookieVerificationEnabled()) - q_SSL_set_options(newConnection.data(), SSL_OP_COOKIE_EXCHANGE); - q_SSL_set_psk_server_callback(newConnection.data(), dtlscallbacks::q_PSK_server_callback); - } else { - q_SSL_set_psk_client_callback(newConnection.data(), dtlscallbacks::q_PSK_client_callback); - } - - tlsContext.swap(newContext); - tlsConnection.swap(newConnection); - - return true; -} - -bool DtlsState::initBIO(QDtlsBasePrivate *dtlsBase) -{ - Q_ASSERT(dtlsBase); - Q_ASSERT(tlsContext.data() && tlsConnection.data()); - - BioMethod customMethod(q_BIO_meth_new(BIO_TYPE_DGRAM, dtlsbio::qdtlsMethodName), - dtlsutil::delete_bio_method); - if (!customMethod.data()) { - dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, - msgFunctionFailed("BIO_meth_new")); - return false; - } - - BIO_METHOD *biom = customMethod.data(); - q_BIO_meth_set_create(biom, dtlsbio::q_dgram_create); - q_BIO_meth_set_destroy(biom, dtlsbio::q_dgram_destroy); - q_BIO_meth_set_read(biom, dtlsbio::q_dgram_read); - q_BIO_meth_set_write(biom, dtlsbio::q_dgram_write); - q_BIO_meth_set_puts(biom, dtlsbio::q_dgram_puts); - q_BIO_meth_set_ctrl(biom, dtlsbio::q_dgram_ctrl); - - BIO *bio = q_BIO_new(biom); - if (!bio) { - dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, - msgFunctionFailed("BIO_new")); - return false; - } - - q_SSL_set_bio(tlsConnection.data(), bio, bio); - - bioMethod.swap(customMethod); - - return true; -} - -void DtlsState::setLinkMtu(QDtlsBasePrivate *dtlsBase) -{ - Q_ASSERT(dtlsBase); - Q_ASSERT(udpSocket); - Q_ASSERT(tlsConnection.data()); - - long mtu = dtlsBase->mtuHint; - if (!mtu) { - // If the underlying QUdpSocket was connected, getsockopt with - // IP_MTU/IP6_MTU can give us some hint: - bool optionFound = false; - if (udpSocket->state() == QAbstractSocket::ConnectedState) { - const QVariant val(udpSocket->socketOption(QAbstractSocket::PathMtuSocketOption)); - if (val.isValid() && val.canConvert()) - mtu = val.toInt(&optionFound); - } - - if (!optionFound || mtu <= 0) { - // OK, our own initial guess. - mtu = long(dtlsutil::MtuGuess::defaultMtu); - } - } - - // For now, we disable this option. - q_SSL_set_options(tlsConnection.data(), SSL_OP_NO_QUERY_MTU); - - q_DTLS_set_link_mtu(tlsConnection.data(), mtu); -} - -} // namespace dtlsopenssl - -QDtlsClientVerifierOpenSSL::QDtlsClientVerifierOpenSSL() - : QDtlsBasePrivate(QSslSocket::SslServerMode, dtlsutil::fallbackSecret()) -{ -} - -bool QDtlsClientVerifierOpenSSL::verifyClient(QUdpSocket *socket, const QByteArray &dgram, - const QHostAddress &address, quint16 port) -{ - Q_ASSERT(socket); - Q_ASSERT(dgram.size()); - Q_ASSERT(!address.isNull()); - Q_ASSERT(port); - - clearDtlsError(); - verifiedClientHello.clear(); - - if (!dtls.init(this, socket, address, port, dgram)) - return false; - - dtls.secret = secret; - dtls.hashAlgorithm = hashAlgorithm; - - Q_ASSERT(dtls.tlsConnection.data()); - QSharedPointer peer(q_BIO_ADDR_new(), dtlsutil::delete_BIO_ADDR); - if (!peer.data()) { - setDtlsError(QDtlsError::TlsInitializationError, - QDtlsClientVerifier::tr("BIO_ADDR_new failed, ignoring client hello")); - return false; - } - - const int ret = q_DTLSv1_listen(dtls.tlsConnection.data(), peer.data()); - if (ret < 0) { - // Since 1.1 - it's a fatal error (not so in 1.0.2 for non-blocking socket) - setDtlsError(QDtlsError::TlsFatalError, QTlsBackendOpenSSL::getErrorsFromOpenSsl()); - return false; - } - - if (ret > 0) { - verifiedClientHello = dgram; - return true; - } - - return false; -} - -QByteArray QDtlsClientVerifierOpenSSL::verifiedHello() const -{ - return verifiedClientHello; -} - -void QDtlsPrivateOpenSSL::TimeoutHandler::start(int hintMs) -{ - Q_ASSERT(timerId == -1); - timerId = startTimer(hintMs > 0 ? hintMs : timeoutMs, Qt::PreciseTimer); -} - -void QDtlsPrivateOpenSSL::TimeoutHandler::doubleTimeout() -{ - if (timeoutMs * 2 < 60000) - timeoutMs *= 2; - else - timeoutMs = 60000; -} - -void QDtlsPrivateOpenSSL::TimeoutHandler::stop() -{ - if (timerId != -1) { - killTimer(timerId); - timerId = -1; - } -} - -void QDtlsPrivateOpenSSL::TimeoutHandler::timerEvent(QTimerEvent *event) -{ - Q_UNUSED(event); - Q_ASSERT(timerId != -1); - - killTimer(timerId); - timerId = -1; - - Q_ASSERT(dtlsConnection); - dtlsConnection->reportTimeout(); -} - -QDtlsPrivateOpenSSL::QDtlsPrivateOpenSSL(QDtls *qObject, QSslSocket::SslMode side) - : QDtlsBasePrivate(side, dtlsutil::fallbackSecret()), q(qObject) -{ - Q_ASSERT(qObject); - - dtls.dtlsPrivate = this; -} - -QSslSocket::SslMode QDtlsPrivateOpenSSL::cryptographMode() const -{ - return mode; -} - -void QDtlsPrivateOpenSSL::setPeer(const QHostAddress &addr, quint16 port, const QString &name) -{ - remoteAddress = addr; - remotePort = port; - peerVfyName = name; -} - -QHostAddress QDtlsPrivateOpenSSL::peerAddress() const -{ - return remoteAddress; -} - -quint16 QDtlsPrivateOpenSSL::peerPort() const -{ - return remotePort; -} - -void QDtlsPrivateOpenSSL::setPeerVerificationName(const QString &name) -{ - peerVfyName = name; -} - -QString QDtlsPrivateOpenSSL::peerVerificationName() const -{ - return peerVfyName; -} - -void QDtlsPrivateOpenSSL::setDtlsMtuHint(quint16 mtu) -{ - mtuHint = mtu; -} - -quint16 QDtlsPrivateOpenSSL::dtlsMtuHint() const -{ - return mtuHint; -} - -QDtls::HandshakeState QDtlsPrivateOpenSSL::state() const -{ - return handshakeState; -} - -bool QDtlsPrivateOpenSSL::isConnectionEncrypted() const -{ - return connectionEncrypted; -} - -bool QDtlsPrivateOpenSSL::startHandshake(QUdpSocket *socket, const QByteArray &dgram) -{ - Q_ASSERT(socket); - Q_ASSERT(handshakeState == QDtls::HandshakeNotStarted); - - clearDtlsError(); - connectionEncrypted = false; - - if (!dtls.init(this, socket, remoteAddress, remotePort, dgram)) - return false; - - if (mode == QSslSocket::SslServerMode && dtlsConfiguration.dtlsCookieVerificationEnabled()) { - dtls.secret = secret; - dtls.hashAlgorithm = hashAlgorithm; - // Let's prepare the state machine so that message sequence 1 does not - // surprise DTLS/OpenSSL (such a message would be disregarded as - // 'stale or future' in SSL_accept otherwise): - int result = 0; - QSharedPointer peer(q_BIO_ADDR_new(), dtlsutil::delete_BIO_ADDR); - if (!peer.data()) { - setDtlsError(QDtlsError::TlsInitializationError, - QDtls::tr("BIO_ADD_new failed, cannot start handshake")); - return false; - } - - // If it's an invalid/unexpected ClientHello, we don't want to send - // VerifyClientRequest - it's a job of QDtlsClientVerifier - so we - // suppress any attempts to write into socket: - dtls.writeSuppressed = true; - result = q_DTLSv1_listen(dtls.tlsConnection.data(), peer.data()); - dtls.writeSuppressed = false; - - if (result <= 0) { - setDtlsError(QDtlsError::TlsFatalError, - QDtls::tr("Cannot start the handshake, verified client hello expected")); - dtls.reset(); - return false; - } - } - - handshakeState = QDtls::HandshakeInProgress; - opensslErrors.clear(); - tlsErrors.clear(); - - return continueHandshake(socket, dgram); -} - -bool QDtlsPrivateOpenSSL::continueHandshake(QUdpSocket *socket, const QByteArray &dgram) -{ - Q_ASSERT(socket); - - Q_ASSERT(handshakeState == QDtls::HandshakeInProgress); - - clearDtlsError(); - - if (timeoutHandler.data()) - timeoutHandler->stop(); - - if (!dtls.init(this, socket, remoteAddress, remotePort, dgram)) - return false; - - dtls.x509Errors.clear(); - - int result = 0; - if (mode == QSslSocket::SslServerMode) - result = q_SSL_accept(dtls.tlsConnection.data()); - else - result = q_SSL_connect(dtls.tlsConnection.data()); - - // DTLSTODO: Investigate/test if it makes sense - QSslSocket can emit - // peerVerifyError at this point (and thus potentially client code - // will close the underlying TCP connection immediately), but we are using - // QUdpSocket, no connection to close, our verification callback returns 1 - // (verified OK) and this probably means OpenSSL has already sent a reply - // to the server's hello/certificate. - - opensslErrors << dtls.x509Errors; - - if (result <= 0) { - const auto code = q_SSL_get_error(dtls.tlsConnection.data(), result); - switch (code) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - // DTLSTODO: to be tested - in principle, if it was the first call to - // continueHandshake and server for some reason discards the client - // hello message (even the verified one) - our 'this' will probably - // forever stay in this strange InProgress state? (the client - // will dully re-transmit the same hello and we discard it again?) - // SSL_get_state can provide more information about state - // machine and we can switch to NotStarted (since we have not - // replied with our hello ...) - if (!timeoutHandler.data()) { - timeoutHandler.reset(new TimeoutHandler); - timeoutHandler->dtlsConnection = this; - } else { - // Back to 1s. - timeoutHandler->resetTimeout(); - } - - timeoutHandler->start(); - - return true; // The handshake is not yet complete. - default: - storePeerCertificates(); - setDtlsError(QDtlsError::TlsFatalError, - QTlsBackendOpenSSL::msgErrorsDuringHandshake()); - dtls.reset(); - handshakeState = QDtls::HandshakeNotStarted; - return false; - } - } - - storePeerCertificates(); - fetchNegotiatedParameters(); - - const bool doVerifyPeer = dtlsConfiguration.peerVerifyMode() == QSslSocket::VerifyPeer - || (dtlsConfiguration.peerVerifyMode() == QSslSocket::AutoVerifyPeer - && mode == QSslSocket::SslClientMode); - - if (!doVerifyPeer || verifyPeer() || tlsErrorsWereIgnored()) { - connectionEncrypted = true; - handshakeState = QDtls::HandshakeComplete; - return true; - } - - setDtlsError(QDtlsError::PeerVerificationError, QDtls::tr("Peer verification failed")); - handshakeState = QDtls::PeerVerificationFailed; - return false; -} - - -bool QDtlsPrivateOpenSSL::handleTimeout(QUdpSocket *socket) -{ - Q_ASSERT(socket); - - Q_ASSERT(timeoutHandler.data()); - Q_ASSERT(dtls.tlsConnection.data()); - - clearDtlsError(); - - dtls.udpSocket = socket; - - if (q_DTLSv1_handle_timeout(dtls.tlsConnection.data()) > 0) { - timeoutHandler->doubleTimeout(); - timeoutHandler->start(); - } else { - timeoutHandler->start(dtlsutil::next_timeoutMs(dtls.tlsConnection.data())); - } - - return true; -} - -bool QDtlsPrivateOpenSSL::resumeHandshake(QUdpSocket *socket) -{ - Q_UNUSED(socket); - Q_ASSERT(socket); - Q_ASSERT(handshakeState == QDtls::PeerVerificationFailed); - - clearDtlsError(); - - if (tlsErrorsWereIgnored()) { - handshakeState = QDtls::HandshakeComplete; - connectionEncrypted = true; - tlsErrors.clear(); - tlsErrorsToIgnore.clear(); - return true; - } - - return false; -} - -void QDtlsPrivateOpenSSL::abortHandshake(QUdpSocket *socket) -{ - Q_ASSERT(socket); - Q_ASSERT(handshakeState == QDtls::PeerVerificationFailed - || handshakeState == QDtls::HandshakeInProgress); - - clearDtlsError(); - - if (handshakeState == QDtls::PeerVerificationFailed) { - // Yes, while peer verification failed, we were actually encrypted. - // Let's play it nice - inform our peer about connection shut down. - sendShutdownAlert(socket); - } else { - resetDtls(); - } -} - -void QDtlsPrivateOpenSSL::sendShutdownAlert(QUdpSocket *socket) -{ - Q_ASSERT(socket); - - clearDtlsError(); - - if (connectionEncrypted && !connectionWasShutdown) { - dtls.udpSocket = socket; - Q_ASSERT(dtls.tlsConnection.data()); - q_SSL_shutdown(dtls.tlsConnection.data()); - } - - resetDtls(); -} - -QList QDtlsPrivateOpenSSL::peerVerificationErrors() const -{ - return tlsErrors; -} - -void QDtlsPrivateOpenSSL::ignoreVerificationErrors(const QList &errorsToIgnore) -{ - tlsErrorsToIgnore = errorsToIgnore; -} - -QSslCipher QDtlsPrivateOpenSSL::dtlsSessionCipher() const -{ - return sessionCipher; -} - -QSsl::SslProtocol QDtlsPrivateOpenSSL::dtlsSessionProtocol() const -{ - return sessionProtocol; -} - -qint64 QDtlsPrivateOpenSSL::writeDatagramEncrypted(QUdpSocket *socket, - const QByteArray &dgram) -{ - Q_ASSERT(socket); - Q_ASSERT(dtls.tlsConnection.data()); - Q_ASSERT(connectionEncrypted); - - clearDtlsError(); - - dtls.udpSocket = socket; - const int written = q_SSL_write(dtls.tlsConnection.data(), - dgram.constData(), dgram.size()); - if (written > 0) - return written; - - const unsigned long errorCode = q_ERR_get_error(); - if (!dgram.size() && errorCode == SSL_ERROR_NONE) { - // With OpenSSL <= 1.1 this can happen. For example, DTLS client - // tries to reconnect (while re-using the same address/port) - - // DTLS server drops a message with unexpected epoch but says - no - // error. We leave to client code to resolve such problems until - // OpenSSL provides something better. - return 0; - } - - switch (errorCode) { - case SSL_ERROR_WANT_WRITE: - case SSL_ERROR_WANT_READ: - // We do not set any error/description ... a user can probably re-try - // sending a datagram. - break; - case SSL_ERROR_ZERO_RETURN: - connectionWasShutdown = true; - setDtlsError(QDtlsError::TlsFatalError, QDtls::tr("The DTLS connection has been closed")); - handshakeState = QDtls::HandshakeNotStarted; - dtls.reset(); - break; - case SSL_ERROR_SYSCALL: - case SSL_ERROR_SSL: - default: - // DTLSTODO: we don't know yet what to do. Tests needed - probably, - // some errors can be just ignored (it's UDP, not TCP after all). - // Unlike QSslSocket we do not abort though. - QString description(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); - if (socket->error() != QAbstractSocket::UnknownSocketError && description.isEmpty()) { - setDtlsError(QDtlsError::UnderlyingSocketError, socket->errorString()); - } else { - setDtlsError(QDtlsError::TlsFatalError, - QDtls::tr("Error while writing: %1").arg(description)); - } - } - - return -1; -} - -QByteArray QDtlsPrivateOpenSSL::decryptDatagram(QUdpSocket *socket, const QByteArray &tlsdgram) -{ - Q_ASSERT(socket); - Q_ASSERT(tlsdgram.size()); - - Q_ASSERT(dtls.tlsConnection.data()); - Q_ASSERT(connectionEncrypted); - - dtls.dgram = tlsdgram; - dtls.udpSocket = socket; - - clearDtlsError(); - - QByteArray dgram; - dgram.resize(tlsdgram.size()); - const int read = q_SSL_read(dtls.tlsConnection.data(), dgram.data(), - dgram.size()); - - if (read > 0) { - dgram.resize(read); - return dgram; - } - - dgram.clear(); - unsigned long errorCode = q_ERR_get_error(); - if (errorCode == SSL_ERROR_NONE) { - const int shutdown = q_SSL_get_shutdown(dtls.tlsConnection.data()); - if (shutdown & SSL_RECEIVED_SHUTDOWN) - errorCode = SSL_ERROR_ZERO_RETURN; - else - return dgram; - } - - switch (errorCode) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - return dgram; - case SSL_ERROR_ZERO_RETURN: - // "The connection was shut down cleanly" ... hmm, whatever, - // needs testing (DTLSTODO). - connectionWasShutdown = true; - setDtlsError(QDtlsError::RemoteClosedConnectionError, - QDtls::tr("The DTLS connection has been shutdown")); - dtls.reset(); - connectionEncrypted = false; - handshakeState = QDtls::HandshakeNotStarted; - return dgram; - case SSL_ERROR_SYSCALL: // some IO error - case SSL_ERROR_SSL: // error in the SSL library - // DTLSTODO: Apparently, some errors can be ignored, for example, - // ECONNRESET etc. This all needs a lot of testing!!! - default: - setDtlsError(QDtlsError::TlsNonFatalError, - QDtls::tr("Error while reading: %1") - .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); - return dgram; - } -} - -unsigned QDtlsPrivateOpenSSL::pskClientCallback(const char *hint, char *identity, - unsigned max_identity_len, - unsigned char *psk, - unsigned max_psk_len) -{ - // The code below is taken (with some modifications) from qsslsocket_openssl - // - alas, we cannot simply re-use it, it's in QSslSocketPrivate. - { - QSslPreSharedKeyAuthenticator authenticator; - // Fill in some read-only fields (for client code) - if (hint) { - identityHint.clear(); - identityHint.append(hint); - } - - QTlsBackend::setupClientPskAuth(&authenticator, hint ? identityHint.constData() : nullptr, - hint ? int(std::strlen(hint)) : 0, max_identity_len, max_psk_len); - pskAuthenticator.swap(authenticator); - } - - // Let the client provide the remaining bits... - emit q->pskRequired(&pskAuthenticator); - - // No PSK set? Return now to make the handshake fail - if (pskAuthenticator.preSharedKey().isEmpty()) - return 0; - - // Copy data back into OpenSSL - const int identityLength = qMin(pskAuthenticator.identity().length(), - pskAuthenticator.maximumIdentityLength()); - std::memcpy(identity, pskAuthenticator.identity().constData(), identityLength); - identity[identityLength] = 0; - - const int pskLength = qMin(pskAuthenticator.preSharedKey().length(), - pskAuthenticator.maximumPreSharedKeyLength()); - std::memcpy(psk, pskAuthenticator.preSharedKey().constData(), pskLength); - - return pskLength; -} - -unsigned QDtlsPrivateOpenSSL::pskServerCallback(const char *identity, unsigned char *psk, - unsigned max_psk_len) -{ - { - QSslPreSharedKeyAuthenticator authenticator; - // Fill in some read-only fields (for the user) - QTlsBackend::setupServerPskAuth(&authenticator, identity, dtlsConfiguration.preSharedKeyIdentityHint(), - max_psk_len); - pskAuthenticator.swap(authenticator); - } - - // Let the client provide the remaining bits... - emit q->pskRequired(&pskAuthenticator); - - // No PSK set? Return now to make the handshake fail - if (pskAuthenticator.preSharedKey().isEmpty()) - return 0; - - // Copy data back into OpenSSL - const int pskLength = qMin(pskAuthenticator.preSharedKey().length(), - pskAuthenticator.maximumPreSharedKeyLength()); - - std::memcpy(psk, pskAuthenticator.preSharedKey().constData(), pskLength); - - return pskLength; -} - -bool QDtlsPrivateOpenSSL::verifyPeer() -{ - QList errors; - - // Check the whole chain for blacklisting (including root, as we check for - // subjectInfo and issuer) - const auto &peerCertificateChain = dtlsConfiguration.peerCertificateChain(); - for (const QSslCertificate &cert : peerCertificateChain) { - if (QSslCertificatePrivate::isBlacklisted(cert)) - errors << QSslError(QSslError::CertificateBlacklisted, cert); - } - - const auto peerCertificate = dtlsConfiguration.peerCertificate(); - if (peerCertificate.isNull()) { - errors << QSslError(QSslError::NoPeerCertificate); - } else if (mode == QSslSocket::SslClientMode) { - // Check the peer certificate itself. First try the subject's common name - // (CN) as a wildcard, then try all alternate subject name DNS entries the - // same way. - - // QSslSocket has a rather twisted logic: if verificationPeerName - // is empty, we call QAbstractSocket::peerName(), which returns - // either peerName (can be set by setPeerName) or host name - // (can be set as a result of connectToHost). - QString name = peerVfyName; - if (name.isEmpty()) { - Q_ASSERT(dtls.udpSocket); - name = dtls.udpSocket->peerName(); - } - - if (!QTlsPrivate::TlsCryptograph::isMatchingHostname(peerCertificate, name)) - errors << QSslError(QSslError::HostNameMismatch, peerCertificate); - } - - // Translate errors from the error list into QSslErrors - using CertClass = QTlsPrivate::X509CertificateOpenSSL; - errors.reserve(errors.size() + opensslErrors.size()); - for (const auto &error : qAsConst(opensslErrors)) { - const auto value = peerCertificateChain.value(error.depth); - errors << CertClass::openSSLErrorToQSslError(error.code, value); - } - - tlsErrors = errors; - return tlsErrors.isEmpty(); -} - -void QDtlsPrivateOpenSSL::storePeerCertificates() -{ - Q_ASSERT(dtls.tlsConnection.data()); - // Store the peer certificate and chain. For clients, the peer certificate - // chain includes the peer certificate; for servers, it doesn't. Both the - // peer certificate and the chain may be empty if the peer didn't present - // any certificate. - X509 *x509 = q_SSL_get_peer_certificate(dtls.tlsConnection.data()); - const auto peerCertificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); - QTlsBackend::storePeerCertificate(dtlsConfiguration, peerCertificate); - q_X509_free(x509); - - auto peerCertificateChain = dtlsConfiguration.peerCertificateChain(); - if (peerCertificateChain.isEmpty()) { - auto stack = q_SSL_get_peer_cert_chain(dtls.tlsConnection.data()); - peerCertificateChain = QTlsPrivate::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(stack); - if (!peerCertificate.isNull() && mode == QSslSocket::SslServerMode) - peerCertificateChain.prepend(peerCertificate); - QTlsBackend::storePeerCertificateChain(dtlsConfiguration, peerCertificateChain); - } -} - -bool QDtlsPrivateOpenSSL::tlsErrorsWereIgnored() const -{ - // check whether the errors we got are all in the list of expected errors - // (applies only if the method QDtlsConnection::ignoreTlsErrors(const - // QList &errors) was called) - for (const QSslError &error : tlsErrors) { - if (!tlsErrorsToIgnore.contains(error)) - return false; - } - - return !tlsErrorsToIgnore.empty(); -} - -void QDtlsPrivateOpenSSL::fetchNegotiatedParameters() -{ - Q_ASSERT(dtls.tlsConnection.data()); - - if (const SSL_CIPHER *cipher = q_SSL_get_current_cipher(dtls.tlsConnection.data())) - sessionCipher = QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(cipher); - else - sessionCipher = {}; - - // Note: cipher's protocol version will be reported as either TLS 1.0 or - // TLS 1.2, that's how it's set by OpenSSL (and that's what they are?). - - switch (q_SSL_version(dtls.tlsConnection.data())) { - case DTLS1_VERSION: - sessionProtocol = QSsl::DtlsV1_0; - break; - case DTLS1_2_VERSION: - sessionProtocol = QSsl::DtlsV1_2; - break; - default: - qCWarning(lcSsl, "unknown protocol version"); - sessionProtocol = QSsl::UnknownProtocol; - } -} - -void QDtlsPrivateOpenSSL::reportTimeout() -{ - emit q->handshakeTimeout(); -} - -void QDtlsPrivateOpenSSL::resetDtls() -{ - dtls.reset(); - connectionEncrypted = false; - tlsErrors.clear(); - tlsErrorsToIgnore.clear(); - QTlsBackend::clearPeerCertificates(dtlsConfiguration); - connectionWasShutdown = false; - handshakeState = QDtls::HandshakeNotStarted; - sessionCipher = {}; - sessionProtocol = QSsl::UnknownProtocol; -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qdtls_openssl_p.h b/src/network/ssl/qdtls_openssl_p.h deleted file mode 100644 index 281e1133cd..0000000000 --- a/src/network/ssl/qdtls_openssl_p.h +++ /dev/null @@ -1,251 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QDTLS_OPENSSL_P_H -#define QDTLS_OPENSSL_P_H - -#include - -#include - -#include - -#include "qtlsbackend_openssl_p.h" -#include "qtls_openssl_p.h" -#include "qdtls_base_p.h" -#include "qdtls_p.h" - -#include -#include - -#include -#include - -#include -#include -#include -#include - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -QT_REQUIRE_CONFIG(openssl); -QT_REQUIRE_CONFIG(dtls); - -QT_BEGIN_NAMESPACE - -class QDtlsPrivateOpenSSL; -class QDtlsBasePrivate; -class QUdpSocket; - -namespace dtlsopenssl -{ - -class DtlsState -{ -public: - // Note, bioMethod _must_ outlive BIOs it was used to create. Thus - // the order of declarations here matters. - using BioMethod = QSharedPointer; - BioMethod bioMethod; - - using TlsContext = QSharedPointer; - TlsContext tlsContext; - - using TlsConnection = QSharedPointer; - TlsConnection tlsConnection; - - QByteArray dgram; - - QHostAddress remoteAddress; - quint16 remotePort = 0; - - QList x509Errors; - - long peeking = false; - QUdpSocket *udpSocket = nullptr; - bool writeSuppressed = false; - - bool init(QDtlsBasePrivate *dtlsBase, QUdpSocket *socket, - const QHostAddress &remote, quint16 port, - const QByteArray &receivedMessage); - - void reset(); - - QDtlsPrivateOpenSSL *dtlsPrivate = nullptr; - QByteArray secret; - -#ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 - QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; -#else - QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha256; -#endif - -private: - - bool initTls(QDtlsBasePrivate *dtlsBase); - bool initCtxAndConnection(QDtlsBasePrivate *dtlsBase); - bool initBIO(QDtlsBasePrivate *dtlsBase); - void setLinkMtu(QDtlsBasePrivate *dtlsBase); -}; - -} // namespace dtlsopenssl - -// The trick with 'right' ancestor in the tree overriding (only once) some shared -// virtual functions is intentional. Too bad MSVC warns me about ... exactly the -// feature of C++ that I want to use. - -QT_WARNING_PUSH -QT_WARNING_DISABLE_MSVC(4250) - -class QDtlsClientVerifierOpenSSL : public QTlsPrivate::DtlsCookieVerifier, public QDtlsBasePrivate -{ -public: - QDtlsClientVerifierOpenSSL(); - - bool verifyClient(QUdpSocket *socket, const QByteArray &dgram, - const QHostAddress &address, quint16 port) override; - QByteArray verifiedHello() const override; - -private: - dtlsopenssl::DtlsState dtls; - QByteArray verifiedClientHello; -}; - -class QDtlsPrivateOpenSSL : public QTlsPrivate::DtlsCryptograph, public QDtlsBasePrivate -{ -public: - - QDtlsPrivateOpenSSL(QDtls *qObject, QSslSocket::SslMode mode); - -private: - - QSslSocket::SslMode cryptographMode() const override; - void setPeer(const QHostAddress &addr, quint16 port, const QString &name) override; - QHostAddress peerAddress() const override; - quint16 peerPort() const override; - void setPeerVerificationName(const QString &name) override; - QString peerVerificationName() const override; - - virtual void setDtlsMtuHint(quint16 mtu) override; - virtual quint16 dtlsMtuHint() const override; - - virtual QDtls::HandshakeState state() const override; - virtual bool isConnectionEncrypted() const override; - - bool startHandshake(QUdpSocket *socket, const QByteArray &datagram) override; - bool continueHandshake(QUdpSocket *socket, const QByteArray &datagram) override; - bool resumeHandshake(QUdpSocket *socket) override; - void abortHandshake(QUdpSocket *socket) override; - bool handleTimeout(QUdpSocket *socket) override; - void sendShutdownAlert(QUdpSocket *socket) override; - - QList peerVerificationErrors() const override; - void ignoreVerificationErrors(const QList &errorsToIgnore) override; - - QSslCipher dtlsSessionCipher() const override; - QSsl::SslProtocol dtlsSessionProtocol() const override; - - qint64 writeDatagramEncrypted(QUdpSocket *socket, const QByteArray &datagram) override; - QByteArray decryptDatagram(QUdpSocket *socket, const QByteArray &tlsdgram) override; - -public: - unsigned pskClientCallback(const char *hint, char *identity, unsigned max_identity_len, - unsigned char *psk, unsigned max_psk_len); - unsigned pskServerCallback(const char *identity, unsigned char *psk, - unsigned max_psk_len); - -private: - - bool verifyPeer(); - void storePeerCertificates(); - bool tlsErrorsWereIgnored() const; - void fetchNegotiatedParameters(); - void reportTimeout(); - void resetDtls(); - - QList opensslErrors; - dtlsopenssl::DtlsState dtls; - - // We have to externally handle timeouts since we have non-blocking - // sockets and OpenSSL(DTLS) with non-blocking UDP sockets does not - // know if a timeout has occurred. - struct TimeoutHandler : QObject - { - TimeoutHandler() = default; - - void start(int hintMs = 0); - void doubleTimeout(); - void resetTimeout() {timeoutMs = 1000;} - void stop(); - void timerEvent(QTimerEvent *event) override; - - int timerId = -1; - int timeoutMs = 1000; - - QDtlsPrivateOpenSSL *dtlsConnection = nullptr; - }; - - QDtls *q = nullptr; - QDtls::HandshakeState handshakeState = QDtls::HandshakeNotStarted; - - QList tlsErrors; - QList tlsErrorsToIgnore; - bool connectionEncrypted = false; - // We will initialize it 'lazily', just in case somebody wants to move - // QDtls to another thread. - QScopedPointer timeoutHandler; - bool connectionWasShutdown = false; - QSslPreSharedKeyAuthenticator pskAuthenticator; - QByteArray identityHint; -}; - -QT_WARNING_POP // C4250 - -QT_END_NAMESPACE - -#endif // QDTLS_OPENSSL_P_H diff --git a/src/network/ssl/qopenssl.cpp b/src/network/ssl/qopenssl.cpp deleted file mode 100644 index 1453b4d881..0000000000 --- a/src/network/ssl/qopenssl.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Copyright (C) 2014 Governikus GmbH & Co. KG -** 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$ -** -****************************************************************************/ - -/**************************************************************************** -** -** In addition, as a special exception, the copyright holders listed above give -** permission to link the code of its release of Qt with the OpenSSL project's -** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the -** same license as the original version), and distribute the linked executables. -** -** You must comply with the GNU General Public License version 2 in all -** respects for all of the code used other than the "OpenSSL" code. If you -** modify this file, you may extend this exception to your version of the file, -** but you are not obligated to do so. If you do not wish to do so, delete -** this exception statement from your version of this file. -** -****************************************************************************/ - -#include "qtlsbackend_openssl_p.h" -#include "qopenssl_p.h" - -QT_BEGIN_NAMESPACE - -Q_GLOBAL_STATIC(QTlsBackendOpenSSL, backendOpenSsl) - -void QSslSocketPrivate::registerAdHocFactory() -{ - // TLSTODO: this is a temporary solution, waiting for - // backends to move to ... plugins. - if (!backendOpenSsl()) - qCWarning(lcSsl, "Failed to create backend factory"); -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qopenssl_p.h b/src/network/ssl/qopenssl_p.h deleted file mode 100644 index fa3efb2c28..0000000000 --- a/src/network/ssl/qopenssl_p.h +++ /dev/null @@ -1,117 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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$ -** -****************************************************************************/ - -/**************************************************************************** -** -** In addition, as a special exception, the copyright holders listed above give -** permission to link the code of its release of Qt with the OpenSSL project's -** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the -** same license as the original version), and distribute the linked executables. -** -** You must comply with the GNU General Public License version 2 in all -** respects for all of the code used other than the "OpenSSL" code. If you -** modify this file, you may extend this exception to your version of the file, -** but you are not obligated to do so. If you do not wish to do so, delete -** this exception statement from your version of this file. -** -****************************************************************************/ - -#ifndef QSSLSOCKET_OPENSSL_P_H -#define QSSLSOCKET_OPENSSL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include "qsslsocket_p.h" -#include "qsslcipher.h" - -#ifdef Q_OS_WIN -#include -#if defined(OCSP_RESPONSE) -#undef OCSP_RESPONSE -#endif -#if defined(X509_NAME) -#undef X509_NAME -#endif -#endif // Q_OS_WIN - -// This file is included in several *.cpp files and provides different -// openssl declarations where they are needed. -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -struct QSslErrorEntry { - int code = 0; - int depth = 0; -}; - -Q_DECLARE_TYPEINFO(QSslErrorEntry, Q_PRIMITIVE_TYPE); - -QT_END_NAMESPACE - -#endif diff --git a/src/network/ssl/qsslcontext_openssl.cpp b/src/network/ssl/qsslcontext_openssl.cpp deleted file mode 100644 index de7ef15d10..0000000000 --- a/src/network/ssl/qsslcontext_openssl.cpp +++ /dev/null @@ -1,824 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Copyright (C) 2014 BlackBerry Limited. All rights reserved. -** Copyright (C) 2014 Governikus GmbH & Co. KG. -** Copyright (C) 2016 Richard J. Moore -** 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$ -** -****************************************************************************/ - -#include -#include - -#include "private/qopenssl_p.h" -#include "private/qssl_p.h" -#include "private/qsslsocket_p.h" -#include "private/qsslcontext_openssl_p.h" -#include "private/qsslsocket_openssl_symbols_p.h" -#include "private/qssldiffiehellmanparameters_p.h" -#include "private/qtlsbackend_openssl_p.h" - -#include - -QT_BEGIN_NAMESPACE - -Q_GLOBAL_STATIC(bool, forceSecurityLevel) - -Q_NETWORK_EXPORT void qt_ForceTlsSecurityLevel() -{ - *forceSecurityLevel() = true; -} - -namespace QTlsPrivate -{ -// These callback functions are defined in qtls_openssl.cpp. -extern "C" int q_X509Callback(int ok, X509_STORE_CTX *ctx); -extern "C" int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx); - -#if QT_CONFIG(ocsp) -extern "C" int qt_OCSP_status_server_callback(SSL *ssl, void *); -#endif // ocsp - -} // namespace QTlsPrivate - -#if QT_CONFIG(dtls) -// defined in qdtls_openssl.cpp: -namespace dtlscallbacks -{ -extern "C" int q_X509DtlsCallback(int ok, X509_STORE_CTX *ctx); -extern "C" int q_generate_cookie_callback(SSL *ssl, unsigned char *dst, - unsigned *cookieLength); -extern "C" int q_verify_cookie_callback(SSL *ssl, const unsigned char *cookie, - unsigned cookieLength); -} -#endif // dtls - -#ifdef TLS1_3_VERSION -extern "C" int q_ssl_sess_set_new_cb(SSL *context, SSL_SESSION *session); -#endif // TLS1_3_VERSION - -static inline QString msgErrorSettingBackendConfig(const QString &why) -{ - return QSslSocket::tr("Error when setting the OpenSSL configuration (%1)").arg(why); -} - -static inline QString msgErrorSettingEllipticCurves(const QString &why) -{ - return QSslSocket::tr("Error when setting the elliptic curves (%1)").arg(why); -} - -long QSslContext::setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions) -{ - long options; - switch (protocol) { - case QSsl::SecureProtocols: - case QSsl::TlsV1_0OrLater: - options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; - break; - case QSsl::TlsV1_1OrLater: - options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; - break; - case QSsl::TlsV1_2OrLater: - options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1; - break; - case QSsl::TlsV1_3OrLater: - options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2; - break; - default: - options = SSL_OP_ALL; - } - - // This option is disabled by default, so we need to be able to clear it - if (sslOptions & QSsl::SslOptionDisableEmptyFragments) - options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; - else - options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; - -#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION - // This option is disabled by default, so we need to be able to clear it - if (sslOptions & QSsl::SslOptionDisableLegacyRenegotiation) - options &= ~SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; - else - options |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; -#endif - -#ifdef SSL_OP_NO_TICKET - if (sslOptions & QSsl::SslOptionDisableSessionTickets) - options |= SSL_OP_NO_TICKET; -#endif -#ifdef SSL_OP_NO_COMPRESSION - if (sslOptions & QSsl::SslOptionDisableCompression) - options |= SSL_OP_NO_COMPRESSION; -#endif - - if (!(sslOptions & QSsl::SslOptionDisableServerCipherPreference)) - options |= SSL_OP_CIPHER_SERVER_PREFERENCE; - - return options; -} - -QSslContext::QSslContext() - : ctx(nullptr), - pkey(nullptr), - session(nullptr), - m_sessionTicketLifeTimeHint(-1) -{ -} - -QSslContext::~QSslContext() -{ - if (ctx) - // This will decrement the reference count by 1 and free the context eventually when possible - q_SSL_CTX_free(ctx); - - if (pkey) - q_EVP_PKEY_free(pkey); - - if (session) - q_SSL_SESSION_free(session); -} - -QSslContext* QSslContext::fromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, bool allowRootCertOnDemandLoading) -{ - QSslContext *sslContext = new QSslContext(); - initSslContext(sslContext, mode, configuration, allowRootCertOnDemandLoading); - return sslContext; -} - -QSharedPointer QSslContext::sharedFromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, bool allowRootCertOnDemandLoading) -{ - QSharedPointer sslContext = QSharedPointer::create(); - initSslContext(sslContext.data(), mode, configuration, allowRootCertOnDemandLoading); - return sslContext; -} - -QSharedPointer QSslContext::sharedFromPrivateConfiguration(QSslSocket::SslMode mode, QSslConfigurationPrivate *privConfiguration, - bool allowRootCertOnDemandLoading) -{ - return sharedFromConfiguration(mode, privConfiguration, allowRootCertOnDemandLoading); -} - -#ifndef OPENSSL_NO_NEXTPROTONEG - -static int next_proto_cb(SSL *, unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, void *arg) -{ - QSslContext::NPNContext *ctx = reinterpret_cast(arg); - - // comment out to debug: -// QList supportedVersions; -// for (unsigned int i = 0; i < inlen; ) { -// QByteArray version(reinterpret_cast(&in[i+1]), in[i]); -// supportedVersions << version; -// i += in[i] + 1; -// } - - int proto = q_SSL_select_next_proto(out, outlen, in, inlen, ctx->data, ctx->len); - switch (proto) { - case OPENSSL_NPN_UNSUPPORTED: - ctx->status = QSslConfiguration::NextProtocolNegotiationNone; - break; - case OPENSSL_NPN_NEGOTIATED: - ctx->status = QSslConfiguration::NextProtocolNegotiationNegotiated; - break; - case OPENSSL_NPN_NO_OVERLAP: - ctx->status = QSslConfiguration::NextProtocolNegotiationUnsupported; - break; - default: - qCWarning(lcSsl, "OpenSSL sent unknown NPN status"); - } - - return SSL_TLSEXT_ERR_OK; -} - -QSslContext::NPNContext QSslContext::npnContext() const -{ - return m_npnContext; -} -#endif // !OPENSSL_NO_NEXTPROTONEG - - - -// Needs to be deleted by caller -SSL* QSslContext::createSsl() -{ - SSL* ssl = q_SSL_new(ctx); - q_SSL_clear(ssl); - - if (!session && !sessionASN1().isEmpty() - && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) { - const unsigned char *data = reinterpret_cast(m_sessionASN1.constData()); - session = q_d2i_SSL_SESSION(nullptr, &data, m_sessionASN1.size()); - // 'session' has refcount 1 already, set by the function above - } - - if (session) { - // Try to resume the last session we cached - if (!q_SSL_set_session(ssl, session)) { - qCWarning(lcSsl, "could not set SSL session"); - q_SSL_SESSION_free(session); - session = nullptr; - } - } - -#ifndef OPENSSL_NO_NEXTPROTONEG - QList protocols = sslConfiguration.d.constData()->nextAllowedProtocols; - if (!protocols.isEmpty()) { - m_supportedNPNVersions.clear(); - for (int a = 0; a < protocols.count(); ++a) { - if (protocols.at(a).size() > 255) { - qCWarning(lcSsl) << "TLS NPN extension" << protocols.at(a) - << "is too long and will be ignored."; - continue; - } else if (protocols.at(a).isEmpty()) { - continue; - } - m_supportedNPNVersions.append(protocols.at(a).size()).append(protocols.at(a)); - } - if (m_supportedNPNVersions.size()) { - m_npnContext.data = reinterpret_cast(m_supportedNPNVersions.data()); - m_npnContext.len = m_supportedNPNVersions.count(); - m_npnContext.status = QSslConfiguration::NextProtocolNegotiationNone; - // Callback's type has a parameter 'const unsigned char ** out' - // since it was introduced in 1.0.2. Internally, OpenSSL's own code - // (tests/examples) cast it to unsigned char * (since it's 'out'). - // We just re-use our NPN callback and cast here: - typedef int (*alpn_callback_t) (SSL *, const unsigned char **, unsigned char *, - const unsigned char *, unsigned int, void *); - // With ALPN callback is for a server side only, for a client m_npnContext.status - // will stay in NextProtocolNegotiationNone. - q_SSL_CTX_set_alpn_select_cb(ctx, alpn_callback_t(next_proto_cb), &m_npnContext); - // Client: - q_SSL_set_alpn_protos(ssl, m_npnContext.data, m_npnContext.len); - // And in case our peer does not support ALPN, but supports NPN: - q_SSL_CTX_set_next_proto_select_cb(ctx, next_proto_cb, &m_npnContext); - } - } -#endif // !OPENSSL_NO_NEXTPROTONEG - - return ssl; -} - -// We cache exactly one session here -bool QSslContext::cacheSession(SSL* ssl) -{ - // don't cache the same session again - if (session && session == q_SSL_get_session(ssl)) - return true; - - // decrease refcount of currently stored session - // (this might happen if there are several concurrent handshakes in flight) - if (session) - q_SSL_SESSION_free(session); - - // cache the session the caller gave us and increase reference count - session = q_SSL_get1_session(ssl); - - if (session && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) { - int sessionSize = q_i2d_SSL_SESSION(session, nullptr); - if (sessionSize > 0) { - m_sessionASN1.resize(sessionSize); - unsigned char *data = reinterpret_cast(m_sessionASN1.data()); - if (!q_i2d_SSL_SESSION(session, &data)) - qCWarning(lcSsl, "could not store persistent version of SSL session"); - m_sessionTicketLifeTimeHint = q_SSL_SESSION_get_ticket_lifetime_hint(session); - } - } - - return (session != nullptr); -} - -QByteArray QSslContext::sessionASN1() const -{ - return m_sessionASN1; -} - -void QSslContext::setSessionASN1(const QByteArray &session) -{ - m_sessionASN1 = session; -} - -int QSslContext::sessionTicketLifeTimeHint() const -{ - return m_sessionTicketLifeTimeHint; -} - -QSslError::SslError QSslContext::error() const -{ - return errorCode; -} - -QString QSslContext::errorString() const -{ - return errorStr; -} - -void QSslContext::initSslContext(QSslContext *sslContext, QSslSocket::SslMode mode, - const QSslConfiguration &configuration, - bool allowRootCertOnDemandLoading) -{ - sslContext->sslConfiguration = configuration; - sslContext->errorCode = QSslError::NoError; - - bool client = (mode == QSslSocket::SslClientMode); - - bool reinitialized = false; - bool unsupportedProtocol = false; - bool isDtls = false; -init_context: - switch (sslContext->sslConfiguration.protocol()) { - case QSsl::DtlsV1_0: - case QSsl::DtlsV1_0OrLater: - case QSsl::DtlsV1_2: - case QSsl::DtlsV1_2OrLater: -#if QT_CONFIG(dtls) - isDtls = true; - sslContext->ctx = q_SSL_CTX_new(client ? q_DTLS_client_method() : q_DTLS_server_method()); -#else // dtls - sslContext->ctx = nullptr; - unsupportedProtocol = true; - qCWarning(lcSsl, "DTLS protocol requested, but feature 'dtls' is disabled"); -#endif // dtls - break; - case QSsl::TlsV1_3: - case QSsl::TlsV1_3OrLater: -#if !defined(TLS1_3_VERSION) - qCWarning(lcSsl, "TLS 1.3 is not supported"); - sslContext->ctx = nullptr; - unsupportedProtocol = true; - break; -#endif // TLS1_3_VERSION - default: - // The ssl options will actually control the supported methods - sslContext->ctx = q_SSL_CTX_new(client ? q_TLS_client_method() : q_TLS_server_method()); - } - - if (!sslContext->ctx) { - // After stopping Flash 10 the SSL library loses its ciphers. Try re-adding them - // by re-initializing the library. - if (!reinitialized) { - reinitialized = true; - if (q_OPENSSL_init_ssl(0, nullptr) == 1) - goto init_context; - } - - sslContext->errorStr = QSslSocket::tr("Error creating SSL context (%1)").arg( - unsupportedProtocol ? QSslSocket::tr("unsupported protocol") : QTlsBackendOpenSSL::getErrorsFromOpenSsl() - ); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - - // A nasty hacked OpenSSL using a level that will make our auto-tests fail: - if (q_SSL_CTX_get_security_level(sslContext->ctx) > 1 && *forceSecurityLevel()) - q_SSL_CTX_set_security_level(sslContext->ctx, 1); - - const long anyVersion = -#if QT_CONFIG(dtls) - isDtls ? DTLS_ANY_VERSION : TLS_ANY_VERSION; -#else - TLS_ANY_VERSION; -#endif // dtls - long minVersion = anyVersion; - long maxVersion = anyVersion; - - switch (sslContext->sslConfiguration.protocol()) { - case QSsl::TlsV1_0: - minVersion = TLS1_VERSION; - maxVersion = TLS1_VERSION; - break; - case QSsl::TlsV1_1: - minVersion = TLS1_1_VERSION; - maxVersion = TLS1_1_VERSION; - break; - case QSsl::TlsV1_2: - minVersion = TLS1_2_VERSION; - maxVersion = TLS1_2_VERSION; - break; - case QSsl::TlsV1_3: -#ifdef TLS1_3_VERSION - minVersion = TLS1_3_VERSION; - maxVersion = TLS1_3_VERSION; -#else - // This protocol is not supported by OpenSSL 1.1 and we handle - // it as an error (see the code above). - Q_UNREACHABLE(); -#endif // TLS1_3_VERSION - break; - // Ranges: - case QSsl::AnyProtocol: - case QSsl::SecureProtocols: - case QSsl::TlsV1_0OrLater: - minVersion = TLS1_VERSION; - maxVersion = 0; - break; - case QSsl::TlsV1_1OrLater: - minVersion = TLS1_1_VERSION; - maxVersion = 0; - break; - case QSsl::TlsV1_2OrLater: - minVersion = TLS1_2_VERSION; - maxVersion = 0; - break; - case QSsl::DtlsV1_0: - minVersion = DTLS1_VERSION; - maxVersion = DTLS1_VERSION; - break; - case QSsl::DtlsV1_0OrLater: - minVersion = DTLS1_VERSION; - maxVersion = DTLS_MAX_VERSION; - break; - case QSsl::DtlsV1_2: - minVersion = DTLS1_2_VERSION; - maxVersion = DTLS1_2_VERSION; - break; - case QSsl::DtlsV1_2OrLater: - minVersion = DTLS1_2_VERSION; - maxVersion = DTLS_MAX_VERSION; - break; - case QSsl::TlsV1_3OrLater: -#ifdef TLS1_3_VERSION - minVersion = TLS1_3_VERSION; - maxVersion = 0; - break; -#else - // This protocol is not supported by OpenSSL 1.1 and we handle - // it as an error (see the code above). - Q_UNREACHABLE(); - break; -#endif // TLS1_3_VERSION - case QSsl::UnknownProtocol: - break; - } - - if (minVersion != anyVersion - && !q_SSL_CTX_set_min_proto_version(sslContext->ctx, minVersion)) { - sslContext->errorStr = QSslSocket::tr("Error while setting the minimal protocol version"); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - - if (maxVersion != anyVersion - && !q_SSL_CTX_set_max_proto_version(sslContext->ctx, maxVersion)) { - sslContext->errorStr = QSslSocket::tr("Error while setting the maximum protocol version"); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - - // Enable bug workarounds. - const long options = setupOpenSslOptions(configuration.protocol(), configuration.d->sslOptions); - q_SSL_CTX_set_options(sslContext->ctx, options); - - // Tell OpenSSL to release memory early - // http://www.openssl.org/docs/ssl/SSL_CTX_set_mode.html - q_SSL_CTX_set_mode(sslContext->ctx, SSL_MODE_RELEASE_BUFFERS); - - auto filterCiphers = [](const QList &ciphers, bool selectTls13) - { - QByteArray cipherString; - - for (const QSslCipher &cipher : ciphers) { - const bool isTls13Cipher = cipher.protocol() == QSsl::TlsV1_3 || cipher.protocol() == QSsl::TlsV1_3OrLater; - if (selectTls13 != isTls13Cipher) - continue; - - if (cipherString.size()) - cipherString.append(':'); - cipherString.append(cipher.name().toLatin1()); - } - return cipherString; - }; - - // Initialize ciphers - QList ciphers = sslContext->sslConfiguration.ciphers(); - if (ciphers.isEmpty()) - ciphers = isDtls ? QTlsBackend::defaultDtlsCiphers() : QTlsBackend::defaultCiphers(); - - const QByteArray preTls13Ciphers = filterCiphers(ciphers, false); - - if (preTls13Ciphers.size()) { - if (!q_SSL_CTX_set_cipher_list(sslContext->ctx, preTls13Ciphers.data())) { - sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - } - - const QByteArray tls13Ciphers = filterCiphers(ciphers, true); -#ifdef TLS1_3_VERSION - if (tls13Ciphers.size()) { - if (!q_SSL_CTX_set_ciphersuites(sslContext->ctx, tls13Ciphers.data())) { - sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - } -#endif // TLS1_3_VERSION - if (!preTls13Ciphers.size() && !tls13Ciphers.size()) { - sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QStringLiteral("")); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - - const QDateTime now = QDateTime::currentDateTimeUtc(); - - // Add all our CAs to this store. - const auto caCertificates = sslContext->sslConfiguration.caCertificates(); - for (const QSslCertificate &caCertificate : caCertificates) { - // From https://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html: - // - // If several CA certificates matching the name, key identifier, and - // serial number condition are available, only the first one will be - // examined. This may lead to unexpected results if the same CA - // certificate is available with different expiration dates. If a - // ``certificate expired'' verification error occurs, no other - // certificate will be searched. Make sure to not have expired - // certificates mixed with valid ones. - // - // See also: QSslSocketBackendPrivate::verify() - if (caCertificate.expiryDate() >= now) { - q_X509_STORE_add_cert(q_SSL_CTX_get_cert_store(sslContext->ctx), (X509 *)caCertificate.handle()); - } - } - - if (QSslSocketPrivate::rootCertOnDemandLoadingSupported() && allowRootCertOnDemandLoading) { - // tell OpenSSL the directories where to look up the root certs on demand - const QList unixDirs = QSslSocketPrivate::unixRootCertDirectories(); - int success = 1; -#if OPENSSL_VERSION_MAJOR < 3 - for (const QByteArray &unixDir : unixDirs) { - if ((success = q_SSL_CTX_load_verify_locations(sslContext->ctx, nullptr, unixDir.constData())) != 1) - break; - } -#else - for (const QByteArray &unixDir : unixDirs) { - if ((success = q_SSL_CTX_load_verify_dir(sslContext->ctx, unixDir.constData())) != 1) - break; - } -#endif // OPENSSL_VERSION_MAJOR - if (success != 1) { - const auto qtErrors = QTlsBackendOpenSSL::getErrorsFromOpenSsl(); - qCWarning(lcSsl) << "An error encountered while to set root certificates location:" - << qtErrors; - } - } - - if (!sslContext->sslConfiguration.localCertificate().isNull()) { - // Require a private key as well. - if (sslContext->sslConfiguration.privateKey().isNull()) { - sslContext->errorStr = QSslSocket::tr("Cannot provide a certificate with no key"); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - - // Load certificate - if (!q_SSL_CTX_use_certificate(sslContext->ctx, (X509 *)sslContext->sslConfiguration.localCertificate().handle())) { - sslContext->errorStr = QSslSocket::tr("Error loading local certificate, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - - if (configuration.d->privateKey.algorithm() == QSsl::Opaque) { - sslContext->pkey = reinterpret_cast(configuration.d->privateKey.handle()); - } else { - // Load private key - sslContext->pkey = q_EVP_PKEY_new(); - // before we were using EVP_PKEY_assign_R* functions and did not use EVP_PKEY_free. - // this lead to a memory leak. Now we use the *_set1_* functions which do not - // take ownership of the RSA/DSA key instance because the QSslKey already has ownership. - if (configuration.d->privateKey.algorithm() == QSsl::Rsa) - q_EVP_PKEY_set1_RSA(sslContext->pkey, reinterpret_cast(configuration.d->privateKey.handle())); - else if (configuration.d->privateKey.algorithm() == QSsl::Dsa) - q_EVP_PKEY_set1_DSA(sslContext->pkey, reinterpret_cast(configuration.d->privateKey.handle())); -#ifndef OPENSSL_NO_EC - else if (configuration.d->privateKey.algorithm() == QSsl::Ec) - q_EVP_PKEY_set1_EC_KEY(sslContext->pkey, reinterpret_cast(configuration.d->privateKey.handle())); -#endif - } - auto pkey = sslContext->pkey; - if (configuration.d->privateKey.algorithm() == QSsl::Opaque) - sslContext->pkey = nullptr; // Don't free the private key, it belongs to QSslKey - - if (!q_SSL_CTX_use_PrivateKey(sslContext->ctx, pkey)) { - sslContext->errorStr = QSslSocket::tr("Error loading private key, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - - // Check if the certificate matches the private key. - if (!q_SSL_CTX_check_private_key(sslContext->ctx)) { - sslContext->errorStr = QSslSocket::tr("Private key does not certify public key, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - - // If we have any intermediate certificates then we need to add them to our chain - bool first = true; - for (const QSslCertificate &cert : qAsConst(configuration.d->localCertificateChain)) { - if (first) { - first = false; - continue; - } - q_SSL_CTX_ctrl(sslContext->ctx, SSL_CTRL_EXTRA_CHAIN_CERT, 0, - q_X509_dup(reinterpret_cast(cert.handle()))); - } - } - - // Initialize peer verification, different callbacks, TLS/DTLS verification first - // (note, all these set_some_callback do not have return value): - if (sslContext->sslConfiguration.peerVerifyMode() == QSslSocket::VerifyNone) { - q_SSL_CTX_set_verify(sslContext->ctx, SSL_VERIFY_NONE, nullptr); - } else { - auto verificationCallback = - #if QT_CONFIG(dtls) - isDtls ? dtlscallbacks::q_X509DtlsCallback : - #endif // dtls - QTlsPrivate::q_X509Callback; - - if (!isDtls && configuration.handshakeMustInterruptOnError()) - verificationCallback = QTlsPrivate::q_X509CallbackDirect; - - auto verificationMode = SSL_VERIFY_PEER; - if (!isDtls && sslContext->sslConfiguration.missingCertificateIsFatal()) - verificationMode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - - q_SSL_CTX_set_verify(sslContext->ctx, verificationMode, verificationCallback); - } - -#ifdef TLS1_3_VERSION - // NewSessionTicket callback: - if (mode == QSslSocket::SslClientMode && !isDtls) { - q_SSL_CTX_sess_set_new_cb(sslContext->ctx, q_ssl_sess_set_new_cb); - q_SSL_CTX_set_session_cache_mode(sslContext->ctx, SSL_SESS_CACHE_CLIENT); - } - -#endif // TLS1_3_VERSION - -#if QT_CONFIG(dtls) - // DTLS cookies: - if (mode == QSslSocket::SslServerMode && isDtls && configuration.dtlsCookieVerificationEnabled()) { - q_SSL_CTX_set_cookie_generate_cb(sslContext->ctx, dtlscallbacks::q_generate_cookie_callback); - q_SSL_CTX_set_cookie_verify_cb(sslContext->ctx, dtlscallbacks::q_verify_cookie_callback); - } -#endif // dtls - - // Set verification depth. - if (sslContext->sslConfiguration.peerVerifyDepth() != 0) - q_SSL_CTX_set_verify_depth(sslContext->ctx, sslContext->sslConfiguration.peerVerifyDepth()); - - // set persisted session if the user set it - if (!configuration.sessionTicket().isEmpty()) - sslContext->setSessionASN1(configuration.sessionTicket()); - - // Set temp DH params - QSslDiffieHellmanParameters dhparams = configuration.diffieHellmanParameters(); - - if (!dhparams.isValid()) { - sslContext->errorStr = QSslSocket::tr("Diffie-Hellman parameters are not valid"); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } - - if (!dhparams.isEmpty()) { - const QByteArray ¶ms = dhparams.d->derData; - const char *ptr = params.constData(); - DH *dh = q_d2i_DHparams(nullptr, reinterpret_cast(&ptr), - params.length()); - if (dh == nullptr) - qFatal("q_d2i_DHparams failed to convert QSslDiffieHellmanParameters to DER form"); - q_SSL_CTX_set_tmp_dh(sslContext->ctx, dh); - q_DH_free(dh); - } - -#ifndef OPENSSL_NO_PSK - if (!client) - q_SSL_CTX_use_psk_identity_hint(sslContext->ctx, sslContext->sslConfiguration.preSharedKeyIdentityHint().constData()); -#endif // !OPENSSL_NO_PSK - - const auto qcurves = sslContext->sslConfiguration.ellipticCurves(); - if (!qcurves.isEmpty()) { -#ifdef OPENSSL_NO_EC - sslContext->errorStr = msgErrorSettingEllipticCurves(QSslSocket::tr("OpenSSL version with disabled elliptic curves")); - sslContext->errorCode = QSslError::UnspecifiedError; - return; -#else - // Set the curves to be used. - std::vector curves; - curves.reserve(qcurves.size()); - for (const auto &sslCurve : qcurves) - curves.push_back(sslCurve.id); - if (!q_SSL_CTX_ctrl(sslContext->ctx, SSL_CTRL_SET_CURVES, long(curves.size()), &curves[0])) { - sslContext->errorStr = msgErrorSettingEllipticCurves(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); - sslContext->errorCode = QSslError::UnspecifiedError; - return; - } -#endif - } - - applyBackendConfig(sslContext); -} - -void QSslContext::applyBackendConfig(QSslContext *sslContext) -{ - const QMap &conf = sslContext->sslConfiguration.backendConfiguration(); - if (conf.isEmpty()) - return; - -#if QT_CONFIG(ocsp) - auto ocspResponsePos = conf.find("Qt-OCSP-response"); - if (ocspResponsePos != conf.end()) { - // This is our private, undocumented configuration option, existing only for - // the purpose of testing OCSP status responses. We don't even check this - // callback was set. If no - the test must fail. - q_SSL_CTX_set_tlsext_status_cb(sslContext->ctx, QTlsPrivate::qt_OCSP_status_server_callback); - if (conf.size() == 1) - return; - } -#endif // ocsp - - QSharedPointer cctx(q_SSL_CONF_CTX_new(), &q_SSL_CONF_CTX_free); - if (cctx) { - q_SSL_CONF_CTX_set_ssl_ctx(cctx.data(), sslContext->ctx); - q_SSL_CONF_CTX_set_flags(cctx.data(), SSL_CONF_FLAG_FILE); - - for (auto i = conf.constBegin(); i != conf.constEnd(); ++i) { - if (i.key() == "Qt-OCSP-response") // This never goes to SSL_CONF_cmd(). - continue; - - if (!i.value().canConvert(QMetaType(QMetaType::QByteArray))) { - sslContext->errorCode = QSslError::UnspecifiedError; - sslContext->errorStr = msgErrorSettingBackendConfig( - QSslSocket::tr("Expecting QByteArray for %1").arg( - QString::fromUtf8(i.key()))); - return; - } - - const QByteArray &value = i.value().toByteArray(); - const int result = q_SSL_CONF_cmd(cctx.data(), i.key().constData(), value.constData()); - if (result == 2) - continue; - - sslContext->errorCode = QSslError::UnspecifiedError; - switch (result) { - case 0: - sslContext->errorStr = msgErrorSettingBackendConfig( - QSslSocket::tr("An error occurred attempting to set %1 to %2").arg( - QString::fromUtf8(i.key()), QString::fromUtf8(value))); - return; - case 1: - sslContext->errorStr = msgErrorSettingBackendConfig( - QSslSocket::tr("Wrong value for %1 (%2)").arg( - QString::fromUtf8(i.key()), QString::fromUtf8(value))); - return; - default: - sslContext->errorStr = msgErrorSettingBackendConfig( - QSslSocket::tr("Unrecognized command %1 = %2").arg( - QString::fromUtf8(i.key()), QString::fromUtf8(value))); - return; - } - } - - if (q_SSL_CONF_CTX_finish(cctx.data()) == 0) { - sslContext->errorStr = msgErrorSettingBackendConfig(QSslSocket::tr("SSL_CONF_finish() failed")); - sslContext->errorCode = QSslError::UnspecifiedError; - } - } else { - sslContext->errorStr = msgErrorSettingBackendConfig(QSslSocket::tr("SSL_CONF_CTX_new() failed")); - sslContext->errorCode = QSslError::UnspecifiedError; - } -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qsslcontext_openssl_p.h b/src/network/ssl/qsslcontext_openssl_p.h deleted file mode 100644 index 4b343e7842..0000000000 --- a/src/network/ssl/qsslcontext_openssl_p.h +++ /dev/null @@ -1,132 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2014 BlackBerry Limited. All rights reserved. -** 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$ -** -****************************************************************************/ - - -#ifndef QSSLCONTEXT_OPENSSL_P_H -#define QSSLCONTEXT_OPENSSL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -#ifndef QT_NO_SSL - -class QSslContext -{ -public: - - ~QSslContext(); - - static QSslContext* fromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, - bool allowRootCertOnDemandLoading); - static QSharedPointer sharedFromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, - bool allowRootCertOnDemandLoading); - static QSharedPointer sharedFromPrivateConfiguration(QSslSocket::SslMode mode, QSslConfigurationPrivate *privConfiguration, - bool allowRootCertOnDemandLoading); - static long setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions); - - QSslError::SslError error() const; - QString errorString() const; - - SSL* createSsl(); - bool cacheSession(SSL*); // should be called when handshake completed - - QByteArray sessionASN1() const; - void setSessionASN1(const QByteArray &sessionASN1); - int sessionTicketLifeTimeHint() const; - -#ifndef OPENSSL_NO_NEXTPROTONEG - // must be public because we want to use it from an OpenSSL callback - struct NPNContext { - NPNContext() : data(nullptr), - len(0), - status(QSslConfiguration::NextProtocolNegotiationNone) - { } - unsigned char *data; - unsigned short len; - QSslConfiguration::NextProtocolNegotiationStatus status; - }; - NPNContext npnContext() const; -#endif // !OPENSSL_NO_NEXTPROTONEG - -protected: - QSslContext(); - friend class QSharedPointer; - -private: - static void initSslContext(QSslContext* sslContext, QSslSocket::SslMode mode, const QSslConfiguration &configuration, - bool allowRootCertOnDemandLoading); - static void applyBackendConfig(QSslContext *sslContext); - -private: - SSL_CTX* ctx; - EVP_PKEY *pkey; - SSL_SESSION *session; - QByteArray m_sessionASN1; - int m_sessionTicketLifeTimeHint; - QSslError::SslError errorCode; - QString errorStr; - QSslConfiguration sslConfiguration; -#ifndef OPENSSL_NO_NEXTPROTONEG - QByteArray m_supportedNPNVersions; - NPNContext m_npnContext; -#endif // !OPENSSL_NO_NEXTPROTONEG -}; - -#endif // QT_NO_SSL - -QT_END_NAMESPACE - -#endif // QSSLCONTEXT_OPENSSL_P_H diff --git a/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp b/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp deleted file mode 100644 index 8e0458de4e..0000000000 --- a/src/network/ssl/qssldiffiehellmanparameters_openssl.cpp +++ /dev/null @@ -1,230 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2015 Mikkel Krautz -** Copyright (C) 2016 Richard J. Moore -** 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$ -** -****************************************************************************/ - -#include "qsslsocket_openssl_symbols_p.h" -#include "qtlsbackend_openssl_p.h" -#include "qsslsocket_p.h" - -#include -#include -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -namespace { - -#ifdef OPENSSL_NO_DEPRECATED_3_0 - -int q_DH_check(DH *dh, int *status) -{ - // DH_check was first deprecated in OpenSSL 3.0.0, as low-level - // API; the EVP_PKEY family of functions was advised as an alternative. - // As of now EVP_PKEY_params_check ends up calling ... DH_check, - // which is good enough. - - Q_ASSERT(dh); - Q_ASSERT(status); - - EVP_PKEY *key = q_EVP_PKEY_new(); - if (!key) { - qCWarning(lcSsl, "EVP_PKEY_new failed"); - QTlsBackendOpenSSL::logAndClearErrorQueue(); - return 0; - } - const auto keyDeleter = qScopeGuard([key](){ - q_EVP_PKEY_free(key); - }); - if (!q_EVP_PKEY_set1_DH(key, dh)) { - qCWarning(lcTlsBackend, "EVP_PKEY_set1_DH failed"); - QTlsBackendOpenSSL::logAndClearErrorQueue(); - return 0; - } - - EVP_PKEY_CTX *keyCtx = q_EVP_PKEY_CTX_new(key, nullptr); - if (!keyCtx) { - qCWarning(lcTlsBackend, "EVP_PKEY_CTX_new failed"); - QTlsBackendOpenSSL::logAndClearErrorQueue(); - return 0; - } - const auto ctxDeleter = qScopeGuard([keyCtx]{ - q_EVP_PKEY_CTX_free(keyCtx); - }); - - const int result = q_EVP_PKEY_param_check(keyCtx); - QTlsBackendOpenSSL::logAndClearErrorQueue(); - // Note: unlike DH_check, we cannot obtain the 'status', - // if the 'result' is 0 (actually the result is 1 only - // if this 'status' was 0). We could probably check the - // errors from the error queue, but it's not needed anyway - // - see the 'isSafeDH' below, how it returns immediately - // on 0. - Q_UNUSED(status); - - return result; -} -#endif // OPENSSL_NO_DEPRECATED_3_0 - -bool isSafeDH(DH *dh) -{ - int status = 0; - int bad = 0; - - // TLSTODO: check it's needed or if supportsSsl() - // is enough. - QSslSocketPrivate::ensureInitialized(); - - // From https://wiki.openssl.org/index.php/Diffie-Hellman_parameters: - // - // The additional call to BN_mod_word(dh->p, 24) - // (and unmasking of DH_NOT_SUITABLE_GENERATOR) - // is performed to ensure your program accepts - // IETF group parameters. OpenSSL checks the prime - // is congruent to 11 when g = 2; while the IETF's - // primes are congruent to 23 when g = 2. - // Without the test, the IETF parameters would - // fail validation. For details, see Diffie-Hellman - // Parameter Check (when g = 2, must p mod 24 == 11?). - // Mark p < 1024 bits as unsafe. - if (q_DH_bits(dh) < 1024) - return false; - - if (q_DH_check(dh, &status) != 1) - return false; - - const BIGNUM *p = nullptr; - const BIGNUM *q = nullptr; - const BIGNUM *g = nullptr; - q_DH_get0_pqg(dh, &p, &q, &g); - - if (q_BN_is_word(const_cast(g), DH_GENERATOR_2)) { - const unsigned long residue = q_BN_mod_word(p, 24); - if (residue == 11 || residue == 23) - status &= ~DH_NOT_SUITABLE_GENERATOR; - } - - bad |= DH_CHECK_P_NOT_PRIME; - bad |= DH_CHECK_P_NOT_SAFE_PRIME; - bad |= DH_NOT_SUITABLE_GENERATOR; - - return !(status & bad); -} - -} // unnamed namespace - -int QTlsBackendOpenSSL::dhParametersFromDer(const QByteArray &der, QByteArray *derData) const -{ - Q_ASSERT(derData); - - if (der.isEmpty()) - return DHParams::InvalidInputDataError; - - const unsigned char *data = reinterpret_cast(der.data()); - const int len = der.size(); - - // TLSTODO: check it's needed (loading ciphers and certs in - // addition to the library!) - QSslSocketPrivate::ensureInitialized(); - - DH *dh = q_d2i_DHparams(nullptr, &data, len); - if (dh) { - const auto dhRaii = qScopeGuard([dh] {q_DH_free(dh);}); - - if (isSafeDH(dh)) - *derData = der; - else - return DHParams::UnsafeParametersError; - } else { - return DHParams::InvalidInputDataError; - } - - return DHParams::NoError; -} - -int QTlsBackendOpenSSL::dhParametersFromPem(const QByteArray &pem, QByteArray *data) const -{ - Q_ASSERT(data); - - if (pem.isEmpty()) - return DHParams::InvalidInputDataError; - - // TLSTODO: check it was not a cargo-cult programming in case of - // DH ... - QSslSocketPrivate::ensureInitialized(); - - BIO *bio = q_BIO_new_mem_buf(const_cast(pem.data()), pem.size()); - if (!bio) - return DHParams::InvalidInputDataError; - - const auto bioRaii = qScopeGuard([bio] - { - q_BIO_free(bio); - }); - - DH *dh = nullptr; - q_PEM_read_bio_DHparams(bio, &dh, nullptr, nullptr); - - if (dh) { - const auto dhGuard = qScopeGuard([dh] - { - q_DH_free(dh); - }); - - if (isSafeDH(dh)) { - char *buf = nullptr; - const int len = q_i2d_DHparams(dh, reinterpret_cast(&buf)); - if (len > 0) - *data = QByteArray(buf, len); - else - return DHParams::InvalidInputDataError; - } else { - return DHParams::UnsafeParametersError; - } - } else { - return DHParams::InvalidInputDataError; - } - - return DHParams::NoError; -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qsslkey_p.cpp b/src/network/ssl/qsslkey_p.cpp index 010f45fca9..8a5af25e75 100644 --- a/src/network/ssl/qsslkey_p.cpp +++ b/src/network/ssl/qsslkey_p.cpp @@ -57,12 +57,8 @@ #include "qssl_p.h" #include "qsslkey.h" #include "qsslkey_p.h" -#ifndef QT_NO_OPENSSL -#include "qsslsocket_openssl_symbols_p.h" -#endif #include "qsslsocket.h" #include "qsslsocket_p.h" -#include "qasn1element_p.h" #include "qtlsbackend_p.h" #include diff --git a/src/network/ssl/qsslkey_p.h b/src/network/ssl/qsslkey_p.h index f5895561b4..6fea1ad66a 100644 --- a/src/network/ssl/qsslkey_p.h +++ b/src/network/ssl/qsslkey_p.h @@ -73,8 +73,8 @@ public: using Cipher = QTlsPrivate::Cipher; - Q_AUTOTEST_EXPORT static QByteArray decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv); - Q_AUTOTEST_EXPORT static QByteArray encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv); + Q_NETWORK_EXPORT static QByteArray decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv); + Q_NETWORK_EXPORT static QByteArray encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv); std::unique_ptr backend; QAtomicInt ref; diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 546c26272d..003bbf0787 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -1573,7 +1573,7 @@ QList QSslSocket::availableBackends() from the list of available backends. \note When selecting a default backend implicitly, QSslSocket prefers - native backends, such as SecureTransport on Darwin, or Schannel on Windows. + the OpenSSL backend if available. \sa setActiveBackend(), availableBackends() */ diff --git a/src/network/ssl/qsslsocket_mac_shared.cpp b/src/network/ssl/qsslsocket_mac_shared.cpp deleted file mode 100644 index 837ac4a4f6..0000000000 --- a/src/network/ssl/qsslsocket_mac_shared.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2015 ownCloud Inc -** 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$ -** -****************************************************************************/ - -#include "qsslcertificate.h" - -#include - -#ifdef Q_OS_MACOS - -#include "qtlsbackend_p.h" - -#include - -#include - -#include -#include - -#endif // Q_OS_MACOS - -QT_BEGIN_NAMESPACE - -#ifdef Q_OS_MACOS -namespace { - -bool hasTrustedSslServerPolicy(SecPolicyRef policy, CFDictionaryRef props) { - QCFType policyProps = SecPolicyCopyProperties(policy); - // only accept certificates with policies for SSL server validation for now - if (CFEqual(CFDictionaryGetValue(policyProps, kSecPolicyOid), kSecPolicyAppleSSL)) { - CFBooleanRef policyClient; - if (CFDictionaryGetValueIfPresent(policyProps, kSecPolicyClient, reinterpret_cast(&policyClient)) && - CFEqual(policyClient, kCFBooleanTrue)) { - return false; // no client certs - } - if (!CFDictionaryContainsKey(props, kSecTrustSettingsResult)) { - // as per the docs, no trust settings result implies full trust - return true; - } - CFNumberRef number = static_cast(CFDictionaryGetValue(props, kSecTrustSettingsResult)); - SecTrustSettingsResult settingsResult; - CFNumberGetValue(number, kCFNumberSInt32Type, &settingsResult); - switch (settingsResult) { - case kSecTrustSettingsResultTrustRoot: - case kSecTrustSettingsResultTrustAsRoot: - return true; - default: - return false; - } - } - return false; -} - -bool isCaCertificateTrusted(SecCertificateRef cfCert, int domain) -{ - QCFType cfTrustSettings; - OSStatus status = SecTrustSettingsCopyTrustSettings(cfCert, SecTrustSettingsDomain(domain), &cfTrustSettings); - if (status == noErr) { - CFIndex size = CFArrayGetCount(cfTrustSettings); - // if empty, trust for everything (as per the Security Framework documentation) - if (size == 0) { - return true; - } else { - for (CFIndex i = 0; i < size; ++i) { - CFDictionaryRef props = static_cast(CFArrayGetValueAtIndex(cfTrustSettings, i)); - if (CFDictionaryContainsKey(props, kSecTrustSettingsPolicy)) { - if (hasTrustedSslServerPolicy((SecPolicyRef)CFDictionaryGetValue(props, kSecTrustSettingsPolicy), props)) - return true; - } - } - } - } else { - qCWarning(lcTlsBackend, "Error receiving trust for a CA certificate"); - } - return false; -} - -} // unnamed namespace -#endif // Q_OS_MACOS - -namespace QTlsPrivate { -QList systemCaCertificates() -{ - QList systemCerts; - // SecTrustSettingsCopyCertificates is not defined on iOS. -#ifdef Q_OS_MACOS - // iterate through all enum members, order: - // kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainSystem - for (int dom = kSecTrustSettingsDomainUser; dom <= int(kSecTrustSettingsDomainSystem); dom++) { - QCFType cfCerts; - OSStatus status = SecTrustSettingsCopyCertificates(SecTrustSettingsDomain(dom), &cfCerts); - if (status == noErr) { - const CFIndex size = CFArrayGetCount(cfCerts); - for (CFIndex i = 0; i < size; ++i) { - SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i); - QCFType derData = SecCertificateCopyData(cfCert); - if (isCaCertificateTrusted(cfCert, dom)) { - if (derData == nullptr) { - qCWarning(lcSsl, "Error retrieving a CA certificate from the system store"); - } else { - systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); - } - } - } - } - } -#endif - return systemCerts; -} -} // namespace QTlsPrivate - -QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl_android.cpp b/src/network/ssl/qsslsocket_openssl_android.cpp deleted file mode 100644 index 22884cb819..0000000000 --- a/src/network/ssl/qsslsocket_openssl_android.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -/**************************************************************************** -** -** In addition, as a special exception, the copyright holders listed above give -** permission to link the code of its release of Qt with the OpenSSL project's -** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the -** same license as the original version), and distribute the linked executables. -** -** You must comply with the GNU General Public License version 2 in all -** respects for all of the code used other than the "OpenSSL" code. If you -** modify this file, you may extend this exception to your version of the file, -** but you are not obligated to do so. If you do not wish to do so, delete -** this exception statement from your version of this file. -** -****************************************************************************/ - -#include "qsslsocket_p.h" -#include -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -QList fetchSslCertificateData() -{ - QList certificateData; - - QJniObject certificates = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", - "getSSLCertificates", - "()[[B"); - if (!certificates.isValid()) - return certificateData; - - QJniEnvironment env; - jobjectArray jcertificates = static_cast(certificates.object()); - const jint nCertificates = env->GetArrayLength(jcertificates); - certificateData.reserve(static_cast(nCertificates)); - - for (int i = 0; i < nCertificates; ++i) { - jbyteArray jCert = static_cast(env->GetObjectArrayElement(jcertificates, i)); - const uint sz = env->GetArrayLength(jCert); - jbyte *buffer = env->GetByteArrayElements(jCert, 0); - certificateData.append(QByteArray(reinterpret_cast(buffer), sz)); - - env->ReleaseByteArrayElements(jCert, buffer, JNI_ABORT); // don't copy back the elements - env->DeleteLocalRef(jCert); - } - - return certificateData; -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp deleted file mode 100644 index 82429800f8..0000000000 --- a/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ /dev/null @@ -1,1234 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Copyright (C) 2014 BlackBerry Limited. All rights reserved. -** Copyright (C) 2016 Richard J. Moore -** 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$ -** -****************************************************************************/ - -/**************************************************************************** -** -** In addition, as a special exception, the copyright holders listed above give -** permission to link the code of its release of Qt with the OpenSSL project's -** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the -** same license as the original version), and distribute the linked executables. -** -** You must comply with the GNU General Public License version 2 in all -** respects for all of the code used other than the "OpenSSL" code. If you -** modify this file, you may extend this exception to your version of the file, -** but you are not obligated to do so. If you do not wish to do so, delete -** this exception statement from your version of this file. -** -****************************************************************************/ - -#include "qssl_p.h" -#include "qsslsocket_openssl_symbols_p.h" - -#ifdef Q_OS_WIN -# include -#elif QT_CONFIG(library) -# include -#endif -#include -#include -#if defined(Q_OS_UNIX) -#include -#endif -#include -#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) -#include -#endif -#ifdef Q_OS_DARWIN -#include "private/qcore_mac_p.h" -#endif - -#include - -QT_BEGIN_NAMESPACE - -/* - Note to maintainer: - ------------------- - - We load OpenSSL symbols dynamically. Because symbols are known to - disappear, and signatures sometimes change, between releases, we need to - be careful about how this is done. To ensure we don't end up dereferencing - null function pointers, and continue running even if certain functions are - missing, we define helper functions for each of the symbols we load from - OpenSSL, all prefixed with "q_" (declared in - qsslsocket_openssl_symbols_p.h). So instead of calling SSL_connect - directly, we call q_SSL_connect, which is a function that checks if the - actual SSL_connect fptr is null, and returns a failure if it is, or calls - SSL_connect if it isn't. - - This requires a somewhat tedious process of declaring each function we - want to call in OpenSSL thrice: once with the q_, in _p.h, once using the - DEFINEFUNC macros below, and once in the function that actually resolves - the symbols, below the DEFINEFUNC declarations below. - - There's one DEFINEFUNC macro declared for every number of arguments - exposed by OpenSSL (feel free to extend when needed). The easiest thing to - do is to find an existing entry that matches the arg count of the function - you want to import, and do the same. - - The first macro arg is the function return type. The second is the - verbatim name of the function/symbol. Then follows a list of N pairs of - argument types with a variable name, and just the variable name (char *a, - a, char *b, b, etc). Finally there's two arguments - a suitable return - statement for the error case (for an int function, return 0 or return -1 - is usually right). Then either just "return" or DUMMYARG, the latter being - for void functions. - - Note: Take into account that these macros and declarations are processed - at compile-time, and the result depends on the OpenSSL headers the - compiling host has installed, but the symbols are resolved at run-time, - possibly with a different version of OpenSSL. -*/ - -#ifndef QT_LINKED_OPENSSL - -namespace { -void qsslSocketUnresolvedSymbolWarning(const char *functionName) -{ - qCWarning(lcSsl, "QSslSocket: cannot call unresolved function %s", functionName); -} - -#if QT_CONFIG(library) -void qsslSocketCannotResolveSymbolWarning(const char *functionName) -{ - qCWarning(lcSsl, "QSslSocket: cannot resolve %s", functionName); -} -#endif - -} - -#endif // QT_LINKED_OPENSSL - -DEFINEFUNC(const unsigned char *, ASN1_STRING_get0_data, const ASN1_STRING *a, a, return nullptr, return) -DEFINEFUNC2(int, OPENSSL_init_ssl, uint64_t opts, opts, const OPENSSL_INIT_SETTINGS *settings, settings, return 0, return) -DEFINEFUNC2(int, OPENSSL_init_crypto, uint64_t opts, opts, const OPENSSL_INIT_SETTINGS *settings, settings, return 0, return) -DEFINEFUNC(BIO *, BIO_new, const BIO_METHOD *a, a, return nullptr, return) -DEFINEFUNC(const BIO_METHOD *, BIO_s_mem, void, DUMMYARG, return nullptr, return) -DEFINEFUNC2(int, BN_is_word, BIGNUM *a, a, BN_ULONG w, w, return 0, return) -DEFINEFUNC(int, EVP_CIPHER_CTX_reset, EVP_CIPHER_CTX *c, c, return 0, return) -DEFINEFUNC(int, EVP_PKEY_up_ref, EVP_PKEY *a, a, return 0, return) -DEFINEFUNC2(EVP_PKEY_CTX *, EVP_PKEY_CTX_new, EVP_PKEY *pkey, pkey, ENGINE *e, e, return nullptr, return) -DEFINEFUNC(int, EVP_PKEY_param_check, EVP_PKEY_CTX *ctx, ctx, return 0, return) -DEFINEFUNC(void, EVP_PKEY_CTX_free, EVP_PKEY_CTX *ctx, ctx, return, return) -DEFINEFUNC(int, EVP_PKEY_base_id, EVP_PKEY *a, a, return NID_undef, return) -DEFINEFUNC(int, RSA_bits, RSA *a, a, return 0, return) -DEFINEFUNC(int, DSA_bits, DSA *a, a, return 0, return) -DEFINEFUNC(int, OPENSSL_sk_num, OPENSSL_STACK *a, a, return -1, return) -DEFINEFUNC2(void, OPENSSL_sk_pop_free, OPENSSL_STACK *a, a, void (*b)(void*), b, return, DUMMYARG) -DEFINEFUNC(OPENSSL_STACK *, OPENSSL_sk_new_null, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC2(void, OPENSSL_sk_push, OPENSSL_STACK *a, a, void *b, b, return, DUMMYARG) -DEFINEFUNC(void, OPENSSL_sk_free, OPENSSL_STACK *a, a, return, DUMMYARG) -DEFINEFUNC2(void *, OPENSSL_sk_value, OPENSSL_STACK *a, a, int b, b, return nullptr, return) -DEFINEFUNC(int, SSL_session_reused, SSL *a, a, return 0, return) -DEFINEFUNC2(unsigned long, SSL_CTX_set_options, SSL_CTX *ctx, ctx, unsigned long op, op, return 0, return) -using info_callback = void (*) (const SSL *ssl, int type, int val); -DEFINEFUNC2(void, SSL_set_info_callback, SSL *ssl, ssl, info_callback cb, cb, return, return) -DEFINEFUNC(const char *, SSL_alert_type_string, int value, value, return nullptr, return) -DEFINEFUNC(const char *, SSL_alert_desc_string_long, int value, value, return nullptr, return) -DEFINEFUNC(int, SSL_CTX_get_security_level, const SSL_CTX *ctx, ctx, return -1, return) -DEFINEFUNC2(void, SSL_CTX_set_security_level, SSL_CTX *ctx, ctx, int level, level, return, return) -#ifdef TLS1_3_VERSION -DEFINEFUNC2(int, SSL_CTX_set_ciphersuites, SSL_CTX *ctx, ctx, const char *str, str, return 0, return) -DEFINEFUNC2(void, SSL_set_psk_use_session_callback, SSL *ssl, ssl, q_SSL_psk_use_session_cb_func_t callback, callback, return, DUMMYARG) -DEFINEFUNC2(void, SSL_CTX_sess_set_new_cb, SSL_CTX *ctx, ctx, NewSessionCallback cb, cb, return, return) -DEFINEFUNC(int, SSL_SESSION_is_resumable, const SSL_SESSION *s, s, return 0, return) -#endif -DEFINEFUNC3(size_t, SSL_get_client_random, SSL *a, a, unsigned char *out, out, size_t outlen, outlen, return 0, return) -DEFINEFUNC3(size_t, SSL_SESSION_get_master_key, const SSL_SESSION *ses, ses, unsigned char *out, out, size_t outlen, outlen, return 0, return) -DEFINEFUNC6(int, CRYPTO_get_ex_new_index, int class_index, class_index, long argl, argl, void *argp, argp, CRYPTO_EX_new *new_func, new_func, CRYPTO_EX_dup *dup_func, dup_func, CRYPTO_EX_free *free_func, free_func, return -1, return) -DEFINEFUNC2(unsigned long, SSL_set_options, SSL *ssl, ssl, unsigned long op, op, return 0, return) - -DEFINEFUNC(const SSL_METHOD *, TLS_method, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(const SSL_METHOD *, TLS_client_method, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(const SSL_METHOD *, TLS_server_method, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(void, X509_up_ref, X509 *a, a, return, DUMMYARG) -DEFINEFUNC(ASN1_TIME *, X509_getm_notBefore, X509 *a, a, return nullptr, return) -DEFINEFUNC(ASN1_TIME *, X509_getm_notAfter, X509 *a, a, return nullptr, return) -DEFINEFUNC(long, X509_get_version, X509 *a, a, return -1, return) -DEFINEFUNC(EVP_PKEY *, X509_get_pubkey, X509 *a, a, return nullptr, return) -DEFINEFUNC2(void, X509_STORE_set_verify_cb, X509_STORE *a, a, X509_STORE_CTX_verify_cb verify_cb, verify_cb, return, DUMMYARG) -DEFINEFUNC3(int, X509_STORE_set_ex_data, X509_STORE *a, a, int idx, idx, void *data, data, return 0, return) -DEFINEFUNC2(void *, X509_STORE_get_ex_data, X509_STORE *r, r, int idx, idx, return nullptr, return) -DEFINEFUNC(STACK_OF(X509) *, X509_STORE_CTX_get0_chain, X509_STORE_CTX *a, a, return nullptr, return) -DEFINEFUNC3(void, CRYPTO_free, void *str, str, const char *file, file, int line, line, return, DUMMYARG) -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) -DEFINEFUNC(int, DH_bits, DH *dh, dh, return 0, return) - -#if QT_CONFIG(dtls) -DEFINEFUNC2(int, DTLSv1_listen, SSL *s, s, BIO_ADDR *c, c, return -1, return) -DEFINEFUNC(BIO_ADDR *, BIO_ADDR_new, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(void, BIO_ADDR_free, BIO_ADDR *ap, ap, return, DUMMYARG) -DEFINEFUNC2(BIO_METHOD *, BIO_meth_new, int type, type, const char *name, name, return nullptr, return) -DEFINEFUNC(void, BIO_meth_free, BIO_METHOD *biom, biom, return, DUMMYARG) -DEFINEFUNC2(int, BIO_meth_set_write, BIO_METHOD *biom, biom, DgramWriteCallback write, write, return 0, return) -DEFINEFUNC2(int, BIO_meth_set_read, BIO_METHOD *biom, biom, DgramReadCallback read, read, return 0, return) -DEFINEFUNC2(int, BIO_meth_set_puts, BIO_METHOD *biom, biom, DgramPutsCallback puts, puts, return 0, return) -DEFINEFUNC2(int, BIO_meth_set_ctrl, BIO_METHOD *biom, biom, DgramCtrlCallback ctrl, ctrl, return 0, return) -DEFINEFUNC2(int, BIO_meth_set_create, BIO_METHOD *biom, biom, DgramCreateCallback crt, crt, return 0, return) -DEFINEFUNC2(int, BIO_meth_set_destroy, BIO_METHOD *biom, biom, DgramDestroyCallback dtr, dtr, return 0, return) -#endif // dtls - -#if QT_CONFIG(ocsp) -DEFINEFUNC(const OCSP_CERTID *, OCSP_SINGLERESP_get0_id, const OCSP_SINGLERESP *x, x, return nullptr, return) -DEFINEFUNC3(OCSP_RESPONSE *, d2i_OCSP_RESPONSE, OCSP_RESPONSE **a, a, const unsigned char **in, in, long len, len, return nullptr, return) -DEFINEFUNC(void, OCSP_RESPONSE_free, OCSP_RESPONSE *rs, rs, return, DUMMYARG) -DEFINEFUNC(OCSP_BASICRESP *, OCSP_response_get1_basic, OCSP_RESPONSE *resp, resp, return nullptr, return) -DEFINEFUNC(void, OCSP_BASICRESP_free, OCSP_BASICRESP *bs, bs, return, DUMMYARG) -DEFINEFUNC(int, OCSP_response_status, OCSP_RESPONSE *resp, resp, return OCSP_RESPONSE_STATUS_INTERNALERROR, return) -DEFINEFUNC4(int, OCSP_basic_verify, OCSP_BASICRESP *bs, bs, STACK_OF(X509) *certs, certs, X509_STORE *st, st, unsigned long flags, flags, return -1, return) -DEFINEFUNC(int, OCSP_resp_count, OCSP_BASICRESP *bs, bs, return 0, return) -DEFINEFUNC2(OCSP_SINGLERESP *, OCSP_resp_get0, OCSP_BASICRESP *bs, bs, int idx, idx, return nullptr, return) -DEFINEFUNC5(int, OCSP_single_get0_status, OCSP_SINGLERESP *single, single, int *reason, reason, ASN1_GENERALIZEDTIME **revtime, revtime, - ASN1_GENERALIZEDTIME **thisupd, thisupd, ASN1_GENERALIZEDTIME **nextupd, nextupd, return -1, return) -DEFINEFUNC4(int, OCSP_check_validity, ASN1_GENERALIZEDTIME *thisupd, thisupd, ASN1_GENERALIZEDTIME *nextupd, nextupd, long nsec, nsec, long maxsec, maxsec, return 0, return) -DEFINEFUNC3(OCSP_CERTID *, OCSP_cert_to_id, const EVP_MD *dgst, dgst, X509 *subject, subject, X509 *issuer, issuer, return nullptr, return) -DEFINEFUNC(void, OCSP_CERTID_free, OCSP_CERTID *cid, cid, return, DUMMYARG) -DEFINEFUNC5(int, OCSP_id_get0_info, ASN1_OCTET_STRING **piNameHash, piNameHash, ASN1_OBJECT **pmd, pmd, - ASN1_OCTET_STRING **piKeyHash, piKeyHash, ASN1_INTEGER **pserial, pserial, OCSP_CERTID *cid, cid, - return 0, return) -DEFINEFUNC2(OCSP_RESPONSE *, OCSP_response_create, int status, status, OCSP_BASICRESP *bs, bs, return nullptr, return) -DEFINEFUNC(const STACK_OF(X509) *, OCSP_resp_get0_certs, const OCSP_BASICRESP *bs, bs, return nullptr, return) -DEFINEFUNC2(int, OCSP_id_cmp, OCSP_CERTID *a, a, OCSP_CERTID *b, b, return -1, return) -DEFINEFUNC7(OCSP_SINGLERESP *, OCSP_basic_add1_status, OCSP_BASICRESP *r, r, OCSP_CERTID *c, c, int s, s, - int re, re, ASN1_TIME *rt, rt, ASN1_TIME *t, t, ASN1_TIME *n, n, return nullptr, return) -DEFINEFUNC(OCSP_BASICRESP *, OCSP_BASICRESP_new, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC2(int, i2d_OCSP_RESPONSE, OCSP_RESPONSE *r, r, unsigned char **ppout, ppout, return 0, return) -DEFINEFUNC6(int, OCSP_basic_sign, OCSP_BASICRESP *br, br, X509 *signer, signer, EVP_PKEY *key, key, - const EVP_MD *dg, dg, STACK_OF(X509) *cs, cs, unsigned long flags, flags, return 0, return) -#endif // ocsp - -DEFINEFUNC2(void, BIO_set_data, BIO *a, a, void *ptr, ptr, return, DUMMYARG) -DEFINEFUNC(void *, BIO_get_data, BIO *a, a, return nullptr, return) -DEFINEFUNC2(void, BIO_set_init, BIO *a, a, int init, init, return, DUMMYARG) -DEFINEFUNC(int, BIO_get_shutdown, BIO *a, a, return -1, return) -DEFINEFUNC2(void, BIO_set_shutdown, BIO *a, a, int shut, shut, return, DUMMYARG) - -DEFINEFUNC(long, ASN1_INTEGER_get, ASN1_INTEGER *a, a, return 0, return) -DEFINEFUNC2(int, ASN1_INTEGER_cmp, const ASN1_INTEGER *a, a, const ASN1_INTEGER *b, b, return 1, return) -DEFINEFUNC(int, ASN1_STRING_length, ASN1_STRING *a, a, return 0, return) -DEFINEFUNC2(int, ASN1_STRING_to_UTF8, unsigned char **a, a, ASN1_STRING *b, b, return 0, return) -DEFINEFUNC2(int, ASN1_TIME_to_tm, const ASN1_TIME *s, s, struct tm *tm, tm, return 0, return) -DEFINEFUNC4(long, BIO_ctrl, BIO *a, a, int b, b, long c, c, void *d, d, return -1, return) -DEFINEFUNC(int, BIO_free, BIO *a, a, return 0, return) -DEFINEFUNC2(BIO *, BIO_new_mem_buf, void *a, a, int b, b, return nullptr, return) -DEFINEFUNC3(int, BIO_read, BIO *a, a, void *b, b, int c, c, return -1, return) - -DEFINEFUNC3(int, BIO_write, BIO *a, a, const void *b, b, int c, c, return -1, return) -DEFINEFUNC(int, BN_num_bits, const BIGNUM *a, a, return 0, return) -DEFINEFUNC2(BN_ULONG, BN_mod_word, const BIGNUM *a, a, BN_ULONG w, w, return static_cast(-1), return) -#ifndef OPENSSL_NO_EC -DEFINEFUNC(const EC_GROUP*, EC_KEY_get0_group, const EC_KEY* k, k, return nullptr, return) -DEFINEFUNC(int, EC_GROUP_get_degree, const EC_GROUP* g, g, return 0, return) -#endif -DEFINEFUNC(DSA *, DSA_new, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(void, DSA_free, DSA *a, a, return, DUMMYARG) -DEFINEFUNC3(X509 *, d2i_X509, X509 **a, a, const unsigned char **b, b, long c, c, return nullptr, return) -DEFINEFUNC2(char *, ERR_error_string, unsigned long a, a, char *b, b, return nullptr, return) -DEFINEFUNC3(void, ERR_error_string_n, unsigned long e, e, char *b, b, size_t len, len, return, DUMMYARG) -DEFINEFUNC(unsigned long, ERR_get_error, DUMMYARG, DUMMYARG, return 0, return) -DEFINEFUNC(EVP_CIPHER_CTX *, EVP_CIPHER_CTX_new, void, DUMMYARG, return nullptr, return) -DEFINEFUNC(void, EVP_CIPHER_CTX_free, EVP_CIPHER_CTX *a, a, return, DUMMYARG) -DEFINEFUNC4(int, EVP_CIPHER_CTX_ctrl, EVP_CIPHER_CTX *ctx, ctx, int type, type, int arg, arg, void *ptr, ptr, return 0, return) -DEFINEFUNC2(int, EVP_CIPHER_CTX_set_key_length, EVP_CIPHER_CTX *ctx, ctx, int keylen, keylen, return 0, return) -DEFINEFUNC5(int, EVP_CipherInit, EVP_CIPHER_CTX *ctx, ctx, const EVP_CIPHER *type, type, const unsigned char *key, key, const unsigned char *iv, iv, int enc, enc, return 0, return) -DEFINEFUNC6(int, EVP_CipherInit_ex, EVP_CIPHER_CTX *ctx, ctx, const EVP_CIPHER *cipher, cipher, ENGINE *impl, impl, const unsigned char *key, key, const unsigned char *iv, iv, int enc, enc, return 0, return) -DEFINEFUNC5(int, EVP_CipherUpdate, EVP_CIPHER_CTX *ctx, ctx, unsigned char *out, out, int *outl, outl, const unsigned char *in, in, int inl, inl, return 0, return) -DEFINEFUNC3(int, EVP_CipherFinal, EVP_CIPHER_CTX *ctx, ctx, unsigned char *out, out, int *outl, outl, return 0, return) -DEFINEFUNC(const EVP_MD *, EVP_get_digestbyname, const char *name, name, return nullptr, return) -#ifndef OPENSSL_NO_DES -DEFINEFUNC(const EVP_CIPHER *, EVP_des_cbc, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(const EVP_CIPHER *, EVP_des_ede3_cbc, DUMMYARG, DUMMYARG, return nullptr, return) -#endif -#ifndef OPENSSL_NO_RC2 -DEFINEFUNC(const EVP_CIPHER *, EVP_rc2_cbc, DUMMYARG, DUMMYARG, return nullptr, return) -#endif -#ifndef OPENSSL_NO_AES -DEFINEFUNC(const EVP_CIPHER *, EVP_aes_128_cbc, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(const EVP_CIPHER *, EVP_aes_192_cbc, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(const EVP_CIPHER *, EVP_aes_256_cbc, DUMMYARG, DUMMYARG, return nullptr, return) -#endif -DEFINEFUNC(const EVP_MD *, EVP_sha1, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC3(int, EVP_PKEY_assign, EVP_PKEY *a, a, int b, b, void *r, r, return -1, return) -DEFINEFUNC2(int, EVP_PKEY_set1_RSA, EVP_PKEY *a, a, RSA *b, b, return -1, return) -DEFINEFUNC2(int, EVP_PKEY_set1_DSA, EVP_PKEY *a, a, DSA *b, b, return -1, return) -DEFINEFUNC2(int, EVP_PKEY_set1_DH, EVP_PKEY *a, a, DH *b, b, return -1, return) -#ifndef OPENSSL_NO_EC -DEFINEFUNC2(int, EVP_PKEY_set1_EC_KEY, EVP_PKEY *a, a, EC_KEY *b, b, return -1, return) -#endif -DEFINEFUNC2(int, EVP_PKEY_cmp, const EVP_PKEY *a, a, const EVP_PKEY *b, b, return -1, return) -DEFINEFUNC(void, EVP_PKEY_free, EVP_PKEY *a, a, return, DUMMYARG) -DEFINEFUNC(DSA *, EVP_PKEY_get1_DSA, EVP_PKEY *a, a, return nullptr, return) -DEFINEFUNC(RSA *, EVP_PKEY_get1_RSA, EVP_PKEY *a, a, return nullptr, return) -DEFINEFUNC(DH *, EVP_PKEY_get1_DH, EVP_PKEY *a, a, return nullptr, return) -#ifndef OPENSSL_NO_EC -DEFINEFUNC(EC_KEY *, EVP_PKEY_get1_EC_KEY, EVP_PKEY *a, a, return nullptr, return) -#endif -DEFINEFUNC(EVP_PKEY *, EVP_PKEY_new, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(int, EVP_PKEY_type, int a, a, return NID_undef, return) -DEFINEFUNC2(int, i2d_X509, X509 *a, a, unsigned char **b, b, return -1, return) -DEFINEFUNC(const char *, OBJ_nid2sn, int a, a, return nullptr, return) -DEFINEFUNC(const char *, OBJ_nid2ln, int a, a, return nullptr, return) -DEFINEFUNC(int, OBJ_sn2nid, const char *s, s, return 0, return) -DEFINEFUNC(int, OBJ_ln2nid, const char *s, s, return 0, return) -DEFINEFUNC3(int, i2t_ASN1_OBJECT, char *a, a, int b, b, ASN1_OBJECT *c, c, return -1, return) -DEFINEFUNC4(int, OBJ_obj2txt, char *a, a, int b, b, ASN1_OBJECT *c, c, int d, d, return -1, return) -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(DSA *, PEM_read_bio_DSAPrivateKey, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) -DEFINEFUNC4(RSA *, PEM_read_bio_RSAPrivateKey, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) - -#ifndef OPENSSL_NO_EC -DEFINEFUNC4(EC_KEY *, PEM_read_bio_ECPrivateKey, BIO *a, a, EC_KEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) -DEFINEFUNC7(int, PEM_write_bio_ECPrivateKey, BIO *a, a, EC_KEY *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(EC_KEY *, PEM_read_bio_EC_PUBKEY, BIO *a, a, EC_KEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) -DEFINEFUNC2(int, PEM_write_bio_EC_PUBKEY, BIO *a, a, EC_KEY *b, b, return 0, return) -#endif // OPENSSL_NO_EC - -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_DSAPrivateKey, BIO *a, a, DSA *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_RSAPrivateKey, BIO *a, a, RSA *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, 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) -DEFINEFUNC4(DSA *, PEM_read_bio_DSA_PUBKEY, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) -DEFINEFUNC4(RSA *, PEM_read_bio_RSA_PUBKEY, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) -DEFINEFUNC2(int, PEM_write_bio_DSA_PUBKEY, BIO *a, a, DSA *b, b, return 0, return) -DEFINEFUNC2(int, PEM_write_bio_RSA_PUBKEY, BIO *a, a, RSA *b, b, return 0, return) -DEFINEFUNC2(int, PEM_write_bio_PUBKEY, BIO *a, a, EVP_PKEY *b, b, return 0, return) -DEFINEFUNC2(void, RAND_seed, const void *a, a, int b, b, return, DUMMYARG) -DEFINEFUNC(int, RAND_status, void, DUMMYARG, return -1, return) -DEFINEFUNC2(int, RAND_bytes, unsigned char *b, b, int n, n, return 0, return) -DEFINEFUNC(RSA *, RSA_new, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(void, RSA_free, RSA *a, a, return, DUMMYARG) -DEFINEFUNC(int, SSL_accept, SSL *a, a, return -1, return) -DEFINEFUNC(int, SSL_clear, SSL *a, a, return -1, return) -DEFINEFUNC3(char *, SSL_CIPHER_description, const SSL_CIPHER *a, a, char *b, b, int c, c, return nullptr, return) -DEFINEFUNC2(int, SSL_CIPHER_get_bits, const SSL_CIPHER *a, a, int *b, b, return 0, return) -DEFINEFUNC(BIO *, SSL_get_rbio, const SSL *s, s, return nullptr, return) -DEFINEFUNC(int, SSL_connect, SSL *a, a, return -1, return) -DEFINEFUNC(int, SSL_CTX_check_private_key, const SSL_CTX *a, a, return -1, return) -DEFINEFUNC4(long, SSL_CTX_ctrl, SSL_CTX *a, a, int b, b, long c, c, void *d, d, return -1, return) -DEFINEFUNC(void, SSL_CTX_free, SSL_CTX *a, a, return, DUMMYARG) -DEFINEFUNC(SSL_CTX *, SSL_CTX_new, const SSL_METHOD *a, a, return nullptr, return) -DEFINEFUNC2(int, SSL_CTX_set_cipher_list, SSL_CTX *a, a, const char *b, b, return -1, return) -DEFINEFUNC3(long, SSL_CTX_callback_ctrl, SSL_CTX *ctx, ctx, int dst, dst, GenericCallbackType cb, cb, return 0, return) -DEFINEFUNC(int, SSL_CTX_set_default_verify_paths, SSL_CTX *a, a, return -1, return) -DEFINEFUNC3(void, SSL_CTX_set_verify, SSL_CTX *a, a, int b, b, int (*c)(int, X509_STORE_CTX *), c, return, DUMMYARG) -DEFINEFUNC2(void, SSL_CTX_set_verify_depth, SSL_CTX *a, a, int b, b, return, DUMMYARG) -DEFINEFUNC2(int, SSL_CTX_use_certificate, SSL_CTX *a, a, X509 *b, b, return -1, return) -DEFINEFUNC3(int, SSL_CTX_use_certificate_file, SSL_CTX *a, a, const char *b, b, int c, c, return -1, return) -DEFINEFUNC2(int, SSL_CTX_use_PrivateKey, SSL_CTX *a, a, EVP_PKEY *b, b, return -1, return) -DEFINEFUNC2(int, SSL_CTX_use_RSAPrivateKey, SSL_CTX *a, a, RSA *b, b, return -1, return) -DEFINEFUNC3(int, SSL_CTX_use_PrivateKey_file, SSL_CTX *a, a, const char *b, b, int c, c, return -1, return) -DEFINEFUNC(X509_STORE *, SSL_CTX_get_cert_store, const SSL_CTX *a, a, return nullptr, return) -DEFINEFUNC(SSL_CONF_CTX *, SSL_CONF_CTX_new, DUMMYARG, DUMMYARG, return nullptr, return); -DEFINEFUNC(void, SSL_CONF_CTX_free, SSL_CONF_CTX *a, a, return ,return); -DEFINEFUNC2(void, SSL_CONF_CTX_set_ssl_ctx, SSL_CONF_CTX *a, a, SSL_CTX *b, b, return, return); -DEFINEFUNC2(unsigned int, SSL_CONF_CTX_set_flags, SSL_CONF_CTX *a, a, unsigned int b, b, return 0, return); -DEFINEFUNC(int, SSL_CONF_CTX_finish, SSL_CONF_CTX *a, a, return 0, return); -DEFINEFUNC3(int, SSL_CONF_cmd, SSL_CONF_CTX *a, a, const char *b, b, const char *c, c, return 0, return); -DEFINEFUNC(void, SSL_free, SSL *a, a, return, DUMMYARG) -DEFINEFUNC(STACK_OF(SSL_CIPHER) *, SSL_get_ciphers, const SSL *a, a, return nullptr, return) -DEFINEFUNC(const SSL_CIPHER *, SSL_get_current_cipher, SSL *a, a, return nullptr, return) -DEFINEFUNC(int, SSL_version, const SSL *a, a, return 0, return) -DEFINEFUNC2(int, SSL_get_error, SSL *a, a, int b, b, return -1, return) -DEFINEFUNC(STACK_OF(X509) *, SSL_get_peer_cert_chain, SSL *a, a, return nullptr, return) -DEFINEFUNC(X509 *, SSL_get_peer_certificate, SSL *a, a, return nullptr, return) -DEFINEFUNC(long, SSL_get_verify_result, const SSL *a, a, return -1, return) -DEFINEFUNC(SSL *, SSL_new, SSL_CTX *a, a, return nullptr, return) -DEFINEFUNC(SSL_CTX *, SSL_get_SSL_CTX, SSL *a, a, return nullptr, return) -DEFINEFUNC4(long, SSL_ctrl, SSL *a, a, int cmd, cmd, long larg, larg, void *parg, parg, return -1, return) -DEFINEFUNC3(int, SSL_read, SSL *a, a, void *b, b, int c, c, return -1, return) -DEFINEFUNC3(void, SSL_set_bio, SSL *a, a, BIO *b, b, BIO *c, c, return, DUMMYARG) -DEFINEFUNC(void, SSL_set_accept_state, SSL *a, a, return, DUMMYARG) -DEFINEFUNC(void, SSL_set_connect_state, SSL *a, a, return, DUMMYARG) -DEFINEFUNC(int, SSL_shutdown, SSL *a, a, return -1, return) -DEFINEFUNC(int, SSL_in_init, const SSL *a, a, return 0, return) -DEFINEFUNC(int, SSL_get_shutdown, const SSL *ssl, ssl, return 0, return) -DEFINEFUNC2(int, SSL_set_session, SSL* to, to, SSL_SESSION *session, session, return -1, return) -DEFINEFUNC(void, SSL_SESSION_free, SSL_SESSION *ses, ses, return, DUMMYARG) -DEFINEFUNC(SSL_SESSION*, SSL_get1_session, SSL *ssl, ssl, return nullptr, return) -DEFINEFUNC(SSL_SESSION*, SSL_get_session, const SSL *ssl, ssl, return nullptr, return) -DEFINEFUNC3(int, SSL_set_ex_data, SSL *ssl, ssl, int idx, idx, void *arg, arg, return 0, return) -DEFINEFUNC2(void *, SSL_get_ex_data, const SSL *ssl, ssl, int idx, idx, return nullptr, return) - -#ifndef OPENSSL_NO_PSK -DEFINEFUNC2(void, SSL_set_psk_client_callback, SSL* ssl, ssl, q_psk_client_callback_t callback, callback, return, DUMMYARG) -DEFINEFUNC2(void, SSL_set_psk_server_callback, SSL* ssl, ssl, q_psk_server_callback_t callback, callback, return, DUMMYARG) -DEFINEFUNC2(int, SSL_CTX_use_psk_identity_hint, SSL_CTX* ctx, ctx, const char *hint, hint, return 0, return) -#endif // !OPENSSL_NO_PSK - -DEFINEFUNC3(int, SSL_write, SSL *a, a, const void *b, b, int c, c, return -1, return) -DEFINEFUNC2(int, X509_cmp, X509 *a, a, X509 *b, b, return -1, return) -DEFINEFUNC4(int, X509_digest, const X509 *x509, x509, const EVP_MD *type, type, unsigned char *md, md, unsigned int *len, len, return -1, return) -DEFINEFUNC(X509 *, X509_dup, X509 *a, a, return nullptr, return) -DEFINEFUNC2(void, X509_print, BIO *a, a, X509 *b, b, return, DUMMYARG); -DEFINEFUNC(ASN1_OBJECT *, X509_EXTENSION_get_object, X509_EXTENSION *a, a, return nullptr, return) -DEFINEFUNC(void, X509_free, X509 *a, a, return, DUMMYARG) -//Q_AUTOTEST_EXPORT ASN1_TIME *q_X509_gmtime_adj(ASN1_TIME *s, long adj); -DEFINEFUNC2(ASN1_TIME *, X509_gmtime_adj, ASN1_TIME *s, s, long adj, adj, return nullptr, return) -DEFINEFUNC(void, ASN1_TIME_free, ASN1_TIME *t, t, return, DUMMYARG) -DEFINEFUNC2(X509_EXTENSION *, X509_get_ext, X509 *a, a, int b, b, return nullptr, return) -DEFINEFUNC(int, X509_get_ext_count, X509 *a, a, return 0, return) -DEFINEFUNC4(void *, X509_get_ext_d2i, X509 *a, a, int b, b, int *c, c, int *d, d, return nullptr, return) -DEFINEFUNC(const X509V3_EXT_METHOD *, X509V3_EXT_get, X509_EXTENSION *a, a, return nullptr, return) -DEFINEFUNC(void *, X509V3_EXT_d2i, X509_EXTENSION *a, a, return nullptr, return) -DEFINEFUNC(int, X509_EXTENSION_get_critical, X509_EXTENSION *a, a, return 0, return) -DEFINEFUNC(ASN1_OCTET_STRING *, X509_EXTENSION_get_data, X509_EXTENSION *a, a, return nullptr, return) -DEFINEFUNC(void, BASIC_CONSTRAINTS_free, BASIC_CONSTRAINTS *a, a, return, DUMMYARG) -DEFINEFUNC(void, AUTHORITY_KEYID_free, AUTHORITY_KEYID *a, a, return, DUMMYARG) -DEFINEFUNC(void, GENERAL_NAME_free, GENERAL_NAME *a, a, return, DUMMYARG) -DEFINEFUNC2(int, ASN1_STRING_print, BIO *a, a, const ASN1_STRING *b, b, return 0, return) -DEFINEFUNC2(int, X509_check_issued, X509 *a, a, X509 *b, b, return -1, return) -DEFINEFUNC(X509_NAME *, X509_get_issuer_name, X509 *a, a, return nullptr, return) -DEFINEFUNC(X509_NAME *, X509_get_subject_name, X509 *a, a, return nullptr, return) -DEFINEFUNC(ASN1_INTEGER *, X509_get_serialNumber, X509 *a, a, return nullptr, return) -DEFINEFUNC(int, X509_verify_cert, X509_STORE_CTX *a, a, return -1, return) -DEFINEFUNC(int, X509_NAME_entry_count, X509_NAME *a, a, return 0, return) -DEFINEFUNC2(X509_NAME_ENTRY *, X509_NAME_get_entry, X509_NAME *a, a, int b, b, return nullptr, return) -DEFINEFUNC(ASN1_STRING *, X509_NAME_ENTRY_get_data, X509_NAME_ENTRY *a, a, return nullptr, return) -DEFINEFUNC(ASN1_OBJECT *, X509_NAME_ENTRY_get_object, X509_NAME_ENTRY *a, a, return nullptr, return) -DEFINEFUNC(EVP_PKEY *, X509_PUBKEY_get, X509_PUBKEY *a, a, return nullptr, return) -DEFINEFUNC(void, X509_STORE_free, X509_STORE *a, a, return, DUMMYARG) -DEFINEFUNC(X509_STORE *, X509_STORE_new, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC2(int, X509_STORE_add_cert, X509_STORE *a, a, X509 *b, b, return 0, return) -DEFINEFUNC(void, X509_STORE_CTX_free, X509_STORE_CTX *a, a, return, DUMMYARG) -DEFINEFUNC4(int, X509_STORE_CTX_init, X509_STORE_CTX *a, a, X509_STORE *b, b, X509 *c, c, STACK_OF(X509) *d, d, return -1, return) -DEFINEFUNC2(int, X509_STORE_CTX_set_purpose, X509_STORE_CTX *a, a, int b, b, return -1, return) -DEFINEFUNC(int, X509_STORE_CTX_get_error, X509_STORE_CTX *a, a, return -1, return) -DEFINEFUNC(int, X509_STORE_CTX_get_error_depth, X509_STORE_CTX *a, a, return -1, return) -DEFINEFUNC(X509 *, X509_STORE_CTX_get_current_cert, X509_STORE_CTX *a, a, return nullptr, return) -DEFINEFUNC(X509_STORE *, X509_STORE_CTX_get0_store, X509_STORE_CTX *ctx, ctx, return nullptr, return) -DEFINEFUNC(X509_STORE_CTX *, X509_STORE_CTX_new, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC2(void *, X509_STORE_CTX_get_ex_data, X509_STORE_CTX *ctx, ctx, int idx, idx, return nullptr, return) -DEFINEFUNC(int, SSL_get_ex_data_X509_STORE_CTX_idx, DUMMYARG, DUMMYARG, return -1, return) - -#if OPENSSL_VERSION_MAJOR < 3 -DEFINEFUNC3(int, SSL_CTX_load_verify_locations, SSL_CTX *ctx, ctx, const char *CAfile, CAfile, const char *CApath, CApath, return 0, return) -#else -DEFINEFUNC2(int, SSL_CTX_load_verify_dir, SSL_CTX *ctx, ctx, const char *CApath, CApath, return 0, return) -#endif // OPENSSL_VERSION_MAJOR - -DEFINEFUNC2(int, i2d_SSL_SESSION, SSL_SESSION *in, in, unsigned char **pp, pp, return 0, return) -DEFINEFUNC3(SSL_SESSION *, d2i_SSL_SESSION, SSL_SESSION **a, a, const unsigned char **pp, pp, long length, length, return nullptr, return) - -#ifndef OPENSSL_NO_NEXTPROTONEG -DEFINEFUNC6(int, SSL_select_next_proto, unsigned char **out, out, unsigned char *outlen, outlen, - const unsigned char *in, in, unsigned int inlen, inlen, - const unsigned char *client, client, unsigned int client_len, client_len, - return -1, return) -DEFINEFUNC3(void, SSL_CTX_set_next_proto_select_cb, SSL_CTX *s, s, - int (*cb) (SSL *ssl, unsigned char **out, - unsigned char *outlen, - const unsigned char *in, - unsigned int inlen, void *arg), cb, - void *arg, arg, return, DUMMYARG) -DEFINEFUNC3(void, SSL_get0_next_proto_negotiated, const SSL *s, s, - const unsigned char **data, data, unsigned *len, len, return, DUMMYARG) -DEFINEFUNC3(int, SSL_set_alpn_protos, SSL *s, s, const unsigned char *protos, protos, - unsigned protos_len, protos_len, return -1, return) -DEFINEFUNC3(void, SSL_CTX_set_alpn_select_cb, SSL_CTX *s, s, - int (*cb) (SSL *ssl, const unsigned char **out, - unsigned char *outlen, - const unsigned char *in, - unsigned int inlen, void *arg), cb, - void *arg, arg, return, DUMMYARG) -DEFINEFUNC3(void, SSL_get0_alpn_selected, const SSL *s, s, const unsigned char **data, data, - unsigned *len, len, return, DUMMYARG) -#endif // !OPENSSL_NO_NEXTPROTONEG - -// DTLS: -#if QT_CONFIG(dtls) -DEFINEFUNC2(void, SSL_CTX_set_cookie_generate_cb, SSL_CTX *ctx, ctx, CookieGenerateCallback cb, cb, return, DUMMYARG) -DEFINEFUNC2(void, SSL_CTX_set_cookie_verify_cb, SSL_CTX *ctx, ctx, CookieVerifyCallback cb, cb, return, DUMMYARG) -DEFINEFUNC(const SSL_METHOD *, DTLS_server_method, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(const SSL_METHOD *, DTLS_client_method, DUMMYARG, DUMMYARG, return nullptr, return) -#endif // dtls -DEFINEFUNC2(void, BIO_set_flags, BIO *b, b, int flags, flags, return, DUMMYARG) -DEFINEFUNC2(void, BIO_clear_flags, BIO *b, b, int flags, flags, return, DUMMYARG) -DEFINEFUNC2(void *, BIO_get_ex_data, BIO *b, b, int idx, idx, return nullptr, return) -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) -DEFINEFUNC(DH *, DH_new, DUMMYARG, DUMMYARG, return nullptr, return) -DEFINEFUNC(void, DH_free, DH *dh, dh, 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) -#ifndef OPENSSL_NO_DEPRECATED_3_0 -DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return) -#endif // OPENSSL_NO_DEPRECATED_3_0 -DEFINEFUNC3(BIGNUM *, BN_bin2bn, const unsigned char *s, s, int len, len, BIGNUM *ret, ret, return nullptr, return) - -#ifndef OPENSSL_NO_EC -DEFINEFUNC(EC_KEY *, EC_KEY_dup, const EC_KEY *ec, ec, return nullptr, return) -DEFINEFUNC(EC_KEY *, EC_KEY_new_by_curve_name, int nid, nid, return nullptr, return) -DEFINEFUNC(void, EC_KEY_free, EC_KEY *ecdh, ecdh, return, DUMMYARG) -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) -#endif // OPENSSL_NO_EC - -DEFINEFUNC5(int, PKCS12_parse, PKCS12 *p12, p12, const char *pass, pass, EVP_PKEY **pkey, pkey, \ - X509 **cert, cert, STACK_OF(X509) **ca, ca, return 1, return); -DEFINEFUNC2(PKCS12 *, d2i_PKCS12_bio, BIO *bio, bio, PKCS12 **pkcs12, pkcs12, return nullptr, return); -DEFINEFUNC(void, PKCS12_free, PKCS12 *pkcs12, pkcs12, return, DUMMYARG) - -#define RESOLVEFUNC(func) \ - if (!(_q_##func = _q_PTR_##func(libs.ssl->resolve(#func))) \ - && !(_q_##func = _q_PTR_##func(libs.crypto->resolve(#func)))) \ - qsslSocketCannotResolveSymbolWarning(#func); - -#if !defined QT_LINKED_OPENSSL - -#if !QT_CONFIG(library) -bool q_resolveOpenSslSymbols() -{ - qCWarning(lcSsl, "QSslSocket: unable to resolve symbols. Qt is configured without the " - "'library' feature, which means runtime resolving of libraries won't work."); - qCWarning(lcSsl, "Either compile Qt statically or with support for runtime resolving " - "of libraries."); - return false; -} -#else - -# ifdef Q_OS_UNIX -struct NumericallyLess -{ - typedef bool result_type; - result_type operator()(QStringView lhs, QStringView rhs) const - { - bool ok = false; - int b = 0; - int a = lhs.toInt(&ok); - if (ok) - b = rhs.toInt(&ok); - if (ok) { - // both toInt succeeded - return a < b; - } else { - // compare as strings; - return lhs < rhs; - } - } -}; - -struct LibGreaterThan -{ - typedef bool result_type; - result_type operator()(QStringView lhs, QStringView rhs) const - { - const auto lhsparts = lhs.split(QLatin1Char('.')); - const auto rhsparts = rhs.split(QLatin1Char('.')); - Q_ASSERT(lhsparts.count() > 1 && rhsparts.count() > 1); - - // note: checking rhs < lhs, the same as lhs > rhs - return std::lexicographical_compare(rhsparts.begin() + 1, rhsparts.end(), - lhsparts.begin() + 1, lhsparts.end(), - NumericallyLess()); - } -}; - -#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) -static int dlIterateCallback(struct dl_phdr_info *info, size_t size, void *data) -{ - if (size < sizeof (info->dlpi_addr) + sizeof (info->dlpi_name)) - return 1; - QDuplicateTracker *paths = (QDuplicateTracker *)data; - QString path = QString::fromLocal8Bit(info->dlpi_name); - if (!path.isEmpty()) { - QFileInfo fi(path); - path = fi.absolutePath(); - if (!path.isEmpty()) - (void)paths->hasSeen(std::move(path)); - } - return 0; -} -#endif - -static QStringList libraryPathList() -{ - QStringList paths; -# ifdef Q_OS_DARWIN - paths = QString::fromLatin1(qgetenv("DYLD_LIBRARY_PATH")) - .split(QLatin1Char(':'), Qt::SkipEmptyParts); - - // search in .app/Contents/Frameworks - UInt32 packageType; - CFBundleGetPackageInfo(CFBundleGetMainBundle(), &packageType, nullptr); - if (packageType == FOUR_CHAR_CODE('APPL')) { - QUrl bundleUrl = QUrl::fromCFURL(QCFType(CFBundleCopyBundleURL(CFBundleGetMainBundle()))); - QUrl frameworksUrl = QUrl::fromCFURL(QCFType(CFBundleCopyPrivateFrameworksURL(CFBundleGetMainBundle()))); - paths << bundleUrl.resolved(frameworksUrl).path(); - } -# else - paths = QString::fromLatin1(qgetenv("LD_LIBRARY_PATH")) - .split(QLatin1Char(':'), Qt::SkipEmptyParts); -# endif - paths << QLatin1String("/lib") << QLatin1String("/usr/lib") << QLatin1String("/usr/local/lib"); - paths << QLatin1String("/lib64") << QLatin1String("/usr/lib64") << QLatin1String("/usr/local/lib64"); - paths << QLatin1String("/lib32") << QLatin1String("/usr/lib32") << QLatin1String("/usr/local/lib32"); - -#if defined(Q_OS_ANDROID) - paths << QLatin1String("/system/lib"); -#elif defined(Q_OS_LINUX) - // discover paths of already loaded libraries - QDuplicateTracker loadedPaths; - dl_iterate_phdr(dlIterateCallback, &loadedPaths); - std::move(loadedPaths).appendTo(paths); -#endif - - return paths; -} - -Q_NEVER_INLINE -static QStringList findAllLibs(QLatin1String filter) -{ - const QStringList paths = libraryPathList(); - QStringList found; - const QStringList filters((QString(filter))); - - for (const QString &path : paths) { - QDir dir(path); - QStringList entryList = dir.entryList(filters, QDir::Files); - - std::sort(entryList.begin(), entryList.end(), LibGreaterThan()); - for (const QString &entry : qAsConst(entryList)) - found << path + QLatin1Char('/') + entry; - } - - return found; -} - -static QStringList findAllLibSsl() -{ - return findAllLibs(QLatin1String("libssl.*")); -} - -static QStringList findAllLibCrypto() -{ - return findAllLibs(QLatin1String("libcrypto.*")); -} -# endif - -#ifdef Q_OS_WIN - -struct LoadedOpenSsl { - std::unique_ptr ssl, crypto; -}; - -static bool tryToLoadOpenSslWin32Library(QLatin1String ssleay32LibName, QLatin1String libeay32LibName, LoadedOpenSsl &result) -{ - auto ssleay32 = std::make_unique(ssleay32LibName); - if (!ssleay32->load(false)) { - return FALSE; - } - - auto libeay32 = std::make_unique(libeay32LibName); - if (!libeay32->load(false)) { - return FALSE; - } - - result.ssl = std::move(ssleay32); - result.crypto = std::move(libeay32); - return TRUE; -} - -static LoadedOpenSsl loadOpenSsl() -{ - LoadedOpenSsl result; - - // With OpenSSL 1.1 the names have changed to libssl-1_1 and libcrypto-1_1 for builds using - // MSVC and GCC, with architecture suffixes for non-x86 builds. - -#if defined(Q_PROCESSOR_X86_64) -#define QT_SSL_SUFFIX "-x64" -#elif defined(Q_PROCESSOR_ARM_64) -#define QT_SSL_SUFFIX "-arm64" -#elif defined(Q_PROCESSOR_ARM_32) -#define QT_SSL_SUFFIX "-arm" -#else -#define QT_SSL_SUFFIX -#endif - - tryToLoadOpenSslWin32Library(QLatin1String("libssl-1_1" QT_SSL_SUFFIX), - QLatin1String("libcrypto-1_1" QT_SSL_SUFFIX), result); - -#undef QT_SSL_SUFFIX - return result; -} -#else - -struct LoadedOpenSsl { - std::unique_ptr ssl, crypto; -}; - -static LoadedOpenSsl loadOpenSsl() -{ - LoadedOpenSsl result = { std::make_unique(), std::make_unique() }; - -# if defined(Q_OS_UNIX) - QLibrary * const libssl = result.ssl.get(); - QLibrary * const libcrypto = result.crypto.get(); - - // Try to find the libssl library on the system. - // - // Up until Qt 4.3, this only searched for the "ssl" library at version -1, that - // is, libssl.so on most Unix systems. However, the .so file isn't present in - // user installations because it's considered a development file. - // - // The right thing to do is to load the library at the major version we know how - // to work with: the SHLIB_VERSION_NUMBER version (macro defined in opensslv.h) - // - // However, OpenSSL is a well-known case of binary-compatibility breakage. To - // avoid such problems, many system integrators and Linux distributions change - // the soname of the binary, letting the full version number be the soname. So - // we'll find libssl.so.0.9.7, libssl.so.0.9.8, etc. in the system. For that - // reason, we will search a few common paths (see findAllLibSsl() above) in hopes - // we find one that works. - // - // If that fails, for OpenSSL 1.0 we also try some fallbacks -- look up - // libssl.so with a hardcoded soname. The reason is QTBUG-68156: the binary - // builds of Qt happen (at the time of this writing) on RHEL machines, - // which change SHLIB_VERSION_NUMBER to a non-portable string. When running - // those binaries on the target systems, this code won't pick up - // libssl.so.MODIFIED_SHLIB_VERSION_NUMBER because it doesn't exist there. - // Given that the only 1.0 supported release (at the time of this writing) - // is 1.0.2, with soname "1.0.0", give that a try too. Note that we mandate - // OpenSSL >= 1.0.0 with a configure-time check, and OpenSSL has kept binary - // compatibility between 1.0.0 and 1.0.2. - // - // It is important, however, to try the canonical name and the unversioned name - // without going through the loop. By not specifying a path, we let the system - // dlopen(3) function determine it for us. This will include any DT_RUNPATH or - // DT_RPATH tags on our library header as well as other system-specific search - // paths. See the man page for dlopen(3) on your system for more information. - -#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 - // first attempt: the canonical name is libssl.so. - libssl->setFileNameAndVersion(QLatin1String("ssl"), QLatin1String(SHLIB_VERSION_NUMBER)); - libcrypto->setFileNameAndVersion(QLatin1String("crypto"), QLatin1String(SHLIB_VERSION_NUMBER)); - if (libcrypto->load() && libssl->load()) { - // libssl.so. and libcrypto.so. found - return result; - } else { - libssl->unload(); - libcrypto->unload(); - } -#endif - -#ifndef Q_OS_DARWIN - // second attempt: find the development files libssl.so and libcrypto.so - // - // disabled on macOS/iOS: - // macOS's /usr/lib/libssl.dylib, /usr/lib/libcrypto.dylib will be picked up in the third - // attempt, _after_ /Contents/Frameworks has been searched. - // iOS does not ship a system libssl.dylib, libcrypto.dylib in the first place. -# if defined(Q_OS_ANDROID) - // OpenSSL 1.1.x must be suffixed otherwise it will use the system libcrypto.so libssl.so which on API-21 are OpenSSL 1.0 not 1.1 - auto openSSLSuffix = [](const QByteArray &defaultSuffix = {}) { - auto suffix = qgetenv("ANDROID_OPENSSL_SUFFIX"); - if (suffix.isEmpty()) - return defaultSuffix; - return suffix; - }; - - static QString suffix = QString::fromLatin1(openSSLSuffix("_1_1")); - - libssl->setFileNameAndVersion(QLatin1String("ssl") + suffix, -1); - libcrypto->setFileNameAndVersion(QLatin1String("crypto") + suffix, -1); -# else - libssl->setFileNameAndVersion(QLatin1String("ssl"), -1); - libcrypto->setFileNameAndVersion(QLatin1String("crypto"), -1); -# endif - if (libcrypto->load() && libssl->load()) { - // libssl.so.0 and libcrypto.so.0 found - return result; - } else { - libssl->unload(); - libcrypto->unload(); - } -#endif - - // third attempt: loop on the most common library paths and find libssl - const QStringList sslList = findAllLibSsl(); - const QStringList cryptoList = findAllLibCrypto(); - - for (const QString &crypto : cryptoList) { - libcrypto->setFileNameAndVersion(crypto, -1); - if (libcrypto->load()) { - QFileInfo fi(crypto); - QString version = fi.completeSuffix(); - - for (const QString &ssl : sslList) { - if (!ssl.endsWith(version)) - continue; - - libssl->setFileNameAndVersion(ssl, -1); - - if (libssl->load()) { - // libssl.so.x and libcrypto.so.x found - return result; - } else { - libssl->unload(); - } - } - } - libcrypto->unload(); - } - - // failed to load anything - result = {}; - return result; - -# else - // not implemented for this platform yet - return result; -# endif -} -#endif - -static QBasicMutex symbolResolveMutex; -static QBasicAtomicInt symbolsResolved = Q_BASIC_ATOMIC_INITIALIZER(false); -static bool triedToResolveSymbols = false; - -bool q_resolveOpenSslSymbols() -{ - if (symbolsResolved.loadAcquire()) - return true; - QMutexLocker locker(&symbolResolveMutex); - if (symbolsResolved.loadRelaxed()) - return true; - if (triedToResolveSymbols) - return false; - triedToResolveSymbols = true; - - LoadedOpenSsl libs = loadOpenSsl(); - if (!libs.ssl || !libs.crypto) - // failed to load them - return false; - - RESOLVEFUNC(OPENSSL_init_ssl) - RESOLVEFUNC(OPENSSL_init_crypto) - RESOLVEFUNC(ASN1_STRING_get0_data) - RESOLVEFUNC(EVP_CIPHER_CTX_reset) - RESOLVEFUNC(EVP_PKEY_up_ref) - RESOLVEFUNC(EVP_PKEY_CTX_new) - RESOLVEFUNC(EVP_PKEY_param_check) - RESOLVEFUNC(EVP_PKEY_CTX_free) - RESOLVEFUNC(EVP_PKEY_base_id) - RESOLVEFUNC(RSA_bits) - RESOLVEFUNC(OPENSSL_sk_new_null) - RESOLVEFUNC(OPENSSL_sk_push) - RESOLVEFUNC(OPENSSL_sk_free) - 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) - RESOLVEFUNC(SSL_alert_desc_string_long) - RESOLVEFUNC(SSL_CTX_get_security_level) - RESOLVEFUNC(SSL_CTX_set_security_level) -#ifdef TLS1_3_VERSION - RESOLVEFUNC(SSL_CTX_set_ciphersuites) - RESOLVEFUNC(SSL_set_psk_use_session_callback) - RESOLVEFUNC(SSL_CTX_sess_set_new_cb) - RESOLVEFUNC(SSL_SESSION_is_resumable) -#endif // TLS 1.3 or OpenSSL > 1.1.1 - - RESOLVEFUNC(SSL_get_client_random) - RESOLVEFUNC(SSL_SESSION_get_master_key) - RESOLVEFUNC(SSL_session_reused) - RESOLVEFUNC(SSL_get_session) - RESOLVEFUNC(SSL_set_options) - RESOLVEFUNC(CRYPTO_get_ex_new_index) - RESOLVEFUNC(TLS_method) - RESOLVEFUNC(TLS_client_method) - RESOLVEFUNC(TLS_server_method) - RESOLVEFUNC(X509_up_ref) - RESOLVEFUNC(X509_STORE_CTX_get0_chain) - RESOLVEFUNC(X509_getm_notBefore) - RESOLVEFUNC(X509_getm_notAfter) - RESOLVEFUNC(X509_get_version) - RESOLVEFUNC(X509_get_pubkey) - RESOLVEFUNC(X509_STORE_set_verify_cb) - RESOLVEFUNC(X509_STORE_set_ex_data) - RESOLVEFUNC(X509_STORE_get_ex_data) - RESOLVEFUNC(CRYPTO_free) - RESOLVEFUNC(OpenSSL_version_num) - RESOLVEFUNC(OpenSSL_version) - - if (!_q_OpenSSL_version) { - // Apparently, we were built with OpenSSL 1.1 enabled but are now using - // a wrong library. - qCWarning(lcSsl, "Incompatible version of OpenSSL"); - return false; - } - - RESOLVEFUNC(SSL_SESSION_get_ticket_lifetime_hint) - RESOLVEFUNC(DH_bits) - RESOLVEFUNC(DSA_bits) - -#if QT_CONFIG(dtls) - RESOLVEFUNC(DTLSv1_listen) - RESOLVEFUNC(BIO_ADDR_new) - RESOLVEFUNC(BIO_ADDR_free) - RESOLVEFUNC(BIO_meth_new) - RESOLVEFUNC(BIO_meth_free) - RESOLVEFUNC(BIO_meth_set_write) - RESOLVEFUNC(BIO_meth_set_read) - RESOLVEFUNC(BIO_meth_set_puts) - RESOLVEFUNC(BIO_meth_set_ctrl) - RESOLVEFUNC(BIO_meth_set_create) - RESOLVEFUNC(BIO_meth_set_destroy) -#endif // dtls - -#if QT_CONFIG(ocsp) - RESOLVEFUNC(OCSP_SINGLERESP_get0_id) - RESOLVEFUNC(d2i_OCSP_RESPONSE) - RESOLVEFUNC(OCSP_RESPONSE_free) - RESOLVEFUNC(OCSP_response_status) - RESOLVEFUNC(OCSP_response_get1_basic) - RESOLVEFUNC(OCSP_BASICRESP_free) - RESOLVEFUNC(OCSP_basic_verify) - RESOLVEFUNC(OCSP_resp_count) - RESOLVEFUNC(OCSP_resp_get0) - RESOLVEFUNC(OCSP_single_get0_status) - RESOLVEFUNC(OCSP_check_validity) - RESOLVEFUNC(OCSP_cert_to_id) - RESOLVEFUNC(OCSP_id_get0_info) - RESOLVEFUNC(OCSP_resp_get0_certs) - RESOLVEFUNC(OCSP_basic_sign) - RESOLVEFUNC(OCSP_response_create) - RESOLVEFUNC(i2d_OCSP_RESPONSE) - RESOLVEFUNC(OCSP_basic_add1_status) - RESOLVEFUNC(OCSP_BASICRESP_new) - RESOLVEFUNC(OCSP_CERTID_free) - RESOLVEFUNC(OCSP_cert_to_id) - RESOLVEFUNC(OCSP_id_cmp) -#endif // ocsp - - RESOLVEFUNC(BIO_set_data) - RESOLVEFUNC(BIO_get_data) - RESOLVEFUNC(BIO_set_init) - RESOLVEFUNC(BIO_get_shutdown) - RESOLVEFUNC(BIO_set_shutdown) - RESOLVEFUNC(ASN1_INTEGER_get) - RESOLVEFUNC(ASN1_INTEGER_cmp) - RESOLVEFUNC(ASN1_STRING_length) - RESOLVEFUNC(ASN1_STRING_to_UTF8) - RESOLVEFUNC(ASN1_TIME_to_tm) - RESOLVEFUNC(BIO_ctrl) - RESOLVEFUNC(BIO_free) - RESOLVEFUNC(BIO_new) - RESOLVEFUNC(BIO_new_mem_buf) - RESOLVEFUNC(BIO_read) - RESOLVEFUNC(BIO_s_mem) - RESOLVEFUNC(BIO_write) - RESOLVEFUNC(BIO_set_flags) - RESOLVEFUNC(BIO_clear_flags) - RESOLVEFUNC(BIO_set_ex_data) - RESOLVEFUNC(BIO_get_ex_data) - -#ifndef OPENSSL_NO_EC - RESOLVEFUNC(EC_KEY_get0_group) - RESOLVEFUNC(EC_GROUP_get_degree) -#endif - RESOLVEFUNC(BN_num_bits) - RESOLVEFUNC(BN_is_word) - RESOLVEFUNC(BN_mod_word) - RESOLVEFUNC(DSA_new) - RESOLVEFUNC(DSA_free) - RESOLVEFUNC(ERR_error_string) - RESOLVEFUNC(ERR_error_string_n) - RESOLVEFUNC(ERR_get_error) - RESOLVEFUNC(EVP_CIPHER_CTX_new) - RESOLVEFUNC(EVP_CIPHER_CTX_free) - RESOLVEFUNC(EVP_CIPHER_CTX_ctrl) - RESOLVEFUNC(EVP_CIPHER_CTX_set_key_length) - RESOLVEFUNC(EVP_CipherInit) - RESOLVEFUNC(EVP_CipherInit_ex) - RESOLVEFUNC(EVP_CipherUpdate) - RESOLVEFUNC(EVP_CipherFinal) - RESOLVEFUNC(EVP_get_digestbyname) -#ifndef OPENSSL_NO_DES - RESOLVEFUNC(EVP_des_cbc) - RESOLVEFUNC(EVP_des_ede3_cbc) -#endif -#ifndef OPENSSL_NO_RC2 - RESOLVEFUNC(EVP_rc2_cbc) -#endif -#ifndef OPENSSL_NO_AES - RESOLVEFUNC(EVP_aes_128_cbc) - RESOLVEFUNC(EVP_aes_192_cbc) - RESOLVEFUNC(EVP_aes_256_cbc) -#endif - RESOLVEFUNC(EVP_sha1) - RESOLVEFUNC(EVP_PKEY_assign) - RESOLVEFUNC(EVP_PKEY_set1_RSA) - RESOLVEFUNC(EVP_PKEY_set1_DSA) - RESOLVEFUNC(EVP_PKEY_set1_DH) - -#ifndef OPENSSL_NO_EC - RESOLVEFUNC(EVP_PKEY_set1_EC_KEY) - RESOLVEFUNC(EVP_PKEY_get1_EC_KEY) - RESOLVEFUNC(PEM_read_bio_ECPrivateKey) - RESOLVEFUNC(PEM_write_bio_ECPrivateKey) - RESOLVEFUNC(PEM_read_bio_EC_PUBKEY) - RESOLVEFUNC(PEM_write_bio_EC_PUBKEY) -#endif // OPENSSL_NO_EC - - RESOLVEFUNC(EVP_PKEY_cmp) - RESOLVEFUNC(EVP_PKEY_free) - RESOLVEFUNC(EVP_PKEY_get1_DSA) - RESOLVEFUNC(EVP_PKEY_get1_RSA) - RESOLVEFUNC(EVP_PKEY_get1_DH) - RESOLVEFUNC(EVP_PKEY_new) - RESOLVEFUNC(EVP_PKEY_type) - RESOLVEFUNC(OBJ_nid2sn) - RESOLVEFUNC(OBJ_nid2ln) - RESOLVEFUNC(OBJ_sn2nid) - RESOLVEFUNC(OBJ_ln2nid) - RESOLVEFUNC(i2t_ASN1_OBJECT) - RESOLVEFUNC(OBJ_obj2txt) - RESOLVEFUNC(OBJ_obj2nid) - RESOLVEFUNC(PEM_read_bio_PrivateKey) - RESOLVEFUNC(PEM_read_bio_DSAPrivateKey) - RESOLVEFUNC(PEM_read_bio_RSAPrivateKey) - RESOLVEFUNC(PEM_read_bio_DHparams) - RESOLVEFUNC(PEM_write_bio_DSAPrivateKey) - RESOLVEFUNC(PEM_write_bio_RSAPrivateKey) - RESOLVEFUNC(PEM_write_bio_PrivateKey) - RESOLVEFUNC(PEM_read_bio_PUBKEY) - RESOLVEFUNC(PEM_read_bio_DSA_PUBKEY) - RESOLVEFUNC(PEM_read_bio_RSA_PUBKEY) - RESOLVEFUNC(PEM_write_bio_DSA_PUBKEY) - RESOLVEFUNC(PEM_write_bio_RSA_PUBKEY) - RESOLVEFUNC(PEM_write_bio_PUBKEY) - RESOLVEFUNC(RAND_seed) - RESOLVEFUNC(RAND_status) - RESOLVEFUNC(RAND_bytes) - RESOLVEFUNC(RSA_new) - RESOLVEFUNC(RSA_free) - RESOLVEFUNC(SSL_CIPHER_description) - RESOLVEFUNC(SSL_CIPHER_get_bits) - RESOLVEFUNC(SSL_get_rbio) - RESOLVEFUNC(SSL_CTX_check_private_key) - RESOLVEFUNC(SSL_CTX_ctrl) - RESOLVEFUNC(SSL_CTX_free) - RESOLVEFUNC(SSL_CTX_new) - RESOLVEFUNC(SSL_CTX_set_cipher_list) - RESOLVEFUNC(SSL_CTX_callback_ctrl) - RESOLVEFUNC(SSL_CTX_set_default_verify_paths) - RESOLVEFUNC(SSL_CTX_set_verify) - RESOLVEFUNC(SSL_CTX_set_verify_depth) - RESOLVEFUNC(SSL_CTX_use_certificate) - RESOLVEFUNC(SSL_CTX_use_certificate_file) - RESOLVEFUNC(SSL_CTX_use_PrivateKey) - RESOLVEFUNC(SSL_CTX_use_RSAPrivateKey) - RESOLVEFUNC(SSL_CTX_use_PrivateKey_file) - RESOLVEFUNC(SSL_CTX_get_cert_store); - RESOLVEFUNC(SSL_CONF_CTX_new); - RESOLVEFUNC(SSL_CONF_CTX_free); - RESOLVEFUNC(SSL_CONF_CTX_set_ssl_ctx); - RESOLVEFUNC(SSL_CONF_CTX_set_flags); - RESOLVEFUNC(SSL_CONF_CTX_finish); - RESOLVEFUNC(SSL_CONF_cmd); - RESOLVEFUNC(SSL_accept) - RESOLVEFUNC(SSL_clear) - RESOLVEFUNC(SSL_connect) - RESOLVEFUNC(SSL_free) - RESOLVEFUNC(SSL_get_ciphers) - RESOLVEFUNC(SSL_get_current_cipher) - RESOLVEFUNC(SSL_version) - RESOLVEFUNC(SSL_get_error) - RESOLVEFUNC(SSL_get_peer_cert_chain) - RESOLVEFUNC(SSL_get_peer_certificate) - RESOLVEFUNC(SSL_get_verify_result) - RESOLVEFUNC(SSL_new) - RESOLVEFUNC(SSL_get_SSL_CTX) - RESOLVEFUNC(SSL_ctrl) - RESOLVEFUNC(SSL_read) - RESOLVEFUNC(SSL_set_accept_state) - RESOLVEFUNC(SSL_set_bio) - RESOLVEFUNC(SSL_set_connect_state) - RESOLVEFUNC(SSL_shutdown) - RESOLVEFUNC(SSL_in_init) - RESOLVEFUNC(SSL_get_shutdown) - RESOLVEFUNC(SSL_set_session) - RESOLVEFUNC(SSL_SESSION_free) - RESOLVEFUNC(SSL_get1_session) - RESOLVEFUNC(SSL_get_session) - RESOLVEFUNC(SSL_set_ex_data) - RESOLVEFUNC(SSL_get_ex_data) - RESOLVEFUNC(SSL_get_ex_data_X509_STORE_CTX_idx) - -#ifndef OPENSSL_NO_PSK - RESOLVEFUNC(SSL_set_psk_client_callback) - RESOLVEFUNC(SSL_set_psk_server_callback) - RESOLVEFUNC(SSL_CTX_use_psk_identity_hint) -#endif // !OPENSSL_NO_PSK - - RESOLVEFUNC(SSL_write) - RESOLVEFUNC(X509_NAME_entry_count) - RESOLVEFUNC(X509_NAME_get_entry) - RESOLVEFUNC(X509_NAME_ENTRY_get_data) - RESOLVEFUNC(X509_NAME_ENTRY_get_object) - RESOLVEFUNC(X509_PUBKEY_get) - RESOLVEFUNC(X509_STORE_free) - RESOLVEFUNC(X509_STORE_new) - RESOLVEFUNC(X509_STORE_add_cert) - RESOLVEFUNC(X509_STORE_CTX_free) - RESOLVEFUNC(X509_STORE_CTX_init) - RESOLVEFUNC(X509_STORE_CTX_new) - RESOLVEFUNC(X509_STORE_CTX_set_purpose) - RESOLVEFUNC(X509_STORE_CTX_get_error) - RESOLVEFUNC(X509_STORE_CTX_get_error_depth) - RESOLVEFUNC(X509_STORE_CTX_get_current_cert) - RESOLVEFUNC(X509_STORE_CTX_get0_store) - RESOLVEFUNC(X509_cmp) - RESOLVEFUNC(X509_STORE_CTX_get_ex_data) - RESOLVEFUNC(X509_dup) - RESOLVEFUNC(X509_print) - RESOLVEFUNC(X509_digest) - RESOLVEFUNC(X509_EXTENSION_get_object) - RESOLVEFUNC(X509_free) - RESOLVEFUNC(X509_gmtime_adj) - RESOLVEFUNC(ASN1_TIME_free) - RESOLVEFUNC(X509_get_ext) - RESOLVEFUNC(X509_get_ext_count) - RESOLVEFUNC(X509_get_ext_d2i) - RESOLVEFUNC(X509V3_EXT_get) - RESOLVEFUNC(X509V3_EXT_d2i) - RESOLVEFUNC(X509_EXTENSION_get_critical) - RESOLVEFUNC(X509_EXTENSION_get_data) - RESOLVEFUNC(BASIC_CONSTRAINTS_free) - RESOLVEFUNC(AUTHORITY_KEYID_free) - RESOLVEFUNC(GENERAL_NAME_free) - RESOLVEFUNC(ASN1_STRING_print) - RESOLVEFUNC(X509_check_issued) - RESOLVEFUNC(X509_get_issuer_name) - RESOLVEFUNC(X509_get_subject_name) - RESOLVEFUNC(X509_get_serialNumber) - RESOLVEFUNC(X509_verify_cert) - RESOLVEFUNC(d2i_X509) - RESOLVEFUNC(i2d_X509) -#if OPENSSL_VERSION_MAJOR < 3 - RESOLVEFUNC(SSL_CTX_load_verify_locations) -#else - RESOLVEFUNC(SSL_CTX_load_verify_dir) -#endif // OPENSSL_VERSION_MAJOR - RESOLVEFUNC(i2d_SSL_SESSION) - RESOLVEFUNC(d2i_SSL_SESSION) - -#ifndef OPENSSL_NO_NEXTPROTONEG - RESOLVEFUNC(SSL_select_next_proto) - RESOLVEFUNC(SSL_CTX_set_next_proto_select_cb) - RESOLVEFUNC(SSL_get0_next_proto_negotiated) - RESOLVEFUNC(SSL_set_alpn_protos) - RESOLVEFUNC(SSL_CTX_set_alpn_select_cb) - RESOLVEFUNC(SSL_get0_alpn_selected) -#endif // !OPENSSL_NO_NEXTPROTONEG - -#if QT_CONFIG(dtls) - RESOLVEFUNC(SSL_CTX_set_cookie_generate_cb) - RESOLVEFUNC(SSL_CTX_set_cookie_verify_cb) - RESOLVEFUNC(DTLS_server_method) - RESOLVEFUNC(DTLS_client_method) -#endif // dtls - - RESOLVEFUNC(CRYPTO_malloc) - RESOLVEFUNC(DH_new) - RESOLVEFUNC(DH_free) - RESOLVEFUNC(d2i_DHparams) - RESOLVEFUNC(i2d_DHparams) -#ifndef OPENSSL_NO_DEPRECATED_3_0 - RESOLVEFUNC(DH_check) -#endif // OPENSSL_NO_DEPRECATED_3_0 - RESOLVEFUNC(BN_bin2bn) - -#ifndef OPENSSL_NO_EC - RESOLVEFUNC(EC_KEY_dup) - RESOLVEFUNC(EC_KEY_new_by_curve_name) - RESOLVEFUNC(EC_KEY_free) - RESOLVEFUNC(EC_get_builtin_curves) -#endif // OPENSSL_NO_EC - - RESOLVEFUNC(PKCS12_parse) - RESOLVEFUNC(d2i_PKCS12_bio) - RESOLVEFUNC(PKCS12_free) - - symbolsResolved.storeRelease(true); - return true; -} -#endif // QT_CONFIG(library) - -#else // !defined QT_LINKED_OPENSSL - -bool q_resolveOpenSslSymbols() -{ -#ifdef QT_NO_OPENSSL - return false; -#endif - return true; -} -#endif // !defined QT_LINKED_OPENSSL - -QDateTime q_getTimeFromASN1(const ASN1_TIME *aTime) -{ - QDateTime result; - tm lTime; - - if (q_ASN1_TIME_to_tm(aTime, &lTime)) { - QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday); - QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec); - result = QDateTime(resDate, resTime, Qt::UTC); - } - - return result; -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h deleted file mode 100644 index f733c64fe3..0000000000 --- a/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ /dev/null @@ -1,761 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Copyright (C) 2014 BlackBerry Limited. All rights reserved. -** 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$ -** -****************************************************************************/ - -/**************************************************************************** -** -** In addition, as a special exception, the copyright holders listed above give -** permission to link the code of its release of Qt with the OpenSSL project's -** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the -** same license as the original version), and distribute the linked executables. -** -** You must comply with the GNU General Public License version 2 in all -** respects for all of the code used other than the "OpenSSL" code. If you -** modify this file, you may extend this exception to your version of the file, -** but you are not obligated to do so. If you do not wish to do so, delete -** this exception statement from your version of this file. -** -****************************************************************************/ - -#ifndef QSSLSOCKET_OPENSSL_SYMBOLS_P_H -#define QSSLSOCKET_OPENSSL_SYMBOLS_P_H - - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include "qopenssl_p.h" -#include - -#if QT_CONFIG(ocsp) -#include "qocsp_p.h" -#endif - -QT_BEGIN_NAMESPACE - -#define DUMMYARG - -#if !defined QT_LINKED_OPENSSL -// **************** Shared declarations ****************** -// ret func(arg) - -# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ - typedef ret (*_q_PTR_##func)(arg); \ - static _q_PTR_##func _q_##func = nullptr; \ - ret q_##func(arg) { \ - if (Q_UNLIKELY(!_q_##func)) { \ - qsslSocketUnresolvedSymbolWarning(#func); \ - err; \ - } \ - funcret _q_##func(a); \ - } - -// ret func(arg1, arg2) -# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ - typedef ret (*_q_PTR_##func)(arg1, arg2); \ - static _q_PTR_##func _q_##func = nullptr; \ - ret q_##func(arg1, arg2) { \ - if (Q_UNLIKELY(!_q_##func)) { \ - qsslSocketUnresolvedSymbolWarning(#func);\ - err; \ - } \ - funcret _q_##func(a, b); \ - } - -// ret func(arg1, arg2, arg3) -# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ - typedef ret (*_q_PTR_##func)(arg1, arg2, arg3); \ - static _q_PTR_##func _q_##func = nullptr; \ - ret q_##func(arg1, arg2, arg3) { \ - if (Q_UNLIKELY(!_q_##func)) { \ - qsslSocketUnresolvedSymbolWarning(#func); \ - err; \ - } \ - funcret _q_##func(a, b, c); \ - } - -// ret func(arg1, arg2, arg3, arg4) -# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ - typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4); \ - static _q_PTR_##func _q_##func = nullptr; \ - ret q_##func(arg1, arg2, arg3, arg4) { \ - if (Q_UNLIKELY(!_q_##func)) { \ - qsslSocketUnresolvedSymbolWarning(#func); \ - err; \ - } \ - funcret _q_##func(a, b, c, d); \ - } - -// ret func(arg1, arg2, arg3, arg4, arg5) -# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ - typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5); \ - static _q_PTR_##func _q_##func = nullptr; \ - ret q_##func(arg1, arg2, arg3, arg4, arg5) { \ - if (Q_UNLIKELY(!_q_##func)) { \ - qsslSocketUnresolvedSymbolWarning(#func); \ - err; \ - } \ - funcret _q_##func(a, b, c, d, e); \ - } - -// ret func(arg1, arg2, arg3, arg4, arg6) -# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ - typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6); \ - static _q_PTR_##func _q_##func = nullptr; \ - ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { \ - if (Q_UNLIKELY(!_q_##func)) { \ - qsslSocketUnresolvedSymbolWarning(#func); \ - err; \ - } \ - funcret _q_##func(a, b, c, d, e, f); \ - } - -// ret func(arg1, arg2, arg3, arg4, arg6, arg7) -# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ - typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ - static _q_PTR_##func _q_##func = nullptr; \ - ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { \ - if (Q_UNLIKELY(!_q_##func)) { \ - qsslSocketUnresolvedSymbolWarning(#func); \ - err; \ - } \ - funcret _q_##func(a, b, c, d, e, f, g); \ - } - -// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) -# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ - typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ - static _q_PTR_##func _q_##func = nullptr; \ - ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { \ - if (Q_UNLIKELY(!_q_##func)) { \ - qsslSocketUnresolvedSymbolWarning(#func); \ - err; \ - } \ - funcret _q_##func(a, b, c, d, e, f, g, h, i); \ - } -// **************** Shared declarations ****************** - -#else // !defined QT_LINKED_OPENSSL - -// **************** Static declarations ****************** - -// ret func(arg) -# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ - ret q_##func(arg) { funcret func(a); } - -// ret func(arg1, arg2) -# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ - ret q_##func(arg1, arg2) { funcret func(a, b); } - -// ret func(arg1, arg2, arg3) -# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ - ret q_##func(arg1, arg2, arg3) { funcret func(a, b, c); } - -// ret func(arg1, arg2, arg3, arg4) -# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ - ret q_##func(arg1, arg2, arg3, arg4) { funcret func(a, b, c, d); } - -// ret func(arg1, arg2, arg3, arg4, arg5) -# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ - ret q_##func(arg1, arg2, arg3, arg4, arg5) { funcret func(a, b, c, d, e); } - -// ret func(arg1, arg2, arg3, arg4, arg6) -# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ - ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { funcret func(a, b, c, d, e, f); } - -// ret func(arg1, arg2, arg3, arg4, arg6, arg7) -# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ - ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { funcret func(a, b, c, d, e, f, g); } - -// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) -# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ - ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { funcret func(a, b, c, d, e, f, g, h, i); } - -// **************** Static declarations ****************** - -#endif // !defined QT_LINKED_OPENSSL - -// TODO: the following lines previously were a part of 1.1 - specific header. -// To reduce the amount of the change, I'm directly copying and pasting the -// content of the header here. Later, can be better sorted/split into groups, -// depending on the functionality. - -const unsigned char * q_ASN1_STRING_get0_data(const ASN1_STRING *x); - -Q_AUTOTEST_EXPORT BIO *q_BIO_new(const BIO_METHOD *a); -Q_AUTOTEST_EXPORT const BIO_METHOD *q_BIO_s_mem(); - -int q_DSA_bits(DSA *a); -int q_EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *c); -Q_AUTOTEST_EXPORT int q_EVP_PKEY_up_ref(EVP_PKEY *a); -EVP_PKEY_CTX *q_EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); -void q_EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); -int q_EVP_PKEY_param_check(EVP_PKEY_CTX *ctx); -int q_EVP_PKEY_base_id(EVP_PKEY *a); -int q_RSA_bits(RSA *a); -Q_AUTOTEST_EXPORT int q_OPENSSL_sk_num(OPENSSL_STACK *a); -Q_AUTOTEST_EXPORT void q_OPENSSL_sk_pop_free(OPENSSL_STACK *a, void (*b)(void *)); -Q_AUTOTEST_EXPORT OPENSSL_STACK *q_OPENSSL_sk_new_null(); -Q_AUTOTEST_EXPORT void q_OPENSSL_sk_push(OPENSSL_STACK *st, void *data); -Q_AUTOTEST_EXPORT void q_OPENSSL_sk_free(OPENSSL_STACK *a); -Q_AUTOTEST_EXPORT void * q_OPENSSL_sk_value(OPENSSL_STACK *a, int b); -int q_SSL_session_reused(SSL *a); -unsigned long q_SSL_CTX_set_options(SSL_CTX *ctx, unsigned long op); -int q_OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); -size_t q_SSL_get_client_random(SSL *a, unsigned char *out, size_t outlen); -size_t q_SSL_SESSION_get_master_key(const SSL_SESSION *session, unsigned char *out, size_t outlen); -int q_CRYPTO_get_ex_new_index(int class_index, long argl, void *argp, CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); -const SSL_METHOD *q_TLS_method(); -const SSL_METHOD *q_TLS_client_method(); -const SSL_METHOD *q_TLS_server_method(); -ASN1_TIME *q_X509_getm_notBefore(X509 *a); -ASN1_TIME *q_X509_getm_notAfter(X509 *a); - -Q_AUTOTEST_EXPORT void q_X509_up_ref(X509 *a); -long q_X509_get_version(X509 *a); -EVP_PKEY *q_X509_get_pubkey(X509 *a); -void q_X509_STORE_set_verify_cb(X509_STORE *ctx, X509_STORE_CTX_verify_cb verify_cb); -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); -int q_DH_bits(DH *dh); - -# define q_SSL_load_error_strings() q_OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS \ - | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL) - -#define q_SKM_sk_num(st) q_OPENSSL_sk_num((OPENSSL_STACK *)st) -#define q_SKM_sk_value(type, st,i) (type *)q_OPENSSL_sk_value((OPENSSL_STACK *)st, i) - -#define q_OPENSSL_add_all_algorithms_conf() q_OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ - | OPENSSL_INIT_ADD_ALL_DIGESTS \ - | OPENSSL_INIT_LOAD_CONFIG, NULL) -#define q_OPENSSL_add_all_algorithms_noconf() q_OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ - | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL) - -int q_OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); -void q_CRYPTO_free(void *str, const char *file, int line); - -long q_OpenSSL_version_num(); -const char *q_OpenSSL_version(int type); - -unsigned long q_SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *session); -unsigned long q_SSL_set_options(SSL *s, unsigned long op); - -#ifdef TLS1_3_VERSION -int q_SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str); - -// The functions below do not really have to be ifdefed like this, but for now -// they only used in TLS 1.3 handshake (and probably future versions). -// Plus, 'is resumalbe' is OpenSSL 1.1.1-only (and again we need it for -// TLS 1.3-specific session management). - -extern "C" -{ -using NewSessionCallback = int (*)(SSL *, SSL_SESSION *); -} - -void q_SSL_CTX_sess_set_new_cb(SSL_CTX *ctx, NewSessionCallback cb); -int q_SSL_SESSION_is_resumable(const SSL_SESSION *s); - -#define q_SSL_CTX_set_session_cache_mode(ctx,m) \ - q_SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SESS_CACHE_MODE,m,NULL) - -#endif - -#if QT_CONFIG(dtls) -// Functions and types required for DTLS support: -extern "C" -{ - -typedef int (*CookieVerifyCallback)(SSL *, const unsigned char *, unsigned); -typedef int (*DgramWriteCallback) (BIO *, const char *, int); -typedef int (*DgramReadCallback) (BIO *, char *, int); -typedef int (*DgramPutsCallback) (BIO *, const char *); -typedef long (*DgramCtrlCallback) (BIO *, int, long, void *); -typedef int (*DgramCreateCallback) (BIO *); -typedef int (*DgramDestroyCallback) (BIO *); - -} - -int q_DTLSv1_listen(SSL *s, BIO_ADDR *client); -BIO_ADDR *q_BIO_ADDR_new(); -void q_BIO_ADDR_free(BIO_ADDR *ap); - -// API we need for a custom dgram BIO: - -BIO_METHOD *q_BIO_meth_new(int type, const char *name); -void q_BIO_meth_free(BIO_METHOD *biom); -int q_BIO_meth_set_write(BIO_METHOD *biom, DgramWriteCallback); -int q_BIO_meth_set_read(BIO_METHOD *biom, DgramReadCallback); -int q_BIO_meth_set_puts(BIO_METHOD *biom, DgramPutsCallback); -int q_BIO_meth_set_ctrl(BIO_METHOD *biom, DgramCtrlCallback); -int q_BIO_meth_set_create(BIO_METHOD *biom, DgramCreateCallback); -int q_BIO_meth_set_destroy(BIO_METHOD *biom, DgramDestroyCallback); - -#endif // dtls - -void q_BIO_set_data(BIO *a, void *ptr); -void *q_BIO_get_data(BIO *a); -void q_BIO_set_init(BIO *a, int init); -int q_BIO_get_shutdown(BIO *a); -void q_BIO_set_shutdown(BIO *a, int shut); - -#if QT_CONFIG(ocsp) -const OCSP_CERTID *q_OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP *x); -#endif // ocsp - -#define q_SSL_CTX_set_min_proto_version(ctx, version) \ - q_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, version, nullptr) - -#define q_SSL_CTX_set_max_proto_version(ctx, version) \ - q_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MAX_PROTO_VERSION, version, nullptr) - -extern "C" { -typedef int (*q_SSL_psk_use_session_cb_func_t)(SSL *, const EVP_MD *, const unsigned char **, size_t *, - SSL_SESSION **); -} -void q_SSL_set_psk_use_session_callback(SSL *s, q_SSL_psk_use_session_cb_func_t); -// Here the content of the 1.1 header ends. - -bool q_resolveOpenSslSymbols(); -long q_ASN1_INTEGER_get(ASN1_INTEGER *a); -int q_ASN1_INTEGER_cmp(const ASN1_INTEGER *x, const ASN1_INTEGER *y); -int q_ASN1_STRING_length(ASN1_STRING *a); -int q_ASN1_STRING_to_UTF8(unsigned char **a, ASN1_STRING *b); -int q_ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm); -long q_BIO_ctrl(BIO *a, int b, long c, void *d); -Q_AUTOTEST_EXPORT int q_BIO_free(BIO *a); -BIO *q_BIO_new_mem_buf(void *a, int b); -int q_BIO_read(BIO *a, void *b, int c); -Q_AUTOTEST_EXPORT int q_BIO_write(BIO *a, const void *b, int c); -int q_BN_num_bits(const BIGNUM *a); -int q_BN_is_word(BIGNUM *a, BN_ULONG w); -BN_ULONG q_BN_mod_word(const BIGNUM *a, BN_ULONG w); - -#ifndef OPENSSL_NO_EC -const EC_GROUP* q_EC_KEY_get0_group(const EC_KEY* k); -int q_EC_GROUP_get_degree(const EC_GROUP* g); -#endif // OPENSSL_NO_EC - -DSA *q_DSA_new(); -void q_DSA_free(DSA *a); -X509 *q_d2i_X509(X509 **a, const unsigned char **b, long c); -char *q_ERR_error_string(unsigned long a, char *b); -void q_ERR_error_string_n(unsigned long e, char *buf, size_t len); -unsigned long q_ERR_get_error(); -EVP_CIPHER_CTX *q_EVP_CIPHER_CTX_new(); -void q_EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *a); -int q_EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr); -int q_EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *x, int keylen); -int q_EVP_CipherInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, const unsigned char *key, const unsigned char *iv, int enc); -int q_EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv, int enc); -int q_EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl); -int q_EVP_CipherFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); -const EVP_MD *q_EVP_get_digestbyname(const char *name); - -#ifndef OPENSSL_NO_DES -const EVP_CIPHER *q_EVP_des_cbc(); -const EVP_CIPHER *q_EVP_des_ede3_cbc(); -#endif // OPENSSL_NO_DES - -#ifndef OPENSSL_NO_RC2 -const EVP_CIPHER *q_EVP_rc2_cbc(); -#endif // OPENSSL_NO_RC2 - -#ifndef OPENSSL_NO_AES -const EVP_CIPHER *q_EVP_aes_128_cbc(); -const EVP_CIPHER *q_EVP_aes_192_cbc(); -const EVP_CIPHER *q_EVP_aes_256_cbc(); -#endif // OPENSSL_NO_AES - -Q_AUTOTEST_EXPORT const EVP_MD *q_EVP_sha1(); -int q_EVP_PKEY_assign(EVP_PKEY *a, int b, void *r); -Q_AUTOTEST_EXPORT int q_EVP_PKEY_set1_RSA(EVP_PKEY *a, RSA *b); -Q_AUTOTEST_EXPORT int q_EVP_PKEY_set1_DSA(EVP_PKEY *a, DSA *b); -Q_AUTOTEST_EXPORT int q_EVP_PKEY_set1_DH(EVP_PKEY *a, DH *b); - -#ifndef OPENSSL_NO_EC -Q_AUTOTEST_EXPORT int q_EVP_PKEY_set1_EC_KEY(EVP_PKEY *a, EC_KEY *b); -#endif - -Q_AUTOTEST_EXPORT int q_EVP_PKEY_cmp(const EVP_PKEY *a, const EVP_PKEY *b); -Q_AUTOTEST_EXPORT void q_EVP_PKEY_free(EVP_PKEY *a); -RSA *q_EVP_PKEY_get1_RSA(EVP_PKEY *a); -DSA *q_EVP_PKEY_get1_DSA(EVP_PKEY *a); -DH *q_EVP_PKEY_get1_DH(EVP_PKEY *a); -#ifndef OPENSSL_NO_EC -EC_KEY *q_EVP_PKEY_get1_EC_KEY(EVP_PKEY *a); -#endif -int q_EVP_PKEY_type(int a); -Q_AUTOTEST_EXPORT EVP_PKEY *q_EVP_PKEY_new(); -int q_i2d_X509(X509 *a, unsigned char **b); -const char *q_OBJ_nid2sn(int a); -const char *q_OBJ_nid2ln(int a); -int q_OBJ_sn2nid(const char *s); -int q_OBJ_ln2nid(const char *s); -int q_i2t_ASN1_OBJECT(char *buf, int buf_len, ASN1_OBJECT *obj); -int q_OBJ_obj2txt(char *buf, int buf_len, ASN1_OBJECT *obj, int no_name); -int q_OBJ_obj2nid(const ASN1_OBJECT *a); -#define q_EVP_get_digestbynid(a) q_EVP_get_digestbyname(q_OBJ_nid2sn(a)) -Q_AUTOTEST_EXPORT EVP_PKEY *q_PEM_read_bio_PrivateKey(BIO *a, EVP_PKEY **b, pem_password_cb *c, void *d); -DSA *q_PEM_read_bio_DSAPrivateKey(BIO *a, DSA **b, pem_password_cb *c, void *d); -RSA *q_PEM_read_bio_RSAPrivateKey(BIO *a, RSA **b, pem_password_cb *c, void *d); - -#ifndef OPENSSL_NO_EC -EC_KEY *q_PEM_read_bio_ECPrivateKey(BIO *a, EC_KEY **b, pem_password_cb *c, void *d); -int q_PEM_write_bio_ECPrivateKey(BIO *a, EC_KEY *b, const EVP_CIPHER *c, unsigned char *d, - int e, pem_password_cb *f, void *g); -EC_KEY *q_PEM_read_bio_EC_PUBKEY(BIO *a, EC_KEY **b, pem_password_cb *c, void *d); -int q_PEM_write_bio_EC_PUBKEY(BIO *a, EC_KEY *b); -#endif // OPENSSL_NO_EC - -DH *q_PEM_read_bio_DHparams(BIO *a, DH **b, pem_password_cb *c, void *d); -int q_PEM_write_bio_DSAPrivateKey(BIO *a, DSA *b, const EVP_CIPHER *c, unsigned char *d, - int e, pem_password_cb *f, void *g); -int q_PEM_write_bio_RSAPrivateKey(BIO *a, RSA *b, const EVP_CIPHER *c, unsigned char *d, - int e, pem_password_cb *f, void *g); -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); -Q_AUTOTEST_EXPORT EVP_PKEY *q_PEM_read_bio_PUBKEY(BIO *a, EVP_PKEY **b, pem_password_cb *c, void *d); -DSA *q_PEM_read_bio_DSA_PUBKEY(BIO *a, DSA **b, pem_password_cb *c, void *d); -RSA *q_PEM_read_bio_RSA_PUBKEY(BIO *a, RSA **b, pem_password_cb *c, void *d); -int q_PEM_write_bio_DSA_PUBKEY(BIO *a, DSA *b); -int q_PEM_write_bio_RSA_PUBKEY(BIO *a, RSA *b); -int q_PEM_write_bio_PUBKEY(BIO *a, EVP_PKEY *b); - -void q_RAND_seed(const void *a, int b); -int q_RAND_status(); -int q_RAND_bytes(unsigned char *b, int n); -RSA *q_RSA_new(); -void q_RSA_free(RSA *a); -int q_SSL_accept(SSL *a); -int q_SSL_clear(SSL *a); -char *q_SSL_CIPHER_description(const SSL_CIPHER *a, char *b, int c); -int q_SSL_CIPHER_get_bits(const SSL_CIPHER *a, int *b); -BIO *q_SSL_get_rbio(const SSL *s); -int q_SSL_connect(SSL *a); -int q_SSL_CTX_check_private_key(const SSL_CTX *a); -long q_SSL_CTX_ctrl(SSL_CTX *a, int b, long c, void *d); -void q_SSL_CTX_free(SSL_CTX *a); -SSL_CTX *q_SSL_CTX_new(const SSL_METHOD *a); -int q_SSL_CTX_set_cipher_list(SSL_CTX *a, const char *b); -int q_SSL_CTX_set_default_verify_paths(SSL_CTX *a); -void q_SSL_CTX_set_verify(SSL_CTX *a, int b, int (*c)(int, X509_STORE_CTX *)); -void q_SSL_CTX_set_verify_depth(SSL_CTX *a, int b); -extern "C" { -typedef void (*GenericCallbackType)(); -} -long q_SSL_CTX_callback_ctrl(SSL_CTX *, int, GenericCallbackType); -int q_SSL_CTX_use_certificate(SSL_CTX *a, X509 *b); -int q_SSL_CTX_use_certificate_file(SSL_CTX *a, const char *b, int c); -int q_SSL_CTX_use_PrivateKey(SSL_CTX *a, EVP_PKEY *b); -int q_SSL_CTX_use_RSAPrivateKey(SSL_CTX *a, RSA *b); -int q_SSL_CTX_use_PrivateKey_file(SSL_CTX *a, const char *b, int c); -X509_STORE *q_SSL_CTX_get_cert_store(const SSL_CTX *a); -SSL_CONF_CTX *q_SSL_CONF_CTX_new(); -void q_SSL_CONF_CTX_free(SSL_CONF_CTX *a); -void q_SSL_CONF_CTX_set_ssl_ctx(SSL_CONF_CTX *a, SSL_CTX *b); -unsigned int q_SSL_CONF_CTX_set_flags(SSL_CONF_CTX *a, unsigned int b); -int q_SSL_CONF_CTX_finish(SSL_CONF_CTX *a); -int q_SSL_CONF_cmd(SSL_CONF_CTX *a, const char *b, const char *c); -void q_SSL_free(SSL *a); -STACK_OF(SSL_CIPHER) *q_SSL_get_ciphers(const SSL *a); -const SSL_CIPHER *q_SSL_get_current_cipher(SSL *a); -int q_SSL_version(const SSL *a); -int q_SSL_get_error(SSL *a, int b); -STACK_OF(X509) *q_SSL_get_peer_cert_chain(SSL *a); -X509 *q_SSL_get_peer_certificate(SSL *a); -long q_SSL_get_verify_result(const SSL *a); -SSL *q_SSL_new(SSL_CTX *a); -SSL_CTX *q_SSL_get_SSL_CTX(SSL *a); -long q_SSL_ctrl(SSL *ssl,int cmd, long larg, void *parg); -int q_SSL_read(SSL *a, void *b, int c); -void q_SSL_set_bio(SSL *a, BIO *b, BIO *c); -void q_SSL_set_accept_state(SSL *a); -void q_SSL_set_connect_state(SSL *a); -int q_SSL_shutdown(SSL *a); -int q_SSL_in_init(const SSL *s); -int q_SSL_get_shutdown(const SSL *ssl); -int q_SSL_set_session(SSL *to, SSL_SESSION *session); -void q_SSL_SESSION_free(SSL_SESSION *ses); -SSL_SESSION *q_SSL_get1_session(SSL *ssl); -SSL_SESSION *q_SSL_get_session(const SSL *ssl); -int q_SSL_set_ex_data(SSL *ssl, int idx, void *arg); -void *q_SSL_get_ex_data(const SSL *ssl, int idx); -#ifndef OPENSSL_NO_PSK -typedef unsigned int (*q_psk_client_callback_t)(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len); -void q_SSL_set_psk_client_callback(SSL *ssl, q_psk_client_callback_t callback); -typedef unsigned int (*q_psk_server_callback_t)(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len); -void q_SSL_set_psk_server_callback(SSL *ssl, q_psk_server_callback_t callback); -int q_SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *hint); -#endif // !OPENSSL_NO_PSK -int q_SSL_write(SSL *a, const void *b, int c); -int q_X509_cmp(X509 *a, X509 *b); -X509 *q_X509_dup(X509 *a); -void q_X509_print(BIO *a, X509*b); -int q_X509_digest(const X509 *x509, const EVP_MD *type, unsigned char *md, unsigned int *len); -ASN1_OBJECT *q_X509_EXTENSION_get_object(X509_EXTENSION *a); -Q_AUTOTEST_EXPORT void q_X509_free(X509 *a); -Q_AUTOTEST_EXPORT ASN1_TIME *q_X509_gmtime_adj(ASN1_TIME *s, long adj); -Q_AUTOTEST_EXPORT void q_ASN1_TIME_free(ASN1_TIME *t); -X509_EXTENSION *q_X509_get_ext(X509 *a, int b); -int q_X509_get_ext_count(X509 *a); -void *q_X509_get_ext_d2i(X509 *a, int b, int *c, int *d); -const X509V3_EXT_METHOD *q_X509V3_EXT_get(X509_EXTENSION *a); -void *q_X509V3_EXT_d2i(X509_EXTENSION *a); -int q_X509_EXTENSION_get_critical(X509_EXTENSION *a); -ASN1_OCTET_STRING *q_X509_EXTENSION_get_data(X509_EXTENSION *a); -void q_BASIC_CONSTRAINTS_free(BASIC_CONSTRAINTS *a); -void q_AUTHORITY_KEYID_free(AUTHORITY_KEYID *a); -int q_ASN1_STRING_print(BIO *a, const ASN1_STRING *b); -int q_X509_check_issued(X509 *a, X509 *b); -X509_NAME *q_X509_get_issuer_name(X509 *a); -X509_NAME *q_X509_get_subject_name(X509 *a); -ASN1_INTEGER *q_X509_get_serialNumber(X509 *a); -int q_X509_verify_cert(X509_STORE_CTX *ctx); -int q_X509_NAME_entry_count(X509_NAME *a); -X509_NAME_ENTRY *q_X509_NAME_get_entry(X509_NAME *a,int b); -ASN1_STRING *q_X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *a); -ASN1_OBJECT *q_X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *a); -EVP_PKEY *q_X509_PUBKEY_get(X509_PUBKEY *a); -void q_X509_STORE_free(X509_STORE *store); -X509_STORE *q_X509_STORE_new(); -int q_X509_STORE_add_cert(X509_STORE *ctx, X509 *x); -void q_X509_STORE_CTX_free(X509_STORE_CTX *storeCtx); -int q_X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, - X509 *x509, STACK_OF(X509) *chain); -X509_STORE_CTX *q_X509_STORE_CTX_new(); -int q_X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose); -int q_X509_STORE_CTX_get_error(X509_STORE_CTX *ctx); -int q_X509_STORE_CTX_get_error_depth(X509_STORE_CTX *ctx); -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 -DH *q_DH_new(); -void q_DH_free(DH *dh); -DH *q_d2i_DHparams(DH **a, const unsigned char **pp, long length); -int q_i2d_DHparams(DH *a, unsigned char **p); - -#ifndef OPENSSL_NO_DEPRECATED_3_0 -int q_DH_check(DH *dh, int *codes); -#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) - -#ifndef OPENSSL_NO_EC -// EC Diffie-Hellman support -EC_KEY *q_EC_KEY_dup(const EC_KEY *src); -EC_KEY *q_EC_KEY_new_by_curve_name(int nid); -void q_EC_KEY_free(EC_KEY *ecdh); -#define q_SSL_CTX_set_tmp_ecdh(ctx, ecdh) q_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_TMP_ECDH, 0, (char *)ecdh) - -// EC curves management -size_t q_EC_get_builtin_curves(EC_builtin_curve *r, size_t nitems); -int q_EC_curve_nist2nid(const char *name); -#endif // OPENSSL_NO_EC - -#define q_SSL_get_server_tmp_key(ssl, key) q_SSL_ctrl((ssl), SSL_CTRL_GET_SERVER_TMP_KEY, 0, (char *)key) - -// PKCS#12 support -int q_PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert, STACK_OF(X509) **ca); -PKCS12 *q_d2i_PKCS12_bio(BIO *bio, PKCS12 **pkcs12); -void q_PKCS12_free(PKCS12 *pkcs12); - -#define q_BIO_get_mem_data(b, pp) (int)q_BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp) -#define q_BIO_pending(b) (int)q_BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL) -#define q_SSL_CTX_set_mode(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_MODE,(op),NULL) -#define q_sk_GENERAL_NAME_num(st) q_SKM_sk_num((st)) -#define q_sk_GENERAL_NAME_value(st, i) q_SKM_sk_value(GENERAL_NAME, (st), (i)) - -void q_GENERAL_NAME_free(GENERAL_NAME *a); - -#define q_sk_X509_num(st) q_SKM_sk_num((st)) -#define q_sk_X509_value(st, i) q_SKM_sk_value(X509, (st), (i)) -#define q_sk_SSL_CIPHER_num(st) q_SKM_sk_num((st)) -#define q_sk_SSL_CIPHER_value(st, i) q_SKM_sk_value(SSL_CIPHER, (st), (i)) -#define q_SSL_CTX_add_extra_chain_cert(ctx,x509) \ - q_SSL_CTX_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)x509) -#define q_EVP_PKEY_assign_RSA(pkey,rsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_RSA,\ - (char *)(rsa)) -#define q_EVP_PKEY_assign_DSA(pkey,dsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_DSA,\ - (char *)(dsa)) -#define q_OpenSSL_add_all_algorithms() q_OPENSSL_add_all_algorithms_conf() - -#if OPENSSL_VERSION_MAJOR < 3 -int q_SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath); -#else -int q_SSL_CTX_load_verify_dir(SSL_CTX *ctx, const char *CApath); -#endif // OPENSSL_VERSION_MAJOR - -int q_i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp); -SSL_SESSION *q_d2i_SSL_SESSION(SSL_SESSION **a, const unsigned char **pp, long length); - -#ifndef OPENSSL_NO_NEXTPROTONEG -int q_SSL_select_next_proto(unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, - const unsigned char *client, unsigned int client_len); -void q_SSL_CTX_set_next_proto_select_cb(SSL_CTX *s, - int (*cb) (SSL *ssl, unsigned char **out, - unsigned char *outlen, - const unsigned char *in, - unsigned int inlen, void *arg), - void *arg); -void q_SSL_get0_next_proto_negotiated(const SSL *s, const unsigned char **data, - unsigned *len); -int q_SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos, - unsigned protos_len); -void q_SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, - int (*cb) (SSL *ssl, - const unsigned char **out, - unsigned char *outlen, - const unsigned char *in, - unsigned int inlen, - void *arg), void *arg); -void q_SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data, - unsigned *len); -#endif // !OPENSSL_NO_NEXTPROTONEG - - -#if QT_CONFIG(dtls) - -extern "C" -{ -typedef int (*CookieGenerateCallback)(SSL *, unsigned char *, unsigned *); -} - -void q_SSL_CTX_set_cookie_generate_cb(SSL_CTX *ctx, CookieGenerateCallback cb); -void q_SSL_CTX_set_cookie_verify_cb(SSL_CTX *ctx, CookieVerifyCallback cb); -const SSL_METHOD *q_DTLS_server_method(); -const SSL_METHOD *q_DTLS_client_method(); - -#endif // dtls - -void *q_X509_STORE_CTX_get_ex_data(X509_STORE_CTX *ctx, int idx); -int q_SSL_get_ex_data_X509_STORE_CTX_idx(); - -#if QT_CONFIG(dtls) -#define q_DTLS_set_link_mtu(ssl, mtu) q_SSL_ctrl((ssl), DTLS_CTRL_SET_LINK_MTU, (mtu), nullptr) -#define q_DTLSv1_get_timeout(ssl, arg) q_SSL_ctrl(ssl, DTLS_CTRL_GET_TIMEOUT, 0, arg) -#define q_DTLSv1_handle_timeout(ssl) q_SSL_ctrl(ssl, DTLS_CTRL_HANDLE_TIMEOUT, 0, nullptr) -#endif // dtls - -void q_BIO_set_flags(BIO *b, int flags); -void q_BIO_clear_flags(BIO *b, int flags); -void *q_BIO_get_ex_data(BIO *b, int idx); -int q_BIO_set_ex_data(BIO *b, int idx, void *data); - -#define q_BIO_set_retry_read(b) q_BIO_set_flags(b, (BIO_FLAGS_READ|BIO_FLAGS_SHOULD_RETRY)) -#define q_BIO_set_retry_write(b) q_BIO_set_flags(b, (BIO_FLAGS_WRITE|BIO_FLAGS_SHOULD_RETRY)) -#define q_BIO_clear_retry_flags(b) q_BIO_clear_flags(b, (BIO_FLAGS_RWS|BIO_FLAGS_SHOULD_RETRY)) -#define q_BIO_set_app_data(s,arg) q_BIO_set_ex_data(s,0,arg) -#define q_BIO_get_app_data(s) q_BIO_get_ex_data(s,0) - -// Helper function -class QDateTime; -QDateTime q_getTimeFromASN1(const ASN1_TIME *aTime); - -#define q_SSL_set_tlsext_status_type(ssl, type) \ - q_SSL_ctrl((ssl), SSL_CTRL_SET_TLSEXT_STATUS_REQ_TYPE, (type), nullptr) - -#if QT_CONFIG(ocsp) - -OCSP_RESPONSE *q_d2i_OCSP_RESPONSE(OCSP_RESPONSE **a, const unsigned char **in, long len); -Q_AUTOTEST_EXPORT int q_i2d_OCSP_RESPONSE(OCSP_RESPONSE *r, unsigned char **ppout); -Q_AUTOTEST_EXPORT OCSP_RESPONSE *q_OCSP_response_create(int status, OCSP_BASICRESP *bs); -Q_AUTOTEST_EXPORT void q_OCSP_RESPONSE_free(OCSP_RESPONSE *rs); -int q_OCSP_response_status(OCSP_RESPONSE *resp); -OCSP_BASICRESP *q_OCSP_response_get1_basic(OCSP_RESPONSE *resp); -Q_AUTOTEST_EXPORT OCSP_SINGLERESP *q_OCSP_basic_add1_status(OCSP_BASICRESP *rsp, OCSP_CERTID *cid, - int status, int reason, ASN1_TIME *revtime, - ASN1_TIME *thisupd, ASN1_TIME *nextupd); -Q_AUTOTEST_EXPORT int q_OCSP_basic_sign(OCSP_BASICRESP *brsp, X509 *signer, EVP_PKEY *key, const EVP_MD *dgst, - STACK_OF(X509) *certs, unsigned long flags); -Q_AUTOTEST_EXPORT OCSP_BASICRESP *q_OCSP_BASICRESP_new(); -Q_AUTOTEST_EXPORT void q_OCSP_BASICRESP_free(OCSP_BASICRESP *bs); -int q_OCSP_basic_verify(OCSP_BASICRESP *bs, STACK_OF(X509) *certs, X509_STORE *st, unsigned long flags); -int q_OCSP_resp_count(OCSP_BASICRESP *bs); -OCSP_SINGLERESP *q_OCSP_resp_get0(OCSP_BASICRESP *bs, int idx); -int q_OCSP_single_get0_status(OCSP_SINGLERESP *single, int *reason, ASN1_GENERALIZEDTIME **revtime, - ASN1_GENERALIZEDTIME **thisupd, ASN1_GENERALIZEDTIME **nextupd); -int q_OCSP_check_validity(ASN1_GENERALIZEDTIME *thisupd, ASN1_GENERALIZEDTIME *nextupd, long nsec, long maxsec); -int q_OCSP_id_get0_info(ASN1_OCTET_STRING **piNameHash, ASN1_OBJECT **pmd, ASN1_OCTET_STRING **pikeyHash, - ASN1_INTEGER **pserial, OCSP_CERTID *cid); - -const STACK_OF(X509) *q_OCSP_resp_get0_certs(const OCSP_BASICRESP *bs); -Q_AUTOTEST_EXPORT OCSP_CERTID *q_OCSP_cert_to_id(const EVP_MD *dgst, X509 *subject, X509 *issuer); -Q_AUTOTEST_EXPORT void q_OCSP_CERTID_free(OCSP_CERTID *cid); -int q_OCSP_id_cmp(OCSP_CERTID *a, OCSP_CERTID *b); - -#define q_SSL_get_tlsext_status_ocsp_resp(ssl, arg) \ - q_SSL_ctrl(ssl, SSL_CTRL_GET_TLSEXT_STATUS_REQ_OCSP_RESP, 0, arg) - -#define q_SSL_CTX_set_tlsext_status_cb(ssl, cb) \ - q_SSL_CTX_callback_ctrl(ssl, SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB, GenericCallbackType(cb)) - -# define q_SSL_set_tlsext_status_ocsp_resp(ssl, arg, arglen) \ - q_SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_STATUS_REQ_OCSP_RESP, arglen, arg) - -#endif // ocsp - - -void *q_CRYPTO_malloc(size_t num, const char *file, int line); -#define q_OPENSSL_malloc(num) q_CRYPTO_malloc(num, "", 0) - -void q_SSL_set_info_callback(SSL *ssl, void (*cb) (const SSL *ssl, int type, int val)); -const char *q_SSL_alert_type_string(int value); -const char *q_SSL_alert_desc_string_long(int value); - -int q_SSL_CTX_get_security_level(const SSL_CTX *ctx); -void q_SSL_CTX_set_security_level(SSL_CTX *ctx, int level); - -QT_END_NAMESPACE - -#endif diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h index c90dfc8883..2816e62381 100644 --- a/src/network/ssl/qsslsocket_p.h +++ b/src/network/ssl/qsslsocket_p.h @@ -103,7 +103,7 @@ public: static bool s_loadRootCertsOnDemand; static bool supportsSsl(); - static void ensureInitialized(); + Q_NETWORK_EXPORT static void ensureInitialized(); static QList defaultCiphers(); static QList defaultDtlsCiphers(); @@ -117,7 +117,7 @@ public: static void resetDefaultEllipticCurves(); static QList defaultCaCertificates(); - static QList systemCaCertificates(); + Q_NETWORK_EXPORT static QList systemCaCertificates(); static void setDefaultCaCertificates(const QList &certs); static void addDefaultCaCertificate(const QSslCertificate &cert); static void addDefaultCaCertificates(const QList &certs); @@ -168,7 +168,6 @@ public: Q_NETWORK_PRIVATE_EXPORT static void setRootCertOnDemandLoadingSupported(bool supported); static QTlsBackend *tlsBackendInUse(); - static void registerAdHocFactory(); // Needed by TlsCryptograph: Q_NETWORK_PRIVATE_EXPORT QSslSocket::SslMode tlsMode() const; diff --git a/src/network/ssl/qsslsocket_qt.cpp b/src/network/ssl/qsslsocket_qt.cpp deleted file mode 100644 index 13bbb76367..0000000000 --- a/src/network/ssl/qsslsocket_qt.cpp +++ /dev/null @@ -1,309 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Jeremy Lainé -** 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$ -** -****************************************************************************/ -#include -#include -#include -#include - -#include "qsslsocket_p.h" -#include "qasn1element_p.h" -#include "qsslkey_p.h" - -QT_BEGIN_NAMESPACE - -/* - PKCS12 helpers. -*/ - -static QAsn1Element wrap(quint8 type, const QAsn1Element &child) -{ - QByteArray value; - QDataStream stream(&value, QIODevice::WriteOnly); - child.write(stream); - return QAsn1Element(type, value); -} - -static QAsn1Element _q_PKCS7_data(const QByteArray &data) -{ - QList items; - items << QAsn1Element::fromObjectId("1.2.840.113549.1.7.1"); - items << wrap(QAsn1Element::Context0Type, - QAsn1Element(QAsn1Element::OctetStringType, data)); - return QAsn1Element::fromVector(items); -} - -/*! - PKCS #12 key derivation. - - Some test vectors: - http://www.drh-consultancy.demon.co.uk/test.txt - \internal -*/ -static QByteArray _q_PKCS12_keygen(char id, const QByteArray &salt, const QString &passPhrase, int n, int r) -{ - const int u = 20; - const int v = 64; - - // password formatting - QByteArray passUnicode(passPhrase.size() * 2 + 2, '\0'); - char *p = passUnicode.data(); - for (int i = 0; i < passPhrase.size(); ++i) { - quint16 ch = passPhrase[i].unicode(); - *(p++) = (ch & 0xff00) >> 8; - *(p++) = (ch & 0xff); - } - - // prepare I - QByteArray D(64, id); - QByteArray S, P; - const int sSize = v * ((salt.size() + v - 1) / v); - S.resize(sSize); - for (int i = 0; i < sSize; ++i) - S[i] = salt[i % salt.size()]; - const int pSize = v * ((passUnicode.size() + v - 1) / v); - P.resize(pSize); - for (int i = 0; i < pSize; ++i) - P[i] = passUnicode[i % passUnicode.size()]; - QByteArray I = S + P; - - // apply hashing - const int c = (n + u - 1) / u; - QByteArray A; - QByteArray B; - B.resize(v); - QCryptographicHash hash(QCryptographicHash::Sha1); - for (int i = 0; i < c; ++i) { - // hash r iterations - QByteArray Ai = D + I; - for (int j = 0; j < r; ++j) { - hash.reset(); - hash.addData(Ai); - Ai = hash.result(); - } - - for (int j = 0; j < v; ++j) - B[j] = Ai[j % u]; - - // modify I as Ij = (Ij + B + 1) modulo 2^v - for (int p = 0; p < I.size(); p += v) { - quint8 carry = 1; - for (int j = v - 1; j >= 0; --j) { - quint16 v = quint8(I[p + j]) + quint8(B[j]) + carry; - I[p + j] = v & 0xff; - carry = (v & 0xff00) >> 8; - } - } - A += Ai; - } - return A.left(n); -} - -static QByteArray _q_PKCS12_salt() -{ - QByteArray salt; - salt.resize(8); - for (int i = 0; i < salt.size(); ++i) - salt[i] = (QRandomGenerator::global()->generate() & 0xff); - return salt; -} - -static QByteArray _q_PKCS12_certBag(const QSslCertificate &cert) -{ - QList items; - items << QAsn1Element::fromObjectId("1.2.840.113549.1.12.10.1.3"); - - // certificate - QList certItems; - certItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.22.1"); - certItems << wrap(QAsn1Element::Context0Type, - QAsn1Element(QAsn1Element::OctetStringType, cert.toDer())); - items << wrap(QAsn1Element::Context0Type, - QAsn1Element::fromVector(certItems)); - - // local key id - const QByteArray localKeyId = cert.digest(QCryptographicHash::Sha1); - QList idItems; - idItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.21"); - idItems << wrap(QAsn1Element::SetType, - QAsn1Element(QAsn1Element::OctetStringType, localKeyId)); - items << wrap(QAsn1Element::SetType, QAsn1Element::fromVector(idItems)); - - // dump - QAsn1Element root = wrap(QAsn1Element::SequenceType, QAsn1Element::fromVector(items)); - QByteArray ba; - QDataStream stream(&ba, QIODevice::WriteOnly); - root.write(stream); - return ba; -} - -static QAsn1Element _q_PKCS12_key(const QSslKey &key) -{ - Q_ASSERT(key.algorithm() == QSsl::Rsa || key.algorithm() == QSsl::Dsa); - - QList keyItems; - keyItems << QAsn1Element::fromInteger(0); - QList algoItems; - if (key.algorithm() == QSsl::Rsa) - algoItems << QAsn1Element::fromObjectId(RSA_ENCRYPTION_OID); - else if (key.algorithm() == QSsl::Dsa) - algoItems << QAsn1Element::fromObjectId(DSA_ENCRYPTION_OID); - algoItems << QAsn1Element(QAsn1Element::NullType); - keyItems << QAsn1Element::fromVector(algoItems); - keyItems << QAsn1Element(QAsn1Element::OctetStringType, key.toDer()); - return QAsn1Element::fromVector(keyItems); -} - -static QByteArray _q_PKCS12_shroudedKeyBag(const QSslKey &key, const QString &passPhrase, const QByteArray &localKeyId) -{ - const int iterations = 2048; - QByteArray salt = _q_PKCS12_salt(); - QByteArray cKey = _q_PKCS12_keygen(1, salt, passPhrase, 24, iterations); - QByteArray cIv = _q_PKCS12_keygen(2, salt, passPhrase, 8, iterations); - - // prepare and encrypt data - QByteArray plain; - QDataStream plainStream(&plain, QIODevice::WriteOnly); - _q_PKCS12_key(key).write(plainStream); - QByteArray crypted = QSslKeyPrivate::encrypt(QTlsPrivate::Cipher::DesEde3Cbc, - plain, cKey, cIv); - - QList items; - items << QAsn1Element::fromObjectId("1.2.840.113549.1.12.10.1.2"); - - // key - QList keyItems; - QList algoItems; - algoItems << QAsn1Element::fromObjectId("1.2.840.113549.1.12.1.3"); - QList paramItems; - paramItems << QAsn1Element(QAsn1Element::OctetStringType, salt); - paramItems << QAsn1Element::fromInteger(iterations); - algoItems << QAsn1Element::fromVector(paramItems); - keyItems << QAsn1Element::fromVector(algoItems); - keyItems << QAsn1Element(QAsn1Element::OctetStringType, crypted); - items << wrap(QAsn1Element::Context0Type, - QAsn1Element::fromVector(keyItems)); - - // local key id - QList idItems; - idItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.21"); - idItems << wrap(QAsn1Element::SetType, - QAsn1Element(QAsn1Element::OctetStringType, localKeyId)); - items << wrap(QAsn1Element::SetType, - QAsn1Element::fromVector(idItems)); - - // dump - QAsn1Element root = wrap(QAsn1Element::SequenceType, QAsn1Element::fromVector(items)); - QByteArray ba; - QDataStream stream(&ba, QIODevice::WriteOnly); - root.write(stream); - return ba; -} - -static QByteArray _q_PKCS12_bag(const QList &certs, const QSslKey &key, const QString &passPhrase) -{ - QList items; - - // certs - for (int i = 0; i < certs.size(); ++i) - items << _q_PKCS7_data(_q_PKCS12_certBag(certs[i])); - - // key - if (!key.isNull()) { - const QByteArray localKeyId = certs.first().digest(QCryptographicHash::Sha1); - items << _q_PKCS7_data(_q_PKCS12_shroudedKeyBag(key, passPhrase, localKeyId)); - } - - // dump - QAsn1Element root = QAsn1Element::fromVector(items); - QByteArray ba; - QDataStream stream(&ba, QIODevice::WriteOnly); - root.write(stream); - return ba; -} - -static QAsn1Element _q_PKCS12_mac(const QByteArray &data, const QString &passPhrase) -{ - const int iterations = 2048; - - // salt generation - QByteArray macSalt = _q_PKCS12_salt(); - QByteArray key = _q_PKCS12_keygen(3, macSalt, passPhrase, 20, iterations); - - // HMAC calculation - QMessageAuthenticationCode hmac(QCryptographicHash::Sha1, key); - hmac.addData(data); - - QList algoItems; - algoItems << QAsn1Element::fromObjectId("1.3.14.3.2.26"); - algoItems << QAsn1Element(QAsn1Element::NullType); - - QList digestItems; - digestItems << QAsn1Element::fromVector(algoItems); - digestItems << QAsn1Element(QAsn1Element::OctetStringType, hmac.result()); - - QList macItems; - macItems << QAsn1Element::fromVector(digestItems); - macItems << QAsn1Element(QAsn1Element::OctetStringType, macSalt); - macItems << QAsn1Element::fromInteger(iterations); - return QAsn1Element::fromVector(macItems); -} - -QByteArray _q_makePkcs12(const QList &certs, const QSslKey &key, const QString &passPhrase) -{ - QList items; - - // version - items << QAsn1Element::fromInteger(3); - - // auth safe - const QByteArray data = _q_PKCS12_bag(certs, key, passPhrase); - items << _q_PKCS7_data(data); - - // HMAC - items << _q_PKCS12_mac(data, passPhrase); - - // dump - QAsn1Element root = QAsn1Element::fromVector(items); - QByteArray ba; - QDataStream stream(&ba, QIODevice::WriteOnly); - root.write(stream); - return ba; -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qtls_openssl.cpp b/src/network/ssl/qtls_openssl.cpp deleted file mode 100644 index 90561943f5..0000000000 --- a/src/network/ssl/qtls_openssl.cpp +++ /dev/null @@ -1,1837 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qsslpresharedkeyauthenticator_p.h" -#include "qsslpresharedkeyauthenticator.h" -#include "qsslsocket_openssl_symbols_p.h" -#include "qsslcertificate_p.h" -#include "qx509_openssl_p.h" -#include "qocspresponse_p.h" -#include "qtls_openssl_p.h" -#include "qsslsocket_p.h" - -#ifdef Q_OS_WIN -#include "qwindowscarootfetcher_p.h" -#endif - -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -namespace { - -QSsl::AlertLevel tlsAlertLevel(int value) -{ - using QSsl::AlertLevel; - - if (const char *typeString = q_SSL_alert_type_string(value)) { - // Documented to return 'W' for warning, 'F' for fatal, - // 'U' for unknown. - switch (typeString[0]) { - case 'W': - return AlertLevel::Warning; - case 'F': - return AlertLevel::Fatal; - default:; - } - } - - return AlertLevel::Unknown; -} - -QString tlsAlertDescription(int value) -{ - QString description = QLatin1String(q_SSL_alert_desc_string_long(value)); - if (!description.size()) - description = QLatin1String("no description provided"); - return description; -} - -QSsl::AlertType tlsAlertType(int value) -{ - // In case for some reason openssl gives us a value, - // which is not in our enum actually, we leave it to - // an application to handle (supposedly they have - // if or switch-statements). - return QSsl::AlertType(value & 0xff); -} - -#ifdef Q_OS_WIN - -QSslCertificate findCertificateToFetch(const QList &tlsErrors, bool checkAIA) -{ - QSslCertificate certToFetch; - - for (const auto &tlsError : tlsErrors) { - switch (tlsError.error()) { - case QSslError::UnableToGetLocalIssuerCertificate: // site presented intermediate cert, but root is unknown - case QSslError::SelfSignedCertificateInChain: // site presented a complete chain, but root is unknown - certToFetch = tlsError.certificate(); - break; - case QSslError::SelfSignedCertificate: - case QSslError::CertificateBlacklisted: - //With these errors, we know it will be untrusted so save time by not asking windows - return QSslCertificate{}; - default: -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << tlsError.errorString(); -#endif - //TODO - this part is strange. - break; - } - } - - 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 - return certToFetch; - } - //The only reason we check this extensions is because an application set trusted - //CA certificates explicitly, thus technically disabling CA fetch. So, if it's - //the case and an intermediate certificate is missing, and no extensions is - //present on the leaf certificate - we fail the handshake immediately. - return QSslCertificate{}; - } - - return certToFetch; -} - -#endif // Q_OS_WIN - -} // unnamed namespace - -namespace QTlsPrivate { - -extern "C" { - -int q_X509Callback(int ok, X509_STORE_CTX *ctx) -{ - if (!ok) { - // Store the error and at which depth the error was detected. - - using ErrorListPtr = QList *; - ErrorListPtr errors = nullptr; - - // Error list is attached to either 'SSL' or 'X509_STORE'. - if (X509_STORE *store = q_X509_STORE_CTX_get0_store(ctx)) // We try store first: - errors = ErrorListPtr(q_X509_STORE_get_ex_data(store, 0)); - - if (!errors) { - // Not found on store? Try SSL and its external data then. According to the OpenSSL's - // documentation: - // - // "Whenever a X509_STORE_CTX object is created for the verification of the - // peer's certificate during a handshake, a pointer to the SSL object is - // stored into the X509_STORE_CTX object to identify the connection affected. - // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be - // used with the correct index." - const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData - + TlsCryptographOpenSSL::errorOffsetInExData; - if (SSL *ssl = static_cast(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx()))) - errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset)); - } - - if (!errors) { - qCWarning(lcTlsBackend, "Neither X509_STORE, nor SSL contains error list, handshake failure"); - return 0; - } - - errors->append(X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); - } - // Always return OK to allow verification to continue. We handle the - // errors gracefully after collecting all errors, after verification has - // completed. - return 1; -} - -int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx) -{ - // Passed to SSL_CTX_set_verify() - // https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_verify.html - // Returns 0 to abort verification, 1 to continue. - - // This is a new, experimental verification callback, reporting - // errors immediately and returning 0 or 1 depending on an application - // either ignoring or not ignoring verification errors as they come. - if (!ctx) { - qCWarning(lcSsl, "Invalid store context (nullptr)"); - return 0; - } - - if (!ok) { - // "Whenever a X509_STORE_CTX object is created for the verification of the - // peer's certificate during a handshake, a pointer to the SSL object is - // stored into the X509_STORE_CTX object to identify the connection affected. - // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be - // used with the correct index." - SSL *ssl = static_cast(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx())); - if (!ssl) { - qCWarning(lcTlsBackend, "No external data (SSL) found in X509 store object"); - return 0; - } - - const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData - + TlsCryptographOpenSSL::socketOffsetInExData; - auto crypto = static_cast(q_SSL_get_ex_data(ssl, offset)); - if (!crypto) { - qCWarning(lcTlsBackend, "No external data (TlsCryptographOpenSSL) found in SSL object"); - return 0; - } - - return crypto->emitErrorFromCallback(ctx); - } - return 1; -} - -#ifndef OPENSSL_NO_PSK -static unsigned q_ssl_psk_client_callback(SSL *ssl, const char *hint, char *identity, unsigned max_identity_len, - unsigned char *psk, unsigned max_psk_len) -{ - auto *tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); - return tls->pskClientTlsCallback(hint, identity, max_identity_len, psk, max_psk_len); -} - -static unsigned int q_ssl_psk_server_callback(SSL *ssl, const char *identity, unsigned char *psk, - unsigned int max_psk_len) -{ - auto *tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); - Q_ASSERT(tls); - return tls->pskServerTlsCallback(identity, psk, max_psk_len); -} - -#ifdef TLS1_3_VERSION -static unsigned q_ssl_psk_restore_client(SSL *ssl, const char *hint, char *identity, unsigned max_identity_len, - unsigned char *psk, unsigned max_psk_len) -{ - Q_UNUSED(hint); - Q_UNUSED(identity); - Q_UNUSED(max_identity_len); - Q_UNUSED(psk); - Q_UNUSED(max_psk_len); - -#ifdef QT_DEBUG - auto tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); - Q_ASSERT(tls); - Q_ASSERT(tls->d); - Q_ASSERT(tls->d->tlsMode() == QSslSocket::SslClientMode); -#endif - q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); - - return 0; -} - -static int q_ssl_psk_use_session_callback(SSL *ssl, const EVP_MD *md, const unsigned char **id, - size_t *idlen, SSL_SESSION **sess) -{ - Q_UNUSED(ssl); - Q_UNUSED(md); - Q_UNUSED(id); - Q_UNUSED(idlen); - Q_UNUSED(sess); - -#ifdef QT_DEBUG - auto *tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); - Q_ASSERT(tls); - Q_ASSERT(tls->d); - Q_ASSERT(tls->d->tlsMode() == QSslSocket::SslClientMode); -#endif - - // Temporarily rebind the psk because it will be called next. The function will restore it. - q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_restore_client); - - return 1; // need to return 1 or else "the connection setup fails." -} - -int q_ssl_sess_set_new_cb(SSL *ssl, SSL_SESSION *session) -{ - if (!ssl) { - qCWarning(lcTlsBackend, "Invalid SSL (nullptr)"); - return 0; - } - if (!session) { - qCWarning(lcTlsBackend, "Invalid SSL_SESSION (nullptr)"); - return 0; - } - - auto *tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); - Q_ASSERT(tls); - return tls->handleNewSessionTicket(ssl); -} -#endif // TLS1_3_VERSION - -#endif // !OPENSSL_NO_PSK - -#if QT_CONFIG(ocsp) - -int qt_OCSP_status_server_callback(SSL *ssl, void *ocspRequest) -{ - Q_UNUSED(ocspRequest); - if (!ssl) - return SSL_TLSEXT_ERR_ALERT_FATAL; - - auto crypto = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); - if (!crypto) - return SSL_TLSEXT_ERR_ALERT_FATAL; - - Q_ASSERT(crypto->d); - Q_ASSERT(crypto->d->tlsMode() == QSslSocket::SslServerMode); - const QByteArray &response = crypto->ocspResponseDer; - Q_ASSERT(response.size()); - - unsigned char *derCopy = static_cast(q_OPENSSL_malloc(size_t(response.size()))); - if (!derCopy) - return SSL_TLSEXT_ERR_ALERT_FATAL; - - std::copy(response.data(), response.data() + response.size(), derCopy); - // We don't check the return value: internally OpenSSL simply assignes the - // pointer (it assumes it now owns this memory btw!) and the length. - q_SSL_set_tlsext_status_ocsp_resp(ssl, derCopy, response.size()); - - return SSL_TLSEXT_ERR_OK; -} - -#endif // ocsp - -void qt_AlertInfoCallback(const SSL *connection, int from, int value) -{ - // Passed to SSL_set_info_callback() - // https://www.openssl.org/docs/man1.1.1/man3/SSL_set_info_callback.html - - if (!connection) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend, "Invalid 'connection' parameter (nullptr)"); -#endif // QSSLSOCKET_DEBUG - return; - } - - const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData - + TlsCryptographOpenSSL::socketOffsetInExData; - auto crypto = static_cast(q_SSL_get_ex_data(connection, offset)); - if (!crypto) { - // SSL_set_ex_data can fail: -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend, "No external data (socket backend) found for parameter 'connection'"); -#endif // QSSLSOCKET_DEBUG - return; - } - - if (!(from & SSL_CB_ALERT)) { - // We only want to know about alerts (at least for now). - return; - } - - if (from & SSL_CB_WRITE) - crypto->alertMessageSent(value); - else - crypto->alertMessageReceived(value); -} - -} // extern "C" - -#if QT_CONFIG(ocsp) -namespace { - -QSslError::SslError qt_OCSP_response_status_to_SslError(long code) -{ - switch (code) { - case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST: - return QSslError::OcspMalformedRequest; - case OCSP_RESPONSE_STATUS_INTERNALERROR: - return QSslError::OcspInternalError; - case OCSP_RESPONSE_STATUS_TRYLATER: - return QSslError::OcspTryLater; - case OCSP_RESPONSE_STATUS_SIGREQUIRED: - return QSslError::OcspSigRequred; - case OCSP_RESPONSE_STATUS_UNAUTHORIZED: - return QSslError::OcspUnauthorized; - case OCSP_RESPONSE_STATUS_SUCCESSFUL: - default: - return {}; - } - Q_UNREACHABLE(); -} - -QOcspRevocationReason qt_OCSP_revocation_reason(int reason) -{ - switch (reason) { - case OCSP_REVOKED_STATUS_NOSTATUS: - return QOcspRevocationReason::None; - case OCSP_REVOKED_STATUS_UNSPECIFIED: - return QOcspRevocationReason::Unspecified; - case OCSP_REVOKED_STATUS_KEYCOMPROMISE: - return QOcspRevocationReason::KeyCompromise; - case OCSP_REVOKED_STATUS_CACOMPROMISE: - return QOcspRevocationReason::CACompromise; - case OCSP_REVOKED_STATUS_AFFILIATIONCHANGED: - return QOcspRevocationReason::AffiliationChanged; - case OCSP_REVOKED_STATUS_SUPERSEDED: - return QOcspRevocationReason::Superseded; - case OCSP_REVOKED_STATUS_CESSATIONOFOPERATION: - return QOcspRevocationReason::CessationOfOperation; - case OCSP_REVOKED_STATUS_CERTIFICATEHOLD: - return QOcspRevocationReason::CertificateHold; - case OCSP_REVOKED_STATUS_REMOVEFROMCRL: - return QOcspRevocationReason::RemoveFromCRL; - default: - return QOcspRevocationReason::None; - } - - Q_UNREACHABLE(); -} - -bool qt_OCSP_certificate_match(OCSP_SINGLERESP *singleResponse, X509 *peerCert, X509 *issuer) -{ - // OCSP_basic_verify does verify that the responder is legit, the response is - // correctly signed, CertID is correct. But it does not know which certificate - // we were presented with by our peer, so it does not check if it's a response - // for our peer's certificate. - Q_ASSERT(singleResponse && peerCert && issuer); - - const OCSP_CERTID *certId = q_OCSP_SINGLERESP_get0_id(singleResponse); // Does not increment refcount. - if (!certId) { - qCWarning(lcSsl, "A SingleResponse without CertID"); - return false; - } - - ASN1_OBJECT *md = nullptr; - ASN1_INTEGER *reportedSerialNumber = nullptr; - const int result = q_OCSP_id_get0_info(nullptr, &md, nullptr, &reportedSerialNumber, const_cast(certId)); - if (result != 1 || !md || !reportedSerialNumber) { - qCWarning(lcSsl, "Failed to extract a hash and serial number from CertID structure"); - return false; - } - - if (!q_X509_get_serialNumber(peerCert)) { - // Is this possible at all? But we have to check this, - // ASN1_INTEGER_cmp (called from OCSP_id_cmp) dereferences - // without any checks at all. - qCWarning(lcSsl, "No serial number in peer's ceritificate"); - return false; - } - - const int nid = q_OBJ_obj2nid(md); - if (nid == NID_undef) { - qCWarning(lcSsl, "Unknown hash algorithm in CertID"); - return false; - } - - const EVP_MD *digest = q_EVP_get_digestbynid(nid); // Does not increment refcount. - if (!digest) { - qCWarning(lcSsl) << "No digest for nid" << nid; - return false; - } - - OCSP_CERTID *recreatedId = q_OCSP_cert_to_id(digest, peerCert, issuer); - if (!recreatedId) { - qCWarning(lcSsl, "Failed to re-create CertID"); - return false; - } - const QSharedPointer guard(recreatedId, q_OCSP_CERTID_free); - - if (q_OCSP_id_cmp(const_cast(certId), recreatedId)) { - qDebug(lcSsl, "Certificate ID mismatch"); - return false; - } - // Bingo! - return true; -} - -} // unnamed namespace -#endif // ocsp - -TlsCryptographOpenSSL::~TlsCryptographOpenSSL() -{ - destroySslContext(); -} - -void TlsCryptographOpenSSL::init(QSslSocket *qObj, QSslSocketPrivate *dObj) -{ - Q_ASSERT(qObj); - Q_ASSERT(dObj); - q = qObj; - d = dObj; - - ocspResponses.clear(); - ocspResponseDer.clear(); - - systemOrSslErrorDetected = false; - handshakeInterrupted = false; - - fetchAuthorityInformation = false; - caToFetch = QSslCertificate{}; -} - -void TlsCryptographOpenSSL::checkSettingSslContext(QSharedPointer tlsContext) -{ - if (sslContextPointer.isNull()) - sslContextPointer = tlsContext; -} - -QSharedPointer TlsCryptographOpenSSL::sslContext() const -{ - return sslContextPointer; -} - -QList TlsCryptographOpenSSL::tlsErrors() const -{ - return sslErrors; -} - -void TlsCryptographOpenSSL::startClientEncryption() -{ - if (!initSslContext()) { - Q_ASSERT(d); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Unable to init SSL Context: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); - return; - } - - // Start connecting. This will place outgoing data in the BIO, so we - // follow up with calling transmit(). - startHandshake(); - transmit(); -} - -void TlsCryptographOpenSSL::startServerEncryption() -{ - if (!initSslContext()) { - Q_ASSERT(d); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Unable to init SSL Context: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); - return; - } - - // Start connecting. This will place outgoing data in the BIO, so we - // follow up with calling transmit(). - startHandshake(); - transmit(); -} - -bool TlsCryptographOpenSSL::startHandshake() -{ - // Check if the connection has been established. Get all errors from the - // verification stage. - Q_ASSERT(q); - Q_ASSERT(d); - - using ScopedBool = QScopedValueRollback; - - if (inSetAndEmitError) - return false; - - const auto mode = d->tlsMode(); - - pendingFatalAlert = false; - errorsReportedFromCallback = false; - QList lastErrors; - q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + errorOffsetInExData, &lastErrors); - - // SSL_set_ex_data can fail, but see the callback's code - we handle this there. - q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + socketOffsetInExData, this); - q_SSL_set_info_callback(ssl, qt_AlertInfoCallback); - - int result = (mode == QSslSocket::SslClientMode) ? q_SSL_connect(ssl) : q_SSL_accept(ssl); - q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + errorOffsetInExData, nullptr); - // Note, unlike errors as external data on SSL object, we do not unset - // a callback/ex-data if alert notifications are enabled: an alert can - // arrive after the handshake, for example, this happens when the server - // does not find a ClientCert or does not like it. - - if (!lastErrors.isEmpty() || errorsReportedFromCallback) - storePeerCertificates(); - - // storePeerCertificate() if called above - would update the - // configuration with peer's certificates. - auto configuration = q->sslConfiguration(); - if (!errorsReportedFromCallback) { - const auto &peerCertificateChain = configuration.peerCertificateChain(); - for (const auto ¤tError : qAsConst(lastErrors)) { - emit q->peerVerifyError(QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(currentError.code, - peerCertificateChain.value(currentError.depth))); - if (q->state() != QAbstractSocket::ConnectedState) - break; - } - } - - errorList << lastErrors; - - // Connection aborted during handshake phase. - if (q->state() != QAbstractSocket::ConnectedState) - return false; - - // Check if we're encrypted or not. - if (result <= 0) { - switch (q_SSL_get_error(ssl, result)) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - // The handshake is not yet complete. - break; - default: - QString errorString = QTlsBackendOpenSSL::msgErrorsDuringHandshake(); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::startHandshake: error!" << errorString; -#endif - { - const ScopedBool bg(inSetAndEmitError, true); - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, errorString); - if (pendingFatalAlert) { - trySendFatalAlert(); - pendingFatalAlert = false; - } - } - q->abort(); - } - return false; - } - - // store peer certificate chain - storePeerCertificates(); - - // Start translating errors. - QList errors; - - // Note, the storePeerCerificates() probably updated the configuration at this point. - configuration = q->sslConfiguration(); - // Check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer) - const auto &peerCertificateChain = configuration.peerCertificateChain(); - for (const QSslCertificate &cert : peerCertificateChain) { - if (QSslCertificatePrivate::isBlacklisted(cert)) { - QSslError error(QSslError::CertificateBlacklisted, cert); - errors << error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - - const bool doVerifyPeer = configuration.peerVerifyMode() == QSslSocket::VerifyPeer - || (configuration.peerVerifyMode() == QSslSocket::AutoVerifyPeer - && mode == QSslSocket::SslClientMode); - -#if QT_CONFIG(ocsp) - // For now it's always QSslSocket::SslClientMode - initSslContext() will bail out early, - // if it's enabled in QSslSocket::SslServerMode. This can change. - if (!configuration.peerCertificate().isNull() && configuration.ocspStaplingEnabled() && doVerifyPeer) { - if (!checkOcspStatus()) { - if (ocspErrors.isEmpty()) { - { - const ScopedBool bg(inSetAndEmitError, true); - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, ocspErrorDescription); - } - q->abort(); - return false; - } - - for (const QSslError &error : ocspErrors) { - errors << error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - } -#endif // ocsp - - // Check the peer certificate itself. First try the subject's common name - // (CN) as a wildcard, then try all alternate subject name DNS entries the - // same way. - if (!configuration.peerCertificate().isNull()) { - // but only if we're a client connecting to a server - // if we're the server, don't check CN - const auto verificationPeerName = d->verificationName(); - if (mode == QSslSocket::SslClientMode) { - QString peerName = (verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); - - if (!isMatchingHostname(configuration.peerCertificate(), peerName)) { - // No matches in common names or alternate names. - QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate()); - errors << error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - } else { - // No peer certificate presented. Report as error if the socket - // expected one. - if (doVerifyPeer) { - QSslError error(QSslError::NoPeerCertificate); - errors << error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - - // Translate errors from the error list into QSslErrors. - errors.reserve(errors.size() + errorList.size()); - for (const auto &error : qAsConst(errorList)) - errors << X509CertificateOpenSSL::openSSLErrorToQSslError(error.code, peerCertificateChain.value(error.depth)); - - if (!errors.isEmpty()) { - sslErrors = errors; -#ifdef Q_OS_WIN - const bool fetchEnabled = QSslSocketPrivate::rootCertOnDemandLoadingSupported() - && d->isRootsOnDemandAllowed(); - // !fetchEnabled is a special case scenario, when we potentially have a missing - // intermediate certificate and a recoverable chain, but on demand cert loading - // was disabled by setCaCertificates call. For this scenario we check if "Authority - // Information Access" is present - wincrypt can deal with such certificates. - QSslCertificate certToFetch; - if (doVerifyPeer && !d->verifyErrorsHaveBeenIgnored()) - certToFetch = findCertificateToFetch(sslErrors, !fetchEnabled); - - //Skip this if not using system CAs, or if the SSL errors are configured in advance to be ignorable - if (!certToFetch.isNull()) { - fetchAuthorityInformation = !fetchEnabled; - //Windows desktop versions starting from vista ship with minimal set of roots and download on demand - //from the windows update server CA roots that are trusted by MS. It also can fetch a missing intermediate - //in case "Authority Information Access" extension is present. - // - //However, this is only transparent if using WinINET - we have to trigger it - //ourselves. - fetchCaRootForCert(certToFetch); - return false; - } -#endif // Q_OS_WIN - if (!checkSslErrors()) - return false; - // A slot, attached to sslErrors signal can call - // abort/close/disconnetFromHost/etc; no need to - // continue handshake then. - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } else { - sslErrors.clear(); - } - - continueHandshake(); - return true; -} - -void TlsCryptographOpenSSL::enableHandshakeContinuation() -{ - handshakeInterrupted = false; -} - -void TlsCryptographOpenSSL::cancelCAFetch() -{ - fetchAuthorityInformation = false; - caToFetch = QSslCertificate{}; -} - -void TlsCryptographOpenSSL::continueHandshake() -{ - Q_ASSERT(q); - Q_ASSERT(d); - - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - const auto mode = d->tlsMode(); - - // if we have a max read buffer size, reset the plain socket's to match - if (const auto maxSize = d->maxReadBufferSize()) - plainSocket->setReadBufferSize(maxSize); - - if (q_SSL_session_reused(ssl)) - QTlsBackend::setPeerSessionShared(d, true); - -#ifdef QT_DECRYPT_SSL_TRAFFIC - if (q_SSL_get_session(ssl)) { - size_t master_key_len = q_SSL_SESSION_get_master_key(q_SSL_get_session(ssl), nullptr, 0); - size_t client_random_len = q_SSL_get_client_random(ssl, nullptr, 0); - QByteArray masterKey(int(master_key_len), Qt::Uninitialized); // Will not overflow - QByteArray clientRandom(int(client_random_len), Qt::Uninitialized); // Will not overflow - - q_SSL_SESSION_get_master_key(q_SSL_get_session(ssl), - reinterpret_cast(masterKey.data()), - masterKey.size()); - q_SSL_get_client_random(ssl, reinterpret_cast(clientRandom.data()), - clientRandom.size()); - - QByteArray debugLineClientRandom("CLIENT_RANDOM "); - debugLineClientRandom.append(clientRandom.toHex().toUpper()); - debugLineClientRandom.append(" "); - debugLineClientRandom.append(masterKey.toHex().toUpper()); - debugLineClientRandom.append("\n"); - - QString sslKeyFile = QDir::tempPath() + QLatin1String("/qt-ssl-keys"); - QFile file(sslKeyFile); - if (!file.open(QIODevice::Append)) - qCWarning(lcSsl) << "could not open file" << sslKeyFile << "for appending"; - if (!file.write(debugLineClientRandom)) - qCWarning(lcSsl) << "could not write to file" << sslKeyFile; - file.close(); - } else { - qCWarning(lcTlsBackend, "could not decrypt SSL traffic"); - } -#endif // QT_DECRYPT_SSL_TRAFFIC - - const auto &configuration = q->sslConfiguration(); - // Cache this SSL session inside the QSslContext - if (!(configuration.testSslOption(QSsl::SslOptionDisableSessionSharing))) { - if (!sslContextPointer->cacheSession(ssl)) { - sslContextPointer.clear(); // we could not cache the session - } else { - // Cache the session for permanent usage as well - if (!(configuration.testSslOption(QSsl::SslOptionDisableSessionPersistence))) { - if (!sslContextPointer->sessionASN1().isEmpty()) - QTlsBackend::setSessionAsn1(d, sslContextPointer->sessionASN1()); - QTlsBackend::setSessionLifetimeHint(d, sslContextPointer->sessionTicketLifeTimeHint()); - } - } - } - -#if !defined(OPENSSL_NO_NEXTPROTONEG) - - QTlsBackend::setAlpnStatus(d, sslContextPointer->npnContext().status); - if (sslContextPointer->npnContext().status == QSslConfiguration::NextProtocolNegotiationUnsupported) { - // we could not agree -> be conservative and use HTTP/1.1 - // T.P.: I have to admit, this is a really strange notion of 'conservative', - // given the protocol-neutral nature of ALPN/NPN. - QTlsBackend::setNegotiatedProtocol(d, QByteArrayLiteral("http/1.1")); - } else { - const unsigned char *proto = nullptr; - unsigned int proto_len = 0; - - q_SSL_get0_alpn_selected(ssl, &proto, &proto_len); - if (proto_len && mode == QSslSocket::SslClientMode) { - // Client does not have a callback that sets it ... - QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNegotiated); - } - - if (!proto_len) { // Test if NPN was more lucky ... - q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len); - } - - if (proto_len) - QTlsBackend::setNegotiatedProtocol(d, QByteArray(reinterpret_cast(proto), proto_len)); - else - QTlsBackend::setNegotiatedProtocol(d,{}); - } -#endif // !defined(OPENSSL_NO_NEXTPROTONEG) - - if (mode == QSslSocket::SslClientMode) { - EVP_PKEY *key; - if (q_SSL_get_server_tmp_key(ssl, &key)) - QTlsBackend::setEphemeralKey(d, QSslKey(key, QSsl::PublicKey)); - } - - d->setEncrypted(true); - emit q->encrypted(); - if (d->isAutoStartingHandshake() && d->isPendingClose()) { - d->setPendingClose(false); - q->disconnectFromHost(); - } -} - -void TlsCryptographOpenSSL::transmit() -{ - Q_ASSERT(q); - Q_ASSERT(d); - - using ScopedBool = QScopedValueRollback; - - if (inSetAndEmitError) - return; - - // If we don't have any SSL context, don't bother transmitting. - if (!ssl) - return; - - auto &writeBuffer = d->tlsWriteBuffer(); - auto &buffer = d->tlsBuffer(); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - bool &emittedBytesWritten = d->tlsEmittedBytesWritten(); - - bool transmitting; - do { - transmitting = false; - - // If the connection is secure, we can transfer data from the write - // buffer (in plain text) to the write BIO through SSL_write. - if (q->isEncrypted() && !writeBuffer.isEmpty()) { - qint64 totalBytesWritten = 0; - int nextDataBlockSize; - while ((nextDataBlockSize = writeBuffer.nextDataBlockSize()) > 0) { - int writtenBytes = q_SSL_write(ssl, writeBuffer.readPointer(), nextDataBlockSize); - if (writtenBytes <= 0) { - int error = q_SSL_get_error(ssl, writtenBytes); - //write can result in a want_write_error - not an error - continue transmitting - if (error == SSL_ERROR_WANT_WRITE) { - transmitting = true; - break; - } else if (error == SSL_ERROR_WANT_READ) { - //write can result in a want_read error, possibly due to renegotiation - not an error - stop transmitting - transmitting = false; - break; - } else { - // ### Better error handling. - const ScopedBool bg(inSetAndEmitError, true); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Unable to write data: %1").arg( - QTlsBackendOpenSSL::getErrorsFromOpenSsl())); - return; - } - } -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encrypted" << writtenBytes << "bytes"; -#endif - writeBuffer.free(writtenBytes); - totalBytesWritten += writtenBytes; - - if (writtenBytes < nextDataBlockSize) { - // break out of the writing loop and try again after we had read - transmitting = true; - break; - } - } - - if (totalBytesWritten > 0) { - // Don't emit bytesWritten() recursively. - if (!emittedBytesWritten) { - emittedBytesWritten = true; - emit q->bytesWritten(totalBytesWritten); - emittedBytesWritten = false; - } - emit q->channelBytesWritten(0, totalBytesWritten); - } - } - - // Check if we've got any data to be written to the socket. - QVarLengthArray data; - int pendingBytes; - while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0 - && plainSocket->openMode() != QIODevice::NotOpen) { - // Read encrypted data from the write BIO into a buffer. - data.resize(pendingBytes); - int encryptedBytesRead = q_BIO_read(writeBio, data.data(), pendingBytes); - - // Write encrypted data from the buffer to the socket. - qint64 actualWritten = plainSocket->write(data.constData(), encryptedBytesRead); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: wrote" << encryptedBytesRead - << "encrypted bytes to the socket" << actualWritten << "actual."; -#endif - if (actualWritten < 0) { - //plain socket write fails if it was in the pending close state. - const ScopedBool bg(inSetAndEmitError, true); - d->setErrorAndEmit(plainSocket->error(), plainSocket->errorString()); - return; - } - transmitting = true; - } - - // Check if we've got any data to be read from the socket. - if (!q->isEncrypted() || !d->maxReadBufferSize() || buffer.size() < d->maxReadBufferSize()) - while ((pendingBytes = plainSocket->bytesAvailable()) > 0) { - // Read encrypted data from the socket into a buffer. - data.resize(pendingBytes); - // just peek() here because q_BIO_write could write less data than expected - int encryptedBytesRead = plainSocket->peek(data.data(), pendingBytes); - -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: read" << encryptedBytesRead << "encrypted bytes from the socket"; -#endif - // Write encrypted data from the buffer into the read BIO. - int writtenToBio = q_BIO_write(readBio, data.constData(), encryptedBytesRead); - - // Throw away the results. - if (writtenToBio > 0) { - plainSocket->skip(writtenToBio); - } else { - // ### Better error handling. - const ScopedBool bg(inSetAndEmitError, true); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Unable to decrypt data: %1") - .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); - return; - } - - transmitting = true; - } - - // If the connection isn't secured yet, this is the time to retry the - // connect / accept. - if (!q->isEncrypted()) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: testing encryption"; -#endif - if (startHandshake()) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encryption established"; -#endif - d->setEncrypted(true); - transmitting = true; - } else if (plainSocket->state() != QAbstractSocket::ConnectedState) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: connection lost"; -#endif - break; - } else if (d->isPaused()) { - // just wait until the user continues - return; - } else { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encryption not done yet"; -#endif - } - } - - // If the request is small and the remote host closes the transmission - // after sending, there's a chance that startHandshake() will already - // have triggered a shutdown. - if (!ssl) - continue; - - // We always read everything from the SSL decryption buffers, even if - // we have a readBufferMaxSize. There's no point in leaving data there - // just so that readBuffer.size() == readBufferMaxSize. - int readBytes = 0; - const int bytesToRead = 4096; - do { - if (q->readChannelCount() == 0) { - // The read buffer is deallocated, don't try resize or write to it. - break; - } - // Don't use SSL_pending(). It's very unreliable. - readBytes = q_SSL_read(ssl, buffer.reserve(bytesToRead), bytesToRead); - if (readBytes > 0) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: decrypted" << readBytes << "bytes"; -#endif - buffer.chop(bytesToRead - readBytes); - - if (bool *readyReadEmittedPointer = d->readyReadPointer()) - *readyReadEmittedPointer = true; - emit q->readyRead(); - emit q->channelReadyRead(0); - transmitting = true; - continue; - } - buffer.chop(bytesToRead); - - // Error. - switch (q_SSL_get_error(ssl, readBytes)) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - // Out of data. - break; - case SSL_ERROR_ZERO_RETURN: - // The remote host closed the connection. -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: remote disconnect"; -#endif - shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves - { - const ScopedBool bg(inSetAndEmitError, true); - d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, - QSslSocket::tr("The TLS/SSL connection has been closed")); - } - return; - case SSL_ERROR_SYSCALL: // some IO error - case SSL_ERROR_SSL: // error in the SSL library - // we do not know exactly what the error is, nor whether we can recover from it, - // so just return to prevent an endless loop in the outer "while" statement - systemOrSslErrorDetected = true; - { - const ScopedBool bg(inSetAndEmitError, true); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error while reading: %1") - .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); - } - return; - default: - // SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: can only happen with a - // BIO_s_connect() or BIO_s_accept(), which we do not call. - // SSL_ERROR_WANT_X509_LOOKUP: can only happen with a - // SSL_CTX_set_client_cert_cb(), which we do not call. - // So this default case should never be triggered. - { - const ScopedBool bg(inSetAndEmitError, true); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error while reading: %1") - .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); - } - break; - } - } while (ssl && readBytes > 0); - } while (ssl && transmitting); -} - -void TlsCryptographOpenSSL::disconnectFromHost() -{ - if (ssl) { - if (!shutdown && !q_SSL_in_init(ssl) && !systemOrSslErrorDetected) { - if (q_SSL_shutdown(ssl) != 1) { - // Some error may be queued, clear it. - QTlsBackendOpenSSL::clearErrorQueue(); - } - shutdown = true; - transmit(); - } - } - Q_ASSERT(d); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - plainSocket->disconnectFromHost(); -} - -void TlsCryptographOpenSSL::disconnected() -{ - Q_ASSERT(d); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - if (plainSocket->bytesAvailable() <= 0) { - destroySslContext(); - } else { - // Move all bytes into the plain buffer. - const qint64 tmpReadBufferMaxSize = d->maxReadBufferSize(); - // Reset temporarily, so the plain socket buffer is completely drained: - d->setMaxReadBufferSize(0); - transmit(); - d->setMaxReadBufferSize(tmpReadBufferMaxSize); - } - //if there is still buffered data in the plain socket, don't destroy the ssl context yet. - //it will be destroyed when the socket is deleted. -} - -QSslCipher TlsCryptographOpenSSL::sessionCipher() const -{ - if (!ssl) - return {}; - - const SSL_CIPHER *sessionCipher = q_SSL_get_current_cipher(ssl); - return sessionCipher ? QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(sessionCipher) : QSslCipher{}; -} - -QSsl::SslProtocol TlsCryptographOpenSSL::sessionProtocol() const -{ - if (!ssl) - return QSsl::UnknownProtocol; - - const int ver = q_SSL_version(ssl); - switch (ver) { - case 0x301: - return QSsl::TlsV1_0; - case 0x302: - return QSsl::TlsV1_1; - case 0x303: - return QSsl::TlsV1_2; - case 0x304: - return QSsl::TlsV1_3; - } - - return QSsl::UnknownProtocol; -} - -QList TlsCryptographOpenSSL::ocsps() const -{ - return ocspResponses; -} - -bool TlsCryptographOpenSSL::checkSslErrors() -{ - Q_ASSERT(q); - Q_ASSERT(d); - - if (sslErrors.isEmpty()) - return true; - - emit q->sslErrors(sslErrors); - - const auto vfyMode = q->peerVerifyMode(); - const auto mode = d->tlsMode(); - - bool doVerifyPeer = vfyMode == QSslSocket::VerifyPeer || (vfyMode == QSslSocket::AutoVerifyPeer - && mode == QSslSocket::SslClientMode); - bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); - // check whether we need to emit an SSL handshake error - if (doVerifyPeer && doEmitSslError) { - if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { - QSslSocketPrivate::pauseSocketNotifiers(q); - d->setPaused(true); - } else { - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, sslErrors.constFirst().errorString()); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - plainSocket->disconnectFromHost(); - } - return false; - } - return true; -} - -int TlsCryptographOpenSSL::handleNewSessionTicket(SSL *connection) -{ - // If we return 1, this means we own the session, but we don't. - // 0 would tell OpenSSL to deref (but they still have it in the - // internal cache). - Q_ASSERT(connection); - - Q_ASSERT(q); - Q_ASSERT(d); - - if (q->sslConfiguration().testSslOption(QSsl::SslOptionDisableSessionPersistence)) { - // We silently ignore, do nothing, remove from cache. - return 0; - } - - SSL_SESSION *currentSession = q_SSL_get_session(connection); - if (!currentSession) { - qCWarning(lcTlsBackend, - "New session ticket callback, the session is invalid (nullptr)"); - return 0; - } - - if (q_SSL_version(connection) < 0x304) { - // We only rely on this mechanics with TLS >= 1.3 - return 0; - } - -#ifdef TLS1_3_VERSION - if (!q_SSL_SESSION_is_resumable(currentSession)) { - qCDebug(lcTlsBackend, "New session ticket, but the session is non-resumable"); - return 0; - } -#endif // TLS1_3_VERSION - - const int sessionSize = q_i2d_SSL_SESSION(currentSession, nullptr); - if (sessionSize <= 0) { - qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); - return 0; - } - - // We have somewhat perverse naming, it's not a ticket, it's a session. - QByteArray sessionTicket(sessionSize, 0); - auto data = reinterpret_cast(sessionTicket.data()); - if (!q_i2d_SSL_SESSION(currentSession, &data)) { - qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); - return 0; - } - - QTlsBackend::setSessionAsn1(d, sessionTicket); - QTlsBackend::setSessionLifetimeHint(d, q_SSL_SESSION_get_ticket_lifetime_hint(currentSession)); - - emit q->newSessionTicketReceived(); - return 0; -} - -void TlsCryptographOpenSSL::alertMessageSent(int value) -{ - Q_ASSERT(q); - Q_ASSERT(d); - - const auto level = tlsAlertLevel(value); - if (level == QSsl::AlertLevel::Fatal && !q->isEncrypted()) { - // Note, this logic is handshake-time only: - pendingFatalAlert = true; - } - - emit q->alertSent(level, tlsAlertType(value), tlsAlertDescription(value)); - -} - -void TlsCryptographOpenSSL::alertMessageReceived(int value) -{ - Q_ASSERT(q); - - emit q->alertReceived(tlsAlertLevel(value), tlsAlertType(value), tlsAlertDescription(value)); -} - -int TlsCryptographOpenSSL::emitErrorFromCallback(X509_STORE_CTX *ctx) -{ - // Returns 0 to abort verification, 1 to continue despite error (as - // OpenSSL expects from the verification callback). - Q_ASSERT(q); - Q_ASSERT(ctx); - - using ScopedBool = QScopedValueRollback; - // While we are not setting, we are emitting and in general - - // we want to prevent accidental recursive startHandshake() - // calls: - const ScopedBool bg(inSetAndEmitError, true); - - X509 *x509 = q_X509_STORE_CTX_get_current_cert(ctx); - if (!x509) { - qCWarning(lcTlsBackend, "Could not obtain the certificate (that failed to verify)"); - return 0; - } - - const QSslCertificate certificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); - const auto errorAndDepth = QTlsPrivate::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx); - const QSslError tlsError = QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(errorAndDepth.code, certificate); - - errorsReportedFromCallback = true; - handshakeInterrupted = true; - emit q->handshakeInterruptedOnError(tlsError); - - // Conveniently so, we also can access 'lastErrors' external data set - // in startHandshake, we store it for the case an application later - // wants to check errors (ignored or not): - const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData - + TlsCryptographOpenSSL::errorOffsetInExData; - if (auto errorList = static_cast *>(q_SSL_get_ex_data(ssl, offset))) - errorList->append(errorAndDepth); - - // An application is expected to ignore this error (by calling ignoreSslErrors) - // in its directly connected slot: - return !handshakeInterrupted; -} - -void TlsCryptographOpenSSL::trySendFatalAlert() -{ - Q_ASSERT(pendingFatalAlert); - Q_ASSERT(d); - - auto *plainSocket = d->plainTcpSocket(); - - pendingFatalAlert = false; - QVarLengthArray data; - int pendingBytes = 0; - while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0 - && plainSocket->openMode() != QIODevice::NotOpen) { - // Read encrypted data from the write BIO into a buffer. - data.resize(pendingBytes); - const int bioReadBytes = q_BIO_read(writeBio, data.data(), pendingBytes); - - // Write encrypted data from the buffer to the socket. - qint64 actualWritten = plainSocket->write(data.constData(), bioReadBytes); - if (actualWritten < 0) - return; - plainSocket->flush(); - } -} - -bool TlsCryptographOpenSSL::initSslContext() -{ - Q_ASSERT(q); - Q_ASSERT(d); - - // If no external context was set (e.g. by QHttpNetworkConnection) we will - // create a new one. - const auto mode = d->tlsMode(); - const auto configuration = q->sslConfiguration(); - if (!sslContextPointer) - sslContextPointer = QSslContext::sharedFromConfiguration(mode, configuration, d->isRootsOnDemandAllowed()); - - if (sslContextPointer->error() != QSslError::NoError) { - d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, sslContextPointer->errorString()); - sslContextPointer.clear(); // deletes the QSslContext - return false; - } - - // Create and initialize SSL session - if (!(ssl = sslContextPointer->createSsl())) { - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error creating SSL session, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); - return false; - } - - if (configuration.protocol() != QSsl::UnknownProtocol && mode == QSslSocket::SslClientMode) { - const auto verificationPeerName = d->verificationName(); - // Set server hostname on TLS extension. RFC4366 section 3.1 requires it in ACE format. - QString tlsHostName = verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName; - if (tlsHostName.isEmpty()) - tlsHostName = d->tlsHostName(); - QByteArray ace = QUrl::toAce(tlsHostName); - // only send the SNI header if the URL is valid and not an IP - if (!ace.isEmpty() - && !QHostAddress().setAddress(tlsHostName) - && !(configuration.testSslOption(QSsl::SslOptionDisableServerNameIndication))) { - // We don't send the trailing dot from the host header if present see - // https://tools.ietf.org/html/rfc6066#section-3 - if (ace.endsWith('.')) - ace.chop(1); - if (!q_SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, ace.data())) - qCWarning(lcTlsBackend, "could not set SSL_CTRL_SET_TLSEXT_HOSTNAME, Server Name Indication disabled"); - } - } - - // Clear the session. - errorList.clear(); - - // Initialize memory BIOs for encryption and decryption. - readBio = q_BIO_new(q_BIO_s_mem()); - writeBio = q_BIO_new(q_BIO_s_mem()); - if (!readBio || !writeBio) { - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error creating SSL session: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); - if (readBio) - q_BIO_free(readBio); - if (writeBio) - q_BIO_free(writeBio); - return false; - } - - // Assign the bios. - q_SSL_set_bio(ssl, readBio, writeBio); - - if (mode == QSslSocket::SslClientMode) - q_SSL_set_connect_state(ssl); - else - q_SSL_set_accept_state(ssl); - - q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData, this); - -#ifndef OPENSSL_NO_PSK - // Set the client callback for PSK - if (mode == QSslSocket::SslClientMode) - q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); - else if (mode == QSslSocket::SslServerMode) - q_SSL_set_psk_server_callback(ssl, &q_ssl_psk_server_callback); - -#if OPENSSL_VERSION_NUMBER >= 0x10101006L - // Set the client callback for TLSv1.3 PSK - if (mode == QSslSocket::SslClientMode - && QSslSocket::sslLibraryBuildVersionNumber() >= 0x10101006L) { - q_SSL_set_psk_use_session_callback(ssl, &q_ssl_psk_use_session_callback); - } -#endif // openssl version >= 0x10101006L - -#endif // OPENSSL_NO_PSK - -#if QT_CONFIG(ocsp) - if (configuration.ocspStaplingEnabled()) { - if (mode == QSslSocket::SslServerMode) { - d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, - QSslSocket::tr("Server-side QSslSocket does not support OCSP stapling")); - return false; - } - if (q_SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp) != 1) { - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Failed to enable OCSP stapling")); - return false; - } - } - - ocspResponseDer.clear(); - const auto backendConfig = configuration.backendConfiguration(); - auto responsePos = backendConfig.find("Qt-OCSP-response"); - if (responsePos != backendConfig.end()) { - // This is our private, undocumented 'API' we use for the auto-testing of - // OCSP-stapling. It must be a der-encoded OCSP response, presumably set - // by tst_QOcsp. - const QVariant data(responsePos.value()); - if (data.canConvert()) - ocspResponseDer = data.toByteArray(); - } - - if (ocspResponseDer.size()) { - if (mode != QSslSocket::SslServerMode) { - d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, - QSslSocket::tr("Client-side sockets do not send OCSP responses")); - return false; - } - } -#endif // ocsp - - return true; -} - -void TlsCryptographOpenSSL::destroySslContext() -{ - if (ssl) { - if (!q_SSL_in_init(ssl) && !systemOrSslErrorDetected) { - // We do not send a shutdown alert here. Just mark the session as - // resumable for qhttpnetworkconnection's "optimization", otherwise - // OpenSSL won't start a session resumption. - if (q_SSL_shutdown(ssl) != 1) { - // Some error may be queued, clear it. - const auto errors = QTlsBackendOpenSSL::getErrorsFromOpenSsl(); - Q_UNUSED(errors); - } - } - q_SSL_free(ssl); - ssl = nullptr; - } - sslContextPointer.clear(); -} - -void TlsCryptographOpenSSL::storePeerCertificates() -{ - Q_ASSERT(d); - - // Store the peer certificate and chain. For clients, the peer certificate - // chain includes the peer certificate; for servers, it doesn't. Both the - // peer certificate and the chain may be empty if the peer didn't present - // any certificate. - X509 *x509 = q_SSL_get_peer_certificate(ssl); - - const auto peerCertificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); - QTlsBackend::storePeerCertificate(d, peerCertificate); - q_X509_free(x509); - auto peerCertificateChain = q->peerCertificateChain(); - if (peerCertificateChain.isEmpty()) { - peerCertificateChain = QTlsPrivate::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(q_SSL_get_peer_cert_chain(ssl)); - if (!peerCertificate.isNull() && d->tlsMode() == QSslSocket::SslServerMode) - peerCertificateChain.prepend(peerCertificate); - QTlsBackend::storePeerCertificateChain(d, peerCertificateChain); - } -} - -#if QT_CONFIG(ocsp) - -bool TlsCryptographOpenSSL::checkOcspStatus() -{ - Q_ASSERT(ssl); - Q_ASSERT(d); - - const auto &configuration = q->sslConfiguration(); - Q_ASSERT(d->tlsMode() == QSslSocket::SslClientMode); // See initSslContext() for SslServerMode - Q_ASSERT(configuration.peerVerifyMode() != QSslSocket::VerifyNone); - - const auto clearErrorQueue = qScopeGuard([] { - QTlsBackendOpenSSL::logAndClearErrorQueue(); - }); - - ocspResponses.clear(); - ocspErrorDescription.clear(); - ocspErrors.clear(); - - const unsigned char *responseData = nullptr; - const long responseLength = q_SSL_get_tlsext_status_ocsp_resp(ssl, &responseData); - if (responseLength <= 0 || !responseData) { - ocspErrors.push_back(QSslError(QSslError::OcspNoResponseFound)); - return false; - } - - OCSP_RESPONSE *response = q_d2i_OCSP_RESPONSE(nullptr, &responseData, responseLength); - if (!response) { - // Treat this as a fatal SslHandshakeError. - ocspErrorDescription = QSslSocket::tr("Failed to decode OCSP response"); - return false; - } - const QSharedPointer responseGuard(response, q_OCSP_RESPONSE_free); - - const int ocspStatus = q_OCSP_response_status(response); - if (ocspStatus != OCSP_RESPONSE_STATUS_SUCCESSFUL) { - // It's not a definitive response, it's an error message (not signed by the responder). - ocspErrors.push_back(QSslError(qt_OCSP_response_status_to_SslError(ocspStatus))); - return false; - } - - OCSP_BASICRESP *basicResponse = q_OCSP_response_get1_basic(response); - if (!basicResponse) { - // SslHandshakeError. - ocspErrorDescription = QSslSocket::tr("Failed to extract basic OCSP response"); - return false; - } - const QSharedPointer basicResponseGuard(basicResponse, q_OCSP_BASICRESP_free); - - SSL_CTX *ctx = q_SSL_get_SSL_CTX(ssl); // Does not increment refcount. - Q_ASSERT(ctx); - X509_STORE *store = q_SSL_CTX_get_cert_store(ctx); // Does not increment refcount. - if (!store) { - // SslHandshakeError. - ocspErrorDescription = QSslSocket::tr("No certificate verification store, cannot verify OCSP response"); - return false; - } - - STACK_OF(X509) *peerChain = q_SSL_get_peer_cert_chain(ssl); // Does not increment refcount. - X509 *peerX509 = q_SSL_get_peer_certificate(ssl); - Q_ASSERT(peerChain || peerX509); - const QSharedPointer peerX509Guard(peerX509, q_X509_free); - // OCSP_basic_verify with 0 as verificationFlags: - // - // 0) Tries to find the OCSP responder's certificate in either peerChain - // or basicResponse->certs. If not found, verification fails. - // 1) It checks the signature using the responder's public key. - // 2) Then it tries to validate the responder's cert (building a chain - // etc.) - // 3) It checks CertID in response. - // 4) Ensures the responder is authorized to sign the status respond. - // - // Note, OpenSSL prior to 1.0.2b would only use bs->certs to - // verify the responder's chain (see their commit 4ba9a4265bd). - // Working this around - is too much fuss for ancient versions we - // are dropping quite soon anyway. - const unsigned long verificationFlags = 0; - const int success = q_OCSP_basic_verify(basicResponse, peerChain, store, verificationFlags); - if (success <= 0) - ocspErrors.push_back(QSslError(QSslError::OcspResponseCannotBeTrusted)); - - if (q_OCSP_resp_count(basicResponse) != 1) { - ocspErrors.push_back(QSslError(QSslError::OcspMalformedResponse)); - return false; - } - - OCSP_SINGLERESP *singleResponse = q_OCSP_resp_get0(basicResponse, 0); - if (!singleResponse) { - ocspErrors.clear(); - // A fatal problem -> SslHandshakeError. - ocspErrorDescription = QSslSocket::tr("Failed to decode a SingleResponse from OCSP status response"); - return false; - } - - // Let's make sure the response is for the correct certificate - we - // can re-create this CertID using our peer's certificate and its - // issuer's public key. - ocspResponses.push_back(QOcspResponse()); - QOcspResponsePrivate *dResponse = ocspResponses.back().d.data(); - dResponse->subjectCert = configuration.peerCertificate(); - bool matchFound = false; - if (dResponse->subjectCert.isSelfSigned()) { - dResponse->signerCert = configuration.peerCertificate(); - matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, peerX509); - } else { - const STACK_OF(X509) *certs = q_SSL_get_peer_cert_chain(ssl); - if (!certs) // Oh, what a cataclysm! Last try: - certs = q_OCSP_resp_get0_certs(basicResponse); - if (certs) { - // It could be the first certificate in 'certs' is our peer's - // certificate. Since it was not captured by the 'self-signed' branch - // above, the CertID will not match and we'll just iterate on to the - // next certificate. So we start from 0, not 1. - for (int i = 0, e = q_sk_X509_num(certs); i < e; ++i) { - X509 *issuer = q_sk_X509_value(certs, i); - matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, issuer); - if (matchFound) { - if (q_X509_check_issued(issuer, peerX509) == X509_V_OK) { - dResponse->signerCert = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(issuer); - break; - } - matchFound = false; - } - } - } - } - - if (!matchFound) { - dResponse->signerCert.clear(); - ocspErrors.push_back({QSslError::OcspResponseCertIdUnknown, configuration.peerCertificate()}); - } - - // Check if the response is valid time-wise: - ASN1_GENERALIZEDTIME *revTime = nullptr; - ASN1_GENERALIZEDTIME *thisUpdate = nullptr; - ASN1_GENERALIZEDTIME *nextUpdate = nullptr; - int reason; - const int certStatus = q_OCSP_single_get0_status(singleResponse, &reason, &revTime, &thisUpdate, &nextUpdate); - if (!thisUpdate) { - // This is unexpected, treat as SslHandshakeError, OCSP_check_validity assumes this pointer - // to be != nullptr. - ocspErrors.clear(); - ocspResponses.clear(); - ocspErrorDescription = QSslSocket::tr("Failed to extract 'this update time' from the SingleResponse"); - return false; - } - - // OCSP_check_validity(this, next, nsec, maxsec) does this check: - // this <= now <= next. They allow some freedom to account - // for delays/time inaccuracy. - // this > now + nsec ? -> NOT_YET_VALID - // if maxsec >= 0: - // now - maxsec > this ? -> TOO_OLD - // now - nsec > next ? -> EXPIRED - // next < this ? -> NEXT_BEFORE_THIS - // OK. - if (!q_OCSP_check_validity(thisUpdate, nextUpdate, 60, -1)) - ocspErrors.push_back({QSslError::OcspResponseExpired, configuration.peerCertificate()}); - - // And finally, the status: - switch (certStatus) { - case V_OCSP_CERTSTATUS_GOOD: - // This certificate was not found among the revoked ones. - dResponse->certificateStatus = QOcspCertificateStatus::Good; - break; - case V_OCSP_CERTSTATUS_REVOKED: - dResponse->certificateStatus = QOcspCertificateStatus::Revoked; - dResponse->revocationReason = qt_OCSP_revocation_reason(reason); - ocspErrors.push_back({QSslError::CertificateRevoked, configuration.peerCertificate()}); - break; - case V_OCSP_CERTSTATUS_UNKNOWN: - dResponse->certificateStatus = QOcspCertificateStatus::Unknown; - ocspErrors.push_back({QSslError::OcspStatusUnknown, configuration.peerCertificate()}); - } - - return !ocspErrors.size(); -} - -#endif // QT_CONFIG(ocsp) - - -unsigned TlsCryptographOpenSSL::pskClientTlsCallback(const char *hint, char *identity, - unsigned max_identity_len, - unsigned char *psk, unsigned max_psk_len) -{ - Q_ASSERT(q); - - QSslPreSharedKeyAuthenticator authenticator; - // Fill in some read-only fields (for the user) - const int hintLength = hint ? int(std::strlen(hint)) : 0; - QTlsBackend::setupClientPskAuth(&authenticator, hint, hintLength, max_identity_len, max_psk_len); - // Let the client provide the remaining bits... - emit q->preSharedKeyAuthenticationRequired(&authenticator); - - // No PSK set? Return now to make the handshake fail - if (authenticator.preSharedKey().isEmpty()) - return 0; - - // Copy data back into OpenSSL - const int identityLength = qMin(authenticator.identity().length(), authenticator.maximumIdentityLength()); - std::memcpy(identity, authenticator.identity().constData(), identityLength); - identity[identityLength] = 0; - - const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); - std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); - return pskLength; -} - -unsigned TlsCryptographOpenSSL::pskServerTlsCallback(const char *identity, unsigned char *psk, - unsigned max_psk_len) -{ - Q_ASSERT(q); - - QSslPreSharedKeyAuthenticator authenticator; - - // Fill in some read-only fields (for the user) - QTlsBackend::setupServerPskAuth(&authenticator, identity, q->sslConfiguration().preSharedKeyIdentityHint(), - max_psk_len); - emit q->preSharedKeyAuthenticationRequired(&authenticator); - - // No PSK set? Return now to make the handshake fail - if (authenticator.preSharedKey().isEmpty()) - return 0; - - // Copy data back into OpenSSL - const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); - std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); - return pskLength; -} - -#ifdef Q_OS_WIN - -void TlsCryptographOpenSSL::fetchCaRootForCert(const QSslCertificate &cert) -{ - Q_ASSERT(d); - Q_ASSERT(q); - - //The root certificate is downloaded from windows update, which blocks for 15 seconds in the worst case - //so the request is done in a worker thread. - QList customRoots; - if (fetchAuthorityInformation) - customRoots = q->sslConfiguration().caCertificates(); - - //Remember we are fetching and what we are fetching: - caToFetch = cert; - - QWindowsCaRootFetcher *fetcher = new QWindowsCaRootFetcher(cert, d->tlsMode(), customRoots, - q->peerVerifyName()); - connect(fetcher, &QWindowsCaRootFetcher::finished, this, &TlsCryptographOpenSSL::caRootLoaded, - Qt::QueuedConnection); - QMetaObject::invokeMethod(fetcher, "start", Qt::QueuedConnection); - QSslSocketPrivate::pauseSocketNotifiers(q); - d->setPaused(true); -} - -void TlsCryptographOpenSSL::caRootLoaded(QSslCertificate cert, QSslCertificate trustedRoot) -{ - if (caToFetch != cert) { - //Ooops, something from the previous connection attempt, ignore! - return; - } - - Q_ASSERT(d); - Q_ASSERT(q); - - //Done, fetched already: - caToFetch = QSslCertificate{}; - - if (fetchAuthorityInformation) { - if (!q->sslConfiguration().caCertificates().contains(trustedRoot)) - trustedRoot = QSslCertificate{}; - fetchAuthorityInformation = false; - } - - if (!trustedRoot.isNull() && !trustedRoot.isBlacklisted()) { - if (QSslSocketPrivate::rootCertOnDemandLoadingSupported()) { - //Add the new root cert to default cert list for use by future sockets - auto defaultConfig = QSslConfiguration::defaultConfiguration(); - defaultConfig.addCaCertificate(trustedRoot); - QSslConfiguration::setDefaultConfiguration(defaultConfig); - } - //Add the new root cert to this socket for future connections - QTlsBackend::addTustedRoot(d, trustedRoot); - //Remove the broken chain ssl errors (as chain is verified by windows) - for (int i=sslErrors.count() - 1; i >= 0; --i) { - if (sslErrors.at(i).certificate() == cert) { - switch (sslErrors.at(i).error()) { - case QSslError::UnableToGetLocalIssuerCertificate: - case QSslError::CertificateUntrusted: - case QSslError::UnableToVerifyFirstCertificate: - case QSslError::SelfSignedCertificateInChain: - // error can be ignored if OS says the chain is trusted - sslErrors.removeAt(i); - break; - default: - // error cannot be ignored - break; - } - } - } - } - - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - // Continue with remaining errors - if (plainSocket) - plainSocket->resume(); - d->setPaused(false); - if (checkSslErrors() && ssl) { - bool willClose = (d->isAutoStartingHandshake() && d->isPendingClose()); - continueHandshake(); - if (!willClose) - transmit(); - } -} - -#endif // Q_OS_WIN - -} // namespace QTlsPrivate - -QT_END_NAMESPACE diff --git a/src/network/ssl/qtls_openssl_p.h b/src/network/ssl/qtls_openssl_p.h deleted file mode 100644 index 1def93831d..0000000000 --- a/src/network/ssl/qtls_openssl_p.h +++ /dev/null @@ -1,169 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLS_OPENSSL_P_H -#define QTLS_OPENSSL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include "qtlsbackend_openssl_p.h" -#include "qsslcontext_openssl_p.h" -#include "qsslcertificate.h" -#include "qocspresponse.h" -#include "qopenssl_p.h" - -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -class TlsCryptographOpenSSL : public TlsCryptograph -{ -public: - enum ExDataOffset { - errorOffsetInExData = 1, - socketOffsetInExData = 2 - }; - - ~TlsCryptographOpenSSL(); - - void init(QSslSocket *qObj, QSslSocketPrivate *dObj) override; - void checkSettingSslContext(QSharedPointer tlsContext) override; - QSharedPointer sslContext() const override; - - QList tlsErrors() const override; - - void startClientEncryption() override; - void startServerEncryption() override; - bool startHandshake(); - void enableHandshakeContinuation() override; - void cancelCAFetch() override; - void continueHandshake() override; - void transmit() override; - void disconnectFromHost() override; - void disconnected() override; - QSslCipher sessionCipher() const override; - QSsl::SslProtocol sessionProtocol() const override; - QList ocsps() const override; - - bool checkSslErrors(); - int handleNewSessionTicket(SSL *connection); - - void alertMessageSent(int encoded); - void alertMessageReceived(int encoded); - - int emitErrorFromCallback(X509_STORE_CTX *ctx); - void trySendFatalAlert(); - -#if QT_CONFIG(ocsp) - bool checkOcspStatus(); -#endif - - QSslSocket *q = nullptr; - QSslSocketPrivate *d = nullptr; - - void storePeerCertificates(); - - unsigned pskClientTlsCallback(const char *hint, char *identity, unsigned max_identity_len, - unsigned char *psk, unsigned max_psk_len); - unsigned pskServerTlsCallback(const char *identity, unsigned char *psk, - unsigned max_psk_len); - -#ifdef Q_OS_WIN - void fetchCaRootForCert(const QSslCertificate &cert); - void caRootLoaded(QSslCertificate certificate, QSslCertificate trustedRoot); -#endif - - QByteArray ocspResponseDer; -private: - // TLSTODO: names were preserved, to make comparison - // easier (see qsslsocket_openssl.cpp, while it exists). - bool initSslContext(); - void destroySslContext(); - - QSharedPointer sslContextPointer; - SSL *ssl = nullptr; // TLSTODO: RAII. - - QList errorList; - QList sslErrors; - - BIO *readBio = nullptr; - BIO *writeBio = nullptr; - - QList ocspResponses; - - // This decription will go to setErrorAndEmit(SslHandshakeError, ocspErrorDescription) - QString ocspErrorDescription; - // These will go to sslErrors() - QList ocspErrors; - - bool systemOrSslErrorDetected = false; - bool handshakeInterrupted = false; - - bool fetchAuthorityInformation = false; - QSslCertificate caToFetch; - - bool inSetAndEmitError = false; - bool pendingFatalAlert = false; - bool errorsReportedFromCallback = false; - - bool shutdown = false; -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QTLS_OPENSSL_P_H - diff --git a/src/network/ssl/qtls_schannel.cpp b/src/network/ssl/qtls_schannel.cpp deleted file mode 100644 index cb4b9ce79d..0000000000 --- a/src/network/ssl/qtls_schannel.cpp +++ /dev/null @@ -1,2322 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -// #define QSSLSOCKET_DEBUG - -#include "qssl_p.h" -#include "qsslsocket.h" -#include "qtls_schannel_p.h" -#include "qsslcertificate.h" -#include "qsslcertificateextension.h" -#include "qsslcertificate_p.h" -#include "qsslcipher_p.h" -#include "qtlsbackend_schannel_p.h" -#include "qtlskey_schannel_p.h" -#include "qx509_schannel_p.h" - -#include -#include -#include -#include -#include - -#define SECURITY_WIN32 -#include -#include - -#if NTDDI_VERSION >= NTDDI_WINBLUE && !defined(Q_CC_MINGW) -// 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 -#endif -#ifndef SECPKG_ATTR_APPLICATION_PROTOCOL -#define SECPKG_ATTR_APPLICATION_PROTOCOL 35 -#endif - -// Another missing MinGW define -#ifndef SEC_E_APPLICATION_PROTOCOL_MISMATCH -#define SEC_E_APPLICATION_PROTOCOL_MISMATCH _HRESULT_TYPEDEF_(0x80090367L) -#endif - -// Also not defined in MinGW....... -#ifndef SP_PROT_TLS1_SERVER -#define SP_PROT_TLS1_SERVER 0x00000040 -#endif -#ifndef SP_PROT_TLS1_CLIENT -#define SP_PROT_TLS1_CLIENT 0x00000080 -#endif -#ifndef SP_PROT_TLS1_0_SERVER -#define SP_PROT_TLS1_0_SERVER SP_PROT_TLS1_SERVER -#endif -#ifndef SP_PROT_TLS1_0_CLIENT -#define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT -#endif -#ifndef SP_PROT_TLS1_0 -#define SP_PROT_TLS1_0 (SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_0_SERVER) -#endif -#ifndef SP_PROT_TLS1_1_SERVER -#define SP_PROT_TLS1_1_SERVER 0x00000100 -#endif -#ifndef SP_PROT_TLS1_1_CLIENT -#define SP_PROT_TLS1_1_CLIENT 0x00000200 -#endif -#ifndef SP_PROT_TLS1_1 -#define SP_PROT_TLS1_1 (SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_1_SERVER) -#endif -#ifndef SP_PROT_TLS1_2_SERVER -#define SP_PROT_TLS1_2_SERVER 0x00000400 -#endif -#ifndef SP_PROT_TLS1_2_CLIENT -#define SP_PROT_TLS1_2_CLIENT 0x00000800 -#endif -#ifndef SP_PROT_TLS1_2 -#define SP_PROT_TLS1_2 (SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_2_SERVER) -#endif -#ifndef SP_PROT_TLS1_3_SERVER -#define SP_PROT_TLS1_3_SERVER 0x00001000 -#endif -#ifndef SP_PROT_TLS1_3_CLIENT -#define SP_PROT_TLS1_3_CLIENT 0x00002000 -#endif -#ifndef SP_PROT_TLS1_3 -#define SP_PROT_TLS1_3 (SP_PROT_TLS1_3_CLIENT | SP_PROT_TLS1_3_SERVER) -#endif - -/* - @future!: - - - Transmitting intermediate certificates - - Look for a way to avoid putting intermediate certificates in the certificate store - - No documentation on how to send the chain - - A stackoverflow question on this from 3 years ago implies schannel only sends intermediate - certificates if it's "in the system or user certificate store". - - https://stackoverflow.com/q/30156584/2493610 - - This can be done by users, but we shouldn't add any and all local intermediate - certs to the stores automatically. - - PSK support - - Was added in Windows 10 (it seems), documentation at time of writing is sparse/non-existent. - - Specifically about how to supply credentials when they're requested. - - Or how to recognize that they're requested in the first place. - - Skip certificate verification. - - Check if "PSK-only" is still required to do PSK _at all_ (all-around bad solution). - - 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) - -*/ - -QT_BEGIN_NAMESPACE - -Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.schannel"); - -// Defined in qsslsocket_qt.cpp. -QByteArray _q_makePkcs12(const QList &certs, const QSslKey &key, - const QString &passPhrase); - -namespace QTlsPrivate { - -QList defaultCiphers() -{ - // Previously the code was in QSslSocketBackendPrivate. - QList 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); - } - - return ciphers; - -} - -} // namespace QTlsPrivate - -namespace { -bool supportsTls13(); -} - -bool QSchannelBackend::s_loadedCiphersAndCerts = false; -Q_GLOBAL_STATIC(QRecursiveMutex, qt_schannel_mutex) - -long QSchannelBackend::tlsLibraryVersionNumber() const -{ - const auto os = QOperatingSystemVersion::current(); - return (os.majorVersion() << 24) | ((os.minorVersion() & 0xFF) << 16) | (os.microVersion() & 0xFFFF); -} - -QString QSchannelBackend::tlsLibraryVersionString() const -{ - const auto os = QOperatingSystemVersion::current(); - return QString::fromLatin1("Secure Channel, %1 %2.%3.%4") - .arg(os.name(), - QString::number(os.majorVersion()), - QString::number(os.minorVersion()), - QString::number(os.microVersion())); -} - -long QSchannelBackend::tlsLibraryBuildVersionNumber() const -{ - return tlsLibraryVersionNumber(); -} - -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())); -} - -void QSchannelBackend::ensureInitialized() const -{ - ensureInitializedImplementation(); -} - -void QSchannelBackend::ensureInitializedImplementation() -{ - const QMutexLocker locker(qt_schannel_mutex); - if (s_loadedCiphersAndCerts) - return; - s_loadedCiphersAndCerts = true; - - setDefaultCaCertificates(systemCaCertificatesImplementation()); - // setDefaultCaCertificates sets it to false, re-enable it: - QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); - - resetDefaultCiphers(); -} - -void QSchannelBackend::resetDefaultCiphers() -{ - setDefaultSupportedCiphers(QTlsPrivate::defaultCiphers()); - setDefaultCiphers(QTlsPrivate::defaultCiphers()); -} - -QString QSchannelBackend::backendName() const -{ - return builtinBackendNames[nameIndexSchannel]; -} - -QList QSchannelBackend::supportedProtocols() const -{ - QList protocols; - - protocols << QSsl::AnyProtocol; - protocols << QSsl::SecureProtocols; - protocols << QSsl::TlsV1_0; - protocols << QSsl::TlsV1_0OrLater; - protocols << QSsl::TlsV1_1; - protocols << QSsl::TlsV1_1OrLater; - protocols << QSsl::TlsV1_2; - protocols << QSsl::TlsV1_2OrLater; - - if (supportsTls13()) { - protocols << QSsl::TlsV1_3; - protocols << QSsl::TlsV1_3OrLater; - } - - return protocols; -} - -QList QSchannelBackend::supportedFeatures() const -{ - QList features; - - features << QSsl::SupportedFeature::ClientSideAlpn; - features << QSsl::SupportedFeature::ServerSideAlpn; - - return features; -} - -QList QSchannelBackend::implementedClasses() const -{ - QList classes; - - classes << QSsl::ImplementedClass::Socket; - classes << QSsl::ImplementedClass::Certificate; - classes << QSsl::ImplementedClass::Key; - - return classes; -} - -QTlsPrivate::TlsKey *QSchannelBackend::createKey() const -{ - return new QTlsPrivate::TlsKeySchannel; -} - -QTlsPrivate::X509Certificate *QSchannelBackend::createCertificate() const -{ - return new QTlsPrivate::X509CertificateSchannel; -} - -QList QSchannelBackend::systemCaCertificates() const -{ - return systemCaCertificatesImplementation(); -} - -QTlsPrivate::TlsCryptograph *QSchannelBackend::createTlsCryptograph() const -{ - return new QTlsPrivate::TlsCryptographSchannel; -} - -QList QSchannelBackend::systemCaCertificatesImplementation() -{ - // Similar to non-Darwin version found in qtlsbackend_openssl.cpp, - // QTlsPrivate::systemCaCertificates function. - QList systemCerts; - auto hSystemStore = QHCertStorePointer(CertOpenSystemStore(0, L"ROOT")); - if (hSystemStore) { - PCCERT_CONTEXT pc = nullptr; - while ((pc = CertFindCertificateInStore(hSystemStore.get(), X509_ASN_ENCODING, 0, - CERT_FIND_ANY, nullptr, pc))) { - systemCerts.append(QTlsPrivate::X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(pc)); - } - } - return systemCerts; -} - -QTlsPrivate::X509PemReaderPtr QSchannelBackend::X509PemReader() const -{ - return QTlsPrivate::X509CertificateGeneric::certificatesFromPem; -} - -QTlsPrivate::X509DerReaderPtr QSchannelBackend::X509DerReader() const -{ - return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; -} - -Q_GLOBAL_STATIC(QSchannelBackend, backendSchannel) - -namespace { - -SecBuffer createSecBuffer(void *ptr, unsigned long length, unsigned long bufferType) -{ - return SecBuffer{ length, bufferType, ptr }; -} - -SecBuffer createSecBuffer(QByteArray &buffer, unsigned long bufferType) -{ - return createSecBuffer(buffer.data(), static_cast(buffer.length()), bufferType); -} - -QString schannelErrorToString(qint32 status) -{ - switch (status) { - case SEC_E_INSUFFICIENT_MEMORY: - return QSslSocket::tr("Insufficient memory"); - case SEC_E_INTERNAL_ERROR: - return QSslSocket::tr("Internal error"); - case SEC_E_INVALID_HANDLE: - return QSslSocket::tr("An internal handle was invalid"); - case SEC_E_INVALID_TOKEN: - return QSslSocket::tr("An internal token was invalid"); - case SEC_E_LOGON_DENIED: - // According to the link below we get this error when Schannel receives TLS1_ALERT_ACCESS_DENIED - // https://docs.microsoft.com/en-us/windows/desktop/secauthn/schannel-error-codes-for-tls-and-ssl-alerts - return QSslSocket::tr("Access denied"); - case SEC_E_NO_AUTHENTICATING_AUTHORITY: - return QSslSocket::tr("No authority could be contacted for authorization"); - case SEC_E_NO_CREDENTIALS: - return QSslSocket::tr("No credentials"); - case SEC_E_TARGET_UNKNOWN: - return QSslSocket::tr("The target is unknown or unreachable"); - case SEC_E_UNSUPPORTED_FUNCTION: - return QSslSocket::tr("An unsupported function was requested"); - case SEC_E_WRONG_PRINCIPAL: - // SNI error - return QSslSocket::tr("The hostname provided does not match the one received from the peer"); - case SEC_E_APPLICATION_PROTOCOL_MISMATCH: - return QSslSocket::tr("No common protocol exists between the client and the server"); - case SEC_E_ILLEGAL_MESSAGE: - return QSslSocket::tr("Unexpected or badly-formatted message received"); - case SEC_E_ENCRYPT_FAILURE: - return QSslSocket::tr("The data could not be encrypted"); - case SEC_E_ALGORITHM_MISMATCH: - return QSslSocket::tr("No cipher suites in common"); - case SEC_E_UNKNOWN_CREDENTIALS: - // This can mean "invalid argument" in some cases... - return QSslSocket::tr("The credentials were not recognized / Invalid argument"); - case SEC_E_MESSAGE_ALTERED: - // According to the Internet it also triggers for messages that are out of order. - // https://microsoft.public.platformsdk.security.narkive.com/4JAvlMvD/help-please-schannel-security-contexts-and-decryptmessage - return QSslSocket::tr("The message was tampered with, damaged or out of sequence."); - case SEC_E_OUT_OF_SEQUENCE: - return QSslSocket::tr("A message was received out of sequence."); - case SEC_E_CONTEXT_EXPIRED: - return QSslSocket::tr("The TLS/SSL connection has been closed"); - default: - return QSslSocket::tr("Unknown error occurred: %1").arg(status); - } -} - -bool supportsTls13() -{ -#ifdef SUPPORTS_TLS13 - static bool supported = []() { - const auto current = QOperatingSystemVersion::current(); - // 20221 just happens to be the preview version I run on my laptop where I tested TLS 1.3. - const auto minimum = - QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 20221); - return current >= minimum; - }(); - return supported; -#else - return false; -#endif -} - -DWORD toSchannelProtocol(QSsl::SslProtocol protocol) -{ - DWORD protocols = SP_PROT_NONE; - switch (protocol) { - case QSsl::UnknownProtocol: - return DWORD(-1); - case QSsl::DtlsV1_0: - case QSsl::DtlsV1_2: - case QSsl::DtlsV1_0OrLater: - case QSsl::DtlsV1_2OrLater: - return DWORD(-1); // Not supported at the moment (@future) - case QSsl::AnyProtocol: - protocols = SP_PROT_TLS1_0 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2; - if (supportsTls13()) - protocols |= SP_PROT_TLS1_3; - break; - case QSsl::TlsV1_0: - protocols = SP_PROT_TLS1_0; - break; - case QSsl::TlsV1_1: - protocols = SP_PROT_TLS1_1; - break; - case QSsl::TlsV1_2: - protocols = SP_PROT_TLS1_2; - break; - case QSsl::TlsV1_3: - if (supportsTls13()) - protocols = SP_PROT_TLS1_3; - else - protocols = DWORD(-1); - break; - case QSsl::SecureProtocols: // TLS v1.0 and later is currently considered secure - case QSsl::TlsV1_0OrLater: - // For the "OrLater" protocols we fall through from one to the next, adding all of them - // in ascending order - protocols = SP_PROT_TLS1_0; - Q_FALLTHROUGH(); - case QSsl::TlsV1_1OrLater: - protocols |= SP_PROT_TLS1_1; - Q_FALLTHROUGH(); - case QSsl::TlsV1_2OrLater: - protocols |= SP_PROT_TLS1_2; - Q_FALLTHROUGH(); - case QSsl::TlsV1_3OrLater: - if (supportsTls13()) - protocols |= SP_PROT_TLS1_3; - else if (protocol == QSsl::TlsV1_3OrLater) - protocols = DWORD(-1); // if TlsV1_3OrLater was specifically chosen we should fail - break; - } - return protocols; -} - -#ifdef SUPPORTS_TLS13 -// In the new API that descended down upon us we are not asked which protocols we want -// but rather which protocols we don't want. So now we have this function to disable -// anything that is not enabled. -DWORD toSchannelProtocolNegated(QSsl::SslProtocol protocol) -{ - DWORD protocols = SP_PROT_ALL; // all protocols - protocols &= ~toSchannelProtocol(protocol); // minus the one(s) we want - return protocols; -} -#endif - -/*! - \internal - Used when converting the established session's \a protocol back to - Qt's own SslProtocol type. - - Only one protocol should be passed in at a time. -*/ -QSsl::SslProtocol toQtSslProtocol(DWORD protocol) -{ -#define MAP_PROTOCOL(sp_protocol, q_protocol) \ - if (protocol & sp_protocol) { \ - Q_ASSERT(!(protocol & ~sp_protocol)); \ - return q_protocol; \ - } - - MAP_PROTOCOL(SP_PROT_TLS1_0, QSsl::TlsV1_0) - MAP_PROTOCOL(SP_PROT_TLS1_1, QSsl::TlsV1_1) - MAP_PROTOCOL(SP_PROT_TLS1_2, QSsl::TlsV1_2) - MAP_PROTOCOL(SP_PROT_TLS1_3, QSsl::TlsV1_3) -#undef MAP_PROTOCOL - Q_UNREACHABLE(); - return QSsl::UnknownProtocol; -} - -/*! - \internal - Used by verifyCertContext to check if a client cert is used by a server or vice versa. -*/ -bool netscapeWrongCertType(const QList &extensions, bool isClient) -{ - 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; - }); - if (netscapeIt != extensions.cend()) { - const QByteArray netscapeCertTypeByte = netscapeIt->value().toByteArray(); - int netscapeCertType = 0; - QDataStream dataStream(netscapeCertTypeByte); - dataStream >> netscapeCertType; - if (dataStream.status() != QDataStream::Status::Ok) - return true; - const int expectedPeerCertType = isClient ? NETSCAPE_SSL_SERVER_AUTH_CERT_TYPE - : NETSCAPE_SSL_CLIENT_AUTH_CERT_TYPE; - if ((netscapeCertType & expectedPeerCertType) == 0) - return true; - } - return false; -} - -/*! - \internal - Used by verifyCertContext to check the basicConstraints certificate - extension to see if the certificate is a certificate authority. - Returns false if the certificate does not have the basicConstraints - extension or if it is not a certificate authority. -*/ -bool isCertificateAuthority(const QList &extensions) -{ - auto it = std::find_if(extensions.cbegin(), extensions.cend(), - [](const QSslCertificateExtension &extension) { - return extension.name() == QLatin1String("basicConstraints"); - }); - if (it != extensions.cend()) { - QVariantMap basicConstraints = it->value().toMap(); - return basicConstraints.value(QLatin1String("ca"), false).toBool(); - } - return false; -} - -/*! - \internal - Returns true if the attributes we requested from the context/handshake have - been given. -*/ -bool matchesContextRequirements(DWORD attributes, DWORD requirements, - QSslSocket::PeerVerifyMode verifyMode, - bool isClient) -{ -#ifdef QSSLSOCKET_DEBUG -#define DEBUG_WARN(message) qCWarning(lcSsl, message) -#else -#define DEBUG_WARN(message) -#endif - -#define CHECK_ATTRIBUTE(attributeName) \ - do { \ - const DWORD req##attributeName = isClient ? ISC_REQ_##attributeName : ASC_REQ_##attributeName; \ - const DWORD ret##attributeName = isClient ? ISC_RET_##attributeName : ASC_RET_##attributeName; \ - if (!(requirements & req##attributeName) != !(attributes & ret##attributeName)) { \ - DEBUG_WARN("Missing attribute \"" #attributeName "\""); \ - return false; \ - } \ - } while (false) - - CHECK_ATTRIBUTE(CONFIDENTIALITY); - CHECK_ATTRIBUTE(REPLAY_DETECT); - CHECK_ATTRIBUTE(SEQUENCE_DETECT); - CHECK_ATTRIBUTE(STREAM); - if (verifyMode == QSslSocket::PeerVerifyMode::VerifyPeer) - CHECK_ATTRIBUTE(MUTUAL_AUTH); - - // This one is manual because there is no server / ASC_ version - if (isClient) { - const auto reqManualCredValidation = ISC_REQ_MANUAL_CRED_VALIDATION; - const auto retManualCredValidation = ISC_RET_MANUAL_CRED_VALIDATION; - if (!(requirements & reqManualCredValidation) != !(attributes & retManualCredValidation)) { - DEBUG_WARN("Missing attribute \"MANUAL_CRED_VALIDATION\""); - return false; - } - } - - return true; -#undef CHECK_ATTRIBUTE -#undef DEBUG_WARN -} - -template -Required const_reinterpret_cast(Actual *p) -{ - return Required(p); -} - -#ifdef SUPPORTS_ALPN -bool supportsAlpn() -{ - return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1; -} - -QByteArray createAlpnString(const QByteArrayList &nextAllowedProtocols) -{ - QByteArray alpnString; - if (!nextAllowedProtocols.isEmpty() && supportsAlpn()) { - const QByteArray names = [&nextAllowedProtocols]() { - QByteArray protocolString; - for (QByteArray proto : nextAllowedProtocols) { - if (proto.size() > 255) { - qCWarning(lcSsl) << "TLS ALPN extension" << proto - << "is too long and will be ignored."; - continue; - } else if (proto.isEmpty()) { - continue; - } - protocolString += char(proto.length()) + proto; - } - return protocolString; - }(); - if (names.isEmpty()) - return alpnString; - - const quint16 namesSize = names.size(); - const quint32 alpnId = SecApplicationProtocolNegotiationExt_ALPN; - const quint32 totalSize = sizeof(alpnId) + sizeof(namesSize) + namesSize; - alpnString = QByteArray::fromRawData(reinterpret_cast(&totalSize), sizeof(totalSize)) - + QByteArray::fromRawData(reinterpret_cast(&alpnId), sizeof(alpnId)) - + QByteArray::fromRawData(reinterpret_cast(&namesSize), sizeof(namesSize)) - + names; - } - return alpnString; -} -#endif // SUPPORTS_ALPN - -qint64 readToBuffer(QByteArray &buffer, QTcpSocket *plainSocket) -{ - Q_ASSERT(plainSocket); - static const qint64 shrinkCutoff = 1024 * 12; - static const qint64 defaultRead = 1024 * 16; - qint64 bytesRead = 0; - - const auto toRead = std::min(defaultRead, plainSocket->bytesAvailable()); - if (toRead > 0) { - const auto bufferSize = buffer.size(); - buffer.reserve(bufferSize + toRead); // avoid growth strategy kicking in - buffer.resize(bufferSize + toRead); - bytesRead = plainSocket->read(buffer.data() + bufferSize, toRead); - buffer.resize(bufferSize + bytesRead); - // In case of excessive memory usage we shrink: - if (buffer.size() < shrinkCutoff && buffer.capacity() > defaultRead) - buffer.shrink_to_fit(); - } - - return bytesRead; -} - -void retainExtraData(QByteArray &buffer, const SecBuffer &secBuffer) -{ - Q_ASSERT(secBuffer.BufferType == SECBUFFER_EXTRA); - if (int(secBuffer.cbBuffer) >= buffer.size()) - return; - -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "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); -} - -qint64 checkIncompleteData(const SecBuffer &secBuffer) -{ - if (secBuffer.BufferType == SECBUFFER_MISSING) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "Need %lu more bytes.", secBuffer.cbBuffer); -#endif - return secBuffer.cbBuffer; -} - return 0; -} - -} // anonymous namespace - - -namespace QTlsPrivate { - -TlsCryptographSchannel::TlsCryptographSchannel() -{ - SecInvalidateHandle(&credentialHandle); - SecInvalidateHandle(&contextHandle); - QSchannelBackend::ensureInitializedImplementation(); -} - -TlsCryptographSchannel::~TlsCryptographSchannel() -{ - closeCertificateStores(); - deallocateContext(); - freeCredentialsHandle(); - CertFreeCertificateContext(localCertContext); -} - -void TlsCryptographSchannel::init(QSslSocket *qObj, QSslSocketPrivate *dObj) -{ - Q_ASSERT(qObj); - Q_ASSERT(dObj); - - q = qObj; - d = dObj; - - reset(); -} - -bool TlsCryptographSchannel::sendToken(void *token, unsigned long tokenLength, bool emitError) -{ - if (tokenLength == 0) - return true; - - Q_ASSERT(d); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - const qint64 written = plainSocket->write(static_cast(token), tokenLength); - if (written != qint64(tokenLength)) { - // Failed to write/buffer everything or an error occurred - if (emitError) - d->setErrorAndEmit(plainSocket->error(), plainSocket->errorString()); - return false; - } - return true; -} - -QString TlsCryptographSchannel::targetName() const -{ - // Used for SNI extension - Q_ASSERT(q); - Q_ASSERT(d); - - const auto verificationPeerName = d->verificationName(); - return verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName; -} - -ULONG TlsCryptographSchannel::getContextRequirements() -{ - Q_ASSERT(d); - Q_ASSERT(q); - - const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; - ULONG req = 0; - - req |= ISC_REQ_ALLOCATE_MEMORY; // Allocate memory for buffers automatically - req |= ISC_REQ_CONFIDENTIALITY; // Encrypt messages - req |= ISC_REQ_REPLAY_DETECT; // Detect replayed messages - req |= ISC_REQ_SEQUENCE_DETECT; // Detect out of sequence messages - req |= ISC_REQ_STREAM; // Support a stream-oriented connection - - if (isClient) { - req |= ISC_REQ_MANUAL_CRED_VALIDATION; // Manually validate certificate - } else { - switch (q->peerVerifyMode()) { - case QSslSocket::PeerVerifyMode::VerifyNone: - // There doesn't seem to be a way to ask for an optional client cert :-( - case QSslSocket::PeerVerifyMode::AutoVerifyPeer: - case QSslSocket::PeerVerifyMode::QueryPeer: - break; - case QSslSocket::PeerVerifyMode::VerifyPeer: - req |= ISC_REQ_MUTUAL_AUTH; - break; - } - } - - return req; -} - -bool TlsCryptographSchannel::acquireCredentialsHandle() -{ - Q_ASSERT(d); - Q_ASSERT(q); - const auto &configuration = q->sslConfiguration(); - - Q_ASSERT(schannelState == SchannelState::InitializeHandshake); - - const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; - const DWORD protocols = toSchannelProtocol(configuration.protocol()); - if (protocols == DWORD(-1)) { - d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, - QSslSocket::tr("Invalid protocol chosen")); - return false; - } - - const CERT_CHAIN_CONTEXT *chainContext = nullptr; - auto freeCertChain = qScopeGuard([&chainContext]() { - if (chainContext) - CertFreeCertificateChain(chainContext); - }); - - DWORD certsCount = 0; - // Set up our certificate stores before trying to use one... - initializeCertificateStores(); - - // Check if user has specified a certificate chain but it could not be loaded. - // This happens if there was something wrong with the certificate chain or there was no private - // key. - if (!configuration.localCertificateChain().isEmpty() && !localCertificateStore) - return true; // 'true' because "tst_QSslSocket::setEmptyKey" expects us to not disconnect - - if (localCertificateStore != nullptr) { - CERT_CHAIN_FIND_BY_ISSUER_PARA findParam; - ZeroMemory(&findParam, sizeof(findParam)); - findParam.cbSize = sizeof(findParam); - findParam.pszUsageIdentifier = isClient ? szOID_PKIX_KP_CLIENT_AUTH : szOID_PKIX_KP_SERVER_AUTH; - - // There should only be one chain in our store, so.. we grab that one. - chainContext = CertFindChainInStore(localCertificateStore.get(), - X509_ASN_ENCODING, - 0, - CERT_CHAIN_FIND_BY_ISSUER, - &findParam, - nullptr); - if (!chainContext) { - const QString message = isClient - ? QSslSocket::tr("The certificate provided cannot be used for a client.") - : QSslSocket::tr("The certificate provided cannot be used for a server."); - d->setErrorAndEmit(QAbstractSocket::SocketError::SslInvalidUserDataError, message); - return false; - } - Q_ASSERT(chainContext->cChain == 1); - Q_ASSERT(chainContext->rgpChain[0]); - Q_ASSERT(chainContext->rgpChain[0]->cbSize >= 1); - Q_ASSERT(chainContext->rgpChain[0]->rgpElement[0]); - Q_ASSERT(!localCertContext); - localCertContext = CertDuplicateCertificateContext(chainContext->rgpChain[0] - ->rgpElement[0] - ->pCertContext); - certsCount = 1; - Q_ASSERT(localCertContext); - } - void *credentials = nullptr; -#ifdef SUPPORTS_TLS13 - TLS_PARAMETERS tlsParameters = { - 0, - nullptr, - toSchannelProtocolNegated(configuration.protocol()), // what protocols to disable - 0, - nullptr, - 0 - }; - if (supportsTls13()) { - SCH_CREDENTIALS *cred = new SCH_CREDENTIALS{ - SCH_CREDENTIALS_VERSION, - 0, - certsCount, - &localCertContext, - nullptr, - 0, - nullptr, - 0, - SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT - | SCH_CRED_NO_DEFAULT_CREDS, - 1, - &tlsParameters - }; - credentials = cred; - } else -#endif // SUPPORTS_TLS13 - { - SCHANNEL_CRED *cred = new SCHANNEL_CRED{ - SCHANNEL_CRED_VERSION, // dwVersion - certsCount, // cCreds - &localCertContext, // paCred (certificate(s) containing a private key for authentication) - nullptr, // hRootStore - - 0, // cMappers (reserved) - nullptr, // aphMappers (reserved) - - 0, // cSupportedAlgs - nullptr, // palgSupportedAlgs (nullptr = system default) - - protocols, // grbitEnabledProtocols - 0, // dwMinimumCipherStrength (0 = system default) - 0, // dwMaximumCipherStrength (0 = system default) - 0, // dwSessionLifespan (0 = schannel default, 10 hours) - SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT - | SCH_CRED_NO_DEFAULT_CREDS, // dwFlags - 0 // dwCredFormat (must be 0) - }; - credentials = cred; - } - Q_ASSERT(credentials != nullptr); - - TimeStamp expiration{}; - auto status = AcquireCredentialsHandle(nullptr, // pszPrincipal (unused) - const_cast(UNISP_NAME), // pszPackage - isClient ? SECPKG_CRED_OUTBOUND : SECPKG_CRED_INBOUND, // fCredentialUse - nullptr, // pvLogonID (unused) - credentials, // pAuthData - nullptr, // pGetKeyFn (unused) - nullptr, // pvGetKeyArgument (unused) - &credentialHandle, // phCredential - &expiration // ptsExpir - ); - -#ifdef SUPPORTS_TLS13 - if (supportsTls13()) { - delete static_cast(credentials); - } else -#endif // SUPPORTS_TLS13 - { - delete static_cast(credentials); - } - - if (status != SEC_E_OK) { - d->setErrorAndEmit(QAbstractSocket::SslInternalError, schannelErrorToString(status)); - return false; - } - return true; -} - -void TlsCryptographSchannel::deallocateContext() -{ - if (SecIsValidHandle(&contextHandle)) { - DeleteSecurityContext(&contextHandle); - SecInvalidateHandle(&contextHandle); - } -} - -void TlsCryptographSchannel::freeCredentialsHandle() -{ - if (SecIsValidHandle(&credentialHandle)) { - FreeCredentialsHandle(&credentialHandle); - SecInvalidateHandle(&credentialHandle); - } -} - -void TlsCryptographSchannel::closeCertificateStores() -{ - localCertificateStore.reset(); - peerCertificateStore.reset(); - caCertificateStore.reset(); -} - -bool TlsCryptographSchannel::createContext() -{ - Q_ASSERT(q); - Q_ASSERT(d); - const auto &configuration = q->sslConfiguration(); - - Q_ASSERT(SecIsValidHandle(&credentialHandle)); - Q_ASSERT(schannelState == SchannelState::InitializeHandshake); - Q_ASSERT(d->tlsMode() == QSslSocket::SslClientMode); - ULONG contextReq = getContextRequirements(); - - SecBuffer outBuffers[3]; - outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); - outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); - outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); - auto freeBuffers = qScopeGuard([&outBuffers]() { - for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { - if (outBuffers[i].pvBuffer) - FreeContextBuffer(outBuffers[i].pvBuffer); - } - }); - SecBufferDesc outputBufferDesc{ - SECBUFFER_VERSION, - ARRAYSIZE(outBuffers), - outBuffers - }; - - TimeStamp expiry; - - SecBufferDesc alpnBufferDesc; - bool useAlpn = false; -#ifdef SUPPORTS_ALPN - QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNone); - QByteArray alpnString = createAlpnString(configuration.allowedNextProtocols()); - useAlpn = !alpnString.isEmpty(); - SecBuffer alpnBuffers[1]; - alpnBuffers[0] = createSecBuffer(alpnString, SECBUFFER_APPLICATION_PROTOCOLS); - alpnBufferDesc = { - SECBUFFER_VERSION, - ARRAYSIZE(alpnBuffers), - alpnBuffers - }; -#endif - - auto status = InitializeSecurityContext(&credentialHandle, // phCredential - nullptr, // phContext - const_reinterpret_cast(targetName().utf16()), // pszTargetName - contextReq, // fContextReq - 0, // Reserved1 - 0, // TargetDataRep (unused) - useAlpn ? &alpnBufferDesc : nullptr, // pInput - 0, // Reserved2 - &contextHandle, // phNewContext - &outputBufferDesc, // pOutput - &contextAttributes, // pfContextAttr - &expiry // ptsExpiry - ); - - // This is the first call to InitializeSecurityContext, so theoretically "CONTINUE_NEEDED" - // should be the only non-error return-code here. - if (status != SEC_I_CONTINUE_NEEDED) { - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error creating SSL context (%1)").arg(schannelErrorToString(status))); - return false; - } - - if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) - return false; - schannelState = SchannelState::PerformHandshake; - return true; -} - -bool TlsCryptographSchannel::acceptContext() -{ - Q_ASSERT(d); - Q_ASSERT(q); - const auto &configuration = q->sslConfiguration(); - auto *plainSocket = d->plainTcpSocket(); - - Q_ASSERT(SecIsValidHandle(&credentialHandle)); - Q_ASSERT(schannelState == SchannelState::InitializeHandshake); - Q_ASSERT(d->tlsMode() == QSslSocket::SslServerMode); - ULONG contextReq = getContextRequirements(); - - if (missingData > plainSocket->bytesAvailable()) - return true; - - missingData = 0; - readToBuffer(intermediateBuffer, plainSocket); - if (intermediateBuffer.isEmpty()) - return true; // definitely need more data.. - - SecBuffer inBuffers[2]; - inBuffers[0] = createSecBuffer(intermediateBuffer, SECBUFFER_TOKEN); - -#ifdef SUPPORTS_ALPN - QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNone); - // The string must be alive when we call AcceptSecurityContext - QByteArray alpnString = createAlpnString(configuration.allowedNextProtocols()); - if (!alpnString.isEmpty()) { - inBuffers[1] = createSecBuffer(alpnString, SECBUFFER_APPLICATION_PROTOCOLS); - } else -#endif - { - inBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); - } - - SecBufferDesc inputBufferDesc{ - SECBUFFER_VERSION, - ARRAYSIZE(inBuffers), - inBuffers - }; - - SecBuffer outBuffers[3]; - outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); - outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); - outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); - auto freeBuffers = qScopeGuard([&outBuffers]() { - for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { - if (outBuffers[i].pvBuffer) - FreeContextBuffer(outBuffers[i].pvBuffer); - } - }); - SecBufferDesc outputBufferDesc{ - SECBUFFER_VERSION, - ARRAYSIZE(outBuffers), - outBuffers - }; - - TimeStamp expiry; - auto status = AcceptSecurityContext( - &credentialHandle, // phCredential - nullptr, // phContext - &inputBufferDesc, // pInput - contextReq, // fContextReq - 0, // TargetDataRep (unused) - &contextHandle, // phNewContext - &outputBufferDesc, // pOutput - &contextAttributes, // pfContextAttr - &expiry // ptsTimeStamp - ); - - if (status == SEC_E_INCOMPLETE_MESSAGE) { - // Need more data - missingData = checkIncompleteData(outBuffers[0]); - return true; - } - - if (inBuffers[1].BufferType == SECBUFFER_EXTRA) { - // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel - // inBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need to - // be stored. - retainExtraData(intermediateBuffer, inBuffers[1]); - } else { /* No 'extra' data, message not incomplete */ - intermediateBuffer.resize(0); - } - - if (status != SEC_I_CONTINUE_NEEDED) { - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Error creating SSL context (%1)").arg(schannelErrorToString(status))); - return false; - } - if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) - return false; - schannelState = SchannelState::PerformHandshake; - return true; -} - -bool TlsCryptographSchannel::performHandshake() -{ - Q_ASSERT(d); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - if (plainSocket->state() == QAbstractSocket::UnconnectedState) { - d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, - QSslSocket::tr("The TLS/SSL connection has been closed")); - return false; - } - Q_ASSERT(SecIsValidHandle(&credentialHandle)); - Q_ASSERT(SecIsValidHandle(&contextHandle)); - Q_ASSERT(schannelState == SchannelState::PerformHandshake); - -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "Bytes available from socket: %lld", plainSocket->bytesAvailable()); - qCDebug(lcSsl, "intermediateBuffer size: %d", intermediateBuffer.size()); -#endif - - if (missingData > plainSocket->bytesAvailable()) - return true; - - missingData = 0; - readToBuffer(intermediateBuffer, plainSocket); - if (intermediateBuffer.isEmpty()) - return true; // no data, will fail - - SecBuffer outBuffers[3] = {}; - const auto freeOutBuffers = [&outBuffers]() { - for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { - if (outBuffers[i].pvBuffer) - FreeContextBuffer(outBuffers[i].pvBuffer); - } - }; - const auto outBuffersGuard = qScopeGuard(freeOutBuffers); - // For this call to InitializeSecurityContext we may need to call it twice. - // In some cases us not having a certificate isn't actually an error, but just a request. - // With Schannel, to ignore this warning, we need to call InitializeSecurityContext again - // when we get SEC_I_INCOMPLETE_CREDENTIALS! As far as I can tell it's not documented anywhere. - // https://stackoverflow.com/a/47479968/2493610 - SECURITY_STATUS status; - short attempts = 2; - do { - SecBuffer inputBuffers[2]; - inputBuffers[0] = createSecBuffer(intermediateBuffer, SECBUFFER_TOKEN); - inputBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); - SecBufferDesc inputBufferDesc{ - SECBUFFER_VERSION, - ARRAYSIZE(inputBuffers), - inputBuffers - }; - - freeOutBuffers(); // free buffers from any previous attempt - outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); - outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); - outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); - SecBufferDesc outputBufferDesc{ - SECBUFFER_VERSION, - ARRAYSIZE(outBuffers), - outBuffers - }; - - ULONG contextReq = getContextRequirements(); - TimeStamp expiry; - status = InitializeSecurityContext( - &credentialHandle, // phCredential - &contextHandle, // phContext - const_reinterpret_cast(targetName().utf16()), // pszTargetName - contextReq, // fContextReq - 0, // Reserved1 - 0, // TargetDataRep (unused) - &inputBufferDesc, // pInput - 0, // Reserved2 - nullptr, // phNewContext (we already have one) - &outputBufferDesc, // pOutput - &contextAttributes, // pfContextAttr - &expiry // ptsExpiry - ); - - if (inputBuffers[1].BufferType == SECBUFFER_EXTRA) { - // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel - // inputBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need - // to be stored. - retainExtraData(intermediateBuffer, inputBuffers[1]); - } else if (status != SEC_E_INCOMPLETE_MESSAGE) { - // Clear the buffer if we weren't asked for more data - intermediateBuffer.resize(0); - } - - --attempts; - } while (status == SEC_I_INCOMPLETE_CREDENTIALS && attempts > 0); - - switch (status) { - case SEC_E_OK: - // Need to transmit a final token in the handshake if 'cbBuffer' is non-zero. - if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) - return false; - schannelState = SchannelState::VerifyHandshake; - return true; - case SEC_I_CONTINUE_NEEDED: - if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) - return false; - // Must call InitializeSecurityContext again later (done through continueHandshake) - return true; - case SEC_I_INCOMPLETE_CREDENTIALS: - // Schannel takes care of picking certificate to send (other than the one we can specify), - // so if we get here then that means we don't have a certificate the server accepts. - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Server did not accept any certificate we could present.")); - return false; - case SEC_I_CONTEXT_EXPIRED: - // "The message sender has finished using the connection and has initiated a shutdown." - if (outBuffers[0].BufferType == SECBUFFER_TOKEN) { - if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) - return false; - } - if (!shutdown) { // we did not initiate this - d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, - QSslSocket::tr("The TLS/SSL connection has been closed")); - } - return true; - case SEC_E_INCOMPLETE_MESSAGE: - // Simply incomplete, wait for more data - missingData = checkIncompleteData(outBuffers[0]); - return true; - case SEC_E_ALGORITHM_MISMATCH: - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Algorithm mismatch")); - shutdown = true; // skip sending the "Shutdown" alert - return false; - } - - // Note: We can get here if the connection is using TLS 1.2 and the server certificate uses - // MD5, which is not allowed in Schannel. This causes an "invalid token" error during handshake. - // (If you came here investigating an error: md5 is insecure, update your certificate) - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Handshake failed: %1").arg(schannelErrorToString(status))); - return false; -} - -bool TlsCryptographSchannel::verifyHandshake() -{ - Q_ASSERT(d); - Q_ASSERT(q); - const auto &configuration = q->sslConfiguration(); - - sslErrors.clear(); - - const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; -#define CHECK_STATUS(status) \ - if (status != SEC_E_OK) { \ - d->setErrorAndEmit(QAbstractSocket::SslInternalError, \ - QSslSocket::tr("Failed to query the TLS context: %1") \ - .arg(schannelErrorToString(status))); \ - return false; \ - } - - // Everything is set up, now make sure there's nothing wrong and query some attributes... - if (!matchesContextRequirements(contextAttributes, getContextRequirements(), - configuration.peerVerifyMode(), isClient)) { - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Did not get the required attributes for the connection.")); - return false; - } - - // Get stream sizes (to know the max size of a message and the size of the header and trailer) - auto status = QueryContextAttributes(&contextHandle, - SECPKG_ATTR_STREAM_SIZES, - &streamSizes); - CHECK_STATUS(status); - - // Get session cipher info - status = QueryContextAttributes(&contextHandle, - SECPKG_ATTR_CONNECTION_INFO, - &connectionInfo); - CHECK_STATUS(status); - -#ifdef SUPPORTS_ALPN - const auto allowedProtos = configuration.allowedNextProtocols(); - if (!allowedProtos.isEmpty() && supportsAlpn()) { - SecPkgContext_ApplicationProtocol alpn; - status = QueryContextAttributes(&contextHandle, - SECPKG_ATTR_APPLICATION_PROTOCOL, - &alpn); - CHECK_STATUS(status); - if (alpn.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { - QByteArray negotiatedProto = QByteArray((const char *)alpn.ProtocolId, - alpn.ProtocolIdSize); - if (!allowedProtos.contains(negotiatedProto)) { - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Unwanted protocol was negotiated")); - return false; - } - QTlsBackend::setNegotiatedProtocol(d, negotiatedProto); - QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNegotiated); - } else { - QTlsBackend::setNegotiatedProtocol(d, {}); - QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationUnsupported); - } - } -#endif // supports ALPN - -#undef CHECK_STATUS - - // Verify certificate - CERT_CONTEXT *certificateContext = nullptr; - auto freeCertificate = qScopeGuard([&certificateContext]() { - if (certificateContext) - CertFreeCertificateContext(certificateContext); - }); - status = QueryContextAttributes(&contextHandle, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, - &certificateContext); - - // QueryPeer can (currently) not work in Schannel since Schannel itself doesn't have a way to - // ask for a certificate and then still be OK if it's not received. - // To work around this we don't request a certificate at all for QueryPeer. - // For servers AutoVerifyPeer is supposed to be treated the same as QueryPeer. - // This means that servers using Schannel will only request client certificate for "VerifyPeer". - if ((!isClient && configuration.peerVerifyMode() == QSslSocket::PeerVerifyMode::VerifyPeer) - || (isClient && configuration.peerVerifyMode() != QSslSocket::PeerVerifyMode::VerifyNone - && configuration.peerVerifyMode() != QSslSocket::PeerVerifyMode::QueryPeer)) { - if (status != SEC_E_OK) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Couldn't retrieve peer certificate, status:" - << schannelErrorToString(status); -#endif - const QSslError error{ QSslError::NoPeerCertificate }; - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - - // verifyCertContext returns false if the user disconnected while it was checking errors. - if (certificateContext && !verifyCertContext(certificateContext)) - return false; - - if (!checkSslErrors() || q->state() != QAbstractSocket::ConnectedState) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << __func__ << "was unsuccessful. Paused:" << paused; -#endif - // If we're paused then checkSslErrors returned false, but it's not an error - return d->isPaused() && q->state() == QAbstractSocket::ConnectedState; - } - - schannelState = SchannelState::Done; - return true; -} - -bool TlsCryptographSchannel::renegotiate() -{ - Q_ASSERT(d); - - SecBuffer outBuffers[3]; - outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); - outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); - outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); - auto freeBuffers = qScopeGuard([&outBuffers]() { - for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { - if (outBuffers[i].pvBuffer) - FreeContextBuffer(outBuffers[i].pvBuffer); - } - }); - SecBufferDesc outputBufferDesc{ - SECBUFFER_VERSION, - ARRAYSIZE(outBuffers), - outBuffers - }; - - ULONG contextReq = getContextRequirements(); - TimeStamp expiry; - SECURITY_STATUS status; - if (d->tlsMode() == QSslSocket::SslClientMode) { - status = InitializeSecurityContext(&credentialHandle, // phCredential - &contextHandle, // phContext - const_reinterpret_cast(targetName().utf16()), // pszTargetName - contextReq, // fContextReq - 0, // Reserved1 - 0, // TargetDataRep (unused) - nullptr, // pInput (nullptr for renegotiate) - 0, // Reserved2 - nullptr, // phNewContext (we already have one) - &outputBufferDesc, // pOutput - &contextAttributes, // pfContextAttr - &expiry // ptsExpiry - ); - } else { - status = AcceptSecurityContext( - &credentialHandle, // phCredential - &contextHandle, // phContext - nullptr, // pInput - contextReq, // fContextReq - 0, // TargetDataRep (unused) - nullptr, // phNewContext - &outputBufferDesc, // pOutput - &contextAttributes, // pfContextAttr, - &expiry // ptsTimeStamp - ); - } - if (status == SEC_I_CONTINUE_NEEDED) { - schannelState = SchannelState::PerformHandshake; - return sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer); - } else if (status == SEC_E_OK) { - schannelState = SchannelState::PerformHandshake; - return true; - } - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QSslSocket::tr("Renegotiation was unsuccessful: %1").arg(schannelErrorToString(status))); - return false; -} - -/*! - \internal - reset the state in preparation for reuse of socket -*/ -void TlsCryptographSchannel::reset() -{ - Q_ASSERT(d); - - closeCertificateStores(); // certificate stores could've changed - deallocateContext(); - freeCredentialsHandle(); // in case we already had one (@future: session resumption requires re-use) - - connectionInfo = {}; - streamSizes = {}; - - CertFreeCertificateContext(localCertContext); - localCertContext = nullptr; - - contextAttributes = 0; - intermediateBuffer.clear(); - schannelState = SchannelState::InitializeHandshake; - - - d->setEncrypted(false); - shutdown = false; - renegotiating = false; - - missingData = 0; -} - -void TlsCryptographSchannel::startClientEncryption() -{ - Q_ASSERT(q); - - if (q->isEncrypted()) - return; // let's not mess up the connection... - reset(); - continueHandshake(); -} - -void TlsCryptographSchannel::startServerEncryption() -{ - Q_ASSERT(q); - - if (q->isEncrypted()) - return; // let's not mess up the connection... - reset(); - continueHandshake(); -} - -void TlsCryptographSchannel::transmit() -{ - Q_ASSERT(q); - Q_ASSERT(d); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - if (d->tlsMode() == QSslSocket::UnencryptedMode) - 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) - return; - - if (schannelState != SchannelState::Done) { - continueHandshake(); - return; - } - - auto &writeBuffer = d->tlsWriteBuffer(); - auto &buffer = d->tlsBuffer(); - if (q->isEncrypted()) { // encrypt data in writeBuffer and write it to plainSocket - qint64 totalBytesWritten = 0; - qint64 writeBufferSize; - while ((writeBufferSize = writeBuffer.size()) > 0) { - const int headerSize = int(streamSizes.cbHeader); - const int trailerSize = int(streamSizes.cbTrailer); - // Try to read 'cbMaximumMessage' bytes from buffer before encrypting. - const int size = int(std::min(writeBufferSize, qint64(streamSizes.cbMaximumMessage))); - QByteArray fullMessage(headerSize + trailerSize + size, Qt::Uninitialized); - { - // Use peek() here instead of read() so we don't lose data if encryption fails. - qint64 copied = writeBuffer.peek(fullMessage.data() + headerSize, size); - Q_ASSERT(copied == size); - } - - SecBuffer inputBuffers[4]{ - createSecBuffer(fullMessage.data(), headerSize, SECBUFFER_STREAM_HEADER), - createSecBuffer(fullMessage.data() + headerSize, size, SECBUFFER_DATA), - createSecBuffer(fullMessage.data() + headerSize + size, trailerSize, SECBUFFER_STREAM_TRAILER), - createSecBuffer(nullptr, 0, SECBUFFER_EMPTY) - }; - SecBufferDesc message{ - SECBUFFER_VERSION, - ARRAYSIZE(inputBuffers), - inputBuffers - }; - auto status = EncryptMessage(&contextHandle, 0, &message, 0); - if (status != SEC_E_OK) { - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Schannel failed to encrypt data: %1") - .arg(schannelErrorToString(status))); - return; - } - // Data was encrypted successfully, so we free() what we peek()ed earlier - writeBuffer.free(size); - - // The trailer's size is not final, so resize fullMessage to not send trailing junk - fullMessage.resize(inputBuffers[0].cbBuffer + inputBuffers[1].cbBuffer + inputBuffers[2].cbBuffer); - const qint64 bytesWritten = plainSocket->write(fullMessage); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "Wrote %lld of total %d bytes", bytesWritten, fullMessage.length()); -#endif - if (bytesWritten >= 0) { - totalBytesWritten += bytesWritten; - } else { - d->setErrorAndEmit(plainSocket->error(), plainSocket->errorString()); - return; - } - } - - if (totalBytesWritten > 0) { - // Don't emit bytesWritten() recursively. - bool &emittedBytesWritten = d->tlsEmittedBytesWritten(); - if (!emittedBytesWritten) { - emittedBytesWritten = true; - emit q->bytesWritten(totalBytesWritten); - emittedBytesWritten = false; - } - emit q->channelBytesWritten(0, totalBytesWritten); - } - } - - 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)) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "We're still missing %lld bytes, will check later.", missingData); -#endif - break; - } - - missingData = 0; - const qint64 bytesRead = readToBuffer(intermediateBuffer, plainSocket); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "Read %lld encrypted bytes from the socket", bytesRead); -#endif - if (intermediateBuffer.length() == 0 || (hadIncompleteData && bytesRead == 0)) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, (hadIncompleteData ? "No new data received, leaving loop!" - : "Nothing to decrypt, leaving loop!")); -#endif - break; - } - hadIncompleteData = false; -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "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(dataBuffer[1].pvBuffer), - dataBuffer[1].cbBuffer); - totalRead += dataBuffer[1].cbBuffer; -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "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 (status == SEC_E_INCOMPLETE_MESSAGE) { - missingData = checkIncompleteData(dataBuffer[0]); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "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(lcSsl, "The internal SSPI handle is invalid!"); - Q_UNREACHABLE(); - } else if (status == SEC_E_INVALID_TOKEN) { - qCWarning(lcSsl, "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(); - d->setErrorAndEmit(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(); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - schannelErrorToString(status)); - break; - } else if (status == SEC_I_CONTEXT_EXPIRED) { - // 'remote' has initiated a shutdown - disconnectFromHost(); - d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, - schannelErrorToString(status)); - break; - } else if (status == SEC_I_RENEGOTIATE) { - // 'remote' wants to renegotiate -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "The peer wants to renegotiate."); -#endif - schannelState = SchannelState::Renegotiate; - renegotiating = true; - - // 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); - } - } -} - -void TlsCryptographSchannel::sendShutdown() -{ - Q_ASSERT(d); - - const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; - DWORD shutdownToken = SCHANNEL_SHUTDOWN; - SecBuffer buffer = createSecBuffer(&shutdownToken, sizeof(DWORD), SECBUFFER_TOKEN); - SecBufferDesc token{ - SECBUFFER_VERSION, - 1, - &buffer - }; - auto status = ApplyControlToken(&contextHandle, &token); - - if (status != SEC_E_OK) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Failed to apply shutdown control token:" << schannelErrorToString(status); -#endif - return; - } - - SecBuffer outBuffers[3]; - outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); - outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); - outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); - auto freeBuffers = qScopeGuard([&outBuffers]() { - for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { - if (outBuffers[i].pvBuffer) - FreeContextBuffer(outBuffers[i].pvBuffer); - } - }); - SecBufferDesc outputBufferDesc{ - SECBUFFER_VERSION, - ARRAYSIZE(outBuffers), - outBuffers - }; - - ULONG contextReq = getContextRequirements(); - TimeStamp expiry; - if (isClient) { - status = InitializeSecurityContext(&credentialHandle, // phCredential - &contextHandle, // phContext - const_reinterpret_cast(targetName().utf16()), // pszTargetName - contextReq, // fContextReq - 0, // Reserved1 - 0, // TargetDataRep (unused) - nullptr, // pInput - 0, // Reserved2 - nullptr, // phNewContext (we already have one) - &outputBufferDesc, // pOutput - &contextAttributes, // pfContextAttr - &expiry // ptsExpiry - ); - } else { - status = AcceptSecurityContext( - &credentialHandle, // phCredential - &contextHandle, // phContext - nullptr, // pInput - contextReq, // fContextReq - 0, // TargetDataRep (unused) - nullptr, // phNewContext - &outputBufferDesc, // pOutput - &contextAttributes, // pfContextAttr, - &expiry // ptsTimeStamp - ); - } - if (status == SEC_E_OK || status == SEC_I_CONTEXT_EXPIRED) { - if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, false)) { - // We failed to send the shutdown message, but it's not that important since we're - // shutting down anyway. - return; - } - } else { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "Failed to initialize shutdown:" << schannelErrorToString(status); -#endif - } -} - -void TlsCryptographSchannel::disconnectFromHost() -{ - Q_ASSERT(q); - Q_ASSERT(d); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - 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) - plainSocket->disconnectFromHost(); -} - -void TlsCryptographSchannel::disconnected() -{ - Q_ASSERT(d); - - shutdown = true; - d->setEncrypted(false); - deallocateContext(); - freeCredentialsHandle(); -} - -QSslCipher TlsCryptographSchannel::sessionCipher() const -{ - Q_ASSERT(q); - - if (!q->isEncrypted()) - return QSslCipher(); - return QSslCipher(QStringLiteral("Schannel"), sessionProtocol()); -} - -QSsl::SslProtocol TlsCryptographSchannel::sessionProtocol() const -{ - if (!q->isEncrypted()) - return QSsl::SslProtocol::UnknownProtocol; - return toQtSslProtocol(connectionInfo.dwProtocol); -} - -void TlsCryptographSchannel::continueHandshake() -{ - Q_ASSERT(q); - Q_ASSERT(d); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - const bool isServer = d->tlsMode() == QSslSocket::SslServerMode; - switch (schannelState) { - case SchannelState::InitializeHandshake: - if (!SecIsValidHandle(&credentialHandle) && !acquireCredentialsHandle()) { - disconnectFromHost(); - return; - } - if (!SecIsValidHandle(&credentialHandle)) // Needed to support tst_QSslSocket::setEmptyKey - return; - if (!SecIsValidHandle(&contextHandle) && !(isServer ? acceptContext() : createContext())) { - disconnectFromHost(); - return; - } - if (schannelState != SchannelState::PerformHandshake) - break; - Q_FALLTHROUGH(); - case SchannelState::PerformHandshake: - if (!performHandshake()) { - disconnectFromHost(); - return; - } - if (schannelState != SchannelState::VerifyHandshake) - break; - Q_FALLTHROUGH(); - case SchannelState::VerifyHandshake: - // if we're in shutdown or renegotiating then we might not need to verify - // (since we already did) - if (!verifyHandshake()) { - shutdown = true; // Skip sending shutdown alert - q->abort(); // We don't want to send buffered data - disconnectFromHost(); - return; - } - if (schannelState != SchannelState::Done) - break; - Q_FALLTHROUGH(); - case SchannelState::Done: - // connectionEncrypted is already true if we come here from a renegotiation - if (!q->isEncrypted()) { - d->setEncrypted(true); // all is done - emit q->encrypted(); - } - renegotiating = false; - if (d->isPendingClose()) { - d->setPendingClose(false); - disconnectFromHost(); - } else { - transmit(); - } - break; - case SchannelState::Renegotiate: - if (!renegotiate()) { - disconnectFromHost(); - return; - } else if (intermediateBuffer.size() || plainSocket->bytesAvailable()) { - continueHandshake(); - } - break; - } -} - -QList TlsCryptographSchannel::tlsErrors() const -{ - return sslErrors; -} - -/* - Copied from qsslsocket_mac.cpp, which was copied from qsslsocket_openssl.cpp -*/ -bool TlsCryptographSchannel::checkSslErrors() -{ - if (sslErrors.isEmpty()) - return true; - - Q_ASSERT(q); - Q_ASSERT(d); - const auto &configuration = q->sslConfiguration(); - auto *plainSocket = d->plainTcpSocket(); - - emit q->sslErrors(sslErrors); - - const bool doVerifyPeer = configuration.peerVerifyMode() == QSslSocket::VerifyPeer - || (configuration.peerVerifyMode() == QSslSocket::AutoVerifyPeer - && d->tlsMode() == QSslSocket::SslClientMode); - const bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); - // check whether we need to emit an SSL handshake error - if (doVerifyPeer && doEmitSslError) { - if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { - QSslSocketPrivate::pauseSocketNotifiers(q); - d->setPaused(true); - } else { - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - sslErrors.constFirst().errorString()); - plainSocket->disconnectFromHost(); - } - return false; - } - - return true; -} - -void TlsCryptographSchannel::initializeCertificateStores() -{ - //// helper function which turns a chain into a certificate store - Q_ASSERT(d); - Q_ASSERT(q); - const auto &configuration = q->sslConfiguration(); - - auto createStoreFromCertificateChain = [](const QList certChain, const QSslKey &privateKey) { - const wchar_t *passphrase = L""; - // Need to embed the private key in the certificate - QByteArray pkcs12 = _q_makePkcs12(certChain, - privateKey, - QString::fromWCharArray(passphrase, 0)); - CRYPT_DATA_BLOB pfxBlob; - pfxBlob.cbData = DWORD(pkcs12.length()); - pfxBlob.pbData = reinterpret_cast(pkcs12.data()); - return QHCertStorePointer(PFXImportCertStore(&pfxBlob, passphrase, 0)); - }; - - if (!configuration.localCertificateChain().isEmpty()) { - if (configuration.privateKey().isNull()) { - d->setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, - QSslSocket::tr("Cannot provide a certificate with no key")); - return; - } - if (localCertificateStore == nullptr) { - localCertificateStore = createStoreFromCertificateChain(configuration.localCertificateChain(), - configuration.privateKey()); - if (localCertificateStore == nullptr) - qCWarning(lcSsl, "Failed to load certificate chain!"); - } - } - - if (!configuration.caCertificates().isEmpty() && !caCertificateStore) { - caCertificateStore = createStoreFromCertificateChain(configuration.caCertificates(), - {}); // No private key for the CA certs - } -} - -bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) -{ - Q_ASSERT(certContext); - Q_ASSERT(q); - Q_ASSERT(d); - - const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; - - // Create a collection of stores so we can pass in multiple stores as additional locations to - // search for the certificate chain - auto tempCertCollection = QHCertStorePointer(CertOpenStore(CERT_STORE_PROV_COLLECTION, - X509_ASN_ENCODING, - 0, - CERT_STORE_CREATE_NEW_FLAG, - nullptr)); - if (!tempCertCollection) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl, "Failed to create certificate store collection!"); -#endif - return false; - } - - if (rootCertOnDemandLoadingAllowed()) { - // @future(maybe): following the OpenSSL backend these certificates should be added into - // 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")); - if (!rootStore) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl, "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(lcSsl, "Failed to add the system root CA certificate store to the certificate store collection!"); -#endif - return false; - } - } - if (caCertificateStore) { - if (!CertAddStoreToCollection(tempCertCollection.get(), caCertificateStore.get(), 0, 1)) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl, "Failed to add the user's CA certificate store to the certificate store collection!"); -#endif - return false; - } - } - - if (!CertAddStoreToCollection(tempCertCollection.get(), certContext->hCertStore, 0, 0)) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcSsl, "Failed to add certificate's origin store to the certificate store collection!"); -#endif - return false; - } - - CERT_CHAIN_PARA parameters; - ZeroMemory(¶meters, sizeof(parameters)); - parameters.cbSize = sizeof(CERT_CHAIN_PARA); - parameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; - parameters.RequestedUsage.Usage.cUsageIdentifier = 1; - LPSTR oid = LPSTR(isClient ? szOID_PKIX_KP_SERVER_AUTH - : szOID_PKIX_KP_CLIENT_AUTH); - parameters.RequestedUsage.Usage.rgpszUsageIdentifier = &oid; - - QTlsBackend::clearPeerCertificates(d); - const CERT_CHAIN_CONTEXT *chainContext = nullptr; - auto freeCertChain = qScopeGuard([&chainContext]() { - if (chainContext) - CertFreeCertificateChain(chainContext); - }); - BOOL status = CertGetCertificateChain(nullptr, // hChainEngine, default - certContext, // pCertContext - nullptr, // pTime, 'now' - tempCertCollection.get(), // hAdditionalStore, additional cert store - ¶meters, // pChainPara - CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, // dwFlags - nullptr, // reserved - &chainContext // ppChainContext - ); - if (status == FALSE || !chainContext || chainContext->cChain == 0) { - QSslError error(QSslError::UnableToVerifyFirstCertificate); - sslErrors += error; - emit q->peerVerifyError(error); - return q->state() == QAbstractSocket::ConnectedState; - } - - // Helper-function to get a QSslCertificate given a CERT_CHAIN_ELEMENT - static auto getCertificateFromChainElement = [](CERT_CHAIN_ELEMENT *element) { - if (!element) - return QSslCertificate(); - - const CERT_CONTEXT *certContext = element->pCertContext; - return QTlsPrivate::X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(certContext); - }; - - // Pick a chain to use as the certificate chain, if multiple are available: - // According to https://docs.microsoft.com/en-gb/windows/desktop/api/wincrypt/ns-wincrypt-_cert_chain_context - // this seems to be the best way to get a trusted chain. - CERT_SIMPLE_CHAIN *chain = chainContext->rgpChain[chainContext->cChain - 1]; - - if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN) { - auto error = QSslError(QSslError::SslError::UnableToGetIssuerCertificate, - getCertificateFromChainElement(chain->rgpElement[chain->cElement - 1])); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_INVALID_BASIC_CONSTRAINTS) { - // @Note: This is actually one of two errors: - // "either the certificate cannot be used to issue other certificates, or the chain path length has been exceeded." - // But here we are checking the chain's status, so we assume the "issuing" error cannot occur here. - auto error = QSslError(QSslError::PathLengthExceeded); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - static const DWORD leftoverCertChainErrorMask = CERT_TRUST_IS_CYCLIC | CERT_TRUST_INVALID_EXTENSION - | CERT_TRUST_INVALID_POLICY_CONSTRAINTS | CERT_TRUST_INVALID_NAME_CONSTRAINTS - | CERT_TRUST_CTL_IS_NOT_TIME_VALID | CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID - | CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE; - if (chain->TrustStatus.dwErrorStatus & leftoverCertChainErrorMask) { - auto error = QSslError(QSslError::SslError::UnspecifiedError); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - - DWORD verifyDepth = chain->cElement; - if (q->peerVerifyDepth() > 0 && DWORD(q->peerVerifyDepth()) < verifyDepth) - verifyDepth = DWORD(q->peerVerifyDepth()); - - const auto &caCertificates = q->sslConfiguration().caCertificates(); - QList peerCertificateChain; - for (DWORD i = 0; i < verifyDepth; i++) { - CERT_CHAIN_ELEMENT *element = chain->rgpElement[i]; - QSslCertificate certificate = getCertificateFromChainElement(element); - const QList 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; -#endif - - peerCertificateChain.append(certificate); - QTlsBackend::storePeerCertificateChain(d, peerCertificateChain); - - if (certificate.isBlacklisted()) { - const auto error = QSslError(QSslError::CertificateBlacklisted, certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - - LONG result = CertVerifyTimeValidity(nullptr /*== now */, element->pCertContext->pCertInfo); - if (result != 0) { - auto error = QSslError(result == -1 ? QSslError::CertificateNotYetValid - : QSslError::CertificateExpired, - certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - - //// Errors - if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_TIME_VALID) { - // handled right above - Q_ASSERT(!sslErrors.isEmpty()); - } - if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) { - auto error = QSslError(QSslError::CertificateRevoked, certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID) { - auto error = QSslError(QSslError::CertificateSignatureFailed, certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - - // While netscape shouldn't be relevant now it defined an extension which is - // still in use. Schannel does not check this automatically, so we do it here. - // It is used to differentiate between client and server certificates. - if (netscapeWrongCertType(extensions, isClient)) - element->TrustStatus.dwErrorStatus |= CERT_TRUST_IS_NOT_VALID_FOR_USAGE; - - if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_VALID_FOR_USAGE) { - auto error = QSslError(QSslError::InvalidPurpose, certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_UNTRUSTED_ROOT) { - // Override this error if we have the certificate inside our trusted CAs list. - const bool isTrustedRoot = caCertificates.contains(certificate); - if (!isTrustedRoot) { - auto error = QSslError(QSslError::CertificateUntrusted, certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - static const DWORD certRevocationCheckUnavailableError = CERT_TRUST_IS_OFFLINE_REVOCATION - | CERT_TRUST_REVOCATION_STATUS_UNKNOWN; - if (element->TrustStatus.dwErrorStatus & certRevocationCheckUnavailableError) { - // @future(maybe): Do something with this - } - - // Dumping ground of errors that don't fit our specific errors - static const DWORD leftoverCertErrorMask = CERT_TRUST_IS_CYCLIC - | CERT_TRUST_INVALID_EXTENSION | CERT_TRUST_INVALID_NAME_CONSTRAINTS - | CERT_TRUST_INVALID_POLICY_CONSTRAINTS - | CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT - | CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT - | CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT - | CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT - | CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT; - if (element->TrustStatus.dwErrorStatus & leftoverCertErrorMask) { - auto error = QSslError(QSslError::UnspecifiedError, certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - 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"); - }); - if (it != extensions.cend()) { - // @Note: This is actually one of two errors: - // "either the certificate cannot be used to issue other certificates, - // or the chain path length has been exceeded." - QVariantMap basicConstraints = it->value().toMap(); - QSslError error; - if (i > 0 && !basicConstraints.value(QLatin1String("ca"), false).toBool()) - error = QSslError(QSslError::InvalidPurpose, certificate); - else - error = QSslError(QSslError::PathLengthExceeded, certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_EXPLICIT_DISTRUST) { - auto error = QSslError(QSslError::CertificateBlacklisted, certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - - if (element->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) { - // If it's self-signed *and* a CA then we can assume it's a root CA certificate - // and we can ignore the "self-signed" note: - // We check the basicConstraints certificate extension when possible, but this didn't - // exist for version 1, so we can only guess in that case - const bool isRootCertificateAuthority = isCertificateAuthority(extensions) - || certificate.version() == "1"; - - // Root certificate tends to be signed by themselves, so ignore self-signed status. - if (!isRootCertificateAuthority) { - auto error = QSslError(QSslError::SelfSignedCertificate, certificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - } - - if (!peerCertificateChain.isEmpty()) - QTlsBackend::storePeerCertificate(d, peerCertificateChain.first()); - - const auto &configuration = q->sslConfiguration(); // Probably, updated by QTlsBackend::storePeerCertificate etc. - // @Note: Somewhat copied from qsslsocket_mac.cpp - const bool doVerifyPeer = q->peerVerifyMode() == QSslSocket::VerifyPeer - || (q->peerVerifyMode() == QSslSocket::AutoVerifyPeer - && d->tlsMode() == QSslSocket::SslClientMode); - // Check the peer certificate itself. First try the subject's common name - // (CN) as a wildcard, then try all alternate subject name DNS entries the - // same way. - if (!configuration.peerCertificate().isNull()) { - // but only if we're a client connecting to a server - // if we're the server, don't check CN - if (d->tlsMode() == QSslSocket::SslClientMode) { - const auto verificationPeerName = d->verificationName(); - const QString peerName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); - if (!isMatchingHostname(configuration.peerCertificate(), peerName)) { - // No matches in common names or alternate names. - const QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate()); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - } else if (doVerifyPeer) { - // No peer certificate presented. Report as error if the socket - // expected one. - const QSslError error(QSslError::NoPeerCertificate); - sslErrors += error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - - return true; -} - -bool TlsCryptographSchannel::rootCertOnDemandLoadingAllowed() -{ - Q_ASSERT(d); - return d->isRootsOnDemandAllowed() && QSslSocketPrivate::rootCertOnDemandLoadingSupported(); -} - -} // namespace QTlsPrivate - -void QSslSocketPrivate::registerAdHocFactory() -{ - // TLSTODO: this is a temporary solution, waiting for - // backends to move to ... plugins. - if (!backendSchannel()) - qCWarning(lcTlsBackend, "Failed to create backend factory"); -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qtls_schannel_p.h b/src/network/ssl/qtls_schannel_p.h deleted file mode 100644 index e4ac15206c..0000000000 --- a/src/network/ssl/qtls_schannel_p.h +++ /dev/null @@ -1,159 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLS_SCHANNEL_P_H -#define QTLS_SCHANNEL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -QT_REQUIRE_CONFIG(schannel); - -#include - -#include "qtlsbackend_schannel_p.h" -#include "qsslsocket_p.h" - -#include "qwincrypt_p.h" - -#define SECURITY_WIN32 -#define SCHANNEL_USE_BLACKLISTS 1 -#include // needed for UNICODE defines -#include -#include -#undef SCHANNEL_USE_BLACKLISTS -#undef SECURITY_WIN32 - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -class TlsCryptographSchannel final : public TlsCryptograph -{ - Q_DISABLE_COPY_MOVE(TlsCryptographSchannel) -public: - TlsCryptographSchannel(); - ~TlsCryptographSchannel(); - - void init(QSslSocket *q, QSslSocketPrivate *d) override; - - void startClientEncryption() override; - void startServerEncryption() override; - void transmit() override; - void disconnectFromHost() override; - void disconnected() override; - QSslCipher sessionCipher() const override; - QSsl::SslProtocol sessionProtocol() const override; - void continueHandshake() override; - QList tlsErrors() const override; - -private: - enum class SchannelState { - InitializeHandshake, // create and transmit context (client)/accept context (server) - PerformHandshake, // get token back, process it - VerifyHandshake, // Verify that things are OK - Done, // Connection encrypted! - Renegotiate // Renegotiating! - } schannelState = SchannelState::InitializeHandshake; - - void reset(); - bool acquireCredentialsHandle(); - ULONG getContextRequirements(); - bool createContext(); // for clients - bool acceptContext(); // for server - bool performHandshake(); - bool verifyHandshake(); - bool renegotiate(); - - bool sendToken(void *token, unsigned long tokenLength, bool emitError = true); - QString targetName() const; - - bool checkSslErrors(); - void deallocateContext(); - void freeCredentialsHandle(); - void closeCertificateStores(); - void sendShutdown(); - - void initializeCertificateStores(); - bool verifyCertContext(CERT_CONTEXT *certContext); - - bool rootCertOnDemandLoadingAllowed(); - - bool hasUndecryptedData() const override { return intermediateBuffer.size() > 0; } - - QSslSocket *q = nullptr; - QSslSocketPrivate *d = nullptr; - - SecPkgContext_ConnectionInfo connectionInfo = {}; - SecPkgContext_StreamSizes streamSizes = {}; - - CredHandle credentialHandle; // Initialized in ctor - CtxtHandle contextHandle; // Initialized in ctor - - QByteArray intermediateBuffer; // data which is left-over or incomplete - - QHCertStorePointer localCertificateStore = nullptr; - QHCertStorePointer peerCertificateStore = nullptr; - QHCertStorePointer caCertificateStore = nullptr; - - const CERT_CONTEXT *localCertContext = nullptr; - - ULONG contextAttributes = 0; - qint64 missingData = 0; - - bool renegotiating = false; - bool shutdown = false; - QList sslErrors; -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QTLS_SCHANNEL_P_H diff --git a/src/network/ssl/qtls_st.cpp b/src/network/ssl/qtls_st.cpp deleted file mode 100644 index e4f5c71c02..0000000000 --- a/src/network/ssl/qtls_st.cpp +++ /dev/null @@ -1,1331 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Copyright (C) 2014 Jeremy Lainé -** 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$ -** -****************************************************************************/ - -#include "qsslsocket.h" - -#include "qssl_p.h" -#include "qtls_st_p.h" -#include "qasn1element_p.h" -#include "qsslcertificate_p.h" -#include "qtlsbackend_st_p.h" -#include "qsslcipher_p.h" -#include "qtlskey_st_p.h" -#include "qsslkey_p.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#ifdef Q_OS_MACOS -#include -#endif - -QT_BEGIN_NAMESPACE - -Q_GLOBAL_STATIC(QSecureTransportBackend, backendSecureTransport) - -// Defined in qsslsocket_qt.cpp. -QByteArray _q_makePkcs12(const QList &certs, const QSslKey &key, - const QString &passPhrase); - -namespace QTlsPrivate { - -// Defined in qtlsbackend_st.cpp -QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher); - -namespace { - -#ifdef Q_OS_MACOS -/* - -Our own temporarykeychain is needed only on macOS where SecPKCS12Import changes -the default keychain and where we see annoying pop-ups asking about accessing a -private key. - -*/ - -struct EphemeralSecKeychain -{ - EphemeralSecKeychain(); - ~EphemeralSecKeychain(); - - SecKeychainRef keychain = nullptr; - Q_DISABLE_COPY_MOVE(EphemeralSecKeychain) -}; - -EphemeralSecKeychain::EphemeralSecKeychain() -{ - const auto uuid = QUuid::createUuid(); - if (uuid.isNull()) { - qCWarning(lcTlsBackend) << "Failed to create a unique keychain name"; - return; - } - - const QByteArray uuidAsByteArray = uuid.toByteArray(); - Q_ASSERT(uuidAsByteArray.size() > 2); - Q_ASSERT(uuidAsByteArray.startsWith('{')); - Q_ASSERT(uuidAsByteArray.endsWith('}')); - const auto uuidAsString = QLatin1String(uuidAsByteArray.data(), uuidAsByteArray.size()).mid(1, uuidAsByteArray.size() - 2); - - const QString keychainName - = QDir::tempPath() + QDir::separator() + uuidAsString + QLatin1String(".keychain"); - // SecKeychainCreate, pathName parameter: - // - // "A constant character string representing the POSIX path indicating where - // to store the keychain." - // - // Internally they seem to use std::string, but this does not really help. - // Fortunately, CFString has a convenient API. - QCFType cfName = keychainName.toCFString(); - std::vector posixPath; - // "Extracts the contents of a string as a NULL-terminated 8-bit string - // appropriate for passing to POSIX APIs." - posixPath.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfName)); - const auto ok = CFStringGetFileSystemRepresentation(cfName, &posixPath[0], - CFIndex(posixPath.size())); - if (!ok) { - qCWarning(lcTlsBackend) << "Failed to create a unique keychain name from" - << "QDir::tempPath()"; - return; - } - - std::vector passUtf8(256); - if (SecRandomCopyBytes(kSecRandomDefault, passUtf8.size(), &passUtf8[0])) { - qCWarning(lcTlsBackend) << "SecRandomCopyBytes: failed to create a key"; - return; - } - - const OSStatus status = SecKeychainCreate(&posixPath[0], passUtf8.size(), - &passUtf8[0], FALSE, nullptr, - &keychain); - if (status != errSecSuccess || !keychain) { - qCWarning(lcTlsBackend) << "SecKeychainCreate: failed to create a custom keychain"; - if (keychain) { - SecKeychainDelete(keychain); - CFRelease(keychain); - keychain = nullptr; - } - } - - if (keychain) { - SecKeychainSettings settings = {}; - settings.version = SEC_KEYCHAIN_SETTINGS_VERS1; - // Strange, huh? But that's what their docs say to do! With lockOnSleep - // == false, set interval to INT_MAX to never lock ... - settings.lockInterval = INT_MAX; - if (SecKeychainSetSettings(keychain, &settings) != errSecSuccess) - qCWarning(lcTlsBackend) << "SecKeychainSettings: failed to disable lock on sleep"; - } - -#ifdef QSSLSOCKET_DEBUG - if (keychain) { - qCDebug(lcTlsBackend) << "Custom keychain with name" << keychainName << "was created" - << "successfully"; - } -#endif -} - -EphemeralSecKeychain::~EphemeralSecKeychain() -{ - if (keychain) { - // clear file off disk - SecKeychainDelete(keychain); - CFRelease(keychain); - } -} - -#endif // Q_OS_MACOS - -void qt_releaseSecureTransportContext(SSLContextRef context) -{ - if (context) - CFRelease(context); -} - -} // unnamed namespace - -// To be also used by qtlsbackend_st.cpp (thus not in unnamed namespace). -SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode) -{ - const bool isServer = mode == QSslSocket::SslServerMode; - const SSLProtocolSide side = isServer ? kSSLServerSide : kSSLClientSide; - // We never use kSSLDatagramType, so it's kSSLStreamType unconditionally. - SSLContextRef context = SSLCreateContext(nullptr, side, kSSLStreamType); - if (!context) - qCWarning(lcSsl) << "SSLCreateContext failed"; - return context; -} - -QSecureTransportContext::QSecureTransportContext(SSLContextRef c) - : context(c) -{ -} - -QSecureTransportContext::~QSecureTransportContext() -{ - qt_releaseSecureTransportContext(context); -} - -QSecureTransportContext::operator SSLContextRef()const -{ - return context; -} - -void QSecureTransportContext::reset(SSLContextRef newContext) -{ - qt_releaseSecureTransportContext(context); - context = newContext; -} - -#if !defined(QT_PLATFORM_UIKIT) // dhparam is only used on macOS. (see the SSLSetDiffieHellmanParams call below) -static const uint8_t dhparam[] = - "\x30\x82\x01\x08\x02\x82\x01\x01\x00\x97\xea\xd0\x46\xf7\xae\xa7\x76\x80" - "\x9c\x74\x56\x98\xd8\x56\x97\x2b\x20\x6c\x77\xe2\x82\xbb\xc8\x84\xbe\xe7" - "\x63\xaf\xcc\x30\xd0\x67\x97\x7d\x1b\xab\x59\x30\xa9\x13\x67\x21\xd7\xd4" - "\x0e\x46\xcf\xe5\x80\xdf\xc9\xb9\xba\x54\x9b\x46\x2f\x3b\x45\xfc\x2f\xaf" - "\xad\xc0\x17\x56\xdd\x52\x42\x57\x45\x70\x14\xe5\xbe\x67\xaa\xde\x69\x75" - "\x30\x0d\xf9\xa2\xc4\x63\x4d\x7a\x39\xef\x14\x62\x18\x33\x44\xa1\xf9\xc1" - "\x52\xd1\xb6\x72\x21\x98\xf8\xab\x16\x1b\x7b\x37\x65\xe3\xc5\x11\x00\xf6" - "\x36\x1f\xd8\x5f\xd8\x9f\x43\xa8\xce\x9d\xbf\x5e\xd6\x2d\xfa\x0a\xc2\x01" - "\x54\xc2\xd9\x81\x54\x55\xb5\x26\xf8\x88\x37\xf5\xfe\xe0\xef\x4a\x34\x81" - "\xdc\x5a\xb3\x71\x46\x27\xe3\xcd\x24\xf6\x1b\xf1\xe2\x0f\xc2\xa1\x39\x53" - "\x5b\xc5\x38\x46\x8e\x67\x4c\xd9\xdd\xe4\x37\x06\x03\x16\xf1\x1d\x7a\xba" - "\x2d\xc1\xe4\x03\x1a\x58\xe5\x29\x5a\x29\x06\x69\x61\x7a\xd8\xa9\x05\x9f" - "\xc1\xa2\x45\x9c\x17\xad\x52\x69\x33\xdc\x18\x8d\x15\xa6\x5e\xcd\x94\xf4" - "\x45\xbb\x9f\xc2\x7b\x85\x00\x61\xb0\x1a\xdc\x3c\x86\xaa\x9f\x5c\x04\xb3" - "\x90\x0b\x35\x64\xff\xd9\xe3\xac\xf2\xf2\xeb\x3a\x63\x02\x01\x02"; -#endif - -OSStatus TlsCryptographSecureTransport::ReadCallback(TlsCryptographSecureTransport *socket, - char *data, size_t *dataLength) -{ - Q_ASSERT(socket); - Q_ASSERT(data); - Q_ASSERT(dataLength); - - Q_ASSERT(socket->d); - QTcpSocket *plainSocket = socket->d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - if (socket->isHandshakeComplete()) { - // Check if it's a renegotiation attempt, when the handshake is complete, the - // session state is 'kSSLConnected': - SSLSessionState currentState = kSSLConnected; - const OSStatus result = SSLGetSessionState(socket->context, ¤tState); - if (result != noErr) { - *dataLength = 0; - return result; - } - - if (currentState == kSSLHandshake) { - // Renegotiation detected, don't allow read more yet - 'transmit' - // will notice this and will call 'startHandshake': - *dataLength = 0; - socket->renegotiating = true; - return errSSLWouldBlock; - } - } - - const qint64 bytes = plainSocket->read(data, *dataLength); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "read" << bytes; -#endif - if (bytes < 0) { - *dataLength = 0; - return errSecIO; - } - - const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : errSecSuccess; - *dataLength = bytes; - - return err; -} - -OSStatus TlsCryptographSecureTransport::WriteCallback(TlsCryptographSecureTransport *socket, - const char *data, size_t *dataLength) -{ - Q_ASSERT(socket); - Q_ASSERT(data); - Q_ASSERT(dataLength); - - Q_ASSERT(socket->d); - QTcpSocket *plainSocket = socket->d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - const qint64 bytes = plainSocket->write(data, *dataLength); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "write" << bytes; -#endif - if (bytes < 0) { - *dataLength = 0; - return errSecIO; - } - - const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : errSecSuccess; - *dataLength = bytes; - - return err; -} - -TlsCryptographSecureTransport::TlsCryptographSecureTransport() - : context(nullptr) -{ -} - -TlsCryptographSecureTransport::~TlsCryptographSecureTransport() -{ - destroySslContext(); -} - -void TlsCryptographSecureTransport::init(QSslSocket *qObj, QSslSocketPrivate *dObj) -{ - Q_ASSERT(qObj); - Q_ASSERT(dObj); - q = qObj; - d = dObj; - - renegotiating = false; - shutdown = false; -} - -void TlsCryptographSecureTransport::continueHandshake() -{ - Q_ASSERT(q); - Q_ASSERT(d); - d->setEncrypted(true); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << d->plainTcpSocket() << "connection encrypted"; -#endif - -#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13_4, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0) - // Unlike OpenSSL, Secure Transport does not allow to negotiate protocols via - // a callback during handshake. We can only set our list of preferred protocols - // (and send it during handshake) and then receive what our peer has sent to us. - // And here we can finally try to find a match (if any). - const auto &configuration = q->sslConfiguration(); - if (__builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { - const auto &requestedProtocols = configuration.allowedNextProtocols(); - if (const int requestedCount = requestedProtocols.size()) { - QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNone); - QTlsBackend::setNegotiatedProtocol(d, {}); - - QCFType cfArray; - const OSStatus result = SSLCopyALPNProtocols(context, &cfArray); - if (result == errSecSuccess && cfArray && CFArrayGetCount(cfArray)) { - const int size = CFArrayGetCount(cfArray); - QList peerProtocols(size); - for (int i = 0; i < size; ++i) - peerProtocols[i] = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(cfArray, i)); - - for (int i = 0; i < requestedCount; ++i) { - const auto requestedName = QString::fromLatin1(requestedProtocols[i]); - for (int j = 0; j < size; ++j) { - if (requestedName == peerProtocols[j]) { - QTlsBackend::setNegotiatedProtocol(d, requestedName.toLatin1()); - QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNegotiated); - break; - } - } - if (configuration.nextProtocolNegotiationStatus() == QSslConfiguration::NextProtocolNegotiationNegotiated) - break; - } - } - } - } -#endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE - - if (!renegotiating) - emit q->encrypted(); - - if (d->isAutoStartingHandshake() && d->isPendingClose()) { - d->setPendingClose(false); - q->disconnectFromHost(); - } -} - -void TlsCryptographSecureTransport::disconnected() -{ - Q_ASSERT(d && d->plainTcpSocket()); - if (d->plainTcpSocket()->bytesAvailable() <= 0) - destroySslContext(); - // If there is still buffered data in the plain socket, don't destroy the ssl context yet. - // It will be destroyed when the socket is deleted. -} - -void TlsCryptographSecureTransport::disconnectFromHost() -{ - Q_ASSERT(d && d->plainTcpSocket()); - if (context) { - if (!shutdown) { - SSLClose(context); - shutdown = true; - } - } - d->plainTcpSocket()->disconnectFromHost(); -} - -QSslCipher TlsCryptographSecureTransport::sessionCipher() const -{ - SSLCipherSuite cipher = 0; - if (context && SSLGetNegotiatedCipher(context, &cipher) == errSecSuccess) - return QSslCipher_from_SSLCipherSuite(cipher); - - return QSslCipher(); -} - -QSsl::SslProtocol TlsCryptographSecureTransport::sessionProtocol() const -{ - if (!context) - return QSsl::UnknownProtocol; - - SSLProtocol protocol = kSSLProtocolUnknown; - const OSStatus err = SSLGetNegotiatedProtocolVersion(context, &protocol); - if (err != errSecSuccess) { - qCWarning(lcTlsBackend) << "SSLGetNegotiatedProtocolVersion failed:" << err; - return QSsl::UnknownProtocol; - } - - switch (protocol) { - case kTLSProtocol1: - return QSsl::TlsV1_0; - case kTLSProtocol11: - return QSsl::TlsV1_1; - case kTLSProtocol12: - return QSsl::TlsV1_2; - case kTLSProtocol13: - return QSsl::TlsV1_3; - default: - return QSsl::UnknownProtocol; - } -} - -void TlsCryptographSecureTransport::startClientEncryption() -{ - if (!initSslContext()) { - Q_ASSERT(d); - // Error description/code were set, 'error' emitted - // by initSslContext, but OpenSSL socket also sets error - // emits a signal twice, so ... - d->setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); - return; - } - - startHandshake(); -} - -void TlsCryptographSecureTransport::startServerEncryption() -{ - if (!initSslContext()) { - // Error description/code were set, 'error' emitted - // by initSslContext, but OpenSSL socket also sets error - // emits a signal twice, so ... - d->setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); - return; - } - - startHandshake(); -} - -void TlsCryptographSecureTransport::transmit() -{ - Q_ASSERT(q); - Q_ASSERT(d); - - // If we don't have any SSL context, don't bother transmitting. - // Edit: if SSL session closed, don't bother either. - if (!context || shutdown) - return; - - if (!isHandshakeComplete()) - startHandshake(); - - auto &writeBuffer = d->tlsWriteBuffer(); - if (isHandshakeComplete() && !writeBuffer.isEmpty()) { - qint64 totalBytesWritten = 0; - while (writeBuffer.nextDataBlockSize() > 0 && context) { - const size_t nextDataBlockSize = writeBuffer.nextDataBlockSize(); - size_t writtenBytes = 0; - const OSStatus err = SSLWrite(context, writeBuffer.readPointer(), nextDataBlockSize, &writtenBytes); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << d->plainTcpSocket() << "SSLWrite returned" << err; -#endif - if (err != errSecSuccess && err != errSSLWouldBlock) { - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QStringLiteral("SSLWrite failed: %1").arg(err)); - break; - } - - if (writtenBytes) { - writeBuffer.free(writtenBytes); - totalBytesWritten += writtenBytes; - } - - if (writtenBytes < nextDataBlockSize) - break; - } - - if (totalBytesWritten > 0) { - // Don't emit bytesWritten() recursively. - auto &emittedBytesWritten = d->tlsEmittedBytesWritten(); - if (!emittedBytesWritten) { - emittedBytesWritten = true; - emit q->bytesWritten(totalBytesWritten); - emittedBytesWritten = false; - } - emit q->channelBytesWritten(0, totalBytesWritten); - } - } - - auto &buffer = d->tlsBuffer(); - const auto readBufferMaxSize = d->maxReadBufferSize(); - if (isHandshakeComplete()) { - QVarLengthArray data; - while (context && (!readBufferMaxSize || buffer.size() < readBufferMaxSize)) { - size_t readBytes = 0; - data.resize(4096); - const OSStatus err = SSLRead(context, data.data(), data.size(), &readBytes); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << d->plainTcpSocket() << "SSLRead returned" << err; -#endif - if (err == errSSLClosedGraceful) { - shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves - d->setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, - QSslSocket::tr("The TLS/SSL connection has been closed")); - break; - } else if (err != errSecSuccess && err != errSSLWouldBlock) { - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QStringLiteral("SSLRead failed: %1").arg(err)); - break; - } - - if (err == errSSLWouldBlock && renegotiating) { - startHandshake(); - break; - } - - if (readBytes) { - buffer.append(data.constData(), readBytes); - if (bool *readyReadEmittedPointer = d->readyReadPointer()) - *readyReadEmittedPointer = true; - emit q->readyRead(); - emit q->channelReadyRead(0); - } - - if (err == errSSLWouldBlock) - break; - } - } -} - -SSLCipherSuite TlsCryptographSecureTransport::SSLCipherSuite_from_QSslCipher(const QSslCipher &ciph) -{ - if (ciph.name() == QLatin1String("AES128-SHA")) - return TLS_RSA_WITH_AES_128_CBC_SHA; - if (ciph.name() == QLatin1String("DHE-RSA-AES128-SHA")) - return TLS_DHE_RSA_WITH_AES_128_CBC_SHA; - if (ciph.name() == QLatin1String("AES256-SHA")) - return TLS_RSA_WITH_AES_256_CBC_SHA; - if (ciph.name() == QLatin1String("DHE-RSA-AES256-SHA")) - return TLS_DHE_RSA_WITH_AES_256_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-ECDSA-NULL-SHA")) - return TLS_ECDH_ECDSA_WITH_NULL_SHA; - if (ciph.name() == QLatin1String("ECDH-ECDSA-RC4-SHA")) - return TLS_ECDH_ECDSA_WITH_RC4_128_SHA; - if (ciph.name() == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) - return TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA")) - return TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA")) - return TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-ECDSA-RC4-SHA")) - return TLS_ECDHE_ECDSA_WITH_RC4_128_SHA; - if (ciph.name() == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) - return TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA")) - return TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA")) - return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-RSA-NULL-SHA")) - return TLS_ECDH_RSA_WITH_NULL_SHA; - if (ciph.name() == QLatin1String("ECDH-RSA-RC4-SHA")) - return TLS_ECDH_RSA_WITH_RC4_128_SHA; - if (ciph.name() == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) - return TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-RSA-AES128-SHA")) - return TLS_ECDH_RSA_WITH_AES_128_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-RSA-AES256-SHA")) - return TLS_ECDH_RSA_WITH_AES_256_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-RSA-RC4-SHA")) - return TLS_ECDHE_RSA_WITH_RC4_128_SHA; - if (ciph.name() == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) - return TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-RSA-AES128-SHA")) - return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA; - if (ciph.name() == QLatin1String("ECDH-RSA-AES256-SHA")) - return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; - if (ciph.name() == QLatin1String("DES-CBC3-SHA")) - return TLS_RSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.name() == QLatin1String("AES128-SHA256")) - return TLS_RSA_WITH_AES_128_CBC_SHA256; - if (ciph.name() == QLatin1String("AES256-SHA256")) - return TLS_RSA_WITH_AES_256_CBC_SHA256; - if (ciph.name() == QLatin1String("DHE-RSA-DES-CBC3-SHA")) - return TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; - if (ciph.name() == QLatin1String("DHE-RSA-AES128-SHA256")) - return TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; - if (ciph.name() == QLatin1String("DHE-RSA-AES256-SHA256")) - return TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; - if (ciph.name() == QLatin1String("AES256-GCM-SHA384")) - return TLS_RSA_WITH_AES_256_GCM_SHA384; - if (ciph.name() == QLatin1String("ECDHE-ECDSA-AES128-SHA256")) - return TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; - if (ciph.name() == QLatin1String("ECDHE-ECDSA-AES256-SHA384")) - return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; - if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA256")) - return TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; - if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA384")) - return TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; - if (ciph.name() == QLatin1String("ECDHE-RSA-AES128-SHA256")) - return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; - if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-SHA384")) - return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; - if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-SHA384")) - return TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; - if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-GCM-SHA384")) - return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; - return 0; -} - -bool TlsCryptographSecureTransport::initSslContext() -{ - Q_ASSERT(q); - Q_ASSERT(d); - - Q_ASSERT_X(!context, Q_FUNC_INFO, "invalid socket state, context is not null"); - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - const auto mode = d->tlsMode(); - - context.reset(qt_createSecureTransportContext(mode)); - if (!context) { - d->setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("SSLCreateContext failed")); - return false; - } - - const OSStatus err = SSLSetIOFuncs(context, - reinterpret_cast(&TlsCryptographSecureTransport::ReadCallback), - reinterpret_cast(&TlsCryptographSecureTransport::WriteCallback)); - if (err != errSecSuccess) { - destroySslContext(); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QStringLiteral("SSLSetIOFuncs failed: %1").arg(err)); - return false; - } - - SSLSetConnection(context, this); - - const auto &configuration = q->sslConfiguration(); - if (mode == QSslSocket::SslServerMode - && !configuration.localCertificateChain().isEmpty()) { - QString errorDescription; - QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; - if (!setSessionCertificate(errorDescription, errorCode)) { - destroySslContext(); - d->setErrorAndEmit(errorCode, errorDescription); - return false; - } - } - - if (!setSessionProtocol()) { - destroySslContext(); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, QStringLiteral("Failed to set protocol version")); - return false; - } - -#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13_4, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0) - if (__builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { - const auto protocolNames = configuration.allowedNextProtocols(); - QCFType cfNames(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks)); - if (cfNames) { - for (const QByteArray &name : protocolNames) { - if (name.size() > 255) { - qCWarning(lcTlsBackend) << "TLS ALPN extension" << name - << "is too long and will be ignored."; - continue; - } else if (name.isEmpty()) { - continue; - } - QCFString cfName(QString::fromLatin1(name).toCFString()); - CFArrayAppendValue(cfNames, cfName); - } - - if (CFArrayGetCount(cfNames)) { - // Up to the application layer to check that negotiation - // failed, and handle this non-TLS error, we do not handle - // the result of this call as an error: - if (SSLSetALPNProtocols(context, cfNames) != errSecSuccess) - qCWarning(lcTlsBackend) << "SSLSetALPNProtocols failed - too long protocol names?"; - } - } else { - qCWarning(lcTlsBackend) << "failed to allocate ALPN names array"; - } - } -#endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE - - if (mode == QSslSocket::SslClientMode) { - // enable Server Name Indication (SNI) - const auto verificationPeerName = d->verificationName(); - QString tlsHostName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); - if (tlsHostName.isEmpty()) - tlsHostName = d->tlsHostName(); - - const QByteArray ace(QUrl::toAce(tlsHostName)); - SSLSetPeerDomainName(context, ace.data(), ace.size()); - // tell SecureTransport we handle peer verification ourselves - OSStatus err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true); - if (err == errSecSuccess) - err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnCertRequested, true); - - if (err != errSecSuccess) { - destroySslContext(); - d->setErrorAndEmit(QSslSocket::SslInternalError, - QStringLiteral("SSLSetSessionOption failed: %1").arg(err)); - return false; - } - // - } else { - if (configuration.peerVerifyMode() != QSslSocket::VerifyNone) { - // kAlwaysAuthenticate - always fails even if we set break on client auth. - OSStatus err = SSLSetClientSideAuthenticate(context, kTryAuthenticate); - if (err == errSecSuccess) { - // We'd like to verify peer ourselves, otherwise handshake will - // most probably fail before we can do anything. - err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnClientAuth, true); - } - - if (err != errSecSuccess) { - destroySslContext(); - d->setErrorAndEmit(QAbstractSocket::SslInternalError, - QStringLiteral("failed to set SSL context option in server mode: %1").arg(err)); - return false; - } - } -#if !defined(QT_PLATFORM_UIKIT) - // No SSLSetDiffieHellmanParams on iOS; calling it is optional according to docs. - SSLSetDiffieHellmanParams(context, dhparam, sizeof(dhparam)); -#endif - } - if (configuration.ciphers().size() > 0) { - QVector cfCiphers; - for (const QSslCipher &cipher : configuration.ciphers()) { - if (auto sslCipher = TlsCryptographSecureTransport::SSLCipherSuite_from_QSslCipher(cipher)) - cfCiphers << sslCipher; - } - if (cfCiphers.size() == 0) { - qCWarning(lcTlsBackend) << "failed to add any of the requested ciphers from the configuration"; - return false; - } - OSStatus err = SSLSetEnabledCiphers(context, cfCiphers.data(), cfCiphers.size()); - if (err != errSecSuccess) { - qCWarning(lcTlsBackend) << "failed to set the ciphers from the configuration"; - return false; - } - } - return true; -} - -void TlsCryptographSecureTransport::destroySslContext() -{ - context.reset(nullptr); -} - -bool TlsCryptographSecureTransport::setSessionCertificate(QString &errorDescription, QAbstractSocket::SocketError &errorCode) -{ - Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); - - Q_ASSERT(d); - const auto &configuration = q->sslConfiguration(); - -#ifdef QSSLSOCKET_DEBUG - auto *plainSocket = d->plainTcpSocket(); -#endif - - QSslCertificate localCertificate; - - if (!configuration.localCertificateChain().isEmpty()) - localCertificate = configuration.localCertificateChain().at(0); - - if (!localCertificate.isNull()) { - // Require a private key as well. - if (configuration.privateKey().isNull()) { - errorCode = QAbstractSocket::SslInvalidUserDataError; - errorDescription = QStringLiteral("Cannot provide a certificate with no key"); - return false; - } - - // import certificates and key - const QString passPhrase(QString::fromLatin1("foobar")); - QCFType pkcs12 = _q_makePkcs12(configuration.localCertificateChain(), - configuration.privateKey(), passPhrase).toCFData(); - QCFType password = passPhrase.toCFString(); - const void *keys[2] = { kSecImportExportPassphrase }; - const void *values[2] = { password }; - CFIndex nKeys = 1; -#ifdef Q_OS_MACOS - bool envOk = false; - const int env = qEnvironmentVariableIntValue("QT_SSL_USE_TEMPORARY_KEYCHAIN", &envOk); - if (envOk && env) { - static const EphemeralSecKeychain temporaryKeychain; - if (temporaryKeychain.keychain) { - nKeys = 2; - keys[1] = kSecImportExportKeychain; - values[1] = temporaryKeychain.keychain; - } - } -#endif - QCFType options = CFDictionaryCreate(nullptr, keys, values, nKeys, - nullptr, nullptr); - QCFType items; - OSStatus err = SecPKCS12Import(pkcs12, options, &items); - if (err != errSecSuccess) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend) << plainSocket - << QStringLiteral("SecPKCS12Import failed: %1").arg(err); -#endif - errorCode = QAbstractSocket::SslInvalidUserDataError; - errorDescription = QStringLiteral("SecPKCS12Import failed: %1").arg(err); - return false; - } - - if (!CFArrayGetCount(items)) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend) << plainSocket << "SecPKCS12Import returned no items"; -#endif - errorCode = QAbstractSocket::SslInvalidUserDataError; - errorDescription = QStringLiteral("SecPKCS12Import returned no items"); - return false; - } - - CFDictionaryRef import = (CFDictionaryRef)CFArrayGetValueAtIndex(items, 0); - SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(import, kSecImportItemIdentity); - if (!identity) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend) << plainSocket << "SecPKCS12Import returned no identity"; -#endif - errorCode = QAbstractSocket::SslInvalidUserDataError; - errorDescription = QStringLiteral("SecPKCS12Import returned no identity"); - return false; - } - - QCFType certs = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); - if (!certs) { - errorCode = QAbstractSocket::SslInternalError; - errorDescription = QStringLiteral("Failed to allocate certificates array"); - return false; - } - - CFArrayAppendValue(certs, identity); - - CFArrayRef chain = (CFArrayRef)CFDictionaryGetValue(import, kSecImportItemCertChain); - if (chain) { - for (CFIndex i = 1, e = CFArrayGetCount(chain); i < e; ++i) - CFArrayAppendValue(certs, CFArrayGetValueAtIndex(chain, i)); - } - - err = SSLSetCertificate(context, certs); - if (err != errSecSuccess) { -#ifdef QSSLSOCKET_DEBUG - qCWarning(lcTlsBackend) - << plainSocket << QStringLiteral("Cannot set certificate and key: %1").arg(err); -#endif - errorCode = QAbstractSocket::SslInvalidUserDataError; - errorDescription = QStringLiteral("Cannot set certificate and key: %1").arg(err); - return false; - } - } - - return true; -} - -bool TlsCryptographSecureTransport::setSessionProtocol() -{ - Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); - Q_ASSERT(q); - Q_ASSERT(d); - // SecureTransport has kTLSProtocol13 constant and also, kTLSProtocolMaxSupported. - // Calling SSLSetProtocolVersionMax/Min with any of these two constants results - // in errInvalidParam and a failure to set the protocol version. This means - // no TLS 1.3 on macOS and iOS. - const auto &configuration = q->sslConfiguration(); - auto *plainSocket = d->plainTcpSocket(); - switch (configuration.protocol()) { - case QSsl::TlsV1_3: - case QSsl::TlsV1_3OrLater: - qCWarning(lcTlsBackend) << plainSocket << "SecureTransport does not support TLS 1.3"; - return false; - default:; - } - - OSStatus err = errSecSuccess; - - if (configuration.protocol() == QSsl::TlsV1_0) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.0"; - #endif - err = SSLSetProtocolVersionMin(context, kTLSProtocol1); - if (err == errSecSuccess) - err = SSLSetProtocolVersionMax(context, kTLSProtocol1); - } else if (configuration.protocol() == QSsl::TlsV1_1) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.1"; - #endif - err = SSLSetProtocolVersionMin(context, kTLSProtocol11); - if (err == errSecSuccess) - err = SSLSetProtocolVersionMax(context, kTLSProtocol11); - } else if (configuration.protocol() == QSsl::TlsV1_2) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.2"; - #endif - err = SSLSetProtocolVersionMin(context, kTLSProtocol12); - if (err == errSecSuccess) - err = SSLSetProtocolVersionMax(context, kTLSProtocol12); - } else if (configuration.protocol() == QSsl::AnyProtocol) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "requesting : any"; - #endif - err = SSLSetProtocolVersionMin(context, kTLSProtocol1); - } else if (configuration.protocol() == QSsl::SecureProtocols) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1 - TLSv1.2"; - #endif - err = SSLSetProtocolVersionMin(context, kTLSProtocol1); - } else if (configuration.protocol() == QSsl::TlsV1_0OrLater) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1 - TLSv1.2"; - #endif - err = SSLSetProtocolVersionMin(context, kTLSProtocol1); - } else if (configuration.protocol() == QSsl::TlsV1_1OrLater) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.1 - TLSv1.2"; - #endif - err = SSLSetProtocolVersionMin(context, kTLSProtocol11); - } else if (configuration.protocol() == QSsl::TlsV1_2OrLater) { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.2"; - #endif - err = SSLSetProtocolVersionMin(context, kTLSProtocol12); - } else { - #ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "no protocol version found in the configuration"; - #endif - return false; - } - - return err == errSecSuccess; -} - -bool TlsCryptographSecureTransport::canIgnoreTrustVerificationFailure() const -{ - Q_ASSERT(q); - Q_ASSERT(d); - const auto &configuration = q->sslConfiguration(); - const QSslSocket::PeerVerifyMode verifyMode = configuration.peerVerifyMode(); - return d->tlsMode() == QSslSocket::SslServerMode - && (verifyMode == QSslSocket::QueryPeer - || verifyMode == QSslSocket::AutoVerifyPeer - || verifyMode == QSslSocket::VerifyNone); -} - -bool TlsCryptographSecureTransport::verifySessionProtocol() const -{ - Q_ASSERT(q); - - const auto &configuration = q->sslConfiguration(); - bool protocolOk = false; - if (configuration.protocol() == QSsl::AnyProtocol) - protocolOk = true; - else if (configuration.protocol() == QSsl::SecureProtocols) - protocolOk = (sessionProtocol() >= QSsl::TlsV1_0); - else if (configuration.protocol() == QSsl::TlsV1_0OrLater) - protocolOk = (sessionProtocol() >= QSsl::TlsV1_0); - else if (configuration.protocol() == QSsl::TlsV1_1OrLater) - protocolOk = (sessionProtocol() >= QSsl::TlsV1_1); - else if (configuration.protocol() == QSsl::TlsV1_2OrLater) - protocolOk = (sessionProtocol() >= QSsl::TlsV1_2); - else if (configuration.protocol() == QSsl::TlsV1_3OrLater) - protocolOk = (sessionProtocol() >= QSsl::TlsV1_3OrLater); - else - protocolOk = (sessionProtocol() == configuration.protocol()); - - return protocolOk; -} - -bool TlsCryptographSecureTransport::verifyPeerTrust() -{ - Q_ASSERT(q); - Q_ASSERT(d); - - const auto mode = d->tlsMode(); - const QSslSocket::PeerVerifyMode verifyMode = q->peerVerifyMode(); - const bool canIgnoreVerify = canIgnoreTrustVerificationFailure(); - - Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); - - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - - QCFType trust; - OSStatus err = SSLCopyPeerTrust(context, &trust); - // !trust - SSLCopyPeerTrust can return errSecSuccess but null trust. - if (err != errSecSuccess || !trust) { - if (!canIgnoreVerify) { - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QStringLiteral("Failed to obtain peer trust: %1").arg(err)); - plainSocket->disconnectFromHost(); - return false; - } else { - return true; - } - } - - QList errors; - - // Store certificates. - // Apple's docs say SetTrustEvaluate must be called before - // SecTrustGetCertificateAtIndex, but this results - // in 'kSecTrustResultRecoverableTrustFailure', so - // here we just ignore 'res' (later we'll use SetAnchor etc. - // and evaluate again). - SecTrustResultType res = kSecTrustResultInvalid; - err = SecTrustEvaluate(trust, &res); - if (err != errSecSuccess) { - // We can not ignore this, it's not even about trust verification - // probably ... - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QStringLiteral("SecTrustEvaluate failed: %1").arg(err)); - plainSocket->disconnectFromHost(); - return false; - } - - QTlsBackend::clearPeerCertificates(d); - - QList peerCertificateChain; - const CFIndex certCount = SecTrustGetCertificateCount(trust); - for (CFIndex i = 0; i < certCount; ++i) { - SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); - QCFType derData = SecCertificateCopyData(cert); - peerCertificateChain << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); - } - QTlsBackend::storePeerCertificateChain(d, peerCertificateChain); - - if (peerCertificateChain.size()) - QTlsBackend::storePeerCertificate(d, peerCertificateChain.at(0)); - - // Check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer): - for (const QSslCertificate &cert : qAsConst(peerCertificateChain)) { - if (QSslCertificatePrivate::isBlacklisted(cert) && !canIgnoreVerify) { - const QSslError error(QSslError::CertificateBlacklisted, cert); - errors << error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - - const bool doVerifyPeer = verifyMode == QSslSocket::VerifyPeer - || (verifyMode == QSslSocket::AutoVerifyPeer - && d->tlsMode() == QSslSocket::SslClientMode); - // Check the peer certificate itself. First try the subject's common name - // (CN) as a wildcard, then try all alternate subject name DNS entries the - // same way. - const auto &peerCertificate = q->peerCertificate(); - if (!peerCertificate.isNull()) { - // but only if we're a client connecting to a server - // if we're the server, don't check CN - const QString verificationPeerName = d->verificationName(); - if (mode == QSslSocket::SslClientMode) { - const QString peerName(verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); - if (!isMatchingHostname(peerCertificate, peerName) && !canIgnoreVerify) { - // No matches in common names or alternate names. - const QSslError error(QSslError::HostNameMismatch, peerCertificate); - errors << error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - } else { - // No peer certificate presented. Report as error if the socket - // expected one. - if (doVerifyPeer && !canIgnoreVerify) { - const QSslError error(QSslError::NoPeerCertificate); - errors << error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; - } - } - - // verify certificate chain - QCFType certArray = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); - const auto &caCertificates = q->sslConfiguration().caCertificates(); - for (const QSslCertificate &cert : caCertificates) { - QCFType certData = cert.toDer().toCFData(); - if (QCFType secRef = SecCertificateCreateWithData(nullptr, certData)) - CFArrayAppendValue(certArray, secRef); - else - qCWarning(lcTlsBackend, "Failed to create SecCertificate from QSslCertificate"); - } - - SecTrustSetAnchorCertificates(trust, certArray); - - // By default SecTrustEvaluate uses both CA certificates provided in - // QSslConfiguration and the ones from the system database. This behavior can - // be unexpected if a user's code tries to limit the trusted CAs to those - // explicitly set in QSslConfiguration. - // Since on macOS we initialize the default QSslConfiguration copying the - // system CA certificates (using SecTrustSettingsCopyCertificates) we can - // call SecTrustSetAnchorCertificatesOnly(trust, true) to force SecTrustEvaluate - // to use anchors only from our QSslConfiguration. - // Unfortunately, SecTrustSettingsCopyCertificates is not available on iOS - // and the default QSslConfiguration always has an empty list of system CA - // certificates. This leaves no way to provide client code with access to the - // actual system CA certificate list (which most use-cases need) other than - // by letting SecTrustEvaluate fall through to the system list; so, in this case - // (even though the client code may have provided its own certs), we retain - // the default behavior. Note, with macOS SDK below 10.12 using 'trust my - // anchors only' may result in some valid chains rejected, apparently the - // ones containing intermediated certificates; so we use this functionality - // on more recent versions only. - - bool anchorsFromConfigurationOnly = false; - -#ifdef Q_OS_MACOS - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSSierra) - anchorsFromConfigurationOnly = true; -#endif // Q_OS_MACOS - - SecTrustSetAnchorCertificatesOnly(trust, anchorsFromConfigurationOnly); - - SecTrustResultType trustResult = kSecTrustResultInvalid; - SecTrustEvaluate(trust, &trustResult); - switch (trustResult) { - case kSecTrustResultUnspecified: - case kSecTrustResultProceed: - break; - default: - if (!canIgnoreVerify) { - const QSslError error(QSslError::CertificateUntrusted, peerCertificate); - errors << error; - emit q->peerVerifyError(error); - } - } - - // report errors - if (!errors.isEmpty() && !canIgnoreVerify) { - sslErrors = errors; - // checkSslErrors unconditionally emits sslErrors: - // a user's slot can abort/close/disconnect on this - // signal, so we also test the socket's state: - if (!checkSslErrors() || q->state() != QAbstractSocket::ConnectedState) - return false; - } else { - sslErrors.clear(); - } - - return true; -} - -/* - Copied verbatim from qsslsocket_openssl.cpp -*/ -bool TlsCryptographSecureTransport::checkSslErrors() -{ - if (sslErrors.isEmpty()) - return true; - - Q_ASSERT(q); - Q_ASSERT(d); - - emit q->sslErrors(sslErrors); - const auto mode = d->tlsMode(); - const auto &configuration = q->sslConfiguration(); - const bool doVerifyPeer = configuration.peerVerifyMode() == QSslSocket::VerifyPeer - || (configuration.peerVerifyMode() == QSslSocket::AutoVerifyPeer - && mode == QSslSocket::SslClientMode); - const bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); - // check whether we need to emit an SSL handshake error - if (doVerifyPeer && doEmitSslError) { - if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { - QSslSocketPrivate::pauseSocketNotifiers(q); - d->setPaused(true); - } else { - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - sslErrors.constFirst().errorString()); - Q_ASSERT(d->plainTcpSocket()); - d->plainTcpSocket()->disconnectFromHost(); - } - return false; - } - - return true; -} - -bool TlsCryptographSecureTransport::startHandshake() -{ - Q_ASSERT(context); - Q_ASSERT(q); - Q_ASSERT(d); - - auto *plainSocket = d->plainTcpSocket(); - Q_ASSERT(plainSocket); - const auto mode = d->tlsMode(); - - OSStatus err = SSLHandshake(context); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << plainSocket << "SSLHandhake returned" << err; -#endif - - if (err == errSSLWouldBlock) { - // startHandshake has to be called again ... later. - return false; - } else if (err == errSSLServerAuthCompleted) { - // errSSLServerAuthCompleted is a define for errSSLPeerAuthCompleted, - // it works for both server/client modes. - // In future we'll evaluate peer's trust at this point, - // for now we just continue. - // if (!verifyPeerTrust()) - // ... - return startHandshake(); - } else if (err == errSSLClientCertRequested) { - Q_ASSERT(mode == QSslSocket::SslClientMode); - QString errorDescription; - QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; - // setSessionCertificate does not fail if we have no certificate. - // Failure means a real error (invalid certificate, no private key, etc). - if (!setSessionCertificate(errorDescription, errorCode)) { - d->setErrorAndEmit(errorCode, errorDescription); - renegotiating = false; - return false; - } else { - // We try to resume a handshake, even if have no - // local certificates ... (up to server to deal with our failure). - return startHandshake(); - } - } else if (err != errSecSuccess) { - if (err == errSSLBadCert && canIgnoreTrustVerificationFailure()) { - // We're on the server side and client did not provide any - // certificate. This is the new 'nice' error returned by - // Security Framework after it was recently updated. - return startHandshake(); - } - - renegotiating = false; - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, - QStringLiteral("SSLHandshake failed: %1").arg(err)); - plainSocket->disconnectFromHost(); - return false; - } - - // Connection aborted during handshake phase. - if (q->state() != QAbstractSocket::ConnectedState) { - qCDebug(lcTlsBackend) << "connection aborted"; - renegotiating = false; - return false; - } - - // check protocol version ourselves, as Secure Transport does not enforce - // the requested min / max versions. - if (!verifySessionProtocol()) { - d->setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, QStringLiteral("Protocol version mismatch")); - plainSocket->disconnectFromHost(); - renegotiating = false; - return false; - } - - if (verifyPeerTrust()) { - continueHandshake(); - renegotiating = false; - return true; - } else { - renegotiating = false; - return false; - } -} - -bool TlsCryptographSecureTransport::isHandshakeComplete() const -{ - Q_ASSERT(q); - return q->isEncrypted() && !renegotiating; -} - -QList TlsCryptographSecureTransport::tlsErrors() const -{ - return sslErrors; -} - -} // namespace QTlsPrivate - -void QSslSocketPrivate::registerAdHocFactory() -{ - // TLSTODO: this is a temporary solution, waiting for - // backends to move to ... plugins. - if (!backendSecureTransport()) - qCWarning(lcTlsBackend, "Failed to create backend factory"); -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qtls_st_p.h b/src/network/ssl/qtls_st_p.h deleted file mode 100644 index 42c0ad622f..0000000000 --- a/src/network/ssl/qtls_st_p.h +++ /dev/null @@ -1,141 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Jeremy Lainé -** 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$ -** -****************************************************************************/ - -#ifndef QTLS_ST_P_H -#define QTLS_ST_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of the QtNetwork library. This header file may change from -// version to version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include "qtlsbackend_st_p.h" - -#include -#include -#include -#include - -#include "qabstractsocket.h" -#include "qsslsocket_p.h" - -#include -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -class QSecureTransportContext -{ -public: - explicit QSecureTransportContext(SSLContextRef context); - ~QSecureTransportContext(); - - operator SSLContextRef () const; - void reset(SSLContextRef newContext); -private: - SSLContextRef context; - - Q_DISABLE_COPY_MOVE(QSecureTransportContext) -}; - -class TlsCryptographSecureTransport : public TlsCryptograph -{ -public: - TlsCryptographSecureTransport(); - ~TlsCryptographSecureTransport() override; - - void init(QSslSocket *qObj, QSslSocketPrivate *dObj) override; - void continueHandshake() override; - void disconnected() override; - void disconnectFromHost() override; - QSslCipher sessionCipher() const override; - QSsl::SslProtocol sessionProtocol() const override; - void startClientEncryption() override; - void startServerEncryption() override; - void transmit() override; - QList tlsErrors() const override; - - SSLCipherSuite SSLCipherSuite_from_QSslCipher(const QSslCipher &ciph); - -private: - // SSL context management/properties: - bool initSslContext(); - void destroySslContext(); - bool setSessionCertificate(QString &errorDescription, - QAbstractSocket::SocketError &errorCode); - bool setSessionProtocol(); - // Aux. functions to do a verification during handshake phase: - bool canIgnoreTrustVerificationFailure() const; - bool verifySessionProtocol() const; - bool verifyPeerTrust(); - - bool checkSslErrors(); - bool startHandshake(); - - bool isHandshakeComplete() const; - - // IO callbacks: - static OSStatus ReadCallback(TlsCryptographSecureTransport *socket, char *data, size_t *dataLength); - static OSStatus WriteCallback(TlsCryptographSecureTransport *plainSocket, const char *data, size_t *dataLength); - - QSecureTransportContext context; - bool renegotiating = false; - QSslSocket *q = nullptr; - QSslSocketPrivate *d = nullptr; - bool shutdown = false; - QList sslErrors; - - Q_DISABLE_COPY_MOVE(TlsCryptographSecureTransport) -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QTLS_ST_P_H diff --git a/src/network/ssl/qtlsbackend.cpp b/src/network/ssl/qtlsbackend.cpp index 079cbccbc0..4c412d436f 100644 --- a/src/network/ssl/qtlsbackend.cpp +++ b/src/network/ssl/qtlsbackend.cpp @@ -46,8 +46,6 @@ #include "qsslcipher_p.h" #include "qsslkey_p.h" #include "qsslkey.h" -#else -#include "qtlsbackend_cert_p.h" #endif #include "qssl_p.h" @@ -63,7 +61,7 @@ QT_BEGIN_NAMESPACE Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, - (QTlsBackend_iid, QStringLiteral("/tlsbackends"))) + (QTlsBackend_iid, QStringLiteral("/tls"))) namespace { @@ -104,14 +102,6 @@ public: while (loader->instance(index)) ++index; - // TLSTODO: obviously, these two below should - // disappear as soon as plugins are in place. -#if QT_CONFIG(ssl) - QSslSocketPrivate::registerAdHocFactory(); -#else - static QTlsBackendCertOnly certGenerator; -#endif // QT_CONFIG(ssl) - return loaded = true; } @@ -244,6 +234,13 @@ bool TlsCryptograph::isMatchingHostname(const QString &cn, const QString &hostna return QSslSocketPrivate::isMatchingHostname(cn, hostname); } +void TlsCryptograph::setErrorAndEmit(QSslSocketPrivate *d, QAbstractSocket::SocketError errorCode, + const QString &errorDescription) const +{ + Q_ASSERT(d); + d->setErrorAndEmit(errorCode, errorDescription); +} + #endif // QT_CONFIG(ssl) #if QT_CONFIG(dtls) @@ -255,7 +252,8 @@ DtlsBase::~DtlsBase() = default; const QString QTlsBackend::builtinBackendNames[] = { QStringLiteral("schannel"), QStringLiteral("securetransport"), - QStringLiteral("openssl") + QStringLiteral("openssl"), + QStringLiteral("cert-only") }; QTlsBackend::QTlsBackend() @@ -436,18 +434,25 @@ QList QTlsBackend::availableBackendNames() QString QTlsBackend::defaultBackendName() { - // We prefer native as default: + // We prefer OpenSSL as default: const auto names = availableBackendNames(); - auto name = builtinBackendNames[nameIndexSchannel]; + auto name = builtinBackendNames[nameIndexOpenSSL]; if (names.contains(name)) return name; - name = builtinBackendNames[nameIndexSecureTransport]; + name = builtinBackendNames[nameIndexSchannel]; if (names.contains(name)) return name; - name = builtinBackendNames[nameIndexOpenSSL]; + name = builtinBackendNames[nameIndexSecureTransport]; if (names.contains(name)) return name; + const auto pos = std::find_if(names.begin(), names.end(), [](const auto &name) { + return name != builtinBackendNames[nameIndexCertOnly]; + }); + + if (pos != names.end()) + return *pos; + if (names.size()) return names[0]; @@ -787,6 +792,16 @@ void QTlsBackend::setEphemeralKey(QSslSocketPrivate *d, const QSslKey &key) d->configuration.ephemeralServerKey = key; } +void QTlsBackend::forceAutotestSecurityLevel() +{ +} + +Q_NETWORK_EXPORT void qt_ForceTlsSecurityLevel() +{ + if (auto *backend = QSslSocketPrivate::tlsBackendInUse()) + backend->forceAutotestSecurityLevel(); +} + #endif // QT_CONFIG(ssl) QT_END_NAMESPACE diff --git a/src/network/ssl/qtlsbackend_cert.cpp b/src/network/ssl/qtlsbackend_cert.cpp deleted file mode 100644 index f541381ecb..0000000000 --- a/src/network/ssl/qtlsbackend_cert.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qtlsbackend_cert_p.h" - -#ifdef QT_NO_SSL - -#include "qx509_generic_p.h" - -#include - -#include - -QT_BEGIN_NAMESPACE - -Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.cert-only"); - -QString QTlsBackendCertOnly::backendName() const -{ - return QStringLiteral("cert-only"); -} - - -QList QTlsBackendCertOnly::supportedProtocols() const -{ - return {}; -} - -QList QTlsBackendCertOnly::supportedFeatures() const -{ - return {}; -} - -QList QTlsBackendCertOnly::implementedClasses() const -{ - QList classes; - classes << QSsl::ImplementedClass::Certificate; - - return classes; -} - -QTlsPrivate::X509Certificate *QTlsBackendCertOnly::createCertificate() const -{ - return new QTlsPrivate::X509CertificateGeneric; -} - -QTlsPrivate::X509PemReaderPtr QTlsBackendCertOnly::X509PemReader() const -{ - return QTlsPrivate::X509CertificateGeneric::certificatesFromPem; -} - -QTlsPrivate::X509DerReaderPtr QTlsBackendCertOnly::X509DerReader() const -{ - return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; -} - -QT_END_NAMESPACE - -#endif // QT_NO_SSL - diff --git a/src/network/ssl/qtlsbackend_cert_p.h b/src/network/ssl/qtlsbackend_cert_p.h deleted file mode 100644 index 4a010dbf79..0000000000 --- a/src/network/ssl/qtlsbackend_cert_p.h +++ /dev/null @@ -1,83 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLSBACKEND_CERT_P_H -#define QTLSBACKEND_CERT_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include "qtlsbackend_p.h" - -#include - -#ifdef QT_NO_SSL - -QT_BEGIN_NAMESPACE - -class QTlsBackendCertOnly final : public QTlsBackend -{ -public: -private: - QString backendName() const override; - - QList supportedProtocols() const override; - QList supportedFeatures() const override; - QList implementedClasses() const override; - - QTlsPrivate::X509Certificate *createCertificate() const override; - QTlsPrivate::X509PemReaderPtr X509PemReader() const override; - QTlsPrivate::X509DerReaderPtr X509DerReader() const override; -}; - -QT_END_NAMESPACE - -#endif // QT_NO_SSL - -#endif // QTLSBACKEND_CERT_P_H diff --git a/src/network/ssl/qtlsbackend_openssl.cpp b/src/network/ssl/qtlsbackend_openssl.cpp deleted file mode 100644 index ef4aab6283..0000000000 --- a/src/network/ssl/qtlsbackend_openssl.cpp +++ /dev/null @@ -1,630 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qtlsbackend_openssl_p.h" -#include "qtlskey_openssl_p.h" -#include "qx509_openssl_p.h" -#include "qtls_openssl_p.h" -#include "qsslcipher_p.h" -//#include "qsslsocket_p.h" -#include "qsslcipher.h" - -#if QT_CONFIG(dtls) -#include "qdtls_openssl_p.h" -#endif // QT_CONFIG(dtls) - -#include "qsslsocket_openssl_symbols_p.h" -#include "qopenssl_p.h" - -#include - -#include -#include -#include -#include -#include - -#include - -QT_BEGIN_NAMESPACE - -Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl"); - -Q_GLOBAL_STATIC(QRecursiveMutex, qt_opensslInitMutex) - -static void q_loadCiphersForConnection(SSL *connection, QList &ciphers, - QList &defaultCiphers) -{ - Q_ASSERT(connection); - - STACK_OF(SSL_CIPHER) *supportedCiphers = q_SSL_get_ciphers(connection); - for (int i = 0; i < q_sk_SSL_CIPHER_num(supportedCiphers); ++i) { - if (SSL_CIPHER *cipher = q_sk_SSL_CIPHER_value(supportedCiphers, i)) { - const auto ciph = QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(cipher); - if (!ciph.isNull()) { - // Unconditionally exclude ADH and AECDH ciphers since they offer no MITM protection - if (!ciph.name().toLower().startsWith(QLatin1String("adh")) && - !ciph.name().toLower().startsWith(QLatin1String("exp-adh")) && - !ciph.name().toLower().startsWith(QLatin1String("aecdh"))) { - ciphers << ciph; - - if (ciph.usedBits() >= 128) - defaultCiphers << ciph; - } - } - } - } -} - -bool QTlsBackendOpenSSL::s_libraryLoaded = false; -bool QTlsBackendOpenSSL::s_loadedCiphersAndCerts = false; -int QTlsBackendOpenSSL::s_indexForSSLExtraData = -1; - -QString QTlsBackendOpenSSL::getErrorsFromOpenSsl() -{ - QString errorString; - char buf[256] = {}; // OpenSSL docs claim both 120 and 256; use the larger. - unsigned long errNum; - while ((errNum = q_ERR_get_error())) { - if (!errorString.isEmpty()) - errorString.append(QLatin1String(", ")); - q_ERR_error_string_n(errNum, buf, sizeof buf); - errorString.append(QString::fromLatin1(buf)); // error is ascii according to man ERR_error_string - } - return errorString; -} - -void QTlsBackendOpenSSL::logAndClearErrorQueue() -{ - const auto errors = getErrorsFromOpenSsl(); - if (errors.size()) - qCWarning(lcTlsBackend) << "Discarding errors:" << errors; -} - -void QTlsBackendOpenSSL::clearErrorQueue() -{ - const auto errs = getErrorsFromOpenSsl(); - Q_UNUSED(errs); -} - -bool QTlsBackendOpenSSL::ensureLibraryLoaded() -{ - if (!q_resolveOpenSslSymbols()) - return false; - - const QMutexLocker locker(qt_opensslInitMutex()); - - if (!s_libraryLoaded) { - // Initialize OpenSSL. - if (q_OPENSSL_init_ssl(0, nullptr) != 1) - return false; - - if (q_OpenSSL_version_num() < 0x10101000L) { - qCWarning(lcTlsBackend, "QSslSocket: OpenSSL >= 1.1.1 is required; %s was found instead", q_OpenSSL_version(OPENSSL_VERSION)); - return false; - } - - q_SSL_load_error_strings(); - q_OpenSSL_add_all_algorithms(); - - s_indexForSSLExtraData = q_CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, 0L, nullptr, nullptr, - nullptr, nullptr); - - // Initialize OpenSSL's random seed. - if (!q_RAND_status()) { - qWarning("Random number generator not seeded, disabling SSL support"); - return false; - } - - s_libraryLoaded = true; - } - - return true; -} - -QString QTlsBackendOpenSSL::backendName() const -{ - return builtinBackendNames[nameIndexOpenSSL]; -} - -bool QTlsBackendOpenSSL::isValid() const -{ - return ensureLibraryLoaded(); -} - -long QTlsBackendOpenSSL::tlsLibraryVersionNumber() const -{ - return q_OpenSSL_version_num(); -} - -QString QTlsBackendOpenSSL::tlsLibraryVersionString() const -{ - const char *versionString = q_OpenSSL_version(OPENSSL_VERSION); - if (!versionString) - return QString(); - - return QString::fromLatin1(versionString); -} - -long QTlsBackendOpenSSL::tlsLibraryBuildVersionNumber() const -{ - return OPENSSL_VERSION_NUMBER; -} - -QString QTlsBackendOpenSSL::tlsLibraryBuildVersionString() const -{ - // Using QStringLiteral to store the version string as unicode and - // avoid false positives from Google searching the playstore for old - // SSL versions. See QTBUG-46265 - return QStringLiteral(OPENSSL_VERSION_TEXT); -} - -void QTlsBackendOpenSSL::ensureInitialized() const -{ - // Old qsslsocket_openssl calls supportsSsl() (which means - // library found and symbols resolved, this already assured - // by the fact we end up in this function (isValid() returned - // true for the backend, see its code). The qsslsocket_openssl - // proceedes with loading certificate, ciphers and elliptic - // curves. - ensureCiphersAndCertsLoaded(); -} - -void QTlsBackendOpenSSL::ensureCiphersAndCertsLoaded() const -{ - const QMutexLocker locker(qt_opensslInitMutex()); - - if (s_loadedCiphersAndCerts) - return; - s_loadedCiphersAndCerts = true; - - resetDefaultCiphers(); - resetDefaultEllipticCurves(); - -#if QT_CONFIG(library) - //load symbols needed to receive certificates from system store -#if defined(Q_OS_QNX) - QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); -#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) - QList dirs = QSslSocketPrivate::unixRootCertDirectories(); - QStringList symLinkFilter; - symLinkFilter << QLatin1String("[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]"); - for (int a = 0; a < dirs.count(); ++a) { - QDirIterator iterator(QLatin1String(dirs.at(a)), symLinkFilter, QDir::Files); - if (iterator.hasNext()) { - QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); - break; - } - } -#endif -#endif // QT_CONFIG(library) - // if on-demand loading was not enabled, load the certs now - if (!QSslSocketPrivate::rootCertOnDemandLoadingSupported()) - setDefaultCaCertificates(systemCaCertificates()); -#ifdef Q_OS_WIN - //Enabled for fetching additional root certs from windows update on windows. - //This flag is set false by setDefaultCaCertificates() indicating the app uses - //its own cert bundle rather than the system one. - //Same logic that disables the unix on demand cert loading. - //Unlike unix, we do preload the certificates from the cert store. - QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); -#endif -} - -void QTlsBackendOpenSSL::resetDefaultCiphers() -{ - SSL_CTX *myCtx = q_SSL_CTX_new(q_TLS_client_method()); - // Note, we assert, not just silently return/bail out early: - // this should never happen and problems with OpenSSL's initialization - // must be caught before this (see supportsSsl()). - Q_ASSERT(myCtx); - SSL *mySsl = q_SSL_new(myCtx); - Q_ASSERT(mySsl); - - QList ciphers; - QList defaultCiphers; - - q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers); - - q_SSL_CTX_free(myCtx); - q_SSL_free(mySsl); - - setDefaultSupportedCiphers(ciphers); - setDefaultCiphers(defaultCiphers); - -#if QT_CONFIG(dtls) - ciphers.clear(); - defaultCiphers.clear(); - myCtx = q_SSL_CTX_new(q_DTLS_client_method()); - if (myCtx) { - mySsl = q_SSL_new(myCtx); - if (mySsl) { - q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers); - setDefaultDtlsCiphers(defaultCiphers); - q_SSL_free(mySsl); - } - q_SSL_CTX_free(myCtx); - } -#endif // dtls -} - -QList QTlsBackendOpenSSL::supportedProtocols() const -{ - QList protocols; - - protocols << QSsl::AnyProtocol; - protocols << QSsl::SecureProtocols; - protocols << QSsl::TlsV1_0; - protocols << QSsl::TlsV1_0OrLater; - protocols << QSsl::TlsV1_1; - protocols << QSsl::TlsV1_1OrLater; - protocols << QSsl::TlsV1_2; - protocols << QSsl::TlsV1_2OrLater; - -#ifdef TLS1_3_VERSION - protocols << QSsl::TlsV1_3; - protocols << QSsl::TlsV1_3OrLater; -#endif // TLS1_3_VERSION - -#if QT_CONFIG(dtls) - protocols << QSsl::DtlsV1_0; - protocols << QSsl::DtlsV1_0OrLater; - protocols << QSsl::DtlsV1_2; - protocols << QSsl::DtlsV1_2OrLater; -#endif // dtls - - return protocols; -} - -QList QTlsBackendOpenSSL::supportedFeatures() const -{ - QList features; - - features << QSsl::SupportedFeature::CertificateVerification; - features << QSsl::SupportedFeature::ClientSideAlpn; - features << QSsl::SupportedFeature::ServerSideAlpn; - features << QSsl::SupportedFeature::Ocsp; - features << QSsl::SupportedFeature::Psk; - features << QSsl::SupportedFeature::SessionTicket; - features << QSsl::SupportedFeature::Alerts; - - return features; -} - -QList QTlsBackendOpenSSL::implementedClasses() const -{ - QList classes; - - classes << QSsl::ImplementedClass::Key; - classes << QSsl::ImplementedClass::Certificate; - classes << QSsl::ImplementedClass::Socket; - classes << QSsl::ImplementedClass::Dtls; - classes << QSsl::ImplementedClass::EllipticCurve; - classes << QSsl::ImplementedClass::DiffieHellman; - - return classes; -} - -QTlsPrivate::TlsKey *QTlsBackendOpenSSL::createKey() const -{ - return new QTlsPrivate::TlsKeyOpenSSL; -} - -QTlsPrivate::X509Certificate *QTlsBackendOpenSSL::createCertificate() const -{ - return new QTlsPrivate::X509CertificateOpenSSL; -} - -namespace QTlsPrivate { - -// TLSTODO: remove. -#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) -QList fetchSslCertificateData(); -#endif - -QList systemCaCertificates(); - -#ifndef Q_OS_DARWIN -QList systemCaCertificates() -{ -#ifdef QSSLSOCKET_DEBUG - QElapsedTimer timer; - timer.start(); -#endif - QList systemCerts; -#if defined(Q_OS_WIN) - HCERTSTORE hSystemStore; - hSystemStore = CertOpenSystemStoreW(0, L"ROOT"); - if (hSystemStore) { - PCCERT_CONTEXT pc = nullptr; - while (1) { - pc = CertFindCertificateInStore(hSystemStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, pc); - if (!pc) - break; - QByteArray der(reinterpret_cast(pc->pbCertEncoded), - static_cast(pc->cbCertEncoded)); - QSslCertificate cert(der, QSsl::Der); - systemCerts.append(cert); - } - CertCloseStore(hSystemStore, 0); - } -#elif defined(Q_OS_UNIX) - QSet certFiles; - QDir currentDir; - QStringList nameFilters; - QList directories; - QSsl::EncodingFormat platformEncodingFormat; -# ifndef Q_OS_ANDROID - directories = QSslSocketPrivate::unixRootCertDirectories(); - nameFilters << QLatin1String("*.pem") << QLatin1String("*.crt"); - platformEncodingFormat = QSsl::Pem; -# else - // Q_OS_ANDROID - QByteArray ministroPath = qgetenv("MINISTRO_SSL_CERTS_PATH"); // Set by Ministro - directories << ministroPath; - nameFilters << QLatin1String("*.der"); - platformEncodingFormat = QSsl::Der; -# ifndef Q_OS_ANDROID_EMBEDDED - if (ministroPath.isEmpty()) { - QList certificateData = fetchSslCertificateData(); - for (int i = 0; i < certificateData.size(); ++i) { - systemCerts.append(QSslCertificate::fromData(certificateData.at(i), QSsl::Der)); - } - } else -# endif //Q_OS_ANDROID_EMBEDDED -# endif //Q_OS_ANDROID - { - currentDir.setNameFilters(nameFilters); - for (int a = 0; a < directories.count(); a++) { - currentDir.setPath(QLatin1String(directories.at(a))); - QDirIterator it(currentDir); - while (it.hasNext()) { - it.next(); - // use canonical path here to not load the same certificate twice if symlinked - certFiles.insert(it.fileInfo().canonicalFilePath()); - } - } - for (const QString& file : qAsConst(certFiles)) - systemCerts.append(QSslCertificate::fromPath(file, platformEncodingFormat)); -# ifndef Q_OS_ANDROID - systemCerts.append(QSslCertificate::fromPath(QLatin1String("/etc/pki/tls/certs/ca-bundle.crt"), QSsl::Pem)); // Fedora, Mandriva - systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/local/share/certs/ca-root-nss.crt"), QSsl::Pem)); // FreeBSD's ca_root_nss -# endif - } -#endif -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcTlsBackend) << "systemCaCertificates retrieval time " << timer.elapsed() << "ms"; - qCDebug(lcTlsBackend) << "imported " << systemCerts.count() << " certificates"; -#endif - - return systemCerts; -} -#endif // !Q_OS_DARWIN -} // namespace QTlsPrivate - -QList QTlsBackendOpenSSL::systemCaCertificates() const -{ - return QTlsPrivate::systemCaCertificates(); -} - -QTlsPrivate::DtlsCookieVerifier *QTlsBackendOpenSSL::createDtlsCookieVerifier() const -{ -#if QT_CONFIG(dtls) - return new QDtlsClientVerifierOpenSSL; -#else - qCWarning(lcTlsBackend, "Feature 'dtls' is disabled, cannot verify DTLS cookies"); - return nullptr; -#endif // QT_CONFIG(dtls) -} - -QTlsPrivate::TlsCryptograph *QTlsBackendOpenSSL::createTlsCryptograph() const -{ - return new QTlsPrivate::TlsCryptographOpenSSL; -} - -QTlsPrivate::DtlsCryptograph *QTlsBackendOpenSSL::createDtlsCryptograph(QDtls *q, int mode) const -{ -#if QT_CONFIG(dtls) - return new QDtlsPrivateOpenSSL(q, QSslSocket::SslMode(mode)); -#else - Q_UNUSED(q); - Q_UNUSED(mode); - qCWarning(lcTlsBackend, "Feature 'dtls' is disabled, cannot encrypt UDP datagrams"); - return nullptr; -#endif // QT_CONFIG(dtls) -} - -QTlsPrivate::X509ChainVerifyPtr QTlsBackendOpenSSL::X509Verifier() const -{ - return QTlsPrivate::X509CertificateOpenSSL::verify; -} - -QTlsPrivate::X509PemReaderPtr QTlsBackendOpenSSL::X509PemReader() const -{ - return QTlsPrivate::X509CertificateOpenSSL::certificatesFromPem; -} - -QTlsPrivate::X509DerReaderPtr QTlsBackendOpenSSL::X509DerReader() const -{ - return QTlsPrivate::X509CertificateOpenSSL::certificatesFromDer; -} - -QTlsPrivate::X509Pkcs12ReaderPtr QTlsBackendOpenSSL::X509Pkcs12Reader() const -{ - return QTlsPrivate::X509CertificateOpenSSL::importPkcs12; -} - -QList QTlsBackendOpenSSL::ellipticCurvesIds() const -{ - QList ids; - -#ifndef OPENSSL_NO_EC - const size_t curveCount = q_EC_get_builtin_curves(nullptr, 0); - QVarLengthArray builtinCurves(static_cast(curveCount)); - - if (q_EC_get_builtin_curves(builtinCurves.data(), curveCount) == curveCount) { - ids.reserve(curveCount); - for (const auto &ec : builtinCurves) - ids.push_back(ec.nid); - } -#endif // OPENSSL_NO_EC - - return ids; -} - - int QTlsBackendOpenSSL::curveIdFromShortName(const QString &name) const - { - int nid = 0; - if (name.isEmpty()) - return nid; - - ensureInitialized(); // TLSTODO: check if it's needed! -#ifndef OPENSSL_NO_EC - const QByteArray curveNameLatin1 = name.toLatin1(); - nid = q_OBJ_sn2nid(curveNameLatin1.data()); - - if (nid == 0) - nid = q_EC_curve_nist2nid(curveNameLatin1.data()); -#endif // !OPENSSL_NO_EC - - return nid; - } - - int QTlsBackendOpenSSL::curveIdFromLongName(const QString &name) const - { - int nid = 0; - if (name.isEmpty()) - return nid; - - ensureInitialized(); - -#ifndef OPENSSL_NO_EC - const QByteArray curveNameLatin1 = name.toLatin1(); - nid = q_OBJ_ln2nid(curveNameLatin1.data()); -#endif - - return nid; - } - - QString QTlsBackendOpenSSL::shortNameForId(int id) const - { - QString result; - -#ifndef OPENSSL_NO_EC - if (id != 0) - result = QString::fromLatin1(q_OBJ_nid2sn(id)); -#endif - - return result; - } - -QString QTlsBackendOpenSSL::longNameForId(int id) const -{ - QString result; - -#ifndef OPENSSL_NO_EC - if (id != 0) - result = QString::fromLatin1(q_OBJ_nid2ln(id)); -#endif - - return result; -} - -// NIDs of named curves allowed in TLS as per RFCs 4492 and 7027, -// see also https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 -static const int tlsNamedCurveNIDs[] = { - // RFC 4492 - NID_sect163k1, - NID_sect163r1, - NID_sect163r2, - NID_sect193r1, - NID_sect193r2, - NID_sect233k1, - NID_sect233r1, - NID_sect239k1, - NID_sect283k1, - NID_sect283r1, - NID_sect409k1, - NID_sect409r1, - NID_sect571k1, - NID_sect571r1, - - NID_secp160k1, - NID_secp160r1, - NID_secp160r2, - NID_secp192k1, - NID_X9_62_prime192v1, // secp192r1 - NID_secp224k1, - NID_secp224r1, - NID_secp256k1, - NID_X9_62_prime256v1, // secp256r1 - NID_secp384r1, - NID_secp521r1, - - // RFC 7027 - NID_brainpoolP256r1, - NID_brainpoolP384r1, - NID_brainpoolP512r1 -}; - -const size_t tlsNamedCurveNIDCount = sizeof(tlsNamedCurveNIDs) / sizeof(tlsNamedCurveNIDs[0]); - -bool QTlsBackendOpenSSL::isTlsNamedCurve(int id) const -{ - const int *const tlsNamedCurveNIDsEnd = tlsNamedCurveNIDs + tlsNamedCurveNIDCount; - return std::find(tlsNamedCurveNIDs, tlsNamedCurveNIDsEnd, id) != tlsNamedCurveNIDsEnd; -} - -QString QTlsBackendOpenSSL::msgErrorsDuringHandshake() -{ - return QSslSocket::tr("Error during SSL handshake: %1").arg(getErrorsFromOpenSsl()); -} - -QSslCipher QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(const SSL_CIPHER *cipher) -{ - Q_ASSERT(cipher); - char buf [256] = {}; - const QString desc = QString::fromLatin1(q_SSL_CIPHER_description(cipher, buf, sizeof(buf))); - int supportedBits = 0; - const int bits = q_SSL_CIPHER_get_bits(cipher, &supportedBits); - return createCiphersuite(desc, bits, supportedBits); -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qtlsbackend_openssl_p.h b/src/network/ssl/qtlsbackend_openssl_p.h deleted file mode 100644 index 1a02bf5bed..0000000000 --- a/src/network/ssl/qtlsbackend_openssl_p.h +++ /dev/null @@ -1,134 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLSBACKEND_OPENSSL_P_H -#define QTLSBACKEND_OPENSSL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include "qssldiffiehellmanparameters.h" -#include "qsslcertificate.h" -#include "qtlsbackend_p.h" - -#include -#include - -#include - -QT_BEGIN_NAMESPACE - -class QTlsBackendOpenSSL final : public QTlsBackend -{ -public: - - static QString getErrorsFromOpenSsl(); - static void logAndClearErrorQueue(); - static void clearErrorQueue(); - - static bool ensureLibraryLoaded(); - // Index used in SSL_get_ex_data to get the matching TlsCryptographerOpenSSL: - static bool s_libraryLoaded; - static bool s_loadedCiphersAndCerts; - static int s_indexForSSLExtraData; - - static QString msgErrorsDuringHandshake(); - static QSslCipher qt_OpenSSL_cipher_to_QSslCipher(const SSL_CIPHER *cipher); -private: - - QString backendName() const override; - bool isValid() const override; - long tlsLibraryVersionNumber() const override; - QString tlsLibraryVersionString() const override; - long tlsLibraryBuildVersionNumber() const override; - QString tlsLibraryBuildVersionString() const override; - - void ensureInitialized() const override; - void ensureCiphersAndCertsLoaded() const; - static void resetDefaultCiphers(); - - QList supportedProtocols() const override; - QList supportedFeatures() const override; - QList implementedClasses() const override; - - // QSslKey: - QTlsPrivate::TlsKey *createKey() const override; - - // QSslCertificate: - QTlsPrivate::X509Certificate *createCertificate() const override; - QList systemCaCertificates() const override; - - QTlsPrivate::TlsCryptograph *createTlsCryptograph() const override; - QTlsPrivate::DtlsCookieVerifier *createDtlsCookieVerifier() const override; - QTlsPrivate::DtlsCryptograph *createDtlsCryptograph(QDtls *q, int mode) const override; - - QTlsPrivate::X509ChainVerifyPtr X509Verifier() const override; - QTlsPrivate::X509PemReaderPtr X509PemReader() const override; - QTlsPrivate::X509DerReaderPtr X509DerReader() const override; - QTlsPrivate::X509Pkcs12ReaderPtr X509Pkcs12Reader() const override; - - // Elliptic curves: - QList ellipticCurvesIds() const override; - int curveIdFromShortName(const QString &name) const override; - int curveIdFromLongName(const QString &name) const override; - QString shortNameForId(int cid) const override; - QString longNameForId(int cid) const override; - bool isTlsNamedCurve(int cid) const override; - - // DH parameters: - using DHParams = QSslDiffieHellmanParameters; - int dhParametersFromDer(const QByteArray &derData, QByteArray *data) const override; - int dhParametersFromPem(const QByteArray &pemData, QByteArray *data) const override; -}; - -QT_END_NAMESPACE - -#endif // QTLSBACKEND_OPENSSL_P_H - - diff --git a/src/network/ssl/qtlsbackend_p.h b/src/network/ssl/qtlsbackend_p.h index 4d2bc25300..1a6110b17b 100644 --- a/src/network/ssl/qtlsbackend_p.h +++ b/src/network/ssl/qtlsbackend_p.h @@ -226,6 +226,9 @@ public: static bool isMatchingHostname(const QSslCertificate &cert, const QString &peerName); static bool isMatchingHostname(const QString &cn, const QString &hostname); + + void setErrorAndEmit(QSslSocketPrivate *d, QAbstractSocket::SocketError errorCode, + const QString &errorDescription) const; }; #else class TlsCryptograph; @@ -371,6 +374,7 @@ public: static constexpr const int nameIndexSchannel = 0; static constexpr const int nameIndexSecureTransport = 1; static constexpr const int nameIndexOpenSSL = 2; + static constexpr const int nameIndexCertOnly = 3; static const QString builtinBackendNames[]; @@ -428,6 +432,8 @@ public: static void addTustedRoot(QSslSocketPrivate *d, const QSslCertificate &rootCert); // The next one - is a "very important" feature! Kidding ... static void setEphemeralKey(QSslSocketPrivate *d, const QSslKey &key); + + virtual void forceAutotestSecurityLevel(); #endif // QT_CONFIG(ssl) Q_DISABLE_COPY_MOVE(QTlsBackend) diff --git a/src/network/ssl/qtlsbackend_schannel_p.h b/src/network/ssl/qtlsbackend_schannel_p.h deleted file mode 100644 index ca1cb9e621..0000000000 --- a/src/network/ssl/qtlsbackend_schannel_p.h +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLSBACKEND_ST_P_H -#define QTLSBACKEND_ST_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include "qtlsbackend_p.h" - -#include - - -QT_BEGIN_NAMESPACE - -class QSchannelBackend : public QTlsBackend -{ -public: - static void ensureInitializedImplementation(); - -private: - long tlsLibraryVersionNumber() const override; - QString tlsLibraryVersionString() const override; - long tlsLibraryBuildVersionNumber() const override; - QString tlsLibraryBuildVersionString() const override; - void ensureInitialized() const override; - - static void resetDefaultCiphers(); - - QString backendName() const override; - QList supportedProtocols() const override; - QList supportedFeatures() const override; - QList implementedClasses() const override; - - QTlsPrivate::TlsKey *createKey() const override; - QTlsPrivate::X509Certificate *createCertificate() const override; - - QTlsPrivate::TlsCryptograph * createTlsCryptograph() const override; - - QList systemCaCertificates() const override; - static QList systemCaCertificatesImplementation(); - - QTlsPrivate::X509PemReaderPtr X509PemReader() const override; - QTlsPrivate::X509DerReaderPtr X509DerReader() const override; - - static bool s_loadedCiphersAndCerts; -}; - -QT_END_NAMESPACE - -#endif // QTLSBACKEND_ST_P_H - - diff --git a/src/network/ssl/qtlsbackend_st.cpp b/src/network/ssl/qtlsbackend_st.cpp deleted file mode 100644 index 7fc7692350..0000000000 --- a/src/network/ssl/qtlsbackend_st.cpp +++ /dev/null @@ -1,341 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qtlsbackend_st_p.h" -#include "qtlskey_st_p.h" -#include "qx509_st_p.h" -#include "qtls_st_p.h" - -#include -#include - -QT_BEGIN_NAMESPACE - -Q_GLOBAL_STATIC(QRecursiveMutex, qt_securetransport_mutex) - -Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.securetransport"); - -namespace QTlsPrivate { - -QList systemCaCertificates(); // defined in qsslsocket_mac_shared.cpp - -SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode); - -QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher) -{ - QString name; - switch (cipher) { - // Sorted as in CipherSuite.h (and groupped by their RFC) - // TLS addenda using AES, per RFC 3268 - case TLS_RSA_WITH_AES_128_CBC_SHA: - name = QLatin1String("AES128-SHA"); - break; - case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: - name = QLatin1String("DHE-RSA-AES128-SHA"); - break; - case TLS_RSA_WITH_AES_256_CBC_SHA: - name = QLatin1String("AES256-SHA"); - break; - case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: - name = QLatin1String("DHE-RSA-AES256-SHA"); - break; - - // ECDSA addenda, RFC 4492 - case TLS_ECDH_ECDSA_WITH_NULL_SHA: - name = QLatin1String("ECDH-ECDSA-NULL-SHA"); - break; - case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: - name = QLatin1String("ECDH-ECDSA-RC4-SHA"); - break; - case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: - name = QLatin1String("ECDH-ECDSA-DES-CBC3-SHA"); - break; - case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: - name = QLatin1String("ECDH-ECDSA-AES128-SHA"); - break; - case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: - name = QLatin1String("ECDH-ECDSA-AES256-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_NULL_SHA: - name = QLatin1String("ECDHE-ECDSA-NULL-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: - name = QLatin1String("ECDHE-ECDSA-RC4-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: - name = QLatin1String("ECDHE-ECDSA-DES-CBC3-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: - name = QLatin1String("ECDHE-ECDSA-AES128-SHA"); - break; - case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: - name = QLatin1String("ECDHE-ECDSA-AES256-SHA"); - break; - case TLS_ECDH_RSA_WITH_NULL_SHA: - name = QLatin1String("ECDH-RSA-NULL-SHA"); - break; - case TLS_ECDH_RSA_WITH_RC4_128_SHA: - name = QLatin1String("ECDH-RSA-RC4-SHA"); - break; - case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: - name = QLatin1String("ECDH-RSA-DES-CBC3-SHA"); - break; - case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: - name = QLatin1String("ECDH-RSA-AES128-SHA"); - break; - case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: - name = QLatin1String("ECDH-RSA-AES256-SHA"); - break; - case TLS_ECDHE_RSA_WITH_NULL_SHA: - name = QLatin1String("ECDHE-RSA-NULL-SHA"); - break; - case TLS_ECDHE_RSA_WITH_RC4_128_SHA: - name = QLatin1String("ECDHE-RSA-RC4-SHA"); - break; - case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: - name = QLatin1String("ECDHE-RSA-DES-CBC3-SHA"); - break; - case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: - name = QLatin1String("ECDHE-RSA-AES128-SHA"); - break; - case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: - name = QLatin1String("ECDHE-RSA-AES256-SHA"); - break; - - // TLS 1.2 addenda, RFC 5246 - case TLS_RSA_WITH_3DES_EDE_CBC_SHA: - name = QLatin1String("DES-CBC3-SHA"); - break; - case TLS_RSA_WITH_AES_128_CBC_SHA256: - name = QLatin1String("AES128-SHA256"); - break; - case TLS_RSA_WITH_AES_256_CBC_SHA256: - name = QLatin1String("AES256-SHA256"); - break; - case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: - name = QLatin1String("DHE-RSA-DES-CBC3-SHA"); - break; - case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: - name = QLatin1String("DHE-RSA-AES128-SHA256"); - break; - case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: - name = QLatin1String("DHE-RSA-AES256-SHA256"); - break; - - // Addendum from RFC 4279, TLS PSK - // all missing atm. - - // RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption - // all missing atm. - - // Addenda from rfc 5288 AES Galois Counter Mode (CGM) Cipher Suites for TLS - case TLS_RSA_WITH_AES_256_GCM_SHA384: - name = QLatin1String("AES256-GCM-SHA384"); - break; - - // RFC 5487 - PSK with SHA-256/384 and AES GCM - // all missing atm. - - // Addenda from rfc 5289 Elliptic Curve Cipher Suites with HMAC SHA-256/384 - case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: - name = QLatin1String("ECDHE-ECDSA-AES128-SHA256"); - break; - case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: - name = QLatin1String("ECDHE-ECDSA-AES256-SHA384"); - break; - case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: - name = QLatin1String("ECDH-ECDSA-AES128-SHA256"); - break; - case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: - name = QLatin1String("ECDH-ECDSA-AES256-SHA384"); - break; - case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: - name = QLatin1String("ECDHE-RSA-AES128-SHA256"); - break; - case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: - name = QLatin1String("ECDHE-RSA-AES256-SHA384"); - break; - case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: - name = QLatin1String("ECDH-RSA-AES128-SHA256"); - break; - case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: - name = QLatin1String("ECDH-RSA-AES256-SHA384"); - break; - - // Addenda from rfc 5289 Elliptic Curve Cipher Suites - // with SHA-256/384 and AES Galois Counter Mode (GCM) - case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: - name = QLatin1String("ECDHE-RSA-AES256-GCM-SHA384"); - break; - - default: - return {}; - } - - return QTlsBackend::createCiphersuite(name, QSsl::TlsV1_2, QLatin1String("TLSv1.2")); -} - -} // namespace QTlsPrivate - -bool QSecureTransportBackend::s_loadedCiphersAndCerts = false; - -QString QSecureTransportBackend::tlsLibraryVersionString() const -{ - return QLatin1String("Secure Transport, ") + QSysInfo::prettyProductName(); -} - -QString QSecureTransportBackend::tlsLibraryBuildVersionString() const -{ - return tlsLibraryVersionString(); -} - -void QSecureTransportBackend::ensureInitialized() const -{ - const QMutexLocker locker(qt_securetransport_mutex()); - if (s_loadedCiphersAndCerts) - return; - - // We have to set it before setDefaultSupportedCiphers, - // since this function can trigger static (global)'s initialization - // and as a result - recursive ensureInitialized call - // from QSslCertificatePrivate's ctor. - s_loadedCiphersAndCerts = true; - - const QTlsPrivate::QSecureTransportContext context(QTlsPrivate::qt_createSecureTransportContext(QSslSocket::SslClientMode)); - if (context) { - QList ciphers; - QList defaultCiphers; - - size_t numCiphers = 0; - // Fails only if any of parameters is null. - SSLGetNumberSupportedCiphers(context, &numCiphers); - QList cfCiphers(numCiphers); - // Fails only if any of parameter is null or number of ciphers is wrong. - SSLGetSupportedCiphers(context, cfCiphers.data(), &numCiphers); - - for (size_t i = 0; i < size_t(cfCiphers.size()); ++i) { - const QSslCipher ciph(QTlsPrivate::QSslCipher_from_SSLCipherSuite(cfCiphers.at(i))); - if (!ciph.isNull()) { - ciphers << ciph; - if (ciph.usedBits() >= 128) - defaultCiphers << ciph; - } - } - - setDefaultSupportedCiphers(ciphers); - setDefaultCiphers(defaultCiphers); - - if (!QSslSocketPrivate::rootCertOnDemandLoadingSupported()) - setDefaultCaCertificates(systemCaCertificates()); - } else { - s_loadedCiphersAndCerts = false; - } -} - -QString QSecureTransportBackend::backendName() const -{ - return builtinBackendNames[nameIndexSecureTransport]; -} - -QTlsPrivate::TlsKey *QSecureTransportBackend::createKey() const -{ - return new QTlsPrivate::TlsKeySecureTransport; -} - -QTlsPrivate::X509Certificate *QSecureTransportBackend::createCertificate() const -{ - return new QTlsPrivate::X509CertificateSecureTransport; -} - -QList QSecureTransportBackend::systemCaCertificates() const -{ - return QTlsPrivate::systemCaCertificates(); -} - -QList QSecureTransportBackend::supportedProtocols() const -{ - QList protocols; - - protocols << QSsl::AnyProtocol; - protocols << QSsl::SecureProtocols; - protocols << QSsl::TlsV1_0; - protocols << QSsl::TlsV1_0OrLater; - protocols << QSsl::TlsV1_1; - protocols << QSsl::TlsV1_1OrLater; - protocols << QSsl::TlsV1_2; - protocols << QSsl::TlsV1_2OrLater; - - return protocols; -} - -QList QSecureTransportBackend::supportedFeatures() const -{ - QList features; - features << QSsl::SupportedFeature::ClientSideAlpn; - - return features; -} - -QList QSecureTransportBackend::implementedClasses() const -{ - QList classes; - classes << QSsl::ImplementedClass::Socket; - classes << QSsl::ImplementedClass::Certificate; - classes << QSsl::ImplementedClass::Key; - - return classes; -} - -QTlsPrivate::X509PemReaderPtr QSecureTransportBackend::X509PemReader() const -{ - return QTlsPrivate::X509CertificateGeneric::certificatesFromPem; -} - -QTlsPrivate::X509DerReaderPtr QSecureTransportBackend::X509DerReader() const -{ - return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; -} - -QTlsPrivate::TlsCryptograph *QSecureTransportBackend::createTlsCryptograph() const -{ - return new QTlsPrivate::TlsCryptographSecureTransport; -} - -QT_END_NAMESPACE - diff --git a/src/network/ssl/qtlsbackend_st_p.h b/src/network/ssl/qtlsbackend_st_p.h deleted file mode 100644 index b0f3050674..0000000000 --- a/src/network/ssl/qtlsbackend_st_p.h +++ /dev/null @@ -1,94 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLSBACKEND_ST_P_H -#define QTLSBACKEND_ST_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include "qtlsbackend_p.h" - -#include - - -QT_BEGIN_NAMESPACE - -class QSecureTransportBackend : public QTlsBackend -{ -private: - - QString tlsLibraryVersionString() const override; - virtual QString tlsLibraryBuildVersionString() const override; - virtual void ensureInitialized() const override; - - QString backendName() const override; - - QList supportedProtocols() const override; - QList supportedFeatures() const override; - QList implementedClasses() const override; - - QTlsPrivate::TlsKey *createKey() const override; - QTlsPrivate::X509Certificate *createCertificate() const override; - - QList systemCaCertificates() const override; - - QTlsPrivate::X509PemReaderPtr X509PemReader() const override; - QTlsPrivate::X509DerReaderPtr X509DerReader() const override; - - QTlsPrivate::TlsCryptograph *createTlsCryptograph() const override; - - static bool s_loadedCiphersAndCerts; -}; - -QT_END_NAMESPACE - -#endif // QTLSBACKEND_ST_P_H - - diff --git a/src/network/ssl/qtlskey_base.cpp b/src/network/ssl/qtlskey_base.cpp deleted file mode 100644 index 13ce063f30..0000000000 --- a/src/network/ssl/qtlskey_base.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qtlskey_base_p.h" -#include "qasn1element_p.h" - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -QByteArray TlsKeyBase::pemFromDer(const QByteArray &der, const QMap &headers) const -{ - QByteArray pem(der.toBase64()); - - const int lineWidth = 64; // RFC 1421 - const int newLines = pem.size() / lineWidth; - const bool rem = pem.size() % lineWidth; - - for (int i = 0; i < newLines; ++i) - pem.insert((i + 1) * lineWidth + i, '\n'); - if (rem) - pem.append('\n'); - - QByteArray extra; - if (!headers.isEmpty()) { - QMap::const_iterator it = headers.constEnd(); - do { - --it; - extra += it.key() + ": " + it.value() + '\n'; - } while (it != headers.constBegin()); - extra += '\n'; - } - - if (isEncryptedPkcs8(der)) { - pem.prepend(pkcs8Header(true) + '\n' + extra); - pem.append(pkcs8Footer(true) + '\n'); - } else if (isPkcs8()) { - pem.prepend(pkcs8Header(false) + '\n' + extra); - pem.append(pkcs8Footer(false) + '\n'); - } else { - pem.prepend(pemHeader() + '\n' + extra); - pem.append(pemFooter() + '\n'); - } - - return pem; -} - -QByteArray TlsKeyBase::pkcs8Header(bool encrypted) -{ - return encrypted - ? QByteArrayLiteral("-----BEGIN ENCRYPTED PRIVATE KEY-----") - : QByteArrayLiteral("-----BEGIN PRIVATE KEY-----"); -} - -QByteArray TlsKeyBase::pkcs8Footer(bool encrypted) -{ - return encrypted - ? QByteArrayLiteral("-----END ENCRYPTED PRIVATE KEY-----") - : QByteArrayLiteral("-----END PRIVATE KEY-----"); -} - -bool TlsKeyBase::isEncryptedPkcs8(const QByteArray &der) -{ - static const QList pbes1OIds { - // PKCS5 - { PKCS5_MD2_DES_CBC_OID }, { PKCS5_MD2_RC2_CBC_OID }, { PKCS5_MD5_DES_CBC_OID }, - { PKCS5_MD5_RC2_CBC_OID }, { PKCS5_SHA1_DES_CBC_OID }, { PKCS5_SHA1_RC2_CBC_OID }, - }; - QAsn1Element elem; - if (!elem.read(der) || elem.type() != QAsn1Element::SequenceType) - return false; - - const auto items = elem.toList(); - if (items.size() != 2 - || items[0].type() != QAsn1Element::SequenceType - || items[1].type() != QAsn1Element::OctetStringType) { - return false; - } - - const auto encryptionSchemeContainer = items[0].toList(); - if (encryptionSchemeContainer.size() != 2 - || encryptionSchemeContainer[0].type() != QAsn1Element::ObjectIdentifierType - || encryptionSchemeContainer[1].type() != QAsn1Element::SequenceType) { - return false; - } - - const QByteArray encryptionScheme = encryptionSchemeContainer[0].toObjectId(); - return encryptionScheme == PKCS5_PBES2_ENCRYPTION_OID - || pbes1OIds.contains(encryptionScheme) - || encryptionScheme.startsWith(PKCS12_OID); -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - - diff --git a/src/network/ssl/qtlskey_base_p.h b/src/network/ssl/qtlskey_base_p.h deleted file mode 100644 index 6befed876c..0000000000 --- a/src/network/ssl/qtlskey_base_p.h +++ /dev/null @@ -1,112 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLSKEY_BASE_P_H -#define QTLSKEY_BASE_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include - -#include - -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -// TLSTODO: Note, 'base' is supposed to move to plugins together with -// 'generic' and 'backendXXX'. -class TlsKeyBase : public TlsKey -{ -public: - TlsKeyBase(KeyType type = QSsl::PublicKey, KeyAlgorithm algorithm = QSsl::Opaque) - : keyType(type), - keyAlgorithm(algorithm) - { - } - - bool isNull() const override - { - return keyIsNull; - } - KeyType type() const override - { - return keyType; - } - KeyAlgorithm algorithm() const override - { - return keyAlgorithm; - } - bool isPkcs8 () const override - { - return false; - } - - QByteArray pemFromDer(const QByteArray &der, const QMap &headers) const override; - -protected: - static QByteArray pkcs8Header(bool encrypted); - static QByteArray pkcs8Footer(bool encrypted); - static bool isEncryptedPkcs8(const QByteArray &der); -public: - // TLSTODO: this public is quick fix needed by old _openssl classes - // will become non-public as soon as those classes fixed. - bool keyIsNull = true; - KeyType keyType = QSsl::PublicKey; - KeyAlgorithm keyAlgorithm = QSsl::Opaque; -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QTLSKEY_BASE_P_H diff --git a/src/network/ssl/qtlskey_generic.cpp b/src/network/ssl/qtlskey_generic.cpp deleted file mode 100644 index b0ab0bfa96..0000000000 --- a/src/network/ssl/qtlskey_generic.cpp +++ /dev/null @@ -1,884 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Jeremy Lainé -** 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$ -** -****************************************************************************/ - -#include "qtlskey_generic_p.h" -#include "qasn1element_p.h" -#include "qsslkey_p.h" - -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include - -QT_BEGIN_NAMESPACE - -// The code here is essentially what we had in qsslkey_qt.cpp before, with -// minimal changes/restructure. - -namespace QTlsPrivate { - -// OIDs of named curves allowed in TLS as per RFCs 4492 and 7027, -// see also https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 -namespace { - -const quint8 bits_table[256] = { - 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4, - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, -}; - -using OidLengthMap = QMap; - -OidLengthMap createOidMap() -{ - OidLengthMap oids; - oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.10045.3.1.1"), 192); // secp192r1 a.k.a prime192v1 - oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.10045.3.1.7"), 256); // secp256r1 a.k.a prime256v1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.1"), 193); // sect193r2 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.10"), 256); // secp256k1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.16"), 283); // sect283k1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.17"), 283); // sect283r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.26"), 233); // sect233k1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.27"), 233); // sect233r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.3"), 239); // sect239k1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.30"), 160); // secp160r2 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.31"), 192); // secp192k1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.32"), 224); // secp224k1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.33"), 224); // secp224r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.34"), 384); // secp384r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.35"), 521); // secp521r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.36"), 409); // sect409k1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.37"), 409); // sect409r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.38"), 571); // sect571k1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.39"), 571); // sect571r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.8"), 160); // secp160r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.9"), 160); // secp160k1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.36.3.3.2.8.1.1.11"), 384); // brainpoolP384r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.36.3.3.2.8.1.1.13"), 512); // brainpoolP512r1 - oids.insert(oids.cend(), QByteArrayLiteral("1.3.36.3.3.2.8.1.1.7"), 256); // brainpoolP256r1 - return oids; -} - -} // Unnamed namespace. - -Q_GLOBAL_STATIC_WITH_ARGS(OidLengthMap, oidLengthMap, (createOidMap())) - -namespace { - -// Maps OIDs to the encryption cipher they specify -const QMap oidCipherMap { - {DES_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::DesCbc}, - {DES_EDE3_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::DesEde3Cbc}, - // {PKCS5_MD2_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, // No MD2 - {PKCS5_MD5_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, - {PKCS5_SHA1_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, - // {PKCS5_MD2_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, // No MD2 - {PKCS5_MD5_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, - {PKCS5_SHA1_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, - {RC2_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Rc2Cbc} - // {RC5_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Rc5Cbc}, // No RC5 - // {AES128_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes128}, // no AES - // {AES192_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes192}, - // {AES256_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes256} -}; - -struct EncryptionData -{ - EncryptionData() = default; - - EncryptionData(QSslKeyPrivate::Cipher cipher, QByteArray key, QByteArray iv) - : initialized(true), cipher(cipher), key(key), iv(iv) - { - } - bool initialized = false; - QSslKeyPrivate::Cipher cipher; - QByteArray key; - QByteArray iv; -}; - -EncryptionData readPbes2(const QList &element, const QByteArray &passPhrase) -{ - // RFC 8018: https://tools.ietf.org/html/rfc8018#section-6.2 - /*** Scheme: *** - * Sequence (scheme-specific info..) - * Sequence (key derivation info) - * Object Identifier (Key derivation algorithm (e.g. PBKDF2)) - * Sequence (salt) - * CHOICE (this entry can be either of the types it contains) - * Octet string (actual salt) - * Object identifier (Anything using this is deferred to a later version of PKCS #5) - * Integer (iteration count) - * Sequence (encryption algorithm info) - * Object identifier (identifier for the algorithm) - * Algorithm dependent, is covered in the switch further down - */ - - static const QMap pbes2OidHashFunctionMap { - // PBES2/PBKDF2 - {HMAC_WITH_SHA1, QCryptographicHash::Sha1}, - {HMAC_WITH_SHA224, QCryptographicHash::Sha224}, - {HMAC_WITH_SHA256, QCryptographicHash::Sha256}, - {HMAC_WITH_SHA512, QCryptographicHash::Sha512}, - {HMAC_WITH_SHA512_224, QCryptographicHash::Sha512}, - {HMAC_WITH_SHA512_256, QCryptographicHash::Sha512}, - {HMAC_WITH_SHA384, QCryptographicHash::Sha384} - }; - - // Values from their respective sections here: https://tools.ietf.org/html/rfc8018#appendix-B.2 - static const QMap cipherKeyLengthMap { - {QSslKeyPrivate::Cipher::DesCbc, 8}, - {QSslKeyPrivate::Cipher::DesEde3Cbc, 24}, - // @note: variable key-length (https://tools.ietf.org/html/rfc8018#appendix-B.2.3) - {QSslKeyPrivate::Cipher::Rc2Cbc, 4} - // @todo: AES(, rc5?) - }; - - const QList keyDerivationContainer = element[0].toList(); - if (keyDerivationContainer.size() != 2 - || keyDerivationContainer[0].type() != QAsn1Element::ObjectIdentifierType - || keyDerivationContainer[1].type() != QAsn1Element::SequenceType) { - return {}; - } - - const QByteArray keyDerivationAlgorithm = keyDerivationContainer[0].toObjectId(); - const auto keyDerivationParams = keyDerivationContainer[1].toList(); - - const auto encryptionAlgorithmContainer = element[1].toList(); - if (encryptionAlgorithmContainer.size() != 2 - || encryptionAlgorithmContainer[0].type() != QAsn1Element::ObjectIdentifierType) { - return {}; - } - - auto iterator = oidCipherMap.constFind(encryptionAlgorithmContainer[0].toObjectId()); - if (iterator == oidCipherMap.cend()) { - qWarning() - << "QSslKey: Unsupported encryption cipher OID:" << encryptionAlgorithmContainer[0].toObjectId() - << "\nFile a bug report to Qt (include the line above)."; - return {}; - } - - QSslKeyPrivate::Cipher cipher = *iterator; - QByteArray key; - QByteArray iv; - switch (cipher) { - case QSslKeyPrivate::Cipher::DesCbc: - case QSslKeyPrivate::Cipher::DesEde3Cbc: - // https://tools.ietf.org/html/rfc8018#appendix-B.2.1 (DES-CBC-PAD) - // https://tools.ietf.org/html/rfc8018#appendix-B.2.2 (DES-EDE3-CBC-PAD) - // @todo https://tools.ietf.org/html/rfc8018#appendix-B.2.5 (AES-CBC-PAD) - /*** Scheme: *** - * Octet string (IV) - */ - if (encryptionAlgorithmContainer[1].type() != QAsn1Element::OctetStringType) - return {}; - - // @note: All AES identifiers should be able to use this branch!! - iv = encryptionAlgorithmContainer[1].value(); - - if (iv.size() != 8) // @note: AES needs 16 bytes - return {}; - break; - case QSslKeyPrivate::Cipher::Rc2Cbc: { - // https://tools.ietf.org/html/rfc8018#appendix-B.2.3 - /*** Scheme: *** - * Sequence (rc2 parameters) - * Integer (rc2 parameter version) - * Octet string (IV) - */ - if (encryptionAlgorithmContainer[1].type() != QAsn1Element::SequenceType) - return {}; - const auto rc2ParametersContainer = encryptionAlgorithmContainer[1].toList(); - if ((rc2ParametersContainer.size() != 1 && rc2ParametersContainer.size() != 2) - || rc2ParametersContainer.back().type() != QAsn1Element::OctetStringType) { - return {}; - } - iv = rc2ParametersContainer.back().value(); - if (iv.size() != 8) - return {}; - break; - } // @todo(?): case (RC5 , AES) - case QSslKeyPrivate::Cipher::Aes128Cbc: - case QSslKeyPrivate::Cipher::Aes192Cbc: - case QSslKeyPrivate::Cipher::Aes256Cbc: - Q_UNREACHABLE(); - } - - if (Q_LIKELY(keyDerivationAlgorithm == PKCS5_PBKDF2_ENCRYPTION_OID)) { - // Definition: https://tools.ietf.org/html/rfc8018#appendix-A.2 - QByteArray salt; - if (keyDerivationParams[0].type() == QAsn1Element::OctetStringType) { - salt = keyDerivationParams[0].value(); - } else if (keyDerivationParams[0].type() == QAsn1Element::ObjectIdentifierType) { - Q_UNIMPLEMENTED(); - /* See paragraph from https://tools.ietf.org/html/rfc8018#appendix-A.2 - which ends with: "such facilities are deferred to a future version of PKCS #5" - */ - return {}; - } else { - return {}; - } - - // Iterations needed to derive the key - int iterationCount = keyDerivationParams[1].toInteger(); - // Optional integer - int keyLength = -1; - int vectorPos = 2; - if (keyDerivationParams.size() > vectorPos - && keyDerivationParams[vectorPos].type() == QAsn1Element::IntegerType) { - keyLength = keyDerivationParams[vectorPos].toInteger(nullptr); - ++vectorPos; - } else { - keyLength = cipherKeyLengthMap[cipher]; - } - - // Optional algorithm identifier (default: HMAC-SHA-1) - QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; - if (keyDerivationParams.size() > vectorPos - && keyDerivationParams[vectorPos].type() == QAsn1Element::SequenceType) { - const auto hashAlgorithmContainer = keyDerivationParams[vectorPos].toList(); - hashAlgorithm = pbes2OidHashFunctionMap[hashAlgorithmContainer.front().toObjectId()]; - Q_ASSERT(hashAlgorithmContainer[1].type() == QAsn1Element::NullType); - ++vectorPos; - } - Q_ASSERT(keyDerivationParams.size() == vectorPos); - - key = QPasswordDigestor::deriveKeyPbkdf2(hashAlgorithm, passPhrase, salt, iterationCount, keyLength); - } else { - qWarning() - << "QSslKey: Unsupported key derivation algorithm OID:" << keyDerivationAlgorithm - << "\nFile a bugreport to Qt (include the line above)."; - return {}; - } - return {cipher, key, iv}; -} - -// Maps OIDs to the hash function it specifies -const QMap pbes1OidHashFunctionMap { -#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 - // PKCS5 - //{PKCS5_MD2_DES_CBC_OID, QCryptographicHash::Md2}, No MD2 - //{PKCS5_MD2_RC2_CBC_OID, QCryptographicHash::Md2}, - {PKCS5_MD5_DES_CBC_OID, QCryptographicHash::Md5}, - {PKCS5_MD5_RC2_CBC_OID, QCryptographicHash::Md5}, -#endif - {PKCS5_SHA1_DES_CBC_OID, QCryptographicHash::Sha1}, - {PKCS5_SHA1_RC2_CBC_OID, QCryptographicHash::Sha1}, - // PKCS12 (unimplemented) - // {PKCS12_SHA1_RC4_128_OID, QCryptographicHash::Sha1}, // No RC4 - // {PKCS12_SHA1_RC4_40_OID, QCryptographicHash::Sha1}, - // @todo: lacking support. @note: there might be code to do this inside qsslsocket_mac... - // further note that more work may be required for the 3DES variations listed to be available. - // {PKCS12_SHA1_3KEY_3DES_CBC_OID, QCryptographicHash::Sha1}, - // {PKCS12_SHA1_2KEY_3DES_CBC_OID, QCryptographicHash::Sha1}, - // {PKCS12_SHA1_RC2_128_CBC_OID, QCryptographicHash::Sha1}, - // {PKCS12_SHA1_RC2_40_CBC_OID, QCryptographicHash::Sha1} -}; - -EncryptionData readPbes1(const QList &element, const QByteArray &encryptionScheme, - const QByteArray &passPhrase) -{ - // RFC 8018: https://tools.ietf.org/html/rfc8018#section-6.1 - // Steps refer to this section: https://tools.ietf.org/html/rfc8018#section-6.1.2 - /*** Scheme: *** - * Sequence (PBE Parameter) - * Octet string (salt) - * Integer (iteration counter) - */ - // Step 1 - if (element.size() != 2 - || element[0].type() != QAsn1Element::ElementType::OctetStringType - || element[1].type() != QAsn1Element::ElementType::IntegerType) { - return {}; - } - QByteArray salt = element[0].value(); - if (salt.size() != 8) - return {}; - - int iterationCount = element[1].toInteger(); - if (iterationCount < 0) - return {}; - - // Step 2 - auto iterator = pbes1OidHashFunctionMap.constFind(encryptionScheme); - if (iterator == pbes1OidHashFunctionMap.cend()) { - // Qt was compiled with ONLY_SHA1 (or it's MD2) - return {}; - } - QCryptographicHash::Algorithm hashAlgorithm = *iterator; - QByteArray key = QPasswordDigestor::deriveKeyPbkdf1(hashAlgorithm, passPhrase, salt, iterationCount, 16); - if (key.size() != 16) - return {}; - - // Step 3 - QByteArray iv = key.right(8); // last 8 bytes are used as IV - key.truncate(8); // first 8 bytes are used for the key - - QSslKeyPrivate::Cipher cipher = oidCipherMap[encryptionScheme]; - // Steps 4-6 are done after returning - return {cipher, key, iv}; -} - -int curveBits(const QByteArray &oid) -{ - const int length = oidLengthMap->value(oid); - return length ? length : -1; -} - -int numberOfBits(const QByteArray &modulus) -{ - int bits = modulus.size() * 8; - for (int i = 0; i < modulus.size(); ++i) { - quint8 b = modulus[i]; - bits -= 8; - if (b != 0) { - bits += bits_table[b]; - break; - } - } - return bits; -} - -QByteArray deriveAesKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, - const QByteArray &iv) -{ - // This is somewhat simplified and shortened version of what OpenSSL does. - // See, for example, EVP_BytesToKey for the "algorithm" itself and elsewhere - // in their code for what they pass as arguments to EVP_BytesToKey when - // deriving encryption keys (when reading/writing pems files with encrypted - // keys). - - Q_ASSERT(iv.size() >= 8); - - QCryptographicHash hash(QCryptographicHash::Md5); - - QByteArray data(passPhrase); - data.append(iv.data(), 8); // AKA PKCS5_SALT_LEN in OpenSSL. - - hash.addData(data); - - if (cipher == Cipher::Aes128Cbc) - return hash.result(); - - QByteArray key(hash.result()); - hash.reset(); - hash.addData(key); - hash.addData(data); - - if (cipher == Cipher::Aes192Cbc) - return key.append(hash.result().constData(), 8); - - return key.append(hash.result()); -} - -QByteArray deriveKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, - const QByteArray &iv) -{ - QByteArray key; - QCryptographicHash hash(QCryptographicHash::Md5); - hash.addData(passPhrase); - hash.addData(iv); - switch (cipher) { - case Cipher::DesCbc: - key = hash.result().left(8); - break; - case Cipher::DesEde3Cbc: - key = hash.result(); - hash.reset(); - hash.addData(key); - hash.addData(passPhrase); - hash.addData(iv); - key += hash.result().left(8); - break; - case Cipher::Rc2Cbc: - key = hash.result(); - break; - case Cipher::Aes128Cbc: - case Cipher::Aes192Cbc: - case Cipher::Aes256Cbc: - return deriveAesKey(cipher, passPhrase, iv); - } - return key; -} - -int extractPkcs8KeyLength(const QList &items, TlsKey *that) -{ - Q_ASSERT(items.size() == 3); - Q_ASSERT(that); - - int keyLength = -1; - - auto getName = [](QSsl::KeyAlgorithm algorithm) { - switch (algorithm){ - case QSsl::Rsa: return "RSA"; - case QSsl::Dsa: return "DSA"; - case QSsl::Dh: return "DH"; - case QSsl::Ec: return "EC"; - case QSsl::Opaque: return "Opaque"; - } - Q_UNREACHABLE(); - }; - - const auto pkcs8Info = items[1].toList(); - if (pkcs8Info.size() != 2 || pkcs8Info[0].type() != QAsn1Element::ObjectIdentifierType) - return -1; - const QByteArray value = pkcs8Info[0].toObjectId(); - if (value == RSA_ENCRYPTION_OID) { - if (Q_UNLIKELY(that->algorithm() != QSsl::Rsa)) { - // We could change the 'algorithm' of QSslKey here and continue loading, but - // this is not supported in the openssl back-end, so we'll fail here and give - // the user some feedback. - qWarning() << "QSslKey: Found RSA key when asked to use" << getName(that->algorithm()) - << "\nLoading will fail."; - return -1; - } - // Luckily it contains the 'normal' RSA-key format inside, so we can just recurse - // and read the key's info. - that->decodeDer(that->type(), that->algorithm(), items[2].value(), {}, true); - // The real info has been filled out in the call above, so return as if it was invalid - // to avoid overwriting the data. - return -1; - } else if (value == EC_ENCRYPTION_OID) { - if (Q_UNLIKELY(that->algorithm() != QSsl::Ec)) { - // As above for RSA. - qWarning() << "QSslKey: Found EC key when asked to use" << getName(that->algorithm()) - << "\nLoading will fail."; - return -1; - } - // I don't know where this is documented, but the elliptic-curve identifier has been - // moved into the "pkcs#8 wrapper", which is what we're interested in. - if (pkcs8Info[1].type() != QAsn1Element::ObjectIdentifierType) - return -1; - keyLength = curveBits(pkcs8Info[1].toObjectId()); - } else if (value == DSA_ENCRYPTION_OID) { - if (Q_UNLIKELY(that->algorithm() != QSsl::Dsa)) { - // As above for RSA. - qWarning() << "QSslKey: Found DSA when asked to use" << getName(that->algorithm()) - << "\nLoading will fail."; - return -1; - } - // DSA's structure is documented here: - // https://www.cryptsoft.com/pkcs11doc/STANDARD/v201-95.pdf in section 11.9. - if (pkcs8Info[1].type() != QAsn1Element::SequenceType) - return -1; - const auto dsaInfo = pkcs8Info[1].toList(); - if (dsaInfo.size() != 3 || dsaInfo[0].type() != QAsn1Element::IntegerType) - return -1; - keyLength = numberOfBits(dsaInfo[0].value()); - } else if (value == DH_ENCRYPTION_OID) { - if (Q_UNLIKELY(that->algorithm() != QSsl::Dh)) { - // As above for RSA. - qWarning() << "QSslKey: Found DH when asked to use" << getName(that->algorithm()) - << "\nLoading will fail."; - return -1; - } - // DH's structure is documented here: - // https://www.cryptsoft.com/pkcs11doc/STANDARD/v201-95.pdf in section 11.9. - if (pkcs8Info[1].type() != QAsn1Element::SequenceType) - return -1; - const auto dhInfo = pkcs8Info[1].toList(); - if (dhInfo.size() < 2 || dhInfo.size() > 3 || dhInfo[0].type() != QAsn1Element::IntegerType) - return -1; - keyLength = numberOfBits(dhInfo[0].value()); - } else { - // in case of unexpected formats: - qWarning() << "QSslKey: Unsupported PKCS#8 key algorithm:" << value - << "\nFile a bugreport to Qt (include the line above)."; - return -1; - } - - return keyLength; -} - -} // Unnamed namespace - -void TlsKeyGeneric::decodeDer(QSsl::KeyType type, QSsl::KeyAlgorithm algorithm, const QByteArray &der, - const QByteArray &passPhrase, bool deepClear) -{ - keyType = type; - keyAlgorithm = algorithm; - - clear(deepClear); - - if (der.isEmpty()) - return; - // decryptPkcs8 decrypts if necessary or returns 'der' unaltered - QByteArray decryptedDer = decryptPkcs8(der, passPhrase); - - QAsn1Element elem; - if (!elem.read(decryptedDer) || elem.type() != QAsn1Element::SequenceType) - return; - - if (type == QSsl::PublicKey) { - // key info - QDataStream keyStream(elem.value()); - if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) - return; - const auto infoItems = elem.toList(); - if (infoItems.size() < 2 || infoItems[0].type() != QAsn1Element::ObjectIdentifierType) - return; - if (algorithm == QSsl::Rsa) { - if (infoItems[0].toObjectId() != RSA_ENCRYPTION_OID) - return; - // key data - if (!elem.read(keyStream) || elem.type() != QAsn1Element::BitStringType || elem.value().isEmpty()) - return; - if (!elem.read(elem.value().mid(1)) || elem.type() != QAsn1Element::SequenceType) - return; - if (!elem.read(elem.value()) || elem.type() != QAsn1Element::IntegerType) - return; - keyLength = numberOfBits(elem.value()); - } else if (algorithm == QSsl::Dsa) { - if (infoItems[0].toObjectId() != DSA_ENCRYPTION_OID) - return; - if (infoItems[1].type() != QAsn1Element::SequenceType) - return; - // key params - const auto params = infoItems[1].toList(); - if (params.isEmpty() || params[0].type() != QAsn1Element::IntegerType) - return; - keyLength = numberOfBits(params[0].value()); - } else if (algorithm == QSsl::Dh) { - if (infoItems[0].toObjectId() != DH_ENCRYPTION_OID) - return; - if (infoItems[1].type() != QAsn1Element::SequenceType) - return; - // key params - const auto params = infoItems[1].toList(); - if (params.isEmpty() || params[0].type() != QAsn1Element::IntegerType) - return; - keyLength = numberOfBits(params[0].value()); - } else if (algorithm == QSsl::Ec) { - if (infoItems[0].toObjectId() != EC_ENCRYPTION_OID) - return; - if (infoItems[1].type() != QAsn1Element::ObjectIdentifierType) - return; - keyLength = curveBits(infoItems[1].toObjectId()); - } - - } else { - const auto items = elem.toList(); - if (items.isEmpty()) - return; - - // version - if (items[0].type() != QAsn1Element::IntegerType) - return; - const QByteArray versionHex = items[0].value().toHex(); - - if (items.size() == 3 && items[1].type() == QAsn1Element::SequenceType - && items[2].type() == QAsn1Element::OctetStringType) { - if (versionHex != "00" && versionHex != "01") - return; - int pkcs8KeyLength = extractPkcs8KeyLength(items, this); - if (pkcs8KeyLength == -1) - return; - pkcs8 = true; - keyLength = pkcs8KeyLength; - } else if (algorithm == QSsl::Rsa) { - if (versionHex != "00") - return; - if (items.size() != 9 || items[1].type() != QAsn1Element::IntegerType) - return; - keyLength = numberOfBits(items[1].value()); - } else if (algorithm == QSsl::Dsa) { - if (versionHex != "00") - return; - if (items.size() != 6 || items[1].type() != QAsn1Element::IntegerType) - return; - keyLength = numberOfBits(items[1].value()); - } else if (algorithm == QSsl::Dh) { - if (versionHex != "00") - return; - if (items.size() < 5 || items.size() > 6 || items[1].type() != QAsn1Element::IntegerType) - return; - keyLength = numberOfBits(items[1].value()); - } else if (algorithm == QSsl::Ec) { - if (versionHex != "01") - return; - if (items.size() != 4 - || items[1].type() != QAsn1Element::OctetStringType - || items[2].type() != QAsn1Element::Context0Type - || items[3].type() != QAsn1Element::Context1Type) - return; - QAsn1Element oidElem; - if (!oidElem.read(items[2].value()) - || oidElem.type() != QAsn1Element::ObjectIdentifierType) - return; - keyLength = curveBits(oidElem.toObjectId()); - } - } - - derData = decryptedDer; - keyIsNull = false; -} - -void TlsKeyGeneric::decodePem(QSsl::KeyType type, QSsl::KeyAlgorithm algorithm, const QByteArray &pem, - const QByteArray &passPhrase, bool deepClear) -{ - keyType = type; - keyAlgorithm = algorithm; - - QMap headers; - QByteArray data = derFromPem(pem, &headers); - - if (headers.value("Proc-Type") == "4,ENCRYPTED") { - const QList dekInfo = headers.value("DEK-Info").split(','); - if (dekInfo.size() != 2) { - clear(deepClear); - return; - } - - QSslKeyPrivate::Cipher cipher; - if (dekInfo.first() == "DES-CBC") { - cipher = Cipher::DesCbc; - } else if (dekInfo.first() == "DES-EDE3-CBC") { - cipher = Cipher::DesEde3Cbc; - } else if (dekInfo.first() == "RC2-CBC") { - cipher = Cipher::Rc2Cbc; - } else if (dekInfo.first() == "AES-128-CBC") { - cipher = Cipher::Aes128Cbc; - } else if (dekInfo.first() == "AES-192-CBC") { - cipher = Cipher::Aes192Cbc; - } else if (dekInfo.first() == "AES-256-CBC") { - cipher = Cipher::Aes256Cbc; - } else { - clear(deepClear); - return; - } - - const QByteArray iv = QByteArray::fromHex(dekInfo.last()); - const QByteArray key = deriveKey(cipher, passPhrase, iv); - data = decrypt(cipher, data, key, iv); - } - - decodeDer(keyType, keyAlgorithm, data, passPhrase, deepClear); -} - -QByteArray TlsKeyGeneric::toPem(const QByteArray &passPhrase) const -{ - QByteArray data; - QMap headers; - - if (type() == QSsl::PrivateKey && !passPhrase.isEmpty()) { - // ### use a cryptographically secure random number generator - quint64 random = QRandomGenerator::system()->generate64(); - QByteArray iv = QByteArray::fromRawData(reinterpret_cast(&random), sizeof(random)); - - auto cipher = Cipher::DesEde3Cbc; - const QByteArray key = deriveKey(cipher, passPhrase, iv); - data = encrypt(cipher, derData, key, iv); - - headers.insert("Proc-Type", "4,ENCRYPTED"); - headers.insert("DEK-Info", "DES-EDE3-CBC," + iv.toHex()); - } else { - data = derData; - } - - return pemFromDer(data, headers); -} - -QByteArray TlsKeyGeneric::derFromPem(const QByteArray &pem, QMap *headers) const -{ - if (derData.size()) - return derData; - - QByteArray header = pemHeader(); - QByteArray footer = pemFooter(); - - QByteArray der(pem); - - int headerIndex = der.indexOf(header); - int footerIndex = der.indexOf(footer, headerIndex + header.length()); - if (type() != QSsl::PublicKey) { - if (headerIndex == -1 || footerIndex == -1) { - header = pkcs8Header(true); - footer = pkcs8Footer(true); - headerIndex = der.indexOf(header); - footerIndex = der.indexOf(footer, headerIndex + header.length()); - } - if (headerIndex == -1 || footerIndex == -1) { - header = pkcs8Header(false); - footer = pkcs8Footer(false); - headerIndex = der.indexOf(header); - footerIndex = der.indexOf(footer, headerIndex + header.length()); - } - } - if (headerIndex == -1 || footerIndex == -1) - return QByteArray(); - - der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); - - if (der.contains("Proc-Type:")) { - // taken from QHttpNetworkReplyPrivate::parseHeader - int i = 0; - while (i < der.count()) { - int j = der.indexOf(':', i); // field-name - if (j == -1) - break; - const QByteArray field = der.mid(i, j - i).trimmed(); - j++; - // any number of LWS is allowed before and after the value - QByteArray value; - do { - i = der.indexOf('\n', j); - if (i == -1) - break; - if (!value.isEmpty()) - value += ' '; - // check if we have CRLF or only LF - bool hasCR = (i && der[i-1] == '\r'); - int length = i -(hasCR ? 1: 0) - j; - value += der.mid(j, length).trimmed(); - j = ++i; - } while (i < der.count() && (der.at(i) == ' ' || der.at(i) == '\t')); - if (i == -1) - break; // something is wrong - - headers->insert(field, value); - } - der = der.mid(i); - } - - return QByteArray::fromBase64(der); // ignores newlines -} - -void TlsKeyGeneric::fromHandle(Qt::HANDLE handle, KeyType expectedType) -{ - opaque = handle; - keyType = expectedType; -} - -void TlsKeyGeneric::clear(bool deep) -{ - keyIsNull = true; - if (deep) - std::memset(derData.data(), 0, derData.size()); - derData.clear(); - keyLength = -1; -} - -QByteArray TlsKeyGeneric::decryptPkcs8(const QByteArray &encrypted, const QByteArray &passPhrase) -{ - // RFC 5958: https://tools.ietf.org/html/rfc5958 - /*** Scheme: *** - * Sequence - * Sequence - * Object Identifier (encryption scheme (currently PBES2, PBES1, @todo PKCS12)) - * Sequence (scheme parameters) - * Octet String (the encrypted data) - */ - QAsn1Element elem; - if (!elem.read(encrypted) || elem.type() != QAsn1Element::SequenceType) - return encrypted; - - const auto items = elem.toList(); - if (items.size() != 2 - || items[0].type() != QAsn1Element::SequenceType - || items[1].type() != QAsn1Element::OctetStringType) { - return encrypted; - } - - const auto encryptionSchemeContainer = items[0].toList(); - - if (encryptionSchemeContainer.size() != 2 - || encryptionSchemeContainer[0].type() != QAsn1Element::ObjectIdentifierType - || encryptionSchemeContainer[1].type() != QAsn1Element::SequenceType) { - return encrypted; - } - - const QByteArray encryptionScheme = encryptionSchemeContainer[0].toObjectId(); - const auto schemeParameterContainer = encryptionSchemeContainer[1].toList(); - - if (schemeParameterContainer.size() != 2 - && schemeParameterContainer[0].type() != QAsn1Element::SequenceType - && schemeParameterContainer[1].type() != QAsn1Element::SequenceType) { - return encrypted; - } - - EncryptionData data; - if (encryptionScheme == PKCS5_PBES2_ENCRYPTION_OID) { - data = readPbes2(schemeParameterContainer, passPhrase); - } else if (pbes1OidHashFunctionMap.contains(encryptionScheme)) { - data = readPbes1(schemeParameterContainer, encryptionScheme, passPhrase); - } else if (encryptionScheme.startsWith(PKCS12_OID)) { - Q_UNIMPLEMENTED(); // this isn't some 'unknown', I know these aren't implemented - return encrypted; - } else { - qWarning() - << "QSslKey: Unsupported encryption scheme OID:" << encryptionScheme - << "\nFile a bugreport to Qt (include the line above)."; - return encrypted; - } - - if (!data.initialized) { - // something went wrong, return - return encrypted; - } - - QByteArray decryptedKey = decrypt(data.cipher, items[1].value(), data.key, data.iv); - // The data is still wrapped in a octet string, so let's unwrap it - QAsn1Element decryptedKeyElement(QAsn1Element::ElementType::OctetStringType, decryptedKey); - return decryptedKeyElement.value(); -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE diff --git a/src/network/ssl/qtlskey_generic_p.h b/src/network/ssl/qtlskey_generic_p.h deleted file mode 100644 index 4c409f5f51..0000000000 --- a/src/network/ssl/qtlskey_generic_p.h +++ /dev/null @@ -1,117 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLSKEY_GENERIC_P_H -#define QTLSKEY_GENERIC_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -// This class is what previously was known as qsslkey_qt: -// it implements most of functionality needed by QSslKey -// not relying on any TLS implementation. It's used by -// our SecureTransport and Schannel backends. -class TlsKeyGeneric : public TlsKeyBase -{ -public: - using TlsKeyBase::TlsKeyBase; - - void decodeDer(KeyType type, KeyAlgorithm algorithm, const QByteArray &der, - const QByteArray &passPhrase, bool deepClear) override; - void decodePem(KeyType type, KeyAlgorithm algorithm, const QByteArray &pem, - const QByteArray &passPhrase, bool deepClear) override; - - QByteArray toPem(const QByteArray &passPhrase) const override; - - QByteArray derFromPem(const QByteArray &pem, QMap *headers) const override; - - void fromHandle(Qt::HANDLE opaque, KeyType expectedType) override; - - void clear(bool deep) override; - - Qt::HANDLE handle() const override - { - return Qt::HANDLE(opaque); - } - - int length() const override - { - return keyLength; - } - - bool isPkcs8() const override - { - return pkcs8; - } - -private: - QByteArray decryptPkcs8(const QByteArray &encrypted, const QByteArray &passPhrase); - - bool pkcs8 = false; - Qt::HANDLE opaque = nullptr; - QByteArray derData; - int keyLength = -1; -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QTLSKEY_GENERIC_P_H diff --git a/src/network/ssl/qtlskey_openssl.cpp b/src/network/ssl/qtlskey_openssl.cpp deleted file mode 100644 index 6d75dfd5b4..0000000000 --- a/src/network/ssl/qtlskey_openssl.cpp +++ /dev/null @@ -1,509 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qsslsocket_openssl_symbols_p.h" -#include "qtlskey_openssl_p.h" -#include "qsslsocket.h" -#include "qsslkey_p.h" - -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -void TlsKeyOpenSSL::decodeDer(QSsl::KeyType type, QSsl::KeyAlgorithm algorithm, const QByteArray &der, - const QByteArray &passPhrase, bool deepClear) -{ - if (der.isEmpty()) - return; - - keyType = type; - keyAlgorithm = algorithm; - - QMap headers; - const auto pem = pemFromDer(der, headers); - - decodePem(type, algorithm, pem, passPhrase, deepClear); -} - -void TlsKeyOpenSSL::decodePem(KeyType type, KeyAlgorithm algorithm, const QByteArray &pem, - const QByteArray &passPhrase, bool deepClear) -{ - if (pem.isEmpty()) - return; - - keyType = type; - keyAlgorithm = algorithm; - - clear(deepClear); - - BIO *bio = q_BIO_new_mem_buf(const_cast(pem.data()), pem.size()); - if (!bio) - return; - - const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); - - void *phrase = const_cast(passPhrase.data()); - - if (algorithm == QSsl::Rsa) { - RSA *result = (type == QSsl::PublicKey) - ? q_PEM_read_bio_RSA_PUBKEY(bio, &rsa, nullptr, phrase) - : q_PEM_read_bio_RSAPrivateKey(bio, &rsa, nullptr, phrase); - if (rsa && rsa == result) - keyIsNull = false; - } else if (algorithm == QSsl::Dsa) { - DSA *result = (type == QSsl::PublicKey) - ? q_PEM_read_bio_DSA_PUBKEY(bio, &dsa, nullptr, phrase) - : q_PEM_read_bio_DSAPrivateKey(bio, &dsa, nullptr, phrase); - if (dsa && dsa == result) - keyIsNull = false; - } else if (algorithm == QSsl::Dh) { - EVP_PKEY *result = (type == QSsl::PublicKey) - ? q_PEM_read_bio_PUBKEY(bio, nullptr, nullptr, phrase) - : q_PEM_read_bio_PrivateKey(bio, nullptr, nullptr, phrase); - if (result) - dh = q_EVP_PKEY_get1_DH(result); - if (dh) - keyIsNull = false; - q_EVP_PKEY_free(result); -#ifndef OPENSSL_NO_EC - } else if (algorithm == QSsl::Ec) { - EC_KEY *result = (type == QSsl::PublicKey) - ? q_PEM_read_bio_EC_PUBKEY(bio, &ec, nullptr, phrase) - : q_PEM_read_bio_ECPrivateKey(bio, &ec, nullptr, phrase); - if (ec && ec == result) - keyIsNull = false; -#endif - } -} - -QByteArray TlsKeyOpenSSL::derFromPem(const QByteArray &pem, QMap *headers) const -{ - QByteArray header = pemHeader(); - QByteArray footer = pemFooter(); - - QByteArray der(pem); - - int headerIndex = der.indexOf(header); - int footerIndex = der.indexOf(footer, headerIndex + header.length()); - if (type() != QSsl::PublicKey) { - if (headerIndex == -1 || footerIndex == -1) { - header = pkcs8Header(true); - footer = pkcs8Footer(true); - headerIndex = der.indexOf(header); - footerIndex = der.indexOf(footer, headerIndex + header.length()); - } - if (headerIndex == -1 || footerIndex == -1) { - header = pkcs8Header(false); - footer = pkcs8Footer(false); - headerIndex = der.indexOf(header); - footerIndex = der.indexOf(footer, headerIndex + header.length()); - } - } - if (headerIndex == -1 || footerIndex == -1) - return QByteArray(); - - der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); - - if (der.contains("Proc-Type:")) { - // taken from QHttpNetworkReplyPrivate::parseHeader - int i = 0; - while (i < der.count()) { - int j = der.indexOf(':', i); // field-name - if (j == -1) - break; - const QByteArray field = der.mid(i, j - i).trimmed(); - j++; - // any number of LWS is allowed before and after the value - QByteArray value; - do { - i = der.indexOf('\n', j); - if (i == -1) - break; - if (!value.isEmpty()) - value += ' '; - // check if we have CRLF or only LF - bool hasCR = (i && der[i-1] == '\r'); - int length = i -(hasCR ? 1: 0) - j; - value += der.mid(j, length).trimmed(); - j = ++i; - } while (i < der.count() && (der.at(i) == ' ' || der.at(i) == '\t')); - if (i == -1) - break; // something is wrong - - headers->insert(field, value); - } - der = der.mid(i); - } - - return QByteArray::fromBase64(der); // ignores newlines -} - -void TlsKeyOpenSSL::clear(bool deep) -{ - keyIsNull = true; - - if (algorithm() == QSsl::Rsa && rsa) { - if (deep) - q_RSA_free(rsa); - rsa = nullptr; - } - if (algorithm() == QSsl::Dsa && dsa) { - if (deep) - q_DSA_free(dsa); - dsa = nullptr; - } - if (algorithm() == QSsl::Dh && dh) { - if (deep) - q_DH_free(dh); - dh = nullptr; - } -#ifndef OPENSSL_NO_EC - if (algorithm() == QSsl::Ec && ec) { - if (deep) - q_EC_KEY_free(ec); - ec = nullptr; - } -#endif - if (algorithm() == QSsl::Opaque && opaque) { - if (deep) - q_EVP_PKEY_free(opaque); - opaque = nullptr; - } -} - -Qt::HANDLE TlsKeyOpenSSL::handle() const -{ - switch (keyAlgorithm) { - case QSsl::Opaque: - return Qt::HANDLE(opaque); - case QSsl::Rsa: - return Qt::HANDLE(rsa); - case QSsl::Dsa: - return Qt::HANDLE(dsa); - case QSsl::Dh: - return Qt::HANDLE(dh); -#ifndef OPENSSL_NO_EC - case QSsl::Ec: - return Qt::HANDLE(ec); -#endif - default: - return Qt::HANDLE(nullptr); - } -} - -int TlsKeyOpenSSL::length() const -{ - if (isNull() || algorithm() == QSsl::Opaque) - return -1; - - switch (algorithm()) { - case QSsl::Rsa: - return q_RSA_bits(rsa); - case QSsl::Dsa: - return q_DSA_bits(dsa); - case QSsl::Dh: - return q_DH_bits(dh); -#ifndef OPENSSL_NO_EC - case QSsl::Ec: - return q_EC_GROUP_get_degree(q_EC_KEY_get0_group(ec)); -#endif - default: - return -1; - } -} - -QByteArray TlsKeyOpenSSL::toPem(const QByteArray &passPhrase) const -{ - if (!QSslSocket::supportsSsl() || isNull() || algorithm() == QSsl::Opaque) - return {}; - - const EVP_CIPHER *cipher = nullptr; - if (type() == QSsl::PrivateKey && !passPhrase.isEmpty()) { -#ifndef OPENSSL_NO_DES - cipher = q_EVP_des_ede3_cbc(); -#else - return {}; -#endif - } - - BIO *bio = q_BIO_new(q_BIO_s_mem()); - if (!bio) - return {}; - - const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); - - bool fail = false; - - if (algorithm() == QSsl::Rsa) { - if (type() == QSsl::PublicKey) { - if (!q_PEM_write_bio_RSA_PUBKEY(bio, rsa)) - fail = true; - } else { - if (!q_PEM_write_bio_RSAPrivateKey( - bio, rsa, cipher, (uchar *)passPhrase.data(), - passPhrase.size(), nullptr, nullptr)) { - fail = true; - } - } - } else if (algorithm() == QSsl::Dsa) { - if (type() == QSsl::PublicKey) { - if (!q_PEM_write_bio_DSA_PUBKEY(bio, dsa)) - fail = true; - } else { - if (!q_PEM_write_bio_DSAPrivateKey( - bio, dsa, cipher, (uchar *)passPhrase.data(), - passPhrase.size(), nullptr, nullptr)) { - fail = true; - } - } - } else if (algorithm() == QSsl::Dh) { - EVP_PKEY *result = q_EVP_PKEY_new(); - if (!result || !q_EVP_PKEY_set1_DH(result, dh)) { - fail = true; - } else if (type() == QSsl::PublicKey) { - if (!q_PEM_write_bio_PUBKEY(bio, result)) - fail = true; - } else if (!q_PEM_write_bio_PrivateKey( - bio, result, cipher, (uchar *)passPhrase.data(), - passPhrase.size(), nullptr, nullptr)) { - fail = true; - } - q_EVP_PKEY_free(result); -#ifndef OPENSSL_NO_EC - } else if (algorithm() == QSsl::Ec) { - if (type() == QSsl::PublicKey) { - if (!q_PEM_write_bio_EC_PUBKEY(bio, ec)) - fail = true; - } else { - if (!q_PEM_write_bio_ECPrivateKey( - bio, ec, cipher, (uchar *)passPhrase.data(), - passPhrase.size(), nullptr, nullptr)) { - fail = true; - } - } -#endif - } else { - fail = true; - } - - QByteArray pem; - if (!fail) { - char *data = nullptr; - const long size = q_BIO_get_mem_data(bio, &data); - if (size > 0 && data) - pem = QByteArray(data, size); - } - - return pem; -} - -void TlsKeyOpenSSL::fromHandle(Qt::HANDLE handle, QSsl::KeyType expectedType) -{ - EVP_PKEY *evpKey = reinterpret_cast(handle); - if (!evpKey || !fromEVP_PKEY(evpKey)) { - opaque = evpKey; - keyAlgorithm = QSsl::Opaque; - } else { - q_EVP_PKEY_free(evpKey); - } - - keyType = expectedType; - keyIsNull = !opaque; -} - -bool TlsKeyOpenSSL::fromEVP_PKEY(EVP_PKEY *pkey) -{ - if (!pkey) - return false; - - switch (q_EVP_PKEY_type(q_EVP_PKEY_base_id(pkey))) { - case EVP_PKEY_RSA: - keyIsNull = false; - keyAlgorithm = QSsl::Rsa; - keyType = QSsl::PrivateKey; - rsa = q_EVP_PKEY_get1_RSA(pkey); - - return true; - case EVP_PKEY_DSA: - keyIsNull = false; - keyAlgorithm = QSsl::Dsa; - keyType = QSsl::PrivateKey; - dsa = q_EVP_PKEY_get1_DSA(pkey); - - return true; - case EVP_PKEY_DH: - keyIsNull = false; - keyAlgorithm = QSsl::Dh; - keyType = QSsl::PrivateKey; - dh = q_EVP_PKEY_get1_DH(pkey); - return true; -#ifndef OPENSSL_NO_EC - case EVP_PKEY_EC: - keyIsNull = false; - keyAlgorithm = QSsl::Ec; - keyType = QSsl::PrivateKey; - ec = q_EVP_PKEY_get1_EC_KEY(pkey); - - return true; -#endif - default:; - // Unknown key type. This could be handled as opaque, but then - // we'd eventually leak memory since we wouldn't be able to free - // the underlying EVP_PKEY structure. For now, we won't support - // this. - } - - return false; -} - -QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, const QByteArray &data, - const QByteArray &key, const QByteArray &iv, bool enc) -{ - const EVP_CIPHER *type = nullptr; - int i = 0, len = 0; - - switch (cipher) { - case Cipher::DesCbc: -#ifndef OPENSSL_NO_DES - type = q_EVP_des_cbc(); -#endif - break; - case Cipher::DesEde3Cbc: -#ifndef OPENSSL_NO_DES - type = q_EVP_des_ede3_cbc(); -#endif - break; - case Cipher::Rc2Cbc: -#ifndef OPENSSL_NO_RC2 - type = q_EVP_rc2_cbc(); -#endif - break; - case Cipher::Aes128Cbc: - type = q_EVP_aes_128_cbc(); - break; - case Cipher::Aes192Cbc: - type = q_EVP_aes_192_cbc(); - break; - case Cipher::Aes256Cbc: - type = q_EVP_aes_256_cbc(); - break; - } - - if (type == nullptr) - return {}; - - QByteArray output; - output.resize(data.size() + EVP_MAX_BLOCK_LENGTH); - - EVP_CIPHER_CTX *ctx = q_EVP_CIPHER_CTX_new(); - q_EVP_CIPHER_CTX_reset(ctx); - q_EVP_CipherInit(ctx, type, nullptr, nullptr, enc); - q_EVP_CIPHER_CTX_set_key_length(ctx, key.size()); - if (cipher == Cipher::Rc2Cbc) - q_EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, 8 * key.size(), nullptr); - - q_EVP_CipherInit_ex(ctx, nullptr, nullptr, - reinterpret_cast(key.constData()), - reinterpret_cast(iv.constData()), - enc); - q_EVP_CipherUpdate(ctx, - reinterpret_cast(output.data()), &len, - reinterpret_cast(data.constData()), data.size()); - q_EVP_CipherFinal(ctx, - reinterpret_cast(output.data()) + len, &i); - len += i; - - q_EVP_CIPHER_CTX_reset(ctx); - q_EVP_CIPHER_CTX_free(ctx); - - return output.left(len); -} - -QByteArray TlsKeyOpenSSL::decrypt(Cipher cipher, const QByteArray &data, - const QByteArray &key, const QByteArray &iv) const -{ - return doCrypt(cipher, data, key, iv, false); -} - -QByteArray TlsKeyOpenSSL::encrypt(Cipher cipher, const QByteArray &data, - const QByteArray &key, const QByteArray &iv) const -{ - return doCrypt(cipher, data, key, iv, true); -} - -TlsKeyOpenSSL *TlsKeyOpenSSL::publicKeyFromX509(X509 *x) -{ - TlsKeyOpenSSL *tlsKey = new TlsKeyOpenSSL; - std::unique_ptr keyRaii(tlsKey); - - tlsKey->keyType = QSsl::PublicKey; - - EVP_PKEY *pkey = q_X509_get_pubkey(x); - Q_ASSERT(pkey); - const int keyType = q_EVP_PKEY_type(q_EVP_PKEY_base_id(pkey)); - - if (keyType == EVP_PKEY_RSA) { - tlsKey->rsa = q_EVP_PKEY_get1_RSA(pkey); - tlsKey->keyAlgorithm = QSsl::Rsa; - tlsKey->keyIsNull = false; - } else if (keyType == EVP_PKEY_DSA) { - tlsKey->dsa = q_EVP_PKEY_get1_DSA(pkey); - tlsKey->keyAlgorithm = QSsl::Dsa; - tlsKey->keyIsNull = false; -#ifndef OPENSSL_NO_EC - } else if (keyType == EVP_PKEY_EC) { - tlsKey->ec = q_EVP_PKEY_get1_EC_KEY(pkey); - tlsKey->keyAlgorithm = QSsl::Ec; - tlsKey->keyIsNull = false; -#endif - } else if (keyType == EVP_PKEY_DH) { - // DH unsupported (key is null) - } else { - // error? (key is null) - } - - q_EVP_PKEY_free(pkey); - return keyRaii.release(); -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE diff --git a/src/network/ssl/qtlskey_openssl_p.h b/src/network/ssl/qtlskey_openssl_p.h deleted file mode 100644 index 5ef51dfd56..0000000000 --- a/src/network/ssl/qtlskey_openssl_p.h +++ /dev/null @@ -1,126 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLSKEY_OPENSSL_H -#define QTLSKEY_OPENSSL_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include "qtlskey_base_p.h" -#include "qtlsbackend_p.h" -#include "qsslkey_p.h" - -#include - -#include -#include - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -QT_REQUIRE_CONFIG(ssl); - -namespace QTlsPrivate { - -class TlsKeyOpenSSL final : public TlsKeyBase -{ -public: - TlsKeyOpenSSL() - : opaque(nullptr) - { - clear(false); - } - ~TlsKeyOpenSSL() - { - clear(true); - } - - void decodeDer(KeyType type, KeyAlgorithm algorithm, const QByteArray &der, - const QByteArray &passPhrase, bool deepClear) override; - void decodePem(KeyType type, KeyAlgorithm algorithm, const QByteArray &pem, - const QByteArray &passPhrase, bool deepClear) override; - - QByteArray toPem(const QByteArray &passPhrase) const override; - QByteArray derFromPem(const QByteArray &pem, QMap *headers) const override; - - void fromHandle(Qt::HANDLE opaque, KeyType expectedType) override; - - void clear(bool deep) override; - Qt::HANDLE handle() const override; - int length() const override; - - QByteArray decrypt(Cipher cipher, const QByteArray &data, - const QByteArray &key, const QByteArray &iv) const override; - QByteArray encrypt(Cipher cipher, const QByteArray &data, - const QByteArray &key, const QByteArray &iv) const override; - - static TlsKeyOpenSSL *publicKeyFromX509(X509 *x); - - union { - EVP_PKEY *opaque; - RSA *rsa; - DSA *dsa; - DH *dh; -#ifndef OPENSSL_NO_EC - EC_KEY *ec; -#endif - }; - - bool fromEVP_PKEY(EVP_PKEY *pkey); -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QTLSKEY_OPENSSL_H diff --git a/src/network/ssl/qtlskey_schannel.cpp b/src/network/ssl/qtlskey_schannel.cpp deleted file mode 100644 index e788eac03a..0000000000 --- a/src/network/ssl/qtlskey_schannel.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qssl_p.h" -#include "qtlskey_schannel_p.h" -#include "qtlsbackend_p.h" -#include "qsslkey_p.h" -#include "qsslkey.h" - -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -namespace { -const wchar_t *getName(QSslKeyPrivate::Cipher cipher) -{ - switch (cipher) { - case QTlsPrivate::Cipher::DesCbc: - return BCRYPT_DES_ALGORITHM; - case QTlsPrivate::Cipher::DesEde3Cbc: - return BCRYPT_3DES_ALGORITHM; - case QTlsPrivate::Cipher::Rc2Cbc: - return BCRYPT_RC2_ALGORITHM; - case QTlsPrivate::Cipher::Aes128Cbc: - case QTlsPrivate::Cipher::Aes192Cbc: - case QTlsPrivate::Cipher::Aes256Cbc: - return BCRYPT_AES_ALGORITHM; - } - Q_UNREACHABLE(); -} - -BCRYPT_ALG_HANDLE getHandle(QSslKeyPrivate::Cipher cipher) -{ - BCRYPT_ALG_HANDLE handle; - NTSTATUS status = BCryptOpenAlgorithmProvider( - &handle, // phAlgorithm - getName(cipher), // pszAlgId - nullptr, // pszImplementation - 0 // dwFlags - ); - if (status < 0) { - qCWarning(lcTlsBackend, "Failed to open algorithm handle (%ld)!", status); - return nullptr; - } - - return handle; -} - -BCRYPT_KEY_HANDLE generateSymmetricKey(BCRYPT_ALG_HANDLE handle, - const QByteArray &key) -{ - BCRYPT_KEY_HANDLE keyHandle; - NTSTATUS status = BCryptGenerateSymmetricKey( - handle, // hAlgorithm - &keyHandle, // phKey - nullptr, // pbKeyObject (can ignore) - 0, // cbKeyObject (also ignoring) - reinterpret_cast(const_cast(key.data())), // pbSecret - ULONG(key.length()), // cbSecret - 0 // dwFlags - ); - if (status < 0) { - qCWarning(lcTlsBackend, "Failed to generate symmetric key (%ld)!", status); - return nullptr; - } - - status = BCryptSetProperty( - keyHandle, // hObject - BCRYPT_CHAINING_MODE, // pszProperty - reinterpret_cast(const_cast(BCRYPT_CHAIN_MODE_CBC)), // pbInput - ARRAYSIZE(BCRYPT_CHAIN_MODE_CBC), // cbInput - 0 // dwFlags - ); - if (status < 0) { - BCryptDestroyKey(keyHandle); - qCWarning(lcTlsBackend, "Failed to change the symmetric key's chaining mode (%ld)!", status); - return nullptr; - } - return keyHandle; -} - -QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, const QByteArray &data, const QByteArray &key, - const QByteArray &iv, bool encrypt) -{ - BCRYPT_ALG_HANDLE handle = getHandle(cipher); - if (!handle) - return {}; - auto handleDealloc = qScopeGuard([&handle]() { - BCryptCloseAlgorithmProvider(handle, 0); - }); - - BCRYPT_KEY_HANDLE keyHandle = generateSymmetricKey(handle, key); - if (!keyHandle) - return {}; - auto keyHandleDealloc = qScopeGuard([&keyHandle]() { - BCryptDestroyKey(keyHandle); - }); - - QByteArray ivCopy = iv; // This gets modified, so we take a copy - - ULONG sizeNeeded = 0; - QVarLengthArray output; - auto cryptFunction = encrypt ? BCryptEncrypt : BCryptDecrypt; - for (int i = 0; i < 2; i++) { - output.resize(int(sizeNeeded)); - auto input = reinterpret_cast(const_cast(data.data())); - // Need to call it twice because the first iteration lets us know the size needed. - NTSTATUS status = cryptFunction( - keyHandle, // hKey - input, // pbInput - ULONG(data.length()), // cbInput - nullptr, // pPaddingInfo - reinterpret_cast(ivCopy.data()), // pbIV - ULONG(ivCopy.length()), // cbIV - sizeNeeded ? output.data() : nullptr, // pbOutput - ULONG(output.length()), // cbOutput - &sizeNeeded, // pcbResult - BCRYPT_BLOCK_PADDING // dwFlags - ); - if (status < 0) { - qCWarning(lcTlsBackend, "%s failed (%ld)!", encrypt ? "Encrypt" : "Decrypt", status); - return {}; - } - } - - return QByteArray(reinterpret_cast(output.constData()), int(sizeNeeded)); -} -} // anonymous namespace - -namespace QTlsPrivate { - -QByteArray TlsKeySchannel::decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, - const QByteArray &iv) const -{ - return doCrypt(cipher, data, key, iv, false); -} - -QByteArray TlsKeySchannel::encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, - const QByteArray &iv) const -{ - return doCrypt(cipher, data, key, iv, true); -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - diff --git a/src/network/ssl/qtlskey_schannel_p.h b/src/network/ssl/qtlskey_schannel_p.h deleted file mode 100644 index 72747e7fa6..0000000000 --- a/src/network/ssl/qtlskey_schannel_p.h +++ /dev/null @@ -1,82 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLSKEY_SCHANNEL_P_H -#define QTLSKEY_SCHANNEL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include - -#include - -QT_REQUIRE_CONFIG(ssl); - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -class TlsKeySchannel final : public TlsKeyGeneric -{ -public: - using TlsKeyGeneric::TlsKeyGeneric; - - QByteArray decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, - const QByteArray &iv) const override; - QByteArray encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, - const QByteArray &iv) const override; -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QTLSKEY_SCHANNEL_P_H - diff --git a/src/network/ssl/qtlskey_st.cpp b/src/network/ssl/qtlskey_st.cpp deleted file mode 100644 index c551729c24..0000000000 --- a/src/network/ssl/qtlskey_st.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Copyright (C) 2014 Jeremy Lainé -** 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$ -** -****************************************************************************/ - -#include "qtlskey_st_p.h" -#include "qsslkey_p.h" - -#include - -#include - -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { -namespace { - -// Before this code was located in qsslkey_mac.cpp. -QByteArray wrapCCCrypt(CCOperation ccOp, QSslKeyPrivate::Cipher cipher, - const QByteArray &data, const QByteArray &key, - const QByteArray &iv) -{ - int blockSize = {}; - CCAlgorithm ccAlgorithm = {}; - switch (cipher) { - case Cipher::DesCbc: - blockSize = kCCBlockSizeDES; - ccAlgorithm = kCCAlgorithmDES; - break; - case Cipher::DesEde3Cbc: - blockSize = kCCBlockSize3DES; - ccAlgorithm = kCCAlgorithm3DES; - break; - case Cipher::Rc2Cbc: - blockSize = kCCBlockSizeRC2; - ccAlgorithm = kCCAlgorithmRC2; - break; - case Cipher::Aes128Cbc: - case Cipher::Aes192Cbc: - case Cipher::Aes256Cbc: - blockSize = kCCBlockSizeAES128; - ccAlgorithm = kCCAlgorithmAES; - break; - } - std::size_t plainLength = 0; - QByteArray plain(data.size() + blockSize, 0); - CCCryptorStatus status = CCCrypt(ccOp, ccAlgorithm, kCCOptionPKCS7Padding, - key.constData(), std::size_t(key.size()), - iv.constData(), data.constData(), std::size_t(data.size()), - plain.data(), std::size_t(plain.size()), &plainLength); - if (status == kCCSuccess) - return plain.left(int(plainLength)); - - return {}; -} - -} // Unnamed namespace. - -QByteArray TlsKeySecureTransport::decrypt(Cipher cipher, const QByteArray &data, - const QByteArray &key, const QByteArray &iv) const -{ - return wrapCCCrypt(kCCDecrypt, cipher, data, key, iv); -} - -QByteArray TlsKeySecureTransport::encrypt(Cipher cipher, const QByteArray &data, - const QByteArray &key, const QByteArray &iv) const -{ - return wrapCCCrypt(kCCEncrypt, cipher, data, key, iv); -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE diff --git a/src/network/ssl/qtlskey_st_p.h b/src/network/ssl/qtlskey_st_p.h deleted file mode 100644 index 7088daf39a..0000000000 --- a/src/network/ssl/qtlskey_st_p.h +++ /dev/null @@ -1,83 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QTLSKEY_ST_P_H -#define QTLSKEY_ST_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include - -#include - -QT_REQUIRE_CONFIG(ssl); - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -class TlsKeySecureTransport final : public TlsKeyGeneric -{ -public: - using TlsKeyGeneric::TlsKeyGeneric; - - QByteArray decrypt(Cipher cipher, const QByteArray &data, - const QByteArray &key, const QByteArray &iv) const override; - QByteArray encrypt(Cipher cipher, const QByteArray &data, - const QByteArray &key, const QByteArray &iv) const override; - - Q_DISABLE_COPY_MOVE(TlsKeySecureTransport) -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QTLSKEY_ST_P_H diff --git a/src/network/ssl/qwincrypt_p.h b/src/network/ssl/qwincrypt_p.h deleted file mode 100644 index 2a7bd1fae2..0000000000 --- a/src/network/ssl/qwincrypt_p.h +++ /dev/null @@ -1,81 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QWINCRYPT_P_H -#define QWINCRYPT_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include - -#include - -#include -#ifndef HCRYPTPROV_LEGACY -#define HCRYPTPROV_LEGACY HCRYPTPROV -#endif // !HCRYPTPROV_LEGACY - -#include - -QT_BEGIN_NAMESPACE - -struct QHCertStoreDeleter { - void operator()(HCERTSTORE store) - { - CertCloseStore(store, 0); - } -}; - -// A simple RAII type used by Schannel code and Window CA fetcher class: -using QHCertStorePointer = std::unique_ptr; - -QT_END_NAMESPACE - -#endif // QWINCRYPT_P_H diff --git a/src/network/ssl/qwindowscarootfetcher.cpp b/src/network/ssl/qwindowscarootfetcher.cpp deleted file mode 100644 index b675ac8d4e..0000000000 --- a/src/network/ssl/qwindowscarootfetcher.cpp +++ /dev/null @@ -1,292 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qwindowscarootfetcher_p.h" - -#include -#include - -#include - -#ifdef QSSLSOCKET_DEBUG -#include "qssl_p.h" // for debug categories -#include -#endif - -#include "qsslsocket_p.h" // Transitively includes Wincrypt.h - -#if QT_CONFIG(openssl) -#include "qopenssl_p.h" -#include "qx509_openssl_p.h" -#endif - -QT_BEGIN_NAMESPACE - -class QWindowsCaRootFetcherThread : public QThread -{ -public: - QWindowsCaRootFetcherThread() - { - qRegisterMetaType(); - setObjectName(QStringLiteral("QWindowsCaRootFetcher")); - start(); - } - ~QWindowsCaRootFetcherThread() - { - quit(); - wait(15500); // worst case, a running request can block for 15 seconds - } -}; - -Q_GLOBAL_STATIC(QWindowsCaRootFetcherThread, windowsCaRootFetcherThread); - -#if QT_CONFIG(openssl) -namespace { -// TLSTODO: we have to ask the currently active TLS backend about verification -// support and get a function pointer. QT_CONFIG(openssl) check is becoming useless -// as soon as we have several plugins. -const QList buildVerifiedChain(const QList &caCertificates, - PCCERT_CHAIN_CONTEXT chainContext, - const QString &peerVerifyName) -{ - // We ended up here because OpenSSL verification failed to - // build a chain, with intermediate certificate missing - // but "Authority Information Access" extension present. - // Also, apparently the normal CA fetching path was disabled - // by setting custom CA certificates. We convert wincrypt's - // structures in QSslCertificate and give OpenSSL the second - // chance to verify the now (apparently) complete chain. - // In addition, wincrypt gives us a benifit of some checks - // we don't have in OpenSSL back-end. - Q_ASSERT(chainContext); - - if (!chainContext->cChain) - return {}; - - QList verifiedChain; - - CERT_SIMPLE_CHAIN *chain = chainContext->rgpChain[chainContext->cChain - 1]; - if (!chain) - return {}; - - if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN) - return {}; // No need to mess with OpenSSL (the chain is still incomplete). - - for (DWORD i = 0; i < chain->cElement; ++i) { - CERT_CHAIN_ELEMENT *element = chain->rgpElement[i]; - QSslCertificate cert(QByteArray(reinterpret_cast(element->pCertContext->pbCertEncoded), - int(element->pCertContext->cbCertEncoded)), QSsl::Der); - - if (cert.isBlacklisted()) - return {}; - - if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) // Good to know! - return {}; - - if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID) - return {}; - - verifiedChain.append(cert); - } - - // We rely on OpenSSL's ability to find other problems. - const auto tlsErrors = QTlsPrivate::X509CertificateOpenSSL::verify(caCertificates, verifiedChain, peerVerifyName); - if (tlsErrors.size()) - verifiedChain.clear(); - - return verifiedChain; -} - -} // unnamed namespace -#endif // QT_CONFIG(openssl) - -QWindowsCaRootFetcher::QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode, - const QList &caCertificates, const QString &hostName) - : cert(certificate), mode(sslMode), explicitlyTrustedCAs(caCertificates), peerVerifyName(hostName) -{ - moveToThread(windowsCaRootFetcherThread()); -} - -QWindowsCaRootFetcher::~QWindowsCaRootFetcher() -{ -} - -void QWindowsCaRootFetcher::start() -{ - QByteArray der = cert.toDer(); - PCCERT_CONTEXT wincert = CertCreateCertificateContext(X509_ASN_ENCODING, (const BYTE *)der.constData(), der.length()); - if (!wincert) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "QWindowsCaRootFetcher failed to convert certificate to windows form"); -#endif - emit finished(cert, QSslCertificate()); - deleteLater(); - return; - } - - CERT_CHAIN_PARA parameters; - memset(¶meters, 0, sizeof(parameters)); - parameters.cbSize = sizeof(parameters); - // set key usage constraint - parameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; - parameters.RequestedUsage.Usage.cUsageIdentifier = 1; - LPSTR oid = (LPSTR)(mode == QSslSocket::SslClientMode ? szOID_PKIX_KP_SERVER_AUTH : szOID_PKIX_KP_CLIENT_AUTH); - parameters.RequestedUsage.Usage.rgpszUsageIdentifier = &oid; - -#ifdef QSSLSOCKET_DEBUG - QElapsedTimer stopwatch; - stopwatch.start(); -#endif - PCCERT_CHAIN_CONTEXT chain; - auto additionalStore = createAdditionalStore(); - BOOL result = CertGetCertificateChain( - nullptr, //default engine - wincert, - nullptr, //current date/time - additionalStore.get(), //default store (nullptr) or CAs an application trusts - ¶meters, - 0, //default dwFlags - nullptr, //reserved - &chain); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QWindowsCaRootFetcher" << stopwatch.elapsed() << "ms to get chain"; -#endif - - QSslCertificate trustedRoot; - if (result) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QWindowsCaRootFetcher - examining windows chains"; - if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR) - qCDebug(lcSsl) << " - TRUSTED"; - else - qCDebug(lcSsl) << " - NOT TRUSTED" << chain->TrustStatus.dwErrorStatus; - if (chain->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) - qCDebug(lcSsl) << " - SELF SIGNED"; - qCDebug(lcSsl) << "QWindowsCaRootFetcher - dumping simple chains"; - for (unsigned int i = 0; i < chain->cChain; i++) { - if (chain->rgpChain[i]->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR) - qCDebug(lcSsl) << " - TRUSTED SIMPLE CHAIN" << i; - else - qCDebug(lcSsl) << " - UNTRUSTED SIMPLE CHAIN" << i << "reason:" << chain->rgpChain[i]->TrustStatus.dwErrorStatus; - for (unsigned int j = 0; j < chain->rgpChain[i]->cElement; j++) { - QSslCertificate foundCert(QByteArray((const char *)chain->rgpChain[i]->rgpElement[j]->pCertContext->pbCertEncoded - , chain->rgpChain[i]->rgpElement[j]->pCertContext->cbCertEncoded), QSsl::Der); - qCDebug(lcSsl) << " - " << foundCert; - } - } - qCDebug(lcSsl) << " - and" << chain->cLowerQualityChainContext << "low quality chains"; //expect 0, we haven't asked for them -#endif - - //based on http://msdn.microsoft.com/en-us/library/windows/desktop/aa377182%28v=vs.85%29.aspx - //about the final chain rgpChain[cChain-1] which must begin with a trusted root to be valid - if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR - && chain->cChain > 0) { - const PCERT_SIMPLE_CHAIN finalChain = chain->rgpChain[chain->cChain - 1]; - // http://msdn.microsoft.com/en-us/library/windows/desktop/aa377544%28v=vs.85%29.aspx - // rgpElement[0] is the end certificate chain element. rgpElement[cElement-1] is the self-signed "root" certificate element. - if (finalChain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR - && finalChain->cElement > 0) { - trustedRoot = QSslCertificate(QByteArray((const char *)finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->pbCertEncoded - , finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->cbCertEncoded), QSsl::Der); - } - } else if (explicitlyTrustedCAs.size()) { - // Setting custom CA in configuration, and those CAs are not trusted by MS. -#if QT_CONFIG(openssl) - const auto verifiedChain = buildVerifiedChain(explicitlyTrustedCAs, chain, peerVerifyName); - if (verifiedChain.size()) - trustedRoot = verifiedChain.last(); -#else - //It's only OpenSSL code-path that can trigger such a fetch. - Q_UNREACHABLE(); -#endif - } - CertFreeCertificateChain(chain); - } - CertFreeCertificateContext(wincert); - - emit finished(cert, trustedRoot); - deleteLater(); -} - -QHCertStorePointer QWindowsCaRootFetcher::createAdditionalStore() const -{ - QHCertStorePointer customStore; - if (explicitlyTrustedCAs.isEmpty()) - return customStore; - - if (HCERTSTORE rawPtr = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr)) { - customStore.reset(rawPtr); - - unsigned rootsAdded = 0; - for (const QSslCertificate &caCert : explicitlyTrustedCAs) { - const auto der = caCert.toDer(); - PCCERT_CONTEXT winCert = CertCreateCertificateContext(X509_ASN_ENCODING, - reinterpret_cast(der.data()), - DWORD(der.length())); - if (!winCert) { -#if defined(QSSLSOCKET_DEBUG) - qCWarning(lcSsl) << "CA fetcher, failed to convert QSslCertificate" - << "to the native representation"; -#endif // QSSLSOCKET_DEBUG - continue; - } - const auto deleter = qScopeGuard([winCert](){ - CertFreeCertificateContext(winCert); - }); - if (CertAddCertificateContextToStore(customStore.get(), winCert, CERT_STORE_ADD_ALWAYS, nullptr)) - ++rootsAdded; -#if defined(QSSLSOCKET_DEBUG) - else //Why assert? With flags we're using and winCert check - should not happen! - Q_ASSERT("CertAddCertificateContextToStore() failed"); -#endif // QSSLSOCKET_DEBUG - } - if (!rootsAdded) //Useless store, no cert was added. - customStore.reset(); -#if defined(QSSLSOCKET_DEBUG) - } else { - - qCWarning(lcSsl) << "CA fetcher, failed to create a custom" - << "store for explicitly trusted CA certificate"; -#endif // QSSLSOCKET_DEBUG - } - - return customStore; -} - -QT_END_NAMESPACE diff --git a/src/network/ssl/qwindowscarootfetcher_p.h b/src/network/ssl/qwindowscarootfetcher_p.h deleted file mode 100644 index ee0d07c1e0..0000000000 --- a/src/network/ssl/qwindowscarootfetcher_p.h +++ /dev/null @@ -1,95 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QWINDOWSCAROOTFETCHER_P_H -#define QWINDOWSCAROOTFETCHER_P_H - -#include - -#include -#include - -#include "qsslcertificate.h" -#include "qsslsocket.h" - -#include "qwincrypt_p.h" - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -QT_BEGIN_NAMESPACE - -class QWindowsCaRootFetcher : public QObject -{ - Q_OBJECT -public: - QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode, - const QList &caCertificates = {}, - const QString &hostName = {}); - ~QWindowsCaRootFetcher(); -public slots: - void start(); -signals: - void finished(QSslCertificate brokenChain, QSslCertificate caroot); -private: - QHCertStorePointer createAdditionalStore() const; - - QSslCertificate cert; - QSslSocket::SslMode mode; - // In case the application set CA certificates in the configuration, - // in the past we did not load missing certs. But this disables - // recoverable case when a certificate has Authority Information Access - // extension. So we try to fetch in this scenario also, but in case - // explicitly trusted root was not in a system store, we'll do - // additional checks, thus we need 'peerVerifyName': - QList explicitlyTrustedCAs; - QString peerVerifyName; -}; - -QT_END_NAMESPACE - -#endif // QWINDOWSCAROOTFETCHER_P_H diff --git a/src/network/ssl/qx509_base.cpp b/src/network/ssl/qx509_base.cpp deleted file mode 100644 index d7b7b81606..0000000000 --- a/src/network/ssl/qx509_base.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qx509_base_p.h" - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -QByteArray X509CertificateBase::subjectInfoToString(QSslCertificate::SubjectInfo info) -{ - QByteArray str; - switch (info) { - case QSslCertificate::Organization: str = QByteArray("O"); break; - case QSslCertificate::CommonName: str = QByteArray("CN"); break; - case QSslCertificate::LocalityName: str = QByteArray("L"); break; - case QSslCertificate::OrganizationalUnitName: str = QByteArray("OU"); break; - case QSslCertificate::CountryName: str = QByteArray("C"); break; - case QSslCertificate::StateOrProvinceName: str = QByteArray("ST"); break; - case QSslCertificate::DistinguishedNameQualifier: str = QByteArray("dnQualifier"); break; - case QSslCertificate::SerialNumber: str = QByteArray("serialNumber"); break; - case QSslCertificate::EmailAddress: str = QByteArray("emailAddress"); break; - } - - return str; -} - -bool X509CertificateBase::matchLineFeed(const QByteArray &pem, int *offset) -{ - Q_ASSERT(offset); - - char ch = 0; - // ignore extra whitespace at the end of the line - while (*offset < pem.size() && (ch = pem.at(*offset)) == ' ') - ++*offset; - - if (ch == '\n') { - *offset += 1; - return true; - } - - if (ch == '\r' && pem.size() > (*offset + 1) && pem.at(*offset + 1) == '\n') { - *offset += 2; - return true; - } - - return false; -} - -bool X509CertificateBase::isNull() const -{ - return null; -} - -QByteArray X509CertificateBase::version() const -{ - return versionString; -} - -QByteArray X509CertificateBase::serialNumber() const -{ - return serialNumberString; -} - -QStringList X509CertificateBase::issuerInfo(QSslCertificate::SubjectInfo info) const -{ - return issuerInfo(subjectInfoToString(info)); -} - -QStringList X509CertificateBase::issuerInfo(const QByteArray &attribute) const -{ - return issuerInfoEntries.values(attribute); -} - -QStringList X509CertificateBase::subjectInfo(QSslCertificate::SubjectInfo info) const -{ - return subjectInfo(subjectInfoToString(info)); -} - -QStringList X509CertificateBase::subjectInfo(const QByteArray &attribute) const -{ - return subjectInfoEntries.values(attribute); -} - -QList X509CertificateBase::subjectInfoAttributes() const -{ - return subjectInfoEntries.uniqueKeys(); -} - -QList X509CertificateBase::issuerInfoAttributes() const -{ - return issuerInfoEntries.uniqueKeys(); -} - -QDateTime X509CertificateBase::effectiveDate() const -{ - return notValidBefore; -} - -QDateTime X509CertificateBase::expiryDate() const -{ - return notValidAfter; -} - -qsizetype X509CertificateBase::numberOfExtensions() const -{ - return extensions.size(); -} - -QString X509CertificateBase::oidForExtension(qsizetype index) const -{ - Q_ASSERT(validIndex(index)); - return extensions[index].oid; -} - -QString X509CertificateBase::nameForExtension(qsizetype index) const -{ - Q_ASSERT(validIndex(index)); - return extensions[index].name; -} - -QVariant X509CertificateBase::valueForExtension(qsizetype index) const -{ - Q_ASSERT(validIndex(index)); - return extensions[index].value; -} - -bool X509CertificateBase::isExtensionCritical(qsizetype index) const -{ - Q_ASSERT(validIndex(index)); - return extensions[index].critical; -} - -bool X509CertificateBase::isExtensionSupported(qsizetype index) const -{ - Q_ASSERT(validIndex(index)); - return extensions[index].supported; -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE diff --git a/src/network/ssl/qx509_base_p.h b/src/network/ssl/qx509_base_p.h deleted file mode 100644 index 6905848e46..0000000000 --- a/src/network/ssl/qx509_base_p.h +++ /dev/null @@ -1,125 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QX509CERTIFICATE_BASE_P_H -#define QX509CERTIFICATE_BASE_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include - -#include - -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -class X509CertificateBase : public X509Certificate -{ -public: - bool isNull() const override; - QByteArray version() const override; - QByteArray serialNumber() const override; - QStringList issuerInfo(QSslCertificate::SubjectInfo info) const override; - QStringList issuerInfo(const QByteArray &attribute) const override; - QStringList subjectInfo(QSslCertificate::SubjectInfo info) const override; - QStringList subjectInfo(const QByteArray &attribute) const override; - QList subjectInfoAttributes() const override; - QList issuerInfoAttributes() const override; - QDateTime effectiveDate() const override; - QDateTime expiryDate() const override; - - qsizetype numberOfExtensions() const override; - QString oidForExtension(qsizetype index) const override; - QString nameForExtension(qsizetype index) const override; - QVariant valueForExtension(qsizetype index) const override; - bool isExtensionCritical(qsizetype index) const override; - bool isExtensionSupported(qsizetype index) const override; - - static QByteArray subjectInfoToString(QSslCertificate::SubjectInfo info); - static bool matchLineFeed(const QByteArray &pem, int *offset); - -protected: - bool validIndex(qsizetype index) const - { - return index >= 0 && index < extensions.size(); - } - - bool null = true; - QByteArray versionString; - QByteArray serialNumberString; - - QMultiMap issuerInfoEntries; - QMultiMap subjectInfoEntries; - QDateTime notValidAfter; - QDateTime notValidBefore; - - struct X509CertificateExtension - { - QString oid; - QString name; - QVariant value; - bool critical = false; - bool supported = false; - }; - - QList extensions; -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QX509CERTIFICATE_BASE_P_H diff --git a/src/network/ssl/qx509_generic.cpp b/src/network/ssl/qx509_generic.cpp deleted file mode 100644 index 40178f5e7c..0000000000 --- a/src/network/ssl/qx509_generic.cpp +++ /dev/null @@ -1,467 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qsslcertificate_p.h" -#include "qx509_generic_p.h" -#include "qasn1element_p.h" - -#include "qssl_p.h" - -#include -#include -#include - -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -namespace { - -QByteArray colonSeparatedHex(const QByteArray &value) -{ - const int size = value.size(); - int i = 0; - while (i < size && !value.at(i)) // skip leading zeros - ++i; - - return value.mid(i).toHex(':'); -} - -} // Unnamed namespace. - -bool X509CertificateGeneric::isEqual(const X509Certificate &rhs) const -{ - const auto &other = static_cast(rhs); - return derData == other.derData; -} - -bool X509CertificateGeneric::isSelfSigned() const -{ - if (null) - return false; - - qCWarning(lcTlsBackend, "QSslCertificate::isSelfSigned: This function does not check, whether the certificate " - "is actually signed. It just checks whether issuer and subject are identical"); - return subjectMatchesIssuer; -} - -QMultiMap X509CertificateGeneric::subjectAlternativeNames() const -{ - return saNames; -} - -#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" -#define ENDCERTSTRING "-----END CERTIFICATE-----" - -QByteArray X509CertificateGeneric::toPem() const -{ - QByteArray array = toDer(); - // Convert to Base64 - wrap at 64 characters. - array = array.toBase64(); - QByteArray tmp; - for (int i = 0; i <= array.size() - 64; i += 64) { - tmp += QByteArray::fromRawData(array.data() + i, 64); - tmp += '\n'; - } - if (int remainder = array.size() % 64) { - tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder); - tmp += '\n'; - } - - return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n"; -} - -QByteArray X509CertificateGeneric::toDer() const -{ - return derData; -} - -QString X509CertificateGeneric::toText() const -{ - Q_UNIMPLEMENTED(); - return {}; -} - -Qt::HANDLE X509CertificateGeneric::handle() const -{ - Q_UNIMPLEMENTED(); - return nullptr; -} - -size_t X509CertificateGeneric::hash(size_t seed) const noexcept -{ - return qHash(toDer(), seed); -} - -QList X509CertificateGeneric::certificatesFromPem(const QByteArray &pem, int count) -{ - QList certificates; - int offset = 0; - while (count == -1 || certificates.size() < count) { - int startPos = pem.indexOf(BEGINCERTSTRING, offset); - if (startPos == -1) - break; - startPos += sizeof(BEGINCERTSTRING) - 1; - if (!matchLineFeed(pem, &startPos)) - break; - - int endPos = pem.indexOf(ENDCERTSTRING, startPos); - if (endPos == -1) - break; - - offset = endPos + sizeof(ENDCERTSTRING) - 1; - if (offset < pem.size() && !matchLineFeed(pem, &offset)) - break; - - QByteArray decoded = QByteArray::fromBase64( - QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); - certificates << certificatesFromDer(decoded, 1);; - } - - return certificates; -} - -QList X509CertificateGeneric::certificatesFromDer(const QByteArray &der, int count) -{ - QList certificates; - - QByteArray data = der; - while (count == -1 || certificates.size() < count) { - QSslCertificate cert; - auto *certBackend = QTlsBackend::backend(cert); - if (!certBackend->parse(data)) - break; - - certificates << cert; - data.remove(0, certBackend->derData.size()); - } - - return certificates; -} - -bool X509CertificateGeneric::parse(const QByteArray &data) -{ - QAsn1Element root; - - QDataStream dataStream(data); - if (!root.read(dataStream) || root.type() != QAsn1Element::SequenceType) - return false; - - QDataStream rootStream(root.value()); - QAsn1Element cert; - if (!cert.read(rootStream) || cert.type() != QAsn1Element::SequenceType) - return false; - - // version or serial number - QAsn1Element elem; - QDataStream certStream(cert.value()); - if (!elem.read(certStream)) - return false; - - if (elem.type() == QAsn1Element::Context0Type) { - QDataStream versionStream(elem.value()); - if (!elem.read(versionStream) - || elem.type() != QAsn1Element::IntegerType - || elem.value().isEmpty()) - return false; - - versionString = QByteArray::number(elem.value().at(0) + 1); - if (!elem.read(certStream)) - return false; - } else { - versionString = QByteArray::number(1); - } - - // serial number - if (elem.type() != QAsn1Element::IntegerType) - return false; - serialNumberString = colonSeparatedHex(elem.value()); - - // algorithm ID - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - // issuer info - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); - issuerInfoEntries = elem.toInfo(); - - // validity period - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - QDataStream validityStream(elem.value()); - if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) - return false; - - notValidBefore = elem.toDateTime(); - if (!notValidBefore.isValid()) - return false; - - if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) - return false; - - notValidAfter = elem.toDateTime(); - if (!notValidAfter.isValid()) - return false; - - - // subject name - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); - subjectInfoEntries = elem.toInfo(); - subjectMatchesIssuer = issuerDer == subjectDer; - - // public key - qint64 keyStart = certStream.device()->pos(); - if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - publicKeyDerData.resize(certStream.device()->pos() - keyStart); - QDataStream keyStream(elem.value()); - if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) - return false; - - - // key algorithm - if (!elem.read(elem.value()) || elem.type() != QAsn1Element::ObjectIdentifierType) - return false; - - const QByteArray oid = elem.toObjectId(); - if (oid == RSA_ENCRYPTION_OID) - publicKeyAlgorithm = QSsl::Rsa; - else if (oid == DSA_ENCRYPTION_OID) - publicKeyAlgorithm = QSsl::Dsa; - else if (oid == EC_ENCRYPTION_OID) - publicKeyAlgorithm = QSsl::Ec; - else - publicKeyAlgorithm = QSsl::Opaque; - - certStream.device()->seek(keyStart); - certStream.readRawData(publicKeyDerData.data(), publicKeyDerData.size()); - - // extensions - while (elem.read(certStream)) { - if (elem.type() == QAsn1Element::Context3Type) { - if (elem.read(elem.value()) && elem.type() == QAsn1Element::SequenceType) { - QDataStream extStream(elem.value()); - while (elem.read(extStream) && elem.type() == QAsn1Element::SequenceType) { - X509CertificateExtension extension; - if (!parseExtension(elem.value(), extension)) - return false; - - if (extension.oid == QLatin1String("2.5.29.17")) { - // subjectAltName - - // Note, parseExtension() returns true for this extensions, - // but considers it to be unsupported and assignes a useless - // value. OpenSSL also treats this extension as unsupported, - // but properly creates a map with 'name' and 'value' taken - // from the extension. We only support 'email', 'IP' and 'DNS', - // but this is what our subjectAlternativeNames map can contain - // anyway. - QVariantMap extValue; - QAsn1Element sanElem; - if (sanElem.read(extension.value.toByteArray()) && sanElem.type() == QAsn1Element::SequenceType) { - QDataStream nameStream(sanElem.value()); - QAsn1Element nameElem; - while (nameElem.read(nameStream)) { - switch (nameElem.type()) { - case QAsn1Element::Rfc822NameType: - saNames.insert(QSsl::EmailEntry, nameElem.toString()); - extValue[QStringLiteral("email")] = nameElem.toString(); - break; - case QAsn1Element::DnsNameType: - saNames.insert(QSsl::DnsEntry, nameElem.toString()); - extValue[QStringLiteral("DNS")] = nameElem.toString(); - break; - case QAsn1Element::IpAddressType: { - QHostAddress ipAddress; - QByteArray ipAddrValue = nameElem.value(); - switch (ipAddrValue.length()) { - case 4: // IPv4 - ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast(ipAddrValue.data()))); - break; - case 16: // IPv6 - ipAddress = QHostAddress(reinterpret_cast(ipAddrValue.data())); - break; - default: // Unknown IP address format - break; - } - if (!ipAddress.isNull()) { - saNames.insert(QSsl::IpAddressEntry, ipAddress.toString()); - extValue[QStringLiteral("IP")] = ipAddress.toString(); - } - break; - } - default: - break; - } - } - extension.value = extValue; - extension.supported = true; - } - } - - extensions << extension; - } - } - } - } - - derData = data.left(dataStream.device()->pos()); - null = false; - return true; -} - -bool X509CertificateGeneric::parseExtension(const QByteArray &data, X509CertificateExtension &extension) -{ - bool ok = false; - bool critical = false; - QAsn1Element oidElem, valElem; - - QDataStream seqStream(data); - - // oid - if (!oidElem.read(seqStream) || oidElem.type() != QAsn1Element::ObjectIdentifierType) - return false; - - const QByteArray oid = oidElem.toObjectId(); - // critical and value - if (!valElem.read(seqStream)) - return false; - - if (valElem.type() == QAsn1Element::BooleanType) { - critical = valElem.toBool(&ok); - - if (!ok || !valElem.read(seqStream)) - return false; - } - - if (valElem.type() != QAsn1Element::OctetStringType) - return false; - - // interpret value - QAsn1Element val; - bool supported = true; - QVariant value; - if (oid == "1.3.6.1.5.5.7.1.1") { - // authorityInfoAccess - if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) - return false; - QVariantMap result; - const auto elems = val.toList(); - for (const QAsn1Element &el : elems) { - const auto items = el.toList(); - if (items.size() != 2) - return false; - const QString key = QString::fromLatin1(items.at(0).toObjectName()); - switch (items.at(1).type()) { - case QAsn1Element::Rfc822NameType: - case QAsn1Element::DnsNameType: - case QAsn1Element::UniformResourceIdentifierType: - result[key] = items.at(1).toString(); - break; - } - } - value = result; - } else if (oid == "2.5.29.14") { - // subjectKeyIdentifier - if (!val.read(valElem.value()) || val.type() != QAsn1Element::OctetStringType) - return false; - value = colonSeparatedHex(val.value()).toUpper(); - } else if (oid == "2.5.29.19") { - // basicConstraints - if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) - return false; - - QVariantMap result; - const auto items = val.toList(); - if (items.size() > 0) { - result[QStringLiteral("ca")] = items.at(0).toBool(&ok); - if (!ok) - return false; - } else { - result[QStringLiteral("ca")] = false; - } - if (items.size() > 1) { - result[QStringLiteral("pathLenConstraint")] = items.at(1).toInteger(&ok); - if (!ok) - return false; - } - value = result; - } else if (oid == "2.5.29.35") { - // authorityKeyIdentifier - if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) - return false; - QVariantMap result; - const auto elems = val.toList(); - for (const QAsn1Element &el : elems) { - if (el.type() == 0x80) { - const QString key = QStringLiteral("keyid"); - result[key] = el.value().toHex(); - } else if (el.type() == 0x82) { - const QString serial = QStringLiteral("serial"); - result[serial] = colonSeparatedHex(el.value()); - } - } - value = result; - } else { - supported = false; - value = valElem.value(); - } - - extension.critical = critical; - extension.supported = supported; - extension.oid = QString::fromLatin1(oid); - extension.name = QString::fromLatin1(oidElem.toObjectName()); - extension.value = value; - - return true; -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE diff --git a/src/network/ssl/qx509_generic_p.h b/src/network/ssl/qx509_generic_p.h deleted file mode 100644 index b0dd3ec4b5..0000000000 --- a/src/network/ssl/qx509_generic_p.h +++ /dev/null @@ -1,101 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ -#ifndef QX509_GENERIC_P_H -#define QX509_GENERIC_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -// TLSTODO: This class is what previously was known as qsslcertificate_qt. -// A part of SecureTransport and Schannel plugin. -class X509CertificateGeneric : public X509CertificateBase -{ -public: - bool isEqual(const X509Certificate &rhs) const override; - bool isSelfSigned() const override; - - QMultiMap subjectAlternativeNames() const override; - QByteArray toPem() const override; - QByteArray toDer() const override; - QString toText() const override; - Qt::HANDLE handle() const override; - - size_t hash(size_t seed) const noexcept override; - - static QList certificatesFromPem(const QByteArray &pem, int count); - static QList certificatesFromDer(const QByteArray &der, int count); - -protected: - - bool subjectMatchesIssuer = false; - QSsl::KeyAlgorithm publicKeyAlgorithm = QSsl::Rsa; - QByteArray publicKeyDerData; - - QMultiMap saNames; - QByteArray derData; - - bool parse(const QByteArray &data); - bool parseExtension(const QByteArray &data, X509CertificateExtension &extension); -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QX509_GENERIC_P_H diff --git a/src/network/ssl/qx509_openssl.cpp b/src/network/ssl/qx509_openssl.cpp deleted file mode 100644 index 6c4a83be0b..0000000000 --- a/src/network/ssl/qx509_openssl.cpp +++ /dev/null @@ -1,929 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qtlsbackend_openssl_p.h" -#include "qsslcertificate_p.h" -#include "qtlskey_openssl_p.h" -#include "qx509_openssl_p.h" - -#include "qsslsocket_openssl_symbols_p.h" -#include "qtlsbackend_openssl_p.h" -#include "qtls_openssl_p.h" -#include "qsslsocket.h" - -#include - -#include -#include -#include -#include -#include -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -namespace { - -// TLSTODO: These helper functions below were static member-functions of -// QSslCertificatePrivate, if-defed with !QT_NO_OPENSSL, no need -// for them to be exposed this way anymore. Remove this comment when -// plugins are ready. -QByteArray asn1ObjectId(ASN1_OBJECT *object) -{ - if (!object) - return {}; - - char buf[80] = {}; // The openssl docs a buffer length of 80 should be more than enough - q_OBJ_obj2txt(buf, sizeof(buf), object, 1); // the 1 says always use the oid not the long name - - return QByteArray(buf); -} - -QByteArray asn1ObjectName(ASN1_OBJECT *object) -{ - if (!object) - return {}; - - const int nid = q_OBJ_obj2nid(object); - if (nid != NID_undef) - return QByteArray(q_OBJ_nid2sn(nid)); - - return asn1ObjectId(object); -} - -QMultiMap mapFromX509Name(X509_NAME *name) -{ - if (!name) - return {}; - - QMultiMap info; - for (int i = 0; i < q_X509_NAME_entry_count(name); ++i) { - X509_NAME_ENTRY *e = q_X509_NAME_get_entry(name, i); - - QByteArray name = asn1ObjectName(q_X509_NAME_ENTRY_get_object(e)); - unsigned char *data = nullptr; - int size = q_ASN1_STRING_to_UTF8(&data, q_X509_NAME_ENTRY_get_data(e)); - info.insert(name, QString::fromUtf8((char*)data, size)); - q_CRYPTO_free(data, nullptr, 0); - } - - return info; -} - -#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" -#define ENDCERTSTRING "-----END CERTIFICATE-----" - -QByteArray x509ToQByteArray(X509 *x509, QSsl::EncodingFormat format) -{ - Q_ASSERT(x509); - - // Use i2d_X509 to convert the X509 to an array. - const int length = q_i2d_X509(x509, nullptr); - if (length <= 0) { - QTlsBackendOpenSSL::logAndClearErrorQueue(); - return {}; - } - - QByteArray array; - array.resize(length); - - char *data = array.data(); - char **dataP = &data; - unsigned char **dataPu = (unsigned char **)dataP; - if (q_i2d_X509(x509, dataPu) < 0) - return QByteArray(); - - if (format == QSsl::Der) - return array; - - // Convert to Base64 - wrap at 64 characters. - array = array.toBase64(); - QByteArray tmp; - for (int i = 0; i <= array.size() - 64; i += 64) { - tmp += QByteArray::fromRawData(array.data() + i, 64); - tmp += '\n'; - } - if (int remainder = array.size() % 64) { - tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder); - tmp += '\n'; - } - - return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n"; -} - -QString x509ToText(X509 *x509) -{ - Q_ASSERT(x509); - - QByteArray result; - BIO *bio = q_BIO_new(q_BIO_s_mem()); - if (!bio) - return QString(); - const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); - - q_X509_print(bio, x509); - - QVarLengthArray data; - int count = q_BIO_read(bio, data.data(), 16384); - if ( count > 0 ) - result = QByteArray( data.data(), count ); - - return QString::fromLatin1(result); -} - -QVariant x509UnknownExtensionToValue(X509_EXTENSION *ext) -{ - // Get the extension specific method object if available - // we cast away the const-ness here because some versions of openssl - // don't use const for the parameters in the functions pointers stored - // in the object. - Q_ASSERT(ext); - - X509V3_EXT_METHOD *meth = const_cast(q_X509V3_EXT_get(ext)); - if (!meth) { - ASN1_OCTET_STRING *value = q_X509_EXTENSION_get_data(ext); - Q_ASSERT(value); - QByteArray result( reinterpret_cast(q_ASN1_STRING_get0_data(value)), - q_ASN1_STRING_length(value)); - return result; - } - - void *ext_internal = q_X509V3_EXT_d2i(ext); - - // If this extension can be converted - if (meth->i2v && ext_internal) { - STACK_OF(CONF_VALUE) *val = meth->i2v(meth, ext_internal, nullptr); - - QVariantMap map; - QVariantList list; - bool isMap = false; - - for (int j = 0; j < q_SKM_sk_num(val); j++) { - CONF_VALUE *nval = q_SKM_sk_value(CONF_VALUE, val, j); - if (nval->name && nval->value) { - isMap = true; - map[QString::fromUtf8(nval->name)] = QString::fromUtf8(nval->value); - } else if (nval->name) { - list << QString::fromUtf8(nval->name); - } else if (nval->value) { - list << QString::fromUtf8(nval->value); - } - } - - if (isMap) - return map; - else - return list; - } else if (meth->i2s && ext_internal) { - QVariant result(QString::fromUtf8(meth->i2s(meth, ext_internal))); - return result; - } else if (meth->i2r && ext_internal) { - QByteArray result; - - BIO *bio = q_BIO_new(q_BIO_s_mem()); - if (!bio) - return result; - - meth->i2r(meth, ext_internal, bio, 0); - - char *bio_buffer; - long bio_size = q_BIO_get_mem_data(bio, &bio_buffer); - result = QByteArray(bio_buffer, bio_size); - - q_BIO_free(bio); - return result; - } - - return QVariant(); -} - -/* - * Convert extensions to a variant. The naming of the keys of the map are - * taken from RFC 5280, however we decided the capitalisation in the RFC - * was too silly for the real world. - */ -QVariant x509ExtensionToValue(X509_EXTENSION *ext) -{ - ASN1_OBJECT *obj = q_X509_EXTENSION_get_object(ext); - int nid = q_OBJ_obj2nid(obj); - switch (nid) { - case NID_basic_constraints: - { - BASIC_CONSTRAINTS *basic = reinterpret_cast(q_X509V3_EXT_d2i(ext)); - if (!basic) - return {}; - QVariantMap result; - result[QLatin1String("ca")] = basic->ca ? true : false; - if (basic->pathlen) - result[QLatin1String("pathLenConstraint")] = (qlonglong)q_ASN1_INTEGER_get(basic->pathlen); - - q_BASIC_CONSTRAINTS_free(basic); - return result; - } - break; - case NID_info_access: - { - AUTHORITY_INFO_ACCESS *info = reinterpret_cast(q_X509V3_EXT_d2i(ext)); - if (!info) - return {}; - QVariantMap result; - for (int i=0; i < q_SKM_sk_num(info); i++) { - ACCESS_DESCRIPTION *ad = q_SKM_sk_value(ACCESS_DESCRIPTION, info, i); - - GENERAL_NAME *name = ad->location; - if (name->type == GEN_URI) { - int len = q_ASN1_STRING_length(name->d.uniformResourceIdentifier); - if (len < 0 || len >= 8192) { - // broken name - continue; - } - - const char *uriStr = reinterpret_cast(q_ASN1_STRING_get0_data(name->d.uniformResourceIdentifier)); - const QString uri = QString::fromUtf8(uriStr, len); - - result[QString::fromUtf8(asn1ObjectName(ad->method))] = uri; - } else { - qCWarning(lcTlsBackend) << "Strange location type" << name->type; - } - } - - q_OPENSSL_sk_pop_free((OPENSSL_STACK*)info, reinterpret_cast(q_OPENSSL_sk_free)); - return result; - } - break; - case NID_subject_key_identifier: - { - void *ext_internal = q_X509V3_EXT_d2i(ext); - if (!ext_internal) - return {}; - // we cast away the const-ness here because some versions of openssl - // don't use const for the parameters in the functions pointers stored - // in the object. - X509V3_EXT_METHOD *meth = const_cast(q_X509V3_EXT_get(ext)); - - return QVariant(QString::fromUtf8(meth->i2s(meth, ext_internal))); - } - break; - case NID_authority_key_identifier: - { - AUTHORITY_KEYID *auth_key = reinterpret_cast(q_X509V3_EXT_d2i(ext)); - if (!auth_key) - return {}; - QVariantMap result; - - // keyid - if (auth_key->keyid) { - QByteArray keyid(reinterpret_cast(auth_key->keyid->data), - auth_key->keyid->length); - result[QLatin1String("keyid")] = keyid.toHex(); - } - - // issuer - // TODO: GENERAL_NAMES - - // serial - if (auth_key->serial) - result[QLatin1String("serial")] = (qlonglong)q_ASN1_INTEGER_get(auth_key->serial); - - q_AUTHORITY_KEYID_free(auth_key); - return result; - } - break; - } - - return {}; -} - -} // Unnamed namespace - -extern "C" int qt_X509Callback(int ok, X509_STORE_CTX *ctx) -{ - if (!ok) { - // Store the error and at which depth the error was detected. - using ErrorListPtr = QList *; - ErrorListPtr errors = nullptr; - - // Error list is attached to either 'SSL' or 'X509_STORE'. - if (X509_STORE *store = q_X509_STORE_CTX_get0_store(ctx)) // We try store first: - errors = ErrorListPtr(q_X509_STORE_get_ex_data(store, 0)); - - if (!errors) { - // Not found on store? Try SSL and its external data then. According to the OpenSSL's - // documentation: - // - // "Whenever a X509_STORE_CTX object is created for the verification of the - // peer's certificate during a handshake, a pointer to the SSL object is - // stored into the X509_STORE_CTX object to identify the connection affected. - // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be - // used with the correct index." - - // TLSTODO: verification callback has to change as soon as TlsCryptographer is in place. - // This is a temporary solution for now to ease the transition. - const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData - + TlsCryptographOpenSSL::errorOffsetInExData; - if (SSL *ssl = static_cast(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx()))) - errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset)); - } - - if (!errors) { - qCWarning(lcTlsBackend, "Neither X509_STORE, nor SSL contains error list, verification failed"); - return 0; - } - - errors->append(X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); - } - // Always return OK to allow verification to continue. We handle the - // errors gracefully after collecting all errors, after verification has - // completed. - return 1; -} - -X509CertificateOpenSSL::X509CertificateOpenSSL() = default; - -X509CertificateOpenSSL::~X509CertificateOpenSSL() -{ - if (x509) - q_X509_free(x509); -} - -bool X509CertificateOpenSSL::isEqual(const X509Certificate &rhs) const -{ - //TLSTODO: to make it safe I'll check the backend type later. - const auto &other = static_cast(rhs); - if (x509 && other.x509) { - const int ret = q_X509_cmp(x509, other.x509); - if (ret >= -1 && ret <= 1) - return ret == 0; - QTlsBackendOpenSSL::logAndClearErrorQueue(); - } - - return false; -} - -bool X509CertificateOpenSSL::isSelfSigned() const -{ - if (!x509) - return false; - - return q_X509_check_issued(x509, x509) == X509_V_OK; -} - -QMultiMap -X509CertificateOpenSSL::subjectAlternativeNames() const -{ - QMultiMap result; - - if (!x509) - return result; - - auto *altNames = static_cast(q_X509_get_ext_d2i(x509, NID_subject_alt_name, - nullptr, nullptr)); - if (!altNames) - return result; - - auto altName = [](ASN1_IA5STRING *ia5, int len) { - const char *altNameStr = reinterpret_cast(q_ASN1_STRING_get0_data(ia5)); - return QString::fromLatin1(altNameStr, len); - }; - - for (int i = 0; i < q_sk_GENERAL_NAME_num(altNames); ++i) { - const GENERAL_NAME *genName = q_sk_GENERAL_NAME_value(altNames, i); - if (genName->type != GEN_DNS && genName->type != GEN_EMAIL && genName->type != GEN_IPADD) - continue; - - const int len = q_ASN1_STRING_length(genName->d.ia5); - if (len < 0 || len >= 8192) { - // broken name - continue; - } - - switch (genName->type) { - case GEN_DNS: - result.insert(QSsl::DnsEntry, altName(genName->d.ia5, len)); - break; - case GEN_EMAIL: - result.insert(QSsl::EmailEntry, altName(genName->d.ia5, len)); - break; - case GEN_IPADD: { - QHostAddress ipAddress; - switch (len) { - case 4: // IPv4 - ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast(genName->d.iPAddress->data))); - break; - case 16: // IPv6 - ipAddress = QHostAddress(reinterpret_cast(genName->d.iPAddress->data)); - break; - default: // Unknown IP address format - break; - } - if (!ipAddress.isNull()) - result.insert(QSsl::IpAddressEntry, ipAddress.toString()); - break; - } - default: - break; - } - } - - q_OPENSSL_sk_pop_free((OPENSSL_STACK*)altNames, reinterpret_cast(q_GENERAL_NAME_free)); - - return result; -} - -TlsKey *X509CertificateOpenSSL::publicKey() const -{ - if (!x509) - return {}; - - return TlsKeyOpenSSL::publicKeyFromX509(x509); -} - -QByteArray X509CertificateOpenSSL::toPem() const -{ - if (!x509) - return {}; - - return x509ToQByteArray(x509, QSsl::Pem); -} - -QByteArray X509CertificateOpenSSL::toDer() const -{ - if (!x509) - return {}; - - return x509ToQByteArray(x509, QSsl::Der); - -} -QString X509CertificateOpenSSL::toText() const -{ - if (!x509) - return {}; - - return x509ToText(x509); -} - -Qt::HANDLE X509CertificateOpenSSL::handle() const -{ - return Qt::HANDLE(x509); -} - -size_t X509CertificateOpenSSL::hash(size_t seed) const noexcept -{ - if (x509) { - const EVP_MD *sha1 = q_EVP_sha1(); - unsigned int len = 0; - unsigned char md[EVP_MAX_MD_SIZE]; - q_X509_digest(x509, sha1, md, &len); - return qHashBits(md, len, seed); - } - - return seed; -} - -QSslCertificate X509CertificateOpenSSL::certificateFromX509(X509 *x509) -{ - QSslCertificate certificate; - - auto *backend = QTlsBackend::backend(certificate); - if (!backend || !x509) - return certificate; - - ASN1_TIME *nbef = q_X509_getm_notBefore(x509); - if (nbef) - backend->notValidBefore = q_getTimeFromASN1(nbef); - - ASN1_TIME *naft = q_X509_getm_notAfter(x509); - if (naft) - backend->notValidAfter = q_getTimeFromASN1(naft); - - backend->null = false; - backend->x509 = q_X509_dup(x509); - - backend->issuerInfoEntries = mapFromX509Name(q_X509_get_issuer_name(x509)); - backend->subjectInfoEntries = mapFromX509Name(q_X509_get_subject_name(x509)); - backend->versionString = QByteArray::number(qlonglong(q_X509_get_version(x509)) + 1); - - if (ASN1_INTEGER *serialNumber = q_X509_get_serialNumber(x509)) { - QByteArray hexString; - hexString.reserve(serialNumber->length * 3); - for (int a = 0; a < serialNumber->length; ++a) { - hexString += QByteArray::number(serialNumber->data[a], 16).rightJustified(2, '0'); - hexString += ':'; - } - hexString.chop(1); - backend->serialNumberString = hexString; - } - - backend->parseExtensions(); - - return certificate; -} - -QList X509CertificateOpenSSL::stackOfX509ToQSslCertificates(STACK_OF(X509) *x509) -{ - if (!x509) - return {}; - - QList certificates; - for (int i = 0; i < q_sk_X509_num(x509); ++i) { - if (X509 *entry = q_sk_X509_value(x509, i)) - certificates << certificateFromX509(entry); - } - - return certificates; -} - -QSslErrorEntry X509CertificateOpenSSL::errorEntryFromStoreContext(X509_STORE_CTX *ctx) -{ - Q_ASSERT(ctx); - - return {q_X509_STORE_CTX_get_error(ctx), q_X509_STORE_CTX_get_error_depth(ctx)}; -} - -QList X509CertificateOpenSSL::verify(const QList &chain, - const QString &hostName) -{ - // This was previously QSslSocketPrivate::verify(). - auto roots = QSslConfiguration::defaultConfiguration().caCertificates(); -#ifndef Q_OS_WIN - // On Windows, system CA certificates are already set as default ones. - // No need to add them again (and again) and also, if the default configuration - // has its own set of CAs, this probably should not be amended by the ones - // from the 'ROOT' store, since it's not what an application chose to trust. - if (QSslSocketPrivate::rootCertOnDemandLoadingSupported()) - roots.append(QSslSocketPrivate::systemCaCertificates()); -#endif // Q_OS_WIN - return verify(roots, chain, hostName); -} - -QList X509CertificateOpenSSL::verify(const QList &caCertificates, - const QList &certificateChain, - const QString &hostName) -{ - // This was previously QSslSocketPrivate::verify(). - if (certificateChain.count() <= 0) - return {QSslError(QSslError::UnspecifiedError)}; - - QList errors; - X509_STORE *certStore = q_X509_STORE_new(); - if (!certStore) { - qCWarning(lcTlsBackend) << "Unable to create certificate store"; - errors << QSslError(QSslError::UnspecifiedError); - return errors; - } - const std::unique_ptr storeGuard(certStore, q_X509_STORE_free); - - const QDateTime now = QDateTime::currentDateTimeUtc(); - for (const QSslCertificate &caCertificate : caCertificates) { - // From https://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html: - // - // If several CA certificates matching the name, key identifier, and - // serial number condition are available, only the first one will be - // examined. This may lead to unexpected results if the same CA - // certificate is available with different expiration dates. If a - // ``certificate expired'' verification error occurs, no other - // certificate will be searched. Make sure to not have expired - // certificates mixed with valid ones. - // - // See also: QSslContext::fromConfiguration() - if (caCertificate.expiryDate() >= now) { - q_X509_STORE_add_cert(certStore, reinterpret_cast(caCertificate.handle())); - } - } - - QList lastErrors; - if (!q_X509_STORE_set_ex_data(certStore, 0, &lastErrors)) { - qCWarning(lcTlsBackend) << "Unable to attach external data (error list) to a store"; - errors << QSslError(QSslError::UnspecifiedError); - return errors; - } - - // Register a custom callback to get all verification errors. - q_X509_STORE_set_verify_cb(certStore, qt_X509Callback); - - // Build the chain of intermediate certificates - STACK_OF(X509) *intermediates = nullptr; - if (certificateChain.length() > 1) { - intermediates = (STACK_OF(X509) *) q_OPENSSL_sk_new_null(); - - if (!intermediates) { - errors << QSslError(QSslError::UnspecifiedError); - return errors; - } - - bool first = true; - for (const QSslCertificate &cert : certificateChain) { - if (first) { - first = false; - continue; - } - - q_OPENSSL_sk_push((OPENSSL_STACK *)intermediates, reinterpret_cast(cert.handle())); - } - } - - X509_STORE_CTX *storeContext = q_X509_STORE_CTX_new(); - if (!storeContext) { - errors << QSslError(QSslError::UnspecifiedError); - return errors; - } - std::unique_ptr ctxGuard(storeContext, q_X509_STORE_CTX_free); - - if (!q_X509_STORE_CTX_init(storeContext, certStore, reinterpret_cast(certificateChain[0].handle()), intermediates)) { - errors << QSslError(QSslError::UnspecifiedError); - return errors; - } - - // Now we can actually perform the verification of the chain we have built. - // We ignore the result of this function since we process errors via the - // callback. - (void) q_X509_verify_cert(storeContext); - ctxGuard.reset(); - q_OPENSSL_sk_free((OPENSSL_STACK *)intermediates); - - // Now process the errors - - if (certificateChain[0].isBlacklisted()) - errors << QSslError(QSslError::CertificateBlacklisted, certificateChain[0]); - - // Check the certificate name against the hostname if one was specified - if (!hostName.isEmpty() && !QSslSocketPrivate::isMatchingHostname(certificateChain[0], hostName)) { - // No matches in common names or alternate names. - QSslError error(QSslError::HostNameMismatch, certificateChain[0]); - errors << error; - } - - // Translate errors from the error list into QSslErrors. - errors.reserve(errors.size() + lastErrors.size()); - for (const auto &error : qAsConst(lastErrors)) - errors << openSSLErrorToQSslError(error.code, certificateChain.value(error.depth)); - - return errors; -} - -QList X509CertificateOpenSSL::certificatesFromPem(const QByteArray &pem, int count) -{ - QList certificates; - - int offset = 0; - while (count == -1 || certificates.size() < count) { - int startPos = pem.indexOf(BEGINCERTSTRING, offset); - if (startPos == -1) - break; - startPos += sizeof(BEGINCERTSTRING) - 1; - if (!matchLineFeed(pem, &startPos)) - break; - - int endPos = pem.indexOf(ENDCERTSTRING, startPos); - if (endPos == -1) - break; - - offset = endPos + sizeof(ENDCERTSTRING) - 1; - if (offset < pem.size() && !matchLineFeed(pem, &offset)) - break; - - QByteArray decoded = QByteArray::fromBase64( - QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); - const unsigned char *data = (const unsigned char *)decoded.data(); - - if (X509 *x509 = q_d2i_X509(nullptr, &data, decoded.size())) { - certificates << certificateFromX509(x509); - q_X509_free(x509); - } - } - - return certificates; -} - -QList X509CertificateOpenSSL::certificatesFromDer(const QByteArray &der, int count) -{ - QList certificates; - - const unsigned char *data = (const unsigned char *)der.data(); - int size = der.size(); - - while (size > 0 && (count == -1 || certificates.size() < count)) { - if (X509 *x509 = q_d2i_X509(nullptr, &data, size)) { - certificates << certificateFromX509(x509); - q_X509_free(x509); - } else { - break; - } - size -= ((const char *)data - der.data()); - } - - return certificates; -} - -bool X509CertificateOpenSSL::importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, - QList *caCertificates, - const QByteArray &passPhrase) -{ - // These are required - Q_ASSERT(device); - Q_ASSERT(key); - Q_ASSERT(cert); - - // Read the file into a BIO - QByteArray pkcs12data = device->readAll(); - if (pkcs12data.size() == 0) - return false; - - BIO *bio = q_BIO_new_mem_buf(const_cast(pkcs12data.constData()), pkcs12data.size()); - if (!bio) { - qCWarning(lcTlsBackend, "BIO_new_mem_buf returned null"); - return false; - } - const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); - - // Create the PKCS#12 object - PKCS12 *p12 = q_d2i_PKCS12_bio(bio, nullptr); - if (!p12) { - qCWarning(lcTlsBackend, "Unable to read PKCS#12 structure, %s", - q_ERR_error_string(q_ERR_get_error(), nullptr)); - return false; - } - const auto p12Raii = qScopeGuard([p12]{q_PKCS12_free(p12);}); - - // Extract the data - EVP_PKEY *pkey = nullptr; - X509 *x509 = nullptr; - STACK_OF(X509) *ca = nullptr; - - if (!q_PKCS12_parse(p12, passPhrase.constData(), &pkey, &x509, &ca)) { - qCWarning(lcTlsBackend, "Unable to parse PKCS#12 structure, %s", - q_ERR_error_string(q_ERR_get_error(), nullptr)); - return false; - } - - const auto x509Raii = qScopeGuard([x509]{q_X509_free(x509);}); - const auto keyRaii = qScopeGuard([pkey]{q_EVP_PKEY_free(pkey);}); - const auto caRaii = qScopeGuard([ca] { - q_OPENSSL_sk_pop_free(reinterpret_cast(ca), - reinterpret_cast(q_X509_free)); - }); - - // Convert to Qt types - auto *tlsKey = QTlsBackend::backend(*key); - if (!tlsKey || !tlsKey->fromEVP_PKEY(pkey)) { - qCWarning(lcTlsBackend, "Unable to convert private key"); - return false; - } - - *cert = certificateFromX509(x509); - - if (caCertificates) - *caCertificates = stackOfX509ToQSslCertificates(ca); - - return true; -} - -QSslError X509CertificateOpenSSL::openSSLErrorToQSslError(int errorCode, const QSslCertificate &cert) -{ - QSslError error; - switch (errorCode) { - case X509_V_OK: - // X509_V_OK is also reported if the peer had no certificate. - break; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: - error = QSslError(QSslError::UnableToGetIssuerCertificate, cert); break; - case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: - error = QSslError(QSslError::UnableToDecryptCertificateSignature, cert); break; - case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: - error = QSslError(QSslError::UnableToDecodeIssuerPublicKey, cert); break; - case X509_V_ERR_CERT_SIGNATURE_FAILURE: - error = QSslError(QSslError::CertificateSignatureFailed, cert); break; - case X509_V_ERR_CERT_NOT_YET_VALID: - error = QSslError(QSslError::CertificateNotYetValid, cert); break; - case X509_V_ERR_CERT_HAS_EXPIRED: - error = QSslError(QSslError::CertificateExpired, cert); break; - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: - error = QSslError(QSslError::InvalidNotBeforeField, cert); break; - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: - error = QSslError(QSslError::InvalidNotAfterField, cert); break; - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - error = QSslError(QSslError::SelfSignedCertificate, cert); break; - case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - error = QSslError(QSslError::SelfSignedCertificateInChain, cert); break; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - error = QSslError(QSslError::UnableToGetLocalIssuerCertificate, cert); break; - case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: - error = QSslError(QSslError::UnableToVerifyFirstCertificate, cert); break; - case X509_V_ERR_CERT_REVOKED: - error = QSslError(QSslError::CertificateRevoked, cert); break; - case X509_V_ERR_INVALID_CA: - error = QSslError(QSslError::InvalidCaCertificate, cert); break; - case X509_V_ERR_PATH_LENGTH_EXCEEDED: - error = QSslError(QSslError::PathLengthExceeded, cert); break; - case X509_V_ERR_INVALID_PURPOSE: - error = QSslError(QSslError::InvalidPurpose, cert); break; - case X509_V_ERR_CERT_UNTRUSTED: - error = QSslError(QSslError::CertificateUntrusted, cert); break; - case X509_V_ERR_CERT_REJECTED: - error = QSslError(QSslError::CertificateRejected, cert); break; - default: - error = QSslError(QSslError::UnspecifiedError, cert); break; - } - return error; -} - -void X509CertificateOpenSSL::parseExtensions() -{ - extensions.clear(); - - if (!x509) - return; - - int count = q_X509_get_ext_count(x509); - if (count <= 0) - return; - - extensions.reserve(count); - - for (int i = 0; i < count; i++) { - X509_EXTENSION *ext = q_X509_get_ext(x509, i); - if (!ext) { - qCWarning(lcTlsBackend) << "Invalid (nullptr) extension at index" << i; - continue; - } - - extensions << convertExtension(ext); - } - - // Converting an extension may result in an error(s), clean them up: - QTlsBackendOpenSSL::clearErrorQueue(); -} - -X509CertificateBase::X509CertificateExtension X509CertificateOpenSSL::convertExtension(X509_EXTENSION *ext) -{ - Q_ASSERT(ext); - - X509CertificateExtension result; - - ASN1_OBJECT *obj = q_X509_EXTENSION_get_object(ext); - if (!obj) - return result; - - result.oid = QString::fromUtf8(asn1ObjectId(obj)); - result.name = QString::fromUtf8(asn1ObjectName(obj)); - - result.critical = bool(q_X509_EXTENSION_get_critical(ext)); - - // Lets see if we have custom support for this one - QVariant extensionValue = x509ExtensionToValue(ext); - if (extensionValue.isValid()) { - result.value = extensionValue; - result.supported = true; - return result; - } - - extensionValue = x509UnknownExtensionToValue(ext); - if (extensionValue.isValid()) - result.value = extensionValue; - - result.supported = false; - - return result; -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE diff --git a/src/network/ssl/qx509_openssl_p.h b/src/network/ssl/qx509_openssl_p.h deleted file mode 100644 index 5bc5ad63f5..0000000000 --- a/src/network/ssl/qx509_openssl_p.h +++ /dev/null @@ -1,133 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QX509_OPENSSL_P_H -#define QX509_OPENSSL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include - -#include -#include - -#include -#include -#include - -#include - -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -// TLSTODO: This class is essentially what qsslcertificate_openssl.cpp -// contains - OpenSSL-based version of QSslCertificatePrivate. Remove -// this comment when plugins are ready. -class X509CertificateOpenSSL final : public X509CertificateBase -{ -public: - X509CertificateOpenSSL(); - ~X509CertificateOpenSSL(); - - // TLSTODO: in future may become movable/copyable (ref-counted based - // OpenSSL's X509 implementation). - - bool isEqual(const X509Certificate &rhs) const override; - bool isSelfSigned() const override; - QMultiMap subjectAlternativeNames() const override; - TlsKey *publicKey() const override; - - QByteArray toPem() const override; - QByteArray toDer() const override; - QString toText() const override; - Qt::HANDLE handle() const override; - - size_t hash(size_t seed) const noexcept override; - - // TLSTODO: these are needed by qsslsocket_openssl and later, by - // TLS code inside OpenSSL plugin. Remove this comment when - // plugins are ready. - static QSslCertificate certificateFromX509(X509 *x); - static QList stackOfX509ToQSslCertificates(STACK_OF(X509) *x509); - static QSslErrorEntry errorEntryFromStoreContext(X509_STORE_CTX *ctx); - - // TLSTODO: remove this comment when plugins are in place. This is what QSslSocketPrivate::verify() - // in qsslsocket_openssl.cpp is (was) doing (in the past). - static QList verify(const QList &chain, const QString &hostName); - static QList verify(const QList &caCertificates, - const QList &certificateChain, - const QString &hostName); - - static QList certificatesFromPem(const QByteArray &pem, int count); - static QList certificatesFromDer(const QByteArray &der, int count); - static bool importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, - QList *caCertificates, - const QByteArray &passPhrase); - - static QSslError openSSLErrorToQSslError(int errorCode, const QSslCertificate &cert); -private: - void parseExtensions(); - static X509CertificateExtension convertExtension(X509_EXTENSION *ext); - - X509 *x509 = nullptr; - - Q_DISABLE_COPY_MOVE(X509CertificateOpenSSL) -}; - -extern "C" int qt_X509Callback(int ok, X509_STORE_CTX *ctx); - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QX509_OPENSSL_P_H diff --git a/src/network/ssl/qx509_schannel.cpp b/src/network/ssl/qx509_schannel.cpp deleted file mode 100644 index a7371e0d97..0000000000 --- a/src/network/ssl/qx509_schannel.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qtlskey_schannel_p.h" -#include "qsslcertificate_p.h" -#include "qx509_schannel_p.h" - -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -X509CertificateSchannel::X509CertificateSchannel() = default; - -X509CertificateSchannel::~X509CertificateSchannel() -{ - if (certificateContext) - CertFreeCertificateContext(certificateContext); -} - -TlsKey *X509CertificateSchannel::publicKey() const -{ - auto key = std::make_unique(QSsl::PublicKey); - if (publicKeyAlgorithm != QSsl::Opaque) - key->decodeDer(QSsl::PublicKey, publicKeyAlgorithm, publicKeyDerData, {}, false); - - return key.release(); -} - -Qt::HANDLE X509CertificateSchannel::handle() const -{ - return Qt::HANDLE(certificateContext); -} - -QSslCertificate X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext) -{ - QByteArray derData = QByteArray((const char *)certificateContext->pbCertEncoded, - certificateContext->cbCertEncoded); - QSslCertificate certificate(derData, QSsl::Der); - - auto *certBackend = QTlsBackend::backend(certificate); - Q_ASSERT(certBackend); - certBackend->certificateContext = CertDuplicateCertificateContext(certificateContext); - return certificate; -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - diff --git a/src/network/ssl/qx509_schannel_p.h b/src/network/ssl/qx509_schannel_p.h deleted file mode 100644 index 3b5d567c7b..0000000000 --- a/src/network/ssl/qx509_schannel_p.h +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QX509_SCHANNEL_P_H -#define QX509_SCHANNEL_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include - -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -class X509CertificateSchannel final : public X509CertificateGeneric -{ -public: - X509CertificateSchannel(); - ~X509CertificateSchannel(); - - TlsKey *publicKey() const override; - Qt::HANDLE handle() const override; - - static QSslCertificate QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext); -private: - const CERT_CONTEXT *certificateContext = nullptr; - - Q_DISABLE_COPY_MOVE(X509CertificateSchannel); -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QX509_SCHANNEL_P_H diff --git a/src/network/ssl/qx509_st.cpp b/src/network/ssl/qx509_st.cpp deleted file mode 100644 index 737b15cef8..0000000000 --- a/src/network/ssl/qx509_st.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#include "qtlskey_st_p.h" -#include "qx509_st_p.h" - -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -TlsKey *X509CertificateSecureTransport::publicKey() const -{ - auto key = std::make_unique(QSsl::PublicKey); - if (publicKeyAlgorithm != QSsl::Opaque) - key->decodeDer(QSsl::PublicKey, publicKeyAlgorithm, publicKeyDerData, {}, false); - - return key.release(); -} - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - diff --git a/src/network/ssl/qx509_st_p.h b/src/network/ssl/qx509_st_p.h deleted file mode 100644 index 8c3969442c..0000000000 --- a/src/network/ssl/qx509_st_p.h +++ /dev/null @@ -1,74 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QX509_ST_P_H -#define QX509_ST_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include - -#include - -#include - -QT_BEGIN_NAMESPACE - -namespace QTlsPrivate { - -class X509CertificateSecureTransport final : public X509CertificateGeneric -{ -public: - TlsKey *publicKey() const override; -}; - -} // namespace QTlsPrivate - -QT_END_NAMESPACE - -#endif // QX509_ST_P_H diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index 4aea4aad94..92aeafb768 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -22,4 +22,5 @@ if(TARGET Qt::PrintSupport) endif() if (TARGET Qt::Network) add_subdirectory(networkinformationbackends) + add_subdirectory(tls) endif() diff --git a/src/plugins/tls/CMakeLists.txt b/src/plugins/tls/CMakeLists.txt new file mode 100644 index 0000000000..a17cda9594 --- /dev/null +++ b/src/plugins/tls/CMakeLists.txt @@ -0,0 +1,13 @@ +if(QT_FEATURE_securetransport) + add_subdirectory(securetransport) +endif() + +if (QT_FEATURE_openssl OR QT_FEATURE_openssl_linked) + add_subdirectory(openssl) +endif() + +if (QT_FEATURE_schannel) + add_subdirectory(schannel) +endif() + +add_subdirectory(certonly) diff --git a/src/plugins/tls/certonly/CMakeLists.txt b/src/plugins/tls/certonly/CMakeLists.txt new file mode 100644 index 0000000000..8a1a5c0691 --- /dev/null +++ b/src/plugins/tls/certonly/CMakeLists.txt @@ -0,0 +1,16 @@ +qt_internal_add_plugin(QTlsBackendCertOnly + OUTPUT_NAME certonlybackend + CLASS_NAME QTlsBackendCertOnly + TYPE tls + SOURCES + ../shared/qx509_base_p.h + ../shared/qx509_base.cpp + ../shared/qx509_generic_p.h + ../shared/qx509_generic.cpp + ../shared/qasn1element_p.h + ../shared/qasn1element.cpp + qtlsbackend_cert.cpp + qtlsbackend_cert_p.h + PUBLIC_LIBRARIES + Qt::NetworkPrivate +) diff --git a/src/plugins/tls/certonly/qtlsbackend_cert.cpp b/src/plugins/tls/certonly/qtlsbackend_cert.cpp new file mode 100644 index 0000000000..c81eb0252e --- /dev/null +++ b/src/plugins/tls/certonly/qtlsbackend_cert.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qtlsbackend_cert_p.h" + +#include "../shared/qx509_generic_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.cert-only"); + +QString QTlsBackendCertOnly::backendName() const +{ + return builtinBackendNames[nameIndexCertOnly]; +} + + +QList QTlsBackendCertOnly::supportedProtocols() const +{ + return {}; +} + +QList QTlsBackendCertOnly::supportedFeatures() const +{ + return {}; +} + +QList QTlsBackendCertOnly::implementedClasses() const +{ + QList classes; + classes << QSsl::ImplementedClass::Certificate; + + return classes; +} + +QTlsPrivate::X509Certificate *QTlsBackendCertOnly::createCertificate() const +{ + return new QTlsPrivate::X509CertificateGeneric; +} + +QTlsPrivate::X509PemReaderPtr QTlsBackendCertOnly::X509PemReader() const +{ + return QTlsPrivate::X509CertificateGeneric::certificatesFromPem; +} + +QTlsPrivate::X509DerReaderPtr QTlsBackendCertOnly::X509DerReader() const +{ + return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; +} + +QT_END_NAMESPACE + diff --git a/src/plugins/tls/certonly/qtlsbackend_cert_p.h b/src/plugins/tls/certonly/qtlsbackend_cert_p.h new file mode 100644 index 0000000000..ddbe02e5a9 --- /dev/null +++ b/src/plugins/tls/certonly/qtlsbackend_cert_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLSBACKEND_CERT_P_H +#define QTLSBACKEND_CERT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QTlsBackendCertOnly final : public QTlsBackend +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QTlsBackend_iid) + Q_INTERFACES(QTlsBackend) +private: + QString backendName() const override; + + QList supportedProtocols() const override; + QList supportedFeatures() const override; + QList implementedClasses() const override; + + QTlsPrivate::X509Certificate *createCertificate() const override; + QTlsPrivate::X509PemReaderPtr X509PemReader() const override; + QTlsPrivate::X509DerReaderPtr X509DerReader() const override; +}; + +QT_END_NAMESPACE + +#endif // QTLSBACKEND_CERT_P_H diff --git a/src/plugins/tls/openssl/CMakeLists.txt b/src/plugins/tls/openssl/CMakeLists.txt new file mode 100644 index 0000000000..3bcad2752e --- /dev/null +++ b/src/plugins/tls/openssl/CMakeLists.txt @@ -0,0 +1,51 @@ +qt_internal_add_plugin(QTlsBackendOpenSSL + OUTPUT_NAME opensslbackend + CLASS_NAME QTlsBackendOpenSSL + TYPE tls + SOURCES + ../shared/qx509_base.cpp ../shared/qx509_base_p.h + ../shared/qtlskey_base.cpp ../shared/qtlskey_base_p.h + ../shared/qasn1element.cpp ../shared/qasn1element_p.h + qtlsbackend_openssl.cpp qtlsbackend_openssl_p.h + qx509_openssl.cpp qx509_openssl_p.h + qtlskey_openssl.cpp qtlskey_openssl_p.h + qtls_openssl.cpp qtls_openssl_p.h + qssldiffiehellmanparameters_openssl.cpp + qsslcontext_openssl.cpp qsslcontext_openssl_p.h + qsslsocket_openssl_symbols.cpp qsslsocket_openssl_symbols_p.h + qopenssl_p.h + PUBLIC_LIBRARIES + Qt::NetworkPrivate + Qt::CorePrivate + DEFINES + OPENSSL_API_COMPAT=0x10100000L +) + +qt_internal_extend_target(QTlsBackendOpenSSL CONDITION QT_FEATURE_dtls + SOURCES + qdtls_openssl.cpp qdtls_openssl_p.h + ../shared/qdtls_base.cpp ../shared/qdtls_base_p.h +) + +qt_internal_extend_target(QTlsBackendOpenSSL CONDITION APPLE + SOURCES + ../shared/qsslsocket_mac_shared.cpp + LIBRARIES + ${FWCoreFoundation} + ${FWSecurity} +) + +qt_internal_extend_target(QTlsBackendOpenSSL CONDITION ANDROID AND NOT ANDROID_EMBEDDED + SOURCES + qsslsocket_openssl_android.cpp +) + +qt_internal_extend_target(QTlsBackendOpenSSL CONDITION QT_FEATURE_openssl + AND QT_FEATURE_ssl AND WIN32 + SOURCES + qwindowscarootfetcher.cpp qwindowscarootfetcher_p.h + ../shared/qwincrypt_p.h + LIBRARIES + crypt32 +) + diff --git a/src/plugins/tls/openssl/qdtls_openssl.cpp b/src/plugins/tls/openssl/qdtls_openssl.cpp new file mode 100644 index 0000000000..55a82f7fd4 --- /dev/null +++ b/src/plugins/tls/openssl/qdtls_openssl.cpp @@ -0,0 +1,1454 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include + +#include "qsslsocket_openssl_symbols_p.h" +#include "qdtls_openssl_p.h" +#include "qx509_openssl_p.h" + +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +#define QT_DTLS_VERBOSE 0 + +#if QT_DTLS_VERBOSE + +#define qDtlsWarning(arg) qWarning(arg) +#define qDtlsDebug(arg) qDebug(arg) + +#else + +#define qDtlsWarning(arg) +#define qDtlsDebug(arg) + +#endif // QT_DTLS_VERBOSE + +namespace dtlsutil +{ + +QByteArray cookie_for_peer(SSL *ssl) +{ + Q_ASSERT(ssl); + + // SSL_get_rbio does not increment the reference count + BIO *readBIO = q_SSL_get_rbio(ssl); + if (!readBIO) { + qCWarning(lcTlsBackend, "No BIO (dgram) found in SSL object"); + return {}; + } + + auto listener = static_cast(q_BIO_get_app_data(readBIO)); + if (!listener) { + qCWarning(lcTlsBackend, "BIO_get_app_data returned invalid (nullptr) value"); + return {}; + } + + const QHostAddress peerAddress(listener->remoteAddress); + const quint16 peerPort(listener->remotePort); + QByteArray peerData; + if (peerAddress.protocol() == QAbstractSocket::IPv6Protocol) { + const Q_IPV6ADDR sin6_addr(peerAddress.toIPv6Address()); + peerData.resize(int(sizeof sin6_addr + sizeof peerPort)); + char *dst = peerData.data(); + std::memcpy(dst, &peerPort, sizeof peerPort); + dst += sizeof peerPort; + std::memcpy(dst, &sin6_addr, sizeof sin6_addr); + } else if (peerAddress.protocol() == QAbstractSocket::IPv4Protocol) { + const quint32 sin_addr(peerAddress.toIPv4Address()); + peerData.resize(int(sizeof sin_addr + sizeof peerPort)); + char *dst = peerData.data(); + std::memcpy(dst, &peerPort, sizeof peerPort); + dst += sizeof peerPort; + std::memcpy(dst, &sin_addr, sizeof sin_addr); + } else { + Q_UNREACHABLE(); + } + + return peerData; +} + +struct FallbackCookieSecret +{ + FallbackCookieSecret() + { + key.resize(32); + const int status = q_RAND_bytes(reinterpret_cast(key.data()), + key.size()); + if (status <= 0) + key.clear(); + } + + QByteArray key; + + Q_DISABLE_COPY_MOVE(FallbackCookieSecret) +}; + +QByteArray fallbackSecret() +{ + static const FallbackCookieSecret generator; + return generator.key; +} + +int next_timeoutMs(SSL *tlsConnection) +{ + Q_ASSERT(tlsConnection); + timeval timeLeft = {}; + q_DTLSv1_get_timeout(tlsConnection, &timeLeft); + return timeLeft.tv_sec * 1000; +} + + +void delete_connection(SSL *ssl) +{ + // The 'deleter' for QSharedPointer. + if (ssl) + q_SSL_free(ssl); +} + +void delete_BIO_ADDR(BIO_ADDR *bio) +{ + // A deleter for QSharedPointer + if (bio) + q_BIO_ADDR_free(bio); +} + +void delete_bio_method(BIO_METHOD *method) +{ + // The 'deleter' for QSharedPointer. + if (method) + q_BIO_meth_free(method); +} + +// The path MTU discovery is non-trivial: it's a mix of getsockopt/setsockopt +// (IP_MTU/IP6_MTU/IP_MTU_DISCOVER) and fallback MTU values. It's not +// supported on all platforms, worse so - imposes specific requirements on +// underlying UDP socket etc. So for now, we either try a user-proposed MTU +// hint or rely on our own fallback value. As a fallback mtu OpenSSL uses 576 +// for IPv4 and 1280 for IPv6 (RFC 791, RFC 2460). To KIS we use 576. This +// rather small MTU value does not affect the size that can be read/written +// by QDtls, only a handshake (which is allowed to fragment). +enum class MtuGuess : long +{ + defaultMtu = 576 +}; + +} // namespace dtlsutil + +namespace dtlscallbacks +{ + +extern "C" int q_generate_cookie_callback(SSL *ssl, unsigned char *dst, + unsigned *cookieLength) +{ + if (!ssl || !dst || !cookieLength) { + qCWarning(lcTlsBackend, + "Failed to generate cookie - invalid (nullptr) parameter(s)"); + return 0; + } + + void *generic = q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData); + if (!generic) { + qCWarning(lcTlsBackend, "SSL_get_ex_data returned nullptr, cannot generate cookie"); + return 0; + } + + *cookieLength = 0; + + auto dtls = static_cast(generic); + if (!dtls->secret.size()) + return 0; + + const QByteArray peerData(dtlsutil::cookie_for_peer(ssl)); + if (!peerData.size()) + return 0; + + QMessageAuthenticationCode hmac(dtls->hashAlgorithm, dtls->secret); + hmac.addData(peerData); + const QByteArray cookie = hmac.result(); + Q_ASSERT(cookie.size() >= 0); + // DTLS1_COOKIE_LENGTH is erroneously 256 bytes long, must be 255 - RFC 6347, 4.2.1. + *cookieLength = qMin(DTLS1_COOKIE_LENGTH - 1, cookie.size()); + std::memcpy(dst, cookie.constData(), *cookieLength); + + return 1; +} + +extern "C" int q_verify_cookie_callback(SSL *ssl, const unsigned char *cookie, + unsigned cookieLength) +{ + if (!ssl || !cookie || !cookieLength) { + qCWarning(lcTlsBackend, "Could not verify cookie, invalid (nullptr or zero) parameters"); + return 0; + } + + unsigned char newCookie[DTLS1_COOKIE_LENGTH] = {}; + unsigned newCookieLength = 0; + if (q_generate_cookie_callback(ssl, newCookie, &newCookieLength) != 1) + return 0; + + return newCookieLength == cookieLength + && !std::memcmp(cookie, newCookie, cookieLength); +} + +extern "C" int q_X509DtlsCallback(int ok, X509_STORE_CTX *ctx) +{ + if (!ok) { + // Store the error and at which depth the error was detected. + SSL *ssl = static_cast(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx())); + if (!ssl) { + qCWarning(lcTlsBackend, "X509_STORE_CTX_get_ex_data returned nullptr, handshake failure"); + return 0; + } + + void *generic = q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData); + if (!generic) { + qCWarning(lcTlsBackend, "SSL_get_ex_data returned nullptr, handshake failure"); + return 0; + } + + auto dtls = static_cast(generic); + dtls->x509Errors.append(QTlsPrivate::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); + } + + // Always return 1 (OK) to allow verification to continue. We handle the + // errors gracefully after collecting all errors, after verification has + // completed. + return 1; +} + +extern "C" unsigned q_PSK_client_callback(SSL *ssl, const char *hint, char *identity, + unsigned max_identity_len, unsigned char *psk, + unsigned max_psk_len) +{ + auto *dtls = static_cast(q_SSL_get_ex_data(ssl, + QTlsBackendOpenSSL::s_indexForSSLExtraData)); + if (!dtls) + return 0; + + Q_ASSERT(dtls->dtlsPrivate); + return dtls->dtlsPrivate->pskClientCallback(hint, identity, max_identity_len, psk, max_psk_len); +} + +extern "C" unsigned q_PSK_server_callback(SSL *ssl, const char *identity, unsigned char *psk, + unsigned max_psk_len) +{ + auto *dtls = static_cast(q_SSL_get_ex_data(ssl, + QTlsBackendOpenSSL::s_indexForSSLExtraData)); + if (!dtls) + return 0; + + Q_ASSERT(dtls->dtlsPrivate); + return dtls->dtlsPrivate->pskServerCallback(identity, psk, max_psk_len); +} + +} // namespace dtlscallbacks + +namespace dtlsbio +{ + +extern "C" int q_dgram_read(BIO *bio, char *dst, int bytesToRead) +{ + if (!bio || !dst || bytesToRead <= 0) { + qCWarning(lcTlsBackend, "invalid input parameter(s)"); + return 0; + } + + q_BIO_clear_retry_flags(bio); + + auto dtls = static_cast(q_BIO_get_app_data(bio)); + // It's us who set data, if OpenSSL does too, the logic here is wrong + // then and we have to use BIO_set_app_data then! + Q_ASSERT(dtls); + int bytesRead = 0; + if (dtls->dgram.size()) { + bytesRead = qMin(dtls->dgram.size(), bytesToRead); + std::memcpy(dst, dtls->dgram.constData(), bytesRead); + + if (!dtls->peeking) + dtls->dgram = dtls->dgram.mid(bytesRead); + } else { + bytesRead = -1; + } + + if (bytesRead <= 0) + q_BIO_set_retry_read(bio); + + return bytesRead; +} + +extern "C" int q_dgram_write(BIO *bio, const char *src, int bytesToWrite) +{ + if (!bio || !src || bytesToWrite <= 0) { + qCWarning(lcTlsBackend, "invalid input parameter(s)"); + return 0; + } + + q_BIO_clear_retry_flags(bio); + + auto dtls = static_cast(q_BIO_get_app_data(bio)); + Q_ASSERT(dtls); + if (dtls->writeSuppressed) { + // See the comment in QDtls::startHandshake. + return bytesToWrite; + } + + QUdpSocket *udpSocket = dtls->udpSocket; + Q_ASSERT(udpSocket); + + const QByteArray dgram(QByteArray::fromRawData(src, bytesToWrite)); + qint64 bytesWritten = -1; + if (udpSocket->state() == QAbstractSocket::ConnectedState) { + bytesWritten = udpSocket->write(dgram); + } else { + bytesWritten = udpSocket->writeDatagram(dgram, dtls->remoteAddress, + dtls->remotePort); + } + + if (bytesWritten <= 0) + q_BIO_set_retry_write(bio); + + Q_ASSERT(bytesWritten <= std::numeric_limits::max()); + return int(bytesWritten); +} + +extern "C" int q_dgram_puts(BIO *bio, const char *src) +{ + if (!bio || !src) { + qCWarning(lcTlsBackend, "invalid input parameter(s)"); + return 0; + } + + return q_dgram_write(bio, src, int(std::strlen(src))); +} + +extern "C" long q_dgram_ctrl(BIO *bio, int cmd, long num, void *ptr) +{ + // This is our custom BIO_ctrl. bio.h defines a lot of BIO_CTRL_* + // and BIO_* constants and BIO_somename macros that expands to BIO_ctrl + // call with one of those constants as argument. What exactly BIO_ctrl + // does - depends on the 'cmd' and the type of BIO (so BIO_ctrl does + // not even have a single well-defined value meaning success or failure). + // We handle only the most generic commands - the ones documented for + // BIO_ctrl - and also DGRAM specific ones. And even for them - in most + // cases we do nothing but report a success or some non-error value. + // Documents also state: "Source/sink BIOs return an 0 if they do not + // recognize the BIO_ctrl() operation." - these are covered by 'default' + // label in the switch-statement below. Debug messages in the switch mean: + // 1) we got a command that is unexpected for dgram BIO, or: + // 2) we do not call any function that would lead to OpenSSL using this + // command. + + if (!bio) { + qDebug(lcTlsBackend, "invalid 'bio' parameter (nullptr)"); + return -1; + } + + auto dtls = static_cast(q_BIO_get_app_data(bio)); + Q_ASSERT(dtls); + + switch (cmd) { + // Let's start from the most generic ones, in the order in which they are + // documented (as BIO_ctrl): + case BIO_CTRL_RESET: + // BIO_reset macro. + // From documentation: + // "BIO_reset() normally returns 1 for success and 0 or -1 for failure. + // File BIOs are an exception, they return 0 for success and -1 for + // failure." + // We have nothing to reset and we are not file BIO. + return 1; + case BIO_C_FILE_SEEK: + case BIO_C_FILE_TELL: + qDtlsWarning("Unexpected cmd (BIO_C_FILE_SEEK/BIO_C_FILE_TELL)"); + // These are for BIO_seek, BIO_tell. We are not a file BIO. + // Non-negative return value means success. + return 0; + case BIO_CTRL_FLUSH: + // BIO_flush, nothing to do, we do not buffer any data. + // 0 or -1 means error, 1 - success. + return 1; + case BIO_CTRL_EOF: + qDtlsWarning("Unexpected cmd (BIO_CTRL_EOF)"); + // BIO_eof, 1 means EOF read. Makes no sense for us. + return 0; + case BIO_CTRL_SET_CLOSE: + // BIO_set_close with BIO_CLOSE/BIO_NOCLOSE flags. Documented as + // always returning 1. + // From the documentation: + // "Typically BIO_CLOSE is used in a source/sink BIO to indicate that + // the underlying I/O stream should be closed when the BIO is freed." + // + // QUdpSocket we work with is not BIO's business, ignoring. + return 1; + case BIO_CTRL_GET_CLOSE: + // BIO_get_close. No, never, see the comment above. + return 0; + case BIO_CTRL_PENDING: + qDtlsWarning("Unexpected cmd (BIO_CTRL_PENDING)"); + // BIO_pending. Not used by DTLS/OpenSSL (we are not buffering). + return 0; + case BIO_CTRL_WPENDING: + // No, we have nothing buffered. + return 0; + // The constants below are not documented as a part BIO_ctrl documentation, + // but they are also not type-specific. + case BIO_CTRL_DUP: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DUP)"); + // BIO_dup_state, not used by DTLS (and socket-related BIOs in general). + // For some very specific BIO type this 'cmd' would copy some state + // from 'bio' to (BIO*)'ptr'. 1 means success. + return 0; + case BIO_CTRL_SET_CALLBACK: + qDtlsWarning("Unexpected cmd (BIO_CTRL_SET_CALLBACK)"); + // BIO_set_info_callback. We never call this, OpenSSL does not do this + // on its own (normally it's used if client code wants to have some + // debug information, for example, dumping handshake state via + // BIO_printf from SSL info_callback). + return 0; + case BIO_CTRL_GET_CALLBACK: + qDtlsWarning("Unexpected cmd (BIO_CTRL_GET_CALLBACK)"); + // BIO_get_info_callback. We never call this. + if (ptr) + *static_cast(ptr) = nullptr; + return 0; + case BIO_CTRL_SET: + case BIO_CTRL_GET: + qDtlsWarning("Unexpected cmd (BIO_CTRL_SET/BIO_CTRL_GET)"); + // Somewhat 'documented' as setting/getting IO type. Not used anywhere + // except BIO_buffer_get_num_lines (which contradics 'get IO type'). + // Ignoring. + return 0; + // DGRAM-specific operation, we have to return some reasonable value + // (so far, I've encountered only peek mode switching, connect). + case BIO_CTRL_DGRAM_CONNECT: + // BIO_ctrl_dgram_connect. Not needed. Our 'dtls' already knows + // the peer's address/port. Report success though. + return 1; + case BIO_CTRL_DGRAM_SET_CONNECTED: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_CONNECTED)"); + // BIO_ctrl_dgram_set_connected. We never call it, OpenSSL does + // not call it on its own (so normally it's done by client code). + // Similar to BIO_CTRL_DGRAM_CONNECT, but it also informs the BIO + // that its UDP socket is connected. We never need it though. + return -1; + case BIO_CTRL_DGRAM_SET_RECV_TIMEOUT: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_RECV_TIMEOUT)"); + // Essentially setsockopt with SO_RCVTIMEO, not needed, our sockets + // are non-blocking. + return -1; + case BIO_CTRL_DGRAM_GET_RECV_TIMEOUT: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_GET_RECV_TIMEOUT)"); + // getsockopt with SO_RCVTIMEO, not needed, our sockets are + // non-blocking. ptr is timeval *. + return -1; + case BIO_CTRL_DGRAM_SET_SEND_TIMEOUT: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_SEND_TIMEOUT)"); + // setsockopt, SO_SNDTIMEO, cannot happen. + return -1; + case BIO_CTRL_DGRAM_GET_SEND_TIMEOUT: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_GET_SEND_TIMEOUT)"); + // getsockopt, SO_SNDTIMEO, cannot happen. + return -1; + case BIO_CTRL_DGRAM_GET_RECV_TIMER_EXP: + // BIO_dgram_recv_timedout. No, we are non-blocking. + return 0; + case BIO_CTRL_DGRAM_GET_SEND_TIMER_EXP: + // BIO_dgram_send_timedout. No, we are non-blocking. + return 0; + case BIO_CTRL_DGRAM_MTU_DISCOVER: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_MTU_DISCOVER)"); + // setsockopt, IP_MTU_DISCOVER/IP6_MTU_DISCOVER, to be done + // in QUdpSocket instead. OpenSSL never calls it, only client + // code. + return 1; + case BIO_CTRL_DGRAM_QUERY_MTU: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_QUERY_MTU)"); + // To be done in QUdpSocket instead. + return 1; + case BIO_CTRL_DGRAM_GET_FALLBACK_MTU: + qDtlsWarning("Unexpected command *BIO_CTRL_DGRAM_GET_FALLBACK_MTU)"); + // Without SSL_OP_NO_QUERY_MTU set on SSL, OpenSSL can request for + // fallback MTU after several re-transmissions. + // Should never happen in our case. + return long(dtlsutil::MtuGuess::defaultMtu); + case BIO_CTRL_DGRAM_GET_MTU: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_GET_MTU)"); + return -1; + case BIO_CTRL_DGRAM_SET_MTU: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_SET_MTU)"); + // Should not happen (we don't call BIO_ctrl with this parameter) + // and set MTU on SSL instead. + return -1; // num is mtu and it's a return value meaning success. + case BIO_CTRL_DGRAM_MTU_EXCEEDED: + qDtlsWarning("Unexpected cmd (BIO_CTRL_DGRAM_MTU_EXCEEDED)"); + return 0; + case BIO_CTRL_DGRAM_GET_PEER: + qDtlsDebug("BIO_CTRL_DGRAM_GET_PEER"); + // BIO_dgram_get_peer. We do not return a real address (DTLS is not + // using this address), but let's pretend a success. + switch (dtls->remoteAddress.protocol()) { + case QAbstractSocket::IPv6Protocol: + return sizeof(sockaddr_in6); + case QAbstractSocket::IPv4Protocol: + return sizeof(sockaddr_in); + default: + return -1; + } + case BIO_CTRL_DGRAM_SET_PEER: + // Similar to BIO_CTRL_DGRAM_CONNECTED. + return 1; + case BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT: + // DTLSTODO: I'm not sure yet, how it's used by OpenSSL. + return 1; + case BIO_CTRL_DGRAM_SET_DONT_FRAG: + qDtlsDebug("BIO_CTRL_DGRAM_SET_DONT_FRAG"); + // To be done in QUdpSocket, it's about IP_DONTFRAG etc. + return 1; + case BIO_CTRL_DGRAM_GET_MTU_OVERHEAD: + // AFAIK it's 28 for IPv4 and 48 for IPv6, but let's pretend it's 0 + // so that OpenSSL does not start suddenly fragmenting the first + // client hello (which will result in DTLSv1_listen rejecting it). + return 0; + case BIO_CTRL_DGRAM_SET_PEEK_MODE: + dtls->peeking = num; + return 1; + default:; +#if QT_DTLS_VERBOSE + qWarning() << "Unexpected cmd (" << cmd << ")"; +#endif + } + + return 0; +} + +extern "C" int q_dgram_create(BIO *bio) +{ + + q_BIO_set_init(bio, 1); + // With a custom BIO you'd normally allocate some implementation-specific + // data and append it to this new BIO using BIO_set_data. We don't need + // it and thus q_dgram_destroy below is a noop. + return 1; +} + +extern "C" int q_dgram_destroy(BIO *bio) +{ + Q_UNUSED(bio); + return 1; +} + +const char * const qdtlsMethodName = "qdtlsbio"; + +} // namespace dtlsbio + +namespace dtlsopenssl +{ + +bool DtlsState::init(QDtlsBasePrivate *dtlsBase, QUdpSocket *socket, + const QHostAddress &remote, quint16 port, + const QByteArray &receivedMessage) +{ + Q_ASSERT(dtlsBase); + Q_ASSERT(socket); + + if (!tlsContext.data() && !initTls(dtlsBase)) + return false; + + udpSocket = socket; + + setLinkMtu(dtlsBase); + + dgram = receivedMessage; + remoteAddress = remote; + remotePort = port; + + // SSL_get_rbio does not increment a reference count. + BIO *bio = q_SSL_get_rbio(tlsConnection.data()); + Q_ASSERT(bio); + q_BIO_set_app_data(bio, this); + + return true; +} + +void DtlsState::reset() +{ + tlsConnection.reset(); + tlsContext.reset(); +} + +bool DtlsState::initTls(QDtlsBasePrivate *dtlsBase) +{ + if (tlsContext.data()) + return true; + + if (!QSslSocket::supportsSsl()) + return false; + + if (!initCtxAndConnection(dtlsBase)) + return false; + + if (!initBIO(dtlsBase)) { + tlsConnection.reset(); + tlsContext.reset(); + return false; + } + + return true; +} + +static QString msgFunctionFailed(const char *function) +{ + //: %1: Some function + return QDtls::tr("%1 failed").arg(QLatin1String(function)); +} + +bool DtlsState::initCtxAndConnection(QDtlsBasePrivate *dtlsBase) +{ + Q_ASSERT(dtlsBase); + Q_ASSERT(QSslSocket::supportsSsl()); + + if (dtlsBase->mode == QSslSocket::UnencryptedMode) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + QDtls::tr("Invalid SslMode, SslServerMode or SslClientMode expected")); + return false; + } + + if (!QDtlsBasePrivate::isDtlsProtocol(dtlsBase->dtlsConfiguration.protocol())) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + QDtls::tr("Invalid protocol version, DTLS protocol expected")); + return false; + } + + const bool rootsOnDemand = QTlsBackend::rootLoadingOnDemandAllowed(dtlsBase->dtlsConfiguration); + TlsContext newContext(QSslContext::sharedFromConfiguration(dtlsBase->mode, dtlsBase->dtlsConfiguration, + rootsOnDemand)); + + if (newContext->error() != QSslError::NoError) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, newContext->errorString()); + return false; + } + + TlsConnection newConnection(newContext->createSsl(), dtlsutil::delete_connection); + if (!newConnection.data()) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + msgFunctionFailed("SSL_new")); + return false; + } + + const int set = q_SSL_set_ex_data(newConnection.data(), + QTlsBackendOpenSSL::s_indexForSSLExtraData, + this); + + if (set != 1 && dtlsBase->dtlsConfiguration.peerVerifyMode() != QSslSocket::VerifyNone) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + msgFunctionFailed("SSL_set_ex_data")); + return false; + } + + if (dtlsBase->mode == QSslSocket::SslServerMode) { + if (dtlsBase->dtlsConfiguration.dtlsCookieVerificationEnabled()) + q_SSL_set_options(newConnection.data(), SSL_OP_COOKIE_EXCHANGE); + q_SSL_set_psk_server_callback(newConnection.data(), dtlscallbacks::q_PSK_server_callback); + } else { + q_SSL_set_psk_client_callback(newConnection.data(), dtlscallbacks::q_PSK_client_callback); + } + + tlsContext.swap(newContext); + tlsConnection.swap(newConnection); + + return true; +} + +bool DtlsState::initBIO(QDtlsBasePrivate *dtlsBase) +{ + Q_ASSERT(dtlsBase); + Q_ASSERT(tlsContext.data() && tlsConnection.data()); + + BioMethod customMethod(q_BIO_meth_new(BIO_TYPE_DGRAM, dtlsbio::qdtlsMethodName), + dtlsutil::delete_bio_method); + if (!customMethod.data()) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + msgFunctionFailed("BIO_meth_new")); + return false; + } + + BIO_METHOD *biom = customMethod.data(); + q_BIO_meth_set_create(biom, dtlsbio::q_dgram_create); + q_BIO_meth_set_destroy(biom, dtlsbio::q_dgram_destroy); + q_BIO_meth_set_read(biom, dtlsbio::q_dgram_read); + q_BIO_meth_set_write(biom, dtlsbio::q_dgram_write); + q_BIO_meth_set_puts(biom, dtlsbio::q_dgram_puts); + q_BIO_meth_set_ctrl(biom, dtlsbio::q_dgram_ctrl); + + BIO *bio = q_BIO_new(biom); + if (!bio) { + dtlsBase->setDtlsError(QDtlsError::TlsInitializationError, + msgFunctionFailed("BIO_new")); + return false; + } + + q_SSL_set_bio(tlsConnection.data(), bio, bio); + + bioMethod.swap(customMethod); + + return true; +} + +void DtlsState::setLinkMtu(QDtlsBasePrivate *dtlsBase) +{ + Q_ASSERT(dtlsBase); + Q_ASSERT(udpSocket); + Q_ASSERT(tlsConnection.data()); + + long mtu = dtlsBase->mtuHint; + if (!mtu) { + // If the underlying QUdpSocket was connected, getsockopt with + // IP_MTU/IP6_MTU can give us some hint: + bool optionFound = false; + if (udpSocket->state() == QAbstractSocket::ConnectedState) { + const QVariant val(udpSocket->socketOption(QAbstractSocket::PathMtuSocketOption)); + if (val.isValid() && val.canConvert()) + mtu = val.toInt(&optionFound); + } + + if (!optionFound || mtu <= 0) { + // OK, our own initial guess. + mtu = long(dtlsutil::MtuGuess::defaultMtu); + } + } + + // For now, we disable this option. + q_SSL_set_options(tlsConnection.data(), SSL_OP_NO_QUERY_MTU); + + q_DTLS_set_link_mtu(tlsConnection.data(), mtu); +} + +} // namespace dtlsopenssl + +QDtlsClientVerifierOpenSSL::QDtlsClientVerifierOpenSSL() + : QDtlsBasePrivate(QSslSocket::SslServerMode, dtlsutil::fallbackSecret()) +{ +} + +bool QDtlsClientVerifierOpenSSL::verifyClient(QUdpSocket *socket, const QByteArray &dgram, + const QHostAddress &address, quint16 port) +{ + Q_ASSERT(socket); + Q_ASSERT(dgram.size()); + Q_ASSERT(!address.isNull()); + Q_ASSERT(port); + + clearDtlsError(); + verifiedClientHello.clear(); + + if (!dtls.init(this, socket, address, port, dgram)) + return false; + + dtls.secret = secret; + dtls.hashAlgorithm = hashAlgorithm; + + Q_ASSERT(dtls.tlsConnection.data()); + QSharedPointer peer(q_BIO_ADDR_new(), dtlsutil::delete_BIO_ADDR); + if (!peer.data()) { + setDtlsError(QDtlsError::TlsInitializationError, + QDtlsClientVerifier::tr("BIO_ADDR_new failed, ignoring client hello")); + return false; + } + + const int ret = q_DTLSv1_listen(dtls.tlsConnection.data(), peer.data()); + if (ret < 0) { + // Since 1.1 - it's a fatal error (not so in 1.0.2 for non-blocking socket) + setDtlsError(QDtlsError::TlsFatalError, QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + return false; + } + + if (ret > 0) { + verifiedClientHello = dgram; + return true; + } + + return false; +} + +QByteArray QDtlsClientVerifierOpenSSL::verifiedHello() const +{ + return verifiedClientHello; +} + +void QDtlsPrivateOpenSSL::TimeoutHandler::start(int hintMs) +{ + Q_ASSERT(timerId == -1); + timerId = startTimer(hintMs > 0 ? hintMs : timeoutMs, Qt::PreciseTimer); +} + +void QDtlsPrivateOpenSSL::TimeoutHandler::doubleTimeout() +{ + if (timeoutMs * 2 < 60000) + timeoutMs *= 2; + else + timeoutMs = 60000; +} + +void QDtlsPrivateOpenSSL::TimeoutHandler::stop() +{ + if (timerId != -1) { + killTimer(timerId); + timerId = -1; + } +} + +void QDtlsPrivateOpenSSL::TimeoutHandler::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + Q_ASSERT(timerId != -1); + + killTimer(timerId); + timerId = -1; + + Q_ASSERT(dtlsConnection); + dtlsConnection->reportTimeout(); +} + +QDtlsPrivateOpenSSL::QDtlsPrivateOpenSSL(QDtls *qObject, QSslSocket::SslMode side) + : QDtlsBasePrivate(side, dtlsutil::fallbackSecret()), q(qObject) +{ + Q_ASSERT(qObject); + + dtls.dtlsPrivate = this; +} + +QSslSocket::SslMode QDtlsPrivateOpenSSL::cryptographMode() const +{ + return mode; +} + +void QDtlsPrivateOpenSSL::setPeer(const QHostAddress &addr, quint16 port, const QString &name) +{ + remoteAddress = addr; + remotePort = port; + peerVfyName = name; +} + +QHostAddress QDtlsPrivateOpenSSL::peerAddress() const +{ + return remoteAddress; +} + +quint16 QDtlsPrivateOpenSSL::peerPort() const +{ + return remotePort; +} + +void QDtlsPrivateOpenSSL::setPeerVerificationName(const QString &name) +{ + peerVfyName = name; +} + +QString QDtlsPrivateOpenSSL::peerVerificationName() const +{ + return peerVfyName; +} + +void QDtlsPrivateOpenSSL::setDtlsMtuHint(quint16 mtu) +{ + mtuHint = mtu; +} + +quint16 QDtlsPrivateOpenSSL::dtlsMtuHint() const +{ + return mtuHint; +} + +QDtls::HandshakeState QDtlsPrivateOpenSSL::state() const +{ + return handshakeState; +} + +bool QDtlsPrivateOpenSSL::isConnectionEncrypted() const +{ + return connectionEncrypted; +} + +bool QDtlsPrivateOpenSSL::startHandshake(QUdpSocket *socket, const QByteArray &dgram) +{ + Q_ASSERT(socket); + Q_ASSERT(handshakeState == QDtls::HandshakeNotStarted); + + clearDtlsError(); + connectionEncrypted = false; + + if (!dtls.init(this, socket, remoteAddress, remotePort, dgram)) + return false; + + if (mode == QSslSocket::SslServerMode && dtlsConfiguration.dtlsCookieVerificationEnabled()) { + dtls.secret = secret; + dtls.hashAlgorithm = hashAlgorithm; + // Let's prepare the state machine so that message sequence 1 does not + // surprise DTLS/OpenSSL (such a message would be disregarded as + // 'stale or future' in SSL_accept otherwise): + int result = 0; + QSharedPointer peer(q_BIO_ADDR_new(), dtlsutil::delete_BIO_ADDR); + if (!peer.data()) { + setDtlsError(QDtlsError::TlsInitializationError, + QDtls::tr("BIO_ADD_new failed, cannot start handshake")); + return false; + } + + // If it's an invalid/unexpected ClientHello, we don't want to send + // VerifyClientRequest - it's a job of QDtlsClientVerifier - so we + // suppress any attempts to write into socket: + dtls.writeSuppressed = true; + result = q_DTLSv1_listen(dtls.tlsConnection.data(), peer.data()); + dtls.writeSuppressed = false; + + if (result <= 0) { + setDtlsError(QDtlsError::TlsFatalError, + QDtls::tr("Cannot start the handshake, verified client hello expected")); + dtls.reset(); + return false; + } + } + + handshakeState = QDtls::HandshakeInProgress; + opensslErrors.clear(); + tlsErrors.clear(); + + return continueHandshake(socket, dgram); +} + +bool QDtlsPrivateOpenSSL::continueHandshake(QUdpSocket *socket, const QByteArray &dgram) +{ + Q_ASSERT(socket); + + Q_ASSERT(handshakeState == QDtls::HandshakeInProgress); + + clearDtlsError(); + + if (timeoutHandler.data()) + timeoutHandler->stop(); + + if (!dtls.init(this, socket, remoteAddress, remotePort, dgram)) + return false; + + dtls.x509Errors.clear(); + + int result = 0; + if (mode == QSslSocket::SslServerMode) + result = q_SSL_accept(dtls.tlsConnection.data()); + else + result = q_SSL_connect(dtls.tlsConnection.data()); + + // DTLSTODO: Investigate/test if it makes sense - QSslSocket can emit + // peerVerifyError at this point (and thus potentially client code + // will close the underlying TCP connection immediately), but we are using + // QUdpSocket, no connection to close, our verification callback returns 1 + // (verified OK) and this probably means OpenSSL has already sent a reply + // to the server's hello/certificate. + + opensslErrors << dtls.x509Errors; + + if (result <= 0) { + const auto code = q_SSL_get_error(dtls.tlsConnection.data(), result); + switch (code) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // DTLSTODO: to be tested - in principle, if it was the first call to + // continueHandshake and server for some reason discards the client + // hello message (even the verified one) - our 'this' will probably + // forever stay in this strange InProgress state? (the client + // will dully re-transmit the same hello and we discard it again?) + // SSL_get_state can provide more information about state + // machine and we can switch to NotStarted (since we have not + // replied with our hello ...) + if (!timeoutHandler.data()) { + timeoutHandler.reset(new TimeoutHandler); + timeoutHandler->dtlsConnection = this; + } else { + // Back to 1s. + timeoutHandler->resetTimeout(); + } + + timeoutHandler->start(); + + return true; // The handshake is not yet complete. + default: + storePeerCertificates(); + setDtlsError(QDtlsError::TlsFatalError, + QTlsBackendOpenSSL::msgErrorsDuringHandshake()); + dtls.reset(); + handshakeState = QDtls::HandshakeNotStarted; + return false; + } + } + + storePeerCertificates(); + fetchNegotiatedParameters(); + + const bool doVerifyPeer = dtlsConfiguration.peerVerifyMode() == QSslSocket::VerifyPeer + || (dtlsConfiguration.peerVerifyMode() == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); + + if (!doVerifyPeer || verifyPeer() || tlsErrorsWereIgnored()) { + connectionEncrypted = true; + handshakeState = QDtls::HandshakeComplete; + return true; + } + + setDtlsError(QDtlsError::PeerVerificationError, QDtls::tr("Peer verification failed")); + handshakeState = QDtls::PeerVerificationFailed; + return false; +} + + +bool QDtlsPrivateOpenSSL::handleTimeout(QUdpSocket *socket) +{ + Q_ASSERT(socket); + + Q_ASSERT(timeoutHandler.data()); + Q_ASSERT(dtls.tlsConnection.data()); + + clearDtlsError(); + + dtls.udpSocket = socket; + + if (q_DTLSv1_handle_timeout(dtls.tlsConnection.data()) > 0) { + timeoutHandler->doubleTimeout(); + timeoutHandler->start(); + } else { + timeoutHandler->start(dtlsutil::next_timeoutMs(dtls.tlsConnection.data())); + } + + return true; +} + +bool QDtlsPrivateOpenSSL::resumeHandshake(QUdpSocket *socket) +{ + Q_UNUSED(socket); + Q_ASSERT(socket); + Q_ASSERT(handshakeState == QDtls::PeerVerificationFailed); + + clearDtlsError(); + + if (tlsErrorsWereIgnored()) { + handshakeState = QDtls::HandshakeComplete; + connectionEncrypted = true; + tlsErrors.clear(); + tlsErrorsToIgnore.clear(); + return true; + } + + return false; +} + +void QDtlsPrivateOpenSSL::abortHandshake(QUdpSocket *socket) +{ + Q_ASSERT(socket); + Q_ASSERT(handshakeState == QDtls::PeerVerificationFailed + || handshakeState == QDtls::HandshakeInProgress); + + clearDtlsError(); + + if (handshakeState == QDtls::PeerVerificationFailed) { + // Yes, while peer verification failed, we were actually encrypted. + // Let's play it nice - inform our peer about connection shut down. + sendShutdownAlert(socket); + } else { + resetDtls(); + } +} + +void QDtlsPrivateOpenSSL::sendShutdownAlert(QUdpSocket *socket) +{ + Q_ASSERT(socket); + + clearDtlsError(); + + if (connectionEncrypted && !connectionWasShutdown) { + dtls.udpSocket = socket; + Q_ASSERT(dtls.tlsConnection.data()); + q_SSL_shutdown(dtls.tlsConnection.data()); + } + + resetDtls(); +} + +QList QDtlsPrivateOpenSSL::peerVerificationErrors() const +{ + return tlsErrors; +} + +void QDtlsPrivateOpenSSL::ignoreVerificationErrors(const QList &errorsToIgnore) +{ + tlsErrorsToIgnore = errorsToIgnore; +} + +QSslCipher QDtlsPrivateOpenSSL::dtlsSessionCipher() const +{ + return sessionCipher; +} + +QSsl::SslProtocol QDtlsPrivateOpenSSL::dtlsSessionProtocol() const +{ + return sessionProtocol; +} + +qint64 QDtlsPrivateOpenSSL::writeDatagramEncrypted(QUdpSocket *socket, + const QByteArray &dgram) +{ + Q_ASSERT(socket); + Q_ASSERT(dtls.tlsConnection.data()); + Q_ASSERT(connectionEncrypted); + + clearDtlsError(); + + dtls.udpSocket = socket; + const int written = q_SSL_write(dtls.tlsConnection.data(), + dgram.constData(), dgram.size()); + if (written > 0) + return written; + + const unsigned long errorCode = q_ERR_get_error(); + if (!dgram.size() && errorCode == SSL_ERROR_NONE) { + // With OpenSSL <= 1.1 this can happen. For example, DTLS client + // tries to reconnect (while re-using the same address/port) - + // DTLS server drops a message with unexpected epoch but says - no + // error. We leave to client code to resolve such problems until + // OpenSSL provides something better. + return 0; + } + + switch (errorCode) { + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + // We do not set any error/description ... a user can probably re-try + // sending a datagram. + break; + case SSL_ERROR_ZERO_RETURN: + connectionWasShutdown = true; + setDtlsError(QDtlsError::TlsFatalError, QDtls::tr("The DTLS connection has been closed")); + handshakeState = QDtls::HandshakeNotStarted; + dtls.reset(); + break; + case SSL_ERROR_SYSCALL: + case SSL_ERROR_SSL: + default: + // DTLSTODO: we don't know yet what to do. Tests needed - probably, + // some errors can be just ignored (it's UDP, not TCP after all). + // Unlike QSslSocket we do not abort though. + QString description(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + if (socket->error() != QAbstractSocket::UnknownSocketError && description.isEmpty()) { + setDtlsError(QDtlsError::UnderlyingSocketError, socket->errorString()); + } else { + setDtlsError(QDtlsError::TlsFatalError, + QDtls::tr("Error while writing: %1").arg(description)); + } + } + + return -1; +} + +QByteArray QDtlsPrivateOpenSSL::decryptDatagram(QUdpSocket *socket, const QByteArray &tlsdgram) +{ + Q_ASSERT(socket); + Q_ASSERT(tlsdgram.size()); + + Q_ASSERT(dtls.tlsConnection.data()); + Q_ASSERT(connectionEncrypted); + + dtls.dgram = tlsdgram; + dtls.udpSocket = socket; + + clearDtlsError(); + + QByteArray dgram; + dgram.resize(tlsdgram.size()); + const int read = q_SSL_read(dtls.tlsConnection.data(), dgram.data(), + dgram.size()); + + if (read > 0) { + dgram.resize(read); + return dgram; + } + + dgram.clear(); + unsigned long errorCode = q_ERR_get_error(); + if (errorCode == SSL_ERROR_NONE) { + const int shutdown = q_SSL_get_shutdown(dtls.tlsConnection.data()); + if (shutdown & SSL_RECEIVED_SHUTDOWN) + errorCode = SSL_ERROR_ZERO_RETURN; + else + return dgram; + } + + switch (errorCode) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return dgram; + case SSL_ERROR_ZERO_RETURN: + // "The connection was shut down cleanly" ... hmm, whatever, + // needs testing (DTLSTODO). + connectionWasShutdown = true; + setDtlsError(QDtlsError::RemoteClosedConnectionError, + QDtls::tr("The DTLS connection has been shutdown")); + dtls.reset(); + connectionEncrypted = false; + handshakeState = QDtls::HandshakeNotStarted; + return dgram; + case SSL_ERROR_SYSCALL: // some IO error + case SSL_ERROR_SSL: // error in the SSL library + // DTLSTODO: Apparently, some errors can be ignored, for example, + // ECONNRESET etc. This all needs a lot of testing!!! + default: + setDtlsError(QDtlsError::TlsNonFatalError, + QDtls::tr("Error while reading: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return dgram; + } +} + +unsigned QDtlsPrivateOpenSSL::pskClientCallback(const char *hint, char *identity, + unsigned max_identity_len, + unsigned char *psk, + unsigned max_psk_len) +{ + // The code below is taken (with some modifications) from qsslsocket_openssl + // - alas, we cannot simply re-use it, it's in QSslSocketPrivate. + { + QSslPreSharedKeyAuthenticator authenticator; + // Fill in some read-only fields (for client code) + if (hint) { + identityHint.clear(); + identityHint.append(hint); + } + + QTlsBackend::setupClientPskAuth(&authenticator, hint ? identityHint.constData() : nullptr, + hint ? int(std::strlen(hint)) : 0, max_identity_len, max_psk_len); + pskAuthenticator.swap(authenticator); + } + + // Let the client provide the remaining bits... + emit q->pskRequired(&pskAuthenticator); + + // No PSK set? Return now to make the handshake fail + if (pskAuthenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int identityLength = qMin(pskAuthenticator.identity().length(), + pskAuthenticator.maximumIdentityLength()); + std::memcpy(identity, pskAuthenticator.identity().constData(), identityLength); + identity[identityLength] = 0; + + const int pskLength = qMin(pskAuthenticator.preSharedKey().length(), + pskAuthenticator.maximumPreSharedKeyLength()); + std::memcpy(psk, pskAuthenticator.preSharedKey().constData(), pskLength); + + return pskLength; +} + +unsigned QDtlsPrivateOpenSSL::pskServerCallback(const char *identity, unsigned char *psk, + unsigned max_psk_len) +{ + { + QSslPreSharedKeyAuthenticator authenticator; + // Fill in some read-only fields (for the user) + QTlsBackend::setupServerPskAuth(&authenticator, identity, dtlsConfiguration.preSharedKeyIdentityHint(), + max_psk_len); + pskAuthenticator.swap(authenticator); + } + + // Let the client provide the remaining bits... + emit q->pskRequired(&pskAuthenticator); + + // No PSK set? Return now to make the handshake fail + if (pskAuthenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int pskLength = qMin(pskAuthenticator.preSharedKey().length(), + pskAuthenticator.maximumPreSharedKeyLength()); + + std::memcpy(psk, pskAuthenticator.preSharedKey().constData(), pskLength); + + return pskLength; +} + +bool QDtlsPrivateOpenSSL::verifyPeer() +{ + QList errors; + + // Check the whole chain for blacklisting (including root, as we check for + // subjectInfo and issuer) + const auto &peerCertificateChain = dtlsConfiguration.peerCertificateChain(); + for (const QSslCertificate &cert : peerCertificateChain) { + if (QSslCertificatePrivate::isBlacklisted(cert)) + errors << QSslError(QSslError::CertificateBlacklisted, cert); + } + + const auto peerCertificate = dtlsConfiguration.peerCertificate(); + if (peerCertificate.isNull()) { + errors << QSslError(QSslError::NoPeerCertificate); + } else if (mode == QSslSocket::SslClientMode) { + // Check the peer certificate itself. First try the subject's common name + // (CN) as a wildcard, then try all alternate subject name DNS entries the + // same way. + + // QSslSocket has a rather twisted logic: if verificationPeerName + // is empty, we call QAbstractSocket::peerName(), which returns + // either peerName (can be set by setPeerName) or host name + // (can be set as a result of connectToHost). + QString name = peerVfyName; + if (name.isEmpty()) { + Q_ASSERT(dtls.udpSocket); + name = dtls.udpSocket->peerName(); + } + + if (!QTlsPrivate::TlsCryptograph::isMatchingHostname(peerCertificate, name)) + errors << QSslError(QSslError::HostNameMismatch, peerCertificate); + } + + // Translate errors from the error list into QSslErrors + using CertClass = QTlsPrivate::X509CertificateOpenSSL; + errors.reserve(errors.size() + opensslErrors.size()); + for (const auto &error : qAsConst(opensslErrors)) { + const auto value = peerCertificateChain.value(error.depth); + errors << CertClass::openSSLErrorToQSslError(error.code, value); + } + + tlsErrors = errors; + return tlsErrors.isEmpty(); +} + +void QDtlsPrivateOpenSSL::storePeerCertificates() +{ + Q_ASSERT(dtls.tlsConnection.data()); + // Store the peer certificate and chain. For clients, the peer certificate + // chain includes the peer certificate; for servers, it doesn't. Both the + // peer certificate and the chain may be empty if the peer didn't present + // any certificate. + X509 *x509 = q_SSL_get_peer_certificate(dtls.tlsConnection.data()); + const auto peerCertificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); + QTlsBackend::storePeerCertificate(dtlsConfiguration, peerCertificate); + q_X509_free(x509); + + auto peerCertificateChain = dtlsConfiguration.peerCertificateChain(); + if (peerCertificateChain.isEmpty()) { + auto stack = q_SSL_get_peer_cert_chain(dtls.tlsConnection.data()); + peerCertificateChain = QTlsPrivate::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(stack); + if (!peerCertificate.isNull() && mode == QSslSocket::SslServerMode) + peerCertificateChain.prepend(peerCertificate); + QTlsBackend::storePeerCertificateChain(dtlsConfiguration, peerCertificateChain); + } +} + +bool QDtlsPrivateOpenSSL::tlsErrorsWereIgnored() const +{ + // check whether the errors we got are all in the list of expected errors + // (applies only if the method QDtlsConnection::ignoreTlsErrors(const + // QList &errors) was called) + for (const QSslError &error : tlsErrors) { + if (!tlsErrorsToIgnore.contains(error)) + return false; + } + + return !tlsErrorsToIgnore.empty(); +} + +void QDtlsPrivateOpenSSL::fetchNegotiatedParameters() +{ + Q_ASSERT(dtls.tlsConnection.data()); + + if (const SSL_CIPHER *cipher = q_SSL_get_current_cipher(dtls.tlsConnection.data())) + sessionCipher = QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(cipher); + else + sessionCipher = {}; + + // Note: cipher's protocol version will be reported as either TLS 1.0 or + // TLS 1.2, that's how it's set by OpenSSL (and that's what they are?). + + switch (q_SSL_version(dtls.tlsConnection.data())) { + case DTLS1_VERSION: + sessionProtocol = QSsl::DtlsV1_0; + break; + case DTLS1_2_VERSION: + sessionProtocol = QSsl::DtlsV1_2; + break; + default: + qCWarning(lcTlsBackend, "unknown protocol version"); + sessionProtocol = QSsl::UnknownProtocol; + } +} + +void QDtlsPrivateOpenSSL::reportTimeout() +{ + emit q->handshakeTimeout(); +} + +void QDtlsPrivateOpenSSL::resetDtls() +{ + dtls.reset(); + connectionEncrypted = false; + tlsErrors.clear(); + tlsErrorsToIgnore.clear(); + QTlsBackend::clearPeerCertificates(dtlsConfiguration); + connectionWasShutdown = false; + handshakeState = QDtls::HandshakeNotStarted; + sessionCipher = {}; + sessionProtocol = QSsl::UnknownProtocol; +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qdtls_openssl_p.h b/src/plugins/tls/openssl/qdtls_openssl_p.h new file mode 100644 index 0000000000..d10d4ce584 --- /dev/null +++ b/src/plugins/tls/openssl/qdtls_openssl_p.h @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QDTLS_OPENSSL_P_H +#define QDTLS_OPENSSL_P_H + +#include + +#include "qsslcontext_openssl_p.h" +#include "qtlsbackend_openssl_p.h" +#include "qtls_openssl_p.h" +#include "qopenssl_p.h" + +#include "../shared/qdtls_base_p.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_REQUIRE_CONFIG(openssl); +QT_REQUIRE_CONFIG(dtls); + +QT_BEGIN_NAMESPACE + +class QDtlsPrivateOpenSSL; +class QDtlsBasePrivate; +class QUdpSocket; + +namespace dtlsopenssl +{ + +class DtlsState +{ +public: + // Note, bioMethod _must_ outlive BIOs it was used to create. Thus + // the order of declarations here matters. + using BioMethod = QSharedPointer; + BioMethod bioMethod; + + using TlsContext = QSharedPointer; + TlsContext tlsContext; + + using TlsConnection = QSharedPointer; + TlsConnection tlsConnection; + + QByteArray dgram; + + QHostAddress remoteAddress; + quint16 remotePort = 0; + + QList x509Errors; + + long peeking = false; + QUdpSocket *udpSocket = nullptr; + bool writeSuppressed = false; + + bool init(QDtlsBasePrivate *dtlsBase, QUdpSocket *socket, + const QHostAddress &remote, quint16 port, + const QByteArray &receivedMessage); + + void reset(); + + QDtlsPrivateOpenSSL *dtlsPrivate = nullptr; + QByteArray secret; + +#ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; +#else + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha256; +#endif + +private: + + bool initTls(QDtlsBasePrivate *dtlsBase); + bool initCtxAndConnection(QDtlsBasePrivate *dtlsBase); + bool initBIO(QDtlsBasePrivate *dtlsBase); + void setLinkMtu(QDtlsBasePrivate *dtlsBase); +}; + +} // namespace dtlsopenssl + +// The trick with 'right' ancestor in the tree overriding (only once) some shared +// virtual functions is intentional. Too bad MSVC warns me about ... exactly the +// feature of C++ that I want to use. + +QT_WARNING_PUSH +QT_WARNING_DISABLE_MSVC(4250) + +class QDtlsClientVerifierOpenSSL : public QTlsPrivate::DtlsCookieVerifier, public QDtlsBasePrivate +{ +public: + QDtlsClientVerifierOpenSSL(); + + bool verifyClient(QUdpSocket *socket, const QByteArray &dgram, + const QHostAddress &address, quint16 port) override; + QByteArray verifiedHello() const override; + +private: + dtlsopenssl::DtlsState dtls; + QByteArray verifiedClientHello; +}; + +class QDtlsPrivateOpenSSL : public QTlsPrivate::DtlsCryptograph, public QDtlsBasePrivate +{ +public: + + QDtlsPrivateOpenSSL(QDtls *qObject, QSslSocket::SslMode mode); + +private: + + QSslSocket::SslMode cryptographMode() const override; + void setPeer(const QHostAddress &addr, quint16 port, const QString &name) override; + QHostAddress peerAddress() const override; + quint16 peerPort() const override; + void setPeerVerificationName(const QString &name) override; + QString peerVerificationName() const override; + + virtual void setDtlsMtuHint(quint16 mtu) override; + virtual quint16 dtlsMtuHint() const override; + + virtual QDtls::HandshakeState state() const override; + virtual bool isConnectionEncrypted() const override; + + bool startHandshake(QUdpSocket *socket, const QByteArray &datagram) override; + bool continueHandshake(QUdpSocket *socket, const QByteArray &datagram) override; + bool resumeHandshake(QUdpSocket *socket) override; + void abortHandshake(QUdpSocket *socket) override; + bool handleTimeout(QUdpSocket *socket) override; + void sendShutdownAlert(QUdpSocket *socket) override; + + QList peerVerificationErrors() const override; + void ignoreVerificationErrors(const QList &errorsToIgnore) override; + + QSslCipher dtlsSessionCipher() const override; + QSsl::SslProtocol dtlsSessionProtocol() const override; + + qint64 writeDatagramEncrypted(QUdpSocket *socket, const QByteArray &datagram) override; + QByteArray decryptDatagram(QUdpSocket *socket, const QByteArray &tlsdgram) override; + +public: + unsigned pskClientCallback(const char *hint, char *identity, unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len); + unsigned pskServerCallback(const char *identity, unsigned char *psk, + unsigned max_psk_len); + +private: + + bool verifyPeer(); + void storePeerCertificates(); + bool tlsErrorsWereIgnored() const; + void fetchNegotiatedParameters(); + void reportTimeout(); + void resetDtls(); + + QList opensslErrors; + dtlsopenssl::DtlsState dtls; + + // We have to externally handle timeouts since we have non-blocking + // sockets and OpenSSL(DTLS) with non-blocking UDP sockets does not + // know if a timeout has occurred. + struct TimeoutHandler : QObject + { + TimeoutHandler() = default; + + void start(int hintMs = 0); + void doubleTimeout(); + void resetTimeout() {timeoutMs = 1000;} + void stop(); + void timerEvent(QTimerEvent *event) override; + + int timerId = -1; + int timeoutMs = 1000; + + QDtlsPrivateOpenSSL *dtlsConnection = nullptr; + }; + + QDtls *q = nullptr; + QDtls::HandshakeState handshakeState = QDtls::HandshakeNotStarted; + + QList tlsErrors; + QList tlsErrorsToIgnore; + bool connectionEncrypted = false; + // We will initialize it 'lazily', just in case somebody wants to move + // QDtls to another thread. + QScopedPointer timeoutHandler; + bool connectionWasShutdown = false; + QSslPreSharedKeyAuthenticator pskAuthenticator; + QByteArray identityHint; +}; + +QT_WARNING_POP // C4250 + +QT_END_NAMESPACE + +#endif // QDTLS_OPENSSL_P_H diff --git a/src/plugins/tls/openssl/qopenssl_p.h b/src/plugins/tls/openssl/qopenssl_p.h new file mode 100644 index 0000000000..6daf72a2f8 --- /dev/null +++ b/src/plugins/tls/openssl/qopenssl_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#ifndef QSSLSOCKET_OPENSSL_P_H +#define QSSLSOCKET_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include + +#ifdef Q_OS_WIN +#include +#if defined(OCSP_RESPONSE) +#undef OCSP_RESPONSE +#endif +#if defined(X509_NAME) +#undef X509_NAME +#endif +#endif // Q_OS_WIN + +// This file is included in several *.cpp files and provides different +// openssl declarations where they are needed. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +struct QSslErrorEntry { + int code = 0; + int depth = 0; +}; + +Q_DECLARE_TYPEINFO(QSslErrorEntry, Q_PRIMITIVE_TYPE); + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tls/openssl/qsslcontext_openssl.cpp b/src/plugins/tls/openssl/qsslcontext_openssl.cpp new file mode 100644 index 0000000000..c0afc32e47 --- /dev/null +++ b/src/plugins/tls/openssl/qsslcontext_openssl.cpp @@ -0,0 +1,827 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Governikus GmbH & Co. KG. +** Copyright (C) 2016 Richard J. Moore +** 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$ +** +****************************************************************************/ + +#include +#include + +#include "qsslsocket_openssl_symbols_p.h" +#include "qsslcontext_openssl_p.h" +#include "qtlsbackend_openssl_p.h" +#include "qopenssl_p.h" + +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(bool, forceSecurityLevel) + +namespace QTlsPrivate +{ +// These callback functions are defined in qtls_openssl.cpp. +extern "C" int q_X509Callback(int ok, X509_STORE_CTX *ctx); +extern "C" int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx); + +#if QT_CONFIG(ocsp) +extern "C" int qt_OCSP_status_server_callback(SSL *ssl, void *); +#endif // ocsp + +} // namespace QTlsPrivate + +#if QT_CONFIG(dtls) +// defined in qdtls_openssl.cpp: +namespace dtlscallbacks +{ +extern "C" int q_X509DtlsCallback(int ok, X509_STORE_CTX *ctx); +extern "C" int q_generate_cookie_callback(SSL *ssl, unsigned char *dst, + unsigned *cookieLength); +extern "C" int q_verify_cookie_callback(SSL *ssl, const unsigned char *cookie, + unsigned cookieLength); +} +#endif // dtls + +#ifdef TLS1_3_VERSION +extern "C" int q_ssl_sess_set_new_cb(SSL *context, SSL_SESSION *session); +#endif // TLS1_3_VERSION + +static inline QString msgErrorSettingBackendConfig(const QString &why) +{ + return QSslSocket::tr("Error when setting the OpenSSL configuration (%1)").arg(why); +} + +static inline QString msgErrorSettingEllipticCurves(const QString &why) +{ + return QSslSocket::tr("Error when setting the elliptic curves (%1)").arg(why); +} + +long QSslContext::setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions) +{ + long options; + switch (protocol) { + case QSsl::SecureProtocols: + case QSsl::TlsV1_0OrLater: + options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; + break; + case QSsl::TlsV1_1OrLater: + options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; + break; + case QSsl::TlsV1_2OrLater: + options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1; + break; + case QSsl::TlsV1_3OrLater: + options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2; + break; + default: + options = SSL_OP_ALL; + } + + // This option is disabled by default, so we need to be able to clear it + if (sslOptions & QSsl::SslOptionDisableEmptyFragments) + options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + else + options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + +#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION + // This option is disabled by default, so we need to be able to clear it + if (sslOptions & QSsl::SslOptionDisableLegacyRenegotiation) + options &= ~SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; + else + options |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; +#endif + +#ifdef SSL_OP_NO_TICKET + if (sslOptions & QSsl::SslOptionDisableSessionTickets) + options |= SSL_OP_NO_TICKET; +#endif +#ifdef SSL_OP_NO_COMPRESSION + if (sslOptions & QSsl::SslOptionDisableCompression) + options |= SSL_OP_NO_COMPRESSION; +#endif + + if (!(sslOptions & QSsl::SslOptionDisableServerCipherPreference)) + options |= SSL_OP_CIPHER_SERVER_PREFERENCE; + + return options; +} + +QSslContext::QSslContext() + : ctx(nullptr), + pkey(nullptr), + session(nullptr), + m_sessionTicketLifeTimeHint(-1) +{ +} + +QSslContext::~QSslContext() +{ + if (ctx) + // This will decrement the reference count by 1 and free the context eventually when possible + q_SSL_CTX_free(ctx); + + if (pkey) + q_EVP_PKEY_free(pkey); + + if (session) + q_SSL_SESSION_free(session); +} + +QSslContext* QSslContext::fromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, bool allowRootCertOnDemandLoading) +{ + QSslContext *sslContext = new QSslContext(); + initSslContext(sslContext, mode, configuration, allowRootCertOnDemandLoading); + return sslContext; +} + +QSharedPointer QSslContext::sharedFromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, bool allowRootCertOnDemandLoading) +{ + QSharedPointer sslContext = QSharedPointer::create(); + initSslContext(sslContext.data(), mode, configuration, allowRootCertOnDemandLoading); + return sslContext; +} + +QSharedPointer QSslContext::sharedFromPrivateConfiguration(QSslSocket::SslMode mode, QSslConfigurationPrivate *privConfiguration, + bool allowRootCertOnDemandLoading) +{ + return sharedFromConfiguration(mode, privConfiguration, allowRootCertOnDemandLoading); +} + +#ifndef OPENSSL_NO_NEXTPROTONEG + +static int next_proto_cb(SSL *, unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) +{ + QSslContext::NPNContext *ctx = reinterpret_cast(arg); + + // comment out to debug: +// QList supportedVersions; +// for (unsigned int i = 0; i < inlen; ) { +// QByteArray version(reinterpret_cast(&in[i+1]), in[i]); +// supportedVersions << version; +// i += in[i] + 1; +// } + + int proto = q_SSL_select_next_proto(out, outlen, in, inlen, ctx->data, ctx->len); + switch (proto) { + case OPENSSL_NPN_UNSUPPORTED: + ctx->status = QSslConfiguration::NextProtocolNegotiationNone; + break; + case OPENSSL_NPN_NEGOTIATED: + ctx->status = QSslConfiguration::NextProtocolNegotiationNegotiated; + break; + case OPENSSL_NPN_NO_OVERLAP: + ctx->status = QSslConfiguration::NextProtocolNegotiationUnsupported; + break; + default: + qCWarning(lcTlsBackend, "OpenSSL sent unknown NPN status"); + } + + return SSL_TLSEXT_ERR_OK; +} + +QSslContext::NPNContext QSslContext::npnContext() const +{ + return m_npnContext; +} +#endif // !OPENSSL_NO_NEXTPROTONEG + + + +// Needs to be deleted by caller +SSL* QSslContext::createSsl() +{ + SSL* ssl = q_SSL_new(ctx); + q_SSL_clear(ssl); + + if (!session && !sessionASN1().isEmpty() + && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) { + const unsigned char *data = reinterpret_cast(m_sessionASN1.constData()); + session = q_d2i_SSL_SESSION(nullptr, &data, m_sessionASN1.size()); + // 'session' has refcount 1 already, set by the function above + } + + if (session) { + // Try to resume the last session we cached + if (!q_SSL_set_session(ssl, session)) { + qCWarning(lcTlsBackend, "could not set SSL session"); + q_SSL_SESSION_free(session); + session = nullptr; + } + } + +#ifndef OPENSSL_NO_NEXTPROTONEG + QList protocols = sslConfiguration.d.constData()->nextAllowedProtocols; + if (!protocols.isEmpty()) { + m_supportedNPNVersions.clear(); + for (int a = 0; a < protocols.count(); ++a) { + if (protocols.at(a).size() > 255) { + qCWarning(lcTlsBackend) << "TLS NPN extension" << protocols.at(a) + << "is too long and will be ignored."; + continue; + } else if (protocols.at(a).isEmpty()) { + continue; + } + m_supportedNPNVersions.append(protocols.at(a).size()).append(protocols.at(a)); + } + if (m_supportedNPNVersions.size()) { + m_npnContext.data = reinterpret_cast(m_supportedNPNVersions.data()); + m_npnContext.len = m_supportedNPNVersions.count(); + m_npnContext.status = QSslConfiguration::NextProtocolNegotiationNone; + // Callback's type has a parameter 'const unsigned char ** out' + // since it was introduced in 1.0.2. Internally, OpenSSL's own code + // (tests/examples) cast it to unsigned char * (since it's 'out'). + // We just re-use our NPN callback and cast here: + typedef int (*alpn_callback_t) (SSL *, const unsigned char **, unsigned char *, + const unsigned char *, unsigned int, void *); + // With ALPN callback is for a server side only, for a client m_npnContext.status + // will stay in NextProtocolNegotiationNone. + q_SSL_CTX_set_alpn_select_cb(ctx, alpn_callback_t(next_proto_cb), &m_npnContext); + // Client: + q_SSL_set_alpn_protos(ssl, m_npnContext.data, m_npnContext.len); + // And in case our peer does not support ALPN, but supports NPN: + q_SSL_CTX_set_next_proto_select_cb(ctx, next_proto_cb, &m_npnContext); + } + } +#endif // !OPENSSL_NO_NEXTPROTONEG + + return ssl; +} + +// We cache exactly one session here +bool QSslContext::cacheSession(SSL* ssl) +{ + // don't cache the same session again + if (session && session == q_SSL_get_session(ssl)) + return true; + + // decrease refcount of currently stored session + // (this might happen if there are several concurrent handshakes in flight) + if (session) + q_SSL_SESSION_free(session); + + // cache the session the caller gave us and increase reference count + session = q_SSL_get1_session(ssl); + + if (session && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) { + int sessionSize = q_i2d_SSL_SESSION(session, nullptr); + if (sessionSize > 0) { + m_sessionASN1.resize(sessionSize); + unsigned char *data = reinterpret_cast(m_sessionASN1.data()); + if (!q_i2d_SSL_SESSION(session, &data)) + qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); + m_sessionTicketLifeTimeHint = q_SSL_SESSION_get_ticket_lifetime_hint(session); + } + } + + return (session != nullptr); +} + +QByteArray QSslContext::sessionASN1() const +{ + return m_sessionASN1; +} + +void QSslContext::setSessionASN1(const QByteArray &session) +{ + m_sessionASN1 = session; +} + +int QSslContext::sessionTicketLifeTimeHint() const +{ + return m_sessionTicketLifeTimeHint; +} + +void QSslContext::forceAutoTestSecurityLevel() +{ + *forceSecurityLevel() = true; +} + +QSslError::SslError QSslContext::error() const +{ + return errorCode; +} + +QString QSslContext::errorString() const +{ + return errorStr; +} + +void QSslContext::initSslContext(QSslContext *sslContext, QSslSocket::SslMode mode, + const QSslConfiguration &configuration, + bool allowRootCertOnDemandLoading) +{ + sslContext->sslConfiguration = configuration; + sslContext->errorCode = QSslError::NoError; + + bool client = (mode == QSslSocket::SslClientMode); + + bool reinitialized = false; + bool unsupportedProtocol = false; + bool isDtls = false; +init_context: + switch (sslContext->sslConfiguration.protocol()) { + case QSsl::DtlsV1_0: + case QSsl::DtlsV1_0OrLater: + case QSsl::DtlsV1_2: + case QSsl::DtlsV1_2OrLater: +#if QT_CONFIG(dtls) + isDtls = true; + sslContext->ctx = q_SSL_CTX_new(client ? q_DTLS_client_method() : q_DTLS_server_method()); +#else // dtls + sslContext->ctx = nullptr; + unsupportedProtocol = true; + qCWarning(lcTlsBackend, "DTLS protocol requested, but feature 'dtls' is disabled"); +#endif // dtls + break; + case QSsl::TlsV1_3: + case QSsl::TlsV1_3OrLater: +#if !defined(TLS1_3_VERSION) + qCWarning(lcTlsBackend, "TLS 1.3 is not supported"); + sslContext->ctx = nullptr; + unsupportedProtocol = true; + break; +#endif // TLS1_3_VERSION + default: + // The ssl options will actually control the supported methods + sslContext->ctx = q_SSL_CTX_new(client ? q_TLS_client_method() : q_TLS_server_method()); + } + + if (!sslContext->ctx) { + // After stopping Flash 10 the SSL library loses its ciphers. Try re-adding them + // by re-initializing the library. + if (!reinitialized) { + reinitialized = true; + if (q_OPENSSL_init_ssl(0, nullptr) == 1) + goto init_context; + } + + sslContext->errorStr = QSslSocket::tr("Error creating SSL context (%1)").arg( + unsupportedProtocol ? QSslSocket::tr("unsupported protocol") : QTlsBackendOpenSSL::getErrorsFromOpenSsl() + ); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // A nasty hacked OpenSSL using a level that will make our auto-tests fail: + if (q_SSL_CTX_get_security_level(sslContext->ctx) > 1 && *forceSecurityLevel()) + q_SSL_CTX_set_security_level(sslContext->ctx, 1); + + const long anyVersion = +#if QT_CONFIG(dtls) + isDtls ? DTLS_ANY_VERSION : TLS_ANY_VERSION; +#else + TLS_ANY_VERSION; +#endif // dtls + long minVersion = anyVersion; + long maxVersion = anyVersion; + + switch (sslContext->sslConfiguration.protocol()) { + case QSsl::TlsV1_0: + minVersion = TLS1_VERSION; + maxVersion = TLS1_VERSION; + break; + case QSsl::TlsV1_1: + minVersion = TLS1_1_VERSION; + maxVersion = TLS1_1_VERSION; + break; + case QSsl::TlsV1_2: + minVersion = TLS1_2_VERSION; + maxVersion = TLS1_2_VERSION; + break; + case QSsl::TlsV1_3: +#ifdef TLS1_3_VERSION + minVersion = TLS1_3_VERSION; + maxVersion = TLS1_3_VERSION; +#else + // This protocol is not supported by OpenSSL 1.1 and we handle + // it as an error (see the code above). + Q_UNREACHABLE(); +#endif // TLS1_3_VERSION + break; + // Ranges: + case QSsl::AnyProtocol: + case QSsl::SecureProtocols: + case QSsl::TlsV1_0OrLater: + minVersion = TLS1_VERSION; + maxVersion = 0; + break; + case QSsl::TlsV1_1OrLater: + minVersion = TLS1_1_VERSION; + maxVersion = 0; + break; + case QSsl::TlsV1_2OrLater: + minVersion = TLS1_2_VERSION; + maxVersion = 0; + break; + case QSsl::DtlsV1_0: + minVersion = DTLS1_VERSION; + maxVersion = DTLS1_VERSION; + break; + case QSsl::DtlsV1_0OrLater: + minVersion = DTLS1_VERSION; + maxVersion = DTLS_MAX_VERSION; + break; + case QSsl::DtlsV1_2: + minVersion = DTLS1_2_VERSION; + maxVersion = DTLS1_2_VERSION; + break; + case QSsl::DtlsV1_2OrLater: + minVersion = DTLS1_2_VERSION; + maxVersion = DTLS_MAX_VERSION; + break; + case QSsl::TlsV1_3OrLater: +#ifdef TLS1_3_VERSION + minVersion = TLS1_3_VERSION; + maxVersion = 0; + break; +#else + // This protocol is not supported by OpenSSL 1.1 and we handle + // it as an error (see the code above). + Q_UNREACHABLE(); + break; +#endif // TLS1_3_VERSION + case QSsl::UnknownProtocol: + break; + } + + if (minVersion != anyVersion + && !q_SSL_CTX_set_min_proto_version(sslContext->ctx, minVersion)) { + sslContext->errorStr = QSslSocket::tr("Error while setting the minimal protocol version"); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + if (maxVersion != anyVersion + && !q_SSL_CTX_set_max_proto_version(sslContext->ctx, maxVersion)) { + sslContext->errorStr = QSslSocket::tr("Error while setting the maximum protocol version"); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // Enable bug workarounds. + const long options = setupOpenSslOptions(configuration.protocol(), configuration.d->sslOptions); + q_SSL_CTX_set_options(sslContext->ctx, options); + + // Tell OpenSSL to release memory early + // http://www.openssl.org/docs/ssl/SSL_CTX_set_mode.html + q_SSL_CTX_set_mode(sslContext->ctx, SSL_MODE_RELEASE_BUFFERS); + + auto filterCiphers = [](const QList &ciphers, bool selectTls13) + { + QByteArray cipherString; + + for (const QSslCipher &cipher : ciphers) { + const bool isTls13Cipher = cipher.protocol() == QSsl::TlsV1_3 || cipher.protocol() == QSsl::TlsV1_3OrLater; + if (selectTls13 != isTls13Cipher) + continue; + + if (cipherString.size()) + cipherString.append(':'); + cipherString.append(cipher.name().toLatin1()); + } + return cipherString; + }; + + // Initialize ciphers + QList ciphers = sslContext->sslConfiguration.ciphers(); + if (ciphers.isEmpty()) + ciphers = isDtls ? QTlsBackend::defaultDtlsCiphers() : QTlsBackend::defaultCiphers(); + + const QByteArray preTls13Ciphers = filterCiphers(ciphers, false); + + if (preTls13Ciphers.size()) { + if (!q_SSL_CTX_set_cipher_list(sslContext->ctx, preTls13Ciphers.data())) { + sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + } + + const QByteArray tls13Ciphers = filterCiphers(ciphers, true); +#ifdef TLS1_3_VERSION + if (tls13Ciphers.size()) { + if (!q_SSL_CTX_set_ciphersuites(sslContext->ctx, tls13Ciphers.data())) { + sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + } +#endif // TLS1_3_VERSION + if (!preTls13Ciphers.size() && !tls13Ciphers.size()) { + sslContext->errorStr = QSslSocket::tr("Invalid or empty cipher list (%1)").arg(QStringLiteral("")); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + const QDateTime now = QDateTime::currentDateTimeUtc(); + + // Add all our CAs to this store. + const auto caCertificates = sslContext->sslConfiguration.caCertificates(); + for (const QSslCertificate &caCertificate : caCertificates) { + // From https://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html: + // + // If several CA certificates matching the name, key identifier, and + // serial number condition are available, only the first one will be + // examined. This may lead to unexpected results if the same CA + // certificate is available with different expiration dates. If a + // ``certificate expired'' verification error occurs, no other + // certificate will be searched. Make sure to not have expired + // certificates mixed with valid ones. + // + // See also: QSslSocketBackendPrivate::verify() + if (caCertificate.expiryDate() >= now) { + q_X509_STORE_add_cert(q_SSL_CTX_get_cert_store(sslContext->ctx), (X509 *)caCertificate.handle()); + } + } + + if (QSslSocketPrivate::rootCertOnDemandLoadingSupported() && allowRootCertOnDemandLoading) { + // tell OpenSSL the directories where to look up the root certs on demand + const QList unixDirs = QSslSocketPrivate::unixRootCertDirectories(); + int success = 1; +#if OPENSSL_VERSION_MAJOR < 3 + for (const QByteArray &unixDir : unixDirs) { + if ((success = q_SSL_CTX_load_verify_locations(sslContext->ctx, nullptr, unixDir.constData())) != 1) + break; + } +#else + for (const QByteArray &unixDir : unixDirs) { + if ((success = q_SSL_CTX_load_verify_dir(sslContext->ctx, unixDir.constData())) != 1) + break; + } +#endif // OPENSSL_VERSION_MAJOR + if (success != 1) { + const auto qtErrors = QTlsBackendOpenSSL::getErrorsFromOpenSsl(); + qCWarning(lcTlsBackend) << "An error encountered while to set root certificates location:" + << qtErrors; + } + } + + if (!sslContext->sslConfiguration.localCertificate().isNull()) { + // Require a private key as well. + if (sslContext->sslConfiguration.privateKey().isNull()) { + sslContext->errorStr = QSslSocket::tr("Cannot provide a certificate with no key"); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // Load certificate + if (!q_SSL_CTX_use_certificate(sslContext->ctx, (X509 *)sslContext->sslConfiguration.localCertificate().handle())) { + sslContext->errorStr = QSslSocket::tr("Error loading local certificate, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + if (configuration.d->privateKey.algorithm() == QSsl::Opaque) { + sslContext->pkey = reinterpret_cast(configuration.d->privateKey.handle()); + } else { + // Load private key + sslContext->pkey = q_EVP_PKEY_new(); + // before we were using EVP_PKEY_assign_R* functions and did not use EVP_PKEY_free. + // this lead to a memory leak. Now we use the *_set1_* functions which do not + // take ownership of the RSA/DSA key instance because the QSslKey already has ownership. + if (configuration.d->privateKey.algorithm() == QSsl::Rsa) + q_EVP_PKEY_set1_RSA(sslContext->pkey, reinterpret_cast(configuration.d->privateKey.handle())); + else if (configuration.d->privateKey.algorithm() == QSsl::Dsa) + q_EVP_PKEY_set1_DSA(sslContext->pkey, reinterpret_cast(configuration.d->privateKey.handle())); +#ifndef OPENSSL_NO_EC + else if (configuration.d->privateKey.algorithm() == QSsl::Ec) + q_EVP_PKEY_set1_EC_KEY(sslContext->pkey, reinterpret_cast(configuration.d->privateKey.handle())); +#endif + } + auto pkey = sslContext->pkey; + if (configuration.d->privateKey.algorithm() == QSsl::Opaque) + sslContext->pkey = nullptr; // Don't free the private key, it belongs to QSslKey + + if (!q_SSL_CTX_use_PrivateKey(sslContext->ctx, pkey)) { + sslContext->errorStr = QSslSocket::tr("Error loading private key, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // Check if the certificate matches the private key. + if (!q_SSL_CTX_check_private_key(sslContext->ctx)) { + sslContext->errorStr = QSslSocket::tr("Private key does not certify public key, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + // If we have any intermediate certificates then we need to add them to our chain + bool first = true; + for (const QSslCertificate &cert : qAsConst(configuration.d->localCertificateChain)) { + if (first) { + first = false; + continue; + } + q_SSL_CTX_ctrl(sslContext->ctx, SSL_CTRL_EXTRA_CHAIN_CERT, 0, + q_X509_dup(reinterpret_cast(cert.handle()))); + } + } + + // Initialize peer verification, different callbacks, TLS/DTLS verification first + // (note, all these set_some_callback do not have return value): + if (sslContext->sslConfiguration.peerVerifyMode() == QSslSocket::VerifyNone) { + q_SSL_CTX_set_verify(sslContext->ctx, SSL_VERIFY_NONE, nullptr); + } else { + auto verificationCallback = + #if QT_CONFIG(dtls) + isDtls ? dtlscallbacks::q_X509DtlsCallback : + #endif // dtls + QTlsPrivate::q_X509Callback; + + if (!isDtls && configuration.handshakeMustInterruptOnError()) + verificationCallback = QTlsPrivate::q_X509CallbackDirect; + + auto verificationMode = SSL_VERIFY_PEER; + if (!isDtls && sslContext->sslConfiguration.missingCertificateIsFatal()) + verificationMode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + + q_SSL_CTX_set_verify(sslContext->ctx, verificationMode, verificationCallback); + } + +#ifdef TLS1_3_VERSION + // NewSessionTicket callback: + if (mode == QSslSocket::SslClientMode && !isDtls) { + q_SSL_CTX_sess_set_new_cb(sslContext->ctx, q_ssl_sess_set_new_cb); + q_SSL_CTX_set_session_cache_mode(sslContext->ctx, SSL_SESS_CACHE_CLIENT); + } + +#endif // TLS1_3_VERSION + +#if QT_CONFIG(dtls) + // DTLS cookies: + if (mode == QSslSocket::SslServerMode && isDtls && configuration.dtlsCookieVerificationEnabled()) { + q_SSL_CTX_set_cookie_generate_cb(sslContext->ctx, dtlscallbacks::q_generate_cookie_callback); + q_SSL_CTX_set_cookie_verify_cb(sslContext->ctx, dtlscallbacks::q_verify_cookie_callback); + } +#endif // dtls + + // Set verification depth. + if (sslContext->sslConfiguration.peerVerifyDepth() != 0) + q_SSL_CTX_set_verify_depth(sslContext->ctx, sslContext->sslConfiguration.peerVerifyDepth()); + + // set persisted session if the user set it + if (!configuration.sessionTicket().isEmpty()) + sslContext->setSessionASN1(configuration.sessionTicket()); + + // Set temp DH params + QSslDiffieHellmanParameters dhparams = configuration.diffieHellmanParameters(); + + if (!dhparams.isValid()) { + sslContext->errorStr = QSslSocket::tr("Diffie-Hellman parameters are not valid"); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } + + if (!dhparams.isEmpty()) { + const QByteArray ¶ms = dhparams.d->derData; + const char *ptr = params.constData(); + DH *dh = q_d2i_DHparams(nullptr, reinterpret_cast(&ptr), + params.length()); + if (dh == nullptr) + qFatal("q_d2i_DHparams failed to convert QSslDiffieHellmanParameters to DER form"); + q_SSL_CTX_set_tmp_dh(sslContext->ctx, dh); + q_DH_free(dh); + } + +#ifndef OPENSSL_NO_PSK + if (!client) + q_SSL_CTX_use_psk_identity_hint(sslContext->ctx, sslContext->sslConfiguration.preSharedKeyIdentityHint().constData()); +#endif // !OPENSSL_NO_PSK + + const auto qcurves = sslContext->sslConfiguration.ellipticCurves(); + if (!qcurves.isEmpty()) { +#ifdef OPENSSL_NO_EC + sslContext->errorStr = msgErrorSettingEllipticCurves(QSslSocket::tr("OpenSSL version with disabled elliptic curves")); + sslContext->errorCode = QSslError::UnspecifiedError; + return; +#else + // Set the curves to be used. + std::vector curves; + curves.reserve(qcurves.size()); + for (const auto &sslCurve : qcurves) + curves.push_back(sslCurve.id); + if (!q_SSL_CTX_ctrl(sslContext->ctx, SSL_CTRL_SET_CURVES, long(curves.size()), &curves[0])) { + sslContext->errorStr = msgErrorSettingEllipticCurves(QTlsBackendOpenSSL::getErrorsFromOpenSsl()); + sslContext->errorCode = QSslError::UnspecifiedError; + return; + } +#endif + } + + applyBackendConfig(sslContext); +} + +void QSslContext::applyBackendConfig(QSslContext *sslContext) +{ + const QMap &conf = sslContext->sslConfiguration.backendConfiguration(); + if (conf.isEmpty()) + return; + +#if QT_CONFIG(ocsp) + auto ocspResponsePos = conf.find("Qt-OCSP-response"); + if (ocspResponsePos != conf.end()) { + // This is our private, undocumented configuration option, existing only for + // the purpose of testing OCSP status responses. We don't even check this + // callback was set. If no - the test must fail. + q_SSL_CTX_set_tlsext_status_cb(sslContext->ctx, QTlsPrivate::qt_OCSP_status_server_callback); + if (conf.size() == 1) + return; + } +#endif // ocsp + + QSharedPointer cctx(q_SSL_CONF_CTX_new(), &q_SSL_CONF_CTX_free); + if (cctx) { + q_SSL_CONF_CTX_set_ssl_ctx(cctx.data(), sslContext->ctx); + q_SSL_CONF_CTX_set_flags(cctx.data(), SSL_CONF_FLAG_FILE); + + for (auto i = conf.constBegin(); i != conf.constEnd(); ++i) { + if (i.key() == "Qt-OCSP-response") // This never goes to SSL_CONF_cmd(). + continue; + + if (!i.value().canConvert(QMetaType(QMetaType::QByteArray))) { + sslContext->errorCode = QSslError::UnspecifiedError; + sslContext->errorStr = msgErrorSettingBackendConfig( + QSslSocket::tr("Expecting QByteArray for %1").arg( + QString::fromUtf8(i.key()))); + return; + } + + const QByteArray &value = i.value().toByteArray(); + const int result = q_SSL_CONF_cmd(cctx.data(), i.key().constData(), value.constData()); + if (result == 2) + continue; + + sslContext->errorCode = QSslError::UnspecifiedError; + switch (result) { + case 0: + sslContext->errorStr = msgErrorSettingBackendConfig( + QSslSocket::tr("An error occurred attempting to set %1 to %2").arg( + QString::fromUtf8(i.key()), QString::fromUtf8(value))); + return; + case 1: + sslContext->errorStr = msgErrorSettingBackendConfig( + QSslSocket::tr("Wrong value for %1 (%2)").arg( + QString::fromUtf8(i.key()), QString::fromUtf8(value))); + return; + default: + sslContext->errorStr = msgErrorSettingBackendConfig( + QSslSocket::tr("Unrecognized command %1 = %2").arg( + QString::fromUtf8(i.key()), QString::fromUtf8(value))); + return; + } + } + + if (q_SSL_CONF_CTX_finish(cctx.data()) == 0) { + sslContext->errorStr = msgErrorSettingBackendConfig(QSslSocket::tr("SSL_CONF_finish() failed")); + sslContext->errorCode = QSslError::UnspecifiedError; + } + } else { + sslContext->errorStr = msgErrorSettingBackendConfig(QSslSocket::tr("SSL_CONF_CTX_new() failed")); + sslContext->errorCode = QSslError::UnspecifiedError; + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qsslcontext_openssl_p.h b/src/plugins/tls/openssl/qsslcontext_openssl_p.h new file mode 100644 index 0000000000..c350a93f5e --- /dev/null +++ b/src/plugins/tls/openssl/qsslcontext_openssl_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** 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$ +** +****************************************************************************/ + + +#ifndef QSSLCONTEXT_OPENSSL_P_H +#define QSSLCONTEXT_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_SSL + +class QSslContext +{ +public: + + ~QSslContext(); + + static QSslContext* fromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, + bool allowRootCertOnDemandLoading); + static QSharedPointer sharedFromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, + bool allowRootCertOnDemandLoading); + static QSharedPointer sharedFromPrivateConfiguration(QSslSocket::SslMode mode, QSslConfigurationPrivate *privConfiguration, + bool allowRootCertOnDemandLoading); + static long setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions); + + QSslError::SslError error() const; + QString errorString() const; + + SSL* createSsl(); + bool cacheSession(SSL*); // should be called when handshake completed + + QByteArray sessionASN1() const; + void setSessionASN1(const QByteArray &sessionASN1); + int sessionTicketLifeTimeHint() const; + + static void forceAutoTestSecurityLevel(); + +#ifndef OPENSSL_NO_NEXTPROTONEG + // must be public because we want to use it from an OpenSSL callback + struct NPNContext { + NPNContext() : data(nullptr), + len(0), + status(QSslConfiguration::NextProtocolNegotiationNone) + { } + unsigned char *data; + unsigned short len; + QSslConfiguration::NextProtocolNegotiationStatus status; + }; + NPNContext npnContext() const; +#endif // !OPENSSL_NO_NEXTPROTONEG + +protected: + QSslContext(); + friend class QSharedPointer; + +private: + static void initSslContext(QSslContext* sslContext, QSslSocket::SslMode mode, const QSslConfiguration &configuration, + bool allowRootCertOnDemandLoading); + static void applyBackendConfig(QSslContext *sslContext); + +private: + SSL_CTX* ctx; + EVP_PKEY *pkey; + SSL_SESSION *session; + QByteArray m_sessionASN1; + int m_sessionTicketLifeTimeHint; + QSslError::SslError errorCode; + QString errorStr; + QSslConfiguration sslConfiguration; +#ifndef OPENSSL_NO_NEXTPROTONEG + QByteArray m_supportedNPNVersions; + NPNContext m_npnContext; +#endif // !OPENSSL_NO_NEXTPROTONEG +}; + +#endif // QT_NO_SSL + +QT_END_NAMESPACE + +#endif // QSSLCONTEXT_OPENSSL_P_H diff --git a/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp b/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp new file mode 100644 index 0000000000..a917a20744 --- /dev/null +++ b/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Mikkel Krautz +** Copyright (C) 2016 Richard J. Moore +** 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$ +** +****************************************************************************/ + +#include "qsslsocket_openssl_symbols_p.h" +#include "qtlsbackend_openssl_p.h" + +#include + +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { + +#ifdef OPENSSL_NO_DEPRECATED_3_0 + +int q_DH_check(DH *dh, int *status) +{ + // DH_check was first deprecated in OpenSSL 3.0.0, as low-level + // API; the EVP_PKEY family of functions was advised as an alternative. + // As of now EVP_PKEY_params_check ends up calling ... DH_check, + // which is good enough. + + Q_ASSERT(dh); + Q_ASSERT(status); + + EVP_PKEY *key = q_EVP_PKEY_new(); + if (!key) { + qCWarning(lcSsl, "EVP_PKEY_new failed"); + QTlsBackendOpenSSL::logAndClearErrorQueue(); + return 0; + } + const auto keyDeleter = qScopeGuard([key](){ + q_EVP_PKEY_free(key); + }); + if (!q_EVP_PKEY_set1_DH(key, dh)) { + qCWarning(lcTlsBackend, "EVP_PKEY_set1_DH failed"); + QTlsBackendOpenSSL::logAndClearErrorQueue(); + return 0; + } + + EVP_PKEY_CTX *keyCtx = q_EVP_PKEY_CTX_new(key, nullptr); + if (!keyCtx) { + qCWarning(lcTlsBackend, "EVP_PKEY_CTX_new failed"); + QTlsBackendOpenSSL::logAndClearErrorQueue(); + return 0; + } + const auto ctxDeleter = qScopeGuard([keyCtx]{ + q_EVP_PKEY_CTX_free(keyCtx); + }); + + const int result = q_EVP_PKEY_param_check(keyCtx); + QTlsBackendOpenSSL::logAndClearErrorQueue(); + // Note: unlike DH_check, we cannot obtain the 'status', + // if the 'result' is 0 (actually the result is 1 only + // if this 'status' was 0). We could probably check the + // errors from the error queue, but it's not needed anyway + // - see the 'isSafeDH' below, how it returns immediately + // on 0. + Q_UNUSED(status); + + return result; +} +#endif // OPENSSL_NO_DEPRECATED_3_0 + +bool isSafeDH(DH *dh) +{ + int status = 0; + int bad = 0; + + // TLSTODO: check it's needed or if supportsSsl() + // is enough. + QSslSocketPrivate::ensureInitialized(); + + // From https://wiki.openssl.org/index.php/Diffie-Hellman_parameters: + // + // The additional call to BN_mod_word(dh->p, 24) + // (and unmasking of DH_NOT_SUITABLE_GENERATOR) + // is performed to ensure your program accepts + // IETF group parameters. OpenSSL checks the prime + // is congruent to 11 when g = 2; while the IETF's + // primes are congruent to 23 when g = 2. + // Without the test, the IETF parameters would + // fail validation. For details, see Diffie-Hellman + // Parameter Check (when g = 2, must p mod 24 == 11?). + // Mark p < 1024 bits as unsafe. + if (q_DH_bits(dh) < 1024) + return false; + + if (q_DH_check(dh, &status) != 1) + return false; + + const BIGNUM *p = nullptr; + const BIGNUM *q = nullptr; + const BIGNUM *g = nullptr; + q_DH_get0_pqg(dh, &p, &q, &g); + + if (q_BN_is_word(const_cast(g), DH_GENERATOR_2)) { + const unsigned long residue = q_BN_mod_word(p, 24); + if (residue == 11 || residue == 23) + status &= ~DH_NOT_SUITABLE_GENERATOR; + } + + bad |= DH_CHECK_P_NOT_PRIME; + bad |= DH_CHECK_P_NOT_SAFE_PRIME; + bad |= DH_NOT_SUITABLE_GENERATOR; + + return !(status & bad); +} + +} // unnamed namespace + +int QTlsBackendOpenSSL::dhParametersFromDer(const QByteArray &der, QByteArray *derData) const +{ + Q_ASSERT(derData); + + if (der.isEmpty()) + return DHParams::InvalidInputDataError; + + const unsigned char *data = reinterpret_cast(der.data()); + const int len = der.size(); + + // TLSTODO: check it's needed (loading ciphers and certs in + // addition to the library!) + QSslSocketPrivate::ensureInitialized(); + + DH *dh = q_d2i_DHparams(nullptr, &data, len); + if (dh) { + const auto dhRaii = qScopeGuard([dh] {q_DH_free(dh);}); + + if (isSafeDH(dh)) + *derData = der; + else + return DHParams::UnsafeParametersError; + } else { + return DHParams::InvalidInputDataError; + } + + return DHParams::NoError; +} + +int QTlsBackendOpenSSL::dhParametersFromPem(const QByteArray &pem, QByteArray *data) const +{ + Q_ASSERT(data); + + if (pem.isEmpty()) + return DHParams::InvalidInputDataError; + + // TLSTODO: check it was not a cargo-cult programming in case of + // DH ... + QSslSocketPrivate::ensureInitialized(); + + BIO *bio = q_BIO_new_mem_buf(const_cast(pem.data()), pem.size()); + if (!bio) + return DHParams::InvalidInputDataError; + + const auto bioRaii = qScopeGuard([bio] + { + q_BIO_free(bio); + }); + + DH *dh = nullptr; + q_PEM_read_bio_DHparams(bio, &dh, nullptr, nullptr); + + if (dh) { + const auto dhGuard = qScopeGuard([dh] + { + q_DH_free(dh); + }); + + if (isSafeDH(dh)) { + char *buf = nullptr; + const int len = q_i2d_DHparams(dh, reinterpret_cast(&buf)); + if (len > 0) + *data = QByteArray(buf, len); + else + return DHParams::InvalidInputDataError; + } else { + return DHParams::UnsafeParametersError; + } + } else { + return DHParams::InvalidInputDataError; + } + + return DHParams::NoError; +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_android.cpp b/src/plugins/tls/openssl/qsslsocket_openssl_android.cpp new file mode 100644 index 0000000000..7f7f067fa2 --- /dev/null +++ b/src/plugins/tls/openssl/qsslsocket_openssl_android.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +QList fetchSslCertificateData() +{ + QList certificateData; + + QJniObject certificates = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative", + "getSSLCertificates", + "()[[B"); + if (!certificates.isValid()) + return certificateData; + + QJniEnvironment env; + jobjectArray jcertificates = static_cast(certificates.object()); + const jint nCertificates = env->GetArrayLength(jcertificates); + certificateData.reserve(static_cast(nCertificates)); + + for (int i = 0; i < nCertificates; ++i) { + jbyteArray jCert = static_cast(env->GetObjectArrayElement(jcertificates, i)); + const uint sz = env->GetArrayLength(jCert); + jbyte *buffer = env->GetByteArrayElements(jCert, 0); + certificateData.append(QByteArray(reinterpret_cast(buffer), sz)); + + env->ReleaseByteArrayElements(jCert, buffer, JNI_ABORT); // don't copy back the elements + env->DeleteLocalRef(jCert); + } + + return certificateData; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp b/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp new file mode 100644 index 0000000000..6b4601163b --- /dev/null +++ b/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp @@ -0,0 +1,1221 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2016 Richard J. Moore +** 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$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#include "qsslsocket_openssl_symbols_p.h" + +#include + +#ifdef Q_OS_WIN +# include +#elif QT_CONFIG(library) +# include +#endif +#include +#include +#if defined(Q_OS_UNIX) +#include +#endif +#include +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +#include +#endif +#ifdef Q_OS_DARWIN +#include +#endif + +#include + +QT_BEGIN_NAMESPACE + +/* + Note to maintainer: + ------------------- + + We load OpenSSL symbols dynamically. Because symbols are known to + disappear, and signatures sometimes change, between releases, we need to + be careful about how this is done. To ensure we don't end up dereferencing + null function pointers, and continue running even if certain functions are + missing, we define helper functions for each of the symbols we load from + OpenSSL, all prefixed with "q_" (declared in + qsslsocket_openssl_symbols_p.h). So instead of calling SSL_connect + directly, we call q_SSL_connect, which is a function that checks if the + actual SSL_connect fptr is null, and returns a failure if it is, or calls + SSL_connect if it isn't. + + This requires a somewhat tedious process of declaring each function we + want to call in OpenSSL thrice: once with the q_, in _p.h, once using the + DEFINEFUNC macros below, and once in the function that actually resolves + the symbols, below the DEFINEFUNC declarations below. + + There's one DEFINEFUNC macro declared for every number of arguments + exposed by OpenSSL (feel free to extend when needed). The easiest thing to + do is to find an existing entry that matches the arg count of the function + you want to import, and do the same. + + The first macro arg is the function return type. The second is the + verbatim name of the function/symbol. Then follows a list of N pairs of + argument types with a variable name, and just the variable name (char *a, + a, char *b, b, etc). Finally there's two arguments - a suitable return + statement for the error case (for an int function, return 0 or return -1 + is usually right). Then either just "return" or DUMMYARG, the latter being + for void functions. + + Note: Take into account that these macros and declarations are processed + at compile-time, and the result depends on the OpenSSL headers the + compiling host has installed, but the symbols are resolved at run-time, + possibly with a different version of OpenSSL. +*/ + +#ifndef QT_LINKED_OPENSSL + +namespace { +void qsslSocketUnresolvedSymbolWarning(const char *functionName) +{ + qCWarning(lcTlsBackend, "QSslSocket: cannot call unresolved function %s", functionName); +} + +#if QT_CONFIG(library) +void qsslSocketCannotResolveSymbolWarning(const char *functionName) +{ + qCWarning(lcTlsBackend, "QSslSocket: cannot resolve %s", functionName); +} +#endif + +} + +#endif // QT_LINKED_OPENSSL + +DEFINEFUNC(const unsigned char *, ASN1_STRING_get0_data, const ASN1_STRING *a, a, return nullptr, return) +DEFINEFUNC2(int, OPENSSL_init_ssl, uint64_t opts, opts, const OPENSSL_INIT_SETTINGS *settings, settings, return 0, return) +DEFINEFUNC2(int, OPENSSL_init_crypto, uint64_t opts, opts, const OPENSSL_INIT_SETTINGS *settings, settings, return 0, return) +DEFINEFUNC(BIO *, BIO_new, const BIO_METHOD *a, a, return nullptr, return) +DEFINEFUNC(const BIO_METHOD *, BIO_s_mem, void, DUMMYARG, return nullptr, return) +DEFINEFUNC2(int, BN_is_word, BIGNUM *a, a, BN_ULONG w, w, return 0, return) +DEFINEFUNC(int, EVP_CIPHER_CTX_reset, EVP_CIPHER_CTX *c, c, return 0, return) +DEFINEFUNC(int, EVP_PKEY_up_ref, EVP_PKEY *a, a, return 0, return) +DEFINEFUNC2(EVP_PKEY_CTX *, EVP_PKEY_CTX_new, EVP_PKEY *pkey, pkey, ENGINE *e, e, return nullptr, return) +DEFINEFUNC(int, EVP_PKEY_param_check, EVP_PKEY_CTX *ctx, ctx, return 0, return) +DEFINEFUNC(void, EVP_PKEY_CTX_free, EVP_PKEY_CTX *ctx, ctx, return, return) +DEFINEFUNC(int, EVP_PKEY_base_id, EVP_PKEY *a, a, return NID_undef, return) +DEFINEFUNC(int, RSA_bits, RSA *a, a, return 0, return) +DEFINEFUNC(int, DSA_bits, DSA *a, a, return 0, return) +DEFINEFUNC(int, OPENSSL_sk_num, OPENSSL_STACK *a, a, return -1, return) +DEFINEFUNC2(void, OPENSSL_sk_pop_free, OPENSSL_STACK *a, a, void (*b)(void*), b, return, DUMMYARG) +DEFINEFUNC(OPENSSL_STACK *, OPENSSL_sk_new_null, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(void, OPENSSL_sk_push, OPENSSL_STACK *a, a, void *b, b, return, DUMMYARG) +DEFINEFUNC(void, OPENSSL_sk_free, OPENSSL_STACK *a, a, return, DUMMYARG) +DEFINEFUNC2(void *, OPENSSL_sk_value, OPENSSL_STACK *a, a, int b, b, return nullptr, return) +DEFINEFUNC(int, SSL_session_reused, SSL *a, a, return 0, return) +DEFINEFUNC2(unsigned long, SSL_CTX_set_options, SSL_CTX *ctx, ctx, unsigned long op, op, return 0, return) +using info_callback = void (*) (const SSL *ssl, int type, int val); +DEFINEFUNC2(void, SSL_set_info_callback, SSL *ssl, ssl, info_callback cb, cb, return, return) +DEFINEFUNC(const char *, SSL_alert_type_string, int value, value, return nullptr, return) +DEFINEFUNC(const char *, SSL_alert_desc_string_long, int value, value, return nullptr, return) +DEFINEFUNC(int, SSL_CTX_get_security_level, const SSL_CTX *ctx, ctx, return -1, return) +DEFINEFUNC2(void, SSL_CTX_set_security_level, SSL_CTX *ctx, ctx, int level, level, return, return) +#ifdef TLS1_3_VERSION +DEFINEFUNC2(int, SSL_CTX_set_ciphersuites, SSL_CTX *ctx, ctx, const char *str, str, return 0, return) +DEFINEFUNC2(void, SSL_set_psk_use_session_callback, SSL *ssl, ssl, q_SSL_psk_use_session_cb_func_t callback, callback, return, DUMMYARG) +DEFINEFUNC2(void, SSL_CTX_sess_set_new_cb, SSL_CTX *ctx, ctx, NewSessionCallback cb, cb, return, return) +DEFINEFUNC(int, SSL_SESSION_is_resumable, const SSL_SESSION *s, s, return 0, return) +#endif +DEFINEFUNC3(size_t, SSL_get_client_random, SSL *a, a, unsigned char *out, out, size_t outlen, outlen, return 0, return) +DEFINEFUNC3(size_t, SSL_SESSION_get_master_key, const SSL_SESSION *ses, ses, unsigned char *out, out, size_t outlen, outlen, return 0, return) +DEFINEFUNC6(int, CRYPTO_get_ex_new_index, int class_index, class_index, long argl, argl, void *argp, argp, CRYPTO_EX_new *new_func, new_func, CRYPTO_EX_dup *dup_func, dup_func, CRYPTO_EX_free *free_func, free_func, return -1, return) +DEFINEFUNC2(unsigned long, SSL_set_options, SSL *ssl, ssl, unsigned long op, op, return 0, return) + +DEFINEFUNC(const SSL_METHOD *, TLS_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const SSL_METHOD *, TLS_client_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const SSL_METHOD *, TLS_server_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, X509_up_ref, X509 *a, a, return, DUMMYARG) +DEFINEFUNC(ASN1_TIME *, X509_getm_notBefore, X509 *a, a, return nullptr, return) +DEFINEFUNC(ASN1_TIME *, X509_getm_notAfter, X509 *a, a, return nullptr, return) +DEFINEFUNC(long, X509_get_version, X509 *a, a, return -1, return) +DEFINEFUNC(EVP_PKEY *, X509_get_pubkey, X509 *a, a, return nullptr, return) +DEFINEFUNC2(void, X509_STORE_set_verify_cb, X509_STORE *a, a, X509_STORE_CTX_verify_cb verify_cb, verify_cb, return, DUMMYARG) +DEFINEFUNC3(int, X509_STORE_set_ex_data, X509_STORE *a, a, int idx, idx, void *data, data, return 0, return) +DEFINEFUNC2(void *, X509_STORE_get_ex_data, X509_STORE *r, r, int idx, idx, return nullptr, return) +DEFINEFUNC(STACK_OF(X509) *, X509_STORE_CTX_get0_chain, X509_STORE_CTX *a, a, return nullptr, return) +DEFINEFUNC3(void, CRYPTO_free, void *str, str, const char *file, file, int line, line, return, DUMMYARG) +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) +DEFINEFUNC(int, DH_bits, DH *dh, dh, return 0, return) + +#if QT_CONFIG(dtls) +DEFINEFUNC2(int, DTLSv1_listen, SSL *s, s, BIO_ADDR *c, c, return -1, return) +DEFINEFUNC(BIO_ADDR *, BIO_ADDR_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, BIO_ADDR_free, BIO_ADDR *ap, ap, return, DUMMYARG) +DEFINEFUNC2(BIO_METHOD *, BIO_meth_new, int type, type, const char *name, name, return nullptr, return) +DEFINEFUNC(void, BIO_meth_free, BIO_METHOD *biom, biom, return, DUMMYARG) +DEFINEFUNC2(int, BIO_meth_set_write, BIO_METHOD *biom, biom, DgramWriteCallback write, write, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_read, BIO_METHOD *biom, biom, DgramReadCallback read, read, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_puts, BIO_METHOD *biom, biom, DgramPutsCallback puts, puts, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_ctrl, BIO_METHOD *biom, biom, DgramCtrlCallback ctrl, ctrl, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_create, BIO_METHOD *biom, biom, DgramCreateCallback crt, crt, return 0, return) +DEFINEFUNC2(int, BIO_meth_set_destroy, BIO_METHOD *biom, biom, DgramDestroyCallback dtr, dtr, return 0, return) +#endif // dtls + +#if QT_CONFIG(ocsp) +DEFINEFUNC(const OCSP_CERTID *, OCSP_SINGLERESP_get0_id, const OCSP_SINGLERESP *x, x, return nullptr, return) +DEFINEFUNC3(OCSP_RESPONSE *, d2i_OCSP_RESPONSE, OCSP_RESPONSE **a, a, const unsigned char **in, in, long len, len, return nullptr, return) +DEFINEFUNC(void, OCSP_RESPONSE_free, OCSP_RESPONSE *rs, rs, return, DUMMYARG) +DEFINEFUNC(OCSP_BASICRESP *, OCSP_response_get1_basic, OCSP_RESPONSE *resp, resp, return nullptr, return) +DEFINEFUNC(void, OCSP_BASICRESP_free, OCSP_BASICRESP *bs, bs, return, DUMMYARG) +DEFINEFUNC(int, OCSP_response_status, OCSP_RESPONSE *resp, resp, return OCSP_RESPONSE_STATUS_INTERNALERROR, return) +DEFINEFUNC4(int, OCSP_basic_verify, OCSP_BASICRESP *bs, bs, STACK_OF(X509) *certs, certs, X509_STORE *st, st, unsigned long flags, flags, return -1, return) +DEFINEFUNC(int, OCSP_resp_count, OCSP_BASICRESP *bs, bs, return 0, return) +DEFINEFUNC2(OCSP_SINGLERESP *, OCSP_resp_get0, OCSP_BASICRESP *bs, bs, int idx, idx, return nullptr, return) +DEFINEFUNC5(int, OCSP_single_get0_status, OCSP_SINGLERESP *single, single, int *reason, reason, ASN1_GENERALIZEDTIME **revtime, revtime, + ASN1_GENERALIZEDTIME **thisupd, thisupd, ASN1_GENERALIZEDTIME **nextupd, nextupd, return -1, return) +DEFINEFUNC4(int, OCSP_check_validity, ASN1_GENERALIZEDTIME *thisupd, thisupd, ASN1_GENERALIZEDTIME *nextupd, nextupd, long nsec, nsec, long maxsec, maxsec, return 0, return) +DEFINEFUNC3(OCSP_CERTID *, OCSP_cert_to_id, const EVP_MD *dgst, dgst, X509 *subject, subject, X509 *issuer, issuer, return nullptr, return) +DEFINEFUNC(void, OCSP_CERTID_free, OCSP_CERTID *cid, cid, return, DUMMYARG) +DEFINEFUNC5(int, OCSP_id_get0_info, ASN1_OCTET_STRING **piNameHash, piNameHash, ASN1_OBJECT **pmd, pmd, + ASN1_OCTET_STRING **piKeyHash, piKeyHash, ASN1_INTEGER **pserial, pserial, OCSP_CERTID *cid, cid, + return 0, return) +DEFINEFUNC2(OCSP_RESPONSE *, OCSP_response_create, int status, status, OCSP_BASICRESP *bs, bs, return nullptr, return) +DEFINEFUNC(const STACK_OF(X509) *, OCSP_resp_get0_certs, const OCSP_BASICRESP *bs, bs, return nullptr, return) +DEFINEFUNC2(int, OCSP_id_cmp, OCSP_CERTID *a, a, OCSP_CERTID *b, b, return -1, return) +DEFINEFUNC7(OCSP_SINGLERESP *, OCSP_basic_add1_status, OCSP_BASICRESP *r, r, OCSP_CERTID *c, c, int s, s, + int re, re, ASN1_TIME *rt, rt, ASN1_TIME *t, t, ASN1_TIME *n, n, return nullptr, return) +DEFINEFUNC(OCSP_BASICRESP *, OCSP_BASICRESP_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(int, i2d_OCSP_RESPONSE, OCSP_RESPONSE *r, r, unsigned char **ppout, ppout, return 0, return) +DEFINEFUNC6(int, OCSP_basic_sign, OCSP_BASICRESP *br, br, X509 *signer, signer, EVP_PKEY *key, key, + const EVP_MD *dg, dg, STACK_OF(X509) *cs, cs, unsigned long flags, flags, return 0, return) +#endif // ocsp + +DEFINEFUNC2(void, BIO_set_data, BIO *a, a, void *ptr, ptr, return, DUMMYARG) +DEFINEFUNC(void *, BIO_get_data, BIO *a, a, return nullptr, return) +DEFINEFUNC2(void, BIO_set_init, BIO *a, a, int init, init, return, DUMMYARG) +DEFINEFUNC(int, BIO_get_shutdown, BIO *a, a, return -1, return) +DEFINEFUNC2(void, BIO_set_shutdown, BIO *a, a, int shut, shut, return, DUMMYARG) + +DEFINEFUNC(long, ASN1_INTEGER_get, ASN1_INTEGER *a, a, return 0, return) +DEFINEFUNC2(int, ASN1_INTEGER_cmp, const ASN1_INTEGER *a, a, const ASN1_INTEGER *b, b, return 1, return) +DEFINEFUNC(int, ASN1_STRING_length, ASN1_STRING *a, a, return 0, return) +DEFINEFUNC2(int, ASN1_STRING_to_UTF8, unsigned char **a, a, ASN1_STRING *b, b, return 0, return) +DEFINEFUNC2(int, ASN1_TIME_to_tm, const ASN1_TIME *s, s, struct tm *tm, tm, return 0, return) +DEFINEFUNC4(long, BIO_ctrl, BIO *a, a, int b, b, long c, c, void *d, d, return -1, return) +DEFINEFUNC(int, BIO_free, BIO *a, a, return 0, return) +DEFINEFUNC2(BIO *, BIO_new_mem_buf, void *a, a, int b, b, return nullptr, return) +DEFINEFUNC3(int, BIO_read, BIO *a, a, void *b, b, int c, c, return -1, return) + +DEFINEFUNC3(int, BIO_write, BIO *a, a, const void *b, b, int c, c, return -1, return) +DEFINEFUNC(int, BN_num_bits, const BIGNUM *a, a, return 0, return) +DEFINEFUNC2(BN_ULONG, BN_mod_word, const BIGNUM *a, a, BN_ULONG w, w, return static_cast(-1), return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC(const EC_GROUP*, EC_KEY_get0_group, const EC_KEY* k, k, return nullptr, return) +DEFINEFUNC(int, EC_GROUP_get_degree, const EC_GROUP* g, g, return 0, return) +#endif +DEFINEFUNC(DSA *, DSA_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, DSA_free, DSA *a, a, return, DUMMYARG) +DEFINEFUNC3(X509 *, d2i_X509, X509 **a, a, const unsigned char **b, b, long c, c, return nullptr, return) +DEFINEFUNC2(char *, ERR_error_string, unsigned long a, a, char *b, b, return nullptr, return) +DEFINEFUNC3(void, ERR_error_string_n, unsigned long e, e, char *b, b, size_t len, len, return, DUMMYARG) +DEFINEFUNC(unsigned long, ERR_get_error, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(EVP_CIPHER_CTX *, EVP_CIPHER_CTX_new, void, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, EVP_CIPHER_CTX_free, EVP_CIPHER_CTX *a, a, return, DUMMYARG) +DEFINEFUNC4(int, EVP_CIPHER_CTX_ctrl, EVP_CIPHER_CTX *ctx, ctx, int type, type, int arg, arg, void *ptr, ptr, return 0, return) +DEFINEFUNC2(int, EVP_CIPHER_CTX_set_key_length, EVP_CIPHER_CTX *ctx, ctx, int keylen, keylen, return 0, return) +DEFINEFUNC5(int, EVP_CipherInit, EVP_CIPHER_CTX *ctx, ctx, const EVP_CIPHER *type, type, const unsigned char *key, key, const unsigned char *iv, iv, int enc, enc, return 0, return) +DEFINEFUNC6(int, EVP_CipherInit_ex, EVP_CIPHER_CTX *ctx, ctx, const EVP_CIPHER *cipher, cipher, ENGINE *impl, impl, const unsigned char *key, key, const unsigned char *iv, iv, int enc, enc, return 0, return) +DEFINEFUNC5(int, EVP_CipherUpdate, EVP_CIPHER_CTX *ctx, ctx, unsigned char *out, out, int *outl, outl, const unsigned char *in, in, int inl, inl, return 0, return) +DEFINEFUNC3(int, EVP_CipherFinal, EVP_CIPHER_CTX *ctx, ctx, unsigned char *out, out, int *outl, outl, return 0, return) +DEFINEFUNC(const EVP_MD *, EVP_get_digestbyname, const char *name, name, return nullptr, return) +#ifndef OPENSSL_NO_DES +DEFINEFUNC(const EVP_CIPHER *, EVP_des_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const EVP_CIPHER *, EVP_des_ede3_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +#endif +#ifndef OPENSSL_NO_RC2 +DEFINEFUNC(const EVP_CIPHER *, EVP_rc2_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +#endif +#ifndef OPENSSL_NO_AES +DEFINEFUNC(const EVP_CIPHER *, EVP_aes_128_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const EVP_CIPHER *, EVP_aes_192_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const EVP_CIPHER *, EVP_aes_256_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +#endif +DEFINEFUNC(const EVP_MD *, EVP_sha1, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC3(int, EVP_PKEY_assign, EVP_PKEY *a, a, int b, b, void *r, r, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_RSA, EVP_PKEY *a, a, RSA *b, b, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_DSA, EVP_PKEY *a, a, DSA *b, b, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_DH, EVP_PKEY *a, a, DH *b, b, return -1, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC2(int, EVP_PKEY_set1_EC_KEY, EVP_PKEY *a, a, EC_KEY *b, b, return -1, return) +#endif +DEFINEFUNC2(int, EVP_PKEY_cmp, const EVP_PKEY *a, a, const EVP_PKEY *b, b, return -1, return) +DEFINEFUNC(void, EVP_PKEY_free, EVP_PKEY *a, a, return, DUMMYARG) +DEFINEFUNC(DSA *, EVP_PKEY_get1_DSA, EVP_PKEY *a, a, return nullptr, return) +DEFINEFUNC(RSA *, EVP_PKEY_get1_RSA, EVP_PKEY *a, a, return nullptr, return) +DEFINEFUNC(DH *, EVP_PKEY_get1_DH, EVP_PKEY *a, a, return nullptr, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC(EC_KEY *, EVP_PKEY_get1_EC_KEY, EVP_PKEY *a, a, return nullptr, return) +#endif +DEFINEFUNC(EVP_PKEY *, EVP_PKEY_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(int, EVP_PKEY_type, int a, a, return NID_undef, return) +DEFINEFUNC2(int, i2d_X509, X509 *a, a, unsigned char **b, b, return -1, return) +DEFINEFUNC(const char *, OBJ_nid2sn, int a, a, return nullptr, return) +DEFINEFUNC(const char *, OBJ_nid2ln, int a, a, return nullptr, return) +DEFINEFUNC(int, OBJ_sn2nid, const char *s, s, return 0, return) +DEFINEFUNC(int, OBJ_ln2nid, const char *s, s, return 0, return) +DEFINEFUNC3(int, i2t_ASN1_OBJECT, char *a, a, int b, b, ASN1_OBJECT *c, c, return -1, return) +DEFINEFUNC4(int, OBJ_obj2txt, char *a, a, int b, b, ASN1_OBJECT *c, c, int d, d, return -1, return) +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(DSA *, PEM_read_bio_DSAPrivateKey, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(RSA *, PEM_read_bio_RSAPrivateKey, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) + +#ifndef OPENSSL_NO_EC +DEFINEFUNC4(EC_KEY *, PEM_read_bio_ECPrivateKey, BIO *a, a, EC_KEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC7(int, PEM_write_bio_ECPrivateKey, BIO *a, a, EC_KEY *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(EC_KEY *, PEM_read_bio_EC_PUBKEY, BIO *a, a, EC_KEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC2(int, PEM_write_bio_EC_PUBKEY, BIO *a, a, EC_KEY *b, b, return 0, return) +#endif // OPENSSL_NO_EC + +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_DSAPrivateKey, BIO *a, a, DSA *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_RSAPrivateKey, BIO *a, a, RSA *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, 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) +DEFINEFUNC4(DSA *, PEM_read_bio_DSA_PUBKEY, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(RSA *, PEM_read_bio_RSA_PUBKEY, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC2(int, PEM_write_bio_DSA_PUBKEY, BIO *a, a, DSA *b, b, return 0, return) +DEFINEFUNC2(int, PEM_write_bio_RSA_PUBKEY, BIO *a, a, RSA *b, b, return 0, return) +DEFINEFUNC2(int, PEM_write_bio_PUBKEY, BIO *a, a, EVP_PKEY *b, b, return 0, return) +DEFINEFUNC2(void, RAND_seed, const void *a, a, int b, b, return, DUMMYARG) +DEFINEFUNC(int, RAND_status, void, DUMMYARG, return -1, return) +DEFINEFUNC2(int, RAND_bytes, unsigned char *b, b, int n, n, return 0, return) +DEFINEFUNC(RSA *, RSA_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, RSA_free, RSA *a, a, return, DUMMYARG) +DEFINEFUNC(int, SSL_accept, SSL *a, a, return -1, return) +DEFINEFUNC(int, SSL_clear, SSL *a, a, return -1, return) +DEFINEFUNC3(char *, SSL_CIPHER_description, const SSL_CIPHER *a, a, char *b, b, int c, c, return nullptr, return) +DEFINEFUNC2(int, SSL_CIPHER_get_bits, const SSL_CIPHER *a, a, int *b, b, return 0, return) +DEFINEFUNC(BIO *, SSL_get_rbio, const SSL *s, s, return nullptr, return) +DEFINEFUNC(int, SSL_connect, SSL *a, a, return -1, return) +DEFINEFUNC(int, SSL_CTX_check_private_key, const SSL_CTX *a, a, return -1, return) +DEFINEFUNC4(long, SSL_CTX_ctrl, SSL_CTX *a, a, int b, b, long c, c, void *d, d, return -1, return) +DEFINEFUNC(void, SSL_CTX_free, SSL_CTX *a, a, return, DUMMYARG) +DEFINEFUNC(SSL_CTX *, SSL_CTX_new, const SSL_METHOD *a, a, return nullptr, return) +DEFINEFUNC2(int, SSL_CTX_set_cipher_list, SSL_CTX *a, a, const char *b, b, return -1, return) +DEFINEFUNC3(long, SSL_CTX_callback_ctrl, SSL_CTX *ctx, ctx, int dst, dst, GenericCallbackType cb, cb, return 0, return) +DEFINEFUNC(int, SSL_CTX_set_default_verify_paths, SSL_CTX *a, a, return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_verify, SSL_CTX *a, a, int b, b, int (*c)(int, X509_STORE_CTX *), c, return, DUMMYARG) +DEFINEFUNC2(void, SSL_CTX_set_verify_depth, SSL_CTX *a, a, int b, b, return, DUMMYARG) +DEFINEFUNC2(int, SSL_CTX_use_certificate, SSL_CTX *a, a, X509 *b, b, return -1, return) +DEFINEFUNC3(int, SSL_CTX_use_certificate_file, SSL_CTX *a, a, const char *b, b, int c, c, return -1, return) +DEFINEFUNC2(int, SSL_CTX_use_PrivateKey, SSL_CTX *a, a, EVP_PKEY *b, b, return -1, return) +DEFINEFUNC2(int, SSL_CTX_use_RSAPrivateKey, SSL_CTX *a, a, RSA *b, b, return -1, return) +DEFINEFUNC3(int, SSL_CTX_use_PrivateKey_file, SSL_CTX *a, a, const char *b, b, int c, c, return -1, return) +DEFINEFUNC(X509_STORE *, SSL_CTX_get_cert_store, const SSL_CTX *a, a, return nullptr, return) +DEFINEFUNC(SSL_CONF_CTX *, SSL_CONF_CTX_new, DUMMYARG, DUMMYARG, return nullptr, return); +DEFINEFUNC(void, SSL_CONF_CTX_free, SSL_CONF_CTX *a, a, return ,return); +DEFINEFUNC2(void, SSL_CONF_CTX_set_ssl_ctx, SSL_CONF_CTX *a, a, SSL_CTX *b, b, return, return); +DEFINEFUNC2(unsigned int, SSL_CONF_CTX_set_flags, SSL_CONF_CTX *a, a, unsigned int b, b, return 0, return); +DEFINEFUNC(int, SSL_CONF_CTX_finish, SSL_CONF_CTX *a, a, return 0, return); +DEFINEFUNC3(int, SSL_CONF_cmd, SSL_CONF_CTX *a, a, const char *b, b, const char *c, c, return 0, return); +DEFINEFUNC(void, SSL_free, SSL *a, a, return, DUMMYARG) +DEFINEFUNC(STACK_OF(SSL_CIPHER) *, SSL_get_ciphers, const SSL *a, a, return nullptr, return) +DEFINEFUNC(const SSL_CIPHER *, SSL_get_current_cipher, SSL *a, a, return nullptr, return) +DEFINEFUNC(int, SSL_version, const SSL *a, a, return 0, return) +DEFINEFUNC2(int, SSL_get_error, SSL *a, a, int b, b, return -1, return) +DEFINEFUNC(STACK_OF(X509) *, SSL_get_peer_cert_chain, SSL *a, a, return nullptr, return) +DEFINEFUNC(X509 *, SSL_get_peer_certificate, SSL *a, a, return nullptr, return) +DEFINEFUNC(long, SSL_get_verify_result, const SSL *a, a, return -1, return) +DEFINEFUNC(SSL *, SSL_new, SSL_CTX *a, a, return nullptr, return) +DEFINEFUNC(SSL_CTX *, SSL_get_SSL_CTX, SSL *a, a, return nullptr, return) +DEFINEFUNC4(long, SSL_ctrl, SSL *a, a, int cmd, cmd, long larg, larg, void *parg, parg, return -1, return) +DEFINEFUNC3(int, SSL_read, SSL *a, a, void *b, b, int c, c, return -1, return) +DEFINEFUNC3(void, SSL_set_bio, SSL *a, a, BIO *b, b, BIO *c, c, return, DUMMYARG) +DEFINEFUNC(void, SSL_set_accept_state, SSL *a, a, return, DUMMYARG) +DEFINEFUNC(void, SSL_set_connect_state, SSL *a, a, return, DUMMYARG) +DEFINEFUNC(int, SSL_shutdown, SSL *a, a, return -1, return) +DEFINEFUNC(int, SSL_in_init, const SSL *a, a, return 0, return) +DEFINEFUNC(int, SSL_get_shutdown, const SSL *ssl, ssl, return 0, return) +DEFINEFUNC2(int, SSL_set_session, SSL* to, to, SSL_SESSION *session, session, return -1, return) +DEFINEFUNC(void, SSL_SESSION_free, SSL_SESSION *ses, ses, return, DUMMYARG) +DEFINEFUNC(SSL_SESSION*, SSL_get1_session, SSL *ssl, ssl, return nullptr, return) +DEFINEFUNC(SSL_SESSION*, SSL_get_session, const SSL *ssl, ssl, return nullptr, return) +DEFINEFUNC3(int, SSL_set_ex_data, SSL *ssl, ssl, int idx, idx, void *arg, arg, return 0, return) +DEFINEFUNC2(void *, SSL_get_ex_data, const SSL *ssl, ssl, int idx, idx, return nullptr, return) + +#ifndef OPENSSL_NO_PSK +DEFINEFUNC2(void, SSL_set_psk_client_callback, SSL* ssl, ssl, q_psk_client_callback_t callback, callback, return, DUMMYARG) +DEFINEFUNC2(void, SSL_set_psk_server_callback, SSL* ssl, ssl, q_psk_server_callback_t callback, callback, return, DUMMYARG) +DEFINEFUNC2(int, SSL_CTX_use_psk_identity_hint, SSL_CTX* ctx, ctx, const char *hint, hint, return 0, return) +#endif // !OPENSSL_NO_PSK + +DEFINEFUNC3(int, SSL_write, SSL *a, a, const void *b, b, int c, c, return -1, return) +DEFINEFUNC2(int, X509_cmp, X509 *a, a, X509 *b, b, return -1, return) +DEFINEFUNC4(int, X509_digest, const X509 *x509, x509, const EVP_MD *type, type, unsigned char *md, md, unsigned int *len, len, return -1, return) +DEFINEFUNC(X509 *, X509_dup, X509 *a, a, return nullptr, return) +DEFINEFUNC2(void, X509_print, BIO *a, a, X509 *b, b, return, DUMMYARG); +DEFINEFUNC(ASN1_OBJECT *, X509_EXTENSION_get_object, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(void, X509_free, X509 *a, a, return, DUMMYARG) +//Q_AUTOTEST_EXPORT ASN1_TIME *q_X509_gmtime_adj(ASN1_TIME *s, long adj); +DEFINEFUNC2(ASN1_TIME *, X509_gmtime_adj, ASN1_TIME *s, s, long adj, adj, return nullptr, return) +DEFINEFUNC(void, ASN1_TIME_free, ASN1_TIME *t, t, return, DUMMYARG) +DEFINEFUNC2(X509_EXTENSION *, X509_get_ext, X509 *a, a, int b, b, return nullptr, return) +DEFINEFUNC(int, X509_get_ext_count, X509 *a, a, return 0, return) +DEFINEFUNC4(void *, X509_get_ext_d2i, X509 *a, a, int b, b, int *c, c, int *d, d, return nullptr, return) +DEFINEFUNC(const X509V3_EXT_METHOD *, X509V3_EXT_get, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(void *, X509V3_EXT_d2i, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(int, X509_EXTENSION_get_critical, X509_EXTENSION *a, a, return 0, return) +DEFINEFUNC(ASN1_OCTET_STRING *, X509_EXTENSION_get_data, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(void, BASIC_CONSTRAINTS_free, BASIC_CONSTRAINTS *a, a, return, DUMMYARG) +DEFINEFUNC(void, AUTHORITY_KEYID_free, AUTHORITY_KEYID *a, a, return, DUMMYARG) +DEFINEFUNC(void, GENERAL_NAME_free, GENERAL_NAME *a, a, return, DUMMYARG) +DEFINEFUNC2(int, ASN1_STRING_print, BIO *a, a, const ASN1_STRING *b, b, return 0, return) +DEFINEFUNC2(int, X509_check_issued, X509 *a, a, X509 *b, b, return -1, return) +DEFINEFUNC(X509_NAME *, X509_get_issuer_name, X509 *a, a, return nullptr, return) +DEFINEFUNC(X509_NAME *, X509_get_subject_name, X509 *a, a, return nullptr, return) +DEFINEFUNC(ASN1_INTEGER *, X509_get_serialNumber, X509 *a, a, return nullptr, return) +DEFINEFUNC(int, X509_verify_cert, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC(int, X509_NAME_entry_count, X509_NAME *a, a, return 0, return) +DEFINEFUNC2(X509_NAME_ENTRY *, X509_NAME_get_entry, X509_NAME *a, a, int b, b, return nullptr, return) +DEFINEFUNC(ASN1_STRING *, X509_NAME_ENTRY_get_data, X509_NAME_ENTRY *a, a, return nullptr, return) +DEFINEFUNC(ASN1_OBJECT *, X509_NAME_ENTRY_get_object, X509_NAME_ENTRY *a, a, return nullptr, return) +DEFINEFUNC(EVP_PKEY *, X509_PUBKEY_get, X509_PUBKEY *a, a, return nullptr, return) +DEFINEFUNC(void, X509_STORE_free, X509_STORE *a, a, return, DUMMYARG) +DEFINEFUNC(X509_STORE *, X509_STORE_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(int, X509_STORE_add_cert, X509_STORE *a, a, X509 *b, b, return 0, return) +DEFINEFUNC(void, X509_STORE_CTX_free, X509_STORE_CTX *a, a, return, DUMMYARG) +DEFINEFUNC4(int, X509_STORE_CTX_init, X509_STORE_CTX *a, a, X509_STORE *b, b, X509 *c, c, STACK_OF(X509) *d, d, return -1, return) +DEFINEFUNC2(int, X509_STORE_CTX_set_purpose, X509_STORE_CTX *a, a, int b, b, return -1, return) +DEFINEFUNC(int, X509_STORE_CTX_get_error, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC(int, X509_STORE_CTX_get_error_depth, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC(X509 *, X509_STORE_CTX_get_current_cert, X509_STORE_CTX *a, a, return nullptr, return) +DEFINEFUNC(X509_STORE *, X509_STORE_CTX_get0_store, X509_STORE_CTX *ctx, ctx, return nullptr, return) +DEFINEFUNC(X509_STORE_CTX *, X509_STORE_CTX_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(void *, X509_STORE_CTX_get_ex_data, X509_STORE_CTX *ctx, ctx, int idx, idx, return nullptr, return) +DEFINEFUNC(int, SSL_get_ex_data_X509_STORE_CTX_idx, DUMMYARG, DUMMYARG, return -1, return) + +#if OPENSSL_VERSION_MAJOR < 3 +DEFINEFUNC3(int, SSL_CTX_load_verify_locations, SSL_CTX *ctx, ctx, const char *CAfile, CAfile, const char *CApath, CApath, return 0, return) +#else +DEFINEFUNC2(int, SSL_CTX_load_verify_dir, SSL_CTX *ctx, ctx, const char *CApath, CApath, return 0, return) +#endif // OPENSSL_VERSION_MAJOR + +DEFINEFUNC2(int, i2d_SSL_SESSION, SSL_SESSION *in, in, unsigned char **pp, pp, return 0, return) +DEFINEFUNC3(SSL_SESSION *, d2i_SSL_SESSION, SSL_SESSION **a, a, const unsigned char **pp, pp, long length, length, return nullptr, return) + +#ifndef OPENSSL_NO_NEXTPROTONEG +DEFINEFUNC6(int, SSL_select_next_proto, unsigned char **out, out, unsigned char *outlen, outlen, + const unsigned char *in, in, unsigned int inlen, inlen, + const unsigned char *client, client, unsigned int client_len, client_len, + return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_next_proto_select_cb, SSL_CTX *s, s, + int (*cb) (SSL *ssl, unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), cb, + void *arg, arg, return, DUMMYARG) +DEFINEFUNC3(void, SSL_get0_next_proto_negotiated, const SSL *s, s, + const unsigned char **data, data, unsigned *len, len, return, DUMMYARG) +DEFINEFUNC3(int, SSL_set_alpn_protos, SSL *s, s, const unsigned char *protos, protos, + unsigned protos_len, protos_len, return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_alpn_select_cb, SSL_CTX *s, s, + int (*cb) (SSL *ssl, const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), cb, + void *arg, arg, return, DUMMYARG) +DEFINEFUNC3(void, SSL_get0_alpn_selected, const SSL *s, s, const unsigned char **data, data, + unsigned *len, len, return, DUMMYARG) +#endif // !OPENSSL_NO_NEXTPROTONEG + +// DTLS: +#if QT_CONFIG(dtls) +DEFINEFUNC2(void, SSL_CTX_set_cookie_generate_cb, SSL_CTX *ctx, ctx, CookieGenerateCallback cb, cb, return, DUMMYARG) +DEFINEFUNC2(void, SSL_CTX_set_cookie_verify_cb, SSL_CTX *ctx, ctx, CookieVerifyCallback cb, cb, return, DUMMYARG) +DEFINEFUNC(const SSL_METHOD *, DTLS_server_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const SSL_METHOD *, DTLS_client_method, DUMMYARG, DUMMYARG, return nullptr, return) +#endif // dtls +DEFINEFUNC2(void, BIO_set_flags, BIO *b, b, int flags, flags, return, DUMMYARG) +DEFINEFUNC2(void, BIO_clear_flags, BIO *b, b, int flags, flags, return, DUMMYARG) +DEFINEFUNC2(void *, BIO_get_ex_data, BIO *b, b, int idx, idx, return nullptr, return) +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) +DEFINEFUNC(DH *, DH_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, DH_free, DH *dh, dh, 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) +#ifndef OPENSSL_NO_DEPRECATED_3_0 +DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return) +#endif // OPENSSL_NO_DEPRECATED_3_0 +DEFINEFUNC3(BIGNUM *, BN_bin2bn, const unsigned char *s, s, int len, len, BIGNUM *ret, ret, return nullptr, return) + +#ifndef OPENSSL_NO_EC +DEFINEFUNC(EC_KEY *, EC_KEY_dup, const EC_KEY *ec, ec, return nullptr, return) +DEFINEFUNC(EC_KEY *, EC_KEY_new_by_curve_name, int nid, nid, return nullptr, return) +DEFINEFUNC(void, EC_KEY_free, EC_KEY *ecdh, ecdh, return, DUMMYARG) +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) +#endif // OPENSSL_NO_EC + +DEFINEFUNC5(int, PKCS12_parse, PKCS12 *p12, p12, const char *pass, pass, EVP_PKEY **pkey, pkey, \ + X509 **cert, cert, STACK_OF(X509) **ca, ca, return 1, return); +DEFINEFUNC2(PKCS12 *, d2i_PKCS12_bio, BIO *bio, bio, PKCS12 **pkcs12, pkcs12, return nullptr, return); +DEFINEFUNC(void, PKCS12_free, PKCS12 *pkcs12, pkcs12, return, DUMMYARG) + +#define RESOLVEFUNC(func) \ + if (!(_q_##func = _q_PTR_##func(libs.ssl->resolve(#func))) \ + && !(_q_##func = _q_PTR_##func(libs.crypto->resolve(#func)))) \ + qsslSocketCannotResolveSymbolWarning(#func); + +#if !defined QT_LINKED_OPENSSL + +#if !QT_CONFIG(library) +bool q_resolveOpenSslSymbols() +{ + qCWarning(lcTlsBackend, "QSslSocket: unable to resolve symbols. Qt is configured without the " + "'library' feature, which means runtime resolving of libraries won't work."); + qCWarning(lcTlsBackend, "Either compile Qt statically or with support for runtime resolving " + "of libraries."); + return false; +} +#else + +# ifdef Q_OS_UNIX +struct NumericallyLess +{ + typedef bool result_type; + result_type operator()(QStringView lhs, QStringView rhs) const + { + bool ok = false; + int b = 0; + int a = lhs.toInt(&ok); + if (ok) + b = rhs.toInt(&ok); + if (ok) { + // both toInt succeeded + return a < b; + } else { + // compare as strings; + return lhs < rhs; + } + } +}; + +struct LibGreaterThan +{ + typedef bool result_type; + result_type operator()(QStringView lhs, QStringView rhs) const + { + const auto lhsparts = lhs.split(QLatin1Char('.')); + const auto rhsparts = rhs.split(QLatin1Char('.')); + Q_ASSERT(lhsparts.count() > 1 && rhsparts.count() > 1); + + // note: checking rhs < lhs, the same as lhs > rhs + return std::lexicographical_compare(rhsparts.begin() + 1, rhsparts.end(), + lhsparts.begin() + 1, lhsparts.end(), + NumericallyLess()); + } +}; + +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +static int dlIterateCallback(struct dl_phdr_info *info, size_t size, void *data) +{ + if (size < sizeof (info->dlpi_addr) + sizeof (info->dlpi_name)) + return 1; + QDuplicateTracker *paths = (QDuplicateTracker *)data; + QString path = QString::fromLocal8Bit(info->dlpi_name); + if (!path.isEmpty()) { + QFileInfo fi(path); + path = fi.absolutePath(); + if (!path.isEmpty()) + (void)paths->hasSeen(std::move(path)); + } + return 0; +} +#endif + +static QStringList libraryPathList() +{ + QStringList paths; +# ifdef Q_OS_DARWIN + paths = QString::fromLatin1(qgetenv("DYLD_LIBRARY_PATH")) + .split(QLatin1Char(':'), Qt::SkipEmptyParts); + + // search in .app/Contents/Frameworks + UInt32 packageType; + CFBundleGetPackageInfo(CFBundleGetMainBundle(), &packageType, nullptr); + if (packageType == FOUR_CHAR_CODE('APPL')) { + QUrl bundleUrl = QUrl::fromCFURL(QCFType(CFBundleCopyBundleURL(CFBundleGetMainBundle()))); + QUrl frameworksUrl = QUrl::fromCFURL(QCFType(CFBundleCopyPrivateFrameworksURL(CFBundleGetMainBundle()))); + paths << bundleUrl.resolved(frameworksUrl).path(); + } +# else + paths = QString::fromLatin1(qgetenv("LD_LIBRARY_PATH")) + .split(QLatin1Char(':'), Qt::SkipEmptyParts); +# endif + paths << QLatin1String("/lib") << QLatin1String("/usr/lib") << QLatin1String("/usr/local/lib"); + paths << QLatin1String("/lib64") << QLatin1String("/usr/lib64") << QLatin1String("/usr/local/lib64"); + paths << QLatin1String("/lib32") << QLatin1String("/usr/lib32") << QLatin1String("/usr/local/lib32"); + +#if defined(Q_OS_ANDROID) + paths << QLatin1String("/system/lib"); +#elif defined(Q_OS_LINUX) + // discover paths of already loaded libraries + QDuplicateTracker loadedPaths; + dl_iterate_phdr(dlIterateCallback, &loadedPaths); + std::move(loadedPaths).appendTo(paths); +#endif + + return paths; +} + +Q_NEVER_INLINE +static QStringList findAllLibs(QLatin1String filter) +{ + const QStringList paths = libraryPathList(); + QStringList found; + const QStringList filters((QString(filter))); + + for (const QString &path : paths) { + QDir dir(path); + QStringList entryList = dir.entryList(filters, QDir::Files); + + std::sort(entryList.begin(), entryList.end(), LibGreaterThan()); + for (const QString &entry : qAsConst(entryList)) + found << path + QLatin1Char('/') + entry; + } + + return found; +} + +static QStringList findAllLibSsl() +{ + return findAllLibs(QLatin1String("libssl.*")); +} + +static QStringList findAllLibCrypto() +{ + return findAllLibs(QLatin1String("libcrypto.*")); +} +# endif + +#ifdef Q_OS_WIN + +struct LoadedOpenSsl { + std::unique_ptr ssl, crypto; +}; + +static bool tryToLoadOpenSslWin32Library(QLatin1String ssleay32LibName, QLatin1String libeay32LibName, LoadedOpenSsl &result) +{ + auto ssleay32 = std::make_unique(ssleay32LibName); + if (!ssleay32->load(false)) { + return FALSE; + } + + auto libeay32 = std::make_unique(libeay32LibName); + if (!libeay32->load(false)) { + return FALSE; + } + + result.ssl = std::move(ssleay32); + result.crypto = std::move(libeay32); + return TRUE; +} + +static LoadedOpenSsl loadOpenSsl() +{ + LoadedOpenSsl result; + + // With OpenSSL 1.1 the names have changed to libssl-1_1 and libcrypto-1_1 for builds using + // MSVC and GCC, with architecture suffixes for non-x86 builds. + +#if defined(Q_PROCESSOR_X86_64) +#define QT_SSL_SUFFIX "-x64" +#elif defined(Q_PROCESSOR_ARM_64) +#define QT_SSL_SUFFIX "-arm64" +#elif defined(Q_PROCESSOR_ARM_32) +#define QT_SSL_SUFFIX "-arm" +#else +#define QT_SSL_SUFFIX +#endif + + tryToLoadOpenSslWin32Library(QLatin1String("libssl-1_1" QT_SSL_SUFFIX), + QLatin1String("libcrypto-1_1" QT_SSL_SUFFIX), result); + +#undef QT_SSL_SUFFIX + return result; +} +#else + +struct LoadedOpenSsl { + std::unique_ptr ssl, crypto; +}; + +static LoadedOpenSsl loadOpenSsl() +{ + LoadedOpenSsl result = { std::make_unique(), std::make_unique() }; + +# if defined(Q_OS_UNIX) + QLibrary * const libssl = result.ssl.get(); + QLibrary * const libcrypto = result.crypto.get(); + + // Try to find the libssl library on the system. + // + // Up until Qt 4.3, this only searched for the "ssl" library at version -1, that + // is, libssl.so on most Unix systems. However, the .so file isn't present in + // user installations because it's considered a development file. + // + // The right thing to do is to load the library at the major version we know how + // to work with: the SHLIB_VERSION_NUMBER version (macro defined in opensslv.h) + // + // However, OpenSSL is a well-known case of binary-compatibility breakage. To + // avoid such problems, many system integrators and Linux distributions change + // the soname of the binary, letting the full version number be the soname. So + // we'll find libssl.so.0.9.7, libssl.so.0.9.8, etc. in the system. For that + // reason, we will search a few common paths (see findAllLibSsl() above) in hopes + // we find one that works. + // + // If that fails, for OpenSSL 1.0 we also try some fallbacks -- look up + // libssl.so with a hardcoded soname. The reason is QTBUG-68156: the binary + // builds of Qt happen (at the time of this writing) on RHEL machines, + // which change SHLIB_VERSION_NUMBER to a non-portable string. When running + // those binaries on the target systems, this code won't pick up + // libssl.so.MODIFIED_SHLIB_VERSION_NUMBER because it doesn't exist there. + // Given that the only 1.0 supported release (at the time of this writing) + // is 1.0.2, with soname "1.0.0", give that a try too. Note that we mandate + // OpenSSL >= 1.0.0 with a configure-time check, and OpenSSL has kept binary + // compatibility between 1.0.0 and 1.0.2. + // + // It is important, however, to try the canonical name and the unversioned name + // without going through the loop. By not specifying a path, we let the system + // dlopen(3) function determine it for us. This will include any DT_RUNPATH or + // DT_RPATH tags on our library header as well as other system-specific search + // paths. See the man page for dlopen(3) on your system for more information. + +#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 + // first attempt: the canonical name is libssl.so. + libssl->setFileNameAndVersion(QLatin1String("ssl"), QLatin1String(SHLIB_VERSION_NUMBER)); + libcrypto->setFileNameAndVersion(QLatin1String("crypto"), QLatin1String(SHLIB_VERSION_NUMBER)); + if (libcrypto->load() && libssl->load()) { + // libssl.so. and libcrypto.so. found + return result; + } else { + libssl->unload(); + libcrypto->unload(); + } +#endif + +#ifndef Q_OS_DARWIN + // second attempt: find the development files libssl.so and libcrypto.so + // + // disabled on macOS/iOS: + // macOS's /usr/lib/libssl.dylib, /usr/lib/libcrypto.dylib will be picked up in the third + // attempt, _after_ /Contents/Frameworks has been searched. + // iOS does not ship a system libssl.dylib, libcrypto.dylib in the first place. +# if defined(Q_OS_ANDROID) + // OpenSSL 1.1.x must be suffixed otherwise it will use the system libcrypto.so libssl.so which on API-21 are OpenSSL 1.0 not 1.1 + auto openSSLSuffix = [](const QByteArray &defaultSuffix = {}) { + auto suffix = qgetenv("ANDROID_OPENSSL_SUFFIX"); + if (suffix.isEmpty()) + return defaultSuffix; + return suffix; + }; + + static QString suffix = QString::fromLatin1(openSSLSuffix("_1_1")); + + libssl->setFileNameAndVersion(QLatin1String("ssl") + suffix, -1); + libcrypto->setFileNameAndVersion(QLatin1String("crypto") + suffix, -1); +# else + libssl->setFileNameAndVersion(QLatin1String("ssl"), -1); + libcrypto->setFileNameAndVersion(QLatin1String("crypto"), -1); +# endif + if (libcrypto->load() && libssl->load()) { + // libssl.so.0 and libcrypto.so.0 found + return result; + } else { + libssl->unload(); + libcrypto->unload(); + } +#endif + + // third attempt: loop on the most common library paths and find libssl + const QStringList sslList = findAllLibSsl(); + const QStringList cryptoList = findAllLibCrypto(); + + for (const QString &crypto : cryptoList) { + libcrypto->setFileNameAndVersion(crypto, -1); + if (libcrypto->load()) { + QFileInfo fi(crypto); + QString version = fi.completeSuffix(); + + for (const QString &ssl : sslList) { + if (!ssl.endsWith(version)) + continue; + + libssl->setFileNameAndVersion(ssl, -1); + + if (libssl->load()) { + // libssl.so.x and libcrypto.so.x found + return result; + } else { + libssl->unload(); + } + } + } + libcrypto->unload(); + } + + // failed to load anything + result = {}; + return result; + +# else + // not implemented for this platform yet + return result; +# endif +} +#endif + +static QBasicMutex symbolResolveMutex; +static QBasicAtomicInt symbolsResolved = Q_BASIC_ATOMIC_INITIALIZER(false); +static bool triedToResolveSymbols = false; + +bool q_resolveOpenSslSymbols() +{ + if (symbolsResolved.loadAcquire()) + return true; + QMutexLocker locker(&symbolResolveMutex); + if (symbolsResolved.loadRelaxed()) + return true; + if (triedToResolveSymbols) + return false; + triedToResolveSymbols = true; + + LoadedOpenSsl libs = loadOpenSsl(); + if (!libs.ssl || !libs.crypto) + // failed to load them + return false; + + RESOLVEFUNC(OPENSSL_init_ssl) + RESOLVEFUNC(OPENSSL_init_crypto) + RESOLVEFUNC(ASN1_STRING_get0_data) + RESOLVEFUNC(EVP_CIPHER_CTX_reset) + RESOLVEFUNC(EVP_PKEY_up_ref) + RESOLVEFUNC(EVP_PKEY_CTX_new) + RESOLVEFUNC(EVP_PKEY_param_check) + RESOLVEFUNC(EVP_PKEY_CTX_free) + RESOLVEFUNC(EVP_PKEY_base_id) + RESOLVEFUNC(RSA_bits) + RESOLVEFUNC(OPENSSL_sk_new_null) + RESOLVEFUNC(OPENSSL_sk_push) + RESOLVEFUNC(OPENSSL_sk_free) + 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) + RESOLVEFUNC(SSL_alert_desc_string_long) + RESOLVEFUNC(SSL_CTX_get_security_level) + RESOLVEFUNC(SSL_CTX_set_security_level) +#ifdef TLS1_3_VERSION + RESOLVEFUNC(SSL_CTX_set_ciphersuites) + RESOLVEFUNC(SSL_set_psk_use_session_callback) + RESOLVEFUNC(SSL_CTX_sess_set_new_cb) + RESOLVEFUNC(SSL_SESSION_is_resumable) +#endif // TLS 1.3 or OpenSSL > 1.1.1 + + RESOLVEFUNC(SSL_get_client_random) + RESOLVEFUNC(SSL_SESSION_get_master_key) + RESOLVEFUNC(SSL_session_reused) + RESOLVEFUNC(SSL_get_session) + RESOLVEFUNC(SSL_set_options) + RESOLVEFUNC(CRYPTO_get_ex_new_index) + RESOLVEFUNC(TLS_method) + RESOLVEFUNC(TLS_client_method) + RESOLVEFUNC(TLS_server_method) + RESOLVEFUNC(X509_up_ref) + RESOLVEFUNC(X509_STORE_CTX_get0_chain) + RESOLVEFUNC(X509_getm_notBefore) + RESOLVEFUNC(X509_getm_notAfter) + RESOLVEFUNC(X509_get_version) + RESOLVEFUNC(X509_get_pubkey) + RESOLVEFUNC(X509_STORE_set_verify_cb) + RESOLVEFUNC(X509_STORE_set_ex_data) + RESOLVEFUNC(X509_STORE_get_ex_data) + RESOLVEFUNC(CRYPTO_free) + RESOLVEFUNC(OpenSSL_version_num) + RESOLVEFUNC(OpenSSL_version) + + if (!_q_OpenSSL_version) { + // Apparently, we were built with OpenSSL 1.1 enabled but are now using + // a wrong library. + qCWarning(lcTlsBackend, "Incompatible version of OpenSSL"); + return false; + } + + RESOLVEFUNC(SSL_SESSION_get_ticket_lifetime_hint) + RESOLVEFUNC(DH_bits) + RESOLVEFUNC(DSA_bits) + +#if QT_CONFIG(dtls) + RESOLVEFUNC(DTLSv1_listen) + RESOLVEFUNC(BIO_ADDR_new) + RESOLVEFUNC(BIO_ADDR_free) + RESOLVEFUNC(BIO_meth_new) + RESOLVEFUNC(BIO_meth_free) + RESOLVEFUNC(BIO_meth_set_write) + RESOLVEFUNC(BIO_meth_set_read) + RESOLVEFUNC(BIO_meth_set_puts) + RESOLVEFUNC(BIO_meth_set_ctrl) + RESOLVEFUNC(BIO_meth_set_create) + RESOLVEFUNC(BIO_meth_set_destroy) +#endif // dtls + +#if QT_CONFIG(ocsp) + RESOLVEFUNC(OCSP_SINGLERESP_get0_id) + RESOLVEFUNC(d2i_OCSP_RESPONSE) + RESOLVEFUNC(OCSP_RESPONSE_free) + RESOLVEFUNC(OCSP_response_status) + RESOLVEFUNC(OCSP_response_get1_basic) + RESOLVEFUNC(OCSP_BASICRESP_free) + RESOLVEFUNC(OCSP_basic_verify) + RESOLVEFUNC(OCSP_resp_count) + RESOLVEFUNC(OCSP_resp_get0) + RESOLVEFUNC(OCSP_single_get0_status) + RESOLVEFUNC(OCSP_check_validity) + RESOLVEFUNC(OCSP_cert_to_id) + RESOLVEFUNC(OCSP_id_get0_info) + RESOLVEFUNC(OCSP_resp_get0_certs) + RESOLVEFUNC(OCSP_basic_sign) + RESOLVEFUNC(OCSP_response_create) + RESOLVEFUNC(i2d_OCSP_RESPONSE) + RESOLVEFUNC(OCSP_basic_add1_status) + RESOLVEFUNC(OCSP_BASICRESP_new) + RESOLVEFUNC(OCSP_CERTID_free) + RESOLVEFUNC(OCSP_cert_to_id) + RESOLVEFUNC(OCSP_id_cmp) +#endif // ocsp + + RESOLVEFUNC(BIO_set_data) + RESOLVEFUNC(BIO_get_data) + RESOLVEFUNC(BIO_set_init) + RESOLVEFUNC(BIO_get_shutdown) + RESOLVEFUNC(BIO_set_shutdown) + RESOLVEFUNC(ASN1_INTEGER_get) + RESOLVEFUNC(ASN1_INTEGER_cmp) + RESOLVEFUNC(ASN1_STRING_length) + RESOLVEFUNC(ASN1_STRING_to_UTF8) + RESOLVEFUNC(ASN1_TIME_to_tm) + RESOLVEFUNC(BIO_ctrl) + RESOLVEFUNC(BIO_free) + RESOLVEFUNC(BIO_new) + RESOLVEFUNC(BIO_new_mem_buf) + RESOLVEFUNC(BIO_read) + RESOLVEFUNC(BIO_s_mem) + RESOLVEFUNC(BIO_write) + RESOLVEFUNC(BIO_set_flags) + RESOLVEFUNC(BIO_clear_flags) + RESOLVEFUNC(BIO_set_ex_data) + RESOLVEFUNC(BIO_get_ex_data) + +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(EC_KEY_get0_group) + RESOLVEFUNC(EC_GROUP_get_degree) +#endif + RESOLVEFUNC(BN_num_bits) + RESOLVEFUNC(BN_is_word) + RESOLVEFUNC(BN_mod_word) + RESOLVEFUNC(DSA_new) + RESOLVEFUNC(DSA_free) + RESOLVEFUNC(ERR_error_string) + RESOLVEFUNC(ERR_error_string_n) + RESOLVEFUNC(ERR_get_error) + RESOLVEFUNC(EVP_CIPHER_CTX_new) + RESOLVEFUNC(EVP_CIPHER_CTX_free) + RESOLVEFUNC(EVP_CIPHER_CTX_ctrl) + RESOLVEFUNC(EVP_CIPHER_CTX_set_key_length) + RESOLVEFUNC(EVP_CipherInit) + RESOLVEFUNC(EVP_CipherInit_ex) + RESOLVEFUNC(EVP_CipherUpdate) + RESOLVEFUNC(EVP_CipherFinal) + RESOLVEFUNC(EVP_get_digestbyname) +#ifndef OPENSSL_NO_DES + RESOLVEFUNC(EVP_des_cbc) + RESOLVEFUNC(EVP_des_ede3_cbc) +#endif +#ifndef OPENSSL_NO_RC2 + RESOLVEFUNC(EVP_rc2_cbc) +#endif +#ifndef OPENSSL_NO_AES + RESOLVEFUNC(EVP_aes_128_cbc) + RESOLVEFUNC(EVP_aes_192_cbc) + RESOLVEFUNC(EVP_aes_256_cbc) +#endif + RESOLVEFUNC(EVP_sha1) + RESOLVEFUNC(EVP_PKEY_assign) + RESOLVEFUNC(EVP_PKEY_set1_RSA) + RESOLVEFUNC(EVP_PKEY_set1_DSA) + RESOLVEFUNC(EVP_PKEY_set1_DH) + +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(EVP_PKEY_set1_EC_KEY) + RESOLVEFUNC(EVP_PKEY_get1_EC_KEY) + RESOLVEFUNC(PEM_read_bio_ECPrivateKey) + RESOLVEFUNC(PEM_write_bio_ECPrivateKey) + RESOLVEFUNC(PEM_read_bio_EC_PUBKEY) + RESOLVEFUNC(PEM_write_bio_EC_PUBKEY) +#endif // OPENSSL_NO_EC + + RESOLVEFUNC(EVP_PKEY_cmp) + RESOLVEFUNC(EVP_PKEY_free) + RESOLVEFUNC(EVP_PKEY_get1_DSA) + RESOLVEFUNC(EVP_PKEY_get1_RSA) + RESOLVEFUNC(EVP_PKEY_get1_DH) + RESOLVEFUNC(EVP_PKEY_new) + RESOLVEFUNC(EVP_PKEY_type) + RESOLVEFUNC(OBJ_nid2sn) + RESOLVEFUNC(OBJ_nid2ln) + RESOLVEFUNC(OBJ_sn2nid) + RESOLVEFUNC(OBJ_ln2nid) + RESOLVEFUNC(i2t_ASN1_OBJECT) + RESOLVEFUNC(OBJ_obj2txt) + RESOLVEFUNC(OBJ_obj2nid) + RESOLVEFUNC(PEM_read_bio_PrivateKey) + RESOLVEFUNC(PEM_read_bio_DSAPrivateKey) + RESOLVEFUNC(PEM_read_bio_RSAPrivateKey) + RESOLVEFUNC(PEM_read_bio_DHparams) + RESOLVEFUNC(PEM_write_bio_DSAPrivateKey) + RESOLVEFUNC(PEM_write_bio_RSAPrivateKey) + RESOLVEFUNC(PEM_write_bio_PrivateKey) + RESOLVEFUNC(PEM_read_bio_PUBKEY) + RESOLVEFUNC(PEM_read_bio_DSA_PUBKEY) + RESOLVEFUNC(PEM_read_bio_RSA_PUBKEY) + RESOLVEFUNC(PEM_write_bio_DSA_PUBKEY) + RESOLVEFUNC(PEM_write_bio_RSA_PUBKEY) + RESOLVEFUNC(PEM_write_bio_PUBKEY) + RESOLVEFUNC(RAND_seed) + RESOLVEFUNC(RAND_status) + RESOLVEFUNC(RAND_bytes) + RESOLVEFUNC(RSA_new) + RESOLVEFUNC(RSA_free) + RESOLVEFUNC(SSL_CIPHER_description) + RESOLVEFUNC(SSL_CIPHER_get_bits) + RESOLVEFUNC(SSL_get_rbio) + RESOLVEFUNC(SSL_CTX_check_private_key) + RESOLVEFUNC(SSL_CTX_ctrl) + RESOLVEFUNC(SSL_CTX_free) + RESOLVEFUNC(SSL_CTX_new) + RESOLVEFUNC(SSL_CTX_set_cipher_list) + RESOLVEFUNC(SSL_CTX_callback_ctrl) + RESOLVEFUNC(SSL_CTX_set_default_verify_paths) + RESOLVEFUNC(SSL_CTX_set_verify) + RESOLVEFUNC(SSL_CTX_set_verify_depth) + RESOLVEFUNC(SSL_CTX_use_certificate) + RESOLVEFUNC(SSL_CTX_use_certificate_file) + RESOLVEFUNC(SSL_CTX_use_PrivateKey) + RESOLVEFUNC(SSL_CTX_use_RSAPrivateKey) + RESOLVEFUNC(SSL_CTX_use_PrivateKey_file) + RESOLVEFUNC(SSL_CTX_get_cert_store); + RESOLVEFUNC(SSL_CONF_CTX_new); + RESOLVEFUNC(SSL_CONF_CTX_free); + RESOLVEFUNC(SSL_CONF_CTX_set_ssl_ctx); + RESOLVEFUNC(SSL_CONF_CTX_set_flags); + RESOLVEFUNC(SSL_CONF_CTX_finish); + RESOLVEFUNC(SSL_CONF_cmd); + RESOLVEFUNC(SSL_accept) + RESOLVEFUNC(SSL_clear) + RESOLVEFUNC(SSL_connect) + RESOLVEFUNC(SSL_free) + RESOLVEFUNC(SSL_get_ciphers) + RESOLVEFUNC(SSL_get_current_cipher) + RESOLVEFUNC(SSL_version) + RESOLVEFUNC(SSL_get_error) + RESOLVEFUNC(SSL_get_peer_cert_chain) + RESOLVEFUNC(SSL_get_peer_certificate) + RESOLVEFUNC(SSL_get_verify_result) + RESOLVEFUNC(SSL_new) + RESOLVEFUNC(SSL_get_SSL_CTX) + RESOLVEFUNC(SSL_ctrl) + RESOLVEFUNC(SSL_read) + RESOLVEFUNC(SSL_set_accept_state) + RESOLVEFUNC(SSL_set_bio) + RESOLVEFUNC(SSL_set_connect_state) + RESOLVEFUNC(SSL_shutdown) + RESOLVEFUNC(SSL_in_init) + RESOLVEFUNC(SSL_get_shutdown) + RESOLVEFUNC(SSL_set_session) + RESOLVEFUNC(SSL_SESSION_free) + RESOLVEFUNC(SSL_get1_session) + RESOLVEFUNC(SSL_get_session) + RESOLVEFUNC(SSL_set_ex_data) + RESOLVEFUNC(SSL_get_ex_data) + RESOLVEFUNC(SSL_get_ex_data_X509_STORE_CTX_idx) + +#ifndef OPENSSL_NO_PSK + RESOLVEFUNC(SSL_set_psk_client_callback) + RESOLVEFUNC(SSL_set_psk_server_callback) + RESOLVEFUNC(SSL_CTX_use_psk_identity_hint) +#endif // !OPENSSL_NO_PSK + + RESOLVEFUNC(SSL_write) + RESOLVEFUNC(X509_NAME_entry_count) + RESOLVEFUNC(X509_NAME_get_entry) + RESOLVEFUNC(X509_NAME_ENTRY_get_data) + RESOLVEFUNC(X509_NAME_ENTRY_get_object) + RESOLVEFUNC(X509_PUBKEY_get) + RESOLVEFUNC(X509_STORE_free) + RESOLVEFUNC(X509_STORE_new) + RESOLVEFUNC(X509_STORE_add_cert) + RESOLVEFUNC(X509_STORE_CTX_free) + RESOLVEFUNC(X509_STORE_CTX_init) + RESOLVEFUNC(X509_STORE_CTX_new) + RESOLVEFUNC(X509_STORE_CTX_set_purpose) + RESOLVEFUNC(X509_STORE_CTX_get_error) + RESOLVEFUNC(X509_STORE_CTX_get_error_depth) + RESOLVEFUNC(X509_STORE_CTX_get_current_cert) + RESOLVEFUNC(X509_STORE_CTX_get0_store) + RESOLVEFUNC(X509_cmp) + RESOLVEFUNC(X509_STORE_CTX_get_ex_data) + RESOLVEFUNC(X509_dup) + RESOLVEFUNC(X509_print) + RESOLVEFUNC(X509_digest) + RESOLVEFUNC(X509_EXTENSION_get_object) + RESOLVEFUNC(X509_free) + RESOLVEFUNC(X509_gmtime_adj) + RESOLVEFUNC(ASN1_TIME_free) + RESOLVEFUNC(X509_get_ext) + RESOLVEFUNC(X509_get_ext_count) + RESOLVEFUNC(X509_get_ext_d2i) + RESOLVEFUNC(X509V3_EXT_get) + RESOLVEFUNC(X509V3_EXT_d2i) + RESOLVEFUNC(X509_EXTENSION_get_critical) + RESOLVEFUNC(X509_EXTENSION_get_data) + RESOLVEFUNC(BASIC_CONSTRAINTS_free) + RESOLVEFUNC(AUTHORITY_KEYID_free) + RESOLVEFUNC(GENERAL_NAME_free) + RESOLVEFUNC(ASN1_STRING_print) + RESOLVEFUNC(X509_check_issued) + RESOLVEFUNC(X509_get_issuer_name) + RESOLVEFUNC(X509_get_subject_name) + RESOLVEFUNC(X509_get_serialNumber) + RESOLVEFUNC(X509_verify_cert) + RESOLVEFUNC(d2i_X509) + RESOLVEFUNC(i2d_X509) +#if OPENSSL_VERSION_MAJOR < 3 + RESOLVEFUNC(SSL_CTX_load_verify_locations) +#else + RESOLVEFUNC(SSL_CTX_load_verify_dir) +#endif // OPENSSL_VERSION_MAJOR + RESOLVEFUNC(i2d_SSL_SESSION) + RESOLVEFUNC(d2i_SSL_SESSION) + +#ifndef OPENSSL_NO_NEXTPROTONEG + RESOLVEFUNC(SSL_select_next_proto) + RESOLVEFUNC(SSL_CTX_set_next_proto_select_cb) + RESOLVEFUNC(SSL_get0_next_proto_negotiated) + RESOLVEFUNC(SSL_set_alpn_protos) + RESOLVEFUNC(SSL_CTX_set_alpn_select_cb) + RESOLVEFUNC(SSL_get0_alpn_selected) +#endif // !OPENSSL_NO_NEXTPROTONEG + +#if QT_CONFIG(dtls) + RESOLVEFUNC(SSL_CTX_set_cookie_generate_cb) + RESOLVEFUNC(SSL_CTX_set_cookie_verify_cb) + RESOLVEFUNC(DTLS_server_method) + RESOLVEFUNC(DTLS_client_method) +#endif // dtls + + RESOLVEFUNC(CRYPTO_malloc) + RESOLVEFUNC(DH_new) + RESOLVEFUNC(DH_free) + RESOLVEFUNC(d2i_DHparams) + RESOLVEFUNC(i2d_DHparams) +#ifndef OPENSSL_NO_DEPRECATED_3_0 + RESOLVEFUNC(DH_check) +#endif // OPENSSL_NO_DEPRECATED_3_0 + RESOLVEFUNC(BN_bin2bn) + +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(EC_KEY_dup) + RESOLVEFUNC(EC_KEY_new_by_curve_name) + RESOLVEFUNC(EC_KEY_free) + RESOLVEFUNC(EC_get_builtin_curves) +#endif // OPENSSL_NO_EC + + RESOLVEFUNC(PKCS12_parse) + RESOLVEFUNC(d2i_PKCS12_bio) + RESOLVEFUNC(PKCS12_free) + + symbolsResolved.storeRelease(true); + return true; +} +#endif // QT_CONFIG(library) + +#else // !defined QT_LINKED_OPENSSL + +bool q_resolveOpenSslSymbols() +{ +#ifdef QT_NO_OPENSSL + return false; +#endif + return true; +} +#endif // !defined QT_LINKED_OPENSSL + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h b/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h new file mode 100644 index 0000000000..1eb6387c23 --- /dev/null +++ b/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h @@ -0,0 +1,759 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** 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$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#ifndef QSSLSOCKET_OPENSSL_SYMBOLS_P_H +#define QSSLSOCKET_OPENSSL_SYMBOLS_P_H + + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "qopenssl_p.h" + +#include + +#if QT_CONFIG(ocsp) +#include +#endif + +QT_BEGIN_NAMESPACE + +#define DUMMYARG + +#if !defined QT_LINKED_OPENSSL +// **************** Shared declarations ****************** +// ret func(arg) + +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a); \ + } + +// ret func(arg1, arg2) +# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func);\ + err; \ + } \ + funcret _q_##func(a, b); \ + } + +// ret func(arg1, arg2, arg3) +# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c); \ + } + +// ret func(arg1, arg2, arg3, arg4) +# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg5) +# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6) +# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7) +# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f, g); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) +# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + static _q_PTR_##func _q_##func = nullptr; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f, g, h, i); \ + } +// **************** Shared declarations ****************** + +#else // !defined QT_LINKED_OPENSSL + +// **************** Static declarations ****************** + +// ret func(arg) +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + ret q_##func(arg) { funcret func(a); } + +// ret func(arg1, arg2) +# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ + ret q_##func(arg1, arg2) { funcret func(a, b); } + +// ret func(arg1, arg2, arg3) +# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ + ret q_##func(arg1, arg2, arg3) { funcret func(a, b, c); } + +// ret func(arg1, arg2, arg3, arg4) +# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4) { funcret func(a, b, c, d); } + +// ret func(arg1, arg2, arg3, arg4, arg5) +# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5) { funcret func(a, b, c, d, e); } + +// ret func(arg1, arg2, arg3, arg4, arg6) +# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { funcret func(a, b, c, d, e, f); } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7) +# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { funcret func(a, b, c, d, e, f, g); } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) +# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { funcret func(a, b, c, d, e, f, g, h, i); } + +// **************** Static declarations ****************** + +#endif // !defined QT_LINKED_OPENSSL + +// TODO: the following lines previously were a part of 1.1 - specific header. +// To reduce the amount of the change, I'm directly copying and pasting the +// content of the header here. Later, can be better sorted/split into groups, +// depending on the functionality. + +const unsigned char * q_ASN1_STRING_get0_data(const ASN1_STRING *x); + +BIO *q_BIO_new(const BIO_METHOD *a); +const BIO_METHOD *q_BIO_s_mem(); + +int q_DSA_bits(DSA *a); +int q_EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *c); +int q_EVP_PKEY_up_ref(EVP_PKEY *a); +EVP_PKEY_CTX *q_EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); +void q_EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); +int q_EVP_PKEY_param_check(EVP_PKEY_CTX *ctx); +int q_EVP_PKEY_base_id(EVP_PKEY *a); +int q_RSA_bits(RSA *a); +int q_OPENSSL_sk_num(OPENSSL_STACK *a); +void q_OPENSSL_sk_pop_free(OPENSSL_STACK *a, void (*b)(void *)); +OPENSSL_STACK *q_OPENSSL_sk_new_null(); +void q_OPENSSL_sk_push(OPENSSL_STACK *st, void *data); +void q_OPENSSL_sk_free(OPENSSL_STACK *a); +void * q_OPENSSL_sk_value(OPENSSL_STACK *a, int b); +int q_SSL_session_reused(SSL *a); +unsigned long q_SSL_CTX_set_options(SSL_CTX *ctx, unsigned long op); +int q_OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); +size_t q_SSL_get_client_random(SSL *a, unsigned char *out, size_t outlen); +size_t q_SSL_SESSION_get_master_key(const SSL_SESSION *session, unsigned char *out, size_t outlen); +int q_CRYPTO_get_ex_new_index(int class_index, long argl, void *argp, CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); +const SSL_METHOD *q_TLS_method(); +const SSL_METHOD *q_TLS_client_method(); +const SSL_METHOD *q_TLS_server_method(); +ASN1_TIME *q_X509_getm_notBefore(X509 *a); +ASN1_TIME *q_X509_getm_notAfter(X509 *a); + +void q_X509_up_ref(X509 *a); +long q_X509_get_version(X509 *a); +EVP_PKEY *q_X509_get_pubkey(X509 *a); +void q_X509_STORE_set_verify_cb(X509_STORE *ctx, X509_STORE_CTX_verify_cb verify_cb); +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); +int q_DH_bits(DH *dh); + +# define q_SSL_load_error_strings() q_OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS \ + | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL) + +#define q_SKM_sk_num(st) q_OPENSSL_sk_num((OPENSSL_STACK *)st) +#define q_SKM_sk_value(type, st,i) (type *)q_OPENSSL_sk_value((OPENSSL_STACK *)st, i) + +#define q_OPENSSL_add_all_algorithms_conf() q_OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ + | OPENSSL_INIT_ADD_ALL_DIGESTS \ + | OPENSSL_INIT_LOAD_CONFIG, NULL) +#define q_OPENSSL_add_all_algorithms_noconf() q_OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ + | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL) + +int q_OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); +void q_CRYPTO_free(void *str, const char *file, int line); + +long q_OpenSSL_version_num(); +const char *q_OpenSSL_version(int type); + +unsigned long q_SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *session); +unsigned long q_SSL_set_options(SSL *s, unsigned long op); + +#ifdef TLS1_3_VERSION +int q_SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str); + +// The functions below do not really have to be ifdefed like this, but for now +// they only used in TLS 1.3 handshake (and probably future versions). +// Plus, 'is resumalbe' is OpenSSL 1.1.1-only (and again we need it for +// TLS 1.3-specific session management). + +extern "C" +{ +using NewSessionCallback = int (*)(SSL *, SSL_SESSION *); +} + +void q_SSL_CTX_sess_set_new_cb(SSL_CTX *ctx, NewSessionCallback cb); +int q_SSL_SESSION_is_resumable(const SSL_SESSION *s); + +#define q_SSL_CTX_set_session_cache_mode(ctx,m) \ + q_SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SESS_CACHE_MODE,m,NULL) + +#endif + +#if QT_CONFIG(dtls) +// Functions and types required for DTLS support: +extern "C" +{ + +typedef int (*CookieVerifyCallback)(SSL *, const unsigned char *, unsigned); +typedef int (*DgramWriteCallback) (BIO *, const char *, int); +typedef int (*DgramReadCallback) (BIO *, char *, int); +typedef int (*DgramPutsCallback) (BIO *, const char *); +typedef long (*DgramCtrlCallback) (BIO *, int, long, void *); +typedef int (*DgramCreateCallback) (BIO *); +typedef int (*DgramDestroyCallback) (BIO *); + +} + +int q_DTLSv1_listen(SSL *s, BIO_ADDR *client); +BIO_ADDR *q_BIO_ADDR_new(); +void q_BIO_ADDR_free(BIO_ADDR *ap); + +// API we need for a custom dgram BIO: + +BIO_METHOD *q_BIO_meth_new(int type, const char *name); +void q_BIO_meth_free(BIO_METHOD *biom); +int q_BIO_meth_set_write(BIO_METHOD *biom, DgramWriteCallback); +int q_BIO_meth_set_read(BIO_METHOD *biom, DgramReadCallback); +int q_BIO_meth_set_puts(BIO_METHOD *biom, DgramPutsCallback); +int q_BIO_meth_set_ctrl(BIO_METHOD *biom, DgramCtrlCallback); +int q_BIO_meth_set_create(BIO_METHOD *biom, DgramCreateCallback); +int q_BIO_meth_set_destroy(BIO_METHOD *biom, DgramDestroyCallback); + +#endif // dtls + +void q_BIO_set_data(BIO *a, void *ptr); +void *q_BIO_get_data(BIO *a); +void q_BIO_set_init(BIO *a, int init); +int q_BIO_get_shutdown(BIO *a); +void q_BIO_set_shutdown(BIO *a, int shut); + +#if QT_CONFIG(ocsp) +const OCSP_CERTID *q_OCSP_SINGLERESP_get0_id(const OCSP_SINGLERESP *x); +#endif // ocsp + +#define q_SSL_CTX_set_min_proto_version(ctx, version) \ + q_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, version, nullptr) + +#define q_SSL_CTX_set_max_proto_version(ctx, version) \ + q_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MAX_PROTO_VERSION, version, nullptr) + +extern "C" { +typedef int (*q_SSL_psk_use_session_cb_func_t)(SSL *, const EVP_MD *, const unsigned char **, size_t *, + SSL_SESSION **); +} +void q_SSL_set_psk_use_session_callback(SSL *s, q_SSL_psk_use_session_cb_func_t); +// Here the content of the 1.1 header ends. + +bool q_resolveOpenSslSymbols(); +long q_ASN1_INTEGER_get(ASN1_INTEGER *a); +int q_ASN1_INTEGER_cmp(const ASN1_INTEGER *x, const ASN1_INTEGER *y); +int q_ASN1_STRING_length(ASN1_STRING *a); +int q_ASN1_STRING_to_UTF8(unsigned char **a, ASN1_STRING *b); +int q_ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm); +long q_BIO_ctrl(BIO *a, int b, long c, void *d); +int q_BIO_free(BIO *a); +BIO *q_BIO_new_mem_buf(void *a, int b); +int q_BIO_read(BIO *a, void *b, int c); +int q_BIO_write(BIO *a, const void *b, int c); +int q_BN_num_bits(const BIGNUM *a); +int q_BN_is_word(BIGNUM *a, BN_ULONG w); +BN_ULONG q_BN_mod_word(const BIGNUM *a, BN_ULONG w); + +#ifndef OPENSSL_NO_EC +const EC_GROUP* q_EC_KEY_get0_group(const EC_KEY* k); +int q_EC_GROUP_get_degree(const EC_GROUP* g); +#endif // OPENSSL_NO_EC + +DSA *q_DSA_new(); +void q_DSA_free(DSA *a); +X509 *q_d2i_X509(X509 **a, const unsigned char **b, long c); +char *q_ERR_error_string(unsigned long a, char *b); +void q_ERR_error_string_n(unsigned long e, char *buf, size_t len); +unsigned long q_ERR_get_error(); +EVP_CIPHER_CTX *q_EVP_CIPHER_CTX_new(); +void q_EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *a); +int q_EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr); +int q_EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *x, int keylen); +int q_EVP_CipherInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, const unsigned char *key, const unsigned char *iv, int enc); +int q_EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv, int enc); +int q_EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl); +int q_EVP_CipherFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); +const EVP_MD *q_EVP_get_digestbyname(const char *name); + +#ifndef OPENSSL_NO_DES +const EVP_CIPHER *q_EVP_des_cbc(); +const EVP_CIPHER *q_EVP_des_ede3_cbc(); +#endif // OPENSSL_NO_DES + +#ifndef OPENSSL_NO_RC2 +const EVP_CIPHER *q_EVP_rc2_cbc(); +#endif // OPENSSL_NO_RC2 + +#ifndef OPENSSL_NO_AES +const EVP_CIPHER *q_EVP_aes_128_cbc(); +const EVP_CIPHER *q_EVP_aes_192_cbc(); +const EVP_CIPHER *q_EVP_aes_256_cbc(); +#endif // OPENSSL_NO_AES + +const EVP_MD *q_EVP_sha1(); +int q_EVP_PKEY_assign(EVP_PKEY *a, int b, void *r); +int q_EVP_PKEY_set1_RSA(EVP_PKEY *a, RSA *b); +int q_EVP_PKEY_set1_DSA(EVP_PKEY *a, DSA *b); +int q_EVP_PKEY_set1_DH(EVP_PKEY *a, DH *b); + +#ifndef OPENSSL_NO_EC +int q_EVP_PKEY_set1_EC_KEY(EVP_PKEY *a, EC_KEY *b); +#endif + +int q_EVP_PKEY_cmp(const EVP_PKEY *a, const EVP_PKEY *b); +void q_EVP_PKEY_free(EVP_PKEY *a); +RSA *q_EVP_PKEY_get1_RSA(EVP_PKEY *a); +DSA *q_EVP_PKEY_get1_DSA(EVP_PKEY *a); +DH *q_EVP_PKEY_get1_DH(EVP_PKEY *a); +#ifndef OPENSSL_NO_EC +EC_KEY *q_EVP_PKEY_get1_EC_KEY(EVP_PKEY *a); +#endif +int q_EVP_PKEY_type(int a); +EVP_PKEY *q_EVP_PKEY_new(); +int q_i2d_X509(X509 *a, unsigned char **b); +const char *q_OBJ_nid2sn(int a); +const char *q_OBJ_nid2ln(int a); +int q_OBJ_sn2nid(const char *s); +int q_OBJ_ln2nid(const char *s); +int q_i2t_ASN1_OBJECT(char *buf, int buf_len, ASN1_OBJECT *obj); +int q_OBJ_obj2txt(char *buf, int buf_len, ASN1_OBJECT *obj, int no_name); +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); +DSA *q_PEM_read_bio_DSAPrivateKey(BIO *a, DSA **b, pem_password_cb *c, void *d); +RSA *q_PEM_read_bio_RSAPrivateKey(BIO *a, RSA **b, pem_password_cb *c, void *d); + +#ifndef OPENSSL_NO_EC +EC_KEY *q_PEM_read_bio_ECPrivateKey(BIO *a, EC_KEY **b, pem_password_cb *c, void *d); +int q_PEM_write_bio_ECPrivateKey(BIO *a, EC_KEY *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +EC_KEY *q_PEM_read_bio_EC_PUBKEY(BIO *a, EC_KEY **b, pem_password_cb *c, void *d); +int q_PEM_write_bio_EC_PUBKEY(BIO *a, EC_KEY *b); +#endif // OPENSSL_NO_EC + +DH *q_PEM_read_bio_DHparams(BIO *a, DH **b, pem_password_cb *c, void *d); +int q_PEM_write_bio_DSAPrivateKey(BIO *a, DSA *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +int q_PEM_write_bio_RSAPrivateKey(BIO *a, RSA *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +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); +EVP_PKEY *q_PEM_read_bio_PUBKEY(BIO *a, EVP_PKEY **b, pem_password_cb *c, void *d); +DSA *q_PEM_read_bio_DSA_PUBKEY(BIO *a, DSA **b, pem_password_cb *c, void *d); +RSA *q_PEM_read_bio_RSA_PUBKEY(BIO *a, RSA **b, pem_password_cb *c, void *d); +int q_PEM_write_bio_DSA_PUBKEY(BIO *a, DSA *b); +int q_PEM_write_bio_RSA_PUBKEY(BIO *a, RSA *b); +int q_PEM_write_bio_PUBKEY(BIO *a, EVP_PKEY *b); + +void q_RAND_seed(const void *a, int b); +int q_RAND_status(); +int q_RAND_bytes(unsigned char *b, int n); +RSA *q_RSA_new(); +void q_RSA_free(RSA *a); +int q_SSL_accept(SSL *a); +int q_SSL_clear(SSL *a); +char *q_SSL_CIPHER_description(const SSL_CIPHER *a, char *b, int c); +int q_SSL_CIPHER_get_bits(const SSL_CIPHER *a, int *b); +BIO *q_SSL_get_rbio(const SSL *s); +int q_SSL_connect(SSL *a); +int q_SSL_CTX_check_private_key(const SSL_CTX *a); +long q_SSL_CTX_ctrl(SSL_CTX *a, int b, long c, void *d); +void q_SSL_CTX_free(SSL_CTX *a); +SSL_CTX *q_SSL_CTX_new(const SSL_METHOD *a); +int q_SSL_CTX_set_cipher_list(SSL_CTX *a, const char *b); +int q_SSL_CTX_set_default_verify_paths(SSL_CTX *a); +void q_SSL_CTX_set_verify(SSL_CTX *a, int b, int (*c)(int, X509_STORE_CTX *)); +void q_SSL_CTX_set_verify_depth(SSL_CTX *a, int b); +extern "C" { +typedef void (*GenericCallbackType)(); +} +long q_SSL_CTX_callback_ctrl(SSL_CTX *, int, GenericCallbackType); +int q_SSL_CTX_use_certificate(SSL_CTX *a, X509 *b); +int q_SSL_CTX_use_certificate_file(SSL_CTX *a, const char *b, int c); +int q_SSL_CTX_use_PrivateKey(SSL_CTX *a, EVP_PKEY *b); +int q_SSL_CTX_use_RSAPrivateKey(SSL_CTX *a, RSA *b); +int q_SSL_CTX_use_PrivateKey_file(SSL_CTX *a, const char *b, int c); +X509_STORE *q_SSL_CTX_get_cert_store(const SSL_CTX *a); +SSL_CONF_CTX *q_SSL_CONF_CTX_new(); +void q_SSL_CONF_CTX_free(SSL_CONF_CTX *a); +void q_SSL_CONF_CTX_set_ssl_ctx(SSL_CONF_CTX *a, SSL_CTX *b); +unsigned int q_SSL_CONF_CTX_set_flags(SSL_CONF_CTX *a, unsigned int b); +int q_SSL_CONF_CTX_finish(SSL_CONF_CTX *a); +int q_SSL_CONF_cmd(SSL_CONF_CTX *a, const char *b, const char *c); +void q_SSL_free(SSL *a); +STACK_OF(SSL_CIPHER) *q_SSL_get_ciphers(const SSL *a); +const SSL_CIPHER *q_SSL_get_current_cipher(SSL *a); +int q_SSL_version(const SSL *a); +int q_SSL_get_error(SSL *a, int b); +STACK_OF(X509) *q_SSL_get_peer_cert_chain(SSL *a); +X509 *q_SSL_get_peer_certificate(SSL *a); +long q_SSL_get_verify_result(const SSL *a); +SSL *q_SSL_new(SSL_CTX *a); +SSL_CTX *q_SSL_get_SSL_CTX(SSL *a); +long q_SSL_ctrl(SSL *ssl,int cmd, long larg, void *parg); +int q_SSL_read(SSL *a, void *b, int c); +void q_SSL_set_bio(SSL *a, BIO *b, BIO *c); +void q_SSL_set_accept_state(SSL *a); +void q_SSL_set_connect_state(SSL *a); +int q_SSL_shutdown(SSL *a); +int q_SSL_in_init(const SSL *s); +int q_SSL_get_shutdown(const SSL *ssl); +int q_SSL_set_session(SSL *to, SSL_SESSION *session); +void q_SSL_SESSION_free(SSL_SESSION *ses); +SSL_SESSION *q_SSL_get1_session(SSL *ssl); +SSL_SESSION *q_SSL_get_session(const SSL *ssl); +int q_SSL_set_ex_data(SSL *ssl, int idx, void *arg); +void *q_SSL_get_ex_data(const SSL *ssl, int idx); +#ifndef OPENSSL_NO_PSK +typedef unsigned int (*q_psk_client_callback_t)(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len); +void q_SSL_set_psk_client_callback(SSL *ssl, q_psk_client_callback_t callback); +typedef unsigned int (*q_psk_server_callback_t)(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len); +void q_SSL_set_psk_server_callback(SSL *ssl, q_psk_server_callback_t callback); +int q_SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *hint); +#endif // !OPENSSL_NO_PSK +int q_SSL_write(SSL *a, const void *b, int c); +int q_X509_cmp(X509 *a, X509 *b); +X509 *q_X509_dup(X509 *a); +void q_X509_print(BIO *a, X509*b); +int q_X509_digest(const X509 *x509, const EVP_MD *type, unsigned char *md, unsigned int *len); +ASN1_OBJECT *q_X509_EXTENSION_get_object(X509_EXTENSION *a); +void q_X509_free(X509 *a); +ASN1_TIME *q_X509_gmtime_adj(ASN1_TIME *s, long adj); +void q_ASN1_TIME_free(ASN1_TIME *t); +X509_EXTENSION *q_X509_get_ext(X509 *a, int b); +int q_X509_get_ext_count(X509 *a); +void *q_X509_get_ext_d2i(X509 *a, int b, int *c, int *d); +const X509V3_EXT_METHOD *q_X509V3_EXT_get(X509_EXTENSION *a); +void *q_X509V3_EXT_d2i(X509_EXTENSION *a); +int q_X509_EXTENSION_get_critical(X509_EXTENSION *a); +ASN1_OCTET_STRING *q_X509_EXTENSION_get_data(X509_EXTENSION *a); +void q_BASIC_CONSTRAINTS_free(BASIC_CONSTRAINTS *a); +void q_AUTHORITY_KEYID_free(AUTHORITY_KEYID *a); +int q_ASN1_STRING_print(BIO *a, const ASN1_STRING *b); +int q_X509_check_issued(X509 *a, X509 *b); +X509_NAME *q_X509_get_issuer_name(X509 *a); +X509_NAME *q_X509_get_subject_name(X509 *a); +ASN1_INTEGER *q_X509_get_serialNumber(X509 *a); +int q_X509_verify_cert(X509_STORE_CTX *ctx); +int q_X509_NAME_entry_count(X509_NAME *a); +X509_NAME_ENTRY *q_X509_NAME_get_entry(X509_NAME *a,int b); +ASN1_STRING *q_X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *a); +ASN1_OBJECT *q_X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *a); +EVP_PKEY *q_X509_PUBKEY_get(X509_PUBKEY *a); +void q_X509_STORE_free(X509_STORE *store); +X509_STORE *q_X509_STORE_new(); +int q_X509_STORE_add_cert(X509_STORE *ctx, X509 *x); +void q_X509_STORE_CTX_free(X509_STORE_CTX *storeCtx); +int q_X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, + X509 *x509, STACK_OF(X509) *chain); +X509_STORE_CTX *q_X509_STORE_CTX_new(); +int q_X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose); +int q_X509_STORE_CTX_get_error(X509_STORE_CTX *ctx); +int q_X509_STORE_CTX_get_error_depth(X509_STORE_CTX *ctx); +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 +DH *q_DH_new(); +void q_DH_free(DH *dh); +DH *q_d2i_DHparams(DH **a, const unsigned char **pp, long length); +int q_i2d_DHparams(DH *a, unsigned char **p); + +#ifndef OPENSSL_NO_DEPRECATED_3_0 +int q_DH_check(DH *dh, int *codes); +#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) + +#ifndef OPENSSL_NO_EC +// EC Diffie-Hellman support +EC_KEY *q_EC_KEY_dup(const EC_KEY *src); +EC_KEY *q_EC_KEY_new_by_curve_name(int nid); +void q_EC_KEY_free(EC_KEY *ecdh); +#define q_SSL_CTX_set_tmp_ecdh(ctx, ecdh) q_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_TMP_ECDH, 0, (char *)ecdh) + +// EC curves management +size_t q_EC_get_builtin_curves(EC_builtin_curve *r, size_t nitems); +int q_EC_curve_nist2nid(const char *name); +#endif // OPENSSL_NO_EC + +#define q_SSL_get_server_tmp_key(ssl, key) q_SSL_ctrl((ssl), SSL_CTRL_GET_SERVER_TMP_KEY, 0, (char *)key) + +// PKCS#12 support +int q_PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert, STACK_OF(X509) **ca); +PKCS12 *q_d2i_PKCS12_bio(BIO *bio, PKCS12 **pkcs12); +void q_PKCS12_free(PKCS12 *pkcs12); + +#define q_BIO_get_mem_data(b, pp) (int)q_BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp) +#define q_BIO_pending(b) (int)q_BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL) +#define q_SSL_CTX_set_mode(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_MODE,(op),NULL) +#define q_sk_GENERAL_NAME_num(st) q_SKM_sk_num((st)) +#define q_sk_GENERAL_NAME_value(st, i) q_SKM_sk_value(GENERAL_NAME, (st), (i)) + +void q_GENERAL_NAME_free(GENERAL_NAME *a); + +#define q_sk_X509_num(st) q_SKM_sk_num((st)) +#define q_sk_X509_value(st, i) q_SKM_sk_value(X509, (st), (i)) +#define q_sk_SSL_CIPHER_num(st) q_SKM_sk_num((st)) +#define q_sk_SSL_CIPHER_value(st, i) q_SKM_sk_value(SSL_CIPHER, (st), (i)) +#define q_SSL_CTX_add_extra_chain_cert(ctx,x509) \ + q_SSL_CTX_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)x509) +#define q_EVP_PKEY_assign_RSA(pkey,rsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_RSA,\ + (char *)(rsa)) +#define q_EVP_PKEY_assign_DSA(pkey,dsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_DSA,\ + (char *)(dsa)) +#define q_OpenSSL_add_all_algorithms() q_OPENSSL_add_all_algorithms_conf() + +#if OPENSSL_VERSION_MAJOR < 3 +int q_SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath); +#else +int q_SSL_CTX_load_verify_dir(SSL_CTX *ctx, const char *CApath); +#endif // OPENSSL_VERSION_MAJOR + +int q_i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp); +SSL_SESSION *q_d2i_SSL_SESSION(SSL_SESSION **a, const unsigned char **pp, long length); + +#ifndef OPENSSL_NO_NEXTPROTONEG +int q_SSL_select_next_proto(unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const unsigned char *client, unsigned int client_len); +void q_SSL_CTX_set_next_proto_select_cb(SSL_CTX *s, + int (*cb) (SSL *ssl, unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), + void *arg); +void q_SSL_get0_next_proto_negotiated(const SSL *s, const unsigned char **data, + unsigned *len); +int q_SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos, + unsigned protos_len); +void q_SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx, + int (*cb) (SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *arg), void *arg); +void q_SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data, + unsigned *len); +#endif // !OPENSSL_NO_NEXTPROTONEG + + +#if QT_CONFIG(dtls) + +extern "C" +{ +typedef int (*CookieGenerateCallback)(SSL *, unsigned char *, unsigned *); +} + +void q_SSL_CTX_set_cookie_generate_cb(SSL_CTX *ctx, CookieGenerateCallback cb); +void q_SSL_CTX_set_cookie_verify_cb(SSL_CTX *ctx, CookieVerifyCallback cb); +const SSL_METHOD *q_DTLS_server_method(); +const SSL_METHOD *q_DTLS_client_method(); + +#endif // dtls + +void *q_X509_STORE_CTX_get_ex_data(X509_STORE_CTX *ctx, int idx); +int q_SSL_get_ex_data_X509_STORE_CTX_idx(); + +#if QT_CONFIG(dtls) +#define q_DTLS_set_link_mtu(ssl, mtu) q_SSL_ctrl((ssl), DTLS_CTRL_SET_LINK_MTU, (mtu), nullptr) +#define q_DTLSv1_get_timeout(ssl, arg) q_SSL_ctrl(ssl, DTLS_CTRL_GET_TIMEOUT, 0, arg) +#define q_DTLSv1_handle_timeout(ssl) q_SSL_ctrl(ssl, DTLS_CTRL_HANDLE_TIMEOUT, 0, nullptr) +#endif // dtls + +void q_BIO_set_flags(BIO *b, int flags); +void q_BIO_clear_flags(BIO *b, int flags); +void *q_BIO_get_ex_data(BIO *b, int idx); +int q_BIO_set_ex_data(BIO *b, int idx, void *data); + +#define q_BIO_set_retry_read(b) q_BIO_set_flags(b, (BIO_FLAGS_READ|BIO_FLAGS_SHOULD_RETRY)) +#define q_BIO_set_retry_write(b) q_BIO_set_flags(b, (BIO_FLAGS_WRITE|BIO_FLAGS_SHOULD_RETRY)) +#define q_BIO_clear_retry_flags(b) q_BIO_clear_flags(b, (BIO_FLAGS_RWS|BIO_FLAGS_SHOULD_RETRY)) +#define q_BIO_set_app_data(s,arg) q_BIO_set_ex_data(s,0,arg) +#define q_BIO_get_app_data(s) q_BIO_get_ex_data(s,0) + +#define q_SSL_set_tlsext_status_type(ssl, type) \ + q_SSL_ctrl((ssl), SSL_CTRL_SET_TLSEXT_STATUS_REQ_TYPE, (type), nullptr) + +#if QT_CONFIG(ocsp) + +OCSP_RESPONSE *q_d2i_OCSP_RESPONSE(OCSP_RESPONSE **a, const unsigned char **in, long len); +int q_i2d_OCSP_RESPONSE(OCSP_RESPONSE *r, unsigned char **ppout); +OCSP_RESPONSE *q_OCSP_response_create(int status, OCSP_BASICRESP *bs); +void q_OCSP_RESPONSE_free(OCSP_RESPONSE *rs); +int q_OCSP_response_status(OCSP_RESPONSE *resp); +OCSP_BASICRESP *q_OCSP_response_get1_basic(OCSP_RESPONSE *resp); +OCSP_SINGLERESP *q_OCSP_basic_add1_status(OCSP_BASICRESP *rsp, OCSP_CERTID *cid, + int status, int reason, ASN1_TIME *revtime, + ASN1_TIME *thisupd, ASN1_TIME *nextupd); +int q_OCSP_basic_sign(OCSP_BASICRESP *brsp, X509 *signer, EVP_PKEY *key, const EVP_MD *dgst, + STACK_OF(X509) *certs, unsigned long flags); +OCSP_BASICRESP *q_OCSP_BASICRESP_new(); +void q_OCSP_BASICRESP_free(OCSP_BASICRESP *bs); +int q_OCSP_basic_verify(OCSP_BASICRESP *bs, STACK_OF(X509) *certs, X509_STORE *st, unsigned long flags); +int q_OCSP_resp_count(OCSP_BASICRESP *bs); +OCSP_SINGLERESP *q_OCSP_resp_get0(OCSP_BASICRESP *bs, int idx); +int q_OCSP_single_get0_status(OCSP_SINGLERESP *single, int *reason, ASN1_GENERALIZEDTIME **revtime, + ASN1_GENERALIZEDTIME **thisupd, ASN1_GENERALIZEDTIME **nextupd); +int q_OCSP_check_validity(ASN1_GENERALIZEDTIME *thisupd, ASN1_GENERALIZEDTIME *nextupd, long nsec, long maxsec); +int q_OCSP_id_get0_info(ASN1_OCTET_STRING **piNameHash, ASN1_OBJECT **pmd, ASN1_OCTET_STRING **pikeyHash, + ASN1_INTEGER **pserial, OCSP_CERTID *cid); + +const STACK_OF(X509) *q_OCSP_resp_get0_certs(const OCSP_BASICRESP *bs); +OCSP_CERTID *q_OCSP_cert_to_id(const EVP_MD *dgst, X509 *subject, X509 *issuer); +void q_OCSP_CERTID_free(OCSP_CERTID *cid); +int q_OCSP_id_cmp(OCSP_CERTID *a, OCSP_CERTID *b); + +#define q_SSL_get_tlsext_status_ocsp_resp(ssl, arg) \ + q_SSL_ctrl(ssl, SSL_CTRL_GET_TLSEXT_STATUS_REQ_OCSP_RESP, 0, arg) + +#define q_SSL_CTX_set_tlsext_status_cb(ssl, cb) \ + q_SSL_CTX_callback_ctrl(ssl, SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB, GenericCallbackType(cb)) + +# define q_SSL_set_tlsext_status_ocsp_resp(ssl, arg, arglen) \ + q_SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_STATUS_REQ_OCSP_RESP, arglen, arg) + +#endif // ocsp + + +void *q_CRYPTO_malloc(size_t num, const char *file, int line); +#define q_OPENSSL_malloc(num) q_CRYPTO_malloc(num, "", 0) + +void q_SSL_set_info_callback(SSL *ssl, void (*cb) (const SSL *ssl, int type, int val)); +const char *q_SSL_alert_type_string(int value); +const char *q_SSL_alert_desc_string_long(int value); + +int q_SSL_CTX_get_security_level(const SSL_CTX *ctx); +void q_SSL_CTX_set_security_level(SSL_CTX *ctx, int level); + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tls/openssl/qtls_openssl.cpp b/src/plugins/tls/openssl/qtls_openssl.cpp new file mode 100644 index 0000000000..9ed9ab7538 --- /dev/null +++ b/src/plugins/tls/openssl/qtls_openssl.cpp @@ -0,0 +1,1839 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qsslsocket_openssl_symbols_p.h" +#include "qx509_openssl_p.h" +#include "qtls_openssl_p.h" + +#ifdef Q_OS_WIN +#include "qwindowscarootfetcher_p.h" +#endif + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { + +QSsl::AlertLevel tlsAlertLevel(int value) +{ + using QSsl::AlertLevel; + + if (const char *typeString = q_SSL_alert_type_string(value)) { + // Documented to return 'W' for warning, 'F' for fatal, + // 'U' for unknown. + switch (typeString[0]) { + case 'W': + return AlertLevel::Warning; + case 'F': + return AlertLevel::Fatal; + default:; + } + } + + return AlertLevel::Unknown; +} + +QString tlsAlertDescription(int value) +{ + QString description = QLatin1String(q_SSL_alert_desc_string_long(value)); + if (!description.size()) + description = QLatin1String("no description provided"); + return description; +} + +QSsl::AlertType tlsAlertType(int value) +{ + // In case for some reason openssl gives us a value, + // which is not in our enum actually, we leave it to + // an application to handle (supposedly they have + // if or switch-statements). + return QSsl::AlertType(value & 0xff); +} + +#ifdef Q_OS_WIN + +QSslCertificate findCertificateToFetch(const QList &tlsErrors, bool checkAIA) +{ + QSslCertificate certToFetch; + + for (const auto &tlsError : tlsErrors) { + switch (tlsError.error()) { + case QSslError::UnableToGetLocalIssuerCertificate: // site presented intermediate cert, but root is unknown + case QSslError::SelfSignedCertificateInChain: // site presented a complete chain, but root is unknown + certToFetch = tlsError.certificate(); + break; + case QSslError::SelfSignedCertificate: + case QSslError::CertificateBlacklisted: + //With these errors, we know it will be untrusted so save time by not asking windows + return QSslCertificate{}; + default: +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << tlsError.errorString(); +#endif + //TODO - this part is strange. + break; + } + } + + 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 + return certToFetch; + } + //The only reason we check this extensions is because an application set trusted + //CA certificates explicitly, thus technically disabling CA fetch. So, if it's + //the case and an intermediate certificate is missing, and no extensions is + //present on the leaf certificate - we fail the handshake immediately. + return QSslCertificate{}; + } + + return certToFetch; +} + +#endif // Q_OS_WIN + +} // unnamed namespace + +namespace QTlsPrivate { + +extern "C" { + +int q_X509Callback(int ok, X509_STORE_CTX *ctx) +{ + if (!ok) { + // Store the error and at which depth the error was detected. + + using ErrorListPtr = QList *; + ErrorListPtr errors = nullptr; + + // Error list is attached to either 'SSL' or 'X509_STORE'. + if (X509_STORE *store = q_X509_STORE_CTX_get0_store(ctx)) // We try store first: + errors = ErrorListPtr(q_X509_STORE_get_ex_data(store, 0)); + + if (!errors) { + // Not found on store? Try SSL and its external data then. According to the OpenSSL's + // documentation: + // + // "Whenever a X509_STORE_CTX object is created for the verification of the + // peer's certificate during a handshake, a pointer to the SSL object is + // stored into the X509_STORE_CTX object to identify the connection affected. + // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be + // used with the correct index." + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::errorOffsetInExData; + if (SSL *ssl = static_cast(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx()))) + errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset)); + } + + if (!errors) { + qCWarning(lcTlsBackend, "Neither X509_STORE, nor SSL contains error list, handshake failure"); + return 0; + } + + errors->append(X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); + } + // Always return OK to allow verification to continue. We handle the + // errors gracefully after collecting all errors, after verification has + // completed. + return 1; +} + +int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx) +{ + // Passed to SSL_CTX_set_verify() + // https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_verify.html + // Returns 0 to abort verification, 1 to continue. + + // This is a new, experimental verification callback, reporting + // errors immediately and returning 0 or 1 depending on an application + // either ignoring or not ignoring verification errors as they come. + if (!ctx) { + qCWarning(lcTlsBackend, "Invalid store context (nullptr)"); + return 0; + } + + if (!ok) { + // "Whenever a X509_STORE_CTX object is created for the verification of the + // peer's certificate during a handshake, a pointer to the SSL object is + // stored into the X509_STORE_CTX object to identify the connection affected. + // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be + // used with the correct index." + SSL *ssl = static_cast(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx())); + if (!ssl) { + qCWarning(lcTlsBackend, "No external data (SSL) found in X509 store object"); + return 0; + } + + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::socketOffsetInExData; + auto crypto = static_cast(q_SSL_get_ex_data(ssl, offset)); + if (!crypto) { + qCWarning(lcTlsBackend, "No external data (TlsCryptographOpenSSL) found in SSL object"); + return 0; + } + + return crypto->emitErrorFromCallback(ctx); + } + return 1; +} + +#ifndef OPENSSL_NO_PSK +static unsigned q_ssl_psk_client_callback(SSL *ssl, const char *hint, char *identity, unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len) +{ + auto *tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + return tls->pskClientTlsCallback(hint, identity, max_identity_len, psk, max_psk_len); +} + +static unsigned int q_ssl_psk_server_callback(SSL *ssl, const char *identity, unsigned char *psk, + unsigned int max_psk_len) +{ + auto *tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + Q_ASSERT(tls); + return tls->pskServerTlsCallback(identity, psk, max_psk_len); +} + +#ifdef TLS1_3_VERSION +static unsigned q_ssl_psk_restore_client(SSL *ssl, const char *hint, char *identity, unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len) +{ + Q_UNUSED(hint); + Q_UNUSED(identity); + Q_UNUSED(max_identity_len); + Q_UNUSED(psk); + Q_UNUSED(max_psk_len); + +#ifdef QT_DEBUG + auto tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + Q_ASSERT(tls); + Q_ASSERT(tls->d); + Q_ASSERT(tls->d->tlsMode() == QSslSocket::SslClientMode); +#endif + q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); + + return 0; +} + +static int q_ssl_psk_use_session_callback(SSL *ssl, const EVP_MD *md, const unsigned char **id, + size_t *idlen, SSL_SESSION **sess) +{ + Q_UNUSED(ssl); + Q_UNUSED(md); + Q_UNUSED(id); + Q_UNUSED(idlen); + Q_UNUSED(sess); + +#ifdef QT_DEBUG + auto *tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + Q_ASSERT(tls); + Q_ASSERT(tls->d); + Q_ASSERT(tls->d->tlsMode() == QSslSocket::SslClientMode); +#endif + + // Temporarily rebind the psk because it will be called next. The function will restore it. + q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_restore_client); + + return 1; // need to return 1 or else "the connection setup fails." +} + +int q_ssl_sess_set_new_cb(SSL *ssl, SSL_SESSION *session) +{ + if (!ssl) { + qCWarning(lcTlsBackend, "Invalid SSL (nullptr)"); + return 0; + } + if (!session) { + qCWarning(lcTlsBackend, "Invalid SSL_SESSION (nullptr)"); + return 0; + } + + auto *tls = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + Q_ASSERT(tls); + return tls->handleNewSessionTicket(ssl); +} +#endif // TLS1_3_VERSION + +#endif // !OPENSSL_NO_PSK + +#if QT_CONFIG(ocsp) + +int qt_OCSP_status_server_callback(SSL *ssl, void *ocspRequest) +{ + Q_UNUSED(ocspRequest); + if (!ssl) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + auto crypto = static_cast(q_SSL_get_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData)); + if (!crypto) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + Q_ASSERT(crypto->d); + Q_ASSERT(crypto->d->tlsMode() == QSslSocket::SslServerMode); + const QByteArray &response = crypto->ocspResponseDer; + Q_ASSERT(response.size()); + + unsigned char *derCopy = static_cast(q_OPENSSL_malloc(size_t(response.size()))); + if (!derCopy) + return SSL_TLSEXT_ERR_ALERT_FATAL; + + std::copy(response.data(), response.data() + response.size(), derCopy); + // We don't check the return value: internally OpenSSL simply assignes the + // pointer (it assumes it now owns this memory btw!) and the length. + q_SSL_set_tlsext_status_ocsp_resp(ssl, derCopy, response.size()); + + return SSL_TLSEXT_ERR_OK; +} + +#endif // ocsp + +void qt_AlertInfoCallback(const SSL *connection, int from, int value) +{ + // Passed to SSL_set_info_callback() + // https://www.openssl.org/docs/man1.1.1/man3/SSL_set_info_callback.html + + if (!connection) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend, "Invalid 'connection' parameter (nullptr)"); +#endif // QSSLSOCKET_DEBUG + return; + } + + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::socketOffsetInExData; + auto crypto = static_cast(q_SSL_get_ex_data(connection, offset)); + if (!crypto) { + // SSL_set_ex_data can fail: +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend, "No external data (socket backend) found for parameter 'connection'"); +#endif // QSSLSOCKET_DEBUG + return; + } + + if (!(from & SSL_CB_ALERT)) { + // We only want to know about alerts (at least for now). + return; + } + + if (from & SSL_CB_WRITE) + crypto->alertMessageSent(value); + else + crypto->alertMessageReceived(value); +} + +} // extern "C" + +#if QT_CONFIG(ocsp) +namespace { + +QSslError::SslError qt_OCSP_response_status_to_SslError(long code) +{ + switch (code) { + case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST: + return QSslError::OcspMalformedRequest; + case OCSP_RESPONSE_STATUS_INTERNALERROR: + return QSslError::OcspInternalError; + case OCSP_RESPONSE_STATUS_TRYLATER: + return QSslError::OcspTryLater; + case OCSP_RESPONSE_STATUS_SIGREQUIRED: + return QSslError::OcspSigRequred; + case OCSP_RESPONSE_STATUS_UNAUTHORIZED: + return QSslError::OcspUnauthorized; + case OCSP_RESPONSE_STATUS_SUCCESSFUL: + default: + return {}; + } + Q_UNREACHABLE(); +} + +QOcspRevocationReason qt_OCSP_revocation_reason(int reason) +{ + switch (reason) { + case OCSP_REVOKED_STATUS_NOSTATUS: + return QOcspRevocationReason::None; + case OCSP_REVOKED_STATUS_UNSPECIFIED: + return QOcspRevocationReason::Unspecified; + case OCSP_REVOKED_STATUS_KEYCOMPROMISE: + return QOcspRevocationReason::KeyCompromise; + case OCSP_REVOKED_STATUS_CACOMPROMISE: + return QOcspRevocationReason::CACompromise; + case OCSP_REVOKED_STATUS_AFFILIATIONCHANGED: + return QOcspRevocationReason::AffiliationChanged; + case OCSP_REVOKED_STATUS_SUPERSEDED: + return QOcspRevocationReason::Superseded; + case OCSP_REVOKED_STATUS_CESSATIONOFOPERATION: + return QOcspRevocationReason::CessationOfOperation; + case OCSP_REVOKED_STATUS_CERTIFICATEHOLD: + return QOcspRevocationReason::CertificateHold; + case OCSP_REVOKED_STATUS_REMOVEFROMCRL: + return QOcspRevocationReason::RemoveFromCRL; + default: + return QOcspRevocationReason::None; + } + + Q_UNREACHABLE(); +} + +bool qt_OCSP_certificate_match(OCSP_SINGLERESP *singleResponse, X509 *peerCert, X509 *issuer) +{ + // OCSP_basic_verify does verify that the responder is legit, the response is + // correctly signed, CertID is correct. But it does not know which certificate + // we were presented with by our peer, so it does not check if it's a response + // for our peer's certificate. + Q_ASSERT(singleResponse && peerCert && issuer); + + const OCSP_CERTID *certId = q_OCSP_SINGLERESP_get0_id(singleResponse); // Does not increment refcount. + if (!certId) { + qCWarning(lcTlsBackend, "A SingleResponse without CertID"); + return false; + } + + ASN1_OBJECT *md = nullptr; + ASN1_INTEGER *reportedSerialNumber = nullptr; + const int result = q_OCSP_id_get0_info(nullptr, &md, nullptr, &reportedSerialNumber, const_cast(certId)); + if (result != 1 || !md || !reportedSerialNumber) { + qCWarning(lcTlsBackend, "Failed to extract a hash and serial number from CertID structure"); + return false; + } + + if (!q_X509_get_serialNumber(peerCert)) { + // Is this possible at all? But we have to check this, + // ASN1_INTEGER_cmp (called from OCSP_id_cmp) dereferences + // without any checks at all. + qCWarning(lcTlsBackend, "No serial number in peer's ceritificate"); + return false; + } + + const int nid = q_OBJ_obj2nid(md); + if (nid == NID_undef) { + qCWarning(lcTlsBackend, "Unknown hash algorithm in CertID"); + return false; + } + + const EVP_MD *digest = q_EVP_get_digestbynid(nid); // Does not increment refcount. + if (!digest) { + qCWarning(lcTlsBackend) << "No digest for nid" << nid; + return false; + } + + OCSP_CERTID *recreatedId = q_OCSP_cert_to_id(digest, peerCert, issuer); + if (!recreatedId) { + qCWarning(lcTlsBackend, "Failed to re-create CertID"); + return false; + } + const QSharedPointer guard(recreatedId, q_OCSP_CERTID_free); + + if (q_OCSP_id_cmp(const_cast(certId), recreatedId)) { + qDebug(lcTlsBackend, "Certificate ID mismatch"); + return false; + } + // Bingo! + return true; +} + +} // unnamed namespace +#endif // ocsp + +TlsCryptographOpenSSL::~TlsCryptographOpenSSL() +{ + destroySslContext(); +} + +void TlsCryptographOpenSSL::init(QSslSocket *qObj, QSslSocketPrivate *dObj) +{ + Q_ASSERT(qObj); + Q_ASSERT(dObj); + q = qObj; + d = dObj; + + ocspResponses.clear(); + ocspResponseDer.clear(); + + systemOrSslErrorDetected = false; + handshakeInterrupted = false; + + fetchAuthorityInformation = false; + caToFetch = QSslCertificate{}; +} + +void TlsCryptographOpenSSL::checkSettingSslContext(QSharedPointer tlsContext) +{ + if (sslContextPointer.isNull()) + sslContextPointer = tlsContext; +} + +QSharedPointer TlsCryptographOpenSSL::sslContext() const +{ + return sslContextPointer; +} + +QList TlsCryptographOpenSSL::tlsErrors() const +{ + return sslErrors; +} + +void TlsCryptographOpenSSL::startClientEncryption() +{ + if (!initSslContext()) { + Q_ASSERT(d); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to init SSL Context: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; + } + + // Start connecting. This will place outgoing data in the BIO, so we + // follow up with calling transmit(). + startHandshake(); + transmit(); +} + +void TlsCryptographOpenSSL::startServerEncryption() +{ + if (!initSslContext()) { + Q_ASSERT(d); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to init SSL Context: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; + } + + // Start connecting. This will place outgoing data in the BIO, so we + // follow up with calling transmit(). + startHandshake(); + transmit(); +} + +bool TlsCryptographOpenSSL::startHandshake() +{ + // Check if the connection has been established. Get all errors from the + // verification stage. + Q_ASSERT(q); + Q_ASSERT(d); + + using ScopedBool = QScopedValueRollback; + + if (inSetAndEmitError) + return false; + + const auto mode = d->tlsMode(); + + pendingFatalAlert = false; + errorsReportedFromCallback = false; + QList lastErrors; + q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + errorOffsetInExData, &lastErrors); + + // SSL_set_ex_data can fail, but see the callback's code - we handle this there. + q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + socketOffsetInExData, this); + q_SSL_set_info_callback(ssl, qt_AlertInfoCallback); + + int result = (mode == QSslSocket::SslClientMode) ? q_SSL_connect(ssl) : q_SSL_accept(ssl); + q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData + errorOffsetInExData, nullptr); + // Note, unlike errors as external data on SSL object, we do not unset + // a callback/ex-data if alert notifications are enabled: an alert can + // arrive after the handshake, for example, this happens when the server + // does not find a ClientCert or does not like it. + + if (!lastErrors.isEmpty() || errorsReportedFromCallback) + storePeerCertificates(); + + // storePeerCertificate() if called above - would update the + // configuration with peer's certificates. + auto configuration = q->sslConfiguration(); + if (!errorsReportedFromCallback) { + const auto &peerCertificateChain = configuration.peerCertificateChain(); + for (const auto ¤tError : qAsConst(lastErrors)) { + emit q->peerVerifyError(QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(currentError.code, + peerCertificateChain.value(currentError.depth))); + if (q->state() != QAbstractSocket::ConnectedState) + break; + } + } + + errorList << lastErrors; + + // Connection aborted during handshake phase. + if (q->state() != QAbstractSocket::ConnectedState) + return false; + + // Check if we're encrypted or not. + if (result <= 0) { + switch (q_SSL_get_error(ssl, result)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // The handshake is not yet complete. + break; + default: + QString errorString = QTlsBackendOpenSSL::msgErrorsDuringHandshake(); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::startHandshake: error!" << errorString; +#endif + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, errorString); + if (pendingFatalAlert) { + trySendFatalAlert(); + pendingFatalAlert = false; + } + } + q->abort(); + } + return false; + } + + // store peer certificate chain + storePeerCertificates(); + + // Start translating errors. + QList errors; + + // Note, the storePeerCerificates() probably updated the configuration at this point. + configuration = q->sslConfiguration(); + // Check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer) + const auto &peerCertificateChain = configuration.peerCertificateChain(); + for (const QSslCertificate &cert : peerCertificateChain) { + if (QSslCertificatePrivate::isBlacklisted(cert)) { + QSslError error(QSslError::CertificateBlacklisted, cert); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + const bool doVerifyPeer = configuration.peerVerifyMode() == QSslSocket::VerifyPeer + || (configuration.peerVerifyMode() == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); + +#if QT_CONFIG(ocsp) + // For now it's always QSslSocket::SslClientMode - initSslContext() will bail out early, + // if it's enabled in QSslSocket::SslServerMode. This can change. + if (!configuration.peerCertificate().isNull() && configuration.ocspStaplingEnabled() && doVerifyPeer) { + if (!checkOcspStatus()) { + if (ocspErrors.isEmpty()) { + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, ocspErrorDescription); + } + q->abort(); + return false; + } + + for (const QSslError &error : ocspErrors) { + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + } +#endif // ocsp + + // Check the peer certificate itself. First try the subject's common name + // (CN) as a wildcard, then try all alternate subject name DNS entries the + // same way. + if (!configuration.peerCertificate().isNull()) { + // but only if we're a client connecting to a server + // if we're the server, don't check CN + const auto verificationPeerName = d->verificationName(); + if (mode == QSslSocket::SslClientMode) { + QString peerName = (verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); + + if (!isMatchingHostname(configuration.peerCertificate(), peerName)) { + // No matches in common names or alternate names. + QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate()); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + } else { + // No peer certificate presented. Report as error if the socket + // expected one. + if (doVerifyPeer) { + QSslError error(QSslError::NoPeerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + // Translate errors from the error list into QSslErrors. + errors.reserve(errors.size() + errorList.size()); + for (const auto &error : qAsConst(errorList)) + errors << X509CertificateOpenSSL::openSSLErrorToQSslError(error.code, peerCertificateChain.value(error.depth)); + + if (!errors.isEmpty()) { + sslErrors = errors; +#ifdef Q_OS_WIN + const bool fetchEnabled = QSslSocketPrivate::rootCertOnDemandLoadingSupported() + && d->isRootsOnDemandAllowed(); + // !fetchEnabled is a special case scenario, when we potentially have a missing + // intermediate certificate and a recoverable chain, but on demand cert loading + // was disabled by setCaCertificates call. For this scenario we check if "Authority + // Information Access" is present - wincrypt can deal with such certificates. + QSslCertificate certToFetch; + if (doVerifyPeer && !d->verifyErrorsHaveBeenIgnored()) + certToFetch = findCertificateToFetch(sslErrors, !fetchEnabled); + + //Skip this if not using system CAs, or if the SSL errors are configured in advance to be ignorable + if (!certToFetch.isNull()) { + fetchAuthorityInformation = !fetchEnabled; + //Windows desktop versions starting from vista ship with minimal set of roots and download on demand + //from the windows update server CA roots that are trusted by MS. It also can fetch a missing intermediate + //in case "Authority Information Access" extension is present. + // + //However, this is only transparent if using WinINET - we have to trigger it + //ourselves. + fetchCaRootForCert(certToFetch); + return false; + } +#endif // Q_OS_WIN + if (!checkSslErrors()) + return false; + // A slot, attached to sslErrors signal can call + // abort/close/disconnetFromHost/etc; no need to + // continue handshake then. + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } else { + sslErrors.clear(); + } + + continueHandshake(); + return true; +} + +void TlsCryptographOpenSSL::enableHandshakeContinuation() +{ + handshakeInterrupted = false; +} + +void TlsCryptographOpenSSL::cancelCAFetch() +{ + fetchAuthorityInformation = false; + caToFetch = QSslCertificate{}; +} + +void TlsCryptographOpenSSL::continueHandshake() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + const auto mode = d->tlsMode(); + + // if we have a max read buffer size, reset the plain socket's to match + if (const auto maxSize = d->maxReadBufferSize()) + plainSocket->setReadBufferSize(maxSize); + + if (q_SSL_session_reused(ssl)) + QTlsBackend::setPeerSessionShared(d, true); + +#ifdef QT_DECRYPT_SSL_TRAFFIC + if (q_SSL_get_session(ssl)) { + size_t master_key_len = q_SSL_SESSION_get_master_key(q_SSL_get_session(ssl), nullptr, 0); + size_t client_random_len = q_SSL_get_client_random(ssl, nullptr, 0); + QByteArray masterKey(int(master_key_len), Qt::Uninitialized); // Will not overflow + QByteArray clientRandom(int(client_random_len), Qt::Uninitialized); // Will not overflow + + q_SSL_SESSION_get_master_key(q_SSL_get_session(ssl), + reinterpret_cast(masterKey.data()), + masterKey.size()); + q_SSL_get_client_random(ssl, reinterpret_cast(clientRandom.data()), + clientRandom.size()); + + QByteArray debugLineClientRandom("CLIENT_RANDOM "); + debugLineClientRandom.append(clientRandom.toHex().toUpper()); + debugLineClientRandom.append(" "); + debugLineClientRandom.append(masterKey.toHex().toUpper()); + debugLineClientRandom.append("\n"); + + QString sslKeyFile = QDir::tempPath() + QLatin1String("/qt-ssl-keys"); + QFile file(sslKeyFile); + if (!file.open(QIODevice::Append)) + qCWarning(lcTlsBackend) << "could not open file" << sslKeyFile << "for appending"; + if (!file.write(debugLineClientRandom)) + qCWarning(lcTlsBackend) << "could not write to file" << sslKeyFile; + file.close(); + } else { + qCWarning(lcTlsBackend, "could not decrypt SSL traffic"); + } +#endif // QT_DECRYPT_SSL_TRAFFIC + + const auto &configuration = q->sslConfiguration(); + // Cache this SSL session inside the QSslContext + if (!(configuration.testSslOption(QSsl::SslOptionDisableSessionSharing))) { + if (!sslContextPointer->cacheSession(ssl)) { + sslContextPointer.clear(); // we could not cache the session + } else { + // Cache the session for permanent usage as well + if (!(configuration.testSslOption(QSsl::SslOptionDisableSessionPersistence))) { + if (!sslContextPointer->sessionASN1().isEmpty()) + QTlsBackend::setSessionAsn1(d, sslContextPointer->sessionASN1()); + QTlsBackend::setSessionLifetimeHint(d, sslContextPointer->sessionTicketLifeTimeHint()); + } + } + } + +#if !defined(OPENSSL_NO_NEXTPROTONEG) + + QTlsBackend::setAlpnStatus(d, sslContextPointer->npnContext().status); + if (sslContextPointer->npnContext().status == QSslConfiguration::NextProtocolNegotiationUnsupported) { + // we could not agree -> be conservative and use HTTP/1.1 + // T.P.: I have to admit, this is a really strange notion of 'conservative', + // given the protocol-neutral nature of ALPN/NPN. + QTlsBackend::setNegotiatedProtocol(d, QByteArrayLiteral("http/1.1")); + } else { + const unsigned char *proto = nullptr; + unsigned int proto_len = 0; + + q_SSL_get0_alpn_selected(ssl, &proto, &proto_len); + if (proto_len && mode == QSslSocket::SslClientMode) { + // Client does not have a callback that sets it ... + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNegotiated); + } + + if (!proto_len) { // Test if NPN was more lucky ... + q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len); + } + + if (proto_len) + QTlsBackend::setNegotiatedProtocol(d, QByteArray(reinterpret_cast(proto), proto_len)); + else + QTlsBackend::setNegotiatedProtocol(d,{}); + } +#endif // !defined(OPENSSL_NO_NEXTPROTONEG) + + if (mode == QSslSocket::SslClientMode) { + EVP_PKEY *key; + if (q_SSL_get_server_tmp_key(ssl, &key)) + QTlsBackend::setEphemeralKey(d, QSslKey(key, QSsl::PublicKey)); + } + + d->setEncrypted(true); + emit q->encrypted(); + if (d->isAutoStartingHandshake() && d->isPendingClose()) { + d->setPendingClose(false); + q->disconnectFromHost(); + } +} + +void TlsCryptographOpenSSL::transmit() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + using ScopedBool = QScopedValueRollback; + + if (inSetAndEmitError) + return; + + // If we don't have any SSL context, don't bother transmitting. + if (!ssl) + return; + + auto &writeBuffer = d->tlsWriteBuffer(); + auto &buffer = d->tlsBuffer(); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + bool &emittedBytesWritten = d->tlsEmittedBytesWritten(); + + bool transmitting; + do { + transmitting = false; + + // If the connection is secure, we can transfer data from the write + // buffer (in plain text) to the write BIO through SSL_write. + if (q->isEncrypted() && !writeBuffer.isEmpty()) { + qint64 totalBytesWritten = 0; + int nextDataBlockSize; + while ((nextDataBlockSize = writeBuffer.nextDataBlockSize()) > 0) { + int writtenBytes = q_SSL_write(ssl, writeBuffer.readPointer(), nextDataBlockSize); + if (writtenBytes <= 0) { + int error = q_SSL_get_error(ssl, writtenBytes); + //write can result in a want_write_error - not an error - continue transmitting + if (error == SSL_ERROR_WANT_WRITE) { + transmitting = true; + break; + } else if (error == SSL_ERROR_WANT_READ) { + //write can result in a want_read error, possibly due to renegotiation - not an error - stop transmitting + transmitting = false; + break; + } else { + // ### Better error handling. + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to write data: %1").arg( + QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; + } + } +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encrypted" << writtenBytes << "bytes"; +#endif + writeBuffer.free(writtenBytes); + totalBytesWritten += writtenBytes; + + if (writtenBytes < nextDataBlockSize) { + // break out of the writing loop and try again after we had read + transmitting = true; + break; + } + } + + if (totalBytesWritten > 0) { + // Don't emit bytesWritten() recursively. + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(totalBytesWritten); + emittedBytesWritten = false; + } + emit q->channelBytesWritten(0, totalBytesWritten); + } + } + + // Check if we've got any data to be written to the socket. + QVarLengthArray data; + int pendingBytes; + while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0 + && plainSocket->openMode() != QIODevice::NotOpen) { + // Read encrypted data from the write BIO into a buffer. + data.resize(pendingBytes); + int encryptedBytesRead = q_BIO_read(writeBio, data.data(), pendingBytes); + + // Write encrypted data from the buffer to the socket. + qint64 actualWritten = plainSocket->write(data.constData(), encryptedBytesRead); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: wrote" << encryptedBytesRead + << "encrypted bytes to the socket" << actualWritten << "actual."; +#endif + if (actualWritten < 0) { + //plain socket write fails if it was in the pending close state. + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, plainSocket->error(), plainSocket->errorString()); + return; + } + transmitting = true; + } + + // Check if we've got any data to be read from the socket. + if (!q->isEncrypted() || !d->maxReadBufferSize() || buffer.size() < d->maxReadBufferSize()) + while ((pendingBytes = plainSocket->bytesAvailable()) > 0) { + // Read encrypted data from the socket into a buffer. + data.resize(pendingBytes); + // just peek() here because q_BIO_write could write less data than expected + int encryptedBytesRead = plainSocket->peek(data.data(), pendingBytes); + +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: read" << encryptedBytesRead << "encrypted bytes from the socket"; +#endif + // Write encrypted data from the buffer into the read BIO. + int writtenToBio = q_BIO_write(readBio, data.constData(), encryptedBytesRead); + + // Throw away the results. + if (writtenToBio > 0) { + plainSocket->skip(writtenToBio); + } else { + // ### Better error handling. + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Unable to decrypt data: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return; + } + + transmitting = true; + } + + // If the connection isn't secured yet, this is the time to retry the + // connect / accept. + if (!q->isEncrypted()) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: testing encryption"; +#endif + if (startHandshake()) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encryption established"; +#endif + d->setEncrypted(true); + transmitting = true; + } else if (plainSocket->state() != QAbstractSocket::ConnectedState) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: connection lost"; +#endif + break; + } else if (d->isPaused()) { + // just wait until the user continues + return; + } else { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: encryption not done yet"; +#endif + } + } + + // If the request is small and the remote host closes the transmission + // after sending, there's a chance that startHandshake() will already + // have triggered a shutdown. + if (!ssl) + continue; + + // We always read everything from the SSL decryption buffers, even if + // we have a readBufferMaxSize. There's no point in leaving data there + // just so that readBuffer.size() == readBufferMaxSize. + int readBytes = 0; + const int bytesToRead = 4096; + do { + if (q->readChannelCount() == 0) { + // The read buffer is deallocated, don't try resize or write to it. + break; + } + // Don't use SSL_pending(). It's very unreliable. + readBytes = q_SSL_read(ssl, buffer.reserve(bytesToRead), bytesToRead); + if (readBytes > 0) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: decrypted" << readBytes << "bytes"; +#endif + buffer.chop(bytesToRead - readBytes); + + if (bool *readyReadEmittedPointer = d->readyReadPointer()) + *readyReadEmittedPointer = true; + emit q->readyRead(); + emit q->channelReadyRead(0); + transmitting = true; + continue; + } + buffer.chop(bytesToRead); + + // Error. + switch (q_SSL_get_error(ssl, readBytes)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // Out of data. + break; + case SSL_ERROR_ZERO_RETURN: + // The remote host closed the connection. +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "TlsCryptographOpenSSL::transmit: remote disconnect"; +#endif + shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); + } + return; + case SSL_ERROR_SYSCALL: // some IO error + case SSL_ERROR_SSL: // error in the SSL library + // we do not know exactly what the error is, nor whether we can recover from it, + // so just return to prevent an endless loop in the outer "while" statement + systemOrSslErrorDetected = true; + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Error while reading: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + } + return; + default: + // SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: can only happen with a + // BIO_s_connect() or BIO_s_accept(), which we do not call. + // SSL_ERROR_WANT_X509_LOOKUP: can only happen with a + // SSL_CTX_set_client_cert_cb(), which we do not call. + // So this default case should never be triggered. + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Error while reading: %1") + .arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + } + break; + } + } while (ssl && readBytes > 0); + } while (ssl && transmitting); +} + +void TlsCryptographOpenSSL::disconnectFromHost() +{ + if (ssl) { + if (!shutdown && !q_SSL_in_init(ssl) && !systemOrSslErrorDetected) { + if (q_SSL_shutdown(ssl) != 1) { + // Some error may be queued, clear it. + QTlsBackendOpenSSL::clearErrorQueue(); + } + shutdown = true; + transmit(); + } + } + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + plainSocket->disconnectFromHost(); +} + +void TlsCryptographOpenSSL::disconnected() +{ + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + if (plainSocket->bytesAvailable() <= 0) { + destroySslContext(); + } else { + // Move all bytes into the plain buffer. + const qint64 tmpReadBufferMaxSize = d->maxReadBufferSize(); + // Reset temporarily, so the plain socket buffer is completely drained: + d->setMaxReadBufferSize(0); + transmit(); + d->setMaxReadBufferSize(tmpReadBufferMaxSize); + } + //if there is still buffered data in the plain socket, don't destroy the ssl context yet. + //it will be destroyed when the socket is deleted. +} + +QSslCipher TlsCryptographOpenSSL::sessionCipher() const +{ + if (!ssl) + return {}; + + const SSL_CIPHER *sessionCipher = q_SSL_get_current_cipher(ssl); + return sessionCipher ? QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(sessionCipher) : QSslCipher{}; +} + +QSsl::SslProtocol TlsCryptographOpenSSL::sessionProtocol() const +{ + if (!ssl) + return QSsl::UnknownProtocol; + + const int ver = q_SSL_version(ssl); + switch (ver) { + case 0x301: + return QSsl::TlsV1_0; + case 0x302: + return QSsl::TlsV1_1; + case 0x303: + return QSsl::TlsV1_2; + case 0x304: + return QSsl::TlsV1_3; + } + + return QSsl::UnknownProtocol; +} + +QList TlsCryptographOpenSSL::ocsps() const +{ + return ocspResponses; +} + +bool TlsCryptographOpenSSL::checkSslErrors() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + if (sslErrors.isEmpty()) + return true; + + emit q->sslErrors(sslErrors); + + const auto vfyMode = q->peerVerifyMode(); + const auto mode = d->tlsMode(); + + bool doVerifyPeer = vfyMode == QSslSocket::VerifyPeer || (vfyMode == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); + bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); + // check whether we need to emit an SSL handshake error + if (doVerifyPeer && doEmitSslError) { + if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { + QSslSocketPrivate::pauseSocketNotifiers(q); + d->setPaused(true); + } else { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, sslErrors.constFirst().errorString()); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + plainSocket->disconnectFromHost(); + } + return false; + } + return true; +} + +int TlsCryptographOpenSSL::handleNewSessionTicket(SSL *connection) +{ + // If we return 1, this means we own the session, but we don't. + // 0 would tell OpenSSL to deref (but they still have it in the + // internal cache). + Q_ASSERT(connection); + + Q_ASSERT(q); + Q_ASSERT(d); + + if (q->sslConfiguration().testSslOption(QSsl::SslOptionDisableSessionPersistence)) { + // We silently ignore, do nothing, remove from cache. + return 0; + } + + SSL_SESSION *currentSession = q_SSL_get_session(connection); + if (!currentSession) { + qCWarning(lcTlsBackend, + "New session ticket callback, the session is invalid (nullptr)"); + return 0; + } + + if (q_SSL_version(connection) < 0x304) { + // We only rely on this mechanics with TLS >= 1.3 + return 0; + } + +#ifdef TLS1_3_VERSION + if (!q_SSL_SESSION_is_resumable(currentSession)) { + qCDebug(lcTlsBackend, "New session ticket, but the session is non-resumable"); + return 0; + } +#endif // TLS1_3_VERSION + + const int sessionSize = q_i2d_SSL_SESSION(currentSession, nullptr); + if (sessionSize <= 0) { + qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); + return 0; + } + + // We have somewhat perverse naming, it's not a ticket, it's a session. + QByteArray sessionTicket(sessionSize, 0); + auto data = reinterpret_cast(sessionTicket.data()); + if (!q_i2d_SSL_SESSION(currentSession, &data)) { + qCWarning(lcTlsBackend, "could not store persistent version of SSL session"); + return 0; + } + + QTlsBackend::setSessionAsn1(d, sessionTicket); + QTlsBackend::setSessionLifetimeHint(d, q_SSL_SESSION_get_ticket_lifetime_hint(currentSession)); + + emit q->newSessionTicketReceived(); + return 0; +} + +void TlsCryptographOpenSSL::alertMessageSent(int value) +{ + Q_ASSERT(q); + Q_ASSERT(d); + + const auto level = tlsAlertLevel(value); + if (level == QSsl::AlertLevel::Fatal && !q->isEncrypted()) { + // Note, this logic is handshake-time only: + pendingFatalAlert = true; + } + + emit q->alertSent(level, tlsAlertType(value), tlsAlertDescription(value)); + +} + +void TlsCryptographOpenSSL::alertMessageReceived(int value) +{ + Q_ASSERT(q); + + emit q->alertReceived(tlsAlertLevel(value), tlsAlertType(value), tlsAlertDescription(value)); +} + +int TlsCryptographOpenSSL::emitErrorFromCallback(X509_STORE_CTX *ctx) +{ + // Returns 0 to abort verification, 1 to continue despite error (as + // OpenSSL expects from the verification callback). + Q_ASSERT(q); + Q_ASSERT(ctx); + + using ScopedBool = QScopedValueRollback; + // While we are not setting, we are emitting and in general - + // we want to prevent accidental recursive startHandshake() + // calls: + const ScopedBool bg(inSetAndEmitError, true); + + X509 *x509 = q_X509_STORE_CTX_get_current_cert(ctx); + if (!x509) { + qCWarning(lcTlsBackend, "Could not obtain the certificate (that failed to verify)"); + return 0; + } + + const QSslCertificate certificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); + const auto errorAndDepth = QTlsPrivate::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx); + const QSslError tlsError = QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(errorAndDepth.code, certificate); + + errorsReportedFromCallback = true; + handshakeInterrupted = true; + emit q->handshakeInterruptedOnError(tlsError); + + // Conveniently so, we also can access 'lastErrors' external data set + // in startHandshake, we store it for the case an application later + // wants to check errors (ignored or not): + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::errorOffsetInExData; + if (auto errorList = static_cast *>(q_SSL_get_ex_data(ssl, offset))) + errorList->append(errorAndDepth); + + // An application is expected to ignore this error (by calling ignoreSslErrors) + // in its directly connected slot: + return !handshakeInterrupted; +} + +void TlsCryptographOpenSSL::trySendFatalAlert() +{ + Q_ASSERT(pendingFatalAlert); + Q_ASSERT(d); + + auto *plainSocket = d->plainTcpSocket(); + + pendingFatalAlert = false; + QVarLengthArray data; + int pendingBytes = 0; + while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0 + && plainSocket->openMode() != QIODevice::NotOpen) { + // Read encrypted data from the write BIO into a buffer. + data.resize(pendingBytes); + const int bioReadBytes = q_BIO_read(writeBio, data.data(), pendingBytes); + + // Write encrypted data from the buffer to the socket. + qint64 actualWritten = plainSocket->write(data.constData(), bioReadBytes); + if (actualWritten < 0) + return; + plainSocket->flush(); + } +} + +bool TlsCryptographOpenSSL::initSslContext() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + // If no external context was set (e.g. by QHttpNetworkConnection) we will + // create a new one. + const auto mode = d->tlsMode(); + const auto configuration = q->sslConfiguration(); + if (!sslContextPointer) + sslContextPointer = QSslContext::sharedFromConfiguration(mode, configuration, d->isRootsOnDemandAllowed()); + + if (sslContextPointer->error() != QSslError::NoError) { + setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError, sslContextPointer->errorString()); + sslContextPointer.clear(); // deletes the QSslContext + return false; + } + + // Create and initialize SSL session + if (!(ssl = sslContextPointer->createSsl())) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Error creating SSL session, %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + return false; + } + + if (configuration.protocol() != QSsl::UnknownProtocol && mode == QSslSocket::SslClientMode) { + const auto verificationPeerName = d->verificationName(); + // Set server hostname on TLS extension. RFC4366 section 3.1 requires it in ACE format. + QString tlsHostName = verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName; + if (tlsHostName.isEmpty()) + tlsHostName = d->tlsHostName(); + QByteArray ace = QUrl::toAce(tlsHostName); + // only send the SNI header if the URL is valid and not an IP + if (!ace.isEmpty() + && !QHostAddress().setAddress(tlsHostName) + && !(configuration.testSslOption(QSsl::SslOptionDisableServerNameIndication))) { + // We don't send the trailing dot from the host header if present see + // https://tools.ietf.org/html/rfc6066#section-3 + if (ace.endsWith('.')) + ace.chop(1); + if (!q_SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, ace.data())) + qCWarning(lcTlsBackend, "could not set SSL_CTRL_SET_TLSEXT_HOSTNAME, Server Name Indication disabled"); + } + } + + // Clear the session. + errorList.clear(); + + // Initialize memory BIOs for encryption and decryption. + readBio = q_BIO_new(q_BIO_s_mem()); + writeBio = q_BIO_new(q_BIO_s_mem()); + if (!readBio || !writeBio) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Error creating SSL session: %1").arg(QTlsBackendOpenSSL::getErrorsFromOpenSsl())); + if (readBio) + q_BIO_free(readBio); + if (writeBio) + q_BIO_free(writeBio); + return false; + } + + // Assign the bios. + q_SSL_set_bio(ssl, readBio, writeBio); + + if (mode == QSslSocket::SslClientMode) + q_SSL_set_connect_state(ssl); + else + q_SSL_set_accept_state(ssl); + + q_SSL_set_ex_data(ssl, QTlsBackendOpenSSL::s_indexForSSLExtraData, this); + +#ifndef OPENSSL_NO_PSK + // Set the client callback for PSK + if (mode == QSslSocket::SslClientMode) + q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback); + else if (mode == QSslSocket::SslServerMode) + q_SSL_set_psk_server_callback(ssl, &q_ssl_psk_server_callback); + +#if OPENSSL_VERSION_NUMBER >= 0x10101006L + // Set the client callback for TLSv1.3 PSK + if (mode == QSslSocket::SslClientMode + && QSslSocket::sslLibraryBuildVersionNumber() >= 0x10101006L) { + q_SSL_set_psk_use_session_callback(ssl, &q_ssl_psk_use_session_callback); + } +#endif // openssl version >= 0x10101006L + +#endif // OPENSSL_NO_PSK + +#if QT_CONFIG(ocsp) + if (configuration.ocspStaplingEnabled()) { + if (mode == QSslSocket::SslServerMode) { + setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError, + QSslSocket::tr("Server-side QSslSocket does not support OCSP stapling")); + return false; + } + if (q_SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp) != 1) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Failed to enable OCSP stapling")); + return false; + } + } + + ocspResponseDer.clear(); + const auto backendConfig = configuration.backendConfiguration(); + auto responsePos = backendConfig.find("Qt-OCSP-response"); + if (responsePos != backendConfig.end()) { + // This is our private, undocumented 'API' we use for the auto-testing of + // OCSP-stapling. It must be a der-encoded OCSP response, presumably set + // by tst_QOcsp. + const QVariant data(responsePos.value()); + if (data.canConvert()) + ocspResponseDer = data.toByteArray(); + } + + if (ocspResponseDer.size()) { + if (mode != QSslSocket::SslServerMode) { + setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError, + QSslSocket::tr("Client-side sockets do not send OCSP responses")); + return false; + } + } +#endif // ocsp + + return true; +} + +void TlsCryptographOpenSSL::destroySslContext() +{ + if (ssl) { + if (!q_SSL_in_init(ssl) && !systemOrSslErrorDetected) { + // We do not send a shutdown alert here. Just mark the session as + // resumable for qhttpnetworkconnection's "optimization", otherwise + // OpenSSL won't start a session resumption. + if (q_SSL_shutdown(ssl) != 1) { + // Some error may be queued, clear it. + const auto errors = QTlsBackendOpenSSL::getErrorsFromOpenSsl(); + Q_UNUSED(errors); + } + } + q_SSL_free(ssl); + ssl = nullptr; + } + sslContextPointer.clear(); +} + +void TlsCryptographOpenSSL::storePeerCertificates() +{ + Q_ASSERT(d); + + // Store the peer certificate and chain. For clients, the peer certificate + // chain includes the peer certificate; for servers, it doesn't. Both the + // peer certificate and the chain may be empty if the peer didn't present + // any certificate. + X509 *x509 = q_SSL_get_peer_certificate(ssl); + + const auto peerCertificate = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(x509); + QTlsBackend::storePeerCertificate(d, peerCertificate); + q_X509_free(x509); + auto peerCertificateChain = q->peerCertificateChain(); + if (peerCertificateChain.isEmpty()) { + peerCertificateChain = QTlsPrivate::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(q_SSL_get_peer_cert_chain(ssl)); + if (!peerCertificate.isNull() && d->tlsMode() == QSslSocket::SslServerMode) + peerCertificateChain.prepend(peerCertificate); + QTlsBackend::storePeerCertificateChain(d, peerCertificateChain); + } +} + +#if QT_CONFIG(ocsp) + +bool TlsCryptographOpenSSL::checkOcspStatus() +{ + Q_ASSERT(ssl); + Q_ASSERT(d); + + const auto &configuration = q->sslConfiguration(); + Q_ASSERT(d->tlsMode() == QSslSocket::SslClientMode); // See initSslContext() for SslServerMode + Q_ASSERT(configuration.peerVerifyMode() != QSslSocket::VerifyNone); + + const auto clearErrorQueue = qScopeGuard([] { + QTlsBackendOpenSSL::logAndClearErrorQueue(); + }); + + ocspResponses.clear(); + ocspErrorDescription.clear(); + ocspErrors.clear(); + + const unsigned char *responseData = nullptr; + const long responseLength = q_SSL_get_tlsext_status_ocsp_resp(ssl, &responseData); + if (responseLength <= 0 || !responseData) { + ocspErrors.push_back(QSslError(QSslError::OcspNoResponseFound)); + return false; + } + + OCSP_RESPONSE *response = q_d2i_OCSP_RESPONSE(nullptr, &responseData, responseLength); + if (!response) { + // Treat this as a fatal SslHandshakeError. + ocspErrorDescription = QSslSocket::tr("Failed to decode OCSP response"); + return false; + } + const QSharedPointer responseGuard(response, q_OCSP_RESPONSE_free); + + const int ocspStatus = q_OCSP_response_status(response); + if (ocspStatus != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + // It's not a definitive response, it's an error message (not signed by the responder). + ocspErrors.push_back(QSslError(qt_OCSP_response_status_to_SslError(ocspStatus))); + return false; + } + + OCSP_BASICRESP *basicResponse = q_OCSP_response_get1_basic(response); + if (!basicResponse) { + // SslHandshakeError. + ocspErrorDescription = QSslSocket::tr("Failed to extract basic OCSP response"); + return false; + } + const QSharedPointer basicResponseGuard(basicResponse, q_OCSP_BASICRESP_free); + + SSL_CTX *ctx = q_SSL_get_SSL_CTX(ssl); // Does not increment refcount. + Q_ASSERT(ctx); + X509_STORE *store = q_SSL_CTX_get_cert_store(ctx); // Does not increment refcount. + if (!store) { + // SslHandshakeError. + ocspErrorDescription = QSslSocket::tr("No certificate verification store, cannot verify OCSP response"); + return false; + } + + STACK_OF(X509) *peerChain = q_SSL_get_peer_cert_chain(ssl); // Does not increment refcount. + X509 *peerX509 = q_SSL_get_peer_certificate(ssl); + Q_ASSERT(peerChain || peerX509); + const QSharedPointer peerX509Guard(peerX509, q_X509_free); + // OCSP_basic_verify with 0 as verificationFlags: + // + // 0) Tries to find the OCSP responder's certificate in either peerChain + // or basicResponse->certs. If not found, verification fails. + // 1) It checks the signature using the responder's public key. + // 2) Then it tries to validate the responder's cert (building a chain + // etc.) + // 3) It checks CertID in response. + // 4) Ensures the responder is authorized to sign the status respond. + // + // Note, OpenSSL prior to 1.0.2b would only use bs->certs to + // verify the responder's chain (see their commit 4ba9a4265bd). + // Working this around - is too much fuss for ancient versions we + // are dropping quite soon anyway. + const unsigned long verificationFlags = 0; + const int success = q_OCSP_basic_verify(basicResponse, peerChain, store, verificationFlags); + if (success <= 0) + ocspErrors.push_back(QSslError(QSslError::OcspResponseCannotBeTrusted)); + + if (q_OCSP_resp_count(basicResponse) != 1) { + ocspErrors.push_back(QSslError(QSslError::OcspMalformedResponse)); + return false; + } + + OCSP_SINGLERESP *singleResponse = q_OCSP_resp_get0(basicResponse, 0); + if (!singleResponse) { + ocspErrors.clear(); + // A fatal problem -> SslHandshakeError. + ocspErrorDescription = QSslSocket::tr("Failed to decode a SingleResponse from OCSP status response"); + return false; + } + + // Let's make sure the response is for the correct certificate - we + // can re-create this CertID using our peer's certificate and its + // issuer's public key. + ocspResponses.push_back(QOcspResponse()); + QOcspResponsePrivate *dResponse = ocspResponses.back().d.data(); + dResponse->subjectCert = configuration.peerCertificate(); + bool matchFound = false; + if (dResponse->subjectCert.isSelfSigned()) { + dResponse->signerCert = configuration.peerCertificate(); + matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, peerX509); + } else { + const STACK_OF(X509) *certs = q_SSL_get_peer_cert_chain(ssl); + if (!certs) // Oh, what a cataclysm! Last try: + certs = q_OCSP_resp_get0_certs(basicResponse); + if (certs) { + // It could be the first certificate in 'certs' is our peer's + // certificate. Since it was not captured by the 'self-signed' branch + // above, the CertID will not match and we'll just iterate on to the + // next certificate. So we start from 0, not 1. + for (int i = 0, e = q_sk_X509_num(certs); i < e; ++i) { + X509 *issuer = q_sk_X509_value(certs, i); + matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, issuer); + if (matchFound) { + if (q_X509_check_issued(issuer, peerX509) == X509_V_OK) { + dResponse->signerCert = QTlsPrivate::X509CertificateOpenSSL::certificateFromX509(issuer); + break; + } + matchFound = false; + } + } + } + } + + if (!matchFound) { + dResponse->signerCert.clear(); + ocspErrors.push_back({QSslError::OcspResponseCertIdUnknown, configuration.peerCertificate()}); + } + + // Check if the response is valid time-wise: + ASN1_GENERALIZEDTIME *revTime = nullptr; + ASN1_GENERALIZEDTIME *thisUpdate = nullptr; + ASN1_GENERALIZEDTIME *nextUpdate = nullptr; + int reason; + const int certStatus = q_OCSP_single_get0_status(singleResponse, &reason, &revTime, &thisUpdate, &nextUpdate); + if (!thisUpdate) { + // This is unexpected, treat as SslHandshakeError, OCSP_check_validity assumes this pointer + // to be != nullptr. + ocspErrors.clear(); + ocspResponses.clear(); + ocspErrorDescription = QSslSocket::tr("Failed to extract 'this update time' from the SingleResponse"); + return false; + } + + // OCSP_check_validity(this, next, nsec, maxsec) does this check: + // this <= now <= next. They allow some freedom to account + // for delays/time inaccuracy. + // this > now + nsec ? -> NOT_YET_VALID + // if maxsec >= 0: + // now - maxsec > this ? -> TOO_OLD + // now - nsec > next ? -> EXPIRED + // next < this ? -> NEXT_BEFORE_THIS + // OK. + if (!q_OCSP_check_validity(thisUpdate, nextUpdate, 60, -1)) + ocspErrors.push_back({QSslError::OcspResponseExpired, configuration.peerCertificate()}); + + // And finally, the status: + switch (certStatus) { + case V_OCSP_CERTSTATUS_GOOD: + // This certificate was not found among the revoked ones. + dResponse->certificateStatus = QOcspCertificateStatus::Good; + break; + case V_OCSP_CERTSTATUS_REVOKED: + dResponse->certificateStatus = QOcspCertificateStatus::Revoked; + dResponse->revocationReason = qt_OCSP_revocation_reason(reason); + ocspErrors.push_back({QSslError::CertificateRevoked, configuration.peerCertificate()}); + break; + case V_OCSP_CERTSTATUS_UNKNOWN: + dResponse->certificateStatus = QOcspCertificateStatus::Unknown; + ocspErrors.push_back({QSslError::OcspStatusUnknown, configuration.peerCertificate()}); + } + + return !ocspErrors.size(); +} + +#endif // QT_CONFIG(ocsp) + + +unsigned TlsCryptographOpenSSL::pskClientTlsCallback(const char *hint, char *identity, + unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len) +{ + Q_ASSERT(q); + + QSslPreSharedKeyAuthenticator authenticator; + // Fill in some read-only fields (for the user) + const int hintLength = hint ? int(std::strlen(hint)) : 0; + QTlsBackend::setupClientPskAuth(&authenticator, hint, hintLength, max_identity_len, max_psk_len); + // Let the client provide the remaining bits... + emit q->preSharedKeyAuthenticationRequired(&authenticator); + + // No PSK set? Return now to make the handshake fail + if (authenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int identityLength = qMin(authenticator.identity().length(), authenticator.maximumIdentityLength()); + std::memcpy(identity, authenticator.identity().constData(), identityLength); + identity[identityLength] = 0; + + const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); + std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); + return pskLength; +} + +unsigned TlsCryptographOpenSSL::pskServerTlsCallback(const char *identity, unsigned char *psk, + unsigned max_psk_len) +{ + Q_ASSERT(q); + + QSslPreSharedKeyAuthenticator authenticator; + + // Fill in some read-only fields (for the user) + QTlsBackend::setupServerPskAuth(&authenticator, identity, q->sslConfiguration().preSharedKeyIdentityHint(), + max_psk_len); + emit q->preSharedKeyAuthenticationRequired(&authenticator); + + // No PSK set? Return now to make the handshake fail + if (authenticator.preSharedKey().isEmpty()) + return 0; + + // Copy data back into OpenSSL + const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength()); + std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength); + return pskLength; +} + +#ifdef Q_OS_WIN + +void TlsCryptographOpenSSL::fetchCaRootForCert(const QSslCertificate &cert) +{ + Q_ASSERT(d); + Q_ASSERT(q); + + //The root certificate is downloaded from windows update, which blocks for 15 seconds in the worst case + //so the request is done in a worker thread. + QList customRoots; + if (fetchAuthorityInformation) + customRoots = q->sslConfiguration().caCertificates(); + + //Remember we are fetching and what we are fetching: + caToFetch = cert; + + QWindowsCaRootFetcher *fetcher = new QWindowsCaRootFetcher(cert, d->tlsMode(), customRoots, + q->peerVerifyName()); + connect(fetcher, &QWindowsCaRootFetcher::finished, this, &TlsCryptographOpenSSL::caRootLoaded, + Qt::QueuedConnection); + QMetaObject::invokeMethod(fetcher, "start", Qt::QueuedConnection); + QSslSocketPrivate::pauseSocketNotifiers(q); + d->setPaused(true); +} + +void TlsCryptographOpenSSL::caRootLoaded(QSslCertificate cert, QSslCertificate trustedRoot) +{ + if (caToFetch != cert) { + //Ooops, something from the previous connection attempt, ignore! + return; + } + + Q_ASSERT(d); + Q_ASSERT(q); + + //Done, fetched already: + caToFetch = QSslCertificate{}; + + if (fetchAuthorityInformation) { + if (!q->sslConfiguration().caCertificates().contains(trustedRoot)) + trustedRoot = QSslCertificate{}; + fetchAuthorityInformation = false; + } + + if (!trustedRoot.isNull() && !trustedRoot.isBlacklisted()) { + if (QSslSocketPrivate::rootCertOnDemandLoadingSupported()) { + //Add the new root cert to default cert list for use by future sockets + auto defaultConfig = QSslConfiguration::defaultConfiguration(); + defaultConfig.addCaCertificate(trustedRoot); + QSslConfiguration::setDefaultConfiguration(defaultConfig); + } + //Add the new root cert to this socket for future connections + QTlsBackend::addTustedRoot(d, trustedRoot); + //Remove the broken chain ssl errors (as chain is verified by windows) + for (int i=sslErrors.count() - 1; i >= 0; --i) { + if (sslErrors.at(i).certificate() == cert) { + switch (sslErrors.at(i).error()) { + case QSslError::UnableToGetLocalIssuerCertificate: + case QSslError::CertificateUntrusted: + case QSslError::UnableToVerifyFirstCertificate: + case QSslError::SelfSignedCertificateInChain: + // error can be ignored if OS says the chain is trusted + sslErrors.removeAt(i); + break; + default: + // error cannot be ignored + break; + } + } + } + } + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + // Continue with remaining errors + if (plainSocket) + plainSocket->resume(); + d->setPaused(false); + if (checkSslErrors() && ssl) { + bool willClose = (d->isAutoStartingHandshake() && d->isPendingClose()); + continueHandshake(); + if (!willClose) + transmit(); + } +} + +#endif // Q_OS_WIN + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qtls_openssl_p.h b/src/plugins/tls/openssl/qtls_openssl_p.h new file mode 100644 index 0000000000..48c9223f99 --- /dev/null +++ b/src/plugins/tls/openssl/qtls_openssl_p.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLS_OPENSSL_P_H +#define QTLS_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "qtlsbackend_openssl_p.h" +#include "qsslcontext_openssl_p.h" +#include "qopenssl_p.h" + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class TlsCryptographOpenSSL : public TlsCryptograph +{ +public: + enum ExDataOffset { + errorOffsetInExData = 1, + socketOffsetInExData = 2 + }; + + ~TlsCryptographOpenSSL(); + + void init(QSslSocket *qObj, QSslSocketPrivate *dObj) override; + void checkSettingSslContext(QSharedPointer tlsContext) override; + QSharedPointer sslContext() const override; + + QList tlsErrors() const override; + + void startClientEncryption() override; + void startServerEncryption() override; + bool startHandshake(); + void enableHandshakeContinuation() override; + void cancelCAFetch() override; + void continueHandshake() override; + void transmit() override; + void disconnectFromHost() override; + void disconnected() override; + QSslCipher sessionCipher() const override; + QSsl::SslProtocol sessionProtocol() const override; + QList ocsps() const override; + + bool checkSslErrors(); + int handleNewSessionTicket(SSL *connection); + + void alertMessageSent(int encoded); + void alertMessageReceived(int encoded); + + int emitErrorFromCallback(X509_STORE_CTX *ctx); + void trySendFatalAlert(); + +#if QT_CONFIG(ocsp) + bool checkOcspStatus(); +#endif + + QSslSocket *q = nullptr; + QSslSocketPrivate *d = nullptr; + + void storePeerCertificates(); + + unsigned pskClientTlsCallback(const char *hint, char *identity, unsigned max_identity_len, + unsigned char *psk, unsigned max_psk_len); + unsigned pskServerTlsCallback(const char *identity, unsigned char *psk, + unsigned max_psk_len); + +#ifdef Q_OS_WIN + void fetchCaRootForCert(const QSslCertificate &cert); + void caRootLoaded(QSslCertificate certificate, QSslCertificate trustedRoot); +#endif + + QByteArray ocspResponseDer; +private: + // TLSTODO: names were preserved, to make comparison + // easier (see qsslsocket_openssl.cpp, while it exists). + bool initSslContext(); + void destroySslContext(); + + QSharedPointer sslContextPointer; + SSL *ssl = nullptr; // TLSTODO: RAII. + + QList errorList; + QList sslErrors; + + BIO *readBio = nullptr; + BIO *writeBio = nullptr; + + QList ocspResponses; + + // This decription will go to setErrorAndEmit(SslHandshakeError, ocspErrorDescription) + QString ocspErrorDescription; + // These will go to sslErrors() + QList ocspErrors; + + bool systemOrSslErrorDetected = false; + bool handshakeInterrupted = false; + + bool fetchAuthorityInformation = false; + QSslCertificate caToFetch; + + bool inSetAndEmitError = false; + bool pendingFatalAlert = false; + bool errorsReportedFromCallback = false; + + bool shutdown = false; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLS_OPENSSL_P_H + diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp new file mode 100644 index 0000000000..60052f3a2f --- /dev/null +++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp @@ -0,0 +1,637 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qsslsocket_openssl_symbols_p.h" +#include "qtlsbackend_openssl_p.h" +#include "qtlskey_openssl_p.h" +#include "qx509_openssl_p.h" +#include "qtls_openssl_p.h" + +#if QT_CONFIG(dtls) +#include "qdtls_openssl_p.h" +#endif // QT_CONFIG(dtls) + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "qopenssl_p.h" + +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl"); + +Q_GLOBAL_STATIC(QRecursiveMutex, qt_opensslInitMutex) + +static void q_loadCiphersForConnection(SSL *connection, QList &ciphers, + QList &defaultCiphers) +{ + Q_ASSERT(connection); + + STACK_OF(SSL_CIPHER) *supportedCiphers = q_SSL_get_ciphers(connection); + for (int i = 0; i < q_sk_SSL_CIPHER_num(supportedCiphers); ++i) { + if (SSL_CIPHER *cipher = q_sk_SSL_CIPHER_value(supportedCiphers, i)) { + const auto ciph = QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(cipher); + if (!ciph.isNull()) { + // Unconditionally exclude ADH and AECDH ciphers since they offer no MITM protection + if (!ciph.name().toLower().startsWith(QLatin1String("adh")) && + !ciph.name().toLower().startsWith(QLatin1String("exp-adh")) && + !ciph.name().toLower().startsWith(QLatin1String("aecdh"))) { + ciphers << ciph; + + if (ciph.usedBits() >= 128) + defaultCiphers << ciph; + } + } + } + } +} + +bool QTlsBackendOpenSSL::s_libraryLoaded = false; +bool QTlsBackendOpenSSL::s_loadedCiphersAndCerts = false; +int QTlsBackendOpenSSL::s_indexForSSLExtraData = -1; + +QString QTlsBackendOpenSSL::getErrorsFromOpenSsl() +{ + QString errorString; + char buf[256] = {}; // OpenSSL docs claim both 120 and 256; use the larger. + unsigned long errNum; + while ((errNum = q_ERR_get_error())) { + if (!errorString.isEmpty()) + errorString.append(QLatin1String(", ")); + q_ERR_error_string_n(errNum, buf, sizeof buf); + errorString.append(QString::fromLatin1(buf)); // error is ascii according to man ERR_error_string + } + return errorString; +} + +void QTlsBackendOpenSSL::logAndClearErrorQueue() +{ + const auto errors = getErrorsFromOpenSsl(); + if (errors.size()) + qCWarning(lcTlsBackend) << "Discarding errors:" << errors; +} + +void QTlsBackendOpenSSL::clearErrorQueue() +{ + const auto errs = getErrorsFromOpenSsl(); + Q_UNUSED(errs); +} + +bool QTlsBackendOpenSSL::ensureLibraryLoaded() +{ + if (!q_resolveOpenSslSymbols()) + return false; + + const QMutexLocker locker(qt_opensslInitMutex()); + + if (!s_libraryLoaded) { + // Initialize OpenSSL. + if (q_OPENSSL_init_ssl(0, nullptr) != 1) + return false; + + if (q_OpenSSL_version_num() < 0x10101000L) { + qCWarning(lcTlsBackend, "QSslSocket: OpenSSL >= 1.1.1 is required; %s was found instead", q_OpenSSL_version(OPENSSL_VERSION)); + return false; + } + + q_SSL_load_error_strings(); + q_OpenSSL_add_all_algorithms(); + + s_indexForSSLExtraData = q_CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, 0L, nullptr, nullptr, + nullptr, nullptr); + + // Initialize OpenSSL's random seed. + if (!q_RAND_status()) { + qWarning("Random number generator not seeded, disabling SSL support"); + return false; + } + + s_libraryLoaded = true; + } + + return true; +} + +QString QTlsBackendOpenSSL::backendName() const +{ + return builtinBackendNames[nameIndexOpenSSL]; +} + +bool QTlsBackendOpenSSL::isValid() const +{ + return ensureLibraryLoaded(); +} + +long QTlsBackendOpenSSL::tlsLibraryVersionNumber() const +{ + return q_OpenSSL_version_num(); +} + +QString QTlsBackendOpenSSL::tlsLibraryVersionString() const +{ + const char *versionString = q_OpenSSL_version(OPENSSL_VERSION); + if (!versionString) + return QString(); + + return QString::fromLatin1(versionString); +} + +long QTlsBackendOpenSSL::tlsLibraryBuildVersionNumber() const +{ + return OPENSSL_VERSION_NUMBER; +} + +QString QTlsBackendOpenSSL::tlsLibraryBuildVersionString() const +{ + // Using QStringLiteral to store the version string as unicode and + // avoid false positives from Google searching the playstore for old + // SSL versions. See QTBUG-46265 + return QStringLiteral(OPENSSL_VERSION_TEXT); +} + +void QTlsBackendOpenSSL::ensureInitialized() const +{ + // Old qsslsocket_openssl calls supportsSsl() (which means + // library found and symbols resolved, this already assured + // by the fact we end up in this function (isValid() returned + // true for the backend, see its code). The qsslsocket_openssl + // proceedes with loading certificate, ciphers and elliptic + // curves. + ensureCiphersAndCertsLoaded(); +} + +void QTlsBackendOpenSSL::ensureCiphersAndCertsLoaded() const +{ + const QMutexLocker locker(qt_opensslInitMutex()); + + if (s_loadedCiphersAndCerts) + return; + s_loadedCiphersAndCerts = true; + + resetDefaultCiphers(); + resetDefaultEllipticCurves(); + +#if QT_CONFIG(library) + //load symbols needed to receive certificates from system store +#if defined(Q_OS_QNX) + QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); +#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) + QList dirs = QSslSocketPrivate::unixRootCertDirectories(); + QStringList symLinkFilter; + symLinkFilter << QLatin1String("[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]"); + for (int a = 0; a < dirs.count(); ++a) { + QDirIterator iterator(QLatin1String(dirs.at(a)), symLinkFilter, QDir::Files); + if (iterator.hasNext()) { + QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); + break; + } + } +#endif +#endif // QT_CONFIG(library) + // if on-demand loading was not enabled, load the certs now + if (!QSslSocketPrivate::rootCertOnDemandLoadingSupported()) + setDefaultCaCertificates(systemCaCertificates()); +#ifdef Q_OS_WIN + //Enabled for fetching additional root certs from windows update on windows. + //This flag is set false by setDefaultCaCertificates() indicating the app uses + //its own cert bundle rather than the system one. + //Same logic that disables the unix on demand cert loading. + //Unlike unix, we do preload the certificates from the cert store. + QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); +#endif +} + +void QTlsBackendOpenSSL::resetDefaultCiphers() +{ + SSL_CTX *myCtx = q_SSL_CTX_new(q_TLS_client_method()); + // Note, we assert, not just silently return/bail out early: + // this should never happen and problems with OpenSSL's initialization + // must be caught before this (see supportsSsl()). + Q_ASSERT(myCtx); + SSL *mySsl = q_SSL_new(myCtx); + Q_ASSERT(mySsl); + + QList ciphers; + QList defaultCiphers; + + q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers); + + q_SSL_CTX_free(myCtx); + q_SSL_free(mySsl); + + setDefaultSupportedCiphers(ciphers); + setDefaultCiphers(defaultCiphers); + +#if QT_CONFIG(dtls) + ciphers.clear(); + defaultCiphers.clear(); + myCtx = q_SSL_CTX_new(q_DTLS_client_method()); + if (myCtx) { + mySsl = q_SSL_new(myCtx); + if (mySsl) { + q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers); + setDefaultDtlsCiphers(defaultCiphers); + q_SSL_free(mySsl); + } + q_SSL_CTX_free(myCtx); + } +#endif // dtls +} + +QList QTlsBackendOpenSSL::supportedProtocols() const +{ + QList protocols; + + protocols << QSsl::AnyProtocol; + protocols << QSsl::SecureProtocols; + protocols << QSsl::TlsV1_0; + protocols << QSsl::TlsV1_0OrLater; + protocols << QSsl::TlsV1_1; + protocols << QSsl::TlsV1_1OrLater; + protocols << QSsl::TlsV1_2; + protocols << QSsl::TlsV1_2OrLater; + +#ifdef TLS1_3_VERSION + protocols << QSsl::TlsV1_3; + protocols << QSsl::TlsV1_3OrLater; +#endif // TLS1_3_VERSION + +#if QT_CONFIG(dtls) + protocols << QSsl::DtlsV1_0; + protocols << QSsl::DtlsV1_0OrLater; + protocols << QSsl::DtlsV1_2; + protocols << QSsl::DtlsV1_2OrLater; +#endif // dtls + + return protocols; +} + +QList QTlsBackendOpenSSL::supportedFeatures() const +{ + QList features; + + features << QSsl::SupportedFeature::CertificateVerification; + features << QSsl::SupportedFeature::ClientSideAlpn; + features << QSsl::SupportedFeature::ServerSideAlpn; + features << QSsl::SupportedFeature::Ocsp; + features << QSsl::SupportedFeature::Psk; + features << QSsl::SupportedFeature::SessionTicket; + features << QSsl::SupportedFeature::Alerts; + + return features; +} + +QList QTlsBackendOpenSSL::implementedClasses() const +{ + QList classes; + + classes << QSsl::ImplementedClass::Key; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Socket; +#if QT_CONFIG(dtls) + classes << QSsl::ImplementedClass::Dtls; +#endif + classes << QSsl::ImplementedClass::EllipticCurve; + classes << QSsl::ImplementedClass::DiffieHellman; + + return classes; +} + +QTlsPrivate::TlsKey *QTlsBackendOpenSSL::createKey() const +{ + return new QTlsPrivate::TlsKeyOpenSSL; +} + +QTlsPrivate::X509Certificate *QTlsBackendOpenSSL::createCertificate() const +{ + return new QTlsPrivate::X509CertificateOpenSSL; +} + +namespace QTlsPrivate { + +// TLSTODO: remove. +#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) +QList fetchSslCertificateData(); +#endif + +QList systemCaCertificates(); + +#ifndef Q_OS_DARWIN +QList systemCaCertificates() +{ +#ifdef QSSLSOCKET_DEBUG + QElapsedTimer timer; + timer.start(); +#endif + QList systemCerts; +#if defined(Q_OS_WIN) + HCERTSTORE hSystemStore; + hSystemStore = CertOpenSystemStoreW(0, L"ROOT"); + if (hSystemStore) { + PCCERT_CONTEXT pc = nullptr; + while (1) { + pc = CertFindCertificateInStore(hSystemStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, pc); + if (!pc) + break; + QByteArray der(reinterpret_cast(pc->pbCertEncoded), + static_cast(pc->cbCertEncoded)); + QSslCertificate cert(der, QSsl::Der); + systemCerts.append(cert); + } + CertCloseStore(hSystemStore, 0); + } +#elif defined(Q_OS_UNIX) + QSet certFiles; + QDir currentDir; + QStringList nameFilters; + QList directories; + QSsl::EncodingFormat platformEncodingFormat; +# ifndef Q_OS_ANDROID + directories = QSslSocketPrivate::unixRootCertDirectories(); + nameFilters << QLatin1String("*.pem") << QLatin1String("*.crt"); + platformEncodingFormat = QSsl::Pem; +# else + // Q_OS_ANDROID + QByteArray ministroPath = qgetenv("MINISTRO_SSL_CERTS_PATH"); // Set by Ministro + directories << ministroPath; + nameFilters << QLatin1String("*.der"); + platformEncodingFormat = QSsl::Der; +# ifndef Q_OS_ANDROID_EMBEDDED + if (ministroPath.isEmpty()) { + QList certificateData = fetchSslCertificateData(); + for (int i = 0; i < certificateData.size(); ++i) { + systemCerts.append(QSslCertificate::fromData(certificateData.at(i), QSsl::Der)); + } + } else +# endif //Q_OS_ANDROID_EMBEDDED +# endif //Q_OS_ANDROID + { + currentDir.setNameFilters(nameFilters); + for (int a = 0; a < directories.count(); a++) { + currentDir.setPath(QLatin1String(directories.at(a))); + QDirIterator it(currentDir); + while (it.hasNext()) { + it.next(); + // use canonical path here to not load the same certificate twice if symlinked + certFiles.insert(it.fileInfo().canonicalFilePath()); + } + } + for (const QString& file : qAsConst(certFiles)) + systemCerts.append(QSslCertificate::fromPath(file, platformEncodingFormat)); +# ifndef Q_OS_ANDROID + systemCerts.append(QSslCertificate::fromPath(QLatin1String("/etc/pki/tls/certs/ca-bundle.crt"), QSsl::Pem)); // Fedora, Mandriva + systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/local/share/certs/ca-root-nss.crt"), QSsl::Pem)); // FreeBSD's ca_root_nss +# endif + } +#endif +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "systemCaCertificates retrieval time " << timer.elapsed() << "ms"; + qCDebug(lcTlsBackend) << "imported " << systemCerts.count() << " certificates"; +#endif + + return systemCerts; +} +#endif // !Q_OS_DARWIN +} // namespace QTlsPrivate + +QList QTlsBackendOpenSSL::systemCaCertificates() const +{ + return QTlsPrivate::systemCaCertificates(); +} + +QTlsPrivate::DtlsCookieVerifier *QTlsBackendOpenSSL::createDtlsCookieVerifier() const +{ +#if QT_CONFIG(dtls) + return new QDtlsClientVerifierOpenSSL; +#else + qCWarning(lcTlsBackend, "Feature 'dtls' is disabled, cannot verify DTLS cookies"); + return nullptr; +#endif // QT_CONFIG(dtls) +} + +QTlsPrivate::TlsCryptograph *QTlsBackendOpenSSL::createTlsCryptograph() const +{ + return new QTlsPrivate::TlsCryptographOpenSSL; +} + +QTlsPrivate::DtlsCryptograph *QTlsBackendOpenSSL::createDtlsCryptograph(QDtls *q, int mode) const +{ +#if QT_CONFIG(dtls) + return new QDtlsPrivateOpenSSL(q, QSslSocket::SslMode(mode)); +#else + Q_UNUSED(q); + Q_UNUSED(mode); + qCWarning(lcTlsBackend, "Feature 'dtls' is disabled, cannot encrypt UDP datagrams"); + return nullptr; +#endif // QT_CONFIG(dtls) +} + +QTlsPrivate::X509ChainVerifyPtr QTlsBackendOpenSSL::X509Verifier() const +{ + return QTlsPrivate::X509CertificateOpenSSL::verify; +} + +QTlsPrivate::X509PemReaderPtr QTlsBackendOpenSSL::X509PemReader() const +{ + return QTlsPrivate::X509CertificateOpenSSL::certificatesFromPem; +} + +QTlsPrivate::X509DerReaderPtr QTlsBackendOpenSSL::X509DerReader() const +{ + return QTlsPrivate::X509CertificateOpenSSL::certificatesFromDer; +} + +QTlsPrivate::X509Pkcs12ReaderPtr QTlsBackendOpenSSL::X509Pkcs12Reader() const +{ + return QTlsPrivate::X509CertificateOpenSSL::importPkcs12; +} + +QList QTlsBackendOpenSSL::ellipticCurvesIds() const +{ + QList ids; + +#ifndef OPENSSL_NO_EC + const size_t curveCount = q_EC_get_builtin_curves(nullptr, 0); + QVarLengthArray builtinCurves(static_cast(curveCount)); + + if (q_EC_get_builtin_curves(builtinCurves.data(), curveCount) == curveCount) { + ids.reserve(curveCount); + for (const auto &ec : builtinCurves) + ids.push_back(ec.nid); + } +#endif // OPENSSL_NO_EC + + return ids; +} + + int QTlsBackendOpenSSL::curveIdFromShortName(const QString &name) const + { + int nid = 0; + if (name.isEmpty()) + return nid; + + ensureInitialized(); // TLSTODO: check if it's needed! +#ifndef OPENSSL_NO_EC + const QByteArray curveNameLatin1 = name.toLatin1(); + nid = q_OBJ_sn2nid(curveNameLatin1.data()); + + if (nid == 0) + nid = q_EC_curve_nist2nid(curveNameLatin1.data()); +#endif // !OPENSSL_NO_EC + + return nid; + } + + int QTlsBackendOpenSSL::curveIdFromLongName(const QString &name) const + { + int nid = 0; + if (name.isEmpty()) + return nid; + + ensureInitialized(); + +#ifndef OPENSSL_NO_EC + const QByteArray curveNameLatin1 = name.toLatin1(); + nid = q_OBJ_ln2nid(curveNameLatin1.data()); +#endif + + return nid; + } + + QString QTlsBackendOpenSSL::shortNameForId(int id) const + { + QString result; + +#ifndef OPENSSL_NO_EC + if (id != 0) + result = QString::fromLatin1(q_OBJ_nid2sn(id)); +#endif + + return result; + } + +QString QTlsBackendOpenSSL::longNameForId(int id) const +{ + QString result; + +#ifndef OPENSSL_NO_EC + if (id != 0) + result = QString::fromLatin1(q_OBJ_nid2ln(id)); +#endif + + return result; +} + +// NIDs of named curves allowed in TLS as per RFCs 4492 and 7027, +// see also https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 +static const int tlsNamedCurveNIDs[] = { + // RFC 4492 + NID_sect163k1, + NID_sect163r1, + NID_sect163r2, + NID_sect193r1, + NID_sect193r2, + NID_sect233k1, + NID_sect233r1, + NID_sect239k1, + NID_sect283k1, + NID_sect283r1, + NID_sect409k1, + NID_sect409r1, + NID_sect571k1, + NID_sect571r1, + + NID_secp160k1, + NID_secp160r1, + NID_secp160r2, + NID_secp192k1, + NID_X9_62_prime192v1, // secp192r1 + NID_secp224k1, + NID_secp224r1, + NID_secp256k1, + NID_X9_62_prime256v1, // secp256r1 + NID_secp384r1, + NID_secp521r1, + + // RFC 7027 + NID_brainpoolP256r1, + NID_brainpoolP384r1, + NID_brainpoolP512r1 +}; + +const size_t tlsNamedCurveNIDCount = sizeof(tlsNamedCurveNIDs) / sizeof(tlsNamedCurveNIDs[0]); + +bool QTlsBackendOpenSSL::isTlsNamedCurve(int id) const +{ + const int *const tlsNamedCurveNIDsEnd = tlsNamedCurveNIDs + tlsNamedCurveNIDCount; + return std::find(tlsNamedCurveNIDs, tlsNamedCurveNIDsEnd, id) != tlsNamedCurveNIDsEnd; +} + +QString QTlsBackendOpenSSL::msgErrorsDuringHandshake() +{ + return QSslSocket::tr("Error during SSL handshake: %1").arg(getErrorsFromOpenSsl()); +} + +QSslCipher QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(const SSL_CIPHER *cipher) +{ + Q_ASSERT(cipher); + char buf [256] = {}; + const QString desc = QString::fromLatin1(q_SSL_CIPHER_description(cipher, buf, sizeof(buf))); + int supportedBits = 0; + const int bits = q_SSL_CIPHER_get_bits(cipher, &supportedBits); + return createCiphersuite(desc, bits, supportedBits); +} + +void QTlsBackendOpenSSL::forceAutotestSecurityLevel() +{ + QSslContext::forceAutoTestSecurityLevel(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl_p.h b/src/plugins/tls/openssl/qtlsbackend_openssl_p.h new file mode 100644 index 0000000000..93b6442a59 --- /dev/null +++ b/src/plugins/tls/openssl/qtlsbackend_openssl_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLSBACKEND_OPENSSL_P_H +#define QTLSBACKEND_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include +#include + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QTlsBackendOpenSSL final : public QTlsBackend +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QTlsBackend_iid) + Q_INTERFACES(QTlsBackend) + +public: + + static QString getErrorsFromOpenSsl(); + static void logAndClearErrorQueue(); + static void clearErrorQueue(); + + static bool ensureLibraryLoaded(); + // Index used in SSL_get_ex_data to get the matching TlsCryptographerOpenSSL: + static bool s_libraryLoaded; + static bool s_loadedCiphersAndCerts; + static int s_indexForSSLExtraData; + + static QString msgErrorsDuringHandshake(); + static QSslCipher qt_OpenSSL_cipher_to_QSslCipher(const SSL_CIPHER *cipher); +private: + + QString backendName() const override; + bool isValid() const override; + long tlsLibraryVersionNumber() const override; + QString tlsLibraryVersionString() const override; + long tlsLibraryBuildVersionNumber() const override; + QString tlsLibraryBuildVersionString() const override; + + void ensureInitialized() const override; + void ensureCiphersAndCertsLoaded() const; + static void resetDefaultCiphers(); + + QList supportedProtocols() const override; + QList supportedFeatures() const override; + QList implementedClasses() const override; + + // QSslKey: + QTlsPrivate::TlsKey *createKey() const override; + + // QSslCertificate: + QTlsPrivate::X509Certificate *createCertificate() const override; + QList systemCaCertificates() const override; + + QTlsPrivate::TlsCryptograph *createTlsCryptograph() const override; + QTlsPrivate::DtlsCookieVerifier *createDtlsCookieVerifier() const override; + QTlsPrivate::DtlsCryptograph *createDtlsCryptograph(QDtls *q, int mode) const override; + + QTlsPrivate::X509ChainVerifyPtr X509Verifier() const override; + QTlsPrivate::X509PemReaderPtr X509PemReader() const override; + QTlsPrivate::X509DerReaderPtr X509DerReader() const override; + QTlsPrivate::X509Pkcs12ReaderPtr X509Pkcs12Reader() const override; + + // Elliptic curves: + QList ellipticCurvesIds() const override; + int curveIdFromShortName(const QString &name) const override; + int curveIdFromLongName(const QString &name) const override; + QString shortNameForId(int cid) const override; + QString longNameForId(int cid) const override; + bool isTlsNamedCurve(int cid) const override; + + // DH parameters: + using DHParams = QSslDiffieHellmanParameters; + int dhParametersFromDer(const QByteArray &derData, QByteArray *data) const override; + int dhParametersFromPem(const QByteArray &pemData, QByteArray *data) const override; + + void forceAutotestSecurityLevel() override; +}; + +QT_END_NAMESPACE + +#endif // QTLSBACKEND_OPENSSL_P_H + + diff --git a/src/plugins/tls/openssl/qtlskey_openssl.cpp b/src/plugins/tls/openssl/qtlskey_openssl.cpp new file mode 100644 index 0000000000..5333623d70 --- /dev/null +++ b/src/plugins/tls/openssl/qtlskey_openssl.cpp @@ -0,0 +1,511 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qsslsocket_openssl_symbols_p.h" +#include "qtlskey_openssl_p.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +void TlsKeyOpenSSL::decodeDer(QSsl::KeyType type, QSsl::KeyAlgorithm algorithm, const QByteArray &der, + const QByteArray &passPhrase, bool deepClear) +{ + if (der.isEmpty()) + return; + + keyType = type; + keyAlgorithm = algorithm; + + QMap headers; + const auto pem = pemFromDer(der, headers); + + decodePem(type, algorithm, pem, passPhrase, deepClear); +} + +void TlsKeyOpenSSL::decodePem(KeyType type, KeyAlgorithm algorithm, const QByteArray &pem, + const QByteArray &passPhrase, bool deepClear) +{ + if (pem.isEmpty()) + return; + + keyType = type; + keyAlgorithm = algorithm; + + clear(deepClear); + + BIO *bio = q_BIO_new_mem_buf(const_cast(pem.data()), pem.size()); + if (!bio) + return; + + const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); + + void *phrase = const_cast(passPhrase.data()); + + if (algorithm == QSsl::Rsa) { + RSA *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_RSA_PUBKEY(bio, &rsa, nullptr, phrase) + : q_PEM_read_bio_RSAPrivateKey(bio, &rsa, nullptr, phrase); + if (rsa && rsa == result) + keyIsNull = false; + } else if (algorithm == QSsl::Dsa) { + DSA *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_DSA_PUBKEY(bio, &dsa, nullptr, phrase) + : q_PEM_read_bio_DSAPrivateKey(bio, &dsa, nullptr, phrase); + if (dsa && dsa == result) + keyIsNull = false; + } else if (algorithm == QSsl::Dh) { + EVP_PKEY *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_PUBKEY(bio, nullptr, nullptr, phrase) + : q_PEM_read_bio_PrivateKey(bio, nullptr, nullptr, phrase); + if (result) + dh = q_EVP_PKEY_get1_DH(result); + if (dh) + keyIsNull = false; + q_EVP_PKEY_free(result); +#ifndef OPENSSL_NO_EC + } else if (algorithm == QSsl::Ec) { + EC_KEY *result = (type == QSsl::PublicKey) + ? q_PEM_read_bio_EC_PUBKEY(bio, &ec, nullptr, phrase) + : q_PEM_read_bio_ECPrivateKey(bio, &ec, nullptr, phrase); + if (ec && ec == result) + keyIsNull = false; +#endif + } +} + +QByteArray TlsKeyOpenSSL::derFromPem(const QByteArray &pem, QMap *headers) const +{ + QByteArray header = pemHeader(); + QByteArray footer = pemFooter(); + + QByteArray der(pem); + + int headerIndex = der.indexOf(header); + int footerIndex = der.indexOf(footer, headerIndex + header.length()); + if (type() != QSsl::PublicKey) { + if (headerIndex == -1 || footerIndex == -1) { + header = pkcs8Header(true); + footer = pkcs8Footer(true); + headerIndex = der.indexOf(header); + footerIndex = der.indexOf(footer, headerIndex + header.length()); + } + if (headerIndex == -1 || footerIndex == -1) { + header = pkcs8Header(false); + footer = pkcs8Footer(false); + headerIndex = der.indexOf(header); + footerIndex = der.indexOf(footer, headerIndex + header.length()); + } + } + if (headerIndex == -1 || footerIndex == -1) + return QByteArray(); + + der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); + + if (der.contains("Proc-Type:")) { + // taken from QHttpNetworkReplyPrivate::parseHeader + int i = 0; + while (i < der.count()) { + int j = der.indexOf(':', i); // field-name + if (j == -1) + break; + const QByteArray field = der.mid(i, j - i).trimmed(); + j++; + // any number of LWS is allowed before and after the value + QByteArray value; + do { + i = der.indexOf('\n', j); + if (i == -1) + break; + if (!value.isEmpty()) + value += ' '; + // check if we have CRLF or only LF + bool hasCR = (i && der[i-1] == '\r'); + int length = i -(hasCR ? 1: 0) - j; + value += der.mid(j, length).trimmed(); + j = ++i; + } while (i < der.count() && (der.at(i) == ' ' || der.at(i) == '\t')); + if (i == -1) + break; // something is wrong + + headers->insert(field, value); + } + der = der.mid(i); + } + + return QByteArray::fromBase64(der); // ignores newlines +} + +void TlsKeyOpenSSL::clear(bool deep) +{ + keyIsNull = true; + + if (algorithm() == QSsl::Rsa && rsa) { + if (deep) + q_RSA_free(rsa); + rsa = nullptr; + } + if (algorithm() == QSsl::Dsa && dsa) { + if (deep) + q_DSA_free(dsa); + dsa = nullptr; + } + if (algorithm() == QSsl::Dh && dh) { + if (deep) + q_DH_free(dh); + dh = nullptr; + } +#ifndef OPENSSL_NO_EC + if (algorithm() == QSsl::Ec && ec) { + if (deep) + q_EC_KEY_free(ec); + ec = nullptr; + } +#endif + if (algorithm() == QSsl::Opaque && opaque) { + if (deep) + q_EVP_PKEY_free(opaque); + opaque = nullptr; + } +} + +Qt::HANDLE TlsKeyOpenSSL::handle() const +{ + switch (keyAlgorithm) { + case QSsl::Opaque: + return Qt::HANDLE(opaque); + case QSsl::Rsa: + return Qt::HANDLE(rsa); + case QSsl::Dsa: + return Qt::HANDLE(dsa); + case QSsl::Dh: + return Qt::HANDLE(dh); +#ifndef OPENSSL_NO_EC + case QSsl::Ec: + return Qt::HANDLE(ec); +#endif + default: + return Qt::HANDLE(nullptr); + } +} + +int TlsKeyOpenSSL::length() const +{ + if (isNull() || algorithm() == QSsl::Opaque) + return -1; + + switch (algorithm()) { + case QSsl::Rsa: + return q_RSA_bits(rsa); + case QSsl::Dsa: + return q_DSA_bits(dsa); + case QSsl::Dh: + return q_DH_bits(dh); +#ifndef OPENSSL_NO_EC + case QSsl::Ec: + return q_EC_GROUP_get_degree(q_EC_KEY_get0_group(ec)); +#endif + default: + return -1; + } +} + +QByteArray TlsKeyOpenSSL::toPem(const QByteArray &passPhrase) const +{ + if (!QSslSocket::supportsSsl() || isNull() || algorithm() == QSsl::Opaque) + return {}; + + const EVP_CIPHER *cipher = nullptr; + if (type() == QSsl::PrivateKey && !passPhrase.isEmpty()) { +#ifndef OPENSSL_NO_DES + cipher = q_EVP_des_ede3_cbc(); +#else + return {}; +#endif + } + + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) + return {}; + + const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); + + bool fail = false; + + if (algorithm() == QSsl::Rsa) { + if (type() == QSsl::PublicKey) { + if (!q_PEM_write_bio_RSA_PUBKEY(bio, rsa)) + fail = true; + } else { + if (!q_PEM_write_bio_RSAPrivateKey( + bio, rsa, cipher, (uchar *)passPhrase.data(), + passPhrase.size(), nullptr, nullptr)) { + fail = true; + } + } + } else if (algorithm() == QSsl::Dsa) { + if (type() == QSsl::PublicKey) { + if (!q_PEM_write_bio_DSA_PUBKEY(bio, dsa)) + fail = true; + } else { + if (!q_PEM_write_bio_DSAPrivateKey( + bio, dsa, cipher, (uchar *)passPhrase.data(), + passPhrase.size(), nullptr, nullptr)) { + fail = true; + } + } + } else if (algorithm() == QSsl::Dh) { + EVP_PKEY *result = q_EVP_PKEY_new(); + if (!result || !q_EVP_PKEY_set1_DH(result, dh)) { + fail = true; + } else if (type() == QSsl::PublicKey) { + if (!q_PEM_write_bio_PUBKEY(bio, result)) + fail = true; + } else if (!q_PEM_write_bio_PrivateKey( + bio, result, cipher, (uchar *)passPhrase.data(), + passPhrase.size(), nullptr, nullptr)) { + fail = true; + } + q_EVP_PKEY_free(result); +#ifndef OPENSSL_NO_EC + } else if (algorithm() == QSsl::Ec) { + if (type() == QSsl::PublicKey) { + if (!q_PEM_write_bio_EC_PUBKEY(bio, ec)) + fail = true; + } else { + if (!q_PEM_write_bio_ECPrivateKey( + bio, ec, cipher, (uchar *)passPhrase.data(), + passPhrase.size(), nullptr, nullptr)) { + fail = true; + } + } +#endif + } else { + fail = true; + } + + QByteArray pem; + if (!fail) { + char *data = nullptr; + const long size = q_BIO_get_mem_data(bio, &data); + if (size > 0 && data) + pem = QByteArray(data, size); + } + + return pem; +} + +void TlsKeyOpenSSL::fromHandle(Qt::HANDLE handle, QSsl::KeyType expectedType) +{ + EVP_PKEY *evpKey = reinterpret_cast(handle); + if (!evpKey || !fromEVP_PKEY(evpKey)) { + opaque = evpKey; + keyAlgorithm = QSsl::Opaque; + } else { + q_EVP_PKEY_free(evpKey); + } + + keyType = expectedType; + keyIsNull = !opaque; +} + +bool TlsKeyOpenSSL::fromEVP_PKEY(EVP_PKEY *pkey) +{ + if (!pkey) + return false; + + switch (q_EVP_PKEY_type(q_EVP_PKEY_base_id(pkey))) { + case EVP_PKEY_RSA: + keyIsNull = false; + keyAlgorithm = QSsl::Rsa; + keyType = QSsl::PrivateKey; + rsa = q_EVP_PKEY_get1_RSA(pkey); + + return true; + case EVP_PKEY_DSA: + keyIsNull = false; + keyAlgorithm = QSsl::Dsa; + keyType = QSsl::PrivateKey; + dsa = q_EVP_PKEY_get1_DSA(pkey); + + return true; + case EVP_PKEY_DH: + keyIsNull = false; + keyAlgorithm = QSsl::Dh; + keyType = QSsl::PrivateKey; + dh = q_EVP_PKEY_get1_DH(pkey); + return true; +#ifndef OPENSSL_NO_EC + case EVP_PKEY_EC: + keyIsNull = false; + keyAlgorithm = QSsl::Ec; + keyType = QSsl::PrivateKey; + ec = q_EVP_PKEY_get1_EC_KEY(pkey); + + return true; +#endif + default:; + // Unknown key type. This could be handled as opaque, but then + // we'd eventually leak memory since we wouldn't be able to free + // the underlying EVP_PKEY structure. For now, we won't support + // this. + } + + return false; +} + +QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv, bool enc) +{ + const EVP_CIPHER *type = nullptr; + int i = 0, len = 0; + + switch (cipher) { + case Cipher::DesCbc: +#ifndef OPENSSL_NO_DES + type = q_EVP_des_cbc(); +#endif + break; + case Cipher::DesEde3Cbc: +#ifndef OPENSSL_NO_DES + type = q_EVP_des_ede3_cbc(); +#endif + break; + case Cipher::Rc2Cbc: +#ifndef OPENSSL_NO_RC2 + type = q_EVP_rc2_cbc(); +#endif + break; + case Cipher::Aes128Cbc: + type = q_EVP_aes_128_cbc(); + break; + case Cipher::Aes192Cbc: + type = q_EVP_aes_192_cbc(); + break; + case Cipher::Aes256Cbc: + type = q_EVP_aes_256_cbc(); + break; + } + + if (type == nullptr) + return {}; + + QByteArray output; + output.resize(data.size() + EVP_MAX_BLOCK_LENGTH); + + EVP_CIPHER_CTX *ctx = q_EVP_CIPHER_CTX_new(); + q_EVP_CIPHER_CTX_reset(ctx); + q_EVP_CipherInit(ctx, type, nullptr, nullptr, enc); + q_EVP_CIPHER_CTX_set_key_length(ctx, key.size()); + if (cipher == Cipher::Rc2Cbc) + q_EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, 8 * key.size(), nullptr); + + q_EVP_CipherInit_ex(ctx, nullptr, nullptr, + reinterpret_cast(key.constData()), + reinterpret_cast(iv.constData()), + enc); + q_EVP_CipherUpdate(ctx, + reinterpret_cast(output.data()), &len, + reinterpret_cast(data.constData()), data.size()); + q_EVP_CipherFinal(ctx, + reinterpret_cast(output.data()) + len, &i); + len += i; + + q_EVP_CIPHER_CTX_reset(ctx); + q_EVP_CIPHER_CTX_free(ctx); + + return output.left(len); +} + +QByteArray TlsKeyOpenSSL::decrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const +{ + return doCrypt(cipher, data, key, iv, false); +} + +QByteArray TlsKeyOpenSSL::encrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const +{ + return doCrypt(cipher, data, key, iv, true); +} + +TlsKeyOpenSSL *TlsKeyOpenSSL::publicKeyFromX509(X509 *x) +{ + TlsKeyOpenSSL *tlsKey = new TlsKeyOpenSSL; + std::unique_ptr keyRaii(tlsKey); + + tlsKey->keyType = QSsl::PublicKey; + + EVP_PKEY *pkey = q_X509_get_pubkey(x); + Q_ASSERT(pkey); + const int keyType = q_EVP_PKEY_type(q_EVP_PKEY_base_id(pkey)); + + if (keyType == EVP_PKEY_RSA) { + tlsKey->rsa = q_EVP_PKEY_get1_RSA(pkey); + tlsKey->keyAlgorithm = QSsl::Rsa; + tlsKey->keyIsNull = false; + } else if (keyType == EVP_PKEY_DSA) { + tlsKey->dsa = q_EVP_PKEY_get1_DSA(pkey); + tlsKey->keyAlgorithm = QSsl::Dsa; + tlsKey->keyIsNull = false; +#ifndef OPENSSL_NO_EC + } else if (keyType == EVP_PKEY_EC) { + tlsKey->ec = q_EVP_PKEY_get1_EC_KEY(pkey); + tlsKey->keyAlgorithm = QSsl::Ec; + tlsKey->keyIsNull = false; +#endif + } else if (keyType == EVP_PKEY_DH) { + // DH unsupported (key is null) + } else { + // error? (key is null) + } + + q_EVP_PKEY_free(pkey); + return keyRaii.release(); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qtlskey_openssl_p.h b/src/plugins/tls/openssl/qtlskey_openssl_p.h new file mode 100644 index 0000000000..815770112f --- /dev/null +++ b/src/plugins/tls/openssl/qtlskey_openssl_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLSKEY_OPENSSL_H +#define QTLSKEY_OPENSSL_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "../shared/qtlskey_base_p.h" + +#include +#include + +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QT_REQUIRE_CONFIG(ssl); + +namespace QTlsPrivate { + +class TlsKeyOpenSSL final : public TlsKeyBase +{ +public: + TlsKeyOpenSSL() + : opaque(nullptr) + { + clear(false); + } + ~TlsKeyOpenSSL() + { + clear(true); + } + + void decodeDer(KeyType type, KeyAlgorithm algorithm, const QByteArray &der, + const QByteArray &passPhrase, bool deepClear) override; + void decodePem(KeyType type, KeyAlgorithm algorithm, const QByteArray &pem, + const QByteArray &passPhrase, bool deepClear) override; + + QByteArray toPem(const QByteArray &passPhrase) const override; + QByteArray derFromPem(const QByteArray &pem, QMap *headers) const override; + + void fromHandle(Qt::HANDLE opaque, KeyType expectedType) override; + + void clear(bool deep) override; + Qt::HANDLE handle() const override; + int length() const override; + + QByteArray decrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const override; + QByteArray encrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const override; + + static TlsKeyOpenSSL *publicKeyFromX509(X509 *x); + + union { + EVP_PKEY *opaque; + RSA *rsa; + DSA *dsa; + DH *dh; +#ifndef OPENSSL_NO_EC + EC_KEY *ec; +#endif + }; + + bool fromEVP_PKEY(EVP_PKEY *pkey); +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLSKEY_OPENSSL_H diff --git a/src/plugins/tls/openssl/qwindowscarootfetcher.cpp b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp new file mode 100644 index 0000000000..45ecbce258 --- /dev/null +++ b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp @@ -0,0 +1,283 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qwindowscarootfetcher_p.h" +#include "qx509_openssl_p.h" +#include "qopenssl_p.h" + +#include +#include + +#include + +#ifdef QSSLSOCKET_DEBUG +#include // for debug categories +#include +#endif + +QT_BEGIN_NAMESPACE + +class QWindowsCaRootFetcherThread : public QThread +{ +public: + QWindowsCaRootFetcherThread() + { + qRegisterMetaType(); + setObjectName(QStringLiteral("QWindowsCaRootFetcher")); + start(); + } + ~QWindowsCaRootFetcherThread() + { + quit(); + wait(15500); // worst case, a running request can block for 15 seconds + } +}; + +Q_GLOBAL_STATIC(QWindowsCaRootFetcherThread, windowsCaRootFetcherThread); + +namespace { + +const QList buildVerifiedChain(const QList &caCertificates, + PCCERT_CHAIN_CONTEXT chainContext, + const QString &peerVerifyName) +{ + // We ended up here because OpenSSL verification failed to + // build a chain, with intermediate certificate missing + // but "Authority Information Access" extension present. + // Also, apparently the normal CA fetching path was disabled + // by setting custom CA certificates. We convert wincrypt's + // structures in QSslCertificate and give OpenSSL the second + // chance to verify the now (apparently) complete chain. + // In addition, wincrypt gives us a benifit of some checks + // we don't have in OpenSSL back-end. + Q_ASSERT(chainContext); + + if (!chainContext->cChain) + return {}; + + QList verifiedChain; + + CERT_SIMPLE_CHAIN *chain = chainContext->rgpChain[chainContext->cChain - 1]; + if (!chain) + return {}; + + if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN) + return {}; // No need to mess with OpenSSL (the chain is still incomplete). + + for (DWORD i = 0; i < chain->cElement; ++i) { + CERT_CHAIN_ELEMENT *element = chain->rgpElement[i]; + QSslCertificate cert(QByteArray(reinterpret_cast(element->pCertContext->pbCertEncoded), + int(element->pCertContext->cbCertEncoded)), QSsl::Der); + + if (cert.isBlacklisted()) + return {}; + + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) // Good to know! + return {}; + + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID) + return {}; + + verifiedChain.append(cert); + } + + // We rely on OpenSSL's ability to find other problems. + const auto tlsErrors = QTlsPrivate::X509CertificateOpenSSL::verify(caCertificates, verifiedChain, peerVerifyName); + if (tlsErrors.size()) + verifiedChain.clear(); + + return verifiedChain; +} + +} // unnamed namespace + +QWindowsCaRootFetcher::QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode, + const QList &caCertificates, const QString &hostName) + : cert(certificate), mode(sslMode), explicitlyTrustedCAs(caCertificates), peerVerifyName(hostName) +{ + moveToThread(windowsCaRootFetcherThread()); +} + +QWindowsCaRootFetcher::~QWindowsCaRootFetcher() +{ +} + +void QWindowsCaRootFetcher::start() +{ + QByteArray der = cert.toDer(); + PCCERT_CONTEXT wincert = CertCreateCertificateContext(X509_ASN_ENCODING, (const BYTE *)der.constData(), der.length()); + if (!wincert) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, "QWindowsCaRootFetcher failed to convert certificate to windows form"); +#endif + emit finished(cert, QSslCertificate()); + deleteLater(); + return; + } + + CERT_CHAIN_PARA parameters; + memset(¶meters, 0, sizeof(parameters)); + parameters.cbSize = sizeof(parameters); + // set key usage constraint + parameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; + parameters.RequestedUsage.Usage.cUsageIdentifier = 1; + LPSTR oid = (LPSTR)(mode == QSslSocket::SslClientMode ? szOID_PKIX_KP_SERVER_AUTH : szOID_PKIX_KP_CLIENT_AUTH); + parameters.RequestedUsage.Usage.rgpszUsageIdentifier = &oid; + +#ifdef QSSLSOCKET_DEBUG + QElapsedTimer stopwatch; + stopwatch.start(); +#endif + PCCERT_CHAIN_CONTEXT chain; + auto additionalStore = createAdditionalStore(); + BOOL result = CertGetCertificateChain( + nullptr, //default engine + wincert, + nullptr, //current date/time + additionalStore.get(), //default store (nullptr) or CAs an application trusts + ¶meters, + 0, //default dwFlags + nullptr, //reserved + &chain); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl) << "QWindowsCaRootFetcher" << stopwatch.elapsed() << "ms to get chain"; +#endif + + QSslCertificate trustedRoot; + if (result) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcSsl) << "QWindowsCaRootFetcher - examining windows chains"; + if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR) + qCDebug(lcSsl) << " - TRUSTED"; + else + qCDebug(lcSsl) << " - NOT TRUSTED" << chain->TrustStatus.dwErrorStatus; + if (chain->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) + qCDebug(lcSsl) << " - SELF SIGNED"; + qCDebug(lcSsl) << "QWindowsCaRootFetcher - dumping simple chains"; + for (unsigned int i = 0; i < chain->cChain; i++) { + if (chain->rgpChain[i]->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR) + qCDebug(lcSsl) << " - TRUSTED SIMPLE CHAIN" << i; + else + qCDebug(lcSsl) << " - UNTRUSTED SIMPLE CHAIN" << i << "reason:" << chain->rgpChain[i]->TrustStatus.dwErrorStatus; + for (unsigned int j = 0; j < chain->rgpChain[i]->cElement; j++) { + QSslCertificate foundCert(QByteArray((const char *)chain->rgpChain[i]->rgpElement[j]->pCertContext->pbCertEncoded + , chain->rgpChain[i]->rgpElement[j]->pCertContext->cbCertEncoded), QSsl::Der); + qCDebug(lcSsl) << " - " << foundCert; + } + } + qCDebug(lcSsl) << " - and" << chain->cLowerQualityChainContext << "low quality chains"; //expect 0, we haven't asked for them +#endif + + //based on http://msdn.microsoft.com/en-us/library/windows/desktop/aa377182%28v=vs.85%29.aspx + //about the final chain rgpChain[cChain-1] which must begin with a trusted root to be valid + if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR + && chain->cChain > 0) { + const PCERT_SIMPLE_CHAIN finalChain = chain->rgpChain[chain->cChain - 1]; + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa377544%28v=vs.85%29.aspx + // rgpElement[0] is the end certificate chain element. rgpElement[cElement-1] is the self-signed "root" certificate element. + if (finalChain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR + && finalChain->cElement > 0) { + trustedRoot = QSslCertificate(QByteArray((const char *)finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->pbCertEncoded + , finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->cbCertEncoded), QSsl::Der); + } + } else if (explicitlyTrustedCAs.size()) { + // Setting custom CA in configuration, and those CAs are not trusted by MS. +#if QT_CONFIG(openssl) + const auto verifiedChain = buildVerifiedChain(explicitlyTrustedCAs, chain, peerVerifyName); + if (verifiedChain.size()) + trustedRoot = verifiedChain.last(); +#else + //It's only OpenSSL code-path that can trigger such a fetch. + Q_UNREACHABLE(); +#endif + } + CertFreeCertificateChain(chain); + } + CertFreeCertificateContext(wincert); + + emit finished(cert, trustedRoot); + deleteLater(); +} + +QHCertStorePointer QWindowsCaRootFetcher::createAdditionalStore() const +{ + QHCertStorePointer customStore; + if (explicitlyTrustedCAs.isEmpty()) + return customStore; + + if (HCERTSTORE rawPtr = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr)) { + customStore.reset(rawPtr); + + unsigned rootsAdded = 0; + for (const QSslCertificate &caCert : explicitlyTrustedCAs) { + const auto der = caCert.toDer(); + PCCERT_CONTEXT winCert = CertCreateCertificateContext(X509_ASN_ENCODING, + reinterpret_cast(der.data()), + DWORD(der.length())); + if (!winCert) { +#if defined(QSSLSOCKET_DEBUG) + qCWarning(lcSsl) << "CA fetcher, failed to convert QSslCertificate" + << "to the native representation"; +#endif // QSSLSOCKET_DEBUG + continue; + } + const auto deleter = qScopeGuard([winCert](){ + CertFreeCertificateContext(winCert); + }); + if (CertAddCertificateContextToStore(customStore.get(), winCert, CERT_STORE_ADD_ALWAYS, nullptr)) + ++rootsAdded; +#if defined(QSSLSOCKET_DEBUG) + else //Why assert? With flags we're using and winCert check - should not happen! + Q_ASSERT("CertAddCertificateContextToStore() failed"); +#endif // QSSLSOCKET_DEBUG + } + if (!rootsAdded) //Useless store, no cert was added. + customStore.reset(); +#if defined(QSSLSOCKET_DEBUG) + } else { + + qCWarning(lcSsl) << "CA fetcher, failed to create a custom" + << "store for explicitly trusted CA certificate"; +#endif // QSSLSOCKET_DEBUG + } + + return customStore; +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qwindowscarootfetcher_p.h b/src/plugins/tls/openssl/qwindowscarootfetcher_p.h new file mode 100644 index 0000000000..ed649b0936 --- /dev/null +++ b/src/plugins/tls/openssl/qwindowscarootfetcher_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QWINDOWSCAROOTFETCHER_P_H +#define QWINDOWSCAROOTFETCHER_P_H + +#include + +#include +#include + +#include +#include + +#include "../shared/qwincrypt_p.h" + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +class QWindowsCaRootFetcher : public QObject +{ + Q_OBJECT +public: + QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode, + const QList &caCertificates = {}, + const QString &hostName = {}); + ~QWindowsCaRootFetcher(); +public slots: + void start(); +signals: + void finished(QSslCertificate brokenChain, QSslCertificate caroot); +private: + QHCertStorePointer createAdditionalStore() const; + + QSslCertificate cert; + QSslSocket::SslMode mode; + // In case the application set CA certificates in the configuration, + // in the past we did not load missing certs. But this disables + // recoverable case when a certificate has Authority Information Access + // extension. So we try to fetch in this scenario also, but in case + // explicitly trusted root was not in a system store, we'll do + // additional checks, thus we need 'peerVerifyName': + QList explicitlyTrustedCAs; + QString peerVerifyName; +}; + +QT_END_NAMESPACE + +#endif // QWINDOWSCAROOTFETCHER_P_H diff --git a/src/plugins/tls/openssl/qx509_openssl.cpp b/src/plugins/tls/openssl/qx509_openssl.cpp new file mode 100644 index 0000000000..bf52c9345c --- /dev/null +++ b/src/plugins/tls/openssl/qx509_openssl.cpp @@ -0,0 +1,939 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qsslsocket_openssl_symbols_p.h" +#include "qtlsbackend_openssl_p.h" +#include "qtlskey_openssl_p.h" +#include "qx509_openssl_p.h" +#include "qtls_openssl_p.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +namespace { + +QByteArray asn1ObjectId(ASN1_OBJECT *object) +{ + if (!object) + return {}; + + char buf[80] = {}; // The openssl docs a buffer length of 80 should be more than enough + q_OBJ_obj2txt(buf, sizeof(buf), object, 1); // the 1 says always use the oid not the long name + + return QByteArray(buf); +} + +QByteArray asn1ObjectName(ASN1_OBJECT *object) +{ + if (!object) + return {}; + + const int nid = q_OBJ_obj2nid(object); + if (nid != NID_undef) + return QByteArray(q_OBJ_nid2sn(nid)); + + return asn1ObjectId(object); +} + +QMultiMap mapFromX509Name(X509_NAME *name) +{ + if (!name) + return {}; + + QMultiMap info; + for (int i = 0; i < q_X509_NAME_entry_count(name); ++i) { + X509_NAME_ENTRY *e = q_X509_NAME_get_entry(name, i); + + QByteArray name = asn1ObjectName(q_X509_NAME_ENTRY_get_object(e)); + unsigned char *data = nullptr; + int size = q_ASN1_STRING_to_UTF8(&data, q_X509_NAME_ENTRY_get_data(e)); + info.insert(name, QString::fromUtf8((char*)data, size)); + q_CRYPTO_free(data, nullptr, 0); + } + + return info; +} + +QDateTime dateTimeFromASN1(const ASN1_TIME *aTime) +{ + QDateTime result; + tm lTime; + + if (q_ASN1_TIME_to_tm(aTime, &lTime)) { + QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday); + QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec); + result = QDateTime(resDate, resTime, Qt::UTC); + } + + return result; +} + + +#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" +#define ENDCERTSTRING "-----END CERTIFICATE-----" + +QByteArray x509ToQByteArray(X509 *x509, QSsl::EncodingFormat format) +{ + Q_ASSERT(x509); + + // Use i2d_X509 to convert the X509 to an array. + const int length = q_i2d_X509(x509, nullptr); + if (length <= 0) { + QTlsBackendOpenSSL::logAndClearErrorQueue(); + return {}; + } + + QByteArray array; + array.resize(length); + + char *data = array.data(); + char **dataP = &data; + unsigned char **dataPu = (unsigned char **)dataP; + if (q_i2d_X509(x509, dataPu) < 0) + return QByteArray(); + + if (format == QSsl::Der) + return array; + + // Convert to Base64 - wrap at 64 characters. + array = array.toBase64(); + QByteArray tmp; + for (int i = 0; i <= array.size() - 64; i += 64) { + tmp += QByteArray::fromRawData(array.data() + i, 64); + tmp += '\n'; + } + if (int remainder = array.size() % 64) { + tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder); + tmp += '\n'; + } + + return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n"; +} + +QString x509ToText(X509 *x509) +{ + Q_ASSERT(x509); + + QByteArray result; + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) + return QString(); + const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); + + q_X509_print(bio, x509); + + QVarLengthArray data; + int count = q_BIO_read(bio, data.data(), 16384); + if ( count > 0 ) + result = QByteArray( data.data(), count ); + + return QString::fromLatin1(result); +} + +QVariant x509UnknownExtensionToValue(X509_EXTENSION *ext) +{ + // Get the extension specific method object if available + // we cast away the const-ness here because some versions of openssl + // don't use const for the parameters in the functions pointers stored + // in the object. + Q_ASSERT(ext); + + X509V3_EXT_METHOD *meth = const_cast(q_X509V3_EXT_get(ext)); + if (!meth) { + ASN1_OCTET_STRING *value = q_X509_EXTENSION_get_data(ext); + Q_ASSERT(value); + QByteArray result( reinterpret_cast(q_ASN1_STRING_get0_data(value)), + q_ASN1_STRING_length(value)); + return result; + } + + void *ext_internal = q_X509V3_EXT_d2i(ext); + + // If this extension can be converted + if (meth->i2v && ext_internal) { + STACK_OF(CONF_VALUE) *val = meth->i2v(meth, ext_internal, nullptr); + + QVariantMap map; + QVariantList list; + bool isMap = false; + + for (int j = 0; j < q_SKM_sk_num(val); j++) { + CONF_VALUE *nval = q_SKM_sk_value(CONF_VALUE, val, j); + if (nval->name && nval->value) { + isMap = true; + map[QString::fromUtf8(nval->name)] = QString::fromUtf8(nval->value); + } else if (nval->name) { + list << QString::fromUtf8(nval->name); + } else if (nval->value) { + list << QString::fromUtf8(nval->value); + } + } + + if (isMap) + return map; + else + return list; + } else if (meth->i2s && ext_internal) { + QVariant result(QString::fromUtf8(meth->i2s(meth, ext_internal))); + return result; + } else if (meth->i2r && ext_internal) { + QByteArray result; + + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) + return result; + + meth->i2r(meth, ext_internal, bio, 0); + + char *bio_buffer; + long bio_size = q_BIO_get_mem_data(bio, &bio_buffer); + result = QByteArray(bio_buffer, bio_size); + + q_BIO_free(bio); + return result; + } + + return QVariant(); +} + +/* + * Convert extensions to a variant. The naming of the keys of the map are + * taken from RFC 5280, however we decided the capitalisation in the RFC + * was too silly for the real world. + */ +QVariant x509ExtensionToValue(X509_EXTENSION *ext) +{ + ASN1_OBJECT *obj = q_X509_EXTENSION_get_object(ext); + int nid = q_OBJ_obj2nid(obj); + switch (nid) { + case NID_basic_constraints: + { + BASIC_CONSTRAINTS *basic = reinterpret_cast(q_X509V3_EXT_d2i(ext)); + if (!basic) + return {}; + QVariantMap result; + result[QLatin1String("ca")] = basic->ca ? true : false; + if (basic->pathlen) + result[QLatin1String("pathLenConstraint")] = (qlonglong)q_ASN1_INTEGER_get(basic->pathlen); + + q_BASIC_CONSTRAINTS_free(basic); + return result; + } + break; + case NID_info_access: + { + AUTHORITY_INFO_ACCESS *info = reinterpret_cast(q_X509V3_EXT_d2i(ext)); + if (!info) + return {}; + QVariantMap result; + for (int i=0; i < q_SKM_sk_num(info); i++) { + ACCESS_DESCRIPTION *ad = q_SKM_sk_value(ACCESS_DESCRIPTION, info, i); + + GENERAL_NAME *name = ad->location; + if (name->type == GEN_URI) { + int len = q_ASN1_STRING_length(name->d.uniformResourceIdentifier); + if (len < 0 || len >= 8192) { + // broken name + continue; + } + + const char *uriStr = reinterpret_cast(q_ASN1_STRING_get0_data(name->d.uniformResourceIdentifier)); + const QString uri = QString::fromUtf8(uriStr, len); + + result[QString::fromUtf8(asn1ObjectName(ad->method))] = uri; + } else { + qCWarning(lcTlsBackend) << "Strange location type" << name->type; + } + } + + q_OPENSSL_sk_pop_free((OPENSSL_STACK*)info, reinterpret_cast(q_OPENSSL_sk_free)); + return result; + } + break; + case NID_subject_key_identifier: + { + void *ext_internal = q_X509V3_EXT_d2i(ext); + if (!ext_internal) + return {}; + // we cast away the const-ness here because some versions of openssl + // don't use const for the parameters in the functions pointers stored + // in the object. + X509V3_EXT_METHOD *meth = const_cast(q_X509V3_EXT_get(ext)); + + return QVariant(QString::fromUtf8(meth->i2s(meth, ext_internal))); + } + break; + case NID_authority_key_identifier: + { + AUTHORITY_KEYID *auth_key = reinterpret_cast(q_X509V3_EXT_d2i(ext)); + if (!auth_key) + return {}; + QVariantMap result; + + // keyid + if (auth_key->keyid) { + QByteArray keyid(reinterpret_cast(auth_key->keyid->data), + auth_key->keyid->length); + result[QLatin1String("keyid")] = keyid.toHex(); + } + + // issuer + // TODO: GENERAL_NAMES + + // serial + if (auth_key->serial) + result[QLatin1String("serial")] = (qlonglong)q_ASN1_INTEGER_get(auth_key->serial); + + q_AUTHORITY_KEYID_free(auth_key); + return result; + } + break; + } + + return {}; +} + +} // Unnamed namespace + +extern "C" int qt_X509Callback(int ok, X509_STORE_CTX *ctx) +{ + if (!ok) { + // Store the error and at which depth the error was detected. + using ErrorListPtr = QList *; + ErrorListPtr errors = nullptr; + + // Error list is attached to either 'SSL' or 'X509_STORE'. + if (X509_STORE *store = q_X509_STORE_CTX_get0_store(ctx)) // We try store first: + errors = ErrorListPtr(q_X509_STORE_get_ex_data(store, 0)); + + if (!errors) { + // Not found on store? Try SSL and its external data then. According to the OpenSSL's + // documentation: + // + // "Whenever a X509_STORE_CTX object is created for the verification of the + // peer's certificate during a handshake, a pointer to the SSL object is + // stored into the X509_STORE_CTX object to identify the connection affected. + // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be + // used with the correct index." + + // TLSTODO: verification callback has to change as soon as TlsCryptographer is in place. + // This is a temporary solution for now to ease the transition. + const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + + TlsCryptographOpenSSL::errorOffsetInExData; + if (SSL *ssl = static_cast(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx()))) + errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset)); + } + + if (!errors) { + qCWarning(lcTlsBackend, "Neither X509_STORE, nor SSL contains error list, verification failed"); + return 0; + } + + errors->append(X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); + } + // Always return OK to allow verification to continue. We handle the + // errors gracefully after collecting all errors, after verification has + // completed. + return 1; +} + +X509CertificateOpenSSL::X509CertificateOpenSSL() = default; + +X509CertificateOpenSSL::~X509CertificateOpenSSL() +{ + if (x509) + q_X509_free(x509); +} + +bool X509CertificateOpenSSL::isEqual(const X509Certificate &rhs) const +{ + //TLSTODO: to make it safe I'll check the backend type later. + const auto &other = static_cast(rhs); + if (x509 && other.x509) { + const int ret = q_X509_cmp(x509, other.x509); + if (ret >= -1 && ret <= 1) + return ret == 0; + QTlsBackendOpenSSL::logAndClearErrorQueue(); + } + + return false; +} + +bool X509CertificateOpenSSL::isSelfSigned() const +{ + if (!x509) + return false; + + return q_X509_check_issued(x509, x509) == X509_V_OK; +} + +QMultiMap +X509CertificateOpenSSL::subjectAlternativeNames() const +{ + QMultiMap result; + + if (!x509) + return result; + + auto *altNames = static_cast(q_X509_get_ext_d2i(x509, NID_subject_alt_name, + nullptr, nullptr)); + if (!altNames) + return result; + + auto altName = [](ASN1_IA5STRING *ia5, int len) { + const char *altNameStr = reinterpret_cast(q_ASN1_STRING_get0_data(ia5)); + return QString::fromLatin1(altNameStr, len); + }; + + for (int i = 0; i < q_sk_GENERAL_NAME_num(altNames); ++i) { + const GENERAL_NAME *genName = q_sk_GENERAL_NAME_value(altNames, i); + if (genName->type != GEN_DNS && genName->type != GEN_EMAIL && genName->type != GEN_IPADD) + continue; + + const int len = q_ASN1_STRING_length(genName->d.ia5); + if (len < 0 || len >= 8192) { + // broken name + continue; + } + + switch (genName->type) { + case GEN_DNS: + result.insert(QSsl::DnsEntry, altName(genName->d.ia5, len)); + break; + case GEN_EMAIL: + result.insert(QSsl::EmailEntry, altName(genName->d.ia5, len)); + break; + case GEN_IPADD: { + QHostAddress ipAddress; + switch (len) { + case 4: // IPv4 + ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast(genName->d.iPAddress->data))); + break; + case 16: // IPv6 + ipAddress = QHostAddress(reinterpret_cast(genName->d.iPAddress->data)); + break; + default: // Unknown IP address format + break; + } + if (!ipAddress.isNull()) + result.insert(QSsl::IpAddressEntry, ipAddress.toString()); + break; + } + default: + break; + } + } + + q_OPENSSL_sk_pop_free((OPENSSL_STACK*)altNames, reinterpret_cast(q_GENERAL_NAME_free)); + + return result; +} + +TlsKey *X509CertificateOpenSSL::publicKey() const +{ + if (!x509) + return {}; + + return TlsKeyOpenSSL::publicKeyFromX509(x509); +} + +QByteArray X509CertificateOpenSSL::toPem() const +{ + if (!x509) + return {}; + + return x509ToQByteArray(x509, QSsl::Pem); +} + +QByteArray X509CertificateOpenSSL::toDer() const +{ + if (!x509) + return {}; + + return x509ToQByteArray(x509, QSsl::Der); + +} +QString X509CertificateOpenSSL::toText() const +{ + if (!x509) + return {}; + + return x509ToText(x509); +} + +Qt::HANDLE X509CertificateOpenSSL::handle() const +{ + return Qt::HANDLE(x509); +} + +size_t X509CertificateOpenSSL::hash(size_t seed) const noexcept +{ + if (x509) { + const EVP_MD *sha1 = q_EVP_sha1(); + unsigned int len = 0; + unsigned char md[EVP_MAX_MD_SIZE]; + q_X509_digest(x509, sha1, md, &len); + return qHashBits(md, len, seed); + } + + return seed; +} + +QSslCertificate X509CertificateOpenSSL::certificateFromX509(X509 *x509) +{ + QSslCertificate certificate; + + auto *backend = QTlsBackend::backend(certificate); + if (!backend || !x509) + return certificate; + + ASN1_TIME *nbef = q_X509_getm_notBefore(x509); + if (nbef) + backend->notValidBefore = dateTimeFromASN1(nbef); + + ASN1_TIME *naft = q_X509_getm_notAfter(x509); + if (naft) + backend->notValidAfter = dateTimeFromASN1(naft); + + backend->null = false; + backend->x509 = q_X509_dup(x509); + + backend->issuerInfoEntries = mapFromX509Name(q_X509_get_issuer_name(x509)); + backend->subjectInfoEntries = mapFromX509Name(q_X509_get_subject_name(x509)); + backend->versionString = QByteArray::number(qlonglong(q_X509_get_version(x509)) + 1); + + if (ASN1_INTEGER *serialNumber = q_X509_get_serialNumber(x509)) { + QByteArray hexString; + hexString.reserve(serialNumber->length * 3); + for (int a = 0; a < serialNumber->length; ++a) { + hexString += QByteArray::number(serialNumber->data[a], 16).rightJustified(2, '0'); + hexString += ':'; + } + hexString.chop(1); + backend->serialNumberString = hexString; + } + + backend->parseExtensions(); + + return certificate; +} + +QList X509CertificateOpenSSL::stackOfX509ToQSslCertificates(STACK_OF(X509) *x509) +{ + if (!x509) + return {}; + + QList certificates; + for (int i = 0; i < q_sk_X509_num(x509); ++i) { + if (X509 *entry = q_sk_X509_value(x509, i)) + certificates << certificateFromX509(entry); + } + + return certificates; +} + +QSslErrorEntry X509CertificateOpenSSL::errorEntryFromStoreContext(X509_STORE_CTX *ctx) +{ + Q_ASSERT(ctx); + + return {q_X509_STORE_CTX_get_error(ctx), q_X509_STORE_CTX_get_error_depth(ctx)}; +} + +QList X509CertificateOpenSSL::verify(const QList &chain, + const QString &hostName) +{ + // This was previously QSslSocketPrivate::verify(). + auto roots = QSslConfiguration::defaultConfiguration().caCertificates(); +#ifndef Q_OS_WIN + // On Windows, system CA certificates are already set as default ones. + // No need to add them again (and again) and also, if the default configuration + // has its own set of CAs, this probably should not be amended by the ones + // from the 'ROOT' store, since it's not what an application chose to trust. + if (QSslSocketPrivate::rootCertOnDemandLoadingSupported()) + roots.append(QSslSocketPrivate::systemCaCertificates()); +#endif // Q_OS_WIN + return verify(roots, chain, hostName); +} + +QList X509CertificateOpenSSL::verify(const QList &caCertificates, + const QList &certificateChain, + const QString &hostName) +{ + // This was previously QSslSocketPrivate::verify(). + if (certificateChain.count() <= 0) + return {QSslError(QSslError::UnspecifiedError)}; + + QList errors; + X509_STORE *certStore = q_X509_STORE_new(); + if (!certStore) { + qCWarning(lcTlsBackend) << "Unable to create certificate store"; + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + const std::unique_ptr storeGuard(certStore, q_X509_STORE_free); + + const QDateTime now = QDateTime::currentDateTimeUtc(); + for (const QSslCertificate &caCertificate : caCertificates) { + // From https://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html: + // + // If several CA certificates matching the name, key identifier, and + // serial number condition are available, only the first one will be + // examined. This may lead to unexpected results if the same CA + // certificate is available with different expiration dates. If a + // ``certificate expired'' verification error occurs, no other + // certificate will be searched. Make sure to not have expired + // certificates mixed with valid ones. + // + // See also: QSslContext::fromConfiguration() + if (caCertificate.expiryDate() >= now) { + q_X509_STORE_add_cert(certStore, reinterpret_cast(caCertificate.handle())); + } + } + + QList lastErrors; + if (!q_X509_STORE_set_ex_data(certStore, 0, &lastErrors)) { + qCWarning(lcTlsBackend) << "Unable to attach external data (error list) to a store"; + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + // Register a custom callback to get all verification errors. + q_X509_STORE_set_verify_cb(certStore, qt_X509Callback); + + // Build the chain of intermediate certificates + STACK_OF(X509) *intermediates = nullptr; + if (certificateChain.length() > 1) { + intermediates = (STACK_OF(X509) *) q_OPENSSL_sk_new_null(); + + if (!intermediates) { + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + bool first = true; + for (const QSslCertificate &cert : certificateChain) { + if (first) { + first = false; + continue; + } + + q_OPENSSL_sk_push((OPENSSL_STACK *)intermediates, reinterpret_cast(cert.handle())); + } + } + + X509_STORE_CTX *storeContext = q_X509_STORE_CTX_new(); + if (!storeContext) { + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + std::unique_ptr ctxGuard(storeContext, q_X509_STORE_CTX_free); + + if (!q_X509_STORE_CTX_init(storeContext, certStore, reinterpret_cast(certificateChain[0].handle()), intermediates)) { + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + // Now we can actually perform the verification of the chain we have built. + // We ignore the result of this function since we process errors via the + // callback. + (void) q_X509_verify_cert(storeContext); + ctxGuard.reset(); + q_OPENSSL_sk_free((OPENSSL_STACK *)intermediates); + + // Now process the errors + + if (certificateChain[0].isBlacklisted()) + errors << QSslError(QSslError::CertificateBlacklisted, certificateChain[0]); + + // Check the certificate name against the hostname if one was specified + if (!hostName.isEmpty() && !TlsCryptograph::isMatchingHostname(certificateChain[0], hostName)) { + // No matches in common names or alternate names. + QSslError error(QSslError::HostNameMismatch, certificateChain[0]); + errors << error; + } + + // Translate errors from the error list into QSslErrors. + errors.reserve(errors.size() + lastErrors.size()); + for (const auto &error : qAsConst(lastErrors)) + errors << openSSLErrorToQSslError(error.code, certificateChain.value(error.depth)); + + return errors; +} + +QList X509CertificateOpenSSL::certificatesFromPem(const QByteArray &pem, int count) +{ + QList certificates; + + int offset = 0; + while (count == -1 || certificates.size() < count) { + int startPos = pem.indexOf(BEGINCERTSTRING, offset); + if (startPos == -1) + break; + startPos += sizeof(BEGINCERTSTRING) - 1; + if (!matchLineFeed(pem, &startPos)) + break; + + int endPos = pem.indexOf(ENDCERTSTRING, startPos); + if (endPos == -1) + break; + + offset = endPos + sizeof(ENDCERTSTRING) - 1; + if (offset < pem.size() && !matchLineFeed(pem, &offset)) + break; + + QByteArray decoded = QByteArray::fromBase64( + QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); + const unsigned char *data = (const unsigned char *)decoded.data(); + + if (X509 *x509 = q_d2i_X509(nullptr, &data, decoded.size())) { + certificates << certificateFromX509(x509); + q_X509_free(x509); + } + } + + return certificates; +} + +QList X509CertificateOpenSSL::certificatesFromDer(const QByteArray &der, int count) +{ + QList certificates; + + const unsigned char *data = (const unsigned char *)der.data(); + int size = der.size(); + + while (size > 0 && (count == -1 || certificates.size() < count)) { + if (X509 *x509 = q_d2i_X509(nullptr, &data, size)) { + certificates << certificateFromX509(x509); + q_X509_free(x509); + } else { + break; + } + size -= ((const char *)data - der.data()); + } + + return certificates; +} + +bool X509CertificateOpenSSL::importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, + QList *caCertificates, + const QByteArray &passPhrase) +{ + // These are required + Q_ASSERT(device); + Q_ASSERT(key); + Q_ASSERT(cert); + + // Read the file into a BIO + QByteArray pkcs12data = device->readAll(); + if (pkcs12data.size() == 0) + return false; + + BIO *bio = q_BIO_new_mem_buf(const_cast(pkcs12data.constData()), pkcs12data.size()); + if (!bio) { + qCWarning(lcTlsBackend, "BIO_new_mem_buf returned null"); + return false; + } + const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); + + // Create the PKCS#12 object + PKCS12 *p12 = q_d2i_PKCS12_bio(bio, nullptr); + if (!p12) { + qCWarning(lcTlsBackend, "Unable to read PKCS#12 structure, %s", + q_ERR_error_string(q_ERR_get_error(), nullptr)); + return false; + } + const auto p12Raii = qScopeGuard([p12]{q_PKCS12_free(p12);}); + + // Extract the data + EVP_PKEY *pkey = nullptr; + X509 *x509 = nullptr; + STACK_OF(X509) *ca = nullptr; + + if (!q_PKCS12_parse(p12, passPhrase.constData(), &pkey, &x509, &ca)) { + qCWarning(lcTlsBackend, "Unable to parse PKCS#12 structure, %s", + q_ERR_error_string(q_ERR_get_error(), nullptr)); + return false; + } + + const auto x509Raii = qScopeGuard([x509]{q_X509_free(x509);}); + const auto keyRaii = qScopeGuard([pkey]{q_EVP_PKEY_free(pkey);}); + const auto caRaii = qScopeGuard([ca] { + q_OPENSSL_sk_pop_free(reinterpret_cast(ca), + reinterpret_cast(q_X509_free)); + }); + + // Convert to Qt types + auto *tlsKey = QTlsBackend::backend(*key); + if (!tlsKey || !tlsKey->fromEVP_PKEY(pkey)) { + qCWarning(lcTlsBackend, "Unable to convert private key"); + return false; + } + + *cert = certificateFromX509(x509); + + if (caCertificates) + *caCertificates = stackOfX509ToQSslCertificates(ca); + + return true; +} + +QSslError X509CertificateOpenSSL::openSSLErrorToQSslError(int errorCode, const QSslCertificate &cert) +{ + QSslError error; + switch (errorCode) { + case X509_V_OK: + // X509_V_OK is also reported if the peer had no certificate. + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + error = QSslError(QSslError::UnableToGetIssuerCertificate, cert); break; + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + error = QSslError(QSslError::UnableToDecryptCertificateSignature, cert); break; + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + error = QSslError(QSslError::UnableToDecodeIssuerPublicKey, cert); break; + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + error = QSslError(QSslError::CertificateSignatureFailed, cert); break; + case X509_V_ERR_CERT_NOT_YET_VALID: + error = QSslError(QSslError::CertificateNotYetValid, cert); break; + case X509_V_ERR_CERT_HAS_EXPIRED: + error = QSslError(QSslError::CertificateExpired, cert); break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + error = QSslError(QSslError::InvalidNotBeforeField, cert); break; + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + error = QSslError(QSslError::InvalidNotAfterField, cert); break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + error = QSslError(QSslError::SelfSignedCertificate, cert); break; + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + error = QSslError(QSslError::SelfSignedCertificateInChain, cert); break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + error = QSslError(QSslError::UnableToGetLocalIssuerCertificate, cert); break; + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + error = QSslError(QSslError::UnableToVerifyFirstCertificate, cert); break; + case X509_V_ERR_CERT_REVOKED: + error = QSslError(QSslError::CertificateRevoked, cert); break; + case X509_V_ERR_INVALID_CA: + error = QSslError(QSslError::InvalidCaCertificate, cert); break; + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + error = QSslError(QSslError::PathLengthExceeded, cert); break; + case X509_V_ERR_INVALID_PURPOSE: + error = QSslError(QSslError::InvalidPurpose, cert); break; + case X509_V_ERR_CERT_UNTRUSTED: + error = QSslError(QSslError::CertificateUntrusted, cert); break; + case X509_V_ERR_CERT_REJECTED: + error = QSslError(QSslError::CertificateRejected, cert); break; + default: + error = QSslError(QSslError::UnspecifiedError, cert); break; + } + return error; +} + +void X509CertificateOpenSSL::parseExtensions() +{ + extensions.clear(); + + if (!x509) + return; + + int count = q_X509_get_ext_count(x509); + if (count <= 0) + return; + + extensions.reserve(count); + + for (int i = 0; i < count; i++) { + X509_EXTENSION *ext = q_X509_get_ext(x509, i); + if (!ext) { + qCWarning(lcTlsBackend) << "Invalid (nullptr) extension at index" << i; + continue; + } + + extensions << convertExtension(ext); + } + + // Converting an extension may result in an error(s), clean them up: + QTlsBackendOpenSSL::clearErrorQueue(); +} + +X509CertificateBase::X509CertificateExtension X509CertificateOpenSSL::convertExtension(X509_EXTENSION *ext) +{ + Q_ASSERT(ext); + + X509CertificateExtension result; + + ASN1_OBJECT *obj = q_X509_EXTENSION_get_object(ext); + if (!obj) + return result; + + result.oid = QString::fromUtf8(asn1ObjectId(obj)); + result.name = QString::fromUtf8(asn1ObjectName(obj)); + + result.critical = bool(q_X509_EXTENSION_get_critical(ext)); + + // Lets see if we have custom support for this one + QVariant extensionValue = x509ExtensionToValue(ext); + if (extensionValue.isValid()) { + result.value = extensionValue; + result.supported = true; + return result; + } + + extensionValue = x509UnknownExtensionToValue(ext); + if (extensionValue.isValid()) + result.value = extensionValue; + + result.supported = false; + + return result; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/openssl/qx509_openssl_p.h b/src/plugins/tls/openssl/qx509_openssl_p.h new file mode 100644 index 0000000000..19f2f7614e --- /dev/null +++ b/src/plugins/tls/openssl/qx509_openssl_p.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QX509_OPENSSL_P_H +#define QX509_OPENSSL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "../shared/qx509_base_p.h" + +#include + +#include +#include +#include + +#include "qopenssl_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class X509CertificateOpenSSL final : public X509CertificateBase +{ +public: + X509CertificateOpenSSL(); + ~X509CertificateOpenSSL(); + + // TLSTODO: in future may become movable/copyable (ref-counted based + // OpenSSL's X509 implementation). + + bool isEqual(const X509Certificate &rhs) const override; + bool isSelfSigned() const override; + QMultiMap subjectAlternativeNames() const override; + TlsKey *publicKey() const override; + + QByteArray toPem() const override; + QByteArray toDer() const override; + QString toText() const override; + Qt::HANDLE handle() const override; + + size_t hash(size_t seed) const noexcept override; + + static QSslCertificate certificateFromX509(X509 *x); + static QList stackOfX509ToQSslCertificates(STACK_OF(X509) *x509); + static QSslErrorEntry errorEntryFromStoreContext(X509_STORE_CTX *ctx); + + static QList verify(const QList &chain, const QString &hostName); + static QList verify(const QList &caCertificates, + const QList &certificateChain, + const QString &hostName); + + static QList certificatesFromPem(const QByteArray &pem, int count); + static QList certificatesFromDer(const QByteArray &der, int count); + static bool importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, + QList *caCertificates, + const QByteArray &passPhrase); + + static QSslError openSSLErrorToQSslError(int errorCode, const QSslCertificate &cert); +private: + void parseExtensions(); + static X509CertificateExtension convertExtension(X509_EXTENSION *ext); + + X509 *x509 = nullptr; + + Q_DISABLE_COPY_MOVE(X509CertificateOpenSSL) +}; + +extern "C" int qt_X509Callback(int ok, X509_STORE_CTX *ctx); + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QX509_OPENSSL_P_H diff --git a/src/plugins/tls/schannel/CMakeLists.txt b/src/plugins/tls/schannel/CMakeLists.txt new file mode 100644 index 0000000000..d3caca92df --- /dev/null +++ b/src/plugins/tls/schannel/CMakeLists.txt @@ -0,0 +1,30 @@ +qt_internal_add_plugin(QSchannelBackend + OUTPUT_NAME schannelbackend + CLASS_NAME QSchannelBackend + TYPE tls + DEFAULT_IF WINDOWS + SOURCES + ../shared/qtlskey_base_p.h + ../shared/qtlskey_base.cpp + ../shared/qtlskey_generic_p.h + ../shared/qtlskey_generic.cpp + ../shared/qx509_base_p.h + ../shared/qx509_base.cpp + ../shared/qx509_generic_p.h + ../shared/qx509_generic.cpp + ../shared/qsslsocket_qt.cpp + ../shared/qwincrypt_p.h + ../shared/qasn1element_p.h + ../shared/qasn1element.cpp + qtls_schannel.cpp qtls_schannel_p.h + qtlsbackend_schannel_p.h + qtlskey_schannel.cpp qtlskey_schannel_p.h + qx509_schannel.cpp qx509_schannel_p.h + LIBRARIES + Crypt32 + Secur32 + bcrypt + ncrypt + PUBLIC_LIBRARIES + Qt::NetworkPrivate +) diff --git a/src/plugins/tls/schannel/qtls_schannel.cpp b/src/plugins/tls/schannel/qtls_schannel.cpp new file mode 100644 index 0000000000..83e9cb1c6c --- /dev/null +++ b/src/plugins/tls/schannel/qtls_schannel.cpp @@ -0,0 +1,2314 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +// #define QSSLSOCKET_DEBUG + +#include "qtlsbackend_schannel_p.h" +#include "qtlskey_schannel_p.h" +#include "qx509_schannel_p.h" +#include "qtls_schannel_p.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#define SECURITY_WIN32 +#include +#include + +#if NTDDI_VERSION >= NTDDI_WINBLUE && !defined(Q_CC_MINGW) +// 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 +#endif +#ifndef SECPKG_ATTR_APPLICATION_PROTOCOL +#define SECPKG_ATTR_APPLICATION_PROTOCOL 35 +#endif + +// Another missing MinGW define +#ifndef SEC_E_APPLICATION_PROTOCOL_MISMATCH +#define SEC_E_APPLICATION_PROTOCOL_MISMATCH _HRESULT_TYPEDEF_(0x80090367L) +#endif + +// Also not defined in MinGW....... +#ifndef SP_PROT_TLS1_SERVER +#define SP_PROT_TLS1_SERVER 0x00000040 +#endif +#ifndef SP_PROT_TLS1_CLIENT +#define SP_PROT_TLS1_CLIENT 0x00000080 +#endif +#ifndef SP_PROT_TLS1_0_SERVER +#define SP_PROT_TLS1_0_SERVER SP_PROT_TLS1_SERVER +#endif +#ifndef SP_PROT_TLS1_0_CLIENT +#define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT +#endif +#ifndef SP_PROT_TLS1_0 +#define SP_PROT_TLS1_0 (SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_0_SERVER) +#endif +#ifndef SP_PROT_TLS1_1_SERVER +#define SP_PROT_TLS1_1_SERVER 0x00000100 +#endif +#ifndef SP_PROT_TLS1_1_CLIENT +#define SP_PROT_TLS1_1_CLIENT 0x00000200 +#endif +#ifndef SP_PROT_TLS1_1 +#define SP_PROT_TLS1_1 (SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_1_SERVER) +#endif +#ifndef SP_PROT_TLS1_2_SERVER +#define SP_PROT_TLS1_2_SERVER 0x00000400 +#endif +#ifndef SP_PROT_TLS1_2_CLIENT +#define SP_PROT_TLS1_2_CLIENT 0x00000800 +#endif +#ifndef SP_PROT_TLS1_2 +#define SP_PROT_TLS1_2 (SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_2_SERVER) +#endif +#ifndef SP_PROT_TLS1_3_SERVER +#define SP_PROT_TLS1_3_SERVER 0x00001000 +#endif +#ifndef SP_PROT_TLS1_3_CLIENT +#define SP_PROT_TLS1_3_CLIENT 0x00002000 +#endif +#ifndef SP_PROT_TLS1_3 +#define SP_PROT_TLS1_3 (SP_PROT_TLS1_3_CLIENT | SP_PROT_TLS1_3_SERVER) +#endif + +/* + @future!: + + - Transmitting intermediate certificates + - Look for a way to avoid putting intermediate certificates in the certificate store + - No documentation on how to send the chain + - A stackoverflow question on this from 3 years ago implies schannel only sends intermediate + certificates if it's "in the system or user certificate store". + - https://stackoverflow.com/q/30156584/2493610 + - This can be done by users, but we shouldn't add any and all local intermediate + certs to the stores automatically. + - PSK support + - Was added in Windows 10 (it seems), documentation at time of writing is sparse/non-existent. + - Specifically about how to supply credentials when they're requested. + - Or how to recognize that they're requested in the first place. + - Skip certificate verification. + - Check if "PSK-only" is still required to do PSK _at all_ (all-around bad solution). + - 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) + +*/ + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.schannel"); + +// Defined in qsslsocket_qt.cpp. +QByteArray _q_makePkcs12(const QList &certs, const QSslKey &key, + const QString &passPhrase); + +namespace QTlsPrivate { + +QList defaultCiphers() +{ + // Previously the code was in QSslSocketBackendPrivate. + QList 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); + } + + return ciphers; + +} + +} // namespace QTlsPrivate + +namespace { +bool supportsTls13(); +} + +bool QSchannelBackend::s_loadedCiphersAndCerts = false; +Q_GLOBAL_STATIC(QRecursiveMutex, qt_schannel_mutex) + +long QSchannelBackend::tlsLibraryVersionNumber() const +{ + const auto os = QOperatingSystemVersion::current(); + return (os.majorVersion() << 24) | ((os.minorVersion() & 0xFF) << 16) | (os.microVersion() & 0xFFFF); +} + +QString QSchannelBackend::tlsLibraryVersionString() const +{ + const auto os = QOperatingSystemVersion::current(); + return QString::fromLatin1("Secure Channel, %1 %2.%3.%4") + .arg(os.name(), + QString::number(os.majorVersion()), + QString::number(os.minorVersion()), + QString::number(os.microVersion())); +} + +long QSchannelBackend::tlsLibraryBuildVersionNumber() const +{ + return tlsLibraryVersionNumber(); +} + +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())); +} + +void QSchannelBackend::ensureInitialized() const +{ + ensureInitializedImplementation(); +} + +void QSchannelBackend::ensureInitializedImplementation() +{ + const QMutexLocker locker(qt_schannel_mutex); + if (s_loadedCiphersAndCerts) + return; + s_loadedCiphersAndCerts = true; + + setDefaultCaCertificates(systemCaCertificatesImplementation()); + // setDefaultCaCertificates sets it to false, re-enable it: + QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true); + + resetDefaultCiphers(); +} + +void QSchannelBackend::resetDefaultCiphers() +{ + setDefaultSupportedCiphers(QTlsPrivate::defaultCiphers()); + setDefaultCiphers(QTlsPrivate::defaultCiphers()); +} + +QString QSchannelBackend::backendName() const +{ + return builtinBackendNames[nameIndexSchannel]; +} + +QList QSchannelBackend::supportedProtocols() const +{ + QList protocols; + + protocols << QSsl::AnyProtocol; + protocols << QSsl::SecureProtocols; + protocols << QSsl::TlsV1_0; + protocols << QSsl::TlsV1_0OrLater; + protocols << QSsl::TlsV1_1; + protocols << QSsl::TlsV1_1OrLater; + protocols << QSsl::TlsV1_2; + protocols << QSsl::TlsV1_2OrLater; + + if (supportsTls13()) { + protocols << QSsl::TlsV1_3; + protocols << QSsl::TlsV1_3OrLater; + } + + return protocols; +} + +QList QSchannelBackend::supportedFeatures() const +{ + QList features; + + features << QSsl::SupportedFeature::ClientSideAlpn; + features << QSsl::SupportedFeature::ServerSideAlpn; + + return features; +} + +QList QSchannelBackend::implementedClasses() const +{ + QList classes; + + classes << QSsl::ImplementedClass::Socket; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Key; + + return classes; +} + +QTlsPrivate::TlsKey *QSchannelBackend::createKey() const +{ + return new QTlsPrivate::TlsKeySchannel; +} + +QTlsPrivate::X509Certificate *QSchannelBackend::createCertificate() const +{ + return new QTlsPrivate::X509CertificateSchannel; +} + +QList QSchannelBackend::systemCaCertificates() const +{ + return systemCaCertificatesImplementation(); +} + +QTlsPrivate::TlsCryptograph *QSchannelBackend::createTlsCryptograph() const +{ + return new QTlsPrivate::TlsCryptographSchannel; +} + +QList QSchannelBackend::systemCaCertificatesImplementation() +{ + // Similar to non-Darwin version found in qtlsbackend_openssl.cpp, + // QTlsPrivate::systemCaCertificates function. + QList systemCerts; + auto hSystemStore = QHCertStorePointer(CertOpenSystemStore(0, L"ROOT")); + if (hSystemStore) { + PCCERT_CONTEXT pc = nullptr; + while ((pc = CertFindCertificateInStore(hSystemStore.get(), X509_ASN_ENCODING, 0, + CERT_FIND_ANY, nullptr, pc))) { + systemCerts.append(QTlsPrivate::X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(pc)); + } + } + return systemCerts; +} + +QTlsPrivate::X509PemReaderPtr QSchannelBackend::X509PemReader() const +{ + return QTlsPrivate::X509CertificateGeneric::certificatesFromPem; +} + +QTlsPrivate::X509DerReaderPtr QSchannelBackend::X509DerReader() const +{ + return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; +} + +namespace { + +SecBuffer createSecBuffer(void *ptr, unsigned long length, unsigned long bufferType) +{ + return SecBuffer{ length, bufferType, ptr }; +} + +SecBuffer createSecBuffer(QByteArray &buffer, unsigned long bufferType) +{ + return createSecBuffer(buffer.data(), static_cast(buffer.length()), bufferType); +} + +QString schannelErrorToString(qint32 status) +{ + switch (status) { + case SEC_E_INSUFFICIENT_MEMORY: + return QSslSocket::tr("Insufficient memory"); + case SEC_E_INTERNAL_ERROR: + return QSslSocket::tr("Internal error"); + case SEC_E_INVALID_HANDLE: + return QSslSocket::tr("An internal handle was invalid"); + case SEC_E_INVALID_TOKEN: + return QSslSocket::tr("An internal token was invalid"); + case SEC_E_LOGON_DENIED: + // According to the link below we get this error when Schannel receives TLS1_ALERT_ACCESS_DENIED + // https://docs.microsoft.com/en-us/windows/desktop/secauthn/schannel-error-codes-for-tls-and-ssl-alerts + return QSslSocket::tr("Access denied"); + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + return QSslSocket::tr("No authority could be contacted for authorization"); + case SEC_E_NO_CREDENTIALS: + return QSslSocket::tr("No credentials"); + case SEC_E_TARGET_UNKNOWN: + return QSslSocket::tr("The target is unknown or unreachable"); + case SEC_E_UNSUPPORTED_FUNCTION: + return QSslSocket::tr("An unsupported function was requested"); + case SEC_E_WRONG_PRINCIPAL: + // SNI error + return QSslSocket::tr("The hostname provided does not match the one received from the peer"); + case SEC_E_APPLICATION_PROTOCOL_MISMATCH: + return QSslSocket::tr("No common protocol exists between the client and the server"); + case SEC_E_ILLEGAL_MESSAGE: + return QSslSocket::tr("Unexpected or badly-formatted message received"); + case SEC_E_ENCRYPT_FAILURE: + return QSslSocket::tr("The data could not be encrypted"); + case SEC_E_ALGORITHM_MISMATCH: + return QSslSocket::tr("No cipher suites in common"); + case SEC_E_UNKNOWN_CREDENTIALS: + // This can mean "invalid argument" in some cases... + return QSslSocket::tr("The credentials were not recognized / Invalid argument"); + case SEC_E_MESSAGE_ALTERED: + // According to the Internet it also triggers for messages that are out of order. + // https://microsoft.public.platformsdk.security.narkive.com/4JAvlMvD/help-please-schannel-security-contexts-and-decryptmessage + return QSslSocket::tr("The message was tampered with, damaged or out of sequence."); + case SEC_E_OUT_OF_SEQUENCE: + return QSslSocket::tr("A message was received out of sequence."); + case SEC_E_CONTEXT_EXPIRED: + return QSslSocket::tr("The TLS/SSL connection has been closed"); + default: + return QSslSocket::tr("Unknown error occurred: %1").arg(status); + } +} + +bool supportsTls13() +{ +#ifdef SUPPORTS_TLS13 + static bool supported = []() { + const auto current = QOperatingSystemVersion::current(); + // 20221 just happens to be the preview version I run on my laptop where I tested TLS 1.3. + const auto minimum = + QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 20221); + return current >= minimum; + }(); + return supported; +#else + return false; +#endif +} + +DWORD toSchannelProtocol(QSsl::SslProtocol protocol) +{ + DWORD protocols = SP_PROT_NONE; + switch (protocol) { + case QSsl::UnknownProtocol: + return DWORD(-1); + case QSsl::DtlsV1_0: + case QSsl::DtlsV1_2: + case QSsl::DtlsV1_0OrLater: + case QSsl::DtlsV1_2OrLater: + return DWORD(-1); // Not supported at the moment (@future) + case QSsl::AnyProtocol: + protocols = SP_PROT_TLS1_0 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2; + if (supportsTls13()) + protocols |= SP_PROT_TLS1_3; + break; + case QSsl::TlsV1_0: + protocols = SP_PROT_TLS1_0; + break; + case QSsl::TlsV1_1: + protocols = SP_PROT_TLS1_1; + break; + case QSsl::TlsV1_2: + protocols = SP_PROT_TLS1_2; + break; + case QSsl::TlsV1_3: + if (supportsTls13()) + protocols = SP_PROT_TLS1_3; + else + protocols = DWORD(-1); + break; + case QSsl::SecureProtocols: // TLS v1.0 and later is currently considered secure + case QSsl::TlsV1_0OrLater: + // For the "OrLater" protocols we fall through from one to the next, adding all of them + // in ascending order + protocols = SP_PROT_TLS1_0; + Q_FALLTHROUGH(); + case QSsl::TlsV1_1OrLater: + protocols |= SP_PROT_TLS1_1; + Q_FALLTHROUGH(); + case QSsl::TlsV1_2OrLater: + protocols |= SP_PROT_TLS1_2; + Q_FALLTHROUGH(); + case QSsl::TlsV1_3OrLater: + if (supportsTls13()) + protocols |= SP_PROT_TLS1_3; + else if (protocol == QSsl::TlsV1_3OrLater) + protocols = DWORD(-1); // if TlsV1_3OrLater was specifically chosen we should fail + break; + } + return protocols; +} + +#ifdef SUPPORTS_TLS13 +// In the new API that descended down upon us we are not asked which protocols we want +// but rather which protocols we don't want. So now we have this function to disable +// anything that is not enabled. +DWORD toSchannelProtocolNegated(QSsl::SslProtocol protocol) +{ + DWORD protocols = SP_PROT_ALL; // all protocols + protocols &= ~toSchannelProtocol(protocol); // minus the one(s) we want + return protocols; +} +#endif + +/*! + \internal + Used when converting the established session's \a protocol back to + Qt's own SslProtocol type. + + Only one protocol should be passed in at a time. +*/ +QSsl::SslProtocol toQtSslProtocol(DWORD protocol) +{ +#define MAP_PROTOCOL(sp_protocol, q_protocol) \ + if (protocol & sp_protocol) { \ + Q_ASSERT(!(protocol & ~sp_protocol)); \ + return q_protocol; \ + } + + MAP_PROTOCOL(SP_PROT_TLS1_0, QSsl::TlsV1_0) + MAP_PROTOCOL(SP_PROT_TLS1_1, QSsl::TlsV1_1) + MAP_PROTOCOL(SP_PROT_TLS1_2, QSsl::TlsV1_2) + MAP_PROTOCOL(SP_PROT_TLS1_3, QSsl::TlsV1_3) +#undef MAP_PROTOCOL + Q_UNREACHABLE(); + return QSsl::UnknownProtocol; +} + +/*! + \internal + Used by verifyCertContext to check if a client cert is used by a server or vice versa. +*/ +bool netscapeWrongCertType(const QList &extensions, bool isClient) +{ + 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; + }); + if (netscapeIt != extensions.cend()) { + const QByteArray netscapeCertTypeByte = netscapeIt->value().toByteArray(); + int netscapeCertType = 0; + QDataStream dataStream(netscapeCertTypeByte); + dataStream >> netscapeCertType; + if (dataStream.status() != QDataStream::Status::Ok) + return true; + const int expectedPeerCertType = isClient ? NETSCAPE_SSL_SERVER_AUTH_CERT_TYPE + : NETSCAPE_SSL_CLIENT_AUTH_CERT_TYPE; + if ((netscapeCertType & expectedPeerCertType) == 0) + return true; + } + return false; +} + +/*! + \internal + Used by verifyCertContext to check the basicConstraints certificate + extension to see if the certificate is a certificate authority. + Returns false if the certificate does not have the basicConstraints + extension or if it is not a certificate authority. +*/ +bool isCertificateAuthority(const QList &extensions) +{ + auto it = std::find_if(extensions.cbegin(), extensions.cend(), + [](const QSslCertificateExtension &extension) { + return extension.name() == QLatin1String("basicConstraints"); + }); + if (it != extensions.cend()) { + QVariantMap basicConstraints = it->value().toMap(); + return basicConstraints.value(QLatin1String("ca"), false).toBool(); + } + return false; +} + +/*! + \internal + Returns true if the attributes we requested from the context/handshake have + been given. +*/ +bool matchesContextRequirements(DWORD attributes, DWORD requirements, + QSslSocket::PeerVerifyMode verifyMode, + bool isClient) +{ +#ifdef QSSLSOCKET_DEBUG +#define DEBUG_WARN(message) qCWarning(lcTlsBackend, message) +#else +#define DEBUG_WARN(message) +#endif + +#define CHECK_ATTRIBUTE(attributeName) \ + do { \ + const DWORD req##attributeName = isClient ? ISC_REQ_##attributeName : ASC_REQ_##attributeName; \ + const DWORD ret##attributeName = isClient ? ISC_RET_##attributeName : ASC_RET_##attributeName; \ + if (!(requirements & req##attributeName) != !(attributes & ret##attributeName)) { \ + DEBUG_WARN("Missing attribute \"" #attributeName "\""); \ + return false; \ + } \ + } while (false) + + CHECK_ATTRIBUTE(CONFIDENTIALITY); + CHECK_ATTRIBUTE(REPLAY_DETECT); + CHECK_ATTRIBUTE(SEQUENCE_DETECT); + CHECK_ATTRIBUTE(STREAM); + if (verifyMode == QSslSocket::PeerVerifyMode::VerifyPeer) + CHECK_ATTRIBUTE(MUTUAL_AUTH); + + // This one is manual because there is no server / ASC_ version + if (isClient) { + const auto reqManualCredValidation = ISC_REQ_MANUAL_CRED_VALIDATION; + const auto retManualCredValidation = ISC_RET_MANUAL_CRED_VALIDATION; + if (!(requirements & reqManualCredValidation) != !(attributes & retManualCredValidation)) { + DEBUG_WARN("Missing attribute \"MANUAL_CRED_VALIDATION\""); + return false; + } + } + + return true; +#undef CHECK_ATTRIBUTE +#undef DEBUG_WARN +} + +template +Required const_reinterpret_cast(Actual *p) +{ + return Required(p); +} + +#ifdef SUPPORTS_ALPN +bool supportsAlpn() +{ + return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1; +} + +QByteArray createAlpnString(const QByteArrayList &nextAllowedProtocols) +{ + QByteArray alpnString; + if (!nextAllowedProtocols.isEmpty() && supportsAlpn()) { + const QByteArray names = [&nextAllowedProtocols]() { + QByteArray protocolString; + for (QByteArray proto : nextAllowedProtocols) { + if (proto.size() > 255) { + qCWarning(lcTlsBackend) << "TLS ALPN extension" << proto + << "is too long and will be ignored."; + continue; + } else if (proto.isEmpty()) { + continue; + } + protocolString += char(proto.length()) + proto; + } + return protocolString; + }(); + if (names.isEmpty()) + return alpnString; + + const quint16 namesSize = names.size(); + const quint32 alpnId = SecApplicationProtocolNegotiationExt_ALPN; + const quint32 totalSize = sizeof(alpnId) + sizeof(namesSize) + namesSize; + alpnString = QByteArray::fromRawData(reinterpret_cast(&totalSize), sizeof(totalSize)) + + QByteArray::fromRawData(reinterpret_cast(&alpnId), sizeof(alpnId)) + + QByteArray::fromRawData(reinterpret_cast(&namesSize), sizeof(namesSize)) + + names; + } + return alpnString; +} +#endif // SUPPORTS_ALPN + +qint64 readToBuffer(QByteArray &buffer, QTcpSocket *plainSocket) +{ + Q_ASSERT(plainSocket); + static const qint64 shrinkCutoff = 1024 * 12; + static const qint64 defaultRead = 1024 * 16; + qint64 bytesRead = 0; + + const auto toRead = std::min(defaultRead, plainSocket->bytesAvailable()); + if (toRead > 0) { + const auto bufferSize = buffer.size(); + buffer.reserve(bufferSize + toRead); // avoid growth strategy kicking in + buffer.resize(bufferSize + toRead); + bytesRead = plainSocket->read(buffer.data() + bufferSize, toRead); + buffer.resize(bufferSize + bytesRead); + // In case of excessive memory usage we shrink: + if (buffer.size() < shrinkCutoff && buffer.capacity() > defaultRead) + buffer.shrink_to_fit(); + } + + return bytesRead; +} + +void retainExtraData(QByteArray &buffer, const SecBuffer &secBuffer) +{ + Q_ASSERT(secBuffer.BufferType == SECBUFFER_EXTRA); + if (int(secBuffer.cbBuffer) >= buffer.size()) + return; + +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, "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); +} + +qint64 checkIncompleteData(const SecBuffer &secBuffer) +{ + if (secBuffer.BufferType == SECBUFFER_MISSING) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, "Need %lu more bytes.", secBuffer.cbBuffer); +#endif + return secBuffer.cbBuffer; +} + return 0; +} + +} // anonymous namespace + + +namespace QTlsPrivate { + +TlsCryptographSchannel::TlsCryptographSchannel() +{ + SecInvalidateHandle(&credentialHandle); + SecInvalidateHandle(&contextHandle); + QSchannelBackend::ensureInitializedImplementation(); +} + +TlsCryptographSchannel::~TlsCryptographSchannel() +{ + closeCertificateStores(); + deallocateContext(); + freeCredentialsHandle(); + CertFreeCertificateContext(localCertContext); +} + +void TlsCryptographSchannel::init(QSslSocket *qObj, QSslSocketPrivate *dObj) +{ + Q_ASSERT(qObj); + Q_ASSERT(dObj); + + q = qObj; + d = dObj; + + reset(); +} + +bool TlsCryptographSchannel::sendToken(void *token, unsigned long tokenLength, bool emitError) +{ + if (tokenLength == 0) + return true; + + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + const qint64 written = plainSocket->write(static_cast(token), tokenLength); + if (written != qint64(tokenLength)) { + // Failed to write/buffer everything or an error occurred + if (emitError) + setErrorAndEmit(d, plainSocket->error(), plainSocket->errorString()); + return false; + } + return true; +} + +QString TlsCryptographSchannel::targetName() const +{ + // Used for SNI extension + Q_ASSERT(q); + Q_ASSERT(d); + + const auto verificationPeerName = d->verificationName(); + return verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName; +} + +ULONG TlsCryptographSchannel::getContextRequirements() +{ + Q_ASSERT(d); + Q_ASSERT(q); + + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; + ULONG req = 0; + + req |= ISC_REQ_ALLOCATE_MEMORY; // Allocate memory for buffers automatically + req |= ISC_REQ_CONFIDENTIALITY; // Encrypt messages + req |= ISC_REQ_REPLAY_DETECT; // Detect replayed messages + req |= ISC_REQ_SEQUENCE_DETECT; // Detect out of sequence messages + req |= ISC_REQ_STREAM; // Support a stream-oriented connection + + if (isClient) { + req |= ISC_REQ_MANUAL_CRED_VALIDATION; // Manually validate certificate + } else { + switch (q->peerVerifyMode()) { + case QSslSocket::PeerVerifyMode::VerifyNone: + // There doesn't seem to be a way to ask for an optional client cert :-( + case QSslSocket::PeerVerifyMode::AutoVerifyPeer: + case QSslSocket::PeerVerifyMode::QueryPeer: + break; + case QSslSocket::PeerVerifyMode::VerifyPeer: + req |= ISC_REQ_MUTUAL_AUTH; + break; + } + } + + return req; +} + +bool TlsCryptographSchannel::acquireCredentialsHandle() +{ + Q_ASSERT(d); + Q_ASSERT(q); + const auto &configuration = q->sslConfiguration(); + + Q_ASSERT(schannelState == SchannelState::InitializeHandshake); + + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; + const DWORD protocols = toSchannelProtocol(configuration.protocol()); + if (protocols == DWORD(-1)) { + setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError, + QSslSocket::tr("Invalid protocol chosen")); + return false; + } + + const CERT_CHAIN_CONTEXT *chainContext = nullptr; + auto freeCertChain = qScopeGuard([&chainContext]() { + if (chainContext) + CertFreeCertificateChain(chainContext); + }); + + DWORD certsCount = 0; + // Set up our certificate stores before trying to use one... + initializeCertificateStores(); + + // Check if user has specified a certificate chain but it could not be loaded. + // This happens if there was something wrong with the certificate chain or there was no private + // key. + if (!configuration.localCertificateChain().isEmpty() && !localCertificateStore) + return true; // 'true' because "tst_QSslSocket::setEmptyKey" expects us to not disconnect + + if (localCertificateStore != nullptr) { + CERT_CHAIN_FIND_BY_ISSUER_PARA findParam; + ZeroMemory(&findParam, sizeof(findParam)); + findParam.cbSize = sizeof(findParam); + findParam.pszUsageIdentifier = isClient ? szOID_PKIX_KP_CLIENT_AUTH : szOID_PKIX_KP_SERVER_AUTH; + + // There should only be one chain in our store, so.. we grab that one. + chainContext = CertFindChainInStore(localCertificateStore.get(), + X509_ASN_ENCODING, + 0, + CERT_CHAIN_FIND_BY_ISSUER, + &findParam, + nullptr); + if (!chainContext) { + const QString message = isClient + ? QSslSocket::tr("The certificate provided cannot be used for a client.") + : QSslSocket::tr("The certificate provided cannot be used for a server."); + setErrorAndEmit(d, QAbstractSocket::SocketError::SslInvalidUserDataError, message); + return false; + } + Q_ASSERT(chainContext->cChain == 1); + Q_ASSERT(chainContext->rgpChain[0]); + Q_ASSERT(chainContext->rgpChain[0]->cbSize >= 1); + Q_ASSERT(chainContext->rgpChain[0]->rgpElement[0]); + Q_ASSERT(!localCertContext); + localCertContext = CertDuplicateCertificateContext(chainContext->rgpChain[0] + ->rgpElement[0] + ->pCertContext); + certsCount = 1; + Q_ASSERT(localCertContext); + } + void *credentials = nullptr; +#ifdef SUPPORTS_TLS13 + TLS_PARAMETERS tlsParameters = { + 0, + nullptr, + toSchannelProtocolNegated(configuration.protocol()), // what protocols to disable + 0, + nullptr, + 0 + }; + if (supportsTls13()) { + SCH_CREDENTIALS *cred = new SCH_CREDENTIALS{ + SCH_CREDENTIALS_VERSION, + 0, + certsCount, + &localCertContext, + nullptr, + 0, + nullptr, + 0, + SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT + | SCH_CRED_NO_DEFAULT_CREDS, + 1, + &tlsParameters + }; + credentials = cred; + } else +#endif // SUPPORTS_TLS13 + { + SCHANNEL_CRED *cred = new SCHANNEL_CRED{ + SCHANNEL_CRED_VERSION, // dwVersion + certsCount, // cCreds + &localCertContext, // paCred (certificate(s) containing a private key for authentication) + nullptr, // hRootStore + + 0, // cMappers (reserved) + nullptr, // aphMappers (reserved) + + 0, // cSupportedAlgs + nullptr, // palgSupportedAlgs (nullptr = system default) + + protocols, // grbitEnabledProtocols + 0, // dwMinimumCipherStrength (0 = system default) + 0, // dwMaximumCipherStrength (0 = system default) + 0, // dwSessionLifespan (0 = schannel default, 10 hours) + SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT + | SCH_CRED_NO_DEFAULT_CREDS, // dwFlags + 0 // dwCredFormat (must be 0) + }; + credentials = cred; + } + Q_ASSERT(credentials != nullptr); + + TimeStamp expiration{}; + auto status = AcquireCredentialsHandle(nullptr, // pszPrincipal (unused) + const_cast(UNISP_NAME), // pszPackage + isClient ? SECPKG_CRED_OUTBOUND : SECPKG_CRED_INBOUND, // fCredentialUse + nullptr, // pvLogonID (unused) + credentials, // pAuthData + nullptr, // pGetKeyFn (unused) + nullptr, // pvGetKeyArgument (unused) + &credentialHandle, // phCredential + &expiration // ptsExpir + ); + +#ifdef SUPPORTS_TLS13 + if (supportsTls13()) { + delete static_cast(credentials); + } else +#endif // SUPPORTS_TLS13 + { + delete static_cast(credentials); + } + + if (status != SEC_E_OK) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, schannelErrorToString(status)); + return false; + } + return true; +} + +void TlsCryptographSchannel::deallocateContext() +{ + if (SecIsValidHandle(&contextHandle)) { + DeleteSecurityContext(&contextHandle); + SecInvalidateHandle(&contextHandle); + } +} + +void TlsCryptographSchannel::freeCredentialsHandle() +{ + if (SecIsValidHandle(&credentialHandle)) { + FreeCredentialsHandle(&credentialHandle); + SecInvalidateHandle(&credentialHandle); + } +} + +void TlsCryptographSchannel::closeCertificateStores() +{ + localCertificateStore.reset(); + peerCertificateStore.reset(); + caCertificateStore.reset(); +} + +bool TlsCryptographSchannel::createContext() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + Q_ASSERT(SecIsValidHandle(&credentialHandle)); + Q_ASSERT(schannelState == SchannelState::InitializeHandshake); + Q_ASSERT(d->tlsMode() == QSslSocket::SslClientMode); + ULONG contextReq = getContextRequirements(); + + SecBuffer outBuffers[3]; + outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); + outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); + outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); + auto freeBuffers = qScopeGuard([&outBuffers]() { + for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { + if (outBuffers[i].pvBuffer) + FreeContextBuffer(outBuffers[i].pvBuffer); + } + }); + SecBufferDesc outputBufferDesc{ + SECBUFFER_VERSION, + ARRAYSIZE(outBuffers), + outBuffers + }; + + TimeStamp expiry; + + SecBufferDesc alpnBufferDesc; + bool useAlpn = false; +#ifdef SUPPORTS_ALPN + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNone); + QByteArray alpnString = createAlpnString(q->sslConfiguration().allowedNextProtocols()); + useAlpn = !alpnString.isEmpty(); + SecBuffer alpnBuffers[1]; + alpnBuffers[0] = createSecBuffer(alpnString, SECBUFFER_APPLICATION_PROTOCOLS); + alpnBufferDesc = { + SECBUFFER_VERSION, + ARRAYSIZE(alpnBuffers), + alpnBuffers + }; +#endif + + auto status = InitializeSecurityContext(&credentialHandle, // phCredential + nullptr, // phContext + const_reinterpret_cast(targetName().utf16()), // pszTargetName + contextReq, // fContextReq + 0, // Reserved1 + 0, // TargetDataRep (unused) + useAlpn ? &alpnBufferDesc : nullptr, // pInput + 0, // Reserved2 + &contextHandle, // phNewContext + &outputBufferDesc, // pOutput + &contextAttributes, // pfContextAttr + &expiry // ptsExpiry + ); + + // This is the first call to InitializeSecurityContext, so theoretically "CONTINUE_NEEDED" + // should be the only non-error return-code here. + if (status != SEC_I_CONTINUE_NEEDED) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Error creating SSL context (%1)").arg(schannelErrorToString(status))); + return false; + } + + if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) + return false; + schannelState = SchannelState::PerformHandshake; + return true; +} + +bool TlsCryptographSchannel::acceptContext() +{ + Q_ASSERT(d); + Q_ASSERT(q); + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + Q_ASSERT(SecIsValidHandle(&credentialHandle)); + Q_ASSERT(schannelState == SchannelState::InitializeHandshake); + Q_ASSERT(d->tlsMode() == QSslSocket::SslServerMode); + ULONG contextReq = getContextRequirements(); + + if (missingData > plainSocket->bytesAvailable()) + return true; + + missingData = 0; + readToBuffer(intermediateBuffer, plainSocket); + if (intermediateBuffer.isEmpty()) + return true; // definitely need more data.. + + SecBuffer inBuffers[2]; + inBuffers[0] = createSecBuffer(intermediateBuffer, SECBUFFER_TOKEN); + +#ifdef SUPPORTS_ALPN + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNone); + // The string must be alive when we call AcceptSecurityContext + QByteArray alpnString = createAlpnString(q->sslConfiguration().allowedNextProtocols()); + if (!alpnString.isEmpty()) { + inBuffers[1] = createSecBuffer(alpnString, SECBUFFER_APPLICATION_PROTOCOLS); + } else +#endif + { + inBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); + } + + SecBufferDesc inputBufferDesc{ + SECBUFFER_VERSION, + ARRAYSIZE(inBuffers), + inBuffers + }; + + SecBuffer outBuffers[3]; + outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); + outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); + outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); + auto freeBuffers = qScopeGuard([&outBuffers]() { + for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { + if (outBuffers[i].pvBuffer) + FreeContextBuffer(outBuffers[i].pvBuffer); + } + }); + SecBufferDesc outputBufferDesc{ + SECBUFFER_VERSION, + ARRAYSIZE(outBuffers), + outBuffers + }; + + TimeStamp expiry; + auto status = AcceptSecurityContext( + &credentialHandle, // phCredential + nullptr, // phContext + &inputBufferDesc, // pInput + contextReq, // fContextReq + 0, // TargetDataRep (unused) + &contextHandle, // phNewContext + &outputBufferDesc, // pOutput + &contextAttributes, // pfContextAttr + &expiry // ptsTimeStamp + ); + + if (status == SEC_E_INCOMPLETE_MESSAGE) { + // Need more data + missingData = checkIncompleteData(outBuffers[0]); + return true; + } + + if (inBuffers[1].BufferType == SECBUFFER_EXTRA) { + // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel + // inBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need to + // be stored. + retainExtraData(intermediateBuffer, inBuffers[1]); + } else { /* No 'extra' data, message not incomplete */ + intermediateBuffer.resize(0); + } + + if (status != SEC_I_CONTINUE_NEEDED) { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Error creating SSL context (%1)").arg(schannelErrorToString(status))); + return false; + } + if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) + return false; + schannelState = SchannelState::PerformHandshake; + return true; +} + +bool TlsCryptographSchannel::performHandshake() +{ + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + if (plainSocket->state() == QAbstractSocket::UnconnectedState) { + setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); + return false; + } + Q_ASSERT(SecIsValidHandle(&credentialHandle)); + Q_ASSERT(SecIsValidHandle(&contextHandle)); + Q_ASSERT(schannelState == SchannelState::PerformHandshake); + +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, "Bytes available from socket: %lld", plainSocket->bytesAvailable()); + qCDebug(lcTlsBackend, "intermediateBuffer size: %d", intermediateBuffer.size()); +#endif + + if (missingData > plainSocket->bytesAvailable()) + return true; + + missingData = 0; + readToBuffer(intermediateBuffer, plainSocket); + if (intermediateBuffer.isEmpty()) + return true; // no data, will fail + + SecBuffer outBuffers[3] = {}; + const auto freeOutBuffers = [&outBuffers]() { + for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { + if (outBuffers[i].pvBuffer) + FreeContextBuffer(outBuffers[i].pvBuffer); + } + }; + const auto outBuffersGuard = qScopeGuard(freeOutBuffers); + // For this call to InitializeSecurityContext we may need to call it twice. + // In some cases us not having a certificate isn't actually an error, but just a request. + // With Schannel, to ignore this warning, we need to call InitializeSecurityContext again + // when we get SEC_I_INCOMPLETE_CREDENTIALS! As far as I can tell it's not documented anywhere. + // https://stackoverflow.com/a/47479968/2493610 + SECURITY_STATUS status; + short attempts = 2; + do { + SecBuffer inputBuffers[2]; + inputBuffers[0] = createSecBuffer(intermediateBuffer, SECBUFFER_TOKEN); + inputBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); + SecBufferDesc inputBufferDesc{ + SECBUFFER_VERSION, + ARRAYSIZE(inputBuffers), + inputBuffers + }; + + freeOutBuffers(); // free buffers from any previous attempt + outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); + outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); + outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); + SecBufferDesc outputBufferDesc{ + SECBUFFER_VERSION, + ARRAYSIZE(outBuffers), + outBuffers + }; + + ULONG contextReq = getContextRequirements(); + TimeStamp expiry; + status = InitializeSecurityContext( + &credentialHandle, // phCredential + &contextHandle, // phContext + const_reinterpret_cast(targetName().utf16()), // pszTargetName + contextReq, // fContextReq + 0, // Reserved1 + 0, // TargetDataRep (unused) + &inputBufferDesc, // pInput + 0, // Reserved2 + nullptr, // phNewContext (we already have one) + &outputBufferDesc, // pOutput + &contextAttributes, // pfContextAttr + &expiry // ptsExpiry + ); + + if (inputBuffers[1].BufferType == SECBUFFER_EXTRA) { + // https://docs.microsoft.com/en-us/windows/desktop/secauthn/extra-buffers-returned-by-schannel + // inputBuffers[1].cbBuffer indicates the amount of bytes _NOT_ processed, the rest need + // to be stored. + retainExtraData(intermediateBuffer, inputBuffers[1]); + } else if (status != SEC_E_INCOMPLETE_MESSAGE) { + // Clear the buffer if we weren't asked for more data + intermediateBuffer.resize(0); + } + + --attempts; + } while (status == SEC_I_INCOMPLETE_CREDENTIALS && attempts > 0); + + switch (status) { + case SEC_E_OK: + // Need to transmit a final token in the handshake if 'cbBuffer' is non-zero. + if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) + return false; + schannelState = SchannelState::VerifyHandshake; + return true; + case SEC_I_CONTINUE_NEEDED: + if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) + return false; + // Must call InitializeSecurityContext again later (done through continueHandshake) + return true; + case SEC_I_INCOMPLETE_CREDENTIALS: + // Schannel takes care of picking certificate to send (other than the one we can specify), + // so if we get here then that means we don't have a certificate the server accepts. + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Server did not accept any certificate we could present.")); + return false; + case SEC_I_CONTEXT_EXPIRED: + // "The message sender has finished using the connection and has initiated a shutdown." + if (outBuffers[0].BufferType == SECBUFFER_TOKEN) { + if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer)) + return false; + } + if (!shutdown) { // we did not initiate this + setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); + } + return true; + case SEC_E_INCOMPLETE_MESSAGE: + // Simply incomplete, wait for more data + missingData = checkIncompleteData(outBuffers[0]); + return true; + case SEC_E_ALGORITHM_MISMATCH: + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Algorithm mismatch")); + shutdown = true; // skip sending the "Shutdown" alert + return false; + } + + // Note: We can get here if the connection is using TLS 1.2 and the server certificate uses + // MD5, which is not allowed in Schannel. This causes an "invalid token" error during handshake. + // (If you came here investigating an error: md5 is insecure, update your certificate) + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Handshake failed: %1").arg(schannelErrorToString(status))); + return false; +} + +bool TlsCryptographSchannel::verifyHandshake() +{ + Q_ASSERT(d); + Q_ASSERT(q); + const auto &configuration = q->sslConfiguration(); + + sslErrors.clear(); + + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; +#define CHECK_STATUS(status) \ + if (status != SEC_E_OK) { \ + setErrorAndEmit(d, QAbstractSocket::SslInternalError, \ + QSslSocket::tr("Failed to query the TLS context: %1") \ + .arg(schannelErrorToString(status))); \ + return false; \ + } + + // Everything is set up, now make sure there's nothing wrong and query some attributes... + if (!matchesContextRequirements(contextAttributes, getContextRequirements(), + configuration.peerVerifyMode(), isClient)) { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Did not get the required attributes for the connection.")); + return false; + } + + // Get stream sizes (to know the max size of a message and the size of the header and trailer) + auto status = QueryContextAttributes(&contextHandle, + SECPKG_ATTR_STREAM_SIZES, + &streamSizes); + CHECK_STATUS(status); + + // Get session cipher info + status = QueryContextAttributes(&contextHandle, + SECPKG_ATTR_CONNECTION_INFO, + &connectionInfo); + CHECK_STATUS(status); + +#ifdef SUPPORTS_ALPN + const auto allowedProtos = configuration.allowedNextProtocols(); + if (!allowedProtos.isEmpty() && supportsAlpn()) { + SecPkgContext_ApplicationProtocol alpn; + status = QueryContextAttributes(&contextHandle, + SECPKG_ATTR_APPLICATION_PROTOCOL, + &alpn); + CHECK_STATUS(status); + if (alpn.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { + QByteArray negotiatedProto = QByteArray((const char *)alpn.ProtocolId, + alpn.ProtocolIdSize); + if (!allowedProtos.contains(negotiatedProto)) { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Unwanted protocol was negotiated")); + return false; + } + QTlsBackend::setNegotiatedProtocol(d, negotiatedProto); + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNegotiated); + } else { + QTlsBackend::setNegotiatedProtocol(d, {}); + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationUnsupported); + } + } +#endif // supports ALPN + +#undef CHECK_STATUS + + // Verify certificate + CERT_CONTEXT *certificateContext = nullptr; + auto freeCertificate = qScopeGuard([&certificateContext]() { + if (certificateContext) + CertFreeCertificateContext(certificateContext); + }); + status = QueryContextAttributes(&contextHandle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &certificateContext); + + // QueryPeer can (currently) not work in Schannel since Schannel itself doesn't have a way to + // ask for a certificate and then still be OK if it's not received. + // To work around this we don't request a certificate at all for QueryPeer. + // For servers AutoVerifyPeer is supposed to be treated the same as QueryPeer. + // This means that servers using Schannel will only request client certificate for "VerifyPeer". + if ((!isClient && configuration.peerVerifyMode() == QSslSocket::PeerVerifyMode::VerifyPeer) + || (isClient && configuration.peerVerifyMode() != QSslSocket::PeerVerifyMode::VerifyNone + && configuration.peerVerifyMode() != QSslSocket::PeerVerifyMode::QueryPeer)) { + if (status != SEC_E_OK) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "Couldn't retrieve peer certificate, status:" + << schannelErrorToString(status); +#endif + const QSslError error{ QSslError::NoPeerCertificate }; + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + // verifyCertContext returns false if the user disconnected while it was checking errors. + if (certificateContext && !verifyCertContext(certificateContext)) + return false; + + if (!checkSslErrors() || q->state() != QAbstractSocket::ConnectedState) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << __func__ << "was unsuccessful. Paused:" << paused; +#endif + // If we're paused then checkSslErrors returned false, but it's not an error + return d->isPaused() && q->state() == QAbstractSocket::ConnectedState; + } + + schannelState = SchannelState::Done; + return true; +} + +bool TlsCryptographSchannel::renegotiate() +{ + Q_ASSERT(d); + + SecBuffer outBuffers[3]; + outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); + outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); + outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); + auto freeBuffers = qScopeGuard([&outBuffers]() { + for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { + if (outBuffers[i].pvBuffer) + FreeContextBuffer(outBuffers[i].pvBuffer); + } + }); + SecBufferDesc outputBufferDesc{ + SECBUFFER_VERSION, + ARRAYSIZE(outBuffers), + outBuffers + }; + + ULONG contextReq = getContextRequirements(); + TimeStamp expiry; + SECURITY_STATUS status; + if (d->tlsMode() == QSslSocket::SslClientMode) { + status = InitializeSecurityContext(&credentialHandle, // phCredential + &contextHandle, // phContext + const_reinterpret_cast(targetName().utf16()), // pszTargetName + contextReq, // fContextReq + 0, // Reserved1 + 0, // TargetDataRep (unused) + nullptr, // pInput (nullptr for renegotiate) + 0, // Reserved2 + nullptr, // phNewContext (we already have one) + &outputBufferDesc, // pOutput + &contextAttributes, // pfContextAttr + &expiry // ptsExpiry + ); + } else { + status = AcceptSecurityContext( + &credentialHandle, // phCredential + &contextHandle, // phContext + nullptr, // pInput + contextReq, // fContextReq + 0, // TargetDataRep (unused) + nullptr, // phNewContext + &outputBufferDesc, // pOutput + &contextAttributes, // pfContextAttr, + &expiry // ptsTimeStamp + ); + } + if (status == SEC_I_CONTINUE_NEEDED) { + schannelState = SchannelState::PerformHandshake; + return sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer); + } else if (status == SEC_E_OK) { + schannelState = SchannelState::PerformHandshake; + return true; + } + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QSslSocket::tr("Renegotiation was unsuccessful: %1").arg(schannelErrorToString(status))); + return false; +} + +/*! + \internal + reset the state in preparation for reuse of socket +*/ +void TlsCryptographSchannel::reset() +{ + Q_ASSERT(d); + + closeCertificateStores(); // certificate stores could've changed + deallocateContext(); + freeCredentialsHandle(); // in case we already had one (@future: session resumption requires re-use) + + connectionInfo = {}; + streamSizes = {}; + + CertFreeCertificateContext(localCertContext); + localCertContext = nullptr; + + contextAttributes = 0; + intermediateBuffer.clear(); + schannelState = SchannelState::InitializeHandshake; + + + d->setEncrypted(false); + shutdown = false; + renegotiating = false; + + missingData = 0; +} + +void TlsCryptographSchannel::startClientEncryption() +{ + Q_ASSERT(q); + + if (q->isEncrypted()) + return; // let's not mess up the connection... + reset(); + continueHandshake(); +} + +void TlsCryptographSchannel::startServerEncryption() +{ + Q_ASSERT(q); + + if (q->isEncrypted()) + return; // let's not mess up the connection... + reset(); + continueHandshake(); +} + +void TlsCryptographSchannel::transmit() +{ + Q_ASSERT(q); + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + if (d->tlsMode() == QSslSocket::UnencryptedMode) + 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) + return; + + if (schannelState != SchannelState::Done) { + continueHandshake(); + return; + } + + auto &writeBuffer = d->tlsWriteBuffer(); + auto &buffer = d->tlsBuffer(); + if (q->isEncrypted()) { // encrypt data in writeBuffer and write it to plainSocket + qint64 totalBytesWritten = 0; + qint64 writeBufferSize; + while ((writeBufferSize = writeBuffer.size()) > 0) { + const int headerSize = int(streamSizes.cbHeader); + const int trailerSize = int(streamSizes.cbTrailer); + // Try to read 'cbMaximumMessage' bytes from buffer before encrypting. + const int size = int(std::min(writeBufferSize, qint64(streamSizes.cbMaximumMessage))); + QByteArray fullMessage(headerSize + trailerSize + size, Qt::Uninitialized); + { + // Use peek() here instead of read() so we don't lose data if encryption fails. + qint64 copied = writeBuffer.peek(fullMessage.data() + headerSize, size); + Q_ASSERT(copied == size); + } + + SecBuffer inputBuffers[4]{ + createSecBuffer(fullMessage.data(), headerSize, SECBUFFER_STREAM_HEADER), + createSecBuffer(fullMessage.data() + headerSize, size, SECBUFFER_DATA), + createSecBuffer(fullMessage.data() + headerSize + size, trailerSize, SECBUFFER_STREAM_TRAILER), + createSecBuffer(nullptr, 0, SECBUFFER_EMPTY) + }; + SecBufferDesc message{ + SECBUFFER_VERSION, + ARRAYSIZE(inputBuffers), + inputBuffers + }; + auto status = EncryptMessage(&contextHandle, 0, &message, 0); + if (status != SEC_E_OK) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QSslSocket::tr("Schannel failed to encrypt data: %1") + .arg(schannelErrorToString(status))); + return; + } + // Data was encrypted successfully, so we free() what we peek()ed earlier + writeBuffer.free(size); + + // The trailer's size is not final, so resize fullMessage to not send trailing junk + 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()); +#endif + if (bytesWritten >= 0) { + totalBytesWritten += bytesWritten; + } else { + setErrorAndEmit(d, plainSocket->error(), plainSocket->errorString()); + return; + } + } + + if (totalBytesWritten > 0) { + // Don't emit bytesWritten() recursively. + bool &emittedBytesWritten = d->tlsEmittedBytesWritten(); + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(totalBytesWritten); + emittedBytesWritten = false; + } + emit q->channelBytesWritten(0, totalBytesWritten); + } + } + + 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)) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, "We're still missing %lld bytes, will check later.", missingData); +#endif + break; + } + + missingData = 0; + const qint64 bytesRead = readToBuffer(intermediateBuffer, plainSocket); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, "Read %lld encrypted bytes from the socket", bytesRead); +#endif + if (intermediateBuffer.length() == 0 || (hadIncompleteData && bytesRead == 0)) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, (hadIncompleteData ? "No new data received, leaving loop!" + : "Nothing to decrypt, leaving loop!")); +#endif + break; + } + hadIncompleteData = false; +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, "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(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()); +#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 (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!"); +#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 +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend, "The peer wants to renegotiate."); +#endif + schannelState = SchannelState::Renegotiate; + renegotiating = true; + + // 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); + } + } +} + +void TlsCryptographSchannel::sendShutdown() +{ + Q_ASSERT(d); + + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; + DWORD shutdownToken = SCHANNEL_SHUTDOWN; + SecBuffer buffer = createSecBuffer(&shutdownToken, sizeof(DWORD), SECBUFFER_TOKEN); + SecBufferDesc token{ + SECBUFFER_VERSION, + 1, + &buffer + }; + auto status = ApplyControlToken(&contextHandle, &token); + + if (status != SEC_E_OK) { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "Failed to apply shutdown control token:" << schannelErrorToString(status); +#endif + return; + } + + SecBuffer outBuffers[3]; + outBuffers[0] = createSecBuffer(nullptr, 0, SECBUFFER_TOKEN); + outBuffers[1] = createSecBuffer(nullptr, 0, SECBUFFER_ALERT); + outBuffers[2] = createSecBuffer(nullptr, 0, SECBUFFER_EMPTY); + auto freeBuffers = qScopeGuard([&outBuffers]() { + for (auto i = 0ull; i < ARRAYSIZE(outBuffers); i++) { + if (outBuffers[i].pvBuffer) + FreeContextBuffer(outBuffers[i].pvBuffer); + } + }); + SecBufferDesc outputBufferDesc{ + SECBUFFER_VERSION, + ARRAYSIZE(outBuffers), + outBuffers + }; + + ULONG contextReq = getContextRequirements(); + TimeStamp expiry; + if (isClient) { + status = InitializeSecurityContext(&credentialHandle, // phCredential + &contextHandle, // phContext + const_reinterpret_cast(targetName().utf16()), // pszTargetName + contextReq, // fContextReq + 0, // Reserved1 + 0, // TargetDataRep (unused) + nullptr, // pInput + 0, // Reserved2 + nullptr, // phNewContext (we already have one) + &outputBufferDesc, // pOutput + &contextAttributes, // pfContextAttr + &expiry // ptsExpiry + ); + } else { + status = AcceptSecurityContext( + &credentialHandle, // phCredential + &contextHandle, // phContext + nullptr, // pInput + contextReq, // fContextReq + 0, // TargetDataRep (unused) + nullptr, // phNewContext + &outputBufferDesc, // pOutput + &contextAttributes, // pfContextAttr, + &expiry // ptsTimeStamp + ); + } + if (status == SEC_E_OK || status == SEC_I_CONTEXT_EXPIRED) { + if (!sendToken(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, false)) { + // We failed to send the shutdown message, but it's not that important since we're + // shutting down anyway. + return; + } + } else { +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << "Failed to initialize shutdown:" << schannelErrorToString(status); +#endif + } +} + +void TlsCryptographSchannel::disconnectFromHost() +{ + Q_ASSERT(q); + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + 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) + plainSocket->disconnectFromHost(); +} + +void TlsCryptographSchannel::disconnected() +{ + Q_ASSERT(d); + + shutdown = true; + d->setEncrypted(false); + deallocateContext(); + freeCredentialsHandle(); +} + +QSslCipher TlsCryptographSchannel::sessionCipher() const +{ + Q_ASSERT(q); + + if (!q->isEncrypted()) + return QSslCipher(); + return QSslCipher(QStringLiteral("Schannel"), sessionProtocol()); +} + +QSsl::SslProtocol TlsCryptographSchannel::sessionProtocol() const +{ + if (!q->isEncrypted()) + return QSsl::SslProtocol::UnknownProtocol; + return toQtSslProtocol(connectionInfo.dwProtocol); +} + +void TlsCryptographSchannel::continueHandshake() +{ + Q_ASSERT(q); + Q_ASSERT(d); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + const bool isServer = d->tlsMode() == QSslSocket::SslServerMode; + switch (schannelState) { + case SchannelState::InitializeHandshake: + if (!SecIsValidHandle(&credentialHandle) && !acquireCredentialsHandle()) { + disconnectFromHost(); + return; + } + if (!SecIsValidHandle(&credentialHandle)) // Needed to support tst_QSslSocket::setEmptyKey + return; + if (!SecIsValidHandle(&contextHandle) && !(isServer ? acceptContext() : createContext())) { + disconnectFromHost(); + return; + } + if (schannelState != SchannelState::PerformHandshake) + break; + Q_FALLTHROUGH(); + case SchannelState::PerformHandshake: + if (!performHandshake()) { + disconnectFromHost(); + return; + } + if (schannelState != SchannelState::VerifyHandshake) + break; + Q_FALLTHROUGH(); + case SchannelState::VerifyHandshake: + // if we're in shutdown or renegotiating then we might not need to verify + // (since we already did) + if (!verifyHandshake()) { + shutdown = true; // Skip sending shutdown alert + q->abort(); // We don't want to send buffered data + disconnectFromHost(); + return; + } + if (schannelState != SchannelState::Done) + break; + Q_FALLTHROUGH(); + case SchannelState::Done: + // connectionEncrypted is already true if we come here from a renegotiation + if (!q->isEncrypted()) { + d->setEncrypted(true); // all is done + emit q->encrypted(); + } + renegotiating = false; + if (d->isPendingClose()) { + d->setPendingClose(false); + disconnectFromHost(); + } else { + transmit(); + } + break; + case SchannelState::Renegotiate: + if (!renegotiate()) { + disconnectFromHost(); + return; + } else if (intermediateBuffer.size() || plainSocket->bytesAvailable()) { + continueHandshake(); + } + break; + } +} + +QList TlsCryptographSchannel::tlsErrors() const +{ + return sslErrors; +} + +/* + Copied from qsslsocket_mac.cpp, which was copied from qsslsocket_openssl.cpp +*/ +bool TlsCryptographSchannel::checkSslErrors() +{ + if (sslErrors.isEmpty()) + return true; + + Q_ASSERT(q); + Q_ASSERT(d); + const auto &configuration = q->sslConfiguration(); + auto *plainSocket = d->plainTcpSocket(); + + emit q->sslErrors(sslErrors); + + const bool doVerifyPeer = configuration.peerVerifyMode() == QSslSocket::VerifyPeer + || (configuration.peerVerifyMode() == QSslSocket::AutoVerifyPeer + && d->tlsMode() == QSslSocket::SslClientMode); + const bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); + // check whether we need to emit an SSL handshake error + if (doVerifyPeer && doEmitSslError) { + if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { + QSslSocketPrivate::pauseSocketNotifiers(q); + d->setPaused(true); + } else { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + sslErrors.constFirst().errorString()); + plainSocket->disconnectFromHost(); + } + return false; + } + + return true; +} + +void TlsCryptographSchannel::initializeCertificateStores() +{ + //// helper function which turns a chain into a certificate store + Q_ASSERT(d); + Q_ASSERT(q); + const auto &configuration = q->sslConfiguration(); + + auto createStoreFromCertificateChain = [](const QList certChain, const QSslKey &privateKey) { + const wchar_t *passphrase = L""; + // Need to embed the private key in the certificate + QByteArray pkcs12 = _q_makePkcs12(certChain, + privateKey, + QString::fromWCharArray(passphrase, 0)); + CRYPT_DATA_BLOB pfxBlob; + pfxBlob.cbData = DWORD(pkcs12.length()); + pfxBlob.pbData = reinterpret_cast(pkcs12.data()); + return QHCertStorePointer(PFXImportCertStore(&pfxBlob, passphrase, 0)); + }; + + if (!configuration.localCertificateChain().isEmpty()) { + if (configuration.privateKey().isNull()) { + setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError, + QSslSocket::tr("Cannot provide a certificate with no key")); + return; + } + if (localCertificateStore == nullptr) { + localCertificateStore = createStoreFromCertificateChain(configuration.localCertificateChain(), + configuration.privateKey()); + if (localCertificateStore == nullptr) + qCWarning(lcTlsBackend, "Failed to load certificate chain!"); + } + } + + if (!configuration.caCertificates().isEmpty() && !caCertificateStore) { + caCertificateStore = createStoreFromCertificateChain(configuration.caCertificates(), + {}); // No private key for the CA certs + } +} + +bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext) +{ + Q_ASSERT(certContext); + Q_ASSERT(q); + Q_ASSERT(d); + + const bool isClient = d->tlsMode() == QSslSocket::SslClientMode; + + // Create a collection of stores so we can pass in multiple stores as additional locations to + // search for the certificate chain + auto tempCertCollection = QHCertStorePointer(CertOpenStore(CERT_STORE_PROV_COLLECTION, + X509_ASN_ENCODING, + 0, + CERT_STORE_CREATE_NEW_FLAG, + nullptr)); + if (!tempCertCollection) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend, "Failed to create certificate store collection!"); +#endif + return false; + } + + if (rootCertOnDemandLoadingAllowed()) { + // @future(maybe): following the OpenSSL backend these certificates should be added into + // 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")); + if (!rootStore) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend, "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!"); +#endif + return false; + } + } + 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!"); +#endif + return false; + } + } + + 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!"); +#endif + return false; + } + + CERT_CHAIN_PARA parameters; + ZeroMemory(¶meters, sizeof(parameters)); + parameters.cbSize = sizeof(CERT_CHAIN_PARA); + parameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; + parameters.RequestedUsage.Usage.cUsageIdentifier = 1; + LPSTR oid = LPSTR(isClient ? szOID_PKIX_KP_SERVER_AUTH + : szOID_PKIX_KP_CLIENT_AUTH); + parameters.RequestedUsage.Usage.rgpszUsageIdentifier = &oid; + + QTlsBackend::clearPeerCertificates(d); + const CERT_CHAIN_CONTEXT *chainContext = nullptr; + auto freeCertChain = qScopeGuard([&chainContext]() { + if (chainContext) + CertFreeCertificateChain(chainContext); + }); + BOOL status = CertGetCertificateChain(nullptr, // hChainEngine, default + certContext, // pCertContext + nullptr, // pTime, 'now' + tempCertCollection.get(), // hAdditionalStore, additional cert store + ¶meters, // pChainPara + CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, // dwFlags + nullptr, // reserved + &chainContext // ppChainContext + ); + if (status == FALSE || !chainContext || chainContext->cChain == 0) { + QSslError error(QSslError::UnableToVerifyFirstCertificate); + sslErrors += error; + emit q->peerVerifyError(error); + return q->state() == QAbstractSocket::ConnectedState; + } + + // Helper-function to get a QSslCertificate given a CERT_CHAIN_ELEMENT + static auto getCertificateFromChainElement = [](CERT_CHAIN_ELEMENT *element) { + if (!element) + return QSslCertificate(); + + const CERT_CONTEXT *certContext = element->pCertContext; + return QTlsPrivate::X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(certContext); + }; + + // Pick a chain to use as the certificate chain, if multiple are available: + // According to https://docs.microsoft.com/en-gb/windows/desktop/api/wincrypt/ns-wincrypt-_cert_chain_context + // this seems to be the best way to get a trusted chain. + CERT_SIMPLE_CHAIN *chain = chainContext->rgpChain[chainContext->cChain - 1]; + + if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN) { + auto error = QSslError(QSslError::SslError::UnableToGetIssuerCertificate, + getCertificateFromChainElement(chain->rgpElement[chain->cElement - 1])); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_INVALID_BASIC_CONSTRAINTS) { + // @Note: This is actually one of two errors: + // "either the certificate cannot be used to issue other certificates, or the chain path length has been exceeded." + // But here we are checking the chain's status, so we assume the "issuing" error cannot occur here. + auto error = QSslError(QSslError::PathLengthExceeded); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + static const DWORD leftoverCertChainErrorMask = CERT_TRUST_IS_CYCLIC | CERT_TRUST_INVALID_EXTENSION + | CERT_TRUST_INVALID_POLICY_CONSTRAINTS | CERT_TRUST_INVALID_NAME_CONSTRAINTS + | CERT_TRUST_CTL_IS_NOT_TIME_VALID | CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID + | CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE; + if (chain->TrustStatus.dwErrorStatus & leftoverCertChainErrorMask) { + auto error = QSslError(QSslError::SslError::UnspecifiedError); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + + DWORD verifyDepth = chain->cElement; + if (q->peerVerifyDepth() > 0 && DWORD(q->peerVerifyDepth()) < verifyDepth) + verifyDepth = DWORD(q->peerVerifyDepth()); + + const auto &caCertificates = q->sslConfiguration().caCertificates(); + QList peerCertificateChain; + for (DWORD i = 0; i < verifyDepth; i++) { + CERT_CHAIN_ELEMENT *element = chain->rgpElement[i]; + QSslCertificate certificate = getCertificateFromChainElement(element); + const QList 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; +#endif + + peerCertificateChain.append(certificate); + QTlsBackend::storePeerCertificateChain(d, peerCertificateChain); + + if (certificate.isBlacklisted()) { + const auto error = QSslError(QSslError::CertificateBlacklisted, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + + LONG result = CertVerifyTimeValidity(nullptr /*== now */, element->pCertContext->pCertInfo); + if (result != 0) { + auto error = QSslError(result == -1 ? QSslError::CertificateNotYetValid + : QSslError::CertificateExpired, + certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + + //// Errors + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_TIME_VALID) { + // handled right above + Q_ASSERT(!sslErrors.isEmpty()); + } + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) { + auto error = QSslError(QSslError::CertificateRevoked, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID) { + auto error = QSslError(QSslError::CertificateSignatureFailed, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + + // While netscape shouldn't be relevant now it defined an extension which is + // still in use. Schannel does not check this automatically, so we do it here. + // It is used to differentiate between client and server certificates. + if (netscapeWrongCertType(extensions, isClient)) + element->TrustStatus.dwErrorStatus |= CERT_TRUST_IS_NOT_VALID_FOR_USAGE; + + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_VALID_FOR_USAGE) { + auto error = QSslError(QSslError::InvalidPurpose, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_UNTRUSTED_ROOT) { + // Override this error if we have the certificate inside our trusted CAs list. + const bool isTrustedRoot = caCertificates.contains(certificate); + if (!isTrustedRoot) { + auto error = QSslError(QSslError::CertificateUntrusted, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + static const DWORD certRevocationCheckUnavailableError = CERT_TRUST_IS_OFFLINE_REVOCATION + | CERT_TRUST_REVOCATION_STATUS_UNKNOWN; + if (element->TrustStatus.dwErrorStatus & certRevocationCheckUnavailableError) { + // @future(maybe): Do something with this + } + + // Dumping ground of errors that don't fit our specific errors + static const DWORD leftoverCertErrorMask = CERT_TRUST_IS_CYCLIC + | CERT_TRUST_INVALID_EXTENSION | CERT_TRUST_INVALID_NAME_CONSTRAINTS + | CERT_TRUST_INVALID_POLICY_CONSTRAINTS + | CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT + | CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT + | CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT + | CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT + | CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT; + if (element->TrustStatus.dwErrorStatus & leftoverCertErrorMask) { + auto error = QSslError(QSslError::UnspecifiedError, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + 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"); + }); + if (it != extensions.cend()) { + // @Note: This is actually one of two errors: + // "either the certificate cannot be used to issue other certificates, + // or the chain path length has been exceeded." + QVariantMap basicConstraints = it->value().toMap(); + QSslError error; + if (i > 0 && !basicConstraints.value(QLatin1String("ca"), false).toBool()) + error = QSslError(QSslError::InvalidPurpose, certificate); + else + error = QSslError(QSslError::PathLengthExceeded, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_EXPLICIT_DISTRUST) { + auto error = QSslError(QSslError::CertificateBlacklisted, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + + if (element->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) { + // If it's self-signed *and* a CA then we can assume it's a root CA certificate + // and we can ignore the "self-signed" note: + // We check the basicConstraints certificate extension when possible, but this didn't + // exist for version 1, so we can only guess in that case + const bool isRootCertificateAuthority = isCertificateAuthority(extensions) + || certificate.version() == "1"; + + // Root certificate tends to be signed by themselves, so ignore self-signed status. + if (!isRootCertificateAuthority) { + auto error = QSslError(QSslError::SelfSignedCertificate, certificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + } + + if (!peerCertificateChain.isEmpty()) + QTlsBackend::storePeerCertificate(d, peerCertificateChain.first()); + + const auto &configuration = q->sslConfiguration(); // Probably, updated by QTlsBackend::storePeerCertificate etc. + // @Note: Somewhat copied from qsslsocket_mac.cpp + const bool doVerifyPeer = q->peerVerifyMode() == QSslSocket::VerifyPeer + || (q->peerVerifyMode() == QSslSocket::AutoVerifyPeer + && d->tlsMode() == QSslSocket::SslClientMode); + // Check the peer certificate itself. First try the subject's common name + // (CN) as a wildcard, then try all alternate subject name DNS entries the + // same way. + if (!configuration.peerCertificate().isNull()) { + // but only if we're a client connecting to a server + // if we're the server, don't check CN + if (d->tlsMode() == QSslSocket::SslClientMode) { + const auto verificationPeerName = d->verificationName(); + const QString peerName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); + if (!isMatchingHostname(configuration.peerCertificate(), peerName)) { + // No matches in common names or alternate names. + const QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate()); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + } else if (doVerifyPeer) { + // No peer certificate presented. Report as error if the socket + // expected one. + const QSslError error(QSslError::NoPeerCertificate); + sslErrors += error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + + return true; +} + +bool TlsCryptographSchannel::rootCertOnDemandLoadingAllowed() +{ + Q_ASSERT(d); + return d->isRootsOnDemandAllowed() && QSslSocketPrivate::rootCertOnDemandLoadingSupported(); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/schannel/qtls_schannel_p.h b/src/plugins/tls/schannel/qtls_schannel_p.h new file mode 100644 index 0000000000..08ba80c543 --- /dev/null +++ b/src/plugins/tls/schannel/qtls_schannel_p.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLS_SCHANNEL_P_H +#define QTLS_SCHANNEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_REQUIRE_CONFIG(schannel); + +#include "../shared/qwincrypt_p.h" + +#include "qtlsbackend_schannel_p.h" + +#include + +#define SECURITY_WIN32 +#define SCHANNEL_USE_BLACKLISTS 1 +#include // needed for UNICODE defines +#include +#include +#undef SCHANNEL_USE_BLACKLISTS +#undef SECURITY_WIN32 + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class TlsCryptographSchannel final : public TlsCryptograph +{ + Q_DISABLE_COPY_MOVE(TlsCryptographSchannel) +public: + TlsCryptographSchannel(); + ~TlsCryptographSchannel(); + + void init(QSslSocket *q, QSslSocketPrivate *d) override; + + void startClientEncryption() override; + void startServerEncryption() override; + void transmit() override; + void disconnectFromHost() override; + void disconnected() override; + QSslCipher sessionCipher() const override; + QSsl::SslProtocol sessionProtocol() const override; + void continueHandshake() override; + QList tlsErrors() const override; + +private: + enum class SchannelState { + InitializeHandshake, // create and transmit context (client)/accept context (server) + PerformHandshake, // get token back, process it + VerifyHandshake, // Verify that things are OK + Done, // Connection encrypted! + Renegotiate // Renegotiating! + } schannelState = SchannelState::InitializeHandshake; + + void reset(); + bool acquireCredentialsHandle(); + ULONG getContextRequirements(); + bool createContext(); // for clients + bool acceptContext(); // for server + bool performHandshake(); + bool verifyHandshake(); + bool renegotiate(); + + bool sendToken(void *token, unsigned long tokenLength, bool emitError = true); + QString targetName() const; + + bool checkSslErrors(); + void deallocateContext(); + void freeCredentialsHandle(); + void closeCertificateStores(); + void sendShutdown(); + + void initializeCertificateStores(); + bool verifyCertContext(CERT_CONTEXT *certContext); + + bool rootCertOnDemandLoadingAllowed(); + + bool hasUndecryptedData() const override { return intermediateBuffer.size() > 0; } + + QSslSocket *q = nullptr; + QSslSocketPrivate *d = nullptr; + + SecPkgContext_ConnectionInfo connectionInfo = {}; + SecPkgContext_StreamSizes streamSizes = {}; + + CredHandle credentialHandle; // Initialized in ctor + CtxtHandle contextHandle; // Initialized in ctor + + QByteArray intermediateBuffer; // data which is left-over or incomplete + + QHCertStorePointer localCertificateStore = nullptr; + QHCertStorePointer peerCertificateStore = nullptr; + QHCertStorePointer caCertificateStore = nullptr; + + const CERT_CONTEXT *localCertContext = nullptr; + + ULONG contextAttributes = 0; + qint64 missingData = 0; + + bool renegotiating = false; + bool shutdown = false; + QList sslErrors; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLS_SCHANNEL_P_H diff --git a/src/plugins/tls/schannel/qtlsbackend_schannel_p.h b/src/plugins/tls/schannel/qtlsbackend_schannel_p.h new file mode 100644 index 0000000000..e53cf17f13 --- /dev/null +++ b/src/plugins/tls/schannel/qtlsbackend_schannel_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLSBACKEND_ST_P_H +#define QTLSBACKEND_ST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include + + +QT_BEGIN_NAMESPACE + +class QSchannelBackend : public QTlsBackend +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QTlsBackend_iid) + Q_INTERFACES(QTlsBackend) + +public: + static void ensureInitializedImplementation(); + +private: + long tlsLibraryVersionNumber() const override; + QString tlsLibraryVersionString() const override; + long tlsLibraryBuildVersionNumber() const override; + QString tlsLibraryBuildVersionString() const override; + void ensureInitialized() const override; + + static void resetDefaultCiphers(); + + QString backendName() const override; + QList supportedProtocols() const override; + QList supportedFeatures() const override; + QList implementedClasses() const override; + + QTlsPrivate::TlsKey *createKey() const override; + QTlsPrivate::X509Certificate *createCertificate() const override; + + QTlsPrivate::TlsCryptograph * createTlsCryptograph() const override; + + QList systemCaCertificates() const override; + static QList systemCaCertificatesImplementation(); + + QTlsPrivate::X509PemReaderPtr X509PemReader() const override; + QTlsPrivate::X509DerReaderPtr X509DerReader() const override; + + static bool s_loadedCiphersAndCerts; +}; + +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 new file mode 100644 index 0000000000..e407da2ed3 --- /dev/null +++ b/src/plugins/tls/schannel/qtlskey_schannel.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include + +#include "qtlskey_schannel_p.h" + +#include "../shared/qwincrypt_p.h" + +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { +const wchar_t *getName(QSslKeyPrivate::Cipher cipher) +{ + switch (cipher) { + case QTlsPrivate::Cipher::DesCbc: + return BCRYPT_DES_ALGORITHM; + case QTlsPrivate::Cipher::DesEde3Cbc: + return BCRYPT_3DES_ALGORITHM; + case QTlsPrivate::Cipher::Rc2Cbc: + return BCRYPT_RC2_ALGORITHM; + case QTlsPrivate::Cipher::Aes128Cbc: + case QTlsPrivate::Cipher::Aes192Cbc: + case QTlsPrivate::Cipher::Aes256Cbc: + return BCRYPT_AES_ALGORITHM; + } + Q_UNREACHABLE(); +} + +BCRYPT_ALG_HANDLE getHandle(QSslKeyPrivate::Cipher cipher) +{ + BCRYPT_ALG_HANDLE handle; + NTSTATUS status = BCryptOpenAlgorithmProvider( + &handle, // phAlgorithm + getName(cipher), // pszAlgId + nullptr, // pszImplementation + 0 // dwFlags + ); + if (status < 0) { + qCWarning(lcTlsBackend, "Failed to open algorithm handle (%ld)!", status); + return nullptr; + } + + return handle; +} + +BCRYPT_KEY_HANDLE generateSymmetricKey(BCRYPT_ALG_HANDLE handle, + const QByteArray &key) +{ + BCRYPT_KEY_HANDLE keyHandle; + NTSTATUS status = BCryptGenerateSymmetricKey( + handle, // hAlgorithm + &keyHandle, // phKey + nullptr, // pbKeyObject (can ignore) + 0, // cbKeyObject (also ignoring) + reinterpret_cast(const_cast(key.data())), // pbSecret + ULONG(key.length()), // cbSecret + 0 // dwFlags + ); + if (status < 0) { + qCWarning(lcTlsBackend, "Failed to generate symmetric key (%ld)!", status); + return nullptr; + } + + status = BCryptSetProperty( + keyHandle, // hObject + BCRYPT_CHAINING_MODE, // pszProperty + reinterpret_cast(const_cast(BCRYPT_CHAIN_MODE_CBC)), // pbInput + ARRAYSIZE(BCRYPT_CHAIN_MODE_CBC), // cbInput + 0 // dwFlags + ); + if (status < 0) { + BCryptDestroyKey(keyHandle); + qCWarning(lcTlsBackend, "Failed to change the symmetric key's chaining mode (%ld)!", status); + return nullptr; + } + return keyHandle; +} + +QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, const QByteArray &data, const QByteArray &key, + const QByteArray &iv, bool encrypt) +{ + BCRYPT_ALG_HANDLE handle = getHandle(cipher); + if (!handle) + return {}; + auto handleDealloc = qScopeGuard([&handle]() { + BCryptCloseAlgorithmProvider(handle, 0); + }); + + BCRYPT_KEY_HANDLE keyHandle = generateSymmetricKey(handle, key); + if (!keyHandle) + return {}; + auto keyHandleDealloc = qScopeGuard([&keyHandle]() { + BCryptDestroyKey(keyHandle); + }); + + QByteArray ivCopy = iv; // This gets modified, so we take a copy + + ULONG sizeNeeded = 0; + QVarLengthArray output; + auto cryptFunction = encrypt ? BCryptEncrypt : BCryptDecrypt; + for (int i = 0; i < 2; i++) { + output.resize(int(sizeNeeded)); + auto input = reinterpret_cast(const_cast(data.data())); + // Need to call it twice because the first iteration lets us know the size needed. + NTSTATUS status = cryptFunction( + keyHandle, // hKey + input, // pbInput + ULONG(data.length()), // cbInput + nullptr, // pPaddingInfo + reinterpret_cast(ivCopy.data()), // pbIV + ULONG(ivCopy.length()), // cbIV + sizeNeeded ? output.data() : nullptr, // pbOutput + ULONG(output.length()), // cbOutput + &sizeNeeded, // pcbResult + BCRYPT_BLOCK_PADDING // dwFlags + ); + if (status < 0) { + qCWarning(lcTlsBackend, "%s failed (%ld)!", encrypt ? "Encrypt" : "Decrypt", status); + return {}; + } + } + + return QByteArray(reinterpret_cast(output.constData()), int(sizeNeeded)); +} +} // anonymous namespace + +namespace QTlsPrivate { + +QByteArray TlsKeySchannel::decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, + const QByteArray &iv) const +{ + return doCrypt(cipher, data, key, iv, false); +} + +QByteArray TlsKeySchannel::encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, + const QByteArray &iv) const +{ + return doCrypt(cipher, data, key, iv, true); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + diff --git a/src/plugins/tls/schannel/qtlskey_schannel_p.h b/src/plugins/tls/schannel/qtlskey_schannel_p.h new file mode 100644 index 0000000000..53c3b447ce --- /dev/null +++ b/src/plugins/tls/schannel/qtlskey_schannel_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLSKEY_SCHANNEL_P_H +#define QTLSKEY_SCHANNEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "../shared/qtlskey_generic_p.h" + +#include + +QT_REQUIRE_CONFIG(ssl); + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class TlsKeySchannel final : public TlsKeyGeneric +{ +public: + using TlsKeyGeneric::TlsKeyGeneric; + + QByteArray decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, + const QByteArray &iv) const override; + QByteArray encrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, + const QByteArray &iv) const override; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLSKEY_SCHANNEL_P_H + diff --git a/src/plugins/tls/schannel/qx509_schannel.cpp b/src/plugins/tls/schannel/qx509_schannel.cpp new file mode 100644 index 0000000000..01a21c69f5 --- /dev/null +++ b/src/plugins/tls/schannel/qx509_schannel.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qtlskey_schannel_p.h" +#include "qx509_schannel_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +X509CertificateSchannel::X509CertificateSchannel() = default; + +X509CertificateSchannel::~X509CertificateSchannel() +{ + if (certificateContext) + CertFreeCertificateContext(certificateContext); +} + +TlsKey *X509CertificateSchannel::publicKey() const +{ + auto key = std::make_unique(QSsl::PublicKey); + if (publicKeyAlgorithm != QSsl::Opaque) + key->decodeDer(QSsl::PublicKey, publicKeyAlgorithm, publicKeyDerData, {}, false); + + return key.release(); +} + +Qt::HANDLE X509CertificateSchannel::handle() const +{ + return Qt::HANDLE(certificateContext); +} + +QSslCertificate X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext) +{ + QByteArray derData = QByteArray((const char *)certificateContext->pbCertEncoded, + certificateContext->cbCertEncoded); + QSslCertificate certificate(derData, QSsl::Der); + + auto *certBackend = QTlsBackend::backend(certificate); + Q_ASSERT(certBackend); + certBackend->certificateContext = CertDuplicateCertificateContext(certificateContext); + return certificate; +} + +} // 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 new file mode 100644 index 0000000000..bf39229e96 --- /dev/null +++ b/src/plugins/tls/schannel/qx509_schannel_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QX509_SCHANNEL_P_H +#define QX509_SCHANNEL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "../shared/qx509_generic_p.h" +#include "../shared/qwincrypt_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class X509CertificateSchannel final : public X509CertificateGeneric +{ +public: + X509CertificateSchannel(); + ~X509CertificateSchannel(); + + TlsKey *publicKey() const override; + Qt::HANDLE handle() const override; + + static QSslCertificate QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext); +private: + const CERT_CONTEXT *certificateContext = nullptr; + + Q_DISABLE_COPY_MOVE(X509CertificateSchannel); +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QX509_SCHANNEL_P_H diff --git a/src/plugins/tls/securetransport/CMakeLists.txt b/src/plugins/tls/securetransport/CMakeLists.txt new file mode 100644 index 0000000000..db9101f43c --- /dev/null +++ b/src/plugins/tls/securetransport/CMakeLists.txt @@ -0,0 +1,33 @@ +qt_internal_add_plugin(QSecureTransportBackend + OUTPUT_NAME securetransportbackend + CLASS_NAME QSecureTransportBackend + TYPE tls + DEFAULT_IF APPLE + SOURCES + ../shared/qsslsocket_mac_shared.cpp + ../shared/qtlskey_generic_p.h + ../shared/qtlskey_generic.cpp + ../shared/qx509_base_p.h + ../shared/qx509_base.cpp + ../shared/qx509_generic_p.h + ../shared/qx509_generic.cpp + ../shared/qtlskey_base_p.h + ../shared/qtlskey_base.cpp + ../shared/qsslsocket_qt.cpp + ../shared/qasn1element_p.h + ../shared/qasn1element.cpp + qtlsbackend_st.cpp + qtlsbackend_st_p.h + qx509_st.cpp + qtlskey_st.cpp + qtlskey_st_p.h + qx509_st_p.h + qtls_st.cpp + qtls_st_p.h + PUBLIC_LIBRARIES + Qt::NetworkPrivate + Qt::CorePrivate + LIBRARIES + ${FWCoreFoundation} + ${FWSecurity} +) diff --git a/src/plugins/tls/securetransport/qtls_st.cpp b/src/plugins/tls/securetransport/qtls_st.cpp new file mode 100644 index 0000000000..306f184f25 --- /dev/null +++ b/src/plugins/tls/securetransport/qtls_st.cpp @@ -0,0 +1,1322 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2014 Jeremy Lainé +** 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$ +** +****************************************************************************/ + +#include "qtls_st_p.h" +#include "qtlsbackend_st_p.h" +#include "qtlskey_st_p.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifdef Q_OS_MACOS +#include +#endif + +QT_BEGIN_NAMESPACE + +// Defined in qsslsocket_qt.cpp. +QByteArray _q_makePkcs12(const QList &certs, const QSslKey &key, + const QString &passPhrase); + +namespace QTlsPrivate { + +// Defined in qtlsbackend_st.cpp +QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher); + +namespace { + +#ifdef Q_OS_MACOS +/* + +Our own temporarykeychain is needed only on macOS where SecPKCS12Import changes +the default keychain and where we see annoying pop-ups asking about accessing a +private key. + +*/ + +struct EphemeralSecKeychain +{ + EphemeralSecKeychain(); + ~EphemeralSecKeychain(); + + SecKeychainRef keychain = nullptr; + Q_DISABLE_COPY_MOVE(EphemeralSecKeychain) +}; + +EphemeralSecKeychain::EphemeralSecKeychain() +{ + const auto uuid = QUuid::createUuid(); + if (uuid.isNull()) { + qCWarning(lcTlsBackend) << "Failed to create a unique keychain name"; + return; + } + + const QByteArray uuidAsByteArray = uuid.toByteArray(); + Q_ASSERT(uuidAsByteArray.size() > 2); + Q_ASSERT(uuidAsByteArray.startsWith('{')); + Q_ASSERT(uuidAsByteArray.endsWith('}')); + const auto uuidAsString = QLatin1String(uuidAsByteArray.data(), uuidAsByteArray.size()).mid(1, uuidAsByteArray.size() - 2); + + const QString keychainName + = QDir::tempPath() + QDir::separator() + uuidAsString + QLatin1String(".keychain"); + // SecKeychainCreate, pathName parameter: + // + // "A constant character string representing the POSIX path indicating where + // to store the keychain." + // + // Internally they seem to use std::string, but this does not really help. + // Fortunately, CFString has a convenient API. + QCFType cfName = keychainName.toCFString(); + std::vector posixPath; + // "Extracts the contents of a string as a NULL-terminated 8-bit string + // appropriate for passing to POSIX APIs." + posixPath.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfName)); + const auto ok = CFStringGetFileSystemRepresentation(cfName, &posixPath[0], + CFIndex(posixPath.size())); + if (!ok) { + qCWarning(lcTlsBackend) << "Failed to create a unique keychain name from" + << "QDir::tempPath()"; + return; + } + + std::vector passUtf8(256); + if (SecRandomCopyBytes(kSecRandomDefault, passUtf8.size(), &passUtf8[0])) { + qCWarning(lcTlsBackend) << "SecRandomCopyBytes: failed to create a key"; + return; + } + + const OSStatus status = SecKeychainCreate(&posixPath[0], passUtf8.size(), + &passUtf8[0], FALSE, nullptr, + &keychain); + if (status != errSecSuccess || !keychain) { + qCWarning(lcTlsBackend) << "SecKeychainCreate: failed to create a custom keychain"; + if (keychain) { + SecKeychainDelete(keychain); + CFRelease(keychain); + keychain = nullptr; + } + } + + if (keychain) { + SecKeychainSettings settings = {}; + settings.version = SEC_KEYCHAIN_SETTINGS_VERS1; + // Strange, huh? But that's what their docs say to do! With lockOnSleep + // == false, set interval to INT_MAX to never lock ... + settings.lockInterval = INT_MAX; + if (SecKeychainSetSettings(keychain, &settings) != errSecSuccess) + qCWarning(lcTlsBackend) << "SecKeychainSettings: failed to disable lock on sleep"; + } + +#ifdef QSSLSOCKET_DEBUG + if (keychain) { + qCDebug(lcTlsBackend) << "Custom keychain with name" << keychainName << "was created" + << "successfully"; + } +#endif +} + +EphemeralSecKeychain::~EphemeralSecKeychain() +{ + if (keychain) { + // clear file off disk + SecKeychainDelete(keychain); + CFRelease(keychain); + } +} + +#endif // Q_OS_MACOS + +void qt_releaseSecureTransportContext(SSLContextRef context) +{ + if (context) + CFRelease(context); +} + +} // unnamed namespace + +// To be also used by qtlsbackend_st.cpp (thus not in unnamed namespace). +SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode) +{ + const bool isServer = mode == QSslSocket::SslServerMode; + const SSLProtocolSide side = isServer ? kSSLServerSide : kSSLClientSide; + // We never use kSSLDatagramType, so it's kSSLStreamType unconditionally. + SSLContextRef context = SSLCreateContext(nullptr, side, kSSLStreamType); + if (!context) + qCWarning(lcTlsBackend) << "SSLCreateContext failed"; + return context; +} + +QSecureTransportContext::QSecureTransportContext(SSLContextRef c) + : context(c) +{ +} + +QSecureTransportContext::~QSecureTransportContext() +{ + qt_releaseSecureTransportContext(context); +} + +QSecureTransportContext::operator SSLContextRef()const +{ + return context; +} + +void QSecureTransportContext::reset(SSLContextRef newContext) +{ + qt_releaseSecureTransportContext(context); + context = newContext; +} + +#if !defined(QT_PLATFORM_UIKIT) // dhparam is only used on macOS. (see the SSLSetDiffieHellmanParams call below) +static const uint8_t dhparam[] = + "\x30\x82\x01\x08\x02\x82\x01\x01\x00\x97\xea\xd0\x46\xf7\xae\xa7\x76\x80" + "\x9c\x74\x56\x98\xd8\x56\x97\x2b\x20\x6c\x77\xe2\x82\xbb\xc8\x84\xbe\xe7" + "\x63\xaf\xcc\x30\xd0\x67\x97\x7d\x1b\xab\x59\x30\xa9\x13\x67\x21\xd7\xd4" + "\x0e\x46\xcf\xe5\x80\xdf\xc9\xb9\xba\x54\x9b\x46\x2f\x3b\x45\xfc\x2f\xaf" + "\xad\xc0\x17\x56\xdd\x52\x42\x57\x45\x70\x14\xe5\xbe\x67\xaa\xde\x69\x75" + "\x30\x0d\xf9\xa2\xc4\x63\x4d\x7a\x39\xef\x14\x62\x18\x33\x44\xa1\xf9\xc1" + "\x52\xd1\xb6\x72\x21\x98\xf8\xab\x16\x1b\x7b\x37\x65\xe3\xc5\x11\x00\xf6" + "\x36\x1f\xd8\x5f\xd8\x9f\x43\xa8\xce\x9d\xbf\x5e\xd6\x2d\xfa\x0a\xc2\x01" + "\x54\xc2\xd9\x81\x54\x55\xb5\x26\xf8\x88\x37\xf5\xfe\xe0\xef\x4a\x34\x81" + "\xdc\x5a\xb3\x71\x46\x27\xe3\xcd\x24\xf6\x1b\xf1\xe2\x0f\xc2\xa1\x39\x53" + "\x5b\xc5\x38\x46\x8e\x67\x4c\xd9\xdd\xe4\x37\x06\x03\x16\xf1\x1d\x7a\xba" + "\x2d\xc1\xe4\x03\x1a\x58\xe5\x29\x5a\x29\x06\x69\x61\x7a\xd8\xa9\x05\x9f" + "\xc1\xa2\x45\x9c\x17\xad\x52\x69\x33\xdc\x18\x8d\x15\xa6\x5e\xcd\x94\xf4" + "\x45\xbb\x9f\xc2\x7b\x85\x00\x61\xb0\x1a\xdc\x3c\x86\xaa\x9f\x5c\x04\xb3" + "\x90\x0b\x35\x64\xff\xd9\xe3\xac\xf2\xf2\xeb\x3a\x63\x02\x01\x02"; +#endif + +OSStatus TlsCryptographSecureTransport::ReadCallback(TlsCryptographSecureTransport *socket, + char *data, size_t *dataLength) +{ + Q_ASSERT(socket); + Q_ASSERT(data); + Q_ASSERT(dataLength); + + Q_ASSERT(socket->d); + QTcpSocket *plainSocket = socket->d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + if (socket->isHandshakeComplete()) { + // Check if it's a renegotiation attempt, when the handshake is complete, the + // session state is 'kSSLConnected': + SSLSessionState currentState = kSSLConnected; + const OSStatus result = SSLGetSessionState(socket->context, ¤tState); + if (result != noErr) { + *dataLength = 0; + return result; + } + + if (currentState == kSSLHandshake) { + // Renegotiation detected, don't allow read more yet - 'transmit' + // will notice this and will call 'startHandshake': + *dataLength = 0; + socket->renegotiating = true; + return errSSLWouldBlock; + } + } + + const qint64 bytes = plainSocket->read(data, *dataLength); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "read" << bytes; +#endif + if (bytes < 0) { + *dataLength = 0; + return errSecIO; + } + + const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : errSecSuccess; + *dataLength = bytes; + + return err; +} + +OSStatus TlsCryptographSecureTransport::WriteCallback(TlsCryptographSecureTransport *socket, + const char *data, size_t *dataLength) +{ + Q_ASSERT(socket); + Q_ASSERT(data); + Q_ASSERT(dataLength); + + Q_ASSERT(socket->d); + QTcpSocket *plainSocket = socket->d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + const qint64 bytes = plainSocket->write(data, *dataLength); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "write" << bytes; +#endif + if (bytes < 0) { + *dataLength = 0; + return errSecIO; + } + + const OSStatus err = (size_t(bytes) < *dataLength) ? errSSLWouldBlock : errSecSuccess; + *dataLength = bytes; + + return err; +} + +TlsCryptographSecureTransport::TlsCryptographSecureTransport() + : context(nullptr) +{ +} + +TlsCryptographSecureTransport::~TlsCryptographSecureTransport() +{ + destroySslContext(); +} + +void TlsCryptographSecureTransport::init(QSslSocket *qObj, QSslSocketPrivate *dObj) +{ + Q_ASSERT(qObj); + Q_ASSERT(dObj); + q = qObj; + d = dObj; + + renegotiating = false; + shutdown = false; +} + +void TlsCryptographSecureTransport::continueHandshake() +{ + Q_ASSERT(q); + Q_ASSERT(d); + d->setEncrypted(true); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << d->plainTcpSocket() << "connection encrypted"; +#endif + +#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13_4, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0) + // Unlike OpenSSL, Secure Transport does not allow to negotiate protocols via + // a callback during handshake. We can only set our list of preferred protocols + // (and send it during handshake) and then receive what our peer has sent to us. + // And here we can finally try to find a match (if any). + const auto &configuration = q->sslConfiguration(); + if (__builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { + const auto &requestedProtocols = configuration.allowedNextProtocols(); + if (const int requestedCount = requestedProtocols.size()) { + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNone); + QTlsBackend::setNegotiatedProtocol(d, {}); + + QCFType cfArray; + const OSStatus result = SSLCopyALPNProtocols(context, &cfArray); + if (result == errSecSuccess && cfArray && CFArrayGetCount(cfArray)) { + const int size = CFArrayGetCount(cfArray); + QList peerProtocols(size); + for (int i = 0; i < size; ++i) + peerProtocols[i] = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(cfArray, i)); + + for (int i = 0; i < requestedCount; ++i) { + const auto requestedName = QString::fromLatin1(requestedProtocols[i]); + for (int j = 0; j < size; ++j) { + if (requestedName == peerProtocols[j]) { + QTlsBackend::setNegotiatedProtocol(d, requestedName.toLatin1()); + QTlsBackend::setAlpnStatus(d, QSslConfiguration::NextProtocolNegotiationNegotiated); + break; + } + } + if (configuration.nextProtocolNegotiationStatus() == QSslConfiguration::NextProtocolNegotiationNegotiated) + break; + } + } + } + } +#endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE + + if (!renegotiating) + emit q->encrypted(); + + if (d->isAutoStartingHandshake() && d->isPendingClose()) { + d->setPendingClose(false); + q->disconnectFromHost(); + } +} + +void TlsCryptographSecureTransport::disconnected() +{ + Q_ASSERT(d && d->plainTcpSocket()); + if (d->plainTcpSocket()->bytesAvailable() <= 0) + destroySslContext(); + // If there is still buffered data in the plain socket, don't destroy the ssl context yet. + // It will be destroyed when the socket is deleted. +} + +void TlsCryptographSecureTransport::disconnectFromHost() +{ + Q_ASSERT(d && d->plainTcpSocket()); + if (context) { + if (!shutdown) { + SSLClose(context); + shutdown = true; + } + } + d->plainTcpSocket()->disconnectFromHost(); +} + +QSslCipher TlsCryptographSecureTransport::sessionCipher() const +{ + SSLCipherSuite cipher = 0; + if (context && SSLGetNegotiatedCipher(context, &cipher) == errSecSuccess) + return QSslCipher_from_SSLCipherSuite(cipher); + + return QSslCipher(); +} + +QSsl::SslProtocol TlsCryptographSecureTransport::sessionProtocol() const +{ + if (!context) + return QSsl::UnknownProtocol; + + SSLProtocol protocol = kSSLProtocolUnknown; + const OSStatus err = SSLGetNegotiatedProtocolVersion(context, &protocol); + if (err != errSecSuccess) { + qCWarning(lcTlsBackend) << "SSLGetNegotiatedProtocolVersion failed:" << err; + return QSsl::UnknownProtocol; + } + + switch (protocol) { + case kTLSProtocol1: + return QSsl::TlsV1_0; + case kTLSProtocol11: + return QSsl::TlsV1_1; + case kTLSProtocol12: + return QSsl::TlsV1_2; + case kTLSProtocol13: + return QSsl::TlsV1_3; + default: + return QSsl::UnknownProtocol; + } +} + +void TlsCryptographSecureTransport::startClientEncryption() +{ + if (!initSslContext()) { + Q_ASSERT(d); + // Error description/code were set, 'error' emitted + // by initSslContext, but OpenSSL socket also sets error, + // emits a signal twice, so ... + setErrorAndEmit(d, QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); + return; + } + + startHandshake(); +} + +void TlsCryptographSecureTransport::startServerEncryption() +{ + if (!initSslContext()) { + // Error description/code were set, 'error' emitted + // by initSslContext, but OpenSSL socket also sets error + // emits a signal twice, so ... + setErrorAndEmit(d, QAbstractSocket::SslInternalError, QStringLiteral("Unable to init SSL Context")); + return; + } + + startHandshake(); +} + +void TlsCryptographSecureTransport::transmit() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + // If we don't have any SSL context, don't bother transmitting. + // Edit: if SSL session closed, don't bother either. + if (!context || shutdown) + return; + + if (!isHandshakeComplete()) + startHandshake(); + + auto &writeBuffer = d->tlsWriteBuffer(); + if (isHandshakeComplete() && !writeBuffer.isEmpty()) { + qint64 totalBytesWritten = 0; + while (writeBuffer.nextDataBlockSize() > 0 && context) { + const size_t nextDataBlockSize = writeBuffer.nextDataBlockSize(); + size_t writtenBytes = 0; + const OSStatus err = SSLWrite(context, writeBuffer.readPointer(), nextDataBlockSize, &writtenBytes); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << d->plainTcpSocket() << "SSLWrite returned" << err; +#endif + if (err != errSecSuccess && err != errSSLWouldBlock) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QStringLiteral("SSLWrite failed: %1").arg(err)); + break; + } + + if (writtenBytes) { + writeBuffer.free(writtenBytes); + totalBytesWritten += writtenBytes; + } + + if (writtenBytes < nextDataBlockSize) + break; + } + + if (totalBytesWritten > 0) { + // Don't emit bytesWritten() recursively. + auto &emittedBytesWritten = d->tlsEmittedBytesWritten(); + if (!emittedBytesWritten) { + emittedBytesWritten = true; + emit q->bytesWritten(totalBytesWritten); + emittedBytesWritten = false; + } + emit q->channelBytesWritten(0, totalBytesWritten); + } + } + + auto &buffer = d->tlsBuffer(); + const auto readBufferMaxSize = d->maxReadBufferSize(); + if (isHandshakeComplete()) { + QVarLengthArray data; + while (context && (!readBufferMaxSize || buffer.size() < readBufferMaxSize)) { + size_t readBytes = 0; + data.resize(4096); + const OSStatus err = SSLRead(context, data.data(), data.size(), &readBytes); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << d->plainTcpSocket() << "SSLRead returned" << err; +#endif + if (err == errSSLClosedGraceful) { + shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves + setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); + break; + } else if (err != errSecSuccess && err != errSSLWouldBlock) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QStringLiteral("SSLRead failed: %1").arg(err)); + break; + } + + if (err == errSSLWouldBlock && renegotiating) { + startHandshake(); + break; + } + + if (readBytes) { + buffer.append(data.constData(), readBytes); + if (bool *readyReadEmittedPointer = d->readyReadPointer()) + *readyReadEmittedPointer = true; + emit q->readyRead(); + emit q->channelReadyRead(0); + } + + if (err == errSSLWouldBlock) + break; + } + } +} + +SSLCipherSuite TlsCryptographSecureTransport::SSLCipherSuite_from_QSslCipher(const QSslCipher &ciph) +{ + if (ciph.name() == QLatin1String("AES128-SHA")) + return TLS_RSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("DHE-RSA-AES128-SHA")) + return TLS_DHE_RSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("AES256-SHA")) + return TLS_RSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("DHE-RSA-AES256-SHA")) + return TLS_DHE_RSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-NULL-SHA")) + return TLS_ECDH_ECDSA_WITH_NULL_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-RC4-SHA")) + return TLS_ECDH_ECDSA_WITH_RC4_128_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) + return TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA")) + return TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA")) + return TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-RC4-SHA")) + return TLS_ECDHE_ECDSA_WITH_RC4_128_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-DES-CBC3-SHA")) + return TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA")) + return TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA")) + return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-NULL-SHA")) + return TLS_ECDH_RSA_WITH_NULL_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-RC4-SHA")) + return TLS_ECDH_RSA_WITH_RC4_128_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) + return TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-AES128-SHA")) + return TLS_ECDH_RSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-AES256-SHA")) + return TLS_ECDH_RSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-RC4-SHA")) + return TLS_ECDHE_RSA_WITH_RC4_128_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-DES-CBC3-SHA")) + return TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-AES128-SHA")) + return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA; + if (ciph.name() == QLatin1String("ECDH-RSA-AES256-SHA")) + return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA; + if (ciph.name() == QLatin1String("DES-CBC3-SHA")) + return TLS_RSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("AES128-SHA256")) + return TLS_RSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("AES256-SHA256")) + return TLS_RSA_WITH_AES_256_CBC_SHA256; + if (ciph.name() == QLatin1String("DHE-RSA-DES-CBC3-SHA")) + return TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; + if (ciph.name() == QLatin1String("DHE-RSA-AES128-SHA256")) + return TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("DHE-RSA-AES256-SHA256")) + return TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; + if (ciph.name() == QLatin1String("AES256-GCM-SHA384")) + return TLS_RSA_WITH_AES_256_GCM_SHA384; + if (ciph.name() == QLatin1String("ECDHE-ECDSA-AES128-SHA256")) + return TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("ECDHE-ECDSA-AES256-SHA384")) + return TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES128-SHA256")) + return TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("ECDH-ECDSA-AES256-SHA384")) + return TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; + if (ciph.name() == QLatin1String("ECDHE-RSA-AES128-SHA256")) + return TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-SHA384")) + return TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; + if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-SHA384")) + return TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; + if (ciph.name() == QLatin1String("ECDHE-RSA-AES256-GCM-SHA384")) + return TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; + return 0; +} + +bool TlsCryptographSecureTransport::initSslContext() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + Q_ASSERT_X(!context, Q_FUNC_INFO, "invalid socket state, context is not null"); + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + const auto mode = d->tlsMode(); + + context.reset(qt_createSecureTransportContext(mode)); + if (!context) { + setErrorAndEmit(d, QAbstractSocket::SslInternalError, QStringLiteral("SSLCreateContext failed")); + return false; + } + + const OSStatus err = SSLSetIOFuncs(context, + reinterpret_cast(&TlsCryptographSecureTransport::ReadCallback), + reinterpret_cast(&TlsCryptographSecureTransport::WriteCallback)); + if (err != errSecSuccess) { + destroySslContext(); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QStringLiteral("SSLSetIOFuncs failed: %1").arg(err)); + return false; + } + + SSLSetConnection(context, this); + + const auto &configuration = q->sslConfiguration(); + if (mode == QSslSocket::SslServerMode + && !configuration.localCertificateChain().isEmpty()) { + QString errorDescription; + QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; + if (!setSessionCertificate(errorDescription, errorCode)) { + destroySslContext(); + setErrorAndEmit(d, errorCode, errorDescription); + return false; + } + } + + if (!setSessionProtocol()) { + destroySslContext(); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, QStringLiteral("Failed to set protocol version")); + return false; + } + +#if QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13_4, __IPHONE_11_0, __TVOS_11_0, __WATCHOS_4_0) + if (__builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)) { + const auto protocolNames = configuration.allowedNextProtocols(); + QCFType cfNames(CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks)); + if (cfNames) { + for (const QByteArray &name : protocolNames) { + if (name.size() > 255) { + qCWarning(lcTlsBackend) << "TLS ALPN extension" << name + << "is too long and will be ignored."; + continue; + } else if (name.isEmpty()) { + continue; + } + QCFString cfName(QString::fromLatin1(name).toCFString()); + CFArrayAppendValue(cfNames, cfName); + } + + if (CFArrayGetCount(cfNames)) { + // Up to the application layer to check that negotiation + // failed, and handle this non-TLS error, we do not handle + // the result of this call as an error: + if (SSLSetALPNProtocols(context, cfNames) != errSecSuccess) + qCWarning(lcTlsBackend) << "SSLSetALPNProtocols failed - too long protocol names?"; + } + } else { + qCWarning(lcTlsBackend) << "failed to allocate ALPN names array"; + } + } +#endif // QT_DARWIN_PLATFORM_SDK_EQUAL_OR_ABOVE + + if (mode == QSslSocket::SslClientMode) { + // enable Server Name Indication (SNI) + const auto verificationPeerName = d->verificationName(); + QString tlsHostName(verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName); + if (tlsHostName.isEmpty()) + tlsHostName = d->tlsHostName(); + + const QByteArray ace(QUrl::toAce(tlsHostName)); + SSLSetPeerDomainName(context, ace.data(), ace.size()); + // tell SecureTransport we handle peer verification ourselves + OSStatus err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true); + if (err == errSecSuccess) + err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnCertRequested, true); + + if (err != errSecSuccess) { + destroySslContext(); + setErrorAndEmit(d, QSslSocket::SslInternalError, + QStringLiteral("SSLSetSessionOption failed: %1").arg(err)); + return false; + } + // + } else { + if (configuration.peerVerifyMode() != QSslSocket::VerifyNone) { + // kAlwaysAuthenticate - always fails even if we set break on client auth. + OSStatus err = SSLSetClientSideAuthenticate(context, kTryAuthenticate); + if (err == errSecSuccess) { + // We'd like to verify peer ourselves, otherwise handshake will + // most probably fail before we can do anything. + err = SSLSetSessionOption(context, kSSLSessionOptionBreakOnClientAuth, true); + } + + if (err != errSecSuccess) { + destroySslContext(); + setErrorAndEmit(d, QAbstractSocket::SslInternalError, + QStringLiteral("failed to set SSL context option in server mode: %1").arg(err)); + return false; + } + } +#if !defined(QT_PLATFORM_UIKIT) + // No SSLSetDiffieHellmanParams on iOS; calling it is optional according to docs. + SSLSetDiffieHellmanParams(context, dhparam, sizeof(dhparam)); +#endif + } + if (configuration.ciphers().size() > 0) { + QVector cfCiphers; + for (const QSslCipher &cipher : configuration.ciphers()) { + if (auto sslCipher = TlsCryptographSecureTransport::SSLCipherSuite_from_QSslCipher(cipher)) + cfCiphers << sslCipher; + } + if (cfCiphers.size() == 0) { + qCWarning(lcTlsBackend) << "failed to add any of the requested ciphers from the configuration"; + return false; + } + OSStatus err = SSLSetEnabledCiphers(context, cfCiphers.data(), cfCiphers.size()); + if (err != errSecSuccess) { + qCWarning(lcTlsBackend) << "failed to set the ciphers from the configuration"; + return false; + } + } + return true; +} + +void TlsCryptographSecureTransport::destroySslContext() +{ + context.reset(nullptr); +} + +bool TlsCryptographSecureTransport::setSessionCertificate(QString &errorDescription, QAbstractSocket::SocketError &errorCode) +{ + Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); + + Q_ASSERT(d); + const auto &configuration = q->sslConfiguration(); + +#ifdef QSSLSOCKET_DEBUG + auto *plainSocket = d->plainTcpSocket(); +#endif + + QSslCertificate localCertificate; + + if (!configuration.localCertificateChain().isEmpty()) + localCertificate = configuration.localCertificateChain().at(0); + + if (!localCertificate.isNull()) { + // Require a private key as well. + if (configuration.privateKey().isNull()) { + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("Cannot provide a certificate with no key"); + return false; + } + + // import certificates and key + const QString passPhrase(QString::fromLatin1("foobar")); + QCFType pkcs12 = _q_makePkcs12(configuration.localCertificateChain(), + configuration.privateKey(), passPhrase).toCFData(); + QCFType password = passPhrase.toCFString(); + const void *keys[2] = { kSecImportExportPassphrase }; + const void *values[2] = { password }; + CFIndex nKeys = 1; +#ifdef Q_OS_MACOS + bool envOk = false; + const int env = qEnvironmentVariableIntValue("QT_SSL_USE_TEMPORARY_KEYCHAIN", &envOk); + if (envOk && env) { + static const EphemeralSecKeychain temporaryKeychain; + if (temporaryKeychain.keychain) { + nKeys = 2; + keys[1] = kSecImportExportKeychain; + values[1] = temporaryKeychain.keychain; + } + } +#endif + QCFType options = CFDictionaryCreate(nullptr, keys, values, nKeys, + nullptr, nullptr); + QCFType items; + OSStatus err = SecPKCS12Import(pkcs12, options, &items); + if (err != errSecSuccess) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend) << plainSocket + << QStringLiteral("SecPKCS12Import failed: %1").arg(err); +#endif + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("SecPKCS12Import failed: %1").arg(err); + return false; + } + + if (!CFArrayGetCount(items)) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend) << plainSocket << "SecPKCS12Import returned no items"; +#endif + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("SecPKCS12Import returned no items"); + return false; + } + + CFDictionaryRef import = (CFDictionaryRef)CFArrayGetValueAtIndex(items, 0); + SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(import, kSecImportItemIdentity); + if (!identity) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend) << plainSocket << "SecPKCS12Import returned no identity"; +#endif + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("SecPKCS12Import returned no identity"); + return false; + } + + QCFType certs = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); + if (!certs) { + errorCode = QAbstractSocket::SslInternalError; + errorDescription = QStringLiteral("Failed to allocate certificates array"); + return false; + } + + CFArrayAppendValue(certs, identity); + + CFArrayRef chain = (CFArrayRef)CFDictionaryGetValue(import, kSecImportItemCertChain); + if (chain) { + for (CFIndex i = 1, e = CFArrayGetCount(chain); i < e; ++i) + CFArrayAppendValue(certs, CFArrayGetValueAtIndex(chain, i)); + } + + err = SSLSetCertificate(context, certs); + if (err != errSecSuccess) { +#ifdef QSSLSOCKET_DEBUG + qCWarning(lcTlsBackend) + << plainSocket << QStringLiteral("Cannot set certificate and key: %1").arg(err); +#endif + errorCode = QAbstractSocket::SslInvalidUserDataError; + errorDescription = QStringLiteral("Cannot set certificate and key: %1").arg(err); + return false; + } + } + + return true; +} + +bool TlsCryptographSecureTransport::setSessionProtocol() +{ + Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); + Q_ASSERT(q); + Q_ASSERT(d); + // SecureTransport has kTLSProtocol13 constant and also, kTLSProtocolMaxSupported. + // Calling SSLSetProtocolVersionMax/Min with any of these two constants results + // in errInvalidParam and a failure to set the protocol version. This means + // no TLS 1.3 on macOS and iOS. + const auto &configuration = q->sslConfiguration(); + auto *plainSocket = d->plainTcpSocket(); + switch (configuration.protocol()) { + case QSsl::TlsV1_3: + case QSsl::TlsV1_3OrLater: + qCWarning(lcTlsBackend) << plainSocket << "SecureTransport does not support TLS 1.3"; + return false; + default:; + } + + OSStatus err = errSecSuccess; + + if (configuration.protocol() == QSsl::TlsV1_0) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.0"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol1); + if (err == errSecSuccess) + err = SSLSetProtocolVersionMax(context, kTLSProtocol1); + } else if (configuration.protocol() == QSsl::TlsV1_1) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.1"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol11); + if (err == errSecSuccess) + err = SSLSetProtocolVersionMax(context, kTLSProtocol11); + } else if (configuration.protocol() == QSsl::TlsV1_2) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol12); + if (err == errSecSuccess) + err = SSLSetProtocolVersionMax(context, kTLSProtocol12); + } else if (configuration.protocol() == QSsl::AnyProtocol) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : any"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol1); + } else if (configuration.protocol() == QSsl::SecureProtocols) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1 - TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol1); + } else if (configuration.protocol() == QSsl::TlsV1_0OrLater) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1 - TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol1); + } else if (configuration.protocol() == QSsl::TlsV1_1OrLater) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.1 - TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol11); + } else if (configuration.protocol() == QSsl::TlsV1_2OrLater) { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "requesting : TLSv1.2"; + #endif + err = SSLSetProtocolVersionMin(context, kTLSProtocol12); + } else { + #ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "no protocol version found in the configuration"; + #endif + return false; + } + + return err == errSecSuccess; +} + +bool TlsCryptographSecureTransport::canIgnoreTrustVerificationFailure() const +{ + Q_ASSERT(q); + Q_ASSERT(d); + const auto &configuration = q->sslConfiguration(); + const QSslSocket::PeerVerifyMode verifyMode = configuration.peerVerifyMode(); + return d->tlsMode() == QSslSocket::SslServerMode + && (verifyMode == QSslSocket::QueryPeer + || verifyMode == QSslSocket::AutoVerifyPeer + || verifyMode == QSslSocket::VerifyNone); +} + +bool TlsCryptographSecureTransport::verifySessionProtocol() const +{ + Q_ASSERT(q); + + const auto &configuration = q->sslConfiguration(); + bool protocolOk = false; + if (configuration.protocol() == QSsl::AnyProtocol) + protocolOk = true; + else if (configuration.protocol() == QSsl::SecureProtocols) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_0); + else if (configuration.protocol() == QSsl::TlsV1_0OrLater) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_0); + else if (configuration.protocol() == QSsl::TlsV1_1OrLater) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_1); + else if (configuration.protocol() == QSsl::TlsV1_2OrLater) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_2); + else if (configuration.protocol() == QSsl::TlsV1_3OrLater) + protocolOk = (sessionProtocol() >= QSsl::TlsV1_3OrLater); + else + protocolOk = (sessionProtocol() == configuration.protocol()); + + return protocolOk; +} + +bool TlsCryptographSecureTransport::verifyPeerTrust() +{ + Q_ASSERT(q); + Q_ASSERT(d); + + const auto mode = d->tlsMode(); + const QSslSocket::PeerVerifyMode verifyMode = q->peerVerifyMode(); + const bool canIgnoreVerify = canIgnoreTrustVerificationFailure(); + + Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)"); + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + + QCFType trust; + OSStatus err = SSLCopyPeerTrust(context, &trust); + // !trust - SSLCopyPeerTrust can return errSecSuccess but null trust. + if (err != errSecSuccess || !trust) { + if (!canIgnoreVerify) { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("Failed to obtain peer trust: %1").arg(err)); + plainSocket->disconnectFromHost(); + return false; + } else { + return true; + } + } + + QList errors; + + // Store certificates. + // Apple's docs say SetTrustEvaluate must be called before + // SecTrustGetCertificateAtIndex, but this results + // in 'kSecTrustResultRecoverableTrustFailure', so + // here we just ignore 'res' (later we'll use SetAnchor etc. + // and evaluate again). + SecTrustResultType res = kSecTrustResultInvalid; + err = SecTrustEvaluate(trust, &res); + if (err != errSecSuccess) { + // We can not ignore this, it's not even about trust verification + // probably ... + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("SecTrustEvaluate failed: %1").arg(err)); + plainSocket->disconnectFromHost(); + return false; + } + + QTlsBackend::clearPeerCertificates(d); + + QList peerCertificateChain; + const CFIndex certCount = SecTrustGetCertificateCount(trust); + for (CFIndex i = 0; i < certCount; ++i) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); + QCFType derData = SecCertificateCopyData(cert); + peerCertificateChain << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); + } + QTlsBackend::storePeerCertificateChain(d, peerCertificateChain); + + if (peerCertificateChain.size()) + QTlsBackend::storePeerCertificate(d, peerCertificateChain.at(0)); + + // Check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer): + for (const QSslCertificate &cert : qAsConst(peerCertificateChain)) { + if (QSslCertificatePrivate::isBlacklisted(cert) && !canIgnoreVerify) { + const QSslError error(QSslError::CertificateBlacklisted, cert); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + const bool doVerifyPeer = verifyMode == QSslSocket::VerifyPeer + || (verifyMode == QSslSocket::AutoVerifyPeer + && d->tlsMode() == QSslSocket::SslClientMode); + // Check the peer certificate itself. First try the subject's common name + // (CN) as a wildcard, then try all alternate subject name DNS entries the + // same way. + const auto &peerCertificate = q->peerCertificate(); + if (!peerCertificate.isNull()) { + // but only if we're a client connecting to a server + // if we're the server, don't check CN + const QString verificationPeerName = d->verificationName(); + if (mode == QSslSocket::SslClientMode) { + const QString peerName(verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); + if (!isMatchingHostname(peerCertificate, peerName) && !canIgnoreVerify) { + // No matches in common names or alternate names. + const QSslError error(QSslError::HostNameMismatch, peerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + } else { + // No peer certificate presented. Report as error if the socket + // expected one. + if (doVerifyPeer && !canIgnoreVerify) { + const QSslError error(QSslError::NoPeerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; + } + } + + // verify certificate chain + QCFType certArray = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); + const auto &caCertificates = q->sslConfiguration().caCertificates(); + for (const QSslCertificate &cert : caCertificates) { + QCFType certData = cert.toDer().toCFData(); + if (QCFType secRef = SecCertificateCreateWithData(nullptr, certData)) + CFArrayAppendValue(certArray, secRef); + else + qCWarning(lcTlsBackend, "Failed to create SecCertificate from QSslCertificate"); + } + + SecTrustSetAnchorCertificates(trust, certArray); + + // By default SecTrustEvaluate uses both CA certificates provided in + // QSslConfiguration and the ones from the system database. This behavior can + // be unexpected if a user's code tries to limit the trusted CAs to those + // explicitly set in QSslConfiguration. + // Since on macOS we initialize the default QSslConfiguration copying the + // system CA certificates (using SecTrustSettingsCopyCertificates) we can + // call SecTrustSetAnchorCertificatesOnly(trust, true) to force SecTrustEvaluate + // to use anchors only from our QSslConfiguration. + // Unfortunately, SecTrustSettingsCopyCertificates is not available on iOS + // and the default QSslConfiguration always has an empty list of system CA + // certificates. This leaves no way to provide client code with access to the + // actual system CA certificate list (which most use-cases need) other than + // by letting SecTrustEvaluate fall through to the system list; so, in this case + // (even though the client code may have provided its own certs), we retain + // the default behavior. Note, with macOS SDK below 10.12 using 'trust my + // anchors only' may result in some valid chains rejected, apparently the + // ones containing intermediated certificates; so we use this functionality + // on more recent versions only. + + bool anchorsFromConfigurationOnly = false; + +#ifdef Q_OS_MACOS + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSSierra) + anchorsFromConfigurationOnly = true; +#endif // Q_OS_MACOS + + SecTrustSetAnchorCertificatesOnly(trust, anchorsFromConfigurationOnly); + + SecTrustResultType trustResult = kSecTrustResultInvalid; + SecTrustEvaluate(trust, &trustResult); + switch (trustResult) { + case kSecTrustResultUnspecified: + case kSecTrustResultProceed: + break; + default: + if (!canIgnoreVerify) { + const QSslError error(QSslError::CertificateUntrusted, peerCertificate); + errors << error; + emit q->peerVerifyError(error); + } + } + + // report errors + if (!errors.isEmpty() && !canIgnoreVerify) { + sslErrors = errors; + // checkSslErrors unconditionally emits sslErrors: + // a user's slot can abort/close/disconnect on this + // signal, so we also test the socket's state: + if (!checkSslErrors() || q->state() != QAbstractSocket::ConnectedState) + return false; + } else { + sslErrors.clear(); + } + + return true; +} + +/* + Copied verbatim from qsslsocket_openssl.cpp +*/ +bool TlsCryptographSecureTransport::checkSslErrors() +{ + if (sslErrors.isEmpty()) + return true; + + Q_ASSERT(q); + Q_ASSERT(d); + + emit q->sslErrors(sslErrors); + const auto mode = d->tlsMode(); + const auto &configuration = q->sslConfiguration(); + const bool doVerifyPeer = configuration.peerVerifyMode() == QSslSocket::VerifyPeer + || (configuration.peerVerifyMode() == QSslSocket::AutoVerifyPeer + && mode == QSslSocket::SslClientMode); + const bool doEmitSslError = !d->verifyErrorsHaveBeenIgnored(); + // check whether we need to emit an SSL handshake error + if (doVerifyPeer && doEmitSslError) { + if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) { + QSslSocketPrivate::pauseSocketNotifiers(q); + d->setPaused(true); + } else { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + sslErrors.constFirst().errorString()); + Q_ASSERT(d->plainTcpSocket()); + d->plainTcpSocket()->disconnectFromHost(); + } + return false; + } + + return true; +} + +bool TlsCryptographSecureTransport::startHandshake() +{ + Q_ASSERT(context); + Q_ASSERT(q); + Q_ASSERT(d); + + auto *plainSocket = d->plainTcpSocket(); + Q_ASSERT(plainSocket); + const auto mode = d->tlsMode(); + + OSStatus err = SSLHandshake(context); +#ifdef QSSLSOCKET_DEBUG + qCDebug(lcTlsBackend) << plainSocket << "SSLHandhake returned" << err; +#endif + + if (err == errSSLWouldBlock) { + // startHandshake has to be called again ... later. + return false; + } else if (err == errSSLServerAuthCompleted) { + // errSSLServerAuthCompleted is a define for errSSLPeerAuthCompleted, + // it works for both server/client modes. + // In future we'll evaluate peer's trust at this point, + // for now we just continue. + // if (!verifyPeerTrust()) + // ... + return startHandshake(); + } else if (err == errSSLClientCertRequested) { + Q_ASSERT(mode == QSslSocket::SslClientMode); + QString errorDescription; + QAbstractSocket::SocketError errorCode = QAbstractSocket::UnknownSocketError; + // setSessionCertificate does not fail if we have no certificate. + // Failure means a real error (invalid certificate, no private key, etc). + if (!setSessionCertificate(errorDescription, errorCode)) { + setErrorAndEmit(d, errorCode, errorDescription); + renegotiating = false; + return false; + } else { + // We try to resume a handshake, even if have no + // local certificates ... (up to server to deal with our failure). + return startHandshake(); + } + } else if (err != errSecSuccess) { + if (err == errSSLBadCert && canIgnoreTrustVerificationFailure()) { + // We're on the server side and client did not provide any + // certificate. This is the new 'nice' error returned by + // Security Framework after it was recently updated. + return startHandshake(); + } + + renegotiating = false; + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, + QStringLiteral("SSLHandshake failed: %1").arg(err)); + plainSocket->disconnectFromHost(); + return false; + } + + // Connection aborted during handshake phase. + if (q->state() != QAbstractSocket::ConnectedState) { + qCDebug(lcTlsBackend) << "connection aborted"; + renegotiating = false; + return false; + } + + // check protocol version ourselves, as Secure Transport does not enforce + // the requested min / max versions. + if (!verifySessionProtocol()) { + setErrorAndEmit(d, QAbstractSocket::SslHandshakeFailedError, QStringLiteral("Protocol version mismatch")); + plainSocket->disconnectFromHost(); + renegotiating = false; + return false; + } + + if (verifyPeerTrust()) { + continueHandshake(); + renegotiating = false; + return true; + } else { + renegotiating = false; + return false; + } +} + +bool TlsCryptographSecureTransport::isHandshakeComplete() const +{ + Q_ASSERT(q); + return q->isEncrypted() && !renegotiating; +} + +QList TlsCryptographSecureTransport::tlsErrors() const +{ + return sslErrors; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/securetransport/qtls_st_p.h b/src/plugins/tls/securetransport/qtls_st_p.h new file mode 100644 index 0000000000..3dad24f348 --- /dev/null +++ b/src/plugins/tls/securetransport/qtls_st_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé +** 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$ +** +****************************************************************************/ + +#ifndef QTLS_ST_P_H +#define QTLS_ST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the QtNetwork library. This header file may change from +// version to version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "qtlsbackend_st_p.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class QSecureTransportContext +{ +public: + explicit QSecureTransportContext(SSLContextRef context); + ~QSecureTransportContext(); + + operator SSLContextRef () const; + void reset(SSLContextRef newContext); +private: + SSLContextRef context; + + Q_DISABLE_COPY_MOVE(QSecureTransportContext) +}; + +class TlsCryptographSecureTransport : public TlsCryptograph +{ +public: + TlsCryptographSecureTransport(); + ~TlsCryptographSecureTransport() override; + + void init(QSslSocket *qObj, QSslSocketPrivate *dObj) override; + void continueHandshake() override; + void disconnected() override; + void disconnectFromHost() override; + QSslCipher sessionCipher() const override; + QSsl::SslProtocol sessionProtocol() const override; + void startClientEncryption() override; + void startServerEncryption() override; + void transmit() override; + QList tlsErrors() const override; + + SSLCipherSuite SSLCipherSuite_from_QSslCipher(const QSslCipher &ciph); + +private: + // SSL context management/properties: + bool initSslContext(); + void destroySslContext(); + bool setSessionCertificate(QString &errorDescription, + QAbstractSocket::SocketError &errorCode); + bool setSessionProtocol(); + // Aux. functions to do a verification during handshake phase: + bool canIgnoreTrustVerificationFailure() const; + bool verifySessionProtocol() const; + bool verifyPeerTrust(); + + bool checkSslErrors(); + bool startHandshake(); + + bool isHandshakeComplete() const; + + // IO callbacks: + static OSStatus ReadCallback(TlsCryptographSecureTransport *socket, char *data, size_t *dataLength); + static OSStatus WriteCallback(TlsCryptographSecureTransport *plainSocket, const char *data, size_t *dataLength); + + QSecureTransportContext context; + bool renegotiating = false; + QSslSocket *q = nullptr; + QSslSocketPrivate *d = nullptr; + bool shutdown = false; + QList sslErrors; + + Q_DISABLE_COPY_MOVE(TlsCryptographSecureTransport) +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLS_ST_P_H diff --git a/src/plugins/tls/securetransport/qtlsbackend_st.cpp b/src/plugins/tls/securetransport/qtlsbackend_st.cpp new file mode 100644 index 0000000000..7fc7692350 --- /dev/null +++ b/src/plugins/tls/securetransport/qtlsbackend_st.cpp @@ -0,0 +1,341 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qtlsbackend_st_p.h" +#include "qtlskey_st_p.h" +#include "qx509_st_p.h" +#include "qtls_st_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QRecursiveMutex, qt_securetransport_mutex) + +Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.securetransport"); + +namespace QTlsPrivate { + +QList systemCaCertificates(); // defined in qsslsocket_mac_shared.cpp + +SSLContextRef qt_createSecureTransportContext(QSslSocket::SslMode mode); + +QSslCipher QSslCipher_from_SSLCipherSuite(SSLCipherSuite cipher) +{ + QString name; + switch (cipher) { + // Sorted as in CipherSuite.h (and groupped by their RFC) + // TLS addenda using AES, per RFC 3268 + case TLS_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("AES128-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("DHE-RSA-AES128-SHA"); + break; + case TLS_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("AES256-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("DHE-RSA-AES256-SHA"); + break; + + // ECDSA addenda, RFC 4492 + case TLS_ECDH_ECDSA_WITH_NULL_SHA: + name = QLatin1String("ECDH-ECDSA-NULL-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDH-ECDSA-RC4-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDH-ECDSA-DES-CBC3-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDH-ECDSA-AES128-SHA"); + break; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDH-ECDSA-AES256-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_NULL_SHA: + name = QLatin1String("ECDHE-ECDSA-NULL-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDHE-ECDSA-RC4-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDHE-ECDSA-DES-CBC3-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDHE-ECDSA-AES128-SHA"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDHE-ECDSA-AES256-SHA"); + break; + case TLS_ECDH_RSA_WITH_NULL_SHA: + name = QLatin1String("ECDH-RSA-NULL-SHA"); + break; + case TLS_ECDH_RSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDH-RSA-RC4-SHA"); + break; + case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDH-RSA-DES-CBC3-SHA"); + break; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDH-RSA-AES128-SHA"); + break; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDH-RSA-AES256-SHA"); + break; + case TLS_ECDHE_RSA_WITH_NULL_SHA: + name = QLatin1String("ECDHE-RSA-NULL-SHA"); + break; + case TLS_ECDHE_RSA_WITH_RC4_128_SHA: + name = QLatin1String("ECDHE-RSA-RC4-SHA"); + break; + case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("ECDHE-RSA-DES-CBC3-SHA"); + break; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + name = QLatin1String("ECDHE-RSA-AES128-SHA"); + break; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + name = QLatin1String("ECDHE-RSA-AES256-SHA"); + break; + + // TLS 1.2 addenda, RFC 5246 + case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("DES-CBC3-SHA"); + break; + case TLS_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("AES128-SHA256"); + break; + case TLS_RSA_WITH_AES_256_CBC_SHA256: + name = QLatin1String("AES256-SHA256"); + break; + case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + name = QLatin1String("DHE-RSA-DES-CBC3-SHA"); + break; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("DHE-RSA-AES128-SHA256"); + break; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + name = QLatin1String("DHE-RSA-AES256-SHA256"); + break; + + // Addendum from RFC 4279, TLS PSK + // all missing atm. + + // RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption + // all missing atm. + + // Addenda from rfc 5288 AES Galois Counter Mode (CGM) Cipher Suites for TLS + case TLS_RSA_WITH_AES_256_GCM_SHA384: + name = QLatin1String("AES256-GCM-SHA384"); + break; + + // RFC 5487 - PSK with SHA-256/384 and AES GCM + // all missing atm. + + // Addenda from rfc 5289 Elliptic Curve Cipher Suites with HMAC SHA-256/384 + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDHE-ECDSA-AES128-SHA256"); + break; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDHE-ECDSA-AES256-SHA384"); + break; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDH-ECDSA-AES128-SHA256"); + break; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDH-ECDSA-AES256-SHA384"); + break; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDHE-RSA-AES128-SHA256"); + break; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDHE-RSA-AES256-SHA384"); + break; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + name = QLatin1String("ECDH-RSA-AES128-SHA256"); + break; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + name = QLatin1String("ECDH-RSA-AES256-SHA384"); + break; + + // Addenda from rfc 5289 Elliptic Curve Cipher Suites + // with SHA-256/384 and AES Galois Counter Mode (GCM) + case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + name = QLatin1String("ECDHE-RSA-AES256-GCM-SHA384"); + break; + + default: + return {}; + } + + return QTlsBackend::createCiphersuite(name, QSsl::TlsV1_2, QLatin1String("TLSv1.2")); +} + +} // namespace QTlsPrivate + +bool QSecureTransportBackend::s_loadedCiphersAndCerts = false; + +QString QSecureTransportBackend::tlsLibraryVersionString() const +{ + return QLatin1String("Secure Transport, ") + QSysInfo::prettyProductName(); +} + +QString QSecureTransportBackend::tlsLibraryBuildVersionString() const +{ + return tlsLibraryVersionString(); +} + +void QSecureTransportBackend::ensureInitialized() const +{ + const QMutexLocker locker(qt_securetransport_mutex()); + if (s_loadedCiphersAndCerts) + return; + + // We have to set it before setDefaultSupportedCiphers, + // since this function can trigger static (global)'s initialization + // and as a result - recursive ensureInitialized call + // from QSslCertificatePrivate's ctor. + s_loadedCiphersAndCerts = true; + + const QTlsPrivate::QSecureTransportContext context(QTlsPrivate::qt_createSecureTransportContext(QSslSocket::SslClientMode)); + if (context) { + QList ciphers; + QList defaultCiphers; + + size_t numCiphers = 0; + // Fails only if any of parameters is null. + SSLGetNumberSupportedCiphers(context, &numCiphers); + QList cfCiphers(numCiphers); + // Fails only if any of parameter is null or number of ciphers is wrong. + SSLGetSupportedCiphers(context, cfCiphers.data(), &numCiphers); + + for (size_t i = 0; i < size_t(cfCiphers.size()); ++i) { + const QSslCipher ciph(QTlsPrivate::QSslCipher_from_SSLCipherSuite(cfCiphers.at(i))); + if (!ciph.isNull()) { + ciphers << ciph; + if (ciph.usedBits() >= 128) + defaultCiphers << ciph; + } + } + + setDefaultSupportedCiphers(ciphers); + setDefaultCiphers(defaultCiphers); + + if (!QSslSocketPrivate::rootCertOnDemandLoadingSupported()) + setDefaultCaCertificates(systemCaCertificates()); + } else { + s_loadedCiphersAndCerts = false; + } +} + +QString QSecureTransportBackend::backendName() const +{ + return builtinBackendNames[nameIndexSecureTransport]; +} + +QTlsPrivate::TlsKey *QSecureTransportBackend::createKey() const +{ + return new QTlsPrivate::TlsKeySecureTransport; +} + +QTlsPrivate::X509Certificate *QSecureTransportBackend::createCertificate() const +{ + return new QTlsPrivate::X509CertificateSecureTransport; +} + +QList QSecureTransportBackend::systemCaCertificates() const +{ + return QTlsPrivate::systemCaCertificates(); +} + +QList QSecureTransportBackend::supportedProtocols() const +{ + QList protocols; + + protocols << QSsl::AnyProtocol; + protocols << QSsl::SecureProtocols; + protocols << QSsl::TlsV1_0; + protocols << QSsl::TlsV1_0OrLater; + protocols << QSsl::TlsV1_1; + protocols << QSsl::TlsV1_1OrLater; + protocols << QSsl::TlsV1_2; + protocols << QSsl::TlsV1_2OrLater; + + return protocols; +} + +QList QSecureTransportBackend::supportedFeatures() const +{ + QList features; + features << QSsl::SupportedFeature::ClientSideAlpn; + + return features; +} + +QList QSecureTransportBackend::implementedClasses() const +{ + QList classes; + classes << QSsl::ImplementedClass::Socket; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Key; + + return classes; +} + +QTlsPrivate::X509PemReaderPtr QSecureTransportBackend::X509PemReader() const +{ + return QTlsPrivate::X509CertificateGeneric::certificatesFromPem; +} + +QTlsPrivate::X509DerReaderPtr QSecureTransportBackend::X509DerReader() const +{ + return QTlsPrivate::X509CertificateGeneric::certificatesFromDer; +} + +QTlsPrivate::TlsCryptograph *QSecureTransportBackend::createTlsCryptograph() const +{ + return new QTlsPrivate::TlsCryptographSecureTransport; +} + +QT_END_NAMESPACE + diff --git a/src/plugins/tls/securetransport/qtlsbackend_st_p.h b/src/plugins/tls/securetransport/qtlsbackend_st_p.h new file mode 100644 index 0000000000..ebce859db3 --- /dev/null +++ b/src/plugins/tls/securetransport/qtlsbackend_st_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLSBACKEND_ST_P_H +#define QTLSBACKEND_ST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include + + +QT_BEGIN_NAMESPACE + +class QSecureTransportBackend : public QTlsBackend +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QTlsBackend_iid) + Q_INTERFACES(QTlsBackend) + +private: + + QString tlsLibraryVersionString() const override; + virtual QString tlsLibraryBuildVersionString() const override; + virtual void ensureInitialized() const override; + + QString backendName() const override; + + QList supportedProtocols() const override; + QList supportedFeatures() const override; + QList implementedClasses() const override; + + QTlsPrivate::TlsKey *createKey() const override; + QTlsPrivate::X509Certificate *createCertificate() const override; + + QList systemCaCertificates() const override; + + QTlsPrivate::X509PemReaderPtr X509PemReader() const override; + QTlsPrivate::X509DerReaderPtr X509DerReader() const override; + + QTlsPrivate::TlsCryptograph *createTlsCryptograph() const override; + + static bool s_loadedCiphersAndCerts; +}; + +QT_END_NAMESPACE + +#endif // QTLSBACKEND_ST_P_H + + diff --git a/src/plugins/tls/securetransport/qtlskey_st.cpp b/src/plugins/tls/securetransport/qtlskey_st.cpp new file mode 100644 index 0000000000..85f86c7bf8 --- /dev/null +++ b/src/plugins/tls/securetransport/qtlskey_st.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2014 Jeremy Lainé +** 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$ +** +****************************************************************************/ + +#include "qtlskey_st_p.h" + +#include + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { +namespace { + +// Before this code was located in qsslkey_mac.cpp. +QByteArray wrapCCCrypt(CCOperation ccOp, QSslKeyPrivate::Cipher cipher, + const QByteArray &data, const QByteArray &key, + const QByteArray &iv) +{ + int blockSize = {}; + CCAlgorithm ccAlgorithm = {}; + switch (cipher) { + case Cipher::DesCbc: + blockSize = kCCBlockSizeDES; + ccAlgorithm = kCCAlgorithmDES; + break; + case Cipher::DesEde3Cbc: + blockSize = kCCBlockSize3DES; + ccAlgorithm = kCCAlgorithm3DES; + break; + case Cipher::Rc2Cbc: + blockSize = kCCBlockSizeRC2; + ccAlgorithm = kCCAlgorithmRC2; + break; + case Cipher::Aes128Cbc: + case Cipher::Aes192Cbc: + case Cipher::Aes256Cbc: + blockSize = kCCBlockSizeAES128; + ccAlgorithm = kCCAlgorithmAES; + break; + } + std::size_t plainLength = 0; + QByteArray plain(data.size() + blockSize, 0); + CCCryptorStatus status = CCCrypt(ccOp, ccAlgorithm, kCCOptionPKCS7Padding, + key.constData(), std::size_t(key.size()), + iv.constData(), data.constData(), std::size_t(data.size()), + plain.data(), std::size_t(plain.size()), &plainLength); + if (status == kCCSuccess) + return plain.left(int(plainLength)); + + return {}; +} + +} // Unnamed namespace. + +QByteArray TlsKeySecureTransport::decrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const +{ + return wrapCCCrypt(kCCDecrypt, cipher, data, key, iv); +} + +QByteArray TlsKeySecureTransport::encrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const +{ + return wrapCCCrypt(kCCEncrypt, cipher, data, key, iv); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/securetransport/qtlskey_st_p.h b/src/plugins/tls/securetransport/qtlskey_st_p.h new file mode 100644 index 0000000000..c9dcc4e3ec --- /dev/null +++ b/src/plugins/tls/securetransport/qtlskey_st_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLSKEY_ST_P_H +#define QTLSKEY_ST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "../shared/qtlskey_generic_p.h" + +#include + +QT_REQUIRE_CONFIG(ssl); + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class TlsKeySecureTransport final : public TlsKeyGeneric +{ +public: + using TlsKeyGeneric::TlsKeyGeneric; + + QByteArray decrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const override; + QByteArray encrypt(Cipher cipher, const QByteArray &data, + const QByteArray &key, const QByteArray &iv) const override; + + Q_DISABLE_COPY_MOVE(TlsKeySecureTransport) +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLSKEY_ST_P_H diff --git a/src/plugins/tls/securetransport/qx509_st.cpp b/src/plugins/tls/securetransport/qx509_st.cpp new file mode 100644 index 0000000000..737b15cef8 --- /dev/null +++ b/src/plugins/tls/securetransport/qx509_st.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qtlskey_st_p.h" +#include "qx509_st_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +TlsKey *X509CertificateSecureTransport::publicKey() const +{ + auto key = std::make_unique(QSsl::PublicKey); + if (publicKeyAlgorithm != QSsl::Opaque) + key->decodeDer(QSsl::PublicKey, publicKeyAlgorithm, publicKeyDerData, {}, false); + + return key.release(); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + diff --git a/src/plugins/tls/securetransport/qx509_st_p.h b/src/plugins/tls/securetransport/qx509_st_p.h new file mode 100644 index 0000000000..5e5b42e791 --- /dev/null +++ b/src/plugins/tls/securetransport/qx509_st_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QX509_ST_P_H +#define QX509_ST_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include "../shared/qx509_generic_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class X509CertificateSecureTransport final : public X509CertificateGeneric +{ +public: + TlsKey *publicKey() const override; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QX509_ST_P_H diff --git a/src/plugins/tls/shared/qasn1element.cpp b/src/plugins/tls/shared/qasn1element.cpp new file mode 100644 index 0000000000..3df76c3774 --- /dev/null +++ b/src/plugins/tls/shared/qasn1element.cpp @@ -0,0 +1,400 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé +** 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$ +** +****************************************************************************/ + + +#include "qasn1element_p.h" + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +typedef QMap OidNameMap; +static OidNameMap createOidMap() +{ + OidNameMap oids; + // used by unit tests + oids.insert(oids.cend(), QByteArrayLiteral("0.9.2342.19200300.100.1.5"), QByteArrayLiteral("favouriteDrink")); + oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.113549.1.9.1"), QByteArrayLiteral("emailAddress")); + oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.1.1"), QByteArrayLiteral("authorityInfoAccess")); + oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.1"), QByteArrayLiteral("OCSP")); + oids.insert(oids.cend(), QByteArrayLiteral("1.3.6.1.5.5.7.48.2"), QByteArrayLiteral("caIssuers")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.14"), QByteArrayLiteral("subjectKeyIdentifier")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.15"), QByteArrayLiteral("keyUsage")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.17"), QByteArrayLiteral("subjectAltName")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.19"), QByteArrayLiteral("basicConstraints")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.29.35"), QByteArrayLiteral("authorityKeyIdentifier")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.10"), QByteArrayLiteral("O")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.11"), QByteArrayLiteral("OU")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.12"), QByteArrayLiteral("title")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.13"), QByteArrayLiteral("description")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.17"), QByteArrayLiteral("postalCode")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.3"), QByteArrayLiteral("CN")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.4"), QByteArrayLiteral("SN")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.41"), QByteArrayLiteral("name")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.42"), QByteArrayLiteral("GN")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.43"), QByteArrayLiteral("initials")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.46"), QByteArrayLiteral("dnQualifier")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.5"), QByteArrayLiteral("serialNumber")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.6"), QByteArrayLiteral("C")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.7"), QByteArrayLiteral("L")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.8"), QByteArrayLiteral("ST")); + oids.insert(oids.cend(), QByteArrayLiteral("2.5.4.9"), QByteArrayLiteral("street")); + return oids; +} +Q_GLOBAL_STATIC_WITH_ARGS(OidNameMap, oidNameMap, (createOidMap())) + +QAsn1Element::QAsn1Element(quint8 type, const QByteArray &value) + : mType(type) + , mValue(value) +{ +} + +bool QAsn1Element::read(QDataStream &stream) +{ + // type + quint8 tmpType; + stream >> tmpType; + if (!tmpType) + return false; + + // length + quint64 length = 0; + quint8 first; + stream >> first; + if (first & 0x80) { + // long form + const quint8 bytes = (first & 0x7f); + if (bytes > 7) + return false; + + quint8 b; + for (int i = 0; i < bytes; i++) { + stream >> b; + length = (length << 8) | b; + } + } else { + // short form + length = (first & 0x7f); + } + + if (length > quint64(std::numeric_limits::max())) + return false; + + // read value in blocks to avoid being fooled by incorrect length + const int BUFFERSIZE = 4 * 1024; + QByteArray tmpValue; + int remainingLength = length; + while (remainingLength) { + char readBuffer[BUFFERSIZE]; + const int bytesToRead = qMin(remainingLength, BUFFERSIZE); + const int count = stream.readRawData(readBuffer, bytesToRead); + if (count != int(bytesToRead)) + return false; + tmpValue.append(readBuffer, bytesToRead); + remainingLength -= bytesToRead; + } + + mType = tmpType; + mValue.swap(tmpValue); + return true; +} + +bool QAsn1Element::read(const QByteArray &data) +{ + QDataStream stream(data); + return read(stream); +} + +void QAsn1Element::write(QDataStream &stream) const +{ + // type + stream << mType; + + // length + qint64 length = mValue.size(); + if (length >= 128) { + // long form + quint8 encodedLength = 0x80; + QByteArray ba; + while (length) { + ba.prepend(quint8((length & 0xff))); + length >>= 8; + encodedLength += 1; + } + stream << encodedLength; + stream.writeRawData(ba.data(), ba.size()); + } else { + // short form + stream << quint8(length); + } + + // value + stream.writeRawData(mValue.data(), mValue.size()); +} + +QAsn1Element QAsn1Element::fromBool(bool val) +{ + return QAsn1Element(QAsn1Element::BooleanType, + QByteArray(1, val ? 0xff : 0x00)); +} + +QAsn1Element QAsn1Element::fromInteger(unsigned int val) +{ + QAsn1Element elem(QAsn1Element::IntegerType); + while (val > 127) { + elem.mValue.prepend(val & 0xff); + val >>= 8; + } + elem.mValue.prepend(val & 0x7f); + return elem; +} + +QAsn1Element QAsn1Element::fromVector(const QList &items) +{ + QAsn1Element seq; + seq.mType = SequenceType; + QDataStream stream(&seq.mValue, QDataStream::WriteOnly); + for (auto it = items.cbegin(), end = items.cend(); it != end; ++it) + it->write(stream); + return seq; +} + +QAsn1Element QAsn1Element::fromObjectId(const QByteArray &id) +{ + QAsn1Element elem; + elem.mType = ObjectIdentifierType; + const QList bits = id.split('.'); + Q_ASSERT(bits.size() > 2); + elem.mValue += quint8((bits[0].toUInt() * 40 + bits[1].toUInt())); + for (int i = 2; i < bits.size(); ++i) { + char buffer[std::numeric_limits::digits / 7 + 2]; + char *pBuffer = buffer + sizeof(buffer); + *--pBuffer = '\0'; + unsigned int node = bits[i].toUInt(); + *--pBuffer = quint8((node & 0x7f)); + node >>= 7; + while (node) { + *--pBuffer = quint8(((node & 0x7f) | 0x80)); + node >>= 7; + } + elem.mValue += pBuffer; + } + return elem; +} + +bool QAsn1Element::toBool(bool *ok) const +{ + if (*this == fromBool(true)) { + if (ok) + *ok = true; + return true; + } else if (*this == fromBool(false)) { + if (ok) + *ok = true; + return false; + } else { + if (ok) + *ok = false; + return false; + } +} + +QDateTime QAsn1Element::toDateTime() const +{ + QDateTime result; + + if (mValue.size() != 13 && mValue.size() != 15) + return result; + + // QDateTime::fromString is lenient and accepts +- signs in front + // of the year; but ASN.1 doesn't allow them. + const auto isAsciiDigit = [](char c) + { + return c >= '0' && c <= '9'; + }; + + if (!isAsciiDigit(mValue[0])) + return result; + + // Timezone must be present, and UTC + if (mValue.back() != 'Z') + return result; + + // In addition, check that we only have digits representing the + // date/time. This should not really be necessary (there's no such + // thing as negative months/days/etc.); it's a workaround for + // QTBUG-84349. + if (!std::all_of(mValue.begin(), mValue.end() - 1, isAsciiDigit)) + 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)); + + Q_ASSERT(result.date().year() >= 1950); + Q_ASSERT(result.date().year() <= 2049); + } else if (mType == GeneralizedTimeType && mValue.size() == 15) { + result = QDateTime::fromString(QString::fromLatin1(mValue), + QStringLiteral("yyyyMMddHHmmsst")); + } + + return result; +} + +QMultiMap QAsn1Element::toInfo() const +{ + QMultiMap info; + QAsn1Element elem; + QDataStream issuerStream(mValue); + while (elem.read(issuerStream) && elem.mType == QAsn1Element::SetType) { + QAsn1Element issuerElem; + QDataStream setStream(elem.mValue); + if (issuerElem.read(setStream) && issuerElem.mType == QAsn1Element::SequenceType) { + const auto elems = issuerElem.toList(); + if (elems.size() == 2) { + const QByteArray key = elems.front().toObjectName(); + if (!key.isEmpty()) + info.insert(key, elems.back().toString()); + } + } + } + return info; +} + +qint64 QAsn1Element::toInteger(bool *ok) const +{ + if (mType != QAsn1Element::IntegerType || mValue.isEmpty()) { + if (ok) + *ok = false; + return 0; + } + + // NOTE: - negative numbers are not handled + // - greater sizes would overflow + if (mValue.at(0) & 0x80 || mValue.size() > 8) { + if (ok) + *ok = false; + return 0; + } + + qint64 value = mValue.at(0) & 0x7f; + for (int i = 1; i < mValue.size(); ++i) + value = (value << 8) | quint8(mValue.at(i)); + + if (ok) + *ok = true; + return value; +} + +QList QAsn1Element::toList() const +{ + QList items; + if (mType == SequenceType) { + QAsn1Element elem; + QDataStream stream(mValue); + while (elem.read(stream)) + items << elem; + } + return items; +} + +QByteArray QAsn1Element::toObjectId() const +{ + QByteArray key; + if (mType == ObjectIdentifierType && !mValue.isEmpty()) { + quint8 b = mValue.at(0); + key += QByteArray::number(b / 40) + '.' + QByteArray::number (b % 40); + unsigned int val = 0; + for (int i = 1; i < mValue.size(); ++i) { + b = mValue.at(i); + val = (val << 7) | (b & 0x7f); + if (!(b & 0x80)) { + key += '.' + QByteArray::number(val); + val = 0; + } + } + } + return key; +} + +QByteArray QAsn1Element::toObjectName() const +{ + QByteArray key = toObjectId(); + return oidNameMap->value(key, key); +} + +QString QAsn1Element::toString() const +{ + // Detect embedded NULs and reject + if (qstrlen(mValue) < uint(mValue.size())) + return QString(); + + if (mType == PrintableStringType || mType == TeletexStringType + || mType == Rfc822NameType || mType == DnsNameType + || mType == UniformResourceIdentifierType) + return QString::fromLatin1(mValue, mValue.size()); + if (mType == Utf8StringType) + return QString::fromUtf8(mValue, mValue.size()); + + return QString(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qasn1element_p.h b/src/plugins/tls/shared/qasn1element_p.h new file mode 100644 index 0000000000..ac74937802 --- /dev/null +++ b/src/plugins/tls/shared/qasn1element_p.h @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé +** 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$ +** +****************************************************************************/ + + +#ifndef QASN1ELEMENT_P_H +#define QASN1ELEMENT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// General +#define RSADSI_OID "1.2.840.113549." + +#define RSA_ENCRYPTION_OID QByteArrayLiteral(RSADSI_OID "1.1.1") +#define DSA_ENCRYPTION_OID QByteArrayLiteral("1.2.840.10040.4.1") +#define EC_ENCRYPTION_OID QByteArrayLiteral("1.2.840.10045.2.1") +#define DH_ENCRYPTION_OID QByteArrayLiteral(RSADSI_OID "1.3.1") + +// These are mostly from the RFC for PKCS#5 +// PKCS#5: https://tools.ietf.org/html/rfc8018#appendix-B +#define PKCS5_OID RSADSI_OID "1.5." +// PKCS#12: https://tools.ietf.org/html/rfc7292#appendix-D) +#define PKCS12_OID RSADSI_OID "1.12." + +// -PBES1 +#define PKCS5_MD2_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "1") // Not (yet) implemented +#define PKCS5_MD2_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "4") // Not (yet) implemented +#define PKCS5_MD5_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "3") +#define PKCS5_MD5_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "6") +#define PKCS5_SHA1_DES_CBC_OID QByteArrayLiteral(PKCS5_OID "10") +#define PKCS5_SHA1_RC2_CBC_OID QByteArrayLiteral(PKCS5_OID "11") +#define PKCS12_SHA1_RC4_128_OID QByteArrayLiteral(PKCS12_OID "1.1") // Not (yet) implemented +#define PKCS12_SHA1_RC4_40_OID QByteArrayLiteral(PKCS12_OID "1.2") // Not (yet) implemented +#define PKCS12_SHA1_3KEY_3DES_CBC_OID QByteArrayLiteral(PKCS12_OID "1.3") +#define PKCS12_SHA1_2KEY_3DES_CBC_OID QByteArrayLiteral(PKCS12_OID "1.4") +#define PKCS12_SHA1_RC2_128_CBC_OID QByteArrayLiteral(PKCS12_OID "1.5") +#define PKCS12_SHA1_RC2_40_CBC_OID QByteArrayLiteral(PKCS12_OID "1.6") + +// -PBKDF2 +#define PKCS5_PBKDF2_ENCRYPTION_OID QByteArrayLiteral(PKCS5_OID "12") + +// -PBES2 +#define PKCS5_PBES2_ENCRYPTION_OID QByteArrayLiteral(PKCS5_OID "13") + +// Digest +#define DIGEST_ALGORITHM_OID RSADSI_OID "2." +// -HMAC-SHA-1 +#define HMAC_WITH_SHA1 QByteArrayLiteral(DIGEST_ALGORITHM_OID "7") +// -HMAC-SHA-2 +#define HMAC_WITH_SHA224 QByteArrayLiteral(DIGEST_ALGORITHM_OID "8") +#define HMAC_WITH_SHA256 QByteArrayLiteral(DIGEST_ALGORITHM_OID "9") +#define HMAC_WITH_SHA384 QByteArrayLiteral(DIGEST_ALGORITHM_OID "10") +#define HMAC_WITH_SHA512 QByteArrayLiteral(DIGEST_ALGORITHM_OID "11") +#define HMAC_WITH_SHA512_224 QByteArrayLiteral(DIGEST_ALGORITHM_OID "12") +#define HMAC_WITH_SHA512_256 QByteArrayLiteral(DIGEST_ALGORITHM_OID "13") + +// Encryption algorithms +#define ENCRYPTION_ALGORITHM_OID RSADSI_OID "3." +#define DES_CBC_ENCRYPTION_OID QByteArrayLiteral("1.3.14.3.2.7") +#define DES_EDE3_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "7") +#define RC2_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "2") +#define RC5_CBC_ENCRYPTION_OID QByteArrayLiteral(ENCRYPTION_ALGORITHM_OID "9") // Not (yet) implemented +#define AES_OID "2.16.840.1.101.3.4.1." +#define AES128_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "2") +#define AES192_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "22") // Not (yet) implemented +#define AES256_CBC_ENCRYPTION_OID QByteArrayLiteral(AES_OID "42") // Not (yet) implemented + +class QAsn1Element +{ +public: + enum ElementType { + // universal + BooleanType = 0x01, + IntegerType = 0x02, + BitStringType = 0x03, + OctetStringType = 0x04, + NullType = 0x05, + ObjectIdentifierType = 0x06, + Utf8StringType = 0x0c, + PrintableStringType = 0x13, + TeletexStringType = 0x14, + UtcTimeType = 0x17, + GeneralizedTimeType = 0x18, + SequenceType = 0x30, + SetType = 0x31, + + // GeneralNameTypes + Rfc822NameType = 0x81, + DnsNameType = 0x82, + UniformResourceIdentifierType = 0x86, + IpAddressType = 0x87, + + // context specific + Context0Type = 0xA0, + Context1Type = 0xA1, + Context3Type = 0xA3 + }; + + explicit QAsn1Element(quint8 type = 0, const QByteArray &value = QByteArray()); + bool read(QDataStream &data); + bool read(const QByteArray &data); + void write(QDataStream &data) const; + + static QAsn1Element fromBool(bool val); + static QAsn1Element fromInteger(unsigned int val); + static QAsn1Element fromVector(const QList &items); + static QAsn1Element fromObjectId(const QByteArray &id); + + bool toBool(bool *ok = nullptr) const; + QDateTime toDateTime() const; + QMultiMap toInfo() const; + qint64 toInteger(bool *ok = nullptr) const; + QList toList() const; + QByteArray toObjectId() const; + QByteArray toObjectName() const; + QString toString() const; + + quint8 type() const { return mType; } + QByteArray value() const { return mValue; } + + friend inline bool operator==(const QAsn1Element &, const QAsn1Element &); + friend inline bool operator!=(const QAsn1Element &, const QAsn1Element &); + +private: + quint8 mType; + QByteArray mValue; +}; +Q_DECLARE_TYPEINFO(QAsn1Element, Q_RELOCATABLE_TYPE); + +inline bool operator==(const QAsn1Element &e1, const QAsn1Element &e2) +{ return e1.mType == e2.mType && e1.mValue == e2.mValue; } + +inline bool operator!=(const QAsn1Element &e1, const QAsn1Element &e2) +{ return e1.mType != e2.mType || e1.mValue != e2.mValue; } + +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/tls/shared/qdtls_base.cpp b/src/plugins/tls/shared/qdtls_base.cpp new file mode 100644 index 0000000000..6a5979eb9e --- /dev/null +++ b/src/plugins/tls/shared/qdtls_base.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qdtls_base_p.h" + +QT_BEGIN_NAMESPACE + +void QDtlsBasePrivate::setDtlsError(QDtlsError code, const QString &description) +{ + errorCode = code; + errorDescription = description; +} + +QDtlsError QDtlsBasePrivate::error() const +{ + return errorCode; +} + +QString QDtlsBasePrivate::errorString() const +{ + return errorDescription; +} + +void QDtlsBasePrivate::clearDtlsError() +{ + errorCode = QDtlsError::NoError; + errorDescription.clear(); +} + +QSslConfiguration QDtlsBasePrivate::configuration() const +{ + return dtlsConfiguration; +} + +void QDtlsBasePrivate::setConfiguration(const QSslConfiguration &configuration) +{ + dtlsConfiguration = configuration; + clearDtlsError(); +} + +bool QDtlsBasePrivate::setCookieGeneratorParameters(const GenParams ¶ms) +{ + if (!params.secret.size()) { + setDtlsError(QDtlsError::InvalidInputParameters, + QDtls::tr("Invalid (empty) secret")); + return false; + } + + clearDtlsError(); + + hashAlgorithm = params.hash; + secret = params.secret; + + return true; +} + +QDtlsClientVerifier::GeneratorParameters +QDtlsBasePrivate::cookieGeneratorParameters() const +{ + return {hashAlgorithm, secret}; +} + +bool QDtlsBasePrivate::isDtlsProtocol(QSsl::SslProtocol protocol) +{ + switch (protocol) { + case QSsl::DtlsV1_0: + case QSsl::DtlsV1_0OrLater: + case QSsl::DtlsV1_2: + case QSsl::DtlsV1_2OrLater: + return true; + default: + return false; + } +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qdtls_base_p.h b/src/plugins/tls/shared/qdtls_base_p.h new file mode 100644 index 0000000000..ca3db50c84 --- /dev/null +++ b/src/plugins/tls/shared/qdtls_base_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QDTLS_BASE_P_H +#define QDTLS_BASE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_REQUIRE_CONFIG(dtls); + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// This class exists to re-implement the shared error/cookie handling +// for both QDtls and QDtlsClientVerifier classes. Use it if/when +// you need it. Backend neutral. +class QDtlsBasePrivate : virtual public QTlsPrivate::DtlsBase +{ +public: + QDtlsBasePrivate(QSslSocket::SslMode m, const QByteArray &s) : mode(m), secret(s) {} + void setDtlsError(QDtlsError code, const QString &description) override; + QDtlsError error() const override; + QString errorString() const override; + void clearDtlsError() override; + + void setConfiguration(const QSslConfiguration &configuration) override; + QSslConfiguration configuration() const override; + + bool setCookieGeneratorParameters(const GenParams &) override; + GenParams cookieGeneratorParameters() const override; + + static bool isDtlsProtocol(QSsl::SslProtocol protocol); + + QHostAddress remoteAddress; + quint16 remotePort = 0; + quint16 mtuHint = 0; + + QDtlsError errorCode = QDtlsError::NoError; + QString errorDescription; + QSslConfiguration dtlsConfiguration; + QSslSocket::SslMode mode = QSslSocket::SslClientMode; + QSslCipher sessionCipher; + QSsl::SslProtocol sessionProtocol = QSsl::UnknownProtocol; + QString peerVfyName; + QByteArray secret; + +#ifdef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; +#else + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha256; +#endif +}; + +QT_END_NAMESPACE + +#endif // QDTLS_BASE_P_H diff --git a/src/plugins/tls/shared/qsslsocket_mac_shared.cpp b/src/plugins/tls/shared/qsslsocket_mac_shared.cpp new file mode 100644 index 0000000000..b808c9e83b --- /dev/null +++ b/src/plugins/tls/shared/qsslsocket_mac_shared.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2015 ownCloud Inc +** 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$ +** +****************************************************************************/ + +#include + +#include + +#include +#include + + +#ifdef Q_OS_MACOS + +#include + +#include +#include + +#endif // Q_OS_MACOS + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcTlsBackend) + +#ifdef Q_OS_MACOS +namespace { + +bool hasTrustedSslServerPolicy(SecPolicyRef policy, CFDictionaryRef props) { + QCFType policyProps = SecPolicyCopyProperties(policy); + // only accept certificates with policies for SSL server validation for now + if (CFEqual(CFDictionaryGetValue(policyProps, kSecPolicyOid), kSecPolicyAppleSSL)) { + CFBooleanRef policyClient; + if (CFDictionaryGetValueIfPresent(policyProps, kSecPolicyClient, reinterpret_cast(&policyClient)) && + CFEqual(policyClient, kCFBooleanTrue)) { + return false; // no client certs + } + if (!CFDictionaryContainsKey(props, kSecTrustSettingsResult)) { + // as per the docs, no trust settings result implies full trust + return true; + } + CFNumberRef number = static_cast(CFDictionaryGetValue(props, kSecTrustSettingsResult)); + SecTrustSettingsResult settingsResult; + CFNumberGetValue(number, kCFNumberSInt32Type, &settingsResult); + switch (settingsResult) { + case kSecTrustSettingsResultTrustRoot: + case kSecTrustSettingsResultTrustAsRoot: + return true; + default: + return false; + } + } + return false; +} + +bool isCaCertificateTrusted(SecCertificateRef cfCert, int domain) +{ + QCFType cfTrustSettings; + OSStatus status = SecTrustSettingsCopyTrustSettings(cfCert, SecTrustSettingsDomain(domain), &cfTrustSettings); + if (status == noErr) { + CFIndex size = CFArrayGetCount(cfTrustSettings); + // if empty, trust for everything (as per the Security Framework documentation) + if (size == 0) { + return true; + } else { + for (CFIndex i = 0; i < size; ++i) { + CFDictionaryRef props = static_cast(CFArrayGetValueAtIndex(cfTrustSettings, i)); + if (CFDictionaryContainsKey(props, kSecTrustSettingsPolicy)) { + if (hasTrustedSslServerPolicy((SecPolicyRef)CFDictionaryGetValue(props, kSecTrustSettingsPolicy), props)) + return true; + } + } + } + } else { + qCWarning(lcTlsBackend, "Error receiving trust for a CA certificate"); + } + return false; +} + +} // unnamed namespace +#endif // Q_OS_MACOS + +namespace QTlsPrivate { +QList systemCaCertificates() +{ + QList systemCerts; + // SecTrustSettingsCopyCertificates is not defined on iOS. +#ifdef Q_OS_MACOS + // iterate through all enum members, order: + // kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainSystem + for (int dom = kSecTrustSettingsDomainUser; dom <= int(kSecTrustSettingsDomainSystem); dom++) { + QCFType cfCerts; + OSStatus status = SecTrustSettingsCopyCertificates(SecTrustSettingsDomain(dom), &cfCerts); + if (status == noErr) { + const CFIndex size = CFArrayGetCount(cfCerts); + for (CFIndex i = 0; i < size; ++i) { + SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i); + QCFType derData = SecCertificateCopyData(cfCert); + if (isCaCertificateTrusted(cfCert, dom)) { + if (derData == nullptr) { + qCWarning(lcTlsBackend, "Error retrieving a CA certificate from the system store"); + } else { + systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der); + } + } + } + } + } +#endif + return systemCerts; +} +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qsslsocket_qt.cpp b/src/plugins/tls/shared/qsslsocket_qt.cpp new file mode 100644 index 0000000000..128459ea19 --- /dev/null +++ b/src/plugins/tls/shared/qsslsocket_qt.cpp @@ -0,0 +1,311 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé +** 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$ +** +****************************************************************************/ + +#include "qasn1element_p.h" + +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +/* + PKCS12 helpers. +*/ + +static QAsn1Element wrap(quint8 type, const QAsn1Element &child) +{ + QByteArray value; + QDataStream stream(&value, QIODevice::WriteOnly); + child.write(stream); + return QAsn1Element(type, value); +} + +static QAsn1Element _q_PKCS7_data(const QByteArray &data) +{ + QList items; + items << QAsn1Element::fromObjectId("1.2.840.113549.1.7.1"); + items << wrap(QAsn1Element::Context0Type, + QAsn1Element(QAsn1Element::OctetStringType, data)); + return QAsn1Element::fromVector(items); +} + +/*! + PKCS #12 key derivation. + + Some test vectors: + http://www.drh-consultancy.demon.co.uk/test.txt + \internal +*/ +static QByteArray _q_PKCS12_keygen(char id, const QByteArray &salt, const QString &passPhrase, int n, int r) +{ + const int u = 20; + const int v = 64; + + // password formatting + QByteArray passUnicode(passPhrase.size() * 2 + 2, '\0'); + char *p = passUnicode.data(); + for (int i = 0; i < passPhrase.size(); ++i) { + quint16 ch = passPhrase[i].unicode(); + *(p++) = (ch & 0xff00) >> 8; + *(p++) = (ch & 0xff); + } + + // prepare I + QByteArray D(64, id); + QByteArray S, P; + const int sSize = v * ((salt.size() + v - 1) / v); + S.resize(sSize); + for (int i = 0; i < sSize; ++i) + S[i] = salt[i % salt.size()]; + const int pSize = v * ((passUnicode.size() + v - 1) / v); + P.resize(pSize); + for (int i = 0; i < pSize; ++i) + P[i] = passUnicode[i % passUnicode.size()]; + QByteArray I = S + P; + + // apply hashing + const int c = (n + u - 1) / u; + QByteArray A; + QByteArray B; + B.resize(v); + QCryptographicHash hash(QCryptographicHash::Sha1); + for (int i = 0; i < c; ++i) { + // hash r iterations + QByteArray Ai = D + I; + for (int j = 0; j < r; ++j) { + hash.reset(); + hash.addData(Ai); + Ai = hash.result(); + } + + for (int j = 0; j < v; ++j) + B[j] = Ai[j % u]; + + // modify I as Ij = (Ij + B + 1) modulo 2^v + for (int p = 0; p < I.size(); p += v) { + quint8 carry = 1; + for (int j = v - 1; j >= 0; --j) { + quint16 v = quint8(I[p + j]) + quint8(B[j]) + carry; + I[p + j] = v & 0xff; + carry = (v & 0xff00) >> 8; + } + } + A += Ai; + } + return A.left(n); +} + +static QByteArray _q_PKCS12_salt() +{ + QByteArray salt; + salt.resize(8); + for (int i = 0; i < salt.size(); ++i) + salt[i] = (QRandomGenerator::global()->generate() & 0xff); + return salt; +} + +static QByteArray _q_PKCS12_certBag(const QSslCertificate &cert) +{ + QList items; + items << QAsn1Element::fromObjectId("1.2.840.113549.1.12.10.1.3"); + + // certificate + QList certItems; + certItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.22.1"); + certItems << wrap(QAsn1Element::Context0Type, + QAsn1Element(QAsn1Element::OctetStringType, cert.toDer())); + items << wrap(QAsn1Element::Context0Type, + QAsn1Element::fromVector(certItems)); + + // local key id + const QByteArray localKeyId = cert.digest(QCryptographicHash::Sha1); + QList idItems; + idItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.21"); + idItems << wrap(QAsn1Element::SetType, + QAsn1Element(QAsn1Element::OctetStringType, localKeyId)); + items << wrap(QAsn1Element::SetType, QAsn1Element::fromVector(idItems)); + + // dump + QAsn1Element root = wrap(QAsn1Element::SequenceType, QAsn1Element::fromVector(items)); + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + root.write(stream); + return ba; +} + +static QAsn1Element _q_PKCS12_key(const QSslKey &key) +{ + Q_ASSERT(key.algorithm() == QSsl::Rsa || key.algorithm() == QSsl::Dsa); + + QList keyItems; + keyItems << QAsn1Element::fromInteger(0); + QList algoItems; + if (key.algorithm() == QSsl::Rsa) + algoItems << QAsn1Element::fromObjectId(RSA_ENCRYPTION_OID); + else if (key.algorithm() == QSsl::Dsa) + algoItems << QAsn1Element::fromObjectId(DSA_ENCRYPTION_OID); + algoItems << QAsn1Element(QAsn1Element::NullType); + keyItems << QAsn1Element::fromVector(algoItems); + keyItems << QAsn1Element(QAsn1Element::OctetStringType, key.toDer()); + return QAsn1Element::fromVector(keyItems); +} + +static QByteArray _q_PKCS12_shroudedKeyBag(const QSslKey &key, const QString &passPhrase, const QByteArray &localKeyId) +{ + const int iterations = 2048; + QByteArray salt = _q_PKCS12_salt(); + QByteArray cKey = _q_PKCS12_keygen(1, salt, passPhrase, 24, iterations); + QByteArray cIv = _q_PKCS12_keygen(2, salt, passPhrase, 8, iterations); + + // prepare and encrypt data + QByteArray plain; + QDataStream plainStream(&plain, QIODevice::WriteOnly); + _q_PKCS12_key(key).write(plainStream); + QByteArray crypted = QSslKeyPrivate::encrypt(QTlsPrivate::Cipher::DesEde3Cbc, + plain, cKey, cIv); + + QList items; + items << QAsn1Element::fromObjectId("1.2.840.113549.1.12.10.1.2"); + + // key + QList keyItems; + QList algoItems; + algoItems << QAsn1Element::fromObjectId("1.2.840.113549.1.12.1.3"); + QList paramItems; + paramItems << QAsn1Element(QAsn1Element::OctetStringType, salt); + paramItems << QAsn1Element::fromInteger(iterations); + algoItems << QAsn1Element::fromVector(paramItems); + keyItems << QAsn1Element::fromVector(algoItems); + keyItems << QAsn1Element(QAsn1Element::OctetStringType, crypted); + items << wrap(QAsn1Element::Context0Type, + QAsn1Element::fromVector(keyItems)); + + // local key id + QList idItems; + idItems << QAsn1Element::fromObjectId("1.2.840.113549.1.9.21"); + idItems << wrap(QAsn1Element::SetType, + QAsn1Element(QAsn1Element::OctetStringType, localKeyId)); + items << wrap(QAsn1Element::SetType, + QAsn1Element::fromVector(idItems)); + + // dump + QAsn1Element root = wrap(QAsn1Element::SequenceType, QAsn1Element::fromVector(items)); + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + root.write(stream); + return ba; +} + +static QByteArray _q_PKCS12_bag(const QList &certs, const QSslKey &key, const QString &passPhrase) +{ + QList items; + + // certs + for (int i = 0; i < certs.size(); ++i) + items << _q_PKCS7_data(_q_PKCS12_certBag(certs[i])); + + // key + if (!key.isNull()) { + const QByteArray localKeyId = certs.first().digest(QCryptographicHash::Sha1); + items << _q_PKCS7_data(_q_PKCS12_shroudedKeyBag(key, passPhrase, localKeyId)); + } + + // dump + QAsn1Element root = QAsn1Element::fromVector(items); + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + root.write(stream); + return ba; +} + +static QAsn1Element _q_PKCS12_mac(const QByteArray &data, const QString &passPhrase) +{ + const int iterations = 2048; + + // salt generation + QByteArray macSalt = _q_PKCS12_salt(); + QByteArray key = _q_PKCS12_keygen(3, macSalt, passPhrase, 20, iterations); + + // HMAC calculation + QMessageAuthenticationCode hmac(QCryptographicHash::Sha1, key); + hmac.addData(data); + + QList algoItems; + algoItems << QAsn1Element::fromObjectId("1.3.14.3.2.26"); + algoItems << QAsn1Element(QAsn1Element::NullType); + + QList digestItems; + digestItems << QAsn1Element::fromVector(algoItems); + digestItems << QAsn1Element(QAsn1Element::OctetStringType, hmac.result()); + + QList macItems; + macItems << QAsn1Element::fromVector(digestItems); + macItems << QAsn1Element(QAsn1Element::OctetStringType, macSalt); + macItems << QAsn1Element::fromInteger(iterations); + return QAsn1Element::fromVector(macItems); +} + +QByteArray _q_makePkcs12(const QList &certs, const QSslKey &key, const QString &passPhrase) +{ + QList items; + + // version + items << QAsn1Element::fromInteger(3); + + // auth safe + const QByteArray data = _q_PKCS12_bag(certs, key, passPhrase); + items << _q_PKCS7_data(data); + + // HMAC + items << _q_PKCS12_mac(data, passPhrase); + + // dump + QAsn1Element root = QAsn1Element::fromVector(items); + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + root.write(stream); + return ba; +} + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qtlskey_base.cpp b/src/plugins/tls/shared/qtlskey_base.cpp new file mode 100644 index 0000000000..13ce063f30 --- /dev/null +++ b/src/plugins/tls/shared/qtlskey_base.cpp @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qtlskey_base_p.h" +#include "qasn1element_p.h" + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +QByteArray TlsKeyBase::pemFromDer(const QByteArray &der, const QMap &headers) const +{ + QByteArray pem(der.toBase64()); + + const int lineWidth = 64; // RFC 1421 + const int newLines = pem.size() / lineWidth; + const bool rem = pem.size() % lineWidth; + + for (int i = 0; i < newLines; ++i) + pem.insert((i + 1) * lineWidth + i, '\n'); + if (rem) + pem.append('\n'); + + QByteArray extra; + if (!headers.isEmpty()) { + QMap::const_iterator it = headers.constEnd(); + do { + --it; + extra += it.key() + ": " + it.value() + '\n'; + } while (it != headers.constBegin()); + extra += '\n'; + } + + if (isEncryptedPkcs8(der)) { + pem.prepend(pkcs8Header(true) + '\n' + extra); + pem.append(pkcs8Footer(true) + '\n'); + } else if (isPkcs8()) { + pem.prepend(pkcs8Header(false) + '\n' + extra); + pem.append(pkcs8Footer(false) + '\n'); + } else { + pem.prepend(pemHeader() + '\n' + extra); + pem.append(pemFooter() + '\n'); + } + + return pem; +} + +QByteArray TlsKeyBase::pkcs8Header(bool encrypted) +{ + return encrypted + ? QByteArrayLiteral("-----BEGIN ENCRYPTED PRIVATE KEY-----") + : QByteArrayLiteral("-----BEGIN PRIVATE KEY-----"); +} + +QByteArray TlsKeyBase::pkcs8Footer(bool encrypted) +{ + return encrypted + ? QByteArrayLiteral("-----END ENCRYPTED PRIVATE KEY-----") + : QByteArrayLiteral("-----END PRIVATE KEY-----"); +} + +bool TlsKeyBase::isEncryptedPkcs8(const QByteArray &der) +{ + static const QList pbes1OIds { + // PKCS5 + { PKCS5_MD2_DES_CBC_OID }, { PKCS5_MD2_RC2_CBC_OID }, { PKCS5_MD5_DES_CBC_OID }, + { PKCS5_MD5_RC2_CBC_OID }, { PKCS5_SHA1_DES_CBC_OID }, { PKCS5_SHA1_RC2_CBC_OID }, + }; + QAsn1Element elem; + if (!elem.read(der) || elem.type() != QAsn1Element::SequenceType) + return false; + + const auto items = elem.toList(); + if (items.size() != 2 + || items[0].type() != QAsn1Element::SequenceType + || items[1].type() != QAsn1Element::OctetStringType) { + return false; + } + + const auto encryptionSchemeContainer = items[0].toList(); + if (encryptionSchemeContainer.size() != 2 + || encryptionSchemeContainer[0].type() != QAsn1Element::ObjectIdentifierType + || encryptionSchemeContainer[1].type() != QAsn1Element::SequenceType) { + return false; + } + + const QByteArray encryptionScheme = encryptionSchemeContainer[0].toObjectId(); + return encryptionScheme == PKCS5_PBES2_ENCRYPTION_OID + || pbes1OIds.contains(encryptionScheme) + || encryptionScheme.startsWith(PKCS12_OID); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + + diff --git a/src/plugins/tls/shared/qtlskey_base_p.h b/src/plugins/tls/shared/qtlskey_base_p.h new file mode 100644 index 0000000000..61bd67119b --- /dev/null +++ b/src/plugins/tls/shared/qtlskey_base_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLSKEY_BASE_P_H +#define QTLSKEY_BASE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class TlsKeyBase : public TlsKey +{ +public: + TlsKeyBase(KeyType type = QSsl::PublicKey, KeyAlgorithm algorithm = QSsl::Opaque) + : keyType(type), + keyAlgorithm(algorithm) + { + } + + bool isNull() const override + { + return keyIsNull; + } + KeyType type() const override + { + return keyType; + } + KeyAlgorithm algorithm() const override + { + return keyAlgorithm; + } + bool isPkcs8 () const override + { + return false; + } + + QByteArray pemFromDer(const QByteArray &der, const QMap &headers) const override; + +protected: + static QByteArray pkcs8Header(bool encrypted); + static QByteArray pkcs8Footer(bool encrypted); + static bool isEncryptedPkcs8(const QByteArray &der); + + bool keyIsNull = true; + KeyType keyType = QSsl::PublicKey; + KeyAlgorithm keyAlgorithm = QSsl::Opaque; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLSKEY_BASE_P_H diff --git a/src/plugins/tls/shared/qtlskey_generic.cpp b/src/plugins/tls/shared/qtlskey_generic.cpp new file mode 100644 index 0000000000..b9eaf3c1f6 --- /dev/null +++ b/src/plugins/tls/shared/qtlskey_generic.cpp @@ -0,0 +1,885 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jeremy Lainé +** 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$ +** +****************************************************************************/ + +#include "qtlskey_generic_p.h" +#include "qasn1element_p.h" + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +// The code here is essentially what we had in qsslkey_qt.cpp before, with +// minimal changes/restructure. + +namespace QTlsPrivate { + +// OIDs of named curves allowed in TLS as per RFCs 4492 and 7027, +// see also https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8 +namespace { + +const quint8 bits_table[256] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, +}; + +using OidLengthMap = QMap; + +OidLengthMap createOidMap() +{ + OidLengthMap oids; + oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.10045.3.1.1"), 192); // secp192r1 a.k.a prime192v1 + oids.insert(oids.cend(), QByteArrayLiteral("1.2.840.10045.3.1.7"), 256); // secp256r1 a.k.a prime256v1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.1"), 193); // sect193r2 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.10"), 256); // secp256k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.16"), 283); // sect283k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.17"), 283); // sect283r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.26"), 233); // sect233k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.27"), 233); // sect233r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.3"), 239); // sect239k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.30"), 160); // secp160r2 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.31"), 192); // secp192k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.32"), 224); // secp224k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.33"), 224); // secp224r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.34"), 384); // secp384r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.35"), 521); // secp521r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.36"), 409); // sect409k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.37"), 409); // sect409r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.38"), 571); // sect571k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.39"), 571); // sect571r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.8"), 160); // secp160r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.132.0.9"), 160); // secp160k1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.36.3.3.2.8.1.1.11"), 384); // brainpoolP384r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.36.3.3.2.8.1.1.13"), 512); // brainpoolP512r1 + oids.insert(oids.cend(), QByteArrayLiteral("1.3.36.3.3.2.8.1.1.7"), 256); // brainpoolP256r1 + return oids; +} + +} // Unnamed namespace. + +Q_GLOBAL_STATIC_WITH_ARGS(OidLengthMap, oidLengthMap, (createOidMap())) + +namespace { + +// Maps OIDs to the encryption cipher they specify +const QMap oidCipherMap { + {DES_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::DesCbc}, + {DES_EDE3_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::DesEde3Cbc}, + // {PKCS5_MD2_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, // No MD2 + {PKCS5_MD5_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, + {PKCS5_SHA1_DES_CBC_OID, QSslKeyPrivate::Cipher::DesCbc}, + // {PKCS5_MD2_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, // No MD2 + {PKCS5_MD5_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, + {PKCS5_SHA1_RC2_CBC_OID, QSslKeyPrivate::Cipher::Rc2Cbc}, + {RC2_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Rc2Cbc} + // {RC5_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Rc5Cbc}, // No RC5 + // {AES128_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes128}, // no AES + // {AES192_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes192}, + // {AES256_CBC_ENCRYPTION_OID, QSslKeyPrivate::Cipher::Aes256} +}; + +struct EncryptionData +{ + EncryptionData() = default; + + EncryptionData(QSslKeyPrivate::Cipher cipher, QByteArray key, QByteArray iv) + : initialized(true), cipher(cipher), key(key), iv(iv) + { + } + bool initialized = false; + QSslKeyPrivate::Cipher cipher; + QByteArray key; + QByteArray iv; +}; + +EncryptionData readPbes2(const QList &element, const QByteArray &passPhrase) +{ + // RFC 8018: https://tools.ietf.org/html/rfc8018#section-6.2 + /*** Scheme: *** + * Sequence (scheme-specific info..) + * Sequence (key derivation info) + * Object Identifier (Key derivation algorithm (e.g. PBKDF2)) + * Sequence (salt) + * CHOICE (this entry can be either of the types it contains) + * Octet string (actual salt) + * Object identifier (Anything using this is deferred to a later version of PKCS #5) + * Integer (iteration count) + * Sequence (encryption algorithm info) + * Object identifier (identifier for the algorithm) + * Algorithm dependent, is covered in the switch further down + */ + + static const QMap pbes2OidHashFunctionMap { + // PBES2/PBKDF2 + {HMAC_WITH_SHA1, QCryptographicHash::Sha1}, + {HMAC_WITH_SHA224, QCryptographicHash::Sha224}, + {HMAC_WITH_SHA256, QCryptographicHash::Sha256}, + {HMAC_WITH_SHA512, QCryptographicHash::Sha512}, + {HMAC_WITH_SHA512_224, QCryptographicHash::Sha512}, + {HMAC_WITH_SHA512_256, QCryptographicHash::Sha512}, + {HMAC_WITH_SHA384, QCryptographicHash::Sha384} + }; + + // Values from their respective sections here: https://tools.ietf.org/html/rfc8018#appendix-B.2 + static const QMap cipherKeyLengthMap { + {QSslKeyPrivate::Cipher::DesCbc, 8}, + {QSslKeyPrivate::Cipher::DesEde3Cbc, 24}, + // @note: variable key-length (https://tools.ietf.org/html/rfc8018#appendix-B.2.3) + {QSslKeyPrivate::Cipher::Rc2Cbc, 4} + // @todo: AES(, rc5?) + }; + + const QList keyDerivationContainer = element[0].toList(); + if (keyDerivationContainer.size() != 2 + || keyDerivationContainer[0].type() != QAsn1Element::ObjectIdentifierType + || keyDerivationContainer[1].type() != QAsn1Element::SequenceType) { + return {}; + } + + const QByteArray keyDerivationAlgorithm = keyDerivationContainer[0].toObjectId(); + const auto keyDerivationParams = keyDerivationContainer[1].toList(); + + const auto encryptionAlgorithmContainer = element[1].toList(); + if (encryptionAlgorithmContainer.size() != 2 + || encryptionAlgorithmContainer[0].type() != QAsn1Element::ObjectIdentifierType) { + return {}; + } + + auto iterator = oidCipherMap.constFind(encryptionAlgorithmContainer[0].toObjectId()); + if (iterator == oidCipherMap.cend()) { + qWarning() + << "QSslKey: Unsupported encryption cipher OID:" << encryptionAlgorithmContainer[0].toObjectId() + << "\nFile a bug report to Qt (include the line above)."; + return {}; + } + + QSslKeyPrivate::Cipher cipher = *iterator; + QByteArray key; + QByteArray iv; + switch (cipher) { + case QSslKeyPrivate::Cipher::DesCbc: + case QSslKeyPrivate::Cipher::DesEde3Cbc: + // https://tools.ietf.org/html/rfc8018#appendix-B.2.1 (DES-CBC-PAD) + // https://tools.ietf.org/html/rfc8018#appendix-B.2.2 (DES-EDE3-CBC-PAD) + // @todo https://tools.ietf.org/html/rfc8018#appendix-B.2.5 (AES-CBC-PAD) + /*** Scheme: *** + * Octet string (IV) + */ + if (encryptionAlgorithmContainer[1].type() != QAsn1Element::OctetStringType) + return {}; + + // @note: All AES identifiers should be able to use this branch!! + iv = encryptionAlgorithmContainer[1].value(); + + if (iv.size() != 8) // @note: AES needs 16 bytes + return {}; + break; + case QSslKeyPrivate::Cipher::Rc2Cbc: { + // https://tools.ietf.org/html/rfc8018#appendix-B.2.3 + /*** Scheme: *** + * Sequence (rc2 parameters) + * Integer (rc2 parameter version) + * Octet string (IV) + */ + if (encryptionAlgorithmContainer[1].type() != QAsn1Element::SequenceType) + return {}; + const auto rc2ParametersContainer = encryptionAlgorithmContainer[1].toList(); + if ((rc2ParametersContainer.size() != 1 && rc2ParametersContainer.size() != 2) + || rc2ParametersContainer.back().type() != QAsn1Element::OctetStringType) { + return {}; + } + iv = rc2ParametersContainer.back().value(); + if (iv.size() != 8) + return {}; + break; + } // @todo(?): case (RC5 , AES) + case QSslKeyPrivate::Cipher::Aes128Cbc: + case QSslKeyPrivate::Cipher::Aes192Cbc: + case QSslKeyPrivate::Cipher::Aes256Cbc: + Q_UNREACHABLE(); + } + + if (Q_LIKELY(keyDerivationAlgorithm == PKCS5_PBKDF2_ENCRYPTION_OID)) { + // Definition: https://tools.ietf.org/html/rfc8018#appendix-A.2 + QByteArray salt; + if (keyDerivationParams[0].type() == QAsn1Element::OctetStringType) { + salt = keyDerivationParams[0].value(); + } else if (keyDerivationParams[0].type() == QAsn1Element::ObjectIdentifierType) { + Q_UNIMPLEMENTED(); + /* See paragraph from https://tools.ietf.org/html/rfc8018#appendix-A.2 + which ends with: "such facilities are deferred to a future version of PKCS #5" + */ + return {}; + } else { + return {}; + } + + // Iterations needed to derive the key + int iterationCount = keyDerivationParams[1].toInteger(); + // Optional integer + int keyLength = -1; + int vectorPos = 2; + if (keyDerivationParams.size() > vectorPos + && keyDerivationParams[vectorPos].type() == QAsn1Element::IntegerType) { + keyLength = keyDerivationParams[vectorPos].toInteger(nullptr); + ++vectorPos; + } else { + keyLength = cipherKeyLengthMap[cipher]; + } + + // Optional algorithm identifier (default: HMAC-SHA-1) + QCryptographicHash::Algorithm hashAlgorithm = QCryptographicHash::Sha1; + if (keyDerivationParams.size() > vectorPos + && keyDerivationParams[vectorPos].type() == QAsn1Element::SequenceType) { + const auto hashAlgorithmContainer = keyDerivationParams[vectorPos].toList(); + hashAlgorithm = pbes2OidHashFunctionMap[hashAlgorithmContainer.front().toObjectId()]; + Q_ASSERT(hashAlgorithmContainer[1].type() == QAsn1Element::NullType); + ++vectorPos; + } + Q_ASSERT(keyDerivationParams.size() == vectorPos); + + key = QPasswordDigestor::deriveKeyPbkdf2(hashAlgorithm, passPhrase, salt, iterationCount, keyLength); + } else { + qWarning() + << "QSslKey: Unsupported key derivation algorithm OID:" << keyDerivationAlgorithm + << "\nFile a bugreport to Qt (include the line above)."; + return {}; + } + return {cipher, key, iv}; +} + +// Maps OIDs to the hash function it specifies +const QMap pbes1OidHashFunctionMap { +#ifndef QT_CRYPTOGRAPHICHASH_ONLY_SHA1 + // PKCS5 + //{PKCS5_MD2_DES_CBC_OID, QCryptographicHash::Md2}, No MD2 + //{PKCS5_MD2_RC2_CBC_OID, QCryptographicHash::Md2}, + {PKCS5_MD5_DES_CBC_OID, QCryptographicHash::Md5}, + {PKCS5_MD5_RC2_CBC_OID, QCryptographicHash::Md5}, +#endif + {PKCS5_SHA1_DES_CBC_OID, QCryptographicHash::Sha1}, + {PKCS5_SHA1_RC2_CBC_OID, QCryptographicHash::Sha1}, + // PKCS12 (unimplemented) + // {PKCS12_SHA1_RC4_128_OID, QCryptographicHash::Sha1}, // No RC4 + // {PKCS12_SHA1_RC4_40_OID, QCryptographicHash::Sha1}, + // @todo: lacking support. @note: there might be code to do this inside qsslsocket_mac... + // further note that more work may be required for the 3DES variations listed to be available. + // {PKCS12_SHA1_3KEY_3DES_CBC_OID, QCryptographicHash::Sha1}, + // {PKCS12_SHA1_2KEY_3DES_CBC_OID, QCryptographicHash::Sha1}, + // {PKCS12_SHA1_RC2_128_CBC_OID, QCryptographicHash::Sha1}, + // {PKCS12_SHA1_RC2_40_CBC_OID, QCryptographicHash::Sha1} +}; + +EncryptionData readPbes1(const QList &element, const QByteArray &encryptionScheme, + const QByteArray &passPhrase) +{ + // RFC 8018: https://tools.ietf.org/html/rfc8018#section-6.1 + // Steps refer to this section: https://tools.ietf.org/html/rfc8018#section-6.1.2 + /*** Scheme: *** + * Sequence (PBE Parameter) + * Octet string (salt) + * Integer (iteration counter) + */ + // Step 1 + if (element.size() != 2 + || element[0].type() != QAsn1Element::ElementType::OctetStringType + || element[1].type() != QAsn1Element::ElementType::IntegerType) { + return {}; + } + QByteArray salt = element[0].value(); + if (salt.size() != 8) + return {}; + + int iterationCount = element[1].toInteger(); + if (iterationCount < 0) + return {}; + + // Step 2 + auto iterator = pbes1OidHashFunctionMap.constFind(encryptionScheme); + if (iterator == pbes1OidHashFunctionMap.cend()) { + // Qt was compiled with ONLY_SHA1 (or it's MD2) + return {}; + } + QCryptographicHash::Algorithm hashAlgorithm = *iterator; + QByteArray key = QPasswordDigestor::deriveKeyPbkdf1(hashAlgorithm, passPhrase, salt, iterationCount, 16); + if (key.size() != 16) + return {}; + + // Step 3 + QByteArray iv = key.right(8); // last 8 bytes are used as IV + key.truncate(8); // first 8 bytes are used for the key + + QSslKeyPrivate::Cipher cipher = oidCipherMap[encryptionScheme]; + // Steps 4-6 are done after returning + return {cipher, key, iv}; +} + +int curveBits(const QByteArray &oid) +{ + const int length = oidLengthMap->value(oid); + return length ? length : -1; +} + +int numberOfBits(const QByteArray &modulus) +{ + int bits = modulus.size() * 8; + for (int i = 0; i < modulus.size(); ++i) { + quint8 b = modulus[i]; + bits -= 8; + if (b != 0) { + bits += bits_table[b]; + break; + } + } + return bits; +} + +QByteArray deriveAesKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, + const QByteArray &iv) +{ + // This is somewhat simplified and shortened version of what OpenSSL does. + // See, for example, EVP_BytesToKey for the "algorithm" itself and elsewhere + // in their code for what they pass as arguments to EVP_BytesToKey when + // deriving encryption keys (when reading/writing pems files with encrypted + // keys). + + Q_ASSERT(iv.size() >= 8); + + QCryptographicHash hash(QCryptographicHash::Md5); + + QByteArray data(passPhrase); + data.append(iv.data(), 8); // AKA PKCS5_SALT_LEN in OpenSSL. + + hash.addData(data); + + if (cipher == Cipher::Aes128Cbc) + return hash.result(); + + QByteArray key(hash.result()); + hash.reset(); + hash.addData(key); + hash.addData(data); + + if (cipher == Cipher::Aes192Cbc) + return key.append(hash.result().constData(), 8); + + return key.append(hash.result()); +} + +QByteArray deriveKey(QSslKeyPrivate::Cipher cipher, const QByteArray &passPhrase, + const QByteArray &iv) +{ + QByteArray key; + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(passPhrase); + hash.addData(iv); + switch (cipher) { + case Cipher::DesCbc: + key = hash.result().left(8); + break; + case Cipher::DesEde3Cbc: + key = hash.result(); + hash.reset(); + hash.addData(key); + hash.addData(passPhrase); + hash.addData(iv); + key += hash.result().left(8); + break; + case Cipher::Rc2Cbc: + key = hash.result(); + break; + case Cipher::Aes128Cbc: + case Cipher::Aes192Cbc: + case Cipher::Aes256Cbc: + return deriveAesKey(cipher, passPhrase, iv); + } + return key; +} + +int extractPkcs8KeyLength(const QList &items, TlsKey *that) +{ + Q_ASSERT(items.size() == 3); + Q_ASSERT(that); + + int keyLength = -1; + + auto getName = [](QSsl::KeyAlgorithm algorithm) { + switch (algorithm){ + case QSsl::Rsa: return "RSA"; + case QSsl::Dsa: return "DSA"; + case QSsl::Dh: return "DH"; + case QSsl::Ec: return "EC"; + case QSsl::Opaque: return "Opaque"; + } + Q_UNREACHABLE(); + }; + + const auto pkcs8Info = items[1].toList(); + if (pkcs8Info.size() != 2 || pkcs8Info[0].type() != QAsn1Element::ObjectIdentifierType) + return -1; + const QByteArray value = pkcs8Info[0].toObjectId(); + if (value == RSA_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm() != QSsl::Rsa)) { + // We could change the 'algorithm' of QSslKey here and continue loading, but + // this is not supported in the openssl back-end, so we'll fail here and give + // the user some feedback. + qWarning() << "QSslKey: Found RSA key when asked to use" << getName(that->algorithm()) + << "\nLoading will fail."; + return -1; + } + // Luckily it contains the 'normal' RSA-key format inside, so we can just recurse + // and read the key's info. + that->decodeDer(that->type(), that->algorithm(), items[2].value(), {}, true); + // The real info has been filled out in the call above, so return as if it was invalid + // to avoid overwriting the data. + return -1; + } else if (value == EC_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm() != QSsl::Ec)) { + // As above for RSA. + qWarning() << "QSslKey: Found EC key when asked to use" << getName(that->algorithm()) + << "\nLoading will fail."; + return -1; + } + // I don't know where this is documented, but the elliptic-curve identifier has been + // moved into the "pkcs#8 wrapper", which is what we're interested in. + if (pkcs8Info[1].type() != QAsn1Element::ObjectIdentifierType) + return -1; + keyLength = curveBits(pkcs8Info[1].toObjectId()); + } else if (value == DSA_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm() != QSsl::Dsa)) { + // As above for RSA. + qWarning() << "QSslKey: Found DSA when asked to use" << getName(that->algorithm()) + << "\nLoading will fail."; + return -1; + } + // DSA's structure is documented here: + // https://www.cryptsoft.com/pkcs11doc/STANDARD/v201-95.pdf in section 11.9. + if (pkcs8Info[1].type() != QAsn1Element::SequenceType) + return -1; + const auto dsaInfo = pkcs8Info[1].toList(); + if (dsaInfo.size() != 3 || dsaInfo[0].type() != QAsn1Element::IntegerType) + return -1; + keyLength = numberOfBits(dsaInfo[0].value()); + } else if (value == DH_ENCRYPTION_OID) { + if (Q_UNLIKELY(that->algorithm() != QSsl::Dh)) { + // As above for RSA. + qWarning() << "QSslKey: Found DH when asked to use" << getName(that->algorithm()) + << "\nLoading will fail."; + return -1; + } + // DH's structure is documented here: + // https://www.cryptsoft.com/pkcs11doc/STANDARD/v201-95.pdf in section 11.9. + if (pkcs8Info[1].type() != QAsn1Element::SequenceType) + return -1; + const auto dhInfo = pkcs8Info[1].toList(); + if (dhInfo.size() < 2 || dhInfo.size() > 3 || dhInfo[0].type() != QAsn1Element::IntegerType) + return -1; + keyLength = numberOfBits(dhInfo[0].value()); + } else { + // in case of unexpected formats: + qWarning() << "QSslKey: Unsupported PKCS#8 key algorithm:" << value + << "\nFile a bugreport to Qt (include the line above)."; + return -1; + } + + return keyLength; +} + +} // Unnamed namespace + +void TlsKeyGeneric::decodeDer(QSsl::KeyType type, QSsl::KeyAlgorithm algorithm, const QByteArray &der, + const QByteArray &passPhrase, bool deepClear) +{ + keyType = type; + keyAlgorithm = algorithm; + + clear(deepClear); + + if (der.isEmpty()) + return; + // decryptPkcs8 decrypts if necessary or returns 'der' unaltered + QByteArray decryptedDer = decryptPkcs8(der, passPhrase); + + QAsn1Element elem; + if (!elem.read(decryptedDer) || elem.type() != QAsn1Element::SequenceType) + return; + + if (type == QSsl::PublicKey) { + // key info + QDataStream keyStream(elem.value()); + if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) + return; + const auto infoItems = elem.toList(); + if (infoItems.size() < 2 || infoItems[0].type() != QAsn1Element::ObjectIdentifierType) + return; + if (algorithm == QSsl::Rsa) { + if (infoItems[0].toObjectId() != RSA_ENCRYPTION_OID) + return; + // key data + if (!elem.read(keyStream) || elem.type() != QAsn1Element::BitStringType || elem.value().isEmpty()) + return; + if (!elem.read(elem.value().mid(1)) || elem.type() != QAsn1Element::SequenceType) + return; + if (!elem.read(elem.value()) || elem.type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(elem.value()); + } else if (algorithm == QSsl::Dsa) { + if (infoItems[0].toObjectId() != DSA_ENCRYPTION_OID) + return; + if (infoItems[1].type() != QAsn1Element::SequenceType) + return; + // key params + const auto params = infoItems[1].toList(); + if (params.isEmpty() || params[0].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(params[0].value()); + } else if (algorithm == QSsl::Dh) { + if (infoItems[0].toObjectId() != DH_ENCRYPTION_OID) + return; + if (infoItems[1].type() != QAsn1Element::SequenceType) + return; + // key params + const auto params = infoItems[1].toList(); + if (params.isEmpty() || params[0].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(params[0].value()); + } else if (algorithm == QSsl::Ec) { + if (infoItems[0].toObjectId() != EC_ENCRYPTION_OID) + return; + if (infoItems[1].type() != QAsn1Element::ObjectIdentifierType) + return; + keyLength = curveBits(infoItems[1].toObjectId()); + } + + } else { + const auto items = elem.toList(); + if (items.isEmpty()) + return; + + // version + if (items[0].type() != QAsn1Element::IntegerType) + return; + const QByteArray versionHex = items[0].value().toHex(); + + if (items.size() == 3 && items[1].type() == QAsn1Element::SequenceType + && items[2].type() == QAsn1Element::OctetStringType) { + if (versionHex != "00" && versionHex != "01") + return; + int pkcs8KeyLength = extractPkcs8KeyLength(items, this); + if (pkcs8KeyLength == -1) + return; + pkcs8 = true; + keyLength = pkcs8KeyLength; + } else if (algorithm == QSsl::Rsa) { + if (versionHex != "00") + return; + if (items.size() != 9 || items[1].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(items[1].value()); + } else if (algorithm == QSsl::Dsa) { + if (versionHex != "00") + return; + if (items.size() != 6 || items[1].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(items[1].value()); + } else if (algorithm == QSsl::Dh) { + if (versionHex != "00") + return; + if (items.size() < 5 || items.size() > 6 || items[1].type() != QAsn1Element::IntegerType) + return; + keyLength = numberOfBits(items[1].value()); + } else if (algorithm == QSsl::Ec) { + if (versionHex != "01") + return; + if (items.size() != 4 + || items[1].type() != QAsn1Element::OctetStringType + || items[2].type() != QAsn1Element::Context0Type + || items[3].type() != QAsn1Element::Context1Type) + return; + QAsn1Element oidElem; + if (!oidElem.read(items[2].value()) + || oidElem.type() != QAsn1Element::ObjectIdentifierType) + return; + keyLength = curveBits(oidElem.toObjectId()); + } + } + + derData = decryptedDer; + keyIsNull = false; +} + +void TlsKeyGeneric::decodePem(QSsl::KeyType type, QSsl::KeyAlgorithm algorithm, const QByteArray &pem, + const QByteArray &passPhrase, bool deepClear) +{ + keyType = type; + keyAlgorithm = algorithm; + + QMap headers; + QByteArray data = derFromPem(pem, &headers); + + if (headers.value("Proc-Type") == "4,ENCRYPTED") { + const QList dekInfo = headers.value("DEK-Info").split(','); + if (dekInfo.size() != 2) { + clear(deepClear); + return; + } + + QSslKeyPrivate::Cipher cipher; + if (dekInfo.first() == "DES-CBC") { + cipher = Cipher::DesCbc; + } else if (dekInfo.first() == "DES-EDE3-CBC") { + cipher = Cipher::DesEde3Cbc; + } else if (dekInfo.first() == "RC2-CBC") { + cipher = Cipher::Rc2Cbc; + } else if (dekInfo.first() == "AES-128-CBC") { + cipher = Cipher::Aes128Cbc; + } else if (dekInfo.first() == "AES-192-CBC") { + cipher = Cipher::Aes192Cbc; + } else if (dekInfo.first() == "AES-256-CBC") { + cipher = Cipher::Aes256Cbc; + } else { + clear(deepClear); + return; + } + + const QByteArray iv = QByteArray::fromHex(dekInfo.last()); + const QByteArray key = deriveKey(cipher, passPhrase, iv); + data = decrypt(cipher, data, key, iv); + } + + decodeDer(keyType, keyAlgorithm, data, passPhrase, deepClear); +} + +QByteArray TlsKeyGeneric::toPem(const QByteArray &passPhrase) const +{ + QByteArray data; + QMap headers; + + if (type() == QSsl::PrivateKey && !passPhrase.isEmpty()) { + // ### use a cryptographically secure random number generator + quint64 random = QRandomGenerator::system()->generate64(); + QByteArray iv = QByteArray::fromRawData(reinterpret_cast(&random), sizeof(random)); + + auto cipher = Cipher::DesEde3Cbc; + const QByteArray key = deriveKey(cipher, passPhrase, iv); + data = encrypt(cipher, derData, key, iv); + + headers.insert("Proc-Type", "4,ENCRYPTED"); + headers.insert("DEK-Info", "DES-EDE3-CBC," + iv.toHex()); + } else { + data = derData; + } + + return pemFromDer(data, headers); +} + +QByteArray TlsKeyGeneric::derFromPem(const QByteArray &pem, QMap *headers) const +{ + if (derData.size()) + return derData; + + QByteArray header = pemHeader(); + QByteArray footer = pemFooter(); + + QByteArray der(pem); + + int headerIndex = der.indexOf(header); + int footerIndex = der.indexOf(footer, headerIndex + header.length()); + if (type() != QSsl::PublicKey) { + if (headerIndex == -1 || footerIndex == -1) { + header = pkcs8Header(true); + footer = pkcs8Footer(true); + headerIndex = der.indexOf(header); + footerIndex = der.indexOf(footer, headerIndex + header.length()); + } + if (headerIndex == -1 || footerIndex == -1) { + header = pkcs8Header(false); + footer = pkcs8Footer(false); + headerIndex = der.indexOf(header); + footerIndex = der.indexOf(footer, headerIndex + header.length()); + } + } + if (headerIndex == -1 || footerIndex == -1) + return QByteArray(); + + der = der.mid(headerIndex + header.size(), footerIndex - (headerIndex + header.size())); + + if (der.contains("Proc-Type:")) { + // taken from QHttpNetworkReplyPrivate::parseHeader + int i = 0; + while (i < der.count()) { + int j = der.indexOf(':', i); // field-name + if (j == -1) + break; + const QByteArray field = der.mid(i, j - i).trimmed(); + j++; + // any number of LWS is allowed before and after the value + QByteArray value; + do { + i = der.indexOf('\n', j); + if (i == -1) + break; + if (!value.isEmpty()) + value += ' '; + // check if we have CRLF or only LF + bool hasCR = (i && der[i-1] == '\r'); + int length = i -(hasCR ? 1: 0) - j; + value += der.mid(j, length).trimmed(); + j = ++i; + } while (i < der.count() && (der.at(i) == ' ' || der.at(i) == '\t')); + if (i == -1) + break; // something is wrong + + headers->insert(field, value); + } + der = der.mid(i); + } + + return QByteArray::fromBase64(der); // ignores newlines +} + +void TlsKeyGeneric::fromHandle(Qt::HANDLE handle, KeyType expectedType) +{ + opaque = handle; + keyType = expectedType; +} + +void TlsKeyGeneric::clear(bool deep) +{ + keyIsNull = true; + if (deep) + std::memset(derData.data(), 0, derData.size()); + derData.clear(); + keyLength = -1; +} + +QByteArray TlsKeyGeneric::decryptPkcs8(const QByteArray &encrypted, const QByteArray &passPhrase) +{ + // RFC 5958: https://tools.ietf.org/html/rfc5958 + /*** Scheme: *** + * Sequence + * Sequence + * Object Identifier (encryption scheme (currently PBES2, PBES1, @todo PKCS12)) + * Sequence (scheme parameters) + * Octet String (the encrypted data) + */ + QAsn1Element elem; + if (!elem.read(encrypted) || elem.type() != QAsn1Element::SequenceType) + return encrypted; + + const auto items = elem.toList(); + if (items.size() != 2 + || items[0].type() != QAsn1Element::SequenceType + || items[1].type() != QAsn1Element::OctetStringType) { + return encrypted; + } + + const auto encryptionSchemeContainer = items[0].toList(); + + if (encryptionSchemeContainer.size() != 2 + || encryptionSchemeContainer[0].type() != QAsn1Element::ObjectIdentifierType + || encryptionSchemeContainer[1].type() != QAsn1Element::SequenceType) { + return encrypted; + } + + const QByteArray encryptionScheme = encryptionSchemeContainer[0].toObjectId(); + const auto schemeParameterContainer = encryptionSchemeContainer[1].toList(); + + if (schemeParameterContainer.size() != 2 + && schemeParameterContainer[0].type() != QAsn1Element::SequenceType + && schemeParameterContainer[1].type() != QAsn1Element::SequenceType) { + return encrypted; + } + + EncryptionData data; + if (encryptionScheme == PKCS5_PBES2_ENCRYPTION_OID) { + data = readPbes2(schemeParameterContainer, passPhrase); + } else if (pbes1OidHashFunctionMap.contains(encryptionScheme)) { + data = readPbes1(schemeParameterContainer, encryptionScheme, passPhrase); + } else if (encryptionScheme.startsWith(PKCS12_OID)) { + Q_UNIMPLEMENTED(); // this isn't some 'unknown', I know these aren't implemented + return encrypted; + } else { + qWarning() + << "QSslKey: Unsupported encryption scheme OID:" << encryptionScheme + << "\nFile a bugreport to Qt (include the line above)."; + return encrypted; + } + + if (!data.initialized) { + // something went wrong, return + return encrypted; + } + + QByteArray decryptedKey = decrypt(data.cipher, items[1].value(), data.key, data.iv); + // The data is still wrapped in a octet string, so let's unwrap it + QAsn1Element decryptedKeyElement(QAsn1Element::ElementType::OctetStringType, decryptedKey); + return decryptedKeyElement.value(); +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qtlskey_generic_p.h b/src/plugins/tls/shared/qtlskey_generic_p.h new file mode 100644 index 0000000000..beb3d410a6 --- /dev/null +++ b/src/plugins/tls/shared/qtlskey_generic_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QTLSKEY_GENERIC_P_H +#define QTLSKEY_GENERIC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include "qtlskey_base_p.h" + + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +// This class is what previously was known as qsslkey_qt: +// it implements most of functionality needed by QSslKey +// not relying on any TLS implementation. It's used by +// our SecureTransport and Schannel backends. +class TlsKeyGeneric : public TlsKeyBase +{ +public: + using TlsKeyBase::TlsKeyBase; + + void decodeDer(KeyType type, KeyAlgorithm algorithm, const QByteArray &der, + const QByteArray &passPhrase, bool deepClear) override; + void decodePem(KeyType type, KeyAlgorithm algorithm, const QByteArray &pem, + const QByteArray &passPhrase, bool deepClear) override; + + QByteArray toPem(const QByteArray &passPhrase) const override; + + QByteArray derFromPem(const QByteArray &pem, QMap *headers) const override; + + void fromHandle(Qt::HANDLE opaque, KeyType expectedType) override; + + void clear(bool deep) override; + + Qt::HANDLE handle() const override + { + return Qt::HANDLE(opaque); + } + + int length() const override + { + return keyLength; + } + + bool isPkcs8() const override + { + return pkcs8; + } + +private: + QByteArray decryptPkcs8(const QByteArray &encrypted, const QByteArray &passPhrase); + + bool pkcs8 = false; + Qt::HANDLE opaque = nullptr; + QByteArray derData; + int keyLength = -1; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QTLSKEY_GENERIC_P_H diff --git a/src/plugins/tls/shared/qwincrypt_p.h b/src/plugins/tls/shared/qwincrypt_p.h new file mode 100644 index 0000000000..2a7bd1fae2 --- /dev/null +++ b/src/plugins/tls/shared/qwincrypt_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QWINCRYPT_P_H +#define QWINCRYPT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include + +#include +#ifndef HCRYPTPROV_LEGACY +#define HCRYPTPROV_LEGACY HCRYPTPROV +#endif // !HCRYPTPROV_LEGACY + +#include + +QT_BEGIN_NAMESPACE + +struct QHCertStoreDeleter { + void operator()(HCERTSTORE store) + { + CertCloseStore(store, 0); + } +}; + +// A simple RAII type used by Schannel code and Window CA fetcher class: +using QHCertStorePointer = std::unique_ptr; + +QT_END_NAMESPACE + +#endif // QWINCRYPT_P_H diff --git a/src/plugins/tls/shared/qx509_base.cpp b/src/plugins/tls/shared/qx509_base.cpp new file mode 100644 index 0000000000..d7b7b81606 --- /dev/null +++ b/src/plugins/tls/shared/qx509_base.cpp @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qx509_base_p.h" + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +QByteArray X509CertificateBase::subjectInfoToString(QSslCertificate::SubjectInfo info) +{ + QByteArray str; + switch (info) { + case QSslCertificate::Organization: str = QByteArray("O"); break; + case QSslCertificate::CommonName: str = QByteArray("CN"); break; + case QSslCertificate::LocalityName: str = QByteArray("L"); break; + case QSslCertificate::OrganizationalUnitName: str = QByteArray("OU"); break; + case QSslCertificate::CountryName: str = QByteArray("C"); break; + case QSslCertificate::StateOrProvinceName: str = QByteArray("ST"); break; + case QSslCertificate::DistinguishedNameQualifier: str = QByteArray("dnQualifier"); break; + case QSslCertificate::SerialNumber: str = QByteArray("serialNumber"); break; + case QSslCertificate::EmailAddress: str = QByteArray("emailAddress"); break; + } + + return str; +} + +bool X509CertificateBase::matchLineFeed(const QByteArray &pem, int *offset) +{ + Q_ASSERT(offset); + + char ch = 0; + // ignore extra whitespace at the end of the line + while (*offset < pem.size() && (ch = pem.at(*offset)) == ' ') + ++*offset; + + if (ch == '\n') { + *offset += 1; + return true; + } + + if (ch == '\r' && pem.size() > (*offset + 1) && pem.at(*offset + 1) == '\n') { + *offset += 2; + return true; + } + + return false; +} + +bool X509CertificateBase::isNull() const +{ + return null; +} + +QByteArray X509CertificateBase::version() const +{ + return versionString; +} + +QByteArray X509CertificateBase::serialNumber() const +{ + return serialNumberString; +} + +QStringList X509CertificateBase::issuerInfo(QSslCertificate::SubjectInfo info) const +{ + return issuerInfo(subjectInfoToString(info)); +} + +QStringList X509CertificateBase::issuerInfo(const QByteArray &attribute) const +{ + return issuerInfoEntries.values(attribute); +} + +QStringList X509CertificateBase::subjectInfo(QSslCertificate::SubjectInfo info) const +{ + return subjectInfo(subjectInfoToString(info)); +} + +QStringList X509CertificateBase::subjectInfo(const QByteArray &attribute) const +{ + return subjectInfoEntries.values(attribute); +} + +QList X509CertificateBase::subjectInfoAttributes() const +{ + return subjectInfoEntries.uniqueKeys(); +} + +QList X509CertificateBase::issuerInfoAttributes() const +{ + return issuerInfoEntries.uniqueKeys(); +} + +QDateTime X509CertificateBase::effectiveDate() const +{ + return notValidBefore; +} + +QDateTime X509CertificateBase::expiryDate() const +{ + return notValidAfter; +} + +qsizetype X509CertificateBase::numberOfExtensions() const +{ + return extensions.size(); +} + +QString X509CertificateBase::oidForExtension(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].oid; +} + +QString X509CertificateBase::nameForExtension(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].name; +} + +QVariant X509CertificateBase::valueForExtension(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].value; +} + +bool X509CertificateBase::isExtensionCritical(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].critical; +} + +bool X509CertificateBase::isExtensionSupported(qsizetype index) const +{ + Q_ASSERT(validIndex(index)); + return extensions[index].supported; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qx509_base_p.h b/src/plugins/tls/shared/qx509_base_p.h new file mode 100644 index 0000000000..b86b573512 --- /dev/null +++ b/src/plugins/tls/shared/qx509_base_p.h @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef QX509CERTIFICATE_BASE_P_H +#define QX509CERTIFICATE_BASE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +class X509CertificateBase : public X509Certificate +{ +public: + bool isNull() const override; + QByteArray version() const override; + QByteArray serialNumber() const override; + QStringList issuerInfo(QSslCertificate::SubjectInfo info) const override; + QStringList issuerInfo(const QByteArray &attribute) const override; + QStringList subjectInfo(QSslCertificate::SubjectInfo info) const override; + QStringList subjectInfo(const QByteArray &attribute) const override; + QList subjectInfoAttributes() const override; + QList issuerInfoAttributes() const override; + QDateTime effectiveDate() const override; + QDateTime expiryDate() const override; + + qsizetype numberOfExtensions() const override; + QString oidForExtension(qsizetype index) const override; + QString nameForExtension(qsizetype index) const override; + QVariant valueForExtension(qsizetype index) const override; + bool isExtensionCritical(qsizetype index) const override; + bool isExtensionSupported(qsizetype index) const override; + + static QByteArray subjectInfoToString(QSslCertificate::SubjectInfo info); + static bool matchLineFeed(const QByteArray &pem, int *offset); + +protected: + bool validIndex(qsizetype index) const + { + return index >= 0 && index < extensions.size(); + } + + bool null = true; + QByteArray versionString; + QByteArray serialNumberString; + + QMultiMap issuerInfoEntries; + QMultiMap subjectInfoEntries; + QDateTime notValidAfter; + QDateTime notValidBefore; + + struct X509CertificateExtension + { + QString oid; + QString name; + QVariant value; + bool critical = false; + bool supported = false; + }; + + QList extensions; +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QX509CERTIFICATE_BASE_P_H diff --git a/src/plugins/tls/shared/qx509_generic.cpp b/src/plugins/tls/shared/qx509_generic.cpp new file mode 100644 index 0000000000..9265498c4e --- /dev/null +++ b/src/plugins/tls/shared/qx509_generic.cpp @@ -0,0 +1,468 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include +#include + +#include "qasn1element_p.h" +#include "qx509_generic_p.h" + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +namespace { + +QByteArray colonSeparatedHex(const QByteArray &value) +{ + const int size = value.size(); + int i = 0; + while (i < size && !value.at(i)) // skip leading zeros + ++i; + + return value.mid(i).toHex(':'); +} + +} // Unnamed namespace. + +bool X509CertificateGeneric::isEqual(const X509Certificate &rhs) const +{ + const auto &other = static_cast(rhs); + return derData == other.derData; +} + +bool X509CertificateGeneric::isSelfSigned() const +{ + if (null) + return false; + + qCWarning(lcTlsBackend, "QSslCertificate::isSelfSigned: This function does not check, whether the certificate " + "is actually signed. It just checks whether issuer and subject are identical"); + return subjectMatchesIssuer; +} + +QMultiMap X509CertificateGeneric::subjectAlternativeNames() const +{ + return saNames; +} + +#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----" +#define ENDCERTSTRING "-----END CERTIFICATE-----" + +QByteArray X509CertificateGeneric::toPem() const +{ + QByteArray array = toDer(); + // Convert to Base64 - wrap at 64 characters. + array = array.toBase64(); + QByteArray tmp; + for (int i = 0; i <= array.size() - 64; i += 64) { + tmp += QByteArray::fromRawData(array.data() + i, 64); + tmp += '\n'; + } + if (int remainder = array.size() % 64) { + tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder); + tmp += '\n'; + } + + return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n"; +} + +QByteArray X509CertificateGeneric::toDer() const +{ + return derData; +} + +QString X509CertificateGeneric::toText() const +{ + Q_UNIMPLEMENTED(); + return {}; +} + +Qt::HANDLE X509CertificateGeneric::handle() const +{ + Q_UNIMPLEMENTED(); + return nullptr; +} + +size_t X509CertificateGeneric::hash(size_t seed) const noexcept +{ + return qHash(toDer(), seed); +} + +QList X509CertificateGeneric::certificatesFromPem(const QByteArray &pem, int count) +{ + QList certificates; + int offset = 0; + while (count == -1 || certificates.size() < count) { + int startPos = pem.indexOf(BEGINCERTSTRING, offset); + if (startPos == -1) + break; + startPos += sizeof(BEGINCERTSTRING) - 1; + if (!matchLineFeed(pem, &startPos)) + break; + + int endPos = pem.indexOf(ENDCERTSTRING, startPos); + if (endPos == -1) + break; + + offset = endPos + sizeof(ENDCERTSTRING) - 1; + if (offset < pem.size() && !matchLineFeed(pem, &offset)) + break; + + QByteArray decoded = QByteArray::fromBase64( + QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); + certificates << certificatesFromDer(decoded, 1);; + } + + return certificates; +} + +QList X509CertificateGeneric::certificatesFromDer(const QByteArray &der, int count) +{ + QList certificates; + + QByteArray data = der; + while (count == -1 || certificates.size() < count) { + QSslCertificate cert; + auto *certBackend = QTlsBackend::backend(cert); + if (!certBackend->parse(data)) + break; + + certificates << cert; + data.remove(0, certBackend->derData.size()); + } + + return certificates; +} + +bool X509CertificateGeneric::parse(const QByteArray &data) +{ + QAsn1Element root; + + QDataStream dataStream(data); + if (!root.read(dataStream) || root.type() != QAsn1Element::SequenceType) + return false; + + QDataStream rootStream(root.value()); + QAsn1Element cert; + if (!cert.read(rootStream) || cert.type() != QAsn1Element::SequenceType) + return false; + + // version or serial number + QAsn1Element elem; + QDataStream certStream(cert.value()); + if (!elem.read(certStream)) + return false; + + if (elem.type() == QAsn1Element::Context0Type) { + QDataStream versionStream(elem.value()); + if (!elem.read(versionStream) + || elem.type() != QAsn1Element::IntegerType + || elem.value().isEmpty()) + return false; + + versionString = QByteArray::number(elem.value().at(0) + 1); + if (!elem.read(certStream)) + return false; + } else { + versionString = QByteArray::number(1); + } + + // serial number + if (elem.type() != QAsn1Element::IntegerType) + return false; + serialNumberString = colonSeparatedHex(elem.value()); + + // algorithm ID + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + // issuer info + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); + issuerInfoEntries = elem.toInfo(); + + // validity period + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QDataStream validityStream(elem.value()); + if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) + return false; + + notValidBefore = elem.toDateTime(); + if (!notValidBefore.isValid()) + return false; + + if (!elem.read(validityStream) || (elem.type() != QAsn1Element::UtcTimeType && elem.type() != QAsn1Element::GeneralizedTimeType)) + return false; + + notValidAfter = elem.toDateTime(); + if (!notValidAfter.isValid()) + return false; + + + // subject name + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length()); + subjectInfoEntries = elem.toInfo(); + subjectMatchesIssuer = issuerDer == subjectDer; + + // public key + qint64 keyStart = certStream.device()->pos(); + if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + publicKeyDerData.resize(certStream.device()->pos() - keyStart); + QDataStream keyStream(elem.value()); + if (!elem.read(keyStream) || elem.type() != QAsn1Element::SequenceType) + return false; + + + // key algorithm + if (!elem.read(elem.value()) || elem.type() != QAsn1Element::ObjectIdentifierType) + return false; + + const QByteArray oid = elem.toObjectId(); + if (oid == RSA_ENCRYPTION_OID) + publicKeyAlgorithm = QSsl::Rsa; + else if (oid == DSA_ENCRYPTION_OID) + publicKeyAlgorithm = QSsl::Dsa; + else if (oid == EC_ENCRYPTION_OID) + publicKeyAlgorithm = QSsl::Ec; + else + publicKeyAlgorithm = QSsl::Opaque; + + certStream.device()->seek(keyStart); + certStream.readRawData(publicKeyDerData.data(), publicKeyDerData.size()); + + // extensions + while (elem.read(certStream)) { + if (elem.type() == QAsn1Element::Context3Type) { + if (elem.read(elem.value()) && elem.type() == QAsn1Element::SequenceType) { + QDataStream extStream(elem.value()); + while (elem.read(extStream) && elem.type() == QAsn1Element::SequenceType) { + X509CertificateExtension extension; + if (!parseExtension(elem.value(), extension)) + return false; + + if (extension.oid == QLatin1String("2.5.29.17")) { + // subjectAltName + + // Note, parseExtension() returns true for this extensions, + // but considers it to be unsupported and assignes a useless + // value. OpenSSL also treats this extension as unsupported, + // but properly creates a map with 'name' and 'value' taken + // from the extension. We only support 'email', 'IP' and 'DNS', + // but this is what our subjectAlternativeNames map can contain + // anyway. + QVariantMap extValue; + QAsn1Element sanElem; + if (sanElem.read(extension.value.toByteArray()) && sanElem.type() == QAsn1Element::SequenceType) { + QDataStream nameStream(sanElem.value()); + QAsn1Element nameElem; + while (nameElem.read(nameStream)) { + switch (nameElem.type()) { + case QAsn1Element::Rfc822NameType: + saNames.insert(QSsl::EmailEntry, nameElem.toString()); + extValue[QStringLiteral("email")] = nameElem.toString(); + break; + case QAsn1Element::DnsNameType: + saNames.insert(QSsl::DnsEntry, nameElem.toString()); + extValue[QStringLiteral("DNS")] = nameElem.toString(); + break; + case QAsn1Element::IpAddressType: { + QHostAddress ipAddress; + QByteArray ipAddrValue = nameElem.value(); + switch (ipAddrValue.length()) { + case 4: // IPv4 + ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast(ipAddrValue.data()))); + break; + case 16: // IPv6 + ipAddress = QHostAddress(reinterpret_cast(ipAddrValue.data())); + break; + default: // Unknown IP address format + break; + } + if (!ipAddress.isNull()) { + saNames.insert(QSsl::IpAddressEntry, ipAddress.toString()); + extValue[QStringLiteral("IP")] = ipAddress.toString(); + } + break; + } + default: + break; + } + } + extension.value = extValue; + extension.supported = true; + } + } + + extensions << extension; + } + } + } + } + + derData = data.left(dataStream.device()->pos()); + null = false; + return true; +} + +bool X509CertificateGeneric::parseExtension(const QByteArray &data, X509CertificateExtension &extension) +{ + bool ok = false; + bool critical = false; + QAsn1Element oidElem, valElem; + + QDataStream seqStream(data); + + // oid + if (!oidElem.read(seqStream) || oidElem.type() != QAsn1Element::ObjectIdentifierType) + return false; + + const QByteArray oid = oidElem.toObjectId(); + // critical and value + if (!valElem.read(seqStream)) + return false; + + if (valElem.type() == QAsn1Element::BooleanType) { + critical = valElem.toBool(&ok); + + if (!ok || !valElem.read(seqStream)) + return false; + } + + if (valElem.type() != QAsn1Element::OctetStringType) + return false; + + // interpret value + QAsn1Element val; + bool supported = true; + QVariant value; + if (oid == "1.3.6.1.5.5.7.1.1") { + // authorityInfoAccess + if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) + return false; + QVariantMap result; + const auto elems = val.toList(); + for (const QAsn1Element &el : elems) { + const auto items = el.toList(); + if (items.size() != 2) + return false; + const QString key = QString::fromLatin1(items.at(0).toObjectName()); + switch (items.at(1).type()) { + case QAsn1Element::Rfc822NameType: + case QAsn1Element::DnsNameType: + case QAsn1Element::UniformResourceIdentifierType: + result[key] = items.at(1).toString(); + break; + } + } + value = result; + } else if (oid == "2.5.29.14") { + // subjectKeyIdentifier + if (!val.read(valElem.value()) || val.type() != QAsn1Element::OctetStringType) + return false; + value = colonSeparatedHex(val.value()).toUpper(); + } else if (oid == "2.5.29.19") { + // basicConstraints + if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) + return false; + + QVariantMap result; + const auto items = val.toList(); + if (items.size() > 0) { + result[QStringLiteral("ca")] = items.at(0).toBool(&ok); + if (!ok) + return false; + } else { + result[QStringLiteral("ca")] = false; + } + if (items.size() > 1) { + result[QStringLiteral("pathLenConstraint")] = items.at(1).toInteger(&ok); + if (!ok) + return false; + } + value = result; + } else if (oid == "2.5.29.35") { + // authorityKeyIdentifier + if (!val.read(valElem.value()) || val.type() != QAsn1Element::SequenceType) + return false; + QVariantMap result; + const auto elems = val.toList(); + for (const QAsn1Element &el : elems) { + if (el.type() == 0x80) { + const QString key = QStringLiteral("keyid"); + result[key] = el.value().toHex(); + } else if (el.type() == 0x82) { + const QString serial = QStringLiteral("serial"); + result[serial] = colonSeparatedHex(el.value()); + } + } + value = result; + } else { + supported = false; + value = valElem.value(); + } + + extension.critical = critical; + extension.supported = supported; + extension.oid = QString::fromLatin1(oid); + extension.name = QString::fromLatin1(oidElem.toObjectName()); + extension.value = value; + + return true; +} + +} // namespace QTlsPrivate + +QT_END_NAMESPACE diff --git a/src/plugins/tls/shared/qx509_generic_p.h b/src/plugins/tls/shared/qx509_generic_p.h new file mode 100644 index 0000000000..3e99dcde62 --- /dev/null +++ b/src/plugins/tls/shared/qx509_generic_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifndef QX509_GENERIC_P_H +#define QX509_GENERIC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +#include "qx509_base_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace QTlsPrivate { + +// A part of SecureTransport and Schannel plugin. +class X509CertificateGeneric : public X509CertificateBase +{ +public: + bool isEqual(const X509Certificate &rhs) const override; + bool isSelfSigned() const override; + + QMultiMap subjectAlternativeNames() const override; + QByteArray toPem() const override; + QByteArray toDer() const override; + QString toText() const override; + Qt::HANDLE handle() const override; + + size_t hash(size_t seed) const noexcept override; + + static QList certificatesFromPem(const QByteArray &pem, int count); + static QList certificatesFromDer(const QByteArray &der, int count); + +protected: + + bool subjectMatchesIssuer = false; + QSsl::KeyAlgorithm publicKeyAlgorithm = QSsl::Rsa; + QByteArray publicKeyDerData; + + QMultiMap saNames; + QByteArray derData; + + bool parse(const QByteArray &data); + bool parseExtension(const QByteArray &data, X509CertificateExtension &extension); +}; + +} // namespace QTlsPrivate + +QT_END_NAMESPACE + +#endif // QX509_GENERIC_P_H -- cgit v1.2.3