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::{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) => {
106                    use CredentialAuthenticationStatus::*;
107                    // ? Revoked and expired credentials are A-OK. They still degrade conversations though.
108                    // TODO: update this after WPB-25524
109                    match pki_env.validate_credential(credential).await {
110                        Revoked => Valid,
111                        Expired => Valid,
112                        status => status,
113                    }
114                }
115            },
116        }
117    }
118}
119
120/// The MLS crypto provider
121#[derive(Debug, Clone)]
122pub struct CryptoProvider {
123    crypto: Arc<RustCrypto>,
124    key_store: Arc<Database>,
125    auth_service: Arc<AuthenticationService>,
126}
127
128impl CryptoProvider {
129    /// Construct a crypto provider with defaults and a given [Database].
130    ///
131    /// See also:
132    ///
133    /// - [Database::open]
134    pub fn new(key_store: Arc<Database>) -> Self {
135        Self::new_with_pki_env(key_store, None)
136    }
137
138    /// Construct a crypto provider with the given database and the PKI environment.
139    pub fn new_with_pki_env(key_store: Arc<Database>, pki_env: Option<Arc<PkiEnvironment>>) -> Self {
140        let pki_env = RwLock::new(pki_env);
141        let auth_service = Arc::new(AuthenticationService { pki_env });
142        Self {
143            key_store,
144            crypto: Arc::clone(&CRYPTO),
145            auth_service,
146        }
147    }
148
149    /// Set pki_env to a new shared pki environment provider
150    pub async fn set_pki_environment(&mut self, pki_env: Option<Arc<PkiEnvironment>>) {
151        *self.auth_service.pki_env.write().await = pki_env;
152    }
153
154    /// Returns whether we have a PKI env setup
155    pub async fn is_pki_env_setup(&self) -> bool {
156        self.auth_service.pki_env.read().await.is_some()
157    }
158
159    /// Reseeds the internal CSPRNG entropy pool with a brand new one.
160    ///
161    /// If [None] is provided, the new entropy will be pulled through the current OS target's capabilities
162    pub fn reseed(&self, entropy_seed: Option<EntropySeed>) -> MlsProviderResult<()> {
163        self.crypto.reseed(entropy_seed)
164    }
165
166    /// Encrypt `ptxt` via HPKE PSK mode.
167    #[expect(clippy::too_many_arguments)]
168    pub fn hpke_seal_psk(
169        &self,
170        config: HpkeConfig,
171        pk_r: &[u8],
172        info: &[u8],
173        aad: &[u8],
174        psk: &[u8],
175        psk_id: &[u8],
176        ptxt: &[u8],
177    ) -> Result<HpkeCiphertext, CryptoError> {
178        self.crypto.hpke_seal_psk(config, pk_r, info, aad, psk, psk_id, ptxt)
179    }
180
181    /// Decrypt `input` via HPKE PSK mode.
182    #[expect(clippy::too_many_arguments)]
183    pub fn hpke_open_psk(
184        &self,
185        config: HpkeConfig,
186        input: &HpkeCiphertext,
187        sk_r: &[u8],
188        info: &[u8],
189        aad: &[u8],
190        psk: &[u8],
191        psk_id: &[u8],
192    ) -> Result<Vec<u8>, CryptoError> {
193        self.crypto.hpke_open_psk(config, input, sk_r, info, aad, psk, psk_id)
194    }
195}
196
197impl openmls_traits::OpenMlsCryptoProvider for CryptoProvider {
198    type CryptoProvider = RustCrypto;
199    type RandProvider = RustCrypto;
200    type KeyStoreProvider = Database;
201    type AuthenticationServiceProvider = AuthenticationService;
202
203    fn crypto(&self) -> &Self::CryptoProvider {
204        &self.crypto
205    }
206
207    fn rand(&self) -> &Self::RandProvider {
208        &self.crypto
209    }
210
211    fn key_store(&self) -> &Self::KeyStoreProvider {
212        &self.key_store
213    }
214
215    fn authentication_service(&self) -> &Self::AuthenticationServiceProvider {
216        &self.auth_service
217    }
218}
219
220/// Passthrough implementation of crypto functionality for references to `MlsCryptoProvider`.
221impl OpenMlsCrypto for &CryptoProvider {
222    fn supports(&self, ciphersuite: Ciphersuite) -> Result<(), CryptoError> {
223        self.crypto.supports(ciphersuite)
224    }
225
226    fn supported_ciphersuites(&self) -> Vec<Ciphersuite> {
227        self.crypto.supported_ciphersuites()
228    }
229
230    fn hkdf_extract(
231        &self,
232        hash_type: HashType,
233        salt: &[u8],
234        ikm: &[u8],
235    ) -> Result<tls_codec::SecretVLBytes, CryptoError> {
236        self.crypto.hkdf_extract(hash_type, salt, ikm)
237    }
238
239    fn hkdf_expand(
240        &self,
241        hash_type: HashType,
242        prk: &[u8],
243        info: &[u8],
244        okm_len: usize,
245    ) -> Result<tls_codec::SecretVLBytes, CryptoError> {
246        self.crypto.hkdf_expand(hash_type, prk, info, okm_len)
247    }
248
249    fn hash(&self, hash_type: HashType, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
250        self.crypto.hash(hash_type, data)
251    }
252
253    fn aead_encrypt(
254        &self,
255        alg: AeadType,
256        key: &[u8],
257        data: &[u8],
258        nonce: &[u8],
259        aad: &[u8],
260    ) -> Result<Vec<u8>, CryptoError> {
261        self.crypto.aead_encrypt(alg, key, data, nonce, aad)
262    }
263
264    fn aead_decrypt(
265        &self,
266        alg: AeadType,
267        key: &[u8],
268        ct_tag: &[u8],
269        nonce: &[u8],
270        aad: &[u8],
271    ) -> Result<Vec<u8>, CryptoError> {
272        self.crypto.aead_decrypt(alg, key, ct_tag, nonce, aad)
273    }
274
275    fn signature_key_gen(&self, alg: SignatureScheme) -> Result<(Vec<u8>, Vec<u8>), CryptoError> {
276        self.crypto.signature_key_gen(alg)
277    }
278
279    fn signature_public_key_len(&self, alg: SignatureScheme) -> usize {
280        self.crypto.signature_public_key_len(alg)
281    }
282
283    fn validate_signature_key(&self, alg: SignatureScheme, key: &[u8]) -> Result<(), CryptoError> {
284        self.crypto.validate_signature_key(alg, key)
285    }
286
287    fn verify_signature(
288        &self,
289        alg: SignatureScheme,
290        data: &[u8],
291        pk: &[u8],
292        signature: &[u8],
293    ) -> Result<(), CryptoError> {
294        self.crypto.verify_signature(alg, data, pk, signature)
295    }
296
297    fn sign(&self, alg: SignatureScheme, data: &[u8], key: &[u8]) -> Result<Vec<u8>, CryptoError> {
298        self.crypto.sign(alg, data, key)
299    }
300
301    fn hpke_seal(
302        &self,
303        config: HpkeConfig,
304        pk_r: &[u8],
305        info: &[u8],
306        aad: &[u8],
307        ptxt: &[u8],
308    ) -> Result<HpkeCiphertext, CryptoError> {
309        self.crypto.hpke_seal(config, pk_r, info, aad, ptxt)
310    }
311
312    fn hpke_open(
313        &self,
314        config: HpkeConfig,
315        input: &HpkeCiphertext,
316        sk_r: &[u8],
317        info: &[u8],
318        aad: &[u8],
319    ) -> Result<Vec<u8>, CryptoError> {
320        self.crypto.hpke_open(config, input, sk_r, info, aad)
321    }
322
323    fn hpke_setup_sender_and_export(
324        &self,
325        config: HpkeConfig,
326        pk_r: &[u8],
327        info: &[u8],
328        exporter_context: &[u8],
329        exporter_length: usize,
330    ) -> Result<(KemOutput, ExporterSecret), CryptoError> {
331        self.crypto
332            .hpke_setup_sender_and_export(config, pk_r, info, exporter_context, exporter_length)
333    }
334
335    fn hpke_setup_receiver_and_export(
336        &self,
337        config: HpkeConfig,
338        enc: &[u8],
339        sk_r: &[u8],
340        info: &[u8],
341        exporter_context: &[u8],
342        exporter_length: usize,
343    ) -> Result<ExporterSecret, CryptoError> {
344        self.crypto
345            .hpke_setup_receiver_and_export(config, enc, sk_r, info, exporter_context, exporter_length)
346    }
347
348    fn derive_hpke_keypair(&self, config: HpkeConfig, ikm: &[u8]) -> Result<HpkeKeyPair, CryptoError> {
349        self.crypto.derive_hpke_keypair(config, ikm)
350    }
351}