LoginSignup
9
6
記事投稿キャンペーン 「2024年!初アウトプットをしよう」

eIDAS 2.0でサポート必須のSD-JWT VCフォーマットとmdoc/mDLフォーマットのVerifiable Credentialを同時に発行してみた (たぶん世界初)

Posted at

はじめに

デジタルアイデンティティ業界は Verifiable Credential (検証可能な資格証明) の話題で持ちきりです。特にヨーロッパでは、Verifiable Credential を管理するための EU Digital Identity Wallet (EUDIW) の整備が eIDAS 2.0 という枠組み (法規制と技術仕様のセット) により要求されるため、技術仕様策定と実装が急ピッチで進んでいます。

そんな中、先日 (2024 年 1 月 19 日) 開催された『OpenID Summit Tokyo 2024』では、ドイツ政府機関 SPRIND から Torsten Lodderstedt 氏、イタリア研究機関 FBK から Amir Sharif 氏、OpenWallet Foundation から Joseph Heenan 氏が来日し、デジタルアイデンティティウォレットに関連する講演を行いました。

同イベントでは私も登壇し、eIDAS 2.0 でサポート必須の Verifiable Credential フォーマットとして指定されている SD-JWT VC フォーマットと mdoc/mDL フォーマットの Verifiable Credential を二つ同時に発行するデモを行いました。

openid_summit_tokyo_2024_taka_session.png

本記事では私の発表内容を振り返りたいと思います。

VC/VP 関連仕様

Verifiable Credential / Verifiable Presentation 関連仕様は大きく二つのカテゴリに分類されます。一つは VC/VP のフォーマットに関する仕様群、もう一つは VC/VP の伝送に関する仕様群です。

vc_vp_specifications.png

関連仕様は他にもあるものの、フォーマットに関する仕様として次の三つを紹介しました。

W3C VCDM は Issuer-Wallet-Verifier モデルの定義の一次情報源とされています。

third-party-model.png

SD-JWT VC は、選択的開示 (Selective Disclosure) を実現する SD-JWT というフォーマットに基づく Verifiable Credential フォーマットです。

mdoc は、ISO/IEC 18013-5:2021 Personal identification, ISO-compliant driving licence, Part 5: Mobile driving licence (mDL) application という ISO 文書で定義されているデータ構造で、CBOR (RFC 8949) というバイナリフォーマットに基づいています。mDL は mdoc の一種で、同 ISO 文書内で定義されており、モバイル運転免許証を表しています。mDL には、氏名などの情報に加え、driving_privileges といった運転免許に特有の情報も含まれています。

VC/VP の伝送に関しては次の三つが中心となる仕様となっています。

  • OpenID for Verifiable Credential Issuance (OID4VCI)
  • OpenID for Verifiable Presentations (OID4VP)
  • Self-Issued OpenID Provider version 2 (SIOPv2)

OID4VCI は Verifiable Credential の発行手順を定義する技術仕様です。私のデモでは、この仕様の最新のワーキンググループドラフトに基づいて SD-JWT VC と mdoc/mDL の発行を行いました。

OID4VCI 仕様は破壊的変更の頻度が高いため、openid.net 上で公開されているバージョンは実装者が参照する文書としては適切ではありません。実装者は、openid/OpenID4VCI レポジトリの main ブランチの更新に自動的に追随するワーキンググループドラフトを参照したほうがよいでしょう。

SD-JWT

選択的開示

SD-JWT VC がベースとしている SD-JWT について簡単に紹介しました。(過去記事: SD-JWT (選択開示のためのデータフォーマット))

Verifiable Credential を発行するクレデンシャルイシュアは、Verifiable Credential に埋め込む情報を用意します。この例では、氏名、生年月日、住所、を用意しています。

selective_disclosure_01.png

それから、非対称鍵アルゴリズムの鍵ペアを用意し、

selective_disclosure_02.png

用意した情報を入力とし、秘密鍵を用いて署名を作成します。

selective_disclosure_03.png

クレデンシャルイシュアは、それらの情報と署名を含む Verifiable Credential を生成します。

selective_disclosure_04.png

クレデンシャルイシュアは、生成した Verifiable Credential をウォレットに発行します。

selective_disclosure_05.png

ウォレットは、開示する情報を取捨選択し (選択的開示)、Verifiable Presentation を作成します。この例では、氏名と生年月日を開示し、住所は非開示としています。

selective_disclosure_06.png

ウォレットは、生成した Verifiable Presentation をベリファイアに提示します。

selective_disclosure_07.png

ベリファイアは何らかの方法で、クレデンシャルイシュアが署名に使用した秘密鍵に対応する公開鍵を取得します。

selective_disclosure_08.png

ベリファイアは、取得した公開鍵を用いて、VP に含まれる署名を検証します。

selective_disclosure_09.png

しかし、この署名検証は失敗します。

selective_disclosure_10.png

なぜなら、署名は「氏名、生年月日、住所」を入力として生成されたにもかかわらず、ベリファイアは氏名と生年月日しか受け取っておらず、「氏名、生年月日」を入力として署名を再計算しても、受け取った署名と再計算した署名の値が一致しないからです。

署名を有効なままに保ちつつ選択的開示を実現するには工夫が必要です。BBS+ (Boneh-Lynn-Shacham signature plus) や CL Signatures (Camenisch-Lysyanskaya Signatures) などは選択的開示を実現するための既存の手法ではありますが、幾つかの理由により、新たに SD-JWT というフォーマットが開発されることになりました。

SD-JWT フォーマット

通常の JWT ではペイロード部にクレーム名とクレーム値がそのまま埋め込まれます。

sd-jwt_01.png

SD-JWT を生成するには、まず、クレーム名とクレーム値の組を取り出します。

sd-jwt_02.png

そこに、十分なエントロピーを持つ任意のソルトを添え、

sd-jwt_03.png

JSON 配列を作成します。

sd-jwt_04.png

この JSON 配列を base64url でエンコードします。このようにして生成した
文字列を、SD-JWT 仕様ではディスクロージャ (Disclosure) と呼んでいます。

sd-jwt_05.png

そして、元々クレームが置かれていた場所に _sd という名前の配列を作り、

sd-jwt_06.png

その配列にディスクロージャのダイジェスト値を追加します。

sd-jwt_07.png

同じ処理を、選択的開示を可能にしたいクレーム群に対して繰り返します。

sd-jwt_08.png

上記のような手順で用意したペイロードを持つ JWT を作成します。仕様はこの JWT を Issuer-signed JWT と呼びます。

sd-jwt_09.png

そして、ディスクロージャ群を後ろに並べ、

sd-jwt_10.png

チルダ (~) で連結します。

sd-jwt_11.png

こうして出来上がった文字列が SD-JWT です。

sd-jwt_12.png

詳細説明は省きますが、キーバインディング (Key Binding) という仕組みのため、公開鍵を Issuer-signed JWT のペイロード部に埋め込み、SD-JWT の末尾に Key Binding JWT を追加する場合もあります。

sd-jwt_13.png

SD-JWT 生成手順の全体像は次のようになります。

sd-jwt.png


さて、全てのディスクロージャを含む SD-JWT を受け取ったとしましょう。この場合、ディスクロージャ群を base64url でデコードすることにより、全てのクレーム名とクレーム値の組を得ることができます。

sd-jwt-vp_1.png

一方で、ディスクロージャ群の一部しか含まれていない場合、含まれているディスクロージャに対応するクレーム名とクレーム値の組しか得ることができません。

sd-jwt-vp_2.png

ここで何が嬉しいのかというと、ディスクロージャ群が一部しか含まれていなくても、Issuer-signed JWT の署名は有効のまま、という点です。つまり、Issuer-signed JWT のヘッダ部と _sd 配列を含むペイロード部を入力として署名を再計算すると、Issuer-signed JWT についている署名と一致するのです。

OID4VCI

OID4VCI は Verifiable Credential の発行手順を定義する技術仕様です。

コンセプト

コンセプト自体は比較的単純です。まず、認可サーバがウォレットにアクセストークンを発行します。

oid4vci_concept_1.png

ウォレットはそのアクセストークンをクレデンシャルイシュアに提示します。

oid4vci_concept_2.png

クレデンシャルイシュアはウォレットに Verifiable Credential を発行します。

oid4vci_concept_3.png

アクセストークン発行

コンセプトは単純なのですが、Verifiable Credential 発行に使えるアクセストークンを発行するための手順が複数定義されています。

まず、クレデンシャルオファーというものがクレデンシャルイシュアから発行済みで、それが事前認可コード (pre-authorized code) を含んでいる場合、

access_token_issuance_1.ja.png

ウォレットはその事前認可コードをトークンエンドポイントに提示することでアクセストークンを取得することができます。

access_token_issuance_2.ja.png

一方、クレデンシャルオファーにイシュアステート (issuer state) が含まれている場合、

access_token_issuance_3.ja.png

そのイシュアステートを含む認可リクエストを認可エンドポイントに投げて認可コードを受け取り、

access_token_issuance_4.ja.png

あとはいつもの認可コードフローでトークンエンドポイントからアクセストークンを受け取ります。

access_token_issuance_5.ja.png

クレデンシャルオファーを使わず、RFC 9396 OAuth 2.0 Rich Authorization Requests で定義されている RAR オブジェクトを使う方法も定義されています。

access_token_issuance_6.ja.png

クレデンシャルイシュアの設定によっては、scope パラメーターの値として使えるショートカットが提供されていることがあり、その場合、scope パラメーターを使うこともできます。

access_token_issuance_7.ja.png

そういうわけで、OID4VCI 仕様ではアクセストークンを取得する方法が 4 つ定義されています。

access_token_issuance.ja.png

Verifiable Credential 発行

アクセストークンをクレデンシャルイシュアのクレデンシャルエンドポイント (credential endpoint) に提示することで、ウォレットは Verifiable Credential を取得することができます。

verifiable_credential_issuance_1.ja.png

しかし、クレデンシャルエンドポイントは、アクセストークンを提示されたときに、すぐに Verifiable Credential を生成できないかもしれません。その場合、クレデンシャルエンドポイントは Verifiable Credential のかわりにトランザクション ID (transaction ID) を発行します。

verifiable_credential_issuance_2.ja.png

トランザクション ID を受け取ったウォレットは、後ほど、クレデンシャルイシュアの遅延クレデンシャルエンドポイント (deferred credential endpoint) にアクセストークンと共にトランザクション ID を提示します。このとき、Verifiable Credential が用意できていれば、Verifiable Credential が得られます。まだ用意できていなければ、issuance_pending エラーが返されます。

verifiable_credential_issuance_3.ja.png

一度に複数の Verifiable Credential を取得したい場合は、クレデンシャルエンドポイントのかわりに一括クレデンシャルエンドポイント (batch credential endpoint) を利用します。

verifiable_credential_issuance_4.ja.png

クレデンシャルエンドポイントと同様、一括クレデンシャルエンドポイントでも Verifiable Credential をすぐに生成できないことがありえます。この場合、すぐに発行できない Verifiable Credential ごとに一つのトランザクション ID が返されます。

verifiable_credential_issuance_5.ja.png

先ほどと同様に、発行されたトランザクション ID を遅延クレデンシャルエンドポイントに提示することで、対応する Verifiable Credential を取得することができます。

verifiable_credential_issuance_6.ja.png

これまでに紹介した Verifiable Credential 発行フローを一つの図にまとめると次のようになります。

verifiable_credential_issuance.png

デモ

デモでは、事前認可コードを用いる事前認可コードフロー (pre-authorized code flow) でアクセストークンを取得し、そのアクセストークンを一括クレデンシャルエンドポイントに提示し、SD-JWT VC フォーマットと mdoc/mDL フォーマットの Verifiable Credential を生成しました。

demo_flow.png

手順が単純なのでデモでは事前認可コードフローを用いましたが、セキュリティ上の懸念からイタリアでは事前認可コードフローをサポートしません。

事前準備

デモ用に幾つかシェル変数を設定しておきます。

CLIENT_ID=218232426
TOKEN_ENDPOINT=https://trial.authlete.net/api/token
BATCH_CREDENTIAL_ENDPOINT=https://trial.authlete.net/api/batch_credential

変数値の中に現れる trial.authlete.net はデモ用の認可サーバ兼クレデンシャルイシュアで、その実装は authlete/java-oauth-server です。

クレデンシャルオファー取得

デモ用のクレデンシャルオファーを生成する https://trial.authlete.net/api/offer/issue にアクセスし、事前認可コードを含むクレデンシャルオファーを取得します。詳細については『4.1.2. 事前認可コード』を参照してください。

取得した事前認可コードをシェル変数に設定します。

PRE_AUTHORIZED_CODE=${取得した事前認可コード}

アクセストークン取得

事前認可コードフローでアクセストークンを取得します。

トークンリクエスト
curl -s $TOKEN_ENDPOINT \
     -d client_id=$CLIENT_ID \
     -d grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code \
     -d pre-authorized_code=$PRE_AUTHORIZED_CODE

取得したアクセストークンをシェル変数に設定します。

ACCESS_TOKEN=${取得したアクセストークン}

Verifiable Credential 取得

一括クレデンシャルエンドポイントに一括クレデンシャルリクエストを投げます。

一括クレデンシャルリクエスト
curl -s $BATCH_CREDENTIAL_ENDPOINT \
     -H "Authorization: Bearer $ACCESS_TOKEN" \
     -H "Content-Type: application/json" \
     --data '{
  "credential_requests": [
    {
      "format": "vc+sd-jwt",
      "vct": "https://credentials.example.com/identity_credential"
    },
    {
      "format": "mso_mdoc",
      "doctype": "org.iso.18013.5.1.mDL",
      "claims": {
        "org.iso.18013.5.1": {
          "family_name": {}, "given_name": {}, "birth_date": {},
          "issue_date": {}, "expiry_date": {}, "issuing_country": {},
          "document_number": {}, "driving_privileges": {}
        }
      }
    }
  ]
}'

リクエストのメッセージボディは JSON です。その JSON には credential_requests という配列があり、その配列の要素は JSON オブジェクトです。それぞれの JSON オブジェクトは、取得したい Verifiable Credential を指定する情報を含んでいます。この例では、一つ目の JSON オブジェクトで SD-JWT VC を、二つ目の JSON オブジェクトで mdoc を指定しています。

エンドポイントからは次のような JSON が返されます。

一括クレデンシャルレスポンス
{
  "credential_responses": [
    {
      "format": "vc+sd-jwt",
      "credential": "eyJraWQiOiJKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrIiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsIl9zZCI6WyIwczNiazZYcC02ZW1rV3cxTFd2OWthaGk5YjNUV0c2NDJLV3dpZEZKeHlvIiwiM3BSUGNUUjItSkJySjhVRjVkZGhtcDhwbDA3MEpheWpoMWEzZVVWRDZNZyIsIjZ6UEtKS2pzc2Y0Q1JNNmhUeDZZVUdQdzRBbm1ZWHZocnFuZDlmdTZMcUkiLCJBVnFKdDdkcWNEVWZLNmJPSEg0UTFEODVfMVNmLXRwM0d0QlM1Mk1Bb3FVIiwiQldydzdVV2YzdjF4cjdOOXFoSFpXMHMwa0FERDVGbFgtUmNQV2dCZEFJOCIsIkhtcHVfdVo4dWo4b2ViMXIyaGg2YmdUY3dNbEJoVHNrUjIxR2tZZVd4TW8iLCJNQ1JpVkRYc3I3MTJ1WW9NRUtWeEJfMmFxX0oweENfa08yNTdwQ3N0RlB3IiwiTUc3WElRV1Y5RFE2dC12WDdmZERUblZ6bnpTZUwwY2gtX0NtNkkyM3ZDWSIsIlB5VEVrajdFdUhScGljdFk5Z1ZpQTVhcTBrYTd2SzJZdDRrX04wbzlTb3ciXSwiaWF0IjoxNzA1MDE4MjA1LCJ2Y3QiOiJodHRwczovL2NyZWRlbnRpYWxzLmV4YW1wbGUuY29tL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2RfYWxnIjoic2hhLTI1NiJ9.ll4JdW-ksNDyVGx-OTueQYojpUYXhUZ6J31fFKGall2SsT5LQt-I5w24AiYhDvxYWRGRCmJF5UI-_3SpNE83wQ~WyJJZEJuZ2xIcF9URGRyeUwycGJLZVNBIiwic3ViIiwiMTAwNCJd~WyJDd0lYNU11clBMZ1VFRnA2U2JhM0dnIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ~WyJveUYtR3Q5LXVwa1FkU0ZMX0pTekNnIiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd~WyJMZG9oSjQ5d2gwcTBubWNJUG92SVhnIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd~"
    },
    {
      "format": "mso_mdoc",
      "credential": "omdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xiNgYWFukaGRpZ2VzdElEAWZyYW5kb21QcbnmTIHt0_17t-AcHkKZbHFlbGVtZW50SWRlbnRpZmllcmppc3N1ZV9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoyMDI0LTAxLTEy2BhYXKRoZGlnZXN0SUQCZnJhbmRvbVBRwvzBVJYBc2plhd7vXZwTcWVsZW1lbnRJZGVudGlmaWVya2V4cGlyeV9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoyMDI1LTAxLTEy2BhYWqRoZGlnZXN0SUQDZnJhbmRvbVDcuBh2xE6SqxDDECOY9H3CcWVsZW1lbnRJZGVudGlmaWVya2ZhbWlseV9uYW1lbGVsZW1lbnRWYWx1ZWtTaWx2ZXJzdG9uZdgYWFKkaGRpZ2VzdElEBGZyYW5kb21QHu5Fe96gJQH-NeOAvSuJdHFlbGVtZW50SWRlbnRpZmllcmpnaXZlbl9uYW1lbGVsZW1lbnRWYWx1ZWRJbmdh2BhYW6RoZGlnZXN0SUQFZnJhbmRvbVDI-4b03R-29ljFhUoZMHP0cWVsZW1lbnRJZGVudGlmaWVyamJpcnRoX2RhdGVsZWxlbWVudFZhbHVl2QPsajE5OTEtMTEtMDbYGFhVpGhkaWdlc3RJRAZmcmFuZG9tUCJlXpl0UAxhiiN9BwSnLeBxZWxlbWVudElkZW50aWZpZXJvaXNzdWluZ19jb3VudHJ5bGVsZW1lbnRWYWx1ZWJVU9gYWFukaGRpZ2VzdElEB2ZyYW5kb21QbWz_ggUxytSax7_FqCzoEHFlbGVtZW50SWRlbnRpZmllcm9kb2N1bWVudF9udW1iZXJsZWxlbWVudFZhbHVlaDEyMzQ1Njc42BhYoqRoZGlnZXN0SUQIZnJhbmRvbVBbSwOg91lMspu_ctBa2uqgcWVsZW1lbnRJZGVudGlmaWVycmRyaXZpbmdfcHJpdmlsZWdlc2xlbGVtZW50VmFsdWWBo3V2ZWhpY2xlX2NhdGVnb3J5X2NvZGVhQWppc3N1ZV9kYXRl2QPsajIwMjMtMDEtMDFrZXhwaXJ5X2RhdGXZA-xqMjA0My0wMS0wMWppc3N1ZXJBdXRohEOhASahGCFZAWEwggFdMIIBBKADAgECAgYBjJHZwhkwCgYIKoZIzj0EAwIwNjE0MDIGA1UEAwwrSjFGd0pQODdDNi1RTl9XU0lPbUpBUWM2bjVDUV9iWmRhRko1R0RuVzFSazAeFw0yMzEyMjIxNDA2NTZaFw0yNDEwMTcxNDA2NTZaMDYxNDAyBgNVBAMMK0oxRndKUDg3QzYtUU5fV1NJT21KQVFjNm41Q1FfYlpkYUZKNUdEblcxUmswWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQCilV5ugmlhHJzDVgqSRE5d8KkoQqX1jVg8WE4aPjFODZQ66fFPFIhWRP3ioVUi67WGQSgTY3F6Vmjf7JMVQ4MMAoGCCqGSM49BAMCA0cAMEQCIGcWNJwFy8RGV4uMwK7k1vEkqQ2xr-BCGRdN8OZur5PeAiBVrNuxV1C9mCW5z2clhDFaXNdP2Lp_7CBQrHQoJhuPcNgYWQHopWd2ZXJzaW9uYzEuMG9kaWdlc3RBbGdvcml0aG1nU0hBLTI1Nmx2YWx1ZURpZ2VzdHOhcW9yZy5pc28uMTgwMTMuNS4xqAFYIKuS8FCeCcvDMwZgEezuuVv-DYsUpdypJp9abJrqHAmXAlggu7D-3vr-NrLg3zigunUzEKFqYAyG5sA-ffvmDjRxZ24DWCC2OBnhoZFhqE7s8PRfdej8t5frp-HgF_2X4qMtzvEY6ARYIBF_rl93VR21umkIdSMiWqFmT5Jxs0n3H5SWonWrJoDrBVggKDvVyMU358Le0n6TkVb2c0BbhbSMJwpswtPLNiZrTR8GWCAFZzJwAmnC7QcMQwq72FDQlmPxk0434cZbh6_rt1VagQdYIHwBHQ3-sVPtco-RcUhuYYq6iivujjYyJmQBbQ_OdhFDCFggcjT2HYgkoxnwWP-9jqO_6-D-d69H9UW2xjpDWrknlvBnZG9jVHlwZXVvcmcuaXNvLjE4MDEzLjUuMS5tRExsdmFsaWRpdHlJbmZvo2ZzaWduZWTAdDIwMjQtMDEtMTJUMDA6MTA6MDVaaXZhbGlkRnJvbcB0MjAyNC0wMS0xMlQwMDoxMDowNVpqdmFsaWRVbnRpbMB0MjAyNS0wMS0xMlQwMDoxMDowNVpYQHFzEb09NFyFlj533FE_1B9I2rku90K52ar64Id1CyOUXWXzhINeVfoJU1cfxgCT2CX1369cGd_TQxSjhVx8bpY"
    }
  ],
  "c_nonce": "BvPJWhd3jvBCeooQTzSBNk0TCU4BHq150odu_vpAvSY",
  "c_nonce_expires_in": 86052
}

レスポンスには credential_responses という配列が含まれており、配列内の要素は、リクエストの credential_requests 配列の各要素に対応しています。

各要素は JSON オブジェクトで、そのオブジェクトに credential プロパティが含まれていれば、その値が発行された Verifiable Credential です。

SD-JWT VC

この例では、一番目の JSON オブジェクト内の credential プロパティの値は SD-JWT VC フォーマットの Verifiable Credential です。

SD-JWT VC
eyJraWQiOiJKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrIiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsIl9zZCI6WyIwczNiazZYcC02ZW1rV3cxTFd2OWthaGk5YjNUV0c2NDJLV3dpZEZKeHlvIiwiM3BSUGNUUjItSkJySjhVRjVkZGhtcDhwbDA3MEpheWpoMWEzZVVWRDZNZyIsIjZ6UEtKS2pzc2Y0Q1JNNmhUeDZZVUdQdzRBbm1ZWHZocnFuZDlmdTZMcUkiLCJBVnFKdDdkcWNEVWZLNmJPSEg0UTFEODVfMVNmLXRwM0d0QlM1Mk1Bb3FVIiwiQldydzdVV2YzdjF4cjdOOXFoSFpXMHMwa0FERDVGbFgtUmNQV2dCZEFJOCIsIkhtcHVfdVo4dWo4b2ViMXIyaGg2YmdUY3dNbEJoVHNrUjIxR2tZZVd4TW8iLCJNQ1JpVkRYc3I3MTJ1WW9NRUtWeEJfMmFxX0oweENfa08yNTdwQ3N0RlB3IiwiTUc3WElRV1Y5RFE2dC12WDdmZERUblZ6bnpTZUwwY2gtX0NtNkkyM3ZDWSIsIlB5VEVrajdFdUhScGljdFk5Z1ZpQTVhcTBrYTd2SzJZdDRrX04wbzlTb3ciXSwiaWF0IjoxNzA1MDE4MjA1LCJ2Y3QiOiJodHRwczovL2NyZWRlbnRpYWxzLmV4YW1wbGUuY29tL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2RfYWxnIjoic2hhLTI1NiJ9.ll4JdW-ksNDyVGx-OTueQYojpUYXhUZ6J31fFKGall2SsT5LQt-I5w24AiYhDvxYWRGRCmJF5UI-_3SpNE83wQ~WyJJZEJuZ2xIcF9URGRyeUwycGJLZVNBIiwic3ViIiwiMTAwNCJd~WyJDd0lYNU11clBMZ1VFRnA2U2JhM0dnIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ~WyJveUYtR3Q5LXVwa1FkU0ZMX0pTekNnIiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd~WyJMZG9oSjQ5d2gwcTBubWNJUG92SVhnIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd~

SD-JWT VC は、各所 base64url でエンコードされているだけなので、デコードはさほど難しい処理ではありません。authlete/oid4vci-demo レポジトリ内にある decode-sd-jwt スクリプトを使うと、次のようなデコード結果が得られます。

{
  "kid": "J1FwJP87C6-QN_WSIOmJAQc6n5CQ_bZdaFJ5GDnW1Rk",
  "typ": "vc+sd-jwt",
  "alg": "ES256"
}
{
  "iss": "https://trial.authlete.net",
  "_sd": [
    "0s3bk6Xp-6emkWw1LWv9kahi9b3TWG642KWwidFJxyo",
    "3pRPcTR2-JBrJ8UF5ddhmp8pl070Jayjh1a3eUVD6Mg",
    "6zPKJKjssf4CRM6hTx6YUGPw4AnmYXvhrqnd9fu6LqI",
    "AVqJt7dqcDUfK6bOHH4Q1D85_1Sf-tp3GtBS52MAoqU",
    "BWrw7UWf3v1xr7N9qhHZW0s0kADD5FlX-RcPWgBdAI8",
    "Hmpu_uZ8uj8oeb1r2hh6bgTcwMlBhTskR21GkYeWxMo",
    "MCRiVDXsr712uYoMEKVxB_2aq_J0xC_kO257pCstFPw",
    "MG7XIQWV9DQ6t-vX7fdDTnVznzSeL0ch-_Cm6I23vCY",
    "PyTEkj7EuHRpictY9gViA5aq0ka7vK2Yt4k_N0o9Sow"
  ],
  "iat": 1705018205,
  "vct": "https://credentials.example.com/identity_credential",
  "_sd_alg": "sha-256"
}
{
  "digest": "6zPKJKjssf4CRM6hTx6YUGPw4AnmYXvhrqnd9fu6LqI",
  "WyJJZEJuZ2xIcF9URGRyeUwycGJLZVNBIiwic3ViIiwiMTAwNCJd": [
    "IdBnglHp_TDdryL2pbKeSA",
    "sub",
    "1004"
  ]
}
{
  "digest": "MG7XIQWV9DQ6t-vX7fdDTnVznzSeL0ch-_Cm6I23vCY",
  "WyJDd0lYNU11clBMZ1VFRnA2U2JhM0dnIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ": [
    "CwIX5MurPLgUEFp6Sba3Gg",
    "given_name",
    "Inga"
  ]
}
{
  "digest": "3pRPcTR2-JBrJ8UF5ddhmp8pl070Jayjh1a3eUVD6Mg",
  "WyJveUYtR3Q5LXVwa1FkU0ZMX0pTekNnIiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd": [
    "oyF-Gt9-upkQdSFL_JSzCg",
    "family_name",
    "Silverstone"
  ]
}
{
  "digest": "Hmpu_uZ8uj8oeb1r2hh6bgTcwMlBhTskR21GkYeWxMo",
  "WyJMZG9oSjQ5d2gwcTBubWNJUG92SVhnIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd": [
    "LdohJ49wh0q0nmcIPovIXg",
    "birthdate",
    "1991-11-06"
  ]
}

図解すると次のようになります。

sd-jwt_vc.png

mdoc/mDL

二番目の JSON オブジェクト内の credential プロパティの値は mdoc/mDL フォーマットの Verifiable Credential です。

mdoc/mDL
omdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xiNgYWFukaGRpZ2VzdElEAWZyYW5kb21QcbnmTIHt0_17t-AcHkKZbHFlbGVtZW50SWRlbnRpZmllcmppc3N1ZV9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoyMDI0LTAxLTEy2BhYXKRoZGlnZXN0SUQCZnJhbmRvbVBRwvzBVJYBc2plhd7vXZwTcWVsZW1lbnRJZGVudGlmaWVya2V4cGlyeV9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoyMDI1LTAxLTEy2BhYWqRoZGlnZXN0SUQDZnJhbmRvbVDcuBh2xE6SqxDDECOY9H3CcWVsZW1lbnRJZGVudGlmaWVya2ZhbWlseV9uYW1lbGVsZW1lbnRWYWx1ZWtTaWx2ZXJzdG9uZdgYWFKkaGRpZ2VzdElEBGZyYW5kb21QHu5Fe96gJQH-NeOAvSuJdHFlbGVtZW50SWRlbnRpZmllcmpnaXZlbl9uYW1lbGVsZW1lbnRWYWx1ZWRJbmdh2BhYW6RoZGlnZXN0SUQFZnJhbmRvbVDI-4b03R-29ljFhUoZMHP0cWVsZW1lbnRJZGVudGlmaWVyamJpcnRoX2RhdGVsZWxlbWVudFZhbHVl2QPsajE5OTEtMTEtMDbYGFhVpGhkaWdlc3RJRAZmcmFuZG9tUCJlXpl0UAxhiiN9BwSnLeBxZWxlbWVudElkZW50aWZpZXJvaXNzdWluZ19jb3VudHJ5bGVsZW1lbnRWYWx1ZWJVU9gYWFukaGRpZ2VzdElEB2ZyYW5kb21QbWz_ggUxytSax7_FqCzoEHFlbGVtZW50SWRlbnRpZmllcm9kb2N1bWVudF9udW1iZXJsZWxlbWVudFZhbHVlaDEyMzQ1Njc42BhYoqRoZGlnZXN0SUQIZnJhbmRvbVBbSwOg91lMspu_ctBa2uqgcWVsZW1lbnRJZGVudGlmaWVycmRyaXZpbmdfcHJpdmlsZWdlc2xlbGVtZW50VmFsdWWBo3V2ZWhpY2xlX2NhdGVnb3J5X2NvZGVhQWppc3N1ZV9kYXRl2QPsajIwMjMtMDEtMDFrZXhwaXJ5X2RhdGXZA-xqMjA0My0wMS0wMWppc3N1ZXJBdXRohEOhASahGCFZAWEwggFdMIIBBKADAgECAgYBjJHZwhkwCgYIKoZIzj0EAwIwNjE0MDIGA1UEAwwrSjFGd0pQODdDNi1RTl9XU0lPbUpBUWM2bjVDUV9iWmRhRko1R0RuVzFSazAeFw0yMzEyMjIxNDA2NTZaFw0yNDEwMTcxNDA2NTZaMDYxNDAyBgNVBAMMK0oxRndKUDg3QzYtUU5fV1NJT21KQVFjNm41Q1FfYlpkYUZKNUdEblcxUmswWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQCilV5ugmlhHJzDVgqSRE5d8KkoQqX1jVg8WE4aPjFODZQ66fFPFIhWRP3ioVUi67WGQSgTY3F6Vmjf7JMVQ4MMAoGCCqGSM49BAMCA0cAMEQCIGcWNJwFy8RGV4uMwK7k1vEkqQ2xr-BCGRdN8OZur5PeAiBVrNuxV1C9mCW5z2clhDFaXNdP2Lp_7CBQrHQoJhuPcNgYWQHopWd2ZXJzaW9uYzEuMG9kaWdlc3RBbGdvcml0aG1nU0hBLTI1Nmx2YWx1ZURpZ2VzdHOhcW9yZy5pc28uMTgwMTMuNS4xqAFYIKuS8FCeCcvDMwZgEezuuVv-DYsUpdypJp9abJrqHAmXAlggu7D-3vr-NrLg3zigunUzEKFqYAyG5sA-ffvmDjRxZ24DWCC2OBnhoZFhqE7s8PRfdej8t5frp-HgF_2X4qMtzvEY6ARYIBF_rl93VR21umkIdSMiWqFmT5Jxs0n3H5SWonWrJoDrBVggKDvVyMU358Le0n6TkVb2c0BbhbSMJwpswtPLNiZrTR8GWCAFZzJwAmnC7QcMQwq72FDQlmPxk0434cZbh6_rt1VagQdYIHwBHQ3-sVPtco-RcUhuYYq6iivujjYyJmQBbQ_OdhFDCFggcjT2HYgkoxnwWP-9jqO_6-D-d69H9UW2xjpDWrknlvBnZG9jVHlwZXVvcmcuaXNvLjE4MDEzLjUuMS5tRExsdmFsaWRpdHlJbmZvo2ZzaWduZWTAdDIwMjQtMDEtMTJUMDA6MTA6MDVaaXZhbGlkRnJvbcB0MjAyNC0wMS0xMlQwMDoxMDowNVpqdmFsaWRVbnRpbMB0MjAyNS0wMS0xMlQwMDoxMDowNVpYQHFzEb09NFyFlj533FE_1B9I2rku90K52ar64Id1CyOUXWXzhINeVfoJU1cfxgCT2CX1369cGd_TQxSjhVx8bpY

この値は、CBOR データを base64url でエンコードしたものです。そのため、デコードするとバイナリデータになるので、人が読んでも分かりません。そこで、CBOR データを解析するオンラインツール『CBOR Zone』を用意しましたので、是非活用してください。

例えば、上記の mdoc を入力として CBOR Zone に渡し、CBOR 診断記法 (RFC 8949, 8. Diagnostic Notation, RFC 8610, Appendix G. Extended Diagnostic Notation) を出力フォーマットとして指定して "Generate" ボタンを押すと、次の情報が得られます。

CBOR診断記法で表現されたmdoc
{
  "docType": "org.iso.18013.5.1.mDL",
  "issuerSigned": {
    "nameSpaces": {
      "org.iso.18013.5.1": [
        24(<<
          {
            "digestID": 1,
            "random": h'71b9e64c81edd3fd7bb7e01c1e42996c',
            "elementIdentifier": "issue_date",
            "elementValue": 1004("2024-01-12")
          }
        >>),
        24(<<
          {
            "digestID": 2,
            "random": h'51c2fcc1549601736a6585deef5d9c13',
            "elementIdentifier": "expiry_date",
            "elementValue": 1004("2025-01-12")
          }
        >>),
        24(<<
          {
            "digestID": 3,
            "random": h'dcb81876c44e92ab10c3102398f47dc2',
            "elementIdentifier": "family_name",
            "elementValue": "Silverstone"
          }
        >>),
        24(<<
          {
            "digestID": 4,
            "random": h'1eee457bdea02501fe35e380bd2b8974',
            "elementIdentifier": "given_name",
            "elementValue": "Inga"
          }
        >>),
        24(<<
          {
            "digestID": 5,
            "random": h'c8fb86f4dd1fb6f658c5854a193073f4',
            "elementIdentifier": "birth_date",
            "elementValue": 1004("1991-11-06")
          }
        >>),
        24(<<
          {
            "digestID": 6,
            "random": h'22655e9974500c618a237d0704a72de0',
            "elementIdentifier": "issuing_country",
            "elementValue": "US"
          }
        >>),
        24(<<
          {
            "digestID": 7,
            "random": h'6d6cff820531cad49ac7bfc5a82ce810',
            "elementIdentifier": "document_number",
            "elementValue": "12345678"
          }
        >>),
        24(<<
          {
            "digestID": 8,
            "random": h'5b4b03a0f7594cb29bbf72d05adaeaa0',
            "elementIdentifier": "driving_privileges",
            "elementValue": [
              {
                "vehicle_category_code": "A",
                "issue_date": 1004("2023-01-01"),
                "expiry_date": 1004("2043-01-01")
              }
            ]
          }
        >>)
      ]
    },
    "issuerAuth": [
      h'a10126',
      {
        33: h'3082015d30820104a0030201020206018c91d9c219300a06082a8648ce3d04030230363134303206035504030c2b4a3146774a50383743362d514e5f5753494f6d4a415163366e3543515f625a6461464a3547446e5731526b301e170d3233313232323134303635365a170d3234313031373134303635365a30363134303206035504030c2b4a3146774a50383743362d514e5f5753494f6d4a415163366e3543515f625a6461464a3547446e5731526b3059301306072a8648ce3d020106082a8648ce3d03010703420004028a5579ba09a58472730d582a49113977c2a4a10a97d63560f1613868f8c5383650eba7c53c52215913f78a85548baed61904a04d8dc5e959a37fb24c550e0c300a06082a8648ce3d040302034700304402206716349c05cbc446578b8cc0aee4d6f124a90db1afe04219174df0e66eaf93de022055acdbb15750bd9825b9cf672584315a5cd74fd8ba7fec2050ac7428261b8f70'
      },
      24(<<
        {
          "version": "1.0",
          "digestAlgorithm": "SHA-256",
          "valueDigests": {
            "org.iso.18013.5.1": {
              1: h'ab92f0509e09cbc333066011eceeb95bfe0d8b14a5dca9269f5a6c9aea1c0997',
              2: h'bbb0fedefafe36b2e0df38a0ba753310a16a600c86e6c03e7dfbe60e3471676e',
              3: h'b63819e1a19161a84eecf0f45f75e8fcb797eba7e1e017fd97e2a32dcef118e8',
              4: h'117fae5f77551db5ba69087523225aa1664f9271b349f71f9496a275ab2680eb',
              5: h'283bd5c8c537e7c2ded27e939156f673405b85b48c270a6cc2d3cb36266b4d1f',
              6: h'056732700269c2ed070c430abbd850d09663f1934e37e1c65b87afebb7555a81',
              7: h'7c011d0dfeb153ed728f9171486e618aba8a2bee8e36322664016d0fce761143',
              8: h'7234f61d8824a319f058ffbd8ea3bfebe0fe77af47f545b6c63a435ab92796f0'
            }
          },
          "docType": "org.iso.18013.5.1.mDL",
          "validityInfo": {
            "signed": 0("2024-01-12T00:10:05Z"),
            "validFrom": 0("2024-01-12T00:10:05Z"),
            "validUntil": 0("2025-01-12T00:10:05Z")
          }
        }
      >>),
      h'717311bd3d345c85963e77dc513fd41f48dab92ef742b9d9aafae087750b23945d65f384835e55fa0953571fc60093d825f5dfaf5c19dfd34314a3855c7c6e96'
    ]
  }
}

シンタックスハイライトすると次のようになります。

mdoc.png

デモは以上です。

実装実験

ヨーロッパでは次のようなコンソーシアム群が作られ、デジタルアイデンティティウォレットに関する大規模な実験 (Large Scale Pilot) が行われています。

OpenID Foundation の枠内では GAIN POC Community Group が OID4VCI の実験を行っています。私の会社 (Authlete 社) も GAIN POC に参加し、クレデンシャルイシュアの実装を提供しています。

日本では、Trusted Web 推進協議会の旗振りの元、幾つかの実証実験が OID4VCI などの Verifiable Credential 関連標準仕様の実装を進めています。

OID4VCI 仕様詳細解説

Authlete 社のウェブサイト上に OID4VCI 仕様の詳細解説を載せているので、是非ご参照ください。おそらく、今後これより詳しい解説文書は世に出てこないと思います。

おわりに

2023 年末の怒涛の仕様変更ラッシュ (おかげで年末年始も休まずコーディング (なお例年通り)) 、オプショナル機能の一括 VC 発行、SD-JWT VC の新規性、mdoc/mDL 技術文書の有償性 (ISO 文書は購入しないと読めない)、GAIN POC 参加者の実装進捗状況を見るに、おそらく、一括クレデンシャルリクエストで SD-JWT VC と mdoc/mDL の同時発行に成功した例は、これが世界初だと思います:v:

そんな感じで Verifiable Credential 関連仕様群の実装に鋭意取り組んでいたところ、4 月 9 日にイタリアのローマで開催される『2nd International Workshop on Trends in Digital Identity』(TDI 2024) というイベントに講演者として招待され、実装経験で得られた知見について何か話すことになりました。

tdi2024.jpg

TDI 2024 の翌日からの三日間 (4 月 10 日 〜 12 日) は、同じ場所で OAuth Security Workshop 2024 (OSW 2024) も開催されます。OSW オーガナイザーの Daniel Fett 博士が 2023 年 4 月から Authlete 社のメンバーになったこともあり、昨年の OSW 2023 に引き続き、今年も Authlete 社が OSW のメインスポンサーを務めます。

osw2024.png

Daniel が SD-JWT や SD-JWT VC の仕様を書き、Joseph が OpenWallet Foundation の取締役や Digital Credentials Protocols (DCP) Working Group の共同議長に就任するなど、気がつくと Authlete 社はデジタルアイデンティティウォレットや Verifiable Credential に深く関わるようになっていました。この分野にご興味のある方は、Authlete 社まで是非お問い合わせください! #PR

余談

OpenID Summit Tokyo 2024 の会場につくと、自分の会社のロゴが大きく印刷されていて、「ありがたいけど、どうして?」、となりました。

openid_summit_tokyo_2024.jpeg
From https://twitter.com/kura_lab/status/1748136975327240407 by @kura_lab

「Authlete 社が米国 OpenID Foundation に Board Member として納めたお金が活用されているからですよ」と教えてもらいました。言われてみればそんな話があったような気がしましたw! そんなことでロゴを大きくしてくださるなんて、ありがとうございました!

OpenID Summit Tokyo 2024 参加者の皆様、運営の皆様、登壇者の皆様、お疲れ様でした&ありがとうございました!

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6