core_crypto/mls/conversation/
encrypt.rs

1//! Application messages are actual text messages user exchange. In MLS they can only be encrypted.
2//!
3//! This table summarizes when a MLS group can create an application message:
4//!
5//! | can encrypt ?     | 0 pend. Commit | 1 pend. Commit |
6//! |-------------------|----------------|----------------|
7//! | 0 pend. Proposal  | ✅              | ❌              |
8//! | 1+ pend. Proposal | ❌              | ❌              |
9
10use mls_crypto_provider::MlsCryptoProvider;
11use openmls::prelude::MlsMessageOutBody;
12
13use super::MlsConversation;
14use crate::context::CentralContext;
15use crate::prelude::Client;
16use crate::{mls::ConversationId, CryptoError, CryptoResult, MlsError};
17
18/// Abstraction over a MLS group capable of encrypting a MLS message
19impl MlsConversation {
20    /// see [CentralContext::encrypt_message]
21    /// It is durable because encrypting increments the message generation
22    #[cfg_attr(test, crate::durable)]
23    pub async fn encrypt_message(
24        &mut self,
25        client: &Client,
26        message: impl AsRef<[u8]>,
27        backend: &MlsCryptoProvider,
28    ) -> CryptoResult<Vec<u8>> {
29        let signer = &self
30            .find_current_credential_bundle(client)
31            .await
32            .map_err(|_| CryptoError::IdentityInitializationError)?
33            .signature_key;
34        let encrypted = self
35            .group
36            .create_message(backend, signer, message.as_ref())
37            .map_err(MlsError::from)?;
38
39        // make sure all application messages are encrypted
40        debug_assert!(matches!(encrypted.body, MlsMessageOutBody::PrivateMessage(_)));
41
42        let encrypted = encrypted.to_bytes().map_err(MlsError::from)?;
43
44        self.persist_group_when_changed(&backend.keystore(), false).await?;
45        Ok(encrypted)
46    }
47}
48
49impl CentralContext {
50    /// Encrypts a raw payload then serializes it to the TLS wire format
51    ///
52    /// # Arguments
53    /// * `conversation` - the group/conversation id
54    /// * `message` - the message as a byte array
55    ///
56    /// # Return type
57    /// This method will return an encrypted TLS serialized message.
58    ///
59    /// # Errors
60    /// If the conversation can't be found, an error will be returned. Other errors are originating
61    /// from OpenMls and the KeyStore
62    #[cfg_attr(test, crate::idempotent)]
63    pub async fn encrypt_message(
64        &self,
65        conversation: &ConversationId,
66        message: impl AsRef<[u8]>,
67    ) -> CryptoResult<Vec<u8>> {
68        let client = self.mls_client().await?;
69        self.get_conversation(conversation)
70            .await?
71            .write()
72            .await
73            .encrypt_message(&client, message, &self.mls_provider().await?)
74            .await
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use wasm_bindgen_test::*;
81
82    use crate::test_utils::*;
83
84    wasm_bindgen_test_configure!(run_in_browser);
85
86    #[apply(all_cred_cipher)]
87    #[wasm_bindgen_test]
88    async fn can_encrypt_app_message(case: TestCase) {
89        run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
90            Box::pin(async move {
91                let id = conversation_id();
92                alice_central
93                    .context
94                    .new_conversation(&id, case.credential_type, case.cfg.clone())
95                    .await
96                    .unwrap();
97                alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
98
99                let msg = b"Hello bob";
100                let encrypted = alice_central.context.encrypt_message(&id, msg).await.unwrap();
101                assert_ne!(&msg[..], &encrypted[..]);
102                let decrypted = bob_central
103                    .context
104                    .decrypt_message(&id, encrypted)
105                    .await
106                    .unwrap()
107                    .app_msg
108                    .unwrap();
109                assert_eq!(&decrypted[..], &msg[..]);
110            })
111        })
112        .await
113    }
114
115    // Ensures encrypting an application message is durable
116    #[apply(all_cred_cipher)]
117    #[wasm_bindgen_test]
118    async fn can_encrypt_consecutive_messages(case: TestCase) {
119        run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
120            Box::pin(async move {
121                let id = conversation_id();
122                alice_central
123                    .context
124                    .new_conversation(&id, case.credential_type, case.cfg.clone())
125                    .await
126                    .unwrap();
127                alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
128
129                let msg = b"Hello bob";
130                let encrypted = alice_central.context.encrypt_message(&id, msg).await.unwrap();
131                assert_ne!(&msg[..], &encrypted[..]);
132                let decrypted = bob_central
133                    .context
134                    .decrypt_message(&id, encrypted)
135                    .await
136                    .unwrap()
137                    .app_msg
138                    .unwrap();
139                assert_eq!(&decrypted[..], &msg[..]);
140
141                let msg = b"Hello bob again";
142                let encrypted = alice_central.context.encrypt_message(&id, msg).await.unwrap();
143                assert_ne!(&msg[..], &encrypted[..]);
144                let decrypted = bob_central
145                    .context
146                    .decrypt_message(&id, encrypted)
147                    .await
148                    .unwrap()
149                    .app_msg
150                    .unwrap();
151                assert_eq!(&decrypted[..], &msg[..]);
152            })
153        })
154        .await
155    }
156}