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