client: use type + builder pattern for resumption config API

Originally developed in #1259.

Co-authored-by: Daniel McCarney <daniel@binaryparadox.net>
Co-authored-by: Jacob Hoffman-Andrews <github@hoffman-andrews.com>
This commit is contained in:
Dirkjan Ochtman 2023-03-29 09:21:50 +02:00 committed by ctz
parent 2ac21fb684
commit 8a2a87b240
11 changed files with 116 additions and 46 deletions

View File

@ -429,7 +429,9 @@ fn make_config(args: &Args) -> Arc<rustls::ClientConfig> {
config.key_log = Arc::new(rustls::KeyLogFile::new());
if args.flag_no_tickets {
config.tls12_resumption = Some(rustls::client::Tls12Resumption::SessionIdOnly);
config.resumption = config
.resumption
.tls12_resumption(rustls::client::Tls12Resumption::SessionIdOnly);
}
if args.flag_no_sni {

View File

@ -11,7 +11,7 @@ use std::ops::DerefMut;
use std::sync::Arc;
use std::time::{Duration, Instant};
use rustls::client::{ClientSessionMemoryCache, NoClientSessionStorage};
use rustls::client::Resumption;
use rustls::server::{
AllowAnyAuthenticatedClient, NoClientAuth, NoServerSessionStorage, ServerSessionMemoryCache,
};
@ -358,9 +358,9 @@ fn make_client_config(
};
if resume != ResumptionParam::No {
cfg.session_storage = ClientSessionMemoryCache::new(128);
cfg.resumption = Resumption::in_memory_sessions(128);
} else {
cfg.session_storage = Arc::new(NoClientSessionStorage {});
cfg.resumption = Resumption::disabled();
}
cfg

View File

@ -4,7 +4,7 @@
// https://boringssl.googlesource.com/boringssl/+/master/ssl/test
//
use rustls::client::{ClientConfig, ClientConnection};
use rustls::client::{ClientConfig, ClientConnection, Resumption};
use rustls::internal::msgs::codec::Codec;
use rustls::internal::msgs::persist;
use rustls::server::{ClientHello, ServerConfig, ServerConnection};
@ -466,7 +466,7 @@ impl ClientCacheWithoutKxHints {
fn new(delay: u32) -> Arc<ClientCacheWithoutKxHints> {
Arc::new(ClientCacheWithoutKxHints {
delay,
storage: client::ClientSessionMemoryCache::new(32),
storage: Arc::new(client::ClientSessionMemoryCache::new(32)),
})
}
}
@ -503,7 +503,7 @@ impl client::ClientSessionStore for ClientCacheWithoutKxHints {
) {
value.rewind_epoch(self.delay);
self.storage
.insert_tls13_ticket(server_name, value);
.insert_tls13_ticket(server_name, value)
}
fn take_tls13_ticket(
@ -550,8 +550,7 @@ fn make_client_cfg(opts: &Options) -> Arc<ClientConfig> {
});
}
let persist = ClientCacheWithoutKxHints::new(opts.resumption_delay);
cfg.session_storage = persist;
cfg.resumption = Resumption::store(ClientCacheWithoutKxHints::new(opts.resumption_delay));
cfg.enable_sni = opts.use_sni;
cfg.max_fragment_size = opts.max_fragment;

View File

@ -7,7 +7,7 @@ use crate::suites::SupportedCipherSuite;
use crate::verify::{self, CertificateTransparencyPolicy};
use crate::{anchors, key, versions};
use super::Tls12Resumption;
use super::client_conn::Resumption;
use std::marker::PhantomData;
use std::sync::Arc;
@ -175,10 +175,9 @@ impl ConfigBuilder<ClientConfig, WantsClientCert> {
cipher_suites: self.state.cipher_suites,
kx_groups: self.state.kx_groups,
alpn_protocols: Vec::new(),
session_storage: handy::ClientSessionMemoryCache::new(256),
resumption: Resumption::default(),
max_fragment_size: None,
client_auth_cert_resolver,
tls12_resumption: Some(Tls12Resumption::SessionIdOrTickets),
versions: self.state.versions,
enable_sni: true,
verifier: self.state.verifier,

View File

@ -17,6 +17,7 @@ use crate::versions;
use crate::ExtractedSecrets;
use crate::KeyLog;
use super::handy::{ClientSessionMemoryCache, NoClientSessionStorage};
use super::hs;
use std::error::Error as StdError;
@ -116,7 +117,8 @@ pub trait ResolvesClientCert: Send + Sync {
/// # Defaults
///
/// * [`ClientConfig::max_fragment_size`]: the default is `None`: TLS packets are not fragmented to a specific size.
/// * [`ClientConfig::session_storage`]: the default stores 256 sessions in memory.
/// * [`ClientConfig::resumption`]: supports resumption with up to 256 server names, using session
/// ids or tickets, with a max of eight tickets per server.
/// * [`ClientConfig::alpn_protocols`]: the default is empty -- no ALPN protocol is negotiated.
/// * [`ClientConfig::key_log`]: key material is not logged.
#[derive(Clone)]
@ -135,8 +137,8 @@ pub struct ClientConfig {
/// If empty, no ALPN extension is sent.
pub alpn_protocols: Vec<Vec<u8>>,
/// How we store session data or tickets.
pub session_storage: Arc<dyn ClientSessionStore>,
/// How and when the client can resume a previous session.
pub resumption: Resumption,
/// The maximum size of TLS message we'll emit. If None, we don't limit TLS
/// message lengths except to the 2**16 limit specified in the standard.
@ -150,13 +152,6 @@ pub struct ClientConfig {
/// How to decide what client auth certificate/keys to use.
pub client_auth_cert_resolver: Arc<dyn ResolvesClientCert>,
/// Whether to support RFC5077 tickets. You must provide a working
/// `session_storage` member for this to have any meaningful
/// effect.
///
/// The default is true.
pub tls12_resumption: Option<Tls12Resumption>,
/// Supported versions, in no particular order. The default
/// is all supported versions.
pub(super) versions: versions::EnabledVersions,
@ -190,6 +185,8 @@ pub struct ClientConfig {
/// What mechanisms to support for resuming a TLS 1.2 session.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Tls12Resumption {
/// Disable 1.2 resumption.
Disabled,
/// Support 1.2 resumption using session ids only.
SessionIdOnly,
/// Support 1.2 resumption using session ids or RFC 5077 tickets.
@ -205,8 +202,8 @@ impl fmt::Debug for ClientConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ClientConfig")
.field("alpn_protocols", &self.alpn_protocols)
.field("resumption", &self.resumption)
.field("max_fragment_size", &self.max_fragment_size)
.field("tls12_resumption", &self.tls12_resumption)
.field("enable_sni", &self.enable_sni)
.field("enable_early_data", &self.enable_early_data)
.finish_non_exhaustive()
@ -250,6 +247,72 @@ impl ClientConfig {
}
}
/// Configuration for how/when a client is allowed to resume a previous session.
#[derive(Clone)]
pub struct Resumption {
/// How we store session data or tickets. The default is to use an in-memory
/// [ClientSessionMemoryCache].
pub(super) store: Arc<dyn ClientSessionStore>,
/// What mechanism is used for resuming a TLS 1.2 session.
pub(super) tls12_resumption: Tls12Resumption,
}
impl Resumption {
/// Create a new `Resumption` that stores data for the given number of sessions in memory.
///
/// This is the default `Resumption` choice, and enables resuming a TLS 1.2 session with
/// a session id or RFC 5077 ticket.
pub fn in_memory_sessions(num: usize) -> Self {
Self {
store: Arc::new(ClientSessionMemoryCache::new(num)),
tls12_resumption: Tls12Resumption::SessionIdOrTickets,
}
}
/// Use a custom [`ClientSessionStore`] implementation to store sessions.
///
/// By default, enables resuming a TLS 1.2 session with a session id or RFC 5077 ticket.
pub fn store(store: Arc<dyn ClientSessionStore>) -> Self {
Self {
store,
tls12_resumption: Tls12Resumption::SessionIdOrTickets,
}
}
/// Disable all use of session resumption.
pub fn disabled() -> Self {
Self {
store: Arc::new(NoClientSessionStorage),
tls12_resumption: Tls12Resumption::Disabled,
}
}
/// Configure whether TLS 1.2 sessions may be resumed, and by what mechanism.
///
/// This is meaningless if you've disabled resumption entirely.
pub fn tls12_resumption(mut self, tls12: Tls12Resumption) -> Self {
self.tls12_resumption = tls12;
self
}
}
impl fmt::Debug for Resumption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Resumption")
.field("tls12_resumption", &self.tls12_resumption)
.finish()
}
}
impl Default for Resumption {
/// Create an in-memory session store resumption with up to 256 server names, allowing
/// a TLS 1.2 session to resume with a session id or RFC 5077 ticket.
fn default() -> Self {
Self::in_memory_sessions(256)
}
}
/// Encodes ways a client can know the expected name of the server.
///
/// This currently covers knowing the DNS name of the server, but

View File

@ -12,7 +12,7 @@ use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
/// An implementer of `ClientSessionStore` which does nothing.
pub struct NoClientSessionStorage {}
pub(super) struct NoClientSessionStorage;
impl client::ClientSessionStore for NoClientSessionStorage {
fn set_kx_hint(&self, _: &ServerName, _: NamedGroup) {}
@ -71,12 +71,12 @@ pub struct ClientSessionMemoryCache {
impl ClientSessionMemoryCache {
/// Make a new ClientSessionMemoryCache. `size` is the
/// maximum number of stored sessions.
pub fn new(size: usize) -> Arc<Self> {
pub fn new(size: usize) -> Self {
let max_servers =
size.saturating_add(MAX_TLS13_TICKETS_PER_SERVER - 1) / MAX_TLS13_TICKETS_PER_SERVER;
Arc::new(Self {
Self {
servers: Mutex::new(limited_cache::LimitedCache::new(max_servers)),
})
}
}
}

View File

@ -45,14 +45,16 @@ fn find_session(
) -> Option<persist::Retrieved<ClientSessionValue>> {
#[allow(clippy::let_and_return, clippy::unnecessary_lazy_evaluations)]
let found = config
.session_storage
.resumption
.store
.take_tls13_ticket(server_name)
.map(ClientSessionValue::Tls13)
.or_else(|| {
#[cfg(feature = "tls12")]
{
config
.session_storage
.resumption
.store
.tls12_session(server_name)
.map(ClientSessionValue::Tls12)
}
@ -386,7 +388,7 @@ fn prepare_resumption<'a>(
Some(resuming) if !resuming.ticket().is_empty() => resuming,
_ => {
if config.supports_version(ProtocolVersion::TLSv1_3)
|| config.tls12_resumption == Some(Tls12Resumption::SessionIdOrTickets)
|| config.resumption.tls12_resumption == Tls12Resumption::SessionIdOrTickets
{
// If we don't have a ticket, request one.
exts.push(ClientExtension::SessionTicket(ClientSessionTicket::Request));
@ -400,7 +402,7 @@ fn prepare_resumption<'a>(
None => {
// TLS 1.2; send the ticket if we have support this protocol version
if config.supports_version(ProtocolVersion::TLSv1_2)
&& config.tls12_resumption == Some(Tls12Resumption::SessionIdOrTickets)
&& config.resumption.tls12_resumption == Tls12Resumption::SessionIdOrTickets
{
exts.push(ClientExtension::SessionTicket(ClientSessionTicket::Offer(
Payload::new(resuming.ticket()),

View File

@ -1019,7 +1019,8 @@ impl ExpectFinished {
);
self.config
.session_storage
.resumption
.store
.set_tls12_session(&self.server_name, session_value);
}
}

View File

@ -140,7 +140,8 @@ pub(super) fn handle_server_hello(
// Remember what KX group the server liked for next time.
config
.session_storage
.resumption
.store
.set_kx_hint(&server_name, their_key_share.group);
// If we change keying when a subsequent handshake message is being joined,
@ -190,7 +191,8 @@ pub(super) fn initial_key_share(
server_name: &ServerName,
) -> Result<kx::KeyExchange, Error> {
let group = config
.session_storage
.resumption
.store
.kx_hint(server_name)
.and_then(|group| kx::KeyExchange::choose(group, &config.kx_groups))
.unwrap_or_else(|| {
@ -883,7 +885,8 @@ impl State<ClientConnectionData> for ExpectFinished {
/* We're now sure this server supports TLS1.3. But if we run out of TLS1.3 tickets
* when connecting to it again, we definitely don't want to attempt a TLS1.2 resumption. */
st.config
.session_storage
.resumption
.store
.remove_tls12_session(&st.server_name);
/* Now move to our application traffic keys. */
@ -892,7 +895,7 @@ impl State<ClientConnectionData> for ExpectFinished {
cx.common.start_traffic();
let st = ExpectTraffic {
session_storage: Arc::clone(&st.config.session_storage),
session_storage: Arc::clone(&st.config.resumption.store),
server_name: st.server_name,
suite: st.suite,
transcript: st.transcript,

View File

@ -409,9 +409,10 @@ pub mod client {
pub use builder::{WantsClientCert, WantsTransparencyPolicyOrClientCert};
pub use client_conn::{
ClientConfig, ClientConnection, ClientConnectionData, ClientSessionStore,
InvalidDnsNameError, ResolvesClientCert, ServerName, Tls12Resumption, WriteEarlyData,
InvalidDnsNameError, ResolvesClientCert, Resumption, ServerName, Tls12Resumption,
WriteEarlyData,
};
pub use handy::{ClientSessionMemoryCache, NoClientSessionStorage};
pub use handy::ClientSessionMemoryCache;
#[cfg(feature = "dangerous_configuration")]
pub use crate::verify::{

View File

@ -8,7 +8,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::sync::Mutex;
use rustls::client::ResolvesClientCert;
use rustls::client::{ResolvesClientCert, Resumption};
use rustls::internal::msgs::base::Payload;
use rustls::internal::msgs::codec::Codec;
use rustls::server::{AllowAnyAnonymousOrAuthenticatedClient, ClientHello, ResolvesServerCert};
@ -2678,7 +2678,7 @@ struct ClientStorage {
impl ClientStorage {
fn new() -> Self {
Self {
storage: rustls::client::ClientSessionMemoryCache::new(1024),
storage: Arc::new(rustls::client::ClientSessionMemoryCache::new(1024)),
ops: Mutex::new(Vec::new()),
}
}
@ -2908,7 +2908,7 @@ fn early_data_configs() -> (Arc<ClientConfig>, Arc<ServerConfig>) {
let kt = KeyType::Rsa;
let mut client_config = make_client_config(kt);
client_config.enable_early_data = true;
client_config.session_storage = Arc::new(ClientStorage::new());
client_config.resumption = Resumption::store(Arc::new(ClientStorage::new()));
let mut server_config = make_server_config(kt);
server_config.max_early_data_size = 1234;
@ -3725,7 +3725,7 @@ fn test_client_sends_helloretryrequest() {
);
let storage = Arc::new(ClientStorage::new());
client_config.session_storage = storage.clone();
client_config.resumption = Resumption::store(storage.clone());
// but server only accepts x25519, so a HRR is required
let server_config =
@ -3823,13 +3823,13 @@ fn test_client_attempts_to_use_unsupported_kx_group() {
// into kx group cache.
let mut client_config_1 =
make_client_config_with_kx_groups(KeyType::Rsa, &[&rustls::kx_group::X25519]);
client_config_1.session_storage = shared_storage.clone();
client_config_1.resumption = Resumption::store(shared_storage.clone());
// second, client only supports secp-384 and so kx group cache
// contains an unusable value.
let mut client_config_2 =
make_client_config_with_kx_groups(KeyType::Rsa, &[&rustls::kx_group::SECP384R1]);
client_config_2.session_storage = shared_storage.clone();
client_config_2.resumption = Resumption::store(shared_storage.clone());
let server_config = make_server_config(KeyType::Rsa);
@ -3869,7 +3869,7 @@ fn test_tls13_client_resumption_does_not_reuse_tickets() {
let shared_storage = Arc::new(ClientStorage::new());
let mut client_config = make_client_config(KeyType::Rsa);
client_config.session_storage = shared_storage.clone();
client_config.resumption = Resumption::store(shared_storage.clone());
let client_config = Arc::new(client_config);
let mut server_config = make_server_config(KeyType::Rsa);
@ -4170,7 +4170,7 @@ fn test_client_rejects_illegal_tls13_ccs() {
fn test_client_tls12_no_resume_after_server_downgrade() {
let mut client_config = common::make_client_config(KeyType::Ed25519);
let client_storage = Arc::new(ClientStorage::new());
client_config.session_storage = client_storage.clone();
client_config.resumption = Resumption::store(client_storage.clone());
let client_config = Arc::new(client_config);
let server_config_1 = Arc::new(common::finish_server_config(