Coverage Report

Created: 2024-12-17 06:15

/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
}