diff --git a/.npmignore b/.npmignore index 2da1930..0a0671f 100644 --- a/.npmignore +++ b/.npmignore @@ -11,7 +11,12 @@ coverage .gitlab-ci.yml package-lock.json +build-and-publish +build.ts +jest.config.js /*.tgz /tmp* /mnt/ /package/ +/src/ +/patches/ diff --git a/README.md b/README.md index c68464b..d324056 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# NZCP.js +# NZCP.js   ![npm](https://img.shields.io/npm/v/@vaxxnz/nzcp) ![NPM](https://img.shields.io/npm/l/@vaxxnz/nzcp) ![npm](https://img.shields.io/npm/dw/@vaxxnz/nzcp) A JavaScript implementation of [NZ COVID Pass](https://github.com/minhealthnz/nzcovidpass-spec) verification, New Zealand's proof of COVID-19 vaccination solution, written in TypeScript. All contributions welcome 🥳 We also have a [Rust implementation](https://github.com/vaxxnz/nzcp-rust/) available. -> This library can be used for both Client and Server-side implementations. +> This library can be used for both in browser and Node.js. ## Install diff --git a/browser-test.html b/browser-test.html deleted file mode 100644 index b9f0a0b..0000000 --- a/browser-test.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - Open the devtools 👇 - - diff --git a/publish b/build-and-publish similarity index 91% rename from publish rename to build-and-publish index 8c527f9..56a5559 100755 --- a/publish +++ b/build-and-publish @@ -1,5 +1,6 @@ #!/bin/bash +yarn build-all echo '@vaxxnz:registry=https://registry.npmjs.org' > .npmrc npm publish echo '@vaxxnz:registry=https://npm.pkg.github.com' > .npmrc diff --git a/build.ts b/build.ts index 8213109..b3ec163 100644 --- a/build.ts +++ b/build.ts @@ -12,7 +12,7 @@ async function main() { format: "cjs", target: "es6" }) - console.log(resultBrowser) + console.log('resultBrowser', resultBrowser) const resultNode = await build({ entryPoints: ['src/node.ts'], @@ -24,6 +24,6 @@ async function main() { format: "cjs", target: "es6" }) - console.log(resultNode) + console.log('resultNode', resultNode) } main() diff --git a/package.json b/package.json index a7c8cb3..f275316 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "test-watch": "jest --watch", "clean": "rm -rf dist build package", "docs": "typedoc --entryPoints src/main.ts", - "build-all": "yarn clean && patch-package && tsc -p tsconfig.json && yarn ts-node build.ts" + "build-all": "yarn clean && patch-package && tsc --emitDeclarationOnly -p tsconfig.json && yarn ts-node build.ts", + "build-and-publish": "./build-and-publish" }, "devDependencies": { "@types/elliptic": "^6.4.14", diff --git a/patches/cbor+6.0.1.patch b/patches/cbor+6.0.1.patch index d9c8d98..e4b8a3a 100644 --- a/patches/cbor+6.0.1.patch +++ b/patches/cbor+6.0.1.patch @@ -1,13 +1,16 @@ diff --git a/node_modules/cbor/lib/constants.js b/node_modules/cbor/lib/constants.js -index 932e1c5..9b4502c 100644 +index 932e1c5..9ca7538 100644 --- a/node_modules/cbor/lib/constants.js +++ b/node_modules/cbor/lib/constants.js -@@ -2,7 +2,7 @@ +@@ -2,7 +2,10 @@ // Let's get consistent first, then we can think about feature testing // for BigNumber support -const {BigNumber} = require('bignumber.js') -+const {BigNumber} = require('bignumber.js/bignumber.mjs') ++const isNode = typeof process !== 'undefined' ++ && process.versions != null ++ && process.versions.node != null ++const {BigNumber} = isNode ? require('bignumber.js') : require('bignumber.js/bignumber.mjs') exports.BigNumber = BigNumber diff --git a/src/base32.test.ts b/src/base32.test.ts new file mode 100644 index 0000000..197ddda --- /dev/null +++ b/src/base32.test.ts @@ -0,0 +1,11 @@ +import { base32 } from "rfc4648"; + +test("Base32 library works", async () => { + const base32Example = + "2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAYFE6VGU4MCDGK7DHLLYWHVPUS2YIDJOA6Y524TD3AZRM263WTY2BE4DPKIF27WKF3UDNNVSVWRDYIYVJ65IRJJJ6Z25M2DO4YZLBHWFQGVQR5ZLIWEQJOZTS3IQ7JTNCFDX"; + + const res = base32.parse(base32Example); + + const base32String = base32.stringify(res); + expect(base32String).toBe(base32Example); +}); diff --git a/src/browser.ts b/src/browser.ts index 6d31900..cbc2700 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -2,5 +2,5 @@ * This file is the entrypoint of browser builds. * The code executes when loaded in a browser. */ -import { verifyPassURI, verifyPassURIWithTrustedIssuers } from './main' -export { verifyPassURI, verifyPassURIWithTrustedIssuers } +import { verifyPassURI, verifyPassURIWithTrustedIssuers } from "./main"; +export { verifyPassURI, verifyPassURIWithTrustedIssuers }; diff --git a/src/cbor.test.ts b/src/cbor.test.ts new file mode 100644 index 0000000..834a444 --- /dev/null +++ b/src/cbor.test.ts @@ -0,0 +1,14 @@ +import { base32 } from "rfc4648"; +import cbor from "cbor"; + +test("CBOR library works", async () => { + const res = base32.parse( + "2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAYFE6VGU4MCDGK7DHLLYWHVPUS2YIDJOA6Y524TD3AZRM263WTY2BE4DPKIF27WKF3UDNNVSVWRDYIYVJ65IRJJJ6Z25M2DO4YZLBHWFQGVQR5ZLIWEQJOZTS3IQ7JTNCFDX" + ); + + const cborobj = cbor.decode(res); + + expect(cborobj.err).toEqual(undefined); + expect(cborobj.tag).toEqual(18); + expect(cborobj.value.length).toEqual(4); +}); diff --git a/src/coseTypes.ts b/src/coseTypes.ts index a0c03a9..3cd09a0 100644 --- a/src/coseTypes.ts +++ b/src/coseTypes.ts @@ -1,8 +1,7 @@ - -type DecodedCOSEValue = (Buffer | Record)[] +type DecodedCOSEValue = (Buffer | Record)[]; export interface DecodedCOSEStructure { - tag: number, - value: DecodedCOSEValue, - err?: Error + tag: number; + value: DecodedCOSEValue; + err?: Error; } diff --git a/src/crypto.ts b/src/crypto.ts index 62b4e75..eef325c 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -33,7 +33,7 @@ export function validateCOSESignature( // ] const SigStructure = ["Signature1", protected_, Buffer.alloc(0), payload_]; const ToBeSigned = cbor.encode(SigStructure); - const messageHash = sha256.digest(ToBeSigned) + const messageHash = sha256.digest(ToBeSigned); const signature = { r: signature_.slice(0, signature_.length / 2), s: signature_.slice(signature_.length / 2), diff --git a/src/cwt.ts b/src/cwt.ts index 6fedf78..f641428 100644 --- a/src/cwt.ts +++ b/src/cwt.ts @@ -9,7 +9,6 @@ import { CWTClaimsResult } from "./generalTypes"; import { decodeCtiToJti } from "./jtiCti"; import { currentTimestamp } from "./util"; - export function parseCWTClaims( rawCWTClaims: RawCWTClaims ): UnvalidatedCWTClaims { @@ -110,7 +109,8 @@ export function validateCWTClaims( return { success: false, violates: { - message: "Not Before claim MUST be present and MUST be a timestamp encoded as an integer in the NumericDate format (as specified in [RFC8392] section 2)", + message: + "Not Before claim MUST be present and MUST be a timestamp encoded as an integer in the NumericDate format (as specified in [RFC8392] section 2)", section: "2.1.0.3.1", link: "https://nzcp.covid19.health.nz/#cwt-claims", }, @@ -126,7 +126,8 @@ export function validateCWTClaims( return { success: false, violates: { - message: "Not Before claim MUST be present and MUST be a timestamp encoded as an integer in the NumericDate format (as specified in [RFC8392] section 2)", + message: + "Not Before claim MUST be present and MUST be a timestamp encoded as an integer in the NumericDate format (as specified in [RFC8392] section 2)", section: "2.1.0.4.1", link: "https://nzcp.covid19.health.nz/#cwt-claims", }, @@ -165,7 +166,6 @@ export function validateCWTClaims( }; } - // Section 2.1.0.5.3 // The vc claim is currrently unregistered and therefore MUST be encoded as a Major Type 3 string as defined by [RFC7049]. if (cwtClaims.vc) { @@ -292,7 +292,13 @@ export function validateCWTClaims( return { success: true, - cwtClaims: { jti: cwtClaims.jti, iss: cwtClaims.iss, nbf: cwtClaims.nbf, exp: cwtClaims.exp, vc: cwtClaims.vc }, + cwtClaims: { + jti: cwtClaims.jti, + iss: cwtClaims.iss, + nbf: cwtClaims.nbf, + exp: cwtClaims.exp, + vc: cwtClaims.vc, + }, violates: null, }; } diff --git a/src/cwtTypes.ts b/src/cwtTypes.ts index 8e41457..0faca0f 100644 --- a/src/cwtTypes.ts +++ b/src/cwtTypes.ts @@ -5,7 +5,7 @@ export interface CredentialSubject { } export interface VC { - '@context': string[]; + "@context": string[]; version: string; type: string[]; credentialSubject: CredentialSubject; @@ -19,17 +19,18 @@ export interface CWTClaims { jti: string; } -export type UnvalidatedCWTClaims = Partial - - +export type UnvalidatedCWTClaims = Partial; export type RawCWTHeaders = Map; -export type RawCWTClaims = Map; +export type RawCWTClaims = Map< + number | string, + string | number | Buffer | unknown +>; interface CWTHeaders { - kid: string - alg: string + kid: string; + alg: string; } -export type UnvalidatedCWTHeaders = Partial +export type UnvalidatedCWTHeaders = Partial; diff --git a/src/did.test.ts b/src/did.test.ts new file mode 100644 index 0000000..3ff662e --- /dev/null +++ b/src/did.test.ts @@ -0,0 +1,17 @@ +import { Resolver } from "did-resolver"; +import { getResolver } from "web-did-resolver"; + +const webResolver = getResolver(); + +const didResolver = new Resolver({ + ...webResolver, +}); + +test("CBOR library works", async () => { + const doc = await didResolver.resolve("did:web:nzcp.covid19.health.nz"); + expect(doc).toBeTruthy(); + expect(doc.didDocument).toBeTruthy(); + expect(doc.didDocument?.id).toBe("did:web:nzcp.covid19.health.nz"); + expect(doc.didDocumentMetadata).toBeTruthy(); + expect(doc.didResolutionMetadata).toBeTruthy(); +}); diff --git a/src/elliptic.test.ts b/src/elliptic.test.ts new file mode 100644 index 0000000..dada568 --- /dev/null +++ b/src/elliptic.test.ts @@ -0,0 +1,28 @@ +import elliptic from "elliptic"; + +const fromHexString = (hexString: string) => + new Uint8Array( + (hexString.match(/.{1,2}/g) ?? []).map((byte) => parseInt(byte, 16)) + ); + +test("elliptic library works", async () => { + const publicKeyHex = + "04cd147e5c6b02a75d95bdb82e8b80c3e8ee9caa685f3ee5cc862d4ec4f97cefad22fe5253a16e5be4d1621e7f18eac995c57f82917f1a9150842383f0b4a4dd3d"; + const messageHashHex = + "0513bb48e77bcfa51209a78d3224b0b2f1a29a9b9c0eff2263b6d08156aee72a"; + const signatureRHex = + "f6a9a841a390a40bd5cee4434cccdb7499d9461840f5c8dff436cba0698b1ab2"; + const signatureSHex = + "4dca052720b9f581200bebac2fff1afa159ce42aeb38d558df9413899db48271"; + + const signature = { + r: fromHexString(signatureRHex), + s: fromHexString(signatureSHex), + }; + + const EC = elliptic.ec; + const ec = new EC("p256"); + const key = ec.keyFromPublic(publicKeyHex, "hex"); + const result = key.verify(fromHexString(messageHashHex), signature); + expect(result).toBe(true); +}); diff --git a/src/generalTypes.ts b/src/generalTypes.ts index 8041791..8f437b5 100644 --- a/src/generalTypes.ts +++ b/src/generalTypes.ts @@ -7,8 +7,8 @@ export interface Violates { } export type VerificationResult = - | { success: true; violates: null, credentialSubject: CredentialSubject } - | { success: false; violates: Violates, credentialSubject: null }; + | { success: true; violates: null; credentialSubject: CredentialSubject } + | { success: false; violates: Violates; credentialSubject: null }; export type CWTClaimsResult = | { success: true; violates: null; cwtClaims: CWTClaims } diff --git a/src/main.test.ts b/src/main.test.ts index 53198b9..e430925 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -2,13 +2,16 @@ import { verifyPassURIWithTrustedIssuers } from "./main"; // This is the list of trusted issuers which work with the examples specified in v1 of NZ COVID Pass - Technical Specification // https://nzcp.covid19.health.nz/ -const nzcpExamplesTrustedIssuers = ["did:web:nzcp.covid19.health.nz"] +const nzcpExamplesTrustedIssuers = ["did:web:nzcp.covid19.health.nz"]; // https://nzcp.covid19.health.nz/#valid-worked-example const validPass = "NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAYFE6VGU4MCDGK7DHLLYWHVPUS2YIDJOA6Y524TD3AZRM263WTY2BE4DPKIF27WKF3UDNNVSVWRDYIYVJ65IRJJJ6Z25M2DO4YZLBHWFQGVQR5ZLIWEQJOZTS3IQ7JTNCFDX"; test("Valid pass is successful", async () => { - const result = await verifyPassURIWithTrustedIssuers(validPass, nzcpExamplesTrustedIssuers); + const result = await verifyPassURIWithTrustedIssuers( + validPass, + nzcpExamplesTrustedIssuers + ); expect(result.success).toBe(true); expect(result.credentialSubject?.givenName).toBe("Jack"); expect(result.credentialSubject?.familyName).toBe("Sparrow"); @@ -19,7 +22,10 @@ test("Valid pass is successful", async () => { const badPublicKeyPass = "NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAY73U6TCQ3KF5KFML5LRCS5D3PCYIB2D3EOIIZRPXPUA2OR3NIYCBMGYRZUMBNBDMIA5BUOZKVOMSVFS246AMU7ADZXWBYP7N4QSKNQ4TETIF4VIRGLHOXWYMR4HGQ7KYHHU"; test("Bad Public Key pass is unsuccessful", async () => { - const result = await verifyPassURIWithTrustedIssuers(badPublicKeyPass, nzcpExamplesTrustedIssuers); + const result = await verifyPassURIWithTrustedIssuers( + badPublicKeyPass, + nzcpExamplesTrustedIssuers + ); expect(result.success).toBe(false); expect(result.violates?.section).toBe("3"); }); @@ -28,7 +34,10 @@ test("Bad Public Key pass is unsuccessful", async () => { const publicKeyNotFoundPass = "NZCP:/1/2KCEVIQEIVVWK6JNGIASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVBMP3LEDMB4CLBS2I7IOYJZW46U2YIBCSOFZMQADVQGM3JKJBLCY7ATASDTUYWIP4RX3SH3IFBJ3QWPQ7FJE6RNT5MU3JHCCGKJISOLIMY3OWH5H5JFUEZKBF27OMB37H5AHF"; test("Public Key Not Found pass is unsuccessful", async () => { - const result = await verifyPassURIWithTrustedIssuers(publicKeyNotFoundPass, nzcpExamplesTrustedIssuers); + const result = await verifyPassURIWithTrustedIssuers( + publicKeyNotFoundPass, + nzcpExamplesTrustedIssuers + ); expect(result.success).toBe(false); expect(result.violates?.section).toBe("5.1.1"); }); @@ -37,7 +46,10 @@ test("Public Key Not Found pass is unsuccessful", async () => { const modifiedSignaturePass = "NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVAYFE6VGU4MCDGK7DHLLYWHVPUS2YIAAAAAAAAAAAAAAAAC63WTY2BE4DPKIF27WKF3UDNNVSVWRDYIYVJ65IRJJJ6Z25M2DO4YZLBHWFQGVQR5ZLIWEQJOZTS3IQ7JTNCFDX"; test("Modified Signature pass is unsuccessful", async () => { - const result = await verifyPassURIWithTrustedIssuers(modifiedSignaturePass, nzcpExamplesTrustedIssuers); + const result = await verifyPassURIWithTrustedIssuers( + modifiedSignaturePass, + nzcpExamplesTrustedIssuers + ); expect(result.success).toBe(false); expect(result.violates?.section).toBe("3"); }); @@ -46,7 +58,10 @@ test("Modified Signature pass is unsuccessful", async () => { const modifiedPayloadPass = "NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEOKKALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUYMBTIFAIGTUKBAAUYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWKU3UMV3GK2TGMFWWS3DZJZQW2ZLDIRXWKY3EN5RGUMJZGYYC2MBUFUYTMB2QMCSPKTKOGBBTFPRTVV4LD2X2JNMEAAAAAAAAAAAAAAAABPN3J4NASOBXVEC5P3FC52BWW2ZK3IR4EMKU7OUIUUU7M5OWNBXOMMVQT3CYDKYI64VULCIEXMZZNUIPUZWRCR3Q"; test("Modified Payload pass is unsuccessful", async () => { - const result = await verifyPassURIWithTrustedIssuers(modifiedPayloadPass, nzcpExamplesTrustedIssuers); + const result = await verifyPassURIWithTrustedIssuers( + modifiedPayloadPass, + nzcpExamplesTrustedIssuers + ); expect(result.success).toBe(false); expect(result.violates?.section).toBe("3"); }); @@ -55,7 +70,10 @@ test("Modified Payload pass is unsuccessful", async () => { const expiredPass = "NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRUX5AM2FQIGTBPBPYWYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVA56TNJCCUN2NVK5NGAYOZ6VIWACYIBM3QXW7SLCMD2WTJ3GSEI5JH7RXAEURGATOHAHXC2O6BEJKBSVI25ICTBR5SFYUDSVLB2F6SJ63LWJ6Z3FWNHOXF6A2QLJNUFRQNTRU"; test("Expired Pass is unsuccessful", async () => { - const result = await verifyPassURIWithTrustedIssuers(expiredPass, nzcpExamplesTrustedIssuers); + const result = await verifyPassURIWithTrustedIssuers( + expiredPass, + nzcpExamplesTrustedIssuers + ); expect(result.success).toBe(false); expect(result.violates?.section).toBe("2.1.0.4.3"); }); @@ -64,17 +82,22 @@ test("Expired Pass is unsuccessful", async () => { const notActivePass = "NZCP:/1/2KCEVIQEIVVWK6JNGEASNICZAEP2KALYDZSGSZB2O5SWEOTOPJRXALTDN53GSZBRHEXGQZLBNR2GQLTOPICRU2XI5UFQIGTMZIQIWYTWMOSGQQDDN5XHIZLYOSBHQJTIOR2HA4Z2F4XXO53XFZ3TGLTPOJTS6MRQGE4C6Y3SMVSGK3TUNFQWY4ZPOYYXQKTIOR2HA4Z2F4XW46TDOAXGG33WNFSDCOJONBSWC3DUNAXG46RPMNXW45DFPB2HGL3WGFTXMZLSONUW63TFGEXDALRQMR2HS4DFQJ2FMZLSNFTGSYLCNRSUG4TFMRSW45DJMFWG6UDVMJWGSY2DN53GSZCQMFZXG4LDOJSWIZLOORUWC3CTOVRGUZLDOSRWSZ3JOZSW4TTBNVSWISTBMNVWUZTBNVUWY6KOMFWWKZ2TOBQXE4TPO5RWI33CNIYTSNRQFUYDILJRGYDVA27NR3GFF4CCGWF66QGMJSJIF3KYID3KTKCBUOIKIC6VZ3SEGTGM3N2JTWKGDBAPLSG76Q3MXIDJRMNLETOKAUTSBOPVQEQAX25MF77RV6QVTTSCV2ZY2VMN7FATRGO3JATR"; test("Not Active pass is unsuccessful", async () => { - const result = await verifyPassURIWithTrustedIssuers(notActivePass, nzcpExamplesTrustedIssuers); + const result = await verifyPassURIWithTrustedIssuers( + notActivePass, + nzcpExamplesTrustedIssuers + ); expect(result.success).toBe(false); expect(result.violates?.section).toBe("2.1.0.3.3"); }); - // Custom Test: non base-32 string in the payload -const notBase32 = "NZCP:/1/asdfghasSDFGHFDSADFGHFDSADFGHGFSDADFGBHFSADFGHFDSFGHFDDS0123456789" +const notBase32 = + "NZCP:/1/asdfghasSDFGHFDSADFGHFDSADFGHGFSDADFGBHFSADFGHFDSFGHFDDS0123456789"; test("Non base-32 string in the payload Pass is unsuccessful", async () => { - const result = await verifyPassURIWithTrustedIssuers(notBase32, nzcpExamplesTrustedIssuers); + const result = await verifyPassURIWithTrustedIssuers( + notBase32, + nzcpExamplesTrustedIssuers + ); expect(result.success).toBe(false); expect(result.violates?.section).toBe("4.7"); }); - diff --git a/src/main.ts b/src/main.ts index b8c9dbe..fceb171 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,13 +13,15 @@ import { VerificationResult } from "./generalTypes"; // The following is a list of trusted issuer identifiers for New Zealand Covid Passes. const nzcpTrustedIssuers = ["did:web:nzcp.identity.health.nz"]; -export const verifyPassURI = async (payload: string): Promise => { - return verifyPassURIWithTrustedIssuers(payload, nzcpTrustedIssuers); -} +export const verifyPassURI = async ( + uri: string +): Promise => { + return verifyPassURIWithTrustedIssuers(uri, nzcpTrustedIssuers); +}; // TODO: add tests for every error path export const verifyPassURIWithTrustedIssuers = async ( - payload: string, + uri: string, trustedIssuers: string[] ): Promise => { // Section 4: 2D Barcode Encoding @@ -29,7 +31,7 @@ export const verifyPassURIWithTrustedIssuers = async ( // Section 4.4 // Parse the form of QR Code payload const payloadRegex = /(NZCP:\/)(\d+)\/([A-Za-z2-7=]+)/; - const payloadMatch = payload.match(payloadRegex); + const payloadMatch = uri.match(payloadRegex); if (!payloadMatch) { return { success: false, @@ -116,7 +118,7 @@ export const verifyPassURIWithTrustedIssuers = async ( // a501781e6469643a7765623a6e7a63702e636f76696431392e6865616c74682e6e7a051a61819a0a041a7450400a627663a46840636f6e7465787482782668747470733a2f2f7777772e77332e6f72672f323031382f63726564656e7469616c732f7631782a68747470733a2f2f6e7a63702e636f76696431392e6865616c74682e6e7a2f636f6e74657874732f76316776657273696f6e65312e302e306474797065827456657269666961626c6543726564656e7469616c6f5075626c6963436f766964506173737163726564656e7469616c5375626a656374a369676976656e4e616d65644a61636b6a66616d696c794e616d656753706172726f7763646f626a313936302d30342d3136075060a4f54d4e304332be33ad78b1eafa4b -- [2], a501781e6469643a7765623a6e7a63702e636f76696431392e6865616c74682e6e7a051a61819a0a041a7450400a627663a46840636f6e7465787482782668747470733a2f2f7777772e77332e6f72672f323031382f63726564656e7469616c732f7631782a68747470733a2f2f6e7a63702e636f76696431392e6865616c74682e6e7a2f636f6e74657874732f76316776657273696f6e65312e302e306474797065827456657269666961626c6543726564656e7469616c6f5075626c6963436f766964506173737163726564656e7469616c5375626a656374a369676976656e4e616d65644a61636b6a66616d696c794e616d656753706172726f7763646f626a313936302d30342d3136075060a4f54d4e304332be33ad78b1eafa4b // 58 -- Bytes, length next 1 byte // 40 -- Bytes, length: 64 - const decodedCOSEStructure = cbor.decode(uint8array) + const decodedCOSEStructure = cbor.decode(uint8array); // 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. diff --git a/src/node.ts b/src/node.ts index 132ace0..190d786 100644 --- a/src/node.ts +++ b/src/node.ts @@ -2,5 +2,5 @@ * This file is the entrypoint of node builds. * The code executes when loaded in a node. */ -import { verifyPassURI, verifyPassURIWithTrustedIssuers } from './main' -export { verifyPassURI, verifyPassURIWithTrustedIssuers } +import { verifyPassURI, verifyPassURIWithTrustedIssuers } from "./main"; +export { verifyPassURI, verifyPassURIWithTrustedIssuers }; diff --git a/src/util.ts b/src/util.ts index 73f8e7b..7d09459 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,3 @@ - // convert js timestamp to unix timestamp export function currentTimestamp(): number { return Date.now() / 1000; @@ -7,8 +6,8 @@ export function currentTimestamp(): number { // from https://nzcp.covid19.health.nz/#adding-base32-padding export function addBase32Padding(base32InputNoPadding: string): string { let result = base32InputNoPadding; - while ((result.length % 8) !== 0) { - result += '=' + while (result.length % 8 !== 0) { + result += "="; } return result; }