From cd096644693a8a53389a4fcf2df3e4f39fd00407 Mon Sep 17 00:00:00 2001 From: An Tran Date: Fri, 18 Oct 2024 16:15:20 +1000 Subject: [PATCH 1/3] Migrate to lua-resty-openssl --- Dockerfile | 3 +- gateway/Roverfile.lock | 35 ++--- gateway/apicast-scm-1.rockspec | 1 + gateway/src/apicast/policy/fapi/fapi.lua | 2 +- .../apicast/policy/oauth_mtls/oauth_mtls.lua | 2 +- .../policy/tls_validation/tls_validation.lua | 10 +- .../policy/upstream_mtls/upstream_mtls.lua | 6 +- gateway/src/resty/oidc/jwk.lua | 132 +---------------- gateway/src/resty/openssl/base.lua | 87 ----------- gateway/src/resty/openssl/bio.lua | 59 -------- gateway/src/resty/openssl/evp.lua | 61 -------- gateway/src/resty/openssl/x509.lua | 103 ------------- gateway/src/resty/openssl/x509/name.lua | 103 ------------- gateway/src/resty/openssl/x509/store.lua | 90 ------------ gateway/src/resty/openssl/x509/store/ctx.lua | 56 ------- spec/fixtures/oidc/jwk/forgerock.apicast.json | 32 +++- spec/policy/apicast/apicast_spec.lua | 4 +- spec/policy/fapi/fapi_spec.lua | 4 +- spec/policy/oauth_mtls/oauth_mtls_spec.lua | 2 +- .../tls_validation/tls_validation_spec.lua | 5 +- spec/resty/openssl/bio_spec.lua | 31 ---- spec/resty/openssl/x509/store_spec.lua | 137 ------------------ spec/resty/openssl/x509_spec.lua | 66 --------- spec/ssl_helper.lua | 19 +++ 24 files changed, 98 insertions(+), 952 deletions(-) delete mode 100644 gateway/src/resty/openssl/base.lua delete mode 100644 gateway/src/resty/openssl/bio.lua delete mode 100644 gateway/src/resty/openssl/evp.lua delete mode 100644 gateway/src/resty/openssl/x509.lua delete mode 100644 gateway/src/resty/openssl/x509/name.lua delete mode 100644 gateway/src/resty/openssl/x509/store.lua delete mode 100644 gateway/src/resty/openssl/x509/store/ctx.lua delete mode 100644 spec/resty/openssl/bio_spec.lua delete mode 100644 spec/resty/openssl/x509/store_spec.lua delete mode 100644 spec/resty/openssl/x509_spec.lua create mode 100644 spec/ssl_helper.lua diff --git a/Dockerfile b/Dockerfile index 3e6a33319..9719cb222 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,6 +61,7 @@ RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/man RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/hamish/lua-resty-iputils-0.3.0-1.src.rock RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/golgote/net-url-0.9-1.src.rock RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/membphis/lua-resty-ipmatcher-0.6.1-0.src.rock +RUN luarocks install --deps-mode=none --tree /usr/local https://luarocks.org/manifests/fffonion/lua-resty-openssl-1.5.1-1.src.rock RUN yum -y remove libyaml-devel m4 openssl-devel git gcc luarocks && \ rm -rf /var/cache/yum && yum clean all -y && \ @@ -93,7 +94,7 @@ WORKDIR /opt/app-root/app USER 1001 ENV LUA_CPATH "./?.so;/usr/lib64/lua/5.1/?.so;/usr/lib64/lua/5.1/loadall.so;/usr/local/lib64/lua/5.1/?.so" -ENV LUA_PATH "/usr/lib64/lua/5.1/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/*/?.lua;" +ENV LUA_PATH "/usr/lib64/lua/5.1/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/*/?.lua;;" WORKDIR /opt/app-root ENTRYPOINT ["container-entrypoint"] diff --git a/gateway/Roverfile.lock b/gateway/Roverfile.lock index 77848e70f..f20d26b7c 100644 --- a/gateway/Roverfile.lock +++ b/gateway/Roverfile.lock @@ -1,32 +1,33 @@ argparse 0.7.1-1||production -busted 2.2.0-1||testing -date 2.2-2||production +busted 2.2.0-1|02f31a9c103a44e166617cfdb6ba1b8994a9c912|testing +date 2.2-2|8d74567cf979c1eab2c6b6ca2e3b978fa40569a2|production dkjson 2.8-1||testing fifo 0.2-0||development inspect 3.1.3-0||production jsonschema 0.8-0|c1d72d86bb3dc5b33da57d47febc47657d29ea74|testing -ldoc 1.5.0-1||development +ldoc 1.5.0-1|09f82c959c50d8c3d5a968c379b1c75de66b002d|development liquid 0.2.0-2||production lua-resty-env 0.4.0-1||production lua-resty-execvp 0.1.1-1||production -lua-resty-http 0.17.1-0||production -lua-resty-ipmatcher 0.6.1-0||production -lua-resty-iputils 0.3.0-2||production -lua-resty-jit-uuid 0.0.7-2||production -lua-resty-jwt 0.2.0-0||production +lua-resty-http 0.17.1-0|4ab4269cf442ba52507aa2c718f606054452fcad|production +lua-resty-ipmatcher 0.6.1-0|62d4c44d67227e8f3fe02331c2f8b90fe0d7ccd1|production +lua-resty-iputils 0.3.0-2|6110b41eaa52efd25e56f89e34412ab95f700d57|production +lua-resty-jit-uuid 0.0.7-2|64ae38de75c9d58f330d89e140ac872771c19223|production +lua-resty-jwt 0.2.0-0|2a62ff95eae91df6bd8655080a4b9b04c61bec6b|production +lua-resty-openssl 1.5.1-1|a900c5f5897448c181dd58073e51cdeeb3fd0029|production lua-resty-repl 0.0.6-0|3878f41b7e8f97b1c96919db19dbee9496569dda|development lua-resty-url 0.3.5-1||production lua-term 0.8-1||testing lua_cliargs 3.0-2||testing -luacov 0.15.0-1||testing -luafilesystem 1.8.0-1||production,development,testing -luassert 1.9.0-1||testing -luasystem 0.4.1-1||testing +luacov 0.15.0-1|19b52ca0298c8942df82dd441d7a4a588db4c413|testing +luafilesystem 1.8.0-1|7c6e1b013caec0602ca4796df3b1d7253a2dd258|production,development,testing +luassert 1.9.0-1|8d8dc8a54cc468048a128a867f6449a6c3fdd11a|testing +luasystem 0.4.1-1|c832d2b857d4174d17247de837426d4cfc95ec2f|testing lyaml 6.2.8-1||production -markdown 0.33-1||development +markdown 0.33-1|8c09109924b218aaecbfd4d4b1de538269c4d765|development mediator_lua 1.1.2-0||testing -net-url 1.1-1||testing -nginx-lua-prometheus 0.20181120-3||production -penlight 1.13.1-1||production,development,testing +net-url 1.1-1|32acd84d06e16ddffc975adafce9cea26f3b2dd1|testing +nginx-lua-prometheus 0.20181120-3|379c0a4d4d6f3c5b0eb93691fc7e14fff498e1ca|production +penlight 1.13.1-1|3b42fd05d8e60998b0fc5830a397b2354a81170b|production,development,testing router 2.1-0||production -say 1.4.1-3||testing \ No newline at end of file +say 1.4.1-3|45a3057e68c52b34ab59ef167efeb2340e356661|testing \ No newline at end of file diff --git a/gateway/apicast-scm-1.rockspec b/gateway/apicast-scm-1.rockspec index e6477cd75..e4c019c36 100644 --- a/gateway/apicast-scm-1.rockspec +++ b/gateway/apicast-scm-1.rockspec @@ -24,6 +24,7 @@ dependencies = { 'nginx-lua-prometheus == 0.20181120', 'lua-resty-jit-uuid', 'lua-resty-ipmatcher', + 'lua-resty-openssl' } build = { type = "make", diff --git a/gateway/src/apicast/policy/fapi/fapi.lua b/gateway/src/apicast/policy/fapi/fapi.lua index ce908f47a..fd597816f 100644 --- a/gateway/src/apicast/policy/fapi/fapi.lua +++ b/gateway/src/apicast/policy/fapi/fapi.lua @@ -69,7 +69,7 @@ function _M:access(context) if self.validate_oauth2_certificate_bound_access_token then if not context.jwt then return error(context.service or {}) end - local cert = X509.parse_pem_cert(ngx.var.ssl_client_raw_cert) + local cert = X509.new(ngx.var.ssl_client_raw_cert) if not check_certificate(cert, context.jwt.cnf) then ngx.log(ngx.WARN, 'fapi oauth_mtls failed for service ', context.service and context.service.id) diff --git a/gateway/src/apicast/policy/oauth_mtls/oauth_mtls.lua b/gateway/src/apicast/policy/oauth_mtls/oauth_mtls.lua index b6b0bb40d..82f901619 100644 --- a/gateway/src/apicast/policy/oauth_mtls/oauth_mtls.lua +++ b/gateway/src/apicast/policy/oauth_mtls/oauth_mtls.lua @@ -33,7 +33,7 @@ end function _M.access(_, context) if not context.jwt then return error(context.service or {}) end - local cert = X509.parse_pem_cert(ngx.var.ssl_client_raw_cert) + local cert = X509.new(ngx.var.ssl_client_raw_cert) if not check_certificate(cert, context.jwt.cnf) then return error(context.service or {}) diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index 07a6c619d..ce14e975f 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -13,10 +13,10 @@ local debug = ngx.config.debug local function init_trusted_store(store, certificates) for _,certificate in ipairs(certificates) do - local cert, err = X509.parse_pem_cert(certificate.pem_certificate) -- TODO: handle errors + local cert, err = X509.new(certificate.pem_certificate) -- TODO: handle errors if cert then - store:add_cert(cert) + store:add(cert) if debug then ngx.log(ngx.DEBUG, 'adding certificate to the tls validation ', tostring(cert:subject_name()), ' SHA1: ', cert:hexdigest('SHA1')) @@ -60,7 +60,7 @@ function _M:ssl_certificate() end function _M:access() - local cert = X509.parse_pem_cert(ngx.var.ssl_client_raw_cert) + local cert = X509.new(ngx.var.ssl_client_raw_cert) if not cert then ngx.status = self.error_status ngx.say("No required TLS certificate was sent") @@ -68,13 +68,15 @@ function _M:access() end local store = self.x509_store + store:set_flags(store.verify_flags.X509_V_FLAG_PARTIAL_CHAIN) -- err is printed inside validate_cert method -- so no need capture the err here - local ok, _ = store:validate_cert(cert) + local ok, err = store:verify(cert) if not ok then ngx.status = self.error_status + ngx.log(ngx.INFO, "TLS certificate validation failed, err: ", err) ngx.say("TLS certificate validation failed") return ngx.exit(ngx.status) end diff --git a/gateway/src/apicast/policy/upstream_mtls/upstream_mtls.lua b/gateway/src/apicast/policy/upstream_mtls/upstream_mtls.lua index 235ac26e5..da9bbd37c 100644 --- a/gateway/src/apicast/policy/upstream_mtls/upstream_mtls.lua +++ b/gateway/src/apicast/policy/upstream_mtls/upstream_mtls.lua @@ -58,17 +58,17 @@ local function read_ca_certificates(ca_certificates) local valid = false local store = X509_STORE.new() for _,certificate in pairs(ca_certificates) do - local cert, err = X509.parse_pem_cert(certificate) + local cert, err = X509.new(certificate) if cert then valid = true - store:add_cert(cert) + store:add(cert) else ngx.log(ngx.INFO, "cannot load certificate, err: ", err) end end if valid then - return store.store + return store.ctx end end diff --git a/gateway/src/resty/oidc/jwk.lua b/gateway/src/resty/oidc/jwk.lua index e4541e7fd..813f1c1ef 100644 --- a/gateway/src/resty/oidc/jwk.lua +++ b/gateway/src/resty/oidc/jwk.lua @@ -1,127 +1,11 @@ local ipairs = ipairs -local b64 = require('ngx.base64') -local ffi = require('ffi') local tab_new = require('resty.core.base').new_tab -local base = require('resty.openssl.base') - -ffi.cdef [[ -typedef struct bio_st BIO; -typedef struct bio_method_st BIO_METHOD; -BIO_METHOD *BIO_s_mem(void); -BIO * BIO_new(BIO_METHOD *type); -void BIO_vfree(BIO *a); -int BIO_read(BIO *b, void *data, int len); - -size_t BIO_ctrl_pending(BIO *b); - -typedef struct bignum_st BIGNUM; -typedef void FILE; - -BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); -BIGNUM *BN_new(void); -void BN_free(BIGNUM *a); - -int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d); -RSA * RSA_new(void); - -void RSA_free(RSA *rsa); - -int PEM_write_RSA_PUBKEY(FILE *fp, RSA *x); -int PEM_write_bio_RSA_PUBKEY(BIO *bp, RSA *x); -]] - -local C = ffi.C -local ffi_gc = ffi.gc -local ffi_assert = base.ffi_assert +local pkey = require ('resty.openssl.pkey') +local cjson = require ('cjson') local _M = { } -_M.jwk_to_pem = { } - -local function b64toBN(str) - local val, err = b64.decode_base64url(str) - if not val then return nil, err end - - local bn = ffi_assert(C.BN_new()) - ffi_gc(bn, C.BN_free) - - ffi_assert(C.BN_bin2bn(val, #val, bn)) - - return bn -end - -local function read_BIO(bio) - -- BIO_ctrl_pending() return the amount of pending data. - local len = C.BIO_ctrl_pending(bio) - local buf = ffi.new("char[?]", len) - ffi_assert(C.BIO_read(bio, buf, len) >= 0) - return ffi.string(buf, len) -end - -local bio_mem = C.BIO_s_mem() - -local function new_BIO() - local bio = ffi_assert(C.BIO_new(bio_mem)) - - ffi_gc(bio, C.BIO_vfree) - - return bio -end - -local function RSA_to_PEM(rsa) - local bio = new_BIO() - - ffi_assert(C.PEM_write_bio_RSA_PUBKEY(bio, rsa), 1) - - return read_BIO(bio) -end - - -local function RSA_new(n, e, d) - --- https://github.com/sfackler/rust-openssl/blob/2df87cfd5974da887b5cb84c81e249f485bed9f7/openssl/src/rsa.rs#L420-L437 - local rsa = ffi_assert(C.RSA_new()) - ffi_gc(rsa, C.RSA_free) - - --[[ - The n, e and d parameter values can be set by calling RSA_set0_key() - and passing the new values for n, e and d as parameters to the function. - The values n and e must be non-NULL the first time this function is called - on a given RSA object. The value d may be NULL. - ]]-- - - ffi_assert(C.RSA_set0_key(rsa, n, e, d), 1) - - --[[ - Calling this function transfers the memory management of the values - to the RSA object, and therefore the values that have been passed - in should not be freed by the caller after this function has been called. - ]]-- - ffi_gc(n, nil) - ffi_gc(e, nil) - - return rsa -end - -function _M.jwk_to_pem.RSA(jwk) - local n, e, err - - -- parameter n: Base64 URL encoded string representing the modulus of the RSA Key. - n, err = b64toBN(jwk.n) - if err then return nil, err end - - -- parameter e: Base64 URL encoded string representing the public exponent of the RSA Key. - e, err = b64toBN(jwk.e) - if err then return nil, err end - - local rsa = RSA_new(n, e) - - -- jwk.rsa = rsa - jwk.pem = RSA_to_PEM(rsa) - - return jwk -end - function _M.convert_keys(res, ...) if not res then return nil, ... end local keys = tab_new(0, #res.keys) @@ -134,13 +18,13 @@ function _M.convert_keys(res, ...) end function _M.convert_jwk_to_pem(jwk) - local fun = _M.jwk_to_pem[jwk.kty] - - if not fun then - return nil, 'unsupported kty' - end + local val, err = pkey.new(cjson.encode(jwk), { format = "JWK" }) + if not val then + return nil, err + end + jwk.pem = val:tostring("public", "PEM") - return fun(jwk) + return jwk end return _M diff --git a/gateway/src/resty/openssl/base.lua b/gateway/src/resty/openssl/base.lua deleted file mode 100644 index 2c2cd0d29..000000000 --- a/gateway/src/resty/openssl/base.lua +++ /dev/null @@ -1,87 +0,0 @@ -local ffi = require('ffi') - -ffi.cdef([[ - typedef long time_t; - - // https://github.com/openssl/openssl/blob/4ace4ccda2934d2628c3d63d41e79abe041621a7/include/openssl/ossl_typ.h - typedef struct x509_store_st X509_STORE; - typedef struct x509_st X509; - typedef struct X509_crl_st X509_CRL; - typedef struct X509_name_st X509_NAME; - typedef struct bio_st BIO; - typedef struct bio_method_st BIO_METHOD; - typedef struct X509_VERIFY_PARAM_st X509_VERIFY_PARAM; - typedef struct stack_st OPENSSL_STACK; - typedef struct evp_md_st { - int type; - int pkey_type; - int md_size; - } EVP_MD; - - unsigned long ERR_get_error(void); - const char *ERR_reason_error_string(unsigned long e); - - void ERR_clear_error(void); -]]) - -local C = ffi.C -local _M = { } - -local error = error - -local function openssl_error() - local code, reason - - while true do - --[[ - https://www.openssl.org/docs/man1.1.0/crypto/ERR_get_error.html - - ERR_get_error() returns the earliest error code - from the thread's error queue and removes the entry. - This function can be called repeatedly - until there are no more error codes to return. - ]]-- - code = C.ERR_get_error() - - if code == 0 then - break - else - reason = C.ERR_reason_error_string(code) - end - end - - C.ERR_clear_error() - - if reason then - return ffi.string(reason) - end -end - -local function ffi_value(ret, expected) - if ret == nil or ret == -1 or (expected and ret ~= expected) then - return nil, openssl_error() or 'expected value, got nil' - end - - return ret -end - -local function ffi_assert(ret, expected) - local value, err = ffi_value(ret, expected) - - if not value then - error(err, 2) - end - - return value -end - -local function tocdata(obj) - return obj and obj.cdata or obj -end - -_M.ffi_assert = ffi_assert -_M.ffi_value = ffi_value -_M.openssl_error = openssl_error -_M.tocdata = tocdata - -return _M diff --git a/gateway/src/resty/openssl/bio.lua b/gateway/src/resty/openssl/bio.lua deleted file mode 100644 index 5fb00b124..000000000 --- a/gateway/src/resty/openssl/bio.lua +++ /dev/null @@ -1,59 +0,0 @@ -local base = require('resty.openssl.base') -local ffi = require('ffi') - -ffi.cdef([[ - // https://www.openssl.org/docs/manmaster/man3/BIO_write.html - BIO_METHOD *BIO_s_mem(void); - BIO * BIO_new(BIO_METHOD *type); - void BIO_vfree(BIO *a); - int BIO_read(BIO *b, void *data, int len); - int BIO_write(BIO *b, const void *data, int dlen); - - size_t BIO_ctrl_pending(BIO *b); -]]) -local C = ffi.C -local ffi_assert = base.ffi_assert -local str_len = string.len -local assert = assert - -local _M = { - -} - -local mt = { - __index = _M, - __new = function(ct, bio_method) - local bio = ffi_assert(C.BIO_new(bio_method)) - - return ffi.new(ct, bio) - end, - __gc = function(self) - C.BIO_vfree(self.cdata) - end, -} - --- no changes to the metamethods possible from this point -local BIO = ffi.metatype('struct { BIO *cdata; }', mt) - -local bio_mem = C.BIO_s_mem() - -function _M:read() - local bio = self.cdata - -- BIO_ctrl_pending() return the amount of pending data. - local len = C.BIO_ctrl_pending(bio) - local buf = ffi.new("char[?]", len) - ffi_assert(C.BIO_read(bio, buf, len) >= 0) - return ffi.string(buf, len) -end - -function _M:write(str) - local len = str_len(assert(str, 'expected string')) - - return ffi_assert(C.BIO_write(self.cdata, str, len)) -end - -function _M.new() - return BIO(bio_mem) -end - -return _M diff --git a/gateway/src/resty/openssl/evp.lua b/gateway/src/resty/openssl/evp.lua deleted file mode 100644 index 2e6a656e4..000000000 --- a/gateway/src/resty/openssl/evp.lua +++ /dev/null @@ -1,61 +0,0 @@ -local ffi = require('ffi') -local base = require('resty.openssl.base') - -ffi.cdef([[ -const EVP_MD *EVP_sha1(void); -const EVP_MD *EVP_sha256(void); -const EVP_MD *EVP_sha512(void); -const EVP_MD *EVP_get_digestbyname(const char *name); -]]) - -local C = ffi.C -local assert = assert -local tocdata = base.tocdata - -local _M = { } - -local function find(name) - local md = C.EVP_get_digestbyname(name) - - if not md then - return nil, 'not found' - end - - return md -end - -local mt = { - __index = _M, - - __new = function(ct, md) - return ffi.new(ct, assert(md)) - end, - - __len = function(self) - return tocdata(self).md_size - end, -} - -local EVP_MD = ffi.metatype('struct { const EVP_MD *cdata; }', mt) - -function _M.new(name) - local md, err = find(name) - - if not md then return nil, err end - - return EVP_MD(md) -end - -function _M.sha1() - return _M.new('SHA1') -end - -function _M.sha256() - return _M.new('SHA256') -end - -function _M.sha512() - return _M.new('SHA512') -end - -return _M diff --git a/gateway/src/resty/openssl/x509.lua b/gateway/src/resty/openssl/x509.lua deleted file mode 100644 index 329edb3bb..000000000 --- a/gateway/src/resty/openssl/x509.lua +++ /dev/null @@ -1,103 +0,0 @@ -local base = require('resty.openssl.base') -local BIO = require('resty.openssl.bio') -local X509_NAME = require('resty.openssl.x509.name') -local EVP_MD = require('resty.openssl.evp') -local resty_str = require('resty.string') -local ffi = require('ffi') -local re_gsub = ngx.re.gsub - -ffi.cdef([[ -int OPENSSL_sk_num(const OPENSSL_STACK *); -void *OPENSSL_sk_value(const OPENSSL_STACK *, int); -void *OPENSSL_sk_shift(OPENSSL_STACK *st); - -X509 *PEM_read_bio_X509(BIO *bp, X509 **x, pem_password_cb *cb, void *u); -X509_NAME *X509_get_subject_name(const X509 *x); -X509_NAME *X509_get_issuer_name(const X509 *x); - -X509 *X509_new(void); -void X509_free(X509 *a); - -int X509_digest(const X509 *data, const EVP_MD *type, unsigned char *md, unsigned int *len); -]]) - -local C = ffi.C -local openssl_error = base.openssl_error -local ffi_assert = base.ffi_assert -local tocdata = base.tocdata -local assert = assert -local _M = {} -local mt = { - __index = _M, - __new = function(ct, x509) - if x509 == nil then - return nil, openssl_error() - else - return ffi.new(ct, x509) - end - end, - __gc = function(self) - C.X509_free(self.cdata) - end -} - -local X509 = ffi.metatype('struct { X509 *cdata; }', mt) - -local function parse_pem_cert(str) - local bio = BIO.new() - - assert(bio:write(str)) - - return X509(C.PEM_read_bio_X509(bio.cdata, nil, nil, nil)) -end - -local function normalize_pem_cert(str) - if not str then return end - if #(str) == 0 then return end - - -- using also jit compiler (j) will result in a segfault with some certificates - return re_gsub(str, [[\s(?!CERTIFICATE)]], '\n', 'o') -end - -function _M.parse_pem_cert(str) - local crt = normalize_pem_cert(str) - - if crt then - return parse_pem_cert(crt) - else - return nil, 'invalid certificate' - end -end - -function _M:subject_name() - -- X509_get_subject_name() returns the subject name of certificate x. - -- The returned value is an internal pointer which MUST NOT be freed. - -- https://www.openssl.org/docs/man1.1.0/crypto/X509_get_subject_name.html - return X509_NAME.new(C.X509_get_subject_name(tocdata(self))) -end - -function _M:issuer_name() - -- X509_get_issuer_name() and X509_set_issuer_name() are identical to X509_get_subject_name() - -- and X509_set_subject_name() except the get and set the issuer name of x. - -- https://www.openssl.org/docs/man1.1.0/crypto/X509_get_subject_name.html - return X509_NAME.new(C.X509_get_issuer_name(tocdata(self))) -end - -function _M:digest(name) - local evp = EVP_MD.new(name) -- TODO: this EVP_MD object can he cached or passed - local md_size = #evp - local buf = ffi.new("unsigned char[?]", md_size) - local len = ffi.new("unsigned int[1]", md_size) - - ffi_assert(C.X509_digest(tocdata(self), tocdata(evp), buf, len), 1) - - return ffi.string(buf, len[0]) -end - -function _M:hexdigest(evp_md) - local digest = self:digest(evp_md) - - return resty_str.to_hex(digest) -end - -return _M diff --git a/gateway/src/resty/openssl/x509/name.lua b/gateway/src/resty/openssl/x509/name.lua deleted file mode 100644 index 42a99e9f3..000000000 --- a/gateway/src/resty/openssl/x509/name.lua +++ /dev/null @@ -1,103 +0,0 @@ -local base = require('resty.openssl.base') -local BIO = require('resty.openssl.bio') -local ffi = require('ffi') -local bit = require('bit') - -ffi.cdef([[ -int X509_NAME_print_ex(BIO *out, const X509_NAME *nm, int indent, unsigned long flags); -char * X509_NAME_oneline(const X509_NAME *a, char *buf, int size); -int X509_NAME_print(BIO *bp, const X509_NAME *name, int obase); -]]) - -local const = { - -} - -const.XN_FLAG_SEP_MASK = bit.lshift(0xf, 16) - -const.XN_FLAG_COMPAT = 0 -const.XN_FLAG_SEP_COMMA_PLUS = bit.lshift(1, 16) -const.XN_FLAG_SEP_CPLUS_SPC = bit.lshift(2, 16) -const.XN_FLAG_SEP_SPLUS_SPC = bit.lshift(3, 16) -const.XN_FLAG_SEP_MULTILINE = bit.lshift(4, 16) -const.XN_FLAG_DN_REV = bit.lshift(1, 20) -const.XN_FLAG_FN_MASK = bit.lshift(0x3, 21) -const.XN_FLAG_FN_SN = 0 -const.XN_FLAG_FN_LN = bit.lshift(1, 21) -const.XN_FLAG_FN_OID = bit.lshift(2, 21) -const.XN_FLAG_FN_NONE = bit.lshift(3, 21) -const.XN_FLAG_SPC_EQ = bit.lshift(1, 23) -const.XN_FLAG_DUMP_UNKNOWN_FIELDS = bit.lshift(1, 24) -const.XN_FLAG_FN_ALIGN = bit.lshift(1, 25) -const.ASN1_STRFLGS_ESC_2253 = 1 -const.ASN1_STRFLGS_ESC_CTRL = 2 -const.ASN1_STRFLGS_ESC_MSB = 4 -const.ASN1_STRFLGS_ESC_QUOTE = 8 -const.CHARTYPE_PRINTABLESTRING = 0x10 -const.CHARTYPE_FIRST_ESC_2253 = 0x20 -const.CHARTYPE_LAST_ESC_2253 = 0x40 -const.ASN1_STRFLGS_UTF8_CONVERT = 0x10 -const.ASN1_STRFLGS_IGNORE_TYPE = 0x20 -const.ASN1_STRFLGS_SHOW_TYPE = 0x40 -const.ASN1_STRFLGS_DUMP_ALL = 0x80 -const.ASN1_STRFLGS_DUMP_UNKNOWN = 0x100 -const.ASN1_STRFLGS_DUMP_DER = 0x200 -const.SN1_STRFLGS_ESC_2254 = 0x400 - -const.ASN1_STRFLGS_RFC2253 = bit.bor( - const.ASN1_STRFLGS_ESC_2253, - const.ASN1_STRFLGS_ESC_CTRL, - const.ASN1_STRFLGS_ESC_MSB, - const.ASN1_STRFLGS_UTF8_CONVERT, - const.ASN1_STRFLGS_DUMP_UNKNOWN, - const.ASN1_STRFLGS_DUMP_DER -) - -const.XN_FLAG_RFC2253 = bit.bor( - const.ASN1_STRFLGS_RFC2253, - const.XN_FLAG_SEP_COMMA_PLUS, - const.XN_FLAG_DN_REV, - const.XN_FLAG_FN_SN, - const.XN_FLAG_DUMP_UNKNOWN_FIELDS -) - -const.XN_FLAG_ONELINE = bit.bor( - const.ASN1_STRFLGS_RFC2253, - const.ASN1_STRFLGS_ESC_QUOTE, - const.XN_FLAG_SEP_CPLUS_SPC, - const.XN_FLAG_SPC_EQ, - const.XN_FLAG_FN_SN -) - -const.XN_FLAG_MULTILINE = bit.bor( - const.ASN1_STRFLGS_ESC_CTRL, - const.ASN1_STRFLGS_ESC_MSB, - const.XN_FLAG_SEP_MULTILINE, - const.XN_FLAG_SPC_EQ, - const.XN_FLAG_FN_LN, - const.XN_FLAG_FN_ALIGN -) - -local C = ffi.C -local tocdata = base.tocdata -local assert = assert -local _M = {} -local mt = { - __index = _M, - __new = ffi.new, - __tostring = function(self) - local bio = BIO.new() - - C.X509_NAME_print_ex(tocdata(bio), tocdata(self), 0, const.XN_FLAG_ONELINE) - - return bio:read() - end -} - -local X509_NAME = ffi.metatype('struct { X509_NAME *cdata; }', mt) - -function _M.new(name) - return X509_NAME(assert(name)) -end - -return _M diff --git a/gateway/src/resty/openssl/x509/store.lua b/gateway/src/resty/openssl/x509/store.lua deleted file mode 100644 index 6d14eb090..000000000 --- a/gateway/src/resty/openssl/x509/store.lua +++ /dev/null @@ -1,90 +0,0 @@ -local base = require('resty.openssl.base') -local X509_STORE_CTX = require('resty.openssl.x509.store.ctx') -local ffi = require('ffi') -local ffi_gc = ffi.gc - -ffi.cdef([[ -// https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_new.html -X509_STORE *X509_STORE_new(void); -void X509_STORE_free(X509_STORE *v); -int X509_STORE_lock(X509_STORE *v); -int X509_STORE_unlock(X509_STORE *v); -int X509_STORE_up_ref(X509_STORE *v); - -// https://www.openssl.org/docs/man1.1.1/man3/X509_STORE_add_cert.html -int X509_STORE_add_cert(X509_STORE *store, X509 *x); -int X509_STORE_add_crl(X509_STORE *ctx, X509_CRL *x); -int X509_STORE_set_depth(X509_STORE *store, int depth); -int X509_STORE_set_flags(X509_STORE *ctx, unsigned long flags); -int X509_STORE_set_purpose(X509_STORE *ctx, int purpose); -int X509_STORE_set_trust(X509_STORE *ctx, int trust); - -// https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_set1_param.html -int X509_STORE_set1_param(X509_STORE *store, X509_VERIFY_PARAM *pm); -X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *ctx); - -// https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_CTX_set0_param.html -X509_VERIFY_PARAM *X509_VERIFY_PARAM_new(void); -int X509_VERIFY_PARAM_set_flags(X509_VERIFY_PARAM *param, - unsigned long flags); -void X509_VERIFY_PARAM_set_time(X509_VERIFY_PARAM *param, time_t t); -time_t X509_VERIFY_PARAM_get_time(const X509_VERIFY_PARAM *param); -void X509_VERIFY_PARAM_free(X509_VERIFY_PARAM *param); - -// https://www.openssl.org/docs/man1.1.1/man3/X509_VERIFY_PARAM_set_depth.html -]]) - -local C = ffi.C -local ffi_assert = base.ffi_assert -local tocdata = base.tocdata - -local X509_V_FLAG_PARTIAL_CHAIN = 0x80000 - -local function X509_VERIFY_PARAM(flags) - local verify_param = ffi_assert(C.X509_VERIFY_PARAM_new()) - - -- https://www.openssl.org/docs/man1.1.0/crypto/X509_VERIFY_PARAM_get_depth.html#example - ffi_assert(C.X509_VERIFY_PARAM_set_flags(verify_param, flags)) - - return ffi_gc(verify_param, C.X509_VERIFY_PARAM_free) -end - -local _M = {} -local mt = { __index = _M } - -function _M:add_cert(x509) - return ffi_assert(C.X509_STORE_add_cert(self.store, tocdata(x509))) -end - -function _M:validate_cert(x509, chain) - local ctx = X509_STORE_CTX.new(self.store, x509, chain) - - return ctx:validate() -end - -function _M:set_time(seconds) - local verify_param = ffi_assert(C.X509_STORE_get0_param(self.store)) - C.X509_VERIFY_PARAM_set_time(verify_param, seconds) -end - -function _M:time() - local verify_param = ffi_assert(C.X509_STORE_get0_param(self.store)) - return C.X509_VERIFY_PARAM_get_time(verify_param) -end - -function _M.new() - local store = ffi_assert(C.X509_STORE_new()) - ffi_gc(store, C.X509_STORE_free) - - -- enabling partial chains allows us to trust leaf certificates - local verify_param = X509_VERIFY_PARAM(X509_V_FLAG_PARTIAL_CHAIN) - - ffi_assert(C.X509_STORE_set1_param(store, verify_param),1) - - local self = setmetatable({ - store = store, - }, mt) - return self -end - -return _M diff --git a/gateway/src/resty/openssl/x509/store/ctx.lua b/gateway/src/resty/openssl/x509/store/ctx.lua deleted file mode 100644 index 1e442ee50..000000000 --- a/gateway/src/resty/openssl/x509/store/ctx.lua +++ /dev/null @@ -1,56 +0,0 @@ -local base = require('resty.openssl.base') local ffi = require('ffi') - -ffi.cdef([[ -// https://www.openssl.org/docs/man1.1.0/crypto/X509_STORE_CTX_init.html -int X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, - X509 *x509, const OPENSSL_STACK *chain); -void X509_STORE_CTX_cleanup(X509_STORE_CTX *ctx); -void X509_STORE_CTX_free(X509_STORE_CTX *ctx); -void X509_STORE_CTX_set0_param(X509_STORE_CTX *ctx, X509_VERIFY_PARAM *param); -X509_VERIFY_PARAM *X509_STORE_CTX_get0_param(X509_STORE_CTX *ctx); - -int X509_verify_cert(X509_STORE_CTX *ctx); -int X509_STORE_CTX_get_error(X509_STORE_CTX *ctx); - -const char *X509_verify_cert_error_string(long n); -]]) - -local C = ffi.C -local ffi_assert = base.ffi_assert -local tocdata = base.tocdata -local openssl_error = base.openssl_error -local setmetatable = setmetatable - -local _M = {} -local mt = { __index = _M } - -local function new_ctx() - local ctx = ffi_assert(C.X509_STORE_CTX_new()) - - return ffi.gc(ctx, C.X509_STORE_CTX_free) -end - -function _M:validate() - local ctx = new_ctx() - - ffi_assert(C.X509_STORE_CTX_init(ctx, self.store, tocdata(self.x509), self.chain), 1) - - local ret = C.X509_verify_cert(ctx) - - if ret == 1 then - return true - else - local err = ffi.string(C.X509_verify_cert_error_string(C.X509_STORE_CTX_get_error(ctx))) - ngx.log(ngx.DEBUG, 'OpenSSL cert validation err: ', openssl_error()) - return false, err - end -end - --- this could be optimized by reusing the context between validations, --- but it is way harder to make safe when there are exceptions - -function _M.new(store, x509, chain) - return setmetatable({ store = store, x509 = x509, chain = chain }, mt) -end - -return _M diff --git a/spec/fixtures/oidc/jwk/forgerock.apicast.json b/spec/fixtures/oidc/jwk/forgerock.apicast.json index dd8f9ca29..cfa2935a6 100644 --- a/spec/fixtures/oidc/jwk/forgerock.apicast.json +++ b/spec/fixtures/oidc/jwk/forgerock.apicast.json @@ -16,5 +16,35 @@ "pem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi7t6m4d/02dZ8dOe+DFc\nuUYiOWueHlNkFwdUfOs06eUETOV6Y9WCXu3D71dbF0Fhou69ez5c3HAZrSVS2qC1\nHtw9NkVlLDeED7qwQQMmSr7RFYNQ6BYekAtn/ScFHpq8Tx4BzhcDb6P0+PHCo+bk\nQedxwhbMD412KSM2UAVQaZ+TW+ngdaaVEs1Cgl4b8xxZ9ZuApXZfpddNdgvjBeeY\nQbZnaqU3b0P5YE0s0YvIQqYmTjxh4RyLfkt6s/BS1obWUOC+0ChRWlpWE7QTEVEW\nJP5yt8hgZ5MecTmBi3yZ/0ts3NsL83413NdbWYh+ChtP696mZbJozflF8jR9pewT\nbQIDAQAB\n-----END PUBLIC KEY-----\n", "alg": "RS256", "e": "AQAB" - } + }, + "Fol7IpdKeLZmzKtCEgi1LDhSIzM=": { + "alg": "ES256", + "crv": "P-256", + "kid": "Fol7IpdKeLZmzKtCEgi1LDhSIzM=", + "kty": "EC", + "pem": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEN7MtObVf92FJTwYvY2ZvTVT3rgZp\n7a7XDtzT/9Rw7IC7E2bKihw+iliHiTUUJzjXK4llWiHGWEyjcpMglVM/dw==\n-----END PUBLIC KEY-----\n", + "use": "sig", + "x": "N7MtObVf92FJTwYvY2ZvTVT3rgZp7a7XDtzT_9Rw7IA", + "y": "uxNmyoocPopYh4k1FCc41yuJZVohxlhMo3KTIJVTP3c" + }, + "I4x/IijvdDsUZMghwNq2gC/7pYQ=": { + "alg": "ES384", + "crv": "P-384", + "kid": "I4x/IijvdDsUZMghwNq2gC/7pYQ=", + "kty": "EC", + "pem": "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEk5wSvW/6JhOuCj+9PdDWdEA4oH90RSmC\n2GTliiUHAhXj6rmTdE2S+/zGmMFxufuVXfbR+tRoVcZMCoUrkKtuZUIyfCgAy8b0\nFWnPZqevwpdoTzGQBOXSNi6uItN/o4tH\n-----END PUBLIC KEY-----\n", + "use": "sig", + "x": "k5wSvW_6JhOuCj-9PdDWdEA4oH90RSmC2GTliiUHAhXj6rmTdE2S-_zGmMFxufuV", + "y": "XfbR-tRoVcZMCoUrkKtuZUIyfCgAy8b0FWnPZqevwpdoTzGQBOXSNi6uItN_o4tH" + }, + "pZSfpEq8tQPeiIe3fnnaWnnr/Zc=": { + "alg": "ES512", + "crv": "P-521", + "kid": "pZSfpEq8tQPeiIe3fnnaWnnr/Zc=", + "kty": "EC", + "pem": "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAd1Ups0MfKb4yJSHpxpqh3+S2nw9c\n3qDXMgovOVoJI5k/zYoppCx/CW0AqcyvDXb2dXH7aS3zHUnWmnUAN70e1e0BSqFE\npxi8DqUSUjdSt+q+m6FWzIQUFDhwz8T2/hYadFz+muKJNVr9QLth8K1AlP0Xw5zb\nW/+LntKncyr+/Dm55F0=\n-----END PUBLIC KEY-----\n", + "use": "sig", + "x": "AHdVKbNDHym-MiUh6caaod_ktp8PXN6g1zIKLzlaCSOZP82KKaQsfwltAKnMrw129nVx-2kt8x1J1pp1ADe9HtXt", + "y": "AUqhRKcYvA6lElI3UrfqvpuhVsyEFBQ4cM_E9v4WGnRc_priiTVa_UC7YfCtQJT9F8Oc21v_i57Sp3Mq_vw5ueRd" + } } diff --git a/spec/policy/apicast/apicast_spec.lua b/spec/policy/apicast/apicast_spec.lua index 21460f200..4f674daf9 100644 --- a/spec/policy/apicast/apicast_spec.lua +++ b/spec/policy/apicast/apicast_spec.lua @@ -43,10 +43,10 @@ describe('APIcast policy', function() local certificate_content = util.read_file(certificate_path) local key_content = util.read_file(certificate_key_path) - local ca_cert, _ = X509.parse_pem_cert(certificate_content) + local ca_cert, _ = X509.new(certificate_content) local ca_store = X509_STORE.new() - ca_store:add_cert(ca_cert) + ca_store:add(ca_cert) local cert = ssl.parse_pem_cert(certificate_content) local key = ssl.parse_pem_priv_key(key_content) diff --git a/spec/policy/fapi/fapi_spec.lua b/spec/policy/fapi/fapi_spec.lua index 144adda68..6e91f39ba 100644 --- a/spec/policy/fapi/fapi_spec.lua +++ b/spec/policy/fapi/fapi_spec.lua @@ -7,8 +7,8 @@ local clientCert = assert(fixture('CA', 'client.crt')) local header_parameter = 'x5t#S256' local function jwt_cnf() - local cnf = b64.encode_base64url(X509.parse_pem_cert(clientCert):digest('SHA256')) - return { [header_parameter] = b64.encode_base64url(X509.parse_pem_cert(clientCert):digest('SHA256')) } + local cnf = b64.encode_base64url(X509.new(clientCert):digest('SHA256')) + return { [header_parameter] = b64.encode_base64url(X509.new(clientCert):digest('SHA256')) } end describe('fapi_1_baseline_profile policy', function() diff --git a/spec/policy/oauth_mtls/oauth_mtls_spec.lua b/spec/policy/oauth_mtls/oauth_mtls_spec.lua index 6dd3028e4..691dc6b87 100644 --- a/spec/policy/oauth_mtls/oauth_mtls_spec.lua +++ b/spec/policy/oauth_mtls/oauth_mtls_spec.lua @@ -7,7 +7,7 @@ local header_parameter = 'x5t#S256' local context = {} local function jwt_cnf() - return { [header_parameter] = b64.encode_base64url(X509.parse_pem_cert(client):digest('SHA256')) } + return { [header_parameter] = b64.encode_base64url(X509.new(client):digest('SHA256')) } end describe('oauth_mtls policy', function() diff --git a/spec/policy/tls_validation/tls_validation_spec.lua b/spec/policy/tls_validation/tls_validation_spec.lua index 9dc438617..bf06d401f 100644 --- a/spec/policy/tls_validation/tls_validation_spec.lua +++ b/spec/policy/tls_validation/tls_validation_spec.lua @@ -3,6 +3,7 @@ local _M = require('apicast.policy.tls_validation') local server = assert(fixture('CA', 'server.crt')) local CA = assert(fixture('CA', 'intermediate-ca.crt')) local client = assert(fixture('CA', 'client.crt')) +local ssl_helper = require 'ssl_helper' describe('tls_validation policy', function() describe('.new', function() @@ -38,7 +39,7 @@ describe('tls_validation policy', function() it('rejects certificates that are not valid yet', function() local policy = _M.new({ whitelist = { { pem_certificate = client }}}) - policy.x509_store:set_time(os.time{ year = 2000, month = 01, day = 01 }) + ssl_helper.set_time(policy.x509_store.ctx, os.time{ year = 2000, month = 01, day = 01 }) ngx.var = { ssl_client_raw_cert = client } policy:access() @@ -49,7 +50,7 @@ describe('tls_validation policy', function() it('rejects certificates that are not longer valid', function() local policy = _M.new({ whitelist = { { pem_certificate = client }}}) - policy.x509_store:set_time(os.time{ year = 2042, month = 01, day = 01 }) + ssl_helper.set_time(policy.x509_store.ctx, os.time{ year = 2042, month = 01, day = 01 }) ngx.var = { ssl_client_raw_cert = client } policy:access() diff --git a/spec/resty/openssl/bio_spec.lua b/spec/resty/openssl/bio_spec.lua deleted file mode 100644 index 787004677..000000000 --- a/spec/resty/openssl/bio_spec.lua +++ /dev/null @@ -1,31 +0,0 @@ -local _M = require('resty.openssl.bio') - -describe('OpenSSL BIO', function() - describe('.new', function() - it('returns cdata', function() - assert.equal('cdata', type(_M.new())) - end) - end) - - describe(':write', function() - it('writes data to bio', function() - local bio = _M.new() - local str = 'foobar' - - assert(bio:write(str)) - assert.equal(str, bio:read()) - end) - - it('requires a string', function() - local bio = _M.new() - - assert.has_error(function () bio:write() end, 'expected string') - end) - - it('empty string return 0', function() - local bio = _M.new() - assert.same(bio:write(""), 0) - end) - end) - -end) diff --git a/spec/resty/openssl/x509/store_spec.lua b/spec/resty/openssl/x509/store_spec.lua deleted file mode 100644 index ccb136491..000000000 --- a/spec/resty/openssl/x509/store_spec.lua +++ /dev/null @@ -1,137 +0,0 @@ -local X509 = require('resty.openssl.x509') -local base = require('resty.openssl.base') - -local CA = X509.parse_pem_cert([[ ------BEGIN CERTIFICATE----- -MIICvDCCAaQCCQCep4rpEMmCcDANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVD -ZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTgxMjA2MTcwNTQ3WhcNMjgxMjAzMTcw -NTQ3WjAgMR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqIfkTccBdVmBqoewL2gBnkDOwk9cKBcLn -uIPge4GfO1Vm4AhDFyZOH9gUmjRH+5Dfu/G+dq7U1jOxTQnvs5U/4857PCTc/rdf -TT/HcG8k6GhBMq6/+gwtT/nOxcFmDkyAOBR2DpvwOd1soOU7lokHkDYTv+kPKrRP -Gc6x7cl3NrsAK154u1xNAGDZiEeThBmi2EanTEZOx4dqkc5pD89P5A/vwjV5LJ+v -jtL+P1FOgK57B3fVFqTL1TNOQdH9BWRZ7z3ZPfSn1PokKA4fazTOZ0iXeQVSIqju -msRk91o+CFXNPJS8NRMsp6Nk6iClyXtaxBWzAcnAxSf9u/UZ6murAgMBAAEwDQYJ -KoZIhvcNAQELBQADggEBAIZo62o53KVLWnDCBxFHwhKVgPa95o1E3RJWuRTI8kdX -L8tehLHorqOCZ1zNIDv8l2QErVUvcxwL/lpuJWZLUvhHPYUg6FDKB+vapVd1yRgR -o4fWkEQkMiKZ4bsSmM00udS5pYGiMHc3vjBcmEPzACIfcv+K29F58Lb3v2ccIXh3 -5pQvDYhqaeivRK6JIDY/+1UnaQt65DeNDAfGeAdar6DbFW+gju9avYGINRJP+BGC -Wce2mRmiNUqt37UO1+NXSLa9+4By0j5I1dMqCRFjwQBUaDgrhQf1xpVbEQ30myyy -Ci818xLwDp7CENLKIBNtg88u9Z+ha81pscKiG9WXCLI= ------END CERTIFICATE----- -]]) - -local pem = [[ ------BEGIN CERTIFICATE----- -MIICvDCCAaQCCQDyra7VGipAyzANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVD -ZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTgxMjA2MTcwOTA1WhcNMjgxMjAzMTcw -OTA1WjAgMQ8wDQYDVQQKDAZDbGllbnQxDTALBgNVBAMMBGN1cmwwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDt9H6xhm0pGqARRGMaUrSbZvetrN1mo+O4 -KuqPRr8I/YhvOEPlc/8VMxF3nyETGjQ+khO9FJGDoDD2S3yGzt1FFiNI6AOPkmux -DZMUQ2alnS7fG0zBUlxRx9otoMx/vH4gnKTfmHofuwPwkLPSWoHf0ZmPLXbm19ds -aKvllOX8vjEjtNprtUzveeDOnuov2GXqo/w+FOnDxYhys1Oidx3LOje5izV7EX4+ -+HH+7EwRV7m4+s/G97z5soo1XIZHHQKKC0DONWTOdeLkqLlAqU0nuuRkFzmbrD4u -2haxqcuyficBgbFWZznLDxJ1fMJzen7YbYea1GycTKe6Wt4xviDDAgMBAAEwDQYJ -KoZIhvcNAQELBQADggEBADY5udciqAIAFtJWVQ+AT+5RAWClGlEfi7wAfsGWUIpi -1mQjkGSqbZ4DSEECsRNiokjSyA5Phi9REg8tDCVaovMANncptUX6PJzCkpkdD5Wo -cMWzF8dZpphyZH+RwGM7aTGmdz/mnxKtVoTt++wLNv2jardRKoFvyu+FBzpTbWBe -2EYaIlGHRrIMoU9ZK3D2rGHK3GsakZT3e76/P5KuyIp1+K7IEWmD4Fk3GM6uM+Rc -Q7zGkdX+LBr85p07DHTcDxAwIT6xXh2J1fhiyart5sHkMg6YZ5JpjitIOEypnyiq -KjTINz0a+0rohUDR6BWkdU5R8Bpbw1Pg7Owx9B51KQM= ------END CERTIFICATE----- -]] - -local client = X509.parse_pem_cert([[ ------BEGIN CERTIFICATE----- -MIICvDCCAaQCCQDyra7VGipAyzANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVD -ZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTgxMjA2MTcwOTA1WhcNMjgxMjAzMTcw -OTA1WjAgMQ8wDQYDVQQKDAZDbGllbnQxDTALBgNVBAMMBGN1cmwwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDt9H6xhm0pGqARRGMaUrSbZvetrN1mo+O4 -KuqPRr8I/YhvOEPlc/8VMxF3nyETGjQ+khO9FJGDoDD2S3yGzt1FFiNI6AOPkmux -DZMUQ2alnS7fG0zBUlxRx9otoMx/vH4gnKTfmHofuwPwkLPSWoHf0ZmPLXbm19ds -aKvllOX8vjEjtNprtUzveeDOnuov2GXqo/w+FOnDxYhys1Oidx3LOje5izV7EX4+ -+HH+7EwRV7m4+s/G97z5soo1XIZHHQKKC0DONWTOdeLkqLlAqU0nuuRkFzmbrD4u -2haxqcuyficBgbFWZznLDxJ1fMJzen7YbYea1GycTKe6Wt4xviDDAgMBAAEwDQYJ -KoZIhvcNAQELBQADggEBADY5udciqAIAFtJWVQ+AT+5RAWClGlEfi7wAfsGWUIpi -1mQjkGSqbZ4DSEECsRNiokjSyA5Phi9REg8tDCVaovMANncptUX6PJzCkpkdD5Wo -cMWzF8dZpphyZH+RwGM7aTGmdz/mnxKtVoTt++wLNv2jardRKoFvyu+FBzpTbWBe -2EYaIlGHRrIMoU9ZK3D2rGHK3GsakZT3e76/P5KuyIp1+K7IEWmD4Fk3GM6uM+Rc -Q7zGkdX+LBr85p07DHTcDxAwIT6xXh2J1fhiyart5sHkMg6YZ5JpjitIOEypnyiq -KjTINz0a+0rohUDR6BWkdU5R8Bpbw1Pg7Owx9B51KQM= ------END CERTIFICATE----- -]]) - -local _M = require('resty.openssl.x509.store') - -describe('OpenSSL X509 Store', function() - describe('.new', function() - it('returns cdata', function () - assert.equals('table', type(_M.new())) - assert.equals('cdata', type(_M.new().store)) - end) - end) - - describe(':add_cert', function() - it('has add_cert method', function() - assert(_M.new().add_cert) - end) - end) - - describe(':time', function() - it('can set and get time', function() - local store = _M.new() - - assert.equal(0, store:time()) - - store:set_time(100) - - assert.equal(100, store:time()) - end) - end) - - describe(':validate_cert', function() - after_each(function() - assert.is_nil(base.openssl_error()) - end) - - it('works with X509 object', function () - local x509 = X509.parse_pem_cert(pem) - local store = _M.new() - - assert.returns_error('unable to get local issuer certificate', store:validate_cert(x509)) - end) - - it('validates missing certificate', function() - local store = _M.new() - - assert.returns_error('Invalid certificate verification context', store:validate_cert()) - end) - - it('validates self signed certificate', function() - local store = _M.new() - - assert.returns_error('self signed certificate', store:validate_cert(CA)) - - store:add_cert(CA) - assert(store:validate_cert(CA)) - end) - - it('validates certificate by itself', function() - local store = _M.new() - - assert.returns_error('unable to get local issuer certificate', store:validate_cert(client)) - - store:add_cert(client) - assert(store:validate_cert(client)) - end) - - it('validates certificate by CA', function() - local store = _M.new() - - assert.returns_error('unable to get local issuer certificate', store:validate_cert(client)) - - store:add_cert(CA) - assert(store:validate_cert(client)) - end) - end) -end) diff --git a/spec/resty/openssl/x509_spec.lua b/spec/resty/openssl/x509_spec.lua deleted file mode 100644 index a6a674137..000000000 --- a/spec/resty/openssl/x509_spec.lua +++ /dev/null @@ -1,66 +0,0 @@ -local _M = require('resty.openssl.x509') - -local pem = [[ ------BEGIN CERTIFICATE----- -MIICvDCCAaQCCQCep4rpEMmCcDANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVD -ZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTgxMjA2MTcwNTQ3WhcNMjgxMjAzMTcw -NTQ3WjAgMR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqIfkTccBdVmBqoewL2gBnkDOwk9cKBcLn -uIPge4GfO1Vm4AhDFyZOH9gUmjRH+5Dfu/G+dq7U1jOxTQnvs5U/4857PCTc/rdf -TT/HcG8k6GhBMq6/+gwtT/nOxcFmDkyAOBR2DpvwOd1soOU7lokHkDYTv+kPKrRP -Gc6x7cl3NrsAK154u1xNAGDZiEeThBmi2EanTEZOx4dqkc5pD89P5A/vwjV5LJ+v -jtL+P1FOgK57B3fVFqTL1TNOQdH9BWRZ7z3ZPfSn1PokKA4fazTOZ0iXeQVSIqju -msRk91o+CFXNPJS8NRMsp6Nk6iClyXtaxBWzAcnAxSf9u/UZ6murAgMBAAEwDQYJ -KoZIhvcNAQELBQADggEBAIZo62o53KVLWnDCBxFHwhKVgPa95o1E3RJWuRTI8kdX -L8tehLHorqOCZ1zNIDv8l2QErVUvcxwL/lpuJWZLUvhHPYUg6FDKB+vapVd1yRgR -o4fWkEQkMiKZ4bsSmM00udS5pYGiMHc3vjBcmEPzACIfcv+K29F58Lb3v2ccIXh3 -5pQvDYhqaeivRK6JIDY/+1UnaQt65DeNDAfGeAdar6DbFW+gju9avYGINRJP+BGC -Wce2mRmiNUqt37UO1+NXSLa9+4By0j5I1dMqCRFjwQBUaDgrhQf1xpVbEQ30myyy -Ci818xLwDp7CENLKIBNtg88u9Z+ha81pscKiG9WXCLI= ------END CERTIFICATE----- -]] - -local certificate = _M.parse_pem_cert(pem) - -describe('OpenSSL X509', function () - - describe('parse_pem_cert', function () - it('returns', function () - local crt = _M.parse_pem_cert(pem) - - assert(crt) - end) - - it('does not crash on invalid certificate', function() - assert.returns_error('no start line', _M.parse_pem_cert('garbage')) - assert.returns_error('bad end line', _M.parse_pem_cert('-----BEGIN CERTIFICATE-----')) - assert.returns_error('invalid certificate', _M.parse_pem_cert('')) - end) - - it('parses wrongly formatted newlines', function () - assert(_M.parse_pem_cert([[-----BEGIN CERTIFICATE----- MIICvDCCAaQCCQDyra7VGipAyzANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVD ZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTgxMjA2MTcwOTA1WhcNMjgxMjAzMTcw OTA1WjAgMQ8wDQYDVQQKDAZDbGllbnQxDTALBgNVBAMMBGN1cmwwggEiMA0GCSqG SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDt9H6xhm0pGqARRGMaUrSbZvetrN1mo+O4 KuqPRr8I/YhvOEPlc/8VMxF3nyETGjQ+khO9FJGDoDD2S3yGzt1FFiNI6AOPkmux DZMUQ2alnS7fG0zBUlxRx9otoMx/vH4gnKTfmHofuwPwkLPSWoHf0ZmPLXbm19ds aKvllOX8vjEjtNprtUzveeDOnuov2GXqo/w+FOnDxYhys1Oidx3LOje5izV7EX4+ +HH+7EwRV7m4+s/G97z5soo1XIZHHQKKC0DONWTOdeLkqLlAqU0nuuRkFzmbrD4u 2haxqcuyficBgbFWZznLDxJ1fMJzen7YbYea1GycTKe6Wt4xviDDAgMBAAEwDQYJ KoZIhvcNAQELBQADggEBADY5udciqAIAFtJWVQ+AT+5RAWClGlEfi7wAfsGWUIpi 1mQjkGSqbZ4DSEECsRNiokjSyA5Phi9REg8tDCVaovMANncptUX6PJzCkpkdD5Wo cMWzF8dZpphyZH+RwGM7aTGmdz/mnxKtVoTt++wLNv2jardRKoFvyu+FBzpTbWBe 2EYaIlGHRrIMoU9ZK3D2rGHK3GsakZT3e76/P5KuyIp1+K7IEWmD4Fk3GM6uM+Rc Q7zGkdX+LBr85p07DHTcDxAwIT6xXh2J1fhiyart5sHkMg6YZ5JpjitIOEypnyiq KjTINz0a+0rohUDR6BWkdU5R8Bpbw1Pg7Owx9B51KQM= -----END CERTIFICATE-----]])) - end) - end) - - describe(':name()', function() - it('returns subject name', function() - assert.equal('CN = Certificate Authority', tostring(certificate:subject_name())) - end) - - it('returns issuer name', function() - assert.equal('CN = Certificate Authority', tostring(certificate:issuer_name())) - end) - end) - - describe(':digest', function () - it('returns a digest', function () - assert(certificate:digest('SHA256')) - end) - end) - - describe(':hexdigest', function () - it('returns a hex formatted digest', function () - assert.equal('874fd0756c3c36c78319ca6e484e670780b86146', certificate:hexdigest('SHA1')) - end) - end) -end) diff --git a/spec/ssl_helper.lua b/spec/ssl_helper.lua new file mode 100644 index 000000000..71c73f858 --- /dev/null +++ b/spec/ssl_helper.lua @@ -0,0 +1,19 @@ +local ffi = require "ffi" +local C = ffi.C + +ffi.cdef([[ + typedef long time_t; + typedef struct X509_VERIFY_PARAM_st X509_VERIFY_PARAM; + typedef struct x509_store_st X509_STORE; + X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *ctx); + void X509_VERIFY_PARAM_set_time(X509_VERIFY_PARAM *param, time_t t); +]]) + +local SSL = {} + +function SSL.set_time(store, seconds) + local verify_param = C.X509_STORE_get0_param(store) + C.X509_VERIFY_PARAM_set_time(verify_param, seconds) +end + +return SSL From cd1d70e6352a9cf5de4e6755177b155199b91a96 Mon Sep 17 00:00:00 2001 From: An Tran Date: Thu, 24 Oct 2024 20:39:30 +1000 Subject: [PATCH 2/3] [tls_validation] Make sure the client cert is present --- .../apicast/policy/tls_validation/tls_validation.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gateway/src/apicast/policy/tls_validation/tls_validation.lua b/gateway/src/apicast/policy/tls_validation/tls_validation.lua index ce14e975f..151bd97c3 100644 --- a/gateway/src/apicast/policy/tls_validation/tls_validation.lua +++ b/gateway/src/apicast/policy/tls_validation/tls_validation.lua @@ -60,13 +60,21 @@ function _M:ssl_certificate() end function _M:access() - local cert = X509.new(ngx.var.ssl_client_raw_cert) - if not cert then + local client_cert = ngx.var.ssl_client_raw_cert + if not client_cert then ngx.status = self.error_status ngx.say("No required TLS certificate was sent") return ngx.exit(ngx.status) end + local cert, err = X509.new(client_cert) + if not cert then + ngx.status = self.error_status + ngx.log(ngx.WARN, "Invalid TLS certificate, err: ", err) + ngx.say("Invalid TLS certificate") + return ngx.exit(ngx.status) + end + local store = self.x509_store store:set_flags(store.verify_flags.X509_V_FLAG_PARTIAL_CHAIN) From 3af4e2d27844a8f5a4c2af0229fbb39e11e3199f Mon Sep 17 00:00:00 2001 From: An Tran Date: Fri, 25 Oct 2024 12:04:56 +1000 Subject: [PATCH 3/3] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee82741be..38052401c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added the `APICAST_HTTPS_VERIFY_CLIENT` variable to allow configuration of the `ssl_verify_client` directive. [PR #1491](https://github.com/3scale/APIcast/pull/1491) [THREESCALE-10156](https://issues.redhat.com/browse/THREESCALE-10156) - Add `APICAST_LUA_SOCKET_KEEPALIVE_REQUESTS` to limit the number of requests a single keepalive socket can handle [PR #1496](https://github.com/3scale/APIcast/pull/1496) [THREESCALE-11321](https://issues.redhat.com/browse/THREESCALE-11321) +- Replace internal OPENSSL module with lua-resty-openssl [PR #1502](https://github.com/3scale/APIcast/pull/1502) [THREESCALE-11412](https://issues.redhat.com/browse/THREESCALE-11412) ## [3.15.0] 2024-04-04