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
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
42// TODO: Add support for Android emulator. Tracking issue: WPB-9646
43// TODO: Add support for iOS emulator when on macOS. Tracking issue: WPB-9646
44fn main() -> Result<()> {
45    run_test()
46}
47
48// need to be handled like this because https://github.com/rust-lang/cargo/issues/5220, otherwise
49// it complains over a lacking main function
50#[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    // Check if we have a correct pwd
64    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    // because cannot use `#[tokio::main]` on wasm target because tokio does not compile on this target
72    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}