From bf48364a8c2ccbb9b3609727b628c2e1f1ed12bd Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Tue, 22 Nov 2016 18:56:46 +0000 Subject: [PATCH] Support setting per-connection default session lifetime value Due to recent changes, changing the SSL session timeout from cert_cb is not possible anymore since the new |SSL_SESSION| is initialized *after* cert_cb is run. The alternative would be using |SSL_CTX_set_timeout| but the specific |SSL_CTX| could be shared by multiple |SSL|s. Setting a value on a per-connection basis is useful in case timeouts need to be calculated dynamically based on specific certificate/domain information that would be retrieved from inside cert_cb (or other callbacks). It would also be possible to set the value to 0 to prevent session resumption, which is not otherwise doable in the handshake callbacks. Change-Id: I730a528c647f83f7f77f59b5b21d7e060e4c9843 Reviewed-on: https://boringssl-review.googlesource.com/12440 Reviewed-by: David Benjamin Commit-Queue: David Benjamin CQ-Verified: CQ bot account: commit-bot@chromium.org --- include/openssl/ssl.h | 13 +++++ ssl/ssl_lib.c | 7 +++ ssl/ssl_session.c | 11 ++-- ssl/ssl_test.cc | 126 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 4 deletions(-) diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 0de49672..f588f46b 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -1721,6 +1721,15 @@ OPENSSL_EXPORT long SSL_CTX_set_timeout(SSL_CTX *ctx, long timeout); * |ctx|. */ OPENSSL_EXPORT long SSL_CTX_get_timeout(const SSL_CTX *ctx); +/* SSL_set_session_timeout sets the default lifetime, in seconds, of the + * session created in |ssl| to |timeout|, and returns the old value. + * + * By default the value |SSL_DEFAULT_SESSION_TIMEOUT| is used, which can be + * overridden at the context level by calling |SSL_CTX_set_timeout|. + * + * If |timeout| is zero the newly created session will not be resumable. */ +OPENSSL_EXPORT long SSL_set_session_timeout(SSL *ssl, long timeout); + /* SSL_CTX_set_session_id_context sets |ctx|'s session ID context to |sid_ctx|. * It returns one on success and zero on error. The session ID context is an * application-defined opaque byte string. A session will not be used in a @@ -4215,6 +4224,10 @@ struct ssl_st { /* TODO(agl): remove once node.js not longer references this. */ int tlsext_status_type; + + /* session_timeout is the default lifetime in seconds of the session + * created in this connection. */ + long session_timeout; }; diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 70a39ea4..aafad333 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -477,6 +477,13 @@ SSL *SSL_new(SSL_CTX *ctx) { ssl->ctx->signed_cert_timestamps_enabled; ssl->ocsp_stapling_enabled = ssl->ctx->ocsp_stapling_enabled; + ssl->session_timeout = SSL_DEFAULT_SESSION_TIMEOUT; + + /* If the context has a default timeout, use it over the default. */ + if (ctx->session_timeout != 0) { + ssl->session_timeout = ctx->session_timeout; + } + return ssl; err: diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c index a6b669d0..a452d329 100644 --- a/ssl/ssl_session.c +++ b/ssl/ssl_session.c @@ -465,10 +465,7 @@ int ssl_get_new_session(SSL *ssl, int is_server) { ssl_get_current_time(ssl, &now); session->time = now.tv_sec; - /* If the context has a default timeout, use it over the default. */ - if (ssl->initial_ctx->session_timeout != 0) { - session->timeout = ssl->initial_ctx->session_timeout; - } + session->timeout = ssl->session_timeout; session->ssl_version = ssl->version; @@ -875,6 +872,12 @@ long SSL_CTX_get_timeout(const SSL_CTX *ctx) { return ctx->session_timeout; } +long SSL_set_session_timeout(SSL *ssl, long timeout) { + long old_timeout = ssl->session_timeout; + ssl->session_timeout = timeout; + return old_timeout; +} + typedef struct timeout_param_st { SSL_CTX *ctx; long time; diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index ae9d5ca4..283b7376 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc @@ -2319,6 +2319,131 @@ static bool TestSessionTimeout(bool is_dtls, const SSL_METHOD *method, return true; } +static int SetSessionTimeoutCallback(SSL *ssl, void *arg) { + long timeout = *(long *) arg; + SSL_set_session_timeout(ssl, timeout); + return 1; +} + +static bool TestSessionTimeoutCertCallback(bool is_dtls, + const SSL_METHOD *method, + uint16_t version) { + static const int kStartTime = 1000; + g_current_time.tv_sec = kStartTime; + + bssl::UniquePtr cert = GetTestCertificate(); + bssl::UniquePtr key = GetTestKey(); + if (!cert || !key) { + return false; + } + + bssl::UniquePtr server_ctx(SSL_CTX_new(method)); + bssl::UniquePtr client_ctx(SSL_CTX_new(method)); + if (!server_ctx || !client_ctx || + !SSL_CTX_use_certificate(server_ctx.get(), cert.get()) || + !SSL_CTX_use_PrivateKey(server_ctx.get(), key.get()) || + !SSL_CTX_set_min_proto_version(client_ctx.get(), version) || + !SSL_CTX_set_max_proto_version(client_ctx.get(), version) || + !SSL_CTX_set_min_proto_version(server_ctx.get(), version) || + !SSL_CTX_set_max_proto_version(server_ctx.get(), version)) { + return false; + } + + SSL_CTX_set_session_cache_mode(client_ctx.get(), SSL_SESS_CACHE_BOTH); + SSL_CTX_set_session_cache_mode(server_ctx.get(), SSL_SESS_CACHE_BOTH); + + SSL_CTX_set_current_time_cb(server_ctx.get(), CurrentTimeCallback); + + long timeout = 25; + SSL_CTX_set_cert_cb(server_ctx.get(), SetSessionTimeoutCallback, &timeout); + + bssl::UniquePtr session = + CreateClientSession(client_ctx.get(), server_ctx.get()); + if (!session) { + fprintf(stderr, "Error getting session.\n"); + return false; + } + + // Advance the clock just behind the timeout. + g_current_time.tv_sec += timeout - 1; + + if (!ExpectSessionReused(client_ctx.get(), server_ctx.get(), session.get(), + true /* expect session reused */)) { + fprintf(stderr, "Error resuming session.\n"); + return false; + } + + // Advance the clock one more second. + g_current_time.tv_sec++; + + if (!ExpectSessionReused(client_ctx.get(), server_ctx.get(), session.get(), + false /* expect session not reused */)) { + fprintf(stderr, "Error resuming session.\n"); + return false; + } + + // Set session timeout to 0 to disable resumption. + timeout = 0; + g_current_time.tv_sec = kStartTime; + + bssl::UniquePtr not_resumable_session = + CreateClientSession(client_ctx.get(), server_ctx.get()); + if (!not_resumable_session) { + fprintf(stderr, "Error getting session.\n"); + return false; + } + + if (!ExpectSessionReused(client_ctx.get(), server_ctx.get(), + not_resumable_session.get(), + false /* expect session not reused */)) { + fprintf(stderr, "Error resuming session with timeout of 0.\n"); + return false; + } + + // Set both context and connection (via callback) default session timeout. + // The connection one is the one that ends up being used. + timeout = 25; + g_current_time.tv_sec = kStartTime; + + SSL_CTX_set_timeout(server_ctx.get(), timeout - 10); + + bssl::UniquePtr ctx_and_cb_session = + CreateClientSession(client_ctx.get(), server_ctx.get()); + if (!ctx_and_cb_session) { + fprintf(stderr, "Error getting session.\n"); + return false; + } + + if (!ExpectSessionReused(client_ctx.get(), server_ctx.get(), + ctx_and_cb_session.get(), + true /* expect session reused */)) { + fprintf(stderr, "Error resuming session with timeout of 0.\n"); + return false; + } + + // Advance the clock just behind the timeout. + g_current_time.tv_sec += timeout - 1; + + if (!ExpectSessionReused(client_ctx.get(), server_ctx.get(), + ctx_and_cb_session.get(), + true /* expect session reused */)) { + fprintf(stderr, "Error resuming session.\n"); + return false; + } + + // Advance the clock one more second. + g_current_time.tv_sec++; + + if (!ExpectSessionReused(client_ctx.get(), server_ctx.get(), + ctx_and_cb_session.get(), + false /* expect session not reused */)) { + fprintf(stderr, "Error resuming session.\n"); + return false; + } + + return true; +} + static int SwitchContext(SSL *ssl, int *out_alert, void *arg) { SSL_CTX *ctx = reinterpret_cast(arg); SSL_set_SSL_CTX(ssl, ctx); @@ -2664,6 +2789,7 @@ int main() { !TestClientHello() || !ForEachVersion(TestSessionIDContext) || !ForEachVersion(TestSessionTimeout) || + !ForEachVersion(TestSessionTimeoutCertCallback) || !ForEachVersion(TestSNICallback) || !TestEarlyCallbackVersionSwitch() || !TestSetVersion() ||