use std::sync::Arc;
use super::envelope::{self, Envelope};
use super::provider::{ContentKey, HardwareProvider};
use super::tier::{DegradeReason, HardwareKind, HardwarePolicy, HardwareProbe, ProtectionTier};
use crate::backend::{BackendKey, KeychainBackend};
use crate::error::{KeystoreError, Result};
pub struct HardwareBoundBackend {
inner: Arc<dyn KeychainBackend>,
provider: Option<Arc<dyn HardwareProvider>>,
tier: ProtectionTier,
}
impl HardwareBoundBackend {
pub fn new<B: KeychainBackend>(
inner: B,
provider: Option<Arc<dyn HardwareProvider>>,
policy: HardwarePolicy,
) -> Result<Self> {
Self::with_inner(Arc::new(inner), provider, policy)
}
pub fn with_inner(
inner: Arc<dyn KeychainBackend>,
provider: Option<Arc<dyn HardwareProvider>>,
policy: HardwarePolicy,
) -> Result<Self> {
let tier = resolve_tier(provider.as_deref(), policy)?;
let provider = if tier.is_hardware_bound() {
provider
} else {
None
};
Ok(Self {
inner,
provider,
tier,
})
}
pub fn tier(&self) -> &ProtectionTier {
&self.tier
}
pub fn blob_tier(&self, key: &BackendKey) -> Result<ProtectionTier> {
let bytes = self.inner.read(key)?;
if !envelope::is_envelope(&bytes) {
return Ok(ProtectionTier::Software(DegradeReason::BlobNotWrapped));
}
let env = envelope::decode(&bytes)?;
match env.hardware_kind() {
Some(kind) => Ok(ProtectionTier::Hardware(kind)),
None => Err(KeystoreError::UnknownHardwareClass {
wire_id: env.hardware_wire_id,
}),
}
}
pub fn inner(&self) -> &Arc<dyn KeychainBackend> {
&self.inner
}
fn wrap_blob(&self, provider: &dyn HardwareProvider, blob: &[u8]) -> Result<Vec<u8>> {
let mut rng = rand_core::OsRng;
let content_key = envelope::random_content_key(&mut rng);
let nonce = envelope::random_nonce(&mut rng);
let wrapped_key = provider.wrap_key(&content_key)?;
envelope::encode(provider.kind(), &content_key, &wrapped_key, &nonce, blob)
}
fn unwrap_blob(&self, provider: &dyn HardwareProvider, bytes: &[u8]) -> Result<Vec<u8>> {
let env = envelope::decode(bytes)?;
require_matching_hardware(&env, provider.kind())?;
let content_key: ContentKey = provider.unwrap_key(&env.wrapped_key)?;
Ok(env.open(&content_key)?.to_vec())
}
}
fn require_matching_hardware(env: &Envelope, ours: HardwareKind) -> Result<()> {
match env.hardware_kind() {
Some(kind) if kind == ours => Ok(()),
Some(kind) => Err(KeystoreError::HardwareKindMismatch {
expected: ours.label(),
found: kind.label(),
}),
None => Err(KeystoreError::UnknownHardwareClass {
wire_id: env.hardware_wire_id,
}),
}
}
fn resolve_tier(
provider: Option<&dyn HardwareProvider>,
policy: HardwarePolicy,
) -> Result<ProtectionTier> {
let Some(provider) = provider else {
return degrade(DegradeReason::NotRequested, policy);
};
match provider.probe() {
HardwareProbe::Absent => degrade(DegradeReason::NoHardwarePresent, policy),
HardwareProbe::Indeterminate { detail } => {
if policy.allows_indeterminate_degrade() {
degrade(DegradeReason::ProbeIndeterminate { detail }, policy)
} else {
Err(KeystoreError::HardwareProbeIndeterminate { detail })
}
}
HardwareProbe::Available(kind) => match verify_hardware(provider, kind) {
Ok(()) => Ok(ProtectionTier::Hardware(kind)),
Err(detail) => degrade(DegradeReason::HardwareUnusable { detail }, policy),
},
}
}
fn verify_hardware(
provider: &dyn HardwareProvider,
probed: HardwareKind,
) -> std::result::Result<(), String> {
if provider.kind() != probed {
return Err(format!(
"provider binds {} but probed {}",
provider.kind().label(),
probed.label()
));
}
if !provider.custody().is_hardware_grade() {
return Err(format!(
"wrapping key custody is {:?}, not NonExportable",
provider.custody()
));
}
let mut rng = rand_core::OsRng;
let probe_key = envelope::random_content_key(&mut rng);
let wrapped = provider
.wrap_key(&probe_key)
.map_err(|e| format!("self-test wrap failed: {e}"))?;
if wrapped.is_empty() {
return Err("self-test wrap produced no wrapped key".to_owned());
}
if wrapped.as_slice() == probe_key.as_slice() {
return Err("self-test wrap returned the content key verbatim".to_owned());
}
let recovered = provider
.unwrap_key(&wrapped)
.map_err(|e| format!("self-test unwrap failed: {e}"))?;
if recovered.as_slice() != probe_key.as_slice() {
return Err("self-test round-trip did not reproduce the key".to_owned());
}
Ok(())
}
fn degrade(reason: DegradeReason, policy: HardwarePolicy) -> Result<ProtectionTier> {
if policy.allows_degrade() {
Ok(ProtectionTier::Software(reason))
} else {
Err(KeystoreError::HardwareRequired { reason })
}
}
impl std::fmt::Debug for HardwareBoundBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HardwareBoundBackend")
.field("tier", &self.tier)
.field("inner", &"<redacted>")
.finish()
}
}
impl KeychainBackend for HardwareBoundBackend {
fn read(&self, key: &BackendKey) -> Result<Vec<u8>> {
let bytes = self.inner.read(key)?;
if !envelope::is_envelope(&bytes) {
return Ok(bytes);
}
match self.provider.as_deref() {
Some(provider) => self.unwrap_blob(provider, &bytes),
None => Err(KeystoreError::NotHardwareBound {
tier: self.tier.to_string(),
}),
}
}
fn write(&self, key: &BackendKey, data: &[u8]) -> Result<()> {
match self.provider.as_deref() {
Some(provider) => {
let sealed = self.wrap_blob(provider, data)?;
self.inner.write(key, &sealed)
}
None => self.inner.write(key, data),
}
}
fn delete(&self, key: &BackendKey) -> Result<()> {
self.inner.delete(key)
}
fn list(&self, prefix: &str) -> Result<Vec<BackendKey>> {
self.inner.list(prefix)
}
fn exists(&self, key: &BackendKey) -> Result<bool> {
self.inner.exists(key)
}
}