Files
pandorafuture 125c762727 chore(deps): 升级核心依赖至最新版本
- rusqlite 0.32 → 0.40,涵盖全部 5 个 crate
- aes 0.8 → 0.9、cbc 0.1 → 0.2,涵盖加解密相关 4 个 crate
- toml 0.8 → 1.1 配置解析库升级
- 迁移 cipher 0.5 新接口,替换已废弃的加解密方法
- 修复 aes::cipher::Array 弃用警告,改用 TryFrom 转换
2026-06-05 00:14:25 +08:00

164 lines
6.0 KiB
Rust

use std::path::{Path, PathBuf};
use tempfile::TempDir;
use wx_context::{DecryptRequest, PersistentCache};
fn build_encrypted_db(
path: &Path,
raw_key: &[u8; 32],
salt: &[u8; 16],
params: &wx_decrypt::CryptoParams,
) {
use aes::cipher::{BlockModeEncrypt, KeyIvInit};
use hmac::{Hmac, Mac};
use sha2::Sha512;
let enc_key = wx_decrypt::kdf::derive_enc_key(raw_key, salt, params);
let mac_key = wx_decrypt::kdf::derive_mac_key(&enc_key, salt, params);
let iv = [0x42u8; 16];
let data_size = params.page_size - params.reserve - params.salt_size;
let plaintext = vec![0u8; data_size];
let mut ciphertext = plaintext;
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
Aes256CbcEnc::new((&enc_key).into(), (&iv).into())
.encrypt_padded::<aes::cipher::block_padding::NoPadding>(&mut ciphertext, data_size)
.unwrap();
let mut page = Vec::with_capacity(params.page_size);
page.extend_from_slice(salt);
page.extend_from_slice(&ciphertext);
page.extend_from_slice(&iv);
page.resize(params.page_size, 0);
let hmac_data_end = params.page_size - params.reserve + params.iv_size;
let mut mac = <Hmac<Sha512> as Mac>::new_from_slice(&mac_key).unwrap();
mac.update(&page[params.salt_size..hmac_data_end]);
mac.update(&1u32.to_le_bytes());
let hmac_result = mac.finalize().into_bytes();
let hmac_start = params.page_size - params.reserve + params.iv_size;
page[hmac_start..hmac_start + params.hmac_size]
.copy_from_slice(&hmac_result[..params.hmac_size]);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(path, &page).unwrap();
}
fn setup_encrypted_dbs(enc_root: &Path, raw_key: &[u8; 32], salt: &[u8; 16]) {
let params = &wx_decrypt::MACOS_4_1_7_31;
let contact_dir = enc_root.join("contact");
let session_dir = enc_root.join("session");
let msg_dir = enc_root.join("message");
std::fs::create_dir_all(&contact_dir).unwrap();
std::fs::create_dir_all(&session_dir).unwrap();
std::fs::create_dir_all(&msg_dir).unwrap();
build_encrypted_db(&contact_dir.join("contact.db"), raw_key, salt, params);
build_encrypted_db(&session_dir.join("session.db"), raw_key, salt, params);
build_encrypted_db(&msg_dir.join("message_0.db"), raw_key, salt, params);
build_encrypted_db(&msg_dir.join("message_1.db"), raw_key, salt, params);
build_encrypted_db(&msg_dir.join("message_2.db"), raw_key, salt, params);
}
fn make_cache(enc_root: PathBuf, cache_root: PathBuf, raw_key: [u8; 32]) -> PersistentCache {
PersistentCache::new_for_test(
cache_root,
enc_root,
Some(raw_key),
&wx_decrypt::MACOS_4_1_7_31,
)
}
// Slow by design: builds encrypted fixtures and runs the real PBKDF2-backed
// decrypt path. Skip by default for routine cargo test runs.
#[test]
#[ignore = "slow decrypt test; runs real PBKDF2/decrypt path"]
fn contacts_only_decrypts_core_dbs() {
let tmp = TempDir::new().unwrap();
let enc_root = tmp.path().join("encrypted");
let cache_root = tmp.path().join("cache");
let raw_key = [0xABu8; 32];
let salt = [0x01u8; 16];
setup_encrypted_dbs(&enc_root, &raw_key, &salt);
let cache = make_cache(enc_root, cache_root.clone(), raw_key);
let stats = DecryptRequest::new().core().execute(&cache).unwrap();
assert_eq!(stats.decrypted, 2, "only contact.db and session.db");
assert_eq!(stats.errors, 0);
assert!(cache_root.join("contact/contact.db").exists());
assert!(cache_root.join("session/session.db").exists());
assert!(!cache_root.join("message/message_0.db").exists());
assert!(!cache_root.join("message/message_1.db").exists());
assert!(!cache_root.join("message/message_2.db").exists());
}
// Slow by design: builds encrypted fixtures and runs the real PBKDF2-backed
// decrypt path. Skip by default for routine cargo test runs.
#[test]
#[ignore = "slow decrypt test; runs real PBKDF2/decrypt path"]
fn core_plus_selected_shards_decrypts_only_requested_dbs() {
let tmp = TempDir::new().unwrap();
let enc_root = tmp.path().join("encrypted");
let cache_root = tmp.path().join("cache");
let raw_key = [0xABu8; 32];
let salt = [0x01u8; 16];
setup_encrypted_dbs(&enc_root, &raw_key, &salt);
let cache = make_cache(enc_root, cache_root.clone(), raw_key);
let stats = DecryptRequest::new()
.core()
.shards(&[0, 2])
.execute(&cache)
.unwrap();
assert_eq!(
stats.decrypted, 4,
"contact + session + message_0 + message_2"
);
assert_eq!(stats.errors, 0);
assert!(cache_root.join("contact/contact.db").exists());
assert!(cache_root.join("session/session.db").exists());
assert!(cache_root.join("message/message_0.db").exists());
assert!(
!cache_root.join("message/message_1.db").exists(),
"shard 1 NOT decrypted"
);
assert!(cache_root.join("message/message_2.db").exists());
}
// Slow by design: performs multiple decrypt passes to verify incremental
// caching behavior. Skip by default for routine cargo test runs.
#[test]
#[ignore = "slow decrypt test; runs real PBKDF2/decrypt path"]
fn incremental_decrypt_skips_cached() {
let tmp = TempDir::new().unwrap();
let enc_root = tmp.path().join("encrypted");
let cache_root = tmp.path().join("cache");
let raw_key = [0xABu8; 32];
let salt = [0x01u8; 16];
setup_encrypted_dbs(&enc_root, &raw_key, &salt);
let cache = make_cache(enc_root, cache_root.clone(), raw_key);
// First: decrypt core only
let stats1 = DecryptRequest::new().core().execute(&cache).unwrap();
assert_eq!(stats1.decrypted, 2);
// Second: decrypt core + shard 0 → core should be skipped
let stats2 = DecryptRequest::new()
.core()
.shards(&[0])
.execute(&cache)
.unwrap();
assert_eq!(stats2.skipped, 2, "contact + session already cached");
assert_eq!(stats2.decrypted, 1, "only message_0 newly decrypted");
assert_eq!(stats2.errors, 0);
}