summaryrefslogtreecommitdiffstats
path: root/src/network/ssl
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/ssl')
-rw-r--r--src/network/ssl/qdtls_openssl.cpp2
-rw-r--r--src/network/ssl/qssl.cpp3
-rw-r--r--src/network/ssl/qssl.h12
-rw-r--r--src/network/ssl/qsslconfiguration.cpp91
-rw-r--r--src/network/ssl/qsslconfiguration.h6
-rw-r--r--src/network/ssl/qsslconfiguration_p.h8
-rw-r--r--src/network/ssl/qsslcontext_openssl.cpp78
-rw-r--r--src/network/ssl/qsslsocket.cpp192
-rw-r--r--src/network/ssl/qsslsocket.h52
-rw-r--r--src/network/ssl/qsslsocket_mac.cpp54
-rw-r--r--src/network/ssl/qsslsocket_openssl.cpp304
-rw-r--r--src/network/ssl/qsslsocket_openssl_p.h13
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols.cpp8
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols_p.h4
-rw-r--r--src/network/ssl/qsslsocket_p.h1
-rw-r--r--src/network/ssl/qsslsocket_schannel.cpp13
-rw-r--r--src/network/ssl/qsslsocket_winrt.cpp6
-rw-r--r--src/network/ssl/ssl.pri8
18 files changed, 664 insertions, 191 deletions
diff --git a/src/network/ssl/qdtls_openssl.cpp b/src/network/ssl/qdtls_openssl.cpp
index 25a6c5f49c..36b4d572fd 100644
--- a/src/network/ssl/qdtls_openssl.cpp
+++ b/src/network/ssl/qdtls_openssl.cpp
@@ -1125,7 +1125,7 @@ qint64 QDtlsPrivateOpenSSL::writeDatagramEncrypted(QUdpSocket *socket,
// some errors can be just ignored (it's UDP, not TCP after all).
// Unlike QSslSocket we do not abort though.
QString description(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
- if (socket->error() != QAbstractSocket::UnknownSocketError && description.isEmpty()) {
+ if (socket->socketError() != QAbstractSocket::UnknownSocketError && description.isEmpty()) {
setDtlsError(QDtlsError::UnderlyingSocketError, socket->errorString());
} else {
setDtlsError(QDtlsError::TlsFatalError,
diff --git a/src/network/ssl/qssl.cpp b/src/network/ssl/qssl.cpp
index c9fa7f85d9..bfbe8eb90f 100644
--- a/src/network/ssl/qssl.cpp
+++ b/src/network/ssl/qssl.cpp
@@ -120,8 +120,6 @@ Q_LOGGING_CATEGORY(lcSsl, "qt.network.ssl");
Describes the protocol of the cipher.
- \value SslV3 SSLv3; not supported by QSslSocket.
- \value SslV2 SSLv2; not supported by QSslSocket.
\value TlsV1_0 TLSv1.0
\value TlsV1_0OrLater TLSv1.0 and later versions. This option is not available when using the WinRT backend due to platform limitations.
\value TlsV1 Obsolete, means the same as TlsV1_0
@@ -137,7 +135,6 @@ Q_LOGGING_CATEGORY(lcSsl, "qt.network.ssl");
\value TlsV1_3OrLater TLSv1.3 and later versions. (Since Qt 5.12)
\value UnknownProtocol The cipher's protocol cannot be determined.
\value AnyProtocol Any supported protocol. This value is used by QSslSocket only.
- \value TlsV1SslV3 Same as TlsV1_0.
\value SecureProtocols The default option, using protocols known to be secure.
*/
diff --git a/src/network/ssl/qssl.h b/src/network/ssl/qssl.h
index 42c7b5c56d..1fd2cf9c6d 100644
--- a/src/network/ssl/qssl.h
+++ b/src/network/ssl/qssl.h
@@ -1,6 +1,6 @@
/****************************************************************************
**
-** Copyright (C) 2016 The Qt Company Ltd.
+** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
@@ -77,17 +77,11 @@ namespace QSsl {
#endif
enum SslProtocol {
- SslV3,
- SslV2,
- TlsV1_0,
-#if QT_DEPRECATED_SINCE(5,0)
- TlsV1 = TlsV1_0,
-#endif
+ TlsV1_0 = 2,
TlsV1_1,
TlsV1_2,
AnyProtocol,
- TlsV1SslV3,
- SecureProtocols,
+ SecureProtocols = AnyProtocol + 2,
TlsV1_0OrLater,
TlsV1_1OrLater,
diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp
index 738c8d4ac5..b6199a2b16 100644
--- a/src/network/ssl/qsslconfiguration.cpp
+++ b/src/network/ssl/qsslconfiguration.cpp
@@ -222,7 +222,9 @@ bool QSslConfiguration::operator==(const QSslConfiguration &other) const
d->nextNegotiatedProtocol == other.d->nextNegotiatedProtocol &&
d->nextProtocolNegotiationStatus == other.d->nextProtocolNegotiationStatus &&
d->dtlsCookieEnabled == other.d->dtlsCookieEnabled &&
- d->ocspStaplingEnabled == other.d->ocspStaplingEnabled;
+ d->ocspStaplingEnabled == other.d->ocspStaplingEnabled &&
+ d->reportFromCallback == other.d->reportFromCallback &&
+ d->missingCertIsFatal == other.d->missingCertIsFatal;
}
/*!
@@ -267,7 +269,9 @@ bool QSslConfiguration::isNull() const
d->nextAllowedProtocols.isEmpty() &&
d->nextNegotiatedProtocol.isNull() &&
d->nextProtocolNegotiationStatus == QSslConfiguration::NextProtocolNegotiationNone &&
- d->ocspStaplingEnabled == false);
+ d->ocspStaplingEnabled == false &&
+ d->reportFromCallback == false &&
+ d->missingCertIsFatal == false);
}
/*!
@@ -1190,6 +1194,89 @@ bool QSslConfiguration::ocspStaplingEnabled() const
return d->ocspStaplingEnabled;
}
+/*!
+ \since 6.0
+
+ Returns true if a verification callback will emit QSslSocket::handshakeInterruptedOnError()
+ early, before concluding the handshake.
+
+ \note This function always returns false for all backends but OpenSSL.
+
+ \sa setHandshakeMustInterruptOnError(), QSslSocket::handshakeInterruptedOnError(), QSslSocket::continueInterruptedHandshake()
+*/
+bool QSslConfiguration::handshakeMustInterruptOnError() const
+{
+ return d->reportFromCallback;
+}
+
+/*!
+ \since 6.0
+
+ If \a interrupt is true and the underlying backend supports this option,
+ errors found during certificate verification are reported immediately
+ by emitting QSslSocket::handshakeInterruptedOnError(). This allows
+ to stop the unfinished handshake and send a proper alert message to
+ a peer. No special action is required from the application in this case.
+ QSslSocket will close the connection after sending the alert message.
+ If the application after inspecting the error wants to continue the
+ handshake, it must call QSslSocket::continueInterruptedHandshake()
+ from its slot function. The signal-slot connection must be direct.
+
+ \note When interrupting handshake is enabled, errors that would otherwise
+ be reported by QSslSocket::peerVerifyError() are instead only reported by
+ QSslSocket::handshakeInterruptedOnError().
+ \note Even if the handshake was continued, these errors will be
+ reported when emitting QSslSocket::sslErrors() signal (and thus must
+ be ignored in the corresponding function slot).
+
+ \sa handshakeMustInterruptOnError(), QSslSocket::handshakeInterruptedOnError(), QSslSocket::continueInterruptedHandshake()
+*/
+void QSslConfiguration::setHandshakeMustInterruptOnError(bool interrupt)
+{
+#if QT_CONFIG(openssl)
+ d->reportFromCallback = interrupt;
+#else
+ qCWarning(lcSsl, "This operation requires OpenSSL as TLS backend");
+#endif
+}
+
+/*!
+ \since 6.0
+
+ Returns true if errors with code QSslError::NoPeerCertificate
+ cannot be ignored.
+
+ \note Always returns false for all TLS backends but OpenSSL.
+
+ \sa QSslSocket::ignoreSslErrors(), setMissingCertificateIsFatal()
+*/
+bool QSslConfiguration::missingCertificateIsFatal() const
+{
+ return d->missingCertIsFatal;
+}
+
+/*!
+ \since 6.0
+
+ If \a cannotRecover is true, and verification mode in use is
+ QSslSocket::VerifyPeer or QSslSocket::AutoVerifyPeer (for a
+ client-side socket), the missing peer's certificate would be
+ treated as an unrecoverable error that cannot be ignored. A proper
+ alert message will be sent to the peer before closing the connection.
+
+ \note Only available if Qt was configured and built with OpenSSL backend.
+
+ \sa QSslSocket::ignoreSslErrors(), QSslSocket::PeerVerifyMode, missingCertificateIsFatal()
+*/
+void QSslConfiguration::setMissingCertificateIsFatal(bool cannotRecover)
+{
+#if QT_CONFIG(openssl)
+ d->missingCertIsFatal = cannotRecover;
+#else
+ qCWarning(lcSsl, "Handling a missing certificate as a fatal error requires an OpenSSL backend");
+#endif // openssl
+}
+
/*! \internal
*/
bool QSslConfigurationPrivate::peerSessionWasShared(const QSslConfiguration &configuration) {
diff --git a/src/network/ssl/qsslconfiguration.h b/src/network/ssl/qsslconfiguration.h
index ad6e23638f..dc4587a835 100644
--- a/src/network/ssl/qsslconfiguration.h
+++ b/src/network/ssl/qsslconfiguration.h
@@ -172,6 +172,12 @@ public:
static void setDefaultDtlsConfiguration(const QSslConfiguration &configuration);
#endif // dtls
+ bool handshakeMustInterruptOnError() const;
+ void setHandshakeMustInterruptOnError(bool interrupt);
+
+ bool missingCertificateIsFatal() const;
+ void setMissingCertificateIsFatal(bool cannotRecover);
+
void setOcspStaplingEnabled(bool enable);
bool ocspStaplingEnabled() const;
diff --git a/src/network/ssl/qsslconfiguration_p.h b/src/network/ssl/qsslconfiguration_p.h
index 83126bb9a0..6ee3490df6 100644
--- a/src/network/ssl/qsslconfiguration_p.h
+++ b/src/network/ssl/qsslconfiguration_p.h
@@ -149,6 +149,14 @@ public:
const bool ocspStaplingEnabled = false;
#endif
+#if QT_CONFIG(openssl)
+ bool reportFromCallback = false;
+ bool missingCertIsFatal = false;
+#else
+ const bool reportFromCallback = false;
+ const bool missingCertIsFatal = false;
+#endif // openssl
+
// in qsslsocket.cpp:
static QSslConfiguration defaultConfiguration();
static void setDefaultConfiguration(const QSslConfiguration &configuration);
diff --git a/src/network/ssl/qsslcontext_openssl.cpp b/src/network/ssl/qsslcontext_openssl.cpp
index 562aa4f518..574f48a2b5 100644
--- a/src/network/ssl/qsslcontext_openssl.cpp
+++ b/src/network/ssl/qsslcontext_openssl.cpp
@@ -56,6 +56,7 @@ QT_BEGIN_NAMESPACE
// defined in qsslsocket_openssl.cpp:
extern int q_X509Callback(int ok, X509_STORE_CTX *ctx);
+extern "C" int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx);
extern QString getErrorsFromOpenSsl();
#if QT_CONFIG(dtls)
@@ -286,42 +287,31 @@ void QSslContext::initSslContext(QSslContext *sslContext, QSslSocket::SslMode mo
bool unsupportedProtocol = false;
bool isDtls = false;
init_context:
- if (sslContext->sslConfiguration.protocol() == QSsl::SslV2) {
- // SSL 2 is no longer supported, but chosen deliberately -> error
- sslContext->ctx = nullptr;
- unsupportedProtocol = true;
- } else if (sslContext->sslConfiguration.protocol() == QSsl::SslV3) {
- // SSL 3 is no longer supported, but chosen deliberately -> error
- sslContext->ctx = nullptr;
- unsupportedProtocol = true;
- } else {
- switch (sslContext->sslConfiguration.protocol()) {
- case QSsl::DtlsV1_0:
- case QSsl::DtlsV1_0OrLater:
- case QSsl::DtlsV1_2:
- case QSsl::DtlsV1_2OrLater:
+ switch (sslContext->sslConfiguration.protocol()) {
+ case QSsl::DtlsV1_0:
+ case QSsl::DtlsV1_0OrLater:
+ case QSsl::DtlsV1_2:
+ case QSsl::DtlsV1_2OrLater:
#if QT_CONFIG(dtls)
- isDtls = true;
- sslContext->ctx = q_SSL_CTX_new(client ? q_DTLS_client_method() : q_DTLS_server_method());
+ isDtls = true;
+ sslContext->ctx = q_SSL_CTX_new(client ? q_DTLS_client_method() : q_DTLS_server_method());
#else // dtls
- sslContext->ctx = nullptr;
- unsupportedProtocol = true;
- qCWarning(lcSsl, "DTLS protocol requested, but feature 'dtls' is disabled");
-
+ sslContext->ctx = nullptr;
+ unsupportedProtocol = true;
+ qCWarning(lcSsl, "DTLS protocol requested, but feature 'dtls' is disabled");
#endif // dtls
- break;
- case QSsl::TlsV1_3:
- case QSsl::TlsV1_3OrLater:
+ break;
+ case QSsl::TlsV1_3:
+ case QSsl::TlsV1_3OrLater:
#if !defined(TLS1_3_VERSION)
- qCWarning(lcSsl, "TLS 1.3 is not supported");
- sslContext->ctx = nullptr;
- unsupportedProtocol = true;
- break;
+ qCWarning(lcSsl, "TLS 1.3 is not supported");
+ sslContext->ctx = nullptr;
+ unsupportedProtocol = true;
+ break;
#endif // TLS1_3_VERSION
- default:
- // The ssl options will actually control the supported methods
- sslContext->ctx = q_SSL_CTX_new(client ? q_TLS_client_method() : q_TLS_server_method());
- }
+ default:
+ // The ssl options will actually control the supported methods
+ sslContext->ctx = q_SSL_CTX_new(client ? q_TLS_client_method() : q_TLS_server_method());
}
if (!sslContext->ctx) {
@@ -373,7 +363,6 @@ init_context:
#endif // TLS1_3_VERSION
break;
// Ranges:
- case QSsl::TlsV1SslV3:
case QSsl::AnyProtocol:
case QSsl::SecureProtocols:
case QSsl::TlsV1_0OrLater:
@@ -415,12 +404,6 @@ init_context:
Q_UNREACHABLE();
break;
#endif // TLS1_3_VERSION
- case QSsl::SslV2:
- case QSsl::SslV3:
- // These protocols are not supported, and we handle
- // them as an error (see the code above).
- Q_UNREACHABLE();
- break;
case QSsl::UnknownProtocol:
break;
}
@@ -589,11 +572,20 @@ init_context:
if (sslContext->sslConfiguration.peerVerifyMode() == QSslSocket::VerifyNone) {
q_SSL_CTX_set_verify(sslContext->ctx, SSL_VERIFY_NONE, nullptr);
} else {
- q_SSL_CTX_set_verify(sslContext->ctx, SSL_VERIFY_PEER,
-#if QT_CONFIG(dtls)
- isDtls ? dtlscallbacks::q_X509DtlsCallback :
-#endif // dtls
- q_X509Callback);
+ auto verificationCallback =
+ #if QT_CONFIG(dtls)
+ isDtls ? dtlscallbacks::q_X509DtlsCallback :
+ #endif // dtls
+ q_X509Callback;
+
+ if (!isDtls && configuration.handshakeMustInterruptOnError())
+ verificationCallback = q_X509CallbackDirect;
+
+ auto verificationMode = SSL_VERIFY_PEER;
+ if (!isDtls && sslContext->sslConfiguration.missingCertificateIsFatal())
+ verificationMode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+
+ q_SSL_CTX_set_verify(sslContext->ctx, verificationMode, verificationCallback);
}
#if QT_CONFIG(dtls)
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index 690251727d..504e27a463 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -228,6 +228,74 @@
*/
/*!
+ \enum QAlertLevel
+ \brief Describes the level of an alert message
+ \relates QSslSocket
+ \since 6.0
+
+ \ingroup network
+ \ingroup ssl
+ \inmodule QtNetwork
+
+ This enum describes the level of an alert message that was sent
+ or received.
+
+ \value Warning Non-fatal alert message
+ \value Fatal Fatal alert message, the underlying backend will
+ handle such an alert properly and close the connection.
+ \value Unknown An alert of unknown level of severity.
+*/
+
+/*!
+ \enum QAlertType
+ \brief Enumerates possible codes that an alert message can have
+ \relates QSslSocket
+ \since 6.0
+
+ \ingroup network
+ \ingroup ssl
+ \inmodule QtNetwork
+
+ See \l{https://tools.ietf.org/html/rfc8446#page-85}{RFC 8446, section 6}
+ for the possible values and their meaning.
+
+ \value CloseNotify,
+ \value UnexpectedMessage
+ \value BadRecordMac
+ \value RecordOverflow
+ \value DecompressionFailure
+ \value HandshakeFailure
+ \value NoCertificate
+ \value BadCertificate
+ \value UnsupportedCertificate
+ \value CertificateRevoked
+ \value CertificateExpired
+ \value CertificateUnknown
+ \value IllegalParameter
+ \value UnknownCa
+ \value AccessDenied
+ \value DecodeError
+ \value DecryptError
+ \value ExportRestriction
+ \value ProtocolVersion
+ \value InsufficientSecurity
+ \value InternalError
+ \value InappropriateFallback
+ \value UserCancelled
+ \value NoRenegotiation
+ \value MissingExtension
+ \value UnsupportedExtension
+ \value CertificateUnobtainable
+ \value UnrecognizedName
+ \value BadCertificateStatusResponse
+ \value BadCertificateHashValue
+ \value UnknownPskIdentity
+ \value CertificateRequired
+ \value NoApplicationProtocol
+ \value UnknownAlertMessage
+*/
+
+/*!
\fn void QSslSocket::encrypted()
This signal is emitted when QSslSocket enters encrypted mode. After this
@@ -289,7 +357,7 @@
If you want to continue connecting despite the errors that have occurred,
you must call QSslSocket::ignoreSslErrors() from inside a slot connected to
this signal. If you need to access the error list at a later point, you
- can call sslErrors() (without arguments).
+ can call sslHandshakeErrors().
\a errors contains one or more errors that prevent QSslSocket from
verifying the identity of the peer.
@@ -322,6 +390,48 @@
\sa QSslPreSharedKeyAuthenticator
*/
+/*!
+ \fn void QSslSocket::alertSent(QAlertLevel level, QAlertType type, const QString &description)
+
+ QSslSocket emits this signal if an alert message was sent to a peer. \a level
+ describes if it was a warning or a fatal error. \a type gives the code
+ of the alert message. When a textual description of the alert message is
+ available, it is supplied in \a description.
+
+ \note This signal is mostly informational and can be used for debugging
+ purposes, normally it does not require any actions from the application.
+ \note Not all backends support this functionality.
+
+ \sa alertReceived(), QAlertLevel, QAlertType
+*/
+
+/*!
+ \fn void QSslSocket::alertReceived(QAlertLevel level, QAlertType type, const QString &description)
+
+ QSslSocket emits this signal if an alert message was received from a peer.
+ \a level tells if the alert was fatal or it was a warning. \a type is the
+ code explaining why the alert was sent. When a textual description of
+ the alert message is available, it is supplied in \a description.
+
+ \note The signal is mostly for informational and debugging purposes and does not
+ require any handling in the application. If the alert was fatal, underlying
+ backend will handle it and close the connection.
+ \note Not all backends support this functionality.
+
+ \sa alertSent(), QAlertLevel, QAlertType
+*/
+
+/*!
+ \fn void QSslSocket::handshakeInterruptedOnError(const QSslError &error)
+
+ QSslSocket emits this signal if a certificate verification error was
+ found and if early error reporting was enabled in QSslConfiguration.
+ An application is expected to inspect the \a error and decide if
+ it wants to continue the handshake, or abort it and send an alert message
+ to the peer. The signal-slot connection must be direct.
+
+ \sa continueInterruptedHandshake(), sslErrors(), QSslConfiguration::setHandshakeMustInterruptOnError()
+*/
#include "qssl_p.h"
#include "qsslsocket.h"
#include "qsslcipher.h"
@@ -549,7 +659,7 @@ bool QSslSocket::setSocketDescriptor(qintptr socketDescriptor, SocketState state
d->createPlainSocket(openMode);
bool retVal = d->plainSocket->setSocketDescriptor(socketDescriptor, state, openMode);
d->cachedSocketDescriptor = d->plainSocket->socketDescriptor();
- d->setError(d->plainSocket->error(), d->plainSocket->errorString());
+ d->setError(d->plainSocket->socketError(), d->plainSocket->errorString());
setSocketState(state);
setOpenMode(openMode);
setLocalPort(d->plainSocket->localPort());
@@ -977,7 +1087,10 @@ void QSslSocket::setSslConfiguration(const QSslConfiguration &configuration)
#if QT_CONFIG(ocsp)
d->configuration.ocspStaplingEnabled = configuration.ocspStaplingEnabled();
#endif
-
+#if QT_CONFIG(openssl)
+ d->configuration.reportFromCallback = configuration.handshakeMustInterruptOnError();
+ d->configuration.missingCertIsFatal = configuration.missingCertificateIsFatal();
+#endif // openssl
// if the CA certificates were set explicitly (either via
// QSslConfiguration::setCaCertificates() or QSslSocket::setCaCertificates(),
// we cannot load the certificates on demand
@@ -1526,7 +1639,7 @@ QList<QSslCertificate> QSslSocket::caCertificates() const
default CA certificate database.
\sa QSslConfiguration::caCertificates(), QSslConfiguration::addCaCertificates(),
- QSslConfiguration::addDefaultCaCertificate()
+ QSslConfiguration::addCaCertificate()
*/
bool QSslSocket::addDefaultCaCertificates(const QString &path, QSsl::EncodingFormat encoding,
QRegExp::PatternSyntax syntax)
@@ -1543,7 +1656,7 @@ bool QSslSocket::addDefaultCaCertificates(const QString &path, QSsl::EncodingFor
SSL socket's CA certificate database is initialized to the default
CA certificate database.
- \sa QSslConfiguration::caCertificates(), QSslConfiguration::addCaCertificates()
+ \sa QSslConfiguration::addCaCertificates()
*/
void QSslSocket::addDefaultCaCertificate(const QSslCertificate &certificate)
{
@@ -1651,7 +1764,7 @@ bool QSslSocket::waitForConnected(int msecs)
bool retVal = d->plainSocket->waitForConnected(msecs);
if (!retVal) {
setSocketState(d->plainSocket->state());
- d->setError(d->plainSocket->error(), d->plainSocket->errorString());
+ d->setError(d->plainSocket->socketError(), d->plainSocket->errorString());
}
return retVal;
}
@@ -1820,21 +1933,42 @@ bool QSslSocket::waitForDisconnected(int msecs)
bool retVal = d->plainSocket->waitForDisconnected(qt_subtract_from_timeout(msecs, stopWatch.elapsed()));
if (!retVal) {
setSocketState(d->plainSocket->state());
- d->setError(d->plainSocket->error(), d->plainSocket->errorString());
+ d->setError(d->plainSocket->socketError(), d->plainSocket->errorString());
}
return retVal;
}
+#if QT_DEPRECATED_SINCE(5, 15)
/*!
+ \deprecated
+
+ Use sslHandshakeErrors() instead.
+
Returns a list of the last SSL errors that occurred. This is the
same list as QSslSocket passes via the sslErrors() signal. If the
connection has been encrypted with no errors, this function will
return an empty list.
- \sa connectToHostEncrypted()
+ \sa connectToHostEncrypted(), sslHandshakeErrors()
*/
QList<QSslError> QSslSocket::sslErrors() const
{
+ return sslHandshakeErrors();
+}
+#endif // QT_DEPRECATED_SINCE(5, 15)
+
+/*!
+ \since 5.15
+
+ Returns a list of the last SSL errors that occurred. This is the
+ same list as QSslSocket passes via the sslErrors() signal. If the
+ connection has been encrypted with no errors, this function will
+ return an empty list.
+
+ \sa connectToHostEncrypted()
+*/
+QList<QSslError> QSslSocket::sslHandshakeErrors() const
+{
Q_D(const QSslSocket);
return d->sslErrors;
}
@@ -2035,7 +2169,7 @@ void QSslSocket::ignoreSslErrors()
You can clear the list of errors you want to ignore by calling this
function with an empty list.
- \sa sslErrors()
+ \sa sslErrors(), sslHandshakeErrors()
*/
void QSslSocket::ignoreSslErrors(const QList<QSslError> &errors)
{
@@ -2043,6 +2177,23 @@ void QSslSocket::ignoreSslErrors(const QList<QSslError> &errors)
d->ignoreErrorsList = errors;
}
+
+/*!
+ \since 6.0
+
+ If an application wants to conclude a handshake even after receiving
+ handshakeInterruptedOnError() signal, it must call this function.
+ This call must be done from a slot function attached to the signal.
+ The signal-slot connection must be direct.
+
+ \sa handshakeInterruptedOnError(), QSslConfiguration::setHandshakeMustInterruptOnError()
+*/
+void QSslSocket::continueInterruptedHandshake()
+{
+ Q_D(QSslSocket);
+ d->handshakeInterrupted = false;
+}
+
/*!
\internal
*/
@@ -2217,13 +2368,24 @@ void QSslSocketPrivate::init()
*/
bool QSslSocketPrivate::verifyProtocolSupported(const char *where)
{
- if (configuration.protocol == QSsl::SslV2 || configuration.protocol == QSsl::SslV3) {
- qCWarning(lcSsl) << where << "Attempted to use an unsupported protocol.";
+ QLatin1String protocolName("DTLS");
+ switch (configuration.protocol) {
+ case QSsl::UnknownProtocol:
+ // UnknownProtocol, according to our docs, is for cipher whose protocol is unknown.
+ // Should not be used when configuring QSslSocket.
+ protocolName = QLatin1String("UnknownProtocol");
+ Q_FALLTHROUGH();
+ case QSsl::DtlsV1_0:
+ case QSsl::DtlsV1_2:
+ case QSsl::DtlsV1_0OrLater:
+ case QSsl::DtlsV1_2OrLater:
+ qCWarning(lcSsl) << where << "QSslConfiguration with unexpected protocol" << protocolName;
setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError,
QSslSocket::tr("Attempted to use an unsupported protocol."));
return false;
+ default:
+ return true;
}
- return true;
}
/*!
@@ -2434,6 +2596,10 @@ void QSslConfigurationPrivate::deepCopyDefaultConfiguration(QSslConfigurationPri
#if QT_CONFIG(ocsp)
ptr->ocspStaplingEnabled = global->ocspStaplingEnabled;
#endif
+#if QT_CONFIG(openssl)
+ ptr->reportFromCallback = global->reportFromCallback;
+ ptr->missingCertIsFatal = global->missingCertIsFatal;
+#endif
}
/*!
@@ -2668,7 +2834,7 @@ void QSslSocketPrivate::_q_errorSlot(QAbstractSocket::SocketError error)
readBufferMaxSize = tmpReadBufferMaxSize;
}
- setErrorAndEmit(plainSocket->error(), plainSocket->errorString());
+ setErrorAndEmit(plainSocket->socketError(), plainSocket->errorString());
}
/*!
diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h
index 843e2d15f5..c12bb53334 100644
--- a/src/network/ssl/qsslsocket.h
+++ b/src/network/ssl/qsslsocket.h
@@ -63,6 +63,49 @@ class QSslEllipticCurve;
class QSslPreSharedKeyAuthenticator;
class QOcspResponse;
+enum class QAlertLevel {
+ Warning,
+ Fatal,
+ Unknown
+};
+
+enum class QAlertType {
+ CloseNotify,
+ UnexpectedMessage = 10,
+ BadRecordMac = 20,
+ RecordOverflow = 22,
+ DecompressionFailure = 30, // reserved
+ HandshakeFailure = 40,
+ NoCertificate = 41, // reserved
+ BadCertificate = 42,
+ UnsupportedCertificate = 43,
+ CertificateRevoked = 44,
+ CertificateExpired = 45,
+ CertificateUnknown = 46,
+ IllegalParameter = 47,
+ UnknownCa = 48,
+ AccessDenied = 49,
+ DecodeError = 50,
+ DecryptError = 51,
+ ExportRestriction = 60, // reserved
+ ProtocolVersion = 70,
+ InsufficientSecurity = 71,
+ InternalError = 80,
+ InappropriateFallback = 86,
+ UserCancelled = 90,
+ NoRenegotiation = 100,
+ MissingExtension = 109,
+ UnsupportedExtension = 110,
+ CertificateUnobtainable = 111, // reserved
+ UnrecognizedName = 112,
+ BadCertificateStatusResponse = 113,
+ BadCertificateHashValue = 114, // reserved
+ UnknownPskIdentity = 115,
+ CertificateRequired = 116,
+ NoApplicationProtocol = 120,
+ UnknownAlertMessage = 255
+};
+
class QSslSocketPrivate;
class Q_NETWORK_EXPORT QSslSocket : public QTcpSocket
{
@@ -192,7 +235,10 @@ public:
bool waitForBytesWritten(int msecs = 30000) override;
bool waitForDisconnected(int msecs = 30000) override;
- QList<QSslError> sslErrors() const;
+#if QT_DEPRECATED_SINCE(5, 15)
+ QT_DEPRECATED_X("Use sslHandshakeErrors()") QList<QSslError> sslErrors() const;
+#endif // QT_DEPRECATED_SINCE(5, 15)
+ QList<QSslError> sslHandshakeErrors() const;
static bool supportsSsl();
static long sslLibraryVersionNumber();
@@ -201,6 +247,7 @@ public:
static QString sslLibraryBuildVersionString();
void ignoreSslErrors(const QList<QSslError> &errors);
+ void continueInterruptedHandshake();
public Q_SLOTS:
void startClientEncryption();
@@ -214,6 +261,9 @@ Q_SIGNALS:
void modeChanged(QSslSocket::SslMode newMode);
void encryptedBytesWritten(qint64 totalBytes);
void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator);
+ void alertSent(QAlertLevel level, QAlertType type, const QString &description);
+ void alertReceived(QAlertLevel level, QAlertType type, const QString &description);
+ void handshakeInterruptedOnError(const QSslError &error);
protected:
qint64 readData(char *data, qint64 maxlen) override;
diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qsslsocket_mac.cpp
index e0e065679d..fe1c43d992 100644
--- a/src/network/ssl/qsslsocket_mac.cpp
+++ b/src/network/ssl/qsslsocket_mac.cpp
@@ -496,10 +496,6 @@ QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const
}
switch (protocol) {
- case kSSLProtocol2:
- return QSsl::SslV2;
- case kSSLProtocol3:
- return QSsl::SslV3;
case kTLSProtocol1:
return QSsl::TlsV1_0;
case kTLSProtocol11:
@@ -657,23 +653,6 @@ QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSLCipherSuite(SSLCipherSui
QSslCipher ciph;
switch (cipher) {
// Sorted as in CipherSuite.h (and groupped by their RFC)
- case SSL_RSA_WITH_NULL_MD5:
- ciph.d->name = QLatin1String("NULL-MD5");
- ciph.d->protocol = QSsl::SslV3;
- break;
- case SSL_RSA_WITH_NULL_SHA:
- ciph.d->name = QLatin1String("NULL-SHA");
- ciph.d->protocol = QSsl::SslV3;
- break;
- case SSL_RSA_WITH_RC4_128_MD5:
- ciph.d->name = QLatin1String("RC4-MD5");
- ciph.d->protocol = QSsl::SslV3;
- break;
- case SSL_RSA_WITH_RC4_128_SHA:
- ciph.d->name = QLatin1String("RC4-SHA");
- ciph.d->protocol = QSsl::SslV3;
- break;
-
// TLS addenda using AES, per RFC 3268
case TLS_RSA_WITH_AES_128_CBC_SHA:
ciph.d->name = QLatin1String("AES128-SHA");
@@ -822,12 +801,8 @@ QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSLCipherSuite(SSLCipherSui
ciph.d->isNull = false;
// protocol
- if (ciph.d->protocol == QSsl::SslV3) {
- ciph.d->protocolString = QLatin1String("SSLv3");
- } else {
- ciph.d->protocol = QSsl::TlsV1_2;
- ciph.d->protocolString = QLatin1String("TLSv1.2");
- }
+ ciph.d->protocol = QSsl::TlsV1_2;
+ ciph.d->protocolString = QLatin1String("TLSv1.2");
const auto bits = ciph.d->name.splitRef(QLatin1Char('-'));
if (bits.size() >= 2) {
@@ -1106,22 +1081,6 @@ bool QSslSocketBackendPrivate::setSessionProtocol()
{
Q_ASSERT_X(context, Q_FUNC_INFO, "invalid SSL context (null)");
- // QSsl::SslV2 == kSSLProtocol2 is disabled in Secure Transport and
- // always fails with errSSLIllegalParam:
- // if (version < MINIMUM_STREAM_VERSION || version > MAXIMUM_STREAM_VERSION)
- // return errSSLIllegalParam;
- // where MINIMUM_STREAM_VERSION is SSL_Version_3_0, MAXIMUM_STREAM_VERSION is TLS_Version_1_2.
- if (configuration.protocol == QSsl::SslV2) {
- qCDebug(lcSsl) << "protocol QSsl::SslV2 is disabled";
- return false;
- }
-
- // SslV3 is unsupported.
- if (configuration.protocol == QSsl::SslV3) {
- qCDebug(lcSsl) << "protocol QSsl::SslV3 is disabled";
- return false;
- }
-
// SecureTransport has kTLSProtocol13 constant and also, kTLSProtocolMaxSupported.
// Calling SSLSetProtocolVersionMax/Min with any of these two constants results
// in errInvalidParam and a failure to set the protocol version. This means
@@ -1162,13 +1121,6 @@ bool QSslSocketBackendPrivate::setSessionProtocol()
qCDebug(lcSsl) << plainSocket << "requesting : any";
#endif
err = SSLSetProtocolVersionMin(context, kTLSProtocol1);
- } else if (configuration.protocol == QSsl::TlsV1SslV3) {
- #ifdef QSSLSOCKET_DEBUG
- qCDebug(lcSsl) << plainSocket << "requesting : SSLv3 - TLSv1.2";
- #endif
- err = SSLSetProtocolVersionMin(context, kTLSProtocol1);
- if (err == errSecSuccess)
- err = SSLSetProtocolVersionMax(context, kTLSProtocol1);
} else if (configuration.protocol == QSsl::SecureProtocols) {
#ifdef QSSLSOCKET_DEBUG
qCDebug(lcSsl) << plainSocket << "requesting : TLSv1 - TLSv1.2";
@@ -1213,8 +1165,6 @@ bool QSslSocketBackendPrivate::verifySessionProtocol() const
bool protocolOk = false;
if (configuration.protocol == QSsl::AnyProtocol)
protocolOk = true;
- else if (configuration.protocol == QSsl::TlsV1SslV3)
- protocolOk = (sessionProtocol() == QSsl::TlsV1_0);
else if (configuration.protocol == QSsl::SecureProtocols)
protocolOk = (sessionProtocol() >= QSsl::TlsV1_0);
else if (configuration.protocol == QSsl::TlsV1_0OrLater)
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
index 8cd0724d83..11989cd2ef 100644
--- a/src/network/ssl/qsslsocket_openssl.cpp
+++ b/src/network/ssl/qsslsocket_openssl.cpp
@@ -92,11 +92,129 @@
#endif
#include <algorithm>
+#include <memory>
#include <string.h>
QT_BEGIN_NAMESPACE
+namespace {
+
+QAlertLevel tlsAlertLevel(int value)
+{
+ if (const char *typeString = q_SSL_alert_type_string(value)) {
+ // Documented to return 'W' for warning, 'F' for fatal,
+ // 'U' for unknown.
+ switch (typeString[0]) {
+ case 'W':
+ return QAlertLevel::Warning;
+ case 'F':
+ return QAlertLevel::Fatal;
+ default:;
+ }
+ }
+
+ return QAlertLevel::Unknown;
+}
+
+QString tlsAlertDescription(int value)
+{
+ QString description = QLatin1String(q_SSL_alert_desc_string_long(value));
+ if (!description.size())
+ description = QLatin1String("no description provided");
+ return description;
+}
+
+QAlertType tlsAlertType(int value)
+{
+ // In case for some reason openssl gives us a value,
+ // which is not in our enum actually, we leave it to
+ // an application to handle (supposedly they have
+ // if or switch-statements).
+ return QAlertType(value & 0xff);
+}
+
+} // Unnamed namespace
+
+extern "C"
+{
+
+void qt_AlertInfoCallback(const SSL *connection, int from, int value)
+{
+ // Passed to SSL_set_info_callback()
+ // https://www.openssl.org/docs/man1.1.1/man3/SSL_set_info_callback.html
+
+ if (!connection) {
+#ifdef QSSLSOCKET_DEBUG
+ qCWarning(lcSsl, "Invalid 'connection' parameter (nullptr)");
+#endif // QSSLSOCKET_DEBUG
+ return;
+ }
+
+ const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData
+ + QSslSocketBackendPrivate::socketOffsetInExData;
+ auto privateSocket =
+ static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(connection, offset));
+ if (!privateSocket) {
+ // SSL_set_ex_data can fail:
+#ifdef QSSLSOCKET_DEBUG
+ qCWarning(lcSsl, "No external data (socket backend) found for parameter 'connection'");
+#endif // QSSLSOCKET_DEBUG
+ return;
+ }
+
+ if (!(from & SSL_CB_ALERT)) {
+ // We only want to know about alerts (at least for now).
+ return;
+ }
+
+ if (from & SSL_CB_WRITE)
+ privateSocket->alertMessageSent(value);
+ else
+ privateSocket->alertMessageReceived(value);
+}
+
+int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx)
+{
+ // Passed to SSL_CTX_set_verify()
+ // https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_verify.html
+ // Returns 0 to abort verification, 1 to continue.
+
+ // This is a new, experimental verification callback, reporting
+ // errors immediately and returning 0 or 1 depending on an application
+ // either ignoring or not ignoring verification errors as they come.
+ if (!ctx) {
+ qCWarning(lcSsl, "Invalid store context (nullptr)");
+ return 0;
+ }
+
+ if (!ok) {
+ // "Whenever a X509_STORE_CTX object is created for the verification of the
+ // peer's certificate during a handshake, a pointer to the SSL object is
+ // stored into the X509_STORE_CTX object to identify the connection affected.
+ // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be
+ // used with the correct index."
+ SSL *ssl = static_cast<SSL *>(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx()));
+ if (!ssl) {
+ qCWarning(lcSsl, "No external data (SSL) found in X509 store object");
+ return 0;
+ }
+
+ const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData
+ + QSslSocketBackendPrivate::socketOffsetInExData;
+ auto privateSocket = static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, offset));
+ if (!privateSocket) {
+ qCWarning(lcSsl, "No external data (QSslSocketBackendPrivate) found in SSL object");
+ return 0;
+ }
+
+ return privateSocket->emitErrorFromCallback(ctx);
+ }
+ return 1;
+}
+
+} // extern "C"
+
Q_GLOBAL_STATIC(QRecursiveMutex, qt_opensslInitMutex)
bool QSslSocketPrivate::s_libraryLoaded = false;
@@ -249,11 +367,7 @@ QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(const SSL_CIPHER
QString protoString = descriptionList.at(1).toString();
ciph.d->protocolString = protoString;
ciph.d->protocol = QSsl::UnknownProtocol;
- if (protoString == QLatin1String("SSLv3"))
- ciph.d->protocol = QSsl::SslV3;
- else if (protoString == QLatin1String("SSLv2"))
- ciph.d->protocol = QSsl::SslV2;
- else if (protoString == QLatin1String("TLSv1"))
+ if (protoString == QLatin1String("TLSv1"))
ciph.d->protocol = QSsl::TlsV1_0;
else if (protoString == QLatin1String("TLSv1.1"))
ciph.d->protocol = QSsl::TlsV1_1;
@@ -408,12 +522,15 @@ int q_X509Callback(int ok, X509_STORE_CTX *ctx)
// Not found on store? Try SSL and its external data then. According to the OpenSSL's
// documentation:
//
- // "Whenever a X509_STORE_CTX object is created for the verification of the peers certificate
- // during a handshake, a pointer to the SSL object is stored into the X509_STORE_CTX object
- // to identify the connection affected. To retrieve this pointer the X509_STORE_CTX_get_ex_data()
- // function can be used with the correct index."
+ // "Whenever a X509_STORE_CTX object is created for the verification of the
+ // peer's certificate during a handshake, a pointer to the SSL object is
+ // stored into the X509_STORE_CTX object to identify the connection affected.
+ // To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be
+ // used with the correct index."
+ const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData
+ + QSslSocketBackendPrivate::errorOffsetInExData;
if (SSL *ssl = static_cast<SSL *>(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx())))
- errors = ErrorListPtr(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData + 1));
+ errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset));
}
if (!errors) {
@@ -459,20 +576,23 @@ void q_setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers);
long QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions)
{
long options;
- if (protocol == QSsl::TlsV1SslV3)
- options = SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3;
- else if (protocol == QSsl::SecureProtocols)
- options = SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3;
- else if (protocol == QSsl::TlsV1_0OrLater)
- options = SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3;
- else if (protocol == QSsl::TlsV1_1OrLater)
- options = SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1;
- else if (protocol == QSsl::TlsV1_2OrLater)
- options = SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1;
- else if (protocol == QSsl::TlsV1_3OrLater)
- options = SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1|SSL_OP_NO_TLSv1_2;
- else
+ switch (protocol) {
+ case QSsl::SecureProtocols:
+ case QSsl::TlsV1_0OrLater:
+ options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+ break;
+ case QSsl::TlsV1_1OrLater:
+ options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
+ break;
+ case QSsl::TlsV1_2OrLater:
+ options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
+ break;
+ case QSsl::TlsV1_3OrLater:
+ options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2;
+ break;
+ default:
options = SSL_OP_ALL;
+ }
// This option is disabled by default, so we need to be able to clear it
if (sslOptions & QSsl::SslOptionDisableEmptyFragments)
@@ -530,10 +650,7 @@ bool QSslSocketBackendPrivate::initSslContext()
return false;
}
- if (configuration.protocol != QSsl::SslV2 &&
- configuration.protocol != QSsl::SslV3 &&
- configuration.protocol != QSsl::UnknownProtocol &&
- mode == QSslSocket::SslClientMode) {
+ if (configuration.protocol != QSsl::UnknownProtocol && mode == QSslSocket::SslClientMode) {
// Set server hostname on TLS extension. RFC4366 section 3.1 requires it in ACE format.
QString tlsHostName = verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName;
if (tlsHostName.isEmpty())
@@ -992,7 +1109,7 @@ void QSslSocketBackendPrivate::transmit()
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());
+ setErrorAndEmit(plainSocket->socketError(), plainSocket->errorString());
return;
}
transmitting = true;
@@ -1199,18 +1316,32 @@ bool QSslSocketBackendPrivate::startHandshake()
if (inSetAndEmitError)
return false;
+ pendingFatalAlert = false;
+ errorsReportedFromCallback = false;
QVector<QSslErrorEntry> lastErrors;
- q_SSL_set_ex_data(ssl, s_indexForSSLExtraData + 1, &lastErrors);
+ q_SSL_set_ex_data(ssl, s_indexForSSLExtraData + errorOffsetInExData, &lastErrors);
+
+ // SSL_set_ex_data can fail, but see the callback's code - we handle this there.
+ q_SSL_set_ex_data(ssl, s_indexForSSLExtraData + socketOffsetInExData, this);
+ q_SSL_set_info_callback(ssl, qt_AlertInfoCallback);
+
int result = (mode == QSslSocket::SslClientMode) ? q_SSL_connect(ssl) : q_SSL_accept(ssl);
- q_SSL_set_ex_data(ssl, s_indexForSSLExtraData + 1, nullptr);
+ q_SSL_set_ex_data(ssl, s_indexForSSLExtraData + errorOffsetInExData, nullptr);
+ // Note, unlike errors as external data on SSL object, we do not unset
+ // a callback/ex-data if alert notifications are enabled: an alert can
+ // arrive after the handshake, for example, this happens when the server
+ // does not find a ClientCert or does not like it.
- if (!lastErrors.isEmpty())
+ if (!lastErrors.isEmpty() || errorsReportedFromCallback)
storePeerCertificates();
- for (const auto &currentError : qAsConst(lastErrors)) {
- emit q->peerVerifyError(_q_OpenSSL_to_QSslError(currentError.code,
- configuration.peerCertificateChain.value(currentError.depth)));
- if (q->state() != QAbstractSocket::ConnectedState)
- break;
+
+ if (!errorsReportedFromCallback) {
+ for (const auto &currentError : qAsConst(lastErrors)) {
+ emit q->peerVerifyError(_q_OpenSSL_to_QSslError(currentError.code,
+ configuration.peerCertificateChain.value(currentError.depth)));
+ if (q->state() != QAbstractSocket::ConnectedState)
+ break;
+ }
}
errorList << lastErrors;
@@ -1234,6 +1365,10 @@ bool QSslSocketBackendPrivate::startHandshake()
{
const ScopedBool bg(inSetAndEmitError, true);
setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, errorString);
+ if (pendingFatalAlert) {
+ trySendFatalAlert();
+ pendingFatalAlert = false;
+ }
}
q->abort();
}
@@ -1703,6 +1838,88 @@ bool QSslSocketBackendPrivate::checkOcspStatus()
#endif // ocsp
+void QSslSocketBackendPrivate::alertMessageSent(int value)
+{
+ Q_Q(QSslSocket);
+
+ const auto level = tlsAlertLevel(value);
+ if (level == QAlertLevel::Fatal && !connectionEncrypted) {
+ // Note, this logic is handshake-time only:
+ pendingFatalAlert = true;
+ }
+
+ emit q->alertSent(level, tlsAlertType(value), tlsAlertDescription(value));
+}
+
+void QSslSocketBackendPrivate::alertMessageReceived(int value)
+{
+ Q_Q(QSslSocket);
+
+ emit q->alertReceived(tlsAlertLevel(value), tlsAlertType(value), tlsAlertDescription(value));
+}
+
+int QSslSocketBackendPrivate::emitErrorFromCallback(X509_STORE_CTX *ctx)
+{
+ // Returns 0 to abort verification, 1 to continue despite error (as
+ // OpenSSL expects from the verification callback).
+ Q_Q(QSslSocket);
+
+ Q_ASSERT(ctx);
+
+ using ScopedBool = QScopedValueRollback<bool>;
+ // While we are not setting, we are emitting and in general -
+ // we want to prevent accidental recursive startHandshake()
+ // calls:
+ const ScopedBool bg(inSetAndEmitError, true);
+
+ X509 *x509 = q_X509_STORE_CTX_get_current_cert(ctx);
+ if (!x509) {
+ qCWarning(lcSsl, "Could not obtain the certificate (that failed to verify)");
+ return 0;
+ }
+ const QSslCertificate certificate = QSslCertificatePrivate::QSslCertificate_from_X509(x509);
+
+ const auto errorAndDepth = QSslErrorEntry::fromStoreContext(ctx);
+ const QSslError tlsError = _q_OpenSSL_to_QSslError(errorAndDepth.code, certificate);
+
+ errorsReportedFromCallback = true;
+ handshakeInterrupted = true;
+ emit q->handshakeInterruptedOnError(tlsError);
+
+ // Conveniently so, we also can access 'lastErrors' external data set
+ // in startHandshake, we store it for the case an application later
+ // wants to check errors (ignored or not):
+ const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData
+ + QSslSocketBackendPrivate::errorOffsetInExData;
+ if (auto errorList = static_cast<QVector<QSslErrorEntry>*>(q_SSL_get_ex_data(ssl, offset)))
+ errorList->append(errorAndDepth);
+
+ // An application is expected to ignore this error (by calling ignoreSslErrors)
+ // in its directly connected slot:
+ return !handshakeInterrupted;
+}
+
+void QSslSocketBackendPrivate::trySendFatalAlert()
+{
+ Q_ASSERT(pendingFatalAlert);
+
+ pendingFatalAlert = false;
+ QVarLengthArray<char, 4096> data;
+ int pendingBytes = 0;
+ while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0
+ && plainSocket->openMode() != QIODevice::NotOpen) {
+ // Read encrypted data from the write BIO into a buffer.
+ data.resize(pendingBytes);
+ const int bioReadBytes = q_BIO_read(writeBio, data.data(), pendingBytes);
+
+ // Write encrypted data from the buffer to the socket.
+ qint64 actualWritten = plainSocket->write(data.constData(), bioReadBytes);
+ if (actualWritten < 0)
+ return;
+ plainSocket->flush();
+ }
+}
+
void QSslSocketBackendPrivate::disconnectFromHost()
{
if (ssl) {
@@ -1746,10 +1963,6 @@ QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const
int ver = q_SSL_version(ssl);
switch (ver) {
- case 0x2:
- return QSsl::SslV2;
- case 0x300:
- return QSsl::SslV3;
case 0x301:
return QSsl::TlsV1_0;
case 0x302:
@@ -1957,6 +2170,7 @@ QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &
errors << QSslError(QSslError::UnspecifiedError);
return errors;
}
+ const std::unique_ptr<X509_STORE, decltype(&q_X509_STORE_free)> storeGuard(certStore, q_X509_STORE_free);
if (s_loadRootCertsOnDemand) {
setDefaultCaCertificates(defaultCaCertificates() + systemCaCertificates());
@@ -1997,7 +2211,6 @@ QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &
intermediates = (STACK_OF(X509) *) q_OPENSSL_sk_new_null();
if (!intermediates) {
- q_X509_STORE_free(certStore);
errors << QSslError(QSslError::UnspecifiedError);
return errors;
}
@@ -2015,14 +2228,12 @@ QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &
X509_STORE_CTX *storeContext = q_X509_STORE_CTX_new();
if (!storeContext) {
- q_X509_STORE_free(certStore);
errors << QSslError(QSslError::UnspecifiedError);
return errors;
}
+ std::unique_ptr<X509_STORE_CTX, decltype(&q_X509_STORE_CTX_free)> ctxGuard(storeContext, q_X509_STORE_CTX_free);
if (!q_X509_STORE_CTX_init(storeContext, certStore, reinterpret_cast<X509 *>(certificateChain[0].handle()), intermediates)) {
- q_X509_STORE_CTX_free(storeContext);
- q_X509_STORE_free(certStore);
errors << QSslError(QSslError::UnspecifiedError);
return errors;
}
@@ -2031,8 +2242,7 @@ QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &
// We ignore the result of this function since we process errors via the
// callback.
(void) q_X509_verify_cert(storeContext);
-
- q_X509_STORE_CTX_free(storeContext);
+ ctxGuard.reset();
q_OPENSSL_sk_free((OPENSSL_STACK *)intermediates);
// Now process the errors
@@ -2054,8 +2264,6 @@ QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &
for (const auto &error : qAsConst(lastErrors))
errors << _q_OpenSSL_to_QSslError(error.code, certificateChain.value(error.depth));
- q_X509_STORE_free(certStore);
-
return errors;
}
diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h
index 0370a7d2ac..06af9f5974 100644
--- a/src/network/ssl/qsslsocket_openssl_p.h
+++ b/src/network/ssl/qsslsocket_openssl_p.h
@@ -131,6 +131,10 @@ public:
SSL_SESSION *session;
QVector<QSslErrorEntry> errorList;
static int s_indexForSSLExtraData; // index used in SSL_get_ex_data to get the matching QSslSocketBackendPrivate
+ enum ExDataOffset {
+ errorOffsetInExData = 1,
+ socketOffsetInExData = 2
+ };
bool inSetAndEmitError = false;
@@ -157,6 +161,15 @@ public:
bool checkOcspStatus();
#endif
+ void alertMessageSent(int encoded);
+ void alertMessageReceived(int encoded);
+
+ int emitErrorFromCallback(X509_STORE_CTX *ctx);
+ void trySendFatalAlert();
+
+ bool pendingFatalAlert = false;
+ bool errorsReportedFromCallback = false;
+
// This decription will go to setErrorAndEmit(SslHandshakeError, ocspErrorDescription)
QString ocspErrorDescription;
// These will go to sslErrors()
diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp
index 3504924888..5b0a70d495 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols.cpp
+++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp
@@ -156,6 +156,10 @@ DEFINEFUNC(void, OPENSSL_sk_free, OPENSSL_STACK *a, a, return, DUMMYARG)
DEFINEFUNC2(void *, OPENSSL_sk_value, OPENSSL_STACK *a, a, int b, b, return nullptr, return)
DEFINEFUNC(int, SSL_session_reused, SSL *a, a, return 0, return)
DEFINEFUNC2(unsigned long, SSL_CTX_set_options, SSL_CTX *ctx, ctx, unsigned long op, op, return 0, return)
+using info_callback = void (*) (const SSL *ssl, int type, int val);
+DEFINEFUNC2(void, SSL_set_info_callback, SSL *ssl, ssl, info_callback cb, cb, return, return)
+DEFINEFUNC(const char *, SSL_alert_type_string, int value, value, return nullptr, return)
+DEFINEFUNC(const char *, SSL_alert_desc_string_long, int value, value, return nullptr, return)
#ifdef TLS1_3_VERSION
DEFINEFUNC2(int, SSL_CTX_set_ciphersuites, SSL_CTX *ctx, ctx, const char *str, str, return 0, return)
DEFINEFUNC2(void, SSL_set_psk_use_session_callback, SSL *ssl, ssl, q_SSL_psk_use_session_cb_func_t callback, callback, return, DUMMYARG)
@@ -839,7 +843,9 @@ bool q_resolveOpenSslSymbols()
RESOLVEFUNC(OPENSSL_sk_value)
RESOLVEFUNC(DH_get0_pqg)
RESOLVEFUNC(SSL_CTX_set_options)
-
+ RESOLVEFUNC(SSL_set_info_callback)
+ RESOLVEFUNC(SSL_alert_type_string)
+ RESOLVEFUNC(SSL_alert_desc_string_long)
#ifdef TLS1_3_VERSION
RESOLVEFUNC(SSL_CTX_set_ciphersuites)
RESOLVEFUNC(SSL_set_psk_use_session_callback)
diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h
index baf1a43113..ac6aa1760f 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols_p.h
+++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h
@@ -719,6 +719,10 @@ int q_OCSP_id_cmp(OCSP_CERTID *a, OCSP_CERTID *b);
void *q_CRYPTO_malloc(size_t num, const char *file, int line);
#define q_OPENSSL_malloc(num) q_CRYPTO_malloc(num, "", 0)
+void q_SSL_set_info_callback(SSL *ssl, void (*cb) (const SSL *ssl, int type, int val));
+const char *q_SSL_alert_type_string(int value);
+const char *q_SSL_alert_desc_string_long(int value);
+
QT_END_NAMESPACE
#endif
diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h
index 1abd18bb32..4b020b6a73 100644
--- a/src/network/ssl/qsslsocket_p.h
+++ b/src/network/ssl/qsslsocket_p.h
@@ -208,6 +208,7 @@ protected:
bool paused;
bool flushTriggered;
QVector<QOcspResponse> ocspResponses;
+ bool handshakeInterrupted = false;
};
#if QT_CONFIG(securetransport) || QT_CONFIG(schannel)
diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp
index 78d807106b..ba15a0a65e 100644
--- a/src/network/ssl/qsslsocket_schannel.cpp
+++ b/src/network/ssl/qsslsocket_schannel.cpp
@@ -226,12 +226,6 @@ DWORD toSchannelProtocol(QSsl::SslProtocol protocol)
protocols = SP_PROT_TLS1_0 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2;
// @future Add TLS 1.3 when supported by Windows!
break;
- case QSsl::SslV2:
- case QSsl::SslV3:
- return DWORD(-1); // Not supported
- case QSsl::TlsV1SslV3:
- protocols = SP_PROT_TLS1_0;
- break;
case QSsl::TlsV1_0:
protocols = SP_PROT_TLS1_0;
break;
@@ -582,7 +576,7 @@ bool QSslSocketBackendPrivate::sendToken(void *token, unsigned long tokenLength,
if (written != qint64(tokenLength)) {
// Failed to write/buffer everything or an error occurred
if (emitError)
- setErrorAndEmit(plainSocket->error(), plainSocket->errorString());
+ setErrorAndEmit(plainSocket->socketError(), plainSocket->errorString());
return false;
}
return true;
@@ -1030,6 +1024,7 @@ bool QSslSocketBackendPrivate::performHandshake()
bool QSslSocketBackendPrivate::verifyHandshake()
{
Q_Q(QSslSocket);
+ sslErrors.clear();
const bool isClient = mode == QSslSocket::SslClientMode;
#define CHECK_STATUS(status) \
@@ -1118,7 +1113,7 @@ bool QSslSocketBackendPrivate::verifyHandshake()
}
// verifyCertContext returns false if the user disconnected while it was checking errors.
- if (certificateContext && sslErrors.isEmpty() && !verifyCertContext(certificateContext))
+ if (certificateContext && !verifyCertContext(certificateContext))
return false;
if (!checkSslErrors() || state != QAbstractSocket::ConnectedState) {
@@ -1291,7 +1286,7 @@ void QSslSocketBackendPrivate::transmit()
if (bytesWritten >= 0) {
totalBytesWritten += bytesWritten;
} else {
- setErrorAndEmit(plainSocket->error(), plainSocket->errorString());
+ setErrorAndEmit(plainSocket->socketError(), plainSocket->errorString());
return;
}
}
diff --git a/src/network/ssl/qsslsocket_winrt.cpp b/src/network/ssl/qsslsocket_winrt.cpp
index 4286b5ea42..5f5201fc82 100644
--- a/src/network/ssl/qsslsocket_winrt.cpp
+++ b/src/network/ssl/qsslsocket_winrt.cpp
@@ -230,13 +230,7 @@ void QSslSocketBackendPrivate::startClientEncryption()
QSsl::SslProtocol protocol = q->protocol();
switch (q->protocol()) {
- case QSsl::SslV2:
- case QSsl::SslV3:
- setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError,
- QStringLiteral("unsupported protocol"));
- return;
case QSsl::AnyProtocol:
- case QSsl::TlsV1SslV3:
protectionLevel = SocketProtectionLevel_Tls10;
break;
case QSsl::TlsV1_0:
diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri
index 0c8b9ccd40..dfbf539303 100644
--- a/src/network/ssl/ssl.pri
+++ b/src/network/ssl/ssl.pri
@@ -115,9 +115,11 @@ qtConfig(ssl) {
# - libs in <OPENSSL_DIR>\lib\VC\static
# - configure: -openssl -openssl-linked -I <OPENSSL_DIR>\include -L <OPENSSL_DIR>\lib\VC\static OPENSSL_LIBS="-lUser32 -lAdvapi32 -lGdi32" OPENSSL_LIBS_DEBUG="-lssleay32MDd -llibeay32MDd" OPENSSL_LIBS_RELEASE="-lssleay32MD -llibeay32MD"
- qtConfig(openssl-linked): \
- QMAKE_USE_FOR_PRIVATE += openssl
- else: \
+ qtConfig(openssl-linked): {
+ android {
+ build_pass: LIBS_PRIVATE += -lssl_$${QT_ARCH} -lcrypto_$${QT_ARCH}
+ } else: QMAKE_USE_FOR_PRIVATE += openssl
+ } else: \
QMAKE_USE_FOR_PRIVATE += openssl/nolink
win32 {
LIBS_PRIVATE += -lcrypt32