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, 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_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<KeySlotIds> = 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<KeySlotIds> = 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<KeySlotIds> = 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<KeySlotIds> = 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<KeySlotIds> = 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<KeySlotIds> = 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::<Sha1>::new();
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::<Sha1>::new();
409        let mut rng = rand::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::rng();
420        rng.random_range(min..=max)
421    }
422
423    /// Generates a new v4 UUID using a cryptographically secure random number generator
424    pub fn new_guid() -> String {
425        let _span = tracing::info_span!("PureCrypto::new_guid").entered();
426        uuid::Uuid::new_v4().to_string()
427    }
428}
429
430#[wasm_bindgen]
431#[derive(Debug)]
432pub enum RsaError {
433    Decryption,
434    Encryption,
435    KeyParse,
436    KeySerialize,
437}
438
439#[cfg(test)]
440mod tests {
441    use std::{num::NonZero, str::FromStr};
442
443    use bitwarden_crypto::EncString;
444
445    use super::*;
446
447    const KEY: &[u8] = &[
448        81, 142, 1, 228, 222, 3, 3, 133, 34, 176, 35, 66, 150, 6, 109, 70, 190, 149, 47, 47, 89,
449        23, 144, 87, 92, 46, 220, 13, 148, 106, 162, 234, 202, 139, 136, 33, 16, 200, 8, 73, 176,
450        172, 185, 187, 224, 10, 65, 223, 228, 54, 92, 181, 8, 213, 162, 221, 117, 254, 245, 111,
451        55, 211, 77, 29,
452    ];
453
454    const ENCRYPTED: &str = "2.Dh7AFLXR+LXcxUaO5cRjpg==|uXyhubjAoNH8lTdy/zgJDQ==|cHEMboj0MYsU5yDRQ1rLCgxcjNbKRc1PWKuv8bpU5pM=";
455    const DECRYPTED: &str = "test";
456    const DECRYPTED_BYTES: &[u8] = b"test";
457    const ENCRYPTED_BYTES: &[u8] = &[
458        2, 209, 195, 115, 49, 205, 253, 128, 162, 169, 246, 175, 217, 144, 73, 108, 191, 27, 113,
459        69, 55, 94, 142, 62, 129, 204, 173, 130, 37, 42, 97, 209, 25, 192, 64, 126, 112, 139, 248,
460        2, 89, 112, 178, 83, 25, 77, 130, 187, 127, 85, 179, 211, 159, 186, 111, 44, 109, 211, 18,
461        120, 104, 144, 4, 76, 3,
462    ];
463
464    const PEM_KEY: &str = "-----BEGIN PRIVATE KEY-----
465MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5
466qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc
467afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm
468qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv
469b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw
470P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam
471rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi
472szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx
4730d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+
4748vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR
475jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach
476vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI
4771u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR
478J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7
479l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ
480TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9
481ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye
482KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN
483wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ
484UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA
485kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W
486pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN
487Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi
488CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup
489PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf
490DnqOsltgPomWZ7xVfMkm9niL2OA=
491-----END PRIVATE KEY-----";
492
493    const SIGNING_KEY_WRAPPING_KEY: &[u8] = &[
494        40, 215, 110, 199, 183, 4, 182, 78, 213, 123, 251, 113, 72, 223, 57, 2, 3, 81, 136, 19, 88,
495        78, 206, 176, 158, 251, 211, 84, 1, 199, 203, 142, 176, 227, 187, 136, 209, 79, 23, 13, 44,
496        224, 90, 10, 191, 72, 22, 227, 171, 105, 107, 139, 24, 49, 9, 150, 103, 139, 151, 204, 165,
497        121, 165, 71,
498    ];
499    const SIGNING_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, 130, 1, 2, 35, 88, 32, 148, 2, 66, 69, 169, 57, 129, 240, 37, 18, 225, 211, 207,
502        133, 66, 143, 204, 238, 113, 152, 43, 112, 133, 173, 179, 17, 202, 135, 175, 237, 1, 59,
503        32, 6,
504    ];
505    const VERIFYING_KEY: &[u8] = &[
506        166, 1, 1, 2, 80, 123, 226, 102, 228, 194, 232, 71, 30, 183, 42, 219, 193, 50, 30, 21, 43,
507        3, 39, 4, 129, 2, 32, 6, 33, 88, 32, 63, 70, 49, 37, 246, 232, 146, 144, 83, 224, 0, 17,
508        111, 248, 16, 242, 69, 195, 84, 46, 39, 218, 55, 63, 90, 112, 148, 91, 224, 186, 122, 4,
509    ];
510    const PUBLIC_KEY: &[u8] = &[
511        48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0,
512        48, 130, 1, 10, 2, 130, 1, 1, 0, 173, 4, 54, 63, 125, 12, 254, 38, 115, 34, 95, 164, 148,
513        115, 86, 140, 129, 74, 19, 70, 212, 212, 130, 163, 105, 249, 101, 120, 154, 46, 194, 250,
514        229, 242, 156, 67, 109, 179, 187, 134, 59, 235, 60, 107, 144, 163, 35, 22, 109, 230, 134,
515        243, 44, 243, 79, 84, 76, 11, 64, 56, 236, 167, 98, 26, 30, 213, 143, 105, 52, 92, 129, 92,
516        88, 22, 115, 135, 63, 215, 79, 8, 11, 183, 124, 10, 73, 231, 170, 110, 210, 178, 22, 100,
517        76, 75, 118, 202, 252, 204, 67, 204, 152, 6, 244, 208, 161, 146, 103, 225, 233, 239, 88,
518        195, 88, 150, 230, 111, 62, 142, 12, 157, 184, 155, 34, 84, 237, 111, 11, 97, 56, 152, 130,
519        14, 72, 123, 140, 47, 137, 5, 97, 166, 4, 147, 111, 23, 65, 78, 63, 208, 198, 50, 161, 39,
520        80, 143, 100, 194, 37, 252, 194, 53, 207, 166, 168, 250, 165, 121, 9, 207, 90, 36, 213,
521        211, 84, 255, 14, 205, 114, 135, 217, 137, 105, 232, 58, 169, 222, 10, 13, 138, 203, 16,
522        12, 122, 72, 227, 95, 160, 111, 54, 200, 198, 143, 156, 15, 143, 196, 50, 150, 204, 144,
523        255, 162, 248, 50, 28, 47, 66, 9, 83, 158, 67, 9, 50, 147, 174, 147, 200, 199, 238, 190,
524        248, 60, 114, 218, 32, 209, 120, 218, 17, 234, 14, 128, 192, 166, 33, 60, 73, 227, 108,
525        201, 41, 160, 81, 133, 171, 205, 221, 2, 3, 1, 0, 1,
526    ];
527
528    const SIGNED_PUBLIC_KEY: &[u8] = &[
529        132, 88, 30, 164, 1, 39, 3, 24, 60, 4, 80, 123, 226, 102, 228, 194, 232, 71, 30, 183, 42,
530        219, 193, 50, 30, 21, 43, 58, 0, 1, 56, 127, 1, 160, 89, 1, 78, 163, 105, 97, 108, 103,
531        111, 114, 105, 116, 104, 109, 0, 109, 99, 111, 110, 116, 101, 110, 116, 70, 111, 114, 109,
532        97, 116, 0, 105, 112, 117, 98, 108, 105, 99, 75, 101, 121, 89, 1, 38, 48, 130, 1, 34, 48,
533        13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2,
534        130, 1, 1, 0, 173, 4, 54, 63, 125, 12, 254, 38, 115, 34, 95, 164, 148, 115, 86, 140, 129,
535        74, 19, 70, 212, 212, 130, 163, 105, 249, 101, 120, 154, 46, 194, 250, 229, 242, 156, 67,
536        109, 179, 187, 134, 59, 235, 60, 107, 144, 163, 35, 22, 109, 230, 134, 243, 44, 243, 79,
537        84, 76, 11, 64, 56, 236, 167, 98, 26, 30, 213, 143, 105, 52, 92, 129, 92, 88, 22, 115, 135,
538        63, 215, 79, 8, 11, 183, 124, 10, 73, 231, 170, 110, 210, 178, 22, 100, 76, 75, 118, 202,
539        252, 204, 67, 204, 152, 6, 244, 208, 161, 146, 103, 225, 233, 239, 88, 195, 88, 150, 230,
540        111, 62, 142, 12, 157, 184, 155, 34, 84, 237, 111, 11, 97, 56, 152, 130, 14, 72, 123, 140,
541        47, 137, 5, 97, 166, 4, 147, 111, 23, 65, 78, 63, 208, 198, 50, 161, 39, 80, 143, 100, 194,
542        37, 252, 194, 53, 207, 166, 168, 250, 165, 121, 9, 207, 90, 36, 213, 211, 84, 255, 14, 205,
543        114, 135, 217, 137, 105, 232, 58, 169, 222, 10, 13, 138, 203, 16, 12, 122, 72, 227, 95,
544        160, 111, 54, 200, 198, 143, 156, 15, 143, 196, 50, 150, 204, 144, 255, 162, 248, 50, 28,
545        47, 66, 9, 83, 158, 67, 9, 50, 147, 174, 147, 200, 199, 238, 190, 248, 60, 114, 218, 32,
546        209, 120, 218, 17, 234, 14, 128, 192, 166, 33, 60, 73, 227, 108, 201, 41, 160, 81, 133,
547        171, 205, 221, 2, 3, 1, 0, 1, 88, 64, 207, 18, 4, 242, 149, 31, 37, 255, 243, 62, 78, 46,
548        12, 150, 134, 159, 69, 89, 62, 222, 132, 12, 177, 74, 155, 80, 154, 37, 77, 176, 19, 142,
549        73, 4, 134, 242, 24, 56, 54, 38, 178, 59, 11, 118, 230, 159, 87, 91, 20, 237, 188, 186,
550        216, 86, 189, 50, 46, 173, 117, 36, 54, 105, 216, 9,
551    ];
552
553    const DERIVED_KDF_MATERIAL_PBKDF2: &[u8] = &[
554        129, 57, 137, 140, 156, 220, 110, 212, 201, 255, 52, 182, 22, 206, 221, 66, 136, 199, 181,
555        89, 252, 175, 82, 168, 79, 204, 88, 174, 166, 60, 52, 79,
556    ];
557    const DERIVED_KDF_MATERIAL_ARGON2ID: &[u8] = &[
558        221, 57, 158, 206, 27, 154, 188, 170, 33, 198, 250, 144, 191, 231, 29, 74, 201, 102, 253,
559        77, 8, 128, 173, 111, 217, 41, 125, 9, 156, 52, 112, 140,
560    ];
561
562    #[test]
563    fn test_symmetric_decrypt() {
564        let enc_string = EncString::from_str(ENCRYPTED).unwrap();
565
566        let result = PureCrypto::symmetric_decrypt_string(enc_string.to_string(), KEY.to_vec());
567        assert!(result.is_ok());
568        assert_eq!(result.unwrap(), DECRYPTED);
569    }
570
571    #[test]
572    fn test_symmetric_encrypt() {
573        let result = PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec());
574        assert!(result.is_ok());
575        // Cannot test encrypted string content because IV is unique per encryption
576    }
577
578    #[test]
579    fn test_symmetric_string_round_trip() {
580        let encrypted =
581            PureCrypto::symmetric_encrypt_string(DECRYPTED.to_string(), KEY.to_vec()).unwrap();
582        let decrypted =
583            PureCrypto::symmetric_decrypt_string(encrypted.clone(), KEY.to_vec()).unwrap();
584        assert_eq!(decrypted, DECRYPTED);
585    }
586
587    #[test]
588    fn test_symmetric_bytes_round_trip() {
589        let encrypted =
590            PureCrypto::symmetric_encrypt_bytes(DECRYPTED.as_bytes().to_vec(), KEY.to_vec())
591                .unwrap();
592        let decrypted =
593            PureCrypto::symmetric_decrypt_bytes(encrypted.clone(), KEY.to_vec()).unwrap();
594        assert_eq!(decrypted, DECRYPTED.as_bytes().to_vec());
595    }
596
597    #[test]
598    fn test_symmetric_decrypt_array_buffer() {
599        let result = PureCrypto::symmetric_decrypt_filedata(ENCRYPTED_BYTES.to_vec(), KEY.to_vec());
600        assert!(result.is_ok());
601        assert_eq!(result.unwrap(), DECRYPTED_BYTES);
602    }
603
604    #[test]
605    fn test_symmetric_encrypt_to_array_buffer() {
606        let result = PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec());
607        assert!(result.is_ok());
608        // Cannot test encrypted string content because IV is unique per encryption
609    }
610
611    #[test]
612    fn test_symmetric_filedata_round_trip() {
613        let encrypted =
614            PureCrypto::symmetric_encrypt_filedata(DECRYPTED_BYTES.to_vec(), KEY.to_vec()).unwrap();
615        let decrypted =
616            PureCrypto::symmetric_decrypt_filedata(encrypted.clone(), KEY.to_vec()).unwrap();
617        assert_eq!(decrypted, DECRYPTED_BYTES);
618    }
619
620    #[test]
621    fn test_make_aes256_cbc_hmac_key() {
622        let key = PureCrypto::make_user_key_aes256_cbc_hmac();
623        assert_eq!(key.len(), 64);
624    }
625
626    #[test]
627    fn test_make_xchacha20_poly1305_key() {
628        let key = PureCrypto::make_user_key_xchacha20_poly1305();
629        assert!(key.len() > 64);
630    }
631
632    #[test]
633    fn roundtrip_encrypt_user_key_with_master_password() {
634        let master_password = "test";
635        let email = "[email protected]";
636        let kdf = Kdf::PBKDF2 {
637            iterations: NonZero::try_from(600000).unwrap(),
638        };
639        let user_key = PureCrypto::make_user_key_aes256_cbc_hmac();
640        let encrypted_user_key = PureCrypto::encrypt_user_key_with_master_password(
641            user_key.clone(),
642            master_password.to_string(),
643            email.to_string(),
644            kdf.clone(),
645        )
646        .unwrap();
647        let decrypted_user_key = PureCrypto::decrypt_user_key_with_master_password(
648            encrypted_user_key,
649            master_password.to_string(),
650            email.to_string(),
651            kdf,
652        )
653        .unwrap();
654        assert_eq!(user_key, decrypted_user_key);
655    }
656
657    #[test]
658    fn test_wrap_unwrap_symmetric_key() {
659        let key_to_be_wrapped = PureCrypto::make_user_key_aes256_cbc_hmac();
660        let wrapping_key = PureCrypto::make_user_key_aes256_cbc_hmac();
661        let wrapped_key =
662            PureCrypto::wrap_symmetric_key(key_to_be_wrapped.clone(), wrapping_key.clone())
663                .unwrap();
664        let unwrapped_key = PureCrypto::unwrap_symmetric_key(wrapped_key, wrapping_key).unwrap();
665        assert_eq!(key_to_be_wrapped, unwrapped_key);
666    }
667
668    #[test]
669    fn test_wrap_encapsulation_key() {
670        let decapsulation_key = PrivateKey::from_pem(PEM_KEY).unwrap();
671        let encapsulation_key = decapsulation_key
672            .to_public_key()
673            .to_der()
674            .unwrap()
675            .as_ref()
676            .to_vec();
677        let wrapping_key = PureCrypto::make_user_key_aes256_cbc_hmac();
678        let wrapped_key =
679            PureCrypto::wrap_encapsulation_key(encapsulation_key.clone(), wrapping_key.clone())
680                .unwrap();
681        let unwrapped_key =
682            PureCrypto::unwrap_encapsulation_key(wrapped_key, wrapping_key).unwrap();
683        assert_eq!(encapsulation_key, unwrapped_key);
684    }
685
686    #[test]
687    fn test_wrap_decapsulation_key() {
688        let decapsulation_key = PrivateKey::from_pem(PEM_KEY).unwrap();
689        let wrapping_key = PureCrypto::make_user_key_aes256_cbc_hmac();
690        let wrapped_key = PureCrypto::wrap_decapsulation_key(
691            decapsulation_key.to_der().unwrap().to_vec(),
692            wrapping_key.clone(),
693        )
694        .unwrap();
695        let unwrapped_key =
696            PureCrypto::unwrap_decapsulation_key(wrapped_key, wrapping_key).unwrap();
697        assert_eq!(decapsulation_key.to_der().unwrap().to_vec(), unwrapped_key);
698    }
699
700    #[test]
701    fn test_encapsulate_key_unsigned() {
702        let shared_key = PureCrypto::make_user_key_aes256_cbc_hmac();
703        let decapsulation_key = PrivateKey::from_pem(PEM_KEY).unwrap();
704        let encapsulation_key = decapsulation_key.to_public_key().to_der().unwrap();
705        let encapsulated_key = PureCrypto::encapsulate_key_unsigned(
706            shared_key.clone(),
707            encapsulation_key.clone().to_vec(),
708        )
709        .unwrap();
710        let unwrapped_key = PureCrypto::decapsulate_key_unsigned(
711            encapsulated_key,
712            decapsulation_key.to_der().unwrap().to_vec(),
713        )
714        .unwrap();
715        assert_eq!(shared_key, unwrapped_key);
716    }
717
718    #[test]
719    fn test_key_algorithm_for_verifying_key() {
720        let verifying_key =
721            VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY.to_vec())).unwrap();
722        let algorithm =
723            PureCrypto::key_algorithm_for_verifying_key(verifying_key.to_cose().to_vec()).unwrap();
724        assert_eq!(algorithm, SignatureAlgorithm::Ed25519);
725    }
726
727    #[test]
728    fn test_verifying_key_for_signing_key() {
729        let wrapped_signing_key = PureCrypto::symmetric_encrypt_bytes(
730            SIGNING_KEY.to_vec(),
731            SIGNING_KEY_WRAPPING_KEY.to_vec(),
732        )
733        .unwrap();
734        let verifying_key =
735            VerifyingKey::from_cose(&CoseKeyBytes::from(VERIFYING_KEY.to_vec())).unwrap();
736        let verifying_key_derived = PureCrypto::verifying_key_for_signing_key(
737            wrapped_signing_key.to_string(),
738            SIGNING_KEY_WRAPPING_KEY.to_vec(),
739        )
740        .unwrap();
741        let verifying_key_derived =
742            VerifyingKey::from_cose(&CoseKeyBytes::from(verifying_key_derived)).unwrap();
743        assert_eq!(verifying_key.to_cose(), verifying_key_derived.to_cose());
744    }
745
746    #[test]
747    fn test_verify_and_unwrap_signed_public_key() {
748        let public_key = PureCrypto::verify_and_unwrap_signed_public_key(
749            SIGNED_PUBLIC_KEY.to_vec(),
750            VERIFYING_KEY.to_vec(),
751        )
752        .unwrap();
753        assert_eq!(public_key, PUBLIC_KEY);
754    }
755
756    #[test]
757    fn test_derive_pbkdf2_output() {
758        let password = "test_password".as_bytes();
759        let email = "[email protected]".as_bytes();
760        let kdf = Kdf::PBKDF2 {
761            iterations: NonZero::try_from(600000).unwrap(),
762        };
763        let derived_key = PureCrypto::derive_kdf_material(password, email, kdf).unwrap();
764        assert_eq!(derived_key, DERIVED_KDF_MATERIAL_PBKDF2);
765    }
766
767    #[test]
768    fn test_derived_argon2_output() {
769        let password = "test_password".as_bytes();
770        let email = "[email protected]".as_bytes();
771        let kdf = Kdf::Argon2id {
772            iterations: NonZero::try_from(3).unwrap(),
773            memory: NonZero::try_from(64).unwrap(),
774            parallelism: NonZero::try_from(4).unwrap(),
775        };
776        let derived_key = PureCrypto::derive_kdf_material(password, email, kdf).unwrap();
777        assert_eq!(derived_key, DERIVED_KDF_MATERIAL_ARGON2ID);
778    }
779
780    #[test]
781    fn test_decrypt_user_key_with_master_key() {
782        let password = "test_password";
783        let email = "[email protected]";
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 master_key = MasterKey::derive(password, email, kdf).unwrap();
790        let (user_key, encrypted_user_key) = master_key.make_user_key().unwrap();
791        let master_key_bytes = master_key.to_base64().into_bytes();
792
793        let decrypted_user_key = PureCrypto::decrypt_user_key_with_master_key(
794            encrypted_user_key.to_string(),
795            master_key_bytes,
796        )
797        .unwrap();
798        assert_eq!(user_key.0.to_encoded().to_vec(), decrypted_user_key);
799    }
800
801    #[test]
802    fn test_rsa_round_trip() {
803        let private_key = PureCrypto::rsa_generate_keypair().unwrap();
804        let public_key = PureCrypto::rsa_extract_public_key(private_key.clone()).unwrap();
805        let plain_data = b"Test RSA encryption data".to_vec();
806        let encrypted_data = PureCrypto::rsa_encrypt_data(plain_data.clone(), public_key).unwrap();
807        let decrypted_data = PureCrypto::rsa_decrypt_data(encrypted_data, private_key).unwrap();
808        assert_eq!(plain_data, decrypted_data);
809    }
810
811    #[test]
812    fn test_new_guid_is_v4_and_unique() {
813        let first = PureCrypto::new_guid();
814        let second = PureCrypto::new_guid();
815
816        let parsed = uuid::Uuid::parse_str(&first).expect("new_guid output must parse as a UUID");
817        assert_eq!(parsed.get_version_num(), 4);
818        assert_ne!(first, second);
819    }
820}