interop/
main.rs

1// Wire
2// Copyright (C) 2022 Wire Swiss GmbH
3
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see http://www.gnu.org/licenses/.
16
17#![cfg_attr(target_family = "wasm", allow(dead_code, unused_imports))]
18
19#[cfg(not(target_family = "wasm"))]
20use crate::clients::{EmulatedClient, EmulatedProteusClient};
21#[cfg(not(target_family = "wasm"))]
22use crate::util::{MlsTransportSuccessProvider, MlsTransportTestExt};
23use color_eyre::eyre::{Result, eyre};
24use core_crypto::prelude::CiphersuiteName;
25use std::rc::Rc;
26use std::sync::Arc;
27use tls_codec::Serialize;
28
29#[cfg(not(target_family = "wasm"))]
30mod build;
31#[cfg(not(target_family = "wasm"))]
32mod clients;
33#[cfg(not(target_family = "wasm"))]
34mod util;
35
36#[cfg(not(target_family = "wasm"))]
37const TEST_SERVER_PORT: &str = "8000";
38#[cfg(not(target_family = "wasm"))]
39const TEST_SERVER_URI: &str = const_format::concatcp!("http://localhost:", TEST_SERVER_PORT);
40
41const MLS_MAIN_CLIENTID: &[u8] = b"test_main";
42const MLS_CONVERSATION_ID: &[u8] = b"test_conversation";
43const ROUNDTRIP_MSG_AMOUNT: usize = 100;
44
45const CIPHERSUITE_IN_USE: CiphersuiteName = CiphersuiteName::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
46
47// TODO: Add support for Android emulator. Tracking issue: WPB-9646
48// TODO: Add support for iOS emulator when on macOS. Tracking issue: WPB-9646
49fn main() -> Result<()> {
50    run_test()
51}
52
53// need to be handled like this because https://github.com/rust-lang/cargo/issues/5220, otherwise
54// it complains over a lacking main function
55#[cfg(not(target_family = "wasm"))]
56fn run_test() -> Result<()> {
57    use std::time::{Duration, Instant};
58
59    use tokio::net::{TcpListener, TcpStream};
60
61    color_eyre::install()?;
62    if std::env::var("RUST_LOG").is_ok() || std::env::var("CI").is_ok() {
63        femme::start();
64    }
65
66    let force_webdriver_install = std::env::var("FORCE_WEBDRIVER_INSTALL").is_ok();
67
68    // Check if we have a correct pwd
69    let current_dir = std::env::current_dir()?;
70    if current_dir.ends_with("interop") {
71        let new_cwd = current_dir.parent().unwrap();
72        log::info!("cwd was {current_dir:?}; setting it to {new_cwd:?}");
73        std::env::set_current_dir(new_cwd)?;
74    }
75    let tempdir = tempfile::tempdir()?;
76
77    // because cannot use `#[tokio::main]` on wasm target because tokio does not compile on this target
78    let runtime = tokio::runtime::Builder::new_multi_thread()
79        .enable_all()
80        .build()
81        .unwrap();
82
83    runtime.block_on(async {
84        build::web::webdriver::setup_webdriver(force_webdriver_install).await?;
85
86        build::web::wasm::build_wasm(tempdir.path().to_path_buf()).await?;
87
88        let spinner = util::RunningProcess::new("Starting HTTP server...", false);
89        let http_server_hwnd = tokio::task::spawn(build::web::wasm::spawn_http_server(tempdir.path().to_path_buf()));
90        spinner.success(format!("HTTP server started at 0.0.0.0:{TEST_SERVER_PORT} [OK]"));
91
92        let mut spinner = util::RunningProcess::new("Starting WebDriver [ChromeDriver & GeckoDriver]...", false);
93        let chrome_driver_addr = TcpListener::bind("127.0.0.1:0").await?.local_addr()?;
94        let mut chrome_webdriver = build::web::webdriver::start_webdriver_chrome(&chrome_driver_addr).await?;
95        spinner.update("Sleeping to wait for Webdrivers to get ready...");
96        let timeout = Duration::from_secs(5);
97        let start = Instant::now();
98        while start.elapsed() < timeout {
99            let chrome_ready = TcpStream::connect(&chrome_driver_addr).await.is_ok();
100            if chrome_ready {
101                break;
102            }
103            tokio::time::sleep(Duration::from_millis(100)).await;
104        }
105        spinner.success("WebDriver [OK]");
106
107        run_mls_test(&chrome_driver_addr).await?;
108
109        #[cfg(feature = "proteus")]
110        run_proteus_test(&chrome_driver_addr).await?;
111
112        chrome_webdriver.kill().await?;
113        http_server_hwnd.abort();
114        Ok(())
115    })
116}
117
118#[cfg(target_family = "wasm")]
119fn run_test() -> Result<()> {
120    panic!("E2E tests cannot be run on WASM")
121}
122
123#[cfg(not(target_family = "wasm"))]
124async fn run_mls_test(chrome_driver_addr: &std::net::SocketAddr) -> Result<()> {
125    use core_crypto::prelude::*;
126    use rand::distributions::DistString;
127
128    log::info!("Using ciphersuite {}", CIPHERSUITE_IN_USE);
129
130    let spinner = util::RunningProcess::new("[MLS] Step 0: Initializing clients & env...", true);
131
132    let ios_client = Rc::new(clients::corecrypto::ios::CoreCryptoIosClient::new().await?);
133    let native_client = Rc::new(clients::corecrypto::native::CoreCryptoNativeClient::new().await?);
134    let ffi_client = Rc::new(clients::corecrypto::ffi::CoreCryptoFfiClient::new().await?);
135    let web_client = Rc::new(clients::corecrypto::web::CoreCryptoWebClient::new(chrome_driver_addr).await?);
136
137    let mut clients: Vec<Rc<dyn clients::EmulatedMlsClient>> = vec![
138        native_client.clone(),
139        ffi_client.clone(),
140        web_client.clone(),
141        ios_client.clone(),
142    ];
143
144    let ciphersuites = vec![CIPHERSUITE_IN_USE.into()];
145    let configuration = MlsCentralConfiguration::try_new(
146        "whatever".into(),
147        "test".into(),
148        Some(MLS_MAIN_CLIENTID.into()),
149        ciphersuites,
150        None,
151        Some(100),
152    )?;
153    let master_client = MlsCentral::try_new_in_memory(configuration).await?;
154
155    let conversation_id = MLS_CONVERSATION_ID.to_vec();
156    let config = MlsConversationConfiguration {
157        ciphersuite: CIPHERSUITE_IN_USE.into(),
158        ..Default::default()
159    };
160    let cc = CoreCrypto::from(master_client.clone());
161
162    let success_provider = Arc::new(MlsTransportSuccessProvider::default());
163
164    cc.provide_transport(success_provider.clone()).await;
165    let transaction = cc.new_transaction().await?;
166    transaction
167        .new_conversation(&conversation_id, MlsCredentialType::Basic, config)
168        .await?;
169
170    spinner.success("[MLS] Step 0: Initializing clients [OK]");
171
172    let spinner = util::RunningProcess::new("[MLS] Step 1: Fetching KeyPackages from clients...", true);
173
174    use tls_codec::Deserialize as _;
175    let mut key_packages = vec![];
176    for c in clients.iter_mut() {
177        let kp = c.get_keypackage().await?;
178        let kp = KeyPackageIn::tls_deserialize(&mut kp.as_slice())?;
179        key_packages.push(kp);
180    }
181
182    spinner.success("[MLS] Step 1: KeyPackages [OK]");
183
184    let spinner = util::RunningProcess::new("[MLS] Step 2: Adding clients to conversation...", true);
185
186    transaction
187        .conversation(&conversation_id)
188        .await?
189        .add_members(key_packages)
190        .await?;
191
192    let conversation_add_msg = success_provider.latest_welcome_message().await;
193    let welcome_raw = conversation_add_msg.tls_serialize_detached()?;
194
195    for c in clients.iter_mut() {
196        let conversation_id_from_welcome = c.process_welcome(&welcome_raw).await?;
197        assert_eq!(conversation_id_from_welcome, conversation_id);
198    }
199
200    spinner.success("[MLS] Step 2: Added clients [OK]");
201
202    let mut spinner = util::RunningProcess::new(
203        format!("[MLS] Step 3: Roundtripping messages [0/{ROUNDTRIP_MSG_AMOUNT}]"),
204        true,
205    );
206
207    let mut prng = rand::thread_rng();
208    let mut message;
209    for i in 1..=ROUNDTRIP_MSG_AMOUNT {
210        message = rand::distributions::Alphanumeric.sample_string(&mut prng, 16);
211
212        log::info!(
213            "Master client [{}] >>> {}",
214            hex::encode(master_client.client_id().await?.as_slice()),
215            message
216        );
217
218        let mut message_to_decrypt = transaction
219            .conversation(&conversation_id)
220            .await?
221            .encrypt_message(&message)
222            .await?;
223
224        for c in clients.iter_mut() {
225            let decrypted_message_raw = c
226                .decrypt_message(&conversation_id, &message_to_decrypt)
227                .await?
228                .ok_or_else(|| {
229                    eyre!(
230                        "[MLS] No message, something went very wrong [Client = {}]",
231                        c.client_type()
232                    )
233                })?;
234
235            let decrypted_message = String::from_utf8(decrypted_message_raw)?;
236
237            log::info!(
238                "{} [{}] <<< {}",
239                c.client_name(),
240                hex::encode(c.client_id()),
241                decrypted_message
242            );
243
244            assert_eq!(
245                decrypted_message,
246                message,
247                "[MLS] Messages differ [Client = {}]",
248                c.client_type()
249            );
250
251            message_to_decrypt = c
252                .encrypt_message(&conversation_id, decrypted_message.as_bytes())
253                .await?;
254        }
255
256        let decrypted_master_raw = transaction
257            .conversation(&conversation_id)
258            .await
259            .unwrap()
260            .decrypt_message(message_to_decrypt)
261            .await?
262            .app_msg
263            .ok_or_else(|| eyre!("[MLS] No message received on master client"))?;
264
265        let decrypted_master = String::from_utf8(decrypted_master_raw)?;
266
267        assert_eq!(decrypted_master, message);
268
269        spinner.update(format!(
270            "[MLS] Step 3: Roundtripping messages... [{i}/{ROUNDTRIP_MSG_AMOUNT}]"
271        ));
272    }
273
274    clients.clear();
275
276    spinner.success(format!(
277        "[MLS] Step 3: Roundtripping {ROUNDTRIP_MSG_AMOUNT} messages... [OK]"
278    ));
279
280    let spinner = util::RunningProcess::new("[MLS] Step 4: Deleting clients...", true);
281
282    Rc::into_inner(ios_client)
283        .expect("Only one strong reference to the interop client")
284        .wipe()
285        .await?;
286    Rc::into_inner(native_client)
287        .expect("Only one strong reference to the interop client")
288        .wipe()
289        .await?;
290    Rc::into_inner(ffi_client)
291        .expect("Only one strong reference to the interop client")
292        .wipe()
293        .await?;
294    Rc::into_inner(web_client)
295        .expect("Only one strong reference to the interop client")
296        .wipe()
297        .await?;
298
299    spinner.success("[MLS] Step 4: Deleting clients [OK]");
300
301    Ok(())
302}
303
304#[cfg(all(not(target_family = "wasm"), feature = "proteus"))]
305async fn run_proteus_test(chrome_driver_addr: &std::net::SocketAddr) -> Result<()> {
306    use core_crypto::prelude::*;
307
308    let spinner = util::RunningProcess::new("[Proteus] Step 0: Initializing clients & env...", true);
309
310    let mut ios_client = clients::corecrypto::ios::CoreCryptoIosClient::new().await?;
311    let mut native_client = clients::corecrypto::native::CoreCryptoNativeClient::new().await?;
312    let mut ffi_client = clients::corecrypto::ffi::CoreCryptoFfiClient::new().await?;
313    let mut web_client = clients::corecrypto::web::CoreCryptoWebClient::new(chrome_driver_addr).await?;
314    let mut cryptobox_native_client = clients::cryptobox::native::CryptoboxNativeClient::new();
315    let mut cryptobox_web_client = clients::cryptobox::web::CryptoboxWebClient::new(chrome_driver_addr).await?;
316
317    ios_client.init().await?;
318    native_client.init().await?;
319    ffi_client.init().await?;
320    web_client.init().await?;
321    cryptobox_native_client.init().await?;
322    cryptobox_web_client.init().await?;
323
324    let ios_client = Rc::new(ios_client);
325    let native_client = Rc::new(native_client);
326    let ffi_client = Rc::new(ffi_client);
327    let web_client = Rc::new(web_client);
328    let cryptobox_native_client = Rc::new(cryptobox_native_client);
329    let cryptobox_web_client = Rc::new(cryptobox_web_client);
330
331    let mut clients: Vec<Rc<dyn clients::EmulatedProteusClient>> = vec![
332        ios_client.clone(),
333        native_client.clone(),
334        ffi_client.clone(),
335        web_client.clone(),
336        cryptobox_native_client.clone(),
337        cryptobox_web_client.clone(),
338    ];
339
340    let configuration = MlsCentralConfiguration::try_new(
341        "whatever".into(),
342        "test".into(),
343        Some(MLS_MAIN_CLIENTID.into()),
344        vec![MlsCiphersuite::default()],
345        None,
346        Some(100),
347    )?;
348    let master_client = CoreCrypto::from(MlsCentral::try_new_in_memory(configuration).await?);
349    let transaction = master_client.new_transaction().await?;
350    transaction.proteus_init().await?;
351
352    let master_fingerprint = master_client.proteus_fingerprint().await?;
353
354    spinner.success("[Proteus] Step 0: Initializing clients [OK]");
355
356    let mut spinner = util::RunningProcess::new("[Proteus] Step 1: Fetching PreKeys from clients...", true);
357
358    let mut client_type_mapping = std::collections::HashMap::new();
359    let mut prekeys = std::collections::HashMap::new();
360    for c in clients.iter_mut() {
361        let client_name = c.client_name();
362        spinner.update(format!("[Proteus] Step 1: PreKeys - {client_name}"));
363        let fingerprint = c.fingerprint().await?;
364        spinner.update(format!("[Proteus] Step 1: PreKeys - {fingerprint}@{client_name}"));
365        client_type_mapping.insert(fingerprint.clone(), client_name.to_string());
366        let prekey = c.get_prekey().await?;
367        prekeys.insert(fingerprint, prekey);
368    }
369
370    spinner.success("[Proteus] Step 1: PreKeys [OK]");
371
372    let mut spinner = util::RunningProcess::new("[Proteus] Step 2: Creating sessions...", true);
373
374    let mut master_sessions = vec![];
375    let mut messages = std::collections::HashMap::new();
376    const PROTEUS_INITIAL_MESSAGE: &[u8] = b"Hello world!";
377    for (fingerprint, prekey) in prekeys {
378        spinner.update(format!(
379            "[Proteus] Step 2: Session master -> {fingerprint}@{}",
380            client_type_mapping[&fingerprint]
381        ));
382        let session_arc = transaction.proteus_session_from_prekey(&fingerprint, &prekey).await?;
383        let mut session = session_arc.write().await;
384        messages.insert(fingerprint, session.encrypt(PROTEUS_INITIAL_MESSAGE)?);
385        master_sessions.push(session.identifier().to_string());
386    }
387
388    let session_id_with_master = format!("session-{master_fingerprint}");
389    for c in clients.iter_mut() {
390        let fingerprint = c.fingerprint().await?;
391        spinner.update(format!(
392            "[Proteus] Step 2: Session {fingerprint}@{} -> master",
393            c.client_name()
394        ));
395        let message = messages.remove(&fingerprint).unwrap();
396        let message_recv = c.session_from_message(&session_id_with_master, &message).await?;
397        assert_eq!(PROTEUS_INITIAL_MESSAGE, message_recv);
398    }
399
400    assert!(messages.is_empty());
401
402    spinner.success("[Proteus] Step 2: Creating sessions [OK]");
403
404    let mut spinner = util::RunningProcess::new(
405        format!("[Proteus] Step 3: Roundtripping messages [0/{ROUNDTRIP_MSG_AMOUNT}]"),
406        true,
407    );
408
409    let mut prng = rand::thread_rng();
410    let mut message = [0u8; 128];
411    let mut master_messages_to_decrypt = std::collections::HashMap::new();
412    for i in 0..ROUNDTRIP_MSG_AMOUNT {
413        use rand::RngCore as _;
414
415        prng.fill_bytes(&mut message);
416
417        let mut messages_to_decrypt = transaction.proteus_encrypt_batched(&master_sessions, &message).await?;
418
419        for c in clients.iter_mut() {
420            let fingerprint = c.fingerprint().await?;
421            let decrypted_message = c
422                .decrypt(
423                    &session_id_with_master,
424                    &messages_to_decrypt.remove(&fingerprint).unwrap(),
425                )
426                .await?;
427
428            assert_eq!(
429                decrypted_message,
430                message,
431                "[Proteus] Messages differ [Client = {}::{}]",
432                c.client_name(),
433                c.client_type(),
434            );
435
436            let ciphertext = c.encrypt(&session_id_with_master, &decrypted_message).await?;
437            master_messages_to_decrypt.insert(fingerprint, ciphertext);
438        }
439
440        for (fingerprint, encrypted) in master_messages_to_decrypt.drain() {
441            let decrypted = transaction.proteus_decrypt(&fingerprint, &encrypted).await?;
442            assert_eq!(decrypted, message);
443        }
444
445        spinner.update(format!(
446            "[Proteus] Step 3: Roundtripping messages... [{i}/{ROUNDTRIP_MSG_AMOUNT}]"
447        ));
448    }
449
450    clients.clear();
451
452    spinner.success(format!(
453        "[Proteus] Step 3: Roundtripping {ROUNDTRIP_MSG_AMOUNT} messages... [OK]"
454    ));
455
456    let spinner = util::RunningProcess::new("[Proteus] Step 4: Deleting clients...", true);
457
458    Rc::into_inner(ios_client)
459        .expect("Only one strong reference to the interop client")
460        .wipe()
461        .await?;
462    Rc::into_inner(native_client)
463        .expect("Only one strong reference to the interop client")
464        .wipe()
465        .await?;
466    Rc::into_inner(ffi_client)
467        .expect("Only one strong reference to the interop client")
468        .wipe()
469        .await?;
470    Rc::into_inner(web_client)
471        .expect("Only one strong reference to the interop client")
472        .wipe()
473        .await?;
474    Rc::into_inner(cryptobox_native_client)
475        .expect("Only one strong reference to the interop client")
476        .wipe()
477        .await?;
478    Rc::into_inner(cryptobox_web_client)
479        .expect("Only one strong reference to the interop client")
480        .wipe()
481        .await?;
482
483    spinner.success("[Proteus] Step 4: Deleting clients [OK]");
484
485    Ok(())
486}