新型コロナワクチン接種証明書アプリが生成するQRコードをかざすだけで瞬時にデコード・検証し、表示するwebアプリ「
高速ワクチン接種証明書チェッカー / vcchecker」を作りました。
「
高速ワクチン接種証明書チェッカー - vcchecker」
webアプリなので、スマホ、PCですぐ使えます。一度起動すれば通信は行いません。検証できないQRコードは表示しません。
認識したワクチン接種証明書のQRコードに重ねて、氏名、生年月日、接種日情報を瞬時にAR表示します。
免許証など本人確認データと合わせて準備してもらえば、大人数対応もスムーズにいけるかも?
現在、読み取ったデータは保存していません。
ご要望など Issues に記述ください。
以下、技術情報。
Node.jsを使っていた先日のデコード・検証コードをDenoとブラウザ上で動くように作り直しました。
デコード部分は、以前作ったzlib.jsをベースにrawinflateと、Base64の亜種、Base64URLを準備したら後は簡単。ヘッダーと中身(payload)と電子署名に分解します。
const decode = (code) => {
if (!code.startsWith("shc:/")) {
throw new Error("is not shc");
}
const list = [];
for (let i = 5; i < code.length; i += 2) {
const c = String.fromCharCode(parseInt(code.substring(i, i + 2)) + 45);
list.push(c);
}
const sss = list.join("").split(".");
const [header0, payload0, signature] = sss.map(d => Base64URL.decode(d));
const payload = JSON.parse(new TextDecoder().decode(rawinflate(payload0)));
const header = JSON.parse(new TextDecoder().decode(header0));
const data = new TextEncoder().encode(sss[0] + "." + sss[1]);
return { header, payload, signature, data };
};
電子署名の検証は、payloadのissで指定されている公開鍵ファイルと、Web Crypto APIを使って検証します。
const fetchJWKS = async (iss) => {
const jwksUrl = iss + "/.well-known/jwks.json";
const res = await fetch(jwksUrl);
const jwks = await res.json();
return jwks;
};
const getJWK = async (iss, jwks) => {
const keys = jwks || await fetchJWKS(iss);
const key = keys.keys[0]
//console.log(key);
key.use = undefined; // cause: DataError: 'use' property of JsonWebKey must be 'undefined'
const pubkey = await crypto.subtle.importKey("jwk", key, {
name: "ECDSA", namedCurve: "P-256"
}, true, ["verify"]);
//console.log(pubkey);
return pubkey;
};
const verify = async (dec, jwks) => {
const iss = dec.payload.iss;
//console.log(iss);
const pubkey = await getJWK(iss, jwks);
const algorithm = { name: "ECDSA", hash: { name: "SHA-256"} };
const result = await crypto.subtle.verify(algorithm, pubkey, dec.signature, dec.data);
return result;
};
中身(payload)を人に見やすく変換する toStringEntry を準備
const toStringEntry = (dec) => {
const entries = dec.payload.vc.credentialSubject.fhirBundle.entry;
const data = [];
for (const entry of entries) {
const r = entry.resource;
switch (r.resourceType) {
case "Patient":
data.push(r.name[0].family + " " + r.name[0].given + " (" + r.birthDate + ")");
break;
case "Immunization":
if (r.status == "completed") {
data.push("ワクチン接種日 " + r.occurrenceDateTime);
}
break;
}
}
return data.join("\n");
};
あとは、これらの関数をひとまとめにしてESモジュール「SMARTHealthCards-es」として公開してできあがり!
const SMARTHealthCards = {
decode,
verify, // async
toStringEntry, // ja
};
export { SMARTHealthCards };
QRコードリーダー「jsQR-es」を使ったAR表示アプリ「vcchecker」のメインはこのようにとってもシンプル。使いやすいようにいろいろと改造していきましょう!
const shc = code.data;
const dec = SMARTHealthCards.decode(shc);
const data = SMARTHealthCards.toStringEntry(dec);
if (data2) {
fillPolygon(pnts);
drawStrings(data2, x, y);
}
const res = await SMARTHealthCards.verify(dec, jwks);
if (res) {
outputMessage.hidden = true;
outputData.parentElement.hidden = false;
data2 = data + "\n[デジタル庁の電子署名を検証済み]";
outputData.innerText = data2;
x = (code.location.topLeftCorner.x + code.location.topRightCorner.x) / 2;
y = (code.location.topLeftCorner.y + code.location.topRightCorner.y) / 2 + 100;
drawStrings(data2, x, y);
}
現在、デジタル庁の公開鍵はCORS設定されておらず、webアプリから取得ができなかったので、ダウンロードして使用しています(結果、デジタル庁署名以外のデータは検証できないので非表示です。
CORS設定をしていただくと、VCI Directoryへのプルリクも通って、良さそうです。
(参考、ワクチンパスポートのトラスト基盤:欧州、米国、日本)
Code for FUKUI では、公共サービスを自分たちで開発する方、大募集中!
シビックテックを学びたい学生は、冬インターンもどうぞ!
まなぼう、つくろう、よくしよう!