/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rustls-0.23.27/src/ticketer.rs
Line | Count | Source |
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::{Error, rand}; |
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 | | 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 | | // The code below aims to make switching as efficient as possible |
104 | | // in the common case that the generator never fails. To achieve this |
105 | | // we run the following steps: |
106 | | // 1. If no switch is necessary, just return the mutexguard |
107 | | // 2. Shift over all of the ticketers (so current becomes previous, |
108 | | // and next becomes current). After this, other threads can |
109 | | // start using the new current ticketer. |
110 | | // 3. unlock mutex and generate new ticketer. |
111 | | // 4. Place new ticketer in next and return current |
112 | | // |
113 | | // There are a few things to note here. First, we don't check whether |
114 | | // a new switch might be needed in step 4, even though, due to locking |
115 | | // and entropy collection, significant amounts of time may have passed. |
116 | | // This is to guarantee that the thread doing the switch will eventually |
117 | | // make progress. |
118 | | // |
119 | | // Second, because next may be None, step 2 can fail. In that case |
120 | | // we enter a recovery mode where we generate 2 new ticketers, one for |
121 | | // next and one for the current ticketer. We then take the mutex a |
122 | | // second time and redo the time check to see if a switch is still |
123 | | // necessary. |
124 | | // |
125 | | // This somewhat convoluted approach ensures good availability of the |
126 | | // mutex, by ensuring that the state is usable and the mutex not held |
127 | | // during generation. It also ensures that, so long as the inner |
128 | | // ticketer never generates panics during encryption/decryption, |
129 | | // we are guaranteed to never panic when holding the mutex. |
130 | | |
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 | match state.next.take() { |
144 | 0 | Some(next) => { |
145 | 0 | state.previous = Some(mem::replace(&mut state.current, next)); |
146 | 0 | state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); |
147 | 0 | } |
148 | 0 | _ => are_recovering = true, |
149 | | } |
150 | | } |
151 | | |
152 | | // We always need a next, so generate it now |
153 | 0 | let next = (self.generator)().ok()?; |
154 | 0 | if !are_recovering { |
155 | | // Normal path, generate new next and place it in the state |
156 | 0 | let mut state = self.state.lock()?; |
157 | 0 | state.next = Some(next); |
158 | 0 | Some(state) |
159 | | } else { |
160 | | // Recovering, generate also a new current ticketer, and modify state |
161 | | // as needed. (we need to redo the time check, otherwise this might |
162 | | // result in very rapid switching of ticketers) |
163 | 0 | let new_current = (self.generator)().ok()?; |
164 | 0 | let mut state = self.state.lock()?; |
165 | 0 | state.next = Some(next); |
166 | 0 | if now > state.next_switch_time { |
167 | 0 | state.previous = Some(mem::replace(&mut state.current, new_current)); |
168 | 0 | state.next_switch_time = now.saturating_add(u64::from(self.lifetime)); |
169 | 0 | } |
170 | 0 | Some(state) |
171 | | } |
172 | 0 | } |
173 | | } |
174 | | |
175 | | impl ProducesTickets for TicketSwitcher { |
176 | 0 | fn lifetime(&self) -> u32 { |
177 | 0 | self.lifetime * 2 |
178 | 0 | } |
179 | | |
180 | 0 | fn enabled(&self) -> bool { |
181 | 0 | true |
182 | 0 | } |
183 | | |
184 | 0 | fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> { |
185 | | #[cfg(feature = "std")] |
186 | 0 | let now = UnixTime::now(); |
187 | | #[cfg(not(feature = "std"))] |
188 | | let now = self |
189 | | .time_provider |
190 | | .current_time() |
191 | | .unwrap(); |
192 | | |
193 | 0 | self.maybe_roll(now)? |
194 | | .current |
195 | 0 | .encrypt(message) |
196 | 0 | } |
197 | | |
198 | 0 | fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> { |
199 | | #[cfg(feature = "std")] |
200 | 0 | let now = UnixTime::now(); |
201 | | #[cfg(not(feature = "std"))] |
202 | | let now = self |
203 | | .time_provider |
204 | | .current_time() |
205 | | .unwrap(); |
206 | | |
207 | 0 | let state = self.maybe_roll(now)?; |
208 | | |
209 | | // Decrypt with the current key; if that fails, try with the previous. |
210 | 0 | state |
211 | 0 | .current |
212 | 0 | .decrypt(ciphertext) |
213 | 0 | .or_else(|| { |
214 | 0 | state |
215 | 0 | .previous |
216 | 0 | .as_ref() |
217 | 0 | .and_then(|previous| previous.decrypt(ciphertext)) |
218 | 0 | }) |
219 | 0 | } |
220 | | } |
221 | | |
222 | | #[cfg(not(feature = "std"))] |
223 | | impl core::fmt::Debug for TicketSwitcher { |
224 | | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
225 | | f.debug_struct("TicketSwitcher") |
226 | | .field("generator", &self.generator) |
227 | | .field("lifetime", &self.lifetime) |
228 | | .field("state", &**self.state.lock().unwrap()) |
229 | | .finish() |
230 | | } |
231 | | } |
232 | | |
233 | | #[cfg(feature = "std")] |
234 | | #[derive(Debug)] |
235 | | pub(crate) struct TicketRotatorState { |
236 | | current: Box<dyn ProducesTickets>, |
237 | | previous: Option<Box<dyn ProducesTickets>>, |
238 | | next_switch_time: u64, |
239 | | } |
240 | | |
241 | | /// A ticketer that has a 'current' sub-ticketer and a single |
242 | | /// 'previous' ticketer. It creates a new ticketer every so |
243 | | /// often, demoting the current ticketer. |
244 | | #[cfg(feature = "std")] |
245 | | pub struct TicketRotator { |
246 | | pub(crate) generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, |
247 | | lifetime: u32, |
248 | | state: RwLock<TicketRotatorState>, |
249 | | } |
250 | | |
251 | | #[cfg(feature = "std")] |
252 | | impl TicketRotator { |
253 | | /// Creates a new `TicketRotator`, which rotates through sub-ticketers |
254 | | /// based on the passage of time. |
255 | | /// |
256 | | /// `lifetime` is in seconds, and is how long the current ticketer |
257 | | /// is used to generate new tickets. Tickets are accepted for no |
258 | | /// longer than twice this duration. `generator` produces a new |
259 | | /// `ProducesTickets` implementation. |
260 | 0 | pub fn new( |
261 | 0 | lifetime: u32, |
262 | 0 | generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>, |
263 | 0 | ) -> Result<Self, Error> { |
264 | | Ok(Self { |
265 | 0 | generator, |
266 | 0 | lifetime, |
267 | 0 | state: RwLock::new(TicketRotatorState { |
268 | 0 | current: generator()?, |
269 | 0 | previous: None, |
270 | 0 | next_switch_time: UnixTime::now() |
271 | 0 | .as_secs() |
272 | 0 | .saturating_add(u64::from(lifetime)), |
273 | | }), |
274 | | }) |
275 | 0 | } |
276 | | |
277 | | /// If it's time, demote the `current` ticketer to `previous` (so it |
278 | | /// does no new encryptions but can do decryption) and replace it |
279 | | /// with a new one. |
280 | | /// |
281 | | /// Calling this regularly will ensure timely key erasure. Otherwise, |
282 | | /// key erasure will be delayed until the next encrypt/decrypt call. |
283 | | /// |
284 | | /// For efficiency, this is also responsible for locking the state rwlock |
285 | | /// and returning it for read. |
286 | 0 | pub(crate) fn maybe_roll( |
287 | 0 | &self, |
288 | 0 | now: UnixTime, |
289 | 0 | ) -> Option<RwLockReadGuard<'_, TicketRotatorState>> { |
290 | 0 | let now = now.as_secs(); |
291 | | |
292 | | // Fast, common, & read-only path in case we do not need to switch |
293 | | // to the next ticketer yet |
294 | | { |
295 | 0 | let read = self.state.read().ok()?; |
296 | | |
297 | 0 | if now <= read.next_switch_time { |
298 | 0 | return Some(read); |
299 | 0 | } |
300 | | } |
301 | | |
302 | | // We need to switch ticketers, and make a new one. |
303 | | // Generate a potential "next" ticketer outside the lock. |
304 | 0 | let next = (self.generator)().ok()?; |
305 | | |
306 | 0 | let mut write = self.state.write().ok()?; |
307 | | |
308 | 0 | if now <= write.next_switch_time { |
309 | | // Another thread beat us to it. Nothing to do. |
310 | 0 | drop(write); |
311 | | |
312 | 0 | return self.state.read().ok(); |
313 | 0 | } |
314 | | |
315 | | // Now we have: |
316 | | // - confirmed we need rotation |
317 | | // - confirmed we are the thread that will do it |
318 | | // - successfully made the replacement ticketer |
319 | 0 | write.previous = Some(mem::replace(&mut write.current, next)); |
320 | 0 | write.next_switch_time = now.saturating_add(u64::from(self.lifetime)); |
321 | 0 | drop(write); |
322 | | |
323 | 0 | self.state.read().ok() |
324 | 0 | } |
325 | | } |
326 | | |
327 | | #[cfg(feature = "std")] |
328 | | impl ProducesTickets for TicketRotator { |
329 | 0 | fn lifetime(&self) -> u32 { |
330 | 0 | self.lifetime * 2 |
331 | 0 | } |
332 | | |
333 | 0 | fn enabled(&self) -> bool { |
334 | 0 | true |
335 | 0 | } |
336 | | |
337 | 0 | fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> { |
338 | 0 | self.maybe_roll(UnixTime::now())? |
339 | | .current |
340 | 0 | .encrypt(message) |
341 | 0 | } |
342 | | |
343 | 0 | fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> { |
344 | 0 | let state = self.maybe_roll(UnixTime::now())?; |
345 | | |
346 | | // Decrypt with the current key; if that fails, try with the previous. |
347 | 0 | state |
348 | 0 | .current |
349 | 0 | .decrypt(ciphertext) |
350 | 0 | .or_else(|| { |
351 | 0 | state |
352 | 0 | .previous |
353 | 0 | .as_ref() |
354 | 0 | .and_then(|previous| previous.decrypt(ciphertext)) |
355 | 0 | }) |
356 | 0 | } |
357 | | } |
358 | | |
359 | | #[cfg(feature = "std")] |
360 | | impl core::fmt::Debug for TicketRotator { |
361 | 0 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
362 | 0 | f.debug_struct("TicketRotator") |
363 | 0 | .finish_non_exhaustive() |
364 | 0 | } |
365 | | } |