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/// The [`MlsGroupJoinConfig`] contains all configuration parameters that are
196/// relevant to group operation at runtime. It is used to configure the group's
197/// behaviour when joining an existing group. To configure a newly created
198/// group, use [`MlsGroupCreateConfig`].
199#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
200pub struct MlsGroupJoinConfig {
201 /// Defines the wire format policy for outgoing and incoming handshake messages.
202 /// Application are always encrypted regardless.
203 pub(crate) wire_format_policy: WireFormatPolicy,
204 /// Size of padding in bytes
205 pub(crate) padding_size: usize,
206 /// Maximum number of past epochs for which application messages
207 /// can be decrypted. The default is 0.
208 #[serde(alias = "max_past_epochs")]
209 // alias for backwards compatibility after renaming field
210 pub(crate) past_epoch_deletion_policy: PastEpochDeletionPolicy,
211 /// Number of resumption secrets to keep
212 pub(crate) number_of_resumption_psks: usize,
213 /// Flag to indicate the Ratchet Tree Extension should be used
214 pub(crate) use_ratchet_tree_extension: bool,
215 /// Sender ratchet configuration
216 pub(crate) sender_ratchet_configuration: SenderRatchetConfiguration,
217}
218
219impl MlsGroupJoinConfig {
220 /// Returns a builder for [`MlsGroupJoinConfig`].
221 pub fn builder() -> MlsGroupJoinConfigBuilder {
222 MlsGroupJoinConfigBuilder::new()
223 }
224
225 /// Returns the wire format policy set in this [`MlsGroupJoinConfig`].
226 pub fn wire_format_policy(&self) -> WireFormatPolicy {
227 self.wire_format_policy
228 }
229
230 /// Returns the padding size set in this [`MlsGroupJoinConfig`].
231 pub fn padding_size(&self) -> usize {
232 self.padding_size
233 }
234
235 /// Returns the [`SenderRatchetConfiguration`] set in this [`MlsGroupJoinConfig`].
236 pub fn sender_ratchet_configuration(&self) -> &SenderRatchetConfiguration {
237 &self.sender_ratchet_configuration
238 }
239
240 /// Returns the max past epochs configured in this [`MlsGroupJoinConfig`]
241 pub(crate) fn max_past_epochs(&self) -> Option<usize> {
242 self.past_epoch_deletion_policy.max_epochs()
243 }
244
245 pub(crate) fn past_epoch_deletion_policy(&self) -> &PastEpochDeletionPolicy {
246 &self.past_epoch_deletion_policy
247 }
248}
249
250/// Specifies configuration for the creation of an [`MlsGroup`]. Refer to the
251/// [User Manual](https://book.openmls.tech/user_manual/group_config.html) for
252/// more information about the different configuration values.
253#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
254pub struct MlsGroupCreateConfig {
255 /// Capabilities advertised in the creator's leaf node
256 pub(crate) capabilities: Capabilities,
257 /// Lifetime of the own leaf node
258 pub(crate) lifetime: Lifetime,
259 /// Ciphersuite and protocol version
260 pub(crate) ciphersuite: Ciphersuite,
261 /// Configuration parameters relevant to group operation at runtime
262 pub(crate) join_config: MlsGroupJoinConfig,
263 /// List of initial group context extensions
264 pub(crate) group_context_extensions: Extensions<GroupContext>,
265 /// List of initial leaf node extensions
266 pub(crate) leaf_node_extensions: Extensions<LeafNode>,
267 /// Flag marking the created group as an emulation group of a virtual
268 /// client. Only consulted at group creation, the group keeps the flag
269 /// itself afterwards.
270 #[cfg(feature = "virtual-clients-draft")]
271 pub(crate) emulation_group: bool,
272}
273
274impl Default for MlsGroupCreateConfig {
275 fn default() -> Self {
276 Self {
277 capabilities: Capabilities::default(),
278 lifetime: Lifetime::default(),
279 ciphersuite: Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
280 join_config: MlsGroupJoinConfig::default(),
281 group_context_extensions: Extensions::default(),
282 leaf_node_extensions: Extensions::default(),
283 #[cfg(feature = "virtual-clients-draft")]
284 emulation_group: false,
285 }
286 }
287}
288
289/// Builder struct for an [`MlsGroupJoinConfig`].
290#[derive(Default)]
291pub struct MlsGroupJoinConfigBuilder {
292 join_config: MlsGroupJoinConfig,
293}
294
295impl MlsGroupJoinConfigBuilder {
296 /// Creates a new builder with default values.
297 fn new() -> Self {
298 Self {
299 join_config: MlsGroupJoinConfig::default(),
300 }
301 }
302
303 /// Sets the `wire_format` property of the [`MlsGroupJoinConfig`].
304 pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
305 self.join_config.wire_format_policy = wire_format_policy;
306 self
307 }
308
309 /// Sets the `padding_size` property of the [`MlsGroupJoinConfig`].
310 pub fn padding_size(mut self, padding_size: usize) -> Self {
311 self.join_config.padding_size = padding_size;
312 self
313 }
314
315 /// Sets the `max_past_epochs` property of the [`MlsGroupJoinConfig`].
316 ///
317 /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
318 /// and is equivalent to setting the past epoch deletion policy to
319 /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
320 ///
321 /// **WARNING**
322 ///
323 /// This feature enables the storage of message secrets from past epochs.
324 /// It is a trade-off between functionality and forward secrecy and should only be enabled
325 /// if the Delivery Service cannot guarantee that application messages will be sent in
326 /// the same epoch in which they were generated. The number for `max_epochs` should be
327 /// as low as possible.
328 pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
329 self.join_config.past_epoch_deletion_policy =
330 PastEpochDeletionPolicy::MaxEpochs(max_past_epochs);
331 self
332 }
333
334 /// Set the policy for deleting past epoch secrets.
335 ///
336 /// By default, storage of past epoch secrets is disabled.
337 ///
338 /// This method overrides the configuration set by [`Self::max_past_epochs()`].
339 ///
340 /// **WARNING**
341 ///
342 /// This feature enables the storage of message secrets from past epochs.
343 /// It is a trade-off between functionality and forward secrecy and should only be enabled
344 /// if the Delivery Service cannot guarantee that application messages will be sent in
345 /// the same epoch in which they were generated. The number for `max_epochs` should be
346 /// as low as possible.
347 pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
348 self.join_config.past_epoch_deletion_policy = policy;
349 self
350 }
351
352 /// Sets the `number_of_resumption_psks` property of the [`MlsGroupJoinConfig`].
353 pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
354 self.join_config.number_of_resumption_psks = number_of_resumption_psks;
355 self
356 }
357
358 /// Sets the `use_ratchet_tree_extension` property of the [`MlsGroupJoinConfig`].
359 pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
360 self.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
361 self
362 }
363
364 /// Sets the `sender_ratchet_configuration` property of the [`MlsGroupJoinConfig`].
365 pub fn sender_ratchet_configuration(
366 mut self,
367 sender_ratchet_configuration: SenderRatchetConfiguration,
368 ) -> Self {
369 self.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
370 self
371 }
372
373 /// Finalizes the builder and returns an [`MlsGroupJoinConfig`].
374 pub fn build(self) -> MlsGroupJoinConfig {
375 self.join_config
376 }
377}
378
379impl MlsGroupCreateConfig {
380 /// Returns a builder for [`MlsGroupCreateConfig`]
381 pub fn builder() -> MlsGroupCreateConfigBuilder {
382 MlsGroupCreateConfigBuilder::new()
383 }
384
385 /// Returns the [`MlsGroupCreateConfig`] wire format policy.
386 pub fn wire_format_policy(&self) -> WireFormatPolicy {
387 self.join_config.wire_format_policy
388 }
389
390 /// Returns the [`MlsGroupCreateConfig`] padding size.
391 pub fn padding_size(&self) -> usize {
392 self.join_config.padding_size
393 }
394
395 /// Returns the [`MlsGroupCreateConfig`] max past epochs.
396 pub fn max_past_epochs(&self) -> Option<usize> {
397 self.join_config.max_past_epochs()
398 }
399
400 /// Returns the [`MlsGroupCreateConfig`] number of resumption psks.
401 pub fn number_of_resumption_psks(&self) -> usize {
402 self.join_config.number_of_resumption_psks
403 }
404
405 /// Returns the [`MlsGroupCreateConfig`] boolean flag that indicates whether ratchet_tree_extension should be used.
406 pub fn use_ratchet_tree_extension(&self) -> bool {
407 self.join_config.use_ratchet_tree_extension
408 }
409
410 /// Returns the [`MlsGroupCreateConfig`] sender ratchet configuration.
411 pub fn sender_ratchet_configuration(&self) -> &SenderRatchetConfiguration {
412 &self.join_config.sender_ratchet_configuration
413 }
414
415 /// Returns the [`Extensions`] set as the initial group context.
416 /// This does not contain the initial group context extensions
417 /// added from builder calls to `external_senders` or `required_capabilities`.
418 pub fn group_context_extensions(&self) -> &Extensions<GroupContext> {
419 &self.group_context_extensions
420 }
421
422 /// Returns the [`MlsGroupCreateConfig`] lifetime configuration.
423 pub fn lifetime(&self) -> &Lifetime {
424 &self.lifetime
425 }
426
427 /// Returns the [`Ciphersuite`].
428 pub fn ciphersuite(&self) -> Ciphersuite {
429 self.ciphersuite
430 }
431
432 /// Returns whether groups created with this config are emulation groups of
433 /// a virtual client. See [`MlsGroupCreateConfigBuilder::emulation_group`].
434 #[cfg(feature = "virtual-clients-draft")]
435 pub fn emulation_group(&self) -> bool {
436 self.emulation_group
437 }
438
439 #[cfg(any(feature = "test-utils", test))]
440 pub fn test_default(ciphersuite: Ciphersuite) -> Self {
441 Self::builder()
442 .wire_format_policy(WireFormatPolicy::new(
443 OutgoingWireFormatPolicy::AlwaysPlaintext,
444 IncomingWireFormatPolicy::Mixed,
445 ))
446 .ciphersuite(ciphersuite)
447 .build()
448 }
449
450 /// Returns the [`MlsGroupJoinConfig`] of groups created with this create config.
451 pub fn join_config(&self) -> &MlsGroupJoinConfig {
452 &self.join_config
453 }
454}
455
456/// Builder for an [`MlsGroupCreateConfig`].
457#[derive(Default, Debug)]
458pub struct MlsGroupCreateConfigBuilder {
459 config: MlsGroupCreateConfig,
460}
461
462impl MlsGroupCreateConfigBuilder {
463 /// Creates a new builder with default values.
464 fn new() -> Self {
465 MlsGroupCreateConfigBuilder {
466 config: MlsGroupCreateConfig::default(),
467 }
468 }
469
470 /// Sets the `wire_format` property of the MlsGroupCreateConfig.
471 pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
472 self.config.join_config.wire_format_policy = wire_format_policy;
473 self
474 }
475
476 /// Sets the `padding_size` property of the MlsGroupCreateConfig.
477 pub fn padding_size(mut self, padding_size: usize) -> Self {
478 self.config.join_config.padding_size = padding_size;
479 self
480 }
481
482 /// Sets the `max_past_epochs` property of the MlsGroupCreateConfig.
483 /// This allows application messages from previous epochs to be decrypted.
484 ///
485 /// This method overrides the policy set by [`Self::set_past_epoch_deletion_policy()`],
486 /// and is equivalent to setting the past epoch deletion policy to
487 /// `PastEpochDeletionPolicy::MaxEpochs(max_past_epochs)`.
488 ///
489 /// **WARNING**
490 ///
491 /// This feature enables the storage of message secrets from past epochs.
492 /// It is a trade-off between functionality and forward secrecy and should only be enabled
493 /// if the Delivery Service cannot guarantee that application messages will be sent in
494 /// the same epoch in which they were generated. The number for `max_epochs` should be
495 /// as low as possible.
496 pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
497 self.config.join_config.past_epoch_deletion_policy =
498 PastEpochDeletionPolicy::MaxEpochs(max_past_epochs);
499 self
500 }
501
502 /// Set the policy for deleting past epoch secrets.
503 ///
504 /// By default, storage of past epoch secrets is disabled.
505 ///
506 /// This method overrides the configuration set by [`Self::max_past_epochs()`].
507 ///
508 /// **WARNING**
509 ///
510 /// This feature enables the storage of message secrets from past epochs.
511 /// It is a trade-off between functionality and forward secrecy and should only be enabled
512 /// if the Delivery Service cannot guarantee that application messages will be sent in
513 /// the same epoch in which they were generated. The number for `max_epochs` should be
514 /// as low as possible.
515 pub fn set_past_epoch_deletion_policy(mut self, policy: PastEpochDeletionPolicy) -> Self {
516 self.config.join_config.past_epoch_deletion_policy = policy;
517 self
518 }
519
520 /// Sets the `number_of_resumption_psks` property of the MlsGroupCreateConfig.
521 pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
522 self.config.join_config.number_of_resumption_psks = number_of_resumption_psks;
523 self
524 }
525
526 /// Sets the `use_ratchet_tree_extension` property of the MlsGroupCreateConfig.
527 pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
528 self.config.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
529 self
530 }
531
532 /// Sets the `capabilities` of the group creator's leaf node.
533 pub fn capabilities(mut self, capabilities: Capabilities) -> Self {
534 self.config.capabilities = capabilities;
535 self
536 }
537
538 /// Marks the group as an emulation group of a virtual client.
539 ///
540 /// This is the application's declaration that the group's members are the
541 /// emulator clients of one virtual client. It is local state, nothing about
542 /// it travels on the wire, and every member of an emulation group has to
543 /// set it. Members that join by Welcome set it on the
544 /// [`StagedWelcome`](crate::group::StagedWelcome) instead, and members that
545 /// join by external commit on the
546 /// [`ExternalCommitBuilder`](crate::group::ExternalCommitBuilder).
547 ///
548 /// An emulation group derives the virtual client's secrets from its
549 /// derivation epochs. The initial epoch is a derivation epoch, and so is
550 /// the output epoch of every commit that changes membership or that carries
551 /// a `new_derivation_epoch` action in its virtual-clients Safe AAD item
552 /// (see [`CommitBuilder::derivation_epoch`]). OpenMLS derives and
553 /// persists the derivation-epoch state itself at group creation, at a
554 /// Welcome join, and when such a commit is merged. Applications should wrap
555 /// every merge in a storage transaction. A merge performs several storage
556 /// writes even without virtual clients, and derivation-epoch registration
557 /// adds more.
558 ///
559 /// Use [`MlsGroup::newest_vc_derivation_epoch`] to look up the derivation
560 /// epoch that virtual-client operations resolve to. It may be older than
561 /// the group's current epoch.
562 ///
563 /// Groups without this flag never write virtual-clients state.
564 ///
565 /// [`CommitBuilder::derivation_epoch`]: crate::group::CommitBuilder::derivation_epoch
566 /// [`MlsGroup::newest_vc_derivation_epoch`]: crate::group::MlsGroup::newest_vc_derivation_epoch
567 #[cfg(feature = "virtual-clients-draft")]
568 pub fn emulation_group(mut self, emulation_group: bool) -> Self {
569 self.config.emulation_group = emulation_group;
570 self
571 }
572
573 /// Sets the `sender_ratchet_configuration` property of the MlsGroupCreateConfig.
574 /// See [`SenderRatchetConfiguration`] for more information.
575 pub fn sender_ratchet_configuration(
576 mut self,
577 sender_ratchet_configuration: SenderRatchetConfiguration,
578 ) -> Self {
579 self.config.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
580 self
581 }
582
583 /// Sets the `lifetime` property of the MlsGroupCreateConfig.
584 pub fn lifetime(mut self, lifetime: Lifetime) -> Self {
585 self.config.lifetime = lifetime;
586 self
587 }
588
589 /// Sets the `ciphersuite` property of the MlsGroupCreateConfig.
590 pub fn ciphersuite(mut self, ciphersuite: Ciphersuite) -> Self {
591 self.config.ciphersuite = ciphersuite;
592 self
593 }
594
595 /// Sets initial group context extensions.
596 pub fn with_group_context_extensions(mut self, extensions: Extensions<GroupContext>) -> Self {
597 self.config.group_context_extensions = extensions;
598 self
599 }
600
601 /// Sets extensions of the group creator's [`LeafNode`].
602 ///
603 /// Returns an error if the extension types are not valid in a leaf node.
604 pub fn with_leaf_node_extensions(
605 mut self,
606 extensions: Extensions<LeafNode>,
607 ) -> Result<Self, LeafNodeValidationError> {
608 // Make sure that the extension type is supported in this context.
609 // This means that the leaf node needs to have support listed in the
610 // the capabilities (https://validation.openmls.tech/#valn0107).
611 if !self.config.capabilities.contains_extensions(&extensions) {
612 return Err(LeafNodeValidationError::ExtensionsNotInCapabilities);
613 }
614
615 // Note that the extensions have already been checked to be allowed here.
616 self.config.leaf_node_extensions = extensions;
617 Ok(self)
618 }
619
620 /// Finalizes the builder and returns an [`MlsGroupCreateConfig`].
621 pub fn build(self) -> MlsGroupCreateConfig {
622 self.config
623 }
624}
625
626/// Defines what wire format is acceptable for incoming handshake messages.
627/// Note that application messages must always be encrypted.
628#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
629pub enum IncomingWireFormatPolicy {
630 /// Handshake messages must always be PrivateMessage
631 AlwaysCiphertext,
632 /// Handshake messages must always be PublicMessage
633 AlwaysPlaintext,
634 /// Handshake messages can either be PrivateMessage or PublicMessage
635 Mixed,
636}
637
638impl IncomingWireFormatPolicy {
639 pub(crate) fn is_compatible_with(&self, wire_format: WireFormat) -> bool {
640 match self {
641 IncomingWireFormatPolicy::AlwaysCiphertext => wire_format == WireFormat::PrivateMessage,
642 IncomingWireFormatPolicy::AlwaysPlaintext => wire_format == WireFormat::PublicMessage,
643 IncomingWireFormatPolicy::Mixed => {
644 wire_format == WireFormat::PrivateMessage
645 || wire_format == WireFormat::PublicMessage
646 }
647 }
648 }
649}
650
651/// Defines what wire format should be used for outgoing handshake messages.
652/// Note that application messages must always be encrypted.
653#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
654pub enum OutgoingWireFormatPolicy {
655 /// Handshake messages must always be PrivateMessage
656 AlwaysCiphertext,
657 /// Handshake messages must always be PublicMessage
658 AlwaysPlaintext,
659}
660
661/// Defines what wire format is desired for outgoing handshake messages.
662/// Note that application messages must always be encrypted.
663#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
664pub struct WireFormatPolicy {
665 outgoing: OutgoingWireFormatPolicy,
666 incoming: IncomingWireFormatPolicy,
667}
668
669impl WireFormatPolicy {
670 /// Creates a new wire format policy from an [`OutgoingWireFormatPolicy`]
671 /// and an [`IncomingWireFormatPolicy`].
672 #[cfg(any(feature = "test-utils", test))]
673 pub(crate) fn new(
674 outgoing: OutgoingWireFormatPolicy,
675 incoming: IncomingWireFormatPolicy,
676 ) -> Self {
677 Self { outgoing, incoming }
678 }
679
680 /// Returns a reference to the wire format policy's outgoing wire format policy.
681 pub fn outgoing(&self) -> OutgoingWireFormatPolicy {
682 self.outgoing
683 }
684
685 /// Returns a reference to the wire format policy's incoming wire format policy.
686 pub fn incoming(&self) -> IncomingWireFormatPolicy {
687 self.incoming
688 }
689}
690
691impl Default for WireFormatPolicy {
692 fn default() -> Self {
693 PURE_CIPHERTEXT_WIRE_FORMAT_POLICY
694 }
695}
696
697impl From<OutgoingWireFormatPolicy> for WireFormat {
698 fn from(outgoing: OutgoingWireFormatPolicy) -> Self {
699 match outgoing {
700 OutgoingWireFormatPolicy::AlwaysCiphertext => WireFormat::PrivateMessage,
701 OutgoingWireFormatPolicy::AlwaysPlaintext => WireFormat::PublicMessage,
702 }
703 }
704}
705
706/// All valid wire format policy combinations.
707/// - [`PURE_PLAINTEXT_WIRE_FORMAT_POLICY`]
708/// - [`PURE_CIPHERTEXT_WIRE_FORMAT_POLICY`]
709/// - [`MIXED_PLAINTEXT_WIRE_FORMAT_POLICY`]
710/// - [`MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY`]
711pub const WIRE_FORMAT_POLICIES: [WireFormatPolicy; 4] = [
712 PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
713 PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
714 MIXED_PLAINTEXT_WIRE_FORMAT_POLICY,
715 MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY,
716];
717
718/// Incoming and outgoing wire formats are always plaintext.
719pub const PURE_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
720 outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
721 incoming: IncomingWireFormatPolicy::AlwaysPlaintext,
722};
723
724/// Incoming and outgoing wire formats are always ciphertext.
725pub const PURE_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
726 outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
727 incoming: IncomingWireFormatPolicy::AlwaysCiphertext,
728};
729
730/// Incoming wire formats can be mixed while outgoing wire formats are always
731/// plaintext.
732pub const MIXED_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
733 outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
734 incoming: IncomingWireFormatPolicy::Mixed,
735};
736
737/// Incoming wire formats can be mixed while outgoing wire formats are always
738/// ciphertext.
739pub const MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
740 outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
741 incoming: IncomingWireFormatPolicy::Mixed,
742};
743
744#[cfg(test)]
745mod tests {
746 use super::PastEpochDeletionPolicy;
747
748 #[test]
749 fn past_epoch_deletion_policy_roundtrip() {
750 for policy in [
751 PastEpochDeletionPolicy::MaxEpochs(0),
752 PastEpochDeletionPolicy::MaxEpochs(42),
753 PastEpochDeletionPolicy::KeepAll,
754 ] {
755 let json = serde_json::to_string(&policy).unwrap();
756 let deserialized: PastEpochDeletionPolicy = serde_json::from_str(&json).unwrap();
757 assert_eq!(deserialized, policy);
758 }
759
760 // MaxEpochs(usize::MAX) is indistinguishable from KeepAll after serialization.
761 let json = serde_json::to_string(&PastEpochDeletionPolicy::MaxEpochs(usize::MAX)).unwrap();
762 let deserialized: PastEpochDeletionPolicy = serde_json::from_str(&json).unwrap();
763 assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
764 }
765
766 #[test]
767 fn past_epoch_deletion_policy_deserializes_plain_integer() {
768 let deserialized: PastEpochDeletionPolicy = serde_json::from_str("42").unwrap();
769 assert_eq!(deserialized, PastEpochDeletionPolicy::MaxEpochs(42));
770
771 let deserialized: PastEpochDeletionPolicy =
772 serde_json::from_str(&u64::MAX.to_string()).unwrap();
773 assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
774 }
775
776 #[test]
777 fn past_epoch_deletion_policy_deserializes_legacy_tagged_format() {
778 // Externally tagged enum format used before 8bdba6f.
779 let deserialized: PastEpochDeletionPolicy =
780 serde_json::from_str(r#"{"MaxEpochs":42}"#).unwrap();
781 assert_eq!(deserialized, PastEpochDeletionPolicy::MaxEpochs(42));
782
783 let deserialized: PastEpochDeletionPolicy = serde_json::from_str(r#""KeepAll""#).unwrap();
784 assert_eq!(deserialized, PastEpochDeletionPolicy::KeepAll);
785 }
786}