Skip to main content

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/1 it 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 the certification module of wai-rs (feature certification); the corpus is live-conformance/certification/, and its second implementation is live-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

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 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:

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:

memberformvalue
waistringcert-refusal/1, the document’s kind and version
submission32 hex digitsthe submission’s identifier (§3.1)
declaration_sha256digestthe declaration’s (§3.1)
artefact_sha256digestthe artefact’s (§3.1)
artefact_bytesintegerthe artefact’s length (§3.1)
name, versionnon-empty stringwhat the maker declared (§3.1)
kindstringthe declared kind (§3.1)
suitesarray of stringsthe declared suites, at least one, none twice, in their order (§3.1)
pinpinwhat the runs were held to (§2)
received_at, refused_attimewhen the certifier took the submission in, and when it refused it, not earlier
limitsobjectat least one member, each a limit name with an integer value: what the confinement held each case to (§3.3)
runsarrayone run a declared suite, in the order of suites (below)
reasonobjectexactly 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)
signerkeythe certification key
sigsignature§4.2

A run is an object of exactly these members:

memberformvalue
suitestringthe suite run
record_sha256digestthe digest of the run’s record as the certifier wrote it
recordpath or nullwhere the publication holds the record: a path for a run that did not pass, null for one that passed (§5.1)
conformantbooleanthe record’s conformant
cases, passed, failedintegerthe record’s summary
stoppedarrayone 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
forbiddenarrayone 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_runintegerhow 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:

codethe deciding runcase
sandbox_violationwas forbidden at a casethe case of its first forbidden entry
limit_exceededwas forbidden at no case, and stopped at onethe case of its first stopped entry
not_conformantneither 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):

  1. It is a document (§2) (cert_malformed).
  2. Its wai is cert-refusal/1 (cert_unknown_format).
  3. It has exactly the members of §4.1, each of its form; its refused_at is not earlier than its received_at; and its bytes are its canonical form (cert_malformed).
  4. Its sig verifies under signer (cert_signature_invalid).
  5. Its submission is the identifier of §3.1 for its declaration_sha256 and artefact_sha256 (cert_submission_mismatch).
  6. Its runs are its submission’s (cert_run_invalid): there is one for each declared suite, in the order of suites, each naming its suite; and in each, cases is passed plus failed; no case is named twice among its stopped and forbidden; those entries and not_run together count no more than failed; not_run is 0 unless the run stopped or was forbidden at a case; each limit is a member of limits; and record is null exactly when the run passes.
  7. 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.
  8. Its reason is the deciding run’s (§4.3): its code and its suite, and, for sandbox_violation and limit_exceeded, its case (cert_reason_mismatch). The case of a not_conformant reason 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:

fileits 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:

memberformvalue
waistringcert-publication/1, the document’s kind and version
pinpinwhat every run of the publication was held to (§2)
published_attimewhen the certifier published it
certifiedarrayone entry a certified submission (below)
refusedarrayone entry a refused submission (below)
signerkeythe certification key
sigsignature§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:

memberformvalue
waistringcert-key/1, the document’s kind and version
keykeythe certification key stated
may_signarray of stringsthe 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_attimefrom when the statement holds
expires_attime or nullwhen it stops holding, later than issued_at; null for no end
signerkeythe stating key: the service’s key that states it, never key itself
sigsignaturebelow

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:

  1. 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, its signer is that key (cert_signer_unexpected).
  2. 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).
  3. 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.
  4. Each certified submission, in order. Its submission is 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: its conformant is true (not_conformant, conformance-results §4.1 rule 2).
  5. Each refused submission, in order. Its refusal reads (§4.4, with its codes); its signer is the manifest’s (cert_signer_mismatch) and its pin is the manifest’s (cert_pin_mismatch); the entry’s submission, declaration_sha256, artefact_sha256, name, version, kind and reason are the refusal’s, and its runs are the refusal’s runs whose record is a path, by suite, path and record_sha256 (cert_entry_mismatch). Then each published record, in order, is its run’s record (step 6) and states its run (cert_run_mismatch): its conformant and 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 is not_conformant, its case is the one §4.3 derives from the deciding run’s record (cert_reason_mismatch).
  6. 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 key is supplied and its signer_pubkey is the manifest’s signer (cert_signer_mismatch); its standard and toolchain are the pin’s (cert_pin_mismatch); its kind is the submission’s, its suite the one listed, its implementation.name and implementation.version the submission’s, and one of its implementation.files has 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:

reasonraised 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:

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