1 | use crate::rand; |
2 | use crate::server::ProducesTickets; |
3 | use crate::Error; |
4 | |
5 | use pki_types::UnixTime; |
6 | |
7 | use alloc::boxed::Box; |
8 | use alloc::vec::Vec; |
9 | use core::mem; |
10 | use std::sync::{Mutex, MutexGuard}; |
11 | |
12 | #[derive (Debug)] |
13 | pub(crate) struct TicketSwitcherState { |
14 | next: Option<Box<dyn ProducesTickets>>, |
15 | current: Box<dyn ProducesTickets>, |
16 | previous: Option<Box<dyn ProducesTickets>>, |
17 | next_switch_time: u64, |
18 | } |
19 | |
20 | /// A ticketer that has a 'current' sub-ticketer and a single |
21 | /// 'previous' ticketer. It creates a new ticketer every so |
22 | /// often, demoting the current ticketer. |
23 | #[derive (Debug)] |
24 | pub struct TicketSwitcher { |
25 | pub(crate) generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, |
26 | lifetime: u32, |
27 | state: Mutex<TicketSwitcherState>, |
28 | } |
29 | |
30 | impl TicketSwitcher { |
31 | /// Creates a new `TicketSwitcher`, which rotates through sub-ticketers |
32 | /// based on the passage of time. |
33 | /// |
34 | /// `lifetime` is in seconds, and is how long the current ticketer |
35 | /// is used to generate new tickets. Tickets are accepted for no |
36 | /// longer than twice this duration. `generator` produces a new |
37 | /// `ProducesTickets` implementation. |
38 | pub fn new( |
39 | lifetime: u32, |
40 | generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, |
41 | ) -> Result<Self, Error> { |
42 | Ok(Self { |
43 | generator, |
44 | lifetime, |
45 | state: Mutex::new(TicketSwitcherState { |
46 | next: Some(generator()?), |
47 | current: generator()?, |
48 | previous: None, |
49 | next_switch_time: UnixTime::now() |
50 | .as_secs() |
51 | .saturating_add(u64::from(lifetime)), |
52 | }), |
53 | }) |
54 | } |
55 | |
56 | /// If it's time, demote the `current` ticketer to `previous` (so it |
57 | /// does no new encryptions but can do decryption) and use next for a |
58 | /// new `current` ticketer. |
59 | /// |
60 | /// Calling this regularly will ensure timely key erasure. Otherwise, |
61 | /// key erasure will be delayed until the next encrypt/decrypt call. |
62 | /// |
63 | /// For efficiency, this is also responsible for locking the state mutex |
64 | /// and returning the mutexguard. |
65 | pub(crate) fn maybe_roll(&self, now: UnixTime) -> Option<MutexGuard<TicketSwitcherState>> { |
66 | // The code below aims to make switching as efficient as possible |
67 | // in the common case that the generator never fails. To achieve this |
68 | // we run the following steps: |
69 | // 1. If no switch is necessary, just return the mutexguard |
70 | // 2. Shift over all of the ticketers (so current becomes previous, |
71 | // and next becomes current). After this, other threads can |
72 | // start using the new current ticketer. |
73 | // 3. unlock mutex and generate new ticketer. |
74 | // 4. Place new ticketer in next and return current |
75 | // |
76 | // There are a few things to note here. First, we don't check whether |
77 | // a new switch might be needed in step 4, even though, due to locking |
78 | // and entropy collection, significant amounts of time may have passed. |
79 | // This is to guarantee that the thread doing the switch will eventually |
80 | // make progress. |
81 | // |
82 | // Second, because next may be None, step 2 can fail. In that case |
83 | // we enter a recovery mode where we generate 2 new ticketers, one for |
84 | // next and one for the current ticketer. We then take the mutex a |
85 | // second time and redo the time check to see if a switch is still |
86 | // necessary. |
87 | // |
88 | // This somewhat convoluted approach ensures good availability of the |
89 | // mutex, by ensuring that the state is usable and the mutex not held |
90 | // during generation. It also ensures that, so long as the inner |
91 | // ticketer never generates panics during encryption/decryption, |
92 | // we are guaranteed to never panic when holding the mutex. |
93 | |
94 | let now = now.as_secs(); |
95 | let mut are_recovering = false; // Are we recovering from previous failure? |
96 | { |
97 | // Scope the mutex so we only take it for as long as needed |
98 | let mut state = self.state.lock().ok()?; |
99 | |
100 | // Fast path in case we do not need to switch to the next ticketer yet |
101 | if now <= state.next_switch_time { |
102 | return Some(state); |
103 | } |
104 | |
105 | // Make the switch, or mark for recovery if not possible |
106 | if let Some(next) = state.next.take() { |
107 | state.previous = Some(mem::replace(&mut state.current, next)); |
108 | state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); |
109 | } else { |
110 | are_recovering = true; |
111 | } |
112 | } |
113 | |
114 | // We always need a next, so generate it now |
115 | let next = (self.generator)().ok()?; |
116 | if !are_recovering { |
117 | // Normal path, generate new next and place it in the state |
118 | let mut state = self.state.lock().ok()?; |
119 | state.next = Some(next); |
120 | Some(state) |
121 | } else { |
122 | // Recovering, generate also a new current ticketer, and modify state |
123 | // as needed. (we need to redo the time check, otherwise this might |
124 | // result in very rapid switching of ticketers) |
125 | let new_current = (self.generator)().ok()?; |
126 | let mut state = self.state.lock().ok()?; |
127 | state.next = Some(next); |
128 | if now > state.next_switch_time { |
129 | state.previous = Some(mem::replace(&mut state.current, new_current)); |
130 | state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); |
131 | } |
132 | Some(state) |
133 | } |
134 | } |
135 | } |
136 | |
137 | impl ProducesTickets for TicketSwitcher { |
138 | fn lifetime(&self) -> u32 { |
139 | self.lifetime * 2 |
140 | } |
141 | |
142 | fn enabled(&self) -> bool { |
143 | true |
144 | } |
145 | |
146 | fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> { |
147 | let state = self.maybe_roll(UnixTime::now())?; |
148 | |
149 | state.current.encrypt(message) |
150 | } |
151 | |
152 | fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> { |
153 | let state = self.maybe_roll(UnixTime::now())?; |
154 | |
155 | // Decrypt with the current key; if that fails, try with the previous. |
156 | state |
157 | .current |
158 | .decrypt(ciphertext) |
159 | .or_else(|| { |
160 | state |
161 | .previous |
162 | .as_ref() |
163 | .and_then(|previous| previous.decrypt(ciphertext)) |
164 | }) |
165 | } |
166 | } |
167 | |