summaryrefslogtreecommitdiffstats
path: root/src/plugins/tls/openssl/qx509_openssl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/tls/openssl/qx509_openssl.cpp')
-rw-r--r--src/plugins/tls/openssl/qx509_openssl.cpp947
1 files changed, 947 insertions, 0 deletions
diff --git a/src/plugins/tls/openssl/qx509_openssl.cpp b/src/plugins/tls/openssl/qx509_openssl.cpp
new file mode 100644
index 0000000000..0cd3749f88
--- /dev/null
+++ b/src/plugins/tls/openssl/qx509_openssl.cpp
@@ -0,0 +1,947 @@
+// 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"
+
+#include <QtNetwork/private/qsslcertificate_p.h>
+
+#include <QtNetwork/qsslsocket.h>
+#include <QtNetwork/qhostaddress.h>
+
+#include <QtCore/qendian.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qscopeguard.h>
+#include <QtCore/qtimezone.h>
+#include <QtCore/qvarlengtharray.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+namespace QTlsPrivate {
+
+namespace {
+
+QByteArray asn1ObjectId(ASN1_OBJECT *object)
+{
+ if (!object)
+ return {};
+
+ char buf[80] = {}; // The openssl docs a buffer length of 80 should be more than enough
+ q_OBJ_obj2txt(buf, sizeof(buf), object, 1); // the 1 says always use the oid not the long name
+
+ return QByteArray(buf);
+}
+
+QByteArray asn1ObjectName(ASN1_OBJECT *object)
+{
+ if (!object)
+ return {};
+
+ const int nid = q_OBJ_obj2nid(object);
+ if (nid != NID_undef)
+ return QByteArray(q_OBJ_nid2sn(nid));
+
+ return asn1ObjectId(object);
+}
+
+QMultiMap<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;
+}
+
+QDateTime dateTimeFromASN1(const ASN1_TIME *aTime)
+{
+ QDateTime result;
+ tm lTime;
+
+ if (q_ASN1_TIME_to_tm(aTime, &lTime)) {
+ QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday);
+ QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec);
+ result = QDateTime(resDate, resTime, QTimeZone::UTC);
+ }
+
+ return result;
+}
+
+
+#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----"
+#define ENDCERTSTRING "-----END CERTIFICATE-----"
+
+QByteArray x509ToQByteArray(X509 *x509, QSsl::EncodingFormat format)
+{
+ Q_ASSERT(x509);
+
+ // Use i2d_X509 to convert the X509 to an array.
+ const int length = q_i2d_X509(x509, nullptr);
+ if (length <= 0) {
+ QTlsBackendOpenSSL::logAndClearErrorQueue();
+ return {};
+ }
+
+ QByteArray array;
+ array.resize(length);
+
+ char *data = array.data();
+ char **dataP = &data;
+ unsigned char **dataPu = (unsigned char **)dataP;
+ if (q_i2d_X509(x509, dataPu) < 0)
+ return QByteArray();
+
+ if (format == QSsl::Der)
+ return array;
+
+ // Convert to Base64 - wrap at 64 characters.
+ array = array.toBase64();
+ QByteArray tmp;
+ for (int i = 0; i <= array.size() - 64; i += 64) {
+ tmp += QByteArray::fromRawData(array.data() + i, 64);
+ tmp += '\n';
+ }
+ if (int remainder = array.size() % 64) {
+ tmp += QByteArray::fromRawData(array.data() + array.size() - remainder, remainder);
+ tmp += '\n';
+ }
+
+ return BEGINCERTSTRING "\n" + tmp + ENDCERTSTRING "\n";
+}
+
+QString x509ToText(X509 *x509)
+{
+ Q_ASSERT(x509);
+
+ QByteArray result;
+ BIO *bio = q_BIO_new(q_BIO_s_mem());
+ if (!bio)
+ return QString();
+ const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);});
+
+ q_X509_print(bio, x509);
+
+ QVarLengthArray<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 (!ext_internal)
+ return {};
+
+ const auto extCleaner = qScopeGuard([meth, ext_internal]{
+ Q_ASSERT(ext_internal && meth);
+
+ if (meth->it)
+ q_ASN1_item_free(static_cast<ASN1_VALUE *>(ext_internal), ASN1_ITEM_ptr(meth->it));
+ else if (meth->ext_free)
+ meth->ext_free(ext_internal);
+ else
+ qCWarning(lcTlsBackend, "No method to free an unknown extension, a potential memory leak?");
+ });
+
+ // If this extension can be converted
+ if (meth->i2v) {
+ STACK_OF(CONF_VALUE) *val = meth->i2v(meth, ext_internal, nullptr);
+ const auto stackCleaner = qScopeGuard([val]{
+ if (val)
+ q_OPENSSL_sk_pop_free((OPENSSL_STACK *)val, (void(*)(void*))q_X509V3_conf_free);
+ });
+
+ QVariantMap map;
+ QVariantList list;
+ bool isMap = false;
+
+ for (int j = 0; j < q_SKM_sk_num(val); j++) {
+ CONF_VALUE *nval = q_SKM_sk_value(CONF_VALUE, val, j);
+ if (nval->name && nval->value) {
+ isMap = true;
+ map[QString::fromUtf8(nval->name)] = QString::fromUtf8(nval->value);
+ } else if (nval->name) {
+ list << QString::fromUtf8(nval->name);
+ } else if (nval->value) {
+ list << QString::fromUtf8(nval->value);
+ }
+ }
+
+ if (isMap)
+ return map;
+ else
+ return list;
+ } else if (meth->i2s) {
+ const char *hexString = meth->i2s(meth, ext_internal);
+ QVariant result(hexString ? QString::fromUtf8(hexString) : QString{});
+ q_OPENSSL_free((void *)hexString);
+ return result;
+ } else if (meth->i2r) {
+ 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);
+
+ // 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));
+
+ void *ext_internal = nullptr; // The value, returned by X509V3_EXT_d2i.
+ const auto extCleaner = qScopeGuard([meth, &ext_internal]() {
+ if (!meth || !ext_internal)
+ return;
+
+ if (meth->it)
+ q_ASN1_item_free(static_cast<ASN1_VALUE *>(ext_internal), ASN1_ITEM_ptr(meth->it));
+ else if (meth->ext_free)
+ meth->ext_free(ext_internal);
+ else
+ qWarning(lcTlsBackend, "Cannot free an extension, a potential memory leak?");
+ });
+
+ const char * hexString = nullptr; // The value returned by meth->i2s.
+ const auto hexStringCleaner = qScopeGuard([&hexString](){
+ if (hexString)
+ q_OPENSSL_free((void*)hexString);
+ });
+
+ switch (nid) {
+ case NID_basic_constraints:
+ {
+ BASIC_CONSTRAINTS *basic = reinterpret_cast<BASIC_CONSTRAINTS *>(q_X509V3_EXT_d2i(ext));
+ if (!basic)
+ return {};
+ QVariantMap result;
+ result["ca"_L1] = basic->ca ? true : false;
+ if (basic->pathlen)
+ result["pathLenConstraint"_L1] = (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(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_AUTHORITY_INFO_ACCESS_free(info);
+ return result;
+ }
+ break;
+ case NID_subject_key_identifier:
+ {
+ ext_internal = q_X509V3_EXT_d2i(ext);
+ if (!ext_internal)
+ return {};
+
+ hexString = meth->i2s(meth, ext_internal);
+ return QVariant(QString::fromUtf8(hexString));
+ }
+ 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["keyid"_L1] = keyid.toHex();
+ }
+
+ // issuer
+ // TODO: GENERAL_NAMES
+
+ // serial
+ if (auth_key->serial)
+ result["serial"_L1] = (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 = QTlsBackendOpenSSL::s_indexForSSLExtraData
+ + TlsCryptographOpenSSL::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 = dateTimeFromASN1(nbef);
+
+ ASN1_TIME *naft = q_X509_getm_notAfter(x509);
+ if (naft)
+ backend->notValidAfter = dateTimeFromASN1(naft);
+
+ backend->null = false;
+ backend->x509 = q_X509_dup(x509);
+
+ backend->issuerInfoEntries = mapFromX509Name(q_X509_get_issuer_name(x509));
+ backend->subjectInfoEntries = mapFromX509Name(q_X509_get_subject_name(x509));
+ backend->versionString = QByteArray::number(qlonglong(q_X509_get_version(x509)) + 1);
+
+ if (ASN1_INTEGER *serialNumber = q_X509_get_serialNumber(x509)) {
+ QByteArray hexString;
+ hexString.reserve(serialNumber->length * 3);
+ for (int a = 0; a < serialNumber->length; ++a) {
+ hexString += QByteArray::number(serialNumber->data[a], 16).rightJustified(2, '0');
+ hexString += ':';
+ }
+ hexString.chop(1);
+ backend->serialNumberString = hexString;
+ }
+
+ backend->parseExtensions();
+
+ return certificate;
+}
+
+QList<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::rootCertOnDemandLoadingSupported())
+ 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.size() <= 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::sharedFromConfiguration()
+ 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.size() > 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() && !TlsCryptograph::isMatchingHostname(certificateChain[0], hostName)) {
+ // No matches in common names or alternate names.
+ QSslError error(QSslError::HostNameMismatch, certificateChain[0]);
+ errors << error;
+ }
+
+ // Translate errors from the error list into QSslErrors.
+ errors.reserve(errors.size() + lastErrors.size());
+ for (const auto &error : std::as_const(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 QTlsPrivate
+
+QT_END_NAMESPACE