From ecbf6dfbf5c70c7bbb840ea948699602fe14baac Mon Sep 17 00:00:00 2001 From: Andrew Knight Date: Wed, 27 Aug 2014 11:52:05 +0300 Subject: [PATCH] ssl: Share the host name matching utilities This moves the socket backend's host name matching functions up to QSslSocketPrivate so that they can be shared between backends. This works, as there is no OpenSSL-specific code here. Change-Id: I73c2081fdc2e60a44c90e90800d1e1877391a626 Reviewed-by: Richard J. Moore --- src/network/ssl/qsslsocket.cpp | 59 +++++++++++++++++++ src/network/ssl/qsslsocket_openssl.cpp | 59 ------------------- src/network/ssl/qsslsocket_openssl_p.h | 2 - src/network/ssl/qsslsocket_p.h | 2 + .../network/ssl/qsslsocket/tst_qsslsocket.cpp | 34 +++++------ 5 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp index 5df550b1c8..aacafd2d56 100644 --- a/src/network/ssl/qsslsocket.cpp +++ b/src/network/ssl/qsslsocket.cpp @@ -2513,6 +2513,65 @@ QSharedPointer QSslSocketPrivate::sslContext(QSslSocket *socket) return (socket) ? socket->d_func()->sslContextPointer : QSharedPointer(); } +bool QSslSocketPrivate::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.subjectAlternativeNames().values(QSsl::DnsEntry)) { + if (isMatchingHostname(altName.toLower(), peerName.toLower())) { + return true; + } + } + + return false; +} + +bool QSslSocketPrivate::isMatchingHostname(const QString &cn, const QString &hostname) +{ + int wildcard = cn.indexOf(QLatin1Char('*')); + + // Check this is a wildcard cert, if not then just compare the strings + if (wildcard < 0) + return cn == hostname; + + int firstCnDot = cn.indexOf(QLatin1Char('.')); + int secondCnDot = cn.indexOf(QLatin1Char('.'), firstCnDot+1); + + // Check at least 3 components + if ((-1 == secondCnDot) || (secondCnDot+1 >= cn.length())) + return false; + + // Check * is last character of 1st component (ie. there's a following .) + if (wildcard+1 != firstCnDot) + return false; + + // Check only one star + if (cn.lastIndexOf(QLatin1Char('*')) != wildcard) + return false; + + // Check characters preceding * (if any) match + if (wildcard && (hostname.leftRef(wildcard) != cn.leftRef(wildcard))) + return false; + + // Check characters following first . match + if (hostname.midRef(hostname.indexOf(QLatin1Char('.'))) != cn.midRef(firstCnDot)) + return false; + + // Check if the hostname is an IP address, if so then wildcards are not allowed + QHostAddress addr(hostname); + if (!addr.isNull()) + return false; + + // Ok, I guess this was a wildcard CN and the hostname matches. + return true; +} + QT_END_NAMESPACE #include "moc_qsslsocket.cpp" diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index f869039687..dc08954d6e 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -1552,65 +1552,6 @@ QList QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates return certificates; } -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.subjectAlternativeNames().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('*')); - - // Check this is a wildcard cert, if not then just compare the strings - if (wildcard < 0) - return cn == hostname; - - int firstCnDot = cn.indexOf(QLatin1Char('.')); - int secondCnDot = cn.indexOf(QLatin1Char('.'), firstCnDot+1); - - // Check at least 3 components - if ((-1 == secondCnDot) || (secondCnDot+1 >= cn.length())) - return false; - - // Check * is last character of 1st component (ie. there's a following .) - if (wildcard+1 != firstCnDot) - return false; - - // Check only one star - if (cn.lastIndexOf(QLatin1Char('*')) != wildcard) - return false; - - // Check characters preceding * (if any) match - if (wildcard && (hostname.leftRef(wildcard) != cn.leftRef(wildcard))) - return false; - - // Check characters following first . match - if (hostname.midRef(hostname.indexOf(QLatin1Char('.'))) != cn.midRef(firstCnDot)) - return false; - - // Check if the hostname is an IP address, if so then wildcards are not allowed - QHostAddress addr(hostname); - if (!addr.isNull()) - return false; - - // Ok, I guess this was a wildcard CN and the hostname matches. - return true; -} - QList QSslSocketBackendPrivate::verify(QList certificateChain, const QString &hostName) { QList errors; diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h index f4f2fe842c..3a1df7c420 100644 --- a/src/network/ssl/qsslsocket_openssl_p.h +++ b/src/network/ssl/qsslsocket_openssl_p.h @@ -142,8 +142,6 @@ public: Q_AUTOTEST_EXPORT static long setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions); static QSslCipher QSslCipher_from_SSL_CIPHER(SSL_CIPHER *cipher); static QList 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 verify(QList certificateChain, const QString &hostName); static QString getErrorsFromOpenSsl(); static bool importPKCS12(QIODevice *device, diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h index 06e12297a4..bda36d2649 100644 --- a/src/network/ssl/qsslsocket_p.h +++ b/src/network/ssl/qsslsocket_p.h @@ -150,6 +150,8 @@ public: QRegExp::PatternSyntax syntax); static void addDefaultCaCertificate(const QSslCertificate &cert); static void addDefaultCaCertificates(const QList &certs); + static bool isMatchingHostname(const QSslCertificate &cert, const QString &peerName); + Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QString &cn, const QString &hostname); #if defined(Q_OS_MACX) static PtrSecCertificateCopyData ptrSecCertificateCopyData; diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp index 30a9e19138..e9a27cd83b 100644 --- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp +++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp @@ -1462,25 +1462,25 @@ void tst_QSslSocket::systemCaCertificates() void tst_QSslSocket::wildcardCertificateNames() { // Passing CN matches - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("www.example.com"), QString("www.example.com")), true ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*.example.com"), QString("www.example.com")), true ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("xxx*.example.com"), QString("xxxwww.example.com")), true ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("f*.example.com"), QString("foo.example.com")), true ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("192.168.0.0"), QString("192.168.0.0")), true ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("www.example.com"), QString("www.example.com")), true ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*.example.com"), QString("www.example.com")), true ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("xxx*.example.com"), QString("xxxwww.example.com")), true ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("f*.example.com"), QString("foo.example.com")), true ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("192.168.0.0"), QString("192.168.0.0")), true ); // Failing CN matches - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("xxx.example.com"), QString("www.example.com")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*"), QString("www.example.com")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*.*.com"), QString("www.example.com")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*.example.com"), QString("baa.foo.example.com")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("f*.example.com"), QString("baa.example.com")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*.com"), QString("example.com")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*fail.com"), QString("example.com")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*.example."), QString("www.example.")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*.example."), QString("www.example")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString(""), QString("www")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*"), QString("www")), false ); - QCOMPARE( QSslSocketBackendPrivate::isMatchingHostname(QString("*.168.0.0"), QString("192.168.0.0")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("xxx.example.com"), QString("www.example.com")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*"), QString("www.example.com")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*.*.com"), QString("www.example.com")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*.example.com"), QString("baa.foo.example.com")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("f*.example.com"), QString("baa.example.com")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*.com"), QString("example.com")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*fail.com"), QString("example.com")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*.example."), QString("www.example.")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*.example."), QString("www.example")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString(""), QString("www")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*"), QString("www")), false ); + QCOMPARE( QSslSocketPrivate::isMatchingHostname(QString("*.168.0.0"), QString("192.168.0.0")), false ); } void tst_QSslSocket::wildcard()