diff --git a/kong/plugins/jwt/daos.lua b/kong/plugins/jwt/daos.lua index 4ed654080b4..1f3b253adaa 100644 --- a/kong/plugins/jwt/daos.lua +++ b/kong/plugins/jwt/daos.lua @@ -12,7 +12,7 @@ local SCHEMA = { key = {type = "string", unique = true, default = utils.random_string}, secret = {type = "string", unique = true, default = utils.random_string}, rsa_public_key = {type = "string"}, - algorithm = {type = "string", enum = {"HS256", "RS256", "ES256"}, default = 'HS256'} + algorithm = {type = "string", enum = {"HS256", "RS256", "RS512", "ES256"}, default = 'HS256'} }, self_check = function(schema, plugin_t, dao, is_update) if plugin_t.algorithm == "RS256" and plugin_t.rsa_public_key == nil then @@ -21,6 +21,12 @@ local SCHEMA = { if plugin_t.algorithm == "RS256" and crypto.pkey.from_pem(plugin_t.rsa_public_key) == nil then return false, Errors.schema "'rsa_public_key' format is invalid" end + if plugin_t.algorithm == "RS512" and plugin_t.rsa_public_key == nil then + return false, Errors.schema "no mandatory 'rsa_public_key'" + end + if plugin_t.algorithm == "RS512" and crypto.pkey.from_pem(plugin_t.rsa_public_key) == nil then + return false, Errors.schema "'rsa_public_key' format is invalid" + end return true end, marshall_event = function(self, t) diff --git a/kong/plugins/jwt/jwt_parser.lua b/kong/plugins/jwt/jwt_parser.lua index 130c733d252..1baabb1c7af 100644 --- a/kong/plugins/jwt/jwt_parser.lua +++ b/kong/plugins/jwt/jwt_parser.lua @@ -27,6 +27,7 @@ local alg_sign = { --["HS384"] = function(data, key) return crypto.hmac.digest("sha384", data, key, true) end, --["HS512"] = function(data, key) return crypto.hmac.digest("sha512", data, key, true) end ["RS256"] = function(data, key) return crypto.sign('sha256', data, crypto.pkey.from_pem(key, true)) end, + ["RS512"] = function(data, key) return crypto.sign('sha512', data, crypto.pkey.from_pem(key, true)) end, ["ES256"] = function(data, key) local pkeyPrivate = crypto.pkey.from_pem(key, true) local signature = crypto.sign('sha256', data, pkeyPrivate) @@ -49,6 +50,10 @@ local alg_verify = { local pkey = assert(crypto.pkey.from_pem(key), "Consumer Public Key is Invalid") return crypto.verify('sha256', data, signature, pkey) end, + ["RS512"] = function(data, signature, key) + local pkey = assert(crypto.pkey.from_pem(key), "Consumer Public Key is Invalid") + return crypto.verify('sha512', data, signature, pkey) + end, ["ES256"] = function(data, signature, key) local pkey = assert(crypto.pkey.from_pem(key), "Consumer Public Key is Invalid") assert(#signature == 64, "Signature must be 64 bytes.") diff --git a/spec/03-plugins/17-jwt/01-jwt_parser_spec.lua b/spec/03-plugins/17-jwt/01-jwt_parser_spec.lua index de12e7a3b66..d74f8f446ff 100644 --- a/spec/03-plugins/17-jwt/01-jwt_parser_spec.lua +++ b/spec/03-plugins/17-jwt/01-jwt_parser_spec.lua @@ -29,6 +29,23 @@ describe("Plugin: jwt (parser)", function() [[tcFKN-Pi7_rWzBtQwP2u4CrFD4ZJbn2sxobzSlFb9fn4nRh_-rPPjDSeHVKwrpsYp]] .. [[FSLBJxwX-KhbeGUfalg2eu9tHLDPHC4gTCpoQKxxRIwfMjW5zlHOZhohKZV2ZtpcgA]] , token) end) + it("should encode using RS512", function() + local token = jwt_parser.encode({ + sub = "1234567890", + name = "John Doe", + admin = true, + }, fixtures.rs512_private_key, "RS512") + + assert.equal([[eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1]] .. + [[ZSwibmFtZSI6IkpvaG4gRG9lIiwic3ViIjoiMTIzNDU2Nzg5MCJ9.]] .. + [[VhoFYud-lrxtkbkfMl0Wkr4fERsDNjGfvHc2hFEecjLqSJ65_cydJ]] .. + [[iU011QqAmlMM8oIRCnoGKvA63XeE7M6qPsNkJ_vHMoqO-Hg3ajx1R]] .. + [[aWmVaHyeTCkkyStNvh88phVSH5EB5wIYjukHErRXLCTL9UhE0Z60f]] .. + [[NzLeEZ5yJZS-rhOK3fa0QSVoTD2QKVITYBcX_xt6NzHzTTx_3kQ1K]] .. + [[lcuueNlOLmCYx_6tissUvMY91KjcZfs3z9tYREu5paFx0pSiPvgNB]] .. + [[vrWQfbm3irr-1YcBH7wJuIinPDrERVohK1v37t8fDnSqhi1tWUati]] .. + [[7Mynkb3JrpCeF3IyReSvkhQA]], token) + end) it("should encode using ES256", function() local token = jwt_parser.encode({ @@ -90,6 +107,12 @@ describe("Plugin: jwt (parser)", function() assert.True(jwt:verify_signature(fixtures.rs256_public_key)) assert.False(jwt:verify_signature(fixtures.rs256_public_key:gsub('QAB', 'zzz'))) end) + it("using RS512", function() + local token = jwt_parser.encode({sub = "foo"}, fixtures.rs512_private_key, 'RS512') + local jwt = assert(jwt_parser:new(token)) + assert.True(jwt:verify_signature(fixtures.rs512_public_key)) + assert.False(jwt:verify_signature(fixtures.rs512_public_key:gsub('AE=', 'zzz'))) + end) it("using ES256", function() for _ = 1, 500 do local token = jwt_parser.encode({sub = "foo"}, fixtures.es256_private_key, 'ES256') diff --git a/spec/03-plugins/17-jwt/03-access_spec.lua b/spec/03-plugins/17-jwt/03-access_spec.lua index 2ee3ea47ccd..d851c0d6264 100644 --- a/spec/03-plugins/17-jwt/03-access_spec.lua +++ b/spec/03-plugins/17-jwt/03-access_spec.lua @@ -12,7 +12,7 @@ local PAYLOAD = { } describe("Plugin: jwt (access)", function() - local jwt_secret, base64_jwt_secret, rsa_jwt_secret_1, rsa_jwt_secret_2 + local jwt_secret, base64_jwt_secret, rsa_jwt_secret_1, rsa_jwt_secret_2, rsa_jwt_secret_3 local proxy_client, admin_client setup(function() @@ -28,6 +28,7 @@ describe("Plugin: jwt (access)", function() local consumer2 = assert(helpers.dao.consumers:insert {username = "jwt_tests_base64_consumer"}) local consumer3 = assert(helpers.dao.consumers:insert {username = "jwt_tests_rsa_consumer_1"}) local consumer4 = assert(helpers.dao.consumers:insert {username = "jwt_tests_rsa_consumer_2"}) + local consumer5 = assert(helpers.dao.consumers:insert {username = "jwt_tests_rsa_consumer_5"}) local anonymous_user = assert(helpers.dao.consumers:insert {username = "no-body"}) assert(helpers.dao.plugins:insert {name = "jwt", config = {}, api_id = api1.id}) @@ -50,6 +51,11 @@ describe("Plugin: jwt (access)", function() algorithm = "RS256", rsa_public_key = fixtures.rs256_public_key }) + rsa_jwt_secret_3 = assert(helpers.dao.jwt_secrets:insert { + consumer_id = consumer5.id, + algorithm = "RS512", + rsa_public_key = fixtures.rs512_public_key + }) assert(helpers.start_kong()) proxy_client = helpers.proxy_client() @@ -263,6 +269,41 @@ describe("Plugin: jwt (access)", function() end) end) +describe("RS512", function() + it("verifies JWT", function() + PAYLOAD.iss = rsa_jwt_secret_3.key + local jwt = jwt_encoder.encode(PAYLOAD, fixtures.rs512_private_key, "RS512") + local authorization = "Bearer " .. jwt + local res = assert(proxy_client:send { + method = "GET", + path = "/request", + headers = { + ["Authorization"] = authorization, + ["Host"] = "jwt.com", + } + }) + local body = cjson.decode(assert.res_status(200, res)) + assert.equal(authorization, body.headers.authorization) + assert.equal("jwt_tests_rsa_consumer_5", body.headers["x-consumer-username"]) + end) + it("identifies Consumer", function() + PAYLOAD.iss = rsa_jwt_secret_3.key + local jwt = jwt_encoder.encode(PAYLOAD, fixtures.rs512_private_key, "RS512") + local authorization = "Bearer " .. jwt + local res = assert(proxy_client:send { + method = "GET", + path = "/request", + headers = { + ["Authorization"] = authorization, + ["Host"] = "jwt.com", + } + }) + local body = cjson.decode(assert.res_status(200, res)) + assert.equal(authorization, body.headers.authorization) + assert.equal("jwt_tests_rsa_consumer_5", body.headers["x-consumer-username"]) + end) + end) + describe("JWT private claims checks", function() it("requires the checked fields to be in the claims", function() local payload = { diff --git a/spec/03-plugins/17-jwt/fixtures.lua b/spec/03-plugins/17-jwt/fixtures.lua index b59934c094b..c28f59b48a3 100644 --- a/spec/03-plugins/17-jwt/fixtures.lua +++ b/spec/03-plugins/17-jwt/fixtures.lua @@ -39,6 +39,46 @@ FLiGOm5uTMEk8S4txs2efueg1XyymilCKzzuXlJvrvPA4u6HI7qNvuvkvUjQmwBH gwIDAQAB -----END PUBLIC KEY----- ]], +rs512_private_key = [[ +-----BEGIN RSA PRIVATE KEY----- +MIIEoQIBAAKCAQBoyqH30IH7YnHk2YLLygDwD0LUvrXRcWwVCsN1/2+DB+FLV8f+ +/VoLegUowlcCea6vJCu9q9vnJqz2UhK7eN/kDYNhnx4WIdc3KjL+SXnp3KZozgn/ +uCUeEYnMNRXLlx7GefG+C1yUgcFAaVJoyxx7dQellqWYrTW3nW9fMhioxSvuJUU8 +u5v31GPzNeF69bfeKdI1NzhVLkztJhogEXdIgYitEcqepJQe1FpSBbVjdT5xbuMN +80pSnJHR11Qw2dPp6lDlao/hnvkYW77CZOVgK02oB0UEqjaasxPcaHiWerSP0yb7 +nCdjR0kTgKA8um/gk0/F+FO3aOkrsZsgpK2vAgMBAAECggEAZVybjrmBAUgYIuTC +P50Fiy831dEizZSIl1Hx/xE1K+lTYy1lpqApmTBODT7uKtbIwWCbbrvt2YjvhNOe +ivhAmLb5flQLJh1Vr2aCLLWl1zA3RukFgvT78jnEsGIo0uU6P4F08/7JblyUMVmu +/O56fnCVFPbC9wuUCieestYiRBw3Z7TwcRmUx5JWJUuj4gzuFfRSyuzYeJoYUSJF +OLu5XtXaW4k0nj/LILC89qxQT/8HIIYa/7+S8TdbBfws6kQt5yiwUUOirzjOeuY9 +RIvbmgapAVZhI2oxofu1r2XNLBBPHFDHlLeJasqRAa7vk3yVYtrcg20c5q3MZ1tx +Q+7NAQKBgQCqUxzBxK0h6d0GKe0rt30NSfNhFiKPR2AkQ8hXsjrCfJ36PbQqRyEt +zw13hgaHoS4yfj+19aQem1ZoTZVsUTvkD8CBBHZ/1PYjPoeOrjt6mDfp402z+f3E +FZTQq0dNFGsSGHn1yRUqXebM3SbbyDIDUqnHXMC1Dzsm2vSu6EJSIwKBgQCdgMAM +bQge8cQSevcARctOjqVpwirfsfqLebP+SBmzyNrs9Z5u8l/0EooojMRaGVClNzvb +yYCW6DWzac0jCKRuD9Svd41gGC3R5PztGGOyvLLkk33ad9NChwCz9np66THmQn6n +B+K/XrjDwUVHnItcRiARyXP3vn3uryv1hvRRBQKBgB7MtreHZDNswc4aiMvN+2wK +wlr9ELTOGGGWbEUHcr62oC6fN9QpVqOc/HdvogCmsd7pm4XA7LOoLWDhHrMeoXDl +NE9gSjllfjjzVroDYbgSjJHby7JO84egy29MebFDjvUPvgYnHY+yuUi0eRFnSzv0 +l8T4TdSv82dcUsDKOSv3AoGAQmlxkUvAKtwiovA6imDjkyJO2UNINL6lOH5+yO+5 +9rbwqQ4AWiPVFeNjYinI+XzHJoMduFVE5VzQl/A60VTpkIcYVUyBzk0jtOdrRsYL +8+fhPsR6Qs5XxCuMvlVl28HMipzrLp8Cm1LjcZdjEQkPMj9XcmiRf5tRGn2+eW8I +QckCgYAYYzr4nHmarWduk/1Fgm2qmFE96U/TjIRmk9vspwk5y47oM7LrnzU2Iyio +vaL3rwMZ0AcBcEOUvANkMCDAxgJZljeDr4IzUMQs95+m7Wb6BQTs4vKSLPGWYdjd +y1FoR04hSreMjG+K+mtQLGJC4USI1AJx1wKihgoxGrI1/7YiwQ== +-----END RSA PRIVATE KEY----- +]], +rs512_public_key = [[ +-----BEGIN PUBLIC KEY----- +MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBoyqH30IH7YnHk2YLLygDw +D0LUvrXRcWwVCsN1/2+DB+FLV8f+/VoLegUowlcCea6vJCu9q9vnJqz2UhK7eN/k +DYNhnx4WIdc3KjL+SXnp3KZozgn/uCUeEYnMNRXLlx7GefG+C1yUgcFAaVJoyxx7 +dQellqWYrTW3nW9fMhioxSvuJUU8u5v31GPzNeF69bfeKdI1NzhVLkztJhogEXdI +gYitEcqepJQe1FpSBbVjdT5xbuMN80pSnJHR11Qw2dPp6lDlao/hnvkYW77CZOVg +K02oB0UEqjaasxPcaHiWerSP0yb7nCdjR0kTgKA8um/gk0/F+FO3aOkrsZsgpK2v +AgMBAAE= +-----END PUBLIC KEY----- +]], es256_private_key = [[ -----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgD8enltAi05AIoF2A