openmls/tree/
sender_ratchet.rs

1//! ### Don't Panic!
2//!
3//! Functions in this module should never panic. However, if there is a bug in
4//! the implementation, a function will return an unrecoverable `LibraryError`.
5//! This means that some functions that are not expected to fail and throw an
6//! error, will still return a `Result` since they may throw a `LibraryError`.
7
8use openmls_traits::crypto::OpenMlsCrypto;
9use std::collections::VecDeque;
10
11use openmls_traits::types::Ciphersuite;
12
13use crate::ciphersuite::{AeadNonce, *};
14use crate::tree::secret_tree::*;
15
16use super::*;
17
18/// The generation of a given [`SenderRatchet`].
19pub(crate) type Generation = u32;
20/// Stores the configuration parameters for `DecryptionRatchet`s.
21///
22/// **Parameters**
23///
24/// - out_of_order_tolerance:
25///   This parameter defines a window for which decryption secrets are kept.
26///   This is useful in case the DS cannot guarantee that all application messages have total order within an epoch.
27///   Use this carefully, since keeping decryption secrets affects forward secrecy within an epoch.
28///   The default value is 5.
29/// - maximum_forward_distance:
30///   This parameter defines how many incoming messages can be skipped. This is useful if the DS
31///   drops application messages. The default value is 1000.
32#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
33pub struct SenderRatchetConfiguration {
34    out_of_order_tolerance: Generation,
35    maximum_forward_distance: Generation,
36}
37
38impl SenderRatchetConfiguration {
39    /// Create a new configuration
40    pub fn new(out_of_order_tolerance: Generation, maximum_forward_distance: Generation) -> Self {
41        Self {
42            out_of_order_tolerance,
43            maximum_forward_distance,
44        }
45    }
46    /// Get a reference to the sender ratchet configuration's out of order tolerance.
47    pub fn out_of_order_tolerance(&self) -> Generation {
48        self.out_of_order_tolerance
49    }
50
51    /// Get a reference to the sender ratchet configuration's maximum forward distance.
52    pub fn maximum_forward_distance(&self) -> Generation {
53        self.maximum_forward_distance
54    }
55}
56
57impl Default for SenderRatchetConfiguration {
58    fn default() -> Self {
59        Self::new(5, 1000)
60    }
61}
62
63/// The key material derived from a [`RatchetSecret`] meant for use with a
64/// nonce-based symmetric encryption scheme.
65pub(crate) type RatchetKeyMaterial = (AeadKey, AeadNonce);
66
67/// A ratchet that can output key material either for encryption
68/// ([`EncryptionRatchet`](SenderRatchet)) or decryption
69/// ([`DecryptionRatchet`]). A [`DecryptionRatchet`] can be configured with an
70/// `out_of_order_tolerance` and a `maximum_forward_distance` (see
71/// [`SenderRatchetConfiguration`]) while an Encryption Ratchet never keeps past
72/// secrets around.
73#[derive(Serialize, Deserialize)]
74#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
75#[cfg_attr(any(feature = "crypto-debug", test), derive(Debug))]
76pub(crate) enum SenderRatchet {
77    EncryptionRatchet(RatchetSecret),
78    DecryptionRatchet(DecryptionRatchet),
79}
80
81impl SenderRatchet {
82    #[cfg(test)]
83    pub(crate) fn generation(&self) -> Generation {
84        match self {
85            SenderRatchet::EncryptionRatchet(enc_ratchet) => enc_ratchet.generation(),
86            SenderRatchet::DecryptionRatchet(dec_ratchet) => dec_ratchet.generation(),
87        }
88    }
89}
90
91/// The core of both types of [`SenderRatchet`]. It contains the current head of
92/// the ratchet chain, as well as its current [`Generation`]. It can be
93/// initialized with a given secret and then ratcheted forward, outputting
94/// [`RatchetKeyMaterial`] and increasing its [`Generation`] each time.
95#[derive(Debug, Serialize, Deserialize, Default)]
96#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
97pub(crate) struct RatchetSecret {
98    secret: Secret,
99    generation: Generation,
100}
101
102impl RatchetSecret {
103    /// Create an initial [`RatchetSecret`] with `generation = 0` from the given
104    /// [`Secret`].
105    pub(crate) fn initial_ratchet_secret(secret: Secret) -> Self {
106        Self {
107            secret,
108            generation: 0,
109        }
110    }
111
112    /// Return the generation of this [`RatchetSecret`].
113    pub(crate) fn generation(&self) -> Generation {
114        self.generation
115    }
116
117    /// Consume this [`RatchetSecret`] to derive a pair of [`RatchetSecrets`],
118    /// as well as the [`RatchetSecret`] of the next generation and return both.
119    pub(crate) fn ratchet_forward(
120        &mut self,
121        crypto: &impl OpenMlsCrypto,
122        ciphersuite: Ciphersuite,
123    ) -> Result<(Generation, RatchetKeyMaterial), SecretTreeError> {
124        log::trace!("Ratcheting forward in generation {}.", self.generation);
125        log_crypto!(trace, "    with secret {:x?}", self.secret);
126
127        // Check if the generation is getting too large.
128        if self.generation == u32::MAX {
129            return Err(SecretTreeError::RatchetTooLong);
130        }
131        let nonce = derive_tree_secret(
132            ciphersuite,
133            &self.secret,
134            "nonce",
135            self.generation,
136            ciphersuite.aead_nonce_length(),
137            crypto,
138        )?;
139        let key = derive_tree_secret(
140            ciphersuite,
141            &self.secret,
142            "key",
143            self.generation,
144            ciphersuite.aead_key_length(),
145            crypto,
146        )?;
147        self.secret = derive_tree_secret(
148            ciphersuite,
149            &self.secret,
150            "secret",
151            self.generation,
152            ciphersuite.hash_length(),
153            crypto,
154        )?;
155        let generation = self.generation;
156        self.generation += 1;
157        Ok((
158            generation,
159            (
160                AeadKey::from_secret(key, ciphersuite),
161                AeadNonce::from_secret(nonce),
162            ),
163        ))
164    }
165
166    #[cfg(test)]
167    pub(crate) fn set_generation(&mut self, generation: Generation) {
168        self.generation = generation
169    }
170}
171
172/// [`SenderRatchet`] used to derive key material for decryption. It keeps the
173/// [`RatchetKeyMaterial`] of epochs around until they are retrieved. This
174/// behaviour can be configured via the `out_of_order_tolerance` and
175/// `maximum_forward_distance` of the given [`SenderRatchetConfiguration`].
176#[derive(Serialize, Deserialize)]
177#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
178#[cfg_attr(any(feature = "crypto-debug", test), derive(Debug))]
179pub struct DecryptionRatchet {
180    past_secrets: VecDeque<Option<RatchetKeyMaterial>>,
181    ratchet_head: RatchetSecret,
182}
183
184impl DecryptionRatchet {
185    /// Creates e new SenderRatchet
186    pub(crate) fn new(secret: Secret) -> Self {
187        Self {
188            past_secrets: VecDeque::new(),
189            ratchet_head: RatchetSecret::initial_ratchet_secret(secret),
190        }
191    }
192
193    /// Remove elements from the `past_secrets` queue until it is within the
194    /// bounds determined by the [`SenderRatchetConfiguration`].
195    fn prune_past_secrets(&mut self, configuration: &SenderRatchetConfiguration) {
196        self.past_secrets
197            .truncate(configuration.out_of_order_tolerance() as usize)
198    }
199
200    /// Get the generation of the ratchet head.
201    pub(crate) fn generation(&self) -> Generation {
202        self.ratchet_head.generation()
203    }
204
205    #[cfg(test)]
206    pub(crate) fn ratchet_secret_mut(&mut self) -> &mut RatchetSecret {
207        &mut self.ratchet_head
208    }
209
210    /// Gets a secret from the SenderRatchet. Returns an error if the generation
211    /// is out of bound.
212    pub(crate) fn secret_for_decryption(
213        &mut self,
214        ciphersuite: Ciphersuite,
215        crypto: &impl OpenMlsCrypto,
216        generation: Generation,
217        configuration: &SenderRatchetConfiguration,
218    ) -> Result<RatchetKeyMaterial, SecretTreeError> {
219        log::debug!("secret_for_decryption");
220        // If generation is too distant in the future
221        if self.generation() < u32::MAX - configuration.maximum_forward_distance()
222            && generation > self.generation() + configuration.maximum_forward_distance()
223        {
224            return Err(SecretTreeError::TooDistantInTheFuture);
225        }
226        // If generation id too distant in the past
227        if generation < self.generation()
228            && (self.generation() - generation) > configuration.out_of_order_tolerance()
229        {
230            log::error!("  Generation is too far in the past (broke out of order tolerance ({}) {generation} < {}).", configuration.out_of_order_tolerance(), self.generation());
231            return Err(SecretTreeError::TooDistantInThePast);
232        }
233        // If generation is the one the ratchet is currently at or in the future
234        if generation >= self.generation() {
235            // Ratchet the chain forward as far as necessary
236            for _ in 0..(generation - self.generation()) {
237                // Derive the key material
238                let ratchet_secrets = {
239                    self.ratchet_head
240                        .ratchet_forward(crypto, ciphersuite)
241                        .map(|(_, key_material)| key_material)
242                }?;
243                // Add it to the front of the queue
244                self.past_secrets.push_front(Some(ratchet_secrets));
245            }
246            let ratchet_secrets = {
247                self.ratchet_head
248                    .ratchet_forward(crypto, ciphersuite)
249                    .map(|(_, key_material)| key_material)
250            }?;
251            // Add an entry to the past secrets queue to keep indexing consistent.
252            self.past_secrets.push_front(None);
253            self.prune_past_secrets(configuration);
254            Ok(ratchet_secrets)
255        } else {
256            // If the requested generation is within the window of past secrets,
257            // we should get a positive index.
258            let window_index = ((self.generation() - generation) as i32) - 1;
259            // We might not have the key material (e.g. we might have discarded
260            // it when generating an encryption secret).
261            let index = if window_index >= 0 {
262                window_index as usize
263            } else {
264                log::error!("  Generation is too far in the past (not in the window).");
265                return Err(SecretTreeError::TooDistantInThePast);
266            };
267            // Get the relevant secrets from the past secrets queue.
268            self.past_secrets
269                .get_mut(index)
270                .ok_or(SecretTreeError::IndexOutOfBounds)?
271                // We use take here to replace the entry in the `past_secrets`
272                // with `None`, thus achieving FS for that secret as soon as the
273                // caller of this function drops it.
274                .take()
275                // If the requested generation was used to decrypt a message
276                // earlier, throw an error.
277                .ok_or(SecretTreeError::SecretReuseError)
278        }
279    }
280}