UNPKG

dcc-decoder

Version:

<img alttext="COVID Green Logo" src="https://raw.githubusercontent.com/lfph/artwork/master/projects/covidgreen/stacked/color/covidgreen-stacked-color.png" width="300" />

178 lines (177 loc) 7.17 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const pako_1 = __importDefault(require("pako")); const cbor_1 = __importDefault(require("cbor")); const cose_js_1 = __importDefault(require("cose-js")); const hcert_1 = require("../types/hcert"); const util_1 = require("../util"); const errors = __importStar(require("../types/errors")); const verify_signature_1 = __importDefault(require("./verify-signature")); // eslint-disable-next-line const base45 = require('base45-js'); function getCountry(cert, type, iss) { try { return iss || cert[type][0].co; } catch (e) { return null; } } function getKid(protectedHeader, unprotectedHeader) { try { if (protectedHeader) { return protectedHeader.get(hcert_1.HEADER_KEYS.KID).toString('base64'); } else { return unprotectedHeader.get(hcert_1.HEADER_KEYS.KID).toString('base64'); } } catch (_a) { return null; } } function getAlgo(protectedHeader, unprotectedHeader) { try { if (protectedHeader) { return protectedHeader.get(hcert_1.HEADER_KEYS.ALGORITHM); } else { return unprotectedHeader.get(hcert_1.HEADER_KEYS.ALGORITHM); } } catch (_a) { return null; } } function getCertType(cert) { var _a, _b, _c; // Just checking if key exists does not suffice. // It should have an item in the array too. // Handles case ES/1.0.0/specialcases/VAC_4.png if ((_a = cert.v) === null || _a === void 0 ? void 0 : _a[0]) return hcert_1.CERT_TYPE.VACCINE; if ((_b = cert.t) === null || _b === void 0 ? void 0 : _b[0]) return hcert_1.CERT_TYPE.TEST; if ((_c = cert.r) === null || _c === void 0 ? void 0 : _c[0]) return hcert_1.CERT_TYPE.RECOVERY; } // function getSignature(message): string | null { // try { // return cbor.decodeFirstSync(message.value[CBOR_STRUCTURE.SIGNATURE]) // } catch { // return null // } // } function ensureCOSEStructure(qrCbor) { const message = cbor_1.default.decodeFirstSync(qrCbor); if (message instanceof cbor_1.default.Tagged && message.tag) { return qrCbor; } return cbor_1.default.encodeCanonical(new cbor_1.default.Tagged(cose_js_1.default.sign.Sign1Tag, message)); } function decodeCbor(qrCbor) { const message = cbor_1.default.decodeFirstSync(qrCbor); const protectedHeader = cbor_1.default.decodeFirstSync(message.value[hcert_1.CBOR_STRUCTURE.PROTECTED_HEADER]); const unprotectedHeader = message.value[hcert_1.CBOR_STRUCTURE.UNPROTECTED_HEADER]; const content = cbor_1.default.decodeFirstSync(message.value[hcert_1.CBOR_STRUCTURE.PAYLOAD]); // const signature = getSignature(message) const kid = getKid(protectedHeader, unprotectedHeader); const algo = getAlgo(protectedHeader, unprotectedHeader); const rawCert = util_1.mapToJSON(content.get(hcert_1.PAYLOAD_KEYS.CONTENT).get(1)); const type = getCertType(rawCert); const country = getCountry(rawCert, type, content.get(hcert_1.PAYLOAD_KEYS.ISSUER)); // move into a function as mapping/transformation grows if (type === hcert_1.CERT_TYPE.TEST) { if (rawCert.t[0].sc instanceof Date) { rawCert.t[0].sc = rawCert.t[0].sc.toISOString(); } } return { kid, country, issuedAt: content.get(hcert_1.PAYLOAD_KEYS.ISSUED_AT), expiresAt: content.get(hcert_1.PAYLOAD_KEYS.EXPIRES_AT), rawCert, algo, type, }; } function findKeysToValidateAgainst(country, kid, signingKeys) { const keys = []; if (kid) { keys.push(...signingKeys.filter(s => s.kid === kid)); } if (keys.length === 0 && !kid) { keys.push(...signingKeys.filter(s => s.country === country)); } return keys; } function decodeQR(qr, signingKeys) { return __awaiter(this, void 0, void 0, function* () { if (!qr.startsWith('HC1:')) { return { error: errors.invalidQR() }; } try { const qrBase45 = qr.replace('HC1:', ''); const qrZipped = base45.decode(qrBase45); const qrCbor = ensureCOSEStructure(Buffer.from(pako_1.default.inflate(qrZipped))); // We decode the whole cbor const { kid, rawCert, country, expiresAt, algo, type, issuedAt } = decodeCbor(qrCbor); const keysToUse = findKeysToValidateAgainst(country, kid, signingKeys); const wrapperData = { country, kid, algo, expiresAt: new Date(expiresAt * 1000), issuedAt: new Date(issuedAt * 1000), keysLength: keysToUse.length, type, }; if (new Date(expiresAt * 1000) < new Date()) { return { rawCert, error: errors.certExpired(), type, wrapperData }; } if (keysToUse.length === 0) { return { rawCert, error: errors.noMatchingSigKey(), type, wrapperData }; } const result = yield verify_signature_1.default(qrCbor, algo, keysToUse); const error = result instanceof Error ? result : undefined; return { rawCert, type, error, wrapperData }; } catch (err) { console.log('Error:', err); return { error: errors.invalidData() }; } }); } exports.default = decodeQR;