🦁

Selective Disclosure JWTが面白そうだったのでまとめた

2022/08/26に公開
6

※この記事はドラフト版の仕様に基づいて書かれています.

Selective Disclosure JWTとは

OAuthやOpenID Connect(OIDC)のアクセストークンなどでよく利用されるJSON Web Token(JWT)に関する仕様です.JWTに含まれる情報(Claim)のうち,必要なものだけを選択的に開示する方法について記述されています.

問題設定


登場人物はIssuer,Holder,Verifierの3者です(Verifierは複数).HolderはIssuerからJWTを発行してもらい.JWTをHolderからVerifierに提示するという状況です.
本ドラフトの問題設定では,Holderは一部の情報だけを各Verifierに共有し,Verifierにとって不要な情報は開示したくないという状況を想定しています.
Self-containedなJWTの場合,含まれる全てのClaimを読めてしまいます.JWTをセキュアに利用する方法としてJSON Web Encryption(JWE)があります.これはJWTを暗号化するものですが,暗号鍵を共有しているVerifierは復号すれば全てのClaimを読めてしまうので,やはりこの問題設定には不適です.

Selective Disclosure JWTによる選択的情報開示

簡単に説明すると,SD-JWTでは,Claimの情報がハッシュ化されたJWTと,ハッシュ前の情報をIssuerからHolderに渡します.Holderは情報がハッシュ化されたJWTと,開示したい情報のみをVerifierに提示して情報の開示範囲を制御します.
SD-JWTでやりとりされる情報は次のとおりです.

項目 説明
Selectively Disclosable JWT (SD-JWT) 選択的開示のために用いられる署名付きJWT.Issuerが生成する.
SD-JWT Salt/Value Container (SVC) SD-JWTに含まれるClaimの生値とClaimごとのソルトが記述されたJSONオブジェクト
SD-JWT Release (SD-JWT-R) Verifierに対して開示したい情報でを含むJWT.Holderが生成する

IssuerによるSD-JSONの生成

まず,今回取り扱う情報を従来のJWT形式で表したものを以下に示します.
Holderの名前,メールアドレス,電話番号,住所,誕生日といった個人情報が含まれています.

{
  "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
  "given_name": "John",
  "family_name": "Doe",
  "email": "johndoe@example.com",
  "phone_number": "+1-202-555-0101",
  "address": {
    "street_address": "123 Main St",
    "locality": "Anytown",
    "region": "Anystate",
    "country": "US"
  },
  "birthdate": "1940-01-01"
}

この情報をSD-JWT形式で生成すると次のようになります.通常のJWTと異なり,Holderの情報はハッシュ化された上で全てsd_digestsに含まれます.また,ハッシュアルゴリズムがsd_hash_algで示されています.

{
  "iss": "https://example.com/issuer",
  "cnf": {
    "jwk" : {
        "kty": "RSA",
        "n": "pm4bOHBg-oYhAyPWzR56AWX3rUIXp11_ICDkGgS6W3ZWLts-hzwI3x65659kg4hVo9dbGoCJE3ZGF_eaetE30UhBUEgpGwrDrQiJ9zqprmcFfr3qvvkGjtth8Zgl1eM2bJcOwE7PCBHWTKWYs152R7g6Jg2OVph-a8rq-q79MhKG5QoW_mTz10QT_6H4c7PjWG1fjh8hpWNnbP_pv6d1zSwZfc5fl6yVRL0DV0V3lGHKe2Wqf_eNGjBrBLVklDTk8-stX_MWLcR-EGmXAOv0UBWitS_dXJKJu-vXJyw14nHSGuxTIK2hx1pttMft9CsvqimXKeDTU14qQL1eE7ihcw",
        "e": "AQAB"
    }
  },
  "iat": 1516239022,
  "exp": 1516247022,
  "sd_hash_alg": "sha-256",
  "sd_digests": {
    "sub": "z4xgEco94diTaSruISPiE7o_wtmcOfnH_8R7X9Pa578",
    "given_name": "PvU7cWjuHUq6w-i9XFpQZhjT-uprQL3GH3mKsAJl0e0",
    "family_name": "H-Relr4cEBMlenyK1gvyx16QVpnt4MEclT5tP0aTLFU",
    "email": "ET2A1JQLF85ZpBulh6UFstGrSfR4B3KM-bjQVllhxqY",
    "phone_number": "SJnciB2DIRVA5cXBrdKoH6n45788mZyUn2rnv74uMVU",
    "address": "0FldqLfGnERPPVDC17od9xb4w3iRJTEQbW_Yk9AmnDw",
    "birthdate": "-L0kMgIbLXe3OEkKTUGwz_QKhjehDeofKGwoPrxLuo4"
  }
}

SD-JWTでは各情報がハッシュ化されているため,SD-JWTをVerifierにそのまま共有しても情報が知られることはありません.一方で,ハッシュ化されているということは不可逆な変換がされているため,Verifierと共有したい情報も共有することができません.従って,HolderはVerifierに対してSD-JWTとは別の形で情報を渡す必要があります.

IssuerによるSVCの生成

HolderがVerifierに渡す情報の元となるのがSD-JWT Salt/Value Container (SVC)です.
SVCはSD-JWTに含まれるハッシュ化されたClaimの元の値と,ハッシュ化に利用したソルトのペアを記述したJSONです.例えば上の例に対応するSVCは次のように表せます.

{
  "sd_release": {
    "sub": "[\"2GLC42sKQveCfGfryNRN9w\", \"6c5c0a49-b589-431d-bae7-219122a9ec2c\"]",
    "given_name": "[\"eluV5Og3gSNII8EYnsxA_A\", \"John\"]",
    "family_name": "[\"6Ij7tM-a5iVPGboS5tmvVA\", \"Doe\"]",
    "email": "[\"eI8ZWm9QnKPpNPeNenHdhQ\", \"johndoe@example.com\"]",
    "phone_number": "[\"Qg_O64zqAxe412a108iroA\", \"+1-202-555-0101\"]",
    "address": "[\"AJx-095VPrpTtN4QMOqROA\", {\"street_address\": \"123 Main St\", \"locality\": \"Anytown\", \"region\": \"Anystate\", \"country\": \"US\"}]",
    "birthdate": "[\"Pc33JM2LchcU_lHggv_ufQ\", \"1940-01-01\"]"
  }
}

sd_releaseに含まれる各情報はそれぞれ[ソルト, 生値]で表現されます.従って,これらの情報を利用することでSD-JWTの各Claimに含まれるハッシュ値を再計算することができます.

HolderによるSD-JWT Releaseの生成

IssuerからSD-JWTとSVCを入手したHolderは,これらを利用して選択的にVerifierへ情報共有します.
SD-JWTはIssuerによって署名されたものなのでHolderは手を加えません.一方で,SVCには全ての情報の生値が含まれているため,SVCから情報を間引きしてSD-JWT Releaseを生成します.
例えば,名前だけを開示するSD-JWT Releaseは次のようになります.

{
  "nonce": "XZOUco1u_gEPknxS78sWWg",
  "aud": "https://example.com/verifier",
  "sd_release": {
    "given_name": "[\"eluV5Og3gSNII8EYnsxA_A\", \"John\"]",
    "family_name": "[\"6Ij7tM-a5iVPGboS5tmvVA\", \"Doe\"]",
  }
}

このように生成したSD-JWT ReleaseをSD-JWTとともにVerifierに送信します.

VerifierによるSD-JWTとSD-JWT Releaseの検証

VerifierはSD-JWTのnonseaudを検証することで,自身のために生成された正規のJWTである(リプレイ攻撃されたものでない)ことを確かめます.
次に,SD-JWT Releaseの生値とソルトからsd_hash_algで示されるハッシュ関数を使ってハッシュ値を生成し,SD-JWTの値と一致するか確かめます.一致すれば,開示された情報は確かにSD-JWTに対応する情報であることがわかります.

セキュリティに関する考慮事項

SD-JWTが改竄されればSD-JWT Releaseに任意の生値を入れられてしまうため,SD-JWTの署名は必須とされています.また,本手法ではハッシュ関数とソルトが非常に重要です.ソルトの使い回しやエントロピーの低いソルトを利用するとハッシュ値から生値を推測する攻撃に利用される可能性があります.また,高いエントロピーを確保するためにソルトの長さは最低128bitとされています.ハッシュ関数は衝突耐性の高い関数を利用すべきであり,MD2,MD4,MD5,RIPEMD-160,SHA-1といった脆弱性の見つかっているハッシュ関数は使うべきでないとされています.

プライバシーに関する考慮事項

Claim値はハッシュ化されているもののSD-JWTにはClaim名(たとえばgiven_nameやlast_name)がそのまま記述されていますが,これらのClaim名はVerifierに追加の情報を与えるものではないとしています.
また,Verifier同士が結託することで選択的に開示された情報を統合できてしまう,という問題が考えられますが,この仕様では考慮外のようです.

所感

選択的に情報を開示するというと属性ベース暗号みたいな方法が浮かびましたが,ハッシュとサイドインフォメーションを使って計算量小さく簡単に実現するのが結構面白いです.ただ,プライバシーに関する考慮事項にあるとおり何度も色々なVerifierにSD-JWTを共有すると統合されて情報がリークするリスクが上がるので,実装上の課題は結構ありそうだなと感じました.

Discussion

takataka

SD-JWTの情報、ありがとうございます。問題設定の図において、VerifierとすべきところがIssuerとなっている箇所があるように見受けられます。

dajiajidajiaji

記事ありがとうございます。興味深かったです。

質問なのですが、このSD-JWTは、HolderからVerifierにデータを提供する際にIssuerがいっさい介在しないことを前提としていると思うのですが、HolderはVerifierの正当性をどのように確認するのでしょうか? Verifierがevilであっても情報提供を防ぐ手立てが無いようにみえます。ドラフトに何らかの記載はあるのでしょうか?

(おまえがドラフト読めよって話かもしれませんが)manaty226さんはドラフトを読み込まれたのだろうと思い、ご存じでしたら教えていただけますと嬉しいです。

manaty226manaty226

ご質問ありがとうございます!

まず、本仕様にはVerifierの正当性の確認は記述されていないです。
その上で私の理解になりますが、
本仕様はOAuthの文脈で書かれているので実ユースケースにおいてVerfierはリソースサーバ(APIサーバなど)を想定されていると思います。従って、Holderは例えばサーバ証明書で自分が送りたい正しいVerfierであることを検証することになると思います。
ただし、個々の正しいVerifierが実は裏で名寄せしていて…というケースは本仕様のプライバシー考慮事項に記載のとおり考えられていないようです。

※投稿した後に思いましたがSD-JWTはアクセストークンではないのでリソースサーバに送るという表現は不適切な表現の気がしています。しかしいずれにせよ証明書などで検証するのだろうとは思います。

dajiajidajiaji

早速ありがとうございます!

まず、本仕様にはVerifierの正当性の確認は記述されていないです。

なるほど、やはりそうですか..。

Holderは例えばサーバ証明書で自分が送りたい正しいVerfierであることを検証することになると思います。

Verifierの認証がサーバ証明書のみ、というのは一般的にかなり弱い気がしますよね。証明書なんて自動で誰でも取得できますし。

OAuthでは、VerifierはClientとして事前にIssuerに登録されクレデンシャルを発行してもらっていて、ユーザ情報を取得する際には認証が走りますよね(最低限client_idが登録済みであることは確認する)。これによって、Verifierに問題があった場合にはIssuerはリボークもできるし、ユーザ情報を誰に提供したかも把握できるし、ユーザが同意したことも監査情報として残しておけるわけですが、こうした担保が一切なく、Verifierにユーザ情報を開示してしまうことになりますよね・・。

Verifierがオンラインであるならば、IssuerがSelective Disclosureなデータ提供APIを用意するのが筋なような気がしました。Verifierの認証もできますし。SD-JWTじゃなくて、(1)ユーザが選択的開示に合意したクレームキーのリストにユーザの署名をつけてshort-lived なJWEを作ってVerifierに渡す。(2)VerifierはJWEを上記データ提供APIに投げて、クレーム値を得る、とした方がシンプルだし安全だと思うのですよね。Holder-Issuer間でつかう暗号鍵の導出にDHを使えば(Holderは毎度違う公開鍵を使うことになるので)名寄せ問題も綺麗に回避できます。

うーむやはり、SD-JWTの存在意義が微妙というか、よくわからなくなってしまいました。

いずれにしても、ありがとうございます。

manaty226manaty226

なるほど、たしかにUserInfoエンドポイントのような仕組みの方が良さそうですね。
アクセストークンと違ってtoken bindingで再利用を防げばいいわけではなくて窃取された時点でevilなverifierの目的が達成されてしまいますし。
Issuerへの問い合わせ負荷は減らせるかなと思いますが、一般的にIssuerの都合がHolderのプライバシーに優先するのも考えづらいので、たしかに分からなくなってきました…

良い問いかけをいただきありがとうございます。