openmls/group/mls_group/
creation.rs

1use errors::NewGroupError;
2use openmls_traits::storage::StorageProvider as StorageProviderTrait;
3
4use super::{builder::MlsGroupBuilder, *};
5use crate::{
6    credentials::CredentialWithKey,
7    group::{
8        commit_builder::external_commits::ExternalCommitBuilder,
9        errors::{ExternalCommitError, WelcomeError},
10    },
11    messages::{
12        group_info::{GroupInfo, VerifiableGroupInfo},
13        Welcome,
14    },
15    schedule::psk::{store::ResumptionPskStore, PreSharedKeyId},
16    storage::OpenMlsProvider,
17    treesync::{
18        errors::{DerivePathError, PublicTreeError},
19        node::leaf_node::{Capabilities, LeafNodeParameters},
20        RatchetTreeIn,
21    },
22};
23
24#[cfg(doc)]
25use crate::key_packages::KeyPackage;
26
27impl MlsGroup {
28    // === Group creation ===
29
30    /// Creates a builder which can be used to configure and build
31    /// a new [`MlsGroup`].
32    pub fn builder() -> MlsGroupBuilder {
33        MlsGroupBuilder::new()
34    }
35
36    /// Creates a new group with the creator as the only member (and a random
37    /// group ID).
38    ///
39    /// This function removes the private key corresponding to the
40    /// `key_package` from the key store.
41    pub fn new<Provider: OpenMlsProvider>(
42        provider: &Provider,
43        signer: &impl Signer,
44        mls_group_create_config: &MlsGroupCreateConfig,
45        credential_with_key: CredentialWithKey,
46    ) -> Result<Self, NewGroupError<Provider::StorageError>> {
47        MlsGroupBuilder::new().build_internal(
48            provider,
49            signer,
50            credential_with_key,
51            Some(mls_group_create_config.clone()),
52        )
53    }
54
55    /// Creates a new group with a given group ID with the creator as the only
56    /// member.
57    pub fn new_with_group_id<Provider: OpenMlsProvider>(
58        provider: &Provider,
59        signer: &impl Signer,
60        mls_group_create_config: &MlsGroupCreateConfig,
61        group_id: GroupId,
62        credential_with_key: CredentialWithKey,
63    ) -> Result<Self, NewGroupError<Provider::StorageError>> {
64        MlsGroupBuilder::new()
65            .with_group_id(group_id)
66            .build_internal(
67                provider,
68                signer,
69                credential_with_key,
70                Some(mls_group_create_config.clone()),
71            )
72    }
73
74    /// Join an existing group through an External Commit.
75    /// The resulting [`MlsGroup`] instance starts off with a pending
76    /// commit (the external commit, which adds this client to the group).
77    /// Merging this commit is necessary for this [`MlsGroup`] instance to
78    /// function properly, as, for example, this client is not yet part of the
79    /// tree. As a result, it is not possible to clear the pending commit. If
80    /// the external commit was rejected due to an epoch change, the
81    /// [`MlsGroup`] instance has to be discarded and a new one has to be
82    /// created using this function based on the latest `ratchet_tree` and
83    /// group info. For more information on the external init process,
84    /// please see Section 11.2.1 in the MLS specification.
85    ///
86    /// Note: If there is a group member in the group with the same identity as
87    /// us, this will create a remove proposal.
88    #[allow(clippy::too_many_arguments)]
89    #[deprecated(
90        since = "0.7.1",
91        note = "Use the `MlsGroup::external_commit_builder` instead."
92    )]
93    pub fn join_by_external_commit<Provider: OpenMlsProvider>(
94        provider: &Provider,
95        signer: &impl Signer,
96        ratchet_tree: Option<RatchetTreeIn>,
97        verifiable_group_info: VerifiableGroupInfo,
98        mls_group_config: &MlsGroupJoinConfig,
99        capabilities: Option<Capabilities>,
100        extensions: Option<Extensions>,
101        aad: &[u8],
102        credential_with_key: CredentialWithKey,
103    ) -> Result<(Self, MlsMessageOut, Option<GroupInfo>), ExternalCommitError<Provider::StorageError>>
104    {
105        let leaf_node_parameters = LeafNodeParameters::builder()
106            .with_capabilities(capabilities.unwrap_or_default())
107            .with_extensions(extensions.unwrap_or_default())
108            .build();
109
110        let mut external_commit_builder = ExternalCommitBuilder::new()
111            .with_aad(aad.to_vec())
112            .with_config(mls_group_config.clone());
113
114        if let Some(ratchet_tree) = ratchet_tree {
115            external_commit_builder = external_commit_builder.with_ratchet_tree(ratchet_tree)
116        }
117
118        let (mls_group, commit_message_bundle) = external_commit_builder
119            .build_group(provider, verifiable_group_info, credential_with_key)?
120            .leaf_node_parameters(leaf_node_parameters)
121            .load_psks(provider.storage())
122            .map_err(|e| {
123                log::error!("Error loading PSKs for external commit: {e:?}");
124                LibraryError::custom("Error loading PSKs for external commit")
125            })?
126            .build(provider.rand(), provider.crypto(), signer, |_| true)?
127            .finalize(provider)?;
128
129        let (commit, _, group_info) = commit_message_bundle.into_contents();
130
131        Ok((mls_group, commit, group_info))
132    }
133}
134
135impl ProcessedWelcome {
136    /// Creates a new processed [`Welcome`] message , which can be
137    /// inspected before creating a [`StagedWelcome`].
138    ///
139    /// This does not require a ratchet tree yet.
140    ///
141    /// [`Welcome`]: crate::messages::Welcome
142    pub fn new_from_welcome<Provider: OpenMlsProvider>(
143        provider: &Provider,
144        mls_group_config: &MlsGroupJoinConfig,
145        welcome: Welcome,
146    ) -> Result<Self, WelcomeError<Provider::StorageError>> {
147        let (resumption_psk_store, key_package_bundle) =
148            keys_for_welcome(mls_group_config, &welcome, provider)?;
149
150        let ciphersuite = welcome.ciphersuite();
151        let Some(egs) = welcome.find_encrypted_group_secret(
152            key_package_bundle
153                .key_package()
154                .hash_ref(provider.crypto())?,
155        ) else {
156            return Err(WelcomeError::JoinerSecretNotFound);
157        };
158
159        // This check seems to be superfluous from the perspective of the RFC, but still doesn't
160        // seem like a bad idea.
161        if welcome.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
162            let e = WelcomeError::CiphersuiteMismatch;
163            log::debug!("new_from_welcome {e:?}");
164            return Err(e);
165        }
166
167        let group_secrets = GroupSecrets::try_from_ciphertext(
168            key_package_bundle.init_private_key(),
169            egs.encrypted_group_secrets(),
170            welcome.encrypted_group_info(),
171            ciphersuite,
172            provider.crypto(),
173        )?;
174        let psk_secret = {
175            let psks = load_psks(
176                provider.storage(),
177                &resumption_psk_store,
178                &group_secrets.psks,
179            )?;
180
181            PskSecret::new(provider.crypto(), ciphersuite, psks)?
182        };
183        let key_schedule = KeySchedule::init(
184            ciphersuite,
185            provider.crypto(),
186            &group_secrets.joiner_secret,
187            psk_secret,
188        )?;
189        let (welcome_key, welcome_nonce) = key_schedule
190            .welcome(provider.crypto(), ciphersuite)
191            .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?
192            .derive_welcome_key_nonce(provider.crypto(), ciphersuite)
193            .map_err(LibraryError::unexpected_crypto_error)?;
194        let verifiable_group_info = VerifiableGroupInfo::try_from_ciphertext(
195            &welcome_key,
196            &welcome_nonce,
197            welcome.encrypted_group_info(),
198            &[],
199            provider.crypto(),
200        )?;
201        if let Some(required_capabilities) =
202            verifiable_group_info.extensions().required_capabilities()
203        {
204            // Also check that our key package actually supports the extensions.
205            // As per the spec, the sender must have checked this. But you never know.
206            key_package_bundle
207                .key_package()
208                .leaf_node()
209                .capabilities()
210                .supports_required_capabilities(required_capabilities)?;
211        }
212
213        // https://validation.openmls.tech/#valn1404
214        // Verify that the cipher_suite in the GroupInfo matches the cipher_suite in the
215        // KeyPackage.
216        if verifiable_group_info.ciphersuite() != key_package_bundle.key_package().ciphersuite() {
217            let e = WelcomeError::CiphersuiteMismatch;
218            log::debug!("new_from_welcome {e:?}");
219            return Err(e);
220        }
221
222        Ok(Self {
223            mls_group_config: mls_group_config.clone(),
224            ciphersuite,
225            group_secrets,
226            key_schedule,
227            verifiable_group_info,
228            resumption_psk_store,
229            key_package_bundle,
230        })
231    }
232
233    /// Get a reference to the GroupInfo in this Welcome message.
234    ///
235    /// **NOTE:** The group info contains **unverified** values. Use with caution.
236    pub fn unverified_group_info(&self) -> &VerifiableGroupInfo {
237        &self.verifiable_group_info
238    }
239
240    /// Get a reference to the PSKs in this Welcome message.
241    ///
242    /// **NOTE:** The group info contains **unverified** values. Use with caution.
243    pub fn psks(&self) -> &[PreSharedKeyId] {
244        &self.group_secrets.psks
245    }
246
247    /// Consume the `ProcessedWelcome` and combine it with the ratchet tree into
248    /// a `StagedWelcome`.
249    pub fn into_staged_welcome<Provider: OpenMlsProvider>(
250        self,
251        provider: &Provider,
252        ratchet_tree: Option<RatchetTreeIn>,
253    ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
254        self.into_staged_welcome_inner(provider, ratchet_tree, LeafNodeLifetimePolicy::Verify)
255    }
256
257    /// Consume the `ProcessedWelcome` and combine it with the ratchet tree into
258    /// a `StagedWelcome`.
259    pub(crate) fn into_staged_welcome_inner<Provider: OpenMlsProvider>(
260        mut self,
261        provider: &Provider,
262        ratchet_tree: Option<RatchetTreeIn>,
263        validate_lifetimes: LeafNodeLifetimePolicy,
264    ) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
265        // Build the ratchet tree and group
266
267        // Set nodes either from the extension or from the `nodes_option`.
268        // If we got a ratchet tree extension in the welcome, we enable it for
269        // this group. Note that this is not strictly necessary. But there's
270        // currently no other mechanism to enable the extension.
271        let ratchet_tree = match self.verifiable_group_info.extensions().ratchet_tree() {
272            Some(extension) => extension.ratchet_tree().clone(),
273            None => match ratchet_tree {
274                Some(ratchet_tree) => ratchet_tree,
275                None => return Err(WelcomeError::MissingRatchetTree),
276            },
277        };
278
279        // Since there is currently only the external pub extension, there is no
280        // group info extension of interest here.
281        let (public_group, _group_info_extensions) = PublicGroup::from_ratchet_tree(
282            provider.crypto(),
283            ratchet_tree,
284            self.verifiable_group_info.clone(),
285            ProposalStore::new(),
286            validate_lifetimes,
287        )?;
288
289        // Find our own leaf in the tree.
290        let own_leaf_index = public_group
291            .members()
292            .find_map(|m| {
293                if m.signature_key
294                    == self
295                        .key_package_bundle
296                        .key_package()
297                        .leaf_node()
298                        .signature_key()
299                        .as_slice()
300                {
301                    Some(m.index)
302                } else {
303                    None
304                }
305            })
306            .ok_or(WelcomeError::PublicTreeError(
307                PublicTreeError::MalformedTree,
308            ))?;
309
310        let (group_epoch_secrets, message_secrets) = {
311            let serialized_group_context = public_group
312                .group_context()
313                .tls_serialize_detached()
314                .map_err(LibraryError::missing_bound_check)?;
315
316            // TODO #751: Implement PSK
317            self.key_schedule
318                .add_context(provider.crypto(), &serialized_group_context)
319                .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
320
321            let epoch_secrets = self
322                .key_schedule
323                .epoch_secrets(provider.crypto(), self.ciphersuite)
324                .map_err(|_| LibraryError::custom("Using the key schedule in the wrong state"))?;
325
326            epoch_secrets.split_secrets(
327                serialized_group_context,
328                public_group.tree_size(),
329                own_leaf_index,
330            )
331        };
332
333        let confirmation_tag = message_secrets
334            .confirmation_key()
335            .tag(
336                provider.crypto(),
337                self.ciphersuite,
338                public_group.group_context().confirmed_transcript_hash(),
339            )
340            .map_err(LibraryError::unexpected_crypto_error)?;
341
342        // Verify confirmation tag
343        // https://validation.openmls.tech/#valn1410
344        if &confirmation_tag != public_group.confirmation_tag() {
345            log::error!("Confirmation tag mismatch");
346            log_crypto!(trace, "  Got:      {:x?}", confirmation_tag);
347            log_crypto!(trace, "  Expected: {:x?}", public_group.confirmation_tag());
348            debug_assert!(false, "Confirmation tag mismatch");
349
350            // in some tests we need to be able to proceed despite the tag being wrong,
351            // e.g. to test whether a later validation check is performed correctly.
352            if !crate::skip_validation::is_disabled::confirmation_tag() {
353                return Err(WelcomeError::ConfirmationTagMismatch);
354            }
355        }
356
357        let message_secrets_store = MessageSecretsStore::new_with_secret(0, message_secrets);
358
359        // Extract and store the resumption PSK for the current epoch.
360        let resumption_psk = group_epoch_secrets.resumption_psk();
361        self.resumption_psk_store
362            .add(public_group.group_context().epoch(), resumption_psk.clone());
363
364        let welcome_sender_index = self.verifiable_group_info.signer();
365        let path_keypairs = if let Some(path_secret) = self.group_secrets.path_secret {
366            let (path_keypairs, _commit_secret) = public_group
367                .derive_path_secrets(
368                    provider.crypto(),
369                    self.ciphersuite,
370                    path_secret,
371                    welcome_sender_index,
372                    own_leaf_index,
373                )
374                .map_err(|e| match e {
375                    DerivePathError::LibraryError(e) => e.into(),
376                    DerivePathError::PublicKeyMismatch => {
377                        WelcomeError::PublicTreeError(PublicTreeError::PublicKeyMismatch)
378                    }
379                })?;
380            Some(path_keypairs)
381        } else {
382            None
383        };
384
385        let staged_welcome = StagedWelcome {
386            mls_group_config: self.mls_group_config,
387            public_group,
388            group_epoch_secrets,
389            own_leaf_index,
390            message_secrets_store,
391            resumption_psk_store: self.resumption_psk_store,
392            verifiable_group_info: self.verifiable_group_info,
393            key_package_bundle: self.key_package_bundle,
394            path_keypairs,
395        };
396
397        Ok(staged_welcome)
398    }
399}
400
401impl StagedWelcome {
402    /// Creates a new staged welcome from a [`Welcome`] message. Returns an error
403    /// ([`WelcomeError::NoMatchingKeyPackage`]) if no [`KeyPackage`]
404    /// can be found.
405    /// Note: calling this function will consume the key material for decrypting the [`Welcome`]
406    /// message, even if the caller does not turn the [`StagedWelcome`] into an [`MlsGroup`].
407    ///
408    /// [`Welcome`]: crate::messages::Welcome
409    pub fn new_from_welcome<Provider: OpenMlsProvider>(
410        provider: &Provider,
411        mls_group_config: &MlsGroupJoinConfig,
412        welcome: Welcome,
413        ratchet_tree: Option<RatchetTreeIn>,
414    ) -> Result<Self, WelcomeError<Provider::StorageError>> {
415        let processed_welcome =
416            ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
417
418        processed_welcome.into_staged_welcome(provider, ratchet_tree)
419    }
420
421    /// Similar to [`StagedWelcome::new_from_welcome`] but as a builder.
422    ///
423    /// The builder allows to set the ratchet tree, skip leaf node lifetime
424    /// validation, and get the [`ProcessedWelcome`] for inspection before staging.
425    pub fn build_from_welcome<'a, Provider: OpenMlsProvider>(
426        provider: &'a Provider,
427        mls_group_config: &MlsGroupJoinConfig,
428        welcome: Welcome,
429        // ratchet_tree: Option<RatchetTreeIn>,
430    ) -> Result<JoinBuilder<'a, Provider>, WelcomeError<Provider::StorageError>> {
431        let processed_welcome =
432            ProcessedWelcome::new_from_welcome(provider, mls_group_config, welcome)?;
433
434        // processed_welcome.into_staged_welcome(provider, ratchet_tree)
435        Ok(JoinBuilder::new(provider, processed_welcome))
436    }
437
438    /// Returns the [`LeafNodeIndex`] of the group member that authored the [`Welcome`] message.
439    ///
440    /// [`Welcome`]: crate::messages::Welcome
441    pub fn welcome_sender_index(&self) -> LeafNodeIndex {
442        self.verifiable_group_info.signer()
443    }
444
445    /// Returns the [`LeafNode`] of the group member that authored the [`Welcome`] message.
446    ///
447    /// [`Welcome`]: crate::messages::Welcome
448    pub fn welcome_sender(&self) -> Result<&LeafNode, LibraryError> {
449        let sender_index = self.welcome_sender_index();
450        self.public_group
451            .leaf(sender_index)
452            .ok_or(LibraryError::custom(
453                "no leaf with given welcome sender index exists",
454            ))
455    }
456
457    /// Get the [`GroupContext`] of this welcome's [`PublicGroup`].
458    pub fn group_context(&self) -> &GroupContext {
459        self.public_group.group_context()
460    }
461
462    /// Get an iterator over all [`Member`]s of this welcome's [`PublicGroup`].
463    pub fn members(&self) -> impl Iterator<Item = Member> + '_ {
464        self.public_group.members()
465    }
466
467    /// Consumes the [`StagedWelcome`] and returns the respective [`MlsGroup`].
468    pub fn into_group<Provider: OpenMlsProvider>(
469        self,
470        provider: &Provider,
471    ) -> Result<MlsGroup, WelcomeError<Provider::StorageError>> {
472        // If we got a path secret, derive the path (which also checks if the
473        // public keys match) and store the derived keys in the key store.
474        let group_keypairs = if let Some(path_keypairs) = self.path_keypairs {
475            let mut keypairs = vec![self.key_package_bundle.encryption_key_pair()];
476            keypairs.extend_from_slice(&path_keypairs);
477            keypairs
478        } else {
479            vec![self.key_package_bundle.encryption_key_pair()]
480        };
481
482        let mut mls_group = MlsGroup {
483            mls_group_config: self.mls_group_config,
484            own_leaf_nodes: vec![],
485            aad: vec![],
486            group_state: MlsGroupState::Operational,
487            public_group: self.public_group,
488            group_epoch_secrets: self.group_epoch_secrets,
489            own_leaf_index: self.own_leaf_index,
490            message_secrets_store: self.message_secrets_store,
491            resumption_psk_store: self.resumption_psk_store,
492        };
493
494        mls_group
495            .store_epoch_keypairs(provider.storage(), group_keypairs.as_slice())
496            .map_err(WelcomeError::StorageError)?;
497        mls_group.set_max_past_epochs(mls_group.mls_group_config.max_past_epochs);
498
499        mls_group
500            .store(provider.storage())
501            .map_err(WelcomeError::StorageError)?;
502
503        Ok(mls_group)
504    }
505}
506
507fn keys_for_welcome<Provider: OpenMlsProvider>(
508    mls_group_config: &MlsGroupJoinConfig,
509    welcome: &Welcome,
510    provider: &Provider,
511) -> Result<
512    (ResumptionPskStore, KeyPackageBundle),
513    WelcomeError<<Provider as OpenMlsProvider>::StorageError>,
514> {
515    let resumption_psk_store = ResumptionPskStore::new(mls_group_config.number_of_resumption_psks);
516    let key_package_bundle: KeyPackageBundle = welcome
517        .secrets()
518        .iter()
519        .find_map(|egs| {
520            let hash_ref = egs.new_member();
521
522            provider
523                .storage()
524                .key_package(&hash_ref)
525                .map_err(WelcomeError::StorageError)
526                .transpose()
527        })
528        .ok_or(WelcomeError::NoMatchingKeyPackage)??;
529    if !key_package_bundle.key_package().last_resort() {
530        provider
531            .storage()
532            .delete_key_package(&key_package_bundle.key_package.hash_ref(provider.crypto())?)
533            .map_err(WelcomeError::StorageError)?;
534    } else {
535        log::debug!("Key package has last resort extension, not deleting");
536    }
537    Ok((resumption_psk_store, key_package_bundle))
538}
539
540/// Verify or skip the validation of leaf node lifetimes in the ratchet tree
541/// when joining a group.
542#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
543pub enum LeafNodeLifetimePolicy {
544    /// Verify the lifetime of leaf nodes in the ratchet tree.
545    ///
546    /// **NOTE:** Only leaf nodes that have never been updated have a lifetime.
547    #[default]
548    Verify,
549
550    /// Skip the verification of the lifeimte in leaf nodes in the ratchet tree.
551    Skip,
552}
553
554/// Builder for joining a group.
555///
556/// Create this with [`StagedWelcome::build_from_welcome`].
557pub struct JoinBuilder<'a, Provider: OpenMlsProvider> {
558    provider: &'a Provider,
559    processed_welcome: ProcessedWelcome,
560    ratchet_tree: Option<RatchetTreeIn>,
561    validate_lifetimes: LeafNodeLifetimePolicy,
562}
563
564impl<'a, Provider: OpenMlsProvider> JoinBuilder<'a, Provider> {
565    /// Create a new builder for the [`JoinBuilder`].
566    pub(crate) fn new(provider: &'a Provider, processed_welcome: ProcessedWelcome) -> Self {
567        Self {
568            provider,
569            processed_welcome,
570            ratchet_tree: None,
571            validate_lifetimes: LeafNodeLifetimePolicy::Verify,
572        }
573    }
574
575    /// The ratchet tree to use for the new group.
576    pub fn with_ratchet_tree(mut self, ratchet_tree: RatchetTreeIn) -> Self {
577        self.ratchet_tree = Some(ratchet_tree);
578        self
579    }
580
581    /// Skip the validation of lifetimes in leaf nodes in the ratchet tree.
582    /// Note that only the leaf nodes are checked that were never updated.
583    ///
584    /// By default they are validated.
585    pub fn skip_lifetime_validation(mut self) -> Self {
586        self.validate_lifetimes = LeafNodeLifetimePolicy::Skip;
587        self
588    }
589
590    /// Get a reference to the [`ProcessedWelcome`].
591    ///
592    /// Use this to inspect the [`Welcome`] message before validation.
593    pub fn processed_welcome(&self) -> &ProcessedWelcome {
594        &self.processed_welcome
595    }
596
597    /// Build the [`StagedWelcome`].
598    pub fn build(self) -> Result<StagedWelcome, WelcomeError<Provider::StorageError>> {
599        self.processed_welcome.into_staged_welcome_inner(
600            self.provider,
601            self.ratchet_tree,
602            self.validate_lifetimes,
603        )
604    }
605}