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