interop/clients/corecrypto/
web.rs1use 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}