interop/clients/cryptobox/
web.rs1use crate::clients::{EmulatedClient, EmulatedClientProtocol, EmulatedClientType, EmulatedProteusClient};
2use color_eyre::eyre::Result;
3use std::cell::Cell;
4use std::net::SocketAddr;
5
6#[derive(Debug)]
7pub(crate) struct CryptoboxWebClient {
8 browser: fantoccini::Client,
9 client_id: uuid::Uuid,
10 prekey_last_id: Cell<u16>,
11}
12
13impl CryptoboxWebClient {
14 pub(crate) async fn new(driver_addr: &SocketAddr, server: &SocketAddr) -> Result<Self> {
15 let client_id = uuid::Uuid::new_v4();
16 let browser = crate::build::web::webdriver::setup_browser(driver_addr, server, "cryptobox").await?;
17
18 Ok(Self {
19 browser,
20 client_id,
21 #[cfg(feature = "proteus")]
22 prekey_last_id: Cell::new(0),
23 })
24 }
25}
26
27#[async_trait::async_trait(?Send)]
28impl EmulatedClient for CryptoboxWebClient {
29 fn client_name(&self) -> &str {
30 "Cryptobox::web"
31 }
32
33 fn client_type(&self) -> EmulatedClientType {
34 EmulatedClientType::Web
35 }
36
37 fn client_id(&self) -> &[u8] {
38 self.client_id.as_bytes().as_slice()
39 }
40
41 fn client_protocol(&self) -> EmulatedClientProtocol {
42 EmulatedClientProtocol::PROTEUS
43 }
44
45 async fn wipe(mut self) -> Result<()> {
46 let _ = self
47 .browser
48 .execute_async(
49 r#"
50const [callback] = arguments;
51window.cbox.deleteData().then(callback);"#,
52 vec![],
53 )
54 .await?;
55
56 Ok(())
57 }
58}
59
60#[async_trait::async_trait(?Send)]
61impl EmulatedProteusClient for CryptoboxWebClient {
62 async fn init(&mut self) -> Result<()> {
63 self.browser
64 .execute_async(
65 r#"
66const [clientId, callback] = arguments;
67// const { createCryptobox } = await import("./cryptobox.js");
68const storeName = `cryptobox-e2e-interop-${clientId}`;
69const cryptobox = await window.createCryptobox(storeName);
70window.cbox = cryptobox;
71callback();"#,
72 vec![self.client_id.as_hyphenated().to_string().into()],
73 )
74 .await?;
75
76 Ok(())
77 }
78
79 async fn get_prekey(&self) -> Result<Vec<u8>> {
80 self.prekey_last_id.replace(self.prekey_last_id.get() + 1);
81
82 let json_response = self
83 .browser
84 .execute_async(
85 r#"
86const [prekeyId, callback] = arguments;
87const [prekey] = await window.cbox.new_prekeys(prekeyId, 1);
88const { key } = window.cbox.serialize_prekey(prekey);
89callback(key);"#,
90 vec![self.prekey_last_id.get().into()],
91 )
92 .await?;
93
94 let key_b64 = json_response.as_str().unwrap();
95
96 use base64::Engine as _;
97 Ok(base64::prelude::BASE64_STANDARD.decode(key_b64)?)
98 }
99
100 async fn session_from_prekey(&self, session_id: &str, prekey: &[u8]) -> Result<()> {
101 self.browser
102 .execute_async(
103 r#"
104const [sessionId, prekey, callback] = arguments;
105const prekeyBundle = Uint8Array.from(Object.values(prekey));
106await window.cbox.session_from_prekey(sessionId, prekeyBundle.buffer);
107callback();"#,
108 vec![session_id.into(), prekey.into()],
109 )
110 .await?;
111
112 Ok(())
113 }
114
115 async fn session_from_message(&self, session_id: &str, message: &[u8]) -> Result<Vec<u8>> {
116 Ok(self
117 .browser
118 .execute_async(
119 r#"
120const [sessionId, message, callback] = arguments;
121const envelope = Uint8Array.from(Object.values(message));
122const cleartext = await window.cbox.decrypt(sessionId, envelope.buffer);
123callback(cleartext);"#,
124 vec![session_id.into(), message.into()],
125 )
126 .await
127 .and_then(|value| Ok(serde_json::from_value(value)?))?)
128 }
129
130 async fn encrypt(&self, session_id: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
131 Ok(self
132 .browser
133 .execute_async(
134 r#"
135const [sessionId, plaintext, callback] = arguments;
136const plaintextBuffer = Uint8Array.from(Object.values(plaintext));
137const encrypted = await window.cbox.encrypt(sessionId, plaintextBuffer.buffer);
138callback(new Uint8Array(encrypted));"#,
139 vec![session_id.into(), plaintext.into()],
140 )
141 .await
142 .and_then(|value| Ok(serde_json::from_value(value)?))?)
143 }
144
145 async fn decrypt(&self, session_id: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
146 Ok(self
147 .browser
148 .execute_async(
149 r#"
150const [sessionId, ciphertext, callback] = arguments;
151const ciphertextBuffer = Uint8Array.from(Object.values(ciphertext));
152const plaintext = await window.cbox.decrypt(sessionId, ciphertextBuffer.buffer);
153callback(new Uint8Array(plaintext));"#,
154 vec![session_id.into(), ciphertext.into()],
155 )
156 .await
157 .and_then(|value| Ok(serde_json::from_value(value)?))?)
158 }
159
160 async fn fingerprint(&self) -> Result<String> {
161 Ok(self
162 .browser
163 .execute_async(
164 r#"
165const [callback] = arguments;
166const identity = window.cbox.getIdentity();
167callback(identity.public_key.fingerprint());"#,
168 vec![],
169 )
170 .await
171 .map(|value| value.as_str().unwrap().to_string())?)
172 }
173}