diff options
Diffstat (limited to 'src/network/ssl/qsslsocket_openssl.cpp')
-rw-r--r-- | src/network/ssl/qsslsocket_openssl.cpp | 267 |
1 files changed, 109 insertions, 158 deletions
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp index cecb4fb753..ba02d13863 100644 --- a/src/network/ssl/qsslsocket_openssl.cpp +++ b/src/network/ssl/qsslsocket_openssl.cpp @@ -66,6 +66,10 @@ #include "qsslpresharedkeyauthenticator.h" #include "qsslpresharedkeyauthenticator_p.h" +#ifdef Q_OS_WIN +#include "qwindowscarootfetcher_p.h" +#endif + #include <QtCore/qdatetime.h> #include <QtCore/qdebug.h> #include <QtCore/qdir.h> @@ -77,15 +81,16 @@ #include <QtCore/qthread.h> #include <QtCore/qurl.h> #include <QtCore/qvarlengtharray.h> +#include <QtCore/qscopedvaluerollback.h> #include <string.h> QT_BEGIN_NAMESPACE #if defined(Q_OS_WIN) - PtrCertOpenSystemStoreW QSslSocketPrivate::ptrCertOpenSystemStoreW = 0; - PtrCertFindCertificateInStore QSslSocketPrivate::ptrCertFindCertificateInStore = 0; - PtrCertCloseStore QSslSocketPrivate::ptrCertCloseStore = 0; + PtrCertOpenSystemStoreW QSslSocketPrivate::ptrCertOpenSystemStoreW = nullptr; + PtrCertFindCertificateInStore QSslSocketPrivate::ptrCertFindCertificateInStore = nullptr; + PtrCertCloseStore QSslSocketPrivate::ptrCertCloseStore = nullptr; #endif bool QSslSocketPrivate::s_libraryLoaded = false; @@ -99,12 +104,13 @@ int QSslSocketBackendPrivate::s_indexForSSLExtraData = -1; QString QSslSocketBackendPrivate::getErrorsFromOpenSsl() { QString errorString; + char buf[256] = {}; // OpenSSL docs claim both 120 and 256; use the larger. unsigned long errNum; while ((errNum = q_ERR_get_error())) { - if (! errorString.isEmpty()) + if (!errorString.isEmpty()) errorString.append(QLatin1String(", ")); - const char *error = q_ERR_error_string(errNum, NULL); - errorString.append(QString::fromLatin1(error)); // error is ascii according to man ERR_error_string + q_ERR_error_string_n(errNum, buf, sizeof buf); + errorString.append(QString::fromLatin1(buf)); // error is ascii according to man ERR_error_string } return errorString; } @@ -134,10 +140,10 @@ static unsigned int q_ssl_psk_server_callback(SSL *ssl, } // extern "C" QSslSocketBackendPrivate::QSslSocketBackendPrivate() - : ssl(0), - readBio(0), - writeBio(0), - session(0) + : ssl(nullptr), + readBio(nullptr), + writeBio(nullptr), + session(nullptr) { // Calls SSL_library_init(). ensureInitialized(); @@ -188,8 +194,7 @@ QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(const SSL_CIPHER return ciph; } -// static -inline QSslErrorEntry QSslErrorEntry::fromStoreContext(X509_STORE_CTX *ctx) +QSslErrorEntry QSslErrorEntry::fromStoreContext(X509_STORE_CTX *ctx) { return { q_X509_STORE_CTX_get_error(ctx), @@ -242,6 +247,33 @@ int q_X509Callback(int ok, X509_STORE_CTX *ctx) return 1; } +static void q_loadCiphersForConnection(SSL *connection, QList<QSslCipher> &ciphers, + QList<QSslCipher> &defaultCiphers) +{ + Q_ASSERT(connection); + + STACK_OF(SSL_CIPHER) *supportedCiphers = q_SSL_get_ciphers(connection); + for (int i = 0; i < q_sk_SSL_CIPHER_num(supportedCiphers); ++i) { + if (SSL_CIPHER *cipher = q_sk_SSL_CIPHER_value(supportedCiphers, i)) { + QSslCipher ciph = QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(cipher); + if (!ciph.isNull()) { + // Unconditionally exclude ADH and AECDH ciphers since they offer no MITM protection + if (!ciph.name().toLower().startsWith(QLatin1String("adh")) && + !ciph.name().toLower().startsWith(QLatin1String("exp-adh")) && + !ciph.name().toLower().startsWith(QLatin1String("aecdh"))) { + ciphers << ciph; + + if (ciph.usedBits() >= 128) + defaultCiphers << ciph; + } + } + } + } +} + +// Defined in qsslsocket.cpp +void q_setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers); + long QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions) { long options; @@ -383,7 +415,7 @@ void QSslSocketBackendPrivate::destroySslContext() { if (ssl) { q_SSL_free(ssl); - ssl = 0; + ssl = nullptr; } sslContextPointer.clear(); } @@ -447,29 +479,28 @@ void QSslSocketPrivate::resetDefaultCiphers() QList<QSslCipher> ciphers; QList<QSslCipher> defaultCiphers; - STACK_OF(SSL_CIPHER) *supportedCiphers = q_SSL_get_ciphers(mySsl); - for (int i = 0; i < q_sk_SSL_CIPHER_num(supportedCiphers); ++i) { - if (SSL_CIPHER *cipher = q_sk_SSL_CIPHER_value(supportedCiphers, i)) { - QSslCipher ciph = QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(cipher); - if (!ciph.isNull()) { - // Unconditionally exclude ADH and AECDH ciphers since they offer no MITM protection - if (!ciph.name().toLower().startsWith(QLatin1String("adh")) && - !ciph.name().toLower().startsWith(QLatin1String("exp-adh")) && - !ciph.name().toLower().startsWith(QLatin1String("aecdh"))) { - ciphers << ciph; - - if (ciph.usedBits() >= 128) - defaultCiphers << ciph; - } - } - } - } + q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers); q_SSL_CTX_free(myCtx); q_SSL_free(mySsl); setDefaultSupportedCiphers(ciphers); setDefaultCiphers(defaultCiphers); + +#if QT_CONFIG(dtls) + ciphers.clear(); + defaultCiphers.clear(); + myCtx = q_SSL_CTX_new(q_DTLS_client_method()); + if (myCtx) { + mySsl = q_SSL_new(myCtx); + if (mySsl) { + q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers); + q_setDefaultDtlsCiphers(defaultCiphers); + q_SSL_free(mySsl); + } + q_SSL_CTX_free(myCtx); + } +#endif // dtls } void QSslSocketPrivate::resetDefaultEllipticCurves() @@ -615,6 +646,11 @@ void QSslSocketBackendPrivate::transmit() { Q_Q(QSslSocket); + using ScopedBool = QScopedValueRollback<bool>; + + if (inSetAndEmitError) + return; + // If we don't have any SSL context, don't bother transmitting. if (!ssl) return; @@ -642,6 +678,7 @@ void QSslSocketBackendPrivate::transmit() break; } else { // ### Better error handling. + const ScopedBool bg(inSetAndEmitError, true); setErrorAndEmit(QAbstractSocket::SslInternalError, QSslSocket::tr("Unable to write data: %1").arg( getErrorsFromOpenSsl())); @@ -687,6 +724,7 @@ void QSslSocketBackendPrivate::transmit() #endif if (actualWritten < 0) { //plain socket write fails if it was in the pending close state. + const ScopedBool bg(inSetAndEmitError, true); setErrorAndEmit(plainSocket->error(), plainSocket->errorString()); return; } @@ -712,6 +750,7 @@ void QSslSocketBackendPrivate::transmit() plainSocket->skip(writtenToBio); } else { // ### Better error handling. + const ScopedBool bg(inSetAndEmitError, true); setErrorAndEmit(QAbstractSocket::SslInternalError, QSslSocket::tr("Unable to decrypt data: %1").arg( getErrorsFromOpenSsl())); @@ -789,15 +828,21 @@ void QSslSocketBackendPrivate::transmit() qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: remote disconnect"; #endif shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves - setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, - QSslSocket::tr("The TLS/SSL connection has been closed")); + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(QAbstractSocket::RemoteHostClosedError, + QSslSocket::tr("The TLS/SSL connection has been closed")); + } return; 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 - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl())); + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl())); + } return; default: // SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: can only happen with a @@ -805,15 +850,18 @@ void QSslSocketBackendPrivate::transmit() // 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. - setErrorAndEmit(QAbstractSocket::SslInternalError, - QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl())); + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(QAbstractSocket::SslInternalError, + QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl())); + } break; } } while (ssl && readBytes > 0); } while (ssl && transmitting); } -static QSslError _q_OpenSSL_to_QSslError(int errorCode, const QSslCertificate &cert) +QSslError _q_OpenSSL_to_QSslError(int errorCode, const QSslCertificate &cert) { QSslError error; switch (errorCode) { @@ -862,12 +910,24 @@ static QSslError _q_OpenSSL_to_QSslError(int errorCode, const QSslCertificate &c return error; } +QString QSslSocketBackendPrivate::msgErrorsDuringHandshake() +{ + return QSslSocket::tr("Error during SSL handshake: %1") + .arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl()); +} + bool QSslSocketBackendPrivate::startHandshake() { Q_Q(QSslSocket); // Check if the connection has been established. Get all errors from the // verification stage. + + using ScopedBool = QScopedValueRollback<bool>; + + if (inSetAndEmitError) + return false; + QMutexLocker locker(&_q_sslErrorList()->mutex); _q_sslErrorList()->errors.clear(); int result = (mode == QSslSocket::SslClientMode) ? q_SSL_connect(ssl) : q_SSL_accept(ssl); @@ -897,12 +957,14 @@ bool QSslSocketBackendPrivate::startHandshake() // The handshake is not yet complete. break; default: - QString errorString - = QSslSocket::tr("Error during SSL handshake: %1").arg(getErrorsFromOpenSsl()); + QString errorString = QSslSocketBackendPrivate::msgErrorsDuringHandshake(); #ifdef QSSLSOCKET_DEBUG qCDebug(lcSsl) << "QSslSocketBackendPrivate::startHandshake: error!" << errorString; #endif - setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, errorString); + { + const ScopedBool bg(inSetAndEmitError, true); + setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, errorString); + } q->abort(); } return false; @@ -1172,119 +1234,6 @@ void QSslSocketBackendPrivate::_q_caRootLoaded(QSslCertificate cert, QSslCertifi } } -class QWindowsCaRootFetcherThread : public QThread -{ -public: - QWindowsCaRootFetcherThread() - { - qRegisterMetaType<QSslCertificate>(); - setObjectName(QStringLiteral("QWindowsCaRootFetcher")); - start(); - } - ~QWindowsCaRootFetcherThread() - { - quit(); - wait(15500); // worst case, a running request can block for 15 seconds - } -}; - -Q_GLOBAL_STATIC(QWindowsCaRootFetcherThread, windowsCaRootFetcherThread); - -QWindowsCaRootFetcher::QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode) - : cert(certificate), mode(sslMode) -{ - moveToThread(windowsCaRootFetcherThread()); -} - -QWindowsCaRootFetcher::~QWindowsCaRootFetcher() -{ -} - -void QWindowsCaRootFetcher::start() -{ - QByteArray der = cert.toDer(); - PCCERT_CONTEXT wincert = CertCreateCertificateContext(X509_ASN_ENCODING, (const BYTE *)der.constData(), der.length()); - if (!wincert) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl, "QWindowsCaRootFetcher failed to convert certificate to windows form"); -#endif - emit finished(cert, QSslCertificate()); - deleteLater(); - return; - } - - CERT_CHAIN_PARA parameters; - memset(¶meters, 0, sizeof(parameters)); - parameters.cbSize = sizeof(parameters); - // set key usage constraint - parameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; - parameters.RequestedUsage.Usage.cUsageIdentifier = 1; - LPSTR oid = (LPSTR)(mode == QSslSocket::SslClientMode ? szOID_PKIX_KP_SERVER_AUTH : szOID_PKIX_KP_CLIENT_AUTH); - parameters.RequestedUsage.Usage.rgpszUsageIdentifier = &oid; - -#ifdef QSSLSOCKET_DEBUG - QElapsedTimer stopwatch; - stopwatch.start(); -#endif - PCCERT_CHAIN_CONTEXT chain; - BOOL result = CertGetCertificateChain( - 0, //default engine - wincert, - 0, //current date/time - 0, //default store - ¶meters, - 0, //default dwFlags - 0, //reserved - &chain); -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QWindowsCaRootFetcher" << stopwatch.elapsed() << "ms to get chain"; -#endif - - QSslCertificate trustedRoot; - if (result) { -#ifdef QSSLSOCKET_DEBUG - qCDebug(lcSsl) << "QWindowsCaRootFetcher - examining windows chains"; - if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR) - qCDebug(lcSsl) << " - TRUSTED"; - else - qCDebug(lcSsl) << " - NOT TRUSTED" << chain->TrustStatus.dwErrorStatus; - if (chain->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED) - qCDebug(lcSsl) << " - SELF SIGNED"; - qCDebug(lcSsl) << "QSslSocketBackendPrivate::fetchCaRootForCert - dumping simple chains"; - for (unsigned int i = 0; i < chain->cChain; i++) { - if (chain->rgpChain[i]->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR) - qCDebug(lcSsl) << " - TRUSTED SIMPLE CHAIN" << i; - else - qCDebug(lcSsl) << " - UNTRUSTED SIMPLE CHAIN" << i << "reason:" << chain->rgpChain[i]->TrustStatus.dwErrorStatus; - for (unsigned int j = 0; j < chain->rgpChain[i]->cElement; j++) { - QSslCertificate foundCert(QByteArray((const char *)chain->rgpChain[i]->rgpElement[j]->pCertContext->pbCertEncoded - , chain->rgpChain[i]->rgpElement[j]->pCertContext->cbCertEncoded), QSsl::Der); - qCDebug(lcSsl) << " - " << foundCert; - } - } - qCDebug(lcSsl) << " - and" << chain->cLowerQualityChainContext << "low quality chains"; //expect 0, we haven't asked for them -#endif - - //based on http://msdn.microsoft.com/en-us/library/windows/desktop/aa377182%28v=vs.85%29.aspx - //about the final chain rgpChain[cChain-1] which must begin with a trusted root to be valid - if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR - && chain->cChain > 0) { - const PCERT_SIMPLE_CHAIN finalChain = chain->rgpChain[chain->cChain - 1]; - // http://msdn.microsoft.com/en-us/library/windows/desktop/aa377544%28v=vs.85%29.aspx - // rgpElement[0] is the end certificate chain element. rgpElement[cElement-1] is the self-signed "root" certificate element. - if (finalChain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR - && finalChain->cElement > 0) { - trustedRoot = QSslCertificate(QByteArray((const char *)finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->pbCertEncoded - , finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->cbCertEncoded), QSsl::Der); - } - } - CertFreeCertificateChain(chain); - } - CertFreeCertificateContext(wincert); - - emit finished(cert, trustedRoot); - deleteLater(); -} #endif void QSslSocketBackendPrivate::disconnectFromHost() @@ -1401,7 +1350,7 @@ QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> & q_X509_STORE_set_verify_cb(certStore, q_X509Callback); // Build the chain of intermediate certificates - STACK_OF(X509) *intermediates = 0; + STACK_OF(X509) *intermediates = nullptr; if (certificateChain.length() > 1) { intermediates = (STACK_OF(X509) *) q_OPENSSL_sk_new_null(); @@ -1494,9 +1443,10 @@ bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device, BIO *bio = q_BIO_new_mem_buf(const_cast<char *>(pkcs12data.constData()), pkcs12data.size()); // Create the PKCS#12 object - PKCS12 *p12 = q_d2i_PKCS12_bio(bio, 0); + PKCS12 *p12 = q_d2i_PKCS12_bio(bio, nullptr); if (!p12) { - qCWarning(lcSsl, "Unable to read PKCS#12 structure, %s", q_ERR_error_string(q_ERR_get_error(), 0)); + qCWarning(lcSsl, "Unable to read PKCS#12 structure, %s", + q_ERR_error_string(q_ERR_get_error(), nullptr)); q_BIO_free(bio); return false; } @@ -1504,10 +1454,11 @@ bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device, // Extract the data EVP_PKEY *pkey = nullptr; X509 *x509; - STACK_OF(X509) *ca = 0; + STACK_OF(X509) *ca = nullptr; if (!q_PKCS12_parse(p12, passPhrase.constData(), &pkey, &x509, &ca)) { - qCWarning(lcSsl, "Unable to parse PKCS#12 structure, %s", q_ERR_error_string(q_ERR_get_error(), 0)); + qCWarning(lcSsl, "Unable to parse PKCS#12 structure, %s", + q_ERR_error_string(q_ERR_get_error(), nullptr)); q_PKCS12_free(p12); q_BIO_free(bio); return false; |