OpenID for Verifiable Credential Issuance

Table of Contents

1. はじめに

この文書は、『OpenID for Verifiable Credential Issuance』(OID4VCI) 仕様、および Authlete (オースリート) が当仕様をどのようにサポートするかを解説します。

優に百枚を超える図を用いて様々な概念の概要と詳細を丁寧に説明します。読者が文書内を行ったり来たりする必要に迫られることなく内容を理解できるよう、説明前の概念を前提知識として使うことを注意深く避けています。また、読者が途中で迷子にならないよう、全体像を説明している途中で詳細説明に入らないようにしています。これらの配慮により、この文書は仕様本体よりも格段に読みやすくなっています。そのため、事前にこの文書を読んでおけば仕様を読む際に大きな助けとなるでしょう。

変更履歴
日付 変更内容
2023 年 10 月 22 日 初版公開。
2023 年 10 月 27 日 『OID4VCI 実装』セクションに幾つかサブセクションを追加。
2023 年 11 月 15 日 下記の仕様変更に追随するため、説明と図を更新。

  • クレデンシャルイシュアの credentials_supported メタデータの型が JSON 配列から JSON オブジェクトに変更された。
  • クレデンシャルオファー内の credentials 配列の要素の型が、文字列のみとなった。 JSON オブジェクトは受け付けられなくなった。
  • クレデンシャルオファー内の credentials 配列の要素が、 credentials_supported オブジェクト内の要素群の scope プロパティではなく、 credentials_supported オブジェクト内のキーを参照するようになった。
2023 年 11 月 24 日 『OID4VCI デモ』セクションを追加。

下記の仕様変更に追随するため、説明と図を更新。

  • SD-JWT VC 仕様のドラフト 01 で、type クレームが vct クレームへと変更された。
2023 年 12 月 30 日 mdoc デモ用のセクションを追加。

下記の仕様変更に追随するため、説明と図を更新。

  • クレデンシャルオファーの credentials プロパティが credential_configurations に変更された。
  • クレデンシャルオファーの user_pin_required 真偽値プロパティが tx_code オブジェクトで置き換えられた。
  • トークンリクエストの user_pin パラメーターが tx_code で置き換えられた。
  • クレデンシャルイシュアの credentials_supported メタデータが credential_configurations_supported に変更された。
  • openid_credential タイプの RAR オブジェクトに credential_configuration_id プロパティが必須になった。 format プロパティは必要なくなった。
  • クレデンシャルイシュアメタデータ内の proof_types_supportedproof_types に変更された。
  • クレデンシャルレスポンス暗号化に関係する三つのプロパティ群 (credential_encryption_jwk, credential_response_encryption_alg 及び credential_response_encryption_enc) が credential_response_encryption JSON オブジェクトにまとめられた。
  • Data Integrity Proof が鍵証明の手法として追加された。
  • SD-JWT VC フォーマット仕様が OID4VCI 仕様に取り込まれた。
  • vc+sd-jwt フォーマットの場合に RAR オブジェクト、クレデンシャルリクエスト、メタデータに含まれていた credential_definition が取り除かれた。 この結果、これまで credential_definition プロパティの内部に置かれていた vct プロパティと claims プロパティが、一つ上の階層に移動した。
  • /.well-known/jwt-issuer/.well-known/jwt-vc-issuer に変更された。
  • SD-JWT のキーバインディング JWT 内の _sd_hash クレームが sd_hash に変更された。
2024 年 1 月 31 日 下記の仕様変更に追随するために更新。

  • クレデンシャルレスポンス暗号化関連のメタデータ群が credential_response_encryption という一つの JSON オブジェクトにまとめられた。
  • クレデンシャルレスポンスから format プロパティが削除された。
  • RAR オブジェクト内の format プロパティが復活した。 openid_credential タイプを持つ RAR オブジェクトは credential_configuration_id プロパティか format プロパティのどちらかを含まなければならなくなった。
  • クレデンシャルオファー内の credential_configurations プロパティが credential_configuration_ids へと名称変更された。
2024 年 2 月 3 日 下記の仕様変更に追随するために更新。

  • クレデンシャル設定内の cryptographic_suites_supported プロパティが credential_signing_alg_values_supported へと名称変更された。
  • クレデンシャル設定内の proof_types プロパティが proof_types_supported へと名称変更され、また、型が文字列配列から JSON オブジェクト群を内包する JSON オブジェクトへと変更された。
2024 年 5 月 11 日 『OID4VCI デモ』セクションに『POTENTIAL 相互運用性イベント / トラック 1 / Light プロファイル』セクションを追加。
2024 年 6 月 5 日 mdoc ペイロードの不具合修正を反映するため、 『OID4VCI デモ』セクションに『POTENTIAL 相互運用性イベント / トラック 1 / Light プロファイル』セクションを更新。 (参考: authlete/cbor PR 10)
2024 年 6 月 6 日 『OID4VCI デモ』セクションに『POTENTIAL 相互運用性イベント / トラック 2 / Light プロファイル』セクションを追加。
2024 年 6 月 11 日 COSE_Key フォーマットの不具合修正を反映するため、『4.3.2.5. 手順 5 : CWT 鍵証明』セクションを更新。
2024 年 6 月 28 日 『OID4VCI デモ』セクションに『POTENTIAL 相互運用性イベント / トラック 2 / Full プロファイル』セクションを追加。

2. OID4VCI 仕様

OID4VCI 仕様は、Verifiable Credential (検証可能な資格証明) の発行に関する規則を定義します。

2.1. 中心となる技術用語

2.1.1. Verifiable Credential

Verifiable Credential (ベリファイアブル クレデンシャル)』(検証可能な資格証明) は、OID4VCI 仕様の中心となる技術用語です。以降、本文書では VC と呼ぶことにします。

用語内の『Credential』(資格証明) は、一人のユーザまたは複数のユーザ (または特定可能な任意の実体) に関するデータ集合を表しています。氏名や生年月日といった情報はユーザに関するデータの例です。

用語内の『Verifiable』(検証可能な) は、データ集合が改竄されていないことを検証可能であることを示しています。技術的には、データ集合が電子的に署名されていることを意味します。

携帯端末上の電子的な運転免許証や健康保険証などが VC の例です。


2.1.2. Credential Issuer

VC は『Credential Issuer (クレデンシャル イシュア)』(資格証明発行者) により発行されます。クレデンシャルイシュアも技術用語です。仕様はクレデンシャルイシュアの動作を記述しています。

2.1.3. Access Token

VC をクレデンシャルイシュアから取得するためには、発行を要求する際にクレデンシャルイシュアに対して『Access Token (アクセス トークン)』(アクセストークン) を提示しなければなりません。このアクセストークンとは、OAuth 2.0 の中心となる仕様である RFC 6749 で定義されているアクセストークンを指します。

2.1.4. Authorization Server

アクセストークンは『Authorization Server (オーソリゼーション サーバ)』(認可サーバ) により発行されます。認可サーバの基本的な動作は RFC 6749 で定義されており、また、その RFC 6749 を中心として、認可サーバの追加機能を定義する数多くの標準仕様が存在します。OID4VCI 仕様も同様に、VC 発行に使えるアクセストークンを認可サーバが発行できるようにするため、認可サーバに対する追加仕様を定めています。

2.1.5. Wallet

OID4VCI 仕様では、VC を取得するために認可サーバやクレデンシャルイシュアとやりとりするソフトウェアアプリケーションを『Wallet (ウォレット)』(ウォレット) と呼びます。技術的には、VC 発行の文脈ではウォレットは OAuth 2.0 のクライアントアプリケーションとして動作します。そのため、OID4VCI 仕様の文脈では、技術的な観点からは、ウォレットという用語とクライアントアプリケーションという用語は相互に交換可能です。

2.1.6. 関係性

次の図は中心となる技術用語間の関係を示しています。

2.2. アクセストークン発行の概要

2.2.1. 事前認可コードフロー

仕様では、VC 発行に使えるアクセストークンを発行するための方法を幾つか定義しています。

それらのうちの一つは完全に新しいもので、『Pre-Authorized Code Flow (プリオーソライズド コード フロー)』(事前認可コードフロー) と呼ばれます。このフローでは、最初の手順として、ウォレットはクレデンシャルイシュアから『pre-authorized code(プリオーソライズド コード)』(事前認可コード) を取得します。

そして、ウォレットは事前認可コードを認可サーバの『Token Endpoint (トークン エンドポイント)』(トークンエンドポイント) (RFC 6749, 3.2. Token Endpoint) に提示します。

引き換えに、ウォレットはアクセストークンを受け取ります。

先ほど説明したように、ウォレットはそのアクセストークンをクレデンシャルイシュアに提示します。具体的には、ウォレットはアクセストークンをクレデンシャルイシュアの『Credential Endpoint (クレデンシャル エンドポイント)</rp』(クレデンシャルエンドポイント) に提示します。

引き換えに、ウォレットは VC を受け取ります。

次の図は事前認可コードフローの概要を示しています。

2.2.2. 認可コードフロー

事前認可コードフロー以外の方法は、伝統的な『Authorization Code Flow (オーソリゼーション コード フロー)』(認可コードフロー) (RFC 6749, 4.1. Authorization Code Grant) の拡張です。

認可コードフローを復習しましょう。

認可コードフローでは、最初の手順として、クライアントアプリケーション (OID4VCI の文脈ではウォレット) はウェブブラウザを介して認可サーバの『Authorization Endpoint (オーソリゼーション エンドポイント)』(認可エンドポイント) (RFC 6749, 3.1. Authorization Endpoint) に『Authorization Request (オーソリゼーション リクエスト)』(認可リクエスト) を送ります。

認可リクエストを受け取ると、認可サーバはウェブブラウザを介してユーザとやり取りを始めます。そしてユーザから同意を得た後、認可サーバはクライアントアプリケーションに『Authorization Code (オーソリゼーション コード)』(認可コード) を発行します。

その後、クライアントアプリケーションはその認可コードを含む『Token Request (トークン リクエスト)』(トークンリクエスト) を認可サーバのトークンエンドポイントに送ります。

引き換えに、クライアントアプリケーションはアクセストークンを受け取ります。

アクセストークンを取得した後の処理は事前認可コードフローのものと同じです。クライアントアプリケーションはクレデンシャルイシュアのクレデンシャルエンドポイントにアクセストークンを提示します。

引き換えに、クライアントアプリケーションは VC を受け取ります。

次の図は、認可コードフローと、それに続く VC 発行を示しています。

OID4VCI 仕様は認可コードフロー中の認可リクエストを拡張します。具体的には、次に挙げる認可リクエストパラメーター群を利用します。

  1. issuer_state リクエストパラメーター
  2. authorization_details リクエストパラメーター
  3. scope リクエストパラメーター

issuer_state リクエストパラメーターは OID4VCI 仕様で定義される新しいパラメーターです。

authorization_details リクエストパラメーターは RFC 9396 OAuth 2.0 Rich Authorization Requests で定義されています。この仕様は RAR (ラー) と略して呼ばれることがあります。

scope リクエストパラメーターは RFC 6749 The OAuth 2.0 Authorization Framework で定義されているパラメーターです。

2.2.3. 認可コードフロー + issuer_state

issuer_state リクエストパラメーターは OID4VCI 仕様で定義されています。このリクエストパラメーターを使うためには、認可リクエストをおこなう前に、ウォレットはクレデンシャルイシュアから『Issuer State (イシュア ステート)』(イシュアステート) を取得しておく必要があります。

その後ウォレットは、そのイシュアステートを issuer_state リクエストパラメーターの値として含む認可リクエストをおこないます。

認可リクエストの残りの部分は、通常の認可コードフローと同じです。

次の図はイシュアステートを用いる認可コードフローを示しています。

2.2.4. 認可コードフロー + authorization_details

RAR 仕様 (RFC 9396) は、認可に関する詳細情報を伝えるための汎用的なパラメーターとして authorization_details を定義しています。このパラメーターをどのように使うかは、それぞれの運用に任されています。

このパラメーターの値は JSON 配列であり、配列の各要素は JSON オブジェクトです。このオブジェクトは『RAR オブジェクト』と呼ばれます。

RAR オブジェクトは柔軟です。任意のプロパティをオブジェクト内に置くことができます。しかし、想定される用途で共通して使われるであろうプロパティがあるため、RAR 仕様は幾つかのプロパティを事前に定義しています。

そのような事前定義されたプロパティ群のうち、"type" プロパティだけは必須です。このプロパティは RAR オブジェクトが何を表しているのかを示します。

そして OID4VCI 仕様は、ウォレットが欲しい VC に関する情報を含む RAR オブジェクトであることを示すための特別な "type" 値として "openid_credential" を定義しています。

2.2.5. 認可コードフロー + scope

scope リクエストパラメーターは OAuth 2.0 の中心仕様 (RFC 6749) で定義されている伝統的なパラメーターの一つです。クライアントアプリケーションが欲しい権限を列挙するのが、このパラメーターの元来の用途です。ユーザがリクエストを承認すれば、認可サーバは要求された権限を持つアクセストークンを発行します。

歴史的に scope リクエストパラメーターは元々意図していた用途外でも利用されてきました。そして、OID4VCI 仕様も同様に scope リクエストパラメーターの使い方を拡張します。

クレデンシャルイシュアは、自身が発行可能な VC を『Credential Configuration (クレデンシャル コンフィギュレーション)』(クレデンシャル設定) として管理しており、そのリストをある場所で公開しています。各クレデンシャル設定は "scope" プロパティを持つことがあります。

ウォレットは、どの種類の VC が欲しいかを示すために、"scope" プロパティの値を scope リクエストパラメーターに含めることができます。

複数のクレデンシャル設定が同じ値を "scope" プロパティに持つ場合もあります。

2.3. クレデンシャル発行の概要

認可サーバからアクセストークンを取得すれば、ウォレットはアクセストークンを提示することにより、クレデンシャルイシュアに VC の発行を要求することができます。

基本的な手順では、ウォレットはクレデンシャルイシュアのクレデンシャルエンドポイントにアクセストークンを含むクレデンシャルリクエストを送ります。

クレデンシャルイシュアは応答として要求された VC を発行します。

2.3.1. 遅延クレデンシャル発行

しかし、要求された時点で VC が用意できていないこともありえます。たとえば、裏で時間のかかるオフライン処理が走っているかもしれません。

このような場合、クレデンシャルイシュアは代わりに『Transaction ID (トランザクション アイディー)』(トランザクション ID) を発行します。

ウォレットは VC 発行の準備が整うまで待機します。その後、受信済みのトランザクション ID とアクセストークンを『Deferred Credential Endpoint (ディファード クレデンシャル エンドポイント)』(遅延クレデンシャルエンドポイント) に提示します。

クレデンシャルイシュアは応答として要求された VC を発行します。

VC が依然として用意できていない場合、遅延クレデンシャルエンドポイントはその旨を示すエラー (たとえば "error":"issuance_pending") を返すでしょう。このような場合、ウォレットは後ほど再び『Deferred Credential Request (ディファード クレデンシャル リクエスト)』(遅延クレデンシャルリクエスト) を実行します。


2.3.2. 一括クレデンシャル発行

ウォレットは一度に複数の VC を入手したいことがあるかもしれません。そのような場合のために『Batch Credential Endpoint (バッチ クレデンシャル エンドポイント)』(一括クレデンシャルエンドポイント) があります。

ウォレットはアクセストークンを含む『Batch Credential Request (バッチ クレデンシャル リクエスト)』(一括クレデンシャルリクエスト) を一括クレデンシャルエンドポイントに送信します。

エンドポイントは、複数の VC またはトランザクション ID を返却します。

各トランザクション ID は、遅延クレデンシャルエンドポイントから VC を取得するために使うことができます。

2.4. アクセストークン発行の詳細

ここまでの説明で、アクセストークン発行とクレデンシャル発行の概要を見てきました。この節ではアクセストークン発行の詳細について見ていこうと思います。


2.4.1. クレデンシャルオファー

クレデンシャルイシュアが事前認可コードを発行する際、それを直接発行する代わりに、それを内包する『Credential Offer (クレデンシャル オファー)』(クレデンシャルオファー) を発行します。

同様に、イシュアステートもクレデンシャルオファーの一部として含まれます。クレデンシャルオファーは、事前認可コードもしくはイシュアステート、または両方を含むことがあります。

クレデンシャルオファーは他の情報も含んでいます。クレデンシャルイシュアの識別子は常に含まれます。

また、クレデンシャルオファーは、クレデンシャルイシュアが提供する VC に関する情報を含んでいます。

2.4.1.1. 値によるクレデンシャルオファー発行

クレデンシャルオファーをウォレットに送信するために URL が使われます。この URL は credential_offer というクエリパラメーターを伴う『Credential Offer Endpoint (クレデンシャル オファー エンドポイント)』(クレデンシャルオファーエンドポイント) です。そのクエリパラメーターの値はクレデンシャルオファーの内容です。

その URL が何らかの方法でアクセスされ、そのアクセスをウォレットが処理することができれば、ウォレットはクレデンシャルオファーを受け取ることができます。

しかし、ここで問題なのは、どのようにそのアクセスを引き起こすかです。OID4VCI 仕様ではクレデンシャルイシュアによる HTTP GET リクエストや HTTP リダイレクションの始動を想定していますが、アクセスの引き起こし方についてクレデンシャルイシュアとウォレットがどのように合意するかは定めていません。

また、クレデンシャルオファーを提供する際、クレデンシャルイシュアはクレデンシャルオファーエンドポイントの値を知ることができないという別の問題もあります。仕様ではウォレットのクレデンシャルオファーエンドポイントを表す credential_offer_endpoint というクライアントメタデータを定義しています。しかし、仕様が言及している「URL を QR コードで表現する手法」が用いられる場合は特に、どのウォレットに対してクレデンシャルオファーを発行しようとしているかをクレデンシャルイシュアは知ることができないので、クレデンシャルイシュアはウォレットのメタデータの情報を知りえません。このような場合のため、クレデンシャルオファーエンドポイントの代替として openid-credential-offer:// が定義されています。


2.4.1.2. 参照によるクレデンシャルオファー発行

クレデンシャルオファーは参照によってウォレットに渡されるかもしれません。具体的には、URL は発行されたクレデンシャルオファーの内容ではなく、その配置場所に関する情報を含んでいるかもしれません。この場合、その場所を示すために credential_offer_uri クエリパラメーターが用いられます。

credential_offer_uri クエリパラメーターの値は、発行されたクレデンシャルオファーの内容を返すエンドポイントを指しています。

その URI にアクセスすることにより、

ウォレットは発行されたクレデンシャルオファーの内容を取得することができます。

2.4.1.3. クレデンシャルオファーの内容

クレデンシャルオファーの実際の内容は JSON オブジェクトです。

クレデンシャルイシュアの識別子が、"credential_issuer" プロパティの値として置かれます。

クレデンシャルイシュアが提供する VC に関する情報は “credential_configuration_ids” 配列に置かれます。配列要素の詳細については後ほど紹介します。

イシュアステートが発行される場合は、幾分ネストした場所に置かれます。

クレデンシャルオファーには、トップレベルのプロパティとして "grants" プロパティがあります。"grants" プロパティの値は JSON オブジェクトです。その "grants" JSON オブジェクト内のキーは、authorization_code のようなグラントタイプ (認可種別) の識別子です。

"grants" JSON オブジェクト内の各エントリの値もまた JSON オブジェクトで、それぞれ、キーが示すグラントタイプに関連するプロパティ群を含んでいます。

イシュアステートの場合は、その値は "grants" JSON オブジェクト内の "authorization_code" JSON オブジェクト内の "issuer_state" プロパティの値として置かれます。

同様に、事前認可コードの場合は、その値は "grants" JSON オブジェクト内の "urn:ietf:params:oauth:grant-type:pre-authorized_code" JSON オブジェクト内の "pre-authorized_code" プロパティの値として置かれます。ここで登場する "urn:ietf:params:oauth:grant-type:pre-authorized_code" は、事前認可コードフローに割り当てられた新しい識別子です。

"urn:ietf:params:oauth:grant-type:pre-authorized_code" JSON オブジェクトは、トランザクションコード の情報を保持する "tx_code" JSON オブジェクトを含むことがあります。tx_code オブジェクトが含まれていた場合、事前認可コードを用いたトークンリクエストはトランザクションコードを含まなければなりません。 詳細については後ほど記述します。

次の図はクレデンシャルオファーの内容の構造を示した概要図です。

2.4.1.4. クレデンシャルオファー内の “credential_configuration_ids”

クレデンシャルオファー内の “credential_configuration_ids” プロパティは、クレデンシャルイシュアが提供する VC に関する設定を保持しています。 このプロパティの値は JSON 配列です。各配列要素は JSON 文字列です。

要素の値はクレデンシャル設定の識別子を表しています。

2.4.2. 事前認可コードフローの詳細

事前認可コードが得られれば、ウォレットはその事前認可コードを用いてトークンリクエストを実行できます。

次の表は、事前認可コードフローに準拠するトークンリクエストで必要とされるリクエストパラメーター群を列挙したものです。

パラメーター 説明
grant_type 値は "urn:ietf:params:oauth:grant-type:pre-authorized_code" でなければなりません。
pre-authorized_code 事前認可コード
tx_code トランザクションコード。クレデンシャルオファーが tx_code オブジェクトを含んでいれば、このパラメーターは必須です。

下記の例のように、事前認可コードが tx_code オブジェクトと共に発行されていれば、tx_code パラメーターは必須になります。 この場合、何らかの方法でトランザクションコードがユーザに届けられるものと想定されます。

{
  "credential_issuer": "...",
  "credential_configuration_ids": [
    "..."
  ],
  "grants": {
    "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
      "pre-authorized_code": "...",
      "tx_code": {
        "length": 6,
        "input_mode": "numeric",
        "description": "Input the one-time code sent via email"
      }
    }
  }
}

ユーザにトランザクションコードの入力を促す UI 部品をウォレットが準備するのを手助けするため、tx_code オブジェクトは次表に挙げるプロパティを含んでいることがあります。

パラメーター 説明
length トランザクションコードの長さ。
input_mode トランザクションコードの入力モード。事前定義されている値は "numeric""text"
description 配送チャネルなど、トランザクションコードに関する情報。

また、クライアント認証に関連するリクエストパラメーターも追加で要求されるかもしれません。例えば private_key_jwt クライアント認証が用いられる場合、client_assertion リクエストパラメーターと client_assertion_type リクエストパラメーターが必要となります。

下記は、OID4VCI 仕様から抜粋したクライアント認証を伴わない事前認可コードフローのトークンリクエストの例です。

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code
&pre-authorized_code=SplxlOBeZQQYbYS6WxSbIA
&tx_code=493536

いつものように、トークンエンドポイントはアクセストークンを含む応答を返します。下記は仕様書から抜粋したトークンレスポンスの例です。

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ",
  "token_type": "bearer",
  "expires_in": 86400,
  "c_nonce": "tZignsnFbp",
  "c_nonce_expires_in": 86400
}

上記の例が示すように、発行されたアクセストークンが VC 発行に使用可能な場合、トークンレスポンスは OAuth 2.0 の中心仕様 (RFC 6749) で定義されている昔ながらのレスポンスパラメーター群 (access_token, token_type, expires_in) に加えて、c_nonce レスポンスパラメーターと c_nonce_expires_in レスポンスパラメーターを含む場合があります。 これらのレスポンスパラメーター群の詳細は後ほど説明します。


2.4.3. 発行可能クレデンシャル

VC 用アクセストークンを発行する手段が幾つか用意されているものの、アクセストークンの実装の観点から言えば、全て一つの共通の目標に収束します。それは、発行可能な VC 種別に関する情報をアクセストークンに紐付けることです。

この文書では、その情報を『Issuable Credential (イシュアブル クレデンシャル)』(発行可能クレデンシャル) と呼びます。ただし、この用語は公式のものではないので、その点はご留意ください。

クレデンシャルオファーの "credential_configuration_ids" 配列の要素 JSON 文字列です。 それらは、クレデンシャル設定を参照することにより発行可能クレデンシャルを間接的に指定しています。

この例では "credential_configuration_ids" 配列の要素数は 1 ですが、配列が複数の要素を含むことはありえます。 それらの要素により、クレデンシャルオファーが表す発行可能クレデンシャル群の集合が形成されます。

そのため、事前認可コードフローを用いるトークンリクエストは、事前認可コードを含むクレデンシャルオファーで指定される発行可能クレデンシャル群に紐付いたアクセストークンを要求していると言えます。

同様に、issuer_state リクエストパラメーターを用いる認可リクエストは、イシュアステートを含むクレデンシャルオファーで指定される発行可能クレデンシャル群に紐付いたアクセストークンを要求していると言えます。

"type":"openid_credential" を持つ RAR オブジェクトは、発行可能クレデンシャルのベースとなるクレデンシャル設定を credential_configuration_id プロパティを使って指定します。

または、代わりに format プロパティを使うこともあります。

scope リクエストパラメーター内の値は、クレデンシャル設定の "scope" プロパティを介して一つ以上の発行可能クレデンシャルを間接的に指定するかもしれません。

現在の OID4VCI 仕様のドラフトでは発行可能クレデンシャルを指定するこれらの仕組みが同時に使われた場合について明示的に言及していませんが、我々の解釈と実装では、これらの仕組みで指定された全ての発行可能クレデンシャル群は結合されて一つの集合を形成します。

次の図は発行可能クレデンシャルを指定する仕組みの全体像を示しています。

図中の credential info (クレデンシャル情報) と RAR オブジェクトの構造について気になっているかもしれません。

しかし、それらの詳細に立ち入る前に、VC のフォーマットについて見ていく必要があります。


2.5. クレデンシャル検証

VC のフォーマットを議論するためには、それらが意図する目的を理解する必要があります。VC の検証処理を一つ一つ見ていくことにしましょう。

まず始めに、クレデンシャルイシュアは VC に含めるデータを用意します。この例では、name (氏名)、birthdate (生年月日)、address (住所) を使います。

データに署名するため、クレデンシャルイシュアは private key (秘密鍵) と public key (公開鍵) の組を用意します。

その後、クレデンシャルイシュアは秘密鍵を用いてデータに署名します。結果として、署名が生成されます。

このデータと署名の集合が VC です。

クレデンシャルイシュアは VC をウォレットに渡します。

ウォレットが VC の署名を検証したければ、何らかの方法でクレデンシャルイシュアから公開鍵を取得し、それを署名検証に用います。検証が成功すれば、その VC が正当なクレデンシャルイシュアから発行されたこと、及びその内容が改竄されていないことが保証されます。

VC の利用方法と ID トークンの利用方法との重要な違いは、VC の『Holder (ホルダー)』(保有者) は VC を他者に提示することがある点です。提示の前に、VC は『Verifiable Presentation (ベリファイアブル プレゼンテーション)』(検証可能なプレゼンテーション) に変換されます。本文書では、以降 Verifiable Presentation を VP と呼ぶことにします。

ウォレットは VP を他者に提示します。VP を受け取る部外者は『Verifier (ベリファイア)』(検証者) と呼ばれます。というのは、ホルダーに対してサービスを提供する前にベリファイアは VP の検証をおこなうものと想定されているからです。

ベリファイアが VP の署名を検証したければ、何らかの方法でクレデンシャルイシュアから公開鍵を取得し、それを署名検証に用います。

2.5.1. キーバインディング

ところで、ベリファイアは次の事項をどうすれば確認できるでしょうか?

  1. VP が当ホルダーによって提示されたこと
  2. VP の元となった VC はクレデンシャルイシュアが当ホルダーに対して発行したものであること

一点目については、ホルダーに秘密鍵と公開鍵の組を提供してもらい、

そのホルダーの秘密鍵を用いて作成した署名を VP に含めることで実現できるでしょう。署名対象のデータは、署名と共に提示される限り、任意のデータでかまいません。

二点目については、ホルダーの公開鍵を VC のデータに含め、クレデンシャルイシュアにデータ全体に対して署名してもらうことで実現できます。

VC とホルダー間のこのような暗号的関連付けは『Key Binding (キー バインディング)』(キーバインディング) と呼ばれます。

ウォレットが暗号的キーバインディング付きの VC を要求する場合、クレデンシャルリクエストに公開鍵を含めます。しかし、クレデンシャルイシュアは提示された公開鍵を無条件に受け入れるわけにはいきません。というのは、悪意のあるウォレットが無関係な公開鍵を提示するかもしれないからです。

そのため、ウォレットは自身が公開鍵の正当な所有者であることを示さなければなりません。これを実現するため、ウォレットは対応する秘密鍵を用いて署名を生成し、公開鍵と併せて提示します。この署名と公開鍵の組みは一般的に『Key Proof (キー プルーフ)』(鍵証明) と呼ばれます。

提示された公開鍵の有効性を確認できれば、クレデンシャルイシュアはキーバインディング付きの VC を生成することができます。

その VC はウォレットに渡され、

ウォレットはその VC を元に VP を生成します。ウォレットは秘密鍵を用いて作成した署名を VP に含めます。

そして VP がベリファイアに渡されます。

ベリファイアは、VP に埋め込まれている公開鍵を用いてウォレットが追加した署名を検証することができます。

次の図は、これまでに説明したクレデンシャル検証処理の全体像を示しています。

2.6. 選択的開示

VP を提示する際、ホルダーは VC の内容の一部のみを開示することを選ぶかもしれません。例えば、VC が氏名、生年月日、住所を含んでいるとき、ホルダーは氏名と生年月日のみを開示し、住所の情報は省くことを選択するかもしれません。

このように情報を選んで開示することを『Selective Disclosure (セレクティブ ディスクロージャ)』(選択的開示) と言います。

しかし、特別な考慮をせずに情報を省略すると、署名検証が失敗してしまいます。なぜなら、署名が対象としたデータ集合とベリファイアが受け取ったデータ集合が異なるからです。

署名を無効化することなく選択的開示を実現する手法が幾つかあります。BBS+ (Boneh-Lynn-Shacham signature plus) や CL Signatures (Camenisch-Lysyanskaya Signatures) はそのような例で、有望のように思われます。しかしながら、現実世界では、特定の手法が採用されるかどうかは下記に挙げるように様々な要因に依存しており、必ずしも学術的に美しい理論に基づく解決策が普及するとは限りません。

  1. 理論の複雑さ
  2. 実装の容易さ
  3. ライセンス費用
  4. 使用する際の法的制限
  5. ハードウェアセキュリティモジュール製品によるサポート
  6. アルゴリズムの頑健性/安全性を業界がどの程度信じているか

徹底的なクレデンシャルプロファイル比較とハッカソンの後、業界は『Selective Disclosure for JWTs (SD-JWT)』と呼ばれる新しいフォーマットを作成することに決めました。


2.6.1. SD-JWT

SD-JWT は JWT (RFC 7519 JSON Web Token (JWT)) を利用して選択的開示を実現するフォーマットです。

通常の JWT のペイロード部には、クレーム名とクレーム値の組が含まれています。

このようなクレームを SD-JWT フォーマットを用いて選択的開示可能とするには、まず、クレームを取り出します。

そして、任意のソルト (salt) を追加し、

そのソルトとクレーム名およびクレーム値を含む JSON 配列を作ります。

次に、その JSON 配列を base64url でエンコードします。SD-JWT 仕様では、この結果得られる文字列を『Disclosure (ディスクロージャ)』(ディスクロージャ) と呼びます。

元のクレームはディスクロージャのダイジェスト値で置き換えられます。ダイジェスト値は base64url でエンコードされ、元のクレームが置かれていた場所に挿入された "_sd" 配列内に置かれます。

選択的開示可能にする必要のある他のクレーム群に対して同じ処理を行います。

そして、クレデンシャルイシュアが署名した JWT (issuer-signed JWT) とディスクロージャ群をチルダ (~) で連結し、一つの文字列を作ります。

結果として得られた文字列が SD-JWT です。

次の処理は任意ですが、キーバインディングをおこないたければ、鍵の組を用意してください。

そして、クレデンシャルイシュアが署名する JWT に公開鍵を埋め込み、

仕様で定義される特定のデータ集合に署名し、結果として得られた JWT を先ほど生成した SD-JWT の末尾におきます。この JWT を『Key Binding JWT (キー バインディング ジョット)』(キーバインディング JWT) と呼びます。

次の図は、これまでに説明した SD-JWT 生成手順を示しています。

ここで鍵となるのは、SD-JWT の受信者が全てのディスクロージャを受け取っていない場合、ディスクロージャに対応するクレームしか再構築できないことです。そして重要なのは、そのような場合においてもクレデンシャルイシュアが署名した JWT の署名は有効のままという点です。

より詳細な情報は SD-JWT 仕様を参照してください。また、Java 言語用に書かれたオープンソースの SD-JWT ライブラリである authlete/sd-jwt の README にも有益な情報があります。

2.7. VC フォーマット

2.7.1. VC フォーマットをめぐる混乱

VC フォーマットをめぐる混乱は、複数の競合する仕様が存在し、それぞれが課題を抱え、依然として開発中であることから来ています。また、様々な国家、地域、業界の組織が異なるフォーマットを推進していることも状況を複雑にしています。

VC となると、多くの人が W3C Verifiable Credentials Data Model (W3C VCDM) を思い浮かべます。これは主に、当文書が『イシュア・ホルダー・ベリファイア』の三者間モデルを定義している一次情報源とみなされることが多いからです。しかし、W3C VCDM 自体も完全無欠というわけではなく、実際に議論は続いています。バージョン 1.1 は 2022 年 3 月 3 日にリリースされましたが、現在バージョン 2.0 の議論が進んでいます。

外部から観察したときに、さらに状況を複雑にしているのは、Securing Verifiable Credentials using JOSE and COSE (w3c/vc-jose-cose) という名前の仕様です。その仕様は Abstract セクションで “defines how to secure credentials and presentations conforming to the VC-DATA-MODEL” と述べていますが、当仕様は W3C VCDM の要求事項と衝突している部分が幾つかあります。例えば、W3C VCDM は "typ" ヘッダパラメーターの値は "JWT" であることを要求していますが、w3c/vc-jose-cose はこの要求に従っていません。また、W3C VCDM は VC や VP を埋め込むための場所として "vc" クレームと "vp" クレームを導入していますが、w3c/vc-jose-cose はこれらのクレームを利用していません。

さらに新参者を混乱させるのは、OID4VCI 仕様は W3C VCDM に基づくクレデンシャルフォーマットのプロファイルとして jwt_vc_jsonjwt_vc_json-ldldp_vc を定義しているものの、OpenID 業界で当仕様に貢献しているほとんどの人は、それらのクレデンシャルフォーマットプロファイル群をサポートする気がないことです。彼らは現在、その労力を SD-JWT や ISO/IEC 18013-5 (Personal identification - ISO-compliant driving licence - Part 5: Mobile driving licence (mDL) application) をベースとする VC フォーマットの仕様策定と実装に注いでいます。

OAuth や OpenID Connect を議論する人々にとって ISO/IEC 18013-5 が扱いにくい理由は、そのフォーマットが、Concise Binary Object Representation (CBOR) (RFC 8949) や CBOR Object Signing and Encryption (COSE) (RFC 9052, RFC 9053) というあまり馴染みのないバイナリフォーマットに基づくからです。また、ISO/IEC 18013-5 に関する詳細な技術情報がオンライン上であまり得られないのは、ISO 標準文書は購入しないと手に入らないからです。

VC 検証のための公開鍵を配布する方法も課題です。ベリファイアが VC を受け取った際、その VC が OID4VCI 仕様に従って発行されたかどうかは分かりません。そのため、クレデンシャルイシュアのメタデータ (/.well-known/openid-credential-issuer) を開始点として公開鍵を探すことをベリファイアに強制することは理想的とは言えません。

代替となる開始点として、ウェルノーンパス /.well-known/jwt-issuerSD-JWT-based Verifiable Credentials (SD-JWT VC) という仕様内で提案されました。しかし、パス名が汎用的過ぎ、JWT 発行関連の他の仕様群と簡単に衝突する恐れがあるので、後日パス名は /.well-known/jwt-vc-issuer に変更されました。 それでも依然として、このパス名は、VC のフォーマットが JWT ベースであることを不必要に想定しているという問題を抱えています。 そのため、この解決方法を好まない人々もいます。 実際に OpenID Federation を活用しているイタリアのエコシステムは /.well-known/jwt-vc-issuer を使わないことを決めました。 彼らは代わりに openid_credential_issuer という新しいエンティティタイプ識別子を定義し、エンティティコンフィギュレーションの “metadata”.“openid_credential_issuer” オブジェクトに VC 検証用の公開鍵を埋め込むことにしました。

関連する議題として、OAuth 2.0 Attestation-Based Client Authentication と呼ばれる新しいクライアント認証方式が現在開発中です。 この方式のため、ウォレットはあらかじめ『Attester (アテスタ)』(証人) から『Wallet Attestation (ウォレット アテステーション)』を取得しておく必要があります。 というのは、クライアント認証を実行するときにそれが必要になるからです。 アテステーションの受け手 (例えば認可サーバ) は、アテステーションの署名を検証するための公開鍵をアテスタから取得しなければなりません。 ここで、アテステーション用の公開鍵配布は、先に述べた VC と似たような問題となります。 そして、/.well-known/jwt-vc-issuer がここでも選択肢の一つとして提案されています。 これはまさに予想された通りの懸念事項です。つまり、アテスタとクレデンシャルイシュアを同じサーバ上で動かすことが技術的に不可能になるのです (それら二つを同じサーバ上で動かすことが概念的に適切かどうかは別問題です)。 加えて、アテステーションのフォーマットが JWT かどうかは本質ではないのです。

しかし、アテステーションベースのクライアント認証に関してより深刻な問題は、 基本的な概念について完全な合意にまだ達していないことです (参照: ISSUE 61)。


2.7.2. VC フォーマットの本質的機能

前節で述べたように、VC フォーマットに関する多くの課題があります。しかし、VC フォーマットに期待される本質的機能は次のものに要約できると我々は考えています。

  1. 検証可能性
  2. キーバインディング
  3. 選択的開示

次の節では、これらの要求事項を満たすことのできる SD-JWT に基づく VC フォーマットについて説明します。


2.7.3. SD-JWT VC

SD-JWT は汎用的なデータフォーマットで、それ自体は VC フォーマットではありません。しかし、特定の要求事項を追加することにより、SD-JWT に基づく VC フォーマット定義することは可能です。SD-JWT-based Verifiable Credentials (SD-JWT VC) はそのような目的で作られた仕様です。

SD-JWT の概要については既に見てきたので、ここでは SD-JWT VC のポイントを下表に簡潔に紹介するにとどめます。詳細については SD-JWT VC 仕様を参照してください。

メディアタイプ application/vc+sd-jwt
クレデンシャルイシュアが署名する JWT 場所 名前 要否 説明
ヘッダ alg 必須 JWT 仕様 (RFC 7519) で要求されている通り。
typ 必須 vc+sd-jwt
ペイロード iss 必須 クレデンシャルイシュアの識別子。
iat 必須 発行日時
nbf 任意 VC 無効期間終了日時 (VC 有効期間開始日時)
exp 任意 有効期限終了日時
cnf 条件により必須 暗号的キーバインディングが要請されていれば必須。 公開鍵を表す "jwk" プロパティを含むべき。 (参照: RFC 7800)
vct 必須 VC 種別を示す識別子。
status 任意 VC の状態確認方法に関する情報。
sub 任意 VC のサブジェクトの識別子。
キーバインディング JWT 場所 名前 要否 説明
ヘッダ alg 必須 JWT 仕様 (RFC 7519) で要求されている通り。
typ 必須 kb+jwt (SD-JWT 仕様で要求されている通り)
ペイロード iat 必須 発行日時
aud 必須 キーバインディング JWT の受信者。典型的にはベリファイア。
nonce 必須 リプレイ攻撃の軽減策のための文字列
sd_hash 必須 クレデンシャルイシュアが署名した JWT と選択したディスクロージャ群のハッシュ値。

クレデンシャルイシュアが署名する JWT 内の vct クレームの実際の値、およびそのクレデンシャル種別固有の追加クレーム群は、それぞれの運営主体が決めることであり、SD-JWT VC 仕様の対象範囲外です。


2.7.4. 他の VC フォーマット

この文書では jwt_vc_json などの他の VC フォーマットについては説明しません。

2.8. アクセストークン用のクレデンシャル情報

VC フォーマットについて見てきたので、アクセストークン用のクレデンシャル情報の議題に戻ることができます。


2.8.1. RAR オブジェクト内のクレデンシャル情報

RAR オブジェクトのタイプが openid_credential の場合、その RAR オブジェクトは発行可能クレデンシャルに関する情報を含んでいます。

そのような RAR オブジェクトは必ず credential_configuration_id プロパティまたは format プロパティを含んでいなければなりません。 この二つのプロパティは相互排他の関係にあります。


2.8.1.1. credential_configuration_id プロパティを含む RAR オブジェクト

credential_configuration_id プロパティの値は、クレデンシャルイシュアメタデータ内のクレデンシャル設定を参照します。

仕様書では明示的に説明されていないものの、仕様書内の例から、 credential_configuration_id プロパティが参照するクレデンシャル設定は、 発行可能クレデンシャルを構築するためのベースとしてのみ使われるということが推察されます。 別の言い方をすると、クレデンシャルオファー内で参照されるクレデンシャル設定とは異なり、RAR オブジェクト内で参照されるクレデンシャル設定は、クレデンシャルフォーマット (及び他の必須プロパティ; vc+sd-jwt フォーマットの場合における vctmso_mdoc フォーマットの場合における doctype など) の情報を取得するためだけに利用されるということです。結果として、RAR オブジェクトにはウォレットが入手したいクレーム群が列挙されることが想定されます。

クレーム群を列挙する方法は、クレデンシャルのフォーマットによって異なります。例えば、仕様書内の例によれば、 jwt_vc_json フォーマットではクレーム群を列挙するのに credential_definition プロパティを用います。

{
  "type": "openid_credential",
  "credential_configuration_id": "UniversityDegreeCredential",
  "credential_definition": {
    "credentialSubject": {
      "given_name": {},
      "family_name": {},
      "degree": {}
    }
  }
}

一方、mso_mdoc フォーマットは claims プロパティを用います。

{
  "type": "openid_credential",
  "credential_configuration_id": "org.iso.18013.5.1.mDL",
  "claims": {
    "org.iso.18013.5.1": {
      "given_name": {},
      "family_name": {},
      "birth_date": {}
    },
    "org.iso.18013.5.1.aamva": {
      "organ_donor": {}
    }
  }
}

2.8.1.2. format プロパティを含む RAR オブジェクト

credential_configuration_id プロパティの代わりに format プロパティを使う場合、 RAR オブジェクトは発行可能クレデンシャルに関する完全な情報を含まなければなりません。

下記の例では VC フォーマットとして mso_mdoc が指定されています。このフォーマットでは doctype プロパティが必須なので、結果として doctype プロパティも含まれています。

{
  "type": "openid_credential",
  "format": "mso_mdoc",
  "doctype": "org.iso.18013.5.1.mDL",
  "claims": {
    "org.iso.18013.5.1": {
      "given_name": {},
      "family_name": {},
      "birth_date": {}
    },
    "org.iso.18013.5.1.aamva": {
      "organ_donor": {}
    }
  }
}

2.8.2. クレデンシャル設定内のクレデンシャル情報

クレデンシャル設定の情報は『Credential Issuer Metadata (クレデンシャル イシュア メタデータ)』(クレデンシャルイシュアメタデータ) の一部として記述されます。

メタデータは JSON オブジェクトで、credentials_configurations_supported JSON オブジェクトを含んでいます。そのオブジェクト内のエントリは、クレデンシャルイシュアがサポートする VC のクレデンシャル設定を表しています。

クレデンシャル設定オブジェクト内のプロパティ群は、 (1) 全てのクレデンシャル設定オブジェクトに共通して現れるものと (2) それぞれのフォーマットに固有のものとに分かれます。 例えば、format プロパティは全ての対応クレデンシャルオブジェクトに含まれていますが、claims プロパティは幾つかのフォーマットの場合のみ存在します。


2.9. クレデンシャル発行の詳細

これまでの説明でアクセストークン発行の詳細を見てきました。次はクレデンシャル発行の詳細について見ていきます。


2.9.1. 鍵証明

『クレデンシャル検証』の節で説明したとおり、キーバインディング可能な VC を取得したいのであれば、ウォレットは鍵証明を提供することになります。

OID4VCI 仕様では、鍵証明のために三つの具体的なフォーマットが定義されています。 それらのフォーマットはそれぞれ、JWT (RFC 7519)、CWT (RFC 8392) 及び Data Integrity (Data Integrity) に基づいています。 将来必要になった際には、鍵証明フォーマットが追加されることもありえます。


2.9.1.1. 鍵証明 JWT

定義により、鍵証明は公開鍵またはその鍵への参照を含んでいます。JWT に基づく鍵証明の場合、鍵情報を含めるために次に挙げる複数の方法を利用できます。鍵証明 JWT は、これらのうちの一つだけを使わなければなりません。

  1. jwk ヘッダパラメーター (RFC 7515, 4.1.3)
  2. x5c ヘッダパラメーター (RFC 7515, 4.1.6)
  3. kid ヘッダパラメーター (RFC 7515, 4.1.4)

これらの方法はいずれも、鍵証明 JWT のヘッダに鍵情報を埋め込みます。

jwk ヘッダパラメーターを使用する場合、公開鍵は JWK (RFC 7517 JSON Web Key (JWK)) 形式で埋め込まれます。

jwk ヘッダパラメーターの値は公開鍵を表す JSON オブジェクトです。

鍵証明 JWT 自身は、その公開鍵に対応する秘密鍵で署名されなければなりません。

x5c ヘッダパラメーターを使用する場合、公開鍵用の X.509 証明書を用意する必要があります。その証明書の DER 表現を base64 でエンコードしたものを x5c JSON 配列の一番目の要素として含めなければなりません。証明書チェーンも用意できるのであれば、証明書と一緒に含めることもできます。x5c パラメーターが期待しているフォーマットの詳細については RFC 7515, 4.1.6. “x5c” (X.509 Certificate Chain) Header Parameter を参照してください。

kid ヘッダパラメーターを使用する場合、その値は公開鍵へと解決可能な DID URL であるべきです。

次に、鍵証明 JWT のペイロード部を見ていきましょう。

次の表は鍵証明 JWT のペイロード部のクレーム群を列挙しています。

名前 要否 説明
iss 条件により必須 クライアントアプリケーション (ウォレット) の識別子。
aud 必須 クレデンシャルイシュアの識別子。
iat 必須 発行日時
nonce 条件により必須 サーバが提供した c_nonce
  • iss クレームはクライアントアプリケーション (ウォレット) の識別子を表しており、ほとんどの場合は必須です。唯一の例外は、アクセストークンが事前認可コードフローで発行され、そのアクセストークン用のトークンリクエストがクライアントアプリケーションを特定する情報を含んでいない場合です。そのようなトークンリクエストは、サーバが事前認可コードフローにおいて匿名アクセスを許可している場合に受け入れられます。この機能を認可サーバがサポートしているかどうかは、真偽値サーバメタデータである pre-authorized_grant_anonymous_access_supported により示されます。

  • aud クレームはクレデンシャルイシュアの識別子を表しており、常に必須です。

  • iat クレームは鍵証明 JWT の発行日時を表しており、詳細な定義は RFC 7519, 4.1.6. “iat” (Issued At) Claim にあります。

  • nonce クレームは、トークンレスポンスまたはクレデンシャルレスポンスに含まれる c_nonce に対応します。トークンレスポンスが c_nonce パラメーターを含んでいる場合、nonce クレームは必須です。また、トークンレスポンスが c_nonce パラメーターを含んでいない場合でも、クレデンシャルイシュアは nonce クレームを要求するかもしれません。c_nonce の詳細は後ほど説明します。

次の表は鍵証明 JWT に対する要求事項の要約です。

鍵証明 JWT 場所 名前 要否 説明
ヘッダ alg 必須 JWT 仕様 (RFC 7519) で要求されている通り。
typ 必須 openid4vci-proof+jwt
jwk 条件により必須 これらのヘッダパラメーター群のどれか一つを含めなければなりません。 公開鍵、または公開鍵への参照を表しています。
x5c
kid
ペイロード iss 条件により必須 クライアントアプリケーション (ウォレット) の識別子。
aud 必須 クレデンシャルイシュアの識別子。
iat 必須 発行日時
nonce 条件により必須 サーバが提供した c_nonce

下記は鍵証明 JWT の例です。

eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiJHVURvZFB1SURJYllocmdmMHZsT3RNd1otczNiaVpFT3hWMFRTRjBKN3R3IiwieCI6InJjdU1FT1BYbVBJRlotc0Jvbkxyb1VvaTVYdGZ4NktWeFlFR09YMi1UbGsiLCJ5IjoiNUw1SUZrUFpNT0doTVpsNHRaSk9ISjdtckZQbnJSeV9RSURUOXRWZF9obyIsImFsZyI6IkVTMjU2In19.eyJpc3MiOiJodHRwczovL3dhbGxldC5leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwiaWF0IjoxNjk3MjM0NzcwLCJub25jZSI6IjZhMzA3YjU1LWM4ZTEtNDg4YS05NjFlLTI1MzQ4ZmYzZTlkYSJ9.Vvo_X_fZyanUZ-y5X0yYtY7d70bbjMKUKqAoDiCBmP3NT4xNfTEpuYl9eu7vxc2fLf67ZdbSfw4rwEp8qvvWpA

鍵証明 JWT の例のヘッダとペイロードをデコードすると、それぞれ次の JSON になります。

{
  "alg": "ES256",
  "typ": "openid4vci-proof+jwt",
  "jwk": {
    "kty": "EC",
    "alg": "ES256",
    "crv": "P-256",
    "x":   "rcuMEOPXmPIFZ-sBonLroUoi5Xtfx6KVxYEGOX2-Tlk",
    "y":   "5L5IFkPZMOGhMZl4tZJOHJ7mrFPnrRy_QIDT9tVd_ho",
    "kid": "GUDodPuIDIbYhrgf0vlOtMwZ-s3biZEOxV0TSF0J7tw"
  }
}
{
  "iss": "https://wallet.example.com",
  "aud": "https://issuer.example.com",
  "iat": 1697234770,
  "nonce": "6a307b55-c8e1-488a-961e-25348ff3e9da"
}
2.9.1.2. その他の鍵証明

この文書では CWT に基づく鍵証明など、他の鍵証明の説明はしません。 それらについては OID4VCI 仕様を参照してください。


2.9.2. c_nonce

鍵証明リプレイへの主要な対抗策として、クレデンシャルイシュアは鍵証明に nonce を含めることを要求するかもしれません。このクレームの値は、認可サーバまたはクレデンシャルイシュアから c_nonce レスポンスパラメーターとして提供されます。

認可サーバからのトークンレスポンスには c_nonce および c_nonce_expires_in レスポンスパラメーターが含まれるかもしれません。c_nonce_expires_inc_nonce の有効期間の秒数を表しています。

ウォレットは、その c_nonce レスポンスパラメーターの値を鍵証明 JWT の nonce の値として使用します。

ウォレットはその鍵証明 JWT をクレデンシャルリクエストに含めます。

クレデンシャルイシュアが要求しているにも関わらず nonce クレームが含まれていない場合、または、指定された nonce 値の期限が切れている場合、クレデンシャルエンドポイントはエラー応答を返します。そのエラー応答は、期待される c_nonce 値、または、新しい c_nonce 値のどちらかを含んでいます。また、nonce 値が有効な場合でも、クレデンシャルレスポンスは将来の利用を想定して c_nonce を含んでいるかもしれません。いずれの場合も、クレデンシャルイシュアが nonce を鍵証明に含めることを要求する場合、クレデンシャルレスポンスに c_nonce が含まれます。

必要に応じ、ウォレットはクレデンシャルエンドポイントから提供された c_nonce を用いて新しい鍵証明を再生成し、その新しい鍵証明を添えて再度クレデンシャルリクエストを実行できます。

次の図は c_nonce の概要を示しています。

2.9.3. クレデンシャルリクエスト

クレデンシャルリクエストは、アクセストークンと JSON 形式のペイロードを含む HTTP POST リクエストです。ペイロードにはクレデンシャル情報および任意の鍵証明が含まれます。

2.9.3.1. クレデンシャルリクエスト内のクレデンシャル情報

クレデンシャルリクエスト内のクレデンシャル情報は、必須の "format" プロパティと、フォーマット固有のプロパティを含んでいます。例えば、"format" プロパティの値が "jwt_vc_json" であれば、"credential_definition" プロパティがあるものと期待されます。

クレデンシャル情報は、ウォレットが入手したい VC について記述したものです。OID4VCI 仕様から抜粋した上記の図内の例で示唆されているように、クレデンシャルリクエスト内のクレデンシャル情報は、アクセストークンに紐付いている発行可能クレデンシャルを指定する以上のことをおこなっています。例えば、クレデンシャルリクエストの例の意図は、given_name クレーム、family_name クレーム、degree クレームのみを含む jwt_vc_json フォーマットの VC を要求するというものです。

しかし、幾つかの問題があります。

  1. 指定された条件を満たす発行可能クレデンシャルを決めるのは簡単ではない。

  2. 複数の発行可能クレデンシャルが条件を満たす可能性がある。

  3. 条件のわずかな違いで、異なる発行可能クレデンシャルが選ばれる可能性がある。

  4. 提示されたアクセストークンが指定された条件を満たす VC を要求する権限を持っているか確認するのは簡単ではない。

簡単に言うと、この仕様は実装に対する配慮が欠けています。 Issue 175 で報告されている問題は、 この仕様の欠陥により引き起こされる問題の一例です。 ですので、仕様が改善されない限り、「クレデンシャルイシュアは、 実行時に (アクセストークンリクエストやクレデンシャルリクエストで) 指定される詳細条件を無視し、構造が固定された VC を発行する」というものになる可能性が高いです。


2.9.3.2. クレデンシャルリクエスト内の鍵証明情報

クレデンシャルリクエスト内の鍵証明情報は "proof" プロパティにより表されます。プロパティの値は JSON オブジェクトです。

"proof" オブジェクトには、鍵証明のフォーマットを示す "proof_type" プロパティが必ず含まれます。

"proof_type" プロパティの値が "jwt" の場合、鍵証明として JWT が使用されます。この場合、"proof" オブジェクトには "jwt" プロパティが含まれます。"jwt" プロパティの値は、鍵証明 JWT 仕様に準拠する JWT です。

次の図はクレデンシャルリクエストの概要を示しています。

2.9.4. クレデンシャルレスポンス

クレデンシャルレスポンスは JSON を含む HTTP レスポンスです。


2.9.4.1. VC を含むクレデンシャルレスポンス

VC の発行に成功した場合、VC は "credential" プロパティの値として JSON に含まれます。"credential" プロパティの値のフォーマットは、VC のフォーマットに依存します。

例えば、SD-JWT VC に準拠する SD-JWT ベースの VC であれば、"credential" プロパティの値は SD-JWT フォーマットの JSON 文字列です。

加えて、先に説明したように、クレデンシャルレスポンスには c_nonce レスポンスパラメーターと c_nonce_expires_in レスポンスパラメーターが含まれることがあります。

ここで SD-JWT ベースの VC の詳細について見ていきましょう。

SD-JWT は、クレデンシャルイシュアが署名した JWT と、0 個以上のディスクロージャ、そして、任意のキーバインディング JWT で構成されます。これらの構成要素は、チルダ (~) を区切り文字として連結されています。なお、キーバインディング JWT はウォレットが生成するので、クレデンシャルイシュアが発行したときには VC はキーバインディング JWT を含んでいないので注意してください。

<クレデンシャルイシュアが署名したJWT>~<ディスクロージャ1>...<ディスクロージャN>~

SD-JWT の最初の構成要素はクレデンシャルイシュアが署名した JWT です。標準 JWT として、クレデンシャルイシュアが署名した JWT のヘッダとペイロードは base64url でデコードできます。

ここで重要な点は次の通りです。

  1. typ ヘッダパラメーターの値は "vc+sd-jwt" である。
  2. ペイロードにはキーバインディングのために "cnf"."jwk" が含まれている。
  3. ペイロードには "_sd_alg" プロパティが含まれており、これはディスクロージャ用のハッシュアルゴリズムを示している。
  4. ペイロードには "given_name" のようなユーザクレームが含まれていない。代わりに "_sd" 配列があり、ユーザクレームのディスクロージャのダイジェスト値を保持している。

SD-JWT ベースの VC の例には、四つのディスクロージャが含まれています。

ディスクロージャを base64url でデコードすることにより、元の JSON 配列が得られます。

ディスクロージャのダイジェスト値は "_sd_alg" プロパティで示されるハッシュアルゴリズムを用いて計算され、"_sd" 配列に列挙されます。ダイジェスト値の順番は、SD-JWT 内のディスクロージャの順番とは独立していなければなりません。この例では、ダイジェスト値は ASCII コードの順番で並べられています。

次の図は、SD-JWT ベースの VC を含むクレデンシャルレスポンスの概要を示しています。

2.9.4.2. トランザクション ID を含むクレデンシャルレスポンス

要求された VC が用意できていない場合、クレデンシャルエンドポイントは VC の代わりにトランザクション ID を返します。トランザクション ID は "transaction_id" レスポンスパラメーターの値としてクレデンシャルレスポンスに含まれます。次のものは OID4VCI 仕様から抜粋した例です。

HTTP/1.1 202 Accepted
Content-Type: application/json
Cache-Control: no-store

{
  "transaction_id": "8xLOxBtZp8",
  "c_nonce": "wlbQc6pCJp",
  "c_nonce_expires_in": 86400
}

トランザクション ID は、後ほどウォレットがクレデンシャルイシュアの遅延クレデンシャルエンドポイントに遅延クレデンシャルリクエストを送る際に使用されます。


2.9.4.3. エラーを含むクレデンシャルレスポンス

クレデンシャルリクエストを成功裡に処理できない場合、クレデンシャルエンドポイントはエラー応答を返します。エラーの種別は "error" レスポンスパラメーターで示されます。次のものは仕様から抜粋したエラーの例です。

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store

{
   "error": "invalid_request"
}

必要な鍵証明が含まれていなかったり、nonce クレームの欠落や期限切れなどの理由により鍵証明が正しくない場合、エラーコード "invalid_proof" が使われます。そのような場合のエラー応答の例を仕様から転載します。

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store

{
  "error": "invalid_proof",
  "error_description":
    "Credential Issuer requires key proof to be bound to a Credential Issuer provided nonce.",
  "c_nonce": "8YE9hCnyV2",
  "c_nonce_expires_in": 86400
}

2.9.5. 遅延クレデンシャルリクエスト

ウォレットは、トランザクション ID を使い遅延クレデンシャルエンドポイントにリクエストを送ることができます。このリクエストは JSON を含む HTTP POST リクエストで、その JSON 内の "transaction_id" プロパティがトランザクション ID の値を保持しています。

POST /deferred_credential HTTP/1.1
Host: issuer.example.com
Content-Type: application/json
Authorization: Bearer czZCaGRSa3F0MzpnWDFmQmF0M2JW

{
   "transaction_id": "8xLOxBtZp8"
}

2.9.6. 遅延クレデンシャルレスポンス

遅延クレデンシャルエンドポイントは JSON を含む HTTP レスポンスを返します。 VC が成功裡に発行されていれば、その JSON は "credential" プロパティを含みます。 このプロパティの値が発行された VC です。

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "credential": "eyJ......CJd~"
}

発行がうまくいかない場合、"error" パラメーターを含むエラー応答が返されます。特に、要求された VC がまだ用意できていない場合はエラーコード "issuance_pending" が使われます。

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store

{
   "error": "issuance_pending"
}

2.9.7. 一括クレデンシャルリクエスト

クレデンシャルイシュアの一括クレデンシャルエンドポイントに一括クレデンシャルリクエストを送ることで、ウォレットは一度に複数の VC を要求することができます。

一括クレデンシャルリクエストは JSON を含む HTTP POST リクエストで、"credential_requests" JSON 配列を含んでいます。配列は JSON オブジェクトのリストで、一つ一つがクレデンシャルリクエストを表しています。

各クレデンシャルリクエストはクレデンシャル情報と任意の鍵証明を含んでいます。

一括クレデンシャルリクエスト内のクレデンシャルリクエスト群が異なるクレデンシャルフォーマット、異なる鍵証明を持つことは許されています。逆もまた真です。クレデンシャルリクエスト群は同じクレデンシャルフォーマット、同じ鍵証明を持っていてもかまいません。

次の図は一括クレデンシャルリクエストの概要を示しています。

2.9.8. 一括クレデンシャルレスポンス

一括クレデンシャルレスポンスは JSON を含む HTTP レスポンスで、"credential_responses" JSON 配列を含んでいます。配列は JSON オブジェクトのリストで、一つ一つがクレデンシャルレスポンスを表しています。配列の要素は、一括クレデンシャルリクエストの "credential_requests" 配列の要素に対応しています。

各クレデンシャルレスポンスは VC またはトランザクション ID を含んでいます。

加えて、将来ウォレットが鍵証明付きのクレデンシャルリクエストまたは一括クレデンシャルリクエストを送るときのため、一括クレデンシャルレスポンスは c_noncec_nonce_expires_in をトップレベルプロパティとして含んでいるかもしれません。

次の図は一括クレデンシャルレスポンスの概要を示しています。

2.10. 公開鍵配布

VC や VP の署名を検証するため、ベリファイアはクレデンシャルイシュアが VC に署名する際に使った秘密鍵に対応する公開鍵を取得する必要があります。

VC の署名を検証するための公開鍵を配布する方法は OIDC4VCI 仕様の範囲外の事項です。しかし、ここで幾つか提案されている手法について見ていきましょう。


2.10.1. X.509 証明書の埋め込み

公開鍵配布の一つの方法として、VC 内に公開鍵用の X.509 証明書を埋め込む方法があります。

JWT ベースの VC の場合、その目的のために "x5c" ヘッダパラメーター (RFC 7515, 4.1.6) が使われることになるでしょう。


2.10.2. エンティティコンフィギュレーションに埋め込み

OpenID Federation 仕様を活用する方法では、クレデンシャルイシュアのエンティティコンフィギュレーションに公開鍵を埋め込みます。

イタリアのエコシステムでは、クレデンシャルイシュアを表す新しいエンティティタイプ識別子として openid_credential_issuer を定義し、その "jwks" メタデータをクレデンシャルイシュアの公開鍵を置く場所として使っています。

2.10.3. jwt-vc-issuer

/.well-known/jwt-vc-issuer も公開鍵配布方法として提案されています。 このウェルノーンパスは、公開鍵を探す開始点となることを意図しています。

このウェルノーンパスは、JWT VC 発行者のメタデータを含む JSON を返します。 JSON 内の "jwks_uri" プロパティが、発行者の JWK Set の場所を示しています。 ベリファイアはその JWK Set 内に目当ての公開鍵を見つけることができるでしょう。

2.11. 仕様の要約

OID4VCI 仕様は VC 発行の規則を定義しています。仕様の二つの主要議題は『アクセストークン発行』と『クレデンシャル発行』です。

アクセストークン発行のため、仕様は発行可能クレデンシャルを指定する方法を幾つか定義しており、これには (1) クレデンシャルオファー内の事前認可コードを使う方法、 (2) クレデンシャルオファー内のイシュアステートを使う方法、 (3) "type":"openid_credential" を持つ RAR オブジェクトを使う方法、 (4) credential_configurations_supported メタデータ内のエントリを参照する scope 値を使う方法、が含まれます。

クレデンシャル発行のため、仕様は三つのエンドポイント、すなわち、(1) クレデンシャルエンドポイント、(2) 一括クレデンシャルエンドポイント、(3) 遅延クレデンシャルエンドポイント、を導入しています。

クレデンシャル情報が幾つかの場所に現れます。その場所とは、 (1) credential_configurations_supported メタデータ、(2) RAR オブジェクト、(3) クレデンシャルリクエスト、(4) 一括クレデンシャルリクエスト、です。 これらの間の一貫性や一意識別性が欠けているため、仕様は意図した目的を完全には達成できないかもしれません。 しかし、完全な相互運用性は犠牲になるものの、実世界のエコシステムは、それぞれ補助的な仕様で補うことで、彼らの個別のニーズを満たす VC を発行することができるでしょう。

仕様は VC フォーマットの詳細には立ち入りませんが、 jwt_vc_jsonjwt_vc_json-ldldp_vcmso_mdocvc+sd-jwt に関連する規則を取り決めました。これらの中で、最近最も注目を集めているのは SD-JWT VC と mdoc (ISO/IEC 18013-5:2021) です。 eIDAS 2.0 は SD-JWT ベースと mdoc ベースのフォーマットのサポートを必須としています。

公開鍵配布方法も仕様の対象外です。公開鍵配布の方法として、(1) X.509 証明書を VC 自身に埋め込む方法、 (2) クレデンシャルイシュアのエンティティコンフィギュレーション内の “openid_credential_issuer”.“jwks” を使う方法、 (3) /.well-known/jwt-vc-issuer を使う方法、が提案されています。

OID4VCI 仕様には依然として改善の余地がありますが、実世界のエコシステムは実用的な妥協と補助的仕様により OID4VCI 仕様を補足することで、それぞれの特定のニーズに対応することができるでしょう。

3. OID4VCI 実装

3.1. Authlete 概要

開発者は、OID4VCI 仕様に準拠した自身のクレデンシャルイシュアおよび認可サーバ・OpenID プロバイダを Authlete を利用して作成することができます。

ほとんどのベンダーは認可サーバなどのフロントエンドサーバの実装を直接提供していますが、Authlete は異なるアプローチを取っています。Authlete は、開発者自身がフロントエンドサーバを実装するのに使うことのできる Web API のセットを提供します。Authlete はフロントエンドサーバ群の後ろに配置され、エンドユーザからは見えません。

Authlete のアーキテクチャは必然的に開発者にフロントエンドサーバの作成を要求しますが、その代わりに開発者は以下の利点を得られます。

  1. 任意の技術コンポーネントを開発者が選べる
    • ユーザ認証方法
    • ユーザ管理システム
    • API ゲートウェイ
    • プログラミング言語
    • Web フレームワーク
    • クラウドサービス
  2. ユーザデータに対する完全な制御
    • ユーザデータを OAuth/OIDC ベンダーのサーバにアップロードする必要はありません。
    • ユーザデータの保護に関するさまざまな規制への対応。
  3. エンドユーザ向けフロントエンドサーバに対する完全な制御
    • UI/UX のすべての側面にわたる企業ブランドの管理。
  4. システム設計における適切なレイヤ分離の強制
    • API 認可がユーザ管理・ユーザ認証から分離される。
    • OAuth/OIDC プロトコル処理が API ゲートウェイやフロントエンドサーバから分離される。

3.2. Authlete 設定

3.2.1. Authlete バージョン

OID4VCI 仕様は、2024 年 4 月頃リリース予定の Authlete 3.0 からサポートされます。

それまでの間、お客様とビジネスパートナー向けにトライアルサーバが利用可能です。OID4VCI の試験利用にご興味のある方はお問い合わせください。

3.2.2. Authlete サーバ設定

Authlete サーバで『Verifiable Credentials』機能を有効にしておく必要があります。オンプレミス版の Authlete を利用されている場合は、この機能を有効にするために設定ファイル (authlete-server.properties) 内に次の行が含まれていることを確認してください。

feature.verifiable_credentials.enabled=true

3.2.3. Authlete サービス設定

プロパティ 説明
verifiableCredentialsEnabled 真偽値 OID4VCI 仕様のサポートなどの Verifiable Credentials 関連機能の有効化・無効化を制御するフラグです。
credentialIssuerMetadata credentialIssuer 文字列 このサービスがクレデンシャルイシュアとして動作する際のクレデンシャルイシュア識別子。 このプロパティは OID4VCI で定義されている credential_issuer メタデータに対応します。

値は、スキーム https、クエリー部無し、フラグメント部無し、の有効な URL でなければなりません。 また、Authlete 固有の制限として、アスキー文字のみ、最大長 200 という制限があります。

クレデンシャルイシュアとして動作するためにはこのプロパティを設定しなければなりません。
authorizationServers 文字列の配列 このサービスがクレデンシャルイシュアとして動作する際に認可処理を任せる認可サーバ群の識別子群。 このプロパティは OID4VCI で定義されている authorization_servers メタデータに対応します。

値は HTTP でアクセス可能な URL でなければなりません。
credentialEndpoint 文字列 このサービスがクレデンシャルイシュアとして動作する際のクレデンシャルエンドポイントの URL。 このプロパティは OID4VCI で定義されている credential_endpoint メタデータに対応します。

値は、スキーム https、フラグメント部無し、の有効な URL でなければなりません。 また、Authlete 固有の制限として、アスキー文字のみ、最大長 200 という制限があります。

クレデンシャルイシュアとして動作するためにはこのプロパティを設定しなければなりません。
batchCredentialEndpoint 文字列 このサービスがクレデンシャルイシュアとして動作する際の一括クレデンシャルエンドポイントの URL。 このプロパティは OID4VCI で定義されている batch_credential_endpoint メタデータに対応します。

値は、スキーム https、フラグメント部無し、の有効な URL でなければなりません。 また、Authlete 固有の制限として、アスキー文字のみ、最大長 200 という制限があります。

バッチクレデンシャルエンドポイントを実装するかどうかは任意です。
deferredCredentialEndpoint 文字列 このサービスがクレデンシャルイシュアとして動作する際の遅延クレデンシャルエンドポイントの URL。 このプロパティは OID4VCI 仕様で定義されている deferred_credential_endpoint メタデータに対応します。

値は、スキーム https、フラグメント部無し、の有効な URL でなければなりません。 また、Authlete 固有の制限として、アスキー文字のみ、最大長 200 という制限があります。

クレデンシャルイシュアのクレデンシャルエンドポイントまたは一括クレデンシャルエンドポイントがトランザクション ID を発行することがある場合、遅延クレデンシャルエンドポイントを実装しなければなりません。
credentialResponseEncryptionAlgValuesSupported 文字列の配列 クレデンシャルレスポンス暗号化においてサポートされる JWE alg アルゴリズム群。 このプロパティは OID4VCI 仕様で定義されている credential_response_encryption.alg_values_supported メタデータに対応します。

有効な値は、"ECDH_ES" 等、 JWEAlg 列挙型の値です。非対称鍵系アルゴリズムのみ指定可能です。
credentialResponseEncryptionEncValuesSupported 文字列の配列 クレデンシャルレスポンス暗号化においてサポートされる JWE enc アルゴリズム群。 このプロパティは OID4VCI 仕様で定義されている credential_response_encryption.enc_values_supported メタデータに対応します。

有効な値は、"A256GCM" 等、 JWEEnc 列挙型の値です。
requireCredentialEncryptionResponse 真偽値 クレデンシャルレスポンスを常に暗号化するかどうかを示すフラグです。 このプロパティは OID4VCI 仕様で定義されている credential_response_encryption.encryption_required メタデータに対応します。

このプロパティが真に設定されている場合、全てのクレデンシャルリクエストは、 credential_response_encryption JSON オブジェクトを含まなければなりません。
credentialsSupported 文字列 このサービスがクレデンシャルイシュアとして動作する際にサポートするクレデンシャル群。 このプロパティは OID4VCI 仕様で定義されている credential_configurations_supported メタデータに対応します。

値は JSON オブジェクトでなければなりません。非アスキー文字を含めることもできますが、 Authlete 固有の制限として、最大文字数は 16383 に制限されます。

クレデンシャルイシュアとして動作するためには、このプロパティを設定しなければなりません。

後方互換性のため、このプロパティの名前は credentialConfigurationsSupported には変更されず、 credentialsSupported のままとなります。
credentialOfferDuration 数値 クレデンシャルオファーの有効時間のデフォルト値を秒数で表すプロパティです。

/vci/offer/create API への API コールが duration リクエストパラメーターを含んでいないか、またはその値が 0 以下の場合、このプロパティの値がデフォルト値として使われます。

このプロパティの値が 0 以下の場合、Authlete サーバ単位で設定されているデフォルト値が使われます。
preAuthorizedGrantAnonymousAccessSupported 真偽値 このプロパティは、事前認可コードフローにおいて匿名クライアントからのトークンリクエストを許可するかどうかを示しています。

このプロパティは OID4VCI 仕様で定義されている pre-authorized_grant_anonymous_access_supported メタデータに対応します。
cnonceDuration 数値 c_nonce 有効時間を秒数で表すプロパティです。

認可サーバのトークンエンドポイントが Verifiable Credential 発行に使用できるアクセストークンを発行する際、アクセストークンと同時に c_nonce を発行します。 また、クレデンシャルイシュアのクレデンシャルエンドポイントとバッチクレデンシャルエンドポイントは、提示された c_nonce が有効期限切れの場合、新しい c_nonce を発行します。 このプロパティはそれらの c_nonce の有効時間として用いられます。

このプロパティの値が 0 以下の場合、Authlete サーバ単位で設定されているデフォルト値が使われます。
credentialTransactionDuration 数値 クレデンシャルリクエストまたは一括クレデンシャルリクエストの結果として発行されるトランザクション ID の有効時間のデフォルト値を秒数で表すプロパティです。

このプロパティの値が 0 以下の場合、Authlete サーバ単位で設定されているデフォルト値が使われます。
credentialDuration 数値 Verifiable Credential のデフォルト有効時間を秒数で表すプロパティです。

/vci/single/issue API や /vci/batch/issue API などの Authlete API は Verifiable Credential を発行します。このプロパティは、それらの Verifiable Credential のデフォルト有効時間を指定します。

値 0 は Verifiable Credential が期限切れしないことを示します。この場合、Verifiable Credential は有効期間を示すプロパティを持ちません。たとえば、JWT ベースの Verifiable Credential であれば、exp クレーム (RFC 7519, Section 4.1.4) を持たないでしょう。

Verifiable Credential を発行する Authlete API は、有効時間を上書きするリクエストパラメーターを認識します。 例えば、/vci/single/issue API のリクエストには order オブジェクトが含まれており、そのオブジェクトの credentialDuration パラメーターでデフォルト有効時間を上書きできます。
credentialJwks 文字列 Verifiable Credential に署名するための秘密鍵を含む JWK セット文書。

/vci/single/issue API や /vci/batch/issue API などの Authlete API は Verifiable Credential を発行します。 このプロパティの内容はこれらの API により参照されます。

Verifiable Credential を発行する可能性のある Authlete API は署名に使う秘密鍵の鍵 ID を指定するリクエストパラメーターを認識します。 例えば、/vci/single/issue API のリクエストには order オブジェクトが含まれており、そのオブジェクトの signingKeyId パラメーターで署名に使う秘密鍵の鍵 ID を指定することができます。 鍵 ID が指定されていないとき、Authlete は自動的に秘密鍵を選択します。

この credentialJwks プロパティが更新される際に JWK セット文書内の JWK 群が kid プロパティ (RFC 7517, Section 4.5) を含んでいない場合、Authlete は自動的に kid プロパティを挿入します。 SHA-256 ハッシュアルゴリズムを用いて計算した JWK Thumbprint (RFC 7638) が kid プロパティの値として使われます。
credentialJwksUri 文字列 クレデンシャルイシュアの JWK セット文書を公開する場所を指す URL。

この URL は、JWT イシュアメタデータの jwks_uri プロパティーの値として使用されます。 メタデータそのものは /.well-known/jwt-issuer で公開されます。 JWT イシュアメタデータの詳細については SD-JWT-based Verifiable Credentials (SD-JWT VC) を参照してください。

3.3. Authlete APIs

3.3.1. OID4VCI 用 Authlete API の全体像

以下の図は、フロントエンドサーバ(クレデンシャルイシュアおよび認可サーバ)のエンドポイントと Authlete API の関係を示しています。Authlete API の詳細については以降の節で説明します。

3.3.2. Authlete API コール

Authlete 2.x と Authlete 3.0 の大きな違いの一つに、Authlete API の呼び出し方の違いがあります。

Authlete 2.x およびそれ以前のバージョンでは、API キーと API シークレットの組 (例えば、サービス API キーとサービス API シークレット) を用いて Authlete API をコールします。 一方、Authlete 3.0 では、アクセストークンを用いて Authlete API をコールします。

開発者は、以前の物とはかなり異なる新しいウェブコンソールを使い、Authlete API 用のアクセストークンを取得できます。 Authlete 2.x およびそれ以前のバージョンでは、二つの別々のウェブコンソール、すなわち、 (認可サーバや OpenID プロバイダに対応するサービスを管理するための) サービスオーナーコンソールと (クライアントアプリケーションを管理するための) デベロッパーコンソールがありました。 しかし、Authlete 3.0 では、提供されるウェブコンソールは一つだけであり、その表示と機能は提示されたアクセストークンの権限に従って変化します。

Authlete 2.x Authlete 3.0
保護 API キー & API シークレット アクセストークン
ウェブコンソール サービスオーナーコンソールとデベロッパーコンソール 単一のコンソール

Authlete API のパス部分にも違いがあります。Authlete 3.0 では、ほとんどの Authlete API は、/api/{サービスID}/auth/authorization のように、パス部にサービス ID を含んでいます。 ここで {サービスID} はサービスの識別子 (つまり Authlete 2.x におけるサービス API キー) です。

Authlete バージョン API パスの例
Authlete 2.x /api/auth/authorization
Authlete 3.0 /api/{サービスID}/auth/authorization

これらの変更は小さくないですが、差異をライブラリレイヤで吸収することにより、プログラムへの影響は最小化できます。 例えば、Java で書かれたサンプル認可サーバ (authlete/java-oauth-server) を使っていて、Authlete 2.x から Authlete 3.0 へ移行する開発者は、設定ファイル (authlete.properties) の内容を

# Authlete 2.x 用
base_url = ...
service.api_key = ..
service.api_secret = ...

から

# Authlete 3.0 用
api_version = V3
base_url = ...
service.api_key = ...
service.access_token = ...

に変更するだけで済みます。


3.4. クレデンシャルオファー発行

既に述べたように、クレデンシャルオファー発行処理はクレデンシャルイシュアによって異なります。

例えば、Webブラウザ経由でユーザとやりとりした後、クレデンシャルイシュアは次のような QR コードを発行するかもしれません。

この QR コードは “openid-credential-offer://?credential_offer={CredentialOffer}” を表していて、{CredentialOffer} の部分が下記のクレデンシャルオファーを保持しています。

{
  "credential_issuer": "https://trial.authlete.net",
  "credential_configuration_ids": [
    "IdentityCredential",
    "org.iso.18013.5.1.mDL"
  ],
  "grants": {
    "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
      "pre-authorized_code": "NH9udMon5pTuuvbsNsHUNWf8tpU__9wt-gsO9LeYthc"
    }
  }
}

代わりにクレデンシャルオファーは次のようなハイパーリンクを表示するかもしれません。

  • openid-credential-offer://?credential_offer_uri={CredentialOfferUri}

ここで {CredentialOfferUri}https%3A%2F%2Ftrial.authlete.net%2Fapi%2Foffer%2FTctoiNm9lYASTBT6XRGb8RQsrClKczCxDtqLY1jLvpk のような URL エンコードされた URL です。


3.4.1. /vci/offer/create API

いずれにしても、クレデンシャルオファーをサポートするクレデンシャルイシュアは、クレデンシャルオファーを作成できなければなりません。 この機能のため、Authlete は /vci/offer/create API を提供します。次の表は当 API の要約です。

/vci/offer/create API へのリクエスト
HTTP メソッドと
Content-Type
GET (クエリパラメーター)
POST application/json
POST application/x-www-form-urlencoded
リクエストパラメーター credentialConfigurationIds 文字列の配列で、クレデンシャルオファーの "credential_configuration_ids" プロパティの値として用いられます。このリクエストパラメーターは必須です。
authorizationCodeGrantIncluded "grants" オブジェクト内に "authorization_code" オブジェクトを含めるかどうかを示す真偽値 (true または false) です。
issuerStateIncluded "grants" オブジェクト内の "authorization_code" オブジェクトに "issuer_state" プロパティを含めるかを示す真偽値 (true または false) です。

このパラメーターが true の場合、Authlete はイシュアステートを生成し、 "authorization_code" オブジェクト内に "issuer_state" プロパティの値として配置します。
preAuthorizedCodeGrantIncluded "grants" オブジェクト内に "urn:ietf:params:oauth:grant-type:pre-authorized_code" オブジェクトを含めるかどうかを示す真偽値 (true または false) です。

このパラメーターが true の場合、Authlete は事前認可コードを生成し、 "urn:ietf:params:oauth:grant-type:pre-authorized_code" オブジェクト内に "pre-authorized_code" プロパティの値として配置します。
txCode 事前認可コードに紐づけるトランザクションコードです。 このパラメーターが空でなければ "urn:ietf:params:oauth:grant-type:pre-authorized_code" オブジェクトに tx_code オブジェクトが埋め込まれます。 結果として、事前認可コードを用いるトークンリクエストは、このパラメーターで指定された値を tx_code リクエストパラメーターの値として使わなければなりません。
txCodeInputMode トランザクションコードの入力モードです。このパラメーターで指定された値は tx_code オブジェクト内の input_mode プロパティの値として用いられます。

OID4VCI 仕様で事前定義された値は "numeric""text" のみですが、将来の拡張性のため、/vci/offer/create API は事前定義されたもの以外の値も受け付けます。
txCodeDescription トランザクションコードの説明です。このパラメーターで指定された値は tx_code オブジェクト内の description プロパティの値として用いられます。
subject クレデンシャルオファーに紐付けるユーザのサブジェクト (一意識別子) です。

このパラメーターは必須です。
duration クレデンシャルオファーの有効秒数です。

このパラメーターが 0 より大きい場合、その値が発行されるクレデンシャルオファーの有効時間として用いられます。 それ以外の場合、サービスの credentialOfferDuration プロパティの値が用いられます。
context クレデンシャルオファーに紐付ける、用途制限のない任意の文字列です。

開発者は好きなようにこのパラメーターを利用できます。 Authlete はこのパラメーターの内容について関知しません。
properties クレデンシャルオファーに紐付けるエクストラプロパティで、用途制限のないキー・バリューの組です。

エクストラプロパティは、最終的にクレデンシャルオファーを使って作成されるアクセストークンに紐付けられます。
jwtAtClaims JWT アクセストークンのペイロード部に追加するクレーム群を JSON オブジェクト形式で表したものです。

このパラメーターは、サービスが発行するアクセストークンの形式が JWT の場合のみ意味があります。 別の言い方をすると、サービスの accessTokenSignAlg プロパティの値が設定されている場合のみ意味があります。

追加クレームは、最終的にクレデンシャルオファーを使って作成されるアクセストークンに紐付けられます。
authTime クレデンシャルオファーを発行する過程で行われたユーザ認証が実行された時刻。

時刻は Unix エポックからの経過秒数で表されます。
acr クレデンシャルオファーを発行する過程で行われたユーザ認証の認証コンテキストクラス参照。

例えば、次のコマンドラインでクレデンシャルオファーが生成されます。

$ BASE_URL=https://nextdev-api.authlete.net
$ SERVICE_ID=986126671
$ ACCESS_TOKEN=${YOUR_ACCESS_TOKEN}
$ curl -s ${BASE_URL}/api/${SERVICE_ID}/vci/offer/create \
    -H "Authorization: Bearer ${ACCESS_TOKEN}" \
    -H "Content-Type: application/json" \
    --data '
{
  "credentialConfigurationIds": [ "IdentityCredential" ],
  "preAuthorizedCodeGrantIncluded": true,
  "txCode": "123456",
  "txCodeInputMode": "numeric",
  "subject": "1001"
}
'

/vci/offer/create API は次のような JSON を返します。

{
  "type": "credentialOfferCreateResponse",
  "resultCode": "A366001",
  "resultMessage": "[A366001] A credential offer was created successfully.",
  "action": "CREATED",
  "info": {
    "authTime": 0,
    "authorizationCodeGrantIncluded": false,
    "credentialConfigurations": [
      "IdentityCredential"
    ],
    "credentialIssuer": "https://trial.authlete.net",
    "credentialOffer": "{\"credential_issuer\":\"https://trial.authlete.net\",\"credential_configuration_ids\":[\"IdentityCredential\"],\"grants\":{\"urn:ietf:params:oauth:grant-type:pre-authorized_code\":{\"pre-authorized_code\":\"rS8D7asTTL8MaXM5yLjQvaAMmPmierRW6oeK-4JP4Uk\",\"tx_code\":{\"length\":6,\"input_mode\":\"numeric\"}}}}",
    "expiresAt": 1703929674224,
    "identifier": "9gjVvas8Q5BkkrkSfZv-DbsBYJvlw6ZPMK-TeCkQDEc",
    "issuerStateIncluded": false,
    "preAuthorizedCode": "rS8D7asTTL8MaXM5yLjQvaAMmPmierRW6oeK-4JP4Uk",
    "preAuthorizedCodeGrantIncluded": true,
    "subject": "1001",
    "txCode": "123456",
    "txCodeInputMode": "numeric"
  }
}

API レスポンス内の "info" オブジェクトが生成されたクレデンシャルオファーの情報を含んでいます。 "info" オブジェクト内の "credentialOffer" プロパティが生成されたクレデンシャルオファーです。 上記例中の "credentialOffer" プロパティを読みやすく整形すると次のようになります。

{
  "credential_issuer": "https://trial.authlete.net",
  "credential_configuration_ids": [
    "IdentityCredential"
  ],
  "grants": {
    "urn:ietf:params:oauth:grant-type:pre-authorized_code": {
      "pre-authorized_code": "rS8D7asTTL8MaXM5yLjQvaAMmPmierRW6oeK-4JP4Uk",
      "tx_code": {
        "length": 6,
        "input_mode": "numeric"
      }
    }
  }
}

“credentialOffer” プロパティの値があれば、次の要素を連結することで URL を構築できます。

  1. クレデンシャルオファーエンドポイント。openid-credential-offer:// など。
  2. ?credential_offer=
  3. URL エンコードした "credentialOffer" の値
openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Ftrial.authlete.net%22%2C%22credential_configuration_ids%22%3A%5B%22IdentityCredential%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22rS8D7asTTL8MaXM5yLjQvaAMmPmierRW6oeK-4JP4Uk%22%2C%22tx_code%22%3A%7B%22length%22%3A6%2C%22input_mode%22%3A%22numeric%22%7D%7D%7D%7D

/vci/offer/create API へのリクエストとそのレスポンスは、それぞれ authlete-java-common ライブライ内の CredentialOfferCreateRequest Java クラスと CredentialOfferCreateResponse Java クラスで表現されます。 詳細はライブラリの JavaDoc を参照してください。


3.4.2. The /vci/offer/info API

/vci/offer/info API はクレデンシャルオファーの情報を返します。 この API はクレデンシャルオファーの識別子を指定する identifier リクエストパラメーターを受け付けます。

/vci/offer/info API へのリクエスト
HTTP メソッドと
Content-Type
GET (パスパラメーター)
POST application/json
POST application/x-www-form-urlencoded
リクエストパラメーター identifier クレデンシャルオファーの識別子。API コールが HTTP GET リクエストの場合、識別子は /vci/offer/info/{identifier} のようにパスの末尾で指定されます。

識別子は /vci/offer/create API のレスポンスに含まれます。"info" オブジェクト内の "identifier" プロパティの値が識別子です。前節の例では、その値は 9gjVvas8Q5BkkrkSfZv-DbsBYJvlw6ZPMK-TeCkQDEc となっています。

次のコマンドラインは、前節で作成されたクレデンシャルオファーの情報を問い合わせます。

$ CREDENTIAL_OFFER_IDENTIFIER=9gjVvas8Q5BkkrkSfZv-DbsBYJvlw6ZPMK-TeCkQDEc
$ curl -s ${BASE_URL}/api/${SERVICE_ID}/vci/offer/info/${CREDENTIAL_OFFER_IDENTIFIER} \
    -H "Authorization: Bearer ${ACCESS_TOKEN}"

/vci/offer/info API は次のような JSON を返します。 内容は /vci/offer/create API が返すものとほぼ同じです。

{
  "type": "credentialOfferInfoResponse",
  "resultCode": "A368001",
  "resultMessage": "[A368001] Information about the credential offer was obtained successfully.",
  "action": "OK",
  "info": {
    "authTime": 0,
    "authorizationCodeGrantIncluded": false,
    "credentialConfigurationIds": [
      "IdentityCredential"
    ],
    "credentialIssuer": "https://trial.authlete.net",
    "credentialOffer": "{\"credential_issuer\":\"https://trial.authlete.net\",\"credential_configuration_ids\":[\"IdentityCredential\"],\"grants\":{\"urn:ietf:params:oauth:grant-type:pre-authorized_code\":{\"pre-authorized_code\":\"rS8D7asTTL8MaXM5yLjQvaAMmPmierRW6oeK-4JP4Uk\",\"tx_code\":{\"length\":6,\"input_mode\":\"numeric\"}}}}",
    "expiresAt": 1703929674000,
    "identifier": "9gjVvas8Q5BkkrkSfZv-DbsBYJvlw6ZPMK-TeCkQDEc",
    "issuerStateIncluded": false,
    "preAuthorizedCode": "rS8D7asTTL8MaXM5yLjQvaAMmPmierRW6oeK-4JP4Uk",
    "preAuthorizedCodeGrantIncluded": true,
    "subject": "1001",
    "txCode": "123456",
    "txCodeInputMode": "numeric"
  }
}

/vci/offer/info API の主目的は、ウォレットからの問い合わせに対してクレデンシャルオファーの情報を返すエンドポイントを、開発者がクレデンシャルイシュア上に実装するのを助けることです。

そのようなエンドポイントがあれば、次の要素を連結することで URL を構築できます。

  1. クレデンシャルオファーエンドポイント。openid-credential-offer:// など。
  2. ?credential_offer_uri=
  3. URL エンコードしたクレデンシャルオファーの識別子を含むエンドポイントの URL。例えば https://trial.authlete.net/api/offer/9gjVvas8Q5BkkrkSfZv-DbsBYJvlw6ZPMK-TeCkQDEc など。
openid-credential-offer://?credential_offer_uri=https%3A%2F%2Ftrial.authlete.net%2Fapi%2Foffer%2F9gjVvas8Q5BkkrkSfZv-DbsBYJvlw6ZPMK-TeCkQDEc

3.4.3. クレデンシャルオファー発行の例

Java で書かれたサンプル認可サーバの実装である authlete/java-oauth-server はクレデンシャルイシュアとしても機能します。開発者が任意のクレデンシャルオファーを生成するための HTML ページが /api/offer/issue エンドポイントにより提供されます。Authlete 3.0 を使う java-oauth-server インスタンスが現在 https://trial.authlete.net で稼働しており、試用を目的として https://trial.authlete.net/api/offer/issue エンドポイントを利用できます。

HTML ページではユーザ認証が要求されます。 java-oauth-server に埋め込まれているテストアカウントを使用できます。 ただし、mdoc フォーマットの VC を生成したい場合は inga を使用してください。

サブジェクト ログイン ID パスワード
1001 john john
1002 jane jane
1003 max max
1004 inga inga

3.5. クレデンシャルエンドポイント実装

下記の Authlete API を用いてクレデンシャルエンドポイントを実装することができます。

Authlete API 説明
1 /auth/introspection 提示されたアクセストークンの有効性を確認し、アクセストークンの情報を返します。
2 /vci/single/parse 受け取ったクレデンシャルリクエストを解析して有効性を確認し、クレデンシャルリクエストの情報を返します。
3 /vci/single/issue Verifiable Credential またはトランザクション ID を発行し、クレデンシャルレスポンスを用意します。

クレデンシャルエンドポイント実装内の処理手順を見ていきましょう。

最初のステップとして、クレデンシャルエンドポイントの実装はウォレットからクレデンシャルリクエストを受け取ります。

クレデンシャルリクエストからアクセストークンを取り出し、Authlete の /auth/introspection API に渡します。

/auth/introspection API はアクセストークンの有効性を確認し、アクセストークンの情報を返します。

アクセストークンが有効であれば、エンドポイントの実装はアクセストークンとクレデンシャルリクエストの本文を /vci/single/parse API に送ります。

/vci/single/parse API はクレデンシャルリクエストの解析と有効性確認をおこない、クレデンシャルリクエストの情報を返します。

エンドポイントの実装は、Authlete が VC を発行するのに必要な情報を含む『クレデンシャル発行指示』(Credential Issuance Order) を準備します。この準備の詳細については後ほど議論します。

エンドポイントの実装はクレデンシャル発行指示とアクセストークンを /vci/single/issue API に送ります。

クレデンシャル発行指示に従い、/vci/single/issue API は VC またはトランザクション ID を発行し、クレデンシャルレスポンスの内容を用意します。

エンドポイントの実装は、ウォレットに返すクレデンシャルレスポンスを表す HTTP レスポンスを構築します。 /vci/single/issue API が用意したレスポンスの内容をクレデンシャルレスポンスのメッセージボディとして使用できます。

最後に、クレデンシャルエンドポイントはクレデンシャルレスポンスをウォレットに返します。

次の図はクレデンシャルエンドポイントの実装内の処理手順を示しています。

3.5.1. クレデンシャル発行指示

クレデンシャル発行指示を用意する手順は次の通りです。


3.5.1.1. クレデンシャル発行指示 ステップ 1

アクセストークン情報からアクセストークンに紐付くユーザのサブジェクト (= 一意識別子) を取得します。 /auth/introspection API のレスポンス (IntrospectionResponse 参照) に含まれる "subject" プロパティがサブジェクトの値を保持しています。


3.5.1.2. クレデンシャル発行指示 ステップ 2

サブジェクトで特定されるユーザの情報をユーザデータベースから取り出します。


3.5.1.3. クレデンシャル発行指示 ステップ 3

アクセストークン情報からアクセストークンに紐付く発行可能クレデンシャルの情報を取得します。 /auth/introspection API のレスポンスに含まれる "issuableCredentials" プロパティが情報を文字列として保持しています。この文字列は JSON 配列としてパースする必要があります。


3.5.1.4. クレデンシャル発行指示 ステップ 4

クレデンシャルリクエスト情報からクレデンシャルリクエストに含まれるクレデンシャル情報を取得します。 /vci/single/parse API のレスポンス (CredentialSingleParseResponse 参照) 内の "info" オブジェクトはクレデンシャルリクエストに関する様々な情報を保持しています。 その "info" オブジェクト内の "format" プロパティと "details" プロパティを併せたものがクレデンシャル情報を表します。

"details" プロパティの値は文字列です。この文字列は JSON オブジェクトとしてパースする必要があります。 その JSON オブジェクトの内容は、"format" パラメーター、"proof" パラメーター、 “credential_response_encryption” パラメーターが含まれていないことを除き、 クレデンシャルリクエストとほぼ同じです。


3.5.1.5. クレデンシャル発行指示 ステップ 5

クレデンシャル情報がいずれかの発行可能クレデンシャルのサブセットであるかどうかを調べることにより、 アクセストークンがクレデンシャルリクエストのための必要な権限を持っていることを確認します。

しかしながら、プログラマであれば、現在の OID4VCI 仕様ではこのステップを実装することが困難なことを理解できるでしょう。 さらに、SD-JWT VC では、「vct の値によりクレームの集合を決め、個々のクレームを一つ一つ指定する必要をなくす」という提案があります。 この提案は、JSON オブジェクト同士の包含関係を機械的に調べることでアクセストークンの権限をチェックすることを不可能にします。

そのため、アクセストークンが十分な権限を持っていることの確認は、クレデンシャルイシュアがそれぞれの方針に基づいて実装することになります。 包含関係に基づく権限チェックは Authlete に実装済みではありますが、無効にしてあります。


3.5.1.6. クレデンシャル発行指示 ステップ 6

クレデンシャル情報に基づき、発行する VC に埋め込むユーザクレームの集合を決定し、ユーザデータベースから取り出しておいたデータセットからそれらのユーザクレーム群の情報を取得します。


3.5.1.7. クレデンシャル発行指示 ステップ 7

集めたデータを使いクレデンシャル発行指示を作成します。

クレデンシャル発行指示は下表に列挙されたプロパティを持つ JSON オブジェクトです。

プロパティ 説明
requestIdentifier 文字列 Authlete がクレデンシャルリクエストに割り当てた識別子。/vci/single/parse API のレスポンスに含まれる info.identifier プロパティがその識別子です。このプロパティは必須です。
credentialPayload 文字列 発行する VC に追加するペイロード。この文字列のフォーマットは JSON オブジェクトでなければなりません。ユーザクレームの集合を JSON に変換後、このプロパティにセットしてください。このプロパティは任意です。
issuanceDeferred 真偽値 クレデンシャル発行を遅延させるかどうかを示すフラグです。このプロパティが true の場合、/vci/single/issue API は VC の代わりにトランザクション ID を発行します。
credentialDuration 整数 VC の有効秒数。このプロパティが正の整数の場合、有効時間として使われます。値が 0 の場合、サービスのデフォルト有効時間が使われます。負の整数の場合、VC は有効期限を持ちません。
signingKeyId 文字列 発行する VC の署名に使う秘密鍵の鍵 ID。省略された場合、Authlete は自動的に鍵を選択します。

3.5.1.8. クレデンシャル発行指示 ステップ 8

/vci/single/issue API へのリクエスト (CredentialSingleIssueRequest 参照) を用意します。

/vci/single/issue API へのリクエスト
HTTP メソッドと Content-Type POST application/json
リクエストパラメーター accessToken クレデンシャルエンドポイントに提示されたアクセストークン。
order VC またはトランザクション ID を発行するための命令を提供するクレデンシャル発行指示。

3.5.1.9. クレデンシャル発行指示 ステップ 9

用意したリクエストを /vci/single/issue API に送ります。


3.5.1.10. クレデンシャル発行指示 ステップの要約

次の図はクレデンシャル発行指示を用意する手順の要約です。


3.6. 一括クレデンシャルエンドポイント実装

執筆予定。

4. OID4VCI デモ

4.1. 事前認可コードフロー + 鍵証明 + SD-JWT VC


4.1.1. セットアップ

このデモで使うリソースをダウンロードします。

git clone git@github.com:authlete/oid4vci-demo.git
cd oid4vci-demo

このデモ用のシェル変数を設定します。

CLIENT_ID=218232426
TOKEN_ENDPOINT=https://trial.authlete.net/api/token
CREDENTIAL_ISSUER=https://trial.authlete.net
CREDENTIAL_ENDPOINT=https://trial.authlete.net/api/credential

4.1.2. 事前認可コード

事前認可コード』を含む『クレデンシャルオファー』を生成するため、 https://trial.authlete.net/api/offer/issue にアクセスします。

この URL で表示されるページは、デモ用に任意のクレデンシャルオファーを生成するためのフォームを提供します。 フォームの『Pre-authorized code grant included』がチェックされていると、生成されるクレデンシャルオファーに事前認可コードが含まれるようになります。

Login ID フィールドと Password フィールドにそれぞれ ingainga を入力し、 Pre-authorized code grant included にチェックがついていることを確認し、 Submit ボタンを押します。これにより、結果ページが表示されます。

結果ページで表示される QR コードは、クレデンシャルオファーを含む URL を表しています。 クレデンシャルオファーの中身は、QR コードの下に置かれている JSON に表示されます。 この JSON 内の pre-authorized_code プロパティの値が発行された事前認可コードです。

次の手順で使うので、発行された事前認可コードをシェル変数 PRE_AUTHORIZED_CODE に設定します。

PRE_AUTHORIZED_CODE=NH9udMon5pTuuvbsNsHUNWf8tpU__9wt-gsO9LeYthc

4.1.3. アクセストークン

事前認可コードフローを用いて『トークンリクエスト』を送ります。 このデモのクライアントはパブリッククライアントなので、クライアント認証は要求されません。 つまり、クライアント認証に関連するリクエストパラメーターを追加する必要はありません。

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": "xj2YRmSV-_e15n7mTXSvkCH-Yw-XklRagEHF5WXE7R4",
  "token_type": "Bearer",
  "expires_in": 86400,
  "scope": null,
  "refresh_token": "Oq7H2GsuES4Z6d_63Dn7rWhJ9rCpgzmzwQ-BYtGR1yE",
  "c_nonce": "EhTC8LA6kVrrO6_XiC7N6N_wXdma2Zs1LHAQBZ5E0T0",
  "c_nonce_expires_in": 86400
}

レスポンスには access_token パラメーターと c_nonce パラメーターが含まれます。 後ほど使うので、これらのレスポンスパラメーターの値をシェル変数に設定します。

ACCESS_TOKEN=xj2YRmSV-_e15n7mTXSvkCH-Yw-XklRagEHF5WXE7R4
C_NONCE=EhTC8LA6kVrrO6_XiC7N6N_wXdma2Zs1LHAQBZ5E0T0

4.1.4. 鍵証明

保有者の鍵 holder.jwkgenerate-key-proof スクリプトを用いて、JWT 形式の『鍵証明』を生成します。 この JWK ファイルとスクリプトは oid4vci-demo レポジトリに含まれています。

./generate-key-proof \
    -i $CREDENTIAL_ISSUER \
    -k holder.jwk \
    -c $CLIENT_ID \
    -n $C_NONCE

generate-key-proof スクリプトは次のような鍵証明を生成します。

eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiUFN4UXJEMnpsMF9tWGNBcXoxbWdxU2VCb0Jobm14Mnl4QkVwckJZOEYyMCIsInkiOiJ4VjhmYmkxRlNvc1V1bkxldUxOdUxrSmlxbVk2VEtpTW51ci1HbjJ3UjEwIn19.eyJpc3MiOiIyMTgyMzI0MjYiLCJhdWQiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsImlhdCI6MTcwMzg0NzM3Niwibm9uY2UiOiJFaFRDOExBNmtWcnJPNl9YaUM3TjZOX3dYZG1hMlpzMUxIQVFCWjVFMFQwIn0.6l8QnPTclDUoWH5PsVsZQDauA_HcIVDGxU9-TfezflIIAzTFgeC5nTr5rLBkEIgcfUvkUOwKqlM06LdVVwTZlw

鍵証明のヘッダとペイロードを base64url でデコードすると、次の JSON が得られます。

{
  "typ": "openid4vci-proof+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "PSxQrD2zl0_mXcAqz1mgqSeBoBhnmx2yxBEprBY8F20",
    "y": "xV8fbi1FSosUunLeuLNuLkJiqmY6TKiMnur-Gn2wR10"
  }
}
{
  "iss": "218232426",
  "aud": "https://trial.authlete.net",
  "iat": 1703847376,
  "nonce": "EhTC8LA6kVrrO6_XiC7N6N_wXdma2Zs1LHAQBZ5E0T0"
}

generate-key-proof スクリプトの実行結果は、次のようにすることで直接シェル変数 KEY_PROOF_JWT に設定することができます。

KEY_PROOF_JWT=`./generate-key-proof -i $CREDENTIAL_ISSUER -k holder.jwk -c $CLIENT_ID -n $C_NONCE`

4.1.5. SD-JWT VC

生成した鍵証明と一緒に、『クレデンシャルエンドポイント』に『クレデンシャルリクエスト』を送ります。

curl -s $CREDENTIAL_ENDPOINT \
       -H "Authorization: Bearer $ACCESS_TOKEN" \
       -H "Content-Type: application/json" \
       --data '{
  "format": "vc+sd-jwt",
  "vct": "https://credentials.example.com/identity_credential",
  "proof": {
    "proof_type": "jwt",
    "jwt":"'${KEY_PROOF_JWT}'"
  }
}'

クレデンシャルエンドポイントは次のような応答を返します。

{
  "credential": "eyJraWQiOiJKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrIiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJfc2QiOlsiMERFMXBjUHo3LURtYlc5NlRLaFlHUFlENi05dnNtUzFra21EWVQ4NnF1NCIsIjdXeHNTejhXSWtBaHdLZmQ0aUVXRFBrOUhuMFdqZ1V6N0pHX3hqekVwTjQiLCJCVVdOQlh2bkJwZ1ZWaUpaLVdPTWFxZHhiOTRfSUR1OEhNaEZnUjU2aXd3IiwiR1BqSG1lOFhaS2JvWFhLMllPU2Y4cE10QXNzSGlKQU5QdW91WkI1QTZuNCIsIklhN3FqdVNDSmdGWDRiX09uS2p4dWJsU0tTWmRWcUhFdEpVRHZHRWtEWVEiLCJQenUtUEVqUlE5bF9vNkVEUmp0QnBpaWZqUVNMV0RNZ2ExM2VscTZpRW44IiwiVTRQWHdKMDMtenlEeUtpZFlPQWsxUkMzNWNZT2FCdnZ5bG9BQnM3bXZ1RSIsImxrT1BCTm9qNFk0OGE4a1F4VmlTSkJlQWdieE5YTEdSaEI5dkliejJCdjgiLCJuUDdMcmk2QmlqVHF0VVI2cTRfakwxTzlyWnd2OE9sdGlEX1lUWGlTa2ZrIiwidF9WbWxQQmxNcENiRG5NUjhzYUdDX2ZqMVZCb3FCLVUyZk1NYWZNbVRNayJdLCJ2Y3QiOiJodHRwczovL2NyZWRlbnRpYWxzLmV4YW1wbGUuY29tL2lkZW50aXR5X2NyZWRlbnRpYWwiLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6Imh0dHBzOi8vdHJpYWwuYXV0aGxldGUubmV0IiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Iiwia2lkIjoiNE05a0lyQjlXWXp0MUdRZ0wxMmx6ZEJac0d5ZVYzbGdQS292MjhvVDVMNCIsIngiOiJQU3hRckQyemwwX21YY0FxejFtZ3FTZUJvQmhubXgyeXhCRXByQlk4RjIwIiwieSI6InhWOGZiaTFGU29zVXVuTGV1TE51TGtKaXFtWTZUS2lNbnVyLUduMndSMTAifX0sImlhdCI6MTcwMzg0NzYwNX0.2k4JTf61BOs461fLToA9VwZbY64i9TIfchZVasC0OCx2rDJYodeRc6uVjYuHBOZUHlMJ1WdTjCFfKuxE5juxMA~WyIxMjBTWFZhTzZKTWxoc0dIZUdVdktRIiwic3ViIiwiMTAwNCJd~WyJVUl9MTU5ocGFLUUVpbEQ1TXc5RnB3IiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ~WyJhT2huYjgySmFMbjZ0OHRZZXpGanl3IiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd~WyJGNzE0ZktfRFpSMm83ajFWRkpIUW5BIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd~",
  "c_nonce": "EhTC8LA6kVrrO6_XiC7N6N_wXdma2Zs1LHAQBZ5E0T0",
  "c_nonce_expires_in": 85956
}

レスポンス内の credential パラメーターの値が発行された SD-JWT VC です。 この SD-JWT VC がシェル変数 SD_JWT に設定されている場合、decode-sd-jwt スクリプトを次のように起動することで、SD-JWT VC の内容をデコードすることができます。

./decode-sd-jwt $SD_JWT

結果は次のようになります。

{
  "kid": "J1FwJP87C6-QN_WSIOmJAQc6n5CQ_bZdaFJ5GDnW1Rk",
  "typ": "vc+sd-jwt",
  "alg": "ES256"
}
{
  "_sd": [
    "0DE1pcPz7-DmbW96TKhYGPYD6-9vsmS1kkmDYT86qu4",
    "7WxsSz8WIkAhwKfd4iEWDPk9Hn0WjgUz7JG_xjzEpN4",
    "BUWNBXvnBpgVViJZ-WOMaqdxb94_IDu8HMhFgR56iww",
    "GPjHme8XZKboXXK2YOSf8pMtAssHiJANPuouZB5A6n4",
    "Ia7qjuSCJgFX4b_OnKjxublSKSZdVqHEtJUDvGEkDYQ",
    "Pzu-PEjRQ9l_o6EDRjtBpiifjQSLWDMga13elq6iEn8",
    "U4PXwJ03-zyDyKidYOAk1RC35cYOaBvvyloABs7mvuE",
    "lkOPBNoj4Y48a8kQxViSJBeAgbxNXLGRhB9vIbz2Bv8",
    "nP7Lri6BijTqtUR6q4_jL1O9rZwv8OltiD_YTXiSkfk",
    "t_VmlPBlMpCbDnMR8saGC_fj1VBoqB-U2fMMafMmTMk"
  ],
  "vct": "https://credentials.example.com/identity_credential",
  "_sd_alg": "sha-256",
  "iss": "https://trial.authlete.net",
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "kid": "4M9kIrB9WYzt1GQgL12lzdBZsGyeV3lgPKov28oT5L4",
      "x": "PSxQrD2zl0_mXcAqz1mgqSeBoBhnmx2yxBEprBY8F20",
      "y": "xV8fbi1FSosUunLeuLNuLkJiqmY6TKiMnur-Gn2wR10"
    }
  },
  "iat": 1703847605
}
{
  "digest": "lkOPBNoj4Y48a8kQxViSJBeAgbxNXLGRhB9vIbz2Bv8",
  "WyIxMjBTWFZhTzZKTWxoc0dIZUdVdktRIiwic3ViIiwiMTAwNCJd": [
    "120SXVaO6JMlhsGHeGUvKQ",
    "sub",
    "1004"
  ]
}
{
  "digest": "0DE1pcPz7-DmbW96TKhYGPYD6-9vsmS1kkmDYT86qu4",
  "WyJVUl9MTU5ocGFLUUVpbEQ1TXc5RnB3IiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ": [
    "UR_LMNhpaKQEilD5Mw9Fpw",
    "given_name",
    "Inga"
  ]
}
{
  "digest": "nP7Lri6BijTqtUR6q4_jL1O9rZwv8OltiD_YTXiSkfk",
  "WyJhT2huYjgySmFMbjZ0OHRZZXpGanl3IiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd": [
    "aOhnb82JaLn6t8tYezFjyw",
    "family_name",
    "Silverstone"
  ]
}
{
  "digest": "U4PXwJ03-zyDyKidYOAk1RC35cYOaBvvyloABs7mvuE",
  "WyJGNzE0ZktfRFpSMm83ajFWRkpIUW5BIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd": [
    "F714fK_DZR2o7j1VFJHQnA",
    "birthdate",
    "1991-11-06"
  ]
}

4.2. 認可コードフロー + PAR + DPoP + mdoc


4.2.1. セットアップ

このデモで使うリソースをダウンロードします。

git clone git@github.com:authlete/oid4vci-demo.git
cd oid4vci-demo

このデモ用のシェル変数を設定します。

CLIENT_ID=218232426
TOKEN_ENDPOINT=https://trial.authlete.net/api/token
CREDENTIAL_ISSUER=https://trial.authlete.net
CREDENTIAL_ENDPOINT=https://trial.authlete.net/api/credential
PAR_ENDPOINT=https://trial.authlete.net/api/par

4.2.2. リクエスト URI

DPoP 用の秘密鍵 dpop.jwkgenerate-dpop-proof スクリプトを用いて DPoP proof JWT (RFC 9449) を生成します。

DPOP_PROOF_JWT=`./generate-dpop-proof -k dpop.jwk -m POST -u $PAR_ENDPOINT`

generate-dpop-proof スクリプトは次のような DPoP proof JWT を生成します。

eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiaEdmcXpHWGdhbzFRZ1ZJVFk2a2lIWU9LYmFMWEJ4VHFQSmE0RU9pbXhoSSIsInkiOiJFMUtpQV9mQTJ4OElycnlzb0dkbkJUTUI1LW8zRUpUX01nUUFfSG1HdTlNIn19.eyJqdGkiOiJoRm9HQkFBN3ZXTHExbWJ6IiwiaHRtIjoiUE9TVCIsImh0dSI6Imh0dHBzOi8vdHJpYWwuYXV0aGxldGUubmV0L2FwaS9wYXIiLCJpYXQiOjE3MDM4NjQ5ODR9.9VZtrwjASCEeO6v0SuGqEttYtoHORtGMNn95mSx4uNv04oA8hSDDBo4CoPQiaGsEjunJ_d_zKR7VsrF9M8BBZA

DPoP proof JWT のヘッダとペイロードを base64url でデコードすると、次の JSON が得られます。 ペイロードの htu クレームの値が PAR エンドポイントの URL であることに注目してください。

{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "hGfqzGXgao1QgVITY6kiHYOKbaLXBxTqPJa4EOimxhI",
    "y": "E1KiA_fA2x8IrrysoGdnBTMB5-o3EJT_MgQA_HmGu9M"
  }
}
{
  "jti": "hFoGBAA7vWLq1mbz",
  "htm": "POST",
  "htu": "https://trial.authlete.net/api/par",
  "iat": 1703864984
}

PAR エンドポイントに PAR リクエストを送ります。ここで注目すべき点は、(1) PAR リクエストに DPoP ヘッダが含まれていること、及び (2) scope パラメーターが org.iso.18013.5.1.mDL を含んでいることです。

curl -s $PAR_ENDPOINT \
     -H "DPoP: $DPOP_PROOF_JWT" \
     -d client_id=$CLIENT_ID \
     -d response_type=code \
     -d scope=org.iso.18013.5.1.mDL

この scope の値は、クレデンシャルイシュアメタデータの credential_configurations_supported オブジェクトに、 scope プロパティの値が org.iso.18013.5.1.mDL であるクレデンシャル設定が少なくとも一つ含まれていることを想定しています。

{
  "credential_configurations_supported": {
    "org.iso.18013.5.1.mDL": {
      "format": "mso_mdoc",
      "doctype": "org.iso.18013.5.1.mDL",
      "scope": "org.iso.18013.5.1.mDL",
      "claims": {
        "org.iso.18013.5.1": {
          "family_name": {},
          ...
        }
      }
    }
  },
  ...
}

PAR エンドポイントは次のようなレスポンスを返します。レスポンス内の request_uri パラメーターの値が発行されたリクエスト URI です。 この値は次のステップで使用します。

{
  "expires_in": 600,
  "request_uri": "urn:ietf:params:oauth:request_uri:du-ptCtuukbVDi2MgOjYwwb99cl-ho0bzzLb0X0u1n0"
}

4.2.3. 認可コード

ウェブブラウザを使い、認可エンドポイントに認可リクエストを送ります。URL 内の $REQUEST_URI を先ほどのステップで PAR エンドポイントから受け取った実際のリクエスト URI で置き換えることを忘れないでください。

https://trial.authlete.net/api/authorization?client_id=218232426&request_uri=$REQUEST_URI

認可ページが表示されます。Login ID フィールドと Password フィールドに inga, inga と入力し、Authorize ボタンを押してください。


リダイレクトエンドポイントへとリダイレクトされます。 このエンドポイントで表示されるページでは、発行された認可コードの値を確認できます。 この認可コードは次のステップで使用します。


4.2.4. アクセストークン

トークンエンドポイントにアクセスするための DPoP proof JWT を生成します。 generate-dpop-proof スクリプトの -u オプションに渡す引数が $TOKEN_ENDPOINT であること ($PAR_ENDPOINT ではないこと) に注意してください。

DPOP_PROOF_JWT=`./generate-dpop-proof -k dpop.jwk -m POST -u $TOKEN_ENDPOINT`

DPoP proof JWT のヘッダとペイロードを base64url でデコードすると、次の JSON が得られます。 ペイロードの htu クレームの値はトークンエンドポイントの URL になっています。

{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "hGfqzGXgao1QgVITY6kiHYOKbaLXBxTqPJa4EOimxhI",
    "y": "E1KiA_fA2x8IrrysoGdnBTMB5-o3EJT_MgQA_HmGu9M"
  }
}
{
  "jti": "KHV6KaCjtqwfnb8r",
  "htm": "POST",
  "htu": "https://trial.authlete.net/api/token",
  "iat": 1703865524
}

認可コードフローを使い、トークンエンドポイントにトークンリクエストを送ります。 次のコマンドを実行する前に、先ほどのステップで発行された認可コードをシェル変数 AUTHORIZATION_CODE に設定することを忘れないでください。

AUTHORIZATION_CODE=QaPvTUqX-aPDnrcFoCcYDZHW66RzC_vfi6EDq7derNs
curl -s $TOKEN_ENDPOINT \
     -H "DPoP: $DPOP_PROOF_JWT" \
     -d client_id=$CLIENT_ID \
     -d grant_type=authorization_code \
     -d code=$AUTHORIZATION_CODE

トークンエンドポイントは次のようなレスポンスを返します。

{
  "access_token": "T01u7-43MOA17hB8DqW-dEaBqUpStWtitYoVW1ewlH4",
  "token_type": "DPoP",
  "expires_in": 86400,
  "scope": "org.iso.18013.5.1.mDL",
  "refresh_token": "17on7yghEeGPbaXmjTEMgEGPG8S79DweIBtCq0MZOHE",
  "c_nonce": "LQwqEX7sT1uJkzzTdULvLsHcUvXfUaT79bkzLWEi7jw",
  "c_nonce_expires_in": 86400
}

レスポンスには access_token パラメーターが含まれています。 次のステップで使うので、このパラメーターの値をシェル変数 ACCESS_TOKEN に設定してください。

ACCESS_TOKEN=T01u7-43MOA17hB8DqW-dEaBqUpStWtitYoVW1ewlH4

4.2.5. mdoc

クレデンシャルエンドポイントにアクセスするための DPoP proof JWT を生成します。 generate-dpop-proof スクリプトの -u オプションに渡す引数が $CREDENTIAL_ENDPOINT であること、 DPoP proof JWT に ath クレームを埋め込むために -a オプションを渡す必要があることに注意してください。

DPOP_PROOF_JWT=`./generate-dpop-proof -k dpop.jwk -m POST -u $CREDENTIAL_ENDPOINT -a $ACCESS_TOKEN`

DPoP proof JWT のヘッダとペイロードを base64url でデコードすると、次の JSON が得られます。 ペイロードには ath クレームが含まれています。

{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "hGfqzGXgao1QgVITY6kiHYOKbaLXBxTqPJa4EOimxhI",
    "y": "E1KiA_fA2x8IrrysoGdnBTMB5-o3EJT_MgQA_HmGu9M"
  }
}
{
  "jti": "caK7sLDiAF9HbOi2",
  "htm": "POST",
  "htu": "https://trial.authlete.net/api/credential",
  "iat": 1703865668,
  "ath": "DUuszLa1NHTMoREsPQE0gB9znn1BRKYPUpOwBpVcLJo"
}

DPoP proof JWT とアクセストークンを併せて、クレデンシャルエンドポイントクレデンシャルリクエストを送ります。

curl -s $CREDENTIAL_ENDPOINT \
     -H "DPoP: $DPOP_PROOF_JWT" \
     -H "Authorization: DPoP $ACCESS_TOKEN" \
     -H "Content-Type: application/json" \
     --data '{
  "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": {}
    }
  }
}'

クレデンシャルエンドポイントは次のようなレスポンスを返します。

{
  "credential": "omdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xiNgYWFukaGRpZ2VzdElEAWZyYW5kb21QJ6e6efO1W1kaYmdXHjpSY3FlbGVtZW50SWRlbnRpZmllcmppc3N1ZV9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoyMDIzLTEyLTI52BhYXKRoZGlnZXN0SUQCZnJhbmRvbVBlLj1uVWnDyBHmzifHfxjhcWVsZW1lbnRJZGVudGlmaWVya2V4cGlyeV9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoyMDI0LTEyLTI52BhYWqRoZGlnZXN0SUQDZnJhbmRvbVAMR8e_GTnz4n7RFXKXgrAQcWVsZW1lbnRJZGVudGlmaWVya2ZhbWlseV9uYW1lbGVsZW1lbnRWYWx1ZWtTaWx2ZXJzdG9uZdgYWFKkaGRpZ2VzdElEBGZyYW5kb21QCHsjtktJ-PkSnEpzHOOCnHFlbGVtZW50SWRlbnRpZmllcmpnaXZlbl9uYW1lbGVsZW1lbnRWYWx1ZWRJbmdh2BhYW6RoZGlnZXN0SUQFZnJhbmRvbVAj-grPYzaQs1Np8Jom4yNpcWVsZW1lbnRJZGVudGlmaWVyamJpcnRoX2RhdGVsZWxlbWVudFZhbHVl2QPsajE5OTEtMTEtMDbYGFhVpGhkaWdlc3RJRAZmcmFuZG9tUByAlsGIDKRVgTjKy9AgNMxxZWxlbWVudElkZW50aWZpZXJvaXNzdWluZ19jb3VudHJ5bGVsZW1lbnRWYWx1ZWJVU9gYWFukaGRpZ2VzdElEB2ZyYW5kb21QpIldNj4zHUGw0vcfVFMsDXFlbGVtZW50SWRlbnRpZmllcm9kb2N1bWVudF9udW1iZXJsZWxlbWVudFZhbHVlaDEyMzQ1Njc42BhYoqRoZGlnZXN0SUQIZnJhbmRvbVByP2ESVgTLndyQCDgw1khMcWVsZW1lbnRJZGVudGlmaWVycmRyaXZpbmdfcHJpdmlsZWdlc2xlbGVtZW50VmFsdWWBo3V2ZWhpY2xlX2NhdGVnb3J5X2NvZGVhQWppc3N1ZV9kYXRl2QPsajIwMjMtMDEtMDFrZXhwaXJ5X2RhdGXZA-xqMjA0My0wMS0wMWppc3N1ZXJBdXRohEOhASahGCFZAWEwggFdMIIBBKADAgECAgYBjJHZwhkwCgYIKoZIzj0EAwIwNjE0MDIGA1UEAwwrSjFGd0pQODdDNi1RTl9XU0lPbUpBUWM2bjVDUV9iWmRhRko1R0RuVzFSazAeFw0yMzEyMjIxNDA2NTZaFw0yNDEwMTcxNDA2NTZaMDYxNDAyBgNVBAMMK0oxRndKUDg3QzYtUU5fV1NJT21KQVFjNm41Q1FfYlpkYUZKNUdEblcxUmswWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQCilV5ugmlhHJzDVgqSRE5d8KkoQqX1jVg8WE4aPjFODZQ66fFPFIhWRP3ioVUi67WGQSgTY3F6Vmjf7JMVQ4MMAoGCCqGSM49BAMCA0cAMEQCIGcWNJwFy8RGV4uMwK7k1vEkqQ2xr-BCGRdN8OZur5PeAiBVrNuxV1C9mCW5z2clhDFaXNdP2Lp_7CBQrHQoJhuPcNgYWQHopWd2ZXJzaW9uYzEuMG9kaWdlc3RBbGdvcml0aG1nU0hBLTI1Nmx2YWx1ZURpZ2VzdHOhcW9yZy5pc28uMTgwMTMuNS4xqAFYIKroraryzIKijf6A0qMLKKEXhO4JkfgeofLS35R-tV-CAlggA1kTuTcqhUUzLu689TY7XaXTKDTQTy-X0CTtJbOgqJsDWCA9I2CynkOK1sgi3P7ZmCZJENPGQl8QsWImdXSNXsqEEARYIJ7fk0E18dWrB3R_X0NGXdshFSplLC2WiMGrqotXnbOVBVggs4p4mEragqpfssdgOpeGrmxeBq8kDp7rpVf-8mngGV4GWCAAGjwiUCuOj0CnVGknOCa217wUJthsqNS8CoxZ5BWQEQdYIDwygoPCSFXyutize7ktwCSKY5T7V4mWpizCFoVSkBy7CFggTWvL8cD_12lYMvje8i2T6YLEwNJNfje5lG6jfeS5I31nZG9jVHlwZXVvcmcuaXNvLjE4MDEzLjUuMS5tRExsdmFsaWRpdHlJbmZvo2ZzaWduZWTAdDIwMjMtMTItMjlUMTY6MDE6NTdaaXZhbGlkRnJvbcB0MjAyMy0xMi0yOVQxNjowMTo1N1pqdmFsaWRVbnRpbMB0MjAyNC0xMi0yOVQxNjowMTo1N1pYQPJyDU5h4tDesMtjRzbxcm77l-np35iKtKAKQj67Vh0XRHJsNxKoX_QJRLPrL1u58HuJDbwyA6c1ewBulm4AUAM",
  "c_nonce": "LQwqEX7sT1uJkzzTdULvLsHcUvXfUaT79bkzLWEi7jw",
  "c_nonce_expires_in": 86256
}

レスポンス内の credential パラメーターの値が発行された mdoc です。

ウェブサイト CBOR Zone (https://cbor.zone/) を使い、mdoc をデコードすることができます。 credential パラメーターの値をコピーし、CBOR Zone の Input セクションのテキストエリアに貼り付け、 base64url ラジオボタンを選択し、Generate ボタンを押してください。mdoc の内容が CBOR 診断記法 (RFC 8949, 8. Diagnostic Notation, RFC 8610, Appendix G. Extended Diagnostic Notation) で表示されます。

{
  "docType": "org.iso.18013.5.1.mDL",
  "issuerSigned": {
    "nameSpaces": {
      "org.iso.18013.5.1": [
        24(<<
          {
            "digestID": 1,
            "random": h'27a7ba79f3b55b591a6267571e3a5263',
            "elementIdentifier": "issue_date",
            "elementValue": 1004("2023-12-29")
          }
        >>),
        24(<<
          {
            "digestID": 2,
            "random": h'652e3d6e5569c3c811e6ce27c77f18e1',
            "elementIdentifier": "expiry_date",
            "elementValue": 1004("2024-12-29")
          }
        >>),
        24(<<
          {
            "digestID": 3,
            "random": h'0c47c7bf1939f3e27ed115729782b010',
            "elementIdentifier": "family_name",
            "elementValue": "Silverstone"
          }
        >>),
        24(<<
          {
            "digestID": 4,
            "random": h'087b23b64b49f8f9129c4a731ce3829c',
            "elementIdentifier": "given_name",
            "elementValue": "Inga"
          }
        >>),
        24(<<
          {
            "digestID": 5,
            "random": h'23fa0acf633690b35369f09a26e32369',
            "elementIdentifier": "birth_date",
            "elementValue": 1004("1991-11-06")
          }
        >>),
        24(<<
          {
            "digestID": 6,
            "random": h'1c8096c1880ca4558138cacbd02034cc',
            "elementIdentifier": "issuing_country",
            "elementValue": "US"
          }
        >>),
        24(<<
          {
            "digestID": 7,
            "random": h'a4895d363e331d41b0d2f71f54532c0d',
            "elementIdentifier": "document_number",
            "elementValue": "12345678"
          }
        >>),
        24(<<
          {
            "digestID": 8,
            "random": h'723f61125604cb9ddc90083830d6484c',
            "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'aae8adaaf2cc82a28dfe80d2a30b28a11784ee0991f81ea1f2d2df947eb55f82',
              2: h'035913b9372a8545332eeebcf5363b5da5d32834d04f2f97d024ed25b3a0a89b',
              3: h'3d2360b29e438ad6c822dcfed998264910d3c6425f10b1622675748d5eca8410',
              4: h'9edf934135f1d5ab07747f5f43465ddb21152a652c2d9688c1abaa8b579db395',
              5: h'b38a78984ada82aa5fb2c7603a9786ae6c5e06af240e9eeba557fef269e0195e',
              6: h'001a3c22502b8e8f40a75469273826b6d7bc1426d86ca8d4bc0a8c59e4159011',
              7: h'3c328283c24855f2bad8b37bb92dc0248a6394fb578996a62cc2168552901cbb',
              8: h'4d6bcbf1c0ffd7695832f8def22d93e982c4c0d24d7e37b9946ea37de4b9237d'
            }
          },
          "docType": "org.iso.18013.5.1.mDL",
          "validityInfo": {
            "signed": 0("2023-12-29T16:01:57Z"),
            "validFrom": 0("2023-12-29T16:01:57Z"),
            "validUntil": 0("2024-12-29T16:01:57Z")
          }
        }
      >>),
      h'f2720d4e61e2d0deb0cb634736f1726efb97e9e9df988ab4a00a423ebb561d1744726c3712a85ff40944b3eb2f5bb9f07b890dbc3203a7357b006e966e005003'
    ]
  }
}

4.3. POTENTIAL 相互運用性イベント / トラック 1 / Light プロファイル

POTENTIAL はヨーロッパのデジタルアイデンティティに特化したヨーロッパの組織です。 この組織は 2024 年春から相互運用性イベントを開催しています。このイベントは 6 つのトラックに分かれています。 トラック 1 と 2 は、クレデンシャルイシュアの相互運用性をテストするためのトラックです。 トラック 1 では VC の形式として mdoc が使用され、トラック 2 では SD-JWT VC が使用されます。

トラック 1 では 2 つのプロファイルが定義されています。 1 つは Light プロファイルと呼ばれ、もう 1 つは Full プロファイルと呼ばれます。 このセクションでは Light プロファイルの手順を説明します。


4.3.1. 設定


4.3.1.1. 認可サーバの設定
パラメータ
発行者識別子 https://trial.authlete.net
認可エンドポイント https://trial.authlete.net/api/authorization
トークンエンドポイント https://trial.authlete.net/api/token
ディスカバリエンドポイント https://trial.authlete.net/.well-known/openid-configuration
エンティティ設定 https://trial.authlete.net/.well-known/openid-federation

認可サーバのソースコードは https://github.com/authlete/java-oauth-server で入手可能です。ただしこの実装はサンプルであり、商用利用は想定していないので注意してください。


4.3.1.2. クレデンシャルイシュアの設定
パラメータ
発行者識別子 https://trial.authlete.net
クレデンシャルエンドポイント https://trial.authlete.net/api/credential
メタデータエンドポイント https://trial.authlete.net/.well-known/openid-credential-issuer
エンティティ設定 https://trial.authlete.net/.well-known/openid-federation

クレデンシャルイシュアのソースコードは認可サーバのソースコードと同じです。


4.3.1.3. クライアントの設定
パラメータ
クライアント識別子 track1_light
クライアントタイプ パブリック (= クライアント認証は要求されない)
リダイレクト URI
  1. https://nextdev-api.authlete.net/api/mock/redirection
  2. eudi-openid4ci://authorize/

このクライアントにリダイレクト URI の追加登録が必要であるか、または自分専用のクライアントが必要であれば、我々にご連絡ください

4.3.2. デモ手順

4.3.2.1. 手順 1 : クレデンシャルオファー

クレデンシャルイシュアのサンプル実装は、開発者がテスト用に任意のクレデンシャルオファーを生成できるウェブページを提供しています。 ページの URL は https://trial.authlete.net/api/offer/issue です。 このウェブページにアクセスすると、クレデンシャルオファーの内容を設定するフォームが表示されます。

以下の指示に従ってフォームを編集してください。

  1. Login ID フィールドと Password フィールドに、 ingainga を入力する。
  2. Credential Configuration IDs を編集する。値は、文字列 potential.light.profile を含む JSON 配列とする。
  3. Authorization Code Grant の横にある “include?” チェックボックスをチェックする。
  4. Pre-Authorized Code Grant の横にある “include?” チェックボックスのチェックを外す。

フォーム編集後に Submit ボタンを押してください。 生成されたクレデンシャルオファーを含む URL を表す QR コードが表示されます。

QR コードの下にある JSON はクレデンシャルオファーの内容を表しています。 JSON 内の issuer_state プロパティの値は発行されたイシュアステートです。 上の例では、イシュアステートの値は tXkAkhSu5N9ORSNES9T64Bd9PAiKn9OmEOT5qDL0lkA です。

イシュアステートは、後ほど実行する認可リクエストに含めることになります。


4.3.2.2. 手順 2 : コードベリファイアとコードチャレンジ

pkce コマンドを用いて、コードベリファイアを生成し、また、対応するコードチャレンジを計算します (RFC 7636 参照)。

git clone git@github.com:authlete/oid4vci-demo.git
cd oid4vci-demo
./pkce

pkce コマンドは生成したコードベリファイアと計算したコードチャレンジを次のように表示します。

CODE_VERIFIER=gzRNlV7DLS_HyKMQKQMrzgYQ8aY3H2rVJ3iIlYK0cjE
CODE_CHALLENGE=j_4gpG9Kr3M7ilMO-MRoSROP-W3h2EZem0KSEU-RAhM

計算したコードチャレンジは次の手順で認可リクエストに含めることになります。 また、生成したコードベリファイアは認可リクエストの後に実行するトークンリクエストに含めることになります。


4.3.2.3. 手順 3 : 認可リクエスト

認可コードフロー (RFC 6749, 4.1 参照) を用いて認可リクエストを実行します。 次の URL をお使いのウェブブラウザのアドレスバーに入力してください。URL 内の ${ISSUER_STATE}${CODE_CHALLENGE} は、これまでの手順で生成したイシュアステートとコードチャレンジの実際の値で置き換えてください。

https://trial.authlete.net/api/authorization?client_id=track1_light&response_type=code&issuer_state=${ISSUER_STATE}&redirect_uri=https://nextdev-api.authlete.net/api/mock/redirection&code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256&prompt=login

URL にアクセスすると、認可ページが表示されます。 このページにはログインフォームがあるので、Login ID フィールドと Password フィールドに ingainga と入力し、Authorize ボタンを押してください。

リダイレクションエンドポイント (RFC 6749, 3.1.2 参照) にリダイレクトされます。

このリダイレクションエンドポイントは、受け取ったキー・バリューの組を表示します。 表示されている code パラメータの値が発行された認可コードです。 この例では、認可コードの値は gR43MQf2olvhMt6KekVDkUOdQPrVYgBiKXMwu_UFnB8 です。

認可コードは次のセクションのトークンリクエストで使います。 認可コードは 10 分で期限切れとなるため、すみやかにトークンリクエスト実行しなければならないことに注意してください。


4.3.2.4. 手順 4 : トークンリクエスト

認可コードフローを用いてトークンリクエストを実行します。下記の curl コマンド内の ${AUTHORIZATION_CODE}${CODE_VERIFIER} は、これまでの手順で取得した認可コードとコードベリファイアの実際の値で置き換えてください。

curl -s https://trial.authlete.net/api/token \
     -d client_id=track1_light \
     -d grant_type=authorization_code \
     -d code=${AUTHORIZATION_CODE} \
     -d redirect_uri=https://nextdev-api.authlete.net/api/mock/redirection \
     -d code_verifier=${CODE_VERIFIER}

トークンリクエストが有効であれば、トークンエンドポイントは次のような JSON を返します。

{
  "access_token": "tNR1stglRuBVUtS2sZ7tiThPcpNDENxY0LniVpRdp0E",
  "token_type": "Bearer",
  "expires_in": 86400,
  "scope": null,
  "refresh_token": "nt3ba0H42UqMBw8FMY2keDgGAF65pBHY45-kydZFmSE",
  "c_nonce": "v-1b-n82kEJGbHROSekGsmR-xEuamCxY_T0tXtQN-dY",
  "c_nonce_expires_in": 86400
}

JSON 内の access_token プロパティの値は発行されたアクセストークンです。 これは、クレデンシャルリクエストを実行する際に提示する必要があります。

c_nonce プロパティの値は鍵証明に含めなければならないノンス値です。


4.3.2.5. 手順 5 : CWT 鍵証明

authlete/cbor ライブラリには、CWT 鍵証明を生成するためのユーティリティクラス CWTKeyProofBuilder が含まれています。当ライブラリのレポジトリに含まれるシェルスクリプト bin/generate-cwt-key-proof は、このユーティリティクラスをコマンドラインから起動するためのラッパーです。

次に示すように CWT 鍵証明を生成することができます。コマンドライン内の ${NONCE} を、 先の手順でトークンエンドポイントから発行された c_nonce の値で置き換えてください。 また、${PRIVATE_KEY_FILE} を、JWK フォーマット (RFC 7517 参照) で秘密鍵を含むファイルの実際のパスで置き換えてください。 authlete/oid4vci-demo レポジトリ内の holder.jwk ファイルをこの用途で使うことができます。

git clone git@github.com:authlete/cbor.git
cd cbor
mvn compile
./bin/generate-cwt-key-proof \
    --issuer https://trial.authlete.net \
    --key ${PRIVATE_KEY_FILE} \
    --client track1_light \
    --nonce ${NONCE}

generate-cwt-key-proof スクリプトは次のように CWT 鍵証明を表示します。

2D3ShFifowEmA3RvcGVuaWQ0dmNpLXByb29mK2N3dGhDT1NFX0tleVh7pgECAlgrMWU1QVk5RXlCMDFYblV6YTZMcEp6azAybjZZX0FtbW5TYjBGQmVOVlZyVQMmIAEhWCA9LFCsPbOXT-ZdwCrPWaCpJ4GgGGebHbLEESmsFjwXbSJYIMVfH24tRUqLFLpy3rizbi5CYqpmOkyojJ7q_hp9sEddoFhgpAFsdHJhY2sxX2xpZ2h0A3gaaHR0cHM6Ly90cmlhbC5hdXRobGV0ZS5uZXQGGmZf3KsKWCt2LTFiLW44MmtFSkdiSFJPU2VrR3NtUi14RXVhbUN4WV9UMHRYdFFOLWRZWEDVVsA-MQ9dAiaIRThTJ5JgmND4RZuhxcIiNx04TZ7fSqlQYJlRW9AyNqXeJHIEl1KqQs_yZtlPd98kRbvziTEi

この CWT 鍵証明を CBOR Zone を用いてデコードすると、次のようになります。

61(18(/ COSE_Sign1 / [
  / protected / <<
    {
      1: -7,
      3: "openid4vci-proof+cwt",
      "COSE_Key": h'a6010202582b3165354159394579423031586e557a61364c704a7a6b30326e36595f416d6d6e5362304642654e56567255032620012158203d2c50ac3db3974fe65dc02acf59a0a92781a018679b1db2c41129ac163c176d225820c55f1f6e2d454a8b14ba72deb8b36e2e4262aa663a4ca88c9eeafe1a7db0475d'
    }
  >>,
  / unprotected / {
  },
  h'a4016c747261636b315f6c6967687403781a68747470733a2f2f747269616c2e617574686c6574652e6e6574061a665fdcab0a582b762d31622d6e38326b454a476248524f53656b47736d522d784575616d4378595f5430745874514e2d6459',
  h'd556c03e310f5d02268845385327926098d0f8459ba1c5c222371d384d9edf4aa9506099515bd03236a5de2472049752aa42cff266d94f77df2445bbf3893122'
]))

保護ヘッダー内の COSE_Key の値は CBOR byte string で、COSE 鍵を内包しています。 この byte string をデコードすると、次の COSE 鍵が得られます。

{
  1: 2,
  2: h'3165354159394579423031586e557a61364c704a7a6b30326e36595f416d6d6e5362304642654e56567255',
  3: -7,
  -1: 1,
  -2: h'3d2c50ac3db3974fe65dc02acf59a0a92781a018679b1db2c41129ac163c176d',
  -3: h'c55f1f6e2d454a8b14ba72deb8b36e2e4262aa663a4ca88c9eeafe1a7db0475d'
}

4.3.2.6. 手順 6 : クレデンシャルリクエスト

アクセストークンと CWT 鍵証明を使い、クレデンシャルリクエストを実行します。 次のコマンドライン内の ${ACCESS_TOKEN}${CWT_KEY_PROOF} は実際の値で置き換えてください。

curl -s https://trial.authlete.net/api/credential \
     -H "Authorization: Bearer ${ACCESS_TOKEN}" \
     -H "Content-Type: application/json" \
     --data '{
  "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": {}
    }
  },
  "proof": {
    "proof_type": "cwt",
    "cwt":"'${CWT_KEY_PROOF}'"
  }
}'

クレデンシャルリクエストが有効であれば、クレデンシャルエンドポイントは次のような JSON を返します。 JSON 内の credential プロパティの値が発行された Verifiable Credential (VC) です。

{
  "credential": "ompuYW1lU3BhY2VzoXFvcmcuaXNvLjE4MDEzLjUuMYjYGFhbpGhkaWdlc3RJRAFmcmFuZG9tUEJDfxiBFQGMwsBY7jE6mkdxZWxlbWVudElkZW50aWZpZXJqaXNzdWVfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMjAyNC0wNi0wNdgYWFykaGRpZ2VzdElEAmZyYW5kb21QuWRGth4zjRXOJN_iGNTy0nFlbGVtZW50SWRlbnRpZmllcmtleHBpcnlfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMjAyNS0wNi0wNdgYWFqkaGRpZ2VzdElEA2ZyYW5kb21Q7Zx7xYZtB0D02nL-x0UGFXFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVrU2lsdmVyc3RvbmXYGFhSpGhkaWdlc3RJRARmcmFuZG9tUPMV5L8B03Uuj0GRMFZvWpJxZWxlbWVudElkZW50aWZpZXJqZ2l2ZW5fbmFtZWxlbGVtZW50VmFsdWVkSW5nYdgYWFukaGRpZ2VzdElEBWZyYW5kb21Q3fLHe4K4bUMJDFsSYKJ513FlbGVtZW50SWRlbnRpZmllcmpiaXJ0aF9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoxOTkxLTExLTA22BhYVaRoZGlnZXN0SUQGZnJhbmRvbVDIzuaMNAe24KBZ3QQpP5o8cWVsZW1lbnRJZGVudGlmaWVyb2lzc3VpbmdfY291bnRyeWxlbGVtZW50VmFsdWViVVPYGFhbpGhkaWdlc3RJRAdmcmFuZG9tUJV-wSoCvKEqVh_g3844LmdxZWxlbWVudElkZW50aWZpZXJvZG9jdW1lbnRfbnVtYmVybGVsZW1lbnRWYWx1ZWgxMjM0NTY3ONgYWKKkaGRpZ2VzdElECGZyYW5kb21QZHMJeAleAPyXtFA-TiWBD3FlbGVtZW50SWRlbnRpZmllcnJkcml2aW5nX3ByaXZpbGVnZXNsZWxlbWVudFZhbHVlgaN1dmVoaWNsZV9jYXRlZ29yeV9jb2RlYUFqaXNzdWVfZGF0ZdkD7GoyMDIzLTAxLTAxa2V4cGlyeV9kYXRl2QPsajIwNDMtMDEtMDFqaXNzdWVyQXV0aIRDoQEmoRghWQFhMIIBXTCCAQSgAwIBAgIGAYyR2cIZMAoGCCqGSM49BAMCMDYxNDAyBgNVBAMMK0oxRndKUDg3QzYtUU5fV1NJT21KQVFjNm41Q1FfYlpkYUZKNUdEblcxUmswHhcNMjMxMjIyMTQwNjU2WhcNMjQxMDE3MTQwNjU2WjA2MTQwMgYDVQQDDCtKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAopVeboJpYRycw1YKkkROXfCpKEKl9Y1YPFhOGj4xTg2UOunxTxSIVkT94qFVIuu1hkEoE2NxelZo3-yTFUODDAKBggqhkjOPQQDAgNHADBEAiBnFjScBcvERleLjMCu5NbxJKkNsa_gQhkXTfDmbq-T3gIgVazbsVdQvZgluc9nJYQxWlzXT9i6f-wgUKx0KCYbj3BZArLYGFkCraZndmVyc2lvbmMxLjBvZGlnZXN0QWxnb3JpdGhtZ1NIQS0yNTZsdmFsdWVEaWdlc3RzoXFvcmcuaXNvLjE4MDEzLjUuMagBWCBtyGKRLbYNCtXpIqSixji4RYcXb4Vf7IDoQta4QfRWsAJYIJVrdxQWwcfqiFi75y5R3Saj8YpA8miZxmeoQL_JtB5-A1ggnoXpxMgIsgiIr8HlJ9JzfalESFVLgFxmES9SqSIsIG0EWCD-4Mo98S8qg8SJ8R-PMO7oCHW3wbdCfU8GGS0nG7VahwVYIE9yvCITC8M8p7-m2M4A5MwokXN3oS97uLkhk2AIj6GRBlgg8IaPGI_7Tp2rf2fLhEq0dDzm71FmTZUPc16BdJsCDgkHWCAKnWjJaTmwvgq1Yon8cLwPaPS1-lOEVASldrxYkeKLcwhYIEXVTVGxIhP9R64iPFGseCD_adyfhYZdCw-eOO8ckRjzbWRldmljZUtleUluZm-iaWRldmljZUtleaYBAgJYKzFlNUFZOUV5QjAxWG5VemE2THBKemswMm42WV9BbW1uU2IwRkJlTlZWclUDJiABIVggPSxQrD2zl0_mXcAqz1mgqSeBoBhnmx2yxBEprBY8F20iWCDFXx9uLUVKixS6ct64s24uQmKqZjpMqIye6v4afbBHXXFrZXlBdXRob3JpemF0aW9uc6FqbmFtZVNwYWNlc4Fxb3JnLmlzby4xODAxMy41LjFnZG9jVHlwZXVvcmcuaXNvLjE4MDEzLjUuMS5tRExsdmFsaWRpdHlJbmZvo2ZzaWduZWTAdDIwMjQtMDYtMDVUMDM6MzU6MTRaaXZhbGlkRnJvbcB0MjAyNC0wNi0wNVQwMzozNToxNFpqdmFsaWRVbnRpbMB0MjAyNS0wNi0wNVQwMzozNToxNFpYQAs0d0MAErcaA1auodhHxivYcqiSXdQW9KtG9HpZoxo_oEPfkf7_dRQm_Z-ffhZn2qbLTc2Op3x0a1R-gif9Mtg",
  "c_nonce": "v-1b-n82kEJGbHROSekGsmR-xEuamCxY_T0tXtQN-dY",
  "c_nonce_expires_in": 86205
}

4.3.3. VC フォーマット

前のセクションの VC を CBOR 診断記法で表すと次のようになります。

{
  "nameSpaces": {
    "org.iso.18013.5.1": [
      24(<<
        {
          "digestID": 1,
          "random": h'42437f188115018cc2c058ee313a9a47',
          "elementIdentifier": "issue_date",
          "elementValue": 1004("2024-06-05")
        }
      >>),
      24(<<
        {
          "digestID": 2,
          "random": h'b96446b61e338d15ce24dfe218d4f2d2',
          "elementIdentifier": "expiry_date",
          "elementValue": 1004("2025-06-05")
        }
      >>),
      24(<<
        {
          "digestID": 3,
          "random": h'ed9c7bc5866d0740f4da72fec7450615',
          "elementIdentifier": "family_name",
          "elementValue": "Silverstone"
        }
      >>),
      24(<<
        {
          "digestID": 4,
          "random": h'f315e4bf01d3752e8f419130566f5a92',
          "elementIdentifier": "given_name",
          "elementValue": "Inga"
        }
      >>),
      24(<<
        {
          "digestID": 5,
          "random": h'ddf2c77b82b86d43090c5b1260a279d7',
          "elementIdentifier": "birth_date",
          "elementValue": 1004("1991-11-06")
        }
      >>),
      24(<<
        {
          "digestID": 6,
          "random": h'c8cee68c3407b6e0a059dd04293f9a3c',
          "elementIdentifier": "issuing_country",
          "elementValue": "US"
        }
      >>),
      24(<<
        {
          "digestID": 7,
          "random": h'957ec12a02bca12a561fe0dfce382e67',
          "elementIdentifier": "document_number",
          "elementValue": "12345678"
        }
      >>),
      24(<<
        {
          "digestID": 8,
          "random": h'64730978095e00fc97b4503e4e25810f',
          "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'
    },
    h'd8185902ada66776657273696f6e63312e306f646967657374416c676f726974686d675348412d3235366c76616c756544696765737473a1716f72672e69736f2e31383031332e352e31a80158206dc862912db60d0ad5e922a4a2c638b84587176f855fec80e842d6b841f456b0025820956b771416c1c7ea8858bbe72e51dd26a3f18a40f26899c667a840bfc9b41e7e0358209e85e9c4c808b20888afc1e527d2737da94448554b805c66112f52a9222c206d045820fee0ca3df12f2a83c489f11f8f30eee80875b7c1b7427d4f06192d271bb55a870558204f72bc22130bc33ca7bfa6d8ce00e4cc28917377a12f7bb8b9219360088fa191065820f0868f188ffb4e9dab7f67cb844ab4743ce6ef51664d950f735e81749b020e090758200a9d68c96939b0be0ab56289fc70bc0f68f4b5fa53845404a576bc5891e28b7308582045d54d51b12213fd47ae223c51ac7820ff69dc9f85865d0b0f9e38ef1c9118f36d6465766963654b6579496e666fa2696465766963654b6579a6010202582b3165354159394579423031586e557a61364c704a7a6b30326e36595f416d6d6e5362304642654e56567255032620012158203d2c50ac3db3974fe65dc02acf59a0a92781a018679b1db2c41129ac163c176d225820c55f1f6e2d454a8b14ba72deb8b36e2e4262aa663a4ca88c9eeafe1a7db0475d716b6579417574686f72697a6174696f6e73a16a6e616d6553706163657381716f72672e69736f2e31383031332e352e3167646f6354797065756f72672e69736f2e31383031332e352e312e6d444c6c76616c6964697479496e666fa3667369676e6564c074323032342d30362d30355430333a33353a31345a6976616c696446726f6dc074323032342d30362d30355430333a33353a31345a6a76616c6964556e74696cc074323032352d30362d30355430333a33353a31345a',
    h'0b3477430012b71a0356aea1d847c62bd872a8925dd416f4ab46f47a59a31a3fa043df91feff751426fd9f9f7e1667daa6cb4dcd8ea77c746b547e8227fd32d8'
  ]
}

この例では、VC は IssuerSigned 構造を表しています。この構造は、 ISO/IEC 18013-5:2021 のセクション “8.3.2.1.2.2 Device retrieval mdoc response” で次のように定義されています。

IssuerSigned = {
  ? "nameSpaces" : IssuerNameSpaces,
  "issuerAuth" : IssuerAuth
}

また、IssuerAuth 構造および関連する構造は次のように定義されています。

IssuerAuth = COSE_Sign1 ; The payload is MobileSecurityObjectBytes

MobileSecurityObjectBytes = #6.24(bstr .cbor MobileSecurityObject)

MobileSecurityObject = {
    "version" : tstr, ; Version of the MobileSecurityObject
    "digestAlgorithm" : tstr, ; Message digest algorithm used
    "valueDigests" : ValueDigests, ; Digests of all data elements per namespace
    "deviceKeyInfo" : DeviceKeyInfo,
    "docType" : tstr, ; docType as used in Documents
    "validityInfo" : ValidityInfo
}

DeviceKeyInfo = {
    "deviceKey" : DeviceKey
    ? "keyAuthorizations" : KeyAuthorizations,
    ? "keyInfo" : KeyInfo
}

DeviceKey = COSE_Key

IssuerAuthMobileSecurityObjectBytes の定義は、(CBOR タグで始まる) MobileSecurityObjectBytesCOSE_Sign1 のペイロードとして直接使われる印象を与えます。しかし、 MobileSecurityObjectBytes をさらに byte string に変換する必要があります。

ですので、h'd81859 で始まる、"issuerAuth" 配列の三番目の要素は、 MobileSecurityObjectBytes を含む byte string を表しています。 その byte string をデコードすることで、次の CBOR 構造を見ることができます。

24(<<
  {
    "version": "1.0",
    "digestAlgorithm": "SHA-256",
    "valueDigests": {
      "org.iso.18013.5.1": {
        1: h'6dc862912db60d0ad5e922a4a2c638b84587176f855fec80e842d6b841f456b0',
        2: h'956b771416c1c7ea8858bbe72e51dd26a3f18a40f26899c667a840bfc9b41e7e',
        3: h'9e85e9c4c808b20888afc1e527d2737da94448554b805c66112f52a9222c206d',
        4: h'fee0ca3df12f2a83c489f11f8f30eee80875b7c1b7427d4f06192d271bb55a87',
        5: h'4f72bc22130bc33ca7bfa6d8ce00e4cc28917377a12f7bb8b9219360088fa191',
        6: h'f0868f188ffb4e9dab7f67cb844ab4743ce6ef51664d950f735e81749b020e09',
        7: h'0a9d68c96939b0be0ab56289fc70bc0f68f4b5fa53845404a576bc5891e28b73',
        8: h'45d54d51b12213fd47ae223c51ac7820ff69dc9f85865d0b0f9e38ef1c9118f3'
      }
    },
    "deviceKeyInfo": {
      "deviceKey": {
        1: 2,
        2: h'3165354159394579423031586e557a61364c704a7a6b30326e36595f416d6d6e5362304642654e56567255',
        3: -7,
        -1: 1,
        -2: h'3d2c50ac3db3974fe65dc02acf59a0a92781a018679b1db2c41129ac163c176d',
        -3: h'c55f1f6e2d454a8b14ba72deb8b36e2e4262aa663a4ca88c9eeafe1a7db0475d'
      },
      "keyAuthorizations": {
        "nameSpaces": [
          "org.iso.18013.5.1"
        ]
      }
    },
    "docType": "org.iso.18013.5.1.mDL",
    "validityInfo": {
      "signed": 0("2024-06-05T03:35:14Z"),
      "validFrom": 0("2024-06-05T03:35:14Z"),
      "validUntil": 0("2025-06-05T03:35:14Z")
    }
  }
>>)

注目すべき点は、CWT 鍵証明に埋め込まれた公開鍵が、deviceKey の値として VC 内に現れることです。 CWT 鍵証明内の COSE_Key の値と VC 内の deviceKey の値が同一であることを確認してください。 双方とも次の COSE Key を保持しています。

{
  / kty /  1:  2 / EC2 /,
  / kid /  2: h'3165354159394579423031586e557a61364c704a7a6b30326e36595f416d6d6e5362304642654e56567255',
  / alg /  3: -7 / ES256 /,
  / crv / -1:  1 / P-256 /,
  /  x  / -2: h'3d2c50ac3db3974fe65dc02acf59a0a92781a018679b1db2c41129ac163c176d',
  /  y  / -3: h'c55f1f6e2d454a8b14ba72deb8b36e2e4262aa663a4ca88c9eeafe1a7db0475d'
}

COSE Key 内の整数ラベルと整数値については IANA: CBOR Object Signing and Encryption (COSE) を参照してください。


4.4. POTENTIAL 相互運用性イベント / トラック 2 / Light プロファイル

POTENTIAL はヨーロッパのデジタルアイデンティティに特化したヨーロッパの組織です。 この組織は 2024 年春から相互運用性イベントを開催しています。このイベントは 6 つのトラックに分かれています。 トラック 1 と 2 は、クレデンシャルイシュアの相互運用性をテストするためのトラックです。 トラック 1 では VC の形式として mdoc が使用され、トラック 2 では SD-JWT VC が使用されます。

トラック 2 では 2 つのプロファイルが定義されています。 1 つは Light プロファイルと呼ばれ、もう 1 つは Full プロファイルと呼ばれます。 このセクションでは Light プロファイルの手順を説明します。


4.4.1. 設定


4.4.1.1. 認可サーバの設定
パラメータ
発行者識別子 https://trial.authlete.net
認可エンドポイント https://trial.authlete.net/api/authorization
トークンエンドポイント https://trial.authlete.net/api/token
ディスカバリエンドポイント https://trial.authlete.net/.well-known/openid-configuration
エンティティ設定 https://trial.authlete.net/.well-known/openid-federation

認可サーバのソースコードは https://github.com/authlete/java-oauth-server で入手可能です。ただしこの実装はサンプルであり、商用利用は想定していないので注意してください。


4.4.1.2. クレデンシャルイシュアの設定
パラメータ
発行者識別子 https://trial.authlete.net
クレデンシャルエンドポイント https://trial.authlete.net/api/credential
メタデータエンドポイント https://trial.authlete.net/.well-known/openid-credential-issuer
エンティティ設定 https://trial.authlete.net/.well-known/openid-federation

クレデンシャルイシュアのソースコードは認可サーバのソースコードと同じです。


4.4.1.3. クライアントの設定
パラメータ
クライアント識別子 track2_light
クライアントタイプ パブリック (= クライアント認証は要求されない)
リダイレクト URI
  1. https://nextdev-api.authlete.net/api/mock/redirection
  2. eudi-openid4ci://authorize/

このクライアントにリダイレクト URI の追加登録が必要であるか、または自分専用のクライアントが必要であれば、我々にご連絡ください

4.4.2. デモ手順

4.4.2.1. 手順 1 : コードベリファイアとコードチャレンジ

pkce コマンドを用いて、コードベリファイアを生成し、また、対応するコードチャレンジを計算します (RFC 7636 参照)。

git clone git@github.com:authlete/oid4vci-demo.git
cd oid4vci-demo
./pkce

pkce コマンドは生成したコードベリファイアと計算したコードチャレンジを次のように表示します。

CODE_VERIFIER=gzRNlV7DLS_HyKMQKQMrzgYQ8aY3H2rVJ3iIlYK0cjE
CODE_CHALLENGE=j_4gpG9Kr3M7ilMO-MRoSROP-W3h2EZem0KSEU-RAhM

計算したコードチャレンジは次の手順で認可リクエストに含めることになります。 また、生成したコードベリファイアは認可リクエストの後に実行するトークンリクエストに含めることになります。


4.4.2.2. 手順 2 : 認可リクエスト

認可コードフロー (RFC 6749, 4.1 参照) を用いて認可リクエストを実行します。 次の URL をお使いのウェブブラウザのアドレスバーに入力してください。URL 内の ${CODE_CHALLENGE} は、先の手順で生成したコードチャレンジの実際の値で置き換えてください。

https://trial.authlete.net/api/authorization?client_id=track2_light&response_type=code&scope=potential.track2.light.profile&redirect_uri=https://nextdev-api.authlete.net/api/mock/redirection&code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256&prompt=login

URL にアクセスすると、認可ページが表示されます。 このページにはログインフォームがあるので、Login ID フィールドと Password フィールドに ingainga と入力し、Authorize ボタンを押してください。

リダイレクションエンドポイント (RFC 6749, 3.1.2 参照) にリダイレクトされます。

このリダイレクションエンドポイントは、受け取ったキー・バリューの組を表示します。 表示されている code パラメータの値が発行された認可コードです。 この例では、認可コードの値は gR43MQf2olvhMt6KekVDkUOdQPrVYgBiKXMwu_UFnB8 です。

認可コードは次のセクションのトークンリクエストで使います。 認可コードは 10 分で期限切れとなるため、すみやかにトークンリクエスト実行しなければならないことに注意してください。


4.4.2.3. 手順 3 : トークンリクエスト

認可コードフローを用いてトークンリクエストを実行します。下記の curl コマンド内の ${AUTHORIZATION_CODE}${CODE_VERIFIER} は、これまでの手順で取得した認可コードとコードベリファイアの実際の値で置き換えてください。

curl -s https://trial.authlete.net/api/token \
     -d client_id=track2_light \
     -d grant_type=authorization_code \
     -d code=${AUTHORIZATION_CODE} \
     -d redirect_uri=https://nextdev-api.authlete.net/api/mock/redirection \
     -d code_verifier=${CODE_VERIFIER}

トークンリクエストが有効であれば、トークンエンドポイントは次のような JSON を返します。

{
  "access_token": "FvF1eeUbbWwteF5v0nfsO6vb0jlWifgsP5U666qKSGs",
  "token_type": "Bearer",
  "expires_in": 86400,
  "scope": "potential.track2.light.profile",
  "refresh_token": "WUCNjsuU02XhOMWN5qHRkKKbZDX5g2HjJx52GWC_1Gw",
  "c_nonce": "rvw3Mo_ZHyEgYOoWQ9GEotemwFbhRvVVdI6e5Z0lhEs",
  "c_nonce_expires_in": 86400
}

JSON 内の access_token プロパティの値は発行されたアクセストークンです。 これは、クレデンシャルリクエストを実行する際に提示する必要があります。

c_nonce プロパティの値は鍵証明に含めなければならないノンス値です。


4.4.2.4. 手順 4 : JWT 鍵証明

保有者の鍵 holder.jwkgenerate-key-proof スクリプトを用いて、JWT 形式の『鍵証明』を生成します。 この JWK ファイルとスクリプトは oid4vci-demo レポジトリに含まれています。

次のコマンドライン内の $C_NONCE は、先の手順で受け取ったトークンレスポンスに含まれる c_nonce プロパティの実際の値で置き換えてください。

./generate-key-proof \
    -i https://trial.authlete.net \
    -k holder.jwk \
    -c track2_light \
    -n $C_NONCE

generate-key-proof スクリプトは次のような鍵証明を生成します。

eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiUFN4UXJEMnpsMF9tWGNBcXoxbWdxU2VCb0Jobm14Mnl4QkVwckJZOEYyMCIsInkiOiJ4VjhmYmkxRlNvc1V1bkxldUxOdUxrSmlxbVk2VEtpTW51ci1HbjJ3UjEwIn19.eyJpc3MiOiJ0cmFjazJfbGlnaHQiLCJhdWQiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsImlhdCI6MTcxNzYxNzk1Nywibm9uY2UiOiJydnczTW9fWkh5RWdZT29XUTlHRW90ZW13RmJoUnZWVmRJNmU1WjBsaEVzIn0.E-9pdaSW2oaFqI2V0N1aRiSRI3LzOxwQFNR5tewaLXxP8R7ZHrU9-M7TLuqP5OmWRecdFrJ9yQAM83kbc4f5-A

鍵証明のヘッダとペイロードを base64url でデコードすると、次の JSON が得られます。

{
  "typ": "openid4vci-proof+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "PSxQrD2zl0_mXcAqz1mgqSeBoBhnmx2yxBEprBY8F20",
    "y": "xV8fbi1FSosUunLeuLNuLkJiqmY6TKiMnur-Gn2wR10"
  }
}
{
  "iss": "track2_light",
  "aud": "https://trial.authlete.net",
  "iat": 1717617957,
  "nonce": "rvw3Mo_ZHyEgYOoWQ9GEotemwFbhRvVVdI6e5Z0lhEs"
}

generate-key-proof スクリプトの実行結果は、次のようにすることで直接シェル変数 JWT_KEY_PROOF に設定することができます。

JWT_KEY_PROOF=`./generate-key-proof -i https://trial.authlete.net -k holder.jwk -c track2_light -n $C_NONCE`

4.4.2.5. 手順 5 : クレデンシャルリクエスト

アクセストークンと JWT 鍵証明を使い、クレデンシャルリクエストを実行します。 次のコマンドライン内の ${ACCESS_TOKEN}${JWT_KEY_PROOF} は実際の値で置き換えてください。

curl -s https://trial.authlete.net/api/credential \
     -H "Authorization: Bearer ${ACCESS_TOKEN}" \
     -H "Content-Type: application/json" \
       --data '{
  "format": "vc+sd-jwt",
  "vct": "urn:eu.europa.ec.eudi:pid:1",
  "proof": {
    "proof_type": "jwt",
    "jwt":"'${JWT_KEY_PROOF}'"
  }
}'

クレデンシャルリクエストが有効であれば、クレデンシャルエンドポイントは次のような JSON を返します。 JSON 内の credential プロパティの値が発行された Verifiable Credential (VC) です。

{
  "credential": "eyJraWQiOiJKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrIiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiRUFiQW1SN1owN1dpMVczczZLSVROMFdhamFzUTlObEJmYm9ocjgtU3hfcyIsImJCZ19NSWlKNnJhTU9jZ0dEd2ZtSUFMSEpFU2NJTEMyRjhCTmpOVW1MdG8iXX0sIl9zZCI6WyIxQThmRmJKMVlhM3RmQ3ZEUXpLNHNMSUhMRFJERWJEYk1oUEVmcF9TRzVVIiwiMjhqTDd3SFV0cTBuMXdaQUpmY1doTDVOWkt5QmtMMFRVM3NpaVNPbGV6WSIsIjJjcl9FNTM2cU1RMDB3Z1BTMURfbHZyZUFhc21tc3B4YU52T1Ffd3ZtTEEiLCI1dXE2ei13QWhaZUJEMkt3VjFONU5lcURqSWpXVHpqQi0xVU91anZOUllZIiwiNndhVlRLelZpMWZoMUxQc0ZfbURKdWJrcmFhZGpnUEtYTUdJN1B6blYwZyIsIjc4cmN0UnNtRWFuV1oybnZPVFdyNVlvWEZ5eW50ZklxMU1OOGp1bXJCOG8iLCI4OHZ3c1VVbHI1RmFiUGhPNVhhY3dIdG5nTXVTdjREVmFfa0E5OUUxa3lzIiwiOUd6YU4xZWc1NlczVEN4aGxwOWZLU093SFNfbmhEeEYxVnJTbEg3Z3ExRSIsIkJ3WkZRVENqVk0xRW5Ic1VIeXQzZ3JjdGhWMlJIVnFkTGJUdjMtLUVrOGciLCJFZDdVbFhYS0sydXdsblJNdHpSZHRnOFQwbVduUXhBOVVOblBldEt1T3VjIiwiRk56eFdmSGRhN0hXcEdkQWpJM1R3WnhXeE5YR0xwNHdqcEd1X0NqR0FYYyIsIkduSUZGOFg5TlpCbVRKQ2E5OUx4UFFST0c1YzZYUWsyemJ1NG9jbG45SWsiLCJMc2ZscHFUTFg4T0pYYk53eTdxcDU3MHRKY0I5T0dQeE9qTUxEU21BUTJJIiwiZHNKVjBHZ2JfZlVjeGlPalRvVUlDaEpGR1F5aFJMZHp2WWltQmNlSTN6YyIsImgydlN3UVN0SV9LcGtxU1djN011TG44VlB5a0VjelJFWjNEZ1hUd3QzM1EiLCJqQ0l4Z0lkY2JOUnVDbUJDeF9DQVQwZ2FvZURCU29VQnl4XzdfRFUxQmRvIiwia3RlSGlZdlhfbWphR0s0YkU4YjYyRktnUnZ2bFRCTVNxLXhpVlJQZTB6USIsInB5dXJrNU9zeGtYS2FoZ2lkbXdrTU56NTBUaWtGLV8zRkY0WG9SYlg0bDQiLCJ2VHc5VlpSOFpNQW0zWkhxZGJxNVBxSVBBZ0M2bVlyWGF4YmFkekFQOGpZIl0sImFkZHJlc3MiOnsiX3NkIjpbIjBXYUJrS3U4Q1lPV1NldjFDTThsYVNwS3VCbTZSTW1odktSR3kyWWRjaWMiLCJNbTdWX1dXRThVSk1TSHV0aV8xd0Nhb0puLUxKcTdaWG5FSDY1dXUxRTlrIiwiUFZKYmFmZjVzWmJCYm1HY2dkRWdOOXZsTWRndU9aal8yMjVYUk0xTHR3RSIsImlRT1pBN2RXQWVUaTg1RE1RQ1N0UmkySnVzSHB6YVJEN0FVRXZPQTJWN1UiLCJtMGVhYlEyTDViTEVPY3UwYjRRTkZQeWNvTHluUDAzYkxoRm9zTVdLeXRRIiwibXV0TU9aLVgxQlRoenM2OXdxQ3E3RFRieHFKNjRnWG9Yb2xHbnpyOUlmZyIsIm5LaXRPS3dBNW9oZ0QtNnVPNERIMW8wQnRoN0tWSndJaXlVSUhfcmJLLUUiLCJyQk15UlR6TFZkQV9uY291bDNLenN1cVluYWxLX0lnYWFMV0Z3aS14MjdzIiwidTgyUXhEemxyRVlsQWlSNUV1bGJ3ZWJSaFNnYWVaZ0o5dkEyRmd4M3ZvVSJdfSwidmN0IjoidXJuOmV1LmV1cm9wYS5lYy5ldWRpOnBpZDoxIiwiX3NkX2FsZyI6InNoYS0yNTYiLCJpc3MiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsImNuZiI6eyJqd2siOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsImtpZCI6IjRNOWtJckI5V1l6dDFHUWdMMTJsemRCWnNHeWVWM2xnUEtvdjI4b1Q1TDQiLCJ4IjoiUFN4UXJEMnpsMF9tWGNBcXoxbWdxU2VCb0Jobm14Mnl4QkVwckJZOEYyMCIsInkiOiJ4VjhmYmkxRlNvc1V1bkxldUxOdUxrSmlxbVk2VEtpTW51ci1HbjJ3UjEwIn19LCJpYXQiOjE3MTc2MTg2NjEsImFnZV9lcXVhbF9vcl9vdmVyIjp7Il9zZCI6WyIzZGo2NXpOMTMzS2haN1QwV1RidHczSVhialh4VHRIZUtoMlRWOWJVMzRjIiwiRFJUNi0tU2YtNDU4bGNaOEdobGd5TU5TZUVyNHRya3R1ZVhkNXNzNGtPRSJdfX0.EaUDXo2hWBNOqsdYBDAWpuHDYfGm9xNepZJn7rafTGAb3kHVxNJoSZTvOvLhk_sy30GpK2kLFWvVlR1nzxSMVg~WyJyeHR0ck5MbXUwNWc5cjVZVmc3bUtnIiwic3ViIiwiMTAwNCJd~WyJaR2lyT3Y3dUdLVXZOWUpVcFZ0QXJBIiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd~WyIwcGt4eEVwVmIwdW5mQUQ1R1VsZXVnIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ~WyI2RWtWSGN2U0lIZ215akE0VW5fZlNRIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd~WyJhUzhNbVlFWHVWR1A5ODYwQV9lZDZRIiwiMTgiLHRydWVd~WyJMX1JRdm00Mll0XzdESHVjTi1IUHRRIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd~WyJmQmpkVWRTV0NBX0o4NXF2anlmcXVBIiwiZm9ybWF0dGVkIiwiMTE0IE9sZCBTdGF0ZSBId3kgMTI3LCBTaG9zaG9uZSwgQ0EgOTIzODQsIFVTQSJd~WyJIOGVpZ0UxckQ2d3o3RDA3b0pnNjl3Iiwic3RyZWV0X2FkZHJlc3MiLCIxMTQgT2xkIFN0YXRlIEh3eSAxMjciXQ~WyIxdngwVjJmTUVrTTBXNnVCYWg5VjRBIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd~WyJHVnZfZWV0VElPLW93NTgzRTRMSDBBIiwicG9zdGFsX2NvZGUiLCJDQSA5MjM4NCJd~WyJrYjJYUkhsMTQxazFyZG1xeGdDM2xRIiwiY291bnRyeSIsIlVTQSJd~WyJLU0VlbEV0dU5qbzZHQWR6NTFqSG93IiwiaXNzdWluZ19hdXRob3JpdHkiLCJVUyJd~WyJ0ZzBZQ3RNeHNPSW54aW9pUExDNV93IiwiaXNzdWluZ19jb3VudHJ5IiwiVVMiXQ~",
  "c_nonce": "rvw3Mo_ZHyEgYOoWQ9GEotemwFbhRvVVdI6e5Z0lhEs",
  "c_nonce_expires_in": 83596
}

4.4.3. VC フォーマット

レスポンス内の credential パラメーターの値が発行された SD-JWT VC です。 この SD-JWT VC がシェル変数 SD_JWT に設定されている場合、decode-sd-jwt スクリプトを次のように起動することで、SD-JWT VC の内容をデコードすることができます。

./decode-sd-jwt $SD_JWT

結果は次のようになります。

{
  "kid": "J1FwJP87C6-QN_WSIOmJAQc6n5CQ_bZdaFJ5GDnW1Rk",
  "typ": "vc+sd-jwt",
  "alg": "ES256"
}
{
  "place_of_birth": {
    "_sd": [
      "EAbAmR7Z07Wi1W3s6KITN0WajasQ9NlBfbohr8-Sx_s",
      "bBg_MIiJ6raMOcgGDwfmIALHJEScILC2F8BNjNUmLto"
    ]
  },
  "_sd": [
    "1A8fFbJ1Ya3tfCvDQzK4sLIHLDRDEbDbMhPEfp_SG5U",
    "28jL7wHUtq0n1wZAJfcWhL5NZKyBkL0TU3siiSOlezY",
    "2cr_E536qMQ00wgPS1D_lvreAasmmspxaNvOQ_wvmLA",
    "5uq6z-wAhZeBD2KwV1N5NeqDjIjWTzjB-1UOujvNRYY",
    "6waVTKzVi1fh1LPsF_mDJubkraadjgPKXMGI7PznV0g",
    "78rctRsmEanWZ2nvOTWr5YoXFyyntfIq1MN8jumrB8o",
    "88vwsUUlr5FabPhO5XacwHtngMuSv4DVa_kA99E1kys",
    "9GzaN1eg56W3TCxhlp9fKSOwHS_nhDxF1VrSlH7gq1E",
    "BwZFQTCjVM1EnHsUHyt3grcthV2RHVqdLbTv3--Ek8g",
    "Ed7UlXXKK2uwlnRMtzRdtg8T0mWnQxA9UNnPetKuOuc",
    "FNzxWfHda7HWpGdAjI3TwZxWxNXGLp4wjpGu_CjGAXc",
    "GnIFF8X9NZBmTJCa99LxPQROG5c6XQk2zbu4ocln9Ik",
    "LsflpqTLX8OJXbNwy7qp570tJcB9OGPxOjMLDSmAQ2I",
    "dsJV0Ggb_fUcxiOjToUIChJFGQyhRLdzvYimBceI3zc",
    "h2vSwQStI_KpkqSWc7MuLn8VPykEczREZ3DgXTwt33Q",
    "jCIxgIdcbNRuCmBCx_CAT0gaoeDBSoUByx_7_DU1Bdo",
    "kteHiYvX_mjaGK4bE8b62FKgRvvlTBMSq-xiVRPe0zQ",
    "pyurk5OsxkXKahgidmwkMNz50TikF-_3FF4XoRbX4l4",
    "vTw9VZR8ZMAm3ZHqdbq5PqIPAgC6mYrXaxbadzAP8jY"
  ],
  "address": {
    "_sd": [
      "0WaBkKu8CYOWSev1CM8laSpKuBm6RMmhvKRGy2Ydcic",
      "Mm7V_WWE8UJMSHuti_1wCaoJn-LJq7ZXnEH65uu1E9k",
      "PVJbaff5sZbBbmGcgdEgN9vlMdguOZj_225XRM1LtwE",
      "iQOZA7dWAeTi85DMQCStRi2JusHpzaRD7AUEvOA2V7U",
      "m0eabQ2L5bLEOcu0b4QNFPycoLynP03bLhFosMWKytQ",
      "mutMOZ-X1BThzs69wqCq7DTbxqJ64gXoXolGnzr9Ifg",
      "nKitOKwA5ohgD-6uO4DH1o0Bth7KVJwIiyUIH_rbK-E",
      "rBMyRTzLVdA_ncoul3KzsuqYnalK_IgaaLWFwi-x27s",
      "u82QxDzlrEYlAiR5EulbwebRhSgaeZgJ9vA2Fgx3voU"
    ]
  },
  "vct": "urn:eu.europa.ec.eudi:pid:1",
  "_sd_alg": "sha-256",
  "iss": "https://trial.authlete.net",
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "kid": "4M9kIrB9WYzt1GQgL12lzdBZsGyeV3lgPKov28oT5L4",
      "x": "PSxQrD2zl0_mXcAqz1mgqSeBoBhnmx2yxBEprBY8F20",
      "y": "xV8fbi1FSosUunLeuLNuLkJiqmY6TKiMnur-Gn2wR10"
    }
  },
  "iat": 1717618661,
  "age_equal_or_over": {
    "_sd": [
      "3dj65zN133KhZ7T0WTbtw3IXbjXxTtHeKh2TV9bU34c",
      "DRT6--Sf-458lcZ8GhlgyMNSeEr4trktueXd5ss4kOE"
    ]
  }
}
{
  "digest": "GnIFF8X9NZBmTJCa99LxPQROG5c6XQk2zbu4ocln9Ik",
  "WyJyeHR0ck5MbXUwNWc5cjVZVmc3bUtnIiwic3ViIiwiMTAwNCJd": [
    "rxttrNLmu05g9r5YVg7mKg",
    "sub",
    "1004"
  ]
}
{
  "digest": "jCIxgIdcbNRuCmBCx_CAT0gaoeDBSoUByx_7_DU1Bdo",
  "WyJaR2lyT3Y3dUdLVXZOWUpVcFZ0QXJBIiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd": [
    "ZGirOv7uGKUvNYJUpVtArA",
    "family_name",
    "Silverstone"
  ]
}
{
  "digest": "h2vSwQStI_KpkqSWc7MuLn8VPykEczREZ3DgXTwt33Q",
  "WyIwcGt4eEVwVmIwdW5mQUQ1R1VsZXVnIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ": [
    "0pkxxEpVb0unfAD5GUleug",
    "given_name",
    "Inga"
  ]
}
{
  "digest": "dsJV0Ggb_fUcxiOjToUIChJFGQyhRLdzvYimBceI3zc",
  "WyI2RWtWSGN2U0lIZ215akE0VW5fZlNRIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd": [
    "6EkVHcvSIHgmyjA4Un_fSQ",
    "birthdate",
    "1991-11-06"
  ]
}
{
  "digest": "DRT6--Sf-458lcZ8GhlgyMNSeEr4trktueXd5ss4kOE",
  "WyJhUzhNbVlFWHVWR1A5ODYwQV9lZDZRIiwiMTgiLHRydWVd": [
    "aS8MmYEXuVGP9860A_ed6Q",
    "18",
    true
  ]
}
{
  "digest": "EAbAmR7Z07Wi1W3s6KITN0WajasQ9NlBfbohr8-Sx_s",
  "WyJMX1JRdm00Mll0XzdESHVjTi1IUHRRIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd": [
    "L_RQvm42Yt_7DHucN-HPtQ",
    "locality",
    "Shoshone"
  ]
}
{
  "digest": "rBMyRTzLVdA_ncoul3KzsuqYnalK_IgaaLWFwi-x27s",
  "WyJmQmpkVWRTV0NBX0o4NXF2anlmcXVBIiwiZm9ybWF0dGVkIiwiMTE0IE9sZCBTdGF0ZSBId3kgMTI3LCBTaG9zaG9uZSwgQ0EgOTIzODQsIFVTQSJd": [
    "fBjdUdSWCA_J85qvjyfquA",
    "formatted",
    "114 Old State Hwy 127, Shoshone, CA 92384, USA"
  ]
}
{
  "digest": "iQOZA7dWAeTi85DMQCStRi2JusHpzaRD7AUEvOA2V7U",
  "WyJIOGVpZ0UxckQ2d3o3RDA3b0pnNjl3Iiwic3RyZWV0X2FkZHJlc3MiLCIxMTQgT2xkIFN0YXRlIEh3eSAxMjciXQ": [
    "H8eigE1rD6wz7D07oJg69w",
    "street_address",
    "114 Old State Hwy 127"
  ]
}
{
  "digest": "Mm7V_WWE8UJMSHuti_1wCaoJn-LJq7ZXnEH65uu1E9k",
  "WyIxdngwVjJmTUVrTTBXNnVCYWg5VjRBIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd": [
    "1vx0V2fMEkM0W6uBah9V4A",
    "locality",
    "Shoshone"
  ]
}
{
  "digest": "PVJbaff5sZbBbmGcgdEgN9vlMdguOZj_225XRM1LtwE",
  "WyJHVnZfZWV0VElPLW93NTgzRTRMSDBBIiwicG9zdGFsX2NvZGUiLCJDQSA5MjM4NCJd": [
    "GVv_eetTIO-ow583E4LH0A",
    "postal_code",
    "CA 92384"
  ]
}
{
  "digest": "m0eabQ2L5bLEOcu0b4QNFPycoLynP03bLhFosMWKytQ",
  "WyJrYjJYUkhsMTQxazFyZG1xeGdDM2xRIiwiY291bnRyeSIsIlVTQSJd": [
    "kb2XRHl141k1rdmqxgC3lQ",
    "country",
    "USA"
  ]
}
{
  "digest": "88vwsUUlr5FabPhO5XacwHtngMuSv4DVa_kA99E1kys",
  "WyJLU0VlbEV0dU5qbzZHQWR6NTFqSG93IiwiaXNzdWluZ19hdXRob3JpdHkiLCJVUyJd": [
    "KSEelEtuNjo6GAdz51jHow",
    "issuing_authority",
    "US"
  ]
}
{
  "digest": "kteHiYvX_mjaGK4bE8b62FKgRvvlTBMSq-xiVRPe0zQ",
  "WyJ0ZzBZQ3RNeHNPSW54aW9pUExDNV93IiwiaXNzdWluZ19jb3VudHJ5IiwiVVMiXQ": [
    "tg0YCtMxsOInxioiPLC5_w",
    "issuing_country",
    "US"
  ]
}

4.5. POTENTIAL 相互運用性イベント / トラック 2 / Full プロファイル

POTENTIAL はヨーロッパのデジタルアイデンティティに特化したヨーロッパの組織です。 この組織は 2024 年春から相互運用性イベントを開催しています。このイベントは 6 つのトラックに分かれています。 トラック 1 と 2 は、クレデンシャルイシュアの相互運用性をテストするためのトラックです。 トラック 1 では VC の形式として mdoc が使用され、トラック 2 では SD-JWT VC が使用されます。

トラック 2 では 2 つのプロファイルが定義されています。 1 つは Light プロファイルと呼ばれ、もう 1 つは Full プロファイルと呼ばれます。 このセクションでは Full プロファイルの手順を説明します。


4.5.1. 設定


4.5.1.1. 認可サーバの設定
パラメータ
発行者識別子 https://trial.authlete.net
認可エンドポイント https://trial.authlete.net/api/authorization
トークンエンドポイント https://trial.authlete.net/api/token
PAR エンドポイント https://trial.authlete.net/api/par
ディスカバリエンドポイント https://trial.authlete.net/.well-known/openid-configuration
エンティティ設定 https://trial.authlete.net/.well-known/openid-federation

認可サーバのソースコードは https://github.com/authlete/java-oauth-server で入手可能です。ただしこの実装はサンプルであり、商用利用は想定していないので注意してください。


4.5.1.2. クレデンシャルイシュアの設定
パラメータ
発行者識別子 https://trial.authlete.net
クレデンシャルエンドポイント https://trial.authlete.net/api/credential
メタデータエンドポイント https://trial.authlete.net/.well-known/openid-credential-issuer
エンティティ設定 https://trial.authlete.net/.well-known/openid-federation

クレデンシャルイシュアのソースコードは認可サーバのソースコードと同じです。


4.5.1.3. クライアントの設定
パラメータ
クライアント識別子 track2_full
クライアントタイプ コンフィデンシャル
クライアント認証方式 attest_jwt_client_auth
リダイレクト URI
  1. https://nextdev-api.authlete.net/api/mock/redirection
  2. eudi-openid4ci://authorize/

このクライアントにリダイレクト URI の追加登録が必要であるか、または自分専用のクライアントが必要であれば、我々にご連絡ください

4.5.2. デモ手順

4.5.2.1. 手順 1 : コードベリファイアとコードチャレンジ

pkce コマンドを用いて、コードベリファイアを生成し、また、対応するコードチャレンジを計算します (RFC 7636 参照)。

git clone git@github.com:authlete/oid4vci-demo.git
cd oid4vci-demo
./pkce

pkce コマンドは生成したコードベリファイアと計算したコードチャレンジを次のように表示します。

CODE_VERIFIER=gzRNlV7DLS_HyKMQKQMrzgYQ8aY3H2rVJ3iIlYK0cjE
CODE_CHALLENGE=j_4gpG9Kr3M7ilMO-MRoSROP-W3h2EZem0KSEU-RAhM

計算したコードチャレンジは PAR リクエスト (RFC 9126) に、生成したコードベリファイアはトークンリクエストに含めることになります。


4.5.2.2. 手順 2 : クライアントアテステーションとクライアントアテステーション PoP

POTENTIAL の Track 2 Full プロファイルでは OAuth 2.0 Attestation-Based Client Authentication と呼ばれる新しいクライアント認証方式を用います。この方式のため、JWT を二つ用意しなければなりません。 それらの JWT はそれぞれ、クライアントアテステーション (Client Attestation)、 クライアントアテステーション PoP (Client Attestation PoP) と呼ばれます。

これらの JWT を生成するため、oid4vci-demo レポジトリには generate-client-attestationgenerate-client-attestation-pop という二つのスクリプトが含まれています。使い方は次の通りです。

使い方: generate-client-attestation [オプション]
        --attester-id=ATTESTER_ID    クライアントアテステーション発行者の識別子
        --attester-key=FILE          クライアントアテステーション発行者の秘密鍵を JWK 形式で含むファイル
        --client-id=CLIENT_ID        クライアントアプリケーションの識別子
        --client-key=FILE            クライアントアプリケーションの公開鍵を JWK 形式で含むファイル
        --duration=DURATION          クライアントアテステーションの有効秒数 (デフォルトは 86400)
使い方: generate-client-attestation-pop [オプション]
        --as-id=AS_ID                認可サーバの識別子
        --client-id=CLIENT_ID        クライアントアプリケーションの識別子
        --client-key=FILE            クライアントアプリケーションの秘密鍵を JWK 形式で含むファイル
        --duration=DURATION          クライアントアテステーション PoP の有効秒数 (デフォルトは 86400)

クライアントアテステーションを生成するためには、クライアントアテステーション発行者の秘密鍵とクライアントアプリケーションの公開鍵が必要です。 クライアントアテステーション発行者の秘密鍵はクライアントアテステーションに署名するのに使われます。 クライアントアプリケーションの公開鍵はクライアントアテステーションに埋め込まれます。

oid4vci-demo レポジトリにはデモ用のクライアントアテステーション発行者秘密鍵 (attester.jwk) とクライアントアプリケーションの秘密鍵 (client.jwk) が含まれています。 これらの鍵を用い、クライアントアテステーションを次のように生成することができます。

./generate-client-attestation \
    --attester-id=https://attester.example.com \
    --attester-key=attester.jwk \
    --client-id=track2_full \
    --client-key=client.jwk

generate-client-attestation スクリプトは次のような JWT フォーマットのクライアントアテステーションを生成します。

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6InpYOXhiSnFCemlNV0s1dm1qWEhnWlRtaVkxNnhmblp4elNFYTBHcVk1X1EifQ.eyJpc3MiOiJodHRwczovL2F0dGVzdGVyLmV4YW1wbGUuY29tIiwic3ViIjoidHJhY2syX2Z1bGwiLCJpYXQiOjE3MTk0NzMzNTIsImV4cCI6MTcxOTU1OTc1MiwiY25mIjp7Imp3ayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6IjFBbVZyNEdvSGRQZ2s0OExXZFMzVDltNm0xbVA0VlRjajl1c29TQm5DUWsiLCJ5IjoidGUtV0l1VUlxMnc4dFhtWHlkbEVYNHBlOWxOZS1QQmNvekE4bjd5OFhURSJ9fX0._jNM-9vEL14AOJrfk03ipIi7YECL-SGXUvqtdSiaYKVGhq7AK15QiqzzIwBj92Lij7Y4DvrtGix-0Pf4hwhPmQ

次の JSON は生成された JWT のヘッダとペイロードを base64url でデコードしたものです。 クライアントアテステーション発行者の識別子が iss クレームの値として使われていること、 クライアントの公開鍵cnf.jwk プロパティの値として埋め込まれていることを確認できます。

{
  "typ": "JWT",
  "alg": "ES256",
  "kid": "zX9xbJqBziMWK5vmjXHgZTmiY16xfnZxzSEa0GqY5_Q"
}
{
  "iss": "https://attester.example.com",
  "sub": "track2_full",
  "iat": 1719473352,
  "exp": 1719559752,
  "cnf": {
    "jwk": {
      "crv": "P-256",
      "kty": "EC",
      "x": "1AmVr4GoHdPgk48LWdS3T9m6m1mP4VTcj9usoSBnCQk",
      "y": "te-WIuUIq2w8tXmXydlEX4pe9lNe-PBcozA8n7y8XTE"
    }
  }
}

クライアントアテステーション PoP を生成するためには、クライアントの秘密鍵が必要です。 クライアントの秘密鍵はクライアントアテステーション PoP に署名するのに使われます。

oid4vci-demo レポジトリ内のクライアント秘密鍵 (client.jwk) を使い、 クライアントアテステーション PoP を次のように生成することができます。

./generate-client-attestation-pop \
    --as-id=https://trial.authlete.net \
    --client-id=track2_full \
    --client-key=client.jwk

generate-client-attestation-pop スクリプトは次のような JWT フォーマットのクライアントアテステーション PoP を生成します。

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijd5TmhJSFZlYVBGSUJfMGstWXBpRkFUb21DTHB4SXYtZTJBQUZtQ1JCTEUifQ.eyJpc3MiOiJ0cmFjazJfZnVsbCIsImlhdCI6MTcxOTQ3MzUxOSwiZXhwIjoxNzE5NTU5OTE5LCJqdGkiOiJvRmJGNXhhdnZLMTBRekJhIiwiYXVkIjoiaHR0cHM6Ly90cmlhbC5hdXRobGV0ZS5uZXQifQ.TXeCcijI8tICBH1bhOcNYTof9D0vDKAlu9Y0rl21EnPkUOCcsohiI8K7B1MlP1z82oC0LlYywXpcZ_gNnAC8BQ

次の JSON は生成された JWT のヘッダとペイロードを base64url でデコードしたものです。 クライアントの識別子が iss クレームの値として使われていること、認可サーバの識別子が aud クレームの値として指定されていることを確認できます。

{
  "typ": "JWT",
  "alg": "ES256",
  "kid": "7yNhIHVeaPFIB_0k-YpiFATomCLpxIv-e2AAFmCRBLE"
}
{
  "iss": "track2_full",
  "iat": 1719473519,
  "exp": 1719559919,
  "jti": "oFbF5xavvK10QzBa",
  "aud": "https://trial.authlete.net"
}

4.5.2.3. 手順 3 : PAR リクエスト用 DPoP Proof JWT

DPoP Proof JWT を生成するため、oid4vci-demo レポジトリ内の generate-dpop-proof スクリプトを使うことができます。

oid4vci-demo レポジトリにある DPoP デモ用の鍵 (dpop.jwk) を使い、次のようにスクリプトを起動できます。

./generate-dpop-proof -k dpop.jwk -m POST \
    -u https://trial.authlete.net/api/par

generate-dpop-proof スクリプトは次のような JWT フォーマットの DPoP Proof JWT を生成します。

eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiaEdmcXpHWGdhbzFRZ1ZJVFk2a2lIWU9LYmFMWEJ4VHFQSmE0RU9pbXhoSSIsInkiOiJFMUtpQV9mQTJ4OElycnlzb0dkbkJUTUI1LW8zRUpUX01nUUFfSG1HdTlNIn19.eyJqdGkiOiJxMWZJS21ad2FMNHlna1JQIiwiaHRtIjoiUE9TVCIsImh0dSI6Imh0dHBzOi8vdHJpYWwuYXV0aGxldGUubmV0L2FwaS9wYXIiLCJpYXQiOjE3MTk0NzQ2MzZ9.K8Mp44eK586UNCE-63bt5-m0v8B8KV840lDBe_5h2wLBcNWceS5x2fbFh9Koe7V7Rrbn6VT_hnCF8jYqkt6-dg

次の JSON は生成された JWT のヘッダとペイロードを base64url でデコードしたものです。 htu クレームが PAR エンドポイントの URL を保持しています。

{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "hGfqzGXgao1QgVITY6kiHYOKbaLXBxTqPJa4EOimxhI",
    "y": "E1KiA_fA2x8IrrysoGdnBTMB5-o3EJT_MgQA_HmGu9M"
  }
}
{
  "jti": "q1fIKmZwaL4ygkRP",
  "htm": "POST",
  "htu": "https://trial.authlete.net/api/par",
  "iat": 1719474636
}
4.5.2.4. 手順 4 : PAR リクエスト

認可エンドポイントよりも PAR エンドポイントが優れている点の一つは、PAR エンドポイントでクライアント認証を行えることです。 このデモでは、PAR エンドポイントにアクセスする際にアテステーションによるクライアント認証を用います。

当クライアント認証方式では二つの JWT、すなわちクライアントアテステーションとクライアントアテステーション PoP、を OAuth-Client-Attestation HTTP ヘッダと OAuth-Client-Attestation-PoP HTTP ヘッダで指定します。

手順 2 で生成したクライアントアテステーションとクライアントアテステーション PoP、および手順 4 で生成した DPoP Proof JWT とともに、次のコマンドを実行することで PAR リクエストを行えます。 コマンドライン内の ${CLIENT_ATTESTATION}${CLIENT_ATTESTATION_POP}${DPOP_PROOF_JWT_FOR_PAR_REQUEST}${CODE_CHALLENGE} は、ここまでの手順で取得した実際の値で置き換えてください。

curl -s https://trial.authlete.net/api/par \
     -H "OAuth-Client-Attestation: ${CLIENT_ATTESTATION}" \
     -H "OAuth-Client-Attestation-PoP: ${CLIENT_ATTESTATION_POP}" \
     -H "DPoP: ${DPOP_PROOF_JWT_FOR_PAR_REQUEST}" \
     -d client_id=track2_full \
     -d response_type=code \
     -d redirect_uri=https://nextdev-api.authlete.net/api/mock/redirection \
     -d scope=potential.track2.full.profile \
     -d code_challenge=${CODE_CHALLENGE} \
     -d code_challenge_method=S256

PAR エンドポイントは次のような JSON を応答として返します。成功した場合、JSON には request_uri プロパティが含まれています。 このプロパティの値は、事前登録された認可リクエストを表すリクエスト URI です。

{
  "expires_in": 600,
  "request_uri": "urn:ietf:params:oauth:request_uri:-CYpNdxTlS3S7e0PQKJVehPMnC0iiIk4pqJpD25k0Ws"
}

この例ではリクエスト URI の値は urn:ietf:params:oauth:request_uri:-CYpNdxTlS3S7e0PQKJVehPMnC0iiIk4pqJpD25k0Ws です。この値は認可リクエストの request_uri パラメーターの値として用いることになります。


4.5.2.5. 手順 5 : 認可リクエスト

次の URL をあなたのウェブブラウザのアドレスバーに入力してください。 これは、認可サーバの認可エンドポイントへの認可リクエストです。 URL 内の ${REQUEST_URI} を直前の手順で取得したリクエスト URI の実際の値で置き換えることを忘れないでください。

https://trial.authlete.net/api/authorization?client_id=track2_full&request_uri=${REQUEST_URI}

認可エンドポイントは次のような認可ページを返します。ログイン ID とパスワードとして ingainga を入力し、続けて Authorize ボタンを押してください。

リダイレクションエンドポイント (RFC 6749, 3.1.2 参照) にリダイレクトされます。

このリダイレクションエンドポイントは、受け取ったキー・バリューの組を表示します。 表示されている code パラメータの値が発行された認可コードです。 この例では、認可コードの値は yn1W7SLX9OEGqFZ2D986iPowVnoQtIRpPByBEyiIrBk です。

認可コードはのちほどトークンリクエストで使います。


4.5.2.6. 手順 6 : トークンリクエスト用 DPoP Proof JWT

トークンエンドポイントにアクセスするため、新しい DPoP Proof JWT を作成する必要があります。 PAR リクエスト用に作成した DPoP Proof JWT を再利用することはできません。

次のコマンドにより DPoP Proof JWT を作成します。-u オプションの値が (PAR エンドポイントではなく) トークンエンドポイントの URL であることに注意してください。

./generate-dpop-proof -k dpop.jwk -m POST \
    -u https://trial.authlete.net/api/token

generate-dpop-proof スクリプトは次のような JWT を生成します。

eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiaEdmcXpHWGdhbzFRZ1ZJVFk2a2lIWU9LYmFMWEJ4VHFQSmE0RU9pbXhoSSIsInkiOiJFMUtpQV9mQTJ4OElycnlzb0dkbkJUTUI1LW8zRUpUX01nUUFfSG1HdTlNIn19.eyJqdGkiOiJFYzVhdklIcTN5Q25pUUFNIiwiaHRtIjoiUE9TVCIsImh0dSI6Imh0dHBzOi8vdHJpYWwuYXV0aGxldGUubmV0L2FwaS90b2tlbiIsImlhdCI6MTcxOTQ3NTAxNX0.0ahIcVPSpMY-g-_SfkB8C84wDhalE1FLfobyDciBoFv16W82gMIuoBw3kDIhHmGJAxxOVKOyTQFKMi25c30vlw

この DPoP Proof JWT のヘッダとペイロードを base64url でデコードすると次のようになります。 htu クレームがトークンエンドポイントの URL を保持しています。

{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "hGfqzGXgao1QgVITY6kiHYOKbaLXBxTqPJa4EOimxhI",
    "y": "E1KiA_fA2x8IrrysoGdnBTMB5-o3EJT_MgQA_HmGu9M"
  }
}
{
  "jti": "Ec5avIHq3yCniQAM",
  "htm": "POST",
  "htu": "https://trial.authlete.net/api/token",
  "iat": 1719475015
}
4.5.2.7. 手順 7 : トークンリクエスト

これまでの手順で次のものを用意しました。

  • クライアントアテステーション (JWT)
  • クライアントアテステーション PoP (JWT)
  • トークンリクエスト用 DPoP Proof JWT (JWT)
  • 認可コード
  • コードベリファイア

これらを用い、次のようにトークンリクエストを実行できます。 コマンドライン内の変数をこれまでの手順で生成した実際の値で置き換えることを忘れないでください。

curl -s https://trial.authlete.net/api/token \
     -H "OAuth-Client-Attestation: ${CLIENT_ATTESTATION}" \
     -H "OAuth-Client-Attestation-PoP: ${CLIENT_ATTESTATION_POP}" \
     -H "DPoP: ${DPOP_PROOF_JWT_FOR_TOKEN_REQUEST}" \
     -d client_id=track2_full \
     -d grant_type=authorization_code \
     -d code=${AUTHORIZATION_CODE} \
     -d redirect_uri=https://nextdev-api.authlete.net/api/mock/redirection \
     -d code_verifier=${CODE_VERIFIER}

トークンエンドポイントは応答として次のような JSON を返します。

{
  "access_token": "_aPAJGv3bnb8UoSbkBCP9XF0tnCHoLDGBc6chbFSWkg",
  "token_type": "DPoP",
  "expires_in": 86400,
  "scope": "potential.track2.full.profile",
  "refresh_token": "NPJv7yAu5YKT7a6qeWes4mL8exDx-bPruXDQE3p8u80",
  "c_nonce": "01bcC_9FtNM22wO9pnUqRkeFQuJNEE3sHA6M-oDnsHs",
  "c_nonce_expires_in": 86400
}

access_token プロパティの値は発行されたアクセストークンです。 のちほど実行するクレデンシャルリクエストにこれを含める必要があります。

c_nonce プロパティの値は発行されたノンスです。この値は次の手順で生成する JWT Key Proof に含める必要があります。


4.5.2.8. 手順 8 : クレデンシャルリクエスト用 JWT Key Proof

POTENTIAL の Track 2 Full プロファイルでは、 “OpenID4VCI PR 293: rework credential and batch credential endpoint” で導入された新しい機能を使います。この新しい機能は、一回のクレデンシャルリクエストで複数の VC を発行することを可能にします。

複数の VC を要求するには、クレデンシャルリクエストに複数の鍵証明を含める必要があります。 そのため、ここでは、異なる保有者鍵を使って二つの JWT Key Proof を作ることにします。

oid4vci-demo レポジトリには、holder.jwkholder2.jwk という二つのデモ用保有者鍵が含まれています。これらの鍵と generate-key-proof スクリプトを用い、次のように JWT Key Proof を生成することができます。コマンドを実行する前に、コマンドライン内の ${C_NONCE} を先の手順で取得したトークンレスポンスに含まれる c_nonce プロパティの実際の値で置き換えてください。

JWT_KEY_PROOF_1=`./generate-key-proof -i https://trial.authlete.net -k holder.jwk -c track2_full -n ${C_NONCE}`
JWT_KEY_PROOF_2=`./generate-key-proof -i https://trial.authlete.net -k holder2.jwk -c track2_full -n ${C_NONCE}`

次のものは holder.jwk を用いた JWT Key Proof の例です。ヘッダ内の jwk クレームが holder.jwk 内の秘密鍵に対応する公開鍵を保持していることに注目してください。

eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiUFN4UXJEMnpsMF9tWGNBcXoxbWdxU2VCb0Jobm14Mnl4QkVwckJZOEYyMCIsInkiOiJ4VjhmYmkxRlNvc1V1bkxldUxOdUxrSmlxbVk2VEtpTW51ci1HbjJ3UjEwIn19.eyJpc3MiOiJ0cmFjazJfZnVsbCIsImF1ZCI6Imh0dHBzOi8vdHJpYWwuYXV0aGxldGUubmV0IiwiaWF0IjoxNzE5NDc2Mjg4LCJub25jZSI6IjAxYmNDXzlGdE5NMjJ3TzlwblVxUmtlRlF1Sk5FRTNzSEE2TS1vRG5zSHMifQ.lgI038MOL6DtbDnPOa8GZen0d2EoiZ-dgn_IU9hJYe5aWxL81hADNOS6v7ChJ9zxcU90sPDEJEyM12tDVm7VPQ
{
  "typ": "openid4vci-proof+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "PSxQrD2zl0_mXcAqz1mgqSeBoBhnmx2yxBEprBY8F20",
    "y": "xV8fbi1FSosUunLeuLNuLkJiqmY6TKiMnur-Gn2wR10"
  }
}
{
  "iss": "track2_full",
  "aud": "https://trial.authlete.net",
  "iat": 1719476288,
  "nonce": "01bcC_9FtNM22wO9pnUqRkeFQuJNEE3sHA6M-oDnsHs"
}

同様に、次のものは holder2.jwk を用いた JWT Key Proof の例です。ヘッダ内の jwk クレームが holder2.jwk 内の秘密鍵に対応する公開鍵を保持しています。

eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiSkotN2YwQXNRNWZJUmRxaDJySXoxSS02SkpBTUowQjUzM1Iybm8tRmtfQSIsInkiOiJ6LTFFZnc2ZmRoZ2RLVEZISVhOSDI5bV9UTlpjWUpDLUxDSVU2WWRQdFI4In19.eyJpc3MiOiJ0cmFjazJfZnVsbCIsImF1ZCI6Imh0dHBzOi8vdHJpYWwuYXV0aGxldGUubmV0IiwiaWF0IjoxNzE5NDc2MzAwLCJub25jZSI6IjAxYmNDXzlGdE5NMjJ3TzlwblVxUmtlRlF1Sk5FRTNzSEE2TS1vRG5zSHMifQ.nNGWEYSDvH_B632TF_Z_ecG14fzmBR6QTjQS3Irjp5xiPLkHtm_XXGvKFeVLTc8erZpmWPvD_2e99DUvDsajyA
{
  "typ": "openid4vci-proof+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "JJ-7f0AsQ5fIRdqh2rIz1I-6JJAMJ0B533R2no-Fk_A",
    "y": "z-1Efw6fdhgdKTFHIXNH29m_TNZcYJC-LCIU6YdPtR8"
  }
}
{
  "iss": "track2_full",
  "aud": "https://trial.authlete.net",
  "iat": 1719476300,
  "nonce": "01bcC_9FtNM22wO9pnUqRkeFQuJNEE3sHA6M-oDnsHs"
}
4.5.2.9. 手順 9 : クレデンシャルリクエスト用 DPoP Proof JWT

クレデンシャルエンドポイントにアクセスするため、再度 DPoP Proof JWT を生成する必要があります。

-u オプションの値としてクレデンシャルエンドポイントの URL を指定することに加え、今回はアクセストークンの値を -a オプション (--at オプションの短縮版) で指定する必要があります。このオプションは DPoP Key Proof に ath クレームを含めるために必要です。

./generate-dpop-proof -k dpop.jwk -m POST \
    -u https://trial.authlete.net/api/credential \
    -a ${ACCESS_TOKEN}

下記はクレデンシャルリクエスト用の DPoP Proof JWT の例です。ペイロードに ath クレームが含まれることに注目してください。 このクレームは、アクセストークンのハッシュ値を表しています。

eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7ImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoiaEdmcXpHWGdhbzFRZ1ZJVFk2a2lIWU9LYmFMWEJ4VHFQSmE0RU9pbXhoSSIsInkiOiJFMUtpQV9mQTJ4OElycnlzb0dkbkJUTUI1LW8zRUpUX01nUUFfSG1HdTlNIn19.eyJqdGkiOiI3Vm9zZW1YZ3ZLNUpVMVI0IiwiaHRtIjoiUE9TVCIsImh0dSI6Imh0dHBzOi8vdHJpYWwuYXV0aGxldGUubmV0L2FwaS9jcmVkZW50aWFsIiwiaWF0IjoxNzE5NDc2ODQxLCJhdGgiOiIzQWV6TzBZMTFnZzgwVm5Zd3puLUpDbXFNVnZkc2pJZXlBMTRoc1BOdHVvIn0.zDZYZdgb2jo4KKM0_OariZK0yrEcIFmPdpImX0Q-pttqw8480-nVdW-lQlQqQvcMtDKEWwZtgcNTsNExgomZ2g
{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "crv": "P-256",
    "kty": "EC",
    "x": "hGfqzGXgao1QgVITY6kiHYOKbaLXBxTqPJa4EOimxhI",
    "y": "E1KiA_fA2x8IrrysoGdnBTMB5-o3EJT_MgQA_HmGu9M"
  }
}
{
  "jti": "7VosemXgvK5JU1R4",
  "htm": "POST",
  "htu": "https://trial.authlete.net/api/credential",
  "iat": 1719476841,
  "ath": "3AezO0Y11gg80VnYwzn-JCmqMVvdsjIeyA14hsPNtuo"
}
4.5.2.10. 手順 10 : クレデンシャルリクエスト

ここまでの手順で次のものが用意できました。

  • アクセストークン
  • クレデンシャルリクエスト用 DPoP Proof JWT (JWT)
  • JWT Key Proof 1 (JWT)
  • JWT Key Proof 2 (JWT)

これらを用い、次のようにクレデンシャルリクエスト実行できます。

curl -s https://trial.authlete.net/api/credential \
     -H "Authorization: Bearer ${ACCESS_TOKEN}" \
     -H "DPoP: ${DPOP_PROOF_JWT_FOR_CREDENTIAL_REQUEST}" \
     -H "Content-Type: application/json" \
       --data '{
  "format": "vc+sd-jwt",
  "vct": "urn:eu.europa.ec.eudi:pid:1",
  "proofs": {
    "jwt": [
      "'${JWT_KEY_PROOF_1}'",
      "'${JWT_KEY_PROOF_2}'"
    ]
  }
}'

ここでポイントとなるのは、クレデンシャルリクエストのメッセージボディが proof プロパティではなく proofs プロパティを含んでいることです。この proofs プロパティは OpenID4VCI PR 293 により新たに導入されたものであり、複数の鍵証明を含めることを可能にします。

クレデンシャルエンドポイントは次のような応答を返します。

{
  "credentials": [
    "eyJraWQiOiJKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrIiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiTkVXa1ZYODhqaENvMVVFZ0ZiTktQNjYyNkVhVXp6TldCRzdId1VZOFY4VSIsIlRfaUs3UG9ISDRfalNBWkdaMVljbXc2ckl2ZE1fM1pHTFpMV0JxUTZCOTgiXX0sIl9zZCI6WyItOWNGNnJ1TkFTRzdMX09ndEdTZlhSX0pDT1VmQ3pkVmF6dVB2RFFsV3FnIiwiMXdVMDdzdHR2NnBMN2FHal95YjJ5Z0tqVUFTeVZpLVk1cUxPcWgzN3ZPQSIsIjR1TF8taHhQTUpJTzVZdTdwTm4wbU95SEJBWjVsR0cwYWNhM2pZaG84cFUiLCI3dUZXV05vQURlTUpzVzFmcnhiVDV4czlHM3h4Qy0wSWRYTGV3RE5yWGxnIiwiOV82Y3hMMXNlRzUxRVdiZjlOcjFIZklpRTVpajdnWU9ONllxRXdqOGl5SSIsIjljWkJFUURYUTY2RGhqd1JPMUhzcXZOLVBLRHRuYW5BWlk2QW03aXUxMEkiLCJBTlRHcEZkaXk2cFpiXzdvb1pXQmQtVVlGUHR3ZDRiNDRXcW5WeGUxQjFBIiwiQkJTTU03ZXdYb2t3cVdSY1lDUmlLazJzbW1pNWdrM05NU21EMlpONnlTcyIsIlA4WG0zMUNUSXN3UzVTNlFyYkstc2tfc1dTUGMtZmJuWGM2c1VfMUpDRkUiLCJUckRwYVZxcnpSVWxfM0ktejE0SWtZdmxEZndTY2djbXI5Ui1maEZjcVhFIiwiZXpPREtNdkNGWW9ER2c0bWNLRFJ1VzJPemlMaml4VjRKRkNRS0Z6QnRsTSIsIm0yYmRpOHNPcEhRZTg1NTNhamJYQTNwakNTRzN0Qjh4T3VvaUN5eVlVYmMiLCJ1dG1yaWxSWGdTRHBsblFkdW5OUzZ2Ym8xOFB2cktsNnhEa2tzVFZ3YXVZIiwieEozS3NOam5faFl6aTNMWlZITDBoRklOWVdFM3EtZVJoRUdGRWVUQlBIOCJdLCJhZGRyZXNzIjp7Il9zZCI6WyIzUTFVdk1GbGl0VFYxM0EwN016ZjI3UzZfVW1Na2JEVFBfa0N3RklkTlJnIiwiQTgzNG9MTlY1dEVPb0c2eVMtRnRFM0FhalR0UF9GV1VhUXhBOWdHVXlnWSIsIkZRdG5MLXBaVkNRUUhRQ1Y3TVl6M0hwaUpOX1JIRmZObDlVSms3SGgwTUkiLCJHVWxzcjBpcUlMTVk1QXk4TnlZMzFiczNkd1NqdkZrd09aZHQxRlR1QndnIiwiSXk1TEFhLVEtNDhkMWN2d0VDN3VxYjBEMFR5VmxWX2l0dkhzck1KZE1UdyIsIkxzYlBzTGg4N0FiU3dwellYRmdTRnZZNHNFTFdZZXB2S0JzeVNBRWxhMXciLCJVZlE0QktRR2ZmZF9wU1VCYkd1RUJQM2d6Rm9zNU9sTFNvMUlxd1k0TWxJIiwiVjQtRTc2V1NKUjBuMU1xZHdsbElTTmoxaEtnRGc4d09qdElyam5rQ2tZZyIsIlhSZkNFZTVPcy1KcFM5OVFXY1pqRF9udXQ3d0Jwc0QzX1djWjZ1amhseDAiLCJpQ05YazdMLTQ3MnZnUmo2R0RDZ0RqWE5SUVBKaXM4amJTb3lRV09KRC1BIiwibkc5LUY4RDVPZWZZNmdLQmxVSU1WdFBaZXluX0FISGhmMzZIRzNmQ2tWNCIsInBYYlJTUTFfeFNUYUhIRjRhaFEzZFZPenQ1ODI3TkYxa0ZLUmtnUEd3cVUiXX0sInZjdCI6InVybjpldS5ldXJvcGEuZWMuZXVkaTpwaWQ6MSIsIl9zZF9hbGciOiJzaGEtMjU2IiwiaXNzIjoiaHR0cHM6Ly90cmlhbC5hdXRobGV0ZS5uZXQiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJraWQiOiI0TTlrSXJCOVdZenQxR1FnTDEybHpkQlpzR3llVjNsZ1BLb3YyOG9UNUw0IiwieCI6IlBTeFFyRDJ6bDBfbVhjQXF6MW1ncVNlQm9CaG5teDJ5eEJFcHJCWThGMjAiLCJ5IjoieFY4ZmJpMUZTb3NVdW5MZXVMTnVMa0ppcW1ZNlRLaU1udXItR24yd1IxMCJ9fSwiaWF0IjoxNzE5NDc2ODU1LCJhZ2VfZXF1YWxfb3Jfb3ZlciI6eyJfc2QiOlsiS1pPZzhDUENVWkI4Tl85ZC1YTVVvQ0tYQ2xTbEh3NHBiTmlFOEE4Mzk5ZyIsImZmZ2lfTmhVV0JWbVNiZ3J5WmtrMXpSdmhJNXJHQjFhd2diT2ZiRC1oZU0iXX19.dlnBVF7xd9uFbaUS0ExMHN-62uOnmzAmGSNOYJrP3jNNpV1ihmu02hdDTFEFuhhMQ2DXeKYG3mnbYZ4gjQogVw~WyJubFJHOFlwVXRyWlZybnZMaGsxVVNRIiwic3ViIiwiMTAwNCJd~WyJ1U0pTVHhqZHBKLVZKYzVJTTY3cHNnIiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd~WyJtWFg5ZHBLNHFZWC16WHRuczdnb2tnIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ~WyJ0Q0NxOUNvWHJRTjJSY3FoY1dUTG9nIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd~WyJlQU1BaG51QlAxQTY4Und3YU9YUU13IiwiMTgiLHRydWVd~WyJ0MVJ2YS1jeTVLQS0xT1hYQW93ZklRIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd~WyJpNXJNNDVZdjN1WHplRnZEekpxbjdnIiwiZm9ybWF0dGVkIiwiMTE0IE9sZCBTdGF0ZSBId3kgMTI3LCBTaG9zaG9uZSwgQ0EgOTIzODQsIFVTQSJd~WyJvaFhiOVdFQTdHTmc0SEVrem9yVG5BIiwic3RyZWV0X2FkZHJlc3MiLCIxMTQgT2xkIFN0YXRlIEh3eSAxMjciXQ~WyJFdmY1dkhCRFc4RmgzQk84SnZNOHR3IiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd~WyIyZFJVWFBfajlIUF9nX1VFYTZRVkh3IiwicG9zdGFsX2NvZGUiLCJDQSA5MjM4NCJd~WyJSbmNCeVBRdW5NbTNtb3hDRnVaOFpRIiwiY291bnRyeSIsIlVTQSJd~WyI1NmJLM1BKcG1wZ1kzdlA4WHNUYnZnIiwiaXNzdWluZ19hdXRob3JpdHkiLCJVUyJd~WyJjdXNabVM1MHVteUk3ZWc2SGhJYWNnIiwiaXNzdWluZ19jb3VudHJ5IiwiVVMiXQ~",
    "eyJraWQiOiJKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrIiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiSXdtalFNd3ROTk9nVkJ5ZW5rYWYxemRyYnNSLWlsbFptN3pHNmFvem1NVSIsIlNxU25Rb0FnbWJqS3dkVzBNMDhvVnVfdktLLWk1bVA3RHNVNnlwdzFOenMiXX0sIl9zZCI6WyIzcXlKY0luTVRFOFZtb0FQdDVNUmNtdUREYXBUTF9BSmRHbGNxUzdJRVBFIiwiNkotbGQ5S3ktQ3hUSXhqbnlGNnYzX0JLajUyR01GQ3hndF9BSnZJTnNRVSIsIjZrY3dwdkRCZ05DVkUzM2RnUmd5YjJ1cERBNW45TUZhNS1ra1phSkZzZTQiLCJEWDRJT0hGVUVoVDZvNlNqTzVoUGZieUdPb3NmcC1HVjhNdmxkZnV2aXJZIiwiRWItMG1PRkNGWWViV2VPY3VNNmtQRWFpRFh4TnZETFBqcjkzbzNROUppcyIsIklLTzl5OEZ2TllpTGRpYUVwT0hBZWpYRGFYMVdXeVRPUnRUdnY3aUlTLU0iLCJJS1QwNGRxZ1NhM1B6aUlNZWRtU0NOWTl6MjRvcEEzRmh6LWUwbTdxV0VZIiwiSXlIemhjSDdpM3NkS182S0VJSWdDaVkzRWdDOVBUaU5zY1dpd1BGS3FHayIsIkpNeXg4QzRhakxVTzNLR3NTRDJobkMxcUJpSV80SThlc2QxZDNlcnNMU2siLCJTUUMzSUJDMlphVlBCZFU0QmYyZUZIcFVDUjJaRU1uZkFQUW1OMmVraWlvIiwiWEVJVmo4X2NXSjRBV1N3cTZWMlpOblA5Z3JfaVU1cUJkcGxTQXIwdC1JOCIsIlo0NXZjM19acUk2Uk1GN0xaaE5XZVp2YlIwRVA3NUNtLXdNcUVuSkdaVkEiLCJfRjZWTE56dTN3cVNTOHBRV1p5eXpOLVRybEZWX1h5LXp1WmNRbDFWd2NRIiwiYXl2UlVTdmc4a3N5Mnk0QjhGUk16NGxoTVdyWFRkcFVZT2p0aVlnVVFEUSIsImc4UjhhZnBRVERzZVFkZjZqbXJfTGNoVUxUc2V1enJ1SWxUVWllNFRXVjAiLCJnd2lwTUJESFRDanFsMHl0b1c2djdBUGo1Vm5sbVpMTWwyUlNYZVZTRXdJIiwiaEJ4UXhqT3QwQzN4SUpZYmNPaFJwU2Y5ajZjSm0wRTNoUU5WVFNPT3oyQSIsImw1UkZ5RVJRUHRFLUx6a2RFUGhXeXo0d215dzFGLU1RSTBBdjFIZER6NEUiLCJxNHZXNHRLNGQydS1vZHkybDNtNl9qYm14NVpTUmlVTzVHN0lwMTNOSjF3IiwidVhpTzQ3WFdXbEdQRGE2cS1zSk5Qc0R6THdXTGxpMTQyN2NQNkEzQzNIVSIsInlCb1B4aURuWjVhaWdJdjVZek9VMTEyQVdHT3BVaVNDRjNKZHgydjZEdDgiLCJ6T1E2alU3X094MDdxWXFJSjdvVzIxNmJiUWRzbXlfQXlHcTBpbW5ueUFjIl0sImFkZHJlc3MiOnsiX3NkIjpbIjJDdml3VXo1eEwwUllGc1BhSFlZeFVKVXgwY09vRFhCYUlsZnNvWHJoem8iLCI1NmswNWpXTmNaLUVwZ1loaDNjUnBESjhuSkVXOC04WnFvSWNyWFVicEFNIiwiN3d5cjd4MzlvMlN6Vjd0cmJ1SUN4d0xqTVBiaXdRdnU0eHVHN09sQUxpWSIsIkpsOEJveURITXkxeVpKQWEwRHliU0JzbUJSZ1hFVU9FUTQxb2xwS29uOHciLCJLc1h4c0JIWW9fb3dVa2J4VlVRT0ZjUHRoOWVqRG5BZzhjYVNXQVd4R2FJIiwiWXpsdWY0bTRMdGFoMEVzaGszQ0Y3aVMtcTJ0Um0yaVdfVXVZaWNtTzlLcyIsIm4talZaSEU2ZDN0ODVaQ1BXYTFOQm5KTVNFdDgweU9MMUphdFF1ZVhmd28iLCJvbUF4a2xVS3JyMk9sNW9nRlk2WGdJLVhtNHY2M1VrVmIxX09XYVhra0VRIiwid1dCa1dQZU1SdUdGM2I2S0drQnhyeGdZY2tqVzNxUERET3RyOVBOd0NiRSJdfSwidmN0IjoidXJuOmV1LmV1cm9wYS5lYy5ldWRpOnBpZDoxIiwiX3NkX2FsZyI6InNoYS0yNTYiLCJpc3MiOiJodHRwczovL3RyaWFsLmF1dGhsZXRlLm5ldCIsImNuZiI6eyJqd2siOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsImtpZCI6IkI4b3M4ZE5CTTF2aU9OWU96T0hmUEtKd3AzU0dsTVlLU05BQ1NXdVpScXMiLCJ4IjoiSkotN2YwQXNRNWZJUmRxaDJySXoxSS02SkpBTUowQjUzM1Iybm8tRmtfQSIsInkiOiJ6LTFFZnc2ZmRoZ2RLVEZISVhOSDI5bV9UTlpjWUpDLUxDSVU2WWRQdFI4In19LCJpYXQiOjE3MTk0NzY4NTUsImFnZV9lcXVhbF9vcl9vdmVyIjp7Il9zZCI6WyJJOEdHR2VXQXJFRzAwUHFiNV9qZkJnMGhYRndET1k5aTRnbU9ZTGp1OS1ZIiwidUhmbDRiZ00wV2I2YTBWSHUzWWx0YU1mSGR1TXZIbzIyMVVBUUJkMDhYVSJdfX0.oI6tgincnKaRqACfYe46PweL1kvbrbjJcnwKnBtGDpUfWbvRTj2B8pn4JWVQ2XPBDEnAJaeO0tJ7B1U1infWMA~WyJBMFYzTDlwdzQ3U1BfSk5XYTVFOGhBIiwic3ViIiwiMTAwNCJd~WyJLZy1VTFN4N2JlOGRlYm5yMngyVjRnIiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd~WyJZLUVCUkw0OTJyUm54T3BGaWtUOE1BIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ~WyJreUFTdHg1SWNUaXVfQ3RNdTBDck5RIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd~WyI4WGJCNGRRZENZbkh6a3BuR094YU1RIiwiMTgiLHRydWVd~WyJYM0RBUHdFWTRGaEQ2UUNNSU5idXBnIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd~WyJ3SDhKTE5tc1dkcVdfZ3Q3RmtQbGpRIiwiZm9ybWF0dGVkIiwiMTE0IE9sZCBTdGF0ZSBId3kgMTI3LCBTaG9zaG9uZSwgQ0EgOTIzODQsIFVTQSJd~WyJEVUJaRXNkWVVPREhoZTJ3TUxFMnpBIiwic3RyZWV0X2FkZHJlc3MiLCIxMTQgT2xkIFN0YXRlIEh3eSAxMjciXQ~WyJ6dXQzd0pnbjN1MExCcTJRNTlXZEFRIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd~WyJsVzdXQTkxakRON1RGWkpRRzJNUi1BIiwicG9zdGFsX2NvZGUiLCJDQSA5MjM4NCJd~WyJlczdiUHpNeXdNaXJON0tzemtpMFdRIiwiY291bnRyeSIsIlVTQSJd~WyJtOEVlUUM2akhIeUlSWGVDQXhERi1nIiwiaXNzdWluZ19hdXRob3JpdHkiLCJVUyJd~WyIxMzhhaHkwMVdvNFFxekZyNFlpSld3IiwiaXNzdWluZ19jb3VudHJ5IiwiVVMiXQ~"
  ],
  "c_nonce": "01bcC_9FtNM22wO9pnUqRkeFQuJNEE3sHA6M-oDnsHs",
  "c_nonce_expires_in": 84589
}

成功時、応答には credential プロパティではなく credentials プロパティが含まれます。 credentials 配列内の要素は発行された VC です。この例では、クレデンシャルリクエストが 2 つの鍵証明を含んでいたので、credentials 配列は二つの VC を含んでいます。

VC のフォーマットは SD-JWT です。oid4vci-demo レポジトリにある decode-sd-jwt スクリプトを用いて、これらの VC をデコードすることができます。

一番目の VC をデコードすると次のようになります。

{
  "kid": "J1FwJP87C6-QN_WSIOmJAQc6n5CQ_bZdaFJ5GDnW1Rk",
  "typ": "vc+sd-jwt",
  "alg": "ES256"
}
{
  "place_of_birth": {
    "_sd": [
      "NEWkVX88jhCo1UEgFbNKP6626EaUzzNWBG7HwUY8V8U",
      "T_iK7PoHH4_jSAZGZ1Ycmw6rIvdM_3ZGLZLWBqQ6B98"
    ]
  },
  "_sd": [
    "-9cF6ruNASG7L_OgtGSfXR_JCOUfCzdVazuPvDQlWqg",
    "1wU07sttv6pL7aGj_yb2ygKjUASyVi-Y5qLOqh37vOA",
    "4uL_-hxPMJIO5Yu7pNn0mOyHBAZ5lGG0aca3jYho8pU",
    "7uFWWNoADeMJsW1frxbT5xs9G3xxC-0IdXLewDNrXlg",
    "9_6cxL1seG51EWbf9Nr1HfIiE5ij7gYON6YqEwj8iyI",
    "9cZBEQDXQ66DhjwRO1HsqvN-PKDtnanAZY6Am7iu10I",
    "ANTGpFdiy6pZb_7ooZWBd-UYFPtwd4b44WqnVxe1B1A",
    "BBSMM7ewXokwqWRcYCRiKk2smmi5gk3NMSmD2ZN6ySs",
    "P8Xm31CTIswS5S6QrbK-sk_sWSPc-fbnXc6sU_1JCFE",
    "TrDpaVqrzRUl_3I-z14IkYvlDfwScgcmr9R-fhFcqXE",
    "ezODKMvCFYoDGg4mcKDRuW2OziLjixV4JFCQKFzBtlM",
    "m2bdi8sOpHQe8553ajbXA3pjCSG3tB8xOuoiCyyYUbc",
    "utmrilRXgSDplnQdunNS6vbo18PvrKl6xDkksTVwauY",
    "xJ3KsNjn_hYzi3LZVHL0hFINYWE3q-eRhEGFEeTBPH8"
  ],
  "address": {
    "_sd": [
      "3Q1UvMFlitTV13A07Mzf27S6_UmMkbDTP_kCwFIdNRg",
      "A834oLNV5tEOoG6yS-FtE3AajTtP_FWUaQxA9gGUygY",
      "FQtnL-pZVCQQHQCV7MYz3HpiJN_RHFfNl9UJk7Hh0MI",
      "GUlsr0iqILMY5Ay8NyY31bs3dwSjvFkwOZdt1FTuBwg",
      "Iy5LAa-Q-48d1cvwEC7uqb0D0TyVlV_itvHsrMJdMTw",
      "LsbPsLh87AbSwpzYXFgSFvY4sELWYepvKBsySAEla1w",
      "UfQ4BKQGffd_pSUBbGuEBP3gzFos5OlLSo1IqwY4MlI",
      "V4-E76WSJR0n1MqdwllISNj1hKgDg8wOjtIrjnkCkYg",
      "XRfCEe5Os-JpS99QWcZjD_nut7wBpsD3_WcZ6ujhlx0",
      "iCNXk7L-472vgRj6GDCgDjXNRQPJis8jbSoyQWOJD-A",
      "nG9-F8D5OefY6gKBlUIMVtPZeyn_AHHhf36HG3fCkV4",
      "pXbRSQ1_xSTaHHF4ahQ3dVOzt5827NF1kFKRkgPGwqU"
    ]
  },
  "vct": "urn:eu.europa.ec.eudi:pid:1",
  "_sd_alg": "sha-256",
  "iss": "https://trial.authlete.net",
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "kid": "4M9kIrB9WYzt1GQgL12lzdBZsGyeV3lgPKov28oT5L4",
      "x": "PSxQrD2zl0_mXcAqz1mgqSeBoBhnmx2yxBEprBY8F20",
      "y": "xV8fbi1FSosUunLeuLNuLkJiqmY6TKiMnur-Gn2wR10"
    }
  },
  "iat": 1719476855,
  "age_equal_or_over": {
    "_sd": [
      "KZOg8CPCUZB8N_9d-XMUoCKXClSlHw4pbNiE8A8399g",
      "ffgi_NhUWBVmSbgryZkk1zRvhI5rGB1awgbOfbD-heM"
    ]
  }
}
{
  "digest": "4uL_-hxPMJIO5Yu7pNn0mOyHBAZ5lGG0aca3jYho8pU",
  "WyJubFJHOFlwVXRyWlZybnZMaGsxVVNRIiwic3ViIiwiMTAwNCJd": [
    "nlRG8YpUtrZVrnvLhk1USQ",
    "sub",
    "1004"
  ]
}
{
  "digest": "9_6cxL1seG51EWbf9Nr1HfIiE5ij7gYON6YqEwj8iyI",
  "WyJ1U0pTVHhqZHBKLVZKYzVJTTY3cHNnIiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd": [
    "uSJSTxjdpJ-VJc5IM67psg",
    "family_name",
    "Silverstone"
  ]
}
{
  "digest": "m2bdi8sOpHQe8553ajbXA3pjCSG3tB8xOuoiCyyYUbc",
  "WyJtWFg5ZHBLNHFZWC16WHRuczdnb2tnIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ": [
    "mXX9dpK4qYX-zXtns7gokg",
    "given_name",
    "Inga"
  ]
}
{
  "digest": "BBSMM7ewXokwqWRcYCRiKk2smmi5gk3NMSmD2ZN6ySs",
  "WyJ0Q0NxOUNvWHJRTjJSY3FoY1dUTG9nIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd": [
    "tCCq9CoXrQN2RcqhcWTLog",
    "birthdate",
    "1991-11-06"
  ]
}
{
  "digest": "KZOg8CPCUZB8N_9d-XMUoCKXClSlHw4pbNiE8A8399g",
  "WyJlQU1BaG51QlAxQTY4Und3YU9YUU13IiwiMTgiLHRydWVd": [
    "eAMAhnuBP1A68RwwaOXQMw",
    "18",
    true
  ]
}
{
  "digest": "T_iK7PoHH4_jSAZGZ1Ycmw6rIvdM_3ZGLZLWBqQ6B98",
  "WyJ0MVJ2YS1jeTVLQS0xT1hYQW93ZklRIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd": [
    "t1Rva-cy5KA-1OXXAowfIQ",
    "locality",
    "Shoshone"
  ]
}
{
  "digest": "V4-E76WSJR0n1MqdwllISNj1hKgDg8wOjtIrjnkCkYg",
  "WyJpNXJNNDVZdjN1WHplRnZEekpxbjdnIiwiZm9ybWF0dGVkIiwiMTE0IE9sZCBTdGF0ZSBId3kgMTI3LCBTaG9zaG9uZSwgQ0EgOTIzODQsIFVTQSJd": [
    "i5rM45Yv3uXzeFvDzJqn7g",
    "formatted",
    "114 Old State Hwy 127, Shoshone, CA 92384, USA"
  ]
}
{
  "digest": "nG9-F8D5OefY6gKBlUIMVtPZeyn_AHHhf36HG3fCkV4",
  "WyJvaFhiOVdFQTdHTmc0SEVrem9yVG5BIiwic3RyZWV0X2FkZHJlc3MiLCIxMTQgT2xkIFN0YXRlIEh3eSAxMjciXQ": [
    "ohXb9WEA7GNg4HEkzorTnA",
    "street_address",
    "114 Old State Hwy 127"
  ]
}
{
  "digest": "GUlsr0iqILMY5Ay8NyY31bs3dwSjvFkwOZdt1FTuBwg",
  "WyJFdmY1dkhCRFc4RmgzQk84SnZNOHR3IiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd": [
    "Evf5vHBDW8Fh3BO8JvM8tw",
    "locality",
    "Shoshone"
  ]
}
{
  "digest": "LsbPsLh87AbSwpzYXFgSFvY4sELWYepvKBsySAEla1w",
  "WyIyZFJVWFBfajlIUF9nX1VFYTZRVkh3IiwicG9zdGFsX2NvZGUiLCJDQSA5MjM4NCJd": [
    "2dRUXP_j9HP_g_UEa6QVHw",
    "postal_code",
    "CA 92384"
  ]
}
{
  "digest": "XRfCEe5Os-JpS99QWcZjD_nut7wBpsD3_WcZ6ujhlx0",
  "WyJSbmNCeVBRdW5NbTNtb3hDRnVaOFpRIiwiY291bnRyeSIsIlVTQSJd": [
    "RncByPQunMm3moxCFuZ8ZQ",
    "country",
    "USA"
  ]
}
{
  "digest": "utmrilRXgSDplnQdunNS6vbo18PvrKl6xDkksTVwauY",
  "WyI1NmJLM1BKcG1wZ1kzdlA4WHNUYnZnIiwiaXNzdWluZ19hdXRob3JpdHkiLCJVUyJd": [
    "56bK3PJpmpgY3vP8XsTbvg",
    "issuing_authority",
    "US"
  ]
}
{
  "digest": "ezODKMvCFYoDGg4mcKDRuW2OziLjixV4JFCQKFzBtlM",
  "WyJjdXNabVM1MHVteUk3ZWc2SGhJYWNnIiwiaXNzdWluZ19jb3VudHJ5IiwiVVMiXQ": [
    "cusZmS50umyI7eg6HhIacg",
    "issuing_country",
    "US"
  ]
}

ここでポイントとなるのは、cnf.jwk プロパティの値が一番目の鍵証明に埋め込まれている公開鍵と合致することです。

"cnf": {
  "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "kid": "4M9kIrB9WYzt1GQgL12lzdBZsGyeV3lgPKov28oT5L4",
    "x": "PSxQrD2zl0_mXcAqz1mgqSeBoBhnmx2yxBEprBY8F20",
    "y": "xV8fbi1FSosUunLeuLNuLkJiqmY6TKiMnur-Gn2wR10"
  }
}

同様に、二番目の VC をデコードすると次のようになります。

{
  "kid": "J1FwJP87C6-QN_WSIOmJAQc6n5CQ_bZdaFJ5GDnW1Rk",
  "typ": "vc+sd-jwt",
  "alg": "ES256"
}
{
  "place_of_birth": {
    "_sd": [
      "IwmjQMwtNNOgVByenkaf1zdrbsR-illZm7zG6aozmMU",
      "SqSnQoAgmbjKwdW0M08oVu_vKK-i5mP7DsU6ypw1Nzs"
    ]
  },
  "_sd": [
    "3qyJcInMTE8VmoAPt5MRcmuDDapTL_AJdGlcqS7IEPE",
    "6J-ld9Ky-CxTIxjnyF6v3_BKj52GMFCxgt_AJvINsQU",
    "6kcwpvDBgNCVE33dgRgyb2upDA5n9MFa5-kkZaJFse4",
    "DX4IOHFUEhT6o6SjO5hPfbyGOosfp-GV8MvldfuvirY",
    "Eb-0mOFCFYebWeOcuM6kPEaiDXxNvDLPjr93o3Q9Jis",
    "IKO9y8FvNYiLdiaEpOHAejXDaX1WWyTORtTvv7iIS-M",
    "IKT04dqgSa3PziIMedmSCNY9z24opA3Fhz-e0m7qWEY",
    "IyHzhcH7i3sdK_6KEIIgCiY3EgC9PTiNscWiwPFKqGk",
    "JMyx8C4ajLUO3KGsSD2hnC1qBiI_4I8esd1d3ersLSk",
    "SQC3IBC2ZaVPBdU4Bf2eFHpUCR2ZEMnfAPQmN2ekiio",
    "XEIVj8_cWJ4AWSwq6V2ZNnP9gr_iU5qBdplSAr0t-I8",
    "Z45vc3_ZqI6RMF7LZhNWeZvbR0EP75Cm-wMqEnJGZVA",
    "_F6VLNzu3wqSS8pQWZyyzN-TrlFV_Xy-zuZcQl1VwcQ",
    "ayvRUSvg8ksy2y4B8FRMz4lhMWrXTdpUYOjtiYgUQDQ",
    "g8R8afpQTDseQdf6jmr_LchULTseuzruIlTUie4TWV0",
    "gwipMBDHTCjql0ytoW6v7APj5VnlmZLMl2RSXeVSEwI",
    "hBxQxjOt0C3xIJYbcOhRpSf9j6cJm0E3hQNVTSOOz2A",
    "l5RFyERQPtE-LzkdEPhWyz4wmyw1F-MQI0Av1HdDz4E",
    "q4vW4tK4d2u-ody2l3m6_jbmx5ZSRiUO5G7Ip13NJ1w",
    "uXiO47XWWlGPDa6q-sJNPsDzLwWLli1427cP6A3C3HU",
    "yBoPxiDnZ5aigIv5YzOU112AWGOpUiSCF3Jdx2v6Dt8",
    "zOQ6jU7_Ox07qYqIJ7oW216bbQdsmy_AyGq0imnnyAc"
  ],
  "address": {
    "_sd": [
      "2CviwUz5xL0RYFsPaHYYxUJUx0cOoDXBaIlfsoXrhzo",
      "56k05jWNcZ-EpgYhh3cRpDJ8nJEW8-8ZqoIcrXUbpAM",
      "7wyr7x39o2SzV7trbuICxwLjMPbiwQvu4xuG7OlALiY",
      "Jl8BoyDHMy1yZJAa0DybSBsmBRgXEUOEQ41olpKon8w",
      "KsXxsBHYo_owUkbxVUQOFcPth9ejDnAg8caSWAWxGaI",
      "Yzluf4m4Ltah0Eshk3CF7iS-q2tRm2iW_UuYicmO9Ks",
      "n-jVZHE6d3t85ZCPWa1NBnJMSEt80yOL1JatQueXfwo",
      "omAxklUKrr2Ol5ogFY6XgI-Xm4v63UkVb1_OWaXkkEQ",
      "wWBkWPeMRuGF3b6KGkBxrxgYckjW3qPDDOtr9PNwCbE"
    ]
  },
  "vct": "urn:eu.europa.ec.eudi:pid:1",
  "_sd_alg": "sha-256",
  "iss": "https://trial.authlete.net",
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "kid": "B8os8dNBM1viONYOzOHfPKJwp3SGlMYKSNACSWuZRqs",
      "x": "JJ-7f0AsQ5fIRdqh2rIz1I-6JJAMJ0B533R2no-Fk_A",
      "y": "z-1Efw6fdhgdKTFHIXNH29m_TNZcYJC-LCIU6YdPtR8"
    }
  },
  "iat": 1719476855,
  "age_equal_or_over": {
    "_sd": [
      "I8GGGeWArEG00Pqb5_jfBg0hXFwDOY9i4gmOYLju9-Y",
      "uHfl4bgM0Wb6a0VHu3YltaMfHduMvHo221UAQBd08XU"
    ]
  }
}
{
  "digest": "l5RFyERQPtE-LzkdEPhWyz4wmyw1F-MQI0Av1HdDz4E",
  "WyJBMFYzTDlwdzQ3U1BfSk5XYTVFOGhBIiwic3ViIiwiMTAwNCJd": [
    "A0V3L9pw47SP_JNWa5E8hA",
    "sub",
    "1004"
  ]
}
{
  "digest": "IKT04dqgSa3PziIMedmSCNY9z24opA3Fhz-e0m7qWEY",
  "WyJLZy1VTFN4N2JlOGRlYm5yMngyVjRnIiwiZmFtaWx5X25hbWUiLCJTaWx2ZXJzdG9uZSJd": [
    "Kg-ULSx7be8debnr2x2V4g",
    "family_name",
    "Silverstone"
  ]
}
{
  "digest": "uXiO47XWWlGPDa6q-sJNPsDzLwWLli1427cP6A3C3HU",
  "WyJZLUVCUkw0OTJyUm54T3BGaWtUOE1BIiwiZ2l2ZW5fbmFtZSIsIkluZ2EiXQ": [
    "Y-EBRL492rRnxOpFikT8MA",
    "given_name",
    "Inga"
  ]
}
{
  "digest": "zOQ6jU7_Ox07qYqIJ7oW216bbQdsmy_AyGq0imnnyAc",
  "WyJreUFTdHg1SWNUaXVfQ3RNdTBDck5RIiwiYmlydGhkYXRlIiwiMTk5MS0xMS0wNiJd": [
    "kyAStx5IcTiu_CtMu0CrNQ",
    "birthdate",
    "1991-11-06"
  ]
}
{
  "digest": "uHfl4bgM0Wb6a0VHu3YltaMfHduMvHo221UAQBd08XU",
  "WyI4WGJCNGRRZENZbkh6a3BuR094YU1RIiwiMTgiLHRydWVd": [
    "8XbB4dQdCYnHzkpnGOxaMQ",
    "18",
    true
  ]
}
{
  "digest": "IwmjQMwtNNOgVByenkaf1zdrbsR-illZm7zG6aozmMU",
  "WyJYM0RBUHdFWTRGaEQ2UUNNSU5idXBnIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd": [
    "X3DAPwEY4FhD6QCMINbupg",
    "locality",
    "Shoshone"
  ]
}
{
  "digest": "7wyr7x39o2SzV7trbuICxwLjMPbiwQvu4xuG7OlALiY",
  "WyJ3SDhKTE5tc1dkcVdfZ3Q3RmtQbGpRIiwiZm9ybWF0dGVkIiwiMTE0IE9sZCBTdGF0ZSBId3kgMTI3LCBTaG9zaG9uZSwgQ0EgOTIzODQsIFVTQSJd": [
    "wH8JLNmsWdqW_gt7FkPljQ",
    "formatted",
    "114 Old State Hwy 127, Shoshone, CA 92384, USA"
  ]
}
{
  "digest": "Jl8BoyDHMy1yZJAa0DybSBsmBRgXEUOEQ41olpKon8w",
  "WyJEVUJaRXNkWVVPREhoZTJ3TUxFMnpBIiwic3RyZWV0X2FkZHJlc3MiLCIxMTQgT2xkIFN0YXRlIEh3eSAxMjciXQ": [
    "DUBZEsdYUODHhe2wMLE2zA",
    "street_address",
    "114 Old State Hwy 127"
  ]
}
{
  "digest": "wWBkWPeMRuGF3b6KGkBxrxgYckjW3qPDDOtr9PNwCbE",
  "WyJ6dXQzd0pnbjN1MExCcTJRNTlXZEFRIiwibG9jYWxpdHkiLCJTaG9zaG9uZSJd": [
    "zut3wJgn3u0LBq2Q59WdAQ",
    "locality",
    "Shoshone"
  ]
}
{
  "digest": "n-jVZHE6d3t85ZCPWa1NBnJMSEt80yOL1JatQueXfwo",
  "WyJsVzdXQTkxakRON1RGWkpRRzJNUi1BIiwicG9zdGFsX2NvZGUiLCJDQSA5MjM4NCJd": [
    "lW7WA91jDN7TFZJQG2MR-A",
    "postal_code",
    "CA 92384"
  ]
}
{
  "digest": "Yzluf4m4Ltah0Eshk3CF7iS-q2tRm2iW_UuYicmO9Ks",
  "WyJlczdiUHpNeXdNaXJON0tzemtpMFdRIiwiY291bnRyeSIsIlVTQSJd": [
    "es7bPzMywMirN7Kszki0WQ",
    "country",
    "USA"
  ]
}
{
  "digest": "IyHzhcH7i3sdK_6KEIIgCiY3EgC9PTiNscWiwPFKqGk",
  "WyJtOEVlUUM2akhIeUlSWGVDQXhERi1nIiwiaXNzdWluZ19hdXRob3JpdHkiLCJVUyJd": [
    "m8EeQC6jHHyIRXeCAxDF-g",
    "issuing_authority",
    "US"
  ]
}
{
  "digest": "6J-ld9Ky-CxTIxjnyF6v3_BKj52GMFCxgt_AJvINsQU",
  "WyIxMzhhaHkwMVdvNFFxekZyNFlpSld3IiwiaXNzdWluZ19jb3VudHJ5IiwiVVMiXQ": [
    "138ahy01Wo4QqzFr4YiJWw",
    "issuing_country",
    "US"
  ]
}

二番目の VC の cnf.jwk プロパティが二番目の鍵証明に埋め込まれた公開鍵に合致することを確認できます。

"cnf": {
  "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "kid": "B8os8dNBM1viONYOzOHfPKJwp3SGlMYKSNACSWuZRqs",
    "x": "JJ-7f0AsQ5fIRdqh2rIz1I-6JJAMJ0B533R2no-Fk_A",
    "y": "z-1Efw6fdhgdKTFHIXNH29m_TNZcYJC-LCIU6YdPtR8"
  }
}