rustls/crypto/ring/
ticketer.rs1use alloc::boxed::Box;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4use core::fmt;
5use core::fmt::{Debug, Formatter};
6use core::sync::atomic::{AtomicUsize, Ordering};
7
8use subtle::ConstantTimeEq;
9
10use super::ring_like::aead;
11use super::ring_like::rand::{SecureRandom, SystemRandom};
12use crate::error::Error;
13use crate::log::debug;
14use crate::polyfill::try_split_at;
15use crate::rand::GetRandomFailed;
16use crate::server::ProducesTickets;
17
18pub struct Ticketer {}
20
21impl Ticketer {
22 #[cfg(feature = "std")]
27 pub fn new() -> Result<Arc<dyn ProducesTickets>, Error> {
28 Ok(Arc::new(crate::ticketer::TicketSwitcher::new(
29 6 * 60 * 60,
30 make_ticket_generator,
31 )?))
32 }
33
34 #[cfg(not(feature = "std"))]
39 pub fn new<M: crate::lock::MakeMutex>(
40 time_provider: &'static dyn TimeProvider,
41 ) -> Result<Arc<dyn ProducesTickets>, Error> {
42 Ok(Arc::new(crate::ticketer::TicketSwitcher::new::<M>(
43 6 * 60 * 60,
44 make_ticket_generator,
45 time_provider,
46 )?))
47 }
48}
49
50fn make_ticket_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
51 Ok(Box::new(AeadTicketer::new()?))
52}
53
54struct AeadTicketer {
59 alg: &'static aead::Algorithm,
60 key: aead::LessSafeKey,
61 key_name: [u8; 16],
62 lifetime: u32,
63
64 maximum_ciphertext_len: AtomicUsize,
73}
74
75impl AeadTicketer {
76 fn new() -> Result<Self, GetRandomFailed> {
77 let mut key = [0u8; 32];
78 SystemRandom::new()
79 .fill(&mut key)
80 .map_err(|_| GetRandomFailed)?;
81
82 let key = aead::UnboundKey::new(TICKETER_AEAD, &key).unwrap();
83
84 let mut key_name = [0u8; 16];
85 SystemRandom::new()
86 .fill(&mut key_name)
87 .map_err(|_| GetRandomFailed)?;
88
89 Ok(Self {
90 alg: TICKETER_AEAD,
91 key: aead::LessSafeKey::new(key),
92 key_name,
93 lifetime: 60 * 60 * 12,
94 maximum_ciphertext_len: AtomicUsize::new(0),
95 })
96 }
97}
98
99impl ProducesTickets for AeadTicketer {
100 fn enabled(&self) -> bool {
101 true
102 }
103
104 fn lifetime(&self) -> u32 {
105 self.lifetime
106 }
107
108 fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
110 let mut nonce_buf = [0u8; 12];
112 SystemRandom::new()
113 .fill(&mut nonce_buf)
114 .ok()?;
115 let nonce = aead::Nonce::assume_unique_for_key(nonce_buf);
116 let aad = aead::Aad::from(self.key_name);
117
118 let mut ciphertext = Vec::with_capacity(
125 self.key_name.len() + nonce_buf.len() + message.len() + self.key.algorithm().tag_len(),
126 );
127 ciphertext.extend(self.key_name);
128 ciphertext.extend(nonce_buf);
129 ciphertext.extend(message);
130 let ciphertext = self
131 .key
132 .seal_in_place_separate_tag(
133 nonce,
134 aad,
135 &mut ciphertext[self.key_name.len() + nonce_buf.len()..],
136 )
137 .map(|tag| {
138 ciphertext.extend(tag.as_ref());
139 ciphertext
140 })
141 .ok()?;
142
143 self.maximum_ciphertext_len
144 .fetch_max(ciphertext.len(), Ordering::SeqCst);
145 Some(ciphertext)
146 }
147
148 fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
150 if ciphertext.len()
151 > self
152 .maximum_ciphertext_len
153 .load(Ordering::SeqCst)
154 {
155 #[cfg(debug_assertions)]
156 debug!("rejected over-length ticket");
157 return None;
158 }
159
160 let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?;
161
162 let (nonce, ciphertext) = try_split_at(ciphertext, self.alg.nonce_len())?;
163
164 if ConstantTimeEq::ct_ne(&self.key_name[..], alleged_key_name).into() {
176 #[cfg(debug_assertions)]
177 debug!("rejected ticket with wrong ticket_name");
178 return None;
179 }
180
181 let nonce = aead::Nonce::try_assume_unique_for_key(nonce).ok()?;
183
184 let mut out = Vec::from(ciphertext);
185
186 let plain_len = self
187 .key
188 .open_in_place(nonce, aead::Aad::from(alleged_key_name), &mut out)
189 .ok()?
190 .len();
191 out.truncate(plain_len);
192
193 Some(out)
194 }
195}
196
197impl Debug for AeadTicketer {
198 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
199 f.debug_struct("AeadTicketer")
201 .field("alg", &self.alg)
202 .field("lifetime", &self.lifetime)
203 .finish()
204 }
205}
206
207static TICKETER_AEAD: &aead::Algorithm = &aead::CHACHA20_POLY1305;
208
209#[cfg(test)]
210mod tests {
211 use core::time::Duration;
212
213 use pki_types::UnixTime;
214
215 use super::*;
216
217 #[test]
218 fn basic_pairwise_test() {
219 let t = Ticketer::new().unwrap();
220 assert!(t.enabled());
221 let cipher = t.encrypt(b"hello world").unwrap();
222 let plain = t.decrypt(&cipher).unwrap();
223 assert_eq!(plain, b"hello world");
224 }
225
226 #[test]
227 fn refuses_decrypt_before_encrypt() {
228 let t = Ticketer::new().unwrap();
229 assert_eq!(t.decrypt(b"hello"), None);
230 }
231
232 #[test]
233 fn refuses_decrypt_larger_than_largest_encryption() {
234 let t = Ticketer::new().unwrap();
235 let mut cipher = t.encrypt(b"hello world").unwrap();
236 assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec()));
237
238 cipher.push(0);
242 assert_eq!(t.decrypt(&cipher), None);
243 }
244
245 #[test]
246 fn ticketswitcher_switching_test() {
247 let t = Arc::new(crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap());
248 let now = UnixTime::now();
249 let cipher1 = t.encrypt(b"ticket 1").unwrap();
250 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
251 {
252 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
254 now.as_secs() + 10,
255 )));
256 }
257 let cipher2 = t.encrypt(b"ticket 2").unwrap();
258 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
259 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
260 {
261 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
263 now.as_secs() + 20,
264 )));
265 }
266 let cipher3 = t.encrypt(b"ticket 3").unwrap();
267 assert!(t.decrypt(&cipher1).is_none());
268 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
269 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
270 }
271
272 #[cfg(test)]
273 fn fail_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
274 Err(GetRandomFailed)
275 }
276
277 #[test]
278 fn ticketswitcher_recover_test() {
279 let mut t = crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap();
280 let now = UnixTime::now();
281 let cipher1 = t.encrypt(b"ticket 1").unwrap();
282 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
283 t.generator = fail_generator;
284 {
285 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
287 now.as_secs() + 10,
288 )));
289 }
290 t.generator = make_ticket_generator;
291 let cipher2 = t.encrypt(b"ticket 2").unwrap();
292 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
293 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
294 {
295 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
297 now.as_secs() + 20,
298 )));
299 }
300 let cipher3 = t.encrypt(b"ticket 3").unwrap();
301 assert!(t.decrypt(&cipher1).is_none());
302 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
303 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
304 }
305
306 #[test]
307 fn aeadticketer_is_debug_and_producestickets() {
308 use alloc::format;
309
310 use super::*;
311
312 let t = make_ticket_generator().unwrap();
313
314 let expect = format!("AeadTicketer {{ alg: {TICKETER_AEAD:?}, lifetime: 43200 }}");
315 assert_eq!(format!("{:?}", t), expect);
316 assert!(t.enabled());
317 assert_eq!(t.lifetime(), 43200);
318 }
319}