aboutsummaryrefslogtreecommitdiffstats
path: root/server/resty/openssl/pkey.lua
diff options
context:
space:
mode:
authorFiete Ostkamp <Fiete.Ostkamp@telekom.de>2023-04-14 11:59:32 +0000
committerFiete Ostkamp <Fiete.Ostkamp@telekom.de>2023-04-14 11:59:32 +0000
commitd68841d9f75636575cd778838a8ceea5fd5aada3 (patch)
tree778c84203ed9bfa4dc1c8234e4e2cf60da6ebd8c /server/resty/openssl/pkey.lua
parent42af09588f1f839b9ab36356f02f34c89559bcfa (diff)
Upload ui
Issue-ID: PORTAL-1084 Signed-off-by: Fiete Ostkamp <Fiete.Ostkamp@telekom.de> Change-Id: Id0c94859a775094e67b0bb9c91ca5e776a08c068
Diffstat (limited to 'server/resty/openssl/pkey.lua')
-rw-r--r--server/resty/openssl/pkey.lua942
1 files changed, 942 insertions, 0 deletions
diff --git a/server/resty/openssl/pkey.lua b/server/resty/openssl/pkey.lua
new file mode 100644
index 0000000..69c5aae
--- /dev/null
+++ b/server/resty/openssl/pkey.lua
@@ -0,0 +1,942 @@
+local ffi = require "ffi"
+local C = ffi.C
+local ffi_gc = ffi.gc
+local ffi_new = ffi.new
+local ffi_str = ffi.string
+local ffi_cast = ffi.cast
+local ffi_copy = ffi.copy
+
+local rsa_macro = require "resty.openssl.include.rsa"
+local dh_macro = require "resty.openssl.include.dh"
+require "resty.openssl.include.bio"
+require "resty.openssl.include.pem"
+require "resty.openssl.include.x509"
+require "resty.openssl.include.evp.pkey"
+local evp_macro = require "resty.openssl.include.evp"
+local pkey_macro = require "resty.openssl.include.evp.pkey"
+local bio_util = require "resty.openssl.auxiliary.bio"
+local digest_lib = require "resty.openssl.digest"
+local rsa_lib = require "resty.openssl.rsa"
+local dh_lib = require "resty.openssl.dh"
+local ec_lib = require "resty.openssl.ec"
+local ecx_lib = require "resty.openssl.ecx"
+local objects_lib = require "resty.openssl.objects"
+local jwk_lib = require "resty.openssl.auxiliary.jwk"
+local ctx_lib = require "resty.openssl.ctx"
+local ctypes = require "resty.openssl.auxiliary.ctypes"
+local format_error = require("resty.openssl.err").format_error
+
+local OPENSSL_11_OR_LATER = require("resty.openssl.version").OPENSSL_11_OR_LATER
+local OPENSSL_111_OR_LATER = require("resty.openssl.version").OPENSSL_111_OR_LATER
+local OPENSSL_3X = require("resty.openssl.version").OPENSSL_3X
+local BORINGSSL = require("resty.openssl.version").BORINGSSL
+
+local ptr_of_uint = ctypes.ptr_of_uint
+local ptr_of_size_t = ctypes.ptr_of_size_t
+local ptr_of_int = ctypes.ptr_of_int
+
+local null = ctypes.null
+local load_pem_args = { null, null, null }
+local load_der_args = { null }
+
+local get_pkey_key
+if OPENSSL_11_OR_LATER then
+ get_pkey_key = {
+ [evp_macro.EVP_PKEY_RSA] = function(ctx) return C.EVP_PKEY_get0_RSA(ctx) end,
+ [evp_macro.EVP_PKEY_EC] = function(ctx) return C.EVP_PKEY_get0_EC_KEY(ctx) end,
+ [evp_macro.EVP_PKEY_DH] = function(ctx) return C.EVP_PKEY_get0_DH(ctx) end
+ }
+else
+ get_pkey_key = {
+ [evp_macro.EVP_PKEY_RSA] = function(ctx) return ctx.pkey and ctx.pkey.rsa end,
+ [evp_macro.EVP_PKEY_EC] = function(ctx) return ctx.pkey and ctx.pkey.ec end,
+ [evp_macro.EVP_PKEY_DH] = function(ctx) return ctx.pkey and ctx.pkey.dh end,
+ }
+end
+
+local load_rsa_key_funcs
+
+if not OPENSSL_3X then
+ load_rsa_key_funcs= {
+ ['PEM_read_bio_RSAPrivateKey'] = true,
+ ['PEM_read_bio_RSAPublicKey'] = true,
+ } -- those functions return RSA* instead of EVP_PKEY*
+end
+
+local function load_pem_der(txt, opts, funcs)
+ local fmt = opts.format or '*'
+ if fmt ~= 'PEM' and fmt ~= 'DER' and fmt ~= "JWK" and fmt ~= '*' then
+ return nil, "expecting 'DER', 'PEM', 'JWK' or '*' as \"format\""
+ end
+
+ local typ = opts.type or '*'
+ if typ ~= 'pu' and typ ~= 'pr' and typ ~= '*' then
+ return nil, "expecting 'pr', 'pu' or '*' as \"type\""
+ end
+
+ if fmt == "JWK" and (typ == "pu" or type == "pr") then
+ return nil, "explictly load private or public key from JWK format is not supported"
+ end
+
+ ngx.log(ngx.DEBUG, "load key using fmt: ", fmt, ", type: ", typ)
+
+ local bio = C.BIO_new_mem_buf(txt, #txt)
+ if bio == nil then
+ return nil, "BIO_new_mem_buf() failed"
+ end
+ ffi_gc(bio, C.BIO_free)
+
+ local ctx
+
+ local fs = funcs[fmt][typ]
+ local passphrase_cb
+ for f, arg in pairs(fs) do
+ -- don't need BIO when loading JWK key: we parse it in Lua land
+ if f == "load_jwk" then
+ local err
+ ctx, err = jwk_lib[f](txt)
+ if ctx == nil then
+ -- if fmt is explictly set to JWK, we should return an error now
+ if fmt == "JWK" then
+ return nil, err
+ end
+ ngx.log(ngx.DEBUG, "jwk decode failed: ", err, ", continuing")
+ end
+ else
+ -- #define BIO_CTRL_RESET 1
+ local code = C.BIO_ctrl(bio, 1, 0, nil)
+ if code ~= 1 then
+ return nil, "BIO_ctrl() failed"
+ end
+
+ -- only pass in passphrase/passphrase_cb to PEM_* functions
+ if fmt == "PEM" or (fmt == "*" and arg == load_pem_args) then
+ if opts.passphrase then
+ local passphrase = opts.passphrase
+ if type(passphrase) ~= "string" then
+ -- clear errors occur when trying
+ C.ERR_clear_error()
+ return nil, "passphrase must be a string"
+ end
+ arg = { null, nil, passphrase }
+ elseif opts.passphrase_cb then
+ passphrase_cb = passphrase_cb or ffi_cast("pem_password_cb", function(buf, size)
+ local p = opts.passphrase_cb()
+ local len = #p -- 1 byte for \0
+ if len > size then
+ ngx.log(ngx.WARN, "pkey:load_pem_der: passphrase truncated from ", len, " to ", size)
+ len = size
+ end
+ ffi_copy(buf, p, len)
+ return len
+ end)
+ arg = { null, passphrase_cb, null }
+ end
+ end
+
+ ctx = C[f](bio, unpack(arg))
+ end
+
+ if ctx ~= nil then
+ ngx.log(ngx.DEBUG, "pkey:load_pem_der: loaded pkey using function ", f)
+
+ -- pkcs1 functions create a rsa rather than evp_pkey
+ -- disable the checking in openssl 3.0 for sail safe
+ if not OPENSSL_3X and load_rsa_key_funcs[f] then
+ local rsa = ctx
+ ctx = C.EVP_PKEY_new()
+ if ctx == null then
+ return nil, format_error("pkey:load_pem_der: EVP_PKEY_new")
+ end
+
+ if C.EVP_PKEY_assign(ctx, evp_macro.EVP_PKEY_RSA, rsa) ~= 1 then
+ C.RSA_free(rsa)
+ C.EVP_PKEY_free(ctx)
+ return nil, "pkey:load_pem_der: EVP_PKEY_assign() failed"
+ end
+ end
+
+ break
+ end
+ end
+ if passphrase_cb ~= nil then
+ passphrase_cb:free()
+ end
+
+ if ctx == nil then
+ return nil, format_error()
+ end
+ -- clear errors occur when trying
+ C.ERR_clear_error()
+ return ctx, nil
+end
+
+local function generate_param(key_type, config)
+ if key_type == evp_macro.EVP_PKEY_DH then
+ local dh_group = config.group
+ if dh_group then
+ local get_group_func = dh_macro.dh_groups[dh_group]
+ if not get_group_func then
+ return nil, "unknown pre-defined group " .. dh_group
+ end
+ local ctx = get_group_func()
+ if ctx == nil then
+ return nil, format_error("DH_get_x")
+ end
+ local params = C.EVP_PKEY_new()
+ if not params then
+ return nil, format_error("EVP_PKEY_new")
+ end
+ ffi_gc(params, C.EVP_PKEY_free)
+ if C.EVP_PKEY_assign(params, key_type, ctx) ~= 1 then
+ return nil, format_error("EVP_PKEY_assign")
+ end
+ return params
+ end
+ end
+
+ local pctx = C.EVP_PKEY_CTX_new_id(key_type, nil)
+ if pctx == nil then
+ return nil, format_error("EVP_PKEY_CTX_new_id")
+ end
+ ffi_gc(pctx, C.EVP_PKEY_CTX_free)
+
+ if C.EVP_PKEY_paramgen_init(pctx) ~= 1 then
+ return nil, format_error("EVP_PKEY_paramgen_init")
+ end
+
+ if key_type == evp_macro.EVP_PKEY_EC then
+ local curve = config.curve or 'prime192v1'
+ local nid = C.OBJ_ln2nid(curve)
+ if nid == 0 then
+ return nil, "unknown curve " .. curve
+ end
+ if pkey_macro.EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, nid) <= 0 then
+ return nil, format_error("EVP_PKEY_CTX_ctrl: EC: curve_nid")
+ end
+ if not BORINGSSL then
+ -- use the named-curve encoding for best backward-compatibilty
+ -- and for playing well with go:crypto/x509
+ -- # define OPENSSL_EC_NAMED_CURVE 0x001
+ if pkey_macro.EVP_PKEY_CTX_set_ec_param_enc(pctx, 1) <= 0 then
+ return nil, format_error("EVP_PKEY_CTX_ctrl: EC: param_enc")
+ end
+ end
+ elseif key_type == evp_macro.EVP_PKEY_DH then
+ local bits = config.bits
+ if not config.param and not bits then
+ bits = 2048
+ end
+ if bits and pkey_macro.EVP_PKEY_CTX_set_dh_paramgen_prime_len(pctx, bits) <= 0 then
+ return nil, format_error("EVP_PKEY_CTX_ctrl: DH: bits")
+ end
+ end
+
+ local ctx_ptr = ffi_new("EVP_PKEY*[1]")
+ if C.EVP_PKEY_paramgen(pctx, ctx_ptr) ~= 1 then
+ return nil, format_error("EVP_PKEY_paramgen")
+ end
+
+ local params = ctx_ptr[0]
+ ffi_gc(params, C.EVP_PKEY_free)
+
+ return params
+end
+
+local load_param_funcs = {
+ [evp_macro.EVP_PKEY_EC] = {
+ ["*"] = {
+ ["*"] = {
+ ['PEM_read_bio_ECPKParameters'] = load_pem_args,
+ -- ['d2i_ECPKParameters_bio'] = load_der_args,
+ }
+ },
+ },
+ [evp_macro.EVP_PKEY_DH] = {
+ ["*"] = {
+ ["*"] = {
+ ['PEM_read_bio_DHparams'] = load_pem_args,
+ -- ['d2i_DHparams_bio'] = load_der_args,
+ }
+ },
+ },
+}
+
+local function generate_key(config)
+ local typ = config.type or 'RSA'
+ local key_type
+
+ if typ == "RSA" then
+ key_type = evp_macro.EVP_PKEY_RSA
+ elseif typ == "EC" then
+ key_type = evp_macro.EVP_PKEY_EC
+ elseif typ == "DH" then
+ key_type = evp_macro.EVP_PKEY_DH
+ elseif evp_macro.ecx_curves[typ] then
+ key_type = evp_macro.ecx_curves[typ]
+ else
+ return nil, "unsupported type " .. typ
+ end
+ if key_type == 0 then
+ return nil, "the linked OpenSSL library doesn't support " .. typ .. " key"
+ end
+
+ local pctx
+
+ if key_type == evp_macro.EVP_PKEY_EC or key_type == evp_macro.EVP_PKEY_DH then
+ local params, err
+ if config.param then
+ -- HACK
+ config.type = nil
+ local ctx, err = load_pem_der(config.param, config, load_param_funcs[key_type])
+ if err then
+ return nil, "load_pem_der: " .. err
+ end
+ if key_type == evp_macro.EVP_PKEY_EC then
+ local ec_group = ctx
+ ffi_gc(ec_group, C.EC_GROUP_free)
+ ctx = C.EC_KEY_new()
+ if ctx == nil then
+ return nil, "EC_KEY_new() failed"
+ end
+ if C.EC_KEY_set_group(ctx, ec_group) ~= 1 then
+ return nil, format_error("EC_KEY_set_group")
+ end
+ end
+ params = C.EVP_PKEY_new()
+ if not params then
+ return nil, format_error("EVP_PKEY_new")
+ end
+ ffi_gc(params, C.EVP_PKEY_free)
+ if C.EVP_PKEY_assign(params, key_type, ctx) ~= 1 then
+ return nil, format_error("EVP_PKEY_assign")
+ end
+ else
+ params, err = generate_param(key_type, config)
+ if err then
+ return nil, "generate_param: " .. err
+ end
+ end
+ pctx = C.EVP_PKEY_CTX_new(params, nil)
+ if pctx == nil then
+ return nil, format_error("EVP_PKEY_CTX_new")
+ end
+ else
+ pctx = C.EVP_PKEY_CTX_new_id(key_type, nil)
+ if pctx == nil then
+ return nil, format_error("EVP_PKEY_CTX_new_id")
+ end
+ end
+
+ ffi_gc(pctx, C.EVP_PKEY_CTX_free)
+
+ if C.EVP_PKEY_keygen_init(pctx) ~= 1 then
+ return nil, format_error("EVP_PKEY_keygen_init")
+ end
+ -- RSA key parameters are set for keygen ctx not paramgen
+ if key_type == evp_macro.EVP_PKEY_RSA then
+ local bits = config.bits or 2048
+ if bits > 4294967295 then
+ return nil, "bits out of range"
+ end
+
+ if pkey_macro.EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, bits) <= 0 then
+ return nil, format_error("EVP_PKEY_CTX_ctrl: RSA: bits")
+ end
+
+ if config.exp then
+ -- don't free exp as it's used internally in key
+ local exp = C.BN_new()
+ if exp == nil then
+ return nil, "BN_new() failed"
+ end
+ C.BN_set_word(exp, config.exp)
+
+ if pkey_macro.EVP_PKEY_CTX_set_rsa_keygen_pubexp(pctx, exp) <= 0 then
+ return nil, format_error("EVP_PKEY_CTX_ctrl: RSA: exp")
+ end
+ end
+ end
+ local ctx_ptr = ffi_new("EVP_PKEY*[1]")
+ -- TODO: move to use EVP_PKEY_gen after drop support for <1.1.1
+ if C.EVP_PKEY_keygen(pctx, ctx_ptr) ~= 1 then
+ return nil, format_error("EVP_PKEY_gen")
+ end
+ return ctx_ptr[0]
+end
+
+local load_key_try_funcs = {} do
+ -- TODO: pkcs1 load functions are not required in openssl 3.0
+ local _load_key_try_funcs = {
+ PEM = {
+ -- Note: make sure we always try load priv key first
+ pr = {
+ ['PEM_read_bio_PrivateKey'] = load_pem_args,
+ -- disable in openssl3.0, PEM_read_bio_PrivateKey can read pkcs1 in 3.0
+ ['PEM_read_bio_RSAPrivateKey'] = not OPENSSL_3X and load_pem_args or nil,
+ },
+ pu = {
+ ['PEM_read_bio_PUBKEY'] = load_pem_args,
+ -- disable in openssl3.0, PEM_read_bio_PrivateKey can read pkcs1 in 3.0
+ ['PEM_read_bio_RSAPublicKey'] = not OPENSSL_3X and load_pem_args or nil,
+ },
+ },
+ DER = {
+ pr = { ['d2i_PrivateKey_bio'] = load_der_args, },
+ pu = { ['d2i_PUBKEY_bio'] = load_der_args, },
+ },
+ JWK = {
+ pr = { ['load_jwk'] = {}, },
+ }
+ }
+ -- populate * funcs
+ local all_funcs = {}
+ local typ_funcs = {}
+ for fmt, ffs in pairs(_load_key_try_funcs) do
+ load_key_try_funcs[fmt] = ffs
+
+ local funcs = {}
+ for typ, fs in pairs(ffs) do
+ for f, arg in pairs(fs) do
+ funcs[f] = arg
+ all_funcs[f] = arg
+ if not typ_funcs[typ] then
+ typ_funcs[typ] = {}
+ end
+ typ_funcs[typ] = arg
+ end
+ end
+ load_key_try_funcs[fmt]["*"] = funcs
+ end
+ load_key_try_funcs["*"] = {}
+ load_key_try_funcs["*"]["*"] = all_funcs
+ for typ, fs in pairs(typ_funcs) do
+ load_key_try_funcs[typ] = fs
+ end
+end
+
+local function __tostring(self, is_priv, fmt, is_pkcs1)
+ if fmt == "JWK" then
+ return jwk_lib.dump_jwk(self, is_priv)
+ elseif is_pkcs1 then
+ if fmt ~= "PEM" or self.key_type ~= evp_macro.EVP_PKEY_RSA then
+ return nil, "PKCS#1 format is only supported to encode RSA key in \"PEM\" format"
+ elseif OPENSSL_3X then -- maybe possible with OSSL_ENCODER_CTX_new_for_pkey though
+ return nil, "writing out RSA key in PKCS#1 format is not supported in OpenSSL 3.0"
+ end
+ end
+ if is_priv then
+ if fmt == "DER" then
+ return bio_util.read_wrap(C.i2d_PrivateKey_bio, self.ctx)
+ end
+ -- PEM
+ if is_pkcs1 then
+ local rsa = get_pkey_key[evp_macro.EVP_PKEY_RSA](self.ctx)
+ if rsa == nil then
+ return nil, "unable to read RSA key for writing"
+ end
+ return bio_util.read_wrap(C.PEM_write_bio_RSAPrivateKey,
+ rsa,
+ nil, nil, 0, nil, nil)
+ end
+ return bio_util.read_wrap(C.PEM_write_bio_PrivateKey,
+ self.ctx,
+ nil, nil, 0, nil, nil)
+ else
+ if fmt == "DER" then
+ return bio_util.read_wrap(C.i2d_PUBKEY_bio, self.ctx)
+ end
+ -- PEM
+ if is_pkcs1 then
+ local rsa = get_pkey_key[evp_macro.EVP_PKEY_RSA](self.ctx)
+ if rsa == nil then
+ return nil, "unable to read RSA key for writing"
+ end
+ return bio_util.read_wrap(C.PEM_write_bio_RSAPublicKey, rsa)
+ end
+ return bio_util.read_wrap(C.PEM_write_bio_PUBKEY, self.ctx)
+ end
+
+end
+
+local _M = {}
+local mt = { __index = _M, __tostring = __tostring }
+
+local empty_table = {}
+local evp_pkey_ptr_ct = ffi.typeof('EVP_PKEY*')
+
+function _M.new(s, opts)
+ local ctx, err
+ s = s or {}
+ if type(s) == 'table' then
+ ctx, err = generate_key(s)
+ if err then
+ err = "pkey.new:generate_key: " .. err
+ end
+ elseif type(s) == 'string' then
+ ctx, err = load_pem_der(s, opts or empty_table, load_key_try_funcs)
+ if err then
+ err = "pkey.new:load_key: " .. err
+ end
+ elseif type(s) == 'cdata' then
+ if ffi.istype(evp_pkey_ptr_ct, s) then
+ ctx = s
+ else
+ return nil, "pkey.new: expect a EVP_PKEY* cdata at #1"
+ end
+ else
+ return nil, "pkey.new: unexpected type " .. type(s) .. " at #1"
+ end
+
+ if err then
+ return nil, err
+ end
+
+ ffi_gc(ctx, C.EVP_PKEY_free)
+
+ local key_type = OPENSSL_3X and C.EVP_PKEY_get_base_id(ctx) or C.EVP_PKEY_base_id(ctx)
+ if key_type == 0 then
+ return nil, "pkey.new: cannot get key_type"
+ end
+ local key_type_is_ecx = (key_type == evp_macro.EVP_PKEY_ED25519) or
+ (key_type == evp_macro.EVP_PKEY_X25519) or
+ (key_type == evp_macro.EVP_PKEY_ED448) or
+ (key_type == evp_macro.EVP_PKEY_X448)
+
+ -- although OpenSSL discourages to use this size for digest/verify
+ -- but this is good enough for now
+ local buf_size = OPENSSL_3X and C.EVP_PKEY_get_size(ctx) or C.EVP_PKEY_size(ctx)
+
+ local self = setmetatable({
+ ctx = ctx,
+ pkey_ctx = nil,
+ rsa_padding = nil,
+ key_type = key_type,
+ key_type_is_ecx = key_type_is_ecx,
+ buf = ctypes.uchar_array(buf_size),
+ buf_size = buf_size,
+ }, mt)
+
+ return self, nil
+end
+
+function _M.istype(l)
+ return l and l.ctx and ffi.istype(evp_pkey_ptr_ct, l.ctx)
+end
+
+function _M:get_key_type()
+ return objects_lib.nid2table(self.key_type)
+end
+
+function _M:get_default_digest_type()
+ if BORINGSSL then
+ return nil, "BoringSSL doesn't have default digest for pkey"
+ end
+
+ local nid = ptr_of_int()
+ local code = C.EVP_PKEY_get_default_digest_nid(self.ctx, nid)
+ if code == -2 then
+ return nil, "operation is not supported by the public key algorithm"
+ elseif code <= 0 then
+ return nil, format_error("get_default_digest", code)
+ end
+
+ local ret = objects_lib.nid2table(nid[0])
+ ret.mandatory = code == 2
+ return ret
+end
+
+function _M:get_provider_name()
+ if not OPENSSL_3X then
+ return false, "pkey:get_provider_name is not supported"
+ end
+ local p = C.EVP_PKEY_get0_provider(self.ctx)
+ if p == nil then
+ return nil
+ end
+ return ffi_str(C.OSSL_PROVIDER_get0_name(p))
+end
+
+if OPENSSL_3X then
+ local param_lib = require "resty.openssl.param"
+ _M.settable_params, _M.set_params, _M.gettable_params, _M.get_param = param_lib.get_params_func("EVP_PKEY", "key_type")
+end
+
+function _M:get_parameters()
+ if not self.key_type_is_ecx then
+ local getter = get_pkey_key[self.key_type]
+ if not getter then
+ return nil, "key getter not defined"
+ end
+ local key = getter(self.ctx)
+ if key == nil then
+ return nil, format_error("EVP_PKEY_get0_{key}")
+ end
+
+ if self.key_type == evp_macro.EVP_PKEY_RSA then
+ return rsa_lib.get_parameters(key)
+ elseif self.key_type == evp_macro.EVP_PKEY_EC then
+ return ec_lib.get_parameters(key)
+ elseif self.key_type == evp_macro.EVP_PKEY_DH then
+ return dh_lib.get_parameters(key)
+ end
+ else
+ return ecx_lib.get_parameters(self.ctx)
+ end
+end
+
+function _M:set_parameters(opts)
+ if not self.key_type_is_ecx then
+ local getter = get_pkey_key[self.key_type]
+ if not getter then
+ return nil, "key getter not defined"
+ end
+ local key = getter(self.ctx)
+ if key == nil then
+ return nil, format_error("EVP_PKEY_get0_{key}")
+ end
+
+ if self.key_type == evp_macro.EVP_PKEY_RSA then
+ return rsa_lib.set_parameters(key, opts)
+ elseif self.key_type == evp_macro.EVP_PKEY_EC then
+ return ec_lib.set_parameters(key, opts)
+ elseif self.key_type == evp_macro.EVP_PKEY_DH then
+ return dh_lib.set_parameters(key, opts)
+ end
+ else
+ -- for ecx keys we always create a new EVP_PKEY and release the old one
+ local ctx, err = ecx_lib.set_parameters(self.key_type, self.ctx, opts)
+ if err then
+ return false, err
+ end
+ self.ctx = ctx
+ end
+end
+
+function _M:is_private()
+ local params = self:get_parameters()
+ if self.key_type == evp_macro.EVP_PKEY_RSA then
+ return params.d ~= nil
+ else
+ return params.private ~= nil
+ end
+end
+
+local ASYMMETRIC_OP_ENCRYPT = 0x1
+local ASYMMETRIC_OP_DECRYPT = 0x2
+local ASYMMETRIC_OP_SIGN_RAW = 0x4
+local ASYMMETRIC_OP_VERIFY_RECOVER = 0x8
+
+local function asymmetric_routine(self, s, op, padding)
+ local pkey_ctx
+
+ if self.key_type == evp_macro.EVP_PKEY_RSA then
+ if padding then
+ padding = tonumber(padding)
+ if not padding then
+ return nil, "invalid padding: " .. __tostring(padding)
+ end
+ else
+ padding = rsa_macro.paddings.RSA_PKCS1_PADDING
+ end
+ end
+
+ if self.pkey_ctx ~= nil and
+ (self.key_type ~= evp_macro.EVP_PKEY_RSA or self.rsa_padding == padding) then
+ pkey_ctx = self.pkey_ctx
+ else
+ pkey_ctx = C.EVP_PKEY_CTX_new(self.ctx, nil)
+ if pkey_ctx == nil then
+ return nil, format_error("pkey:asymmetric_routine EVP_PKEY_CTX_new()")
+ end
+ ffi_gc(pkey_ctx, C.EVP_PKEY_CTX_free)
+ self.pkey_ctx = pkey_ctx
+ end
+
+ local f, fint, op_name
+ if op == ASYMMETRIC_OP_ENCRYPT then
+ fint = C.EVP_PKEY_encrypt_init
+ f = C.EVP_PKEY_encrypt
+ op_name = "encrypt"
+ elseif op == ASYMMETRIC_OP_DECRYPT then
+ fint = C.EVP_PKEY_decrypt_init
+ f = C.EVP_PKEY_decrypt
+ op_name = "decrypt"
+ elseif op == ASYMMETRIC_OP_SIGN_RAW then
+ fint = C.EVP_PKEY_sign_init
+ f = C.EVP_PKEY_sign
+ op_name = "sign"
+ elseif op == ASYMMETRIC_OP_VERIFY_RECOVER then
+ fint = C.EVP_PKEY_verify_recover_init
+ f = C.EVP_PKEY_verify_recover
+ op_name = "verify_recover"
+ else
+ error("bad \"op\", got " .. op, 2)
+ end
+
+ local code = fint(pkey_ctx)
+ if code < 1 then
+ return nil, format_error("pkey:asymmetric_routine EVP_PKEY_" .. op_name .. "_init", code)
+ end
+
+ -- EVP_PKEY_CTX_ctrl must be called after *_init
+ if self.key_type == evp_macro.EVP_PKEY_RSA and padding then
+ if pkey_macro.EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding) ~= 1 then
+ return nil, format_error("pkey:asymmetric_routine EVP_PKEY_CTX_set_rsa_padding")
+ end
+ self.rsa_padding = padding
+ end
+
+ local length = ptr_of_size_t(self.buf_size)
+
+ if f(pkey_ctx, self.buf, length, s, #s) <= 0 then
+ return nil, format_error("pkey:asymmetric_routine EVP_PKEY_" .. op_name)
+ end
+
+ return ffi_str(self.buf, length[0]), nil
+end
+
+_M.PADDINGS = rsa_macro.paddings
+
+function _M:encrypt(s, padding)
+ return asymmetric_routine(self, s, ASYMMETRIC_OP_ENCRYPT, padding)
+end
+
+function _M:decrypt(s, padding)
+ return asymmetric_routine(self, s, ASYMMETRIC_OP_DECRYPT, padding)
+end
+
+function _M:sign_raw(s, padding)
+ -- TODO: temporary hack before OpenSSL has proper check for existence of private key
+ if self.key_type_is_ecx and not self:is_private() then
+ return nil, "pkey:sign_raw: missing private key"
+ end
+
+ return asymmetric_routine(self, s, ASYMMETRIC_OP_SIGN_RAW, padding)
+end
+
+function _M:verify_recover(s, padding)
+ return asymmetric_routine(self, s, ASYMMETRIC_OP_VERIFY_RECOVER, padding)
+end
+
+local evp_pkey_ctx_ptr_ptr_ct = ffi.typeof('EVP_PKEY_CTX*[1]')
+
+local function sign_verify_prepare(self, fint, md_alg, padding, opts)
+ local pkey_ctx
+
+ if self.key_type == evp_macro.EVP_PKEY_RSA and padding then
+ pkey_ctx = C.EVP_PKEY_CTX_new(self.ctx, nil)
+ if pkey_ctx == nil then
+ return nil, format_error("pkey:sign_verify_prepare EVP_PKEY_CTX_new()")
+ end
+ ffi_gc(pkey_ctx, C.EVP_PKEY_CTX_free)
+ end
+
+ local md_ctx = C.EVP_MD_CTX_new()
+ if md_ctx == nil then
+ return nil, "pkey:sign_verify_prepare: EVP_MD_CTX_new() failed"
+ end
+ ffi_gc(md_ctx, C.EVP_MD_CTX_free)
+
+ local algo
+ if md_alg then
+ if OPENSSL_3X then
+ algo = C.EVP_MD_fetch(ctx_lib.get_libctx(), md_alg, nil)
+ else
+ algo = C.EVP_get_digestbyname(md_alg)
+ end
+ if algo == nil then
+ return nil, string.format("pkey:sign_verify_prepare: invalid digest type \"%s\"", md_alg)
+ end
+ end
+
+ local ppkey_ctx = evp_pkey_ctx_ptr_ptr_ct()
+ ppkey_ctx[0] = pkey_ctx
+ if fint(md_ctx, ppkey_ctx, algo, nil, self.ctx) ~= 1 then
+ return nil, format_error("pkey:sign_verify_prepare: Init failed")
+ end
+
+ if self.key_type == evp_macro.EVP_PKEY_RSA then
+ if padding then
+ if pkey_macro.EVP_PKEY_CTX_set_rsa_padding(ppkey_ctx[0], padding) ~= 1 then
+ return nil, format_error("pkey:sign_verify_prepare EVP_PKEY_CTX_set_rsa_padding")
+ end
+ end
+ if opts and opts.pss_saltlen and padding ~= rsa_macro.paddings.RSA_PKCS1_PSS_PADDING then
+ if pkey_macro.EVP_PKEY_CTX_set_rsa_pss_saltlen(ppkey_ctx[0], opts.pss_saltlen) ~= 1 then
+ return nil, format_error("pkey:sign_verify_prepare EVP_PKEY_CTX_set_rsa_pss_saltlen")
+ end
+ end
+ end
+
+ return md_ctx
+end
+
+function _M:sign(digest, md_alg, padding, opts)
+ -- TODO: temporary hack before OpenSSL has proper check for existence of private key
+ if self.key_type_is_ecx and not self:is_private() then
+ return nil, "pkey:sign: missing private key"
+ end
+
+ if digest_lib.istype(digest) then
+ local length = ptr_of_uint()
+ if C.EVP_SignFinal(digest.ctx, self.buf, length, self.ctx) ~= 1 then
+ return nil, format_error("pkey:sign: EVP_SignFinal")
+ end
+ return ffi_str(self.buf, length[0]), nil
+ elseif type(digest) == "string" then
+ if not OPENSSL_111_OR_LATER and not BORINGSSL then
+ -- we can still support earilier version with *Update and *Final
+ -- but we choose to not relying on the legacy interface for simplicity
+ return nil, "pkey:sign: new-style sign only available in OpenSSL 1.1.1 (or BoringSSL 1.1.0) or later"
+ elseif BORINGSSL and not md_alg and not self.key_type_is_ecx then
+ return nil, "pkey:sign: BoringSSL doesn't provide default digest, md_alg must be specified"
+ end
+
+ local md_ctx, err = sign_verify_prepare(self, C.EVP_DigestSignInit, md_alg, padding, opts)
+ if err then
+ return nil, err
+ end
+
+ local length = ptr_of_size_t(self.buf_size)
+ if C.EVP_DigestSign(md_ctx, self.buf, length, digest, #digest) ~= 1 then
+ return nil, format_error("pkey:sign: EVP_DigestSign")
+ end
+ return ffi_str(self.buf, length[0]), nil
+ else
+ return nil, "pkey:sign: expect a digest instance or a string at #1"
+ end
+end
+
+function _M:verify(signature, digest, md_alg, padding, opts)
+ if type(signature) ~= "string" then
+ return nil, "pkey:verify: expect a string at #1"
+ end
+
+ local code
+ if digest_lib.istype(digest) then
+ code = C.EVP_VerifyFinal(digest.ctx, signature, #signature, self.ctx)
+ elseif type(digest) == "string" then
+ if not OPENSSL_111_OR_LATER and not BORINGSSL then
+ -- we can still support earilier version with *Update and *Final
+ -- but we choose to not relying on the legacy interface for simplicity
+ return nil, "pkey:verify: new-style verify only available in OpenSSL 1.1.1 (or BoringSSL 1.1.0) or later"
+ elseif BORINGSSL and not md_alg and not self.key_type_is_ecx then
+ return nil, "pkey:verify: BoringSSL doesn't provide default digest, md_alg must be specified"
+ end
+
+ local md_ctx, err = sign_verify_prepare(self, C.EVP_DigestVerifyInit, md_alg, padding, opts)
+ if err then
+ return nil, err
+ end
+
+ code = C.EVP_DigestVerify(md_ctx, signature, #signature, digest, #digest)
+ else
+ return nil, "pkey:verify: expect a digest instance or a string at #2"
+ end
+
+ if code == 0 then
+ return false, nil
+ elseif code == 1 then
+ return true, nil
+ end
+ return false, format_error("pkey:verify")
+end
+
+function _M:derive(peerkey)
+ if not self.istype(peerkey) then
+ return nil, "pkey:derive: expect a pkey instance at #1"
+ end
+ local pctx = C.EVP_PKEY_CTX_new(self.ctx, nil)
+ if pctx == nil then
+ return nil, "pkey:derive: EVP_PKEY_CTX_new() failed"
+ end
+ ffi_gc(pctx, C.EVP_PKEY_CTX_free)
+ local code = C.EVP_PKEY_derive_init(pctx)
+ if code <= 0 then
+ return nil, format_error("pkey:derive: EVP_PKEY_derive_init", code)
+ end
+
+ code = C.EVP_PKEY_derive_set_peer(pctx, peerkey.ctx)
+ if code <= 0 then
+ return nil, format_error("pkey:derive: EVP_PKEY_derive_set_peer", code)
+ end
+
+ local buflen = ptr_of_size_t()
+ code = C.EVP_PKEY_derive(pctx, nil, buflen)
+ if code <= 0 then
+ return nil, format_error("pkey:derive: EVP_PKEY_derive check buffer size", code)
+ end
+
+ local buf = ctypes.uchar_array(buflen[0])
+ code = C.EVP_PKEY_derive(pctx, buf, buflen)
+ if code <= 0 then
+ return nil, format_error("pkey:derive: EVP_PKEY_derive", code)
+ end
+
+ return ffi_str(buf, buflen[0])
+end
+
+local function pub_or_priv_is_pri(pub_or_priv)
+ if pub_or_priv == 'private' or pub_or_priv == 'PrivateKey' then
+ return true
+ elseif not pub_or_priv or pub_or_priv == 'public' or pub_or_priv == 'PublicKey' then
+ return false
+ else
+ return nil, string.format("can only export private or public key, not %s", pub_or_priv)
+ end
+end
+
+function _M:tostring(pub_or_priv, fmt, pkcs1)
+ local is_priv, err = pub_or_priv_is_pri(pub_or_priv)
+ if err then
+ return nil, "pkey:tostring: " .. err
+ end
+ return __tostring(self, is_priv, fmt, pkcs1)
+end
+
+function _M:to_PEM(pub_or_priv, pkcs1)
+ return self:tostring(pub_or_priv, "PEM", pkcs1)
+end
+
+function _M.paramgen(config)
+ local typ = config.type
+ local key_type, write_func, get_ctx_func
+ if typ == "EC" then
+ key_type = evp_macro.EVP_PKEY_EC
+ if key_type == 0 then
+ return nil, "pkey.paramgen: the linked OpenSSL library doesn't support EC key"
+ end
+ write_func = C.PEM_write_bio_ECPKParameters
+ get_ctx_func = function(ctx)
+ local ctx = get_pkey_key[key_type](ctx)
+ if ctx == nil then
+ error(format_error("pkey.paramgen: EVP_PKEY_get0_{key}"))
+ end
+ return C.EC_KEY_get0_group(ctx)
+ end
+ elseif typ == "DH" then
+ key_type = evp_macro.EVP_PKEY_DH
+ if key_type == 0 then
+ return nil, "pkey.paramgen: the linked OpenSSL library doesn't support DH key"
+ end
+ write_func = C.PEM_write_bio_DHparams
+ get_ctx_func = get_pkey_key[key_type]
+ else
+ return nil, "pkey.paramgen: unsupported type " .. type
+ end
+
+ local params, err = generate_param(key_type, config)
+ if err then
+ return nil, "pkey.paramgen: generate_param: " .. err
+ end
+
+ local ctx = get_ctx_func(params)
+ if ctx == nil then
+ return nil, format_error("pkey.paramgen: EVP_PKEY_get0_{key}")
+ end
+
+ return bio_util.read_wrap(write_func, ctx)
+end
+
+return _M