💻

Selective Disclosure JWTを軽く実装してみた

2022/08/28に公開

はじめに

Selective Disclosure JWTという,アクセストークンなどで用いられるJWTに含まれる属性情報を選択的に開示する仕様のドラフトに関する記事を書きましたので,ついでに仕様を読みつつGo言語で実装してみました.

Goによる実装と動作

実装したものはここに置いています.

IssuerによるSD-JWTとSVCの生成

IssuerではClaimをハッシュ化したSD-JWTと,ハッシュ生成につかったソルトと生値を保持するSVCをシリアライズしたissuerTokenを生成します.

// Issuerの生成
issuer := NewSdJwtIssuer(issuerName, privateIssuerKey)

// SD-JWTに含む属性情報をセットしてトークン生成
claims := map[string]string{
	"name":    "John Doe",
	"country": "Japan",
}
issuerToken, err := issuer.CreateIssuerToken(nonce, claims, SHA256, &holderPrivateKey.PublicKey)

生成されたJWTをJWT.ioでデコードしてみると次のようなハッシュ化されたClaimを取得できます.

  "sd_digests": {
    "country": "Nv89yAmhI2Zz_9QYIPUrg6xoqIKFcgW3xu3INp-YpNk",
    "name": "wqGJgiMRhg5z7zvo8U0CJXtyHd_eShV8SoCKl1a5kJE"
  },

また,SD-JWTと一緒に送られるSVCには各ソルト値と生値が入っています.

{
  "sd_release": {
    "country": [
      "Tld_irzPdIyVDIiosLNJHA",
      "Japan"
    ],
    "name": [
      "0pia1me2zBzLIoAkSlf4zw",
      "John Doe"
    ]
  }
}

Holderによる検証とSD-JWT-Releaseの生成

HolderではSD-JWTの検証とSD-JWT-Releaseの生成を行います.

// Holderの生成
holder := NewDfJwtHolder(holderPrivateKey)
// Issuerから発行されたトークンの検証
ok, err := holder.VerifyIssuerToken(issuerToken)

// Holderが開示する情報を選択してSD-JWT-Releaseを含むトークンを生成
releaseClaimKeys := []string{"name"}
holderToken, err := holder.CreateHolderToken(issuerToken, releaseClaimKeys, nonce, audience)

Holderでは,SVCに含まれるClaimのキー名がSD-JWTに含まれ,かつ計算されるハッシュ値がSD-JWTのsd_digestsと一致するか検証します.

func ismatchDigests(sdJwtDigests map[string]string, releaseDigests map[string][2]string, hashType HashType) (bool, error) {
	// check all reLease claim's keys exist in SD-JWT sd_digests and match release claim's hashed values.
	for key, values := range releaseDigests {
		digest, ok := sdJwtDigests[key]
		if !ok {
			return false, fmt.Errorf("digests of SD-JWT and SVC are unmatch: %s", key)
		}
		salt, err := base64.RawURLEncoding.DecodeString(values[0])
		if err != nil {
			return false, fmt.Errorf("internal server error")
		}
		hashedValue := createHashedValue(string(salt), values[1], hashType)
		if digest != hashedValue {
			return false, fmt.Errorf("digests of SD-JWT and SVC are unmatch: %s", key)
		}
	}
	return true, nil
}

例えば名前属性だけを開示するためにHolderが生成するSD-JWT-Releaseの中身は下のようになります.

{
  "aud": [
    "https://example.com/verifier"
  ],
  "nonce": "XZOUco1u_gEPknxS78sWWg",
  "sd_release": {
    "name": [
      "EpJAiBKcfY1FTp1nTlmckQ",
      "John Doe"
    ]
  }
}

VerifierではSD-JWTとSD-JWT-Releaseを使って署名の検証やClaim値の検証(Holderのトークン検証と同様)などを行いますが,今回の実装ではClaimのハッシュが一致するかしか検証してません.

実装してみての感想

仕様自体が簡単な技術の組み合わせで構成されているので特段難しいことを書かずにJWT生成や検証ができました.ただ,HolderやVerifierでSD-JWTやVSC,SD-JWT-Releaseを検証しないといけないと考えると従来のアクセストークンの取り扱いに比べてちょっと面倒ごとが増えるので,そのあたり製品で使うならライブラリ実装が欲しくなりそう.

Discussion