clia_rustls_mod/
ticketer.rs

1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::mem;
4use std::sync::{Mutex, MutexGuard};
5
6use pki_types::UnixTime;
7
8use crate::server::ProducesTickets;
9use crate::{rand, Error};
10
11#[derive(Debug)]
12pub(crate) struct TicketSwitcherState {
13    next: Option<Box<dyn ProducesTickets>>,
14    current: Box<dyn ProducesTickets>,
15    previous: Option<Box<dyn ProducesTickets>>,
16    next_switch_time: u64,
17}
18
19/// A ticketer that has a 'current' sub-ticketer and a single
20/// 'previous' ticketer.  It creates a new ticketer every so
21/// often, demoting the current ticketer.
22#[derive(Debug)]
23pub struct TicketSwitcher {
24    pub(crate) generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>,
25    lifetime: u32,
26    state: Mutex<TicketSwitcherState>,
27}
28
29impl TicketSwitcher {
30    /// Creates a new `TicketSwitcher`, which rotates through sub-ticketers
31    /// based on the passage of time.
32    ///
33    /// `lifetime` is in seconds, and is how long the current ticketer
34    /// is used to generate new tickets.  Tickets are accepted for no
35    /// longer than twice this duration.  `generator` produces a new
36    /// `ProducesTickets` implementation.
37    pub fn new(
38        lifetime: u32,
39        generator: fn() -> Result<Box<dyn ProducesTickets>, rand::GetRandomFailed>,
40    ) -> Result<Self, Error> {
41        Ok(Self {
42            generator,
43            lifetime,
44            state: Mutex::new(TicketSwitcherState {
45                next: Some(generator()?),
46                current: generator()?,
47                previous: None,
48                next_switch_time: UnixTime::now()
49                    .as_secs()
50                    .saturating_add(u64::from(lifetime)),
51            }),
52        })
53    }
54
55    /// If it's time, demote the `current` ticketer to `previous` (so it
56    /// does no new encryptions but can do decryption) and use next for a
57    /// new `current` ticketer.
58    ///
59    /// Calling this regularly will ensure timely key erasure.  Otherwise,
60    /// key erasure will be delayed until the next encrypt/decrypt call.
61    ///
62    /// For efficiency, this is also responsible for locking the state mutex
63    /// and returning the mutexguard.
64    pub(crate) fn maybe_roll(&self, now: UnixTime) -> Option<MutexGuard<TicketSwitcherState>> {
65        // The code below aims to make switching as efficient as possible
66        // in the common case that the generator never fails. To achieve this
67        // we run the following steps:
68        //  1. If no switch is necessary, just return the mutexguard
69        //  2. Shift over all of the ticketers (so current becomes previous,
70        //     and next becomes current). After this, other threads can
71        //     start using the new current ticketer.
72        //  3. unlock mutex and generate new ticketer.
73        //  4. Place new ticketer in next and return current
74        //
75        // There are a few things to note here. First, we don't check whether
76        // a new switch might be needed in step 4, even though, due to locking
77        // and entropy collection, significant amounts of time may have passed.
78        // This is to guarantee that the thread doing the switch will eventually
79        // make progress.
80        //
81        // Second, because next may be None, step 2 can fail. In that case
82        // we enter a recovery mode where we generate 2 new ticketers, one for
83        // next and one for the current ticketer. We then take the mutex a
84        // second time and redo the time check to see if a switch is still
85        // necessary.
86        //
87        // This somewhat convoluted approach ensures good availability of the
88        // mutex, by ensuring that the state is usable and the mutex not held
89        // during generation. It also ensures that, so long as the inner
90        // ticketer never generates panics during encryption/decryption,
91        // we are guaranteed to never panic when holding the mutex.
92
93        let now = now.as_secs();
94        let mut are_recovering = false; // Are we recovering from previous failure?
95        {
96            // Scope the mutex so we only take it for as long as needed
97            let mut state = self.state.lock().ok()?;
98
99            // Fast path in case we do not need to switch to the next ticketer yet
100            if now <= state.next_switch_time {
101                return Some(state);
102            }
103
104            // Make the switch, or mark for recovery if not possible
105            if let Some(next) = state.next.take() {
106                state.previous = Some(mem::replace(&mut state.current, next));
107                state.next_switch_time = now.saturating_add(u64::from(self.lifetime));
108            } else {
109                are_recovering = true;
110            }
111        }
112
113        // We always need a next, so generate it now
114        let next = (self.generator)().ok()?;
115        if !are_recovering {
116            // Normal path, generate new next and place it in the state
117            let mut state = self.state.lock().ok()?;
118            state.next = Some(next);
119            Some(state)
120        } else {
121            // Recovering, generate also a new current ticketer, and modify state
122            // as needed. (we need to redo the time check, otherwise this might
123            // result in very rapid switching of ticketers)
124            let new_current = (self.generator)().ok()?;
125            let mut state = self.state.lock().ok()?;
126            state.next = Some(next);
127            if now > state.next_switch_time {
128                state.previous = Some(mem::replace(&mut state.current, new_current));
129                state.next_switch_time = now.saturating_add(u64::from(self.lifetime));
130            }
131            Some(state)
132        }
133    }
134}
135
136impl ProducesTickets for TicketSwitcher {
137    fn lifetime(&self) -> u32 {
138        self.lifetime * 2
139    }
140
141    fn enabled(&self) -> bool {
142        true
143    }
144
145    fn encrypt(&self, message: &[u8]) -> Option<Vec<u8>> {
146        let state = self.maybe_roll(UnixTime::now())?;
147
148        state.current.encrypt(message)
149    }
150
151    fn decrypt(&self, ciphertext: &[u8]) -> Option<Vec<u8>> {
152        let state = self.maybe_roll(UnixTime::now())?;
153
154        // Decrypt with the current key; if that fails, try with the previous.
155        state
156            .current
157            .decrypt(ciphertext)
158            .or_else(|| {
159                state
160                    .previous
161                    .as_ref()
162                    .and_then(|previous| previous.decrypt(ciphertext))
163            })
164    }
165}