core_crypto/mls/conversation/conversation_guard/
encrypt.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! The methods in this module are concerned with message encryption.

use super::ConversationGuard;
use super::Result;
use crate::MlsError;
use crate::mls::conversation::ConversationWithMls as _;
use openmls::prelude::MlsMessageOutBody;

impl ConversationGuard {
    /// Encrypts a raw payload then serializes it to the TLS wire format
    /// Can only be called when there is no pending commit and no pending proposal.
    ///
    /// # Arguments
    /// * `message` - the message as a byte array
    ///
    /// # Return type
    /// This method will return an encrypted TLS serialized message.
    ///
    /// # Errors
    /// If the conversation can't be found, an error will be returned. Other errors are originating
    /// from OpenMls and the KeyStore
    pub async fn encrypt_message(&mut self, message: impl AsRef<[u8]>) -> Result<Vec<u8>> {
        let backend = self.mls_provider().await?;
        let credential = self.credential_bundle().await?;
        let signer = credential.signature_key();
        let mut inner = self.conversation_mut().await;
        let encrypted = inner
            .group
            .create_message(&backend, signer, message.as_ref())
            .map_err(MlsError::wrap("creating message"))?;

        // make sure all application messages are encrypted
        debug_assert!(matches!(encrypted.body, MlsMessageOutBody::PrivateMessage(_)));

        let encrypted = encrypted
            .to_bytes()
            .map_err(MlsError::wrap("constructing byte vector of encrypted message"))?;

        inner.persist_group_when_changed(&backend.keystore(), false).await?;
        Ok(encrypted)
    }
}

#[cfg(test)]
mod tests {
    use wasm_bindgen_test::*;

    use crate::test_utils::*;

    wasm_bindgen_test_configure!(run_in_browser);

    #[apply(all_cred_cipher)]
    #[wasm_bindgen_test]
    async fn can_encrypt_app_message(case: TestCase) {
        run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
            Box::pin(async move {
                let id = conversation_id();
                alice_central
                    .context
                    .new_conversation(&id, case.credential_type, case.cfg.clone())
                    .await
                    .unwrap();
                alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();

                let msg = b"Hello bob";
                let encrypted = alice_central
                    .context
                    .conversation(&id)
                    .await
                    .unwrap()
                    .encrypt_message(msg)
                    .await
                    .unwrap();
                assert_ne!(&msg[..], &encrypted[..]);
                let decrypted = bob_central
                    .context
                    .conversation(&id)
                    .await
                    .unwrap()
                    .decrypt_message(encrypted)
                    .await
                    .unwrap()
                    .app_msg
                    .unwrap();
                assert_eq!(&decrypted[..], &msg[..]);
            })
        })
        .await
    }

    // Ensures encrypting an application message is durable
    #[apply(all_cred_cipher)]
    #[wasm_bindgen_test]
    async fn can_encrypt_consecutive_messages(case: TestCase) {
        run_test_with_client_ids(case.clone(), ["alice", "bob"], move |[alice_central, bob_central]| {
            Box::pin(async move {
                let id = conversation_id();
                alice_central
                    .context
                    .new_conversation(&id, case.credential_type, case.cfg.clone())
                    .await
                    .unwrap();
                alice_central.invite_all(&case, &id, [&bob_central]).await.unwrap();
                let mut alice_conversation = alice_central.context.conversation(&id).await.unwrap();

                let msg = b"Hello bob";
                let encrypted = alice_conversation.encrypt_message(msg).await.unwrap();
                assert_ne!(&msg[..], &encrypted[..]);
                let decrypted = bob_central
                    .context
                    .conversation(&id)
                    .await
                    .unwrap()
                    .decrypt_message(encrypted)
                    .await
                    .unwrap()
                    .app_msg
                    .unwrap();
                assert_eq!(&decrypted[..], &msg[..]);

                let msg = b"Hello bob again";
                let encrypted = alice_conversation.encrypt_message(msg).await.unwrap();
                assert_ne!(&msg[..], &encrypted[..]);
                let decrypted = bob_central
                    .context
                    .conversation(&id)
                    .await
                    .unwrap()
                    .decrypt_message(encrypted)
                    .await
                    .unwrap()
                    .app_msg
                    .unwrap();
                assert_eq!(&decrypted[..], &msg[..]);
            })
        })
        .await
    }
}