Skip to content

Instantly share code, notes, and snippets.

@peakwinter
Last active September 1, 2021 13:28
Show Gist options
  • Save peakwinter/c820795563d54089280940e9b6da7073 to your computer and use it in GitHub Desktop.
Save peakwinter/c820795563d54089280940e9b6da7073 to your computer and use it in GitHub Desktop.
/**
* Decode and display information contained in a SMART Health Card (SHC) QR code
* Usage: node vaccineqr.js /path/to/qrcode.png
*
* Required dependencies:
* - jimp
* - jsqr
*
* 2021 Jacob Cook
*/
const { promises: fs } = require("fs");
const path = require("path");
const jimp = require("jimp");
const jsQR = require("jsqr");
const zlib = require("zlib");
const util = require("util");
function jwtSectionToBuffer(section) {
return Buffer.from(section.replace("-", "+").replace("_", "/"), "base64");
}
function parseJWTSection(section) {
return JSON.parse(jwtSectionToBuffer(section).toString("utf8"));
}
const zlibInflateRaw = util.promisify(zlib.inflateRaw);
/**
* Reads a QR code and returns its data as a string.
* @param {Buffer} buffer QR code image buffer in JPG/PNG/BMP format
* @returns {Promise<string>}
*/
async function readQRCode(buffer) {
const processed_image = await jimp.read(buffer);
const qr = jsQR(processed_image.bitmap.data, processed_image.getWidth(), processed_image.getHeight());
if (!qr) {
throw new Error("QR code could not be processed");
}
return qr.data;
}
/**
* Parses a SMART Health Card's raw string representation into a signed JSON Web Token.
* @param {string} shc
* @returns {string}
*/
function parseSHCtoJWT(shc) {
const data = shc.replace(/^shc:\//, "").replace(/[^0-9]/, "");
const chars = [];
let index = 0;
while (index < data.length) {
const buf = Number(data.slice(index, index + 2)) + 45;
chars.push(String.fromCodePoint(buf));
index += 2;
}
return Buffer.from(chars.join(""), "ascii").toString("utf8");
}
/**
* Parses a JSON Web Token into its header, payload and signatures.
* @param {string} jwt
*/
async function parseJWT(jwt) {
const [raw_header, raw_payload, signature] = jwt.split(".");
const header = parseJWTSection(raw_header);
let payload = raw_payload;
if (header.zip === "DEF") {
payload = await zlibInflateRaw(jwtSectionToBuffer(raw_payload));
}
payload = JSON.parse(payload.toString("utf8"));
return { header, payload, signature };
}
async function main() {
let image_path = process.argv[2];
if (!image_path) {
throw new Error("No image path provided");
}
if (image_path.startsWith(".")) {
image_path = path.join(__dirname, image_path);
}
const raw_image = await fs.readFile(image_path);
const shc = await readQRCode(raw_image);
const jwt = parseSHCtoJWT(shc);
const result = await parseJWT(jwt);
console.log(JSON.stringify(result));
}
main()
.then(() => {
process.exit(0);
})
.catch((err) => {
console.error(err);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment