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