mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-25 05:03:05 +00:00
win_util: add DPAPI wrappers
Adds wrappers for CryptProtectData and CryptUnprotectData. BUG=b:324301901 TEST=unit tests Change-Id: I89373a6e546c0364471d05b3f665de0e96b3cd81 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/5277999 Reviewed-by: Judson Powers <judsonp@google.com> Reviewed-by: Vikram Auradkar <auradkar@google.com> Commit-Queue: Noah Gold <nkgold@google.com>
This commit is contained in:
parent
9a180d024b
commit
75e21814ac
2 changed files with 183 additions and 0 deletions
181
win_util/src/dpapi.rs
Normal file
181
win_util/src/dpapi.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
// Copyright 2024 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
//! Safe, Rusty wrappers around DPAPI.
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use winapi::um::dpapi::CryptProtectData;
|
||||
use winapi::um::dpapi::CryptUnprotectData;
|
||||
use winapi::um::winbase::LocalFree;
|
||||
use winapi::um::wincrypt::DATA_BLOB;
|
||||
|
||||
use crate::syscall_bail;
|
||||
|
||||
/// Wrapper around buffers allocated by DPAPI that can be freed with LocalFree.
|
||||
pub struct LocalAllocBuffer {
|
||||
ptr: *mut u8,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl LocalAllocBuffer {
|
||||
/// # Safety
|
||||
/// 0. ptr is a valid buffer of length len and is safe to free with LocalFree.
|
||||
/// 1. The caller transfers ownership of the buffer to this object on construction.
|
||||
unsafe fn new(ptr: *mut u8, len: usize) -> Self {
|
||||
Self { ptr, len }
|
||||
}
|
||||
|
||||
pub fn as_mut_slice(&mut self) -> &mut [u8] {
|
||||
// SAFETY: ptr is a pointer to a buffer of length len.
|
||||
unsafe { slice::from_raw_parts_mut(self.ptr, self.len) }
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
// SAFETY: ptr is a pointer to a buffer of length len.
|
||||
unsafe { slice::from_raw_parts(self.ptr, self.len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LocalAllocBuffer {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: when this struct is created, the caller guarantees
|
||||
// ptr is a valid pointer to a buffer that can be freed with LocalFree.
|
||||
unsafe {
|
||||
LocalFree(self.ptr as *mut c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Summary
|
||||
/// Wrapper around CryptProtectData that displays no UI.
|
||||
pub fn crypt_protect_data(plaintext: &mut [u8]) -> Result<LocalAllocBuffer> {
|
||||
let mut plaintext_blob = DATA_BLOB {
|
||||
cbData: plaintext
|
||||
.len()
|
||||
.try_into()
|
||||
.context("plaintext size won't fit in DWORD")?,
|
||||
pbData: plaintext.as_mut_ptr(),
|
||||
};
|
||||
let mut ciphertext_blob = DATA_BLOB {
|
||||
cbData: 0,
|
||||
pbData: ptr::null_mut(),
|
||||
};
|
||||
|
||||
// SAFETY: the FFI call is safe because
|
||||
// 1. plaintext_blob lives longer than the call.
|
||||
// 2. ciphertext_blob lives longer than the call, and we later give
|
||||
// ownership of the memory the kernel allocates to LocalAllocBuffer
|
||||
// which guarantees it is freed.
|
||||
let res = unsafe {
|
||||
CryptProtectData(
|
||||
&mut plaintext_blob as *mut _,
|
||||
/* szDataDescr= */ ptr::null_mut(),
|
||||
/* pOptionalEntropy= */ ptr::null_mut(),
|
||||
/* pvReserved= */ ptr::null_mut(),
|
||||
/* pPromptStruct */ ptr::null_mut(),
|
||||
/* dwFlags */ 0,
|
||||
&mut ciphertext_blob as *mut _,
|
||||
)
|
||||
};
|
||||
if res == 0 {
|
||||
syscall_bail!("CryptProtectData failed");
|
||||
}
|
||||
|
||||
let ciphertext_len: usize = ciphertext_blob
|
||||
.cbData
|
||||
.try_into()
|
||||
.context("resulting ciphertext had an invalid size")?;
|
||||
|
||||
// SAFETY: safe because ciphertext_blob refers to a valid buffer of the specified length. This
|
||||
// is guaranteed because CryptProtectData returned success.
|
||||
Ok(unsafe { LocalAllocBuffer::new(ciphertext_blob.pbData, ciphertext_len) })
|
||||
}
|
||||
|
||||
/// # Summary
|
||||
/// Wrapper around CryptProtectData that displays no UI.
|
||||
pub fn crypt_unprotect_data(ciphertext: &mut [u8]) -> Result<LocalAllocBuffer> {
|
||||
let mut ciphertext_blob = DATA_BLOB {
|
||||
cbData: ciphertext
|
||||
.len()
|
||||
.try_into()
|
||||
.context("plaintext size won't fit in DWORD")?,
|
||||
pbData: ciphertext.as_mut_ptr(),
|
||||
};
|
||||
let mut plaintext_blob = DATA_BLOB {
|
||||
cbData: 0,
|
||||
pbData: ptr::null_mut(),
|
||||
};
|
||||
|
||||
// SAFETY: the FFI call is safe because
|
||||
// 1. ciphertext_blob lives longer than the call.
|
||||
// 2. plaintext_blob lives longer than the call, and we later give
|
||||
// ownership of the memory the kernel allocates to LocalAllocBuffer
|
||||
// which guarantees it is freed.
|
||||
let res = unsafe {
|
||||
CryptUnprotectData(
|
||||
&mut ciphertext_blob as *mut _,
|
||||
/* szDataDescr= */ ptr::null_mut(),
|
||||
/* pOptionalEntropy= */ ptr::null_mut(),
|
||||
/* pvReserved= */ ptr::null_mut(),
|
||||
/* pPromptStruct */ ptr::null_mut(),
|
||||
/* dwFlags */ 0,
|
||||
&mut plaintext_blob as *mut _,
|
||||
)
|
||||
};
|
||||
if res == 0 {
|
||||
syscall_bail!("CryptUnprotectData failed");
|
||||
}
|
||||
|
||||
let plaintext_len: usize = plaintext_blob
|
||||
.cbData
|
||||
.try_into()
|
||||
.context("resulting plaintext had an invalid size")?;
|
||||
|
||||
// SAFETY: safe because plaintext_blob refers to a valid buffer of the specified length. This
|
||||
// is guaranteed because CryptUnprotectData returned success.
|
||||
Ok(unsafe { LocalAllocBuffer::new(plaintext_blob.pbData, plaintext_len) })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn encrypt_empty_string_is_valid() {
|
||||
let plaintext_str = "";
|
||||
let mut plaintext_buffer = Vec::from(plaintext_str.as_bytes());
|
||||
|
||||
let mut ciphertext_buffer = crypt_protect_data(plaintext_buffer.as_mut_slice()).unwrap();
|
||||
let decrypted_plaintext_buffer =
|
||||
crypt_unprotect_data(ciphertext_buffer.as_mut_slice()).unwrap();
|
||||
let decrypted_plaintext_str =
|
||||
std::str::from_utf8(decrypted_plaintext_buffer.as_slice()).unwrap();
|
||||
assert_eq!(plaintext_str, decrypted_plaintext_str);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encrypt_decrypt_plaintext_matches() {
|
||||
let plaintext_str = "test plaintext";
|
||||
let mut plaintext_buffer = Vec::from(plaintext_str.as_bytes());
|
||||
|
||||
let mut ciphertext_buffer = crypt_protect_data(plaintext_buffer.as_mut_slice()).unwrap();
|
||||
|
||||
// If our plaintext & ciphertext are the same, something is very wrong.
|
||||
assert_ne!(plaintext_str.as_bytes(), ciphertext_buffer.as_slice());
|
||||
|
||||
// Decrypt the ciphertext and make sure it's our original plaintext.
|
||||
let decrypted_plaintext_buffer =
|
||||
crypt_unprotect_data(ciphertext_buffer.as_mut_slice()).unwrap();
|
||||
let decrypted_plaintext_str =
|
||||
std::str::from_utf8(decrypted_plaintext_buffer.as_slice()).unwrap();
|
||||
assert_eq!(plaintext_str, decrypted_plaintext_str);
|
||||
}
|
||||
}
|
|
@ -55,6 +55,8 @@ use winapi::um::winnt::WCHAR;
|
|||
|
||||
pub use crate::dll_notification::*;
|
||||
|
||||
pub mod dpapi;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! syscall_bail {
|
||||
($details:expr) => {
|
||||
|
|
Loading…
Reference in a new issue