summaryrefslogtreecommitdiffstats
path: root/src/network/ssl/qsslsocket_openssl.cpp
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@qt.io>2020-05-14 16:40:08 +0200
committerTimur Pocheptsov <timur.pocheptsov@qt.io>2020-05-29 16:58:43 +0200
commit73158a9cb0942c2cdb3c6a98bcfd5763eed65c85 (patch)
tree0ac4b5b9a110d0c0b76ea9d9a758b18a106ea43e /src/network/ssl/qsslsocket_openssl.cpp
parent2216f10ffdd7e6e836dd0b63f5130bcac2f071d7 (diff)
CA fetcher (Windows) - relax the logic a bit
In case a certificate chain is missing an intermediate, for a certificate having "Authority Information Access" extension it's possible to fetch this intermediate and build the chain up to the trusted root. Unfortunately, it's not always possible to install the root certificate in the system "ROOT" store and then an application wants to set it in the socket's configuration, using setCaCertificates(). But this call also disables CA fetcher ('no on demand root loading'). It makes sense to relax this logic for such certificates and try to fetch the intermediate CA and then have the complete chain verified. Pick-to: 5.15 Fixes: QTBUG-84173 Change-Id: I5b9b4271767eba6f5fd2b5cf05e942360c6aa245 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io> Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src/network/ssl/qsslsocket_openssl.cpp')
-rw-r--r--src/network/ssl/qsslsocket_openssl.cpp131
1 files changed, 88 insertions, 43 deletions
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:
//