covid-certificate-checker
Version:
Web-Library for Reading and Verifying Covid Vaccination Certificates
124 lines • 11.2 kB
JavaScript
import { __awaiter } from "tslib";
import * as b45 from "base45-ts";
import * as pako from 'pako';
import * as cbor from 'cbor-web';
import * as x509 from "@peculiar/x509";
import { toBase64String } from "./utils";
import { CertMapperUtil } from "./CertMapper.util";
//See: https://github.com/ehn-dcc-development/ehn-sign-verify-javascript-trivial/blob/main/cose_verify.js
export default class ElectronicHealthCertificateChecker {
static decode(certificate) {
try {
const decodedCertificate = this.decodeCBOR(this.inflateZlib(this.decodeBase45(this.removeHC1Header(certificate))));
return decodedCertificate.hcertCertClaim;
}
catch (e) {
console.error("Error has happened at decoding Certificate", e);
throw e;
}
}
static verify(certificate, dscKey) {
return __awaiter(this, void 0, void 0, function* () {
try {
return ElectronicHealthCertificateChecker.verifyCertificate(certificate, dscKey);
}
catch (e) {
console.error(e);
throw e;
}
});
}
static verifyWithTrustList(certificate, trustList) {
return __awaiter(this, void 0, void 0, function* () {
try {
return ElectronicHealthCertificateChecker.verifyCertificate(certificate, undefined, trustList);
}
catch (e) {
console.error(e);
throw e;
}
});
}
static verifyCertificate(certificate, publicKey, trustList) {
return __awaiter(this, void 0, void 0, function* () {
if (!trustList && !publicKey) {
throw new DOMException("Please submit either a Public-Key or a Trust-List for verifying!");
}
if (!this.supportsWebCryptoApi()) {
throw new DOMException("Your Browser doesn't support WebCrypto-Api. Please provide a Polyfill!");
}
const decodedCertificate = this.decodeCBOR(this.inflateZlib(this.decodeBase45(this.removeHC1Header(certificate))));
let filteredCertificates = [];
if (trustList && trustList.certificates) {
filteredCertificates = trustList.certificates.filter(certificate => certificate.kid == decodedCertificate.kid);
if (filteredCertificates.length < 1) {
console.error("No Certificate in Trust-List with specified kid: " + decodedCertificate.kid);
}
}
const key = filteredCertificates.length > 0 ? filteredCertificates[0].rawData : publicKey;
if (!key) {
return {
healthCertificateClaim: decodedCertificate.hcertCertClaim,
isVerified: false,
};
}
const isVerified = yield this.verifyCoseMessage(decodedCertificate.coseMessage, key);
return {
healthCertificateClaim: decodedCertificate.hcertCertClaim,
isVerified
};
});
}
static removeHC1Header(certificate) {
if (certificate.startsWith("HC1:")) {
return certificate.substring(4);
}
console.warn("No HC1:-Prefix");
return certificate;
}
static decodeBase45(encodedMessage) {
return b45.decode(encodedMessage);
}
static inflateZlib(compressedString) {
return pako.inflate(compressedString);
}
static decodeCBOR(cborByteArray) {
const data = cbor.decode(cborByteArray);
const [protectHeader, unprotectHeader, payload, sig] = data.value;
const coseMessage = {
protectHeader,
unprotectHeader,
payload,
sig,
};
const decodedHeaders = cbor.decode(protectHeader);
let kidUintArray;
if (decodedHeaders && decodedHeaders.has(4)) {
kidUintArray = decodedHeaders.get(4);
}
else if (unprotectHeader && unprotectHeader.has(4)) {
kidUintArray = unprotectHeader.get(4);
}
const kid = toBase64String(kidUintArray);
const decodedPaylod = cbor.decode(payload);
const hcertCertClaim = CertMapperUtil.payloadToHCERTClaim(decodedPaylod);
return { coseMessage, hcertCertClaim, kid };
}
static supportsWebCryptoApi() {
return !!crypto
&& 'subtle' in crypto
&& 'importKey' in crypto.subtle
&& 'verify' in crypto.subtle;
}
static verifyCoseMessage(coseMessage, publicKey) {
return __awaiter(this, void 0, void 0, function* () {
const cert = new x509.X509Certificate(publicKey);
const rawData = cert.publicKey.rawData;
//See: https://github.com/btielen/covid-certificate/blob/7f8631a2e0bdc5debf5c9877858d71ffd8ade329/src/cose/ECDS256SignatureVerifier.ts#L6
const pk = yield crypto.subtle.importKey("spki", rawData, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
const verified = yield crypto.subtle.verify({ name: "ECDSA", hash: "sha-256" }, pk, coseMessage.sig, cbor.encode(["Signature1", coseMessage.protectHeader, (new Uint8Array(0)).buffer, coseMessage.payload]));
return verified;
});
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRWxlY3Ryb25pY0hlYWx0aENlcnRpZmljYXRlQ2hlY2tlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9saWIvRWxlY3Ryb25pY0hlYWx0aENlcnRpZmljYXRlQ2hlY2tlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsT0FBTyxLQUFLLEdBQUcsTUFBTSxXQUFXLENBQUM7QUFDakMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxLQUFLLElBQUksTUFBTSxVQUFVLENBQUM7QUFDakMsT0FBTyxLQUFLLElBQUksTUFBTSxnQkFBZ0IsQ0FBQztBQUN2QyxPQUFPLEVBQUMsY0FBYyxFQUFDLE1BQU0sU0FBUyxDQUFDO0FBQ3ZDLE9BQU8sRUFBQyxjQUFjLEVBQUMsTUFBTSxtQkFBbUIsQ0FBQztBQU1qRCx5R0FBeUc7QUFDekcsTUFBTSxDQUFDLE9BQU8sT0FBTyxrQ0FBa0M7SUFFbkQsTUFBTSxDQUFDLE1BQU0sQ0FBQyxXQUFtQjtRQUM3QixJQUFJO1lBQ0EsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ25ILE9BQU8sa0JBQWtCLENBQUMsY0FBYyxDQUFDO1NBQzVDO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDUixPQUFPLENBQUMsS0FBSyxDQUFDLDRDQUE0QyxFQUFFLENBQUMsQ0FBQyxDQUFDO1lBQy9ELE1BQU0sQ0FBQyxDQUFDO1NBQ1g7SUFDTCxDQUFDO0lBRUQsTUFBTSxDQUFPLE1BQU0sQ0FDZixXQUFtQixFQUNuQixNQUFjOztZQU1kLElBQUk7Z0JBQ0EsT0FBTyxrQ0FBa0MsQ0FBQyxpQkFBaUIsQ0FBQyxXQUFXLEVBQUUsTUFBTSxDQUFDLENBQUM7YUFDcEY7WUFBQyxPQUFPLENBQUMsRUFB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