rustls/
ticketer.rs

1use crate::rand;
2use crate::server::ProducesTickets;
3use crate::Error;
4
5use pki_types::UnixTime;
6
7use alloc::boxed::Box;
8use alloc::vec::Vec;
9use core::mem;
10use std::sync::{Mutex, MutexGuard};
11
12#[derive(Debug)]
13pub(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)]
24pub struct TicketSwitcher {
25    pub(crate) generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>,
26    lifetime: u32,
27    state: Mutex<TicketSwitcherState>,
28}
29
30impl 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
137impl 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}