interop/clients/corecrypto/
ios.rs1use 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 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 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}