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