Skip to main content

bitwarden_wasm_internal/
pure_crypto.rs

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