Skip to main content

openmls/group/mls_group/
updates.rs

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