Coverage Report

Created: 2025-10-31 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}