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#[serde(from = "SerdeSenderRatchet")]
81#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
82#[cfg_attr(any(feature = "crypto-debug", test), derive(Debug))]
83pub(crate) enum SenderRatchet {
84    #[cfg_attr(feature = "virtual-clients-draft", allow(unused))]
85    EncryptionRatchet(RatchetSecret),
86    DecryptionRatchet(DecryptionRatchet),
87    #[cfg(feature = "virtual-clients-draft")]
88    DualUse(DualUseRatchet),
89}
90
91/// Wire representation of [`SenderRatchet`] used to deserialize it via
92/// `#[serde(from = ...)]`. Unlike [`SenderRatchet`], the `DualUse` variant
93/// here is always present (its payload just changes shape depending on the
94/// `virtual-clients-draft` feature), so a value persisted by a build with the
95/// feature enabled can still be read by a build with the feature disabled,
96/// and vice versa. This avoids two failure modes of deserializing directly
97/// into [`SenderRatchet`]: an own ratchet persisted as `EncryptionRatchet`
98/// before the feature was turned on would otherwise never become a
99/// `DualUse` ratchet, and an own ratchet persisted as `DualUse` would
100/// otherwise fail to deserialize at all once the feature is turned off.
101#[derive(Deserialize)]
102enum SerdeSenderRatchet {
103    EncryptionRatchet(RatchetSecret),
104    DecryptionRatchet(DecryptionRatchet),
105    DualUse(SerdeDualUseRatchet),
106}
107
108#[cfg(feature = "virtual-clients-draft")]
109type SerdeDualUseRatchet = DualUseRatchet;
110
111#[cfg(not(feature = "virtual-clients-draft"))]
112#[derive(Deserialize)]
113struct SerdeDualUseRatchet {
114    ratchet_head: RatchetSecret,
115}
116
117impl From<SerdeSenderRatchet> for SenderRatchet {
118    fn from(repr: SerdeSenderRatchet) -> Self {
119        match repr {
120            SerdeSenderRatchet::EncryptionRatchet(ratchet_secret) => {
121                #[cfg(feature = "virtual-clients-draft")]
122                {
123                    SenderRatchet::DualUse(DualUseRatchet::from(ratchet_secret))
124                }
125                #[cfg(not(feature = "virtual-clients-draft"))]
126                {
127                    SenderRatchet::EncryptionRatchet(ratchet_secret)
128                }
129            }
130            SerdeSenderRatchet::DecryptionRatchet(dec_ratchet) => {
131                SenderRatchet::DecryptionRatchet(dec_ratchet)
132            }
133            SerdeSenderRatchet::DualUse(dual_ratchet) => {
134                #[cfg(feature = "virtual-clients-draft")]
135                {
136                    SenderRatchet::DualUse(dual_ratchet)
137                }
138                #[cfg(not(feature = "virtual-clients-draft"))]
139                {
140                    SenderRatchet::EncryptionRatchet(dual_ratchet.ratchet_head)
141                }
142            }
143        }
144    }
145}
146
147impl SenderRatchet {
148    #[cfg(test)]
149    pub(crate) fn generation(&self) -> Generation {
150        match self {
151            SenderRatchet::EncryptionRatchet(enc_ratchet) => enc_ratchet.generation(),
152            SenderRatchet::DecryptionRatchet(dec_ratchet) => dec_ratchet.generation(),
153            #[cfg(feature = "virtual-clients-draft")]
154            SenderRatchet::DualUse(dual_ratchet) => dual_ratchet.generation(),
155        }
156    }
157}
158
159/// The core of both types of [`SenderRatchet`]. It contains the current head of
160/// the ratchet chain, as well as its current [`Generation`]. It can be
161/// initialized with a given secret and then ratcheted forward, outputting
162/// [`RatchetKeyMaterial`] and increasing its [`Generation`] each time.
163#[derive(Debug, Serialize, Deserialize, Default)]
164#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
165pub(crate) struct RatchetSecret {
166    secret: Secret,
167    generation: Generation,
168}
169
170impl RatchetSecret {
171    /// Create an initial [`RatchetSecret`] with `generation = 0` from the given
172    /// [`Secret`].
173    pub(crate) fn initial_ratchet_secret(secret: Secret) -> Self {
174        Self {
175            secret,
176            generation: 0,
177        }
178    }
179
180    /// Return the generation of this [`RatchetSecret`].
181    pub(crate) fn generation(&self) -> Generation {
182        self.generation
183    }
184
185    /// Consume this [`RatchetSecret`] to derive a pair of [`RatchetSecrets`],
186    /// as well as the [`RatchetSecret`] of the next generation and return both.
187    pub(crate) fn ratchet_forward(
188        &mut self,
189        crypto: &impl OpenMlsCrypto,
190        ciphersuite: Ciphersuite,
191    ) -> Result<(Generation, RatchetKeyMaterial), SecretTreeError> {
192        log::trace!("Ratcheting forward in generation {}.", self.generation);
193        log_crypto!(trace, "    with secret {:x?}", self.secret);
194
195        // Check if the generation is getting too large.
196        if self.generation == u32::MAX {
197            return Err(SecretTreeError::RatchetTooLong);
198        }
199        let nonce = derive_tree_secret(
200            ciphersuite,
201            &self.secret,
202            "nonce",
203            self.generation,
204            ciphersuite.aead_nonce_length(),
205            crypto,
206        )?;
207        let key = derive_tree_secret(
208            ciphersuite,
209            &self.secret,
210            "key",
211            self.generation,
212            ciphersuite.aead_key_length(),
213            crypto,
214        )?;
215        self.secret = derive_tree_secret(
216            ciphersuite,
217            &self.secret,
218            "secret",
219            self.generation,
220            ciphersuite.hash_length(),
221            crypto,
222        )?;
223        let generation = self.generation;
224        self.generation += 1;
225        Ok((
226            generation,
227            (
228                AeadKey::from_secret(key, ciphersuite),
229                AeadNonce::from_secret(nonce),
230            ),
231        ))
232    }
233
234    #[cfg(test)]
235    pub(crate) fn set_generation(&mut self, generation: Generation) {
236        self.generation = generation
237    }
238}
239
240/// [`SenderRatchet`] used to derive key material for decryption. It keeps the
241/// [`RatchetKeyMaterial`] of epochs around until they are retrieved. This
242/// behaviour can be configured via the `out_of_order_tolerance` and
243/// `maximum_forward_distance` of the given [`SenderRatchetConfiguration`].
244#[derive(Serialize, Deserialize)]
245#[cfg_attr(any(feature = "test-utils", test), derive(PartialEq, Clone))]
246#[cfg_attr(any(feature = "crypto-debug", test), derive(Debug))]
247pub struct DecryptionRatchet {
248    past_secrets: VecDeque<Option<RatchetKeyMaterial>>,
249    ratchet_head: RatchetSecret,
250}
251
252impl DecryptionRatchet {
253    /// Creates e new SenderRatchet
254    pub(crate) fn new(secret: Secret) -> Self {
255        Self {
256            past_secrets: VecDeque::new(),
257            ratchet_head: RatchetSecret::initial_ratchet_secret(secret),
258        }
259    }
260
261    /// Get the generation of the ratchet head.
262    #[cfg(test)]
263    pub(crate) fn generation(&self) -> Generation {
264        self.ratchet_head.generation()
265    }
266
267    #[cfg(test)]
268    pub(crate) fn ratchet_secret_mut(&mut self) -> &mut RatchetSecret {
269        &mut self.ratchet_head
270    }
271
272    /// Gets a secret from the SenderRatchet. Returns an error if the generation
273    /// is out of bound.
274    pub(crate) fn secret_for_decryption(
275        &mut self,
276        ciphersuite: Ciphersuite,
277        crypto: &impl OpenMlsCrypto,
278        generation: Generation,
279        configuration: &SenderRatchetConfiguration,
280    ) -> Result<RatchetKeyMaterial, SecretTreeError> {
281        log::debug!("secret_for_decryption");
282        let head_generation = self.ratchet_head.generation();
283        // If generation is too distant in the future
284        if head_generation < u32::MAX - configuration.maximum_forward_distance()
285            && generation > head_generation + configuration.maximum_forward_distance()
286        {
287            return Err(SecretTreeError::TooDistantInTheFuture);
288        }
289        // If generation is too distant in the past
290        if generation < head_generation
291            && (head_generation - generation) > configuration.out_of_order_tolerance()
292        {
293            log::error!("  Generation is too far in the past (broke out of order tolerance ({}) {generation} < {head_generation}).", configuration.out_of_order_tolerance());
294            return Err(SecretTreeError::TooDistantInThePast);
295        }
296        // If generation is the one the ratchet is currently at or in the future
297        if generation >= head_generation {
298            // Ratchet the chain forward as far as necessary
299            for _ in 0..(generation - head_generation) {
300                // Derive the key material
301                let ratchet_secrets = self
302                    .ratchet_head
303                    .ratchet_forward(crypto, ciphersuite)
304                    .map(|(_, key_material)| key_material)?;
305                // Add it to the front of the queue
306                self.past_secrets.push_front(Some(ratchet_secrets));
307            }
308            let ratchet_secrets = self
309                .ratchet_head
310                .ratchet_forward(crypto, ciphersuite)
311                .map(|(_, key_material)| key_material)?;
312            // Add an entry to the past secrets queue to keep indexing consistent.
313            self.past_secrets.push_front(None);
314            self.past_secrets
315                .truncate(configuration.out_of_order_tolerance() as usize);
316            Ok(ratchet_secrets)
317        } else {
318            // If the requested generation is within the window of past secrets,
319            // we should get a positive index.
320            let window_index = ((head_generation - generation) as i32) - 1;
321            // We might not have the key material (e.g. we might have discarded
322            // it when generating an encryption secret).
323            let index = if window_index >= 0 {
324                window_index as usize
325            } else {
326                log::error!("  Generation is too far in the past (not in the window).");
327                return Err(SecretTreeError::TooDistantInThePast);
328            };
329            // Get the relevant secrets from the past secrets queue.
330            self.past_secrets
331                .get_mut(index)
332                .ok_or(SecretTreeError::IndexOutOfBounds)?
333                // We use take here to replace the entry in the `past_secrets`
334                // with `None`, thus achieving FS for that secret as soon as the
335                // caller of this function drops it.
336                .take()
337                // If the requested generation was used to decrypt a message
338                // earlier, throw an error.
339                .ok_or(SecretTreeError::SecretReuseError)
340        }
341    }
342}
343
344#[cfg(test)]
345mod persistence_tests {
346    use super::*;
347
348    // Reading an `EncryptionRatchet` as `DualUse` when enabling the `virtual-clients-draft` feature.
349    #[cfg(feature = "virtual-clients-draft")]
350    #[test]
351    fn pre_feature_encryption_ratchet_is_promoted_to_dual_use() {
352        let ratchet_head = RatchetSecret::initial_ratchet_secret(Secret::from_slice(&[0; 32]));
353        let restored: SenderRatchet =
354            SerdeSenderRatchet::EncryptionRatchet(ratchet_head.clone()).into();
355        match restored {
356            SenderRatchet::DualUse(dual_use) => {
357                assert_eq!(dual_use.generation(), ratchet_head.generation())
358            }
359            _ => panic!("expected promotion to DualUse"),
360        }
361    }
362
363    // A `DualUse` ratchet persisted when using the `virtual-clients-draft` that should
364    // be downgraded to a `EncryptionRatchet` when not using the feature.
365    #[cfg(not(feature = "virtual-clients-draft"))]
366    #[test]
367    fn persisted_dual_use_is_readable_without_the_feature() {
368        let ratchet_head = RatchetSecret::initial_ratchet_secret(Secret::from_slice(&[0; 32]));
369        let restored: SenderRatchet = SerdeSenderRatchet::DualUse(SerdeDualUseRatchet {
370            ratchet_head: ratchet_head.clone(),
371        })
372        .into();
373        match restored {
374            SenderRatchet::EncryptionRatchet(rs) => {
375                assert_eq!(rs.generation(), ratchet_head.generation())
376            }
377            _ => panic!("expected fallback to EncryptionRatchet"),
378        }
379    }
380}