SMART Trust HCERT library tools
SMART Trust Tools
Library API contract · WIT package who:smart-trust@0.1.0

API (WIT)

The API is defined first, in WIT, the WebAssembly Component Model interface language. Rust exports it from its WebAssembly component; Kotlin follows it through fixed mapping rules. Requirement tags such as [STT-COS-002] link into the graph explorer. For the same API as JavaScript callers see it, open the TypeScript reference, generated by jco and TypeDoc.

Worlds: the WebAssembly components the Rust build ships

WorldImports (host provides)Exports
hcert-codec—types codec cose
hcert-verifierwasitypes codec cose subclaims dvc vhl payloads trust verifier
hcert-issuerwasitypes codec cose subclaims dvc vhl payloads issuer
hcert—includes hcert-verifier, hcert-issuer

Files

types.wit

source
package who:smart-trust@0.1.0;

/// Types shared by every interface.
///
/// WIT has no recursive types, so arbitrary CBOR never crosses the API
/// boundary as a tree: it crosses as encoded bytes (`cbor-bytes`). The typed
/// payload (DVCMin) crosses as a record.
interface types {
    /// COSE algorithm. es256 = -7 (primary), ps256 = -37 (secondary).
    /// Both MUST be implemented. [STT-COS-002]
    enum cose-algorithm { es256, ps256 }

    /// 8-byte key identifier: first 8 bytes of SHA-256 over the DER DSC.
    /// [STT-KID-001]
    type kid = list<u8>;

    /// CWT NumericDate: integer seconds since the Unix epoch. [STT-CWT-002]
    type numeric-date = s64;

    /// Encoded CBOR data item.
    type cbor-bytes = list<u8>;

    /// An hcert subclaim number and what it carries. Negative numbers are
    /// the development range. [STT-SEL-001, STT-SEL-003]
    record subclaim {
        key: s32,
        title: string,
        /// Canonical URL of the FHIR logical model, when there is one.
        logical-model: option<string>,
    }

    /// Bounds applied while decoding untrusted input. [STT-CBR-003,
    /// STT-ZIP-002]
    record decode-limits {
        max-input-chars: u32,
        max-inflated-bytes: u32,
        max-depth: u32,
        max-items: u32,
        max-string-bytes: u32,
    }

    enum severity { blocking, warning, information }

    /// One payload validation finding. `path` names the element.
    record validation-issue {
        path: string,
        code: string,
        message: string,
        severity: severity,
    }

    /// Verification steps, in the order they run. [STT-VER-001]
    enum step {
        prefix,
        base45,
        inflate,
        cose,
        header,
        trust-lookup,
        signature,
        key-usage,
        cwt,
        time,
        hcert,
        payload,
    }

    /// Why verification stopped. [STT-VER-008]
    enum failure-reason {
        bad-prefix,
        unsupported-context-version,
        base45-invalid,
        inflate-failed,
        limit-exceeded,
        cose-malformed,
        header-invalid,
        unsupported-algorithm,
        kid-not-found,
        signature-invalid,
        key-usage-mismatch,
        expired,
        not-yet-valid,
        hcert-missing,
        multiple-subclaims,
        payload-invalid,
    }

    /// A non-fatal observation made while verifying.
    record diagnostic {
        step: step,
        severity: severity,
        message: string,
    }
}

codec.wit

source
package who:smart-trust@0.1.0;

/// Steps 4, 6, 7, 8: CBOR, ZLIB, Base45 and the HC1 context identifier.
/// Pure functions: no crypto, no I/O, no clock.
interface codec {
    use types.{decode-limits, cbor-bytes};

    variant codec-error {
        /// Character outside the alphabet, dangling character or overflow.
        /// [STT-B45-002]
        base45-invalid(string),
        inflate-failed(string),
        /// A decode-limits bound was hit. [STT-CBR-003, STT-ZIP-002]
        limit-exceeded(string),
        cbor-malformed(string),
        /// No `HC[1-9A-Z]:` context identifier. [STT-HC1-001]
        bad-prefix,
        /// A well-formed context identifier other than HC1, e.g. "HC2".
        unsupported-context-version(string),
    }

    /// RFC 9285 Base45. [STT-B45-001]
    base45-encode: func(data: list<u8>) -> string;
    base45-decode: func(text: string) -> result<list<u8>, codec-error>;

    /// ZLIB (RFC 1950) wrapping DEFLATE (RFC 1951). [STT-ZIP-001]
    zlib-compress: func(data: list<u8>, level: u8) -> list<u8>;
    zlib-inflate: func(data: list<u8>, max-out: u32) -> result<list<u8>, codec-error>;

    /// Returns the context identifier version character (e.g. "1") and
    /// the remainder after the colon. [STT-HC1-001]
    detect-context: func(text: string) -> result<tuple<string, string>, codec-error>;

    /// Steps 6 to 8: COSE bytes to "HC1:..." string. The result uses only
    /// QR Alphanumeric characters. [STT-QR-001]
    to-hc1: func(cose: list<u8>) -> string;
    /// Steps 8 to 6 reversed: "HC1:..." string to COSE bytes.
    from-hc1: func(text: string, limits: decode-limits) -> result<list<u8>, codec-error>;

    /// Re-encode CBOR using RFC 8949 core deterministic encoding.
    /// [STT-CBR-001]
    cbor-deterministic: func(cbor: cbor-bytes, limits: decode-limits) -> result<cbor-bytes, codec-error>;
}

cose.wit

source
package who:smart-trust@0.1.0;

/// Step 5: COSE_Sign1, key identifiers and signature formats.
interface cose {
    use types.{cose-algorithm, kid, decode-limits};

    variant cose-error {
        malformed(string),
        /// Protected header failed type/length checks. [STT-VER-003]
        header-invalid(string),
        unsupported-algorithm(s32),
        /// ES256 signature not exactly 64 bytes, or bad DER. [STT-COS-005]
        bad-signature-format(string),
        signature-invalid,
        bad-certificate(string),
    }

    /// A parsed COSE_Sign1. `protected` is kept as received so the
    /// Sig_structure is rebuilt from the original bytes. [STT-COS-004]
    record sign1 {
        protected: list<u8>,
        payload: list<u8>,
        signature: list<u8>,
        alg: option<cose-algorithm>,
        /// The protected kid if present, else the unprotected kid.
        /// [STT-COS-003]
        kid: option<kid>,
        kid-in-protected: bool,
        /// Whether the input carried CBOR tag 18. [STT-COS-001]
        tagged: bool,
    }

    /// First 8 bytes of SHA-256 over the DER-encoded DSC. [STT-KID-001]
    kid-from-dsc: func(dsc-der: list<u8>) -> kid;

    enum base64-alphabet { standard, url-safe }

    /// [STT-KID-003]
    kid-to-base64: func(k: kid, alphabet: base64-alphabet) -> string;
    kid-from-base64: func(text: string) -> result<kid, cose-error>;

    parse-sign1: func(bytes: list<u8>, limits: decode-limits) -> result<sign1, cose-error>;

    /// CBOR of ["Signature1", protected, h'', payload].
    sig-structure: func(protected: list<u8>, payload: list<u8>) -> list<u8>;

    /// Verify a parsed COSE_Sign1 against one DSC. Low level: does not
    /// check EKU, time or trust. [STT-COS-002]
    verify-sign1: func(s: sign1, dsc-der: list<u8>) -> result<_, cose-error>;

    /// ASN.1 DER ECDSA-Sig-Value <-> fixed-width r||s. [STT-COS-005]
    ecdsa-der-to-raw: func(der: list<u8>) -> result<list<u8>, cose-error>;
    ecdsa-raw-to-der: func(raw: list<u8>) -> result<list<u8>, cose-error>;
}

trust.wit

source
package who:smart-trust@0.1.0;

/// Trust material. The host fetches it (GDHCN DID trustlist, pinned PEM)
/// and fills the store, so the core never does network I/O and stays
/// synchronous. [STT-TRU-001, STT-GEN-012]
interface trust {
    use types.{kid, numeric-date};

    variant trust-error {
        parse(string),
        /// A DID document names a trust domain the loaded GDHCN Trust Domain
        /// code system does not hold. [STT-SEL-006]
        unknown-trust-domain(string),
        /// A DID document's proof does not verify, or the document is not
        /// one the proof profile models. [STT-TRU-006]
        proof-invalid(string),
    }

    record trusted-dsc {
        der: list<u8>,
        kid: kid,
        country: option<string>,
        not-before: numeric-date,
        not-after: numeric-date,
        /// Extended Key Usage OIDs; empty means valid for any type.
        /// [STT-VER-004]
        extended-key-usages: list<string>,
        /// Trust domain and participant, when the DSC came from a DID
        /// trustlist document.
        domain: option<string>,
        participant: option<string>,
    }

    resource trust-store {
        constructor();
        /// Each add-* returns the number of DSCs added. [STT-TRU-005]
        add-pem: func(pem: string) -> result<u32, trust-error>;
        add-der: func(der: list<u8>) -> result<u32, trust-error>;
        /// A GDHCN DID trustlist v2 document, embedded or reference
        /// variant. [STT-TRU-002]
        add-did-document: func(json: string) -> result<u32, trust-error>;
        /// 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]
        add-verified-did-document: func(json: string, signer: string) -> result<u32, trust-error>;
        /// The trust domains a DID document may name, read from a GDHCN
        /// Trust Domain CodeSystem (FHIR JSON) the host fetched. Data, not
        /// an enum: once loaded, a DID document naming another domain is
        /// refused whole; before, any domain is kept as given. Loading
        /// again replaces the set. Returns the number of codes.
        /// [STT-SEL-006]
        load-trust-domains: func(codesystem-json: string) -> result<u32, trust-error>;
        /// The loaded trust domain codes; empty when none are loaded.
        trust-domains: func() -> list<string>;
        /// Every DSC with this kid (kids are not unique). [STT-KID-002]
        candidates: func(k: kid) -> list<trusted-dsc>;
        size: func() -> u32;
        clear: func();
    }

    /// 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]
    verify-did-proof: func(document: string, signer: string) -> result<_, trust-error>;
}

vhl.wit

source
package who:smart-trust@0.1.0;

/// Verifiable Health Links, as the IHE ITI VHL profile specifies them, with
/// the SMART Health Links payload format. Pure functions: the host does the
/// HTTP (ITI-YY5 manifest search, ITI-68 document GETs) over mutual TLS, holds
/// the receiver's signing key and logs audit events. [STT-VHL-010]
interface vhl {
    use types.{validation-issue, numeric-date};

    /// 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]
    record vhl-payload {
        /// Manifest URL: a FHIR List search with _id, code, status and
        /// patient.identifier.
        url: string,
        /// 32 random bytes, 43 base64url characters.
        key: string,
        /// Expiry, epoch seconds.
        exp: option<s64>,
        /// L and/or P, in alphabetical order.
        flag: option<string>,
        /// At most 80 characters.
        label: option<string>,
        /// Protocol version; 1 when absent.
        v: option<s32>,
        /// extension.fhirBaseUrl, for the OAuth with SSRAA option.
        fhir-base-url: option<string>,
    }

    variant vhl-error {
        invalid(list<validation-issue>),
        /// Not of the form vhlink:/<base64url JSON>.
        not-a-vhlink(string),
        manifest(string),
        /// Wrong key, a bad tag, or a malformed JWE.
        decrypt-failed(string),
        /// A JWE alg or enc other than dir / A256GCM. [STT-VHL-007]
        unsupported(string),
    }

    /// Every rule of the payload format; with `now`, an expired link too.
    /// [STT-VHL-001, STT-VHL-009]
    validate: func(p: vhl-payload, now: option<numeric-date>) -> list<validation-issue>;

    /// [STT-VHL-003]
    to-vhlink: func(p: vhl-payload) -> string;
    from-vhlink: func(text: string) -> result<vhl-payload, vhl-error>;

    /// A new key from the platform's secure random generator. [STT-VHL-004]
    new-key: func() -> string;

    /// The ITI-YY5 Retrieve Manifest request for the host to POST.
    record manifest-request {
        /// <base>/List/_search
        url: string,
        /// application/x-www-form-urlencoded
        content-type: string,
        body: string,
    }

    /// [STT-VHL-005]
    build-manifest-request: func(p: vhl-payload, recipient: string, passcode: option<string>, embedded-length-max: option<u32>) -> result<manifest-request, vhl-error>;

    /// One document listed by the manifest.
    record manifest-document {
        /// DocumentReference.content.attachment.url, when the DocumentReference
        /// was included; the host GETs it (ITI-68).
        url: option<string>,
        /// List.entry.item.reference, when it was not; the host reads it first.
        reference: option<string>,
        /// The decrypted content type (attachment.contentType).
        content-type: option<string>,
        title: option<string>,
    }

    /// The ITI-YY5 searchset Bundle. [STT-VHL-006]
    parse-manifest: func(bundle-json: string) -> result<list<manifest-document>, vhl-error>;

    record decrypted {
        /// The JWE cty header, when present.
        content-type: option<string>,
        content: list<u8>,
    }

    /// JWE Compact, dir / A256GCM, the payload key. [STT-VHL-007]
    encrypt: func(content: list<u8>, content-type: string, key: string) -> result<string, vhl-error>;
    decrypt: func(jwe: string, key: string) -> result<decrypted, vhl-error>;

    /// What the receiver signs for the HTTP Message Signatures option.
    record signature-request {
        method: string,
        authority: string,
        path: string,
        content-type: option<string>,
        body: option<list<u8>>,
        keyid: string,
        /// e.g. ecdsa-p256-sha256
        alg: string,
        created: numeric-date,
    }

    record signature-base {
        /// RFC 9530 Content-Digest header value, when there is a body.
        content-digest: option<string>,
        /// Signature-Input header value.
        signature-input: string,
        /// The RFC 9421 signature base: the bytes the host signs.
        base: list<u8>,
    }

    /// [STT-VHL-008]
    build-signature-base: func(r: signature-request) -> signature-base;
    /// The Signature header value. An ecdsa-p256-sha256 DER signature is
    /// converted to r||s.
    signature-header: func(signature: list<u8>, alg: string) -> result<string, vhl-error>;
}

payload.wit

source
package who:smart-trust@0.1.0;

/// 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]
interface subclaims {
    use types.{subclaim};

    /// Payload kinds that have a typed codec. Others travel as raw CBOR.
    enum payload-kind { dvc-min, vhl }

    resource registry {
        /// Starts with the IG defaults: 1, 3, 4, 5, -6, -7; VHL is 5. [STT-SEL-001]
        constructor();
        lookup: func(key: s32) -> option<subclaim>;
        register: func(s: subclaim);
        all: func() -> list<subclaim>;
        /// Which subclaim number carries the given payload kind.
        subclaim-for: func(kind: payload-kind) -> option<s32>;
        set-subclaim-for: func(kind: payload-kind, key: s32);
    }
}

/// Step 2: the DVCMin payload (trust-phw DVCMin). [STT-PAY-010]
interface dvc {
    use types.{validation-issue, cbor-bytes, decode-limits};

    /// DVCMinVaccineDetails. [STT-PAY-002]
    record dvc-min-vaccine-details {
        /// Vaccine product identifier.
        vp: string,
        /// Date of vaccination (FHIR date).
        dv: string,
        /// Supervising clinician.
        cn: option<string>,
        /// Issuing organization identifier.
        is: option<string>,
        /// Batch or lot number.
        bo: string,
    }

    /// DVCMin. vx is 1..1: one immunization event per certificate.
    /// [STT-PAY-001, STT-PAY-003, STT-PAY-005]
    record dvc-min {
        n: string,
        dob: string,
        s: string,
        /// ISO 3166-1 alpha-3. [STT-PAY-007]
        nt: string,
        id: option<string>,
        dt: option<string>,
        gn: option<string>,
        vx: dvc-min-vaccine-details,
        v: string,
        /// Elements this version does not know, kept as CBOR map entries
        /// (key, encoded value). [STT-PAY-008]
        extensions: list<tuple<string, cbor-bytes>>,
    }

    /// Cardinality, invariant (must-have-issuer-or-clinician-name) and nt
    /// checks. [STT-PAY-004, STT-PAY-007]
    validate: func(p: dvc-min) -> list<validation-issue>;

    to-cbor: func(p: dvc-min) -> cbor-bytes;
    from-cbor: func(cbor: cbor-bytes, limits: decode-limits) -> result<dvc-min, list<validation-issue>>;
}

/// 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]
interface payloads {
    use types.{cbor-bytes};
    use dvc.{dvc-min};
    use vhl.{vhl-payload};

    record raw-payload {
        subclaim: s32,
        cbor: cbor-bytes,
    }

    variant payload {
        dvc-min(dvc-min),
        vhl(vhl-payload),
        raw(raw-payload),
    }
}

pipeline.wit

source
package who:smart-trust@0.1.0;

/// End-to-end issuance (steps 3 to 8).
///
/// Signing is two-phase so that any key holder can sign without the core
/// calling back into the host: prepare() returns the Sig_structure bytes,
/// the caller signs them with an HSM, KMS, Android Keystore or WebCrypto,
/// and finish() assembles the certificate. [STT-ISS-004, STT-COS-006]
interface issuer {
    use types.{cose-algorithm, kid, numeric-date, validation-issue};
    use payloads.{payload};
    use subclaims.{registry};

    record validity {
        iat: numeric-date,
        /// Mandatory: exp is the only revocation mechanism. [STT-CWT-003]
        exp: numeric-date,
    }

    record issue-request {
        /// One or more payloads, each under its own subclaim in the hcert
        /// claim, in subclaim-key order on the wire. [STT-SEL-004]
        payloads: list<payload>,
        /// CWT iss, alpha-2. [STT-CWT-005]
        iss: option<string>,
        validity: validity,
        /// DSC whose key will sign; kid is derived from it. [STT-KID-001]
        dsc-der: list<u8>,
        alg: cose-algorithm,
        /// Refuse negative subclaims unless true. [STT-SEL-003]
        allow-development-subclaims: bool,
    }

    variant issue-error {
        payload-invalid(list<validation-issue>),
        /// The request carries no payload.
        no-payload,
        /// Two payloads map to the same subclaim. [STT-SEL-004]
        duplicate-subclaim(s32),
        development-subclaim-not-allowed(s32),
        unknown-subclaim(s32),
        /// iat before or exp after the DSC validity. [STT-CWT-004]
        validity-outside-dsc,
        bad-certificate(string),
        bad-signature-format(string),
    }

    record issued-hcert {
        /// QR-ready: QR Alphanumeric characters only. [STT-QR-001]
        hc1: string,
        /// Raw COSE_Sign1 for 8-bit-safe transports. [STT-GEN-009]
        cose: list<u8>,
    }

    resource pending-signature {
        /// Bytes to sign (CBOR Sig_structure). [STT-COS-004]
        to-be-signed: func() -> list<u8>;
        algorithm: func() -> cose-algorithm;
        kid: func() -> kid;
    }

    /// Phase 1: validate, build CWT, CBOR and protected header.
    prepare: func(req: issue-request, reg: borrow<registry>) -> result<pending-signature, issue-error>;

    /// Phase 2: attach the signature. ES256 accepts r||s or DER and always
    /// emits r||s. [STT-COS-005]
    finish: func(pending: pending-signature, signature: list<u8>) -> result<issued-hcert, issue-error>;
}

/// 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].
interface verifier {
    use types.{kid, numeric-date, decode-limits, cbor-bytes, step, failure-reason, diagnostic};
    use payloads.{payload};
    use trust.{trust-store, trusted-dsc};
    use subclaims.{registry};

    record verifier-options {
        limits: decode-limits,
        clock-skew-seconds: u32,
        /// Refuse an hcert claim with more entries than this
        /// (multiple-subclaims): a deployment's strict profile. None: any
        /// number. [STT-SEL-004]
        max-payloads: option<u32>,
    }

    /// One entry of the hcert claim. [STT-SEL-004]
    record hcert-entry {
        subclaim: s32,
        /// Raw CBOR for a subclaim the registry does not know. [STT-SEL-002]
        payload: payload,
        /// False when the DSC's EKU does not permit this entry's
        /// certificate type; the entry is returned, marked, and a
        /// diagnostic says so. [STT-VER-004]
        key-usage-authorised: bool,
    }

    record verified-hcert {
        iss: option<string>,
        iat: numeric-date,
        exp: numeric-date,
        /// The valid entries, in subclaim-key order. An entry whose payload
        /// fails its model check is left out and reported as a diagnostic;
        /// verification fails at payload only when none is left, and at
        /// key-usage only when no entry is authorised. [STT-SEL-004]
        entries: list<hcert-entry>,
        /// The DSC that verified the signature.
        dsc: trusted-dsc,
        /// Unknown CWT claims, as (encoded key, encoded value).
        /// [STT-CWT-006]
        other-claims: list<tuple<cbor-bytes, cbor-bytes>>,
        diagnostics: list<diagnostic>,
    }

    record verification-failure {
        step: step,
        reason: failure-reason,
        detail: string,
        diagnostics: list<diagnostic>,
    }

    /// Payload content is only reachable through `verified`.
    /// [STT-VER-002, STT-VER-008]
    variant verification-result {
        verified(verified-hcert),
        failed(verification-failure),
    }

    /// 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.
    record unverified-entry {
        subclaim: s32,
        /// The payload, when it decoded and met its model.
        payload: option<payload>,
    }

    record unverified-hcert {
        kid: option<kid>,
        iss: option<string>,
        iat: option<numeric-date>,
        exp: option<numeric-date>,
        /// Every entry of the hcert claim, in subclaim-key order.
        entries: list<unverified-entry>,
        diagnostics: list<diagnostic>,
    }

    /// `now` is passed in: no ambient clock. [STT-VER-005]
    verify: func(hc1: string, store: borrow<trust-store>, reg: borrow<registry>, now: numeric-date, options: verifier-options) -> verification-result;

    /// Same, for raw COSE_Sign1 bytes. [STT-GEN-009]
    verify-cose: func(cose: list<u8>, store: borrow<trust-store>, reg: borrow<registry>, now: numeric-date, options: verifier-options) -> verification-result;

    /// [STT-VER-011]
    read-unverified: func(hc1: string, reg: borrow<registry>, limits: decode-limits) -> result<unverified-hcert, verification-failure>;
}

worlds.wit

source
package who:smart-trust@0.1.0;

/// Pure encoding and decoding: the pipeline steps on their own. No host
/// imports. [STT-GEN-002]
world hcert-codec {
    export types;
    export codec;
    export cose;
}

/// 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]
world hcert-verifier {
    import wasi:random/random@0.2.6;
    export types;
    export codec;
    export cose;
    export subclaims;
    export dvc;
    export vhl;
    export payloads;
    export trust;
    export verifier;
}

/// Issuer: what a clinic or national issuance service embeds. Randomness
/// as for the verifier; signing stays two-phase, so no key enters.
world hcert-issuer {
    import wasi:random/random@0.2.6;
    export types;
    export codec;
    export cose;
    export subclaims;
    export dvc;
    export vhl;
    export payloads;
    export issuer;
}

/// Everything.
world hcert {
    include hcert-verifier;
    include hcert-issuer;
}