core_crypto/mls/conversation/conversation_guard/
commit.rs
1use openmls::prelude::{KeyPackageIn, LeafNode};
4
5use crate::mls::conversation::{ConversationWithMls as _, Error};
6use crate::mls::credential::CredentialBundle;
7use crate::prelude::{MlsCredentialType, MlsGroupInfoBundle};
8use crate::{
9 LeafError, MlsError, MlsTransportResponse, RecursiveError,
10 e2e_identity::NewCrlDistributionPoints,
11 mls::{
12 conversation::{ConversationGuard, Result, commit::MlsCommitBundle},
13 credential::crl::{extract_crl_uris_from_credentials, get_new_crl_distribution_points},
14 },
15 prelude::ClientId,
16};
17
18#[derive(Clone, Copy, PartialEq, Eq)]
20pub(crate) enum TransportedCommitPolicy {
21 Merge,
23 None,
25}
26
27impl ConversationGuard {
28 async fn send_and_merge_commit(&mut self, commit: MlsCommitBundle) -> Result<()> {
29 match self.send_commit(commit).await {
30 Ok(TransportedCommitPolicy::None) => Ok(()),
31 Ok(TransportedCommitPolicy::Merge) => {
32 let client = self.session().await?;
33 let backend = self.crypto_provider().await?;
34 let mut conversation = self.inner.write().await;
35 conversation.commit_accepted(&client, &backend).await
36 }
37 Err(e @ Error::MessageRejected { .. }) => {
38 self.clear_pending_commit().await?;
39 Err(e)
40 }
41 Err(e) => Err(e),
42 }
43 }
44
45 async fn send_commit(&mut self, mut commit: MlsCommitBundle) -> Result<TransportedCommitPolicy> {
47 let transport = self
48 .context()
49 .await?
50 .mls_transport()
51 .await
52 .map_err(RecursiveError::transaction("getting mls transport"))?;
53 let transport = transport.as_ref().ok_or::<Error>(
54 RecursiveError::root("getting mls transport")(crate::Error::MlsTransportNotProvided).into(),
55 )?;
56 let client = self.session().await?;
57 let backend = self.crypto_provider().await?;
58
59 let inner = self.conversation().await;
60 let epoch_before_sending = inner.group().epoch().as_u64();
61 drop(inner);
63
64 loop {
65 match transport
66 .send_commit_bundle(commit.clone())
67 .await
68 .map_err(RecursiveError::root("sending commit bundle"))?
69 {
70 MlsTransportResponse::Success => {
71 return Ok(TransportedCommitPolicy::Merge);
72 }
73 MlsTransportResponse::Abort { reason } => {
74 return Err(Error::MessageRejected { reason });
75 }
76 MlsTransportResponse::Retry => {
77 let mut inner = self.conversation_mut().await;
78 let epoch_after_sending = inner.group().epoch().as_u64();
79 if epoch_before_sending == epoch_after_sending {
80 continue;
84 }
85
86 let Some(commit_to_retry) = inner.commit_pending_proposals(&client, &backend).await? else {
93 return Ok(TransportedCommitPolicy::None);
95 };
96 commit = commit_to_retry;
97 }
98 }
99 }
100 }
101
102 pub async fn add_members(&mut self, key_packages: Vec<KeyPackageIn>) -> Result<NewCrlDistributionPoints> {
104 self.ensure_no_pending_commit().await?;
105 let backend = self.crypto_provider().await?;
106 let credential = self.credential_bundle().await?;
107 let signer = credential.signature_key();
108 let mut conversation = self.conversation_mut().await;
109
110 let crl_dps = extract_crl_uris_from_credentials(key_packages.iter().filter_map(|kp| {
112 let mls_credential = kp.credential().mls_credential();
113 matches!(mls_credential, openmls::prelude::MlsCredentialType::X509(_)).then_some(mls_credential)
114 }))
115 .map_err(RecursiveError::mls_credential("extracting crl uris from credentials"))?;
116 let crl_new_distribution_points = get_new_crl_distribution_points(&backend, crl_dps)
117 .await
118 .map_err(RecursiveError::mls_credential("getting new crl distribution points"))?;
119
120 let (commit, welcome, group_info) = conversation
121 .group
122 .add_members(&backend, signer, key_packages)
123 .await
124 .map_err(MlsError::wrap("group add members"))?;
125
126 let welcome = Some(welcome);
128 let group_info = Self::group_info(group_info)?;
129
130 conversation
131 .persist_group_when_changed(&backend.keystore(), false)
132 .await?;
133
134 drop(conversation);
136
137 let commit = MlsCommitBundle {
138 commit,
139 welcome,
140 group_info,
141 };
142
143 self.send_and_merge_commit(commit).await?;
144
145 Ok(crl_new_distribution_points)
146 }
147
148 pub async fn remove_members(&mut self, clients: &[ClientId]) -> Result<()> {
154 self.ensure_no_pending_commit().await?;
155 let backend = self.crypto_provider().await?;
156 let credential = self.credential_bundle().await?;
157 let signer = credential.signature_key();
158 let mut conversation = self.inner.write().await;
159
160 let members = conversation
161 .group
162 .members()
163 .filter_map(|kp| {
164 clients
165 .iter()
166 .any(move |client_id| client_id.as_slice() == kp.credential.identity())
167 .then_some(kp.index)
168 })
169 .collect::<Vec<_>>();
170
171 let (commit, welcome, group_info) = conversation
172 .group
173 .remove_members(&backend, signer, &members)
174 .await
175 .map_err(MlsError::wrap("group remove members"))?;
176
177 let group_info = Self::group_info(group_info)?;
178
179 conversation
180 .persist_group_when_changed(&backend.keystore(), false)
181 .await?;
182
183 drop(conversation);
185
186 self.send_and_merge_commit(MlsCommitBundle {
187 commit,
188 welcome,
189 group_info,
190 })
191 .await
192 }
193
194 pub async fn update_key_material(&mut self) -> Result<()> {
196 let commit = self.update_key_material_inner(None, None).await?;
197 self.send_and_merge_commit(commit).await
198 }
199
200 pub async fn e2ei_rotate(&mut self, cb: Option<&CredentialBundle>) -> Result<()> {
206 let client = &self.session().await?;
207 let conversation = self.conversation().await;
208
209 let cb = match cb {
210 Some(cb) => cb,
211 None => &client
212 .find_most_recent_credential_bundle(
213 conversation.ciphersuite().signature_algorithm(),
214 MlsCredentialType::X509,
215 )
216 .await
217 .map_err(RecursiveError::mls_client("finding most recent x509 credential bundle"))?,
218 };
219
220 let mut leaf_node = conversation
221 .group
222 .own_leaf()
223 .ok_or(LeafError::InternalMlsError)?
224 .clone();
225 leaf_node.set_credential_with_key(cb.to_mls_credential_with_key());
226
227 drop(conversation);
229
230 let commit = self.update_key_material_inner(Some(cb), Some(leaf_node)).await?;
231
232 self.send_and_merge_commit(commit).await
233 }
234
235 pub(crate) async fn update_key_material_inner(
236 &mut self,
237 cb: Option<&CredentialBundle>,
238 leaf_node: Option<LeafNode>,
239 ) -> Result<MlsCommitBundle> {
240 self.ensure_no_pending_commit().await?;
241 let session = &self.session().await?;
242 let backend = &self.crypto_provider().await?;
243 let mut conversation = self.conversation_mut().await;
244 let cb = match cb {
245 None => &conversation.find_most_recent_credential_bundle(session).await?,
246 Some(cb) => cb,
247 };
248 let (commit, welcome, group_info) = conversation
249 .group
250 .explicit_self_update(backend, &cb.signature_key, leaf_node)
251 .await
252 .map_err(MlsError::wrap("group self update"))?;
253
254 let group_info = group_info.ok_or(LeafError::MissingGroupInfo)?;
256 let group_info = MlsGroupInfoBundle::try_new_full_plaintext(group_info)?;
257
258 conversation
259 .persist_group_when_changed(&backend.keystore(), false)
260 .await?;
261
262 Ok(MlsCommitBundle {
263 welcome,
264 commit,
265 group_info,
266 })
267 }
268
269 pub async fn commit_pending_proposals(&mut self) -> Result<()> {
271 self.ensure_no_pending_commit().await?;
272 let client = self.session().await?;
273 let backend = self.crypto_provider().await?;
274 let mut conversation = self.inner.write().await;
275 let commit = conversation.commit_pending_proposals(&client, &backend).await?;
276 drop(conversation);
277 let Some(commit) = commit else {
278 return Ok(());
279 };
280 self.send_and_merge_commit(commit).await
281 }
282}