interop/clients/cryptobox/
web.rs

1// Wire
2// Copyright (C) 2022 Wire Swiss GmbH
3
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see http://www.gnu.org/licenses/.
16
17use crate::clients::{EmulatedClient, EmulatedClientProtocol, EmulatedClientType, EmulatedProteusClient};
18use color_eyre::eyre::Result;
19use std::cell::Cell;
20use std::net::SocketAddr;
21
22#[derive(Debug)]
23pub(crate) struct CryptoboxWebClient {
24    browser: fantoccini::Client,
25    client_id: uuid::Uuid,
26    prekey_last_id: Cell<u16>,
27}
28
29impl CryptoboxWebClient {
30    pub(crate) async fn new(driver_addr: &SocketAddr) -> Result<Self> {
31        let client_id = uuid::Uuid::new_v4();
32        let browser = crate::build::web::webdriver::setup_browser(driver_addr, "cryptobox").await?;
33
34        Ok(Self {
35            browser,
36            client_id,
37            #[cfg(feature = "proteus")]
38            prekey_last_id: Cell::new(0),
39        })
40    }
41}
42
43#[async_trait::async_trait(?Send)]
44impl EmulatedClient for CryptoboxWebClient {
45    fn client_name(&self) -> &str {
46        "Cryptobox::web"
47    }
48
49    fn client_type(&self) -> EmulatedClientType {
50        EmulatedClientType::Web
51    }
52
53    fn client_id(&self) -> &[u8] {
54        self.client_id.as_bytes().as_slice()
55    }
56
57    fn client_protocol(&self) -> EmulatedClientProtocol {
58        EmulatedClientProtocol::PROTEUS
59    }
60
61    async fn wipe(mut self) -> Result<()> {
62        let _ = self
63            .browser
64            .execute_async(
65                r#"
66const [callback] = arguments;
67window.cbox.deleteData().then(callback);"#,
68                vec![],
69            )
70            .await?;
71
72        Ok(())
73    }
74}
75
76#[async_trait::async_trait(?Send)]
77impl EmulatedProteusClient for CryptoboxWebClient {
78    async fn init(&mut self) -> Result<()> {
79        self.browser
80            .execute_async(
81                r#"
82const [clientId, callback] = arguments;
83// const { createCryptobox } = await import("./cryptobox.js");
84const storeName = `cryptobox-e2e-interop-${clientId}`;
85const cryptobox = await window.createCryptobox(storeName);
86window.cbox = cryptobox;
87callback();"#,
88                vec![self.client_id.as_hyphenated().to_string().into()],
89            )
90            .await?;
91
92        Ok(())
93    }
94
95    async fn get_prekey(&self) -> Result<Vec<u8>> {
96        self.prekey_last_id.replace(self.prekey_last_id.get() + 1);
97
98        let json_response = self
99            .browser
100            .execute_async(
101                r#"
102const [prekeyId, callback] = arguments;
103const [prekey] = await window.cbox.new_prekeys(prekeyId, 1);
104const { key } = window.cbox.serialize_prekey(prekey);
105callback(key);"#,
106                vec![self.prekey_last_id.get().into()],
107            )
108            .await?;
109
110        let key_b64 = json_response.as_str().unwrap();
111
112        use base64::Engine as _;
113        Ok(base64::prelude::BASE64_STANDARD.decode(key_b64)?)
114    }
115
116    async fn session_from_prekey(&self, session_id: &str, prekey: &[u8]) -> Result<()> {
117        self.browser
118            .execute_async(
119                r#"
120const [sessionId, prekey, callback] = arguments;
121const prekeyBundle = Uint8Array.from(Object.values(prekey));
122await window.cbox.session_from_prekey(sessionId, prekeyBundle.buffer);
123callback();"#,
124                vec![session_id.into(), prekey.into()],
125            )
126            .await?;
127
128        Ok(())
129    }
130
131    async fn session_from_message(&self, session_id: &str, message: &[u8]) -> Result<Vec<u8>> {
132        Ok(self
133            .browser
134            .execute_async(
135                r#"
136const [sessionId, message, callback] = arguments;
137const envelope = Uint8Array.from(Object.values(message));
138const cleartext = await window.cbox.decrypt(sessionId, envelope.buffer);
139callback(cleartext);"#,
140                vec![session_id.into(), message.into()],
141            )
142            .await
143            .and_then(|value| Ok(serde_json::from_value(value)?))?)
144    }
145
146    async fn encrypt(&self, session_id: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
147        Ok(self
148            .browser
149            .execute_async(
150                r#"
151const [sessionId, plaintext, callback] = arguments;
152const plaintextBuffer = Uint8Array.from(Object.values(plaintext));
153const encrypted = await window.cbox.encrypt(sessionId, plaintextBuffer.buffer);
154callback(new Uint8Array(encrypted));"#,
155                vec![session_id.into(), plaintext.into()],
156            )
157            .await
158            .and_then(|value| Ok(serde_json::from_value(value)?))?)
159    }
160
161    async fn decrypt(&self, session_id: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
162        Ok(self
163            .browser
164            .execute_async(
165                r#"
166const [sessionId, ciphertext, callback] = arguments;
167const ciphertextBuffer = Uint8Array.from(Object.values(ciphertext));
168const plaintext = await window.cbox.decrypt(sessionId, ciphertextBuffer.buffer);
169callback(new Uint8Array(plaintext));"#,
170                vec![session_id.into(), ciphertext.into()],
171            )
172            .await
173            .and_then(|value| Ok(serde_json::from_value(value)?))?)
174    }
175
176    async fn fingerprint(&self) -> Result<String> {
177        Ok(self
178            .browser
179            .execute_async(
180                r#"
181const [callback] = arguments;
182const identity = window.cbox.getIdentity();
183callback(identity.public_key.fingerprint());"#,
184                vec![],
185            )
186            .await
187            .map(|value| value.as_str().unwrap().to_string())?)
188    }
189}