core_crypto/mls/conversation/mutable/
commit.rs1use std::{borrow::Borrow, collections::HashMap};
4
5use openmls::prelude::KeyPackageIn;
6
7use super::history_sharing::HistoryClientUpdateOutcome;
8use crate::{
9 ClientId, ClientIdRef, CredentialRef, GroupInfoBundle, LeafError, OpenMlsError, RecursiveError,
10 mls::{
11 conversation::{ConversationMut, Error, Result, commit::CommitBundle},
12 credential::Credential,
13 },
14};
15
16impl ConversationMut {
17 pub(super) async fn send_and_merge_commit(&mut self, commit: CommitBundle) -> Result<()> {
18 let history_client_update_result = self.update_history_client().await?;
19 if history_client_update_result == HistoryClientUpdateOutcome::CommitSentAndMerged {
20 return Ok(());
21 }
22
23 match self.send_commit(commit).await {
24 Ok(()) => self.merge_commit().await,
25 e @ Err(_) => {
26 self.clear_pending_commit().await?;
27 e
28 }
29 }
30 }
31
32 pub(super) async fn merge_commit(&mut self) -> Result<()> {
33 self.commit_accepted().await?;
34 let conversation_id = self.id().to_owned();
35 let epoch = self.epoch().await;
36
37 self.tx_context
38 .queue_epoch_changed(conversation_id, epoch)
39 .await
40 .map_err(RecursiveError::transaction("queueing epoch changed notification"))?;
41
42 Ok(())
43 }
44
45 pub(super) async fn send_commit(&mut self, commit: CommitBundle) -> Result<()> {
47 let transport = self.transport().await?;
48
49 transport
50 .send_commit_bundle(commit)
51 .await
52 .map_err(RecursiveError::root("sending commit bundle"))
53 .map_err(Into::into)
54 }
55
56 pub async fn add_members(&mut self, key_packages: Vec<KeyPackageIn>) -> Result<()> {
58 let commit = self.add_members_inner(key_packages).await?;
59
60 self.send_and_merge_commit(commit).await?;
61
62 Ok(())
63 }
64
65 pub(super) async fn add_members_inner(&mut self, key_packages: Vec<KeyPackageIn>) -> Result<CommitBundle> {
66 self.ensure_no_pending_commit().await?;
67 let backend = self.crypto_provider().await?;
68 let credential = self.credential().await?;
69
70 self.mutate_group(async |_, group, _| {
71 let signer = credential.signature_key();
72 let (commit, welcome, group_info) = group
73 .add_members(&backend, signer, key_packages.clone())
74 .await
75 .map_err(|err| {
76 if Self::err_is_duplicate_signature_key(&err) {
77 Self::clients_with_duplicate_signature_keys(key_packages.as_ref())
78 .map(|affected_clients| Error::DuplicateSignature { affected_clients })
79 .unwrap_or_else(|e| e)
80 } else {
81 OpenMlsError::wrap("group add members")(err).into()
82 }
83 })?;
84
85 Ok(CommitBundle {
86 commit,
87 welcome: Some(welcome),
88 group_info: Self::group_info(group_info)?,
89 encrypted_message: None,
90 })
91 })
92 .await
93 }
94
95 fn err_is_duplicate_signature_key(
96 err: &openmls::prelude::AddMembersError<core_crypto_keystore::CryptoKeystoreError>,
97 ) -> bool {
98 matches!(
99 err,
100 openmls::prelude::AddMembersError::CreateCommitError(
101 openmls::prelude::CreateCommitError::ProposalValidationError(
102 openmls::prelude::ProposalValidationError::DuplicateSignatureKey
103 )
104 )
105 )
106 }
107
108 fn clients_with_duplicate_signature_keys(key_packages: &[KeyPackageIn]) -> Result<Vec<(ClientId, ClientId)>> {
109 let mut seen_signature_keys = HashMap::new();
110 let mut duplicate_pairs = Vec::new();
111
112 for key_package in key_packages {
113 let signature_key = key_package.unverified_credential().signature_key.as_slice().to_vec();
114
115 let client_id: ClientId = key_package
116 .credential()
117 .identity()
118 .try_into()
119 .map_err(RecursiveError::mls_client("client id from bytes"))?;
120
121 if let Some(previous_client_id) = seen_signature_keys.insert(signature_key, client_id.clone()) {
122 duplicate_pairs.push((previous_client_id, client_id));
123 }
124 }
125
126 Ok(duplicate_pairs)
127 }
128
129 pub async fn remove_members(&mut self, clients: &[impl Borrow<ClientIdRef>]) -> Result<()> {
135 self.remove_members_or_history_clients(clients.iter().map(|e| e.borrow().as_ref()))
136 .await
137 }
138
139 pub(crate) async fn remove_members_or_history_clients(
140 &mut self,
141 clients: impl Iterator<Item = &[u8]> + Clone,
142 ) -> Result<()> {
143 self.ensure_no_pending_commit().await?;
144 let backend = self.crypto_provider().await?;
145 let credential = self.credential().await?;
146 let signer = credential.signature_key();
147 let (commit, welcome, group_info) = self
148 .mutate_group(async |_, group, _| {
149 let members = group
150 .members()
151 .filter_map(|member| {
152 clients
153 .clone()
154 .any(move |client_id| client_id == member.credential.identity())
155 .then_some(member.index)
156 })
157 .collect::<Vec<_>>();
158 group
159 .remove_members(&backend, signer, &members)
160 .await
161 .map_err(OpenMlsError::wrap("group remove members"))
162 .map_err(Into::into)
163 })
164 .await?;
165
166 let group_info = Self::group_info(group_info)?;
167
168 self.send_and_merge_commit(CommitBundle {
169 commit,
170 welcome,
171 group_info,
172 encrypted_message: None,
173 })
174 .await
175 }
176
177 pub async fn update_key_material(&mut self) -> Result<()> {
179 let credential = self.credential().await?;
180 let commit = self.set_credential_inner(&credential).await?;
181 self.send_and_merge_commit(commit).await
182 }
183
184 pub async fn set_credential_by_ref(&mut self, credential_ref: &CredentialRef) -> Result<()> {
186 let database = self.database().await?;
187 let credential = credential_ref
188 .load(&*database)
189 .await
190 .map_err(RecursiveError::mls_credential_ref("loading credential from ref"))?;
191 let commit = self.set_credential_inner(&credential).await?;
192
193 self.send_and_merge_commit(commit).await
194 }
195
196 pub(crate) async fn set_credential_inner(&mut self, credential: &Credential) -> Result<CommitBundle> {
199 self.ensure_no_pending_commit().await?;
200 let backend = self.crypto_provider().await?;
201 let credential = credential.clone();
202
203 self.mutate_group(async |_, group, _| {
204 let updated_leaf_node = {
208 let leaf_node = group.own_leaf().ok_or(LeafError::InternalMlsError)?;
209 if leaf_node.credential() == &credential.mls_credential {
210 None
211 } else {
212 let mut leaf_node = leaf_node.clone();
213 leaf_node.set_credential_with_key(credential.to_mls_credential_with_key());
214 Some(leaf_node)
215 }
216 };
217
218 let (commit, welcome, group_info) = group
219 .explicit_self_update(&backend, &credential.signature_key_pair, updated_leaf_node)
220 .await
221 .map_err(OpenMlsError::wrap("group self update"))?;
222
223 let group_info = group_info.ok_or(LeafError::MissingGroupInfo)?;
225 let group_info = GroupInfoBundle::try_new_full_plaintext(group_info)?;
226
227 Ok(CommitBundle {
228 welcome,
229 commit,
230 group_info,
231 encrypted_message: None,
232 })
233 })
234 .await
235 }
236
237 pub async fn commit_pending_proposals(&mut self) -> Result<()> {
239 self.ensure_no_pending_commit().await?;
240 let commit = self.commit_pending_proposals_inner().await?;
241 let Some(commit) = commit else {
242 return Ok(());
243 };
244 self.send_and_merge_commit(commit).await
245 }
246
247 pub(crate) async fn commit_pending_proposals_inner(&mut self) -> Result<Option<CommitBundle>> {
248 if self.group().await.pending_proposals().next().is_none() {
249 return Ok(None);
250 }
251
252 let crypto_provider = self.crypto_provider().await?;
253 let credential = self.credential().await?;
254
255 let (commit, welcome, openmls_group_info) = self
256 .mutate_group(async |_, group, _| {
257 let signer = &credential.signature_key_pair;
258 group
259 .commit_to_pending_proposals(&crypto_provider, signer)
260 .await
261 .map_err(OpenMlsError::wrap("group commit to pending proposals"))
262 .map_err(Into::into)
263 })
264 .await?;
265 let group_info = GroupInfoBundle::try_new_full_plaintext(
266 openmls_group_info.expect("creating a commit always produces a group info"),
267 )?;
268
269 Ok(Some(CommitBundle {
270 welcome,
271 commit,
272 group_info,
273 encrypted_message: None,
274 }))
275 }
276
277 pub(crate) async fn commit_inline_proposals(
278 &mut self,
279 proposals: Vec<openmls::prelude::Proposal>,
280 ) -> Result<Option<CommitBundle>> {
281 if proposals.is_empty() {
282 return Ok(None);
283 }
284
285 let provider = &self.crypto_provider().await?;
286 let credential = self.credential().await?;
287
288 let (commit, welcome, openmls_group_info) = self
289 .mutate_group(async |_, group, _| {
290 let signer = &credential.signature_key_pair;
291 group
292 .commit_to_inline_proposals(provider, signer, proposals)
293 .await
294 .map_err(OpenMlsError::wrap("group commit to pending proposals"))
295 .map_err(Into::into)
296 })
297 .await?;
298 let group_info = GroupInfoBundle::try_new_full_plaintext(
299 openmls_group_info.expect("creating a commit always produces a group info"),
300 )?;
301
302 Ok(Some(CommitBundle {
303 welcome,
304 commit,
305 group_info,
306 encrypted_message: None,
307 }))
308 }
309}