rustls/crypto/ring/
ticketer.rs1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::fmt;
4use core::fmt::{Debug, Formatter};
5use core::sync::atomic::{AtomicUsize, Ordering};
6
7use subtle::ConstantTimeEq;
8
9use super::ring_like::aead;
10use super::ring_like::rand::{SecureRandom, SystemRandom};
11use crate::error::Error;
12#[cfg(debug_assertions)]
13use crate::log::debug;
14use crate::polyfill::try_split_at;
15use crate::rand::GetRandomFailed;
16use crate::server::ProducesTickets;
17use crate::sync::Arc;
18
19pub struct Ticketer {}
21
22impl Ticketer {
23 #[cfg(feature = "std")]
28 pub fn new() -> Result<Arc<dyn ProducesTickets>, Error> {
29 Ok(Arc::new(crate::ticketer::TicketRotator::new(
30 6 * 60 * 60,
31 make_ticket_generator,
32 )?))
33 }
34}
35
36fn make_ticket_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
37 Ok(Box::new(AeadTicketer::new()?))
38}
39
40struct AeadTicketer {
45 alg: &'static aead::Algorithm,
46 key: aead::LessSafeKey,
47 key_name: [u8; 16],
48 lifetime: u32,
49
50 maximum_ciphertext_len: AtomicUsize,
59}
60
61impl AeadTicketer {
62 fn new() -> Result<Self, GetRandomFailed> {
63 let mut key = [0u8; 32];
64 SystemRandom::new()
65 .fill(&mut key)
66 .map_err(|_| GetRandomFailed)?;
67
68 let key = aead::UnboundKey::new(TICKETER_AEAD, &key).unwrap();
69
70 let mut key_name = [0u8; 16];
71 SystemRandom::new()
72 .fill(&mut key_name)
73 .map_err(|_| GetRandomFailed)?;
74
75 Ok(Self {
76 alg: TICKETER_AEAD,
77 key: aead::LessSafeKey::new(key),
78 key_name,
79 lifetime: 60 * 60 * 12,
80 maximum_ciphertext_len: AtomicUsize::new(0),
81 })
82 }
83}
84
85impl ProducesTickets for AeadTicketer {
86 fn enabled(&self) -> bool {
87 true
88 }
89
90 fn lifetime(&self) -> u32 {
91 self.lifetime
92 }
93
94 fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
96 let mut nonce_buf = [0u8; 12];
98 SystemRandom::new()
99 .fill(&mut nonce_buf)
100 .ok()?;
101 let nonce = aead::Nonce::assume_unique_for_key(nonce_buf);
102 let aad = aead::Aad::from(self.key_name);
103
104 let mut ciphertext = Vec::with_capacity(
111 self.key_name.len() + nonce_buf.len() + message.len() + self.key.algorithm().tag_len(),
112 );
113 ciphertext.extend(self.key_name);
114 ciphertext.extend(nonce_buf);
115 ciphertext.extend(message);
116 let ciphertext = self
117 .key
118 .seal_in_place_separate_tag(
119 nonce,
120 aad,
121 &mut ciphertext[self.key_name.len() + nonce_buf.len()..],
122 )
123 .map(|tag| {
124 ciphertext.extend(tag.as_ref());
125 ciphertext
126 })
127 .ok()?;
128
129 self.maximum_ciphertext_len
130 .fetch_max(ciphertext.len(), Ordering::SeqCst);
131 Some(ciphertext)
132 }
133
134 fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
136 if ciphertext.len()
137 > self
138 .maximum_ciphertext_len
139 .load(Ordering::SeqCst)
140 {
141 #[cfg(debug_assertions)]
142 debug!("rejected over-length ticket");
143 return None;
144 }
145
146 let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?;
147
148 let (nonce, ciphertext) = try_split_at(ciphertext, self.alg.nonce_len())?;
149
150 if ConstantTimeEq::ct_ne(&self.key_name[..], alleged_key_name).into() {
162 #[cfg(debug_assertions)]
163 debug!("rejected ticket with wrong ticket_name");
164 return None;
165 }
166
167 let nonce = aead::Nonce::try_assume_unique_for_key(nonce).ok()?;
169
170 let mut out = Vec::from(ciphertext);
171
172 let plain_len = self
173 .key
174 .open_in_place(nonce, aead::Aad::from(alleged_key_name), &mut out)
175 .ok()?
176 .len();
177 out.truncate(plain_len);
178
179 Some(out)
180 }
181}
182
183impl Debug for AeadTicketer {
184 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
185 f.debug_struct("AeadTicketer")
187 .field("alg", &self.alg)
188 .field("lifetime", &self.lifetime)
189 .finish()
190 }
191}
192
193static TICKETER_AEAD: &aead::Algorithm = &aead::CHACHA20_POLY1305;
194
195#[cfg(test)]
196mod tests {
197 use core::time::Duration;
198
199 use pki_types::UnixTime;
200
201 use super::*;
202
203 #[test]
204 fn basic_pairwise_test() {
205 let t = Ticketer::new().unwrap();
206 assert!(t.enabled());
207 let cipher = t.encrypt(b"hello world").unwrap();
208 let plain = t.decrypt(&cipher).unwrap();
209 assert_eq!(plain, b"hello world");
210 }
211
212 #[test]
213 fn refuses_decrypt_before_encrypt() {
214 let t = Ticketer::new().unwrap();
215 assert_eq!(t.decrypt(b"hello"), None);
216 }
217
218 #[test]
219 fn refuses_decrypt_larger_than_largest_encryption() {
220 let t = Ticketer::new().unwrap();
221 let mut cipher = t.encrypt(b"hello world").unwrap();
222 assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec()));
223
224 cipher.push(0);
228 assert_eq!(t.decrypt(&cipher), None);
229 }
230
231 #[test]
232 fn ticketrotator_switching_test() {
233 let t = Arc::new(crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap());
234 let now = UnixTime::now();
235 let cipher1 = t.encrypt(b"ticket 1").unwrap();
236 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
237 {
238 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
240 now.as_secs() + 10,
241 )));
242 }
243 let cipher2 = t.encrypt(b"ticket 2").unwrap();
244 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
245 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
246 {
247 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
249 now.as_secs() + 20,
250 )));
251 }
252 let cipher3 = t.encrypt(b"ticket 3").unwrap();
253 assert!(t.decrypt(&cipher1).is_none());
254 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
255 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
256 }
257
258 #[test]
259 fn ticketrotator_remains_usable_over_temporary_ticketer_creation_failure() {
260 let mut t = crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap();
261 let now = UnixTime::now();
262 let cipher1 = t.encrypt(b"ticket 1").unwrap();
263 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
264 t.generator = fail_generator;
265 {
266 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
269 now.as_secs() + 10,
270 )));
271 }
272
273 let cipher2 = t.encrypt(b"ticket 2").unwrap();
275 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
276 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
277
278 t.generator = make_ticket_generator;
280 {
281 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
282 now.as_secs() + 20,
283 )));
284 }
285 let cipher3 = t.encrypt(b"ticket 3").unwrap();
286 assert!(t.decrypt(&cipher1).is_some());
287 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
288 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
289 }
290
291 #[test]
292 fn ticketswitcher_switching_test() {
293 #[expect(deprecated)]
294 let t = Arc::new(crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap());
295 let now = UnixTime::now();
296 let cipher1 = t.encrypt(b"ticket 1").unwrap();
297 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
298 {
299 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
301 now.as_secs() + 10,
302 )));
303 }
304 let cipher2 = t.encrypt(b"ticket 2").unwrap();
305 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
306 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
307 {
308 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
310 now.as_secs() + 20,
311 )));
312 }
313 let cipher3 = t.encrypt(b"ticket 3").unwrap();
314 assert!(t.decrypt(&cipher1).is_none());
315 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
316 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
317 }
318
319 #[test]
320 fn ticketswitcher_recover_test() {
321 #[expect(deprecated)]
322 let mut t = crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap();
323 let now = UnixTime::now();
324 let cipher1 = t.encrypt(b"ticket 1").unwrap();
325 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
326 t.generator = fail_generator;
327 {
328 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
330 now.as_secs() + 10,
331 )));
332 }
333 t.generator = make_ticket_generator;
334 let cipher2 = t.encrypt(b"ticket 2").unwrap();
335 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
336 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
337 {
338 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
340 now.as_secs() + 20,
341 )));
342 }
343 let cipher3 = t.encrypt(b"ticket 3").unwrap();
344 assert!(t.decrypt(&cipher1).is_none());
345 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
346 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
347 }
348
349 #[test]
350 fn aeadticketer_is_debug_and_producestickets() {
351 use alloc::format;
352
353 use super::*;
354
355 let t = make_ticket_generator().unwrap();
356
357 let expect = format!("AeadTicketer {{ alg: {TICKETER_AEAD:?}, lifetime: 43200 }}");
358 assert_eq!(format!("{t:?}"), expect);
359 assert!(t.enabled());
360 assert_eq!(t.lifetime(), 43200);
361 }
362
363 fn fail_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
364 Err(GetRandomFailed)
365 }
366}