Skip to main content

HardwareBoundBackend

Struct HardwareBoundBackend 

Source
pub struct HardwareBoundBackend { /* private fields */ }
Expand description

A KeychainBackend that hardware-binds every blob it stores, degrading to the underlying software envelope when the host has no usable hardware.

§Example

use std::sync::Arc;
use dig_keystore::backend::{BackendKey, FileBackend, KeychainBackend};
use dig_keystore::hardware::{HardwareBoundBackend, HardwarePolicy};

let inner = FileBackend::new("/var/lib/dig/keys");
// No provider available in this build: opens, and says so honestly.
let backend = HardwareBoundBackend::new(inner, None, HardwarePolicy::Optional)?;

// What the HOST can do — the tier a new write would get.
println!("host: {}", backend.tier());

// What protects THIS key — the only answer fit to show a user, because a
// capable host can still hold a keystore that was never wrapped.
let key = BackendKey::new("identity");
let tier = backend.blob_tier(&key)?;
if tier.is_hardware_bound() {
    println!("this key is {tier}");
} else {
    // Never claim protection this key does not have.
    println!("this key is {tier}");
}

Implementations§

Source§

impl HardwareBoundBackend

Source

pub fn new<B: KeychainBackend>( inner: B, provider: Option<Arc<dyn HardwareProvider>>, policy: HardwarePolicy, ) -> Result<Self>

Decorate inner, resolving the protection tier once.

Pass provider = None to store through inner unchanged while reporting DegradeReason::NotRequested.

§Errors

Fails closed rather than silently degrading, per policy:

Source

pub fn with_inner( inner: Arc<dyn KeychainBackend>, provider: Option<Arc<dyn HardwareProvider>>, policy: HardwarePolicy, ) -> Result<Self>

As new, for an already shared backend.

Source

pub fn tier(&self) -> &ProtectionTier

What this host is bound to — the tier every newly written blob gets.

This is a statement about the machine, not about any particular stored key. On a hardware-capable host it reports Hardware even if a given keystore predates hardware binding and is still a bare §3 blob, because a capable host does not retroactively protect bytes already at rest.

Before telling a user that a specific key is hardware-protected, ask blob_tier instead. Rendering “protected by your TPM” from this method would claim copy-resistance that an unwrapped legacy blob does not have.

Source

pub fn blob_tier(&self, key: &BackendKey) -> Result<ProtectionTier>

What protects the key material stored at key, read from the blob itself.

This is the question a UI actually has, and it is not the same as tier: a hardware-capable host can hold a keystore written before hardware binding existed, which is protected by the passphrase envelope alone and does open on another machine. Answering from the stored bytes is what keeps that distinction honest.

The tier reported is the blob’s own, independent of this host: a blob sealed by an Apple Secure Enclave reads as Hardware(MacSecureEnclave) even on Windows. Whether this host can open it is a separate question, answered by read.

§Errors

Both error cases fail closed: a wrapped blob is never reported as software-protected just because this build cannot fully classify it. Guessing in either direction is what this method exists to avoid.

Source

pub fn inner(&self) -> &Arc<dyn KeychainBackend>

The underlying storage, for callers that need it directly.

Source

pub fn unbind(&self, key: &BackendKey) -> Result<ProtectionTier>

Return the key at key to the portable software form, so it opens on a host that no longer has this hardware. Returns the tier the blob is in afterwards.

§Why this exists

Hardware binding makes the trusted component a second required factor. A TPM is cleared by a firmware update, a mainboard swap or a BIOS reset — routine events — and after one the correct passphrase is no longer enough: the sealed blob is unopenable, by design and permanently. unbind is the way back, and it must be taken while the hardware still answers. There is no recovery afterwards; that is what non-exportable custody means.

Unbinding does not expose a secret. What it stores is the AES-256-GCM + Argon2id passphrase envelope that was always the floor (SPEC.md §3) — the same bytes a host with no hardware writes. It gives up cross-machine binding, nothing else.

Nothing is written until the plaintext is in hand, and the result is verified from storage before this reports success: telling a user their seed is portable when it is not is the one failure here with a catastrophic follow-on action, since they may then clear the TPM.

§Errors
Source

pub fn bind(&self, key: &BackendKey) -> Result<ProtectionTier>

Bind the key at key to this host’s hardware, migrating a blob written before hardware binding existed. Returns the tier the blob is in afterwards.

Already-bound blobs are left alone: sealing an envelope inside a second envelope would produce a blob whose unwrap yields another envelope, which nothing can open.

This is the operation that can strand a seed, because it overwrites the only copy with bytes only this hardware can open. So the new blob is read back from storage and reopened through the hardware BEFORE the call reports success, and the previous bytes are restored if it cannot be. See unbind for the way back out.

§Errors

Trait Implementations§

Source§

impl Debug for HardwareBoundBackend

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Redacted: reports the tier (which is not sensitive and is the point of the type) but never the inner store or any key material.

Source§

impl KeychainBackend for HardwareBoundBackend

Source§

fn read(&self, key: &BackendKey) -> Result<Vec<u8>>

Read a blob, unwrapping it when it carries a hardware envelope.

A blob without the envelope prefix is returned untouched, which is what lets every keystore written before this feature — and any future inner format — keep opening (§5.1).

A blob with an envelope that this host cannot open is an error, never the raw envelope bytes: an envelope copied to a machine without the sealing hardware must fail loudly rather than hand back ciphertext that a caller would then try to parse as a keystore.

Source§

fn write(&self, key: &BackendKey, data: &[u8]) -> Result<()>

Write a blob, sealing it into a hardware envelope in the hardware tier and passing the software-sealed bytes straight through otherwise.

Source§

fn delete(&self, key: &BackendKey) -> Result<()>

Remove the blob at key. Implementations should best-effort overwrite the storage before removing so residual disk sectors do not retain the ciphertext.
Source§

fn list(&self, prefix: &str) -> Result<Vec<BackendKey>>

List keys that start with prefix. Order is unspecified.
Source§

fn exists(&self, key: &BackendKey) -> Result<bool>

Whether a blob exists at key. Default impl delegates to read; backends with cheaper existence checks should override.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.