152 lines
4.0 KiB
Lua
152 lines
4.0 KiB
Lua
local ffi = require "ffi"
|
|
|
|
|
|
local C = ffi.C
|
|
local ffi_new = ffi.new
|
|
|
|
|
|
ffi.cdef[[
|
|
typedef unsigned char u_char;
|
|
|
|
int RAND_bytes(u_char *buf, int num);
|
|
|
|
unsigned long ERR_get_error(void);
|
|
void ERR_load_crypto_strings(void);
|
|
void ERR_free_strings(void);
|
|
|
|
const char *ERR_reason_error_string(unsigned long e);
|
|
|
|
int open(const char * filename, int flags, ...);
|
|
size_t read(int fd, void *buf, size_t count);
|
|
int write(int fd, const void *ptr, int numbytes);
|
|
int close(int fd);
|
|
char *strerror(int errnum);
|
|
]]
|
|
|
|
|
|
local _M = {}
|
|
|
|
|
|
local get_rand_bytes
|
|
do
|
|
local ngx_log = ngx.log
|
|
local WARN = ngx.WARN
|
|
|
|
local system_constants = require "lua_system_constants"
|
|
local O_RDONLY = system_constants.O_RDONLY()
|
|
local ffi_fill = ffi.fill
|
|
local ffi_str = ffi.string
|
|
local bytes_buf_t = ffi.typeof "char[?]"
|
|
|
|
local function urandom_bytes(buf, size)
|
|
local fd = C.open("/dev/urandom", O_RDONLY, 0) -- mode is ignored
|
|
if fd < 0 then
|
|
ngx_log(WARN, "Error opening random fd: ",
|
|
ffi_str(C.strerror(ffi.errno())))
|
|
|
|
return false
|
|
end
|
|
|
|
local res = C.read(fd, buf, size)
|
|
if res <= 0 then
|
|
ngx_log(WARN, "Error reading from urandom: ",
|
|
ffi_str(C.strerror(ffi.errno())))
|
|
|
|
return false
|
|
end
|
|
|
|
if C.close(fd) ~= 0 then
|
|
ngx_log(WARN, "Error closing urandom: ",
|
|
ffi_str(C.strerror(ffi.errno())))
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- try to get n_bytes of CSPRNG data, first via /dev/urandom,
|
|
-- and then falling back to OpenSSL if necessary
|
|
get_rand_bytes = function(n_bytes, urandom)
|
|
local buf = ffi_new(bytes_buf_t, n_bytes)
|
|
ffi_fill(buf, n_bytes, 0x0)
|
|
|
|
-- only read from urandom if we were explicitly asked
|
|
if urandom then
|
|
local rc = urandom_bytes(buf, n_bytes)
|
|
|
|
-- if the read of urandom was successful, we returned true
|
|
-- and buf is filled with our bytes, so return it as a string
|
|
if rc then
|
|
return ffi_str(buf, n_bytes)
|
|
end
|
|
end
|
|
|
|
if C.RAND_bytes(buf, n_bytes) == 0 then
|
|
-- get error code
|
|
local err_code = C.ERR_get_error()
|
|
if err_code == 0 then
|
|
return nil, "could not get SSL error code from the queue"
|
|
end
|
|
|
|
-- get human-readable error string
|
|
C.ERR_load_crypto_strings()
|
|
local err = C.ERR_reason_error_string(err_code)
|
|
C.ERR_free_strings()
|
|
|
|
return nil, "could not get random bytes (" ..
|
|
"reason:" .. ffi_str(err) .. ") "
|
|
end
|
|
|
|
return ffi_str(buf, n_bytes)
|
|
end
|
|
end
|
|
_M.get_rand_bytes = get_rand_bytes
|
|
|
|
|
|
--- Generates a random unique string
|
|
-- @return string The random string (a chunk of base64ish-encoded random bytes)
|
|
local random_string
|
|
do
|
|
local rand = math.random
|
|
local byte = string.byte
|
|
local encode_base64 = ngx.encode_base64
|
|
|
|
local OUTPUT = require("string.buffer").new(32)
|
|
local SLASH_BYTE = byte("/")
|
|
local PLUS_BYTE = byte("+")
|
|
|
|
-- generate a random-looking string by retrieving a chunk of bytes and
|
|
-- replacing non-alphanumeric characters with random alphanumeric replacements
|
|
-- (we dont care about deriving these bytes securely)
|
|
-- this serves to attempt to maintain some backward compatibility with the
|
|
-- previous implementation (stripping a UUID of its hyphens), while significantly
|
|
-- expanding the size of the keyspace.
|
|
random_string = function()
|
|
OUTPUT:reset()
|
|
|
|
-- get 24 bytes, which will return a 32 char string after encoding
|
|
-- this is done in attempt to maintain backwards compatibility as
|
|
-- much as possible while improving the strength of this function
|
|
-- TODO: in future we may want to have variable length option to this
|
|
local str = encode_base64(get_rand_bytes(24, true), true)
|
|
|
|
for i = 1, 32 do
|
|
local b = byte(str, i)
|
|
if b == SLASH_BYTE then
|
|
OUTPUT:putf("%c", rand(65, 90)) -- A-Z
|
|
elseif b == PLUS_BYTE then
|
|
OUTPUT:putf("%c", rand(97, 122)) -- a-z
|
|
else
|
|
OUTPUT:putf("%c", b)
|
|
end
|
|
end
|
|
|
|
str = OUTPUT:get()
|
|
|
|
return str
|
|
end
|
|
end
|
|
_M.random_string = random_string
|
|
|
|
|
|
return _M
|