#![allow(dead_code, unused_imports, unused_qualifications, unreachable_patterns)]
#[allow(unused_imports)]
use super::error::{Result, StorageError};
use super::platform::BackendKind;
use super::StorageConfig;
use crate::internal::core::traits::{EnclaveKeyManager, EnclaveSigner};
use tracing::debug;
pub struct AppSigningBackend {
kind: BackendKind,
inner: SigningInner,
}
enum SigningInner {
#[cfg(target_os = "macos")]
SecureEnclave(crate::internal::apple::SecureEnclaveSigner),
#[cfg(target_os = "windows")]
Tpm(crate::internal::windows::TpmSigner),
#[cfg(all(target_os = "linux", target_env = "gnu", feature = "linux-tpm"))]
LinuxTpm(crate::internal::linux_tpm::LinuxTpmSigner),
#[cfg(target_os = "linux")]
Software(crate::internal::keyring::SoftwareSigner),
#[cfg(target_os = "linux")]
WslBridge(BridgeSignerWrapper),
}
#[cfg(target_os = "linux")]
struct BridgeSignerWrapper {
bridge_path: std::path::PathBuf,
app_name: String,
key_label: String,
access_policy: crate::internal::core::types::AccessPolicy,
}
#[cfg(target_os = "linux")]
impl EnclaveKeyManager for BridgeSignerWrapper {
fn generate(
&self,
label: &str,
_key_type: crate::internal::core::types::KeyType,
_policy: crate::internal::core::types::AccessPolicy,
) -> crate::internal::core::Result<Vec<u8>> {
crate::internal::bridge::bridge_init_signing(
&self.bridge_path,
&self.app_name,
label,
self.access_policy,
)?;
self.public_key(label)
}
fn public_key(&self, label: &str) -> crate::internal::core::Result<Vec<u8>> {
crate::internal::bridge::bridge_public_key(
&self.bridge_path,
&self.app_name,
label,
self.access_policy,
)
}
fn list_keys(&self) -> crate::internal::core::Result<Vec<String>> {
crate::internal::bridge::bridge_list_keys(
&self.bridge_path,
&self.app_name,
&self.key_label,
self.access_policy,
)
}
fn delete_key(&self, label: &str) -> crate::internal::core::Result<()> {
crate::internal::bridge::bridge_delete_signing(&self.bridge_path, &self.app_name, label)
}
fn is_available(&self) -> bool {
true
}
fn key_exists(&self, label: &str) -> crate::internal::core::Result<bool> {
crate::internal::bridge::bridge_signing_key_exists(&self.bridge_path, &self.app_name, label)
}
}
#[cfg(target_os = "linux")]
impl EnclaveSigner for BridgeSignerWrapper {
fn sign(&self, label: &str, data: &[u8]) -> crate::internal::core::Result<Vec<u8>> {
crate::internal::bridge::bridge_sign(
&self.bridge_path,
&self.app_name,
label,
data,
self.access_policy,
)
}
}
#[cfg(target_os = "linux")]
#[allow(unsafe_code)]
unsafe impl Send for BridgeSignerWrapper {}
#[cfg(target_os = "linux")]
#[allow(unsafe_code)]
unsafe impl Sync for BridgeSignerWrapper {}
impl std::fmt::Debug for AppSigningBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AppSigningBackend")
.field("kind", &self.kind)
.finish()
}
}
impl AppSigningBackend {
#[allow(clippy::needless_return, unreachable_code)]
pub fn init(mut config: StorageConfig) -> Result<Self> {
config.app_name = crate::internal::core::signing::ensure_safe_app_name(&config.app_name);
#[cfg(target_os = "macos")]
{
return Self::init_macos(&config);
}
#[cfg(target_os = "windows")]
{
return Self::init_windows(&config);
}
#[cfg(target_os = "linux")]
{
if config.force_keyring {
debug!("--keyring flag: forcing software keyring backend for signing");
return Self::init_linux_keyring(&config);
}
if crate::internal::wsl::is_wsl() {
debug!("WSL detected, trying bridge for signing");
return Self::init_wsl(&config);
}
return Self::init_linux(&config);
}
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
{
let _ = config;
Err(StorageError::NotAvailable)
}
}
#[cfg(target_os = "macos")]
fn init_macos(config: &StorageConfig) -> Result<Self> {
let keys_dir = config
.keys_dir
.clone()
.unwrap_or_else(|| crate::internal::core::metadata::keys_dir(&config.app_name));
let mut keychain_config = crate::internal::apple::KeychainConfig::with_keys_dir(
&config.app_name,
keys_dir.clone(),
)
.with_user_presence(config.wrapping_key_user_presence)
.with_cache_ttl(config.wrapping_key_cache_ttl);
if let Some(ref group) = config.keychain_access_group {
keychain_config = keychain_config.with_access_group(group.clone());
}
let signer = crate::internal::apple::SecureEnclaveSigner::with_config(keychain_config);
if !signer.is_available() {
return Err(StorageError::NotAvailable);
}
crate::internal::app_storage::platform::verify_meta_integrity(
&config.app_name,
&keys_dir,
&config.key_label,
)?;
debug!(
"Secure Enclave signing backend ready (app={})",
config.app_name,
);
Ok(Self {
kind: BackendKind::SecureEnclave,
inner: SigningInner::SecureEnclave(signer),
})
}
#[cfg(target_os = "windows")]
fn init_windows(config: &StorageConfig) -> Result<Self> {
let keys_dir = config
.keys_dir
.clone()
.unwrap_or_else(|| crate::internal::core::metadata::keys_dir(&config.app_name));
let signer =
crate::internal::windows::TpmSigner::with_keys_dir(&config.app_name, keys_dir.clone());
if !signer.is_available() {
return Err(StorageError::NotAvailable);
}
crate::internal::app_storage::platform::verify_meta_integrity(
&config.app_name,
&keys_dir,
&config.key_label,
)?;
debug!("TPM signing backend ready (app={})", config.app_name,);
Ok(Self {
kind: BackendKind::Tpm,
inner: SigningInner::Tpm(signer),
})
}
#[cfg(target_os = "linux")]
fn init_linux(config: &StorageConfig) -> Result<Self> {
use super::backend_marker;
let prior = backend_marker::read(&config.app_name).ok().flatten();
#[cfg(all(target_env = "gnu", feature = "linux-tpm"))]
if crate::internal::linux_tpm::is_available() {
let keys_dir = config
.keys_dir
.clone()
.unwrap_or_else(|| crate::internal::core::metadata::keys_dir(&config.app_name));
let signer = crate::internal::linux_tpm::LinuxTpmSigner::with_keys_dir(
&config.app_name,
keys_dir.clone(),
);
crate::internal::app_storage::platform::verify_meta_integrity(
&config.app_name,
&keys_dir,
&config.key_label,
)?;
debug!("Linux TPM signing backend ready (app={})", config.app_name);
drop(backend_marker::write(&config.app_name, BackendKind::Tpm));
return Ok(Self {
kind: BackendKind::Tpm,
inner: SigningInner::LinuxTpm(signer),
});
}
if matches!(prior, Some(BackendKind::Tpm)) {
return Err(StorageError::KeyInitFailed(format!(
"TPM backend was used previously for app {} but is no longer available; \
refusing to silently downgrade to the keyring backend (TPM keys can't be \
used by it). Restore TPM access (check tcsd / tpm2-abrmd / kernel module) \
or, if you are intentionally migrating away from TPM, delete \
{} and regenerate the affected keys.",
config.app_name,
crate::internal::core::metadata::config_dir(&config.app_name)
.join(".backend")
.display()
)));
}
let backend = Self::init_linux_keyring(config)?;
let keys_dir = config
.keys_dir
.clone()
.unwrap_or_else(|| crate::internal::core::metadata::keys_dir(&config.app_name));
crate::internal::app_storage::platform::verify_meta_integrity(
&config.app_name,
&keys_dir,
&config.key_label,
)?;
drop(backend_marker::write(&config.app_name, backend.kind));
Ok(backend)
}
#[cfg(target_os = "linux")]
fn init_wsl(config: &StorageConfig) -> Result<Self> {
let bridge_path = crate::internal::app_storage::platform::find_bridge_executable(
&config.app_name,
&config.extra_bridge_paths,
)
.ok_or(StorageError::NotAvailable)?;
debug!(
"WSL TPM signing bridge found at {} (app={})",
bridge_path.display(),
config.app_name
);
crate::internal::bridge::bridge_signing_key_exists(
&bridge_path,
&config.app_name,
&config.key_label,
)
.map_err(|e| StorageError::KeyInitFailed(e.to_string()))?;
let wrapper = BridgeSignerWrapper {
bridge_path,
app_name: config.app_name.clone(),
key_label: config.key_label.clone(),
access_policy: config.access_policy,
};
Ok(Self {
kind: BackendKind::TpmBridge,
inner: SigningInner::WslBridge(wrapper),
})
}
#[cfg(target_os = "linux")]
fn init_linux_keyring(config: &StorageConfig) -> Result<Self> {
if !crate::internal::keyring::has_keyring_feature() {
return Err(StorageError::NotAvailable);
}
let keys_dir = config
.keys_dir
.clone()
.unwrap_or_else(|| crate::internal::core::metadata::keys_dir(&config.app_name));
let signer =
crate::internal::keyring::SoftwareSigner::with_keys_dir(&config.app_name, keys_dir);
debug!(
"Linux keyring signing backend ready (app={})",
config.app_name
);
Ok(Self {
kind: BackendKind::Keyring,
inner: SigningInner::Software(signer),
})
}
pub fn signer(&self) -> &dyn EnclaveSigner {
match &self.inner {
#[cfg(target_os = "macos")]
SigningInner::SecureEnclave(s) => s,
#[cfg(target_os = "windows")]
SigningInner::Tpm(s) => s,
#[cfg(all(target_os = "linux", target_env = "gnu", feature = "linux-tpm"))]
SigningInner::LinuxTpm(s) => s,
#[cfg(target_os = "linux")]
SigningInner::Software(s) => s,
#[cfg(target_os = "linux")]
SigningInner::WslBridge(s) => s,
}
}
pub fn key_manager(&self) -> &dyn EnclaveKeyManager {
match &self.inner {
#[cfg(target_os = "macos")]
SigningInner::SecureEnclave(s) => s,
#[cfg(target_os = "windows")]
SigningInner::Tpm(s) => s,
#[cfg(all(target_os = "linux", target_env = "gnu", feature = "linux-tpm"))]
SigningInner::LinuxTpm(s) => s,
#[cfg(target_os = "linux")]
SigningInner::Software(s) => s,
#[cfg(target_os = "linux")]
SigningInner::WslBridge(s) => s,
}
}
pub fn backend_kind(&self) -> BackendKind {
self.kind
}
}