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    // Check if we have a correct pwd
48    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    // because cannot use `#[tokio::main]` on wasm target because tokio does not compile on this target
57    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}