2021-12-26
新型コロナワクチン接種証明書アプリが生成する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 では、公共サービスを自分たちで開発する方、大募集中!
シビックテックを学びたい学生は、冬インターンもどうぞ!
まなぼう、つくろう、よくしよう!

Tweet
クリエイティブ・コモンズ・ライセンス
本ブログの記事や写真は「Creative Commons — CC BY 4.0」の下に提供します。記事内で紹介するプログラムや作品は、それぞれに記載されたライセンスを参照ください。
CC BY / @taisukef / アイコン画像 / プロフィール画像 / 「一日一創」画像 / RSS