#![allow(
dead_code,
unused_imports,
unused_qualifications,
unreachable_patterns,
unsafe_code,
trivial_casts,
clippy::ptr_as_ptr,
let_underscore_drop
)]
use super::convert::{eccpublic_blob_to_sec1, sec1_to_eccpublic_blob};
use super::key;
use super::provider;
use super::state;
use crate::internal::core::metadata;
use crate::internal::core::traits::{EnclaveEncryptor, EnclaveKeyManager};
use crate::internal::core::types::validate_label;
use crate::internal::core::{AccessPolicy, Error, KeyType, Result};
use std::ffi::c_void;
use std::ptr;
use windows::core::PCWSTR;
use windows::Win32::Security::Cryptography::*;
const ECDH_P256_ALGORITHM: &str = "ECDH_P256";
const BCRYPT_ECDH_PUBLIC_P256_MAGIC: u32 = 0x314B_4345;
const ECIES_VERSION: u8 = 0x01;
const GCM_NONCE_SIZE: usize = 12;
const GCM_TAG_SIZE: usize = 16;
const MIN_CIPHERTEXT_LEN: usize = 1 + 65 + GCM_NONCE_SIZE + GCM_TAG_SIZE;
#[derive(Debug)]
pub struct TpmEncryptor {
app_name: String,
keys_dir_override: Option<std::path::PathBuf>,
hello_gate: Option<std::sync::Arc<crate::internal::windows::hello_gate::HelloGate>>,
hello_cache_ttl: std::time::Duration,
hello_prompt: String,
}
impl TpmEncryptor {
pub fn new(app_name: &str) -> Self {
TpmEncryptor {
app_name: app_name.to_string(),
keys_dir_override: None,
hello_gate: None,
hello_cache_ttl: std::time::Duration::ZERO,
hello_prompt: default_hello_prompt(app_name),
}
}
pub fn with_keys_dir(app_name: &str, keys_dir: std::path::PathBuf) -> Self {
TpmEncryptor {
app_name: app_name.to_string(),
keys_dir_override: Some(keys_dir),
hello_gate: None,
hello_cache_ttl: std::time::Duration::ZERO,
hello_prompt: default_hello_prompt(app_name),
}
}
#[must_use]
pub fn with_hello_gate(
mut self,
gate: std::sync::Arc<crate::internal::windows::hello_gate::HelloGate>,
cache_ttl: std::time::Duration,
) -> Self {
self.hello_gate = Some(gate);
self.hello_cache_ttl = cache_ttl;
self
}
#[must_use]
pub fn with_hello_prompt(mut self, prompt: impl Into<String>) -> Self {
self.hello_prompt = prompt.into();
self
}
pub fn hello_gate_enabled(&self) -> bool {
self.hello_gate.is_some()
}
fn keys_dir(&self) -> std::path::PathBuf {
self.keys_dir_override
.clone()
.unwrap_or_else(|| metadata::keys_dir(&self.app_name))
}
fn ensure_hello_verified(&self, label: &str) -> Result<()> {
let Some(gate) = self.hello_gate.as_ref() else {
return Ok(());
};
let scope = format!("{}:{}", self.app_name, label);
gate.ensure_verified(&scope, &self.hello_prompt, self.hello_cache_ttl)
}
}
fn default_hello_prompt(app_name: &str) -> String {
format!("Unlock {app_name} credentials")
}
impl EnclaveKeyManager for TpmEncryptor {
fn generate(&self, label: &str, key_type: KeyType, policy: AccessPolicy) -> Result<Vec<u8>> {
validate_label(label)?;
if key_type != KeyType::Encryption {
return Err(Error::KeyOperation {
operation: "generate".into(),
detail: "TpmEncryptor only supports encryption keys".into(),
});
}
let dir = self.keys_dir();
let provider = provider::open_provider()?;
let state = state::KeyMaterialState::acquire(&dir)?;
let silent = self.hello_gate_enabled();
state.ensure_label_available(label, || {
let open_result = if silent {
key::open_key_silent(&provider, &self.app_name, label)
} else {
key::open_key(&provider, &self.app_name, label)
};
match open_result {
Ok(_key) => Ok(state::AuthoritativeKeyState::Present),
Err(Error::KeyNotFound { .. }) => Ok(state::AuthoritativeKeyState::Missing),
Err(error) => Err(error),
}
})?;
let (_key_handle, pub_key) = if silent {
key::create_key_silent(
&provider,
&self.app_name,
label,
ECDH_P256_ALGORITHM,
policy,
)?
} else {
key::create_key(
&provider,
&self.app_name,
label,
ECDH_P256_ALGORITHM,
policy,
)?
};
state.persist_generated_key(label, key_type, policy, &pub_key, || {
key::delete_key(&self.app_name, label)
})?;
if let Ok(Some(hmac_key)) =
crate::internal::windows::meta_hmac::load_or_create(&self.app_name)
{
let meta = crate::internal::core::KeyMeta::new(label, key_type, policy);
if let Err(e) = crate::internal::core::metadata::save_meta_with_hmac(
state.dir(),
label,
&meta,
hmac_key.as_slice(),
) {
tracing::warn!(
label = label,
error = %e,
"post-persist meta-HMAC sidecar write failed; \
next load's auto-migrate will retry"
);
}
}
Ok(pub_key)
}
fn public_key(&self, label: &str) -> Result<Vec<u8>> {
validate_label(label)?;
let dir = self.keys_dir();
let state = state::KeyMaterialState::acquire(&dir)?;
let provider = provider::open_provider()?;
let key_handle = key::open_key(&provider, &self.app_name, label)?;
let pub_key = crate::internal::windows::export::export_public_key(&key_handle)?;
metadata::sync_pub_key(state.dir(), label, &pub_key)
}
fn list_keys(&self) -> Result<Vec<String>> {
let provider = provider::open_provider()?;
key::enumerate_keys(&provider, &self.app_name)
}
fn delete_key(&self, label: &str) -> Result<()> {
validate_label(label)?;
let dir = self.keys_dir();
let state = state::KeyMaterialState::acquire(&dir)?;
state.reconcile_deleted_key(label, || match key::delete_key(&self.app_name, label) {
Ok(()) => Ok(state::AuthoritativeKeyState::Present),
Err(Error::KeyNotFound { .. }) => Ok(state::AuthoritativeKeyState::Missing),
Err(error) => Err(error),
})
}
fn is_available(&self) -> bool {
provider::is_available()
}
}
impl EnclaveEncryptor for TpmEncryptor {
fn encrypt(&self, label: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
validate_label(label)?;
let stored_pub = self.public_key(label)?;
unsafe { ecies_encrypt(&stored_pub, plaintext) }
}
fn decrypt(&self, label: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
validate_label(label)?;
if ciphertext.len() < MIN_CIPHERTEXT_LEN {
return Err(Error::DecryptFailed {
detail: format!(
"ciphertext too short: {} < {MIN_CIPHERTEXT_LEN}",
ciphertext.len()
),
});
}
if ciphertext[0] != ECIES_VERSION {
return Err(Error::DecryptFailed {
detail: format!("unsupported ECIES version: 0x{:02x}", ciphertext[0]),
});
}
let ephemeral_pub = &ciphertext[1..66];
let nonce = &ciphertext[66..66 + GCM_NONCE_SIZE];
let ct_and_tag = &ciphertext[66 + GCM_NONCE_SIZE..];
if ct_and_tag.len() < GCM_TAG_SIZE {
return Err(Error::DecryptFailed {
detail: "ciphertext too short for tag".into(),
});
}
let ct = &ct_and_tag[..ct_and_tag.len() - GCM_TAG_SIZE];
let tag = &ct_and_tag[ct_and_tag.len() - GCM_TAG_SIZE..];
let provider = provider::open_provider()?;
let key_handle = if self.hello_gate.is_some() {
key::open_key_silent(&provider, &self.app_name, label)?
} else {
key::open_key(&provider, &self.app_name, label)?
};
let dir = self.keys_dir();
let expected_policy = match metadata::load_meta(&dir, label) {
Ok(meta) => meta.access_policy,
Err(Error::KeyNotFound { .. }) => AccessPolicy::None,
Err(err) => return Err(err),
};
crate::internal::windows::ui_policy::verify_ui_policy_matches(
&key_handle,
expected_policy,
)?;
self.ensure_hello_verified(label)?;
unsafe { ecies_decrypt(&key_handle, ephemeral_pub, nonce, ct, tag) }
}
}
fn to_wide(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
unsafe fn ecies_encrypt(stored_pub_sec1: &[u8], plaintext: &[u8]) -> Result<Vec<u8>> {
let ecdh_alg_name = to_wide(ECDH_P256_ALGORITHM);
let eccpub_blob_type = to_wide("ECCPUBLICBLOB");
let mut ecdh_alg = BCRYPT_ALG_HANDLE::default();
BCryptOpenAlgorithmProvider(
&mut ecdh_alg,
PCWSTR(ecdh_alg_name.as_ptr()),
None,
Default::default(),
)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptOpenAlgorithmProvider(ECDH): {e}"),
})?;
let mut ephemeral_key = BCRYPT_KEY_HANDLE::default();
BCryptGenerateKeyPair(ecdh_alg, &mut ephemeral_key, 256, 0)
.ok()
.map_err(|e| {
let _ = BCryptCloseAlgorithmProvider(ecdh_alg, 0).ok();
Error::EncryptFailed {
detail: format!("BCryptGenerateKeyPair: {e}"),
}
})?;
BCryptFinalizeKeyPair(ephemeral_key, 0).ok().map_err(|e| {
let _ = BCryptDestroyKey(ephemeral_key).ok();
let _ = BCryptCloseAlgorithmProvider(ecdh_alg, 0).ok();
Error::EncryptFailed {
detail: format!("BCryptFinalizeKeyPair: {e}"),
}
})?;
let eph_pub_sec1 = {
let mut sz: u32 = 0;
BCryptExportKey(
ephemeral_key,
BCRYPT_KEY_HANDLE::default(),
PCWSTR(eccpub_blob_type.as_ptr()),
None,
&mut sz,
0,
)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptExportKey eph size: {e}"),
})?;
let mut blob = vec![0_u8; sz as usize];
BCryptExportKey(
ephemeral_key,
BCRYPT_KEY_HANDLE::default(),
PCWSTR(eccpub_blob_type.as_ptr()),
Some(&mut blob),
&mut sz,
0,
)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptExportKey eph: {e}"),
})?;
blob.truncate(sz as usize);
eccpublic_blob_to_sec1(&blob)?
};
let stored_blob = sec1_to_eccpublic_blob(stored_pub_sec1, BCRYPT_ECDH_PUBLIC_P256_MAGIC)?;
let mut stored_key = BCRYPT_KEY_HANDLE::default();
BCryptImportKeyPair(
ecdh_alg,
BCRYPT_KEY_HANDLE::default(),
PCWSTR(eccpub_blob_type.as_ptr()),
&mut stored_key,
&stored_blob,
0,
)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptImportKeyPair: {e}"),
})?;
let mut secret = BCRYPT_SECRET_HANDLE::default();
BCryptSecretAgreement(ephemeral_key, stored_key, &mut secret, 0)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptSecretAgreement: {e}"),
})?;
let kdf_name = to_wide("HASH");
let mut sha256_alg = to_wide("SHA256");
let mut kdf_buffer = BCryptBuffer {
cbBuffer: (sha256_alg.len() * std::mem::size_of::<u16>()) as u32,
BufferType: KDF_HASH_ALGORITHM,
pvBuffer: sha256_alg.as_mut_ptr() as *mut c_void,
};
let kdf_params = BCryptBufferDesc {
ulVersion: BCRYPTBUFFER_VERSION,
cBuffers: 1,
pBuffers: &mut kdf_buffer,
};
let mut derived_key = vec![0_u8; 32];
let mut derived_len: u32 = 0;
BCryptDeriveKey(
secret,
PCWSTR(kdf_name.as_ptr()),
Some(&kdf_params),
Some(&mut derived_key),
&mut derived_len,
0,
)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptDeriveKey: {e}"),
})?;
derived_key.truncate(derived_len as usize);
let _ = BCryptDestroySecret(secret).ok();
let _ = BCryptDestroyKey(stored_key).ok();
let _ = BCryptDestroyKey(ephemeral_key).ok();
let _ = BCryptCloseAlgorithmProvider(ecdh_alg, 0).ok();
let (nonce, tag, ct) = aes_gcm_encrypt(&derived_key, plaintext)?;
let mut output = Vec::with_capacity(1 + 65 + GCM_NONCE_SIZE + ct.len() + GCM_TAG_SIZE);
output.push(ECIES_VERSION);
output.extend_from_slice(&eph_pub_sec1);
output.extend_from_slice(&nonce);
output.extend_from_slice(&ct);
output.extend_from_slice(&tag);
Ok(output)
}
unsafe fn ecies_decrypt(
tpm_key: &provider::NcryptHandle,
ephemeral_pub_sec1: &[u8],
nonce: &[u8],
ct: &[u8],
tag: &[u8],
) -> Result<Vec<u8>> {
let eccpub_blob_type = to_wide("ECCPUBLICBLOB");
let platform_prov = provider::open_provider()?;
let eph_blob = sec1_to_eccpublic_blob(ephemeral_pub_sec1, BCRYPT_ECDH_PUBLIC_P256_MAGIC)?;
let mut eph_key = NCRYPT_KEY_HANDLE::default();
NCryptImportKey(
platform_prov.as_prov(),
NCRYPT_KEY_HANDLE::default(),
PCWSTR(eccpub_blob_type.as_ptr()),
None,
&mut eph_key,
&eph_blob,
NCRYPT_FLAGS::default(),
)
.map_err(|e| Error::DecryptFailed {
detail: format!("NCryptImportKey(eph): {e}"),
})?;
let mut secret = NCRYPT_SECRET_HANDLE::default();
NCryptSecretAgreement(
tpm_key.as_key(),
eph_key,
&mut secret,
NCRYPT_FLAGS::default(),
)
.map_err(|e| Error::DecryptFailed {
detail: format!("NCryptSecretAgreement: {e}"),
})?;
let kdf_name = to_wide("HASH");
let mut sha256_alg = to_wide("SHA256");
let mut kdf_buffer = BCryptBuffer {
cbBuffer: (sha256_alg.len() * std::mem::size_of::<u16>()) as u32,
BufferType: KDF_HASH_ALGORITHM,
pvBuffer: sha256_alg.as_mut_ptr() as *mut c_void,
};
let kdf_params = BCryptBufferDesc {
ulVersion: BCRYPTBUFFER_VERSION,
cBuffers: 1,
pBuffers: &mut kdf_buffer,
};
let mut derived_key = vec![0_u8; 32];
let mut derived_len: u32 = 0;
NCryptDeriveKey(
secret,
PCWSTR(kdf_name.as_ptr()),
Some(&kdf_params),
Some(&mut derived_key),
&mut derived_len,
0_u32,
)
.map_err(|e| Error::DecryptFailed {
detail: format!("NCryptDeriveKey: {e}"),
})?;
derived_key.truncate(derived_len as usize);
drop(NCryptFreeObject(NCRYPT_HANDLE(secret.0)));
drop(NCryptFreeObject(NCRYPT_HANDLE(eph_key.0)));
aes_gcm_decrypt(&derived_key, nonce, ct, tag)
}
unsafe fn aes_gcm_encrypt(
key_bytes: &[u8],
plaintext: &[u8],
) -> Result<([u8; GCM_NONCE_SIZE], [u8; GCM_TAG_SIZE], Vec<u8>)> {
let aes_alg_name = to_wide("AES");
let chain_mode_prop = to_wide("ChainingMode");
let gcm_mode = to_wide("ChainingModeGCM");
let mut aes_alg = BCRYPT_ALG_HANDLE::default();
BCryptOpenAlgorithmProvider(
&mut aes_alg,
PCWSTR(aes_alg_name.as_ptr()),
None,
Default::default(),
)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptOpenAlgorithmProvider(AES): {e}"),
})?;
BCryptSetProperty(
aes_alg.into(),
PCWSTR(chain_mode_prop.as_ptr()),
std::slice::from_raw_parts(gcm_mode.as_ptr() as *const u8, gcm_mode.len() * 2),
0,
)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptSetProperty(GCM): {e}"),
})?;
let mut aes_key = BCRYPT_KEY_HANDLE::default();
BCryptGenerateSymmetricKey(aes_alg, &mut aes_key, None, key_bytes, 0)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptGenerateSymmetricKey: {e}"),
})?;
let mut nonce = [0_u8; GCM_NONCE_SIZE];
BCryptGenRandom(
BCRYPT_ALG_HANDLE::default(),
&mut nonce,
BCRYPT_USE_SYSTEM_PREFERRED_RNG,
)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptGenRandom: {e}"),
})?;
let mut tag = [0_u8; GCM_TAG_SIZE];
let auth_info = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO {
cbSize: std::mem::size_of::<BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO>() as u32,
dwInfoVersion: 1, pbNonce: nonce.as_mut_ptr(),
cbNonce: GCM_NONCE_SIZE as u32,
pbAuthData: ptr::null_mut(),
cbAuthData: 0,
pbTag: tag.as_mut_ptr(),
cbTag: GCM_TAG_SIZE as u32,
pbMacContext: ptr::null_mut(),
cbMacContext: 0,
cbAAD: 0,
cbData: 0,
dwFlags: 0,
};
let mut ciphertext = vec![0_u8; plaintext.len()];
let mut ct_len: u32 = 0;
BCryptEncrypt(
aes_key,
Some(plaintext),
Some(&auth_info as *const _ as *const _),
None,
Some(&mut ciphertext),
&mut ct_len,
BCRYPT_FLAGS::default(),
)
.ok()
.map_err(|e| Error::EncryptFailed {
detail: format!("BCryptEncrypt(AES-GCM): {e}"),
})?;
ciphertext.truncate(ct_len as usize);
let _ = BCryptDestroyKey(aes_key).ok();
let _ = BCryptCloseAlgorithmProvider(aes_alg, 0).ok();
Ok((nonce, tag, ciphertext))
}
unsafe fn aes_gcm_decrypt(
key_bytes: &[u8],
nonce: &[u8],
ciphertext: &[u8],
tag: &[u8],
) -> Result<Vec<u8>> {
let aes_alg_name = to_wide("AES");
let chain_mode_prop = to_wide("ChainingMode");
let gcm_mode = to_wide("ChainingModeGCM");
let mut aes_alg = BCRYPT_ALG_HANDLE::default();
BCryptOpenAlgorithmProvider(
&mut aes_alg,
PCWSTR(aes_alg_name.as_ptr()),
None,
Default::default(),
)
.ok()
.map_err(|e| Error::DecryptFailed {
detail: format!("BCryptOpenAlgorithmProvider(AES): {e}"),
})?;
BCryptSetProperty(
aes_alg.into(),
PCWSTR(chain_mode_prop.as_ptr()),
std::slice::from_raw_parts(gcm_mode.as_ptr() as *const u8, gcm_mode.len() * 2),
0,
)
.ok()
.map_err(|e| Error::DecryptFailed {
detail: format!("BCryptSetProperty(GCM): {e}"),
})?;
let mut aes_key = BCRYPT_KEY_HANDLE::default();
BCryptGenerateSymmetricKey(aes_alg, &mut aes_key, None, key_bytes, 0)
.ok()
.map_err(|e| Error::DecryptFailed {
detail: format!("BCryptGenerateSymmetricKey: {e}"),
})?;
let mut nonce_copy = [0_u8; GCM_NONCE_SIZE];
nonce_copy[..nonce.len()].copy_from_slice(nonce);
let mut tag_copy = [0_u8; GCM_TAG_SIZE];
tag_copy[..tag.len()].copy_from_slice(tag);
let auth_info = BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO {
cbSize: std::mem::size_of::<BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO>() as u32,
dwInfoVersion: 1,
pbNonce: nonce_copy.as_mut_ptr(),
cbNonce: GCM_NONCE_SIZE as u32,
pbAuthData: ptr::null_mut(),
cbAuthData: 0,
pbTag: tag_copy.as_mut_ptr(),
cbTag: GCM_TAG_SIZE as u32,
pbMacContext: ptr::null_mut(),
cbMacContext: 0,
cbAAD: 0,
cbData: 0,
dwFlags: 0,
};
let mut plaintext = vec![0_u8; ciphertext.len()];
let mut pt_len: u32 = 0;
BCryptDecrypt(
aes_key,
Some(ciphertext),
Some(&auth_info as *const _ as *const _),
None,
Some(&mut plaintext),
&mut pt_len,
BCRYPT_FLAGS::default(),
)
.ok()
.map_err(|e| Error::DecryptFailed {
detail: format!("BCryptDecrypt(AES-GCM): {e}"),
})?;
plaintext.truncate(pt_len as usize);
let _ = BCryptDestroyKey(aes_key).ok();
let _ = BCryptCloseAlgorithmProvider(aes_alg, 0).ok();
Ok(plaintext)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aes_gcm_roundtrip_32byte_key() {
let key = [0x42_u8; 32]; let plaintext = b"the quick brown fox jumps over the lazy dog";
let (nonce, tag, ciphertext) =
unsafe { aes_gcm_encrypt(&key, plaintext) }.expect("AES-GCM encrypt failed");
assert_eq!(ciphertext.len(), plaintext.len());
let recovered = unsafe { aes_gcm_decrypt(&key, &nonce, &ciphertext, &tag) }
.expect("AES-GCM decrypt failed");
assert_eq!(recovered, plaintext);
}
#[test]
fn aes_gcm_tampered_ciphertext_fails() {
let key = [0xAB_u8; 32];
let plaintext = b"sensitive data";
let (nonce, tag, mut ciphertext) =
unsafe { aes_gcm_encrypt(&key, plaintext) }.expect("encrypt failed");
if !ciphertext.is_empty() {
ciphertext[0] ^= 0xFF;
}
let result = unsafe { aes_gcm_decrypt(&key, &nonce, &ciphertext, &tag) };
assert!(
result.is_err(),
"tampered ciphertext should fail decryption"
);
}
#[test]
fn aes_gcm_empty_plaintext() {
let key = [0x01_u8; 32];
let plaintext = b"";
let (nonce, tag, ciphertext) =
unsafe { aes_gcm_encrypt(&key, plaintext) }.expect("encrypt failed");
assert!(ciphertext.is_empty());
let recovered =
unsafe { aes_gcm_decrypt(&key, &nonce, &ciphertext, &tag) }.expect("decrypt failed");
assert!(recovered.is_empty());
}
#[test]
fn ecies_encrypt_produces_valid_envelope() {
let ecdh_alg_name = to_wide(ECDH_P256_ALGORITHM);
let eccpub_blob_type = to_wide("ECCPUBLICBLOB");
unsafe {
let mut ecdh_alg = BCRYPT_ALG_HANDLE::default();
BCryptOpenAlgorithmProvider(
&mut ecdh_alg,
PCWSTR(ecdh_alg_name.as_ptr()),
None,
Default::default(),
)
.ok()
.expect("open ECDH provider");
let mut key = BCRYPT_KEY_HANDLE::default();
BCryptGenerateKeyPair(ecdh_alg, &mut key, 256, 0)
.ok()
.expect("generate key pair");
BCryptFinalizeKeyPair(key, 0)
.ok()
.expect("finalize key pair");
let mut sz: u32 = 0;
BCryptExportKey(
key,
BCRYPT_KEY_HANDLE::default(),
PCWSTR(eccpub_blob_type.as_ptr()),
None,
&mut sz,
0,
)
.ok()
.expect("export size");
let mut blob = vec![0_u8; sz as usize];
BCryptExportKey(
key,
BCRYPT_KEY_HANDLE::default(),
PCWSTR(eccpub_blob_type.as_ptr()),
Some(&mut blob),
&mut sz,
0,
)
.ok()
.expect("export key");
blob.truncate(sz as usize);
let pub_sec1 = crate::internal::windows::convert::eccpublic_blob_to_sec1(&blob)
.expect("convert to SEC1");
let plaintext = b"ecies roundtrip test payload";
let envelope = ecies_encrypt(&pub_sec1, plaintext)
.expect("ecies_encrypt should succeed with SHA-256 KDF");
assert!(envelope.len() >= MIN_CIPHERTEXT_LEN);
assert_eq!(envelope[0], ECIES_VERSION);
assert_eq!(envelope[1], 0x04, "SEC1 uncompressed prefix");
let _ = BCryptDestroyKey(key).ok();
let _ = BCryptCloseAlgorithmProvider(ecdh_alg, 0).ok();
}
}
#[test]
fn ecies_tpm_roundtrip() {
use crate::internal::core::traits::{EnclaveEncryptor, EnclaveKeyManager};
let enc = TpmEncryptor::new("roundtrip-test");
if !enc.is_available() {
return;
}
let label = "rt-key";
let _ = enc.delete_key(label);
enc.generate(label, KeyType::Encryption, AccessPolicy::None)
.expect("generate");
let plaintext = b"ECIES roundtrip proof - encrypt then decrypt";
let ciphertext = enc.encrypt(label, plaintext).expect("encrypt");
assert!(ciphertext.len() > plaintext.len());
assert_eq!(ciphertext[0], ECIES_VERSION);
let recovered = enc.decrypt(label, &ciphertext).expect("decrypt");
assert_eq!(recovered, plaintext);
let _ = enc.delete_key(label);
}
}