mls_rs_crypto_rustcrypto/
lib.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// Copyright by contributors to this project.
3// SPDX-License-Identifier: (Apache-2.0 OR MIT)
4
5#![cfg_attr(not(feature = "std"), no_std)]
6extern crate alloc;
7
8pub mod aead;
9mod ec;
10pub mod ec_signer;
11pub mod ecdh;
12pub mod kdf;
13pub mod mac;
14
15#[cfg(feature = "x509")]
16pub mod x509;
17
18#[cfg(feature = "x509")]
19mod ec_for_x509;
20
21use crate::aead::Aead;
22use ec_signer::{EcSigner, EcSignerError};
23use ecdh::Ecdh;
24use kdf::Kdf;
25use mac::{Hash, HashError};
26use mls_rs_crypto_hpke::{
27    context::{ContextR, ContextS},
28    dhkem::DhKem,
29    hpke::{Hpke, HpkeError},
30};
31use mls_rs_crypto_traits::{AeadType, KdfType, KemId, KemType};
32use rand_core::{OsRng, RngCore};
33
34use mls_rs_core::{
35    crypto::{
36        CipherSuite, CipherSuiteProvider, CryptoProvider, HpkeCiphertext, HpkePublicKey,
37        HpkeSecretKey, SignaturePublicKey, SignatureSecretKey,
38    },
39    error::{AnyError, IntoAnyError},
40};
41use zeroize::Zeroizing;
42
43use alloc::vec;
44use alloc::vec::Vec;
45
46#[cfg(all(test, target_arch = "wasm32"))]
47wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
48
49#[derive(Debug)]
50#[cfg_attr(feature = "std", derive(thiserror::Error))]
51pub enum RustCryptoError {
52    #[cfg_attr(feature = "std", error(transparent))]
53    AeadError(AnyError),
54    #[cfg_attr(feature = "std", error(transparent))]
55    HpkeError(HpkeError),
56    #[cfg_attr(feature = "std", error(transparent))]
57    KdfError(AnyError),
58    #[cfg_attr(feature = "std", error(transparent))]
59    HashError(HashError),
60    #[cfg_attr(feature = "std", error("rand core error: {0:?}"))]
61    RandError(rand_core::Error),
62    #[cfg_attr(feature = "std", error(transparent))]
63    EcSignerError(EcSignerError),
64}
65
66impl From<rand_core::Error> for RustCryptoError {
67    fn from(value: rand_core::Error) -> Self {
68        RustCryptoError::RandError(value)
69    }
70}
71
72impl From<HpkeError> for RustCryptoError {
73    fn from(e: HpkeError) -> Self {
74        RustCryptoError::HpkeError(e)
75    }
76}
77
78impl From<HashError> for RustCryptoError {
79    fn from(e: HashError) -> Self {
80        RustCryptoError::HashError(e)
81    }
82}
83
84impl From<EcSignerError> for RustCryptoError {
85    fn from(e: EcSignerError) -> Self {
86        RustCryptoError::EcSignerError(e)
87    }
88}
89
90impl IntoAnyError for RustCryptoError {
91    #[cfg(feature = "std")]
92    fn into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> {
93        Ok(self.into())
94    }
95}
96
97#[derive(Debug, Clone)]
98#[non_exhaustive]
99pub struct RustCryptoProvider {
100    pub enabled_cipher_suites: Vec<CipherSuite>,
101}
102
103impl RustCryptoProvider {
104    pub fn new() -> Self {
105        Self::default()
106    }
107
108    pub fn with_enabled_cipher_suites(enabled_cipher_suites: Vec<CipherSuite>) -> Self {
109        Self {
110            enabled_cipher_suites,
111        }
112    }
113
114    pub fn all_supported_cipher_suites() -> Vec<CipherSuite> {
115        vec![
116            CipherSuite::P256_AES128,
117            CipherSuite::P384_AES256,
118            CipherSuite::CURVE25519_AES128,
119            CipherSuite::CURVE25519_CHACHA,
120        ]
121    }
122}
123
124impl Default for RustCryptoProvider {
125    fn default() -> Self {
126        Self {
127            enabled_cipher_suites: Self::all_supported_cipher_suites(),
128        }
129    }
130}
131
132impl CryptoProvider for RustCryptoProvider {
133    type CipherSuiteProvider = RustCryptoCipherSuite<DhKem<Ecdh, Kdf>, Kdf, Aead>;
134
135    fn supported_cipher_suites(&self) -> Vec<CipherSuite> {
136        self.enabled_cipher_suites.clone()
137    }
138
139    fn cipher_suite_provider(
140        &self,
141        cipher_suite: CipherSuite,
142    ) -> Option<Self::CipherSuiteProvider> {
143        if !self.enabled_cipher_suites.contains(&cipher_suite) {
144            return None;
145        }
146
147        let kdf = Kdf::new(cipher_suite)?;
148        let ecdh = Ecdh::new(cipher_suite)?;
149        let kem_id = KemId::new(cipher_suite)?;
150        let kem = DhKem::new(ecdh, kdf, kem_id as u16, kem_id.n_secret());
151        let aead = Aead::new(cipher_suite)?;
152
153        RustCryptoCipherSuite::new(cipher_suite, kem, kdf, aead)
154    }
155}
156
157#[derive(Clone)]
158pub struct RustCryptoCipherSuite<KEM, KDF, AEAD>
159where
160    KEM: KemType + Clone,
161    KDF: KdfType + Clone,
162    AEAD: AeadType + Clone,
163{
164    cipher_suite: CipherSuite,
165    aead: AEAD,
166    kdf: KDF,
167    hash: Hash,
168    hpke: Hpke<KEM, KDF, AEAD>,
169    ec_signer: EcSigner,
170}
171
172impl<KEM, KDF, AEAD> RustCryptoCipherSuite<KEM, KDF, AEAD>
173where
174    KEM: KemType + Clone,
175    KDF: KdfType + Clone,
176    AEAD: AeadType + Clone,
177{
178    pub fn new(cipher_suite: CipherSuite, kem: KEM, kdf: KDF, aead: AEAD) -> Option<Self> {
179        let hpke = Hpke::new(kem, kdf.clone(), Some(aead.clone()));
180
181        Some(Self {
182            cipher_suite,
183            kdf,
184            aead,
185            hash: Hash::new(cipher_suite).ok()?,
186            hpke,
187            ec_signer: EcSigner::new(cipher_suite)?,
188        })
189    }
190
191    pub fn random_bytes(&self, out: &mut [u8]) -> Result<(), RustCryptoError> {
192        OsRng.try_fill_bytes(out).map_err(Into::into)
193    }
194}
195
196#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
197#[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
198#[cfg_attr(
199    all(not(target_arch = "wasm32"), mls_build_async),
200    maybe_async::must_be_async
201)]
202impl<KEM, KDF, AEAD> CipherSuiteProvider for RustCryptoCipherSuite<KEM, KDF, AEAD>
203where
204    KEM: KemType + Clone + Send + Sync,
205    KDF: KdfType + Clone + Send + Sync,
206    AEAD: AeadType + Clone + Send + Sync,
207{
208    type Error = RustCryptoError;
209    // TODO exporter_secret in this struct is not zeroized
210    type HpkeContextR = ContextR<KDF, AEAD>;
211    type HpkeContextS = ContextS<KDF, AEAD>;
212
213    async fn hash(&self, data: &[u8]) -> Result<Vec<u8>, Self::Error> {
214        Ok(self.hash.hash(data))
215    }
216
217    async fn mac(&self, key: &[u8], data: &[u8]) -> Result<Vec<u8>, Self::Error> {
218        Ok(self.hash.mac(key, data)?)
219    }
220
221    async fn aead_seal(
222        &self,
223        key: &[u8],
224        data: &[u8],
225        aad: Option<&[u8]>,
226        nonce: &[u8],
227    ) -> Result<Vec<u8>, Self::Error> {
228        self.aead
229            .seal(key, data, aad, nonce)
230            .await
231            .map_err(|e| RustCryptoError::AeadError(e.into_any_error()))
232    }
233
234    async fn aead_open(
235        &self,
236        key: &[u8],
237        cipher_text: &[u8],
238        aad: Option<&[u8]>,
239        nonce: &[u8],
240    ) -> Result<Zeroizing<Vec<u8>>, Self::Error> {
241        self.aead
242            .open(key, cipher_text, aad, nonce)
243            .await
244            .map_err(|e| RustCryptoError::AeadError(e.into_any_error()))
245            .map(Zeroizing::new)
246    }
247
248    fn aead_key_size(&self) -> usize {
249        self.aead.key_size()
250    }
251
252    fn aead_nonce_size(&self) -> usize {
253        self.aead.nonce_size()
254    }
255
256    async fn kdf_expand(
257        &self,
258        prk: &[u8],
259        info: &[u8],
260        len: usize,
261    ) -> Result<Zeroizing<Vec<u8>>, Self::Error> {
262        self.kdf
263            .expand(prk, info, len)
264            .await
265            .map_err(|e| RustCryptoError::KdfError(e.into_any_error()))
266            .map(Zeroizing::new)
267    }
268
269    async fn kdf_extract(
270        &self,
271        salt: &[u8],
272        ikm: &[u8],
273    ) -> Result<Zeroizing<Vec<u8>>, Self::Error> {
274        self.kdf
275            .extract(salt, ikm)
276            .await
277            .map_err(|e| RustCryptoError::KdfError(e.into_any_error()))
278            .map(Zeroizing::new)
279    }
280
281    fn kdf_extract_size(&self) -> usize {
282        self.kdf.extract_size()
283    }
284
285    async fn hpke_seal(
286        &self,
287        remote_key: &HpkePublicKey,
288        info: &[u8],
289        aad: Option<&[u8]>,
290        pt: &[u8],
291    ) -> Result<HpkeCiphertext, Self::Error> {
292        Ok(self.hpke.seal(remote_key, info, None, aad, pt).await?)
293    }
294
295    async fn hpke_open(
296        &self,
297        ciphertext: &HpkeCiphertext,
298        local_secret: &HpkeSecretKey,
299        local_public: &HpkePublicKey,
300        info: &[u8],
301        aad: Option<&[u8]>,
302    ) -> Result<Vec<u8>, Self::Error> {
303        Ok(self
304            .hpke
305            .open(ciphertext, local_secret, local_public, info, None, aad)
306            .await?)
307    }
308
309    async fn hpke_setup_r(
310        &self,
311        enc: &[u8],
312        local_secret: &HpkeSecretKey,
313        local_public: &HpkePublicKey,
314        info: &[u8],
315    ) -> Result<Self::HpkeContextR, Self::Error> {
316        Ok(self
317            .hpke
318            .setup_receiver(enc, local_secret, local_public, info, None)
319            .await?)
320    }
321
322    async fn hpke_setup_s(
323        &self,
324        remote_key: &HpkePublicKey,
325        info: &[u8],
326    ) -> Result<(Vec<u8>, Self::HpkeContextS), Self::Error> {
327        Ok(self.hpke.setup_sender(remote_key, info, None).await?)
328    }
329
330    async fn kem_derive(&self, ikm: &[u8]) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error> {
331        Ok(self.hpke.derive(ikm).await?)
332    }
333
334    async fn kem_generate(&self) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error> {
335        Ok(self.hpke.generate().await?)
336    }
337
338    fn kem_public_key_validate(&self, key: &HpkePublicKey) -> Result<(), Self::Error> {
339        Ok(self.hpke.public_key_validate(key)?)
340    }
341
342    fn random_bytes(&self, out: &mut [u8]) -> Result<(), Self::Error> {
343        self.random_bytes(out)
344    }
345
346    fn cipher_suite(&self) -> CipherSuite {
347        self.cipher_suite
348    }
349
350    async fn sign(
351        &self,
352        secret_key: &SignatureSecretKey,
353        data: &[u8],
354    ) -> Result<Vec<u8>, Self::Error> {
355        Ok(self.ec_signer.sign(secret_key, data)?)
356    }
357
358    async fn verify(
359        &self,
360        public_key: &SignaturePublicKey,
361        signature: &[u8],
362        data: &[u8],
363    ) -> Result<(), Self::Error> {
364        Ok(self.ec_signer.verify(public_key, signature, data)?)
365    }
366
367    async fn signature_key_generate(
368        &self,
369    ) -> Result<(SignatureSecretKey, SignaturePublicKey), Self::Error> {
370        Ok(self.ec_signer.signature_key_generate()?)
371    }
372
373    async fn signature_key_derive_public(
374        &self,
375        secret_key: &SignatureSecretKey,
376    ) -> Result<SignaturePublicKey, Self::Error> {
377        Ok(self.ec_signer.signature_key_derive_public(secret_key)?)
378    }
379}
380
381#[cfg(not(mls_build_async))]
382#[test]
383fn mls_core_tests() {
384    let provider = RustCryptoProvider::new();
385    mls_rs_core::crypto::test_suite::verify_tests(&provider, true);
386
387    for cs in RustCryptoProvider::all_supported_cipher_suites() {
388        let mut hpke = provider.cipher_suite_provider(cs).unwrap().hpke;
389
390        mls_rs_core::crypto::test_suite::verify_hpke_context_tests(&hpke, cs);
391        mls_rs_core::crypto::test_suite::verify_hpke_encap_tests(&mut hpke, cs);
392    }
393}
394
395#[cfg(all(test, target_arch = "wasm32"))]
396#[wasm_bindgen_test::wasm_bindgen_test]
397async fn mls_rs_core_test() {
398    let provider = RustCryptoProvider::new();
399    mls_rs_core::crypto::test_suite::verify_tests(&provider, true).await;
400}