use std::fs;
use std::path::{Path, PathBuf};
use std::ptr;
use rand::RngCore;
use windows::core::Error;
use windows::core::PCWSTR;
use windows::Win32::Foundation::LocalFree;
use windows::Win32::Security::Cryptography::{CryptProtectData, CryptUnprotectData, CRYPT_INTEGER_BLOB};
use crate::error::{EnigmaStorageError, Result};
use crate::key_provider::{KeyProvider, MasterKey};
pub struct WindowsDpapiKeyProvider {
root: PathBuf,
}
impl WindowsDpapiKeyProvider {
pub fn new<P: AsRef<Path>>(root: P) -> Self {
WindowsDpapiKeyProvider {
root: root.as_ref().to_path_buf(),
}
}
fn blob_path(&self) -> PathBuf {
self.root.join(".enigma_storage_dpapi_blob")
}
fn ensure_root(&self) -> Result<()> {
fs::create_dir_all(&self.root).map_err(|e| EnigmaStorageError::KeyProviderError(e.to_string()))
}
fn protect(&self, data: &[u8]) -> Result<Vec<u8>> {
let mut input = CRYPT_INTEGER_BLOB {
cbData: data.len() as u32,
pbData: data.as_ptr() as *mut u8,
};
let mut output = CRYPT_INTEGER_BLOB {
cbData: 0,
pbData: ptr::null_mut(),
};
let status = unsafe {
CryptProtectData(
&mut input,
PCWSTR::null(),
ptr::null(),
ptr::null_mut(),
ptr::null_mut(),
0,
&mut output,
)
};
if !status.as_bool() {
let err = Error::from_win32();
return Err(EnigmaStorageError::KeyProviderError(format!("DPAPI protect failed: {}", err)));
}
let slice = unsafe { std::slice::from_raw_parts(output.pbData, output.cbData as usize) };
let result = slice.to_vec();
unsafe {
if !output.pbData.is_null() {
LocalFree(output.pbData as isize);
}
}
Ok(result)
}
fn unprotect(&self, data: &[u8]) -> Result<Vec<u8>> {
let mut input = CRYPT_INTEGER_BLOB {
cbData: data.len() as u32,
pbData: data.as_ptr() as *mut u8,
};
let mut output = CRYPT_INTEGER_BLOB {
cbData: 0,
pbData: ptr::null_mut(),
};
let status = unsafe {
CryptUnprotectData(
&mut input,
ptr::null_mut(),
ptr::null(),
ptr::null_mut(),
ptr::null_mut(),
0,
&mut output,
)
};
if !status.as_bool() {
let err = Error::from_win32();
return Err(EnigmaStorageError::KeyProviderError(format!("DPAPI unprotect failed: {}", err)));
}
let slice = unsafe { std::slice::from_raw_parts(output.pbData, output.cbData as usize) };
let result = slice.to_vec();
unsafe {
if !output.pbData.is_null() {
LocalFree(output.pbData as isize);
}
}
Ok(result)
}
}
impl KeyProvider for WindowsDpapiKeyProvider {
fn get_or_create_master_key(&self) -> Result<MasterKey> {
self.ensure_root()?;
let path = self.blob_path();
if path.exists() {
return self.get_master_key();
}
let mut key = [0u8; 32];
rand::thread_rng().fill_bytes(&mut key);
let blob = self.protect(&key)?;
fs::write(&path, blob).map_err(|e| EnigmaStorageError::KeyProviderError(e.to_string()))?;
Ok(MasterKey::new(key))
}
fn get_master_key(&self) -> Result<MasterKey> {
let data = fs::read(self.blob_path()).map_err(|e| EnigmaStorageError::KeyProviderError(e.to_string()))?;
let plain = self.unprotect(&data)?;
if plain.len() != 32 {
return Err(EnigmaStorageError::InvalidKey);
}
let mut key = [0u8; 32];
key.copy_from_slice(&plain);
Ok(MasterKey::new(key))
}
}