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 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")]
40 pub fn new() -> Result<Arc<dyn ProducesTickets>, Error> {
41 Ok(Arc::new(crate::ticketer::TicketRotator::new(
42 crate::ticketer::TicketRotator::SIX_HOURS,
43 make_ticket_generator,
44 )?))
45 }
46}
47
48fn make_ticket_generator() -> Result<Box<dyn ProducesTickets>, Error> {
49 Ok(Box::new(Rfc5077Ticketer::new()?))
50}
51
52struct Rfc5077Ticketer {
54 aes_encrypt_key: PaddedBlockEncryptingKey,
55 aes_decrypt_key: PaddedBlockDecryptingKey,
56 hmac_key: hmac::Key,
57 key_name: [u8; 16],
58 maximum_ciphertext_len: AtomicUsize,
59}
60
61impl Rfc5077Ticketer {
62 fn new() -> Result<Self, Error> {
63 let rand = SystemRandom::new();
64
65 let mut aes_key = [0u8; AES_256_KEY_LEN];
67 rand.fill(&mut aes_key)
68 .map_err(|_| GetRandomFailed)?;
69
70 let aes_encrypt_key =
75 UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?;
76 let aes_encrypt_key =
77 PaddedBlockEncryptingKey::cbc_pkcs7(aes_encrypt_key).map_err(unspecified_err)?;
78
79 let aes_decrypt_key =
81 UnboundCipherKey::new(&AES_256, &aes_key[..]).map_err(unspecified_err)?;
82 let aes_decrypt_key =
83 PaddedBlockDecryptingKey::cbc_pkcs7(aes_decrypt_key).map_err(unspecified_err)?;
84
85 let hmac_key = hmac::Key::generate(hmac::HMAC_SHA256, &rand).map_err(unspecified_err)?;
87
88 let mut key_name = [0u8; 16];
90 rand.fill(&mut key_name)
91 .map_err(|_| GetRandomFailed)?;
92
93 Ok(Self {
94 aes_encrypt_key,
95 aes_decrypt_key,
96 hmac_key,
97 key_name,
98 maximum_ciphertext_len: AtomicUsize::new(0),
99 })
100 }
101}
102
103impl ProducesTickets for Rfc5077Ticketer {
104 fn enabled(&self) -> bool {
105 true
106 }
107
108 fn lifetime(&self) -> u32 {
109 0
112 }
113
114 fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
116 let mut encrypted_state = Vec::from(message);
119 let dec_ctx = self
120 .aes_encrypt_key
121 .encrypt(&mut encrypted_state)
122 .ok()?;
123 let iv: &[u8] = (&dec_ctx).try_into().ok()?;
124
125 let mut hmac_data =
132 Vec::with_capacity(self.key_name.len() + iv.len() + 2 + encrypted_state.len());
133 hmac_data.extend(&self.key_name);
134 hmac_data.extend(iv);
135 hmac_data.extend(
136 u16::try_from(encrypted_state.len())
137 .ok()?
138 .to_be_bytes(),
139 );
140 hmac_data.extend(&encrypted_state);
141 let tag = hmac::sign(&self.hmac_key, &hmac_data);
142 let tag = tag.as_ref();
143
144 let mut ciphertext =
151 Vec::with_capacity(self.key_name.len() + iv.len() + encrypted_state.len() + tag.len());
152 ciphertext.extend(self.key_name);
153 ciphertext.extend(iv);
154 ciphertext.extend(encrypted_state);
155 ciphertext.extend(tag);
156
157 self.maximum_ciphertext_len
158 .fetch_max(ciphertext.len(), Ordering::SeqCst);
159
160 Some(ciphertext)
161 }
162
163 fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
164 if ciphertext.len()
165 > self
166 .maximum_ciphertext_len
167 .load(Ordering::SeqCst)
168 {
169 #[cfg(debug_assertions)]
170 debug!("rejected over-length ticket");
171 return None;
172 }
173
174 let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?;
176
177 let (iv, ciphertext) = try_split_at(ciphertext, AES_CBC_IV_LEN)?;
179
180 let tag_len = self
182 .hmac_key
183 .algorithm()
184 .digest_algorithm()
185 .output_len();
186 let (enc_state, mac) = try_split_at(ciphertext, ciphertext.len() - tag_len)?;
187
188 let mut hmac_data =
190 Vec::with_capacity(alleged_key_name.len() + iv.len() + 2 + enc_state.len());
191 hmac_data.extend(alleged_key_name);
192 hmac_data.extend(iv);
193 hmac_data.extend(
194 u16::try_from(enc_state.len())
195 .ok()?
196 .to_be_bytes(),
197 );
198 hmac_data.extend(enc_state);
199 hmac::verify(&self.hmac_key, &hmac_data, mac).ok()?;
200
201 let iv = iv::FixedLength::try_from(iv).ok()?;
203 let dec_context = DecryptionContext::Iv128(iv);
204
205 let mut out = Vec::from(enc_state);
207 let plaintext = self
208 .aes_decrypt_key
209 .decrypt(&mut out, dec_context)
210 .ok()?;
211
212 Some(plaintext.into())
213 }
214}
215
216impl Debug for Rfc5077Ticketer {
217 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
218 f.debug_struct("Rfc5077Ticketer")
220 .finish_non_exhaustive()
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use core::time::Duration;
227
228 use pki_types::UnixTime;
229
230 use super::*;
231
232 #[test]
233 fn basic_pairwise_test() {
234 let t = Ticketer::new().unwrap();
235 assert!(t.enabled());
236 let cipher = t.encrypt(b"hello world").unwrap();
237 let plain = t.decrypt(&cipher).unwrap();
238 assert_eq!(plain, b"hello world");
239 }
240
241 #[test]
242 fn refuses_decrypt_before_encrypt() {
243 let t = Ticketer::new().unwrap();
244 assert_eq!(t.decrypt(b"hello"), None);
245 }
246
247 #[test]
248 fn refuses_decrypt_larger_than_largest_encryption() {
249 let t = Ticketer::new().unwrap();
250 let mut cipher = t.encrypt(b"hello world").unwrap();
251 assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec()));
252
253 cipher.push(0);
257 assert_eq!(t.decrypt(&cipher), None);
258 }
259
260 #[test]
261 fn ticketrotator_switching_test() {
262 let t = Arc::new(crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap());
263 let now = UnixTime::now();
264 let cipher1 = t.encrypt(b"ticket 1").unwrap();
265 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
266 {
267 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
269 now.as_secs() + 10,
270 )));
271 }
272 let cipher2 = t.encrypt(b"ticket 2").unwrap();
273 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
274 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
275 {
276 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
278 now.as_secs() + 20,
279 )));
280 }
281 let cipher3 = t.encrypt(b"ticket 3").unwrap();
282 assert!(t.decrypt(&cipher1).is_none());
283 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
284 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
285 }
286
287 #[test]
288 fn ticketrotator_remains_usable_over_temporary_ticketer_creation_failure() {
289 let mut t = crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap();
290 let now = UnixTime::now();
291 let cipher1 = t.encrypt(b"ticket 1").unwrap();
292 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
293 t.generator = fail_generator;
294 {
295 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
298 now.as_secs() + 10,
299 )));
300 }
301
302 let cipher2 = t.encrypt(b"ticket 2").unwrap();
304 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
305 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
306
307 t.generator = make_ticket_generator;
309 {
310 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
311 now.as_secs() + 20,
312 )));
313 }
314 let cipher3 = t.encrypt(b"ticket 3").unwrap();
315 assert!(t.decrypt(&cipher1).is_some());
316 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
317 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
318 }
319
320 #[test]
321 fn rfc5077ticketer_is_debug_and_producestickets() {
322 use alloc::format;
323
324 use super::*;
325
326 let t = make_ticket_generator().unwrap();
327
328 assert_eq!(format!("{t:?}"), "Rfc5077Ticketer { .. }");
329 assert!(t.enabled());
330 assert_eq!(t.lifetime(), 0);
331 }
332
333 fn fail_generator() -> Result<Box<dyn ProducesTickets>, Error> {
334 Err(Error::FailedToGetRandomBytes)
335 }
336}