diff options
Diffstat (limited to 'server/resty/openssl/x509')
-rw-r--r-- | server/resty/openssl/x509/altname.lua | 248 | ||||
-rw-r--r-- | server/resty/openssl/x509/chain.lua | 76 | ||||
-rw-r--r-- | server/resty/openssl/x509/crl.lua | 607 | ||||
-rw-r--r-- | server/resty/openssl/x509/csr.lua | 531 | ||||
-rw-r--r-- | server/resty/openssl/x509/extension.lua | 281 | ||||
-rw-r--r-- | server/resty/openssl/x509/extension/dist_points.lua | 75 | ||||
-rw-r--r-- | server/resty/openssl/x509/extension/info_access.lua | 137 | ||||
-rw-r--r-- | server/resty/openssl/x509/extensions.lua | 84 | ||||
-rw-r--r-- | server/resty/openssl/x509/init.lua | 1071 | ||||
-rw-r--r-- | server/resty/openssl/x509/name.lua | 156 | ||||
-rw-r--r-- | server/resty/openssl/x509/revoked.lua | 108 | ||||
-rw-r--r-- | server/resty/openssl/x509/store.lua | 227 |
12 files changed, 3601 insertions, 0 deletions
diff --git a/server/resty/openssl/x509/altname.lua b/server/resty/openssl/x509/altname.lua new file mode 100644 index 0000000..34bf9e0 --- /dev/null +++ b/server/resty/openssl/x509/altname.lua @@ -0,0 +1,248 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc +local ffi_cast = ffi.cast +local ffi_str = ffi.string + +require "resty.openssl.include.x509" +require "resty.openssl.include.x509v3" +local asn1_macro = require "resty.openssl.include.asn1" +local stack_lib = require "resty.openssl.stack" +local name_lib = require "resty.openssl.x509.name" +local altname_macro = require "resty.openssl.include.x509.altname" + +local _M = {} + +local general_names_ptr_ct = ffi.typeof("GENERAL_NAMES*") + +local STACK = "GENERAL_NAME" +local new = stack_lib.new_of(STACK) +local add = stack_lib.add_of(STACK) +local dup = stack_lib.dup_of(STACK) + +local types = altname_macro.types + +local AF_INET = 2 +local AF_INET6 = 10 +if ffi.os == "OSX" then + AF_INET6 = 30 +elseif ffi.os == "BSD" then + AF_INET6 = 28 +elseif ffi.os == "Windows" then + AF_INET6 = 23 +end + +ffi.cdef [[ + typedef int socklen_t; + int inet_pton(int af, const char *restrict src, void *restrict dst); + const char *inet_ntop(int af, const void *restrict src, + char *restrict dst, socklen_t size); +]] + +local ip_buffer = ffi.new("unsigned char [46]") -- 46 bytes enough for both string ipv6 and binary ipv6 + +-- similar to GENERAL_NAME_print, but returns value instead of print +local gn_decode = function(ctx) + local typ = ctx.type + local k = altname_macro.literals[typ] + local v + if typ == types.OtherName then + v = "OtherName:<unsupported>" + elseif typ == types.RFC822Name then + v = ffi_str(asn1_macro.ASN1_STRING_get0_data(ctx.d.rfc822Name)) + elseif typ == types.DNS then + v = ffi_str(asn1_macro.ASN1_STRING_get0_data(ctx.d.dNSName)) + elseif typ == types.X400 then + v = "X400:<unsupported>" + elseif typ == types.DirName then + v = name_lib.dup(ctx.d.directoryName) + elseif typ == types.EdiParty then + v = "EdiParty:<unsupported>" + elseif typ == types.URI then + v = ffi_str(asn1_macro.ASN1_STRING_get0_data(ctx.d.uniformResourceIdentifier)) + elseif typ == types.IP then + v = asn1_macro.ASN1_STRING_get0_data(ctx.d.iPAddress) + local l = tonumber(C.ASN1_STRING_length(ctx.d.iPAddress)) + if l ~= 4 and l ~= 16 then + error("Unknown IP address type") + end + v = C.inet_ntop(l == 4 and AF_INET or AF_INET6, v, ip_buffer, 46) + v = ffi_str(v) + elseif typ == types.RID then + v = "RID:<unsupported>" + else + error("unknown type" .. typ .. "-> " .. types.OtherName) + end + return { k, v } +end + +-- shared with info_access +_M.gn_decode = gn_decode + +local mt = stack_lib.mt_of(STACK, gn_decode, _M) +local mt__pairs = mt.__pairs +mt.__pairs = function(tbl) + local f = mt__pairs(tbl) + return function() + local _, e = f() + if not e then return end + return unpack(e) + end +end + +function _M.new() + local ctx = new() + if ctx == nil then + return nil, "x509.altname.new: OPENSSL_sk_new_null() failed" + end + local cast = ffi_cast("GENERAL_NAMES*", ctx) + + local self = setmetatable({ + ctx = ctx, + cast = cast, + _is_shallow_copy = false, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l.cast and ffi.istype(general_names_ptr_ct, l.cast) +end + +function _M.dup(ctx) + if ctx == nil or not ffi.istype(general_names_ptr_ct, ctx) then + return nil, "x509.altname.dup: expect a GENERAL_NAMES* ctx at #1" + end + + local dup_ctx = dup(ctx) + + return setmetatable({ + cast = ffi_cast("GENERAL_NAMES*", dup_ctx), + ctx = dup_ctx, + -- don't let lua gc the original stack to keep its elements + _dupped_from = ctx, + _is_shallow_copy = true, + _elem_refs = {}, + _elem_refs_idx = 1, + }, mt), nil +end + +local function gn_set(gn, typ, value) + if type(typ) ~= 'string' then + return "x509.altname:gn_set: expect a string at #1" + end + local typ_lower = typ:lower() + if type(value) ~= 'string' then + return "x509.altname:gn_set: except a string at #2" + end + + local txt = value + local gn_type = types[typ_lower] + + if not gn_type then + return "x509.altname:gn_set: unknown type " .. typ + end + + if gn_type == types.IP then + if C.inet_pton(AF_INET, txt, ip_buffer) == 1 then + txt = ffi_str(ip_buffer, 4) + elseif C.inet_pton(AF_INET6, txt, ip_buffer) == 1 then + txt = ffi_str(ip_buffer, 16) + else + return "x509.altname:gn_set: invalid IP address " .. txt + end + + elseif gn_type ~= types.Email and + gn_type ~= types.URI and + gn_type ~= types.DNS then + return "x509.altname:gn_set: setting type " .. typ .. " is currently not supported" + end + + gn.type = gn_type + + local asn1_string = C.ASN1_IA5STRING_new() + if asn1_string == nil then + return "x509.altname:gn_set: ASN1_STRING_type_new() failed" + end + + local code = C.ASN1_STRING_set(asn1_string, txt, #txt) + if code ~= 1 then + C.ASN1_STRING_free(asn1_string) + return "x509.altname:gn_set: ASN1_STRING_set() failed: " .. code + end + gn.d.ia5 = asn1_string +end + +-- shared with info_access +_M.gn_set = gn_set + +function _M:add(typ, value) + + -- the stack element stays with stack + -- we shouldn't add gc handler if it's already been + -- pushed to stack. instead, rely on the gc handler + -- of the stack to release all memories + local gn = C.GENERAL_NAME_new() + if gn == nil then + return nil, "x509.altname:add: GENERAL_NAME_new() failed" + end + + local err = gn_set(gn, typ, value) + if err then + C.GENERAL_NAME_free(gn) + return nil, err + end + + local _, err = add(self.ctx, gn) + if err then + C.GENERAL_NAME_free(gn) + return nil, err + end + + -- if the stack is duplicated, the gc handler is not pop_free + -- handle the gc by ourselves + if self._is_shallow_copy then + ffi_gc(gn, C.GENERAL_NAME_free) + self._elem_refs[self._elem_refs_idx] = gn + self._elem_refs_idx = self._elem_refs_idx + 1 + end + return self +end + +_M.all = function(self) + local ret = {} + local _next = mt.__pairs(self) + while true do + local k, v = _next() + if k then + ret[k] = v + else + break + end + end + return ret +end + +_M.each = mt.__pairs +_M.index = mt.__index +_M.count = mt.__len + +mt.__tostring = function(self) + local values = {} + local _next = mt.__pairs(self) + while true do + local k, v = _next() + if k then + table.insert(values, k .. "=" .. v) + else + break + end + end + table.sort(values) + return table.concat(values, "/") +end + +_M.tostring = mt.__tostring + +return _M diff --git a/server/resty/openssl/x509/chain.lua b/server/resty/openssl/x509/chain.lua new file mode 100644 index 0000000..5557ea0 --- /dev/null +++ b/server/resty/openssl/x509/chain.lua @@ -0,0 +1,76 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc + +local stack_lib = require "resty.openssl.stack" +local x509_lib = require "resty.openssl.x509" +local format_error = require("resty.openssl.err").format_error + +local _M = {} + +local stack_ptr_ct = ffi.typeof("OPENSSL_STACK*") + +local STACK = "X509" +local gc = stack_lib.gc_of(STACK) +local new = stack_lib.new_of(STACK) +local add = stack_lib.add_of(STACK) +local mt = stack_lib.mt_of(STACK, x509_lib.dup, _M) + +function _M.new() + local raw = new() + + local self = setmetatable({ + stack_of = STACK, + ctx = raw, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l.ctx and ffi.istype(stack_ptr_ct, l.ctx) + and l.stack_of and l.stack_of == STACK +end + +function _M.dup(ctx) + if ctx == nil or not ffi.istype(stack_ptr_ct, ctx) then + return nil, "x509.chain.dup: expect a stack ctx at #1, got " .. type(ctx) + end + -- sk_X509_dup plus up ref for each X509 element + local ctx = C.X509_chain_up_ref(ctx) + if ctx == nil then + return nil, "x509.chain.dup: X509_chain_up_ref() failed" + end + ffi_gc(ctx, gc) + + return setmetatable({ + stack_of = STACK, + ctx = ctx, + }, mt) +end + +function _M:add(x509) + if not x509_lib.istype(x509) then + return nil, "x509.chain:add: expect a x509 instance at #1" + end + + local dup = C.X509_dup(x509.ctx) + if dup == nil then + return nil, format_error("x509.chain:add: X509_dup") + end + + local _, err = add(self.ctx, dup) + if err then + C.X509_free(dup) + return nil, err + end + + return true +end + +_M.all = stack_lib.all_func(mt) +_M.each = mt.__ipairs +_M.index = mt.__index +_M.count = mt.__len + +return _M diff --git a/server/resty/openssl/x509/crl.lua b/server/resty/openssl/x509/crl.lua new file mode 100644 index 0000000..3ee4501 --- /dev/null +++ b/server/resty/openssl/x509/crl.lua @@ -0,0 +1,607 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc + +require "resty.openssl.include.x509.crl" +require "resty.openssl.include.pem" +require "resty.openssl.include.x509v3" +local asn1_lib = require("resty.openssl.asn1") +local bn_lib = require("resty.openssl.bn") +local revoked_lib = require("resty.openssl.x509.revoked") +local digest_lib = require("resty.openssl.digest") +local extension_lib = require("resty.openssl.x509.extension") +local pkey_lib = require("resty.openssl.pkey") +local bio_util = require "resty.openssl.auxiliary.bio" +local ctx_lib = require "resty.openssl.ctx" +local stack_lib = require "resty.openssl.stack" +local txtnid2nid = require("resty.openssl.objects").txtnid2nid +local find_sigid_algs = require("resty.openssl.objects").find_sigid_algs +local format_error = require("resty.openssl.err").format_error +local version = require("resty.openssl.version") +local OPENSSL_10 = version.OPENSSL_10 +local OPENSSL_11_OR_LATER = version.OPENSSL_11_OR_LATER +local OPENSSL_3X = version.OPENSSL_3X +local BORINGSSL = version.BORINGSSL +local BORINGSSL_110 = version.BORINGSSL_110 -- used in boringssl-fips-20190808 + +local accessors = {} + +accessors.set_issuer_name = C.X509_CRL_set_issuer_name +accessors.set_version = C.X509_CRL_set_version + + +if OPENSSL_11_OR_LATER and not BORINGSSL_110 then + accessors.get_last_update = C.X509_CRL_get0_lastUpdate + accessors.set_last_update = C.X509_CRL_set1_lastUpdate + accessors.get_next_update = C.X509_CRL_get0_nextUpdate + accessors.set_next_update = C.X509_CRL_set1_nextUpdate + accessors.get_version = C.X509_CRL_get_version + accessors.get_issuer_name = C.X509_CRL_get_issuer -- returns internal ptr + accessors.get_signature_nid = C.X509_CRL_get_signature_nid + -- BORINGSSL_110 exports X509_CRL_get_signature_nid, but just ignored for simplicity + accessors.get_revoked = C.X509_CRL_get_REVOKED +elseif OPENSSL_10 or BORINGSSL_110 then + accessors.get_last_update = function(crl) + if crl == nil or crl.crl == nil then + return nil + end + return crl.crl.lastUpdate + end + accessors.set_last_update = C.X509_CRL_set_lastUpdate + accessors.get_next_update = function(crl) + if crl == nil or crl.crl == nil then + return nil + end + return crl.crl.nextUpdate + end + accessors.set_next_update = C.X509_CRL_set_nextUpdate + accessors.get_version = function(crl) + if crl == nil or crl.crl == nil then + return nil + end + return C.ASN1_INTEGER_get(crl.crl.version) + end + accessors.get_issuer_name = function(crl) + if crl == nil or crl.crl == nil then + return nil + end + return crl.crl.issuer + end + accessors.get_signature_nid = function(crl) + if crl == nil or crl.crl == nil or crl.crl.sig_alg == nil then + return nil + end + return C.OBJ_obj2nid(crl.crl.sig_alg.algorithm) + end + accessors.get_revoked = function(crl) + return crl.crl.revoked + end +end + +local function __tostring(self, fmt) + if not fmt or fmt == 'PEM' then + return bio_util.read_wrap(C.PEM_write_bio_X509_CRL, self.ctx) + elseif fmt == 'DER' then + return bio_util.read_wrap(C.i2d_X509_CRL_bio, self.ctx) + else + return nil, "x509.crl:tostring: can only write PEM or DER format, not " .. fmt + end +end + +local _M = {} +local mt = { __index = _M, __tostring = __tostring } + +local x509_crl_ptr_ct = ffi.typeof("X509_CRL*") + +function _M.new(crl, fmt, properties) + local ctx + if not crl then + if OPENSSL_3X then + ctx = C.X509_CRL_new_ex(ctx_lib.get_libctx(), properties) + else + ctx = C.X509_CRL_new() + end + if ctx == nil then + return nil, "x509.crl.new: X509_CRL_new() failed" + end + elseif type(crl) == "string" then + -- routine for load an existing csr + local bio = C.BIO_new_mem_buf(crl, #crl) + if bio == nil then + return nil, format_error("x509.crl.new: BIO_new_mem_buf") + end + + fmt = fmt or "*" + while true do -- luacheck: ignore 512 -- loop is executed at most once + if fmt == "PEM" or fmt == "*" then + ctx = C.PEM_read_bio_X509_CRL(bio, nil, nil, nil) + if ctx ~= nil then + break + elseif fmt == "*" then + -- BIO_reset; #define BIO_CTRL_RESET 1 + local code = C.BIO_ctrl(bio, 1, 0, nil) + if code ~= 1 then + return nil, "x509.crl.new: BIO_ctrl() failed: " .. code + end + end + end + if fmt == "DER" or fmt == "*" then + ctx = C.d2i_X509_CRL_bio(bio, nil) + end + break + end + C.BIO_free(bio) + if ctx == nil then + return nil, format_error("x509.crl.new") + end + -- clear errors occur when trying + C.ERR_clear_error() + else + return nil, "x509.crl.new: expect nil or a string at #1" + end + ffi_gc(ctx, C.X509_CRL_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l and l.ctx and ffi.istype(x509_crl_ptr_ct, l.ctx) +end + +function _M.dup(ctx) + if not ffi.istype(x509_crl_ptr_ct, ctx) then + return nil, "x509.crl.dup: expect a x509.crl ctx at #1" + end + local ctx = C.X509_CRL_dup(ctx) + if ctx == nil then + return nil, "x509.crl.dup: X509_CRL_dup() failed" + end + + ffi_gc(ctx, C.X509_CRL_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M:tostring(fmt) + return __tostring(self, fmt) +end + +function _M:to_PEM() + return __tostring(self, "PEM") +end + +function _M:text() + return bio_util.read_wrap(C.X509_CRL_print, self.ctx) +end + +local function revoked_decode(ctx) + if OPENSSL_10 then + error("x509.crl:revoked_decode: not supported on OpenSSL 1.0") + end + + local ret = {} + local serial = C.X509_REVOKED_get0_serialNumber(ctx) + if serial ~= nil then + serial = C.ASN1_INTEGER_to_BN(serial, nil) + if serial == nil then + error("x509.crl:revoked_decode: ASN1_INTEGER_to_BN() failed") + end + ffi_gc(serial, C.BN_free) + ret["serial_number"] = bn_lib.to_hex({ctx = serial}) + end + + local date = C.X509_REVOKED_get0_revocationDate(ctx) + if date ~= nil then + date = asn1_lib.asn1_to_unix(date) + ret["revocation_date"] = date + end + + return ret +end + +local revoked_mt = stack_lib.mt_of("X509_REVOKED", revoked_decode, _M) + +local function nil_iter() return nil end +local function revoked_iter(self) + local stack = accessors.get_revoked(self.ctx) + if stack == nil then + return nil_iter + end + + return revoked_mt.__ipairs({ctx = stack}) +end + +mt.__pairs = revoked_iter +mt.__ipairs = revoked_iter +mt.__index = function(self, k) + local i = tonumber(k) + if not i then + return _M[k] + end + + local stack = accessors.get_revoked(self.ctx) + if stack == nil then + return nil + end + + return revoked_mt.__index({ctx = stack}, i) +end +mt.__len = function(self) + local stack = accessors.get_revoked(self.ctx) + if stack == nil then + return 0 + end + + return revoked_mt.__len({ctx = stack}) +end + +_M.all = function(self) + local ret = {} + local _next = mt.__pairs(self) + while true do + local k, v = _next() + if k then + ret[k] = v + else + break + end + end + return ret +end +_M.each = mt.__pairs +_M.index = mt.__index +_M.count = mt.__len + +--- Adds revoked item to stack of revoked certificates of crl +-- @tparam table Instance of crl module +-- @tparam table Instance of revoked module +-- @treturn boolean true if revoked item was successfully added or false otherwise +-- @treturn[opt] string Returns optional error message in case of error +function _M:add_revoked(revoked) + if not revoked_lib.istype(revoked) then + return false, "x509.crl:add_revoked: expect a revoked instance at #1" + end + local ctx = C.X509_REVOKED_dup(revoked.ctx) + if ctx == nil then + return nil, "x509.crl:add_revoked: X509_REVOKED_dup() failed" + end + + if C.X509_CRL_add0_revoked(self.ctx, ctx) == 0 then + return false, format_error("x509.crl:add_revoked") + end + + return true +end + +local ptr_ptr_of_x509_revoked = ffi.typeof("X509_REVOKED*[1]") +function _M:get_by_serial(sn) + local bn, err + if bn_lib.istype(sn) then + bn = sn + elseif type(sn) == "string" then + bn, err = bn_lib.from_hex(sn) + if err then + return nil, "x509.crl:find: can't decode bn: " .. err + end + else + return nil, "x509.crl:find: expect a bn instance at #1" + end + + local sn_asn1 = C.BN_to_ASN1_INTEGER(bn.ctx, nil) + if sn_asn1 == nil then + return nil, "x509.crl:find: BN_to_ASN1_INTEGER() failed" + end + ffi_gc(sn_asn1, C.ASN1_INTEGER_free) + + local pp = ptr_ptr_of_x509_revoked() + local code = C.X509_CRL_get0_by_serial(self.ctx, pp, sn_asn1) + if code == 1 then + return revoked_decode(pp[0]) + elseif code == 2 then + return nil, "not revoked (removeFromCRL)" + end + + -- 0 or other + return nil +end + + +-- START AUTO GENERATED CODE + +-- AUTO GENERATED +function _M:sign(pkey, digest) + if not pkey_lib.istype(pkey) then + return false, "x509.crl:sign: expect a pkey instance at #1" + end + + local digest_algo + if digest then + if not digest_lib.istype(digest) then + return false, "x509.crl:sign: expect a digest instance at #2" + elseif not digest.algo then + return false, "x509.crl:sign: expect a digest instance to have algo member" + end + digest_algo = digest.algo + elseif BORINGSSL then + digest_algo = C.EVP_get_digestbyname('sha256') + end + + -- returns size of signature if success + if C.X509_CRL_sign(self.ctx, pkey.ctx, digest_algo) == 0 then + return false, format_error("x509.crl:sign") + end + + return true +end + +-- AUTO GENERATED +function _M:verify(pkey) + if not pkey_lib.istype(pkey) then + return false, "x509.crl:verify: expect a pkey instance at #1" + end + + local code = C.X509_CRL_verify(self.ctx, pkey.ctx) + if code == 1 then + return true + elseif code == 0 then + return false + else -- typically -1 + return false, format_error("x509.crl:verify", code) + end +end + +-- AUTO GENERATED +local function get_extension(ctx, nid_txt, last_pos) + last_pos = (last_pos or 0) - 1 + local nid, err = txtnid2nid(nid_txt) + if err then + return nil, nil, err + end + local pos = C.X509_CRL_get_ext_by_NID(ctx, nid, last_pos) + if pos == -1 then + return nil + end + local ctx = C.X509_CRL_get_ext(ctx, pos) + if ctx == nil then + return nil, nil, format_error() + end + return ctx, pos +end + +-- AUTO GENERATED +function _M:add_extension(extension) + if not extension_lib.istype(extension) then + return false, "x509.crl:add_extension: expect a x509.extension instance at #1" + end + + -- X509_CRL_add_ext returnes the stack on success, and NULL on error + -- the X509_EXTENSION ctx is dupped internally + if C.X509_CRL_add_ext(self.ctx, extension.ctx, -1) == nil then + return false, format_error("x509.crl:add_extension") + end + + return true +end + +-- AUTO GENERATED +function _M:get_extension(nid_txt, last_pos) + local ctx, pos, err = get_extension(self.ctx, nid_txt, last_pos) + if err then + return nil, nil, "x509.crl:get_extension: " .. err + end + local ext, err = extension_lib.dup(ctx) + if err then + return nil, nil, "x509.crl:get_extension: " .. err + end + return ext, pos+1 +end + +local X509_CRL_delete_ext +if OPENSSL_11_OR_LATER then + X509_CRL_delete_ext = C.X509_CRL_delete_ext +elseif OPENSSL_10 then + X509_CRL_delete_ext = function(ctx, pos) + return C.X509v3_delete_ext(ctx.crl.extensions, pos) + end +else + X509_CRL_delete_ext = function(...) + error("X509_CRL_delete_ext undefined") + end +end + +-- AUTO GENERATED +function _M:set_extension(extension, last_pos) + if not extension_lib.istype(extension) then + return false, "x509.crl:set_extension: expect a x509.extension instance at #1" + end + + last_pos = (last_pos or 0) - 1 + + local nid = extension:get_object().nid + local pos = C.X509_CRL_get_ext_by_NID(self.ctx, nid, last_pos) + -- pos may be -1, which means not found, it's fine, we will add new one instead of replace + + local removed = X509_CRL_delete_ext(self.ctx, pos) + C.X509_EXTENSION_free(removed) + + if C.X509_CRL_add_ext(self.ctx, extension.ctx, pos) == nil then + return false, format_error("x509.crl:set_extension") + end + + return true +end + +-- AUTO GENERATED +function _M:set_extension_critical(nid_txt, crit, last_pos) + local ctx, _, err = get_extension(self.ctx, nid_txt, last_pos) + if err then + return nil, "x509.crl:set_extension_critical: " .. err + end + + if C.X509_EXTENSION_set_critical(ctx, crit and 1 or 0) ~= 1 then + return false, format_error("x509.crl:set_extension_critical") + end + + return true +end + +-- AUTO GENERATED +function _M:get_extension_critical(nid_txt, last_pos) + local ctx, _, err = get_extension(self.ctx, nid_txt, last_pos) + if err then + return nil, "x509.crl:get_extension_critical: " .. err + end + + return C.X509_EXTENSION_get_critical(ctx) == 1 +end + +-- AUTO GENERATED +function _M:get_issuer_name() + local got = accessors.get_issuer_name(self.ctx) + if got == nil then + return nil + end + local lib = require("resty.openssl.x509.name") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED +function _M:set_issuer_name(toset) + local lib = require("resty.openssl.x509.name") + if lib.istype and not lib.istype(toset) then + return false, "x509.crl:set_issuer_name: expect a x509.name instance at #1" + end + toset = toset.ctx + if accessors.set_issuer_name(self.ctx, toset) == 0 then + return false, format_error("x509.crl:set_issuer_name") + end + return true +end + +-- AUTO GENERATED +function _M:get_last_update() + local got = accessors.get_last_update(self.ctx) + if got == nil then + return nil + end + + got = asn1_lib.asn1_to_unix(got) + + return got +end + +-- AUTO GENERATED +function _M:set_last_update(toset) + if type(toset) ~= "number" then + return false, "x509.crl:set_last_update: expect a number at #1" + end + + toset = C.ASN1_TIME_set(nil, toset) + ffi_gc(toset, C.ASN1_STRING_free) + + if accessors.set_last_update(self.ctx, toset) == 0 then + return false, format_error("x509.crl:set_last_update") + end + return true +end + +-- AUTO GENERATED +function _M:get_next_update() + local got = accessors.get_next_update(self.ctx) + if got == nil then + return nil + end + + got = asn1_lib.asn1_to_unix(got) + + return got +end + +-- AUTO GENERATED +function _M:set_next_update(toset) + if type(toset) ~= "number" then + return false, "x509.crl:set_next_update: expect a number at #1" + end + + toset = C.ASN1_TIME_set(nil, toset) + ffi_gc(toset, C.ASN1_STRING_free) + + if accessors.set_next_update(self.ctx, toset) == 0 then + return false, format_error("x509.crl:set_next_update") + end + return true +end + +-- AUTO GENERATED +function _M:get_version() + local got = accessors.get_version(self.ctx) + if got == nil then + return nil + end + + got = tonumber(got) + 1 + + return got +end + +-- AUTO GENERATED +function _M:set_version(toset) + if type(toset) ~= "number" then + return false, "x509.crl:set_version: expect a number at #1" + end + + -- Note: this is defined by standards (X.509 et al) to be one less than the certificate version. + -- So a version 3 certificate will return 2 and a version 1 certificate will return 0. + toset = toset - 1 + + if accessors.set_version(self.ctx, toset) == 0 then + return false, format_error("x509.crl:set_version") + end + return true +end + + +-- AUTO GENERATED +function _M:get_signature_nid() + local nid = accessors.get_signature_nid(self.ctx) + if nid <= 0 then + return nil, format_error("x509.crl:get_signature_nid") + end + + return nid +end + +-- AUTO GENERATED +function _M:get_signature_name() + local nid = accessors.get_signature_nid(self.ctx) + if nid <= 0 then + return nil, format_error("x509.crl:get_signature_name") + end + + return ffi.string(C.OBJ_nid2sn(nid)) +end + +-- AUTO GENERATED +function _M:get_signature_digest_name() + local nid = accessors.get_signature_nid(self.ctx) + if nid <= 0 then + return nil, format_error("x509.crl:get_signature_digest_name") + end + + local nid = find_sigid_algs(nid) + + return ffi.string(C.OBJ_nid2sn(nid)) +end +-- END AUTO GENERATED CODE + +return _M + diff --git a/server/resty/openssl/x509/csr.lua b/server/resty/openssl/x509/csr.lua new file mode 100644 index 0000000..08c4860 --- /dev/null +++ b/server/resty/openssl/x509/csr.lua @@ -0,0 +1,531 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc +local ffi_cast = ffi.cast + +require "resty.openssl.include.pem" +require "resty.openssl.include.x509v3" +require "resty.openssl.include.x509.csr" +require "resty.openssl.include.asn1" +local stack_macro = require "resty.openssl.include.stack" +local stack_lib = require "resty.openssl.stack" +local pkey_lib = require "resty.openssl.pkey" +local digest_lib = require("resty.openssl.digest") +local extension_lib = require("resty.openssl.x509.extension") +local extensions_lib = require("resty.openssl.x509.extensions") +local bio_util = require "resty.openssl.auxiliary.bio" +local ctypes = require "resty.openssl.auxiliary.ctypes" +local ctx_lib = require "resty.openssl.ctx" +local txtnid2nid = require("resty.openssl.objects").txtnid2nid +local find_sigid_algs = require("resty.openssl.objects").find_sigid_algs +local format_error = require("resty.openssl.err").format_error +local version = require("resty.openssl.version") +local OPENSSL_10 = version.OPENSSL_10 +local OPENSSL_11_OR_LATER = version.OPENSSL_11_OR_LATER +local OPENSSL_3X = version.OPENSSL_3X +local BORINGSSL = version.BORINGSSL +local BORINGSSL_110 = version.BORINGSSL_110 -- used in boringssl-fips-20190808 + +local accessors = {} + +accessors.set_subject_name = C.X509_REQ_set_subject_name +accessors.get_pubkey = C.X509_REQ_get_pubkey +accessors.set_pubkey = C.X509_REQ_set_pubkey +accessors.set_version = C.X509_REQ_set_version + +if OPENSSL_11_OR_LATER or BORINGSSL_110 then + accessors.get_signature_nid = C.X509_REQ_get_signature_nid +elseif OPENSSL_10 then + accessors.get_signature_nid = function(csr) + if csr == nil or csr.sig_alg == nil then + return nil + end + return C.OBJ_obj2nid(csr.sig_alg.algorithm) + end +end + +if OPENSSL_11_OR_LATER and not BORINGSSL_110 then + accessors.get_subject_name = C.X509_REQ_get_subject_name -- returns internal ptr + accessors.get_version = C.X509_REQ_get_version +elseif OPENSSL_10 or BORINGSSL_110 then + accessors.get_subject_name = function(csr) + if csr == nil or csr.req_info == nil then + return nil + end + return csr.req_info.subject + end + accessors.get_version = function(csr) + if csr == nil or csr.req_info == nil then + return nil + end + return C.ASN1_INTEGER_get(csr.req_info.version) + end +end + +local function __tostring(self, fmt) + if not fmt or fmt == 'PEM' then + return bio_util.read_wrap(C.PEM_write_bio_X509_REQ, self.ctx) + elseif fmt == 'DER' then + return bio_util.read_wrap(C.i2d_X509_REQ_bio, self.ctx) + else + return nil, "x509.csr:tostring: can only write PEM or DER format, not " .. fmt + end +end + +local _M = {} +local mt = { __index = _M, __tostring = __tostring } + +local x509_req_ptr_ct = ffi.typeof("X509_REQ*") + +local stack_ptr_type = ffi.typeof("struct stack_st *[1]") +local x509_extensions_gc = stack_lib.gc_of("X509_EXTENSION") + +function _M.new(csr, fmt, properties) + local ctx + if not csr then + if OPENSSL_3X then + ctx = C.X509_REQ_new_ex(ctx_lib.get_libctx(), properties) + else + ctx = C.X509_REQ_new() + end + if ctx == nil then + return nil, "x509.csr.new: X509_REQ_new() failed" + end + elseif type(csr) == "string" then + -- routine for load an existing csr + local bio = C.BIO_new_mem_buf(csr, #csr) + if bio == nil then + return nil, format_error("x509.csr.new: BIO_new_mem_buf") + end + + fmt = fmt or "*" + while true do -- luacheck: ignore 512 -- loop is executed at most once + if fmt == "PEM" or fmt == "*" then + ctx = C.PEM_read_bio_X509_REQ(bio, nil, nil, nil) + if ctx ~= nil then + break + elseif fmt == "*" then + -- BIO_reset; #define BIO_CTRL_RESET 1 + local code = C.BIO_ctrl(bio, 1, 0, nil) + if code ~= 1 then + return nil, "x509.csr.new: BIO_ctrl() failed: " .. code + end + end + end + if fmt == "DER" or fmt == "*" then + ctx = C.d2i_X509_REQ_bio(bio, nil) + end + break + end + C.BIO_free(bio) + if ctx == nil then + return nil, format_error("x509.csr.new") + end + -- clear errors occur when trying + C.ERR_clear_error() + else + return nil, "x509.csr.new: expect nil or a string at #1" + end + ffi_gc(ctx, C.X509_REQ_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l and l.ctx and ffi.istype(x509_req_ptr_ct, l.ctx) +end + +function _M:tostring(fmt) + return __tostring(self, fmt) +end + +function _M:to_PEM() + return __tostring(self, "PEM") +end + +function _M:check_private_key(key) + if not pkey_lib.istype(key) then + return false, "x509.csr:check_private_key: except a pkey instance at #1" + end + + if not key:is_private() then + return false, "x509.csr:check_private_key: not a private key" + end + + if C.X509_REQ_check_private_key(self.ctx, key.ctx) == 1 then + return true + end + return false, format_error("x509.csr:check_private_key") +end + +--- Get all csr extensions +-- @tparam table self Instance of csr +-- @treturn Extensions object +function _M:get_extensions() + local extensions = C.X509_REQ_get_extensions(self.ctx) + -- GC handler is sk_X509_EXTENSION_pop_free + ffi_gc(extensions, x509_extensions_gc) + + return extensions_lib.dup(extensions) +end + +local function get_extension(ctx, nid_txt, last_pos) + local nid, err = txtnid2nid(nid_txt) + if err then + return nil, nil, err + end + + local extensions = C.X509_REQ_get_extensions(ctx) + if extensions == nil then + return nil, nil, format_error("csr.get_extension: X509_REQ_get_extensions") + end + ffi_gc(extensions, x509_extensions_gc) + + -- make 1-index array to 0-index + last_pos = (last_pos or 0) -1 + local ext_idx = C.X509v3_get_ext_by_NID(extensions, nid, last_pos) + if ext_idx == -1 then + err = ("X509v3_get_ext_by_NID extension for %d not found"):format(nid) + return nil, -1, format_error(err) + end + + local ctx = C.X509v3_get_ext(extensions, ext_idx) + if ctx == nil then + return nil, nil, format_error("X509v3_get_ext") + end + + return ctx, ext_idx, nil +end + +--- Get a csr extension +-- @tparam table self Instance of csr +-- @tparam string|number Nid number or name of the extension +-- @tparam number Position to start looking for the extension; default to look from start if omitted +-- @treturn Parsed extension object or nil if not found +function _M:get_extension(nid_txt, last_pos) + local ctx, pos, err = get_extension(self.ctx, nid_txt, last_pos) + if err then + return nil, nil, "x509.csr:get_extension: " .. err + end + local ext, err = extension_lib.dup(ctx) + if err then + return nil, nil, "x509.csr:get_extension: " .. err + end + return ext, pos+1 +end + +local function modify_extension(replace, ctx, nid, toset, crit) + local extensions_ptr = stack_ptr_type() + extensions_ptr[0] = C.X509_REQ_get_extensions(ctx) + local need_cleanup = extensions_ptr[0] ~= nil and + -- extensions_ptr being nil is fine: it may just because there's no extension yet + -- https://github.com/openssl/openssl/commit/2039ac07b401932fa30a05ade80b3626e189d78a + -- introduces a change that a empty stack instead of NULL will be returned in no extension + -- is found. so we need to double check the number if it's not NULL. + stack_macro.OPENSSL_sk_num(extensions_ptr[0]) > 0 + + local flag + if replace then + -- x509v3.h: # define X509V3_ADD_REPLACE 2L + flag = 0x2 + else + -- x509v3.h: # define X509V3_ADD_APPEND 1L + flag = 0x1 + end + + local code = C.X509V3_add1_i2d(extensions_ptr, nid, toset, crit and 1 or 0, flag) + -- when the stack is newly allocated, we want to cleanup the newly created stack as well + -- setting the gc handler here as it's mutated in X509V3_add1_i2d if it's pointing to NULL + ffi_gc(extensions_ptr[0], x509_extensions_gc) + if code ~= 1 then + return false, format_error("X509V3_add1_i2d", code) + end + + code = C.X509_REQ_add_extensions(ctx, extensions_ptr[0]) + if code ~= 1 then + return false, format_error("X509_REQ_add_extensions", code) + end + + if need_cleanup then + -- cleanup old attributes + -- delete the first only, why? + local attr = C.X509_REQ_delete_attr(ctx, 0) + if attr ~= nil then + C.X509_ATTRIBUTE_free(attr) + end + end + + -- mark encoded form as invalid so next time it will be re-encoded + if OPENSSL_11_OR_LATER then + C.i2d_re_X509_REQ_tbs(ctx, nil) + else + ctx.req_info.enc.modified = 1 + end + + return true +end + +local function add_extension(...) + return modify_extension(false, ...) +end + +local function replace_extension(...) + return modify_extension(true, ...) +end + +function _M:add_extension(extension) + if not extension_lib.istype(extension) then + return false, "x509:set_extension: expect a x509.extension instance at #1" + end + + local nid = extension:get_object().nid + local toset = extension_lib.to_data(extension, nid) + return add_extension(self.ctx, nid, toset.ctx, extension:get_critical()) +end + +function _M:set_extension(extension) + if not extension_lib.istype(extension) then + return false, "x509:set_extension: expect a x509.extension instance at #1" + end + + local nid = extension:get_object().nid + local toset = extension_lib.to_data(extension, nid) + return replace_extension(self.ctx, nid, toset.ctx, extension:get_critical()) +end + +function _M:set_extension_critical(nid_txt, crit, last_pos) + local nid, err = txtnid2nid(nid_txt) + if err then + return nil, "x509.csr:set_extension_critical: " .. err + end + + local extension, _, err = get_extension(self.ctx, nid, last_pos) + if err then + return nil, "x509.csr:set_extension_critical: " .. err + end + + local toset = extension_lib.to_data({ + ctx = extension + }, nid) + return replace_extension(self.ctx, nid, toset.ctx, crit and 1 or 0) +end + +function _M:get_extension_critical(nid_txt, last_pos) + local ctx, _, err = get_extension(self.ctx, nid_txt, last_pos) + if err then + return nil, "x509.csr:get_extension_critical: " .. err + end + + return C.X509_EXTENSION_get_critical(ctx) == 1 +end + +-- START AUTO GENERATED CODE + +-- AUTO GENERATED +function _M:sign(pkey, digest) + if not pkey_lib.istype(pkey) then + return false, "x509.csr:sign: expect a pkey instance at #1" + end + + local digest_algo + if digest then + if not digest_lib.istype(digest) then + return false, "x509.csr:sign: expect a digest instance at #2" + elseif not digest.algo then + return false, "x509.csr:sign: expect a digest instance to have algo member" + end + digest_algo = digest.algo + elseif BORINGSSL then + digest_algo = C.EVP_get_digestbyname('sha256') + end + + -- returns size of signature if success + if C.X509_REQ_sign(self.ctx, pkey.ctx, digest_algo) == 0 then + return false, format_error("x509.csr:sign") + end + + return true +end + +-- AUTO GENERATED +function _M:verify(pkey) + if not pkey_lib.istype(pkey) then + return false, "x509.csr:verify: expect a pkey instance at #1" + end + + local code = C.X509_REQ_verify(self.ctx, pkey.ctx) + if code == 1 then + return true + elseif code == 0 then + return false + else -- typically -1 + return false, format_error("x509.csr:verify", code) + end +end +-- AUTO GENERATED +function _M:get_subject_name() + local got = accessors.get_subject_name(self.ctx) + if got == nil then + return nil + end + local lib = require("resty.openssl.x509.name") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED +function _M:set_subject_name(toset) + local lib = require("resty.openssl.x509.name") + if lib.istype and not lib.istype(toset) then + return false, "x509.csr:set_subject_name: expect a x509.name instance at #1" + end + toset = toset.ctx + if accessors.set_subject_name(self.ctx, toset) == 0 then + return false, format_error("x509.csr:set_subject_name") + end + return true +end + +-- AUTO GENERATED +function _M:get_pubkey() + local got = accessors.get_pubkey(self.ctx) + if got == nil then + return nil + end + local lib = require("resty.openssl.pkey") + -- returned a copied instance directly + return lib.new(got) +end + +-- AUTO GENERATED +function _M:set_pubkey(toset) + local lib = require("resty.openssl.pkey") + if lib.istype and not lib.istype(toset) then + return false, "x509.csr:set_pubkey: expect a pkey instance at #1" + end + toset = toset.ctx + if accessors.set_pubkey(self.ctx, toset) == 0 then + return false, format_error("x509.csr:set_pubkey") + end + return true +end + +-- AUTO GENERATED +function _M:get_version() + local got = accessors.get_version(self.ctx) + if got == nil then + return nil + end + + got = tonumber(got) + 1 + + return got +end + +-- AUTO GENERATED +function _M:set_version(toset) + if type(toset) ~= "number" then + return false, "x509.csr:set_version: expect a number at #1" + end + + -- Note: this is defined by standards (X.509 et al) to be one less than the certificate version. + -- So a version 3 certificate will return 2 and a version 1 certificate will return 0. + toset = toset - 1 + + if accessors.set_version(self.ctx, toset) == 0 then + return false, format_error("x509.csr:set_version") + end + return true +end + +local NID_subject_alt_name = C.OBJ_sn2nid("subjectAltName") +assert(NID_subject_alt_name ~= 0) + +-- AUTO GENERATED: EXTENSIONS +function _M:get_subject_alt_name() + local crit = ctypes.ptr_of_int() + local extensions = C.X509_REQ_get_extensions(self.ctx) + -- GC handler is sk_X509_EXTENSION_pop_free + ffi_gc(extensions, x509_extensions_gc) + local got = C.X509V3_get_d2i(extensions, NID_subject_alt_name, crit, nil) + crit = tonumber(crit[0]) + if crit == -1 then -- not found + return nil + elseif crit == -2 then + return nil, "x509.csr:get_subject_alt_name: extension of subject_alt_name occurs more than one times, " .. + "this is not yet implemented. Please use get_extension instead." + elseif got == nil then + return nil, format_error("x509.csr:get_subject_alt_name") + end + + -- Note: here we only free the stack itself not elements + -- since there seems no way to increase ref count for a GENERAL_NAME + -- we left the elements referenced by the new-dup'ed stack + local got_ref = got + ffi_gc(got_ref, stack_lib.gc_of("GENERAL_NAME")) + got = ffi_cast("GENERAL_NAMES*", got_ref) + local lib = require("resty.openssl.x509.altname") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_subject_alt_name(toset) + local lib = require("resty.openssl.x509.altname") + if lib.istype and not lib.istype(toset) then + return false, "x509.csr:set_subject_alt_name: expect a x509.altname instance at #1" + end + toset = toset.ctx + return replace_extension(self.ctx, NID_subject_alt_name, toset) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_subject_alt_name_critical(crit) + return _M.set_extension_critical(self, NID_subject_alt_name, crit) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:get_subject_alt_name_critical() + return _M.get_extension_critical(self, NID_subject_alt_name) +end + + +-- AUTO GENERATED +function _M:get_signature_nid() + local nid = accessors.get_signature_nid(self.ctx) + if nid <= 0 then + return nil, format_error("x509.csr:get_signature_nid") + end + + return nid +end + +-- AUTO GENERATED +function _M:get_signature_name() + local nid = accessors.get_signature_nid(self.ctx) + if nid <= 0 then + return nil, format_error("x509.csr:get_signature_name") + end + + return ffi.string(C.OBJ_nid2sn(nid)) +end + +-- AUTO GENERATED +function _M:get_signature_digest_name() + local nid = accessors.get_signature_nid(self.ctx) + if nid <= 0 then + return nil, format_error("x509.csr:get_signature_digest_name") + end + + local nid = find_sigid_algs(nid) + + return ffi.string(C.OBJ_nid2sn(nid)) +end +-- END AUTO GENERATED CODE + +return _M + diff --git a/server/resty/openssl/x509/extension.lua b/server/resty/openssl/x509/extension.lua new file mode 100644 index 0000000..ca23158 --- /dev/null +++ b/server/resty/openssl/x509/extension.lua @@ -0,0 +1,281 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc +local ffi_new = ffi.new +local ffi_cast = ffi.cast +local ffi_str = ffi.string + +require "resty.openssl.include.x509" +require "resty.openssl.include.x509.extension" +require "resty.openssl.include.x509v3" +require "resty.openssl.include.bio" +require "resty.openssl.include.conf" +local asn1_macro = require("resty.openssl.include.asn1") +local objects_lib = require "resty.openssl.objects" +local stack_lib = require("resty.openssl.stack") +local bio_util = require "resty.openssl.auxiliary.bio" +local format_error = require("resty.openssl.err").format_error +local OPENSSL_3X = require("resty.openssl.version").OPENSSL_3X +local BORINGSSL = require("resty.openssl.version").BORINGSSL + +local _M = {} +local mt = { __index = _M } + +local x509_extension_ptr_ct = ffi.typeof("X509_EXTENSION*") + +local extension_types = { + issuer = "resty.openssl.x509", + subject = "resty.openssl.x509", + request = "resty.openssl.x509.csr", + crl = "resty.openssl.x509.crl", +} + +if OPENSSL_3X then + extension_types["issuer_pkey"] = "resty.openssl.pkey" +end + +local nconf_load +if BORINGSSL then + nconf_load = function() + return nil, "NCONF_load_bio not exported in BoringSSL" + end +else + nconf_load = function(conf, str) + local bio = C.BIO_new_mem_buf(str, #str) + if bio == nil then + return format_error("BIO_new_mem_buf") + end + ffi_gc(bio, C.BIO_free) + + if C.NCONF_load_bio(conf, bio, nil) ~= 1 then + return format_error("NCONF_load_bio") + end + end +end + +function _M.new(txtnid, value, data) + local nid, err = objects_lib.txtnid2nid(txtnid) + if err then + return nil, "x509.extension.new: " .. err + end + if type(value) ~= 'string' then + return nil, "x509.extension.new: expect string at #2" + end + -- get a ptr and also zerofill the struct + local x509_ctx_ptr = ffi_new('X509V3_CTX[1]') + + local conf = C.NCONF_new(nil) + if conf == nil then + return nil, format_error("NCONF_new") + end + ffi_gc(conf, C.NCONF_free) + + if type(data) == 'table' then + local args = {} + if data.db then + if type(data.db) ~= 'string' then + return nil, "x509.extension.new: expect data.db must be a string" + end + err = nconf_load(conf, data) + if err then + return nil, "x509.extension.new: " .. err + end + end + + for k, t in pairs(extension_types) do + if data[k] then + local lib = require(t) + if not lib.istype(data[k]) then + return nil, "x509.extension.new: expect data." .. k .. " to be a " .. t .. " instance" + end + args[k] = data[k].ctx + end + end + C.X509V3_set_ctx(x509_ctx_ptr[0], args.issuer, args.subject, args.request, args.crl, 0) + + if OPENSSL_3X and args.issuer_pkey then + if C.X509V3_set_issuer_pkey(x509_ctx_ptr[0], args.issuer_pkey) ~= 1 then + return nil, format_error("x509.extension.new: X509V3_set_issuer_pkey") + end + end + + elseif type(data) == 'string' then + err = nconf_load(conf, data) + if err then + return nil, "x509.extension.new: " .. err + end + elseif data then + return nil, "x509.extension.new: expect nil, string a table at #3" + end + + -- setting conf is required for some extensions to load + -- crypto/x509/v3_conf.c:do_ext_conf "else if (method->r2i) {" branch + C.X509V3_set_nconf(x509_ctx_ptr[0], conf) + + local ctx = C.X509V3_EXT_nconf_nid(conf, x509_ctx_ptr[0], nid, value) + if ctx == nil then + return nil, format_error("x509.extension.new") + end + ffi_gc(ctx, C.X509_EXTENSION_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l.ctx and ffi.istype(x509_extension_ptr_ct, l.ctx) +end + +function _M.dup(ctx) + if not ffi.istype(x509_extension_ptr_ct, ctx) then + return nil, "x509.extension.dup: expect a x509.extension ctx at #1" + end + local ctx = C.X509_EXTENSION_dup(ctx) + if ctx == nil then + return nil, "x509.extension.dup: X509_EXTENSION_dup() failed" + end + + ffi_gc(ctx, C.X509_EXTENSION_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M.from_der(value, txtnid, crit) + local nid, err = objects_lib.txtnid2nid(txtnid) + if err then + return nil, "x509.extension.from_der: " .. err + end + if type(value) ~= 'string' then + return nil, "x509.extension.from_der: expect string at #1" + end + + local asn1 = C.ASN1_STRING_new() + if asn1 == nil then + return nil, format_error("x509.extension.from_der: ASN1_STRING_new") + end + ffi_gc(asn1, C.ASN1_STRING_free) + + if C.ASN1_STRING_set(asn1, value, #value) ~= 1 then + return nil, format_error("x509.extension.from_der: ASN1_STRING_set") + end + + local ctx = C.X509_EXTENSION_create_by_NID(nil, nid, crit and 1 or 0, asn1) + if ctx == nil then + return nil, format_error("x509.extension.from_der: X509_EXTENSION_create_by_NID") + end + ffi_gc(ctx, C.X509_EXTENSION_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M:to_der() + local asn1 = C.X509_EXTENSION_get_data(self.ctx) + + return ffi_str(asn1_macro.ASN1_STRING_get0_data(asn1)) +end + +function _M.from_data(any, txtnid, crit) + local nid, err = objects_lib.txtnid2nid(txtnid) + if err then + return nil, "x509.extension.from_der: " .. err + end + + if type(any) ~= "table" or type(any.ctx) ~= "cdata" then + return nil, "x509.extension.from_data: expect a table with ctx at #1" + elseif type(nid) ~= "number" then + return nil, "x509.extension.from_data: expect a table at #2" + end + + local ctx = C.X509V3_EXT_i2d(nid, crit and 1 or 0, any.ctx) + if ctx == nil then + return nil, format_error("x509.extension.from_data: X509V3_EXT_i2d") + end + ffi_gc(ctx, C.X509_EXTENSION_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +local NID_subject_alt_name = C.OBJ_sn2nid("subjectAltName") +assert(NID_subject_alt_name ~= 0) + +function _M.to_data(extension, nid) + if not _M.istype(extension) then + return nil, "x509.extension.dup: expect a x509.extension ctx at #1" + elseif type(nid) ~= "number" then + return nil, "x509.extension.to_data: expect a table at #2" + end + + local void_ptr = C.X509V3_EXT_d2i(extension.ctx) + if void_ptr == nil then + return nil, format_error("x509.extension:to_data: X509V3_EXT_d2i") + end + + if nid == NID_subject_alt_name then + -- Note: here we only free the stack itself not elements + -- since there seems no way to increase ref count for a GENERAL_NAME + -- we left the elements referenced by the new-dup'ed stack + ffi_gc(void_ptr, stack_lib.gc_of("GENERAL_NAME")) + local got = ffi_cast("GENERAL_NAMES*", void_ptr) + local lib = require("resty.openssl.x509.altname") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) + end + + return nil, string.format("x509.extension:to_data: don't know how to convert to NID %d", nid) +end + +function _M:get_object() + -- retruns the internal pointer + local asn1 = C.X509_EXTENSION_get_object(self.ctx) + + return objects_lib.obj2table(asn1) +end + +function _M:get_critical() + return C.X509_EXTENSION_get_critical(self.ctx) == 1 +end + +function _M:set_critical(crit) + if C.X509_EXTENSION_set_critical(self.ctx, crit and 1 or 0) ~= 1 then + return false, format_error("x509.extension:set_critical") + end + return true +end + +function _M:tostring() + local ret, err = bio_util.read_wrap(C.X509V3_EXT_print, self.ctx, 0, 0) + if not err then + return ret + end + -- fallback to ASN.1 print + local asn1 = C.X509_EXTENSION_get_data(self.ctx) + return bio_util.read_wrap(C.ASN1_STRING_print, asn1) +end + +_M.text = _M.tostring + +mt.__tostring = function(tbl) + local txt, err = _M.text(tbl) + if err then + error(err) + end + return txt +end + + +return _M diff --git a/server/resty/openssl/x509/extension/dist_points.lua b/server/resty/openssl/x509/extension/dist_points.lua new file mode 100644 index 0000000..b1d419b --- /dev/null +++ b/server/resty/openssl/x509/extension/dist_points.lua @@ -0,0 +1,75 @@ +local ffi = require "ffi" + +require "resty.openssl.include.x509" +require "resty.openssl.include.x509v3" +local altname_lib = require "resty.openssl.x509.altname" +local stack_lib = require "resty.openssl.stack" + +local _M = {} + +local stack_ptr_ct = ffi.typeof("OPENSSL_STACK*") + +local STACK = "DIST_POINT" +local new = stack_lib.new_of(STACK) +local dup = stack_lib.dup_of(STACK) + +-- TODO: return other attributes? +local cdp_decode_fullname = function(ctx) + return altname_lib.dup(ctx.distpoint.name.fullname) +end + +local mt = stack_lib.mt_of(STACK, cdp_decode_fullname, _M) + +function _M.new() + local ctx = new() + if ctx == nil then + return nil, "OPENSSL_sk_new_null() failed" + end + + local self = setmetatable({ + ctx = ctx, + _is_shallow_copy = false, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l.cast and ffi.istype(stack_ptr_ct, l.cast) +end + +function _M.dup(ctx) + if ctx == nil or not ffi.istype(stack_ptr_ct, ctx) then + return nil, "expect a stack ctx at #1" + end + local dup_ctx = dup(ctx) + + return setmetatable({ + ctx = dup_ctx, + -- don't let lua gc the original stack to keep its elements + _dupped_from = ctx, + _is_shallow_copy = true, + _elem_refs = {}, + _elem_refs_idx = 1, + }, mt), nil +end + +_M.all = function(stack) + local ret = {} + local _next = mt.__ipairs(stack) + while true do + local i, e = _next() + if i then + ret[i] = e + else + break + end + end + return ret +end + +_M.each = mt.__ipairs +_M.index = mt.__index +_M.count = mt.__len + +return _M diff --git a/server/resty/openssl/x509/extension/info_access.lua b/server/resty/openssl/x509/extension/info_access.lua new file mode 100644 index 0000000..21025a8 --- /dev/null +++ b/server/resty/openssl/x509/extension/info_access.lua @@ -0,0 +1,137 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc +local ffi_cast = ffi.cast + +require "resty.openssl.include.x509" +require "resty.openssl.include.x509v3" +require "resty.openssl.include.err" +local altname_lib = require "resty.openssl.x509.altname" +local stack_lib = require "resty.openssl.stack" + +local _M = {} + +local authority_info_access_ptr_ct = ffi.typeof("AUTHORITY_INFO_ACCESS*") + +local STACK = "ACCESS_DESCRIPTION" +local new = stack_lib.new_of(STACK) +local add = stack_lib.add_of(STACK) +local dup = stack_lib.dup_of(STACK) + +local aia_decode = function(ctx) + local nid = C.OBJ_obj2nid(ctx.method) + local gn = altname_lib.gn_decode(ctx.location) + return { nid, unpack(gn) } +end + +local mt = stack_lib.mt_of(STACK, aia_decode, _M) +local mt__pairs = mt.__pairs +mt.__pairs = function(tbl) + local f = mt__pairs(tbl) + return function() + local _, e = f() + if not e then return end + return unpack(e) + end +end + +function _M.new() + local ctx = new() + if ctx == nil then + return nil, "OPENSSL_sk_new_null() failed" + end + local cast = ffi_cast("AUTHORITY_INFO_ACCESS*", ctx) + + local self = setmetatable({ + ctx = ctx, + cast = cast, + _is_shallow_copy = false, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l.cast and ffi.istype(authority_info_access_ptr_ct, l.cast) +end + +function _M.dup(ctx) + if ctx == nil or not ffi.istype(authority_info_access_ptr_ct, ctx) then + return nil, "expect a AUTHORITY_INFO_ACCESS* ctx at #1" + end + local dup_ctx = dup(ctx) + + return setmetatable({ + ctx = dup_ctx, + cast = ffi_cast("AUTHORITY_INFO_ACCESS*", dup_ctx), + -- don't let lua gc the original stack to keep its elements + _dupped_from = ctx, + _is_shallow_copy = true, + _elem_refs = {}, + _elem_refs_idx = 1, + }, mt), nil +end + +function _M:add(nid, typ, value) + -- the stack element stays with stack + -- we shouldn't add gc handler if it's already been + -- pushed to stack. instead, rely on the gc handler + -- of the stack to release all memories + local ad = C.ACCESS_DESCRIPTION_new() + if ad == nil then + return nil, "ACCESS_DESCRIPTION_new() failed" + end + + -- C.ASN1_OBJECT_free(ad.method) + + local asn1 = C.OBJ_txt2obj(nid, 0) + if asn1 == nil then + C.ACCESS_DESCRIPTION_free(ad) + -- clean up error occurs during OBJ_txt2* + C.ERR_clear_error() + return nil, "invalid NID text " .. (nid or "nil") + end + + ad.method = asn1 + + local err = altname_lib.gn_set(ad.location, typ, value) + if err then + C.ACCESS_DESCRIPTION_free(ad) + return nil, err + end + + local _, err = add(self.ctx, ad) + if err then + C.ACCESS_DESCRIPTION_free(ad) + return nil, err + end + + -- if the stack is duplicated, the gc handler is not pop_free + -- handle the gc by ourselves + if self._is_shallow_copy then + ffi_gc(ad, C.ACCESS_DESCRIPTION_free) + self._elem_refs[self._elem_refs_idx] = ad + self._elem_refs_idx = self._elem_refs_idx + 1 + end + return self +end + +_M.all = function(stack) + local ret = {} + local _next = mt.__ipairs(stack) + while true do + local i, e = _next() + if i then + ret[i] = e + else + break + end + end + return ret +end + +_M.each = mt.__ipairs +_M.index = mt.__index +_M.count = mt.__len + +return _M diff --git a/server/resty/openssl/x509/extensions.lua b/server/resty/openssl/x509/extensions.lua new file mode 100644 index 0000000..3b64b8a --- /dev/null +++ b/server/resty/openssl/x509/extensions.lua @@ -0,0 +1,84 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc + +local stack_lib = require "resty.openssl.stack" +local extension_lib = require "resty.openssl.x509.extension" +local format_error = require("resty.openssl.err").format_error + +local _M = {} + +local stack_ptr_ct = ffi.typeof("OPENSSL_STACK*") + +local STACK = "X509_EXTENSION" +local new = stack_lib.new_of(STACK) +local add = stack_lib.add_of(STACK) +local dup = stack_lib.dup_of(STACK) +local mt = stack_lib.mt_of(STACK, extension_lib.dup, _M) + +function _M.new() + local raw = new() + + local self = setmetatable({ + stack_of = STACK, + ctx = raw, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l.ctx and ffi.istype(stack_ptr_ct, l.ctx) + and l.stack_of and l.stack_of == STACK +end + +function _M.dup(ctx) + if ctx == nil or not ffi.istype(stack_ptr_ct, ctx) then + return nil, "x509.extensions.dup: expect a stack ctx at #1, got " .. type(ctx) + end + + local dup_ctx = dup(ctx) + + return setmetatable({ + ctx = dup_ctx, + -- don't let lua gc the original stack to keep its elements + _dupped_from = ctx, + _is_shallow_copy = true, + _elem_refs = {}, + _elem_refs_idx = 1, + }, mt), nil +end + +function _M:add(extension) + if not extension_lib.istype(extension) then + return nil, "expect a x509.extension instance at #1" + end + + local dup = C.X509_EXTENSION_dup(extension.ctx) + if dup == nil then + return nil, format_error("extensions:add: X509_EXTENSION_dup") + end + + local _, err = add(self.ctx, dup) + if err then + C.X509_EXTENSION_free(dup) + return nil, err + end + + -- if the stack is duplicated, the gc handler is not pop_free + -- handle the gc by ourselves + if self._is_shallow_copy then + ffi_gc(dup, C.X509_EXTENSION_free) + self._elem_refs[self._elem_refs_idx] = dup + self._elem_refs_idx = self._elem_refs_idx + 1 + end + + return true +end + +_M.all = stack_lib.all_func(mt) +_M.each = mt.__ipairs +_M.index = mt.__index +_M.count = mt.__len + +return _M diff --git a/server/resty/openssl/x509/init.lua b/server/resty/openssl/x509/init.lua new file mode 100644 index 0000000..5c259c8 --- /dev/null +++ b/server/resty/openssl/x509/init.lua @@ -0,0 +1,1071 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc +local ffi_str = ffi.string +local ffi_cast = ffi.cast + +require "resty.openssl.include.x509" +require "resty.openssl.include.x509v3" +require "resty.openssl.include.evp" +require "resty.openssl.include.objects" +local stack_macro = require("resty.openssl.include.stack") +local stack_lib = require("resty.openssl.stack") +local asn1_lib = require("resty.openssl.asn1") +local digest_lib = require("resty.openssl.digest") +local extension_lib = require("resty.openssl.x509.extension") +local pkey_lib = require("resty.openssl.pkey") +local bio_util = require "resty.openssl.auxiliary.bio" +local txtnid2nid = require("resty.openssl.objects").txtnid2nid +local find_sigid_algs = require("resty.openssl.objects").find_sigid_algs +local ctypes = require "resty.openssl.auxiliary.ctypes" +local ctx_lib = require "resty.openssl.ctx" +local format_error = require("resty.openssl.err").format_error +local version = require("resty.openssl.version") +local OPENSSL_10 = version.OPENSSL_10 +local OPENSSL_11_OR_LATER = version.OPENSSL_11_OR_LATER +local OPENSSL_3X = version.OPENSSL_3X +local BORINGSSL = version.BORINGSSL +local BORINGSSL_110 = version.BORINGSSL_110 -- used in boringssl-fips-20190808 + +-- accessors provides an openssl version neutral interface to lua layer +-- it doesn't handle any error, expect that to be implemented in +-- _M.set_X or _M.get_X +local accessors = {} + +accessors.get_pubkey = C.X509_get_pubkey -- returns new evp_pkey instance, don't need to dup +accessors.set_pubkey = C.X509_set_pubkey +accessors.set_version = C.X509_set_version +accessors.set_serial_number = C.X509_set_serialNumber +accessors.get_subject_name = C.X509_get_subject_name -- returns internal ptr, we dup it +accessors.set_subject_name = C.X509_set_subject_name +accessors.get_issuer_name = C.X509_get_issuer_name -- returns internal ptr, we dup it +accessors.set_issuer_name = C.X509_set_issuer_name +accessors.get_signature_nid = C.X509_get_signature_nid + +-- generally, use get1 if we return a lua table wrapped ctx which doesn't support dup. +-- in that case, a new struct is returned from C api, and we will handle gc. +-- openssl will increment the reference count for returned ptr, and won't free it when +-- parent struct is freed. +-- otherwise, use get0, which returns an internal pointer, we don't need to free it up. +-- it will be gone together with the parent struct. + +if BORINGSSL_110 then + accessors.get_not_before = C.X509_get0_notBefore -- returns internal ptr, we convert to number + accessors.set_not_before = C.X509_set_notBefore + accessors.get_not_after = C.X509_get0_notAfter -- returns internal ptr, we convert to number + accessors.set_not_after = C.X509_set_notAfter + accessors.get_version = function(x509) + if x509 == nil or x509.cert_info == nil or x509.cert_info.validity == nil then + return nil + end + return C.ASN1_INTEGER_get(x509.cert_info.version) + end + accessors.get_serial_number = C.X509_get_serialNumber -- returns internal ptr, we convert to bn +elseif OPENSSL_11_OR_LATER then + accessors.get_not_before = C.X509_get0_notBefore -- returns internal ptr, we convert to number + accessors.set_not_before = C.X509_set1_notBefore + accessors.get_not_after = C.X509_get0_notAfter -- returns internal ptr, we convert to number + accessors.set_not_after = C.X509_set1_notAfter + accessors.get_version = C.X509_get_version -- returns int + accessors.get_serial_number = C.X509_get0_serialNumber -- returns internal ptr, we convert to bn +elseif OPENSSL_10 then + accessors.get_not_before = function(x509) + if x509 == nil or x509.cert_info == nil or x509.cert_info.validity == nil then + return nil + end + return x509.cert_info.validity.notBefore + end + accessors.set_not_before = C.X509_set_notBefore + accessors.get_not_after = function(x509) + if x509 == nil or x509.cert_info == nil or x509.cert_info.validity == nil then + return nil + end + return x509.cert_info.validity.notAfter + end + accessors.set_not_after = C.X509_set_notAfter + accessors.get_version = function(x509) + if x509 == nil or x509.cert_info == nil or x509.cert_info.validity == nil then + return nil + end + return C.ASN1_INTEGER_get(x509.cert_info.version) + end + accessors.get_serial_number = C.X509_get_serialNumber -- returns internal ptr, we convert to bn +end + +local function __tostring(self, fmt) + if not fmt or fmt == 'PEM' then + return bio_util.read_wrap(C.PEM_write_bio_X509, self.ctx) + elseif fmt == 'DER' then + return bio_util.read_wrap(C.i2d_X509_bio, self.ctx) + else + return nil, "x509:tostring: can only write PEM or DER format, not " .. fmt + end +end + +local _M = {} +local mt = { __index = _M, __tostring = __tostring } + + +local x509_ptr_ct = ffi.typeof("X509*") + +-- only PEM format is supported for now +function _M.new(cert, fmt, properties) + local ctx + if not cert then + -- routine for create a new cert + if OPENSSL_3X then + ctx = C.X509_new_ex(ctx_lib.get_libctx(), properties) + else + ctx = C.X509_new() + end + if ctx == nil then + return nil, format_error("x509.new") + end + ffi_gc(ctx, C.X509_free) + + C.X509_gmtime_adj(accessors.get_not_before(ctx), 0) + C.X509_gmtime_adj(accessors.get_not_after(ctx), 0) + elseif type(cert) == "string" then + -- routine for load an existing cert + local bio = C.BIO_new_mem_buf(cert, #cert) + if bio == nil then + return nil, format_error("x509.new: BIO_new_mem_buf") + end + + fmt = fmt or "*" + while true do -- luacheck: ignore 512 -- loop is executed at most once + if fmt == "PEM" or fmt == "*" then + ctx = C.PEM_read_bio_X509(bio, nil, nil, nil) + if ctx ~= nil then + break + elseif fmt == "*" then + -- BIO_reset; #define BIO_CTRL_RESET 1 + local code = C.BIO_ctrl(bio, 1, 0, nil) + if code ~= 1 then + C.BIO_free(bio) + return nil, "x509.new: BIO_ctrl() failed: " .. code + end + end + end + if fmt == "DER" or fmt == "*" then + ctx = C.d2i_X509_bio(bio, nil) + end + break + end + C.BIO_free(bio) + if ctx == nil then + return nil, format_error("x509.new") + end + -- clear errors occur when trying + C.ERR_clear_error() + ffi_gc(ctx, C.X509_free) + elseif type(cert) == 'cdata' then + if ffi.istype(x509_ptr_ct, cert) then + ctx = cert + ffi_gc(ctx, C.X509_free) + else + return nil, "x509.new: expect a X509* cdata at #1" + end + else + return nil, "x509.new: expect nil or a string at #1" + end + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l.ctx and ffi.istype(x509_ptr_ct, l.ctx) +end + +function _M.dup(ctx) + if not ffi.istype(x509_ptr_ct, ctx) then + return nil, "x509.dup: expect a x509 ctx at #1" + end + local ctx = C.X509_dup(ctx) + if ctx == nil then + return nil, "x509.dup: X509_dup() failed" + end + + ffi_gc(ctx, C.X509_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M:tostring(fmt) + return __tostring(self, fmt) +end + +function _M:to_PEM() + return __tostring(self, "PEM") +end + +function _M:set_lifetime(not_before, not_after) + local ok, err + if not_before then + ok, err = self:set_not_before(not_before) + if err then + return ok, err + end + end + + if not_after then + ok, err = self:set_not_after(not_after) + if err then + return ok, err + end + end + + return true +end + +function _M:get_lifetime() + local not_before, err = self:get_not_before() + if not_before == nil then + return nil, nil, err + end + local not_after, err = self:get_not_after() + if not_after == nil then + return nil, nil, err + end + + return not_before, not_after, nil +end + +-- note: index is 0 based +local OPENSSL_STRING_value_at = function(ctx, i) + local ct = ffi_cast("OPENSSL_STRING", stack_macro.OPENSSL_sk_value(ctx, i)) + if ct == nil then + return nil + end + return ffi_str(ct) +end + +function _M:get_ocsp_url(return_all) + local st = C.X509_get1_ocsp(self.ctx) + + local count = stack_macro.OPENSSL_sk_num(st) + if count == 0 then + return + end + + local ret + if return_all then + ret = {} + for i=0,count-1 do + ret[i+1] = OPENSSL_STRING_value_at(st, i) + end + else + ret = OPENSSL_STRING_value_at(st, 0) + end + + C.X509_email_free(st) + return ret +end + +function _M:get_ocsp_request() + +end + +function _M:get_crl_url(return_all) + local cdp, err = self:get_crl_distribution_points() + if err then + return nil, err + end + + if not cdp or cdp:count() == 0 then + return + end + + if return_all then + local ret = {} + local cdp_iter = cdp:each() + while true do + local _, gn = cdp_iter() + if not gn then + break + end + local gn_iter = gn:each() + while true do + local k, v = gn_iter() + if not k then + break + elseif k == "URI" then + table.insert(ret, v) + end + end + end + return ret + else + local gn, err = cdp:index(1) + if err then + return nil, err + end + local iter = gn:each() + while true do + local k, v = iter() + if not k then + break + elseif k == "URI" then + return v + end + end + end +end + +local digest_length = ctypes.ptr_of_uint() +local digest_buf, digest_buf_size +local function digest(self, cfunc, typ, properties) + -- TODO: dedup the following with resty.openssl.digest + local ctx + if OPENSSL_11_OR_LATER then + ctx = C.EVP_MD_CTX_new() + ffi_gc(ctx, C.EVP_MD_CTX_free) + elseif OPENSSL_10 then + ctx = C.EVP_MD_CTX_create() + ffi_gc(ctx, C.EVP_MD_CTX_destroy) + end + if ctx == nil then + return nil, "x509:digest: failed to create EVP_MD_CTX" + end + + local algo + if OPENSSL_3X then + algo = C.EVP_MD_fetch(ctx_lib.get_libctx(), typ or 'sha1', properties) + else + algo = C.EVP_get_digestbyname(typ or 'sha1') + end + if algo == nil then + return nil, string.format("x509:digest: invalid digest type \"%s\"", typ) + end + + local md_size = OPENSSL_3X and C.EVP_MD_get_size(algo) or C.EVP_MD_size(algo) + if not digest_buf or digest_buf_size < md_size then + digest_buf = ctypes.uchar_array(md_size) + digest_buf_size = md_size + end + + if cfunc(self.ctx, algo, digest_buf, digest_length) ~= 1 then + return nil, format_error("x509:digest") + end + + return ffi_str(digest_buf, digest_length[0]) +end + +function _M:digest(typ, properties) + return digest(self, C.X509_digest, typ, properties) +end + +function _M:pubkey_digest(typ, properties) + return digest(self, C.X509_pubkey_digest, typ, properties) +end + +function _M:check_private_key(key) + if not pkey_lib.istype(key) then + return false, "x509:check_private_key: except a pkey instance at #1" + end + + if not key:is_private() then + return false, "x509:check_private_key: not a private key" + end + + if C.X509_check_private_key(self.ctx, key.ctx) == 1 then + return true + end + return false, format_error("x509:check_private_key") +end + +-- START AUTO GENERATED CODE + +-- AUTO GENERATED +function _M:sign(pkey, digest) + if not pkey_lib.istype(pkey) then + return false, "x509:sign: expect a pkey instance at #1" + end + + local digest_algo + if digest then + if not digest_lib.istype(digest) then + return false, "x509:sign: expect a digest instance at #2" + elseif not digest.algo then + return false, "x509:sign: expect a digest instance to have algo member" + end + digest_algo = digest.algo + elseif BORINGSSL then + digest_algo = C.EVP_get_digestbyname('sha256') + end + + -- returns size of signature if success + if C.X509_sign(self.ctx, pkey.ctx, digest_algo) == 0 then + return false, format_error("x509:sign") + end + + return true +end + +-- AUTO GENERATED +function _M:verify(pkey) + if not pkey_lib.istype(pkey) then + return false, "x509:verify: expect a pkey instance at #1" + end + + local code = C.X509_verify(self.ctx, pkey.ctx) + if code == 1 then + return true + elseif code == 0 then + return false + else -- typically -1 + return false, format_error("x509:verify", code) + end +end + +-- AUTO GENERATED +local function get_extension(ctx, nid_txt, last_pos) + last_pos = (last_pos or 0) - 1 + local nid, err = txtnid2nid(nid_txt) + if err then + return nil, nil, err + end + local pos = C.X509_get_ext_by_NID(ctx, nid, last_pos) + if pos == -1 then + return nil + end + local ctx = C.X509_get_ext(ctx, pos) + if ctx == nil then + return nil, nil, format_error() + end + return ctx, pos +end + +-- AUTO GENERATED +function _M:add_extension(extension) + if not extension_lib.istype(extension) then + return false, "x509:add_extension: expect a x509.extension instance at #1" + end + + -- X509_add_ext returnes the stack on success, and NULL on error + -- the X509_EXTENSION ctx is dupped internally + if C.X509_add_ext(self.ctx, extension.ctx, -1) == nil then + return false, format_error("x509:add_extension") + end + + return true +end + +-- AUTO GENERATED +function _M:get_extension(nid_txt, last_pos) + local ctx, pos, err = get_extension(self.ctx, nid_txt, last_pos) + if err then + return nil, nil, "x509:get_extension: " .. err + end + local ext, err = extension_lib.dup(ctx) + if err then + return nil, nil, "x509:get_extension: " .. err + end + return ext, pos+1 +end + +local X509_delete_ext +if OPENSSL_11_OR_LATER then + X509_delete_ext = C.X509_delete_ext +elseif OPENSSL_10 then + X509_delete_ext = function(ctx, pos) + return C.X509v3_delete_ext(ctx.cert_info.extensions, pos) + end +else + X509_delete_ext = function(...) + error("X509_delete_ext undefined") + end +end + +-- AUTO GENERATED +function _M:set_extension(extension, last_pos) + if not extension_lib.istype(extension) then + return false, "x509:set_extension: expect a x509.extension instance at #1" + end + + last_pos = (last_pos or 0) - 1 + + local nid = extension:get_object().nid + local pos = C.X509_get_ext_by_NID(self.ctx, nid, last_pos) + -- pos may be -1, which means not found, it's fine, we will add new one instead of replace + + local removed = X509_delete_ext(self.ctx, pos) + C.X509_EXTENSION_free(removed) + + if C.X509_add_ext(self.ctx, extension.ctx, pos) == nil then + return false, format_error("x509:set_extension") + end + + return true +end + +-- AUTO GENERATED +function _M:set_extension_critical(nid_txt, crit, last_pos) + local ctx, _, err = get_extension(self.ctx, nid_txt, last_pos) + if err then + return nil, "x509:set_extension_critical: " .. err + end + + if C.X509_EXTENSION_set_critical(ctx, crit and 1 or 0) ~= 1 then + return false, format_error("x509:set_extension_critical") + end + + return true +end + +-- AUTO GENERATED +function _M:get_extension_critical(nid_txt, last_pos) + local ctx, _, err = get_extension(self.ctx, nid_txt, last_pos) + if err then + return nil, "x509:get_extension_critical: " .. err + end + + return C.X509_EXTENSION_get_critical(ctx) == 1 +end + +-- AUTO GENERATED +function _M:get_serial_number() + local got = accessors.get_serial_number(self.ctx) + if got == nil then + return nil + end + + -- returns a new BIGNUM instance + got = C.ASN1_INTEGER_to_BN(got, nil) + if got == nil then + return false, format_error("x509:set: BN_to_ASN1_INTEGER") + end + -- bn will be duplicated thus this ctx should be freed up + ffi_gc(got, C.BN_free) + + local lib = require("resty.openssl.bn") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED +function _M:set_serial_number(toset) + local lib = require("resty.openssl.bn") + if lib.istype and not lib.istype(toset) then + return false, "x509:set_serial_number: expect a bn instance at #1" + end + toset = toset.ctx + + toset = C.BN_to_ASN1_INTEGER(toset, nil) + if toset == nil then + return false, format_error("x509:set: BN_to_ASN1_INTEGER") + end + -- "A copy of the serial number is used internally + -- so serial should be freed up after use."" + ffi_gc(toset, C.ASN1_INTEGER_free) + + if accessors.set_serial_number(self.ctx, toset) == 0 then + return false, format_error("x509:set_serial_number") + end + return true +end + +-- AUTO GENERATED +function _M:get_not_before() + local got = accessors.get_not_before(self.ctx) + if got == nil then + return nil + end + + got = asn1_lib.asn1_to_unix(got) + + return got +end + +-- AUTO GENERATED +function _M:set_not_before(toset) + if type(toset) ~= "number" then + return false, "x509:set_not_before: expect a number at #1" + end + + toset = C.ASN1_TIME_set(nil, toset) + ffi_gc(toset, C.ASN1_STRING_free) + + if accessors.set_not_before(self.ctx, toset) == 0 then + return false, format_error("x509:set_not_before") + end + return true +end + +-- AUTO GENERATED +function _M:get_not_after() + local got = accessors.get_not_after(self.ctx) + if got == nil then + return nil + end + + got = asn1_lib.asn1_to_unix(got) + + return got +end + +-- AUTO GENERATED +function _M:set_not_after(toset) + if type(toset) ~= "number" then + return false, "x509:set_not_after: expect a number at #1" + end + + toset = C.ASN1_TIME_set(nil, toset) + ffi_gc(toset, C.ASN1_STRING_free) + + if accessors.set_not_after(self.ctx, toset) == 0 then + return false, format_error("x509:set_not_after") + end + return true +end + +-- AUTO GENERATED +function _M:get_pubkey() + local got = accessors.get_pubkey(self.ctx) + if got == nil then + return nil + end + local lib = require("resty.openssl.pkey") + -- returned a copied instance directly + return lib.new(got) +end + +-- AUTO GENERATED +function _M:set_pubkey(toset) + local lib = require("resty.openssl.pkey") + if lib.istype and not lib.istype(toset) then + return false, "x509:set_pubkey: expect a pkey instance at #1" + end + toset = toset.ctx + if accessors.set_pubkey(self.ctx, toset) == 0 then + return false, format_error("x509:set_pubkey") + end + return true +end + +-- AUTO GENERATED +function _M:get_subject_name() + local got = accessors.get_subject_name(self.ctx) + if got == nil then + return nil + end + local lib = require("resty.openssl.x509.name") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED +function _M:set_subject_name(toset) + local lib = require("resty.openssl.x509.name") + if lib.istype and not lib.istype(toset) then + return false, "x509:set_subject_name: expect a x509.name instance at #1" + end + toset = toset.ctx + if accessors.set_subject_name(self.ctx, toset) == 0 then + return false, format_error("x509:set_subject_name") + end + return true +end + +-- AUTO GENERATED +function _M:get_issuer_name() + local got = accessors.get_issuer_name(self.ctx) + if got == nil then + return nil + end + local lib = require("resty.openssl.x509.name") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED +function _M:set_issuer_name(toset) + local lib = require("resty.openssl.x509.name") + if lib.istype and not lib.istype(toset) then + return false, "x509:set_issuer_name: expect a x509.name instance at #1" + end + toset = toset.ctx + if accessors.set_issuer_name(self.ctx, toset) == 0 then + return false, format_error("x509:set_issuer_name") + end + return true +end + +-- AUTO GENERATED +function _M:get_version() + local got = accessors.get_version(self.ctx) + if got == nil then + return nil + end + + got = tonumber(got) + 1 + + return got +end + +-- AUTO GENERATED +function _M:set_version(toset) + if type(toset) ~= "number" then + return false, "x509:set_version: expect a number at #1" + end + + -- Note: this is defined by standards (X.509 et al) to be one less than the certificate version. + -- So a version 3 certificate will return 2 and a version 1 certificate will return 0. + toset = toset - 1 + + if accessors.set_version(self.ctx, toset) == 0 then + return false, format_error("x509:set_version") + end + return true +end + +local NID_subject_alt_name = C.OBJ_sn2nid("subjectAltName") +assert(NID_subject_alt_name ~= 0) + +-- AUTO GENERATED: EXTENSIONS +function _M:get_subject_alt_name() + local crit = ctypes.ptr_of_int() + -- X509_get_ext_d2i returns internal pointer, always dup + -- for now this function always returns the first found extension + local got = C.X509_get_ext_d2i(self.ctx, NID_subject_alt_name, crit, nil) + crit = tonumber(crit[0]) + if crit == -1 then -- not found + return nil + elseif crit == -2 then + return nil, "x509:get_subject_alt_name: extension of subject_alt_name occurs more than one times, " .. + "this is not yet implemented. Please use get_extension instead." + elseif got == nil then + return nil, format_error("x509:get_subject_alt_name") + end + + -- Note: here we only free the stack itself not elements + -- since there seems no way to increase ref count for a GENERAL_NAME + -- we left the elements referenced by the new-dup'ed stack + local got_ref = got + ffi_gc(got_ref, stack_lib.gc_of("GENERAL_NAME")) + got = ffi_cast("GENERAL_NAMES*", got_ref) + local lib = require("resty.openssl.x509.altname") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_subject_alt_name(toset) + local lib = require("resty.openssl.x509.altname") + if lib.istype and not lib.istype(toset) then + return false, "x509:set_subject_alt_name: expect a x509.altname instance at #1" + end + toset = toset.ctx + -- x509v3.h: # define X509V3_ADD_REPLACE 2L + if C.X509_add1_ext_i2d(self.ctx, NID_subject_alt_name, toset, 0, 0x2) ~= 1 then + return false, format_error("x509:set_subject_alt_name") + end + return true +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_subject_alt_name_critical(crit) + return _M.set_extension_critical(self, NID_subject_alt_name, crit) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:get_subject_alt_name_critical() + return _M.get_extension_critical(self, NID_subject_alt_name) +end + +local NID_issuer_alt_name = C.OBJ_sn2nid("issuerAltName") +assert(NID_issuer_alt_name ~= 0) + +-- AUTO GENERATED: EXTENSIONS +function _M:get_issuer_alt_name() + local crit = ctypes.ptr_of_int() + -- X509_get_ext_d2i returns internal pointer, always dup + -- for now this function always returns the first found extension + local got = C.X509_get_ext_d2i(self.ctx, NID_issuer_alt_name, crit, nil) + crit = tonumber(crit[0]) + if crit == -1 then -- not found + return nil + elseif crit == -2 then + return nil, "x509:get_issuer_alt_name: extension of issuer_alt_name occurs more than one times, " .. + "this is not yet implemented. Please use get_extension instead." + elseif got == nil then + return nil, format_error("x509:get_issuer_alt_name") + end + + -- Note: here we only free the stack itself not elements + -- since there seems no way to increase ref count for a GENERAL_NAME + -- we left the elements referenced by the new-dup'ed stack + local got_ref = got + ffi_gc(got_ref, stack_lib.gc_of("GENERAL_NAME")) + got = ffi_cast("GENERAL_NAMES*", got_ref) + local lib = require("resty.openssl.x509.altname") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_issuer_alt_name(toset) + local lib = require("resty.openssl.x509.altname") + if lib.istype and not lib.istype(toset) then + return false, "x509:set_issuer_alt_name: expect a x509.altname instance at #1" + end + toset = toset.ctx + -- x509v3.h: # define X509V3_ADD_REPLACE 2L + if C.X509_add1_ext_i2d(self.ctx, NID_issuer_alt_name, toset, 0, 0x2) ~= 1 then + return false, format_error("x509:set_issuer_alt_name") + end + return true +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_issuer_alt_name_critical(crit) + return _M.set_extension_critical(self, NID_issuer_alt_name, crit) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:get_issuer_alt_name_critical() + return _M.get_extension_critical(self, NID_issuer_alt_name) +end + +local NID_basic_constraints = C.OBJ_sn2nid("basicConstraints") +assert(NID_basic_constraints ~= 0) + +-- AUTO GENERATED: EXTENSIONS +function _M:get_basic_constraints(name) + local crit = ctypes.ptr_of_int() + -- X509_get_ext_d2i returns internal pointer, always dup + -- for now this function always returns the first found extension + local got = C.X509_get_ext_d2i(self.ctx, NID_basic_constraints, crit, nil) + crit = tonumber(crit[0]) + if crit == -1 then -- not found + return nil + elseif crit == -2 then + return nil, "x509:get_basic_constraints: extension of basic_constraints occurs more than one times, " .. + "this is not yet implemented. Please use get_extension instead." + elseif got == nil then + return nil, format_error("x509:get_basic_constraints") + end + + local ctx = ffi_cast("BASIC_CONSTRAINTS*", got) + + local ca = ctx.ca == 0xFF + local pathlen = tonumber(C.ASN1_INTEGER_get(ctx.pathlen)) + + C.BASIC_CONSTRAINTS_free(ctx) + + if not name or type(name) ~= "string" then + got = { + ca = ca, + pathlen = pathlen, + } + elseif string.lower(name) == "ca" then + got = ca + elseif string.lower(name) == "pathlen" then + got = pathlen + end + + return got +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_basic_constraints(toset) + if type(toset) ~= "table" then + return false, "x509:set_basic_constraints: expect a table at #1" + end + + local cfg_lower = {} + for k, v in pairs(toset) do + cfg_lower[string.lower(k)] = v + end + + toset = C.BASIC_CONSTRAINTS_new() + if toset == nil then + return false, format_error("x509:set_BASIC_CONSTRAINTS") + end + ffi_gc(toset, C.BASIC_CONSTRAINTS_free) + + toset.ca = cfg_lower.ca and 0xFF or 0 + local pathlen = cfg_lower.pathlen and tonumber(cfg_lower.pathlen) + if pathlen then + C.ASN1_INTEGER_free(toset.pathlen) + + local asn1 = C.ASN1_STRING_type_new(pathlen) + if asn1 == nil then + return false, format_error("x509:set_BASIC_CONSTRAINTS: ASN1_STRING_type_new") + end + toset.pathlen = asn1 + + local code = C.ASN1_INTEGER_set(asn1, pathlen) + if code ~= 1 then + return false, format_error("x509:set_BASIC_CONSTRAINTS: ASN1_INTEGER_set", code) + end + end + + -- x509v3.h: # define X509V3_ADD_REPLACE 2L + if C.X509_add1_ext_i2d(self.ctx, NID_basic_constraints, toset, 0, 0x2) ~= 1 then + return false, format_error("x509:set_basic_constraints") + end + return true +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_basic_constraints_critical(crit) + return _M.set_extension_critical(self, NID_basic_constraints, crit) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:get_basic_constraints_critical() + return _M.get_extension_critical(self, NID_basic_constraints) +end + +local NID_info_access = C.OBJ_sn2nid("authorityInfoAccess") +assert(NID_info_access ~= 0) + +-- AUTO GENERATED: EXTENSIONS +function _M:get_info_access() + local crit = ctypes.ptr_of_int() + -- X509_get_ext_d2i returns internal pointer, always dup + -- for now this function always returns the first found extension + local got = C.X509_get_ext_d2i(self.ctx, NID_info_access, crit, nil) + crit = tonumber(crit[0]) + if crit == -1 then -- not found + return nil + elseif crit == -2 then + return nil, "x509:get_info_access: extension of info_access occurs more than one times, " .. + "this is not yet implemented. Please use get_extension instead." + elseif got == nil then + return nil, format_error("x509:get_info_access") + end + + -- Note: here we only free the stack itself not elements + -- since there seems no way to increase ref count for a ACCESS_DESCRIPTION + -- we left the elements referenced by the new-dup'ed stack + local got_ref = got + ffi_gc(got_ref, stack_lib.gc_of("ACCESS_DESCRIPTION")) + got = ffi_cast("AUTHORITY_INFO_ACCESS*", got_ref) + local lib = require("resty.openssl.x509.extension.info_access") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_info_access(toset) + local lib = require("resty.openssl.x509.extension.info_access") + if lib.istype and not lib.istype(toset) then + return false, "x509:set_info_access: expect a x509.extension.info_access instance at #1" + end + toset = toset.ctx + -- x509v3.h: # define X509V3_ADD_REPLACE 2L + if C.X509_add1_ext_i2d(self.ctx, NID_info_access, toset, 0, 0x2) ~= 1 then + return false, format_error("x509:set_info_access") + end + return true +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_info_access_critical(crit) + return _M.set_extension_critical(self, NID_info_access, crit) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:get_info_access_critical() + return _M.get_extension_critical(self, NID_info_access) +end + +local NID_crl_distribution_points = C.OBJ_sn2nid("crlDistributionPoints") +assert(NID_crl_distribution_points ~= 0) + +-- AUTO GENERATED: EXTENSIONS +function _M:get_crl_distribution_points() + local crit = ctypes.ptr_of_int() + -- X509_get_ext_d2i returns internal pointer, always dup + -- for now this function always returns the first found extension + local got = C.X509_get_ext_d2i(self.ctx, NID_crl_distribution_points, crit, nil) + crit = tonumber(crit[0]) + if crit == -1 then -- not found + return nil + elseif crit == -2 then + return nil, "x509:get_crl_distribution_points: extension of crl_distribution_points occurs more than one times, " .. + "this is not yet implemented. Please use get_extension instead." + elseif got == nil then + return nil, format_error("x509:get_crl_distribution_points") + end + + -- Note: here we only free the stack itself not elements + -- since there seems no way to increase ref count for a DIST_POINT + -- we left the elements referenced by the new-dup'ed stack + local got_ref = got + ffi_gc(got_ref, stack_lib.gc_of("DIST_POINT")) + got = ffi_cast("OPENSSL_STACK*", got_ref) + local lib = require("resty.openssl.x509.extension.dist_points") + -- the internal ptr is returned, ie we need to copy it + return lib.dup(got) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_crl_distribution_points(toset) + local lib = require("resty.openssl.x509.extension.dist_points") + if lib.istype and not lib.istype(toset) then + return false, "x509:set_crl_distribution_points: expect a x509.extension.dist_points instance at #1" + end + toset = toset.ctx + -- x509v3.h: # define X509V3_ADD_REPLACE 2L + if C.X509_add1_ext_i2d(self.ctx, NID_crl_distribution_points, toset, 0, 0x2) ~= 1 then + return false, format_error("x509:set_crl_distribution_points") + end + return true +end + +-- AUTO GENERATED: EXTENSIONS +function _M:set_crl_distribution_points_critical(crit) + return _M.set_extension_critical(self, NID_crl_distribution_points, crit) +end + +-- AUTO GENERATED: EXTENSIONS +function _M:get_crl_distribution_points_critical() + return _M.get_extension_critical(self, NID_crl_distribution_points) +end + + +-- AUTO GENERATED +function _M:get_signature_nid() + local nid = accessors.get_signature_nid(self.ctx) + if nid <= 0 then + return nil, format_error("x509:get_signature_nid") + end + + return nid +end + +-- AUTO GENERATED +function _M:get_signature_name() + local nid = accessors.get_signature_nid(self.ctx) + if nid <= 0 then + return nil, format_error("x509:get_signature_name") + end + + return ffi.string(C.OBJ_nid2sn(nid)) +end + +-- AUTO GENERATED +function _M:get_signature_digest_name() + local nid = accessors.get_signature_nid(self.ctx) + if nid <= 0 then + return nil, format_error("x509:get_signature_digest_name") + end + + local nid = find_sigid_algs(nid) + + return ffi.string(C.OBJ_nid2sn(nid)) +end +-- END AUTO GENERATED CODE + +return _M diff --git a/server/resty/openssl/x509/name.lua b/server/resty/openssl/x509/name.lua new file mode 100644 index 0000000..f83fcc1 --- /dev/null +++ b/server/resty/openssl/x509/name.lua @@ -0,0 +1,156 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc +local ffi_str = ffi.string + +require "resty.openssl.include.x509.name" +require "resty.openssl.include.err" +local objects_lib = require "resty.openssl.objects" +local asn1_macro = require "resty.openssl.include.asn1" + +-- local MBSTRING_FLAG = 0x1000 +local MBSTRING_ASC = 0x1001 -- (MBSTRING_FLAG|1) + +local _M = {} + +local x509_name_ptr_ct = ffi.typeof("X509_NAME*") + +-- starts from 0 +local function value_at(ctx, i) + local entry = C.X509_NAME_get_entry(ctx, i) + local obj = C.X509_NAME_ENTRY_get_object(entry) + local ret = objects_lib.obj2table(obj) + + local str = C.X509_NAME_ENTRY_get_data(entry) + if str ~= nil then + ret.blob = ffi_str(asn1_macro.ASN1_STRING_get0_data(str)) + end + + return ret +end + +local function iter(tbl) + local i = 0 + local n = tonumber(C.X509_NAME_entry_count(tbl.ctx)) + return function() + i = i + 1 + if i <= n then + local obj = value_at(tbl.ctx, i-1) + return obj.sn or obj.ln or obj.id, obj + end + end +end + +local mt = { + __index = _M, + __pairs = iter, + __len = function(tbl) return tonumber(C.X509_NAME_entry_count(tbl.ctx)) end, +} + +function _M.new() + local ctx = C.X509_NAME_new() + if ctx == nil then + return nil, "x509.name.new: X509_NAME_new() failed" + end + ffi_gc(ctx, C.X509_NAME_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l.ctx and ffi.istype(x509_name_ptr_ct, l.ctx) +end + +function _M.dup(ctx) + if not ffi.istype(x509_name_ptr_ct, ctx) then + return nil, "x509.name.dup: expect a x509.name ctx at #1, got " .. type(ctx) + end + local ctx = C.X509_NAME_dup(ctx) + ffi_gc(ctx, C.X509_NAME_free) + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +function _M:add(nid, txt) + local asn1 = C.OBJ_txt2obj(nid, 0) + if asn1 == nil then + -- clean up error occurs during OBJ_txt2* + C.ERR_clear_error() + return nil, "x509.name:add: invalid NID text " .. (nid or "nil") + end + + local code = C.X509_NAME_add_entry_by_OBJ(self.ctx, asn1, MBSTRING_ASC, txt, #txt, -1, 0) + C.ASN1_OBJECT_free(asn1) + + if code ~= 1 then + return nil, "x509.name:add: X509_NAME_add_entry_by_OBJ() failed" + end + + return self +end + +function _M:find(nid, last_pos) + local asn1 = C.OBJ_txt2obj(nid, 0) + if asn1 == nil then + -- clean up error occurs during OBJ_txt2* + C.ERR_clear_error() + return nil, nil, "x509.name:find: invalid NID text " .. (nid or "nil") + end + -- make 1-index array to 0-index + last_pos = (last_pos or 0) - 1 + + local pos = C.X509_NAME_get_index_by_OBJ(self.ctx, asn1, last_pos) + if pos == -1 then + return nil + end + + C.ASN1_OBJECT_free(asn1) + + return value_at(self.ctx, pos), pos+1 +end + +-- fallback function to iterate if LUAJIT_ENABLE_LUA52COMPAT not enabled +function _M:all() + local ret = {} + local _next = iter(self) + while true do + local k, obj = _next() + if obj then + ret[k] = obj + else + break + end + end + return ret +end + +function _M:each() + return iter(self) +end + +mt.__tostring = function(self) + local values = {} + local _next = iter(self) + while true do + local k, v = _next() + if k then + table.insert(values, k .. "=" .. v.blob) + else + break + end + end + table.sort(values) + return table.concat(values, "/") +end + +_M.tostring = mt.__tostring + +return _M diff --git a/server/resty/openssl/x509/revoked.lua b/server/resty/openssl/x509/revoked.lua new file mode 100644 index 0000000..9762200 --- /dev/null +++ b/server/resty/openssl/x509/revoked.lua @@ -0,0 +1,108 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc + +require "resty.openssl.include.x509.crl" +require "resty.openssl.include.x509.revoked" +local bn_lib = require("resty.openssl.bn") +local format_error = require("resty.openssl.err").format_error + +local _M = {} +local mt = { __index = _M } + +local x509_revoked_ptr_ct = ffi.typeof('X509_REVOKED*') + +local NID_crl_reason = C.OBJ_txt2nid("CRLReason") +assert(NID_crl_reason > 0) + +--- Creates new instance of X509_REVOKED data +-- @tparam bn|number sn Serial number as number or bn instance +-- @tparam number time Revocation time +-- @tparam number reason Revocation reason +-- @treturn table instance of the module or nil +-- @treturn[opt] string Returns optional error message in case of error +function _M.new(sn, time, reason) + --- only convert to bn if it is number + if type(sn) == "number"then + sn = bn_lib.new(sn) + end + if not bn_lib.istype(sn) then + return nil, "x509.revoked.new: sn should be number or a bn instance" + end + + if type(time) ~= "number" then + return nil, "x509.revoked.new: expect a number at #2" + end + if type(reason) ~= "number" then + return nil, "x509.revoked.new: expect a number at #3" + end + + local ctx = C.X509_REVOKED_new() + ffi_gc(ctx, C.X509_REVOKED_free) + + -- serial number + local sn_asn1 = C.BN_to_ASN1_INTEGER(sn.ctx, nil) + if sn_asn1 == nil then + return nil, "x509.revoked.new: BN_to_ASN1_INTEGER() failed" + end + ffi_gc(sn_asn1, C.ASN1_INTEGER_free) + + if C.X509_REVOKED_set_serialNumber(ctx, sn_asn1) == 0 then + return nil, format_error("x509.revoked.new: X509_REVOKED_set_serialNumber()") + end + + -- time + time = C.ASN1_TIME_set(nil, time) + if time == nil then + return nil, format_error("x509.revoked.new: ASN1_TIME_set()") + end + ffi_gc(time, C.ASN1_STRING_free) + + if C.X509_REVOKED_set_revocationDate(ctx, time) == 0 then + return nil, format_error("x509.revoked.new: X509_REVOKED_set_revocationDate()") + end + + -- reason + local reason_asn1 = C.ASN1_ENUMERATED_new() + if reason_asn1 == nil then + return nil, "x509.revoked.new: ASN1_ENUMERATED_new() failed" + end + ffi_gc(reason_asn1, C.ASN1_ENUMERATED_free) + + local reason_ext = C.X509_EXTENSION_new() + if reason_ext == nil then + return nil, "x509.revoked.new: X509_EXTENSION_new() failed" + end + ffi_gc(reason_ext, C.X509_EXTENSION_free) + + if C.ASN1_ENUMERATED_set(reason_asn1, reason) == 0 then + return nil, format_error("x509.revoked.new: ASN1_ENUMERATED_set()") + end + + if C.X509_EXTENSION_set_data(reason_ext, reason_asn1) == 0 then + return nil, format_error("x509.revoked.new: X509_EXTENSION_set_data()") + end + + if C.X509_EXTENSION_set_object(reason_ext, C.OBJ_nid2obj(NID_crl_reason)) == 0 then + return nil, format_error("x509.revoked.new: X509_EXTENSION_set_object()") + end + + if C.X509_REVOKED_add_ext(ctx, reason_ext, 0) == 0 then + return nil, format_error("x509.revoked.new: X509_EXTENSION_set_object()") + end + + local self = setmetatable({ + ctx = ctx, + }, mt) + + return self, nil +end + +--- Type check +-- @tparam table Instance of revoked module +-- @treturn boolean true if instance is instance of revoked module false otherwise +function _M.istype(l) + return l and l.ctx and ffi.istype(x509_revoked_ptr_ct, l.ctx) +end + +return _M diff --git a/server/resty/openssl/x509/store.lua b/server/resty/openssl/x509/store.lua new file mode 100644 index 0000000..1722e4c --- /dev/null +++ b/server/resty/openssl/x509/store.lua @@ -0,0 +1,227 @@ +local ffi = require "ffi" +local C = ffi.C +local ffi_gc = ffi.gc +local ffi_str = ffi.string +local bor = bit.bor + +local x509_vfy_macro = require "resty.openssl.include.x509_vfy" +local x509_lib = require "resty.openssl.x509" +local chain_lib = require "resty.openssl.x509.chain" +local crl_lib = require "resty.openssl.x509.crl" +local ctx_lib = require "resty.openssl.ctx" +local format_error = require("resty.openssl.err").format_all_error +local format_all_error = require("resty.openssl.err").format_error +local OPENSSL_3X = require("resty.openssl.version").OPENSSL_3X + +local _M = {} +local mt = { __index = _M } + +_M.verify_flags = x509_vfy_macro.verify_flags + +local x509_store_ptr_ct = ffi.typeof('X509_STORE*') + +function _M.new() + local ctx = C.X509_STORE_new() + if ctx == nil then + return nil, "x509.store.new: X509_STORE_new() failed" + end + ffi_gc(ctx, C.X509_STORE_free) + + local self = setmetatable({ + ctx = ctx, + _elem_refs = {}, + _elem_refs_idx = 1, + }, mt) + + return self, nil +end + +function _M.istype(l) + return l and l.ctx and ffi.istype(x509_store_ptr_ct, l.ctx) +end + +function _M:use_default(properties) + if x509_vfy_macro.X509_STORE_set_default_paths(self.ctx, ctx_lib.get_libctx(), properties) ~= 1 then + return false, format_all_error("x509.store:use_default") + end + return true +end + +function _M:add(item) + local dup + local err + if x509_lib.istype(item) then + dup = C.X509_dup(item.ctx) + if dup == nil then + return false, "x509.store:add: X509_dup() failed" + end + -- ref counter of dup is increased by 1 + if C.X509_STORE_add_cert(self.ctx, dup) ~= 1 then + err = format_all_error("x509.store:add: X509_STORE_add_cert") + end + -- decrease the dup ctx ref count immediately to make leak test happy + C.X509_free(dup) + elseif crl_lib.istype(item) then + dup = C.X509_CRL_dup(item.ctx) + if dup == nil then + return false, "x509.store:add: X509_CRL_dup() failed" + end + -- ref counter of dup is increased by 1 + if C.X509_STORE_add_crl(self.ctx, dup) ~= 1 then + err = format_all_error("x509.store:add: X509_STORE_add_crl") + end + + -- define X509_V_FLAG_CRL_CHECK 0x4 + -- enables CRL checking for the certificate chain leaf certificate. + -- An error occurs if a suitable CRL cannot be found. + -- Note: this does not check for certificates in the chain. + if C.X509_STORE_set_flags(self.ctx, 0x4) ~= 1 then + return false, format_error("x509.store:add: X509_STORE_set_flags") + end + -- decrease the dup ctx ref count immediately to make leak test happy + C.X509_CRL_free(dup) + else + return false, "x509.store:add: expect an x509 or crl instance at #1" + end + + if err then + return false, err + end + + -- X509_STORE doesn't have stack gc handler, we need to gc by ourselves + self._elem_refs[self._elem_refs_idx] = dup + self._elem_refs_idx = self._elem_refs_idx + 1 + + return true +end + +function _M:load_file(path, properties) + if type(path) ~= "string" then + return false, "x509.store:load_file: expect a string at #1" + else + if x509_vfy_macro.X509_STORE_load_locations(self.ctx, path, nil, + ctx_lib.get_libctx(), properties) ~= 1 then + return false, format_all_error("x509.store:load_file") + end + end + + return true +end + +function _M:load_directory(path, properties) + if type(path) ~= "string" then + return false, "x509.store:load_directory expect a string at #1" + else + if x509_vfy_macro.X509_STORE_load_locations(self.ctx, nil, path, + ctx_lib.get_libctx(), properties) ~= 1 then + return false, format_all_error("x509.store:load_directory") + end + end + + return true +end + +function _M:set_depth(depth) + depth = depth and tonumber(depth) + if not depth then + return nil, "x509.store:set_depth: expect a number at #1" + end + + if C.X509_STORE_set_depth(self.ctx, depth) ~= 1 then + return false, format_error("x509.store:set_depth") + end + + return true +end + +function _M:set_purpose(purpose) + if type(purpose) ~= "string" then + return nil, "x509.store:set_purpose: expect a string at #1" + end + + local pchar = ffi.new("char[?]", #purpose, purpose) + local idx = C.X509_PURPOSE_get_by_sname(pchar) + idx = tonumber(idx) + + if idx == -1 then + return false, "invalid purpose \"" .. purpose .. "\"" + end + + local purp = C.X509_PURPOSE_get0(idx) + local i = C.X509_PURPOSE_get_id(purp) + + if C.X509_STORE_set_purpose(self.ctx, i) ~= 1 then + return false, format_error("x509.store:set_purpose: X509_STORE_set_purpose") + end + + return true +end + +function _M:set_flags(...) + local flag = 0 + for _, f in ipairs({...}) do + flag = bor(flag, f) + end + + if C.X509_STORE_set_flags(self.ctx, flag) ~= 1 then + return false, format_error("x509.store:set_flags: X509_STORE_set_flags") + end + + return true +end + +function _M:verify(x509, chain, return_chain, properties, verify_method) + if not x509_lib.istype(x509) then + return nil, "x509.store:verify: expect a x509 instance at #1" + elseif chain and not chain_lib.istype(chain) then + return nil, "x509.store:verify: expect a x509.chain instance at #1" + end + + local ctx + if OPENSSL_3X then + ctx = C.X509_STORE_CTX_new_ex(ctx_lib.get_libctx(), properties) + else + ctx = C.X509_STORE_CTX_new() + end + if ctx == nil then + return nil, "x509.store:verify: X509_STORE_CTX_new() failed" + end + + ffi_gc(ctx, C.X509_STORE_CTX_free) + + local chain_dup_ctx + if chain then + local chain_dup, err = chain_lib.dup(chain.ctx) + if err then + return nil, err + end + chain_dup_ctx = chain_dup.ctx + end + + if C.X509_STORE_CTX_init(ctx, self.ctx, x509.ctx, chain_dup_ctx) ~= 1 then + return nil, format_error("x509.store:verify: X509_STORE_CTX_init") + end + + if verify_method and C.X509_STORE_CTX_set_default(ctx, verify_method) ~= 1 then + return nil, "x509.store:verify: invalid verify_method \"" .. verify_method .. "\"" + end + + local code = C.X509_verify_cert(ctx) + if code == 1 then -- verified + if not return_chain then + return true, nil + end + local ret_chain_ctx = x509_vfy_macro.X509_STORE_CTX_get0_chain(ctx) + return chain_lib.dup(ret_chain_ctx) + elseif code == 0 then -- unverified + local vfy_code = C.X509_STORE_CTX_get_error(ctx) + + return nil, ffi_str(C.X509_verify_cert_error_string(vfy_code)) + end + + -- error + return nil, format_error("x509.store:verify: X509_verify_cert", code) + +end + +return _M |