interop/clients/corecrypto/
ios.rs

1use crate::{
2    CIPHERSUITE_IN_USE,
3    clients::{EmulatedClient, EmulatedClientProtocol, EmulatedClientType, EmulatedMlsClient},
4};
5use base64::{Engine as _, engine::general_purpose};
6use color_eyre::eyre::Result;
7use core_crypto::prelude::{KeyPackage, KeyPackageIn};
8use std::cell::{Cell, RefCell};
9use std::fs;
10use std::io::{BufRead, BufReader, Read};
11use std::process::{Child, ChildStdout, Command, Output, Stdio};
12use std::time::Duration;
13use thiserror::Error;
14use tls_codec::Deserialize;
15
16#[derive(Debug)]
17struct SimulatorDriver {
18    device: String,
19    process: Child,
20    output: RefCell<BufReader<ChildStdout>>,
21}
22
23#[derive(Debug, serde::Deserialize)]
24enum InteropResult {
25    #[serde(rename = "success")]
26    Success { value: String },
27    #[serde(rename = "failure")]
28    Failure { message: String },
29}
30
31#[derive(Error, Debug)]
32#[error("simulator driver error: {msg}")]
33struct SimulatorDriverError {
34    msg: String,
35}
36
37impl SimulatorDriver {
38    fn new(device: String, application: String) -> Self {
39        let application = Self::launch_application(&device, &application, true).expect("Failed ot launch application");
40
41        Self {
42            device,
43            process: application.0,
44            output: RefCell::new(application.1),
45        }
46    }
47
48    fn boot_device(device: &str) -> std::io::Result<Output> {
49        Command::new("xcrun").args(["simctl", "boot", device]).output()
50    }
51
52    fn launch_application(
53        device: &str,
54        application: &str,
55        boot_device: bool,
56    ) -> Result<(Child, BufReader<ChildStdout>)> {
57        log::info!("launching application: {} on {}", application, device);
58
59        let mut process = Command::new("xcrun")
60            .args([
61                "simctl",
62                "launch",
63                "--console-pty",
64                "--terminate-running-process",
65                device,
66                application,
67            ])
68            .stdout(Stdio::piped())
69            .stderr(Stdio::piped())
70            .spawn()
71            .expect("Failed to launch application");
72
73        let mut output = BufReader::new(
74            process
75                .stdout
76                .take()
77                .expect("Expected stdout to be available on child process"),
78        );
79
80        // Wait for child process to launch or fail
81        std::thread::sleep(Duration::from_secs(3));
82        match process.try_wait() {
83            Ok(None) => {}
84            Ok(Some(exit_status)) => {
85                if boot_device && exit_status.code() == Some(149) {
86                    log::info!("device is shutdown, booting...");
87                    Self::boot_device(device)?;
88                    return Self::launch_application(device, application, false);
89                }
90
91                let mut error_message = String::new();
92                process
93                    .stderr
94                    .map(|mut stderr| stderr.read_to_string(&mut error_message));
95                panic!("Failed to launch application ({}): {}", exit_status, error_message)
96            }
97            Err(error) => {
98                panic!("Failed to launch application: {}", error)
99            }
100        }
101
102        // Waiting for confirmation that the application has launched.
103        let mut line = String::new();
104        while !line.contains("Ready") {
105            line.clear();
106            output
107                .read_line(&mut line)
108                .expect("was expecting ready signal on stdout");
109        }
110
111        log::info!("application launched: {}", line);
112        Ok((process, output))
113    }
114
115    async fn execute(&self, action: String) -> Result<String> {
116        log::info!("interop://{}", action);
117
118        Command::new("xcrun")
119            .args([
120                "simctl",
121                "openurl",
122                &self.device,
123                format!("interop://{}", action).as_str(),
124            ])
125            .output()
126            .expect("Failed to execute action");
127
128        let mut result = String::new();
129        let mut output = self.output.try_borrow_mut()?;
130
131        output.read_line(&mut result)?;
132
133        log::info!("{}", result);
134
135        let result: InteropResult = serde_json::from_str(result.trim())?;
136
137        match result {
138            InteropResult::Success { value } => Ok(value),
139            InteropResult::Failure { message } => Err(SimulatorDriverError { msg: message }.into()),
140        }
141    }
142}
143
144impl Drop for SimulatorDriver {
145    fn drop(&mut self) {
146        self.process.kill().expect("expected child process to be killed")
147    }
148}
149
150#[derive(Debug)]
151pub(crate) struct CoreCryptoIosClient {
152    driver: SimulatorDriver,
153    client_id: Vec<u8>,
154    #[cfg(feature = "proteus")]
155    prekey_last_id: Cell<u16>,
156}
157
158impl CoreCryptoIosClient {
159    pub(crate) async fn new() -> Result<Self> {
160        let client_id = uuid::Uuid::new_v4();
161        let client_id_str = client_id.as_hyphenated().to_string();
162        let client_id_base64 = general_purpose::STANDARD.encode(client_id_str.as_str());
163        let ciphersuite = CIPHERSUITE_IN_USE as u16;
164        let device = std::env::var("INTEROP_SIMULATOR_DEVICE").unwrap_or("booted".into());
165
166        let driver = SimulatorDriver::new(device, "com.wire.InteropClient".into());
167        log::info!("initialising core crypto with ciphersuite {}", ciphersuite);
168        driver
169            .execute(format!(
170                "init-mls?client={}&ciphersuite={}",
171                client_id_base64, ciphersuite
172            ))
173            .await?;
174
175        Ok(Self {
176            driver,
177            client_id: client_id.into_bytes().into(),
178            #[cfg(feature = "proteus")]
179            prekey_last_id: Cell::new(0),
180        })
181    }
182}
183
184#[async_trait::async_trait(?Send)]
185impl EmulatedClient for CoreCryptoIosClient {
186    fn client_name(&self) -> &str {
187        "CoreCrypto::ios"
188    }
189
190    fn client_type(&self) -> EmulatedClientType {
191        EmulatedClientType::AppleiOS
192    }
193
194    fn client_id(&self) -> &[u8] {
195        self.client_id.as_slice()
196    }
197
198    fn client_protocol(&self) -> EmulatedClientProtocol {
199        EmulatedClientProtocol::MLS | EmulatedClientProtocol::PROTEUS
200    }
201
202    async fn wipe(mut self) -> Result<()> {
203        Ok(())
204    }
205}
206
207#[async_trait::async_trait(?Send)]
208impl EmulatedMlsClient for CoreCryptoIosClient {
209    async fn get_keypackage(&self) -> Result<Vec<u8>> {
210        let ciphersuite = CIPHERSUITE_IN_USE as u16;
211        let start = std::time::Instant::now();
212        let kp_base64 = self
213            .driver
214            .execute(format!("get-key-package?ciphersuite={}", ciphersuite))
215            .await?;
216        let kp_raw = general_purpose::STANDARD.decode(kp_base64)?;
217        let kp: KeyPackage = KeyPackageIn::tls_deserialize(&mut kp_raw.as_slice())?.into();
218
219        log::info!(
220            "KP Init Key [took {}ms]: Client {} [{}] - {}",
221            start.elapsed().as_millis(),
222            self.client_name(),
223            hex::encode(&self.client_id),
224            hex::encode(kp.hpke_init_key()),
225        );
226
227        Ok(kp_raw)
228    }
229
230    async fn add_client(&self, conversation_id: &[u8], kp: &[u8]) -> Result<()> {
231        let cid_base64 = general_purpose::STANDARD.encode(conversation_id);
232        let kp_base64 = general_purpose::STANDARD.encode(kp);
233        let ciphersuite = CIPHERSUITE_IN_USE as u16;
234        self.driver
235            .execute(format!(
236                "add-client?cid={}&ciphersuite={}&kp={}",
237                cid_base64, ciphersuite, kp_base64
238            ))
239            .await?;
240
241        Ok(())
242    }
243
244    async fn kick_client(&self, conversation_id: &[u8], client_id: &[u8]) -> Result<()> {
245        let cid_base64 = general_purpose::STANDARD.encode(conversation_id);
246        let client_id_base64 = general_purpose::STANDARD.encode(client_id);
247        self.driver
248            .execute(format!("remove-client?cid={}&client={}", cid_base64, client_id_base64))
249            .await?;
250
251        Ok(())
252    }
253
254    async fn process_welcome(&self, welcome: &[u8]) -> Result<Vec<u8>> {
255        let welcome_path = std::env::temp_dir().join(format!("welcome-{}", uuid::Uuid::new_v4().as_hyphenated()));
256        fs::write(&welcome_path, welcome)?;
257        let conversation_id_base64 = self
258            .driver
259            .execute(format!(
260                "process-welcome?welcome_path={}",
261                welcome_path.to_str().unwrap()
262            ))
263            .await?;
264        let conversation_id = general_purpose::STANDARD.decode(conversation_id_base64)?;
265
266        Ok(conversation_id)
267    }
268
269    async fn encrypt_message(&self, conversation_id: &[u8], message: &[u8]) -> Result<Vec<u8>> {
270        let cid_base64 = general_purpose::STANDARD.encode(conversation_id);
271        let message_base64 = general_purpose::STANDARD.encode(message);
272        let encrypted_message_base64 = self
273            .driver
274            .execute(format!("encrypt-message?cid={}&message={}", cid_base64, message_base64))
275            .await?;
276        let encrypted_message = general_purpose::STANDARD.decode(encrypted_message_base64)?;
277
278        Ok(encrypted_message)
279    }
280
281    async fn decrypt_message(&self, conversation_id: &[u8], message: &[u8]) -> Result<Option<Vec<u8>>> {
282        let cid_base64 = general_purpose::STANDARD.encode(conversation_id);
283        let message_base64 = general_purpose::STANDARD.encode(message);
284        let result = self
285            .driver
286            .execute(format!("decrypt-message?cid={}&message={}", cid_base64, message_base64))
287            .await?;
288
289        if result == "decrypted protocol message" {
290            Ok(None)
291        } else {
292            let decrypted_message = general_purpose::STANDARD.decode(result)?;
293            Ok(Some(decrypted_message))
294        }
295    }
296}
297
298#[cfg(feature = "proteus")]
299#[async_trait::async_trait(?Send)]
300impl crate::clients::EmulatedProteusClient for CoreCryptoIosClient {
301    async fn init(&mut self) -> Result<()> {
302        self.driver.execute("init-proteus".into()).await?;
303        Ok(())
304    }
305
306    async fn get_prekey(&self) -> Result<Vec<u8>> {
307        let prekey_last_id = self.prekey_last_id.get() + 1;
308        self.prekey_last_id.replace(prekey_last_id);
309
310        let prekey_base64 = self.driver.execute(format!("get-prekey?id={}", prekey_last_id)).await?;
311        let prekey = general_purpose::STANDARD.decode(prekey_base64)?;
312
313        Ok(prekey)
314    }
315
316    async fn session_from_prekey(&self, session_id: &str, prekey: &[u8]) -> Result<()> {
317        let prekey_base64 = general_purpose::STANDARD.encode(prekey);
318        self.driver
319            .execute(format!(
320                "session-from-prekey?session_id={}&prekey={}",
321                session_id, prekey_base64
322            ))
323            .await?;
324
325        Ok(())
326    }
327
328    async fn session_from_message(&self, session_id: &str, message: &[u8]) -> Result<Vec<u8>> {
329        let message_base64 = general_purpose::STANDARD.encode(message);
330        let decrypted_message_base64 = self
331            .driver
332            .execute(format!(
333                "session-from-message?session_id={}&message={}",
334                session_id, message_base64
335            ))
336            .await?;
337        let decrypted_message = general_purpose::STANDARD.decode(decrypted_message_base64)?;
338
339        Ok(decrypted_message)
340    }
341    async fn encrypt(&self, session_id: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
342        let plaintext_base64 = general_purpose::STANDARD.encode(plaintext);
343        let encrypted_message_base64 = self
344            .driver
345            .execute(format!(
346                "encrypt-proteus?session_id={}&message={}",
347                session_id, plaintext_base64
348            ))
349            .await?;
350        let encrypted_message = general_purpose::STANDARD.decode(encrypted_message_base64)?;
351
352        Ok(encrypted_message)
353    }
354
355    async fn decrypt(&self, session_id: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
356        let ciphertext_base64 = general_purpose::STANDARD.encode(ciphertext);
357        let decrypted_message_base64 = self
358            .driver
359            .execute(format!(
360                "decrypt-proteus?session_id={}&message={}",
361                session_id, ciphertext_base64
362            ))
363            .await?;
364        let decrypted_message = general_purpose::STANDARD.decode(decrypted_message_base64)?;
365
366        Ok(decrypted_message)
367    }
368
369    async fn fingerprint(&self) -> Result<String> {
370        let fingerprint = self.driver.execute("get-fingerprint".into()).await?;
371
372        Ok(fingerprint)
373    }
374}