Skip to main content

dig_keystore/hardware/
provider.rs

1//! The [`HardwareProvider`] seam — the whole contract a platform binding must
2//! satisfy.
3//!
4//! # Why the seam is this narrow
5//!
6//! A hardware trusted component cannot wrap a keystore blob directly. A TPM 2.0
7//! key reached through CNG is asymmetric (RSA/ECC) and bounded by the modulus
8//! size; a Secure Enclave key is a P-256 key that never leaves the chip. So the
9//! envelope is **hybrid**: a random 32-byte content key encrypts the blob with
10//! AES-256-GCM in this crate, and the hardware only ever wraps *that key*.
11//!
12//! Keeping the trait to two 32-byte operations means each platform binding is a
13//! few dozen lines of FFI instead of a second envelope format, and every
14//! platform shares one audited AEAD path.
15//!
16//! # Where implementations will live
17//!
18//! **No platform provider ships yet** — this release contains the trait and the
19//! envelope, and nothing that binds to real hardware. Passing no provider
20//! resolves [`Software(NotRequested)`](super::DegradeReason::NotRequested).
21//!
22//! This package sets `unsafe_code = "forbid"` as a spec-pinned security property
23//! (`SPEC.md` §12/§13.2, conformance C-15), so raw CNG / Security Framework FFI
24//! cannot live here. Real bindings are therefore *planned* for a separate
25//! `hardware/` workspace member (`dig-keystore-hardware`) that will mirror the
26//! existing `wasm/` split, tracked as **dig_ecosystem #1693**; they will be
27//! injected through this trait.
28
29use zeroize::Zeroizing;
30
31use super::tier::{HardwareKind, HardwareProbe};
32use crate::error::Result;
33
34/// Length of the symmetric content key a provider wraps.
35pub const CONTENT_KEY_LEN: usize = 32;
36
37/// The content key that a hardware provider wraps and unwraps.
38///
39/// Zeroized on drop: this is the only plaintext key material that transits the
40/// provider boundary.
41pub type ContentKey = Zeroizing<[u8; CONTENT_KEY_LEN]>;
42
43/// Where a provider's wrapping key physically lives.
44///
45/// This is the property hardware binding actually buys, so it is reported
46/// explicitly rather than inferred from the provider's name.
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum KeyCustody {
49    /// The wrapping key was generated inside the hardware component and cannot
50    /// be exported from it. Copying the sealed blob to another machine does not
51    /// let an attacker open it.
52    ///
53    /// An implementation MUST NOT report this unless the key was created
54    /// non-exportable *and* an export attempt is refused by the platform.
55    NonExportable,
56
57    /// The wrapping key exists in process memory at some point. Offers no
58    /// cross-machine binding; a provider reporting this MUST NOT be treated as
59    /// a hardware tier.
60    ProcessMemory,
61}
62
63impl KeyCustody {
64    /// Whether this custody level is strong enough to claim a hardware tier.
65    pub const fn is_hardware_grade(self) -> bool {
66        matches!(self, Self::NonExportable)
67    }
68}
69
70/// A binding to one OS hardware trusted component.
71///
72/// Implementations are injected into
73/// [`HardwareBoundBackend`](super::HardwareBoundBackend), which probes and
74/// self-tests them before claiming a hardware tier — an implementation is never
75/// taken at its word.
76pub trait HardwareProvider: Send + Sync + 'static {
77    /// Which hardware class this provider binds to.
78    fn kind(&self) -> HardwareKind;
79
80    /// Inspect the host for usable hardware.
81    ///
82    /// MUST return [`HardwareProbe::Indeterminate`] — never
83    /// [`Absent`](HardwareProbe::Absent) — when the inspection itself fails
84    /// (an error, a timeout, an empty or unintelligible response). "I could not
85    /// tell" and "there is none" are different answers and the caller acts
86    /// differently on each.
87    fn probe(&self) -> HardwareProbe;
88
89    /// Where this provider's wrapping key lives.
90    fn custody(&self) -> KeyCustody;
91
92    /// Encrypt `content_key` to the hardware wrapping key.
93    ///
94    /// The returned bytes are opaque to this crate and are stored verbatim in
95    /// the envelope header.
96    fn wrap_key(&self, content_key: &ContentKey) -> Result<Vec<u8>>;
97
98    /// Decrypt a previously wrapped content key using the hardware key.
99    ///
100    /// MUST fail — never return arbitrary bytes — when `wrapped` was sealed by a
101    /// different hardware key (for instance a blob copied from another machine).
102    /// That failure *is* the cross-machine binding guarantee.
103    fn unwrap_key(&self, wrapped: &[u8]) -> Result<ContentKey>;
104}