core_crypto/transaction_context/conversation/
welcome.rs1use std::borrow::BorrowMut as _;
4
5use super::{Error, Result, TransactionContext};
6use crate::{
7 RecursiveError,
8 mls::credential::crl::{extract_crl_uris_from_group, get_new_crl_distribution_points},
9 prelude::{MlsConversation, MlsConversationConfiguration, MlsCustomConfiguration, WelcomeBundle},
10};
11use openmls::prelude::{MlsMessageIn, MlsMessageInBody};
12use tls_codec::Deserialize as _;
13
14impl TransactionContext {
15 #[cfg_attr(test, crate::dispotent)]
27 pub async fn process_raw_welcome_message(
28 &self,
29 welcome: Vec<u8>,
30 custom_cfg: MlsCustomConfiguration,
31 ) -> Result<WelcomeBundle> {
32 let mut cursor = std::io::Cursor::new(welcome);
33 let welcome =
34 MlsMessageIn::tls_deserialize(&mut cursor).map_err(Error::tls_deserialize("mls message in (welcome)"))?;
35 self.process_welcome_message(welcome, custom_cfg).await
36 }
37
38 #[cfg_attr(test, crate::dispotent)]
52 pub async fn process_welcome_message(
53 &self,
54 welcome: MlsMessageIn,
55 custom_cfg: MlsCustomConfiguration,
56 ) -> Result<WelcomeBundle> {
57 let MlsMessageInBody::Welcome(welcome) = welcome.extract() else {
58 return Err(Error::CallerError(
59 "the message provided to process_welcome_message was not a welcome message",
60 ));
61 };
62 let cs = welcome.ciphersuite().into();
63 let configuration = MlsConversationConfiguration {
64 ciphersuite: cs,
65 custom: custom_cfg,
66 ..Default::default()
67 };
68 let mls_provider = self
69 .mls_provider()
70 .await
71 .map_err(RecursiveError::transaction("getting mls provider"))?;
72 let mut mls_groups = self
73 .mls_groups()
74 .await
75 .map_err(RecursiveError::transaction("getting mls groups"))?;
76 let conversation =
77 MlsConversation::from_welcome_message(welcome, configuration, &mls_provider, mls_groups.borrow_mut())
78 .await
79 .map_err(RecursiveError::mls_conversation("creating conversation from welcome"))?;
80
81 let crl_new_distribution_points = get_new_crl_distribution_points(
83 &mls_provider,
84 extract_crl_uris_from_group(&conversation.group)
85 .map_err(RecursiveError::mls_credential("extracting crl uris from group"))?,
86 )
87 .await
88 .map_err(RecursiveError::mls_credential("getting new crl distribution points"))?;
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
100#[cfg(test)]
101mod tests {
102 use wasm_bindgen_test::*;
103
104 use crate::test_utils::*;
105
106 use super::*;
107
108 wasm_bindgen_test_configure!(run_in_browser);
109
110 #[apply(all_cred_cipher)]
111 #[wasm_bindgen_test]
112 async fn joining_from_welcome_should_prune_local_key_material(case: TestCase) {
113 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
114 Box::pin(async move {
115 let id = conversation_id();
116 let bob = bob_central.rand_key_package(&case).await;
118 let prev_count = bob_central.context.count_entities().await;
120
121 alice_central
123 .context
124 .new_conversation(&id, case.credential_type, case.cfg.clone())
125 .await
126 .unwrap();
127
128 alice_central
129 .context
130 .conversation(&id)
131 .await
132 .unwrap()
133 .add_members(vec![bob])
134 .await
135 .unwrap();
136
137 let welcome = alice_central.mls_transport.latest_welcome_message().await;
138 bob_central
140 .context
141 .process_welcome_message(welcome.into(), case.custom_cfg())
142 .await
143 .unwrap();
144
145 let next_count = bob_central.context.count_entities().await;
147 assert_eq!(next_count.key_package, prev_count.key_package - 1);
148 assert_eq!(next_count.hpke_private_key, prev_count.hpke_private_key - 1);
149 assert_eq!(next_count.encryption_keypair, prev_count.encryption_keypair - 1);
150 })
151 })
152 .await;
153 }
154
155 #[apply(all_cred_cipher)]
156 #[wasm_bindgen_test]
157 async fn process_welcome_should_fail_when_already_exists(case: TestCase) {
158 use crate::LeafError;
159
160 run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
161 Box::pin(async move {
162 let id = conversation_id();
163 alice_central
164 .context
165 .new_conversation(&id, case.credential_type, case.cfg.clone())
166 .await
167 .unwrap();
168 let bob = bob_central.rand_key_package(&case).await;
169 alice_central
170 .context
171 .conversation(&id)
172 .await
173 .unwrap()
174 .add_members(vec![bob])
175 .await
176 .unwrap();
177
178 let welcome = alice_central.mls_transport.latest_welcome_message().await;
179 bob_central
181 .context
182 .new_conversation(&id, case.credential_type, case.cfg.clone())
183 .await
184 .unwrap();
185 let join_welcome = bob_central
186 .context
187 .process_welcome_message(welcome.into(), case.custom_cfg())
188 .await;
189 assert!(
190 matches!(join_welcome.unwrap_err(),
191 Error::Recursive(crate::RecursiveError::MlsConversation { source, .. })
192 if matches!(*source, crate::mls::conversation::Error::Leaf(LeafError::ConversationAlreadyExists(ref i)) if i == &id
193 )
194 )
195 );
196 })
197 })
198 .await;
199 }
200}