use std::sync::Arc;
use super::double::{FakeDevice, WrapBehaviour};
use super::envelope::{self, ENVELOPE_MAGIC};
use super::provider::{HardwareProvider, KeyCustody};
use super::tier::{DegradeReason, HardwareKind, HardwarePolicy, ProtectionTier};
use super::HardwareBoundBackend;
use crate::backend::{BackendKey, KeychainBackend, MemoryBackend};
use crate::error::KeystoreError;
const ALL_KINDS: [HardwareKind; 3] = [
HardwareKind::WindowsTpm20,
HardwareKind::MacSecureEnclave,
HardwareKind::LinuxTpm20,
];
fn v1_shaped_blob(seed: u8) -> Vec<u8> {
let mut blob = Vec::with_capacity(105);
blob.extend_from_slice(b"DIGVK1");
blob.extend_from_slice(&1u16.to_be_bytes()); blob.extend_from_slice(&1u16.to_be_bytes()); blob.push(0x01); blob.extend_from_slice(&65536u32.to_be_bytes());
blob.extend_from_slice(&3u32.to_be_bytes());
blob.push(4); blob.push(0x01); blob.extend_from_slice(&[seed; 16]); blob.extend_from_slice(&[seed ^ 0xFF; 12]); blob.extend_from_slice(&48u32.to_be_bytes()); blob.extend_from_slice(&[seed; 48]); let crc = crc32fast::hash(&blob);
blob.extend_from_slice(&crc.to_be_bytes());
assert_eq!(blob.len(), 105, "fixture must match the real v1 file size");
blob
}
fn backend_with(
device: Option<FakeDevice>,
policy: HardwarePolicy,
) -> crate::Result<HardwareBoundBackend> {
let provider = device.map(|d| Arc::new(d) as Arc<dyn HardwareProvider>);
HardwareBoundBackend::new(MemoryBackend::default(), provider, policy)
}
#[test]
fn tier_reports_the_exact_hardware_kind_per_platform() {
for kind in ALL_KINDS {
let be = backend_with(
Some(FakeDevice::working(kind, 1)),
HardwarePolicy::Preferred,
)
.expect("working device must resolve");
assert_eq!(
*be.tier(),
ProtectionTier::Hardware(kind),
"tier must name {kind} exactly"
);
assert_eq!(be.tier().hardware_kind(), Some(kind));
assert!(be.tier().is_hardware_bound());
assert!(
be.tier().degrade_reason().is_none(),
"a hardware tier has no degrade reason"
);
}
}
#[test]
fn software_tiers_are_distinguishable_and_carry_the_right_reason() {
let cases: Vec<(FakeDevice, DegradeReason)> = vec![
(
FakeDevice::absent(HardwareKind::LinuxTpm20),
DegradeReason::NoHardwarePresent,
),
(
FakeDevice::indeterminate(HardwareKind::LinuxTpm20, "tpm2 socket timed out"),
DegradeReason::ProbeIndeterminate {
detail: "tpm2 socket timed out".to_owned(),
},
),
];
for (device, expected) in cases {
let be = backend_with(Some(device), HardwarePolicy::Optional).expect("optional opens");
assert_eq!(*be.tier(), ProtectionTier::Software(expected.clone()));
assert!(
!be.tier().is_hardware_bound(),
"a degraded tier must never read as hardware-bound"
);
assert_eq!(be.tier().degrade_reason(), Some(&expected));
}
let be = backend_with(None, HardwarePolicy::Optional).expect("no provider opens");
assert_eq!(
*be.tier(),
ProtectionTier::Software(DegradeReason::NotRequested)
);
}
#[test]
fn a_claimed_hardware_tier_is_refuted_when_the_claim_is_not_backed_by_behaviour() {
let kind = HardwareKind::WindowsTpm20;
let liars = [
FakeDevice::working(kind, 1).with_behaviour(WrapBehaviour::FailWrap),
FakeDevice::working(kind, 1).with_behaviour(WrapBehaviour::FailUnwrap),
FakeDevice::working(kind, 1).with_behaviour(WrapBehaviour::Passthrough),
FakeDevice::working(kind, 1).with_behaviour(WrapBehaviour::WrongKeyOnUnwrap),
FakeDevice::working(kind, 1).with_custody(KeyCustody::ProcessMemory),
FakeDevice::working(kind, 1).with_kind(HardwareKind::LinuxTpm20),
];
for (i, liar) in liars.into_iter().enumerate() {
let be = backend_with(Some(liar), HardwarePolicy::Optional)
.unwrap_or_else(|e| panic!("case {i}: optional policy must still open: {e}"));
assert!(
!be.tier().is_hardware_bound(),
"case {i}: an unbacked hardware claim must not be reported as hardware-bound, got {:?}",
be.tier()
);
assert!(
matches!(
be.tier().degrade_reason(),
Some(DegradeReason::HardwareUnusable { .. })
),
"case {i}: expected HardwareUnusable, got {:?}",
be.tier().degrade_reason()
);
}
let honest =
backend_with(Some(FakeDevice::working(kind, 1)), HardwarePolicy::Optional).unwrap();
assert_eq!(*honest.tier(), ProtectionTier::Hardware(kind));
}
#[test]
fn indeterminate_probe_fails_closed_while_confident_absence_degrades() {
let kind = HardwareKind::LinuxTpm20;
let err = backend_with(
Some(FakeDevice::indeterminate(
kind,
"no /dev/tpmrm0 and tpm2 probe errored",
)),
HardwarePolicy::Preferred,
)
.expect_err("an indeterminate probe must fail closed under the default policy");
match err {
KeystoreError::HardwareProbeIndeterminate { detail } => {
assert!(
detail.contains("tpm2 probe errored"),
"detail preserved: {detail}"
);
}
other => panic!("expected HardwareProbeIndeterminate, got {other:?}"),
}
let be = backend_with(Some(FakeDevice::absent(kind)), HardwarePolicy::Preferred)
.expect("a confident absence must still open under the default policy");
assert_eq!(
*be.tier(),
ProtectionTier::Software(DegradeReason::NoHardwarePresent)
);
}
#[test]
fn required_policy_refuses_every_non_hardware_outcome_with_its_reason() {
let kind = HardwareKind::MacSecureEnclave;
let absent = backend_with(Some(FakeDevice::absent(kind)), HardwarePolicy::Required)
.expect_err("Required must refuse an absent component");
assert!(matches!(
absent,
KeystoreError::HardwareRequired {
reason: DegradeReason::NoHardwarePresent
}
));
let none = backend_with(None, HardwarePolicy::Required)
.expect_err("Required must refuse a missing provider");
assert!(matches!(
none,
KeystoreError::HardwareRequired {
reason: DegradeReason::NotRequested
}
));
let unusable = backend_with(
Some(FakeDevice::working(kind, 1).with_behaviour(WrapBehaviour::FailWrap)),
HardwarePolicy::Required,
)
.expect_err("Required must refuse unusable hardware");
assert!(matches!(
unusable,
KeystoreError::HardwareRequired {
reason: DegradeReason::HardwareUnusable { .. }
}
));
let indeterminate = backend_with(
Some(FakeDevice::indeterminate(
kind,
"SecItemCopyMatching errored",
)),
HardwarePolicy::Required,
)
.expect_err("Required must refuse an undeterminable component");
assert!(matches!(
indeterminate,
KeystoreError::HardwareProbeIndeterminate { .. }
));
let ok = backend_with(Some(FakeDevice::working(kind, 1)), HardwarePolicy::Required)
.expect("Required must accept working hardware");
assert_eq!(*ok.tier(), ProtectionTier::Hardware(kind));
}
#[test]
fn no_hardware_still_unlocks_and_stores_the_software_envelope_unchanged() {
let inner = Arc::new(MemoryBackend::default());
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::absent(HardwareKind::LinuxTpm20))),
HardwarePolicy::Preferred,
)
.expect("degrade must open");
let key = BackendKey::new("identity");
let blob = v1_shaped_blob(0x5A);
be.write(&key, &blob).unwrap();
assert_eq!(be.read(&key).unwrap(), blob, "round-trip must be exact");
assert_eq!(
inner.read(&key).unwrap(),
blob,
"the software tier must store the sealed blob verbatim, not re-wrap it"
);
assert!(
!envelope::is_envelope(&inner.read(&key).unwrap()),
"no hardware envelope may be written when no hardware is bound"
);
assert!(!be.tier().is_hardware_bound());
}
#[test]
fn a_blob_sealed_by_one_device_cannot_be_opened_by_another() {
let kind = HardwareKind::WindowsTpm20;
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let blob = v1_shaped_blob(0x11);
let machine_a = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(kind, 0xA1))),
HardwarePolicy::Required,
)
.unwrap();
machine_a.write(&key, &blob).unwrap();
assert_eq!(machine_a.read(&key).unwrap(), blob);
let machine_b = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(kind, 0xB2))),
HardwarePolicy::Required,
)
.unwrap();
let err = machine_b
.read(&key)
.expect_err("another machine's hardware must not open this blob");
assert!(
matches!(err, KeystoreError::HardwareUnwrapFailed { .. }),
"expected HardwareUnwrapFailed, got {err:?}"
);
}
#[test]
fn the_stored_envelope_never_contains_the_content_key() {
let kind = HardwareKind::WindowsTpm20;
let inner = Arc::new(MemoryBackend::default());
let device = Arc::new(FakeDevice::working(kind, 0xC3));
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(device.clone()),
HardwarePolicy::Required,
)
.unwrap();
let key = BackendKey::new("identity");
let blob = v1_shaped_blob(0x22);
be.write(&key, &blob).unwrap();
let stored = inner.read(&key).unwrap();
assert!(envelope::is_envelope(&stored));
assert_eq!(&stored[..6], ENVELOPE_MAGIC);
let content_key = device
.last_wrapped_content_key()
.expect("the device must have been asked to wrap a content key");
assert!(
!contains_subslice(&stored, &content_key),
"the plaintext content key must not appear anywhere in the stored envelope"
);
let wrapped_len = u16::from_be_bytes([stored[22], stored[23]]) as usize;
let wrapped = &stored[envelope::HEADER_FIXED..envelope::HEADER_FIXED + wrapped_len];
assert_ne!(
wrapped,
&content_key[..],
"the wrapped key must not be the content key verbatim"
);
assert!(
!contains_subslice(&stored, &blob),
"the sealed inner blob must not appear verbatim in the envelope"
);
}
fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool {
needle.len() <= haystack.len() && haystack.windows(needle.len()).any(|w| w == needle)
}
#[test]
fn an_envelope_cannot_be_opened_on_a_host_with_no_hardware() {
let kind = HardwareKind::LinuxTpm20;
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let sealed_on_hardware = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(kind, 0xD4))),
HardwarePolicy::Required,
)
.unwrap();
sealed_on_hardware
.write(&key, &v1_shaped_blob(0x33))
.unwrap();
let no_hardware = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::absent(kind))),
HardwarePolicy::Preferred,
)
.unwrap();
let err = no_hardware
.read(&key)
.expect_err("a hardware envelope must not open without hardware");
match err {
KeystoreError::NotHardwareBound { tier } => {
assert!(
tier.contains("software-wrapped"),
"the error must say what tier this host actually has: {tier}"
);
}
other => panic!("expected NotHardwareBound, got {other:?}"),
}
}
#[test]
fn an_envelope_from_a_different_hardware_class_is_refused_distinctly() {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let mac = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(
HardwareKind::MacSecureEnclave,
1,
))),
HardwarePolicy::Required,
)
.unwrap();
mac.write(&key, &v1_shaped_blob(0x44)).unwrap();
let windows = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(HardwareKind::WindowsTpm20, 1))),
HardwarePolicy::Required,
)
.unwrap();
let err = windows.read(&key).expect_err("cross-class must be refused");
match err {
KeystoreError::HardwareKindMismatch { expected, found } => {
assert_eq!(expected, HardwareKind::WindowsTpm20.label());
assert_eq!(found, HardwareKind::MacSecureEnclave.label());
}
other => panic!("expected HardwareKindMismatch, got {other:?}"),
}
}
#[test]
fn a_pre_existing_v1_blob_reads_unchanged_in_the_hardware_tier() {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("legacy");
let legacy = v1_shaped_blob(0x77);
inner.write(&key, &legacy).unwrap();
let be = HardwareBoundBackend::with_inner(
inner,
Some(Arc::new(FakeDevice::working(HardwareKind::WindowsTpm20, 9))),
HardwarePolicy::Required,
)
.unwrap();
assert!(be.tier().is_hardware_bound());
assert_eq!(
be.read(&key).unwrap(),
legacy,
"a pre-existing v1 blob must read back byte-identically"
);
assert_eq!(
be.blob_tier(&key).unwrap(),
ProtectionTier::Software(DegradeReason::BlobNotWrapped)
);
}
#[test]
fn blob_tier_distinguishes_an_unwrapped_key_from_a_wrapped_one_on_the_same_host() {
let inner = Arc::new(MemoryBackend::default());
let legacy_key = BackendKey::new("legacy");
let bound_key = BackendKey::new("bound");
inner.write(&legacy_key, &v1_shaped_blob(0xA0)).unwrap();
let be = HardwareBoundBackend::with_inner(
inner,
Some(Arc::new(FakeDevice::working(HardwareKind::WindowsTpm20, 3))),
HardwarePolicy::Required,
)
.unwrap();
be.write(&bound_key, &v1_shaped_blob(0xB0)).unwrap();
assert_eq!(
*be.tier(),
ProtectionTier::Hardware(HardwareKind::WindowsTpm20)
);
let legacy_tier = be.blob_tier(&legacy_key).unwrap();
assert!(
!legacy_tier.is_hardware_bound(),
"an unwrapped legacy blob must not report as hardware-bound, got {legacy_tier:?}"
);
assert_eq!(
legacy_tier.degrade_reason(),
Some(&DegradeReason::BlobNotWrapped)
);
assert_ne!(
&legacy_tier,
be.tier(),
"host tier and blob tier must be able to disagree"
);
assert_eq!(
be.blob_tier(&bound_key).unwrap(),
ProtectionTier::Hardware(HardwareKind::WindowsTpm20)
);
}
#[test]
fn blob_tier_names_the_sealing_class_not_the_host_class() {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("from_mac");
let mac = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(
HardwareKind::MacSecureEnclave,
7,
))),
HardwarePolicy::Required,
)
.unwrap();
mac.write(&key, &v1_shaped_blob(0xC0)).unwrap();
let windows = HardwareBoundBackend::with_inner(
inner,
Some(Arc::new(FakeDevice::working(HardwareKind::WindowsTpm20, 7))),
HardwarePolicy::Required,
)
.unwrap();
assert_eq!(
windows.blob_tier(&key).unwrap(),
ProtectionTier::Hardware(HardwareKind::MacSecureEnclave),
"the blob's own sealing class must be reported, not the host's"
);
}
#[test]
fn blob_tier_fails_closed_on_a_blob_it_cannot_classify() {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(HardwareKind::WindowsTpm20, 4))),
HardwarePolicy::Required,
)
.unwrap();
be.write(&key, &v1_shaped_blob(0xD0)).unwrap();
let good = inner.read(&key).unwrap();
let mut future = good.clone();
future[8] = 0x7F;
reseal_crc(&mut future);
inner.write(&key, &future).unwrap();
match be.blob_tier(&key) {
Err(KeystoreError::UnknownHardwareClass { wire_id }) => assert_eq!(wire_id, 0x7F),
other => panic!("expected UnknownHardwareClass, got {other:?}"),
}
let mut corrupt = good.clone();
corrupt[30] ^= 0xFF;
inner.write(&key, &corrupt).unwrap();
assert!(
be.blob_tier(&key).is_err(),
"a corrupt envelope must not be classified as software-protected"
);
inner.write(&key, &good).unwrap();
assert!(be.blob_tier(&key).unwrap().is_hardware_bound());
}
#[test]
fn a_malformed_envelope_is_distinct_from_a_hardware_refusal() {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let sealing = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(
HardwareKind::WindowsTpm20,
0x51,
))),
HardwarePolicy::Required,
)
.unwrap();
sealing.write(&key, &v1_shaped_blob(0xE0)).unwrap();
let good = inner.read(&key).unwrap();
let mut no_key = good.clone();
let w = u16::from_be_bytes([good[22], good[23]]) as usize;
let p = u32::from_be_bytes(good[24..28].try_into().unwrap()) as usize;
no_key[22..24].copy_from_slice(&0u16.to_be_bytes());
no_key[24..28].copy_from_slice(&((p + w) as u32).to_be_bytes());
reseal_crc(&mut no_key);
inner.write(&key, &no_key).unwrap();
assert!(
matches!(
sealing.read(&key),
Err(KeystoreError::MalformedEnvelope { .. })
),
"a malformed envelope must not be reported as a hardware refusal"
);
inner.write(&key, &good).unwrap();
let other_machine = HardwareBoundBackend::with_inner(
inner,
Some(Arc::new(FakeDevice::working(
HardwareKind::WindowsTpm20,
0x62,
))),
HardwarePolicy::Required,
)
.unwrap();
assert!(
matches!(
other_machine.read(&key),
Err(KeystoreError::HardwareUnwrapFailed { .. })
),
"a foreign device must still report a hardware refusal"
);
}
fn reseal_crc(bytes: &mut [u8]) {
let body = bytes.len() - 4;
let crc = crc32fast::hash(&bytes[..body]);
bytes[body..].copy_from_slice(&crc.to_be_bytes());
}
#[test]
fn the_device_double_refuses_malformed_input_rather_than_inventing_a_key() {
let honest = FakeDevice::working(HardwareKind::LinuxTpm20, 1);
assert!(matches!(
honest.unwrap_key(&[0u8; 4]),
Err(KeystoreError::HardwareUnwrapFailed { .. })
));
assert!(matches!(
honest.unwrap_key(&[9u8; 20]),
Err(KeystoreError::HardwareUnwrapFailed { .. })
));
let recall = FakeDevice::working(HardwareKind::LinuxTpm20, 1)
.with_behaviour(WrapBehaviour::EmptyWrapWithRecall);
assert!(recall.last_wrapped_content_key().is_none());
assert!(matches!(
recall.unwrap_key(&[]),
Err(KeystoreError::HardwareUnwrapFailed { .. })
));
let passthrough =
FakeDevice::working(HardwareKind::LinuxTpm20, 1).with_behaviour(WrapBehaviour::Passthrough);
assert!(matches!(
passthrough.unwrap_key(b"not a key"),
Err(KeystoreError::HardwareUnwrapFailed { .. })
));
let key = envelope::random_content_key(&mut rand_core::OsRng);
let wrapped = honest.wrap_key(&key).unwrap();
assert_eq!(
honest.unwrap_key(&wrapped).unwrap().as_slice(),
key.as_slice()
);
assert_eq!(honest.last_wrapped_content_key().unwrap(), *key);
}
fn good_envelope() -> Vec<u8> {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(
HardwareKind::LinuxTpm20,
0x33,
))),
HardwarePolicy::Required,
)
.unwrap();
be.write(&key, &v1_shaped_blob(0x5C)).unwrap();
inner.read(&key).unwrap()
}
#[test]
fn an_envelope_whose_declared_lengths_disagree_with_its_size_is_rejected() {
let good = good_envelope();
let wrapped_len = u16::from_be_bytes([good[22], good[23]]);
let payload_len = u32::from_be_bytes(good[24..28].try_into().unwrap());
let mut under = good.clone();
under[22..24].copy_from_slice(&(wrapped_len - 1).to_be_bytes());
reseal_crc(&mut under);
match envelope::decode_for_test(&under) {
Err(KeystoreError::Truncated { claimed, available }) => {
assert_eq!(available, good.len());
assert_eq!(claimed, good.len() - 1);
}
other => panic!("under-declared envelope must be Truncated, got {other:?}"),
}
let mut over = good.clone();
over[24..28].copy_from_slice(&(payload_len + 1_000).to_be_bytes());
reseal_crc(&mut over);
match envelope::decode_for_test(&over) {
Err(KeystoreError::Truncated { claimed, available }) => {
assert_eq!(available, good.len());
assert_eq!(claimed, good.len() + 1_000);
}
other => panic!("over-declared envelope must be Truncated, got {other:?}"),
}
assert!(envelope::decode_for_test(&good).is_ok());
}
#[test]
fn an_envelope_whose_payload_cannot_hold_a_tag_is_rejected() {
let good = good_envelope();
let wrapped_len = u16::from_be_bytes([good[22], good[23]]) as usize;
const SHORT_PAYLOAD: u32 = 15;
let total = envelope::HEADER_FIXED + wrapped_len + SHORT_PAYLOAD as usize + 4;
let mut short = good[..total].to_vec();
short[24..28].copy_from_slice(&SHORT_PAYLOAD.to_be_bytes());
reseal_crc(&mut short);
match envelope::decode_for_test(&short) {
Err(KeystoreError::Truncated { claimed, .. }) => {
assert_eq!(claimed, SHORT_PAYLOAD as usize);
}
other => panic!("a sub-tag payload must be Truncated, got {other:?}"),
}
const TAG_ONLY: u32 = 16;
let total = envelope::HEADER_FIXED + wrapped_len + TAG_ONLY as usize + 4;
let mut at_bound = good[..total].to_vec();
at_bound[24..28].copy_from_slice(&TAG_ONLY.to_be_bytes());
reseal_crc(&mut at_bound);
assert!(
!matches!(
envelope::decode_for_test(&at_bound),
Err(KeystoreError::Truncated { .. })
),
"a payload of exactly TAG_SIZE must pass the structural length rules"
);
}
#[test]
fn an_unknown_envelope_version_is_rejected_with_the_version_it_saw() {
let mut future = good_envelope();
future[6..8].copy_from_slice(&0x0002u16.to_be_bytes());
reseal_crc(&mut future);
match envelope::decode_for_test(&future) {
Err(KeystoreError::UnsupportedFormat { found }) => assert_eq!(found, 0x0002),
other => panic!("an unknown envelope version must be rejected, got {other:?}"),
}
}
#[test]
fn an_unknown_envelope_cipher_id_is_rejected_with_the_id_it_saw() {
let mut other_cipher = good_envelope();
other_cipher[9] = 0x02;
reseal_crc(&mut other_cipher);
match envelope::decode_for_test(&other_cipher) {
Err(KeystoreError::UnsupportedCipher(id)) => assert_eq!(id, 0x02),
other => panic!("an unknown cipher id must be rejected, got {other:?}"),
}
}
#[test]
fn decode_rejects_a_foreign_magic_without_relying_on_its_caller() {
let mut foreign = good_envelope();
foreign[..6].copy_from_slice(b"DIGZZ9");
reseal_crc(&mut foreign);
match envelope::decode_for_test(&foreign) {
Err(KeystoreError::UnknownMagic { saw }) => assert_eq!(&saw, b"DIGZZ9"),
other => panic!("decode must check the magic itself, got {other:?}"),
}
}
#[test]
fn the_self_test_rejects_a_provider_that_wraps_to_nothing() {
let kind = HardwareKind::WindowsTpm20;
let liar = FakeDevice::working(kind, 1).with_behaviour(WrapBehaviour::EmptyWrapWithRecall);
let be = backend_with(Some(liar), HardwarePolicy::Optional).expect("optional opens");
assert!(
!be.tier().is_hardware_bound(),
"a provider that wraps to nothing must not reach a hardware tier, got {:?}",
be.tier()
);
assert!(matches!(
be.tier().degrade_reason(),
Some(DegradeReason::HardwareUnusable { .. })
));
assert!(matches!(
backend_with(
Some(FakeDevice::working(kind, 1).with_behaviour(WrapBehaviour::EmptyWrapWithRecall)),
HardwarePolicy::Required
),
Err(KeystoreError::HardwareRequired {
reason: DegradeReason::HardwareUnusable { .. }
})
));
}
#[test]
fn every_non_envelope_prefix_passes_through_including_unknown_ones() {
for magic in [
&b"DIGVK1"[..],
&b"DIGLW1"[..],
&b"DIGOP1"[..],
&b"DIGXX9"[..], &b"short"[..], &b""[..], ] {
assert!(
!envelope::is_envelope(magic),
"{magic:?} must not be treated as a hardware envelope"
);
}
assert!(envelope::is_envelope(ENVELOPE_MAGIC));
assert!(!envelope::is_envelope(b"DIGHW"));
assert!(envelope::is_envelope(b"DIGHW1trailing"));
}
#[test]
fn editing_the_header_breaks_authentication_not_just_the_crc() {
let kind = HardwareKind::LinuxTpm20;
let inner = Arc::new(MemoryBackend::default());
let device = Arc::new(FakeDevice::working(kind, 5));
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(device.clone()),
HardwarePolicy::Required,
)
.unwrap();
let key = BackendKey::new("identity");
be.write(&key, &v1_shaped_blob(0x66)).unwrap();
let mut stored = inner.read(&key).unwrap();
assert_eq!(stored[8], HardwareKind::LinuxTpm20.wire_id());
stored[8] = HardwareKind::WindowsTpm20.wire_id();
let body_len = stored.len() - 4;
let crc = crc32fast::hash(&stored[..body_len]);
stored[body_len..].copy_from_slice(&crc.to_be_bytes());
inner.write(&key, &stored).unwrap();
let relabelled = HardwareBoundBackend::with_inner(
inner,
Some(Arc::new(FakeDevice::working(HardwareKind::WindowsTpm20, 5))),
HardwarePolicy::Required,
)
.unwrap();
assert!(
matches!(relabelled.read(&key), Err(KeystoreError::DecryptFailed)),
"a relabelled header must fail the AES-GCM tag"
);
}
#[test]
fn the_codec_rejects_truncated_corrupt_and_empty_key_envelopes() {
let kind = HardwareKind::LinuxTpm20;
let inner = Arc::new(MemoryBackend::default());
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(kind, 5))),
HardwarePolicy::Required,
)
.unwrap();
let key = BackendKey::new("identity");
be.write(&key, &v1_shaped_blob(0x88)).unwrap();
let good = inner.read(&key).unwrap();
const FLOOR: usize = 48;
for cut in [0usize, 6, 27, FLOOR - 1] {
let err = envelope::decode_for_test(&good[..cut]).expect_err("below floor must fail");
assert!(
matches!(err, KeystoreError::Truncated { available, .. } if available == cut),
"cut {cut} is below the floor and must be Truncated, got {err:?}"
);
}
for cut in [FLOOR, good.len() - 1] {
let err = envelope::decode_for_test(&good[..cut]).expect_err("short blob must fail");
assert!(
matches!(err, KeystoreError::CrcMismatch { .. }),
"cut {cut} clears the floor, so the CRC must be what objects, got {err:?}"
);
}
let mut corrupt = good.clone();
let last_payload = corrupt.len() - 5;
corrupt[last_payload] ^= 0xFF;
assert!(matches!(
envelope::decode_for_test(&corrupt),
Err(KeystoreError::CrcMismatch { .. })
));
let mut no_key = good.clone();
let old_wrapped_len = u16::from_be_bytes([good[22], good[23]]) as usize;
let old_payload_len = u32::from_be_bytes(good[24..28].try_into().unwrap()) as usize;
assert!(
old_wrapped_len > 0,
"the control envelope must have a wrapped key"
);
no_key[22..24].copy_from_slice(&0u16.to_be_bytes());
no_key[24..28].copy_from_slice(&((old_payload_len + old_wrapped_len) as u32).to_be_bytes());
let body_len = no_key.len() - 4;
let crc = crc32fast::hash(&no_key[..body_len]);
no_key[body_len..].copy_from_slice(&crc.to_be_bytes());
let err = envelope::decode_for_test(&no_key).expect_err("empty wrapped key must fail");
assert!(
matches!(err, KeystoreError::MalformedEnvelope { .. }),
"an envelope declaring no wrapped key is MALFORMED, not a hardware refusal, got {err:?}"
);
assert!(envelope::decode_for_test(&good).is_ok());
}
#[test]
fn hardware_kind_wire_ids_are_stable_distinct_and_strict() {
let mut seen = Vec::new();
for kind in ALL_KINDS {
let id = kind.wire_id();
assert!(!seen.contains(&id), "duplicate wire id {id:#04x}");
seen.push(id);
assert_eq!(HardwareKind::from_wire_id(id), Some(kind));
}
assert_eq!(HardwareKind::WindowsTpm20.wire_id(), 0x01);
assert_eq!(HardwareKind::MacSecureEnclave.wire_id(), 0x02);
assert_eq!(HardwareKind::LinuxTpm20.wire_id(), 0x03);
assert_eq!(HardwareKind::from_wire_id(0x00), None);
assert_eq!(HardwareKind::from_wire_id(0xFF), None);
}
#[test]
fn debug_redacts_storage_and_display_distinguishes_tiers() {
let hw = backend_with(
Some(FakeDevice::working(HardwareKind::WindowsTpm20, 1)),
HardwarePolicy::Required,
)
.unwrap();
let rendered = format!("{hw:?}");
assert!(rendered.contains("<redacted>"));
assert!(rendered.contains("Hardware"));
let sw = backend_with(
Some(FakeDevice::absent(HardwareKind::WindowsTpm20)),
HardwarePolicy::Preferred,
)
.unwrap();
assert!(hw.tier().to_string().contains("hardware-bound"));
assert!(sw.tier().to_string().contains("software-wrapped"));
assert_ne!(hw.tier().to_string(), sw.tier().to_string());
}
#[test]
fn enumeration_and_deletion_delegate_in_both_tiers() {
for device in [
FakeDevice::working(HardwareKind::WindowsTpm20, 1),
FakeDevice::absent(HardwareKind::WindowsTpm20),
] {
let be = backend_with(Some(device), HardwarePolicy::Optional).unwrap();
let a = BackendKey::new("wallet/a");
let b = BackendKey::new("other/b");
be.write(&a, &v1_shaped_blob(1)).unwrap();
be.write(&b, &v1_shaped_blob(2)).unwrap();
assert!(be.exists(&a).unwrap());
let listed: Vec<String> = be
.list("wallet/")
.unwrap()
.into_iter()
.map(|k| k.0)
.collect();
assert_eq!(listed, vec!["wallet/a".to_owned()]);
be.delete(&a).unwrap();
assert!(!be.exists(&a).unwrap());
assert!(be.exists(&b).unwrap());
}
}
#[test]
fn unbind_returns_the_blob_to_a_form_a_machine_without_the_hardware_can_open() {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let blob = v1_shaped_blob(0x33);
let bound = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(HardwareKind::WindowsTpm20, 7))),
HardwarePolicy::Required,
)
.unwrap();
bound.write(&key, &blob).unwrap();
assert!(
envelope::is_envelope(&inner.read(&key).unwrap()),
"precondition: the blob really is hardware-bound"
);
let tier = bound
.unbind(&key)
.expect("unbind while the hardware answers");
assert_eq!(
tier,
ProtectionTier::Software(DegradeReason::BlobNotWrapped)
);
let stranded = HardwareBoundBackend::with_inner(inner.clone(), None, HardwarePolicy::Optional)
.expect("a host with no hardware still opens");
assert_eq!(
stranded.read(&key).unwrap(),
blob,
"after unbind the sealed keystore must open without the hardware"
);
assert_eq!(
stranded.blob_tier(&key).unwrap(),
ProtectionTier::Software(DegradeReason::BlobNotWrapped),
"and it reports the protection it actually has, not the one it had"
);
}
#[test]
fn bind_migrates_a_legacy_blob_up_and_is_idempotent() {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let blob = v1_shaped_blob(0x44);
inner.write(&key, &blob).unwrap();
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(HardwareKind::LinuxTpm20, 9))),
HardwarePolicy::Required,
)
.unwrap();
assert_eq!(
be.blob_tier(&key).unwrap(),
ProtectionTier::Software(DegradeReason::BlobNotWrapped),
"precondition: a capable host does not retroactively protect old bytes"
);
let tier = be.bind(&key).expect("migrate up");
assert_eq!(tier, ProtectionTier::Hardware(HardwareKind::LinuxTpm20));
assert_eq!(be.read(&key).unwrap(), blob, "and it still opens here");
let again = be
.bind(&key)
.expect("binding an already-bound blob is a no-op");
assert_eq!(again, ProtectionTier::Hardware(HardwareKind::LinuxTpm20));
assert_eq!(
be.read(&key).unwrap(),
blob,
"one unwrap must still reach the keystore, not a second envelope"
);
}
#[test]
fn a_bind_that_cannot_be_reopened_restores_the_previous_bytes() {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let blob = v1_shaped_blob(0x55);
inner.write(&key, &blob).unwrap();
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(
FakeDevice::working(HardwareKind::WindowsTpm20, 3).failing_unwrap_after(1),
)),
HardwarePolicy::Required,
)
.expect("the device is honest at construction");
let err = be
.bind(&key)
.expect_err("a seal that cannot be reopened must not be committed");
assert!(
matches!(err, KeystoreError::HardwareUnwrapFailed { .. }),
"it reports the hardware failing to reopen its own seal: {err}"
);
assert_eq!(
inner.read(&key).unwrap(),
blob,
"the previous, openable bytes are restored — bind is all-or-nothing"
);
}
#[test]
fn unbind_leaves_the_blob_intact_when_the_hardware_can_no_longer_open_it() {
let inner = Arc::new(MemoryBackend::default());
let key = BackendKey::new("identity");
let blob = v1_shaped_blob(0x66);
let device = FakeDevice::working(HardwareKind::MacSecureEnclave, 0x5E);
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(device.clone())),
HardwarePolicy::Required,
)
.unwrap();
be.write(&key, &blob).unwrap();
let sealed = inner.read(&key).unwrap();
device.rotate_device_key(0x99);
let err = be
.unbind(&key)
.expect_err("the cleared hardware cannot open its own envelope");
assert!(matches!(err, KeystoreError::HardwareUnwrapFailed { .. }));
assert_eq!(
inner.read(&key).unwrap(),
sealed,
"a failed unbind must not disturb the stored bytes"
);
}
#[test]
fn unbind_refuses_to_report_success_when_the_store_kept_the_envelope() {
struct WriteDroppingStore(MemoryBackend);
impl KeychainBackend for WriteDroppingStore {
fn read(&self, key: &BackendKey) -> crate::Result<Vec<u8>> {
self.0.read(key)
}
fn write(&self, _key: &BackendKey, _data: &[u8]) -> crate::Result<()> {
Ok(()) }
fn delete(&self, key: &BackendKey) -> crate::Result<()> {
self.0.delete(key)
}
fn list(&self, prefix: &str) -> crate::Result<Vec<BackendKey>> {
self.0.list(prefix)
}
fn exists(&self, key: &BackendKey) -> crate::Result<bool> {
self.0.exists(key)
}
}
let key = BackendKey::new("identity");
let blob = v1_shaped_blob(0x77);
let device = FakeDevice::working(HardwareKind::LinuxTpm20, 0x21);
let staging = Arc::new(MemoryBackend::default());
let sealer = HardwareBoundBackend::with_inner(
staging.clone(),
Some(Arc::new(device.clone())),
HardwarePolicy::Required,
)
.unwrap();
sealer.write(&key, &blob).unwrap();
let sealed = staging.read(&key).unwrap();
let real = MemoryBackend::default();
real.write(&key, &sealed).unwrap();
let be = HardwareBoundBackend::with_inner(
Arc::new(WriteDroppingStore(real)),
Some(Arc::new(device)),
HardwarePolicy::Required,
)
.unwrap();
let err = be
.unbind(&key)
.expect_err("a store that kept the envelope must not read as unbound");
assert!(
err.to_string().contains("still hardware-bound"),
"the error says the blob is STILL bound, so the user does not retire the \
trusted component on the strength of it: {err}"
);
}
#[cfg(feature = "os-keychain")]
#[test]
fn hardware_envelope_round_trips_through_the_os_keychain_backend() {
use crate::backend::os_keychain::test_support::fake_backend;
let be = HardwareBoundBackend::new(
fake_backend(),
Some(Arc::new(FakeDevice::working(
HardwareKind::WindowsTpm20,
0x5A,
))),
HardwarePolicy::Required,
)
.unwrap();
let key = BackendKey::new("identity");
let mut payload = b"DIGOP1".to_vec();
payload.extend_from_slice(&[0x77; 64]);
be.write(&key, &payload)
.expect("a hardware-bound write must be storable in the OS credential store");
assert_eq!(be.read(&key).unwrap(), payload);
}
#[cfg(feature = "os-keychain")]
#[test]
fn unbind_returns_a_non_container_payload_through_the_os_keychain_backend() {
use crate::backend::os_keychain::test_support::fake_backend;
let inner: Arc<dyn KeychainBackend> = Arc::new(fake_backend());
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(FakeDevice::working(
HardwareKind::WindowsTpm20,
0x5A,
))),
HardwarePolicy::Required,
)
.unwrap();
let key = BackendKey::new("identity");
let payload = b"an-opaque-secret-with-no-dig-magic".to_vec();
be.write(&key, &payload).unwrap();
assert!(
envelope::is_envelope(&inner.read(&key).unwrap()),
"the hardware tier stores an envelope"
);
let tier = be
.unbind(&key)
.expect("unbind must be able to put the unwrapped original back");
assert_eq!(
tier,
ProtectionTier::Software(DegradeReason::BlobNotWrapped)
);
assert_eq!(
inner.read(&key).unwrap(),
payload,
"the original bytes are back in the store, byte-identical"
);
}
#[cfg(feature = "os-keychain")]
#[test]
fn a_failed_bind_restores_a_legacy_blob_through_the_os_keychain_backend() {
use crate::backend::os_keychain::test_support::fake_backend_seeded;
let legacy = b"written-by-v0.6.1".to_vec();
let inner: Arc<dyn KeychainBackend> = Arc::new(fake_backend_seeded(&[("identity", &legacy)]));
let key = BackendKey::new("identity");
let be = HardwareBoundBackend::with_inner(
inner.clone(),
Some(Arc::new(
FakeDevice::working(HardwareKind::WindowsTpm20, 3).failing_unwrap_after(1),
)),
HardwarePolicy::Required,
)
.expect("the device is honest at construction");
let err = be
.bind(&key)
.expect_err("a seal that cannot be reopened must not be committed");
assert!(
matches!(err, KeystoreError::HardwareUnwrapFailed { .. }),
"it reports the hardware failing to reopen its own seal: {err}"
);
assert_eq!(
inner.read(&key).unwrap(),
legacy,
"the legacy, openable bytes are restored — never left as an unopenable envelope"
);
}