diff --git a/README.md b/README.md index 9d4db252..58b95702 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,8 @@ Table of Contents + [ssl_ctx.from_request](#ssl_ctxfrom_request) + [ssl_ctx.from_socket](#ssl_ctxfrom_socket) + [ssl_ctx:set_alpns](#ssl_ctxset_alpns) + * [resty.openssl.crypto](#restyopensslcrypto) + + [crypto.memcmp](#cryptomemcmp) * [Functions for stack-like objects](#functions-for-stack-like-objects) + [metamethods](#metamethods) + [each](#each) @@ -4518,6 +4520,23 @@ literal for the protocol, like `"h2"`. [Back to TOC](#table-of-contents) +## resty.openssl.crypto + +Module to interact with utility openssl functions. + +[Back to TOC](#table-of-contents) + +### crypto.memcmp + +**syntax**: *res, err = crypto.memcmp(a, b, len)* + +Performs constant-time comparison of 2 memory regions a and b with len bytes. See [CRYPTO_memcmp](https://docs.openssl.org/3.2/man3/CRYPTO_memcmp) +for more info. The 2 memory regions must be of type string or cdata. + +Returns 0 if the memory regions are equal and nonzero otherwise. + +[Back to TOC](#table-of-contents) + ## Functions for stack-like objects [Back to TOC](#table-of-contents) diff --git a/lib/resty/openssl/crypto.lua b/lib/resty/openssl/crypto.lua new file mode 100644 index 00000000..84eb2340 --- /dev/null +++ b/lib/resty/openssl/crypto.lua @@ -0,0 +1,19 @@ +local ffi = require "ffi" +local ffi_cast = ffi.cast +local C = ffi.C +require "resty.openssl.include.crypto" + +local _M = {} + +function _M.memcmp(a, b, len) + if type(len) ~= "number" or len < 1 then + return nil, "crypto:memcmp arg 'len' must be a number > 0" + end + if type(a) ~= "string" and type(a) ~= "cdata" then + return nil, "crypto:memcmp only strings and cdata types are supported" + end + + return C.CRYPTO_memcmp(ffi_cast("void*", a), ffi_cast("void*", b), len) +end + +return _M diff --git a/lib/resty/openssl/include/crypto.lua b/lib/resty/openssl/include/crypto.lua index 59711d05..ee812627 100644 --- a/lib/resty/openssl/include/crypto.lua +++ b/lib/resty/openssl/include/crypto.lua @@ -5,6 +5,7 @@ ffi.cdef [[ int FIPS_mode(void); int FIPS_mode_set(int ONOFF); void CRYPTO_free(void *ptr, const char *file, int line); + int CRYPTO_memcmp(const void *a, const void *b, size_t len); ]] local OPENSSL_free = function(ptr) diff --git a/t/openssl/crypto.t b/t/openssl/crypto.t new file mode 100644 index 00000000..36de5853 --- /dev/null +++ b/t/openssl/crypto.t @@ -0,0 +1,147 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua 'no_plan'; +use Cwd qw(cwd); + + +my $pwd = cwd(); + +my $use_luacov = $ENV{'TEST_NGINX_USE_LUACOV'} // ''; + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;$pwd/lib/?/init.lua;;"; + init_by_lua_block { + if "1" == "$use_luacov" then + require 'luacov.tick' + jit.off() + end + } +}; + +run_tests(); + +__DATA__ + +=== TEST 1: memcmp: memory regions are equal +--- http_config eval: $::HttpConfig +--- config + location =/t { + content_by_lua_block { + local ffi = require("ffi") + local crypto = require("resty.openssl.crypto") + local res, err + + -- test string + res, err = crypto.memcmp("abc", "abc", 3) + ngx.say(res) + ngx.say(err) + + -- test cdata + local ffi_string_a = ffi.new("char[?]", 3) + ffi.copy(ffi_string_a, "abc") + local ffi_string_b = ffi.new("char[?]", 3) + ffi.copy(ffi_string_b, "abc") + res, err = crypto.memcmp(ffi_string_a, ffi_string_b, 3) + ngx.say(res) + ngx.say(err) + } + } +--- request + GET /t +--- response_body +0 +nil +0 +nil +--- no_error_log +[error] + +=== TEST 2: memcmp: memory regions are not equal +--- http_config eval: $::HttpConfig +--- config + location =/t { + content_by_lua_block { + local ffi = require("ffi") + local crypto = require("resty.openssl.crypto") + local res, err + + -- test string + res, err = crypto.memcmp("abc", "acc", 3) + ngx.say(res) + ngx.say(err) + + -- test cdata + local ffi_string_a = ffi.new("char[?]", 3) + ffi.copy(ffi_string_a, "abc") + local ffi_string_b = ffi.new("char[?]", 3) + ffi.copy(ffi_string_b, "acc") + res, err = crypto.memcmp(ffi_string_a, ffi_string_b, 3) + ngx.say(res) + ngx.say(err) + + ffi_string_a = ffi.new("char[?]", 4) + ffi.copy(ffi_string_a, "abc") + ffi_string_b = ffi.new("char[?]", 4) + ffi.copy(ffi_string_b, "abcd") + res, err = crypto.memcmp(ffi_string_a, ffi_string_b, 4) + ngx.say(res) + ngx.say(err) + } + } +--- request + GET /t +--- response_body +1 +nil +1 +nil +1 +nil +--- no_error_log +[error] + +=== TEST 3: memcmp: invalid arg len +--- http_config eval: $::HttpConfig +--- config + location =/t { + content_by_lua_block { + local crypto = require("resty.openssl.crypto") + local res, err = crypto.memcmp("123123123123", "123", -1) + ngx.say(res) + ngx.say(err) + } + } +--- request + GET /t +--- response_body +nil +crypto:memcmp arg 'len' must be a number > 0 +--- no_error_log +[error] + +=== TEST 4: memcmp: invalid arg types +--- http_config eval: $::HttpConfig +--- config + location =/t { + content_by_lua_block { + local crypto = require("resty.openssl.crypto") + res, err = crypto.memcmp(100, 102, 1) + ngx.say(res) + ngx.say(err) + + local table_a = { "a", b=1, 101 } + local table_b = { "a", b=1, 102 } + res, err = crypto.memcmp(table_a, table_b, 2) + ngx.say(res) + ngx.say(err) + } + } +--- request + GET /t +--- response_body +nil +crypto:memcmp only strings and cdata types are supported +nil +crypto:memcmp only strings and cdata types are supported +--- no_error_log +[error]