diff options
author | Richard Moore <rich@kde.org> | 2011-06-18 15:53:53 +0100 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2011-07-12 13:57:14 +0200 |
commit | 451f3b3785ea5d08f5092978b6ebe17f25ef7a88 (patch) | |
tree | 3f690085d0e7a0029aeeea97368cad583f5b2a1d /src/network | |
parent | ad35a41739c8e1fb6db62ed37b764448b2e59ece (diff) |
Add the ability to verify a chain of certificates
Currently it is only possible to verify a certificate chain when
connecting to a server. This change makes it possible to verify a
chain at any time.
Change-Id: Ib70ad7b81418f880e995f391b82ce59561ededb8
Merge-request: 11
Reviewed-by: Peter Hartmann <peter.hartmann@nokia.com>
Reviewed-on: http://codereview.qt.nokia.com/1509
Diffstat (limited to 'src/network')
-rw-r--r-- | src/network/ssl/qsslcertificate.cpp | 12 | ||||
-rw-r--r-- | src/network/ssl/qsslcertificate.h | 3 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl.cpp | 181 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl_p.h | 2 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl_symbols.cpp | 6 | ||||
-rw-r--r-- | src/network/ssl/qsslsocket_openssl_symbols_p.h | 4 |
6 files changed, 182 insertions, 26 deletions
diff --git a/src/network/ssl/qsslcertificate.cpp b/src/network/ssl/qsslcertificate.cpp index 839a253b99..41e54b2eb6 100644 --- a/src/network/ssl/qsslcertificate.cpp +++ b/src/network/ssl/qsslcertificate.cpp @@ -121,6 +121,7 @@ #include <QtCore/qfile.h> #include <QtCore/qfileinfo.h> #include <QtCore/qmap.h> +#include <QtCore/qmutex.h> #include <QtCore/qstring.h> #include <QtCore/qstringlist.h> #include <QtCore/qvarlengtharray.h> @@ -666,6 +667,17 @@ QList<QSslCertificate> QSslCertificate::fromData(const QByteArray &data, QSsl::E : QSslCertificatePrivate::certificatesFromDer(data); } +/*! + Verifies a certificate chain. If a hostName is specified then the certificate is + also checked to see if it is valid for the specified hostName. + Note that the first certificate in the list should be the leaf certificate of + the chain to be verified. + */ +QList<QSslError> QSslCertificate::verify(QList<QSslCertificate> certificateChain, const QString &hostName) +{ + return QSslSocketBackendPrivate::verify(certificateChain, hostName); +} + void QSslCertificatePrivate::init(const QByteArray &data, QSsl::EncodingFormat format) { if (!data.isEmpty()) { diff --git a/src/network/ssl/qsslcertificate.h b/src/network/ssl/qsslcertificate.h index 4de84dd4ba..a057d7a17d 100644 --- a/src/network/ssl/qsslcertificate.h +++ b/src/network/ssl/qsslcertificate.h @@ -62,6 +62,7 @@ QT_MODULE(Network) class QDateTime; class QIODevice; +class QSslError; class QSslKey; class QStringList; template <typename T, typename U> class QMultiMap; @@ -122,6 +123,8 @@ public: static QList<QSslCertificate> fromData( const QByteArray &data, QSsl::EncodingFormat format = QSsl::Pem); + static QList<QSslError> verify(QList<QSslCertificate> certificateChain, const QString &hostName = QString()); + Qt::HANDLE handle() const; private: diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index c7e938a705..84e5200043 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -1283,33 +1283,14 @@ bool QSslSocketBackendPrivate::startHandshake() // if we're the server, don't check CN if (mode == QSslSocket::SslClientMode) { QString peerName = (verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName); - QStringList commonNameList = configuration.peerCertificate.subjectInfo(QSslCertificate::CommonName); - bool matched = false; - foreach (const QString &commonName, commonNameList) { - if (isMatchingHostname(commonName.toLower(), peerName.toLower())) { - matched = true; - break; - } - } - - if (!matched) { - foreach (const QString &altName, configuration.peerCertificate - .alternateSubjectNames().values(QSsl::DnsEntry)) { - if (isMatchingHostname(altName.toLower(), peerName.toLower())) { - matched = true; - break; - } - } - } - - if (!matched) { - // No matches in common names or alternate names. - QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate); - errors << error; - emit q->peerVerifyError(error); - if (q->state() != QAbstractSocket::ConnectedState) - return false; + if (!isMatchingHostname(configuration.peerCertificate, peerName)) { + // No matches in common names or alternate names. + QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate); + errors << error; + emit q->peerVerifyError(error); + if (q->state() != QAbstractSocket::ConnectedState) + return false; } } } else { @@ -1444,6 +1425,25 @@ QString QSslSocketBackendPrivate::getErrorsFromOpenSsl() return errorString; } +bool QSslSocketBackendPrivate::isMatchingHostname(const QSslCertificate &cert, const QString &peerName) +{ + QStringList commonNameList = cert.subjectInfo(QSslCertificate::CommonName); + + foreach (const QString &commonName, commonNameList) { + if (isMatchingHostname(commonName.toLower(), peerName.toLower())) { + return true; + } + } + + foreach (const QString &altName, cert.alternateSubjectNames().values(QSsl::DnsEntry)) { + if (isMatchingHostname(altName.toLower(), peerName.toLower())) { + return true; + } + } + + return false; +} + bool QSslSocketBackendPrivate::isMatchingHostname(const QString &cn, const QString &hostname) { int wildcard = cn.indexOf(QLatin1Char('*')); @@ -1484,4 +1484,133 @@ bool QSslSocketBackendPrivate::isMatchingHostname(const QString &cn, const QStri return true; } +QList<QSslError> QSslSocketBackendPrivate::verify(QList<QSslCertificate> certificateChain, const QString &hostName) +{ + QList<QSslError> errors; + if (certificateChain.count() <= 0) { + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + // Setup the store with the default CA certificates + X509_STORE *certStore = q_X509_STORE_new(); + if (!certStore) { + qWarning() << "Unable to create certificate store"; + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + QList<QSslCertificate> expiredCerts; + + foreach (const QSslCertificate &caCertificate, QSslSocket::defaultCaCertificates()) { + // add expired certs later, so that the + // valid ones are used before the expired ones + if (!caCertificate.isValid()) { + expiredCerts.append(caCertificate); + } else { + q_X509_STORE_add_cert(certStore, (X509 *)caCertificate.handle()); + } + } + + bool addExpiredCerts = true; +#if defined(Q_OS_MAC) && (MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5) + //On Leopard SSL does not work if we add the expired certificates. + if (QSysInfo::MacintoshVersion == QSysInfo::MV_10_5) + addExpiredCerts = false; +#endif + // now add the expired certs + if (addExpiredCerts) { + foreach (const QSslCertificate &caCertificate, expiredCerts) { + q_X509_STORE_add_cert(certStore, (X509 *)caCertificate.handle()); + } + } + + _q_sslErrorList()->mutex.lock(); + + // Register a custom callback to get all verification errors. + X509_STORE_set_verify_cb_func(certStore, q_X509Callback); + + // Build the chain of intermediate certificates + STACK_OF(X509) *intermediates = 0; + if (certificateChain.length() > 1) { + intermediates = (STACK_OF(X509) *) q_sk_new_null(); + + if (!intermediates) { + q_X509_STORE_free(certStore); + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + bool first = true; + foreach (const QSslCertificate &cert, certificateChain) { + if (first) { + first = false; + continue; + } +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + q_sk_push( (_STACK *)intermediates, (X509 *)cert.handle()); +#else + q_sk_push( (STACK *)intermediates, (X509 *)cert.handle()); +#endif + } + } + + X509_STORE_CTX *storeContext = q_X509_STORE_CTX_new(); + if (!storeContext) { + q_X509_STORE_free(certStore); + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + if (!q_X509_STORE_CTX_init(storeContext, certStore, (X509 *)certificateChain[0].handle(), intermediates)) { + q_X509_STORE_CTX_free(storeContext); + q_X509_STORE_free(certStore); + errors << QSslError(QSslError::UnspecifiedError); + return errors; + } + + // Now we can actually perform the verification of the chain we have built. + // We ignore the result of this function since we process errors via the + // callback. + (void) q_X509_verify_cert(storeContext); + + q_X509_STORE_CTX_free(storeContext); +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + q_sk_free( (_STACK *) intermediates); +#else + q_sk_free( (STACK *) intermediates); +#endif + + // Now process the errors + const QList<QPair<int, int> > errorList = _q_sslErrorList()->errors; + _q_sslErrorList()->errors.clear(); + + _q_sslErrorList()->mutex.unlock(); + + // Translate the errors + if (QSslCertificatePrivate::isBlacklisted(certificateChain[0])) { + QSslError error(QSslError::CertificateBlacklisted, certificateChain[0]); + errors << error; + } + + // Check the certificate name against the hostname if one was specified + if ((!hostName.isEmpty()) && (!isMatchingHostname(certificateChain[0], hostName))) { + // No matches in common names or alternate names. + QSslError error(QSslError::HostNameMismatch, certificateChain[0]); + errors << error; + } + + // Translate errors from the error list into QSslErrors. + for (int i = 0; i < errorList.size(); ++i) { + const QPair<int, int> &errorAndDepth = errorList.at(i); + int err = errorAndDepth.first; + int depth = errorAndDepth.second; + errors << _q_OpenSSL_to_QSslError(err, certificateChain.value(depth)); + } + + q_X509_STORE_free(certStore); + + return errors; +} + QT_END_NAMESPACE diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h index 7e489a4eff..080ad0064c 100644 --- a/src/network/ssl/qsslsocket_openssl_p.h +++ b/src/network/ssl/qsslsocket_openssl_p.h @@ -120,7 +120,9 @@ public: static QSslCipher QSslCipher_from_SSL_CIPHER(SSL_CIPHER *cipher); static QList<QSslCertificate> STACKOFX509_to_QSslCertificates(STACK_OF(X509) *x509); + static bool isMatchingHostname(const QSslCertificate &cert, const QString &peerName); Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QString &cn, const QString &hostname); + static QList<QSslError> verify(QList<QSslCertificate> certificateChain, const QString &hostName); static QString getErrorsFromOpenSsl(); }; diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp index 31afab003f..be8da0eaf0 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -157,9 +157,13 @@ DEFINEFUNC(void, RSA_free, RSA *a, a, return, DUMMYARG) DEFINEFUNC(int, sk_num, STACK *a, a, return -1, return) DEFINEFUNC2(void, sk_pop_free, STACK *a, a, void (*b)(void*), b, return, DUMMYARG) #if OPENSSL_VERSION_NUMBER >= 0x10000000L +DEFINEFUNC(_STACK *, sk_new_null, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC2(void, sk_push, _STACK *a, a, void *b, b, return, DUMMYARG) DEFINEFUNC(void, sk_free, _STACK *a, a, return, DUMMYARG) DEFINEFUNC2(void *, sk_value, STACK *a, a, int b, b, return 0, return) #else +DEFINEFUNC(STACK *, sk_new_null, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC2(void, sk_push, STACK *a, a, void *b, b, return, DUMMYARG) DEFINEFUNC(void, sk_free, STACK *a, a, return, DUMMYARG) DEFINEFUNC2(char *, sk_value, STACK *a, a, int b, b, return 0, return) #endif @@ -710,6 +714,8 @@ bool q_resolveOpenSslSymbols() RESOLVEFUNC(RAND_seed) RESOLVEFUNC(RAND_status) RESOLVEFUNC(RSA_free) + RESOLVEFUNC(sk_new_null) + RESOLVEFUNC(sk_push) RESOLVEFUNC(sk_free) RESOLVEFUNC(sk_num) RESOLVEFUNC(sk_pop_free) diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h index cd3aa07bc0..a1db6d9320 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols_p.h +++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h @@ -263,9 +263,13 @@ void q_RSA_free(RSA *a); int q_sk_num(STACK *a); void q_sk_pop_free(STACK *a, void (*b)(void *)); #if OPENSSL_VERSION_NUMBER >= 0x10000000L +_STACK *q_sk_new_null(); +void q_sk_push(_STACK *st, void *data); void q_sk_free(_STACK *a); void * q_sk_value(STACK *a, int b); #else +STACK *q_sk_new_null(); +void q_sk_push(STACK *st, void *data); void q_sk_free(STACK *a); char * q_sk_value(STACK *a, int b); #endif |