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