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