Skip to content

Commit

Permalink
feat(hmac) multiple HMAC algorithms support and enforce_headers opt…
Browse files Browse the repository at this point in the history
…ion (#2644)

- Support for HMAC-SHA256, HMAC-SHA384, HMAC-SHA512.
- User can enforce which headers must at least be used for
  http signature creation using `config.enforce_headers`.
  • Loading branch information
shashiranjan84 authored and Tieske committed Jun 23, 2017
1 parent d3f550d commit 0da9be8
Show file tree
Hide file tree
Showing 6 changed files with 307 additions and 17 deletions.
78 changes: 75 additions & 3 deletions kong/plugins/hmac-auth/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local utils = require "kong.tools.utils"
local responses = require "kong.tools.responses"
local constants = require "kong.constants"
local singletons = require "kong.singletons"
local crypto = require "crypto"
local resty_sha256 = require "resty.sha256"

local math_abs = math.abs
Expand All @@ -10,14 +11,16 @@ local ngx_gmatch = ngx.re.gmatch
local ngx_decode_base64 = ngx.decode_base64
local ngx_encode_base64 = ngx.encode_base64
local ngx_parse_time = ngx.parse_http_time
local ngx_sha1 = ngx.hmac_sha1
local ngx_set_header = ngx.req.set_header
local ngx_get_headers = ngx.req.get_headers
local ngx_log = ngx.log
local req_read_body = ngx.req.read_body
local req_get_body_data = ngx.req.get_body_data
local ngx_hmac_sha1 = ngx.hmac_sha1

local split = utils.split
local fmt = string.format
local ipairs = ipairs

local AUTHORIZATION = "authorization"
local PROXY_AUTHORIZATION = "proxy-authorization"
Expand All @@ -27,8 +30,70 @@ local DIGEST = "digest"
local SIGNATURE_NOT_VALID = "HMAC signature cannot be verified"
local SIGNATURE_NOT_SAME = "HMAC signature does not match"

local new_tab
do
local ok
ok, new_tab = pcall(require, "table.new")
if not ok then
new_tab = function() return {} end
end
end

local _M = {}

local hmac = {
["hmac-sha1"] = function(secret, data)
return ngx_hmac_sha1(secret, data)
end,
["hmac-sha256"] = function(secret, data)
return crypto.hmac.digest("sha256", data, secret, true)
end,
["hmac-sha384"] = function(secret, data)
return crypto.hmac.digest("sha384", data, secret, true)
end,
["hmac-sha512"] = function(secret, data)
return crypto.hmac.digest("sha512", data, secret, true)
end
}

local function list_as_set(list)
local set = new_tab(0, #list)
for _, v in ipairs(list) do
set[v] = true
end

return set
end

local function validate_params(params, conf)
-- check username and signature are present
if not params.username and params.signature then
return nil, "username or signature missing"
end

-- check enforced headers are present
if conf.enforce_headers and #conf.enforce_headers >= 1 then
local enforced_header_set = list_as_set(conf.enforce_headers)
for _, header in ipairs(params.hmac_headers) do
enforced_header_set[header] = nil
end
for _, header in ipairs(conf.enforce_headers) do
if enforced_header_set[header] then
return nil, "enforced header not used for signature creation"
end
end
end

-- check supported alorithm used
for _, algo in ipairs(conf.algorithms) do
if algo == params.algorithm then
return true
end
end

return nil, fmt("algorithm %s not supported", params.algorithm)
end

local function retrieve_hmac_fields(request, headers, header_name, conf)
local hmac_params = {}
local authorization_header = headers[header_name]
Expand Down Expand Up @@ -84,7 +149,7 @@ local function create_hash(request, hmac_params, headers)
signing_string = signing_string .. "\n"
end
end
return ngx_sha1(hmac_params.secret, signing_string)
return hmac[hmac_params.algorithm](hmac_params.secret, signing_string)
end

local function validate_signature(request, hmac_params, headers)
Expand Down Expand Up @@ -196,26 +261,33 @@ local function do_authentication(conf)

-- retrieve hmac parameter from Proxy-Authorization header
local hmac_params = retrieve_hmac_fields(ngx.req, headers, PROXY_AUTHORIZATION, conf)

-- Try with the authorization header
if not hmac_params.username then
hmac_params = retrieve_hmac_fields(ngx.req, headers, AUTHORIZATION, conf)
end
if not (hmac_params.username and hmac_params.signature) then

local ok, err = validate_params(hmac_params, conf)
if not ok then
ngx_log(ngx.DEBUG, err)
return false, {status = 403, message = SIGNATURE_NOT_VALID}
end

-- validate signature
local credential = load_credential(hmac_params.username)
if not credential then
ngx_log(ngx.DEBUG, "failed to retrieve credential for ", hmac_params.username)
return false, {status = 403, message = SIGNATURE_NOT_VALID}
end
hmac_params.secret = credential.secret

if not validate_signature(ngx.req, hmac_params, headers) then
return false, { status = 403, message = SIGNATURE_NOT_SAME }
end

-- If request body validation is enabled, then verify digest.
if conf.validate_request_body and not validate_body(headers[DIGEST]) then
ngx_log(ngx.DEBUG, "digest validation failed")
return false, { status = 403, message = SIGNATURE_NOT_SAME }
end

Expand Down
21 changes: 21 additions & 0 deletions kong/plugins/hmac-auth/migrations/cassandra.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,26 @@ return {
down = [[
DROP TABLE hmacauth_credentials;
]]
},
{
name = "2017-06-21-132400_init_hmacauth",
up = function(_, _, dao)
local rows, err = dao.plugins:find_all { name = "hmac-auth" }
if err then
return err
end

for _, row in ipairs(rows) do
row.config.validate_request_body = false
row.config.enforce_headers = {}
row.config.algorithms = { "hmac-sha1" }
local _, err = dao.plugins:update(row, row)
if err then
return err
end
end
end,
down = function()
end
}
}
21 changes: 21 additions & 0 deletions kong/plugins/hmac-auth/migrations/postgres.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,26 @@ return {
down = [[
DROP TABLE hmacauth_credentials;
]]
},
{
name = "2017-06-21-132400_init_hmacauth",
up = function(_, _, dao)
local rows, err = dao.plugins:find_all { name = "hmac-auth" }
if err then
return err
end

for _, row in ipairs(rows) do
row.config.validate_request_body = false
row.config.enforce_headers = {}
row.config.algorithms = {"hmac-sha1"}
local _, err = dao.plugins:update(row, row)
if err then
return err
end
end
end,
down = function()
end
}
}
13 changes: 11 additions & 2 deletions kong/plugins/hmac-auth/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ local function check_user(anonymous)
if anonymous == "" or utils.is_valid_uuid(anonymous) then
return true
end

return false, "the anonymous user must be empty or a valid uuid"
end

Expand All @@ -15,12 +15,21 @@ local function check_clock_skew_positive(v)
return true
end

local algorithms = {
"hmac-sha1",
"hmac-sha256",
"hmac-sha384",
"hmac-sha512",
}

return {
no_consumer = true,
fields = {
hide_credentials = { type = "boolean", default = false },
clock_skew = { type = "number", default = 300, func = check_clock_skew_positive },
anonymous = {type = "string", default = "", func = check_user},
anonymous = { type = "string", default = "", func = check_user },
validate_request_body = { type = "boolean", default = false },
enforce_headers = { type = "array", default = {} },
algorithms = { type = "array", default = algorithms, enum = algorithms }
}
}
11 changes: 8 additions & 3 deletions spec/03-plugins/20-hmac-auth/01-schema_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ describe("Plugin: hmac-auth (schema)", function()
it("accepts empty config", function()
local ok, err = validate_entity({}, hmac_auth_schema)
assert.is_nil(err)
assert.True(ok)
assert.is_true(ok)
end)
it("accepts correct clock skew", function()
local ok, err = validate_entity({clock_skew = 10}, hmac_auth_schema)
assert.is_nil(err)
assert.True(ok)
assert.is_true(ok)
end)
it("errors with negative clock skew", function()
local ok, err = validate_entity({clock_skew = -10}, hmac_auth_schema)
assert.equal("Clock Skew should be positive", err.clock_skew)
assert.False(ok)
assert.is_false(ok)
end)
it("errors with wrong algorithm", function()
local ok, err = validate_entity({algorithms = {"sha1024"}}, hmac_auth_schema)
assert.equal('"sha1024" is not allowed. Allowed values are: "hmac-sha1", "hmac-sha256", "hmac-sha384", "hmac-sha512"', err.algorithms)
assert.is_false(ok)
end)
end)
Loading

0 comments on commit 0da9be8

Please sign in to comment.