diff -r 5dc02b23752f -r 3e2da88830cd src/network/ssl/qsslsocket_openssl.cpp --- 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 #include #include - -static void initNetworkResources() -{ - // Initialize resources - Q_INIT_RESOURCE(network); -} +#include // 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 +#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 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 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 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(); + 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(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(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