diff --git a/apisix/plugins/jwt-auth/parser.lua b/apisix/plugins/jwt-auth/parser.lua index 098a26825dcf..0d2be383c6c9 100644 --- a/apisix/plugins/jwt-auth/parser.lua +++ b/apisix/plugins/jwt-auth/parser.lua @@ -32,6 +32,7 @@ local ipairs = ipairs local type = type local error = error local pcall = pcall +local tostring = tostring local default_claims = { "nbf", @@ -223,8 +224,28 @@ end function _M.verify_signature(self, key) - return alg_verify[self.header.alg](self.raw_header .. "." .. - self.raw_payload, base64_decode(self.signature), key) + local verifier = alg_verify[self.header.alg] + if not verifier then + return false, "unsupported algorithm: " .. tostring(self.header.alg) + end + + local signature = base64_decode(self.signature) + if not signature then + return false, "failed to decode signature" + end + + -- the per-algorithm verifiers assert on signature length and key validity, + -- so guard with pcall to turn a malformed token into a clean rejection + -- instead of letting the error propagate as a 500 response + local ok, verified, verify_err = pcall(verifier, + self.raw_header .. "." .. self.raw_payload, signature, key) + if not ok then + -- verifier raised: `verified` holds the caught error message + return false, verified + end + + -- preserve the verifier's own (verified, err) return contract + return verified, verify_err end diff --git a/t/plugin/jwt-auth.t b/t/plugin/jwt-auth.t index f9cb66ec46d9..29eb6b869201 100644 --- a/t/plugin/jwt-auth.t +++ b/t/plugin/jwt-auth.t @@ -1428,3 +1428,65 @@ GET /hello?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4 {"message":"failed to verify jwt"} --- error_log failed to verify jwt: 'exp' claim expired at Tue, 23 Jul 2019 08:28:21 GMT + + + +=== TEST 59: malformed signatures must be rejected with 401, not crash with 500 +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + -- ES256 consumer: the per-algorithm verifier asserts the signature + -- length and decodes it from base64url, so a malformed signature + -- used to raise a Lua error and surface as a 500 instead of a 401. + 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-----" + } + } + }]] + ) + assert(code < 300, body) + + 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" + }]] + ) + assert(code < 300, body) + + -- valid ES256 header + payload (key claim = user-key-es256), with two + -- malformed signatures: "YWJj" decodes to 3 bytes (not the required + -- 64), and "@@@@" is not valid base64url so decoding returns nil + local header_payload = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9" + .. ".eyJrZXkiOiJ1c2VyLWtleS1lczI1NiIsIm5iZiI6MTcyNzI3NDk4M30" + for _, sig in ipairs({"YWJj", "@@@@"}) do + local rc, rb = t('/hello?jwt=' .. header_payload .. "." .. sig, + ngx.HTTP_GET) + assert(rc == 401, "signature '" .. sig .. "' expected 401 but got " + .. tostring(rc)) + assert(string.find(rb, "failed to verify jwt", 1, true), rb) + end + ngx.say("passed") + } + } +--- response_body +passed +--- no_error_log +[error]