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