core_crypto/mls/conversation/conversation_guard/
commit.rs1use openmls::prelude::KeyPackageIn;
4
5use crate::mls::conversation::{ConversationWithMls as _, Error};
6use crate::mls::credential::CredentialBundle;
7use crate::prelude::MlsCredentialType;
8use crate::{
9 LeafError, MlsError, MlsTransportResponse, RecursiveError,
10 e2e_identity::init_certificates::NewCrlDistributionPoint,
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.mls_client().await?;
33 let backend = self.mls_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 .central()
49 .await?
50 .mls_transport()
51 .await
52 .map_err(RecursiveError::root("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.mls_client().await?;
57 let backend = self.mls_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<NewCrlDistributionPoint> {
104 let backend = self.mls_provider().await?;
105 let credential = self.credential_bundle().await?;
106 let signer = credential.signature_key();
107 let mut conversation = self.conversation_mut().await;
108
109 let crl_dps = extract_crl_uris_from_credentials(key_packages.iter().filter_map(|kp| {
111 let mls_credential = kp.credential().mls_credential();
112 matches!(mls_credential, openmls::prelude::MlsCredentialType::X509(_)).then_some(mls_credential)
113 }))
114 .map_err(RecursiveError::mls_credential("extracting crl uris from credentials"))?;
115 let crl_new_distribution_points = get_new_crl_distribution_points(&backend, crl_dps)
116 .await
117 .map_err(RecursiveError::mls_credential("getting new crl distribution points"))?;
118
119 let (commit, welcome, group_info) = conversation
120 .group
121 .add_members(&backend, signer, key_packages)
122 .await
123 .map_err(MlsError::wrap("group add members"))?;
124
125 let welcome = Some(welcome);
127 let group_info = Self::group_info(group_info)?;
128
129 conversation
130 .persist_group_when_changed(&backend.keystore(), false)
131 .await?;
132
133 drop(conversation);
135
136 let commit = MlsCommitBundle {
137 commit,
138 welcome,
139 group_info,
140 };
141
142 self.send_and_merge_commit(commit).await?;
143
144 Ok(crl_new_distribution_points)
145 }
146
147 pub async fn remove_members(&mut self, clients: &[ClientId]) -> Result<()> {
153 let backend = self.mls_provider().await?;
154 let credential = self.credential_bundle().await?;
155 let signer = credential.signature_key();
156 let mut conversation = self.inner.write().await;
157
158 let members = conversation
159 .group
160 .members()
161 .filter_map(|kp| {
162 clients
163 .iter()
164 .any(move |client_id| client_id.as_slice() == kp.credential.identity())
165 .then_some(kp.index)
166 })
167 .collect::<Vec<_>>();
168
169 let (commit, welcome, group_info) = conversation
170 .group
171 .remove_members(&backend, signer, &members)
172 .await
173 .map_err(MlsError::wrap("group remove members"))?;
174
175 let group_info = Self::group_info(group_info)?;
176
177 conversation
178 .persist_group_when_changed(&backend.keystore(), false)
179 .await?;
180
181 drop(conversation);
183
184 self.send_and_merge_commit(MlsCommitBundle {
185 commit,
186 welcome,
187 group_info,
188 })
189 .await
190 }
191
192 pub async fn update_key_material(&mut self) -> Result<()> {
199 let client = self.mls_client().await?;
200 let backend = self.mls_provider().await?;
201 let mut conversation = self.inner.write().await;
202 let commit = conversation
203 .update_keying_material(&client, &backend, None, None)
204 .await?;
205 drop(conversation);
206 self.send_and_merge_commit(commit).await
207 }
208
209 pub async fn e2ei_rotate(&mut self, cb: Option<&CredentialBundle>) -> Result<()> {
215 let client = &self.mls_client().await?;
216 let backend = &self.mls_provider().await?;
217 let mut conversation = self.inner.write().await;
218
219 let cb = match cb {
220 Some(cb) => cb,
221 None => &client
222 .find_most_recent_credential_bundle(
223 conversation.ciphersuite().signature_algorithm(),
224 MlsCredentialType::X509,
225 )
226 .await
227 .map_err(RecursiveError::mls_client("finding most recent x509 credential bundle"))?,
228 };
229
230 let mut leaf_node = conversation
231 .group
232 .own_leaf()
233 .ok_or(LeafError::InternalMlsError)?
234 .clone();
235 leaf_node.set_credential_with_key(cb.to_mls_credential_with_key());
236
237 let commit = conversation
238 .update_keying_material(client, backend, Some(cb), Some(leaf_node))
239 .await?;
240 drop(conversation);
242
243 self.send_and_merge_commit(commit).await
244 }
245
246 pub async fn commit_pending_proposals(&mut self) -> Result<()> {
248 let client = self.mls_client().await?;
249 let backend = self.mls_provider().await?;
250 let mut conversation = self.inner.write().await;
251 let commit = conversation.commit_pending_proposals(&client, &backend).await?;
252 drop(conversation);
253 let Some(commit) = commit else {
254 return Ok(());
255 };
256 self.send_and_merge_commit(commit).await
257 }
258}