interop/clients/corecrypto/
web.rs
1use 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}