HCERT library requirements
95 requirements, one cat-harness req: node each. Every link below is an edge in the graph:
library sections, external schemas, references and owner directives a requirement references; the broader
requirement it is derived from; the smart-trust FHIR Requirements it satisfies; the skills that realize it; the WIT items and CDDL rules whose comments cite it; and the open issues
(beans) that affect it.
- GEN Scope, architecture and language strategy · 14
- SEL Step 1: select payload type · 5
- PAY Step 2: assemble payload · 9
- CWT Step 3: CWT claim set · 6
- CBR Step 4: CBOR · 4
- COS Step 5: COSE_Sign1 · 6
- KID Key identifier · 3
- ZIP Step 6: compression · 2
- B45 Step 7: Base45 · 2
- HC1 Step 8: context identifier · 1
- QR Step 9: QR code · 2
- VER Verification · 10
- TRU Trust list · 4
- VHL Verifiable Health Link · 10
- ISS Issuance · 2
- SEC Security · 2
- NFR Non-functional · 2
- TST Conformance testing · 11
GEN · Scope, architecture and language strategy
STT-GEN-001 · Issuance and verification of the full HCERT QR pipeline
SHALL scope derived kotlin rust open in explorer · node
The library SHALL implement issuance (steps 1 to 9) and verification (QR Code Generation section 11) of HCERT QR codes.
Engineering consequence of QR Code Generation, 1. The pipeline at a glance. QR Code Generation, 1. The pipeline at a glance: “Steps 3 to 9 never vary. A verifier runs the same list upwards.”.
References
Satisfies (smart-trust)
- IssuerVDHC-generate Generate Verifiable Digital Health Certificate source ↗
- IssuerVDHC-sign Sign Verifiable Digital Health Certificate source ↗
- UtilizeVDHC-validate-hash Compute Hash source ↗
Realized by skills
- hcert-assemble-payload Step 2: build and validate the payload against its logical model (DVCMin, or a VHL payload), keeping unknown elements.
- hcert-build-cwt Step 3: wrap the payload in a CWT claim set (iss, iat, exp, hcert -260) inside the DSC's validity.
- hcert-compress Step 6: ZLIB/DEFLATE after signing, and bounded inflation.
- hcert-encode-base45 Step 7: RFC 9285 Base45, decoded strictly.
- hcert-encode-cbor Step 4: serialise as RFC 8949 deterministic CBOR, and decode untrusted CBOR within limits.
- hcert-prefix-context Steps 8 and 9: the HC1: context identifier, giving a QR-ready string the application renders with its own QR encoder.
- hcert-select-payload Step 1: choose the logical model and hcert subclaim for a use case, from the registry the IG defines.
- hcert-sign-cose Step 5: sign as COSE_Sign1 (ES256 or PS256) in two phases, the private key staying with its holder.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Verified by tests
Test plans
Open issues
STT-GEN-002 · Every step callable on its own
SHALL architecture derived kotlin rust open in explorer · node
Each pipeline step SHALL be exposed as a separate public operation (payload codec, CBOR, COSE sign/parse, ZLIB, Base45, HC1 prefix) as well as through the end-to-end issuer and verifier, so that integrators can substitute or inspect any stage.
Engineering consequence of QR Code Generation, 1. The pipeline at a glance; QR Code Generation, Generating a QR Code. QR Code Generation, Generating a QR Code: “The first step below is therefore a selection step, and everything after it is mechanical.”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- every step is its own call and they compose to a verifiable hc1 STT-GEN-002 every step is its own call and they compose to a verifiable hc1
- every step is callable on its own, and the steps compose to the pipeline STT-GEN-002 every step is callable on its own, and the steps compose to the pipeline
Test plans
STT-GEN-003 · Payload-agnostic core
SHALL architecture derived kotlin rust open in explorer · node
The modules for steps 3 to 9 SHALL NOT depend on any payload logical model. Payloads SHALL be plugged in through a PayloadCodec registered against a subclaim.
Engineering consequence of QR Code Generation, Generating a QR Code. QR Code Generation, Generating a QR Code: “The container, the signature and the transport encoding are the same for every GDHCN trust domain ... What changes from one use case to the next is only the payload”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
Test plans
STT-GEN-004 · Kotlin first, for JVM, Android and Node
SHALL non-functional directive kotlin open in explorer · node
The first implementation SHALL be a Kotlin Multiplatform library with all pipeline logic in common code. It SHALL ship for three targets: JVM (Java 11+), Android, and Node.js (Kotlin/JS IR, published to npm with an @JsExport facade and generated TypeScript declarations). Other KMP targets MAY follow.
Stakeholder directive, in the context of Stakeholder directive: Kotlin library first, Rust library second; Stakeholder directive: transpile targets and API-first.
References
- dir-languages Owner directive: Kotlin library first, Rust library second
- dir-targets Owner directive: transpile targets and API-first
Realized by skills
Verified by tests
- node-smoke.mjs
- build.gradle.kts
- cryptography comes from the platform, with no third-party runtime dependency STT-SEC-002 STT-GEN-004 cryptography comes from the platform, with no third-party runtime dependency
Test plans
STT-GEN-005 · Rust second, for WebAssembly
SHALL non-functional directive rust open in explorer · node
The second implementation SHALL be a Rust library meeting this same requirement set. It SHALL build for wasm32 as a WebAssembly component implementing the WIT worlds in api/wit, and SHALL be transpiled with jco into an npm package usable from Node.js and browsers. A native crate SHALL also be published. The codec module SHOULD build without std (with alloc).
Stakeholder directive, in the context of Stakeholder directive: Kotlin library first, Rust library second; Stakeholder directive: transpile targets and API-first; WIT, WebAssembly Component Model IDL; jco: JavaScript toolchain for WebAssembly components.
References
- dir-languages Owner directive: Kotlin library first, Rust library second
- dir-targets Owner directive: transpile targets and API-first
- Component Model WIT, the WebAssembly Component Model interface definition language source ↗
- 1.35.0 jco, the JavaScript toolchain for WebAssembly components source ↗
Realized by skills
Verified by tests
Test plans
STT-GEN-006 · Kotlin and Rust behave the same
SHALL architecture directive kotlin rust open in explorer · node
The Kotlin and Rust libraries SHALL expose the same conceptual operations, the same configuration knobs and the same failure taxonomy, with idiomatic naming in each language, and SHALL pass the same conformance vectors.
Stakeholder directive, in the context of Stakeholder directive: Kotlin library first, Rust library second.
References
Derived from (broader requirement)
Realized by skills
Verified by tests
- the unified profile's document shapes, and what it refuses STT-TRU-006 STT-GEN-006 the unified profile's document shapes, and what it refuses
- the signer's key controller, and DER signatures, are held to the unified profile STT-TRU-006 STT-GEN-006 the signer's key controller, and DER signatures, are held to the unified profile
- cross.ts
Test plans
Open issues
STT-GEN-007 · HCERT Specification is authoritative
SHALL non-functional direct kotlin rust open in explorer · node
Where the QR Code Generation walkthrough and the HCERT Specification differ, the library SHALL follow the HCERT Specification. Each release SHALL state which HCERT Specification version and which smart-trust IG version it implements (currently 1.1.1 and 1.7.5).
Restates QR Code Generation, Generating a QR Code; Change Log, v1.7.5. QR Code Generation, Generating a QR Code: “Where this page and the HCERT Specification appear to differ, the HCERT Specification is authoritative.”.
References
Realized by skills
STT-GEN-008 · CBOR is the wire format; JSON is optional
SHALL architecture direct kotlin rust open in explorer · node
The library SHALL work entirely on in-memory structures and CBOR. It SHALL NOT need a JSON representation to issue or verify. A JSON view of payloads MAY be provided for display and debugging.
Restates QR Code Generation, 5. Step 4 — Serialise as CBOR; HCERT Specification, 3.2.7 Health Certificate Claim. QR Code Generation, 5. Step 4 — Serialise as CBOR: “the wire format is CBOR, and an implementation need never produce the JSON form at all.”.
References
Realized by skills
STT-GEN-009 · Raw transport besides QR
SHALL functional derived kotlin rust open in explorer · node
The issuer SHALL be able to return the signed COSE_Sign1 bytes without the barcode encodings, and the verifier SHALL accept COSE_Sign1 bytes directly, for 8-bit-safe transports (NFC, Bluetooth, app-to-app).
Engineering consequence of HCERT Specification, 4.1 Raw. HCERT Specification, 4.1 Raw: “the HCERT container and its payloads may be transferred as-is, utilising any underlying, 8 bit safe, reliable data transport”.
References
Realized by skills
- hcert-sign-cose Step 5: sign as COSE_Sign1 (ES256 or PS256) in two phases, the private key staying with its holder.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
Verified by tests
Test plans
STT-GEN-010 · API defined first, in WIT
SHALL architecture directive kotlin rust open in explorer · node
The library API SHALL be defined in WIT (api/wit) and reviewed before implementation. The WIT SHALL be the contract both implementations follow: Rust exports it from its WebAssembly component, and Kotlin follows it through documented mapping rules. Each WIT item SHALL cite the requirement(s) it implements. Breaking WIT changes SHALL bump the package version.
Stakeholder directive, in the context of Stakeholder directive: transpile targets and API-first; WIT, WebAssembly Component Model IDL.
References
- dir-targets Owner directive: transpile targets and API-first
- Component Model WIT, the WebAssembly Component Model interface definition language source ↗
Derived from (broader requirement)
Realized by skills
Verified by tests
Test plans
Open issues
STT-GEN-011 · Same JavaScript surface from Kotlin/JS and Rust/WASM
SHALL architecture derived kotlin rust open in explorer · node
The Node.js packages built from Kotlin/JS and from the Rust WebAssembly component SHALL expose the same TypeScript surface: the declarations jco generates from the WIT (api/generated/ts). The Kotlin/JS declarations SHALL type-check against it.
Engineering consequence of Stakeholder directive: transpile targets and API-first; jco: JavaScript toolchain for WebAssembly components.
References
- dir-targets Owner directive: transpile targets and API-first
- 1.35.0 jco, the JavaScript toolchain for WebAssembly components source ↗
Derived from (broader requirement)
Realized by skills
Verified by tests
Test plans
STT-GEN-012 · Synchronous core with no I/O
SHALL architecture derived kotlin rust open in explorer · node
The pipeline core SHALL do no network or file I/O and SHALL NOT read an ambient clock. Trust material SHALL be loaded into a trust store by the host, and the current time SHALL be passed as an argument. Fetching, caching and refreshing trust lists SHALL live in separate, platform-specific modules.
Engineering consequence of WIT, WebAssembly Component Model IDL; QR Code Generation, 2.2 Choosing between a DVC and a VHL. QR Code Generation, 2.2 Choosing between a DVC and a VHL: “Works fully offline — Yes — verifier needs only the trust list”.
References
- Component Model WIT, the WebAssembly Component Model interface definition language source ↗
- QR §2 2. Step 1 — Select the payload type source ↗
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
Test plans
Open issues
STT-GEN-013 · Types and schemas trace to open standards
SHALL architecture directive kotlin rust open in explorer · node
Every data type in the API contract, wire schema and payload models SHALL trace to an openly available standard: IETF CBOR/CDDL (RFC 8949, RFC 8610) for the wire format, HL7 FHIR data types and W3C XSD datatypes for payload elements, W3C JSON-LD and SHACL for payload semantics, ITU-T X.680 for certificates, and I-JSON (RFC 7493) for JSON files. Paywalled standards SHALL NOT be used as type or schema references.
Stakeholder directive, in the context of Stakeholder directive: open standards for types and schemas; RFC 8610 Concise Data Definition Language (CDDL); HL7 FHIR R5 Data Types; W3C XML Schema 1.1 Part 2: Datatypes; W3C JSON-LD 1.1; W3C Shapes Constraint Language (SHACL); ITU-T X.680 ASN.1 (free); RFC 7493 I-JSON.
References
- dir-open-standards Owner directive: open standards for types and schemas
- RFC 8610 RFC 8610 Concise Data Definition Language (CDDL) source ↗
- 5.0.0 HL7 FHIR R5 source ↗
- 1.1 XML Schema Definition Language (XSD) 1.1 Part 2: Datatypes source ↗
- 1.1 JSON-LD 1.1 source ↗
- W3C Recommendation 2017 Shapes Constraint Language (SHACL) source ↗
- X.680 ITU-T X.680 Abstract Syntax Notation One (ASN.1) source ↗
- RFC 7493 RFC 7493 The I-JSON Message Format source ↗
Realized by skills
STT-GEN-014 · Kotlin API skeleton generated from the WIT
SHALL architecture directive kotlin rust open in explorer · node
The Kotlin API (types and interfaces, no behaviour) SHALL be generated from the WIT contract by tools/wit2kotlin/wit2kotlin.py, following the WIT to Kotlin mapping rules in api/README.md, into a Kotlin Multiplatform module targeting the JVM and Node.js. The generated API SHALL NOT expose the contract language: no Kotlin type, member or KDoc names WIT or a WIT world, and worlds are not generated. Kotlin implementations SHALL implement the generated interfaces. CI SHALL fail when the generated skeleton is out of date with the WIT or does not compile.
Stakeholder directive, in the context of Stakeholder directive: transpile targets and API-first; WIT, WebAssembly Component Model IDL.
References
- dir-targets Owner directive: transpile targets and API-first
- Component Model WIT, the WebAssembly Component Model interface definition language source ↗
Derived from (broader requirement)
Realized by skills
Verified by tests
Test plans
SEL · Step 1: select payload type
STT-SEL-001 · Built-in subclaim registry
SHALL functional direct kotlin rust open in explorer · node
The library SHALL ship a subclaim registry containing 1 (HCertDCC), 3 (DDCCCoreDataSetVS), 4 (DDCCCoreDataSetTR), 5 (VHL URI), -6 (DVCMin) and -7 (MedicationTreatmentLineMin), each with its status (Assigned, Proposed, Development range) and logical-model canonical URL.
Restates QR Code Generation, 2.1 Subclaims defined in this guide; HCERT Specification, 3.2.7.1 Subclaims; HCert logical model. QR Code Generation, 2.1 Subclaims defined in this guide: “These are the subclaims enumerated by the HCert logical model.”. HCERT Specification, 3.2.7.1 Subclaims: “While the logical model enlists assigned sub claims, the ones listed in this specification are considered authoritative”.
References
- QR §2 2. Step 1 — Select the payload type source ↗
- HCERT §3.2.7 3.2.7 Health Certificate Claim source ↗
- HCert logical model (FSH) source ↗
Realized by skills
Constrains (WIT, CDDL)
- subclaims Subclaim registry. Its defaults are the WHO-listed subclaims; the subclaim a payload type is issued under is configuration, so DVC can move from -6 to an assigned number without an API break. [STT-SEL-001, STT-SEL-002, STT-SEL-003]
- subclaim An hcert subclaim number and what it carries. Negative numbers are the development range. [STT-SEL-001, STT-SEL-003]
- hcert Issuance: exactly one subclaim entry, so hcert is a choice of single-entry maps. [STT-SEL-004] (OI-02) Subclaim registry (QR Code Generation 2.1). [STT-SEL-001] Adding a subclaim means adding an alternative here.
Verified by tests
- the subclaims are the guides and development ones need opting in STT-SEL-001 STT-SEL-003 the subclaims are the guides and development ones need opting in
- the registry holds the subclaims the guide defines STT-SEL-001 the registry holds the subclaims the guide defines
- suite.ts
Test plans
Open issues
STT-SEL-002 · Registry is extensible
SHALL api derived kotlin rust open in explorer · node
Integrators SHALL be able to register more subclaims and their codecs, and override the defaults, at runtime, without forking the library.
Engineering consequence of HCERT Specification, 3.2.7.1 Subclaims. HCERT Specification, 3.2.7.1 Subclaims: “subclaims for negative integer values are for development purposes and are free to use”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- a host registers its own subclaim and issues under it STT-SEL-002 a host registers its own subclaim and issues under it
- suite.ts
Test plans
STT-SEL-003 · Development-range subclaims need explicit opt-in
SHALL functional derived kotlin rust open in explorer · node
The registry SHALL classify subclaims >= 0 as WHO-reserved and negative subclaims as development range. The issuer SHALL refuse to issue a development-range subclaim unless the issuer policy explicitly allows it. The subclaim number used for a payload type SHALL be configuration, not a constant in a codec, so that DVC can move from -6 to an assigned number without an API break.
Engineering consequence of QR Code Generation, 2.1 Subclaims defined in this guide; HCERT Specification, 3.2.7.1 Subclaims. QR Code Generation, 2.1 Subclaims defined in this guide: “Do not treat a negative subclaim as a production allocation.”.
References
- QR §2 2. Step 1 — Select the payload type source ↗
- HCERT §3.2.7 3.2.7 Health Certificate Claim source ↗
Realized by skills
Constrains (WIT, CDDL)
- subclaims Subclaim registry. Its defaults are the WHO-listed subclaims; the subclaim a payload type is issued under is configuration, so DVC can move from -6 to an assigned number without an API break. [STT-SEL-001, STT-SEL-002, STT-SEL-003]
- subclaim An hcert subclaim number and what it carries. Negative numbers are the development range. [STT-SEL-001, STT-SEL-003]
Verified by tests
- the subclaims are the guides and development ones need opting in STT-SEL-001 STT-SEL-003 the subclaims are the guides and development ones need opting in
- a development-range subclaim needs the explicit opt-in STT-SEL-003 a development-range subclaim needs the explicit opt-in
- suite.ts
Test plans
Open issues
STT-SEL-004 · One or more payloads per HCERT
SHALL functional derived kotlin rust open in explorer · node
The issuer SHALL accept one or more payloads and emit an hcert (-260) map with one entry per payload, refusing a request with no payload (NoPayload) or two payloads under the same subclaim (DuplicateSubclaim). The verifier SHALL accept an hcert map with any number of entries and return each valid entry with its subclaim, in wire order. It SHALL fail with HcertMissing when there is none, and with MultipleSubclaims when there are more entries than the caller's optional maxPayloads allows. An entry whose payload fails its model check SHALL be left out of the result and reported as a diagnostic; verification SHALL fail with that entry's own reason only when no valid entry remains.
Engineering consequence of QR Code Generation, 2. Step 1 — Select the payload type; QR Code Generation, 4. Step 3 — Build the CWT claim set; HCERT Specification, 3.2.7 Health Certificate Claim; the CWT payload logical model (-260 1..*). QR Code Generation, 2. Step 1 — Select the payload type: “Carrying exactly one is an implementation choice, not a constraint of the container, and it is the choice made throughout this page.”. HCERT Specification, 3.2.7 Health Certificate Claim: “Several different types of health certificate MAY exist under the same claim”. How a verifier treats several entries is the owner's decision of 2026-10-22 (bean OI-02), pending upstream text.
References
- QR §2 2. Step 1 — Select the payload type source ↗
- QR §4 4. Step 3 — Build the CWT claim set source ↗
- HCERT §3.2.7 3.2.7 Health Certificate Claim source ↗
Realized by skills
- hcert-build-cwt Step 3: wrap the payload in a CWT claim set (iss, iat, exp, hcert -260) inside the DSC's validity.
- hcert-select-payload Step 1: choose the logical model and hcert subclaim for a use case, from the registry the IG defines.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
- verifier Verification (QR Code Generation section 11): the steps in STT-VER-001 order, each fixed by the IG rather than a policy switch. The hcert claim may carry several entries (HCERT 3.2.7) [STT-SEL-004]; trust-list presence is the DSC validity check [STT-VER-006]; EKU is checked against each entry's certificate type [STT-VER-004].
- hcert-entry One entry of the hcert claim. [STT-SEL-004]
- hcert Issuance: exactly one subclaim entry, so hcert is a choice of single-entry maps. [STT-SEL-004] (OI-02) Subclaim registry (QR Code Generation 2.1). [STT-SEL-001] Adding a subclaim means adding an alternative here.
Verified by tests
- a certificate with a dvc and a vhl verifies with both entries STT-SEL-004 STT-ISS-004 a certificate with a dvc and a vhl verifies with both entries
- max payloads refuses more entries than the profile allows STT-SEL-004 max payloads refuses more entries than the profile allows
- an invalid entry is left out and reported STT-SEL-004 an invalid entry is left out and reported
- issuance refuses no payload and a duplicate subclaim STT-SEL-004 issuance refuses no payload and a duplicate subclaim
- an issued HCERT carries each payload under its own subclaim, in key order STT-SEL-004 an issued HCERT carries each payload under its own subclaim, in key order
- a certificate with a DVC and a VHL verifies with both entries STT-SEL-004 STT-ISS-004 a certificate with a DVC and a VHL verifies with both entries
- maxPayloads refuses more entries than the profile allows STT-SEL-004 maxPayloads refuses more entries than the profile allows
- an invalid entry is left out and reported STT-SEL-004 an invalid entry is left out and reported
- key usage is checked per entry STT-VER-004 STT-SEL-004 key usage is checked per entry
- issuance refuses no payload and a duplicate subclaim STT-SEL-004 issuance refuses no payload and a duplicate subclaim
- an unknown second entry is kept raw unless the profile allows one STT-VER-007 STT-SEL-004 an unknown second entry is kept raw unless the profile allows one
- key usage is checked per entry STT-VER-004 STT-SEL-004 key usage is checked per entry
- cross.ts
Test plans
Open issues
STT-SEL-006 · Trust domain codes come from the value set
SHALL functional derived kotlin rust open in explorer · node
Trust domain codes (currently DDCC, IPS-PILGRIMAGE, PH4H, ICVP) SHALL be treated as data loaded from, or checked against, the GDHCN Trust Domain value set. They SHALL NOT be a closed enum.
Engineering consequence of QR Code Generation, 2.1 Subclaims defined in this guide; Trust Domains; GDHCN Trust Domain value set. QR Code Generation, 2.1 Subclaims defined in this guide: “The registered production trust domains are DDCC, IPS-PILGRIMAGE, PH4H and ICVP.”.
References
- QR §2 2. Step 1 — Select the payload type source ↗
- Domains Trust Domains source ↗
- GDHCN Trust Domain value set source ↗
- CodeSystem-Domains.json Test vector vectors/gdhcn-trust-domains/CodeSystem-Domains.json source ↗
Realized by skills
- hcert-select-payload Step 1: choose the logical model and hcert subclaim for a use case, from the registry the IG defines.
- hcert-trust-list Hold the trusted DSCs the host obtained (PEM, DER, GDHCN DID trustlist v2) and find every candidate for a kid.
Constrains (WIT, CDDL)
Verified by tests
- trust domains are read from the GDHCN code system, not a closed list STT-SEL-006 trust domains are read from the GDHCN code system, not a closed list
- did documents load and trust domains come from the code system STT-TRU-002 STT-SEL-006 did documents load and trust domains come from the code system
- suite.ts
Test plans
Open issues
PAY · Step 2: assemble payload
STT-PAY-001 · Typed DVCMin model
SHALL functional direct kotlin rust open in explorer · node
The library SHALL provide a typed DVCMin payload with the short element names and cardinalities of the logical model: n 1..1, dob 1..1, s 1..1, nt 1..1, id 0..1, dt 0..1, gn 0..1, vx 1..1, v 1..1, and a PayloadCodec for it.
Restates QR Code Generation, 3. Step 2 — Assemble the payload; DVCMin logical model (trust-phw). QR Code Generation, 3. Step 2 — Assemble the payload: “The DVC payload uses short element names because every byte is paid for twice”.
References
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- a newborn's DVCMin, with no identity document, round-trips as typed fields STT-PAY-001 STT-PAY-002 STT-PAY-005 a newborn's DVCMin, with no identity document, round-trips as typed fields
- a dvcmin round trips and keeps unknown elements STT-PAY-001 STT-PAY-008 a dvcmin round trips and keeps unknown elements
- both PR 129 certificates verify and decode as published STT-TST-001 STT-PAY-001 both PR 129 certificates verify and decode as published
- both pr129 certificates verify and decode as published STT-TST-001 STT-PAY-001 both pr129 certificates verify and decode as published
- suite.ts
Test plans
Open issues
STT-PAY-002 · Typed DVCMinVaccineDetails model
SHALL functional direct kotlin rust open in explorer · node
The library SHALL provide the vx structure with vp 1..1, dv 1..1, cn 0..1, is 0..1 and bo 1..1.
Restates QR Code Generation, 3. Step 2 — Assemble the payload; DVCMinVaccineDetails logical model (trust-phw). QR Code Generation, 3. Step 2 — Assemble the payload: “Populate the logical model you selected.”.
References
- QR §3 3. Step 2 — Assemble the payload source ↗
- DVCMinVaccineDetails logical model DVCMinVaccineDetails logical model (FSH) source ↗
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- a newborn's DVCMin, with no identity document, round-trips as typed fields STT-PAY-001 STT-PAY-002 STT-PAY-005 a newborn's DVCMin, with no identity document, round-trips as typed fields
- the logical model rules are checked STT-PAY-002 STT-PAY-003 the logical model rules are checked
- suite.ts
Test plans
STT-PAY-003 · One immunization event per DVC
SHALL functional direct kotlin rust open in explorer · node
A DVCMin SHALL carry exactly one vx. The library SHALL provide a helper that turns one visit with N administered vaccines into N payloads, one per vaccine, and so N certificates.
Restates QR Code Generation, 3. Step 2 — Assemble the payload; QR Code Generation, 12.1 Scenario. QR Code Generation, 3. Step 2 — Assemble the payload: “vx is 1..1. One DVC documents one immunization event. A visit at which two vaccines are administered produces two certificates and two QR codes.”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- one immunization event per DVC STT-PAY-003 one immunization event per DVC
- the logical model rules are checked STT-PAY-002 STT-PAY-003 the logical model rules are checked
Test plans
STT-PAY-004 · Issuer-or-clinician invariant
SHALL functional direct kotlin rust open in explorer · node
Validation SHALL fail a DVCMin whose vx has neither is nor cn (invariant must-have-issuer-or-clinician-name). The issuer SHALL NOT sign a payload that fails validation.
Restates QR Code Generation, 3. Step 2 — Assemble the payload. QR Code Generation, 3. Step 2 — Assemble the payload: “At least one of is or cn must be present”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
Test plans
STT-PAY-005 · Subjects with no identity document
SHALL functional direct kotlin rust open in explorer · node
The DVCMin model SHALL allow id and dt to be absent and SHALL support gn (parent or guardian) for subjects such as newborns.
Restates QR Code Generation, 3. Step 2 — Assemble the payload; QR Code Generation, 12.1 Scenario. QR Code Generation, 3. Step 2 — Assemble the payload: “id is optional precisely because many certificate subjects have no identification document.”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
Test plans
STT-PAY-007 · Nationality is ISO 3166-1 alpha-3
SHALL functional direct kotlin rust open in explorer · node
Validation SHALL check that nt is an ISO 3166-1 alpha-3 code, including user-assigned codes such as XXA, which is used for testing.
Restates QR Code Generation, 3. Step 2 — Assemble the payload; QR Code Generation, 12.3 Test issuer. QR Code Generation, 3. Step 2 — Assemble the payload: “Nationality, ISO 3166-1 alpha-3”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- an invalid payload is refused, naming each rule STT-PAY-004 STT-PAY-007 an invalid payload is refused, naming each rule
- suite.ts
Test plans
STT-PAY-008 · Preserve unknown payload elements
SHALL functional derived kotlin rust open in explorer · node
Decoding a typed payload SHALL keep unknown elements and SHALL NOT fail because of them, so that newer template versions (v) can still be read and displayed.
Engineering consequence of HCERT Specification, 3.2.7 Health Certificate Claim. HCERT Specification, 3.2.7 Health Certificate Claim: “Decoding applications SHOULD however be permissive and robust in these aspects”.
References
Realized by skills
- hcert-assemble-payload Step 2: build and validate the payload against its logical model (DVCMin, or a VHL payload), keeping unknown elements.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Verified by tests
- unknown payload elements are kept STT-PAY-008 unknown payload elements are kept
- a dvcmin round trips and keeps unknown elements STT-PAY-001 STT-PAY-008 a dvcmin round trips and keeps unknown elements
- suite.ts
Test plans
STT-PAY-009 · Other payloads
SHALL functional derived kotlin rust open in explorer · node
Subclaim 5 SHALL have a typed codec holding a URI string. Subclaims 1, 3, 4 and -7 SHALL at least be issuable and verifiable as raw CBOR payloads. Typed codecs for them MAY follow.
Engineering consequence of QR Code Generation, 2.1 Subclaims defined in this guide; HCert logical model; MedicationTreatmentLineMin logical model (ph4h); DDCC Implementation Guide.
References
- QR §2 2. Step 1 — Select the payload type source ↗
- HCert logical model (FSH) source ↗
- MedicationTreatmentLineMin logical MedicationTreatmentLineMin logical model (ph4h) source ↗
- DDCC Implementation Guide source ↗
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
Test plans
Open issues
STT-PAY-010 · Payload models derived from FHIR logical models
SHALL architecture directive kotlin rust open in explorer · node
Payload schemas SHALL be derived mechanically from the published FHIR logical models, not written by hand: a JSON-LD 1.1 context that maps the payload's short element names to IRIs, SHACL shapes for cardinality, primitive patterns, invariants and bindings, and CDDL for the CBOR encoding. Inputs SHALL be pinned to a source commit. Typed payload codecs SHALL be checked against these derived schemas.
Stakeholder directive, in the context of Stakeholder directive: open standards for types and schemas; DVCMin logical model (trust-phw); DVCMinVaccineDetails logical model (trust-phw); W3C JSON-LD 1.1; W3C Shapes Constraint Language (SHACL). DVCMin logical model (trust-phw): “Minimal DVC payload for use within an HCERT Payload”.
References
- dir-open-standards Owner directive: open standards for types and schemas
- DVCMin logical model (FSH) source ↗
- DVCMinVaccineDetails logical model DVCMinVaccineDetails logical model (FSH) source ↗
- 1.1 JSON-LD 1.1 source ↗
- W3C Recommendation 2017 Shapes Constraint Language (SHACL) source ↗
Realized by skills
- hcert-assemble-payload Step 2: build and validate the payload against its logical model (DVCMin, or a VHL payload), keeping unknown elements.
- hcert-contract-maintenance Keep the contract and everything generated from it current: the WIT, its TypeScript view and Kotlin skeleton, the CDDL, and the payload models derived from the FHIR logical models.
Constrains (WIT, CDDL)
Verified by tests
Test plans
Open issues
CWT · Step 3: CWT claim set
STT-CWT-001 · Integer claim keys
SHALL functional direct kotlin rust open in explorer · node
The CWT claim set SHALL use the integer keys 1 (iss), 6 (iat), 4 (exp) and -260 (hcert). The hcert map SHALL use integer subclaim keys. The quoted keys in the documentation ("1", "-260", "-6") are JSON notation only and SHALL NOT be emitted as text keys.
Restates QR Code Generation, 4. Step 3 — Build the CWT claim set; QR Code Generation, 5. Step 4 — Serialise as CBOR; QR Code Generation, 12.4 The payload; HCERT Specification, 3.2.1 CWT Structure Overview. QR Code Generation, 4. Step 3 — Build the CWT claim set: “Place the payload under its subclaim, inside the hcert claim, inside a CWT claim set”. QR Code Generation, 5. Step 4 — Serialise as CBOR: “The JSON shown throughout this page and in the logical models is schema notation only”.
References
- QR §4 4. Step 3 — Build the CWT claim set source ↗
- QR §5 5. Step 4 — Serialise as CBOR source ↗
- QR §12.4 12.4 The payload source ↗
- HCERT §3.2.1 3.2.1 CWT Structure Overview source ↗
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- claim keys are integers and dates are integer NumericDates STT-CWT-001 STT-CWT-002 claim keys are integers and dates are integer NumericDates
- prepare builds the claims and a protected alg and kid STT-CWT-001 STT-COS-002 STT-COS-003 prepare builds the claims and a protected alg and kid
Test plans
STT-CWT-002 · NumericDate integers
SHALL functional direct kotlin rust open in explorer · node
iat and exp SHALL be encoded as integer NumericDate (seconds since the epoch). The decoder SHOULD accept floating-point NumericDate and truncate it.
Restates HCERT Specification, 3.2.5 Expiration Time; HCERT Specification, 3.2.6 Issued At; RFC 8392 CBOR Web Token (CWT). HCERT Specification, 3.2.5 Expiration Time: “SHALL hold a timestamp in the integer NumericDate format”.
References
- HCERT §3.2.5 3.2.5 Expiration Time source ↗
- HCERT §3.2.6 3.2.6 Issued At source ↗
- RFC 8392 RFC 8392 CBOR Web Token (CWT) source ↗
Realized by skills
Constrains (WIT, CDDL)
- numeric-date CWT NumericDate: integer seconds since the Unix epoch. [STT-CWT-002]
- cwt-claims CWT claim set. [STT-CWT-001]
Verified by tests
Test plans
STT-CWT-003 · Expiry is mandatory on issuance
SHALL functional derived kotlin rust open in explorer · node
The issuer API SHALL require an explicit validity (exp, or a validity period) and SHALL have no default of "never expires". exp is the only revocation mechanism.
Engineering consequence of QR Code Generation, 4. Step 3 — Build the CWT claim set; HCERT Specification, 6.1 HCERT Signature Validity Time. QR Code Generation, 4. Step 3 — Build the CWT claim set: “exp is the only revocation mechanism this specification offers”.
References
- QR §4 4. Step 3 — Build the CWT claim set source ↗
- HCERT §6.1 6.1 HCERT Signature Validity Time source ↗
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
Test plans
STT-CWT-004 · Validity inside the DSC validity
SHALL functional direct kotlin rust open in explorer · node
Before signing, the issuer SHALL check that iat is not before the DSC notBefore and exp is not after the DSC notAfter, and SHALL refuse to sign otherwise.
Restates QR Code Generation, 4. Step 3 — Build the CWT claim set; HCERT Specification, 3.2.5 Expiration Time; HCERT Specification, 3.2.6 Issued At. QR Code Generation, 4. Step 3 — Build the CWT claim set: “MUST NOT predate the DSC validity period ... MUST NOT exceed the DSC validity period”.
References
- QR §4 4. Step 3 — Build the CWT claim set source ↗
- HCERT §3.2.5 3.2.5 Expiration Time source ↗
- HCERT §3.2.6 3.2.6 Issued At source ↗
Realized by skills
- hcert-build-cwt Step 3: wrap the payload in a CWT claim set (iss, iat, exp, hcert -260) inside the DSC's validity.
- hcert-sign-cose Step 5: sign as COSE_Sign1 (ES256 or PS256) in two phases, the private key staying with its holder.
Verified by tests
- the validity must fit inside the DSC's STT-CWT-004 the validity must fit inside the DSC's
- validity outside the dsc is refused STT-CWT-004 validity outside the dsc is refused
- suite.ts
Test plans
STT-CWT-005 · iss is an optional alpha-2 string
SHALL functional derived kotlin rust open in explorer · node
iss SHALL be optional and SHALL be encoded as a text string. When present, the issuer SHALL require two upper-case letters, the alpha-2 form used in iss and in the DSC's X.509 C attribute.
Engineering consequence of HCERT Specification, 3.2.4 Issuer; QR Code Generation, 12.3 Test issuer. QR Code Generation, 12.3 Test issuer: “GDHCN participant codes are ISO 3166-1 alpha-3, while the CWT iss claim and the X.509 country attribute take the alpha-2 form”.
References
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
Test plans
Open issues
STT-CWT-006 · Preserve unknown claims
SHALL functional derived kotlin rust open in explorer · node
The decoder SHALL keep CWT claims it does not recognise and expose them to the caller.
Engineering consequence of RFC 8392 CBOR Web Token (CWT); HCERT Specification, 3.2.7 Health Certificate Claim.
References
Realized by skills
- hcert-build-cwt Step 3: wrap the payload in a CWT claim set (iss, iat, exp, hcert -260) inside the DSC's validity.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Verified by tests
Test plans
CBR · Step 4: CBOR
STT-CBR-001 · Deterministic CBOR on encode
SHALL functional derived kotlin rust open in explorer · node
The encoder SHALL produce deterministic CBOR (RFC 8949 section 4.2.1 core deterministic encoding). For the HCERT key sets this matches the RFC 7049 canonical (length-first) ordering. Same input gives same bytes, in both languages.
Engineering consequence of QR Code Generation, 5. Step 4 — Serialise as CBOR; RFC 8949 CBOR (obsoletes RFC 7049).
References
- QR §5 5. Step 4 — Serialise as CBOR source ↗
- RFC 8949 RFC 8949 Concise Binary Object Representation (CBOR) source ↗
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- maps are encoded in deterministic key order, whatever order they were built in STT-CBR-001 maps are encoded in deterministic key order, whatever order they were built in
- RFC 8949 Appendix A numbers re-encode in their shortest form STT-CBR-001 RFC 8949 Appendix A numbers re-encode in their shortest form
- indefinite lengths re-encode as definite (RFC 8949 Appendix A) STT-CBR-001 indefinite lengths re-encode as definite (RFC 8949 Appendix A)
- maps are encoded in deterministic key order STT-CBR-001 maps are encoded in deterministic key order
- rfc 8949 appendix a numbers re encode in their shortest form STT-CBR-001 rfc 8949 appendix a numbers re encode in their shortest form
- indefinite lengths re encode as definite STT-CBR-001 indefinite lengths re encode as definite
- deterministic re-encoding reproduces the issued CWT byte for byte STT-TST-002 STT-CBR-001 deterministic re-encoding reproduces the issued CWT byte for byte
- deterministic re encoding reproduces the issued cwt byte for byte STT-TST-002 STT-CBR-001 deterministic re encoding reproduces the issued cwt byte for byte
- suite.ts
- cross.ts
Test plans
Open issues
STT-CBR-002 · NFC normalisation
SHOULD functional direct kotlin rust open in explorer · node
The encoder SHOULD NFC-normalise text strings. The decoder SHALL accept strings that are not normalised, and comparison helpers SHALL behave as if both sides were normalised to NFC.
Restates QR Code Generation, 5. Step 4 — Serialise as CBOR; HCERT Specification, 3.2.7 Health Certificate Claim. QR Code Generation, 5. Step 4 — Serialise as CBOR: “Strings SHOULD be NFC-normalised. Decoders SHOULD be permissive about input that is not.”.
References
Realized by skills
- hcert-encode-cbor Step 4: serialise as RFC 8949 deterministic CBOR, and decode untrusted CBOR within limits.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Verified by tests
- text is NFC-normalised on encode, and a non-normalised string still decodes STT-CBR-002 text is NFC-normalised on encode, and a non-normalised string still decodes
- text is nfc normalised on encode STT-CBR-002 text is nfc normalised on encode
Test plans
STT-CBR-003 · Bounded CBOR decoding
SHALL security derived kotlin rust open in explorer · node
The CBOR decoder SHALL enforce configurable limits on nesting depth, item count and string/byte-string length, and SHALL reject malformed or truncated input with a typed error, without panicking or throwing unchecked exceptions.
Engineering consequence of HCERT Specification, 6.3 Input Data Validation. HCERT Specification, 6.3 Input Data Validation: “all input fields MUST be properly validated by data types, lengths and contents”.
References
Realized by skills
- hcert-encode-cbor Step 4: serialise as RFC 8949 deterministic CBOR, and decode untrusted CBOR within limits.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
Verified by tests
- nesting beyond the limit is refused STT-CBR-003 nesting beyond the limit is refused
- malformed indefinite-length items and out-of-range integers are refused with a typed error STT-CBR-003 malformed indefinite-length items and out-of-range integers are refused with a typed error
- malformed or oversized input is refused with a typed error STT-CBR-003 malformed or oversized input is refused with a typed error
- suite.ts
Test plans
STT-CBR-004 · Wire format specified in CDDL
SHALL functional derived kotlin rust open in explorer · node
The CBOR wire format (COSE_Sign1, protected header, CWT claim set, hcert map and payloads) SHALL be specified in CDDL (RFC 8610), and encoders SHALL produce data that validates against it. The envelope SHALL be hand-written from the HCERT Specification; payload rules SHALL be generated from the logical models (STT-PAY-010).
Engineering consequence of Stakeholder directive: open standards for types and schemas; QR Code Generation, 5. Step 4 — Serialise as CBOR; HCERT Specification, 3.2.1 CWT Structure Overview; RFC 8610 Concise Data Definition Language (CDDL); RFC 9052 COSE: Structures and Process. QR Code Generation, 5. Step 4 — Serialise as CBOR: “The JSON shown throughout this page and in the logical models is schema notation only — the wire format is CBOR”.
References
- dir-open-standards Owner directive: open standards for types and schemas
- QR §5 5. Step 4 — Serialise as CBOR source ↗
- HCERT §3.2.1 3.2.1 CWT Structure Overview source ↗
- RFC 8610 RFC 8610 Concise Data Definition Language (CDDL) source ↗
- RFC 9052 RFC 9052 CBOR Object Signing and Encryption (COSE): Structures and Process source ↗
Realized by skills
- hcert-build-cwt Step 3: wrap the payload in a CWT claim set (iss, iat, exp, hcert -260) inside the DSC's validity.
- hcert-encode-cbor Step 4: serialise as RFC 8949 deterministic CBOR, and decode untrusted CBOR within limits.
- hcert-sign-cose Step 5: sign as COSE_Sign1 (ES256 or PS256) in two phases, the private key staying with its holder.
- hcert-contract-maintenance Keep the contract and everything generated from it current: the WIT, its TypeScript view and Kotlin skeleton, the CDDL, and the payload models derived from the FHIR logical models.
Verified by tests
Test plans
Open issues
COS · Step 5: COSE_Sign1
STT-COS-001 · COSE_Sign1 structure
SHALL functional direct kotlin rust open in explorer · node
The issuer SHALL emit 18([protected bstr, unprotected map, payload bstr, signature bstr]). The verifier SHALL accept the tagged form and SHOULD also accept an untagged COSE_Sign1 array.
Restates QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1; RFC 8152 CBOR Object Signing and Encryption (COSE). QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1: “The resulting structure is 18([protected, unprotected, payload, signature]).”.
References
- QR §6 6. Step 5 — Sign as a COSE_Sign1 source ↗
- RFC 8152 RFC 8152 COSE (obsoleted by RFC 9052) source ↗
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- a tagged COSE_Sign1 with alg and kid in the protected header STT-COS-001 STT-COS-003 a tagged COSE_Sign1 with alg and kid in the protected header
- suite.ts
Test plans
STT-COS-002 · Both ES256 and PS256
SHALL functional direct kotlin rust open in explorer · node
The library SHALL implement signing and verification for ES256 (alg -7, ECDSA P-256 with SHA-256) and PS256 (alg -37, RSASSA-PSS with SHA-256, MGF1 SHA-256, salt length 32, modulus at least 2048 bits).
Restates QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1; HCERT Specification, 3.2.2 Signature Algorithm; RFC 8230 RSA algorithms with COSE (PS256). QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1: “Both algorithms MUST be implemented, even though only one is used to sign any given certificate”.
References
- QR §6 6. Step 5 — Sign as a COSE_Sign1 source ↗
- HCERT §3.2.2 3.2.2 Signature Algorithm source ↗
- RFC 8230 RFC 8230 Using RSA Algorithms with COSE Messages (PS256) source ↗
Realized by skills
- hcert-sign-cose Step 5: sign as COSE_Sign1 (ES256 or PS256) in two phases, the private key staying with its holder.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
- verify-sign1 Verify a parsed COSE_Sign1 against one DSC. Low level: does not check EKU, time or trust. [STT-COS-002]
- cose-algorithm COSE algorithm. es256 = -7 (primary), ps256 = -37 (secondary). Both MUST be implemented. [STT-COS-002]
- es256 primary [STT-COS-002]
- ps256 secondary [STT-COS-002]
Verified by tests
- prepare builds the claims and a protected alg and kid STT-CWT-001 STT-COS-002 STT-COS-003 prepare builds the claims and a protected alg and kid
- both ES256 and PS256 sign and verify STT-COS-002 STT-GEN-001 both ES256 and PS256 sign and verify
- suite.ts
Test plans
STT-COS-003 · kid placement
SHALL functional direct kotlin rust open in explorer · node
The issuer SHALL put alg and kid in the protected header. The verifier SHALL accept a kid in either the protected or the unprotected header, and SHALL use the protected one when both are present.
Restates QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1; HCERT Specification, 3.2.3 Key Identifier. QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1: “verifiers MUST accept both placements — but when it appears in both, the protected one wins.”.
References
Realized by skills
- hcert-sign-cose Step 5: sign as COSE_Sign1 (ES256 or PS256) in two phases, the private key staying with its holder.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
- protected-header alg and kid in the protected header. [STT-COS-003]
- unprotected-header kid MAY appear here instead; the protected one wins. [STT-COS-003]
Verified by tests
- prepare builds the claims and a protected alg and kid STT-CWT-001 STT-COS-002 STT-COS-003 prepare builds the claims and a protected alg and kid
- a tagged COSE_Sign1 with alg and kid in the protected header STT-COS-001 STT-COS-003 a tagged COSE_Sign1 with alg and kid in the protected header
- suite.ts
Test plans
STT-COS-004 · Sig_structure
SHALL functional direct kotlin rust open in explorer · node
The signature input SHALL be the CBOR encoding of ["Signature1", protected header bytes exactly as transmitted, empty external_aad, payload bytes]. The verifier SHALL use the received protected bytes and SHALL NOT re-encode the decoded header.
Restates QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1; RFC 8152 CBOR Object Signing and Encryption (COSE). QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1: “serialise it as CBOR, and sign those bytes.”.
References
- QR §6 6. Step 5 — Sign as a COSE_Sign1 source ↗
- RFC 8152 RFC 8152 COSE (obsoleted by RFC 9052) source ↗
Realized by skills
- hcert-sign-cose Step 5: sign as COSE_Sign1 (ES256 or PS256) in two phases, the private key staying with its holder.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
- sign1 A parsed COSE_Sign1. `protected` is kept as received so the Sig_structure is rebuilt from the original bytes. [STT-COS-004]
- to-be-signed Bytes to sign (CBOR Sig_structure). [STT-COS-004]
Verified by tests
- what is signed is the Signature1 structure prepare returns STT-COS-004 STT-ISS-004 what is signed is the Signature1 structure prepare returns
- suite.ts
Test plans
STT-COS-005 · Fixed-width r||s ECDSA signatures
SHALL functional direct kotlin rust open in explorer · node
ES256 signatures SHALL be carried as 64-byte r||s. The library SHALL convert from and to the ASN.1 DER form returned or expected by platform crypto APIs (JCA, Android Keystore, PKCS#11, RustCrypto). The verifier SHALL reject an ES256 signature that is not exactly 64 bytes.
Restates QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1. QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1: “For ES256 the COSE signature value is the fixed-width concatenation r || s, 32 bytes each — not the ASN.1 DER encoding”.
References
Realized by skills
- hcert-sign-cose Step 5: sign as COSE_Sign1 (ES256 or PS256) in two phases, the private key staying with its holder.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
- ecdsa-der-to-raw ASN.1 DER ECDSA-Sig-Value <-> fixed-width r||s. [STT-COS-005]
- finish Phase 2: attach the signature. ES256 accepts r||s or DER and always emits r||s. [STT-COS-005]
- es256-signature ES256 signatures are fixed-width r||s. [STT-COS-005]
Verified by tests
- two phase issuance with es256 and ps256 verifies STT-ISS-004 STT-COS-005 two phase issuance with es256 and ps256 verifies
- an ES256 signature is a fixed-width 64-byte r and s, whatever the signer returns STT-COS-005 an ES256 signature is a fixed-width 64-byte r and s, whatever the signer returns
- suite.ts
Test plans
STT-COS-006 · No private keys required in-process
SHALL security derived kotlin rust open in explorer · node
The issuer SHALL NOT need access to private key material: issuance is prepare, then the caller signs the returned bytes by any means (HSM, KMS, PKCS#11, Android Keystore, WebCrypto), then finish (STT-ISS-004). A software ES256 and PS256 signer SHALL be provided for tests only, in the testkit module.
Engineering consequence of HCERT Specification, 6.2 Key Management. HCERT Specification, 6.2 Key Management: “Use of HSMs for signing health certificates is highly encouraged.”.
References
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- finish refuses a signature from another key STT-COS-006 finish refuses a signature from another key
- the library holds no private key, and refuses a signature from another key STT-COS-006 the library holds no private key, and refuses a signature from another key
- suite.ts
- cross.ts
Test plans
KID · Key identifier
STT-KID-001 · kid derivation
SHALL functional direct kotlin rust open in explorer · node
kid SHALL be the first 8 bytes of SHA-256 over the DER-encoded DSC. The issuer SHALL derive it from the Signer's DSC, not take it from the caller.
Restates QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1; HCERT Specification, A.1 The Key Identifier (`kid`s). QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1: “take the DER encoding of the DSC and use the first 8 bytes of its SHA-256 fingerprint”.
References
Realized by skills
- hcert-sign-cose Step 5: sign as COSE_Sign1 (ES256 or PS256) in two phases, the private key staying with its holder.
- hcert-trust-list Hold the trusted DSCs the host obtained (PEM, DER, GDHCN DID trustlist v2) and find every candidate for a kid.
Constrains (WIT, CDDL)
- kid-from-dsc First 8 bytes of SHA-256 over the DER-encoded DSC. [STT-KID-001]
- kid 8-byte key identifier: first 8 bytes of SHA-256 over the DER DSC. [STT-KID-001]
- kid First 8 bytes of SHA-256 over the DER DSC. [STT-KID-001]
Verified by tests
- a DSC is found by its kid, the first 8 bytes of its SHA-256 STT-TRU-001 STT-KID-001 a DSC is found by its kid, the first 8 bytes of its SHA-256
- the vector's kid is the DSC's STT-TST-001 STT-KID-001 the vector's kid is the DSC's
- the vectors kid is the dscs STT-TST-001 STT-KID-001 the vectors kid is the dscs
- suite.ts
Test plans
STT-KID-002 · kid is a lookup hint, not unique
SHALL functional direct kotlin rust open in explorer · node
Trust-list lookup by kid SHALL return every matching DSC, and the verifier SHALL try each candidate until one verifies. A kid match alone SHALL NOT count as authentication.
Restates QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1; HCERT Specification, 3.2.3 Key Identifier. QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1: “a verifier that finds several DSCs with the same kid MUST try all of them”.
References
Realized by skills
- hcert-trust-list Hold the trusted DSCs the host obtained (PEM, DER, GDHCN DID trustlist v2) and find every candidate for a kid.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
Verified by tests
- pem loads once and candidates are found by kid STT-TRU-005 STT-KID-002 pem loads once and candidates are found by kid
- a kid nobody trusts fails the trust lookup STT-VER-006 STT-KID-002 a kid nobody trusts fails the trust lookup
- suite.ts
Test plans
STT-KID-003 · kid text forms
SHALL api derived kotlin rust open in explorer · node
Kid SHALL convert to and from hex, standard base64 and URL-safe base64. Trust-list matching SHALL compare the kid bytes, not their text form.
Engineering consequence of QR Code Generation, 12.6 The certificates; GDHCN Trustlists (DID), Example DID documents. QR Code Generation, 12.6 The certificates: “Both certificates carry the key identifier kW/6VnwPRKI= (hex 916ffa567c0f44a2).”.
References
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- a kid reads and writes as standard and URL-safe base64 STT-KID-003 a kid reads and writes as standard and URL-safe base64
- suite.ts
Test plans
Open issues
ZIP · Step 6: compression
STT-ZIP-001 · ZLIB/Deflate after signing
SHALL functional direct kotlin rust open in explorer · node
The issuer SHALL compress the COSE_Sign1 bytes with ZLIB (RFC 1950) using Deflate (RFC 1951) after signing. The verifier SHALL decompress before parsing COSE.
Restates QR Code Generation, 7. Step 6 — Compress; HCERT Specification, 4.2.1 Payload (CWT) Compression. QR Code Generation, 7. Step 6 — Compress: “Compression comes after signing, so the verifier decompresses before it verifies.”.
References
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- what is compressed is the signed COSE_Sign1, as a ZLIB stream STT-ZIP-001 what is compressed is the signed COSE_Sign1, as a ZLIB stream
- zlib round trips and inflation is bounded STT-ZIP-001 STT-ZIP-002 zlib round trips and inflation is bounded
- suite.ts
Test plans
STT-ZIP-002 · Decompression bomb protection
SHALL security derived kotlin rust open in explorer · node
Inflation SHALL stop with a typed error once the output exceeds a configurable limit (DecodeLimits.maxInflatedBytes).
Engineering consequence of HCERT Specification, 6.3 Input Data Validation.
References
Derived from (broader requirement)
Realized by skills
- hcert-compress Step 6: ZLIB/DEFLATE after signing, and bounded inflation.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
Verified by tests
- a decompression bomb stops at the limit STT-ZIP-002 a decompression bomb stops at the limit
- zlib round trips and inflation is bounded STT-ZIP-001 STT-ZIP-002 zlib round trips and inflation is bounded
- suite.ts
Test plans
B45 · Step 7: Base45
STT-B45-001 · Base45 per RFC 9285
SHALL functional direct kotlin rust open in explorer · node
Base45 encoding and decoding SHALL follow RFC 9285, the published form of the draft the IG cites.
Restates QR Code Generation, 8. Step 7 — Encode as Base45; HCERT Specification, 4.2.2 QR 2D Barcode; RFC 9285 The Base45 Data Encoding. QR Code Generation, 8. Step 7 — Encode as Base45: “its 45-character alphabet is exactly the QR Alphanumeric character set”.
References
- QR §8 8. Step 7 — Encode as Base45 source ↗
- HCERT §4.2.2 4.2.2 QR 2D Barcode source ↗
- RFC 9285 RFC 9285 The Base45 Data Encoding source ↗
Realized by skills
Constrains (WIT, CDDL)
- base45-encode RFC 9285 Base45. [STT-B45-001]
- hc1-string Context identifier and Base45 text (step 8). [STT-HC1-001, STT-B45-001]
Verified by tests
- the RFC 9285 section 4-3 examples STT-B45-001 the RFC 9285 section 4-3 examples
- rfc 9285 examples encode and decode STT-B45-001 rfc 9285 examples encode and decode
- suite.ts
Test plans
Open issues
STT-B45-002 · Strict Base45 decoding
SHALL security derived kotlin rust open in explorer · node
The decoder SHALL reject characters outside the 45-character alphabet, a dangling single character, and triplets or pairs whose value overflows (above 65535, or above 255 for a trailing pair).
Engineering consequence of RFC 9285 The Base45 Data Encoding; HCERT Specification, 6.3 Input Data Validation.
References
Derived from (broader requirement)
Realized by skills
- hcert-encode-base45 Step 7: RFC 9285 Base45, decoded strictly.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Verified by tests
- lower case, a dangling character and an out-of-range triple are refused STT-B45-002 lower case, a dangling character and an out-of-range triple are refused
- decoding is strict STT-B45-002 decoding is strict
- suite.ts
Test plans
HC1 · Step 8: context identifier
STT-HC1-001 · HC1 prefix
SHALL functional direct kotlin rust open in explorer · node
The issuer SHALL prefix the Base45 string with "HC1:". The verifier SHALL require a context identifier matching HC[1-9A-Z]: and strip it. It SHALL report any identifier other than HC1 as an unsupported version, distinct from malformed input.
Restates QR Code Generation, 9. Step 8 — Prefix the context identifier; HCERT Specification, 4.2.2 QR 2D Barcode. QR Code Generation, 9. Step 8 — Prefix the context identifier: “A future backwards-incompatible revision will use a new identifier, incrementing through [1-9] and then [A-Z].”.
References
Realized by skills
- hcert-prefix-context Steps 8 and 9: the HC1: context identifier, giving a QR-ready string the application renders with its own QR encoder.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
- detect-context Returns the context identifier version character (e.g. "1") and the remainder after the colon. [STT-HC1-001]
- hc1-string Context identifier and Base45 text (step 8). [STT-HC1-001, STT-B45-001]
Verified by tests
- HC1 is the only context, version 1 the only version STT-HC1-001 HC1 is the only context, version 1 the only version
- the context identifier is read and checked STT-HC1-001 the context identifier is read and checked
- suite.ts
Test plans
QR · Step 9: QR code
STT-QR-001 · QR-ready HC1 output; no QR encoding in the library
SHALL functional derived kotlin rust open in explorer · node
The HC1 string SHALL consist only of characters in the QR Alphanumeric set (the Base45 alphabet and the "HC1:" prefix), so that integrators can encode it as a single Alphanumeric-mode segment with error correction Q using any QR encoder. The libraries SHALL NOT encode or render QR symbols.
Restates QR Code Generation, 10. Step 9 — Render the QR code; HCERT Specification, 4.2.2 QR 2D Barcode; ISO/IEC 18004:2015 QR Code. HCERT Specification, 4.2.2 QR 2D Barcode: “The Alphanumeric (Mode 2/QR Code symbols 0010) MUST be used in conjunction with Base45.”.
References
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- the output is a QR-ready string in the Alphanumeric alphabet STT-QR-001 the output is a QR-ready string in the Alphanumeric alphabet
- the pr129 hc1 string renders as the version 16 q symbol the page states STT-QR-001 the pr129 hc1 string renders as the version 16 q symbol the page states
- suite.ts
Test plans
STT-QR-005 · Camera scanning out of scope
SHALL scope derived kotlin rust open in explorer · node
Decoding QR images from a camera or file is out of scope for the core libraries. The verifier entry point SHALL be the scanned text (HC1 string) or raw COSE bytes.
Engineering consequence of QR Code Generation, 11. Verifying: the same steps upwards. QR Code Generation, 11. Verifying: the same steps upwards: “Confirm the HC1: prefix and strip it.”.
References
Realized by skills
VER · Verification
STT-VER-001 · Verification order
SHALL functional direct kotlin rust open in explorer · node
The verifier SHALL run the QR Code Generation section 11 sequence in order: prefix, Base45, inflate, COSE parse, header validation, trust lookup by kid, signature verification, EKU check, CWT decode and time checks, subclaim dispatch, business rules.
Restates QR Code Generation, 1. The pipeline at a glance; QR Code Generation, 11. Verifying: the same steps upwards. QR Code Generation, 1. The pipeline at a glance: “A verifier runs the same list upwards.”.
References
Satisfies (smart-trust)
Realized by skills
Constrains (WIT, CDDL)
- verifier Verification (QR Code Generation section 11): the steps in STT-VER-001 order, each fixed by the IG rather than a policy switch. The hcert claim may carry several entries (HCERT 3.2.7) [STT-SEL-004]; trust-list presence is the DSC validity check [STT-VER-006]; EKU is checked against each entry's certificate type [STT-VER-004].
- step Verification steps, in the order they run. [STT-VER-001]
Verified by tests
- a tampered payload fails at the signature, before any payload is read STT-VER-001 STT-VER-002 a tampered payload fails at the signature, before any payload is read
- suite.ts
Test plans
STT-VER-002 · No payload access before the signature is verified
SHALL security derived kotlin rust open in explorer · node
The API SHALL make the payload and claims reachable only through a successful verification result type. Code that inspects an unverified payload SHALL go through an explicitly named unsafe or diagnostic call.
Engineering consequence of QR Code Generation, 11. Verifying: the same steps upwards; HCERT Specification, 6.3 Input Data Validation. QR Code Generation, 11. Verifying: the same steps upwards: “Verify the signature before processing the payload.”. HCERT Specification, 6.3 Input Data Validation: “The Issuer Signature SHALL also be verified before any processing of the contents of the HCERT takes place.”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
- verification-result Payload content is only reachable through `verified`. [STT-VER-002, STT-VER-008]
Verified by tests
Test plans
STT-VER-003 · Defensive protected-header parsing
SHALL security direct kotlin rust open in explorer · node
Before the signature is checked, the verifier SHALL validate the types and lengths in the protected header: alg is a supported integer, kid is a byte string of the expected length, and no duplicate labels. It SHALL fail closed on anything unexpected.
Restates QR Code Generation, 11. Verifying: the same steps upwards; HCERT Specification, 6.3 Input Data Validation. QR Code Generation, 11. Verifying: the same steps upwards: “Parsing the protected header necessarily happens before signature verification, which makes it an attack surface”.
References
Derived from (broader requirement)
Realized by skills
Verified by tests
- a protected header with an unsupported alg is refused STT-VER-003 a protected header with an unsupported alg is refused
- headers are checked before any signature STT-VER-003 headers are checked before any signature
Test plans
STT-VER-004 · EKU check against certificate type
SHALL functional derived kotlin rust open in explorer · node
If the matching DSC has an Extended Key Usage extension with any of 1.3.6.1.4.1.1847.2021.1.1 (test), .1.2 (vaccinations) or .1.3 (recovery), the verifier SHALL check it against the certificate type of each hcert entry: DVC (-6) and DDCC VS (3) are vaccinations, DDCC TR (4) is test, and a DCC (1) is the type of its single v, t or r entry. An entry whose type the EKU does not permit SHALL be returned marked as not authorised (keyUsageAuthorised false), with a diagnostic; verification SHALL fail with KeyUsageMismatch only when no entry is authorised. A DSC with no such extension SHALL be accepted for any type.
Engineering consequence of QR Code Generation, 11. Verifying: the same steps upwards; HCERT Specification, A.2 Extended Key Usage Identifiers. HCERT Specification, A.2 Extended Key Usage Identifiers: “If one or more are present, the verifiers SHALL verify the key usage against the stored HCERT.”.
References
- QR §11 11. Verifying: the same steps upwards source ↗
- HCERT §A.2 A.2 Extended Key Usage Identifiers source ↗
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- key usage is checked per entry STT-VER-004 STT-SEL-004 key usage is checked per entry
- a DSC whose key usage allows test certificates only cannot sign a vaccination STT-VER-004 a DSC whose key usage allows test certificates only cannot sign a vaccination
- a DCC is a test, vaccination or recovery certificate by its own entry STT-VER-004 a DCC is a test, vaccination or recovery certificate by its own entry
- key usage is checked per entry STT-VER-004 STT-SEL-004 key usage is checked per entry
- cross.ts
Test plans
Open issues
STT-VER-005 · Time checks with an injected clock
SHALL functional direct kotlin rust open in explorer · node
The verifier SHALL reject a certificate whose exp is in the past, and one whose iat is in the future beyond a configurable clock-skew tolerance. The current time SHALL be passed in by the caller (now), never read from an ambient clock.
Restates QR Code Generation, 11. Verifying: the same steps upwards; HCERT Specification, 3.2.5 Expiration Time; HCERT Specification, 3.2.6 Issued At. QR Code Generation, 11. Verifying: the same steps upwards: “Decode the CBOR payload and check exp and iat against the current time.”. HCERT Specification, 3.2.5 Expiration Time: “after which a Verifier MUST reject the Payload as expired”.
References
- QR §11 11. Verifying: the same steps upwards source ↗
- HCERT §3.2.5 3.2.5 Expiration Time source ↗
- HCERT §3.2.6 3.2.6 Issued At source ↗
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
Test plans
STT-VER-006 · Trust-list presence is the DSC validity check
SHALL functional direct kotlin rust open in explorer · node
The verifier SHALL NOT need CRL or OCSP access. A DSC is valid if it is on the current trust list. The verifier SHOULD also check the DSC's X.509 validity period against the injected clock.
Restates HCERT Specification, 5.1 Simplified SCA/DSC. HCERT Specification, 5.1 Simplified SCA/DSC: “the primary validity mechanism is the presence of the certificate on the most recent version of that certificate list.”.
References
Realized by skills
- hcert-trust-list Hold the trusted DSCs the host obtained (PEM, DER, GDHCN DID trustlist v2) and find every candidate for a kid.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Constrains (WIT, CDDL)
Verified by tests
Test plans
STT-VER-007 · Dispatch to logical model, then business rules
SHALL functional direct kotlin rust open in explorer · node
After verification the verifier SHALL decode each hcert entry with the codec registered for its subclaim, falling back to RawPayload. It SHALL then run any registered BusinessRuleHooks and attach their outcomes to the result.
Restates QR Code Generation, 11. Verifying: the same steps upwards. QR Code Generation, 11. Verifying: the same steps upwards: “Read the hcert claim, dispatch on the subclaim to the right logical model, and only then apply business rules.”.
References
Satisfies (smart-trust)
- UtilizeVDHC-display-verified-content Display verified content source ↗
- UtilizeVDHC-execute-business-rule Execute business rules source ↗
Realized by skills
Verified by tests
- the payload is dispatched to its logical model by subclaim STT-VER-007 the payload is dispatched to its logical model by subclaim
- an unknown second entry is kept raw unless the profile allows one STT-VER-007 STT-SEL-004 an unknown second entry is kept raw unless the profile allows one
Test plans
STT-VER-008 · Typed verification result and failure taxonomy
SHALL api derived kotlin rust open in explorer · node
A certificate failing verification SHALL produce a Failed result, not an exception or panic. Failed results SHALL name the step that failed and a reason from a closed set shared by both languages: BadPrefix, UnsupportedContextVersion, Base45Invalid, InflateFailed, LimitExceeded, CoseMalformed, HeaderInvalid, UnsupportedAlgorithm, KidNotFound, SignatureInvalid, KeyUsageMismatch, Expired, NotYetValid, HcertMissing, MultipleSubclaims, PayloadInvalid. Exceptions or errors SHALL be reserved for programming errors and infrastructure failures.
Engineering consequence of QR Code Generation, 11. Verifying: the same steps upwards; Stakeholder directive: Kotlin library first, Rust library second.
References
- QR §11 11. Verifying: the same steps upwards source ↗
- dir-languages Owner directive: Kotlin library first, Rust library second
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
- verification-result Payload content is only reachable through `verified`. [STT-VER-002, STT-VER-008]
- failure-reason Why verification stopped. [STT-VER-008]
Verified by tests
- every failure is a value naming its step and reason, offline STT-VER-008 STT-VER-009 every failure is a value naming its step and reason, offline
- suite.ts
Test plans
STT-VER-009 · Fully offline verification
SHALL functional derived kotlin rust open in explorer · node
Given a cached or static trust list, verification SHALL need no network access.
Engineering consequence of QR Code Generation, 2.2 Choosing between a DVC and a VHL; QR Code Generation, 12.1 Scenario. QR Code Generation, 2.2 Choosing between a DVC and a VHL: “Works fully offline — Yes — verifier needs only the trust list”.
References
Realized by skills
- hcert-trust-list Hold the trusted DSCs the host obtained (PEM, DER, GDHCN DID trustlist v2) and find every candidate for a kid.
- hcert-verify Verify an HC1 string or raw COSE: the steps upwards, trust lookup by kid, signature, key usage, time and payload; or read it explicitly unverified for triage.
Verified by tests
- every failure is a value naming its step and reason, offline STT-VER-008 STT-VER-009 every failure is a value naming its step and reason, offline
- the core does no network, file or asynchronous I-O STT-GEN-012 STT-VHL-010 STT-VER-009 the core does no network, file or asynchronous I-O
Test plans
STT-VER-011 · Unverified read for triage
SHALL functional directive kotlin rust open in explorer · node
The verifier SHALL offer an explicitly named unverified read (read-unverified) that decodes an HC1 string up to and including the payload without any trust lookup or signature check, for display and triage where verification is not possible, such as a community health worker offline at a household (journey J2). Its result type SHALL be distinct from a verified result, SHALL carry the kid, the claims and every hcert entry it found, and SHALL state that nothing in it is verified. It SHALL apply the same decode limits as verify.
Stakeholder directive, in the context of Stakeholder directive: the library must carry the workshop user journeys; QR Code Generation, 11. Verifying a certificate. Stakeholder directive: the library must carry the workshop user journeys: “lightweight QR scan reads basic vaccine metadata without requiring full cryptographic verification at the household edge”.
References
- dir-journeys Owner directive: the library must carry the workshop user journeys
- QR §11 11. Verifying: the same steps upwards source ↗
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
- unverified-entry Decoded, NOT verified: no trust lookup, no signature check. For display and triage where verification is impossible (an offline household visit, journey J2); never a basis for trust. [STT-VER-011] One entry of an unverified hcert claim.
- read-unverified [STT-VER-011]
Verified by tests
- an unverified read for triage is marked so STT-VER-011 an unverified read for triage is marked so
- read unverified says so STT-VER-011 read unverified says so
- suite.ts
Test plans
TRU · Trust list
STT-TRU-001 · Trust store
SHALL api derived kotlin rust open in explorer · node
Trust material SHALL be held in a trust store that the host fills with DSCs it has obtained, and that returns every trusted DSC matching a kid. The store SHALL do no network or file I/O (STT-GEN-012).
Engineering consequence of HCERT Specification, Appendix A - Trust Management; QR Code Generation, 11. Verifying: the same steps upwards. HCERT Specification, Appendix A - Trust Management: “Ultimately, every Verifier needs to have a list of all public keys it is willing to trust”.
References
- HCERT: Appendix A - Trust Managemen Appendix A - Trust Management source ↗
- QR §11 11. Verifying: the same steps upwards source ↗
Satisfies (smart-trust)
- UtilizeVDHC-retreive-keys Retrieve matching keys source ↗
- RetrievePKIMaterial Retrieve PKI material source ↗
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- a DSC is found by its kid, the first 8 bytes of its SHA-256 STT-TRU-001 STT-KID-001 a DSC is found by its kid, the first 8 bytes of its SHA-256
- bad trust material is refused with a typed error and nothing is added STT-TRU-001 bad trust material is refused with a typed error and nothing is added
- the xxr certificates verify through the json surface against a did document STT-TST-011 STT-TRU-001 the xxr certificates verify through the json surface against a did document
Test plans
Open issues
STT-TRU-002 · GDHCN DID trustlist v2 documents
SHALL functional direct kotlin rust open in explorer · node
The trust store SHALL load GDHCN DID trustlist v2 documents, embedded and reference variants, reading DSCs from verificationMethod publicKeyJwk (x5c) with the kid as the fragment. Fetching and caching the documents are the host's; checking their proofs is the host's choice, with the library's proof verification (STT-TRU-006) or its own.
Restates GDHCN Trustlists (DID), Trustlists 2.0.0; GDHCN Trustlists (DID), DID trustlists structure; GDHCN Trustlists (DID), Example DID documents; Endpoints, Trustlist Distribution Endpoints; W3C DID Core. GDHCN Trustlists (DID), DID trustlists structure: “Note that "-" character can be used as a wildcard on each sublevel of root.”. GDHCN Trustlists (DID), Example DID documents: “Specific keys use base64 encoded key id (kid) as identifier, It is represented as fragments (#) in verification method id”.
References
- DID: Trustlists 2.0.0 Trustlists 2.0.0 source ↗
- DID: DID trustlists structure DID trustlists structure source ↗
- DID: Example DID documents Example DID documents source ↗
- Endpoints: Trustlist Distribution Endpo Trustlist Distribution Endpoints source ↗
- 1.0 Decentralized Identifiers (DIDs) v1.0 source ↗
Satisfies (smart-trust)
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- a GDHCN DID trustlist v2 document loads its DSCs STT-TRU-002 a GDHCN DID trustlist v2 document loads its DSCs
- did documents load and trust domains come from the code system STT-TRU-002 STT-SEL-006 did documents load and trust domains come from the code system
Test plans
Open issues
STT-TRU-005 · Static trust lists
SHALL functional direct kotlin rust open in explorer · node
The trust store SHALL load DSCs from PEM and DER, for tests, pinned bilateral or national lists, and fully offline deployments.
Restates HCERT Specification, Appendix A - Trust Management; RFC 7517 JSON Web Key. HCERT Specification, Appendix A - Trust Management: “the file format of this trusted list may vary, e.g., it can be a signed JWKS”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- a static list of PEM certificates loads STT-TRU-005 a static list of PEM certificates loads
- pem loads once and candidates are found by kid STT-TRU-005 STT-KID-002 pem loads once and candidates are found by kid
- suite.ts
Test plans
STT-TRU-006 · Trust list DID document proofs
SHALL functional derived kotlin rust open in explorer · node
Both libraries SHALL verify a GDHCN trust list DID document's JsonWebSignature2020 proof against the did:web document of its verificationMethod, which the host supplies: the URDNA2015 canonical N-Quads of the document without its proof and of the proof options (the proof without jws, with the security v3 context) are each hashed with SHA-256, and the detached b64:false ES256 JWS is verified over ASCII(header) || '.' || SHA-256(options) || SHA-256(document), accepting a DER or a 64-byte r||s signature. Canonicalization SHALL use pinned copies of the contexts the signer uses (did/v1, jws-2020/v1, the tng-context for DEV, UAT or production, security v3) and SHALL fail closed on any term, key or value shape the profile does not model, so that an unmodelled term can never be silently dropped from the signed bytes. The trust store SHALL offer an add that refuses a document whose proof does not verify, adding nothing.
Engineering consequence of the GDHCN trust list as served (DID trustlist v2 documents carrying a JsonWebSignature2020 proof by WHO's signing DID) and of the owner's decision of 2026-10-23 to verify those proofs in the libraries. The signing profile is that of WorldHealthOrganization/tng-key-distribution (ld-signatures-java 1.11.0); vectors in vectors/gdhcn-dev-trustlist.
References
- XXR-DSC-did.doc.nq Test vector vectors/gdhcn-dev-trustlist/XXR-DSC-did.doc.nq source ↗
- root-did.json Test vector vectors/gdhcn-dev-trustlist/root-did.json source ↗
- XXR-DSC-did.json Test vector vectors/smart-trust-af75be2/XXR-DSC-did.json source ↗
- RFC 8785 RFC 8785 JSON Canonicalization Scheme (JCS) source ↗
- W3C Recommendation 2024-05-21 RDF Dataset Canonicalization (RDFC-1.0, formerly URDNA2015) source ↗
- CCG Report JSON Web Signature 2020 (JsonWebSignature2020 Linked Data Proof suite) source ↗
- RFC 7518 RFC 7518 JSON Web Algorithms (JWA) source ↗
- 1.0 Decentralized Identifiers (DIDs) v1.0 source ↗
Realized by skills
Constrains (WIT, CDDL)
- add-verified-did-document Same, but only after the document's proof verifies against `signer`, the did:web document of the proof's verificationMethod, which the host fetched; on any failure nothing is added. [STT-TRU-006]
- verify-did-proof Verify a GDHCN trust list DID document's JsonWebSignature2020 proof, as the TNG key distribution service makes it: URDNA2015 over the document and the proof options with pinned contexts, a detached b64:false ES256 JWS whose signature may be DER or r||s. `signer` is the did:web document of the proof's verificationMethod. Fails closed on any term the profile does not model. [STT-TRU-006]
Verified by tests
- the XXR DSC document's canonical N-Quads equal the golden files byte for byte STT-TRU-006 the XXR DSC document's canonical N-Quads equal the golden files byte for byte
- the DEV trust list documents, the 234-key root among them, verify against WHO's signing DID STT-TRU-006 the DEV trust list documents, the 234-key root among them, verify against WHO's signing DID
- a changed byte in an x5c, the wrong signer, an unmodelled term and a wrong JWS header fail proof-invalid STT-TRU-006 a changed byte in an x5c, the wrong signer, an unmodelled term and a wrong JWS header fail proof-invalid
- the trust store adds a document whose proof verifies, and nothing when it does not STT-TRU-006 the trust store adds a document whose proof verifies, and nothing when it does not
- a 64-byte r-s signature verifies as the DER one does, and so do 00-padded coordinates STT-TRU-006 a 64-byte r-s signature verifies as the DER one does, and so do 00-padded coordinates
- the signer document and the signature are checked before any key is trusted STT-TRU-006 the signer document and the signature are checked before any key is trusted
- the unified profile's document shapes, and what it refuses STT-TRU-006 STT-GEN-006 the unified profile's document shapes, and what it refuses
- the signer's key controller, and DER signatures, are held to the unified profile STT-TRU-006 STT-GEN-006 the signer's key controller, and DER signatures, are held to the unified profile
- canonical n quads equal the golden files STT-TRU-006 canonical n quads equal the golden files
- dev trust list documents verify against who signing did STT-TRU-006 dev trust list documents verify against who signing did
- a changed byte the wrong signer an unmodelled term or header is proof invalid STT-TRU-006 a changed byte the wrong signer an unmodelled term or header is proof invalid
- a 64 byte r s signature also verifies STT-TRU-006 a 64 byte r s signature also verifies
- add verified did document adds only verified documents STT-TRU-006 add verified did document adds only verified documents
- signer coordinates and profile variants STT-TRU-006 signer coordinates and profile variants
- unified profile rows give the same verdict STT-TRU-006 unified profile rows give the same verdict
- verify trusts a did document only once its proof verifies against the signer STT-TRU-006 verify trusts a did document only once its proof verifies against the signer
- smoke.mjs
- suite.ts
Test plans
Open issues
VHL · Verifiable Health Link
STT-VHL-001 · VHL payload in the SMART Health Links payload format
SHALL functional direct kotlin rust open in explorer · node
The library SHALL model the VHL payload as the SMART Health Links payload format that IHE VHL reuses: url (required, https), key (required, 43 base64url characters encoding 32 bytes), exp (optional integer, epoch seconds), flag (optional; the letters L and P in alphabetical order), label (optional, at most 80 characters), v (optional integer, default 1) and extension.fhirBaseUrl (optional). Validation SHALL report each violation. The single-file U flag is out of scope and SHALL be reported as unsupported.
Restates SMART Health Links; IHE VHL ITI-YY3, 2:3.YY3.4.1.3 Expected Actions; VerifiableHealthLinkPayload logical model (trust-phw); Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5. IHE VHL ITI-YY3 Generate VHL, 2:3.YY3.4.1.3 Expected Actions: “Create the VHL payload (using the SHL payload format)”. Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5: “Single file - ignore”.
References
- STU1 SMART Health Cards and Links: SMART Health Links source ↗
- ITI-YY3 2:3.YY3.4.1.3 2:3.YY3.4.1.3 Expected Actions source ↗
- HealthLinkPayload and VerifiableHe HealthLinkPayload and VerifiableHealthLinkPayload logical models (FSH) source ↗
- dir-vhl Owner directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5
Realized by skills
Constrains (WIT, CDDL)
- vhl-payload The SMART Health Links payload format that IHE VHL reuses. At HCERT claim -260 key 5 it is a CBOR map with these text keys (fhir-base-url is extension.fhirBaseUrl). [STT-VHL-001, STT-VHL-002]
- validate Every rule of the payload format; with `now`, an expired link too. [STT-VHL-001, STT-VHL-009]
- vhl-payload The SMART Health Links payload format that IHE VHL reuses, as a CBOR map: the primary form at key 5. [STT-VHL-001, STT-VHL-002] (OI-21, OI-23)
Verified by tests
- suite.ts
- the IHE ITI-YY3 example payload is valid, and each rule is enforced STT-VHL-001 the IHE ITI-YY3 example payload is valid, and each rule is enforced
- the ihe iti yy3 example payload is valid and each rule is enforced STT-VHL-001 the ihe iti yy3 example payload is valid and each rule is enforced
Test plans
Open issues
STT-VHL-002 · VHL payload at HCERT key 5 as a CBOR map
SHALL functional directive kotlin rust open in explorer · node
The issuer SHALL carry the VHL payload at HCERT claim -260 key 5 as a CBOR map with text keys, exp and v as CBOR integers. The verifier SHALL read key 5 as that map, and SHALL also accept a vhlink:/ text string there, so that VHLs built to IHE ITI-YY3 still verify.
Stakeholder directive, in the context of Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5; HCERT Specification, 3.2.7.1.5; IHE VHL ITI-YY3, 2:3.YY3.4.1.3 Expected Actions; IHE VHL ITI-YY4 Provide VHL. Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5: “CBOR map primary”. IHE VHL ITI-YY3 Generate VHL, 2:3.YY3.4.1.3 Expected Actions: “SHALL contain the VHL payload embedded in HCERT claim structure (claim key -260, key 5)”.
References
- dir-vhl Owner directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5
- HCERT §3.2.7 3.2.7 Health Certificate Claim source ↗
- ITI-YY3 2:3.YY3.4.1.3 2:3.YY3.4.1.3 Expected Actions source ↗
- ITI-YY4 IHE VHL ITI-YY4 Provide VHL source ↗
Derived from (broader requirement)
Realized by skills
- vhl-receive Use a VHL: verify it, build the ITI-YY5 manifest request with its HTTP Message Signature, read the manifest Bundle and decrypt each document (journey J4.1).
- vhl-share Issue a VHL: a key, documents encrypted as JWE dir / A256GCM, and the payload as a CBOR map at HCERT key 5 (journey J4.0).
Constrains (WIT, CDDL)
- payloads What an hcert entry carries: a typed DVCMin or VHL payload, or any other subclaim as encoded CBOR. [STT-GEN-003, STT-PAY-009, STT-VHL-002]
- vhl-payload The SMART Health Links payload format that IHE VHL reuses. At HCERT claim -260 key 5 it is a CBOR map with these text keys (fhir-base-url is extension.fhirBaseUrl). [STT-VHL-001, STT-VHL-002]
- hcert-vhl Verifiable Health Link [STT-VHL-002]
- vhl-payload The SMART Health Links payload format that IHE VHL reuses, as a CBOR map: the primary form at key 5. [STT-VHL-001, STT-VHL-002] (OI-21, OI-23)
Verified by tests
- suite.ts
- the payload is a CBOR map at HCERT key 5, and verifies STT-VHL-002 the payload is a CBOR map at HCERT key 5, and verifies
- a vhlink string at key 5 still verifies, and the map is smaller STT-VHL-002 STT-VHL-003 a vhlink string at key 5 still verifies, and the map is smaller
- the payload is a cbor map at hcert key 5 and verifies STT-VHL-002 the payload is a cbor map at hcert key 5 and verifies
- a vhlink string at key 5 still verifies and the map is smaller STT-VHL-002 STT-VHL-003 a vhlink string at key 5 still verifies and the map is smaller
Test plans
Open issues
STT-VHL-003 · vhlink:/ text form
SHALL functional direct kotlin rust open in explorer · node
The library SHALL convert a VHL payload to and from its text form: the minified JSON, base64url-encoded without padding (RFC 4648), prefixed with vhlink:/.
Restates IHE VHL ITI-YY3, 2:3.YY3.4.1.3 Expected Actions; RFC 4648. IHE VHL ITI-YY3 Generate VHL, 2:3.YY3.4.1.3 Expected Actions: “Prefixed with vhlink:/”.
References
- ITI-YY3 2:3.YY3.4.1.3 2:3.YY3.4.1.3 Expected Actions source ↗
- RFC 4648 RFC 4648 The Base16, Base32, and Base64 Data Encodings source ↗
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- suite.ts
- a vhlink string at key 5 still verifies, and the map is smaller STT-VHL-002 STT-VHL-003 a vhlink string at key 5 still verifies, and the map is smaller
- vhlink round-trips, and reads the IHE example link STT-VHL-003 vhlink round-trips, and reads the IHE example link
- a vhlink string at key 5 still verifies and the map is smaller STT-VHL-002 STT-VHL-003 a vhlink string at key 5 still verifies and the map is smaller
- vhlink round trips and reads the ihe example link STT-VHL-003 vhlink round trips and reads the ihe example link
Test plans
STT-VHL-004 · VHL key generation
SHALL security derived kotlin rust open in explorer · node
The library SHALL generate a VHL key as 32 bytes from the platform's cryptographically secure random generator, encoded as 43 base64url characters.
Engineering consequence of IHE VHL ITI-YY3, 2:3.YY3.4.1.3 Expected Actions; SMART Health Links.
References
- ITI-YY3 2:3.YY3.4.1.3 2:3.YY3.4.1.3 Expected Actions source ↗
- STU1 SMART Health Cards and Links: SMART Health Links source ↗
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
- new-key A new key from the platform's secure random generator. [STT-VHL-004]
- hcert-verifier Verifier: what a scanning app or border system embeds. The host supplies secure random bytes (wasi:random), which VHL keys and JWE IVs need; the verifier itself draws none. [STT-VHL-004, STT-VHL-007]
Verified by tests
- suite.ts
- a new key is 32 random bytes, base64url without padding STT-VHL-004 a new key is 32 random bytes, base64url without padding
- a new key is 32 random bytes base64url without padding STT-VHL-004 a new key is 32 random bytes base64url without padding
Test plans
STT-VHL-005 · ITI-YY5 manifest request
SHALL functional direct kotlin rust open in explorer · node
The library SHALL build the ITI-YY5 Retrieve Manifest request from a VHL payload: POST to <base>/List/_search (accepting a manifest URL of either the List?... or the List/_search?... form), Content-Type application/x-www-form-urlencoded, body holding the manifest URL's search parameters (_id, code, status and patient.identifier required; _include=List:item optional) plus recipient (required), passcode (required when, and only when, the payload's flag has P) and embeddedLengthMax (optional). It SHALL refuse a payload whose URL lacks a required parameter. Sending the request is the host's.
Restates IHE VHL ITI-YY5, 2:3.YY5.4.1.2 Message Semantics; Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5. IHE VHL ITI-YY5 Retrieve Manifest, 2:3.YY5.4.1.2 Message Semantics: “The VHL Receiver performs an HTTP POST to the _search endpoint with the manifest URL parameters from the VHL payload”. Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5: “Manifest -> go with IHE VHL”.
References
- ITI-YY5 2:3.YY5.4.1.2 2:3.YY5.4.1.2 Message Semantics source ↗
- dir-vhl Owner directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- suite.ts
- the ITI-YY5 request is the exact body of the IHE example STT-VHL-005 the ITI-YY5 request is the exact body of the IHE example
- a List URL is searched, and the passcode follows the P flag STT-VHL-005 a List URL is searched, and the passcode follows the P flag
- the iti yy5 request is the exact body of the ihe example STT-VHL-005 the iti yy5 request is the exact body of the ihe example
- a list url is searched and the passcode follows the p flag STT-VHL-005 a list url is searched and the passcode follows the p flag
Test plans
Open issues
STT-VHL-006 · ITI-YY5 manifest response
SHALL functional direct kotlin rust open in explorer · node
The library SHALL read the ITI-YY5 response, a FHIR searchset Bundle: the List with search.mode match and, when the Include DocumentReference option was used, the DocumentReferences with search.mode include. For each document it SHALL return the attachment URL and the decrypted content type (attachment.contentType); for List items whose DocumentReference is not included it SHALL return the reference for the host to read.
Restates IHE VHL ITI-YY5, 2:3.YY5.4.2.2 Message Semantics; IHE VHL ITI-YY5, 2:3.YY5.4.2.4 Document Content Retrieval. IHE VHL ITI-YY5 Retrieve Manifest, 2:3.YY5.4.2.2 Message Semantics: “the Retrieve Manifest response is — at the FHIR level — an MHD ITI-66 response message”.
References
- ITI-YY5 2:3.YY5.4.2.2 2:3.YY5.4.2.2 Message Semantics source ↗
- ITI-YY5 2:3.YY5.4.2.4 2:3.YY5.4.2.4 Document Content Retrieval source ↗
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- suite.ts
- the IHE Case B manifest Bundle reads as documents STT-VHL-006 the IHE Case B manifest Bundle reads as documents
- a Case A manifest without includes hands back references STT-VHL-006 a Case A manifest without includes hands back references
- the ihe case b manifest bundle reads as documents STT-VHL-006 the ihe case b manifest bundle reads as documents
- a case a manifest without includes hands back references STT-VHL-006 a case a manifest without includes hands back references
Test plans
Open issues
STT-VHL-007 · Document encryption: JWE dir / A256GCM
SHALL security direct kotlin rust open in explorer · node
Document binaries SHALL be encrypted and decrypted as JWE Compact Serialization (RFC 7516) with alg dir and enc A256GCM (RFC 7518), the VHL key as the content key and the protected header as additional authenticated data, following SMART Health Links. Decryption SHALL refuse any other alg or enc, SHALL accept zip DEF, and SHALL fail on a bad tag. Encryption SHALL set cty to the document's content type.
Restates IHE VHL ITI-YY5, 2:3.YY5.4.2.5 Document Encryption; RFC 7516; RFC 7518; Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5. IHE VHL ITI-YY5 Retrieve Manifest, 2:3.YY5.4.2.5 Document Encryption: “VHL document binaries are encrypted following the SMART Health Links — Encrypting and Decrypting Files convention”. Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5: “Encryption as VHL”.
References
- ITI-YY5 2:3.YY5.4.2.5 2:3.YY5.4.2.5 Document Encryption source ↗
- RFC 7516 RFC 7516 JSON Web Encryption (JWE) source ↗
- RFC 7518 RFC 7518 JSON Web Algorithms (JWA) source ↗
- dir-vhl Owner directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5
Derived from (broader requirement)
Realized by skills
- vhl-receive Use a VHL: verify it, build the ITI-YY5 manifest request with its HTTP Message Signature, read the manifest Bundle and decrypt each document (journey J4.1).
- vhl-share Issue a VHL: a key, documents encrypted as JWE dir / A256GCM, and the payload as a CBOR map at HCERT key 5 (journey J4.0).
Constrains (WIT, CDDL)
- encrypt JWE Compact, dir / A256GCM, the payload key. [STT-VHL-007]
- hcert-verifier Verifier: what a scanning app or border system embeds. The host supplies secure random bytes (wasi:random), which VHL keys and JWE IVs need; the verifier itself draws none. [STT-VHL-004, STT-VHL-007]
Verified by tests
- suite.ts
- decrypts what node crypto encrypted, with and without zip DEF STT-VHL-007 decrypts what node crypto encrypted, with and without zip DEF
- a wrong key, a changed byte and another enc are refused STT-VHL-007 a wrong key, a changed byte and another enc are refused
- decrypts what node crypto encrypted with and without zip def STT-VHL-007 decrypts what node crypto encrypted with and without zip def
- a wrong key a changed byte and another enc are refused STT-VHL-007 a wrong key a changed byte and another enc are refused
- a document is encrypted as compact JWE dir A256GCM STT-VHL-007 a document is encrypted as compact JWE dir A256GCM
- a document is encrypted as compact jwe dir a256gcm STT-VHL-007 a document is encrypted as compact jwe dir a256gcm
Test plans
Open issues
STT-VHL-008 · Receiver authentication: HTTP Message Signatures
SHALL security direct kotlin rust open in explorer · node
For the ITI-YY5 HTTP Message Signatures option the library SHALL compute Content-Digest (RFC 9530, sha-256, Structured Field byte sequence) and the RFC 9421 signature base over @method, @path, @authority, content-type and content-digest with created, keyid and alg parameters, return the bytes for the host to sign with the receiver's key, and assemble the Signature-Input and Signature headers from the result. For ecdsa-p256-sha256 the signature SHALL be the 64-byte r||s; a DER signature SHALL be converted.
Restates IHE VHL ITI-YY5, 2:3.YY5.4.1.3 HTTP Message Signatures; RFC 9421; RFC 9530; Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5. IHE VHL ITI-YY5 Retrieve Manifest, 2:3.YY5.4.1.3 Authentication Option - HTTP Message Signatures: “Implementations MAY authenticate using HTTP Message Signatures per RFC 9421”. Stakeholder directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5: “Trust - IHE VHL”.
References
- ITI-YY5 2:3.YY5.4.1.3 2:3.YY5.4.1.3 Authentication Option - HTTP Message Signatures source ↗
- RFC 9421 RFC 9421 HTTP Message Signatures source ↗
- RFC 9530 RFC 9530 Digest Fields source ↗
- dir-vhl Owner directive: Verifiable Health Links follow IHE VHL, CBOR map at key 5
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- suite.ts
- the Content-Digest is RFC 9530 STT-VHL-008 the Content-Digest is RFC 9530
- the RFC 9421 signature base has the IHE shape STT-VHL-008 the RFC 9421 signature base has the IHE shape
- a DER signature becomes the raw header value and verifies STT-VHL-008 a DER signature becomes the raw header value and verifies
- the content digest is rfc 9530 STT-VHL-008 the content digest is rfc 9530
- the rfc 9421 signature base has the ihe shape STT-VHL-008 the rfc 9421 signature base has the ihe shape
- a der signature becomes the raw header value and verifies STT-VHL-008 a der signature becomes the raw header value and verifies
Test plans
Open issues
STT-VHL-009 · VHL expiry
SHALL functional direct kotlin rust open in explorer · node
Validating a VHL payload against a caller-supplied now SHALL report an expired link when the payload's exp has passed, in addition to the HCERT exp check made by the verifier.
Restates IHE VHL ITI-YY4 Provide VHL. IHE VHL ITI-YY4 Provide VHL: “Check the payload's exp (if present) - reject if current time > expiration”.
References
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- suite.ts
- an expired link fails verification, by its own exp STT-VHL-009 an expired link fails verification, by its own exp
- an expired link fails verification by its own exp STT-VHL-009 an expired link fails verification by its own exp
Test plans
STT-VHL-010 · VHL with no I/O in the library
SHALL architecture derived kotlin rust open in explorer · node
The VHL functions SHALL do no network, file or clock access: the host sends the ITI-YY5 request and the ITI-68 document GETs over mutual TLS, holds the receiver's signing key, and logs AuditEvents. The library builds, parses, signs-by-proxy and decrypts.
Engineering consequence of IHE VHL ITI-YY5, 2:3.YY5.4.2.4 Document Content Retrieval; Stakeholder directive: the library must carry the workshop user journeys. IHE VHL ITI-YY5 Retrieve Manifest, 2:3.YY5.4.2.4 Document Content Retrieval: “Transport: Same ATNA secure channel used for ITI-YY5”.
References
- ITI-YY5 2:3.YY5.4.2.4 2:3.YY5.4.2.4 Document Content Retrieval source ↗
- dir-journeys Owner directive: the library must carry the workshop user journeys
Derived from (broader requirement)
Realized by skills
- vhl-receive Use a VHL: verify it, build the ITI-YY5 manifest request with its HTTP Message Signature, read the manifest Bundle and decrypt each document (journey J4.1).
- vhl-share Issue a VHL: a key, documents encrypted as JWE dir / A256GCM, and the payload as a CBOR map at HCERT key 5 (journey J4.0).
Constrains (WIT, CDDL)
Verified by tests
Test plans
ISS · Issuance
STT-ISS-003 · Test keys never in production artifacts
SHALL NOT security derived kotlin rust open in explorer · node
Fixed test keys and test PKI (such as the XXA SCA and DSC) SHALL only ship in the testkit module. Production modules SHALL NOT contain or accept them by default.
Engineering consequence of QR Code Generation, 12.3 Issuer. QR Code Generation, 12.3 Issuer: “The private key is held in the build environment and never enters this repository”.
References
Realized by skills
Verified by tests
- a test dsc around a host key issues and verifies STT-ISS-003 a test dsc around a host key issues and verifies
- no private key, and no test key, is in the library's sources STT-ISS-003 no private key, and no test key, is in the library's sources
Test plans
STT-ISS-004 · Two-phase signing
SHALL api derived kotlin rust open in explorer · node
Issuance SHALL be two-phase: prepare returns the exact bytes to sign (the Sig_structure), the caller signs them by any means, including asynchronous ones such as WebCrypto or a remote KMS, and finish assembles the certificate and returns the HC1 string and the COSE bytes. For ES256, finish SHALL accept r||s or DER and SHALL always emit r||s.
Engineering consequence of HCERT Specification, 6.2 Key Management; W3C Web Cryptography API; QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1. QR Code Generation, 6. Step 5 — Sign as a COSE_Sign1: “Converting between the two is the single most common interoperability bug in HCERT implementations.”.
References
- HCERT §6.2 6.2 Key Management source ↗
- W3C Recommendation Web Cryptography API source ↗
- QR §6 6. Step 5 — Sign as a COSE_Sign1 source ↗
Derived from (broader requirement)
Realized by skills
Constrains (WIT, CDDL)
Verified by tests
- a certificate with a dvc and a vhl verifies with both entries STT-SEL-004 STT-ISS-004 a certificate with a dvc and a vhl verifies with both entries
- two phase issuance with es256 and ps256 verifies STT-ISS-004 STT-COS-005 two phase issuance with es256 and ps256 verifies
- a certificate with a DVC and a VHL verifies with both entries STT-SEL-004 STT-ISS-004 a certificate with a DVC and a VHL verifies with both entries
- what is signed is the Signature1 structure prepare returns STT-COS-004 STT-ISS-004 what is signed is the Signature1 structure prepare returns
- suite.ts
Test plans
Open issues
SEC · Security
STT-SEC-001 · No payload content in logs
SHALL NOT security derived kotlin rust open in explorer · node
The libraries SHALL NOT log payload content, HC1 strings or decoded claims at any default log level. A QR code holds readable personal data.
Engineering consequence of QR Code Generation, 2.2 Choosing between a DVC and a VHL; HCERT Specification, 6.1 HCERT Signature Validity Time. QR Code Generation, 2.2 Choosing between a DVC and a VHL: “All data is in the QR and is readable by anyone who scans it”.
References
- QR §2 2. Step 1 — Select the payload type source ↗
- HCERT §6.1 6.1 HCERT Signature Validity Time source ↗
Realized by skills
Verified by tests
- a failure's message does not carry the payload STT-SEC-001 a failure's message does not carry the payload
- the core writes nothing to a log or console STT-SEC-001 the core writes nothing to a log or console
Test plans
STT-SEC-002 · Vetted crypto backends
SHALL security derived kotlin rust open in explorer · node
Cryptography SHALL use platform or vetted libraries: JCA/BouncyCastle or Android Keystore for Kotlin JVM/Android, RustCrypto (p256, rsa, sha2) or ring/aws-lc-rs for Rust. Signature verification SHALL be constant-time where the backend allows. The libraries SHALL NOT contain their own implementations of cryptographic primitives.
Engineering consequence of HCERT Specification, 6.2 Key Management.
References
Realized by skills
Verified by tests
- build.gradle.kts
- cryptography comes from the platform, with no third-party runtime dependency STT-SEC-002 STT-GEN-004 cryptography comes from the platform, with no third-party runtime dependency
Test plans
NFR · Non-functional
STT-NFR-001 · Versioning and spec traceability
SHALL non-functional derived kotlin rust open in explorer · node
Releases SHALL follow semantic versioning. A change to the wire format or to subclaim defaults SHALL be a major or minor version change, as documented in a changelog that cites the smart-trust change (changes.md entry) it implements.
Engineering consequence of Change Log, v1.7.5; QR Code Generation, 9. Step 8 — Prefix the context identifier.
References
Derived from (broader requirement)
Realized by skills
STT-NFR-002 · Distribution
SHALL non-functional directive kotlin rust open in explorer · node
The Kotlin library SHALL be published to Maven Central (JVM, Android AAR) and to npm (Kotlin/JS). The Rust library SHALL be published to crates.io, and its jco-transpiled WebAssembly component to npm. All coordinates are still to be chosen.
Stakeholder directive, in the context of Stakeholder directive: Kotlin library first, Rust library second; Stakeholder directive: transpile targets and API-first.
References
- dir-languages Owner directive: Kotlin library first, Rust library second
- dir-targets Owner directive: transpile targets and API-first
Realized by skills
Open issues
TST · Conformance testing
STT-TST-001 · PR 129 example as a conformance vector
SHALL test derived kotlin rust open in explorer · node
Both libraries SHALL verify the two HC1 strings in dvc-qr-example-XXA.json against dvc-qr-example-XXA-dsc.pem, with the clock fixed inside 2026-09-29T08:30Z to 2027-09-29T08:30Z. Each SHALL give kid WHM+BYqqZiE=, subclaim -6, and payloads equal to those in the JSON. The committed files SHALL be used as-is; they SHALL NOT be regenerated.
Engineering consequence of the DVC QR example as published on PR 129's branch (WHO 50bcb25, test participant XXA): its machine-readable summary and test DSC. The merged guide (af75be2) reissued the example under DEV participant XXR without publishing the DSC (STT-TST-011), so this pair remains the end-to-end verification vector.
References
- dvc-qr-example-XXA.json Test vector vectors/smart-trust-pr129/dvc-qr-example-XXA.json source ↗
- dvc-qr-example-XXA-dsc.pem Test vector vectors/smart-trust-pr129/dvc-qr-example-XXA-dsc.pem source ↗
Realized by skills
Verified by tests
- the vector's kid is the DSC's STT-TST-001 STT-KID-001 the vector's kid is the DSC's
- both PR 129 certificates verify and decode as published STT-TST-001 STT-PAY-001 both PR 129 certificates verify and decode as published
- the vectors kid is the dscs STT-TST-001 STT-KID-001 the vectors kid is the dscs
- both pr129 certificates verify and decode as published STT-TST-001 STT-PAY-001 both pr129 certificates verify and decode as published
- the pr129 vector verifies through the json surface STT-TST-001 the pr129 vector verifies through the json surface
- suite.ts
Test plans
Open issues
STT-TST-002 · Byte-exact CBOR vector
SHALL test derived kotlin rust open in explorer · node
Encoding a published example's claim set with deterministic CBOR SHALL give exactly the payload bytes inside its COSE_Sign1: 222 bytes for the XXR hepb1 certificate and 221 for bcg, 210 and 209 for the XXA pair. The merged guide's XXR certificates are published as fixed strings, so their COSE_Sign1, deflated and HC1 sizes SHALL equal those the vector records, which for hepatitis B are the 307, 293 and 444 that section 12.5 states. Sizes of newly issued certificates SHALL NOT be asserted exactly, because signatures are randomized.
Engineering consequence of QR Code Generation, 12.5 Size at each stage, as merged (WHO af75be2), and of the PR 129 branch's machine-readable summary (test participant XXA).
References
- QR §12.5 12.5 Size at each stage source ↗
- dvc-qr-example-XXR.json Test vector vectors/smart-trust-af75be2/dvc-qr-example-XXR.json source ↗
- dvc-qr-example-XXA.json Test vector vectors/smart-trust-pr129/dvc-qr-example-XXA.json source ↗
Realized by skills
Verified by tests
- deterministic re-encoding reproduces the issued CWT byte for byte STT-TST-002 STT-CBR-001 deterministic re-encoding reproduces the issued CWT byte for byte
- the merged guide's XXR certificates decode as published STT-TST-011 STT-TST-002 the merged guide's XXR certificates decode as published
- deterministic re encoding reproduces the issued cwt byte for byte STT-TST-002 STT-CBR-001 deterministic re encoding reproduces the issued cwt byte for byte
- the merged guides xxr certificates decode as published STT-TST-011 STT-TST-002 the merged guides xxr certificates decode as published
- suite.ts
Test plans
Open issues
STT-TST-003 · Cross-implementation interoperability
SHALL test derived kotlin rust open in explorer · node
Certificates issued by each library (ES256 and PS256) SHALL verify in the other library, and the two libraries SHALL build the same Sig_structure and COSE_Sign1 bytes from the same request and signature.
Engineering consequence of the stakeholder directive: Kotlin library first, Rust library second. The reference-script clause was dropped on 2026-10-22 (owner's decision): WHO PR 129 removed generate_dvc_qr.py from the guide, and its certificates are already covered by the PR 129 vector (STT-TST-001).
References
Derived from (broader requirement)
Realized by skills
Verified by tests
Test plans
Open issues
STT-TST-004 · Negative vectors
SHALL test derived kotlin rust open in explorer · node
The shared vector set SHALL include failing cases with the expected failure reason for each: tampered payload, tampered protected header, wrong or unknown kid, duplicate kid with only the second DSC valid, a DER-encoded ES256 signature, expired, iat before the DSC validity, EKU mismatch, missing or unsupported prefix (HC2:), invalid Base45, decompression bomb, deeply nested CBOR, multiple subclaims, and a DVC without is or cn.
Engineering consequence of QR Code Generation, 11. Verifying: the same steps upwards; HCERT Specification, 6.3 Input Data Validation.
References
Realized by skills
Verified by tests
- the shared negative vectors fail at the named step with the named reason STT-TST-004 STT-TST-005 the shared negative vectors fail at the named step with the named reason
- the shared negative vectors fail at the named step with the named reason STT-TST-004 STT-TST-005 the shared negative vectors fail at the named step with the named reason
- suite.ts
Test plans
STT-TST-005 · Language-neutral vector format
SHALL test derived kotlin rust open in explorer · node
Conformance vectors SHALL be stored once, in a language-neutral format (JSON plus PEM), in this repository, and loaded by both test suites.
Engineering consequence of DVC QR example, machine-readable summary (test participant XXA); Stakeholder directive: Kotlin library first, Rust library second.
References
- dvc-qr-example-XXA.json Test vector vectors/smart-trust-pr129/dvc-qr-example-XXA.json source ↗
- dir-languages Owner directive: Kotlin library first, Rust library second
- negative-vectors.json Test vector vectors/negative/negative-vectors.json source ↗
Derived from (broader requirement)
Realized by skills
Verified by tests
- the shared negative vectors fail at the named step with the named reason STT-TST-004 STT-TST-005 the shared negative vectors fail at the named step with the named reason
- node-smoke.mjs
- the shared negative vectors fail at the named step with the named reason STT-TST-004 STT-TST-005 the shared negative vectors fail at the named step with the named reason
- suite.ts
- component-test.mjs
Test plans
Open issues
STT-TST-006 · Fuzzing of decoders
SHALL test derived kotlin rust open in explorer · node
The Base45, ZLIB, CBOR, COSE and full verifier entry points SHALL be fuzzed in CI. No input may crash them, hang them, or exceed DecodeLimits. Rust SHALL use a coverage-guided fuzzer (cargo-fuzz). Kotlin SHALL use a seeded mutation fuzzer in its own test suite, which needs no dependency; a coverage-guided JVM fuzzer (Jazzer) MAY replace it once it can be pinned under the dependency policy.
Engineering consequence of HCERT Specification, 6.3 Input Data Validation. HCERT Specification, 6.3 Input Data Validation: “a potential attacker may attempt to inject carefully crafted information designed to compromise the security of the system”.
References
Realized by skills
Verified by tests
- fuzzed Base45 never throws and never decodes more than its input STT-TST-006 fuzzed Base45 never throws and never decodes more than its input
- fuzzed ZLIB never throws and never inflates past the limit STT-TST-006 fuzzed ZLIB never throws and never inflates past the limit
- fuzzed CBOR never throws in the deterministic re-encoder STT-TST-006 fuzzed CBOR never throws in the deterministic re-encoder
- fuzzed COSE never throws in the Sign1 parser STT-TST-006 fuzzed COSE never throws in the Sign1 parser
- fuzzed HC1 strings never throw in the QR layers or the verifier STT-TST-006 fuzzed HC1 strings never throw in the QR layers or the verifier
- fuzzed COSE bytes never throw in the verifier STT-TST-006 fuzzed COSE bytes never throw in the verifier
- fuzzed base45 never panics and never decodes more than its input STT-TST-006 fuzzed base45 never panics and never decodes more than its input
- fuzzed zlib never panics and never inflates past the limit STT-TST-006 fuzzed zlib never panics and never inflates past the limit
- fuzzed cbor never panics in the deterministic re encoder STT-TST-006 fuzzed cbor never panics in the deterministic re encoder
- fuzzed cose never panics in the sign1 parser STT-TST-006 fuzzed cose never panics in the sign1 parser
- fuzzed hc1 strings never panic in the qr layers or the verifier STT-TST-006 fuzzed hc1 strings never panic in the qr layers or the verifier
- fuzzed cose bytes never panic in the verifier STT-TST-006 fuzzed cose bytes never panic in the verifier
Test plans
Open issues
STT-TST-007 · One TypeScript conformance suite for both Node packages
SHALL test derived kotlin rust open in explorer · node
A single TypeScript test suite, type-checked against api/generated/ts and driven by the shared vectors, SHALL run in CI against both the Kotlin/JS npm package and the jco-transpiled Rust component, and SHALL pass on both.
Engineering consequence of Stakeholder directive: transpile targets and API-first; DVC QR example, machine-readable summary (test participant XXA).
References
- dir-targets Owner directive: transpile targets and API-first
- dvc-qr-example-XXA.json Test vector vectors/smart-trust-pr129/dvc-qr-example-XXA.json source ↗
Derived from (broader requirement)
Realized by skills
Verified by tests
Test plans
Open issues
STT-TST-008 · Schema checks against the vectors
SHALL test derived kotlin rust open in explorer · node
CI SHALL validate every conformance vector against the CDDL (COSE_Sign1, protected header, CWT claims) and its payload against the SHACL shapes through the JSON-LD context. It SHALL also check that mutated payloads (invariant broken, bad FHIR date, missing required element, bad FHIR id) are rejected and that valid variants (cn only, partial date) are accepted, by both CDDL and SHACL.
Engineering consequence of DVC QR example, machine-readable summary (test participant XXA); RFC 8610 Concise Data Definition Language (CDDL); W3C Shapes Constraint Language (SHACL).
References
- dvc-qr-example-XXA.json Test vector vectors/smart-trust-pr129/dvc-qr-example-XXA.json source ↗
- RFC 8610 RFC 8610 Concise Data Definition Language (CDDL) source ↗
- W3C Recommendation 2017 Shapes Constraint Language (SHACL) source ↗
Realized by skills
Verified by tests
Test plans
STT-TST-009 · Agreement with the WHO Interoperability Test Bed
SHALL test derived kotlin rust open in explorer · node
Each library SHALL reach the same result as the WHO-ITB gdhcn-helper on every WHO-ITB HCERT track's test data: the same COSE bytes, kid, issuer and signature verdict, and, for the VHL track, the same link, manifest and decrypted content. FHIR profile validation and StructureMap transforms stay with the ITB; the library's part ends at the payload claim it hands over.
Engineering consequence of the WHO-ITB HCERT test suites (Tracks 1 to 3), the conformance tests WHO runs for GDHCN trust network participants. A library that disagrees with the ITB on a track's decode or verification would fail participants the ITB passes.
References
- WHO-ITB: Interoperability Test Bed WHO-ITB: Interoperability Test Bed with HCERT validation (GDHCN Trust Network) source ↗
- dir-languages Owner directive: Kotlin library first, Rust library second
Derived from (broader requirement)
Realized by skills
Test plans
Open issues
STT-TST-010 · Coverage floor
SHALL test derived kotlin rust open in explorer · node
Each library's CI SHALL measure the coverage of its core by its own tests, line and branch coverage where its stable toolchain measures branches and line and region coverage where it does not, and SHALL fail when any measure falls below a recorded floor. A floor SHALL be raised as tests are added and SHALL NOT be lowered to let a change pass.
Engineering consequence of the test plan hcert-library-conformance: a named test per requirement shows that each requirement is exercised, not that each branch of the decoders is. Coverage measured in CI, with a floor that only rises, keeps new code from arriving untested.
References
Derived from (broader requirement)
Realized by skills
Verified by tests
Test plans
STT-TST-011 · The merged guide's example decodes as published
SHALL test derived kotlin rust open in explorer · node
Both libraries SHALL decode the two HC1 strings of QR Code Generation 12.6 without verifying them, and SHALL read kid kW/6VnwPRKI=, iss XR, subclaim -6 and the payloads of 12.4, with the PreQual product identifiers in vp. Without the XXR DSC, verification SHALL stop at TRUST_LOOKUP with KID_NOT_FOUND. With XXR's DSC DID document from the GDHCN DEV trust list in the trust store, both certificates SHALL verify, with the payload's key usage authorised and no diagnostics.
Engineering consequence of QR Code Generation, 12.6 The certificates, as merged (WHO af75be2): two DVC certificates issued by GDHCN DEV participant XXR, whose DSC is on the DEV trust list. QR Code Generation, 12.6 The certificates: “To verify them, resolve that kid against the GDHCN trust list rather than against any copy of the certificate shipped beside the QR code.”.
References
- QR §12.6 12.6 The certificates source ↗
- QR §12.4 12.4 The payload source ↗
- dvc-qr-example-XXR.json Test vector vectors/smart-trust-af75be2/dvc-qr-example-XXR.json source ↗
- XXR-DSC-did.json Test vector vectors/smart-trust-af75be2/XXR-DSC-did.json source ↗
Realized by skills
Verified by tests
- the merged guide's XXR certificates decode as published STT-TST-011 STT-TST-002 the merged guide's XXR certificates decode as published
- the merged guide's XXR certificates verify against the DEV trust list's DSC STT-TST-011 the merged guide's XXR certificates verify against the DEV trust list's DSC
- the merged guides xxr certificates decode as published STT-TST-011 STT-TST-002 the merged guides xxr certificates decode as published
- the merged guides xxr certificates verify against the dev trust lists dsc STT-TST-011 the merged guides xxr certificates verify against the dev trust lists dsc
- the xxr certificates verify through the json surface against a did document STT-TST-011 STT-TRU-001 the xxr certificates verify through the json surface against a did document
- suite.ts