mirror of
https://github.com/covidpass-org/covidpass.git
synced 2025-02-23 15:07:44 +01:00
verifyJWS working now... picked up latest issuer code
This commit is contained in:
parent
db5052aa9a
commit
52d27089cc
@ -1,6 +1,5 @@
|
||||
// adapted from https://github.com/fproulx/shc-covid19-decoder/blob/main/src/shc.js
|
||||
|
||||
const jose = require("node-jose");
|
||||
const jsQR = require("jsqr");
|
||||
const zlib = require("zlib");
|
||||
import {Receipt} from "./payload";
|
||||
@ -13,54 +12,6 @@ export function getQRFromImage(imageData) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getScannedJWS(shcString) {
|
||||
return shcString
|
||||
.match(/^shc:\/(.+)$/)[1]
|
||||
.match(/(..?)/g)
|
||||
.map((num) => String.fromCharCode(parseInt(num, 10) + 45))
|
||||
.join("");
|
||||
}
|
||||
|
||||
//TODO: switch to https://github.com/smart-on-fhir/health-cards-dev-tools at some point
|
||||
|
||||
export function verifyJWS(jws) {
|
||||
return jose.JWK.asKey({
|
||||
kid: "some-kid",
|
||||
alg: "ES256",
|
||||
kty: "EC",
|
||||
crv: "P-256",
|
||||
use: "sig",
|
||||
x: "XSxuwW_VI_s6lAw6LAlL8N7REGzQd_zXeIVDHP_j_Do",
|
||||
y: "88-aI4WAEl4YmUpew40a9vq_w5OcFvsuaKMxJRLRLL0",
|
||||
}).then(function (key) {
|
||||
const { verify } = jose.JWS.createVerify(key);
|
||||
console.log("jws", jws);
|
||||
return verify(jws);
|
||||
});
|
||||
}
|
||||
|
||||
export function decodeJWS(jws) : Promise<object[]> {
|
||||
const verifiedPayload = jws.split(".")[1];
|
||||
const decodedPayload = Buffer.from(verifiedPayload, "base64");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
zlib.inflateRaw(decodedPayload, function (err, decompressedResult) {
|
||||
let scannedResult;
|
||||
if (typeof err === "object" && err) {
|
||||
console.log("Unable to decompress");
|
||||
reject();
|
||||
} else {
|
||||
console.log(decompressedResult);
|
||||
scannedResult = decompressedResult.toString("utf8");
|
||||
const entries =
|
||||
JSON.parse(scannedResult).vc.credentialSubject.fhirBundle.entry;
|
||||
|
||||
resolve(entries);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// vaccine codes based on Alex Dunae's findings
|
||||
// https://gist.github.com/alexdunae/49cc0ea95001da3360ad6896fa5677ec
|
||||
// http://mchp-appserv.cpe.umanitoba.ca/viewConcept.php?printer=Y&conceptID=1514
|
||||
|
66
src/issuers.js
Normal file
66
src/issuers.js
Normal file
@ -0,0 +1,66 @@
|
||||
const issuers = [
|
||||
{
|
||||
id: "ca.qc",
|
||||
iss: "https://covid19.quebec.ca/PreuveVaccinaleApi/issuer",
|
||||
keys: [
|
||||
{ kid: "qFdl0tDZK9JAWP6g9_cAv57c3KWxMKwvxCrRVSzcxvM",
|
||||
alg: "ES256", kty: "EC", crv: "P-256", use: "sig",
|
||||
x: "XSxuwW_VI_s6lAw6LAlL8N7REGzQd_zXeIVDHP_j_Do",
|
||||
y: "88-aI4WAEl4YmUpew40a9vq_w5OcFvsuaKMxJRLRLL0" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "us.ca",
|
||||
iss: "https://myvaccinerecord.cdph.ca.gov/creds",
|
||||
keys: [
|
||||
{ kid: "7JvktUpf1_9NPwdM-70FJT3YdyTiSe2IvmVxxgDSRb0",
|
||||
alg: "ES256", kty: "EC", crv: "P-256", use: "sig",
|
||||
x: "3dQz5ZlbazChP3U7bdqShfF0fvSXLXD9WMa1kqqH6i4",
|
||||
y: "FV4AsWjc7ZmfhSiHsw2gjnDMKNLwNqi2jMLmJpiKWtE" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "us.ny",
|
||||
iss: "https://ekeys.ny.gov/epass/doh/dvc/2021",
|
||||
keys: [
|
||||
{ kid: "9ENs36Gsu-GmkWIyIH9XCozU9BFhLeaXvwrT3B97Wok",
|
||||
alg: "ES256", kty: "EC", crv: "P-256", use: "sig",
|
||||
x: "--M0AedrNg31sHZGAs6qg7WU9LwnDCMWmd6KjiKfrZU",
|
||||
y: "rSf2dKerJFW3-oUNcvyrI2x39hV2EbazORZvh44ukjg" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "us.la",
|
||||
iss: "https://healthcardcert.lawallet.com",
|
||||
keys: [
|
||||
{ kid: "UOvXbgzZj4zL-lt1uJVS_98NHQrQz48FTdqQyNEdaNE",
|
||||
alg: "ES256", kty: "EC", crv: "P-256", use: "sig",
|
||||
x: "n1PxhSk7DQj8ZBK3VIfwhlcN__QG357gbiTfZYt1gn8",
|
||||
y: "ZDGv5JYe4mCm75HCsHG8UkIyffr1wcZMwJjH8v5cGCc" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "ca.yt",
|
||||
iss: "https://pvc.service.yukon.ca/issuer",
|
||||
keys: [
|
||||
{ kid: "UnHGY-iyCIr__dzyqcxUiApMwU9lfeXnzT2i5Eo7TvE",
|
||||
alg: "ES256", kty: "EC", crv: "P-256", use: "sig",
|
||||
x: "wCeT9rdLYTpOK52OK0-oRbwDxbljJdNiDuxPsPt_1go",
|
||||
y: "IgFPi1OrHtJWJGwPMvlueeHmULUKEpScgpQtoHNjX-Q" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "ca.bc",
|
||||
iss: "https://smarthealthcard.phsa.ca/v1/issuer",
|
||||
keys: [
|
||||
{ kid: "XCqxdhhS7SWlPqihaUXovM_FjU65WeoBFGc_ppent0Q",
|
||||
alg: "ES256", kty: "EC", crv: "P-256", use: "sig",
|
||||
x: "xscSbZemoTx1qFzFo-j9VSnvAXdv9K-3DchzJvNnwrY",
|
||||
y: "jA5uS5bz8R2nxf_TU-0ZmXq6CKWZhAG1Y4icAx8a9CA" },
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
issuers,
|
||||
};
|
@ -4,6 +4,7 @@ import jsQR, {QRCode} from "jsqr";
|
||||
import { getCertificatesInfoFromPDF } from "@ninja-labs/verify-pdf"; // ES6
|
||||
import * as Sentry from '@sentry/react';
|
||||
import * as Decode from './decode';
|
||||
import {getScannedJWS, verifyJWS, decodeJWS} from "./shc";
|
||||
import { PNG } from 'pngjs/browser';
|
||||
|
||||
import { PDFPageProxy, TextContent, TextItem } from "pdfjs-dist/types/display/api";
|
||||
@ -89,51 +90,29 @@ async function processBC(pdfPage: PDFPageProxy) {
|
||||
const imageData = await getImageDataFromPdf(pdfPage);
|
||||
const code : QRCode = await Decode.getQRFromImage(imageData);
|
||||
let rawData = code.data;
|
||||
let jws = Decode.getScannedJWS(rawData);
|
||||
// const verified = Decode.verifyJWS(jws);
|
||||
const verified = true;
|
||||
const jws = getScannedJWS(rawData);
|
||||
|
||||
let decoded = await decodeJWS(jws);
|
||||
|
||||
console.log(JSON.stringify(decoded, null, 2));
|
||||
|
||||
const verified = verifyJWS(jws, decoded.iss);
|
||||
|
||||
if (verified) {
|
||||
let decoded = await Decode.decodeJWS(jws);
|
||||
console.log(decoded);
|
||||
|
||||
let receipts = Decode.decodedStringToReceipt(decoded);
|
||||
let receipts = Decode.decodedStringToReceipt(decoded.vc.credentialSubject.fhirBundle.entry);
|
||||
|
||||
console.log(receipts);
|
||||
|
||||
return Promise.resolve({receipts: receipts, rawData: rawData});
|
||||
|
||||
} else {
|
||||
return Promise.reject('QR code not signed by BC or QC');
|
||||
|
||||
return Promise.reject(`Issuer ${decoded.iss} cannot be verified.`);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async function processBCPNG(fileBuffer : ArrayBuffer): Promise<any> {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
new PNG({ filterType: 4 }).parse(fileBuffer, async function (error, data) {
|
||||
const scannedQR = jsQR(new Uint8ClampedArray(data.data.buffer), data.width, data.height)
|
||||
if (scannedQR) {
|
||||
//console.log(scannedQR.data);
|
||||
let jws = Decode.getScannedJWS(scannedQR.data);
|
||||
// const verified = Decode.verifyJWS(jws);
|
||||
const verified = true;
|
||||
|
||||
if (verified) {
|
||||
let decoded = await Decode.decodeJWS(jws);
|
||||
//console.log(decoded);
|
||||
let receipts = Decode.decodedStringToReceipt(decoded);
|
||||
//console.log(receipts);
|
||||
resolve({receipts: receipts, rawData: scannedQR.data});
|
||||
} else {
|
||||
reject('QR code not signed by BC or QC');
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid QR code')
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
async function processON(signedPdfBuffer : ArrayBuffer, content: TextContent): Promise<any> {
|
||||
|
||||
// check for certs first
|
||||
@ -148,9 +127,7 @@ async function processON(signedPdfBuffer : ArrayBuffer, content: TextContent): P
|
||||
const issuedToOntarioHealth = (result.issuedTo.commonName == 'covid19signer.ontariohealth.ca');
|
||||
console.log(`PDF is signed by ${result.issuedBy.organizationName}, issued to ${result.issuedTo.commonName}`);
|
||||
|
||||
const bypass = window.location.href.includes('grassroots2');
|
||||
|
||||
if ((isClientCertificate && issuedByEntrust && issuedToOntarioHealth) || bypass) {
|
||||
if ((isClientCertificate && issuedByEntrust && issuedToOntarioHealth)) {
|
||||
|
||||
console.log('getting receipt details inside PDF');
|
||||
|
||||
|
78
src/shc.js
Normal file
78
src/shc.js
Normal file
@ -0,0 +1,78 @@
|
||||
const jose = require("node-jose");
|
||||
const jsQR = require("jsqr");
|
||||
const zlib = require("zlib");
|
||||
const { issuers } = require("./issuers");
|
||||
|
||||
function getQRFromImage(imageData) {
|
||||
return jsQR(
|
||||
new Uint8ClampedArray(imageData.data.buffer),
|
||||
imageData.width,
|
||||
imageData.height
|
||||
);
|
||||
}
|
||||
|
||||
function getScannedJWS(shcString) {
|
||||
try {
|
||||
return shcString
|
||||
.match(/^shc:\/(.+)$/)[1]
|
||||
.match(/(..?)/g)
|
||||
.map((num) => String.fromCharCode(parseInt(num, 10) + 45))
|
||||
.join("");
|
||||
} catch (e) {
|
||||
error = new Error("parsing shc string failed");
|
||||
error.cause = e;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function verifyJWS(jws, iss) {
|
||||
const issuer = issuers.find(el => el.iss === iss);
|
||||
if (!issuer) {
|
||||
error = new Error("Unknown issuer " + iss);
|
||||
error.customMessage = true;
|
||||
return Promise.reject(error);
|
||||
}
|
||||
return jose.JWK.asKeyStore({ keys: issuer.keys }).then(function (keyStore) {
|
||||
const { verify } = jose.JWS.createVerify(keyStore);
|
||||
console.log("jws", jws);
|
||||
return verify(jws);
|
||||
});
|
||||
}
|
||||
|
||||
function decodeJWS(jws) {
|
||||
try {
|
||||
const payload = jws.split(".")[1];
|
||||
return decodeJWSPayload(Buffer.from(payload, "base64"));
|
||||
} catch (e) {
|
||||
error = new Error("decoding payload failed");
|
||||
error.cause = e;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function decodeJWSPayload(decodedPayload) {
|
||||
return new Promise((resolve, reject) => {
|
||||
zlib.inflateRaw(decodedPayload, function (err, decompressedResult) {
|
||||
if (typeof err === "object" && err) {
|
||||
console.log("Unable to decompress");
|
||||
reject(err);
|
||||
} else {
|
||||
try {
|
||||
console.log(decompressedResult);
|
||||
scannedResult = decompressedResult.toString("utf8");
|
||||
resolve(JSON.parse(scannedResult));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getQRFromImage,
|
||||
getScannedJWS,
|
||||
verifyJWS,
|
||||
decodeJWS,
|
||||
decodeJWSPayload,
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user