bitrouter_attestation/types.rs
1//! Normalized result types shared by every
2//! [`ConfidentialVerifier`](crate::ConfidentialVerifier) impl.
3//!
4//! These mirror private-ai-gateway's `UpstreamVerifiedEvent` / `ChannelBinding`
5//! normalization (`src/aci/receipt.rs`), but are produced **client-side** and
6//! carry each provider's *native* integrity proof rather than a re-signed ACI
7//! receipt. See the refactor spec §2.
8
9/// The exact bytes of one request/response exchange to verify (L1.5).
10///
11/// Hashing is over these raw bytes verbatim — including any trailing newlines a
12/// streamed response carries — because the TEE signs `sha256` of the same
13/// bytes. Anything that re-serializes the body (e.g. NEAR's gateway) breaks the
14/// match, so verifiable calls must use NEAR direct-completions (spec §3).
15pub struct ExchangeInput<'a> {
16 pub model: &'a str,
17 /// Exact bytes the client sent.
18 pub request_body: &'a [u8],
19 /// Exact bytes the client received.
20 pub response_body: &'a [u8],
21 /// Chat id taken from the response body's `id` field; selects the signature.
22 pub chat_id: &'a str,
23 pub now_unix: u64,
24}
25
26/// The per-check breakdown of an attestation, mirroring private-ai-gateway's
27/// `AciDcapVerifier` check set (`src/aci/verifier/dcap.rs`). Every field is
28/// surfaced so a caller can see *why* a verdict is (un)verified, gateway-style.
29#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
30pub struct AttestationChecks {
31 /// NVIDIA NRAS verdict PASS, nonce echoed, EAT signature valid.
32 pub gpu_nras_pass: bool,
33 /// Intel signature + collateral + measurements (via `dcap-qvl`).
34 pub dcap_quote_valid: bool,
35 /// `report_data` embeds the attested signing key and the client nonce.
36 pub report_data_binds_key_and_nonce: bool,
37 /// `sha256(compose) == mr_config`.
38 pub compose_matches_mr_config: bool,
39 /// LOAD-BEARING (spec §1.5 cond. 1): `workload_id ∈ accepted_workload_ids`
40 /// OR `image_digest ∈ accepted_image_digests`, under
41 /// `accepted_kms_root_public_keys`. The policy fails to construct if
42 /// unpinned; without this, every other check passes for an attacker-owned
43 /// genuine TEE running a malicious model.
44 pub policy_accepts: bool,
45 /// TD debug-bit off.
46 pub debug_disabled: bool,
47 /// dstack RTMR3 / event-log replay, when the report carries an event log.
48 pub event_log_rtmr_ok: Option<bool>,
49 /// LOAD-BEARING (issue #567): the quote's firmware-measured base registers
50 /// (`MRTD ‖ RTMR0 ‖ RTMR1 ‖ RTMR2`) match a pinned reference bundle. Those
51 /// registers are set before the guest runs and cannot be forged on genuine
52 /// TDX hardware, so without this gate a malicious base image could forge the
53 /// guest-extended RTMR3 labels (`event_log_rtmr_ok`) and pass `policy_accepts`
54 /// while running unauthorized code. `false` fail-closed when the quote never
55 /// verified or its base registers aren't pinned.
56 pub base_measurements_match: bool,
57 /// The platform's Intel DCAP TCB status, surfaced as a claim (e.g.
58 /// `"UpToDate"`, `"OutOfDate"`). `None` when no collateral verification
59 /// produced one. The hard gate is [`Self::tcb_level_acceptable`].
60 pub tcb_status: Option<String>,
61 /// Whether [`Self::tcb_status`] meets the verifier's configured TCB floor
62 /// (default: require `UpToDate`; advisories may be explicitly allow-listed).
63 /// LOAD-BEARING: a signature-valid quote from a genuine TEE running
64 /// **out-of-date** microcode passes every other DCAP check, so without this
65 /// gate a known-vulnerable platform verifies. `false` fail-closed when the
66 /// status is missing or below the floor.
67 pub tcb_level_acceptable: bool,
68}
69
70impl AttestationChecks {
71 /// All-false checks — the fail-closed default for a node whose evidence
72 /// couldn't be gathered or fully evaluated.
73 pub fn failed() -> Self {
74 Self {
75 gpu_nras_pass: false,
76 dcap_quote_valid: false,
77 report_data_binds_key_and_nonce: false,
78 compose_matches_mr_config: false,
79 policy_accepts: false,
80 debug_disabled: false,
81 event_log_rtmr_ok: None,
82 base_measurements_match: false,
83 tcb_status: None,
84 tcb_level_acceptable: false,
85 }
86 }
87
88 /// True iff every mandatory check passed. `tcb_status` is a surfaced claim,
89 /// but `tcb_level_acceptable` (derived from it against the verifier's TCB
90 /// floor) **is** a gate: a stale-but-signature-valid quote must not pass.
91 /// `event_log_rtmr_ok` is **required** to be `Some(true)`: it is the anchor
92 /// that binds the cloud-supplied `info` (and thus `policy_accepts`) to the
93 /// genuine TEE measurement, so a `None` ("not checked") or `Some(false)`
94 /// ("replay/binding failed") verdict must not pass. `base_measurements_match`
95 /// is likewise a hard gate (issue #567): RTMR3 is only trustworthy once the
96 /// firmware-measured base registers it sits above are pinned and matched.
97 pub fn all_pass(&self) -> bool {
98 self.gpu_nras_pass
99 && self.dcap_quote_valid
100 && self.report_data_binds_key_and_nonce
101 && self.compose_matches_mr_config
102 && self.policy_accepts
103 && self.debug_disabled
104 && self.event_log_rtmr_ok == Some(true)
105 && self.base_measurements_match
106 && self.tcb_level_acceptable
107 }
108}
109
110/// L1 verdict: the model endpoint is genuine TEE hardware running the
111/// *legitimate* (policy-pinned) model. Yields the attested signing identity set
112/// that L1.5 binds a chat signature to.
113#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
114pub struct AttestationVerdict {
115 pub model: String,
116 pub verified: bool,
117 /// The attested signing addresses from `model_attestations[]` — a SET,
118 /// because NEAR serves multi-node and caches per node. A chat signature is
119 /// trusted iff it recovers to one of these AND `checks.policy_accepts`
120 /// holds (spec §1.5 cond. 1 & 2).
121 pub attested_addresses: Vec<String>,
122 /// Honest trust-boundary label (gateway convention): `"near-ai-model"` when
123 /// we verified the model quote directly; would be `"near-ai-gateway"` if we
124 /// ever fell back to trusting NEAR's gateway (as `nearai.py` does).
125 pub trust_boundary: String,
126 pub nonce: String,
127 pub checks: AttestationChecks,
128 pub verified_at_unix: u64,
129}
130
131impl AttestationVerdict {
132 /// A fully-failed, fail-closed verdict (spec §1.5 cond. 3) with every check
133 /// false. Used when a fetch is withheld or a sub-check fails — never a
134 /// silent pass.
135 pub fn unverified(model: impl Into<String>, nonce: impl Into<String>, now_unix: u64) -> Self {
136 Self {
137 model: model.into(),
138 verified: false,
139 attested_addresses: Vec::new(),
140 trust_boundary: String::new(),
141 nonce: nonce.into(),
142 checks: AttestationChecks::failed(),
143 verified_at_unix: now_unix,
144 }
145 }
146}
147
148/// Each provider's *native* integrity proof — the portable artifact a third
149/// party could re-check. Mirrors the gateway's evidence/`ChannelBinding`, but
150/// kept provider-native rather than normalized to one receipt shape.
151#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
152pub enum IntegrityProof {
153 /// NEAR's per-chat signature over `{model}:{sha256(req)}:{sha256(resp)}`,
154 /// EIP-191 ECDSA, recoverable to the attested signing address.
155 NearChatSignature {
156 text: String,
157 signature: String,
158 signing_address: String,
159 },
160 /// A real ACI gateway's signed receipt (future `AciGatewayVerifier`).
161 AciReceipt {
162 receipt: serde_json::Value,
163 gateway_attestation: serde_json::Value,
164 },
165}
166
167/// L1.5 result: a specific exchange provably ran in the attested TEE unmodified.
168/// ← gateway's `UpstreamVerifiedEvent`, normalized and client-side.
169#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
170pub struct VerifiedExchange {
171 pub provider: String,
172 pub model: String,
173 /// `sha256(request_body)`, hex.
174 pub request_hash: String,
175 /// `sha256(response_body)`, hex.
176 pub response_hash: String,
177 pub attestation: AttestationVerdict,
178 pub integrity: IntegrityProof,
179 /// `attestation.verified && integrity holds && binds to attested key`.
180 pub verified: bool,
181}
182
183/// Errors a [`ConfidentialVerifier`](crate::ConfidentialVerifier) can return.
184/// Note that a *failed
185/// verification* is not an error — it is a verdict with `verified=false`
186/// (fail-closed). `VerifyError` is reserved for the verifier being unable to
187/// even reach a verdict it can trust (misconfiguration, malformed input).
188#[derive(Debug, thiserror::Error)]
189pub enum VerifyError {
190 /// A network fetch (report, signature, NRAS) failed.
191 #[error("transport error fetching {what}: {source}")]
192 Transport {
193 what: &'static str,
194 #[source]
195 source: Box<dyn std::error::Error + Send + Sync>,
196 },
197 /// A wire payload could not be parsed into the expected shape.
198 #[error("malformed {what}: {detail}")]
199 Malformed { what: &'static str, detail: String },
200 /// The DCAP policy was constructed without the mandatory pins (spec §1.5
201 /// cond. 1). The verifier refuses to run unpinned.
202 #[error("attestation policy misconfigured: {0}")]
203 Policy(String),
204 /// No verifier is registered for the requested provider.
205 #[error("no confidential verifier registered for provider {0:?}")]
206 UnknownProvider(String),
207}