Table of Contents
この文書は、『OpenID for Verifiable Credential Issuance』(OID4VCI) 仕様、および Authlete が当仕様をどのようにサポートするかを解説します。
優に百枚を超える図を用いて様々な概念の概要と詳細を丁寧に説明します。読者が文書内を行ったり来たりする必要に迫られることなく内容を理解できるよう、説明前の概念を前提知識として使うことを注意深く避けています。また、読者が途中で迷子にならないよう、全体像を説明している途中で詳細説明に入らないようにしています。これらの配慮により、この文書は仕様本体よりも格段に読みやすくなっています。そのため、事前にこの文書を読んでおけば仕様を読む際に大きな助けとなるでしょう。
変更履歴 | |
---|---|
日付 | 変更内容 |
2023 年 10 月 22 日 | 初版公開。 |
2023 年 10 月 27 日 | 『OID4VCI 実装』セクションに幾つかサブセクションを追加。 |
2023 年 11 月 15 日 |
下記の仕様変更に追随するため、説明と図を更新。
|
2023 年 11 月 24 日 |
『OID4VCI デモ』セクションを追加。
下記の仕様変更に追随するため、説明と図を更新。
|
2023 年 12 月 30 日 |
mdoc デモ用のセクションを追加。
下記の仕様変更に追随するため、説明と図を更新。
|
2024 年 1 月 31 日 |
下記の仕様変更に追随するために更新。
|
2024 年 2 月 3 日 |
下記の仕様変更に追随するために更新。
|
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 プロファイル』セクションを追加。 |
OID4VCI 仕様は、Verifiable Credential (検証可能な資格証明) の発行に関する規則を定義します。
『Verifiable Credential 』(検証可能な資格証明) は、OID4VCI 仕様の中心となる技術用語です。以降、本文書では VC と呼ぶことにします。
用語内の『Credential』(資格証明) は、一人のユーザまたは複数のユーザ (または特定可能な任意の実体) に関するデータ集合を表しています。氏名や生年月日といった情報はユーザに関するデータの例です。
用語内の『Verifiable』(検証可能な) は、データ集合が改竄されていないことを検証可能であることを示しています。技術的には、データ集合が電子的に署名されていることを意味します。
携帯端末上の電子的な運転免許証や健康保険証などが VC の例です。
VC は『Credential Issuer 』(資格証明発行者) により発行されます。クレデンシャルイシュアも技術用語です。仕様はクレデンシャルイシュアの動作を記述しています。
VC をクレデンシャルイシュアから取得するためには、発行を要求する際にクレデンシャルイシュアに対して『Access Token 』(アクセストークン) を提示しなければなりません。このアクセストークンとは、OAuth 2.0 の中心となる仕様である RFC 6749 で定義されているアクセストークンを指します。
アクセストークンは『Authorization Server 』(認可サーバ) により発行されます。認可サーバの基本的な動作は RFC 6749 で定義されており、また、その RFC 6749 を中心として、認可サーバの追加機能を定義する数多くの標準仕様が存在します。OID4VCI 仕様も同様に、VC 発行に使えるアクセストークンを認可サーバが発行できるようにするため、認可サーバに対する追加仕様を定めています。
OID4VCI 仕様では、VC を取得するために認可サーバやクレデンシャルイシュアとやりとりするソフトウェアアプリケーションを『Wallet 』(ウォレット) と呼びます。技術的には、VC 発行の文脈ではウォレットは OAuth 2.0 のクライアントアプリケーションとして動作します。そのため、OID4VCI 仕様の文脈では、技術的な観点からは、ウォレットという用語とクライアントアプリケーションという用語は相互に交換可能です。
次の図は中心となる技術用語間の関係を示しています。
仕様では、VC 発行に使えるアクセストークンを発行するための方法を幾つか定義しています。
それらのうちの一つは完全に新しいもので、『Pre-Authorized Code Flow 』(事前認可コードフロー) と呼ばれます。このフローでは、最初の手順として、ウォレットはクレデンシャルイシュアから『pre-authorized code』(事前認可コード) を取得します。
そして、ウォレットは事前認可コードを認可サーバの『Token Endpoint 』(トークンエンドポイント) (RFC 6749, 3.2. Token Endpoint) に提示します。
引き換えに、ウォレットはアクセストークンを受け取ります。
先ほど説明したように、ウォレットはそのアクセストークンをクレデンシャルイシュアに提示します。具体的には、ウォレットはアクセストークンをクレデンシャルイシュアの『Credential Endpoint 』(クレデンシャルエンドポイント) に提示します。
引き換えに、ウォレットは VC を受け取ります。
次の図は事前認可コードフローの概要を示しています。
事前認可コードフロー以外の方法は、伝統的な『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 仕様は認可コードフロー中の認可リクエストを拡張します。具体的には、次に挙げる認可リクエストパラメーター群を利用します。
issuer_state
リクエストパラメーターauthorization_details
リクエストパラメーターscope
リクエストパラメーターissuer_state
リクエストパラメーターは OID4VCI 仕様で定義される新しいパラメーターです。
authorization_details
リクエストパラメーターは RFC 9396 OAuth 2.0 Rich Authorization Requests で定義されています。この仕様は RAR
scope
リクエストパラメーターは RFC 6749 The OAuth 2.0 Authorization Framework で定義されているパラメーターです。
issuer_state
リクエストパラメーターは OID4VCI 仕様で定義されています。このリクエストパラメーターを使うためには、認可リクエストをおこなう前に、ウォレットはクレデンシャルイシュアから『Issuer State
その後ウォレットは、そのイシュアステートを issuer_state
リクエストパラメーターの値として含む認可リクエストをおこないます。
認可リクエストの残りの部分は、通常の認可コードフローと同じです。
次の図はイシュアステートを用いる認可コードフローを示しています。
RAR 仕様 (RFC 9396) は、認可に関する詳細情報を伝えるための汎用的なパラメーターとして authorization_details
を定義しています。このパラメーターをどのように使うかは、それぞれの運用に任されています。
このパラメーターの値は JSON 配列であり、配列の各要素は JSON オブジェクトです。このオブジェクトは『RAR オブジェクト』と呼ばれます。
RAR オブジェクトは柔軟です。任意のプロパティをオブジェクト内に置くことができます。しかし、想定される用途で共通して使われるであろうプロパティがあるため、RAR 仕様は幾つかのプロパティを事前に定義しています。
そのような事前定義されたプロパティ群のうち、"type"
プロパティだけは必須です。このプロパティは RAR オブジェクトが何を表しているのかを示します。
そして OID4VCI 仕様は、ウォレットが欲しい VC に関する情報を含む RAR オブジェクトであることを示すための特別な "type"
値として "openid_credential"
を定義しています。
scope
リクエストパラメーターは OAuth 2.0 の中心仕様 (RFC 6749) で定義されている伝統的なパラメーターの一つです。クライアントアプリケーションが欲しい権限を列挙するのが、このパラメーターの元来の用途です。ユーザがリクエストを承認すれば、認可サーバは要求された権限を持つアクセストークンを発行します。
歴史的に scope
リクエストパラメーターは元々意図していた用途外でも利用されてきました。そして、OID4VCI 仕様も同様に scope
リクエストパラメーターの使い方を拡張します。
クレデンシャルイシュアは、自身が発行可能な VC を『Credential Configuration "scope"
プロパティを持つことがあります。
ウォレットは、どの種類の VC が欲しいかを示すために、"scope"
プロパティの値を scope
リクエストパラメーターに含めることができます。
複数のクレデンシャル設定が同じ値を "scope"
プロパティに持つ場合もあります。
認可サーバからアクセストークンを取得すれば、ウォレットはアクセストークンを提示することにより、クレデンシャルイシュアに VC の発行を要求することができます。
基本的な手順では、ウォレットはクレデンシャルイシュアのクレデンシャルエンドポイントにアクセストークンを含むクレデンシャルリクエストを送ります。
クレデンシャルイシュアは応答として要求された VC を発行します。
しかし、要求された時点で VC が用意できていないこともありえます。たとえば、裏で時間のかかるオフライン処理が走っているかもしれません。
このような場合、クレデンシャルイシュアは代わりに『Transaction ID
ウォレットは VC 発行の準備が整うまで待機します。その後、受信済みのトランザクション ID とアクセストークンを『Deferred Credential Endpoint
クレデンシャルイシュアは応答として要求された VC を発行します。
VC が依然として用意できていない場合、遅延クレデンシャルエンドポイントはその旨を示すエラー (たとえば "error":"issuance_pending"
) を返すでしょう。このような場合、ウォレットは後ほど再び『Deferred Credential Request
ウォレットは一度に複数の VC を入手したいことがあるかもしれません。そのような場合のために『Batch Credential Endpoint
ウォレットはアクセストークンを含む『Batch Credential Request
エンドポイントは、複数の VC またはトランザクション ID を返却します。
各トランザクション ID は、遅延クレデンシャルエンドポイントから VC を取得するために使うことができます。
ここまでの説明で、アクセストークン発行とクレデンシャル発行の概要を見てきました。この節ではアクセストークン発行の詳細について見ていこうと思います。
クレデンシャルイシュアが事前認可コードを発行する際、それを直接発行する代わりに、それを内包する『Credential Offer
同様に、イシュアステートもクレデンシャルオファーの一部として含まれます。クレデンシャルオファーは、事前認可コードもしくはイシュアステート、または両方を含むことがあります。
クレデンシャルオファーは他の情報も含んでいます。クレデンシャルイシュアの識別子は常に含まれます。
また、クレデンシャルオファーは、クレデンシャルイシュアが提供する VC に関する情報を含んでいます。
クレデンシャルオファーをウォレットに送信するために URL が使われます。この URL は credential_offer
というクエリパラメーターを伴う『Credential Offer Endpoint
その URL が何らかの方法でアクセスされ、そのアクセスをウォレットが処理することができれば、ウォレットはクレデンシャルオファーを受け取ることができます。
しかし、ここで問題なのは、どのようにそのアクセスを引き起こすかです。OID4VCI 仕様ではクレデンシャルイシュアによる HTTP GET リクエストや HTTP リダイレクションの始動を想定していますが、アクセスの引き起こし方についてクレデンシャルイシュアとウォレットがどのように合意するかは定めていません。
また、クレデンシャルオファーを提供する際、クレデンシャルイシュアはクレデンシャルオファーエンドポイントの値を知ることができないという別の問題もあります。仕様ではウォレットのクレデンシャルオファーエンドポイントを表す credential_offer_endpoint
というクライアントメタデータを定義しています。しかし、仕様が言及している「URL を QR コードで表現する手法」が用いられる場合は特に、どのウォレットに対してクレデンシャルオファーを発行しようとしているかをクレデンシャルイシュアは知ることができないので、クレデンシャルイシュアはウォレットのメタデータの情報を知りえません。このような場合のため、クレデンシャルオファーエンドポイントの代替として openid-credential-offer://
が定義されています。
クレデンシャルオファーは参照によってウォレットに渡されるかもしれません。具体的には、URL は発行されたクレデンシャルオファーの内容ではなく、その配置場所に関する情報を含んでいるかもしれません。この場合、その場所を示すために credential_offer_uri
クエリパラメーターが用いられます。
credential_offer_uri
クエリパラメーターの値は、発行されたクレデンシャルオファーの内容を返すエンドポイントを指しています。
その URI にアクセスすることにより、
ウォレットは発行されたクレデンシャルオファーの内容を取得することができます。
クレデンシャルオファーの実際の内容は JSON オブジェクトです。
クレデンシャルイシュアの識別子が、"credential_issuer"
プロパティの値として置かれます。
クレデンシャルイシュアが提供する VC に関する情報は “credential_
配列に置かれます。配列要素の詳細については後ほど紹介します。
イシュアステートが発行される場合は、幾分ネストした場所に置かれます。
クレデンシャルオファーには、トップレベルのプロパティとして "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
オブジェクトが含まれていた場合、事前認可コードを用いたトークンリクエストはトランザクションコードを含まなければなりません。
詳細については後ほど記述します。
次の図はクレデンシャルオファーの内容の構造を示した概要図です。
クレデンシャルオファー内の “credential_
プロパティは、クレデンシャルイシュアが提供する VC に関する設定を保持しています。
このプロパティの値は JSON 配列です。各配列要素は JSON 文字列です。
要素の値はクレデンシャル設定の識別子を表しています。
事前認可コードが得られれば、ウォレットはその事前認可コードを用いてトークンリクエストを実行できます。
次の表は、事前認可コードフローに準拠するトークンリクエストで必要とされるリクエストパラメーター群を列挙したものです。
パラメーター | 説明 |
---|---|
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_
レスポンスパラメーターを含む場合があります。
これらのレスポンスパラメーター群の詳細は後ほど説明します。
VC 用アクセストークンを発行する手段が幾つか用意されているものの、アクセストークンの実装の観点から言えば、全て一つの共通の目標に収束します。それは、発行可能な VC 種別に関する情報をアクセストークンに紐付けることです。
この文書では、その情報を『Issuable Credential
クレデンシャルオファーの "credential_configuration_ids"
配列の要素 JSON 文字列です。
それらは、クレデンシャル設定を参照することにより発行可能クレデンシャルを間接的に指定しています。
この例では "credential_configuration_ids"
配列の要素数は 1 ですが、配列が複数の要素を含むことはありえます。
それらの要素により、クレデンシャルオファーが表す発行可能クレデンシャル群の集合が形成されます。
そのため、事前認可コードフローを用いるトークンリクエストは、事前認可コードを含むクレデンシャルオファーで指定される発行可能クレデンシャル群に紐付いたアクセストークンを要求していると言えます。
同様に、issuer_state
リクエストパラメーターを用いる認可リクエストは、イシュアステートを含むクレデンシャルオファーで指定される発行可能クレデンシャル群に紐付いたアクセストークンを要求していると言えます。
"type":"openid_credential"
を持つ RAR オブジェクトは、発行可能クレデンシャルのベースとなるクレデンシャル設定を
credential_
プロパティを使って指定します。
または、代わりに format
プロパティを使うこともあります。
scope
リクエストパラメーター内の値は、クレデンシャル設定の "scope"
プロパティを介して一つ以上の発行可能クレデンシャルを間接的に指定するかもしれません。
現在の OID4VCI 仕様のドラフトでは発行可能クレデンシャルを指定するこれらの仕組みが同時に使われた場合について明示的に言及していませんが、我々の解釈と実装では、これらの仕組みで指定された全ての発行可能クレデンシャル群は結合されて一つの集合を形成します。
次の図は発行可能クレデンシャルを指定する仕組みの全体像を示しています。
図中の credential info (クレデンシャル情報) と RAR オブジェクトの構造について気になっているかもしれません。
しかし、それらの詳細に立ち入る前に、VC のフォーマットについて見ていく必要があります。
VC のフォーマットを議論するためには、それらが意図する目的を理解する必要があります。VC の検証処理を一つ一つ見ていくことにしましょう。
まず始めに、クレデンシャルイシュアは VC に含めるデータを用意します。この例では、name (氏名)、birthdate (生年月日)、address (住所) を使います。
データに署名するため、クレデンシャルイシュアは private key (秘密鍵) と public key (公開鍵) の組を用意します。
その後、クレデンシャルイシュアは秘密鍵を用いてデータに署名します。結果として、署名が生成されます。
このデータと署名の集合が VC です。
クレデンシャルイシュアは VC をウォレットに渡します。
ウォレットが VC の署名を検証したければ、何らかの方法でクレデンシャルイシュアから公開鍵を取得し、それを署名検証に用います。検証が成功すれば、その VC が正当なクレデンシャルイシュアから発行されたこと、及びその内容が改竄されていないことが保証されます。
VC の利用方法と ID トークンの利用方法との重要な違いは、VC の『Holder
ウォレットは VP を他者に提示します。VP を受け取る部外者は『Verifier
ベリファイアが VP の署名を検証したければ、何らかの方法でクレデンシャルイシュアから公開鍵を取得し、それを署名検証に用います。
ところで、ベリファイアは次の事項をどうすれば確認できるでしょうか?
一点目については、ホルダーに秘密鍵と公開鍵の組を提供してもらい、
そのホルダーの秘密鍵を用いて作成した署名を VP に含めることで実現できるでしょう。署名対象のデータは、署名と共に提示される限り、任意のデータでかまいません。
二点目については、ホルダーの公開鍵を VC のデータに含め、クレデンシャルイシュアにデータ全体に対して署名してもらうことで実現できます。
VC とホルダー間のこのような暗号的関連付けは『Key Binding
ウォレットが暗号的キーバインディング付きの VC を要求する場合、クレデンシャルリクエストに公開鍵を含めます。しかし、クレデンシャルイシュアは提示された公開鍵を無条件に受け入れるわけにはいきません。というのは、悪意のあるウォレットが無関係な公開鍵を提示するかもしれないからです。
そのため、ウォレットは自身が公開鍵の正当な所有者であることを示さなければなりません。これを実現するため、ウォレットは対応する秘密鍵を用いて署名を生成し、公開鍵と併せて提示します。この署名と公開鍵の組みは一般的に『Key Proof
提示された公開鍵の有効性を確認できれば、クレデンシャルイシュアはキーバインディング付きの VC を生成することができます。
その VC はウォレットに渡され、
ウォレットはその VC を元に VP を生成します。ウォレットは秘密鍵を用いて作成した署名を VP に含めます。
そして VP がベリファイアに渡されます。
ベリファイアは、VP に埋め込まれている公開鍵を用いてウォレットが追加した署名を検証することができます。
次の図は、これまでに説明したクレデンシャル検証処理の全体像を示しています。
VP を提示する際、ホルダーは VC の内容の一部のみを開示することを選ぶかもしれません。例えば、VC が氏名、生年月日、住所を含んでいるとき、ホルダーは氏名と生年月日のみを開示し、住所の情報は省くことを選択するかもしれません。
このように情報を選んで開示することを『Selective Disclosure
しかし、特別な考慮をせずに情報を省略すると、署名検証が失敗してしまいます。なぜなら、署名が対象としたデータ集合とベリファイアが受け取ったデータ集合が異なるからです。
署名を無効化することなく選択的開示を実現する手法が幾つかあります。BBS+ (Boneh-Lynn-Shacham signature plus) や CL Signatures (Camenisch-Lysyanskaya Signatures) はそのような例で、有望のように思われます。しかしながら、現実世界では、特定の手法が採用されるかどうかは下記に挙げるように様々な要因に依存しており、必ずしも学術的に美しい理論に基づく解決策が普及するとは限りません。
徹底的なクレデンシャルプロファイル比較とハッカソンの後、業界は『Selective Disclosure for JWTs (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
次の図は、これまでに説明した SD-JWT 生成手順を示しています。
ここで鍵となるのは、SD-JWT の受信者が全てのディスクロージャを受け取っていない場合、ディスクロージャに対応するクレームしか再構築できないことです。そして重要なのは、そのような場合においてもクレデンシャルイシュアが署名した JWT の署名は有効のままという点です。
より詳細な情報は SD-JWT 仕様を参照してください。また、Java 言語用に書かれたオープンソースの SD-JWT ライブラリである authlete/sd-jwt の README にも有益な情報があります。
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_json
、jwt_vc_json-ld
、ldp_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
)
を開始点として公開鍵を探すことをベリファイアに強制することは理想的とは言えません。
代替となる開始点として、ウェルノーンパス /.well-known
が
SD-JWT-based Verifiable Credentials (SD-JWT VC)
という仕様内で提案されました。しかし、パス名が汎用的過ぎ、JWT
発行関連の他の仕様群と簡単に衝突する恐れがあるので、後日パス名は
/.well-known
に変更されました。
それでも依然として、このパス名は、VC のフォーマットが JWT
ベースであることを不必要に想定しているという問題を抱えています。
そのため、この解決方法を好まない人々もいます。
実際に OpenID Federation を活用しているイタリアのエコシステムは
/.well-known
を使わないことを決めました。
彼らは代わりに openid_
という新しいエンティティタイプ識別子を定義し、エンティティコンフィギュレーションの
“metadata”
オブジェクトに
VC 検証用の公開鍵を埋め込むことにしました。
関連する議題として、OAuth 2.0 Attestation-Based Client Authentication
と呼ばれる新しいクライアント認証方式が現在開発中です。
この方式のため、ウォレットはあらかじめ『Attester /.well-known
がここでも選択肢の一つとして提案されています。
これはまさに予想された通りの懸念事項です。つまり、アテスタとクレデンシャルイシュアを同じサーバ上で動かすことが技術的に不可能になるのです
(それら二つを同じサーバ上で動かすことが概念的に適切かどうかは別問題です)。
加えて、アテステーションのフォーマットが JWT かどうかは本質ではないのです。
しかし、アテステーションベースのクライアント認証に関してより深刻な問題は、 基本的な概念について完全な合意にまだ達していないことです (参照: ISSUE 61)。
前節で述べたように、VC フォーマットに関する多くの課題があります。しかし、VC フォーマットに期待される本質的機能は次のものに要約できると我々は考えています。
次の節では、これらの要求事項を満たすことのできる 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 仕様の対象範囲外です。
この文書では jwt_vc_json
などの他の VC フォーマットについては説明しません。
VC フォーマットについて見てきたので、アクセストークン用のクレデンシャル情報の議題に戻ることができます。
RAR オブジェクトのタイプが openid_credential
の場合、その RAR
オブジェクトは発行可能クレデンシャルに関する情報を含んでいます。
そのような RAR オブジェクトは必ず credential_
プロパティまたは format
プロパティを含んでいなければなりません。
この二つのプロパティは相互排他の関係にあります。
credential_configuration_id
プロパティを含む RAR オブジェクトcredential_
プロパティの値は、クレデンシャルイシュアメタデータ内のクレデンシャル設定を参照します。
仕様書では明示的に説明されていないものの、仕様書内の例から、
credential_
プロパティが参照するクレデンシャル設定は、
発行可能クレデンシャルを構築するためのベースとしてのみ使われるということが推察されます。
別の言い方をすると、クレデンシャルオファー内で参照されるクレデンシャル設定とは異なり、RAR
オブジェクト内で参照されるクレデンシャル設定は、クレデンシャルフォーマット (及び他の必須プロパティ;
vc+sd-jwt
フォーマットの場合における vct
や mso_mdoc
フォーマットの場合における doctype
など)
の情報を取得するためだけに利用されるということです。結果として、RAR
オブジェクトにはウォレットが入手したいクレーム群が列挙されることが想定されます。
クレーム群を列挙する方法は、クレデンシャルのフォーマットによって異なります。例えば、仕様書内の例によれば、
jwt_vc_json
フォーマットではクレーム群を列挙するのに credential_
プロパティを用います。
{
"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": {}
}
}
}
format
プロパティを含む RAR オブジェクトcredential_
プロパティの代わりに 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": {}
}
}
}
クレデンシャル設定の情報は『Credential Issuer Metadata
メタデータは JSON オブジェクトで、credentials_
JSON オブジェクトを含んでいます。そのオブジェクト内のエントリは、クレデンシャルイシュアがサポートする
VC のクレデンシャル設定を表しています。
クレデンシャル設定オブジェクト内のプロパティ群は、
(1) 全てのクレデンシャル設定オブジェクトに共通して現れるものと
(2) それぞれのフォーマットに固有のものとに分かれます。
例えば、format
プロパティは全ての対応クレデンシャルオブジェクトに含まれていますが、claims
プロパティは幾つかのフォーマットの場合のみ存在します。
これまでの説明でアクセストークン発行の詳細を見てきました。次はクレデンシャル発行の詳細について見ていきます。
『クレデンシャル検証』の節で説明したとおり、キーバインディング可能な VC を取得したいのであれば、ウォレットは鍵証明を提供することになります。
OID4VCI 仕様では、鍵証明のために三つの具体的なフォーマットが定義されています。 それらのフォーマットはそれぞれ、JWT (RFC 7519)、CWT (RFC 8392) 及び Data Integrity (Data Integrity) に基づいています。 将来必要になった際には、鍵証明フォーマットが追加されることもありえます。
定義により、鍵証明は公開鍵またはその鍵への参照を含んでいます。JWT に基づく鍵証明の場合、鍵情報を含めるために次に挙げる複数の方法を利用できます。鍵証明 JWT は、これらのうちの一つだけを使わなければなりません。
jwk
ヘッダパラメーター (RFC 7515, 4.1.3)x5c
ヘッダパラメーター (RFC 7515, 4.1.6)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_
により示されます。
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"
}
この文書では CWT に基づく鍵証明など、他の鍵証明の説明はしません。 それらについては OID4VCI 仕様を参照してください。
鍵証明リプレイへの主要な対抗策として、クレデンシャルイシュアは鍵証明に nonce
を含めることを要求するかもしれません。このクレームの値は、認可サーバまたはクレデンシャルイシュアから c_nonce
レスポンスパラメーターとして提供されます。
認可サーバからのトークンレスポンスには c_nonce
および c_nonce_
レスポンスパラメーターが含まれるかもしれません。c_nonce_
は
c_nonce
の有効期間の秒数を表しています。
ウォレットは、その c_nonce
レスポンスパラメーターの値を鍵証明 JWT の nonce
の値として使用します。
ウォレットはその鍵証明 JWT をクレデンシャルリクエストに含めます。
クレデンシャルイシュアが要求しているにも関わらず nonce
クレームが含まれていない場合、または、指定された nonce
値の期限が切れている場合、クレデンシャルエンドポイントはエラー応答を返します。そのエラー応答は、期待される c_nonce
値、または、新しい c_nonce
値のどちらかを含んでいます。また、nonce
値が有効な場合でも、クレデンシャルレスポンスは将来の利用を想定して c_nonce
を含んでいるかもしれません。いずれの場合も、クレデンシャルイシュアが nonce
を鍵証明に含めることを要求する場合、クレデンシャルレスポンスに c_nonce
が含まれます。
必要に応じ、ウォレットはクレデンシャルエンドポイントから提供された c_nonce
を用いて新しい鍵証明を再生成し、その新しい鍵証明を添えて再度クレデンシャルリクエストを実行できます。
次の図は c_nonce
の概要を示しています。
クレデンシャルリクエストは、アクセストークンと JSON 形式のペイロードを含む HTTP POST リクエストです。ペイロードにはクレデンシャル情報および任意の鍵証明が含まれます。
クレデンシャルリクエスト内のクレデンシャル情報は、必須の "format"
プロパティと、フォーマット固有のプロパティを含んでいます。例えば、"format"
プロパティの値が "jwt_vc_json"
であれば、"credential_definition"
プロパティがあるものと期待されます。
クレデンシャル情報は、ウォレットが入手したい VC について記述したものです。OID4VCI 仕様から抜粋した上記の図内の例で示唆されているように、クレデンシャルリクエスト内のクレデンシャル情報は、アクセストークンに紐付いている発行可能クレデンシャルを指定する以上のことをおこなっています。例えば、クレデンシャルリクエストの例の意図は、given_name
クレーム、family_name
クレーム、degree
クレームのみを含む jwt_vc_json
フォーマットの VC を要求するというものです。
しかし、幾つかの問題があります。
指定された条件を満たす発行可能クレデンシャルを決めるのは簡単ではない。
複数の発行可能クレデンシャルが条件を満たす可能性がある。
条件のわずかな違いで、異なる発行可能クレデンシャルが選ばれる可能性がある。
提示されたアクセストークンが指定された条件を満たす VC を要求する権限を持っているか確認するのは簡単ではない。
簡単に言うと、この仕様は実装に対する配慮が欠けています。 Issue 175 で報告されている問題は、 この仕様の欠陥により引き起こされる問題の一例です。 ですので、仕様が改善されない限り、「クレデンシャルイシュアは、 実行時に (アクセストークンリクエストやクレデンシャルリクエストで) 指定される詳細条件を無視し、構造が固定された VC を発行する」というものになる可能性が高いです。
クレデンシャルリクエスト内の鍵証明情報は "proof"
プロパティにより表されます。プロパティの値は JSON オブジェクトです。
"proof"
オブジェクトには、鍵証明のフォーマットを示す "proof_type"
プロパティが必ず含まれます。
"proof_type"
プロパティの値が "jwt"
の場合、鍵証明として JWT が使用されます。この場合、"proof"
オブジェクトには "jwt"
プロパティが含まれます。"jwt"
プロパティの値は、鍵証明 JWT 仕様に準拠する JWT です。
次の図はクレデンシャルリクエストの概要を示しています。
クレデンシャルレスポンスは JSON を含む HTTP レスポンスです。
VC の発行に成功した場合、VC は "credential"
プロパティの値として JSON に含まれます。"credential"
プロパティの値のフォーマットは、VC のフォーマットに依存します。
例えば、SD-JWT VC に準拠する SD-JWT ベースの VC であれば、"credential"
プロパティの値は SD-JWT フォーマットの JSON 文字列です。
加えて、先に説明したように、クレデンシャルレスポンスには c_nonce
レスポンスパラメーターと
c_nonce_
レスポンスパラメーターが含まれることがあります。
ここで SD-JWT ベースの VC の詳細について見ていきましょう。
SD-JWT は、クレデンシャルイシュアが署名した JWT と、0 個以上のディスクロージャ、そして、任意のキーバインディング JWT で構成されます。これらの構成要素は、チルダ (~
) を区切り文字として連結されています。なお、キーバインディング JWT はウォレットが生成するので、クレデンシャルイシュアが発行したときには VC はキーバインディング JWT を含んでいないので注意してください。
<クレデンシャルイシュアが署名したJWT>~<ディスクロージャ1>...<ディスクロージャN>~
SD-JWT の最初の構成要素はクレデンシャルイシュアが署名した JWT です。標準 JWT として、クレデンシャルイシュアが署名した JWT のヘッダとペイロードは base64url でデコードできます。
ここで重要な点は次の通りです。
typ
ヘッダパラメーターの値は "vc+sd-jwt"
である。"cnf"."jwk"
が含まれている。"_sd_alg"
プロパティが含まれており、これはディスクロージャ用のハッシュアルゴリズムを示している。"given_name"
のようなユーザクレームが含まれていない。代わりに "_sd"
配列があり、ユーザクレームのディスクロージャのダイジェスト値を保持している。SD-JWT ベースの VC の例には、四つのディスクロージャが含まれています。
ディスクロージャを base64url でデコードすることにより、元の JSON 配列が得られます。
ディスクロージャのダイジェスト値は "_sd_alg"
プロパティで示されるハッシュアルゴリズムを用いて計算され、"_sd"
配列に列挙されます。ダイジェスト値の順番は、SD-JWT 内のディスクロージャの順番とは独立していなければなりません。この例では、ダイジェスト値は ASCII コードの順番で並べられています。
次の図は、SD-JWT ベースの VC を含むクレデンシャルレスポンスの概要を示しています。
要求された 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 は、後ほどウォレットがクレデンシャルイシュアの遅延クレデンシャルエンドポイントに遅延クレデンシャルリクエストを送る際に使用されます。
クレデンシャルリクエストを成功裡に処理できない場合、クレデンシャルエンドポイントはエラー応答を返します。エラーの種別は "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
}
ウォレットは、トランザクション 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"
}
遅延クレデンシャルエンドポイントは 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"
}
クレデンシャルイシュアの一括クレデンシャルエンドポイントに一括クレデンシャルリクエストを送ることで、ウォレットは一度に複数の VC を要求することができます。
一括クレデンシャルリクエストは JSON を含む HTTP POST リクエストで、"credential_requests"
JSON 配列を含んでいます。配列は JSON オブジェクトのリストで、一つ一つがクレデンシャルリクエストを表しています。
各クレデンシャルリクエストはクレデンシャル情報と任意の鍵証明を含んでいます。
一括クレデンシャルリクエスト内のクレデンシャルリクエスト群が異なるクレデンシャルフォーマット、異なる鍵証明を持つことは許されています。逆もまた真です。クレデンシャルリクエスト群は同じクレデンシャルフォーマット、同じ鍵証明を持っていてもかまいません。
次の図は一括クレデンシャルリクエストの概要を示しています。
一括クレデンシャルレスポンスは JSON を含む HTTP レスポンスで、"credential_responses"
JSON 配列を含んでいます。配列は JSON オブジェクトのリストで、一つ一つがクレデンシャルレスポンスを表しています。配列の要素は、一括クレデンシャルリクエストの "credential_requests"
配列の要素に対応しています。
各クレデンシャルレスポンスは VC またはトランザクション ID を含んでいます。
加えて、将来ウォレットが鍵証明付きのクレデンシャルリクエストまたは一括クレデンシャルリクエストを送るときのため、一括クレデンシャルレスポンスは
c_nonce
と c_nonce_
をトップレベルプロパティとして含んでいるかもしれません。
次の図は一括クレデンシャルレスポンスの概要を示しています。
VC や VP の署名を検証するため、ベリファイアはクレデンシャルイシュアが VC に署名する際に使った秘密鍵に対応する公開鍵を取得する必要があります。
VC の署名を検証するための公開鍵を配布する方法は OIDC4VCI 仕様の範囲外の事項です。しかし、ここで幾つか提案されている手法について見ていきましょう。
公開鍵配布の一つの方法として、VC 内に公開鍵用の X.509 証明書を埋め込む方法があります。
JWT ベースの VC の場合、その目的のために "x5c"
ヘッダパラメーター (RFC 7515, 4.1.6) が使われることになるでしょう。
OpenID Federation 仕様を活用する方法では、クレデンシャルイシュアのエンティティコンフィギュレーションに公開鍵を埋め込みます。
イタリアのエコシステムでは、クレデンシャルイシュアを表す新しいエンティティタイプ識別子として openid_
を定義し、その "jwks"
メタデータをクレデンシャルイシュアの公開鍵を置く場所として使っています。
/.well-known
も公開鍵配布方法として提案されています。
このウェルノーンパスは、公開鍵を探す開始点となることを意図しています。
このウェルノーンパスは、JWT VC 発行者のメタデータを含む JSON を返します。
JSON 内の "jwks_uri"
プロパティが、発行者の JWK Set の場所を示しています。
ベリファイアはその JWK Set 内に目当ての公開鍵を見つけることができるでしょう。
OID4VCI 仕様は VC 発行の規則を定義しています。仕様の二つの主要議題は『アクセストークン発行』と『クレデンシャル発行』です。
アクセストークン発行のため、仕様は発行可能クレデンシャルを指定する方法を幾つか定義しており、これには
(1) クレデンシャルオファー内の事前認可コードを使う方法、
(2) クレデンシャルオファー内のイシュアステートを使う方法、
(3) "type":"openid_credential"
を持つ RAR オブジェクトを使う方法、
(4) credential_
メタデータ内のエントリを参照する scope
値を使う方法、が含まれます。
クレデンシャル発行のため、仕様は三つのエンドポイント、すなわち、(1) クレデンシャルエンドポイント、(2) 一括クレデンシャルエンドポイント、(3) 遅延クレデンシャルエンドポイント、を導入しています。
クレデンシャル情報が幾つかの場所に現れます。その場所とは、
(1) credential_
メタデータ、(2) RAR オブジェクト、(3) クレデンシャルリクエスト、(4) 一括クレデンシャルリクエスト、です。
これらの間の一貫性や一意識別性が欠けているため、仕様は意図した目的を完全には達成できないかもしれません。
しかし、完全な相互運用性は犠牲になるものの、実世界のエコシステムは、それぞれ補助的な仕様で補うことで、彼らの個別のニーズを満たす
VC を発行することができるでしょう。
仕様は VC フォーマットの詳細には立ち入りませんが、
jwt_vc_json
、jwt_vc_json-ld
、ldp_vc
、mso_mdoc
、vc+sd-jwt
に関連する規則を取り決めました。これらの中で、最近最も注目を集めているのは
SD-JWT VC と mdoc (ISO/IEC 18013-5:2021) です。
eIDAS 2.0 は SD-JWT ベースと mdoc ベースのフォーマットのサポートを必須としています。
公開鍵配布方法も仕様の対象外です。公開鍵配布の方法として、(1) X.509 証明書を VC 自身に埋め込む方法、
(2) クレデンシャルイシュアのエンティティコンフィギュレーション内の
“openid_
を使う方法、
(3) /.well-known
を使う方法、が提案されています。
OID4VCI 仕様には依然として改善の余地がありますが、実世界のエコシステムは実用的な妥協と補助的仕様により OID4VCI 仕様を補足することで、それぞれの特定のニーズに対応することができるでしょう。
開発者は、OID4VCI 仕様に準拠した自身のクレデンシャルイシュアおよび認可サーバ・OpenID プロバイダを Authlete を利用して作成することができます。
ほとんどのベンダーは認可サーバなどのフロントエンドサーバの実装を直接提供していますが、Authlete は異なるアプローチを取っています。Authlete は、開発者自身がフロントエンドサーバを実装するのに使うことのできる Web API のセットを提供します。Authlete はフロントエンドサーバ群の後ろに配置され、エンドユーザからは見えません。
Authlete のアーキテクチャは必然的に開発者にフロントエンドサーバの作成を要求しますが、その代わりに開発者は以下の利点を得られます。
OID4VCI 仕様は、2024 年 4 月頃リリース予定の Authlete 3.0 からサポートされます。
それまでの間、お客様とビジネスパートナー向けにトライアルサーバが利用可能です。OID4VCI の試験利用にご興味のある方はお問い合わせください。
Authlete サーバで『Verifiable Credentials』機能を有効にしておく必要があります。オンプレミス版の
Authlete を利用されている場合は、この機能を有効にするために設定ファイル
(authlete-server
) 内に次の行が含まれていることを確認してください。
feature.verifiable_credentials.enabled=true
プロパティ | 型 | 説明 | |
verifiable |
真偽値 | OID4VCI 仕様のサポートなどの Verifiable Credentials 関連機能の有効化・無効化を制御するフラグです。 | |
credential |
credential |
文字列 |
このサービスがクレデンシャルイシュアとして動作する際のクレデンシャルイシュア識別子。
このプロパティは OID4VCI で定義されている credential_
メタデータに対応します。
値は、スキーム https 、クエリー部無し、フラグメント部無し、の有効な URL でなければなりません。
また、Authlete 固有の制限として、アスキー文字のみ、最大長 200 という制限があります。
クレデンシャルイシュアとして動作するためにはこのプロパティを設定しなければなりません。 |
authorization |
文字列の |
このサービスがクレデンシャルイシュアとして動作する際に認可処理を任せる認可サーバ群の識別子群。
このプロパティは OID4VCI で定義されている authorization_
メタデータに対応します。
値は HTTP でアクセス可能な URL でなければなりません。 |
|
credential |
文字列 |
このサービスがクレデンシャルイシュアとして動作する際のクレデンシャルエンドポイントの URL。
このプロパティは OID4VCI で定義されている credential_
メタデータに対応します。
値は、スキーム https 、フラグメント部無し、の有効な URL でなければなりません。
また、Authlete 固有の制限として、アスキー文字のみ、最大長 200 という制限があります。
クレデンシャルイシュアとして動作するためにはこのプロパティを設定しなければなりません。 |
|
batch |
文字列 |
このサービスがクレデンシャルイシュアとして動作する際の一括クレデンシャルエンドポイントの URL。
このプロパティは OID4VCI で定義されている batch_
メタデータに対応します。
値は、スキーム https 、フラグメント部無し、の有効な URL でなければなりません。
また、Authlete 固有の制限として、アスキー文字のみ、最大長 200 という制限があります。
バッチクレデンシャルエンドポイントを実装するかどうかは任意です。 |
|
deferred |
文字列 |
このサービスがクレデンシャルイシュアとして動作する際の遅延クレデンシャルエンドポイントの URL。
このプロパティは OID4VCI 仕様で定義されている deferred_
メタデータに対応します。
値は、スキーム https 、フラグメント部無し、の有効な URL でなければなりません。
また、Authlete 固有の制限として、アスキー文字のみ、最大長 200 という制限があります。
クレデンシャルイシュアのクレデンシャルエンドポイントまたは一括クレデンシャルエンドポイントがトランザクション ID を発行することがある場合、遅延クレデンシャルエンドポイントを実装しなければなりません。 |
|
credential |
文字列の |
クレデンシャルレスポンス暗号化においてサポートされる JWE alg アルゴリズム群。
このプロパティは OID4VCI 仕様で定義されている
credential_
メタデータに対応します。
有効な値は、 "ECDH_ES" 等、
JWEAlg 列挙型の値です。非対称鍵系アルゴリズムのみ指定可能です。
|
|
credential |
文字列の |
クレデンシャルレスポンス暗号化においてサポートされる JWE enc アルゴリズム群。
このプロパティは OID4VCI 仕様で定義されている
credential_
メタデータに対応します。
有効な値は、 "A256GCM" 等、
JWEEnc 列挙型の値です。
|
|
require |
真偽値 |
クレデンシャルレスポンスを常に暗号化するかどうかを示すフラグです。
このプロパティは OID4VCI 仕様で定義されている
credential_
メタデータに対応します。
このプロパティが真に設定されている場合、全てのクレデンシャルリクエストは、 credential_ JSON
オブジェクトを含まなければなりません。
|
|
credentials |
文字列 |
このサービスがクレデンシャルイシュアとして動作する際にサポートするクレデンシャル群。
このプロパティは OID4VCI 仕様で定義されている
credential_
メタデータに対応します。
値は JSON オブジェクトでなければなりません。非アスキー文字を含めることもできますが、 Authlete 固有の制限として、最大文字数は 16383 に制限されます。 クレデンシャルイシュアとして動作するためには、このプロパティを設定しなければなりません。 後方互換性のため、このプロパティの名前は credential には変更されず、
credentials のままとなります。
|
|
credential |
数値 |
クレデンシャルオファーの有効時間のデフォルト値を秒数で表すプロパティです。
/vci/offer/create API への API コールが duration
リクエストパラメーターを含んでいないか、またはその値が 0
以下の場合、このプロパティの値がデフォルト値として使われます。
このプロパティの値が 0 以下の場合、Authlete サーバ単位で設定されているデフォルト値が使われます。 |
|
preAuthorized |
真偽値 |
このプロパティは、事前認可コードフローにおいて匿名クライアントからのトークンリクエストを許可するかどうかを示しています。
このプロパティは OID4VCI 仕様で定義されている pre-authorized_ メタデータに対応します。
|
|
cnonce |
数値 |
c_nonce 有効時間を秒数で表すプロパティです。
認可サーバのトークンエンドポイントが Verifiable Credential 発行に使用できるアクセストークンを発行する際、アクセストークンと同時に c_nonce を発行します。
また、クレデンシャルイシュアのクレデンシャルエンドポイントとバッチクレデンシャルエンドポイントは、提示された
c_nonce が有効期限切れの場合、新しい c_nonce を発行します。
このプロパティはそれらの c_nonce の有効時間として用いられます。
このプロパティの値が 0 以下の場合、Authlete サーバ単位で設定されているデフォルト値が使われます。 |
|
credential |
数値 |
クレデンシャルリクエストまたは一括クレデンシャルリクエストの結果として発行されるトランザクション
ID の有効時間のデフォルト値を秒数で表すプロパティです。
このプロパティの値が 0 以下の場合、Authlete サーバ単位で設定されているデフォルト値が使われます。 |
|
credential |
数値 |
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
オブジェクトが含まれており、そのオブジェクトの credential
パラメーターでデフォルト有効時間を上書きできます。
|
|
credential |
文字列 |
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
オブジェクトが含まれており、そのオブジェクトの signing
パラメーターで署名に使う秘密鍵の鍵 ID を指定することができます。
鍵 ID が指定されていないとき、Authlete は自動的に秘密鍵を選択します。
この credential プロパティが更新される際に JWK セット文書内の
JWK 群が kid プロパティ
(RFC 7517,
Section 4.5)
を含んでいない場合、Authlete は自動的に kid プロパティを挿入します。
SHA-256 ハッシュアルゴリズムを用いて計算した JWK Thumbprint
(RFC 7638) が
kid プロパティの値として使われます。
|
|
credential |
文字列 |
クレデンシャルイシュアの JWK セット文書を公開する場所を指す URL。
この URL は、JWT イシュアメタデータの jwks_uri プロパティーの値として使用されます。
メタデータそのものは /.well-known で公開されます。
JWT イシュアメタデータの詳細については
SD-JWT-based Verifiable Credentials (SD-JWT VC) を参照してください。
|
以下の図は、フロントエンドサーバ(クレデンシャルイシュアおよび認可サーバ)のエンドポイントと Authlete API の関係を示しています。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 = ...
に変更するだけで済みます。
既に述べたように、クレデンシャルオファー発行処理はクレデンシャルイシュアによって異なります。
例えば、Webブラウザ経由でユーザとやりとりした後、クレデンシャルイシュアは次のような QR コードを発行するかもしれません。
この QR コードは “openid-credential-offer://
”
を表していて、{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 です。
/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 |
|
credential |
文字列の配列で、クレデンシャルオファーの "credential_
プロパティの値として用いられます。このリクエストパラメーターは必須です。
|
|
authorization |
"grants" オブジェクト内に "authorization_code"
オブジェクトを含めるかどうかを示す真偽値 (true または false ) です。
|
|
issuer |
"grants" オブジェクト内の "authorization_code"
オブジェクトに "issuer_state" プロパティを含めるかを示す真偽値
(true または false ) です。
このパラメーターが true の場合、Authlete はイシュアステートを生成し、
"authorization_code" オブジェクト内に "issuer_state"
プロパティの値として配置します。
|
|
preAuthorized |
"grants" オブジェクト内に
"urn:
オブジェクトを含めるかどうかを示す真偽値 (true または false ) です。
このパラメーターが true の場合、Authlete は事前認可コードを生成し、
"urn:
オブジェクト内に "pre-authorized_code" プロパティの値として配置します。
|
|
tx |
事前認可コードに紐づけるトランザクションコードです。
このパラメーターが空でなければ
"urn:
オブジェクトに tx_code オブジェクトが埋め込まれます。
結果として、事前認可コードを用いるトークンリクエストは、このパラメーターで指定された値を
tx_code リクエストパラメーターの値として使わなければなりません。
|
|
tx |
トランザクションコードの入力モードです。このパラメーターで指定された値は
tx_code オブジェクト内の input_mode
プロパティの値として用いられます。
OID4VCI 仕様で事前定義された値は "numeric" と "text"
のみですが、将来の拡張性のため、/vci API
は事前定義されたもの以外の値も受け付けます。
|
|
tx |
トランザクションコードの説明です。このパラメーターで指定された値は
tx_code オブジェクト内の description
プロパティの値として用いられます。
|
|
subject |
クレデンシャルオファーに紐付けるユーザのサブジェクト (一意識別子) です。
このパラメーターは必須です。 |
|
duration |
クレデンシャルオファーの有効秒数です。
このパラメーターが 0 より大きい場合、その値が発行されるクレデンシャルオファーの有効時間として用いられます。 それ以外の場合、サービスの credential プロパティの値が用いられます。
|
|
context |
クレデンシャルオファーに紐付ける、用途制限のない任意の文字列です。
開発者は好きなようにこのパラメーターを利用できます。 Authlete はこのパラメーターの内容について関知しません。 |
|
properties |
クレデンシャルオファーに紐付けるエクストラプロパティで、用途制限のないキー・バリューの組です。
エクストラプロパティは、最終的にクレデンシャルオファーを使って作成されるアクセストークンに紐付けられます。 |
|
jwtAtClaims |
JWT アクセストークンのペイロード部に追加するクレーム群を JSON オブジェクト形式で表したものです。
このパラメーターは、サービスが発行するアクセストークンの形式が JWT の場合のみ意味があります。 別の言い方をすると、サービスの access
プロパティの値が設定されている場合のみ意味があります。
追加クレームは、最終的にクレデンシャルオファーを使って作成されるアクセストークンに紐付けられます。 |
|
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 を構築できます。
openid-credential-offer://
など。?credential_offer=
"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 ライブライ内の
Credential
Java クラスと
Credential
Java クラスで表現されます。
詳細はライブラリの JavaDoc を参照してください。
/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 を構築できます。
openid-credential-offer://
など。?credential_offer_uri=
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
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 |
下記の 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 が用意したレスポンスの内容をクレデンシャルレスポンスのメッセージボディとして使用できます。
最後に、クレデンシャルエンドポイントはクレデンシャルレスポンスをウォレットに返します。
次の図はクレデンシャルエンドポイントの実装内の処理手順を示しています。
クレデンシャル発行指示を用意する手順は次の通りです。
アクセストークン情報からアクセストークンに紐付くユーザのサブジェクト (= 一意識別子) を取得します。
/auth/introspection
API のレスポンス (IntrospectionResponse 参照)
に含まれる "subject"
プロパティがサブジェクトの値を保持しています。
サブジェクトで特定されるユーザの情報をユーザデータベースから取り出します。
アクセストークン情報からアクセストークンに紐付く発行可能クレデンシャルの情報を取得します。
/auth/introspection
API のレスポンスに含まれる "issuableCredentials"
プロパティが情報を文字列として保持しています。この文字列は JSON 配列としてパースする必要があります。
クレデンシャルリクエスト情報からクレデンシャルリクエストに含まれるクレデンシャル情報を取得します。
/vci/single/parse
API のレスポンス (CredentialSingleParseResponse 参照)
内の "info"
オブジェクトはクレデンシャルリクエストに関する様々な情報を保持しています。
その "info"
オブジェクト内の "format"
プロパティと "details"
プロパティを併せたものがクレデンシャル情報を表します。
"details"
プロパティの値は文字列です。この文字列は JSON オブジェクトとしてパースする必要があります。
その JSON オブジェクトの内容は、"format"
パラメーター、"proof"
パラメーター、
“credential_
パラメーターが含まれていないことを除き、
クレデンシャルリクエストとほぼ同じです。
クレデンシャル情報がいずれかの発行可能クレデンシャルのサブセットであるかどうかを調べることにより、 アクセストークンがクレデンシャルリクエストのための必要な権限を持っていることを確認します。
しかしながら、プログラマであれば、現在の OID4VCI 仕様ではこのステップを実装することが困難なことを理解できるでしょう。
さらに、SD-JWT VC では、「vct
の値によりクレームの集合を決め、個々のクレームを一つ一つ指定する必要をなくす」という提案があります。
この提案は、JSON オブジェクト同士の包含関係を機械的に調べることでアクセストークンの権限をチェックすることを不可能にします。
そのため、アクセストークンが十分な権限を持っていることの確認は、クレデンシャルイシュアがそれぞれの方針に基づいて実装することになります。 包含関係に基づく権限チェックは Authlete に実装済みではありますが、無効にしてあります。
クレデンシャル情報に基づき、発行する VC に埋め込むユーザクレームの集合を決定し、ユーザデータベースから取り出しておいたデータセットからそれらのユーザクレーム群の情報を取得します。
集めたデータを使いクレデンシャル発行指示を作成します。
クレデンシャル発行指示は下表に列挙されたプロパティを持つ 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 は自動的に鍵を選択します。 |
/vci/single/issue
API へのリクエスト (CredentialSingleIssueRequest 参照) を用意します。
/vci/single/issue API へのリクエスト | ||
HTTP メソッドと Content-Type | POST | application/json |
accessToken |
クレデンシャルエンドポイントに提示されたアクセストークン。 | |
order |
VC またはトランザクション ID を発行するための命令を提供するクレデンシャル発行指示。 |
用意したリクエストを /vci/single/issue
API に送ります。
次の図はクレデンシャル発行指示を用意する手順の要約です。
執筆予定。
このデモで使うリソースをダウンロードします。
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
『事前認可コード』を含む『クレデンシャルオファー』を生成するため、 https://trial.authlete.net/api/offer/issue にアクセスします。
この URL で表示されるページは、デモ用に任意のクレデンシャルオファーを生成するためのフォームを提供します。 フォームの『Pre-authorized code grant included』がチェックされていると、生成されるクレデンシャルオファーに事前認可コードが含まれるようになります。
Login ID フィールドと Password フィールドにそれぞれ inga
、inga
を入力し、
Pre-authorized code grant included にチェックがついていることを確認し、
Submit ボタンを押します。これにより、結果ページが表示されます。
結果ページで表示される QR コードは、クレデンシャルオファーを含む URL を表しています。
クレデンシャルオファーの中身は、QR コードの下に置かれている JSON に表示されます。
この JSON 内の pre-authorized_code
プロパティの値が発行された事前認可コードです。
次の手順で使うので、発行された事前認可コードをシェル変数 PRE_AUTHORIZED_CODE
に設定します。
PRE_AUTHORIZED_CODE=NH9udMon5pTuuvbsNsHUNWf8tpU__9wt-gsO9LeYthc
事前認可コードフローを用いて『トークンリクエスト』を送ります。 このデモのクライアントはパブリッククライアントなので、クライアント認証は要求されません。 つまり、クライアント認証に関連するリクエストパラメーターを追加する必要はありません。
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
保有者の鍵 holder.jwk
と generate-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`
生成した鍵証明と一緒に、『クレデンシャルエンドポイント』に『クレデンシャルリクエスト』を送ります。
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"
]
}
このデモで使うリソースをダウンロードします。
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
DPoP 用の秘密鍵 dpop.jwk
と generate-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.
を含んでいることです。
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_
オブジェクトに、
scope
プロパティの値が org.
であるクレデンシャル設定が少なくとも一つ含まれていることを想定しています。
{
"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"
}
ウェブブラウザを使い、認可エンドポイントに認可リクエストを送ります。URL 内の $REQUEST_URI
を先ほどのステップで PAR エンドポイントから受け取った実際のリクエスト URI で置き換えることを忘れないでください。
https://trial.authlete.net/api/authorization?client_id=218232426&request_uri=$REQUEST_URI
認可ページが表示されます。Login ID フィールドと Password フィールドに inga
, inga
と入力し、Authorize ボタンを押してください。
リダイレクトエンドポイントへとリダイレクトされます。 このエンドポイントで表示されるページでは、発行された認可コードの値を確認できます。 この認可コードは次のステップで使用します。
トークンエンドポイントにアクセスするための 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
クレデンシャルエンドポイントにアクセスするための 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'
]
}
}
POTENTIAL はヨーロッパのデジタルアイデンティティに特化したヨーロッパの組織です。 この組織は 2024 年春から相互運用性イベントを開催しています。このイベントは 6 つのトラックに分かれています。 トラック 1 と 2 は、クレデンシャルイシュアの相互運用性をテストするためのトラックです。 トラック 1 では VC の形式として mdoc が使用され、トラック 2 では SD-JWT VC が使用されます。
トラック 1 では 2 つのプロファイルが定義されています。 1 つは Light プロファイルと呼ばれ、もう 1 つは Full プロファイルと呼ばれます。 このセクションでは Light プロファイルの手順を説明します。
パラメータ | 値 |
---|---|
発行者識別子 | 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 で入手可能です。ただしこの実装はサンプルであり、商用利用は想定していないので注意してください。
パラメータ | 値 |
---|---|
発行者識別子 | 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 |
クレデンシャルイシュアのソースコードは認可サーバのソースコードと同じです。
パラメータ | 値 |
---|---|
クライアント識別子 | track1_light |
クライアントタイプ | パブリック (= クライアント認証は要求されない) |
リダイレクト URI |
|
このクライアントにリダイレクト URI の追加登録が必要であるか、または自分専用のクライアントが必要であれば、我々にご連絡ください。
クレデンシャルイシュアのサンプル実装は、開発者がテスト用に任意のクレデンシャルオファーを生成できるウェブページを提供しています。 ページの URL は https://trial.authlete.net/api/offer/issue です。 このウェブページにアクセスすると、クレデンシャルオファーの内容を設定するフォームが表示されます。
以下の指示に従ってフォームを編集してください。
inga
、inga
を入力する。potential.light.profile
を含む JSON 配列とする。フォーム編集後に Submit ボタンを押してください。 生成されたクレデンシャルオファーを含む URL を表す QR コードが表示されます。
QR コードの下にある JSON はクレデンシャルオファーの内容を表しています。
JSON 内の issuer_state
プロパティの値は発行されたイシュアステートです。
上の例では、イシュアステートの値は
tXkAkhSu5N9ORSNES9T64Bd9PAiKn9OmEOT5qDL0lkA
です。
イシュアステートは、後ほど実行する認可リクエストに含めることになります。
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
計算したコードチャレンジは次の手順で認可リクエストに含めることになります。 また、生成したコードベリファイアは認可リクエストの後に実行するトークンリクエストに含めることになります。
認可コードフロー (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 フィールドに
inga
、inga
と入力し、Authorize ボタンを押してください。
リダイレクションエンドポイント (RFC 6749, 3.1.2 参照) にリダイレクトされます。
このリダイレクションエンドポイントは、受け取ったキー・バリューの組を表示します。
表示されている code
パラメータの値が発行された認可コードです。
この例では、認可コードの値は
gR43MQf2olvhMt6KekVDkUOdQPrVYgBiKXMwu_UFnB8
です。
認可コードは次のセクションのトークンリクエストで使います。 認可コードは 10 分で期限切れとなるため、すみやかにトークンリクエスト実行しなければならないことに注意してください。
認可コードフローを用いてトークンリクエストを実行します。下記の 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
プロパティの値は鍵証明に含めなければならないノンス値です。
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'
}
アクセストークンと 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
}
前のセクションの 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
IssuerAuth
と Mobile
の定義は、(CBOR タグで始まる) Mobile
が
COSE_Sign1
のペイロードとして直接使われる印象を与えます。しかし、
Mobile
をさらに byte string
に変換する必要があります。
ですので、h'd81859
で始まる、"issuerAuth"
配列の三番目の要素は、
Mobile
を含む 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) を参照してください。
POTENTIAL はヨーロッパのデジタルアイデンティティに特化したヨーロッパの組織です。 この組織は 2024 年春から相互運用性イベントを開催しています。このイベントは 6 つのトラックに分かれています。 トラック 1 と 2 は、クレデンシャルイシュアの相互運用性をテストするためのトラックです。 トラック 1 では VC の形式として mdoc が使用され、トラック 2 では SD-JWT VC が使用されます。
トラック 2 では 2 つのプロファイルが定義されています。 1 つは Light プロファイルと呼ばれ、もう 1 つは Full プロファイルと呼ばれます。 このセクションでは Light プロファイルの手順を説明します。
パラメータ | 値 |
---|---|
発行者識別子 | 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 で入手可能です。ただしこの実装はサンプルであり、商用利用は想定していないので注意してください。
パラメータ | 値 |
---|---|
発行者識別子 | 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 |
クレデンシャルイシュアのソースコードは認可サーバのソースコードと同じです。
パラメータ | 値 |
---|---|
クライアント識別子 | track2_light |
クライアントタイプ | パブリック (= クライアント認証は要求されない) |
リダイレクト URI |
|
このクライアントにリダイレクト URI の追加登録が必要であるか、または自分専用のクライアントが必要であれば、我々にご連絡ください。
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
計算したコードチャレンジは次の手順で認可リクエストに含めることになります。 また、生成したコードベリファイアは認可リクエストの後に実行するトークンリクエストに含めることになります。
認可コードフロー (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 フィールドに
inga
、inga
と入力し、Authorize ボタンを押してください。
リダイレクションエンドポイント (RFC 6749, 3.1.2 参照) にリダイレクトされます。
このリダイレクションエンドポイントは、受け取ったキー・バリューの組を表示します。
表示されている code
パラメータの値が発行された認可コードです。
この例では、認可コードの値は
gR43MQf2olvhMt6KekVDkUOdQPrVYgBiKXMwu_UFnB8
です。
認可コードは次のセクションのトークンリクエストで使います。 認可コードは 10 分で期限切れとなるため、すみやかにトークンリクエスト実行しなければならないことに注意してください。
認可コードフローを用いてトークンリクエストを実行します。下記の 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
プロパティの値は鍵証明に含めなければならないノンス値です。
保有者の鍵 holder.jwk
と generate-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`
アクセストークンと 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
}
レスポンス内の 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"
]
}
POTENTIAL はヨーロッパのデジタルアイデンティティに特化したヨーロッパの組織です。 この組織は 2024 年春から相互運用性イベントを開催しています。このイベントは 6 つのトラックに分かれています。 トラック 1 と 2 は、クレデンシャルイシュアの相互運用性をテストするためのトラックです。 トラック 1 では VC の形式として mdoc が使用され、トラック 2 では SD-JWT VC が使用されます。
トラック 2 では 2 つのプロファイルが定義されています。 1 つは Light プロファイルと呼ばれ、もう 1 つは Full プロファイルと呼ばれます。 このセクションでは Full プロファイルの手順を説明します。
パラメータ | 値 |
---|---|
発行者識別子 | 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 で入手可能です。ただしこの実装はサンプルであり、商用利用は想定していないので注意してください。
パラメータ | 値 |
---|---|
発行者識別子 | 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 |
クレデンシャルイシュアのソースコードは認可サーバのソースコードと同じです。
パラメータ | 値 |
---|---|
クライアント識別子 | track2_full |
クライアントタイプ | コンフィデンシャル |
クライアント認証方式 | attest_jwt_client_auth |
リダイレクト URI |
|
このクライアントにリダイレクト URI の追加登録が必要であるか、または自分専用のクライアントが必要であれば、我々にご連絡ください。
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) に、生成したコードベリファイアはトークンリクエストに含めることになります。
POTENTIAL の Track 2 Full プロファイルでは OAuth 2.0 Attestation-Based Client Authentication と呼ばれる新しいクライアント認証方式を用います。この方式のため、JWT を二つ用意しなければなりません。 それらの JWT はそれぞれ、クライアントアテステーション (Client Attestation)、 クライアントアテステーション PoP (Client Attestation PoP) と呼ばれます。
これらの JWT を生成するため、oid4vci-demo レポジトリには
generate-
と
generate-
という二つのスクリプトが含まれています。使い方は次の通りです。
使い方: 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-
スクリプトは次のような 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-
スクリプトは次のような 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"
}
DPoP Proof JWT を生成するため、oid4vci-demo レポジトリ内の
generate-
スクリプトを使うことができます。
oid4vci-demo レポジトリにある DPoP デモ用の鍵 (dpop.jwk
) を使い、次のようにスクリプトを起動できます。
./generate-dpop-proof -k dpop.jwk -m POST \
-u https://trial.authlete.net/api/par
generate-
スクリプトは次のような 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
}
認可エンドポイントよりも PAR エンドポイントが優れている点の一つは、PAR エンドポイントでクライアント認証を行えることです。 このデモでは、PAR エンドポイントにアクセスする際にアテステーションによるクライアント認証を用います。
当クライアント認証方式では二つの JWT、すなわちクライアントアテステーションとクライアントアテステーション PoP、を
OAuth-
HTTP ヘッダと
OAuth-
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
パラメーターの値として用いることになります。
次の URL をあなたのウェブブラウザのアドレスバーに入力してください。
これは、認可サーバの認可エンドポイントへの認可リクエストです。
URL 内の ${REQUEST_URI}
を直前の手順で取得したリクエスト URI
の実際の値で置き換えることを忘れないでください。
https://trial.authlete.net/api/authorization?client_id=track2_full&request_uri=${REQUEST_URI}
認可エンドポイントは次のような認可ページを返します。ログイン ID とパスワードとして
inga
、inga
を入力し、続けて Authorize ボタンを押してください。
リダイレクションエンドポイント (RFC 6749, 3.1.2 参照) にリダイレクトされます。
このリダイレクションエンドポイントは、受け取ったキー・バリューの組を表示します。
表示されている code
パラメータの値が発行された認可コードです。
この例では、認可コードの値は
yn1W7SLX9OEGqFZ2D986iPowVnoQtIRpPByBEyiIrBk
です。
認可コードはのちほどトークンリクエストで使います。
トークンエンドポイントにアクセスするため、新しい 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-
スクリプトは次のような 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
}
これまでの手順で次のものを用意しました。
これらを用い、次のようにトークンリクエストを実行できます。 コマンドライン内の変数をこれまでの手順で生成した実際の値で置き換えることを忘れないでください。
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 に含める必要があります。
POTENTIAL の Track 2 Full プロファイルでは、 “OpenID4VCI PR 293: rework credential and batch credential endpoint” で導入された新しい機能を使います。この新しい機能は、一回のクレデンシャルリクエストで複数の VC を発行することを可能にします。
複数の VC を要求するには、クレデンシャルリクエストに複数の鍵証明を含める必要があります。 そのため、ここでは、異なる保有者鍵を使って二つの JWT Key Proof を作ることにします。
oid4vci-demo レポジトリには、holder.jwk
と holder2.jwk
という二つのデモ用保有者鍵が含まれています。これらの鍵と
generate-
スクリプトを用い、次のように 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"
}
クレデンシャルエンドポイントにアクセスするため、再度 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"
}
ここまでの手順で次のものが用意できました。
これらを用い、次のようにクレデンシャルリクエスト実行できます。
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-
スクリプトを用いて、これらの 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"
}
}