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