summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorRichard Moore <rich@kde.org>2011-06-18 15:53:53 +0100
committerQt by Nokia <qt-info@nokia.com>2011-07-12 13:57:14 +0200
commit451f3b3785ea5d08f5092978b6ebe17f25ef7a88 (patch)
tree3f690085d0e7a0029aeeea97368cad583f5b2a1d /src/network
parentad35a41739c8e1fb6db62ed37b764448b2e59ece (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.cpp12
-rw-r--r--src/network/ssl/qsslcertificate.h3
-rw-r--r--src/network/ssl/qsslsocket_openssl.cpp181
-rw-r--r--src/network/ssl/qsslsocket_openssl_p.h2
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols.cpp6
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols_p.h4
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