core_crypto/mls/conversation/
welcome.rs1use std::borrow::BorrowMut;
2
3use crate::context::CentralContext;
4use crate::{
5 e2e_identity::init_certificates::NewCrlDistributionPoint,
6 group_store::GroupStore,
7 mls::credential::crl::{extract_crl_uris_from_group, get_new_crl_distribution_points},
8 prelude::{
9 ConversationId, CryptoError, CryptoResult, MlsConversation, MlsConversationConfiguration,
10 MlsCustomConfiguration, MlsError,
11 },
12};
13use core_crypto_keystore::{connection::FetchFromDatabase, entities::PersistedMlsPendingGroup};
14use mls_crypto_provider::MlsCryptoProvider;
15use openmls::prelude::{MlsGroup, MlsMessageIn, MlsMessageInBody, Welcome};
16use openmls_traits::OpenMlsCryptoProvider;
17use tls_codec::Deserialize;
18
19#[derive(Debug)]
21pub struct WelcomeBundle {
22 pub id: ConversationId,
24 pub crl_new_distribution_points: NewCrlDistributionPoint,
26}
27
28impl CentralContext {
29 #[cfg_attr(test, crate::dispotent)]
41 pub async fn process_raw_welcome_message(
42 &self,
43 welcome: Vec<u8>,
44 custom_cfg: MlsCustomConfiguration,
45 ) -> CryptoResult<WelcomeBundle> {
46 let mut cursor = std::io::Cursor::new(welcome);
47 let welcome = MlsMessageIn::tls_deserialize(&mut cursor).map_err(MlsError::from)?;
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 ) -> CryptoResult<WelcomeBundle> {
70 let welcome = match welcome.extract() {
71 MlsMessageInBody::Welcome(welcome) => welcome,
72 _ => return Err(CryptoError::ConsumerError),
73 };
74 let cs = welcome.ciphersuite().into();
75 let configuration = MlsConversationConfiguration {
76 ciphersuite: cs,
77 custom: custom_cfg,
78 ..Default::default()
79 };
80 let mls_provider = self.mls_provider().await?;
81 let mut mls_groups = self.mls_groups().await?;
82 let conversation =
83 MlsConversation::from_welcome_message(welcome, configuration, &mls_provider, mls_groups.borrow_mut())
84 .await?;
85
86 let crl_new_distribution_points =
88 get_new_crl_distribution_points(&mls_provider, extract_crl_uris_from_group(&conversation.group)?).await?;
89
90 let id = conversation.id.clone();
91 mls_groups.insert(id.clone(), conversation);
92
93 Ok(WelcomeBundle {
94 id,
95 crl_new_distribution_points,
96 })
97 }
98}
99
100impl MlsConversation {
101 async fn from_welcome_message(
112 welcome: Welcome,
113 configuration: MlsConversationConfiguration,
114 backend: &MlsCryptoProvider,
115 mls_groups: &mut GroupStore<MlsConversation>,
116 ) -> CryptoResult<Self> {
117 let mls_group_config = configuration.as_openmls_default_configuration()?;
118
119 let group = MlsGroup::new_from_welcome(backend, &mls_group_config, welcome, None).await;
120
121 let group = match group {
122 Err(openmls::prelude::WelcomeError::NoMatchingKeyPackage)
123 | Err(openmls::prelude::WelcomeError::NoMatchingEncryptionKey) => return Err(CryptoError::OrphanWelcome),
124 _ => group.map_err(MlsError::from)?,
125 };
126
127 let id = ConversationId::from(group.group_id().as_slice());
128 let existing_conversation = mls_groups.get_fetch(&id[..], &backend.keystore(), None).await;
129 let conversation_exists = existing_conversation.ok().flatten().is_some();
130
131 let pending_group = backend.key_store().find::<PersistedMlsPendingGroup>(&id[..]).await;
132 let pending_group_exists = pending_group.ok().flatten().is_some();
133
134 if conversation_exists || pending_group_exists {
135 return Err(CryptoError::ConversationAlreadyExists(id));
136 }
137
138 Self::from_mls_group(group, configuration, backend).await
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use wasm_bindgen_test::*;
145
146 use crate::{prelude::MlsConversationCreationMessage, test_utils::*};
147
148 use super::*;
149
150 wasm_bindgen_test_configure!(run_in_browser);
151
152 #[apply(all_cred_cipher)]
153 #[wasm_bindgen_test]
154 async fn joining_from_welcome_should_prune_local_key_material(case: TestCase) {
155 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
156 Box::pin(async move {
157 let id = conversation_id();
158 let bob = bob_central.rand_key_package(&case).await;
160 let prev_count = bob_central.context.count_entities().await;
162
163 alice_central
165 .context
166 .new_conversation(&id, case.credential_type, case.cfg.clone())
167 .await
168 .unwrap();
169
170 let MlsConversationCreationMessage { welcome, .. } = alice_central
171 .context
172 .add_members_to_conversation(&id, vec![bob])
173 .await
174 .unwrap();
175
176 bob_central
178 .context
179 .process_welcome_message(welcome.into(), case.custom_cfg())
180 .await
181 .unwrap();
182
183 let next_count = bob_central.context.count_entities().await;
185 assert_eq!(next_count.key_package, prev_count.key_package - 1);
186 assert_eq!(next_count.hpke_private_key, prev_count.hpke_private_key - 1);
187 assert_eq!(next_count.encryption_keypair, prev_count.encryption_keypair - 1);
188 })
189 })
190 .await;
191 }
192
193 #[apply(all_cred_cipher)]
194 #[wasm_bindgen_test]
195 async fn process_welcome_should_fail_when_already_exists(case: TestCase) {
196 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
197 Box::pin(async move {
198 let id = conversation_id();
199 alice_central
200 .context
201 .new_conversation(&id, case.credential_type, case.cfg.clone())
202 .await
203 .unwrap();
204 let bob = bob_central.rand_key_package(&case).await;
205 let welcome = alice_central
206 .context
207 .add_members_to_conversation(&id, vec![bob])
208 .await
209 .unwrap()
210 .welcome;
211
212 bob_central
214 .context
215 .new_conversation(&id, case.credential_type, case.cfg.clone())
216 .await
217 .unwrap();
218 let join_welcome = bob_central
219 .context
220 .process_welcome_message(welcome.into(), case.custom_cfg())
221 .await;
222 assert!(matches!(join_welcome.unwrap_err(), CryptoError::ConversationAlreadyExists(i) if i == id));
223 })
224 })
225 .await;
226 }
227}