はじめに
Token Status List (TSL) という標準仕様が、そろそろ IETF の RFC として承認されそうです。
この仕様は、JOSE や COSE をベースとするトークン群 (JWT、SD-JWT VC、CWT、ISO mdoc など) のステータス情報 (有効、無効など) を取り扱うためのものです。
大量のトークンのステータスをまとめて表現できるようにするため、ステータス群はビット配列で表現され、DEFLATE で圧縮されます。
このステータスのリストを表現するためのデータ構造として、TSL 仕様は StatusList を定義しています。この記事では、この StatusList について見ていきます。
StatusListの構造
フォーマットが JSON か CBOR かに関わらず、StatusList は次のプロパティを持っています。
| プロパティ | 要否 | 説明 |
|---|---|---|
bits |
必須 | 一つのステータスを表現するのに用いるビットの数。1、2、4 または 8。 |
lst |
必須 | バイト配列で表現されたステータス群を DEFLATE 圧縮したもの。CBOR ではバイト配列、JSON では base64url 文字列で表現される。 |
aggregation_uri |
任意 | ステータスリストトークン群を公開する URI 群のリストを得られる場所。 |
下記は仕様書から抜粋した JSON 形式の StatusList の例です。
{
"bits": 1,
"lst": "eNrbuRgAAhcBXQ"
}
{
"bits": 2,
"lst": "eNo76fITAAPfAgc"
}
bits プロパティは、一つのステータスを表現するのに用いるビット数を表しており、取りうる値は 1、2、4、または 8 です。ビット数を 1 とする場合、$2^1 = 2$ 種類のステータスを表現できます。例えば、有効・無効の二種類のステータスを表現できます。ビット数を 2 とする場合は $2^2 = 4$ 種類、4 なら $2^4 = 16$ 種類、8 なら $2^8 = 256$ 種類のステータスを表現できます。
lst プロパティは、ステータス群を列挙したバイト配列を DEFLATE 圧縮したものです。CBOR フォーマットでは DEFLATE の結果をそのままバイト配列として持ち、一方で JSON フォーマットでは base64url エンコードして文字列として持ちます。
例えば、ステータスを表現するのに用いるビット数を 1 とし、16 個のトークンのステータスが次の通りだとします。
| インデックス | ステータス | 値 |
|---|---|---|
| 0 | 無効 | 1 |
| 1 | 有効 | 0 |
| 2 | 有効 | 0 |
| 3 | 無効 | 1 |
| 4 | 無効 | 1 |
| 5 | 無効 | 1 |
| 6 | 有効 | 0 |
| 7 | 無効 | 1 |
| 8 | 無効 | 1 |
| 9 | 無効 | 1 |
| 10 | 有効 | 0 |
| 11 | 有効 | 0 |
| 12 | 有効 | 0 |
| 13 | 無効 | 1 |
| 14 | 有効 | 0 |
| 15 | 無効 | 1 |
これらを次の図の要領でパックすると、[0xB9, 0xA3] というバイト配列が得られます。
このバイト配列を DEFLATE で圧縮したものが lst にセットされます。
同様にして、ステータスを表現するのに用いるビット数を 2 とし、12 個のトークンのステータスが次の通りだとします。
| インデックス | ステータス |
|---|---|
| 0 | 01 |
| 1 | 10 |
| 2 | 00 |
| 3 | 11 |
| 4 | 00 |
| 5 | 01 |
| 6 | 00 |
| 7 | 01 |
| 8 | 01 |
| 9 | 10 |
| 10 | 11 |
| 11 | 11 |
このステータス群を次の図の要領でパックすると、[0xC9, 0x44, 0xF9] というバイト配列が得られます。
このバイト配列を DEFLATE で圧縮したものが lst にセットされます。
StatusListの実装
Authlete 社がオープンソースで公開している CBOR ライブラリ authlete/cbor (for Java) は、バージョン 1.21 以降、StatusList を表すクラスが StatusList という名前で com.authlete.cbor.tsl パッケージ内に入っています。
この StatusList クラスのコンストラクタは、int bits、byte[] lst、String aggregationUri という引数を取ります。
public StatusList(int bits, byte[] lst, String aggregationUri)
しかしながら、そもそも lst の値を用意するのが大変なので、このコンストラクタを呼び出すまでの道のりは遠いです。そこで、StatusList クラスのインスタンスを生成するためのユーティリティクラス StatusListBuilder を用意してあります。
StatusListBuilder の使用手順は次のとおりです。
- ステータスの表現に使うビット数を 1、2、4、8 の中から決める
- ビット数をコンストラクタに渡して
StatusListBuilderインスタンスを作成する - 必要な分だけ
valueAtメソッドを呼び、ステータスを設定する -
aggregation_uriを設定したければaggregationUriメソッドを呼ぶ -
valueAtメソッドに指定したインデックスにより自動拡張されたステータス数を上回る領域を確保したければ、capacityメソッドを呼ぶ - 最後に
buildメソッドを呼んでStatusListインスタンスを作成する
例えば、TSL 仕様の 4.1. Compressed Byte Array に挙げられている下記のステータス群を持つ StatusList を作成するには、
status[0] = 0b1
status[1] = 0b0
status[2] = 0b0
status[3] = 0b1
status[4] = 0b1
status[5] = 0b1
status[6] = 0b0
status[7] = 0b1
status[8] = 0b1
status[9] = 0b1
status[10] = 0b0
status[11] = 0b0
status[12] = 0b0
status[13] = 0b1
status[14] = 0b0
status[15] = 0b1
次のように書きます。
// ステータスを表現するのに使うビット数
int bits = 1;
// ビット数をコンストラクタに渡して StatusListBuilder のインスタンスを作成する
StatusListBuilder builder = new StatusListBuilder(bits);
// ステータスを設定する
builder.valueAt(StatusTypeValue.INVALID, 0);
builder.valueAt(StatusTypeValue.VALID, 1);
builder.valueAt(StatusTypeValue.VALID, 2);
builder.valueAt(StatusTypeValue.INVALID, 3);
builder.valueAt(StatusTypeValue.INVALID, 4);
builder.valueAt(StatusTypeValue.INVALID, 5);
builder.valueAt(StatusTypeValue.VALID, 6);
builder.valueAt(StatusTypeValue.INVALID, 7);
builder.valueAt(StatusTypeValue.INVALID, 8);
builder.valueAt(StatusTypeValue.INVALID, 9);
builder.valueAt(StatusTypeValue.VALID, 10);
builder.valueAt(StatusTypeValue.VALID, 11);
builder.valueAt(StatusTypeValue.VALID, 12);
builder.valueAt(StatusTypeValue.INVALID, 13);
builder.valueAt(StatusTypeValue.VALID, 14);
builder.valueAt(StatusTypeValue.INVALID, 15);
// StatusList のインスタンスを作成する
StatusList statusList = builder.build();
StatusList は CBORItem のサブクラスなので、encodeToHex() メソッドを用いて 16 進数で表現したり、
System.out.println(statusList.encodeToHex());
a2646269747301636c73744a78dadbb918000217015d
toString() メソッドや prettify() メソッドを用いて Diagnostic Notation で表現したりできます。
System.out.println(statusList.prettify());
{
"bits": 1,
"lst": h'78dadbb918000217015d'
}
Diagnostic Notation については、RFC 8949 Section 8、RFC 8610 Appendix G をご参照ください。
ちなみに、『GSON:バイト配列とbase64url文字列の双方向変換』で紹介している Base64UrlAdapter を用いてバイト配列を base64url 文字列に変換すれば (他の方法でも良いのですがとにかく lst の値をバイト配列から base64url 文字列に変換すれば)、
// Base64UrlAdapter を有効にして Gson を作成する
Gson gson = new GsonBuilder()
.registerTypeAdapter(byte[].class, new Base64UrlAdapter())
.setPrettyPrinting()
.create();
// parse() メソッドで CBORItem を一般的な Java クラスのオブジェクトに変換
Object statusListObject = statusList.parse();
// JSON に変換する
String json = gson.toJson(statusListObject);
// JSON を出力する
System.out.println(json);
TSL 仕様の 4.2. Status List in JSON Format の規定に従う JSON を生成できます。
{
"bits": 1,
"lst": "eNrbuRgAAhcBXQ"
}
おわりに
Java で CBOR を扱う必要が出てきたときは、是非 authlete/cbor ライブラリを使ってみてください。Authlete 社自身も OpenID for Verifiable Credential Issuance 1.0 (OID4VCI) の実装で authlete/cbor ライブラリを使っています。
また、CBOR Zone というウェブサイトも運営しています。CBOR データを下記のフォーマット間で相互変換することができます。便利なので使ってみてください!
- 16 進数
- Base64
- Base64URL
- Diagnostic Notation
(このスクリーンショットは、StatusList の 16 進数表現から Diagnostic Notation への変換を示しています)


