#![allow(dead_code, unused_imports, unused_qualifications, unreachable_patterns)]
#[allow(unused_imports)]
use std::path::PathBuf;
use zeroize::Zeroizing;
pub fn meta_hmac_key(app_name: &str) -> Option<Zeroizing<Vec<u8>>> {
#[cfg(target_os = "macos")]
{
match crate::internal::apple::meta_hmac::load_or_create(app_name) {
Ok(key) => key,
Err(e) => {
tracing::warn!(
error = %e,
"macOS meta-HMAC key load failed; falling back to no-HMAC mode"
);
None
}
}
}
#[cfg(target_os = "windows")]
{
match crate::internal::windows::meta_hmac::load_or_create(app_name) {
Ok(key) => key,
Err(e) => {
tracing::warn!(
error = %e,
"Windows DPAPI meta-HMAC key load failed; falling back to no-HMAC mode"
);
None
}
}
}
#[cfg(target_os = "linux")]
{
crate::internal::keyring::meta_hmac_key(app_name)
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
let _ = app_name;
None
}
}
pub fn verify_meta_integrity(
app_name: &str,
keys_dir: &std::path::Path,
label: &str,
) -> crate::internal::app_storage::error::Result<()> {
let meta_path = keys_dir.join(format!("{label}.meta"));
if !meta_path.exists() {
return Ok(());
}
let hmac_key = match meta_hmac_key(app_name) {
Some(k) => k,
None => {
return Ok(());
}
};
match crate::internal::core::metadata::load_meta_with_hmac(
keys_dir,
label,
hmac_key.as_slice(),
crate::internal::core::metadata::MetaIntegrityMode::RequireSidecar,
) {
Ok(_) => Ok(()),
Err(e) => {
let msg = e.to_string();
if msg.contains(crate::internal::core::metadata::META_HMAC_VERIFY_OP) {
return Err(crate::internal::app_storage::error::StorageError::KeyInitFailed(msg));
}
if msg.contains(crate::internal::core::metadata::META_HMAC_MISSING_OP) {
tracing::warn!(
label = %label,
"`.meta.hmac` sidecar missing — migrating from existing meta. \
If you did not just upgrade, treat this as suspicious and \
regenerate the key."
);
if let Err(migrate_err) = crate::internal::core::metadata::migrate_meta_to_hmac(
keys_dir,
label,
hmac_key.as_slice(),
) {
return Err(
crate::internal::app_storage::error::StorageError::KeyInitFailed(
migrate_err.to_string(),
),
);
}
return Ok(());
}
Ok(())
}
}
}
pub fn check_meta_integrity(
app_name: &str,
label: &str,
keys_dir: &std::path::Path,
) -> crate::internal::app_storage::error::Result<()> {
let meta_path = keys_dir.join(format!("{label}.meta"));
if !meta_path.exists() {
return Ok(());
}
#[cfg(target_os = "macos")]
{
let hmac_key = match crate::internal::apple::meta_hmac::load_existing(app_name) {
Ok(Some(k)) => k,
_ => return Ok(()),
};
let outcome = crate::internal::apple::meta_tag::verify(
app_name,
label,
keys_dir,
hmac_key.as_slice(),
)
.map_err(|e| {
crate::internal::app_storage::error::StorageError::KeyInitFailed(e.to_string())
})?;
match outcome {
crate::internal::apple::meta_tag::VerifyOutcome::Match
| crate::internal::apple::meta_tag::VerifyOutcome::NoMeta
| crate::internal::apple::meta_tag::VerifyOutcome::KeychainUnavailable => Ok(()),
crate::internal::apple::meta_tag::VerifyOutcome::Tamper => Err(
crate::internal::app_storage::error::StorageError::KeyInitFailed(format!(
"key '{label}': metadata integrity check failed. The on-disk meta does \
not match the keychain-stored tag — meta may have been tampered with. \
Refusing to proceed. Regenerate the key to restore a known-good state."
)),
),
crate::internal::apple::meta_tag::VerifyOutcome::Legacy => Err(
crate::internal::app_storage::error::StorageError::KeyInitFailed(format!(
"key '{label}' has no integrity tag. This is a one-time migration \
required by upgrading to a build that introduces meta integrity tags, \
and is not something future upgrades will repeat. If you have already \
run `{app_name} migrate-meta` on this machine, treat this as a tamper \
signal — do not run it again. Regenerate the affected key instead. \
Before migrating, verify the key's current policy looks correct: \
`{app_name} inspect {label}`. To migrate: `{app_name} migrate-meta`."
)),
),
}
}
#[cfg(target_os = "windows")]
{
let hmac_key = match crate::internal::windows::meta_hmac::load_existing(app_name) {
Ok(Some(k)) => k,
_ => return Ok(()),
};
let outcome = crate::internal::windows::meta_tag::verify(
app_name,
label,
keys_dir,
hmac_key.as_slice(),
)
.map_err(|e| {
crate::internal::app_storage::error::StorageError::KeyInitFailed(e.to_string())
})?;
match outcome {
crate::internal::windows::meta_tag::VerifyOutcome::Match
| crate::internal::windows::meta_tag::VerifyOutcome::NoMeta
| crate::internal::windows::meta_tag::VerifyOutcome::KeychainUnavailable => Ok(()),
crate::internal::windows::meta_tag::VerifyOutcome::Tamper => Err(
crate::internal::app_storage::error::StorageError::KeyInitFailed(format!(
"key '{label}': metadata integrity check failed. The on-disk meta does \
not match the keychain-stored tag — meta may have been tampered with. \
Refusing to proceed. Regenerate the key to restore a known-good state."
)),
),
crate::internal::windows::meta_tag::VerifyOutcome::Legacy => Err(
crate::internal::app_storage::error::StorageError::KeyInitFailed(format!(
"key '{label}' has no integrity tag. This is a one-time migration \
required by upgrading to a build that introduces meta integrity tags, \
and is not something future upgrades will repeat. If you have already \
run `{app_name} migrate-meta` on this machine, treat this as a tamper \
signal — do not run it again. Regenerate the affected key instead. \
Before migrating, verify the key's current policy looks correct: \
`{app_name} inspect {label}`. To migrate: `{app_name} migrate-meta`."
)),
),
}
}
#[cfg(target_os = "linux")]
{
let hmac_key = match crate::internal::keyring::meta_hmac_key_existing(app_name) {
Ok(Some(k)) => k,
_ => return Ok(()),
};
let outcome = crate::internal::keyring::meta_tag::verify(
app_name,
label,
keys_dir,
hmac_key.as_slice(),
)
.map_err(|e| {
crate::internal::app_storage::error::StorageError::KeyInitFailed(e.to_string())
})?;
match outcome {
crate::internal::keyring::meta_tag::VerifyOutcome::Match
| crate::internal::keyring::meta_tag::VerifyOutcome::NoMeta
| crate::internal::keyring::meta_tag::VerifyOutcome::KeychainUnavailable => Ok(()),
crate::internal::keyring::meta_tag::VerifyOutcome::Tamper => Err(
crate::internal::app_storage::error::StorageError::KeyInitFailed(format!(
"key '{label}': metadata integrity check failed. The on-disk meta does \
not match the keychain-stored tag — meta may have been tampered with. \
Refusing to proceed. Regenerate the key to restore a known-good state."
)),
),
crate::internal::keyring::meta_tag::VerifyOutcome::Legacy => Err(
crate::internal::app_storage::error::StorageError::KeyInitFailed(format!(
"key '{label}' has no integrity tag. This is a one-time migration \
required by upgrading to a build that introduces meta integrity tags, \
and is not something future upgrades will repeat. If you have already \
run `{app_name} migrate-meta` on this machine, treat this as a tamper \
signal — do not run it again. Regenerate the affected key instead. \
Before migrating, verify the key's current policy looks correct: \
`{app_name} inspect {label}`. To migrate: `{app_name} migrate-meta`."
)),
),
}
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
let _ = (app_name, label, keys_dir);
Ok(())
}
}
pub fn store_file_tag(
app_name: &str,
path_label: &str,
tag: &[u8; 32],
) -> crate::internal::core::Result<()> {
#[cfg(target_os = "macos")]
{
crate::internal::apple::meta_tag::store(app_name, path_label, tag)
}
#[cfg(target_os = "windows")]
{
crate::internal::windows::meta_tag::store(app_name, path_label, tag)
}
#[cfg(target_os = "linux")]
{
crate::internal::keyring::meta_tag::store(app_name, path_label, tag)
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
let _ = (app_name, path_label, tag);
Ok(())
}
}
pub fn load_file_tag(
app_name: &str,
path_label: &str,
) -> crate::internal::core::Result<Option<[u8; 32]>> {
#[cfg(target_os = "macos")]
{
crate::internal::apple::meta_tag::load(app_name, path_label)
}
#[cfg(target_os = "windows")]
{
crate::internal::windows::meta_tag::load(app_name, path_label)
}
#[cfg(target_os = "linux")]
{
crate::internal::keyring::meta_tag::load(app_name, path_label)
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
let _ = (app_name, path_label);
Ok(None)
}
}
pub fn delete_file_tag(app_name: &str, path_label: &str) -> crate::internal::core::Result<()> {
#[cfg(target_os = "macos")]
{
crate::internal::apple::meta_tag::delete(app_name, path_label)
}
#[cfg(target_os = "windows")]
{
crate::internal::windows::meta_tag::delete(app_name, path_label)
}
#[cfg(target_os = "linux")]
{
crate::internal::keyring::meta_tag::delete(app_name, path_label)
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
let _ = (app_name, path_label);
Ok(())
}
}
pub fn delete_meta_hmac_key(app_name: &str) -> crate::internal::core::Result<()> {
#[cfg(target_os = "macos")]
{
crate::internal::apple::meta_hmac::delete(app_name)
}
#[cfg(target_os = "windows")]
{
crate::internal::windows::meta_hmac::delete(app_name)
}
#[cfg(target_os = "linux")]
{
let _ = app_name;
Ok(())
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
let _ = app_name;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendKind {
SecureEnclave,
Tpm,
WindowsDpapi,
TpmBridge,
Keyring,
}
impl std::fmt::Display for BackendKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BackendKind::SecureEnclave => write!(f, "Secure Enclave"),
BackendKind::Tpm => write!(f, "TPM 2.0"),
BackendKind::WindowsDpapi => write!(f, "Windows DPAPI"),
BackendKind::TpmBridge => write!(f, "TPM 2.0 (WSL Bridge)"),
BackendKind::Keyring => write!(f, "Keyring"),
}
}
}
#[cfg(target_os = "linux")]
pub fn find_bridge_executable(app_name: &str, extra_paths: &[String]) -> Option<PathBuf> {
if let Some(path) = crate::internal::bridge::find_bridge(app_name) {
return Some(path);
}
let auto_paths = [
format!("/mnt/c/Program Files/{app_name}/{app_name}-tpm-bridge.exe"),
format!("/mnt/c/ProgramData/{app_name}/{app_name}-tpm-bridge.exe"),
];
for path_str in &auto_paths {
let path = std::path::Path::new(path_str);
if path.exists() {
return Some(path.to_path_buf());
}
}
for path_str in extra_paths {
let path = std::path::Path::new(path_str);
if path.is_absolute() && path.exists() {
return Some(path.to_path_buf());
}
}
None
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn backend_kind_display() {
assert_eq!(BackendKind::SecureEnclave.to_string(), "Secure Enclave");
assert_eq!(BackendKind::Tpm.to_string(), "TPM 2.0");
assert_eq!(BackendKind::WindowsDpapi.to_string(), "Windows DPAPI");
assert_eq!(BackendKind::TpmBridge.to_string(), "TPM 2.0 (WSL Bridge)");
assert_eq!(BackendKind::Keyring.to_string(), "Keyring");
}
#[test]
fn backend_kind_eq() {
assert_eq!(BackendKind::SecureEnclave, BackendKind::SecureEnclave);
assert_ne!(BackendKind::SecureEnclave, BackendKind::Tpm);
}
#[test]
fn backend_kind_clone() {
let kind = BackendKind::Tpm;
let cloned = kind;
assert_eq!(kind, cloned);
}
#[test]
fn backend_kind_debug_nonempty() {
for kind in [
BackendKind::SecureEnclave,
BackendKind::Tpm,
BackendKind::WindowsDpapi,
BackendKind::TpmBridge,
BackendKind::Keyring,
] {
let s = format!("{kind:?}");
assert!(!s.is_empty());
}
}
#[test]
fn backend_kind_all_variants_display_nonempty() {
for kind in [
BackendKind::SecureEnclave,
BackendKind::Tpm,
BackendKind::WindowsDpapi,
BackendKind::TpmBridge,
BackendKind::Keyring,
] {
assert!(!kind.to_string().is_empty());
}
}
#[test]
fn backend_kind_all_pairs_not_equal() {
let kinds = [
BackendKind::SecureEnclave,
BackendKind::Tpm,
BackendKind::WindowsDpapi,
BackendKind::TpmBridge,
BackendKind::Keyring,
];
for i in 0..kinds.len() {
for j in 0..kinds.len() {
if i != j {
assert_ne!(kinds[i], kinds[j]);
}
}
}
}
#[test]
fn verify_meta_integrity_succeeds_when_meta_file_absent() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-platform-test-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
let result = verify_meta_integrity("test-app", &dir, "nonexistent-label");
assert!(result.is_ok());
drop(std::fs::remove_dir_all(&dir));
}
#[test]
fn check_meta_integrity_succeeds_when_meta_file_absent() {
let dir = std::env::temp_dir().join(format!(
"enclaveapp-platform-test2-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
let result = check_meta_integrity("test-app", "nonexistent-label", &dir);
assert!(result.is_ok());
drop(std::fs::remove_dir_all(&dir));
}
#[cfg(target_os = "linux")]
#[test]
fn find_bridge_executable_returns_none_on_dev_machine() {
drop(find_bridge_executable("test-app", &[]));
}
}