summaryrefslogtreecommitdiffstats
path: root/src/plugins/tls/openssl/qtlsbackend_openssl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/tls/openssl/qtlsbackend_openssl.cpp')
-rw-r--r--src/plugins/tls/openssl/qtlsbackend_openssl.cpp611
1 files changed, 611 insertions, 0 deletions
diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp
new file mode 100644
index 0000000000..d73515724b
--- /dev/null
+++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp
@@ -0,0 +1,611 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsslsocket_openssl_symbols_p.h"
+#include "qtlsbackend_openssl_p.h"
+#include "qtlskey_openssl_p.h"
+#include "qx509_openssl_p.h"
+#include "qtls_openssl_p.h"
+
+#if QT_CONFIG(dtls)
+#include "qdtls_openssl_p.h"
+#endif // QT_CONFIG(dtls)
+
+#include <QtNetwork/private/qsslcipher_p.h>
+
+#include <QtNetwork/qsslcipher.h>
+#include <QtNetwork/qssl.h>
+
+#include <QtCore/qdir.h>
+#include <QtCore/qdirlisting.h>
+#include <QtCore/qlist.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qscopeguard.h>
+#include <QtCore/qset.h>
+
+#include "qopenssl_p.h"
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
+constexpr auto DefaultWarningLevel = QtCriticalMsg;
+#else
+constexpr auto DefaultWarningLevel = QtDebugMsg;
+#endif
+
+Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl", DefaultWarningLevel);
+
+static void q_loadCiphersForConnection(SSL *connection, QList<QSslCipher> &ciphers,
+ QList<QSslCipher> &defaultCiphers)
+{
+ Q_ASSERT(connection);
+
+ STACK_OF(SSL_CIPHER) *supportedCiphers = q_SSL_get_ciphers(connection);
+ for (int i = 0; i < q_sk_SSL_CIPHER_num(supportedCiphers); ++i) {
+ if (SSL_CIPHER *cipher = q_sk_SSL_CIPHER_value(supportedCiphers, i)) {
+ const auto ciph = QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(cipher);
+ if (!ciph.isNull()) {
+ // Unconditionally exclude ADH and AECDH ciphers since they offer no MITM protection
+ if (!ciph.name().toLower().startsWith("adh"_L1) &&
+ !ciph.name().toLower().startsWith("exp-adh"_L1) &&
+ !ciph.name().toLower().startsWith("aecdh"_L1)) {
+ ciphers << ciph;
+
+ if (ciph.usedBits() >= 128)
+ defaultCiphers << ciph;
+ }
+ }
+ }
+ }
+}
+
+int QTlsBackendOpenSSL::s_indexForSSLExtraData = -1;
+
+QString QTlsBackendOpenSSL::getErrorsFromOpenSsl()
+{
+ QString errorString;
+ char buf[256] = {}; // OpenSSL docs claim both 120 and 256; use the larger.
+ unsigned long errNum;
+ while ((errNum = q_ERR_get_error())) {
+ if (!errorString.isEmpty())
+ errorString.append(", "_L1);
+ q_ERR_error_string_n(errNum, buf, sizeof buf);
+ errorString.append(QLatin1StringView(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()
+{
+ while (q_ERR_get_error())
+ ;
+}
+
+bool QTlsBackendOpenSSL::ensureLibraryLoaded()
+{
+ static bool libraryLoaded = []() {
+ if (!q_resolveOpenSslSymbols())
+ return false;
+
+ // Initialize OpenSSL.
+ if (q_OPENSSL_init_ssl(0, nullptr) != 1)
+ return false;
+
+ if (q_OpenSSL_version_num() < 0x10101000L) {
+ qCWarning(lcTlsBackend, "QSslSocket: OpenSSL >= 1.1.1 is required; %s was found instead", q_OpenSSL_version(OPENSSL_VERSION));
+ return false;
+ }
+
+ q_SSL_load_error_strings();
+ q_OpenSSL_add_all_algorithms();
+
+ s_indexForSSLExtraData = q_CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, 0L, nullptr, nullptr,
+ nullptr, nullptr);
+
+ // Initialize OpenSSL's random seed.
+ if (!q_RAND_status()) {
+ qWarning("Random number generator not seeded, disabling SSL support");
+ return false;
+ }
+
+ return true;
+ }();
+
+ return libraryLoaded;
+}
+
+QString QTlsBackendOpenSSL::backendName() const
+{
+ return builtinBackendNames[nameIndexOpenSSL];
+}
+
+bool QTlsBackendOpenSSL::isValid() const
+{
+ return ensureLibraryLoaded();
+}
+
+long QTlsBackendOpenSSL::tlsLibraryVersionNumber() const
+{
+ return q_OpenSSL_version_num();
+}
+
+QString QTlsBackendOpenSSL::tlsLibraryVersionString() const
+{
+ const char *versionString = q_OpenSSL_version(OPENSSL_VERSION);
+ if (!versionString)
+ return QString();
+
+ return QString::fromLatin1(versionString);
+}
+
+long QTlsBackendOpenSSL::tlsLibraryBuildVersionNumber() const
+{
+ return OPENSSL_VERSION_NUMBER;
+}
+
+QString QTlsBackendOpenSSL::tlsLibraryBuildVersionString() const
+{
+ // Using QStringLiteral to store the version string as unicode and
+ // avoid false positives from Google searching the playstore for old
+ // SSL versions. See QTBUG-46265
+ return QStringLiteral(OPENSSL_VERSION_TEXT);
+}
+
+void QTlsBackendOpenSSL::ensureInitialized() const
+{
+ // Old qsslsocket_openssl calls supportsSsl() (which means
+ // library found and symbols resolved, this already assured
+ // by the fact we end up in this function (isValid() returned
+ // true for the backend, see its code). The qsslsocket_openssl
+ // proceedes with loading certificate, ciphers and elliptic
+ // curves.
+ ensureCiphersAndCertsLoaded();
+}
+
+void QTlsBackendOpenSSL::ensureCiphersAndCertsLoaded() const
+{
+ Q_CONSTINIT static bool initializationStarted = false;
+ Q_CONSTINIT static QAtomicInt initialized = Q_BASIC_ATOMIC_INITIALIZER(0);
+ Q_CONSTINIT static QRecursiveMutex initMutex;
+
+ if (initialized.loadAcquire())
+ return;
+
+ const QMutexLocker locker(&initMutex);
+
+ if (initializationStarted || initialized.loadAcquire())
+ return;
+
+ // Indicate that the initialization has already started in the current
+ // thread in case of recursive calls. The atomic variable cannot be used
+ // for this because it is checked without holding the init mutex.
+ initializationStarted = true;
+
+ auto guard = qScopeGuard([] { initialized.storeRelease(1); });
+
+ resetDefaultCiphers();
+ resetDefaultEllipticCurves();
+
+#if QT_CONFIG(library)
+ //load symbols needed to receive certificates from system store
+#if defined(Q_OS_QNX)
+ QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true);
+#elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
+ // check whether we can enable on-demand root-cert loading (i.e. check whether the sym links are there)
+ const QList<QByteArray> dirs = QSslSocketPrivate::unixRootCertDirectories();
+ const QStringList symLinkFilter{
+ u"[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].[0-9]"_s};
+ for (const auto &dir : dirs) {
+ QDirListing dirList(QString::fromLatin1(dir), symLinkFilter, QDir::Files);
+ if (dirList.cbegin() != dirList.cend()) { // Not empty
+ QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true);
+ break;
+ }
+ }
+#endif
+#endif // QT_CONFIG(library)
+ // if on-demand loading was not enabled, load the certs now
+ if (!QSslSocketPrivate::rootCertOnDemandLoadingSupported())
+ setDefaultCaCertificates(systemCaCertificates());
+#ifdef Q_OS_WIN
+ //Enabled for fetching additional root certs from windows update on windows.
+ //This flag is set false by setDefaultCaCertificates() indicating the app uses
+ //its own cert bundle rather than the system one.
+ //Same logic that disables the unix on demand cert loading.
+ //Unlike unix, we do preload the certificates from the cert store.
+ QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true);
+#endif
+}
+
+void QTlsBackendOpenSSL::resetDefaultCiphers()
+{
+ SSL_CTX *myCtx = q_SSL_CTX_new(q_TLS_client_method());
+ // Note, we assert, not just silently return/bail out early:
+ // this should never happen and problems with OpenSSL's initialization
+ // must be caught before this (see supportsSsl()).
+ Q_ASSERT(myCtx);
+ SSL *mySsl = q_SSL_new(myCtx);
+ Q_ASSERT(mySsl);
+
+ QList<QSslCipher> ciphers;
+ QList<QSslCipher> defaultCiphers;
+
+ q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers);
+
+ q_SSL_CTX_free(myCtx);
+ q_SSL_free(mySsl);
+
+ setDefaultSupportedCiphers(ciphers);
+ setDefaultCiphers(defaultCiphers);
+
+#if QT_CONFIG(dtls)
+ ciphers.clear();
+ defaultCiphers.clear();
+ myCtx = q_SSL_CTX_new(q_DTLS_client_method());
+ if (myCtx) {
+ mySsl = q_SSL_new(myCtx);
+ if (mySsl) {
+ q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers);
+ setDefaultDtlsCiphers(defaultCiphers);
+ q_SSL_free(mySsl);
+ }
+ q_SSL_CTX_free(myCtx);
+ }
+#endif // dtls
+}
+
+QList<QSsl::SslProtocol> QTlsBackendOpenSSL::supportedProtocols() const
+{
+ QList<QSsl::SslProtocol> protocols;
+
+ protocols << QSsl::AnyProtocol;
+ protocols << QSsl::SecureProtocols;
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+ protocols << QSsl::TlsV1_0;
+ protocols << QSsl::TlsV1_0OrLater;
+ protocols << QSsl::TlsV1_1;
+ protocols << QSsl::TlsV1_1OrLater;
+QT_WARNING_POP
+ protocols << QSsl::TlsV1_2;
+ protocols << QSsl::TlsV1_2OrLater;
+
+#ifdef TLS1_3_VERSION
+ protocols << QSsl::TlsV1_3;
+ protocols << QSsl::TlsV1_3OrLater;
+#endif // TLS1_3_VERSION
+
+#if QT_CONFIG(dtls)
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+ protocols << QSsl::DtlsV1_0;
+ protocols << QSsl::DtlsV1_0OrLater;
+QT_WARNING_POP
+ 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;
+
+#if !defined(OPENSSL_NO_TLSEXT)
+ features << QSsl::SupportedFeature::ClientSideAlpn;
+ features << QSsl::SupportedFeature::ServerSideAlpn;
+#endif // !OPENSSL_NO_TLSEXT
+
+ 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;
+#if QT_CONFIG(dtls)
+ classes << QSsl::ImplementedClass::Dtls;
+ classes << QSsl::ImplementedClass::DtlsCookie;
+#endif
+ classes << QSsl::ImplementedClass::EllipticCurve;
+ classes << QSsl::ImplementedClass::DiffieHellman;
+
+ return classes;
+}
+
+QTlsPrivate::TlsKey *QTlsBackendOpenSSL::createKey() const
+{
+ return new QTlsPrivate::TlsKeyOpenSSL;
+}
+
+QTlsPrivate::X509Certificate *QTlsBackendOpenSSL::createCertificate() const
+{
+ return new QTlsPrivate::X509CertificateOpenSSL;
+}
+
+namespace QTlsPrivate {
+
+#ifdef Q_OS_ANDROID
+QList<QByteArray> fetchSslCertificateData();
+#endif
+
+QList<QSslCertificate> systemCaCertificates();
+
+#ifndef Q_OS_DARWIN
+QList<QSslCertificate> systemCaCertificates()
+{
+#ifdef QSSLSOCKET_DEBUG
+ QElapsedTimer timer;
+ timer.start();
+#endif
+ QList<QSslCertificate> systemCerts;
+#if defined(Q_OS_WIN)
+ HCERTSTORE hSystemStore;
+ hSystemStore =
+ CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0,
+ CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
+ if (hSystemStore) {
+ PCCERT_CONTEXT pc = nullptr;
+ while (1) {
+ pc = CertFindCertificateInStore(hSystemStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, pc);
+ if (!pc)
+ break;
+ QByteArray der(reinterpret_cast<const char *>(pc->pbCertEncoded),
+ static_cast<int>(pc->cbCertEncoded));
+ QSslCertificate cert(der, QSsl::Der);
+ systemCerts.append(cert);
+ }
+ CertCloseStore(hSystemStore, 0);
+ }
+#elif defined(Q_OS_ANDROID)
+ const QList<QByteArray> certData = fetchSslCertificateData();
+ for (auto certDatum : certData)
+ systemCerts.append(QSslCertificate::fromData(certDatum, QSsl::Der));
+#elif defined(Q_OS_UNIX)
+ {
+ const QList<QByteArray> directories = QSslSocketPrivate::unixRootCertDirectories();
+ QSet<QString> certFiles = {
+ QStringLiteral("/etc/pki/tls/certs/ca-bundle.crt"), // Fedora, Mandriva
+ QStringLiteral("/usr/local/share/certs/ca-root-nss.crt") // FreeBSD's ca_root_nss
+ };
+ QDir currentDir;
+ currentDir.setNameFilters(QStringList{QStringLiteral("*.pem"), QStringLiteral("*.crt")});
+ for (const auto &directory : directories) {
+ currentDir.setPath(QLatin1StringView(directory));
+ for (const auto &dirEntry : QDirListing(currentDir)) {
+ // use canonical path here to not load the same certificate twice if symlinked
+ certFiles.insert(dirEntry.canonicalFilePath());
+ }
+ }
+ for (const QString& file : std::as_const(certFiles))
+ systemCerts.append(QSslCertificate::fromPath(file, QSsl::Pem));
+ }
+#endif // platform
+#ifdef QSSLSOCKET_DEBUG
+ qCDebug(lcTlsBackend) << "systemCaCertificates retrieval time " << timer.elapsed() << "ms";
+ qCDebug(lcTlsBackend) << "imported " << systemCerts.count() << " certificates";
+#endif
+
+ return systemCerts;
+}
+#endif // !Q_OS_DARWIN
+} // namespace QTlsPrivate
+
+QList<QSslCertificate> QTlsBackendOpenSSL::systemCaCertificates() const
+{
+ return QTlsPrivate::systemCaCertificates();
+}
+
+QTlsPrivate::DtlsCookieVerifier *QTlsBackendOpenSSL::createDtlsCookieVerifier() const
+{
+#if QT_CONFIG(dtls)
+ return new QDtlsClientVerifierOpenSSL;
+#else
+ qCWarning(lcTlsBackend, "Feature 'dtls' is disabled, cannot verify DTLS cookies");
+ return nullptr;
+#endif // QT_CONFIG(dtls)
+}
+
+QTlsPrivate::TlsCryptograph *QTlsBackendOpenSSL::createTlsCryptograph() const
+{
+ return new QTlsPrivate::TlsCryptographOpenSSL;
+}
+
+QTlsPrivate::DtlsCryptograph *QTlsBackendOpenSSL::createDtlsCryptograph(QDtls *q, int mode) const
+{
+#if QT_CONFIG(dtls)
+ return new QDtlsPrivateOpenSSL(q, QSslSocket::SslMode(mode));
+#else
+ Q_UNUSED(q);
+ Q_UNUSED(mode);
+ qCWarning(lcTlsBackend, "Feature 'dtls' is disabled, cannot encrypt UDP datagrams");
+ return nullptr;
+#endif // QT_CONFIG(dtls)
+}
+
+QTlsPrivate::X509ChainVerifyPtr QTlsBackendOpenSSL::X509Verifier() const
+{
+ return QTlsPrivate::X509CertificateOpenSSL::verify;
+}
+
+QTlsPrivate::X509PemReaderPtr QTlsBackendOpenSSL::X509PemReader() const
+{
+ return QTlsPrivate::X509CertificateOpenSSL::certificatesFromPem;
+}
+
+QTlsPrivate::X509DerReaderPtr QTlsBackendOpenSSL::X509DerReader() const
+{
+ return QTlsPrivate::X509CertificateOpenSSL::certificatesFromDer;
+}
+
+QTlsPrivate::X509Pkcs12ReaderPtr QTlsBackendOpenSSL::X509Pkcs12Reader() const
+{
+ return QTlsPrivate::X509CertificateOpenSSL::importPkcs12;
+}
+
+QList<int> QTlsBackendOpenSSL::ellipticCurvesIds() const
+{
+ QList<int> ids;
+
+#ifndef OPENSSL_NO_EC
+ const size_t curveCount = q_EC_get_builtin_curves(nullptr, 0);
+ QVarLengthArray<EC_builtin_curve> builtinCurves(static_cast<int>(curveCount));
+
+ if (q_EC_get_builtin_curves(builtinCurves.data(), curveCount) == curveCount) {
+ ids.reserve(curveCount);
+ for (const auto &ec : builtinCurves)
+ ids.push_back(ec.nid);
+ }
+#endif // OPENSSL_NO_EC
+
+ return ids;
+}
+
+ int QTlsBackendOpenSSL::curveIdFromShortName(const QString &name) const
+ {
+ int nid = 0;
+ if (name.isEmpty())
+ return nid;
+
+ ensureInitialized(); // TLSTODO: check if it's needed!
+#ifndef OPENSSL_NO_EC
+ const QByteArray curveNameLatin1 = name.toLatin1();
+ nid = q_OBJ_sn2nid(curveNameLatin1.data());
+
+ if (nid == 0)
+ nid = q_EC_curve_nist2nid(curveNameLatin1.data());
+#endif // !OPENSSL_NO_EC
+
+ return nid;
+ }
+
+ int QTlsBackendOpenSSL::curveIdFromLongName(const QString &name) const
+ {
+ int nid = 0;
+ if (name.isEmpty())
+ return nid;
+
+ ensureInitialized();
+
+#ifndef OPENSSL_NO_EC
+ const QByteArray curveNameLatin1 = name.toLatin1();
+ nid = q_OBJ_ln2nid(curveNameLatin1.data());
+#endif
+
+ return nid;
+ }
+
+ QString QTlsBackendOpenSSL::shortNameForId(int id) const
+ {
+ QString result;
+
+#ifndef OPENSSL_NO_EC
+ if (id != 0)
+ result = QString::fromLatin1(q_OBJ_nid2sn(id));
+#endif
+
+ return result;
+ }
+
+QString QTlsBackendOpenSSL::longNameForId(int id) const
+{
+ QString result;
+
+#ifndef OPENSSL_NO_EC
+ if (id != 0)
+ result = QString::fromLatin1(q_OBJ_nid2ln(id));
+#endif
+
+ return result;
+}
+
+// NIDs of named curves allowed in TLS as per RFCs 4492 and 7027,
+// see also https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8
+static const int tlsNamedCurveNIDs[] = {
+ // RFC 4492
+ NID_sect163k1,
+ NID_sect163r1,
+ NID_sect163r2,
+ NID_sect193r1,
+ NID_sect193r2,
+ NID_sect233k1,
+ NID_sect233r1,
+ NID_sect239k1,
+ NID_sect283k1,
+ NID_sect283r1,
+ NID_sect409k1,
+ NID_sect409r1,
+ NID_sect571k1,
+ NID_sect571r1,
+
+ NID_secp160k1,
+ NID_secp160r1,
+ NID_secp160r2,
+ NID_secp192k1,
+ NID_X9_62_prime192v1, // secp192r1
+ NID_secp224k1,
+ NID_secp224r1,
+ NID_secp256k1,
+ NID_X9_62_prime256v1, // secp256r1
+ NID_secp384r1,
+ NID_secp521r1,
+
+ // RFC 7027
+ NID_brainpoolP256r1,
+ NID_brainpoolP384r1,
+ NID_brainpoolP512r1
+};
+
+const size_t tlsNamedCurveNIDCount = sizeof(tlsNamedCurveNIDs) / sizeof(tlsNamedCurveNIDs[0]);
+
+bool QTlsBackendOpenSSL::isTlsNamedCurve(int id) const
+{
+ const int *const tlsNamedCurveNIDsEnd = tlsNamedCurveNIDs + tlsNamedCurveNIDCount;
+ return std::find(tlsNamedCurveNIDs, tlsNamedCurveNIDsEnd, id) != tlsNamedCurveNIDsEnd;
+}
+
+QString QTlsBackendOpenSSL::msgErrorsDuringHandshake()
+{
+ return QSslSocket::tr("Error during SSL handshake: %1").arg(getErrorsFromOpenSsl());
+}
+
+QSslCipher QTlsBackendOpenSSL::qt_OpenSSL_cipher_to_QSslCipher(const SSL_CIPHER *cipher)
+{
+ Q_ASSERT(cipher);
+ char buf [256] = {};
+ const QString desc = QString::fromLatin1(q_SSL_CIPHER_description(cipher, buf, sizeof(buf)));
+ int supportedBits = 0;
+ const int bits = q_SSL_CIPHER_get_bits(cipher, &supportedBits);
+ return createCiphersuite(desc, bits, supportedBits);
+}
+
+void QTlsBackendOpenSSL::forceAutotestSecurityLevel()
+{
+ QSslContext::forceAutoTestSecurityLevel();
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qtlsbackend_openssl_p.cpp"