Skip to main content

openmls/group/mls_group/
updates.rs

1use commit_builder::CommitMessageBundle;
2use errors::{ProposeSelfUpdateError, SelfUpdateError};
3use openmls_traits::{signatures::Signer, storage::StorageProvider as _};
4
5use crate::{credentials::NewSignerBundle, storage::OpenMlsProvider, treesync::LeafNodeParameters};
6
7use super::*;
8
9impl MlsGroup {
10    /// Updates the own leaf node. The application can choose to update the
11    /// credential, the capabilities, and the extensions by buliding the
12    /// [`LeafNodeParameters`].
13    ///
14    /// If successful, it returns a tuple of [`MlsMessageOut`] (containing the
15    /// commit), an optional [`MlsMessageOut`] (containing the [`Welcome`]) and
16    /// the [GroupInfo]. The [`Welcome`] is [Some] when the queue of pending
17    /// proposals contained add proposals The [GroupInfo] is [Some] if the group
18    /// has the `use_ratchet_tree_extension` flag set.
19    ///
20    /// Returns an error if there is a pending commit.
21    ///
22    /// [`Welcome`]: crate::messages::Welcome
23    pub fn self_update<Provider: OpenMlsProvider>(
24        &mut self,
25        provider: &Provider,
26        signer: &impl Signer,
27        leaf_node_parameters: LeafNodeParameters,
28    ) -> Result<CommitMessageBundle, SelfUpdateError<Provider::StorageError>> {
29        self.is_operational()?;
30
31        let bundle = self
32            .commit_builder()
33            .leaf_node_parameters(leaf_node_parameters)
34            .consume_proposal_store(true)
35            .load_psks(provider.storage())?
36            .build(provider.rand(), provider.crypto(), signer, |_| true)?
37            .stage_commit(provider)?;
38
39        self.reset_aad();
40
41        Ok(bundle)
42    }
43
44    /// Updates the own leaf node. The application can choose to update the
45    /// credential, the capabilities, and the extensions by buliding the
46    /// [`LeafNodeParameters`].
47    ///
48    /// In contrast to `self_update`, this function allows updating the
49    /// signature public key in the senders leaf node. Note that `new_signer`
50    /// MUST be the private key corresponding to the public key set in the
51    /// `leaf_node_parameters`.
52    ///
53    /// If successful, it returns a tuple of [`MlsMessageOut`] (containing the
54    /// commit), an optional [`MlsMessageOut`] (containing the [`Welcome`]) and
55    /// the [GroupInfo]. The [`Welcome`] is [Some] when the queue of pending
56    /// proposals contained add proposals The [GroupInfo] is [Some] if the group
57    /// has the `use_ratchet_tree_extension` flag set.
58    ///
59    /// Returns an error if there is a pending commit.
60    ///
61    /// [`Welcome`]: crate::messages::Welcome
62    pub fn self_update_with_new_signer<Provider: OpenMlsProvider, S: Signer>(
63        &mut self,
64        provider: &Provider,
65        old_signer: &impl Signer,
66        new_signer: NewSignerBundle<'_, S>,
67        leaf_node_parameters: LeafNodeParameters,
68    ) -> Result<CommitMessageBundle, SelfUpdateError<Provider::StorageError>> {
69        self.is_operational()?;
70
71        let bundle = self
72            .commit_builder()
73            .leaf_node_parameters(leaf_node_parameters)
74            .consume_proposal_store(true)
75            .load_psks(provider.storage())?
76            .build_with_new_signer(
77                provider.rand(),
78                provider.crypto(),
79                old_signer,
80                new_signer,
81                |_| true,
82            )?
83            .stage_commit(provider)?;
84
85        self.reset_aad();
86
87        Ok(bundle)
88    }
89
90    /// Creates a proposal to update the own leaf node. Optionally, a
91    /// [`LeafNode`] can be provided to update the leaf node. Note that its
92    /// private key must be manually added to the key store.
93    fn create_self_update_proposal_internal<Provider: OpenMlsProvider, S: Signer>(
94        &mut self,
95        provider: &Provider,
96        old_signer: &impl Signer,
97        new_signer: Option<NewSignerBundle<'_, S>>,
98        mut leaf_node_parameters: LeafNodeParameters,
99    ) -> Result<AuthenticatedContent, ProposeSelfUpdateError<Provider::StorageError>> {
100        self.is_operational()?;
101
102        // Here we clone our own leaf to rekey it such that we don't change the
103        // tree.
104        // The new leaf node will be applied later when the proposal is
105        // committed.
106        let mut own_leaf = self
107            .public_group()
108            .leaf(self.own_leaf_index())
109            .ok_or_else(|| LibraryError::custom("The tree is broken. Couldn't find own leaf."))?
110            .clone();
111
112        if let Some(new_signer) = new_signer {
113            if self.ciphersuite().signature_algorithm() != new_signer.signer.signature_scheme() {
114                return Err(ProposeSelfUpdateError::InvalidSignerCiphersuite);
115            }
116
117            // Reconcile `leaf_node_parameters.credential_with_key` with
118            // `new_signer.credential_with_key`. Mirrors the commit-path logic in
119            // `CommitBuilder::build_internal`.
120            if let Some(ln_cred) = leaf_node_parameters.credential_with_key() {
121                if ln_cred != &new_signer.credential_with_key {
122                    return Err(ProposeSelfUpdateError::InvalidLeafNodeParameters);
123                }
124            } else {
125                leaf_node_parameters.set_credential_with_key(new_signer.credential_with_key);
126            }
127
128            own_leaf.update(
129                self.ciphersuite(),
130                provider,
131                new_signer.signer,
132                self.group_id().clone(),
133                self.own_leaf_index(),
134                leaf_node_parameters,
135            )?;
136        } else {
137            own_leaf.update(
138                self.ciphersuite(),
139                provider,
140                old_signer,
141                self.group_id().clone(),
142                self.own_leaf_index(),
143                leaf_node_parameters,
144            )?;
145        }
146
147        // Validate that the updated leaf node supports all group context extensions
148        // https://validation.openmls.tech/#valn0602
149        let leaf_supports_all_extensions = self
150            .public_group()
151            .group_context()
152            .extensions()
153            .iter()
154            .all(|extension| own_leaf.supports_extension(&extension.extension_type()));
155
156        if !leaf_supports_all_extensions {
157            return Err(ProposeSelfUpdateError::UnsupportedGroupContextExtensions);
158        }
159
160        let update_proposal =
161            self.create_update_proposal(self.framing_parameters(), own_leaf.clone(), old_signer)?;
162
163        provider
164            .storage()
165            .append_own_leaf_node(self.group_id(), &own_leaf)
166            .map_err(ProposeSelfUpdateError::StorageError)?;
167        self.own_leaf_nodes.push(own_leaf);
168
169        Ok(update_proposal)
170    }
171
172    fn propose_self_update_internal<Provider: OpenMlsProvider, S: Signer>(
173        &mut self,
174        provider: &Provider,
175        old_signer: &impl Signer,
176        new_signer: Option<NewSignerBundle<'_, S>>,
177        leaf_node_parameters: LeafNodeParameters,
178    ) -> Result<(MlsMessageOut, ProposalRef), ProposeSelfUpdateError<Provider::StorageError>> {
179        let update_proposal = self.create_self_update_proposal_internal(
180            provider,
181            old_signer,
182            new_signer,
183            leaf_node_parameters,
184        )?;
185        let proposal = QueuedProposal::from_authenticated_content_by_ref(
186            self.ciphersuite(),
187            provider.crypto(),
188            update_proposal.clone(),
189        )?;
190        let proposal_ref = proposal.proposal_reference();
191        provider
192            .storage()
193            .queue_proposal(self.group_id(), &proposal_ref, &proposal)
194            .map_err(ProposeSelfUpdateError::StorageError)?;
195        self.proposal_store_mut().add(proposal);
196
197        let mls_message = self.content_to_mls_message(update_proposal, provider)?;
198
199        self.reset_aad();
200        Ok((mls_message, proposal_ref))
201    }
202
203    /// Creates a proposal to update the own leaf node. The application can
204    /// choose to update the credential, the capabilities, and the extensions by
205    /// building the [`LeafNodeParameters`].
206    pub fn propose_self_update<Provider: OpenMlsProvider, S: Signer>(
207        &mut self,
208        provider: &Provider,
209        signer: &S,
210        leaf_node_parameters: LeafNodeParameters,
211    ) -> Result<(MlsMessageOut, ProposalRef), ProposeSelfUpdateError<Provider::StorageError>> {
212        self.propose_self_update_internal(
213            provider,
214            signer,
215            None::<NewSignerBundle<'_, S>>,
216            leaf_node_parameters,
217        )
218    }
219
220    /// Creates an Update proposal that rotates the sender's signature key.
221    ///
222    /// In contrast to [`Self::propose_self_update`], this function allows
223    /// updating the signature public key of the sender's leaf node. The
224    /// produced MLS message's envelope is authenticated using `old_signer`
225    /// (required because the sender's current leaf in the group tree still
226    /// carries the old signature key), while the new leaf embedded in the
227    /// `UpdateProposal` is self-signed by `new_signer.signer` so that it
228    /// validates against its own `signature_key` field at the receiver.
229    ///
230    /// If `leaf_node_parameters` sets `credential_with_key`, it MUST equal
231    /// `new_signer.credential_with_key`. If it is not set the new-signer credential
232    /// is folded in automatically.
233    ///
234    /// Returns an error if there is a pending commit.
235    pub fn propose_self_update_with_new_signer<Provider: OpenMlsProvider, S: Signer>(
236        &mut self,
237        provider: &Provider,
238        old_signer: &impl Signer,
239        new_signer: NewSignerBundle<'_, S>,
240        leaf_node_parameters: LeafNodeParameters,
241    ) -> Result<(MlsMessageOut, ProposalRef), ProposeSelfUpdateError<Provider::StorageError>> {
242        self.propose_self_update_internal(
243            provider,
244            old_signer,
245            Some(new_signer),
246            leaf_node_parameters,
247        )
248    }
249}