はじめに
これは、Authlete の /api/jose/verify
API の解説文書である。
(諸般の事情で日本語の技術情報を自社ウェブサイト上で公開する準備が整っていないので Qiita で技術情報を提供している。。。 orz... )
1. 用途
JOSE(JavaScript Object Signing and Encryption)オブジェクトの検証をおこなう。
2. 検証項目
- 署名検証(署名されている場合)
- 必須クレームの存在有無(必須と指定されたクレーム群がある場合)
- 時刻関連の標準クレーム群のチェック(ペイロードが JSON としてパース可能で、かつ当該クレーム群を含んでいる場合)
3. API への入力
項目 | 値 |
---|---|
HTTP メソッド | POST |
Content-Type |
application/json application/x-www-form-urlencoded
|
保護 | サービス API キー & API シークレット |
パラメーター | 型 | 要否 | デフォルト | 説明 |
---|---|---|---|---|
jose |
文字列 | 必須 | - | 検証対象の JOSE オブジェクト |
mandatoryClaims |
文字列配列 | 任意 | null | 必須クレーム群の名前 |
clockSkew |
数値 | 任意 | 0 | 許容する時刻のずれ(秒単位) |
clientIdentifier |
文字列 | 条件 | null | クライアント識別子 |
signedByClient |
真偽値 | 任意 | false | 署名がクライアントの鍵でおこなわれたか |
Content-Type が application/x-www-form-urlencoded
の場合、mandatoryClaims
はクレーム名を空白文字区切りで列挙した単一文字列。
authlete-java-common ライブラリでは、/api/jose/verify
API への入力は JoseVerifyRequest
クラスで表現されている。
4. API からの出力
項目 | 値 |
---|---|
HTTP ステータスコード | 200 |
Content-Type | application/json |
パラメーター | 型 | 説明 |
---|---|---|
resultCode |
文字列 | 処理結果コード |
resultMessage |
文字列 | 処理結果メッセージ |
valid |
真偽値 | JOSE オブジェクトが有効か |
signatureValid |
真偽値 | 署名が有効か |
missingClaims |
文字列配列 | 必須とされているのに含まれていないクレーム群 |
invalidClaims |
文字列配列 | 有効ではないクレーム群 |
errorDescriptions |
文字列配列 | エラーメッセージ群 |
authlete-java-common ライブラリでは、/api/jose/verify
API からの出力は JoseVerifyResponse
クラスで表現されている。
5. AuthleteApi
authlete-java-common ライブラリが定義する AuthleteApi
インターフェースでは、/api/jose/verify
API とのやりとりは verifyJose
メソッドとして表現されている。
JoseVerifyResponse verifyJose(JoseVerifyRequest request) throws AuthleteApiException
6. API 動作詳細
6.1. JOSE 解析
-
jose
リクエストパラメーターが指定されているかチェックする。指定されていなかったり、空文字列であれば、検証処理終了。処理結果コードA160201
でレスポンスを返す。検証結果valid
はfalse
となる。 -
jose
リクエストパラメーターの値を JOSE オブジェクトとみなしてパースする。パースに失敗した場合は検証処理終了。処理結果コードA160202
でレスポンスを返す。検証結果valid
はfalse
となる。 -
JOSE オブジェクトが JWE の場合、検証処理終了。処理結果コード
A160101
でレスポンスを返す。検証結果valid
はfalse
となる。(将来 JWE に対応する可能性はあるが、現時点では対応していない)
6.2. ペイロード検証
-
ペイロードを JSON としてパースする。(パースに失敗してもエラーとはみなさない)
-
ペイロードが JSON としてパースできなかったが、
mandatoryClaims
リクエストパラメーターで必須クレーム群が指定されていた場合、それらのクレーム群をmissingClaims
レスポンスパラメーターにセットし、A160203
を処理結果コードとしてセットする。検証結果valid
はfalse
となる。 -
ペイロードが JSON としてパースできた場合、
mandatoryClaims
リクエストパラメーターで必須クレーム群が指定されていれば、それらが JSON 内に含まれているかどうかチェックする。含まれていない場合は、当該クレームの名前をmissingClaims
レスポンスパラメーターに追加する。必須クレームと指定されたのに含まれていないクレームが一つでもある場合、処理結果コード にA160204
がセットされ、検証結果valid
はfalse
となる。 -
ペイロードが JSON としてパースできた場合、
exp
(Expiration Time)クレーム、iat
(Issued At)クレーム、nbf
(Not Before)クレーム、が存在していれば、それらの値をチェックする。これらのクレーム群は時刻に関するもので、値のチェックに際しては、clockSkew
リクエストパラメーターの値が考慮される。 -
exp
クレームの値が数値ではない場合、処理結果コードとしてA160205
がセットされ、invalidClaims
レスポンスパラメーターにexp
が追加され、検証結果valid
はfalse
となる。exp
クレームが数値であれば、その値が現在時刻より未来かどうかがチェックされる。現在時刻以前(過去)であれば、それは現在時刻が JOSE オブジェクトの有効期限を過ぎているということを意味しており、処理結果コードとして160206
がセットされ、invalidClaims
レスポンスパラメーターにexp
が追加され、検証結果valid
はfalse
となる。 -
iat
クレームの値が数値ではない場合、処理結果コードとしてA160205
がセットされ、invalidClaims
レスポンスパラメーターにiat
が追加され、検証結果valid
はfalse
となる。iat
クレームが数値であれば、その値が現在時刻以前かどうかがチェックされる。現在時刻より後(未来)であれば、それは JOSE オブジェクトの発行時刻が未来ということを意味しており、処理結果コードとして160207
がセットされ、invalidClaims
レスポンスパラメーターにiat
が追加され、検証結果valid
はfalse
となる。 -
nbf
クレームの値が数値ではない場合、処理結果コードとしてA160205
がセットされ、invalidClaims
レスポンスパラメーターにnbf
が追加され、検証結果valid
はfalse
となる。nbf
クレームが数値であれば、その値が現在時刻以前かどうかがチェックされる。現在時刻より後(未来)であれば、それは JOSE オブジェクトがまだ有効になっていないということを意味しており、処理結果コードとして160208
がセットされ、invalidClaims
レスポンスパラメーターにnbf
が追加され、検証結果valid
はfalse
となる。
6.3. 署名検証
-
JOSE オブジェクトに署名がついている場合、署名の検証をおこなう。
-
署名検証時、まず、JWS ヘッダーから
alg
パラメーターを取り出す。 -
alg
パラメーターの示すアルゴリズムが対称鍵系(HS256
、HS384
、HS512
)の場合、OpenID Connect Core 1.0 の 10.1. Signing, Symmetric Signatures のルールに基づいて共有鍵を計算するため、クライアントアプリケーションのクライアントシークレットの値が必要となる。よって、クライアントアプリケーションを特定する必要がある。/api/jose/verify
API の実装は、clientIdentifier
リクエストパラメーターが指定されていれば、その値をクライアント識別子として用いる。もしもclientIdentifier
リクエストパラメーターが指定されていない場合、signedByClient
リクエストパラメーターの値がtrue
であり、かつペイロードが JSON としてパースできる場合のみ、ペイロードの JSON に含まれるiss
クレームの値をクライアント識別子とみなす。iss
を参照する際、その値が文字列でない場合、処理結果コードにA160214
がセットされ、検証結果valid
はfalse
となる。ここまでの処理でクライアント識別子を特定できていなければ、処理結果コードにA160215
がセットされ、検証結果valid
はfalse
となる。 -
(対称鍵系の続き)上記処理で特定されたクライアント識別子に該当するクライアントが存在しない場合(他のサービス下にあるクライアントであったり、ロックされていたりする場合も含む)、処理結果コードに
A160216
がセットされ、検証結果valid
はfalse
となる。 -
(対称鍵系の続き)上記処理で特定されたクライアントのクライアントシークレットを用いて署名の検証をおこなう。署名が不正であれば、処理結果コードに
A160219
がセットされ、検証結果valid
はfalse
になる。一方、署名が有効であれば、処理結果コードにA160001
がセットされ、検証結果valid
はtrue
となる。ここで検証処理は終わり、レスポンスが返される。 -
alg
パラメーターの示すアルゴリズムが非対称鍵系の場合、署名検証に用いるための公開鍵が必要となる。signedByClient
リクエストパラメーターの値がfalse
(デフォルト)の場合、JOSE オブジェクトの署名はサーバーの秘密鍵を用いておこなわれたとみなされ、/api/jose/verify
API の実装はサーバーの JWK Set ドキュメントを取得しようとする。このとき、サーバーの JWK Set ドキュメントが登録されていなければ、処理結果コードとしてA160218
がセットされ、検証結果valid
はfalse
となる。一方、signedByClient
リクエストパラメーターの値がtrue
の場合、JOSE オブジェクトの署名はクライアントの秘密鍵を用いておこなわれたとみなされ、/api/jose/verify
API の実装はクライアントの JWK Set ドキュメントを取得しようとする。このとき、まず、クライアント識別子の特定がおこなわれるが、この処理は対称鍵系アルゴリズムの際にクライアントシークレットを求めるためにおこなったのと同じ手順であるため、処理結果コードとしてA160214
、A160215
、A160216
が発生する可能性がある。クライアント識別子の特定後、当該クライアントの JWK Set ドキュメントの取得が試みられるが、このとき、クライアントの JWK Set ドキュメントが登録されていなえれば、処理結果コードとしてA160301
が設定され、検証結果valid
はfalse
となる。 -
(非対称鍵系の続き)上記処理で求められた JWK Set ドキュメントから、署名検証に用いるための公開鍵を取り出す。公開鍵の検索時、JWK の
kty
(必須)、kid
、alg
、use
、key_ops
が考慮される。署名検証に用いることが可能な JWK が複数存在する場合、基本的には、より制限項目の多いものが選ばれる。例えば、kty
しか持たない JWK よりも、kty
に加えてalg
やuse
も持っている JWK のほうが選ばれる。適切な JWK が存在しない場合、処理結果コードとしてA160221
がセットされ、検証結果valid
はfalse
となる。逆に、利用可能な JWK が複数存在し、それらのうち一つに絞り込むことができなかった場合、処理結果コードとしてA160220
がセットされ、検証結果valid
はfalse
となる。 -
(非対称鍵系の続き)上記処理で特定された JWK を用いて署名の検証をおこなう。署名が不正であれば、処理結果コードに
A160219
がセットされ、検証結果valid
はfalse
になる。一方、署名が有効であれば、処理結果コードにA160001
がセットされ、検証結果valid
はtrue
となる。ここで検証処理は終わり、レスポンスが返される。
7. 処理結果コード一覧
/api/jose/verify
API のレスポンスの resultCode
レスポンスパラメーターが取りうる値は下表のとおり。
resultCode |
resultMessage |
---|---|
A160001 |
The JOSE is valid. |
A160101 |
JWE is not supported. |
A160102 |
Failed to create a signature verifier for the symmetric algorithm ('{Algorithm}') specified in the JWS header. |
A160103 |
Failed to get the service's JWK Set. |
A160104 |
The service's JWK Set is not found. |
A160105 |
Failed to create a signature verifier for the RSA algorithm ({Algorithm}) from the JWK (kid = {Key ID}). |
A160106 |
Failed to create a signature verifier for the EC algorithm ({Algorithm}) from the JWK (kid = {Key ID}). |
A160107 |
Failed to create a signature verifier for the unsupported algorithm ({Algorithm}). |
A160108 |
Failed to get the client JWK Set. |
A160109 |
The client's JWK Set is not found. |
A160110 |
Failed to verify the signature. |
A160201 |
The 'jose' request parameter is missing or empty. |
A160202 |
Failed to parse the value of the 'jose' request parameter as JOSE. |
A160203 |
The payload of the JOSE object couldn't be parsed as JSON although some claims were declared as mandatory. |
A160204 |
The mandatory claim '{Claim}' is missing. |
A160205 |
The value of the claim '{Claim}' is not an integer. |
A160206 |
The expiration time of the JOSE has been reached: exp = {Expiration Time}, current time = {Current Time}, clock skew = {Clock Skew} |
A160207 |
The issue time exceeds the current time: iat = {Issued At}, current time = {Current Time}, clock skew = {Clock Skew} |
A160208 |
The current time has not reached the time before which the JOSE must be regarded as invalid: nbf = {Not Before}, current time = {Current Time}, clock skew = {Clock Skew} |
A160209 |
The JWS does not have a valid header. |
A160210 |
The JWS header does not have a valid 'alg' parameter. |
A160211 |
The 'alg' value ('{Algorithm}') in the JWS header is not supported. |
A160212 |
The unsecured JWS does not have a valid payload. |
A160213 |
The JWS does not have a valid payload. |
A160214 |
The value of the claim '%s' is not a string. |
A160215 |
The identifier of the client application is necessary for verification but it is not available. |
A160216 |
Failed to get information about the client application (identifier = '{Client Identifier}'). |
A160217 |
Cannot verify the signature because the shared symmetric key based on the client secret cannot be computed. |
A160218 |
The service's JWK Set has not been registered. |
A160219 |
The signature of the JOSE object is invalid. |
A160220 |
Cannot determine a JWK because there are multiple JWKs that match the conditions for signature verification. (alg = {Algorithm}, kid = {Key ID}) |
A160221 |
Could not find any appropriate JWK for signature verification in the {client or service}'s JWK Set. (alg = {Algorithm}, kid = {Key ID}) |
A160301 |
The client's JWK Set has not been registered. |
- 注1:API をたたく際のサービス API キーと API シークレットが不正である場合など、
/api/jose/verify
API の中心処理とは別の箇所で問題が発生したときは、この表には存在しない処理結果コードが入る。 - 注2:Authlete の実装に不具合が無い限り発生しないと思われる処理結果コードも上記表内にリストしてある。
8. 実験例
8.1. 準備
8.1.1. サービスの JWK Set
サービスの JWK Set として次の内容を持つ service.jwks
ファイルを用意する。
{
"keys": [
{
"kty":"EC",
"crv":"P-256",
"x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
"y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
"d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"
}
]
}
同じ内容を、サービスの JWK Set として Authlete に登録する。具体的には、Authlete のサービスオーナーコンソール( https://so.authlete.com/ )でサービスの編集画面を開き、『JWK セット』タブの『JWK セットの内容』に上記内容を貼り付けて保存する。
8.1.2. クライアントの JWK セット
クライアントの JWS Set として次の内容を持つ client.jwks
ファイルを用意する。
{
"keys": [
{
"kty": "RSA",
"d": "FtRc49AydjACyZcDiBXW-WZu-CaDA-9INUzR63DYHmKa27CxnRTAjJmKdEcQIthftclx5L-mK_k7i7vRXb4kgzqKLaUxsCrltiZK2Q2s7PK1J_eSyUmklcOm22OdiqQxjXL5fQzGFjpA9DLbkR8ARlRlwo4E_3tnBf5848N3BOH3tvrw4BHY1GPOP12_7FWMPu9nNi2wkpyrfhzc0R6nduDR2BshfdliKK8fvo1BKr0aaOAoyxSA7TQ03ySpSWRjEAUdgAgHkBMThsJZ4E6P6BP-3RMXrMEsIM8HQOO46IU55qRxeWFXBtsbN8SqiNBguM4suXeY403YaPqDf2tpkQ",
"e": "AQAB",
"n": "gcZ7r2BDpQPRlTwLcA3Vv9vyjos0vxoMq3HIXEGJ_HaaBQIjbxlfOOnAfXJi8WDZCLUvR0opmoKv7bBl0mFaEjs6vGqoTHyREMPHw0JTU5fciBZxkylGqjpS5EbPT3ciKlWN0rU3jn8JwgtRvFUIAEgI29LH6--JA0w0XVtvUh5wDaXQUCSWMG3MaWhAxpyQSlr9C2xKwNSQSwIuP1zw1sf9fJbjUa2X-kNj5na8JbuRWMAmz95CZa8p7LDtUcxHK9zqznqOXcQHat12s_2MpkX-bO0LRFapg6nsgV6bw3e-ond9xFXY85k5OhAgU4Ex_SAZvIWr4eZ3kt5oeFUEIw"
}
]
}
また、公開鍵だけを含む client-public.jwks
ファイルを次の内容で用意する。
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"n": "gcZ7r2BDpQPRlTwLcA3Vv9vyjos0vxoMq3HIXEGJ_HaaBQIjbxlfOOnAfXJi8WDZCLUvR0opmoKv7bBl0mFaEjs6vGqoTHyREMPHw0JTU5fciBZxkylGqjpS5EbPT3ciKlWN0rU3jn8JwgtRvFUIAEgI29LH6--JA0w0XVtvUh5wDaXQUCSWMG3MaWhAxpyQSlr9C2xKwNSQSwIuP1zw1sf9fJbjUa2X-kNj5na8JbuRWMAmz95CZa8p7LDtUcxHK9zqznqOXcQHat12s_2MpkX-bO0LRFapg6nsgV6bw3e-ond9xFXY85k5OhAgU4Ex_SAZvIWr4eZ3kt5oeFUEIw"
}
]
}
client-public.jwks
の内容を、クライアントの JWK Set として Authlete に登録する。具体的には、デベロッパーコンソール( https://cd.authlete.com/{service-api-key} )でアプリの編集画面を開き、『JWK セット』タブの『JWK セットの内容』に client-public.jwks
の内容を貼り付けて保存する。
8.1.3. ペイロード
JOSE のペイロード部分の内容を持つ payload.json
ファイルを用意する。
$ CLIENT_ID={クライアントID} # デベロッパーコンソールで値を確認できる
$ printf '{"exp":%d,"iss":"%s"}' `date +%s` ${CLIENT_ID} > payload.json
上記コマンドを実行すると、例えば次のような内容のファイルが生成される。
{"exp":1532158923,"iss":"7688853985532"}
ちなみに exp
(Expiration Time)の値には現在時刻(「date +%s
」コマンドの実行結果)がセットされるので、生成時点で既に有効期限切れとなる。
8.1.4. authlete-generator コマンド
JOSE 生成処理をおこなうツールは幾つも存在するが、どれも使い勝手がいまいちなので、jose-generator を自作した(ただし完成はしていない)。以降、jose-generator を使用するので、あらかじめダウンロード、コンパイル、設定をしておく。
$ git clone https://github.com/authlete/authlete-jose
$ cd authlete-jose
$ mvn compile
$ . ./bin/jose-generator-completion
$ cd ..
8.2. 実験
8.2.1. 疎通確認
まず、Authlete の /api/jose/verify
API と通信できることを確認する。
$ API_KEY={サービスのAPIキー} # サービスオーナーコンソールで値を確認できる。
$ API_SEC={サービスのAPIシークレット} # サービスオーナーコンソールで値を確認できる。
$ curl --user $API_KEY:$API_SEC https://api.authlete.com/api/jose/verify -d jose=
jose
リクエストパラメーターの値が空だというエラーメッセージを含む下記のような JSON が返ってくれば、疎通確認は成功である。
{
"type":"joseVerifyResponse",
"resultCode":"A160201",
"resultMessage":"[A160201] The 'jose' request parameter is missing or empty.",
"errorDescriptions":[
"[A160201] The 'jose' request parameter is missing or empty."
],
"signatureValid":false,
"valid":false
}
8.2.2. Unsecured JWS
一番簡単な例として、署名無しの JWS、いわゆる Unsecured JWS を生成してみる。
$ ./authlete-jose/bin/jose-generator --payload-file payload.json
eyJhbGciOiJub25lIn0.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ.
生成された Unsecured JWS を /api/jose/verify
API に渡す。
$ curl --user $API_KEY:$API_SEC https://api.authlete.com/api/jose/verify \
-d jose=eyJhbGciOiJub25lIn0.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ.
次のような応答が返ってくる。
{
"type":"joseVerifyResponse",
"resultCode":"A160206",
"resultMessage":"[A160206] The expiration time of the JOSE has been reached: exp = 1532158923, current time = 1532161214, clock skew = 0",
"errorDescriptions":[
"[A160206] The expiration time of the JOSE has been reached: exp = 1532158923, current time = 1532161214, clock skew = 0"
],
"invalidClaims":[
"exp"
],
"signatureValid":false,
"valid":false
}
exp
クレームの値は生成した時点で期限切れなので、JOSE オブジェクトは無効と判断される。そのため、レスポンスの valid
パラメーターの値は false
となる。また、exp
クレームの値が無効なので、invalidClaims
に exp
が含まれている。
8.2.3. Clock Skew
API 呼び出し側のシステム時刻と Authlete サーバーが動いているマシンのシステム時刻の間に、差分があるかもしれない。この差分は、exp
クレーム、iat
クレーム、nbf
クレームの値の検証に影響を与えてしまう。
/api/jose/verify
API は、オプショナルパラメーター clockSkew
を解釈する。これは、時刻差の許容秒数を指定するリクエストパラメーターだ。例えば、clockSkew
に 3600 という値を与えると、現在時刻が exp
を過ぎていても、それが 1 時間以内であれば許容される。
例えば、先の Unsecured JWS の検証時に -d clockSkew=3600
というパラメーターを与えると、
$ curl --user $API_KEY:$API_SEC https://api.authlete.com/api/jose/verify \
-d jose=eyJhbGciOiJub25lIn0.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ. \
-d clockSkew=3600
JOSE は有効と判断される。(もちろん payload.json
を生成した時刻からまだ 1 時間経っていないことが前提)
{
"type":"joseVerifyResponse",
"resultCode":"A160001",
"resultMessage":"[A160001] The JOSE is valid.",
"signatureValid":false,
"valid":true
}
8.2.4. サービスの鍵で署名
サービス側の鍵で署名してみる。service.jwks
の JWK Set には Elliptic Curve の鍵が一つだけ入っているので、署名アルゴリズムとして ES256
を指定する。
$ ./authlete-jose/bin/jose-generator \
--payload-file payload.json \
-s --signing-alg ES256 \
--jwks-signing-file service.jwks
eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ.688djW8JIOXE5KfoM2Ce_xnJOvUWhoH_z0248WMtbQpnQL4ySVDDBZQBHEZ8wWmHDXbKB4m1WSkehlt-lc2tRg
生成された JOSE オブジェクトを /api/jose/verify
API に渡す。
$ curl --user $API_KEY:$API_SEC https://api.authlete.com/api/jose/verify \
-d jose=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ.688djW8JIOXE5KfoM2Ce_xnJOvUWhoH_z0248WMtbQpnQL4ySVDDBZQBHEZ8wWmHDXbKB4m1WSkehlt-lc2tRg
次のような結果が返ってくる。
{
"type":"joseVerifyResponse",
"resultCode":"A160206",
"resultMessage":"[A160206] The expiration time of the JOSE has been reached: exp = 1532158923, current time = 1532162935, clock skew = 0",
"errorDescriptions":[
"[A160206] The expiration time of the JOSE has been reached: exp = 1532158923, current time = 1532162935, clock skew = 0"
],
"invalidClaims":[
"exp"
],
"signatureValid":true,
"valid":false
}
exp
クレームが無効(有効期限切れ)なので、JOSE オブジェクト全体としては無効であるが(valid=false
)、署名自体は有効である(signatureValid=true
)ことが分かる。
8.2.5. クライアントの鍵で署名
クライアント側の鍵で署名してみる。client.jwks
の JWK Set には RSA の鍵が一つだけ入っているので、署名アルゴリズムとして RS256
を指定する。
$ ./authlete-jose/bin/jose-generator \
--payload-file payload.json \
-s --signing-alg RS256 \
--jwks-signing-file client.jwks
eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ.eDvnD_l1YvjvKaNQgeJwnWu55rnuGGDJrwUD9M_wgZYKxitEP55f9aYLE4aaHUE9uS2FCz72VUJGa4FPbA4EkNwB7-Xfj8Dut9EJZRhy8L5BvTpjP2nPIIyhGUkLrbZwa4Adc78G6Q2WuVS6gYPXXDbqXGU3ciDzCboejrC87S3Ymjurb_swO5ylVb6gKJNvXg5cMqlyZ0NWdqcF8ekyiyo5o5bcqvhwX64ROyZrCaoB1ndQ8hc4RzYP83K5doQfEH01rwRZlU9LGSBUuTaPujny-JPBBIINEFv23h99u9xs4Onj3T5yuhwNeIJx1FKTmtBZJOE0mefQptGb35Bg_g
生成された JOSE オブジェクトを /api/jose/verify
API に渡す。今回は exp
クレームに関するエラー表示を避けるため、-d clockSkew=86400
を渡している。
$ curl --user $API_KEY:$API_SEC https://api.authlete.com/api/jose/verify \
-d jose=eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ.eDvnD_l1YvjvKaNQgeJwnWu55rnuGGDJrwUD9M_wgZYKxitEP55f9aYLE4aaHUE9uS2FCz72VUJGa4FPbA4EkNwB7-Xfj8Dut9EJZRhy8L5BvTpjP2nPIIyhGUkLrbZwa4Adc78G6Q2WuVS6gYPXXDbqXGU3ciDzCboejrC87S3Ymjurb_swO5ylVb6gKJNvXg5cMqlyZ0NWdqcF8ekyiyo5o5bcqvhwX64ROyZrCaoB1ndQ8hc4RzYP83K5doQfEH01rwRZlU9LGSBUuTaPujny-JPBBIINEFv23h99u9xs4Onj3T5yuhwNeIJx1FKTmtBZJOE0mefQptGb35Bg_g \
-d clockSkew=86400
結果は次のとおりで、署名が不正(signatureValid=false
)という判定となっている。
{
"type":"joseVerifyResponse",
"resultCode":"A160221",
"resultMessage":"[A160221] Could not find any appropriate JWK for signature verification in the service's JWK Set. (alg = RS256, kid = null)",
"errorDescriptions":[
"[A160221] Could not find any appropriate JWK for signature verification in the service's JWK Set. (alg = RS256, kid = null)"
],
"signatureValid":false,
"valid":false
}
エラーメッセージによると、署名検証に使えそうな JWK がサービスの JWK Set 内に見つからなかったことが原因のようだ。
それはそうである。なぜなら、署名はクライアントの秘密鍵を用いておこなったのだから、署名の検証はクライアントの公開鍵を用いておこなう必要があるのだ。/api/jose/verify
API の実装は、サービスの JWK Set ではなく、クライアントの JWK Set を見なければならない。
signedByClient=true
というリクエストパラメーターを与えると、「JOSE オブジェクトはクライアントの鍵を使って署名された」ということを /api/jose/verify
API の実装に伝えることができる。これにより、クライアントの JWK Set が調べられることになる。ただしこの際、どのクライアントの JWK Set を調べればよいのかを /api/jose/verify
API の実装は知る必要があるので、clientIdentifier
リクエストパラメーターによりクライアント識別子の値も渡す必要がある。
コマンドラインは次のようになる。
$ curl --user $API_KEY:$API_SEC https://api.authlete.com/api/jose/verify \
-d jose=eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ.eDvnD_l1YvjvKaNQgeJwnWu55rnuGGDJrwUD9M_wgZYKxitEP55f9aYLE4aaHUE9uS2FCz72VUJGa4FPbA4EkNwB7-Xfj8Dut9EJZRhy8L5BvTpjP2nPIIyhGUkLrbZwa4Adc78G6Q2WuVS6gYPXXDbqXGU3ciDzCboejrC87S3Ymjurb_swO5ylVb6gKJNvXg5cMqlyZ0NWdqcF8ekyiyo5o5bcqvhwX64ROyZrCaoB1ndQ8hc4RzYP83K5doQfEH01rwRZlU9LGSBUuTaPujny-JPBBIINEFv23h99u9xs4Onj3T5yuhwNeIJx1FKTmtBZJOE0mefQptGb35Bg_g \
-d clockSkew=86400 \
-d signedByClient=true \
-d clientIdentifier=$CLIENT_ID
結果は次の通り。
{
"type":"joseVerifyResponse",
"resultCode":"A160001","resultMessage":"[A160001] The JOSE is valid.",
"signatureValid":true,
"valid":true
}
なお、JOSE オブジェクトのペイロード部分が JSON であり、その JSON が iss
クレームを持ち、その値がクライアント識別子である場合、clientIdentifier
リクエストパラメーターを省略できる。
payload.json
生成時にクライアント ID を iss
クレームに設定しているはずなので、次のコマンドラインでも署名検証に成功するはずである。
$ curl --user $API_KEY:$API_SEC https://api.authlete.com/api/jose/verify \
-d jose=eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ.eDvnD_l1YvjvKaNQgeJwnWu55rnuGGDJrwUD9M_wgZYKxitEP55f9aYLE4aaHUE9uS2FCz72VUJGa4FPbA4EkNwB7-Xfj8Dut9EJZRhy8L5BvTpjP2nPIIyhGUkLrbZwa4Adc78G6Q2WuVS6gYPXXDbqXGU3ciDzCboejrC87S3Ymjurb_swO5ylVb6gKJNvXg5cMqlyZ0NWdqcF8ekyiyo5o5bcqvhwX64ROyZrCaoB1ndQ8hc4RzYP83K5doQfEH01rwRZlU9LGSBUuTaPujny-JPBBIINEFv23h99u9xs4Onj3T5yuhwNeIJx1FKTmtBZJOE0mefQptGb35Bg_g \
-d clockSkew=86400 \
-d signedByClient=true
8.2.6. 必須パラメーター
JOSE オブジェクトのペイロードが含んでいるべき必須パラメーターを mandatoryClaims
リクエストパラメーターで指定することができる。
payload.json
には exp
クレームと iss
クレームしか含まれていないが、iat
クレームと abcdefg
クレームを必須クレームと指定して /api/jose/verify
API を呼び出してみよう。(ここでは Unsecured JWS を使う)
$ curl --user $API_KEY:$API_SEC https://api.authlete.com/api/jose/verify \
-d jose=eyJhbGciOiJub25lIn0.eyJleHAiOjE1MzIxNTg5MjMsImlzcyI6Ijc2ODg4NTM5ODU1MzIifQ. \
-d mandatoryClaims="iat abcdefg"
結果は次のようになる。missingClaims
に abcdefg
と iat
がリストされていることが分かる。
{
"type":"joseVerifyResponse",
"resultCode":"A160204",
"resultMessage":"[A160204] The mandatory claim 'abcdefg' is missing.",
"errorDescriptions":[
"[A160204] The mandatory claim 'abcdefg' is missing.",
"[A160204] The mandatory claim 'iat' is missing.",
"[A160206] The expiration time of the JOSE has been reached: exp = 1532158923, current time = 1532165493, clock skew = 0"
],
"invalidClaims":[
"exp"
],
"missingClaims":[
"abcdefg",
"iat"
],
"signatureValid":false,
"valid":false
}
おわりに
/api/jose/verify
API により、「サービスのキーもしくはクライアントのキーを用いて署名された JOSE オブジェクトの検証」をすることができる。
この API は、とあるお客様から要望を受け、とある特殊なユースケースのために作成したものなので、他のユーザーにとって利用価値があるかどうかは不明である。しかし、それなりに汎用的なので、もしかしたら他の誰かも使ってくれるかもしれない。
試してないが、ID トークンの署名の検証も、たぶんこの API でできる。