summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
Diffstat (limited to 'src/network')
-rw-r--r--src/network/ssl/qsslcertificate_openssl.cpp3
-rw-r--r--src/network/ssl/qsslsocket.cpp1
-rw-r--r--src/network/ssl/qsslsocket_openssl.cpp131
-rw-r--r--src/network/ssl/qsslsocket_openssl_p.h2
-rw-r--r--src/network/ssl/qsslsocket_p.h16
-rw-r--r--src/network/ssl/qsslsocket_schannel_p.h10
-rw-r--r--src/network/ssl/qwindowscarootfetcher.cpp127
-rw-r--r--src/network/ssl/qwindowscarootfetcher_p.h20
8 files changed, 252 insertions, 58 deletions
diff --git a/src/network/ssl/qsslcertificate_openssl.cpp b/src/network/ssl/qsslcertificate_openssl.cpp
index 2bb7c930f2..cd178313b7 100644
--- a/src/network/ssl/qsslcertificate_openssl.cpp
+++ b/src/network/ssl/qsslcertificate_openssl.cpp
@@ -528,6 +528,9 @@ QList<QSslCertificateExtension> QSslCertificate::extensions() const
result << QSslCertificatePrivate::convertExtension(ext);
}
+ // Converting an extension may result in an error(s), clean them up.
+ Q_UNUSED(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
+
return result;
}
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index 667a2e7267..4b6d8c21d1 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -2326,6 +2326,7 @@ void QSslSocketPrivate::init()
writeBuffer.clear();
configuration.peerCertificate.clear();
configuration.peerCertificateChain.clear();
+ fetchAuthorityInformation = false;
}
/*!
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
index 4c96c02ade..9d95a58118 100644
--- a/src/network/ssl/qsslsocket_openssl.cpp
+++ b/src/network/ssl/qsslsocket_openssl.cpp
@@ -134,6 +134,49 @@ QAlertType tlsAlertType(int value)
return QAlertType(value & 0xff);
}
+#ifdef Q_OS_WIN
+
+QSslCertificate findCertificateToFetch(const QVector<QSslError> &tlsErrors, bool checkAIA)
+{
+ QSslCertificate certToFetch;
+
+ for (const auto &tlsError : tlsErrors) {
+ switch (tlsError.error()) {
+ case QSslError::UnableToGetLocalIssuerCertificate: // site presented intermediate cert, but root is unknown
+ case QSslError::SelfSignedCertificateInChain: // site presented a complete chain, but root is unknown
+ certToFetch = tlsError.certificate();
+ break;
+ case QSslError::SelfSignedCertificate:
+ case QSslError::CertificateBlacklisted:
+ //With these errors, we know it will be untrusted so save time by not asking windows
+ return QSslCertificate{};
+ default:
+#ifdef QSSLSOCKET_DEBUG
+ qCDebug(lcSsl) << tlsError.errorString();
+#endif
+ //TODO - this part is strange.
+ break;
+ }
+ }
+
+ if (checkAIA) {
+ const auto extensions = certToFetch.extensions();
+ for (const auto &ext : extensions) {
+ if (ext.oid() == QStringLiteral("1.3.6.1.5.5.7.1.1")) // See RFC 4325
+ return certToFetch;
+ }
+ //The only reason we check this extensions is because an application set trusted
+ //CA certificates explicitly, thus technically disabling CA fetch. So, if it's
+ //the case and an intermediate certificate is missing, and no extensions is
+ //present on the leaf certificate - we fail the handshake immediately.
+ return QSslCertificate{};
+ }
+
+ return certToFetch;
+}
+
+#endif // Q_OS_WIN
+
} // Unnamed namespace
extern "C"
@@ -1489,40 +1532,27 @@ bool QSslSocketBackendPrivate::startHandshake()
sslErrors = errors;
#ifdef Q_OS_WIN
+ const bool fetchEnabled = s_loadRootCertsOnDemand
+ && allowRootCertOnDemandLoading;
+ // !fetchEnabled is a special case scenario, when we potentially have a missing
+ // intermediate certificate and a recoverable chain, but on demand cert loading
+ // was disabled by setCaCertificates call. For this scenario we check if "Authority
+ // Information Access" is present - wincrypt can deal with such certificates.
+ QSslCertificate certToFetch;
+ if (doVerifyPeer && !verifyErrorsHaveBeenIgnored())
+ certToFetch = findCertificateToFetch(sslErrors, !fetchEnabled);
+
//Skip this if not using system CAs, or if the SSL errors are configured in advance to be ignorable
- if (doVerifyPeer
- && s_loadRootCertsOnDemand
- && allowRootCertOnDemandLoading
- && !verifyErrorsHaveBeenIgnored()) {
- //Windows desktop versions starting from vista ship with minimal set of roots
- //and download on demand from the windows update server CA roots that are
- //trusted by MS.
+ if (!certToFetch.isNull()) {
+ fetchAuthorityInformation = !fetchEnabled;
+ //Windows desktop versions starting from vista ship with minimal set of roots and download on demand
+ //from the windows update server CA roots that are trusted by MS. It also can fetch a missing intermediate
+ //in case "Authority Information Access" extension is present.
+ //
//However, this is only transparent if using WinINET - we have to trigger it
//ourselves.
- QSslCertificate certToFetch;
- bool fetchCertificate = true;
- for (int i=0; i< sslErrors.count(); i++) {
- switch (sslErrors.at(i).error()) {
- case QSslError::UnableToGetLocalIssuerCertificate: // site presented intermediate cert, but root is unknown
- case QSslError::SelfSignedCertificateInChain: // site presented a complete chain, but root is unknown
- certToFetch = sslErrors.at(i).certificate();
- break;
- case QSslError::SelfSignedCertificate:
- case QSslError::CertificateBlacklisted:
- //With these errors, we know it will be untrusted so save time by not asking windows
- fetchCertificate = false;
- break;
- default:
-#ifdef QSSLSOCKET_DEBUG
- qCDebug(lcSsl) << sslErrors.at(i).errorString();
-#endif
- break;
- }
- }
- if (fetchCertificate && !certToFetch.isNull()) {
- fetchCaRootForCert(certToFetch);
- return false;
- }
+ fetchCaRootForCert(certToFetch);
+ return false;
}
#endif
if (!checkSslErrors())
@@ -1699,7 +1729,11 @@ void QSslSocketBackendPrivate::fetchCaRootForCert(const QSslCertificate &cert)
Q_Q(QSslSocket);
//The root certificate is downloaded from windows update, which blocks for 15 seconds in the worst case
//so the request is done in a worker thread.
- QWindowsCaRootFetcher *fetcher = new QWindowsCaRootFetcher(cert, mode);
+ QList<QSslCertificate> customRoots;
+ if (fetchAuthorityInformation)
+ customRoots = configuration.caCertificates;
+
+ QWindowsCaRootFetcher *fetcher = new QWindowsCaRootFetcher(cert, mode, customRoots, q->peerVerifyName());
QObject::connect(fetcher, SIGNAL(finished(QSslCertificate,QSslCertificate)), q, SLOT(_q_caRootLoaded(QSslCertificate,QSslCertificate)), Qt::QueuedConnection);
QMetaObject::invokeMethod(fetcher, "start", Qt::QueuedConnection);
pauseSocketNotifiers(q);
@@ -1709,6 +1743,12 @@ void QSslSocketBackendPrivate::fetchCaRootForCert(const QSslCertificate &cert)
//This is the callback from QWindowsCaRootFetcher, trustedRoot will be invalid (default constructed) if it failed.
void QSslSocketBackendPrivate::_q_caRootLoaded(QSslCertificate cert, QSslCertificate trustedRoot)
{
+ if (fetchAuthorityInformation) {
+ if (!configuration.caCertificates.contains(trustedRoot))
+ trustedRoot = QSslCertificate{};
+ fetchAuthorityInformation = false;
+ }
+
if (!trustedRoot.isNull() && !trustedRoot.isBlacklisted()) {
if (s_loadRootCertsOnDemand) {
//Add the new root cert to default cert list for use by future sockets
@@ -1735,6 +1775,7 @@ void QSslSocketBackendPrivate::_q_caRootLoaded(QSslCertificate cert, QSslCertifi
}
}
}
+
// Continue with remaining errors
if (plainSocket)
plainSocket->resume();
@@ -2249,14 +2290,23 @@ QList<QSslCertificate> QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates
return certificates;
}
-QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &certificateChain, const QString &hostName)
+QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &certificateChain,
+ const QString &hostName)
{
- QList<QSslError> errors;
- if (certificateChain.count() <= 0) {
- errors << QSslError(QSslError::UnspecifiedError);
- return errors;
- }
+ if (s_loadRootCertsOnDemand)
+ setDefaultCaCertificates(defaultCaCertificates() + systemCaCertificates());
+
+ return verify(QSslConfiguration::defaultConfiguration().caCertificates(), certificateChain, hostName);
+}
+QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &caCertificates,
+ const QList<QSslCertificate> &certificateChain,
+ const QString &hostName)
+{
+ if (certificateChain.count() <= 0)
+ return {QSslError(QSslError::UnspecifiedError)};
+
+ QList<QSslError> errors;
// Setup the store with the default CA certificates
X509_STORE *certStore = q_X509_STORE_new();
if (!certStore) {
@@ -2266,12 +2316,7 @@ QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &
}
const std::unique_ptr<X509_STORE, decltype(&q_X509_STORE_free)> storeGuard(certStore, q_X509_STORE_free);
- if (s_loadRootCertsOnDemand) {
- setDefaultCaCertificates(defaultCaCertificates() + systemCaCertificates());
- }
-
const QDateTime now = QDateTime::currentDateTimeUtc();
- const auto caCertificates = QSslConfiguration::defaultConfiguration().caCertificates();
for (const QSslCertificate &caCertificate : caCertificates) {
// From https://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html:
//
diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h
index 04feec9f45..de1dcfc91a 100644
--- a/src/network/ssl/qsslsocket_openssl_p.h
+++ b/src/network/ssl/qsslsocket_openssl_p.h
@@ -181,6 +181,8 @@ public:
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,
diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h
index 25625a1313..971650a71c 100644
--- a/src/network/ssl/qsslsocket_p.h
+++ b/src/network/ssl/qsslsocket_p.h
@@ -74,6 +74,7 @@ class QSslContext;
#include <CoreFoundation/CFArray.h>
#elif defined(Q_OS_WIN)
#include <QtCore/qt_windows.h>
+#include <memory>
#ifndef Q_OS_WINRT
#include <wincrypt.h>
#endif // !Q_OS_WINRT
@@ -90,6 +91,20 @@ QT_BEGIN_NAMESPACE
typedef OSStatus (*PtrSecTrustCopyAnchorCertificates)(CFArrayRef*);
#endif
+#if defined(Q_OS_WIN)
+
+// Those are needed by both OpenSSL and SChannel back-ends on Windows:
+struct QHCertStoreDeleter {
+ void operator()(HCERTSTORE store)
+ {
+ CertCloseStore(store, 0);
+ }
+};
+
+using QHCertStorePointer = std::unique_ptr<void, QHCertStoreDeleter>;
+
+#endif // Q_OS_WIN
+
class QSslSocketPrivate : public QTcpSocketPrivate
{
Q_DECLARE_PUBLIC(QSslSocket)
@@ -208,6 +223,7 @@ protected:
bool systemOrSslErrorDetected = false;
QVector<QOcspResponse> ocspResponses;
bool handshakeInterrupted = false;
+ bool fetchAuthorityInformation = false;
};
#if QT_CONFIG(securetransport) || QT_CONFIG(schannel)
diff --git a/src/network/ssl/qsslsocket_schannel_p.h b/src/network/ssl/qsslsocket_schannel_p.h
index a184deef49..fe29dadec0 100644
--- a/src/network/ssl/qsslsocket_schannel_p.h
+++ b/src/network/ssl/qsslsocket_schannel_p.h
@@ -62,18 +62,8 @@ QT_REQUIRE_CONFIG(schannel);
#include <schnlsp.h>
#undef SECURITY_WIN32
-#include <memory>
-
QT_BEGIN_NAMESPACE
-struct QHCertStoreDeleter {
- void operator()(HCERTSTORE store)
- {
- CertCloseStore(store, 0);
- }
-};
-typedef std::unique_ptr<void, QHCertStoreDeleter> QHCertStorePointer;
-
class QSslSocketBackendPrivate final : public QSslSocketPrivate
{
Q_DISABLE_COPY_MOVE(QSslSocketBackendPrivate)
diff --git a/src/network/ssl/qwindowscarootfetcher.cpp b/src/network/ssl/qwindowscarootfetcher.cpp
index f83f96886c..c414ca580b 100644
--- a/src/network/ssl/qwindowscarootfetcher.cpp
+++ b/src/network/ssl/qwindowscarootfetcher.cpp
@@ -42,6 +42,8 @@
#include <QtCore/QThread>
#include <QtGlobal>
+#include <QtCore/qscopeguard.h>
+
#ifdef QSSLSOCKET_DEBUG
#include "qssl_p.h" // for debug categories
#include <QtCore/QElapsedTimer>
@@ -49,6 +51,10 @@
#include "qsslsocket_p.h" // Transitively includes Wincrypt.h
+#if QT_CONFIG(openssl)
+#include "qsslsocket_openssl_p.h"
+#endif
+
QT_BEGIN_NAMESPACE
class QWindowsCaRootFetcherThread : public QThread
@@ -69,8 +75,67 @@ public:
Q_GLOBAL_STATIC(QWindowsCaRootFetcherThread, windowsCaRootFetcherThread);
-QWindowsCaRootFetcher::QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode)
- : cert(certificate), mode(sslMode)
+#if QT_CONFIG(openssl)
+namespace {
+
+const QList<QSslCertificate> buildVerifiedChain(const QList<QSslCertificate> &caCertificates,
+ PCCERT_CHAIN_CONTEXT chainContext,
+ const QString &peerVerifyName)
+{
+ // We ended up here because OpenSSL verification failed to
+ // build a chain, with intermediate certificate missing
+ // but "Authority Information Access" extension present.
+ // Also, apparently the normal CA fetching path was disabled
+ // by setting custom CA certificates. We convert wincrypt's
+ // structures in QSslCertificate and give OpenSSL the second
+ // chance to verify the now (apparently) complete chain.
+ // In addition, wincrypt gives us a benifit of some checks
+ // we don't have in OpenSSL back-end.
+ Q_ASSERT(chainContext);
+
+ if (!chainContext->cChain)
+ return {};
+
+ QList<QSslCertificate> verifiedChain;
+
+ CERT_SIMPLE_CHAIN *chain = chainContext->rgpChain[chainContext->cChain - 1];
+ if (!chain)
+ return {};
+
+ if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN)
+ return {}; // No need to mess with OpenSSL (the chain is still incomplete).
+
+ for (DWORD i = 0; i < chain->cElement; ++i) {
+ CERT_CHAIN_ELEMENT *element = chain->rgpElement[i];
+ QSslCertificate cert(QByteArray(reinterpret_cast<const char*>(element->pCertContext->pbCertEncoded),
+ int(element->pCertContext->cbCertEncoded)), QSsl::Der);
+
+ if (cert.isBlacklisted())
+ return {};
+
+ if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) // Good to know!
+ return {};
+
+ if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID)
+ return {};
+
+ verifiedChain.append(cert);
+ }
+
+ // We rely on OpenSSL's ability to find other problems.
+ const auto tlsErrors = QSslSocketBackendPrivate::verify(caCertificates, verifiedChain, peerVerifyName);
+ if (tlsErrors.size())
+ verifiedChain.clear();
+
+ return verifiedChain;
+}
+
+} // unnamed namespace
+#endif // QT_CONFIG(openssl)
+
+QWindowsCaRootFetcher::QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode,
+ const QList<QSslCertificate> &caCertificates, const QString &hostName)
+ : cert(certificate), mode(sslMode), explicitlyTrustedCAs(caCertificates), peerVerifyName(hostName)
{
moveToThread(windowsCaRootFetcherThread());
}
@@ -106,11 +171,12 @@ void QWindowsCaRootFetcher::start()
stopwatch.start();
#endif
PCCERT_CHAIN_CONTEXT chain;
+ auto additionalStore = createAdditionalStore();
BOOL result = CertGetCertificateChain(
nullptr, //default engine
wincert,
nullptr, //current date/time
- nullptr, //default store
+ additionalStore.get(), //default store (nullptr) or CAs an application trusts
&parameters,
0, //default dwFlags
nullptr, //reserved
@@ -156,6 +222,16 @@ void QWindowsCaRootFetcher::start()
trustedRoot = QSslCertificate(QByteArray((const char *)finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->pbCertEncoded
, finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->cbCertEncoded), QSsl::Der);
}
+ } else if (explicitlyTrustedCAs.size()) {
+ // Setting custom CA in configuration, and those CAs are not trusted by MS.
+#if QT_CONFIG(openssl)
+ const auto verifiedChain = buildVerifiedChain(explicitlyTrustedCAs, chain, peerVerifyName);
+ if (verifiedChain.size())
+ trustedRoot = verifiedChain.last();
+#else
+ //It's only OpenSSL code-path that can trigger such a fetch.
+ Q_UNREACHABLE();
+#endif
}
CertFreeCertificateChain(chain);
}
@@ -165,4 +241,49 @@ void QWindowsCaRootFetcher::start()
deleteLater();
}
+QHCertStorePointer QWindowsCaRootFetcher::createAdditionalStore() const
+{
+ QHCertStorePointer customStore;
+ if (explicitlyTrustedCAs.isEmpty())
+ return customStore;
+
+ if (HCERTSTORE rawPtr = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr)) {
+ customStore.reset(rawPtr);
+
+ unsigned rootsAdded = 0;
+ for (const QSslCertificate &caCert : explicitlyTrustedCAs) {
+ const auto der = caCert.toDer();
+ PCCERT_CONTEXT winCert = CertCreateCertificateContext(X509_ASN_ENCODING,
+ reinterpret_cast<const BYTE *>(der.data()),
+ DWORD(der.length()));
+ if (!winCert) {
+#if defined(QSSLSOCKET_DEBUG)
+ qCWarning(lcSsl) << "CA fetcher, failed to convert QSslCertificate"
+ << "to the native representation";
+#endif // QSSLSOCKET_DEBUG
+ continue;
+ }
+ const auto deleter = qScopeGuard([winCert](){
+ CertFreeCertificateContext(winCert);
+ });
+ if (CertAddCertificateContextToStore(customStore.get(), winCert, CERT_STORE_ADD_ALWAYS, nullptr))
+ ++rootsAdded;
+#if defined(QSSLSOCKET_DEBUG)
+ else //Why assert? With flags we're using and winCert check - should not happen!
+ Q_ASSERT("CertAddCertificateContextToStore() failed");
+#endif // QSSLSOCKET_DEBUG
+ }
+ if (!rootsAdded) //Useless store, no cert was added.
+ customStore.reset();
+#if defined(QSSLSOCKET_DEBUG)
+ } else {
+
+ qCWarning(lcSsl) << "CA fetcher, failed to create a custom"
+ << "store for explicitly trusted CA certificate";
+#endif // QSSLSOCKET_DEBUG
+ }
+
+ return customStore;
+}
+
QT_END_NAMESPACE
diff --git a/src/network/ssl/qwindowscarootfetcher_p.h b/src/network/ssl/qwindowscarootfetcher_p.h
index 181c309388..e98e59f0cf 100644
--- a/src/network/ssl/qwindowscarootfetcher_p.h
+++ b/src/network/ssl/qwindowscarootfetcher_p.h
@@ -43,9 +43,13 @@
#include <QtCore/QtGlobal>
#include <QtCore/QObject>
+#include "qsslsocket_p.h"
+
#include "qsslsocket.h"
#include "qsslcertificate.h"
+#include <memory>
+
//
// W A R N I N G
// -------------
@@ -61,17 +65,29 @@ QT_BEGIN_NAMESPACE
class QWindowsCaRootFetcher : public QObject
{
- Q_OBJECT;
+ Q_OBJECT
public:
- QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode);
+ QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode,
+ const QList<QSslCertificate> &caCertificates = {},
+ const QString &hostName = {});
~QWindowsCaRootFetcher();
public slots:
void start();
signals:
void finished(QSslCertificate brokenChain, QSslCertificate caroot);
private:
+ QHCertStorePointer createAdditionalStore() const;
+
QSslCertificate cert;
QSslSocket::SslMode mode;
+ // In case the application set CA certificates in the configuration,
+ // in the past we did not load missing certs. But this disables
+ // recoverable case when a certificate has Authority Information Access
+ // extension. So we try to fetch in this scenario also, but in case
+ // explicitly trusted root was not in a system store, we'll do
+ // additional checks, thus we need 'peerVerifyName':
+ QList<QSslCertificate> explicitlyTrustedCAs;
+ QString peerVerifyName;
};
QT_END_NAMESPACE