interop/
main.rs

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
28// TODO: Add support for Android emulator. Tracking issue: WPB-9646
29// TODO: Add support for iOS emulator when on macOS. Tracking issue: WPB-9646
30fn main() -> Result<()> {
31    run_test()
32}
33
34// need to be handled like this because https://github.com/rust-lang/cargo/issues/5220, otherwise
35// it complains over a lacking main function
36#[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    // Check if we have a correct pwd
50    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    // because cannot use `#[tokio::main]` on wasm target because tokio does not compile on this target
59    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}