clia_rustls_mod/crypto/ring/
ticketer.rs

1#![allow(clippy::duplicate_mod)]
2
3use alloc::boxed::Box;
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6use core::fmt;
7use core::fmt::{Debug, Formatter};
8
9use super::ring_like::aead;
10use super::ring_like::rand::{SecureRandom, SystemRandom};
11use super::TICKETER_AEAD;
12use crate::error::Error;
13use crate::rand::GetRandomFailed;
14use crate::server::ProducesTickets;
15
16/// A concrete, safe ticket creation mechanism.
17pub struct Ticketer {}
18
19impl Ticketer {
20    /// Make the recommended Ticketer.  This produces tickets
21    /// with a 12 hour life and randomly generated keys.
22    ///
23    /// The encryption mechanism used is injected via TICKETER_AEAD;
24    /// it must take a 256-bit key and 96-bit nonce.
25    pub fn new() -> Result<Arc<dyn ProducesTickets>, Error> {
26        Ok(Arc::new(crate::ticketer::TicketSwitcher::new(
27            6 * 60 * 60,
28            make_ticket_generator,
29        )?))
30    }
31}
32
33fn make_ticket_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
34    let mut key = [0u8; 32];
35    SystemRandom::new()
36        .fill(&mut key)
37        .map_err(|_| GetRandomFailed)?;
38
39    let key = aead::UnboundKey::new(TICKETER_AEAD, &key).unwrap();
40
41    Ok(Box::new(AeadTicketer {
42        alg: TICKETER_AEAD,
43        key: aead::LessSafeKey::new(key),
44        lifetime: 60 * 60 * 12,
45    }))
46}
47
48/// This is a `ProducesTickets` implementation which uses
49/// any *ring* `aead::Algorithm` to encrypt and authentication
50/// the ticket payload.  It does not enforce any lifetime
51/// constraint.
52struct AeadTicketer {
53    alg: &'static aead::Algorithm,
54    key: aead::LessSafeKey,
55    lifetime: u32,
56}
57
58impl ProducesTickets for AeadTicketer {
59    fn enabled(&self) -> bool {
60        true
61    }
62    fn lifetime(&self) -> u32 {
63        self.lifetime
64    }
65
66    /// Encrypt `message` and return the ciphertext.
67    fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
68        // Random nonce, because a counter is a privacy leak.
69        let mut nonce_buf = [0u8; 12];
70        SystemRandom::new()
71            .fill(&mut nonce_buf)
72            .ok()?;
73        let nonce = aead::Nonce::assume_unique_for_key(nonce_buf);
74        let aad = aead::Aad::empty();
75
76        let mut ciphertext =
77            Vec::with_capacity(nonce_buf.len() + message.len() + self.key.algorithm().tag_len());
78        ciphertext.extend(nonce_buf);
79        ciphertext.extend(message);
80        self.key
81            .seal_in_place_separate_tag(nonce, aad, &mut ciphertext[nonce_buf.len()..])
82            .map(|tag| {
83                ciphertext.extend(tag.as_ref());
84                ciphertext
85            })
86            .ok()
87    }
88
89    /// Decrypt `ciphertext` and recover the original message.
90    fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
91        // Non-panicking `let (nonce, ciphertext) = ciphertext.split_at(...)`.
92        let nonce = ciphertext.get(..self.alg.nonce_len())?;
93        let ciphertext = ciphertext.get(nonce.len()..)?;
94
95        // This won't fail since `nonce` has the required length.
96        let nonce = aead::Nonce::try_assume_unique_for_key(nonce).ok()?;
97
98        let mut out = Vec::from(ciphertext);
99
100        let plain_len = self
101            .key
102            .open_in_place(nonce, aead::Aad::empty(), &mut out)
103            .ok()?
104            .len();
105        out.truncate(plain_len);
106
107        Some(out)
108    }
109}
110
111impl Debug for AeadTicketer {
112    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
113        // Note: we deliberately omit the key from the debug output.
114        f.debug_struct("AeadTicketer")
115            .field("alg", &self.alg)
116            .field("lifetime", &self.lifetime)
117            .finish()
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use core::time::Duration;
124
125    use pki_types::UnixTime;
126
127    use super::*;
128
129    #[test]
130    fn basic_pairwise_test() {
131        let t = Ticketer::new().unwrap();
132        assert!(t.enabled());
133        let cipher = t.encrypt(b"hello world").unwrap();
134        let plain = t.decrypt(&cipher).unwrap();
135        assert_eq!(plain, b"hello world");
136    }
137
138    #[test]
139    fn ticketswitcher_switching_test() {
140        let t = Arc::new(crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap());
141        let now = UnixTime::now();
142        let cipher1 = t.encrypt(b"ticket 1").unwrap();
143        assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
144        {
145            // Trigger new ticketer
146            t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
147                now.as_secs() + 10,
148            )));
149        }
150        let cipher2 = t.encrypt(b"ticket 2").unwrap();
151        assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
152        assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
153        {
154            // Trigger new ticketer
155            t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
156                now.as_secs() + 20,
157            )));
158        }
159        let cipher3 = t.encrypt(b"ticket 3").unwrap();
160        assert!(t.decrypt(&cipher1).is_none());
161        assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
162        assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
163    }
164
165    #[cfg(test)]
166    fn fail_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
167        Err(GetRandomFailed)
168    }
169
170    #[test]
171    fn ticketswitcher_recover_test() {
172        let mut t = crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap();
173        let now = UnixTime::now();
174        let cipher1 = t.encrypt(b"ticket 1").unwrap();
175        assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
176        t.generator = fail_generator;
177        {
178            // Failed new ticketer
179            t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
180                now.as_secs() + 10,
181            )));
182        }
183        t.generator = make_ticket_generator;
184        let cipher2 = t.encrypt(b"ticket 2").unwrap();
185        assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
186        assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
187        {
188            // recover
189            t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
190                now.as_secs() + 20,
191            )));
192        }
193        let cipher3 = t.encrypt(b"ticket 3").unwrap();
194        assert!(t.decrypt(&cipher1).is_none());
195        assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
196        assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
197    }
198
199    #[test]
200    fn aeadticketer_is_debug_and_producestickets() {
201        use alloc::format;
202
203        use super::*;
204
205        let t = make_ticket_generator().unwrap();
206
207        let expect = format!("AeadTicketer {{ alg: {TICKETER_AEAD:?}, lifetime: 43200 }}");
208        assert_eq!(format!("{:?}", t), expect);
209        assert!(t.enabled());
210        assert_eq!(t.lifetime(), 43200);
211    }
212}