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
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]>
62 lines
3.1 KiB
Markdown
62 lines
3.1 KiB
Markdown
# EAT interoperability (RATS)
|
|
|
|
The attestation (`spec/attestation.md`, SPEC §7) is the standard's own signed
|
|
form: Ed25519 over the JCS-canonical core. To interoperate with the broader
|
|
remote-attestation ecosystem, a host **SHOULD** also emit the attestation as an
|
|
**EAT** — an Entity Attestation Token (RFC 9711), carried in a CWT (RFC 8392)
|
|
and signed as a COSE_Sign1 token with EdDSA. This places a Sandbox attestation
|
|
on the same footing as the tokens emitted by hardware roots that already speak
|
|
EAT (NVIDIA NRAS device tokens, Arm CCA platform/realm tokens, Veraison
|
|
attestation results), so a generic **RATS** (RFC 9334) verifier can consume it.
|
|
|
|
The EAT is a **view** of the attestation, not a replacement. It is signed by the
|
|
same Supervisor key that signs the native attestation, and it references the
|
|
canonical attestation by content address — so the two cannot disagree.
|
|
|
|
## Claims
|
|
|
|
The CWT claims-set is a CBOR map with integer keys:
|
|
|
|
| Key | Claim | Value |
|
|
|---|---|---|
|
|
| `6` | `iat` | run-finish time, seconds |
|
|
| `10` | `nonce` | the run binding, `BLAKE3(unit_id ‖ receipt_id)` (32 bytes) |
|
|
| `256` | `ueid` | unique entity id, `BLAKE3(host_id)[..16]` |
|
|
| `265` | `eat_profile` | `https://transaction.science/sandbox/eat/v1` |
|
|
| `273` | `measurements` | `[ { 1: tier, 2: tee-launch-measurement? } ]` |
|
|
| `-77001` | *(profile, private)* | `{ 1: attestation_id, 2: tier, 3: receipt_id }` |
|
|
|
|
The `nonce` binds the token to a specific run; the profile claim lets a verifier
|
|
that understands this profile resolve and re-verify the canonical attestation
|
|
(`attestation_id` is its content address). `measurements` carries the tier and,
|
|
when the boundary was hardware-enforced (S6+), the TEE launch measurement.
|
|
|
|
## Signing and verification
|
|
|
|
The claims-set is the COSE_Sign1 **payload**. The protected header is
|
|
`{ 1: -8 }` (`alg = EdDSA`); the unprotected header carries the steward `kid`
|
|
(COSE label `4`). This is the family's JCR-1 COSE_Sign1 primitive
|
|
(`spec/receipts.md`), reused verbatim, so the same tooling verifies receipts and
|
|
EATs.
|
|
|
|
A verifier **MUST**:
|
|
|
|
1. verify the COSE_Sign1 signature against the Supervisor's public key and
|
|
confirm `alg = EdDSA`;
|
|
2. confirm `eat_profile` is the Sandbox profile;
|
|
3. for this profile, resolve the referenced `attestation_id` and re-verify the
|
|
native attestation if it requires the full §7 guarantees (the EAT alone
|
|
conveys the standard claims, not the entire attestation core);
|
|
4. where it holds the run's receipt, confirm `nonce` is that run's binding,
|
|
`BLAKE3(unit_id ‖ receipt_id)`. Without this check any token the
|
|
Supervisor key signed verifies, whichever run it is for.
|
|
|
|
## Reference
|
|
|
|
`sandbox-attest::eat` implements both directions: `Attestation::seal_eat(kid,
|
|
sk)` produces the signed CWT, and `verify_eat(cwt, vk, kid)` checks the
|
|
signature and profile and returns the recovered claims. Every sealed run
|
|
(`SealedRun::eat`) carries the EAT alongside the native attestation, and the
|
|
`sandbox run-wasm` reference run prints and verifies it. The encoding is a
|
|
self-contained deterministic CBOR writer + reader (no external CBOR dependency),
|
|
keeping the token reproducible.
|