本記事の目的
- OpenID Connect(以降、OIDC)・OAuthのフェデレーションセキュリティ対策について、トークン署名・トークン暗号化・Holder-of-Key(HoK)を整理する。
トークン署名
概要説明
- OIDCで利用するIDトークンは、基本的にはJSON Web Signature(JWS)形式であり、OpenID Providerによる署名が付与されている。
- KeyCloakが発行したIDトークンをjwt.ioで確認してみると、ヘッダ・ペイロード・署名から構成されていることが分かる。
トークン暗号化
概要説明
- OIDCでは、JSON Web Encryption(JWE)形式を利用することで、IDトークンを暗号化できる。
- JWEはヘッダ・Encrypted Key・初期ベクタ・暗号文・認証タグから構成されている。
BASE64URL(UTF8(JWE Protected Header)) || '.' ||
BASE64URL(JWE Encrypted Key) || '.' ||
BASE64URL(JWE Initialization Vector) || '.' ||
BASE64URL(JWE Ciphertext) || '.' ||
BASE64URL(JWE Authentication Tag)
- 前述のJWSが暗号化されており、JWEはJWSを内包している。
(参考: RFC 7516: JSON Web Encryption (JWE))
KeyCloakでの実装サンプル
概要理解を目的として、KeyCloakでJWE実装を試みる。Production環境で利用する場合は適切なアルゴリズムや復号ロジックを実装すること。
-
KeyCloak管理コンソールから、対象のクライアントを選択する。
-
Advanced > Fine grain OpenID Connect configurationにて、ID token encryptionを設定する。
-
IDトークンを取得し、JWEヘッダの中身を確認してみる。
# ブラウザから認証リクエストを送信し、認可コードを取得する
http://localhost:8080/realms/myrealm/protocol/openid-connect/auth?client_id=dummy&redirect_uri=http://localhost:3000&response_type=code&scope=openid
# トークンリクエストを送信する
$ curl -X POST \
http://localhost:8080/realms/myrealm/protocol/openid-connect/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d "grant_type=authorization_code" \
-d "code=dummy" \
-d "client_id=dummy" \
-d "client_secret=dummy" \
-d "redirect_uri=http://localhost:3000"
# JWEのHEADERをデコードして中身を確認してみる
$ JWE="dummy"
$ echo $JWE
BASE64URL(UTF8(JWE Protected Header)).BASE64URL(JWE Encrypted Key).BASE64URL(JWE Initialization Vector).BASE64URL(JWE Ciphertext).BASE64URL(JWE Authentication Tag)
$ HEADER=$(echo $JWE | cut -d'.' -f1)
$ echo -n ${HEADER}==== | fold -w 4 | sed '$ d' | tr -d '\n' | base64 -d
{"alg":"RSA-OAEP","enc":"A256GCM","cty":"JWT","kid":"dummy"}
(参考: Server Administration Guide)
Holder-of-Keyトークン
概要説明
- OAuthやOIDCではBearerトークンを利用することが多く、トークンの利用者を限定できない。(=トークンを手に入れてしまえば、誰でもアクセスを許可されてしまう。)
- Holder-of-Keyトークンであれば、保有者以外のトークン利用を制限することができる。
- 具体的な実装方法としては、クライアント証明書によるmTLSやDemonstrating Proof-of-Possession (DPoP)等が候補となる。
Demonstrating Proof-of-Possession (DPoP)について
- アクセストークンにクライアントの公開鍵を紐づけることで、秘密鍵を所有していないクライアントによるトークン利用を防ぐ仕組み。
- (事前準備)クライアントは秘密鍵、公開鍵、DPoP Proofを生成する。
- (A)クライアントは認可コードやDPoP Proofを含めて、トークンリクエストを送信する。
- (B)認可サーバは公開鍵をバインドしたアクセストークンをクライアントに返却する。
- (C)クライアントによるアクセストークン利用時には、DPoP Proofを含むリクエストをリソースサーバに送信する。
- (D)リソースサーバ側での検証に成功した場合、保護されたリソースがクライアントへ返却される。
+--------+ +---------------+
| |--(A)-- Token Request ------------------->| |
| Client | (DPoP Proof) | Authorization |
| | | Server |
| |<-(B)-- DPoP-Bound Access Token ----------| |
| | (token_type=DPoP) +---------------+
| |
| |
| | +---------------+
| |--(C)-- DPoP-Bound Access Token --------->| |
| | (DPoP Proof) | Resource |
| | | Server |
| |<-(D)-- Protected Resource ---------------| |
| | +---------------+
+--------+
(上記図表はRFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP)から抜粋)
- DPoP Proofはクライアントの秘密鍵で署名されたJWTである。
- type
- DPoP Proofであることを示すためのフィールド(dpop+jwtが含まれる)
- alg
- 非対称デジタル署名アルゴリズム
- jwk
- 公開鍵の情報
- jti
- DPoP Proofの一意な識別子
- htm
- HTTPメソッド
- htu
- HTTPのターゲットURI
- iat
- JWTのタイムスタンプ
- type
{
"typ":"dpop+jwt",
"alg":"ES256",
"jwk": {
"kty":"EC",
"x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
"y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA",
"crv":"P-256"
}
}
.
{
"jti":"-BwC3ESc6acc2lTc",
"htm":"POST",
"htu":"https://server.example.com/token",
"iat":1562262616
}
(上記DPoP Proofの例はRFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP)から抜粋)
(参考: RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP), Configure OAuth 2.0 Demonstrating Proof-of-Possession | Okta Developer)
KeyCloakでの実装方法について
- 詳細は割愛するが、KeyCloakであればmTLSとDPoPの両者を実装可能である。
- 本記事の執筆時点ではDPoPはプレビューサポートであることに留意すること。
(参考: Server Administration Guide, Keycloak 23.0.0 released - Keycloak)
注意事項
- 本記事は万全を期して作成していますが、お気づきの点がありましたら、ご連絡よろしくお願いします。
- なお、本記事の内容を利用した結果及び影響について、筆者は一切の責任を負いませんので、予めご了承ください。