A room with both `[age]` and `[co]` broke: co-performance's clock follows and logs every performer's objects over a relay session of its own, which holds no grant and no token, so age-assurance §5.5 served it nothing rated. - age-assurance §5.5: a session that proves possession of a room key (the authority's or the clock's) is the room's own service, served every object of the room. The proof is a GRANT_PROOF naming no grant (32 bytes of zero) over the session's challenge, checked as live-rooms §10.5.4 checks a proof; one that does not pass refuses the session with that section's reason; one that passes by a key that is no room key gives nothing. Co-performance §8 points to it; §11 and §12 name the reasons and the new vectors. - joule-moq: `grant_proof::NO_GRANT`, `add_room_key_proof`, `room_key_proofs`. - joule-moq-relay: SETUP checks room-key proofs; a session proving a key the relay holds as a room key (an authority's, for its rooms; `[floor]` or `[dvr]` room keys, for every room) has `SessionCtx::service`, and its threshold there is past every age. `ClientOptions::room_keys` presents such proofs. tests/age_room_service.rs: a room rated 18 with the floor held; the clock's and the authority's proofs served every group, the clock's token alone and a non-room key refused `below_threshold`, a forged clock proof and one over another challenge closed UNAUTHORIZED. - joule-moq-room: the clock's session proves both room keys it holds. - wai-rs `age_assurance::room_service`, the corpus case `room_service`, and `age_assurance_verify.py` taught the rule, with three new checks and four mutations, all caught. - e2e: scenario `ageco`, a room with both `[age]` and `[co]`. Co-Authored-By: Claude Opus 5.5 <[email protected]>
79 lines
5.6 KiB
Markdown
79 lines
5.6 KiB
Markdown
# live-conformance/age — age assurance
|
|
|
|
The corpus of [`extensions/age-assurance.md`](../../extensions/age-assurance.md) §12, in one
|
|
file, `vectors.json`:
|
|
|
|
- `principals`: each Ed25519 key, by name (informative);
|
|
- `issuer_keys`: the three issuer keys of the policies, each its `source`, `issuer`,
|
|
`threshold` and `key` (RFC 9578 §6.5's SubjectPublicKeyInfo, hexadecimal), informative;
|
|
- `rfc9578_a2_vector1`: RFC 9578 Appendix A.2's first test vector, as published: `pkI`,
|
|
`token_challenge`, the client's `nonce`, its `blind` *r* and its PSS `salt`, the
|
|
`token_request` and `token_response` of its issuance, and the `token`, in hexadecimal. A
|
|
conforming verifier finds that the token's `challenge_digest` is the SHA-256 of
|
|
`token_challenge`, its `token_key_id` the SHA-256 of `pkI`, and its authenticator *s* a valid
|
|
RSASSA-PSS signature under `pkI` (RFC 9578 §6.4); and that the issuance holds (§6.1 to §6.3
|
|
there, RFC 9474 §4.2 to §4.4): the request's blinded message is (*s*·*r*)^e mod *n*, and the
|
|
response *s*·*r* mod *n*. A client of the issuance protocol reproduces `token_request` and
|
|
`token` from the others;
|
|
- `admission_logs`: admission logs by name, each a list of admissions, `t` (room-clock
|
|
microseconds), `challenges` (the TokenChallenges the relay sent that session, hexadecimal)
|
|
and `token` (hexadecimal);
|
|
- `cases`: each a `name`, an `about`, a `kind` (one of §9.1's), its inputs, and `expect`, what
|
|
a verifier gives it.
|
|
|
|
Every record is a JSON text (§2). A room's records are hexadecimal: a `descriptor`, `grants`
|
|
and `chains` (each chain a list of `WEV1` records), and `key_log` (`null` throughout), as the
|
|
live-rooms corpus gives them.
|
|
|
|
**Cases.** By `kind`:
|
|
|
|
- `ratings`: `claims`, rating claims. `expect`: `accept` with `claims`, each its `link`,
|
|
`signer`, `subject` (the subject's `type`), `age` and `derived` (1 for a claim with inputs, 0
|
|
otherwise); or `refuse` at `["claim", i]`.
|
|
- `admission`: a room `descriptor`, an age `policy`, the session's `challenges`, the nonces
|
|
`redeemed`, a `token`, the age `required` and the binding's `origin`: `null` over MOQT, where
|
|
a challenge's `origin_info` is the room's scope of live-rooms §10.4, or the service's server
|
|
name over HTTP (§5.6), where it is RFC 9577's list of server names. `expect`: `accept` with
|
|
the `issuer`, the `threshold` and the `key_id` the token admits under; or `refuse` at
|
|
`["policy"]` or `["token"]`.
|
|
- `serving`: a room `descriptor`, a `track` of it (`namespace`, a list of its fields, and
|
|
`name`), `claims`, and `requests`, each a `threshold`, a `group` and `held`, the indexes of
|
|
the claims the relay holds when it serves the request, and, for a session that presented a
|
|
proof of a room key (§5.5), its `service`: the `proof` (hexadecimal) and the `challenge` and
|
|
`context` the relay holds for the session. `expect`: `accept` with the `track`'s digest and,
|
|
for each request, the group's `age` and its `outcome`, `serve` (with `service` `true` for the
|
|
room's own service) or `refuse` with the `reason`; or `refuse` at `["claim", i]`.
|
|
- `predicate`: a sink's `anchor` (each key's `issuer`, `threshold` and `key`), its `credentials`
|
|
(tokens, hexadecimal), the `claims` it holds, and `objects`, each a position: a
|
|
`content_hash`, a `track` digest and a `group`, any of them `null`. `expect`: `accept` with the
|
|
sink's `state` and, for each object, its `age` and `verdict`, `present` or `withhold`.
|
|
- `receipt`: a room `descriptor`, an age `policy`, a `receipt`, and `admissions`: the name of an
|
|
admission log, or `null` for none. `expect`: `accept` with the receipt's `link` and its
|
|
`cells` (each `window`, `issuer`, `threshold`, `admitted` and `suppressed`); or `refuse` at
|
|
`["policy"]`, `["admission", i]` or `["receipt"]`.
|
|
- `room`: a room. `expect`: `accept` with the room's own outcome (`room_outcome`, `accept` or
|
|
`gap`), `meets_profile` (1 or 0), each grant's `thresholds` in the order given, and the
|
|
`minor_grants` that took effect, each a grant digest in the merged order; or `refuse` at
|
|
`["room", …]` with the room verifier's own place (`["room", "record", c, i]` for record `i` of
|
|
chain `c`), `["grant", i]` or `["record", c, i]`.
|
|
|
|
A verifier that reaches another outcome, reason, place or statement does not conform.
|
|
|
|
**Provenance.** Generated here: `wai_age_assurance_vectors gen` writes `vectors.json` from
|
|
records authored in code, from fixed seeds. Every Ed25519 key is SHA-256 of a fixed label; every
|
|
time is a constant (the rooms' `t0` is 2026-10-21T00:00:00Z); every content hash, token nonce,
|
|
redemption context and RSASSA-PSS salt is SHA-256 output of a fixed label; and Ed25519 signing is
|
|
deterministic, so the file is the same bytes on every machine. The issuer keys are published test
|
|
keys whose private keys are published with them: RFC 7517 Appendix A.2's RSA key (threshold 13),
|
|
RFC 9500 §2.1's `testRSA2048` (16) and RFC 9578 Appendix A.2's issuer key (18), each written as
|
|
RFC 9578 §6.5's SubjectPublicKeyInfo; the generator signs each token's input with the key
|
|
directly, the signature the blind protocol would also yield. `rfc9578_a2_vector1` is that RFC's
|
|
vector as printed. The challenges over MOQT carry the room's scope of live-rooms §10.4, and the
|
|
refused ones the scopes of the revision before it, or a scope broken one way; those over HTTP
|
|
carry `room.example`, the corpus's service. The rating schemes, their values and the issuers are `.example` names (RFC
|
|
2606) and a `tag:` URI written for the corpus: no scheme here is any classification body's, and
|
|
nothing in the corpus states what any content deserves. No recorded data.
|
|
|
|
```
|
|
python3 -I ../age_assurance_verify.py .
|
|
```
|