Skip to main content

rustls/client/
handy.rs

1use core::hash::Hasher;
2
3use super::config::{ClientCredentialResolver, ClientSessionStore};
4use super::{ClientSessionKey, CredentialRequest, Tls12Session, Tls13Session};
5use crate::crypto::SelectedCredential;
6use crate::crypto::kx::NamedGroup;
7use crate::enums::CertificateType;
8
9/// An implementer of [`ClientSessionStore`] which does nothing.
10#[derive(Debug)]
11pub(super) struct NoClientSessionStorage;
12
13impl ClientSessionStore for NoClientSessionStorage {
14    fn set_kx_hint(&self, _: ClientSessionKey<'static>, _: NamedGroup) {}
15
16    fn kx_hint(&self, _: &ClientSessionKey<'_>) -> Option<NamedGroup> {
17        None
18    }
19
20    fn set_tls12_session(&self, _: ClientSessionKey<'static>, _: Tls12Session) {}
21
22    fn tls12_session(&self, _: &ClientSessionKey<'_>) -> Option<Tls12Session> {
23        None
24    }
25
26    fn remove_tls12_session(&self, _: &ClientSessionKey<'_>) {}
27
28    fn insert_tls13_ticket(&self, _: ClientSessionKey<'static>, _: Tls13Session) {}
29
30    fn take_tls13_ticket(&self, _: &ClientSessionKey<'_>) -> Option<Tls13Session> {
31        None
32    }
33}
34
35mod cache {
36    use alloc::collections::VecDeque;
37    use core::fmt;
38
39    use super::*;
40    use crate::client::Tls13Session;
41    use crate::crypto::kx::NamedGroup;
42    use crate::limited_cache;
43    use crate::lock::Mutex;
44
45    const MAX_TLS13_TICKETS_PER_SERVER: usize = 8;
46
47    struct ServerData {
48        kx_hint: Option<NamedGroup>,
49
50        // Zero or one TLS1.2 sessions.
51        tls12: Option<Tls12Session>,
52
53        // Up to MAX_TLS13_TICKETS_PER_SERVER TLS1.3 tickets, oldest first.
54        tls13: VecDeque<Tls13Session>,
55    }
56
57    impl Default for ServerData {
58        fn default() -> Self {
59            Self {
60                kx_hint: None,
61                tls12: None,
62                tls13: VecDeque::with_capacity(MAX_TLS13_TICKETS_PER_SERVER),
63            }
64        }
65    }
66
67    /// An implementer of `ClientSessionStore` that stores everything
68    /// in memory.
69    ///
70    /// It enforces a limit on the number of entries to bound memory usage.
71    pub struct ClientSessionMemoryCache {
72        servers: Mutex<limited_cache::LimitedCache<ClientSessionKey<'static>, ServerData>>,
73    }
74
75    impl ClientSessionMemoryCache {
76        /// Make a new `ClientSessionMemoryCache`.
77        ///
78        /// `size` determines the maximum number of servers to remember:
79        /// `size` divided by `MAX_TLS13_TICKETS_PER_SERVER` (8), rounded
80        /// up. Each server keeps up to 8 TLS 1.3 tickets. When the maximum
81        /// is reached, the server that was added first is evicted.
82        ///
83        /// For example, `new(100)` remembers up to 13 servers. Any `size`
84        /// from 1 to 8 remembers one server, and a `size` of 0 remembers
85        /// none.
86        pub fn new(size: usize) -> Self {
87            // Round up so that any non-zero size holds at least one server.
88            let max_servers = size.saturating_add(MAX_TLS13_TICKETS_PER_SERVER - 1)
89                / MAX_TLS13_TICKETS_PER_SERVER;
90            Self {
91                servers: Mutex::new(limited_cache::LimitedCache::new(max_servers)),
92            }
93        }
94    }
95
96    impl ClientSessionStore for ClientSessionMemoryCache {
97        fn set_kx_hint(&self, key: ClientSessionKey<'static>, group: NamedGroup) {
98            self.servers
99                .lock()
100                .unwrap()
101                .get_or_insert_default_and_edit(key, |data| data.kx_hint = Some(group));
102        }
103
104        fn kx_hint(&self, key: &ClientSessionKey<'_>) -> Option<NamedGroup> {
105            self.servers
106                .lock()
107                .unwrap()
108                .get(key)
109                .and_then(|sd| sd.kx_hint)
110        }
111
112        fn set_tls12_session(&self, key: ClientSessionKey<'static>, value: Tls12Session) {
113            self.servers
114                .lock()
115                .unwrap()
116                .get_or_insert_default_and_edit(key, |data| data.tls12 = Some(value));
117        }
118
119        fn tls12_session(&self, key: &ClientSessionKey<'_>) -> Option<Tls12Session> {
120            self.servers
121                .lock()
122                .unwrap()
123                .get(key)
124                .and_then(|sd| sd.tls12.as_ref().cloned())
125        }
126
127        fn remove_tls12_session(&self, key: &ClientSessionKey<'static>) {
128            self.servers
129                .lock()
130                .unwrap()
131                .get_mut(key)
132                .and_then(|data| data.tls12.take());
133        }
134
135        fn insert_tls13_ticket(&self, key: ClientSessionKey<'static>, value: Tls13Session) {
136            self.servers
137                .lock()
138                .unwrap()
139                .get_or_insert_default_and_edit(key, |data| {
140                    if data.tls13.len() == data.tls13.capacity() {
141                        data.tls13.pop_front();
142                    }
143                    data.tls13.push_back(value);
144                });
145        }
146
147        fn take_tls13_ticket(&self, key: &ClientSessionKey<'static>) -> Option<Tls13Session> {
148            self.servers
149                .lock()
150                .unwrap()
151                .get_mut(key)
152                .and_then(|data| data.tls13.pop_back())
153        }
154    }
155
156    impl fmt::Debug for ClientSessionMemoryCache {
157        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158            // Note: we omit self.servers as it may contain sensitive data.
159            f.debug_struct("ClientSessionMemoryCache")
160                .finish_non_exhaustive()
161        }
162    }
163}
164
165pub use cache::ClientSessionMemoryCache;
166
167#[derive(Debug)]
168pub(super) struct FailResolveClientCert {}
169
170impl ClientCredentialResolver for FailResolveClientCert {
171    fn resolve(&self, _: &CredentialRequest<'_>) -> Option<SelectedCredential> {
172        None
173    }
174
175    fn supported_certificate_types(&self) -> &'static [CertificateType] {
176        &[]
177    }
178
179    fn hash_config(&self, _: &mut dyn Hasher) {}
180}
181
182#[cfg(test)]
183mod tests {
184    use alloc::vec::Vec;
185    use core::time::Duration;
186
187    use pki_types::{CertificateDer, ServerName, UnixTime};
188
189    use super::{ClientSessionMemoryCache, NoClientSessionStorage};
190    use crate::client::{
191        ClientSessionKey, ClientSessionStore, Tls12Session, Tls13ClientSessionInput, Tls13Session,
192    };
193    use crate::crypto::kx::NamedGroup;
194    use crate::crypto::{
195        CertificateIdentity, CipherSuite, Identity, TEST_PROVIDER, tls12_suite, tls13_suite,
196    };
197    use crate::msgs::{
198        NewSessionTicketExtensions, NewSessionTicketPayloadTls13, SessionId, SizedPayload,
199    };
200    use crate::sync::Arc;
201    use crate::tls13::Tls13ProtocolSuite;
202    use crate::verify::VerifiedIdentity;
203
204    #[test]
205    fn test_noclientsessionstorage_does_nothing() {
206        let c = NoClientSessionStorage {};
207        let key = session_key("example.com");
208        let now = UnixTime::now();
209
210        c.set_kx_hint(key.clone(), NamedGroup::X25519);
211        assert_eq!(None, c.kx_hint(&key));
212
213        {
214            c.set_tls12_session(
215                key.clone(),
216                Tls12Session::new(
217                    tls12_suite(CipherSuite(0xff12), &TEST_PROVIDER),
218                    SessionId::empty(),
219                    Arc::new(SizedPayload::empty()),
220                    &[0u8; 48],
221                    VerifiedIdentity::assertion(Identity::X509(CertificateIdentity {
222                        end_entity: CertificateDer::from(&[][..]),
223                        intermediates: Vec::new(),
224                    })),
225                    now,
226                    Duration::ZERO,
227                    true,
228                ),
229            );
230            assert!(c.tls12_session(&key).is_none());
231            c.remove_tls12_session(&key);
232        }
233
234        c.insert_tls13_ticket(key.clone(), tls13_session(now));
235        assert!(c.take_tls13_ticket(&key).is_none());
236    }
237
238    /// For each `size` from 1 to 8, the cache keeps one server's key
239    /// exchange hint and TLS 1.3 ticket.
240    #[test]
241    fn test_clientsessionmemorycache_small_sizes_retain_server() {
242        for size in 1..=8 {
243            let c = ClientSessionMemoryCache::new(size);
244            let key = session_key("example.com");
245
246            c.set_kx_hint(key.clone(), NamedGroup::X25519);
247            assert_eq!(c.kx_hint(&key), Some(NamedGroup::X25519), "size {size}");
248
249            c.insert_tls13_ticket(key.clone(), tls13_session(UnixTime::now()));
250            assert!(c.take_tls13_ticket(&key).is_some(), "size {size}");
251        }
252    }
253
254    /// A cache of size 9 remembers two servers: after three servers are
255    /// added, the first is evicted and the other two remain.
256    #[test]
257    fn test_clientsessionmemorycache_evicts_oldest_server() {
258        let c = ClientSessionMemoryCache::new(9);
259        let first = session_key("first.example.com");
260        let second = session_key("second.example.com");
261        let third = session_key("third.example.com");
262
263        c.set_kx_hint(first.clone(), NamedGroup::X25519);
264        c.set_kx_hint(second.clone(), NamedGroup::X25519);
265        c.set_kx_hint(third.clone(), NamedGroup::X25519);
266
267        assert_eq!(c.kx_hint(&first), None);
268        assert_eq!(c.kx_hint(&second), Some(NamedGroup::X25519));
269        assert_eq!(c.kx_hint(&third), Some(NamedGroup::X25519));
270    }
271
272    /// Test helper: builds a cache key for `name` with a fixed (all-zero)
273    /// config hash, so keys differ only by server name.
274    fn session_key(name: &'static str) -> ClientSessionKey<'static> {
275        ClientSessionKey {
276            config_hash: Default::default(),
277            server_name: ServerName::try_from(name).unwrap(),
278        }
279    }
280
281    /// Test helper: builds a placeholder TLS 1.3 ticket. Its contents are
282    /// empty or dummy values; the tests only check whether the cache
283    /// keeps it.
284    fn tls13_session(now: UnixTime) -> Tls13Session {
285        Tls13Session::new(
286            &NewSessionTicketPayloadTls13 {
287                lifetime: Duration::ZERO,
288                age_add: 0,
289                nonce: SizedPayload::empty(),
290                ticket: Arc::new(SizedPayload::empty()),
291                extensions: NewSessionTicketExtensions {
292                    max_early_data_size: None,
293                },
294            },
295            Tls13ClientSessionInput {
296                suite: Tls13ProtocolSuite::Tcp(tls13_suite(CipherSuite(0xff13), &TEST_PROVIDER)),
297                peer_identity: VerifiedIdentity::assertion(Identity::X509(CertificateIdentity {
298                    end_entity: CertificateDer::from(&[][..]),
299                    intermediates: Vec::new(),
300                })),
301                quic_params: None,
302            },
303            &[],
304            now,
305        )
306    }
307}