#[cfg(windows)]
use anyhow::{Context, Result};
#[cfg(windows)]
use std::{ptr, slice};
#[cfg(windows)]
use winapi::shared::minwindef::DWORD;
#[cfg(windows)]
use winapi::um::dpapi::{CryptProtectData, CryptUnprotectData};
#[cfg(windows)]
use winapi::um::wincrypt::DATA_BLOB;
#[cfg(windows)]
#[derive(Clone, Copy, Debug)]
pub enum Scope {
User,
Machine,
}
#[cfg(windows)]
fn to_blob(data: &[u8]) -> DATA_BLOB {
DATA_BLOB {
cbData: data.len() as DWORD,
pbData: data.as_ptr() as *mut u8,
}
}
#[cfg(windows)]
pub fn encrypt_data(data: &[u8], scope: Scope, entropy: Option<&[u8]>) -> Result<Vec<u8>> {
log::debug!("Encrypting with DPAPI ({:?} scope)", scope);
let flags = match scope {
Scope::User => 0, Scope::Machine => 0x4, };
unsafe {
let mut input = to_blob(data);
let mut entropy_blob = if let Some(ent) = entropy {
to_blob(ent)
} else {
DATA_BLOB {
cbData: 0,
pbData: ptr::null_mut(),
}
};
let mut output = DATA_BLOB {
cbData: 0,
pbData: ptr::null_mut(),
};
let success = CryptProtectData(
&mut input,
ptr::null(),
if entropy.is_some() {
&mut entropy_blob
} else {
ptr::null_mut()
},
ptr::null_mut(),
ptr::null_mut(),
flags,
&mut output,
);
if success == 0 {
return Err(std::io::Error::last_os_error()).context("CryptProtectData failed");
}
let encrypted = slice::from_raw_parts(output.pbData, output.cbData as usize).to_vec();
winapi::um::winbase::LocalFree(output.pbData as *mut _);
Ok(encrypted)
}
}
#[cfg(windows)]
pub fn decrypt_data(data: &[u8], scope: Scope, entropy: Option<&[u8]>) -> Result<Vec<u8>> {
log::debug!("Decrypting with DPAPI ({:?} scope)", scope);
let flags = match scope {
Scope::User => 0,
Scope::Machine => 0x4,
};
unsafe {
let mut input = to_blob(data);
let mut entropy_blob = if let Some(ent) = entropy {
to_blob(ent)
} else {
DATA_BLOB {
cbData: 0,
pbData: ptr::null_mut(),
}
};
let mut output = DATA_BLOB {
cbData: 0,
pbData: ptr::null_mut(),
};
let success = CryptUnprotectData(
&mut input,
ptr::null_mut(),
if entropy.is_some() {
&mut entropy_blob
} else {
ptr::null_mut()
},
ptr::null_mut(),
ptr::null_mut(),
flags,
&mut output,
);
if success == 0 {
return Err(std::io::Error::last_os_error()).context("CryptUnprotectData failed");
}
let decrypted = slice::from_raw_parts(output.pbData, output.cbData as usize).to_vec();
winapi::um::winbase::LocalFree(output.pbData as *mut _);
Ok(decrypted)
}
}
#[cfg(test)]
#[cfg(windows)]
mod tests {
use super::*;
#[test]
fn round_trip_user_scope() {
let original = b"user secret";
let encrypted = encrypt_data(original, Scope::User, None).expect("User encryption failed");
assert_ne!(original.to_vec(), encrypted);
let decrypted =
decrypt_data(&encrypted, Scope::User, None).expect("User decryption failed");
assert_eq!(original.to_vec(), decrypted);
}
#[test]
fn round_trip_user_scope_entropy() {
let original = b"user secret";
let entropy = b"user entropy";
let encrypted =
encrypt_data(original, Scope::User, Some(entropy)).expect("User encryption failed");
assert_ne!(original.to_vec(), encrypted);
let decrypted =
decrypt_data(&encrypted, Scope::User, Some(entropy)).expect("User decryption failed");
assert_eq!(original.to_vec(), decrypted);
}
#[test]
fn round_trip_machine_scope() {
let original = b"machine secret";
let encrypted =
encrypt_data(original, Scope::Machine, None).expect("Machine encryption failed");
assert_ne!(original.to_vec(), encrypted);
let decrypted =
decrypt_data(&encrypted, Scope::Machine, None).expect("Machine decryption failed");
assert_eq!(original.to_vec(), decrypted);
}
#[test]
fn round_trip_machine_scope_entropy() {
let original = b"machine secret";
let entropy = b"user entropy";
let encrypted = encrypt_data(original, Scope::Machine, Some(entropy))
.expect("Machine encryption failed");
assert_ne!(original.to_vec(), encrypted);
let decrypted = decrypt_data(&encrypted, Scope::Machine, Some(entropy))
.expect("Machine decryption failed");
assert_eq!(original.to_vec(), decrypted);
}
#[test]
fn handles_empty_input() {
let data = b"";
let encrypted = encrypt_data(data, Scope::Machine, None).expect("Encrypt empty");
let decrypted = decrypt_data(&encrypted, Scope::Machine, None).expect("Decrypt empty");
assert_eq!(data.to_vec(), decrypted);
}
#[test]
fn handles_empty_input_entropy() {
let data = b"";
let entropy = b"random entropy";
let encrypted = encrypt_data(data, Scope::Machine, Some(entropy)).expect("Encrypt empty");
let decrypted =
decrypt_data(&encrypted, Scope::Machine, Some(entropy)).expect("Decrypt empty");
assert_eq!(data.to_vec(), decrypted);
}
#[test]
fn handles_empty_entropy() {
let data = b"random value";
let entropy = b"";
let encrypted = encrypt_data(data, Scope::Machine, Some(entropy)).expect("Encrypt empty");
let decrypted =
decrypt_data(&encrypted, Scope::Machine, Some(entropy)).expect("Decrypt empty");
assert_eq!(data.to_vec(), decrypted);
}
#[test]
fn handles_large_input() {
let data = vec![0xAAu8; 5 * 1024 * 1024];
let encrypted = encrypt_data(&data, Scope::Machine, None).expect("Encrypt large");
let decrypted = decrypt_data(&encrypted, Scope::Machine, None).expect("Decrypt large");
assert_eq!(data, decrypted);
}
#[test]
fn handles_large_input_entropy() {
let data = vec![0xAAu8; 5 * 1024 * 1024];
let entropy = b"random entropy";
let encrypted = encrypt_data(&data, Scope::Machine, Some(entropy)).expect("Encrypt large");
let decrypted =
decrypt_data(&encrypted, Scope::Machine, Some(entropy)).expect("Decrypt large");
assert_eq!(data, decrypted);
}
#[test]
fn handles_large_entropy() {
let data = b"Random input";
let entropy = &vec![0xAAu8; 5 * 1024 * 1024];
let encrypted = encrypt_data(data, Scope::Machine, Some(entropy)).expect("Encrypt large");
let decrypted =
decrypt_data(&encrypted, Scope::Machine, Some(entropy)).expect("Decrypt large");
assert_eq!(data.to_vec(), decrypted);
}
#[test]
fn fails_on_corrupted_data() {
let original = b"important";
let mut encrypted = encrypt_data(original, Scope::Machine, None).expect("Encrypt failed");
encrypted[0] ^= 0xFF;
let result = decrypt_data(&encrypted, Scope::Machine, None);
assert!(result.is_err(), "Corrupted data should fail");
}
#[test]
fn fails_on_corrupted_data_entropy() {
let original = b"important";
let entropy = b"entropy";
let mut encrypted =
encrypt_data(original, Scope::Machine, Some(entropy)).expect("Encrypt failed");
encrypted[0] ^= 0xFF;
let result = decrypt_data(&encrypted, Scope::Machine, Some(entropy));
assert!(result.is_err(), "Corrupted data should fail");
}
#[test]
fn fails_on_wrong_entropy() {
let original = b"user secret";
let entropy = b"user entropy";
let bad_entropy = b"bad entropy";
let encrypted =
encrypt_data(original, Scope::User, Some(entropy)).expect("User encryption failed");
assert_ne!(original.to_vec(), encrypted);
let result = decrypt_data(&encrypted, Scope::User, Some(bad_entropy));
assert!(result.is_err(), "Wrong entropy should fail");
}
#[test]
fn entropy_encrypts_differently() {
let original = b"user secret";
let entropy = b"user entropy";
let bad_entropy = b"bad entropy";
let encrypted =
encrypt_data(original, Scope::User, Some(entropy)).expect("User encryption failed");
assert_ne!(original.to_vec(), encrypted);
let other_encrypted =
encrypt_data(original, Scope::User, Some(bad_entropy)).expect("User encryption failed");
assert_ne!(encrypted, other_encrypted);
}
}