interop/clients/cryptobox/
web.rs

1use 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}