はじめに
選択的開示 (Selective Disclosure) を JWT を用いて実現する SD-JWT が、ついに IETF から標準仕様 RFC 9901 Selective Disclosure for JSON Web Tokens として公開されました。RFC 9449 (DPoP) に続き、仕様著者欄に私の会社 (Authlete 社) の名前を載せていただいており、大変ありがたいです。
SD-JWT は Verifiable Credential のフォーマットの一つである SD-JWT VC の基盤にもなっているため、デジタル・アイデンティティの文脈でも注目されています。
SD-JWT の意義やフォーマットに関する記事は既に幾つも存在します。
- 祝・RFC 9901! SD-JWT がインターネット標準に―― デジタルIDウォレット時代の「必要なところだけ見せる」新ルール〜「Verifiable Credentials とアイデンティティ・ウォレットを支えるコア・フォーマット SD-JWT の概要」 (by 米国 OpenID Foundation 理事長 﨑村夏彦さん)
- SD-JWT (選択的開示のためのデータフォーマット)
そのため、ここではそれらの説明を繰り返しません。かわりに、この記事では、SD-JWT の仕様を既に知っていることを前提に、実際に SD-JWT の読み書きをプログラミングでおこなう方法についてご紹介します。
プログラミングは Authlete 社がオープンソースとして公開している Java 用 SD-JWT ライブラリ (authlete/sd-jwt) を用いて行います。このライブラリは、Authlete の OpenID for Verifiable Credential Issuance 1.0 仕様の実装で用いられているほか、OpenID Foundation による公式 Conformance Suite でも利用されています。
| SD-JWT ライブラリ | 場所 |
|---|---|
| ソースコード | https://github.com/authlete/sd-jwt |
| JavaDoc | https://authlete.github.io/sd-jwt |
ライブラリ利用設定
当 SD-JWT ライブラリは Maven Central Repository に com.authlete:sd-jwt という名前で登録されています。pom.xml ファイルに次の dependency を追加することで利用できます。
<dependency>
<groupId>com.authlete</groupId>
<artifactId>sd-jwt</artifactId>
<version>${sd-jwt.version}</version>
</dependency>
${sd-jwt.version} となっている箇所は実際のバージョン番号で置き換えてください。
SD-JWT ライブラリは GSON に依存しています。
Disclosure
次の図は SD-JWT の構造を示しています。
Disclosure が重要な構成要素となっています。当 SD-JWT ライブラリでは、この Disclosure を Disclosure クラスで表します。
Disclosure は、対象がオブジェクトのプロパティであれば、ソルト・クレーム名・クレーム値の三つの要素で構成されます。一方、対象が配列の要素の場合は、ソルト・クレーム値の二つの要素で構成されます。違いは、クレーム名が含まれるかどうかです。
オブジェクトのプロパティを表す Disclosure を作る場合は、クレーム名を引数の一つとして受け取る次のコンストラクタのどちらかを用います。
Disclosure(String claimName, Object claimValue)Disclosure(String salt, String claimName, Object claimValue)
引数が二つのコンストラクタを用いた場合、ソルトはコンストラクタ内部で自動生成されます。
// Disclosure のコンストラクタに渡す三つの引数
String salt = "_26bc4LT-ac6q2KI6cBW5es";
String claimName = "family_name";
Object claimValue = "Möbius";
// Disclosure インスタンスを生成する。
Disclosure disclosure = new Disclosure(salt, claimName, claimValue);
// Disclosure のコンストラクタに渡す二つの引数
String claimName = "family_name";
Object claimValue = "Möbius";
// Disclosure インスタンスを生成する。ソルトは自動生成される。
Disclosure disclosure = new Disclosure(claimName, claimValue);
配列要素を表す Disclosure を作る場合、クレーム名に該当する引数の値を null とするか、もしくは単純に引数を一つしか取らないコンストラクタを用います。
// クレーム値 (配列要素の値)
Object claimValue = "array-element0";
// Disclosure インスタンスを生成する。
Disclosure disclosure = new Disclosure(claimValue);
Disclosure インスタンスを生成できれば、getDisclosure() メソッドまたは toString() メソッドによりシリアラズされた表現を求めることができます。
// シリアライズする。serialized1 と serialized2 は同じ値となる。
String serialized1 = disclosure.getDisclosure();
String serialized2 = disclosure.toString();
parse(String) メソッドを用いると、シリアライズされた表現から Disclosure インスタンスを生成できます。
Disclosure disclosure = Disclosure.parse(
"WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0");
Disclosure Digest
SD-JWT の Issuer-signed-JWT のペイロード部には、Disclosure そのものではなく、Disclosure のダイジェスト値が入ります。ダイジェスト値は次のメソッドのどちらかを用いて求めることができます。
digest()digest(String hashAlgorithm)
引数を取らない digest() メソッドは、デフォルトのハッシュアルゴリズムである sha-256 を用いてダイジェスト値を計算します。一方、String インスタンスを引数として取る digest(String) メソッドは、指定されたハッシュアルゴリズムを用いてダイジェスト値を計算します。
// デフォルトのハッシュアルゴリズム (sha-256) を用いてダイジェスト値を求める。
String digestSha256 = disclosure.digest();
// ハッシュアルゴリズム sha-512 を用いてダイジェスト値を求める。
String digestSha512 = disclosure.digest("sha-512");
Selective Disclosure Object
SDObjectBuilder クラスは、_sd 配列を含む JSON オブジェクトを表す Map インスタンスを生成するためのユーティリティクラスです。
SDObjectBuilder クラスの典型的な利用手順は次の通りです。
-
SDObjectBuilderインスタンスを生成する - 必要に応じて通常のクレームを追加する
- 必要に応じて Disclosure のダイジェスト値を追加する
-
build()メソッドをコールしてMapインスタンスを生成する
// SDObjectBuilder インスタンスを生成する。
SDObjectBuilder builder = new SDObjectBuilder();
// 通常のクレームを追加する。
builder.putClaim("my_claim_name", "my_claim_value");
// Disclosure のダイジェスト値を追加する。
Disclosure disclosure = new Disclosure(
"_26bc4LT-ac6q2KI6cBW5es", "family_name", "Möbius");
builder.putSDClaim(disclosure);
// Map インスタンスを生成する。
Map<String, Object> map = builder.build();
上記のコードで得られる Map インスタンスは、次の JSON と同じ内容を持ちます。
{
"my_claim_name": "my_claim_value",
"_sd": [
"TZjouOTrBKEwUNjNDs9yeMzBoQn8FFLPaJjRRmAtwrM"
]
}
SDObjectBuilder が Disclosure のダイジェスト値を求める際に用いるハッシュアルゴリズムにデフォルト (sha-256) 以外のものを指定したい場合は、アルゴリズム名を引数に取るコンストラクタを用います。
SDObjectBuilder builder = new SDObjectBuilder("sha-512");
Map インスタンスを生成する際、build() メソッドにかえて build(true) メソッドを呼ぶと、ハッシュアルゴリズム名が _sd_alg クレームの値として Map に埋め込まれます。
Map<String, Object> map = builder.build(true);
下記は、build(true) で生成された Map インスタンスが保持するデータの例を JSON で表現したものです。
{
"_sd": [
"j35wlGQlyQ8b4OE3Py6l3AAvOskjcNOxj0SsiVSrVdmVs8bapSUelViRDbmlntFABkp6_zSz1fA-dlWGUxGpEA"
],
"_sd_alg": "sha-512"
}
Decoy Digest
元々のクレームの数を攻撃者が簡単に推測できないようにするため、何のクレームにも対応しない無意味な Disclosure ダイジェスト値を含めることがあります。このようなダイジェスト値は Decoy Digest (RFC 9901 Section 4.2.5. Decoy Digests) と呼ばれます。
putDecoyDigest() メソッドや putDecoyDigests(int) メソッドで Decoy Digest を追加することができます。
// Decoy Digest を一つ追加する。
builder.putDecoyDigest();
// 指定された数の Decoy Digest を追加する。
builder.putDecoyDigests(3);
Issuer-signed JWT
SD-JWT を表す文字列を ~ で分割したとき、先頭の要素は JWT になっています。RFC 9901 では、この JWT を Issuer-signed JWT と呼んでいます。なお、仕様がまだ流動的だった頃に実装された authlete/sd-jwt ライブラリでは、その JWT を Credential JWT と呼んでいます。
この JWT のペイロード部を生成するためのユーティリティクラスとして、authlete/sd-jwt ライブラリは SDOBjectBuilder を提供しています。しかし、ライブラリは JWT を生成するためのユーティリティクラス群を提供していません。
これは意図した設計です。理由は、開発者が任意の JWT ライブラリを使用できるようにするためです。個人的には Nimbus JOSE + JWT ライブラリ一択ですが、authlete/sd-jwt ライブラリ自体は Nimbus JOSE + JWT ライブラリに依存しないようにしています。
下記は Issuer-signed JWT を作成するプログラムの例です。ペイロード部の生成に authlete/sd-jwt ライブラリの SDObjectBuilder クラスを、それ以外の処理に Nimbus JOSE + JWT ライブラリのクラス群を用いています。
Issuer-signed JWT を生成するプログラム
import java.text.*;
import java.util.*;
import com.authlete.sd.*;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.*;
import com.nimbusds.jwt.*;
public class SDJWTExample
{
public static void main(String[] args) throws Exception
{
// 選択的開示可能 (selectively-disclosable) なクレーム群を
// Disclosure のリストとして用意する。
List<Disclosure> disclosures = buildDisclosures();
// 通常のクレーム群を用意する。
Map<String, Object> regularClaims = buildRegularClaims();
// Issuer-signed JWT に署名するための秘密鍵を作成する。
ECKey privateKey = generatePrivateKey();
// Issuer-signed JWT を作成する。
String issuerSignedJwt =
buildIssuerSignedJwt(disclosures, regularClaims, privateKey);
// Issuer-signed JWT を出力する。
System.out.println(issuerSignedJwt);
}
private static List<Disclosure> buildDisclosures()
{
// Disclosure のリスト
List<Disclosure> disclosures = new ArrayList<>();
// クレーム "family_name":"Kawasaki" を表す Disclosure を追加する。
disclosures.add(new Disclosure("family_name", "Kawasaki"));
// クレーム "given_name":"Takahiko" を表す Disclosure を追加する。
disclosures.add(new Disclosure("given_name", "Takahiko"));
return disclosures;
}
private static Map<String, Object> buildRegularClaims()
{
// 「クレーム名 → クレーム値」のペア
Map<String, Object> claims = new LinkedHashMap<>();
// クレーム "nickname":"Taka" を追加する。
claims.put("nickname", "Taka");
// SD-JWT VC 仕様に沿うように、vct クレームを追加する。
claims.put("vct", "https://credentials.example.com/identity_credential");
return claims;
}
private static ECKey generatePrivateKey() throws JOSEException
{
// アルゴリズム ES256 用の秘密鍵を生成する。
return new ECKeyGenerator(Curve.P_256).generate();
}
private static String buildIssuerSignedJwt(
List<Disclosure> disclosures, Map<String, Object> regularClaims, ECKey privateKey)
throws JOSEException, ParseException
{
// Issuer-signed JWT のペイロード部分を作成する。
Map<String, Object> claims =
buildPayload(disclosures, regularClaims);
// Issuer-signed JWT (未署名) を作成する。
SignedJWT jwt = buildJwt(claims);
// Issuer-signed JWT に署名する。
signJwt(jwt, privateKey);
// Issuer-signed JWT をシリアライズする。
String serialized = jwt.serialize();
return serialized;
}
private static Map<String, Object> buildPayload(
List<Disclosure> disclosures, Map<String, Object> regularClaims)
{
// Issuer-signed JWT のペイロードを組み立てるための
// SDObjectBuilder インスタンスを作成する。ここでは
// デフォルトコンストラクタを使っているので、ハッシュ
// アルゴリズムはデフォルトの sha-256 になる。
SDObjectBuilder builder = new SDObjectBuilder();
// 選択的開示可能なクレーム群を追加する。
disclosures.forEach(builder::putSDClaim);
// 通常クレーム群を追加する。
regularClaims.forEach((k, v) -> builder.putClaim(k, v));
// ペイロード部を表す Map インスタンスを作成する。ここで
// 作成される Map インスタンスには _sd 配列が含まれる。
Map<String, Object> payload = builder.build();
return payload;
}
private static SignedJWT buildJwt(Map<String, Object> payload)
throws JOSEException, ParseException
{
// Issuer-signed JWT のヘッダ部分を作成する。その内容は
// {"alg":"ES256","typ":"dc+sd-jwt"} とする。ここで
// 用いる typ クレームの値は SD-JWT VC 仕様で定義されている。
JWSHeader header =
new JWSHeader.Builder(JWSAlgorithm.ES256)
.type(new JOSEObjectType("dc+sd-jwt")).build();
// Map で表現されているペイロードを、Nimbus JOSE + JWT
// ライブラリの JWTClaimsSet に変換する。
JWTClaimsSet claimsSet = JWTClaimsSet.parse(payload);
// Issuer-signed JWT (未署名) を生成する。
SignedJWT jwt = new SignedJWT(header, claimsSet);
return jwt;
}
private static void signJwt(SignedJWT jwt, ECKey privateKey) throws JOSEException
{
// 署名をおこなう signer を作成する。
JWSSigner signer = new ECDSASigner(privateKey);
// 署名する。
jwt.sign(signer);
}
}
このプログラムは、次のような Issuer-signed JWT を出力します。
eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJuaWNrbmFtZSI6IlRha2EiLCJfc2QiOlsiVmZ5X1MxeEVLQTc4c3Fsa19MMWlHLTZ6NnVkcFpwZGl2di1BMFBaNjNMVSIsImZsRWFDVnVEUWxidV9feDV4Vndpb25iVTIzdUtFZ1pZdW9NZG90Nnp4Y3MiXSwidmN0IjoiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlLmNvbS9pZGVudGl0eV9jcmVkZW50aWFsIn0.mYX7hh1jOzQLS_NlUEVgTaPG0sijFSYXGLqANDPQFJD3DAyS-NZSNVmHn_iQWu3Pa9Ev6DNnwOyAPTe-Eg4uEg
この Issuer-signed JWT のヘッダとペイロードを base64url でデコードすると、次の JSON が得られます。
{
"typ": "dc+sd-jwt",
"alg": "ES256"
}
{
"nickname": "Taka",
"_sd": [
"Vfy_S1xEKA78sqlk_L1iG-6z6udpZpdivv-A0PZ63LU",
"flEaCVuDQlbu__x5xVwionbU23uKEgZYuoMdot6zxcs"
],
"vct": "https://credentials.example.com/identity_credential"
}
ペイロード部には、nickname クレームと vct クレームの名前と値がそのまま入っていますが、family_name クレームと given_name クレームは入っていません。代わりに、family_name クレームと given_name クレームに対応するダイジェスト値が _sd 配列に入っています。
ダイジェスト値から元の値を求めることはできないため、この Issuer-signed JWT からだけでは "family_name":"Kawasaki"、"given_name":"Takahiko" という情報は得られません。
SD-JWT
SD-JWT は
- Issuer-signed JWT
- 0 個以上の Disclosure
- 任意の Key Binding JWT
で構成されます。
Issuer-signed JWT~Disclosure 1~...~Disclosure N~[Key Binding JWT] |
|---|
SD-JWT ライブラリの SDJWT クラスは SD-JWT を表しており、上記の構成に対応する二つのコンストラクタを提供しています。
SDJWT(String issuerSignedJwt, Collection<Disclosure> disclosures)SDJWT(String issuerSignedJwt, Collection<Disclosure> disclosures, String keyBindingJwt)
SDJWT クラスのコンストラクタの第一引数はシリアライズされた Issuer-signed JWT、第二引数は Disclosure のリストです。
Issuer-signed JWT を表す引数の型は String であり、Nimbus JOSE + JWT ライブラリの SignedJWT ではありません。敢えて型を String にすることで、特定の JWT ライブラリに依存しないようにしています。
先ほど挙げたサンプルプログラムを下記のように変更することで、SD-JWT を生成して、それをシリアライズしたものを出力することができます。
public static void main(String[] args) throws Exception
{
// 選択的開示可能な (selectively-disclosable) なクレーム群を
// Disclosure のリストとして用意する。
List<Disclosure> disclosures = buildDisclosures();
// 通常のクレーム群を用意する。
Map<String, Object> regularClaims = buildRegularClaims();
// Issuer-signed JWT に署名するための秘密鍵を作成する。
ECKey privateKey = generatePrivateKey();
// Issuer-signed JWT を作成する。
String issuerSignedJwt =
buildIssuerSignedJwt(disclosures, regularClaims, privateKey);
- // Issuer-signed JWT を出力する。
- System.out.println(issuerSignedJwt);
+ // SD-JWT を作成する。
+ SDJWT sdjwt = new SDJWT(issuerSignedJwt, disclosures);
+
+ // SD-JWT をシリアライズして出力する。
+ System.out.println(sdjwt);
}
変更後のプログラムを実行すると、次のような SD-JWT が出力されます。
eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJuaWNrbmFtZSI6IlRha2EiLCJfc2QiOlsiQmxTRkNtLVJyLVc0UUZSV1pyb09keVNwV3BmUnVuTExGZDh4djB4VXJaVSIsIkk5Z2JrWGxNVEhHTThzTHNjenpVMDNlVEl2U1R2OWlnTlE5alpKLS1XYlkiXSwidmN0IjoiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlLmNvbS9pZGVudGl0eV9jcmVkZW50aWFsIn0.G365hmMY_Wp9vSVmifxKNXdVgxcXGuY_wevarIK95sgKFs6U8Me1a7WinSUVlQrWHDjT0AC9nM0GbYYV4hD6DQ~WyI5UFgwUXdUZXo3QlhOcjlkWVhpRVlnIiwiZmFtaWx5X25hbWUiLCJLYXdhc2FraSJd~WyJVTjByajNRTkJTeWJmSnNHY1NEYktBIiwiZ2l2ZW5fbmFtZSIsIlRha2FoaWtvIl0~
SDJWT クラスの parse(String) メソッドを用いると、シリアライズされた表現から SDJWT インスタンスを作成できます。
SDJWT sdjwt = SDJWT.parse(
"""\
eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJua\
WNrbmFtZSI6IlRha2EiLCJfc2QiOlsiQmxTRkNtLVJyLVc0UUZ\
SV1pyb09keVNwV3BmUnVuTExGZDh4djB4VXJaVSIsIkk5Z2JrW\
GxNVEhHTThzTHNjenpVMDNlVEl2U1R2OWlnTlE5alpKLS1XYlk\
iXSwidmN0IjoiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlL\
mNvbS9pZGVudGl0eV9jcmVkZW50aWFsIn0.G365hmMY_Wp9vSV\
mifxKNXdVgxcXGuY_wevarIK95sgKFs6U8Me1a7WinSUVlQrWH\
DjT0AC9nM0GbYYV4hD6DQ~WyI5UFgwUXdUZXo3QlhOcjlkWVhp\
RVlnIiwiZmFtaWx5X25hbWUiLCJLYXdhc2FraSJd~WyJVTjBya\
jNRTkJTeWJmSnNHY1NEYktBIiwiZ2l2ZW5fbmFtZSIsIlRha2F\
oaWtvIl0~\
"""
);
SDObjectDecoder
SD-JWT に含まれる各 Disclosure のダイジェスト値を計算し、Issuer-signed JWT のペイロード内のダイジェスト値と突き合わせることで、元々のクレーム名・クレーム値を得ることができます。
元々のクレーム値が JSON オブジェクトで、その中に _sd 配列が入っている場合もあるので、ダイジェスト値の突き合わせ処理は再帰処理になることがあります。配列要素が "..." というキーを一つだけ持つ JSON オブジェクトの場合、それも処理対象になります。
_sd_alg クレームのチェック等、諸々を併せて考えると、この突き合わせ処理は難しくないものの、いちいち実装を書くのは面倒です。そのため、SD-JWT ライブラリは、この処理を実行する SDObjectDecoder というユーティリティクラスを提供しています。
SDObjectDecoder の decode メソッドのいずれかを用いることで、突き合わせ処理を実行することができます (SDObjectDecoder の JavaDoc)。
次のサンプルプログラムは、第一引数で指定された SD-JWT の Issuer-signed JWT のペイロード部をデコードして JSON 形式で表示します。
SD-JWT の Issuer-signed JWT のペイロードをデコードするプログラム
import java.util.*;
import com.authlete.sd.*;
import com.google.gson.*;
import com.nimbusds.jwt.*;
public class DecodeExample
{
public static void main(String[] args) throws Exception
{
// コマンドラインで与えられた第一引数の値を SD-JWT としてパースする。
SDJWT sdjwt = SDJWT.parse(args[0]);
// String で表現されている Issuer-signed JWT を、Nimbus JOSE + JWT
// ライブラリの SignedJWT クラスのインスタンスに変換する。
SignedJWT jwt = SignedJWT.parse(sdjwt.getCredentialJwt());
// Issuer-signed JWT のペイロードを Map クラスのインスタンスに変換する。
// 大抵の場合、この Map インスタンスには _sd 配列が入っている。
Map<String, Object> encodedPayload =
jwt.getJWTClaimsSet().toJSONObject();
// SD-JWT に含まれる Disclosure 群を取り出す。
List<Disclosure> disclosures = sdjwt.getDisclosures();
// ペイロード内のダイジェスト値を対応するクレームに置き換える処理を
// おこなう SDObjectDecoder を作成する。
SDObjectDecoder decoder = new SDObjectDecoder();
// 各 Disclosure のダイジェスト値を計算し、ペイロード内のダイジェスト値と
// 突き合わせをおこない、ダイジェスト値を元々のクレームで置き換える。
Map<String, Object> decodedPayload =
decoder.decode(encodedPayload, disclosures);
// Map インスタンスの内容を JSON に変換する。
String decodedPayloadJson =
new GsonBuilder().setPrettyPrinting().create().toJson(decodedPayload);
// JSON を出力する。
System.out.println(decodedPayloadJson);
}
}
SDJWT というシェル変数に SD-JWT が設定されているとした場合、
SDJWT=eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJuaWNrbmFtZSI6IlRha2EiLCJfc2QiOlsiQmxTRkNtLVJyLVc0UUZSV1pyb09keVNwV3BmUnVuTExGZDh4djB4VXJaVSIsIkk5Z2JrWGxNVEhHTThzTHNjenpVMDNlVEl2U1R2OWlnTlE5alpKLS1XYlkiXSwidmN0IjoiaHR0cHM6Ly9jcmVkZW50aWFscy5leGFtcGxlLmNvbS9pZGVudGl0eV9jcmVkZW50aWFsIn0.G365hmMY_Wp9vSVmifxKNXdVgxcXGuY_wevarIK95sgKFs6U8Me1a7WinSUVlQrWHDjT0AC9nM0GbYYV4hD6DQ~WyI5UFgwUXdUZXo3QlhOcjlkWVhpRVlnIiwiZmFtaWx5X25hbWUiLCJLYXdhc2FraSJd~WyJVTjByajNRTkJTeWJmSnNHY1NEYktBIiwiZ2l2ZW5fbmFtZSIsIlRha2FoaWtvIl0~
第一引数に $SDJWT を指定してサンプルプログラムを起動すると、
java -cp nimbus-jose-jwt.jar:sd-jwt.jar:gson.jar DecodeExample.java $SDJWT
次のような JSON が出力されます。
{
"family_name": "Kawasaki",
"given_name": "Takahiko",
"vct": "https://credentials.example.com/identity_credential",
"nickname": "Taka"
}
SD-JWT VC/VP の検証
Issuer-signed JWT のペイロードを Disclosure と突き合わせてデコードするのは、SDObjectDecoder を使えば簡単にできます。しかし、その内容を信頼するためには Issuer-signed JWT の署名を検証する必要があります。また、SD-JWT 形式の Verifiable Presentation が Key Binding JWT (RFC 9901 Section 4.3. Key Binding JWT) を含んでいる場合、その Key Binding JWT の署名の検証や sd_hash クレームのチェックも必要です。
SD-JWT ライブラリのテストに含まれる VerificationTest.java は、SD-JWT 形式の Verifiable Credential / Verifiable Presentation の生成処理と署名検証処理のサンプルも兼ねているので、必要に応じてご参照ください。
その他のトピック
Map インスタンスの内容を再帰的に選択的開示可能にするための SDObjectEncoder クラスや、選択的開示可能な配列要素を表す Map インスタンスを生成するための Disclosure.toArrayElement() メソッドなど、この記事では取り上げていない機能もあります。必要に応じて SD-JWT ライブラリのドキュメント (README / JavaDoc) やソースコードをご参照ください。
おわりに
Authlete 社は多くの標準仕様策定作業に貢献しています。SD-JWT 仕様 (RFC 9901) はその一例に過ぎません。業界最先端の標準仕様に関わる仕事にご興味のある方、是非 Authlete 社の採用情報をチェックしてください!

