Skip to main content

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