summaryrefslogtreecommitdiffstats
path: root/src/plugins/tls/openssl/qwindowscarootfetcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/tls/openssl/qwindowscarootfetcher.cpp')
-rw-r--r--src/plugins/tls/openssl/qwindowscarootfetcher.cpp249
1 files changed, 249 insertions, 0 deletions
diff --git a/src/plugins/tls/openssl/qwindowscarootfetcher.cpp b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp
new file mode 100644
index 0000000000..a18aae0b71
--- /dev/null
+++ b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp
@@ -0,0 +1,249 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowscarootfetcher_p.h"
+#include "qx509_openssl_p.h"
+#include "qopenssl_p.h"
+
+#include <QtCore/QThread>
+#include <QtGlobal>
+
+#include <QtCore/qscopeguard.h>
+
+#ifdef QSSLSOCKET_DEBUG
+#include <QtNetwork/private/qtlsbackend_p.h> // for debug categories
+#include <QtCore/QElapsedTimer>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QWindowsCaRootFetcherThread : public QThread
+{
+public:
+ QWindowsCaRootFetcherThread()
+ {
+ qRegisterMetaType<QSslCertificate>();
+ setObjectName(QStringLiteral("QWindowsCaRootFetcher"));
+ start();
+ }
+ ~QWindowsCaRootFetcherThread()
+ {
+ quit();
+ wait(15500); // worst case, a running request can block for 15 seconds
+ }
+};
+
+Q_GLOBAL_STATIC(QWindowsCaRootFetcherThread, windowsCaRootFetcherThread);
+
+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 benefit 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 = QTlsPrivate::X509CertificateOpenSSL::verify(caCertificates, verifiedChain, peerVerifyName);
+ if (tlsErrors.size())
+ verifiedChain.clear();
+
+ return verifiedChain;
+}
+
+} // unnamed namespace
+
+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());
+}
+
+QWindowsCaRootFetcher::~QWindowsCaRootFetcher()
+{
+}
+
+void QWindowsCaRootFetcher::start()
+{
+ QByteArray der = cert.toDer();
+ PCCERT_CONTEXT wincert = CertCreateCertificateContext(X509_ASN_ENCODING, (const BYTE *)der.constData(), der.length());
+ if (!wincert) {
+#ifdef QSSLSOCKET_DEBUG
+ qCDebug(lcTlsBackend, "QWindowsCaRootFetcher failed to convert certificate to windows form");
+#endif
+ emit finished(cert, QSslCertificate());
+ deleteLater();
+ return;
+ }
+
+ CERT_CHAIN_PARA parameters;
+ memset(&parameters, 0, sizeof(parameters));
+ parameters.cbSize = sizeof(parameters);
+ // set key usage constraint
+ parameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
+ parameters.RequestedUsage.Usage.cUsageIdentifier = 1;
+ LPSTR oid = (LPSTR)(mode == QSslSocket::SslClientMode ? szOID_PKIX_KP_SERVER_AUTH : szOID_PKIX_KP_CLIENT_AUTH);
+ parameters.RequestedUsage.Usage.rgpszUsageIdentifier = &oid;
+
+#ifdef QSSLSOCKET_DEBUG
+ QElapsedTimer stopwatch;
+ stopwatch.start();
+#endif
+ PCCERT_CHAIN_CONTEXT chain;
+ auto additionalStore = createAdditionalStore();
+ BOOL result = CertGetCertificateChain(
+ nullptr, //default engine
+ wincert,
+ nullptr, //current date/time
+ additionalStore.get(), //default store (nullptr) or CAs an application trusts
+ &parameters,
+ 0, //default dwFlags
+ nullptr, //reserved
+ &chain);
+#ifdef QSSLSOCKET_DEBUG
+ qCDebug(lcSsl) << "QWindowsCaRootFetcher" << stopwatch.elapsed() << "ms to get chain";
+#endif
+
+ QSslCertificate trustedRoot;
+ if (result) {
+#ifdef QSSLSOCKET_DEBUG
+ qCDebug(lcSsl) << "QWindowsCaRootFetcher - examining windows chains";
+ if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
+ qCDebug(lcSsl) << " - TRUSTED";
+ else
+ qCDebug(lcSsl) << " - NOT TRUSTED" << chain->TrustStatus.dwErrorStatus;
+ if (chain->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED)
+ qCDebug(lcSsl) << " - SELF SIGNED";
+ qCDebug(lcSsl) << "QWindowsCaRootFetcher - dumping simple chains";
+ for (unsigned int i = 0; i < chain->cChain; i++) {
+ if (chain->rgpChain[i]->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
+ qCDebug(lcSsl) << " - TRUSTED SIMPLE CHAIN" << i;
+ else
+ qCDebug(lcSsl) << " - UNTRUSTED SIMPLE CHAIN" << i << "reason:" << chain->rgpChain[i]->TrustStatus.dwErrorStatus;
+ for (unsigned int j = 0; j < chain->rgpChain[i]->cElement; j++) {
+ QSslCertificate foundCert(QByteArray((const char *)chain->rgpChain[i]->rgpElement[j]->pCertContext->pbCertEncoded
+ , chain->rgpChain[i]->rgpElement[j]->pCertContext->cbCertEncoded), QSsl::Der);
+ qCDebug(lcSsl) << " - " << foundCert;
+ }
+ }
+ qCDebug(lcSsl) << " - and" << chain->cLowerQualityChainContext << "low quality chains"; //expect 0, we haven't asked for them
+#endif
+
+ //based on http://msdn.microsoft.com/en-us/library/windows/desktop/aa377182%28v=vs.85%29.aspx
+ //about the final chain rgpChain[cChain-1] which must begin with a trusted root to be valid
+ if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR
+ && chain->cChain > 0) {
+ const PCERT_SIMPLE_CHAIN finalChain = chain->rgpChain[chain->cChain - 1];
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa377544%28v=vs.85%29.aspx
+ // rgpElement[0] is the end certificate chain element. rgpElement[cElement-1] is the self-signed "root" certificate element.
+ if (finalChain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR
+ && finalChain->cElement > 0) {
+ 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);
+ }
+ CertFreeCertificateContext(wincert);
+
+ emit finished(cert, trustedRoot);
+ 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
+
+#include "moc_qwindowscarootfetcher_p.cpp"