// 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 #include #include #include #include #include #include #include #include #include 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 mapFromX509Name(X509_NAME *name) { if (!name) return {}; QMultiMap info; for (int i = 0; i < q_X509_NAME_entry_count(name); ++i) { X509_NAME_ENTRY *e = q_X509_NAME_get_entry(name, i); QByteArray name = asn1ObjectName(q_X509_NAME_ENTRY_get_object(e)); unsigned char *data = nullptr; int size = q_ASN1_STRING_to_UTF8(&data, q_X509_NAME_ENTRY_get_data(e)); info.insert(name, QString::fromUtf8((char*)data, size)); q_CRYPTO_free(data, nullptr, 0); } return info; } QDateTime dateTimeFromASN1(const ASN1_TIME *aTime) { QDateTime result; tm lTime; if (q_ASN1_TIME_to_tm(aTime, &lTime)) { QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday); QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec); result = QDateTime(resDate, resTime, 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 data; int count = q_BIO_read(bio, data.data(), 16384); if ( count > 0 ) result = QByteArray( data.data(), count ); return QString::fromLatin1(result); } QVariant x509UnknownExtensionToValue(X509_EXTENSION *ext) { // Get the extension specific method object if available // we cast away the const-ness here because some versions of openssl // don't use const for the parameters in the functions pointers stored // in the object. Q_ASSERT(ext); X509V3_EXT_METHOD *meth = const_cast(q_X509V3_EXT_get(ext)); if (!meth) { ASN1_OCTET_STRING *value = q_X509_EXTENSION_get_data(ext); Q_ASSERT(value); QByteArray result( reinterpret_cast(q_ASN1_STRING_get0_data(value)), q_ASN1_STRING_length(value)); return result; } void *ext_internal = q_X509V3_EXT_d2i(ext); if (!ext_internal) return {}; const auto extCleaner = qScopeGuard([meth, ext_internal]{ Q_ASSERT(ext_internal && meth); if (meth->it) q_ASN1_item_free(static_cast(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(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(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(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(q_X509V3_EXT_d2i(ext)); if (!info) return {}; QVariantMap result; for (int i=0; i < q_SKM_sk_num(info); i++) { ACCESS_DESCRIPTION *ad = q_SKM_sk_value(ACCESS_DESCRIPTION, info, i); GENERAL_NAME *name = ad->location; if (name->type == GEN_URI) { int len = q_ASN1_STRING_length(name->d.uniformResourceIdentifier); if (len < 0 || len >= 8192) { // broken name continue; } const char *uriStr = reinterpret_cast(q_ASN1_STRING_get0_data(name->d.uniformResourceIdentifier)); const QString uri = QString::fromUtf8(uriStr, len); result[QString::fromUtf8(asn1ObjectName(ad->method))] = uri; } else { qCWarning(lcTlsBackend) << "Strange location type" << name->type; } } q_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(q_X509V3_EXT_d2i(ext)); if (!auth_key) return {}; QVariantMap result; // keyid if (auth_key->keyid) { QByteArray keyid(reinterpret_cast(auth_key->keyid->data), auth_key->keyid->length); result["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 *; ErrorListPtr errors = nullptr; // Error list is attached to either 'SSL' or 'X509_STORE'. if (X509_STORE *store = q_X509_STORE_CTX_get0_store(ctx)) // We try store first: errors = ErrorListPtr(q_X509_STORE_get_ex_data(store, 0)); if (!errors) { // Not found on store? Try SSL and its external data then. According to the OpenSSL's // documentation: // // "Whenever a X509_STORE_CTX object is created for the verification of the // peer's certificate during a handshake, a pointer to the SSL object is // stored into the X509_STORE_CTX object to identify the connection affected. // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be // used with the correct index." // TLSTODO: verification callback has to change as soon as TlsCryptographer is in place. // This is a temporary solution for now to ease the transition. const auto offset = QTlsBackendOpenSSL::s_indexForSSLExtraData + TlsCryptographOpenSSL::errorOffsetInExData; if (SSL *ssl = static_cast(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx()))) errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset)); } if (!errors) { qCWarning(lcTlsBackend, "Neither X509_STORE, nor SSL contains error list, verification failed"); return 0; } errors->append(X509CertificateOpenSSL::errorEntryFromStoreContext(ctx)); } // Always return OK to allow verification to continue. We handle the // errors gracefully after collecting all errors, after verification has // completed. return 1; } X509CertificateOpenSSL::X509CertificateOpenSSL() = default; X509CertificateOpenSSL::~X509CertificateOpenSSL() { if (x509) q_X509_free(x509); } bool X509CertificateOpenSSL::isEqual(const X509Certificate &rhs) const { //TLSTODO: to make it safe I'll check the backend type later. const auto &other = static_cast(rhs); if (x509 && other.x509) { const int ret = q_X509_cmp(x509, other.x509); if (ret >= -1 && ret <= 1) return ret == 0; QTlsBackendOpenSSL::logAndClearErrorQueue(); } return false; } bool X509CertificateOpenSSL::isSelfSigned() const { if (!x509) return false; return q_X509_check_issued(x509, x509) == X509_V_OK; } QMultiMap X509CertificateOpenSSL::subjectAlternativeNames() const { QMultiMap result; if (!x509) return result; auto *altNames = static_cast(q_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr)); if (!altNames) return result; auto altName = [](ASN1_IA5STRING *ia5, int len) { const char *altNameStr = reinterpret_cast(q_ASN1_STRING_get0_data(ia5)); return QString::fromLatin1(altNameStr, len); }; for (int i = 0; i < q_sk_GENERAL_NAME_num(altNames); ++i) { const GENERAL_NAME *genName = q_sk_GENERAL_NAME_value(altNames, i); if (genName->type != GEN_DNS && genName->type != GEN_EMAIL && genName->type != GEN_IPADD) continue; const int len = q_ASN1_STRING_length(genName->d.ia5); if (len < 0 || len >= 8192) { // broken name continue; } switch (genName->type) { case GEN_DNS: result.insert(QSsl::DnsEntry, altName(genName->d.ia5, len)); break; case GEN_EMAIL: result.insert(QSsl::EmailEntry, altName(genName->d.ia5, len)); break; case GEN_IPADD: { QHostAddress ipAddress; switch (len) { case 4: // IPv4 ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast(genName->d.iPAddress->data))); break; case 16: // IPv6 ipAddress = QHostAddress(reinterpret_cast(genName->d.iPAddress->data)); break; default: // Unknown IP address format break; } if (!ipAddress.isNull()) result.insert(QSsl::IpAddressEntry, ipAddress.toString()); break; } default: break; } } q_OPENSSL_sk_pop_free((OPENSSL_STACK*)altNames, reinterpret_cast(q_GENERAL_NAME_free)); return result; } TlsKey *X509CertificateOpenSSL::publicKey() const { if (!x509) return {}; return TlsKeyOpenSSL::publicKeyFromX509(x509); } QByteArray X509CertificateOpenSSL::toPem() const { if (!x509) return {}; return x509ToQByteArray(x509, QSsl::Pem); } QByteArray X509CertificateOpenSSL::toDer() const { if (!x509) return {}; return x509ToQByteArray(x509, QSsl::Der); } QString X509CertificateOpenSSL::toText() const { if (!x509) return {}; return x509ToText(x509); } Qt::HANDLE X509CertificateOpenSSL::handle() const { return Qt::HANDLE(x509); } size_t X509CertificateOpenSSL::hash(size_t seed) const noexcept { if (x509) { const EVP_MD *sha1 = q_EVP_sha1(); unsigned int len = 0; unsigned char md[EVP_MAX_MD_SIZE]; q_X509_digest(x509, sha1, md, &len); return qHashBits(md, len, seed); } return seed; } QSslCertificate X509CertificateOpenSSL::certificateFromX509(X509 *x509) { QSslCertificate certificate; auto *backend = QTlsBackend::backend(certificate); if (!backend || !x509) return certificate; ASN1_TIME *nbef = q_X509_getm_notBefore(x509); if (nbef) backend->notValidBefore = dateTimeFromASN1(nbef); ASN1_TIME *naft = q_X509_getm_notAfter(x509); if (naft) backend->notValidAfter = dateTimeFromASN1(naft); backend->null = false; backend->x509 = q_X509_dup(x509); backend->issuerInfoEntries = mapFromX509Name(q_X509_get_issuer_name(x509)); backend->subjectInfoEntries = mapFromX509Name(q_X509_get_subject_name(x509)); backend->versionString = QByteArray::number(qlonglong(q_X509_get_version(x509)) + 1); if (ASN1_INTEGER *serialNumber = q_X509_get_serialNumber(x509)) { QByteArray hexString; hexString.reserve(serialNumber->length * 3); for (int a = 0; a < serialNumber->length; ++a) { hexString += QByteArray::number(serialNumber->data[a], 16).rightJustified(2, '0'); hexString += ':'; } hexString.chop(1); backend->serialNumberString = hexString; } backend->parseExtensions(); return certificate; } QList X509CertificateOpenSSL::stackOfX509ToQSslCertificates(STACK_OF(X509) *x509) { if (!x509) return {}; QList certificates; for (int i = 0; i < q_sk_X509_num(x509); ++i) { if (X509 *entry = q_sk_X509_value(x509, i)) certificates << certificateFromX509(entry); } return certificates; } QSslErrorEntry X509CertificateOpenSSL::errorEntryFromStoreContext(X509_STORE_CTX *ctx) { Q_ASSERT(ctx); return {q_X509_STORE_CTX_get_error(ctx), q_X509_STORE_CTX_get_error_depth(ctx)}; } QList X509CertificateOpenSSL::verify(const QList &chain, const QString &hostName) { // This was previously QSslSocketPrivate::verify(). auto roots = QSslConfiguration::defaultConfiguration().caCertificates(); #ifndef Q_OS_WIN // On Windows, system CA certificates are already set as default ones. // No need to add them again (and again) and also, if the default configuration // has its own set of CAs, this probably should not be amended by the ones // from the 'ROOT' store, since it's not what an application chose to trust. if (QSslSocketPrivate::rootCertOnDemandLoadingSupported()) roots.append(QSslSocketPrivate::systemCaCertificates()); #endif // Q_OS_WIN return verify(roots, chain, hostName); } QList X509CertificateOpenSSL::verify(const QList &caCertificates, const QList &certificateChain, const QString &hostName) { // This was previously QSslSocketPrivate::verify(). if (certificateChain.size() <= 0) return {QSslError(QSslError::UnspecifiedError)}; QList errors; X509_STORE *certStore = q_X509_STORE_new(); if (!certStore) { qCWarning(lcTlsBackend) << "Unable to create certificate store"; errors << QSslError(QSslError::UnspecifiedError); return errors; } const std::unique_ptr storeGuard(certStore, q_X509_STORE_free); const QDateTime now = QDateTime::currentDateTimeUtc(); for (const QSslCertificate &caCertificate : caCertificates) { // From https://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html: // // If several CA certificates matching the name, key identifier, and // serial number condition are available, only the first one will be // examined. This may lead to unexpected results if the same CA // certificate is available with different expiration dates. If a // ``certificate expired'' verification error occurs, no other // certificate will be searched. Make sure to not have expired // certificates mixed with valid ones. // // See also: QSslContext::sharedFromConfiguration() if (caCertificate.expiryDate() >= now) { q_X509_STORE_add_cert(certStore, reinterpret_cast(caCertificate.handle())); } } QList lastErrors; if (!q_X509_STORE_set_ex_data(certStore, 0, &lastErrors)) { qCWarning(lcTlsBackend) << "Unable to attach external data (error list) to a store"; errors << QSslError(QSslError::UnspecifiedError); return errors; } // Register a custom callback to get all verification errors. q_X509_STORE_set_verify_cb(certStore, qt_X509Callback); // Build the chain of intermediate certificates STACK_OF(X509) *intermediates = nullptr; if (certificateChain.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(cert.handle())); } } X509_STORE_CTX *storeContext = q_X509_STORE_CTX_new(); if (!storeContext) { errors << QSslError(QSslError::UnspecifiedError); return errors; } std::unique_ptr ctxGuard(storeContext, q_X509_STORE_CTX_free); if (!q_X509_STORE_CTX_init(storeContext, certStore, reinterpret_cast(certificateChain[0].handle()), intermediates)) { errors << QSslError(QSslError::UnspecifiedError); return errors; } // Now we can actually perform the verification of the chain we have built. // We ignore the result of this function since we process errors via the // callback. (void) q_X509_verify_cert(storeContext); ctxGuard.reset(); q_OPENSSL_sk_free((OPENSSL_STACK *)intermediates); // Now process the errors if (certificateChain[0].isBlacklisted()) errors << QSslError(QSslError::CertificateBlacklisted, certificateChain[0]); // Check the certificate name against the hostname if one was specified if (!hostName.isEmpty() && !TlsCryptograph::isMatchingHostname(certificateChain[0], hostName)) { // No matches in common names or alternate names. QSslError error(QSslError::HostNameMismatch, certificateChain[0]); errors << error; } // Translate errors from the error list into QSslErrors. errors.reserve(errors.size() + lastErrors.size()); for (const auto &error : std::as_const(lastErrors)) errors << openSSLErrorToQSslError(error.code, certificateChain.value(error.depth)); return errors; } QList X509CertificateOpenSSL::certificatesFromPem(const QByteArray &pem, int count) { QList certificates; int offset = 0; while (count == -1 || certificates.size() < count) { int startPos = pem.indexOf(BEGINCERTSTRING, offset); if (startPos == -1) break; startPos += sizeof(BEGINCERTSTRING) - 1; if (!matchLineFeed(pem, &startPos)) break; int endPos = pem.indexOf(ENDCERTSTRING, startPos); if (endPos == -1) break; offset = endPos + sizeof(ENDCERTSTRING) - 1; if (offset < pem.size() && !matchLineFeed(pem, &offset)) break; QByteArray decoded = QByteArray::fromBase64( QByteArray::fromRawData(pem.data() + startPos, endPos - startPos)); const unsigned char *data = (const unsigned char *)decoded.data(); if (X509 *x509 = q_d2i_X509(nullptr, &data, decoded.size())) { certificates << certificateFromX509(x509); q_X509_free(x509); } } return certificates; } QList X509CertificateOpenSSL::certificatesFromDer(const QByteArray &der, int count) { QList certificates; const unsigned char *data = (const unsigned char *)der.data(); int size = der.size(); while (size > 0 && (count == -1 || certificates.size() < count)) { if (X509 *x509 = q_d2i_X509(nullptr, &data, size)) { certificates << certificateFromX509(x509); q_X509_free(x509); } else { break; } size -= ((const char *)data - der.data()); } return certificates; } bool X509CertificateOpenSSL::importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert, QList *caCertificates, const QByteArray &passPhrase) { // These are required Q_ASSERT(device); Q_ASSERT(key); Q_ASSERT(cert); // Read the file into a BIO QByteArray pkcs12data = device->readAll(); if (pkcs12data.size() == 0) return false; BIO *bio = q_BIO_new_mem_buf(const_cast(pkcs12data.constData()), pkcs12data.size()); if (!bio) { qCWarning(lcTlsBackend, "BIO_new_mem_buf returned null"); return false; } const auto bioRaii = qScopeGuard([bio]{q_BIO_free(bio);}); // Create the PKCS#12 object PKCS12 *p12 = q_d2i_PKCS12_bio(bio, nullptr); if (!p12) { qCWarning(lcTlsBackend, "Unable to read PKCS#12 structure, %s", q_ERR_error_string(q_ERR_get_error(), nullptr)); return false; } const auto p12Raii = qScopeGuard([p12]{q_PKCS12_free(p12);}); // Extract the data EVP_PKEY *pkey = nullptr; X509 *x509 = nullptr; STACK_OF(X509) *ca = nullptr; if (!q_PKCS12_parse(p12, passPhrase.constData(), &pkey, &x509, &ca)) { qCWarning(lcTlsBackend, "Unable to parse PKCS#12 structure, %s", q_ERR_error_string(q_ERR_get_error(), nullptr)); return false; } const auto x509Raii = qScopeGuard([x509]{q_X509_free(x509);}); const auto keyRaii = qScopeGuard([pkey]{q_EVP_PKEY_free(pkey);}); const auto caRaii = qScopeGuard([ca] { q_OPENSSL_sk_pop_free(reinterpret_cast(ca), reinterpret_cast(q_X509_free)); }); // Convert to Qt types auto *tlsKey = QTlsBackend::backend(*key); if (!tlsKey || !tlsKey->fromEVP_PKEY(pkey)) { qCWarning(lcTlsBackend, "Unable to convert private key"); return false; } *cert = certificateFromX509(x509); if (caCertificates) *caCertificates = stackOfX509ToQSslCertificates(ca); return true; } QSslError X509CertificateOpenSSL::openSSLErrorToQSslError(int errorCode, const QSslCertificate &cert) { QSslError error; switch (errorCode) { case X509_V_OK: // X509_V_OK is also reported if the peer had no certificate. break; case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: error = QSslError(QSslError::UnableToGetIssuerCertificate, cert); break; case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: error = QSslError(QSslError::UnableToDecryptCertificateSignature, cert); break; case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: error = QSslError(QSslError::UnableToDecodeIssuerPublicKey, cert); break; case X509_V_ERR_CERT_SIGNATURE_FAILURE: error = QSslError(QSslError::CertificateSignatureFailed, cert); break; case X509_V_ERR_CERT_NOT_YET_VALID: error = QSslError(QSslError::CertificateNotYetValid, cert); break; case X509_V_ERR_CERT_HAS_EXPIRED: error = QSslError(QSslError::CertificateExpired, cert); break; case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: error = QSslError(QSslError::InvalidNotBeforeField, cert); break; case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: error = QSslError(QSslError::InvalidNotAfterField, cert); break; case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: error = QSslError(QSslError::SelfSignedCertificate, cert); break; case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: error = QSslError(QSslError::SelfSignedCertificateInChain, cert); break; case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: error = QSslError(QSslError::UnableToGetLocalIssuerCertificate, cert); break; case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: error = QSslError(QSslError::UnableToVerifyFirstCertificate, cert); break; case X509_V_ERR_CERT_REVOKED: error = QSslError(QSslError::CertificateRevoked, cert); break; case X509_V_ERR_INVALID_CA: error = QSslError(QSslError::InvalidCaCertificate, cert); break; case X509_V_ERR_PATH_LENGTH_EXCEEDED: error = QSslError(QSslError::PathLengthExceeded, cert); break; case X509_V_ERR_INVALID_PURPOSE: error = QSslError(QSslError::InvalidPurpose, cert); break; case X509_V_ERR_CERT_UNTRUSTED: error = QSslError(QSslError::CertificateUntrusted, cert); break; case X509_V_ERR_CERT_REJECTED: error = QSslError(QSslError::CertificateRejected, cert); break; default: error = QSslError(QSslError::UnspecifiedError, cert); break; } return error; } void X509CertificateOpenSSL::parseExtensions() { extensions.clear(); if (!x509) return; int count = q_X509_get_ext_count(x509); if (count <= 0) return; extensions.reserve(count); for (int i = 0; i < count; i++) { X509_EXTENSION *ext = q_X509_get_ext(x509, i); if (!ext) { qCWarning(lcTlsBackend) << "Invalid (nullptr) extension at index" << i; continue; } extensions << convertExtension(ext); } // Converting an extension may result in an error(s), clean them up: QTlsBackendOpenSSL::clearErrorQueue(); } X509CertificateBase::X509CertificateExtension X509CertificateOpenSSL::convertExtension(X509_EXTENSION *ext) { Q_ASSERT(ext); X509CertificateExtension result; ASN1_OBJECT *obj = q_X509_EXTENSION_get_object(ext); if (!obj) return result; result.oid = QString::fromUtf8(asn1ObjectId(obj)); result.name = QString::fromUtf8(asn1ObjectName(obj)); result.critical = bool(q_X509_EXTENSION_get_critical(ext)); // Lets see if we have custom support for this one QVariant extensionValue = x509ExtensionToValue(ext); if (extensionValue.isValid()) { result.value = extensionValue; result.supported = true; return result; } extensionValue = x509UnknownExtensionToValue(ext); if (extensionValue.isValid()) result.value = extensionValue; result.supported = false; return result; } } // namespace QTlsPrivate QT_END_NAMESPACE