LoginSignup
1
1

More than 5 years have passed since last update.

Authlete /api/jose/verify API

Posted at

はじめに

これは、Authlete/api/jose/verify API の解説文書である。

(諸般の事情で日本語の技術情報を自社ウェブサイト上で公開する準備が整っていないので Qiita で技術情報を提供している。。。 orz... )

1. 用途

JOSE(JavaScript Object Signing and Encryption)オブジェクトの検証をおこなう。

2. 検証項目

  1. 署名検証(署名されている場合)
  2. 必須クレームの存在有無(必須と指定されたクレーム群がある場合)
  3. 時刻関連の標準クレーム群のチェック(ペイロードが 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 解析

  1. jose リクエストパラメーターが指定されているかチェックする。指定されていなかったり、空文字列であれば、検証処理終了。処理結果コード A160201 でレスポンスを返す。検証結果 validfalse となる。

  2. jose リクエストパラメーターの値を JOSE オブジェクトとみなしてパースする。パースに失敗した場合は検証処理終了。処理結果コード A160202 でレスポンスを返す。検証結果 validfalse となる。

  3. JOSE オブジェクトが JWE の場合、検証処理終了。処理結果コード A160101 でレスポンスを返す。検証結果 validfalse となる。(将来 JWE に対応する可能性はあるが、現時点では対応していない)

6.2. ペイロード検証

  1. ペイロードを JSON としてパースする。(パースに失敗してもエラーとはみなさない)

  2. ペイロードが JSON としてパースできなかったが、mandatoryClaims リクエストパラメーターで必須クレーム群が指定されていた場合、それらのクレーム群を missingClaims レスポンスパラメーターにセットし、A160203 を処理結果コードとしてセットする。検証結果 validfalse となる。

  3. ペイロードが JSON としてパースできた場合、mandatoryClaims リクエストパラメーターで必須クレーム群が指定されていれば、それらが JSON 内に含まれているかどうかチェックする。含まれていない場合は、当該クレームの名前を missingClaims レスポンスパラメーターに追加する。必須クレームと指定されたのに含まれていないクレームが一つでもある場合、処理結果コード に A160204 がセットされ、検証結果 validfalse となる。

  4. ペイロードが JSON としてパースできた場合、exp(Expiration Time)クレーム、iat(Issued At)クレーム、nbf(Not Before)クレーム、が存在していれば、それらの値をチェックする。これらのクレーム群は時刻に関するもので、値のチェックに際しては、clockSkew リクエストパラメーターの値が考慮される。

  5. exp クレームの値が数値ではない場合、処理結果コードとして A160205 がセットされ、invalidClaims レスポンスパラメーターに exp が追加され、検証結果 validfalse となる。exp クレームが数値であれば、その値が現在時刻より未来かどうかがチェックされる。現在時刻以前(過去)であれば、それは現在時刻が JOSE オブジェクトの有効期限を過ぎているということを意味しており、処理結果コードとして 160206 がセットされ、invalidClaims レスポンスパラメーターに exp が追加され、検証結果 validfalse となる。

  6. iat クレームの値が数値ではない場合、処理結果コードとして A160205 がセットされ、invalidClaims レスポンスパラメーターに iat が追加され、検証結果 validfalse となる。iat クレームが数値であれば、その値が現在時刻以前かどうかがチェックされる。現在時刻より後(未来)であれば、それは JOSE オブジェクトの発行時刻が未来ということを意味しており、処理結果コードとして 160207 がセットされ、invalidClaims レスポンスパラメーターに iat が追加され、検証結果 validfalse となる。

  7. nbf クレームの値が数値ではない場合、処理結果コードとして A160205 がセットされ、invalidClaims レスポンスパラメーターに nbf が追加され、検証結果 validfalse となる。nbf クレームが数値であれば、その値が現在時刻以前かどうかがチェックされる。現在時刻より後(未来)であれば、それは JOSE オブジェクトがまだ有効になっていないということを意味しており、処理結果コードとして 160208 がセットされ、invalidClaims レスポンスパラメーターに nbf が追加され、検証結果 validfalse となる。

6.3. 署名検証

  1. JOSE オブジェクトに署名がついている場合、署名の検証をおこなう。

  2. 署名検証時、まず、JWS ヘッダーから alg パラメーターを取り出す。

  3. alg パラメーターの示すアルゴリズムが対称鍵系(HS256HS384HS512)の場合、OpenID Connect Core 1.0 の 10.1. Signing, Symmetric Signatures のルールに基づいて共有鍵を計算するため、クライアントアプリケーションのクライアントシークレットの値が必要となる。よって、クライアントアプリケーションを特定する必要がある。/api/jose/verify API の実装は、clientIdentifier リクエストパラメーターが指定されていれば、その値をクライアント識別子として用いる。もしも clientIdentifier リクエストパラメーターが指定されていない場合、signedByClient リクエストパラメーターの値が true であり、かつペイロードが JSON としてパースできる場合のみ、ペイロードの JSON に含まれる iss クレームの値をクライアント識別子とみなす。iss を参照する際、その値が文字列でない場合、処理結果コードに A160214 がセットされ、検証結果 validfalse となる。ここまでの処理でクライアント識別子を特定できていなければ、処理結果コードに A160215 がセットされ、検証結果 validfalse となる。

  4. (対称鍵系の続き)上記処理で特定されたクライアント識別子に該当するクライアントが存在しない場合(他のサービス下にあるクライアントであったり、ロックされていたりする場合も含む)、処理結果コードに A160216 がセットされ、検証結果 validfalse となる。

  5. (対称鍵系の続き)上記処理で特定されたクライアントのクライアントシークレットを用いて署名の検証をおこなう。署名が不正であれば、処理結果コードに A160219 がセットされ、検証結果 validfalse になる。一方、署名が有効であれば、処理結果コードに A160001 がセットされ、検証結果 validtrue となる。ここで検証処理は終わり、レスポンスが返される。

  6. alg パラメーターの示すアルゴリズムが非対称鍵系の場合、署名検証に用いるための公開鍵が必要となる。signedByClient リクエストパラメーターの値が false(デフォルト)の場合、JOSE オブジェクトの署名はサーバーの秘密鍵を用いておこなわれたとみなされ、/api/jose/verify API の実装はサーバーの JWK Set ドキュメントを取得しようとする。このとき、サーバーの JWK Set ドキュメントが登録されていなければ、処理結果コードとして A160218 がセットされ、検証結果 validfalse となる。一方、signedByClient リクエストパラメーターの値が true の場合、JOSE オブジェクトの署名はクライアントの秘密鍵を用いておこなわれたとみなされ、/api/jose/verify API の実装はクライアントの JWK Set ドキュメントを取得しようとする。このとき、まず、クライアント識別子の特定がおこなわれるが、この処理は対称鍵系アルゴリズムの際にクライアントシークレットを求めるためにおこなったのと同じ手順であるため、処理結果コードとして A160214A160215A160216 が発生する可能性がある。クライアント識別子の特定後、当該クライアントの JWK Set ドキュメントの取得が試みられるが、このとき、クライアントの JWK Set ドキュメントが登録されていなえれば、処理結果コードとして A160301 が設定され、検証結果 validfalse となる。

  7. (非対称鍵系の続き)上記処理で求められた JWK Set ドキュメントから、署名検証に用いるための公開鍵を取り出す。公開鍵の検索時、JWK の kty(必須)、kidalgusekey_ops が考慮される。署名検証に用いることが可能な JWK が複数存在する場合、基本的には、より制限項目の多いものが選ばれる。例えば、kty しか持たない JWK よりも、kty に加えて alguse も持っている JWK のほうが選ばれる。適切な JWK が存在しない場合、処理結果コードとして A160221 がセットされ、検証結果 validfalse となる。逆に、利用可能な JWK が複数存在し、それらのうち一つに絞り込むことができなかった場合、処理結果コードとして A160220 がセットされ、検証結果 validfalse となる。

  8. (非対称鍵系の続き)上記処理で特定された JWK を用いて署名の検証をおこなう。署名が不正であれば、処理結果コードに A160219 がセットされ、検証結果 validfalse になる。一方、署名が有効であれば、処理結果コードに A160001 がセットされ、検証結果 validtrue となる。ここで検証処理は終わり、レスポンスが返される。

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 クレームの値が無効なので、invalidClaimsexp が含まれている。

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"

結果は次のようになる。missingClaimsabcdefgiat がリストされていることが分かる。

{
  "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 でできる。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1