Skip to main content

openmls/test_utils/
mod.rs

1//! Test utilities
2#![allow(dead_code)]
3#![allow(unused_imports)]
4
5use std::{
6    fmt::Write as FmtWrite,
7    fs::File,
8    io::{BufReader, Write},
9};
10
11use openmls_basic_credential::SignatureKeyPair;
12pub use openmls_traits::{
13    storage::StorageProvider as StorageProviderTrait,
14    types::{Ciphersuite, HpkeKeyPair},
15    OpenMlsProvider,
16};
17use serde::{self, de::DeserializeOwned, Serialize};
18
19#[cfg(test)]
20use crate::group::tests_and_kats::utils::CredentialWithKeyAndSigner;
21pub use crate::utils::*;
22use crate::{
23    ciphersuite::{HpkePrivateKey, OpenMlsSignaturePublicKey},
24    credentials::{Credential, CredentialType, CredentialWithKey},
25    key_packages::{KeyPackage, KeyPackageBuilder},
26    prelude::KeyPackageBundle,
27    treesync::node::{
28        encryption_keys::{EncryptionKeyPair, EncryptionPrivateKey},
29        leaf_node::{Capabilities, CapabilitiesBuilder},
30    },
31};
32
33pub mod frankenstein;
34pub mod restricted_provider;
35pub mod storage_state;
36pub mod test_framework;
37
38pub mod single_group_test_framework;
39
40/// A capabilities builder covering exactly the two dimensions every leaf node
41/// must advertise about itself: `ciphersuite`, and a `Basic` credential.
42///
43/// Setting capabilities explicitly means they are taken at face value, so a
44/// test that needs a particular extension or proposal type advertised has to
45/// state the ciphersuite and credential type too. This is the base to chain
46/// `.extensions(...)` / `.proposals(...)` onto for that. Use
47/// `Capabilities::builder()` directly when the credential under test isn't
48/// `Basic`.
49///
50/// A test that doesn't care about capabilities at all should set none, and let
51/// them be derived from the leaf.
52///
53/// This helper must keep setting *exactly* these two dimensions. Tests rely on
54/// the ones it leaves empty: `capabilities_check.rs` builds a party through it
55/// specifically so that party does **not** support a custom proposal type, and
56/// adding `.proposals(...)` here would make that test vacuous without failing.
57pub fn minimal_capabilities_for(ciphersuite: Ciphersuite) -> CapabilitiesBuilder {
58    Capabilities::builder()
59        .ciphersuites(vec![ciphersuite])
60        .credentials(vec![CredentialType::Basic])
61}
62
63pub(crate) fn write(file_name: &str, obj: impl Serialize) {
64    let mut file = match File::create(file_name) {
65        Ok(f) => f,
66        Err(_) => panic!("Couldn't open file {file_name}."),
67    };
68    file.write_all(
69        serde_json::to_string_pretty(&obj)
70            .expect("Error serializing test vectors")
71            .as_bytes(),
72    )
73    .expect("Error writing test vector file");
74}
75
76// the macro is used in other files, suppress false positive
77#[allow(unused_macros)]
78macro_rules! read_json {
79    ($file_name:expr) => {{
80        let data = include_str!($file_name);
81        serde_json::from_str(data).expect(&format!("Error reading file {}", $file_name))
82    }};
83}
84
85pub(crate) fn read<T: DeserializeOwned>(file_name: &str) -> T {
86    let file = match File::open(file_name) {
87        Ok(f) => f,
88        Err(_) => panic!("Couldn't open file {file_name}."),
89    };
90    let reader = BufReader::new(file);
91    match serde_json::from_reader(reader) {
92        Ok(r) => r,
93        Err(e) => panic!("Error reading file.\n{e:?}"),
94    }
95}
96
97/// Convert `bytes` to a hex string.
98pub fn bytes_to_hex(bytes: &[u8]) -> String {
99    let mut hex = String::new();
100    for &b in bytes {
101        write!(&mut hex, "{b:02X}").expect("Unable to write to string");
102    }
103    hex
104}
105
106/// Convert a hex string to a byte vector.
107pub fn hex_to_bytes(hex: &str) -> Vec<u8> {
108    assert!(hex.len().is_multiple_of(2));
109    let mut bytes = Vec::new();
110    for i in 0..(hex.len() / 2) {
111        bytes.push(
112            u8::from_str_radix(&hex[2 * i..2 * i + 2], 16).expect("An unexpected error occurred."),
113        );
114    }
115    bytes
116}
117
118/// Convert a hex string to a byte vector.
119/// If the input is `None`, this returns an empty vector.
120pub fn hex_to_bytes_option(hex: Option<String>) -> Vec<u8> {
121    match hex {
122        Some(s) => hex_to_bytes(&s),
123        None => vec![],
124    }
125}
126
127// === Convenience functions ===
128
129#[cfg(test)]
130pub(crate) struct GroupCandidate {
131    pub identity: Vec<u8>,
132    pub key_package: KeyPackageBundle,
133    pub signature_keypair: SignatureKeyPair,
134    pub credential_with_key_and_signer: CredentialWithKeyAndSigner,
135}
136
137#[cfg(test)]
138pub(crate) fn generate_group_candidate(
139    identity: &[u8],
140    ciphersuite: Ciphersuite,
141    provider: &impl OpenMlsProvider,
142    use_store: bool,
143) -> GroupCandidate {
144    use crate::{credentials::BasicCredential, prelude::KeyPackageBundle};
145
146    let credential_with_key_and_signer = {
147        let credential = BasicCredential::new(identity.to_vec());
148
149        let signature_keypair = SignatureKeyPair::new(ciphersuite.signature_algorithm()).unwrap();
150
151        // Store if there is a key store.
152        if use_store {
153            signature_keypair.store(provider.storage()).unwrap();
154        }
155
156        let signature_pkey = OpenMlsSignaturePublicKey::new(
157            signature_keypair.to_public_vec().into(),
158            ciphersuite.signature_algorithm(),
159        )
160        .unwrap();
161
162        CredentialWithKeyAndSigner {
163            credential_with_key: CredentialWithKey {
164                credential: credential.into(),
165                signature_key: signature_pkey.into(),
166            },
167            signer: signature_keypair,
168        }
169    };
170
171    let key_package = {
172        let builder = KeyPackageBuilder::new();
173
174        if use_store {
175            builder
176                .build(
177                    ciphersuite,
178                    provider,
179                    &credential_with_key_and_signer.signer,
180                    credential_with_key_and_signer.credential_with_key.clone(),
181                )
182                .unwrap()
183        } else {
184            // We don't want to store anything. So...
185            let provider = OpenMlsRustCrypto::default();
186
187            let key_package_creation_result = builder
188                .build_without_storage(
189                    ciphersuite,
190                    &provider,
191                    &credential_with_key_and_signer.signer,
192                    credential_with_key_and_signer.credential_with_key.clone(),
193                )
194                .unwrap();
195
196            KeyPackageBundle::new(
197                key_package_creation_result.key_package,
198                key_package_creation_result.init_private_key,
199                key_package_creation_result
200                    .encryption_keypair
201                    .private_key()
202                    .clone(),
203            )
204        }
205    };
206
207    GroupCandidate {
208        identity: identity.to_vec(),
209        key_package,
210        signature_keypair: credential_with_key_and_signer.signer.clone(),
211        credential_with_key_and_signer,
212    }
213}
214
215#[cfg(all(
216    feature = "libcrux-provider",
217    not(any(
218        target_arch = "wasm32",
219        all(target_arch = "x86", target_os = "windows")
220    ))
221))]
222pub type OpenMlsLibcrux = openmls_libcrux_crypto::Provider;
223pub type OpenMlsRustCrypto = openmls_rust_crypto::OpenMlsRustCrypto;