🆔

SSF関連仕様紹介 : RFC 9493 Subject Identifiers for Security Event Tokens

2024/05/06に公開

ritou です。

OpenID FoundationのShared Signals Working Groupで策定が進められている Shared Signals Framework (SSF)とそれに関連する仕様に注目して紹介していきます。

https://openid.net/wg/sharedsignals/

今回紹介するのは RFC 9493 Subject Identifiers for Security Event Tokens という仕様です。

https://datatracker.ietf.org/doc/html/rfc9493

理解するにあたってポイントになりそうな部分を抜粋して紹介します。 雰囲気を掴んだら自分で仕様を読んで完全に理解するのが良いでしょう。

概要

この仕様では何が定義されているのか、というところですが

Security events communicated within Security Event Tokens may support a variety of identifiers to identify subjects related to the event.

// Security Event Token(SET): SSFのCAEPやRISCで送られるJWT
// Security Events : SETのPayloadに含まれるイベント情報
// これはユーザーだけではなく、デバイスとか色々ある

This specification formalizes the notion of Subject Identifiers as structured information that describes a subject and named formats that define the syntax and semantics for encoding Subject Identifiers as JSON objects.

// RFC7519にてJWTのsub claimは次のように定義されている
// "sub" value is a case-sensitive string containing a StringOrURI value.
// この仕様では構造化された情報としてSubject Identifierを表現する

It also establishes a registry for defining and allocating names for such formats as well as the JSON Web Token (JWT) "sub_id" Claim.

// SETのためのものだけど、JWTの "sub_id" クレームとして汎用的に使える
// -> JWTの"sub_id"クレームやその他の箇所で利用されるSubjectの識別子表現として利用可能

いろんな種類のSubject Identifierを表現しようとする時に、単純なString/StringOrURIだと、prefix使ったりして工夫しないといけなかったりしますが、構造化で表現してしまえというお話です。
SSFの用途以外にも活用できそうな仕様なので存在を覚えておきましょう。

Introduction

いくつかの例示があります。

メールアドレスを識別子として表現する場合

{
  "format": "email",
  "email": "user@example.com"
}

わりと直感的ですね。電話番号も同様です。
これをJWTの "sub_id" クレームで利用する場合、

{
  "iss": "issuer.example.com",
  "sub_id": {
    "format": "phone_number",
    "phone_number": "+12065550100"
  }
}

となります。

ちなみに私は普段から口を酸っぱくして 「メアドや電話番号をID連携の際のキーとして使うな高校校歌」 を歌っておりますが、こういう表現も使われる可能性があるよねと言われたら「それははい」という感じです。

先ほどの例はJWTの "sub" クレームの代わりに "sub_id" クレームを利用する感じでしたが、次はSSEのイベント表現の中で、このメアドからこのメアドへのメールがこのメアドのやつに...みたいなのを表現する場合の例です。

{
  "iss": "issuer.example.com",
  "iat": 1508184845,
  "aud": "aud.example.com",
  "events": {
    "https://secevent.example.com/events/message-interception": {
      "from": {
        "format": "email",
        "email": "alice@example.com"
      },
      "to": {
        "format": "email",
        "email": "bob@example.com"
      },
      "interceptor": {
        "format": "email",
        "email": "eve@example.com"
      }
    }
  }
}

雰囲気はこんなもんだと思います。

Subject Identifiers

上記の例でも出ていましたが、 "format" メンバーで識別子の種類を指定、その上でそれに対応したルールに従い表現する ということです。

Identifier Formats vs Principal Types

この仕様では、"email"というIdentifier Formatである場合に指定される値がメールアドレスであることは定義しますが、それがメールアドレス自体を示しているのかメールアドレスを所持しているユーザーを示しているのかは定義しません前者の曖昧さを取り払う部分と後者の曖昧さを残す部分 には注意が必要ですね。

Identifier Format Definitions

それではこの仕様で定義されているIdentifierを見てみましょう。
どれが必須とかNULLダメとかは仕様を参照してください。

Account Identifier Format

"RFC7565 The 'acct' URI Scheme" で定義されているURIを用いるフォーマットです。

https://www.rfc-editor.org/rfc/rfc7565.html

{
  "format": "account",
  "uri": "acct:example.user@service.example.com"
}

これまでは 文字列がacct:から始まる みたいな判別をしつつやっていたところを構造的に表現する感じですね。この後も似たようなのが出てきますよ。

Email Identifier Format

メールアドレス形式の識別子表現です。

{
  "format": "email",
  "email": "user@example.com"
}

ここにだけ丁寧に "Email Canonicalization" という説明があります。
メールアドレスの仕様ではドメイン部分だけcase-insensitiveなんだけど、実際はローカルパートもcase-insensitiveだったりドットを無視したりするのでカオス、というのを最近業務で確認したことを思い出して頭痛が痛いです。話が脱線しましたが、この辺りも注意が必要ですよという説明でした。

Issuer and Subject Identifier Format

ID連携のRPが外部サービスのアカウント管理をするときに利用する、"Issuer"と"Subject"の組み合わせによる識別子表現です。

{
  "format": "iss_sub",
  "iss": "https://issuer.example.com/",
  "sub": "145234573"
}

Opaque Identifier Format

UUIDやハッシュ+エンコード値のような文字列を利用する識別子表現です。

{
  "format": "opaque",
  "id": "11112222333344445555"
}

Phone Number Identifier Format

国際化電話番号を用いた識別子表現です。

{
  "format": "phone_number",
  "phone_number": "+12065550100"
}

Decentralized Identifier (DID) Format

最近はVerifiable Credentialsの方が本質だと言われて落ち着いた感のあるDIDを用いた識別子表現です。

{
  "format": "did",
  "url": "did:example:123456"
}

より具体的な例も示されていますね。

{
  "format": "did",
  "url": "did:example:123456/did/url/path?versionId=1"
}

ちなみにDIDは "scheme", "DID method", "DID method-specific identifier" から構成されていますが、その詳細までは分解しません。

Uniform Resource Identifier (URI) Format

URIを用いた識別子表現です。

{
  "format": "uri",
  "uri": "https://user.example.com/"
}

URNを使う例も載せられています。

{
  "format": "uri",
  "uri": "urn:uuid:4e851e98-83c4-4743-a5da-150ecb53042f"
}

これはこれで異論ありませんが、なんだかこの仕様を意識すればするほど、 ここまでの例で紹介した "uri" メンバーはこのUniform Resource Identifier (URI) Formatではないのか? と思ったりするかもとちょっとだけ思いました。

Aliases Identifier Format

JSONの配列を利用して、識別子を複数並べてエイリアスを表現する識別子表現です。それぞれはここまでに定義されたフォーマットのものが入ります。

{
  "format": "aliases",
  "identifiers": [
    {
      "format": "email",
      "email": "user@example.com"
    },
    {
      "format": "phone_number",
      "phone_number": "+12065550100"
    },
    {
      "format": "email",
      "email": "user+qualifier@example.com"
    }
  ]
}

Subject Identifiers in JWTs

ここまで定義されたものをJWTの中で使っていく中で、気をつける点があります。

JWT "sub_id" Claim

JWTにはSubject Identifier を表現するための "sub" クレームがあります。こちらは文字列で表現されるのに対して、"sub_id" クレームの方はJSON形式で表現されます。

あるJWTの中に "sub" と "sub_id" クレームの両方が入っていたらどうするか、というところですが、同じ Subject を示す必要があります。そのようなJWTを受け取った側は、両方を組み合わせて解釈はしない とあります。最初に "sub_id" を解釈しようとし、それができない場合は "sub" を利用するようにフォールバックするというのが想定されています。

"sub_id" クレームしかない場合はまぁいいでしょう。

{
  "iss": "issuer.example.com",
  "sub_id": {
    "format": "email",
    "email": "user@example.com"
  }
}

同じSubjectを表現するために、同じ識別子を使うパターン、これもイメージしやすいです。

{
  "iss": "issuer.example.com",
  "sub": "user@example.com",
 "sub_id": {
    "format": "email",
      "email": "user@example.com"
  }
}

"sub" と "sub_id" で同じ識別子の種類でありながら別の値が入ることもあると。
これでも同じユーザーだと言われてもちょっと混乱しそうですがそういうことらしい。

{
  "iss": "issuer.example.com",
  "sub": "liz@example.com",
  "sub_id": {
    "format": "email",
    "email": "elizabeth@example.com"
  }
}

別の識別子の種類を使うのは、これはこれでイメージしやすいですね。

{
  "iss": "issuer.example.com",
  "sub": "user@example.com",
  "sub_id": {
    "format": "account",
    "uri": "acct:example.user@service.example.com"
  }
}

JWT "sub_id" Claim and "iss_sub" Subject Identifier

もう一つ混乱しそうなものとして、 "iss_sub" フォーマットの "sub_id" クレームとJWTの "iss", "sub" クレームの関係があります。これも必ずしも同じとはなりません。

まずは "iss" と "sub_id" の "iss" が一緒の例です。
これまでの "sub" を "Issuer and Subject Identifier Format" な "sub_id" として表現するとこうなる、という感じです。

{
  "iss": "issuer.example.com",
  "sub_id": {
    "format": "iss_sub",
    "iss": "issuer.example.com",
    "sub": "example_user"
  }
}

次は "iss" と "sub_id" の "iss" が異なるケースです。

{
  "iss": "client.example.com",
  "sub_id": {
    "format": "iss_sub",
      "iss": "issuer.example.com",
      "sub": "example_user"
  }
}

JWTのIssuerは "client.example.com" ですが、このJWT自体のSubjectは "issuer.example.com" が Isser である "example_user" というユーザーです。
例えば、Googleログインを実装するRPがバックエンドなり別のシステムに "このGoogleユーザーに対して⚪︎⚪︎してくれ" みたいなリクエストを送る場合などに使いどころがありそうですが、使い方によっては大事故につながることもあるあたりなので気をつけましょう。

そして、JWTの "iss" と "sub" が存在、かつ "sub_id" の両者とも異なるケースです。

{
  "iss": "client.example.com",
  "sub": "client_user",
  "sub_id": {
    "format": "iss_sub",
    "iss": "issuer.example.com",
    "sub": "example_user"
  }
}

パッと思いつきませんが、こういう場合も当然あり得るというところでしょう。なんとなく後で混乱しそうなので覚えておきましょう。

終わり

識別子の表現についての仕様を紹介しました。
SSF文脈のユースケースだけではなく、いろんなデータをイベントとして送信するユースケースでは割と汎用的に利用できそうな仕様です。

今回最初からしれっと出てきたSETについてはDraftの時にQiitaに記事を書いたものの、2018年とかなのでもう一度見直そうかと思います。

https://qiita.com/ritou/items/ca07761fc3a36039be54

ではまた。

Discussion