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