aboutsummaryrefslogtreecommitdiffstats
path: root/server/resty/openssl/kdf.lua
diff options
context:
space:
mode:
Diffstat (limited to 'server/resty/openssl/kdf.lua')
-rw-r--r--server/resty/openssl/kdf.lua388
1 files changed, 388 insertions, 0 deletions
diff --git a/server/resty/openssl/kdf.lua b/server/resty/openssl/kdf.lua
new file mode 100644
index 0000000..62188bc
--- /dev/null
+++ b/server/resty/openssl/kdf.lua
@@ -0,0 +1,388 @@
+local ffi = require "ffi"
+local C = ffi.C
+local ffi_gc = ffi.gc
+local ffi_str = ffi.string
+
+require("resty.openssl.objects")
+require("resty.openssl.include.evp.md")
+-- used by legacy EVP_PKEY_derive interface
+require("resty.openssl.include.evp.pkey")
+local kdf_macro = require "resty.openssl.include.evp.kdf"
+local ctx_lib = require "resty.openssl.ctx"
+local format_error = require("resty.openssl.err").format_error
+local version_num = require("resty.openssl.version").version_num
+local version_text = require("resty.openssl.version").version_text
+local BORINGSSL = require("resty.openssl.version").BORINGSSL
+local OPENSSL_3X = require("resty.openssl.version").OPENSSL_3X
+local ctypes = require "resty.openssl.auxiliary.ctypes"
+
+--[[
+https://wiki.openssl.org/index.php/EVP_Key_Derivation
+
+OpenSSL 1.0.2 and above provides PBKDF2 by way of PKCS5_PBKDF2_HMAC and PKCS5_PBKDF2_HMAC_SHA1.
+OpenSSL 1.1.0 and above additionally provides HKDF and TLS1 PRF KDF by way of EVP_PKEY_derive and Scrypt by way of EVP_PBE_scrypt
+OpenSSL 1.1.1 and above additionally provides Scrypt by way of EVP_PKEY_derive.
+OpenSSL 3.0 additionally provides Single Step KDF, SSH KDF, PBKDF2, Scrypt, HKDF, ANSI X9.42 KDF, ANSI X9.63 KDF and TLS1 PRF KDF by way of EVP_KDF.
+From OpenSSL 3.0 the recommended way of performing key derivation is to use the EVP_KDF functions. If compatibility with OpenSSL 1.1.1 is required then a limited set of KDFs can be used via EVP_PKEY_derive.
+]]
+
+local NID_id_pbkdf2 = -1
+local NID_id_scrypt = -2
+local NID_tls1_prf = -3
+local NID_hkdf = -4
+if version_num >= 0x10002000 then
+ NID_id_pbkdf2 = C.OBJ_txt2nid("PBKDF2")
+ assert(NID_id_pbkdf2 > 0)
+end
+if version_num >= 0x10100000 and not BORINGSSL then
+ NID_hkdf = C.OBJ_txt2nid("HKDF")
+ assert(NID_hkdf > 0)
+ NID_tls1_prf = C.OBJ_txt2nid("TLS1-PRF")
+ assert(NID_tls1_prf > 0)
+ -- we use EVP_PBE_scrypt to do scrypt, so this is supported >= 1.1.0
+ NID_id_scrypt = C.OBJ_txt2nid("id-scrypt")
+ assert(NID_id_scrypt > 0)
+end
+
+local _M = {
+ HKDEF_MODE_EXTRACT_AND_EXPAND = kdf_macro.EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND,
+ HKDEF_MODE_EXTRACT_ONLY = kdf_macro.EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY,
+ HKDEF_MODE_EXPAND_ONLY = kdf_macro.EVP_PKEY_HKDEF_MODE_EXPAND_ONLY,
+
+ PBKDF2 = NID_id_pbkdf2,
+ SCRYPT = NID_id_scrypt,
+ TLS1_PRF = NID_tls1_prf,
+ HKDF = NID_hkdf,
+}
+
+local type_literals = {
+ [NID_id_pbkdf2] = "PBKDF2",
+ [NID_id_scrypt] = "scrypt",
+ [NID_tls1_prf] = "TLS-1PRF",
+ [NID_hkdf] = "HKDF",
+}
+
+local TYPE_NUMBER = 0x1
+local TYPE_STRING = 0x2
+
+local function check_options(opt, nid, field, typ, is_optional, required_only_if_nid)
+ local v = opt[field]
+ if not v then
+ if is_optional or (required_only_if_nid and required_only_if_nid ~= nid) then
+ return typ == TYPE_NUMBER and 0 or nil
+ else
+ return nil, "\"" .. field .. "\" must be set"
+ end
+ end
+
+ if typ == TYPE_NUMBER then
+ v = tonumber(v)
+ if not typ then
+ return nil, "except a number as \"" .. field .. "\""
+ end
+ elseif typ == TYPE_STRING then
+ if type(v) ~= "string" then
+ return nil, "except a string as \"" .. field .. "\""
+ end
+ else
+ error("don't known how to check " .. typ, 2)
+ end
+
+ return v
+end
+
+local function check_hkdf_options(opt)
+ local mode = opt.hkdf_mode
+ if not mode or version_num < 0x10101000 then
+ mode = _M.HKDEF_MODE_EXTRACT_AND_EXPAND
+ end
+
+ if mode == _M.HKDEF_MODE_EXTRACT_AND_EXPAND and (
+ not opt.salt or not opt.hkdf_info) then
+ return '""salt" and "hkdf_info" are required for EXTRACT_AND_EXPAND mode'
+ elseif mode == _M.HKDEF_MODE_EXTRACT_ONLY and not opt.salt then
+ return '"salt" is required for EXTRACT_ONLY mode'
+ elseif mode == _M.EVP_PKEY_HKDEF_MODE_EXPAND_ONLY and not opt.hkdf_info then
+ return '"hkdf_info" is required for EXPAND_ONLY mode'
+ end
+
+ return nil
+end
+
+local options_schema = {
+ outlen = { TYPE_NUMBER },
+ pass = { TYPE_STRING, true },
+ salt = { TYPE_STRING, true },
+ md = { TYPE_STRING, true },
+ -- pbkdf2 only
+ pbkdf2_iter = { TYPE_NUMBER, true },
+ -- hkdf only
+ hkdf_key = { TYPE_STRING, nil, NID_hkdf },
+ hkdf_mode = { TYPE_NUMBER, true },
+ hkdf_info = { TYPE_STRING, true },
+ -- tls1-prf
+ tls1_prf_secret = { TYPE_STRING, nil, NID_tls1_prf },
+ tls1_prf_seed = { TYPE_STRING, nil, NID_tls1_prf },
+ -- scrypt only
+ scrypt_maxmem = { TYPE_NUMBER, true },
+ scrypt_N = { TYPE_NUMBER, nil, NID_id_scrypt },
+ scrypt_r = { TYPE_NUMBER, nil, NID_id_scrypt },
+ scrypt_p = { TYPE_NUMBER, nil, NID_id_scrypt },
+}
+
+local outlen = ctypes.ptr_of_uint64()
+
+function _M.derive(options)
+ local typ = options.type
+ if not typ then
+ return nil, "kdf.derive: \"type\" must be set"
+ elseif type(typ) ~= "number" then
+ return nil, "kdf.derive: expect a number as \"type\""
+ end
+
+ if typ <= 0 then
+ return nil, "kdf.derive: kdf type " .. (type_literals[typ] or tostring(typ)) ..
+ " not supported in " .. version_text
+ end
+
+ for k, v in pairs(options_schema) do
+ local v, err = check_options(options, typ, k, unpack(v))
+ if err then
+ return nil, "kdf.derive: " .. err
+ end
+ options[k] = v
+ end
+
+ if typ == NID_hkdf then
+ local err = check_hkdf_options(options)
+ if err then
+ return nil, "kdf.derive: " .. err
+ end
+ end
+
+ local salt_len = 0
+ if options.salt then
+ salt_len = #options.salt
+ end
+ local pass_len = 0
+ if options.pass then
+ pass_len = #options.pass
+ end
+
+ local md
+ if OPENSSL_3X then
+ md = C.EVP_MD_fetch(ctx_lib.get_libctx(), options.md or 'sha1', options.properties)
+ else
+ md = C.EVP_get_digestbyname(options.md or 'sha1')
+ end
+ if md == nil then
+ return nil, string.format("kdf.derive: invalid digest type \"%s\"", md)
+ end
+
+ local buf = ctypes.uchar_array(options.outlen)
+
+ -- begin legacay low level routines
+ local code
+ if typ == NID_id_pbkdf2 then
+ -- make openssl 1.0.2 happy
+ if version_num < 0x10100000 and not options.pass then
+ options.pass = ""
+ pass_len = 0
+ end
+ -- https://www.openssl.org/docs/man1.1.0/man3/PKCS5_PBKDF2_HMAC.html
+ local iter = options.pbkdf2_iter
+ if iter < 1 then
+ iter = 1
+ end
+ code = C.PKCS5_PBKDF2_HMAC(
+ options.pass, pass_len,
+ options.salt, salt_len, iter,
+ md, options.outlen, buf
+ )
+ elseif typ == NID_id_scrypt then
+ code = C.EVP_PBE_scrypt(
+ options.pass, pass_len,
+ options.salt, salt_len,
+ options.scrypt_N, options.scrypt_r, options.scrypt_p, options.scrypt_maxmem,
+ buf, options.outlen
+ )
+ elseif typ ~= NID_tls1_prf and typ ~= NID_hkdf then
+ return nil, string.format("kdf.derive: unknown type %d", typ)
+ end
+ if code then
+ if code ~= 1 then
+ return nil, format_error("kdf.derive")
+ else
+ return ffi_str(buf, options.outlen)
+ end
+ end
+ -- end legacay low level routines
+
+ -- begin EVP_PKEY_derive routines
+ outlen[0] = options.outlen
+
+ local ctx = C.EVP_PKEY_CTX_new_id(typ, nil)
+ if ctx == nil then
+ return nil, format_error("kdf.derive: EVP_PKEY_CTX_new_id")
+ end
+ ffi_gc(ctx, C.EVP_PKEY_CTX_free)
+ if C.EVP_PKEY_derive_init(ctx) ~= 1 then
+ return nil, format_error("kdf.derive: EVP_PKEY_derive_init")
+ end
+
+ if typ == NID_tls1_prf then
+ if kdf_macro.EVP_PKEY_CTX_set_tls1_prf_md(ctx, md) ~= 1 then
+ return nil, format_error("kdf.derive: EVP_PKEY_CTX_set_tls1_prf_md")
+ end
+ if kdf_macro.EVP_PKEY_CTX_set1_tls1_prf_secret(ctx, options.tls1_prf_secret) ~= 1 then
+ return nil, format_error("kdf.derive: EVP_PKEY_CTX_set1_tls1_prf_secret")
+ end
+ if kdf_macro.EVP_PKEY_CTX_add1_tls1_prf_seed(ctx, options.tls1_prf_seed) ~= 1 then
+ return nil, format_error("kdf.derive: EVP_PKEY_CTX_add1_tls1_prf_seed")
+ end
+ elseif typ == NID_hkdf then
+ if kdf_macro.EVP_PKEY_CTX_set_hkdf_md(ctx, md) ~= 1 then
+ return nil, format_error("kdf.derive: EVP_PKEY_CTX_set_hkdf_md")
+ end
+ if options.salt and
+ kdf_macro.EVP_PKEY_CTX_set1_hkdf_salt(ctx, options.salt) ~= 1 then
+ return nil, format_error("kdf.derive: EVP_PKEY_CTX_set1_hkdf_salt")
+ end
+ if options.hkdf_key and
+ kdf_macro.EVP_PKEY_CTX_set1_hkdf_key(ctx, options.hkdf_key) ~= 1 then
+ return nil, format_error("kdf.derive: EVP_PKEY_CTX_set1_hkdf_key")
+ end
+ if options.hkdf_info and
+ kdf_macro.EVP_PKEY_CTX_add1_hkdf_info(ctx, options.hkdf_info) ~= 1 then
+ return nil, format_error("kdf.derive: EVP_PKEY_CTX_add1_hkdf_info")
+ end
+ if options.hkdf_mode then
+ if version_num >= 0x10101000 then
+ if kdf_macro.EVP_PKEY_CTX_set_hkdf_mode(ctx, options.hkdf_mode) ~= 1 then
+ return nil, format_error("kdf.derive: EVP_PKEY_CTX_set_hkdf_mode")
+ end
+ if options.hkdf_mode == _M.HKDEF_MODE_EXTRACT_ONLY then
+ local md_size = OPENSSL_3X and C.EVP_MD_get_size(md) or C.EVP_MD_size(md)
+ if options.outlen ~= md_size then
+ options.outlen = md_size
+ ngx.log(ngx.WARN, "hkdf_mode EXTRACT_ONLY outputs fixed length of ", md_size,
+ " key, ignoring options.outlen")
+ end
+ outlen[0] = md_size
+ buf = ctypes.uchar_array(md_size)
+ end
+ else
+ ngx.log(ngx.WARN, "hkdf_mode is not effective in ", version_text)
+ end
+ end
+ else
+ return nil, string.format("kdf.derive: unknown type %d", typ)
+ end
+ code = C.EVP_PKEY_derive(ctx, buf, outlen)
+ if code == -2 then
+ return nil, "kdf.derive: operation is not supported by the public key algorithm"
+ end
+ -- end EVP_PKEY_derive routines
+
+ return ffi_str(buf, options.outlen)
+end
+
+if not OPENSSL_3X then
+ return _M
+end
+
+_M.derive_legacy = _M.derive
+_M.derive = nil
+
+-- OPENSSL 3.0 style API
+local param_lib = require "resty.openssl.param"
+local SIZE_MAX = ctypes.SIZE_MAX
+
+local mt = {__index = _M}
+
+local kdf_ctx_ptr_ct = ffi.typeof('EVP_KDF_CTX*')
+
+function _M.new(typ, properties)
+ local algo = C.EVP_KDF_fetch(ctx_lib.get_libctx(), typ, properties)
+ if algo == nil then
+ return nil, format_error(string.format("mac.new: invalid mac type \"%s\"", typ))
+ end
+
+ local ctx = C.EVP_KDF_CTX_new(algo)
+ if ctx == nil then
+ return nil, "mac.new: failed to create EVP_MAC_CTX"
+ end
+ ffi_gc(ctx, C.EVP_KDF_CTX_free)
+
+ local buf
+ local buf_size = tonumber(C.EVP_KDF_CTX_get_kdf_size(ctx))
+ if buf_size == SIZE_MAX then -- no fixed size
+ buf_size = nil
+ else
+ buf = ctypes.uchar_array(buf_size)
+ end
+
+ return setmetatable({
+ ctx = ctx,
+ algo = algo,
+ buf = buf,
+ buf_size = buf_size,
+ }, mt), nil
+end
+
+function _M.istype(l)
+ return l and l.ctx and ffi.istype(kdf_ctx_ptr_ct, l.ctx)
+end
+
+function _M:get_provider_name()
+ local p = C.EVP_KDF_get0_provider(self.algo)
+ if p == nil then
+ return nil
+ end
+ return ffi_str(C.OSSL_PROVIDER_get0_name(p))
+end
+
+_M.settable_params, _M.set_params, _M.gettable_params, _M.get_param = param_lib.get_params_func("EVP_KDF_CTX")
+
+function _M:derive(outlen, options, options_count)
+ if not _M.istype(self) then
+ return _M.derive_legacy(self)
+ end
+
+ if self.buf_size and outlen then
+ return nil, string.format("kdf:derive: this KDF has fixed output size %d, "..
+ "it can't be set manually", self.buf_size)
+ end
+
+ outlen = self.buf_size or outlen
+ local buf = self.buf or ctypes.uchar_array(outlen)
+
+ if options_count then
+ options_count = options_count - 1
+ else
+ options_count = 0
+ for k, v in pairs(options) do options_count = options_count + 1 end
+ end
+
+ local param, err
+ if options_count > 0 then
+ local schema = self:settable_params(true) -- raw schema
+ param, err = param_lib.construct(options, nil, schema)
+ if err then
+ return nil, "kdf:derive: " .. err
+ end
+ end
+
+ if C.EVP_KDF_derive(self.ctx, buf, outlen, param) ~= 1 then
+ return nil, format_error("kdf:derive")
+ end
+
+ return ffi_str(buf, outlen)
+end
+
+function _M:reset()
+ C.EVP_KDF_CTX_reset(self.ctx)
+ return true
+end
+
+return _M \ No newline at end of file