bitwarden_wasm_internal/
pure_crypto.rs

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