Skip to main content

core_crypto/mls_provider/
mod.rs

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