1#![cfg(all(mls_build_async, target_arch = "wasm32"))]
6
7mod aead;
8mod ec;
9mod hkdf;
10mod key_type;
11
12use mls_rs_core::{
13 crypto::{
14 CipherSuite, CipherSuiteProvider, CryptoProvider, HpkeCiphertext, HpkePublicKey,
15 HpkeSecretKey, SignaturePublicKey, SignatureSecretKey,
16 },
17 error::{AnyError, IntoAnyError},
18};
19
20use mls_rs_crypto_hpke::{
21 context::{ContextR, ContextS},
22 dhkem::DhKem,
23 hpke::Hpke,
24};
25
26use mls_rs_crypto_traits::{AeadType, KdfType, KemId};
27
28use wasm_bindgen::JsValue;
29use web_sys::SubtleCrypto;
30use zeroize::Zeroizing;
31
32use crate::{
33 aead::Aead,
34 ec::{EcSigner, Ecdh},
35 hkdf::Hkdf,
36};
37
38#[derive(Debug, thiserror::Error)]
39pub enum CryptoError {
40 #[error("Unsupported ciphersuite")]
41 UnsupportedCipherSuite,
42 #[error("JS error {0}")]
43 JsValue(String),
44 #[error("Key has wrong length for cipher suite")]
45 WrongKeyLength,
46 #[error("Window not found")]
47 WindowNotFound,
48 #[error("Invalid signature")]
49 InvalidSignature,
50 #[error("Der encoding error {0}")]
51 DerError(String),
52 #[error("Could not compute EC public key from seed")]
53 CouldNotComputePublicKey,
54 #[error(transparent)]
55 HpkeError(AnyError),
56}
57
58impl From<JsValue> for CryptoError {
59 fn from(e: JsValue) -> Self {
60 Self::JsValue(format!("{e:?}"))
61 }
62}
63
64#[inline]
65pub(crate) fn get_crypto() -> Result<SubtleCrypto, CryptoError> {
66 Ok(web_sys::window()
67 .ok_or(CryptoError::WindowNotFound)?
68 .crypto()?
69 .subtle())
70}
71
72#[derive(Clone, Default, Debug)]
73pub struct WebCryptoProvider;
74
75impl WebCryptoProvider {
76 pub fn new() -> Self {
77 Self
78 }
79
80 pub fn all_supported_cipher_suites() -> Vec<CipherSuite> {
81 vec![
82 CipherSuite::P256_AES128,
83 CipherSuite::P384_AES256,
84 CipherSuite::P521_AES256,
85 ]
86 }
87}
88
89#[derive(Clone)]
90pub struct WebCryptoCipherSuite {
91 aead: Aead,
92 hkdf: Hkdf,
93 ec_signer: EcSigner,
94 hpke: Hpke<DhKem<Ecdh, Hkdf>, Hkdf, Aead>,
95 cipher_suite: CipherSuite,
96}
97
98impl WebCryptoCipherSuite {
99 pub fn new(cipher_suite: CipherSuite) -> Option<Self> {
100 let kem_id = KemId::new(cipher_suite)?;
101 let hkdf = Hkdf::new(cipher_suite)?;
102 let dh = Ecdh::new(cipher_suite)?;
103
104 let dhkem = DhKem::new(dh, hkdf.clone(), kem_id as u16, kem_id.n_secret());
105 let aead = Aead::new(cipher_suite)?;
106
107 Some(Self {
108 aead: aead.clone(),
109 hkdf: hkdf.clone(),
110 ec_signer: EcSigner::new(cipher_suite)?,
111 hpke: Hpke::new(dhkem, hkdf, Some(aead)),
112 cipher_suite,
113 })
114 }
115}
116
117impl CryptoProvider for WebCryptoProvider {
118 type CipherSuiteProvider = WebCryptoCipherSuite;
119
120 fn supported_cipher_suites(&self) -> Vec<CipherSuite> {
121 Self::all_supported_cipher_suites()
122 }
123
124 fn cipher_suite_provider(&self, cipher_suite: CipherSuite) -> Option<WebCryptoCipherSuite> {
125 WebCryptoCipherSuite::new(cipher_suite)
126 }
127}
128
129impl IntoAnyError for CryptoError {}
130
131#[maybe_async::must_be_async(?Send)]
132impl CipherSuiteProvider for WebCryptoCipherSuite {
133 type Error = CryptoError;
134
135 type HpkeContextS = ContextS<Hkdf, Aead>;
136 type HpkeContextR = ContextR<Hkdf, Aead>;
137
138 fn cipher_suite(&self) -> mls_rs_core::crypto::CipherSuite {
139 self.cipher_suite
140 }
141
142 async fn hash(&self, data: &[u8]) -> Result<Vec<u8>, Self::Error> {
143 self.hkdf.hash(data).await
144 }
145
146 async fn mac(&self, key: &[u8], data: &[u8]) -> Result<Vec<u8>, Self::Error> {
147 self.hkdf.hmac(key, data).await
148 }
149
150 async fn aead_seal(
151 &self,
152 key: &[u8],
153 data: &[u8],
154 aad: Option<&[u8]>,
155 nonce: &[u8],
156 ) -> Result<Vec<u8>, Self::Error> {
157 self.aead.seal(key, data, aad, nonce).await
158 }
159
160 async fn aead_open(
161 &self,
162 key: &[u8],
163 ciphertext: &[u8],
164 aad: Option<&[u8]>,
165 nonce: &[u8],
166 ) -> Result<Zeroizing<Vec<u8>>, Self::Error> {
167 self.aead
168 .open(key, ciphertext, aad, nonce)
169 .await
170 .map(Zeroizing::new)
171 }
172
173 fn aead_key_size(&self) -> usize {
174 self.aead.key_size()
175 }
176
177 fn aead_nonce_size(&self) -> usize {
178 self.aead.nonce_size()
179 }
180
181 async fn kdf_extract(
182 &self,
183 salt: &[u8],
184 ikm: &[u8],
185 ) -> Result<Zeroizing<Vec<u8>>, Self::Error> {
186 self.hkdf.extract(salt, ikm).await.map(Zeroizing::new)
187 }
188
189 async fn kdf_expand(
190 &self,
191 prk: &[u8],
192 info: &[u8],
193 len: usize,
194 ) -> Result<Zeroizing<Vec<u8>>, Self::Error> {
195 self.hkdf.expand(prk, info, len).await.map(Zeroizing::new)
196 }
197
198 fn kdf_extract_size(&self) -> usize {
199 self.hkdf.extract_size()
200 }
201
202 async fn hpke_seal(
203 &self,
204 remote_key: &HpkePublicKey,
205 info: &[u8],
206 aad: Option<&[u8]>,
207 pt: &[u8],
208 ) -> Result<HpkeCiphertext, Self::Error> {
209 self.hpke
210 .seal(remote_key, info, None, aad, pt)
211 .await
212 .map_err(|e| CryptoError::HpkeError(e.into_any_error()))
213 }
214
215 async fn hpke_open(
216 &self,
217 ciphertext: &HpkeCiphertext,
218 local_secret: &HpkeSecretKey,
219 local_public: &HpkePublicKey,
220 info: &[u8],
221 aad: Option<&[u8]>,
222 ) -> Result<Vec<u8>, Self::Error> {
223 self.hpke
224 .open(ciphertext, local_secret, local_public, info, None, aad)
225 .await
226 .map_err(|e| CryptoError::HpkeError(e.into_any_error()))
227 }
228
229 async fn hpke_setup_s(
230 &self,
231 remote_key: &HpkePublicKey,
232 info: &[u8],
233 ) -> Result<(Vec<u8>, Self::HpkeContextS), Self::Error> {
234 self.hpke
235 .setup_sender(remote_key, info, None)
236 .await
237 .map_err(|e| CryptoError::HpkeError(e.into_any_error()))
238 }
239
240 async fn hpke_setup_r(
241 &self,
242 kem_output: &[u8],
243 local_secret: &HpkeSecretKey,
244 local_public: &HpkePublicKey,
245 info: &[u8],
246 ) -> Result<Self::HpkeContextR, Self::Error> {
247 self.hpke
248 .setup_receiver(kem_output, local_secret, local_public, info, None)
249 .await
250 .map_err(|e| CryptoError::HpkeError(e.into_any_error()))
251 }
252
253 async fn kem_derive(&self, ikm: &[u8]) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error> {
254 self.hpke
255 .derive(ikm)
256 .await
257 .map_err(|e| CryptoError::HpkeError(e.into_any_error()))
258 }
259
260 async fn kem_generate(&self) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error> {
261 self.hpke
262 .generate()
263 .await
264 .map_err(|e| CryptoError::HpkeError(e.into_any_error()))
265 }
266
267 fn kem_public_key_validate(&self, key: &HpkePublicKey) -> Result<(), Self::Error> {
268 self.hpke
269 .public_key_validate(key)
270 .map_err(|e| CryptoError::HpkeError(e.into_any_error()))
271 }
272
273 fn random_bytes(&self, out: &mut [u8]) -> Result<(), Self::Error> {
274 web_sys::window()
275 .ok_or(CryptoError::WindowNotFound)?
276 .crypto()?
277 .get_random_values_with_u8_array(out)?;
278
279 Ok(())
280 }
281
282 async fn signature_key_generate(
283 &self,
284 ) -> Result<(SignatureSecretKey, SignaturePublicKey), Self::Error> {
285 self.ec_signer.generate().await
286 }
287
288 async fn signature_key_derive_public(
289 &self,
290 secret_key: &SignatureSecretKey,
291 ) -> Result<SignaturePublicKey, Self::Error> {
292 self.ec_signer.derive_public(secret_key)
293 }
294
295 async fn sign(
296 &self,
297 secret_key: &SignatureSecretKey,
298 data: &[u8],
299 ) -> Result<Vec<u8>, Self::Error> {
300 self.ec_signer.sign(secret_key, data).await
301 }
302
303 async fn verify(
304 &self,
305 public_key: &SignaturePublicKey,
306 signature: &[u8],
307 data: &[u8],
308 ) -> Result<(), Self::Error> {
309 self.ec_signer.verify(public_key, data, signature).await
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
316
317 #[wasm_bindgen_test::wasm_bindgen_test]
318 async fn mls_rs_core_test() {
319 mls_rs_core::crypto::test_suite::verify_tests(&crate::WebCryptoProvider, false).await
320 }
321}