diff options
author | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2020-01-27 14:11:08 +0100 |
---|---|---|
committer | Timur Pocheptsov <timur.pocheptsov@qt.io> | 2020-01-29 19:38:43 +0100 |
commit | b36b7abb40f04f265c0453a2f4beb466ed462976 (patch) | |
tree | 2353834692f75f65c9dfdace5dbe83f205bfe783 /src/network/ssl/qsslsocket_openssl.cpp | |
parent | 33c9a1e0bcf9c7ced67d5ec62225d6295671d33b (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/ssl/qsslsocket_openssl.cpp')
-rw-r--r-- | src/network/ssl/qsslsocket_openssl.cpp | 70 |
1 files changed, 70 insertions, 0 deletions
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); |