/rust/registry/src/index.crates.io-6f17d22bba15001f/rustls-0.23.20/src/ticketer.rs
Line | Count | Source (jump to first uncovered line) |
1 | | use alloc::boxed::Box; |
2 | | use alloc::vec::Vec; |
3 | | use core::mem; |
4 | | #[cfg(feature = "std")] |
5 | | use std::sync::{RwLock, RwLockReadGuard}; |
6 | | |
7 | | use pki_types::UnixTime; |
8 | | |
9 | | use crate::lock::{Mutex, MutexGuard}; |
10 | | use crate::server::ProducesTickets; |
11 | | #[cfg(not(feature = "std"))] |
12 | | use crate::time_provider::TimeProvider; |
13 | | use crate::{rand, Error}; |
14 | | |
15 | | #[derive(Debug)] |
16 | | pub(crate) struct TicketSwitcherState { |
17 | | next: Option<Box<dyn ProducesTickets>>, |
18 | | current: Box<dyn ProducesTickets>, |
19 | | previous: Option<Box<dyn ProducesTickets>>, |
20 | | next_switch_time: u64, |
21 | | } |
22 | | |
23 | | /// A ticketer that has a 'current' sub-ticketer and a single |
24 | | /// 'previous' ticketer. It creates a new ticketer every so |
25 | | /// often, demoting the current ticketer. |
26 | | #[cfg_attr(feature = "std", derive(Debug))] |
27 | | pub struct TicketSwitcher { |
28 | | pub(crate) generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, |
29 | | lifetime: u32, |
30 | | state: Mutex<TicketSwitcherState>, |
31 | | #[cfg(not(feature = "std"))] |
32 | | time_provider: &'static dyn TimeProvider, |
33 | | } |
34 | | |
35 | | impl TicketSwitcher { |
36 | | /// Creates a new `TicketSwitcher`, which rotates through sub-ticketers |
37 | | /// based on the passage of time. |
38 | | /// |
39 | | /// `lifetime` is in seconds, and is how long the current ticketer |
40 | | /// is used to generate new tickets. Tickets are accepted for no |
41 | | /// longer than twice this duration. `generator` produces a new |
42 | | /// `ProducesTickets` implementation. |
43 | | #[cfg(feature = "std")] |
44 | | #[deprecated(note = "use TicketRotator instead")] |
45 | 0 | pub fn new( |
46 | 0 | lifetime: u32, |
47 | 0 | generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, |
48 | 0 | ) -> Result<Self, Error> { |
49 | 0 | Ok(Self { |
50 | 0 | generator, |
51 | 0 | lifetime, |
52 | 0 | state: Mutex::new(TicketSwitcherState { |
53 | 0 | next: Some(generator()?), |
54 | 0 | current: generator()?, |
55 | 0 | previous: None, |
56 | 0 | next_switch_time: UnixTime::now() |
57 | 0 | .as_secs() |
58 | 0 | .saturating_add(u64::from(lifetime)), |
59 | | }), |
60 | | }) |
61 | 0 | } |
62 | | |
63 | | /// Creates a new `TicketSwitcher`, which rotates through sub-ticketers |
64 | | /// based on the passage of time. |
65 | | /// |
66 | | /// `lifetime` is in seconds, and is how long the current ticketer |
67 | | /// is used to generate new tickets. Tickets are accepted for no |
68 | | /// longer than twice this duration. `generator` produces a new |
69 | | /// `ProducesTickets` implementation. |
70 | | #[cfg(not(feature = "std"))] |
71 | | pub fn new<M: crate::lock::MakeMutex>( |
72 | | lifetime: u32, |
73 | | generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, |
74 | | time_provider: &'static dyn TimeProvider, |
75 | | ) -> Result<Self, Error> { |
76 | | Ok(Self { |
77 | | generator, |
78 | | lifetime, |
79 | | state: Mutex::new::<M>(TicketSwitcherState { |
80 | | next: Some(generator()?), |
81 | | current: generator()?, |
82 | | previous: None, |
83 | | next_switch_time: time_provider |
84 | | .current_time() |
85 | | .unwrap() |
86 | | .as_secs() |
87 | | .saturating_add(u64::from(lifetime)), |
88 | | }), |
89 | | time_provider, |
90 | | }) |
91 | | } |
92 | | |
93 | | /// If it's time, demote the `current` ticketer to `previous` (so it |
94 | | /// does no new encryptions but can do decryption) and use next for a |
95 | | /// new `current` ticketer. |
96 | | /// |
97 | | /// Calling this regularly will ensure timely key erasure. Otherwise, |
98 | | /// key erasure will be delayed until the next encrypt/decrypt call. |
99 | | /// |
100 | | /// For efficiency, this is also responsible for locking the state mutex |
101 | | /// and returning the mutexguard. |
102 | 0 | pub(crate) fn maybe_roll(&self, now: UnixTime) -> Option<MutexGuard<'_, TicketSwitcherState>> { |
103 | 0 | // The code below aims to make switching as efficient as possible |
104 | 0 | // in the common case that the generator never fails. To achieve this |
105 | 0 | // we run the following steps: |
106 | 0 | // 1. If no switch is necessary, just return the mutexguard |
107 | 0 | // 2. Shift over all of the ticketers (so current becomes previous, |
108 | 0 | // and next becomes current). After this, other threads can |
109 | 0 | // start using the new current ticketer. |
110 | 0 | // 3. unlock mutex and generate new ticketer. |
111 | 0 | // 4. Place new ticketer in next and return current |
112 | 0 | // |
113 | 0 | // There are a few things to note here. First, we don't check whether |
114 | 0 | // a new switch might be needed in step 4, even though, due to locking |
115 | 0 | // and entropy collection, significant amounts of time may have passed. |
116 | 0 | // This is to guarantee that the thread doing the switch will eventually |
117 | 0 | // make progress. |
118 | 0 | // |
119 | 0 | // Second, because next may be None, step 2 can fail. In that case |
120 | 0 | // we enter a recovery mode where we generate 2 new ticketers, one for |
121 | 0 | // next and one for the current ticketer. We then take the mutex a |
122 | 0 | // second time and redo the time check to see if a switch is still |
123 | 0 | // necessary. |
124 | 0 | // |
125 | 0 | // This somewhat convoluted approach ensures good availability of the |
126 | 0 | // mutex, by ensuring that the state is usable and the mutex not held |
127 | 0 | // during generation. It also ensures that, so long as the inner |
128 | 0 | // ticketer never generates panics during encryption/decryption, |
129 | 0 | // we are guaranteed to never panic when holding the mutex. |
130 | 0 |
|
131 | 0 | let now = now.as_secs(); |
132 | 0 | let mut are_recovering = false; // Are we recovering from previous failure? |
133 | | { |
134 | | // Scope the mutex so we only take it for as long as needed |
135 | 0 | let mut state = self.state.lock()?; |
136 | | |
137 | | // Fast path in case we do not need to switch to the next ticketer yet |
138 | 0 | if now <= state.next_switch_time { |
139 | 0 | return Some(state); |
140 | 0 | } |
141 | | |
142 | | // Make the switch, or mark for recovery if not possible |
143 | 0 | if let Some(next) = state.next.take() { |
144 | 0 | state.previous = Some(mem::replace(&mut state.current, next)); |
145 | 0 | state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); |
146 | 0 | } else { |
147 | 0 | are_recovering = true; |
148 | 0 | } |
149 | | } |
150 | | |
151 | | // We always need a next, so generate it now |
152 | 0 | let next = (self.generator)().ok()?; |
153 | 0 | if !are_recovering { |
154 | | // Normal path, generate new next and place it in the state |
155 | 0 | let mut state = self.state.lock()?; |
156 | 0 | state.next = Some(next); |
157 | 0 | Some(state) |
158 | | } else { |
159 | | // Recovering, generate also a new current ticketer, and modify state |
160 | | // as needed. (we need to redo the time check, otherwise this might |
161 | | // result in very rapid switching of ticketers) |
162 | 0 | let new_current = (self.generator)().ok()?; |
163 | 0 | let mut state = self.state.lock()?; |
164 | 0 | state.next = Some(next); |
165 | 0 | if now > state.next_switch_time { |
166 | 0 | state.previous = Some(mem::replace(&mut state.current, new_current)); |
167 | 0 | state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); |
168 | 0 | } |
169 | 0 | Some(state) |
170 | | } |
171 | 0 | } |
172 | | } |
173 | | |
174 | | impl ProducesTickets for TicketSwitcher { |
175 | 0 | fn lifetime(&self) -> u32 { |
176 | 0 | self.lifetime * 2 |
177 | 0 | } |
178 | | |
179 | 0 | fn enabled(&self) -> bool { |
180 | 0 | true |
181 | 0 | } |
182 | | |
183 | 0 | fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> { |
184 | 0 | #[cfg(feature = "std")] |
185 | 0 | let now = UnixTime::now(); |
186 | 0 | #[cfg(not(feature = "std"))] |
187 | 0 | let now = self |
188 | 0 | .time_provider |
189 | 0 | .current_time() |
190 | 0 | .unwrap(); |
191 | 0 |
|
192 | 0 | self.maybe_roll(now)? |
193 | | .current |
194 | 0 | .encrypt(message) |
195 | 0 | } |
196 | | |
197 | 0 | fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> { |
198 | 0 | #[cfg(feature = "std")] |
199 | 0 | let now = UnixTime::now(); |
200 | | #[cfg(not(feature = "std"))] |
201 | | let now = self |
202 | | .time_provider |
203 | | .current_time() |
204 | | .unwrap(); |
205 | | |
206 | 0 | let state = self.maybe_roll(now)?; |
207 | | |
208 | | // Decrypt with the current key; if that fails, try with the previous. |
209 | 0 | state |
210 | 0 | .current |
211 | 0 | .decrypt(ciphertext) |
212 | 0 | .or_else(|| { |
213 | 0 | state |
214 | 0 | .previous |
215 | 0 | .as_ref() |
216 | 0 | .and_then(|previous| previous.decrypt(ciphertext)) |
217 | 0 | }) |
218 | 0 | } |
219 | | } |
220 | | |
221 | | #[cfg(not(feature = "std"))] |
222 | | impl core::fmt::Debug for TicketSwitcher { |
223 | | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
224 | | f.debug_struct("TicketSwitcher") |
225 | | .field("generator", &self.generator) |
226 | | .field("lifetime", &self.lifetime) |
227 | | .field("state", &**self.state.lock().unwrap()) |
228 | | .finish() |
229 | | } |
230 | | } |
231 | | |
232 | | #[cfg(feature = "std")] |
233 | | #[derive(Debug)] |
234 | | pub(crate) struct TicketRotatorState { |
235 | | current: Box<dyn ProducesTickets>, |
236 | | previous: Option<Box<dyn ProducesTickets>>, |
237 | | next_switch_time: u64, |
238 | | } |
239 | | |
240 | | /// A ticketer that has a 'current' sub-ticketer and a single |
241 | | /// 'previous' ticketer. It creates a new ticketer every so |
242 | | /// often, demoting the current ticketer. |
243 | | #[cfg(feature = "std")] |
244 | | pub struct TicketRotator { |
245 | | pub(crate) generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, |
246 | | lifetime: u32, |
247 | | state: RwLock<TicketRotatorState>, |
248 | | } |
249 | | |
250 | | #[cfg(feature = "std")] |
251 | | impl TicketRotator { |
252 | | /// Creates a new `TicketRotator`, which rotates through sub-ticketers |
253 | | /// based on the passage of time. |
254 | | /// |
255 | | /// `lifetime` is in seconds, and is how long the current ticketer |
256 | | /// is used to generate new tickets. Tickets are accepted for no |
257 | | /// longer than twice this duration. `generator` produces a new |
258 | | /// `ProducesTickets` implementation. |
259 | 0 | pub fn new( |
260 | 0 | lifetime: u32, |
261 | 0 | generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, |
262 | 0 | ) -> Result<Self, Error> { |
263 | 0 | Ok(Self { |
264 | 0 | generator, |
265 | 0 | lifetime, |
266 | 0 | state: RwLock::new(TicketRotatorState { |
267 | 0 | current: generator()?, |
268 | 0 | previous: None, |
269 | 0 | next_switch_time: UnixTime::now() |
270 | 0 | .as_secs() |
271 | 0 | .saturating_add(u64::from(lifetime)), |
272 | | }), |
273 | | }) |
274 | 0 | } |
275 | | |
276 | | /// If it's time, demote the `current` ticketer to `previous` (so it |
277 | | /// does no new encryptions but can do decryption) and replace it |
278 | | /// with a new one. |
279 | | /// |
280 | | /// Calling this regularly will ensure timely key erasure. Otherwise, |
281 | | /// key erasure will be delayed until the next encrypt/decrypt call. |
282 | | /// |
283 | | /// For efficiency, this is also responsible for locking the state rwlock |
284 | | /// and returning it for read. |
285 | 0 | pub(crate) fn maybe_roll( |
286 | 0 | &self, |
287 | 0 | now: UnixTime, |
288 | 0 | ) -> Option<RwLockReadGuard<'_, TicketRotatorState>> { |
289 | 0 | let now = now.as_secs(); |
290 | | |
291 | | // Fast, common, & read-only path in case we do not need to switch |
292 | | // to the next ticketer yet |
293 | | { |
294 | 0 | let read = self.state.read().ok()?; |
295 | | |
296 | 0 | if now <= read.next_switch_time { |
297 | 0 | return Some(read); |
298 | 0 | } |
299 | | } |
300 | | |
301 | | // We need to switch ticketers, and make a new one. |
302 | | // Generate a potential "next" ticketer outside the lock. |
303 | 0 | let next = (self.generator)().ok()?; |
304 | | |
305 | 0 | let mut write = self.state.write().ok()?; |
306 | | |
307 | 0 | if now <= write.next_switch_time { |
308 | | // Another thread beat us to it. Nothing to do. |
309 | 0 | drop(write); |
310 | 0 |
|
311 | 0 | return self.state.read().ok(); |
312 | 0 | } |
313 | 0 |
|
314 | 0 | // Now we have: |
315 | 0 | // - confirmed we need rotation |
316 | 0 | // - confirmed we are the thread that will do it |
317 | 0 | // - successfully made the replacement ticketer |
318 | 0 | write.previous = Some(mem::replace(&mut write.current, next)); |
319 | 0 | write.next_switch_time = now.saturating_add(u64::from(self.lifetime)); |
320 | 0 | drop(write); |
321 | 0 |
|
322 | 0 | self.state.read().ok() |
323 | 0 | } |
324 | | } |
325 | | |
326 | | #[cfg(feature = "std")] |
327 | | impl ProducesTickets for TicketRotator { |
328 | 0 | fn lifetime(&self) -> u32 { |
329 | 0 | self.lifetime * 2 |
330 | 0 | } |
331 | | |
332 | 0 | fn enabled(&self) -> bool { |
333 | 0 | true |
334 | 0 | } |
335 | | |
336 | 0 | fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> { |
337 | 0 | self.maybe_roll(UnixTime::now())? |
338 | | .current |
339 | 0 | .encrypt(message) |
340 | 0 | } |
341 | | |
342 | 0 | fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> { |
343 | 0 | let state = self.maybe_roll(UnixTime::now())?; |
344 | | |
345 | | // Decrypt with the current key; if that fails, try with the previous. |
346 | 0 | state |
347 | 0 | .current |
348 | 0 | .decrypt(ciphertext) |
349 | 0 | .or_else(|| { |
350 | 0 | state |
351 | 0 | .previous |
352 | 0 | .as_ref() |
353 | 0 | .and_then(|previous| previous.decrypt(ciphertext)) |
354 | 0 | }) |
355 | 0 | } |
356 | | } |
357 | | |
358 | | #[cfg(feature = "std")] |
359 | | impl core::fmt::Debug for TicketRotator { |
360 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
361 | 0 | f.debug_struct("TicketRotator") |
362 | 0 | .finish_non_exhaustive() |
363 | 0 | } |
364 | | } |