Skip to main content

tf_types/
trust_overlay.rs

1//! Trust-level overlays — Rust mirror of
2//! `tools/tf-types-ts/src/core/trust-overlay.ts`. See that file for the
3//! design rationale; this module exists so the daemon, RPC server, and
4//! conformance vectors can produce identical TrustLevel decisions in
5//! both languages.
6
7use serde::{Deserialize, Serialize};
8
9use crate::generated::{ActorIdentity, ProofLevel, TrustLevel};
10
11#[derive(Clone, Debug, Default, Serialize, Deserialize)]
12pub struct PostureContext {
13    #[serde(default)]
14    pub hardware_backed: bool,
15    #[serde(default)]
16    pub attestation_verified: bool,
17    #[serde(default)]
18    pub proof_level_achieved: Option<ProofLevel>,
19    #[serde(default)]
20    pub recent_verification_seconds: Option<u64>,
21    #[serde(default)]
22    pub stale_after_seconds: Option<u64>,
23    #[serde(default)]
24    pub quorum_approvers_at_least: Option<u32>,
25    #[serde(default)]
26    pub untrusted_relay_path: bool,
27    #[serde(default)]
28    pub recently_revoked: bool,
29    #[serde(default)]
30    pub publicly_anchored: bool,
31    #[serde(default)]
32    pub compliance_attested: bool,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36pub struct TrustOverlayResult {
37    pub level: TrustLevel,
38    pub reasons: Vec<String>,
39}
40
41const ORDER: [TrustLevel; 8] = [
42    TrustLevel::T0,
43    TrustLevel::T1,
44    TrustLevel::T2,
45    TrustLevel::T3,
46    TrustLevel::T4,
47    TrustLevel::T5,
48    TrustLevel::T6,
49    TrustLevel::T7,
50];
51
52fn rank(level: &TrustLevel) -> usize {
53    ORDER.iter().position(|t| t == level).unwrap_or(0)
54}
55
56fn level_from_rank(r: usize) -> TrustLevel {
57    let clamped = r.min(ORDER.len() - 1);
58    ORDER[clamped].clone()
59}
60
61pub fn compose_trust_level(
62    identity: &ActorIdentity,
63    posture: &PostureContext,
64) -> TrustOverlayResult {
65    let mut reasons = Vec::new();
66    let base = highest_level(&identity.trust_levels);
67    let mut r = rank(&base);
68    reasons.push(format!("base={}", trust_level_str(&base)));
69
70    if posture.recently_revoked {
71        reasons.push("revoked → T0".into());
72        return TrustOverlayResult {
73            level: TrustLevel::T0,
74            reasons,
75        };
76    }
77    let stale = posture.stale_after_seconds.unwrap_or(86_400);
78    if let Some(seen) = posture.recent_verification_seconds {
79        if seen > stale {
80            reasons.push(format!("stale ({}s > {}s) → T0", seen, stale));
81            return TrustOverlayResult {
82                level: TrustLevel::T0,
83                reasons,
84            };
85        }
86    }
87
88    if posture.hardware_backed && r < rank(&TrustLevel::T4) {
89        r = rank(&TrustLevel::T4);
90        reasons.push("hardware-backed → ≥T4".into());
91    }
92    if posture.attestation_verified && r < rank(&TrustLevel::T4) {
93        r = rank(&TrustLevel::T4);
94        reasons.push("attestation verified → ≥T4".into());
95    }
96    if posture.quorum_approvers_at_least.unwrap_or(0) >= 2 && r < rank(&TrustLevel::T5) {
97        r = rank(&TrustLevel::T5);
98        reasons.push("quorum ≥2 → ≥T5".into());
99    }
100    if posture.publicly_anchored && r < rank(&TrustLevel::T6) {
101        r = rank(&TrustLevel::T6);
102        reasons.push("publicly anchored → ≥T6".into());
103    }
104    if posture.compliance_attested && r < rank(&TrustLevel::T7) {
105        r = rank(&TrustLevel::T7);
106        reasons.push("compliance attestation → T7".into());
107    }
108    if let Some(level) = &posture.proof_level_achieved {
109        if let Some(target) = proof_level_minimum_trust(level) {
110            if rank(&target) > r {
111                r = rank(&target);
112                reasons.push(format!(
113                    "proof level {} → ≥{}",
114                    proof_level_str(level),
115                    trust_level_str(&target)
116                ));
117            }
118        }
119    }
120
121    if posture.untrusted_relay_path && r > 0 {
122        r -= 1;
123        reasons.push("untrusted relay path → -1".into());
124    }
125
126    TrustOverlayResult {
127        level: level_from_rank(r),
128        reasons,
129    }
130}
131
132fn highest_level(levels: &[TrustLevel]) -> TrustLevel {
133    if levels.is_empty() {
134        return TrustLevel::T0;
135    }
136    let mut best = TrustLevel::T0;
137    for l in levels {
138        if rank(l) > rank(&best) {
139            best = l.clone();
140        }
141    }
142    best
143}
144
145fn proof_level_minimum_trust(level: &ProofLevel) -> Option<TrustLevel> {
146    match level {
147        ProofLevel::L0 => Some(TrustLevel::T0),
148        ProofLevel::L1 => Some(TrustLevel::T1),
149        ProofLevel::L2 => Some(TrustLevel::T2),
150        ProofLevel::L3 => Some(TrustLevel::T3),
151        ProofLevel::L4 => Some(TrustLevel::T6),
152        ProofLevel::L5 => Some(TrustLevel::T7),
153    }
154}
155
156fn trust_level_str(level: &TrustLevel) -> &'static str {
157    match level {
158        TrustLevel::T0 => "T0",
159        TrustLevel::T1 => "T1",
160        TrustLevel::T2 => "T2",
161        TrustLevel::T3 => "T3",
162        TrustLevel::T4 => "T4",
163        TrustLevel::T5 => "T5",
164        TrustLevel::T6 => "T6",
165        TrustLevel::T7 => "T7",
166    }
167}
168
169fn proof_level_str(level: &ProofLevel) -> &'static str {
170    match level {
171        ProofLevel::L0 => "L0",
172        ProofLevel::L1 => "L1",
173        ProofLevel::L2 => "L2",
174        ProofLevel::L3 => "L3",
175        ProofLevel::L4 => "L4",
176        ProofLevel::L5 => "L5",
177    }
178}