1 use crate::rand;
2 use crate::server::ProducesTickets;
3 use crate::Error;
4 
5 use ring::aead;
6 use std::mem;
7 use std::sync::{Arc, Mutex, MutexGuard};
8 use std::time;
9 
10 /// The timebase for expiring and rolling tickets and ticketing
11 /// keys.  This is UNIX wall time in seconds.
12 ///
13 /// This is guaranteed to be on or after the UNIX epoch.
14 #[derive(Clone, Copy, Debug)]
15 pub struct TimeBase(time::Duration);
16 
17 impl TimeBase {
18     #[inline]
now() -> Result<Self, time::SystemTimeError>19     pub fn now() -> Result<Self, time::SystemTimeError> {
20         Ok(Self(
21             time::SystemTime::now().duration_since(time::UNIX_EPOCH)?,
22         ))
23     }
24 
25     #[inline]
as_secs(&self) -> u6426     pub fn as_secs(&self) -> u64 {
27         self.0.as_secs()
28     }
29 }
30 
31 /// This is a `ProducesTickets` implementation which uses
32 /// any *ring* `aead::Algorithm` to encrypt and authentication
33 /// the ticket payload.  It does not enforce any lifetime
34 /// constraint.
35 struct AeadTicketer {
36     alg: &'static aead::Algorithm,
37     key: aead::LessSafeKey,
38     lifetime: u32,
39 }
40 
41 impl AeadTicketer {
42     /// Make a ticketer with recommended configuration and a random key.
new() -> Result<Self, rand::GetRandomFailed>43     fn new() -> Result<Self, rand::GetRandomFailed> {
44         let mut key = [0u8; 32];
45         rand::fill_random(&mut key)?;
46 
47         let alg = &aead::CHACHA20_POLY1305;
48         let key = aead::UnboundKey::new(alg, &key).unwrap();
49 
50         Ok(Self {
51             alg,
52             key: aead::LessSafeKey::new(key),
53             lifetime: 60 * 60 * 12,
54         })
55     }
56 }
57 
58 impl ProducesTickets for AeadTicketer {
enabled(&self) -> bool59     fn enabled(&self) -> bool {
60         true
61     }
lifetime(&self) -> u3262     fn lifetime(&self) -> u32 {
63         self.lifetime
64     }
65 
66     /// Encrypt `message` and return the ciphertext.
encrypt(&self, message: &[u8]) -> Option<Vec<u8>>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         rand::fill_random(&mut nonce_buf).ok()?;
71         let nonce = ring::aead::Nonce::assume_unique_for_key(nonce_buf);
72         let aad = ring::aead::Aad::empty();
73 
74         let mut ciphertext =
75             Vec::with_capacity(nonce_buf.len() + message.len() + self.key.algorithm().tag_len());
76         ciphertext.extend(&nonce_buf);
77         ciphertext.extend(message);
78         self.key
79             .seal_in_place_separate_tag(nonce, aad, &mut ciphertext[nonce_buf.len()..])
80             .map(|tag| {
81                 ciphertext.extend(tag.as_ref());
82                 ciphertext
83             })
84             .ok()
85     }
86 
87     /// Decrypt `ciphertext` and recover the original message.
decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>>88     fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
89         // Non-panicking `let (nonce, ciphertext) = ciphertext.split_at(...)`.
90         let nonce = ciphertext.get(..self.alg.nonce_len())?;
91         let ciphertext = ciphertext.get(nonce.len()..)?;
92 
93         // This won't fail since `nonce` has the required length.
94         let nonce = ring::aead::Nonce::try_assume_unique_for_key(nonce).ok()?;
95 
96         let mut out = Vec::from(ciphertext);
97 
98         let plain_len = self
99             .key
100             .open_in_place(nonce, aead::Aad::empty(), &mut out)
101             .ok()?
102             .len();
103         out.truncate(plain_len);
104 
105         Some(out)
106     }
107 }
108 
109 struct TicketSwitcherState {
110     current: Box<dyn ProducesTickets>,
111     previous: Option<Box<dyn ProducesTickets>>,
112     next_switch_time: u64,
113 }
114 
115 /// A ticketer that has a 'current' sub-ticketer and a single
116 /// 'previous' ticketer.  It creates a new ticketer every so
117 /// often, demoting the current ticketer.
118 struct TicketSwitcher {
119     generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>,
120     lifetime: u32,
121     state: Mutex<TicketSwitcherState>,
122 }
123 
124 impl TicketSwitcher {
125     /// `lifetime` is in seconds, and is how long the current ticketer
126     /// is used to generate new tickets.  Tickets are accepted for no
127     /// longer than twice this duration.  `generator` produces a new
128     /// `ProducesTickets` implementation.
new( lifetime: u32, generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, ) -> Result<Self, Error>129     fn new(
130         lifetime: u32,
131         generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>,
132     ) -> Result<Self, Error> {
133         let now = TimeBase::now()?;
134         Ok(Self {
135             generator,
136             lifetime,
137             state: Mutex::new(TicketSwitcherState {
138                 current: generator()?,
139                 previous: None,
140                 next_switch_time: now.as_secs() + u64::from(lifetime),
141             }),
142         })
143     }
144 
145     /// If it's time, demote the `current` ticketer to `previous` (so it
146     /// does no new encryptions but can do decryption) and make a fresh
147     /// `current` ticketer.
148     ///
149     /// Calling this regularly will ensure timely key erasure.  Otherwise,
150     /// key erasure will be delayed until the next encrypt/decrypt call.
maybe_roll( &self, now: TimeBase, state: &mut MutexGuard<TicketSwitcherState>, ) -> Result<(), rand::GetRandomFailed>151     fn maybe_roll(
152         &self,
153         now: TimeBase,
154         state: &mut MutexGuard<TicketSwitcherState>,
155     ) -> Result<(), rand::GetRandomFailed> {
156         let now = now.as_secs();
157         if now > state.next_switch_time {
158             state.previous = Some(mem::replace(&mut state.current, (self.generator)()?));
159             state.next_switch_time = now + u64::from(self.lifetime);
160         }
161         Ok(())
162     }
163 }
164 
165 impl ProducesTickets for TicketSwitcher {
lifetime(&self) -> u32166     fn lifetime(&self) -> u32 {
167         self.lifetime * 2
168     }
169 
enabled(&self) -> bool170     fn enabled(&self) -> bool {
171         true
172     }
173 
encrypt(&self, message: &[u8]) -> Option<Vec<u8>>174     fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
175         let mut state = self.state.lock().ok()?;
176         self.maybe_roll(TimeBase::now().ok()?, &mut state)
177             .ok()?;
178 
179         state.current.encrypt(message)
180     }
181 
decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>>182     fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
183         let mut state = self.state.lock().ok()?;
184 
185         self.maybe_roll(TimeBase::now().ok()?, &mut state)
186             .ok()?;
187 
188         // Decrypt with the current key; if that fails, try with the previous.
189         state
190             .current
191             .decrypt(ciphertext)
192             .or_else(|| {
193                 state
194                     .previous
195                     .as_ref()
196                     .and_then(|previous| previous.decrypt(ciphertext))
197             })
198     }
199 }
200 
201 /// A concrete, safe ticket creation mechanism.
202 pub struct Ticketer {}
203 
generate_inner() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>204 fn generate_inner() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed> {
205     Ok(Box::new(AeadTicketer::new()?))
206 }
207 
208 impl Ticketer {
209     /// Make the recommended Ticketer.  This produces tickets
210     /// with a 12 hour life and randomly generated keys.
211     ///
212     /// The encryption mechanism used in Chacha20Poly1305.
new() -> Result<Arc<dyn ProducesTickets>, Error>213     pub fn new() -> Result<Arc<dyn ProducesTickets>, Error> {
214         Ok(Arc::new(TicketSwitcher::new(6 * 60 * 60, generate_inner)?))
215     }
216 }
217 
218 #[test]
basic_pairwise_test()219 fn basic_pairwise_test() {
220     let t = Ticketer::new().unwrap();
221     assert_eq!(true, t.enabled());
222     let cipher = t.encrypt(b"hello world").unwrap();
223     let plain = t.decrypt(&cipher).unwrap();
224     assert_eq!(plain, b"hello world");
225 }
226