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