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