bunkerweb/lib/resty/openssl/param.lua
Théophile Diot 411cd2df5a Squashed 'src/deps/src/lua-resty-openssl/' changes from e56da6c5f..529f0c5ad
529f0c5ad release: 1.5.0
1f7d7b326 tests(*) unload provider to make valgrind happy
766955521 fix(param) fix issue when gettable schema may be overwritten by settable schema
8c366c22c fix(param) save converted value to prevent potential use-after-free
a0711de99 fix(x509.csr) fix potential use-after-free in set_extension and add_extension
407d31ec3 fix(x509.*) fix potential use-after-free when get or set subject_alt_name, info_access and dist_points
e0872dcfa chore(x509.*) use const type name
b16f759c2 fix(x509.store) fix potential use-after-free in store:verify and store:check_revocation
48ab40148 tests(ci) catch more GC corner cases
e924ee045 fix(pkey) fix potential use-after-free in pkey.paramgen (#176)
224fae68c fix(bn) fix potential use-after-free in bn.new (#177)
a88f1ba30 fix(x509.store) fix the string is not NUL terminated in set_purpose (#174)
d94064cc7 fix(objects): fix a buffer overflow issue in find_sigid_algs. (#175)
7d6d8b5d2 fix(asn1) correct time_t to be 64 bits type (#171)
30bc5b7f4 doc(examples) update comment for raw-sign-and-recover

git-subtree-dir: src/deps/src/lua-resty-openssl
git-subtree-split: 529f0c5ad1a3275a2313b68003650e7c5693dc3d
2024-07-24 12:22:21 +01:00

339 lines
No EOL
10 KiB
Lua

local ffi = require "ffi"
local C = ffi.C
local ffi_new = ffi.new
local ffi_str = ffi.string
local ffi_cast = ffi.cast
require "resty.openssl.include.param"
local format_error = require("resty.openssl.err").format_error
local bn_lib = require("resty.openssl.bn")
local null = require("resty.openssl.auxiliary.ctypes").null
local nkeys = require "resty.openssl.auxiliary.compat".nkeys
local OSSL_PARAM_INTEGER = 1
local OSSL_PARAM_UNSIGNED_INTEGER = 2
local OSSL_PARAM_REAL = 3
local OSSL_PARAM_UTF8_STRING = 4
local OSSL_PARAM_OCTET_STRING = 5
local OSSL_PARAM_UTF8_PTR = 6
local OSSL_PARAM_OCTET_PTR = 7
local alter_type_key = {}
local buf_param_key = {}
local buf_anchor_key = {}
local function construct(buf_t, length, types_map, types_size)
if not length then
length = nkeys(buf_t)
end
local params = ffi_new("OSSL_PARAM[?]", length + 1)
local i = 0
local buf_param, buf_anchored
for key, value in pairs(buf_t) do
if key == buf_anchor_key then
goto continue
end
local typ = types_map[key]
if not typ then
return nil, "param:construct: unknown key \"" .. key .. "\""
end
local param, buf, size
if value == null then -- out
value = nil
size = types_size and types_size[key] or 100
if typ == OSSL_PARAM_UTF8_STRING or typ == OSSL_PARAM_OCTET_STRING then
buf = ffi_new("char[?]", size)
end
else
local numeric = type(value) == "number"
if (numeric and typ >= OSSL_PARAM_UTF8_STRING) or
(not numeric and typ <= OSSL_PARAM_UNSIGNED_INTEGER) then
local alter_typ = types_map[alter_type_key] and types_map[alter_type_key][key]
if alter_typ and ((numeric and alter_typ <= OSSL_PARAM_UNSIGNED_INTEGER) or
(not numeric and alter_typ >= OSSL_PARAM_UTF8_STRING)) then
typ = alter_typ
else
return nil, "param:construct: key \"" .. key .. "\" can't be a " .. type(value)
end
end
end
if typ == "bn" then -- out only
buf = ffi_new("char[?]", size)
param = C.OSSL_PARAM_construct_BN(key, buf, size)
buf_param = buf_param or {}
buf_param[key] = param
elseif typ == OSSL_PARAM_INTEGER then
buf = value and ffi_new("int[1]", value) or ffi_new("int[1]")
param = C.OSSL_PARAM_construct_int(key, buf)
elseif typ == OSSL_PARAM_UNSIGNED_INTEGER then
buf = value and ffi_new("unsigned int[1]", value) or
ffi_new("unsigned int[1]")
param = C.OSSL_PARAM_construct_uint(key, buf)
elseif typ == OSSL_PARAM_UTF8_STRING then
buf = value ~= nil and ffi_cast("char *", value) or buf
param = C.OSSL_PARAM_construct_utf8_string(key, buf, value and #value or size)
elseif typ == OSSL_PARAM_OCTET_STRING then
buf = value ~= nil and ffi_cast("char *", value) or buf
param = C.OSSL_PARAM_construct_octet_string(key, ffi_cast("void*", buf),
value and #value or size)
elseif typ == OSSL_PARAM_UTF8_PTR then -- out only
buf = ffi_new("char*[1]")
param = C.OSSL_PARAM_construct_utf8_ptr(key, buf, 0)
elseif typ == OSSL_PARAM_OCTET_PTR then -- out only
buf = ffi_new("char*[1]")
param = C.OSSL_PARAM_construct_octet_ptr(key, ffi_cast("void**", buf), 0)
else
error("type " .. typ .. " is not yet implemented")
end
if value == nil then -- out
buf_t[key] = buf
else -- in
-- save value as OSSL_PARAM_construct_* doesn't copy the value
buf_anchored = buf_anchored or {}
buf_anchored[key] = buf
end
params[i] = param
i = i + 1
::continue::
end
buf_t[buf_anchor_key] = buf_anchored
buf_t[buf_param_key] = buf_param
params[length] = C.OSSL_PARAM_construct_end()
return params
end
local function parse(buf_t, length, types_map, types_size)
for key, buf in pairs(buf_t) do
local typ = types_map[key]
local sz = types_size and types_size[key]
if key == buf_param_key then -- luacheck: ignore
-- ignore
elseif buf == nil or buf[0] == nil then
buf_t[key] = nil
elseif typ == "bn" then
local bn_t = ffi_new("BIGNUM*[1]")
local param = buf_t[buf_param_key][key]
if C.OSSL_PARAM_get_BN(param, bn_t) ~= 1 then
return nil, format_error("param:parse: OSSL_PARAM_get_BN")
end
buf_t[key] = assert(bn_lib.dup(bn_t[0]))
C.BN_free(bn_t[0])
elseif typ == OSSL_PARAM_INTEGER or
typ == OSSL_PARAM_UNSIGNED_INTEGER then
buf_t[key] = tonumber(buf[0])
elseif typ == OSSL_PARAM_UTF8_STRING or
typ == OSSL_PARAM_OCTET_STRING then
buf_t[key] = sz and ffi_str(buf, sz) or ffi_str(buf)
elseif typ == OSSL_PARAM_UTF8_PTR or
typ == OSSL_PARAM_OCTET_PTR then
buf_t[key] = sz and ffi_str(buf[0], sz) or ffi_str(buf[0])
elseif not typ then
return nil, "param:parse: unknown key type \"" .. key .. "\""
else
error("type " .. typ .. " is not yet implemented")
end
end
-- for GC
buf_t[buf_param_key] = nil
return buf_t
end
local param_type_readable = {
[OSSL_PARAM_UNSIGNED_INTEGER] = "unsigned integer",
[OSSL_PARAM_INTEGER] = "integer",
[OSSL_PARAM_REAL] = "real number",
[OSSL_PARAM_UTF8_PTR] = "pointer to a UTF8 encoded string",
[OSSL_PARAM_UTF8_STRING] = "UTF8 encoded string",
[OSSL_PARAM_OCTET_PTR] = "pointer to an octet string",
[OSSL_PARAM_OCTET_STRING] = "octet string",
}
local function readable_data_type(p)
local typ = p.data_type
local literal = param_type_readable[typ]
if not literal then
literal = string.format("unknown type [%d]", typ)
end
local sz = tonumber(p.data_size)
if sz == 0 then
literal = literal .. " (arbitrary size)"
else
literal = literal .. string.format(" (max %d bytes large)", sz)
end
return literal
end
local function parse_params_schema(params, schema, schema_readable)
if params == nil then
return nil, format_error("parse_params_schema")
end
local i = 0
while true do
local p = params[i]
if p.key == nil then
break
end
local key = ffi_str(p.key)
if schema then
-- TODO: don't support same key with different types for now
-- prefer string type over integer types
local typ = tonumber(p.data_type)
if schema[key] then
schema[alter_type_key] = schema[alter_type_key] or {}
schema[alter_type_key][key] = typ
else
schema[key] = typ
end
end
-- if schema_return_size then -- only non-ptr string types are needed actually
-- schema_return_size[key] = tonumber(p.return_size)
-- end
if schema_readable then
table.insert(schema_readable, { key, readable_data_type(p) })
end
i = i + 1
end
return schema
end
local param_maps_set, param_maps_get = {}, {}
local function get_params_func(typ, field)
local typ_lower = typ:sub(5):lower()
if typ_lower:sub(-4) == "_ctx" then
typ_lower = typ_lower:sub(0, -5)
end
-- field name for indexing schema, usually the (const) one created by
-- EVP_TYP_fetch or EVP_get_typebynam,e
field = field or "algo"
local cf_settable = C[typ .. "_settable_params"]
local settable = function(self, raw)
local k = self[field]
if raw and param_maps_set[k] then
return param_maps_set[k]
end
local param = cf_settable(self.ctx)
-- no params, this is fine, shouldn't be regarded as an error
if param == nil then
param_maps_set[k] = {}
return {}
end
local schema, schema_reabale = {}, raw and nil or {}
parse_params_schema(param, schema, schema_reabale)
param_maps_set[k] = schema
return raw and schema or schema_reabale
end
local cf_set = C[typ .. "_set_params"]
local set = function(self, params)
if not param_maps_set[self[field]] then
local ok, err = self:settable_params(true) -- only query raw schema to save memory
if not ok then
return false, typ_lower .. ":set_params: " .. err
end
end
local oparams, err = construct(params, nil, param_maps_set[self[field]])
if err then
return false, typ_lower .. ":set_params: " .. err
end
if cf_set(self.ctx, oparams) ~= 1 then
return false, format_error(typ_lower .. ":set_params: " .. typ .. "_set_params")
end
return true
end
local cf_gettable = C[typ .. "_gettable_params"]
local gettable = function(self, raw)
local k = self[field]
if raw and param_maps_get[k] then
return param_maps_get[k]
end
local param = cf_gettable(self.ctx)
-- no params, this is fine, shouldn't be regarded as an error
if param == nil then
param_maps_get[k] = {}
return {}
end
local schema, schema_reabale = {}, raw and nil or {}
parse_params_schema(param, schema, schema_reabale)
param_maps_get[k] = schema
return raw and schema or schema_reabale
end
local cf_get = C[typ .. "_get_params"]
local get_buffer, get_size_map = {}, {}
local get = function(self, key, want_size, want_type)
if not param_maps_get[self[field]] then
local ok, err = self:gettable_params(true) -- only query raw schema to save memory
if not ok then
return false, typ_lower .. ":set_params: " .. err
end
end
local schema = param_maps_get[self[field]]
if schema == nil or not schema[key] then -- nil or null
return nil, typ_lower .. ":get_param: unknown key \"" .. key .. "\""
end
table.clear(get_buffer)
table.clear(get_size_map)
get_buffer[key] = null
get_size_map[key] = want_size
schema = want_type and { [key] = want_type } or schema
local req, err = construct(get_buffer, 1, schema, get_size_map)
if not req then
return nil, typ_lower .. ":get_param: failed to construct params: " .. err
end
if cf_get(self.ctx, req) ~= 1 then
return nil, format_error(typ_lower .. ":get_param:get")
end
get_buffer, err = parse(get_buffer, 1, schema, get_size_map)
if err then
return nil, typ_lower .. ":get_param: failed to parse params: " .. err
end
return get_buffer[key]
end
return settable, set, gettable, get
end
return {
OSSL_PARAM_INTEGER = OSSL_PARAM_INTEGER,
OSSL_PARAM_UNSIGNED_INTEGER = OSSL_PARAM_INTEGER,
OSSL_PARAM_REAL = OSSL_PARAM_REAL,
OSSL_PARAM_UTF8_STRING = OSSL_PARAM_UTF8_STRING,
OSSL_PARAM_OCTET_STRING = OSSL_PARAM_OCTET_STRING,
OSSL_PARAM_UTF8_PTR = OSSL_PARAM_UTF8_PTR,
OSSL_PARAM_OCTET_PTR = OSSL_PARAM_OCTET_PTR,
construct = construct,
parse = parse,
parse_params_schema = parse_params_schema,
get_params_func = get_params_func,
}