From cd774f11d562508d17d13a59bfb61da739ff9e2f Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Mon, 8 Apr 2024 15:10:04 +0100 Subject: [PATCH] Enable session resumption This implements `SSL_CTX_sess_set_cache_size`, `SSL_CTX_sess_get_cache_size` & `SSL_CTX_set_session_id_context`. --- rustls-libssl/MATRIX.md | 4 +- rustls-libssl/build.rs | 1 - rustls-libssl/src/cache.rs | 79 ++++++++++++++++++++++++++++++++++++++ rustls-libssl/src/entry.rs | 52 ++++++++++++++++--------- rustls-libssl/src/lib.rs | 49 +++++++++++++++++------ 5 files changed, 152 insertions(+), 33 deletions(-) create mode 100644 rustls-libssl/src/cache.rs diff --git a/rustls-libssl/MATRIX.md b/rustls-libssl/MATRIX.md index cbce171..029833e 100644 --- a/rustls-libssl/MATRIX.md +++ b/rustls-libssl/MATRIX.md @@ -179,7 +179,7 @@ | `SSL_CTX_set_recv_max_early_data` | | | | | `SSL_CTX_set_security_callback` | | | | | `SSL_CTX_set_security_level` | | | | -| `SSL_CTX_set_session_id_context` | | :white_check_mark: | :exclamation: [^stub] | +| `SSL_CTX_set_session_id_context` | | :white_check_mark: | :white_check_mark: | | `SSL_CTX_set_session_ticket_cb` | | | | | `SSL_CTX_set_srp_cb_arg` [^deprecatedin_3_0] [^srp] | | | | | `SSL_CTX_set_srp_client_pwd_callback` [^deprecatedin_3_0] [^srp] | | | | @@ -461,7 +461,7 @@ | `SSL_set_security_callback` | | | | | `SSL_set_security_level` | | | | | `SSL_set_session` | :white_check_mark: | :white_check_mark: | :exclamation: [^stub] | -| `SSL_set_session_id_context` | | | :exclamation: [^stub] | +| `SSL_set_session_id_context` | | | | | `SSL_set_session_secret_cb` | | | | | `SSL_set_session_ticket_ext` | | | | | `SSL_set_session_ticket_ext_cb` | | | | diff --git a/rustls-libssl/build.rs b/rustls-libssl/build.rs index 34fb6d2..d676daf 100644 --- a/rustls-libssl/build.rs +++ b/rustls-libssl/build.rs @@ -174,7 +174,6 @@ const ENTRYPOINTS: &[&str] = &[ "SSL_set_post_handshake_auth", "SSL_set_quiet_shutdown", "SSL_set_session", - "SSL_set_session_id_context", "SSL_set_shutdown", "SSL_set_SSL_CTX", "SSL_set_verify", diff --git a/rustls-libssl/src/cache.rs b/rustls-libssl/src/cache.rs new file mode 100644 index 0000000..1be138b --- /dev/null +++ b/rustls-libssl/src/cache.rs @@ -0,0 +1,79 @@ +use std::sync::Arc; + +use rustls::client::ClientSessionMemoryCache; +use rustls::client::ClientSessionStore; +use rustls::server::ServerSessionMemoryCache; +use rustls::server::{ProducesTickets, StoresServerSessions}; + +/// A container for session caches that can live inside +/// an `SSL_CTX` but outlive a rustls `ServerConfig`/`ClientConfig` +pub struct SessionCaches { + size: usize, + context: Vec, + client: Option>, + server: Option>, + ticketer: Option>, +} + +impl SessionCaches { + pub fn with_size(size: usize) -> Self { + // a user who has one `SSL_CTX` for both clients and servers will end + // up with twice as many sessions as this, since rustls caches + // client and server sessions separately. + // + // the common case is to have those separate (it is, for example, + // impossible to configure certs/keys separately for client and + // servers in a given `SSL_CTX`) so this should be ok. + Self { + size, + context: vec![], + client: None, + server: None, + ticketer: None, + } + } + + pub fn get_client(&mut self) -> Arc { + Arc::clone( + self.client + .get_or_insert_with(|| Arc::new(ClientSessionMemoryCache::new(self.size))), + ) + } + + pub fn get_server(&mut self) -> Arc { + Arc::clone( + self.server + .get_or_insert_with(|| ServerSessionMemoryCache::new(self.size)), + ) + } + + pub fn get_ticketer(&mut self) -> Arc { + Arc::clone( + self.ticketer + .get_or_insert_with(|| crate::provider::Ticketer::new().unwrap()), + ) + } + + pub fn set_context(&mut self, context: &[u8]) { + // This is a different behaviour to that described by `SSL_set_session_id_context()`: + // the context isn't bound to an individual session but instead is an epoch for + // the entire set of stored sessions & ticketer keying. + if context != self.context { + context.clone_into(&mut self.context); + self.client = None; + self.server = None; + self.ticketer = None; + } + } + + pub fn size(&self) -> usize { + self.size + } +} + +impl Default for SessionCaches { + fn default() -> Self { + // this is SSL_SESSION_CACHE_MAX_SIZE_DEFAULT + Self::with_size(1024 * 20) + } +} diff --git a/rustls-libssl/src/entry.rs b/rustls-libssl/src/entry.rs index 2349f8c..fe695ab 100644 --- a/rustls-libssl/src/entry.rs +++ b/rustls-libssl/src/entry.rs @@ -258,6 +258,14 @@ entry! { inner.set_servername_callback_context(parg); C_INT_SUCCESS as c_long } + Ok(SslCtrl::SetSessCacheSize) => { + if larg < 0 { + return 0; + } + inner.set_session_cache_size(larg as usize); + C_INT_SUCCESS as c_long + } + Ok(SslCtrl::GetSessCacheSize) => inner.get_session_cache_size() as c_long, Err(()) => { log::warn!("unimplemented _SSL_CTX_ctrl(..., {cmd}, {larg}, ...)"); 0 @@ -766,6 +774,26 @@ entry! { } } +entry! { + pub fn _SSL_CTX_set_session_id_context( + ctx: *mut SSL_CTX, + sid_ctx: *const c_uchar, + sid_ctx_len: c_uint, + ) -> c_int { + let ctx = try_clone_arc!(ctx); + let sid_ctx = try_slice!(sid_ctx, sid_ctx_len); + + match ctx + .lock() + .map_err(|_| Error::cannot_lock()) + .map(|mut ctx| ctx.set_session_id_context(sid_ctx)) + { + Err(e) => e.raise().into(), + Ok(()) => C_INT_SUCCESS, + } + } +} + impl Castable for SSL_CTX { type Ownership = OwnershipArc; type RustType = Mutex; @@ -898,7 +926,10 @@ entry! { C_INT_SUCCESS as i64 } // not a defined operation in the OpenSSL API - Ok(SslCtrl::SetTlsExtServerNameCallback) | Ok(SslCtrl::SetTlsExtServerNameArg) => 0, + Ok(SslCtrl::SetTlsExtServerNameCallback) + | Ok(SslCtrl::SetTlsExtServerNameArg) + | Ok(SslCtrl::SetSessCacheSize) + | Ok(SslCtrl::GetSessCacheSize) => 0, Err(()) => { log::warn!("unimplemented _SSL_ctrl(..., {cmd}, {larg}, ...)"); 0 @@ -1803,6 +1834,8 @@ num_enum! { enum SslCtrl { Mode = 33, SetMsgCallbackArg = 16, + SetSessCacheSize = 42, + GetSessCacheSize = 43, SetTlsExtServerNameCallback = 53, SetTlsExtServerNameArg = 54, SetTlsExtHostname = 55, @@ -1885,23 +1918,6 @@ entry_stub! { pub type SSL_CTX_sess_remove_cb = Option; -entry_stub! { - pub fn _SSL_CTX_set_session_id_context( - _ctx: *mut SSL_CTX, - _sid_ctx: *const c_uchar, - _sid_ctx_len: c_uint, - ) -> c_int; -} - -entry_stub! { - - pub fn _SSL_set_session_id_context( - _ssl: *mut SSL, - _sid_ctx: *const c_uchar, - _sid_ctx_len: c_uint, - ) -> c_int; -} - entry_stub! { pub fn _SSL_CTX_set_keylog_callback(_ctx: *mut SSL_CTX, _cb: SSL_CTX_keylog_cb_func); } diff --git a/rustls-libssl/src/lib.rs b/rustls-libssl/src/lib.rs index c5d1d3e..983c339 100644 --- a/rustls-libssl/src/lib.rs +++ b/rustls-libssl/src/lib.rs @@ -11,6 +11,7 @@ use openssl_sys::{ EVP_PKEY, SSL_ERROR_NONE, SSL_ERROR_SSL, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, X509, X509_STORE, X509_V_ERR_UNSPECIFIED, }; +use rustls::client::Resumption; use rustls::crypto::aws_lc_rs as provider; use rustls::pki_types::{CertificateDer, ServerName}; use rustls::server::{Accepted, Acceptor}; @@ -20,6 +21,7 @@ use rustls::{ }; mod bio; +mod cache; mod callbacks; #[macro_use] mod constants; @@ -215,6 +217,7 @@ pub struct SslContext { method: &'static SslMethod, ex_data: ex_data::ExData, versions: EnabledVersions, + caches: cache::SessionCaches, raw_options: u64, verify_mode: VerifyMode, verify_depth: c_int, @@ -236,6 +239,7 @@ impl SslContext { method, ex_data: ex_data::ExData::default(), versions: EnabledVersions::default(), + caches: cache::SessionCaches::default(), raw_options: 0, verify_mode: VerifyMode::default(), verify_depth: -1, @@ -264,6 +268,11 @@ impl SslContext { self.ex_data.get(idx) } + fn option_is_set(&self, opt: SslOption) -> bool { + let mask = opt as u64; + self.raw_options & mask == mask + } + fn get_options(&self) -> u64 { self.raw_options } @@ -308,6 +317,19 @@ impl SslContext { .unwrap_or_default() } + fn get_session_cache_size(&self) -> usize { + self.caches.size() + } + + fn set_session_cache_size(&mut self, size: usize) { + // divergence: OpenSSL can adjust the cache size without emptying it. + self.caches = cache::SessionCaches::with_size(size); + } + + fn set_session_id_context(&mut self, context: &[u8]) { + self.caches.set_context(context); + } + fn set_max_early_data(&mut self, max: u32) { self.max_early_data = max; } @@ -744,11 +766,7 @@ impl Ssl { None => ServerName::try_from("0.0.0.0").unwrap(), }; - let method = self - .ctx - .lock() - .map(|ctx| ctx.method) - .map_err(|_| error::Error::cannot_lock())?; + let mut ctx = self.ctx.lock().map_err(|_| error::Error::cannot_lock())?; let provider = Arc::new(provider::default_provider()); let verifier = Arc::new(verifier::ServerVerifier::new( @@ -758,7 +776,7 @@ impl Ssl { &self.verify_server_name, )); - let versions = self.versions.reduce_versions(method.client_versions)?; + let versions = self.versions.reduce_versions(ctx.method.client_versions)?; let wants_resolver = ClientConfig::builder_with_provider(provider) .with_protocol_versions(&versions) @@ -773,6 +791,7 @@ impl Ssl { }; config.alpn_protocols.clone_from(&self.alpn); + config.resumption = Resumption::store(ctx.caches.get_client()); let client_conn = ClientConnection::new(Arc::new(config), sni_server_name.clone()) .map_err(error::Error::from_rustls)?; @@ -837,11 +856,7 @@ impl Ssl { } fn init_server_conn(&mut self) -> Result<(), error::Error> { - let method = self - .ctx - .lock() - .map(|ctx| ctx.method) - .map_err(|_| error::Error::cannot_lock())?; + let mut ctx = self.ctx.lock().map_err(|_| error::Error::cannot_lock())?; let provider = Arc::new(provider::default_provider()); let verifier = Arc::new( @@ -858,7 +873,7 @@ impl Ssl { .server_resolver() .ok_or_else(|| error::Error::bad_data("missing server keys"))?; - let versions = self.versions.reduce_versions(method.server_versions)?; + let versions = self.versions.reduce_versions(ctx.method.server_versions)?; let mut config = ServerConfig::builder_with_provider(provider) .with_protocol_versions(&versions) @@ -868,6 +883,10 @@ impl Ssl { config.alpn_protocols = mem::take(&mut self.alpn); config.max_early_data_size = self.max_early_data; + config.session_storage = ctx.caches.get_server(); + if !ctx.option_is_set(SslOption::NoTicket) { + config.ticketer = ctx.caches.get_ticketer(); + } let accepted = match mem::replace(&mut self.conn, ConnState::Nothing) { ConnState::Accepted(accepted) => accepted, @@ -1348,6 +1367,12 @@ impl EnabledVersions { } } +/// Subset of SSL_OP values that we interpret. +#[repr(u64)] +enum SslOption { + NoTicket = 1 << 14, +} + #[cfg(test)] mod tests { use super::*;