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::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
25// TODO: Add support for Android emulator. Tracking issue: WPB-9646
26// TODO: Add support for iOS emulator when on macOS. Tracking issue: WPB-9646
27fn 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// need to be handled like this because https://github.com/rust-lang/cargo/issues/5220, otherwise
82// it complains over a lacking main function
83#[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    // Check if we have a correct pwd
93    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    // because cannot use `#[tokio::main]` on wasm target because tokio does not compile on this target
102    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}