From 6a7f437b1fefd64714de9b51084ecf45e2ddbace Mon Sep 17 00:00:00 2001 From: adudek-bw Date: Thu, 18 Sep 2025 13:00:52 -0400 Subject: [PATCH] Add unit tests to chromium importer (#16462) * Add unit tests to chromium importer --- .../bitwarden_chromium_importer/src/crypto.rs | 42 ++++++- .../bitwarden_chromium_importer/src/util.rs | 109 +++++++++++++++++- 2 files changed, 146 insertions(+), 5 deletions(-) diff --git a/apps/desktop/desktop_native/bitwarden_chromium_importer/src/crypto.rs b/apps/desktop/desktop_native/bitwarden_chromium_importer/src/crypto.rs index a2b87d758a4..ce10b27b83f 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_importer/src/crypto.rs +++ b/apps/desktop/desktop_native/bitwarden_chromium_importer/src/crypto.rs @@ -1,17 +1,51 @@ //! Cryptographic primitives used in the SDK -use anyhow::{Result, anyhow}; +use anyhow::{anyhow, Result}; use aes::cipher::{ block_padding::Pkcs7, generic_array::GenericArray, typenum::U32, BlockDecryptMut, KeyIvInit, }; +#[allow(clippy::question_mark)] pub fn decrypt_aes256(iv: &[u8; 16], data: &[u8], key: GenericArray) -> Result> { let iv = GenericArray::from_slice(iv); let mut data = data.to_vec(); - return cbc::Decryptor::::new(&key, iv) + let result = cbc::Decryptor::::new(&key, iv) .decrypt_padded_mut::(&mut data) - .map_err(|_| anyhow!("Failed to decrypt data"))?; - + .map_err(|_| anyhow!("Failed to decrypt data")); + if let Err(e) = result { + return Err(e); + } Ok(data) } + +#[cfg(test)] +mod tests { + use aes::cipher::{ + generic_array::{sequence::GenericSequence, GenericArray}, + ArrayLength, + }; + use base64::{engine::general_purpose::STANDARD, Engine}; + + pub fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec { + (0..length).map(|i| offset + i as u8 * increment).collect() + } + pub fn generate_generic_array>( + offset: u8, + increment: u8, + ) -> GenericArray { + GenericArray::generate(|i| offset + i as u8 * increment) + } + + #[test] + fn test_decrypt_aes256() { + let iv = generate_vec(16, 0, 1); + let iv: &[u8; 16] = iv.as_slice().try_into().unwrap(); + let key = generate_generic_array(0, 1); + let data: Vec = STANDARD.decode("ByUF8vhyX4ddU9gcooznwA==").unwrap(); + + let decrypted = super::decrypt_aes256(iv, &data, key).unwrap(); + + assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!\u{6}\u{6}\u{6}\u{6}\u{6}\u{6}"); + } +} diff --git a/apps/desktop/desktop_native/bitwarden_chromium_importer/src/util.rs b/apps/desktop/desktop_native/bitwarden_chromium_importer/src/util.rs index 5edd4a2610f..e9c20ab621d 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_importer/src/util.rs +++ b/apps/desktop/desktop_native/bitwarden_chromium_importer/src/util.rs @@ -29,7 +29,7 @@ pub fn split_encrypted_string_and_validate<'a>( pub fn decrypt_aes_128_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result> { let decryptor = cbc::Decryptor::::new_from_slices(key, iv)?; - let plaintext = decryptor + let plaintext: Vec = decryptor .decrypt_padded_vec_mut::(ciphertext) .map_err(|e| anyhow!("Failed to decrypt: {}", e))?; Ok(plaintext) @@ -41,3 +41,110 @@ pub fn derive_saltysalt(password: &[u8], iterations: u32) -> Result> { .map_err(|e| anyhow!("Failed to derive master key: {}", e))?; Ok(key) } + +#[cfg(test)] +mod tests { + pub fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec { + (0..length).map(|i| offset + i as u8 * increment).collect() + } + pub fn generate_generic_array>( + offset: u8, + increment: u8, + ) -> GenericArray { + GenericArray::generate(|i| offset + i as u8 * increment) + } + + use aes::cipher::{ + block_padding::Pkcs7, + generic_array::{sequence::GenericSequence, GenericArray}, + ArrayLength, BlockEncryptMut, KeyIvInit, + }; + + const LENGTH16: usize = 16; + const LENGTH10: usize = 10; + const LENGTH0: usize = 0; + + fn run_split_encrypted_string_test<'a, const N: usize>( + successfully_split: bool, + plaintext_to_encrypt: &'a str, + version: &'a str, + password: Vec, + ) { + let res = super::split_encrypted_string(plaintext_to_encrypt.as_bytes()); + + assert_eq!(res.is_ok(), successfully_split); + if let Ok((version_found, password_found)) = res { + assert_eq!(version_found, version); + assert_eq!(password_found.len(), password.len()); + assert_eq!(password_found, &password); + } + } + + #[test] + fn test_split_encrypted_string_success_v10() { + run_split_encrypted_string_test::( + true, + "v10EncryptMe!", + "v10", + vec![69, 110, 99, 114, 121, 112, 116, 77, 101, 33], + ); + } + + #[test] + fn test_split_encrypted_string_fail_no_password() { + run_split_encrypted_string_test::(true, "v09", "v09", Vec::::new()); + } + + #[test] + fn test_split_encrypted_string_fail_too_small() { + run_split_encrypted_string_test::(false, "v0", "v0", vec![0]); + } + + fn run_split_encrypted_string_and_validate_test( + valid_version: bool, + plaintext_to_encrypt: &str, + supported_versions: &[&str], + ) { + let result = super::split_encrypted_string_and_validate( + plaintext_to_encrypt.as_bytes(), + supported_versions, + ); + assert_eq!(result.is_ok(), valid_version); + } + + #[test] + fn test_split_encrypted_string_and_validate_version_found_from_single_version() { + run_split_encrypted_string_and_validate_test(true, "v10EncryptMe!", &["v10"]); + } + + #[test] + fn test_split_encrypted_string_and_validate_version_found_from_multiple_versions() { + run_split_encrypted_string_and_validate_test(true, "v10EncryptMe!", &["v11", "v10"]); + } + + #[test] + fn test_split_encrypted_string_and_validate_version_not_found() { + run_split_encrypted_string_and_validate_test(false, "v10EncryptMe!", &["v11", "v12"]); + } + + #[test] + fn test_split_encrypted_string_and_validate_version_not_found_empty_list() { + run_split_encrypted_string_and_validate_test(false, "v10EncryptMe!", &[]); + } + + #[test] + fn test_decrypt_aes_128_cbc() { + let offset = 0; + let increment = 1; + + let iv = generate_vec(LENGTH16, offset, increment); + let iv: &[u8; LENGTH16] = iv.as_slice().try_into().unwrap(); + let key: GenericArray = generate_generic_array(0, 1); + let data = cbc::Encryptor::::new(&key, iv.into()) + .encrypt_padded_vec_mut::("EncryptMe!".as_bytes()); + + let decrypted = super::decrypt_aes_128_cbc(&key, iv, &data).unwrap(); + + assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!"); + } +}