Skip to main content

core_crypto/transaction_context/conversation/
welcome.rs

1//! This module contains transactional conversation operations that are related to processing welcome messages.
2
3use std::borrow::BorrowMut as _;
4
5use openmls::prelude::{MlsMessageIn, MlsMessageInBody};
6
7use super::{Error, Result, TransactionContext};
8use crate::{ConversationId, MlsConversation, MlsConversationConfiguration, RecursiveError};
9
10impl TransactionContext {
11    /// Create a conversation from a received MLS Welcome message
12    ///
13    /// # Arguments
14    /// * `welcome` - a `Welcome` message received as a result of a commit adding new members to a group
15    ///
16    /// # Return type
17    /// This function will return the conversation/group id
18    ///
19    /// # Errors
20    /// Errors can be originating from the KeyStore of from OpenMls:
21    /// * if no [openmls::key_packages::KeyPackage] can be read from the KeyStore
22    /// * if the message can't be decrypted
23    #[cfg_attr(test, crate::dispotent)]
24    pub async fn process_welcome_message(&self, welcome: impl Into<MlsMessageIn>) -> Result<ConversationId> {
25        let database = &self.database().await?;
26        let MlsMessageInBody::Welcome(welcome) = welcome.into().extract() else {
27            return Err(Error::CallerError(
28                "the message provided to process_welcome_message was not a welcome message",
29            ));
30        };
31        let cs = welcome.ciphersuite().into();
32        let configuration = MlsConversationConfiguration {
33            ciphersuite: cs,
34            ..Default::default()
35        };
36        let mls_provider = self
37            .mls_provider()
38            .await
39            .map_err(RecursiveError::transaction("getting mls provider"))?;
40        let mut mls_groups = self
41            .mls_groups()
42            .await
43            .map_err(RecursiveError::transaction("getting mls groups"))?;
44        let conversation = MlsConversation::from_welcome_message(
45            welcome,
46            configuration,
47            &mls_provider,
48            database,
49            mls_groups.borrow_mut(),
50        )
51        .await
52        .map_err(RecursiveError::mls_conversation("creating conversation from welcome"))?;
53
54        let id = conversation.id.clone();
55        mls_groups.insert(&id, conversation);
56
57        Ok(id)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use crate::test_utils::*;
65
66    #[apply(all_cred_cipher)]
67    async fn joining_from_welcome_should_prune_local_key_material(case: TestContext) {
68        let [alice, bob] = case.sessions().await;
69        Box::pin(async move {
70            // has to be before the original key_package count because it creates one
71            // Create a conversation from alice, where she invites bob
72            let commit_guard = case.create_conversation([&alice]).await.invite([&bob]).await;
73
74            // Keep track of the whatever amount was initially generated
75            let prev_count = bob.transaction.count_entities().await;
76            // Bob accepts the welcome message, and as such, it should prune the used keypackage from the store
77            commit_guard.notify_members().await;
78
79            // Ensure we're left with 1 less keypackage bundle in the store, because it was consumed with the OpenMLS
80            // Welcome message
81            let next_count = bob.transaction.count_entities().await;
82            assert_eq!(next_count.key_package, prev_count.key_package - 1);
83            assert_eq!(next_count.hpke_private_key, prev_count.hpke_private_key - 1);
84            assert_eq!(next_count.encryption_keypair, prev_count.encryption_keypair - 1);
85        })
86        .await;
87    }
88
89    #[apply(all_cred_cipher)]
90    async fn process_welcome_should_fail_when_already_exists(case: TestContext) {
91        use crate::LeafError;
92
93        let [alice, bob] = case.sessions().await;
94        Box::pin(async move {
95            let credential_ref = &bob.initial_credential;
96            let commit = case.create_conversation([&alice]).await.invite([&bob]).await;
97            let conversation = commit.conversation();
98            let id = conversation.id().clone();
99                // Meanwhile Bob creates a conversation with the exact same id as the one he's trying to join
100                bob
101                    .transaction
102                    .new_conversation(&id, credential_ref, case.cfg.clone())
103                    .await
104                    .unwrap();
105
106                let welcome = conversation.transport().await.latest_welcome_message().await;
107                let join_welcome = bob
108                    .transaction
109                    .process_welcome_message(welcome)
110                    .await;
111                assert!(
112                    matches!(join_welcome.unwrap_err(),
113                    Error::Recursive(crate::RecursiveError::MlsConversation { source, .. })
114                        if matches!(*source, crate::mls::conversation::Error::Leaf(LeafError::ConversationAlreadyExists(ref i)) if i == &id
115                        )
116                    )
117                );
118            })
119        .await;
120    }
121}