1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::fmt;
4use core::fmt::{Debug, Formatter};
5use core::sync::atomic::{AtomicUsize, Ordering};
6
7use aws_lc_rs::cipher::{
8 AES_256, AES_256_KEY_LEN, AES_CBC_IV_LEN, DecryptionContext, PaddedBlockDecryptingKey,
9 PaddedBlockEncryptingKey, UnboundCipherKey,
10};
11use aws_lc_rs::{hmac, iv};
12
13use super::ring_like::rand::{SecureRandom, SystemRandom};
14use super::unspecified_err;
15use crate::error::Error;
16#[cfg(debug_assertions)]
17use crate::log::debug;
18use crate::polyfill::try_split_at;
19use crate::rand::GetRandomFailed;
20use crate::server::ProducesTickets;
21use crate::sync::Arc;
22
23pub struct Ticketer {}
25
26impl Ticketer {
27 #[cfg(feature = "std")]
35 pub fn new() -> Result<Arc<dyn ProducesTickets>, Error> {
36 Ok(Arc::new(crate::ticketer::TicketRotator::new(
37 6 * 60 * 60,
38 make_ticket_generator,
39 )?))
40 }
41}
42
43fn make_ticket_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
44 Ok(Box::new(
50 Rfc5077Ticketer::new().map_err(|_| GetRandomFailed)?,
51 ))
52}
53
54struct Rfc5077Ticketer {
56 aes_encrypt_key: PaddedBlockEncryptingKey,
57 aes_decrypt_key: PaddedBlockDecryptingKey,
58 hmac_key: hmac::Key,
59 key_name: [u8; 16],
60 lifetime: u32,
61 maximum_ciphertext_len: AtomicUsize,
62}
63
64impl Rfc5077Ticketer {
65 fn new() -> Result<Self, Error> {
66 let rand = SystemRandom::new();
67
68 let mut aes_key = [0u8; AES_256_KEY_LEN];
70 rand.fill(&mut aes_key)
71 .map_err(|_| GetRandomFailed)?;
72
73 let aes_encrypt_key =
78 UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?;
79 let aes_encrypt_key =
80 PaddedBlockEncryptingKey::cbc_pkcs7(aes_encrypt_key).map_err(unspecified_err)?;
81
82 let aes_decrypt_key =
84 UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?;
85 let aes_decrypt_key =
86 PaddedBlockDecryptingKey::cbc_pkcs7(aes_decrypt_key).map_err(unspecified_err)?;
87
88 let hmac_key = hmac::Key::generate(hmac::HMAC_SHA256, &rand).map_err(unspecified_err)?;
90
91 let mut key_name = [0u8; 16];
93 rand.fill(&mut key_name)
94 .map_err(|_| GetRandomFailed)?;
95
96 Ok(Self {
97 aes_encrypt_key,
98 aes_decrypt_key,
99 hmac_key,
100 key_name,
101 lifetime: 60 * 60 * 12,
102 maximum_ciphertext_len: AtomicUsize::new(0),
103 })
104 }
105}
106
107impl ProducesTickets for Rfc5077Ticketer {
108 fn enabled(&self) -> bool {
109 true
110 }
111
112 fn lifetime(&self) -> u32 {
113 self.lifetime
114 }
115
116 fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
118 let mut encrypted_state = Vec::from(message);
121 let dec_ctx = self
122 .aes_encrypt_key
123 .encrypt(&mut encrypted_state)
124 .ok()?;
125 let iv: &[u8] = (&dec_ctx).try_into().ok()?;
126
127 let mut hmac_data =
134 Vec::with_capacity(self.key_name.len() + iv.len() + 2 + encrypted_state.len());
135 hmac_data.extend(&self.key_name);
136 hmac_data.extend(iv);
137 hmac_data.extend(
138 u16::try_from(encrypted_state.len())
139 .ok()?
140 .to_be_bytes(),
141 );
142 hmac_data.extend(&encrypted_state);
143 let tag = hmac::sign(&self.hmac_key, &hmac_data);
144 let tag = tag.as_ref();
145
146 let mut ciphertext =
153 Vec::with_capacity(self.key_name.len() + iv.len() + encrypted_state.len() + tag.len());
154 ciphertext.extend(self.key_name);
155 ciphertext.extend(iv);
156 ciphertext.extend(encrypted_state);
157 ciphertext.extend(tag);
158
159 self.maximum_ciphertext_len
160 .fetch_max(ciphertext.len(), Ordering::SeqCst);
161
162 Some(ciphertext)
163 }
164
165 fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
166 if ciphertext.len()
167 > self
168 .maximum_ciphertext_len
169 .load(Ordering::SeqCst)
170 {
171 #[cfg(debug_assertions)]
172 debug!("rejected over-length ticket");
173 return None;
174 }
175
176 let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?;
178
179 let (iv, ciphertext) = try_split_at(ciphertext, AES_CBC_IV_LEN)?;
181
182 let tag_len = self
184 .hmac_key
185 .algorithm()
186 .digest_algorithm()
187 .output_len();
188 let (enc_state, mac) = try_split_at(ciphertext, ciphertext.len() - tag_len)?;
189
190 let mut hmac_data =
192 Vec::with_capacity(alleged_key_name.len() + iv.len() + 2 + enc_state.len());
193 hmac_data.extend(alleged_key_name);
194 hmac_data.extend(iv);
195 hmac_data.extend(
196 u16::try_from(enc_state.len())
197 .ok()?
198 .to_be_bytes(),
199 );
200 hmac_data.extend(enc_state);
201 hmac::verify(&self.hmac_key, &hmac_data, mac).ok()?;
202
203 let iv = iv::FixedLength::try_from(iv).ok()?;
205 let dec_context = DecryptionContext::Iv128(iv);
206
207 let mut out = Vec::from(enc_state);
209 let plaintext = self
210 .aes_decrypt_key
211 .decrypt(&mut out, dec_context)
212 .ok()?;
213
214 Some(plaintext.into())
215 }
216}
217
218impl Debug for Rfc5077Ticketer {
219 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
220 f.debug_struct("Rfc5077Ticketer")
222 .field("lifetime", &self.lifetime)
223 .finish()
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use core::time::Duration;
230
231 use pki_types::UnixTime;
232
233 use super::*;
234
235 #[test]
236 fn basic_pairwise_test() {
237 let t = Ticketer::new().unwrap();
238 assert!(t.enabled());
239 let cipher = t.encrypt(b"hello world").unwrap();
240 let plain = t.decrypt(&cipher).unwrap();
241 assert_eq!(plain, b"hello world");
242 }
243
244 #[test]
245 fn refuses_decrypt_before_encrypt() {
246 let t = Ticketer::new().unwrap();
247 assert_eq!(t.decrypt(b"hello"), None);
248 }
249
250 #[test]
251 fn refuses_decrypt_larger_than_largest_encryption() {
252 let t = Ticketer::new().unwrap();
253 let mut cipher = t.encrypt(b"hello world").unwrap();
254 assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec()));
255
256 cipher.push(0);
260 assert_eq!(t.decrypt(&cipher), None);
261 }
262
263 #[test]
264 fn ticketrotator_switching_test() {
265 let t = Arc::new(crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap());
266 let now = UnixTime::now();
267 let cipher1 = t.encrypt(b"ticket 1").unwrap();
268 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
269 {
270 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
272 now.as_secs() + 10,
273 )));
274 }
275 let cipher2 = t.encrypt(b"ticket 2").unwrap();
276 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
277 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
278 {
279 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
281 now.as_secs() + 20,
282 )));
283 }
284 let cipher3 = t.encrypt(b"ticket 3").unwrap();
285 assert!(t.decrypt(&cipher1).is_none());
286 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
287 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
288 }
289
290 #[test]
291 fn ticketrotator_remains_usable_over_temporary_ticketer_creation_failure() {
292 let mut t = crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap();
293 let now = UnixTime::now();
294 let cipher1 = t.encrypt(b"ticket 1").unwrap();
295 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
296 t.generator = fail_generator;
297 {
298 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
301 now.as_secs() + 10,
302 )));
303 }
304
305 let cipher2 = t.encrypt(b"ticket 2").unwrap();
307 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
308 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
309
310 t.generator = make_ticket_generator;
312 {
313 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
314 now.as_secs() + 20,
315 )));
316 }
317 let cipher3 = t.encrypt(b"ticket 3").unwrap();
318 assert!(t.decrypt(&cipher1).is_some());
319 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
320 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
321 }
322
323 #[test]
324 fn ticketswitcher_switching_test() {
325 #[expect(deprecated)]
326 let t = Arc::new(crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap());
327 let now = UnixTime::now();
328 let cipher1 = t.encrypt(b"ticket 1").unwrap();
329 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
330 {
331 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
333 now.as_secs() + 10,
334 )));
335 }
336 let cipher2 = t.encrypt(b"ticket 2").unwrap();
337 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
338 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
339 {
340 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
342 now.as_secs() + 20,
343 )));
344 }
345 let cipher3 = t.encrypt(b"ticket 3").unwrap();
346 assert!(t.decrypt(&cipher1).is_none());
347 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
348 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
349 }
350
351 #[test]
352 fn ticketswitcher_recover_test() {
353 #[expect(deprecated)]
354 let mut t = crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap();
355 let now = UnixTime::now();
356 let cipher1 = t.encrypt(b"ticket 1").unwrap();
357 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
358 t.generator = fail_generator;
359 {
360 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
362 now.as_secs() + 10,
363 )));
364 }
365 t.generator = make_ticket_generator;
366 let cipher2 = t.encrypt(b"ticket 2").unwrap();
367 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
368 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
369 {
370 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
372 now.as_secs() + 20,
373 )));
374 }
375 let cipher3 = t.encrypt(b"ticket 3").unwrap();
376 assert!(t.decrypt(&cipher1).is_none());
377 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
378 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
379 }
380
381 #[test]
382 fn rfc5077ticketer_is_debug_and_producestickets() {
383 use alloc::format;
384
385 use super::*;
386
387 let t = make_ticket_generator().unwrap();
388
389 assert_eq!(format!("{t:?}"), "Rfc5077Ticketer { lifetime: 43200 }");
390 assert!(t.enabled());
391 assert_eq!(t.lifetime(), 43200);
392 }
393
394 fn fail_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
395 Err(GetRandomFailed)
396 }
397}