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