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
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    #[openmls_traits::maybe_async]
33    pub async fn self_update<Provider: OpenMlsProvider>(
34        &mut self,
35        provider: &Provider,
36        signer: &impl Signer,
37        leaf_node_parameters: LeafNodeParameters,
38    ) -> Result<CommitMessageBundle, SelfUpdateError<Provider::StorageError>> {
39        self.is_operational()?;
40
41        let bundle = self
42            .commit_builder()
43            .leaf_node_parameters(leaf_node_parameters)
44            .consume_proposal_store(true)
45            .load_psks(provider.storage())
46            .await?
47            .build(provider.rand(), provider.crypto(), signer, |_| true)?
48            .stage_commit(provider)
49            .await?;
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    #[openmls_traits::maybe_async]
81    pub async fn self_update_with_new_signer<Provider: OpenMlsProvider, S: Signer>(
82        &mut self,
83        provider: &Provider,
84        old_signer: &impl Signer,
85        new_signer: NewSignerBundle<'_, S>,
86        leaf_node_parameters: LeafNodeParameters,
87    ) -> Result<CommitMessageBundle, SelfUpdateError<Provider::StorageError>> {
88        self.is_operational()?;
89
90        let bundle = self
91            .commit_builder()
92            .leaf_node_parameters(leaf_node_parameters)
93            .consume_proposal_store(true)
94            .load_psks(provider.storage())
95            .await?
96            .build_with_new_signer(
97                provider.rand(),
98                provider.crypto(),
99                old_signer,
100                new_signer,
101                |_| true,
102            )?
103            .stage_commit(provider)
104            .await?;
105
106        self.reset_aad();
107
108        Ok(bundle)
109    }
110
111    /// Creates a proposal to update the own leaf node. Optionally, a
112    /// [`LeafNode`] can be provided to update the leaf node. Note that its
113    /// private key must be manually added to the key store.
114    #[openmls_traits::maybe_async]
115    async fn create_self_update_proposal_internal<Provider: OpenMlsProvider, S: Signer>(
116        &mut self,
117        provider: &Provider,
118        old_signer: &impl Signer,
119        new_signer: Option<NewSignerBundle<'_, S>>,
120        mut leaf_node_parameters: LeafNodeParameters,
121    ) -> Result<AuthenticatedContent, ProposeSelfUpdateError<Provider::StorageError>> {
122        self.is_operational()?;
123
124        // Here we clone our own leaf to rekey it such that we don't change the
125        // tree.
126        // The new leaf node will be applied later when the proposal is
127        // committed.
128        let mut own_leaf = self
129            .public_group()
130            .leaf(self.own_leaf_index())
131            .ok_or_else(|| LibraryError::custom("The tree is broken. Couldn't find own leaf."))?
132            .clone();
133        let constraints = self.public_group().leaf_node_constraints();
134
135        if let Some(new_signer) = new_signer {
136            if self.ciphersuite().signature_algorithm() != new_signer.signer.signature_scheme() {
137                return Err(ProposeSelfUpdateError::InvalidSignerCiphersuite);
138            }
139
140            // Reconcile `leaf_node_parameters.credential_with_key` with
141            // `new_signer.credential_with_key`. Mirrors the commit-path logic in
142            // `CommitBuilder::build_internal`.
143            if let Some(ln_cred) = leaf_node_parameters.credential_with_key() {
144                if ln_cred != &new_signer.credential_with_key {
145                    return Err(ProposeSelfUpdateError::InvalidLeafNodeParameters);
146                }
147            } else {
148                leaf_node_parameters.set_credential_with_key(new_signer.credential_with_key);
149            }
150
151            own_leaf
152                .update(
153                    self.ciphersuite(),
154                    provider,
155                    new_signer.signer,
156                    self.group_id().clone(),
157                    self.own_leaf_index(),
158                    leaf_node_parameters,
159                    &constraints,
160                )
161                .await?;
162        } else {
163            own_leaf
164                .update(
165                    self.ciphersuite(),
166                    provider,
167                    old_signer,
168                    self.group_id().clone(),
169                    self.own_leaf_index(),
170                    leaf_node_parameters,
171                    &constraints,
172                )
173                .await?;
174        }
175
176        let aad = self.outgoing_authenticated_data()?;
177        let framing_parameters = FramingParameters::new(&aad, self.outgoing_wire_format());
178        let update_proposal =
179            self.create_update_proposal(framing_parameters, own_leaf.clone(), old_signer)?;
180
181        provider
182            .storage()
183            .append_own_leaf_node(self.group_id(), &own_leaf)
184            .await
185            .map_err(ProposeSelfUpdateError::StorageError)?;
186        self.own_leaf_nodes.push(own_leaf);
187
188        Ok(update_proposal)
189    }
190
191    #[openmls_traits::maybe_async]
192    pub(crate) async 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
201            .create_self_update_proposal_internal(
202                provider,
203                old_signer,
204                new_signer,
205                leaf_node_parameters,
206            )
207            .await?;
208        let proposal = QueuedProposal::from_authenticated_content_by_ref(
209            self.ciphersuite(),
210            provider.crypto(),
211            update_proposal.clone(),
212        )?;
213        let proposal_ref = proposal.proposal_reference();
214        provider
215            .storage()
216            .queue_proposal(self.group_id(), &proposal_ref, &proposal)
217            .await
218            .map_err(ProposeSelfUpdateError::StorageError)?;
219        self.proposal_store_mut().add(proposal);
220
221        let framing = self
222            .content_to_mls_message(update_proposal, provider)
223            .await?;
224
225        self.reset_aad();
226        Ok((framing, proposal_ref))
227    }
228
229    /// Creates a proposal to update the own leaf node. The application can
230    /// choose to update the credential, the capabilities, and the extensions by
231    /// building the [`LeafNodeParameters`].
232    ///
233    /// Under the `virtual-clients-draft` feature this function is unavailable.
234    /// Use [`Self::propose_unconfirmed`] with
235    /// [`Propose::Update`](crate::group::Propose::Update), which retains the
236    /// handshake secret and returns the confirmation data.
237    #[cfg(any(not(feature = "virtual-clients-draft"), feature = "test-utils", test))]
238    #[openmls_traits::maybe_async]
239    pub async fn propose_self_update<Provider: OpenMlsProvider, S: Signer>(
240        &mut self,
241        provider: &Provider,
242        signer: &S,
243        leaf_node_parameters: LeafNodeParameters,
244    ) -> Result<(MlsMessageOut, ProposalRef), ProposeSelfUpdateError<Provider::StorageError>> {
245        let (framing, proposal_ref) = self
246            .propose_self_update_internal(
247                provider,
248                signer,
249                None::<NewSignerBundle<'_, S>>,
250                leaf_node_parameters,
251            )
252            .await?;
253        Ok((framing.message, proposal_ref))
254    }
255
256    /// Creates an Update proposal that rotates the sender's signature key.
257    ///
258    /// In contrast to [`Self::propose_self_update`], this function allows
259    /// updating the signature public key of the sender's leaf node. The
260    /// produced MLS message's envelope is authenticated using `old_signer`
261    /// (required because the sender's current leaf in the group tree still
262    /// carries the old signature key), while the new leaf embedded in the
263    /// `UpdateProposal` is self-signed by `new_signer.signer` so that it
264    /// validates against its own `signature_key` field at the receiver.
265    ///
266    /// If `leaf_node_parameters` sets `credential_with_key`, it MUST equal
267    /// `new_signer.credential_with_key`. If it is not set the new-signer credential
268    /// is folded in automatically.
269    ///
270    /// Returns an error if there is a pending commit.
271    ///
272    /// Under the `virtual-clients-draft` feature this function is unavailable.
273    /// Use [`Self::propose_self_update_with_new_signer_unconfirmed`], which
274    /// retains the handshake secret and returns the confirmation data.
275    #[cfg(any(not(feature = "virtual-clients-draft"), feature = "test-utils", test))]
276    #[openmls_traits::maybe_async]
277    pub async fn propose_self_update_with_new_signer<Provider: OpenMlsProvider, S: Signer>(
278        &mut self,
279        provider: &Provider,
280        old_signer: &impl Signer,
281        new_signer: NewSignerBundle<'_, S>,
282        leaf_node_parameters: LeafNodeParameters,
283    ) -> Result<(MlsMessageOut, ProposalRef), ProposeSelfUpdateError<Provider::StorageError>> {
284        let (framing, proposal_ref) = self
285            .propose_self_update_internal(
286                provider,
287                old_signer,
288                Some(new_signer),
289                leaf_node_parameters,
290            )
291            .await?;
292        Ok((framing.message, proposal_ref))
293    }
294
295    /// Like [`Self::propose_self_update_with_new_signer`], but retains the
296    /// handshake secret and returns the [`HandshakeConfirmationData`] alongside
297    /// the framed proposal, so a virtual client can confirm the proposal with
298    /// [`MlsGroup::confirm_handshake_message`] once the Delivery Service has
299    /// accepted it. The confirmation is `None` for a proposal framed as a
300    /// plaintext PublicMessage.
301    ///
302    /// [`MlsGroup::confirm_handshake_message`]: crate::group::MlsGroup::confirm_handshake_message
303    #[cfg(feature = "virtual-clients-draft")]
304    #[openmls_traits::maybe_async]
305    pub async fn propose_self_update_with_new_signer_unconfirmed<
306        Provider: OpenMlsProvider,
307        S: Signer,
308    >(
309        &mut self,
310        provider: &Provider,
311        old_signer: &impl Signer,
312        new_signer: NewSignerBundle<'_, S>,
313        leaf_node_parameters: LeafNodeParameters,
314    ) -> Result<
315        (
316            MlsMessageOut,
317            ProposalRef,
318            Option<HandshakeConfirmationData>,
319        ),
320        ProposeSelfUpdateError<Provider::StorageError>,
321    > {
322        let (framing, proposal_ref) = self
323            .propose_self_update_internal(
324                provider,
325                old_signer,
326                Some(new_signer),
327                leaf_node_parameters,
328            )
329            .await?;
330        Ok((framing.message, proposal_ref, framing.confirmation))
331    }
332}