diff options
author | Shane Kearns <ext-shane.2.kearns@nokia.com> | 2012-03-23 11:01:42 +0000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-04-11 15:49:02 +0200 |
commit | 7386ab17df94e58efeb2f2fba91b9f816834c077 (patch) | |
tree | 7c74a96457f9d9ab36fa09b4a38f32602d54182d /src/network/ssl/qsslsocket_openssl.cpp | |
parent | 62cda62c0c70a75de3f87cbd802a1be24d27ef18 (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/qsslsocket_openssl.cpp')
-rw-r--r-- | src/network/ssl/qsslsocket_openssl.cpp | 238 |
1 files changed, 224 insertions, 14 deletions
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(¶meters, 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 + ¶meters, + 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) { |