diff options
Diffstat (limited to 'server/resty/openssl.lua')
-rw-r--r-- | server/resty/openssl.lua | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/server/resty/openssl.lua b/server/resty/openssl.lua new file mode 100644 index 0000000..27ef5cc --- /dev/null +++ b/server/resty/openssl.lua @@ -0,0 +1,476 @@ +local ffi = require("ffi") +local C = ffi.C +local ffi_cast = ffi.cast +local ffi_str = ffi.string + +local format_error = require("resty.openssl.err").format_error + +local OPENSSL_3X, BORINGSSL + +local function try_require_modules() + package.loaded["resty.openssl.version"] = nil + + local pok, lib = pcall(require, "resty.openssl.version") + if pok then + OPENSSL_3X = lib.OPENSSL_3X + BORINGSSL = lib.BORINGSSL + + require "resty.openssl.include.crypto" + require "resty.openssl.include.objects" + else + package.loaded["resty.openssl.version"] = nil + end +end +try_require_modules() + + +local _M = { + _VERSION = '0.8.16', +} + +local libcrypto_name +local lib_patterns = { + "%s", "%s.so.3", "%s.so.1.1", "%s.so.1.0" +} + +function _M.load_library() + for _, pattern in ipairs(lib_patterns) do + -- true: load to global namespae + local pok, _ = pcall(ffi.load, string.format(pattern, "crypto"), true) + if pok then + libcrypto_name = string.format(pattern, "crypto") + ffi.load(string.format(pattern, "ssl"), true) + + try_require_modules() + + return libcrypto_name + end + end + + return false, "unable to load crypto library" +end + +function _M.load_modules() + _M.bn = require("resty.openssl.bn") + _M.cipher = require("resty.openssl.cipher") + _M.digest = require("resty.openssl.digest") + _M.hmac = require("resty.openssl.hmac") + _M.kdf = require("resty.openssl.kdf") + _M.pkey = require("resty.openssl.pkey") + _M.objects = require("resty.openssl.objects") + _M.rand = require("resty.openssl.rand") + _M.version = require("resty.openssl.version") + _M.x509 = require("resty.openssl.x509") + _M.altname = require("resty.openssl.x509.altname") + _M.chain = require("resty.openssl.x509.chain") + _M.csr = require("resty.openssl.x509.csr") + _M.crl = require("resty.openssl.x509.crl") + _M.extension = require("resty.openssl.x509.extension") + _M.extensions = require("resty.openssl.x509.extensions") + _M.name = require("resty.openssl.x509.name") + _M.revoked = require("resty.openssl.x509.revoked") + _M.store = require("resty.openssl.x509.store") + _M.pkcs12 = require("resty.openssl.pkcs12") + _M.ssl = require("resty.openssl.ssl") + _M.ssl_ctx = require("resty.openssl.ssl_ctx") + + if OPENSSL_3X then + _M.provider = require("resty.openssl.provider") + _M.mac = require("resty.openssl.mac") + _M.ctx = require("resty.openssl.ctx") + end + + _M.bignum = _M.bn +end + +function _M.luaossl_compat() + _M.load_modules() + + _M.csr.setSubject = _M.csr.set_subject_name + _M.csr.setPublicKey = _M.csr.set_pubkey + + _M.x509.setPublicKey = _M.x509.set_pubkey + _M.x509.getPublicKey = _M.x509.get_pubkey + _M.x509.setSerial = _M.x509.set_serial_number + _M.x509.getSerial = _M.x509.get_serial_number + _M.x509.setSubject = _M.x509.set_subject_name + _M.x509.getSubject = _M.x509.get_subject_name + _M.x509.setIssuer = _M.x509.set_issuer_name + _M.x509.getIssuer = _M.x509.get_issuer_name + _M.x509.getOCSP = _M.x509.get_ocsp_url + + local pkey_new = _M.pkey.new + _M.pkey.new = function(a, b) + if type(a) == "string" then + return pkey_new(a, b and unpack(b)) + else + return pkey_new(a, b) + end + end + + _M.cipher.encrypt = function(self, key, iv, padding) + return self, _M.cipher.init(self, key, iv, true, not padding) + end + _M.cipher.decrypt = function(self, key, iv, padding) + return self, _M.cipher.init(self, key, iv, false, not padding) + end + + local digest_update = _M.digest.update + _M.digest.update = function(self, ...) + local ok, err = digest_update(self, ...) + if ok then + return self + else + return nil, err + end + end + + local store_verify = _M.store.verify + _M.store.verify = function(...) + local ok, err = store_verify(...) + if err then + return false, err + else + return true, ok + end + end + + local kdf_derive = _M.kdf.derive + local kdf_keys_mappings = { + iter = "pbkdf2_iter", + key = "hkdf_key", + info = "hkdf_info", + secret = "tls1_prf_secret", + seed = "tls1_prf_seed", + maxmem_bytes = "scrypt_maxmem", + N = "scrypt_N", + r = "scrypt_r", + p = "scrypt_p", + } + _M.kdf.derive = function(o) + for k1, k2 in pairs(kdf_keys_mappings) do + o[k1] = o[k2] + o[k2] = nil + end + local hkdf_mode = o.hkdf_mode + if hkdf_mode == "extract_and_expand" then + o.hkdf_mode = _M.kdf.HKDEF_MODE_EXTRACT_AND_EXPAND + elseif hkdf_mode == "extract_only" then + o.hkdf_mode = _M.kdf.HKDEF_MODE_EXTRACT_ONLY + elseif hkdf_mode == "expand_only" then + o.hkdf_mode = _M.kdf.HKDEF_MODE_EXPAND_ONLY + end + return kdf_derive(o) + end + + _M.pkcs12.new = function(tbl) + local certs = {} + local passphrase = tbl.passphrase + if not tbl.key then + return nil, "key must be set" + end + for _, cert in ipairs(tbl.certs) do + if not _M.x509.istype(cert) then + return nil, "certs must contains only x509 instance" + end + if cert:check_private_key(tbl.key) then + tbl.cert = cert + else + certs[#certs+1] = cert + end + end + tbl.cacerts = certs + return _M.pkcs12.encode(tbl, passphrase) + end + + _M.crl.add = _M.crl.add_revoked + _M.crl.lookupSerial = _M.crl.get_by_serial + + for mod, tbl in pairs(_M) do + if type(tbl) == 'table' then + + -- avoid using a same table as the iterrator will change + local new_tbl = {} + -- luaossl always error() out + for k, f in pairs(tbl) do + if type(f) == 'function' then + local of = f + new_tbl[k] = function(...) + local ret = { of(...) } + if ret and #ret > 1 and ret[#ret] then + error(mod .. "." .. k .. "(): " .. ret[#ret]) + end + return unpack(ret) + end + end + end + + for k, f in pairs(new_tbl) do + tbl[k] = f + end + + setmetatable(tbl, { + __index = function(t, k) + local tok + -- handle special case + if k == 'toPEM' then + tok = 'to_PEM' + else + tok = k:gsub("(%l)(%u)", function(a, b) return a .. "_" .. b:lower() end) + if tok == k then + return + end + end + if type(tbl[tok]) == 'function' then + return tbl[tok] + end + end + }) + end + end + + -- skip error() conversion + _M.pkcs12.parse = function(p12, passphrase) + local r, err = _M.pkcs12.decode(p12, passphrase) + if err then error(err) end + return r.key, r.cert, r.cacerts + end +end + +if OPENSSL_3X then + require "resty.openssl.include.evp" + local provider = require "resty.openssl.provider" + local ctx_lib = require "resty.openssl.ctx" + local fips_provider_ctx + + function _M.set_fips_mode(enable, self_test) + if (not not enable) == _M.get_fips_mode() then + return true + end + + if enable then + local p, err = provider.load("fips") + if not p then + return false, err + end + fips_provider_ctx = p + if self_test then + local ok, err = p:self_test() + if not ok then + return false, err + end + end + + elseif fips_provider_ctx then -- disable + local p = fips_provider_ctx + fips_provider_ctx = nil + return p:unload() + end + + -- set algorithm in fips mode in default ctx + -- this deny/allow non-FIPS compliant algorithms to be used from EVP interface + -- and redirect/remove redirect implementation to fips provider + if C.EVP_default_properties_enable_fips(ctx_lib.get_libctx(), enable and 1 or 0) == 0 then + return false, format_error("openssl.set_fips_mode: EVP_default_properties_enable_fips") + end + + return true + end + + function _M.get_fips_mode() + local pok = provider.is_available("fips") + if not pok then + return false + end + + return C.EVP_default_properties_is_fips_enabled(ctx_lib.get_libctx()) == 1 + end + +else + function _M.set_fips_mode(enable) + if (not not enable) == _M.get_fips_mode() then + return true + end + + if C.FIPS_mode_set(enable and 1 or 0) == 0 then + return false, format_error("openssl.set_fips_mode") + end + + return true + end + + function _M.get_fips_mode() + return C.FIPS_mode() == 1 + end +end + +function _M.set_default_properties(props) + if not OPENSSL_3X then + return nil, "openssl.set_default_properties is only not supported from OpenSSL 3.0" + end + + local ctx_lib = require "resty.openssl.ctx" + + if C.EVP_set_default_properties(ctx_lib.get_libctx(), props) == 0 then + return false, format_error("openssl.EVP_set_default_properties") + end + + return true +end + +local function list_legacy(typ, get_nid_cf) + local typ_lower = string.lower(typ:sub(5)) -- cut off EVP_ + require ("resty.openssl.include.evp." .. typ_lower) + + local ret = {} + local fn = ffi_cast("fake_openssl_" .. typ_lower .. "_list_fn*", + function(elem, from, to, arg) + if elem ~= nil then + local nid = get_nid_cf(elem) + table.insert(ret, ffi_str(C.OBJ_nid2sn(nid))) + end + -- from/to (renamings) are ignored + end) + C[typ .. "_do_all_sorted"](fn, nil) + fn:free() + + return ret +end + +local function list_provided(typ) + local typ_lower = string.lower(typ:sub(5)) -- cut off EVP_ + local typ_ptr = typ .. "*" + require ("resty.openssl.include.evp." .. typ_lower) + local ctx_lib = require "resty.openssl.ctx" + + local ret = {} + + local fn = ffi_cast("fake_openssl_" .. typ_lower .. "_provided_list_fn*", + function(elem, _) + elem = ffi_cast(typ_ptr, elem) + local name = ffi_str(C[typ .. "_get0_name"](elem)) + -- alternate names are ignored, retrieve use TYPE_names_do_all + local prov = ffi_str(C.OSSL_PROVIDER_get0_name(C[typ .. "_get0_provider"](elem))) + table.insert(ret, name .. " @ " .. prov) + end) + + C[typ .. "_do_all_provided"](ctx_lib.get_libctx(), fn, nil) + fn:free() + + table.sort(ret) + return ret +end + +function _M.list_cipher_algorithms() + if BORINGSSL then + return nil, "openssl.list_cipher_algorithms is not supported on BoringSSL" + end + + require "resty.openssl.include.evp.cipher" + local ret = list_legacy("EVP_CIPHER", + OPENSSL_3X and C.EVP_CIPHER_get_nid or C.EVP_CIPHER_nid) + + if OPENSSL_3X then + local ret_provided = list_provided("EVP_CIPHER") + for _, r in ipairs(ret_provided) do + table.insert(ret, r) + end + end + + return ret +end + +function _M.list_digest_algorithms() + if BORINGSSL then + return nil, "openssl.list_digest_algorithms is not supported on BoringSSL" + end + + require "resty.openssl.include.evp.md" + local ret = list_legacy("EVP_MD", + OPENSSL_3X and C.EVP_MD_get_type or C.EVP_MD_type) + + if OPENSSL_3X then + local ret_provided = list_provided("EVP_MD") + for _, r in ipairs(ret_provided) do + table.insert(ret, r) + end + end + + return ret +end + +function _M.list_mac_algorithms() + if not OPENSSL_3X then + return nil, "openssl.list_mac_algorithms is only supported from OpenSSL 3.0" + end + + return list_provided("EVP_MAC") +end + +function _M.list_kdf_algorithms() + if not OPENSSL_3X then + return nil, "openssl.list_kdf_algorithms is only supported from OpenSSL 3.0" + end + + return list_provided("EVP_KDF") +end + +local valid_ssl_protocols = { + ["SSLv3"] = 0x0300, + ["TLSv1"] = 0x0301, + ["TLSv1.1"] = 0x0302, + ["TLSv1.2"] = 0x0303, + ["TLSv1.3"] = 0x0304, +} + +function _M.list_ssl_ciphers(cipher_list, ciphersuites, protocol) + local ssl_lib = require("resty.openssl.ssl") + local ssl_macro = require("resty.openssl.include.ssl") + + if protocol then + if not valid_ssl_protocols[protocol] then + return nil, "unknown protocol \"" .. protocol .. "\"" + end + protocol = valid_ssl_protocols[protocol] + end + + local ssl_ctx = C.SSL_CTX_new(C.TLS_server_method()) + if ssl_ctx == nil then + return nil, format_error("SSL_CTX_new") + end + ffi.gc(ssl_ctx, C.SSL_CTX_free) + + local ssl = C.SSL_new(ssl_ctx) + if ssl == nil then + return nil, format_error("SSL_new") + end + ffi.gc(ssl, C.SSL_free) + + if protocol then + if ssl_macro.SSL_set_min_proto_version(ssl, protocol) == 0 or + ssl_macro.SSL_set_max_proto_version(ssl, protocol) == 0 then + return nil, format_error("SSL_set_min/max_proto_version") + end + end + + ssl = { ctx = ssl } + + local ok, err + if cipher_list then + ok, err = ssl_lib.set_cipher_list(ssl, cipher_list) + if not ok then + return nil, err + end + end + + if ciphersuites then + ok, err = ssl_lib.set_ciphersuites(ssl, ciphersuites) + if not ok then + return nil, err + end + end + + return ssl_lib.get_ciphers(ssl) +end + +return _M |