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 aad = self.outgoing_authenticated_data()?;
161        let framing_parameters = FramingParameters::new(&aad, self.outgoing_wire_format());
162        let update_proposal =
163            self.create_update_proposal(framing_parameters, own_leaf.clone(), old_signer)?;
164
165        provider
166            .storage()
167            .append_own_leaf_node(self.group_id(), &own_leaf)
168            .map_err(ProposeSelfUpdateError::StorageError)?;
169        self.own_leaf_nodes.push(own_leaf);
170
171        Ok(update_proposal)
172    }
173
174    fn propose_self_update_internal<Provider: OpenMlsProvider, S: Signer>(
175        &mut self,
176        provider: &Provider,
177        old_signer: &impl Signer,
178        new_signer: Option<NewSignerBundle<'_, S>>,
179        leaf_node_parameters: LeafNodeParameters,
180    ) -> Result<(MlsMessageOut, ProposalRef), ProposeSelfUpdateError<Provider::StorageError>> {
181        let update_proposal = self.create_self_update_proposal_internal(
182            provider,
183            old_signer,
184            new_signer,
185            leaf_node_parameters,
186        )?;
187        let proposal = QueuedProposal::from_authenticated_content_by_ref(
188            self.ciphersuite(),
189            provider.crypto(),
190            update_proposal.clone(),
191        )?;
192        let proposal_ref = proposal.proposal_reference();
193        provider
194            .storage()
195            .queue_proposal(self.group_id(), &proposal_ref, &proposal)
196            .map_err(ProposeSelfUpdateError::StorageError)?;
197        self.proposal_store_mut().add(proposal);
198
199        let mls_message = self.content_to_mls_message(update_proposal, provider)?;
200
201        self.reset_aad();
202        Ok((mls_message, proposal_ref))
203    }
204
205    /// Creates a proposal to update the own leaf node. The application can
206    /// choose to update the credential, the capabilities, and the extensions by
207    /// building the [`LeafNodeParameters`].
208    pub fn propose_self_update<Provider: OpenMlsProvider, S: Signer>(
209        &mut self,
210        provider: &Provider,
211        signer: &S,
212        leaf_node_parameters: LeafNodeParameters,
213    ) -> Result<(MlsMessageOut, ProposalRef), ProposeSelfUpdateError<Provider::StorageError>> {
214        self.propose_self_update_internal(
215            provider,
216            signer,
217            None::<NewSignerBundle<'_, S>>,
218            leaf_node_parameters,
219        )
220    }
221
222    /// Creates an Update proposal that rotates the sender's signature key.
223    ///
224    /// In contrast to [`Self::propose_self_update`], this function allows
225    /// updating the signature public key of the sender's leaf node. The
226    /// produced MLS message's envelope is authenticated using `old_signer`
227    /// (required because the sender's current leaf in the group tree still
228    /// carries the old signature key), while the new leaf embedded in the
229    /// `UpdateProposal` is self-signed by `new_signer.signer` so that it
230    /// validates against its own `signature_key` field at the receiver.
231    ///
232    /// If `leaf_node_parameters` sets `credential_with_key`, it MUST equal
233    /// `new_signer.credential_with_key`. If it is not set the new-signer credential
234    /// is folded in automatically.
235    ///
236    /// Returns an error if there is a pending commit.
237    pub fn propose_self_update_with_new_signer<Provider: OpenMlsProvider, S: Signer>(
238        &mut self,
239        provider: &Provider,
240        old_signer: &impl Signer,
241        new_signer: NewSignerBundle<'_, S>,
242        leaf_node_parameters: LeafNodeParameters,
243    ) -> Result<(MlsMessageOut, ProposalRef), ProposeSelfUpdateError<Provider::StorageError>> {
244        self.propose_self_update_internal(
245            provider,
246            old_signer,
247            Some(new_signer),
248            leaf_node_parameters,
249        )
250    }
251}