Skip to main content

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}