Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding RS256 support to JWT plugin #1053

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion kong/plugins/jwt/daos.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ local SCHEMA = {
created_at = {type = "timestamp", immutable = true, dao_insert_value = true},
consumer_id = {type = "id", required = true, foreign = "consumers:id"},
key = {type = "string", unique = true, default = utils.random_string},
secret = {type = "string", unique = true, default = utils.random_string}
secret = {type = "string", unique = true, default = utils.random_string},
algorithm = {type = "string", enum = {"HS256", "RS256"}, default = 'HS256'}
},
marshall_event = function(self, t)
return {id = t.id, consumer_id = t.consumer_id, key = t.key}
Expand Down
5 changes: 5 additions & 0 deletions kong/plugins/jwt/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ function JwtHandler:access(conf)
return responses.send_HTTP_FORBIDDEN("No credentials found for given '"..conf.key_claim_name.."'")
end

-- Verify "alg"
if jwt.header.alg ~= jwt_secret.algorithm then
return responses.send_HTTP_FORBIDDEN("Invalid algorithm")
end

local jwt_secret_value = jwt_secret.secret
if conf.secret_is_base64 then
jwt_secret_value = jwt:b64_decode(jwt_secret_value)
Expand Down
10 changes: 6 additions & 4 deletions kong/plugins/jwt/jwt_parser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,21 @@ local string_rep = string.rep
local setmetatable = setmetatable

--- Supported algorithms for signing tokens.
-- Only support HS256 for our use case.
local alg_sign = {
["HS256"] = function(data, key) return crypto.hmac.digest("sha256", data, key, true) end
["HS256"] = function(data, key) return crypto.hmac.digest("sha256", data, key, true) end,
--["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
}

--- Supported algorithms for verifying tokens.
-- Only support HS256 for our use case.
local alg_verify = {
["HS256"] = function(data, signature, key) return signature == alg_sign["HS256"](data, key) end
["HS256"] = function(data, signature, key) return signature == alg_sign["HS256"](data, key) end,
--["HS384"] = function(data, signature, key) return signature == alg_sign["HS384"](data, key) end,
--["HS512"] = function(data, signature, key) return signature == alg_sign["HS512"](data, key) end
["RS256"] = function(data, signature, key)
return crypto.verify('sha256', data, signature, crypto.pkey.from_pem(key))
end
}

--- base 64 encoding
Expand Down
9 changes: 9 additions & 0 deletions kong/plugins/jwt/migrations/cassandra.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,14 @@ return {
down = [[
DROP TABLE jwt_secrets;
]]
},
{
name = "2016-03-07-jwt-auth",
up = [[
ALTER TABLE jwt_secrets ADD algorithm text;
]],
down = [[
ALTER TABLE jwt_secrets DROP algorithm;
]]
}
}
9 changes: 9 additions & 0 deletions kong/plugins/jwt/migrations/postgres.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,14 @@ return {
down = [[
DROP TABLE jwt_secrets;
]]
},
{
name = "2016-03-07-jwt-auth",
up = [[
ALTER TABLE jwt_secrets ADD COLUMN algorithm varchar(5);
]],
down = [[
ALTER TABLE jwt_secrets DROP COLUMN algorithm;
]]
}
}
10 changes: 10 additions & 0 deletions spec/plugins/jwt/access_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ describe("JWT access", function()
assert.equal("Invalid signature", body.message)
end)

it("should return 403 Forbidden if the alg does not match the credential", function()
local header = {typ = "JWT", alg = 'RS256'}
local jwt = jwt_encoder.encode(PAYLOAD, jwt_secret.secret, 'HS256', header)
local authorization = "Bearer "..jwt
local response, status = http_client.get(STUB_GET_URL, nil, {host = "jwt.com", authorization = authorization})
assert.equal(403, status)
local body = json.decode(response)
assert.equal("Invalid algorithm", body.message)
end)

it("should proxy the request with token and consumer headers if it was verified", function()
PAYLOAD.iss = jwt_secret.key
local jwt = jwt_encoder.encode(PAYLOAD, jwt_secret.secret)
Expand Down
61 changes: 59 additions & 2 deletions spec/plugins/jwt/jwt_parser_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,66 @@ require "kong.tools.ngx_stub"

local jwt_parser = require "kong.plugins.jwt.jwt_parser"

local rs256_private_key = [[
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAw5mp3MS3hVLkHwB9lMrEx34MjYCmKeH/XeMLexNpTd1FzuNv
6rArovTY763CDo1Tp0xHz0LPlDJJtpqAgsnfDwCcgn6ddZTo1u7XYzgEDfS8J4SY
dcKxZiSdVTpb9k7pByXfnwK/fwq5oeBAJXISv5ZLB1IEVZHhUvGCH0udlJ2vadqu
R03phBHcvlNmMbJGWAetkdcKyi+7TaW7OUSjlge4WYERgYzBB6eJH+UfPjmw3aSP
ZcNXt2RckPXEbNrL8TVXYdEvwLJoJv9/I8JPFLiGOm5uTMEk8S4txs2efueg1Xyy
milCKzzuXlJvrvPA4u6HI7qNvuvkvUjQmwBHgwIDAQABAoIBAQCP3ZblTT8abdRh
xQ+Y/+bqQBjlfwk4ZwRXvuYz2Rwr7CMrP3eSq4785ZAmAaxo3aP4ug9bL23UN4Sm
LU92YxqQQ0faZ1xTHnp/k96SGKJKzYYSnuEwREoMscOS60C2kmWtHzsyDmhg/bd5
i6JCqHuHtPhsYvPTKGANjJrDf+9gXazArmwYrdTnyBeFC88SeRG8uH2lP2VyqHiw
ZvEQ3PkRRY0yJRqEtrIRIlgVDuuu2PhPg+MR4iqR1RONjDUFaSJjR7UYWY/m/dmg
HlalqpKjOzW6RcMmymLKaW6wF3y8lbs0qCjCYzrD3bZnlXN1kIw6cxhplfrSNyGZ
BY/qWytJAoGBAO8UsagT8tehCu/5smHpG5jgMY96XKPxFw7VYcZwuC5aiMAbhKDO
OmHxYrXBT/8EQMIk9kd4r2JUrIx+VKO01wMAn6fF4VMrrXlEuOKDX6ZE1ay0OJ0v
gCmFtKB/EFXXDQLV24pgYgQLxnj+FKFV2dQLmv5ZsAVcmBHSkM9PBdUlAoGBANFx
QPuVaSgRLFlXw9QxLXEJbBFuljt6qgfL1YDj/ANgafO8HMepY6jUUPW5LkFye188
J9wS+EPmzSJGxdga80DUnf18yl7wme0odDI/7D8gcTfu3nYcCkQzeykZNGAwEe+0
SvhXB9fjWgs8kFIjJIxKGmlMJRMHWN1qaECEkg2HAoGBAIb93EHW4as21wIgrsPx
5w8up00n/d7jZe2ONiLhyl0B6WzvHLffOb/Ll7ygZhbLw/TbAePhFMYkoTjCq++z
UCP12i/U3yEi7FQopWvgWcV74FofeEfoZikLwa1NkV+miUYskkVTnoRCUdJHREbE
PrYnx2AOLAEbAxItHm6vY8+xAoGAL85JBePpt8KLu+zjfximhamf6C60zejGzLbD
CgN/74lfRcoHS6+nVs73l87n9vpZnLhPZNVTo7QX2J4M5LHqGj8tvMFyM895Yv+b
3ihnFVWjYh/82Tq3QS/7Cbt+EAKI5Yzim+LJoIZ9dBkj3Au3eOolMym1QK2ppAh4
uVlJORsCgYBv/zpNukkXrSxVHjeZj582nkdAGafYvT0tEQ1u3LERgifUNwhmHH+m
1OcqJKpbgQhGzidXK6lPiVFpsRXv9ICP7o96FjmQrMw2lAfC7stYnFLKzv+cj8L9
h4hhNWM6i/DHXjPsHgwdzlX4ulq8M7dR8Oqm9DrbdAyWz8h8/kzsnA==
-----END RSA PRIVATE KEY-----
]]

local rs256_public_key = [[
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw5mp3MS3hVLkHwB9lMrE
x34MjYCmKeH/XeMLexNpTd1FzuNv6rArovTY763CDo1Tp0xHz0LPlDJJtpqAgsnf
DwCcgn6ddZTo1u7XYzgEDfS8J4SYdcKxZiSdVTpb9k7pByXfnwK/fwq5oeBAJXIS
v5ZLB1IEVZHhUvGCH0udlJ2vadquR03phBHcvlNmMbJGWAetkdcKyi+7TaW7OUSj
lge4WYERgYzBB6eJH+UfPjmw3aSPZcNXt2RckPXEbNrL8TVXYdEvwLJoJv9/I8JP
FLiGOm5uTMEk8S4txs2efueg1XyymilCKzzuXlJvrvPA4u6HI7qNvuvkvUjQmwBH
gwIDAQAB
-----END PUBLIC KEY-----
]]

describe("JWT parser", function()
describe("Encoding", function()
it("should properly encode", function()
it("should properly encode using HS256", function()
local token = jwt_parser.encode({
sub = "1234567890",
name = "John Doe",
admin = true
}, "secret")
assert.equal("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwibmFtZSI6IkpvaG4gRG9lIiwic3ViIjoiMTIzNDU2Nzg5MCJ9.eNK_fimsCW3Q-meOXyc_dnZHubl2D4eZkIcn6llniCk", token)
end)
it("should properly encode using RS256", function()
local token = jwt_parser.encode({
sub = "1234567890",
name = "John Doe",
admin = true
}, rs256_private_key, 'RS256')
assert.equal("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwibmFtZSI6IkpvaG4gRG9lIiwic3ViIjoiMTIzNDU2Nzg5MCJ9.EiOLxyMimY8vbLR8EcGOlXAiEe-eEVn7Aewgu0gYIBPyiEhVTq0CzB_XtHoQ_0y4gBBBZVRnz1pgruOtNmOzcaoXnyplFm1IbrCCBKYQeA4lanmu_-Wzk6Dw4p-TimRHpf8EEHBUJSEbVEyet3cpozUo2Ep0dEfA_Nf3T-g8RjfOYXkFTr3M6FuIDq95cFZloH-DRGodUVQX508wgggtcFKN-Pi7_rWzBtQwP2u4CrFD4ZJbn2sxobzSlFb9fn4nRh_-rPPjDSeHVKwrpsYpFSLBJxwX-KhbeGUfalg2eu9tHLDPHC4gTCpoQKxxRIwfMjW5zlHOZhohKZV2ZtpcgA", token)
end)
end)
describe("Decoding", function()
it("should throw an error if not given a string", function()
Expand All @@ -31,13 +81,20 @@ describe("JWT parser", function()
end)
end)
describe("Verify signature", function()
it("should verify a signature", function()
it("should verify a signature using HS256", function()
local token = jwt_parser.encode({sub = "foo"}, "secret")
local jwt, err = jwt_parser:new(token)
assert.falsy(err)
assert.True(jwt:verify_signature("secret"))
assert.False(jwt:verify_signature("invalid"))
end)
it("should verify a signature using RS256", function()
local token = jwt_parser.encode({sub = "foo"}, rs256_private_key, 'RS256')
local jwt, err = jwt_parser:new(token)
assert.falsy(err)
assert.True(jwt:verify_signature(rs256_public_key))
assert.False(jwt:verify_signature(rs256_public_key:gsub('QAB', 'zzz')))
end)
end)
describe("Verify registered claims", function()
it("should require claims passed as arguments", function()
Expand Down