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