portable_rustls/crypto/aws_lc_rs/
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 aws_lc_rs::cipher::{
8 DecryptionContext, PaddedBlockDecryptingKey, PaddedBlockEncryptingKey, UnboundCipherKey,
9 AES_256, AES_256_KEY_LEN, AES_CBC_IV_LEN,
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 #[cfg(not(feature = "std"))]
50 pub fn new<M: crate::lock::MakeMutex>(
51 time_provider: &'static dyn TimeProvider,
52 ) -> Result<Arc<dyn ProducesTickets>, Error> {
53 Ok(Arc::new(crate::ticketer::TicketSwitcher::new::<M>(
54 6 * 60 * 60,
55 make_ticket_generator,
56 time_provider,
57 )?))
58 }
59}
60
61fn make_ticket_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
62 Ok(Box::new(
68 Rfc5077Ticketer::new().map_err(|_| GetRandomFailed)?,
69 ))
70}
71
72struct Rfc5077Ticketer {
74 aes_encrypt_key: PaddedBlockEncryptingKey,
75 aes_decrypt_key: PaddedBlockDecryptingKey,
76 hmac_key: hmac::Key,
77 key_name: [u8; 16],
78 lifetime: u32,
79 maximum_ciphertext_len: AtomicUsize,
80}
81
82impl Rfc5077Ticketer {
83 fn new() -> Result<Self, Error> {
84 let rand = SystemRandom::new();
85
86 let mut aes_key = [0u8; AES_256_KEY_LEN];
88 rand.fill(&mut aes_key)
89 .map_err(|_| GetRandomFailed)?;
90
91 let aes_encrypt_key =
96 UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?;
97 let aes_encrypt_key =
98 PaddedBlockEncryptingKey::cbc_pkcs7(aes_encrypt_key).map_err(unspecified_err)?;
99
100 let aes_decrypt_key =
102 UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?;
103 let aes_decrypt_key =
104 PaddedBlockDecryptingKey::cbc_pkcs7(aes_decrypt_key).map_err(unspecified_err)?;
105
106 let hmac_key = hmac::Key::generate(hmac::HMAC_SHA256, &rand).map_err(unspecified_err)?;
108
109 let mut key_name = [0u8; 16];
111 rand.fill(&mut key_name)
112 .map_err(|_| GetRandomFailed)?;
113
114 Ok(Self {
115 aes_encrypt_key,
116 aes_decrypt_key,
117 hmac_key,
118 key_name,
119 lifetime: 60 * 60 * 12,
120 maximum_ciphertext_len: AtomicUsize::new(0),
121 })
122 }
123}
124
125impl ProducesTickets for Rfc5077Ticketer {
126 fn enabled(&self) -> bool {
127 true
128 }
129
130 fn lifetime(&self) -> u32 {
131 self.lifetime
132 }
133
134 fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
136 let mut encrypted_state = Vec::from(message);
139 let dec_ctx = self
140 .aes_encrypt_key
141 .encrypt(&mut encrypted_state)
142 .ok()?;
143 let iv: &[u8] = (&dec_ctx).try_into().ok()?;
144
145 let mut hmac_data =
152 Vec::with_capacity(self.key_name.len() + iv.len() + 2 + encrypted_state.len());
153 hmac_data.extend(&self.key_name);
154 hmac_data.extend(iv);
155 hmac_data.extend(
156 u16::try_from(encrypted_state.len())
157 .ok()?
158 .to_be_bytes(),
159 );
160 hmac_data.extend(&encrypted_state);
161 let tag = hmac::sign(&self.hmac_key, &hmac_data);
162 let tag = tag.as_ref();
163
164 let mut ciphertext =
171 Vec::with_capacity(self.key_name.len() + iv.len() + encrypted_state.len() + tag.len());
172 ciphertext.extend(self.key_name);
173 ciphertext.extend(iv);
174 ciphertext.extend(encrypted_state);
175 ciphertext.extend(tag);
176
177 self.maximum_ciphertext_len
178 .fetch_max(ciphertext.len(), Ordering::SeqCst);
179
180 Some(ciphertext)
181 }
182
183 fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
184 if ciphertext.len()
185 > self
186 .maximum_ciphertext_len
187 .load(Ordering::SeqCst)
188 {
189 #[cfg(debug_assertions)]
190 debug!("rejected over-length ticket");
191 return None;
192 }
193
194 let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?;
196
197 let (iv, ciphertext) = try_split_at(ciphertext, AES_CBC_IV_LEN)?;
199
200 let tag_len = self
202 .hmac_key
203 .algorithm()
204 .digest_algorithm()
205 .output_len();
206 let (enc_state, mac) = try_split_at(ciphertext, ciphertext.len() - tag_len)?;
207
208 let mut hmac_data =
210 Vec::with_capacity(alleged_key_name.len() + iv.len() + 2 + enc_state.len());
211 hmac_data.extend(alleged_key_name);
212 hmac_data.extend(iv);
213 hmac_data.extend(
214 u16::try_from(enc_state.len())
215 .ok()?
216 .to_be_bytes(),
217 );
218 hmac_data.extend(enc_state);
219 hmac::verify(&self.hmac_key, &hmac_data, mac).ok()?;
220
221 let iv = iv::FixedLength::try_from(iv).ok()?;
223 let dec_context = DecryptionContext::Iv128(iv);
224
225 let mut out = Vec::from(enc_state);
227 let plaintext = self
228 .aes_decrypt_key
229 .decrypt(&mut out, dec_context)
230 .ok()?;
231
232 Some(plaintext.into())
233 }
234}
235
236impl Debug for Rfc5077Ticketer {
237 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
238 f.debug_struct("Rfc5077Ticketer")
240 .field("lifetime", &self.lifetime)
241 .finish()
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use core::time::Duration;
248
249 use pki_types::UnixTime;
250
251 use super::*;
252
253 #[test]
254 fn basic_pairwise_test() {
255 let t = Ticketer::new().unwrap();
256 assert!(t.enabled());
257 let cipher = t.encrypt(b"hello world").unwrap();
258 let plain = t.decrypt(&cipher).unwrap();
259 assert_eq!(plain, b"hello world");
260 }
261
262 #[test]
263 fn refuses_decrypt_before_encrypt() {
264 let t = Ticketer::new().unwrap();
265 assert_eq!(t.decrypt(b"hello"), None);
266 }
267
268 #[test]
269 fn refuses_decrypt_larger_than_largest_encryption() {
270 let t = Ticketer::new().unwrap();
271 let mut cipher = t.encrypt(b"hello world").unwrap();
272 assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec()));
273
274 cipher.push(0);
278 assert_eq!(t.decrypt(&cipher), None);
279 }
280
281 #[test]
282 fn ticketrotator_switching_test() {
283 let t = Arc::new(crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap());
284 let now = UnixTime::now();
285 let cipher1 = t.encrypt(b"ticket 1").unwrap();
286 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
287 {
288 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
290 now.as_secs() + 10,
291 )));
292 }
293 let cipher2 = t.encrypt(b"ticket 2").unwrap();
294 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
295 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
296 {
297 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
299 now.as_secs() + 20,
300 )));
301 }
302 let cipher3 = t.encrypt(b"ticket 3").unwrap();
303 assert!(t.decrypt(&cipher1).is_none());
304 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
305 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
306 }
307
308 #[test]
309 fn ticketrotator_remains_usable_over_temporary_ticketer_creation_failure() {
310 let mut t = crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap();
311 let now = UnixTime::now();
312 let cipher1 = t.encrypt(b"ticket 1").unwrap();
313 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
314 t.generator = fail_generator;
315 {
316 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
319 now.as_secs() + 10,
320 )));
321 }
322
323 let cipher2 = t.encrypt(b"ticket 2").unwrap();
325 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
326 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
327
328 t.generator = make_ticket_generator;
330 {
331 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
332 now.as_secs() + 20,
333 )));
334 }
335 let cipher3 = t.encrypt(b"ticket 3").unwrap();
336 assert!(t.decrypt(&cipher1).is_some());
337 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
338 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
339 }
340
341 #[test]
342 fn ticketswitcher_switching_test() {
343 #[expect(deprecated)]
344 let t = Arc::new(crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap());
345 let now = UnixTime::now();
346 let cipher1 = t.encrypt(b"ticket 1").unwrap();
347 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
348 {
349 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
351 now.as_secs() + 10,
352 )));
353 }
354 let cipher2 = t.encrypt(b"ticket 2").unwrap();
355 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
356 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
357 {
358 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
360 now.as_secs() + 20,
361 )));
362 }
363 let cipher3 = t.encrypt(b"ticket 3").unwrap();
364 assert!(t.decrypt(&cipher1).is_none());
365 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
366 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
367 }
368
369 #[test]
370 fn ticketswitcher_recover_test() {
371 #[expect(deprecated)]
372 let mut t = crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap();
373 let now = UnixTime::now();
374 let cipher1 = t.encrypt(b"ticket 1").unwrap();
375 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
376 t.generator = fail_generator;
377 {
378 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
380 now.as_secs() + 10,
381 )));
382 }
383 t.generator = make_ticket_generator;
384 let cipher2 = t.encrypt(b"ticket 2").unwrap();
385 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
386 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
387 {
388 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
390 now.as_secs() + 20,
391 )));
392 }
393 let cipher3 = t.encrypt(b"ticket 3").unwrap();
394 assert!(t.decrypt(&cipher1).is_none());
395 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
396 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
397 }
398
399 #[test]
400 fn rfc5077ticketer_is_debug_and_producestickets() {
401 use alloc::format;
402
403 use super::*;
404
405 let t = make_ticket_generator().unwrap();
406
407 assert_eq!(format!("{:?}", t), "Rfc5077Ticketer { lifetime: 43200 }");
408 assert!(t.enabled());
409 assert_eq!(t.lifetime(), 43200);
410 }
411
412 fn fail_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
413 Err(GetRandomFailed)
414 }
415}