core_crypto/mls/conversation/
welcome.rs1use std::borrow::BorrowMut;
2
3use super::{Error, Result};
4use crate::{
5 LeafError, MlsError, RecursiveError,
6 context::CentralContext,
7 e2e_identity::init_certificates::NewCrlDistributionPoint,
8 group_store::GroupStore,
9 mls::credential::crl::{extract_crl_uris_from_group, get_new_crl_distribution_points},
10 prelude::{ConversationId, MlsConversation, MlsConversationConfiguration, MlsCustomConfiguration},
11};
12use core_crypto_keystore::{connection::FetchFromDatabase, entities::PersistedMlsPendingGroup};
13use mls_crypto_provider::MlsCryptoProvider;
14use openmls::prelude::{MlsGroup, MlsMessageIn, MlsMessageInBody, Welcome};
15use openmls_traits::OpenMlsCryptoProvider;
16use tls_codec::Deserialize;
17
18#[derive(Debug)]
20pub struct WelcomeBundle {
21 pub id: ConversationId,
23 pub crl_new_distribution_points: NewCrlDistributionPoint,
25}
26
27impl CentralContext {
28 #[cfg_attr(test, crate::dispotent)]
40 pub async fn process_raw_welcome_message(
41 &self,
42 welcome: Vec<u8>,
43 custom_cfg: MlsCustomConfiguration,
44 ) -> Result<WelcomeBundle> {
45 let mut cursor = std::io::Cursor::new(welcome);
46 let welcome =
47 MlsMessageIn::tls_deserialize(&mut cursor).map_err(Error::tls_deserialize("mls message in (welcome)"))?;
48 self.process_welcome_message(welcome, custom_cfg).await
49 }
50
51 #[cfg_attr(test, crate::dispotent)]
65 pub async fn process_welcome_message(
66 &self,
67 welcome: MlsMessageIn,
68 custom_cfg: MlsCustomConfiguration,
69 ) -> Result<WelcomeBundle> {
70 let MlsMessageInBody::Welcome(welcome) = welcome.extract() else {
71 return Err(Error::CallerError(
72 "the message provided to process_welcome_message was not a welcome message",
73 ));
74 };
75 let cs = welcome.ciphersuite().into();
76 let configuration = MlsConversationConfiguration {
77 ciphersuite: cs,
78 custom: custom_cfg,
79 ..Default::default()
80 };
81 let mls_provider = self
82 .mls_provider()
83 .await
84 .map_err(RecursiveError::root("getting mls provider"))?;
85 let mut mls_groups = self
86 .mls_groups()
87 .await
88 .map_err(RecursiveError::root("getting mls groups"))?;
89 let conversation =
90 MlsConversation::from_welcome_message(welcome, configuration, &mls_provider, mls_groups.borrow_mut())
91 .await?;
92
93 let crl_new_distribution_points = get_new_crl_distribution_points(
95 &mls_provider,
96 extract_crl_uris_from_group(&conversation.group)
97 .map_err(RecursiveError::mls_credential("extracting crl uris from group"))?,
98 )
99 .await
100 .map_err(RecursiveError::mls_credential("getting new crl distribution points"))?;
101
102 let id = conversation.id.clone();
103 mls_groups.insert(id.clone(), conversation);
104
105 Ok(WelcomeBundle {
106 id,
107 crl_new_distribution_points,
108 })
109 }
110}
111
112impl MlsConversation {
113 async fn from_welcome_message(
124 welcome: Welcome,
125 configuration: MlsConversationConfiguration,
126 backend: &MlsCryptoProvider,
127 mls_groups: &mut GroupStore<MlsConversation>,
128 ) -> Result<Self> {
129 let mls_group_config = configuration.as_openmls_default_configuration()?;
130
131 let group = MlsGroup::new_from_welcome(backend, &mls_group_config, welcome, None).await;
132
133 let group = match group {
134 Err(openmls::prelude::WelcomeError::NoMatchingKeyPackage)
135 | Err(openmls::prelude::WelcomeError::NoMatchingEncryptionKey) => return Err(Error::OrphanWelcome),
136 _ => group.map_err(MlsError::wrap("group could not be created from welcome"))?,
137 };
138
139 let id = ConversationId::from(group.group_id().as_slice());
140 let existing_conversation = mls_groups.get_fetch(&id[..], &backend.keystore(), None).await;
141 let conversation_exists = existing_conversation.ok().flatten().is_some();
142
143 let pending_group = backend.key_store().find::<PersistedMlsPendingGroup>(&id[..]).await;
144 let pending_group_exists = pending_group.ok().flatten().is_some();
145
146 if conversation_exists || pending_group_exists {
147 return Err(LeafError::ConversationAlreadyExists(id).into());
148 }
149
150 Self::from_mls_group(group, configuration, backend).await
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use wasm_bindgen_test::*;
157
158 use crate::test_utils::*;
159
160 use super::*;
161
162 wasm_bindgen_test_configure!(run_in_browser);
163
164 #[apply(all_cred_cipher)]
165 #[wasm_bindgen_test]
166 async fn joining_from_welcome_should_prune_local_key_material(case: TestCase) {
167 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
168 Box::pin(async move {
169 let id = conversation_id();
170 let bob = bob_central.rand_key_package(&case).await;
172 let prev_count = bob_central.context.count_entities().await;
174
175 alice_central
177 .context
178 .new_conversation(&id, case.credential_type, case.cfg.clone())
179 .await
180 .unwrap();
181
182 alice_central
183 .context
184 .conversation(&id)
185 .await
186 .unwrap()
187 .add_members(vec![bob])
188 .await
189 .unwrap();
190
191 let welcome = alice_central.mls_transport.latest_welcome_message().await;
192 bob_central
194 .context
195 .process_welcome_message(welcome.into(), case.custom_cfg())
196 .await
197 .unwrap();
198
199 let next_count = bob_central.context.count_entities().await;
201 assert_eq!(next_count.key_package, prev_count.key_package - 1);
202 assert_eq!(next_count.hpke_private_key, prev_count.hpke_private_key - 1);
203 assert_eq!(next_count.encryption_keypair, prev_count.encryption_keypair - 1);
204 })
205 })
206 .await;
207 }
208
209 #[apply(all_cred_cipher)]
210 #[wasm_bindgen_test]
211 async fn process_welcome_should_fail_when_already_exists(case: TestCase) {
212 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
213 Box::pin(async move {
214 let id = conversation_id();
215 alice_central
216 .context
217 .new_conversation(&id, case.credential_type, case.cfg.clone())
218 .await
219 .unwrap();
220 let bob = bob_central.rand_key_package(&case).await;
221 alice_central
222 .context
223 .conversation(&id)
224 .await
225 .unwrap()
226 .add_members(vec![bob])
227 .await
228 .unwrap();
229
230 let welcome = alice_central.mls_transport.latest_welcome_message().await;
231 bob_central
233 .context
234 .new_conversation(&id, case.credential_type, case.cfg.clone())
235 .await
236 .unwrap();
237 let join_welcome = bob_central
238 .context
239 .process_welcome_message(welcome.into(), case.custom_cfg())
240 .await;
241 assert!(
242 matches!(join_welcome.unwrap_err(), Error::Leaf(LeafError::ConversationAlreadyExists(i)) if i == id)
243 );
244 })
245 })
246 .await;
247 }
248}