summaryrefslogtreecommitdiffstats
path: root/src/network/ssl/qsslsocket_openssl.cpp
diff options
context:
space:
mode:
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:
//