interop/clients/corecrypto/
web.rs

1// Wire
2// Copyright (C) 2022 Wire Swiss GmbH
3
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see http://www.gnu.org/licenses/.
16
17use crate::{
18    CIPHERSUITE_IN_USE,
19    clients::{EmulatedClient, EmulatedClientProtocol, EmulatedClientType, EmulatedMlsClient},
20};
21use color_eyre::eyre::Result;
22use core_crypto::prelude::{KeyPackage, KeyPackageIn};
23use std::cell::Cell;
24use std::net::SocketAddr;
25use tls_codec::Deserialize;
26
27#[derive(Debug)]
28pub(crate) struct CoreCryptoWebClient {
29    browser: fantoccini::Client,
30    client_id: Vec<u8>,
31    #[cfg(feature = "proteus")]
32    prekey_last_id: Cell<u16>,
33}
34
35impl CoreCryptoWebClient {
36    pub(crate) async fn new(driver_addr: &SocketAddr) -> Result<Self> {
37        let client_id = uuid::Uuid::new_v4();
38        let client_id_str = client_id.as_hyphenated().to_string();
39        let ciphersuite = CIPHERSUITE_IN_USE as u16;
40        let client_config = serde_json::json!({
41            "databaseName": format!("db-{client_id_str}"),
42            "key": "test",
43            "ciphersuites": [ciphersuite],
44            "clientId": client_id_str
45        });
46        let browser = crate::build::web::webdriver::setup_browser(driver_addr, "core-crypto").await?;
47
48        let _ = browser
49            .execute_async(
50                r#"
51const [clientConfig, callback] = arguments;
52const { CoreCrypto, Ciphersuite, CredentialType } = await import("./corecrypto.js");
53window.CoreCrypto = CoreCrypto;
54window.cc = await window.CoreCrypto.init(clientConfig);
55window.ciphersuite = Ciphersuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
56window.credentialType = CredentialType.Basic;
57
58window.deliveryService = {
59    async sendCommitBundle() {
60        return "success";
61    },
62    async sendMessage() {
63        return "success";
64    },
65};
66
67await window.cc.provideTransport(window.deliveryService);
68
69callback();"#,
70                vec![client_config],
71            )
72            .await?;
73
74        Ok(Self {
75            browser,
76            client_id: client_id.into_bytes().into(),
77            #[cfg(feature = "proteus")]
78            prekey_last_id: Cell::new(0),
79        })
80    }
81}
82
83#[async_trait::async_trait(?Send)]
84impl EmulatedClient for CoreCryptoWebClient {
85    fn client_name(&self) -> &str {
86        "CoreCrypto::wasm"
87    }
88
89    fn client_type(&self) -> EmulatedClientType {
90        EmulatedClientType::Web
91    }
92
93    fn client_id(&self) -> &[u8] {
94        self.client_id.as_slice()
95    }
96
97    fn client_protocol(&self) -> EmulatedClientProtocol {
98        EmulatedClientProtocol::MLS | EmulatedClientProtocol::PROTEUS
99    }
100
101    async fn wipe(mut self) -> Result<()> {
102        let client_id = uuid::Uuid::from_slice(self.client_id.as_slice())?;
103        let client_id_str = client_id.as_hyphenated().to_string();
104        let database_name = format!("db-{client_id_str}");
105        let _ = self
106            .browser
107            .execute_async(
108                r#"
109    const [databaseName, callback] = arguments;
110    await window.cc.close();
111    const result = window.indexedDB.deleteDatabase(databaseName);
112    result.onsuccess = callback;
113    result.onfailure = callback;"#,
114                vec![serde_json::json!(database_name)],
115            )
116            .await?;
117
118        Ok(())
119    }
120}
121
122#[async_trait::async_trait(?Send)]
123impl EmulatedMlsClient for CoreCryptoWebClient {
124    async fn get_keypackage(&self) -> Result<Vec<u8>> {
125        let ciphersuite = CIPHERSUITE_IN_USE as u16;
126        let start = std::time::Instant::now();
127        let kp_raw = self
128            .browser
129            .execute_async(
130                r#"
131const [ciphersuite, callback] = arguments;
132window.cc.transaction((ctx) => 
133    ctx.clientKeypackages(ciphersuite, window.credentialType, 1)
134).then(([kp]) => callback(kp));"#,
135                vec![serde_json::json!(ciphersuite)],
136            )
137            .await
138            .and_then(|value| Ok(serde_json::from_value::<Vec<u8>>(value)?))?;
139
140        let kp: KeyPackage = KeyPackageIn::tls_deserialize(&mut kp_raw.as_slice())?.into();
141
142        log::info!(
143            "KP Init Key [took {}ms]: Client {} [{}] - {}",
144            start.elapsed().as_millis(),
145            self.client_name(),
146            hex::encode(&self.client_id),
147            hex::encode(kp.hpke_init_key()),
148        );
149
150        Ok(kp_raw)
151    }
152
153    async fn add_client(&self, conversation_id: &[u8], kp: &[u8]) -> Result<()> {
154        self.browser
155            .execute_async(
156                r#"
157const [cId, kp, callback] = arguments;
158const conversationId = Uint8Array.from(Object.values(cId));
159const keyPackage = Uint8Array.from(Object.values(kp));
160if (!window.cc.conversationExists(conversationId)) {
161    await window.cc.transaction((ctx) =>
162        ctx.createConversation(conversationId)
163    );
164}
165window.cc.transaction((ctx) =>
166    ctx.addClientsToConversation(conversationId, [{ kp: keyPackage }]))
167.then(({ welcome }) => callback(welcome));"#,
168                vec![conversation_id.into(), kp.into()],
169            )
170            .await?;
171        Ok(())
172    }
173
174    async fn kick_client(&self, conversation_id: &[u8], client_id: &[u8]) -> Result<()> {
175        Ok(self
176            .browser
177            .execute_async(
178                r#"
179const [cId, clId, callback] = arguments;
180const conversationId = Uint8Array.from(Object.values(cId));
181const clientId = Uint8Array.from(Object.values(clId));
182window.cc.transaction((ctx) =>
183    ctx.removeClientsFromConversation(conversationId, [clientId]))
184.then(({ commit }) => callback(commit));"#,
185                vec![conversation_id.into(), client_id.into()],
186            )
187            .await
188            .and_then(|value| Ok(serde_json::from_value(value)?))?)
189    }
190
191    async fn process_welcome(&self, welcome: &[u8]) -> Result<Vec<u8>> {
192        Ok(self
193            .browser
194            .execute_async(
195                r#"
196const [welcome, callback] = arguments;
197const welcomeMessage = Uint8Array.from(Object.values(welcome));
198window.cc.transaction((ctx) =>
199    ctx.processWelcomeMessage(welcomeMessage))
200.then(({ id }) => callback(id));"#,
201                vec![welcome.into()],
202            )
203            .await
204            .and_then(|value| Ok(serde_json::from_value(value)?))?)
205    }
206
207    async fn encrypt_message(&self, conversation_id: &[u8], message: &[u8]) -> Result<Vec<u8>> {
208        Ok(self
209            .browser
210            .execute_async(
211                r#"
212const [cId, cleartext, callback] = arguments;
213const conversationId = Uint8Array.from(Object.values(cId));
214const message = Uint8Array.from(Object.values(cleartext));
215window.cc.transaction((ctx) =>
216    ctx.encryptMessage(conversationId, message))
217.then(callback);"#,
218                vec![conversation_id.into(), message.into()],
219            )
220            .await
221            .and_then(|value| Ok(serde_json::from_value(value)?))?)
222    }
223
224    async fn decrypt_message(&self, conversation_id: &[u8], message: &[u8]) -> Result<Option<Vec<u8>>> {
225        let res = self
226            .browser
227            .execute_async(
228                r#"
229const [cId, encMessage, callback] = arguments;
230const conversationId = Uint8Array.from(Object.values(cId));
231const encryptedMessage = Uint8Array.from(Object.values(encMessage));
232window.cc.transaction((ctx) =>
233    ctx.decryptMessage(conversationId, encryptedMessage)
234).then(({ message }) => callback(message));"#,
235                vec![conversation_id.into(), message.into()],
236            )
237            .await?;
238
239        if res.is_null() {
240            Ok(None)
241        } else {
242            Ok(Some(serde_json::from_value(res)?))
243        }
244    }
245}
246
247#[cfg(feature = "proteus")]
248#[async_trait::async_trait(?Send)]
249impl crate::clients::EmulatedProteusClient for CoreCryptoWebClient {
250    async fn init(&mut self) -> Result<()> {
251        self.browser
252            .execute_async(
253                r#"
254const [callback] = arguments;
255window.cc.transaction((ctx) =>
256    ctx.proteusInit()
257).then(callback);"#,
258                vec![],
259            )
260            .await?;
261
262        Ok(())
263    }
264
265    async fn get_prekey(&self) -> Result<Vec<u8>> {
266        let prekey_last_id = self.prekey_last_id.get() + 1;
267        self.prekey_last_id.replace(prekey_last_id);
268        let prekey = self
269            .browser
270            .execute_async(
271                r#"
272const [prekeyId, callback] = arguments;
273window.cc.transaction((ctx) =>
274    ctx.proteusNewPrekey(prekeyId)
275).then(callback);"#,
276                vec![prekey_last_id.into()],
277            )
278            .await
279            .and_then(|value| Ok(serde_json::from_value(value)?))?;
280
281        Ok(prekey)
282    }
283
284    async fn session_from_prekey(&self, session_id: &str, prekey: &[u8]) -> Result<()> {
285        self.browser
286            .execute_async(
287                r#"
288const [sessionId, prekey, callback] = arguments;
289const prekeyBuffer = Uint8Array.from(Object.values(prekey));
290window.cc.transaction((ctx) =>
291    ctx.proteusSessionFromPrekey(sessionId, prekeyBuffer)
292).then(callback);"#,
293                vec![session_id.into(), prekey.into()],
294            )
295            .await?;
296        Ok(())
297    }
298
299    async fn session_from_message(&self, session_id: &str, message: &[u8]) -> Result<Vec<u8>> {
300        let cleartext = self
301            .browser
302            .execute_async(
303                r#"
304const [sessionId, message, callback] = arguments;
305const messageBuffer = Uint8Array.from(Object.values(message));
306window.cc.transaction((ctx) =>
307    ctx.proteusSessionFromMessage(sessionId, messageBuffer)
308).then(callback);"#,
309                vec![session_id.into(), message.into()],
310            )
311            .await
312            .and_then(|value| Ok(serde_json::from_value(value)?))?;
313
314        Ok(cleartext)
315    }
316    async fn encrypt(&self, session_id: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
317        let ciphertext = self
318            .browser
319            .execute_async(
320                r#"
321const [sessionId, plaintext, callback] = arguments;
322const plaintextBuffer = Uint8Array.from(Object.values(plaintext));
323window.cc.transaction((ctx) =>
324    ctx.proteusEncrypt(sessionId, plaintextBuffer)
325).then(callback);"#,
326                vec![session_id.into(), plaintext.into()],
327            )
328            .await
329            .and_then(|value| Ok(serde_json::from_value(value)?))?;
330
331        Ok(ciphertext)
332    }
333
334    async fn decrypt(&self, session_id: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
335        let cleartext = self
336            .browser
337            .execute_async(
338                r#"
339const [sessionId, ciphertext, callback] = arguments;
340const ciphertextBuffer = Uint8Array.from(Object.values(ciphertext));
341window.cc.transaction((ctx) =>
342    ctx.proteusDecrypt(sessionId, ciphertextBuffer)
343).then(callback);"#,
344                vec![session_id.into(), ciphertext.into()],
345            )
346            .await
347            .and_then(|value| Ok(serde_json::from_value(value)?))?;
348
349        Ok(cleartext)
350    }
351
352    async fn fingerprint(&self) -> Result<String> {
353        Ok(self
354            .browser
355            .execute_async(
356                "const [callback] = arguments; window.cc.proteusFingerprint().then(callback);",
357                vec![],
358            )
359            .await?
360            .as_str()
361            .unwrap()
362            .into())
363    }
364}