1#![allow(clippy::duplicate_mod)]
2
3use crate::error::Error;
4use crate::rand::GetRandomFailed;
5use crate::server::ProducesTickets;
6
7use super::ring_like::aead;
8use super::ring_like::rand::{SecureRandom, SystemRandom};
9
10use alloc::boxed::Box;
11use alloc::sync::Arc;
12use alloc::vec::Vec;
13use core::fmt;
14use core::fmt::{Debug, Formatter};
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 Chacha20Poly1305.
24 pub fn new() -> Result<Arc<dyn ProducesTickets>, Error> {
25 Ok(Arc::new(data:crate::ticketer::TicketSwitcher::new(
26 lifetime:6 * 60 * 60,
27 make_ticket_generator,
28 )?))
29 }
30}
31
32fn make_ticket_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
33 let mut key: [u8; 32] = [0u8; 32];
34 SystemRandom::new()
35 .fill(&mut key)
36 .map_err(|_| GetRandomFailed)?;
37
38 let alg: &Algorithm = &aead::CHACHA20_POLY1305;
39 let key: UnboundKey = aead::UnboundKey::new(algorithm:alg, &key).unwrap();
40
41 Ok(Box::new(AeadTicketer {
42 alg,
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&mut DebugStruct<'_, '_>.debug_struct("AeadTicketer")
115 .field("alg", &self.alg)
116 .field(name:"lifetime", &self.lifetime)
117 .finish()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 use core::time::Duration;
126 use pki_types::UnixTime;
127
128 #[test]
129 fn basic_pairwise_test() {
130 let t = Ticketer::new().unwrap();
131 assert!(t.enabled());
132 let cipher = t.encrypt(b"hello world").unwrap();
133 let plain = t.decrypt(&cipher).unwrap();
134 assert_eq!(plain, b"hello world");
135 }
136
137 #[test]
138 fn ticketswitcher_switching_test() {
139 let t = Arc::new(crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap());
140 let now = UnixTime::now();
141 let cipher1 = t.encrypt(b"ticket 1").unwrap();
142 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
143 {
144 // Trigger new ticketer
145 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
146 now.as_secs() + 10,
147 )));
148 }
149 let cipher2 = t.encrypt(b"ticket 2").unwrap();
150 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
151 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
152 {
153 // Trigger new ticketer
154 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
155 now.as_secs() + 20,
156 )));
157 }
158 let cipher3 = t.encrypt(b"ticket 3").unwrap();
159 assert!(t.decrypt(&cipher1).is_none());
160 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
161 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
162 }
163
164 #[cfg(test)]
165 fn fail_generator() -> Result<Box<dyn ProducesTickets>, GetRandomFailed> {
166 Err(GetRandomFailed)
167 }
168
169 #[test]
170 fn ticketswitcher_recover_test() {
171 let mut t = crate::ticketer::TicketSwitcher::new(1, make_ticket_generator).unwrap();
172 let now = UnixTime::now();
173 let cipher1 = t.encrypt(b"ticket 1").unwrap();
174 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
175 t.generator = fail_generator;
176 {
177 // Failed new ticketer
178 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
179 now.as_secs() + 10,
180 )));
181 }
182 t.generator = make_ticket_generator;
183 let cipher2 = t.encrypt(b"ticket 2").unwrap();
184 assert_eq!(t.decrypt(&cipher1).unwrap(), b"ticket 1");
185 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
186 {
187 // recover
188 t.maybe_roll(UnixTime::since_unix_epoch(Duration::from_secs(
189 now.as_secs() + 20,
190 )));
191 }
192 let cipher3 = t.encrypt(b"ticket 3").unwrap();
193 assert!(t.decrypt(&cipher1).is_none());
194 assert_eq!(t.decrypt(&cipher2).unwrap(), b"ticket 2");
195 assert_eq!(t.decrypt(&cipher3).unwrap(), b"ticket 3");
196 }
197
198 #[test]
199 fn aeadticketer_is_debug_and_producestickets() {
200 use super::*;
201 use alloc::format;
202
203 let t = make_ticket_generator().unwrap();
204
205 assert_eq!(
206 format!("{:?}", t),
207 "AeadTicketer { alg: CHACHA20_POLY1305, lifetime: 43200 }"
208 );
209 assert!(t.enabled());
210 assert_eq!(t.lifetime(), 43200);
211 }
212}
213