Move QSslCertificate's details and cert-related code to the plugins

Also since we have to properly support 'no-ssl' configure option
(alas, we support QSslCertificate on such builds) - introduce
a minimal crippled QTlsBackendCertOnly, which depends on
X509CertificateGeneric.

Pick-to: dev
Fixes: QTBUG-90954
Task-number: QTBUG-65922
Change-Id: Ib9d62903f16b7c0eaaa23e319a822c24a7631dc6
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Timur Pocheptsov 2021-02-22 13:45:07 +01:00
parent 0ea39a7c42
commit 41fc143635
20 changed files with 541 additions and 1727 deletions

View File

@ -47,6 +47,10 @@ qt_internal_add_module(Network
ssl/qsslcertificate.cpp ssl/qsslcertificate.h ssl/qsslcertificate_p.h
ssl/qsslcertificateextension.cpp ssl/qsslcertificateextension.h ssl/qsslcertificateextension_p.h
ssl/qtls_utils_p.h
ssl/qtlsbackend.cpp ssl/qtlsbackend_p.h
ssl/qtlsbackend_cert.cpp ssl/qtlsbackend_cert_p.h
ssl/qx509_base.cpp ssl/qx509_base_p.h
ssl/qx509_generic.cpp ssl/qx509_generic_p.h
DEFINES
QT_NO_FOREACH
QT_NO_USING_NAMESPACE
@ -310,11 +314,6 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_system_proxies
QT_USE_SYSTEM_PROXIES
)
qt_internal_extend_target(Network CONDITION NOT QT_FEATURE_openssl
SOURCES
ssl/qsslcertificate_qt.cpp
)
qt_internal_extend_target(Network CONDITION QT_FEATURE_ssl
SOURCES
ssl/qocspresponse.cpp ssl/qocspresponse.h ssl/qocspresponse_p.h
@ -326,14 +325,11 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_ssl
ssl/qsslkey.h ssl/qsslkey_p.cpp ssl/qsslkey_p.h
ssl/qsslpresharedkeyauthenticator.cpp ssl/qsslpresharedkeyauthenticator.h ssl/qsslpresharedkeyauthenticator_p.h
ssl/qsslsocket.cpp ssl/qsslsocket.h ssl/qsslsocket_p.h
ssl/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
SOURCES
ssl/qsslcertificate_schannel.cpp
ssl/qssldiffiehellmanparameters_dummy.cpp
ssl/qsslellipticcurve_dummy.cpp
ssl/qsslsocket_qt.cpp
@ -341,7 +337,6 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_schannel AND QT_FEATURE_s
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
@ -360,7 +355,6 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_securetransport AND QT_FE
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
)
@ -371,7 +365,6 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_dtls AND QT_FEATURE_ssl
qt_internal_extend_target(Network CONDITION QT_FEATURE_openssl AND QT_FEATURE_ssl
SOURCES
ssl/qsslcertificate_openssl.cpp
ssl/qsslcontext_openssl.cpp ssl/qsslcontext_openssl_p.h
ssl/qssldiffiehellmanparameters_openssl.cpp
ssl/qsslellipticcurve_openssl.cpp

View File

@ -47,6 +47,7 @@
#include "qsslsocket_openssl_p.h"
#include "qsslcertificate_p.h"
#include "qdtls_openssl_p.h"
#include "qx509_openssl_p.h"
#include "qudpsocket.h"
#include "qssl_p.h"
@ -258,7 +259,7 @@ extern "C" int q_X509DtlsCallback(int ok, X509_STORE_CTX *ctx)
}
auto dtls = static_cast<dtlsopenssl::DtlsState *>(generic);
dtls->x509Errors.append(QSslErrorEntry::fromStoreContext(ctx));
dtls->x509Errors.append(QSsl::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx));
}
// Always return 1 (OK) to allow verification to continue. We handle the
@ -1263,9 +1264,6 @@ unsigned QDtlsPrivateOpenSSL::pskServerCallback(const char *identity, unsigned c
return pskLength;
}
// The definition is located in qsslsocket_openssl.cpp.
QSslError _q_OpenSSL_to_QSslError(int errorCode, const QSslCertificate &cert);
bool QDtlsPrivateOpenSSL::verifyPeer()
{
// DTLSTODO: Windows-specific code for CA fetcher is not here yet.
@ -1300,10 +1298,11 @@ bool QDtlsPrivateOpenSSL::verifyPeer()
}
// Translate errors from the error list into QSslErrors
using CertClass = QSsl::X509CertificateOpenSSL;
errors.reserve(errors.size() + opensslErrors.size());
for (const auto &error : qAsConst(opensslErrors)) {
errors << _q_OpenSSL_to_QSslError(error.code,
dtlsConfiguration.peerCertificateChain.value(error.depth));
const auto value = dtlsConfiguration.peerCertificateChain.value(error.depth);
errors << CertClass::openSSLErrorToQSslError(error.code, value);
}
tlsErrors = errors;
@ -1318,11 +1317,11 @@ void QDtlsPrivateOpenSSL::storePeerCertificates()
// peer certificate and the chain may be empty if the peer didn't present
// any certificate.
X509 *x509 = q_SSL_get_peer_certificate(dtls.tlsConnection.data());
dtlsConfiguration.peerCertificate = QSslCertificatePrivate::QSslCertificate_from_X509(x509);
dtlsConfiguration.peerCertificate = QSsl::X509CertificateOpenSSL::certificateFromX509(x509);
q_X509_free(x509);
if (dtlsConfiguration.peerCertificateChain.isEmpty()) {
auto stack = q_SSL_get_peer_cert_chain(dtls.tlsConnection.data());
dtlsConfiguration.peerCertificateChain = QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates(stack);
dtlsConfiguration.peerCertificateChain = QSsl::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(stack);
if (!dtlsConfiguration.peerCertificate.isNull() && mode == QSslSocket::SslServerMode)
dtlsConfiguration.peerCertificateChain.prepend(dtlsConfiguration.peerCertificate);
}

View File

@ -131,22 +131,18 @@
*/
#include <QtNetwork/qtnetworkglobal.h>
#ifndef QT_NO_OPENSSL
#include "qsslsocket_openssl_symbols_p.h"
#endif
#ifdef QT_SECURETRANSPORT
#include "qsslsocket_mac_p.h"
#endif
#if QT_CONFIG(schannel)
#include "qsslsocket_schannel_p.h"
#endif
#if QT_CONFIG(regularexpression)
#include "qregularexpression.h"
#endif
#include "qssl_p.h"
#include "qsslcertificate.h"
#include "qsslcertificateextension_p.h"
#include "qsslcertificate_p.h"
#include "qsslcertificate.h"
#include "qssl_p.h"
#ifndef QT_NO_SSL
#include "qsslsocket_p.h"
#include "qsslkey_p.h"
#endif
@ -156,6 +152,21 @@
QT_BEGIN_NAMESPACE
QSslCertificatePrivate::QSslCertificatePrivate()
{
#ifndef QT_NO_SSL
QSslSocketPrivate::ensureInitialized();
#endif
const QTlsBackend *tlsBackend = QTlsBackend::activeOrAnyBackend();
if (tlsBackend)
backend.reset(tlsBackend->createCertificate());
else
qCWarning(lcSsl, "No TLS backend is available");
}
QSslCertificatePrivate::~QSslCertificatePrivate() = default;
/*!
Constructs a QSslCertificate by reading \a format encoded data
from \a device and using the first certificate found. You can
@ -165,13 +176,25 @@ QT_BEGIN_NAMESPACE
QSslCertificate::QSslCertificate(QIODevice *device, QSsl::EncodingFormat format)
: d(new QSslCertificatePrivate)
{
#ifndef QT_NO_OPENSSL
QSslSocketPrivate::ensureInitialized();
if (device && QSslSocket::supportsSsl())
#else
if (device)
#endif
d->init(device->readAll(), format);
if (device) {
const auto data = device->readAll();
if (data.isEmpty())
return;
const auto *tlsBackend = QTlsBackend::activeOrAnyBackend();
if (!tlsBackend)
return;
auto *X509Reader = format == QSsl::Pem ? tlsBackend->X509PemReader() : tlsBackend->X509DerReader();
if (!X509Reader) {
qCWarning(lcSsl, "Current TLS plugin does not support reading from PEM/DER");
return;
}
QList<QSslCertificate> certs = X509Reader(data, 1);
if (!certs.isEmpty())
d = certs.first().d;
}
}
/*!
@ -183,11 +206,22 @@ QSslCertificate::QSslCertificate(QIODevice *device, QSsl::EncodingFormat format)
QSslCertificate::QSslCertificate(const QByteArray &data, QSsl::EncodingFormat format)
: d(new QSslCertificatePrivate)
{
#ifndef QT_NO_OPENSSL
QSslSocketPrivate::ensureInitialized();
if (QSslSocket::supportsSsl())
#endif
d->init(data, format);
if (data.isEmpty())
return;
const auto *tlsBackend = QTlsBackend::activeOrAnyBackend();
if (!tlsBackend)
return;
auto *X509Reader = format == QSsl::Pem ? tlsBackend->X509PemReader() : tlsBackend->X509DerReader();
if (!X509Reader) {
qCWarning(lcSsl, "Current TLS plugin does not support reading from PEM/DER");
return;
}
QList<QSslCertificate> certs = X509Reader(data, 1);
if (!certs.isEmpty())
d = certs.first().d;
}
/*!
@ -229,6 +263,20 @@ QSslCertificate &QSslCertificate::operator=(const QSslCertificate &other)
returns \c false.
*/
bool QSslCertificate::operator==(const QSslCertificate &other) const
{
if (d == other.d)
return true;
if (isNull() && other.isNull())
return true;
if (d->backend.get() && other.d->backend.get())
return d->backend->isEqual(*other.d->backend.get());
return false;
}
/*!
\fn bool QSslCertificate::operator!=(const QSslCertificate &other) const
@ -246,6 +294,13 @@ QSslCertificate &QSslCertificate::operator=(const QSslCertificate &other)
\sa clear()
*/
bool QSslCertificate::isNull() const
{
if (const auto *backend = d->backend.get())
return backend->isNull();
return true;
}
/*!
Returns \c true if this certificate is blacklisted; otherwise
@ -268,6 +323,13 @@ bool QSslCertificate::isBlacklisted() const
A certificate is considered self-signed its issuer and subject
are identical.
*/
bool QSslCertificate::isSelfSigned() const
{
if (const auto *backend = d->backend.get())
return backend->isSelfSigned();
return false;
}
/*!
Clears the contents of this certificate, making it a null
@ -286,12 +348,26 @@ void QSslCertificate::clear()
\fn QByteArray QSslCertificate::version() const
Returns the certificate's version string.
*/
QByteArray QSslCertificate::version() const
{
if (const auto *backend = d->backend.get())
return backend->version();
return {};
}
/*!
\fn QByteArray QSslCertificate::serialNumber() const
Returns the certificate's serial number string in hexadecimal format.
*/
QByteArray QSslCertificate::serialNumber() const
{
if (const auto *backend = d->backend.get())
return backend->serialNumber();
return {};
}
/*!
Returns a cryptographic digest of this certificate. By default,
@ -313,6 +389,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\sa subjectInfo()
*/
QStringList QSslCertificate::issuerInfo(SubjectInfo info) const
{
if (const auto *backend = d->backend.get())
return backend->issuerInfo(info);
return {};
}
/*!
\fn QStringList QSslCertificate::issuerInfo(const QByteArray &attribute) const
@ -323,6 +406,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\sa subjectInfo()
*/
QStringList QSslCertificate::issuerInfo(const QByteArray &attribute) const
{
if (const auto *backend = d->backend.get())
return backend->issuerInfo(attribute);
return {};
}
/*!
\fn QString QSslCertificate::subjectInfo(SubjectInfo subject) const
@ -333,6 +423,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\sa issuerInfo()
*/
QStringList QSslCertificate::subjectInfo(SubjectInfo info) const
{
if (const auto *backend = d->backend.get())
return backend->subjectInfo(info);
return {};
}
/*!
\fn QStringList QSslCertificate::subjectInfo(const QByteArray &attribute) const
@ -343,6 +440,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\sa issuerInfo()
*/
QStringList QSslCertificate::subjectInfo(const QByteArray &attribute) const
{
if (const auto *backend = d->backend.get())
return backend->subjectInfo(attribute);
return {};
}
/*!
\fn QList<QByteArray> QSslCertificate::subjectInfoAttributes() const
@ -356,6 +460,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\sa subjectInfo()
*/
QList<QByteArray> QSslCertificate::subjectInfoAttributes() const
{
if (const auto *backend = d->backend.get())
return backend->subjectInfoAttributes();
return {};
}
/*!
\fn QList<QByteArray> QSslCertificate::issuerInfoAttributes() const
@ -369,6 +480,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\sa subjectInfo()
*/
QList<QByteArray> QSslCertificate::issuerInfoAttributes() const
{
if (const auto *backend = d->backend.get())
return backend->issuerInfoAttributes();
return {};
}
/*!
\fn QMultiMap<QSsl::AlternativeNameEntryType, QString> QSslCertificate::subjectAlternativeNames() const
@ -385,6 +503,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\sa subjectInfo()
*/
QMultiMap<QSsl::AlternativeNameEntryType, QString> QSslCertificate::subjectAlternativeNames() const
{
if (const auto *backend = d->backend.get())
return backend->subjectAlternativeNames();
return {};
}
/*!
\fn QDateTime QSslCertificate::effectiveDate() const
@ -394,6 +519,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\sa expiryDate()
*/
QDateTime QSslCertificate::effectiveDate() const
{
if (const auto *backend = d->backend.get())
return backend->effectiveDate();
return {};
}
/*!
\fn QDateTime QSslCertificate::expiryDate() const
@ -403,6 +535,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\sa effectiveDate()
*/
QDateTime QSslCertificate::expiryDate() const
{
if (const auto *backend = d->backend.get())
return backend->expiryDate();
return {};
}
/*!
\fn Qt::HANDLE QSslCertificate::handle() const
@ -416,11 +555,29 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
non-portable, and its return value may vary from platform to
platform or change from minor release to minor release.
*/
Qt::HANDLE QSslCertificate::handle() const
{
if (const auto *backend = d->backend.get())
return backend->handle();
return {};
}
#ifndef QT_NO_SSL
/*!
\fn QSslKey QSslCertificate::publicKey() const
Returns the certificate subject's public key.
*/
QSslKey QSslCertificate::publicKey() const
{
QSslKey key;
if (const auto *backend = d->backend.get())
QTlsBackend::resetBackend(key, backend->publicKey());
return key;
}
#endif // QT_NO_SSL
/*!
\fn QList<QSslCertificateExtension> QSslCertificate::extensions() const
@ -428,6 +585,10 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
Returns a list containing the X509 extensions of this certificate.
\since 5.0
*/
QList<QSslCertificateExtension> QSslCertificate::extensions() const
{
return d->extensions();
}
/*!
\fn QByteArray QSslCertificate::toPem() const
@ -435,6 +596,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
Returns this certificate converted to a PEM (Base64) encoded
representation.
*/
QByteArray QSslCertificate::toPem() const
{
if (const auto *backend = d->backend.get())
return backend->toPem();
return {};
}
/*!
\fn QByteArray QSslCertificate::toDer() const
@ -442,6 +610,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
Returns this certificate converted to a DER (binary) encoded
representation.
*/
QByteArray QSslCertificate::toDer() const
{
if (const auto *backend = d->backend.get())
return backend->toDer();
return {};
}
/*!
\fn QString QSslCertificate::toText() const
@ -451,6 +626,13 @@ QByteArray QSslCertificate::digest(QCryptographicHash::Algorithm algorithm) cons
\since 5.0
*/
QString QSslCertificate::toText() const
{
if (const auto *backend = d->backend.get())
return backend->toText();
return {};
}
/*!
\since 5.15
@ -576,13 +758,22 @@ QList<QSslCertificate> QSslCertificate::fromDevice(QIODevice *device, QSsl::Enco
*/
QList<QSslCertificate> QSslCertificate::fromData(const QByteArray &data, QSsl::EncodingFormat format)
{
return (format == QSsl::Pem)
? QSslCertificatePrivate::certificatesFromPem(data)
: QSslCertificatePrivate::certificatesFromDer(data);
const auto *tlsBackend = QTlsBackend::activeOrAnyBackend();
if (!tlsBackend) {
qCWarning(lcSsl, "No TLS backend is available");
return {};
}
auto reader = format == QSsl::Pem ? tlsBackend->X509PemReader() : tlsBackend->X509DerReader();
if (!reader) {
qCWarning(lcSsl, "The available TLS backend does not support reading PEM/DER");
return {};
}
return reader(data, -1);
}
#ifndef QT_NO_SSL
/*!
Verifies a certificate chain. The chain to be verified is passed in the
\a certificateChain parameter. The first certificate in the list should
@ -597,13 +788,19 @@ QList<QSslCertificate> QSslCertificate::fromData(const QByteArray &data, QSsl::E
\since 5.0
*/
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
QList<QSslError> QSslCertificate::verify(const QList<QSslCertificate> &certificateChain, const QString &hostName)
#else
QList<QSslError> QSslCertificate::verify(QList<QSslCertificate> certificateChain, const QString &hostName)
#endif
{
return QSslSocketBackendPrivate::verify(certificateChain, hostName);
const auto *tlsBackend = QTlsBackend::activeOrAnyBackend();
if (!tlsBackend) {
qCWarning(lcSsl, "No TLS backend is available");
return {};
}
auto verifyPtr = tlsBackend->X509Verifier();
if (!verifyPtr) {
qCWarning(lcSsl, "Available TLS backend does not support manual certificate verification");
return {};
}
return verifyPtr(certificateChain, hostName);
}
/*!
@ -623,10 +820,43 @@ bool QSslCertificate::importPkcs12(QIODevice *device,
QList<QSslCertificate> *caCertificates,
const QByteArray &passPhrase)
{
return QSslSocketBackendPrivate::importPkcs12(device, key, certificate, caCertificates, passPhrase);
}
if (!device || !key || !certificate)
return false;
#endif
const auto *tlsBackend = QTlsBackend::activeOrAnyBackend();
if (!tlsBackend) {
qCWarning(lcSsl, "No TLS backend is available");
return false;
}
if (auto reader = tlsBackend->X509Pkcs12Reader())
return reader(device, key, certificate, caCertificates, passPhrase);
qCWarning(lcSsl, "Available TLS backend does not support PKCS12");
return false;
}
#endif // QT_NO_SSL
QList<QSslCertificateExtension> QSslCertificatePrivate::extensions() const
{
QList<QSslCertificateExtension> result;
if (backend.get()) {
auto nExt = backend->numberOfExtensions();
for (decltype (nExt) i = 0; i < nExt; ++i) {
QSslCertificateExtension ext;
ext.d->oid = backend->oidForExtension(i);
ext.d->name = backend->nameForExtension(i);
ext.d->value = backend->valueForExtension(i);
ext.d->critical = backend->isExtensionCritical(i);
ext.d->supported = backend->isExtensionSupported(i);
result << ext;
}
}
return result;
}
// These certificates are known to be fraudulent and were created during the comodo
// compromise. See http://www.comodo.com/Comodo-Fraud-Incident-2011-03-23.html
@ -757,6 +987,16 @@ QString QSslCertificate::subjectDisplayName() const
return QString();
}
/*!
\internal
Returns X509 backend this QSslCertificate is using.
*/
QSsl::X509Certificate *QSslCertificate::backendImplementation() const
{
return d->backend.get();
}
/*!
\fn size_t qHash(const QSslCertificate &key, size_t seed)
@ -764,6 +1004,14 @@ QString QSslCertificate::subjectDisplayName() const
\since 5.4
\relates QHash
*/
size_t qHash(const QSslCertificate &key, size_t seed) noexcept
{
if (const auto *backend = key.d->backend.get())
return backend->hash(seed);
return seed;
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const QSslCertificate &certificate)

View File

@ -148,12 +148,7 @@ public:
const QByteArray &data, QSsl::EncodingFormat format = QSsl::Pem);
#ifndef QT_NO_SSL
#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
static QList<QSslError> verify(const QList<QSslCertificate> &certificateChain, const QString &hostName = QString());
#else
static QList<QSslError> verify(QList<QSslCertificate> certificateChain, const QString &hostName = QString());
#endif
static bool importPkcs12(QIODevice *device,
QSslKey *key, QSslCertificate *cert,
QList<QSslCertificate> *caCertificates = nullptr,
@ -163,10 +158,7 @@ public:
Qt::HANDLE handle() const;
private:
QSsl::X509Certificate *backendImplementation() const
{
return nullptr; // TLSTODO
}
QSsl::X509Certificate *backendImplementation() const;
QExplicitlySharedDataPointer<QSslCertificatePrivate> d;
friend class QSslCertificatePrivate;
friend class QSslSocketBackendPrivate;

View File

@ -1,799 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Copyright (C) 2016 Richard J. Moore <rich@kde.org>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qssl_p.h"
#include "qsslsocket_openssl_symbols_p.h"
#include "qsslcertificate_p.h"
#include "qsslkey_p.h"
#include "qsslcertificateextension_p.h"
#include "qtlsbackend_openssl_p.h"
#include "qtlskey_openssl_p.h"
#include <QtCore/qendian.h>
#include <QtCore/qmutex.h>
QT_BEGIN_NAMESPACE
constexpr int MutexPoolSize = 17;
static QBasicMutex mutexPool[MutexPoolSize];
namespace QMutexPool {
static QBasicMutex *globalInstanceGet(const void *addr)
{
return mutexPool + (quintptr(addr) % MutexPoolSize);
}
}
// forward declaration
static QMultiMap<QByteArray, QString> _q_mapFromX509Name(X509_NAME *name);
bool QSslCertificate::operator==(const QSslCertificate &other) const
{
if (d == other.d)
return true;
if (d->null && other.d->null)
return true;
if (d->x509 && other.d->x509) {
const int ret = q_X509_cmp(d->x509, other.d->x509);
if (ret >= -1 && ret <= 1)
return ret == 0;
QSslSocketBackendPrivate::logAndClearErrorQueue();
}
return false;
}
size_t qHash(const QSslCertificate &key, size_t seed) noexcept
{
if (X509 * const x509 = key.d->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;
}
bool QSslCertificate::isNull() const
{
return d->null;
}
bool QSslCertificate::isSelfSigned() const
{
if (!d->x509)
return false;
return (q_X509_check_issued(d->x509, d->x509) == X509_V_OK);
}
QByteArray QSslCertificate::version() const
{
#if QT_CONFIG(thread)
QMutexLocker lock(QMutexPool::globalInstanceGet(d.data()));
#endif
if (d->versionString.isEmpty() && d->x509)
d->versionString = QByteArray::number(qlonglong(q_X509_get_version(d->x509)) + 1);
return d->versionString;
}
QByteArray QSslCertificate::serialNumber() const
{
#if QT_CONFIG(thread)
QMutexLocker lock(QMutexPool::globalInstanceGet(d.data()));
#endif
if (d->serialNumberString.isEmpty() && d->x509) {
ASN1_INTEGER *serialNumber = q_X509_get_serialNumber(d->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);
d->serialNumberString = hexString;
}
return d->serialNumberString;
}
QStringList QSslCertificate::issuerInfo(SubjectInfo info) const
{
#if QT_CONFIG(thread)
QMutexLocker lock(QMutexPool::globalInstanceGet(d.data()));
#endif
// lazy init
if (d->issuerInfo.isEmpty() && d->x509)
d->issuerInfo =
_q_mapFromX509Name(q_X509_get_issuer_name(d->x509));
return d->issuerInfo.values(d->subjectInfoToString(info));
}
QStringList QSslCertificate::issuerInfo(const QByteArray &attribute) const
{
#if QT_CONFIG(thread)
QMutexLocker lock(QMutexPool::globalInstanceGet(d.data()));
#endif
// lazy init
if (d->issuerInfo.isEmpty() && d->x509)
d->issuerInfo =
_q_mapFromX509Name(q_X509_get_issuer_name(d->x509));
return d->issuerInfo.values(attribute);
}
QStringList QSslCertificate::subjectInfo(SubjectInfo info) const
{
#if QT_CONFIG(thread)
QMutexLocker lock(QMutexPool::globalInstanceGet(d.data()));
#endif
// lazy init
if (d->subjectInfo.isEmpty() && d->x509)
d->subjectInfo =
_q_mapFromX509Name(q_X509_get_subject_name(d->x509));
return d->subjectInfo.values(d->subjectInfoToString(info));
}
QStringList QSslCertificate::subjectInfo(const QByteArray &attribute) const
{
#if QT_CONFIG(thread)
QMutexLocker lock(QMutexPool::globalInstanceGet(d.data()));
#endif
// lazy init
if (d->subjectInfo.isEmpty() && d->x509)
d->subjectInfo =
_q_mapFromX509Name(q_X509_get_subject_name(d->x509));
return d->subjectInfo.values(attribute);
}
QList<QByteArray> QSslCertificate::subjectInfoAttributes() const
{
#if QT_CONFIG(thread)
QMutexLocker lock(QMutexPool::globalInstanceGet(d.data()));
#endif
// lazy init
if (d->subjectInfo.isEmpty() && d->x509)
d->subjectInfo =
_q_mapFromX509Name(q_X509_get_subject_name(d->x509));
return d->subjectInfo.uniqueKeys();
}
QList<QByteArray> QSslCertificate::issuerInfoAttributes() const
{
#if QT_CONFIG(thread)
QMutexLocker lock(QMutexPool::globalInstanceGet(d.data()));
#endif
// lazy init
if (d->issuerInfo.isEmpty() && d->x509)
d->issuerInfo =
_q_mapFromX509Name(q_X509_get_issuer_name(d->x509));
return d->issuerInfo.uniqueKeys();
}
QMultiMap<QSsl::AlternativeNameEntryType, QString> QSslCertificate::subjectAlternativeNames() const
{
QMultiMap<QSsl::AlternativeNameEntryType, QString> result;
if (!d->x509)
return result;
STACK_OF(GENERAL_NAME) *altNames = (STACK_OF(GENERAL_NAME) *)q_X509_get_ext_d2i(
d->x509, NID_subject_alt_name, nullptr, nullptr);
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);
};
if (altNames) {
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;
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;
}
QDateTime QSslCertificate::effectiveDate() const
{
return d->notValidBefore;
}
QDateTime QSslCertificate::expiryDate() const
{
return d->notValidAfter;
}
Qt::HANDLE QSslCertificate::handle() const
{
return Qt::HANDLE(d->x509);
}
QSslKey QSslCertificate::publicKey() const
{
if (!d->x509)
return QSslKey();
QSslKey key;
auto *tlsKey = QTlsBackend::backend<QSsl::TlsKeyOpenSSL>(key);
tlsKey->keyType = QSsl::PublicKey;
EVP_PKEY *pkey = q_X509_get_pubkey(d->x509);
Q_ASSERT(pkey);
const int keyType = q_EVP_PKEY_type(q_EVP_PKEY_base_id(pkey));
if (keyType == EVP_PKEY_RSA) {
tlsKey->rsa = q_EVP_PKEY_get1_RSA(pkey);
tlsKey->keyAlgorithm = QSsl::Rsa;
tlsKey->keyIsNull = false;
} else if (keyType == EVP_PKEY_DSA) {
tlsKey->dsa = q_EVP_PKEY_get1_DSA(pkey);
tlsKey->keyAlgorithm = QSsl::Dsa;
tlsKey->keyIsNull = false;
#ifndef OPENSSL_NO_EC
} else if (keyType == EVP_PKEY_EC) {
tlsKey->ec = q_EVP_PKEY_get1_EC_KEY(pkey);
tlsKey->keyAlgorithm = QSsl::Ec;
tlsKey->keyIsNull = false;
#endif
} else if (keyType == EVP_PKEY_DH) {
// DH unsupported
} else {
// error?
}
q_EVP_PKEY_free(pkey);
return key;
}
/*
* Convert unknown extensions to a QVariant.
*/
static 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;
}
//const unsigned char *data = ext->value->data;
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.
*/
static 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 QVariant();
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 QVariant();
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(QSslCertificatePrivate::asn1ObjectName(ad->method))] = uri;
} else {
qCWarning(lcSsl) << "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 QVariant();
// 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 QVariant();
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 QVariant();
}
QSslCertificateExtension QSslCertificatePrivate::convertExtension(X509_EXTENSION *ext)
{
Q_ASSERT(ext);
QSslCertificateExtension result;
ASN1_OBJECT *obj = q_X509_EXTENSION_get_object(ext);
if (!obj) {
qCWarning(lcSsl, "Invalid (nullptr) ASN1_OBJECT");
return result;
}
QByteArray oid = QSslCertificatePrivate::asn1ObjectId(obj);
QByteArray name = QSslCertificatePrivate::asn1ObjectName(obj);
result.d->oid = QString::fromUtf8(oid);
result.d->name = QString::fromUtf8(name);
bool critical = q_X509_EXTENSION_get_critical(ext);
result.d->critical = critical;
// Lets see if we have custom support for this one
QVariant extensionValue = x509ExtensionToValue(ext);
if (extensionValue.isValid()) {
result.d->value = extensionValue;
result.d->supported = true;
return result;
}
extensionValue = x509UnknownExtensionToValue(ext);
if (extensionValue.isValid()) {
result.d->value = extensionValue;
result.d->supported = false;
return result;
}
return result;
}
QList<QSslCertificateExtension> QSslCertificate::extensions() const
{
QList<QSslCertificateExtension> result;
if (!d->x509)
return result;
int count = q_X509_get_ext_count(d->x509);
if (count <= 0)
return result;
result.reserve(count);
for (int i = 0; i < count; i++) {
X509_EXTENSION *ext = q_X509_get_ext(d->x509, i);
if (!ext) {
qCWarning(lcSsl) << "Invalid (nullptr) extension at index" << i;
continue;
}
result << QSslCertificatePrivate::convertExtension(ext);
}
// Converting an extension may result in an error(s), clean them up.
Q_UNUSED(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
return result;
}
QByteArray QSslCertificate::toPem() const
{
if (!d->x509)
return QByteArray();
return d->QByteArray_from_X509(d->x509, QSsl::Pem);
}
QByteArray QSslCertificate::toDer() const
{
if (!d->x509)
return QByteArray();
return d->QByteArray_from_X509(d->x509, QSsl::Der);
}
QString QSslCertificate::toText() const
{
if (!d->x509)
return QString();
return d->text_from_X509(d->x509);
}
#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----"
#define ENDCERTSTRING "-----END CERTIFICATE-----"
void QSslCertificatePrivate::init(const QByteArray &data, QSsl::EncodingFormat format)
{
if (!data.isEmpty()) {
const QList<QSslCertificate> certs = (format == QSsl::Pem)
? certificatesFromPem(data, 1)
: certificatesFromDer(data, 1);
if (!certs.isEmpty()) {
*this = *certs.first().d;
if (x509)
x509 = q_X509_dup(x509);
}
}
}
// ### refactor against QSsl::pemFromDer() etc. (to avoid redundant implementations)
QByteArray QSslCertificatePrivate::QByteArray_from_X509(X509 *x509, QSsl::EncodingFormat format)
{
if (!x509) {
qCWarning(lcSsl, "QSslSocketBackendPrivate::X509_to_QByteArray: null X509");
return QByteArray();
}
// Use i2d_X509 to convert the X509 to an array.
int length = q_i2d_X509(x509, nullptr);
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 QSslCertificatePrivate::text_from_X509(X509 *x509)
{
if (!x509) {
qCWarning(lcSsl, "QSslSocketBackendPrivate::text_from_X509: null X509");
return QString();
}
QByteArray result;
BIO *bio = q_BIO_new(q_BIO_s_mem());
if (!bio)
return QString();
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 );
}
q_BIO_free(bio);
return QString::fromLatin1(result);
}
QByteArray QSslCertificatePrivate::asn1ObjectId(ASN1_OBJECT *object)
{
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 QSslCertificatePrivate::asn1ObjectName(ASN1_OBJECT *object)
{
int nid = q_OBJ_obj2nid(object);
if (nid != NID_undef)
return QByteArray(q_OBJ_nid2sn(nid));
return asn1ObjectId(object);
}
static QMultiMap<QByteArray, QString> _q_mapFromX509Name(X509_NAME *name)
{
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 = QSslCertificatePrivate::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));
#if QT_CONFIG(opensslv11)
q_CRYPTO_free(data, nullptr, 0);
#else
q_CRYPTO_free(data);
#endif
}
return info;
}
QSslCertificate QSslCertificatePrivate::QSslCertificate_from_X509(X509 *x509)
{
QSslCertificate certificate;
if (!x509 || !QSslSocket::supportsSsl())
return certificate;
ASN1_TIME *nbef = q_X509_getm_notBefore(x509);
ASN1_TIME *naft = q_X509_getm_notAfter(x509);
certificate.d->notValidBefore = q_getTimeFromASN1(nbef);
certificate.d->notValidAfter = q_getTimeFromASN1(naft);
certificate.d->null = false;
certificate.d->x509 = q_X509_dup(x509);
return certificate;
}
static bool matchLineFeed(const QByteArray &pem, int *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;
}
QList<QSslCertificate> QSslCertificatePrivate::certificatesFromPem(const QByteArray &pem, int count)
{
QList<QSslCertificate> certificates;
QSslSocketPrivate::ensureInitialized();
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 << QSslCertificate_from_X509(x509);
q_X509_free(x509);
}
}
return certificates;
}
QList<QSslCertificate> QSslCertificatePrivate::certificatesFromDer(const QByteArray &der, int count)
{
QList<QSslCertificate> certificates;
QSslSocketPrivate::ensureInitialized();
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 << QSslCertificate_from_X509(x509);
q_X509_free(x509);
} else {
break;
}
size -= ((const char *)data - der.data());
}
return certificates;
}
QT_END_NAMESPACE

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** 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.
@ -38,11 +38,8 @@
****************************************************************************/
#ifndef QSSLCERTIFICATE_OPENSSL_P_H
#define QSSLCERTIFICATE_OPENSSL_P_H
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qsslcertificate.h"
#ifndef QSSLCERTIFICATE_P_H
#define QSSLCERTIFICATE_P_H
//
// W A R N I N G
@ -55,99 +52,34 @@
// We mean it.
//
#ifndef QT_NO_SSL
#include "qsslsocket_p.h"
#endif
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "qsslcertificateextension.h"
#include <QtCore/qdatetime.h>
#include <QtCore/qmap.h>
#include "qsslcertificate.h"
#include "qtlsbackend_p.h"
#ifndef QT_NO_OPENSSL
#include <openssl/x509.h>
#else
struct X509;
struct X509_EXTENSION;
struct ASN1_OBJECT;
#endif
#include <qlist.h>
#if QT_CONFIG(schannel)
#include <wincrypt.h>
#endif
#include <memory>
QT_BEGIN_NAMESPACE
// forward declaration
class QSslCertificatePrivate
{
public:
QSslCertificatePrivate()
: null(true), x509(nullptr)
{
#ifndef QT_NO_SSL
QSslSocketPrivate::ensureInitialized();
#endif
}
QSslCertificatePrivate();
~QSslCertificatePrivate();
~QSslCertificatePrivate()
{
#ifndef QT_NO_OPENSSL
if (x509)
q_X509_free(x509);
#endif
#if QT_CONFIG(schannel)
if (certificateContext)
CertFreeCertificateContext(certificateContext);
#endif
}
bool null;
QByteArray versionString;
QByteArray serialNumberString;
QMultiMap<QByteArray, QString> issuerInfo;
QMultiMap<QByteArray, QString> subjectInfo;
QDateTime notValidAfter;
QDateTime notValidBefore;
#ifdef QT_NO_OPENSSL
bool subjectMatchesIssuer;
QSsl::KeyAlgorithm publicKeyAlgorithm;
QByteArray publicKeyDerData;
QMultiMap<QSsl::AlternativeNameEntryType, QString> subjectAlternativeNames;
QList<QSslCertificateExtension> extensions;
QByteArray derData;
bool parse(const QByteArray &data);
bool parseExtension(const QByteArray &data, QSslCertificateExtension *extension);
#endif
X509 *x509;
void init(const QByteArray &data, QSsl::EncodingFormat format);
static QByteArray asn1ObjectId(ASN1_OBJECT *object);
static QByteArray asn1ObjectName(ASN1_OBJECT *object);
static QByteArray QByteArray_from_X509(X509 *x509, QSsl::EncodingFormat format);
static QString text_from_X509(X509 *x509);
static QSslCertificate QSslCertificate_from_X509(X509 *x509);
static QList<QSslCertificate> certificatesFromPem(const QByteArray &pem, int count = -1);
static QList<QSslCertificate> certificatesFromDer(const QByteArray &der, int count = -1);
QList<QSslCertificateExtension> extensions() const;
static bool isBlacklisted(const QSslCertificate &certificate);
static QSslCertificateExtension convertExtension(X509_EXTENSION *ext);
static QByteArray subjectInfoToString(QSslCertificate::SubjectInfo info);
friend class QSslSocketBackendPrivate;
QAtomicInt ref;
#if QT_CONFIG(schannel)
const CERT_CONTEXT *certificateContext = nullptr;
static QSslCertificate QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext);
#endif
std::unique_ptr<QSsl::X509Certificate> backend;
};
QT_END_NAMESPACE
#endif // QSSLCERTIFICATE_OPENSSL_P_H
#endif // QSSLCERTIFICATE_P_H

View File

@ -1,580 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qsslcertificate.h"
#include "qsslcertificate_p.h"
#include "qssl_p.h"
#ifndef QT_NO_SSL
#include "qsslkey.h"
#include "qsslkey_p.h"
#include "qtlskey_generic_p.h"
#include "qtlsbackend_p.h"
#endif
#include "qsslcertificateextension.h"
#include "qsslcertificateextension_p.h"
#include "qasn1element_p.h"
#include <QtCore/qdatastream.h>
#include <QtCore/qendian.h>
#include <QtNetwork/qhostaddress.h>
QT_BEGIN_NAMESPACE
bool QSslCertificate::operator==(const QSslCertificate &other) const
{
if (d == other.d)
return true;
if (d->null && other.d->null)
return true;
return d->derData == other.d->derData;
}
size_t qHash(const QSslCertificate &key, size_t seed) noexcept
{
// DER is the native encoding here, so toDer() is just "return d->derData":
return qHash(key.toDer(), seed);
}
bool QSslCertificate::isNull() const
{
return d->null;
}
bool QSslCertificate::isSelfSigned() const
{
if (d->null)
return false;
qCWarning(lcSsl,
"QSslCertificate::isSelfSigned: This function does not check, whether the certificate "
"is actually signed. It just checks whether issuer and subject are identical");
return d->subjectMatchesIssuer;
}
QByteArray QSslCertificate::version() const
{
return d->versionString;
}
QByteArray QSslCertificate::serialNumber() const
{
return d->serialNumberString;
}
QStringList QSslCertificate::issuerInfo(SubjectInfo info) const
{
return issuerInfo(QSslCertificatePrivate::subjectInfoToString(info));
}
QStringList QSslCertificate::issuerInfo(const QByteArray &attribute) const
{
return d->issuerInfo.values(attribute);
}
QStringList QSslCertificate::subjectInfo(SubjectInfo info) const
{
return subjectInfo(QSslCertificatePrivate::subjectInfoToString(info));
}
QStringList QSslCertificate::subjectInfo(const QByteArray &attribute) const
{
return d->subjectInfo.values(attribute);
}
QList<QByteArray> QSslCertificate::subjectInfoAttributes() const
{
return d->subjectInfo.uniqueKeys();
}
QList<QByteArray> QSslCertificate::issuerInfoAttributes() const
{
return d->issuerInfo.uniqueKeys();
}
QMultiMap<QSsl::AlternativeNameEntryType, QString> QSslCertificate::subjectAlternativeNames() const
{
return d->subjectAlternativeNames;
}
QDateTime QSslCertificate::effectiveDate() const
{
return d->notValidBefore;
}
QDateTime QSslCertificate::expiryDate() const
{
return d->notValidAfter;
}
#if !QT_CONFIG(schannel) // implemented in qsslcertificate_schannel.cpp
Qt::HANDLE QSslCertificate::handle() const
{
Q_UNIMPLEMENTED();
return nullptr;
}
#endif
#ifndef QT_NO_SSL
QSslKey QSslCertificate::publicKey() const
{
QSslKey key;
auto *tlsKey = QTlsBackend::backend<QSsl::TlsKeyGeneric>(key);
if (!tlsKey)
return key;
tlsKey->keyType = QSsl::PublicKey;
if (d->publicKeyAlgorithm != QSsl::Opaque)
tlsKey->decodeDer(QSsl::PublicKey, d->publicKeyAlgorithm, d->publicKeyDerData, {}, false);
return key;
}
#endif
QList<QSslCertificateExtension> QSslCertificate::extensions() const
{
return d->extensions;
}
#define BEGINCERTSTRING "-----BEGIN CERTIFICATE-----"
#define ENDCERTSTRING "-----END CERTIFICATE-----"
QByteArray QSslCertificate::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 QSslCertificate::toDer() const
{
return d->derData;
}
QString QSslCertificate::toText() const
{
Q_UNIMPLEMENTED();
return QString();
}
void QSslCertificatePrivate::init(const QByteArray &data, QSsl::EncodingFormat format)
{
if (!data.isEmpty()) {
const QList<QSslCertificate> certs = (format == QSsl::Pem)
? certificatesFromPem(data, 1)
: certificatesFromDer(data, 1);
if (!certs.isEmpty()) {
*this = *certs.first().d;
#if QT_CONFIG(schannel)
if (certificateContext)
certificateContext = CertDuplicateCertificateContext(certificateContext);
#endif
}
}
}
static bool matchLineFeed(const QByteArray &pem, int *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;
}
QList<QSslCertificate> QSslCertificatePrivate::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> QSslCertificatePrivate::certificatesFromDer(const QByteArray &der, int count)
{
QList<QSslCertificate> certificates;
QByteArray data = der;
while (count == -1 || certificates.size() < count) {
QSslCertificate cert;
if (!cert.d->parse(data))
break;
certificates << cert;
data.remove(0, cert.d->derData.size());
}
return certificates;
}
static 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(':');
}
bool QSslCertificatePrivate::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());
issuerInfo = 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());
subjectInfo = 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) {
QSslCertificateExtension 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:
subjectAlternativeNames.insert(QSsl::EmailEntry, nameElem.toString());
extValue[QStringLiteral("email")] = nameElem.toString();
break;
case QAsn1Element::DnsNameType:
subjectAlternativeNames.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()) {
subjectAlternativeNames.insert(QSsl::IpAddressEntry, ipAddress.toString());
extValue[QStringLiteral("IP")] = ipAddress.toString();
}
break;
}
default:
break;
}
}
extension.d->value = extValue;
extension.d->supported = true;
}
}
extensions << extension;
}
}
}
}
derData = data.left(dataStream.device()->pos());
null = false;
return true;
}
bool QSslCertificatePrivate::parseExtension(const QByteArray &data, QSslCertificateExtension *extension)
{
bool ok;
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->d->critical = critical;
extension->d->supported = supported;
extension->d->oid = QString::fromLatin1(oid);
extension->d->name = QString::fromLatin1(oidElem.toObjectName());
extension->d->value = value;
return true;
}
QT_END_NAMESPACE

View File

@ -1382,7 +1382,7 @@ bool QSslSocketBackendPrivate::verifyPeerTrust()
// verify certificate chain
QCFType<CFMutableArrayRef> certArray = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
for (const QSslCertificate &cert : qAsConst(configuration.caCertificates)) {
QCFType<CFDataRef> certData = cert.d->derData.toCFData();
QCFType<CFDataRef> certData = cert.toDer().toCFData();
if (QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(nullptr, certData))
CFArrayAppendValue(certArray, secRef);
else

View File

@ -43,6 +43,7 @@
#include "qssl_p.h"
#include "qsslsocket.h"
#include "qsslsocket_p.h"
#ifndef QT_NO_OPENSSL
# include "qsslsocket_openssl_p.h"

View File

@ -65,6 +65,7 @@
#include "qsslellipticcurve.h"
#include "qsslpresharedkeyauthenticator.h"
#include "qsslpresharedkeyauthenticator_p.h"
#include "qtlsbackend_openssl_p.h"
#include "qocspresponse_p.h"
#include "qsslkey.h"
#include "qtlsbackend_openssl_p.h"
@ -463,14 +464,6 @@ QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(const SSL_CIPHER
return ciph;
}
QSslErrorEntry QSslErrorEntry::fromStoreContext(X509_STORE_CTX *ctx)
{
return {
q_X509_STORE_CTX_get_error(ctx),
q_X509_STORE_CTX_get_error_depth(ctx)
};
}
#if QT_CONFIG(ocsp)
QSslError::SslError qt_OCSP_response_status_to_SslError(long code)
@ -612,7 +605,7 @@ int q_X509Callback(int ok, X509_STORE_CTX *ctx)
return 0;
}
errors->append(QSslErrorEntry::fromStoreContext(ctx));
errors->append(QSsl::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx));
}
// Always return OK to allow verification to continue. We handle the
// errors gracefully after collecting all errors, after verification has
@ -1334,55 +1327,6 @@ void QSslSocketBackendPrivate::transmit()
} while (ssl && transmitting);
}
QSslError _q_OpenSSL_to_QSslError(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;
}
QString QSslSocketBackendPrivate::msgErrorsDuringHandshake()
{
return QSslSocket::tr("Error during SSL handshake: %1")
@ -1422,7 +1366,7 @@ bool QSslSocketBackendPrivate::startHandshake()
if (!errorsReportedFromCallback) {
for (const auto &currentError : qAsConst(lastErrors)) {
emit q->peerVerifyError(_q_OpenSSL_to_QSslError(currentError.code,
emit q->peerVerifyError(QSsl::X509CertificateOpenSSL::openSSLErrorToQSslError(currentError.code,
configuration.peerCertificateChain.value(currentError.depth)));
if (q->state() != QAbstractSocket::ConnectedState)
break;
@ -1538,7 +1482,7 @@ bool QSslSocketBackendPrivate::startHandshake()
// Translate errors from the error list into QSslErrors.
errors.reserve(errors.size() + errorList.size());
for (const auto &error : qAsConst(errorList))
errors << _q_OpenSSL_to_QSslError(error.code, configuration.peerCertificateChain.value(error.depth));
errors << QSsl::X509CertificateOpenSSL::openSSLErrorToQSslError(error.code, configuration.peerCertificateChain.value(error.depth));
if (!errors.isEmpty()) {
sslErrors = errors;
@ -1589,10 +1533,10 @@ void QSslSocketBackendPrivate::storePeerCertificates()
// peer certificate and the chain may be empty if the peer didn't present
// any certificate.
X509 *x509 = q_SSL_get_peer_certificate(ssl);
configuration.peerCertificate = QSslCertificatePrivate::QSslCertificate_from_X509(x509);
configuration.peerCertificate = QSsl::X509CertificateOpenSSL::certificateFromX509(x509);
q_X509_free(x509);
if (configuration.peerCertificateChain.isEmpty()) {
configuration.peerCertificateChain = STACKOFX509_to_QSslCertificates(q_SSL_get_peer_cert_chain(ssl));
configuration.peerCertificateChain = QSsl::X509CertificateOpenSSL::stackOfX509ToQSslCertificates(q_SSL_get_peer_cert_chain(ssl));
if (!configuration.peerCertificate.isNull() && mode == QSslSocket::SslServerMode)
configuration.peerCertificateChain.prepend(configuration.peerCertificate);
}
@ -1932,7 +1876,7 @@ bool QSslSocketBackendPrivate::checkOcspStatus()
matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, issuer);
if (matchFound) {
if (q_X509_check_issued(issuer, peerX509) == X509_V_OK) {
dResponse->signerCert = QSslCertificatePrivate::QSslCertificate_from_X509(issuer);
dResponse->signerCert = QSsl::X509CertificateOpenSSL::certificateFromX509(issuer);
break;
}
matchFound = false;
@ -2033,10 +1977,10 @@ int QSslSocketBackendPrivate::emitErrorFromCallback(X509_STORE_CTX *ctx)
qCWarning(lcSsl, "Could not obtain the certificate (that failed to verify)");
return 0;
}
const QSslCertificate certificate = QSslCertificatePrivate::QSslCertificate_from_X509(x509);
const auto errorAndDepth = QSslErrorEntry::fromStoreContext(ctx);
const QSslError tlsError = _q_OpenSSL_to_QSslError(errorAndDepth.code, certificate);
const QSslCertificate certificate = QSsl::X509CertificateOpenSSL::certificateFromX509(x509);
const auto errorAndDepth = QSsl::X509CertificateOpenSSL::errorEntryFromStoreContext(ctx);
const QSslError tlsError = QSsl::X509CertificateOpenSSL::openSSLErrorToQSslError(errorAndDepth.code, certificate);
errorsReportedFromCallback = true;
handshakeInterrupted = true;
@ -2310,17 +2254,6 @@ void QSslSocketPrivate::ensureCiphersAndCertsLoaded()
#endif
}
QList<QSslCertificate> QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates(STACK_OF(X509) *x509)
{
ensureInitialized();
QList<QSslCertificate> certificates;
for (int i = 0; i < q_sk_X509_num(x509); ++i) {
if (X509 *entry = q_sk_X509_value(x509, i))
certificates << QSslCertificatePrivate::QSslCertificate_from_X509(entry);
}
return certificates;
}
QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &certificateChain,
const QString &hostName)
{
@ -2334,79 +2267,6 @@ QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &
return QSsl::X509CertificateOpenSSL::verify(caCertificates, certificateChain, hostName);
}
bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device,
QSslKey *key, QSslCertificate *cert,
QList<QSslCertificate> *caCertificates,
const QByteArray &passPhrase)
{
if (!supportsSsl())
return false;
// 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());
// Create the PKCS#12 object
PKCS12 *p12 = q_d2i_PKCS12_bio(bio, nullptr);
if (!p12) {
qCWarning(lcSsl, "Unable to read PKCS#12 structure, %s",
q_ERR_error_string(q_ERR_get_error(), nullptr));
q_BIO_free(bio);
return false;
}
// Extract the data
EVP_PKEY *pkey = nullptr;
X509 *x509;
STACK_OF(X509) *ca = nullptr;
if (!q_PKCS12_parse(p12, passPhrase.constData(), &pkey, &x509, &ca)) {
qCWarning(lcSsl, "Unable to parse PKCS#12 structure, %s",
q_ERR_error_string(q_ERR_get_error(), nullptr));
q_PKCS12_free(p12);
q_BIO_free(bio);
return false;
}
// Convert to Qt types
auto *tlsKey = QTlsBackend::backend<QSsl::TlsKeyOpenSSL>(*key);
if (!tlsKey || !tlsKey->fromEVP_PKEY(pkey)) {
qCWarning(lcSsl, "Unable to convert private key");
q_OPENSSL_sk_pop_free(reinterpret_cast<OPENSSL_STACK *>(ca),
reinterpret_cast<void (*)(void *)>(q_X509_free));
q_X509_free(x509);
q_EVP_PKEY_free(pkey);
q_PKCS12_free(p12);
q_BIO_free(bio);
return false;
}
*cert = QSslCertificatePrivate::QSslCertificate_from_X509(x509);
if (caCertificates)
*caCertificates = QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates(ca);
// Clean up
q_OPENSSL_sk_pop_free(reinterpret_cast<OPENSSL_STACK *>(ca),
reinterpret_cast<void (*)(void *)>(q_X509_free));
q_X509_free(x509);
q_EVP_PKEY_free(pkey);
q_PKCS12_free(p12);
q_BIO_free(bio);
return true;
}
void QSslSocketPrivate::registerAdHocFactory()
{
// TLSTODO: this is a temporary solution, waiting for

View File

@ -110,9 +110,8 @@ QT_BEGIN_NAMESPACE
struct QSslErrorEntry {
int code;
int depth;
static QSslErrorEntry fromStoreContext(X509_STORE_CTX *ctx);
};
Q_DECLARE_TYPEINFO(QSslErrorEntry, Q_PRIMITIVE_TYPE);
class QSslSocketBackendPrivate : public QSslSocketPrivate
@ -179,16 +178,11 @@ public:
Q_AUTOTEST_EXPORT static long setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions);
static QSslCipher QSslCipher_from_SSL_CIPHER(const SSL_CIPHER *cipher);
static QList<QSslCertificate> STACKOFX509_to_QSslCertificates(STACK_OF(X509) *x509);
static QList<QSslError> verify(const QList<QSslCertificate> &certificateChain, const QString &hostName);
static QList<QSslError> verify(const QList<QSslCertificate> &cas, const QList<QSslCertificate> &certificateChain,
const QString &hostName);
static QString getErrorsFromOpenSsl();
static void logAndClearErrorQueue();
static bool importPkcs12(QIODevice *device,
QSslKey *key, QSslCertificate *cert,
QList<QSslCertificate> *caCertificates,
const QByteArray &passPhrase);
static QString msgErrorsDuringHandshake();
};

View File

@ -630,7 +630,7 @@ QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
PCCERT_CONTEXT pc = nullptr;
while ((pc = CertFindCertificateInStore(hSystemStore.get(), X509_ASN_ENCODING, 0,
CERT_FIND_ANY, nullptr, pc))) {
systemCerts.append(QSslCertificatePrivate::QSslCertificate_from_CERT_CONTEXT(pc));
systemCerts.append(QSsl::X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(pc));
}
}
return systemCerts;
@ -1991,7 +1991,7 @@ bool QSslSocketBackendPrivate::verifyCertContext(CERT_CONTEXT *certContext)
return QSslCertificate();
const CERT_CONTEXT *certContext = element->pCertContext;
return QSslCertificatePrivate::QSslCertificate_from_CERT_CONTEXT(certContext);
return QSsl::X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(certContext);
};
// Pick a chain to use as the certificate chain, if multiple are available:

View File

@ -38,7 +38,15 @@
****************************************************************************/
#include "qtlsbackend_p.h"
#if QT_CONFIG(ssl)
#include "qsslsocket_p.h"
#include "qsslkey_p.h"
#include "qsslkey.h"
#else
#include "qtlsbackend_cert_p.h"
#endif
#include "qssl_p.h"
#include <QtCore/private/qfactoryloader_p.h>
@ -93,8 +101,13 @@ public:
while (loader->instance(index))
++index;
// TLSTODO: obviously, this one should go away:
// TLSTODO: obviously, these two below should
// disappear as soon as plugins are in place.
#if QT_CONFIG(ssl)
QSslSocketPrivate::registerAdHocFactory();
#else
static QTlsBackendCertOnly certGenerator;
#endif // QT_CONFIG(ssl)
return loaded = true;
}
@ -180,6 +193,12 @@ QByteArray TlsKey::pemFooter() const
X509Certificate::~X509Certificate() = default;
TlsKey *X509Certificate::publicKey() const
{
// 'no-ssl' build has no key support either.
return nullptr;
}
} // namespace QSsl
const QString QTlsBackend::builtinBackendNames[] = {
@ -286,6 +305,9 @@ QString QTlsBackend::defaultBackendName()
if (names.contains(name))
return name;
if (names.size())
return names[0];
return {};
}
@ -301,6 +323,15 @@ QTlsBackend *QTlsBackend::findBackend(const QString &backendName)
return nullptr;
}
QTlsBackend *QTlsBackend::activeOrAnyBackend()
{
#if QT_CONFIG(ssl)
return QSslSocketPrivate::tlsBackendInUse();
#else
return findBackend(defaultBackendName());
#endif // QT_CONFIG(ssl)
}
QList<QSsl::SslProtocol> QTlsBackend::supportedProtocols(const QString &backendName)
{
if (!backends())
@ -334,4 +365,14 @@ QList<QSsl::ImplementedClass> QTlsBackend::implementedClasses(const QString &bac
return {};
}
void QTlsBackend::resetBackend(QSslKey &key, QSsl::TlsKey *keyBackend)
{
#if QT_CONFIG(ssl)
key.d->keyBackend.reset(keyBackend);
#else
Q_UNUSED(key);
Q_UNUSED(keyBackend);
#endif // QT_CONFIG(ssl)
}
QT_END_NAMESPACE

View File

@ -0,0 +1,96 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtlsbackend_cert_p.h"
#ifdef QT_NO_SSL
#include "qx509_generic_p.h"
#include <qssl.h>
#include <qlist.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.cert-only");
QString QTlsBackendCertOnly::backendName() const
{
return QStringLiteral("cert-only");
}
QList<QSsl::SslProtocol> QTlsBackendCertOnly::supportedProtocols() const
{
return {};
}
QList<QSsl::SupportedFeature> QTlsBackendCertOnly::supportedFeatures() const
{
return {};
}
QList<QSsl::ImplementedClass> QTlsBackendCertOnly::implementedClasses() const
{
QList<QSsl::ImplementedClass> classes;
classes << QSsl::ImplementedClass::Certificate;
return classes;
}
QSsl::X509Certificate *QTlsBackendCertOnly::createCertificate() const
{
return new QSsl::X509CertificateGeneric;
}
QSsl::X509PemReaderPtr QTlsBackendCertOnly::X509PemReader() const
{
return QSsl::X509CertificateGeneric::certificatesFromPem;
}
QSsl::X509DerReaderPtr QTlsBackendCertOnly::X509DerReader() const
{
return QSsl::X509CertificateGeneric::certificatesFromDer;
}
QT_END_NAMESPACE
#endif // QT_NO_SSL

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** 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.
@ -37,26 +37,44 @@
**
****************************************************************************/
#include "qsslcertificate.h"
#include "qsslcertificate_p.h"
#ifndef QTLSBACKEND_CERT_P_H
#define QTLSBACKEND_CERT_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <private/qtnetworkglobal_p.h>
#include "qtlsbackend_p.h"
#include <QtCore/qglobal.h>
#include <wincrypt.h>
QT_BEGIN_NAMESPACE
QSslCertificate QSslCertificatePrivate::QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext)
class QTlsBackendCertOnly final : public QTlsBackend
{
QByteArray derData = QByteArray((const char *)certificateContext->pbCertEncoded,
certificateContext->cbCertEncoded);
public:
private:
QString backendName() const override;
QSslCertificate certificate(derData, QSsl::Der);
certificate.d->certificateContext = CertDuplicateCertificateContext(certificateContext);
return certificate;
}
QList<QSsl::SslProtocol> supportedProtocols() const override;
QList<QSsl::SupportedFeature> supportedFeatures() const override;
QList<QSsl::ImplementedClass> implementedClasses() const override;
Qt::HANDLE QSslCertificate::handle() const
{
return Qt::HANDLE(d->certificateContext);
}
QSsl::X509Certificate *createCertificate() const override;
QSsl::X509PemReaderPtr X509PemReader() const override;
QSsl::X509DerReaderPtr X509DerReader() const override;
};
QT_END_NAMESPACE
#endif // QTLSBACKEND_CERT_P_H

View File

@ -51,10 +51,9 @@
// We mean it.
//
#include <private/qtnetworkglobal_p.h>
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <private/qsslkey_p.h>
#include <private/qssl_p.h>
#include "qssl_p.h"
#include <QtNetwork/qsslcertificate.h>
#include <QtNetwork/qsslerror.h>
@ -76,6 +75,7 @@ QT_BEGIN_NAMESPACE
class QByteArray;
class QIODevice;
class QSslKey;
namespace QSsl {
@ -147,7 +147,8 @@ public:
virtual QMultiMap<QSsl::AlternativeNameEntryType, QString> subjectAlternativeNames() const = 0;
virtual QDateTime effectiveDate() const = 0;
virtual QDateTime expiryDate() const = 0;
virtual TlsKey *publicKey() const = 0;
virtual TlsKey *publicKey() const;
// Extensions. Plugins do not expose internal representation
// and cannot rely on QSslCertificate's internals.
@ -220,6 +221,7 @@ public:
static QList<QString> availableBackendNames();
static QString defaultBackendName();
static QTlsBackend *findBackend(const QString &backendName);
static QTlsBackend *activeOrAnyBackend();
static QList<QSsl::SslProtocol> supportedProtocols(const QString &backendName);
static QList<QSsl::SupportedFeature> supportedFeatures(const QString &backendName);
@ -238,6 +240,8 @@ public:
return static_cast<DynamicType *>(o.backendImplementation());
}
static void resetBackend(QSslKey &key, QSsl::TlsKey *keyBackend);
Q_DISABLE_COPY_MOVE(QTlsBackend)
};

View File

@ -101,28 +101,6 @@ public:
return pkcs8;
}
QByteArray decrypt(Cipher cipher, const QByteArray &data,
const QByteArray &key, const QByteArray &iv) const override
{
// The real implementation is to be provided by Schannel or SecureTransport.
Q_UNUSED(cipher)
Q_UNUSED(data)
Q_UNUSED(key)
Q_UNUSED(iv)
return {};
}
QByteArray encrypt(Cipher cipher, const QByteArray &data,
const QByteArray &key, const QByteArray &iv) const override
{
// The real implementation is to be provided by Schannel or SecureTransport.
Q_UNUSED(cipher)
Q_UNUSED(data)
Q_UNUSED(key)
Q_UNUSED(iv)
return {};
}
private:
QByteArray decryptPkcs8(const QByteArray &encrypted, const QByteArray &passPhrase);

View File

@ -37,7 +37,6 @@
**
****************************************************************************/
#include "qtlskey_generic_p.h"
#include "qx509_generic_p.h"
#include "qasn1element_p.h"

View File

@ -46,6 +46,14 @@ QT_BEGIN_NAMESPACE
namespace QSsl {
X509CertificateSchannel::X509CertificateSchannel() = default;
X509CertificateSchannel::~X509CertificateSchannel()
{
if (certificateContext)
CertFreeCertificateContext(certificateContext);
}
TlsKey *X509CertificateSchannel::publicKey() const
{
auto key = std::make_unique<TlsKeySchannel>(PublicKey);
@ -55,6 +63,23 @@ TlsKey *X509CertificateSchannel::publicKey() const
return key.release();
}
Qt::HANDLE X509CertificateSchannel::handle() const
{
return Qt::HANDLE(certificateContext);
}
QSslCertificate X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext)
{
QByteArray derData = QByteArray((const char *)certificateContext->pbCertEncoded,
certificateContext->cbCertEncoded);
QSslCertificate certificate(derData, QSsl::Der);
auto *certBackend = QTlsBackend::backend<X509CertificateSchannel>(certificate);
Q_ASSERT(certBackend);
certBackend->certificateContext = CertDuplicateCertificateContext(certificateContext);
return certificate;
}
} // namespace QSsl.
QT_END_NAMESPACE

View File

@ -57,6 +57,9 @@
#include <QtCore/qglobal.h>
#include <QtCore/qt_windows.h>
#include <wincrypt.h>
QT_BEGIN_NAMESPACE
namespace QSsl {
@ -64,7 +67,17 @@ namespace QSsl {
class X509CertificateSchannel final : public X509CertificateGeneric
{
public:
X509CertificateSchannel();
~X509CertificateSchannel();
TlsKey *publicKey() const override;
Qt::HANDLE handle() const override;
static QSslCertificate QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext);
private:
const CERT_CONTEXT *certificateContext = nullptr;
Q_DISABLE_COPY_MOVE(X509CertificateSchannel);
};
} // namespace QSsl.