summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@qt.io>2020-01-27 14:11:08 +0100
committerTimur Pocheptsov <timur.pocheptsov@qt.io>2020-01-29 19:38:43 +0100
commitb36b7abb40f04f265c0453a2f4beb466ed462976 (patch)
tree2353834692f75f65c9dfdace5dbe83f205bfe783 /src/network
parent33c9a1e0bcf9c7ced67d5ec62225d6295671d33b (diff)
Implement/fix session resumption with TLS 1.3
The session we cache at the end of a handshake is non-resumable in TLS 1.3, since NewSessionTicket message appears quite some time after the handshake was complete. OpenSSL has a callback where we can finally obtain a resumable session and inform an application about session ticket updated by emitting a signal. Truism: OpenSSL-only. [ChangeLog][QtNetwork] A new signal introduced to report when a valid session ticket received (TLS 1.3) Fixes: QTBUG-81591 Change-Id: I4d22fad5cc082e431577e20ddbda2835e864b511 Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Diffstat (limited to 'src/network')
-rw-r--r--src/network/ssl/qsslconfiguration.cpp6
-rw-r--r--src/network/ssl/qsslcontext_openssl.cpp21
-rw-r--r--src/network/ssl/qsslsocket.cpp16
-rw-r--r--src/network/ssl/qsslsocket.h1
-rw-r--r--src/network/ssl/qsslsocket_openssl.cpp70
-rw-r--r--src/network/ssl/qsslsocket_openssl_p.h1
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols.cpp4
-rw-r--r--src/network/ssl/qsslsocket_openssl_symbols_p.h18
8 files changed, 130 insertions, 7 deletions
diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp
index d0674042b8..f5ce02807f 100644
--- a/src/network/ssl/qsslconfiguration.cpp
+++ b/src/network/ssl/qsslconfiguration.cpp
@@ -782,7 +782,7 @@ bool QSslConfiguration::testSslOption(QSsl::SslOption option) const
knowledge of the session allows for eavesdropping on data
encrypted with the session parameters.
- \sa setSessionTicket(), QSsl::SslOptionDisableSessionPersistence, setSslOption()
+ \sa setSessionTicket(), QSsl::SslOptionDisableSessionPersistence, setSslOption(), QSslSocket::newSessionTicketReceived()
*/
QByteArray QSslConfiguration::sessionTicket() const
{
@@ -797,7 +797,7 @@ QByteArray QSslConfiguration::sessionTicket() const
for this to work, and \a sessionTicket must be in ASN.1 format
as returned by sessionTicket().
- \sa sessionTicket(), QSsl::SslOptionDisableSessionPersistence, setSslOption()
+ \sa sessionTicket(), QSsl::SslOptionDisableSessionPersistence, setSslOption(), QSslSocket::newSessionTicketReceived()
*/
void QSslConfiguration::setSessionTicket(const QByteArray &sessionTicket)
{
@@ -815,7 +815,7 @@ void QSslConfiguration::setSessionTicket(const QByteArray &sessionTicket)
QSsl::SslOptionDisableSessionPersistence was not turned off,
this function returns -1.
- \sa sessionTicket(), QSsl::SslOptionDisableSessionPersistence, setSslOption()
+ \sa sessionTicket(), QSsl::SslOptionDisableSessionPersistence, setSslOption(), QSslSocket::newSessionTicketReceived()
*/
int QSslConfiguration::sessionTicketLifeTimeHint() const
{
diff --git a/src/network/ssl/qsslcontext_openssl.cpp b/src/network/ssl/qsslcontext_openssl.cpp
index 562aa4f518..0aa8a4f438 100644
--- a/src/network/ssl/qsslcontext_openssl.cpp
+++ b/src/network/ssl/qsslcontext_openssl.cpp
@@ -70,6 +70,10 @@ extern "C" int q_verify_cookie_callback(SSL *ssl, const unsigned char *cookie,
}
#endif // dtls
+#ifdef TLS1_3_VERSION
+extern "C" int q_ssl_sess_set_new_cb(SSL *context, SSL_SESSION *session);
+#endif // TLS1_3_VERSION
+
// Defined in qsslsocket.cpp
QList<QSslCipher> q_getDefaultDtlsCiphers();
@@ -168,8 +172,8 @@ SSL* QSslContext::createSsl()
if (!session && !sessionASN1().isEmpty()
&& !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) {
const unsigned char *data = reinterpret_cast<const unsigned char *>(m_sessionASN1.constData());
- session = q_d2i_SSL_SESSION(
- nullptr, &data, m_sessionASN1.size()); // refcount is 1 already, set by function above
+ session = q_d2i_SSL_SESSION(nullptr, &data, m_sessionASN1.size());
+ // 'session' has refcount 1 already, set by the function above
}
if (session) {
@@ -585,7 +589,8 @@ init_context:
}
}
- // Initialize peer verification.
+ // Initialize peer verification, different callbacks, TLS/DTLS verification first
+ // (note, all these set_some_callback do not have return value):
if (sslContext->sslConfiguration.peerVerifyMode() == QSslSocket::VerifyNone) {
q_SSL_CTX_set_verify(sslContext->ctx, SSL_VERIFY_NONE, nullptr);
} else {
@@ -596,7 +601,17 @@ init_context:
q_X509Callback);
}
+#ifdef TLS1_3_VERSION
+ // NewSessionTicket callback:
+ if (mode == QSslSocket::SslClientMode && !isDtls) {
+ q_SSL_CTX_sess_set_new_cb(sslContext->ctx, q_ssl_sess_set_new_cb);
+ q_SSL_CTX_set_session_cache_mode(sslContext->ctx, SSL_SESS_CACHE_CLIENT);
+ }
+
+#endif // TLS1_3_VERSION
+
#if QT_CONFIG(dtls)
+ // DTLS cookies:
if (mode == QSslSocket::SslServerMode && isDtls && configuration.dtlsCookieVerificationEnabled()) {
q_SSL_CTX_set_cookie_generate_cb(sslContext->ctx, dtlscallbacks::q_generate_cookie_callback);
q_SSL_CTX_set_cookie_verify_cb(sslContext->ctx, dtlscallbacks::q_verify_cookie_callback);
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index 19be48a656..8fa9f914a2 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -322,6 +322,22 @@
\sa QSslPreSharedKeyAuthenticator
*/
+/*!
+ \fn void QSslSocket::newSessionTicketReceived()
+ \since 5.15
+
+ If TLS 1.3 protocol was negotiated during a handshake, QSslSocket
+ emits this signal after receiving NewSessionTicket message. Session
+ and session ticket's lifetime hint are updated in the socket's
+ configuration. The session can be used for session resumption (and
+ a shortened handshake) in future TLS connections.
+
+ \note This functionality enabled only with OpenSSL backend and requires
+ OpenSSL v 1.1.1 or above.
+
+ \sa QSslSocket::sslConfiguration(), QSslConfiguration::sessionTicket(), QSslConfiguration::sessionTicketLifeTimeHint()
+*/
+
#include "qssl_p.h"
#include "qsslsocket.h"
#include "qsslcipher.h"
diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h
index 2c3b876c49..298e7aa6c8 100644
--- a/src/network/ssl/qsslsocket.h
+++ b/src/network/ssl/qsslsocket.h
@@ -217,6 +217,7 @@ Q_SIGNALS:
void modeChanged(QSslSocket::SslMode newMode);
void encryptedBytesWritten(qint64 totalBytes);
void preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator *authenticator);
+ void newSessionTicketReceived();
protected:
qint64 readData(char *data, qint64 maxlen) override;
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
index 41f933b299..4be27affca 100644
--- a/src/network/ssl/qsslsocket_openssl.cpp
+++ b/src/network/ssl/qsslsocket_openssl.cpp
@@ -183,6 +183,22 @@ static int q_ssl_psk_use_session_callback(SSL *ssl, const EVP_MD *md, const unsi
return 1; // need to return 1 or else "the connection setup fails."
}
+
+int q_ssl_sess_set_new_cb(SSL *ssl, SSL_SESSION *session)
+{
+ if (!ssl) {
+ qCWarning(lcSsl, "Invalid SSL (nullptr)");
+ return 0;
+ }
+ if (!session) {
+ qCWarning(lcSsl, "Invalid SSL_SESSION (nullptr)");
+ return 0;
+ }
+
+ auto socketPrivate = static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl,
+ QSslSocketBackendPrivate::s_indexForSSLExtraData));
+ return socketPrivate->handleNewSessionTicket(ssl);
+}
#endif // TLS1_3_VERSION
#endif // !OPENSSL_NO_PSK
@@ -1392,6 +1408,60 @@ void QSslSocketBackendPrivate::storePeerCertificates()
}
}
+int QSslSocketBackendPrivate::handleNewSessionTicket(SSL *connection)
+{
+ // If we return 1, this means we own the session, but we don't.
+ // 0 would tell OpenSSL to deref (but they still have it in the
+ // internal cache).
+ Q_Q(QSslSocket);
+
+ Q_ASSERT(connection);
+
+ if (q->sslConfiguration().testSslOption(QSsl::SslOptionDisableSessionPersistence)) {
+ // We silently ignore, do nothing, remove from cache.
+ return 0;
+ }
+
+ SSL_SESSION *currentSession = q_SSL_get_session(connection);
+ if (!currentSession) {
+ qCWarning(lcSsl,
+ "New session ticket callback, the session is invalid (nullptr)");
+ return 0;
+ }
+
+ if (q_SSL_version(connection) < 0x304) {
+ // We only rely on this mechanics with TLS >= 1.3
+ return 0;
+ }
+
+#ifdef TLS1_3_VERSION
+ if (!q_SSL_SESSION_is_resumable(currentSession)) {
+ qCDebug(lcSsl, "New session ticket, but the session is non-resumable");
+ return 0;
+ }
+#endif // TLS1_3_VERSION
+
+ const int sessionSize = q_i2d_SSL_SESSION(currentSession, nullptr);
+ if (sessionSize <= 0) {
+ qCWarning(lcSsl, "could not store persistent version of SSL session");
+ return 0;
+ }
+
+ // We have somewhat perverse naming, it's not a ticket, it's a session.
+ QByteArray sessionTicket(sessionSize, 0);
+ auto data = reinterpret_cast<unsigned char *>(sessionTicket.data());
+ if (!q_i2d_SSL_SESSION(currentSession, &data)) {
+ qCWarning(lcSsl, "could not store persistent version of SSL session");
+ return 0;
+ }
+
+ configuration.sslSession = sessionTicket;
+ configuration.sslSessionTicketLifeTimeHint = int(q_SSL_SESSION_get_ticket_lifetime_hint(currentSession));
+
+ emit q->newSessionTicketReceived();
+ return 0;
+}
+
bool QSslSocketBackendPrivate::checkSslErrors()
{
Q_Q(QSslSocket);
diff --git a/src/network/ssl/qsslsocket_openssl_p.h b/src/network/ssl/qsslsocket_openssl_p.h
index 0370a7d2ac..47ccf06e71 100644
--- a/src/network/ssl/qsslsocket_openssl_p.h
+++ b/src/network/ssl/qsslsocket_openssl_p.h
@@ -146,6 +146,7 @@ public:
void continueHandshake() override;
bool checkSslErrors();
void storePeerCertificates();
+ int handleNewSessionTicket(SSL *context);
unsigned int tlsPskClientCallback(const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len);
unsigned int tlsPskServerCallback(const char *identity, unsigned char *psk, unsigned int max_psk_len);
#ifdef Q_OS_WIN
diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp
index 3504924888..71a268ae6e 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols.cpp
+++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp
@@ -159,6 +159,8 @@ DEFINEFUNC2(unsigned long, SSL_CTX_set_options, SSL_CTX *ctx, ctx, unsigned long
#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)
+DEFINEFUNC2(void, SSL_CTX_sess_set_new_cb, SSL_CTX *ctx, ctx, NewSessionCallback cb, cb, return, return)
+DEFINEFUNC(int, SSL_SESSION_is_resumable, const SSL_SESSION *s, s, return 0, return)
#endif
DEFINEFUNC3(size_t, SSL_get_client_random, SSL *a, a, unsigned char *out, out, size_t outlen, outlen, return 0, return)
DEFINEFUNC3(size_t, SSL_SESSION_get_master_key, const SSL_SESSION *ses, ses, unsigned char *out, out, size_t outlen, outlen, return 0, return)
@@ -843,6 +845,8 @@ bool q_resolveOpenSslSymbols()
#ifdef TLS1_3_VERSION
RESOLVEFUNC(SSL_CTX_set_ciphersuites)
RESOLVEFUNC(SSL_set_psk_use_session_callback)
+ RESOLVEFUNC(SSL_CTX_sess_set_new_cb)
+ RESOLVEFUNC(SSL_SESSION_is_resumable)
#endif // TLS 1.3 or OpenSSL > 1.1.1
RESOLVEFUNC(SSL_get_client_random)
diff --git a/src/network/ssl/qsslsocket_openssl_symbols_p.h b/src/network/ssl/qsslsocket_openssl_symbols_p.h
index baf1a43113..f35e0ba22b 100644
--- a/src/network/ssl/qsslsocket_openssl_symbols_p.h
+++ b/src/network/ssl/qsslsocket_openssl_symbols_p.h
@@ -224,7 +224,6 @@ QT_BEGIN_NAMESPACE
// To reduce the amount of the change, I'm directly copying and pasting the
// content of the header here. Later, can be better sorted/split into groups,
// depending on the functionality.
-//#include "qsslsocket_openssl11_symbols_p.h"
const unsigned char * q_ASN1_STRING_get0_data(const ASN1_STRING *x);
@@ -287,6 +286,23 @@ unsigned long q_SSL_set_options(SSL *s, unsigned long op);
#ifdef TLS1_3_VERSION
int q_SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str);
+
+// The functions below do not really have to be ifdefed like this, but for now
+// they only used in TLS 1.3 handshake (and probably future versions).
+// Plus, 'is resumalbe' is OpenSSL 1.1.1-only (and again we need it for
+// TLS 1.3-specific session management).
+
+extern "C"
+{
+using NewSessionCallback = int (*)(SSL *, SSL_SESSION *);
+}
+
+void q_SSL_CTX_sess_set_new_cb(SSL_CTX *ctx, NewSessionCallback cb);
+int q_SSL_SESSION_is_resumable(const SSL_SESSION *s);
+
+#define q_SSL_CTX_set_session_cache_mode(ctx,m) \
+ q_SSL_CTX_ctrl(ctx,SSL_CTRL_SET_SESS_CACHE_MODE,m,NULL)
+
#endif
#if QT_CONFIG(dtls)