1#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
2
3use core_crypto::DatabaseKey;
4
5#[cfg(not(target_family = "wasm"))]
6use crate::clients::{EmulatedClient, EmulatedProteusClient};
7#[cfg(not(target_family = "wasm"))]
8use crate::util::{MlsTransportSuccessProvider, MlsTransportTestExt};
9use color_eyre::eyre::{Result, eyre};
10use core_crypto::prelude::CiphersuiteName;
11use std::rc::Rc;
12use std::sync::Arc;
13use tls_codec::Serialize;
14
15#[cfg(not(target_family = "wasm"))]
16mod build;
17#[cfg(not(target_family = "wasm"))]
18mod clients;
19#[cfg(not(target_family = "wasm"))]
20mod util;
21
22const MLS_MAIN_CLIENTID: &[u8] = b"test_main";
23const MLS_CONVERSATION_ID: &[u8] = b"test_conversation";
24const ROUNDTRIP_MSG_AMOUNT: usize = 100;
25
26const CIPHERSUITE_IN_USE: CiphersuiteName = CiphersuiteName::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
27
28fn main() -> Result<()> {
31 run_test()
32}
33
34#[cfg(not(target_family = "wasm"))]
37fn run_test() -> Result<()> {
38 use std::time::{Duration, Instant};
39
40 use tokio::net::{TcpListener, TcpStream};
41
42 color_eyre::install()?;
43 if std::env::var("RUST_LOG").is_ok() || std::env::var("CI").is_ok() {
44 femme::start();
45 }
46
47 let force_webdriver_install = std::env::var("FORCE_WEBDRIVER_INSTALL").is_ok();
48
49 let current_dir = std::env::current_dir()?;
51 if current_dir.ends_with("interop") {
52 let new_cwd = current_dir.parent().unwrap();
53 log::info!("cwd was {current_dir:?}; setting it to {new_cwd:?}");
54 std::env::set_current_dir(new_cwd)?;
55 }
56 let tempdir = tempfile::tempdir()?;
57
58 let runtime = tokio::runtime::Builder::new_multi_thread()
60 .enable_all()
61 .build()
62 .unwrap();
63
64 runtime.block_on(async {
65 build::web::webdriver::setup_webdriver(force_webdriver_install).await?;
66
67 build::web::wasm::build_wasm(tempdir.path().to_path_buf()).await?;
68
69 let spinner = util::RunningProcess::new("Starting HTTP server...", false);
70 let (server, server_task) = build::web::wasm::bind_http_server(tempdir.path().to_path_buf());
71 let http_server_hwnd = tokio::task::spawn(server_task);
72 spinner.success(format!("HTTP server started {server} [OK]"));
73
74 let mut spinner = util::RunningProcess::new("Starting WebDriver [ChromeDriver & GeckoDriver]...", false);
75 let chrome_driver_addr = TcpListener::bind("127.0.0.1:0").await?.local_addr()?;
76 let mut chrome_webdriver = build::web::webdriver::start_webdriver_chrome(&chrome_driver_addr).await?;
77 spinner.update("Sleeping to wait for Webdrivers to get ready...");
78 let timeout = Duration::from_secs(5);
79 let start = Instant::now();
80 while start.elapsed() < timeout {
81 let chrome_ready = TcpStream::connect(&chrome_driver_addr).await.is_ok();
82 if chrome_ready {
83 break;
84 }
85 tokio::time::sleep(Duration::from_millis(100)).await;
86 }
87 spinner.success("WebDriver [OK]");
88
89 run_mls_test(&chrome_driver_addr, &server).await?;
90
91 #[cfg(feature = "proteus")]
92 run_proteus_test(&chrome_driver_addr, &server).await?;
93
94 chrome_webdriver.kill().await?;
95 http_server_hwnd.abort();
96 Ok(())
97 })
98}
99
100#[cfg(target_family = "wasm")]
101fn run_test() -> Result<()> {
102 panic!("E2E tests cannot be run on WASM")
103}
104
105#[cfg(not(target_family = "wasm"))]
106async fn run_mls_test(chrome_driver_addr: &std::net::SocketAddr, web_server: &std::net::SocketAddr) -> Result<()> {
107 use core_crypto::prelude::*;
108 use rand::distributions::DistString;
109
110 log::info!("Using ciphersuite {}", CIPHERSUITE_IN_USE);
111
112 let spinner = util::RunningProcess::new("[MLS] Step 0: Initializing clients & env...", true);
113
114 let ios_client = Rc::new(clients::corecrypto::ios::CoreCryptoIosClient::new().await?);
115 let native_client = Rc::new(clients::corecrypto::native::CoreCryptoNativeClient::new().await?);
116 let ffi_client = Rc::new(clients::corecrypto::ffi::CoreCryptoFfiClient::new().await?);
117 let web_client = Rc::new(clients::corecrypto::web::CoreCryptoWebClient::new(chrome_driver_addr, web_server).await?);
118
119 let mut clients: Vec<Rc<dyn clients::EmulatedMlsClient>> = vec![
120 native_client.clone(),
121 ffi_client.clone(),
122 web_client.clone(),
123 ios_client.clone(),
124 ];
125
126 let ciphersuites = vec![CIPHERSUITE_IN_USE.into()];
127 let configuration = MlsClientConfiguration::try_new(
128 "whatever".into(),
129 DatabaseKey::generate(),
130 Some(MLS_MAIN_CLIENTID.into()),
131 ciphersuites,
132 None,
133 Some(100),
134 )?;
135 let master_client = Session::try_new_in_memory(configuration).await?;
136
137 let conversation_id = MLS_CONVERSATION_ID.to_vec();
138 let config = MlsConversationConfiguration {
139 ciphersuite: CIPHERSUITE_IN_USE.into(),
140 ..Default::default()
141 };
142 let cc = CoreCrypto::from(master_client.clone());
143
144 let success_provider = Arc::new(MlsTransportSuccessProvider::default());
145
146 cc.provide_transport(success_provider.clone()).await;
147 let transaction = cc.new_transaction().await?;
148 transaction
149 .new_conversation(&conversation_id, MlsCredentialType::Basic, config)
150 .await?;
151
152 spinner.success("[MLS] Step 0: Initializing clients [OK]");
153
154 let spinner = util::RunningProcess::new("[MLS] Step 1: Fetching KeyPackages from clients...", true);
155
156 use tls_codec::Deserialize as _;
157 let mut key_packages = vec![];
158 for c in clients.iter_mut() {
159 let kp = c.get_keypackage().await?;
160 let kp = KeyPackageIn::tls_deserialize(&mut kp.as_slice())?;
161 key_packages.push(kp);
162 }
163
164 spinner.success("[MLS] Step 1: KeyPackages [OK]");
165
166 let spinner = util::RunningProcess::new("[MLS] Step 2: Adding clients to conversation...", true);
167
168 transaction
169 .conversation(&conversation_id)
170 .await?
171 .add_members(key_packages)
172 .await?;
173
174 let conversation_add_msg = success_provider.latest_welcome_message().await;
175 let welcome_raw = conversation_add_msg.tls_serialize_detached()?;
176
177 for c in clients.iter_mut() {
178 let conversation_id_from_welcome = c.process_welcome(&welcome_raw).await?;
179 assert_eq!(conversation_id_from_welcome, conversation_id);
180 }
181
182 spinner.success("[MLS] Step 2: Added clients [OK]");
183
184 let mut spinner = util::RunningProcess::new(
185 format!("[MLS] Step 3: Roundtripping messages [0/{ROUNDTRIP_MSG_AMOUNT}]"),
186 true,
187 );
188
189 let mut prng = rand::thread_rng();
190 let mut message;
191 for i in 1..=ROUNDTRIP_MSG_AMOUNT {
192 message = rand::distributions::Alphanumeric.sample_string(&mut prng, 16);
193
194 log::info!(
195 "Master client [{}] >>> {}",
196 hex::encode(master_client.id().await?.as_slice()),
197 message
198 );
199
200 let mut message_to_decrypt = transaction
201 .conversation(&conversation_id)
202 .await?
203 .encrypt_message(&message)
204 .await?;
205
206 for c in clients.iter_mut() {
207 let decrypted_message_raw = c
208 .decrypt_message(&conversation_id, &message_to_decrypt)
209 .await?
210 .ok_or_else(|| {
211 eyre!(
212 "[MLS] No message, something went very wrong [Client = {}]",
213 c.client_type()
214 )
215 })?;
216
217 let decrypted_message = String::from_utf8(decrypted_message_raw)?;
218
219 log::info!(
220 "{} [{}] <<< {}",
221 c.client_name(),
222 hex::encode(c.client_id()),
223 decrypted_message
224 );
225
226 assert_eq!(
227 decrypted_message,
228 message,
229 "[MLS] Messages differ [Client = {}]",
230 c.client_type()
231 );
232
233 message_to_decrypt = c
234 .encrypt_message(&conversation_id, decrypted_message.as_bytes())
235 .await?;
236 }
237
238 let decrypted_master_raw = transaction
239 .conversation(&conversation_id)
240 .await
241 .unwrap()
242 .decrypt_message(message_to_decrypt)
243 .await?
244 .app_msg
245 .ok_or_else(|| eyre!("[MLS] No message received on master client"))?;
246
247 let decrypted_master = String::from_utf8(decrypted_master_raw)?;
248
249 assert_eq!(decrypted_master, message);
250
251 spinner.update(format!(
252 "[MLS] Step 3: Roundtripping messages... [{i}/{ROUNDTRIP_MSG_AMOUNT}]"
253 ));
254 }
255
256 clients.clear();
257
258 spinner.success(format!(
259 "[MLS] Step 3: Roundtripping {ROUNDTRIP_MSG_AMOUNT} messages... [OK]"
260 ));
261
262 let spinner = util::RunningProcess::new("[MLS] Step 4: Deleting clients...", true);
263
264 Rc::into_inner(ios_client)
265 .expect("Only one strong reference to the interop client")
266 .wipe()
267 .await?;
268 Rc::into_inner(native_client)
269 .expect("Only one strong reference to the interop client")
270 .wipe()
271 .await?;
272 Rc::into_inner(ffi_client)
273 .expect("Only one strong reference to the interop client")
274 .wipe()
275 .await?;
276 Rc::into_inner(web_client)
277 .expect("Only one strong reference to the interop client")
278 .wipe()
279 .await?;
280
281 spinner.success("[MLS] Step 4: Deleting clients [OK]");
282
283 Ok(())
284}
285
286#[cfg(all(not(target_family = "wasm"), feature = "proteus"))]
287async fn run_proteus_test(chrome_driver_addr: &std::net::SocketAddr, web_server: &std::net::SocketAddr) -> Result<()> {
288 use core_crypto::prelude::*;
289
290 let spinner = util::RunningProcess::new("[Proteus] Step 0: Initializing clients & env...", true);
291
292 let mut ios_client = clients::corecrypto::ios::CoreCryptoIosClient::new().await?;
293 let mut native_client = clients::corecrypto::native::CoreCryptoNativeClient::new().await?;
294 let mut ffi_client = clients::corecrypto::ffi::CoreCryptoFfiClient::new().await?;
295 let mut web_client = clients::corecrypto::web::CoreCryptoWebClient::new(chrome_driver_addr, web_server).await?;
296 let mut cryptobox_native_client = clients::cryptobox::native::CryptoboxNativeClient::new();
297 let mut cryptobox_web_client =
298 clients::cryptobox::web::CryptoboxWebClient::new(chrome_driver_addr, web_server).await?;
299
300 ios_client.init().await?;
301 native_client.init().await?;
302 ffi_client.init().await?;
303 web_client.init().await?;
304 cryptobox_native_client.init().await?;
305 cryptobox_web_client.init().await?;
306
307 let ios_client = Rc::new(ios_client);
308 let native_client = Rc::new(native_client);
309 let ffi_client = Rc::new(ffi_client);
310 let web_client = Rc::new(web_client);
311 let cryptobox_native_client = Rc::new(cryptobox_native_client);
312 let cryptobox_web_client = Rc::new(cryptobox_web_client);
313
314 let mut clients: Vec<Rc<dyn clients::EmulatedProteusClient>> = vec![
315 ios_client.clone(),
316 native_client.clone(),
317 ffi_client.clone(),
318 web_client.clone(),
319 cryptobox_native_client.clone(),
320 cryptobox_web_client.clone(),
321 ];
322
323 let configuration = MlsClientConfiguration::try_new(
324 "whatever".into(),
325 DatabaseKey::generate(),
326 Some(MLS_MAIN_CLIENTID.into()),
327 vec![MlsCiphersuite::default()],
328 None,
329 Some(100),
330 )?;
331 let master_client = CoreCrypto::from(Session::try_new_in_memory(configuration).await?);
332 let transaction = master_client.new_transaction().await?;
333 transaction.proteus_init().await?;
334
335 let master_fingerprint = master_client.proteus_fingerprint().await?;
336
337 spinner.success("[Proteus] Step 0: Initializing clients [OK]");
338
339 let mut spinner = util::RunningProcess::new("[Proteus] Step 1: Fetching PreKeys from clients...", true);
340
341 let mut client_type_mapping = std::collections::HashMap::new();
342 let mut prekeys = std::collections::HashMap::new();
343 for c in clients.iter_mut() {
344 let client_name = c.client_name();
345 spinner.update(format!("[Proteus] Step 1: PreKeys - {client_name}"));
346 let fingerprint = c.fingerprint().await?;
347 spinner.update(format!("[Proteus] Step 1: PreKeys - {fingerprint}@{client_name}"));
348 client_type_mapping.insert(fingerprint.clone(), client_name.to_string());
349 let prekey = c.get_prekey().await?;
350 prekeys.insert(fingerprint, prekey);
351 }
352
353 spinner.success("[Proteus] Step 1: PreKeys [OK]");
354
355 let mut spinner = util::RunningProcess::new("[Proteus] Step 2: Creating sessions...", true);
356
357 let mut master_sessions = vec![];
358 let mut messages = std::collections::HashMap::new();
359 const PROTEUS_INITIAL_MESSAGE: &[u8] = b"Hello world!";
360 for (fingerprint, prekey) in prekeys {
361 spinner.update(format!(
362 "[Proteus] Step 2: Session master -> {fingerprint}@{}",
363 client_type_mapping[&fingerprint]
364 ));
365 let session_arc = transaction.proteus_session_from_prekey(&fingerprint, &prekey).await?;
366 let mut session = session_arc.write().await;
367 messages.insert(fingerprint, session.encrypt(PROTEUS_INITIAL_MESSAGE)?);
368 master_sessions.push(session.identifier().to_string());
369 }
370
371 let session_id_with_master = format!("session-{master_fingerprint}");
372 for c in clients.iter_mut() {
373 let fingerprint = c.fingerprint().await?;
374 spinner.update(format!(
375 "[Proteus] Step 2: Session {fingerprint}@{} -> master",
376 c.client_name()
377 ));
378 let message = messages.remove(&fingerprint).unwrap();
379 let message_recv = c.session_from_message(&session_id_with_master, &message).await?;
380 assert_eq!(PROTEUS_INITIAL_MESSAGE, message_recv);
381 }
382
383 assert!(messages.is_empty());
384
385 spinner.success("[Proteus] Step 2: Creating sessions [OK]");
386
387 let mut spinner = util::RunningProcess::new(
388 format!("[Proteus] Step 3: Roundtripping messages [0/{ROUNDTRIP_MSG_AMOUNT}]"),
389 true,
390 );
391
392 let mut prng = rand::thread_rng();
393 let mut message = [0u8; 128];
394 let mut master_messages_to_decrypt = std::collections::HashMap::new();
395 for i in 0..ROUNDTRIP_MSG_AMOUNT {
396 use rand::RngCore as _;
397
398 prng.fill_bytes(&mut message);
399
400 let mut messages_to_decrypt = transaction.proteus_encrypt_batched(&master_sessions, &message).await?;
401
402 for c in clients.iter_mut() {
403 let fingerprint = c.fingerprint().await?;
404 let decrypted_message = c
405 .decrypt(
406 &session_id_with_master,
407 &messages_to_decrypt.remove(&fingerprint).unwrap(),
408 )
409 .await?;
410
411 assert_eq!(
412 decrypted_message,
413 message,
414 "[Proteus] Messages differ [Client = {}::{}]",
415 c.client_name(),
416 c.client_type(),
417 );
418
419 let ciphertext = c.encrypt(&session_id_with_master, &decrypted_message).await?;
420 master_messages_to_decrypt.insert(fingerprint, ciphertext);
421 }
422
423 for (fingerprint, encrypted) in master_messages_to_decrypt.drain() {
424 let decrypted = transaction.proteus_decrypt(&fingerprint, &encrypted).await?;
425 assert_eq!(decrypted, message);
426 }
427
428 spinner.update(format!(
429 "[Proteus] Step 3: Roundtripping messages... [{i}/{ROUNDTRIP_MSG_AMOUNT}]"
430 ));
431 }
432
433 clients.clear();
434
435 spinner.success(format!(
436 "[Proteus] Step 3: Roundtripping {ROUNDTRIP_MSG_AMOUNT} messages... [OK]"
437 ));
438
439 let spinner = util::RunningProcess::new("[Proteus] Step 4: Deleting clients...", true);
440
441 Rc::into_inner(ios_client)
442 .expect("Only one strong reference to the interop client")
443 .wipe()
444 .await?;
445 Rc::into_inner(native_client)
446 .expect("Only one strong reference to the interop client")
447 .wipe()
448 .await?;
449 Rc::into_inner(ffi_client)
450 .expect("Only one strong reference to the interop client")
451 .wipe()
452 .await?;
453 Rc::into_inner(web_client)
454 .expect("Only one strong reference to the interop client")
455 .wipe()
456 .await?;
457 Rc::into_inner(cryptobox_native_client)
458 .expect("Only one strong reference to the interop client")
459 .wipe()
460 .await?;
461 Rc::into_inner(cryptobox_web_client)
462 .expect("Only one strong reference to the interop client")
463 .wipe()
464 .await?;
465
466 spinner.success("[Proteus] Step 4: Deleting clients [OK]");
467
468 Ok(())
469}