Skip to content

Commit

Permalink
feat(jwt-auth): support ES256 digital algorithm (#7627)
Browse files Browse the repository at this point in the history
Co-authored-by: qihaiyan <qihaiyan@hisense.com>
  • Loading branch information
qihaiyan and qihaiyan authored Aug 9, 2022
1 parent 381981e commit 535812d
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 21 deletions.
24 changes: 13 additions & 11 deletions apisix/plugins/jwt-auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ local consumer_schema = {
secret = {type = "string"},
algorithm = {
type = "string",
enum = {"HS256", "HS512", "RS256"},
enum = {"HS256", "HS512", "RS256", "ES256"},
default = "HS256"
},
exp = {type = "integer", minimum = 1, default = 86400},
Expand Down Expand Up @@ -94,7 +94,7 @@ local consumer_schema = {
public_key = {type = "string"},
private_key= {type = "string"},
algorithm = {
enum = {"RS256"},
enum = {"RS256", "ES256"},
},
},
required = {"public_key", "private_key"},
Expand All @@ -106,7 +106,7 @@ local consumer_schema = {
properties = {}
},
algorithm = {
enum = {"RS256"},
enum = {"RS256", "ES256"},
},
},
required = {"vault"},
Expand Down Expand Up @@ -166,15 +166,15 @@ function _M.check_schema(conf, schema_type)
return true
end

if conf.algorithm ~= "RS256" and not conf.secret then
if conf.algorithm ~= "RS256" and conf.algorithm ~= "ES256" and not conf.secret then
conf.secret = ngx_encode_base64(resty_random.bytes(32, true))
elseif conf.base64_secret then
if ngx_decode_base64(conf.secret) == nil then
return false, "base64_secret required but the secret is not in base64 format"
end
end

if conf.algorithm == "RS256" then
if conf.algorithm == "RS256" or conf.algorithm == "ES256" then
-- Possible options are a) both are in vault, b) both in schema
-- c) one in schema, another in vault.
if not conf.public_key then
Expand Down Expand Up @@ -240,7 +240,7 @@ local function get_secret(conf, consumer_name)
end


local function get_rsa_keypair(conf, consumer_name)
local function get_rsa_or_ecdsa_keypair(conf, consumer_name)
local public_key = conf.public_key
local private_key = conf.private_key
-- if keys are present in conf, no need to query vault (fallback)
Expand Down Expand Up @@ -309,8 +309,10 @@ local function sign_jwt_with_HS(key, consumer, payload)
end


local function sign_jwt_with_RS256(key, consumer, payload)
local public_key, private_key, err = get_rsa_keypair(consumer.auth_conf, consumer.username)
local function sign_jwt_with_RS256_ES256(key, consumer, payload)
local public_key, private_key, err = get_rsa_or_ecdsa_keypair(
consumer.auth_conf, consumer.username
)
if not public_key then
core.log.error("failed to sign jwt, err: ", err)
core.response.exit(503, "failed to sign jwt")
Expand Down Expand Up @@ -345,12 +347,12 @@ local function algorithm_handler(consumer, method_only)
end

return get_secret(consumer.auth_conf, consumer.username)
elseif consumer.auth_conf.algorithm == "RS256" then
elseif consumer.auth_conf.algorithm == "RS256" or consumer.auth_conf.algorithm == "ES256" then
if method_only then
return sign_jwt_with_RS256
return sign_jwt_with_RS256_ES256
end

local public_key, _, err = get_rsa_keypair(consumer.auth_conf, consumer.username)
local public_key, _, err = get_rsa_or_ecdsa_keypair(consumer.auth_conf, consumer.username)
return public_key, err
end
end
Expand Down
8 changes: 4 additions & 4 deletions docs/en/latest/plugins/jwt-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ For Consumer:
|---------------|---------|-------------------------------------------------------|---------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| key | string | True | | | Unique key for a Consumer. |
| secret | string | False | | | The encryption key. If unspecified, auto generated in the background. |
| public_key | string | True if `RS256` is set for the `algorithm` attribute. | | | RSA public key. |
| private_key | string | True if `RS256` is set for the `algorithm` attribute. | | | RSA private key. |
| algorithm | string | False | "HS256" | ["HS256", "HS512", "RS256"] | Encryption algorithm. |
| public_key | string | True if `RS256` or `ES256` is set for the `algorithm` attribute. | | | RSA or ECDSA public key. |
| private_key | string | True if `RS256` or `ES256` is set for the `algorithm` attribute. | | | RSA or ECDSA private key. |
| algorithm | string | False | "HS256" | ["HS256", "HS512", "RS256", "ES256"] | Encryption algorithm. |
| exp | integer | False | 86400 | [1,...] | Expiry time of the token in seconds. |
| base64_secret | boolean | False | false | | Set to true if the secret is base64 encoded. |
| vault | object | False | | | Set to true to use Vault for storing and retrieving secret (secret for HS256/HS512 or public_key and private_key for RS256). By default, the Vault path is `kv/apisix/consumer/<consumer_name>/jwt-auth`. |
| vault | object | False | | | Set to true to use Vault for storing and retrieving secret (secret for HS256/HS512 or public_key and private_key for RS256/ES256). By default, the Vault path is `kv/apisix/consumer/<consumer_name>/jwt-auth`. |
| lifetime_grace_period | integer | False | 0 | [0,...] | Define the leeway in seconds to account for clock skew between the server that generated the jwt and the server validating it. Value should be zero (0) or a positive integer. |

:::info IMPORTANT
Expand Down
8 changes: 4 additions & 4 deletions docs/zh/latest/plugins/jwt-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ Consumer 端:
| ------------- | ------- | ----- | ------- | --------------------------- | ------------------------------------------------------------------------------------------------------------ |
| key | string || | | Consumer 的 `access_key` 必须是唯一的。如果不同 Consumer 使用了相同的 `access_key` ,将会出现请求匹配异常。 |
| secret | string || | | 加密秘钥。如果未指定,后台将会自动生成。 |
| public_key | string || | | RSA 公钥, `algorithm` 属性选择 `RS256` 算法时必选。 |
| private_key | string || | | RSA 私钥, `algorithm` 属性选择 `RS256` 算法时必选。 |
| algorithm | string || "HS256" | ["HS256", "HS512", "RS256"] | 加密算法。 |
| public_key | string || | | RSA 或 ECDSA 公钥, `algorithm` 属性选择 `RS256``ES256` 算法时必选。 |
| private_key | string || | | RSA 或 ECDSA 私钥, `algorithm` 属性选择 `RS256``ES256` 算法时必选。 |
| algorithm | string || "HS256" | ["HS256", "HS512", "RS256", "ES256"] | 加密算法。 |
| exp | integer || 86400 | [1,...] | token 的超时时间。 |
| base64_secret | boolean || false | | 当设置为 `true` 时,密钥为 base64 编码。 |
| vault | object || | | 是否使用 Vault 作为存储和检索密钥(HS256/HS512 的密钥或 RS256 的公钥和私钥)的方式。该插件默认使用 `kv/apisix/consumer/<consumer name>/jwt-auth` 路径进行密钥检索。 |
| vault | object || | | 是否使用 Vault 作为存储和检索密钥(HS256/HS512 的密钥或 RS256/ES256 的公钥和私钥)的方式。该插件默认使用 `kv/apisix/consumer/<consumer name>/jwt-auth` 路径进行密钥检索。 |
| lifetime_grace_period | integer || 0 | [0,...] | 定义生成 JWT 的服务器和验证 JWT 的服务器之间的时钟偏移。该值应该是零(0)或一个正整数。 |

:::info IMPORTANT
Expand Down
95 changes: 93 additions & 2 deletions t/plugin/jwt-auth.t
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ property "key" is required
local code, body, raw = t('/apisix/admin/schema/plugins/jwt-auth?schema_type=consumer',
ngx.HTTP_GET,
[[
{"dependencies":{"algorithm":{"oneOf":[{"properties":{"algorithm":{"default":"HS256","enum":["HS256","HS512"]}}},{"required":["public_key","private_key"],"properties":{"algorithm":{"enum":["RS256"]},"public_key":{"type":"string"},"private_key":{"type":"string"}}}]}},"required":["key"],"type":"object","properties":{"base64_secret":{"default":false,"type":"boolean"},"secret":{"type":"string"},"algorithm":{"enum":["HS256","HS512","RS256"],"default":"HS256","type":"string"},"exp":{"minimum":1,"default":86400,"type":"integer"},"key":{"type":"string"}}}
{"dependencies":{"algorithm":{"oneOf":[{"properties":{"algorithm":{"default":"HS256","enum":["HS256","HS512"]}}},{"required":["public_key","private_key"],"properties":{"algorithm":{"enum":["RS256","ES256"]},"public_key":{"type":"string"},"private_key":{"type":"string"}}}]}},"required":["key"],"type":"object","properties":{"base64_secret":{"default":false,"type":"boolean"},"secret":{"type":"string"},"algorithm":{"enum":["HS256","HS512","RS256","ES256"],"default":"HS256","type":"string"},"exp":{"minimum":1,"default":86400,"type":"integer"},"key":{"type":"string"}}}
]]
)
Expand Down Expand Up @@ -1083,7 +1083,7 @@ hello world
content_by_lua_block {
local plugin = require("apisix.plugins.jwt-auth")
local core = require("apisix.core")
local conf = {key = "123", algorithm = "ES256"}
local conf = {key = "123", algorithm = "ES512"}
local ok, err = plugin.check_schema(conf, core.schema.TYPE_CONSUMER)
if not ok then
Expand Down Expand Up @@ -1235,3 +1235,94 @@ qr/failed to validate dependent schema for \\"algorithm\\"/
--- error_code: 400
--- response_body_like eval
qr/failed to validate dependent schema for \\"algorithm\\"/
=== TEST 52: add consumer with username and plugins with public_key, private_key(ES256)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/consumers',
ngx.HTTP_PUT,
[[{
"username": "kerouac",
"plugins": {
"jwt-auth": {
"key": "user-key-es256",
"algorithm": "ES256",
"public_key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2\nOF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r\n1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G\n-----END PRIVATE KEY-----"
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 53: JWT sign and verify use ES256 algorithm(private_key numbits = 512)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
"jwt-auth": {}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed
=== TEST 54: sign/verify use ES256 algorithm(private_key numbits = 512)
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, err, sign = t('/apisix/plugin/jwt/sign?key=user-key-es256',
ngx.HTTP_GET
)
if code > 200 then
ngx.status = code
ngx.say(err)
return
end
local code, _, res = t('/hello?jwt=' .. sign,
ngx.HTTP_GET
)
ngx.status = code
ngx.print(res)
}
}
--- response_body
hello world

0 comments on commit 535812d

Please sign in to comment.