open-standards/sandbox/spec/attestation.md
Transaction Science df918bf256
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 5s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 50s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m53s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m12s
standards workspaces build + test / proof-rs (proof-conformance, proof/proof-rs, proof-rs, linux, true) (pull_request) Successful in 1m37s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m36s
standards workspaces build + test / joulecontract-rs (joulecontract/joulecontract-rs, joulecontract-rs, linux-arm64, true) (pull_request) Successful in 1m16s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 3m32s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m13s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m33s
sandbox: one report_data binding for a run, a relying-party challenge, and evidence a third party can verify
A confidential run's TEE report bound BLAKE3(unit) ‖ BLAKE3(receipt), with
no domain tag. The outcome sat only under the Supervisor's signature, the
only freshness input was a host-chosen nonce (every call site passed
[0; 8]), and the report was dropped once the host had checked it, so no
one else could. verify_eat compared no nonce, so any token the Supervisor
key signed verified, whichever run it was for.

sandbox-runtime (SPEC §7.6):
- run_report_data is one preimage: BLAKE3-XOF-64 in derive-key mode
  ("sandbox/1 run report_data v1") over length-prefixed unit_id,
  receipt_id and JCS(outcome), then 0x01 ‖ challenge, or 0x00 when there
  is none. The receipt binds the metered energy, so the report vouches for
  the joules through it. It replaces tee_report_data.
- TierRequest.challenge carries the relying party's 32 bytes into
  run_and_seal. seal_run_with_mechanism takes a challenge; seal_run and its
  callers are unchanged.
- The sealed TeeEvidence carries report_hex (the report itself) and
  challenge_hex.
- verify_run_evidence lets a party other than the host check:
  - the report's signature and policy;
  - that its report_data binds the attestation's unit, receipt and
    outcome and the verifier's OWN challenge, never the recorded one;
  - that the summary is the report's.
  Evidence without its report is refused.

sandbox-attest:
- Outcome::core is shared by the attestation core and the preimage.
- TeeEvidence.report_hex and challenge_hex are omitted when absent, so
  evidence sealed before them keeps its bytes.
- run_nonce is exposed.
- verify_eat takes expect_nonce and refuses a token whose nonce is not
  that run's. The CLI, MCP and build paths now pass the run's nonce. The
  MCP verify_eat tool takes an optional expected_nonce_hex and reports
  nonce_checked.

SPEC §7.6 gives the binding, the challenge and the third-party check;
§7.8, spec/eat.md and spec/attestation.md are updated to match.

Also: an orphaned doc comment in sandbox-conformance's vectors.rs that
clippy refused (it predates this change).

Tests:
- Binding and challenge.
- An unchallenged report is never taken for a challenged one.
- The preimage separates its inputs, including a length-delimitation
  collision.
- A requested challenge reaches the report through run_and_seal.
- An EAT is checked against the run the verifier holds.
Mutation testing: 12 mutants, all killed. They cover:
- the challenge, its presence tag, the outcome, the receipt and the
  length prefixes;
- the verifier trusting the recorded challenge, the summary check, and the
  report and challenge being carried;
- the seal and run_and_seal dropping the challenge;
- the EAT nonce check.

Verified on the build host:
- sandbox-rs: 421 tests (--locked); every conformance class passes.
- Clippy -D warnings clean on sandbox-runtime, -attest, -tee, -build,
  -mcp, -cli and -conformance.

Not in this change: running it on SEV-SNP hardware (a billable cloud cell),
a challenge slot in jcp-wire, and TDX on the run path, which still
verifies SEV-SNP only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 07:41:40 -04:00

90 lines
7.5 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,
report_hex : String?, challenge_hex : String? } // omitted when absent
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 through the report's `report_data` (one preimage over the unit, the receipt, the outcome and the relying party's challenge, SPEC §7.6), 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.