summaryrefslogtreecommitdiffstats
path: root/src/network/ssl/qsslsocket_openssl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/ssl/qsslsocket_openssl.cpp')
-rw-r--r--src/network/ssl/qsslsocket_openssl.cpp272
1 files changed, 114 insertions, 158 deletions
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
index 11f8a40199..37bb3e4933 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();
}
@@ -442,34 +474,38 @@ void QSslSocketPrivate::resetDefaultCiphers()
#else
SSL_CTX *myCtx = q_SSL_CTX_new(q_SSLv23_client_method());
#endif
+ // Note, we assert, not just silently return/bail out early:
+ // this should never happen and problems with OpenSSL's initialization
+ // must be caught before this (see supportsSsl()).
+ Q_ASSERT(myCtx);
SSL *mySsl = q_SSL_new(myCtx);
+ Q_ASSERT(mySsl);
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 +651,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 +683,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()));
@@ -688,6 +730,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;
}
@@ -713,6 +756,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()));
@@ -794,15 +838,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
@@ -810,15 +860,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) {
@@ -867,12 +920,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);
@@ -902,12 +967,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;
@@ -1177,119 +1244,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(&parameters, 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
- &parameters,
- 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()
@@ -1406,7 +1360,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();
@@ -1499,9 +1453,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;
}
@@ -1509,10 +1464,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;