#![allow(
dead_code,
unused_imports,
unused_qualifications,
unreachable_patterns,
unsafe_code,
trivial_casts,
clippy::ptr_as_ptr
)]
use super::provider::NcryptHandle;
use crate::internal::core::AccessPolicy;
use windows::core::PCWSTR;
use windows::Win32::Security::Cryptography::*;
use windows::Win32::Security::OBJECT_SECURITY_INFORMATION;
const EXPECTED_NCRYPT_UI_POLICY_SIZE: usize = if cfg!(target_pointer_width = "64") {
32
} else {
20
};
const _: () = assert!(
std::mem::size_of::<NCRYPT_UI_POLICY>() == EXPECTED_NCRYPT_UI_POLICY_SIZE,
"NCRYPT_UI_POLICY layout changed — NCryptSetProperty / NCryptGetProperty \
cbInput values in this module will desync. Update the expected size \
constant after auditing the new layout."
);
pub fn set_ui_policy(
key_handle: &NcryptHandle,
policy: AccessPolicy,
) -> crate::internal::core::Result<()> {
if policy == AccessPolicy::None {
return Ok(());
}
let ui_policy = NCRYPT_UI_POLICY {
dwVersion: 1,
dwFlags: NCRYPT_UI_PROTECT_KEY_FLAG,
pszCreationTitle: PCWSTR::null(),
pszFriendlyName: PCWSTR::null(),
pszDescription: PCWSTR::null(),
};
let prop_name: Vec<u16> = "UI Policy"
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let result = unsafe {
NCryptSetProperty(
NCRYPT_HANDLE(key_handle.as_key().0),
PCWSTR(prop_name.as_ptr()),
std::slice::from_raw_parts(
&ui_policy as *const _ as *const u8,
std::mem::size_of::<NCRYPT_UI_POLICY>(),
),
NCRYPT_FLAGS::default(),
)
};
result.map_err(|e| crate::internal::core::Error::KeyOperation {
operation: "set_ui_policy".into(),
detail: format!("NCryptSetProperty(UI Policy): {e}"),
})
}
pub fn verify_ui_policy_matches(
key_handle: &NcryptHandle,
expected: AccessPolicy,
) -> crate::internal::core::Result<()> {
let prop_name: Vec<u16> = "UI Policy"
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let mut required: u32 = 0;
let size_result = unsafe {
NCryptGetProperty(
NCRYPT_HANDLE(key_handle.as_key().0),
PCWSTR(prop_name.as_ptr()),
None,
&mut required,
OBJECT_SECURITY_INFORMATION(0),
)
};
if let Err(e) = size_result {
if expected == AccessPolicy::None {
return Ok(());
}
return Err(crate::internal::core::Error::KeyOperation {
operation: "verify_ui_policy".into(),
detail: format!(
"NCryptGetProperty(UI Policy) size query for key with expected policy \
{expected:?}: {e}",
),
});
}
if (required as usize) < std::mem::size_of::<NCRYPT_UI_POLICY>() {
return Err(crate::internal::core::Error::KeyOperation {
operation: "verify_ui_policy".into(),
detail: format!(
"NCryptGetProperty(UI Policy) reported a buffer size of {required} bytes, \
smaller than sizeof(NCRYPT_UI_POLICY)={}",
std::mem::size_of::<NCRYPT_UI_POLICY>()
),
});
}
let mut buf = vec![0_u8; required as usize];
let mut actual_size = required;
let result = unsafe {
NCryptGetProperty(
NCRYPT_HANDLE(key_handle.as_key().0),
PCWSTR(prop_name.as_ptr()),
Some(&mut buf),
&mut actual_size,
OBJECT_SECURITY_INFORMATION(0),
)
};
if let Err(e) = result {
if expected == AccessPolicy::None {
return Ok(());
}
return Err(crate::internal::core::Error::KeyOperation {
operation: "verify_ui_policy".into(),
detail: format!(
"NCryptGetProperty(UI Policy) for key with expected policy {expected:?}: {e}",
),
});
}
let actual_flags = unsafe { (*(buf.as_ptr() as *const NCRYPT_UI_POLICY)).dwFlags };
let has_protect_flag =
(actual_flags & NCRYPT_UI_PROTECT_KEY_FLAG) == NCRYPT_UI_PROTECT_KEY_FLAG;
let expected_protect_flag = expected != AccessPolicy::None;
if has_protect_flag == expected_protect_flag {
return Ok(());
}
let detail = if expected_protect_flag {
format!("key is missing NCRYPT_UI_PROTECT_KEY_FLAG but metadata expects {expected:?}")
} else {
"key has NCRYPT_UI_PROTECT_KEY_FLAG set but metadata expects AccessPolicy::None".into()
};
Err(crate::internal::core::Error::KeyOperation {
operation: "verify_ui_policy".into(),
detail,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::internal::windows::key::{create_key, delete_key};
use crate::internal::windows::provider::open_provider;
const TEST_APP: &str = "enclaveapp_test_ui_policy";
const ECDSA_P256: &str = "ECDSA_P256";
struct KeyGuard {
label: String,
}
impl Drop for KeyGuard {
fn drop(&mut self) {
let _r = delete_key(TEST_APP, &self.label);
}
}
fn unique_label(prefix: &str) -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
format!("{prefix}-{}-{nanos}", std::process::id())
}
#[test]
#[ignore = "requires a real TPM 2.0; run on the matrix-test laptop"]
fn verify_round_trips_for_presence_required_key() {
let provider = open_provider().expect("open_provider");
let label = unique_label("any");
let _guard = KeyGuard {
label: label.clone(),
};
let (_key, _pub_key) =
create_key(&provider, TEST_APP, &label, ECDSA_P256, AccessPolicy::Any)
.expect("create_key with AccessPolicy::Any");
let key =
crate::internal::windows::key::open_key(&provider, TEST_APP, &label).expect("open_key");
verify_ui_policy_matches(&key, AccessPolicy::Any)
.expect("verify_ui_policy_matches(Any) on a presence-required key must succeed");
}
#[test]
#[ignore = "requires a real TPM 2.0; run on the matrix-test laptop"]
fn verify_round_trips_for_no_presence_key() {
let provider = open_provider().expect("open_provider");
let label = unique_label("none");
let _guard = KeyGuard {
label: label.clone(),
};
let (_key, _pub_key) =
create_key(&provider, TEST_APP, &label, ECDSA_P256, AccessPolicy::None)
.expect("create_key with AccessPolicy::None");
let key =
crate::internal::windows::key::open_key(&provider, TEST_APP, &label).expect("open_key");
verify_ui_policy_matches(&key, AccessPolicy::None)
.expect("verify_ui_policy_matches(None) on a no-presence key must succeed");
}
#[test]
#[ignore = "requires a real TPM 2.0; run on the matrix-test laptop"]
fn verify_rejects_planted_key_missing_protect_flag() {
let provider = open_provider().expect("open_provider");
let label = unique_label("plant");
let _guard = KeyGuard {
label: label.clone(),
};
let (_key, _pub_key) =
create_key(&provider, TEST_APP, &label, ECDSA_P256, AccessPolicy::None)
.expect("create_key with AccessPolicy::None");
let key =
crate::internal::windows::key::open_key(&provider, TEST_APP, &label).expect("open_key");
let err = verify_ui_policy_matches(&key, AccessPolicy::Any)
.expect_err("verify_ui_policy_matches(Any) against a None-policy key must fail");
assert!(
err.to_string()
.contains("missing NCRYPT_UI_PROTECT_KEY_FLAG"),
"error message should name the missing-flag mismatch, got: {err}"
);
}
#[test]
#[ignore = "requires a real TPM 2.0; run on the matrix-test laptop"]
fn verify_rejects_extra_protect_flag() {
let provider = open_provider().expect("open_provider");
let label = unique_label("extra");
let _guard = KeyGuard {
label: label.clone(),
};
let (_key, _pub_key) =
create_key(&provider, TEST_APP, &label, ECDSA_P256, AccessPolicy::Any)
.expect("create_key with AccessPolicy::Any");
let key =
crate::internal::windows::key::open_key(&provider, TEST_APP, &label).expect("open_key");
let err = verify_ui_policy_matches(&key, AccessPolicy::None)
.expect_err("verify_ui_policy_matches(None) against an Any-policy key must fail");
assert!(
err.to_string()
.contains("NCRYPT_UI_PROTECT_KEY_FLAG set but metadata expects"),
"error message should name the extra-flag mismatch, got: {err}"
);
}
}