You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2022/08/09 01:43:17 UTC

[apisix] branch master updated: feat(jwt-auth): support ES256 digital algorithm (#7627)

This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 535812d81 feat(jwt-auth): support ES256 digital algorithm (#7627)
535812d81 is described below

commit 535812d81f973f08d8c51843835fc1637e5262cd
Author: HaiYan <ha...@hotmail.com>
AuthorDate: Tue Aug 9 09:43:11 2022 +0800

    feat(jwt-auth): support ES256 digital algorithm (#7627)
    
    Co-authored-by: qihaiyan <qi...@hisense.com>
---
 apisix/plugins/jwt-auth.lua        | 24 +++++-----
 docs/en/latest/plugins/jwt-auth.md |  8 ++--
 docs/zh/latest/plugins/jwt-auth.md |  8 ++--
 t/plugin/jwt-auth.t                | 95 +++++++++++++++++++++++++++++++++++++-
 4 files changed, 114 insertions(+), 21 deletions(-)

diff --git a/apisix/plugins/jwt-auth.lua b/apisix/plugins/jwt-auth.lua
index 3cd601778..36006975f 100644
--- a/apisix/plugins/jwt-auth.lua
+++ b/apisix/plugins/jwt-auth.lua
@@ -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},
@@ -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"},
@@ -106,7 +106,7 @@ local consumer_schema = {
                             properties = {}
                         },
                         algorithm = {
-                            enum = {"RS256"},
+                            enum = {"RS256", "ES256"},
                         },
                     },
                     required = {"vault"},
@@ -166,7 +166,7 @@ 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
@@ -174,7 +174,7 @@ function _M.check_schema(conf, schema_type)
         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
@@ -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)
@@ -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")
@@ -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
diff --git a/docs/en/latest/plugins/jwt-auth.md b/docs/en/latest/plugins/jwt-auth.md
index 2019d1a6b..353ddd3a2 100644
--- a/docs/en/latest/plugins/jwt-auth.md
+++ b/docs/en/latest/plugins/jwt-auth.md
@@ -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
diff --git a/docs/zh/latest/plugins/jwt-auth.md b/docs/zh/latest/plugins/jwt-auth.md
index b8828ed90..a6ea26a82 100644
--- a/docs/zh/latest/plugins/jwt-auth.md
+++ b/docs/zh/latest/plugins/jwt-auth.md
@@ -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
diff --git a/t/plugin/jwt-auth.t b/t/plugin/jwt-auth.t
index bfee5cf0d..cfea01313 100644
--- a/t/plugin/jwt-auth.t
+++ b/t/plugin/jwt-auth.t
@@ -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": [...]
+{"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"}," [...]
                 ]]
                 )
 
@@ -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
@@ -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