WAI Extension: Certification
Mirrored from the canonical text at commit 0d3f0d7c ().
Status: Draft. Defines what a certifier publishes when it runs another party’s build against WAI’s conformance corpora, beside conformance-results, whose record
conform-result/1it uses as it is: the runs of a submission held together, every suite the submission declares run whole and every one required to pass (§3); a signed refusal (cert-refusal/1, §4) for a submission that did not pass, whose reason is a run that did not pass (conformance-results §4.1 rule 2,not_conformant) or what stopped a run outside the corpus’s cases: a limit the run reached (limit_exceeded) or a call its confinement forbids (sandbox_violation); and a signed manifest (cert-publication/1, §5) that lists every file of a certifier’s publication by its SHA-256. A relying party checks a whole publication offline, against the corpora it holds (§6): every file the manifest lists is there with its SHA-256 and nothing else is; every record of a certified submission is admitted (conformance-results §4 and §4.1); every refusal and the manifest are signed by the certification key; and the records a refused submission publishes are of runs that did not pass, under a name no list of conforming implementations reads. And a key statement (cert-key/1, §5.4) lets another key of the certifier’s service, one a relying party already holds, state the certification key, which the relying party then holds (§6.4). No new transport. The reference implementation is thecertificationmodule ofwai-rs(featurecertification); the corpus islive-conformance/certification/, and its second implementation islive-conformance/certification_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.A section number in this text is this extension’s unless it names another document.
1. Scope
In scope: a submission and the runs a certifier holds it to (§3), the refusal (§4), the publication and its manifest (§5), the key statement (§5.4), what a relying party checks of a publication and in what order, with the certification key or under a key statement (§6), and the codes it refuses one with (§7).
Out of scope: how a maker submits a build, and the form of what it declares, which are the certifier’s; how a run is confined, what each limit is set to and which calls are forbidden; who may submit, accounts and what a run costs. A publication states what the certifier’s runs observed, and that the certifier states it; it is not a licence, a mark or a warranty, and it makes no other behaviour of a build conform (conformance-results §6).
Relationship to the core: this text builds on conformance-results, a
core item, by reference: its record (§2), its acceptance (§4), its admission (§4.1) and what its
signature shows (§6). It adds no member to conform-result/1 and changes none of its rules. It
registers no capability; its documents’ kinds and its files’ names are wire identifiers.
2. Terms and encodings
- Certifier: a party that runs submissions against WAI’s corpora and publishes what it found. Certification key: the Ed25519 key (RFC 8032) it signs its refusals, its manifests and the records of its runs with. Stating key: another key of the certifier’s service, which states the certification key in a key statement (§5.4).
- Document: a JSON text (RFC 8259) in UTF-8, read as conformance-results §4 step 1 reads a record: one object, no member named twice in any object, no string that is not Unicode text, nested at most 64 deep, and every number an integer from 0 to 2^53 − 1 written in plain decimal (no sign, fraction or exponent). Its canonical form is RFC 8785’s. A refusal and a manifest are written as their canonical form: a file whose bytes are not its own canonical form is not one.
- Digest: SHA-256 (FIPS 180-4) over a file’s bytes, written as 64 lowercase hexadecimal digits. A refusal’s and a manifest’s digest is therefore the SHA-256 of its canonical form.
- Hex, key, signature: lowercase hexadecimal; a key is 64 digits, a signature 128.
- Time:
YYYY-MM-DDTHH:MM:SSZ, as a record’sstarted_atis written (conformance-results §2), so that two times compare as strings. - Pin: what a certifier’s runs were held to, an object of exactly
commit, the commit of WAI’s repository whose corpora the runs read (40 lowercase hexadecimal digits);standard, the version of WAI those corpora are (a string, as a record’sstandard); andtoolchain, the program that ran them (an object of exactlynameandversion, each a string, as a record’stoolchain). - Path: where a file is in a publication: one to eight segments joined by
/, each 1 to 128 bytes, an ASCII letter or digit and then letters, digits,.,_,+and-; at most 512 bytes in all. No segment is.or.., and a path names no folder outside the publication. - Limit name: 1 to 32 bytes, a lowercase ASCII letter and then lowercase letters, digits and
_(wall_ms,cpu_s,memory_mib). - Held suite: what a relying party holds of a suite, as conformance-results §4.1 rules 3 and 4 read it: its kind and name, its corpus digest (conformance-results §2.1), and its cases by name, each with whether its corpus marks it informative (conformance-results §2.3).
3. Submissions and runs
3.1 A submission
A submission is a build and what its maker declared of it. The build is one file, the
artefact, named by its digest (artefact_sha256) and its length in bytes (artefact_bytes).
The declaration is what the maker sent with it, in the form the certifier takes submissions
in, named by its digest (declaration_sha256); WAI reads nothing else of it. It declares:
- a
nameand aversion, each a non-empty string: what the maker calls the build; - a
kind: the kind of implementation it is, one conformance-results §2 names (sink,decoder, …); suites: the suites of that kind it is to be held to, at least one, none twice, in the order the maker gave them.
A submission’s identifier, submission, is the first 16 bytes, as 32 lowercase hexadecimal
digits, of the SHA-256 over the 32 bytes declaration_sha256 names followed by the 32 bytes
artefact_sha256 names. Two submissions of one build with two declarations are two submissions.
3.2 Its runs
A certifier runs each declared suite once, against the corpus of that suite at its pin, and writes
the run’s record, a conform-result/1 (conformance-results §2), in which:
kindis the declared kind andsuitethe suite;standardandtoolchainare the pin’s;corpus.digestis the suite’s corpus digest at the pin, andcasesholds every case of the suite, each once. A case the run did not reach is written withpassfalse, itsgotsaying it was not run;implementation.nameandimplementation.versionare the declared ones, andimplementation.filesnames the artefact by its digest (among the other files conformance-results §2.2 names);keyissuppliedandsigner_pubkeyis the certification key.
So every run’s record is one that conformance-results §4.1 rules 1, 3 and 4 hold for, and rule 2 decides whether the run’s cases passed.
3.3 Stops
A certifier confines every case it runs, and names each limit it holds a case to by a limit name. A run stops at a case when the case reached one of those limits, and is forbidden at a case when the case made a call the confinement does not allow. A certifier writes such a case as failed in the record, and MAY run no further case of that suite; a case it does not run then is not run (§3.2).
3.4 Certified or refused
A run passes when its record is conformant and it neither stopped nor was forbidden at any case, an informative one included: a corpus marks a case informative for what its answer is held to (conformance-results §2.3), not for how a build behaves while answering it. A submission is certified when the run of every suite it declared passes, and refused otherwise. A submission is certified or refused whole: a run that passed does not certify a submission another of whose runs did not, and the records of a refused submission are never published as records that admit it (§5.1).
4. The refusal
4.1 Members
A refusal is a document whose members are exactly these, each of the form given:
| member | form | value |
|---|---|---|
wai | string | cert-refusal/1, the document’s kind and version |
submission | 32 hex digits | the submission’s identifier (§3.1) |
declaration_sha256 | digest | the declaration’s (§3.1) |
artefact_sha256 | digest | the artefact’s (§3.1) |
artefact_bytes | integer | the artefact’s length (§3.1) |
name, version | non-empty string | what the maker declared (§3.1) |
kind | string | the declared kind (§3.1) |
suites | array of strings | the declared suites, at least one, none twice, in their order (§3.1) |
pin | pin | what the runs were held to (§2) |
received_at, refused_at | time | when the certifier took the submission in, and when it refused it, not earlier |
limits | object | at least one member, each a limit name with an integer value: what the confinement held each case to (§3.3) |
runs | array | one run a declared suite, in the order of suites (below) |
reason | object | exactly code (a code of §4.3), suite (a string), case (a string or null) and detail (a string, the certifier’s words, which no check reads) |
signer | key | the certification key |
sig | signature | §4.2 |
A run is an object of exactly these members:
| member | form | value |
|---|---|---|
suite | string | the suite run |
record_sha256 | digest | the digest of the run’s record as the certifier wrote it |
record | path or null | where the publication holds the record: a path for a run that did not pass, null for one that passed (§5.1) |
conformant | boolean | the record’s conformant |
cases, passed, failed | integer | the record’s summary |
stopped | array | one object a case the run stopped at, of exactly case (a string) and limit (a limit name, a member of limits), in the order the run met them |
forbidden | array | one object a case the run was forbidden at, of exactly case and call (each a string: call names what was refused, in the certifier’s words), in the order the run met them |
not_run | integer | how many cases of the suite the run did not reach after a stop (§3.3) |
4.2 Signature and digest
sig is a pure Ed25519 signature (RFC 8032 §5.1) by signer over the 16 bytes
wai:cert-refusal (ASCII) and the byte 0x01, followed by the canonical form of the refusal
without its sig member. It is verified by WAI’s strict rule (SPEC §7
Signatures). A refusal is named by its digest (§2), its record kind 32 in
receipt-ledger §5.2.
4.3 What a refusal states
The deciding run is the first run, in the order of runs, that did not pass (§3.4). Its
reason is derived from it, and a refusal states that reason, with the deciding run’s suite:
| code | the deciding run | case |
|---|---|---|
sandbox_violation | was forbidden at a case | the case of its first forbidden entry |
limit_exceeded | was forbidden at no case, and stopped at one | the case of its first stopped entry |
not_conformant | neither stopped nor was forbidden, and its record is not conformant (conformance-results §4.1 rule 2) | the first case of its record, in the record’s order, that is required and did not pass; null when its record has no such case |
A forbidden call is stated before a limit, and both before a failed answer: a build that made a call its confinement refused is refused for that, whatever else its run did.
4.4 Reading a refusal
A refusal alone is read in this order, and refused at the first rule that fails, with the code the rule names (§7):
- It is a document (§2) (
cert_malformed). - Its
waiiscert-refusal/1(cert_unknown_format). - It has exactly the members of §4.1, each of its form; its
refused_atis not earlier than itsreceived_at; and its bytes are its canonical form (cert_malformed). - Its
sigverifies undersigner(cert_signature_invalid). - Its
submissionis the identifier of §3.1 for itsdeclaration_sha256andartefact_sha256(cert_submission_mismatch). - Its runs are its submission’s (
cert_run_invalid): there is one for each declared suite, in the order ofsuites, each naming its suite; and in each,casesispassedplusfailed; no case is named twice among itsstoppedandforbidden; those entries andnot_runtogether count no more thanfailed;not_runis 0 unless the run stopped or was forbidden at a case; eachlimitis a member oflimits; andrecordisnullexactly when the run passes. - At least one of its runs did not pass (
cert_refusal_unfounded): a refusal of a submission every run of which passed states nothing a certifier observed. - Its
reasonis the deciding run’s (§4.3): itscodeand itssuite, and, forsandbox_violationandlimit_exceeded, itscase(cert_reason_mismatch). Thecaseof anot_conformantreason is held to the deciding run’s record where the record is read (§6.2 step 6).
5. The publication
5.1 Files and names
A publication is a folder of files. Its manifest is the one file at its top whose name
ends .wai-publication.json. Every other file is listed by the manifest, at its path from the
folder, with its digest, and holds one of these, its name ending with the suffix given:
| file | its name ends |
|---|---|
| the record of a run of a certified submission | .wai-conform.json |
| a refusal | .wai-refusal.json |
| the record of a run of a refused submission, a run that did not pass | .wai-refused.json |
A list of conforming implementations that reads the records under a folder by their suffix
.wai-conform.json therefore reads only records that admit their implementation, and never a
refusal or a record of a refused submission. A certifier publishes the record of every run of a
refused submission that did not pass, and of none that passed: a refused submission’s passing
run states only what its refusal does, and its record, under any name, could be read as one that
admits the build.
5.2 The manifest
A manifest is a document whose members are exactly these, each of the form given:
| member | form | value |
|---|---|---|
wai | string | cert-publication/1, the document’s kind and version |
pin | pin | what every run of the publication was held to (§2) |
published_at | time | when the certifier published it |
certified | array | one entry a certified submission (below) |
refused | array | one entry a refused submission (below) |
signer | key | the certification key |
sig | signature | §5.3 |
certified and refused together hold at least one entry. Each entry has submission,
declaration_sha256, artefact_sha256, name, version and kind, each of the form a
refusal gives it (§4.1). A certified entry has besides exactly records: one object a
declared suite, in their order, at least one and no suite twice, each of exactly suite (a
string), file (a path) and sha256 (a digest), the record of that suite’s run. A refused
entry has besides exactly reason, a code of §4.3, its refusal’s; file and sha256, the
refusal’s path and digest; and runs, one object of exactly suite, file and sha256 for
each run of its refusal whose record is a path, in the refusal’s order: the published records of
its runs that did not pass.
5.3 Signature and digest
sig is a pure Ed25519 signature by signer over the 20 bytes wai:cert-publication (ASCII)
and the byte 0x01, followed by the canonical form of the manifest without its sig member,
verified by the strict rule. A manifest is named by its digest (§2), its record kind 33 in
receipt-ledger §5.2.
5.4 A key statement
A relying party that holds no certification key may hold another key of the certifier’s service: a key it already relies on for the service’s other statements. A key statement is that key’s statement of the certification key, a document whose members are exactly these:
| member | form | value |
|---|---|---|
wai | string | cert-key/1, the document’s kind and version |
key | key | the certification key stated |
may_sign | array of strings | the kinds of document the key signs for the service, at least one, none twice: conform-result/1, cert-refusal/1 and cert-publication/1 |
issued_at | time | from when the statement holds |
expires_at | time or null | when it stops holding, later than issued_at; null for no end |
signer | key | the stating key: the service’s key that states it, never key itself |
sig | signature | below |
sig is a pure Ed25519 signature by signer over the 12 bytes wai:cert-key (ASCII) and the
byte 0x01, followed by the canonical form of the statement without its sig member, verified by
the strict rule. A statement is written as its canonical form. A key statement is read as a
refusal’s steps 1 to 4 read a refusal (§4.4), with these members and this signature
(cert_malformed, cert_unknown_format, cert_signature_invalid); a statement whose key is its
signer, or whose expires_at is not later than its issued_at, is cert_malformed. A service
keeps a statement beside a publication, never in its folder (§5.1), under a name ending
.wai-cert-key.json.
A statement shows that the holder of the stating key named the certification key for those kinds
of document, over that span. It does not make the stating key the service’s: a relying party
holds that key, or it holds nothing here. A service that publishes a key log
(jwp-receipts §7.2) MAY list its certification key there instead, with the
authority conform/certify, which this text registers (jwp-receipts §7.5): a relying party that
has anchored the service’s origin confirms the key there (jwp-receipts §7.4), and holds it as a
certification key.
6. Verification
6.1 Inputs
A relying party checks a publication with: its files, each by its path and bytes; the suites it holds (§2), against which it admits records as conformance-results §4.1 does; and, where it holds the certification key, that key, or in its place a key statement and the stating keys it holds (§6.4). It needs nothing else, and fetches nothing.
6.2 Steps
A verifier checks a publication in this order, and refuses it at the first step that fails, with the code the step names and the path of the file it was reading:
- The manifest. The publication has exactly one manifest (§5.1) (
cert_no_manifest); it reads as a refusal’s steps 1 to 4 read a refusal (§4.4), with the manifest’s kind, members (§5.2) and signature (§5.3) (cert_malformed,cert_unknown_format,cert_signature_invalid); and, where the relying party holds the certification key, itssigneris that key (cert_signer_unexpected). - The listing. No path is listed twice, and no submission is (
cert_listed_twice); and each listed path ends with the suffix of what the manifest lists it as (§5.1) (cert_file_misnamed). - The files. Each listed file, in the manifest’s order, is in the publication
(
cert_file_missing) and has the digest the manifest lists (cert_file_digest_mismatch); and no file but the manifest is there that the manifest does not list (cert_file_unlisted), the publication’s files taken in the byte order of their paths. - Each certified submission, in order. Its
submissionis the identifier of §3.1 for its digests (cert_submission_mismatch); and each record it lists, in order, is its run’s record (step 6) and admits it: itsconformantistrue(not_conformant, conformance-results §4.1 rule 2). - Each refused submission, in order. Its refusal reads (§4.4, with its codes); its
signeris the manifest’s (cert_signer_mismatch) and itspinis the manifest’s (cert_pin_mismatch); the entry’ssubmission,declaration_sha256,artefact_sha256,name,version,kindandreasonare the refusal’s, and itsrunsare the refusal’s runs whoserecordis a path, by suite, path andrecord_sha256(cert_entry_mismatch). Then each published record, in order, is its run’s record (step 6) and states its run (cert_run_mismatch): itsconformantand its summary are the run’s, and every case the run stopped or was forbidden at is a case of the record that did not pass. And where the refusal’s reason isnot_conformant, itscaseis the one §4.3 derives from the deciding run’s record (cert_reason_mismatch). - A run’s record, read for a submission and a suite. It is accepted (conformance-results §4,
with the code of the step that refuses it, conformance-results §5); its
keyissuppliedand itssigner_pubkeyis the manifest’ssigner(cert_signer_mismatch); itsstandardandtoolchainare the pin’s (cert_pin_mismatch); itskindis the submission’s, itssuitethe one listed, itsimplementation.nameandimplementation.versionthe submission’s, and one of itsimplementation.fileshas the artefact’s digest (cert_record_mismatch); and its suite is one the relying party holds (suite_unknown), whose corpus digest it names, whose cases it holds each once, and no case of which it marks informative that the held suite does not (corpus_mismatch): conformance-results §4.1 rules 3 and 4, with their codes.
A record a certified submission lists is admitted under all four rules of conformance-results §4.1 (steps 4 and 6). A record a refused submission lists is held to rules 1, 3 and 4 and is a run that did not pass: its refusal’s run states so (§4.4 rule 6), and the record says what the run says (step 5).
6.3 Outcome
A publication that passes every step is accepted, and its outcome is the list of its certified submissions and of its refused submissions, each refused one with its reason’s code. A verifier writes a refusal of it as the code and the path of the file the failing step read: none where the publication has no one manifest; the manifest’s for the rest of steps 1 and 2 and for a certified submission’s identifier (step 4); the listed or unlisted file’s for step 3; a record’s for step 6 and for a record that does not state its run or does not admit its submission; and a refusal’s for the rest of step 5.
6.4 Under a key statement
A relying party that holds a key statement and the stating keys it relies on, in place of the
certification key, checks a publication so. Before §6.2, the statement reads (§5.4, with its
codes) and its signer is one of the stating keys held (cert_key_unknown); a refusal at either
names no file. Then §6.2 runs with the statement’s key as the certification key the relying
party holds, and, after step 1: the statement holds at the manifest’s published_at, not earlier
than its issued_at and earlier than its expires_at where it has one (cert_key_not_in_force);
and its may_sign holds every kind of document the publication’s manifest lists:
cert-publication/1, cert-refusal/1 where it lists a refused submission, and conform-result/1
where it lists a record (cert_key_not_for). Each names the manifest’s path.
7. Reasons
The codes this extension adds, each with where it is raised. Beside them a verifier refuses a
publication with conformance-results §5’s codes, for a record it does not accept (§6.2 step 6),
and with §4.1’s, not_conformant, suite_unknown and corpus_mismatch:
| reason | raised at |
|---|---|
cert_malformed | §4.4 rules 1 and 3; §5.4; §6.2 step 1 |
cert_unknown_format | §4.4 rule 2; §5.4; §6.2 step 1 |
cert_signature_invalid | §4.4 rule 4; §5.4; §6.2 step 1 |
cert_submission_mismatch | §4.4 rule 5; §6.2 step 4 |
cert_run_invalid | §4.4 rule 6 |
cert_refusal_unfounded | §4.4 rule 7 |
cert_reason_mismatch | §4.4 rule 8; §6.2 step 5 |
cert_no_manifest | §6.2 step 1 |
cert_signer_unexpected | §6.2 step 1 |
cert_listed_twice | §6.2 step 2 |
cert_file_misnamed | §6.2 step 2 |
cert_file_missing | §6.2 step 3 |
cert_file_digest_mismatch | §6.2 step 3 |
cert_file_unlisted | §6.2 step 3 |
cert_signer_mismatch | §6.2 steps 5 and 6 |
cert_pin_mismatch | §6.2 steps 5 and 6 |
cert_entry_mismatch | §6.2 step 5 |
cert_run_mismatch | §6.2 step 5 |
cert_record_mismatch | §6.2 step 6 |
cert_key_unknown | §6.4 |
cert_key_not_in_force | §6.4 |
cert_key_not_for | §6.4 |
8. Conformance
live-conformance/certification/ holds the corpus, written
from fixed seeds by wai_certification_vectors gen as vectors.json. Its suites, records,
artefacts and declarations are examples: the suites it holds are made for it and are no corpus of
WAI’s. It has:
- Refusals read alone: one for each code of §4.3, a stop in an informative case among them, and a run forbidden and stopped at once; and one negative control for each rule of §4.4.
- Publications: one of a certified submission and four refused ones, with and without the certification key held; the certified one alone, and one refused one alone; the same publication against a later revision of a suite’s corpus; and one negative control for each step of §6.2, each code of §7 and the conformance-results codes a record is refused with among them.
- Key statements read alone, for every kind and for a span, and one negative control for each rule of §5.4; and the publication checked under a statement (§6.4): accepted for every kind, for a span, and with no refused submission under a statement that names no refusal; and refused for a stating key not held, a statement that does not verify, one of another key, one not yet or no longer holding at the publication’s time, and one that names no kind the publication holds.
A conforming verifier gives every case the outcome, the reason and the file the corpus states.
wai_certification_vectors verify and the test tests/certification.rs check that the file
regenerates byte for byte, that wai-rs gives every case its outcome, that every code of §7
appears, and that §7 lists exactly the reference’s codes and §4.3 exactly its refusal’s.
live-conformance/certification_verify.py is the second implementation, written from this text:
it shares no code with wai-rs; it accepts records with the conformance-results corpus’s second
implementation (conform-result-conformance/conform_result_verify.py), and writes the refusal,
the manifest and §6 itself; it mutates copies of the corpus and requires every copy to fail, and
drops each of its own checks in turn and requires the corpus to catch the weakened verifier.
9. Security and privacy considerations
- What a publication discloses. A refusal names a build by its digest and states the calls and limits that stopped it, in the certifier’s words. A record names its command line and the files it ran, as conformance-results §8 says; a certifier SHOULD run from paths and addresses it is willing to publish.
- Whose word it is. Every refusal, manifest and record of a publication is signed by the
certification key, and a publication whose files are all the manifest lists, unchanged, is the
certifier’s whole statement at its
published_at. It shows the holder of that key stated it; which party that is, a relying party learns as it learns any key (conformance-results §6): from a key it holds already, by a key statement (§5.4), or from the service’s key log. A record signed by another key is refused in a publication, though it may be a genuine record of someone’s run. - A statement is as good as its stating key. A key statement moves trust from the stating key
to the certification key and adds none: a relying party that holds the stating key from a party
it does not rely on gains nothing from the statement. A stating key that is compromised can state
any key; a service that replaces its certification key issues a statement with an
expires_atand a new one for the next key, and a relying party holds a publication to the statement in force at the publication’s time. - Accepted is not trusted. The corpus digest is how a relying party knows which corpus a run
read (conformance-results §2.1); a certifier can run any corpus it likes, and a publication
against another corpus is refused
corpus_mismatchby a relying party that holds WAI’s. - Nothing hidden by omission. A manifest lists every file, and a publication holding one more
is refused; a refused submission’s records that did not pass are listed by its refusal, so a
certifier cannot publish a submission’s passing runs alone. A certifier can still omit a whole
submission from a publication; a maker that holds what it submitted asks for its refusal by its
submission. - Bounded reading. A verifier reads each file once. It MAY refuse a publication larger
than it is prepared to read, as
cert_malformed.