From d34b702a17b4f491e2a97e971da1d6125d482066 Mon Sep 17 00:00:00 2001 From: Wangchong Zhou Date: Thu, 27 Aug 2020 01:19:15 +0800 Subject: [PATCH] feat(x509.csr) finish {set,add}_extension functions --- README.md | 56 +++++- lib/resty/openssl/include/ossl_typ.lua | 2 + lib/resty/openssl/include/stack.lua | 4 + lib/resty/openssl/include/x509/csr.lua | 7 + lib/resty/openssl/include/x509/init.lua | 3 - lib/resty/openssl/x509/crl.lua | 8 - lib/resty/openssl/x509/csr.lua | 243 ++++++++++++++++++------ lib/resty/openssl/x509/init.lua | 19 -- scripts/templates/x509_functions.j2 | 14 +- scripts/type_x509_req.py | 33 +++- t/fixtures/ext.csr | 19 -- t/fixtures/test.csr | 34 ++-- t/openssl/x509/csr.t | 173 +++++++++++++++-- 13 files changed, 455 insertions(+), 160 deletions(-) delete mode 100644 t/fixtures/ext.csr diff --git a/README.md b/README.md index e0d695ad..0be9b595 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,10 @@ Table of Contents + [csr.istype](#csristype) + [csr:get_*, csr:set_*](#csrget_-csrset_) + [csr:get_extension](#csrget_extension) - + [csr:get_extensions](#csrget_extensions) + + [csr:add_extension](#csradd_extension) + + [csr:set_extension](#csrset_extension) + + [csr:get_extension_critical](#csrget_extension_critical) + + [csr:set_extension_critical](#csrset_extension_critical) + [csr:sign](#csrsign) + [csr:verify](#csrverify) + [csr:tostring](#csrtostring) @@ -1712,6 +1715,9 @@ Additionally, getters and setters for extensions are also available: | ------------ | ---- | ----------- | | subject_alt_name | [x509.altname](#restyopensslx509altname) | [Subject Alternative Name](https://tools.ietf.org/html/rfc5280#section-4.2.1.6) of the certificate request, SANs are usually used to define "additional Common Names" | +For all extensions, `get_{extension}_critical` and `set_{extension}_critical` is also supported to +access the `critical` flag of the extension. + If the attribute is not found, getter will return `nil, nil`. ```lua @@ -1722,7 +1728,14 @@ ngx.say(version) -- outputs 3 ``` -Setters and getters for x509 attributes share the same syntax. +Note that user may also access the certain extension by [csr:get_extension](#csrget_extension) and +[csr:set_extension](#csrset_extension), while the later two function returns or requires +[extension](#restyopensslx509extension) instead. User may use getter and setters listed here if modification +of current extensions is needed; use [csr:get_extension](#csrget_extension) or +[csr:set_extension](#csrset_extension) if user are adding or replacing the whole extension or +getters/setters are not implemented. If the getter returned a type of `x509.*` instance, it can be +converted to a [extension](#restyopensslx509extension) instance by [extension:from_data](#extensionfrom_data), +and thus used by [csr:get_extension](#csrget_extension) and [csr:set_extension](#csrset_extension) [Back to TOC](#table-of-contents) @@ -1757,6 +1770,45 @@ Return all extensions as a [resty.openssl.x509.extensions](#restyopensslx509exte [Back to TOC](#table-of-contents) +### csr:add_extension + +**syntax**: *ok, err = csr:add_extension(extension)* + +Adds an X.509 `extension` to csr, the first argument must be a +[resty.openssl.x509.extension](#restyopensslx509extension) instance. + +[Back to TOC](#table-of-contents) + +### csr:set_extension + +**syntax**: *ok, err = csr:set_extension(extension)* + +Adds an X.509 `extension` to csr, the first argument must be a +[resty.openssl.x509.extension](#restyopensslx509extension) instance. +The difference from [csr:add_extension](#csradd_extension) is that +in this function if a `extension` with same type already exists, +the old extension will be replaced. + +Note this function is not thread-safe. + +[Back to TOC](#table-of-contents) + +### csr:get_extension_critical + +**syntax**: *ok, err = csr:get_extension_critical(nid_or_txt)* + +Get critical flag of the X.509 `extension` matching the given [NID] from csr. + +[Back to TOC](#table-of-contents) + +### csr:set_extension_critical + +**syntax**: *ok, err = csr:set_extension_critical(nid_or_txt, crit?)* + +Set critical flag of the X.509 `extension` matching the given [NID] to csr. + +[Back to TOC](#table-of-contents) + ### csr:sign **syntax**: *ok, err = csr:sign(pkey, digest?)* diff --git a/lib/resty/openssl/include/ossl_typ.lua b/lib/resty/openssl/include/ossl_typ.lua index 764c8af0..6474ebc6 100644 --- a/lib/resty/openssl/include/ossl_typ.lua +++ b/lib/resty/openssl/include/ossl_typ.lua @@ -16,6 +16,8 @@ ffi.cdef( typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX; typedef struct engine_st ENGINE; typedef struct x509_st X509; + typedef struct x509_attributes_st X509_ATTRIBUTE; + typedef struct X509_extension_st X509_EXTENSION; typedef struct X509_name_st X509_NAME; typedef struct X509_name_entry_st X509_NAME_ENTRY; typedef struct X509_req_st X509_REQ; diff --git a/lib/resty/openssl/include/stack.lua b/lib/resty/openssl/include/stack.lua index 987e4ffe..90df99cf 100644 --- a/lib/resty/openssl/include/stack.lua +++ b/lib/resty/openssl/include/stack.lua @@ -29,6 +29,7 @@ if OPENSSL_11_OR_LATER then void *OPENSSL_sk_value(const OPENSSL_STACK *, int); OPENSSL_STACK *OPENSSL_sk_dup(const OPENSSL_STACK *st); void OPENSSL_sk_free(OPENSSL_STACK *); + void *OPENSSL_sk_delete(OPENSSL_STACK *st, int loc); typedef void (*OPENSSL_sk_freefunc)(void *); typedef void *(*OPENSSL_sk_copyfunc)(const void *); @@ -44,6 +45,7 @@ if OPENSSL_11_OR_LATER then _M.OPENSSL_sk_num = C.OPENSSL_sk_num _M.OPENSSL_sk_value = C.OPENSSL_sk_value _M.OPENSSL_sk_dup = C.OPENSSL_sk_dup + _M.OPENSSL_sk_delete = C.OPENSSL_sk_delete _M.OPENSSL_sk_free = C.OPENSSL_sk_free _M.OPENSSL_sk_deep_copy = C.OPENSSL_sk_deep_copy elseif OPENSSL_10 then @@ -59,6 +61,7 @@ elseif OPENSSL_10 then void *sk_value(const _STACK *, int); _STACK *sk_dup(_STACK *st); void sk_free(_STACK *st); + void *sk_delete(_STACK *st, int loc); _STACK *sk_deep_copy(_STACK *, void *(*)(void *), void (*)(void *)); ]] @@ -69,6 +72,7 @@ elseif OPENSSL_10 then _M.OPENSSL_sk_pop_free = C.sk_pop_free _M.OPENSSL_sk_num = C.sk_num _M.OPENSSL_sk_value = C.sk_value + _M.OPENSSL_sk_delete = C.sk_delete _M.OPENSSL_sk_dup = C.sk_dup _M.OPENSSL_sk_free = C.sk_free _M.OPENSSL_sk_deep_copy = C.sk_deep_copy diff --git a/lib/resty/openssl/include/x509/csr.lua b/lib/resty/openssl/include/x509/csr.lua index c4b512e8..1739f64d 100644 --- a/lib/resty/openssl/include/x509/csr.lua +++ b/lib/resty/openssl/include/x509/csr.lua @@ -27,6 +27,13 @@ ffi.cdef [[ X509_EXTENSION *X509_CRL_get_ext(const X509_CRL *x, int loc); int X509_CRL_get_ext_by_NID(const X509_CRL *x, int nid, int lastpos); + int i2d_re_X509_REQ_tbs(X509_REQ *req, unsigned char **pp); + void X509_ATTRIBUTE_free(X509_ATTRIBUTE *a); + int X509_REQ_get_attr_by_NID(const X509_REQ *req, int nid, int lastpos); + X509_ATTRIBUTE *X509_REQ_delete_attr(X509_REQ *req, int loc); + + int *X509_REQ_get_extension_nids(void); + int X509_REQ_sign(X509_REQ *x, EVP_PKEY *pkey, const EVP_MD *md); int X509_REQ_verify(X509_REQ *a, EVP_PKEY *r); diff --git a/lib/resty/openssl/include/x509/init.lua b/lib/resty/openssl/include/x509/init.lua index c3457479..342f4d71 100644 --- a/lib/resty/openssl/include/x509/init.lua +++ b/lib/resty/openssl/include/x509/init.lua @@ -15,12 +15,9 @@ ffi.cdef [[ int i2d_X509_bio(BIO *bp, X509 *x509); X509 *d2i_X509_bio(BIO *bp, X509 **x509); - // STACK_OF(X509) OPENSSL_STACK *X509_chain_up_ref(OPENSSL_STACK *chain); - typedef struct X509_extension_st X509_EXTENSION; - int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md); int X509_verify(X509 *a, EVP_PKEY *r); diff --git a/lib/resty/openssl/x509/crl.lua b/lib/resty/openssl/x509/crl.lua index a737b46c..03a7c5bf 100644 --- a/lib/resty/openssl/x509/crl.lua +++ b/lib/resty/openssl/x509/crl.lua @@ -346,8 +346,6 @@ function _M:set_issuer_name(toset) 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 @@ -374,8 +372,6 @@ function _M:set_last_update(toset) 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 @@ -402,8 +398,6 @@ function _M:set_next_update(toset) 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 @@ -431,8 +425,6 @@ function _M:set_version(toset) if accessors.set_version(self.ctx, toset) == 0 then return false, format_error("x509.crl:set_version") end - - return true end diff --git a/lib/resty/openssl/x509/csr.lua b/lib/resty/openssl/x509/csr.lua index 7efcfd61..ab6f24b5 100644 --- a/lib/resty/openssl/x509/csr.lua +++ b/lib/resty/openssl/x509/csr.lua @@ -1,19 +1,19 @@ 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 altname_lib = require "resty.openssl.x509.altname" local digest_lib = require("resty.openssl.digest") local extension_lib = require("resty.openssl.x509.extension") local extensions_lib = require("resty.openssl.x509.extensions") local util = require "resty.openssl.util" +local ctypes = require "resty.openssl.aux.ctypes" local txtnid2nid = require("resty.openssl.objects").txtnid2nid local format_error = require("resty.openssl.err").format_error local OPENSSL_10 = require("resty.openssl.version").OPENSSL_10 @@ -59,6 +59,9 @@ 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) local ctx if not csr then @@ -114,45 +117,6 @@ function _M.istype(l) return l and l and l.ctx and ffi.istype(x509_req_ptr_ct, l.ctx) end - -local X509_EXTENSION_stack_gc = stack_lib.gc_of("X509_EXTENSION") -local stack_ptr_type = ffi.typeof("struct stack_st *[1]") - --- https://github.com/wahern/luaossl/blob/master/src/openssl.c -local function xr_modifyRequestedExtension(csr, target_nid, value, crit, flags) - local has_attrs = C.X509_REQ_get_attr_count(csr) - if has_attrs > 0 then - return false, "x509.csr:xr_modifyRequestedExtension: X509_REQ already has more than more attributes" .. - "modifying is currently not supported" - end - - local sk = stack_ptr_type() - sk[0] = C.X509_REQ_get_extensions(csr) - - local code - code = C.X509V3_add1_i2d(sk, target_nid, value, crit, flags) - local err - if code ~= 1 then - err = "x509.csr:xr_modifyRequestedExtension: X509V3_add1_i2d() failed: " .. code - end - code = C.X509_REQ_add_extensions(csr, sk[0]) - if code ~= 1 then - err = "x509.csr:xr_modifyRequestedExtension: X509_REQ_add_extensions() failed: " .. code - end - - X509_EXTENSION_stack_gc(sk[0]) - return true, err -end - -function _M:set_subject_alt_name(alt) - if not altname_lib.istype(alt) then - return false, "x509.csr:set_subject_alt_name: expect a x509.altname instance at #1" - end - -- #define NID_subject_alt_name 85 - -- #define X509V3_ADD_REPLACE 2L - return xr_modifyRequestedExtension(self.ctx, 85, alt.ctx, 0, 2) -end - -- backward compatibility _M.set_subject_alt = _M.set_subject_alt_name @@ -164,12 +128,10 @@ function _M:to_PEM() return tostring(self, "PEM") end -local x509_extensions_gc = stack_lib.gc_of("X509_EXTENSION") - --- Get all csr extensions -- @tparam table self Instance of csr -- @treturn Extensions object -function _M.get_extensions(self) +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) @@ -177,18 +139,13 @@ function _M.get_extensions(self) return extensions_lib.dup(extensions) 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(self, nid, last_pos) - local i, err = txtnid2nid(nid) +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(self.ctx) + 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 @@ -196,20 +153,137 @@ function _M.get_extension(self, nid, last_pos) -- make 1-index array to 0-index last_pos = (last_pos or 0) -1 - local ext_idx = C.X509v3_get_ext_by_NID(extensions, i, last_pos) + local ext_idx = C.X509v3_get_ext_by_NID(extensions, nid, last_pos) if ext_idx == -1 then - err = ("x509.csr.get_extension: X509v3_get_ext_by_NID extension for %d not found"):format(nid) - return nil, nil, format_error(err) + 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("csr.get_extension: X509v3_get_ext") + return nil, nil, format_error("X509v3_get_ext") end - return extension_lib.dup(ctx), ext_idx+1, nil + 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 + -- extensions_ptr being nil is fine: it may just because there's no extension yet + + 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 @@ -273,8 +347,6 @@ function _M:set_subject_name(toset) 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 @@ -298,8 +370,6 @@ function _M:set_pubkey(toset) if accessors.set_pubkey(self.ctx, toset) == 0 then return false, format_error("x509.csr:set_pubkey") end - - return true end -- AUTO GENERATED @@ -327,8 +397,57 @@ function _M:set_version(toset) if accessors.set_version(self.ctx, toset) == 0 then return false, format_error("x509.csr:set_version") end +end - return true +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 diff --git a/lib/resty/openssl/x509/init.lua b/lib/resty/openssl/x509/init.lua index ed513345..e6a01e57 100644 --- a/lib/resty/openssl/x509/init.lua +++ b/lib/resty/openssl/x509/init.lua @@ -515,8 +515,6 @@ function _M:set_serial_number(toset) if accessors.set_serial_number(self.ctx, toset) == 0 then return false, format_error("x509:set_serial_number") end - - return true end -- AUTO GENERATED @@ -543,8 +541,6 @@ function _M:set_not_before(toset) if accessors.set_not_before(self.ctx, toset) == 0 then return false, format_error("x509:set_not_before") end - - return true end -- AUTO GENERATED @@ -571,8 +567,6 @@ function _M:set_not_after(toset) if accessors.set_not_after(self.ctx, toset) == 0 then return false, format_error("x509:set_not_after") end - - return true end -- AUTO GENERATED @@ -596,8 +590,6 @@ function _M:set_pubkey(toset) if accessors.set_pubkey(self.ctx, toset) == 0 then return false, format_error("x509:set_pubkey") end - - return true end -- AUTO GENERATED @@ -621,8 +613,6 @@ function _M:set_subject_name(toset) if accessors.set_subject_name(self.ctx, toset) == 0 then return false, format_error("x509:set_subject_name") end - - return true end -- AUTO GENERATED @@ -646,8 +636,6 @@ function _M:set_issuer_name(toset) if accessors.set_issuer_name(self.ctx, toset) == 0 then return false, format_error("x509:set_issuer_name") end - - return true end -- AUTO GENERATED @@ -675,8 +663,6 @@ function _M:set_version(toset) 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") @@ -720,7 +706,6 @@ function _M:set_subject_alt_name(toset) 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 @@ -775,7 +760,6 @@ function _M:set_issuer_alt_name(toset) 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 @@ -867,7 +851,6 @@ function _M:set_basic_constraints(toset) 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 @@ -922,7 +905,6 @@ function _M:set_info_access(toset) 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 @@ -977,7 +959,6 @@ function _M:set_crl_distribution_points(toset) 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 diff --git a/scripts/templates/x509_functions.j2 b/scripts/templates/x509_functions.j2 index 351cd16b..8c147453 100644 --- a/scripts/templates/x509_functions.j2 +++ b/scripts/templates/x509_functions.j2 @@ -165,9 +165,16 @@ function _M:get_{{ f.field }}({%- if f.extension and f.type == "table" -%}name{% end {%- else %} local crit = ctypes.ptr_of_int() + {%- if modname == "x509.csr" %} + 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_{{ f.field }}, crit, nil) + {%- else %} -- 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_{{ f.field }}, crit, nil) + {%- endif %} crit = tonumber(crit[0]) if crit == -1 then -- not found return nil @@ -221,13 +228,16 @@ function _M:set_{{ f.field }}(toset) return false, format_error("{{ modname }}:set_{{ f.field }}") end {%- else %} + {%- if modname == "x509.csr" %} + return replace_extension(self.ctx, NID_{{ f.field }}, toset) + {%- else %} -- x509v3.h: # define X509V3_ADD_REPLACE 2L if C.X509_add1_ext_i2d(self.ctx, NID_{{ f.field }}, toset, 0, 0x2) ~= 1 then return false, format_error("{{ modname }}:set_{{ f.field }}") end - {%- endif %} - return true + {%- endif %} + {%- endif %} end {%- if f.extension %} diff --git a/scripts/type_x509_req.py b/scripts/type_x509_req.py index 3910348f..91694cf3 100644 --- a/scripts/type_x509_req.py +++ b/scripts/type_x509_req.py @@ -10,20 +10,20 @@ "field": "subject_name", "type": "x509.name", "dup": True, - "sample_printable": "C=US/CN=test.mars/L=San Francisco/O=Mars Co./OU=Terraforming Department/ST=California", + "sample_printable": "C=US/CN=example.com/L=Los Angeles/O=SSL Support/OU=SSL Support/ST=California", }, { "field": "pubkey", "type": "pkey", "sample_printable": '''-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy00jcwmJKMr9+/RwQd0G -CAvw685rqAJD1uKuBotlHTeN95xHI+xZCsRpFblAdhtaDb5Xlc33Fn+cjJ8nF2WS -1HslaZVM51m3sePsDlufBuVCKh+7KqQk64pNejYBasSR+jG7WWEjivR8yclQouJZ -T7WaF6SEET2dxiqXWAWmSbT4J3heP4b3xAAEqsa9kZJX9vQNOB5mqOyvrtqlULNm -WJkKjf8gOtuZePfopEHuyUK2YGVBHCciIfHKeBsIPc2EMajEZYSl0N5/1i4zFxdU -XlLP/qQqafrc2noEz6lv5LXbK2v4T05/5L+klJzcVnCnWnVsaYAUkaEJ5kNyIHpU -AQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPOIBIoblSLFv/ifj8GD +CNL5NhDX2JVUQKcWC19KtWYQg1HPnaGIy+Dj9tYSBw8T8xc9hbJ1TYGbBIMKfBUz +KoTt5yLdVIM/HJm3m9ImvAbK7TYcx1U9TJEMxN6686whAUMBr4B7ql4VTXqu6TgD +cdbcQ5wsPVOiFHJTTwgVwt7eVCBMFAkZn+qQz+WigM5HEp8KFrzwAK142H2ucuyf +gGS4+XQSsUdwNWh9GPRZgRt3R2h5ymYkQB/cbg596alCquoizI6QCfwQx3or9Dg1 +f3rlwf8H5HIVH3hATGIr7GpbKka/JH2PYNGfi5KqsJssVQfu84m+5WXDB+90KHJE +cwIDAQAB -----END PUBLIC KEY----- ''', }, @@ -43,5 +43,22 @@ got = tonumber(got) + 1 ''', }, + +################## extensions ###################### + +{ + "field": "subject_alt_name", + "type": "x509.altname", + "dup": True, + "extension": "subjectAltName", + "sample_printable": 'DNS=example.com', + "get_converter": ''' + -- 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)''', +}, ] } \ No newline at end of file diff --git a/t/fixtures/ext.csr b/t/fixtures/ext.csr deleted file mode 100644 index 0197920b..00000000 --- a/t/fixtures/ext.csr +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIDFTCCAf0CAQAwejELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx -FDASBgNVBAcTC0xvcyBBbmdlbGVzMRQwEgYDVQQKEwtTU0wgU3VwcG9ydDEUMBIG -A1UECxMLU1NMIFN1cHBvcnQxFDASBgNVBAMTC2V4YW1wbGUuY29tMIIBIjANBgkq -hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPOIBIoblSLFv/ifj8GDCNL5NhDX2JVU -QKcWC19KtWYQg1HPnaGIy+Dj9tYSBw8T8xc9hbJ1TYGbBIMKfBUzKoTt5yLdVIM/ -HJm3m9ImvAbK7TYcx1U9TJEMxN6686whAUMBr4B7ql4VTXqu6TgDcdbcQ5wsPVOi -FHJTTwgVwt7eVCBMFAkZn+qQz+WigM5HEp8KFrzwAK142H2ucuyfgGS4+XQSsUdw -NWh9GPRZgRt3R2h5ymYkQB/cbg596alCquoizI6QCfwQx3or9Dg1f3rlwf8H5HIV -H3hATGIr7GpbKka/JH2PYNGfi5KqsJssVQfu84m+5WXDB+90KHJEcwIDAQABoFYw -VAYJKoZIhvcNAQkOMUcwRTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUE -DDAKBggrBgEFBQcDATAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0B -AQUFAAOCAQEAgBSVMeTB9pfgZCllMPBFffeduMePyDA1SzLYjSFkh660sFFiwGAV -MTnnYFHH3k6ueRVal3gzxZJ6ehr+ms1/CRO8rlY+B6geMCbGCbCvcAET0n505aYH -v8vlvqrdSx8Ur/9sisbynCkdk2qgc3rbnDbsAAonZIXf+blacaYTZdGUxso6qtY6 -6mhI+ulqmkDk3Quc02ityvuGEbN8UuUGxc+kg0aIqMWWNKUGpTq/aRWpC7kuCUFZ -fmvPwnMhzgKBPzOXwyauVxAV0Mm/1uwPu9GNVQDgewy4Rjbm5bNwIjce3W1tVMWT -FR+x0BtV+D2A62fJWB2Yv9oERJbZQnvLqw== ------END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/t/fixtures/test.csr b/t/fixtures/test.csr index 7ed0ae9a..0197920b 100644 --- a/t/fixtures/test.csr +++ b/t/fixtures/test.csr @@ -1,17 +1,19 @@ -----BEGIN CERTIFICATE REQUEST----- -MIICyTCCAbECAQAwgYMxCzAJBgNVBAYTAlVTMRIwEAYDVQQDDAl0ZXN0Lm1hcnMx -FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xETAPBgNVBAoMCE1hcnMgQ28uMRMwEQYD -VQQIDApDYWxpZm9ybmlhMSAwHgYDVQQLDBdUZXJyYWZvcm1pbmcgRGVwYXJ0bWVu -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMtNI3MJiSjK/fv0cEHd -BggL8OvOa6gCQ9birgaLZR03jfecRyPsWQrEaRW5QHYbWg2+V5XN9xZ/nIyfJxdl -ktR7JWmVTOdZt7Hj7A5bnwblQiofuyqkJOuKTXo2AWrEkfoxu1lhI4r0fMnJUKLi -WU+1mhekhBE9ncYql1gFpkm0+Cd4Xj+G98QABKrGvZGSV/b0DTgeZqjsr67apVCz -ZliZCo3/IDrbmXj36KRB7slCtmBlQRwnIiHxyngbCD3NhDGoxGWEpdDef9YuMxcX -VF5Sz/6kKmn63Np6BM+pb+S12ytr+E9Of+S/pJSc3FZwp1p1bGmAFJGhCeZDciB6 -VAECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQAcbLm6XFG+ihQJcydQXCZcEkuy -lKDTdYn0j8NzPsUf8Nmz4GWepWHyUn0sxmpV9dIWTxLDRWWrVb3nlkb9/sIItg0W -3uGvQnOzyLkd76Ouk+/NxX+qracY/bpAaBAN58Mw8nb57tpIbAB+/kyFaQr6hdqU -sNI/wHiZUXAoZm12Rt/C4BRSbrZhC/+9ia0t5XS3v1d9xgJnSqdmr6OCenRQU9J8 -UO5p8QDUld9hlHLCSOVTSXQlzULTsP+VywduXffZEEdeGCLRLArkBbCq7HHH67TB -E71V2+Y3fHpLaLwzz3qblXSAST8xMwnBpGN3vr6vn5Tt1LlMvz+oWqJji5ie ------END CERTIFICATE REQUEST----- +MIIDFTCCAf0CAQAwejELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWEx +FDASBgNVBAcTC0xvcyBBbmdlbGVzMRQwEgYDVQQKEwtTU0wgU3VwcG9ydDEUMBIG +A1UECxMLU1NMIFN1cHBvcnQxFDASBgNVBAMTC2V4YW1wbGUuY29tMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPOIBIoblSLFv/ifj8GDCNL5NhDX2JVU +QKcWC19KtWYQg1HPnaGIy+Dj9tYSBw8T8xc9hbJ1TYGbBIMKfBUzKoTt5yLdVIM/ +HJm3m9ImvAbK7TYcx1U9TJEMxN6686whAUMBr4B7ql4VTXqu6TgDcdbcQ5wsPVOi +FHJTTwgVwt7eVCBMFAkZn+qQz+WigM5HEp8KFrzwAK142H2ucuyfgGS4+XQSsUdw +NWh9GPRZgRt3R2h5ymYkQB/cbg596alCquoizI6QCfwQx3or9Dg1f3rlwf8H5HIV +H3hATGIr7GpbKka/JH2PYNGfi5KqsJssVQfu84m+5WXDB+90KHJEcwIDAQABoFYw +VAYJKoZIhvcNAQkOMUcwRTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUE +DDAKBggrBgEFBQcDATAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0B +AQUFAAOCAQEAgBSVMeTB9pfgZCllMPBFffeduMePyDA1SzLYjSFkh660sFFiwGAV +MTnnYFHH3k6ueRVal3gzxZJ6ehr+ms1/CRO8rlY+B6geMCbGCbCvcAET0n505aYH +v8vlvqrdSx8Ur/9sisbynCkdk2qgc3rbnDbsAAonZIXf+blacaYTZdGUxso6qtY6 +6mhI+ulqmkDk3Quc02ityvuGEbN8UuUGxc+kg0aIqMWWNKUGpTq/aRWpC7kuCUFZ +fmvPwnMhzgKBPzOXwyauVxAV0Mm/1uwPu9GNVQDgewy4Rjbm5bNwIjce3W1tVMWT +FR+x0BtV+D2A62fJWB2Yv9oERJbZQnvLqw== +-----END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/t/openssl/x509/csr.t b/t/openssl/x509/csr.t index 3b40df80..6ee6e7c7 100644 --- a/t/openssl/x509/csr.t +++ b/t/openssl/x509/csr.t @@ -149,7 +149,7 @@ x509.csr:sign: expect a pkey instance at #1 --- config location =/t { content_by_lua_block { - local f = io.open("t/fixtures/ext.csr"):read("*a") + local f = io.open("t/fixtures/test.csr"):read("*a") local c = myassert(require("resty.openssl.x509.csr").new(f)) local exts = c:get_extensions() if #exts == 0 then @@ -172,7 +172,7 @@ x509.csr:sign: expect a pkey instance at #1 --- config location =/t { content_by_lua_block { - local f = io.open("t/fixtures/ext.csr"):read("*a") + local f = io.open("t/fixtures/test.csr"):read("*a") local c = myassert(require("resty.openssl.x509.csr").new(f)) local ext, pos = c:get_extension(83) if not ext then @@ -203,7 +203,7 @@ nil --- config location =/t { content_by_lua_block { - local f = io.open("t/fixtures/ext.csr"):read("*a") + local f = io.open("t/fixtures/test.csr"):read("*a") local c = myassert(require("resty.openssl.x509.csr").new(f)) local ext = c:get_extension('basicConstraints') if not ext then @@ -220,12 +220,12 @@ nil --- no_error_log [error] -=== TEST 9: x509.csr:get_extension shud return nil if wrong nid name is given +=== TEST 9: x509.csr:get_extension should return nil if wrong nid name is given --- http_config eval: $::HttpConfig --- config location =/t { content_by_lua_block { - local f = io.open("t/fixtures/ext.csr"):read("*a") + local f = io.open("t/fixtures/test.csr"):read("*a") local c = myassert(require("resty.openssl.x509.csr").new(f)) local ext, err = c:get_extension('test') if not ext then @@ -242,13 +242,74 @@ nil --- no_error_log [error] +=== TEST 10: Adds extension +--- http_config eval: $::HttpConfig +--- config + location =/t { + content_by_lua_block { + local f = io.open("t/fixtures/test.csr"):read("*a") + local c = myassert(require("resty.openssl.x509.csr").new(f)) + local altname = require("resty.openssl.x509.altname").new() + altname:add("DNS", "test.com") + altname:add("DNS", "test2.com") + local extension = require("resty.openssl.x509.extension") + local ext = myassert(extension.from_data(altname, 85, false)) + + local ok = myassert(c:add_extension(ext)) + + local ext, _ = c:get_extension("subjectAltName") + + ngx.update_time() + local fname = "ci_" .. math.floor(ngx.now() * 1000) + local f = io.open(fname, "wb") + f:write(c:tostring()) + f:close() + ngx.say(io.popen("openssl req -in " .. fname .. " -noout -text", 'r'):read("*a")) + os.remove(fname) + } + } +--- request + GET /t +--- response_body_like eval +"DNS:example.com.+DNS:test.com, DNS:test2.com +" +--- no_error_log +[error] -=== TEST 10: x509.csr:sign should succeed +=== TEST 11: Set extension --- http_config eval: $::HttpConfig --- config location =/t { content_by_lua_block { - local f = io.open("t/fixtures/ext.csr"):read("*a") + local f = io.open("t/fixtures/test.csr"):read("*a") + local c = myassert(require("resty.openssl.x509.csr").new(f)) + local altname = require("resty.openssl.x509.altname").new() + altname:add("DNS", "test.com") + altname:add("DNS", "test2.com") + local extension = require("resty.openssl.x509.extension") + local ext = myassert(extension.from_data(altname, 85, false)) + + local ok = myassert(c:set_extension(ext)) + + local ext, _ = c:get_extension("subjectAltName") + + ngx.say(tostring(ext)) + } + } +--- request + GET /t +--- response_body eval +"DNS:test.com, DNS:test2.com +" +--- no_error_log +[error] + +=== TEST 12: x509.csr:sign should succeed +--- http_config eval: $::HttpConfig +--- config + location =/t { + content_by_lua_block { + local f = io.open("t/fixtures/test.csr"):read("*a") local c = myassert(require("resty.openssl.x509.csr").new(f)) local d = myassert(require("resty.openssl.digest").new("SHA256")) local p = myassert(require("resty.openssl.pkey").new()) @@ -270,7 +331,7 @@ nil # START AUTO GENERATED CODE -=== TEST 11: x509.csr:get_subject_name (AUTOGEN) +=== TEST 13: x509.csr:get_subject_name (AUTOGEN) --- http_config eval: $::HttpConfig --- config location =/t { @@ -286,11 +347,11 @@ nil --- request GET /t --- response_body eval -"C=US/CN=test.mars/L=San Francisco/O=Mars Co./OU=Terraforming Department/ST=California" +"C=US/CN=example.com/L=Los Angeles/O=SSL Support/OU=SSL Support/ST=California" --- no_error_log [error] -=== TEST 12: x509.csr:set_subject_name (AUTOGEN) +=== TEST 14: x509.csr:set_subject_name (AUTOGEN) --- http_config eval: $::HttpConfig --- config location =/t { @@ -318,7 +379,7 @@ nil --- no_error_log [error] -=== TEST 13: x509.csr:get_pubkey (AUTOGEN) +=== TEST 15: x509.csr:get_pubkey (AUTOGEN) --- http_config eval: $::HttpConfig --- config location =/t { @@ -335,19 +396,19 @@ nil GET /t --- response_body eval "-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy00jcwmJKMr9+/RwQd0G -CAvw685rqAJD1uKuBotlHTeN95xHI+xZCsRpFblAdhtaDb5Xlc33Fn+cjJ8nF2WS -1HslaZVM51m3sePsDlufBuVCKh+7KqQk64pNejYBasSR+jG7WWEjivR8yclQouJZ -T7WaF6SEET2dxiqXWAWmSbT4J3heP4b3xAAEqsa9kZJX9vQNOB5mqOyvrtqlULNm -WJkKjf8gOtuZePfopEHuyUK2YGVBHCciIfHKeBsIPc2EMajEZYSl0N5/1i4zFxdU -XlLP/qQqafrc2noEz6lv5LXbK2v4T05/5L+klJzcVnCnWnVsaYAUkaEJ5kNyIHpU -AQIDAQAB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPOIBIoblSLFv/ifj8GD +CNL5NhDX2JVUQKcWC19KtWYQg1HPnaGIy+Dj9tYSBw8T8xc9hbJ1TYGbBIMKfBUz +KoTt5yLdVIM/HJm3m9ImvAbK7TYcx1U9TJEMxN6686whAUMBr4B7ql4VTXqu6TgD +cdbcQ5wsPVOiFHJTTwgVwt7eVCBMFAkZn+qQz+WigM5HEp8KFrzwAK142H2ucuyf +gGS4+XQSsUdwNWh9GPRZgRt3R2h5ymYkQB/cbg596alCquoizI6QCfwQx3or9Dg1 +f3rlwf8H5HIVH3hATGIr7GpbKka/JH2PYNGfi5KqsJssVQfu84m+5WXDB+90KHJE +cwIDAQAB -----END PUBLIC KEY----- " --- no_error_log [error] -=== TEST 14: x509.csr:set_pubkey (AUTOGEN) +=== TEST 16: x509.csr:set_pubkey (AUTOGEN) --- http_config eval: $::HttpConfig --- config location =/t { @@ -375,7 +436,7 @@ AQIDAQAB --- no_error_log [error] -=== TEST 15: x509.csr:get_version (AUTOGEN) +=== TEST 17: x509.csr:get_version (AUTOGEN) --- http_config eval: $::HttpConfig --- config location =/t { @@ -394,7 +455,7 @@ AQIDAQAB --- no_error_log [error] -=== TEST 16: x509.csr:set_version (AUTOGEN) +=== TEST 18: x509.csr:set_version (AUTOGEN) --- http_config eval: $::HttpConfig --- config location =/t { @@ -419,4 +480,74 @@ AQIDAQAB "ok" --- no_error_log [error] + +=== TEST 19: x509.csr:get_subject_alt_name (AUTOGEN) +--- http_config eval: $::HttpConfig +--- config + location =/t { + content_by_lua_block { + local f = io.open("t/fixtures/test.csr"):read("*a") + local c = myassert(require("resty.openssl.x509.csr").new(f)) + + local get = myassert(c:get_subject_alt_name()) + get = get:_tostring() + ngx.print(get) + } + } +--- request + GET /t +--- response_body eval +"DNS=example.com" +--- no_error_log +[error] + +=== TEST 20: x509.csr:set_subject_alt_name (AUTOGEN) +--- http_config eval: $::HttpConfig +--- config + location =/t { + content_by_lua_block { + local f = io.open("t/fixtures/test.csr"):read("*a") + local c = myassert(require("resty.openssl.x509.csr").new(f)) + local toset = require("resty.openssl.x509.altname").new():add('DNS', 'earth.galaxy') + local ok = myassert(c:set_subject_alt_name(toset)) + + local get = myassert(c:get_subject_alt_name()) + get = get:_tostring() + toset = toset:_tostring() + if get ~= toset then + ngx.say(get) + ngx.say(toset) + else + ngx.print("ok") + end + } + } +--- request + GET /t +--- response_body eval +"ok" +--- no_error_log +[error] + +=== TEST 22: x509.csr:get/set_subject_alt_name_critical (AUTOGEN) +--- http_config eval: $::HttpConfig +--- config + location =/t { + content_by_lua_block { + local f = io.open("t/fixtures/test.csr"):read("*a") + local c = myassert(require("resty.openssl.x509.csr").new(f)) + + local crit = myassert(c:get_subject_alt_name_critical()) + + local ok, err = myassert(c:set_subject_alt_name_critical(not crit)) + + ngx.say(c:get_subject_alt_name_critical() == not crit) + } + } +--- request + GET /t +--- response_body +true +--- no_error_log +[error] # END AUTO GENERATED CODE