src/network/ssl/qsslsocket_openssl.cpp
changeset 33 3e2da88830cd
parent 30 5dc02b23752f
child 37 758a864f9613
--- a/src/network/ssl/qsslsocket_openssl.cpp	Tue Jul 06 15:10:48 2010 +0300
+++ b/src/network/ssl/qsslsocket_openssl.cpp	Wed Aug 18 10:37:55 2010 +0300
@@ -57,15 +57,26 @@
 #include <QtCore/qthread.h>
 #include <QtCore/qurl.h>
 #include <QtCore/qvarlengtharray.h>
-
-static void initNetworkResources()
-{
-    // Initialize resources
-    Q_INIT_RESOURCE(network);
-}
+#include <QLibrary> // for loading the security lib for the CA store
 
 QT_BEGIN_NAMESPACE
 
+#if defined(Q_OS_MAC)
+#define kSecTrustSettingsDomainSystem 2 // so we do not need to include the header file
+    PtrSecCertificateGetData QSslSocketPrivate::ptrSecCertificateGetData = 0;
+    PtrSecTrustSettingsCopyCertificates QSslSocketPrivate::ptrSecTrustSettingsCopyCertificates = 0;
+    PtrSecTrustCopyAnchorCertificates QSslSocketPrivate::ptrSecTrustCopyAnchorCertificates = 0;
+#elif defined(Q_OS_WIN)
+    PtrCertOpenSystemStoreW QSslSocketPrivate::ptrCertOpenSystemStoreW = 0;
+    PtrCertFindCertificateInStore QSslSocketPrivate::ptrCertFindCertificateInStore = 0;
+    PtrCertCloseStore QSslSocketPrivate::ptrCertCloseStore = 0;
+#elif defined(Q_OS_SYMBIAN)
+#include <QtCore/private/qcore_symbian_p.h>
+#endif
+
+bool QSslSocketPrivate::s_libraryLoaded = false;
+bool QSslSocketPrivate::s_loadedCiphersAndCerts = false;
+
 // Useful defines
 #define SSL_ERRORSTR() QString::fromLocal8Bit(q_ERR_error_string(q_ERR_get_error(), NULL))
 
@@ -188,7 +199,7 @@
             ciph.d->protocol = QSsl::SslV2;
         else if (protoString == QLatin1String("TLSv1"))
             ciph.d->protocol = QSsl::TlsV1;
-        
+
         if (descriptionList.at(2).startsWith(QLatin1String("Kx=")))
             ciph.d->keyExchangeMethod = descriptionList.at(2).mid(3);
         if (descriptionList.at(3).startsWith(QLatin1String("Au=")))
@@ -290,8 +301,20 @@
     }
 
     // Add all our CAs to this store.
-    foreach (const QSslCertificate &caCertificate, q->caCertificates())
+    QList<QSslCertificate> expiredCerts;
+    foreach (const QSslCertificate &caCertificate, q->caCertificates()) {
+        // 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(ctx->cert_store, (X509 *)caCertificate.handle());
+        }
+    }
+    // now add the expired certs
+    foreach (const QSslCertificate &caCertificate, expiredCerts) {
         q_X509_STORE_add_cert(ctx->cert_store, (X509 *)caCertificate.handle());
+    }
 
     // Register a custom callback to get all verification errors.
     X509_STORE_set_verify_cb_func(ctx->cert_store, q_X509Callback);
@@ -344,7 +367,7 @@
     // Set verification depth.
     if (configuration.peerVerifyDepth != 0)
         q_SSL_CTX_set_verify_depth(ctx, configuration.peerVerifyDepth);
-    
+
     // Create and initialize SSL session
     if (!(ssl = q_SSL_new(ctx))) {
         // ### Bad error code
@@ -392,22 +415,24 @@
 /*!
     \internal
 
-    Declared static in QSslSocketPrivate, makes sure the SSL libraries have
-    been initialized.
+    Does the minimum amount of initialization to determine whether SSL
+    is supported or not.
 */
-bool QSslSocketPrivate::ensureInitialized()
+
+bool QSslSocketPrivate::supportsSsl()
+{
+    return ensureLibraryLoaded();
+}
+
+bool QSslSocketPrivate::ensureLibraryLoaded()
 {
     if (!q_resolveOpenSslSymbols())
         return false;
 
     // Check if the library itself needs to be initialized.
     QMutexLocker locker(openssl_locks()->initLock());
-    static int q_initialized = false;
-    if (!q_initialized) {
-        q_initialized = true;
-
-        // Initialize resources
-        initNetworkResources();
+    if (!s_libraryLoaded) {
+        s_libraryLoaded = true;
 
         // Initialize OpenSSL.
         q_CRYPTO_set_id_callback(id_function);
@@ -444,13 +469,72 @@
             if (!attempts)
                 return false;
         }
-
-        resetDefaultCiphers();
-        setDefaultCaCertificates(systemCaCertificates());
     }
     return true;
 }
 
+void QSslSocketPrivate::ensureCiphersAndCertsLoaded()
+{
+    QMutexLocker locker(openssl_locks()->initLock());
+    if (s_loadedCiphersAndCerts)
+        return;
+    s_loadedCiphersAndCerts = true;
+
+    resetDefaultCiphers();
+
+    //load symbols needed to receive certificates from system store
+#if defined(Q_OS_MAC)
+    QLibrary securityLib("/System/Library/Frameworks/Security.framework/Versions/Current/Security");
+    if (securityLib.load()) {
+        ptrSecCertificateGetData = (PtrSecCertificateGetData) securityLib.resolve("SecCertificateGetData");
+        if (!ptrSecCertificateGetData)
+            qWarning("could not resolve symbols in security library"); // should never happen
+
+        ptrSecTrustSettingsCopyCertificates = (PtrSecTrustSettingsCopyCertificates) securityLib.resolve("SecTrustSettingsCopyCertificates");
+        if (!ptrSecTrustSettingsCopyCertificates) { // method was introduced in Leopard, use legacy method if it's not there
+            ptrSecTrustCopyAnchorCertificates = (PtrSecTrustCopyAnchorCertificates) securityLib.resolve("SecTrustCopyAnchorCertificates");
+            if (!ptrSecTrustCopyAnchorCertificates)
+                qWarning("could not resolve symbols in security library"); // should never happen
+        }
+    } else {
+        qWarning("could not load security library");
+    }
+#elif defined(Q_OS_WIN)
+    HINSTANCE hLib = LoadLibraryW(L"Crypt32");
+    if (hLib) {
+#if defined(Q_OS_WINCE)
+        ptrCertOpenSystemStoreW = (PtrCertOpenSystemStoreW)GetProcAddress(hLib, L"CertOpenStore");
+        ptrCertFindCertificateInStore = (PtrCertFindCertificateInStore)GetProcAddress(hLib, L"CertFindCertificateInStore");
+        ptrCertCloseStore = (PtrCertCloseStore)GetProcAddress(hLib, L"CertCloseStore");
+#else
+        ptrCertOpenSystemStoreW = (PtrCertOpenSystemStoreW)GetProcAddress(hLib, "CertOpenSystemStoreW");
+        ptrCertFindCertificateInStore = (PtrCertFindCertificateInStore)GetProcAddress(hLib, "CertFindCertificateInStore");
+        ptrCertCloseStore = (PtrCertCloseStore)GetProcAddress(hLib, "CertCloseStore");
+#endif
+        if (!ptrCertOpenSystemStoreW || !ptrCertFindCertificateInStore || !ptrCertCloseStore)
+            qWarning("could not resolve symbols in crypt32 library"); // should never happen
+    } else {
+        qWarning("could not load crypt32 library"); // should never happen
+    }
+#endif
+    setDefaultCaCertificates(systemCaCertificates());
+}
+
+/*!
+    \internal
+
+    Declared static in QSslSocketPrivate, makes sure the SSL libraries have
+    been initialized.
+*/
+
+void QSslSocketPrivate::ensureInitialized()
+{
+    if (!supportsSsl())
+        return;
+
+    ensureCiphersAndCertsLoaded();
+}
+
 /*!
     \internal
 
@@ -484,15 +568,218 @@
     setDefaultCiphers(ciphers);
 }
 
+#if defined(Q_OS_SYMBIAN)
+
+QCertificateRetriever::QCertificateRetriever(QCertificateConsumer* parent)
+    : CActive(EPriorityStandard)
+    , certStore(0)
+    , certFilter(0)
+    , consumer(parent)
+    , currentCertificateIndex(0)
+    , certDescriptor(0, 0)
+{
+    CActiveScheduler::Add(this);
+    QT_TRAP_THROWING(certStore = CUnifiedCertStore::NewL(qt_s60GetRFs(), EFalse));
+    QT_TRAP_THROWING(certFilter = CCertAttributeFilter::NewL());
+    certFilter->SetFormat(EX509Certificate);
+}
+
+QCertificateRetriever::~QCertificateRetriever()
+{
+    delete certFilter;
+    delete certStore;
+    Cancel();
+}
+
+void QCertificateRetriever::fetch()
+{
+    certStore->Initialize(iStatus);
+    state = Initializing;
+    SetActive();
+}
+
+void QCertificateRetriever::list()
+{
+    certStore->List(certs, *certFilter, iStatus);
+    state = Listing;
+    SetActive();
+}
+
+void QCertificateRetriever::retrieveNextCertificate()
+{
+    CCTCertInfo* cert = certs[currentCertificateIndex];
+    currentCertificate.resize(cert->Size());
+    certDescriptor.Set((TUint8*)currentCertificate.data(), 0, currentCertificate.size());
+    certStore->Retrieve(*cert, certDescriptor, iStatus);
+    state = RetrievingCertificates;
+    SetActive();
+}
+
+void QCertificateRetriever::RunL()
+{
+    QT_TRYCATCH_LEAVING(run());
+}
+
+void QCertificateRetriever::run()
+{
+    switch (state) {
+    case Initializing:
+        list();
+        break;
+    case Listing:
+        currentCertificateIndex = 0;
+        retrieveNextCertificate();
+        break;
+    case RetrievingCertificates:
+        consumer->addEncodedCertificate(currentCertificate);
+        currentCertificate = QByteArray();
+
+        currentCertificateIndex++;
+
+        if (currentCertificateIndex < certs.Count())
+            retrieveNextCertificate();
+        else
+            consumer->finish();
+        break;
+    }
+}
+
+void QCertificateRetriever::DoCancel()
+{
+    switch (state) {
+    case Initializing:
+        certStore->CancelInitialize();
+        break;
+    case Listing:
+        certStore->CancelList();
+        break;
+    case RetrievingCertificates:
+        certStore->CancelRetrieve();
+        break;
+    }
+}
+
+QCertificateConsumer::QCertificateConsumer(QObject* parent)
+    : QObject(parent)
+    , retriever(0)
+{
+}
+
+QCertificateConsumer::~QCertificateConsumer()
+{
+    delete retriever;
+}
+
+void QCertificateConsumer::finish()
+{
+    delete retriever;
+    retriever = 0;
+    emit finished();
+}
+
+void QCertificateConsumer::start()
+{
+    retriever = new QCertificateRetriever(this);
+    Q_CHECK_PTR(retriever);
+    retriever->fetch();
+}
+
+#endif // defined(Q_OS_SYMBIAN)
+
 QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
 {
-    // Qt provides a default bundle of certificates
-    QFile caBundle(QLatin1String(":/trolltech/network/ssl/qt-ca-bundle.crt"));
-    if (caBundle.open(QIODevice::ReadOnly | QIODevice::Text))
-        return QSslCertificate::fromDevice(&caBundle);
+    ensureInitialized();
+    QList<QSslCertificate> systemCerts;
+#if defined(Q_OS_MAC)
+    CFArrayRef cfCerts;
+    OSStatus status = 1;
+
+    OSStatus SecCertificateGetData (
+       SecCertificateRef certificate,
+       CSSM_DATA_PTR data
+    );
 
-    // Unreachable; return no bundle.
-    return QList<QSslCertificate>();
+    if (ptrSecCertificateGetData) {
+        if (ptrSecTrustSettingsCopyCertificates)
+            status = ptrSecTrustSettingsCopyCertificates(kSecTrustSettingsDomainSystem, &cfCerts);
+        else if (ptrSecTrustCopyAnchorCertificates)
+            status = ptrSecTrustCopyAnchorCertificates(&cfCerts);
+        if (!status) {
+            CFIndex size = CFArrayGetCount(cfCerts);
+            for (CFIndex i = 0; i < size; ++i) {
+                SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i);
+                CSSM_DATA data;
+                CSSM_DATA_PTR dataPtr = &data;
+                if (ptrSecCertificateGetData(cfCert, dataPtr)) {
+                    qWarning("error retrieving a CA certificate from the system store");
+                } else {
+                    int len = data.Length;
+                    char *rawData = reinterpret_cast<char *>(data.Data);
+                    QByteArray rawCert(rawData, len);
+                    systemCerts.append(QSslCertificate::fromData(rawCert, QSsl::Der));
+                }
+            }
+        }
+        else {
+           // no detailed error handling here
+           qWarning("could not retrieve system CA certificates");
+        }
+    }
+#elif defined(Q_OS_WIN)
+    if (ptrCertOpenSystemStoreW && ptrCertFindCertificateInStore && ptrCertCloseStore) {
+        HCERTSTORE hSystemStore;
+#if defined(Q_OS_WINCE)
+        hSystemStore = ptrCertOpenSystemStoreW(CERT_STORE_PROV_SYSTEM_W,
+                                               0,
+                                               0,
+                                               CERT_STORE_NO_CRYPT_RELEASE_FLAG|CERT_SYSTEM_STORE_CURRENT_USER,
+                                               L"ROOT");
+#else
+        hSystemStore = ptrCertOpenSystemStoreW(0, L"ROOT");
+#endif
+        if(hSystemStore) {
+            PCCERT_CONTEXT pc = NULL;
+            while(1) {
+                pc = ptrCertFindCertificateInStore( hSystemStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, pc);
+                if(!pc)
+                    break;
+                QByteArray der((const char *)(pc->pbCertEncoded), static_cast<int>(pc->cbCertEncoded));
+                QSslCertificate cert(der, QSsl::Der);
+                systemCerts.append(cert);
+            }
+            ptrCertCloseStore(hSystemStore, 0);
+        }
+    }
+#elif defined(Q_OS_UNIX)
+    systemCerts.append(QSslCertificate::fromPath(QLatin1String("/var/ssl/certs/*.pem"), QSsl::Pem, QRegExp::Wildcard)); // AIX
+    systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/local/ssl/certs/*.pem"), QSsl::Pem, QRegExp::Wildcard)); // Solaris
+    systemCerts.append(QSslCertificate::fromPath(QLatin1String("/opt/openssl/certs/*.pem"), QSsl::Pem, QRegExp::Wildcard)); // HP-UX
+    systemCerts.append(QSslCertificate::fromPath(QLatin1String("/etc/ssl/certs/*.pem"), QSsl::Pem, QRegExp::Wildcard)); // (K)ubuntu, OpenSUSE, Mandriva, ...
+    systemCerts.append(QSslCertificate::fromPath(QLatin1String("/etc/pki/tls/certs/ca-bundle.crt"), QSsl::Pem)); // Fedora
+    systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/lib/ssl/certs/*.pem"), QSsl::Pem, QRegExp::Wildcard)); // Gentoo, Mandrake
+    systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/share/ssl/*.pem"), QSsl::Pem, QRegExp::Wildcard)); // Centos, Redhat, SuSE
+    systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/local/ssl/*.pem"), QSsl::Pem, QRegExp::Wildcard)); // Normal OpenSSL Tarball
+#elif defined(Q_OS_SYMBIAN)
+    QThread* certThread = new QThread;
+
+    QCertificateConsumer *consumer = new QCertificateConsumer();
+    consumer->moveToThread(certThread);
+    QObject::connect(certThread, SIGNAL(started()), consumer, SLOT(start()));
+    QObject::connect(consumer, SIGNAL(finished()), certThread, SLOT(quit()), Qt::DirectConnection);
+
+    certThread->start();
+    certThread->wait();
+    foreach (const QByteArray &encodedCert, consumer->encodedCertificates()) {
+        QSslCertificate cert(encodedCert, QSsl::Der);
+        if (!cert.isNull())
+            systemCerts.append(cert);
+    }
+
+    delete consumer;
+    delete certThread;
+#endif
+
+    return systemCerts;
 }
 
 void QSslSocketBackendPrivate::startClientEncryption()
@@ -537,7 +824,7 @@
     bool transmitting;
     do {
         transmitting = false;
-        
+
         // If the connection is secure, we can transfer data from the write
         // buffer (in plain text) to the write BIO through SSL_write.
         if (connectionEncrypted && !writeBuffer.isEmpty()) {
@@ -684,8 +971,20 @@
 #endif
                 plainSocket->disconnectFromHost();
                 break;
+            case SSL_ERROR_SYSCALL: // some IO error
+            case SSL_ERROR_SSL: // error in the SSL library
+                // we do not know exactly what the error is, nor whether we can recover from it,
+                // so just return to prevent an endless loop in the outer "while" statement
+                q->setErrorString(QSslSocket::tr("Error while reading: %1").arg(SSL_ERRORSTR()));
+                q->setSocketError(QAbstractSocket::UnknownSocketError);
+                emit q->error(QAbstractSocket::UnknownSocketError);
+                return;
             default:
-                // ### Handle errors better.
+                // SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: can only happen with a
+                // BIO_s_connect() or BIO_s_accept(), which we do not call.
+                // SSL_ERROR_WANT_X509_LOOKUP: can only happen with a
+                // SSL_CTX_set_client_cert_cb(), which we do not call.
+                // So this default case should never be triggered.
                 q->setErrorString(QSslSocket::tr("Error while reading: %1").arg(SSL_ERRORSTR()));
                 q->setSocketError(QAbstractSocket::UnknownSocketError);
                 emit q->error(QAbstractSocket::UnknownSocketError);
@@ -819,17 +1118,16 @@
             QString peerName = (verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName);
             QString commonName = configuration.peerCertificate.subjectInfo(QSslCertificate::CommonName);
 
-            QRegExp regexp(commonName, Qt::CaseInsensitive, QRegExp::Wildcard);
-            if (!regexp.exactMatch(peerName)) {
+            if (!isMatchingHostname(commonName.toLower(), peerName.toLower())) {
                 bool matched = false;
                 foreach (const QString &altName, configuration.peerCertificate
                          .alternateSubjectNames().values(QSsl::DnsEntry)) {
-                    regexp.setPattern(altName);
-                    if (regexp.exactMatch(peerName)) {
+                    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);
@@ -959,4 +1257,39 @@
     return certificates;
 }
 
+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;
+
+    // Ok, I guess this was a wildcard CN and the hostname matches.
+    return true;
+}
+
 QT_END_NAMESPACE