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 } = 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}