Skip to main content

bitwarden_wasm_internal/
pure_crypto.rs

1use std::str::FromStr;
2
3use bitwarden_core::key_management::KeyIds;
4#[allow(deprecated)]
5use bitwarden_crypto::dangerous_derive_kdf_material;
6use bitwarden_crypto::{
7    BitwardenLegacyKeyBytes, CoseKeyBytes, CoseSerializable, CoseSign1Bytes, CryptoError,
8    Decryptable, EncString, Kdf, KeyDecryptable, KeyEncryptable, KeyStore, MasterKey,
9    OctetStreamBytes, Pkcs8PrivateKeyBytes, PrimitiveEncryptable, PrivateKey, PublicKey,
10    PublicKeyEncryptionAlgorithm, SignatureAlgorithm, SignedPublicKey, SigningKey,
11    SpkiPublicKeyBytes, SymmetricCryptoKey, UnsignedSharedKey, VerifyingKey,
12};
13use rand::Rng;
14use rsa::{
15    Oaep, RsaPrivateKey, RsaPublicKey,
16    pkcs8::{DecodePrivateKey, DecodePublicKey},
17};
18use sha1::Sha1;
19use wasm_bindgen::prelude::*;
20
21/// This module represents a stopgap solution to provide access to primitive crypto functions for JS
22/// clients. It is not intended to be used outside of the JS clients and this pattern should not be
23/// proliferated. It is necessary because we want to use SDK crypto prior to the SDK being fully
24/// responsible for state and keys.
25#[wasm_bindgen]
26pub struct PureCrypto {}
27
28// Encryption
29#[wasm_bindgen]
30impl PureCrypto {
31    /// DEPRECATED: Use `symmetric_decrypt_string` instead.
32    /// Cleanup ticket: <https://bitwarden.atlassian.net/browse/PM-21247>
33    pub fn symmetric_decrypt(enc_string: String, key: Vec<u8>) -> Result<String, CryptoError> {
34        Self::symmetric_decrypt_string(enc_string, key)
35    }
36
37    pub fn symmetric_decrypt_string(
38        enc_string: String,
39        key: Vec<u8>,
40    ) -> Result<String, CryptoError> {
41        let _span = tracing::info_span!("PureCrypto::symmetric_decrypt_string").entered();
42        let key = &BitwardenLegacyKeyBytes::from(key);
43        EncString::from_str(&enc_string)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
44    }
45
46    pub fn symmetric_decrypt_bytes(
47        enc_string: String,
48        key: Vec<u8>,
49    ) -> Result<Vec<u8>, CryptoError> {
50        let _span = tracing::info_span!("PureCrypto::symmetric_decrypt_bytes").entered();
51        let key = &BitwardenLegacyKeyBytes::from(key);
52        EncString::from_str(&enc_string)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
53    }
54
55    /// DEPRECATED: Use `symmetric_decrypt_filedata` instead.
56    /// Cleanup ticket: <https://bitwarden.atlassian.net/browse/PM-21247>
57    pub fn symmetric_decrypt_array_buffer(
58        enc_bytes: Vec<u8>,
59        key: Vec<u8>,
60    ) -> Result<Vec<u8>, CryptoError> {
61        Self::symmetric_decrypt_filedata(enc_bytes, key)
62    }
63
64    pub fn symmetric_decrypt_filedata(
65        enc_bytes: Vec<u8>,
66        key: Vec<u8>,
67    ) -> Result<Vec<u8>, CryptoError> {
68        let _span = tracing::info_span!("PureCrypto::symmetric_decrypt_filedata").entered();
69        let key = &BitwardenLegacyKeyBytes::from(key);
70        EncString::from_buffer(&enc_bytes)?.decrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
71    }
72
73    pub fn symmetric_encrypt_string(plain: String, key: Vec<u8>) -> Result<String, CryptoError> {
74        let _span = tracing::info_span!("PureCrypto::symmetric_encrypt_string").entered();
75        let key = &BitwardenLegacyKeyBytes::from(key);
76        plain
77            .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
78            .map(|enc| enc.to_string())
79    }
80
81    /// DEPRECATED: Only used by send keys
82    pub fn symmetric_encrypt_bytes(plain: Vec<u8>, key: Vec<u8>) -> Result<String, CryptoError> {
83        let _span = tracing::info_span!("PureCrypto::symmetric_encrypt_bytes").entered();
84        let key = &BitwardenLegacyKeyBytes::from(key);
85        OctetStreamBytes::from(plain)
86            .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?)
87            .map(|enc| enc.to_string())
88    }
89
90    pub fn symmetric_encrypt_filedata(
91        plain: Vec<u8>,
92        key: Vec<u8>,
93    ) -> Result<Vec<u8>, CryptoError> {
94        let _span = tracing::info_span!("PureCrypto::symmetric_encrypt_filedata").entered();
95        let key = &BitwardenLegacyKeyBytes::from(key);
96        OctetStreamBytes::from(plain)
97            .encrypt_with_key(&SymmetricCryptoKey::try_from(key)?)?
98            .to_buffer()
99    }
100
101    pub fn decrypt_user_key_with_master_password(
102        encrypted_user_key: String,
103        master_password: String,
104        email: String,
105        kdf: Kdf,
106    ) -> Result<Vec<u8>, CryptoError> {
107        let _span = tracing::info_span!(
108            "PureCrypto::decrypt_user_key_with_master_password",
109            email = %email,
110            kdf = ?kdf
111        )
112        .entered();
113
114        let master_key = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?;
115        let encrypted_user_key = EncString::from_str(&encrypted_user_key)?;
116        let result = master_key
117            .decrypt_user_key(encrypted_user_key)
118            .map_err(|_| CryptoError::InvalidKey)?;
119        Ok(result.to_encoded().to_vec())
120    }
121
122    pub fn encrypt_user_key_with_master_password(
123        user_key: Vec<u8>,
124        master_password: String,
125        email: String,
126        kdf: Kdf,
127    ) -> Result<String, CryptoError> {
128        let _span = tracing::info_span!(
129            "PureCrypto::encrypt_user_key_with_master_password",
130            email = %email,
131            kdf = ?kdf
132        )
133        .entered();
134        let master_key = MasterKey::derive(master_password.as_str(), email.as_str(), &kdf)?;
135        let user_key = &BitwardenLegacyKeyBytes::from(user_key);
136        let user_key = SymmetricCryptoKey::try_from(user_key)?;
137        let result = master_key.encrypt_user_key(&user_key)?;
138        Ok(result.to_string())
139    }
140
141    pub fn make_user_key_aes256_cbc_hmac() -> Vec<u8> {
142        SymmetricCryptoKey::make_aes256_cbc_hmac_key()
143            .to_encoded()
144            .to_vec()
145    }
146
147    pub fn make_user_key_xchacha20_poly1305() -> Vec<u8> {
148        SymmetricCryptoKey::make_xchacha20_poly1305_key()
149            .to_encoded()
150            .to_vec()
151    }
152
153    /// Wraps (encrypts) a symmetric key using a symmetric wrapping key, returning the wrapped key
154    /// as an EncString.
155    pub fn wrap_symmetric_key(
156        key_to_be_wrapped: Vec<u8>,
157        wrapping_key: Vec<u8>,
158    ) -> Result<String, CryptoError> {
159        let _span = tracing::info_span!("PureCrypto::wrap_symmetric_key").entered();
160        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
161        let mut context = tmp_store.context();
162        let wrapping_key =
163            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(wrapping_key))?;
164        let wrapping_key = context.add_local_symmetric_key(wrapping_key);
165        let key_to_be_wrapped =
166            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(key_to_be_wrapped))?;
167        let key_to_wrap = context.add_local_symmetric_key(key_to_be_wrapped);
168        // Note: The order of arguments is different here, and should probably be refactored
169        Ok(context
170            .wrap_symmetric_key(wrapping_key, key_to_wrap)?
171            .to_string())
172    }
173
174    /// Unwraps (decrypts) a wrapped symmetric key using a symmetric wrapping key, returning the
175    /// unwrapped key as a serialized byte array.
176    pub fn unwrap_symmetric_key(
177        wrapped_key: String,
178        wrapping_key: Vec<u8>,
179    ) -> Result<Vec<u8>, CryptoError> {
180        let _span = tracing::info_span!("PureCrypto::unwrap_symmetric_key").entered();
181        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
182        let mut context = tmp_store.context();
183        let wrapping_key =
184            SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(wrapping_key))?;
185        let wrapping_key = context.add_local_symmetric_key(wrapping_key);
186        // Note: The order of arguments is different here, and should probably be refactored
187        let unwrapped = context
188            .unwrap_symmetric_key(wrapping_key, &EncString::from_str(wrapped_key.as_str())?)?;
189        #[allow(deprecated)]
190        let key = context.dangerous_get_symmetric_key(unwrapped)?;
191        Ok(key.to_encoded().to_vec())
192    }
193
194    /// Wraps (encrypts) an SPKI DER encoded encapsulation (public) key using a symmetric wrapping
195    /// key. Note: Usually, a public key is - by definition - public, so this should not be
196    /// used. The specific use-case for this function is to enable rotateable key sets, where
197    /// the "public key" is not public, with the intent of preventing the server from being able
198    /// to overwrite the user key unlocked by the rotateable keyset.
199    pub fn wrap_encapsulation_key(
200        encapsulation_key: Vec<u8>,
201        wrapping_key: Vec<u8>,
202    ) -> Result<String, CryptoError> {
203        let _span = tracing::info_span!("PureCrypto::wrap_encapsulation_key").entered();
204        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
205        let mut context = tmp_store.context();
206        let wrapping_key = context.add_local_symmetric_key(SymmetricCryptoKey::try_from(
207            &BitwardenLegacyKeyBytes::from(wrapping_key),
208        )?);
209        Ok(SpkiPublicKeyBytes::from(encapsulation_key)
210            .encrypt(&mut context, wrapping_key)?
211            .to_string())
212    }
213
214    /// Unwraps (decrypts) a wrapped SPKI DER encoded encapsulation (public) key using a symmetric
215    /// wrapping key.
216    pub fn unwrap_encapsulation_key(
217        wrapped_key: String,
218        wrapping_key: Vec<u8>,
219    ) -> Result<Vec<u8>, CryptoError> {
220        let _span = tracing::info_span!("PureCrypto::unwrap_encapsulation_key").entered();
221        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
222        let mut context = tmp_store.context();
223        let wrapping_key = context.add_local_symmetric_key(SymmetricCryptoKey::try_from(
224            &BitwardenLegacyKeyBytes::from(wrapping_key),
225        )?);
226        EncString::from_str(wrapped_key.as_str())?.decrypt(&mut context, wrapping_key)
227    }
228
229    /// Wraps (encrypts) a PKCS8 DER encoded decapsulation (private) key using a symmetric wrapping
230    /// key,
231    pub fn wrap_decapsulation_key(
232        decapsulation_key: Vec<u8>,
233        wrapping_key: Vec<u8>,
234    ) -> Result<String, CryptoError> {
235        let _span = tracing::info_span!("PureCrypto::wrap_decapsulation_key").entered();
236        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
237        let mut context = tmp_store.context();
238        let wrapping_key = context.add_local_symmetric_key(SymmetricCryptoKey::try_from(
239            &BitwardenLegacyKeyBytes::from(wrapping_key),
240        )?);
241        Ok(Pkcs8PrivateKeyBytes::from(decapsulation_key)
242            .encrypt(&mut context, wrapping_key)?
243            .to_string())
244    }
245
246    /// Unwraps (decrypts) a wrapped PKCS8 DER encoded decapsulation (private) key using a symmetric
247    /// wrapping key.
248    pub fn unwrap_decapsulation_key(
249        wrapped_key: String,
250        wrapping_key: Vec<u8>,
251    ) -> Result<Vec<u8>, CryptoError> {
252        let _span = tracing::info_span!("PureCrypto::unwrap_decapsulation_key").entered();
253        let tmp_store: KeyStore<KeyIds> = KeyStore::default();
254        let mut context = tmp_store.context();
255        let wrapping_key = context.add_local_symmetric_key(SymmetricCryptoKey::try_from(
256            &BitwardenLegacyKeyBytes::from(wrapping_key),
257        )?);
258        EncString::from_str(wrapped_key.as_str())?.decrypt(&mut context, wrapping_key)
259    }
260
261    /// Encapsulates (encrypts) a symmetric key using an public-key/encapsulation-key
262    /// in SPKI format, returning the encapsulated key as a string. Note: This is unsigned, so
263    /// the sender's authenticity cannot be verified by the recipient.
264    pub fn encapsulate_key_unsigned(
265        shared_key: Vec<u8>,
266        encapsulation_key: Vec<u8>,
267    ) -> Result<String, CryptoError> {
268        let _span = tracing::info_span!("PureCrypto::encapsulate_key_unsigned").entered();
269        let encapsulation_key = PublicKey::from_der(&SpkiPublicKeyBytes::from(encapsulation_key))?;
270        #[expect(deprecated)]
271        Ok(UnsignedSharedKey::encapsulate_key_unsigned(
272            &SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(shared_key))?,
273            &encapsulation_key,
274        )?
275        .to_string())
276    }
277
278    /// Decapsulates (decrypts) a symmetric key using an decapsulation-key/private-key in PKCS8
279    /// DER format. Note: This is unsigned, so the sender's authenticity cannot be verified by the
280    /// recipient.
281    pub fn decapsulate_key_unsigned(
282        encapsulated_key: String,
283        decapsulation_key: Vec<u8>,
284    ) -> Result<Vec<u8>, CryptoError> {
285        let _span = tracing::info_span!("PureCrypto::decapsulate_key_unsigned").entered();
286        #[expect(deprecated)]
287        Ok(UnsignedSharedKey::from_str(encapsulated_key.as_str())?
288            .decapsulate_key_unsigned(&PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(
289                decapsulation_key,
290            ))?)?
291            .to_encoded()
292            .to_vec())
293    }
294
295    /// Given a wrapped signing key and the symmetric key it is wrapped with, this returns
296    /// the corresponding verifying key.
297    pub fn verifying_key_for_signing_key(
298        signing_key: String,
299        wrapping_key: Vec<u8>,
300    ) -> Result<Vec<u8>, CryptoError> {
301        let _span = tracing::info_span!("PureCrypto::verifying_key_for_signing_key").entered();
302        let bytes = Self::symmetric_decrypt_bytes(signing_key, wrapping_key)?;
303        let signing_key = SigningKey::from_cose(&CoseKeyBytes::from(bytes))?;
304        let verifying_key = signing_key.to_verifying_key();
305        Ok(verifying_key.to_cose().to_vec())
306    }
307
308    /// Returns the algorithm used for the given verifying key.
309    pub fn key_algorithm_for_verifying_key(
310        verifying_key: Vec<u8>,
311    ) -> Result<SignatureAlgorithm, CryptoError> {
312        let _span = tracing::info_span!("PureCrypto::key_algorithm_for_verifying_key").entered();
313        let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(verifying_key))?;
314        let algorithm = verifying_key.algorithm();
315        Ok(algorithm)
316    }
317
318    /// For a given signing identity (verifying key), this function verifies that the signing
319    /// identity claimed ownership of the public key. This is a one-sided claim and merely shows
320    /// that the signing identity has the intent to receive messages encrypted to the public
321    /// key.
322    pub fn verify_and_unwrap_signed_public_key(
323        signed_public_key: Vec<u8>,
324        verifying_key: Vec<u8>,
325    ) -> Result<Vec<u8>, CryptoError> {
326        let _span =
327            tracing::info_span!("PureCrypto::verify_and_unwrap_signed_public_key").entered();
328        let signed_public_key = SignedPublicKey::try_from(CoseSign1Bytes::from(signed_public_key))?;
329        let verifying_key = VerifyingKey::from_cose(&CoseKeyBytes::from(verifying_key))?;
330        signed_public_key
331            .verify_and_unwrap(&verifying_key)
332            .map(|public_key| public_key.to_der())?
333            .map(|pk| pk.to_vec())
334    }
335
336    /// Derive output of the KDF for a [bitwarden_crypto::Kdf] configuration.
337    pub fn derive_kdf_material(
338        password: &[u8],
339        salt: &[u8],
340        kdf: Kdf,
341    ) -> Result<Vec<u8>, CryptoError> {
342        let _span = tracing::info_span!("PureCrypto::derive_kdf_material", kdf = ?kdf).entered();
343        #[allow(deprecated)]
344        dangerous_derive_kdf_material(password, salt, &kdf)
345    }
346
347    pub fn decrypt_user_key_with_master_key(
348        encrypted_user_key: String,
349        master_key: Vec<u8>,
350    ) -> Result<Vec<u8>, CryptoError> {
351        let _span = tracing::info_span!("PureCrypto::decrypt_user_key_with_master_key").entered();
352        let master_key = &BitwardenLegacyKeyBytes::from(master_key);
353        let master_key = &SymmetricCryptoKey::try_from(master_key)?;
354        let master_key = MasterKey::try_from(master_key)?;
355        let encrypted_user_key = EncString::from_str(&encrypted_user_key)?;
356        let result = master_key
357            .decrypt_user_key(encrypted_user_key)
358            .map_err(|_| CryptoError::InvalidKey)?;
359        Ok(result.to_encoded().to_vec())
360    }
361
362    /// Given a decrypted private RSA key PKCS8 DER this
363    /// returns the corresponding public RSA key in DER format.
364    /// HAZMAT WARNING: Do not use outside of implementing cryptofunctionservice
365    pub fn rsa_extract_public_key(private_key: Vec<u8>) -> Result<Vec<u8>, RsaError> {
366        let _span = tracing::info_span!("PureCrypto::rsa_extract_public_key").entered();
367        let private_key = PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(private_key))
368            .map_err(|_| RsaError::KeyParse)?;
369        let public_key = private_key.to_public_key();
370        Ok(public_key
371            .to_der()
372            .map_err(|_| RsaError::KeySerialize)?
373            .to_vec())
374    }
375
376    /// Generates a new RSA key pair and returns the private key
377    /// HAZMAT WARNING: Do not use outside of implementing cryptofunctionservice
378    pub fn rsa_generate_keypair() -> Result<Vec<u8>, RsaError> {
379        let _span = tracing::info_span!("PureCrypto::rsa_generate_keypair").entered();
380        let private_key = PrivateKey::make(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
381        Ok(private_key
382            .to_der()
383            .map_err(|_| RsaError::KeySerialize)?
384            .to_vec())
385    }
386
387    /// Decrypts data using RSAES-OAEP with SHA-1
388    /// HAZMAT WARNING: Do not use outside of implementing cryptofunctionservice
389    pub fn rsa_decrypt_data(
390        encrypted_data: Vec<u8>,
391        private_key: Vec<u8>,
392    ) -> Result<Vec<u8>, RsaError> {
393        let _span = tracing::info_span!("PureCrypto::rsa_decrypt_data").entered();
394        let private_key = RsaPrivateKey::from_pkcs8_der(private_key.as_slice())
395            .map_err(|_| RsaError::KeyParse)?;
396        let padding = Oaep::new::<Sha1>();
397        private_key
398            .decrypt(padding, &encrypted_data)
399            .map_err(|_| RsaError::Decryption)
400    }
401
402    /// Encrypts data using RSAES-OAEP with SHA-1
403    /// HAZMAT WARNING: Do not use outside of implementing cryptofunctionservice
404    pub fn rsa_encrypt_data(plain_data: Vec<u8>, public_key: Vec<u8>) -> Result<Vec<u8>, RsaError> {
405        let _span = tracing::info_span!("PureCrypto::rsa_encrypt_data").entered();
406        let public_key = RsaPublicKey::from_public_key_der(public_key.as_slice())
407            .map_err(|_| RsaError::KeyParse)?;
408        let padding = Oaep::new::<Sha1>();
409        let mut rng = rand::thread_rng();
410        public_key
411            .encrypt(&mut rng, padding, &plain_data)
412            .map_err(|_| RsaError::Encryption)
413    }
414
415    /// Generates a cryptographically secure random number between the given min and max
416    /// (inclusive).
417    pub fn random_number(min: u32, max: u32) -> u32 {
418        let _span = tracing::info_span!("PureCrypto::random_number").entered();
419        let mut rng = rand::thread_rng();
420        rng.gen_range(min..=max)
421    }
422}
423
424#[wasm_bindgen]
425#[derive(Debug)]
426pub enum RsaError {
427    Decryption,
428    Encryption,
429    KeyParse,
430    KeySerialize,
431}
432
433#[cfg(test)]
434mod tests {
435    use std::{num::NonZero, str::FromStr};
436
437    use bitwarden_crypto::EncString;
438
439    use super::*;
440
441    const KEY: &[u8] = &[
442        81, 142, 1, 228, 222, 3, 3, 133, 34, 176, 35, 66, 150, 6, 109, 70, 190, 149, 47, 47, 89,
443        23, 144, 87, 92, 46, 220, 13, 148, 106, 162, 234, 202, 139, 136, 33, 16, 200, 8, 73, 176,
444        172, 185, 187, 224, 10, 65, 223, 228, 54, 92, 181, 8, 213, 162, 221, 117, 254, 245, 111,
445        55, 211, 77, 29,
446    ];
447
448    const ENCRYPTED: &str = "2.Dh7AFLXR+LXcxUaO5cRjpg==|uXyhubjAoNH8lTdy/zgJDQ==|cHEMboj0MYsU5yDRQ1rLCgxcjNbKRc1PWKuv8bpU5pM=";
449    const DECRYPTED: &str = "test";
450    const DECRYPTED_BYTES: &[u8] = b"test";
451    const ENCRYPTED_BYTES: &[u8] = &[
452        2, 209, 195, 115, 49, 205, 253, 128, 162, 169, 246, 175, 217, 144, 73, 108, 191, 27, 113,
453        69, 55, 94, 142, 62, 129, 204, 173, 130, 37, 42, 97, 209, 25, 192, 64, 126, 112, 139, 248,
454        2, 89, 112, 178, 83, 25, 77, 130, 187, 127, 85, 179, 211, 159, 186, 111, 44, 109, 211, 18,
455        120, 104, 144, 4, 76, 3,
456    ];
457
458    const PEM_KEY: &str = "-----BEGIN PRIVATE KEY-----
459MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5
460qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc
461afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm
462qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv
463b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw
464P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam
465rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi
466szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx
4670d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+
4688vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR
469jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach
470vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI
4711u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR
472J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7
473l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ
474TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9
475ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye
476KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN
477wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ
478UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA
479kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W
480pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN
481Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi
482CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup
483PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf
484DnqOsltgPomWZ7xVfMkm9niL2OA=
485-----END PRIVATE KEY-----";
486
487    const SIGNING_KEY_WRAPPING_KEY: &[u8] = &[
488        40, 215, 110, 199, 183, 4, 182, 78, 213, 123, 251, 113, 72, 223, 57, 2, 3, 81, 136, 19, 88,
489        78, 206, 176, 158, 251, 211, 84, 1, 199, 203, 142, 176, 227, 187, 136, 209, 79, 23, 13, 44,
490        224, 90, 10, 191, 72, 22, 227, 171, 105, 107, 139, 24, 49, 9, 150, 103, 139, 151, 204, 165,
491        121, 165, 71,
492    ];
493    const SIGNING_KEY: &[u8] = &[
494        166, 1, 1, 2, 80, 123, 226, 102, 228, 194, 232, 71, 30, 183, 42, 219, 193, 50, 30, 21, 43,
495        3, 39, 4, 130, 1, 2, 35, 88, 32, 148, 2, 66, 69, 169, 57, 129, 240, 37, 18, 225, 211, 207,
496        133, 66, 143, 204, 238, 113, 152, 43, 112, 133, 173, 179, 17, 202, 135, 175, 237, 1, 59,
497        32, 6,
498    ];
499    const VERIFYING_KEY: &[u8] = &[
500        166, 1, 1, 2, 80, 123, 226, 102, 228, 194, 232, 71, 30, 183, 42, 219, 193, 50, 30, 21, 43,
501        3, 39, 4, 129, 2, 32, 6, 33, 88, 32, 63, 70, 49, 37, 246, 232, 146, 144, 83, 224, 0, 17,
502        111, 248, 16, 242, 69, 195, 84, 46, 39, 218, 55, 63, 90, 112, 148, 91, 224, 186, 122, 4,
503    ];
504    const PUBLIC_KEY: &[u8] = &[
505        48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0,
506        48, 130, 1, 10, 2, 130, 1, 1, 0, 173, 4, 54, 63, 125, 12, 254, 38, 115, 34, 95, 164, 148,
507        115, 86, 140, 129, 74, 19, 70, 212, 212, 130, 163, 105, 249, 101, 120, 154, 46, 194, 250,
508        229, 242, 156, 67, 109, 179, 187, 134, 59, 235, 60, 107, 144, 163, 35, 22, 109, 230, 134,
509        243, 44, 243, 79, 84, 76, 11, 64, 56, 236, 167, 98, 26, 30, 213, 143, 105, 52, 92, 129, 92,
510        88, 22, 115, 135, 63, 215, 79, 8, 11, 183, 124, 10, 73, 231, 170, 110, 210, 178, 22, 100,
511        76, 75, 118, 202, 252, 204, 67, 204, 152, 6, 244, 208, 161, 146, 103, 225, 233, 239, 88,
512        195, 88, 150, 230, 111, 62, 142, 12, 157, 184, 155, 34, 84, 237, 111, 11, 97, 56, 152, 130,
513        14, 72, 123, 140, 47, 137, 5, 97, 166, 4, 147, 111, 23, 65, 78, 63, 208, 198, 50, 161, 39,
514        80, 143, 100, 194, 37, 252, 194, 53, 207, 166, 168, 250, 165, 121, 9, 207, 90, 36, 213,
515        211, 84, 255, 14, 205, 114, 135, 217, 137, 105, 232, 58, 169, 222, 10, 13, 138, 203, 16,
516        12, 122, 72, 227, 95, 160, 111, 54, 200, 198, 143, 156, 15, 143, 196, 50, 150, 204, 144,
517        255, 162, 248, 50, 28, 47, 66, 9, 83, 158, 67, 9, 50, 147, 174, 147, 200, 199, 238, 190,
518        248, 60, 114, 218, 32, 209, 120, 218, 17, 234, 14, 128, 192, 166, 33, 60, 73, 227, 108,
519        201, 41, 160, 81, 133, 171, 205, 221, 2, 3, 1, 0, 1,
520    ];
521
522    const SIGNED_PUBLIC_KEY: &[u8] = &[
523        132, 88, 30, 164, 1, 39, 3, 24, 60, 4, 80, 123, 226, 102, 228, 194, 232, 71, 30, 183, 42,
524        219, 193, 50, 30, 21, 43, 58, 0, 1, 56, 127, 1, 160, 89, 1, 78, 163, 105, 97, 108, 103,
525        111, 114, 105, 116, 104, 109, 0, 109, 99, 111, 110, 116, 101, 110, 116, 70, 111, 114, 109,
526        97, 116, 0, 105, 112, 117, 98, 108, 105, 99, 75, 101, 121, 89, 1, 38, 48, 130, 1, 34, 48,
527        13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2,
528        130, 1, 1, 0, 173, 4, 54, 63, 125, 12, 254, 38, 115, 34, 95, 164, 148, 115, 86, 140, 129,
529        74, 19, 70, 212, 212, 130, 163, 105, 249, 101, 120, 154, 46, 194, 250, 229, 242, 156, 67,
530        109, 179, 187, 134, 59, 235, 60, 107, 144, 163, 35, 22, 109, 230, 134, 243, 44, 243, 79,
531        84, 76, 11, 64, 56, 236, 167, 98, 26, 30, 213, 143, 105, 52, 92, 129, 92, 88, 22, 115, 135,
532        63, 215, 79, 8, 11, 183, 124, 10, 73, 231, 170, 110, 210, 178, 22, 100, 76, 75, 118, 202,
533        252, 204, 67, 204, 152, 6, 244, 208, 161, 146, 103, 225, 233, 239, 88, 195, 88, 150, 230,
534        111, 62, 142, 12, 157, 184, 155, 34, 84, 237, 111, 11, 97, 56, 152, 130, 14, 72, 123, 140,
535        47, 137, 5, 97, 166, 4, 147, 111, 23, 65, 78, 63, 208, 198, 50, 161, 39, 80, 143, 100, 194,
536        37, 252, 194, 53, 207, 166, 168, 250, 165, 121, 9, 207, 90, 36, 213, 211, 84, 255, 14, 205,
537        114, 135, 217, 137, 105, 232, 58, 169, 222, 10, 13, 138, 203, 16, 12, 122, 72, 227, 95,
538        160, 111, 54, 200, 198, 143, 156, 15, 143, 196, 50, 150, 204, 144, 255, 162, 248, 50, 28,
539        47, 66, 9, 83, 158, 67, 9, 50, 147, 174, 147, 200, 199, 238, 190, 248, 60, 114, 218, 32,
540        209, 120, 218, 17, 234, 14, 128, 192, 166, 33, 60, 73, 227, 108, 201, 41, 160, 81, 133,
541        171, 205, 221, 2, 3, 1, 0, 1, 88, 64, 207, 18, 4, 242, 149, 31, 37, 255, 243, 62, 78, 46,
542        12, 150, 134, 159, 69, 89, 62, 222, 132, 12, 177, 74, 155, 80, 154, 37, 77, 176, 19, 142,
543        73, 4, 134, 242, 24, 56, 54, 38, 178, 59, 11, 118, 230, 159, 87, 91, 20, 237, 188, 186,
544        216, 86, 189, 50, 46, 173, 117, 36, 54, 105, 216, 9,
545    ];
546
547    const DERIVED_KDF_MATERIAL_PBKDF2: &[u8] = &[
548        129, 57, 137, 140, 156, 220, 110, 212, 201, 255, 52, 182, 22, 206, 221, 66, 136, 199, 181,
549        89, 252, 175, 82, 168, 79, 204, 88, 174, 166, 60, 52, 79,
550    ];
551    const DERIVED_KDF_MATERIAL_ARGON2ID: &[u8] = &[
552        221, 57, 158, 206, 27, 154, 188, 170, 33, 198, 250, 144, 191, 231, 29, 74, 201, 102, 253,
553        77, 8, 128, 173, 111, 217, 41, 125, 9, 156, 52, 112, 140,
554    ];
555
556    #[test]
557    fn test_symmetric_decrypt() {
558        let enc_string = EncString::from_str(ENCRYPTED).unwrap();
559
560        let result = PureCrypto::symmetric_decrypt_string(enc_string.to_string(), KEY.to_vec());
561        assert!(result.is_ok());
562        assert_eq!(result.unwrap(), DECRYPTED);
563    }
564
565    #[test]
566    fn test_symmetric_encrypt() {
567        let result = PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec());
568        assert!(result.is_ok());
569        // Cannot test encrypted string content because IV is unique per encryption
570    }
571
572    #[test]
573    fn test_symmetric_string_round_trip() {
574        let encrypted =
575            PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec()).unwrap();
576        let decrypted =
577            PureCrypto::symmetric_decrypt_string(encrypted.clone(), KEY.to_vec()).unwrap();
578        assert_eq!(decrypted, DECRYPTED);
579    }
580
581    #[test]
582    fn test_symmetric_bytes_round_trip() {
583        let encrypted =
584            PureCrypto::symmetric_encrypt_bytes(DECRYPTED.as_bytes().to_vec(), KEY.to_vec())
585                .unwrap();
586        let decrypted =
587            PureCrypto::symmetric_decrypt_bytes(encrypted.clone(), KEY.to_vec()).unwrap();
588        assert_eq!(decrypted, DECRYPTED.as_bytes().to_vec());
589    }
590
591    #[test]
592    fn test_symmetric_decrypt_array_buffer() {
593        let result = PureCrypto::symmetric_decrypt_filedata(ENCRYPTED_BYTES.to_vec(), KEY.to_vec());
594        assert!(result.is_ok());
595        assert_eq!(result.unwrap(), DECRYPTED_BYTES);
596    }
597
598    #[test]
599    fn test_symmetric_encrypt_to_array_buffer() {
600        let result = PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec());
601        assert!(result.is_ok());
602        // Cannot test encrypted string content because IV is unique per encryption
603    }
604
605    #[test]
606    fn test_symmetric_filedata_round_trip() {
607        let encrypted =
608            PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()).unwrap();
609        let decrypted =
610            PureCrypto::symmetric_decrypt_filedata(encrypted.clone(), KEY.to_vec()).unwrap();
611        assert_eq!(decrypted, DECRYPTED_BYTES);
612    }
613
614    #[test]
615    fn test_make_aes256_cbc_hmac_key() {
616        let key = PureCrypto::make_user_key_aes256_cbc_hmac();
617        assert_eq!(key.len(), 64);
618    }
619
620    #[test]
621    fn test_make_xchacha20_poly1305_key() {
622        let key = PureCrypto::make_user_key_xchacha20_poly1305();
623        assert!(key.len() > 64);
624    }
625
626    #[test]
627    fn roundtrip_encrypt_user_key_with_master_password() {
628        let master_password = "test";
629        let email = "[email protected]";
630        let kdf = Kdf::PBKDF2 {
631            iterations: NonZero::try_from(600000).unwrap(),
632        };
633        let user_key = PureCrypto::make_user_key_aes256_cbc_hmac();
634        let encrypted_user_key = PureCrypto::encrypt_user_key_with_master_password(
635            user_key.clone(),
636            master_password.to_string(),
637            email.to_string(),
638            kdf.clone(),
639        )
640        .unwrap();
641        let decrypted_user_key = PureCrypto::decrypt_user_key_with_master_password(
642            encrypted_user_key,
643            master_password.to_string(),
644            email.to_string(),
645            kdf,
646        )
647        .unwrap();
648        assert_eq!(user_key, decrypted_user_key);
649    }
650
651    #[test]
652    fn test_wrap_unwrap_symmetric_key() {
653        let key_to_be_wrapped = PureCrypto::make_user_key_aes256_cbc_hmac();
654        let wrapping_key = PureCrypto::make_user_key_aes256_cbc_hmac();
655        let wrapped_key =
656            PureCrypto::wrap_symmetric_key(key_to_be_wrapped.clone(), wrapping_key.clone())
657                .unwrap();
658        let unwrapped_key = PureCrypto::unwrap_symmetric_key(wrapped_key, wrapping_key).unwrap();
659        assert_eq!(key_to_be_wrapped, unwrapped_key);
660    }
661
662    #[test]
663    fn test_wrap_encapsulation_key() {
664        let decapsulation_key = PrivateKey::from_pem(PEM_KEY).unwrap();
665        let encapsulation_key = decapsulation_key
666            .to_public_key()
667            .to_der()
668            .unwrap()
669            .as_ref()
670            .to_vec();
671        let wrapping_key = PureCrypto::make_user_key_aes256_cbc_hmac();
672        let wrapped_key =
673            PureCrypto::wrap_encapsulation_key(encapsulation_key.clone(), wrapping_key.clone())
674                .unwrap();
675        let unwrapped_key =
676            PureCrypto::unwrap_encapsulation_key(wrapped_key, wrapping_key).unwrap();
677        assert_eq!(encapsulation_key, unwrapped_key);
678    }
679
680    #[test]
681    fn test_wrap_decapsulation_key() {
682        let decapsulation_key = PrivateKey::from_pem(PEM_KEY).unwrap();
683        let wrapping_key = PureCrypto::make_user_key_aes256_cbc_hmac();
684        let wrapped_key = PureCrypto::wrap_decapsulation_key(
685            decapsulation_key.to_der().unwrap().to_vec(),
686            wrapping_key.clone(),
687        )
688        .unwrap();
689        let unwrapped_key =
690            PureCrypto::unwrap_decapsulation_key(wrapped_key, wrapping_key).unwrap();
691        assert_eq!(decapsulation_key.to_der().unwrap().to_vec(), unwrapped_key);
692    }
693
694    #[test]
695    fn test_encapsulate_key_unsigned() {
696        let shared_key = PureCrypto::make_user_key_aes256_cbc_hmac();
697        let decapsulation_key = PrivateKey::from_pem(PEM_KEY).unwrap();
698        let encapsulation_key = decapsulation_key.to_public_key().to_der().unwrap();
699        let encapsulated_key = PureCrypto::encapsulate_key_unsigned(
700            shared_key.clone(),
701            encapsulation_key.clone().to_vec(),
702        )
703        .unwrap();
704        let unwrapped_key = PureCrypto::decapsulate_key_unsigned(
705            encapsulated_key,
706            decapsulation_key.to_der().unwrap().to_vec(),
707        )
708        .unwrap();
709        assert_eq!(shared_key, unwrapped_key);
710    }
711
712    #[test]
713    fn test_key_algorithm_for_verifying_key() {
714        let verifying_key =
715            VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY.to_vec())).unwrap();
716        let algorithm =
717            PureCrypto::key_algorithm_for_verifying_key(verifying_key.to_cose().to_vec()).unwrap();
718        assert_eq!(algorithm, SignatureAlgorithm::Ed25519);
719    }
720
721    #[test]
722    fn test_verifying_key_for_signing_key() {
723        let wrapped_signing_key = PureCrypto::symmetric_encrypt_bytes(
724            SIGNING_KEY.to_vec(),
725            SIGNING_KEY_WRAPPING_KEY.to_vec(),
726        )
727        .unwrap();
728        let verifying_key =
729            VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY.to_vec())).unwrap();
730        let verifying_key_derived = PureCrypto::verifying_key_for_signing_key(
731            wrapped_signing_key.to_string(),
732            SIGNING_KEY_WRAPPING_KEY.to_vec(),
733        )
734        .unwrap();
735        let verifying_key_derived =
736            VerifyingKey::from_cose(&CoseKeyBytes::from(verifying_key_derived)).unwrap();
737        assert_eq!(verifying_key.to_cose(), verifying_key_derived.to_cose());
738    }
739
740    #[test]
741    fn test_verify_and_unwrap_signed_public_key() {
742        let public_key = PureCrypto::verify_and_unwrap_signed_public_key(
743            SIGNED_PUBLIC_KEY.to_vec(),
744            VERIFYING_KEY.to_vec(),
745        )
746        .unwrap();
747        assert_eq!(public_key, PUBLIC_KEY);
748    }
749
750    #[test]
751    fn test_derive_pbkdf2_output() {
752        let password = "test_password".as_bytes();
753        let email = "[email protected]".as_bytes();
754        let kdf = Kdf::PBKDF2 {
755            iterations: NonZero::try_from(600000).unwrap(),
756        };
757        let derived_key = PureCrypto::derive_kdf_material(password, email, kdf).unwrap();
758        assert_eq!(derived_key, DERIVED_KDF_MATERIAL_PBKDF2);
759    }
760
761    #[test]
762    fn test_derived_argon2_output() {
763        let password = "test_password".as_bytes();
764        let email = "[email protected]".as_bytes();
765        let kdf = Kdf::Argon2id {
766            iterations: NonZero::try_from(3).unwrap(),
767            memory: NonZero::try_from(64).unwrap(),
768            parallelism: NonZero::try_from(4).unwrap(),
769        };
770        let derived_key = PureCrypto::derive_kdf_material(password, email, kdf).unwrap();
771        assert_eq!(derived_key, DERIVED_KDF_MATERIAL_ARGON2ID);
772    }
773
774    #[test]
775    fn test_decrypt_user_key_with_master_key() {
776        let password = "test_password";
777        let email = "[email protected]";
778        let kdf = &Kdf::Argon2id {
779            iterations: NonZero::try_from(3).unwrap(),
780            memory: NonZero::try_from(64).unwrap(),
781            parallelism: NonZero::try_from(4).unwrap(),
782        };
783        let master_key = MasterKey::derive(password, email, kdf).unwrap();
784        let (user_key, encrypted_user_key) = master_key.make_user_key().unwrap();
785        let master_key_bytes = master_key.to_base64().into_bytes();
786
787        let decrypted_user_key = PureCrypto::decrypt_user_key_with_master_key(
788            encrypted_user_key.to_string(),
789            master_key_bytes,
790        )
791        .unwrap();
792        assert_eq!(user_key.0.to_encoded().to_vec(), decrypted_user_key);
793    }
794
795    #[test]
796    fn test_rsa_round_trip() {
797        let private_key = PureCrypto::rsa_generate_keypair().unwrap();
798        let public_key = PureCrypto::rsa_extract_public_key(private_key.clone()).unwrap();
799        let plain_data = b"Test RSA encryption data".to_vec();
800        let encrypted_data = PureCrypto::rsa_encrypt_data(plain_data.clone(), public_key).unwrap();
801        let decrypted_data = PureCrypto::rsa_decrypt_data(encrypted_data, private_key).unwrap();
802        assert_eq!(plain_data, decrypted_data);
803    }
804}