xref: /aosp_15_r20/external/crosvm/win_util/src/dpapi.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1*bb4ee6a4SAndroid Build Coastguard Worker // Copyright 2024 The ChromiumOS Authors
2*bb4ee6a4SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*bb4ee6a4SAndroid Build Coastguard Worker // found in the LICENSE file.
4*bb4ee6a4SAndroid Build Coastguard Worker 
5*bb4ee6a4SAndroid Build Coastguard Worker #![deny(unsafe_op_in_unsafe_fn)]
6*bb4ee6a4SAndroid Build Coastguard Worker 
7*bb4ee6a4SAndroid Build Coastguard Worker //! Safe, Rusty wrappers around DPAPI.
8*bb4ee6a4SAndroid Build Coastguard Worker 
9*bb4ee6a4SAndroid Build Coastguard Worker use std::ffi::c_void;
10*bb4ee6a4SAndroid Build Coastguard Worker use std::ptr;
11*bb4ee6a4SAndroid Build Coastguard Worker use std::slice;
12*bb4ee6a4SAndroid Build Coastguard Worker 
13*bb4ee6a4SAndroid Build Coastguard Worker use anyhow::Context;
14*bb4ee6a4SAndroid Build Coastguard Worker use anyhow::Result;
15*bb4ee6a4SAndroid Build Coastguard Worker use winapi::um::dpapi::CryptProtectData;
16*bb4ee6a4SAndroid Build Coastguard Worker use winapi::um::dpapi::CryptUnprotectData;
17*bb4ee6a4SAndroid Build Coastguard Worker use winapi::um::winbase::LocalFree;
18*bb4ee6a4SAndroid Build Coastguard Worker use winapi::um::wincrypt::DATA_BLOB;
19*bb4ee6a4SAndroid Build Coastguard Worker use zeroize::Zeroize;
20*bb4ee6a4SAndroid Build Coastguard Worker 
21*bb4ee6a4SAndroid Build Coastguard Worker use crate::syscall_bail;
22*bb4ee6a4SAndroid Build Coastguard Worker 
23*bb4ee6a4SAndroid Build Coastguard Worker /// Wrapper around buffers allocated by DPAPI that can be freed with LocalFree.
24*bb4ee6a4SAndroid Build Coastguard Worker pub struct LocalAllocBuffer {
25*bb4ee6a4SAndroid Build Coastguard Worker     ptr: *mut u8,
26*bb4ee6a4SAndroid Build Coastguard Worker     len: usize,
27*bb4ee6a4SAndroid Build Coastguard Worker }
28*bb4ee6a4SAndroid Build Coastguard Worker 
29*bb4ee6a4SAndroid Build Coastguard Worker impl LocalAllocBuffer {
30*bb4ee6a4SAndroid Build Coastguard Worker     /// # Safety
31*bb4ee6a4SAndroid Build Coastguard Worker     /// 0. ptr is a valid buffer of length len and is safe to free with LocalFree.
32*bb4ee6a4SAndroid Build Coastguard Worker     /// 1. The caller transfers ownership of the buffer to this object on construction.
new(ptr: *mut u8, len: usize) -> Self33*bb4ee6a4SAndroid Build Coastguard Worker     unsafe fn new(ptr: *mut u8, len: usize) -> Self {
34*bb4ee6a4SAndroid Build Coastguard Worker         Self { ptr, len }
35*bb4ee6a4SAndroid Build Coastguard Worker     }
36*bb4ee6a4SAndroid Build Coastguard Worker 
as_mut_slice(&mut self) -> &mut [u8]37*bb4ee6a4SAndroid Build Coastguard Worker     pub fn as_mut_slice(&mut self) -> &mut [u8] {
38*bb4ee6a4SAndroid Build Coastguard Worker         // SAFETY: ptr is a pointer to a buffer of length len.
39*bb4ee6a4SAndroid Build Coastguard Worker         unsafe { slice::from_raw_parts_mut(self.ptr, self.len) }
40*bb4ee6a4SAndroid Build Coastguard Worker     }
41*bb4ee6a4SAndroid Build Coastguard Worker 
as_slice(&self) -> &[u8]42*bb4ee6a4SAndroid Build Coastguard Worker     pub fn as_slice(&self) -> &[u8] {
43*bb4ee6a4SAndroid Build Coastguard Worker         // SAFETY: ptr is a pointer to a buffer of length len.
44*bb4ee6a4SAndroid Build Coastguard Worker         unsafe { slice::from_raw_parts(self.ptr, self.len) }
45*bb4ee6a4SAndroid Build Coastguard Worker     }
46*bb4ee6a4SAndroid Build Coastguard Worker }
47*bb4ee6a4SAndroid Build Coastguard Worker 
48*bb4ee6a4SAndroid Build Coastguard Worker impl Drop for LocalAllocBuffer {
drop(&mut self)49*bb4ee6a4SAndroid Build Coastguard Worker     fn drop(&mut self) {
50*bb4ee6a4SAndroid Build Coastguard Worker         // This buffer likely contains cryptographic key material. Zero it.
51*bb4ee6a4SAndroid Build Coastguard Worker         self.as_mut_slice().zeroize();
52*bb4ee6a4SAndroid Build Coastguard Worker 
53*bb4ee6a4SAndroid Build Coastguard Worker         // SAFETY: when this struct is created, the caller guarantees
54*bb4ee6a4SAndroid Build Coastguard Worker         // ptr is a valid pointer to a buffer that can be freed with LocalFree.
55*bb4ee6a4SAndroid Build Coastguard Worker         unsafe {
56*bb4ee6a4SAndroid Build Coastguard Worker             LocalFree(self.ptr as *mut c_void);
57*bb4ee6a4SAndroid Build Coastguard Worker         }
58*bb4ee6a4SAndroid Build Coastguard Worker     }
59*bb4ee6a4SAndroid Build Coastguard Worker }
60*bb4ee6a4SAndroid Build Coastguard Worker 
61*bb4ee6a4SAndroid Build Coastguard Worker /// # Summary
62*bb4ee6a4SAndroid Build Coastguard Worker /// Wrapper around CryptProtectData that displays no UI.
crypt_protect_data(plaintext: &mut [u8]) -> Result<LocalAllocBuffer>63*bb4ee6a4SAndroid Build Coastguard Worker pub fn crypt_protect_data(plaintext: &mut [u8]) -> Result<LocalAllocBuffer> {
64*bb4ee6a4SAndroid Build Coastguard Worker     let mut plaintext_blob = DATA_BLOB {
65*bb4ee6a4SAndroid Build Coastguard Worker         cbData: plaintext
66*bb4ee6a4SAndroid Build Coastguard Worker             .len()
67*bb4ee6a4SAndroid Build Coastguard Worker             .try_into()
68*bb4ee6a4SAndroid Build Coastguard Worker             .context("plaintext size won't fit in DWORD")?,
69*bb4ee6a4SAndroid Build Coastguard Worker         pbData: plaintext.as_mut_ptr(),
70*bb4ee6a4SAndroid Build Coastguard Worker     };
71*bb4ee6a4SAndroid Build Coastguard Worker     let mut ciphertext_blob = DATA_BLOB {
72*bb4ee6a4SAndroid Build Coastguard Worker         cbData: 0,
73*bb4ee6a4SAndroid Build Coastguard Worker         pbData: ptr::null_mut(),
74*bb4ee6a4SAndroid Build Coastguard Worker     };
75*bb4ee6a4SAndroid Build Coastguard Worker 
76*bb4ee6a4SAndroid Build Coastguard Worker     // SAFETY: the FFI call is safe because
77*bb4ee6a4SAndroid Build Coastguard Worker     // 1. plaintext_blob lives longer than the call.
78*bb4ee6a4SAndroid Build Coastguard Worker     // 2. ciphertext_blob lives longer than the call, and we later give ownership of the memory the
79*bb4ee6a4SAndroid Build Coastguard Worker     //    kernel allocates to LocalAllocBuffer which guarantees it is freed.
80*bb4ee6a4SAndroid Build Coastguard Worker     let res = unsafe {
81*bb4ee6a4SAndroid Build Coastguard Worker         CryptProtectData(
82*bb4ee6a4SAndroid Build Coastguard Worker             &mut plaintext_blob as *mut _,
83*bb4ee6a4SAndroid Build Coastguard Worker             /* szDataDescr= */ ptr::null_mut(),
84*bb4ee6a4SAndroid Build Coastguard Worker             /* pOptionalEntropy= */ ptr::null_mut(),
85*bb4ee6a4SAndroid Build Coastguard Worker             /* pvReserved= */ ptr::null_mut(),
86*bb4ee6a4SAndroid Build Coastguard Worker             /* pPromptStruct */ ptr::null_mut(),
87*bb4ee6a4SAndroid Build Coastguard Worker             /* dwFlags */ 0,
88*bb4ee6a4SAndroid Build Coastguard Worker             &mut ciphertext_blob as *mut _,
89*bb4ee6a4SAndroid Build Coastguard Worker         )
90*bb4ee6a4SAndroid Build Coastguard Worker     };
91*bb4ee6a4SAndroid Build Coastguard Worker     if res == 0 {
92*bb4ee6a4SAndroid Build Coastguard Worker         syscall_bail!("CryptProtectData failed");
93*bb4ee6a4SAndroid Build Coastguard Worker     }
94*bb4ee6a4SAndroid Build Coastguard Worker 
95*bb4ee6a4SAndroid Build Coastguard Worker     let ciphertext_len: usize = ciphertext_blob
96*bb4ee6a4SAndroid Build Coastguard Worker         .cbData
97*bb4ee6a4SAndroid Build Coastguard Worker         .try_into()
98*bb4ee6a4SAndroid Build Coastguard Worker         .context("resulting ciphertext had an invalid size")?;
99*bb4ee6a4SAndroid Build Coastguard Worker 
100*bb4ee6a4SAndroid Build Coastguard Worker     // SAFETY: safe because ciphertext_blob refers to a valid buffer of the specified length. This
101*bb4ee6a4SAndroid Build Coastguard Worker     // is guaranteed because CryptProtectData returned success.
102*bb4ee6a4SAndroid Build Coastguard Worker     Ok(unsafe { LocalAllocBuffer::new(ciphertext_blob.pbData, ciphertext_len) })
103*bb4ee6a4SAndroid Build Coastguard Worker }
104*bb4ee6a4SAndroid Build Coastguard Worker 
105*bb4ee6a4SAndroid Build Coastguard Worker /// # Summary
106*bb4ee6a4SAndroid Build Coastguard Worker /// Wrapper around CryptProtectData that displays no UI.
crypt_unprotect_data(ciphertext: &mut [u8]) -> Result<LocalAllocBuffer>107*bb4ee6a4SAndroid Build Coastguard Worker pub fn crypt_unprotect_data(ciphertext: &mut [u8]) -> Result<LocalAllocBuffer> {
108*bb4ee6a4SAndroid Build Coastguard Worker     let mut ciphertext_blob = DATA_BLOB {
109*bb4ee6a4SAndroid Build Coastguard Worker         cbData: ciphertext
110*bb4ee6a4SAndroid Build Coastguard Worker             .len()
111*bb4ee6a4SAndroid Build Coastguard Worker             .try_into()
112*bb4ee6a4SAndroid Build Coastguard Worker             .context("plaintext size won't fit in DWORD")?,
113*bb4ee6a4SAndroid Build Coastguard Worker         pbData: ciphertext.as_mut_ptr(),
114*bb4ee6a4SAndroid Build Coastguard Worker     };
115*bb4ee6a4SAndroid Build Coastguard Worker     let mut plaintext_blob = DATA_BLOB {
116*bb4ee6a4SAndroid Build Coastguard Worker         cbData: 0,
117*bb4ee6a4SAndroid Build Coastguard Worker         pbData: ptr::null_mut(),
118*bb4ee6a4SAndroid Build Coastguard Worker     };
119*bb4ee6a4SAndroid Build Coastguard Worker 
120*bb4ee6a4SAndroid Build Coastguard Worker     // SAFETY: the FFI call is safe because
121*bb4ee6a4SAndroid Build Coastguard Worker     // 1. ciphertext_blob lives longer than the call.
122*bb4ee6a4SAndroid Build Coastguard Worker     // 2. plaintext_blob lives longer than the call, and we later give ownership of the memory the
123*bb4ee6a4SAndroid Build Coastguard Worker     //    kernel allocates to LocalAllocBuffer which guarantees it is freed.
124*bb4ee6a4SAndroid Build Coastguard Worker     let res = unsafe {
125*bb4ee6a4SAndroid Build Coastguard Worker         CryptUnprotectData(
126*bb4ee6a4SAndroid Build Coastguard Worker             &mut ciphertext_blob as *mut _,
127*bb4ee6a4SAndroid Build Coastguard Worker             /* szDataDescr= */ ptr::null_mut(),
128*bb4ee6a4SAndroid Build Coastguard Worker             /* pOptionalEntropy= */ ptr::null_mut(),
129*bb4ee6a4SAndroid Build Coastguard Worker             /* pvReserved= */ ptr::null_mut(),
130*bb4ee6a4SAndroid Build Coastguard Worker             /* pPromptStruct */ ptr::null_mut(),
131*bb4ee6a4SAndroid Build Coastguard Worker             /* dwFlags */ 0,
132*bb4ee6a4SAndroid Build Coastguard Worker             &mut plaintext_blob as *mut _,
133*bb4ee6a4SAndroid Build Coastguard Worker         )
134*bb4ee6a4SAndroid Build Coastguard Worker     };
135*bb4ee6a4SAndroid Build Coastguard Worker     if res == 0 {
136*bb4ee6a4SAndroid Build Coastguard Worker         syscall_bail!("CryptUnprotectData failed");
137*bb4ee6a4SAndroid Build Coastguard Worker     }
138*bb4ee6a4SAndroid Build Coastguard Worker 
139*bb4ee6a4SAndroid Build Coastguard Worker     let plaintext_len: usize = plaintext_blob
140*bb4ee6a4SAndroid Build Coastguard Worker         .cbData
141*bb4ee6a4SAndroid Build Coastguard Worker         .try_into()
142*bb4ee6a4SAndroid Build Coastguard Worker         .context("resulting plaintext had an invalid size")?;
143*bb4ee6a4SAndroid Build Coastguard Worker 
144*bb4ee6a4SAndroid Build Coastguard Worker     // SAFETY: safe because plaintext_blob refers to a valid buffer of the specified length. This
145*bb4ee6a4SAndroid Build Coastguard Worker     // is guaranteed because CryptUnprotectData returned success.
146*bb4ee6a4SAndroid Build Coastguard Worker     Ok(unsafe { LocalAllocBuffer::new(plaintext_blob.pbData, plaintext_len) })
147*bb4ee6a4SAndroid Build Coastguard Worker }
148*bb4ee6a4SAndroid Build Coastguard Worker 
149*bb4ee6a4SAndroid Build Coastguard Worker #[cfg(test)]
150*bb4ee6a4SAndroid Build Coastguard Worker mod tests {
151*bb4ee6a4SAndroid Build Coastguard Worker     use super::*;
152*bb4ee6a4SAndroid Build Coastguard Worker 
153*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
encrypt_empty_string_is_valid()154*bb4ee6a4SAndroid Build Coastguard Worker     fn encrypt_empty_string_is_valid() {
155*bb4ee6a4SAndroid Build Coastguard Worker         let plaintext_str = "";
156*bb4ee6a4SAndroid Build Coastguard Worker         let mut plaintext_buffer = Vec::from(plaintext_str.as_bytes());
157*bb4ee6a4SAndroid Build Coastguard Worker 
158*bb4ee6a4SAndroid Build Coastguard Worker         let mut ciphertext_buffer = crypt_protect_data(plaintext_buffer.as_mut_slice()).unwrap();
159*bb4ee6a4SAndroid Build Coastguard Worker         let decrypted_plaintext_buffer =
160*bb4ee6a4SAndroid Build Coastguard Worker             crypt_unprotect_data(ciphertext_buffer.as_mut_slice()).unwrap();
161*bb4ee6a4SAndroid Build Coastguard Worker         let decrypted_plaintext_str =
162*bb4ee6a4SAndroid Build Coastguard Worker             std::str::from_utf8(decrypted_plaintext_buffer.as_slice()).unwrap();
163*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(plaintext_str, decrypted_plaintext_str);
164*bb4ee6a4SAndroid Build Coastguard Worker     }
165*bb4ee6a4SAndroid Build Coastguard Worker 
166*bb4ee6a4SAndroid Build Coastguard Worker     #[test]
encrypt_decrypt_plaintext_matches()167*bb4ee6a4SAndroid Build Coastguard Worker     fn encrypt_decrypt_plaintext_matches() {
168*bb4ee6a4SAndroid Build Coastguard Worker         let plaintext_str = "test plaintext";
169*bb4ee6a4SAndroid Build Coastguard Worker         let mut plaintext_buffer = Vec::from(plaintext_str.as_bytes());
170*bb4ee6a4SAndroid Build Coastguard Worker 
171*bb4ee6a4SAndroid Build Coastguard Worker         let mut ciphertext_buffer = crypt_protect_data(plaintext_buffer.as_mut_slice()).unwrap();
172*bb4ee6a4SAndroid Build Coastguard Worker 
173*bb4ee6a4SAndroid Build Coastguard Worker         // If our plaintext & ciphertext are the same, something is very wrong.
174*bb4ee6a4SAndroid Build Coastguard Worker         assert_ne!(plaintext_str.as_bytes(), ciphertext_buffer.as_slice());
175*bb4ee6a4SAndroid Build Coastguard Worker 
176*bb4ee6a4SAndroid Build Coastguard Worker         // Decrypt the ciphertext and make sure it's our original plaintext.
177*bb4ee6a4SAndroid Build Coastguard Worker         let decrypted_plaintext_buffer =
178*bb4ee6a4SAndroid Build Coastguard Worker             crypt_unprotect_data(ciphertext_buffer.as_mut_slice()).unwrap();
179*bb4ee6a4SAndroid Build Coastguard Worker         let decrypted_plaintext_str =
180*bb4ee6a4SAndroid Build Coastguard Worker             std::str::from_utf8(decrypted_plaintext_buffer.as_slice()).unwrap();
181*bb4ee6a4SAndroid Build Coastguard Worker         assert_eq!(plaintext_str, decrypted_plaintext_str);
182*bb4ee6a4SAndroid Build Coastguard Worker     }
183*bb4ee6a4SAndroid Build Coastguard Worker }
184