OpenID ConnectのSelf-Issued OP(OIDC SIOP)の話

おはようございます、ritouです。

OpenID Connect Messagesの仕様で定義されてるSelf-Issued OpenID Providerについてのお話です。

いきなり参考リンク

英語読める人は仕様読めばよい。

ざっくり説明

こんな感じです。

  • OS(PC, モバイル端末)やブラウザ内で動作し、openid://で呼び出される
  • ユニークなRSAやECDSAとかのprivate/publicな鍵ペアを生成して安全に管理する
  • クライアント(Webサービスやアプリ)からリクエストを受けたら署名付のID Tokenを返す

というところです。
よくあるフローの説明は後にして、返されるID Tokenの中身の説明をします。

ID Tokenの中身

通常のOpenID Providerが発行するID Tokenにはユーザー識別子や対象のclient_idなどの「いつ誰がどのサービスに・・・」という情報が含まれます。
Self-Issued OPが発行するID TokenのPayloadには次のような値が含まれます。

{
"iss": "https://self-issued.me",
"sub": "wBy8QvHbPzUnL0x63h13QqvUYcOur1X0cbQpPVRqX5k",
"aud": "https://client.example.org/cb",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"sub_jwk": {
"kty":"RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx
4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs
tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2
QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI
SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb
w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB"
}
}

http://openid.net/specs/openid-connect-messages-1_0.html#self_issued.validation
  • "iss": 発行者。Self-Issued OPの場合は"https://self-issued.me"となります。
  • "sub_jwk" : 秘密鍵を用いて生成した署名の検証に利用できる公開鍵情報。上記の例はRSAの場合。
  • "sub": 公開鍵情報の値のハッシュ値をごにょごにょした値
  • "aud": 発行対象のWebサービスとかアプリとかの識別子。事前登録とかできないのでURL形式になる
  • "nonce": リクエストに含まれたnonceの値
  • "exp": 有効期限のタイムスタンプ
  • "iat": 発行時のタイムスタンプ

よって、

  • ユニークな鍵ペアを管理しているSelf-Issued OPが秘密鍵を用いて書名を生成し、検証用の公開鍵を含めているので、受け取った側はPayloadが改ざんされてないことを検証できる
  • Self-Issued OPがユニークな鍵ペアを保管し、その公開鍵のハッシュ値みたいなのを識別子として扱うので、Self-Issued OPが動作する端末/ブラウザ単位の識別が可能

ということになります。
なんとなく理解できたでしょうか?

次は、いちおうリクエスト/レスポンスがどうなるかを説明します。

認可要求/応答

Self-Issued OPの認可要求のURLは次のようになります。
OpenID ConnectやOAuth 2.0の認可要求を理解している方にとっては難しいものはありません。

HTTP/1.1 302 Found
Location: openid://
?response_type=id_token
&client_id=https%3A%2F%2Fclient.example.org%2Fcb
&scope=openid%20profile
&state=af0ifjsldkj
&nonce=n-0S6_WzA2Mj
registration=&%7B%22logo_uri%22%3A%22https%3A%2F%2F
client.example.org%2Flogo.png%22%7D

http://openid.net/specs/openid-connect-messages-1_0.html#self_issued.request

重要なことは

  • 認可URLは "openid://"
  • response_typeはid_token
  • client_idはURLの形式 : 静的/動的なclient登録ができないため

というあたりですね。

認可応答はclient_idに指定されたURLのフラグメント部分にID Tokenが付加されます。

https://client.example.org/cb#id_token=(ID Token)&state=

ではどんなフローで使えそうかを説明します。

Self-Issued OPをモバイル端末の特定に利用するユースケース

あるサービスが、端末の所有者の同意の下に端末識別子を取得するフローを図にしてみました。
カスタムURIスキームで起動するので、モバイルアプリ、Webアプリの両方からSelf-Issued OPにID Tokenを要求できます。

世の中にはユーザーに問い合わせることなくしれっと端末の識別を行いたい、いろんなサービスのデータ同士をひっつけて楽しいことしたい方々が大勢いらっしゃるように思われますね。ク○ったれが。
モバイルアプリであればインストール時に各種パーミッションをとったりしますが、Self-Issued OPがあれば認可要求のタイミングで端末所有者の同意のもとに端末の識別子に使える値を得ることができそうです。

実装するために必要な機能

実装するためには、次の機能が必要です。

  • 1) ユニークな鍵ペア生成機能
  • 2) 鍵ペア(というか秘密鍵)を安全に保管するストレージ
  • 3) カスタムURIスキームを用いたSelf-Issued OPの呼び出し

1はなんとかなりそうですね。

OpenID Connectの仕様にはSelf-Issued OPを誰が実装するかを書いていませんでした。
普通のアプリ開発者が実装することを考えた場合、2は同一署名を持つアプリだけがアクセスできる領域を使う感じになるでしょうか。
Androidでrootとられたら・・・とかまぁいろいろつっこみどころも絶えないところですね。
あと、一番不安なのは3ですね。openid://が指定されたら必ずSelf-Issued OPが呼び出され、リクエストが渡される必要がありますが、OAuthのあたりでも散々議論されてるように、OSによってカスタムURIスキームの扱いが異なったりします。

あとは同一端末に複数のSelf-Issued OPが存在したら話がさらにややこしくなったりしそうなので、個人的にはSelf-Issued OPはOSやブラウザのネイティブな機能として実装されるのがよいと思います。

こんな機能も欲しい

こういう端末の識別あたりの話では、名寄せを防ぎたいケースがあります。
現在の仕様にはこのあたり書いてないように見えますが、PPIDを利用したいClient向けに鍵ペアを複数生成、aud単位で利用する鍵を使い分けられるようになるととても便利そうです。

アプリ単体で利用できそうなところ

現状、ベンダーが提供する識別子などを利用せずにアプリから利用端末を特定したい場合、DeviceIDのようなものをバックエンドサーバーから受け取って保存したりする方法が考えられます。
しかし、いくらHTTPSを使ってもアプリからの通信を覗けるツールはありますし、第3者に隠し通すことはできないと考えてもよいでしょう。

Self-Issued OPを参考にして、下記のような機能を実装してみても面白そうです。

  • アプリ内で鍵ペアを生成もしくはバックエンドで生成したものをAPIなどで受け取り、端末側で保管
  • 毎回のリクエストもしくは重要な処理などの際に、パラメータをPayloadとして保管されている秘密鍵で署名したJSON Web Tokenを生成

というようにすると、バックエンドサーバーがアクセス元の端末とパラメータが改ざんされていないことを確認できそうです。

こう書くとOAuth 1.0みたいな発想ですね。
既にいくつかのサービスで端末の管理目的で似たようなことをしてるっぽいです。
面白そうなユースケースを思いついたかたがいらっしゃったら教えてください。

まとめ

Self-Issued OPを用いた端末特定のユースケースについて、ざっくり流れを紹介しました。
W3CのWeb Cryptography API等とともにこのあたりは今後も注目していきたいと思います。
まぁ、モバイル端末やOSレベルよりも、Chromeあたりがさくっと実装してブラウザの特定ができるような時代が先に来るような気もしますね。

ではまた!