summaryrefslogtreecommitdiffstats
path: root/src/network/ssl
diff options
context:
space:
mode:
authorShane Kearns <ext-shane.2.kearns@nokia.com>2012-03-23 11:01:42 +0000
committerQt by Nokia <qt-info@nokia.com>2012-04-11 15:49:02 +0200
commit7386ab17df94e58efeb2f2fba91b9f816834c077 (patch)
tree7c74a96457f9d9ab36fa09b4a38f32602d54182d /src/network/ssl
parent62cda62c0c70a75de3f87cbd802a1be24d27ef18 (diff)
Use windows API to update missing CA roots
Windows ships with a minimal set of CA roots. When using windows API to verify a certificate, it will fetch the root certificate from windows update (assuming it is part of the Microsoft trust program). As we are using openssl, this does not happen transparently. If SSL errors occur which indicate a broken chain then attempt to fix it using the windows API before emitting sslErrors. If the system CA certs are not in use (a CA bundle has been set on the socket or as the global configuration), then this is skipped. This is so an application can continue to use its own cert bundle rather than trusting the system certs. Key usage is specified, so that windows will return not trusted status if the root is not suitable for SSL (server auth or client auth OID). Testability: - to test, must delete the CA cert(s) from the "third party root certification authorities" section of the cert store using mmc.exe. - If the workaround of installing the windows XP cert bundle was performed, then you also need to delete certs from the "trusted root certification authorities" section. This is dangerous, be careful not to delete the required certificates which are documented on MS website - Naturally, modifying these areas of the cert store requires elevated privilege. Task-number: QTBUG-24827 Change-Id: I5cfe71c8a10595731f6bbbbabaaefa3313496654 Reviewed-by: Richard J. Moore <rich@kde.org> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Diffstat (limited to 'src/network/ssl')
-rw-r--r--src/network/ssl/qsslsocket.h3
-rw-r--r--src/network/ssl/qsslsocket_openssl.cpp238
-rw-r--r--src/network/ssl/qsslsocket_openssl_p.h22
-rw-r--r--src/network/ssl/qsslsocket_p.h3
-rw-r--r--src/network/ssl/ssl.pri2
5 files changed, 254 insertions, 14 deletions
diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h
index aa16425e9a..e420972041 100644
--- a/src/network/ssl/qsslsocket.h
+++ b/src/network/ssl/qsslsocket.h
@@ -213,6 +213,9 @@ private:
Q_PRIVATE_SLOT(d_func(), void _q_flushWriteBuffer())
Q_PRIVATE_SLOT(d_func(), void _q_flushReadBuffer())
Q_PRIVATE_SLOT(d_func(), void _q_resumeImplementation())
+#ifdef Q_OS_WIN
+ Q_PRIVATE_SLOT(d_func(), void _q_caRootLoaded(QSslCertificate,QSslCertificate))
+#endif
friend class QSslSocketBackendPrivate;
};
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
index df60a0fcce..0e55f5134c 100644
--- a/src/network/ssl/qsslsocket_openssl.cpp
+++ b/src/network/ssl/qsslsocket_openssl.cpp
@@ -661,6 +661,15 @@ void QSslSocketPrivate::ensureCiphersAndCertsLoaded()
// if on-demand loading was not enabled, load the certs now
if (!s_loadRootCertsOnDemand)
setDefaultCaCertificates(systemCaCertificates());
+#ifdef Q_OS_WIN
+ //Enabled for fetching additional root certs from windows update on windows 6+
+ //This flag is set false by setDefaultCaCertificates() indicating the app uses
+ //its own cert bundle rather than the system one.
+ //Same logic that disables the unix on demand cert loading.
+ //Unlike unix, we do preload the certificates from the cert store.
+ if ((QSysInfo::windowsVersion() & QSysInfo::WV_NT_based) >= QSysInfo::WV_6_0)
+ s_loadRootCertsOnDemand = true;
+#endif
}
/*!
@@ -1221,22 +1230,28 @@ bool QSslSocketBackendPrivate::startHandshake()
if (!errors.isEmpty()) {
sslErrors = errors;
- emit q->sslErrors(errors);
-
- bool doEmitSslError = !verifyErrorsHaveBeenIgnored();
- // check whether we need to emit an SSL handshake error
- if (doVerifyPeer && doEmitSslError) {
- if (q->pauseMode() & QAbstractSocket::PauseOnNotify) {
- pauseSocketNotifiers(q);
- paused = true;
- } else {
- q->setErrorString(sslErrors.first().errorString());
- q->setSocketError(QAbstractSocket::SslHandshakeFailedError);
- emit q->error(QAbstractSocket::SslHandshakeFailedError);
- plainSocket->disconnectFromHost();
+
+#ifdef Q_OS_WIN
+ //Skip this if not using system CAs, or if the SSL errors are configured in advance to be ignorable
+ if (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.
+ //However, this is only transparent if using WinINET - we have to trigger it
+ //ourselves.
+ for (int i=0; i< sslErrors.count(); i++) {
+ if (sslErrors.at(i).error() == QSslError::UnableToGetLocalIssuerCertificate) {
+ fetchCaRootForCert(sslErrors.at(i).certificate());
+ return false;
+ }
}
- return false;
}
+#endif
+
+ if (!checkSslErrors())
+ return false;
} else {
sslErrors.clear();
}
@@ -1245,6 +1260,201 @@ bool QSslSocketBackendPrivate::startHandshake()
return true;
}
+bool QSslSocketBackendPrivate::checkSslErrors()
+{
+ Q_Q(QSslSocket);
+ if (sslErrors.isEmpty())
+ return true;
+
+ emit q->sslErrors(sslErrors);
+
+ bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer
+ || (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer
+ && mode == QSslSocket::SslClientMode);
+ bool doEmitSslError = !verifyErrorsHaveBeenIgnored();
+ // check whether we need to emit an SSL handshake error
+ if (doVerifyPeer && doEmitSslError) {
+ if (q->pauseMode() & QAbstractSocket::PauseOnNotify) {
+ pauseSocketNotifiers(q);
+ paused = true;
+ } else {
+ q->setErrorString(sslErrors.first().errorString());
+ q->setSocketError(QAbstractSocket::SslHandshakeFailedError);
+ emit q->error(QAbstractSocket::SslHandshakeFailedError);
+ plainSocket->disconnectFromHost();
+ }
+ return false;
+ }
+ return true;
+}
+
+#ifdef Q_OS_WIN
+
+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);
+ QObject::connect(fetcher, SIGNAL(finished(QSslCertificate,QSslCertificate)), q, SLOT(_q_caRootLoaded(QSslCertificate,QSslCertificate)), Qt::QueuedConnection);
+ QMetaObject::invokeMethod(fetcher, "start", Qt::QueuedConnection);
+ pauseSocketNotifiers(q);
+ paused = true;
+}
+
+//This is the callback from QWindowsCaRootFetcher, trustedRoot will be invalid (default constructed) if it failed.
+void QSslSocketBackendPrivate::_q_caRootLoaded(QSslCertificate cert, QSslCertificate trustedRoot)
+{
+ Q_Q(QSslSocket);
+ if (trustedRoot.isValid()) {
+ if (s_loadRootCertsOnDemand) {
+ //Add the new root cert to default cert list for use by future sockets
+ QSslSocket::addDefaultCaCertificate(trustedRoot);
+ }
+ //Add the new root cert to this socket for future connections
+ q->addCaCertificate(trustedRoot);
+ //Remove the broken chain ssl errors (as chain is verified by windows)
+ for (int i=sslErrors.count() - 1; i >= 0; --i) {
+ if (sslErrors.at(i).certificate() == cert) {
+ switch (sslErrors.at(i).error()) {
+ case QSslError::UnableToGetLocalIssuerCertificate:
+ case QSslError::CertificateUntrusted:
+ case QSslError::UnableToVerifyFirstCertificate:
+ // error can be ignored if OS says the chain is trusted
+ sslErrors.removeAt(i);
+ break;
+ default:
+ // error cannot be ignored
+ break;
+ }
+ }
+ }
+ }
+ // Continue with remaining errors
+ if (plainSocket)
+ plainSocket->resume();
+ paused = false;
+ if (checkSslErrors())
+ continueHandshake();
+}
+
+Q_DECLARE_METATYPE(QSslCertificate);
+
+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);
+
+QWindowsCaRootFetcher::QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode)
+ : cert(certificate), mode(sslMode)
+{
+ 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
+ qDebug("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 = (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;
+ BOOL result = CertGetCertificateChain(
+ 0, //default engine
+ wincert,
+ 0, //current date/time
+ 0, //default store
+ &parameters,
+ 0, //default dwFlags
+ 0, //reserved
+ &chain);
+#ifdef QSSLSOCKET_DEBUG
+ qDebug() << "QWindowsCaRootFetcher" << stopwatch.elapsed() << "ms to get chain";
+#endif
+
+ QSslCertificate trustedRoot;
+ if (result) {
+#ifdef QSSLSOCKET_DEBUG
+ qDebug() << "QWindowsCaRootFetcher - examining windows chains";
+ if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
+ qDebug() << " - TRUSTED";
+ else
+ qDebug() << " - NOT TRUSTED" << chain->TrustStatus.dwErrorStatus;
+ if (chain->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED)
+ qDebug() << " - SELF SIGNED";
+ qDebug() << "QSslSocketBackendPrivate::fetchCaRootForCert - dumping simple chains";
+ for (unsigned int i = 0; i < chain->cChain; i++) {
+ if (chain->rgpChain[i]->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
+ qDebug() << " - TRUSTED SIMPLE CHAIN" << i;
+ else
+ qDebug() << " - 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);
+ qDebug() << " - " << foundCert;
+ }
+ }
+ qDebug() << " - 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);
+ }
+ }
+ CertFreeCertificateChain(chain);
+ }
+ CertFreeCertificateContext(wincert);
+
+ emit finished(cert, trustedRoot);
+ deleteLater();
+}
+#endif
+
void QSslSocketBackendPrivate::disconnectFromHost()
{
if (ssl) {
diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h
index b31eae9c97..deeceb8d85 100644
--- a/src/network/ssl/qsslsocket_openssl_p.h
+++ b/src/network/ssl/qsslsocket_openssl_p.h
@@ -117,6 +117,11 @@ public:
void disconnected();
QSslCipher sessionCipher() const;
void continueHandshake();
+ bool checkSslErrors();
+#ifdef Q_OS_WIN
+ void fetchCaRootForCert(const QSslCertificate &cert);
+ void _q_caRootLoaded(QSslCertificate,QSslCertificate);
+#endif
Q_AUTOTEST_EXPORT static long setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions);
static QSslCipher QSslCipher_from_SSL_CIPHER(SSL_CIPHER *cipher);
@@ -127,6 +132,23 @@ public:
static QString getErrorsFromOpenSsl();
};
+#ifdef Q_OS_WIN
+class QWindowsCaRootFetcher : public QObject
+{
+ Q_OBJECT;
+public:
+ QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode);
+ ~QWindowsCaRootFetcher();
+public slots:
+ void start();
+signals:
+ void finished(QSslCertificate brokenChain, QSslCertificate caroot);
+private:
+ QSslCertificate cert;
+ QSslSocket::SslMode mode;
+};
+#endif
+
QT_END_NAMESPACE
#endif
diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h
index 44114481df..e5d1144c2c 100644
--- a/src/network/ssl/qsslsocket_p.h
+++ b/src/network/ssl/qsslsocket_p.h
@@ -160,6 +160,9 @@ public:
void _q_flushWriteBuffer();
void _q_flushReadBuffer();
void _q_resumeImplementation();
+#ifdef Q_OS_WIN
+ virtual void _q_caRootLoaded(QSslCertificate,QSslCertificate) = 0;
+#endif
// Platform specific functions
virtual void startClientEncryption() = 0;
diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri
index 87bcc9378a..c81e461d3f 100644
--- a/src/network/ssl/ssl.pri
+++ b/src/network/ssl/ssl.pri
@@ -28,4 +28,6 @@ contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) {
# Add optional SSL libs
LIBS_PRIVATE += $$OPENSSL_LIBS
+
+ windows:LIBS += -lcrypt32
}