From 550514890c30372217e14631e403eb61285f51e6 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Fri, 16 Jan 2026 16:18:30 -0800 Subject: [PATCH 01/13] Rust UB in progress --- rust/sdk/Cargo.toml | 4 +- rust/sdk/crates/libs/sdk-enclave/Cargo.toml | 9 +- rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 3 + .../sdk-enclave/src/userboundkey/crypto.rs | 314 +++++++++ .../libs/sdk-enclave/src/userboundkey/mod.rs | 625 ++++++++++++++++++ .../sdk-enclave/src/userboundkey/types.rs | 29 + .../sdk-enclave/src/userboundkey/utils.rs | 108 +++ rust/sdk/crates/libs/sdk-host/Cargo.toml | 13 +- rust/sdk/crates/libs/sdk-host/src/lib.rs | 3 + .../libs/sdk-host/src/userboundkey/mod.rs | 461 +++++++++++++ .../libs/sdk-host/src/userboundkey/types.rs | 4 + .../crates/libs/userboundkey-kcm/src/lib.rs | 58 ++ rust/sdk/crates/libs/userboundkey.edl | 99 +++ .../src/manual_bindings/vertdll.rs | 10 + rust/sdk/generate_codegen_for_workspace.ps1 | 9 + 15 files changed, 1742 insertions(+), 7 deletions(-) create mode 100644 rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs create mode 100644 rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs create mode 100644 rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs create mode 100644 rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs create mode 100644 rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs create mode 100644 rust/sdk/crates/libs/sdk-host/src/userboundkey/types.rs create mode 100644 rust/sdk/crates/libs/userboundkey.edl diff --git a/rust/sdk/Cargo.toml b/rust/sdk/Cargo.toml index 953b7b76..0b957577 100644 --- a/rust/sdk/Cargo.toml +++ b/rust/sdk/Cargo.toml @@ -3,8 +3,8 @@ resolver = "3" members = [ "crates/libs/sdk-enclave", "crates/libs/sdk-host", - "crates/libs/sdk-enclave/generated/veil_abi_gen", - "crates/libs/sdk-host/generated/veil_abi_gen", + "crates/libs/sdk-enclave/generated/userboundkey_gen", + "crates/libs/sdk-host/generated/userboundkey_gen", "crates/libs/userboundkey-kcm", "crates/tests/sdk", "crates/libs/windows-enclave", diff --git a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml index d06cb53e..c1388651 100644 --- a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml @@ -3,8 +3,11 @@ name = "vbsenclave-sdk-enclave" version = "0.1.0" edition = { workspace = true } +[features] +default = [] +userboundkey = [] + [dependencies] spin = { workspace = true } -veil_abi_enclave_gen = { path = "./generated/veil_abi_gen" } -windows-enclave = {workspace = true } - +userboundkey_enclave_gen = { path = "./generated/userboundkey_gen" } +windows-enclave = { workspace = true } diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index 5aa5786a..c4ad4c07 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -5,3 +5,6 @@ extern crate alloc; mod enclave_ffi; + +#[cfg(feature = "userboundkey")] +pub mod userboundkey; diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs new file mode 100644 index 00000000..f7f03c19 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs @@ -0,0 +1,314 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Cryptographic utilities for VTL1 enclave operations +//! +//! This module provides crypto primitives used by user-bound key operations, +//! including random number generation and enclave sealing. + +use alloc::vec::Vec; + +use windows_enclave::bcrypt::{BCryptGenRandom, BCRYPT_USE_SYSTEM_PREFERRED_RNG}; +use windows_enclave::vertdll::{ + EnclaveSealData, EnclaveUnsealData, GetProcessHeap, HEAP_ZERO_MEMORY, HeapAlloc, HeapFree, + ENCLAVE_UNSEAL_FLAG_STALE_KEY, +}; + +use super::types::UserBoundKeyError; + +// +// Constants +// + +/// Size of symmetric key in bytes (AES-256-GCM) +pub const SYMMETRIC_KEY_SIZE_BYTES: usize = 32; + +/// Size of DH key in bits (ECDH P-384) +#[allow(dead_code)] +pub const DH_KEY_SIZE_BITS: usize = 384; + +/// Size of signature key in bits (ECDSA P-384) +#[allow(dead_code)] +pub const SIGNATURE_KEY_SIZE_BITS: usize = 384; + +/// Size of nonce in bytes +#[allow(dead_code)] +pub const NONCE_SIZE: usize = 12; + +/// Size of authentication tag in bytes +#[allow(dead_code)] +pub const TAG_SIZE: usize = 16; + +// +// Types +// + +/// Fixed-size symmetric key bytes +pub type SymmetricKeyBytes = [u8; SYMMETRIC_KEY_SIZE_BYTES]; + +/// Enclave sealing identity policy +#[repr(i32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum EnclaveSealingIdentityPolicy { + /// Seal to the exact enclave image + SealToExactCode = 1, + /// Seal to the enclave signer + SealToSigner = 2, +} + +// +// RAII Helpers +// + +/// RAII wrapper for heap-allocated memory +pub struct HeapBuffer { + ptr: *mut core::ffi::c_void, + size: usize, +} + +impl HeapBuffer { + /// Allocate a new heap buffer of the specified size + pub fn new(size: usize) -> Option { + unsafe { + let heap = GetProcessHeap(); + let ptr = HeapAlloc(heap, HEAP_ZERO_MEMORY, size); + if ptr.is_null() { + None + } else { + Some(Self { ptr, size }) + } + } + } + + /// Get a const pointer to the buffer + #[allow(dead_code)] + pub fn as_ptr(&self) -> *const core::ffi::c_void { + self.ptr + } + + /// Get a mutable pointer to the buffer + pub fn as_mut_ptr(&mut self) -> *mut core::ffi::c_void { + self.ptr + } + + /// Get the buffer contents as a slice + pub fn as_slice(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.ptr as *const u8, self.size) } + } + + /// Copy the buffer contents to a Vec + pub fn to_vec(&self) -> Vec { + self.as_slice().to_vec() + } +} + +impl Drop for HeapBuffer { + fn drop(&mut self) { + if !self.ptr.is_null() { + unsafe { + let heap = GetProcessHeap(); + HeapFree(heap, 0, self.ptr); + } + } + } +} + +// +// Helper Functions +// + +/// Convert HRESULT to Result +pub fn check_hr(hr: i32) -> Result<(), UserBoundKeyError> { + if hr >= 0 { + Ok(()) + } else { + Err(UserBoundKeyError::Hresult(hr)) + } +} + +// +// Random Number Generation +// + +/// Convert NTSTATUS to Result +fn check_ntstatus(status: i32) -> Result<(), UserBoundKeyError> { + if status >= 0 { + Ok(()) + } else { + // Convert NTSTATUS to HRESULT-like error + // NTSTATUS errors have the high bit set; HRESULT uses 0x8xxxxxxx format + Err(UserBoundKeyError::Hresult(status)) + } +} + +/// Generate random bytes using BCryptGenRandom +/// +/// Uses the system preferred RNG to generate cryptographically secure random bytes. +pub fn generate_random() -> Result<[u8; N], UserBoundKeyError> { + let mut buffer = [0u8; N]; + generate_random_bytes(&mut buffer)?; + Ok(buffer) +} + +/// Generate random bytes into a mutable slice +#[allow(dead_code)] +pub fn generate_random_bytes(buffer: &mut [u8]) -> Result<(), UserBoundKeyError> { + unsafe { + // BCryptGenRandom with BCRYPT_USE_SYSTEM_PREFERRED_RNG uses the system RNG + // and doesn't require an algorithm handle (pass null) + let status = BCryptGenRandom( + core::ptr::null_mut(), + buffer.as_mut_ptr(), + buffer.len() as u32, + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ); + check_ntstatus(status) + } +} + +/// Generate a symmetric key (32 bytes of random data) +pub fn generate_symmetric_key_bytes() -> Result { + generate_random::() +} + +// +// Enclave Sealing +// + +/// Seal data using enclave sealing +/// +/// Encrypts and binds data to the enclave identity according to the specified policy. +/// +/// # Arguments +/// * `data` - The plaintext data to seal +/// * `sealing_policy` - The identity policy for sealing +/// * `runtime_policy` - Runtime policy flags +/// +/// # Returns +/// The sealed (encrypted) data blob +pub fn seal_data( + data: &[u8], + sealing_policy: EnclaveSealingIdentityPolicy, + runtime_policy: u32, +) -> Result, UserBoundKeyError> { + unsafe { + // First call to get required size + let mut sealed_size: u32 = 0; + let hr = EnclaveSealData( + data.as_ptr() as *const core::ffi::c_void, + data.len() as u32, + sealing_policy as i32, + runtime_policy, + core::ptr::null_mut(), + 0, + &mut sealed_size, + ); + check_hr(hr)?; + + // Allocate buffer and seal + let mut sealed_buffer = + HeapBuffer::new(sealed_size as usize).ok_or(UserBoundKeyError::AllocationFailed)?; + + let hr = EnclaveSealData( + data.as_ptr() as *const core::ffi::c_void, + data.len() as u32, + sealing_policy as i32, + runtime_policy, + sealed_buffer.as_mut_ptr(), + sealed_size, + &mut sealed_size, + ); + check_hr(hr)?; + + Ok(sealed_buffer.to_vec()) + } +} + +/// Unseal data using enclave sealing +/// +/// Decrypts data that was previously sealed to this enclave. +/// +/// # Arguments +/// * `sealed_data` - The sealed data blob +/// +/// # Returns +/// A tuple of (unsealed_data, unseal_flags). Check unseal_flags for ENCLAVE_UNSEAL_FLAG_STALE_KEY +/// to determine if the sealing key has changed and the data should be resealed. +pub fn unseal_data(sealed_data: &[u8]) -> Result<(Vec, u32), UserBoundKeyError> { + unsafe { + // First call to get required size + let mut unsealed_size: u32 = 0; + let hr = EnclaveUnsealData( + sealed_data.as_ptr() as *const core::ffi::c_void, + sealed_data.len() as u32, + core::ptr::null_mut(), + 0, + &mut unsealed_size, + core::ptr::null_mut(), + core::ptr::null_mut(), + ); + check_hr(hr)?; + + // Allocate buffer and unseal + let mut unsealed_buffer = + HeapBuffer::new(unsealed_size as usize).ok_or(UserBoundKeyError::AllocationFailed)?; + let mut unseal_flags: u32 = 0; + + let hr = EnclaveUnsealData( + sealed_data.as_ptr() as *const core::ffi::c_void, + sealed_data.len() as u32, + unsealed_buffer.as_mut_ptr(), + unsealed_size, + &mut unsealed_size, + core::ptr::null_mut(), + &mut unseal_flags, + ); + check_hr(hr)?; + + Ok((unsealed_buffer.to_vec(), unseal_flags)) + } +} + +/// Check if unseal flags indicate a stale key +/// +/// When the sealing key has rotated, unsealing still succeeds but sets this flag +/// to indicate the data should be resealed with the new key. +#[inline] +pub fn is_stale_key(unseal_flags: u32) -> bool { + (unseal_flags & ENCLAVE_UNSEAL_FLAG_STALE_KEY) != 0 +} + +// +// Nonce utilities +// + +/// Create a nonce buffer from a numeric value +/// +/// Creates a 12-byte nonce with the value placed at the end (big-endian position). +#[allow(dead_code)] +pub fn make_nonce_from_number(nonce: u64) -> [u8; NONCE_SIZE] { + let mut buffer = [0u8; NONCE_SIZE]; + // Place nonce value at the end of the buffer + let nonce_bytes = nonce.to_le_bytes(); + buffer[NONCE_SIZE - 8..].copy_from_slice(&nonce_bytes); + buffer +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_make_nonce_from_number() { + let nonce = make_nonce_from_number(0x0102030405060708); + assert_eq!(nonce[0..4], [0, 0, 0, 0]); + assert_eq!(nonce[4..12], [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); + } + + #[test] + fn test_is_stale_key() { + assert!(!is_stale_key(0)); + assert!(is_stale_key(ENCLAVE_UNSEAL_FLAG_STALE_KEY)); + assert!(is_stale_key(ENCLAVE_UNSEAL_FLAG_STALE_KEY | 0x10)); + } +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs new file mode 100644 index 00000000..750a1433 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs @@ -0,0 +1,625 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! VTL1 User-Bound Key Implementation +//! +//! Implements enclave-side user-bound key operations using VBS enclave sealing +//! and the veinterop.dll APIs for KCM (Key Credential Manager) interactions. + +mod crypto; +mod types; +mod utils; + +pub use crypto::{ + EnclaveSealingIdentityPolicy, SYMMETRIC_KEY_SIZE_BYTES, +}; +pub use types::*; + +use alloc::vec::Vec; + +use userboundkey_enclave_gen::implementation::trusted::Trusted; +use userboundkey_enclave_gen::implementation::types::{ + attestationReportAndSessionInfo, keyCredentialCacheConfig, +}; +use userboundkey_enclave_gen::stubs::untrusted; + +use windows_enclave::veinterop::{ + CloseUserBoundKeyAuthContext, CloseUserBoundKeySession, + CreateUserBoundKeyRequestForDeriveSharedSecret, + CreateUserBoundKeyRequestForRetrieveAuthorizationContext, GetUserBoundKeyAuthContext, + InitializeUserBoundKeySession, ProtectUserBoundKey, USER_BOUND_KEY_AUTH_CONTEXT_HANDLE, + USER_BOUND_KEY_AUTH_CONTEXT_PROPERTY, USER_BOUND_KEY_AUTH_CONTEXT_PROPERTY_NAME, + USER_BOUND_KEY_SESSION_HANDLE, UnprotectUserBoundKey, ValidateUserBoundKeyAuthContext, +}; +use windows_enclave::vertdll::{GetProcessHeap, HeapFree}; + +use crypto::{check_hr, generate_symmetric_key_bytes, is_stale_key, seal_data, unseal_data}; +use utils::get_enclave_base_address_u64; + +// BCrypt algorithm pseudo-handle value from Windows SDK bcrypt.h. +// BCRYPT_ECDH_P384_ALG_HANDLE = ((BCRYPT_ALG_HANDLE) 0x000002b1) +// Stored as u64 for EDL interface compatibility. +const BCRYPT_ECDH_P384_ALG_HANDLE_U64: u64 = 0x000002b1; + +/// RAII wrapper for session handle +struct UniqueSessionHandle(USER_BOUND_KEY_SESSION_HANDLE); + +impl UniqueSessionHandle { + fn new(handle: USER_BOUND_KEY_SESSION_HANDLE) -> Self { + Self(handle) + } + + fn get(&self) -> USER_BOUND_KEY_SESSION_HANDLE { + self.0 + } + + #[allow(dead_code)] + fn release(&mut self) -> USER_BOUND_KEY_SESSION_HANDLE { + let handle = self.0; + self.0 = core::ptr::null_mut(); + handle + } +} + +impl Drop for UniqueSessionHandle { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + let _ = CloseUserBoundKeySession(self.0); + } + } + } +} + +/// RAII wrapper for auth context handle +struct UniqueAuthContextHandle(USER_BOUND_KEY_AUTH_CONTEXT_HANDLE); + +impl UniqueAuthContextHandle { + fn get(&self) -> USER_BOUND_KEY_AUTH_CONTEXT_HANDLE { + self.0 + } +} + +impl Drop for UniqueAuthContextHandle { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + let _ = CloseUserBoundKeyAuthContext(self.0); + } + } + } +} + +/// RAII wrapper for credential (calls VTL0 to delete) +struct UniqueCredential(u64); + +impl UniqueCredential { + fn new(credential: u64) -> Self { + Self(credential) + } + + fn get(&self) -> u64 { + self.0 + } + + #[allow(dead_code)] + fn release(&mut self) -> u64 { + let credential = self.0; + self.0 = 0; + credential + } +} + +impl Drop for UniqueCredential { + fn drop(&mut self) { + if self.0 != 0 { + let _ = untrusted::userboundkey_delete_credential(self.0); + } + } +} + +/// Get ephemeral public key bytes from bound key structure +/// +/// The bound key structure format: +/// - [4 bytes] enclave public key blob size +/// - [N bytes] enclave public key blob (ephemeral public key) +fn get_ephemeral_public_key_bytes_from_bound_key( + bound_key_bytes: &[u8], +) -> Result, UserBoundKeyError> { + if bound_key_bytes.len() < 4 { + return Err(UserBoundKeyError::InvalidData("Bound key too small")); + } + + let public_key_size = u32::from_le_bytes([ + bound_key_bytes[0], + bound_key_bytes[1], + bound_key_bytes[2], + bound_key_bytes[3], + ]) as usize; + + let remaining = bound_key_bytes.len() - 4; + if remaining < public_key_size || public_key_size == 0 { + return Err(UserBoundKeyError::InvalidData("Invalid public key size")); + } + + Ok(bound_key_bytes[4..4 + public_key_size].to_vec()) +} + +/// Validate that formatted key name ends with expected suffix +fn validate_formatted_key_name( + formatted_key_name: &str, + original_key_name: &str, +) -> Result<(), UserBoundKeyError> { + let expected_suffix = alloc::format!("//{}", original_key_name); + if !formatted_key_name.ends_with(&expected_suffix) { + return Err(UserBoundKeyError::SecurityViolation( + "Formatted key name does not match expected pattern", + )); + } + Ok(()) +} + +/// Create a new user-bound key +/// +/// This function: +/// 1. Generates a symmetric key +/// 2. Creates a Windows Hello credential with VBS attestation +/// 3. Protects the key using the KCM auth context +/// 4. Seals the protected key to the enclave +/// +/// # Arguments +/// * `key_name` - Name for the Windows Hello credential +/// * `cache_config` - Cache configuration for the credential +/// * `message` - Message to display during Windows Hello prompt +/// * `window_id` - Window ID for the prompt +/// * `sealing_policy` - Enclave sealing identity policy +/// * `runtime_policy` - Runtime policy flags +/// * `key_credential_creation_option` - Credential creation options +/// +/// # Returns +/// Sealed key material that can be stored and later loaded +pub fn create_user_bound_key( + key_name: &str, + cache_config: &keyCredentialCacheConfig, + message: &str, + window_id: u64, + sealing_policy: EnclaveSealingIdentityPolicy, + runtime_policy: u32, + key_credential_creation_option: u32, +) -> Result, UserBoundKeyError> { + // Generate symmetric key + let user_key_bytes = generate_symmetric_key_bytes()?; + + create_user_bound_key_with_custom_key( + key_name, + cache_config, + message, + window_id, + sealing_policy, + runtime_policy, + key_credential_creation_option, + &user_key_bytes, + ) +} + +/// Create a user-bound key with custom key bytes +pub fn create_user_bound_key_with_custom_key( + key_name: &str, + cache_config: &keyCredentialCacheConfig, + message: &str, + window_id: u64, + sealing_policy: EnclaveSealingIdentityPolicy, + runtime_policy: u32, + key_credential_creation_option: u32, + custom_key_bytes: &[u8], +) -> Result, UserBoundKeyError> { + // Format the key name (calls VTL0) + let formatted_key_name = untrusted::userboundkey_format_key_name(&key_name.into()) + .map_err(|e| UserBoundKeyError::AbiError(e))?; + + // Security validation + validate_formatted_key_name(&formatted_key_name, key_name)?; + + // Get enclave base address + let enclave_ptr = get_enclave_base_address_u64()?; + + // Establish session (calls VTL0 which triggers Windows Hello) + let credential_and_session = untrusted::userboundkey_establish_session_for_create( + enclave_ptr, + &key_name.into(), + BCRYPT_ECDH_P384_ALG_HANDLE_U64, + &message.into(), + window_id, + cache_config, + key_credential_creation_option, + ) + .map_err(|e| UserBoundKeyError::AbiError(e))?; + + // RAII wrappers for cleanup + let credential = UniqueCredential::new(credential_and_session.credential); + let session_handle = UniqueSessionHandle::new( + credential_and_session.sessionInfo as USER_BOUND_KEY_SESSION_HANDLE, + ); + + // Create encrypted KCM request for RetrieveAuthorizationContext + let mut encrypted_request_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut encrypted_request_size: u32 = 0; + let mut local_nonce: u64 = 0; + + // Convert formatted key name to wide string + let formatted_key_name_wide: Vec = formatted_key_name + .encode_utf16() + .chain(core::iter::once(0)) + .collect(); + + unsafe { + let hr = CreateUserBoundKeyRequestForRetrieveAuthorizationContext( + session_handle.get(), + formatted_key_name_wide.as_ptr(), + &mut local_nonce, + &mut encrypted_request_ptr, + &mut encrypted_request_size, + ); + check_hr(hr)?; + } + + // Convert to Vec + let encrypted_request = unsafe { + core::slice::from_raw_parts( + encrypted_request_ptr as *const u8, + encrypted_request_size as usize, + ) + .to_vec() + }; + + // Free the allocated memory + unsafe { + let heap = GetProcessHeap(); + HeapFree(heap, 0, encrypted_request_ptr); + } + + // Get authorization context from credential (calls VTL0) + let auth_context_blob = untrusted::userboundkey_get_authorization_context_from_credential( + credential.get(), + &encrypted_request, + &message.into(), + window_id, + ) + .map_err(|e| UserBoundKeyError::AbiError(e))?; + + // Get auth context handle + let mut auth_context_handle: USER_BOUND_KEY_AUTH_CONTEXT_HANDLE = core::ptr::null_mut(); + unsafe { + let hr = GetUserBoundKeyAuthContext( + session_handle.get(), + auth_context_blob.as_ptr() as *const core::ffi::c_void, + auth_context_blob.len() as u32, + local_nonce, + &mut auth_context_handle, + ); + check_hr(hr)?; + } + let auth_context = UniqueAuthContextHandle(auth_context_handle); + + // Validate auth context + let prop = USER_BOUND_KEY_AUTH_CONTEXT_PROPERTY { + name: USER_BOUND_KEY_AUTH_CONTEXT_PROPERTY_NAME::UserBoundKeyAuthContextPropertyCacheConfig, + size: core::mem::size_of::() as u32, + value: cache_config as *const _ as *mut core::ffi::c_void, + }; + + unsafe { + let hr = ValidateUserBoundKeyAuthContext( + formatted_key_name_wide.as_ptr(), + auth_context.get(), + 1, + &prop, + ); + check_hr(hr)?; + } + + // Protect the user key + let mut bound_key_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut bound_key_size: u32 = 0; + + unsafe { + let hr = ProtectUserBoundKey( + auth_context.get(), + custom_key_bytes.as_ptr() as *const core::ffi::c_void, + custom_key_bytes.len() as u32, + &mut bound_key_ptr, + &mut bound_key_size, + ); + check_hr(hr)?; + } + + // Convert bound key to Vec + let bound_key_bytes = unsafe { + core::slice::from_raw_parts(bound_key_ptr as *const u8, bound_key_size as usize).to_vec() + }; + + // Free the allocated memory + unsafe { + let heap = GetProcessHeap(); + HeapFree(heap, 0, bound_key_ptr); + } + + // Seal the bound key + let sealed_key = seal_data(&bound_key_bytes, sealing_policy, runtime_policy)?; + + Ok(sealed_key) +} + +/// Load a previously created user-bound key +/// +/// # Arguments +/// * `key_name` - Name of the Windows Hello credential +/// * `cache_config` - Expected cache configuration +/// * `message` - Message to display during Windows Hello prompt +/// * `window_id` - Window ID for the prompt +/// * `sealed_bound_key_bytes` - Previously sealed key material +/// * `needs_reseal` - Output: whether the key needs to be resealed +/// +/// # Returns +/// The decrypted user key bytes +pub fn load_user_bound_key( + key_name: &str, + cache_config: &keyCredentialCacheConfig, + message: &str, + window_id: u64, + sealed_bound_key_bytes: &[u8], +) -> Result<(Vec, bool), UserBoundKeyError> { + // Unseal the bound key + let (bound_key_bytes, unseal_flags) = unseal_data(sealed_bound_key_bytes)?; + + // Check if key is stale + if is_stale_key(unseal_flags) { + return Err(UserBoundKeyError::StaleKey); + } + + // Extract ephemeral public key from bound key structure + let ephemeral_public_key = get_ephemeral_public_key_bytes_from_bound_key(&bound_key_bytes)?; + + // Format the key name + let formatted_key_name = untrusted::userboundkey_format_key_name(&key_name.into()) + .map_err(|e| UserBoundKeyError::AbiError(e))?; + + // Security validation + validate_formatted_key_name(&formatted_key_name, key_name)?; + + // Get enclave base address + let enclave_ptr = get_enclave_base_address_u64()?; + + // Establish session for load (calls VTL0) + let credential_and_session = untrusted::userboundkey_establish_session_for_load( + enclave_ptr, + &key_name.into(), + &message.into(), + window_id, + ) + .map_err(|e| UserBoundKeyError::AbiError(e))?; + + // RAII wrappers + let credential = UniqueCredential::new(credential_and_session.credential); + let session_handle = UniqueSessionHandle::new( + credential_and_session.sessionInfo as USER_BOUND_KEY_SESSION_HANDLE, + ); + + // Create encrypted request for RetrieveAuthorizationContext + let mut encrypted_rac_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut encrypted_rac_size: u32 = 0; + let mut local_nonce: u64 = 0; + + let formatted_key_name_wide: Vec = formatted_key_name + .encode_utf16() + .chain(core::iter::once(0)) + .collect(); + + unsafe { + let hr = CreateUserBoundKeyRequestForRetrieveAuthorizationContext( + session_handle.get(), + formatted_key_name_wide.as_ptr(), + &mut local_nonce, + &mut encrypted_rac_ptr, + &mut encrypted_rac_size, + ); + check_hr(hr)?; + } + + let encrypted_rac_request = unsafe { + core::slice::from_raw_parts(encrypted_rac_ptr as *const u8, encrypted_rac_size as usize) + .to_vec() + }; + + unsafe { + let heap = GetProcessHeap(); + HeapFree(heap, 0, encrypted_rac_ptr); + } + + // Get authorization context (calls VTL0) + let auth_context_blob = untrusted::userboundkey_get_authorization_context_from_credential( + credential.get(), + &encrypted_rac_request, + &message.into(), + window_id, + ) + .map_err(|e| UserBoundKeyError::AbiError(e))?; + + // Get auth context handle + let mut auth_context_handle: USER_BOUND_KEY_AUTH_CONTEXT_HANDLE = core::ptr::null_mut(); + unsafe { + let hr = GetUserBoundKeyAuthContext( + session_handle.get(), + auth_context_blob.as_ptr() as *const core::ffi::c_void, + auth_context_blob.len() as u32, + local_nonce, + &mut auth_context_handle, + ); + check_hr(hr)?; + } + let auth_context = UniqueAuthContextHandle(auth_context_handle); + + // Validate auth context + let prop = USER_BOUND_KEY_AUTH_CONTEXT_PROPERTY { + name: USER_BOUND_KEY_AUTH_CONTEXT_PROPERTY_NAME::UserBoundKeyAuthContextPropertyCacheConfig, + size: core::mem::size_of::() as u32, + value: cache_config as *const _ as *mut core::ffi::c_void, + }; + + unsafe { + let hr = ValidateUserBoundKeyAuthContext( + formatted_key_name_wide.as_ptr(), + auth_context.get(), + 1, + &prop, + ); + check_hr(hr)?; + } + + // Create encrypted request for DeriveSharedSecret + let mut encrypted_dss_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut encrypted_dss_size: u32 = 0; + + unsafe { + let hr = CreateUserBoundKeyRequestForDeriveSharedSecret( + session_handle.get(), + formatted_key_name_wide.as_ptr(), + ephemeral_public_key.as_ptr() as *const core::ffi::c_void, + ephemeral_public_key.len() as u32, + &mut local_nonce, + &mut encrypted_dss_ptr, + &mut encrypted_dss_size, + ); + check_hr(hr)?; + } + + let encrypted_dss_request = unsafe { + core::slice::from_raw_parts(encrypted_dss_ptr as *const u8, encrypted_dss_size as usize) + .to_vec() + }; + + unsafe { + let heap = GetProcessHeap(); + HeapFree(heap, 0, encrypted_dss_ptr); + } + + // Get secret from credential (calls VTL0 - prompts for Windows Hello) + let secret = untrusted::userboundkey_get_secret_from_credential( + credential.get(), + &encrypted_dss_request, + &message.into(), + window_id, + ) + .map_err(|e| UserBoundKeyError::AbiError(e))?; + + // Unprotect the user key + let mut user_key_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut user_key_size: u32 = 0; + + unsafe { + let hr = UnprotectUserBoundKey( + session_handle.get(), + auth_context.get(), + secret.as_ptr() as *const core::ffi::c_void, + secret.len() as u32, + bound_key_bytes.as_ptr() as *const core::ffi::c_void, + bound_key_bytes.len() as u32, + local_nonce, + &mut user_key_ptr, + &mut user_key_size, + ); + check_hr(hr)?; + } + + let user_key_bytes = unsafe { + core::slice::from_raw_parts(user_key_ptr as *const u8, user_key_size as usize).to_vec() + }; + + unsafe { + let heap = GetProcessHeap(); + HeapFree(heap, 0, user_key_ptr); + } + + Ok((user_key_bytes, false)) +} + +/// Reseal a user-bound key with current enclave identity +/// +/// This is used when load_user_bound_key returns StaleKey error, +/// indicating the sealing key has changed. The caller should have +/// already unsealed the data (which succeeds even with a stale key) +/// and passes the unsealed bound key bytes to this function. +/// +/// # Arguments +/// * `bound_key_bytes` - Previously unsealed bound key bytes (not sealed) +/// * `sealing_policy` - Enclave sealing identity policy for the new seal +/// * `runtime_policy` - Runtime policy flags +/// +/// # Returns +/// Newly sealed key material +pub fn reseal_user_bound_key( + bound_key_bytes: &[u8], + sealing_policy: EnclaveSealingIdentityPolicy, + runtime_policy: u32, +) -> Result, UserBoundKeyError> { + // Re-seal with current enclave identity + let resealed_data = seal_data(bound_key_bytes, sealing_policy, runtime_policy)?; + + Ok(resealed_data) +} + +/// Implementation of the Trusted trait for EDL-generated code +pub struct TrustedImpl; + +#[allow(non_snake_case)] +impl Trusted for TrustedImpl { + fn userboundkey_get_attestation_report( + challenge: &Vec, + ) -> Result { + unsafe { + let mut report_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut report_size: u32 = 0; + let mut session_handle: USER_BOUND_KEY_SESSION_HANDLE = core::ptr::null_mut(); + + let hr = InitializeUserBoundKeySession( + challenge.as_ptr() as *const core::ffi::c_void, + challenge.len() as u32, + &mut report_ptr, + &mut report_size, + &mut session_handle, + ); + + if hr < 0 { + return Err(userboundkey_enclave_gen::AbiError::Hresult(hr)); + } + + // Convert report to Vec + let report = + core::slice::from_raw_parts(report_ptr as *const u8, report_size as usize).to_vec(); + + // Free the allocated memory + let heap = GetProcessHeap(); + HeapFree(heap, 0, report_ptr); + + Ok(attestationReportAndSessionInfo { + report, + sessionInfo: session_handle as u64, + }) + } + } + + fn userboundkey_close_session( + sessionInfo: u64, + ) -> Result<(), userboundkey_enclave_gen::AbiError> { + if sessionInfo != 0 { + unsafe { + let session_handle = sessionInfo as USER_BOUND_KEY_SESSION_HANDLE; + let hr = CloseUserBoundKeySession(session_handle); + if hr < 0 { + return Err(userboundkey_enclave_gen::AbiError::Hresult(hr)); + } + } + } + Ok(()) + } +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs new file mode 100644 index 00000000..7cb683a7 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Type definitions for user-bound key operations + +/// Error type for user-bound key operations +#[derive(Debug)] +pub enum UserBoundKeyError { + /// HRESULT error from Windows API + Hresult(i32), + /// ABI layer error + AbiError(userboundkey_enclave_gen::AbiError), + /// Memory allocation failed + AllocationFailed, + /// Invalid data format + InvalidData(&'static str), + /// Security policy violation + SecurityViolation(&'static str), + /// The sealing key is stale and data needs to be resealed + StaleKey, + /// Feature not implemented + NotImplemented(&'static str), +} + +impl From for UserBoundKeyError { + fn from(err: userboundkey_enclave_gen::AbiError) -> Self { + UserBoundKeyError::AbiError(err) + } +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs new file mode 100644 index 00000000..00418731 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Utility functions for VTL1 enclave operations +//! +//! This module provides general-purpose utilities for enclave operations, +//! similar to utils.vtl1.h in the C++ implementation. + +use core::sync::atomic::{AtomicPtr, Ordering}; + +use windows_enclave::vertdll::{ + EnclaveGetEnclaveInformation, ENCLAVE_FLAG_DYNAMIC_DEBUG_ACTIVE, + ENCLAVE_FLAG_DYNAMIC_DEBUG_ENABLED, ENCLAVE_FLAG_FULL_DEBUG_ENABLED, ENCLAVE_INFORMATION, +}; + +use super::types::UserBoundKeyError; + +/// Static storage for cached enclave information +static ENCLAVE_INFO_CACHE: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); + +/// Get enclave information, caching it on first access +/// +/// This function retrieves the enclave information from the system on first call +/// and caches it for subsequent calls, similar to `veil::vtl1::enclave_information()`. +pub fn get_enclave_information() -> Result<&'static ENCLAVE_INFORMATION, UserBoundKeyError> { + // Check if already cached + let ptr = ENCLAVE_INFO_CACHE.load(Ordering::Acquire); + if !ptr.is_null() { + return Ok(unsafe { &*ptr }); + } + + // Allocate and initialize + // We use a Box that we leak to get a 'static reference + let mut info: ENCLAVE_INFORMATION = unsafe { core::mem::zeroed() }; + + let hr = unsafe { + EnclaveGetEnclaveInformation( + core::mem::size_of::() as u32, + &mut info, + ) + }; + + if hr < 0 { + return Err(UserBoundKeyError::Hresult(hr)); + } + + // Leak the box to get a 'static pointer + let boxed = alloc::boxed::Box::new(info); + let new_ptr = alloc::boxed::Box::into_raw(boxed); + + // Try to set the cache (compare-and-swap) + match ENCLAVE_INFO_CACHE.compare_exchange( + core::ptr::null_mut(), + new_ptr, + Ordering::Release, + Ordering::Acquire, + ) { + Ok(_) => { + // We successfully set the cache + Ok(unsafe { &*new_ptr }) + } + Err(existing) => { + // Another thread beat us - free our allocation and use theirs + unsafe { + let _ = alloc::boxed::Box::from_raw(new_ptr); + } + Ok(unsafe { &*existing }) + } + } +} + +/// Get the base address of the enclave +/// +/// Returns the enclave's base address as a pointer, suitable for passing +/// to APIs that require an enclave identifier. +pub fn get_enclave_base_address() -> Result<*mut core::ffi::c_void, UserBoundKeyError> { + let info = get_enclave_information()?; + Ok(info.BaseAddress) +} + +/// Get the base address of the enclave as a u64 +/// +/// Convenience function that returns the base address as a u64, +/// suitable for EDL interface calls. +pub fn get_enclave_base_address_u64() -> Result { + Ok(get_enclave_base_address()? as u64) +} + +/// Check if full debug is enabled for the enclave +#[allow(dead_code)] +pub fn is_enclave_full_debug_enabled() -> Result { + let info = get_enclave_information()?; + Ok((info.Identity.Flags & ENCLAVE_FLAG_FULL_DEBUG_ENABLED) != 0) +} + +/// Check if dynamic debug is enabled for the enclave +#[allow(dead_code)] +pub fn is_enclave_dynamic_debug_enabled() -> Result { + let info = get_enclave_information()?; + Ok((info.Identity.Flags & ENCLAVE_FLAG_DYNAMIC_DEBUG_ENABLED) != 0) +} + +/// Check if dynamic debug is currently active for the enclave +#[allow(dead_code)] +pub fn is_enclave_dynamic_debug_active() -> Result { + let info = get_enclave_information()?; + Ok((info.Identity.Flags & ENCLAVE_FLAG_DYNAMIC_DEBUG_ACTIVE) != 0) +} diff --git a/rust/sdk/crates/libs/sdk-host/Cargo.toml b/rust/sdk/crates/libs/sdk-host/Cargo.toml index 694f9fe7..7027e966 100644 --- a/rust/sdk/crates/libs/sdk-host/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-host/Cargo.toml @@ -3,17 +3,26 @@ name = "vbsenclave-sdk-host" version = "0.1.0" edition.workspace = true +[features] +default = [] +userboundkey = [] + [dependencies.windows] workspace = true features = [ "Win32_System", "Win32_System_Memory", + "Win32_System_Threading", + "Win32_Security", + "Win32_Security_Authorization", "Security_Credentials", "Security_Cryptography", - "Security_Cryptography_Certificates" + "Storage_Streams" ] [dependencies] spin = { workspace = true } userboundkey-kcm = { workspace = true } -veil_abi_host_gen = { path = "./generated/veil_abi_gen" } \ No newline at end of file +userboundkey_host_gen = { path = "./generated/userboundkey_gen" } +windows-core = { workspace = true } +windows-future = { workspace = true } \ No newline at end of file diff --git a/rust/sdk/crates/libs/sdk-host/src/lib.rs b/rust/sdk/crates/libs/sdk-host/src/lib.rs index 47e9a72d..d4af10c6 100644 --- a/rust/sdk/crates/libs/sdk-host/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-host/src/lib.rs @@ -3,6 +3,9 @@ mod host_ffi; +#[cfg(feature = "userboundkey")] +pub mod userboundkey; + // The userboundkey-kcm crate is not intended to be published as a standalone package. // Long term, these KCM APIs should be consumed from the windows-rs crate. // Until those APIs are available there, the sdk-host crate re-exports the required diff --git a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs new file mode 100644 index 00000000..7c794ade --- /dev/null +++ b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs @@ -0,0 +1,461 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! VTL0 User-Bound Key Implementation +//! +//! Implements EDL untrusted callbacks for user-bound key operations. +//! This module provides VTL0/host-side Windows Hello integration with VBS enclave support. + +mod types; + +#[allow(unused_imports)] +pub use types::*; +use userboundkey_host_gen::AbiError; +pub use userboundkey_host_gen::UserBoundKeyVtl0Host; +pub use userboundkey_host_gen::implementation::types::{ + attestationReportAndSessionInfo, credentialAndSessionInfo, keyCredentialCacheConfig, +}; +use userboundkey_host_gen::implementation::untrusted::Untrusted; + +use std::sync::{Arc, Mutex}; + +use windows::{ + Security::Cryptography::CryptographicBuffer, + Storage::Streams::IBuffer, + Win32::{ + Foundation::{CloseHandle, E_FAIL, E_INVALIDARG, HANDLE}, + Security::Authorization::ConvertSidToStringSidW, + Security::{GetTokenInformation, PSID, TOKEN_QUERY, TOKEN_USER, TokenUser}, + System::Threading::{GetCurrentProcess, OpenProcessToken}, + }, + core::HSTRING, +}; +use windows_core::{Interface, PWSTR}; + +use userboundkey_kcm::{ + AttestationChallengeHandler, ChallengeResponseKind, IBuffer as KcmIBuffer, + IKeyCredentialCacheConfigurationFactory, KeyCredential, KeyCredentialCacheConfiguration, + KeyCredentialCacheOption, KeyCredentialCreationOption, KeyCredentialManager, + KeyCredentialStatus, TimeSpan, WindowId, +}; + +/// RAII wrapper for session handle that calls back to VTL1 on cleanup +struct UniqueSessionHandle { + handle: usize, + enclave_ptr: usize, +} + +impl UniqueSessionHandle { + fn new() -> Self { + Self { + handle: 0, + enclave_ptr: 0, + } + } + + #[allow(dead_code)] + fn get(&self) -> usize { + self.handle + } + + fn release(&mut self) -> usize { + let result = self.handle; + self.handle = 0; + self.enclave_ptr = 0; + result + } + + fn set(&mut self, handle: usize, enclave_ptr: usize) { + self.reset(); + self.handle = handle; + self.enclave_ptr = enclave_ptr; + } + + fn reset(&mut self) { + if self.handle != 0 && self.enclave_ptr != 0 { + // Call back to VTL1 to close session + let enclave_interface = + UserBoundKeyVtl0Host::new(self.enclave_ptr as *mut core::ffi::c_void); + let _ = enclave_interface.userboundkey_close_session(self.handle as u64); + } + self.handle = 0; + self.enclave_ptr = 0; + } +} + +impl Drop for UniqueSessionHandle { + fn drop(&mut self) { + self.reset(); + } +} + +/// Convert a Windows IBuffer to a Vec +fn buffer_to_vec(buffer: &IBuffer) -> windows_core::Result> { + let mut byte_array = windows::core::Array::::new(); + CryptographicBuffer::CopyToByteArray(buffer, &mut byte_array)?; + Ok(byte_array.to_vec()) +} + +/// Convert Vec to a Windows IBuffer +fn vec_to_buffer(data: &[u8]) -> windows_core::Result { + CryptographicBuffer::CreateFromByteArray(data) +} + +/// Convert a KcmIBuffer (from userboundkey-kcm) to Vec +fn kcm_buffer_to_vec(buffer: &KcmIBuffer) -> windows_core::Result> { + // Cast KcmIBuffer to windows IBuffer + let windows_buffer: IBuffer = buffer.cast()?; + buffer_to_vec(&windows_buffer) +} + +/// Convert Vec to a KcmIBuffer +fn vec_to_kcm_buffer(data: &[u8]) -> windows_core::Result { + let windows_buffer = vec_to_buffer(data)?; + windows_buffer.cast() +} + +/// Get the algorithm name string from the ECDH algorithm handle +fn get_algorithm(ecdh_algorithm: u64) -> Result { + // BCrypt algorithm pseudo-handle values from Windows SDK bcrypt.h + const BCRYPT_ECDH_P384_ALG_HANDLE: u64 = 0x000002b1; + const BCRYPT_ECDH_P256_ALG_HANDLE: u64 = 0x000002a1; + + if ecdh_algorithm == BCRYPT_ECDH_P384_ALG_HANDLE { + Ok(HSTRING::from("ECDH_P384")) + } else if ecdh_algorithm == BCRYPT_ECDH_P256_ALG_HANDLE { + Ok(HSTRING::from("ECDH_P256")) + } else { + Err(AbiError::Hresult(E_INVALIDARG.0)) + } +} + +/// Convert keyCredentialCacheConfig to the WinRT KeyCredentialCacheConfiguration +fn convert_cache_config( + cache_config: &keyCredentialCacheConfig, +) -> windows_core::Result { + let cache_option = match cache_config.cacheOption { + 0 => KeyCredentialCacheOption::NoCache, + 1 => KeyCredentialCacheOption::CacheWhenUnlocked, + _ => KeyCredentialCacheOption::NoCache, + }; + + // Convert timeout from seconds to TimeSpan (100-nanosecond units) + let timeout = TimeSpan { + Duration: (cache_config.cacheTimeoutInSeconds as i64) * 10_000_000, + }; + + // Use activation factory to create instance + let factory = windows_core::factory::< + KeyCredentialCacheConfiguration, + IKeyCredentialCacheConfigurationFactory, + >()?; + factory.CreateInstance(cache_option, timeout, cache_config.cacheUsageCount) +} + +/// Create an attestation challenge callback that calls into VTL1 +fn create_challenge_callback( + session_info: Arc>, + enclave_ptr: usize, +) -> AttestationChallengeHandler { + AttestationChallengeHandler::new(move |challenge: windows_core::Ref| { + // Get the IBuffer reference using ok() which returns Result<&T> + let challenge_buffer: &KcmIBuffer = challenge.ok()?; + // Convert KcmIBuffer to Vec + let challenge_vec = kcm_buffer_to_vec(challenge_buffer)?; + + // Call into VTL1 to get attestation report + let enclave_interface = UserBoundKeyVtl0Host::new(enclave_ptr as *mut core::ffi::c_void); + let attestation_result = enclave_interface + .userboundkey_get_attestation_report(&challenge_vec) + .map_err(|_| windows_core::Error::from(E_FAIL))?; + + // Store session handle + { + let mut session = session_info.lock().unwrap(); + session.set(attestation_result.sessionInfo as usize, enclave_ptr); + } + + // Convert report to KcmIBuffer + vec_to_kcm_buffer(&attestation_result.report) + }) +} + +/// Format a key name with user SID for Windows Hello +fn format_user_hello_key_name(name: &str) -> Result { + unsafe { + let mut process_token = HANDLE::default(); + OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut process_token) + .map_err(|_| AbiError::Hresult(E_FAIL.0))?; + + // Get token user info size + let mut return_length: u32 = 0; + let _ = GetTokenInformation(process_token, TokenUser, None, 0, &mut return_length); + + // Allocate buffer and get token user info + let mut buffer = vec![0u8; return_length as usize]; + GetTokenInformation( + process_token, + TokenUser, + Some(buffer.as_mut_ptr() as *mut _), + return_length, + &mut return_length, + ) + .map_err(|_| AbiError::Hresult(E_FAIL.0))?; + + let _ = CloseHandle(process_token); + + let token_user = &*(buffer.as_ptr() as *const TOKEN_USER); + let user_sid = token_user.User.Sid; + + // Convert SID to string + let sid_string = sid_to_string(user_sid)?; + + // Format: {SID}//{SID}//{keyName} + Ok(format!("{}//{}//{}", sid_string, sid_string, name)) + } +} + +/// Convert a PSID to a string representation +fn sid_to_string(sid: PSID) -> Result { + unsafe { + let mut string_sid = PWSTR::null(); + ConvertSidToStringSidW(sid, &mut string_sid).map_err(|_| AbiError::Hresult(E_FAIL.0))?; + + // Convert PWSTR to String + let len = (0..).take_while(|&i| *string_sid.0.add(i) != 0).count(); + let slice = std::slice::from_raw_parts(string_sid.0, len); + let result = String::from_utf16_lossy(slice); + + // Note: We're leaking the string memory here since LocalFree requires more setup. + // In a production implementation, proper cleanup should be added. + let _ = string_sid.0; // Prevent unused warning + + Ok(result) + } +} + +/// Untrusted implementation struct +pub struct UntrustedImpl; + +#[allow(non_snake_case)] +#[allow(unused_variables)] +impl Untrusted for UntrustedImpl { + fn userboundkey_establish_session_for_create( + enclave: u64, + keyName: &String, + ecdhProtocol: u64, + message: &String, + windowId: u64, + cacheConfig: &keyCredentialCacheConfig, + keyCredentialCreationOption: u32, + ) -> Result { + let algorithm = get_algorithm(ecdhProtocol)?; + + let cache_configuration = + convert_cache_config(cacheConfig).map_err(|e| AbiError::Hresult(e.code().0))?; + + let session_info = Arc::new(Mutex::new(UniqueSessionHandle::new())); + let enclave_ptr = enclave as usize; + + // Try to delete existing key first (ignore errors) + let key_name_hstring = HSTRING::from(keyName.as_str()); + if let Ok(delete_op) = KeyCredentialManager::DeleteAsync(&key_name_hstring) { + let _ = delete_op.join(); + } + + // Create the credential with VBS attestation + let message_hstring = HSTRING::from(message.as_str()); + let win_id = WindowId { Value: windowId }; + let creation_option = KeyCredentialCreationOption(keyCredentialCreationOption as i32); + + let callback = create_challenge_callback(session_info.clone(), enclave_ptr); + + let credential_result = KeyCredentialManager::RequestCreateAsync2( + &key_name_hstring, + creation_option, + &algorithm, + &message_hstring, + &cache_configuration, + win_id, + ChallengeResponseKind::VirtualizationBasedSecurityEnclave, + &callback, + ) + .map_err(|e| AbiError::Hresult(e.code().0))? + .join() + .map_err(|e| AbiError::Hresult(e.code().0))?; + + // Check if operation was successful + let status = credential_result + .Status() + .map_err(|e| AbiError::Hresult(e.code().0))?; + if status != KeyCredentialStatus::Success { + return Err(AbiError::Hresult(status.0)); + } + + // Get credential and convert to raw pointer + let credential = credential_result + .Credential() + .map_err(|e| AbiError::Hresult(e.code().0))?; + let credential_ptr = windows_core::Interface::as_raw(&credential) as u64; + + // Transfer session ownership + let session_handle = { + let mut session = session_info.lock().unwrap(); + session.release() as u64 + }; + + // Add reference to credential since we're transferring ownership + // We need to prevent the credential from being released + std::mem::forget(credential); + + Ok(credentialAndSessionInfo { + credential: credential_ptr, + sessionInfo: session_handle, + }) + } + + fn userboundkey_establish_session_for_load( + enclave: u64, + keyName: &String, + message: &String, + windowId: u64, + ) -> Result { + let _ = (message, windowId); // Mark as intentionally unused + let session_info = Arc::new(Mutex::new(UniqueSessionHandle::new())); + let enclave_ptr = enclave as usize; + + let key_name_hstring = HSTRING::from(keyName.as_str()); + let callback = create_challenge_callback(session_info.clone(), enclave_ptr); + + let credential_result = KeyCredentialManager::OpenAsync2( + &key_name_hstring, + ChallengeResponseKind::VirtualizationBasedSecurityEnclave, + &callback, + ) + .map_err(|e| AbiError::Hresult(e.code().0))? + .join() + .map_err(|e| AbiError::Hresult(e.code().0))?; + + // Check if operation was successful + let status = credential_result + .Status() + .map_err(|e| AbiError::Hresult(e.code().0))?; + if status != KeyCredentialStatus::Success { + return Err(AbiError::Hresult(status.0)); + } + + // Get credential and convert to raw pointer + let credential = credential_result + .Credential() + .map_err(|e| AbiError::Hresult(e.code().0))?; + let credential_ptr = windows_core::Interface::as_raw(&credential) as u64; + + // Transfer session ownership + let session_handle = { + let mut session = session_info.lock().unwrap(); + session.release() as u64 + }; + + // Prevent credential from being released (transfer ownership) + std::mem::forget(credential); + + Ok(credentialAndSessionInfo { + credential: credential_ptr, + sessionInfo: session_handle, + }) + } + + fn userboundkey_get_authorization_context_from_credential( + credential: u64, + encryptedRequest: &Vec, + message: &String, + windowId: u64, + ) -> Result, AbiError> { + // Reconstruct KeyCredential from raw pointer + // We use from_raw and then manually AddRef since the caller owns the credential + let key_credential: KeyCredential = unsafe { + let raw_ptr = credential as *mut core::ffi::c_void; + if raw_ptr.is_null() { + return Err(AbiError::Hresult(E_INVALIDARG.0)); + } + // Clone increases ref count - the original is still owned by caller + let temp = KeyCredential::from_raw(raw_ptr); + let cloned = temp.clone(); + // Forget temp to not decrement refcount + core::mem::forget(temp); + cloned + }; + + // Convert encrypted request to IBuffer + let encrypted_buffer = + vec_to_kcm_buffer(encryptedRequest).map_err(|e| AbiError::Hresult(e.code().0))?; + + // Call RetrieveAuthorizationContext on the credential + let auth_context = key_credential + .RetrieveAuthorizationContext(&encrypted_buffer) + .map_err(|e| AbiError::Hresult(e.code().0))?; + + kcm_buffer_to_vec(&auth_context).map_err(|e| AbiError::Hresult(e.code().0)) + } + + fn userboundkey_get_secret_from_credential( + credential: u64, + encryptedRequest: &Vec, + message: &String, + windowId: u64, + ) -> Result, AbiError> { + // Reconstruct KeyCredential from raw pointer + // We use from_raw and then manually AddRef since the caller owns the credential + let key_credential: KeyCredential = unsafe { + let raw_ptr = credential as *mut core::ffi::c_void; + if raw_ptr.is_null() { + return Err(AbiError::Hresult(E_INVALIDARG.0)); + } + // Clone increases ref count - the original is still owned by caller + let temp = KeyCredential::from_raw(raw_ptr); + let cloned = temp.clone(); + // Forget temp to not decrement refcount + core::mem::forget(temp); + cloned + }; + + // Convert encrypted request to IBuffer + let encrypted_buffer = + vec_to_kcm_buffer(encryptedRequest).map_err(|e| AbiError::Hresult(e.code().0))?; + + let win_id = WindowId { Value: windowId }; + let message_hstring = HSTRING::from(message.as_str()); + + // Call RequestDeriveSharedSecretAsync on the credential + let operation_result = key_credential + .RequestDeriveSharedSecretAsync(win_id, &message_hstring, &encrypted_buffer) + .map_err(|e| AbiError::Hresult(e.code().0))? + .join() + .map_err(|e| AbiError::Hresult(e.code().0))?; + + let result_buffer = operation_result + .Result() + .map_err(|e| AbiError::Hresult(e.code().0))?; + + kcm_buffer_to_vec(&result_buffer).map_err(|e| AbiError::Hresult(e.code().0)) + } + + fn userboundkey_format_key_name(keyName: &String) -> Result { + format_user_hello_key_name(keyName) + } + + fn userboundkey_delete_credential(credential: u64) -> Result<(), AbiError> { + if credential == 0 { + return Ok(()); + } + + // Take ownership of the credential and let it drop + unsafe { + let raw_ptr = credential as *mut core::ffi::c_void; + let _ = KeyCredential::from_raw(raw_ptr); + // Credential will be released when dropped + } + + Ok(()) + } +} diff --git a/rust/sdk/crates/libs/sdk-host/src/userboundkey/types.rs b/rust/sdk/crates/libs/sdk-host/src/userboundkey/types.rs new file mode 100644 index 00000000..73bb6759 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-host/src/userboundkey/types.rs @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Type definitions for user-bound key operations diff --git a/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs b/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs index 14a7fece..54cc26d2 100644 --- a/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs +++ b/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs @@ -3,3 +3,61 @@ mod bindings; pub use bindings::*; + +use windows_core::{Interface, HSTRING, Param}; +use windows_future::IAsyncOperation; + +// Extension constant for KeyCredentialStatus +#[allow(non_upper_case_globals)] +impl KeyCredentialStatus { + /// Algorithm is not supported + pub const AlgorithmNotSupported: Self = Self(7i32); +} + +/// Extension methods for KeyCredential to access IKeyCredential2 APIs. +#[allow(non_snake_case)] +impl KeyCredential { + /// Requests derivation of a shared secret using the credential. + pub fn RequestDeriveSharedSecretAsync( + &self, + window_id: WindowId, + message: &HSTRING, + encrypted_request: P2, + ) -> windows_core::Result> + where + P2: Param, + { + let this = &Interface::cast::(self)?; + unsafe { + let mut result__ = core::mem::zeroed(); + (Interface::vtable(this).RequestDeriveSharedSecretAsync)( + Interface::as_raw(this), + window_id, + core::mem::transmute_copy(message), + encrypted_request.param().abi(), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + } + } + + /// Retrieves the authorization context for the credential. + pub fn RetrieveAuthorizationContext( + &self, + encrypted_request: P0, + ) -> windows_core::Result + where + P0: Param, + { + let this = &Interface::cast::(self)?; + unsafe { + let mut result__ = core::mem::zeroed(); + (Interface::vtable(this).RetrieveAuthorizationContext)( + Interface::as_raw(this), + encrypted_request.param().abi(), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + } + } +} diff --git a/rust/sdk/crates/libs/userboundkey.edl b/rust/sdk/crates/libs/userboundkey.edl new file mode 100644 index 00000000..5b85237c --- /dev/null +++ b/rust/sdk/crates/libs/userboundkey.edl @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// EDL file for user-bound key operations between VTL0 (host) and VTL1 (enclave). +// This defines the cross-VTL interface for Windows Hello VBS enclave integration. + +enclave +{ + // Configuration for credential caching behavior + struct keyCredentialCacheConfig + { + uint32_t cacheOption; + uint32_t cacheTimeoutInSeconds; + uint32_t cacheUsageCount; + }; + + // Result from VTL1 attestation report generation + struct attestationReportAndSessionInfo + { + vector report; + uint64_t sessionInfo; + }; + + // Result from VTL0 credential establishment + struct credentialAndSessionInfo + { + uint64_t credential; + uint64_t sessionInfo; + }; + + // Trusted functions: Called from VTL0 into VTL1 (enclave) + trusted + { + // Generate an attestation report for the given challenge. + // This initializes a user-bound key session in VTL1. + attestationReportAndSessionInfo userboundkey_get_attestation_report( + vector challenge + ); + + // Close a user-bound key session and release associated resources. + void userboundkey_close_session( + uint64_t sessionInfo + ); + }; + + // Untrusted functions: Called from VTL1 into VTL0 (host) + untrusted + { + // Establish a session for creating a new Windows Hello key. + // Returns credential handle and session info on success. + credentialAndSessionInfo userboundkey_establish_session_for_create( + uint64_t enclave, + string keyName, + uint64_t ecdhProtocol, + string message, + uint64_t windowId, + keyCredentialCacheConfig cacheConfig, + uint32_t keyCredentialCreationOption + ); + + // Establish a session for loading an existing Windows Hello key. + // Returns credential handle and session info on success. + credentialAndSessionInfo userboundkey_establish_session_for_load( + uint64_t enclave, + string keyName, + string message, + uint64_t windowId + ); + + // Get authorization context from credential using encrypted KCM request. + // Returns the authorization context bytes. + vector userboundkey_get_authorization_context_from_credential( + uint64_t credential, + vector encryptedRequest, + string message, + uint64_t windowId + ); + + // Derive shared secret from credential using encrypted KCM request. + // Returns the encrypted shared secret bytes. + vector userboundkey_get_secret_from_credential( + uint64_t credential, + vector encryptedRequest, + string message, + uint64_t windowId + ); + + // Format a key name with current user's SID for Windows Hello. + // Returns the formatted key name string. + string userboundkey_format_key_name( + string keyName + ); + + // Delete/release a credential handle in VTL0. + void userboundkey_delete_credential( + uint64_t credential + ); + }; +}; diff --git a/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs index a6c9f95d..849cce6b 100644 --- a/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs +++ b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs @@ -58,3 +58,13 @@ pub const PAGE_WRITECOPY: u32 = 8u32; pub const MEM_IMAGE: PAGE_TYPE = 16777216u32; pub const MEM_MAPPED: u32 = 262144u32; pub const MEM_PRIVATE: u32 = 131072u32; + +// Enclave identity constants (used for manual struct definitions if needed) +// Note: ENCLAVE_IDENTITY and ENCLAVE_INFORMATION structs are in generated bindings +pub const IMAGE_ENCLAVE_LONG_ID_LENGTH: usize = 32; +pub const IMAGE_ENCLAVE_SHORT_ID_LENGTH: usize = 16; + +// Enclave type constants (not in generated bindings) +pub const ENCLAVE_TYPE_SGX: u32 = 0x00000001; +pub const ENCLAVE_TYPE_SGX2: u32 = 0x00000002; +pub const ENCLAVE_TYPE_VBS: u32 = 0x00000010; diff --git a/rust/sdk/generate_codegen_for_workspace.ps1 b/rust/sdk/generate_codegen_for_workspace.ps1 index ae47bf29..7aa7434e 100644 --- a/rust/sdk/generate_codegen_for_workspace.ps1 +++ b/rust/sdk/generate_codegen_for_workspace.ps1 @@ -18,6 +18,15 @@ $hostSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-host" -Namespace "veil_abi" ` -Vtl0ClassName "export_interface" +# Generate userboundkey-specific EDL crates +$userboundkeyEdlPath = Join-Path $SdkWorkspacePath "crates\libs\userboundkey.edl" +. "$scriptsDir\generate_codegen_crates.ps1" ` + -HostAppOutDir "$hostSdkCrate\generated" ` + -EnclaveOutDir "$enclaveSdkCrate\generated" ` + -EdlPath $userboundkeyEdlPath ` + -Namespace "userboundkey" ` + -Vtl0ClassName "UserBoundKeyVtl0Host" + # Below this comment, Call the generate_codegen_crates.ps1 script # on any other crates in the sdk workspace that need codegen bindings. # E.g for any sample crates. From 2160f129cd3d1d12a619d40d1320aa5fd89fe8b1 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Tue, 20 Jan 2026 12:45:26 -0800 Subject: [PATCH 02/13] Rust UB works --- rust/scripts/generate_codegen_crates.ps1 | 13 +- rust/scripts/sign-enclave.ps1 | 33 +- rust/sdk/Cargo.toml | 14 + rust/sdk/build.ps1 | 145 +++++++ rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 6 + .../sdk-enclave/src/userboundkey/crypto.rs | 317 +++++++++++++- .../libs/sdk-enclave/src/userboundkey/mod.rs | 14 +- .../sdk-enclave/src/userboundkey/types.rs | 20 + .../sdk-enclave/src/userboundkey/utils.rs | 4 +- rust/sdk/crates/libs/sdk-host/src/enclave.rs | 405 ++++++++++++++++++ rust/sdk/crates/libs/sdk-host/src/lib.rs | 2 + .../libs/sdk-host/src/userboundkey/mod.rs | 45 +- .../libs/userboundkey-kcm/src/bindings.rs | 74 +--- .../crates/libs/userboundkey-kcm/src/lib.rs | 79 +++- .../sdk/crates/samples/userboundkey/README.md | 210 +++++++++ .../sdk/crates/samples/userboundkey/build.ps1 | 88 ++++ .../userboundkey/enclave/.cargo/config.toml | 5 + .../samples/userboundkey/enclave/Cargo.toml | 17 + .../samples/userboundkey/enclave/build.rs | 9 + .../userboundkey/enclave/src/edl_impls.rs | 347 +++++++++++++++ .../enclave/src/enclave_constants.rs | 32 ++ .../samples/userboundkey/enclave/src/lib.rs | 88 ++++ .../samples/userboundkey/host/Cargo.toml | 16 + .../userboundkey/host/src/edl_impls.rs | 18 + .../samples/userboundkey/host/src/main.rs | 375 ++++++++++++++++ .../userboundkey/userboundkey_sample.edl | 66 +++ rust/sdk/generate_and_build_crates.ps1 | 20 - rust/sdk/generate_codegen_for_workspace.ps1 | 36 -- 28 files changed, 2336 insertions(+), 162 deletions(-) create mode 100644 rust/sdk/build.ps1 create mode 100644 rust/sdk/crates/libs/sdk-host/src/enclave.rs create mode 100644 rust/sdk/crates/samples/userboundkey/README.md create mode 100644 rust/sdk/crates/samples/userboundkey/build.ps1 create mode 100644 rust/sdk/crates/samples/userboundkey/enclave/.cargo/config.toml create mode 100644 rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml create mode 100644 rust/sdk/crates/samples/userboundkey/enclave/build.rs create mode 100644 rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs create mode 100644 rust/sdk/crates/samples/userboundkey/enclave/src/enclave_constants.rs create mode 100644 rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs create mode 100644 rust/sdk/crates/samples/userboundkey/host/Cargo.toml create mode 100644 rust/sdk/crates/samples/userboundkey/host/src/edl_impls.rs create mode 100644 rust/sdk/crates/samples/userboundkey/host/src/main.rs create mode 100644 rust/sdk/crates/samples/userboundkey/userboundkey_sample.edl delete mode 100644 rust/sdk/generate_and_build_crates.ps1 delete mode 100644 rust/sdk/generate_codegen_for_workspace.ps1 diff --git a/rust/scripts/generate_codegen_crates.ps1 b/rust/scripts/generate_codegen_crates.ps1 index 8ebc3682..df4a496d 100644 --- a/rust/scripts/generate_codegen_crates.ps1 +++ b/rust/scripts/generate_codegen_crates.ps1 @@ -13,7 +13,9 @@ param( [Parameter(Mandatory = $true)] [string]$Namespace = "", - [string]$Vtl0ClassName = "" + [string]$Vtl0ClassName = "", + + [string]$ImportDirectories = "" ) $ErrorActionPreference = "Stop" @@ -21,13 +23,17 @@ $ErrorActionPreference = "Stop" # Import helper that sets $edlCodeGenToolsPath . "$PSScriptRoot\get_codegen_executable.ps1" +# Build import directories argument if provided +$importDirArg = if ($ImportDirectories -ne "") { "--ImportDirectories", $ImportDirectories } else { @() } + # Run codegen for the enclave crate & $edlCodeGenToolsPath ` --namespace $Namespace ` --language rust ` --EdlPath $EdlPath ` --VirtualTrustLayer enclave ` - --OutputDirectory $EnclaveOutDir + --OutputDirectory $EnclaveOutDir ` + @importDirArg # Run codegen for the host crate & $edlCodeGenToolsPath ` @@ -36,4 +42,5 @@ $ErrorActionPreference = "Stop" --EdlPath $EdlPath ` --VirtualTrustLayer hostapp ` --Vtl0ClassName $Vtl0ClassName ` - --OutputDirectory $HostAppOutDir + --OutputDirectory $HostAppOutDir ` + @importDirArg diff --git a/rust/scripts/sign-enclave.ps1 b/rust/scripts/sign-enclave.ps1 index d13f7d4b..d5a9eaa9 100644 --- a/rust/scripts/sign-enclave.ps1 +++ b/rust/scripts/sign-enclave.ps1 @@ -22,23 +22,37 @@ if (-not (Test-Path $sdkRegPath)) { throw "Windows SDK not found. Please install the latest Windows 11 SDK." } -# Read the two important values +# Read the installation folder $sdk = Get-ItemProperty -Path $sdkRegPath - $installationFolder = $sdk.InstallationFolder -$productVersion = $sdk.ProductVersion Write-Host "`nAttempting to run veiid.exe and sign enclave DLL using certificate '$CertName'..." -if (-not $installationFolder -or -not $productVersion) { +if (-not $installationFolder) { throw "Incomplete Windows SDK installation. Please reinstall the latest Windows SDK." } -# Build full SDK bin path -$sdkBin = Join-Path $installationFolder "bin\$productVersion.0\x64" +# Find an SDK version that has veiid.exe in its bin folder +# List all version folders under bin and find one with veiid.exe +$binRoot = Join-Path $installationFolder "bin" +if (-not (Test-Path $binRoot)) { + throw "Windows SDK bin folder not found at: $binRoot. Please install the latest Windows SDK." +} -if (-not (Test-Path $sdkBin)) { - throw "Windows SDK bin folder not found at: $sdkBin. Please install (or repair) the latest Windows SDK." +$sdkBin = $null +$versionFolders = Get-ChildItem -Path $binRoot -Directory | Sort-Object Name -Descending +foreach ($versionFolder in $versionFolders) { + $candidateBin = Join-Path $versionFolder.FullName "x64" + $candidateVeiid = Join-Path $candidateBin "veiid.exe" + if (Test-Path $candidateVeiid) { + $sdkBin = $candidateBin + Write-Host "Found SDK tools in: $sdkBin" + break + } +} + +if (-not $sdkBin) { + throw "Could not find veiid.exe in any SDK version under $binRoot. Please install Windows SDK 10.0.26100.0 or higher." } # Compute tool paths @@ -84,3 +98,6 @@ if ($LASTEXITCODE -ne 0) { } else { Write-Host "Enclave DLL signing completed with no warnings." } + +# Exit with success since signing completed (even with warnings) +exit 0 \ No newline at end of file diff --git a/rust/sdk/Cargo.toml b/rust/sdk/Cargo.toml index 0b957577..b4adb598 100644 --- a/rust/sdk/Cargo.toml +++ b/rust/sdk/Cargo.toml @@ -8,15 +8,29 @@ members = [ "crates/libs/userboundkey-kcm", "crates/tests/sdk", "crates/libs/windows-enclave", + # Sample crates + "crates/samples/userboundkey/enclave", + "crates/samples/userboundkey/host", + "crates/samples/userboundkey/enclave/generated/userboundkey_sample_gen", + "crates/samples/userboundkey/host/generated/userboundkey_sample_gen", ] [workspace.package] edition = "2024" +# Enclaves require panic=abort since they are no_std cdylib +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + [workspace.dependencies] +edlcodegen-tools = { git = "https://github.com/microsoft/VbsEnclaveTooling" } regex = "1.11" spin = "0.10.0" userboundkey-kcm = { version = "0.1.0", path = "crates/libs/userboundkey-kcm" } +userboundkey_host_gen = { version = "0.0.0", path = "crates/libs/sdk-host/generated/userboundkey_gen" } vbsenclave-sdk-enclave = { version = "0.1.0", path = "crates/libs/sdk-enclave" } vbsenclave-sdk-host = { version = "0.1.0", path = "crates/libs/sdk-host" } windows = "0.62.2" diff --git a/rust/sdk/build.ps1 b/rust/sdk/build.ps1 new file mode 100644 index 00000000..85b72c38 --- /dev/null +++ b/rust/sdk/build.ps1 @@ -0,0 +1,145 @@ +# Generates EDL bindings and builds the entire SDK workspace. +# +# Usage: +# .\build.ps1 # Debug build +# .\build.ps1 -Configuration Release # Release build +# .\build.ps1 -CertName "MyCert" # Debug build + sign enclaves +# .\build.ps1 -Configuration Release -CertName "MyCert" # Release build + sign enclaves + +param( + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Debug", + + [string]$CertName +) + +$ErrorActionPreference = "Stop" + +$sdkRoot = $PSScriptRoot +$rustRoot = Resolve-Path "$sdkRoot\.." +$repoRoot = Resolve-Path "$rustRoot\.." +$scriptsDir = Join-Path $rustRoot "scripts" + +Write-Host "`n=== VBS Enclave SDK Build Script ===" -ForegroundColor Cyan +Write-Host "Configuration: $Configuration" +if ($CertName) { + Write-Host "Certificate: $CertName (will sign enclaves)" +} + +$totalSteps = if ($CertName) { 4 } else { 3 } + +# ============================================================================ +# Step 1: Generate SDK EDL bindings +# ============================================================================ +Write-Host "`n[1/$totalSteps] Generating SDK EDL bindings..." -ForegroundColor Yellow + +$generateScript = Join-Path $scriptsDir "generate_codegen_crates.ps1" + +# SDK crate paths +$enclaveSdkCrate = Join-Path $sdkRoot "crates\libs\sdk-enclave" +$hostSdkCrate = Join-Path $sdkRoot "crates\libs\sdk-host" + +# Generate veil_abi EDL bindings +$veilAbiEdl = Join-Path $repoRoot "src\VbsEnclaveSDK\veil_abi.edl" +& $generateScript ` + -HostAppOutDir "$hostSdkCrate\generated" ` + -EnclaveOutDir "$enclaveSdkCrate\generated" ` + -EdlPath $veilAbiEdl ` + -Namespace "veil_abi" ` + -Vtl0ClassName "export_interface" + +if ($LASTEXITCODE -ne 0) { throw "veil_abi EDL generation failed." } + +# Generate userboundkey EDL bindings +$userboundkeyEdl = Join-Path $sdkRoot "crates\libs\userboundkey.edl" +& $generateScript ` + -HostAppOutDir "$hostSdkCrate\generated" ` + -EnclaveOutDir "$enclaveSdkCrate\generated" ` + -EdlPath $userboundkeyEdl ` + -Namespace "userboundkey" ` + -Vtl0ClassName "UserBoundKeyVtl0Host" + +if ($LASTEXITCODE -ne 0) { throw "userboundkey EDL generation failed." } + +Write-Host "SDK EDL bindings generated." -ForegroundColor Green + +# ============================================================================ +# Step 2: Generate sample EDL bindings +# ============================================================================ +Write-Host "`n[2/$totalSteps] Generating sample EDL bindings..." -ForegroundColor Yellow + +# Userboundkey sample +$sampleDir = Join-Path $sdkRoot "crates\samples\userboundkey" +$sampleEdl = Join-Path $sampleDir "userboundkey_sample.edl" +$libsImportDir = Join-Path $sdkRoot "crates\libs" + +& $generateScript ` + -HostAppOutDir "$sampleDir\host\generated" ` + -EnclaveOutDir "$sampleDir\enclave\generated" ` + -EdlPath $sampleEdl ` + -Namespace "userboundkey_sample" ` + -Vtl0ClassName "userboundkey_sampleWrapper" ` + -ImportDirectories $libsImportDir + +if ($LASTEXITCODE -ne 0) { throw "userboundkey_sample EDL generation failed." } + +Write-Host "Sample EDL bindings generated." -ForegroundColor Green + +# ============================================================================ +# Step 3: Format and build workspace +# ============================================================================ +Write-Host "`n[3/$totalSteps] Building workspace..." -ForegroundColor Yellow + +# Format generated code +$formatScript = Join-Path $scriptsDir "invoke_rustfmt_workspace.ps1" +& $formatScript -WorkspacePath $sdkRoot + +# Build workspace +Push-Location $sdkRoot +try { + $buildArgs = @("build", "--workspace") + if ($Configuration -eq "Release") { + $buildArgs += "--release" + } + + cargo @buildArgs + + if ($LASTEXITCODE -ne 0) { throw "Cargo build failed." } +} finally { + Pop-Location +} + +Write-Host "Build completed." -ForegroundColor Green + +# ============================================================================ +# Step 4: Sign enclaves (optional) +# ============================================================================ +if ($CertName) { + Write-Host "`n[4/$totalSteps] Signing enclave DLLs..." -ForegroundColor Yellow + + $signScript = Join-Path $scriptsDir "sign-enclave.ps1" + $targetDir = if ($Configuration -eq "Release") { "release" } else { "debug" } + $targetPath = Join-Path $sdkRoot "target\$targetDir" + + # Find all enclave DLLs (convention: *_enclave.dll) + $enclaveDlls = Get-ChildItem -Path $targetPath -Filter "*_enclave.dll" -ErrorAction SilentlyContinue + + if ($enclaveDlls.Count -eq 0) { + Write-Host "No enclave DLLs found to sign." -ForegroundColor Yellow + } else { + foreach ($dll in $enclaveDlls) { + Write-Host "Signing $($dll.Name)..." -ForegroundColor Gray + & $signScript -DllPath $dll.FullName -CertName $CertName + if ($LASTEXITCODE -ne 0) { throw "Failed to sign $($dll.Name)" } + } + Write-Host "All enclaves signed." -ForegroundColor Green + } +} + +Write-Host "`n=== SDK Build Complete ===" -ForegroundColor Cyan +$targetDir = if ($Configuration -eq "Release") { "release" } else { "debug" } +Write-Host "Output directory: $(Join-Path $sdkRoot "target\$targetDir")" +if (-not $CertName) { + Write-Host "" + Write-Host "Tip: Use -CertName to sign enclave DLLs after build." +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index c4ad4c07..8fb7685c 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -8,3 +8,9 @@ mod enclave_ffi; #[cfg(feature = "userboundkey")] pub mod userboundkey; + +// Re-export the userboundkey generated enclave crate. +// This is needed by applications that use the SDK's userboundkey module so they can +// export the SDK's enclave functions (callback registration, attestation, etc.). +#[cfg(feature = "userboundkey")] +pub use userboundkey_enclave_gen; diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs index f7f03c19..112a7603 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs @@ -4,14 +4,17 @@ //! Cryptographic utilities for VTL1 enclave operations //! //! This module provides crypto primitives used by user-bound key operations, -//! including random number generation and enclave sealing. +//! including random number generation, enclave sealing, and AES-GCM encryption. use alloc::vec::Vec; -use windows_enclave::bcrypt::{BCryptGenRandom, BCRYPT_USE_SYSTEM_PREFERRED_RNG}; +use windows_enclave::bcrypt::{ + BCRYPT_AES_GCM_ALG_HANDLE, BCRYPT_KEY_HANDLE, BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptDecrypt, + BCryptDestroyKey, BCryptEncrypt, BCryptGenRandom, BCryptGenerateSymmetricKey, +}; use windows_enclave::vertdll::{ - EnclaveSealData, EnclaveUnsealData, GetProcessHeap, HEAP_ZERO_MEMORY, HeapAlloc, HeapFree, - ENCLAVE_UNSEAL_FLAG_STALE_KEY, + ENCLAVE_UNSEAL_FLAG_STALE_KEY, EnclaveSealData, EnclaveUnsealData, GetProcessHeap, + HEAP_ZERO_MEMORY, HeapAlloc, HeapFree, }; use super::types::UserBoundKeyError; @@ -294,6 +297,307 @@ pub fn make_nonce_from_number(nonce: u64) -> [u8; NONCE_SIZE] { buffer } +/// Zero nonce for AES-GCM (all zeros) +pub const ZERO_NONCE: [u8; NONCE_SIZE] = [0u8; NONCE_SIZE]; + +// +// BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure +// This is not in the generated bindings, so we define it manually. +// See: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_authenticated_cipher_mode_info +// + +/// Version for BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO +const BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION: u32 = 1; + +/// Authenticated cipher mode info structure for AES-GCM +#[repr(C)] +#[derive(Clone)] +pub struct BcryptAuthenticatedCipherModeInfo { + pub cb_size: u32, + pub dw_info_version: u32, + pub pb_nonce: *mut u8, + pub cb_nonce: u32, + pub pb_auth_data: *mut u8, + pub cb_auth_data: u32, + pub pb_tag: *mut u8, + pub cb_tag: u32, + pub pb_mac_context: *mut u8, + pub cb_mac_context: u32, + pub cb_aad: u32, + pub cb_data: u64, + pub dw_flags: u32, +} + +impl Default for BcryptAuthenticatedCipherModeInfo { + fn default() -> Self { + Self { + cb_size: core::mem::size_of::() as u32, + dw_info_version: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, + pb_nonce: core::ptr::null_mut(), + cb_nonce: 0, + pb_auth_data: core::ptr::null_mut(), + cb_auth_data: 0, + pb_tag: core::ptr::null_mut(), + cb_tag: 0, + pb_mac_context: core::ptr::null_mut(), + cb_mac_context: 0, + cb_aad: 0, + cb_data: 0, + dw_flags: 0, + } + } +} + +impl BcryptAuthenticatedCipherModeInfo { + /// Create a new cipher mode info for encryption (tag will be written) + pub fn for_encrypt(nonce: &mut [u8], tag: &mut [u8]) -> Self { + Self { + cb_size: core::mem::size_of::() as u32, + dw_info_version: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, + pb_nonce: nonce.as_mut_ptr(), + cb_nonce: nonce.len() as u32, + pb_auth_data: core::ptr::null_mut(), + cb_auth_data: 0, + pb_tag: tag.as_mut_ptr(), + cb_tag: tag.len() as u32, + pb_mac_context: core::ptr::null_mut(), + cb_mac_context: 0, + cb_aad: 0, + cb_data: 0, + dw_flags: 0, + } + } + + /// Create a new cipher mode info for decryption (tag is input) + pub fn for_decrypt(nonce: &[u8], tag: &[u8]) -> Self { + Self { + cb_size: core::mem::size_of::() as u32, + dw_info_version: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, + pb_nonce: nonce.as_ptr() as *mut u8, + cb_nonce: nonce.len() as u32, + pb_auth_data: core::ptr::null_mut(), + cb_auth_data: 0, + pb_tag: tag.as_ptr() as *mut u8, + cb_tag: tag.len() as u32, + pb_mac_context: core::ptr::null_mut(), + cb_mac_context: 0, + cb_aad: 0, + cb_data: 0, + dw_flags: 0, + } + } +} + +// +// Symmetric Key Handle (RAII wrapper) +// + +/// RAII wrapper for BCrypt symmetric key handle +pub struct SymmetricKeyHandle { + handle: BCRYPT_KEY_HANDLE, +} + +impl SymmetricKeyHandle { + /// Create a symmetric key from raw key bytes using AES-GCM + pub fn from_bytes(key_bytes: &[u8]) -> Result { + let mut handle: BCRYPT_KEY_HANDLE = core::ptr::null_mut(); + + unsafe { + let status = BCryptGenerateSymmetricKey( + BCRYPT_AES_GCM_ALG_HANDLE, + &mut handle, + core::ptr::null_mut(), // No key object buffer needed + 0, + key_bytes.as_ptr(), + key_bytes.len() as u32, + 0, + ); + check_ntstatus(status)?; + } + + Ok(Self { handle }) + } + + /// Get the raw handle for use with BCrypt functions + pub fn handle(&self) -> BCRYPT_KEY_HANDLE { + self.handle + } +} + +impl Drop for SymmetricKeyHandle { + fn drop(&mut self) { + if !self.handle.is_null() { + unsafe { + let _ = BCryptDestroyKey(self.handle); + } + } + } +} + +// +// AES-GCM Encryption / Decryption +// + +/// Encrypt data using AES-GCM +/// +/// # Arguments +/// * `key` - The symmetric key handle +/// * `plaintext` - Data to encrypt +/// * `nonce` - 12-byte nonce (IV) +/// +/// # Returns +/// A tuple of (ciphertext, tag) where tag is 16 bytes +pub fn encrypt( + key: &SymmetricKeyHandle, + plaintext: &[u8], + nonce: &[u8; NONCE_SIZE], +) -> Result<(Vec, [u8; TAG_SIZE]), UserBoundKeyError> { + let mut tag = [0u8; TAG_SIZE]; + let mut nonce_copy = *nonce; + + // AES-GCM: ciphertext length equals plaintext length + let mut ciphertext = alloc::vec![0u8; plaintext.len()]; + let mut result_size: u32 = 0; + + let cipher_info = BcryptAuthenticatedCipherModeInfo::for_encrypt(&mut nonce_copy, &mut tag); + + unsafe { + let status = BCryptEncrypt( + key.handle(), + plaintext.as_ptr(), + plaintext.len() as u32, + &cipher_info as *const _ as *const core::ffi::c_void, + core::ptr::null_mut(), // No IV buffer (using nonce in cipher_info) + 0, + ciphertext.as_mut_ptr(), + ciphertext.len() as u32, + &mut result_size, + 0, + ); + check_ntstatus(status)?; + } + + Ok((ciphertext, tag)) +} + +/// Decrypt data using AES-GCM +/// +/// # Arguments +/// * `key` - The symmetric key handle +/// * `ciphertext` - Data to decrypt +/// * `nonce` - 12-byte nonce (IV) used during encryption +/// * `tag` - 16-byte authentication tag from encryption +/// +/// # Returns +/// The decrypted plaintext +pub fn decrypt( + key: &SymmetricKeyHandle, + ciphertext: &[u8], + nonce: &[u8; NONCE_SIZE], + tag: &[u8; TAG_SIZE], +) -> Result, UserBoundKeyError> { + // AES-GCM: plaintext length equals ciphertext length + let mut plaintext = alloc::vec![0u8; ciphertext.len()]; + let mut result_size: u32 = 0; + + let cipher_info = BcryptAuthenticatedCipherModeInfo::for_decrypt(nonce, tag); + + unsafe { + let status = BCryptDecrypt( + key.handle(), + ciphertext.as_ptr(), + ciphertext.len() as u32, + &cipher_info as *const _ as *const core::ffi::c_void, + core::ptr::null_mut(), // No IV buffer (using nonce in cipher_info) + 0, + plaintext.as_mut_ptr(), + plaintext.len() as u32, + &mut result_size, + 0, + ); + + // STATUS_AUTH_TAG_MISMATCH = 0xC000A002 + if status == -1073700862i32 { + return Err(UserBoundKeyError::InvalidData( + "Authentication tag mismatch", + )); + } + check_ntstatus(status)?; + } + + Ok(plaintext) +} + +/// Encrypt data and append the tag to the output +/// +/// # Arguments +/// * `key` - The symmetric key handle +/// * `plaintext` - Data to encrypt +/// * `nonce` - 12-byte nonce (IV) +/// +/// # Returns +/// Combined output: [ciphertext][tag:16 bytes] +pub fn encrypt_and_tag( + key: &SymmetricKeyHandle, + plaintext: &[u8], + nonce: &[u8; NONCE_SIZE], +) -> Result, UserBoundKeyError> { + let (ciphertext, tag) = encrypt(key, plaintext, nonce)?; + + let mut combined = Vec::with_capacity(ciphertext.len() + TAG_SIZE); + combined.extend_from_slice(&ciphertext); + combined.extend_from_slice(&tag); + + Ok(combined) +} + +/// Encrypt data with zero nonce and append tag +/// +/// Uses a zero nonce (all zeros). Only safe when each key is used once. +pub fn encrypt_and_tag_zero_nonce( + key: &SymmetricKeyHandle, + plaintext: &[u8], +) -> Result, UserBoundKeyError> { + encrypt_and_tag(key, plaintext, &ZERO_NONCE) +} + +/// Decrypt data that has the tag appended +/// +/// # Arguments +/// * `key` - The symmetric key handle +/// * `combined` - Combined input: [ciphertext][tag:16 bytes] +/// * `nonce` - 12-byte nonce (IV) used during encryption +/// +/// # Returns +/// The decrypted plaintext +pub fn decrypt_and_untag( + key: &SymmetricKeyHandle, + combined: &[u8], + nonce: &[u8; NONCE_SIZE], +) -> Result, UserBoundKeyError> { + if combined.len() < TAG_SIZE { + return Err(UserBoundKeyError::InvalidData("Data too short for tag")); + } + + let ciphertext_len = combined.len() - TAG_SIZE; + let ciphertext = &combined[..ciphertext_len]; + let tag: [u8; TAG_SIZE] = combined[ciphertext_len..] + .try_into() + .map_err(|_| UserBoundKeyError::InvalidData("Invalid tag length"))?; + + decrypt(key, ciphertext, nonce, &tag) +} + +/// Decrypt data with zero nonce that has tag appended +/// +/// Uses a zero nonce (all zeros). +pub fn decrypt_and_untag_zero_nonce( + key: &SymmetricKeyHandle, + combined: &[u8], +) -> Result, UserBoundKeyError> { + decrypt_and_untag(key, combined, &ZERO_NONCE) +} + #[cfg(test)] mod tests { use super::*; @@ -302,7 +606,10 @@ mod tests { fn test_make_nonce_from_number() { let nonce = make_nonce_from_number(0x0102030405060708); assert_eq!(nonce[0..4], [0, 0, 0, 0]); - assert_eq!(nonce[4..12], [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]); + assert_eq!( + nonce[4..12], + [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01] + ); } #[test] diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs index 750a1433..493bec11 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs @@ -11,16 +11,19 @@ mod types; mod utils; pub use crypto::{ - EnclaveSealingIdentityPolicy, SYMMETRIC_KEY_SIZE_BYTES, + EnclaveSealingIdentityPolicy, NONCE_SIZE, SYMMETRIC_KEY_SIZE_BYTES, SymmetricKeyHandle, + TAG_SIZE, ZERO_NONCE, decrypt, decrypt_and_untag, decrypt_and_untag_zero_nonce, encrypt, + encrypt_and_tag, encrypt_and_tag_zero_nonce, }; pub use types::*; +// Re-export the cache config type for use by samples +pub use userboundkey_enclave_gen::implementation::types::keyCredentialCacheConfig; + use alloc::vec::Vec; use userboundkey_enclave_gen::implementation::trusted::Trusted; -use userboundkey_enclave_gen::implementation::types::{ - attestationReportAndSessionInfo, keyCredentialCacheConfig, -}; +use userboundkey_enclave_gen::implementation::types::attestationReportAndSessionInfo; use userboundkey_enclave_gen::stubs::untrusted; use windows_enclave::veinterop::{ @@ -213,7 +216,7 @@ pub fn create_user_bound_key_with_custom_key( key_credential_creation_option: u32, custom_key_bytes: &[u8], ) -> Result, UserBoundKeyError> { - // Format the key name (calls VTL0) + // Format the key name (calls VTL0) - this is the FIRST VTL0 call let formatted_key_name = untrusted::userboundkey_format_key_name(&key_name.into()) .map_err(|e| UserBoundKeyError::AbiError(e))?; @@ -224,6 +227,7 @@ pub fn create_user_bound_key_with_custom_key( let enclave_ptr = get_enclave_base_address_u64()?; // Establish session (calls VTL0 which triggers Windows Hello) + // Requires at least 2 enclave threads - one for this call and one for the callback let credential_and_session = untrusted::userboundkey_establish_session_for_create( enclave_ptr, &key_name.into(), diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs index 7cb683a7..8d609c6a 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs @@ -27,3 +27,23 @@ impl From for UserBoundKeyError { UserBoundKeyError::AbiError(err) } } + +impl UserBoundKeyError { + /// Convert the error to an HRESULT code + pub fn to_hresult(&self) -> i32 { + match self { + UserBoundKeyError::Hresult(hr) => *hr, + UserBoundKeyError::AbiError(e) => e.to_hresult().0, + UserBoundKeyError::AllocationFailed => -2147024882, // E_OUTOFMEMORY + UserBoundKeyError::InvalidData(_) => -2147024809, // E_INVALIDARG + UserBoundKeyError::SecurityViolation(_) => -2147024891, // E_ACCESSDENIED + UserBoundKeyError::StaleKey => 0x80090325_u32 as i32, // SEC_E_BAD_PKGID or similar + UserBoundKeyError::NotImplemented(_) => -2147467263, // E_NOTIMPL + } + } + + /// Check if this error indicates the sealing key is stale + pub fn is_stale_key(&self) -> bool { + matches!(self, UserBoundKeyError::StaleKey) + } +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs index 00418731..4d43517f 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs @@ -9,8 +9,8 @@ use core::sync::atomic::{AtomicPtr, Ordering}; use windows_enclave::vertdll::{ - EnclaveGetEnclaveInformation, ENCLAVE_FLAG_DYNAMIC_DEBUG_ACTIVE, - ENCLAVE_FLAG_DYNAMIC_DEBUG_ENABLED, ENCLAVE_FLAG_FULL_DEBUG_ENABLED, ENCLAVE_INFORMATION, + ENCLAVE_FLAG_DYNAMIC_DEBUG_ACTIVE, ENCLAVE_FLAG_DYNAMIC_DEBUG_ENABLED, + ENCLAVE_FLAG_FULL_DEBUG_ENABLED, ENCLAVE_INFORMATION, EnclaveGetEnclaveInformation, }; use super::types::UserBoundKeyError; diff --git a/rust/sdk/crates/libs/sdk-host/src/enclave.rs b/rust/sdk/crates/libs/sdk-host/src/enclave.rs new file mode 100644 index 00000000..f7767370 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-host/src/enclave.rs @@ -0,0 +1,405 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! VBS Enclave lifecycle management APIs. +//! +//! This module provides Rust equivalents of the C++ `veil::vtl0::enclave` namespace +//! from `enclave_api.vtl0.h`. It handles creating, loading, initializing, and +//! cleaning up VBS enclaves. +//! +//! # Example +//! +//! ```no_run +//! use vbsenclave_sdk_host::enclave::{EnclaveHandle, megabytes}; +//! use std::path::Path; +//! +//! // Create and initialize an enclave +//! let enclave = EnclaveHandle::create_and_initialize( +//! Path::new("my_enclave.dll"), +//! megabytes(32), +//! None, // Use default owner ID +//! )?; +//! +//! // Use enclave.as_ptr() for enclave calls +//! // Enclave is automatically terminated and deleted when dropped +//! # Ok::<(), Box>(()) +//! ``` + +use std::ffi::c_void; +use std::path::Path; +use windows::core::{Error, HRESULT, Result}; + +// FFI declarations for enclave APIs +mod ffi { + use std::ffi::c_void; + + windows::core::link!("api-ms-win-core-enclave-l1-1-0.dll" "system" fn CreateEnclave( + hprocess: *mut c_void, + lpaddress: *const c_void, + dwsize: usize, + dwinitialcommitment: usize, + flenclavetype: u32, + lpenclaveinformation: *const c_void, + dwinfolength: u32, + lpenclaveerror: *mut u32 + ) -> *mut c_void); + + windows::core::link!("api-ms-win-core-enclave-l1-1-0.dll" "system" fn LoadEnclaveImageW( + lpenclaveaddress: *const c_void, + lpimagename: *const u16 + ) -> i32); + + windows::core::link!("api-ms-win-core-enclave-l1-1-0.dll" "system" fn InitializeEnclave( + hprocess: *mut c_void, + lpaddress: *const c_void, + lpenclaveinformation: *const c_void, + dwinfolength: u32, + lpenclaveerror: *mut u32 + ) -> i32); + + windows::core::link!("api-ms-win-core-enclave-l1-1-0.dll" "system" fn TerminateEnclave( + lpaddress: *const c_void, + fwait: i32 + ) -> i32); + + windows::core::link!("api-ms-win-core-enclave-l1-1-0.dll" "system" fn DeleteEnclave( + lpaddress: *const c_void + ) -> i32); + + windows::core::link!("api-ms-win-core-enclave-l1-1-0.dll" "system" fn IsEnclaveTypeSupported( + flenclavetype: u32 + ) -> i32); +} + +/// VBS enclave type constant. +pub const ENCLAVE_TYPE_VBS: u32 = 0x10; + +/// Length of the enclave owner ID. +pub const IMAGE_ENCLAVE_LONG_ID_LENGTH: usize = 32; + +/// Default enclave size (32 MB). +pub const DEFAULT_ENCLAVE_SIZE: usize = 32 * 1024 * 1024; + +/// Enclave creation info for VBS enclaves. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct EnclaveCreateInfoVbs { + /// Flags for enclave creation. + pub flags: u32, + /// Owner ID for the enclave. + pub owner_id: [u8; IMAGE_ENCLAVE_LONG_ID_LENGTH], +} + +impl Default for EnclaveCreateInfoVbs { + fn default() -> Self { + Self { + flags: 0, + owner_id: [0u8; IMAGE_ENCLAVE_LONG_ID_LENGTH], + } + } +} + +/// Enclave initialization info for VBS enclaves. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct EnclaveInitInfoVbs { + /// Size of this structure. + pub length: u32, + /// Number of threads to create. + pub thread_count: u32, +} + +impl Default for EnclaveInitInfoVbs { + fn default() -> Self { + Self { + length: core::mem::size_of::() as u32, + thread_count: 1, + } + } +} + +/// Convert megabytes to bytes. +#[inline] +pub const fn megabytes(mb: usize) -> usize { + mb * 0x100000 +} + +/// Check if VBS enclaves are supported on this system. +pub fn is_vbs_supported() -> bool { + unsafe { ffi::IsEnclaveTypeSupported(ENCLAVE_TYPE_VBS) != 0 } +} + +/// Create a new VBS enclave. +/// +/// # Arguments +/// +/// * `size` - Size of the enclave in bytes. Use [`megabytes`] helper. +/// * `owner_id` - Optional owner ID. If None, uses zeros. +/// * `flags` - Enclave creation flags. +/// * `initial_commitment` - Initial memory commitment (usually 0). +/// +/// # Returns +/// +/// Raw pointer to the enclave on success. +/// +/// # Errors +/// +/// Returns an error if VBS is not supported or enclave creation fails. +pub fn create( + size: usize, + owner_id: Option<&[u8]>, + flags: u32, + initial_commitment: usize, +) -> Result<*mut c_void> { + if !is_vbs_supported() { + return Err(Error::new( + HRESULT(-2147024891i32), // E_ACCESSDENIED + "VBS enclave type not supported", + )); + } + + let mut create_info = EnclaveCreateInfoVbs { + flags, + owner_id: [0u8; IMAGE_ENCLAVE_LONG_ID_LENGTH], + }; + + if let Some(id) = owner_id { + let len = id.len().min(IMAGE_ENCLAVE_LONG_ID_LENGTH); + create_info.owner_id[..len].copy_from_slice(&id[..len]); + } + + let mut enclave_error: u32 = 0; + + let enclave = unsafe { + ffi::CreateEnclave( + -1isize as *mut c_void, // GetCurrentProcess() pseudo-handle + std::ptr::null(), // Let system choose address + size, + initial_commitment, + ENCLAVE_TYPE_VBS, + &create_info as *const _ as *const c_void, + core::mem::size_of::() as u32, + &mut enclave_error, + ) + }; + + if enclave.is_null() { + let last_error = unsafe { windows::Win32::Foundation::GetLastError() }; + return Err(Error::new( + HRESULT::from_win32(last_error.0), + "Failed to create VBS enclave", + )); + } + + Ok(enclave) +} + +/// Load an enclave image (DLL) into an existing enclave. +/// +/// # Arguments +/// +/// * `enclave` - Pointer to the enclave. +/// * `image_path` - Path to the enclave DLL. +/// +/// # Errors +/// +/// Returns an error if loading fails. +pub fn load_image(enclave: *mut c_void, image_path: &Path) -> Result<()> { + let wide_path: Vec = image_path + .as_os_str() + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + + let result = unsafe { ffi::LoadEnclaveImageW(enclave, wide_path.as_ptr()) }; + + if result == 0 { + let last_error = unsafe { windows::Win32::Foundation::GetLastError() }; + return Err(Error::new( + HRESULT::from_win32(last_error.0), + "Failed to load enclave image", + )); + } + + Ok(()) +} + +use std::os::windows::ffi::OsStrExt; + +/// Initialize an enclave after loading its image. +/// +/// # Arguments +/// +/// * `enclave` - Pointer to the enclave. +/// * `thread_count` - Number of threads to create (usually 1). +/// +/// # Errors +/// +/// Returns an error if initialization fails. +pub fn initialize(enclave: *mut c_void, thread_count: u32) -> Result<()> { + let init_info = EnclaveInitInfoVbs { + length: core::mem::size_of::() as u32, + thread_count, + }; + + let mut enclave_error: u32 = 0; + + let result = unsafe { + ffi::InitializeEnclave( + -1isize as *mut c_void, // GetCurrentProcess() + enclave, + &init_info as *const _ as *const c_void, + init_info.length, + &mut enclave_error, + ) + }; + + if result == 0 { + let last_error = unsafe { windows::Win32::Foundation::GetLastError() }; + return Err(Error::new( + HRESULT::from_win32(last_error.0), + "Failed to initialize enclave", + )); + } + + Ok(()) +} + +/// Terminate an enclave. +/// +/// # Arguments +/// +/// * `enclave` - Pointer to the enclave. +/// * `wait` - If true, wait for all threads to terminate. +pub fn terminate(enclave: *mut c_void, wait: bool) { + unsafe { + let _ = ffi::TerminateEnclave(enclave, if wait { 1 } else { 0 }); + } +} + +/// Delete an enclave. +/// +/// # Arguments +/// +/// * `enclave` - Pointer to the enclave. +pub fn delete(enclave: *mut c_void) { + unsafe { + let _ = ffi::DeleteEnclave(enclave); + } +} + +/// A handle to a VBS enclave that manages its lifecycle. +/// +/// When dropped, the enclave is automatically terminated and deleted. +/// This is equivalent to the C++ `unique_enclave` type. +pub struct EnclaveHandle { + enclave: *mut c_void, +} + +impl EnclaveHandle { + /// Create a new enclave handle from a raw pointer. + /// + /// # Safety + /// + /// The caller must ensure the pointer is a valid enclave pointer + /// that was created with `CreateEnclave`. + pub unsafe fn from_raw(enclave: *mut c_void) -> Self { + Self { enclave } + } + + /// Create, load, and initialize a VBS enclave in one step. + /// + /// This is the recommended way to create an enclave. It: + /// 1. Creates the enclave + /// 2. Loads the enclave DLL + /// 3. Initializes the enclave with 2 threads (needed for callback support) + /// + /// # Arguments + /// + /// * `dll_path` - Path to the enclave DLL. + /// * `size` - Size of the enclave in bytes. Use [`megabytes`] helper. + /// * `owner_id` - Optional owner ID bytes. If None, uses zeros. + /// + /// # Example + /// + /// ```no_run + /// use vbsenclave_sdk_host::enclave::{EnclaveHandle, megabytes}; + /// use std::path::Path; + /// + /// let enclave = EnclaveHandle::create_and_initialize( + /// Path::new("my_enclave.dll"), + /// megabytes(32), + /// None, + /// )?; + /// # Ok::<(), windows::core::Error>(()) + /// ``` + pub fn create_and_initialize( + dll_path: &Path, + size: usize, + owner_id: Option<&[u8]>, + ) -> Result { + // Default to 2 threads for callback support (e.g., Windows Hello attestation) + Self::create_and_initialize_with_threads(dll_path, size, owner_id, 2) + } + + /// Create, load, and initialize a VBS enclave with a specific thread count. + /// + /// Use this when you need more control over the number of enclave threads. + /// For user-bound key operations, at least 2 threads are required. + /// + /// # Arguments + /// + /// * `dll_path` - Path to the enclave DLL. + /// * `size` - Size of the enclave in bytes. Use [`megabytes`] helper. + /// * `owner_id` - Optional owner ID bytes. If None, uses zeros. + /// * `thread_count` - Number of threads to initialize. Use at least 2 for callback support. + pub fn create_and_initialize_with_threads( + dll_path: &Path, + size: usize, + owner_id: Option<&[u8]>, + thread_count: u32, + ) -> Result { + // Create the enclave + let enclave_ptr = create(size, owner_id, 0, 0)?; + + // If any subsequent step fails, we need to clean up + let handle = unsafe { Self::from_raw(enclave_ptr) }; + + // Load the enclave image + load_image(enclave_ptr, dll_path)?; + + // Initialize with specified thread count + initialize(enclave_ptr, thread_count)?; + + Ok(handle) + } + + /// Get the raw enclave pointer for use with enclave calls. + #[inline] + pub fn as_ptr(&self) -> *mut c_void { + self.enclave + } + + /// Consume this handle without running the destructor. + /// + /// The caller becomes responsible for terminating and deleting the enclave. + pub fn into_raw(self) -> *mut c_void { + let ptr = self.enclave; + std::mem::forget(self); + ptr + } +} + +impl Drop for EnclaveHandle { + fn drop(&mut self) { + if !self.enclave.is_null() { + // fWait = TRUE means wait for all threads to terminate + terminate(self.enclave, true); + delete(self.enclave); + } + } +} + +// EnclaveHandle is Send but not Sync - you can move it between threads +// but shouldn't share references across threads. +unsafe impl Send for EnclaveHandle {} diff --git a/rust/sdk/crates/libs/sdk-host/src/lib.rs b/rust/sdk/crates/libs/sdk-host/src/lib.rs index d4af10c6..6a8a12c4 100644 --- a/rust/sdk/crates/libs/sdk-host/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-host/src/lib.rs @@ -3,6 +3,8 @@ mod host_ffi; +pub mod enclave; + #[cfg(feature = "userboundkey")] pub mod userboundkey; diff --git a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs index 7c794ade..a09f3484 100644 --- a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs @@ -158,16 +158,31 @@ fn create_challenge_callback( enclave_ptr: usize, ) -> AttestationChallengeHandler { AttestationChallengeHandler::new(move |challenge: windows_core::Ref| { + println!("[SDK-Host] Challenge callback invoked!"); + // Get the IBuffer reference using ok() which returns Result<&T> let challenge_buffer: &KcmIBuffer = challenge.ok()?; // Convert KcmIBuffer to Vec let challenge_vec = kcm_buffer_to_vec(challenge_buffer)?; + println!( + "[SDK-Host] Challenge size: {} bytes, calling VTL1 for attestation report...", + challenge_vec.len() + ); + // Call into VTL1 to get attestation report let enclave_interface = UserBoundKeyVtl0Host::new(enclave_ptr as *mut core::ffi::c_void); let attestation_result = enclave_interface .userboundkey_get_attestation_report(&challenge_vec) - .map_err(|_| windows_core::Error::from(E_FAIL))?; + .map_err(|e| { + println!("[SDK-Host] VTL1 attestation call failed: {:?}", e); + windows_core::Error::from(E_FAIL) + })?; + + println!( + "[SDK-Host] Got attestation report: {} bytes", + attestation_result.report.len() + ); // Store session handle { @@ -249,19 +264,29 @@ impl Untrusted for UntrustedImpl { cacheConfig: &keyCredentialCacheConfig, keyCredentialCreationOption: u32, ) -> Result { + println!("[SDK-Host] userboundkey_establish_session_for_create called"); + println!( + "[SDK-Host] keyName={}, windowId={}, option={}", + keyName, windowId, keyCredentialCreationOption + ); + let algorithm = get_algorithm(ecdhProtocol)?; + println!("[SDK-Host] algorithm resolved"); let cache_configuration = convert_cache_config(cacheConfig).map_err(|e| AbiError::Hresult(e.code().0))?; + println!("[SDK-Host] cache config created"); let session_info = Arc::new(Mutex::new(UniqueSessionHandle::new())); let enclave_ptr = enclave as usize; // Try to delete existing key first (ignore errors) let key_name_hstring = HSTRING::from(keyName.as_str()); + println!("[SDK-Host] Deleting existing key (if any)..."); if let Ok(delete_op) = KeyCredentialManager::DeleteAsync(&key_name_hstring) { let _ = delete_op.join(); } + println!("[SDK-Host] Delete complete"); // Create the credential with VBS attestation let message_hstring = HSTRING::from(message.as_str()); @@ -270,6 +295,8 @@ impl Untrusted for UntrustedImpl { let callback = create_challenge_callback(session_info.clone(), enclave_ptr); + println!("[SDK-Host] Calling KeyCredentialManager::RequestCreateAsync2..."); + let credential_result = KeyCredentialManager::RequestCreateAsync2( &key_name_hstring, creation_option, @@ -280,9 +307,19 @@ impl Untrusted for UntrustedImpl { ChallengeResponseKind::VirtualizationBasedSecurityEnclave, &callback, ) - .map_err(|e| AbiError::Hresult(e.code().0))? - .join() - .map_err(|e| AbiError::Hresult(e.code().0))?; + .map_err(|e| { + println!("[SDK-Host] RequestCreateAsync2 failed to start: {:?}", e); + AbiError::Hresult(e.code().0) + })?; + + println!("[SDK-Host] RequestCreateAsync2 started, waiting for completion (join)..."); + + let credential_result = credential_result.join().map_err(|e| { + println!("[SDK-Host] RequestCreateAsync2.join() failed: {:?}", e); + AbiError::Hresult(e.code().0) + })?; + + println!("[SDK-Host] RequestCreateAsync2 completed!"); // Check if operation was successful let status = credential_result diff --git a/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs b/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs index 90372ad6..911f9b8c 100644 --- a/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs +++ b/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs @@ -1763,67 +1763,6 @@ impl KeyCredentialManager { .and_then(|| windows_core::Type::from_abi(result__)) }) } - pub fn RequestCreateAsync2( - name: &windows_core::HSTRING, - option: KeyCredentialCreationOption, - algorithm: &windows_core::HSTRING, - message: &windows_core::HSTRING, - cacheconfiguration: P4, - windowid: WindowId, - callbacktype: ChallengeResponseKind, - attestationcallback: P7, - ) -> windows_core::Result> - where - P4: windows_core::Param, - P7: windows_core::Param, - { - Self::IKeyCredentialManagerStatics2(|this| unsafe { - let mut result__ = core::mem::zeroed(); - (windows_core::Interface::vtable(this).RequestCreateAsync)( - windows_core::Interface::as_raw(this), - core::mem::transmute_copy(name), - option, - core::mem::transmute_copy(algorithm), - core::mem::transmute_copy(message), - cacheconfiguration.param().abi(), - windowid, - callbacktype, - attestationcallback.param().abi(), - &mut result__, - ) - .and_then(|| windows_core::Type::from_abi(result__)) - }) - } - pub fn OpenAsync2( - name: &windows_core::HSTRING, - callbacktype: ChallengeResponseKind, - attestationcallback: P2, - ) -> windows_core::Result> - where - P2: windows_core::Param, - { - Self::IKeyCredentialManagerStatics2(|this| unsafe { - let mut result__ = core::mem::zeroed(); - (windows_core::Interface::vtable(this).OpenAsync)( - windows_core::Interface::as_raw(this), - core::mem::transmute_copy(name), - callbacktype, - attestationcallback.param().abi(), - &mut result__, - ) - .and_then(|| windows_core::Type::from_abi(result__)) - }) - } - pub fn GetSecureId() -> windows_core::Result { - Self::IKeyCredentialManagerStatics2(|this| unsafe { - let mut result__ = core::mem::zeroed(); - (windows_core::Interface::vtable(this).GetSecureId)( - windows_core::Interface::as_raw(this), - &mut result__, - ) - .and_then(|| windows_core::Type::from_abi(result__)) - }) - } fn IKeyCredentialManagerStatics< R, F: FnOnce(&IKeyCredentialManagerStatics) -> windows_core::Result, @@ -1836,18 +1775,6 @@ impl KeyCredentialManager { > = windows_core::imp::FactoryCache::new(); SHARED.call(callback) } - fn IKeyCredentialManagerStatics2< - R, - F: FnOnce(&IKeyCredentialManagerStatics2) -> windows_core::Result, - >( - callback: F, - ) -> windows_core::Result { - static SHARED: windows_core::imp::FactoryCache< - KeyCredentialManager, - IKeyCredentialManagerStatics2, - > = windows_core::imp::FactoryCache::new(); - SHARED.call(callback) - } } impl windows_core::RuntimeName for KeyCredentialManager { const NAME: &'static str = "Windows.Security.Credentials.KeyCredentialManager"; @@ -1953,6 +1880,7 @@ impl KeyCredentialStatus { pub const UserPrefersPassword: Self = Self(4i32); pub const CredentialAlreadyExists: Self = Self(5i32); pub const SecurityDeviceLocked: Self = Self(6i32); + pub const AlgorithmNotSupported: Self = Self(7i32); } impl windows_core::TypeKind for KeyCredentialStatus { type TypeKind = windows_core::CopyType; diff --git a/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs b/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs index 54cc26d2..402716c5 100644 --- a/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs +++ b/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs @@ -4,14 +4,81 @@ mod bindings; pub use bindings::*; -use windows_core::{Interface, HSTRING, Param}; +use windows_core::{HSTRING, Interface, Param}; use windows_future::IAsyncOperation; -// Extension constant for KeyCredentialStatus -#[allow(non_upper_case_globals)] -impl KeyCredentialStatus { - /// Algorithm is not supported - pub const AlgorithmNotSupported: Self = Self(7i32); +/// Extension methods for KeyCredentialManager to access IKeyCredentialManagerStatics2 APIs. +#[allow(non_snake_case)] +impl KeyCredentialManager { + /// Creates a key credential with VBS attestation support. + pub fn RequestCreateAsync2( + name: &HSTRING, + option: KeyCredentialCreationOption, + algorithm: &HSTRING, + message: &HSTRING, + cache_configuration: &KeyCredentialCacheConfiguration, + window_id: WindowId, + callback_type: ChallengeResponseKind, + attestation_callback: &AttestationChallengeHandler, + ) -> windows_core::Result> { + Self::IKeyCredentialManagerStatics2(|this| unsafe { + let mut result__ = core::mem::zeroed(); + (Interface::vtable(this).RequestCreateAsync)( + Interface::as_raw(this), + core::mem::transmute_copy(name), + option, + core::mem::transmute_copy(algorithm), + core::mem::transmute_copy(message), + core::mem::transmute_copy(cache_configuration), + window_id, + callback_type, + core::mem::transmute_copy(attestation_callback), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + }) + } + + /// Opens an existing key credential with VBS attestation support. + pub fn OpenAsync2( + name: &HSTRING, + callback_type: ChallengeResponseKind, + attestation_callback: &AttestationChallengeHandler, + ) -> windows_core::Result> { + Self::IKeyCredentialManagerStatics2(|this| unsafe { + let mut result__ = core::mem::zeroed(); + (Interface::vtable(this).OpenAsync)( + Interface::as_raw(this), + core::mem::transmute_copy(name), + callback_type, + core::mem::transmute_copy(attestation_callback), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + }) + } + + /// Gets the secure identifier for the credential manager. + pub fn GetSecureId() -> windows_core::Result { + Self::IKeyCredentialManagerStatics2(|this| unsafe { + let mut result__ = core::mem::zeroed(); + (Interface::vtable(this).GetSecureId)(Interface::as_raw(this), &mut result__) + .and_then(|| windows_core::Type::from_abi(result__)) + }) + } + + fn IKeyCredentialManagerStatics2< + R, + F: FnOnce(&IKeyCredentialManagerStatics2) -> windows_core::Result, + >( + callback: F, + ) -> windows_core::Result { + static SHARED: windows_core::imp::FactoryCache< + KeyCredentialManager, + IKeyCredentialManagerStatics2, + > = windows_core::imp::FactoryCache::new(); + SHARED.call(callback) + } } /// Extension methods for KeyCredential to access IKeyCredential2 APIs. diff --git a/rust/sdk/crates/samples/userboundkey/README.md b/rust/sdk/crates/samples/userboundkey/README.md new file mode 100644 index 00000000..e2664a47 --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/README.md @@ -0,0 +1,210 @@ +# User-Bound Key Sample + +This sample demonstrates how to use the VBS Enclave SDK's user-bound key functionality +to create Windows Hello-protected encryption keys that are sealed to the enclave. + +## Overview + +The sample consists of: + +- **`userboundkey_sample.edl`** - EDL interface defining the sample's trusted functions +- **`enclave/`** - Enclave DLL that implements the encryption/decryption logic +- **`host/`** - Host application with a menu-driven interface + +## Features + +1. **Create User-Bound Key** - Creates a new encryption key protected by Windows Hello + and sealed to the enclave using VBS enclave sealing. + +2. **Load User-Bound Key** - Loads an existing sealed key (triggers Windows Hello prompt). + +3. **Delete User-Bound Key** - Removes the sealed key file. + +4. **Encrypt Data** - Loads the key and encrypts user-provided text. + +5. **Decrypt Data** - Loads the key and decrypts previously encrypted data. + +## Security Model + +- The encryption key is generated inside the VTL1 enclave +- The key is sealed using VBS enclave sealing (bound to enclave identity) +- Windows Hello biometric/PIN is required to access the key +- The cache configuration is set securely in VTL1 (VTL0 cannot influence it) +- Key material never leaves the enclave in plaintext + +## Building + +### Prerequisites + +Building the enclave DLL requires: +- Windows SDK with VBS enclave libraries (`ucrt_enclave`) +- SDK versions 10.0.19041.0, 10.0.22621.0, or 10.0.26100.0 are known to work +- Visual Studio 2022 with MSVC enclave toolchain +- A code signing certificate for VBS enclaves (see below) + +To verify your SDK has the required libraries: +```powershell +Test-Path "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\ucrt_enclave" +``` + +### Quick Build (Recommended) + +Use the provided build script that generates EDL bindings, builds, and signs in one step: + +```powershell +cd C:\VbsEnclaveTooling\rust\sdk\crates\samples\userboundkey + +# Build with your signing certificate +.\build.ps1 -CertName "YourCertificateName" + +# For release build +.\build.ps1 -CertName "YourCertificateName" -Configuration Release +``` + +The script performs: +1. **Generate EDL bindings** - Creates Rust bindings from the EDL file +2. **Build** - Compiles both host and enclave +3. **Sign** - Signs the enclave DLL with veiid.exe and signtool + +### Manual Build Commands + +If you prefer manual steps: + +```powershell +# From the rust/sdk directory + +# Build host application only (always works) +cargo build -p userboundkey-sample-host + +# Build enclave (requires proper Windows SDK with ucrt_enclave) +cargo build -p userboundkey-sample-enclave + +# Sign the enclave (required before running) +..\..\scripts\sign-enclave.ps1 -DllPath "target\debug\userboundkey_sample_enclave.dll" -CertName "YourCertificateName" + +# Check compilation without linking (useful for CI) +cargo check --all-features +``` + +### Creating a Test Signing Certificate + +To create a self-signed test certificate for development: + +```powershell +# Create a self-signed certificate (run as Administrator) +$cert = New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=MyTestEnclaveCert" -CertStoreLocation Cert:\CurrentUser\My +``` + +## Running + +### Prerequisites for Running + +1. **Windows 11** with VBS (Virtualization Based Security) enabled +2. **Windows Hello** configured (PIN, fingerprint, or facial recognition) +3. **Developer Mode** enabled (optional, for debug enclaves) + +To verify VBS is enabled: +```powershell +# Check VBS status +Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard | Select-Object VirtualizationBasedSecurityStatus +# 2 = Running +``` + +### Steps to Run + +1. **Build the sample using the build script:** + ```powershell + cd C:\VbsEnclaveTooling\rust\sdk\crates\samples\userboundkey + .\build.ps1 -CertName "YourCertificateName" + ``` + +2. **Run the sample:** + ```powershell + cd C:\VbsEnclaveTooling\rust\sdk\target\debug + .\userboundkey-sample.exe + ``` + +3. **Use the interactive menu:** + ``` + === User-Bound Key Sample (Rust) === + + *** User-Bound Key Management and Encryption Menu *** + 1. Create UB Key <- First, create a new user-bound key + 2. Load UB Key <- Load existing key from file + 3. Delete UB Key <- Delete the key file + 4. Encrypt Data <- Encrypt text (requires key) + 5. Decrypt Data <- Decrypt data (requires key) + 6. Exit + ``` + +### Typical Workflow + +1. **Select option 1** to create a new user-bound key + - Windows Hello prompt will appear (PIN/biometric) + - Key is created and saved to `MyEncryptionKey-001` + +2. **Select option 4** to encrypt data + - Enter text when prompted + - Windows Hello prompt may appear to unlock the key + - Encrypted data saved to `encrypted_userbound` + +3. **Select option 5** to decrypt data + - Windows Hello prompt may appear + - Decrypted text is displayed + +### Troubleshooting + +**"Failed to load enclave"** +- Ensure VBS is enabled in Windows settings +- Check that the enclave DLL exists next to the host executable +- Try running as Administrator + +**"Windows Hello prompt does not appear"** +- Ensure Windows Hello is configured in Settings > Accounts > Sign-in options +- Try creating a new key (option 1) + +**"Key needs resealing"** +- This is normal after enclave updates +- The sample automatically handles resealing + +## Files Created + +When running the sample, the following files are created in the current directory: + +- `userboundkey.sealed` - The sealed encryption key +- `encrypted_userbound.bin` - Encrypted data output + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Host Application (VTL0) │ +│ - Menu interface │ +│ - File I/O for sealed keys and encrypted data │ +│ - Calls enclave via EDL interface │ +└─────────────────────────────────────────────────────────────┘ + │ + │ EDL Interface + │ (CreateUserBoundKey, LoadAndEncrypt, LoadAndDecrypt) + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Enclave DLL (VTL1) │ +│ - Creates/loads user-bound keys via SDK │ +│ - Performs encryption/decryption │ +│ - Manages key caching │ +└─────────────────────────────────────────────────────────────┘ + │ + │ SDK userboundkey module + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Windows Hello / KCM (VTL0) │ +│ - Biometric/PIN authentication │ +│ - Key Credential Manager │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Key Reseal + +If the enclave's sealing key has changed (e.g., after an enclave update), the SDK +will automatically detect this and provide resealed key bytes. The sample handles +this by saving the resealed key back to disk. diff --git a/rust/sdk/crates/samples/userboundkey/build.ps1 b/rust/sdk/crates/samples/userboundkey/build.ps1 new file mode 100644 index 00000000..e6b9423d --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/build.ps1 @@ -0,0 +1,88 @@ +# Builds and signs the userboundkey sample. +# +# Usage: +# .\build.ps1 -CertName "MyTestEnclaveCertNew" +# .\build.ps1 -CertName "MyTestEnclaveCertNew" -Configuration Release + +param( + [Parameter(Mandatory=$true)] + [string]$CertName, + + [ValidateSet("Debug", "Release")] + [string]$Configuration = "Debug" +) + +$ErrorActionPreference = "Stop" + +$sampleDir = $PSScriptRoot +$sdkRoot = Resolve-Path "$sampleDir\..\..\..\" +$rustRoot = Resolve-Path "$sdkRoot\.." +$scriptsDir = Join-Path $rustRoot "scripts" + +Write-Host "`n=== User-Bound Key Sample Build ===" -ForegroundColor Cyan +Write-Host "Configuration: $Configuration" +Write-Host "Certificate: $CertName" + +# ============================================================================ +# Step 1: Generate EDL bindings +# ============================================================================ +Write-Host "`n[1/3] Generating EDL bindings..." -ForegroundColor Yellow + +$generateScript = Join-Path $scriptsDir "generate_codegen_crates.ps1" +$sampleEdl = Join-Path $sampleDir "userboundkey_sample.edl" +$libsImportDir = Join-Path $sdkRoot "crates\libs" + +& $generateScript ` + -HostAppOutDir "$sampleDir\host\generated" ` + -EnclaveOutDir "$sampleDir\enclave\generated" ` + -EdlPath $sampleEdl ` + -Namespace "userboundkey_sample" ` + -Vtl0ClassName "userboundkey_sampleWrapper" ` + -ImportDirectories $libsImportDir + +if ($LASTEXITCODE -ne 0) { throw "EDL generation failed." } +Write-Host "EDL bindings generated." -ForegroundColor Green + +# ============================================================================ +# Step 2: Build host and enclave +# ============================================================================ +Write-Host "`n[2/3] Building..." -ForegroundColor Yellow + +Push-Location $sdkRoot +try { + $buildArgs = @("build", "-p", "userboundkey-sample-host", "-p", "userboundkey-sample-enclave") + if ($Configuration -eq "Release") { + $buildArgs += "--release" + } + + cargo @buildArgs + + if ($LASTEXITCODE -ne 0) { throw "Cargo build failed." } +} finally { + Pop-Location +} +Write-Host "Build completed." -ForegroundColor Green + +# ============================================================================ +# Step 3: Sign the enclave +# ============================================================================ +Write-Host "`n[3/3] Signing enclave..." -ForegroundColor Yellow + +$targetDir = if ($Configuration -eq "Release") { "release" } else { "debug" } +$enclaveDll = Join-Path $sdkRoot "target\$targetDir\userboundkey_sample_enclave.dll" +$signScript = Join-Path $scriptsDir "sign-enclave.ps1" + +& $signScript -DllPath $enclaveDll -CertName $CertName + +if ($LASTEXITCODE -ne 0) { throw "Signing failed." } +Write-Host "Enclave signed." -ForegroundColor Green + +# ============================================================================ +# Done +# ============================================================================ +Write-Host "`n=== Build Complete ===" -ForegroundColor Cyan +Write-Host "Output: $(Join-Path $sdkRoot "target\$targetDir")" +Write-Host "" +Write-Host "To run:" +Write-Host " cd $(Join-Path $sdkRoot "target\$targetDir")" +Write-Host " .\userboundkey-sample.exe" diff --git a/rust/sdk/crates/samples/userboundkey/enclave/.cargo/config.toml b/rust/sdk/crates/samples/userboundkey/enclave/.cargo/config.toml new file mode 100644 index 00000000..7cb9a9a8 --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/enclave/.cargo/config.toml @@ -0,0 +1,5 @@ +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" diff --git a/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml b/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml new file mode 100644 index 00000000..9dfbdfed --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "userboundkey-sample-enclave" +version = "0.1.0" +edition = { workspace = true } + +[lib] +# Make sure crate builds a dll and not a static lib +crate-type = ["cdylib"] + +[build-dependencies] +edlcodegen-tools = { workspace = true } + +[dependencies] +# Generated code from userboundkey_sample.edl +userboundkey_sample_enclave_gen = { path = "generated/userboundkey_sample_gen" } +# The SDK provides the userboundkey implementation +vbsenclave-sdk-enclave = { workspace = true, features = ["userboundkey"] } diff --git a/rust/sdk/crates/samples/userboundkey/enclave/build.rs b/rust/sdk/crates/samples/userboundkey/enclave/build.rs new file mode 100644 index 00000000..093c74dd --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/enclave/build.rs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +fn main() -> Result<(), Box> { + // Emits the linker arguments needed to build this crate as a VBS enclave. + edlcodegen_tools::set_enclave_linker_flags(); + edlcodegen_tools::link_win_sdk_enclave_libs()?; + Ok(()) +} diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs new file mode 100644 index 00000000..1f9e357a --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs @@ -0,0 +1,347 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Enclave implementation for the user-bound key sample. +//! Functionally equivalent to C++ SampleEnclave/UserBound/userbound_keys.cpp + +#![allow(unused_imports)] +#![allow(non_snake_case)] + +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; +use userboundkey_sample_enclave_gen::AbiError; +use userboundkey_sample_enclave_gen::implementation::trusted::Trusted; +use userboundkey_sample_enclave_gen::implementation::types::edl::WString; +use userboundkey_sample_enclave_gen::implementation::types::{DecryptResult, EncryptResult}; +use userboundkey_sample_enclave_gen::stubs::untrusted::debug_print; + +// Import SDK functions and types +use vbsenclave_sdk_enclave::userboundkey::{ + EnclaveSealingIdentityPolicy, SymmetricKeyHandle, TAG_SIZE, ZERO_NONCE, create_user_bound_key, + decrypt, encrypt, keyCredentialCacheConfig, load_user_bound_key, reseal_user_bound_key, +}; + +/// Runtime policy: no dynamic debug allowed (matches C++ g_runtimePolicy = 0) +const RUNTIME_POLICY_NO_DEBUG: u32 = 0; + +/// VTL1 function to create secure cache configuration. +/// This ensures VTL0 has no influence over cache configuration values. +/// Matches C++ CreateSecureKeyCredentialCacheConfig() +fn create_secure_cache_config() -> keyCredentialCacheConfig { + keyCredentialCacheConfig { + cacheOption: 0, // NoCache - most secure option + cacheTimeoutInSeconds: 0, // No timeout when not caching + cacheUsageCount: 0, // No usage count when not caching + } +} + +/// Global encryption key storage (matches C++ g_encryptionKey pattern) +/// In no_std enclave, we use a static mut with unsafe access +static mut ENCRYPTION_KEY: Option = None; + +#[allow(static_mut_refs)] +fn is_ubk_loaded() -> bool { + unsafe { ENCRYPTION_KEY.is_some() } +} + +#[allow(static_mut_refs)] +fn set_encryption_key(key: SymmetricKeyHandle) { + unsafe { + ENCRYPTION_KEY = Some(key); + } +} + +#[allow(static_mut_refs)] +fn get_encryption_key() -> Option<&'static SymmetricKeyHandle> { + unsafe { ENCRYPTION_KEY.as_ref() } +} + +/// Helper function to convert WString to String for SDK calls +fn wstring_to_string(ws: &WString) -> alloc::string::String { + // WString stores UTF-16 in wchars field, convert to UTF-8 String + alloc::string::String::from_utf16_lossy(&ws.wchars) +} + +/// Helper function to ensure user-bound key is loaded. +/// Handles initial load attempt and optional reseal if needed. +/// Matches C++ EnsureUserBoundKeyLoaded() +fn ensure_user_bound_key_loaded( + hello_key_name: &str, + pin_message: &str, + window_id: u64, + secured_encryption_key_bytes: &[u8], + needs_reseal: &mut bool, + resealed_encryption_key_bytes: &mut Vec, +) -> Result<(), AbiError> { + // Only load if not already loaded (matches C++ IsUBKLoaded check) + if is_ubk_loaded() { + return Ok(()); + } + + let secure_config = create_secure_cache_config(); + #[allow(unused_assignments)] + let mut loaded_key_bytes: Vec = Vec::new(); + #[allow(unused_assignments)] + let mut load_succeeded = false; + + // First attempt to load the user-bound key + match load_user_bound_key( + hello_key_name, + &secure_config, + pin_message, + window_id, + secured_encryption_key_bytes, + ) { + Ok((key_bytes, stale)) => { + loaded_key_bytes = key_bytes; + load_succeeded = true; + + // If stale, mark for reseal + if stale { + *needs_reseal = true; + if let Ok(resealed) = reseal_user_bound_key( + &loaded_key_bytes, + EnclaveSealingIdentityPolicy::SealToExactCode, + RUNTIME_POLICY_NO_DEBUG, + ) { + *resealed_encryption_key_bytes = resealed; + } + } + } + Err(e) => { + // First load failed, attempt reseal and retry + match reseal_user_bound_key( + secured_encryption_key_bytes, + EnclaveSealingIdentityPolicy::SealToExactCode, + RUNTIME_POLICY_NO_DEBUG, + ) { + Ok(resealed_bytes) => { + *needs_reseal = true; + *resealed_encryption_key_bytes = resealed_bytes.clone(); + + // Retry loading with resealed bytes + match load_user_bound_key( + hello_key_name, + &secure_config, + pin_message, + window_id, + &resealed_bytes, + ) { + Ok((key_bytes, _)) => { + loaded_key_bytes = key_bytes; + load_succeeded = true; + } + Err(e2) => { + return Err(AbiError::Hresult(e2.to_hresult())); + } + } + } + Err(_) => { + return Err(AbiError::Hresult(e.to_hresult())); + } + } + } + } + + if !load_succeeded { + return Err(AbiError::Hresult(-2147024809)); // E_INVALIDARG + } + + // Create symmetric key from loaded raw key material + // Matches C++: auto newEncryptionKey = veil::vtl1::crypto::create_symmetric_key(loadedKeyBytes); + let symmetric_key = SymmetricKeyHandle::from_bytes(&loaded_key_bytes) + .map_err(|e| AbiError::Hresult(e.to_hresult()))?; + set_encryption_key(symmetric_key); + + Ok(()) +} + +/// Enclave implementation of the Trusted trait +pub struct EnclaveImpl; + +impl Trusted for EnclaveImpl { + /// Create a user-bound key. + /// Matches C++ MyEnclaveCreateUserBoundKey() + fn CreateUserBoundKey( + helloKeyName: &WString, + pinMessage: &WString, + windowId: u64, + keyCredentialCreationOption: u32, + ) -> Result, AbiError> { + let _ = debug_print(&String::from("CreateUserBoundKey: Starting")); + + let cache_config = create_secure_cache_config(); + let key_name = wstring_to_string(helloKeyName); + let pin_msg = wstring_to_string(pinMessage); + + let _ = debug_print(&format!( + "CreateUserBoundKey: key_name={}, windowId={}, option={}", + key_name, windowId, keyCredentialCreationOption + )); + + let _ = debug_print(&String::from( + "CreateUserBoundKey: Calling SDK create_user_bound_key...", + )); + + // Create user-bound key with enclave sealing + // Matches C++: veil::vtl1::userboundkey::create_user_bound_key(...) + let sealed_key = create_user_bound_key( + &key_name, + &cache_config, + &pin_msg, + windowId, + EnclaveSealingIdentityPolicy::SealToExactCode, + RUNTIME_POLICY_NO_DEBUG, + keyCredentialCreationOption, + ) + .map_err(|e| { + let _ = debug_print(&format!("CreateUserBoundKey: SDK error: {:?}", e)); + AbiError::Hresult(e.to_hresult()) + })?; + + let _ = debug_print(&format!( + "CreateUserBoundKey: Success! Got {} sealed bytes", + sealed_key.len() + )); + + // Return sealed key bytes - do NOT create symmetric key here + // Matches C++ comment: "Do NOT try to create a symmetric key here" + Ok(sealed_key) + } + + /// Load user-bound key and encrypt data. + /// Matches C++ MyEnclaveLoadUserBoundKeyAndEncryptData() + fn LoadUserBoundKeyAndEncryptData( + helloKeyName: &WString, + pinMessage: &WString, + windowId: u64, + securedEncryptionKeyBytes: &Vec, + inputData: &WString, + ) -> Result { + let key_name = wstring_to_string(helloKeyName); + let pin_msg = wstring_to_string(pinMessage); + let mut needs_reseal = false; + let mut resealed_bytes = Vec::new(); + + // Ensure key is loaded (handles reseal if needed) + ensure_user_bound_key_loaded( + &key_name, + &pin_msg, + windowId, + securedEncryptionKeyBytes, + &mut needs_reseal, + &mut resealed_bytes, + )?; + + // Get the key and encrypt + let key = get_encryption_key().ok_or(AbiError::Hresult(-2147024809))?; + + // Convert input to bytes (wchar_t / UTF-16 LE data like C++) + // Matches C++: veil::vtl1::as_data_span(inputData.c_str()) + let input_bytes: Vec = inputData + .wchars + .iter() + .flat_map(|c| c.to_le_bytes()) + .collect(); + + // Encrypt using AES-GCM with zero nonce + // Matches C++: veil::vtl1::crypto::encrypt(keyHandle, ..., veil::vtl1::crypto::zero_nonce) + let (encrypted, tag) = encrypt(key, &input_bytes, &ZERO_NONCE) + .map_err(|e| AbiError::Hresult(e.to_hresult()))?; + + // Combine tag and encrypted data into single output + // Format: [tag_size (4 bytes)][tag_data][encrypted_data] + // Matches C++ format exactly + let tag_size = tag.len() as u32; + let mut combined = Vec::with_capacity(4 + tag.len() + encrypted.len()); + combined.extend_from_slice(&tag_size.to_le_bytes()); + combined.extend_from_slice(&tag); + combined.extend(encrypted); + + Ok(EncryptResult { + combinedOutputData: combined, + needsReseal: needs_reseal, + resealedEncryptionKeyBytes: resealed_bytes, + }) + } + + /// Load user-bound key and decrypt data. + /// Matches C++ MyEnclaveLoadUserBoundKeyAndDecryptData() + fn LoadUserBoundKeyAndDecryptData( + helloKeyName: &WString, + pinMessage: &WString, + windowId: u64, + securedEncryptionKeyBytes: &Vec, + combinedInputData: &Vec, + ) -> Result { + let key_name = wstring_to_string(helloKeyName); + let pin_msg = wstring_to_string(pinMessage); + let mut needs_reseal = false; + let mut resealed_bytes = Vec::new(); + + // Parse combined input: [tag_size (4 bytes)][tag_data][encrypted_data] + // Matches C++ parsing logic + if combinedInputData.len() < 4 { + return Err(AbiError::Hresult(-2147024809)); // E_INVALIDARG + } + + // Read tag size from first 4 bytes + let tag_size = u32::from_le_bytes([ + combinedInputData[0], + combinedInputData[1], + combinedInputData[2], + combinedInputData[3], + ]) as usize; + + // Validate tag size + if tag_size == 0 || tag_size > combinedInputData.len() - 4 { + return Err(AbiError::Hresult(-2147024809)); // E_INVALIDARG + } + + // Extract tag and encrypted data + let tag = &combinedInputData[4..4 + tag_size]; + let encrypted = &combinedInputData[4 + tag_size..]; + + // Ensure key is loaded + ensure_user_bound_key_loaded( + &key_name, + &pin_msg, + windowId, + securedEncryptionKeyBytes, + &mut needs_reseal, + &mut resealed_bytes, + )?; + + // Get the key and decrypt + let key = get_encryption_key().ok_or(AbiError::Hresult(-2147024809))?; + + // Convert tag slice to fixed-size array + let mut tag_array = [0u8; TAG_SIZE]; + if tag.len() != TAG_SIZE { + return Err(AbiError::Hresult(-2147024809)); // E_INVALIDARG + } + tag_array.copy_from_slice(tag); + + // Decrypt using AES-GCM with zero nonce + // Matches C++: veil::vtl1::crypto::decrypt(keyHandle, ..., veil::vtl1::crypto::zero_nonce, tag) + let decrypted_bytes = decrypt(key, encrypted, &ZERO_NONCE, &tag_array) + .map_err(|e| AbiError::Hresult(e.to_hresult()))?; + + // Convert decrypted bytes to WString (UTF-16 LE) + // Matches C++: veil::vtl1::to_wstring(decryptedBytes) + let utf16: Vec = decrypted_bytes + .chunks_exact(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect(); + + // Create WString from UTF-16 data + let decrypted_string = WString { wchars: utf16 }; + + Ok(DecryptResult { + decryptedData: decrypted_string, + needsReseal: needs_reseal, + resealedEncryptionKeyBytes: resealed_bytes, + }) + } +} diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/enclave_constants.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/enclave_constants.rs new file mode 100644 index 00000000..bdcce213 --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/enclave_constants.rs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Temporary: The contents of this file should be provided by the future +// windows-enclave crate. For now, we define the necessary constants and +// structs here. + +#[allow(dead_code)] // debug policy not used in release builds +pub const IMAGE_ENCLAVE_POLICY_DEBUGGABLE: u32 = 0x0000_0001; +pub const IMAGE_ENCLAVE_SHORT_ID_LENGTH: usize = 16; +pub const IMAGE_ENCLAVE_FLAG_PRIMARY_IMAGE: u32 = 0x0000_0001; + +// See: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_enclave_config64 +#[repr(C)] +pub struct ImageEnclaveConfig { + pub size: u32, + pub minimum_required_config_size: u32, + pub policy_flags: u32, + pub number_of_imports: u32, + pub import_list: u32, + pub import_entry_size: u32, + pub family_id: [u8; IMAGE_ENCLAVE_SHORT_ID_LENGTH], + pub image_id: [u8; IMAGE_ENCLAVE_SHORT_ID_LENGTH], + pub image_version: u32, + pub security_version: u32, + pub enclave_size: usize, + pub number_of_threads: u32, + pub enclave_flags: u32, +} + +pub const IMAGE_ENCLAVE_MINIMUM_CONFIG_SIZE: u32 = + core::mem::offset_of!(ImageEnclaveConfig, enclave_flags) as u32; diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs new file mode 100644 index 00000000..fab03a79 --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! VBS Enclave for User-Bound Key Sample +//! +//! This enclave demonstrates how to use the VBS enclave SDK's user-bound key +//! functionality with Windows Hello protection. + +#![no_std] +extern crate alloc; + +mod edl_impls; +mod enclave_constants; + +use core::panic::PanicInfo; +use enclave_constants::*; + +// Panic handler for no_std environment +#[panic_handler] +fn panic(_panic: &PanicInfo<'_>) -> ! { + loop {} +} + +// Export the sample's enclave functions (CreateUserBoundKey, LoadUserBoundKeyAndEncryptData, etc.) +userboundkey_sample_enclave_gen::export_enclave_functions!(edl_impls::EnclaveImpl); + +// Export the SDK's userboundkey enclave functions in a separate module to avoid naming conflicts. +// This is necessary because: +// 1. The SDK's userboundkey module (create_user_bound_key, load_user_bound_key, etc.) +// internally calls VTL0 through the userboundkey.edl untrusted functions. +// 2. The host needs to register those callbacks, which requires the +// __AbiRegisterVtl0Callbacks_userboundkey__ export. +// 3. The SDK also exports trusted functions (get_attestation_report, close_session) +// that are called by the Windows Hello attestation flow. +mod sdk_exports { + vbsenclave_sdk_enclave::userboundkey_enclave_gen::export_enclave_functions!( + vbsenclave_sdk_enclave::userboundkey::TrustedImpl + ); +} + +/// Enclave policy flags - enable debugging in debug builds +pub const ENCLAVE_CONFIG_POLICY_FLAGS: u32 = { + #[cfg(debug_assertions)] + { + IMAGE_ENCLAVE_POLICY_DEBUGGABLE + } + #[cfg(not(debug_assertions))] + { + 0 + } +}; + +/// Enclave configuration structure required for VBS enclave loading. +/// This must be present and correctly configured for the enclave to load. +#[unsafe(no_mangle)] +#[allow(non_upper_case_globals)] +pub static __enclave_config: ImageEnclaveConfig = ImageEnclaveConfig { + size: core::mem::size_of::() as u32, + minimum_required_config_size: IMAGE_ENCLAVE_MINIMUM_CONFIG_SIZE, + policy_flags: ENCLAVE_CONFIG_POLICY_FLAGS, + number_of_imports: 0, + import_list: 0, + import_entry_size: 0, + // Family ID - unique to your application family + family_id: [0xFE, 0xFE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + // Image ID - unique to this specific enclave + image_id: [ + 0x55, 0x42, 0x4B, 0x53, // "UBKS" - User Bound Key Sample + 0x00, 0x01, 0x00, 0x00, // Version 0.1.0.0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + image_version: 1, + security_version: 1, + enclave_size: 0x2000_0000, // 512 MB - must match host's create request + number_of_threads: 16, + enclave_flags: IMAGE_ENCLAVE_FLAG_PRIMARY_IMAGE, +}; + +/// DLL entry point - required for the enclave DLL. +#[unsafe(no_mangle)] +#[allow(non_snake_case)] +pub extern "system" fn DllMain( + _dll_handle: *const core::ffi::c_void, + _reason: u32, + _reserved: *mut core::ffi::c_void, +) -> bool { + true +} diff --git a/rust/sdk/crates/samples/userboundkey/host/Cargo.toml b/rust/sdk/crates/samples/userboundkey/host/Cargo.toml new file mode 100644 index 00000000..01295361 --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/host/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "userboundkey-sample-host" +version = "0.1.0" +edition = { workspace = true } + +[[bin]] +name = "userboundkey-sample" +path = "src/main.rs" + +[dependencies] +# Generated code from userboundkey_sample.edl +userboundkey_sample_host_gen = { path = "generated/userboundkey_sample_gen" } +# The SDK provides the enclave loading and VTL0 userboundkey callbacks +vbsenclave-sdk-host = { workspace = true, features = ["userboundkey"] } +# Windows APIs +windows = { workspace = true, features = ["Win32_Foundation", "Security_Cryptography", "Storage_Streams", "Win32_UI_WindowsAndMessaging"] } diff --git a/rust/sdk/crates/samples/userboundkey/host/src/edl_impls.rs b/rust/sdk/crates/samples/userboundkey/host/src/edl_impls.rs new file mode 100644 index 00000000..60f6630d --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/host/src/edl_impls.rs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Host-side implementation of EDL untrusted functions. + +use userboundkey_sample_host_gen::AbiError; +use userboundkey_sample_host_gen::implementation::untrusted::Untrusted; + +/// Implementation of the Untrusted trait for the sample +pub struct UntrustedImpl; + +impl Untrusted for UntrustedImpl { + /// Debug print function - prints to console from enclave + fn debug_print(message: &String) -> Result<(), AbiError> { + println!("[Enclave] {}", message); + Ok(()) + } +} diff --git a/rust/sdk/crates/samples/userboundkey/host/src/main.rs b/rust/sdk/crates/samples/userboundkey/host/src/main.rs new file mode 100644 index 00000000..22a5da46 --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/host/src/main.rs @@ -0,0 +1,375 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! User-Bound Key Sample Host Application +//! Functionally equivalent to C++ SampleApps/HostAppUserBound/main.cpp + +#![allow(dead_code)] + +mod edl_impls; + +use std::fs; +use std::io::{self, Write}; +use std::path::{Path, PathBuf}; + +// VBS Enclave SDK for loading enclaves +use vbsenclave_sdk_host::KeyCredentialManager; +use vbsenclave_sdk_host::enclave::{EnclaveHandle, megabytes}; +// SDK's userboundkey module for VTL0 callbacks +use vbsenclave_sdk_host::userboundkey::{UntrustedImpl as SdkUntrustedImpl, UserBoundKeyVtl0Host}; + +// Windows APIs for buffer conversion +use windows::Security::Cryptography::CryptographicBuffer; +use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow; + +// Generated host stubs for enclave calls +use userboundkey_sample_host_gen::implementation::types::edl::WString; +use userboundkey_sample_host_gen::stubs::trusted::userboundkey_sampleWrapper; + +const KEY_NAME: &str = "MyEncryptionKey-001"; +const PIN_MESSAGE: &str = "User-Bound Key Sample"; + +fn get_key_file_path() -> PathBuf { + std::env::current_dir().unwrap().join(KEY_NAME) +} + +fn get_encrypted_file_path() -> PathBuf { + std::env::current_dir().unwrap().join("encrypted_userbound") +} + +fn save_binary_data(path: &PathBuf, data: &[u8]) -> io::Result<()> { + fs::write(path, data) +} + +fn load_binary_data(path: &PathBuf) -> io::Result> { + fs::read(path) +} + +fn print_menu(is_key_loaded: bool) { + println!("\n*** User-Bound Key Management and Encryption Menu ***"); + println!("1. Create UB Key"); + println!("2. Load UB Key"); + println!("3. Delete UB Key"); + println!("4. Encrypt Data"); + println!("5. Decrypt Data"); + println!("6. Exit"); + println!( + "Key Status: {}", + if is_key_loaded { + "Loaded" + } else { + "Not Loaded" + } + ); + print!("Enter your choice: "); + io::stdout().flush().unwrap(); +} + +/// Create a WString from a Rust string +fn to_wstring(s: &str) -> WString { + let wchars: Vec = s.encode_utf16().chain(std::iter::once(0)).collect(); + WString { wchars } +} + +/// Get secure ID from Windows Hello. +/// Matches C++ GetSecureIdFromWindowsHello() / veil::vtl0::appmodel::owner_id() +fn get_secure_id_from_windows_hello() -> Result, String> { + // Get the secure ID buffer from KeyCredentialManager + let secure_id_buffer = KeyCredentialManager::GetSecureId() + .map_err(|e| format!("Failed to get secure ID: {:?}", e))?; + + // Cast from userboundkey_kcm::IBuffer to windows::Storage::Streams::IBuffer + let windows_buffer: windows::Storage::Streams::IBuffer = + unsafe { std::mem::transmute(secure_id_buffer) }; + + // Convert IBuffer to Vec using CryptographicBuffer + let mut byte_array = windows::core::Array::::new(); + CryptographicBuffer::CopyToByteArray(&windows_buffer, &mut byte_array) + .map_err(|e| format!("Failed to copy buffer: {:?}", e))?; + + Ok(byte_array.to_vec()) +} + +/// Load the enclave DLL and return the wrapper interface +fn load_enclave( + enclave_path: &Path, +) -> Result<(EnclaveHandle, userboundkey_sampleWrapper), String> { + println!("Loading enclave from: {}", enclave_path.display()); + + // Try to get the secure ID from Windows Hello (matches C++ veil::vtl0::appmodel::owner_id()) + // If Windows Hello isn't available, we continue without owner ID but key creation will fail + let owner_id = match get_secure_id_from_windows_hello() { + Ok(id) => { + println!("Got secure ID from Windows Hello ({} bytes)", id.len()); + Some(id) + } + Err(e) => { + println!("Warning: Could not get secure ID from Windows Hello: {}", e); + println!("Enclave will load but user-bound key operations may fail."); + None + } + }; + + // Create, load, and initialize the enclave (512MB like C++ version) + let enclave = EnclaveHandle::create_and_initialize( + enclave_path, + megabytes(512), + owner_id.as_deref(), // Pass the secure ID as owner ID if available + ) + .map_err(|e| format!("Failed to create/initialize enclave: {:?}", e))?; + + // Create the wrapper interface for enclave calls + let wrapper = userboundkey_sampleWrapper::new(enclave.as_ptr()); + + // Register the sample's VTL0 callbacks (for debug_print, etc.) + wrapper + .register_vtl0_callbacks::() + .map_err(|e| format!("Failed to register sample callbacks: {:?}", e))?; + + // Register the SDK's VTL0 callbacks so enclave can call back to host + // The SDK enclave functions (create_user_bound_key, etc.) use these callbacks + let sdk_wrapper = UserBoundKeyVtl0Host::new(enclave.as_ptr()); + sdk_wrapper + .register_vtl0_callbacks::() + .map_err(|e| format!("Failed to register SDK callbacks: {:?}", e))?; + + println!("Enclave loaded and callbacks registered successfully."); + Ok((enclave, wrapper)) +} + +fn main() { + println!("=== User-Bound Key Sample (Rust) ==="); + println!("Functionally equivalent to C++ HostAppUserBound"); + println!(); + + // Get enclave DLL path (should be in same directory as host executable) + let exe_dir = std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(|p| p.to_path_buf())) + .unwrap_or_else(|| std::env::current_dir().unwrap()); + let enclave_path = exe_dir.join("userboundkey_sample_enclave.dll"); + + // Load the enclave + let (_enclave_handle, enclave) = match load_enclave(&enclave_path) { + Ok(result) => result, + Err(e) => { + eprintln!("Failed to load enclave: {}", e); + eprintln!("Make sure {} exists.", enclave_path.display()); + return; + } + }; + + let key_file_path = get_key_file_path(); + let encrypted_file_path = get_encrypted_file_path(); + let mut secured_key_bytes: Vec = Vec::new(); + let mut is_key_loaded = false; + + // Try to load existing key from file + if key_file_path.exists() { + if let Ok(data) = load_binary_data(&key_file_path) { + secured_key_bytes = data; + is_key_loaded = true; + println!("Found existing key file: {}", key_file_path.display()); + } + } + + // Window ID - use foreground window like C++ version + // GetForegroundWindow returns the handle of the window with which the user is currently working + let window_id: u64 = unsafe { GetForegroundWindow().0 as u64 }; + + loop { + print_menu(is_key_loaded); + + let mut input = String::new(); + if io::stdin().read_line(&mut input).is_err() { + continue; + } + + let choice: u32 = match input.trim().parse() { + Ok(n) => n, + Err(_) => { + println!("Invalid input."); + continue; + } + }; + + match choice { + 1 => { + // Create User-Bound Key + println!("\n[Create User-Bound Key]"); + println!("Window ID: 0x{:X}", window_id); + println!("Calling enclave... (Windows Hello prompt should appear)"); + + let key_name = to_wstring(KEY_NAME); + let pin_message = to_wstring(PIN_MESSAGE); + + // KeyCredentialCreationOption::ReplaceExisting = 1 + const REPLACE_EXISTING: u32 = 1; + + match enclave.CreateUserBoundKey( + &key_name, + &pin_message, + window_id, + REPLACE_EXISTING, + ) { + Ok(key_bytes) => { + if save_binary_data(&key_file_path, &key_bytes).is_ok() { + println!("Key created and saved to: {}", key_file_path.display()); + secured_key_bytes = key_bytes; + is_key_loaded = true; + } else { + eprintln!("Failed to save key to file."); + } + } + Err(e) => { + eprintln!("Failed to create key: {:?}", e); + } + } + } + 2 => { + // Load User-Bound Key + println!("\n[Load User-Bound Key]"); + if key_file_path.exists() { + match load_binary_data(&key_file_path) { + Ok(data) => { + secured_key_bytes = data; + is_key_loaded = true; + println!("Key loaded from: {}", key_file_path.display()); + } + Err(e) => { + eprintln!("Failed to load key: {}", e); + } + } + } else { + println!("No key file found. Create one first."); + } + } + 3 => { + // Delete User-Bound Key + println!("\n[Delete User-Bound Key]"); + if fs::remove_file(&key_file_path).is_ok() { + println!("Key file deleted."); + secured_key_bytes.clear(); + is_key_loaded = false; + } else { + println!("No key file to delete."); + } + } + 4 => { + // Encrypt Data + println!("\n[Encrypt Data]"); + if !is_key_loaded { + println!("Load or create a key first."); + continue; + } + + print!("Enter text to encrypt: "); + io::stdout().flush().unwrap(); + let mut text = String::new(); + if io::stdin().read_line(&mut text).is_err() { + continue; + } + + let key_name = to_wstring(KEY_NAME); + let pin_message = to_wstring(PIN_MESSAGE); + let input_data = to_wstring(text.trim()); + + match enclave.LoadUserBoundKeyAndEncryptData( + &key_name, + &pin_message, + window_id, + &secured_key_bytes, + &input_data, + ) { + Ok(result) => { + // Check if key needs resealing + if result.needsReseal && !result.resealedEncryptionKeyBytes.is_empty() { + println!("Key was resealed, updating stored key..."); + secured_key_bytes = result.resealedEncryptionKeyBytes; + if let Err(e) = save_binary_data(&key_file_path, &secured_key_bytes) { + eprintln!("Warning: Failed to save resealed key: {}", e); + } + } + + // Save encrypted data + if save_binary_data(&encrypted_file_path, &result.combinedOutputData) + .is_ok() + { + println!("Encrypted data saved to: {}", encrypted_file_path.display()); + println!("Encrypted {} bytes.", result.combinedOutputData.len()); + } else { + eprintln!("Failed to save encrypted data."); + } + } + Err(e) => { + eprintln!("Encryption failed: {:?}", e); + } + } + } + 5 => { + // Decrypt Data + println!("\n[Decrypt Data]"); + if !encrypted_file_path.exists() { + println!("No encrypted data file found."); + continue; + } + if !is_key_loaded { + println!("Load or create a key first."); + continue; + } + + let combined_data = match load_binary_data(&encrypted_file_path) { + Ok(data) => data, + Err(e) => { + eprintln!("Failed to load encrypted data: {}", e); + continue; + } + }; + + let key_name = to_wstring(KEY_NAME); + let pin_message = to_wstring(PIN_MESSAGE); + + match enclave.LoadUserBoundKeyAndDecryptData( + &key_name, + &pin_message, + window_id, + &secured_key_bytes, + &combined_data, + ) { + Ok(result) => { + // Check if key needs resealing + if result.needsReseal && !result.resealedEncryptionKeyBytes.is_empty() { + println!("Key was resealed, updating stored key..."); + secured_key_bytes = result.resealedEncryptionKeyBytes; + if let Err(e) = save_binary_data(&key_file_path, &secured_key_bytes) { + eprintln!("Warning: Failed to save resealed key: {}", e); + } + } + + // Convert WString to Rust String + let decrypted_text: String = result + .decryptedData + .wchars + .iter() + .take_while(|&&c| c != 0) + .map(|&c| char::from_u32(c as u32).unwrap_or('?')) + .collect(); + + println!("Decrypted data: {}", decrypted_text); + } + Err(e) => { + eprintln!("Decryption failed: {:?}", e); + } + } + } + 6 => { + println!("Exiting."); + break; + } + _ => println!("Invalid choice."), + } + } + + // EnclaveHandle will automatically terminate and delete the enclave on drop + println!("Enclave cleanup complete."); +} diff --git a/rust/sdk/crates/samples/userboundkey/userboundkey_sample.edl b/rust/sdk/crates/samples/userboundkey/userboundkey_sample.edl new file mode 100644 index 00000000..1313f1a9 --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/userboundkey_sample.edl @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Sample EDL for user-bound key operations. +// Note: This sample uses the SDK's userboundkey module which has its own +// EDL callbacks. The SDK host registers those callbacks separately. + +enclave +{ + // Result from encrypt operation + struct EncryptResult + { + // Combined output: [tag_size(4 bytes)][tag][encrypted_data] + vector combinedOutputData; + bool needsReseal; + vector resealedEncryptionKeyBytes; + }; + + // Result from decrypt operation + struct DecryptResult + { + wstring decryptedData; + bool needsReseal; + vector resealedEncryptionKeyBytes; + }; + + // Trusted functions: Called from host (VTL0) into enclave (VTL1) + trusted + { + // Create a new user-bound encryption key protected by Windows Hello. + // Returns the sealed key bytes that should be stored by the host. + vector CreateUserBoundKey( + wstring helloKeyName, + wstring pinMessage, + uintptr_t windowId, + uint32_t keyCredentialCreationOption + ); + + // Load an existing user-bound key and encrypt data. + // Handles reseal if the key was sealed with an older enclave version. + EncryptResult LoadUserBoundKeyAndEncryptData( + wstring helloKeyName, + wstring pinMessage, + uintptr_t windowId, + vector securedEncryptionKeyBytes, + wstring inputData + ); + + // Load an existing user-bound key and decrypt data. + // Handles reseal if the key was sealed with an older enclave version. + DecryptResult LoadUserBoundKeyAndDecryptData( + wstring helloKeyName, + wstring pinMessage, + uintptr_t windowId, + vector securedEncryptionKeyBytes, + vector combinedInputData + ); + }; + + // Untrusted functions: Called from enclave (VTL1) to host (VTL0) + untrusted + { + // Debug print function - prints to console from enclave + void debug_print(string message); + }; +}; diff --git a/rust/sdk/generate_and_build_crates.ps1 b/rust/sdk/generate_and_build_crates.ps1 deleted file mode 100644 index 358e5828..00000000 --- a/rust/sdk/generate_and_build_crates.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -# Generates the edlcodegen bindings for the host and enclave sdk crates from -# the veil_abi EDL file and builds the sdk workspace. - -param( - [ValidateSet("debug", "release")] - [string]$Configuration = "debug" -) - -$errorActionPreference = "Stop" - -# Locate shared scripts folder -. "$PSScriptRoot\..\scripts\get_common_paths.ps1" - -# Generate edlcodegen crates for the workspace first before attempting -# to build the sdk workspace. -. "$PSScriptRoot\generate_codegen_for_workspace.ps1" - -# Build the sdk workspace now that codegeneration is completed. -. "$scriptsDir\invoke_cargo_build.ps1" -Path $PSScriptRoot -Configuration $Configuration - diff --git a/rust/sdk/generate_codegen_for_workspace.ps1 b/rust/sdk/generate_codegen_for_workspace.ps1 deleted file mode 100644 index 7aa7434e..00000000 --- a/rust/sdk/generate_codegen_for_workspace.ps1 +++ /dev/null @@ -1,36 +0,0 @@ -# Generates the edlcodegen bindings for all host and enclave sdk crates. - -$errorActionPreference = "Stop" - -# Locate shared scripts folder -. "$PSScriptRoot\..\scripts\get_common_paths.ps1" - -# SDK crate paths -$edlPath = Join-Path $repoRoot "src\VbsEnclaveSDK\veil_abi.edl" -$enclaveSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-enclave" -$hostSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-host" - -# Generate edlcodegen crates for the sdk-host and sdk-enclave crates -. "$scriptsDir\generate_codegen_crates.ps1" ` - -HostAppOutDir "$hostSdkCrate\generated" ` - -EnclaveOutDir "$enclaveSdkCrate\generated" ` - -EdlPath $edlPath ` - -Namespace "veil_abi" ` - -Vtl0ClassName "export_interface" - -# Generate userboundkey-specific EDL crates -$userboundkeyEdlPath = Join-Path $SdkWorkspacePath "crates\libs\userboundkey.edl" -. "$scriptsDir\generate_codegen_crates.ps1" ` - -HostAppOutDir "$hostSdkCrate\generated" ` - -EnclaveOutDir "$enclaveSdkCrate\generated" ` - -EdlPath $userboundkeyEdlPath ` - -Namespace "userboundkey" ` - -Vtl0ClassName "UserBoundKeyVtl0Host" - -# Below this comment, Call the generate_codegen_crates.ps1 script -# on any other crates in the sdk workspace that need codegen bindings. -# E.g for any sample crates. - -# Format the workspace after code generation to make sure all generated code -# is properly formatted. -. "$scriptsDir\invoke_rustfmt_workspace.ps1" -WorkspacePath $PSScriptRoot \ No newline at end of file From e7464688c360ada9ea79b91600bfc2c0f2b5a5b0 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Tue, 20 Jan 2026 14:13:34 -0800 Subject: [PATCH 03/13] Cleanups and additional changes --- .../libs/sdk-enclave/src/userboundkey/mod.rs | 18 +++++++----- .../sdk-enclave/src/userboundkey/utils.rs | 3 +- rust/sdk/crates/libs/sdk-host/src/enclave.rs | 4 +-- .../userboundkey/enclave/src/edl_impls.rs | 29 ++++++------------- .../samples/userboundkey/host/src/main.rs | 15 +++++----- 5 files changed, 29 insertions(+), 40 deletions(-) diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs index 493bec11..6b969250 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs @@ -362,10 +362,10 @@ pub fn create_user_bound_key_with_custom_key( /// * `message` - Message to display during Windows Hello prompt /// * `window_id` - Window ID for the prompt /// * `sealed_bound_key_bytes` - Previously sealed key material -/// * `needs_reseal` - Output: whether the key needs to be resealed /// /// # Returns -/// The decrypted user key bytes +/// A tuple of (decrypted user key bytes, needs_reseal flag). +/// If `needs_reseal` is true, the caller should reseal the key with [`reseal_user_bound_key`]. pub fn load_user_bound_key( key_name: &str, cache_config: &keyCredentialCacheConfig, @@ -549,18 +549,20 @@ pub fn load_user_bound_key( /// Reseal a user-bound key with current enclave identity /// -/// This is used when load_user_bound_key returns StaleKey error, -/// indicating the sealing key has changed. The caller should have -/// already unsealed the data (which succeeds even with a stale key) -/// and passes the unsealed bound key bytes to this function. +/// This function re-seals data that was previously sealed by an older enclave version. +/// It is used when the sealing key has rotated (detected by `is_stale_key` flag from unsealing). +/// +/// Note: The input `bound_key_bytes` should be the unsealed (decrypted) bound key data, +/// NOT the sealed blob. If you have sealed data, first unseal it with `unseal_data` +/// from the crypto module, then pass the unsealed result here. /// /// # Arguments -/// * `bound_key_bytes` - Previously unsealed bound key bytes (not sealed) +/// * `bound_key_bytes` - Previously unsealed bound key bytes (NOT sealed data) /// * `sealing_policy` - Enclave sealing identity policy for the new seal /// * `runtime_policy` - Runtime policy flags /// /// # Returns -/// Newly sealed key material +/// Newly sealed key material that can be stored and used with future loads pub fn reseal_user_bound_key( bound_key_bytes: &[u8], sealing_policy: EnclaveSealingIdentityPolicy, diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs index 4d43517f..07cf25a4 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs @@ -3,8 +3,7 @@ //! Utility functions for VTL1 enclave operations //! -//! This module provides general-purpose utilities for enclave operations, -//! similar to utils.vtl1.h in the C++ implementation. +//! This module provides general-purpose utilities for enclave operations. use core::sync::atomic::{AtomicPtr, Ordering}; diff --git a/rust/sdk/crates/libs/sdk-host/src/enclave.rs b/rust/sdk/crates/libs/sdk-host/src/enclave.rs index f7767370..ec6b10fc 100644 --- a/rust/sdk/crates/libs/sdk-host/src/enclave.rs +++ b/rust/sdk/crates/libs/sdk-host/src/enclave.rs @@ -3,8 +3,7 @@ //! VBS Enclave lifecycle management APIs. //! -//! This module provides Rust equivalents of the C++ `veil::vtl0::enclave` namespace -//! from `enclave_api.vtl0.h`. It handles creating, loading, initializing, and +//! This module provides APIs for creating, loading, initializing, and //! cleaning up VBS enclaves. //! //! # Example @@ -291,7 +290,6 @@ pub fn delete(enclave: *mut c_void) { /// A handle to a VBS enclave that manages its lifecycle. /// /// When dropped, the enclave is automatically terminated and deleted. -/// This is equivalent to the C++ `unique_enclave` type. pub struct EnclaveHandle { enclave: *mut c_void, } diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs index 1f9e357a..4158d205 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs @@ -2,7 +2,9 @@ // Licensed under the MIT License. //! Enclave implementation for the user-bound key sample. -//! Functionally equivalent to C++ SampleEnclave/UserBound/userbound_keys.cpp +//! +//! Implements trusted EDL functions for creating, loading, and using +//! user-bound encryption keys protected by Windows Hello. #![allow(unused_imports)] #![allow(non_snake_case)] @@ -22,12 +24,11 @@ use vbsenclave_sdk_enclave::userboundkey::{ decrypt, encrypt, keyCredentialCacheConfig, load_user_bound_key, reseal_user_bound_key, }; -/// Runtime policy: no dynamic debug allowed (matches C++ g_runtimePolicy = 0) +/// Runtime policy: no dynamic debug allowed const RUNTIME_POLICY_NO_DEBUG: u32 = 0; /// VTL1 function to create secure cache configuration. /// This ensures VTL0 has no influence over cache configuration values. -/// Matches C++ CreateSecureKeyCredentialCacheConfig() fn create_secure_cache_config() -> keyCredentialCacheConfig { keyCredentialCacheConfig { cacheOption: 0, // NoCache - most secure option @@ -36,8 +37,8 @@ fn create_secure_cache_config() -> keyCredentialCacheConfig { } } -/// Global encryption key storage (matches C++ g_encryptionKey pattern) -/// In no_std enclave, we use a static mut with unsafe access +/// Global encryption key storage. +/// In no_std enclave, we use a static mut with unsafe access. static mut ENCRYPTION_KEY: Option = None; #[allow(static_mut_refs)] @@ -65,7 +66,6 @@ fn wstring_to_string(ws: &WString) -> alloc::string::String { /// Helper function to ensure user-bound key is loaded. /// Handles initial load attempt and optional reseal if needed. -/// Matches C++ EnsureUserBoundKeyLoaded() fn ensure_user_bound_key_loaded( hello_key_name: &str, pin_message: &str, @@ -74,7 +74,7 @@ fn ensure_user_bound_key_loaded( needs_reseal: &mut bool, resealed_encryption_key_bytes: &mut Vec, ) -> Result<(), AbiError> { - // Only load if not already loaded (matches C++ IsUBKLoaded check) + // Only load if not already loaded if is_ubk_loaded() { return Ok(()); } @@ -149,7 +149,6 @@ fn ensure_user_bound_key_loaded( } // Create symmetric key from loaded raw key material - // Matches C++: auto newEncryptionKey = veil::vtl1::crypto::create_symmetric_key(loadedKeyBytes); let symmetric_key = SymmetricKeyHandle::from_bytes(&loaded_key_bytes) .map_err(|e| AbiError::Hresult(e.to_hresult()))?; set_encryption_key(symmetric_key); @@ -162,7 +161,6 @@ pub struct EnclaveImpl; impl Trusted for EnclaveImpl { /// Create a user-bound key. - /// Matches C++ MyEnclaveCreateUserBoundKey() fn CreateUserBoundKey( helloKeyName: &WString, pinMessage: &WString, @@ -185,7 +183,6 @@ impl Trusted for EnclaveImpl { )); // Create user-bound key with enclave sealing - // Matches C++: veil::vtl1::userboundkey::create_user_bound_key(...) let sealed_key = create_user_bound_key( &key_name, &cache_config, @@ -206,12 +203,11 @@ impl Trusted for EnclaveImpl { )); // Return sealed key bytes - do NOT create symmetric key here - // Matches C++ comment: "Do NOT try to create a symmetric key here" + // The key is created on load, not on create. Ok(sealed_key) } /// Load user-bound key and encrypt data. - /// Matches C++ MyEnclaveLoadUserBoundKeyAndEncryptData() fn LoadUserBoundKeyAndEncryptData( helloKeyName: &WString, pinMessage: &WString, @@ -237,8 +233,7 @@ impl Trusted for EnclaveImpl { // Get the key and encrypt let key = get_encryption_key().ok_or(AbiError::Hresult(-2147024809))?; - // Convert input to bytes (wchar_t / UTF-16 LE data like C++) - // Matches C++: veil::vtl1::as_data_span(inputData.c_str()) + // Convert input to bytes (UTF-16 LE data) let input_bytes: Vec = inputData .wchars .iter() @@ -246,13 +241,11 @@ impl Trusted for EnclaveImpl { .collect(); // Encrypt using AES-GCM with zero nonce - // Matches C++: veil::vtl1::crypto::encrypt(keyHandle, ..., veil::vtl1::crypto::zero_nonce) let (encrypted, tag) = encrypt(key, &input_bytes, &ZERO_NONCE) .map_err(|e| AbiError::Hresult(e.to_hresult()))?; // Combine tag and encrypted data into single output // Format: [tag_size (4 bytes)][tag_data][encrypted_data] - // Matches C++ format exactly let tag_size = tag.len() as u32; let mut combined = Vec::with_capacity(4 + tag.len() + encrypted.len()); combined.extend_from_slice(&tag_size.to_le_bytes()); @@ -267,7 +260,6 @@ impl Trusted for EnclaveImpl { } /// Load user-bound key and decrypt data. - /// Matches C++ MyEnclaveLoadUserBoundKeyAndDecryptData() fn LoadUserBoundKeyAndDecryptData( helloKeyName: &WString, pinMessage: &WString, @@ -281,7 +273,6 @@ impl Trusted for EnclaveImpl { let mut resealed_bytes = Vec::new(); // Parse combined input: [tag_size (4 bytes)][tag_data][encrypted_data] - // Matches C++ parsing logic if combinedInputData.len() < 4 { return Err(AbiError::Hresult(-2147024809)); // E_INVALIDARG } @@ -324,12 +315,10 @@ impl Trusted for EnclaveImpl { tag_array.copy_from_slice(tag); // Decrypt using AES-GCM with zero nonce - // Matches C++: veil::vtl1::crypto::decrypt(keyHandle, ..., veil::vtl1::crypto::zero_nonce, tag) let decrypted_bytes = decrypt(key, encrypted, &ZERO_NONCE, &tag_array) .map_err(|e| AbiError::Hresult(e.to_hresult()))?; // Convert decrypted bytes to WString (UTF-16 LE) - // Matches C++: veil::vtl1::to_wstring(decryptedBytes) let utf16: Vec = decrypted_bytes .chunks_exact(2) .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) diff --git a/rust/sdk/crates/samples/userboundkey/host/src/main.rs b/rust/sdk/crates/samples/userboundkey/host/src/main.rs index 22a5da46..f054a009 100644 --- a/rust/sdk/crates/samples/userboundkey/host/src/main.rs +++ b/rust/sdk/crates/samples/userboundkey/host/src/main.rs @@ -2,7 +2,8 @@ // Licensed under the MIT License. //! User-Bound Key Sample Host Application -//! Functionally equivalent to C++ SampleApps/HostAppUserBound/main.cpp +//! +//! Demonstrates loading a VBS enclave and using user-bound keys with Windows Hello. #![allow(dead_code)] @@ -72,7 +73,8 @@ fn to_wstring(s: &str) -> WString { } /// Get secure ID from Windows Hello. -/// Matches C++ GetSecureIdFromWindowsHello() / veil::vtl0::appmodel::owner_id() +/// +/// Returns the secure ID buffer that identifies this Windows Hello user. fn get_secure_id_from_windows_hello() -> Result, String> { // Get the secure ID buffer from KeyCredentialManager let secure_id_buffer = KeyCredentialManager::GetSecureId() @@ -96,8 +98,8 @@ fn load_enclave( ) -> Result<(EnclaveHandle, userboundkey_sampleWrapper), String> { println!("Loading enclave from: {}", enclave_path.display()); - // Try to get the secure ID from Windows Hello (matches C++ veil::vtl0::appmodel::owner_id()) - // If Windows Hello isn't available, we continue without owner ID but key creation will fail + // Try to get the secure ID from Windows Hello. + // If Windows Hello isn't available, we continue without owner ID but key creation will fail. let owner_id = match get_secure_id_from_windows_hello() { Ok(id) => { println!("Got secure ID from Windows Hello ({} bytes)", id.len()); @@ -110,7 +112,7 @@ fn load_enclave( } }; - // Create, load, and initialize the enclave (512MB like C++ version) + // Create, load, and initialize the enclave (512MB) let enclave = EnclaveHandle::create_and_initialize( enclave_path, megabytes(512), @@ -139,7 +141,6 @@ fn load_enclave( fn main() { println!("=== User-Bound Key Sample (Rust) ==="); - println!("Functionally equivalent to C++ HostAppUserBound"); println!(); // Get enclave DLL path (should be in same directory as host executable) @@ -173,7 +174,7 @@ fn main() { } } - // Window ID - use foreground window like C++ version + // Window ID - use foreground window for Windows Hello prompts // GetForegroundWindow returns the handle of the window with which the user is currently working let window_id: u64 = unsafe { GetForegroundWindow().0 as u64 }; From 7368b11469da81a3ebd39743823561a30af72953 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Tue, 20 Jan 2026 14:52:15 -0800 Subject: [PATCH 04/13] Convert to wstring --- .../libs/sdk-enclave/src/userboundkey/mod.rs | 105 ++++++++++------ .../libs/sdk-host/src/userboundkey/mod.rs | 118 ++++++++++-------- rust/sdk/crates/libs/userboundkey.edl | 18 +-- .../userboundkey/enclave/src/edl_impls.rs | 38 ++---- .../samples/userboundkey/host/src/main.rs | 4 +- 5 files changed, 154 insertions(+), 129 deletions(-) diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs index 6b969250..e2b5d9cc 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs @@ -148,13 +148,26 @@ fn get_ephemeral_public_key_bytes_from_bound_key( Ok(bound_key_bytes[4..4 + public_key_size].to_vec()) } -/// Validate that formatted key name ends with expected suffix -fn validate_formatted_key_name( - formatted_key_name: &str, - original_key_name: &str, +/// Validate that formatted key name ends with expected suffix (wide string version) +fn validate_formatted_key_name_wide( + formatted_key_name: &[u16], + original_key_name: &[u16], ) -> Result<(), UserBoundKeyError> { - let expected_suffix = alloc::format!("//{}", original_key_name); - if !formatted_key_name.ends_with(&expected_suffix) { + // Build expected suffix: "//" + original_key_name + let slash_slash: [u16; 2] = ['/' as u16, '/' as u16]; + let expected_suffix_len = 2 + original_key_name.len(); + + if formatted_key_name.len() < expected_suffix_len { + return Err(UserBoundKeyError::SecurityViolation( + "Formatted key name does not match expected pattern", + )); + } + + // Check if formatted_key_name ends with "//{original_key_name}" + let suffix_start = formatted_key_name.len() - expected_suffix_len; + let formatted_suffix = &formatted_key_name[suffix_start..]; + + if formatted_suffix[0..2] != slash_slash || formatted_suffix[2..] != *original_key_name { return Err(UserBoundKeyError::SecurityViolation( "Formatted key name does not match expected pattern", )); @@ -171,9 +184,9 @@ fn validate_formatted_key_name( /// 4. Seals the protected key to the enclave /// /// # Arguments -/// * `key_name` - Name for the Windows Hello credential +/// * `key_name` - Name for the Windows Hello credential (UTF-16 wide string without null terminator) /// * `cache_config` - Cache configuration for the credential -/// * `message` - Message to display during Windows Hello prompt +/// * `message` - Message to display during Windows Hello prompt (UTF-16 wide string without null terminator) /// * `window_id` - Window ID for the prompt /// * `sealing_policy` - Enclave sealing identity policy /// * `runtime_policy` - Runtime policy flags @@ -182,9 +195,9 @@ fn validate_formatted_key_name( /// # Returns /// Sealed key material that can be stored and later loaded pub fn create_user_bound_key( - key_name: &str, + key_name: &[u16], cache_config: &keyCredentialCacheConfig, - message: &str, + message: &[u16], window_id: u64, sealing_policy: EnclaveSealingIdentityPolicy, runtime_policy: u32, @@ -207,9 +220,9 @@ pub fn create_user_bound_key( /// Create a user-bound key with custom key bytes pub fn create_user_bound_key_with_custom_key( - key_name: &str, + key_name: &[u16], cache_config: &keyCredentialCacheConfig, - message: &str, + message: &[u16], window_id: u64, sealing_policy: EnclaveSealingIdentityPolicy, runtime_policy: u32, @@ -217,22 +230,31 @@ pub fn create_user_bound_key_with_custom_key( custom_key_bytes: &[u8], ) -> Result, UserBoundKeyError> { // Format the key name (calls VTL0) - this is the FIRST VTL0 call - let formatted_key_name = untrusted::userboundkey_format_key_name(&key_name.into()) + // Create WString from key_name slice + let key_name_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { + wchars: key_name.to_vec(), + }; + let formatted_key_name_wstring = untrusted::userboundkey_format_key_name(&key_name_wstring) .map_err(|e| UserBoundKeyError::AbiError(e))?; - // Security validation - validate_formatted_key_name(&formatted_key_name, key_name)?; + // Security validation (wide string version) + validate_formatted_key_name_wide(&formatted_key_name_wstring.wchars, key_name)?; // Get enclave base address let enclave_ptr = get_enclave_base_address_u64()?; + // Create WString for message + let message_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { + wchars: message.to_vec(), + }; + // Establish session (calls VTL0 which triggers Windows Hello) // Requires at least 2 enclave threads - one for this call and one for the callback let credential_and_session = untrusted::userboundkey_establish_session_for_create( enclave_ptr, - &key_name.into(), + &key_name_wstring, BCRYPT_ECDH_P384_ALG_HANDLE_U64, - &message.into(), + &message_wstring, window_id, cache_config, key_credential_creation_option, @@ -250,11 +272,9 @@ pub fn create_user_bound_key_with_custom_key( let mut encrypted_request_size: u32 = 0; let mut local_nonce: u64 = 0; - // Convert formatted key name to wide string - let formatted_key_name_wide: Vec = formatted_key_name - .encode_utf16() - .chain(core::iter::once(0)) - .collect(); + // Add null terminator for Windows API + let mut formatted_key_name_wide: Vec = formatted_key_name_wstring.wchars.clone(); + formatted_key_name_wide.push(0); unsafe { let hr = CreateUserBoundKeyRequestForRetrieveAuthorizationContext( @@ -286,7 +306,7 @@ pub fn create_user_bound_key_with_custom_key( let auth_context_blob = untrusted::userboundkey_get_authorization_context_from_credential( credential.get(), &encrypted_request, - &message.into(), + &message_wstring, window_id, ) .map_err(|e| UserBoundKeyError::AbiError(e))?; @@ -357,9 +377,9 @@ pub fn create_user_bound_key_with_custom_key( /// Load a previously created user-bound key /// /// # Arguments -/// * `key_name` - Name of the Windows Hello credential +/// * `key_name` - Name of the Windows Hello credential (UTF-16 wide string without null terminator) /// * `cache_config` - Expected cache configuration -/// * `message` - Message to display during Windows Hello prompt +/// * `message` - Message to display during Windows Hello prompt (UTF-16 wide string without null terminator) /// * `window_id` - Window ID for the prompt /// * `sealed_bound_key_bytes` - Previously sealed key material /// @@ -367,9 +387,9 @@ pub fn create_user_bound_key_with_custom_key( /// A tuple of (decrypted user key bytes, needs_reseal flag). /// If `needs_reseal` is true, the caller should reseal the key with [`reseal_user_bound_key`]. pub fn load_user_bound_key( - key_name: &str, + key_name: &[u16], cache_config: &keyCredentialCacheConfig, - message: &str, + message: &[u16], window_id: u64, sealed_bound_key_bytes: &[u8], ) -> Result<(Vec, bool), UserBoundKeyError> { @@ -384,21 +404,29 @@ pub fn load_user_bound_key( // Extract ephemeral public key from bound key structure let ephemeral_public_key = get_ephemeral_public_key_bytes_from_bound_key(&bound_key_bytes)?; - // Format the key name - let formatted_key_name = untrusted::userboundkey_format_key_name(&key_name.into()) + // Format the key name (calls VTL0) + let key_name_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { + wchars: key_name.to_vec(), + }; + let formatted_key_name_wstring = untrusted::userboundkey_format_key_name(&key_name_wstring) .map_err(|e| UserBoundKeyError::AbiError(e))?; - // Security validation - validate_formatted_key_name(&formatted_key_name, key_name)?; + // Security validation (wide string version) + validate_formatted_key_name_wide(&formatted_key_name_wstring.wchars, key_name)?; // Get enclave base address let enclave_ptr = get_enclave_base_address_u64()?; + // Create WString for message + let message_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { + wchars: message.to_vec(), + }; + // Establish session for load (calls VTL0) let credential_and_session = untrusted::userboundkey_establish_session_for_load( enclave_ptr, - &key_name.into(), - &message.into(), + &key_name_wstring, + &message_wstring, window_id, ) .map_err(|e| UserBoundKeyError::AbiError(e))?; @@ -414,10 +442,9 @@ pub fn load_user_bound_key( let mut encrypted_rac_size: u32 = 0; let mut local_nonce: u64 = 0; - let formatted_key_name_wide: Vec = formatted_key_name - .encode_utf16() - .chain(core::iter::once(0)) - .collect(); + // Add null terminator for Windows API + let mut formatted_key_name_wide: Vec = formatted_key_name_wstring.wchars.clone(); + formatted_key_name_wide.push(0); unsafe { let hr = CreateUserBoundKeyRequestForRetrieveAuthorizationContext( @@ -444,7 +471,7 @@ pub fn load_user_bound_key( let auth_context_blob = untrusted::userboundkey_get_authorization_context_from_credential( credential.get(), &encrypted_rac_request, - &message.into(), + &message_wstring, window_id, ) .map_err(|e| UserBoundKeyError::AbiError(e))?; @@ -511,7 +538,7 @@ pub fn load_user_bound_key( let secret = untrusted::userboundkey_get_secret_from_credential( credential.get(), &encrypted_dss_request, - &message.into(), + &message_wstring, window_id, ) .map_err(|e| UserBoundKeyError::AbiError(e))?; diff --git a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs index a09f3484..3955b8d5 100644 --- a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs @@ -13,7 +13,8 @@ pub use types::*; use userboundkey_host_gen::AbiError; pub use userboundkey_host_gen::UserBoundKeyVtl0Host; pub use userboundkey_host_gen::implementation::types::{ - attestationReportAndSessionInfo, credentialAndSessionInfo, keyCredentialCacheConfig, + attestationReportAndSessionInfo, credentialAndSessionInfo, edl::WString, + keyCredentialCacheConfig, }; use userboundkey_host_gen::implementation::untrusted::Untrusted; @@ -195,51 +196,16 @@ fn create_challenge_callback( }) } -/// Format a key name with user SID for Windows Hello -fn format_user_hello_key_name(name: &str) -> Result { - unsafe { - let mut process_token = HANDLE::default(); - OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut process_token) - .map_err(|_| AbiError::Hresult(E_FAIL.0))?; - - // Get token user info size - let mut return_length: u32 = 0; - let _ = GetTokenInformation(process_token, TokenUser, None, 0, &mut return_length); - - // Allocate buffer and get token user info - let mut buffer = vec![0u8; return_length as usize]; - GetTokenInformation( - process_token, - TokenUser, - Some(buffer.as_mut_ptr() as *mut _), - return_length, - &mut return_length, - ) - .map_err(|_| AbiError::Hresult(E_FAIL.0))?; - - let _ = CloseHandle(process_token); - - let token_user = &*(buffer.as_ptr() as *const TOKEN_USER); - let user_sid = token_user.User.Sid; - - // Convert SID to string - let sid_string = sid_to_string(user_sid)?; - - // Format: {SID}//{SID}//{keyName} - Ok(format!("{}//{}//{}", sid_string, sid_string, name)) - } -} - -/// Convert a PSID to a string representation -fn sid_to_string(sid: PSID) -> Result { +/// Convert a PSID to a wide string representation (Vec) +fn sid_to_wide_string(sid: PSID) -> Result, AbiError> { unsafe { let mut string_sid = PWSTR::null(); ConvertSidToStringSidW(sid, &mut string_sid).map_err(|_| AbiError::Hresult(E_FAIL.0))?; - // Convert PWSTR to String + // Convert PWSTR to Vec let len = (0..).take_while(|&i| *string_sid.0.add(i) != 0).count(); let slice = std::slice::from_raw_parts(string_sid.0, len); - let result = String::from_utf16_lossy(slice); + let result = slice.to_vec(); // Note: We're leaking the string memory here since LocalFree requires more setup. // In a production implementation, proper cleanup should be added. @@ -252,22 +218,27 @@ fn sid_to_string(sid: PSID) -> Result { /// Untrusted implementation struct pub struct UntrustedImpl; +/// Helper to convert WString to HSTRING +fn wstring_to_hstring(ws: &WString) -> HSTRING { + HSTRING::from_wide(&ws.wchars) +} + #[allow(non_snake_case)] #[allow(unused_variables)] impl Untrusted for UntrustedImpl { fn userboundkey_establish_session_for_create( enclave: u64, - keyName: &String, + keyName: &WString, ecdhProtocol: u64, - message: &String, + message: &WString, windowId: u64, cacheConfig: &keyCredentialCacheConfig, keyCredentialCreationOption: u32, ) -> Result { println!("[SDK-Host] userboundkey_establish_session_for_create called"); println!( - "[SDK-Host] keyName={}, windowId={}, option={}", - keyName, windowId, keyCredentialCreationOption + "[SDK-Host] windowId={}, option={}", + windowId, keyCredentialCreationOption ); let algorithm = get_algorithm(ecdhProtocol)?; @@ -281,7 +252,7 @@ impl Untrusted for UntrustedImpl { let enclave_ptr = enclave as usize; // Try to delete existing key first (ignore errors) - let key_name_hstring = HSTRING::from(keyName.as_str()); + let key_name_hstring = wstring_to_hstring(keyName); println!("[SDK-Host] Deleting existing key (if any)..."); if let Ok(delete_op) = KeyCredentialManager::DeleteAsync(&key_name_hstring) { let _ = delete_op.join(); @@ -289,7 +260,7 @@ impl Untrusted for UntrustedImpl { println!("[SDK-Host] Delete complete"); // Create the credential with VBS attestation - let message_hstring = HSTRING::from(message.as_str()); + let message_hstring = wstring_to_hstring(message); let win_id = WindowId { Value: windowId }; let creation_option = KeyCredentialCreationOption(keyCredentialCreationOption as i32); @@ -353,15 +324,15 @@ impl Untrusted for UntrustedImpl { fn userboundkey_establish_session_for_load( enclave: u64, - keyName: &String, - message: &String, + keyName: &WString, + message: &WString, windowId: u64, ) -> Result { let _ = (message, windowId); // Mark as intentionally unused let session_info = Arc::new(Mutex::new(UniqueSessionHandle::new())); let enclave_ptr = enclave as usize; - let key_name_hstring = HSTRING::from(keyName.as_str()); + let key_name_hstring = wstring_to_hstring(keyName); let callback = create_challenge_callback(session_info.clone(), enclave_ptr); let credential_result = KeyCredentialManager::OpenAsync2( @@ -405,7 +376,7 @@ impl Untrusted for UntrustedImpl { fn userboundkey_get_authorization_context_from_credential( credential: u64, encryptedRequest: &Vec, - message: &String, + message: &WString, windowId: u64, ) -> Result, AbiError> { // Reconstruct KeyCredential from raw pointer @@ -438,7 +409,7 @@ impl Untrusted for UntrustedImpl { fn userboundkey_get_secret_from_credential( credential: u64, encryptedRequest: &Vec, - message: &String, + message: &WString, windowId: u64, ) -> Result, AbiError> { // Reconstruct KeyCredential from raw pointer @@ -461,7 +432,7 @@ impl Untrusted for UntrustedImpl { vec_to_kcm_buffer(encryptedRequest).map_err(|e| AbiError::Hresult(e.code().0))?; let win_id = WindowId { Value: windowId }; - let message_hstring = HSTRING::from(message.as_str()); + let message_hstring = wstring_to_hstring(message); // Call RequestDeriveSharedSecretAsync on the credential let operation_result = key_credential @@ -477,8 +448,47 @@ impl Untrusted for UntrustedImpl { kcm_buffer_to_vec(&result_buffer).map_err(|e| AbiError::Hresult(e.code().0)) } - fn userboundkey_format_key_name(keyName: &String) -> Result { - format_user_hello_key_name(keyName) + fn userboundkey_format_key_name(keyName: &WString) -> Result { + unsafe { + let mut process_token = HANDLE::default(); + OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut process_token) + .map_err(|_| AbiError::Hresult(E_FAIL.0))?; + + // Get token user info size + let mut return_length: u32 = 0; + let _ = GetTokenInformation(process_token, TokenUser, None, 0, &mut return_length); + + // Allocate buffer and get token user info + let mut buffer = vec![0u8; return_length as usize]; + GetTokenInformation( + process_token, + TokenUser, + Some(buffer.as_mut_ptr() as *mut _), + return_length, + &mut return_length, + ) + .map_err(|_| AbiError::Hresult(E_FAIL.0))?; + + let _ = CloseHandle(process_token); + + let token_user = &*(buffer.as_ptr() as *const TOKEN_USER); + let user_sid = token_user.User.Sid; + + // Convert SID to wide string + let sid_wide = sid_to_wide_string(user_sid)?; + + // Build formatted key: {SID}//{SID}//{keyName} + let slash_slash: [u16; 2] = ['/' as u16, '/' as u16]; + let mut result = + Vec::with_capacity(sid_wide.len() + 2 + sid_wide.len() + 2 + keyName.wchars.len()); + result.extend_from_slice(&sid_wide); + result.extend_from_slice(&slash_slash); + result.extend_from_slice(&sid_wide); + result.extend_from_slice(&slash_slash); + result.extend_from_slice(&keyName.wchars); + + Ok(WString { wchars: result }) + } } fn userboundkey_delete_credential(credential: u64) -> Result<(), AbiError> { diff --git a/rust/sdk/crates/libs/userboundkey.edl b/rust/sdk/crates/libs/userboundkey.edl index 5b85237c..80661570 100644 --- a/rust/sdk/crates/libs/userboundkey.edl +++ b/rust/sdk/crates/libs/userboundkey.edl @@ -50,9 +50,9 @@ enclave // Returns credential handle and session info on success. credentialAndSessionInfo userboundkey_establish_session_for_create( uint64_t enclave, - string keyName, + wstring keyName, uint64_t ecdhProtocol, - string message, + wstring message, uint64_t windowId, keyCredentialCacheConfig cacheConfig, uint32_t keyCredentialCreationOption @@ -62,8 +62,8 @@ enclave // Returns credential handle and session info on success. credentialAndSessionInfo userboundkey_establish_session_for_load( uint64_t enclave, - string keyName, - string message, + wstring keyName, + wstring message, uint64_t windowId ); @@ -72,7 +72,7 @@ enclave vector userboundkey_get_authorization_context_from_credential( uint64_t credential, vector encryptedRequest, - string message, + wstring message, uint64_t windowId ); @@ -81,14 +81,14 @@ enclave vector userboundkey_get_secret_from_credential( uint64_t credential, vector encryptedRequest, - string message, + wstring message, uint64_t windowId ); // Format a key name with current user's SID for Windows Hello. - // Returns the formatted key name string. - string userboundkey_format_key_name( - string keyName + // Returns the formatted key name wide string. + wstring userboundkey_format_key_name( + wstring keyName ); // Delete/release a credential handle in VTL0. diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs index 4158d205..96c77a41 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs @@ -58,17 +58,11 @@ fn get_encryption_key() -> Option<&'static SymmetricKeyHandle> { unsafe { ENCRYPTION_KEY.as_ref() } } -/// Helper function to convert WString to String for SDK calls -fn wstring_to_string(ws: &WString) -> alloc::string::String { - // WString stores UTF-16 in wchars field, convert to UTF-8 String - alloc::string::String::from_utf16_lossy(&ws.wchars) -} - /// Helper function to ensure user-bound key is loaded. /// Handles initial load attempt and optional reseal if needed. fn ensure_user_bound_key_loaded( - hello_key_name: &str, - pin_message: &str, + hello_key_name: &[u16], + pin_message: &[u16], window_id: u64, secured_encryption_key_bytes: &[u8], needs_reseal: &mut bool, @@ -170,23 +164,21 @@ impl Trusted for EnclaveImpl { let _ = debug_print(&String::from("CreateUserBoundKey: Starting")); let cache_config = create_secure_cache_config(); - let key_name = wstring_to_string(helloKeyName); - let pin_msg = wstring_to_string(pinMessage); let _ = debug_print(&format!( - "CreateUserBoundKey: key_name={}, windowId={}, option={}", - key_name, windowId, keyCredentialCreationOption + "CreateUserBoundKey: windowId={}, option={}", + windowId, keyCredentialCreationOption )); let _ = debug_print(&String::from( "CreateUserBoundKey: Calling SDK create_user_bound_key...", )); - // Create user-bound key with enclave sealing + // Create user-bound key with enclave sealing (pass wchars directly) let sealed_key = create_user_bound_key( - &key_name, + &helloKeyName.wchars, &cache_config, - &pin_msg, + &pinMessage.wchars, windowId, EnclaveSealingIdentityPolicy::SealToExactCode, RUNTIME_POLICY_NO_DEBUG, @@ -215,15 +207,13 @@ impl Trusted for EnclaveImpl { securedEncryptionKeyBytes: &Vec, inputData: &WString, ) -> Result { - let key_name = wstring_to_string(helloKeyName); - let pin_msg = wstring_to_string(pinMessage); let mut needs_reseal = false; let mut resealed_bytes = Vec::new(); - // Ensure key is loaded (handles reseal if needed) + // Ensure key is loaded (handles reseal if needed) - pass wchars directly ensure_user_bound_key_loaded( - &key_name, - &pin_msg, + &helloKeyName.wchars, + &pinMessage.wchars, windowId, securedEncryptionKeyBytes, &mut needs_reseal, @@ -267,8 +257,6 @@ impl Trusted for EnclaveImpl { securedEncryptionKeyBytes: &Vec, combinedInputData: &Vec, ) -> Result { - let key_name = wstring_to_string(helloKeyName); - let pin_msg = wstring_to_string(pinMessage); let mut needs_reseal = false; let mut resealed_bytes = Vec::new(); @@ -294,10 +282,10 @@ impl Trusted for EnclaveImpl { let tag = &combinedInputData[4..4 + tag_size]; let encrypted = &combinedInputData[4 + tag_size..]; - // Ensure key is loaded + // Ensure key is loaded - pass wchars directly ensure_user_bound_key_loaded( - &key_name, - &pin_msg, + &helloKeyName.wchars, + &pinMessage.wchars, windowId, securedEncryptionKeyBytes, &mut needs_reseal, diff --git a/rust/sdk/crates/samples/userboundkey/host/src/main.rs b/rust/sdk/crates/samples/userboundkey/host/src/main.rs index f054a009..52f55150 100644 --- a/rust/sdk/crates/samples/userboundkey/host/src/main.rs +++ b/rust/sdk/crates/samples/userboundkey/host/src/main.rs @@ -66,9 +66,9 @@ fn print_menu(is_key_loaded: bool) { io::stdout().flush().unwrap(); } -/// Create a WString from a Rust string +/// Create a WString from a Rust string (without null terminator - SDK adds it when needed) fn to_wstring(s: &str) -> WString { - let wchars: Vec = s.encode_utf16().chain(std::iter::once(0)).collect(); + let wchars: Vec = s.encode_utf16().collect(); WString { wchars } } From 4c0cf38a75c70d3293232f1ce13604e2f3f8cbca Mon Sep 17 00:00:00 2001 From: Soham Das Date: Wed, 21 Jan 2026 16:08:54 -0800 Subject: [PATCH 05/13] More changes to consume SDK --- rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 34 ++++++++++++++++- rust/sdk/crates/libs/sdk-host/src/lib.rs | 37 +++++++++++++++++++ .../samples/userboundkey/enclave/src/lib.rs | 16 ++------ .../samples/userboundkey/host/src/main.rs | 12 ++---- 4 files changed, 76 insertions(+), 23 deletions(-) diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index 8fb7685c..e0665aef 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -10,7 +10,37 @@ mod enclave_ffi; pub mod userboundkey; // Re-export the userboundkey generated enclave crate. -// This is needed by applications that use the SDK's userboundkey module so they can -// export the SDK's enclave functions (callback registration, attestation, etc.). +// This is used internally by the export_sdk_enclave_functions! macro. #[cfg(feature = "userboundkey")] pub use userboundkey_enclave_gen; + +/// Export all SDK enclave functions. +/// +/// Call this macro once in your enclave's lib.rs to enable SDK features. +/// This exports the necessary callback registration and trusted functions +/// that the SDK needs to communicate with VTL0. +/// +/// # Example +/// +/// ```rust,ignore +/// // In your enclave's lib.rs: +/// vbsenclave_sdk_enclave::export_sdk_enclave_functions!(); +/// ``` +/// +/// This is equivalent to manually exporting each SDK module's functions, +/// but hides the internal implementation details. +#[cfg(feature = "userboundkey")] +#[macro_export] +macro_rules! export_sdk_enclave_functions { + () => { + // Export SDK userboundkey enclave functions in a private module + // to avoid naming conflicts with the application's own exports + mod __sdk_userboundkey_exports { + $crate::userboundkey_enclave_gen::export_enclave_functions!( + $crate::userboundkey::TrustedImpl + ); + } + + // Future SDK features will be added here as additional modules + }; +} diff --git a/rust/sdk/crates/libs/sdk-host/src/lib.rs b/rust/sdk/crates/libs/sdk-host/src/lib.rs index 6a8a12c4..18877ad5 100644 --- a/rust/sdk/crates/libs/sdk-host/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-host/src/lib.rs @@ -14,3 +14,40 @@ pub mod userboundkey; // KCM surface. In practice, most consumers only need // KeyCredentialManager::GetSecureId(). pub use userboundkey_kcm::KeyCredentialManager; + +/// Register all SDK VTL0 callbacks with the enclave. +/// +/// Call this function once after loading your enclave to enable SDK features. +/// This registers the necessary callbacks that allow the enclave's SDK functions +/// to communicate back to VTL0 (e.g., for Windows Hello integration). +/// +/// # Arguments +/// +/// * `enclave_ptr` - Pointer to the loaded enclave (from `EnclaveHandle::as_ptr()`) +/// +/// # Returns +/// +/// Returns `Ok(())` on success, or an error string on failure. +/// +/// # Example +/// +/// ```rust,ignore +/// use vbsenclave_sdk_host::{register_sdk_callbacks, enclave::EnclaveHandle}; +/// +/// let enclave = EnclaveHandle::create_and_initialize(path, size, owner_id)?; +/// register_sdk_callbacks(enclave.as_ptr())?; +/// ``` +#[cfg(feature = "userboundkey")] +pub fn register_sdk_callbacks(enclave_ptr: *mut core::ffi::c_void) -> Result<(), String> { + // Register userboundkey callbacks + let sdk_wrapper = userboundkey::UserBoundKeyVtl0Host::new(enclave_ptr); + sdk_wrapper + .register_vtl0_callbacks::() + .map_err(|e| format!("Failed to register SDK userboundkey callbacks: {:?}", e))?; + + // Future SDK features will be added here: + // #[cfg(feature = "secure_storage")] + // { ... register secure_storage callbacks ... } + + Ok(()) +} diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs index fab03a79..322c0860 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs @@ -24,19 +24,9 @@ fn panic(_panic: &PanicInfo<'_>) -> ! { // Export the sample's enclave functions (CreateUserBoundKey, LoadUserBoundKeyAndEncryptData, etc.) userboundkey_sample_enclave_gen::export_enclave_functions!(edl_impls::EnclaveImpl); -// Export the SDK's userboundkey enclave functions in a separate module to avoid naming conflicts. -// This is necessary because: -// 1. The SDK's userboundkey module (create_user_bound_key, load_user_bound_key, etc.) -// internally calls VTL0 through the userboundkey.edl untrusted functions. -// 2. The host needs to register those callbacks, which requires the -// __AbiRegisterVtl0Callbacks_userboundkey__ export. -// 3. The SDK also exports trusted functions (get_attestation_report, close_session) -// that are called by the Windows Hello attestation flow. -mod sdk_exports { - vbsenclave_sdk_enclave::userboundkey_enclave_gen::export_enclave_functions!( - vbsenclave_sdk_enclave::userboundkey::TrustedImpl - ); -} +// Export the SDK's enclave functions (userboundkey callbacks, attestation, etc.) +// This single macro call handles all SDK features and hides internal implementation details. +vbsenclave_sdk_enclave::export_sdk_enclave_functions!(); /// Enclave policy flags - enable debugging in debug builds pub const ENCLAVE_CONFIG_POLICY_FLAGS: u32 = { diff --git a/rust/sdk/crates/samples/userboundkey/host/src/main.rs b/rust/sdk/crates/samples/userboundkey/host/src/main.rs index 52f55150..a109c5b1 100644 --- a/rust/sdk/crates/samples/userboundkey/host/src/main.rs +++ b/rust/sdk/crates/samples/userboundkey/host/src/main.rs @@ -16,8 +16,7 @@ use std::path::{Path, PathBuf}; // VBS Enclave SDK for loading enclaves use vbsenclave_sdk_host::KeyCredentialManager; use vbsenclave_sdk_host::enclave::{EnclaveHandle, megabytes}; -// SDK's userboundkey module for VTL0 callbacks -use vbsenclave_sdk_host::userboundkey::{UntrustedImpl as SdkUntrustedImpl, UserBoundKeyVtl0Host}; +use vbsenclave_sdk_host::register_sdk_callbacks; // Windows APIs for buffer conversion use windows::Security::Cryptography::CryptographicBuffer; @@ -128,12 +127,9 @@ fn load_enclave( .register_vtl0_callbacks::() .map_err(|e| format!("Failed to register sample callbacks: {:?}", e))?; - // Register the SDK's VTL0 callbacks so enclave can call back to host - // The SDK enclave functions (create_user_bound_key, etc.) use these callbacks - let sdk_wrapper = UserBoundKeyVtl0Host::new(enclave.as_ptr()); - sdk_wrapper - .register_vtl0_callbacks::() - .map_err(|e| format!("Failed to register SDK callbacks: {:?}", e))?; + // Register all SDK VTL0 callbacks so enclave can call back to host + // This single function call handles all SDK features (userboundkey, etc.) + register_sdk_callbacks(enclave.as_ptr())?; println!("Enclave loaded and callbacks registered successfully."); Ok((enclave, wrapper)) From ccde232ba6a8944b5921cd825d178878da652148 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Wed, 21 Jan 2026 17:05:11 -0800 Subject: [PATCH 06/13] Addressed copilot comments --- .../libs/sdk-host/src/userboundkey/mod.rs | 35 ++++++++-- .../sdk/crates/samples/userboundkey/README.md | 4 +- .../userboundkey/enclave/src/edl_impls.rs | 65 +++++++------------ .../samples/userboundkey/host/src/main.rs | 6 +- 4 files changed, 57 insertions(+), 53 deletions(-) diff --git a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs index 3955b8d5..b45008ba 100644 --- a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs @@ -24,7 +24,7 @@ use windows::{ Security::Cryptography::CryptographicBuffer, Storage::Streams::IBuffer, Win32::{ - Foundation::{CloseHandle, E_FAIL, E_INVALIDARG, HANDLE}, + Foundation::{CloseHandle, E_FAIL, E_INVALIDARG, HANDLE, HLOCAL, LocalFree}, Security::Authorization::ConvertSidToStringSidW, Security::{GetTokenInformation, PSID, TOKEN_QUERY, TOKEN_USER, TokenUser}, System::Threading::{GetCurrentProcess, OpenProcessToken}, @@ -187,7 +187,17 @@ fn create_challenge_callback( // Store session handle { - let mut session = session_info.lock().unwrap(); + let mut session = match session_info.lock() { + Ok(guard) => guard, + Err(poisoned) => { + println!( + "[SDK-Host] Warning: session_info mutex was poisoned: {:?}", + poisoned + ); + // Recover the guard from the poisoned mutex - the data may still be usable + poisoned.into_inner() + } + }; session.set(attestation_result.sessionInfo as usize, enclave_ptr); } @@ -207,9 +217,8 @@ fn sid_to_wide_string(sid: PSID) -> Result, AbiError> { let slice = std::slice::from_raw_parts(string_sid.0, len); let result = slice.to_vec(); - // Note: We're leaking the string memory here since LocalFree requires more setup. - // In a production implementation, proper cleanup should be added. - let _ = string_sid.0; // Prevent unused warning + // Free the memory allocated by ConvertSidToStringSidW + let _ = LocalFree(Some(HLOCAL(string_sid.0 as *mut _))); Ok(result) } @@ -308,7 +317,13 @@ impl Untrusted for UntrustedImpl { // Transfer session ownership let session_handle = { - let mut session = session_info.lock().unwrap(); + let mut session = match session_info.lock() { + Ok(guard) => guard, + Err(poisoned) => { + println!("[SDK-Host] Warning: session_info mutex was poisoned"); + poisoned.into_inner() + } + }; session.release() as u64 }; @@ -360,7 +375,13 @@ impl Untrusted for UntrustedImpl { // Transfer session ownership let session_handle = { - let mut session = session_info.lock().unwrap(); + let mut session = match session_info.lock() { + Ok(guard) => guard, + Err(poisoned) => { + println!("[SDK-Host] Warning: session_info mutex was poisoned"); + poisoned.into_inner() + } + }; session.release() as u64 }; diff --git a/rust/sdk/crates/samples/userboundkey/README.md b/rust/sdk/crates/samples/userboundkey/README.md index e2664a47..e250ac25 100644 --- a/rust/sdk/crates/samples/userboundkey/README.md +++ b/rust/sdk/crates/samples/userboundkey/README.md @@ -171,8 +171,8 @@ Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\D When running the sample, the following files are created in the current directory: -- `userboundkey.sealed` - The sealed encryption key -- `encrypted_userbound.bin` - Encrypted data output +- `MyEncryptionKey-001` - The sealed encryption key +- `encrypted_userbound` - Encrypted data output ## Architecture diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs index 96c77a41..f35783b9 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs @@ -74,13 +74,9 @@ fn ensure_user_bound_key_loaded( } let secure_config = create_secure_cache_config(); - #[allow(unused_assignments)] - let mut loaded_key_bytes: Vec = Vec::new(); - #[allow(unused_assignments)] - let mut load_succeeded = false; - // First attempt to load the user-bound key - match load_user_bound_key( + // Try to load the user-bound key, with fallback to reseal on failure + let loaded_key_bytes = match load_user_bound_key( hello_key_name, &secure_config, pin_message, @@ -88,59 +84,44 @@ fn ensure_user_bound_key_loaded( secured_encryption_key_bytes, ) { Ok((key_bytes, stale)) => { - loaded_key_bytes = key_bytes; - load_succeeded = true; - // If stale, mark for reseal if stale { *needs_reseal = true; if let Ok(resealed) = reseal_user_bound_key( - &loaded_key_bytes, + &key_bytes, EnclaveSealingIdentityPolicy::SealToExactCode, RUNTIME_POLICY_NO_DEBUG, ) { *resealed_encryption_key_bytes = resealed; } } + key_bytes } Err(e) => { // First load failed, attempt reseal and retry - match reseal_user_bound_key( + let resealed_bytes = reseal_user_bound_key( secured_encryption_key_bytes, EnclaveSealingIdentityPolicy::SealToExactCode, RUNTIME_POLICY_NO_DEBUG, - ) { - Ok(resealed_bytes) => { - *needs_reseal = true; - *resealed_encryption_key_bytes = resealed_bytes.clone(); - - // Retry loading with resealed bytes - match load_user_bound_key( - hello_key_name, - &secure_config, - pin_message, - window_id, - &resealed_bytes, - ) { - Ok((key_bytes, _)) => { - loaded_key_bytes = key_bytes; - load_succeeded = true; - } - Err(e2) => { - return Err(AbiError::Hresult(e2.to_hresult())); - } - } - } - Err(_) => { - return Err(AbiError::Hresult(e.to_hresult())); - } - } + ) + .map_err(|_| AbiError::Hresult(e.to_hresult()))?; + + *needs_reseal = true; + *resealed_encryption_key_bytes = resealed_bytes.clone(); + + // Retry loading with resealed bytes + let (key_bytes, _) = load_user_bound_key( + hello_key_name, + &secure_config, + pin_message, + window_id, + &resealed_bytes, + ) + .map_err(|e2| AbiError::Hresult(e2.to_hresult()))?; + + key_bytes } - } - - if !load_succeeded { - return Err(AbiError::Hresult(-2147024809)); // E_INVALIDARG - } + }; // Create symmetric key from loaded raw key material let symmetric_key = SymmetricKeyHandle::from_bytes(&loaded_key_bytes) diff --git a/rust/sdk/crates/samples/userboundkey/host/src/main.rs b/rust/sdk/crates/samples/userboundkey/host/src/main.rs index a109c5b1..bc6d40ee 100644 --- a/rust/sdk/crates/samples/userboundkey/host/src/main.rs +++ b/rust/sdk/crates/samples/userboundkey/host/src/main.rs @@ -21,6 +21,7 @@ use vbsenclave_sdk_host::register_sdk_callbacks; // Windows APIs for buffer conversion use windows::Security::Cryptography::CryptographicBuffer; use windows::Win32::UI::WindowsAndMessaging::GetForegroundWindow; +use windows::core::Interface; // Generated host stubs for enclave calls use userboundkey_sample_host_gen::implementation::types::edl::WString; @@ -80,8 +81,9 @@ fn get_secure_id_from_windows_hello() -> Result, String> { .map_err(|e| format!("Failed to get secure ID: {:?}", e))?; // Cast from userboundkey_kcm::IBuffer to windows::Storage::Streams::IBuffer - let windows_buffer: windows::Storage::Streams::IBuffer = - unsafe { std::mem::transmute(secure_id_buffer) }; + let windows_buffer: windows::Storage::Streams::IBuffer = secure_id_buffer + .cast() + .map_err(|e| format!("Failed to cast IBuffer: {:?}", e))?; // Convert IBuffer to Vec using CryptographicBuffer let mut byte_array = windows::core::Array::::new(); From 25f6d877743020608c9ee4288bdec849fe2df9f4 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Thu, 22 Jan 2026 13:19:29 -0800 Subject: [PATCH 07/13] Addressing PR comments (in progress) --- rust/scripts/generate_codegen_crates.ps1 | 7 +- rust/sdk/Cargo.toml | 2 +- rust/sdk/crates/libs/sdk-enclave/Cargo.toml | 5 +- rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 3 - .../sdk-enclave/src/userboundkey/crypto.rs | 183 ++----------- .../libs/sdk-enclave/src/userboundkey/mod.rs | 241 +++++++++++------- .../sdk-enclave/src/userboundkey/types.rs | 29 ++- rust/sdk/crates/libs/sdk-host/Cargo.toml | 5 +- rust/sdk/crates/libs/sdk-host/src/lib.rs | 2 - .../libs/sdk-host/src/userboundkey/mod.rs | 6 +- .../crates/libs/windows-enclave/src/lib.rs | 1 + .../src/manual_bindings/bcrypt.rs | 68 +++++ .../src/manual_bindings/mod.rs | 1 + .../src/manual_bindings/vertdll.rs | 1 + .../samples/userboundkey/enclave/Cargo.toml | 2 +- .../userboundkey/enclave/src/edl_impls.rs | 27 +- .../samples/userboundkey/host/Cargo.toml | 2 +- 17 files changed, 285 insertions(+), 300 deletions(-) create mode 100644 rust/sdk/crates/libs/windows-enclave/src/manual_bindings/bcrypt.rs diff --git a/rust/scripts/generate_codegen_crates.ps1 b/rust/scripts/generate_codegen_crates.ps1 index df4a496d..6aeb05f0 100644 --- a/rust/scripts/generate_codegen_crates.ps1 +++ b/rust/scripts/generate_codegen_crates.ps1 @@ -23,9 +23,6 @@ $ErrorActionPreference = "Stop" # Import helper that sets $edlCodeGenToolsPath . "$PSScriptRoot\get_codegen_executable.ps1" -# Build import directories argument if provided -$importDirArg = if ($ImportDirectories -ne "") { "--ImportDirectories", $ImportDirectories } else { @() } - # Run codegen for the enclave crate & $edlCodeGenToolsPath ` --namespace $Namespace ` @@ -33,7 +30,7 @@ $importDirArg = if ($ImportDirectories -ne "") { "--ImportDirectories", $ImportD --EdlPath $EdlPath ` --VirtualTrustLayer enclave ` --OutputDirectory $EnclaveOutDir ` - @importDirArg + --ImportDirectories $ImportDirectories # Run codegen for the host crate & $edlCodeGenToolsPath ` @@ -43,4 +40,4 @@ $importDirArg = if ($ImportDirectories -ne "") { "--ImportDirectories", $ImportD --VirtualTrustLayer hostapp ` --Vtl0ClassName $Vtl0ClassName ` --OutputDirectory $HostAppOutDir ` - @importDirArg + --ImportDirectories $ImportDirectories diff --git a/rust/sdk/Cargo.toml b/rust/sdk/Cargo.toml index b4adb598..ac1bc93c 100644 --- a/rust/sdk/Cargo.toml +++ b/rust/sdk/Cargo.toml @@ -30,9 +30,9 @@ edlcodegen-tools = { git = "https://github.com/microsoft/VbsEnclaveTooling" } regex = "1.11" spin = "0.10.0" userboundkey-kcm = { version = "0.1.0", path = "crates/libs/userboundkey-kcm" } -userboundkey_host_gen = { version = "0.0.0", path = "crates/libs/sdk-host/generated/userboundkey_gen" } vbsenclave-sdk-enclave = { version = "0.1.0", path = "crates/libs/sdk-enclave" } vbsenclave-sdk-host = { version = "0.1.0", path = "crates/libs/sdk-host" } +widestring = { version = "1.1", default-features = false, features = ["alloc"] } windows = "0.62.2" windows-bindgen = "0.66.0" windows-enclave = { version = "0.1.0", path = "crates/libs/windows-enclave" } diff --git a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml index c1388651..c34abc27 100644 --- a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml @@ -3,11 +3,8 @@ name = "vbsenclave-sdk-enclave" version = "0.1.0" edition = { workspace = true } -[features] -default = [] -userboundkey = [] - [dependencies] spin = { workspace = true } userboundkey_enclave_gen = { path = "./generated/userboundkey_gen" } +widestring = { workspace = true } windows-enclave = { workspace = true } diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index e0665aef..5c601c0b 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -6,12 +6,10 @@ extern crate alloc; mod enclave_ffi; -#[cfg(feature = "userboundkey")] pub mod userboundkey; // Re-export the userboundkey generated enclave crate. // This is used internally by the export_sdk_enclave_functions! macro. -#[cfg(feature = "userboundkey")] pub use userboundkey_enclave_gen; /// Export all SDK enclave functions. @@ -29,7 +27,6 @@ pub use userboundkey_enclave_gen; /// /// This is equivalent to manually exporting each SDK module's functions, /// but hides the internal implementation details. -#[cfg(feature = "userboundkey")] #[macro_export] macro_rules! export_sdk_enclave_functions { () => { diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs index 112a7603..60664ab7 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs @@ -9,13 +9,11 @@ use alloc::vec::Vec; use windows_enclave::bcrypt::{ - BCRYPT_AES_GCM_ALG_HANDLE, BCRYPT_KEY_HANDLE, BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptDecrypt, - BCryptDestroyKey, BCryptEncrypt, BCryptGenRandom, BCryptGenerateSymmetricKey, -}; -use windows_enclave::vertdll::{ - ENCLAVE_UNSEAL_FLAG_STALE_KEY, EnclaveSealData, EnclaveUnsealData, GetProcessHeap, - HEAP_ZERO_MEMORY, HeapAlloc, HeapFree, + BCRYPT_AES_GCM_ALG_HANDLE, BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO, BCRYPT_KEY_HANDLE, + BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptDecrypt, BCryptDestroyKey, BCryptEncrypt, + BCryptGenRandom, BCryptGenerateSymmetricKey, }; +use windows_enclave::vertdll::{ENCLAVE_UNSEAL_FLAG_STALE_KEY, EnclaveSealData, EnclaveUnsealData}; use super::types::UserBoundKeyError; @@ -59,63 +57,6 @@ pub enum EnclaveSealingIdentityPolicy { SealToSigner = 2, } -// -// RAII Helpers -// - -/// RAII wrapper for heap-allocated memory -pub struct HeapBuffer { - ptr: *mut core::ffi::c_void, - size: usize, -} - -impl HeapBuffer { - /// Allocate a new heap buffer of the specified size - pub fn new(size: usize) -> Option { - unsafe { - let heap = GetProcessHeap(); - let ptr = HeapAlloc(heap, HEAP_ZERO_MEMORY, size); - if ptr.is_null() { - None - } else { - Some(Self { ptr, size }) - } - } - } - - /// Get a const pointer to the buffer - #[allow(dead_code)] - pub fn as_ptr(&self) -> *const core::ffi::c_void { - self.ptr - } - - /// Get a mutable pointer to the buffer - pub fn as_mut_ptr(&mut self) -> *mut core::ffi::c_void { - self.ptr - } - - /// Get the buffer contents as a slice - pub fn as_slice(&self) -> &[u8] { - unsafe { core::slice::from_raw_parts(self.ptr as *const u8, self.size) } - } - - /// Copy the buffer contents to a Vec - pub fn to_vec(&self) -> Vec { - self.as_slice().to_vec() - } -} - -impl Drop for HeapBuffer { - fn drop(&mut self) { - if !self.ptr.is_null() { - unsafe { - let heap = GetProcessHeap(); - HeapFree(heap, 0, self.ptr); - } - } - } -} - // // Helper Functions // @@ -138,9 +79,7 @@ fn check_ntstatus(status: i32) -> Result<(), UserBoundKeyError> { if status >= 0 { Ok(()) } else { - // Convert NTSTATUS to HRESULT-like error - // NTSTATUS errors have the high bit set; HRESULT uses 0x8xxxxxxx format - Err(UserBoundKeyError::Hresult(status)) + Err(UserBoundKeyError::NtStatus(status)) } } @@ -208,22 +147,21 @@ pub fn seal_data( ); check_hr(hr)?; - // Allocate buffer and seal - let mut sealed_buffer = - HeapBuffer::new(sealed_size as usize).ok_or(UserBoundKeyError::AllocationFailed)?; + // Allocate Vec and seal directly into it + let mut sealed_buffer = alloc::vec![0u8; sealed_size as usize]; let hr = EnclaveSealData( data.as_ptr() as *const core::ffi::c_void, data.len() as u32, sealing_policy as i32, runtime_policy, - sealed_buffer.as_mut_ptr(), + sealed_buffer.as_mut_ptr() as *mut core::ffi::c_void, sealed_size, &mut sealed_size, ); check_hr(hr)?; - Ok(sealed_buffer.to_vec()) + Ok(sealed_buffer) } } @@ -252,15 +190,14 @@ pub fn unseal_data(sealed_data: &[u8]) -> Result<(Vec, u32), UserBoundKeyErr ); check_hr(hr)?; - // Allocate buffer and unseal - let mut unsealed_buffer = - HeapBuffer::new(unsealed_size as usize).ok_or(UserBoundKeyError::AllocationFailed)?; + // Allocate Vec and unseal directly into it + let mut unsealed_buffer = alloc::vec![0u8; unsealed_size as usize]; let mut unseal_flags: u32 = 0; let hr = EnclaveUnsealData( sealed_data.as_ptr() as *const core::ffi::c_void, sealed_data.len() as u32, - unsealed_buffer.as_mut_ptr(), + unsealed_buffer.as_mut_ptr() as *mut core::ffi::c_void, unsealed_size, &mut unsealed_size, core::ptr::null_mut(), @@ -268,7 +205,7 @@ pub fn unseal_data(sealed_data: &[u8]) -> Result<(Vec, u32), UserBoundKeyErr ); check_hr(hr)?; - Ok((unsealed_buffer.to_vec(), unseal_flags)) + Ok((unsealed_buffer, unseal_flags)) } } @@ -287,7 +224,7 @@ pub fn is_stale_key(unseal_flags: u32) -> bool { /// Create a nonce buffer from a numeric value /// -/// Creates a 12-byte nonce with the value placed at the end (big-endian position). +/// Creates a 12-byte nonce with the value placed at the end (little-endian). #[allow(dead_code)] pub fn make_nonce_from_number(nonce: u64) -> [u8; NONCE_SIZE] { let mut buffer = [0u8; NONCE_SIZE]; @@ -300,94 +237,6 @@ pub fn make_nonce_from_number(nonce: u64) -> [u8; NONCE_SIZE] { /// Zero nonce for AES-GCM (all zeros) pub const ZERO_NONCE: [u8; NONCE_SIZE] = [0u8; NONCE_SIZE]; -// -// BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure -// This is not in the generated bindings, so we define it manually. -// See: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_authenticated_cipher_mode_info -// - -/// Version for BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO -const BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION: u32 = 1; - -/// Authenticated cipher mode info structure for AES-GCM -#[repr(C)] -#[derive(Clone)] -pub struct BcryptAuthenticatedCipherModeInfo { - pub cb_size: u32, - pub dw_info_version: u32, - pub pb_nonce: *mut u8, - pub cb_nonce: u32, - pub pb_auth_data: *mut u8, - pub cb_auth_data: u32, - pub pb_tag: *mut u8, - pub cb_tag: u32, - pub pb_mac_context: *mut u8, - pub cb_mac_context: u32, - pub cb_aad: u32, - pub cb_data: u64, - pub dw_flags: u32, -} - -impl Default for BcryptAuthenticatedCipherModeInfo { - fn default() -> Self { - Self { - cb_size: core::mem::size_of::() as u32, - dw_info_version: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, - pb_nonce: core::ptr::null_mut(), - cb_nonce: 0, - pb_auth_data: core::ptr::null_mut(), - cb_auth_data: 0, - pb_tag: core::ptr::null_mut(), - cb_tag: 0, - pb_mac_context: core::ptr::null_mut(), - cb_mac_context: 0, - cb_aad: 0, - cb_data: 0, - dw_flags: 0, - } - } -} - -impl BcryptAuthenticatedCipherModeInfo { - /// Create a new cipher mode info for encryption (tag will be written) - pub fn for_encrypt(nonce: &mut [u8], tag: &mut [u8]) -> Self { - Self { - cb_size: core::mem::size_of::() as u32, - dw_info_version: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, - pb_nonce: nonce.as_mut_ptr(), - cb_nonce: nonce.len() as u32, - pb_auth_data: core::ptr::null_mut(), - cb_auth_data: 0, - pb_tag: tag.as_mut_ptr(), - cb_tag: tag.len() as u32, - pb_mac_context: core::ptr::null_mut(), - cb_mac_context: 0, - cb_aad: 0, - cb_data: 0, - dw_flags: 0, - } - } - - /// Create a new cipher mode info for decryption (tag is input) - pub fn for_decrypt(nonce: &[u8], tag: &[u8]) -> Self { - Self { - cb_size: core::mem::size_of::() as u32, - dw_info_version: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, - pb_nonce: nonce.as_ptr() as *mut u8, - cb_nonce: nonce.len() as u32, - pb_auth_data: core::ptr::null_mut(), - cb_auth_data: 0, - pb_tag: tag.as_ptr() as *mut u8, - cb_tag: tag.len() as u32, - pb_mac_context: core::ptr::null_mut(), - cb_mac_context: 0, - cb_aad: 0, - cb_data: 0, - dw_flags: 0, - } - } -} - // // Symmetric Key Handle (RAII wrapper) // @@ -459,7 +308,7 @@ pub fn encrypt( let mut ciphertext = alloc::vec![0u8; plaintext.len()]; let mut result_size: u32 = 0; - let cipher_info = BcryptAuthenticatedCipherModeInfo::for_encrypt(&mut nonce_copy, &mut tag); + let cipher_info = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO::for_encrypt(&mut nonce_copy, &mut tag); unsafe { let status = BCryptEncrypt( @@ -500,7 +349,7 @@ pub fn decrypt( let mut plaintext = alloc::vec![0u8; ciphertext.len()]; let mut result_size: u32 = 0; - let cipher_info = BcryptAuthenticatedCipherModeInfo::for_decrypt(nonce, tag); + let cipher_info = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO::for_decrypt(nonce, tag); unsafe { let status = BCryptDecrypt( diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs index e2b5d9cc..d55b4dba 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs @@ -20,11 +20,16 @@ pub use types::*; // Re-export the cache config type for use by samples pub use userboundkey_enclave_gen::implementation::types::keyCredentialCacheConfig; +// Re-export widestring types for public API consumers +pub use widestring::U16Str; + use alloc::vec::Vec; +use userboundkey_enclave_gen::AbiError; use userboundkey_enclave_gen::implementation::trusted::Trusted; use userboundkey_enclave_gen::implementation::types::attestationReportAndSessionInfo; use userboundkey_enclave_gen::stubs::untrusted; +use widestring::U16String; use windows_enclave::veinterop::{ CloseUserBoundKeyAuthContext, CloseUserBoundKeySession, @@ -44,6 +49,48 @@ use utils::get_enclave_base_address_u64; // Stored as u64 for EDL interface compatibility. const BCRYPT_ECDH_P384_ALG_HANDLE_U64: u64 = 0x000002b1; +/// RAII wrapper for heap-allocated buffers returned by veinterop APIs. +/// Automatically frees the buffer via HeapFree when dropped. +struct HeapBuffer { + ptr: *mut core::ffi::c_void, + size: u32, +} + +impl HeapBuffer { + /// Takes ownership of a heap-allocated buffer. + /// + /// # Safety + /// The pointer must have been allocated via GetProcessHeap/HeapAlloc + /// and the size must be accurate. + unsafe fn from_raw(ptr: *mut core::ffi::c_void, size: u32) -> Self { + Self { ptr, size } + } + + /// Returns true if the buffer is null or empty. + fn is_empty(&self) -> bool { + self.ptr.is_null() || self.size == 0 + } + + /// Copies the buffer contents to a Vec. + fn to_vec(&self) -> Vec { + if self.is_empty() { + return Vec::new(); + } + unsafe { core::slice::from_raw_parts(self.ptr as *const u8, self.size as usize).to_vec() } + } +} + +impl Drop for HeapBuffer { + fn drop(&mut self) { + if !self.ptr.is_null() { + unsafe { + let heap = GetProcessHeap(); + HeapFree(heap, 0, self.ptr); + } + } + } +} + /// RAII wrapper for session handle struct UniqueSessionHandle(USER_BOUND_KEY_SESSION_HANDLE); @@ -150,11 +197,11 @@ fn get_ephemeral_public_key_bytes_from_bound_key( /// Validate that formatted key name ends with expected suffix (wide string version) fn validate_formatted_key_name_wide( - formatted_key_name: &[u16], - original_key_name: &[u16], + formatted_key_name: &U16Str, + original_key_name: &U16Str, ) -> Result<(), UserBoundKeyError> { // Build expected suffix: "//" + original_key_name - let slash_slash: [u16; 2] = ['/' as u16, '/' as u16]; + let slash_slash = U16String::from_str("//"); let expected_suffix_len = 2 + original_key_name.len(); if formatted_key_name.len() < expected_suffix_len { @@ -165,9 +212,11 @@ fn validate_formatted_key_name_wide( // Check if formatted_key_name ends with "//{original_key_name}" let suffix_start = formatted_key_name.len() - expected_suffix_len; - let formatted_suffix = &formatted_key_name[suffix_start..]; + let formatted_suffix = &formatted_key_name.as_slice()[suffix_start..]; - if formatted_suffix[0..2] != slash_slash || formatted_suffix[2..] != *original_key_name { + if formatted_suffix[0..2] != slash_slash.as_slice()[..] + || formatted_suffix[2..] != *original_key_name.as_slice() + { return Err(UserBoundKeyError::SecurityViolation( "Formatted key name does not match expected pattern", )); @@ -195,9 +244,9 @@ fn validate_formatted_key_name_wide( /// # Returns /// Sealed key material that can be stored and later loaded pub fn create_user_bound_key( - key_name: &[u16], + key_name: &U16Str, cache_config: &keyCredentialCacheConfig, - message: &[u16], + message: &U16Str, window_id: u64, sealing_policy: EnclaveSealingIdentityPolicy, runtime_policy: u32, @@ -220,9 +269,9 @@ pub fn create_user_bound_key( /// Create a user-bound key with custom key bytes pub fn create_user_bound_key_with_custom_key( - key_name: &[u16], + key_name: &U16Str, cache_config: &keyCredentialCacheConfig, - message: &[u16], + message: &U16Str, window_id: u64, sealing_policy: EnclaveSealingIdentityPolicy, runtime_policy: u32, @@ -232,20 +281,21 @@ pub fn create_user_bound_key_with_custom_key( // Format the key name (calls VTL0) - this is the FIRST VTL0 call // Create WString from key_name slice let key_name_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { - wchars: key_name.to_vec(), + wchars: key_name.as_slice().to_vec(), }; let formatted_key_name_wstring = untrusted::userboundkey_format_key_name(&key_name_wstring) .map_err(|e| UserBoundKeyError::AbiError(e))?; // Security validation (wide string version) - validate_formatted_key_name_wide(&formatted_key_name_wstring.wchars, key_name)?; + let formatted_key_name_u16str = U16Str::from_slice(&formatted_key_name_wstring.wchars); + validate_formatted_key_name_wide(formatted_key_name_u16str, key_name)?; // Get enclave base address let enclave_ptr = get_enclave_base_address_u64()?; // Create WString for message let message_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { - wchars: message.to_vec(), + wchars: message.as_slice().to_vec(), }; // Establish session (calls VTL0 which triggers Windows Hello) @@ -272,14 +322,13 @@ pub fn create_user_bound_key_with_custom_key( let mut encrypted_request_size: u32 = 0; let mut local_nonce: u64 = 0; - // Add null terminator for Windows API - let mut formatted_key_name_wide: Vec = formatted_key_name_wstring.wchars.clone(); - formatted_key_name_wide.push(0); + // Create null-terminated wide string for Windows API + let formatted_key_name_with_null = U16String::from(formatted_key_name_u16str); unsafe { let hr = CreateUserBoundKeyRequestForRetrieveAuthorizationContext( session_handle.get(), - formatted_key_name_wide.as_ptr(), + formatted_key_name_with_null.as_ptr(), &mut local_nonce, &mut encrypted_request_ptr, &mut encrypted_request_size, @@ -287,21 +336,18 @@ pub fn create_user_bound_key_with_custom_key( check_hr(hr)?; } - // Convert to Vec - let encrypted_request = unsafe { - core::slice::from_raw_parts( - encrypted_request_ptr as *const u8, - encrypted_request_size as usize, - ) - .to_vec() - }; + // Take ownership of the heap-allocated buffer + let encrypted_request_buf = + unsafe { HeapBuffer::from_raw(encrypted_request_ptr, encrypted_request_size) }; - // Free the allocated memory - unsafe { - let heap = GetProcessHeap(); - HeapFree(heap, 0, encrypted_request_ptr); + // Defensive check - API contract guarantees valid output on S_OK + if encrypted_request_buf.is_empty() { + return Err(UserBoundKeyError::InvalidData("API returned empty request")); } + // Convert to Vec (HeapBuffer will free on drop) + let encrypted_request = encrypted_request_buf.to_vec(); + // Get authorization context from credential (calls VTL0) let auth_context_blob = untrusted::userboundkey_get_authorization_context_from_credential( credential.get(), @@ -334,7 +380,7 @@ pub fn create_user_bound_key_with_custom_key( unsafe { let hr = ValidateUserBoundKeyAuthContext( - formatted_key_name_wide.as_ptr(), + formatted_key_name_with_null.as_ptr(), auth_context.get(), 1, &prop, @@ -357,17 +403,19 @@ pub fn create_user_bound_key_with_custom_key( check_hr(hr)?; } - // Convert bound key to Vec - let bound_key_bytes = unsafe { - core::slice::from_raw_parts(bound_key_ptr as *const u8, bound_key_size as usize).to_vec() - }; + // Take ownership of the heap-allocated buffer + let bound_key_buf = unsafe { HeapBuffer::from_raw(bound_key_ptr, bound_key_size) }; - // Free the allocated memory - unsafe { - let heap = GetProcessHeap(); - HeapFree(heap, 0, bound_key_ptr); + // Defensive check - API contract guarantees valid output on S_OK + if bound_key_buf.is_empty() { + return Err(UserBoundKeyError::InvalidData( + "API returned empty bound key", + )); } + // Convert bound key to Vec (HeapBuffer will free on drop) + let bound_key_bytes = bound_key_buf.to_vec(); + // Seal the bound key let sealed_key = seal_data(&bound_key_bytes, sealing_policy, runtime_policy)?; @@ -387,9 +435,9 @@ pub fn create_user_bound_key_with_custom_key( /// A tuple of (decrypted user key bytes, needs_reseal flag). /// If `needs_reseal` is true, the caller should reseal the key with [`reseal_user_bound_key`]. pub fn load_user_bound_key( - key_name: &[u16], + key_name: &U16Str, cache_config: &keyCredentialCacheConfig, - message: &[u16], + message: &U16Str, window_id: u64, sealed_bound_key_bytes: &[u8], ) -> Result<(Vec, bool), UserBoundKeyError> { @@ -406,20 +454,21 @@ pub fn load_user_bound_key( // Format the key name (calls VTL0) let key_name_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { - wchars: key_name.to_vec(), + wchars: key_name.as_slice().to_vec(), }; let formatted_key_name_wstring = untrusted::userboundkey_format_key_name(&key_name_wstring) .map_err(|e| UserBoundKeyError::AbiError(e))?; // Security validation (wide string version) - validate_formatted_key_name_wide(&formatted_key_name_wstring.wchars, key_name)?; + let formatted_key_name_u16str = U16Str::from_slice(&formatted_key_name_wstring.wchars); + validate_formatted_key_name_wide(formatted_key_name_u16str, key_name)?; // Get enclave base address let enclave_ptr = get_enclave_base_address_u64()?; // Create WString for message let message_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { - wchars: message.to_vec(), + wchars: message.as_slice().to_vec(), }; // Establish session for load (calls VTL0) @@ -442,14 +491,13 @@ pub fn load_user_bound_key( let mut encrypted_rac_size: u32 = 0; let mut local_nonce: u64 = 0; - // Add null terminator for Windows API - let mut formatted_key_name_wide: Vec = formatted_key_name_wstring.wchars.clone(); - formatted_key_name_wide.push(0); + // Create null-terminated wide string for Windows API + let formatted_key_name_with_null = U16String::from(formatted_key_name_u16str); unsafe { let hr = CreateUserBoundKeyRequestForRetrieveAuthorizationContext( session_handle.get(), - formatted_key_name_wide.as_ptr(), + formatted_key_name_with_null.as_ptr(), &mut local_nonce, &mut encrypted_rac_ptr, &mut encrypted_rac_size, @@ -457,16 +505,17 @@ pub fn load_user_bound_key( check_hr(hr)?; } - let encrypted_rac_request = unsafe { - core::slice::from_raw_parts(encrypted_rac_ptr as *const u8, encrypted_rac_size as usize) - .to_vec() - }; + // Take ownership of the heap-allocated buffer + let encrypted_rac_buf = unsafe { HeapBuffer::from_raw(encrypted_rac_ptr, encrypted_rac_size) }; - unsafe { - let heap = GetProcessHeap(); - HeapFree(heap, 0, encrypted_rac_ptr); + // Defensive check - API contract guarantees valid output on S_OK + if encrypted_rac_buf.is_empty() { + return Err(UserBoundKeyError::InvalidData("API returned empty request")); } + // Convert to Vec (HeapBuffer will free on drop) + let encrypted_rac_request = encrypted_rac_buf.to_vec(); + // Get authorization context (calls VTL0) let auth_context_blob = untrusted::userboundkey_get_authorization_context_from_credential( credential.get(), @@ -499,7 +548,7 @@ pub fn load_user_bound_key( unsafe { let hr = ValidateUserBoundKeyAuthContext( - formatted_key_name_wide.as_ptr(), + formatted_key_name_with_null.as_ptr(), auth_context.get(), 1, &prop, @@ -514,7 +563,7 @@ pub fn load_user_bound_key( unsafe { let hr = CreateUserBoundKeyRequestForDeriveSharedSecret( session_handle.get(), - formatted_key_name_wide.as_ptr(), + formatted_key_name_with_null.as_ptr(), ephemeral_public_key.as_ptr() as *const core::ffi::c_void, ephemeral_public_key.len() as u32, &mut local_nonce, @@ -524,16 +573,17 @@ pub fn load_user_bound_key( check_hr(hr)?; } - let encrypted_dss_request = unsafe { - core::slice::from_raw_parts(encrypted_dss_ptr as *const u8, encrypted_dss_size as usize) - .to_vec() - }; + // Take ownership of the heap-allocated buffer + let encrypted_dss_buf = unsafe { HeapBuffer::from_raw(encrypted_dss_ptr, encrypted_dss_size) }; - unsafe { - let heap = GetProcessHeap(); - HeapFree(heap, 0, encrypted_dss_ptr); + // Defensive check - API contract guarantees valid output on S_OK + if encrypted_dss_buf.is_empty() { + return Err(UserBoundKeyError::InvalidData("API returned empty request")); } + // Convert to Vec (HeapBuffer will free on drop) + let encrypted_dss_request = encrypted_dss_buf.to_vec(); + // Get secret from credential (calls VTL0 - prompts for Windows Hello) let secret = untrusted::userboundkey_get_secret_from_credential( credential.get(), @@ -562,15 +612,19 @@ pub fn load_user_bound_key( check_hr(hr)?; } - let user_key_bytes = unsafe { - core::slice::from_raw_parts(user_key_ptr as *const u8, user_key_size as usize).to_vec() - }; + // Take ownership of the heap-allocated buffer + let user_key_buf = unsafe { HeapBuffer::from_raw(user_key_ptr, user_key_size) }; - unsafe { - let heap = GetProcessHeap(); - HeapFree(heap, 0, user_key_ptr); + // Defensive check - API contract guarantees valid output on S_OK + if user_key_buf.is_empty() { + return Err(UserBoundKeyError::InvalidData( + "API returned empty user key", + )); } + // Convert to Vec (HeapBuffer will free on drop) + let user_key_bytes = user_key_buf.to_vec(); + Ok((user_key_bytes, false)) } @@ -608,48 +662,49 @@ pub struct TrustedImpl; impl Trusted for TrustedImpl { fn userboundkey_get_attestation_report( challenge: &Vec, - ) -> Result { - unsafe { - let mut report_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); - let mut report_size: u32 = 0; - let mut session_handle: USER_BOUND_KEY_SESSION_HANDLE = core::ptr::null_mut(); + ) -> Result { + let mut report_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut report_size: u32 = 0; + let mut session_handle: USER_BOUND_KEY_SESSION_HANDLE = core::ptr::null_mut(); - let hr = InitializeUserBoundKeySession( + let hr = unsafe { + InitializeUserBoundKeySession( challenge.as_ptr() as *const core::ffi::c_void, challenge.len() as u32, &mut report_ptr, &mut report_size, &mut session_handle, - ); + ) + }; - if hr < 0 { - return Err(userboundkey_enclave_gen::AbiError::Hresult(hr)); - } - - // Convert report to Vec - let report = - core::slice::from_raw_parts(report_ptr as *const u8, report_size as usize).to_vec(); + if hr < 0 { + return Err(AbiError::Hresult(hr)); + } - // Free the allocated memory - let heap = GetProcessHeap(); - HeapFree(heap, 0, report_ptr); + // Take ownership of the heap-allocated buffer + let report_buf = unsafe { HeapBuffer::from_raw(report_ptr, report_size) }; - Ok(attestationReportAndSessionInfo { - report, - sessionInfo: session_handle as u64, - }) + // Defensive check - API contract guarantees valid output on S_OK + if report_buf.is_empty() { + return Err(AbiError::Hresult(-1)); } + + // Convert report to Vec (HeapBuffer will free on drop) + let report = report_buf.to_vec(); + + Ok(attestationReportAndSessionInfo { + report, + sessionInfo: session_handle as u64, + }) } - fn userboundkey_close_session( - sessionInfo: u64, - ) -> Result<(), userboundkey_enclave_gen::AbiError> { + fn userboundkey_close_session(sessionInfo: u64) -> Result<(), AbiError> { if sessionInfo != 0 { unsafe { let session_handle = sessionInfo as USER_BOUND_KEY_SESSION_HANDLE; let hr = CloseUserBoundKeySession(session_handle); if hr < 0 { - return Err(userboundkey_enclave_gen::AbiError::Hresult(hr)); + return Err(AbiError::Hresult(hr)); } } } diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs index 8d609c6a..be88bdf5 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs @@ -3,13 +3,18 @@ //! Type definitions for user-bound key operations +use userboundkey_enclave_gen::AbiError; +use windows_enclave::vertdll::RtlNtStatusToDosError; + /// Error type for user-bound key operations #[derive(Debug)] pub enum UserBoundKeyError { /// HRESULT error from Windows API Hresult(i32), + /// NTSTATUS error from BCrypt/NT APIs + NtStatus(i32), /// ABI layer error - AbiError(userboundkey_enclave_gen::AbiError), + AbiError(AbiError), /// Memory allocation failed AllocationFailed, /// Invalid data format @@ -22,17 +27,35 @@ pub enum UserBoundKeyError { NotImplemented(&'static str), } -impl From for UserBoundKeyError { - fn from(err: userboundkey_enclave_gen::AbiError) -> Self { +impl From for UserBoundKeyError { + fn from(err: AbiError) -> Self { UserBoundKeyError::AbiError(err) } } +/// Converts a Win32 error code to an HRESULT. +/// Equivalent to the HRESULT_FROM_WIN32 macro. +#[inline] +fn hresult_from_win32(error: u32) -> i32 { + if error == 0 { + 0 // S_OK + } else { + // FACILITY_WIN32 = 7, so (7 << 16) | 0x80000000 = 0x80070000 + ((error & 0x0000FFFF) | 0x80070000) as i32 + } +} + impl UserBoundKeyError { /// Convert the error to an HRESULT code pub fn to_hresult(&self) -> i32 { match self { UserBoundKeyError::Hresult(hr) => *hr, + // NTSTATUS to HRESULT: Convert via RtlNtStatusToDosError then HRESULT_FROM_WIN32 + UserBoundKeyError::NtStatus(status) => { + // SAFETY: RtlNtStatusToDosError is a pure function that converts NTSTATUS to Win32 error + let win32_error = unsafe { RtlNtStatusToDosError(*status) }; + hresult_from_win32(win32_error) + } UserBoundKeyError::AbiError(e) => e.to_hresult().0, UserBoundKeyError::AllocationFailed => -2147024882, // E_OUTOFMEMORY UserBoundKeyError::InvalidData(_) => -2147024809, // E_INVALIDARG diff --git a/rust/sdk/crates/libs/sdk-host/Cargo.toml b/rust/sdk/crates/libs/sdk-host/Cargo.toml index 7027e966..bb77ca29 100644 --- a/rust/sdk/crates/libs/sdk-host/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-host/Cargo.toml @@ -3,10 +3,6 @@ name = "vbsenclave-sdk-host" version = "0.1.0" edition.workspace = true -[features] -default = [] -userboundkey = [] - [dependencies.windows] workspace = true features = [ @@ -24,5 +20,6 @@ features = [ spin = { workspace = true } userboundkey-kcm = { workspace = true } userboundkey_host_gen = { path = "./generated/userboundkey_gen" } +widestring = { workspace = true } windows-core = { workspace = true } windows-future = { workspace = true } \ No newline at end of file diff --git a/rust/sdk/crates/libs/sdk-host/src/lib.rs b/rust/sdk/crates/libs/sdk-host/src/lib.rs index 18877ad5..baed58a6 100644 --- a/rust/sdk/crates/libs/sdk-host/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-host/src/lib.rs @@ -5,7 +5,6 @@ mod host_ffi; pub mod enclave; -#[cfg(feature = "userboundkey")] pub mod userboundkey; // The userboundkey-kcm crate is not intended to be published as a standalone package. @@ -37,7 +36,6 @@ pub use userboundkey_kcm::KeyCredentialManager; /// let enclave = EnclaveHandle::create_and_initialize(path, size, owner_id)?; /// register_sdk_callbacks(enclave.as_ptr())?; /// ``` -#[cfg(feature = "userboundkey")] pub fn register_sdk_callbacks(enclave_ptr: *mut core::ffi::c_void) -> Result<(), String> { // Register userboundkey callbacks let sdk_wrapper = userboundkey::UserBoundKeyVtl0Host::new(enclave_ptr); diff --git a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs index b45008ba..65355774 100644 --- a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs @@ -499,13 +499,13 @@ impl Untrusted for UntrustedImpl { let sid_wide = sid_to_wide_string(user_sid)?; // Build formatted key: {SID}//{SID}//{keyName} - let slash_slash: [u16; 2] = ['/' as u16, '/' as u16]; + let slash_slash = widestring::u16str!("//"); let mut result = Vec::with_capacity(sid_wide.len() + 2 + sid_wide.len() + 2 + keyName.wchars.len()); result.extend_from_slice(&sid_wide); - result.extend_from_slice(&slash_slash); + result.extend_from_slice(slash_slash.as_slice()); result.extend_from_slice(&sid_wide); - result.extend_from_slice(&slash_slash); + result.extend_from_slice(slash_slash.as_slice()); result.extend_from_slice(&keyName.wchars); Ok(WString { wchars: result }) diff --git a/rust/sdk/crates/libs/windows-enclave/src/lib.rs b/rust/sdk/crates/libs/windows-enclave/src/lib.rs index 394e4c5a..36516da9 100644 --- a/rust/sdk/crates/libs/windows-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/windows-enclave/src/lib.rs @@ -12,6 +12,7 @@ mod manual_bindings; pub mod bcrypt { include!(concat!(env!("OUT_DIR"), "/bcrypt.rs")); + pub use super::manual_bindings::bcrypt::*; } pub mod vertdll { diff --git a/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/bcrypt.rs b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/bcrypt.rs new file mode 100644 index 00000000..e7a635cb --- /dev/null +++ b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/bcrypt.rs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Manual BCrypt bindings for types not included in the generated bindings. + +/// Version for BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO +pub const BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION: u32 = 1; + +/// Authenticated cipher mode info structure for AES-GCM +/// +/// This structure is used with BCryptEncrypt and BCryptDecrypt when using +/// authenticated encryption modes like AES-GCM. +/// +/// See: https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_authenticated_cipher_mode_info +#[repr(C)] +#[derive(Clone, Default)] +pub struct BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO { + pub cbSize: u32, + pub dwInfoVersion: u32, + pub pbNonce: *mut u8, + pub cbNonce: u32, + pub pbAuthData: *mut u8, + pub cbAuthData: u32, + pub pbTag: *mut u8, + pub cbTag: u32, + pub pbMacContext: *mut u8, + pub cbMacContext: u32, + pub cbAAD: u32, + pub cbData: u64, + pub dwFlags: u32, +} + +impl BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO { + /// Create a new initialized cipher mode info structure + pub fn new() -> Self { + Self { + cbSize: core::mem::size_of::() as u32, + dwInfoVersion: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, + ..Default::default() + } + } + + /// Create a new cipher mode info for encryption (tag will be written) + pub fn for_encrypt(nonce: &mut [u8], tag: &mut [u8]) -> Self { + Self { + cbSize: core::mem::size_of::() as u32, + dwInfoVersion: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, + pbNonce: nonce.as_mut_ptr(), + cbNonce: nonce.len() as u32, + pbTag: tag.as_mut_ptr(), + cbTag: tag.len() as u32, + ..Default::default() + } + } + + /// Create a new cipher mode info for decryption (tag is input) + pub fn for_decrypt(nonce: &[u8], tag: &[u8]) -> Self { + Self { + cbSize: core::mem::size_of::() as u32, + dwInfoVersion: BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO_VERSION, + pbNonce: nonce.as_ptr() as *mut u8, + cbNonce: nonce.len() as u32, + pbTag: tag.as_ptr() as *mut u8, + cbTag: tag.len() as u32, + ..Default::default() + } + } +} diff --git a/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/mod.rs b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/mod.rs index 9506bc9b..efbfbcdc 100644 --- a/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/mod.rs +++ b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/mod.rs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +pub mod bcrypt; pub mod veinterop; pub mod vertdll; diff --git a/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs index 849cce6b..ff866de9 100644 --- a/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs +++ b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs @@ -15,6 +15,7 @@ windows_link::link!("vertdll.dll" "system" fn EnclaveRestrictContainingProcessAc windows_link::link!("vertdll.dll" "system" fn LdrDisableThreadCalloutsForDll(baseaddress: *mut core::ffi::c_void) -> NTSTATUS); windows_link::link!("vertdll.dll" "system" fn LdrResolveDelayLoadedAPI(parentmodulebase: *const core::ffi::c_void, delayloaddescriptor: *const IMAGE_DELAYLOAD_DESCRIPTOR, failuredllhook: PDELAYLOAD_FAILURE_DLL_CALLBACK, failuresystemhook: PDELAYLOAD_FAILURE_SYSTEM_ROUTINE, thunkaddress: *mut IMAGE_THUNK_DATA32, flags: u32) -> *mut core::ffi::c_void); windows_link::link!("vertdll.dll" "system" fn RtlGetLastNtStatus() -> NTSTATUS); +windows_link::link!("vertdll.dll" "system" fn RtlNtStatusToDosError(status: NTSTATUS) -> u32); windows_link::link!("vertdll.dll" "system" fn RtlRaiseStatus(status: NTSTATUS)); windows_link::link!("vertdll.dll" "system" fn RtlUnhandledExceptionFilter(exceptionpointers: *const EXCEPTION_POINTERS) -> i32); diff --git a/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml b/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml index 9dfbdfed..81f75a6d 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml +++ b/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml @@ -14,4 +14,4 @@ edlcodegen-tools = { workspace = true } # Generated code from userboundkey_sample.edl userboundkey_sample_enclave_gen = { path = "generated/userboundkey_sample_gen" } # The SDK provides the userboundkey implementation -vbsenclave-sdk-enclave = { workspace = true, features = ["userboundkey"] } +vbsenclave-sdk-enclave = { workspace = true } diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs index f35783b9..b5b16aa9 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs @@ -20,8 +20,9 @@ use userboundkey_sample_enclave_gen::stubs::untrusted::debug_print; // Import SDK functions and types use vbsenclave_sdk_enclave::userboundkey::{ - EnclaveSealingIdentityPolicy, SymmetricKeyHandle, TAG_SIZE, ZERO_NONCE, create_user_bound_key, - decrypt, encrypt, keyCredentialCacheConfig, load_user_bound_key, reseal_user_bound_key, + EnclaveSealingIdentityPolicy, SymmetricKeyHandle, TAG_SIZE, U16Str, ZERO_NONCE, + create_user_bound_key, decrypt, encrypt, keyCredentialCacheConfig, load_user_bound_key, + reseal_user_bound_key, }; /// Runtime policy: no dynamic debug allowed @@ -61,8 +62,8 @@ fn get_encryption_key() -> Option<&'static SymmetricKeyHandle> { /// Helper function to ensure user-bound key is loaded. /// Handles initial load attempt and optional reseal if needed. fn ensure_user_bound_key_loaded( - hello_key_name: &[u16], - pin_message: &[u16], + hello_key_name: &U16Str, + pin_message: &U16Str, window_id: u64, secured_encryption_key_bytes: &[u8], needs_reseal: &mut bool, @@ -155,11 +156,11 @@ impl Trusted for EnclaveImpl { "CreateUserBoundKey: Calling SDK create_user_bound_key...", )); - // Create user-bound key with enclave sealing (pass wchars directly) + // Create user-bound key with enclave sealing let sealed_key = create_user_bound_key( - &helloKeyName.wchars, + U16Str::from_slice(&helloKeyName.wchars), &cache_config, - &pinMessage.wchars, + U16Str::from_slice(&pinMessage.wchars), windowId, EnclaveSealingIdentityPolicy::SealToExactCode, RUNTIME_POLICY_NO_DEBUG, @@ -191,10 +192,10 @@ impl Trusted for EnclaveImpl { let mut needs_reseal = false; let mut resealed_bytes = Vec::new(); - // Ensure key is loaded (handles reseal if needed) - pass wchars directly + // Ensure key is loaded (handles reseal if needed) ensure_user_bound_key_loaded( - &helloKeyName.wchars, - &pinMessage.wchars, + U16Str::from_slice(&helloKeyName.wchars), + U16Str::from_slice(&pinMessage.wchars), windowId, securedEncryptionKeyBytes, &mut needs_reseal, @@ -263,10 +264,10 @@ impl Trusted for EnclaveImpl { let tag = &combinedInputData[4..4 + tag_size]; let encrypted = &combinedInputData[4 + tag_size..]; - // Ensure key is loaded - pass wchars directly + // Ensure key is loaded ensure_user_bound_key_loaded( - &helloKeyName.wchars, - &pinMessage.wchars, + U16Str::from_slice(&helloKeyName.wchars), + U16Str::from_slice(&pinMessage.wchars), windowId, securedEncryptionKeyBytes, &mut needs_reseal, diff --git a/rust/sdk/crates/samples/userboundkey/host/Cargo.toml b/rust/sdk/crates/samples/userboundkey/host/Cargo.toml index 01295361..5560dde3 100644 --- a/rust/sdk/crates/samples/userboundkey/host/Cargo.toml +++ b/rust/sdk/crates/samples/userboundkey/host/Cargo.toml @@ -11,6 +11,6 @@ path = "src/main.rs" # Generated code from userboundkey_sample.edl userboundkey_sample_host_gen = { path = "generated/userboundkey_sample_gen" } # The SDK provides the enclave loading and VTL0 userboundkey callbacks -vbsenclave-sdk-host = { workspace = true, features = ["userboundkey"] } +vbsenclave-sdk-host = { workspace = true } # Windows APIs windows = { workspace = true, features = ["Win32_Foundation", "Security_Cryptography", "Storage_Streams", "Win32_UI_WindowsAndMessaging"] } From 4d7bd9fc51029185f3edc8babcb0b93d50e8e536 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Thu, 22 Jan 2026 14:21:53 -0800 Subject: [PATCH 08/13] Address PR comments 2 (in progress) --- rust/sdk/Cargo.toml | 4 +- rust/sdk/build.ps1 | 10 ++-- rust/sdk/crates/libs/sdk-enclave/Cargo.toml | 2 +- rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 8 +-- .../libs/sdk-enclave/src/userboundkey/mod.rs | 18 +++--- .../sdk-enclave/src/userboundkey/types.rs | 2 +- rust/sdk/crates/libs/sdk-host/Cargo.toml | 2 +- rust/sdk/crates/libs/sdk-host/src/enclave.rs | 35 +++++------ .../libs/sdk-host/src/userboundkey/mod.rs | 12 ++-- .../libs/sdk-host/src/userboundkey/types.rs | 4 -- .../crates/libs/{userboundkey.edl => sdk.edl} | 0 .../src/manual_bindings/vertdll.rs | 30 ++++++++++ .../samples/userboundkey/enclave/Cargo.toml | 2 + .../enclave/src/enclave_constants.rs | 32 ---------- .../samples/userboundkey/enclave/src/lib.rs | 6 +- .../samples/userboundkey/host/src/main.rs | 60 +++++++------------ 16 files changed, 98 insertions(+), 129 deletions(-) delete mode 100644 rust/sdk/crates/libs/sdk-host/src/userboundkey/types.rs rename rust/sdk/crates/libs/{userboundkey.edl => sdk.edl} (100%) delete mode 100644 rust/sdk/crates/samples/userboundkey/enclave/src/enclave_constants.rs diff --git a/rust/sdk/Cargo.toml b/rust/sdk/Cargo.toml index ac1bc93c..02eadf77 100644 --- a/rust/sdk/Cargo.toml +++ b/rust/sdk/Cargo.toml @@ -3,8 +3,8 @@ resolver = "3" members = [ "crates/libs/sdk-enclave", "crates/libs/sdk-host", - "crates/libs/sdk-enclave/generated/userboundkey_gen", - "crates/libs/sdk-host/generated/userboundkey_gen", + "crates/libs/sdk-enclave/generated/sdk_gen", + "crates/libs/sdk-host/generated/sdk_gen", "crates/libs/userboundkey-kcm", "crates/tests/sdk", "crates/libs/windows-enclave", diff --git a/rust/sdk/build.ps1 b/rust/sdk/build.ps1 index 85b72c38..4205f202 100644 --- a/rust/sdk/build.ps1 +++ b/rust/sdk/build.ps1 @@ -50,16 +50,16 @@ $veilAbiEdl = Join-Path $repoRoot "src\VbsEnclaveSDK\veil_abi.edl" if ($LASTEXITCODE -ne 0) { throw "veil_abi EDL generation failed." } -# Generate userboundkey EDL bindings -$userboundkeyEdl = Join-Path $sdkRoot "crates\libs\userboundkey.edl" +# Generate SDK EDL bindings +$sdkEdl = Join-Path $sdkRoot "crates\libs\sdk.edl" & $generateScript ` -HostAppOutDir "$hostSdkCrate\generated" ` -EnclaveOutDir "$enclaveSdkCrate\generated" ` - -EdlPath $userboundkeyEdl ` - -Namespace "userboundkey" ` + -EdlPath $sdkEdl ` + -Namespace "sdk" ` -Vtl0ClassName "UserBoundKeyVtl0Host" -if ($LASTEXITCODE -ne 0) { throw "userboundkey EDL generation failed." } +if ($LASTEXITCODE -ne 0) { throw "SDK EDL generation failed." } Write-Host "SDK EDL bindings generated." -ForegroundColor Green diff --git a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml index c34abc27..5ff4b67a 100644 --- a/rust/sdk/crates/libs/sdk-enclave/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-enclave/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = { workspace = true } [dependencies] +sdk_enclave_gen = { path = "./generated/sdk_gen" } spin = { workspace = true } -userboundkey_enclave_gen = { path = "./generated/userboundkey_gen" } widestring = { workspace = true } windows-enclave = { workspace = true } diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index 5c601c0b..96872d85 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -8,9 +8,9 @@ mod enclave_ffi; pub mod userboundkey; -// Re-export the userboundkey generated enclave crate. +// Re-export the SDK generated enclave crate. // This is used internally by the export_sdk_enclave_functions! macro. -pub use userboundkey_enclave_gen; +pub use sdk_enclave_gen; /// Export all SDK enclave functions. /// @@ -33,9 +33,7 @@ macro_rules! export_sdk_enclave_functions { // Export SDK userboundkey enclave functions in a private module // to avoid naming conflicts with the application's own exports mod __sdk_userboundkey_exports { - $crate::userboundkey_enclave_gen::export_enclave_functions!( - $crate::userboundkey::TrustedImpl - ); + $crate::sdk_enclave_gen::export_enclave_functions!($crate::userboundkey::TrustedImpl); } // Future SDK features will be added here as additional modules diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs index d55b4dba..2f01db7c 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs @@ -18,17 +18,17 @@ pub use crypto::{ pub use types::*; // Re-export the cache config type for use by samples -pub use userboundkey_enclave_gen::implementation::types::keyCredentialCacheConfig; +pub use sdk_enclave_gen::implementation::types::keyCredentialCacheConfig; // Re-export widestring types for public API consumers pub use widestring::U16Str; use alloc::vec::Vec; -use userboundkey_enclave_gen::AbiError; -use userboundkey_enclave_gen::implementation::trusted::Trusted; -use userboundkey_enclave_gen::implementation::types::attestationReportAndSessionInfo; -use userboundkey_enclave_gen::stubs::untrusted; +use sdk_enclave_gen::AbiError; +use sdk_enclave_gen::implementation::trusted::Trusted; +use sdk_enclave_gen::implementation::types::attestationReportAndSessionInfo; +use sdk_enclave_gen::stubs::untrusted; use widestring::U16String; use windows_enclave::veinterop::{ @@ -280,7 +280,7 @@ pub fn create_user_bound_key_with_custom_key( ) -> Result, UserBoundKeyError> { // Format the key name (calls VTL0) - this is the FIRST VTL0 call // Create WString from key_name slice - let key_name_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { + let key_name_wstring = sdk_enclave_gen::implementation::types::edl::WString { wchars: key_name.as_slice().to_vec(), }; let formatted_key_name_wstring = untrusted::userboundkey_format_key_name(&key_name_wstring) @@ -294,7 +294,7 @@ pub fn create_user_bound_key_with_custom_key( let enclave_ptr = get_enclave_base_address_u64()?; // Create WString for message - let message_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { + let message_wstring = sdk_enclave_gen::implementation::types::edl::WString { wchars: message.as_slice().to_vec(), }; @@ -453,7 +453,7 @@ pub fn load_user_bound_key( let ephemeral_public_key = get_ephemeral_public_key_bytes_from_bound_key(&bound_key_bytes)?; // Format the key name (calls VTL0) - let key_name_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { + let key_name_wstring = sdk_enclave_gen::implementation::types::edl::WString { wchars: key_name.as_slice().to_vec(), }; let formatted_key_name_wstring = untrusted::userboundkey_format_key_name(&key_name_wstring) @@ -467,7 +467,7 @@ pub fn load_user_bound_key( let enclave_ptr = get_enclave_base_address_u64()?; // Create WString for message - let message_wstring = userboundkey_enclave_gen::implementation::types::edl::WString { + let message_wstring = sdk_enclave_gen::implementation::types::edl::WString { wchars: message.as_slice().to_vec(), }; diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs index be88bdf5..411538a3 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs @@ -3,7 +3,7 @@ //! Type definitions for user-bound key operations -use userboundkey_enclave_gen::AbiError; +use sdk_enclave_gen::AbiError; use windows_enclave::vertdll::RtlNtStatusToDosError; /// Error type for user-bound key operations diff --git a/rust/sdk/crates/libs/sdk-host/Cargo.toml b/rust/sdk/crates/libs/sdk-host/Cargo.toml index bb77ca29..4a9ed7d2 100644 --- a/rust/sdk/crates/libs/sdk-host/Cargo.toml +++ b/rust/sdk/crates/libs/sdk-host/Cargo.toml @@ -17,9 +17,9 @@ features = [ ] [dependencies] +sdk_host_gen = { path = "./generated/sdk_gen" } spin = { workspace = true } userboundkey-kcm = { workspace = true } -userboundkey_host_gen = { path = "./generated/userboundkey_gen" } widestring = { workspace = true } windows-core = { workspace = true } windows-future = { workspace = true } \ No newline at end of file diff --git a/rust/sdk/crates/libs/sdk-host/src/enclave.rs b/rust/sdk/crates/libs/sdk-host/src/enclave.rs index ec6b10fc..2eb8a289 100644 --- a/rust/sdk/crates/libs/sdk-host/src/enclave.rs +++ b/rust/sdk/crates/libs/sdk-host/src/enclave.rs @@ -26,6 +26,7 @@ use std::ffi::c_void; use std::path::Path; +use widestring::U16CString; use windows::core::{Error, HRESULT, Result}; // FFI declarations for enclave APIs @@ -81,7 +82,7 @@ pub const DEFAULT_ENCLAVE_SIZE: usize = 32 * 1024 * 1024; /// Enclave creation info for VBS enclaves. #[repr(C)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Default)] pub struct EnclaveCreateInfoVbs { /// Flags for enclave creation. pub flags: u32, @@ -89,15 +90,6 @@ pub struct EnclaveCreateInfoVbs { pub owner_id: [u8; IMAGE_ENCLAVE_LONG_ID_LENGTH], } -impl Default for EnclaveCreateInfoVbs { - fn default() -> Self { - Self { - flags: 0, - owner_id: [0u8; IMAGE_ENCLAVE_LONG_ID_LENGTH], - } - } -} - /// Enclave initialization info for VBS enclaves. #[repr(C)] #[derive(Clone, Copy)] @@ -204,11 +196,18 @@ pub fn create( /// /// Returns an error if loading fails. pub fn load_image(enclave: *mut c_void, image_path: &Path) -> Result<()> { - let wide_path: Vec = image_path - .as_os_str() - .encode_wide() - .chain(std::iter::once(0)) - .collect(); + let path_str = image_path.to_str().ok_or_else(|| { + Error::new( + HRESULT(-2147024809i32), // E_INVALIDARG + "Invalid path: not valid UTF-8", + ) + })?; + let wide_path = U16CString::from_str(path_str).map_err(|_| { + Error::new( + HRESULT(-2147024809i32), // E_INVALIDARG + "Invalid path: contains interior null", + ) + })?; let result = unsafe { ffi::LoadEnclaveImageW(enclave, wide_path.as_ptr()) }; @@ -223,8 +222,6 @@ pub fn load_image(enclave: *mut c_void, image_path: &Path) -> Result<()> { Ok(()) } -use std::os::windows::ffi::OsStrExt; - /// Initialize an enclave after loading its image. /// /// # Arguments @@ -397,7 +394,3 @@ impl Drop for EnclaveHandle { } } } - -// EnclaveHandle is Send but not Sync - you can move it between threads -// but shouldn't share references across threads. -unsafe impl Send for EnclaveHandle {} diff --git a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs index 65355774..375556df 100644 --- a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs @@ -6,17 +6,13 @@ //! Implements EDL untrusted callbacks for user-bound key operations. //! This module provides VTL0/host-side Windows Hello integration with VBS enclave support. -mod types; - -#[allow(unused_imports)] -pub use types::*; -use userboundkey_host_gen::AbiError; -pub use userboundkey_host_gen::UserBoundKeyVtl0Host; -pub use userboundkey_host_gen::implementation::types::{ +use sdk_host_gen::AbiError; +pub use sdk_host_gen::UserBoundKeyVtl0Host; +pub use sdk_host_gen::implementation::types::{ attestationReportAndSessionInfo, credentialAndSessionInfo, edl::WString, keyCredentialCacheConfig, }; -use userboundkey_host_gen::implementation::untrusted::Untrusted; +use sdk_host_gen::implementation::untrusted::Untrusted; use std::sync::{Arc, Mutex}; diff --git a/rust/sdk/crates/libs/sdk-host/src/userboundkey/types.rs b/rust/sdk/crates/libs/sdk-host/src/userboundkey/types.rs deleted file mode 100644 index 73bb6759..00000000 --- a/rust/sdk/crates/libs/sdk-host/src/userboundkey/types.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! Type definitions for user-bound key operations diff --git a/rust/sdk/crates/libs/userboundkey.edl b/rust/sdk/crates/libs/sdk.edl similarity index 100% rename from rust/sdk/crates/libs/userboundkey.edl rename to rust/sdk/crates/libs/sdk.edl diff --git a/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs index ff866de9..899a4350 100644 --- a/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs +++ b/rust/sdk/crates/libs/windows-enclave/src/manual_bindings/vertdll.rs @@ -69,3 +69,33 @@ pub const IMAGE_ENCLAVE_SHORT_ID_LENGTH: usize = 16; pub const ENCLAVE_TYPE_SGX: u32 = 0x00000001; pub const ENCLAVE_TYPE_SGX2: u32 = 0x00000002; pub const ENCLAVE_TYPE_VBS: u32 = 0x00000010; + +// Enclave policy flags +#[allow(dead_code)] +pub const IMAGE_ENCLAVE_POLICY_DEBUGGABLE: u32 = 0x0000_0001; + +// Enclave image flags +pub const IMAGE_ENCLAVE_FLAG_PRIMARY_IMAGE: u32 = 0x0000_0001; + +/// Enclave configuration structure. +/// See: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_enclave_config64 +#[repr(C)] +pub struct ImageEnclaveConfig { + pub size: u32, + pub minimum_required_config_size: u32, + pub policy_flags: u32, + pub number_of_imports: u32, + pub import_list: u32, + pub import_entry_size: u32, + pub family_id: [u8; IMAGE_ENCLAVE_SHORT_ID_LENGTH], + pub image_id: [u8; IMAGE_ENCLAVE_SHORT_ID_LENGTH], + pub image_version: u32, + pub security_version: u32, + pub enclave_size: usize, + pub number_of_threads: u32, + pub enclave_flags: u32, +} + +/// Minimum config size required (offset of enclave_flags field). +pub const IMAGE_ENCLAVE_MINIMUM_CONFIG_SIZE: u32 = + core::mem::offset_of!(ImageEnclaveConfig, enclave_flags) as u32; diff --git a/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml b/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml index 81f75a6d..c061155f 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml +++ b/rust/sdk/crates/samples/userboundkey/enclave/Cargo.toml @@ -15,3 +15,5 @@ edlcodegen-tools = { workspace = true } userboundkey_sample_enclave_gen = { path = "generated/userboundkey_sample_gen" } # The SDK provides the userboundkey implementation vbsenclave-sdk-enclave = { workspace = true } +# For enclave configuration constants +windows-enclave = { workspace = true } diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/enclave_constants.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/enclave_constants.rs deleted file mode 100644 index bdcce213..00000000 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/enclave_constants.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// Temporary: The contents of this file should be provided by the future -// windows-enclave crate. For now, we define the necessary constants and -// structs here. - -#[allow(dead_code)] // debug policy not used in release builds -pub const IMAGE_ENCLAVE_POLICY_DEBUGGABLE: u32 = 0x0000_0001; -pub const IMAGE_ENCLAVE_SHORT_ID_LENGTH: usize = 16; -pub const IMAGE_ENCLAVE_FLAG_PRIMARY_IMAGE: u32 = 0x0000_0001; - -// See: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_enclave_config64 -#[repr(C)] -pub struct ImageEnclaveConfig { - pub size: u32, - pub minimum_required_config_size: u32, - pub policy_flags: u32, - pub number_of_imports: u32, - pub import_list: u32, - pub import_entry_size: u32, - pub family_id: [u8; IMAGE_ENCLAVE_SHORT_ID_LENGTH], - pub image_id: [u8; IMAGE_ENCLAVE_SHORT_ID_LENGTH], - pub image_version: u32, - pub security_version: u32, - pub enclave_size: usize, - pub number_of_threads: u32, - pub enclave_flags: u32, -} - -pub const IMAGE_ENCLAVE_MINIMUM_CONFIG_SIZE: u32 = - core::mem::offset_of!(ImageEnclaveConfig, enclave_flags) as u32; diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs index 322c0860..4bd971e8 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/lib.rs @@ -10,10 +10,12 @@ extern crate alloc; mod edl_impls; -mod enclave_constants; use core::panic::PanicInfo; -use enclave_constants::*; +use windows_enclave::vertdll::{ + IMAGE_ENCLAVE_FLAG_PRIMARY_IMAGE, IMAGE_ENCLAVE_MINIMUM_CONFIG_SIZE, + IMAGE_ENCLAVE_POLICY_DEBUGGABLE, ImageEnclaveConfig, +}; // Panic handler for no_std environment #[panic_handler] diff --git a/rust/sdk/crates/samples/userboundkey/host/src/main.rs b/rust/sdk/crates/samples/userboundkey/host/src/main.rs index bc6d40ee..93a77ad1 100644 --- a/rust/sdk/crates/samples/userboundkey/host/src/main.rs +++ b/rust/sdk/crates/samples/userboundkey/host/src/main.rs @@ -75,20 +75,16 @@ fn to_wstring(s: &str) -> WString { /// Get secure ID from Windows Hello. /// /// Returns the secure ID buffer that identifies this Windows Hello user. -fn get_secure_id_from_windows_hello() -> Result, String> { +fn get_secure_id_from_windows_hello() -> Result, Box> { // Get the secure ID buffer from KeyCredentialManager - let secure_id_buffer = KeyCredentialManager::GetSecureId() - .map_err(|e| format!("Failed to get secure ID: {:?}", e))?; + let secure_id_buffer = KeyCredentialManager::GetSecureId()?; // Cast from userboundkey_kcm::IBuffer to windows::Storage::Streams::IBuffer - let windows_buffer: windows::Storage::Streams::IBuffer = secure_id_buffer - .cast() - .map_err(|e| format!("Failed to cast IBuffer: {:?}", e))?; + let windows_buffer: windows::Storage::Streams::IBuffer = secure_id_buffer.cast()?; // Convert IBuffer to Vec using CryptographicBuffer let mut byte_array = windows::core::Array::::new(); - CryptographicBuffer::CopyToByteArray(&windows_buffer, &mut byte_array) - .map_err(|e| format!("Failed to copy buffer: {:?}", e))?; + CryptographicBuffer::CopyToByteArray(&windows_buffer, &mut byte_array)?; Ok(byte_array.to_vec()) } @@ -96,30 +92,19 @@ fn get_secure_id_from_windows_hello() -> Result, String> { /// Load the enclave DLL and return the wrapper interface fn load_enclave( enclave_path: &Path, -) -> Result<(EnclaveHandle, userboundkey_sampleWrapper), String> { +) -> Result<(EnclaveHandle, userboundkey_sampleWrapper), Box> { println!("Loading enclave from: {}", enclave_path.display()); - // Try to get the secure ID from Windows Hello. - // If Windows Hello isn't available, we continue without owner ID but key creation will fail. - let owner_id = match get_secure_id_from_windows_hello() { - Ok(id) => { - println!("Got secure ID from Windows Hello ({} bytes)", id.len()); - Some(id) - } - Err(e) => { - println!("Warning: Could not get secure ID from Windows Hello: {}", e); - println!("Enclave will load but user-bound key operations may fail."); - None - } - }; + // Get the secure ID from Windows Hello - required for user-bound key operations + let owner_id = get_secure_id_from_windows_hello()?; + println!( + "Got secure ID from Windows Hello ({} bytes)", + owner_id.len() + ); // Create, load, and initialize the enclave (512MB) - let enclave = EnclaveHandle::create_and_initialize( - enclave_path, - megabytes(512), - owner_id.as_deref(), // Pass the secure ID as owner ID if available - ) - .map_err(|e| format!("Failed to create/initialize enclave: {:?}", e))?; + let enclave = + EnclaveHandle::create_and_initialize(enclave_path, megabytes(512), Some(&owner_id))?; // Create the wrapper interface for enclave calls let wrapper = userboundkey_sampleWrapper::new(enclave.as_ptr()); @@ -127,7 +112,12 @@ fn load_enclave( // Register the sample's VTL0 callbacks (for debug_print, etc.) wrapper .register_vtl0_callbacks::() - .map_err(|e| format!("Failed to register sample callbacks: {:?}", e))?; + .map_err(|e| { + format!( + "Failed to register sample callbacks: 0x{:08X}", + e.to_hresult().0 + ) + })?; // Register all SDK VTL0 callbacks so enclave can call back to host // This single function call handles all SDK features (userboundkey, etc.) @@ -137,7 +127,7 @@ fn load_enclave( Ok((enclave, wrapper)) } -fn main() { +fn main() -> Result<(), Box> { println!("=== User-Bound Key Sample (Rust) ==="); println!(); @@ -149,14 +139,7 @@ fn main() { let enclave_path = exe_dir.join("userboundkey_sample_enclave.dll"); // Load the enclave - let (_enclave_handle, enclave) = match load_enclave(&enclave_path) { - Ok(result) => result, - Err(e) => { - eprintln!("Failed to load enclave: {}", e); - eprintln!("Make sure {} exists.", enclave_path.display()); - return; - } - }; + let (_enclave_handle, enclave) = load_enclave(&enclave_path)?; let key_file_path = get_key_file_path(); let encrypted_file_path = get_encrypted_file_path(); @@ -371,4 +354,5 @@ fn main() { // EnclaveHandle will automatically terminate and delete the enclave on drop println!("Enclave cleanup complete."); + Ok(()) } From 235a691fac71b1ea8eb68f50909c7f8f1dd6c67b Mon Sep 17 00:00:00 2001 From: Soham Das Date: Thu, 22 Jan 2026 16:11:50 -0800 Subject: [PATCH 09/13] Address PR comments 3 (in progress) --- rust/sdk/build.ps1 | 145 ------ .../libs/sdk-enclave/src/common/crypto.rs | 434 +++++++++++++++++ .../crates/libs/sdk-enclave/src/common/mod.rs | 12 + .../src/{userboundkey => common}/utils.rs | 56 ++- rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 1 + .../sdk-enclave/src/userboundkey/crypto.rs | 455 ++---------------- .../libs/sdk-enclave/src/userboundkey/mod.rs | 8 +- .../sdk-enclave/src/userboundkey/types.rs | 19 + rust/sdk/crates/libs/sdk-host/src/lib.rs | 6 +- .../sdk/crates/samples/userboundkey/README.md | 27 +- .../sdk/crates/samples/userboundkey/build.ps1 | 88 ---- .../userboundkey/enclave/src/edl_impls.rs | 8 +- .../samples/userboundkey/host/src/main.rs | 6 +- rust/sdk/generate_and_build_crates.ps1 | 69 +++ rust/sdk/generate_codegen_for_workspace.ps1 | 56 +++ 15 files changed, 702 insertions(+), 688 deletions(-) delete mode 100644 rust/sdk/build.ps1 create mode 100644 rust/sdk/crates/libs/sdk-enclave/src/common/crypto.rs create mode 100644 rust/sdk/crates/libs/sdk-enclave/src/common/mod.rs rename rust/sdk/crates/libs/sdk-enclave/src/{userboundkey => common}/utils.rs (64%) delete mode 100644 rust/sdk/crates/samples/userboundkey/build.ps1 create mode 100644 rust/sdk/generate_and_build_crates.ps1 create mode 100644 rust/sdk/generate_codegen_for_workspace.ps1 diff --git a/rust/sdk/build.ps1 b/rust/sdk/build.ps1 deleted file mode 100644 index 4205f202..00000000 --- a/rust/sdk/build.ps1 +++ /dev/null @@ -1,145 +0,0 @@ -# Generates EDL bindings and builds the entire SDK workspace. -# -# Usage: -# .\build.ps1 # Debug build -# .\build.ps1 -Configuration Release # Release build -# .\build.ps1 -CertName "MyCert" # Debug build + sign enclaves -# .\build.ps1 -Configuration Release -CertName "MyCert" # Release build + sign enclaves - -param( - [ValidateSet("Debug", "Release")] - [string]$Configuration = "Debug", - - [string]$CertName -) - -$ErrorActionPreference = "Stop" - -$sdkRoot = $PSScriptRoot -$rustRoot = Resolve-Path "$sdkRoot\.." -$repoRoot = Resolve-Path "$rustRoot\.." -$scriptsDir = Join-Path $rustRoot "scripts" - -Write-Host "`n=== VBS Enclave SDK Build Script ===" -ForegroundColor Cyan -Write-Host "Configuration: $Configuration" -if ($CertName) { - Write-Host "Certificate: $CertName (will sign enclaves)" -} - -$totalSteps = if ($CertName) { 4 } else { 3 } - -# ============================================================================ -# Step 1: Generate SDK EDL bindings -# ============================================================================ -Write-Host "`n[1/$totalSteps] Generating SDK EDL bindings..." -ForegroundColor Yellow - -$generateScript = Join-Path $scriptsDir "generate_codegen_crates.ps1" - -# SDK crate paths -$enclaveSdkCrate = Join-Path $sdkRoot "crates\libs\sdk-enclave" -$hostSdkCrate = Join-Path $sdkRoot "crates\libs\sdk-host" - -# Generate veil_abi EDL bindings -$veilAbiEdl = Join-Path $repoRoot "src\VbsEnclaveSDK\veil_abi.edl" -& $generateScript ` - -HostAppOutDir "$hostSdkCrate\generated" ` - -EnclaveOutDir "$enclaveSdkCrate\generated" ` - -EdlPath $veilAbiEdl ` - -Namespace "veil_abi" ` - -Vtl0ClassName "export_interface" - -if ($LASTEXITCODE -ne 0) { throw "veil_abi EDL generation failed." } - -# Generate SDK EDL bindings -$sdkEdl = Join-Path $sdkRoot "crates\libs\sdk.edl" -& $generateScript ` - -HostAppOutDir "$hostSdkCrate\generated" ` - -EnclaveOutDir "$enclaveSdkCrate\generated" ` - -EdlPath $sdkEdl ` - -Namespace "sdk" ` - -Vtl0ClassName "UserBoundKeyVtl0Host" - -if ($LASTEXITCODE -ne 0) { throw "SDK EDL generation failed." } - -Write-Host "SDK EDL bindings generated." -ForegroundColor Green - -# ============================================================================ -# Step 2: Generate sample EDL bindings -# ============================================================================ -Write-Host "`n[2/$totalSteps] Generating sample EDL bindings..." -ForegroundColor Yellow - -# Userboundkey sample -$sampleDir = Join-Path $sdkRoot "crates\samples\userboundkey" -$sampleEdl = Join-Path $sampleDir "userboundkey_sample.edl" -$libsImportDir = Join-Path $sdkRoot "crates\libs" - -& $generateScript ` - -HostAppOutDir "$sampleDir\host\generated" ` - -EnclaveOutDir "$sampleDir\enclave\generated" ` - -EdlPath $sampleEdl ` - -Namespace "userboundkey_sample" ` - -Vtl0ClassName "userboundkey_sampleWrapper" ` - -ImportDirectories $libsImportDir - -if ($LASTEXITCODE -ne 0) { throw "userboundkey_sample EDL generation failed." } - -Write-Host "Sample EDL bindings generated." -ForegroundColor Green - -# ============================================================================ -# Step 3: Format and build workspace -# ============================================================================ -Write-Host "`n[3/$totalSteps] Building workspace..." -ForegroundColor Yellow - -# Format generated code -$formatScript = Join-Path $scriptsDir "invoke_rustfmt_workspace.ps1" -& $formatScript -WorkspacePath $sdkRoot - -# Build workspace -Push-Location $sdkRoot -try { - $buildArgs = @("build", "--workspace") - if ($Configuration -eq "Release") { - $buildArgs += "--release" - } - - cargo @buildArgs - - if ($LASTEXITCODE -ne 0) { throw "Cargo build failed." } -} finally { - Pop-Location -} - -Write-Host "Build completed." -ForegroundColor Green - -# ============================================================================ -# Step 4: Sign enclaves (optional) -# ============================================================================ -if ($CertName) { - Write-Host "`n[4/$totalSteps] Signing enclave DLLs..." -ForegroundColor Yellow - - $signScript = Join-Path $scriptsDir "sign-enclave.ps1" - $targetDir = if ($Configuration -eq "Release") { "release" } else { "debug" } - $targetPath = Join-Path $sdkRoot "target\$targetDir" - - # Find all enclave DLLs (convention: *_enclave.dll) - $enclaveDlls = Get-ChildItem -Path $targetPath -Filter "*_enclave.dll" -ErrorAction SilentlyContinue - - if ($enclaveDlls.Count -eq 0) { - Write-Host "No enclave DLLs found to sign." -ForegroundColor Yellow - } else { - foreach ($dll in $enclaveDlls) { - Write-Host "Signing $($dll.Name)..." -ForegroundColor Gray - & $signScript -DllPath $dll.FullName -CertName $CertName - if ($LASTEXITCODE -ne 0) { throw "Failed to sign $($dll.Name)" } - } - Write-Host "All enclaves signed." -ForegroundColor Green - } -} - -Write-Host "`n=== SDK Build Complete ===" -ForegroundColor Cyan -$targetDir = if ($Configuration -eq "Release") { "release" } else { "debug" } -Write-Host "Output directory: $(Join-Path $sdkRoot "target\$targetDir")" -if (-not $CertName) { - Write-Host "" - Write-Host "Tip: Use -CertName to sign enclave DLLs after build." -} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/common/crypto.rs b/rust/sdk/crates/libs/sdk-enclave/src/common/crypto.rs new file mode 100644 index 00000000..00ce6477 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-enclave/src/common/crypto.rs @@ -0,0 +1,434 @@ +//! Common cryptographic utilities for VBS Enclave SDK +//! +//! This module provides general-purpose cryptographic functionality that can be +//! used across different modules in the SDK, including: +//! - BCrypt API wrappers and RAII types +//! - Enclave sealing and unsealing +//! - Random number generation +//! - AES-GCM encryption and decryption +//! - Error checking helpers + +extern crate alloc; + +use alloc::{boxed::Box, format, vec::Vec}; +use core::ffi::c_void; +use windows_enclave::bcrypt::*; +use windows_enclave::vertdll::*; + +/// Sealing identity policy for enclave data sealing +#[derive(Debug, Clone, Copy)] +pub enum EnclaveSealingIdentityPolicy { + /// Seal to enclave identity (SIGNER + PRODUCT_ID + VERSION) + EnclaveIdentity = 1, + /// Seal to signer only (allows version upgrades) + SignerOnly = 2, +} + +// +// Helper functions (public for use by other modules) +// + +/// Check HRESULT and convert to error +pub fn check_hr(hr: i32) -> Result<(), Box> { + if hr < 0 { + return Err(format!("HRESULT error: 0x{:08x}", hr).into()); + } + Ok(()) +} + +/// Check NTSTATUS and convert to error +pub fn check_ntstatus(status: i32) -> Result<(), Box> { + if status < 0 { + return Err(format!("NTSTATUS error: 0x{:08x}", status).into()); + } + Ok(()) +} + +// +// Random number generation +// + +/// Generate cryptographically secure random bytes +/// +/// Uses BCryptGenRandom with BCRYPT_USE_SYSTEM_PREFERRED_RNG flag. +/// This is safe to call from enclaves as it uses the enclave's entropy source. +pub fn generate_random(buffer: &mut [u8]) -> Result<(), Box> { + unsafe { + let status = BCryptGenRandom( + core::ptr::null_mut(), // Use system RNG + buffer.as_mut_ptr(), + buffer.len() as u32, + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ); + check_ntstatus(status)?; + } + Ok(()) +} + +/// Generate random bytes of the specified length +pub fn generate_random_bytes(count: usize) -> Result, Box> { + let mut buffer = alloc::vec![0u8; count]; + generate_random(&mut buffer)?; + Ok(buffer) +} + +/// Generate a cryptographically secure random u64 +pub fn generate_random_u64() -> Result> { + let mut buffer = [0u8; 8]; + generate_random(&mut buffer)?; + Ok(u64::from_le_bytes(buffer)) +} + +/// Generate random bytes suitable for use as a symmetric key +pub fn generate_symmetric_key_bytes( + key_size: usize, +) -> Result, Box> { + generate_random_bytes(key_size) +} + +// +// Enclave sealing +// + +/// Seal data using enclave sealing +/// +/// Encrypts data such that it can only be decrypted by the same enclave +/// (or enclaves with compatible identity based on the sealing policy). +/// +/// # Arguments +/// * `data` - The data to seal +/// * `sealing_policy` - Identity policy for sealing +/// * `runtime_policy` - Runtime policy flags (pass 0 for default) +/// +/// # Returns +/// The sealed data blob +pub fn seal_data( + data: &[u8], + sealing_policy: EnclaveSealingIdentityPolicy, + runtime_policy: u32, +) -> Result, Box> { + unsafe { + // First call to get required size + let mut sealed_size: u32 = 0; + let hr = EnclaveSealData( + data.as_ptr() as *const c_void, + data.len() as u32, + sealing_policy as i32, + runtime_policy, + core::ptr::null_mut(), + 0, + &mut sealed_size, + ); + check_hr(hr)?; + + // Allocate Vec and seal directly into it + let mut sealed_buffer = alloc::vec![0u8; sealed_size as usize]; + + let hr = EnclaveSealData( + data.as_ptr() as *const c_void, + data.len() as u32, + sealing_policy as i32, + runtime_policy, + sealed_buffer.as_mut_ptr() as *mut c_void, + sealed_size, + &mut sealed_size, + ); + check_hr(hr)?; + + Ok(sealed_buffer) + } +} + +/// Unseal data using enclave sealing +/// +/// Decrypts data that was previously sealed to this enclave. +/// +/// # Arguments +/// * `sealed_data` - The sealed data blob +/// +/// # Returns +/// A tuple of (unsealed_data, unseal_flags). Check unseal_flags for stale key flags +/// to determine if the sealing key has changed and the data should be resealed. +pub fn unseal_data(sealed_data: &[u8]) -> Result<(Vec, u32), Box> { + unsafe { + // First call to get required size + let mut unsealed_size: u32 = 0; + let hr = EnclaveUnsealData( + sealed_data.as_ptr() as *const c_void, + sealed_data.len() as u32, + core::ptr::null_mut(), + 0, + &mut unsealed_size, + core::ptr::null_mut(), + core::ptr::null_mut(), + ); + check_hr(hr)?; + + // Allocate Vec and unseal directly into it + let mut unsealed_buffer = alloc::vec![0u8; unsealed_size as usize]; + let mut unseal_flags: u32 = 0; + + let hr = EnclaveUnsealData( + sealed_data.as_ptr() as *const c_void, + sealed_data.len() as u32, + unsealed_buffer.as_mut_ptr() as *mut c_void, + unsealed_size, + &mut unsealed_size, + core::ptr::null_mut(), + &mut unseal_flags, + ); + check_hr(hr)?; + + Ok((unsealed_buffer, unseal_flags)) + } +} + +// +// Nonce utilities +// + +/// Create a nonce buffer from a numeric value +/// +/// Creates a 12-byte nonce with the value placed at the end (little-endian). +pub fn make_nonce_from_number(nonce: u64, nonce_size: usize) -> Vec { + let mut buffer = alloc::vec![0u8; nonce_size]; + if nonce_size >= 8 { + // Place nonce value at the end of the buffer + let nonce_bytes = nonce.to_le_bytes(); + buffer[nonce_size - 8..].copy_from_slice(&nonce_bytes); + } + buffer +} + +// +// Symmetric Key Handle (RAII wrapper) +// + +/// RAII wrapper for BCrypt symmetric key handle +pub struct SymmetricKeyHandle { + handle: BCRYPT_KEY_HANDLE, +} + +impl SymmetricKeyHandle { + /// Create a symmetric key from raw key bytes using AES-GCM + pub fn from_bytes(key_bytes: &[u8]) -> Result> { + let mut handle: BCRYPT_KEY_HANDLE = core::ptr::null_mut(); + + unsafe { + let status = BCryptGenerateSymmetricKey( + BCRYPT_AES_GCM_ALG_HANDLE, + &mut handle, + core::ptr::null_mut(), // No key object buffer needed + 0, + key_bytes.as_ptr(), + key_bytes.len() as u32, + 0, + ); + check_ntstatus(status)?; + } + + Ok(Self { handle }) + } + + /// Get the raw handle for use with BCrypt functions + pub fn handle(&self) -> BCRYPT_KEY_HANDLE { + self.handle + } +} + +impl Drop for SymmetricKeyHandle { + fn drop(&mut self) { + if !self.handle.is_null() { + unsafe { + let _ = BCryptDestroyKey(self.handle); + } + } + } +} + +// +// AES-GCM Encryption / Decryption +// + +/// Encrypt data using AES-GCM +/// +/// # Arguments +/// * `key` - The symmetric key handle +/// * `plaintext` - Data to encrypt +/// * `nonce` - Nonce (IV) bytes +/// * `tag_size` - Size of authentication tag in bytes +/// +/// # Returns +/// A tuple of (ciphertext, tag) +pub fn encrypt( + key: &SymmetricKeyHandle, + plaintext: &[u8], + nonce: &[u8], + tag_size: usize, +) -> Result<(Vec, Vec), Box> { + let mut tag = alloc::vec![0u8; tag_size]; + let mut nonce_copy = nonce.to_vec(); + + // AES-GCM: ciphertext length equals plaintext length + let mut ciphertext = alloc::vec![0u8; plaintext.len()]; + let mut result_size: u32 = 0; + + let cipher_info = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO::for_encrypt(&mut nonce_copy, &mut tag); + + unsafe { + let status = BCryptEncrypt( + key.handle(), + plaintext.as_ptr(), + plaintext.len() as u32, + &cipher_info as *const _ as *const c_void, + core::ptr::null_mut(), // No IV buffer (using nonce in cipher_info) + 0, + ciphertext.as_mut_ptr(), + ciphertext.len() as u32, + &mut result_size, + 0, + ); + check_ntstatus(status)?; + } + + Ok((ciphertext, tag)) +} + +/// Decrypt data using AES-GCM +/// +/// # Arguments +/// * `key` - The symmetric key handle +/// * `ciphertext` - Data to decrypt +/// * `nonce` - Nonce (IV) bytes used during encryption +/// * `tag` - Authentication tag from encryption +/// +/// # Returns +/// The decrypted plaintext +pub fn decrypt( + key: &SymmetricKeyHandle, + ciphertext: &[u8], + nonce: &[u8], + tag: &[u8], +) -> Result, Box> { + // AES-GCM: plaintext length equals ciphertext length + let mut plaintext = alloc::vec![0u8; ciphertext.len()]; + let mut result_size: u32 = 0; + + let cipher_info = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO::for_decrypt(nonce, tag); + + unsafe { + let status = BCryptDecrypt( + key.handle(), + ciphertext.as_ptr(), + ciphertext.len() as u32, + &cipher_info as *const _ as *const c_void, + core::ptr::null_mut(), // No IV buffer (using nonce in cipher_info) + 0, + plaintext.as_mut_ptr(), + plaintext.len() as u32, + &mut result_size, + 0, + ); + + // STATUS_AUTH_TAG_MISMATCH = 0xC000A002 + if status == -1073700862i32 { + return Err("Authentication tag mismatch".into()); + } + check_ntstatus(status)?; + } + + Ok(plaintext) +} + +/// Encrypt data and append the tag to the output +/// +/// # Arguments +/// * `key` - The symmetric key handle +/// * `plaintext` - Data to encrypt +/// * `nonce` - Nonce (IV) bytes +/// * `tag_size` - Size of authentication tag in bytes +/// +/// # Returns +/// Combined output: [ciphertext][tag] +pub fn encrypt_and_tag( + key: &SymmetricKeyHandle, + plaintext: &[u8], + nonce: &[u8], + tag_size: usize, +) -> Result, Box> { + let (ciphertext, tag) = encrypt(key, plaintext, nonce, tag_size)?; + + let mut combined = Vec::with_capacity(ciphertext.len() + tag_size); + combined.extend_from_slice(&ciphertext); + combined.extend_from_slice(&tag); + + Ok(combined) +} + +/// Decrypt data that has the tag appended +/// +/// # Arguments +/// * `key` - The symmetric key handle +/// * `combined` - Combined input: [ciphertext][tag] +/// * `nonce` - Nonce (IV) bytes used during encryption +/// * `tag_size` - Size of authentication tag in bytes +/// +/// # Returns +/// The decrypted plaintext +pub fn decrypt_and_untag( + key: &SymmetricKeyHandle, + combined: &[u8], + nonce: &[u8], + tag_size: usize, +) -> Result, Box> { + if combined.len() < tag_size { + return Err("Data too short for tag".into()); + } + + let ciphertext_len = combined.len() - tag_size; + let ciphertext = &combined[..ciphertext_len]; + let tag = &combined[ciphertext_len..]; + + decrypt(key, ciphertext, nonce, tag) +} + +/// Encrypt data with a zero nonce and append tag +/// +/// Uses a zero nonce (all zeros). Only safe when each key is used once. +pub fn encrypt_and_tag_zero_nonce( + key: &SymmetricKeyHandle, + plaintext: &[u8], + nonce_size: usize, + tag_size: usize, +) -> Result, Box> { + let zero_nonce = alloc::vec![0u8; nonce_size]; + encrypt_and_tag(key, plaintext, &zero_nonce, tag_size) +} + +/// Decrypt data with a zero nonce that has tag appended +/// +/// Uses a zero nonce (all zeros). +pub fn decrypt_and_untag_zero_nonce( + key: &SymmetricKeyHandle, + combined: &[u8], + nonce_size: usize, + tag_size: usize, +) -> Result, Box> { + let zero_nonce = alloc::vec![0u8; nonce_size]; + decrypt_and_untag(key, combined, &zero_nonce, tag_size) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_make_nonce_from_number() { + let nonce = make_nonce_from_number(0x0102030405060708, 12); + assert_eq!(nonce[0..4], [0, 0, 0, 0]); + assert_eq!( + nonce[4..12], + [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01] + ); + } +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/common/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/common/mod.rs new file mode 100644 index 00000000..1c15beba --- /dev/null +++ b/rust/sdk/crates/libs/sdk-enclave/src/common/mod.rs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Common utilities for VTL1 enclave operations +//! +//! This module provides general-purpose utilities that can be used across +//! different SDK modules for enclave operations. + +pub mod crypto; +mod utils; + +pub use utils::*; diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs b/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs similarity index 64% rename from rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs rename to rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs index 07cf25a4..c625e9fa 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/utils.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs @@ -3,33 +3,53 @@ //! Utility functions for VTL1 enclave operations //! -//! This module provides general-purpose utilities for enclave operations. +//! This module provides general-purpose utilities for enclave operations +//! that can be used across different SDK modules. use core::sync::atomic::{AtomicPtr, Ordering}; +use spin::Once; use windows_enclave::vertdll::{ ENCLAVE_FLAG_DYNAMIC_DEBUG_ACTIVE, ENCLAVE_FLAG_DYNAMIC_DEBUG_ENABLED, ENCLAVE_FLAG_FULL_DEBUG_ENABLED, ENCLAVE_INFORMATION, EnclaveGetEnclaveInformation, }; -use super::types::UserBoundKeyError; +/// Common error type for enclave utility operations +#[derive(Debug, Clone, Copy)] +pub enum EnclaveUtilsError { + /// Windows HRESULT error + Hresult(i32), +} -/// Static storage for cached enclave information -static ENCLAVE_INFO_CACHE: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); +impl EnclaveUtilsError { + /// Convert to HRESULT for compatibility + pub fn to_hresult(self) -> i32 { + match self { + EnclaveUtilsError::Hresult(hr) => hr, + } + } +} + +/// Get singleton for enclave information cache pointer +fn enclave_info_cache() -> &'static AtomicPtr { + static ENCLAVE_INFO_CACHE: Once> = Once::new(); + ENCLAVE_INFO_CACHE.call_once(|| AtomicPtr::new(core::ptr::null_mut())) +} /// Get enclave information, caching it on first access /// /// This function retrieves the enclave information from the system on first call /// and caches it for subsequent calls, similar to `veil::vtl1::enclave_information()`. -pub fn get_enclave_information() -> Result<&'static ENCLAVE_INFORMATION, UserBoundKeyError> { +pub fn get_enclave_information() -> Result { + let cache = enclave_info_cache(); + // Check if already cached - let ptr = ENCLAVE_INFO_CACHE.load(Ordering::Acquire); + let ptr = cache.load(Ordering::Acquire); if !ptr.is_null() { - return Ok(unsafe { &*ptr }); + return Ok(unsafe { *ptr }); } - // Allocate and initialize - // We use a Box that we leak to get a 'static reference + // Initialize enclave information let mut info: ENCLAVE_INFORMATION = unsafe { core::mem::zeroed() }; let hr = unsafe { @@ -40,7 +60,7 @@ pub fn get_enclave_information() -> Result<&'static ENCLAVE_INFORMATION, UserBou }; if hr < 0 { - return Err(UserBoundKeyError::Hresult(hr)); + return Err(EnclaveUtilsError::Hresult(hr)); } // Leak the box to get a 'static pointer @@ -48,7 +68,7 @@ pub fn get_enclave_information() -> Result<&'static ENCLAVE_INFORMATION, UserBou let new_ptr = alloc::boxed::Box::into_raw(boxed); // Try to set the cache (compare-and-swap) - match ENCLAVE_INFO_CACHE.compare_exchange( + match cache.compare_exchange( core::ptr::null_mut(), new_ptr, Ordering::Release, @@ -56,14 +76,14 @@ pub fn get_enclave_information() -> Result<&'static ENCLAVE_INFORMATION, UserBou ) { Ok(_) => { // We successfully set the cache - Ok(unsafe { &*new_ptr }) + Ok(unsafe { *new_ptr }) } Err(existing) => { // Another thread beat us - free our allocation and use theirs unsafe { let _ = alloc::boxed::Box::from_raw(new_ptr); } - Ok(unsafe { &*existing }) + Ok(unsafe { *existing }) } } } @@ -72,7 +92,7 @@ pub fn get_enclave_information() -> Result<&'static ENCLAVE_INFORMATION, UserBou /// /// Returns the enclave's base address as a pointer, suitable for passing /// to APIs that require an enclave identifier. -pub fn get_enclave_base_address() -> Result<*mut core::ffi::c_void, UserBoundKeyError> { +pub fn get_enclave_base_address() -> Result<*mut core::ffi::c_void, EnclaveUtilsError> { let info = get_enclave_information()?; Ok(info.BaseAddress) } @@ -81,27 +101,27 @@ pub fn get_enclave_base_address() -> Result<*mut core::ffi::c_void, UserBoundKey /// /// Convenience function that returns the base address as a u64, /// suitable for EDL interface calls. -pub fn get_enclave_base_address_u64() -> Result { +pub fn get_enclave_base_address_u64() -> Result { Ok(get_enclave_base_address()? as u64) } /// Check if full debug is enabled for the enclave #[allow(dead_code)] -pub fn is_enclave_full_debug_enabled() -> Result { +pub fn is_enclave_full_debug_enabled() -> Result { let info = get_enclave_information()?; Ok((info.Identity.Flags & ENCLAVE_FLAG_FULL_DEBUG_ENABLED) != 0) } /// Check if dynamic debug is enabled for the enclave #[allow(dead_code)] -pub fn is_enclave_dynamic_debug_enabled() -> Result { +pub fn is_enclave_dynamic_debug_enabled() -> Result { let info = get_enclave_information()?; Ok((info.Identity.Flags & ENCLAVE_FLAG_DYNAMIC_DEBUG_ENABLED) != 0) } /// Check if dynamic debug is currently active for the enclave #[allow(dead_code)] -pub fn is_enclave_dynamic_debug_active() -> Result { +pub fn is_enclave_dynamic_debug_active() -> Result { let info = get_enclave_information()?; Ok((info.Identity.Flags & ENCLAVE_FLAG_DYNAMIC_DEBUG_ACTIVE) != 0) } diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index 96872d85..f897432b 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -6,6 +6,7 @@ extern crate alloc; mod enclave_ffi; +pub mod common; pub mod userboundkey; // Re-export the SDK generated enclave crate. diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs index 60664ab7..ac88ae1f 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/crypto.rs @@ -1,466 +1,91 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -//! Cryptographic utilities for VTL1 enclave operations +//! User-bound key specific cryptographic constants and utilities. //! -//! This module provides crypto primitives used by user-bound key operations, -//! including random number generation, enclave sealing, and AES-GCM encryption. +//! This module provides UBK-specific constants and convenience wrappers over +//! `common::crypto` that use UBK-specific sizes and error types. -use alloc::vec::Vec; +use crate::common::crypto; +use crate::userboundkey::types::UserBoundKeyError; +use alloc::{string::ToString, vec::Vec}; -use windows_enclave::bcrypt::{ - BCRYPT_AES_GCM_ALG_HANDLE, BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO, BCRYPT_KEY_HANDLE, - BCRYPT_USE_SYSTEM_PREFERRED_RNG, BCryptDecrypt, BCryptDestroyKey, BCryptEncrypt, - BCryptGenRandom, BCryptGenerateSymmetricKey, +// Re-export from common +pub use crypto::{ + EnclaveSealingIdentityPolicy, SymmetricKeyHandle, check_hr, generate_symmetric_key_bytes, + seal_data, unseal_data, }; -use windows_enclave::vertdll::{ENCLAVE_UNSEAL_FLAG_STALE_KEY, EnclaveSealData, EnclaveUnsealData}; - -use super::types::UserBoundKeyError; // -// Constants +// UBK-specific constants // -/// Size of symmetric key in bytes (AES-256-GCM) +/// Size of symmetric keys in bytes (AES-256) pub const SYMMETRIC_KEY_SIZE_BYTES: usize = 32; -/// Size of DH key in bits (ECDH P-384) -#[allow(dead_code)] -pub const DH_KEY_SIZE_BITS: usize = 384; - -/// Size of signature key in bits (ECDSA P-384) +/// Size of Diffie-Hellman keys in bits #[allow(dead_code)] -pub const SIGNATURE_KEY_SIZE_BITS: usize = 384; +pub const DH_KEY_SIZE_BITS: u32 = 384; -/// Size of nonce in bytes -#[allow(dead_code)] +/// Size of nonce for AES-GCM in bytes pub const NONCE_SIZE: usize = 12; -/// Size of authentication tag in bytes -#[allow(dead_code)] +/// Size of authentication tag for AES-GCM in bytes pub const TAG_SIZE: usize = 16; -// -// Types -// - -/// Fixed-size symmetric key bytes -pub type SymmetricKeyBytes = [u8; SYMMETRIC_KEY_SIZE_BYTES]; - -/// Enclave sealing identity policy -#[repr(i32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EnclaveSealingIdentityPolicy { - /// Seal to the exact enclave image - SealToExactCode = 1, - /// Seal to the enclave signer - SealToSigner = 2, -} - -// -// Helper Functions -// - -/// Convert HRESULT to Result -pub fn check_hr(hr: i32) -> Result<(), UserBoundKeyError> { - if hr >= 0 { - Ok(()) - } else { - Err(UserBoundKeyError::Hresult(hr)) - } -} - -// -// Random Number Generation -// - -/// Convert NTSTATUS to Result -fn check_ntstatus(status: i32) -> Result<(), UserBoundKeyError> { - if status >= 0 { - Ok(()) - } else { - Err(UserBoundKeyError::NtStatus(status)) - } -} - -/// Generate random bytes using BCryptGenRandom -/// -/// Uses the system preferred RNG to generate cryptographically secure random bytes. -pub fn generate_random() -> Result<[u8; N], UserBoundKeyError> { - let mut buffer = [0u8; N]; - generate_random_bytes(&mut buffer)?; - Ok(buffer) -} - -/// Generate random bytes into a mutable slice -#[allow(dead_code)] -pub fn generate_random_bytes(buffer: &mut [u8]) -> Result<(), UserBoundKeyError> { - unsafe { - // BCryptGenRandom with BCRYPT_USE_SYSTEM_PREFERRED_RNG uses the system RNG - // and doesn't require an algorithm handle (pass null) - let status = BCryptGenRandom( - core::ptr::null_mut(), - buffer.as_mut_ptr(), - buffer.len() as u32, - BCRYPT_USE_SYSTEM_PREFERRED_RNG, - ); - check_ntstatus(status) - } -} +/// Flag indicating that the unsealing key is stale and data should be resealed +pub const ENCLAVE_UNSEAL_FLAG_STALE_KEY: u32 = 0x00000001; -/// Generate a symmetric key (32 bytes of random data) -pub fn generate_symmetric_key_bytes() -> Result { - generate_random::() -} +/// Zero nonce for AES-GCM (all zeros) +pub const ZERO_NONCE: [u8; NONCE_SIZE] = [0u8; NONCE_SIZE]; // -// Enclave Sealing +// UBK-specific functions // -/// Seal data using enclave sealing -/// -/// Encrypts and binds data to the enclave identity according to the specified policy. -/// -/// # Arguments -/// * `data` - The plaintext data to seal -/// * `sealing_policy` - The identity policy for sealing -/// * `runtime_policy` - Runtime policy flags -/// -/// # Returns -/// The sealed (encrypted) data blob -pub fn seal_data( - data: &[u8], - sealing_policy: EnclaveSealingIdentityPolicy, - runtime_policy: u32, -) -> Result, UserBoundKeyError> { - unsafe { - // First call to get required size - let mut sealed_size: u32 = 0; - let hr = EnclaveSealData( - data.as_ptr() as *const core::ffi::c_void, - data.len() as u32, - sealing_policy as i32, - runtime_policy, - core::ptr::null_mut(), - 0, - &mut sealed_size, - ); - check_hr(hr)?; - - // Allocate Vec and seal directly into it - let mut sealed_buffer = alloc::vec![0u8; sealed_size as usize]; - - let hr = EnclaveSealData( - data.as_ptr() as *const core::ffi::c_void, - data.len() as u32, - sealing_policy as i32, - runtime_policy, - sealed_buffer.as_mut_ptr() as *mut core::ffi::c_void, - sealed_size, - &mut sealed_size, - ); - check_hr(hr)?; - - Ok(sealed_buffer) - } -} - -/// Unseal data using enclave sealing -/// -/// Decrypts data that was previously sealed to this enclave. -/// -/// # Arguments -/// * `sealed_data` - The sealed data blob -/// -/// # Returns -/// A tuple of (unsealed_data, unseal_flags). Check unseal_flags for ENCLAVE_UNSEAL_FLAG_STALE_KEY -/// to determine if the sealing key has changed and the data should be resealed. -pub fn unseal_data(sealed_data: &[u8]) -> Result<(Vec, u32), UserBoundKeyError> { - unsafe { - // First call to get required size - let mut unsealed_size: u32 = 0; - let hr = EnclaveUnsealData( - sealed_data.as_ptr() as *const core::ffi::c_void, - sealed_data.len() as u32, - core::ptr::null_mut(), - 0, - &mut unsealed_size, - core::ptr::null_mut(), - core::ptr::null_mut(), - ); - check_hr(hr)?; - - // Allocate Vec and unseal directly into it - let mut unsealed_buffer = alloc::vec![0u8; unsealed_size as usize]; - let mut unseal_flags: u32 = 0; - - let hr = EnclaveUnsealData( - sealed_data.as_ptr() as *const core::ffi::c_void, - sealed_data.len() as u32, - unsealed_buffer.as_mut_ptr() as *mut core::ffi::c_void, - unsealed_size, - &mut unsealed_size, - core::ptr::null_mut(), - &mut unseal_flags, - ); - check_hr(hr)?; - - Ok((unsealed_buffer, unseal_flags)) - } -} - /// Check if unseal flags indicate a stale key -/// -/// When the sealing key has rotated, unsealing still succeeds but sets this flag -/// to indicate the data should be resealed with the new key. #[inline] pub fn is_stale_key(unseal_flags: u32) -> bool { (unseal_flags & ENCLAVE_UNSEAL_FLAG_STALE_KEY) != 0 } -// -// Nonce utilities -// - -/// Create a nonce buffer from a numeric value -/// -/// Creates a 12-byte nonce with the value placed at the end (little-endian). -#[allow(dead_code)] -pub fn make_nonce_from_number(nonce: u64) -> [u8; NONCE_SIZE] { - let mut buffer = [0u8; NONCE_SIZE]; - // Place nonce value at the end of the buffer - let nonce_bytes = nonce.to_le_bytes(); - buffer[NONCE_SIZE - 8..].copy_from_slice(&nonce_bytes); - buffer -} - -/// Zero nonce for AES-GCM (all zeros) -pub const ZERO_NONCE: [u8; NONCE_SIZE] = [0u8; NONCE_SIZE]; - -// -// Symmetric Key Handle (RAII wrapper) -// - -/// RAII wrapper for BCrypt symmetric key handle -pub struct SymmetricKeyHandle { - handle: BCRYPT_KEY_HANDLE, -} - -impl SymmetricKeyHandle { - /// Create a symmetric key from raw key bytes using AES-GCM - pub fn from_bytes(key_bytes: &[u8]) -> Result { - let mut handle: BCRYPT_KEY_HANDLE = core::ptr::null_mut(); - - unsafe { - let status = BCryptGenerateSymmetricKey( - BCRYPT_AES_GCM_ALG_HANDLE, - &mut handle, - core::ptr::null_mut(), // No key object buffer needed - 0, - key_bytes.as_ptr(), - key_bytes.len() as u32, - 0, - ); - check_ntstatus(status)?; - } - - Ok(Self { handle }) - } - - /// Get the raw handle for use with BCrypt functions - pub fn handle(&self) -> BCRYPT_KEY_HANDLE { - self.handle - } -} - -impl Drop for SymmetricKeyHandle { - fn drop(&mut self) { - if !self.handle.is_null() { - unsafe { - let _ = BCryptDestroyKey(self.handle); - } - } - } -} - -// -// AES-GCM Encryption / Decryption -// - -/// Encrypt data using AES-GCM -/// -/// # Arguments -/// * `key` - The symmetric key handle -/// * `plaintext` - Data to encrypt -/// * `nonce` - 12-byte nonce (IV) +/// Encrypt data using AES-GCM with UBK constants. /// -/// # Returns -/// A tuple of (ciphertext, tag) where tag is 16 bytes +/// This is a convenience wrapper over `common::crypto::encrypt` that uses +/// UBK-specific tag size and returns `UserBoundKeyError`. pub fn encrypt( key: &SymmetricKeyHandle, plaintext: &[u8], - nonce: &[u8; NONCE_SIZE], -) -> Result<(Vec, [u8; TAG_SIZE]), UserBoundKeyError> { - let mut tag = [0u8; TAG_SIZE]; - let mut nonce_copy = *nonce; - - // AES-GCM: ciphertext length equals plaintext length - let mut ciphertext = alloc::vec![0u8; plaintext.len()]; - let mut result_size: u32 = 0; - - let cipher_info = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO::for_encrypt(&mut nonce_copy, &mut tag); - - unsafe { - let status = BCryptEncrypt( - key.handle(), - plaintext.as_ptr(), - plaintext.len() as u32, - &cipher_info as *const _ as *const core::ffi::c_void, - core::ptr::null_mut(), // No IV buffer (using nonce in cipher_info) - 0, - ciphertext.as_mut_ptr(), - ciphertext.len() as u32, - &mut result_size, - 0, - ); - check_ntstatus(status)?; - } - - Ok((ciphertext, tag)) + nonce: &[u8], +) -> Result<(Vec, Vec), UserBoundKeyError> { + crypto::encrypt(key, plaintext, nonce, TAG_SIZE) + .map_err(|e| UserBoundKeyError::CryptoError(e.to_string())) } -/// Decrypt data using AES-GCM -/// -/// # Arguments -/// * `key` - The symmetric key handle -/// * `ciphertext` - Data to decrypt -/// * `nonce` - 12-byte nonce (IV) used during encryption -/// * `tag` - 16-byte authentication tag from encryption +/// Decrypt data using AES-GCM. /// -/// # Returns -/// The decrypted plaintext +/// This is a convenience wrapper over `common::crypto::decrypt` that returns +/// `UserBoundKeyError`. pub fn decrypt( key: &SymmetricKeyHandle, ciphertext: &[u8], - nonce: &[u8; NONCE_SIZE], - tag: &[u8; TAG_SIZE], + nonce: &[u8], + tag: &[u8], ) -> Result, UserBoundKeyError> { - // AES-GCM: plaintext length equals ciphertext length - let mut plaintext = alloc::vec![0u8; ciphertext.len()]; - let mut result_size: u32 = 0; - - let cipher_info = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO::for_decrypt(nonce, tag); - - unsafe { - let status = BCryptDecrypt( - key.handle(), - ciphertext.as_ptr(), - ciphertext.len() as u32, - &cipher_info as *const _ as *const core::ffi::c_void, - core::ptr::null_mut(), // No IV buffer (using nonce in cipher_info) - 0, - plaintext.as_mut_ptr(), - plaintext.len() as u32, - &mut result_size, - 0, - ); - - // STATUS_AUTH_TAG_MISMATCH = 0xC000A002 - if status == -1073700862i32 { - return Err(UserBoundKeyError::InvalidData( - "Authentication tag mismatch", - )); + crypto::decrypt(key, ciphertext, nonce, tag).map_err(|e| { + let error_str = e.to_string(); + if error_str.contains("Authentication tag mismatch") { + UserBoundKeyError::InvalidData("Authentication tag mismatch") + } else { + UserBoundKeyError::CryptoError(error_str) } - check_ntstatus(status)?; - } - - Ok(plaintext) -} - -/// Encrypt data and append the tag to the output -/// -/// # Arguments -/// * `key` - The symmetric key handle -/// * `plaintext` - Data to encrypt -/// * `nonce` - 12-byte nonce (IV) -/// -/// # Returns -/// Combined output: [ciphertext][tag:16 bytes] -pub fn encrypt_and_tag( - key: &SymmetricKeyHandle, - plaintext: &[u8], - nonce: &[u8; NONCE_SIZE], -) -> Result, UserBoundKeyError> { - let (ciphertext, tag) = encrypt(key, plaintext, nonce)?; - - let mut combined = Vec::with_capacity(ciphertext.len() + TAG_SIZE); - combined.extend_from_slice(&ciphertext); - combined.extend_from_slice(&tag); - - Ok(combined) -} - -/// Encrypt data with zero nonce and append tag -/// -/// Uses a zero nonce (all zeros). Only safe when each key is used once. -pub fn encrypt_and_tag_zero_nonce( - key: &SymmetricKeyHandle, - plaintext: &[u8], -) -> Result, UserBoundKeyError> { - encrypt_and_tag(key, plaintext, &ZERO_NONCE) -} - -/// Decrypt data that has the tag appended -/// -/// # Arguments -/// * `key` - The symmetric key handle -/// * `combined` - Combined input: [ciphertext][tag:16 bytes] -/// * `nonce` - 12-byte nonce (IV) used during encryption -/// -/// # Returns -/// The decrypted plaintext -pub fn decrypt_and_untag( - key: &SymmetricKeyHandle, - combined: &[u8], - nonce: &[u8; NONCE_SIZE], -) -> Result, UserBoundKeyError> { - if combined.len() < TAG_SIZE { - return Err(UserBoundKeyError::InvalidData("Data too short for tag")); - } - - let ciphertext_len = combined.len() - TAG_SIZE; - let ciphertext = &combined[..ciphertext_len]; - let tag: [u8; TAG_SIZE] = combined[ciphertext_len..] - .try_into() - .map_err(|_| UserBoundKeyError::InvalidData("Invalid tag length"))?; - - decrypt(key, ciphertext, nonce, &tag) -} - -/// Decrypt data with zero nonce that has tag appended -/// -/// Uses a zero nonce (all zeros). -pub fn decrypt_and_untag_zero_nonce( - key: &SymmetricKeyHandle, - combined: &[u8], -) -> Result, UserBoundKeyError> { - decrypt_and_untag(key, combined, &ZERO_NONCE) + }) } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_make_nonce_from_number() { - let nonce = make_nonce_from_number(0x0102030405060708); - assert_eq!(nonce[0..4], [0, 0, 0, 0]); - assert_eq!( - nonce[4..12], - [0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01] - ); - } - #[test] fn test_is_stale_key() { assert!(!is_stale_key(0)); diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs index 2f01db7c..afc1ee96 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs @@ -8,12 +8,10 @@ mod crypto; mod types; -mod utils; pub use crypto::{ EnclaveSealingIdentityPolicy, NONCE_SIZE, SYMMETRIC_KEY_SIZE_BYTES, SymmetricKeyHandle, - TAG_SIZE, ZERO_NONCE, decrypt, decrypt_and_untag, decrypt_and_untag_zero_nonce, encrypt, - encrypt_and_tag, encrypt_and_tag_zero_nonce, + TAG_SIZE, ZERO_NONCE, decrypt, encrypt, }; pub use types::*; @@ -41,8 +39,8 @@ use windows_enclave::veinterop::{ }; use windows_enclave::vertdll::{GetProcessHeap, HeapFree}; +use crate::common::get_enclave_base_address_u64; use crypto::{check_hr, generate_symmetric_key_bytes, is_stale_key, seal_data, unseal_data}; -use utils::get_enclave_base_address_u64; // BCrypt algorithm pseudo-handle value from Windows SDK bcrypt.h. // BCRYPT_ECDH_P384_ALG_HANDLE = ((BCRYPT_ALG_HANDLE) 0x000002b1) @@ -253,7 +251,7 @@ pub fn create_user_bound_key( key_credential_creation_option: u32, ) -> Result, UserBoundKeyError> { // Generate symmetric key - let user_key_bytes = generate_symmetric_key_bytes()?; + let user_key_bytes = generate_symmetric_key_bytes(SYMMETRIC_KEY_SIZE_BYTES)?; create_user_bound_key_with_custom_key( key_name, diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs index 411538a3..9174e721 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs @@ -3,6 +3,8 @@ //! Type definitions for user-bound key operations +use crate::common::EnclaveUtilsError; +use alloc::{boxed::Box, string::String}; use sdk_enclave_gen::AbiError; use windows_enclave::vertdll::RtlNtStatusToDosError; @@ -25,6 +27,8 @@ pub enum UserBoundKeyError { StaleKey, /// Feature not implemented NotImplemented(&'static str), + /// General cryptographic error + CryptoError(String), } impl From for UserBoundKeyError { @@ -33,6 +37,20 @@ impl From for UserBoundKeyError { } } +impl From for UserBoundKeyError { + fn from(err: EnclaveUtilsError) -> Self { + match err { + EnclaveUtilsError::Hresult(hr) => UserBoundKeyError::Hresult(hr), + } + } +} + +impl From> for UserBoundKeyError { + fn from(err: Box) -> Self { + UserBoundKeyError::CryptoError(alloc::format!("{}", err)) + } +} + /// Converts a Win32 error code to an HRESULT. /// Equivalent to the HRESULT_FROM_WIN32 macro. #[inline] @@ -62,6 +80,7 @@ impl UserBoundKeyError { UserBoundKeyError::SecurityViolation(_) => -2147024891, // E_ACCESSDENIED UserBoundKeyError::StaleKey => 0x80090325_u32 as i32, // SEC_E_BAD_PKGID or similar UserBoundKeyError::NotImplemented(_) => -2147467263, // E_NOTIMPL + UserBoundKeyError::CryptoError(_) => -2147467259, // E_FAIL } } diff --git a/rust/sdk/crates/libs/sdk-host/src/lib.rs b/rust/sdk/crates/libs/sdk-host/src/lib.rs index baed58a6..837c16f0 100644 --- a/rust/sdk/crates/libs/sdk-host/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-host/src/lib.rs @@ -26,7 +26,7 @@ pub use userboundkey_kcm::KeyCredentialManager; /// /// # Returns /// -/// Returns `Ok(())` on success, or an error string on failure. +/// Returns `Ok(())` on success, or an error on failure. /// /// # Example /// @@ -36,7 +36,9 @@ pub use userboundkey_kcm::KeyCredentialManager; /// let enclave = EnclaveHandle::create_and_initialize(path, size, owner_id)?; /// register_sdk_callbacks(enclave.as_ptr())?; /// ``` -pub fn register_sdk_callbacks(enclave_ptr: *mut core::ffi::c_void) -> Result<(), String> { +pub fn register_sdk_callbacks( + enclave_ptr: *mut core::ffi::c_void, +) -> Result<(), Box> { // Register userboundkey callbacks let sdk_wrapper = userboundkey::UserBoundKeyVtl0Host::new(enclave_ptr); sdk_wrapper diff --git a/rust/sdk/crates/samples/userboundkey/README.md b/rust/sdk/crates/samples/userboundkey/README.md index e250ac25..be3b1907 100644 --- a/rust/sdk/crates/samples/userboundkey/README.md +++ b/rust/sdk/crates/samples/userboundkey/README.md @@ -52,19 +52,30 @@ Test-Path "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.26100.0\ucrt_enclave" Use the provided build script that generates EDL bindings, builds, and signs in one step: ```powershell -cd C:\VbsEnclaveTooling\rust\sdk\crates\samples\userboundkey +cd C:\VbsEnclaveTooling\rust\sdk # Build with your signing certificate -.\build.ps1 -CertName "YourCertificateName" +.\generate_and_build_crates.ps1 -CertName "YourCertificateName" # For release build -.\build.ps1 -CertName "YourCertificateName" -Configuration Release +.\generate_and_build_crates.ps1 -Configuration release -CertName "YourCertificateName" ``` The script performs: -1. **Generate EDL bindings** - Creates Rust bindings from the EDL file -2. **Build** - Compiles both host and enclave -3. **Sign** - Signs the enclave DLL with veiid.exe and signtool +1. **Generate EDL bindings** - Creates Rust bindings from all EDL files (SDK + samples) +2. **Build** - Compiles entire SDK workspace including samples +3. **Sign** - Signs all enclave DLLs with veiid.exe and signtool + +### EDL Generation Only + +If you're working on EDL files and want fast intellisense updates without building: + +```powershell +cd C:\VbsEnclaveTooling\rust\sdk + +# Generate EDL bindings only (fast) +.\generate_codegen_for_workspace.ps1 +``` ### Manual Build Commands @@ -114,8 +125,8 @@ Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\D 1. **Build the sample using the build script:** ```powershell - cd C:\VbsEnclaveTooling\rust\sdk\crates\samples\userboundkey - .\build.ps1 -CertName "YourCertificateName" + cd C:\VbsEnclaveTooling\rust\sdk + .\generate_and_build_crates.ps1 -CertName "YourCertificateName" ``` 2. **Run the sample:** diff --git a/rust/sdk/crates/samples/userboundkey/build.ps1 b/rust/sdk/crates/samples/userboundkey/build.ps1 deleted file mode 100644 index e6b9423d..00000000 --- a/rust/sdk/crates/samples/userboundkey/build.ps1 +++ /dev/null @@ -1,88 +0,0 @@ -# Builds and signs the userboundkey sample. -# -# Usage: -# .\build.ps1 -CertName "MyTestEnclaveCertNew" -# .\build.ps1 -CertName "MyTestEnclaveCertNew" -Configuration Release - -param( - [Parameter(Mandatory=$true)] - [string]$CertName, - - [ValidateSet("Debug", "Release")] - [string]$Configuration = "Debug" -) - -$ErrorActionPreference = "Stop" - -$sampleDir = $PSScriptRoot -$sdkRoot = Resolve-Path "$sampleDir\..\..\..\" -$rustRoot = Resolve-Path "$sdkRoot\.." -$scriptsDir = Join-Path $rustRoot "scripts" - -Write-Host "`n=== User-Bound Key Sample Build ===" -ForegroundColor Cyan -Write-Host "Configuration: $Configuration" -Write-Host "Certificate: $CertName" - -# ============================================================================ -# Step 1: Generate EDL bindings -# ============================================================================ -Write-Host "`n[1/3] Generating EDL bindings..." -ForegroundColor Yellow - -$generateScript = Join-Path $scriptsDir "generate_codegen_crates.ps1" -$sampleEdl = Join-Path $sampleDir "userboundkey_sample.edl" -$libsImportDir = Join-Path $sdkRoot "crates\libs" - -& $generateScript ` - -HostAppOutDir "$sampleDir\host\generated" ` - -EnclaveOutDir "$sampleDir\enclave\generated" ` - -EdlPath $sampleEdl ` - -Namespace "userboundkey_sample" ` - -Vtl0ClassName "userboundkey_sampleWrapper" ` - -ImportDirectories $libsImportDir - -if ($LASTEXITCODE -ne 0) { throw "EDL generation failed." } -Write-Host "EDL bindings generated." -ForegroundColor Green - -# ============================================================================ -# Step 2: Build host and enclave -# ============================================================================ -Write-Host "`n[2/3] Building..." -ForegroundColor Yellow - -Push-Location $sdkRoot -try { - $buildArgs = @("build", "-p", "userboundkey-sample-host", "-p", "userboundkey-sample-enclave") - if ($Configuration -eq "Release") { - $buildArgs += "--release" - } - - cargo @buildArgs - - if ($LASTEXITCODE -ne 0) { throw "Cargo build failed." } -} finally { - Pop-Location -} -Write-Host "Build completed." -ForegroundColor Green - -# ============================================================================ -# Step 3: Sign the enclave -# ============================================================================ -Write-Host "`n[3/3] Signing enclave..." -ForegroundColor Yellow - -$targetDir = if ($Configuration -eq "Release") { "release" } else { "debug" } -$enclaveDll = Join-Path $sdkRoot "target\$targetDir\userboundkey_sample_enclave.dll" -$signScript = Join-Path $scriptsDir "sign-enclave.ps1" - -& $signScript -DllPath $enclaveDll -CertName $CertName - -if ($LASTEXITCODE -ne 0) { throw "Signing failed." } -Write-Host "Enclave signed." -ForegroundColor Green - -# ============================================================================ -# Done -# ============================================================================ -Write-Host "`n=== Build Complete ===" -ForegroundColor Cyan -Write-Host "Output: $(Join-Path $sdkRoot "target\$targetDir")" -Write-Host "" -Write-Host "To run:" -Write-Host " cd $(Join-Path $sdkRoot "target\$targetDir")" -Write-Host " .\userboundkey-sample.exe" diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs index b5b16aa9..fb9031c7 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs @@ -90,7 +90,7 @@ fn ensure_user_bound_key_loaded( *needs_reseal = true; if let Ok(resealed) = reseal_user_bound_key( &key_bytes, - EnclaveSealingIdentityPolicy::SealToExactCode, + EnclaveSealingIdentityPolicy::EnclaveIdentity, RUNTIME_POLICY_NO_DEBUG, ) { *resealed_encryption_key_bytes = resealed; @@ -102,7 +102,7 @@ fn ensure_user_bound_key_loaded( // First load failed, attempt reseal and retry let resealed_bytes = reseal_user_bound_key( secured_encryption_key_bytes, - EnclaveSealingIdentityPolicy::SealToExactCode, + EnclaveSealingIdentityPolicy::EnclaveIdentity, RUNTIME_POLICY_NO_DEBUG, ) .map_err(|_| AbiError::Hresult(e.to_hresult()))?; @@ -126,7 +126,7 @@ fn ensure_user_bound_key_loaded( // Create symmetric key from loaded raw key material let symmetric_key = SymmetricKeyHandle::from_bytes(&loaded_key_bytes) - .map_err(|e| AbiError::Hresult(e.to_hresult()))?; + .map_err(|_| AbiError::Hresult(-2147467259))?; // E_FAIL set_encryption_key(symmetric_key); Ok(()) @@ -162,7 +162,7 @@ impl Trusted for EnclaveImpl { &cache_config, U16Str::from_slice(&pinMessage.wchars), windowId, - EnclaveSealingIdentityPolicy::SealToExactCode, + EnclaveSealingIdentityPolicy::EnclaveIdentity, RUNTIME_POLICY_NO_DEBUG, keyCredentialCreationOption, ) diff --git a/rust/sdk/crates/samples/userboundkey/host/src/main.rs b/rust/sdk/crates/samples/userboundkey/host/src/main.rs index 93a77ad1..51a57228 100644 --- a/rust/sdk/crates/samples/userboundkey/host/src/main.rs +++ b/rust/sdk/crates/samples/userboundkey/host/src/main.rs @@ -25,7 +25,7 @@ use windows::core::Interface; // Generated host stubs for enclave calls use userboundkey_sample_host_gen::implementation::types::edl::WString; -use userboundkey_sample_host_gen::stubs::trusted::userboundkey_sampleWrapper; +use userboundkey_sample_host_gen::stubs::trusted::UntrustedImpl; const KEY_NAME: &str = "MyEncryptionKey-001"; const PIN_MESSAGE: &str = "User-Bound Key Sample"; @@ -92,7 +92,7 @@ fn get_secure_id_from_windows_hello() -> Result, Box Result<(EnclaveHandle, userboundkey_sampleWrapper), Box> { +) -> Result<(EnclaveHandle, UntrustedImpl), Box> { println!("Loading enclave from: {}", enclave_path.display()); // Get the secure ID from Windows Hello - required for user-bound key operations @@ -107,7 +107,7 @@ fn load_enclave( EnclaveHandle::create_and_initialize(enclave_path, megabytes(512), Some(&owner_id))?; // Create the wrapper interface for enclave calls - let wrapper = userboundkey_sampleWrapper::new(enclave.as_ptr()); + let wrapper = UntrustedImpl::new(enclave.as_ptr()); // Register the sample's VTL0 callbacks (for debug_print, etc.) wrapper diff --git a/rust/sdk/generate_and_build_crates.ps1 b/rust/sdk/generate_and_build_crates.ps1 new file mode 100644 index 00000000..78e9eb36 --- /dev/null +++ b/rust/sdk/generate_and_build_crates.ps1 @@ -0,0 +1,69 @@ +# Complete build pipeline for the VBS Enclave SDK workspace. +# +# This script performs the full build process: +# 1. Generates EDL bindings for all SDK and sample crates +# 2. Compiles the entire workspace with Cargo +# 3. Optionally signs enclave DLLs for deployment +# +# Usage: +# .\generate_and_build_crates.ps1 # Debug build only +# .\generate_and_build_crates.ps1 -Configuration release # Release build only +# .\generate_and_build_crates.ps1 -CertName "MyCert" # Debug build + sign +# .\generate_and_build_crates.ps1 -Configuration release -CertName "MyCert" # Release + sign + +param( + [ValidateSet("debug", "release")] + [string]$Configuration = "debug", + + [string]$CertName +) + +$errorActionPreference = "Stop" + +# Locate shared scripts folder +. "$PSScriptRoot\..\scripts\get_common_paths.ps1" + +Write-Host "`n=== VBS Enclave SDK Build ===" -ForegroundColor Cyan +Write-Host "Configuration: $Configuration" +if ($CertName) { + Write-Host "Certificate: $CertName (will sign enclaves)" +} + +# Step 1: Generate EDL bindings for entire workspace (SDK + samples) +Write-Host "`nGenerating EDL bindings..." -ForegroundColor Yellow +. "$PSScriptRoot\generate_codegen_for_workspace.ps1" + +# Step 2: Build the entire SDK workspace with Cargo +Write-Host "`nBuilding workspace..." -ForegroundColor Yellow +. "$scriptsDir\invoke_cargo_build.ps1" -Path $PSScriptRoot -Configuration $Configuration + +# Step 3: Sign enclave DLLs if certificate is provided +if ($CertName) { + Write-Host "`nSigning enclave DLLs..." -ForegroundColor Yellow + + $signScript = Join-Path $scriptsDir "sign-enclave.ps1" + $targetDir = if ($Configuration -eq "release") { "release" } else { "debug" } + $targetPath = Join-Path $PSScriptRoot "target\$targetDir" + + # Find all enclave DLLs (convention: *_enclave.dll) + $enclaveDlls = Get-ChildItem -Path $targetPath -Filter "*_enclave.dll" -ErrorAction SilentlyContinue + + if ($enclaveDlls.Count -eq 0) { + Write-Host "No enclave DLLs found to sign." -ForegroundColor Yellow + } else { + foreach ($dll in $enclaveDlls) { + Write-Host "Signing $($dll.Name)..." -ForegroundColor Gray + & $signScript -DllPath $dll.FullName -CertName $CertName + if ($LASTEXITCODE -ne 0) { throw "Failed to sign $($dll.Name)" } + } + Write-Host "All enclaves signed." -ForegroundColor Green + } +} + +Write-Host "`n=== SDK Build Complete ===" -ForegroundColor Cyan +$targetDir = if ($Configuration -eq "release") { "release" } else { "debug" } +Write-Host "Output directory: $(Join-Path $PSScriptRoot "target\$targetDir")" +if (-not $CertName) { + Write-Host "" + Write-Host "Tip: Use -CertName to sign enclave DLLs after build." -ForegroundColor Gray +} \ No newline at end of file diff --git a/rust/sdk/generate_codegen_for_workspace.ps1 b/rust/sdk/generate_codegen_for_workspace.ps1 new file mode 100644 index 00000000..9e7f86f5 --- /dev/null +++ b/rust/sdk/generate_codegen_for_workspace.ps1 @@ -0,0 +1,56 @@ +# Generates EDL code bindings for the entire SDK workspace. +# +# This script generates Rust bindings from EDL files for: +# - SDK core libraries (veil_abi and sdk namespaces) +# - Sample applications (userboundkey_sample) +# +# Use this script when: +# - Working on EDL interface definitions +# - Need updated IntelliSense after EDL changes +# - Want fast iteration without full compilation +# +# The generated bindings are automatically formatted with rustfmt. + +$errorActionPreference = "Stop" + +# Locate shared scripts folder +. "$PSScriptRoot\..\scripts\get_common_paths.ps1" + +# SDK crate paths +$edlPath = Join-Path $repoRoot "src\VbsEnclaveSDK\veil_abi.edl" +$enclaveSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-enclave" +$hostSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-host" + +# Generate veil_abi EDL bindings (core VBS enclave interface) +. "$scriptsDir\generate_codegen_crates.ps1" ` + -HostAppOutDir "$hostSdkCrate\generated" ` + -EnclaveOutDir "$enclaveSdkCrate\generated" ` + -EdlPath $edlPath ` + -Namespace "veil_abi" ` + -Vtl0ClassName "export_interface" + +# Generate SDK EDL bindings (user-bound key APIs and other SDK features) +$sdkEdl = Join-Path $SdkWorkspacePath "crates\libs\sdk.edl" +. "$scriptsDir\generate_codegen_crates.ps1" ` + -HostAppOutDir "$hostSdkCrate\generated" ` + -EnclaveOutDir "$enclaveSdkCrate\generated" ` + -EdlPath $sdkEdl ` + -Namespace "sdk" ` + -Vtl0ClassName "UserBoundKeyVtl0Host" + +# Generate sample EDL bindings (userboundkey sample application interface) +$sampleDir = Join-Path $SdkWorkspacePath "crates\samples\userboundkey" +$sampleEdl = Join-Path $sampleDir "userboundkey_sample.edl" +$libsImportDir = Join-Path $SdkWorkspacePath "crates\libs" + +. "$scriptsDir\generate_codegen_crates.ps1" ` + -HostAppOutDir "$sampleDir\host\generated" ` + -EnclaveOutDir "$sampleDir\enclave\generated" ` + -EdlPath $sampleEdl ` + -Namespace "userboundkey_sample" ` + -ImportDir $libsImportDir ` + -Vtl0ClassName "UntrustedImpl" + +# Format the workspace after code generation to make sure all generated code +# is properly formatted. +. "$scriptsDir\invoke_rustfmt_workspace.ps1" -WorkspacePath $PSScriptRoot \ No newline at end of file From 5adbf267f8649c3c5071dd4b139cf244ab63fd03 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Thu, 22 Jan 2026 16:38:11 -0800 Subject: [PATCH 10/13] Addressed PR comments 4 (in progress) --- rust/sdk/Cargo.toml | 12 ----- .../libs/userboundkey-kcm/src/bindings.rs | 1 - .../crates/samples/userboundkey/Cargo.toml | 34 +++++++++++++ rust/sdk/generate_and_build_crates.ps1 | 48 +++++++++++++++---- rust/sdk/generate_codegen_for_workspace.ps1 | 8 ++-- 5 files changed, 77 insertions(+), 26 deletions(-) create mode 100644 rust/sdk/crates/samples/userboundkey/Cargo.toml diff --git a/rust/sdk/Cargo.toml b/rust/sdk/Cargo.toml index 02eadf77..5415ef8b 100644 --- a/rust/sdk/Cargo.toml +++ b/rust/sdk/Cargo.toml @@ -8,23 +8,11 @@ members = [ "crates/libs/userboundkey-kcm", "crates/tests/sdk", "crates/libs/windows-enclave", - # Sample crates - "crates/samples/userboundkey/enclave", - "crates/samples/userboundkey/host", - "crates/samples/userboundkey/enclave/generated/userboundkey_sample_gen", - "crates/samples/userboundkey/host/generated/userboundkey_sample_gen", ] [workspace.package] edition = "2024" -# Enclaves require panic=abort since they are no_std cdylib -[profile.dev] -panic = "abort" - -[profile.release] -panic = "abort" - [workspace.dependencies] edlcodegen-tools = { git = "https://github.com/microsoft/VbsEnclaveTooling" } regex = "1.11" diff --git a/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs b/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs index 911f9b8c..d79448db 100644 --- a/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs +++ b/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs @@ -1880,7 +1880,6 @@ impl KeyCredentialStatus { pub const UserPrefersPassword: Self = Self(4i32); pub const CredentialAlreadyExists: Self = Self(5i32); pub const SecurityDeviceLocked: Self = Self(6i32); - pub const AlgorithmNotSupported: Self = Self(7i32); } impl windows_core::TypeKind for KeyCredentialStatus { type TypeKind = windows_core::CopyType; diff --git a/rust/sdk/crates/samples/userboundkey/Cargo.toml b/rust/sdk/crates/samples/userboundkey/Cargo.toml new file mode 100644 index 00000000..ffdaeb56 --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/Cargo.toml @@ -0,0 +1,34 @@ +# Workspace for the user-bound key sample (host and enclave) +[workspace] +resolver = "3" +members = [ + "host", + "host/generated/userboundkey_sample_gen", + "enclave", + "enclave/generated/userboundkey_sample_gen", +] + +[workspace.package] +edition = "2024" + +[workspace.dependencies] +# SDK dependencies - use path for local development +vbsenclave-sdk-enclave = { version = "0.1.0", path = "../../libs/sdk-enclave" } +vbsenclave-sdk-host = { version = "0.1.0", path = "../../libs/sdk-host" } +windows-enclave = { version = "0.1.0", path = "../../libs/windows-enclave" } + +# External dependencies +edlcodegen-tools = { git = "https://github.com/microsoft/VbsEnclaveTooling" } +spin = "0.10.0" +widestring = { version = "1.1", default-features = false, features = ["alloc"] } +windows = "0.62.2" +windows-core = "0.62.2" + +# The enclave crate requires panic=abort (no_std cdylib). +# This applies to the entire workspace, but the host is a simple CLI +# so it doesn't critically need unwinding. +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" diff --git a/rust/sdk/generate_and_build_crates.ps1 b/rust/sdk/generate_and_build_crates.ps1 index 78e9eb36..254081bb 100644 --- a/rust/sdk/generate_and_build_crates.ps1 +++ b/rust/sdk/generate_and_build_crates.ps1 @@ -2,8 +2,9 @@ # # This script performs the full build process: # 1. Generates EDL bindings for all SDK and sample crates -# 2. Compiles the entire workspace with Cargo -# 3. Optionally signs enclave DLLs for deployment +# 2. Compiles the SDK workspace with Cargo +# 3. Compiles sample workspaces separately +# 4. Optionally signs enclave DLLs for deployment # # Usage: # .\generate_and_build_crates.ps1 # Debug build only @@ -29,24 +30,50 @@ if ($CertName) { Write-Host "Certificate: $CertName (will sign enclaves)" } -# Step 1: Generate EDL bindings for entire workspace (SDK + samples) +# Step 1: Generate EDL bindings for SDK and samples Write-Host "`nGenerating EDL bindings..." -ForegroundColor Yellow . "$PSScriptRoot\generate_codegen_for_workspace.ps1" -# Step 2: Build the entire SDK workspace with Cargo -Write-Host "`nBuilding workspace..." -ForegroundColor Yellow +# Step 2: Build the SDK workspace with Cargo +Write-Host "`nBuilding SDK workspace..." -ForegroundColor Yellow . "$scriptsDir\invoke_cargo_build.ps1" -Path $PSScriptRoot -Configuration $Configuration -# Step 3: Sign enclave DLLs if certificate is provided +# Step 3: Build sample workspaces separately +# Samples are in their own workspaces to allow different panic profiles +# (enclave uses panic=abort, host uses default panic=unwind) +Write-Host "`nBuilding sample workspaces..." -ForegroundColor Yellow + +$sampleWorkspaces = @( + (Join-Path $PSScriptRoot "crates\samples\userboundkey") +) + +foreach ($samplePath in $sampleWorkspaces) { + if (Test-Path (Join-Path $samplePath "Cargo.toml")) { + Write-Host "Building sample: $(Split-Path $samplePath -Leaf)" -ForegroundColor Gray + . "$scriptsDir\invoke_cargo_build.ps1" -Path $samplePath -Configuration $Configuration + } +} + +# Step 4: Sign enclave DLLs if certificate is provided if ($CertName) { Write-Host "`nSigning enclave DLLs..." -ForegroundColor Yellow $signScript = Join-Path $scriptsDir "sign-enclave.ps1" $targetDir = if ($Configuration -eq "release") { "release" } else { "debug" } - $targetPath = Join-Path $PSScriptRoot "target\$targetDir" + + # Collect enclave DLLs from SDK and sample target directories + $targetPaths = @( + (Join-Path $PSScriptRoot "target\$targetDir") + ) + foreach ($samplePath in $sampleWorkspaces) { + $targetPaths += (Join-Path $samplePath "target\$targetDir") + } - # Find all enclave DLLs (convention: *_enclave.dll) - $enclaveDlls = Get-ChildItem -Path $targetPath -Filter "*_enclave.dll" -ErrorAction SilentlyContinue + $enclaveDlls = @() + foreach ($targetPath in $targetPaths) { + $dlls = Get-ChildItem -Path $targetPath -Filter "*_enclave.dll" -ErrorAction SilentlyContinue + if ($dlls) { $enclaveDlls += $dlls } + } if ($enclaveDlls.Count -eq 0) { Write-Host "No enclave DLLs found to sign." -ForegroundColor Yellow @@ -62,7 +89,8 @@ if ($CertName) { Write-Host "`n=== SDK Build Complete ===" -ForegroundColor Cyan $targetDir = if ($Configuration -eq "release") { "release" } else { "debug" } -Write-Host "Output directory: $(Join-Path $PSScriptRoot "target\$targetDir")" +Write-Host "SDK output: $(Join-Path $PSScriptRoot "target\$targetDir")" +Write-Host "Sample output: $(Join-Path $PSScriptRoot "crates\samples\userboundkey\target\$targetDir")" if (-not $CertName) { Write-Host "" Write-Host "Tip: Use -CertName to sign enclave DLLs after build." -ForegroundColor Gray diff --git a/rust/sdk/generate_codegen_for_workspace.ps1 b/rust/sdk/generate_codegen_for_workspace.ps1 index 9e7f86f5..d88667dd 100644 --- a/rust/sdk/generate_codegen_for_workspace.ps1 +++ b/rust/sdk/generate_codegen_for_workspace.ps1 @@ -51,6 +51,8 @@ $libsImportDir = Join-Path $SdkWorkspacePath "crates\libs" -ImportDir $libsImportDir ` -Vtl0ClassName "UntrustedImpl" -# Format the workspace after code generation to make sure all generated code -# is properly formatted. -. "$scriptsDir\invoke_rustfmt_workspace.ps1" -WorkspacePath $PSScriptRoot \ No newline at end of file +# Format SDK workspace +. "$scriptsDir\invoke_rustfmt_workspace.ps1" -WorkspacePath $PSScriptRoot + +# Format sample workspace (now in its own workspace) +. "$scriptsDir\invoke_rustfmt_workspace.ps1" -WorkspacePath $sampleDir \ No newline at end of file From 871313975881a993241c8dfe7e7df53f31424f82 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Thu, 22 Jan 2026 16:59:58 -0800 Subject: [PATCH 11/13] Use the updated binding for NGC APIs --- .../libs/userboundkey-kcm/src/bindings.rs | 73 ++++++++++ .../crates/libs/userboundkey-kcm/src/lib.rs | 137 ++---------------- 2 files changed, 85 insertions(+), 125 deletions(-) diff --git a/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs b/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs index c39c1308..b7f34c3e 100644 --- a/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs +++ b/rust/sdk/crates/libs/userboundkey-kcm/src/bindings.rs @@ -1803,6 +1803,67 @@ impl KeyCredentialManager { .and_then(|| windows_core::Type::from_abi(result__)) }) } + pub fn RequestCreateAsync2( + name: &windows_core::HSTRING, + option: KeyCredentialCreationOption, + algorithm: &windows_core::HSTRING, + message: &windows_core::HSTRING, + cacheconfiguration: P4, + windowid: WindowId, + callbacktype: ChallengeResponseKind, + attestationcallback: P7, + ) -> windows_core::Result> + where + P4: windows_core::Param, + P7: windows_core::Param, + { + Self::IKeyCredentialManagerStatics2(|this| unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).RequestCreateAsync)( + windows_core::Interface::as_raw(this), + core::mem::transmute_copy(name), + option, + core::mem::transmute_copy(algorithm), + core::mem::transmute_copy(message), + cacheconfiguration.param().abi(), + windowid, + callbacktype, + attestationcallback.param().abi(), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + }) + } + pub fn OpenAsync2( + name: &windows_core::HSTRING, + callbacktype: ChallengeResponseKind, + attestationcallback: P2, + ) -> windows_core::Result> + where + P2: windows_core::Param, + { + Self::IKeyCredentialManagerStatics2(|this| unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).OpenAsync)( + windows_core::Interface::as_raw(this), + core::mem::transmute_copy(name), + callbacktype, + attestationcallback.param().abi(), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + }) + } + pub fn GetSecureId() -> windows_core::Result { + Self::IKeyCredentialManagerStatics2(|this| unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).GetSecureId)( + windows_core::Interface::as_raw(this), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + }) + } fn IKeyCredentialManagerStatics< R, F: FnOnce(&IKeyCredentialManagerStatics) -> windows_core::Result, @@ -1815,6 +1876,18 @@ impl KeyCredentialManager { > = windows_core::imp::FactoryCache::new(); SHARED.call(callback) } + fn IKeyCredentialManagerStatics2< + R, + F: FnOnce(&IKeyCredentialManagerStatics2) -> windows_core::Result, + >( + callback: F, + ) -> windows_core::Result { + static SHARED: windows_core::imp::FactoryCache< + KeyCredentialManager, + IKeyCredentialManagerStatics2, + > = windows_core::imp::FactoryCache::new(); + SHARED.call(callback) + } } impl windows_core::RuntimeName for KeyCredentialManager { const NAME: &'static str = "Windows.Security.Credentials.KeyCredentialManager"; diff --git a/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs b/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs index 402716c5..2729b44e 100644 --- a/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs +++ b/rust/sdk/crates/libs/userboundkey-kcm/src/lib.rs @@ -1,130 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +//! Windows NGC (Next Generation Credentials) bindings for VBS enclave user-bound keys. +//! +//! This crate provides Rust bindings for the Windows KeyCredentialManager APIs +//! that support VBS enclave attestation. These APIs are generated using windows-bindgen. +//! +//! Key APIs: +//! - `KeyCredentialManager::RequestCreateAsync2` - Create credentials with VBS attestation +//! - `KeyCredentialManager::OpenAsync2` - Open credentials with VBS attestation +//! - `KeyCredentialManager::GetSecureId` - Get the secure identifier +//! - `KeyCredential::RequestDeriveSharedSecretAsync` - Derive shared secrets +//! - `KeyCredential::RetrieveAuthorizationContext` - Get authorization context + mod bindings; pub use bindings::*; - -use windows_core::{HSTRING, Interface, Param}; -use windows_future::IAsyncOperation; - -/// Extension methods for KeyCredentialManager to access IKeyCredentialManagerStatics2 APIs. -#[allow(non_snake_case)] -impl KeyCredentialManager { - /// Creates a key credential with VBS attestation support. - pub fn RequestCreateAsync2( - name: &HSTRING, - option: KeyCredentialCreationOption, - algorithm: &HSTRING, - message: &HSTRING, - cache_configuration: &KeyCredentialCacheConfiguration, - window_id: WindowId, - callback_type: ChallengeResponseKind, - attestation_callback: &AttestationChallengeHandler, - ) -> windows_core::Result> { - Self::IKeyCredentialManagerStatics2(|this| unsafe { - let mut result__ = core::mem::zeroed(); - (Interface::vtable(this).RequestCreateAsync)( - Interface::as_raw(this), - core::mem::transmute_copy(name), - option, - core::mem::transmute_copy(algorithm), - core::mem::transmute_copy(message), - core::mem::transmute_copy(cache_configuration), - window_id, - callback_type, - core::mem::transmute_copy(attestation_callback), - &mut result__, - ) - .and_then(|| windows_core::Type::from_abi(result__)) - }) - } - - /// Opens an existing key credential with VBS attestation support. - pub fn OpenAsync2( - name: &HSTRING, - callback_type: ChallengeResponseKind, - attestation_callback: &AttestationChallengeHandler, - ) -> windows_core::Result> { - Self::IKeyCredentialManagerStatics2(|this| unsafe { - let mut result__ = core::mem::zeroed(); - (Interface::vtable(this).OpenAsync)( - Interface::as_raw(this), - core::mem::transmute_copy(name), - callback_type, - core::mem::transmute_copy(attestation_callback), - &mut result__, - ) - .and_then(|| windows_core::Type::from_abi(result__)) - }) - } - - /// Gets the secure identifier for the credential manager. - pub fn GetSecureId() -> windows_core::Result { - Self::IKeyCredentialManagerStatics2(|this| unsafe { - let mut result__ = core::mem::zeroed(); - (Interface::vtable(this).GetSecureId)(Interface::as_raw(this), &mut result__) - .and_then(|| windows_core::Type::from_abi(result__)) - }) - } - - fn IKeyCredentialManagerStatics2< - R, - F: FnOnce(&IKeyCredentialManagerStatics2) -> windows_core::Result, - >( - callback: F, - ) -> windows_core::Result { - static SHARED: windows_core::imp::FactoryCache< - KeyCredentialManager, - IKeyCredentialManagerStatics2, - > = windows_core::imp::FactoryCache::new(); - SHARED.call(callback) - } -} - -/// Extension methods for KeyCredential to access IKeyCredential2 APIs. -#[allow(non_snake_case)] -impl KeyCredential { - /// Requests derivation of a shared secret using the credential. - pub fn RequestDeriveSharedSecretAsync( - &self, - window_id: WindowId, - message: &HSTRING, - encrypted_request: P2, - ) -> windows_core::Result> - where - P2: Param, - { - let this = &Interface::cast::(self)?; - unsafe { - let mut result__ = core::mem::zeroed(); - (Interface::vtable(this).RequestDeriveSharedSecretAsync)( - Interface::as_raw(this), - window_id, - core::mem::transmute_copy(message), - encrypted_request.param().abi(), - &mut result__, - ) - .and_then(|| windows_core::Type::from_abi(result__)) - } - } - - /// Retrieves the authorization context for the credential. - pub fn RetrieveAuthorizationContext( - &self, - encrypted_request: P0, - ) -> windows_core::Result - where - P0: Param, - { - let this = &Interface::cast::(self)?; - unsafe { - let mut result__ = core::mem::zeroed(); - (Interface::vtable(this).RetrieveAuthorizationContext)( - Interface::as_raw(this), - encrypted_request.param().abi(), - &mut result__, - ) - .and_then(|| windows_core::Type::from_abi(result__)) - } - } -} From d3ae3d635eb57bd9b6fc02b1ea752f13268bb4a8 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Fri, 23 Jan 2026 10:27:35 -0800 Subject: [PATCH 12/13] Fix clippy warnings --- .github/workflows/rust_ci.yml | 6 ++- .../libs/sdk-enclave/src/userboundkey/mod.rs | 15 +++---- rust/sdk/crates/libs/sdk-host/src/enclave.rs | 40 ++++++++++++++----- .../userboundkey/enclave/src/edl_impls.rs | 14 +++---- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/.github/workflows/rust_ci.yml b/.github/workflows/rust_ci.yml index e79fa3cd..237061e3 100644 --- a/.github/workflows/rust_ci.yml +++ b/.github/workflows/rust_ci.yml @@ -97,4 +97,8 @@ jobs: - name: test SDK working-directory: ./rust/sdk - run: cargo test --workspace + run: cargo test + --workspace + --exclude vbsenclave-sdk-enclave + --exclude windows-enclave + --exclude sdk_enclave_gen diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs index afc1ee96..8bdb1723 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs @@ -266,6 +266,7 @@ pub fn create_user_bound_key( } /// Create a user-bound key with custom key bytes +#[allow(clippy::too_many_arguments)] pub fn create_user_bound_key_with_custom_key( key_name: &U16Str, cache_config: &keyCredentialCacheConfig, @@ -282,7 +283,7 @@ pub fn create_user_bound_key_with_custom_key( wchars: key_name.as_slice().to_vec(), }; let formatted_key_name_wstring = untrusted::userboundkey_format_key_name(&key_name_wstring) - .map_err(|e| UserBoundKeyError::AbiError(e))?; + .map_err(UserBoundKeyError::AbiError)?; // Security validation (wide string version) let formatted_key_name_u16str = U16Str::from_slice(&formatted_key_name_wstring.wchars); @@ -307,7 +308,7 @@ pub fn create_user_bound_key_with_custom_key( cache_config, key_credential_creation_option, ) - .map_err(|e| UserBoundKeyError::AbiError(e))?; + .map_err(UserBoundKeyError::AbiError)?; // RAII wrappers for cleanup let credential = UniqueCredential::new(credential_and_session.credential); @@ -353,7 +354,7 @@ pub fn create_user_bound_key_with_custom_key( &message_wstring, window_id, ) - .map_err(|e| UserBoundKeyError::AbiError(e))?; + .map_err(UserBoundKeyError::AbiError)?; // Get auth context handle let mut auth_context_handle: USER_BOUND_KEY_AUTH_CONTEXT_HANDLE = core::ptr::null_mut(); @@ -455,7 +456,7 @@ pub fn load_user_bound_key( wchars: key_name.as_slice().to_vec(), }; let formatted_key_name_wstring = untrusted::userboundkey_format_key_name(&key_name_wstring) - .map_err(|e| UserBoundKeyError::AbiError(e))?; + .map_err(UserBoundKeyError::AbiError)?; // Security validation (wide string version) let formatted_key_name_u16str = U16Str::from_slice(&formatted_key_name_wstring.wchars); @@ -476,7 +477,7 @@ pub fn load_user_bound_key( &message_wstring, window_id, ) - .map_err(|e| UserBoundKeyError::AbiError(e))?; + .map_err(UserBoundKeyError::AbiError)?; // RAII wrappers let credential = UniqueCredential::new(credential_and_session.credential); @@ -521,7 +522,7 @@ pub fn load_user_bound_key( &message_wstring, window_id, ) - .map_err(|e| UserBoundKeyError::AbiError(e))?; + .map_err(UserBoundKeyError::AbiError)?; // Get auth context handle let mut auth_context_handle: USER_BOUND_KEY_AUTH_CONTEXT_HANDLE = core::ptr::null_mut(); @@ -589,7 +590,7 @@ pub fn load_user_bound_key( &message_wstring, window_id, ) - .map_err(|e| UserBoundKeyError::AbiError(e))?; + .map_err(UserBoundKeyError::AbiError)?; // Unprotect the user key let mut user_key_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); diff --git a/rust/sdk/crates/libs/sdk-host/src/enclave.rs b/rust/sdk/crates/libs/sdk-host/src/enclave.rs index 2eb8a289..cda91db9 100644 --- a/rust/sdk/crates/libs/sdk-host/src/enclave.rs +++ b/rust/sdk/crates/libs/sdk-host/src/enclave.rs @@ -192,10 +192,14 @@ pub fn create( /// * `enclave` - Pointer to the enclave. /// * `image_path` - Path to the enclave DLL. /// +/// # Safety +/// +/// The caller must ensure `enclave` is a valid pointer returned by `create()`. +/// /// # Errors /// /// Returns an error if loading fails. -pub fn load_image(enclave: *mut c_void, image_path: &Path) -> Result<()> { +pub unsafe fn load_image(enclave: *mut c_void, image_path: &Path) -> Result<()> { let path_str = image_path.to_str().ok_or_else(|| { Error::new( HRESULT(-2147024809i32), // E_INVALIDARG @@ -229,10 +233,14 @@ pub fn load_image(enclave: *mut c_void, image_path: &Path) -> Result<()> { /// * `enclave` - Pointer to the enclave. /// * `thread_count` - Number of threads to create (usually 1). /// +/// # Safety +/// +/// The caller must ensure `enclave` is a valid pointer returned by `create()`. +/// /// # Errors /// /// Returns an error if initialization fails. -pub fn initialize(enclave: *mut c_void, thread_count: u32) -> Result<()> { +pub unsafe fn initialize(enclave: *mut c_void, thread_count: u32) -> Result<()> { let init_info = EnclaveInitInfoVbs { length: core::mem::size_of::() as u32, thread_count, @@ -267,7 +275,11 @@ pub fn initialize(enclave: *mut c_void, thread_count: u32) -> Result<()> { /// /// * `enclave` - Pointer to the enclave. /// * `wait` - If true, wait for all threads to terminate. -pub fn terminate(enclave: *mut c_void, wait: bool) { +/// +/// # Safety +/// +/// The caller must ensure `enclave` is a valid pointer returned by `create()`. +pub unsafe fn terminate(enclave: *mut c_void, wait: bool) { unsafe { let _ = ffi::TerminateEnclave(enclave, if wait { 1 } else { 0 }); } @@ -278,7 +290,11 @@ pub fn terminate(enclave: *mut c_void, wait: bool) { /// # Arguments /// /// * `enclave` - Pointer to the enclave. -pub fn delete(enclave: *mut c_void) { +/// +/// # Safety +/// +/// The caller must ensure `enclave` is a valid pointer returned by `create()`. +pub unsafe fn delete(enclave: *mut c_void) { unsafe { let _ = ffi::DeleteEnclave(enclave); } @@ -361,10 +377,12 @@ impl EnclaveHandle { let handle = unsafe { Self::from_raw(enclave_ptr) }; // Load the enclave image - load_image(enclave_ptr, dll_path)?; + // SAFETY: enclave_ptr was just returned by create() and is valid + unsafe { load_image(enclave_ptr, dll_path)? }; // Initialize with specified thread count - initialize(enclave_ptr, thread_count)?; + // SAFETY: enclave_ptr is valid and image has been loaded + unsafe { initialize(enclave_ptr, thread_count)? }; Ok(handle) } @@ -388,9 +406,13 @@ impl EnclaveHandle { impl Drop for EnclaveHandle { fn drop(&mut self) { if !self.enclave.is_null() { - // fWait = TRUE means wait for all threads to terminate - terminate(self.enclave, true); - delete(self.enclave); + // SAFETY: self.enclave is valid because it came from create() + // and we only set it to null after this point + unsafe { + // fWait = TRUE means wait for all threads to terminate + terminate(self.enclave, true); + delete(self.enclave); + } } } } diff --git a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs index fb9031c7..af24869e 100644 --- a/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs +++ b/rust/sdk/crates/samples/userboundkey/enclave/src/edl_impls.rs @@ -143,18 +143,18 @@ impl Trusted for EnclaveImpl { windowId: u64, keyCredentialCreationOption: u32, ) -> Result, AbiError> { - let _ = debug_print(&String::from("CreateUserBoundKey: Starting")); + debug_print(&String::from("CreateUserBoundKey: Starting"))?; let cache_config = create_secure_cache_config(); - let _ = debug_print(&format!( + debug_print(&format!( "CreateUserBoundKey: windowId={}, option={}", windowId, keyCredentialCreationOption - )); + ))?; - let _ = debug_print(&String::from( + debug_print(&String::from( "CreateUserBoundKey: Calling SDK create_user_bound_key...", - )); + ))?; // Create user-bound key with enclave sealing let sealed_key = create_user_bound_key( @@ -171,10 +171,10 @@ impl Trusted for EnclaveImpl { AbiError::Hresult(e.to_hresult()) })?; - let _ = debug_print(&format!( + debug_print(&format!( "CreateUserBoundKey: Success! Got {} sealed bytes", sealed_key.len() - )); + ))?; // Return sealed key bytes - do NOT create symmetric key here // The key is created on load, not on create. From 7cd3521db6f2fb6e5d292a26d43c61252034e3e4 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Fri, 23 Jan 2026 11:41:06 -0800 Subject: [PATCH 13/13] Addressed PR comments Round 2 --- .../libs/sdk-enclave/src/common/crypto.rs | 69 ++- .../crates/libs/sdk-enclave/src/common/mod.rs | 3 + .../libs/sdk-enclave/src/common/sdk_impls.rs | 35 ++ .../libs/sdk-enclave/src/common/utils.rs | 93 ++- rust/sdk/crates/libs/sdk-enclave/src/lib.rs | 6 +- .../libs/sdk-enclave/src/userboundkey/mod.rs | 96 +-- .../sdk-enclave/src/userboundkey/types.rs | 19 +- .../crates/libs/sdk-host/src/common/mod.rs | 8 + .../libs/sdk-host/src/common/sdk_impls.rs | 91 +++ rust/sdk/crates/libs/sdk-host/src/enclave.rs | 28 +- rust/sdk/crates/libs/sdk-host/src/lib.rs | 21 +- .../libs/sdk-host/src/userboundkey/mod.rs | 547 +++++++++--------- .../sdk/crates/samples/userboundkey/README.md | 5 +- .../sdk/crates/samples/userboundkey/build.ps1 | 65 +++ .../samples/userboundkey/host/src/main.rs | 36 +- rust/sdk/generate_and_build_crates.ps1 | 90 +-- rust/sdk/generate_codegen_for_workspace.ps1 | 15 +- 17 files changed, 726 insertions(+), 501 deletions(-) create mode 100644 rust/sdk/crates/libs/sdk-enclave/src/common/sdk_impls.rs create mode 100644 rust/sdk/crates/libs/sdk-host/src/common/mod.rs create mode 100644 rust/sdk/crates/libs/sdk-host/src/common/sdk_impls.rs create mode 100644 rust/sdk/crates/samples/userboundkey/build.ps1 diff --git a/rust/sdk/crates/libs/sdk-enclave/src/common/crypto.rs b/rust/sdk/crates/libs/sdk-enclave/src/common/crypto.rs index 00ce6477..b6c486ae 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/common/crypto.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/common/crypto.rs @@ -10,11 +10,38 @@ extern crate alloc; -use alloc::{boxed::Box, format, vec::Vec}; +use alloc::vec::Vec; use core::ffi::c_void; +use core::fmt; use windows_enclave::bcrypt::*; use windows_enclave::vertdll::*; +/// Cryptographic operation errors +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CryptoError { + /// HRESULT failure from Windows API + Hresult(i32), + /// NTSTATUS failure from BCrypt/NT API + NtStatus(i32), + /// Authentication tag mismatch during decryption + AuthTagMismatch, + /// Data is too short for the expected format + DataTooShort, +} + +impl fmt::Display for CryptoError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CryptoError::Hresult(hr) => write!(f, "HRESULT error: 0x{:08x}", hr), + CryptoError::NtStatus(status) => write!(f, "NTSTATUS error: 0x{:08x}", status), + CryptoError::AuthTagMismatch => write!(f, "Authentication tag mismatch"), + CryptoError::DataTooShort => write!(f, "Data too short"), + } + } +} + +impl core::error::Error for CryptoError {} + /// Sealing identity policy for enclave data sealing #[derive(Debug, Clone, Copy)] pub enum EnclaveSealingIdentityPolicy { @@ -29,17 +56,17 @@ pub enum EnclaveSealingIdentityPolicy { // /// Check HRESULT and convert to error -pub fn check_hr(hr: i32) -> Result<(), Box> { +pub fn check_hr(hr: i32) -> Result<(), CryptoError> { if hr < 0 { - return Err(format!("HRESULT error: 0x{:08x}", hr).into()); + return Err(CryptoError::Hresult(hr)); } Ok(()) } /// Check NTSTATUS and convert to error -pub fn check_ntstatus(status: i32) -> Result<(), Box> { +pub fn check_ntstatus(status: i32) -> Result<(), CryptoError> { if status < 0 { - return Err(format!("NTSTATUS error: 0x{:08x}", status).into()); + return Err(CryptoError::NtStatus(status)); } Ok(()) } @@ -52,7 +79,7 @@ pub fn check_ntstatus(status: i32) -> Result<(), Box> { /// /// Uses BCryptGenRandom with BCRYPT_USE_SYSTEM_PREFERRED_RNG flag. /// This is safe to call from enclaves as it uses the enclave's entropy source. -pub fn generate_random(buffer: &mut [u8]) -> Result<(), Box> { +pub fn generate_random(buffer: &mut [u8]) -> Result<(), CryptoError> { unsafe { let status = BCryptGenRandom( core::ptr::null_mut(), // Use system RNG @@ -66,23 +93,21 @@ pub fn generate_random(buffer: &mut [u8]) -> Result<(), Box Result, Box> { +pub fn generate_random_bytes(count: usize) -> Result, CryptoError> { let mut buffer = alloc::vec![0u8; count]; generate_random(&mut buffer)?; Ok(buffer) } /// Generate a cryptographically secure random u64 -pub fn generate_random_u64() -> Result> { +pub fn generate_random_u64() -> Result { let mut buffer = [0u8; 8]; generate_random(&mut buffer)?; Ok(u64::from_le_bytes(buffer)) } /// Generate random bytes suitable for use as a symmetric key -pub fn generate_symmetric_key_bytes( - key_size: usize, -) -> Result, Box> { +pub fn generate_symmetric_key_bytes(key_size: usize) -> Result, CryptoError> { generate_random_bytes(key_size) } @@ -106,7 +131,7 @@ pub fn seal_data( data: &[u8], sealing_policy: EnclaveSealingIdentityPolicy, runtime_policy: u32, -) -> Result, Box> { +) -> Result, CryptoError> { unsafe { // First call to get required size let mut sealed_size: u32 = 0; @@ -149,7 +174,7 @@ pub fn seal_data( /// # Returns /// A tuple of (unsealed_data, unseal_flags). Check unseal_flags for stale key flags /// to determine if the sealing key has changed and the data should be resealed. -pub fn unseal_data(sealed_data: &[u8]) -> Result<(Vec, u32), Box> { +pub fn unseal_data(sealed_data: &[u8]) -> Result<(Vec, u32), CryptoError> { unsafe { // First call to get required size let mut unsealed_size: u32 = 0; @@ -211,7 +236,7 @@ pub struct SymmetricKeyHandle { impl SymmetricKeyHandle { /// Create a symmetric key from raw key bytes using AES-GCM - pub fn from_bytes(key_bytes: &[u8]) -> Result> { + pub fn from_bytes(key_bytes: &[u8]) -> Result { let mut handle: BCRYPT_KEY_HANDLE = core::ptr::null_mut(); unsafe { @@ -265,7 +290,7 @@ pub fn encrypt( plaintext: &[u8], nonce: &[u8], tag_size: usize, -) -> Result<(Vec, Vec), Box> { +) -> Result<(Vec, Vec), CryptoError> { let mut tag = alloc::vec![0u8; tag_size]; let mut nonce_copy = nonce.to_vec(); @@ -309,7 +334,7 @@ pub fn decrypt( ciphertext: &[u8], nonce: &[u8], tag: &[u8], -) -> Result, Box> { +) -> Result, CryptoError> { // AES-GCM: plaintext length equals ciphertext length let mut plaintext = alloc::vec![0u8; ciphertext.len()]; let mut result_size: u32 = 0; @@ -332,7 +357,7 @@ pub fn decrypt( // STATUS_AUTH_TAG_MISMATCH = 0xC000A002 if status == -1073700862i32 { - return Err("Authentication tag mismatch".into()); + return Err(CryptoError::AuthTagMismatch); } check_ntstatus(status)?; } @@ -355,7 +380,7 @@ pub fn encrypt_and_tag( plaintext: &[u8], nonce: &[u8], tag_size: usize, -) -> Result, Box> { +) -> Result, CryptoError> { let (ciphertext, tag) = encrypt(key, plaintext, nonce, tag_size)?; let mut combined = Vec::with_capacity(ciphertext.len() + tag_size); @@ -380,9 +405,9 @@ pub fn decrypt_and_untag( combined: &[u8], nonce: &[u8], tag_size: usize, -) -> Result, Box> { +) -> Result, CryptoError> { if combined.len() < tag_size { - return Err("Data too short for tag".into()); + return Err(CryptoError::DataTooShort); } let ciphertext_len = combined.len() - tag_size; @@ -400,7 +425,7 @@ pub fn encrypt_and_tag_zero_nonce( plaintext: &[u8], nonce_size: usize, tag_size: usize, -) -> Result, Box> { +) -> Result, CryptoError> { let zero_nonce = alloc::vec![0u8; nonce_size]; encrypt_and_tag(key, plaintext, &zero_nonce, tag_size) } @@ -413,7 +438,7 @@ pub fn decrypt_and_untag_zero_nonce( combined: &[u8], nonce_size: usize, tag_size: usize, -) -> Result, Box> { +) -> Result, CryptoError> { let zero_nonce = alloc::vec![0u8; nonce_size]; decrypt_and_untag(key, combined, &zero_nonce, tag_size) } diff --git a/rust/sdk/crates/libs/sdk-enclave/src/common/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/common/mod.rs index 1c15beba..804d25e1 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/common/mod.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/common/mod.rs @@ -7,6 +7,9 @@ //! different SDK modules for enclave operations. pub mod crypto; +pub mod sdk_impls; mod utils; +pub use crypto::CryptoError; +pub use sdk_impls::EnclaveImpl; pub use utils::*; diff --git a/rust/sdk/crates/libs/sdk-enclave/src/common/sdk_impls.rs b/rust/sdk/crates/libs/sdk-enclave/src/common/sdk_impls.rs new file mode 100644 index 00000000..0ea9319b --- /dev/null +++ b/rust/sdk/crates/libs/sdk-enclave/src/common/sdk_impls.rs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! SDK implementation of the EDL Trusted trait. +//! +//! This module provides the `EnclaveImpl` struct that implements the generated +//! `Trusted` trait by delegating to free functions in feature modules. +//! This pattern allows feature implementations to live in their own modules +//! while the trait impl acts as a thin wrapper. + +use alloc::vec::Vec; +use sdk_enclave_gen::AbiError; +use sdk_enclave_gen::implementation::trusted::Trusted; +use sdk_enclave_gen::implementation::types::attestationReportAndSessionInfo; + +use crate::userboundkey; + +/// SDK enclave implementation of the Trusted trait. +/// +/// This struct implements all EDL trusted functions by delegating to +/// free functions in the appropriate feature modules. +pub struct EnclaveImpl; + +#[allow(non_snake_case)] +impl Trusted for EnclaveImpl { + fn userboundkey_get_attestation_report( + challenge: &Vec, + ) -> Result { + userboundkey::userboundkey_get_attestation_report(challenge) + } + + fn userboundkey_close_session(sessionInfo: u64) -> Result<(), AbiError> { + userboundkey::userboundkey_close_session(sessionInfo) + } +} diff --git a/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs b/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs index c625e9fa..9f73b455 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/common/utils.rs @@ -6,7 +6,7 @@ //! This module provides general-purpose utilities for enclave operations //! that can be used across different SDK modules. -use core::sync::atomic::{AtomicPtr, Ordering}; +use core::mem; use spin::Once; use windows_enclave::vertdll::{ @@ -30,62 +30,49 @@ impl EnclaveUtilsError { } } -/// Get singleton for enclave information cache pointer -fn enclave_info_cache() -> &'static AtomicPtr { - static ENCLAVE_INFO_CACHE: Once> = Once::new(); - ENCLAVE_INFO_CACHE.call_once(|| AtomicPtr::new(core::ptr::null_mut())) -} - -/// Get enclave information, caching it on first access -/// -/// This function retrieves the enclave information from the system on first call -/// and caches it for subsequent calls, similar to `veil::vtl1::enclave_information()`. -pub fn get_enclave_information() -> Result { - let cache = enclave_info_cache(); - - // Check if already cached - let ptr = cache.load(Ordering::Acquire); - if !ptr.is_null() { - return Ok(unsafe { *ptr }); - } - - // Initialize enclave information - let mut info: ENCLAVE_INFORMATION = unsafe { core::mem::zeroed() }; +/// Wrapper for ENCLAVE_INFORMATION that implements Send + Sync. +/// SAFETY: The BaseAddress pointer is read-only after initialization +/// and points to enclave memory which is valid for the enclave's lifetime. +#[derive(Clone, Copy)] +struct SyncEnclaveInfo(ENCLAVE_INFORMATION); - let hr = unsafe { - EnclaveGetEnclaveInformation( - core::mem::size_of::() as u32, - &mut info, - ) - }; +// SAFETY: ENCLAVE_INFORMATION contains a *mut c_void (BaseAddress) but: +// 1. It's effectively read-only after initialization +// 2. It points to enclave memory mapped for the process lifetime +// 3. We only ever read from it, never write through it +unsafe impl Send for SyncEnclaveInfo {} +unsafe impl Sync for SyncEnclaveInfo {} - if hr < 0 { - return Err(EnclaveUtilsError::Hresult(hr)); - } +/// Cached enclave information (initialized once) +static ENCLAVE_INFO: Once> = Once::new(); - // Leak the box to get a 'static pointer - let boxed = alloc::boxed::Box::new(info); - let new_ptr = alloc::boxed::Box::into_raw(boxed); - - // Try to set the cache (compare-and-swap) - match cache.compare_exchange( - core::ptr::null_mut(), - new_ptr, - Ordering::Release, - Ordering::Acquire, - ) { - Ok(_) => { - // We successfully set the cache - Ok(unsafe { *new_ptr }) - } - Err(existing) => { - // Another thread beat us - free our allocation and use theirs - unsafe { - let _ = alloc::boxed::Box::from_raw(new_ptr); +/// Get enclave information, initializing and caching it on first access. +/// +/// - Thread-safe +/// - No heap allocation +/// - No atomic pointer management +/// - Error is cached to avoid repeated syscalls +pub fn get_enclave_information() -> Result { + ENCLAVE_INFO + .call_once(|| { + let mut info: ENCLAVE_INFORMATION = unsafe { mem::zeroed() }; + + let hr = unsafe { + EnclaveGetEnclaveInformation( + mem::size_of::() as u32, + &mut info, + ) + }; + + if hr < 0 { + return Err(EnclaveUtilsError::Hresult(hr)); } - Ok(unsafe { *existing }) - } - } + + Ok(SyncEnclaveInfo(info)) + }) + .as_ref() + .map(|s| s.0) + .map_err(|e| *e) } /// Get the base address of the enclave diff --git a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs index f897432b..a0a35327 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/lib.rs @@ -31,10 +31,10 @@ pub use sdk_enclave_gen; #[macro_export] macro_rules! export_sdk_enclave_functions { () => { - // Export SDK userboundkey enclave functions in a private module + // Export SDK enclave functions in a private module // to avoid naming conflicts with the application's own exports - mod __sdk_userboundkey_exports { - $crate::sdk_enclave_gen::export_enclave_functions!($crate::userboundkey::TrustedImpl); + mod __sdk_exports { + $crate::sdk_enclave_gen::export_enclave_functions!($crate::common::EnclaveImpl); } // Future SDK features will be added here as additional modules diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs index 8bdb1723..7410d020 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/mod.rs @@ -24,7 +24,6 @@ pub use widestring::U16Str; use alloc::vec::Vec; use sdk_enclave_gen::AbiError; -use sdk_enclave_gen::implementation::trusted::Trusted; use sdk_enclave_gen::implementation::types::attestationReportAndSessionInfo; use sdk_enclave_gen::stubs::untrusted; use widestring::U16String; @@ -654,59 +653,62 @@ pub fn reseal_user_bound_key( Ok(resealed_data) } -/// Implementation of the Trusted trait for EDL-generated code -pub struct TrustedImpl; - +/// Get attestation report for a challenge and initialize a user-bound key session. +/// +/// This is the trusted implementation called from the EDL interface. #[allow(non_snake_case)] -impl Trusted for TrustedImpl { - fn userboundkey_get_attestation_report( - challenge: &Vec, - ) -> Result { - let mut report_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); - let mut report_size: u32 = 0; - let mut session_handle: USER_BOUND_KEY_SESSION_HANDLE = core::ptr::null_mut(); - - let hr = unsafe { - InitializeUserBoundKeySession( - challenge.as_ptr() as *const core::ffi::c_void, - challenge.len() as u32, - &mut report_ptr, - &mut report_size, - &mut session_handle, - ) - }; - - if hr < 0 { - return Err(AbiError::Hresult(hr)); - } - - // Take ownership of the heap-allocated buffer - let report_buf = unsafe { HeapBuffer::from_raw(report_ptr, report_size) }; +#[allow(clippy::ptr_arg)] // Signature must match EDL-generated trait +pub fn userboundkey_get_attestation_report( + challenge: &Vec, +) -> Result { + let mut report_ptr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut report_size: u32 = 0; + let mut session_handle: USER_BOUND_KEY_SESSION_HANDLE = core::ptr::null_mut(); + + let hr = unsafe { + InitializeUserBoundKeySession( + challenge.as_ptr() as *const core::ffi::c_void, + challenge.len() as u32, + &mut report_ptr, + &mut report_size, + &mut session_handle, + ) + }; - // Defensive check - API contract guarantees valid output on S_OK - if report_buf.is_empty() { - return Err(AbiError::Hresult(-1)); - } + if hr < 0 { + return Err(AbiError::Hresult(hr)); + } - // Convert report to Vec (HeapBuffer will free on drop) - let report = report_buf.to_vec(); + // Take ownership of the heap-allocated buffer + let report_buf = unsafe { HeapBuffer::from_raw(report_ptr, report_size) }; - Ok(attestationReportAndSessionInfo { - report, - sessionInfo: session_handle as u64, - }) + // Defensive check - API contract guarantees valid output on S_OK + if report_buf.is_empty() { + return Err(AbiError::Hresult(-1)); } - fn userboundkey_close_session(sessionInfo: u64) -> Result<(), AbiError> { - if sessionInfo != 0 { - unsafe { - let session_handle = sessionInfo as USER_BOUND_KEY_SESSION_HANDLE; - let hr = CloseUserBoundKeySession(session_handle); - if hr < 0 { - return Err(AbiError::Hresult(hr)); - } + // Convert report to Vec (HeapBuffer will free on drop) + let report = report_buf.to_vec(); + + Ok(attestationReportAndSessionInfo { + report, + sessionInfo: session_handle as u64, + }) +} + +/// Close a user-bound key session and release associated resources. +/// +/// This is the trusted implementation called from the EDL interface. +#[allow(non_snake_case)] +pub fn userboundkey_close_session(sessionInfo: u64) -> Result<(), AbiError> { + if sessionInfo != 0 { + unsafe { + let session_handle = sessionInfo as USER_BOUND_KEY_SESSION_HANDLE; + let hr = CloseUserBoundKeySession(session_handle); + if hr < 0 { + return Err(AbiError::Hresult(hr)); } } - Ok(()) } + Ok(()) } diff --git a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs index 9174e721..6be9cfa7 100644 --- a/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs +++ b/rust/sdk/crates/libs/sdk-enclave/src/userboundkey/types.rs @@ -3,8 +3,8 @@ //! Type definitions for user-bound key operations -use crate::common::EnclaveUtilsError; -use alloc::{boxed::Box, string::String}; +use crate::common::{CryptoError, EnclaveUtilsError}; +use alloc::string::String; use sdk_enclave_gen::AbiError; use windows_enclave::vertdll::RtlNtStatusToDosError; @@ -45,9 +45,18 @@ impl From for UserBoundKeyError { } } -impl From> for UserBoundKeyError { - fn from(err: Box) -> Self { - UserBoundKeyError::CryptoError(alloc::format!("{}", err)) +impl From for UserBoundKeyError { + fn from(err: CryptoError) -> Self { + match err { + CryptoError::Hresult(hr) => UserBoundKeyError::Hresult(hr), + CryptoError::NtStatus(status) => UserBoundKeyError::NtStatus(status), + CryptoError::AuthTagMismatch => { + UserBoundKeyError::CryptoError(String::from("Authentication tag mismatch")) + } + CryptoError::DataTooShort => { + UserBoundKeyError::CryptoError(String::from("Data too short for tag")) + } + } } } diff --git a/rust/sdk/crates/libs/sdk-host/src/common/mod.rs b/rust/sdk/crates/libs/sdk-host/src/common/mod.rs new file mode 100644 index 00000000..1e4cd4e5 --- /dev/null +++ b/rust/sdk/crates/libs/sdk-host/src/common/mod.rs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Common utilities and SDK implementations for VTL0 host operations. + +pub mod sdk_impls; + +pub use sdk_impls::HostImpl; diff --git a/rust/sdk/crates/libs/sdk-host/src/common/sdk_impls.rs b/rust/sdk/crates/libs/sdk-host/src/common/sdk_impls.rs new file mode 100644 index 00000000..bdf108ac --- /dev/null +++ b/rust/sdk/crates/libs/sdk-host/src/common/sdk_impls.rs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! SDK implementation of the EDL Untrusted trait. +//! +//! This module provides the `HostImpl` struct that implements the generated +//! `Untrusted` trait by delegating to free functions in feature modules. +//! This pattern allows feature implementations to live in their own modules +//! while the trait impl acts as a thin wrapper. + +use sdk_host_gen::AbiError; +use sdk_host_gen::implementation::types::{ + credentialAndSessionInfo, edl::WString, keyCredentialCacheConfig, +}; +use sdk_host_gen::implementation::untrusted::Untrusted; + +use crate::userboundkey; + +/// SDK host implementation of the Untrusted trait. +/// +/// This struct implements all EDL untrusted functions by delegating to +/// free functions in the appropriate feature modules. +pub struct HostImpl; + +#[allow(non_snake_case)] +impl Untrusted for HostImpl { + fn userboundkey_establish_session_for_create( + enclave: u64, + keyName: &WString, + ecdhProtocol: u64, + message: &WString, + windowId: u64, + cacheConfig: &keyCredentialCacheConfig, + keyCredentialCreationOption: u32, + ) -> Result { + userboundkey::userboundkey_establish_session_for_create( + enclave, + keyName, + ecdhProtocol, + message, + windowId, + cacheConfig, + keyCredentialCreationOption, + ) + } + + fn userboundkey_establish_session_for_load( + enclave: u64, + keyName: &WString, + message: &WString, + windowId: u64, + ) -> Result { + userboundkey::userboundkey_establish_session_for_load(enclave, keyName, message, windowId) + } + + fn userboundkey_get_authorization_context_from_credential( + credential: u64, + encryptedRequest: &Vec, + message: &WString, + windowId: u64, + ) -> Result, AbiError> { + userboundkey::userboundkey_get_authorization_context_from_credential( + credential, + encryptedRequest, + message, + windowId, + ) + } + + fn userboundkey_get_secret_from_credential( + credential: u64, + encryptedRequest: &Vec, + message: &WString, + windowId: u64, + ) -> Result, AbiError> { + userboundkey::userboundkey_get_secret_from_credential( + credential, + encryptedRequest, + message, + windowId, + ) + } + + fn userboundkey_format_key_name(keyName: &WString) -> Result { + userboundkey::userboundkey_format_key_name(keyName) + } + + fn userboundkey_delete_credential(credential: u64) -> Result<(), AbiError> { + userboundkey::userboundkey_delete_credential(credential) + } +} diff --git a/rust/sdk/crates/libs/sdk-host/src/enclave.rs b/rust/sdk/crates/libs/sdk-host/src/enclave.rs index cda91db9..181dfd37 100644 --- a/rust/sdk/crates/libs/sdk-host/src/enclave.rs +++ b/rust/sdk/crates/libs/sdk-host/src/enclave.rs @@ -10,11 +10,10 @@ //! //! ```no_run //! use vbsenclave_sdk_host::enclave::{EnclaveHandle, megabytes}; -//! use std::path::Path; //! //! // Create and initialize an enclave //! let enclave = EnclaveHandle::create_and_initialize( -//! Path::new("my_enclave.dll"), +//! "my_enclave.dll", //! megabytes(32), //! None, // Use default owner ID //! )?; @@ -25,7 +24,6 @@ //! ``` use std::ffi::c_void; -use std::path::Path; use widestring::U16CString; use windows::core::{Error, HRESULT, Result}; @@ -190,7 +188,7 @@ pub fn create( /// # Arguments /// /// * `enclave` - Pointer to the enclave. -/// * `image_path` - Path to the enclave DLL. +/// * `image_path` - Path to the enclave DLL (filename or full path). /// /// # Safety /// @@ -199,14 +197,8 @@ pub fn create( /// # Errors /// /// Returns an error if loading fails. -pub unsafe fn load_image(enclave: *mut c_void, image_path: &Path) -> Result<()> { - let path_str = image_path.to_str().ok_or_else(|| { - Error::new( - HRESULT(-2147024809i32), // E_INVALIDARG - "Invalid path: not valid UTF-8", - ) - })?; - let wide_path = U16CString::from_str(path_str).map_err(|_| { +pub unsafe fn load_image(enclave: *mut c_void, image_path: &str) -> Result<()> { + let wide_path = U16CString::from_str(image_path).map_err(|_| { Error::new( HRESULT(-2147024809i32), // E_INVALIDARG "Invalid path: contains interior null", @@ -327,7 +319,8 @@ impl EnclaveHandle { /// /// # Arguments /// - /// * `dll_path` - Path to the enclave DLL. + /// * `dll_path` - Path to the enclave DLL (filename or full path). + /// If just a filename, `LoadEnclaveImageW` will search the current directory and PATH. /// * `size` - Size of the enclave in bytes. Use [`megabytes`] helper. /// * `owner_id` - Optional owner ID bytes. If None, uses zeros. /// @@ -335,17 +328,16 @@ impl EnclaveHandle { /// /// ```no_run /// use vbsenclave_sdk_host::enclave::{EnclaveHandle, megabytes}; - /// use std::path::Path; /// /// let enclave = EnclaveHandle::create_and_initialize( - /// Path::new("my_enclave.dll"), + /// "my_enclave.dll", /// megabytes(32), /// None, /// )?; /// # Ok::<(), windows::core::Error>(()) /// ``` pub fn create_and_initialize( - dll_path: &Path, + dll_path: &str, size: usize, owner_id: Option<&[u8]>, ) -> Result { @@ -360,12 +352,12 @@ impl EnclaveHandle { /// /// # Arguments /// - /// * `dll_path` - Path to the enclave DLL. + /// * `dll_path` - Path to the enclave DLL (filename or full path). /// * `size` - Size of the enclave in bytes. Use [`megabytes`] helper. /// * `owner_id` - Optional owner ID bytes. If None, uses zeros. /// * `thread_count` - Number of threads to initialize. Use at least 2 for callback support. pub fn create_and_initialize_with_threads( - dll_path: &Path, + dll_path: &str, size: usize, owner_id: Option<&[u8]>, thread_count: u32, diff --git a/rust/sdk/crates/libs/sdk-host/src/lib.rs b/rust/sdk/crates/libs/sdk-host/src/lib.rs index 837c16f0..62760fbf 100644 --- a/rust/sdk/crates/libs/sdk-host/src/lib.rs +++ b/rust/sdk/crates/libs/sdk-host/src/lib.rs @@ -3,10 +3,16 @@ mod host_ffi; +pub mod common; pub mod enclave; - pub mod userboundkey; +// Re-export AbiError for consumers who call register_sdk_callbacks +pub use sdk_host_gen::AbiError; + +// Re-export HostImpl for consumers +pub use common::HostImpl; + // The userboundkey-kcm crate is not intended to be published as a standalone package. // Long term, these KCM APIs should be consumed from the windows-rs crate. // Until those APIs are available there, the sdk-host crate re-exports the required @@ -36,18 +42,13 @@ pub use userboundkey_kcm::KeyCredentialManager; /// let enclave = EnclaveHandle::create_and_initialize(path, size, owner_id)?; /// register_sdk_callbacks(enclave.as_ptr())?; /// ``` -pub fn register_sdk_callbacks( - enclave_ptr: *mut core::ffi::c_void, -) -> Result<(), Box> { - // Register userboundkey callbacks - let sdk_wrapper = userboundkey::UserBoundKeyVtl0Host::new(enclave_ptr); - sdk_wrapper - .register_vtl0_callbacks::() - .map_err(|e| format!("Failed to register SDK userboundkey callbacks: {:?}", e))?; +pub fn register_sdk_callbacks(enclave_ptr: *mut core::ffi::c_void) -> Result<(), AbiError> { + // Register SDK callbacks using the SdkHost interface and HostImpl + let sdk_wrapper = userboundkey::SdkHost::new(enclave_ptr); + sdk_wrapper.register_vtl0_callbacks::()?; // Future SDK features will be added here: // #[cfg(feature = "secure_storage")] - // { ... register secure_storage callbacks ... } Ok(()) } diff --git a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs index 375556df..a7a5d565 100644 --- a/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs +++ b/rust/sdk/crates/libs/sdk-host/src/userboundkey/mod.rs @@ -7,12 +7,11 @@ //! This module provides VTL0/host-side Windows Hello integration with VBS enclave support. use sdk_host_gen::AbiError; -pub use sdk_host_gen::UserBoundKeyVtl0Host; +pub use sdk_host_gen::SdkHost; pub use sdk_host_gen::implementation::types::{ attestationReportAndSessionInfo, credentialAndSessionInfo, edl::WString, keyCredentialCacheConfig, }; -use sdk_host_gen::implementation::untrusted::Untrusted; use std::sync::{Arc, Mutex}; @@ -71,8 +70,7 @@ impl UniqueSessionHandle { fn reset(&mut self) { if self.handle != 0 && self.enclave_ptr != 0 { // Call back to VTL1 to close session - let enclave_interface = - UserBoundKeyVtl0Host::new(self.enclave_ptr as *mut core::ffi::c_void); + let enclave_interface = SdkHost::new(self.enclave_ptr as *mut core::ffi::c_void); let _ = enclave_interface.userboundkey_close_session(self.handle as u64); } self.handle = 0; @@ -168,7 +166,7 @@ fn create_challenge_callback( ); // Call into VTL1 to get attestation report - let enclave_interface = UserBoundKeyVtl0Host::new(enclave_ptr as *mut core::ffi::c_void); + let enclave_interface = SdkHost::new(enclave_ptr as *mut core::ffi::c_void); let attestation_result = enclave_interface .userboundkey_get_attestation_report(&challenge_vec) .map_err(|e| { @@ -220,306 +218,317 @@ fn sid_to_wide_string(sid: PSID) -> Result, AbiError> { } } -/// Untrusted implementation struct -pub struct UntrustedImpl; - /// Helper to convert WString to HSTRING fn wstring_to_hstring(ws: &WString) -> HSTRING { HSTRING::from_wide(&ws.wchars) } +/// Establish a session for creating a new Windows Hello key. #[allow(non_snake_case)] #[allow(unused_variables)] -impl Untrusted for UntrustedImpl { - fn userboundkey_establish_session_for_create( - enclave: u64, - keyName: &WString, - ecdhProtocol: u64, - message: &WString, - windowId: u64, - cacheConfig: &keyCredentialCacheConfig, - keyCredentialCreationOption: u32, - ) -> Result { - println!("[SDK-Host] userboundkey_establish_session_for_create called"); - println!( - "[SDK-Host] windowId={}, option={}", - windowId, keyCredentialCreationOption - ); - - let algorithm = get_algorithm(ecdhProtocol)?; - println!("[SDK-Host] algorithm resolved"); - - let cache_configuration = - convert_cache_config(cacheConfig).map_err(|e| AbiError::Hresult(e.code().0))?; - println!("[SDK-Host] cache config created"); - - let session_info = Arc::new(Mutex::new(UniqueSessionHandle::new())); - let enclave_ptr = enclave as usize; - - // Try to delete existing key first (ignore errors) - let key_name_hstring = wstring_to_hstring(keyName); - println!("[SDK-Host] Deleting existing key (if any)..."); - if let Ok(delete_op) = KeyCredentialManager::DeleteAsync(&key_name_hstring) { - let _ = delete_op.join(); - } - println!("[SDK-Host] Delete complete"); - - // Create the credential with VBS attestation - let message_hstring = wstring_to_hstring(message); - let win_id = WindowId { Value: windowId }; - let creation_option = KeyCredentialCreationOption(keyCredentialCreationOption as i32); - - let callback = create_challenge_callback(session_info.clone(), enclave_ptr); - - println!("[SDK-Host] Calling KeyCredentialManager::RequestCreateAsync2..."); - - let credential_result = KeyCredentialManager::RequestCreateAsync2( - &key_name_hstring, - creation_option, - &algorithm, - &message_hstring, - &cache_configuration, - win_id, - ChallengeResponseKind::VirtualizationBasedSecurityEnclave, - &callback, - ) - .map_err(|e| { - println!("[SDK-Host] RequestCreateAsync2 failed to start: {:?}", e); - AbiError::Hresult(e.code().0) - })?; - - println!("[SDK-Host] RequestCreateAsync2 started, waiting for completion (join)..."); - - let credential_result = credential_result.join().map_err(|e| { - println!("[SDK-Host] RequestCreateAsync2.join() failed: {:?}", e); - AbiError::Hresult(e.code().0) - })?; - - println!("[SDK-Host] RequestCreateAsync2 completed!"); - - // Check if operation was successful - let status = credential_result - .Status() - .map_err(|e| AbiError::Hresult(e.code().0))?; - if status != KeyCredentialStatus::Success { - return Err(AbiError::Hresult(status.0)); - } - - // Get credential and convert to raw pointer - let credential = credential_result - .Credential() - .map_err(|e| AbiError::Hresult(e.code().0))?; - let credential_ptr = windows_core::Interface::as_raw(&credential) as u64; +pub fn userboundkey_establish_session_for_create( + enclave: u64, + keyName: &WString, + ecdhProtocol: u64, + message: &WString, + windowId: u64, + cacheConfig: &keyCredentialCacheConfig, + keyCredentialCreationOption: u32, +) -> Result { + println!("[SDK-Host] userboundkey_establish_session_for_create called"); + println!( + "[SDK-Host] windowId={}, option={}", + windowId, keyCredentialCreationOption + ); + + let algorithm = get_algorithm(ecdhProtocol)?; + println!("[SDK-Host] algorithm resolved"); + + let cache_configuration = + convert_cache_config(cacheConfig).map_err(|e| AbiError::Hresult(e.code().0))?; + println!("[SDK-Host] cache config created"); + + let session_info = Arc::new(Mutex::new(UniqueSessionHandle::new())); + let enclave_ptr = enclave as usize; + + // Try to delete existing key first (ignore errors) + let key_name_hstring = wstring_to_hstring(keyName); + println!("[SDK-Host] Deleting existing key (if any)..."); + if let Ok(delete_op) = KeyCredentialManager::DeleteAsync(&key_name_hstring) { + let _ = delete_op.join(); + } + println!("[SDK-Host] Delete complete"); + + // Create the credential with VBS attestation + let message_hstring = wstring_to_hstring(message); + let win_id = WindowId { Value: windowId }; + let creation_option = KeyCredentialCreationOption(keyCredentialCreationOption as i32); + + let callback = create_challenge_callback(session_info.clone(), enclave_ptr); + + println!("[SDK-Host] Calling KeyCredentialManager::RequestCreateAsync2..."); + + let credential_result = KeyCredentialManager::RequestCreateAsync2( + &key_name_hstring, + creation_option, + &algorithm, + &message_hstring, + &cache_configuration, + win_id, + ChallengeResponseKind::VirtualizationBasedSecurityEnclave, + &callback, + ) + .map_err(|e| { + println!("[SDK-Host] RequestCreateAsync2 failed to start: {:?}", e); + AbiError::Hresult(e.code().0) + })?; + + println!("[SDK-Host] RequestCreateAsync2 started, waiting for completion (join)..."); + + let credential_result = credential_result.join().map_err(|e| { + println!("[SDK-Host] RequestCreateAsync2.join() failed: {:?}", e); + AbiError::Hresult(e.code().0) + })?; + + println!("[SDK-Host] RequestCreateAsync2 completed!"); + + // Check if operation was successful + let status = credential_result + .Status() + .map_err(|e| AbiError::Hresult(e.code().0))?; + if status != KeyCredentialStatus::Success { + return Err(AbiError::Hresult(status.0)); + } - // Transfer session ownership - let session_handle = { - let mut session = match session_info.lock() { - Ok(guard) => guard, - Err(poisoned) => { - println!("[SDK-Host] Warning: session_info mutex was poisoned"); - poisoned.into_inner() - } - }; - session.release() as u64 + // Get credential and convert to raw pointer + let credential = credential_result + .Credential() + .map_err(|e| AbiError::Hresult(e.code().0))?; + let credential_ptr = windows_core::Interface::as_raw(&credential) as u64; + + // Transfer session ownership + let session_handle = { + let mut session = match session_info.lock() { + Ok(guard) => guard, + Err(poisoned) => { + println!("[SDK-Host] Warning: session_info mutex was poisoned"); + poisoned.into_inner() + } }; + session.release() as u64 + }; - // Add reference to credential since we're transferring ownership - // We need to prevent the credential from being released - std::mem::forget(credential); + // Add reference to credential since we're transferring ownership + // We need to prevent the credential from being released + std::mem::forget(credential); - Ok(credentialAndSessionInfo { - credential: credential_ptr, - sessionInfo: session_handle, - }) - } + Ok(credentialAndSessionInfo { + credential: credential_ptr, + sessionInfo: session_handle, + }) +} - fn userboundkey_establish_session_for_load( - enclave: u64, - keyName: &WString, - message: &WString, - windowId: u64, - ) -> Result { - let _ = (message, windowId); // Mark as intentionally unused - let session_info = Arc::new(Mutex::new(UniqueSessionHandle::new())); - let enclave_ptr = enclave as usize; - - let key_name_hstring = wstring_to_hstring(keyName); - let callback = create_challenge_callback(session_info.clone(), enclave_ptr); - - let credential_result = KeyCredentialManager::OpenAsync2( - &key_name_hstring, - ChallengeResponseKind::VirtualizationBasedSecurityEnclave, - &callback, - ) - .map_err(|e| AbiError::Hresult(e.code().0))? - .join() +/// Establish a session for loading an existing Windows Hello key. +#[allow(non_snake_case)] +#[allow(unused_variables)] +pub fn userboundkey_establish_session_for_load( + enclave: u64, + keyName: &WString, + message: &WString, + windowId: u64, +) -> Result { + let _ = (message, windowId); // Mark as intentionally unused + let session_info = Arc::new(Mutex::new(UniqueSessionHandle::new())); + let enclave_ptr = enclave as usize; + + let key_name_hstring = wstring_to_hstring(keyName); + let callback = create_challenge_callback(session_info.clone(), enclave_ptr); + + let credential_result = KeyCredentialManager::OpenAsync2( + &key_name_hstring, + ChallengeResponseKind::VirtualizationBasedSecurityEnclave, + &callback, + ) + .map_err(|e| AbiError::Hresult(e.code().0))? + .join() + .map_err(|e| AbiError::Hresult(e.code().0))?; + + // Check if operation was successful + let status = credential_result + .Status() .map_err(|e| AbiError::Hresult(e.code().0))?; + if status != KeyCredentialStatus::Success { + return Err(AbiError::Hresult(status.0)); + } - // Check if operation was successful - let status = credential_result - .Status() - .map_err(|e| AbiError::Hresult(e.code().0))?; - if status != KeyCredentialStatus::Success { - return Err(AbiError::Hresult(status.0)); - } - - // Get credential and convert to raw pointer - let credential = credential_result - .Credential() - .map_err(|e| AbiError::Hresult(e.code().0))?; - let credential_ptr = windows_core::Interface::as_raw(&credential) as u64; - - // Transfer session ownership - let session_handle = { - let mut session = match session_info.lock() { - Ok(guard) => guard, - Err(poisoned) => { - println!("[SDK-Host] Warning: session_info mutex was poisoned"); - poisoned.into_inner() - } - }; - session.release() as u64 + // Get credential and convert to raw pointer + let credential = credential_result + .Credential() + .map_err(|e| AbiError::Hresult(e.code().0))?; + let credential_ptr = windows_core::Interface::as_raw(&credential) as u64; + + // Transfer session ownership + let session_handle = { + let mut session = match session_info.lock() { + Ok(guard) => guard, + Err(poisoned) => { + println!("[SDK-Host] Warning: session_info mutex was poisoned"); + poisoned.into_inner() + } }; + session.release() as u64 + }; - // Prevent credential from being released (transfer ownership) - std::mem::forget(credential); + // Prevent credential from being released (transfer ownership) + std::mem::forget(credential); - Ok(credentialAndSessionInfo { - credential: credential_ptr, - sessionInfo: session_handle, - }) - } + Ok(credentialAndSessionInfo { + credential: credential_ptr, + sessionInfo: session_handle, + }) +} - fn userboundkey_get_authorization_context_from_credential( - credential: u64, - encryptedRequest: &Vec, - message: &WString, - windowId: u64, - ) -> Result, AbiError> { - // Reconstruct KeyCredential from raw pointer - // We use from_raw and then manually AddRef since the caller owns the credential - let key_credential: KeyCredential = unsafe { - let raw_ptr = credential as *mut core::ffi::c_void; - if raw_ptr.is_null() { - return Err(AbiError::Hresult(E_INVALIDARG.0)); - } - // Clone increases ref count - the original is still owned by caller - let temp = KeyCredential::from_raw(raw_ptr); - let cloned = temp.clone(); - // Forget temp to not decrement refcount - core::mem::forget(temp); - cloned - }; +/// Get authorization context from a credential. +#[allow(non_snake_case)] +#[allow(unused_variables)] +#[allow(clippy::ptr_arg)] // Signature must match EDL-generated trait +pub fn userboundkey_get_authorization_context_from_credential( + credential: u64, + encryptedRequest: &Vec, + message: &WString, + windowId: u64, +) -> Result, AbiError> { + // Reconstruct KeyCredential from raw pointer + // We use from_raw and then manually AddRef since the caller owns the credential + let key_credential: KeyCredential = unsafe { + let raw_ptr = credential as *mut core::ffi::c_void; + if raw_ptr.is_null() { + return Err(AbiError::Hresult(E_INVALIDARG.0)); + } + // Clone increases ref count - the original is still owned by caller + let temp = KeyCredential::from_raw(raw_ptr); + let cloned = temp.clone(); + // Forget temp to not decrement refcount + core::mem::forget(temp); + cloned + }; - // Convert encrypted request to IBuffer - let encrypted_buffer = - vec_to_kcm_buffer(encryptedRequest).map_err(|e| AbiError::Hresult(e.code().0))?; + // Convert encrypted request to IBuffer + let encrypted_buffer = + vec_to_kcm_buffer(encryptedRequest).map_err(|e| AbiError::Hresult(e.code().0))?; - // Call RetrieveAuthorizationContext on the credential - let auth_context = key_credential - .RetrieveAuthorizationContext(&encrypted_buffer) - .map_err(|e| AbiError::Hresult(e.code().0))?; + // Call RetrieveAuthorizationContext on the credential + let auth_context = key_credential + .RetrieveAuthorizationContext(&encrypted_buffer) + .map_err(|e| AbiError::Hresult(e.code().0))?; - kcm_buffer_to_vec(&auth_context).map_err(|e| AbiError::Hresult(e.code().0)) - } + kcm_buffer_to_vec(&auth_context).map_err(|e| AbiError::Hresult(e.code().0)) +} - fn userboundkey_get_secret_from_credential( - credential: u64, - encryptedRequest: &Vec, - message: &WString, - windowId: u64, - ) -> Result, AbiError> { - // Reconstruct KeyCredential from raw pointer - // We use from_raw and then manually AddRef since the caller owns the credential - let key_credential: KeyCredential = unsafe { - let raw_ptr = credential as *mut core::ffi::c_void; - if raw_ptr.is_null() { - return Err(AbiError::Hresult(E_INVALIDARG.0)); - } - // Clone increases ref count - the original is still owned by caller - let temp = KeyCredential::from_raw(raw_ptr); - let cloned = temp.clone(); - // Forget temp to not decrement refcount - core::mem::forget(temp); - cloned - }; +/// Get shared secret from a credential. +#[allow(non_snake_case)] +#[allow(unused_variables)] +#[allow(clippy::ptr_arg)] // Signature must match EDL-generated trait +pub fn userboundkey_get_secret_from_credential( + credential: u64, + encryptedRequest: &Vec, + message: &WString, + windowId: u64, +) -> Result, AbiError> { + // Reconstruct KeyCredential from raw pointer + // We use from_raw and then manually AddRef since the caller owns the credential + let key_credential: KeyCredential = unsafe { + let raw_ptr = credential as *mut core::ffi::c_void; + if raw_ptr.is_null() { + return Err(AbiError::Hresult(E_INVALIDARG.0)); + } + // Clone increases ref count - the original is still owned by caller + let temp = KeyCredential::from_raw(raw_ptr); + let cloned = temp.clone(); + // Forget temp to not decrement refcount + core::mem::forget(temp); + cloned + }; - // Convert encrypted request to IBuffer - let encrypted_buffer = - vec_to_kcm_buffer(encryptedRequest).map_err(|e| AbiError::Hresult(e.code().0))?; + // Convert encrypted request to IBuffer + let encrypted_buffer = + vec_to_kcm_buffer(encryptedRequest).map_err(|e| AbiError::Hresult(e.code().0))?; - let win_id = WindowId { Value: windowId }; - let message_hstring = wstring_to_hstring(message); + let win_id = WindowId { Value: windowId }; + let message_hstring = wstring_to_hstring(message); - // Call RequestDeriveSharedSecretAsync on the credential - let operation_result = key_credential - .RequestDeriveSharedSecretAsync(win_id, &message_hstring, &encrypted_buffer) - .map_err(|e| AbiError::Hresult(e.code().0))? - .join() - .map_err(|e| AbiError::Hresult(e.code().0))?; + // Call RequestDeriveSharedSecretAsync on the credential + let operation_result = key_credential + .RequestDeriveSharedSecretAsync(win_id, &message_hstring, &encrypted_buffer) + .map_err(|e| AbiError::Hresult(e.code().0))? + .join() + .map_err(|e| AbiError::Hresult(e.code().0))?; - let result_buffer = operation_result - .Result() - .map_err(|e| AbiError::Hresult(e.code().0))?; + let result_buffer = operation_result + .Result() + .map_err(|e| AbiError::Hresult(e.code().0))?; - kcm_buffer_to_vec(&result_buffer).map_err(|e| AbiError::Hresult(e.code().0)) - } + kcm_buffer_to_vec(&result_buffer).map_err(|e| AbiError::Hresult(e.code().0)) +} - fn userboundkey_format_key_name(keyName: &WString) -> Result { - unsafe { - let mut process_token = HANDLE::default(); - OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut process_token) - .map_err(|_| AbiError::Hresult(E_FAIL.0))?; - - // Get token user info size - let mut return_length: u32 = 0; - let _ = GetTokenInformation(process_token, TokenUser, None, 0, &mut return_length); - - // Allocate buffer and get token user info - let mut buffer = vec![0u8; return_length as usize]; - GetTokenInformation( - process_token, - TokenUser, - Some(buffer.as_mut_ptr() as *mut _), - return_length, - &mut return_length, - ) +/// Format a key name with the current user's SID. +#[allow(non_snake_case)] +pub fn userboundkey_format_key_name(keyName: &WString) -> Result { + unsafe { + let mut process_token = HANDLE::default(); + OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut process_token) .map_err(|_| AbiError::Hresult(E_FAIL.0))?; - let _ = CloseHandle(process_token); + // Get token user info size + let mut return_length: u32 = 0; + let _ = GetTokenInformation(process_token, TokenUser, None, 0, &mut return_length); + + // Allocate buffer and get token user info + let mut buffer = vec![0u8; return_length as usize]; + GetTokenInformation( + process_token, + TokenUser, + Some(buffer.as_mut_ptr() as *mut _), + return_length, + &mut return_length, + ) + .map_err(|_| AbiError::Hresult(E_FAIL.0))?; + + let _ = CloseHandle(process_token); - let token_user = &*(buffer.as_ptr() as *const TOKEN_USER); - let user_sid = token_user.User.Sid; + let token_user = &*(buffer.as_ptr() as *const TOKEN_USER); + let user_sid = token_user.User.Sid; - // Convert SID to wide string - let sid_wide = sid_to_wide_string(user_sid)?; + // Convert SID to wide string + let sid_wide = sid_to_wide_string(user_sid)?; - // Build formatted key: {SID}//{SID}//{keyName} - let slash_slash = widestring::u16str!("//"); - let mut result = - Vec::with_capacity(sid_wide.len() + 2 + sid_wide.len() + 2 + keyName.wchars.len()); - result.extend_from_slice(&sid_wide); - result.extend_from_slice(slash_slash.as_slice()); - result.extend_from_slice(&sid_wide); - result.extend_from_slice(slash_slash.as_slice()); - result.extend_from_slice(&keyName.wchars); + // Build formatted key: {SID}//{SID}//{keyName} + let slash_slash = widestring::u16str!("//"); + let mut result = + Vec::with_capacity(sid_wide.len() + 2 + sid_wide.len() + 2 + keyName.wchars.len()); + result.extend_from_slice(&sid_wide); + result.extend_from_slice(slash_slash.as_slice()); + result.extend_from_slice(&sid_wide); + result.extend_from_slice(slash_slash.as_slice()); + result.extend_from_slice(&keyName.wchars); - Ok(WString { wchars: result }) - } + Ok(WString { wchars: result }) } +} - fn userboundkey_delete_credential(credential: u64) -> Result<(), AbiError> { - if credential == 0 { - return Ok(()); - } - - // Take ownership of the credential and let it drop - unsafe { - let raw_ptr = credential as *mut core::ffi::c_void; - let _ = KeyCredential::from_raw(raw_ptr); - // Credential will be released when dropped - } +/// Delete a credential handle. +#[allow(non_snake_case)] +pub fn userboundkey_delete_credential(credential: u64) -> Result<(), AbiError> { + if credential == 0 { + return Ok(()); + } - Ok(()) + // Take ownership of the credential and let it drop + unsafe { + let raw_ptr = credential as *mut core::ffi::c_void; + let _ = KeyCredential::from_raw(raw_ptr); + // Credential will be released when dropped } + + Ok(()) } diff --git a/rust/sdk/crates/samples/userboundkey/README.md b/rust/sdk/crates/samples/userboundkey/README.md index be3b1907..34cbc0a7 100644 --- a/rust/sdk/crates/samples/userboundkey/README.md +++ b/rust/sdk/crates/samples/userboundkey/README.md @@ -37,8 +37,9 @@ The sample consists of: ### Prerequisites Building the enclave DLL requires: -- Windows SDK with VBS enclave libraries (`ucrt_enclave`) -- SDK versions 10.0.19041.0, 10.0.22621.0, or 10.0.26100.0 are known to work +- **Windows 11 Build 26100.2314 or later** or **Windows Server 2025 or later** +- Windows SDK 10.0.26100.0 or later with VBS enclave libraries (`ucrt_enclave`) +- `veinterop.lib` from SDK 10.0.26100.7463 or later (for VBS enclave interop APIs) - Visual Studio 2022 with MSVC enclave toolchain - A code signing certificate for VBS enclaves (see below) diff --git a/rust/sdk/crates/samples/userboundkey/build.ps1 b/rust/sdk/crates/samples/userboundkey/build.ps1 new file mode 100644 index 00000000..f05e356b --- /dev/null +++ b/rust/sdk/crates/samples/userboundkey/build.ps1 @@ -0,0 +1,65 @@ +# Build script for the User-Bound Key sample. +# +# This script builds and optionally signs the userboundkey sample enclave. +# +# Usage: +# .\build.ps1 # Debug build only +# .\build.ps1 -Configuration release # Release build only +# .\build.ps1 -CertName "MyCert" # Debug build + sign +# .\build.ps1 -Clean # Clean and rebuild + +param( + [ValidateSet("debug", "release")] + [string]$Configuration = "debug", + + [switch]$Clean, + + [string]$CertName +) + +$errorActionPreference = "Stop" + +# Locate shared scripts folder +. "$PSScriptRoot\..\..\..\..\scripts\get_common_paths.ps1" + +Write-Host "`n=== User-Bound Key Sample Build ===" -ForegroundColor Cyan +Write-Host "Configuration: $Configuration" +if ($CertName) { + Write-Host "Certificate: $CertName (will sign enclave)" +} + +# Clean if requested +if ($Clean) { + Write-Host "`nCleaning build artifacts..." -ForegroundColor Yellow + cargo clean +} + +# Build the sample workspace +Write-Host "`nBuilding sample..." -ForegroundColor Yellow +. "$scriptsDir\invoke_cargo_build.ps1" -Path $PSScriptRoot -Configuration $Configuration + +# Sign enclave DLL if certificate is provided +if ($CertName) { + Write-Host "`nSigning enclave DLL..." -ForegroundColor Yellow + + $signScript = Join-Path $scriptsDir "sign-enclave.ps1" + $targetDir = if ($Configuration -eq "release") { "release" } else { "debug" } + $targetPath = Join-Path $PSScriptRoot "target\$targetDir" + + $enclaveDlls = Get-ChildItem -Path $targetPath -Filter "*_enclave.dll" -ErrorAction SilentlyContinue + + if (-not $enclaveDlls) { + Write-Host "No enclave DLLs found to sign." -ForegroundColor Yellow + } else { + foreach ($dll in $enclaveDlls) { + Write-Host "Signing $($dll.Name)..." -ForegroundColor Gray + & $signScript -DllPath $dll.FullName -CertName $CertName + if ($LASTEXITCODE -ne 0) { throw "Failed to sign $($dll.Name)" } + } + Write-Host "Enclave signed." -ForegroundColor Green + } +} + +Write-Host "`n=== Sample Build Complete ===" -ForegroundColor Cyan +$targetDir = if ($Configuration -eq "release") { "release" } else { "debug" } +Write-Host "Output: $(Join-Path $PSScriptRoot "target\$targetDir")" diff --git a/rust/sdk/crates/samples/userboundkey/host/src/main.rs b/rust/sdk/crates/samples/userboundkey/host/src/main.rs index 51a57228..8658b43b 100644 --- a/rust/sdk/crates/samples/userboundkey/host/src/main.rs +++ b/rust/sdk/crates/samples/userboundkey/host/src/main.rs @@ -11,7 +11,7 @@ mod edl_impls; use std::fs; use std::io::{self, Write}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; // VBS Enclave SDK for loading enclaves use vbsenclave_sdk_host::KeyCredentialManager; @@ -91,9 +91,9 @@ fn get_secure_id_from_windows_hello() -> Result, Box Result<(EnclaveHandle, UntrustedImpl), Box> { - println!("Loading enclave from: {}", enclave_path.display()); + println!("Loading enclave from: {}", enclave_path); // Get the secure ID from Windows Hello - required for user-bound key operations let owner_id = get_secure_id_from_windows_hello()?; @@ -121,7 +121,12 @@ fn load_enclave( // Register all SDK VTL0 callbacks so enclave can call back to host // This single function call handles all SDK features (userboundkey, etc.) - register_sdk_callbacks(enclave.as_ptr())?; + register_sdk_callbacks(enclave.as_ptr()).map_err(|e| { + format!( + "Failed to register SDK callbacks: 0x{:08X}", + e.to_hresult().0 + ) + })?; println!("Enclave loaded and callbacks registered successfully."); Ok((enclave, wrapper)) @@ -131,15 +136,8 @@ fn main() -> Result<(), Box> { println!("=== User-Bound Key Sample (Rust) ==="); println!(); - // Get enclave DLL path (should be in same directory as host executable) - let exe_dir = std::env::current_exe() - .ok() - .and_then(|p| p.parent().map(|p| p.to_path_buf())) - .unwrap_or_else(|| std::env::current_dir().unwrap()); - let enclave_path = exe_dir.join("userboundkey_sample_enclave.dll"); - - // Load the enclave - let (_enclave_handle, enclave) = load_enclave(&enclave_path)?; + // Load the enclave (LoadEnclaveImageW searches current directory and PATH) + let (_enclave_handle, enclave) = load_enclave("userboundkey_sample_enclave.dll")?; let key_file_path = get_key_file_path(); let encrypted_file_path = get_encrypted_file_path(); @@ -147,12 +145,12 @@ fn main() -> Result<(), Box> { let mut is_key_loaded = false; // Try to load existing key from file - if key_file_path.exists() { - if let Ok(data) = load_binary_data(&key_file_path) { - secured_key_bytes = data; - is_key_loaded = true; - println!("Found existing key file: {}", key_file_path.display()); - } + if key_file_path.exists() + && let Ok(data) = load_binary_data(&key_file_path) + { + secured_key_bytes = data; + is_key_loaded = true; + println!("Found existing key file: {}", key_file_path.display()); } // Window ID - use foreground window for Windows Hello prompts diff --git a/rust/sdk/generate_and_build_crates.ps1 b/rust/sdk/generate_and_build_crates.ps1 index 254081bb..4f78e7a4 100644 --- a/rust/sdk/generate_and_build_crates.ps1 +++ b/rust/sdk/generate_and_build_crates.ps1 @@ -3,19 +3,22 @@ # This script performs the full build process: # 1. Generates EDL bindings for all SDK and sample crates # 2. Compiles the SDK workspace with Cargo -# 3. Compiles sample workspaces separately +# 3. Optionally compiles sample workspaces (use -IncludeSamples) # 4. Optionally signs enclave DLLs for deployment # # Usage: -# .\generate_and_build_crates.ps1 # Debug build only -# .\generate_and_build_crates.ps1 -Configuration release # Release build only -# .\generate_and_build_crates.ps1 -CertName "MyCert" # Debug build + sign -# .\generate_and_build_crates.ps1 -Configuration release -CertName "MyCert" # Release + sign +# .\generate_and_build_crates.ps1 # Debug build (SDK only) +# .\generate_and_build_crates.ps1 -IncludeSamples # Debug build + samples +# .\generate_and_build_crates.ps1 -Configuration release # Release build (SDK only) +# .\generate_and_build_crates.ps1 -CertName "MyCert" # Debug build + sign +# .\generate_and_build_crates.ps1 -IncludeSamples -CertName "MyCert" # Full build + sign param( [ValidateSet("debug", "release")] [string]$Configuration = "debug", + [switch]$IncludeSamples, + [string]$CertName ) @@ -26,6 +29,9 @@ $errorActionPreference = "Stop" Write-Host "`n=== VBS Enclave SDK Build ===" -ForegroundColor Cyan Write-Host "Configuration: $Configuration" +if ($IncludeSamples) { + Write-Host "Samples: Included" +} if ($CertName) { Write-Host "Certificate: $CertName (will sign enclaves)" } @@ -38,60 +44,62 @@ Write-Host "`nGenerating EDL bindings..." -ForegroundColor Yellow Write-Host "`nBuilding SDK workspace..." -ForegroundColor Yellow . "$scriptsDir\invoke_cargo_build.ps1" -Path $PSScriptRoot -Configuration $Configuration -# Step 3: Build sample workspaces separately -# Samples are in their own workspaces to allow different panic profiles -# (enclave uses panic=abort, host uses default panic=unwind) -Write-Host "`nBuilding sample workspaces..." -ForegroundColor Yellow - -$sampleWorkspaces = @( - (Join-Path $PSScriptRoot "crates\samples\userboundkey") -) - -foreach ($samplePath in $sampleWorkspaces) { - if (Test-Path (Join-Path $samplePath "Cargo.toml")) { - Write-Host "Building sample: $(Split-Path $samplePath -Leaf)" -ForegroundColor Gray - . "$scriptsDir\invoke_cargo_build.ps1" -Path $samplePath -Configuration $Configuration - } -} - -# Step 4: Sign enclave DLLs if certificate is provided +# Step 3: Sign SDK enclave DLLs if certificate is provided if ($CertName) { - Write-Host "`nSigning enclave DLLs..." -ForegroundColor Yellow + Write-Host "`nSigning SDK enclave DLLs..." -ForegroundColor Yellow $signScript = Join-Path $scriptsDir "sign-enclave.ps1" $targetDir = if ($Configuration -eq "release") { "release" } else { "debug" } + $targetPath = Join-Path $PSScriptRoot "target\$targetDir" - # Collect enclave DLLs from SDK and sample target directories - $targetPaths = @( - (Join-Path $PSScriptRoot "target\$targetDir") - ) - foreach ($samplePath in $sampleWorkspaces) { - $targetPaths += (Join-Path $samplePath "target\$targetDir") - } - - $enclaveDlls = @() - foreach ($targetPath in $targetPaths) { - $dlls = Get-ChildItem -Path $targetPath -Filter "*_enclave.dll" -ErrorAction SilentlyContinue - if ($dlls) { $enclaveDlls += $dlls } - } - - if ($enclaveDlls.Count -eq 0) { - Write-Host "No enclave DLLs found to sign." -ForegroundColor Yellow + $enclaveDlls = Get-ChildItem -Path $targetPath -Filter "*_enclave.dll" -ErrorAction SilentlyContinue + + if (-not $enclaveDlls) { + Write-Host "No SDK enclave DLLs found to sign." -ForegroundColor Yellow } else { foreach ($dll in $enclaveDlls) { Write-Host "Signing $($dll.Name)..." -ForegroundColor Gray & $signScript -DllPath $dll.FullName -CertName $CertName if ($LASTEXITCODE -ne 0) { throw "Failed to sign $($dll.Name)" } } - Write-Host "All enclaves signed." -ForegroundColor Green + Write-Host "SDK enclaves signed." -ForegroundColor Green + } +} + +# Step 4: Build samples (only if -IncludeSamples is specified) +# Each sample has its own build.ps1 script that handles building and signing +$sampleScripts = @( + (Join-Path $PSScriptRoot "crates\samples\userboundkey\build.ps1") +) + +if ($IncludeSamples) { + Write-Host "`nBuilding samples..." -ForegroundColor Yellow + + foreach ($sampleScript in $sampleScripts) { + if (Test-Path $sampleScript) { + $sampleName = Split-Path (Split-Path $sampleScript -Parent) -Leaf + Write-Host "Building sample: $sampleName" -ForegroundColor Gray + + if ($CertName) { + & $sampleScript -Configuration $Configuration -CertName $CertName + } else { + & $sampleScript -Configuration $Configuration + } + if ($LASTEXITCODE -ne 0) { throw "Failed to build sample: $sampleName" } + } } } Write-Host "`n=== SDK Build Complete ===" -ForegroundColor Cyan $targetDir = if ($Configuration -eq "release") { "release" } else { "debug" } Write-Host "SDK output: $(Join-Path $PSScriptRoot "target\$targetDir")" -Write-Host "Sample output: $(Join-Path $PSScriptRoot "crates\samples\userboundkey\target\$targetDir")" +if ($IncludeSamples) { + Write-Host "Sample output: $(Join-Path $PSScriptRoot "crates\samples\userboundkey\target\$targetDir")" +} if (-not $CertName) { Write-Host "" Write-Host "Tip: Use -CertName to sign enclave DLLs after build." -ForegroundColor Gray +} +if (-not $IncludeSamples) { + Write-Host "Tip: Use -IncludeSamples to also build sample applications." -ForegroundColor Gray } \ No newline at end of file diff --git a/rust/sdk/generate_codegen_for_workspace.ps1 b/rust/sdk/generate_codegen_for_workspace.ps1 index d88667dd..c73aa7c0 100644 --- a/rust/sdk/generate_codegen_for_workspace.ps1 +++ b/rust/sdk/generate_codegen_for_workspace.ps1 @@ -1,7 +1,7 @@ # Generates EDL code bindings for the entire SDK workspace. # # This script generates Rust bindings from EDL files for: -# - SDK core libraries (veil_abi and sdk namespaces) +# - SDK core libraries (sdk namespace) # - Sample applications (userboundkey_sample) # # Use this script when: @@ -17,26 +17,17 @@ $errorActionPreference = "Stop" . "$PSScriptRoot\..\scripts\get_common_paths.ps1" # SDK crate paths -$edlPath = Join-Path $repoRoot "src\VbsEnclaveSDK\veil_abi.edl" $enclaveSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-enclave" $hostSdkCrate = Join-Path $SdkWorkspacePath "crates/libs/sdk-host" -# Generate veil_abi EDL bindings (core VBS enclave interface) -. "$scriptsDir\generate_codegen_crates.ps1" ` - -HostAppOutDir "$hostSdkCrate\generated" ` - -EnclaveOutDir "$enclaveSdkCrate\generated" ` - -EdlPath $edlPath ` - -Namespace "veil_abi" ` - -Vtl0ClassName "export_interface" - -# Generate SDK EDL bindings (user-bound key APIs and other SDK features) +# Generate SDK EDL bindings (user-bound key APIs and other SDK features) $sdkEdl = Join-Path $SdkWorkspacePath "crates\libs\sdk.edl" . "$scriptsDir\generate_codegen_crates.ps1" ` -HostAppOutDir "$hostSdkCrate\generated" ` -EnclaveOutDir "$enclaveSdkCrate\generated" ` -EdlPath $sdkEdl ` -Namespace "sdk" ` - -Vtl0ClassName "UserBoundKeyVtl0Host" + -Vtl0ClassName "SdkHost" # Generate sample EDL bindings (userboundkey sample application interface) $sampleDir = Join-Path $SdkWorkspacePath "crates\samples\userboundkey"