core_crypto/transaction_context/conversation/
welcome.rs1use openmls::prelude::{MlsMessageIn, MlsMessageInBody};
4
5use super::{Error, Result, TransactionContext};
6use crate::{ConversationConfiguration, ConversationId, KeystoreError};
7
8impl TransactionContext {
9 #[cfg_attr(test, crate::dispotent)]
22 pub async fn process_welcome_message(&self, welcome: impl Into<MlsMessageIn>) -> Result<ConversationId> {
23 let MlsMessageInBody::Welcome(welcome) = welcome.into().extract() else {
24 return Err(Error::CallerError(
25 "the message provided to process_welcome_message was not a welcome message",
26 ));
27 };
28
29 let configuration = ConversationConfiguration {
30 cipher_suite: welcome.ciphersuite().into(),
31 ..Default::default()
32 };
33
34 let inner = self.inner().await?;
35 let conversation = inner
36 .transaction()
37 .with_savepoint(
38 "process_welcome_message_savepoint",
39 async || {
40 self.persist_conversation_from_welcome_message(welcome, configuration)
41 .await
42 },
43 |context| Box::new(move |err| KeystoreError::wrap(context)(err).into()),
44 )
45 .await?;
46
47 let id = conversation.id().to_owned();
48
49 Ok(id)
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::test_utils::*;
56
57 #[apply(all_cred_cipher)]
58 async fn joining_from_welcome_should_prune_local_key_material(case: TestContext) {
59 let [alice, bob] = case.sessions().await;
60 Box::pin(async move {
61 let commit_guard = case.create_conversation([&alice]).await.invite([&bob]).await;
64
65 let prev_count = bob.transaction.count_entities().await;
67 commit_guard.notify_members().await;
69
70 let next_count = bob.transaction.count_entities().await;
73 assert_eq!(next_count.key_package, prev_count.key_package - 1);
74 assert_eq!(next_count.hpke_private_key, prev_count.hpke_private_key - 1);
75 assert_eq!(next_count.encryption_keypair, prev_count.encryption_keypair - 1);
76 })
77 .await;
78 }
79
80 #[apply(all_cred_cipher)]
81 async fn process_welcome_should_fail_when_already_exists(case: TestContext) {
82 use crate::LeafError;
83
84 let [alice, mut bob] = case.sessions().await;
85 Box::pin(async move {
86 let credential_ref = &bob.initial_credential;
87 let commit = case.create_conversation([&alice]).await.invite([&bob]).await;
88 let conversation = commit.conversation();
89 let id = conversation.id().clone();
90 bob
92 .transaction
93 .new_conversation(&id, credential_ref, case.cfg.clone())
94 .await
95 .unwrap();
96
97 let key_package_refs_before = bob.transaction.get_key_package_refs().await.unwrap();
99
100 let welcome = conversation.transport().await.latest_welcome_message().await;
101
102 bob.commit_transaction().await;
106
107 let join_welcome = bob
108 .transaction
109 .process_welcome_message(welcome)
110 .await;
111
112 let key_package_refs_after = bob.transaction.get_key_package_refs().await.unwrap();
114
115 assert!(!key_package_refs_before.is_empty());
116 assert_eq!(key_package_refs_before, key_package_refs_after);
117 assert!(innermost_source_matches!(join_welcome.unwrap_err(), LeafError::ConversationAlreadyExists(i) if i == &id));
118 })
119 .await;
120 }
121
122 #[apply(all_cred_cipher)]
130 async fn failed_welcome_should_restore_key_material_in_same_transaction(case: TestContext) {
131 let [alice, bob] = case.sessions().await;
132 let credential_ref = &bob.initial_credential;
133 let commit = case.create_conversation([&alice]).await.invite([&bob]).await;
134 let conversation = commit.conversation();
135 let id = conversation.id().clone();
136
137 bob.transaction
140 .new_conversation(&id, credential_ref, case.cfg.clone())
141 .await
142 .unwrap();
143
144 let welcome = conversation.transport().await.latest_welcome_message().await;
145
146 let count_before = bob.transaction.count_entities().await;
147 let key_package_refs_before = bob.transaction.get_key_package_refs().await.unwrap();
148 assert!(!key_package_refs_before.is_empty());
149
150 let join_welcome = bob.transaction.process_welcome_message(welcome).await;
151 assert!(innermost_source_matches!(
152 join_welcome.unwrap_err(),
153 crate::LeafError::ConversationAlreadyExists(i) if i == &id
154 ));
155
156 let count_after = bob.transaction.count_entities().await;
159 assert_eq!(count_before, count_after);
160 let key_package_refs_after = bob.transaction.get_key_package_refs().await.unwrap();
161 assert_eq!(key_package_refs_before, key_package_refs_after);
162 }
163
164 #[apply(all_cred_cipher)]
168 async fn restored_key_material_should_still_be_able_to_join_from_welcome(case: TestContext) {
169 let [alice, bob] = case.sessions().await;
170 let credential_ref = &bob.initial_credential;
171 let commit = case.create_conversation([&alice]).await.invite([&bob]).await;
172 let conversation = commit.conversation();
173 let id = conversation.id().clone();
174
175 bob.transaction
177 .new_conversation(&id, credential_ref, case.cfg.clone())
178 .await
179 .unwrap();
180
181 let welcome = conversation.transport().await.latest_welcome_message().await;
182
183 let join_welcome = bob.transaction.process_welcome_message(welcome.clone()).await;
184 assert!(innermost_source_matches!(
185 join_welcome.unwrap_err(),
186 crate::LeafError::ConversationAlreadyExists(i) if i == &id
187 ));
188
189 bob.transaction.conversation(&id).await.unwrap().wipe().await.unwrap();
191
192 let joined_id = bob.transaction.process_welcome_message(welcome).await.unwrap();
194 assert_eq!(joined_id, id);
195
196 let message = bob
198 .transaction
199 .conversation(&id)
200 .await
201 .unwrap()
202 .encrypt_message(b"hello")
203 .await
204 .unwrap();
205 let decrypted = conversation
206 .guard_of(&alice)
207 .await
208 .decrypt_message(&message)
209 .await
210 .unwrap();
211 assert_eq!(decrypted.as_text().unwrap().plaintext, b"hello");
212 }
213}