1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! Hardware monotonic counter evidence.
//!
//! # Problem
//!
//! In v1, `event_counter: u64` was caller-controlled and non-monotonic.
//! A replay attacker could reuse a captured quote because the counter
//! provided no real freshness guarantee.
//!
//! # Solution
//!
//! [`CounterEvidence`] is an explicit enum with three variants:
//!
//! - `HardwareMonotonic` — a counter backed by TPM NV or equivalent hardware.
//! The verifier can require this variant via policy.
//! - `SoftwareObserved` — a counter from a software source (OS clock, sequence
//! number). Weaker than hardware, but better than nothing.
//! - `Unsupported` — the backend has no counter. The verifier must rely
//! entirely on the nonce for freshness.
//!
//! The policy engine's `RequireHardwareCounter` rule rejects anything that
//! is not `HardwareMonotonic`.
// ── CounterEvidence ───────────────────────────────────────────────────────
/// Monotonic counter evidence from a hardware or software source.
///
/// The verifier uses this to bound the age of an attestation quote when
/// no real-time clock is available, or as an additional freshness signal
/// alongside the nonce.
///
/// # Ordering
///
/// `HardwareMonotonic` > `SoftwareObserved` > `Unsupported` in terms of
/// trust strength. Policy rules can require a minimum trust level.
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum CounterEvidence {
/// A counter backed by hardware (e.g. TPM NV monotonic counter,
/// TPM audit counter, or equivalent).
///
/// The value is guaranteed to be non-decreasing across power cycles
/// (within the hardware's lifetime). The verifier can store the last
/// seen value and reject quotes with a lower counter.
HardwareMonotonic(u64),
/// A counter from a software source (e.g. OS uptime, sequence number).
///
/// Weaker than `HardwareMonotonic` — an attacker with OS access could
/// manipulate it. Acceptable for low-security deployments or when
/// hardware counters are unavailable.
SoftwareObserved(u64),
/// The backend does not support any counter.
///
/// Freshness relies entirely on the nonce. The verifier policy must
/// explicitly permit this via `allow_no_counter`.
Unsupported,
}
impl CounterEvidence {
/// Returns the counter value if available (either variant with a value).
#[must_use]
pub fn value(self) -> Option<u64> {
match self {
Self::HardwareMonotonic(v) | Self::SoftwareObserved(v) => Some(v),
Self::Unsupported => None,
}
}
/// Returns `true` if this counter is hardware-backed.
#[must_use]
pub fn is_hardware_backed(self) -> bool {
matches!(self, Self::HardwareMonotonic(_))
}
/// Returns `true` if any counter value is available.
#[must_use]
pub fn is_available(self) -> bool {
!matches!(self, Self::Unsupported)
}
/// Returns the trust level as an ordinal (higher = more trusted).
///
/// Used by policy rules that require a minimum trust level.
#[must_use]
pub fn trust_level(self) -> u8 {
match self {
Self::HardwareMonotonic(_) => 2,
Self::SoftwareObserved(_) => 1,
Self::Unsupported => 0,
}
}
}
// ── Tests ─────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hardware_monotonic_is_hardware_backed() {
assert!(CounterEvidence::HardwareMonotonic(42).is_hardware_backed());
assert!(!CounterEvidence::SoftwareObserved(42).is_hardware_backed());
assert!(!CounterEvidence::Unsupported.is_hardware_backed());
}
#[test]
fn value_extraction() {
assert_eq!(CounterEvidence::HardwareMonotonic(99).value(), Some(99));
assert_eq!(CounterEvidence::SoftwareObserved(7).value(), Some(7));
assert_eq!(CounterEvidence::Unsupported.value(), None);
}
#[test]
fn trust_level_ordering() {
assert!(
CounterEvidence::HardwareMonotonic(0).trust_level()
> CounterEvidence::SoftwareObserved(0).trust_level()
);
assert!(
CounterEvidence::SoftwareObserved(0).trust_level()
> CounterEvidence::Unsupported.trust_level()
);
}
#[test]
fn is_available() {
assert!(CounterEvidence::HardwareMonotonic(1).is_available());
assert!(CounterEvidence::SoftwareObserved(1).is_available());
assert!(!CounterEvidence::Unsupported.is_available());
}
}