§ NZ COVID Pass - Technical Specification v1

This repository is home to the technical specification for the New Zealand COVID Pass.

The New Zealand COVID Pass is a cryptographically signed document which can be represented in the form of a QR Code that enables an individual to express proof of having met certain health policy requirements in regards to COVID-19 such as being vaccinated against the virus.

§ Terminology

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in BCP 14 [RFC2119], [RFC8174] when, and only when, they appear in all capitals, as shown here.

§ Data Model

The New Zealand COVID Pass is a digitally signed payload optimised for rendering via a 2D barcode for easy machine verifiability.

At an abstract level, regardless of the underlying pass type, the common elements contained within a pass are:

See the Pass Types section for more details on the additional data elements present in each pass type.

§ CWT Claims

cti: CWT Token ID, this claim represents a unique identifier for the pass, it MUST be present and its decoded value MUST be a valid UUID in the form of a URI as specified by [RFC4122]. The value of this identifier MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different pass. It is RECOMMENDED that the UUID be generated in accordance with version 4 as specified by [RFC4122]. This claim is mapped to the (Credential ID) property in the W3C VC standard. The claim key for cti of 7 MUST be used.

iss: Issuer, this claim represents the party who issued the pass, it MUST be present and its decoded value MUST be a Decentralized Identifier who’s DID Method MUST correspond to web which is defined by the did:web specification. Verifying parties MUST validate this field via the verification rules outlined in section 6 This claim is mapped to the (Credential Issuer) property in the W3C VC standard. The claim key for iss of 1 MUST be used.

nbf: Not Before, this claim represents the earliest datetime at which the pass is considered valid by the party who issued it, this claim MUST be present and its value MUST be a timestamp encoded as an integer in the NumericDate format (as specified in [RFC8392] section 2). Verifying parties MUST validate that the current datetime is after or equal to the value of this claim and if not they MUST reject the pass as not being active. This claim is mapped to the Credential Issuance Date property in the W3C VC standard. NOTE - As per the standard this date can be sometime in the future, from when the pass is created. The claim key for nbf of 5 MUST be used.

exp: Expiry, this claim represents the datetime at which the pass is considered expired by the party who issued it, this claim MUST be present and its value MUST be a timestamp encoded as an integer in the NumericDate format (as specified in [RFC8392] section 2). Verifying parties MUST validate that the current datetime is before the value of this claim and if not they MUST reject the pass as being expired. This claim is mapped to the Credential Expiration Date property in the W3C VC standard. The claim key for exp of 4 MUST be used.

vc: Verifiable Credential CWT claim, this claim MUST be present and its value MUST follow the structure of verifiable credential claim structure. This claim is mapped to the JWT Verifiable Credential claim. The vc claim is currrently unregistered and therefore MUST be encoded as a Major Type 3 string as defined by [RFC7049].

§ Mapping JTI <-> CTI

The JTI claim as defined by JWT represents a unique identifier for the token, in the context of the W3C Verifiable Credentials Data Model 1.0 this claim is mapped to the Credential ID property. CWT defines an equivalent claim to JTI known as CTI. However, due to the fact that the value type of the CTI claim (byte string) is different to the JTI claim (string) a value mapping is required. Furthermore, in its decoded form as per the W3C Verifiable Credentials Data Model 1.0 the resulting JTI claim MUST be a valid URI.

To meet these requirements the decoded form of this CTI claim value MUST be a valid UUID as defined by [RFC4122], the following is an example of such form:

urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6

In addition to this the following encoding and decoding rules apply to this claim. Whereby the process of encoding refers to transformation of the string based value representation to its equivalent byte string representation (JTI -> CTI). And the process of decoding refers to the transformation of the byte string representation to its equivalent string based representation (CTI -> JTI).

Encoding

Given a valid UUID URI form (urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6)

  1. Parse the 128 bit UUID from the input URI (f81d4fae-7dec-11d0-a765-00a0c91e6bf6)
  2. Parse the representative 16 bytes from the UUID in big-endian format (bytes expressed in hexadecimal f8 1d 4f ae 7d ec 11 d0 a7 65 00 a0 c9 1e 6b f6)

Decoding

Given a valid UUID in binary form (bytes expressed in hexadecimal f8 1d 4f ae 7d ec 11 d0 a7 65 00 a0 c9 1e 6b f6)

  1. Parse the 16 byte value and convert to hexadecimal form (f81d4fae7dec11d0a76500a0c91e6bf6)
  2. In accordance with the ABNF syntax defined by [RFC4122] split the resulting hexadecimal string along the 4-2-2-2-6 hex octet pattern. (f81d4fae-7dec-11d0-a765-00a0c91e6bf6)
  3. Prepend the prefix of urn:uuid to the result obtained (urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6)

§ CWT Headers

kid: This header MUST be present in the protected header section of the COSE_Sign1 structure. The claim key of 4 is used to identify this claim. Its value corresponds to a relative reference to the key used to verify the pass, which MUST be combined with the value in the iss claim in the payload in accordance with the processing rules outlined in section 6. This value MUST be encoded as a Major Type 3 string as defined by [RFC7049].

alg: Algorithm as per Cryptographic Digital Signature Algorithm. The claim key of 1 is used to identify this claim. It MUST be present in the protected header section of the COSE_Sign1 structure and its claim value MUST be set to the value corresponding to ES256 algorithm registration, which is the numeric value of -7 as per IANA registry.

§ Verifiable Credential Claim Structure

The following section outlines the additional normative requirements around the contents of the vc claim value as defined in the W3C Verifiable Credentials standard.

@context: JSON-LD Context property for conformance to the W3C VC standard. This property MUST be present and its value MUST be an array of strings where the first value MUST equal https://www.w3.org/2018/credentials/v1.

The following is an example including an additional JSON-LD context entry that defines the additional vocabulary specific to the New Zealand COVID Pass.

["https://www.w3.org/2018/credentials/v1", "https://nzcp.covid19.health.nz/contexts/v1"] 

type: Type property for conformance to the W3C VC standard. This property MUST be present and its value MUST be an array of two string values, whose first element is VerifiableCredential and second element corresponds to one defined in the pass types section.

Example

["VerifiableCredential", "PublicCovidPass"]

version: Version property of the New Zealand Covid Pass. This property MUST be present and its value MUST be a string who’s value corresponds to a valid version identifier as defined by semver. For the purposes of this version of the specification this value MUST be 1.0.0.

credentialSubject: Credential Subject property MUST be present and its value MUST be a JSON object with properties determined by the declared pass type for the pass.

§ Pass Types

Within the New Zealand COVID Pass the concept of a pass type exists due to the association to the W3C Verifiable Credentials Data Model 1.0.

As stated by the standard in the type section, a Verifiable Credential MUST include a type property with a value which includes “VerifiableCredential”.

For the purposes of the New Zealand COVID Pass the Verifiable Credential MUST also include one of the following types.

The pass types defined above communicate the type of pass that has been issued and communicates what other claims should/must be present in the credentialSubject property, the section below details what each type defines.

§ PublicCovidPass

When a verifiable credential features this type (PublicCovidPass) it is a New Zealand COVID Pass which includes the following additional information in the credentialSubject property.

Below is an example decoded CWT payload of a PublicCovidPass where the CWT claims have been mapped to their JWT counterparts.

{
    "iss": "did:web:example.nz",
    "nbf": 1516239022,
    "exp": 1516239922,
    "jti": "urn:uuid:cc599d04-0d51-4f7e-8ef5-d7b5f8461c5f",
    "vc": {
        "@context": [ "https://www.w3.org/2018/credentials/v1", "https://nzcp.covid19.health.nz/contexts/v1" ],
        "version": "1.0.0",
        "type": [ "VerifiableCredential", "PublicCovidPass" ],
        "credentialSubject": {
            "givenName": "John Andrew",
            "familyName": "Doe",
            "dob": "1979-04-14"
        }
    }
}

§ Relationship to W3C VC Data Model

The W3C Verifiable Credentials Data Model 1.0 standard specifies an abstract data model for expressing information that is verifiable through the usage of cryptographic digital signatures. Although the primary representation format discussed in the standard is [JSON-LD] with Linked Data Proofs as the proof format. The standard is agnostic to other proof formats.

Excerpt from Proof formats section of the specification:

The data model described in this specification is designed to be proof format agnostic. This specification does not normatively require any particular digital proof or signature format.

Due to the unique requirements of the New Zealand COVID Pass including the requirement that the overall expression be en-codable into a QR Code that is easily scannable, the data model is encoded using [RFC7049] and CWT as defined by [RFC8392] as the proof format.

Furthermore, within the the W3C Verifiable Credentials Data Model 1.0 encoding with the proof format of JWT is defined. Due to the relationship CWT and JWT share including common claim registrations in the COSE IANA registry, the JWT encoding can be directly mapped to CWT.

§ Cryptographic Digital Signature Algorithm Selection

COSE [RFC8152] is a general purpose digital signature encoding format that supports several different cryptographic algorithms, however for the purposes of promoting secure and interoperable implementations all New Zealand COVID Passes MUST use Elliptic Curve Digital Signature Algorithm (ECDSA) as defined in (ISO/IEC 14888–3:2006) section 2.3, using the P–256 parameters as defined in appendix D (D.1.2.3) of (FIPS PUB 186–4) in combination with the SHA–256 hash algorithm as defined in FIPS PUB 180-4.

This algorithm corresponds to ES256 in the COSE IANA Algorithm Registry.

COSE [RFC8152] supports two sign structures COSE_Sign and COSE_Sign1, with the primary difference being that the former supports multiple signatures whilst the latter does not. For the purposes of promoting secure and interoperable implementations and there being no present requirement for multiple signatures, all New Zealand COVID Passes MUST use the COSE_Sign1 structure and MUST use the tagged variation of the CBOR structure using the registered tag of 18 to represent this.

Verifying parties MUST validate the digital signature on a New Zealand COVID Pass and MUST reject passes that fail this check as being invalid.

§ 2D Barcode Encoding

This section defines how a New Zealand COVID Pass is encoded in the form of a 2D barcode.

The QR format as defined in (ISO/IEC 18004:2015) MUST be used as the 2D barcode format.

The payload of the QR Code MUST be encoded using the alphanumeric mode, whereby the encoded string takes the following form, making it a valid URI as per [RFC3986].

NZCP:/<version-identifier>/<base32-encoded-CWT>

The payload of the QR Code MUST begin with the prefix of NZCP:/, followed by the two further payload elements described below that are delimited by a / character.

The version-identifier portion of the payload is an un-signed integer representing the major version the pass was generated with as defined by semver conventions. This value for this release of the specification MUST be 1.

The base32-encoded-CWT portion on the payload MUST use the base32 encoding as defined by [RFC4648] WITHOUT padding, to encode the outputted binary payload resulting from the COSE sign operation.

NOTE

Because the alphanumeric mode of QR codes does not include the = as a valid character, the padding of a base32 string represented by the = character MUST be removed prior to QR code encoding.

The following is a simple javascript snippet designed to show how an implementor can remove the padding from an input base32 string.

const removeBase32Padding = (base32Input) => {
    return base32Input.split('=')[0]
}

// Running the following 
const payload = "MFZWS33ENBQXG2DJOBSGQ2LBONUWQ4DEMFUXA43ENBUXAYLTNBUWI===";
console.log("Base32 payload: " + payload);
console.log("Base32 payload with padding removed: " + removeBase32Padding(payload));

// Yields the following output
// Base32 payload: MFZWS33ENBQXG2DJOBSGQ2LBONUWQ4DEMFUXA43ENBUXAYLTNBUWI===
// Base32 payload with padding removed: MFZWS33ENBQXG2DJOBSGQ2LBONUWQ4DEMFUXA43ENBUXAYLTNBUWI
NOTE

Some base32 decoding implementations may fail to decode a base32 string that is missing the required padding as defined by [RFC4648].

The following is a simple javascript snippet designed to show how an implementor can add the required padding to a base32 string.

    const addBase32Padding = (base32InputNoPadding) => {
        var result = base32InputNoPadding;
        while ((result.length % 8) !== 0) {
            result += '='
        }
        return result;
    }


    // Running the following 
    const payload = "MFZWS33ENBQXG2DJOBSGQ2LBONUWQ4DEMFUXA43ENBUXAYLTNBUWI";
    console.log("Base32 payload without padding: " + payload);
    console.log("Base32 payload with padding: " + addBase32Padding(payload));

    // Yields the following output
    // Base32 payload without padding: MFZWS33ENBQXG2DJOBSGQ2LBONUWQ4DEMFUXA43ENBUXAYLTNBUWI
    // Base32 payload with padding: MFZWS33ENBQXG2DJOBSGQ2LBONUWQ4DEMFUXA43ENBUXAYLTNBUWI===

§ Issuer Identifier

Establishing who issued a New Zealand Covid pass and validating is a crucial step in being able to trust the information contained within the pass.

The Decentralized Identifiers [DID-CORE] specification defines an identifier syntax and accompanying data model for representing public cryptographic information in a consistent manner.

In essence given a DID (the identifier) it can be resolved to the associated set of public keys (the did document) required to verify cryptographically signed information by the party represented by the DID.

This specification makes use of the did:web DID method.

For verifying parties it is RECOMMENDED that prior to trying to resolve the public keys of the issuer of a New Zealand COVID Pass, the verifying party SHOULD first establish whether they trust the reported issuer, for example by validating the iss value reported in the pass matches one listed in the trusted issuers.

§ DID Document

The public key referenced by the decoded CWT MUST be listed/authorized under the assertionMethod verification relationship in the resolved DID document.

The public key referenced by the decoded CWT MUST be a valid P-256 public key suitable for usage with the Elliptic Curve Digital Signature Algorithm (ECDSA) as defined in (ISO/IEC 14888–3:2006) section 2.3.

The expression of the public key referenced by the decoded CWT MUST be in the form of a JWK as per [RFC7517].

This public key JWK expression MUST NOT publish any JSON Web Key Parameters that are classified as “Private” under the “Parameter Information Class” category of the JSON Web Key Parameters IANA registry.

This public key JWK expression MUST set a crv property which has a value of P-256. Additionally, the JWK MUST have a kty property set to EC.

Below is a non-normative example of a did:web DID Document:

{
    "@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1"],
    "verificationMethod": [{
        "id": "did:web:example.com#key1",
        "type": "JsonWebKey2020",
        "controller": "did:web:example.com",
        "publicKeyJwk": {
          "kty":"EC",
          "crv":"P-256",
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
        },
    }],
    "assertionMethod": ["did:web:example.com#key1"],
    "authentication": ["did:web:example.com#key1"],
}

§ Example - resolving an issuer’s identifier to their public keys

This section outlines how a verifying party, given a valid New Zealand COVID pass, retrieves the public keys for the issuer of the pass so they can perform the cryptographic verification of the digital signature.

Given did:web:example.com as the value of the iss claim and key-1 as the value of the kid header parameter.

Perform an HTTPS GET request to https://example.com/.well-known/did.json with the Accept header set to application/json. If the request fails or response body is not valid JSON then fail verification.

Given the response body to this request as the following

{
    "@context": ["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1"],
    "verificationMethod": [{
        "id": "did:web:example.com#key1",
        "type": "JsonWebKey2020",
        "controller": "did:web:example.com",
        "publicKeyJwk": {
          "kty":"EC",
          "crv":"P-256",
          "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
          "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
        },
    }],
    "assertionMethod": ["did:web:example.com#key1"],
    "authentication": ["did:web:example.com#key1"],
}

Combine the iss value and kid value separated with a # to get the absolute key reference (did:web:example.com#key1).

Check the list of elements referenced in the assertionMethod array contained in the document, if the property does not exist or the array does not contain the absolute key reference computed above then fail verification.

Check the returned verificationMethod is of type JsonWebKey2020, if not then fail verification.

Check the returned verificationMethod features a publicKeyJwk entry and validate that its value a valid JWK as per [RFC7517].

§ Trusted Issuers

The following is a list of trusted issuer identifiers for New Zealand Covid Passes.

[
  "did:web:nzcp.identity.health.nz"
]

A verifying party during validation of a New Zealand Covid Pass MUST check that the issuer of the pass, conveyed by the iss claim in the decoded payload matches one of those defined above.

§ Examples

§ Steps to verify a New Zealand COVID Pass

This section is non-normative and is intended to help those developing tools for verifying parties.

§ Valid Worked Example

This section is non-normative and is intended to provide a worked example to help those developing tools for verifying parties.

NOTE

The issuer of this example is did:web:nzcp.covid19.health.nz and must be set to be a trusted issuer for the purposes of testing in order to yield the expected valid result.

The payload decoded from the QR is of the following form:

NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAYFE6VGU4MCDGK7DHLLYWHVPUS2YIDJOA6Y524TD3AZRM263WTY2BE4DPKIF27WKF3UDNNVSVWRDYIYVJ65IRJJJ6Z25M2DO4YZLBHWFQGVQR5ZLIWEQJOZTS3IQ7JTNCFDX

Decoding the base32 portion of this above string to a byte string (adding padding if required) yields the following bytes expressed in hexadecimal.

d2844aa204456b65792d310126a059011fa501781e6469643a7765623a6e7a63702e636f76696431392e6865616c74682e6e7a051a61819a0a041a7450400a627663a46840636f6e7465787482782668747470733a2f2f7777772e77332e6f72672f323031382f63726564656e7469616c732f7631782a68747470733a2f2f6e7a63702e636f76696431392e6865616c74682e6e7a2f636f6e74657874732f76316776657273696f6e65312e302e306474797065827456657269666961626c6543726564656e7469616c6f5075626c6963436f766964506173737163726564656e7469616c5375626a656374a369676976656e4e616d65644a61636b6a66616d696c794e616d656753706172726f7763646f626a313936302d30342d3136075060a4f54d4e304332be33ad78b1eafa4b5840d2e07b1dd7263d833166bdbb4f1a093837a905d7eca2ee836b6b2ada23c23154fba88a529f675d6686ee632b09ec581ab08f72b458904bb3396d10fa66d11477

Decoding this byte string as a CBOR structure and rendering it via the expanded form shown throughout [RFC7049] yields the following. Let this result be known as the Decoded COSE structure.

  d2                -- Tag #18
    84              -- Array, 4 items
      4a            -- Bytes, length: 10
        a204456b65792d310126 -- [0], a204456b65792d310126
      a0            -- [1], {}
      59            -- Bytes, length next 2 bytes
        011f        -- Bytes, length: 287
          a501781e6469643a7765623a6e7a63702e636f76696431392e6865616c74682e6e7a051a61819a0a041a7450400a627663a46840636f6e7465787482782668747470733a2f2f7777772e77332e6f72672f323031382f63726564656e7469616c732f7631782a68747470733a2f2f6e7a63702e636f76696431392e6865616c74682e6e7a2f636f6e74657874732f76316776657273696f6e65312e302e306474797065827456657269666961626c6543726564656e7469616c6f5075626c6963436f766964506173737163726564656e7469616c5375626a656374a369676976656e4e616d65644a61636b6a66616d696c794e616d656753706172726f7763646f626a313936302d30342d3136075060a4f54d4e304332be33ad78b1eafa4b -- [2], a501781e6469643a7765623a6e7a63702e636f76696431392e6865616c74682e6e7a051a61819a0a041a7450400a627663a46840636f6e7465787482782668747470733a2f2f7777772e77332e6f72672f323031382f63726564656e7469616c732f7631782a68747470733a2f2f6e7a63702e636f76696431392e6865616c74682e6e7a2f636f6e74657874732f76316776657273696f6e65312e302e306474797065827456657269666961626c6543726564656e7469616c6f5075626c6963436f766964506173737163726564656e7469616c5375626a656374a369676976656e4e616d65644a61636b6a66616d696c794e616d656753706172726f7763646f626a313936302d30342d3136075060a4f54d4e304332be33ad78b1eafa4b
      58            -- Bytes, length next 1 byte
        40          -- Bytes, length: 64
          d2e07b1dd7263d833166bdbb4f1a093837a905d7eca2ee836b6b2ada23c23154fba88a529f675d6686ee632b09ec581ab08f72b458904bb3396d10fa66d11477 -- [3], d2e07b1dd7263d833166bdbb4f1a093837a905d7eca2ee836b6b2ada23c23154fba88a529f675d6686ee632b09ec581ab08f72b458904bb3396d10fa66d11477
0xd2844aa204456b65792d310126a059011fa501781e6469643a7765623a6e7a63702e636f76696431392e6865616c74682e6e7a051a61819a0a041a7450400a627663a46840636f6e7465787482782668747470733a2f2f7777772e77332e6f72672f323031382f63726564656e7469616c732f7631782a68747470733a2f2f6e7a63702e636f76696431392e6865616c74682e6e7a2f636f6e74657874732f76316776657273696f6e65312e302e306474797065827456657269666961626c6543726564656e7469616c6f5075626c6963436f766964506173737163726564656e7469616c5375626a656374a369676976656e4e616d65644a61636b6a66616d696c794e616d656753706172726f7763646f626a313936302d30342d3136075060a4f54d4e304332be33ad78b1eafa4b5840d2e07b1dd7263d833166bdbb4f1a093837a905d7eca2ee836b6b2ada23c23154fba88a529f675d6686ee632b09ec581ab08f72b458904bb3396d10fa66d11477

Decoding the byte string present in the first element of the Decoded COSE structure, as a CBOR structure and rendering it via the expanded form yields the following. Let this result be known as the Decoded CWT protected headers.

  a2                -- Map, 2 pairs
    04              -- {Key:0}, 4
    45              -- Bytes, length: 5
      6b65792d31    -- {Val:0}, 6b65792d31
    01              -- {Key:1}, 1
    26              -- {Val:1}, -7
0xa204456b65792d310126

Mapping the Decoded CWT protected headers to JSON, substituting the numeric claim tags (e.g 1 -> alg, 4 -> kid) for their registered string equivalents yields the following.

{
  "kid": "key-1",
  "alg": "ES256"
}

Decoding the byte string present in the third element of the Decoded COSE structure, as a CBOR structure and rendering it via the expanded form yields the following. Let this result be known as the Decoded CWT payload.

  a5                -- Map, 5 pairs
    01              -- {Key:0}, 1
    78              -- String, length next 1 byte
      1e            -- String, length: 30
        6469643a7765623a6e7a63702e636f76696431392e6865616c74682e6e7a -- {Val:0}, "did:web:nzcp.covid19.health.nz"
    05              -- {Key:1}, 5
    1a              -- Positive number, next 4 bytes
      61819a0a      -- {Val:1}, 1635883530
    04              -- {Key:2}, 4
    1a              -- Positive number, next 4 bytes
      7450400a      -- {Val:2}, 1951416330
    62              -- String, length: 2
      7663          -- {Key:3}, "vc"
    a4              -- {Val:3}, Map, 4 pairs
      68            -- String, length: 8
        40636f6e74657874 -- {Key:0}, "@context"
      82            -- {Val:0}, Array, 2 items
        78          -- String, length next 1 byte
          26        -- String, length: 38
            68747470733a2f2f7777772e77332e6f72672f323031382f63726564656e7469616c732f7631 -- [0], "https://www.w3.org/2018/credentials/v1"
        78          -- String, length next 1 byte
          2a        -- String, length: 42
            68747470733a2f2f6e7a63702e636f76696431392e6865616c74682e6e7a2f636f6e74657874732f7631 -- [1], "https://nzcp.covid19.health.nz/contexts/v1"
      67            -- String, length: 7
        76657273696f6e -- {Key:1}, "version"
      65            -- String, length: 5
        312e302e30  -- {Val:1}, "1.0.0"
      64            -- String, length: 4
        74797065    -- {Key:2}, "type"
      82            -- {Val:2}, Array, 2 items
        74          -- String, length: 20
          56657269666961626c6543726564656e7469616c -- [0], "VerifiableCredential"
        6f          -- String, length: 15
          5075626c6963436f76696450617373 -- [1], "PublicCovidPass"
      71            -- String, length: 17
        63726564656e7469616c5375626a656374 -- {Key:3}, "credentialSubject"
      a3            -- {Val:3}, Map, 3 pairs
        69          -- String, length: 9
          676976656e4e616d65 -- {Key:0}, "givenName"
        64          -- String, length: 4
          4a61636b  -- {Val:0}, "Jack"
        6a          -- String, length: 10
          66616d696c794e616d65 -- {Key:1}, "familyName"
        67          -- String, length: 7
          53706172726f77 -- {Val:1}, "Sparrow"
        63          -- String, length: 3
          646f62    -- {Key:2}, "dob"
        6a          -- String, length: 10
          313936302d30342d3136 -- {Val:2}, "1960-04-16"
    07              -- {Key:4}, 7
    50              -- Bytes, length: 16
      60a4f54d4e304332be33ad78b1eafa4b -- {Val:4}, 60a4f54d4e304332be33ad78b1eafa4b
0xa501781e6469643a7765623a6e7a63702e636f76696431392e6865616c74682e6e7a051a61819a0a041a7450400a627663a46840636f6e7465787482782668747470733a2f2f7777772e77332e6f72672f323031382f63726564656e7469616c732f7631782a68747470733a2f2f6e7a63702e636f76696431392e6865616c74682e6e7a2f636f6e74657874732f76316776657273696f6e65312e302e306474797065827456657269666961626c6543726564656e7469616c6f5075626c6963436f766964506173737163726564656e7469616c5375626a656374a369676976656e4e616d65644a61636b6a66616d696c794e616d656753706172726f7763646f626a313936302d30342d3136075060a4f54d4e304332be33ad78b1eafa4b

Mapping the Decoded CWT payload to JSON, substituting the numeric claim tags (e.g 1 -> iss, 4 -> exp) for their registered string equivalents.

{
  "iss": "did:web:nzcp.covid19.health.nz",
  "nbf": 1635883530,
  "exp": 1951416330,
  "jti": "urn:uuid:60a4f54d-4e30-4332-be33-ad78b1eafa4b",
  "vc": {
    "@context": [
      "https://www.w3.org/2018/credentials/v1",
      "https://nzcp.covid19.health.nz/contexts/v1"
    ],
    "version": "1.0.0",
    "type": [
      "VerifiableCredential",
      "PublicCovidPass"
    ],
    "credentialSubject": {
      "givenName": "Jack",
      "familyName": "Sparrow",
      "dob": "1960-04-16"
    }
  }
}

Taking the value of the iss claim in the CWT payload and the kid value from the protected headers and resolving it to the issuers DID document as detailed in the issuer identifier section, yields the following JSON document. Located here.

{
  "@context": "https://w3.org/ns/did/v1",
  "id": "did:web:nzcp.covid19.health.nz",
  "verificationMethod": [
    {
      "id": "did:web:nzcp.covid19.health.nz#key-1",
      "controller": "did:web:nzcp.covid19.health.nz",
      "type": "JsonWebKey2020",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "zRR-XGsCp12Vvbgui4DD6O6cqmhfPuXMhi1OxPl8760",
        "y": "Iv5SU6FuW-TRYh5_GOrJlcV_gpF_GpFQhCOD8LSk3T0"
      }
    }
  ],
  "assertionMethod": [
    "did:web:nzcp.covid19.health.nz#key-1"
  ]
}

Extracting the issuers public key from the resolved DID document, yields the following public key expressed in JWK form which is required to cryptographically verify the pass.

{
  "kty": "EC",
  "crv": "P-256",
  "x": "zRR-XGsCp12Vvbgui4DD6O6cqmhfPuXMhi1OxPl8760",
  "y": "Iv5SU6FuW-TRYh5_GOrJlcV_gpF_GpFQhCOD8LSk3T0"
}

Using the encoded protected headers, encoded payload, issuer public key and signature value inline with the rules stipulated by [RFC8152] to performing cryptographic verification using the Elliptic Curve Digital Signature Algorithm with SHA-256 returns a valid digital signature.

NOTE

The expiry of this example pass is 10 years from issuance which does not represent the period under-which an actual New Zealand COVID Pass would be issued for.

§ Invalid examples

This section is non-normative and is intended to provide a set of invalid examples to help those developing tools for verifying parties.

NOTE

The issuer of all of these examples is did:web:nzcp.covid19.health.nz which must be set to be trusted as an issuer for the purposes of testing in order to yield the following results.

§ Bad Public Key

The following New Zealand COVID Pass features a digital signature that was signed by a different private/public key than the one resolved by the information in the pass. The expected result from validating this pass is FAIL or ERROR due to unable to validate the digital signature.

Below is the decoded contents of the QR Code

NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAY73U6TCQ3KF5KFML5LRCS5D3PCYIB2D3EOIIZRPXPUA2OR3NIYCBMGYRZUMBNBDMIA5BUOZKVOMSVFS246AMU7ADZXWBYP7N4QSKNQ4TETIF4VIRGLHOXWYMR4HGQ7KYHHU

§ Public Key Not Found

The following is a New Zealand COVID Pass that references a public key that is not found in the Issuers DID Document. The expected result from validating this pass is FAIL or ERROR due to not being able to obtain the public key required to verify the pass.

Below is the decoded contents of the QR Code

NZCP:/1/2KCEVIQEIVVWK6JNGIASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVBMP3LEDMB4CLBS2I7IOYJZW46U2YIBCSOFZMQADVQGM3JKJBLCY7ATASDTUYWIP4RX3SH3IFBJ3QWPQ7FJE6RNT5MU3JHCCGKJISOLIMY3OWH5H5JFUEZKBF27OMB37H5AHF

§ Modified Signature

The following is a New Zealand COVID Pass that features a digital signature that has been modified in an invalid way. The expected result from validating this pass is FAIL or ERROR due to not being able to validate the digital signature.

Below is the decoded contents of the QR Code

NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAYFE6VGU4MCDGK7DHLLYWHVPUS2YIAAAAAAAAAAAAAAAAC63WTY2BE4DPKIF27WKF3UDNNVSVWRDYIYVJ65IRJJJ6Z25M2DO4YZLBHWFQGVQR5ZLIWEQJOZTS3IQ7JTNCFDX

§ Modified Payload

The following is a New Zealand COVID Pass that features a modified payload which invalidates the accompanying digital signature. The expected result from validating this pass is FAIL or ERROR due to not being able to validate the digital signature.

Below is the decoded contents of the QR Code

NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEOKKALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWKU3UMV3GK2TGMFWWS3DZJZQW2ZLDIRXWKY3EN5RGUMJZGYYC2MBUFUYTMB2QMCSPKTKOGBBTFPRTVV4LD2X2JNMEAAAAAAAAAAAAAAAABPN3J4NASOBXVEC5P3FC52BWW2ZK3IR4EMKU7OUIUUU7M5OWNBXOMMVQT3CYDKYI64VULCIEXMZZNUIPUZWRCR3Q

§ Expired Pass

The following is a New Zealand COVID Pass that is expired, which is communicated via the value of the exp claim. The expected result from validating this pass is EXPIRED.

Below is the decoded contents of the QR Code

NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUX5AM2FQIGTBPBPYWYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVA56TNJCCUN2NVK5NGAYOZ6VIWACYIBM3QXW7SLCMD2WTJ3GSEI5JH7RXAEURGATOHAHXC2O6BEJKBSVI25ICTBR5SFYUDSVLB2F6SJ63LWJ6Z3FWNHOXF6A2QLJNUFRQNTRU

§ Not Active Pass

The following is a New Zealand COVID Pass that is not active yet, which is communicated via the value of the nbf claim. The expected result from validating this pass is NOT ACTIVE.

Below is the decoded contents of the QR Code

NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRU2XI5UFQIGTMZIQIWYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVA27NR3GFF4CCGWF66QGMJSJIF3KYID3KTKCBUOIKIC6VZ3SEGTGM3N2JTWKGDBAPLSG76Q3MXIDJRMNLETOKAUTSBOPVQEQAX25MF77RV6QVTTSCV2ZY2VMN7FATRGO3JATR

§ References

§ Normative

The following is a list of normative references

DID-CORE
Decentralized Identifiers (DIDs) v1.0. Drummond Reed; Manu Sporny; Markus Sabadello; Dave Longley; Christopher Allen; Jonathan Holt; 2020-09-07. Status: WD.
JSON-LD
JSON-LD 1.0. Manu Sporny; Gregg Kellogg; Markus Lanthaler; 2014-01-16. Status: REC.
RFC2119
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner; 1997-03. Status: Best Current Practice.
RFC3986
Uniform Resource Identifier (URI): Generic Syntax. T. Berners-Lee; R. Fielding; L. Masinter; 2005-01. Status: Internet Standard.
RFC4122
A Universally Unique IDentifier (UUID) URN Namespace. P. Leach; M. Mealling; R. Salz; 2005-07. Status: Proposed Standard.
RFC4648
The Base16, Base32, and Base64 Data Encodings. S. Josefsson; 2006-10. Status: Proposed Standard.
RFC7049
Concise Binary Object Representation (CBOR). C. Bormann; P. Hoffman; 2013-10. Status: Proposed Standard.
RFC7517
JSON Web Key (JWK). M. Jones; 2015-05. Status: Proposed Standard.
RFC8152
CBOR Object Signing and Encryption (COSE). J. Schaad; 2017-07. Status: Proposed Standard.
RFC8174
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba; 2017-05. Status: Best Current Practice.
RFC8392
CBOR Web Token (CWT). M. Jones; E. Wahlstroem; S. Erdtman; H. Tschofenig; 2018-05. Status: Proposed Standard.

§ Appendices

§ JSON Schema Definitions

CWT JSON Schema definition

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "title": "NZ Covid Pass CWT",
    "$comment": "Schema version 1.0.0",
    "type": "object",
    "required": [
        "iss",
        "nbf",
        "exp",
        "vc"
    ],
    "properties": {
        "iss": {
            "title": "Verifiable Credential Issuer",
            "description": "The issuer of the verifiable credential",
            "type": "string",
            "examples": [
                "did:web:example.com"
            ]
        },
        "nbf": {
            "title": "Not Before",
            "description": "The issued date of the verifiable credential",
            "type": "integer"
        },
        "exp": {
            "title": "Expiry",
            "description": "The expiry of the verifiable credential",
            "type": "integer"
        },
        "vc": {
            "description": "Verifiable Credential Claim",
            "$ref": "#/$defs/vc"
        }
    },
    "$defs": {
        "vc": {
            "type": "object",
            "required": [
                "@context",
                "type",
                "version",
                "credentialSubject"
            ],
            "properties": {
                "@context": {
                    "title": "JSON-LD Context Processing Directive",
                    "type": "array",
                    "minItems": 1
                },
                "type": {
                    "title": "Credential Type",
                    "type": "array",
                    "const": [
                        "VerifiableCredential",
                        "PublicCovidPass"
                    ],
                    "minItems": 2,
                    "maxItems": 2
                },
                "version": {
                    "title": "Schema version",
                    "description": "Version of the schema, according to Semantic versioning (ISO, https://semver.org/ version 2.0.0 or newer)",
                    "type": "string",
                    "pattern": "^\\d+.\\d+.\\d+$",
                    "examples": [
                        "1.0.0"
                    ]
                },
                "credentialSubject": {
                    "description": "Credential Subject",
                    "$ref": "#/$defs/credentialSubject"
                }
            }
        },
        "credentialSubject": {
            "description": "Credential Subject definition for verifiable credential",
            "required": [
                "givenName",
                "dob"
            ],
            "type": "object",
            "properties": {
                "givenName": {
                    "title": "Given Name(s)",
                    "description": "The given name(s) of the person addressed in the pass",
                    "type": "string",
                    "maxLength": 100,
                    "examples": [
                        "John"
                    ]
                },
                "familyName": {
                    "title": "Family Name(s)",
                    "description": "The family name(s) of the person addressed in the pass",
                    "type": "string",
                    "maxLength": 100,
                    "examples": [
                        "Doe"
                    ]
                },
                "dob": {
                    "title": "Date of birth",
                    "description": "Date of Birth of the person addressed in the pass. ISO 8601 date format(YYYY-MM-DD)",
                    "type": "string",
                    "format": "date",
                    "examples": [
                        "1979-04-14"
                    ]
                }
            }
        }
    }
}

VC JSON Schema definition

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "title": "NZ Covid Pass Verifiable Credential Claim",
    "$comment": "Schema version 1.0.0",
    "type": "object",
    "required": [
        "@context",
        "type",
        "version",
        "credentialSubject"
    ],
    "properties": {
        "@context": {
            "title": "JSON-LD Context Processing Directive",
            "type": "array",
            "minItems": 1
        },
        "type": {
            "title": "Credential Type",
            "type": "array",
            "const": [
                "VerifiableCredential",
                "PublicCovidPass"
            ],
            "minItems": 2,
            "maxItems": 2
        },
        "version": {
            "title": "Schema version",
            "description": "Version of the schema, according to Semantic versioning (ISO, https://semver.org/ version 2.0.0 or newer)",
            "type": "string",
            "pattern": "^\\d+.\\d+.\\d+$",
            "examples": [
                "1.0.0"
            ]
        },
        "credentialSubject": {
            "description": "Credential Subject",
            "$ref": "#/$defs/credentialSubject"
        }
    },
    "$defs": {
        "credentialSubject": {
            "description": "Credential Subject definition for verifiable credential",
            "required": [
                "givenName",
                "dob"
            ],
            "type": "object",
            "properties": {
                "givenName": {
                    "title": "Given Name(s)",
                    "description": "The given name(s) of the person addressed in the pass",
                    "type": "string",
                    "maxLength": 100,
                    "examples": [
                        "John"
                    ]
                },
                "familyName": {
                    "title": "Family Name(s)",
                    "description": "The family name(s) of the person addressed in the pass",
                    "type": "string",
                    "maxLength": 100,
                    "examples": [
                        "Doe"
                    ]
                },
                "dob": {
                    "title": "Date of birth",
                    "description": "Date of Birth of the person addressed in the pass. ISO 8601 date format(YYYY-MM-DD)",
                    "type": "string",
                    "format": "date",
                    "examples": [
                        "1979-04-14"
                    ]
                }
            }
        }
    }
}

§ JSON-LD Context Definition

{
    "@context": {
        "@version": 1.1,
        "@protected": true,
        "id": "@id",
        "type": "@type",
        "version": "https://nzcp.covid19.health.nz#version",
        "CovidPass": {
            "@id": "https://nzcp.covid19.health.nz#PublicCovidPass",
            "@context": {
                "@version": 1.1,
                "@protected": true,
                "id": "@id",
                "type": "@type",
                "givenName": "https://schema.org/givenName",
                "familyName": "https://schema.org/familyName",
                "dob": {
                    "@id": "http://schema.org/birthDate",
                    "@type": "http://www.w3.org/2001/XMLSchema#dateTime"
                }
            }
        }
    }
}

§ 2D Barcode Encoding Options Rational

Numerous encoding options were considered during the design of this specification

Base45 as documented in the ietf internet draft and used by the EU DCC is an optimal encoding set for encoding abitrary content into a QR Code via the alphanumeric mode. However due to the characters chosen to makeup the encoding set including those un-supported (e.g space ) or reserved for special purposes in the URI standards (e.g $), creates interoperability challenges. For instance many adjacent systems surrounding QR Code technology, such as mobile platforms (iOS/android) expect a decoded payload from a QR code to be a URI and as such will automatically perform certain operations like escaping invalid characters such as spaces ( ) transforming them into %20. This has the effect of transforming an encoded string away from its original form and hence making it un-processable to a verifier.

Example issues

Smart health cards which use JWT as the underlying digital signature and payload representation layer, do a custom algorithmic transform of the base64 form of a JWT into a payload compatible with numeric mode QR code encoding. This complexity is caused by JWT’s having to be encoded as base64 as per the standard, this constraint does not exist with CWT and hence encoding an NZCP this way would be redundant complexity.

QR Codes do define a binary data encoding mode, which would appear to make the most sense for encoding a binary structure like a CWT. However, unfortunately most QR code generator and reader libraries enforce the ISO 8859-1 character set over this mode effectively rendering the format un-useable for binary data.

Table of Contents