Skip to main content

openmls/group/mls_group/
config.rs

1//! Configuration module for [`MlsGroup`] configurations.
2//!
3//! ## Building an MlsGroupCreateConfig
4//! The [`MlsGroupCreateConfigBuilder`] makes it easy to build configurations for the
5//! [`MlsGroup`].
6//!
7//! ```
8//! use openmls::prelude::*;
9//!
10//! let group_config = MlsGroupCreateConfig::builder()
11//!     .use_ratchet_tree_extension(true)
12//!     .build();
13//! ```
14//!
15//! See [`MlsGroupCreateConfigBuilder`](MlsGroupCreateConfigBuilder#implementations) for
16//! all options that can be configured.
17//!
18//! ### Wire format policies
19//! Only some combination of possible wire formats are valid within OpenMLS.
20//! The [`WIRE_FORMAT_POLICIES`] lists all valid options that can be set.
21//!
22//! ```
23//! use openmls::prelude::*;
24//!
25//! let group_config = MlsGroupCreateConfig::builder()
26//!     .wire_format_policy(MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY)
27//!     .build();
28//! ```
29
30#[cfg(not(target_arch = "wasm32"))]
31use std::time::SystemTime;
32#[cfg(target_arch = "wasm32")]
33use web_time::SystemTime;
34
35use super::*;
36use crate::{
37    extensions::Extensions,
38    key_packages::Lifetime,
39    tree::sender_ratchet::SenderRatchetConfiguration,
40    treesync::{errors::LeafNodeValidationError, node::leaf_node::Capabilities},
41};
42use serde::{Deserialize, Serialize};
43
44/// Configures the automatic deletion of past epoch secrets.
45///
46/// **WARNING**
47///
48/// Policies other than `MaxEpochs(0)` enable the storage of message secrets from past epochs.
49/// It is a trade-off between functionality and forward secrecy and should only be enabled
50/// if the Delivery Service cannot guarantee that application messages will be sent in
51/// the same epoch in which they were generated. The number for `max_epochs` should be
52/// as low as possible.
53///
54/// If the MaxEpochs policy is set to MaxEpochs(usize::MAX), if will be returned
55/// as KeepAll after deserialization due to backwards compatibility constraints.
56#[derive(Clone, Debug, PartialEq, Eq)]
57pub enum PastEpochDeletionPolicy {
58    /// Keep at most `n` past epoch secrets.
59    MaxEpochs(usize),
60    /// Keep all past epoch secrets.
61    ///
62    /// NOTE: The application is responsible for deleting past epoch secrets when
63    /// `KeepAll` is set. Past epoch secrets can be deleted manually using:
64    /// - [`MlsGroup::delete_past_epoch_secrets()`]
65    KeepAll,
66}
67
68impl Default for PastEpochDeletionPolicy {
69    fn default() -> Self {
70        Self::MaxEpochs(0)
71    }
72}
73
74impl Serialize for PastEpochDeletionPolicy {
75    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76    where
77        S: serde::Serializer,
78    {
79        let usize = match self {
80            Self::MaxEpochs(epochs) => *epochs,
81            Self::KeepAll => usize::MAX,
82        };
83        serializer.serialize_u64(usize as u64)
84    }
85}
86
87impl<'de> Deserialize<'de> for PastEpochDeletionPolicy {
88    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
89        // Previously supported format (changed to plain integer in 8bdba6f)
90        #[derive(Deserialize)]
91        enum Tagged {
92            MaxEpochs(usize),
93            KeepAll,
94        }
95
96        #[derive(Deserialize)]
97        #[serde(untagged)]
98        enum Format {
99            Int(u64),
100            Tagged(Tagged),
101        }
102
103        Ok(match Format::deserialize(deserializer)? {
104            Format::Int(u64::MAX) => Self::KeepAll,
105            Format::Int(n) => {
106                Self::MaxEpochs(usize::try_from(n).map_err(serde::de::Error::custom)?)
107            }
108            Format::Tagged(Tagged::MaxEpochs(n)) => Self::MaxEpochs(n),
109            Format::Tagged(Tagged::KeepAll) => Self::KeepAll,
110        })
111    }
112}
113
114/// The input to [`MlsGroup::delete_past_epoch_secrets()`].
115///
116/// This struct can be used for manual deletion of past epoch secrets by the application.
117///
118/// An [`MlsGroup`] also applies automatic deletion of past epoch secrets by default.
119///
120/// For more information, see [`PastEpochDeletionPolicy`] and [`MlsGroup::set_past_epoch_deletion_policy()`].
121///
122/// These methods can be used by the application to set up time-based deletion schedules:
123/// - [`PastEpochDeletion::before_timestamp()`]
124/// - [`PastEpochDeletion::older_than_duration()`]
125///
126/// **NOTE**: Epoch secrets that were created using `openmls=0.8.1` or earlier will not yet include a timestamp.
127/// After migration, these may not always be deleted by applying a time-based [`PastEpochDeletion`]. Only if a new secret that does include a timestamp is added later, and it matches the time-based condition in the [`PastEpochDeletion`], all earlier past epoch secrets without timestamps will be deleted, as well. However, otherwise, past epoch secrets without timestamps will not be affected by applying time-based [`PastEpochDeletion`]s.
128///
129/// To manually delete all past epoch secrets without timestamps, see:
130/// [`PastEpochDeletion::delete_all_without_timestamps()`]
131pub struct PastEpochDeletion {
132    pub(crate) config: Option<PastEpochDeletionTimeConfig>,
133    pub(crate) max_past_epochs: Option<usize>,
134}
135
136/// A duration or timestamp before which to delete past epoch secrets.
137pub(crate) enum PastEpochDeletionTimeConfig {
138    OlderThanDuration(std::time::Duration),
139    BeforeTimestamp(SystemTime),
140    DeleteAllWithoutTimestamp,
141}
142
143impl PastEpochDeletion {
144    /// Delete all past epoch secrets older than a provided duration.
145    pub fn older_than_duration(duration: std::time::Duration) -> Self {
146        Self {
147            config: Some(PastEpochDeletionTimeConfig::OlderThanDuration(duration)),
148            max_past_epochs: None,
149        }
150    }
151
152    /// Delete all past epoch secrets before a provided timestamp.
153    pub fn before_timestamp(timestamp: SystemTime) -> Self {
154        Self {
155            config: Some(PastEpochDeletionTimeConfig::BeforeTimestamp(timestamp)),
156            max_past_epochs: None,
157        }
158    }
159
160    /// Delete all past epoch secrets without timestamps.
161    ///
162    /// NOTE: This will delete all past epoch secrets having the legacy
163    /// format that does not include a timestamp.
164    pub fn delete_all_without_timestamps() -> Self {
165        Self {
166            config: Some(PastEpochDeletionTimeConfig::DeleteAllWithoutTimestamp),
167            max_past_epochs: None,
168        }
169    }
170
171    /// Delete all past epoch secrets.
172    pub fn delete_all() -> Self {
173        Self {
174            config: None,
175            max_past_epochs: None,
176        }
177    }
178
179    /// Set the number of `max_past_epochs` that should be kept, at most.
180    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
181        self.max_past_epochs = Some(max_past_epochs);
182        self
183    }
184}
185
186impl PastEpochDeletionPolicy {
187    pub(crate) fn max_epochs(&self) -> Option<usize> {
188        match self {
189            Self::MaxEpochs(epochs) => Some(*epochs),
190            Self::KeepAll => None,
191        }
192    }
193}
194
195/// Configures how many virtual-clients derivation epochs an emulation group
196/// keeps, so that delayed messages from sibling emulator clients can still be
197/// processed.
198///
199/// Registering a new derivation epoch drops the epochs beyond the window. Their
200/// key material is deleted unless a higher-level group or a retained KeyPackage
201/// still references it.
202#[cfg(feature = "virtual-clients-draft")]
203#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
204pub enum VcDerivationEpochRetentionPolicy {
205    /// Keep at most `n` derivation epochs.
206    MaxEpochs(usize),
207    /// Keep every derivation epoch. The application deletes them with
208    /// [`MlsGroup::delete_vc_derivation_epochs()`].
209    KeepAll,
210}
211
212#[cfg(feature = "virtual-clients-draft")]
213impl Default for VcDerivationEpochRetentionPolicy {
214    fn default() -> Self {
215        Self::MaxEpochs(5)
216    }
217}
218
219#[cfg(feature = "virtual-clients-draft")]
220impl VcDerivationEpochRetentionPolicy {
221    pub(crate) fn max_epochs(&self) -> Option<usize> {
222        match self {
223            Self::MaxEpochs(epochs) => Some(*epochs),
224            Self::KeepAll => None,
225        }
226    }
227}
228
229/// Selects the derivation epochs [`MlsGroup::delete_vc_derivation_epochs()`]
230/// deletes: those superseded before a point in time, optionally capped to a
231/// number of surviving epochs. An epoch is superseded when the next one is
232/// registered. The newest epoch is never selected.
233#[cfg(feature = "virtual-clients-draft")]
234pub struct VcDerivationEpochDeletion {
235    pub(crate) time: VcDerivationEpochDeletionTime,
236    pub(crate) max_epochs: Option<usize>,
237}
238
239/// A duration or timestamp before which superseded derivation epochs are
240/// deleted.
241#[cfg(feature = "virtual-clients-draft")]
242pub(crate) enum VcDerivationEpochDeletionTime {
243    OlderThanDuration(std::time::Duration),
244    BeforeTimestamp(SystemTime),
245}
246
247#[cfg(feature = "virtual-clients-draft")]
248impl VcDerivationEpochDeletion {
249    /// Delete all derivation epochs superseded more than `duration` ago.
250    pub fn older_than_duration(duration: std::time::Duration) -> Self {
251        Self {
252            time: VcDerivationEpochDeletionTime::OlderThanDuration(duration),
253            max_epochs: None,
254        }
255    }
256
257    /// Delete all derivation epochs superseded before `timestamp`.
258    pub fn before_timestamp(timestamp: SystemTime) -> Self {
259        Self {
260            time: VcDerivationEpochDeletionTime::BeforeTimestamp(timestamp),
261            max_epochs: None,
262        }
263    }
264
265    /// Additionally cap the number of derivation epochs that survive.
266    pub fn max_epochs(mut self, max_epochs: usize) -> Self {
267        self.max_epochs = Some(max_epochs);
268        self
269    }
270}
271
272/// Returned by [`MlsGroup::delete_vc_derivation_epochs()`]. Selected epochs
273/// whose per-epoch state was already absent appear in neither list.
274#[cfg(feature = "virtual-clients-draft")]
275#[derive(Debug, Default, Clone, PartialEq, Eq)]
276pub struct VcDerivationEpochDeletionResult {
277    /// The derivation epochs whose per-epoch state was deleted.
278    pub deleted: Vec<crate::components::vc_derivation_info::EpochId>,
279    /// The derivation epochs whose per-epoch state was kept because something
280    /// still references it.
281    pub kept: Vec<crate::components::vc_derivation_info::EpochId>,
282}
283
284/// The [`MlsGroupJoinConfig`] contains all configuration parameters that are
285/// relevant to group operation at runtime. It is used to configure the group's
286/// behaviour when joining an existing group. To configure a newly created
287/// group, use [`MlsGroupCreateConfig`].
288#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
289pub struct MlsGroupJoinConfig {
290    /// Defines the wire format policy for outgoing and incoming handshake messages.
291    /// Application are always encrypted regardless.
292    pub(crate) wire_format_policy: WireFormatPolicy,
293    /// Size of padding in bytes
294    pub(crate) padding_size: usize,
295    /// Maximum number of past epochs for which application messages
296    /// can be decrypted. The default is 0.
297    #[serde(alias = "max_past_epochs")]
298    // alias for backwards compatibility after renaming field
299    pub(crate) past_epoch_deletion_policy: PastEpochDeletionPolicy,
300    /// Number of resumption secrets to keep
301    pub(crate) number_of_resumption_psks: usize,
302    /// Flag to indicate the Ratchet Tree Extension should be used
303    pub(crate) use_ratchet_tree_extension: bool,
304    /// Sender ratchet configuration
305    pub(crate) sender_ratchet_configuration: SenderRatchetConfiguration,
306    /// Derivation-epoch retention policy, only consulted on emulation groups.
307    /// See [`VcDerivationEpochRetentionPolicy`].
308    #[cfg(feature = "virtual-clients-draft")]
309    #[serde(default)]
310    pub(crate) vc_derivation_epoch_retention_policy: VcDerivationEpochRetentionPolicy,
311}
312
313impl MlsGroupJoinConfig {
314    /// Returns a builder for [`MlsGroupJoinConfig`].
315    pub fn builder() -> MlsGroupJoinConfigBuilder {
316        MlsGroupJoinConfigBuilder::new()
317    }
318
319    /// Returns the wire format policy set in this  [`MlsGroupJoinConfig`].
320    pub fn wire_format_policy(&self) -> WireFormatPolicy {
321        self.wire_format_policy
322    }
323
324    /// Returns the padding size set in this  [`MlsGroupJoinConfig`].
325    pub fn padding_size(&self) -> usize {
326        self.padding_size
327    }
328
329    /// Returns the [`SenderRatchetConfiguration`] set in this  [`MlsGroupJoinConfig`].
330    pub fn sender_ratchet_configuration(&self) -> &SenderRatchetConfiguration {
331        &self.sender_ratchet_configuration
332    }
333
334    /// Returns the max past epochs configured in this [`MlsGroupJoinConfig`]
335    pub(crate) fn max_past_epochs(&self) -> Option<usize> {
336        self.past_epoch_deletion_policy.max_epochs()
337    }
338
339    pub(crate) fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
340        &self.past_epoch_deletion_policy
341    }
342
343    /// Returns the derivation-epoch retention policy set in this
344    /// [`MlsGroupJoinConfig`].
345    #[cfg(feature = "virtual-clients-draft")]
346    pub fn vc_derivation_epoch_retention_policy(&self) -> &VcDerivationEpochRetentionPolicy {
347        &self.vc_derivation_epoch_retention_policy
348    }
349}
350
351/// Specifies configuration for the creation of an [`MlsGroup`]. Refer to the
352/// [User Manual](https://book.openmls.tech/user_manual/group_config.html) for
353/// more information about the different configuration values.
354#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
355pub struct MlsGroupCreateConfig {
356    /// Capabilities advertised in the creator's leaf node
357    pub(crate) capabilities: Capabilities,
358    /// Lifetime of the own leaf node
359    pub(crate) lifetime: Lifetime,
360    /// Ciphersuite and protocol version
361    pub(crate) ciphersuite: Ciphersuite,
362    /// Configuration parameters relevant to group operation at runtime
363    pub(crate) join_config: MlsGroupJoinConfig,
364    /// List of initial group context extensions
365    pub(crate) group_context_extensions: Extensions<GroupContext>,
366    /// List of initial leaf node extensions
367    pub(crate) leaf_node_extensions: Extensions<LeafNode>,
368    /// Flag marking the created group as an emulation group of a virtual
369    /// client. Only consulted at group creation, the group keeps the flag
370    /// itself afterwards.
371    #[cfg(feature = "virtual-clients-draft")]
372    pub(crate) emulation_group: bool,
373}
374
375impl Default for MlsGroupCreateConfig {
376    fn default() -> Self {
377        Self {
378            capabilities: Capabilities::default(),
379            lifetime: Lifetime::default(),
380            ciphersuite: Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
381            join_config: MlsGroupJoinConfig::default(),
382            group_context_extensions: Extensions::default(),
383            leaf_node_extensions: Extensions::default(),
384            #[cfg(feature = "virtual-clients-draft")]
385            emulation_group: false,
386        }
387    }
388}
389
390/// Builder struct for an [`MlsGroupJoinConfig`].
391#[derive(Default)]
392pub struct MlsGroupJoinConfigBuilder {
393    join_config: MlsGroupJoinConfig,
394}
395
396impl MlsGroupJoinConfigBuilder {
397    /// Creates a new builder with default values.
398    fn new() -> Self {
399        Self {
400            join_config: MlsGroupJoinConfig::default(),
401        }
402    }
403
404    /// Sets the `wire_format` property of the [`MlsGroupJoinConfig`].
405    pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
406        self.join_config.wire_format_policy = wire_format_policy;
407        self
408    }
409
410    /// Sets the `padding_size` property of the [`MlsGroupJoinConfig`].
411    pub fn padding_size(mut self, padding_size: usize) -> Self {
412        self.join_config.padding_size = padding_size;
413        self
414    }
415
416    /// Sets the `max_past_epochs` property of the [`MlsGroupJoinConfig`].
417    ///
418    /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
419    /// and is equivalent to setting the past epoch deletion policy to
420    /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
421    ///
422    /// **WARNING**
423    ///
424    /// This feature enables the storage of message secrets from past epochs.
425    /// It is a trade-off between functionality and forward secrecy and should only be enabled
426    /// if the Delivery Service cannot guarantee that application messages will be sent in
427    /// the same epoch in which they were generated. The number for `max_epochs` should be
428    /// as low as possible.
429    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
430        self.join_config.past_epoch_deletion_policy =
431            PastEpochDeletionPolicy::MaxEpochs(max_past_epochs);
432        self
433    }
434
435    /// Set the policy for deleting past epoch secrets.
436    ///
437    /// By default, storage of past epoch secrets is disabled.
438    ///
439    /// This method overrides the configuration set by [`Self::max_past_epochs()`].
440    ///
441    /// **WARNING**
442    ///
443    /// This feature enables the storage of message secrets from past epochs.
444    /// It is a trade-off between functionality and forward secrecy and should only be enabled
445    /// if the Delivery Service cannot guarantee that application messages will be sent in
446    /// the same epoch in which they were generated. The number for `max_epochs` should be
447    /// as low as possible.
448    pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
449        self.join_config.past_epoch_deletion_policy = policy;
450        self
451    }
452
453    /// Sets the derivation-epoch retention policy. See
454    /// [`VcDerivationEpochRetentionPolicy`].
455    #[cfg(feature = "virtual-clients-draft")]
456    pub fn set_vc_derivation_epoch_retention_policy(
457        mut self,
458        policy: VcDerivationEpochRetentionPolicy,
459    ) -> Self {
460        self.join_config.vc_derivation_epoch_retention_policy = policy;
461        self
462    }
463
464    /// Sets the `number_of_resumption_psks` property of the [`MlsGroupJoinConfig`].
465    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
466        self.join_config.number_of_resumption_psks = number_of_resumption_psks;
467        self
468    }
469
470    /// Sets the `use_ratchet_tree_extension` property of the [`MlsGroupJoinConfig`].
471    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
472        self.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
473        self
474    }
475
476    /// Sets the `sender_ratchet_configuration` property of the [`MlsGroupJoinConfig`].
477    pub fn sender_ratchet_configuration(
478        mut self,
479        sender_ratchet_configuration: SenderRatchetConfiguration,
480    ) -> Self {
481        self.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
482        self
483    }
484
485    /// Finalizes the builder and returns an [`MlsGroupJoinConfig`].
486    pub fn build(self) -> MlsGroupJoinConfig {
487        self.join_config
488    }
489}
490
491impl MlsGroupCreateConfig {
492    /// Returns a builder for [`MlsGroupCreateConfig`]
493    pub fn builder() -> MlsGroupCreateConfigBuilder {
494        MlsGroupCreateConfigBuilder::new()
495    }
496
497    /// Returns the [`MlsGroupCreateConfig`] wire format policy.
498    pub fn wire_format_policy(&self) -> WireFormatPolicy {
499        self.join_config.wire_format_policy
500    }
501
502    /// Returns the [`MlsGroupCreateConfig`] padding size.
503    pub fn padding_size(&self) -> usize {
504        self.join_config.padding_size
505    }
506
507    /// Returns the [`MlsGroupCreateConfig`] max past epochs.
508    pub fn max_past_epochs(&self) -> Option<usize> {
509        self.join_config.max_past_epochs()
510    }
511
512    /// Returns the [`MlsGroupCreateConfig`] number of resumption psks.
513    pub fn number_of_resumption_psks(&self) -> usize {
514        self.join_config.number_of_resumption_psks
515    }
516
517    /// Returns the [`MlsGroupCreateConfig`] boolean flag that indicates whether ratchet_tree_extension should be used.
518    pub fn use_ratchet_tree_extension(&self) -> bool {
519        self.join_config.use_ratchet_tree_extension
520    }
521
522    /// Returns the [`MlsGroupCreateConfig`] sender ratchet configuration.
523    pub fn sender_ratchet_configuration(&self) -> &SenderRatchetConfiguration {
524        &self.join_config.sender_ratchet_configuration
525    }
526
527    /// Returns the [`Extensions`] set as the initial group context.
528    /// This does not contain the initial group context extensions
529    /// added from builder calls to `external_senders` or `required_capabilities`.
530    pub fn group_context_extensions(&self) -> &Extensions<GroupContext> {
531        &self.group_context_extensions
532    }
533
534    /// Returns the [`MlsGroupCreateConfig`] lifetime configuration.
535    pub fn lifetime(&self) -> &Lifetime {
536        &self.lifetime
537    }
538
539    /// Returns the [`Ciphersuite`].
540    pub fn ciphersuite(&self) -> Ciphersuite {
541        self.ciphersuite
542    }
543
544    /// Returns whether groups created with this config are emulation groups of
545    /// a virtual client. See [`MlsGroupCreateConfigBuilder::emulation_group`].
546    #[cfg(feature = "virtual-clients-draft")]
547    pub fn emulation_group(&self) -> bool {
548        self.emulation_group
549    }
550
551    #[cfg(any(feature = "test-utils", test))]
552    pub fn test_default(ciphersuite: Ciphersuite) -> Self {
553        Self::builder()
554            .wire_format_policy(WireFormatPolicy::new(
555                OutgoingWireFormatPolicy::AlwaysPlaintext,
556                IncomingWireFormatPolicy::Mixed,
557            ))
558            .ciphersuite(ciphersuite)
559            .build()
560    }
561
562    /// Returns the [`MlsGroupJoinConfig`] of groups created with this create config.
563    pub fn join_config(&self) -> &MlsGroupJoinConfig {
564        &self.join_config
565    }
566}
567
568/// Builder for an [`MlsGroupCreateConfig`].
569#[derive(Default, Debug)]
570pub struct MlsGroupCreateConfigBuilder {
571    config: MlsGroupCreateConfig,
572}
573
574impl MlsGroupCreateConfigBuilder {
575    /// Creates a new builder with default values.
576    fn new() -> Self {
577        MlsGroupCreateConfigBuilder {
578            config: MlsGroupCreateConfig::default(),
579        }
580    }
581
582    /// Sets the `wire_format` property of the MlsGroupCreateConfig.
583    pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
584        self.config.join_config.wire_format_policy = wire_format_policy;
585        self
586    }
587
588    /// Sets the `padding_size` property of the MlsGroupCreateConfig.
589    pub fn padding_size(mut self, padding_size: usize) -> Self {
590        self.config.join_config.padding_size = padding_size;
591        self
592    }
593
594    /// Sets the `max_past_epochs` property of the MlsGroupCreateConfig.
595    /// This allows application messages from previous epochs to be decrypted.
596    ///
597    /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
598    /// and is equivalent to setting the past epoch deletion policy to
599    /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
600    ///
601    /// **WARNING**
602    ///
603    /// This feature enables the storage of message secrets from past epochs.
604    /// It is a trade-off between functionality and forward secrecy and should only be enabled
605    /// if the Delivery Service cannot guarantee that application messages will be sent in
606    /// the same epoch in which they were generated. The number for `max_epochs` should be
607    /// as low as possible.
608    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
609        self.config.join_config.past_epoch_deletion_policy =
610            PastEpochDeletionPolicy::MaxEpochs(max_past_epochs);
611        self
612    }
613
614    /// Set the policy for deleting past epoch secrets.
615    ///
616    /// By default, storage of past epoch secrets is disabled.
617    ///
618    /// This method overrides the configuration set by [`Self::max_past_epochs()`].
619    ///
620    /// **WARNING**
621    ///
622    /// This feature enables the storage of message secrets from past epochs.
623    /// It is a trade-off between functionality and forward secrecy and should only be enabled
624    /// if the Delivery Service cannot guarantee that application messages will be sent in
625    /// the same epoch in which they were generated. The number for `max_epochs` should be
626    /// as low as possible.
627    pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
628        self.config.join_config.past_epoch_deletion_policy = policy;
629        self
630    }
631
632    /// Sets the derivation-epoch retention policy. See
633    /// [`VcDerivationEpochRetentionPolicy`].
634    #[cfg(feature = "virtual-clients-draft")]
635    pub fn set_vc_derivation_epoch_retention_policy(
636        mut self,
637        policy: VcDerivationEpochRetentionPolicy,
638    ) -> Self {
639        self.config.join_config.vc_derivation_epoch_retention_policy = policy;
640        self
641    }
642
643    /// Sets the `number_of_resumption_psks` property of the MlsGroupCreateConfig.
644    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
645        self.config.join_config.number_of_resumption_psks = number_of_resumption_psks;
646        self
647    }
648
649    /// Sets the `use_ratchet_tree_extension` property of the MlsGroupCreateConfig.
650    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
651        self.config.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
652        self
653    }
654
655    /// Sets the `capabilities` of the group creator's leaf node.
656    pub fn capabilities(mut self, capabilities: Capabilities) -> Self {
657        self.config.capabilities = capabilities;
658        self
659    }
660
661    /// Marks the group as an emulation group of a virtual client.
662    ///
663    /// This is the application's declaration that the group's members are the
664    /// emulator clients of one virtual client. It is local state, nothing about
665    /// it travels on the wire, and every member of an emulation group has to
666    /// set it. Members that join by Welcome set it on the
667    /// [`StagedWelcome`](crate::group::StagedWelcome) instead, and members that
668    /// join by external commit on the
669    /// [`ExternalCommitBuilder`](crate::group::ExternalCommitBuilder).
670    ///
671    /// An emulation group derives the virtual client's secrets from its
672    /// derivation epochs. The initial epoch is a derivation epoch, and so is
673    /// the output epoch of every commit that changes membership or that carries
674    /// a `new_derivation_epoch` action in its virtual-clients Safe AAD item
675    /// (see [`CommitBuilder::derivation_epoch`]). OpenMLS derives and
676    /// persists the derivation-epoch state itself at group creation, at a
677    /// Welcome join, and when such a commit is merged.
678    ///
679    /// Use [`MlsGroup::newest_vc_derivation_epoch`] to look up the derivation
680    /// epoch that virtual-client operations resolve to. It may be older than
681    /// the group's current epoch.
682    ///
683    /// Groups without this flag never write virtual-clients state.
684    ///
685    /// [`CommitBuilder::derivation_epoch`]: crate::group::CommitBuilder::derivation_epoch
686    /// [`MlsGroup::newest_vc_derivation_epoch`]: crate::group::MlsGroup::newest_vc_derivation_epoch
687    #[cfg(feature = "virtual-clients-draft")]
688    pub fn emulation_group(mut self, emulation_group: bool) -> Self {
689        self.config.emulation_group = emulation_group;
690        self
691    }
692
693    /// Sets the `sender_ratchet_configuration` property of the MlsGroupCreateConfig.
694    /// See [`SenderRatchetConfiguration`] for more information.
695    pub fn sender_ratchet_configuration(
696        mut self,
697        sender_ratchet_configuration: SenderRatchetConfiguration,
698    ) -> Self {
699        self.config.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
700        self
701    }
702
703    /// Sets the `lifetime` property of the MlsGroupCreateConfig.
704    pub fn lifetime(mut self, lifetime: Lifetime) -> Self {
705        self.config.lifetime = lifetime;
706        self
707    }
708
709    /// Sets the `ciphersuite` property of the MlsGroupCreateConfig.
710    pub fn ciphersuite(mut self, ciphersuite: Ciphersuite) -> Self {
711        self.config.ciphersuite = ciphersuite;
712        self
713    }
714
715    /// Sets initial group context extensions.
716    pub fn with_group_context_extensions(mut self, extensions: Extensions<GroupContext>) -> Self {
717        self.config.group_context_extensions = extensions;
718        self
719    }
720
721    /// Sets extensions of the group creator's [`LeafNode`].
722    ///
723    /// Returns an error if the extension types are not valid in a leaf node.
724    pub fn with_leaf_node_extensions(
725        mut self,
726        extensions: Extensions<LeafNode>,
727    ) -> Result<Self, LeafNodeValidationError> {
728        // Make sure that the extension type is supported in this context.
729        // This means that the leaf node needs to have support listed in the
730        // the capabilities (https://validation.openmls.tech/#valn0107).
731        if !self.config.capabilities.contains_extensions(&extensions) {
732            return Err(LeafNodeValidationError::ExtensionsNotInCapabilities);
733        }
734
735        // Note that the extensions have already been checked to be allowed here.
736        self.config.leaf_node_extensions = extensions;
737        Ok(self)
738    }
739
740    /// Finalizes the builder and returns an [`MlsGroupCreateConfig`].
741    pub fn build(self) -> MlsGroupCreateConfig {
742        self.config
743    }
744}
745
746/// Defines what wire format is acceptable for incoming handshake messages.
747/// Note that application messages must always be encrypted.
748#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
749pub enum IncomingWireFormatPolicy {
750    /// Handshake messages must always be PrivateMessage
751    AlwaysCiphertext,
752    /// Handshake messages must always be PublicMessage
753    AlwaysPlaintext,
754    /// Handshake messages can either be PrivateMessage or PublicMessage
755    Mixed,
756}
757
758impl IncomingWireFormatPolicy {
759    pub(crate) fn is_compatible_with(&self, wire_format: WireFormat) -> bool {
760        match self {
761            IncomingWireFormatPolicy::AlwaysCiphertext => wire_format == WireFormat::PrivateMessage,
762            IncomingWireFormatPolicy::AlwaysPlaintext => wire_format == WireFormat::PublicMessage,
763            IncomingWireFormatPolicy::Mixed => {
764                wire_format == WireFormat::PrivateMessage
765                    || wire_format == WireFormat::PublicMessage
766            }
767        }
768    }
769}
770
771/// Defines what wire format should be used for outgoing handshake messages.
772/// Note that application messages must always be encrypted.
773#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
774pub enum OutgoingWireFormatPolicy {
775    /// Handshake messages must always be PrivateMessage
776    AlwaysCiphertext,
777    /// Handshake messages must always be PublicMessage
778    AlwaysPlaintext,
779}
780
781/// Defines what wire format is desired for outgoing handshake messages.
782/// Note that application messages must always be encrypted.
783#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
784pub struct WireFormatPolicy {
785    outgoing: OutgoingWireFormatPolicy,
786    incoming: IncomingWireFormatPolicy,
787}
788
789impl WireFormatPolicy {
790    /// Creates a new wire format policy from an [`OutgoingWireFormatPolicy`]
791    /// and an [`IncomingWireFormatPolicy`].
792    #[cfg(any(feature = "test-utils", test))]
793    pub(crate) fn new(
794        outgoing: OutgoingWireFormatPolicy,
795        incoming: IncomingWireFormatPolicy,
796    ) -> Self {
797        Self { outgoing, incoming }
798    }
799
800    /// Returns a reference to the wire format policy's outgoing wire format policy.
801    pub fn outgoing(&self) -> OutgoingWireFormatPolicy {
802        self.outgoing
803    }
804
805    /// Returns a reference to the wire format policy's incoming wire format policy.
806    pub fn incoming(&self) -> IncomingWireFormatPolicy {
807        self.incoming
808    }
809}
810
811impl Default for WireFormatPolicy {
812    fn default() -> Self {
813        PURE_CIPHERTEXT_WIRE_FORMAT_POLICY
814    }
815}
816
817impl From<OutgoingWireFormatPolicy> for WireFormat {
818    fn from(outgoing: OutgoingWireFormatPolicy) -> Self {
819        match outgoing {
820            OutgoingWireFormatPolicy::AlwaysCiphertext => WireFormat::PrivateMessage,
821            OutgoingWireFormatPolicy::AlwaysPlaintext => WireFormat::PublicMessage,
822        }
823    }
824}
825
826/// All valid wire format policy combinations.
827/// - [`PURE_PLAINTEXT_WIRE_FORMAT_POLICY`]
828/// - [`PURE_CIPHERTEXT_WIRE_FORMAT_POLICY`]
829/// - [`MIXED_PLAINTEXT_WIRE_FORMAT_POLICY`]
830/// - [`MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY`]
831pub const WIRE_FORMAT_POLICIES: [WireFormatPolicy; 4] = [
832    PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
833    PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
834    MIXED_PLAINTEXT_WIRE_FORMAT_POLICY,
835    MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY,
836];
837
838/// Incoming and outgoing wire formats are always plaintext.
839pub const PURE_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
840    outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
841    incoming: IncomingWireFormatPolicy::AlwaysPlaintext,
842};
843
844/// Incoming and outgoing wire formats are always ciphertext.
845pub const PURE_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
846    outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
847    incoming: IncomingWireFormatPolicy::AlwaysCiphertext,
848};
849
850/// Incoming wire formats can be mixed while outgoing wire formats are always
851/// plaintext.
852pub const MIXED_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
853    outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
854    incoming: IncomingWireFormatPolicy::Mixed,
855};
856
857/// Incoming wire formats can be mixed while outgoing wire formats are always
858/// ciphertext.
859pub const MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
860    outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
861    incoming: IncomingWireFormatPolicy::Mixed,
862};
863
864#[cfg(test)]
865mod tests {
866    use super::PastEpochDeletionPolicy;
867
868    #[test]
869    fn past_epoch_deletion_policy_roundtrip() {
870        for policy in [
871            PastEpochDeletionPolicy::MaxEpochs(0),
872            PastEpochDeletionPolicy::MaxEpochs(42),
873            PastEpochDeletionPolicy::KeepAll,
874        ] {
875            let json = serde_json::to_string(&policy).unwrap();
876            let deserialized: PastEpochDeletionPolicy = serde_json::from_str(&json).unwrap();
877            assert_eq!(deserialized, policy);
878        }
879
880        // MaxEpochs(usize::MAX) is indistinguishable from KeepAll after serialization.
881        let json = serde_json::to_string(&PastEpochDeletionPolicy::MaxEpochs(usize::MAX)).unwrap();
882        let deserialized: PastEpochDeletionPolicy = serde_json::from_str(&json).unwrap();
883        assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
884    }
885
886    #[test]
887    fn past_epoch_deletion_policy_deserializes_plain_integer() {
888        let deserialized: PastEpochDeletionPolicy = serde_json::from_str("42").unwrap();
889        assert_eq!(deserialized, PastEpochDeletionPolicy::MaxEpochs(42));
890
891        let deserialized: PastEpochDeletionPolicy =
892            serde_json::from_str(&u64::MAX.to_string()).unwrap();
893        assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
894    }
895
896    #[test]
897    fn past_epoch_deletion_policy_deserializes_legacy_tagged_format() {
898        // Externally tagged enum format used before 8bdba6f.
899        let deserialized: PastEpochDeletionPolicy =
900            serde_json::from_str(r#"{"MaxEpochs":42}"#).unwrap();
901        assert_eq!(deserialized, PastEpochDeletionPolicy::MaxEpochs(42));
902
903        let deserialized: PastEpochDeletionPolicy = serde_json::from_str(r#""KeepAll""#).unwrap();
904        assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
905    }
906}