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}