-
Notifications
You must be signed in to change notification settings - Fork 143
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
Added support for HMAC-SHA256 and other SHA* algorithms #18
Changes from all commits
036e573
126006a
0c2ad42
439aada
825b523
c02fdd7
b0d7918
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,94 @@ | ||||
-- Adds HMAC support to Lua with multiple algorithms, via OpenSSL and FFI | ||||
-- | ||||
-- Author: ddragosd@gmail.com | ||||
-- Date: 16/05/14 | ||||
-- | ||||
|
||||
|
||||
local ffi = require "ffi" | ||||
local ffi_new = ffi.new | ||||
local ffi_str = ffi.string | ||||
local C = ffi.C | ||||
local resty_string = require "resty.string" | ||||
local setmetatable = setmetatable | ||||
local error = error | ||||
|
||||
|
||||
local _M = { _VERSION = '0.09' } | ||||
|
||||
|
||||
local mt = { __index = _M } | ||||
|
||||
-- | ||||
-- EVP_MD is defined in openssl/evp.h | ||||
-- HMAC is defined in openssl/hmac.h | ||||
-- | ||||
ffi.cdef[[ | ||||
typedef struct env_md_st EVP_MD; | ||||
typedef struct env_md_ctx_st EVP_MD_CTX; | ||||
unsigned char *HMAC(const EVP_MD *evp_md, const void *key, int key_len, | ||||
const unsigned char *d, size_t n, unsigned char *md, | ||||
unsigned int *md_len); | ||||
const EVP_MD *EVP_sha1(void); | ||||
const EVP_MD *EVP_sha224(void); | ||||
const EVP_MD *EVP_sha256(void); | ||||
const EVP_MD *EVP_sha384(void); | ||||
const EVP_MD *EVP_sha512(void); | ||||
]] | ||||
|
||||
-- table definind the available algorithms and the length of each digest | ||||
-- for more information @see: http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf | ||||
local available_algorithms = { | ||||
sha1 = { alg = C.EVP_sha1(), length = 160/8 }, | ||||
sha224 = { alg = C.EVP_sha224(), length = 224/8 }, | ||||
sha256 = { alg = C.EVP_sha256(), length = 256/8 }, | ||||
sha384 = { alg = C.EVP_sha384(), length = 384/8 }, | ||||
sha512 = { alg = C.EVP_sha512(), length = 512/8 } | ||||
} | ||||
|
||||
-- 64 is the max lenght and it covers up to sha512 algorithm | ||||
local digest_len = ffi_new("int[?]", 64) | ||||
local buf = ffi_new("char[?]", 64) | ||||
|
||||
|
||||
function _M.new(self) | ||||
return setmetatable({}, mt) | ||||
end | ||||
|
||||
local function getDigestAlgorithm(dtype) | ||||
local md_name = available_algorithms[dtype] | ||||
if ( md_name == nil ) then | ||||
error("attempt to use unknown algorithm: '" .. dtype .. | ||||
"'.\n Available algorithms are: sha1,sha224,sha256,sha384,sha512") | ||||
end | ||||
return md_name.alg, md_name.length | ||||
end | ||||
|
||||
--- | ||||
-- Returns the HMAC digest. The hashing algorithm is defined by the dtype parameter. | ||||
-- The optional raw flag, defaulted to false, is a boolean indicating whether the output should be a direct binary | ||||
-- equivalent of the HMAC or formatted as a hexadecimal string (the default) | ||||
-- | ||||
-- @param self | ||||
-- @param dtype The hashing algorithm to use is specified by dtype | ||||
-- @param key The secret | ||||
-- @param msg The message to be signed | ||||
-- @param raw When true, it returns the binary format, else, the hex format is returned | ||||
-- | ||||
function _M.digest(self, dtype, key, msg, raw) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This creates a new context for every MAC which can get expensive if you're creating / verifying many MAC values. Also, it doesn't allow for a single MAC to be updated at a later time (e.g. for large values / stream values). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the feedback. Line 84 with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think adding Had I known someone else was working on this I would have collaborated with you directly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jkeys089 sounds great ! I'm all for collaboration also. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To use the existing It doesn't make too much difference I think but it does keep the API a bit simpler and keeps the API similar other libs (e.g. lua-resty-string/lib/resty/aes.lua Line 128 in a1596e5
|
||||
local evp_md, digest_length_int = getDigestAlgorithm(dtype) | ||||
if key == nil or msg == nil then | ||||
error("attempt to digest with a null key or message") | ||||
end | ||||
|
||||
C.HMAC(evp_md, key, #key, msg, #msg, buf, digest_len) | ||||
|
||||
if raw == true then | ||||
return ffi_str(buf,digest_length_int) | ||||
end | ||||
|
||||
return resty_string.to_hex(ffi_str(buf,digest_length_int)) | ||||
end | ||||
|
||||
|
||||
return _M |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
# vi:ft= | ||
|
||
use Test::Nginx::Socket; | ||
|
||
repeat_each(2); | ||
|
||
plan tests => repeat_each() * (3 * blocks()); | ||
|
||
our $HttpConfig = <<'_EOC_'; | ||
#lua_code_cache off; | ||
lua_package_path 'lib/?.lua;;'; | ||
lua_package_cpath 'lib/?.so;;'; | ||
_EOC_ | ||
|
||
no_long_string(); | ||
|
||
run_tests(); | ||
|
||
__DATA__ | ||
|
||
=== TEST 1: hello HMAC-SHA-1 | ||
--- http_config eval: $::HttpConfig | ||
--- config | ||
location /t { | ||
content_by_lua ' | ||
local resty_hmac_sha1 = require "resty.hmac" | ||
local hmac_sha1 = resty_hmac_sha1:new() | ||
|
||
local digest = hmac_sha1:digest("sha1","secret-key","Hello world") | ||
ngx.say("hmac_sha1: ", digest) | ||
|
||
--test with an empty string | ||
digest = hmac_sha1:digest("sha1","secret-key","") | ||
ngx.say("hmac_sha1: ", digest) | ||
'; | ||
} | ||
--- request | ||
GET /t | ||
--- response_body | ||
hmac_sha1: 3a20c85ba3af4c1b1eec24c672cfb3db803e3637 | ||
hmac_sha1: 0877fcf3af864ddf56157f9f4e39eb48dedd74fd | ||
--- no_error_log | ||
[error] | ||
|
||
=== TEST 2: hello HMAC-SHA-224 | ||
--- http_config eval: $::HttpConfig | ||
--- config | ||
location /t { | ||
content_by_lua ' | ||
local resty_hmac_sha224 = require "resty.hmac" | ||
local hmac_sha224 = resty_hmac_sha224:new() | ||
|
||
local digest = hmac_sha224:digest("sha224","secret-key","Hello world") | ||
ngx.say("hmac_sha224: ", digest) | ||
|
||
--test with an empty string | ||
digest = hmac_sha224:digest("sha224","secret-key","") | ||
ngx.say("hmac_sha224: ", digest) | ||
'; | ||
} | ||
--- request | ||
GET /t | ||
--- response_body | ||
hmac_sha224: a38aa774b3d7f49e6f3a7006cdfac9f8aeab4427dd3fb47123be5874 | ||
hmac_sha224: a41ef5660a729abe83238c6921861ddd157a2314df03d98252a9ecac | ||
--- no_error_log | ||
[error] | ||
|
||
|
||
=== TEST 3: hello HMAC-SHA-256 | ||
--- http_config eval: $::HttpConfig | ||
--- config | ||
location /t { | ||
content_by_lua ' | ||
local resty_hmac_sha256 = require "resty.hmac" | ||
local hmac_sha256 = resty_hmac_sha256:new() | ||
|
||
local digest = hmac_sha256:digest("sha256","secret-key","Hello world") | ||
ngx.say("hmac_sha256: ", digest) | ||
|
||
--test with an empty string | ||
digest = hmac_sha256:digest("sha256","secret-key","") | ||
ngx.say("hmac_sha256: ", digest) | ||
'; | ||
} | ||
--- request | ||
GET /t | ||
--- response_body | ||
hmac_sha256: 902dd133c19fef9216f144694b1b9cc9e06c7be3252019f7e12909ff07122220 | ||
hmac_sha256: 345fba21f06a4f75ed673fb93dc16cd47d8dc7a69f52e84e3016fcf69835fdb8 | ||
--- no_error_log | ||
[error] | ||
|
||
|
||
=== TEST 4: hello HMAC-SHA-384 | ||
--- http_config eval: $::HttpConfig | ||
--- config | ||
location /t { | ||
content_by_lua ' | ||
local resty_hmac_sha384 = require "resty.hmac" | ||
local hmac_sha384 = resty_hmac_sha384:new() | ||
|
||
local digest = hmac_sha384:digest("sha384","secret-key","Hello world") | ||
ngx.say("hmac_sha384: ", digest) | ||
|
||
--test with an empty string | ||
digest = hmac_sha384:digest("sha384","secret-key","") | ||
ngx.say("hmac_sha384: ", digest) | ||
'; | ||
} | ||
--- request | ||
GET /t | ||
--- response_body | ||
hmac_sha384: 7baab15202e53288e026c9b318c08527692ad27ef8903bcb405bcd097e4ed7611cb542d760234ef04536da3a16e906bf | ||
hmac_sha384: 3e4c852a0ce874f8bef33bb899b7fa938f5ce8418bafc530e7c2df532b7be4ad49f5b57ca49c50d9080c16b74ef124dc | ||
--- no_error_log | ||
[error] | ||
|
||
|
||
|
||
=== TEST 5: hello HMAC-SHA-512 | ||
--- http_config eval: $::HttpConfig | ||
--- config | ||
location /t { | ||
content_by_lua ' | ||
local resty_hmac_sha512 = require "resty.hmac" | ||
local hmac_sha512 = resty_hmac_sha512:new() | ||
|
||
local digest = hmac_sha512:digest("sha512","secret-key","Hello world") | ||
ngx.say("hmac_sha512: ", digest) | ||
|
||
--test with an empty string | ||
digest = hmac_sha512:digest("sha512","secret-key","") | ||
ngx.say("hmac_sha512: ", digest) | ||
'; | ||
} | ||
--- request | ||
GET /t | ||
--- response_body | ||
hmac_sha512: cbec52174d245ed147e57860e9317605895e1b4af43b080701bccb083f2194cd3ada40623420c2ab1b4c77ce6e6d26b149128867035eba259d495524fa230dee | ||
hmac_sha512: 1560eeb87551d027de6007027af3faab5f644f8ef96519c4b519531a6620c755b61d210f179754f991607151b4b9a9db3377132b9e8587f803cdf8763499bcdc | ||
--- no_error_log | ||
[error] | ||
|
||
=== TEST 6: test with an invalid HMAC algorithm | ||
--- http_config eval: $::HttpConfig | ||
--- config | ||
location /t { | ||
content_by_lua ' | ||
local resty_hmac_sha256 = require "resty.hmac" | ||
local hmac_sha256 = resty_hmac_sha256:new() | ||
|
||
local digest = hmac_sha256:digest("INVALID_SHA","secret-key","Hello world") | ||
ngx.say("hmac_sha256: ", digest) | ||
|
||
--test with an empty string | ||
digest = hmac_sha256:digest("INVALID_SHA","secret-key","") | ||
ngx.say("hmac_sha256: ", digest) | ||
'; | ||
} | ||
--- request | ||
GET /t | ||
--- response_body_like | ||
.*500 Internal Server Error.* | ||
--- error_code: 500 | ||
--- grep_error_log eval: qr/attempt to use unknown algorithm: 'INVALID_SHA'.*?/ | ||
--- grep_error_log_out | ||
attempt to use unknown algorithm: 'INVALID_SHA' | ||
|
||
=== TEST 7: test with null secret or message | ||
--- http_config eval: $::HttpConfig | ||
--- config | ||
location /t { | ||
content_by_lua ' | ||
local resty_hmac_sha256 = require "resty.hmac" | ||
local hmac_sha256 = resty_hmac_sha256:new() | ||
|
||
local digest = hmac_sha256:digest("sha256",nil,"Hello world") | ||
ngx.say("hmac_sha256: ", digest) | ||
|
||
digest = hmac_sha256:digest("sha256",nil,"") | ||
ngx.say("hmac_sha256: ", digest) | ||
'; | ||
} | ||
--- request | ||
GET /t | ||
--- response_body_like | ||
.*500 Internal Server Error.* | ||
--- error_code: 500 | ||
--- grep_error_log eval: qr/attempt to digest with a null key or message.*?/ | ||
--- grep_error_log_out | ||
attempt to digest with a null key or message | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this declaring a single buffer instance that gets reused across all instances of hmac? I'm new to Lua, but if this is the case, that would be dangerous (wouldn't it?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ljwagerfield This is a temporary buffer that is used inside a single Lua function that never yields. Since nginx uses single threaded processes and it does not yield for Lua light threads, it is safe.