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::server::ProducesTickets;
16use crate::sync::Arc;
17
18pub struct Ticketer {}
20
21impl Ticketer {
22 #[cfg(feature = "std")]
32 pub fn new() -> Result<Arc<dyn ProducesTickets>, Error> {
33 Ok(Arc::new(crate::ticketer::TicketRotator::new(
34 crate::ticketer::TicketRotator::SIX_HOURS,
35 make_ticket_generator,
36 )?))
37 }
38}
39
40fn make_ticket_generator() -> Result<Box<dyn ProducesTickets>, Error> {
41 Ok(Box::new(AeadTicketer::new()?))
42}
43
44struct AeadTicketer {
49 alg: &'static aead::Algorithm,
50 key: aead::LessSafeKey,
51 key_name: [u8; 16],
52
53 maximum_ciphertext_len: AtomicUsize,
62}
63
64impl AeadTicketer {
65 fn new() -> Result<Self, Error> {
66 let mut key = [0u8; 32];
67 SystemRandom::new()
68 .fill(&mut key)
69 .map_err(|_| Error::FailedToGetRandomBytes)?;
70
71 let key = aead::UnboundKey::new(TICKETER_AEAD, &key).unwrap();
72
73 let mut key_name = [0u8; 16];
74 SystemRandom::new()
75 .fill(&mut key_name)
76 .map_err(|_| Error::FailedToGetRandomBytes)?;
77
78 Ok(Self {
79 alg: TICKETER_AEAD,
80 key: aead::LessSafeKey::new(key),
81 key_name,
82 maximum_ciphertext_len: AtomicUsize::new(0),
83 })
84 }
85}
86
87impl ProducesTickets for AeadTicketer {
88 fn enabled(&self) -> bool {
89 true
90 }
91
92 fn lifetime(&self) -> u32 {
93 0
96 }
97
98 fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
100 let mut nonce_buf = [0u8; 12];
102 SystemRandom::new()
103 .fill(&mut nonce_buf)
104 .ok()?;
105 let nonce = aead::Nonce::assume_unique_for_key(nonce_buf);
106 let aad = aead::Aad::from(self.key_name);
107
108 let mut ciphertext = Vec::with_capacity(
115 self.key_name.len() + nonce_buf.len() + message.len() + self.key.algorithm().tag_len(),
116 );
117 ciphertext.extend(self.key_name);
118 ciphertext.extend(nonce_buf);
119 ciphertext.extend(message);
120 let ciphertext = self
121 .key
122 .seal_in_place_separate_tag(
123 nonce,
124 aad,
125 &mut ciphertext[self.key_name.len() + nonce_buf.len()..],
126 )
127 .map(|tag| {
128 ciphertext.extend(tag.as_ref());
129 ciphertext
130 })
131 .ok()?;
132
133 self.maximum_ciphertext_len
134 .fetch_max(ciphertext.len(), Ordering::SeqCst);
135 Some(ciphertext)
136 }
137
138 fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
140 if ciphertext.len()
141 > self
142 .maximum_ciphertext_len
143 .load(Ordering::SeqCst)
144 {
145 #[cfg(debug_assertions)]
146 debug!("rejected over-length ticket");
147 return None;
148 }
149
150 let (alleged_key_name, ciphertext) = try_split_at(ciphertext, self.key_name.len())?;
151
152 let (nonce, ciphertext) = try_split_at(ciphertext, self.alg.nonce_len())?;
153
154 if ConstantTimeEq::ct_ne(&self.key_name[..], alleged_key_name).into() {
166 #[cfg(debug_assertions)]
167 debug!("rejected ticket with wrong ticket_name");
168 return None;
169 }
170
171 let nonce = aead::Nonce::try_assume_unique_for_key(nonce).ok()?;
173
174 let mut out = Vec::from(ciphertext);
175
176 let plain_len = self
177 .key
178 .open_in_place(nonce, aead::Aad::from(alleged_key_name), &mut out)
179 .ok()?
180 .len();
181 out.truncate(plain_len);
182
183 Some(out)
184 }
185}
186
187impl Debug for AeadTicketer {
188 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
189 f.debug_struct("AeadTicketer")
191 .field("alg", &self.alg)
192 .finish()
193 }
194}
195
196static TICKETER_AEAD: &aead::Algorithm = &aead::CHACHA20_POLY1305;
197
198#[cfg(test)]
199mod tests {
200 use core::time::Duration;
201
202 use pki_types::UnixTime;
203
204 use super::*;
205
206 #[test]
207 fn basic_pairwise_test() {
208 let t = Ticketer::new().unwrap();
209 assert!(t.enabled());
210 let cipher = t.encrypt(b"hello world").unwrap();
211 let plain = t.decrypt(&cipher).unwrap();
212 assert_eq!(plain, b"hello world");
213 }
214
215 #[test]
216 fn refuses_decrypt_before_encrypt() {
217 let t = Ticketer::new().unwrap();
218 assert_eq!(t.decrypt(b"hello"), None);
219 }
220
221 #[test]
222 fn refuses_decrypt_larger_than_largest_encryption() {
223 let t = Ticketer::new().unwrap();
224 let mut cipher = t.encrypt(b"hello world").unwrap();
225 assert_eq!(t.decrypt(&cipher), Some(b"hello world".to_vec()));
226
227 cipher.push(0);
231 assert_eq!(t.decrypt(&cipher), None);
232 }
233
234 #[test]
235 fn ticketrotator_switching_test() {
236 let t = Arc::new(crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap());
237 let now = UnixTime::now();
238 let cipher1 = t.encrypt(b"ticket 1").unwrap();
239 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
240 {
241 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
243 now.as_secs() + 10,
244 )));
245 }
246 let cipher2 = t.encrypt(b"ticket 2").unwrap();
247 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
248 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
249 {
250 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
252 now.as_secs() + 20,
253 )));
254 }
255 let cipher3 = t.encrypt(b"ticket 3").unwrap();
256 assert!(t.decrypt(&cipher1).is_none());
257 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
258 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
259 }
260
261 #[test]
262 fn ticketrotator_remains_usable_over_temporary_ticketer_creation_failure() {
263 let mut t = crate::ticketer::TicketRotator::new(1, make_ticket_generator).unwrap();
264 let now = UnixTime::now();
265 let cipher1 = t.encrypt(b"ticket 1").unwrap();
266 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
267 t.generator = fail_generator;
268 {
269 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
272 now.as_secs() + 10,
273 )));
274 }
275
276 let cipher2 = t.encrypt(b"ticket 2").unwrap();
278 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
279 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
280
281 t.generator = make_ticket_generator;
283 {
284 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
285 now.as_secs() + 20,
286 )));
287 }
288 let cipher3 = t.encrypt(b"ticket 3").unwrap();
289 assert!(t.decrypt(&cipher1).is_some());
290 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
291 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
292 }
293
294 #[test]
295 fn aeadticketer_is_debug_and_producestickets() {
296 use alloc::format;
297
298 use super::*;
299
300 let t = make_ticket_generator().unwrap();
301
302 let expect = format!("AeadTicketer {{ alg: {TICKETER_AEAD:?} }}");
303 assert_eq!(format!("{t:?}"), expect);
304 assert!(t.enabled());
305 assert_eq!(t.lifetime(), 0);
306 }
307
308 fn fail_generator() -> Result<Box<dyn ProducesTickets>, Error> {
309 Err(Error::FailedToGetRandomBytes)
310 }
311}