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
30use super::*;
31use crate::{
32    extensions::errors::InvalidExtensionError,
33    key_packages::Lifetime,
34    tree::sender_ratchet::SenderRatchetConfiguration,
35    treesync::{errors::LeafNodeValidationError, node::leaf_node::Capabilities},
36};
37use serde::{Deserialize, Serialize};
38
39/// The [`MlsGroupJoinConfig`] contains all configuration parameters that are
40/// relevant to group operation at runtime. It is used to configure the group's
41/// behaviour when joining an existing group. To configure a newly created
42/// group, use [`MlsGroupCreateConfig`].
43#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub struct MlsGroupJoinConfig {
45    /// Defines the wire format policy for outgoing and incoming handshake messages.
46    /// Application are always encrypted regardless.
47    pub(crate) wire_format_policy: WireFormatPolicy,
48    /// Size of padding in bytes
49    pub(crate) padding_size: usize,
50    /// Maximum number of past epochs for which application messages
51    /// can be decrypted. The default is 0.
52    pub(crate) max_past_epochs: usize,
53    /// Number of resumption secrets to keep
54    pub(crate) number_of_resumption_psks: usize,
55    /// Flag to indicate the Ratchet Tree Extension should be used
56    pub(crate) use_ratchet_tree_extension: bool,
57    /// Sender ratchet configuration
58    pub(crate) sender_ratchet_configuration: SenderRatchetConfiguration,
59}
60
61impl MlsGroupJoinConfig {
62    /// Returns a builder for [`MlsGroupJoinConfig`].
63    pub fn builder() -> MlsGroupJoinConfigBuilder {
64        MlsGroupJoinConfigBuilder::new()
65    }
66
67    /// Returns the wire format policy set in this  [`MlsGroupJoinConfig`].
68    pub fn wire_format_policy(&self) -> WireFormatPolicy {
69        self.wire_format_policy
70    }
71
72    /// Returns the padding size set in this  [`MlsGroupJoinConfig`].
73    pub fn padding_size(&self) -> usize {
74        self.padding_size
75    }
76
77    /// Returns the [`SenderRatchetConfiguration`] set in this  [`MlsGroupJoinConfig`].
78    pub fn sender_ratchet_configuration(&self) -> &SenderRatchetConfiguration {
79        &self.sender_ratchet_configuration
80    }
81}
82
83/// Specifies configuration for the creation of an [`MlsGroup`]. Refer to the
84/// [User Manual](https://book.openmls.tech/user_manual/group_config.html) for
85/// more information about the different configuration values.
86#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
87pub struct MlsGroupCreateConfig {
88    /// Capabilities advertised in the creator's leaf node
89    pub(crate) capabilities: Capabilities,
90    /// Lifetime of the own leaf node
91    pub(crate) lifetime: Lifetime,
92    /// Ciphersuite and protocol version
93    pub(crate) ciphersuite: Ciphersuite,
94    /// Configuration parameters relevant to group operation at runtime
95    pub(crate) join_config: MlsGroupJoinConfig,
96    /// List of initial group context extensions
97    pub(crate) group_context_extensions: Extensions,
98    /// List of initial leaf node extensions
99    pub(crate) leaf_node_extensions: Extensions,
100}
101
102impl Default for MlsGroupCreateConfig {
103    fn default() -> Self {
104        Self {
105            capabilities: Capabilities::default(),
106            lifetime: Lifetime::default(),
107            ciphersuite: Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519,
108            join_config: MlsGroupJoinConfig::default(),
109            group_context_extensions: Extensions::default(),
110            leaf_node_extensions: Extensions::default(),
111        }
112    }
113}
114
115/// Builder struct for an [`MlsGroupJoinConfig`].
116#[derive(Default)]
117pub struct MlsGroupJoinConfigBuilder {
118    join_config: MlsGroupJoinConfig,
119}
120
121impl MlsGroupJoinConfigBuilder {
122    /// Creates a new builder with default values.
123    fn new() -> Self {
124        Self {
125            join_config: MlsGroupJoinConfig::default(),
126        }
127    }
128
129    /// Sets the `wire_format` property of the [`MlsGroupJoinConfig`].
130    pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
131        self.join_config.wire_format_policy = wire_format_policy;
132        self
133    }
134
135    /// Sets the `padding_size` property of the [`MlsGroupJoinConfig`].
136    pub fn padding_size(mut self, padding_size: usize) -> Self {
137        self.join_config.padding_size = padding_size;
138        self
139    }
140
141    /// Sets the `max_past_epochs` property of the [`MlsGroupJoinConfig`].
142    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
143        self.join_config.max_past_epochs = max_past_epochs;
144        self
145    }
146
147    /// Sets the `number_of_resumption_psks` property of the [`MlsGroupJoinConfig`].
148    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
149        self.join_config.number_of_resumption_psks = number_of_resumption_psks;
150        self
151    }
152
153    /// Sets the `use_ratchet_tree_extension` property of the [`MlsGroupJoinConfig`].
154    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
155        self.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
156        self
157    }
158
159    /// Sets the `sender_ratchet_configuration` property of the [`MlsGroupJoinConfig`].
160    pub fn sender_ratchet_configuration(
161        mut self,
162        sender_ratchet_configuration: SenderRatchetConfiguration,
163    ) -> Self {
164        self.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
165        self
166    }
167
168    /// Finalizes the builder and returns an [`MlsGroupJoinConfig`].
169    pub fn build(self) -> MlsGroupJoinConfig {
170        self.join_config
171    }
172}
173
174impl MlsGroupCreateConfig {
175    /// Returns a builder for [`MlsGroupCreateConfig`]
176    pub fn builder() -> MlsGroupCreateConfigBuilder {
177        MlsGroupCreateConfigBuilder::new()
178    }
179
180    /// Returns the [`MlsGroupCreateConfig`] wire format policy.
181    pub fn wire_format_policy(&self) -> WireFormatPolicy {
182        self.join_config.wire_format_policy
183    }
184
185    /// Returns the [`MlsGroupCreateConfig`] padding size.
186    pub fn padding_size(&self) -> usize {
187        self.join_config.padding_size
188    }
189
190    /// Returns the [`MlsGroupCreateConfig`] max past epochs.
191    pub fn max_past_epochs(&self) -> usize {
192        self.join_config.max_past_epochs
193    }
194
195    /// Returns the [`MlsGroupCreateConfig`] number of resumption psks.
196    pub fn number_of_resumption_psks(&self) -> usize {
197        self.join_config.number_of_resumption_psks
198    }
199
200    /// Returns the [`MlsGroupCreateConfig`] boolean flag that indicates whether ratchet_tree_extension should be used.
201    pub fn use_ratchet_tree_extension(&self) -> bool {
202        self.join_config.use_ratchet_tree_extension
203    }
204
205    /// Returns the [`MlsGroupCreateConfig`] sender ratchet configuration.
206    pub fn sender_ratchet_configuration(&self) -> &SenderRatchetConfiguration {
207        &self.join_config.sender_ratchet_configuration
208    }
209
210    /// Returns the [`Extensions`] set as the initial group context.
211    /// This does not contain the initial group context extensions
212    /// added from builder calls to `external_senders` or `required_capabilities`.
213    pub fn group_context_extensions(&self) -> &Extensions {
214        &self.group_context_extensions
215    }
216
217    /// Returns the [`MlsGroupCreateConfig`] lifetime configuration.
218    pub fn lifetime(&self) -> &Lifetime {
219        &self.lifetime
220    }
221
222    /// Returns the [`Ciphersuite`].
223    pub fn ciphersuite(&self) -> Ciphersuite {
224        self.ciphersuite
225    }
226
227    #[cfg(any(feature = "test-utils", test))]
228    pub fn test_default(ciphersuite: Ciphersuite) -> Self {
229        Self::builder()
230            .wire_format_policy(WireFormatPolicy::new(
231                OutgoingWireFormatPolicy::AlwaysPlaintext,
232                IncomingWireFormatPolicy::Mixed,
233            ))
234            .ciphersuite(ciphersuite)
235            .build()
236    }
237
238    /// Returns the [`MlsGroupJoinConfig`] of groups created with this create config.
239    pub fn join_config(&self) -> &MlsGroupJoinConfig {
240        &self.join_config
241    }
242}
243
244/// Builder for an [`MlsGroupCreateConfig`].
245#[derive(Default, Debug)]
246pub struct MlsGroupCreateConfigBuilder {
247    config: MlsGroupCreateConfig,
248}
249
250impl MlsGroupCreateConfigBuilder {
251    /// Creates a new builder with default values.
252    fn new() -> Self {
253        MlsGroupCreateConfigBuilder {
254            config: MlsGroupCreateConfig::default(),
255        }
256    }
257
258    /// Sets the `wire_format` property of the MlsGroupCreateConfig.
259    pub fn wire_format_policy(mut self, wire_format_policy: WireFormatPolicy) -> Self {
260        self.config.join_config.wire_format_policy = wire_format_policy;
261        self
262    }
263
264    /// Sets the `padding_size` property of the MlsGroupCreateConfig.
265    pub fn padding_size(mut self, padding_size: usize) -> Self {
266        self.config.join_config.padding_size = padding_size;
267        self
268    }
269
270    /// Sets the `max_past_epochs` property of the MlsGroupCreateConfig.
271    /// This allows application messages from previous epochs to be decrypted.
272    ///
273    /// **WARNING**
274    ///
275    /// This feature enables the storage of message secrets from past epochs.
276    /// It is a trade-off between functionality and forward secrecy and should only be enabled
277    /// if the Delivery Service cannot guarantee that application messages will be sent in
278    /// the same epoch in which they were generated. The number for `max_epochs` should be
279    /// as low as possible.
280    pub fn max_past_epochs(mut self, max_past_epochs: usize) -> Self {
281        self.config.join_config.max_past_epochs = max_past_epochs;
282        self
283    }
284
285    /// Sets the `number_of_resumption_psks` property of the MlsGroupCreateConfig.
286    pub fn number_of_resumption_psks(mut self, number_of_resumption_psks: usize) -> Self {
287        self.config.join_config.number_of_resumption_psks = number_of_resumption_psks;
288        self
289    }
290
291    /// Sets the `use_ratchet_tree_extension` property of the MlsGroupCreateConfig.
292    pub fn use_ratchet_tree_extension(mut self, use_ratchet_tree_extension: bool) -> Self {
293        self.config.join_config.use_ratchet_tree_extension = use_ratchet_tree_extension;
294        self
295    }
296
297    /// Sets the `capabilities` of the group creator's leaf node.
298    pub fn capabilities(mut self, capabilities: Capabilities) -> Self {
299        self.config.capabilities = capabilities;
300        self
301    }
302
303    /// Sets the `sender_ratchet_configuration` property of the MlsGroupCreateConfig.
304    /// See [`SenderRatchetConfiguration`] for more information.
305    pub fn sender_ratchet_configuration(
306        mut self,
307        sender_ratchet_configuration: SenderRatchetConfiguration,
308    ) -> Self {
309        self.config.join_config.sender_ratchet_configuration = sender_ratchet_configuration;
310        self
311    }
312
313    /// Sets the `lifetime` property of the MlsGroupCreateConfig.
314    pub fn lifetime(mut self, lifetime: Lifetime) -> Self {
315        self.config.lifetime = lifetime;
316        self
317    }
318
319    /// Sets the `ciphersuite` property of the MlsGroupCreateConfig.
320    pub fn ciphersuite(mut self, ciphersuite: Ciphersuite) -> Self {
321        self.config.ciphersuite = ciphersuite;
322        self
323    }
324
325    /// Sets initial group context extensions.
326    pub fn with_group_context_extensions(
327        mut self,
328        extensions: Extensions,
329    ) -> Result<Self, InvalidExtensionError> {
330        let is_valid_in_group_context = extensions.application_id().is_none()
331            && extensions.ratchet_tree().is_none()
332            && extensions.external_pub().is_none();
333        if !is_valid_in_group_context {
334            return Err(InvalidExtensionError::IllegalInGroupContext);
335        }
336        self.config.group_context_extensions = extensions;
337        Ok(self)
338    }
339
340    /// Sets extensions of the group creator's [`LeafNode`].
341    pub fn with_leaf_node_extensions(
342        mut self,
343        extensions: Extensions,
344    ) -> Result<Self, LeafNodeValidationError> {
345        // None of the default extensions are leaf node extensions, so only
346        // unknown extensions can be leaf node extensions.
347        let is_valid_in_leaf_node = extensions
348            .iter()
349            .all(|e| matches!(e.extension_type(), ExtensionType::Unknown(_)));
350        if !is_valid_in_leaf_node {
351            log::error!("Leaf node extensions must be unknown extensions.");
352            return Err(LeafNodeValidationError::UnsupportedExtensions);
353        }
354
355        // Make sure that the extension type is supported in this context.
356        // This means that the leaf node needs to have support listed in the
357        // the capabilities (https://validation.openmls.tech/#valn0107).
358        if !self.config.capabilities.contains_extensions(&extensions) {
359            return Err(LeafNodeValidationError::ExtensionsNotInCapabilities);
360        }
361
362        self.config.leaf_node_extensions = extensions;
363        Ok(self)
364    }
365
366    /// Finalizes the builder and retursn an `[MlsGroupCreateConfig`].
367    pub fn build(self) -> MlsGroupCreateConfig {
368        self.config
369    }
370}
371
372/// Defines what wire format is acceptable for incoming handshake messages.
373/// Note that application messages must always be encrypted.
374#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
375pub enum IncomingWireFormatPolicy {
376    /// Handshake messages must always be PrivateMessage
377    AlwaysCiphertext,
378    /// Handshake messages must always be PublicMessage
379    AlwaysPlaintext,
380    /// Handshake messages can either be PrivateMessage or PublicMessage
381    Mixed,
382}
383
384impl IncomingWireFormatPolicy {
385    pub(crate) fn is_compatible_with(&self, wire_format: WireFormat) -> bool {
386        match self {
387            IncomingWireFormatPolicy::AlwaysCiphertext => wire_format == WireFormat::PrivateMessage,
388            IncomingWireFormatPolicy::AlwaysPlaintext => wire_format == WireFormat::PublicMessage,
389            IncomingWireFormatPolicy::Mixed => {
390                wire_format == WireFormat::PrivateMessage
391                    || wire_format == WireFormat::PublicMessage
392            }
393        }
394    }
395}
396
397/// Defines what wire format should be used for outgoing handshake messages.
398/// Note that application messages must always be encrypted.
399#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
400pub enum OutgoingWireFormatPolicy {
401    /// Handshake messages must always be PrivateMessage
402    AlwaysCiphertext,
403    /// Handshake messages must always be PublicMessage
404    AlwaysPlaintext,
405}
406
407/// Defines what wire format is desired for outgoing handshake messages.
408/// Note that application messages must always be encrypted.
409#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
410pub struct WireFormatPolicy {
411    outgoing: OutgoingWireFormatPolicy,
412    incoming: IncomingWireFormatPolicy,
413}
414
415impl WireFormatPolicy {
416    /// Creates a new wire format policy from an [`OutgoingWireFormatPolicy`]
417    /// and an [`IncomingWireFormatPolicy`].
418    #[cfg(any(feature = "test-utils", test))]
419    pub(crate) fn new(
420        outgoing: OutgoingWireFormatPolicy,
421        incoming: IncomingWireFormatPolicy,
422    ) -> Self {
423        Self { outgoing, incoming }
424    }
425
426    /// Returns a reference to the wire format policy's outgoing wire format policy.
427    pub fn outgoing(&self) -> OutgoingWireFormatPolicy {
428        self.outgoing
429    }
430
431    /// Returns a reference to the wire format policy's incoming wire format policy.
432    pub fn incoming(&self) -> IncomingWireFormatPolicy {
433        self.incoming
434    }
435}
436
437impl Default for WireFormatPolicy {
438    fn default() -> Self {
439        PURE_CIPHERTEXT_WIRE_FORMAT_POLICY
440    }
441}
442
443impl From<OutgoingWireFormatPolicy> for WireFormat {
444    fn from(outgoing: OutgoingWireFormatPolicy) -> Self {
445        match outgoing {
446            OutgoingWireFormatPolicy::AlwaysCiphertext => WireFormat::PrivateMessage,
447            OutgoingWireFormatPolicy::AlwaysPlaintext => WireFormat::PublicMessage,
448        }
449    }
450}
451
452/// All valid wire format policy combinations.
453/// - [`PURE_PLAINTEXT_WIRE_FORMAT_POLICY`]
454/// - [`PURE_CIPHERTEXT_WIRE_FORMAT_POLICY`]
455/// - [`MIXED_PLAINTEXT_WIRE_FORMAT_POLICY`]
456/// - [`MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY`]
457pub const WIRE_FORMAT_POLICIES: [WireFormatPolicy; 4] = [
458    PURE_PLAINTEXT_WIRE_FORMAT_POLICY,
459    PURE_CIPHERTEXT_WIRE_FORMAT_POLICY,
460    MIXED_PLAINTEXT_WIRE_FORMAT_POLICY,
461    MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY,
462];
463
464/// Incoming and outgoing wire formats are always plaintext.
465pub const PURE_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
466    outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
467    incoming: IncomingWireFormatPolicy::AlwaysPlaintext,
468};
469
470/// Incoming and outgoing wire formats are always ciphertext.
471pub const PURE_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
472    outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
473    incoming: IncomingWireFormatPolicy::AlwaysCiphertext,
474};
475
476/// Incoming wire formats can be mixed while outgoing wire formats are always
477/// plaintext.
478pub const MIXED_PLAINTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
479    outgoing: OutgoingWireFormatPolicy::AlwaysPlaintext,
480    incoming: IncomingWireFormatPolicy::Mixed,
481};
482
483/// Incoming wire formats can be mixed while outgoing wire formats are always
484/// ciphertext.
485pub const MIXED_CIPHERTEXT_WIRE_FORMAT_POLICY: WireFormatPolicy = WireFormatPolicy {
486    outgoing: OutgoingWireFormatPolicy::AlwaysCiphertext,
487    incoming: IncomingWireFormatPolicy::Mixed,
488};