Skip to main content

core_crypto/mls_provider/
mod.rs

1// TODO: remove this expect(unreachable_pub) once the E2EI parts have been coupled.
2#![expect(unreachable_pub)]
3use std::sync::Arc;
4
5use async_lock::RwLock;
6pub use core_crypto_keystore::Database;
7
8mod crypto_provider;
9mod error;
10
11pub(crate) use crypto_provider::CRYPTO;
12pub use crypto_provider::RustCrypto;
13pub use error::{Error, MlsProviderResult};
14use openmls_traits::{
15    authentication_service::{CredentialAuthenticationStatus, CredentialRef},
16    crypto::OpenMlsCrypto,
17    types::{
18        AeadType, Ciphersuite, CryptoError, ExporterSecret, HashType, HpkeCiphertext, HpkeConfig, HpkeKeyPair,
19        KemOutput, SignatureScheme,
20    },
21};
22// TODO: remove this allow(unused) once the E2EI parts have been coupled.
23#[allow(unused)]
24pub use wire_e2e_identity::pki::{CertProfile, CertificateGenerationArgs, PkiKeypair};
25use wire_e2e_identity::pki_env::PkiEnvironment;
26
27/// 32-byte raw entropy seed
28pub type RawEntropySeed = <rand_chacha::ChaCha20Rng as rand::SeedableRng>::Seed;
29
30#[derive(Debug, Clone, Default, PartialEq, Eq, zeroize::ZeroizeOnDrop)]
31#[repr(transparent)]
32/// Wrapped 32-byte entropy seed with bounds check
33pub struct EntropySeed(RawEntropySeed);
34
35impl EntropySeed {
36    /// The expected length of the entopy seed, in bytes.
37    pub const EXPECTED_LEN: usize = std::mem::size_of::<EntropySeed>() / std::mem::size_of::<u8>();
38
39    /// Create an entropy seed from the provided slice.
40    pub fn try_from_slice(data: &[u8]) -> MlsProviderResult<Self> {
41        if data.len() < Self::EXPECTED_LEN {
42            return Err(Error::EntropySeedLength {
43                actual: data.len(),
44                expected: Self::EXPECTED_LEN,
45            });
46        }
47
48        let mut inner = RawEntropySeed::default();
49        inner.copy_from_slice(&data[..Self::EXPECTED_LEN]);
50
51        Ok(Self(inner))
52    }
53
54    /// Create an entropy seed from the provided raw entropy seed.
55    pub fn from_raw(raw: RawEntropySeed) -> Self {
56        Self(raw)
57    }
58}
59
60impl std::ops::Deref for EntropySeed {
61    type Target = [u8];
62    fn deref(&self) -> &Self::Target {
63        &self.0
64    }
65}
66
67impl std::ops::DerefMut for EntropySeed {
68    fn deref_mut(&mut self) -> &mut Self::Target {
69        &mut self.0
70    }
71}
72
73#[derive(Debug)]
74pub struct AuthenticationService {
75    /// The PKI Environment type is complicated, but it's all necessary:
76    ///
77    /// - The inner `Arc` derives from two facts: the PKI environment is provided across FFI, and it's `!Clone`, so we
78    ///   have to retain that `Arc` because the foreign environment is more-or-less guaranteed to have kept a reference
79    ///   to it.
80    /// - The `Option` is there because the PKI environment is initially unset and may never be set, according to
81    ///   client behavior.
82    /// - The `RwLock` is there because we need to be able to set the PKI environment, implying interior mutability.
83    pki_env: RwLock<Option<Arc<PkiEnvironment>>>,
84}
85
86impl AuthenticationService {
87    pub async fn pki_env(&self) -> Option<Arc<PkiEnvironment>> {
88        self.pki_env.read().await.clone()
89    }
90}
91
92#[cfg_attr(target_os = "unknown", async_trait::async_trait(?Send))]
93#[cfg_attr(not(target_os = "unknown"), async_trait::async_trait)]
94impl openmls_traits::authentication_service::AuthenticationServiceDelegate for AuthenticationService {
95    async fn validate_credential<'a>(&'a self, credential: CredentialRef<'a>) -> CredentialAuthenticationStatus {
96        match credential {
97            // We assume that Basic credentials are always valid
98            CredentialRef::Basic { .. } => CredentialAuthenticationStatus::Valid,
99
100            CredentialRef::X509 { .. } => match self.pki_env.read().await.as_ref() {
101                None => {
102                    log::warn!("unable to validate X509 credentials: PKI environment is unset");
103                    CredentialAuthenticationStatus::Unknown
104                }
105                Some(pki_env) => pki_env.validate_credential(credential).await,
106            },
107        }
108    }
109}
110
111/// The MLS crypto provider
112#[derive(Debug, Clone)]
113pub struct CryptoProvider {
114    crypto: Arc<RustCrypto>,
115    key_store: Arc<Database>,
116    auth_service: Arc<AuthenticationService>,
117}
118
119impl CryptoProvider {
120    /// Construct a crypto provider with defaults and a given [Database].
121    ///
122    /// See also:
123    ///
124    /// - [Database::open]
125    pub fn new(key_store: Arc<Database>) -> Self {
126        Self::new_with_pki_env(key_store, None)
127    }
128
129    /// Construct a crypto provider with the given database and the PKI environment.
130    pub fn new_with_pki_env(key_store: Arc<Database>, pki_env: Option<Arc<PkiEnvironment>>) -> Self {
131        let pki_env = RwLock::new(pki_env);
132        let auth_service = Arc::new(AuthenticationService { pki_env });
133        Self {
134            key_store,
135            crypto: Arc::clone(&CRYPTO),
136            auth_service,
137        }
138    }
139
140    /// Set pki_env to a new shared pki environment provider
141    pub async fn set_pki_environment(&mut self, pki_env: Option<Arc<PkiEnvironment>>) {
142        *self.auth_service.pki_env.write().await = pki_env;
143    }
144
145    /// Returns whether we have a PKI env setup
146    pub async fn is_pki_env_setup(&self) -> bool {
147        self.auth_service.pki_env.read().await.is_some()
148    }
149
150    /// Reseeds the internal CSPRNG entropy pool with a brand new one.
151    ///
152    /// If [None] is provided, the new entropy will be pulled through the current OS target's capabilities
153    pub fn reseed(&self, entropy_seed: Option<EntropySeed>) -> MlsProviderResult<()> {
154        self.crypto.reseed(entropy_seed)
155    }
156
157    /// Encrypt `ptxt` via HPKE PSK mode.
158    #[expect(clippy::too_many_arguments)]
159    pub fn hpke_seal_psk(
160        &self,
161        config: HpkeConfig,
162        pk_r: &[u8],
163        info: &[u8],
164        aad: &[u8],
165        psk: &[u8],
166        psk_id: &[u8],
167        ptxt: &[u8],
168    ) -> Result<HpkeCiphertext, CryptoError> {
169        self.crypto.hpke_seal_psk(config, pk_r, info, aad, psk, psk_id, ptxt)
170    }
171}
172
173impl openmls_traits::OpenMlsCryptoProvider for CryptoProvider {
174    type CryptoProvider = RustCrypto;
175    type RandProvider = RustCrypto;
176    type KeyStoreProvider = Database;
177    type AuthenticationServiceProvider = AuthenticationService;
178
179    fn crypto(&self) -> &Self::CryptoProvider {
180        &self.crypto
181    }
182
183    fn rand(&self) -> &Self::RandProvider {
184        &self.crypto
185    }
186
187    fn key_store(&self) -> &Self::KeyStoreProvider {
188        &self.key_store
189    }
190
191    fn authentication_service(&self) -> &Self::AuthenticationServiceProvider {
192        &self.auth_service
193    }
194}
195
196/// Passthrough implementation of crypto functionality for references to `MlsCryptoProvider`.
197impl OpenMlsCrypto for &CryptoProvider {
198    fn supports(&self, ciphersuite: Ciphersuite) -> Result<(), CryptoError> {
199        self.crypto.supports(ciphersuite)
200    }
201
202    fn supported_ciphersuites(&self) -> Vec<Ciphersuite> {
203        self.crypto.supported_ciphersuites()
204    }
205
206    fn hkdf_extract(
207        &self,
208        hash_type: HashType,
209        salt: &[u8],
210        ikm: &[u8],
211    ) -> Result<tls_codec::SecretVLBytes, CryptoError> {
212        self.crypto.hkdf_extract(hash_type, salt, ikm)
213    }
214
215    fn hkdf_expand(
216        &self,
217        hash_type: HashType,
218        prk: &[u8],
219        info: &[u8],
220        okm_len: usize,
221    ) -> Result<tls_codec::SecretVLBytes, CryptoError> {
222        self.crypto.hkdf_expand(hash_type, prk, info, okm_len)
223    }
224
225    fn hash(&self, hash_type: HashType, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
226        self.crypto.hash(hash_type, data)
227    }
228
229    fn aead_encrypt(
230        &self,
231        alg: AeadType,
232        key: &[u8],
233        data: &[u8],
234        nonce: &[u8],
235        aad: &[u8],
236    ) -> Result<Vec<u8>, CryptoError> {
237        self.crypto.aead_encrypt(alg, key, data, nonce, aad)
238    }
239
240    fn aead_decrypt(
241        &self,
242        alg: AeadType,
243        key: &[u8],
244        ct_tag: &[u8],
245        nonce: &[u8],
246        aad: &[u8],
247    ) -> Result<Vec<u8>, CryptoError> {
248        self.crypto.aead_decrypt(alg, key, ct_tag, nonce, aad)
249    }
250
251    fn signature_key_gen(&self, alg: SignatureScheme) -> Result<(Vec<u8>, Vec<u8>), CryptoError> {
252        self.crypto.signature_key_gen(alg)
253    }
254
255    fn signature_public_key_len(&self, alg: SignatureScheme) -> usize {
256        self.crypto.signature_public_key_len(alg)
257    }
258
259    fn validate_signature_key(&self, alg: SignatureScheme, key: &[u8]) -> Result<(), CryptoError> {
260        self.crypto.validate_signature_key(alg, key)
261    }
262
263    fn verify_signature(
264        &self,
265        alg: SignatureScheme,
266        data: &[u8],
267        pk: &[u8],
268        signature: &[u8],
269    ) -> Result<(), CryptoError> {
270        self.crypto.verify_signature(alg, data, pk, signature)
271    }
272
273    fn sign(&self, alg: SignatureScheme, data: &[u8], key: &[u8]) -> Result<Vec<u8>, CryptoError> {
274        self.crypto.sign(alg, data, key)
275    }
276
277    fn hpke_seal(
278        &self,
279        config: HpkeConfig,
280        pk_r: &[u8],
281        info: &[u8],
282        aad: &[u8],
283        ptxt: &[u8],
284    ) -> Result<HpkeCiphertext, CryptoError> {
285        self.crypto.hpke_seal(config, pk_r, info, aad, ptxt)
286    }
287
288    fn hpke_open(
289        &self,
290        config: HpkeConfig,
291        input: &HpkeCiphertext,
292        sk_r: &[u8],
293        info: &[u8],
294        aad: &[u8],
295    ) -> Result<Vec<u8>, CryptoError> {
296        self.crypto.hpke_open(config, input, sk_r, info, aad)
297    }
298
299    fn hpke_setup_sender_and_export(
300        &self,
301        config: HpkeConfig,
302        pk_r: &[u8],
303        info: &[u8],
304        exporter_context: &[u8],
305        exporter_length: usize,
306    ) -> Result<(KemOutput, ExporterSecret), CryptoError> {
307        self.crypto
308            .hpke_setup_sender_and_export(config, pk_r, info, exporter_context, exporter_length)
309    }
310
311    fn hpke_setup_receiver_and_export(
312        &self,
313        config: HpkeConfig,
314        enc: &[u8],
315        sk_r: &[u8],
316        info: &[u8],
317        exporter_context: &[u8],
318        exporter_length: usize,
319    ) -> Result<ExporterSecret, CryptoError> {
320        self.crypto
321            .hpke_setup_receiver_and_export(config, enc, sk_r, info, exporter_context, exporter_length)
322    }
323
324    fn derive_hpke_keypair(&self, config: HpkeConfig, ikm: &[u8]) -> Result<HpkeKeyPair, CryptoError> {
325        self.crypto.derive_hpke_keypair(config, ikm)
326    }
327}