こんにちは、富士榮です。
20日にデジタル庁がリリースしたワクチン接種証明アプリが話題ですね。内容的にはSMART Health Cardの仕様に沿った証明データが出てきているという話だったので中身を紐解いてみようかと思います。何しろSMART Health Cardの中身はW3CのVerifiable Credentials(VC)なので。
参考1)
https://www.digital.go.jp/policies/vaccinecert/faq_06 より
参考2)
This document describes how clinical information, modeled in FHIR, can be presented in a form based on W3C Verifiable Credentials (VC).
https://spec.smarthealth.cards/credential-modeling/ より
ちなみに今年のWWDCで発表された通りiOS15のHealthアプリはSMART Health Cardをネイティブで扱えるようになっているので、実はこの接種証明アプリがなくても直接iPhoneのWalletに接種証明を入れることもできます。今はマイナンバーカードを使った本人確認〜接種証明の発行までをワンストップで実行させるには今回のようなアプリの形になっている方が扱いやすいとは思いますが、今回対象外になった自治体の住民や新旧の姓を併記にしているような方達はセルフサービスでの発行ではなく窓口等でQR発行〜iPhoneのWalletへの読み込みができるようになると便利だと思います。
ちなみに現在も接種証明アプリで表示したQRコードを他のiPhoneで読み込むとWalletへ接種証明を入れることもできます。どこぞの記事には他人の接種証明が入れれちゃうのは問題だ、、みたいな意見も書かれていましたが、本来的には接種証明と本人確認は別の話だと思いますし、どっちにしても検証側が本人確認+接種確認をセットでやるのが筋だと思うので、なんだかなぁ、と思ってみてます。
ちなみに、海外版のQRを解くとちゃんと英字氏名も入っているのですが、Walletに入れるとofficialではなくusualのtypeになっているname属性の値(日本語)が使われてしまうのがちょっと残念ですね。海外で見せた時に読めないだろう、、、という。
ということで中身を読んでみます。
1. 国内用と海外用
先のFAQを見ると国内用と海外用があることがわかりますし、アプリを使うと国内用、海外用のどちらか、あるいは両方を発行することができます。国内用ではマイナンバーカードでの本人確認、海外用では加えてパスポートの確認が必要です。(ICAO用ですね。SMART Health Card版ではパスポート情報は不要なので)
今回はVerifiable Credentialsを採用しているSMART Health Cardを解いていこうと思うので、国内用、もしくは海外用でもSMART Health Card版(QRコードを出すところで「SHC」を選択)を使います。
2. QRコードを単純に読んでみる
アプリにQRコードを画像ファイルとしてダウンロードさせる機能があるのでダウンロードして入っている文字列を読み出してみます。
この辺りのサイトを使うとMacでもできるので便利です。
shc:/〜という形でnumericなデータが見えます。
これは仕様を読むと、JWS Compact SerializationでエンコードしたレコードをQRにエンコードする際はChunkをdecimalで表現した文字列を作ることになっているようです。具体的にはOrd(c)-45をすることでBase64UrlEncodeされたJWSの各文字を数値に変換しています。
ということで、逆に文字列に戻してあげるにはJWSchars.map(num => String.fromCharCode(parseInt(num, 10) + 45)).join('');と言った感じで+45をして文字コードにしてあげればいいってことです。
うまく繋げばいつもの「eyJ〜」が見えてきますので、decodeしてあげましょう。
3. payloadはinflateRawする
ここまで来れば単純にjwt.ioとかでdecodeすれば済むかな、と思ったらpayloadがバケバケでした。headerをよくみると"zip": "DEF"とあるのでpayloadがdeflateされてますね。
これも仕様をみるとサイズを小さくするためにraw deflateがかかっているようです。仕方がないのでzlibなどを使ってinflateRawしてあげます。これでようやくまともに読めるpayloadができるのでtoString()して文字列としてJSONを取得してあげましょう。こんな感じでとれました。
{"iss": "https://vc.vrs.digital.go.jp/issuer", "nbf": 1639958323.321147, "vc": {"type": ["https://smarthealth.cards#health-card", "https://smarthealth.cards#immunization", "https://smarthealth.cards#covid19"], "credentialSubject": {"fhirVersion": "4.0.1", "fhirBundle": {"resourceType": "Bundle", "type": "collection", "entry": [{"fullUrl": "resource:0", "resource": {"resourceType": "Patient", "name": [{"use": "usual", "family": "\u5bcc\u58eb\u69ae", "given": ["\u5c1a\u5bdb"]}, {"use": "official", "family": "NAOHIRO", "given": ["FUJIE"]}], "birthDate": "1974-09-28"}}, {"fullUrl": "resource:1", "resource": {"resourceType": "Immunization", "status": "completed", "vaccineCode": {"coding": [{"system": "http://hl7.org/fhir/sid/cvx", "code": "207"}]}, "patient": {"reference": "resource:0"}, "occurrenceDateTime": "2021-07-20", "performer": [{"actor": {"display": "MHLW_Gov_of_JAPAN"}}], "lotNumber": "3003190"}}, {"fullUrl": "resource:2", "resource": {"resourceType": "Immunization", "status": "completed", "vaccineCode": {"coding": [{"system": "http://hl7.org/fhir/sid/cvx", "code": "207"}]}, "patient": {"reference": "resource:0"}, "occurrenceDateTime": "2021-08-17", "performer": [{"actor": {"display": "MHLW_Gov_of_JAPAN"}}], "lotNumber": "3004734"}}]}}}}
あとは整形してあげればOKなので、じっくり中身を見れるようになります。ちなみにコードを書けばいいんでしょうけど面倒なのでこの辺りのツールを使ってみやすくしてます。
4. じっくり覗き込む
とりあえず整形したpayloadがこれです。
{ "iss": "https://vc.vrs.digital.go.jp/issuer", "nbf": 1639958323.321147, "vc": { "type": [ "https://smarthealth.cards#health-card", "https://smarthealth.cards#immunization", "https://smarthealth.cards#covid19" ], "credentialSubject": { "fhirVersion": "4.0.1", "fhirBundle": { "resourceType": "Bundle", "type": "collection", "entry": [ { "fullUrl": "resource:0", "resource": { "resourceType": "Patient", "name": [ { "use": "usual", "family": "富士榮", "given": [ "尚寛" ] }, { "use": "official", "family": "NAOHIRO", "given": [ "FUJIE" ] } ], "birthDate": "1974-09-28" } }, { "fullUrl": "resource:1", "resource": { "resourceType": "Immunization", "status": "completed", "vaccineCode": { "coding": [ { "system": "http://hl7.org/fhir/sid/cvx", "code": "207" } ] }, "patient": { "reference": "resource:0" }, "occurrenceDateTime": "2021-07-20", "performer": [ { "actor": { "display": "MHLW_Gov_of_JAPAN" } } ], "lotNumber": "3003190" } }, { "fullUrl": "resource:2", "resource": { "resourceType": "Immunization", "status": "completed", "vaccineCode": { "coding": [ { "system": "http://hl7.org/fhir/sid/cvx", "code": "207" } ] }, "patient": { "reference": "resource:0" }, "occurrenceDateTime": "2021-08-17", "performer": [ { "actor": { "display": "MHLW_Gov_of_JAPAN" } } ], "lotNumber": "3004734" } } ] } } } }
5. 見えてくること
なるほど、ということで読んでいくと以下のことがわかります。
- Verifiable Credentialsですね
- CredentialTypeは3種類
- https://smarthealth.cards#health-card"
- "https://smarthealth.cards#immunization"
- "https://smarthealth.cards#covid19"
- CredentialSubjectにはFHIRの仕様でデータが入っている
- resource:0がpatient情報、つまり接種を受けた人の情報(と言っても名前と生年月日)
- resource:1/2がImmunization情報、つまり接種情報の1回目と2回目
- vaccineCodeがワクチンの種類
- https://build.fhir.org/valueset-vaccine-code.htmlをみると、207は「SARS-COV-2 (COVID-19) vaccine, mRNA, spike protein, LNP, preservative free, 100 mcg/0.5mL dose」とあるのでCOVID-19のワクチンっぽい。
- しかしFHIRのバージョンが4.0.1とCredentialSubjectには書いてあるけど、4.0.1のvaccineCodeにはCOVID-19の定義がないのは間に合ってないってことなのかな?上記のコードは4.6.0のもの。4.0.1のvaccineCodeはこちら。
- performerは「MHLW_Gov_of_JAPAN」とあるので日本の厚生労働省
- lotNumberがワクチンのロットですね。ちなみに鉄粉が入ってたっていう件、私はビンゴでした・・・
と、ざっくりですがQRコードからJWSを復元、decodeしてみました、という話でした。
他にもJWSの署名を深堀をしたり、と興味は尽きない接種証明アプリですが、総じてこの短期間で使いやすいアプリが出てきたっていうのは素晴らしいことだな、と思っています。(個人の感想)
0 件のコメント:
コメントを投稿