All checks were successful
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 3s
The previous commit made the sealed tier true. It did not make it checkable. Below S6 the standard requires no evidence at all — §7.5 says so outright, and `validate_structure` enforces evidence only at S6 (TEE) and S7 (locality) — so `tier = "S2"` is the host's word with a signature around it, and the honest `S0` the last commit produced is equally just the host's word. §7.9 adds an optional `mechanism`: the enforcement runtime (name AND version) plus a commitment to the policy profile applied and the egress allowlist installed. An attestation carrying one is `sandbox.attestation/2`; without one it stays `/1` and canonicalizes to byte-identical JSON, so every attestation sealed before this section — and every published vector — verifies unchanged. That is the same add-field/bump-profile/dispatch shape used for `payload_hash`. What this is careful not to claim: a mechanism is NOT proof of isolation, and both the SPEC text and the type docs say so. A host willing to misstate its tier can misstate its profile; a commitment made by the party under scrutiny cannot bootstrap its own trustworthiness. What changes is the shape of the claim — an unadorned tier label is unfalsifiable, while a profile_id is checkable against a known-good profile a relying party already trusts, and a false one is a specific, attributable lie about a named artifact. A party that needs proof rather than assertion still requires S6+. Design notes: - The commitment length-prefixes each field rather than concatenating them. Bare concatenation is not injective: "seatbelt/14" + "5;deny-net" and "seatbelt/145" + ";deny-net" would collide. C11 asserts they no longer do. - Requiring a mechanism is a *relying-party* check, not a producer obligation. Making it mandatory would retroactively invalidate every honest attestation sealed before §7.9, and a standard does not get to make historical records non-conformant. It lives in `require_mechanism_evidence()`, the same posture §7.5 already takes for isolation proof. - A mechanism at S0 is rejected, not ignored. S0 is unconfined; describing a boundary that does not exist is incoherent rather than merely redundant. - `Executor::mechanism()` defaults to `None` — as with `attained_tier()`, the component that did the work is the one that gets to describe it, and silence beats invention. `run_and_seal` takes it from the executor; the bare `seal_run` primitive keeps its old signature and passes `None`. - `WasmExecutor` is a real producer, so the field is not decorative: it commits to fuel metering, the fuel budget and the memory cap — deliberately NOT `uj_per_million_fuel`, an accounting coefficient that confines nothing and would make two identically-confined runs disagree when someone retunes it. - wasmtime exposes no version constant and Cargo gives no compile-time access to a dependency's resolved version, so `WASMTIME_VERSION` is hand-maintained and a test fails the build if it drifts from Cargo.lock. A stale value would attest confinement to a runtime that did not run. Verified: 194 workspace tests pass, conformance passes with no skips across all 11 classes. C4 attestation.json — a published vector — still passes untouched, which is the backward-compatibility claim holding in practice rather than in argument.
89 lines
7.4 KiB
Markdown
89 lines
7.4 KiB
Markdown
# The attestation (signed claim + replay journal)
|
|
|
|
Normative companion to [`SPEC.md`](../SPEC.md) §7 and §8. RFC 2119 keywords apply.
|
|
|
|
An **attestation** is the Supervisor's signed statement of what a run was: which image ran, at which tier, under which grants, on which hardware, with which energy account, and — where recorded — replayable to a journal root. It is the artifact a third party verifies without trusting the host.
|
|
|
|
## Core
|
|
|
|
```
|
|
Attestation {
|
|
unit_id : ContentId,
|
|
image_id : ContentId,
|
|
grants_id : ContentId,
|
|
tier : Tier, // S0..S7
|
|
devices : [DeviceIdentity],
|
|
receipt_id : ContentId, // the energy receipt for this run (SPEC §6)
|
|
journal_root : Option<Hash>, // BLAKE3 root of the replay journal (SPEC §8)
|
|
outcome : Outcome, // Completed | Failed | Terminated{ breach }
|
|
tee : Option<TeeEvidence>, // present iff tier >= S6 (SPEC §7.6)
|
|
locality : Option<Locality>, // REQUIRED at S7 (SPEC §7.7)
|
|
audit_prev : Option<ContentId>, // REQUIRED at S7: audit-chain head before this run
|
|
mechanism : Option<Mechanism>, // how confinement was established; S1-S5 (SPEC §7.9)
|
|
host_id : String,
|
|
started_at_us : u64,
|
|
finished_at_us: u64,
|
|
}
|
|
|
|
DeviceIdentity { class : DeviceClass, model : String, driver : String }
|
|
|
|
Mechanism { runtime : String, profile_id : ContentId }
|
|
// runtime : enforcement runtime, name AND version ("wasmtime/43.0.2")
|
|
// profile_id : BLAKE3 over the length-prefixed encoding of
|
|
// (runtime, policy profile applied, egress allowlist installed)
|
|
|
|
Outcome = Completed
|
|
| Failed
|
|
| Terminated { breach : Breach } // EnergyBudget | CpuTime | Memory | Tamper | GrantMutation
|
|
|
|
TeeEvidence { kind : SevSnp | Tdx | ArmCca, measurement_hex : String, tcb : String }
|
|
|
|
Locality { region : String, jurisdiction : String }
|
|
```
|
|
|
|
## Confidential and sovereign binding (S6/S7)
|
|
|
|
At **S6**, the host obtains a TEE report from the platform's attestation interface, binds it to the run by placing a value derived from the run identity in the report's `report_data`, and **verifies** it — signature against the VCEK, and measurement / TCB / debug-state / report-data against policy — before sealing the `TeeEvidence` summary (SPEC §7.6). The reference `sandbox-tee` crate implements the full SEV-SNP verification path (parse, policy, ECDSA-P384/SHA-384); report *generation* is hardware-bound (`configfs-tsm` on real confidential hardware), with a tagged software-grounded provider for wiring.
|
|
|
|
An attestation carrying `mechanism` uses the profile tag `sandbox.attestation/2`; one without it remains `sandbox.attestation/1` and canonicalizes to byte-identical JSON, so attestations sealed before §7.9 verify unchanged. A `mechanism` **MUST NOT** be present at S0 (unconfined by definition), and is **not** proof of isolation — a host that will misstate its tier can misstate its profile. It makes the claim checkable against a known-good profile rather than unfalsifiable. A relying party needing proof rather than assertion still requires S6+ (SPEC §7.5, §7.9).
|
|
|
|
At **S7**, the host additionally binds `locality` and links the run into a hash-chained audit log: `audit_prev` is the chain head before this run, and after signing the host appends the attestation's content address, advancing the head (`head' = BLAKE3(head ‖ attestation_id)`). The chain is tamper-evident, so the ordered sequence of sovereign runs is itself attestable (SPEC §7.7).
|
|
|
|
## Device identity (REQUIRED — heterogeneity clause)
|
|
|
|
`devices` **MUST** name, for **each device class the unit used**, its `{ class, model, driver }`. A run that dispatched accelerator work but does not record that accelerator's identity is **not conformant** (SPEC §7.2). The classes in the attestation's `devices` **MUST** match the classes in the receipt's `devices` (SPEC §6.2): the same silicon that was metered is the silicon that is named.
|
|
|
|
## Binding, not duplication
|
|
|
|
The attestation does **not** restate the energy figures. It carries `receipt_id`, the content address of the receipt core, so the receipt is bound to the run by hash. A verifier checks the attestation signature, then checks that the receipt it holds addresses to `receipt_id`. The grant set is bound the same way through `grants_id`, and the executed artifact through `image_id`. None can be swapped without breaking the signature chain.
|
|
|
|
## Signing (Ed25519 over JCS)
|
|
|
|
The attestation signs **Ed25519** (RFC 8032) over the **JCS-canonicalized JSON** (RFC 8785) of its core, with SHA-256 backing the content addresses it references. This is the attestation primitive of the **ARL Sandbox**; a Sandbox attestation and an ARL-S session attestation verify under the same code path, so a measurement run (ARL-S) and a production run (Sandbox) speak one attestation language.
|
|
|
|
The reference `sandbox-attest` crate implements JCS canonicalization and the sign/verify pair, with vectors fixing the canonical bytes and a known-answer signature (vector class **C4**).
|
|
|
|
### Why two crypto forms in one standard
|
|
|
|
The **receipt** seals as JCR-1 (det-CBOR + COSE) because it is a member of the family's receipt class and must verify under shared JCR-1 tooling. The **attestation** signs Ed25519-over-JCS because it is the generalization of ARL-S and must verify under shared ARL-S tooling. They cross-reference by content address (§ "Binding, not duplication"). This is deliberate interoperability with two existing surfaces, not redundancy.
|
|
|
|
## Key custody
|
|
|
|
The Supervisor's signing key **MUST** be unreachable by the unit and by the host's in-boundary code — TPM, HSM, or secure enclave at production tiers (SPEC §7.4). The unit has no path to the telemetry stream or the signing operation. The Supervisor's read of physical telemetry occurs over a channel the unit cannot inspect or influence.
|
|
|
|
## Anti-evasion (recorded in the attestation)
|
|
|
|
The host **MUST** normalize the environment the unit observes — hostname, paths, process tree, time, timezone, locale — to deployment-realistic values, and **MUST** record probing in the attestation: hardware enumeration, root filesystem walks, attempts to read or defeat the energy interface, timing-channel probing. A detected VM-escape, privilege escalation, or out-of-allocation write **MUST** set `outcome = Terminated{ Tamper }` and invalidate the run. Any mutation to the content-addressed grant set, image, or harness configuration during the run **MUST** set `outcome = Terminated{ GrantMutation }` (SPEC §1.2, §7.5).
|
|
|
|
## Deterministic replay (SPEC §8)
|
|
|
|
Where a host records a **replay journal**, it logs the unit's nondeterministic inputs in order:
|
|
|
|
```
|
|
JournalEntry = HostCall { name : String, args_hash : Hash, result_hash : Hash }
|
|
| TimerFired { at_us : u64 }
|
|
| Random { draw_hash : Hash }
|
|
| Checkpoint { state_hash : Hash }
|
|
```
|
|
|
|
The journal's BLAKE3 root **MUST** appear as `journal_root`. Given the recorded inputs and seeds, the host **MUST** be able to reconstruct the run to the identical sequence of `Checkpoint` state hashes. Replay is for **audit**, not re-execution: re-running a stochastic unit produces a new sample; replay reproduces the run that happened. A journal that does not replay to its recorded root invalidates the attestation. The reference `sandbox-attest` crate computes the journal root and verifies a `Consistent | Diverged{at} | Truncated` replay outcome.
|