diff options
author | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2021-01-26 17:20:29 +0100 |
---|---|---|
committer | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2021-02-07 16:10:18 +0100 |
commit | 405337ee7276be4b76e86745c0694c51283b6b07 (patch) | |
tree | 563beb2e35aa24edf8b6346ca42a42850cc0488f /src | |
parent | 137f5518b84439845aeb5d115341b32fee33bf10 (diff) |
Introduce QSsl::X509Certificate interface and its implementations
To enable QSslCertificate to use TLS plugins. All backend-specific
code is to be moved from QSslCertificate(Private) making them
backend-neutral.
Pick-to: dev
Task-number: QTBUG-90954
Task-number: QTBUG-65922
Change-Id: Ic9d5abf91e42ce81fe56239f95ae97b64035e950
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Diffstat (limited to 'src')
27 files changed, 2890 insertions, 192 deletions
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 9c9976441f..756d443033 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -328,6 +328,7 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_ssl ssl/qsslsocket.cpp ssl/qsslsocket.h ssl/qsslsocket_p.h ssl/qtlsbackend.cpp ssl/qtlsbackend_p.h ssl/qtlskey_base.cpp ssl/qtlskey_base_p.h + ssl/qx509_base.cpp ssl/qx509_base_p.h ) qt_internal_extend_target(Network CONDITION QT_FEATURE_schannel AND QT_FEATURE_ssl @@ -339,8 +340,11 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_schannel AND QT_FEATURE_s ssl/qsslkey_schannel.cpp ssl/qsslsocket_qt.cpp ssl/qsslsocket_schannel.cpp ssl/qsslsocket_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_generic.cpp ssl/qx509_generic_p.h + ssl/qx509_schannel.cpp ssl/qx509_schannel_p.h LIBRARIES Crypt32 Secur32 @@ -359,6 +363,9 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_securetransport AND QT_FE 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_generic.cpp ssl/qx509_generic_p.h + ssl/qx509_st.cpp ssl/qx509_st_p.h ) qt_internal_extend_target(Network CONDITION QT_FEATURE_dtls AND QT_FEATURE_ssl @@ -376,6 +383,8 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_openssl AND QT_FEATURE_ss ssl/qsslsocket_openssl.cpp ssl/qsslsocket_openssl_p.h ssl/qsslsocket_openssl_symbols.cpp ssl/qsslsocket_openssl_symbols_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 ) diff --git a/src/network/ssl/qsslcertificate.h b/src/network/ssl/qsslcertificate.h index cf97dcbee8..a2ba90465f 100644 --- a/src/network/ssl/qsslcertificate.h +++ b/src/network/ssl/qsslcertificate.h @@ -66,6 +66,12 @@ class QSslCertificate; // qHash is a friend, but we can't use default arguments for friends (ยง8.3.6.4) Q_NETWORK_EXPORT size_t qHash(const QSslCertificate &key, size_t seed = 0) noexcept; +namespace QSsl { + +class X509Certificate; + +} // namespace QSsl. + class QSslCertificatePrivate; class Q_NETWORK_EXPORT QSslCertificate { @@ -157,9 +163,14 @@ public: Qt::HANDLE handle() const; private: + QSsl::X509Certificate *backendImplementation() const + { + return nullptr; // TLSTODO + } QExplicitlySharedDataPointer<QSslCertificatePrivate> d; friend class QSslCertificatePrivate; friend class QSslSocketBackendPrivate; + friend class QTlsBackend; friend Q_NETWORK_EXPORT size_t qHash(const QSslCertificate &key, size_t seed) noexcept; }; diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qsslsocket_mac.cpp index c4d5b3ece7..3747456bf7 100644 --- a/src/network/ssl/qsslsocket_mac.cpp +++ b/src/network/ssl/qsslsocket_mac.cpp @@ -44,7 +44,7 @@ #include "qsslsocket_mac_p.h" #include "qasn1element_p.h" #include "qsslcertificate_p.h" -#include "qtlsbackend_p.h" +#include "qtlsbackend_st_p.h" #include "qsslcipher_p.h" #include "qtlskey_st_p.h" #include "qsslkey_p.h" @@ -75,59 +75,9 @@ QT_BEGIN_NAMESPACE -namespace -{ - -// These two classes are ad-hoc temporary solution, to be replaced -// by the real things soon. -class SecureTransportBackend : public QTlsBackend -{ -private: - QString backendName() const override - { - return builtinBackendNames[nameIndexSecureTransport]; - } - QSsl::TlsKey *createKey() const override - { - return new QSsl::TlsKeySecureTransport; - } - - QList<QSsl::SslProtocol> supportedProtocols() const override - { - QList<QSsl::SslProtocol> 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<QSsl::SupportedFeature> supportedFeatures() const override - { - QList<QSsl::SupportedFeature> features; - features << QSsl::SupportedFeature::ClientSideAlpn; - - return features; - } - - QList<QSsl::ImplementedClass> implementedClasses() const override - { - QList<QSsl::ImplementedClass> classes; - classes << QSsl::ImplementedClass::Socket; - classes << QSsl::ImplementedClass::Certificate; - classes << QSsl::ImplementedClass::Key; - - return classes; - } -}; +Q_GLOBAL_STATIC(QSecureTransportBackend, backend) -Q_GLOBAL_STATIC(SecureTransportBackend, backend) +namespace { #ifdef Q_OS_MACOS /* diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index c73c2fc235..72cde16dce 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -67,7 +67,7 @@ #include "qsslpresharedkeyauthenticator_p.h" #include "qocspresponse_p.h" #include "qsslkey.h" -#include "qtlsbackend_p.h" +#include "qtlsbackend_openssl_p.h" #include "qtlskey_openssl_p.h" #ifdef Q_OS_WIN @@ -101,80 +101,9 @@ QT_BEGIN_NAMESPACE -namespace { +Q_GLOBAL_STATIC(QTlsBackendOpenSSL, backend) -// These two classes are ad-hoc temporary solution, to be replaced -// by the real things soon. -class OpenSSLBackend : public QTlsBackend -{ -private: - QString backendName() const override - { - return builtinBackendNames[nameIndexOpenSSL]; - } - QSsl::TlsKey *createKey() const override - { - return new QSsl::TlsKeyOpenSSL; - } - QList<QSsl::SslProtocol> supportedProtocols() const override - { - QList<QSsl::SslProtocol> 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<QSsl::SupportedFeature> supportedFeatures() const override - { - QList<QSsl::SupportedFeature> 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<QSsl::ImplementedClass> implementedClasses() const override - { - QList<QSsl::ImplementedClass> 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; - } -}; - -Q_GLOBAL_STATIC(OpenSSLBackend, backend) +namespace { QSsl::AlertLevel tlsAlertLevel(int value) { diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp index eb5c752bed..b235001ef8 100644 --- a/src/network/ssl/qsslsocket_schannel.cpp +++ b/src/network/ssl/qsslsocket_schannel.cpp @@ -46,8 +46,9 @@ #include "qsslcertificateextension.h" #include "qsslcertificate_p.h" #include "qsslcipher_p.h" -#include "qtlsbackend_p.h" +#include "qtlsbackend_schannel_p.h" #include "qtlskey_schannel_p.h" +#include "qx509_schannel_p.h" #include <QtCore/qscopeguard.h> #include <QtCore/qoperatingsystemversion.h> @@ -159,65 +160,79 @@ QT_BEGIN_NAMESPACE namespace { - bool supportsTls13(); -class SchannelBackend : public QTlsBackend +} + +QString QSchannelBackend::backendName() const { -private: - QString backendName() const override - { - return builtinBackendNames[nameIndexSchannel]; - } + return builtinBackendNames[nameIndexSchannel]; +} - QSsl::TlsKey *createKey() const override - { - return new QSsl::TlsKeySchannel; - } +QList<QSsl::SslProtocol> QSchannelBackend::supportedProtocols() const +{ + QList<QSsl::SslProtocol> protocols; - QList<QSsl::SslProtocol> supportedProtocols() const override - { - QList<QSsl::SslProtocol> 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; - } + 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; + if (supportsTls13()) { + protocols << QSsl::TlsV1_3; + protocols << QSsl::TlsV1_3OrLater; } - QList<QSsl::SupportedFeature> supportedFeatures() const override - { - QList<QSsl::SupportedFeature> features; + return protocols; +} - features << QSsl::SupportedFeature::ClientSideAlpn; - features << QSsl::SupportedFeature::ServerSideAlpn; +QList<QSsl::SupportedFeature> QSchannelBackend::supportedFeatures() const +{ + QList<QSsl::SupportedFeature> features; - return features; - } + features << QSsl::SupportedFeature::ClientSideAlpn; + features << QSsl::SupportedFeature::ServerSideAlpn; - QList<QSsl::ImplementedClass> implementedClasses() const override - { - QList<QSsl::ImplementedClass> classes; + return features; +} - classes << QSsl::ImplementedClass::Socket; - classes << QSsl::ImplementedClass::Certificate; - classes << QSsl::ImplementedClass::Key; +QList<QSsl::ImplementedClass> QSchannelBackend::implementedClasses() const +{ + QList<QSsl::ImplementedClass> classes; - return classes; - } -}; + classes << QSsl::ImplementedClass::Socket; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Key; + + return classes; +} + +QSsl::TlsKey *QSchannelBackend::createKey() const +{ + return new QSsl::TlsKeySchannel; +} + +QSsl::X509Certificate *QSchannelBackend::createCertificate() const +{ + return new QSsl::X509CertificateSchannel; +} -Q_GLOBAL_STATIC(SchannelBackend, backend) +QSsl::X509PemReaderPtr QSchannelBackend::X509PemReader() const +{ + return QSsl::X509CertificateGeneric::certificatesFromPem; +} + +QSsl::X509DerReaderPtr QSchannelBackend::X509DerReader() const +{ + return QSsl::X509CertificateGeneric::certificatesFromDer; +} + +Q_GLOBAL_STATIC(QSchannelBackend, backend) + +namespace { SecBuffer createSecBuffer(void *ptr, unsigned long length, unsigned long bufferType) { diff --git a/src/network/ssl/qtlsbackend.cpp b/src/network/ssl/qtlsbackend.cpp index 85dc90cf61..ac6d965bd3 100644 --- a/src/network/ssl/qtlsbackend.cpp +++ b/src/network/ssl/qtlsbackend.cpp @@ -176,6 +176,7 @@ QByteArray TlsKey::pemFooter() const return {}; } +X509Certificate::~X509Certificate() = default; } // namespace QSsl diff --git a/src/network/ssl/qtlsbackend_openssl.cpp b/src/network/ssl/qtlsbackend_openssl.cpp new file mode 100644 index 0000000000..b0dd920aed --- /dev/null +++ b/src/network/ssl/qtlsbackend_openssl.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** 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 "qsslsocket_openssl_symbols_p.h" + +#include <qssl.h> + +#include <qlist.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl"); + +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); +} + +QString QTlsBackendOpenSSL::backendName() const +{ + return builtinBackendNames[nameIndexOpenSSL]; +} + +QList<QSsl::SslProtocol> QTlsBackendOpenSSL::supportedProtocols() const +{ + QList<QSsl::SslProtocol> 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<QSsl::SupportedFeature> QTlsBackendOpenSSL::supportedFeatures() const +{ + QList<QSsl::SupportedFeature> 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<QSsl::ImplementedClass> QTlsBackendOpenSSL::implementedClasses() const +{ + QList<QSsl::ImplementedClass> 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; +} + +QSsl::TlsKey *QTlsBackendOpenSSL::createKey() const +{ + return new QSsl::TlsKeyOpenSSL; +} + +QSsl::X509Certificate *QTlsBackendOpenSSL::createCertificate() const +{ + return new QSsl::X509CertificateOpenSSL; +} + +QSsl::X509ChainVerifyPtr QTlsBackendOpenSSL::X509Verifier() const +{ + return QSsl::X509CertificateOpenSSL::verify; +} + +QSsl::X509PemReaderPtr QTlsBackendOpenSSL::X509PemReader() const +{ + return QSsl::X509CertificateOpenSSL::certificatesFromPem; +} + +QSsl::X509DerReaderPtr QTlsBackendOpenSSL::X509DerReader() const +{ + return QSsl::X509CertificateOpenSSL::certificatesFromDer; +} + +QSsl::X509Pkcs12ReaderPtr QTlsBackendOpenSSL::X509Pkcs12Reader() const +{ + return QSsl::X509CertificateOpenSSL::importPkcs12; +} + +QT_END_NAMESPACE diff --git a/src/network/ssl/qtlsbackend_openssl_p.h b/src/network/ssl/qtlsbackend_openssl_p.h new file mode 100644 index 0000000000..67472980af --- /dev/null +++ b/src/network/ssl/qtlsbackend_openssl_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 <private/qtnetworkglobal_p.h> + +#include "qtlsbackend_p.h" + +#include <QtCore/qglobal.h> + + +QT_BEGIN_NAMESPACE + +class QTlsBackendOpenSSL final : public QTlsBackend +{ +public: + static QString getErrorsFromOpenSsl(); + static void logAndClearErrorQueue(); + static void clearErrorQueue(); +private: + QString backendName() const override; + + QList<QSsl::SslProtocol> supportedProtocols() const override; + QList<QSsl::SupportedFeature> supportedFeatures() const override; + QList<QSsl::ImplementedClass> implementedClasses() const override; + + // QSslKey: + QSsl::TlsKey *createKey() const override; + + // QSslCertificate: + QSsl::X509Certificate *createCertificate() const override; + QSsl::X509ChainVerifyPtr X509Verifier() const override; + QSsl::X509PemReaderPtr X509PemReader() const override; + QSsl::X509DerReaderPtr X509DerReader() const override; + QSsl::X509Pkcs12ReaderPtr X509Pkcs12Reader() 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 b0a54ecf59..b09c631108 100644 --- a/src/network/ssl/qtlsbackend_p.h +++ b/src/network/ssl/qtlsbackend_p.h @@ -60,6 +60,7 @@ #include <QtNetwork/qsslkey.h> #include <QtNetwork/qssl.h> +#include <QtCore/qloggingcategory.h> #include <QtCore/qnamespace.h> #include <QtCore/qobject.h> #include <QtCore/qglobal.h> @@ -123,12 +124,48 @@ public: QByteArray pemFooter() const; }; -// Abstraction above OpenSSL's X509, or our generic +// An abstraction hiding OpenSSL's X509 or our generic // 'derData'-based code. -class X509Certificate; +class X509Certificate +{ +public: + virtual ~X509Certificate(); + + virtual bool isEqual(const X509Certificate &rhs) const = 0; + virtual bool isNull() const = 0; + virtual bool isSelfSigned() const = 0; + virtual QByteArray version() const = 0; + virtual QByteArray serialNumber() const = 0; + virtual QStringList issuerInfo(QSslCertificate::SubjectInfo info) const = 0; + virtual QStringList issuerInfo(const QByteArray &attribute) const = 0; + virtual QStringList subjectInfo(QSslCertificate::SubjectInfo info) const = 0; + virtual QStringList subjectInfo(const QByteArray &attribute) const = 0; + + virtual QList<QByteArray> subjectInfoAttributes() const = 0; + virtual QList<QByteArray> issuerInfoAttributes() const = 0; + virtual QMultiMap<QSsl::AlternativeNameEntryType, QString> subjectAlternativeNames() const = 0; + virtual QDateTime effectiveDate() const = 0; + virtual QDateTime expiryDate() const = 0; + virtual TlsKey *publicKey() const = 0; + + // Extensions. Plugins do not expose internal representation + // and cannot rely on QSslCertificate's internals. + virtual qsizetype numberOfExtensions() const = 0; + virtual QString oidForExtension(qsizetype index) const = 0; + virtual QString nameForExtension(qsizetype index) const = 0; + virtual QVariant valueForExtension(qsizetype index) const = 0; + virtual bool isExtensionCritical(qsizetype index) const = 0; + virtual bool isExtensionSupported(qsizetype index) const = 0; + + virtual QByteArray toPem() const = 0; + virtual QByteArray toDer() const = 0; + virtual QString toText() const = 0; + + virtual Qt::HANDLE handle() const = 0; + + virtual size_t hash(size_t seed) const noexcept = 0; +}; -// X509-related auxiliary functions, previously static -// member-functions in different classes. using X509ChainVerifyPtr = QList<QSslError> (*)(const QList<QSslCertificate> &chain, const QString &hostName); using X509PemReaderPtr = QList<QSslCertificate> (*)(const QByteArray &pem, int count); @@ -201,6 +238,8 @@ public: Q_DISABLE_COPY_MOVE(QTlsBackend) }; +Q_DECLARE_LOGGING_CATEGORY(lcTlsBackend) + #define QTlsBackend_iid "org.qt-project.Qt.QTlsBackend" Q_DECLARE_INTERFACE(QTlsBackend, QTlsBackend_iid); diff --git a/src/network/ssl/qtlsbackend_schannel_p.h b/src/network/ssl/qtlsbackend_schannel_p.h new file mode 100644 index 0000000000..6cc2f58c54 --- /dev/null +++ b/src/network/ssl/qtlsbackend_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 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 <private/qtnetworkglobal_p.h> + +#include "qtlsbackend_p.h" + +#include <QtCore/qglobal.h> + + +QT_BEGIN_NAMESPACE + +class QSchannelBackend : public QTlsBackend +{ +private: + QString backendName() const override; + QList<QSsl::SslProtocol> supportedProtocols() const override; + QList<QSsl::SupportedFeature> supportedFeatures() const override; + QList<QSsl::ImplementedClass> implementedClasses() const override; + + QSsl::TlsKey *createKey() const override; + QSsl::X509Certificate *createCertificate() const override; + + QSsl::X509PemReaderPtr X509PemReader() const override; + QSsl::X509DerReaderPtr X509DerReader() const override; +}; + +QT_END_NAMESPACE + +#endif // QTLSBACKEND_ST_P_H + + diff --git a/src/network/ssl/qtlsbackend_st.cpp b/src/network/ssl/qtlsbackend_st.cpp new file mode 100644 index 0000000000..b9746e9a9a --- /dev/null +++ b/src/network/ssl/qtlsbackend_st.cpp @@ -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$ +** +****************************************************************************/ + +#include "qtlsbackend_st_p.h" +#include "qtlskey_st_p.h" +#include "qx509_st_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.securetransport"); + +QString QSecureTransportBackend::backendName() const +{ + return builtinBackendNames[nameIndexSecureTransport]; +} + +QSsl::TlsKey *QSecureTransportBackend::createKey() const +{ + return new QSsl::TlsKeySecureTransport; +} + +QSsl::X509Certificate *QSecureTransportBackend::createCertificate() const +{ + return new QSsl::X509CertificateSecureTransport; +} + +QList<QSsl::SslProtocol> QSecureTransportBackend::supportedProtocols() const +{ + QList<QSsl::SslProtocol> 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<QSsl::SupportedFeature> QSecureTransportBackend::supportedFeatures() const +{ + QList<QSsl::SupportedFeature> features; + features << QSsl::SupportedFeature::ClientSideAlpn; + + return features; +} + +QList<QSsl::ImplementedClass> QSecureTransportBackend::implementedClasses() const +{ + QList<QSsl::ImplementedClass> classes; + classes << QSsl::ImplementedClass::Socket; + classes << QSsl::ImplementedClass::Certificate; + classes << QSsl::ImplementedClass::Key; + + return classes; +} + +QSsl::X509PemReaderPtr QSecureTransportBackend::X509PemReader() const +{ + return QSsl::X509CertificateGeneric::certificatesFromPem; +} + +QSsl::X509DerReaderPtr QSecureTransportBackend::X509DerReader() const +{ + return QSsl::X509CertificateGeneric::certificatesFromDer; +} + +QT_END_NAMESPACE + diff --git a/src/network/ssl/qtlsbackend_st_p.h b/src/network/ssl/qtlsbackend_st_p.h new file mode 100644 index 0000000000..5f8a0a4b02 --- /dev/null +++ b/src/network/ssl/qtlsbackend_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 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 <private/qtnetworkglobal_p.h> + +#include "qtlsbackend_p.h" + +#include <QtCore/qglobal.h> + + +QT_BEGIN_NAMESPACE + +class QSecureTransportBackend : public QTlsBackend +{ +private: + QString backendName() const override; + + QList<QSsl::SslProtocol> supportedProtocols() const override; + QList<QSsl::SupportedFeature> supportedFeatures() const override; + QList<QSsl::ImplementedClass> implementedClasses() const override; + + QSsl::TlsKey *createKey() const override; + QSsl::X509Certificate *createCertificate() const override; + + QSsl::X509PemReaderPtr X509PemReader() const override; + QSsl::X509DerReaderPtr X509DerReader() const override; +}; + +QT_END_NAMESPACE + +#endif // QTLSBACKEND_ST_P_H + + diff --git a/src/network/ssl/qtlskey_base_p.h b/src/network/ssl/qtlskey_base_p.h index 48850aa781..df1c47b20e 100644 --- a/src/network/ssl/qtlskey_base_p.h +++ b/src/network/ssl/qtlskey_base_p.h @@ -68,6 +68,12 @@ namespace QSsl { class TlsKeyBase : public TlsKey { public: + TlsKeyBase(KeyType type = PublicKey, KeyAlgorithm algorithm = Opaque) + : keyType(type), + keyAlgorithm(algorithm) + { + } + bool isNull() const override { return keyIsNull; @@ -93,8 +99,8 @@ protected: static bool isEncryptedPkcs8(const QByteArray &der); bool keyIsNull = true; - QSsl::KeyType keyType = QSsl::PublicKey; - QSsl::KeyAlgorithm keyAlgorithm = QSsl::Opaque; + KeyType keyType = PublicKey; + KeyAlgorithm keyAlgorithm = Opaque; }; } // namespace QSsl diff --git a/src/network/ssl/qtlskey_generic_p.h b/src/network/ssl/qtlskey_generic_p.h index b7dcc05be2..d8b4c47975 100644 --- a/src/network/ssl/qtlskey_generic_p.h +++ b/src/network/ssl/qtlskey_generic_p.h @@ -73,10 +73,11 @@ namespace QSsl { class TlsKeyGeneric : public TlsKeyBase { public: - TlsKeyGeneric() + TlsKeyGeneric(KeyType type = PublicKey, KeyAlgorithm algorithm = Opaque) + : TlsKeyBase(type, algorithm) { - // Note, while clear is pure-virtual, the final-overrider - // in this class is sufficient. Same for d-tor below. + // Note, while clear is pure-virtual in the base class, + // the final-overrider in this class is sufficient. clear(false); } ~TlsKeyGeneric() diff --git a/src/network/ssl/qtlskey_schannel.cpp b/src/network/ssl/qtlskey_schannel.cpp index 24ea10ba70..d53cfe61e9 100644 --- a/src/network/ssl/qtlskey_schannel.cpp +++ b/src/network/ssl/qtlskey_schannel.cpp @@ -39,6 +39,7 @@ #include "qssl_p.h" #include "qtlskey_schannel_p.h" +#include "qtlsbackend_p.h" #include "qsslkey_p.h" #include "qsslkey.h" @@ -77,8 +78,7 @@ BCRYPT_ALG_HANDLE getHandle(QSslKeyPrivate::Cipher cipher) 0 // dwFlags ); if (status < 0) { - // TLSTODO: - //qCWarning(lcSChannel, "Failed to open algorithm handle (%ld)!", status); + qCWarning(lcTlsBackend, "Failed to open algorithm handle (%ld)!", status); return nullptr; } @@ -99,8 +99,7 @@ BCRYPT_KEY_HANDLE generateSymmetricKey(BCRYPT_ALG_HANDLE handle, 0 // dwFlags ); if (status < 0) { - // TLSTODO - category - //qCWarning(lcSChannel, "Failed to generate symmetric key (%ld)!", status); + qCWarning(lcTlsBackend, "Failed to generate symmetric key (%ld)!", status); return nullptr; } @@ -113,8 +112,7 @@ BCRYPT_KEY_HANDLE generateSymmetricKey(BCRYPT_ALG_HANDLE handle, ); if (status < 0) { BCryptDestroyKey(keyHandle); - // TLSTODO: category - //qCWarning(lcSChannel, "Failed to change the symmetric key's chaining mode (%ld)!", status); + qCWarning(lcTlsBackend, "Failed to change the symmetric key's chaining mode (%ld)!", status); return nullptr; } return keyHandle; @@ -159,7 +157,7 @@ QByteArray doCrypt(QSslKeyPrivate::Cipher cipher, const QByteArray &data, const BCRYPT_BLOCK_PADDING // dwFlags ); if (status < 0) { - qCWarning(lcSsl, "%s failed (%ld)!", encrypt ? "Encrypt" : "Decrypt", status); + qCWarning(lcTlsBackend, "%s failed (%ld)!", encrypt ? "Encrypt" : "Decrypt", status); return {}; } } diff --git a/src/network/ssl/qtlskey_schannel_p.h b/src/network/ssl/qtlskey_schannel_p.h index 274f642422..2001453922 100644 --- a/src/network/ssl/qtlskey_schannel_p.h +++ b/src/network/ssl/qtlskey_schannel_p.h @@ -64,7 +64,7 @@ namespace QSsl { class TlsKeySchannel final : public TlsKeyGeneric { public: - TlsKeySchannel() = default; + using TlsKeyGeneric::TlsKeyGeneric; QByteArray decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv) const override; diff --git a/src/network/ssl/qtlskey_st_p.h b/src/network/ssl/qtlskey_st_p.h index 7bc12edb45..9a8a84094f 100644 --- a/src/network/ssl/qtlskey_st_p.h +++ b/src/network/ssl/qtlskey_st_p.h @@ -64,7 +64,7 @@ namespace QSsl { class TlsKeySecureTransport final : public TlsKeyGeneric { public: - TlsKeySecureTransport() = default; + using TlsKeyGeneric::TlsKeyGeneric; QByteArray decrypt(Cipher cipher, const QByteArray &data, const QByteArray &key, const QByteArray &iv) const override; diff --git a/src/network/ssl/qx509_base.cpp b/src/network/ssl/qx509_base.cpp new file mode 100644 index 0000000000..9f8f8c3ba1 --- /dev/null +++ b/src/network/ssl/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 QSsl { + +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<QByteArray> X509CertificateBase::subjectInfoAttributes() const +{ + return subjectInfoEntries.uniqueKeys(); +} + +QList<QByteArray> 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 QSsl + +QT_END_NAMESPACE diff --git a/src/network/ssl/qx509_base_p.h b/src/network/ssl/qx509_base_p.h new file mode 100644 index 0000000000..27060dd5a8 --- /dev/null +++ b/src/network/ssl/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 <private/qtnetworkglobal_p.h> + +#include <private/qtlsbackend_p.h> + +#include <qssl.h> + +#include <QtCore/qbytearray.h> +#include <QtCore/qstring.h> +#include <QtCore/qglobal.h> +#include <QtCore/qlist.h> + +QT_BEGIN_NAMESPACE + +namespace QSsl { + +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<QByteArray> subjectInfoAttributes() const override; + QList<QByteArray> 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<QByteArray, QString> issuerInfoEntries; + QMultiMap<QByteArray, QString> subjectInfoEntries; + QDateTime notValidAfter; + QDateTime notValidBefore; + + struct X509CertificateExtension + { + QString oid; + QString name; + QVariant value; + bool critical = false; + bool supported = false; + }; + + QList<X509CertificateExtension> extensions; +}; + +} // namespace QSsl + +QT_END_NAMESPACE + +#endif // QX509CERTIFICATE_BASE_P_H diff --git a/src/network/ssl/qx509_generic.cpp b/src/network/ssl/qx509_generic.cpp new file mode 100644 index 0000000000..9d982d31f5 --- /dev/null +++ b/src/network/ssl/qx509_generic.cpp @@ -0,0 +1,467 @@ +/**************************************************************************** +** +** 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 "qx509_generic_p.h" +#include "qasn1element_p.h" + +#include "qssl_p.h" + +#include <qhostaddress.h> +#include <qendian.h> +#include <qhash.h> + +#include <memory> + +QT_BEGIN_NAMESPACE + +namespace QSsl { + +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<const X509CertificateGeneric &>(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<QSsl::AlternativeNameEntryType, QString> 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<QSslCertificate> X509CertificateGeneric::certificatesFromPem(const QByteArray &pem, int count) +{ + QList<QSslCertificate> 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<QSslCertificate> X509CertificateGeneric::certificatesFromDer(const QByteArray &der, int count) +{ + QList<QSslCertificate> certificates; + + QByteArray data = der; + while (count == -1 || certificates.size() < count) { + QSslCertificate cert; + auto *certBackend = QTlsBackend::backend<X509CertificateGeneric>(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<quint32 *>(ipAddrValue.data()))); + break; + case 16: // IPv6 + ipAddress = QHostAddress(reinterpret_cast<quint8 *>(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 QSsl + +QT_END_NAMESPACE diff --git a/src/network/ssl/qx509_generic_p.h b/src/network/ssl/qx509_generic_p.h new file mode 100644 index 0000000000..82bd4e84af --- /dev/null +++ b/src/network/ssl/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 <private/qtnetworkglobal_p.h> + +#include <private/qtlsbackend_p.h> +#include <private/qx509_base_p.h> + +#include <QtCore/qbytearray.h> +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +namespace QSsl { + +// 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<QSsl::AlternativeNameEntryType, QString> 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<QSslCertificate> certificatesFromPem(const QByteArray &pem, int count); + static QList<QSslCertificate> certificatesFromDer(const QByteArray &der, int count); + +protected: + + bool subjectMatchesIssuer = false; + QSsl::KeyAlgorithm publicKeyAlgorithm = QSsl::Rsa; + QByteArray publicKeyDerData; + + QMultiMap<QSsl::AlternativeNameEntryType, QString> saNames; + QByteArray derData; + + bool parse(const QByteArray &data); + bool parseExtension(const QByteArray &data, X509CertificateExtension &extension); +}; + +} // namespace QSsl + +QT_END_NAMESPACE + +#endif // QX509_GENERIC_P_H diff --git a/src/network/ssl/qx509_openssl.cpp b/src/network/ssl/qx509_openssl.cpp new file mode 100644 index 0000000000..376b24ab37 --- /dev/null +++ b/src/network/ssl/qx509_openssl.cpp @@ -0,0 +1,927 @@ +/**************************************************************************** +** +** 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 "qsslsocket_openssl_symbols_p.h" + +#include "qsslsocket.h" + +#include <QtNetwork/qhostaddress.h> + +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qscopeguard.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qendian.h> +#include <QtCore/qhash.h> + +QT_BEGIN_NAMESPACE + +namespace QSsl { + +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<QByteArray, QString> mapFromX509Name(X509_NAME *name) +{ + if (!name) + return {}; + + QMultiMap<QByteArray, QString> 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<char, 16384> 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<X509V3_EXT_METHOD *>(q_X509V3_EXT_get(ext)); + if (!meth) { + ASN1_OCTET_STRING *value = q_X509_EXTENSION_get_data(ext); + Q_ASSERT(value); + QByteArray result( reinterpret_cast<const char *>(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(CONF_VALUE, 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<BASIC_CONSTRAINTS *>(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<AUTHORITY_INFO_ACCESS *>(q_X509V3_EXT_d2i(ext)); + if (!info) + return {}; + QVariantMap result; + for (int i=0; i < q_SKM_sk_num(ACCESS_DESCRIPTION, 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<const char *>(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<void(*)(void *)>(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<X509V3_EXT_METHOD *>(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<AUTHORITY_KEYID *>(q_X509V3_EXT_d2i(ext)); + if (!auth_key) + return {}; + QVariantMap result; + + // keyid + if (auth_key->keyid) { + QByteArray keyid(reinterpret_cast<const char *>(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<QSslErrorEntry> *; + 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 = QSslSocketBackendPrivate::s_indexForSSLExtraData + + QSslSocketBackendPrivate::errorOffsetInExData; + if (SSL *ssl = static_cast<SSL *>(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<const X509CertificateOpenSSL &>(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<QSsl::AlternativeNameEntryType, QString> +X509CertificateOpenSSL::subjectAlternativeNames() const +{ + QMultiMap<QSsl::AlternativeNameEntryType, QString> result; + + if (!x509) + return result; + + auto *altNames = static_cast<STACK_OF(GENERAL_NAME) *>(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<const char *>(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<quint32 *>(genName->d.iPAddress->data))); + break; + case 16: // IPv6 + ipAddress = QHostAddress(reinterpret_cast<quint8 *>(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<void(*)(void*)>(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<X509CertificateOpenSSL>(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<QSslCertificate> X509CertificateOpenSSL::stackOfX509ToQSslCertificates(STACK_OF(X509) *x509) +{ + if (!x509) + return {}; + + QList<QSslCertificate> 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<QSslError> X509CertificateOpenSSL::verify(const QList<QSslCertificate> &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::s_loadRootCertsOnDemand) + roots.append(QSslSocketPrivate::systemCaCertificates()); +#endif // Q_OS_WIN + return verify(roots, chain, hostName); +} + +QList<QSslError> X509CertificateOpenSSL::verify(const QList<QSslCertificate> &caCertificates, + const QList<QSslCertificate> &certificateChain, + const QString &hostName) +{ + // This was previously QSslSocketPrivate::verify(). + if (certificateChain.count() <= 0) + return {QSslError(QSslError::UnspecifiedError)}; + + QList<QSslError> 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<X509_STORE, decltype(&q_X509_STORE_free)> 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<X509 *>(caCertificate.handle())); + } + } + + QList<QSslErrorEntry> 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<X509 *>(cert.handle())); + } + } + + X509_STORE_CTX *storeContext = q_X509_STORE_CTX_new(); + if (!storeContext) { + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + std::unique_ptr<X509_STORE_CTX, decltype(&q_X509_STORE_CTX_free)> ctxGuard(storeContext, q_X509_STORE_CTX_free); + + if (!q_X509_STORE_CTX_init(storeContext, certStore, reinterpret_cast<X509 *>(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<QSslCertificate> X509CertificateOpenSSL::certificatesFromPem(const QByteArray &pem, int count) +{ + QList<QSslCertificate> 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<QSslCertificate> X509CertificateOpenSSL::certificatesFromDer(const QByteArray &der, int count) +{ + QList<QSslCertificate> 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<QSslCertificate> *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<char *>(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<OPENSSL_STACK *>(ca), + reinterpret_cast<void (*)(void *)>(q_X509_free)); + }); + + // Convert to Qt types + auto *tlsKey = QTlsBackend::backend<TlsKeyOpenSSL>(*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 QSsl + +QT_END_NAMESPACE diff --git a/src/network/ssl/qx509_openssl_p.h b/src/network/ssl/qx509_openssl_p.h new file mode 100644 index 0000000000..92752e5f48 --- /dev/null +++ b/src/network/ssl/qx509_openssl_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** 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 <private/qtnetworkglobal_p.h> + +// TLSTODO: only temporary, and only because of QSslErrorEntry! +#include <private/qsslsocket_openssl_p.h> + +#include <private/qtlsbackend_p.h> +#include <private/qx509_base_p.h> + +#include <QtCore/qvariant.h> +#include <QtCore/qglobal.h> +#include <QtCore/qstring.h> + +#include <openssl/x509.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +namespace QSsl { + +// 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<QSsl::AlternativeNameEntryType, QString> 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<QSslCertificate> 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<QSslError> verify(const QList<QSslCertificate> &chain, const QString &hostName); + static QList<QSslError> verify(const QList<QSslCertificate> &caCertificates, + const QList<QSslCertificate> &certificateChain, + const QString &hostName); + + static QList<QSslCertificate> certificatesFromPem(const QByteArray &pem, int count); + static QList<QSslCertificate> certificatesFromDer(const QByteArray &der, int count); + static bool importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, + QList<QSslCertificate> *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 QSsl. + +QT_END_NAMESPACE + +#endif // QX509_OPENSSL_P_H diff --git a/src/network/ssl/qx509_schannel.cpp b/src/network/ssl/qx509_schannel.cpp new file mode 100644 index 0000000000..252bb470cb --- /dev/null +++ b/src/network/ssl/qx509_schannel.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_schannel_p.h" +#include "qx509_schannel_p.h" + +#include <memory> + +QT_BEGIN_NAMESPACE + +namespace QSsl { + +TlsKey *X509CertificateSchannel::publicKey() const +{ + auto key = std::make_unique<TlsKeySchannel>(PublicKey); + if (publicKeyAlgorithm != QSsl::Opaque) + key->decodeDer(PublicKey, publicKeyAlgorithm, publicKeyDerData, {}, false); + + return key.release(); +} + +} // namespace QSsl. + +QT_END_NAMESPACE + diff --git a/src/network/ssl/qx509_schannel_p.h b/src/network/ssl/qx509_schannel_p.h new file mode 100644 index 0000000000..5408ea5a8e --- /dev/null +++ b/src/network/ssl/qx509_schannel_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_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 <private/qtnetworkglobal_p.h> + +#include <private/qx509_generic_p.h> + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +namespace QSsl { + +class X509CertificateSchannel final : public X509CertificateGeneric +{ +public: + TlsKey *publicKey() const override; +}; + +} // namespace QSsl. + +QT_END_NAMESPACE + +#endif // QX509_SCHANNEL_P_H diff --git a/src/network/ssl/qx509_st.cpp b/src/network/ssl/qx509_st.cpp new file mode 100644 index 0000000000..7183ca2768 --- /dev/null +++ b/src/network/ssl/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 <memory> + +QT_BEGIN_NAMESPACE + +namespace QSsl { + +TlsKey *X509CertificateSecureTransport::publicKey() const +{ + auto key = std::make_unique<TlsKeySecureTransport>(PublicKey); + if (publicKeyAlgorithm != QSsl::Opaque) + key->decodeDer(PublicKey, publicKeyAlgorithm, publicKeyDerData, {}, false); + + return key.release(); +} + +} // namespace QSsl. + +QT_END_NAMESPACE + diff --git a/src/network/ssl/qx509_st_p.h b/src/network/ssl/qx509_st_p.h new file mode 100644 index 0000000000..4f1220d104 --- /dev/null +++ b/src/network/ssl/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 <private/qtnetworkglobal_p.h> + +#include <private/qx509_generic_p.h> + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +namespace QSsl { + +class X509CertificateSecureTransport final : public X509CertificateGeneric +{ +public: + TlsKey *publicKey() const override; +}; + +} // namespace QSsl. + +QT_END_NAMESPACE + +#endif // QX509_ST_P_H |