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