OpenID ConnectのJWTとJWKを手軽につくりたい

OpenID ConnectのJWT(IDトークン)とJWKを、ペイロードの値を色々変えながら作りたいと思って、シェル上で作るスクリプトを作成しました。

## 必要なツール
* openssl
* nodejs
* jq

## 結論
```bash
#/bin/sh
header='
{
  "kid": "12345",
  "alg": "RS256"
}'
payload='
{
  "iss": "https://example.com",
  "sub": "user-id-123",
  "aud": "client-app-id-123",
  "exp": 1735689600,
  "iat": 1563980400
}'

function pack() {
  # Remove return and space
  echo $1 | sed -e "s/[\r\n]\+//g" | sed -e "s/ //g"
}

if [ ! -f private-key.pem ]; then
  # Private and Public keys
  openssl genrsa 2048 > private-key.pem
  openssl rsa -in private-key.pem -pubout -out public-key.pem
fi

# Base64 Encoding
b64_header=$(pack "$header" | openssl enc -e -A -base64)
b64_payload=$(pack "$payload" | openssl enc -e -A -base64)
signature=$(echo -n $b64_header.$b64_payload | openssl dgst -sha256 -sign private-key.pem | openssl enc -e -A -base64)
# Export JWT 
echo $b64_header.$b64_payload.$signature > jwt.txt
# Create JWK from public key
if [ ! -d ./node_modules/pem-jwk ]; then
  # A tool to convert PEM to JWK
  npm install pem-jwk
fi
jwk=$(./node_modules/.bin/pem-jwk public-key.pem)
# Add additional fields
jwk=$(echo '{"use":"sig"}' $jwk $header | jq -cs add)
# Export JWK
echo '{"keys":['$jwk']}'| jq . > jwks.json

echo "--- JWT ---"
cat jwt.txt
echo -e "\n--- JWK ---"
jq . jwks.json
```
[Generate private and public keys, and create JWT and JWKs](https://gist.github.com/shu-yusa/213901a5a0902de5ad3f62a61036f4ce)

## 解説

### 秘密鍵と公開鍵の作成
```bash
if [ ! -f private-key.pem ]; then
  # Private and Public keys
  openssl genrsa 2048 > private-key.pem
  openssl rsa -in private-key.pem -pubout -out public-key.pem
fi
```

### 署名の作成
ヘッダとペイロードをBase64エンコードして、上記で作成した秘密鍵で署名を作成し、Base64エンコード。`pack()`は改行文字とスペースの削除用に定義した関数。

```bash
function pack() {
  # Remove return and space
  echo $1 | sed -e "s/[\r\n]\+//g" | sed -e "s/ //g"
}

b64_header=$(pack "$header" | openssl enc -e -A -base64)
b64_payload=$(pack "$payload" | openssl enc -e -A -base64)
signature=$(echo -n $b64_header.$b64_payload | openssl dgst -sha256 -sign private-key.pem | openssl enc -e -A -base64)
```

### JWT（IDトークン)を作成

```bash
echo $b64_header.$b64_payload.$signature > jwt.txt
```

### PEM形式の公開鍵をJWKに変換
[pem-jwk - npm](https://www.npmjs.com/package/pem-jwk)を使いました。

```bash
if [ ! -d ./node_modules/pem-jwk ]; then
  # A tool to convert PEM to JWK
  npm install pem-jwk
fi
jwk=$(./node_modules/.bin/pem-jwk public-key.pem)
# Add additional fields
jwk=$(echo '{"use":"sig"}' $jwk $header | jq -cs add)
# Export JWK
echo '{"keys":['$jwk']}'| jq . > jwks.json
```

## まとめ
出力サンプル

```bash
--- JWT ---
eyJraWQiOiIxMjM0NSIsImFsZyI6IlJTMjU2In0K.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoidXNlci1pZC0xMjMiLCJhdWQiOiJjbGllbnQtYXBwLWlkLTEyMyIsImV4cCI6MTczNTY4OTYwMCwiaWF0IjoxNTYzOTgwNDAwfQo=.J7GjvEODv1NeEJ3uOK99unlrtOtqMdJNydYmSStdLuiq5h+ut6E0edMjTqgJZfOMNr3vM23EJXOmk+kit/j1zGJBHgev0DoTIWrrN+fjeE7seCVZGf0eblC9VZbq9FU0t8WQz4vQeZpy1oRLIUPj6go0gw5ed8Yo+pBZi+/nPJsmy450ChfFpE1cx58VQ4zM9eEnQz4jKInevyR5cTn1iYuLhDhPQmRDdlLg3IuxgRjwxsLBjZjtiyn6mntZHOeTSAlLfmoWKNiMtN1qNoEf2e9s3pqctIwRQQKsPAfO64q//te8jGGxzTFLUV2JP23AxwzrOEcfuZyCLB5u4RKI/Q==

--- JWK ---
{
  "keys": [
    {
      "use": "sig",
      "kty": "RSA",
      "n": "4iNwSXpIvLz4DbG5HUyy5jUOnsdOD99dCA56oDWU6PSV3lTbPBWyngrdcpoC50MT_yqBMiQvaNRZRgcH82jsRrs_HPGDSMO1hEv7LUlwJy5MWBOcKeGLpAL9P9FXKnjvx2dQpWlXZC3D5KsdHc8dsMnxR8D3-T5XWDGwqnEMLWoG1uRPG7lual2PhrX3wpZMY3gmgx4WnElxv8DZs9GArvMzsojtPpgJ1cmnlzDzZbQWd5lJwW_k7S8jcEiBEwfoITzY1umQKWetMe9id1-ohH1tbe7vXpxpm59j4Fo5HZoTA3VUR6Jkbnjqf6TPhEOwJUnf-DMyewZXW2dxjvTPPw",
      "e": "AQAB",
      "kid": "12345",
      "alg": "RS256"
    }
  ]
}
```
各種クレームの値を変えて、IDトークンの検証をテストしたいときなどに便利かもしれないです。`iss`, `exp`, `aud`などの値を変えてトークンの検証が失敗するテストケースなど。

## 参考
* [[前編] IDトークンが分かれば OpenID Connect が分かる - Qiita](https://qiita.com/TakahikoKawasaki/items/8f0e422c7edd2d220e06)
* [OpenID Connect の JWT の署名を自力で検証してみると見えてきた公開鍵暗号の実装の話 - Qiita](https://qiita.com/bobunderson/items/d48f89e2b3e6ad9f9c4c)
* [JSON Web Key (JWK)](https://openid-foundation-japan.github.io/rfc7517.ja.html#useDef)
* [【OpenID Connect】公開されているJWKを使用してIDTokenを検証するやり方 - さっしーブログ](http://www.sassy-blog.com/entry/20171003/1506991888)