Skip to content

Commit 0625be9

Browse files
committed
feat(mac) add EVP_MAC
1 parent e0c3d61 commit 0625be9

File tree

3 files changed

+253
-0
lines changed

3 files changed

+253
-0
lines changed

lib/resty/openssl/include/evp/mac.lua

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
local ffi = require "ffi"
2+
3+
require "resty.openssl.include.ossl_typ"
4+
require "resty.openssl.include.provider"
5+
6+
ffi.cdef [[
7+
typedef struct evp_mac_st EVP_MAC;
8+
typedef struct evp_mac_ctx_st EVP_MAC_CTX;
9+
10+
EVP_MAC_CTX *EVP_MAC_CTX_new(EVP_MAC *mac);
11+
void EVP_MAC_CTX_free(EVP_MAC_CTX *ctx);
12+
13+
const OSSL_PROVIDER *EVP_MAC_get0_provider(const EVP_MAC *mac);
14+
EVP_MAC *EVP_MAC_fetch(OSSL_LIB_CTX *libctx, const char *algorithm,
15+
const char *properties);
16+
17+
int EVP_MAC_init(EVP_MAC_CTX *ctx, const unsigned char *key, size_t keylen,
18+
const OSSL_PARAM params[]);
19+
int EVP_MAC_update(EVP_MAC_CTX *ctx, const unsigned char *data, size_t datalen);
20+
int EVP_MAC_final(EVP_MAC_CTX *ctx,
21+
unsigned char *out, size_t *outl, size_t outsize);
22+
23+
size_t EVP_MAC_CTX_get_mac_size(EVP_MAC_CTX *ctx);
24+
]]

lib/resty/openssl/mac.lua

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
local ffi = require "ffi"
2+
local C = ffi.C
3+
local ffi_gc = ffi.gc
4+
local ffi_str = ffi.string
5+
6+
require "resty.openssl.include.evp.mac"
7+
local params_macro = require "resty.openssl.include.param"
8+
local ctypes = require "resty.openssl.auxiliary.ctypes"
9+
local format_error = require("resty.openssl.err").format_error
10+
local OPENSSL_30 = require("resty.openssl.version").OPENSSL_30
11+
12+
local _M = {}
13+
local mt = {__index = _M}
14+
15+
local mac_ctx_ptr_ct = ffi.typeof('EVP_MAC*')
16+
local param_types = {
17+
cipher = params_macro.OSSL_PARAM_UTF8_STRING,
18+
digest = params_macro.OSSL_PARAM_UTF8_STRING,
19+
}
20+
local params = {}
21+
22+
function _M.new(key, typ, cipher, digest, properties)
23+
if not OPENSSL_30 then
24+
return false, "EVP_MAC is only supported from OpenSSL 3.0"
25+
end
26+
27+
local dtyp = C.EVP_MAC_fetch(nil, typ, properties)
28+
if dtyp == nil then
29+
return nil, format_error(string.format("mac.new: invalid mac type \"%s\"", typ))
30+
end
31+
32+
local ctx = C.EVP_MAC_CTX_new(dtyp)
33+
if ctx == nil then
34+
return nil, "mac.new: failed to create EVP_MAC_CTX"
35+
end
36+
ffi_gc(ctx, C.EVP_MAC_CTX_free)
37+
38+
params.digest = digest
39+
params.cipher = cipher
40+
local p = params_macro.construct(params, 2, param_types)
41+
42+
local code = C.EVP_MAC_init(ctx, key, #key, p)
43+
if code ~= 1 then
44+
return nil, format_error(string.format("mac.new: invalid cipher or digest type"))
45+
end
46+
47+
local md_size = C.EVP_MAC_CTX_get_mac_size(ctx)
48+
49+
return setmetatable({
50+
ctx = ctx,
51+
dtyp = dtyp,
52+
buf = ctypes.uchar_array(md_size),
53+
buf_size = md_size,
54+
}, mt), nil
55+
end
56+
57+
function _M.istype(l)
58+
return l and l.ctx and ffi.istype(mac_ctx_ptr_ct, l.ctx)
59+
end
60+
61+
function _M:get_provider_name()
62+
local p = C.EVP_MAC_get0_provider(self.dtyp)
63+
if p == nil then
64+
return nil
65+
end
66+
return ffi_str(C.OSSL_PROVIDER_get0_name(p))
67+
end
68+
69+
function _M:update(...)
70+
for _, s in ipairs({...}) do
71+
if C.EVP_MAC_update(self.ctx, s, #s) ~= 1 then
72+
return false, format_error("digest:update")
73+
end
74+
end
75+
return true, nil
76+
end
77+
78+
function _M:final(s)
79+
if s then
80+
local _, err = self:update(s)
81+
if err then
82+
return nil, err
83+
end
84+
end
85+
86+
local length = ctypes.ptr_of_size_t()
87+
-- no return value of EVP_DigestFinal_ex
88+
C.EVP_MAC_final(self.ctx, self.buf, length, self.buf_size)
89+
if length[0] == nil or length[0] <= 0 then
90+
return nil, format_error("digest:final: EVP_MAC_final")
91+
end
92+
return ffi_str(self.buf, length[0])
93+
end
94+
95+
return _M

t/openssl/mac.t

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# vim:set ft= ts=4 sw=4 et fdm=marker:
2+
3+
use Test::Nginx::Socket::Lua 'no_plan';
4+
use Cwd qw(cwd);
5+
6+
7+
my $pwd = cwd();
8+
9+
my $use_luacov = $ENV{'TEST_NGINX_USE_LUACOV'} // '';
10+
11+
our $HttpConfig = qq{
12+
lua_package_path "$pwd/t/openssl/?.lua;$pwd/lib/?.lua;$pwd/lib/?/init.lua;;";
13+
init_by_lua_block {
14+
if "1" == "$use_luacov" then
15+
require 'luacov.tick'
16+
jit.off()
17+
end
18+
_G.myassert = require("helper").myassert
19+
}
20+
};
21+
22+
run_tests();
23+
24+
__DATA__
25+
=== TEST 1: Calculate hmac correctly
26+
--- http_config eval: $::HttpConfig
27+
--- config
28+
location =/t {
29+
content_by_lua_block {
30+
if not require("resty.openssl.version").OPENSSL_30 then
31+
ngx.say("kwUMjYrP0BSJb8cIJvWYoiM1Kc4mQxZOTwSiTTLRhDM=")
32+
ngx.exit(0)
33+
end
34+
35+
local hmac = myassert(require("resty.openssl.mac").new("goose", "HMAC", nil, "sha256"))
36+
37+
myassert(hmac:update("🦢🦢🦢🦢🦢🦢"))
38+
ngx.print(ngx.encode_base64(myassert(hmac:final())))
39+
}
40+
}
41+
--- request
42+
GET /t
43+
--- response_body_like eval
44+
"kwUMjYrP0BSJb8cIJvWYoiM1Kc4mQxZOTwSiTTLRhDM="
45+
--- no_error_log
46+
[error]
47+
48+
=== TEST 2: Update accepts vardiac args
49+
--- http_config eval: $::HttpConfig
50+
--- config
51+
location =/t {
52+
content_by_lua_block {
53+
if not require("resty.openssl.version").OPENSSL_30 then
54+
ngx.say("kwUMjYrP0BSJb8cIJvWYoiM1Kc4mQxZOTwSiTTLRhDM=")
55+
ngx.exit(0)
56+
end
57+
58+
local hmac = myassert(require("resty.openssl.mac").new("goose", "HMAC", nil, "sha256"))
59+
60+
hmac:update("🦢", "🦢🦢", "🦢🦢", "🦢")
61+
ngx.print(ngx.encode_base64(hmac:final()))
62+
}
63+
}
64+
--- request
65+
GET /t
66+
--- response_body_like eval
67+
"kwUMjYrP0BSJb8cIJvWYoiM1Kc4mQxZOTwSiTTLRhDM="
68+
--- no_error_log
69+
[error]
70+
71+
=== TEST 3: Final accepts optional arg
72+
--- http_config eval: $::HttpConfig
73+
--- config
74+
location =/t {
75+
content_by_lua_block {
76+
if not require("resty.openssl.version").OPENSSL_30 then
77+
ngx.say("kwUMjYrP0BSJb8cIJvWYoiM1Kc4mQxZOTwSiTTLRhDM=")
78+
ngx.exit(0)
79+
end
80+
81+
local hmac = myassert(require("resty.openssl.mac").new("goose", "HMAC", nil, "sha256"))
82+
83+
myassert(hmac:update("🦢", "🦢🦢", "🦢🦢"))
84+
ngx.print(ngx.encode_base64(myassert(hmac:final("🦢"))))
85+
}
86+
}
87+
--- request
88+
GET /t
89+
--- response_body_like eval
90+
"kwUMjYrP0BSJb8cIJvWYoiM1Kc4mQxZOTwSiTTLRhDM="
91+
--- no_error_log
92+
[error]
93+
94+
=== TEST 4: Rejects unknown hash
95+
--- http_config eval: $::HttpConfig
96+
--- config
97+
location =/t {
98+
content_by_lua_block {
99+
if not require("resty.openssl.version").OPENSSL_30 then
100+
ngx.say("mac.new: invalid cipher or digest type")
101+
ngx.exit(0)
102+
end
103+
local hmac, err = require("resty.openssl.mac").new("goose", "HMAC", nil, "sha257")
104+
ngx.print(err)
105+
}
106+
}
107+
--- request
108+
GET /t
109+
--- response_body_like eval
110+
"mac.new: invalid cipher or digest type.*"
111+
--- no_error_log
112+
[error]
113+
114+
=== TEST 5: Returns provider
115+
--- http_config eval: $::HttpConfig
116+
--- config
117+
location =/t {
118+
content_by_lua_block {
119+
if not require("resty.openssl.version").OPENSSL_30 then
120+
ngx.say("default")
121+
ngx.exit(0)
122+
end
123+
124+
local mac = require("resty.openssl.mac")
125+
local m = myassert(mac.new("goose", "HMAC", nil, "sha256"))
126+
ngx.say(myassert(m:get_provider_name()))
127+
}
128+
}
129+
--- request
130+
GET /t
131+
--- response_body
132+
default
133+
--- no_error_log
134+
[error]

0 commit comments

Comments
 (0)