interop/clients/corecrypto/
web.rs

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