From b2125ad7dc87e8f1689edff381f53113a9d1a644 Mon Sep 17 00:00:00 2001 From: willmafh Date: Sat, 3 Oct 2026 20:02:44 +0800 Subject: [PATCH 1/4] feature: ssl_verify_by_lua* directives working after receiving client certificates, allowing us to use Lua to dynamically control their verification. --- config | 2 + src/ngx_http_lua_common.h | 8 + src/ngx_http_lua_control.c | 6 +- src/ngx_http_lua_ctx.c | 6 +- src/ngx_http_lua_module.c | 81 ++ src/ngx_http_lua_ssl.h | 3 +- src/ngx_http_lua_ssl_verifyby.c | 710 +++++++++++++++++ src/ngx_http_lua_ssl_verifyby.h | 35 + src/ngx_http_lua_util.c | 3 + src/ngx_http_lua_util.h | 4 +- t/172-ssl-verify.t | 1274 +++++++++++++++++++++++++++++++ 11 files changed, 2126 insertions(+), 6 deletions(-) create mode 100644 src/ngx_http_lua_ssl_verifyby.c create mode 100644 src/ngx_http_lua_ssl_verifyby.h create mode 100644 t/172-ssl-verify.t diff --git a/config b/config index 6de049665d..5417bc3b1a 100644 --- a/config +++ b/config @@ -296,6 +296,7 @@ HTTP_LUA_SRCS=" \ $ngx_addon_dir/src/ngx_http_lua_balancer.c \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_storeby.c \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_fetchby.c \ + $ngx_addon_dir/src/ngx_http_lua_ssl_verifyby.c \ $ngx_addon_dir/src/ngx_http_lua_ssl.c \ $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_certby.c \ $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_verifyby.c \ @@ -362,6 +363,7 @@ HTTP_LUA_DEPS=" \ $ngx_addon_dir/src/ngx_http_lua_ssl_export_keying_material.h \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_storeby.h \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_fetchby.h \ + $ngx_addon_dir/src/ngx_http_lua_ssl_verifyby.h \ $ngx_addon_dir/src/ngx_http_lua_ssl.h \ $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_certby.h \ $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_verifyby.h \ diff --git a/src/ngx_http_lua_common.h b/src/ngx_http_lua_common.h index 28538cc224..853e955da3 100644 --- a/src/ngx_http_lua_common.h +++ b/src/ngx_http_lua_common.h @@ -173,6 +173,7 @@ typedef struct { #define NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY 0x00010000 #define NGX_HTTP_LUA_CONTEXT_PRECONTENT 0x00020000 #define NGX_HTTP_LUA_CONTEXT_PROXY_SSL_CERT 0x00040000 +#define NGX_HTTP_LUA_CONTEXT_SSL_VERIFY 0x00080000 #define NGX_HTTP_LUA_FFI_NO_REQ_CTX -100 #define NGX_HTTP_LUA_FFI_BAD_CONTEXT -101 @@ -373,6 +374,13 @@ struct ngx_http_lua_srv_conf_s { u_char *ssl_client_hello_src_key; u_char *ssl_client_hello_chunkname; int ssl_client_hello_src_ref; + + ngx_http_lua_srv_conf_handler_pt ssl_verify_handler; + ngx_str_t ssl_verify_src; + u_char *ssl_verify_src_key; + u_char *ssl_verify_chunkname; + int ssl_verify_src_ref; + ngx_flag_t skip_openssl_default_verify; #endif ngx_http_lua_srv_conf_handler_pt server_rewrite_handler; diff --git a/src/ngx_http_lua_control.c b/src/ngx_http_lua_control.c index 895604c42c..8688dca1d5 100644 --- a/src/ngx_http_lua_control.c +++ b/src/ngx_http_lua_control.c @@ -394,7 +394,8 @@ ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, u_char *err, | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH, + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY, err, errlen) != NGX_OK) { @@ -408,7 +409,8 @@ ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, u_char *err, #endif | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH)) + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY)) { #if (NGX_HTTP_SSL) diff --git a/src/ngx_http_lua_ctx.c b/src/ngx_http_lua_ctx.c index 391de151c7..23b6c3e9b5 100644 --- a/src/ngx_http_lua_ctx.c +++ b/src/ngx_http_lua_ctx.c @@ -95,7 +95,8 @@ ngx_http_lua_ffi_get_ctx_ref(ngx_http_request_t *r, int *in_ssl_phase, *in_ssl_phase = ctx->context & (NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE); + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY); *ssl_ctx_ref = LUA_NOREF; #if (NGX_HTTP_SSL) @@ -133,7 +134,8 @@ ngx_http_lua_ffi_set_ctx_ref(ngx_http_request_t *r, int ref) if (ctx->context & (NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE)) + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY)) { ssl_ctx = ngx_http_lua_ssl_get_ctx(r->connection->ssl->connection); if (ssl_ctx == NULL) { diff --git a/src/ngx_http_lua_module.c b/src/ngx_http_lua_module.c index 19880ff599..3f8749af03 100644 --- a/src/ngx_http_lua_module.c +++ b/src/ngx_http_lua_module.c @@ -32,6 +32,7 @@ #include "ngx_http_lua_ssl_certby.h" #include "ngx_http_lua_ssl_session_storeby.h" #include "ngx_http_lua_ssl_session_fetchby.h" +#include "ngx_http_lua_ssl_verifyby.h" #include "ngx_http_lua_proxy_ssl_certby.h" #include "ngx_http_lua_proxy_ssl_verifyby.h" @@ -696,6 +697,27 @@ static ngx_command_t ngx_http_lua_cmds[] = { 0, (void *) ngx_http_lua_ssl_sess_fetch_handler_file }, + { ngx_string("ssl_verify_by_lua_block"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_http_lua_ssl_verify_by_lua_block, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + (void *) ngx_http_lua_ssl_verify_handler_inline }, + + { ngx_string("ssl_verify_by_lua_file"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_lua_ssl_verify_by_lua, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + (void *) ngx_http_lua_ssl_verify_handler_file }, + + { ngx_string("lua_skip_openssl_default_verify"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_lua_srv_conf_t, srv.skip_openssl_default_verify), + NULL }, + #if HAVE_LUA_PROXY_SSL /* same context as proxy_pass directive */ { ngx_string("proxy_ssl_certificate_by_lua_block"), @@ -1305,6 +1327,11 @@ ngx_http_lua_create_srv_conf(ngx_conf_t *cf) * lscf->srv.ssl_sess_fetch_chunkname = NULL; * lscf->srv.ssl_sess_fetch_src_key = NULL; * + * lscf->srv.ssl_verify_handler = NULL; + * lscf->srv.ssl_verify_src = { 0, NULL }; + * lscf->srv.ssl_verify_chunkname = NULL; + * lscf->srv.ssl_verify_src_key = NULL; + * * lscf->srv.server_rewrite_handler = NULL; * lscf->srv.server_rewrite_src = { 0, NULL }; * lscf->srv.server_rewrite_chunkname = NULL; @@ -1323,6 +1350,8 @@ ngx_http_lua_create_srv_conf(ngx_conf_t *cf) lscf->srv.ssl_cert_src_ref = LUA_REFNIL; lscf->srv.ssl_sess_store_src_ref = LUA_REFNIL; lscf->srv.ssl_sess_fetch_src_ref = LUA_REFNIL; + lscf->srv.ssl_verify_src_ref = LUA_REFNIL; + lscf->srv.skip_openssl_default_verify = NGX_CONF_UNSET; #endif lscf->srv.server_rewrite_src_ref = LUA_REFNIL; @@ -1473,6 +1502,58 @@ ngx_http_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } } + if (conf->srv.ssl_verify_src.len == 0) { + conf->srv.ssl_verify_src = prev->srv.ssl_verify_src; + conf->srv.ssl_verify_src_ref = prev->srv.ssl_verify_src_ref; + conf->srv.ssl_verify_src_key = prev->srv.ssl_verify_src_key; + conf->srv.ssl_verify_handler = prev->srv.ssl_verify_handler; + conf->srv.ssl_verify_chunkname + = prev->srv.ssl_verify_chunkname; + } + + if (conf->srv.ssl_verify_src.len) { + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); + if (sscf == NULL || sscf->ssl.ctx == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl configured for the server"); + + return NGX_CONF_ERROR; + } + + if (sscf->verify == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "ssl_verify_client can't be off " + "when using ssl_verify_by_lua*"); + + return NGX_CONF_ERROR; + } +#ifdef LIBRESSL_VERSION_NUMBER + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "LibreSSL does not support by ssl_verify_by_lua*"); + return NGX_CONF_ERROR; + +#else + +#if defined(SSL_ERROR_WANT_RETRY_VERIFY) && \ + OPENSSL_VERSION_NUMBER >= 0x40100000L + + SSL_CTX_set_cert_verify_callback(sscf->ssl.ctx, + ngx_http_lua_ssl_verify_handler, + NULL); + +#else + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "OpenSSL too old to support ssl_verify_by_lua*"); + return NGX_CONF_ERROR; + +#endif +#endif + } + + ngx_conf_merge_value(conf->srv.skip_openssl_default_verify, + prev->srv.skip_openssl_default_verify, 0); + #endif /* NGX_HTTP_SSL */ if (conf->srv.server_rewrite_src.value.len == 0) { diff --git a/src/ngx_http_lua_ssl.h b/src/ngx_http_lua_ssl.h index a7cdc6e633..51f8fc2d0b 100644 --- a/src/ngx_http_lua_ssl.h +++ b/src/ngx_http_lua_ssl.h @@ -24,8 +24,8 @@ typedef struct { ngx_str_t session_id; -#if HAVE_LUA_PROXY_SSL X509_STORE_CTX *x509_store; +#if HAVE_LUA_PROXY_SSL ngx_pool_t *pool; #endif @@ -48,6 +48,7 @@ typedef struct { unsigned entered_client_hello_handler:1; unsigned entered_cert_handler:1; unsigned entered_sess_fetch_handler:1; + unsigned entered_ssl_verify_handler:1; #if HAVE_LUA_PROXY_SSL unsigned entered_proxy_ssl_cert_handler:1; unsigned entered_proxy_ssl_verify_handler:1; diff --git a/src/ngx_http_lua_ssl_verifyby.c b/src/ngx_http_lua_ssl_verifyby.c new file mode 100644 index 0000000000..1493ac1646 --- /dev/null +++ b/src/ngx_http_lua_ssl_verifyby.c @@ -0,0 +1,710 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#if (NGX_HTTP_SSL) + + +#include "ngx_http_lua_cache.h" +#include "ngx_http_lua_initworkerby.h" +#include "ngx_http_lua_util.h" +#include "ngx_http_ssl_module.h" +#include "ngx_http_lua_contentby.h" +#include "ngx_http_lua_ssl_verifyby.h" +#include "ngx_http_lua_directive.h" +#include "ngx_http_lua_ssl.h" + + +static void ngx_http_lua_ssl_verify_done(void *data); +static void ngx_http_lua_ssl_verify_aborted(void *data); +static u_char *ngx_http_lua_log_ssl_verify_error(ngx_log_t *log, + u_char *buf, size_t len); +static ngx_int_t ngx_http_lua_ssl_verify_by_chunk(lua_State *L, + ngx_http_request_t *r); + + +ngx_int_t +ngx_http_lua_ssl_verify_handler_file(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_http_lua_cache_loadfile(r->connection->log, L, + lscf->srv.ssl_verify_src.data, + &lscf->srv.ssl_verify_src_ref, + lscf->srv.ssl_verify_src_key); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_http_lua_assert(lua_isfunction(L, -1)); + + return ngx_http_lua_ssl_verify_by_chunk(L, r); +} + + +ngx_int_t +ngx_http_lua_ssl_verify_handler_inline(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, + lscf->srv.ssl_verify_src.data, + lscf->srv.ssl_verify_src.len, + &lscf->srv.ssl_verify_src_ref, + lscf->srv.ssl_verify_src_key, + (const char *) lscf->srv.ssl_verify_chunkname); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_http_lua_assert(lua_isfunction(L, -1)); + + return ngx_http_lua_ssl_verify_by_chunk(L, r); +} + + +char * +ngx_http_lua_ssl_verify_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *rv; + ngx_conf_t save; + + save = *cf; + cf->handler = ngx_http_lua_ssl_verify_by_lua; + cf->handler_conf = conf; + + rv = ngx_http_lua_conf_lua_block_parse(cf, cmd); + + *cf = save; + + return rv; +} + + +char * +ngx_http_lua_ssl_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#if !defined(SSL_ERROR_WANT_RETRY_VERIFY) || \ + OPENSSL_VERSION_NUMBER < 0x40100000L + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "at least OpenSSL 4.1.0 required but found " + OPENSSL_VERSION_TEXT); + + return NGX_CONF_ERROR; + +#else + + size_t chunkname_len; + u_char *chunkname; + u_char *cache_key = NULL; + u_char *name; + ngx_str_t *value; + ngx_http_lua_srv_conf_t *lscf = conf; + + /* must specify a concrete handler */ + if (cmd->post == NULL) { + return NGX_CONF_ERROR; + } + + if (lscf->srv.ssl_verify_handler) { + return "is duplicate"; + } + + if (ngx_http_lua_ssl_init(cf->log) != NGX_OK) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + lscf->srv.ssl_verify_handler = + (ngx_http_lua_srv_conf_handler_pt) cmd->post; + + if (cmd->post == ngx_http_lua_ssl_verify_handler_file) { + /* Lua code in an external file */ + + name = ngx_http_lua_rebase_path(cf->pool, value[1].data, + value[1].len); + if (name == NULL) { + return NGX_CONF_ERROR; + } + + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + + lscf->srv.ssl_verify_src.data = name; + lscf->srv.ssl_verify_src.len = ngx_strlen(name); + + } else { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, + "ssl_verify_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + + chunkname = ngx_http_lua_gen_chunk_name(cf, "ssl_verify_by_lua", + sizeof("ssl_verify_by_lua") - 1, + &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } + + /* Don't eval nginx variables for inline lua code */ + lscf->srv.ssl_verify_src = value[1]; + lscf->srv.ssl_verify_chunkname = chunkname; + } + + lscf->srv.ssl_verify_src_key = cache_key; + + return NGX_CONF_OK; + +#endif +} + + +int +ngx_http_lua_ssl_verify_handler(X509_STORE_CTX *x509_store, void *arg) +{ + lua_State *L; + ngx_int_t rc; + ngx_connection_t *c, *fc; + ngx_http_request_t *r = NULL; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_lua_srv_conf_t *lscf; + ngx_http_core_loc_conf_t *clcf; + ngx_http_lua_ssl_ctx_t *cctx; + ngx_ssl_conn_t *ssl_conn; + + ssl_conn = X509_STORE_CTX_get_ex_data(x509_store, + SSL_get_ex_data_X509_STORE_CTX_idx()); + + c = ngx_ssl_get_connection(ssl_conn); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl verify: connection reusable: %ud", c->reusable); + + cctx = ngx_http_lua_ssl_get_ctx(c->ssl->connection); + + dd("ssl verify handler, cert-verify-ctx=%p", cctx); + + if (cctx && cctx->entered_ssl_verify_handler) { + /* not the first time */ + + if (cctx->done) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_verify_by_lua: " + "cert verify callback exit code: %d", + cctx->exit_code); + + dd("lua ssl verify done, finally"); + return cctx->exit_code; + } + + return SSL_set_retry_verify(ssl_conn); + } + + dd("first time"); + +#if (nginx_version < 1017009) + ngx_reusable_connection(c, 0); +#endif + + hc = c->data; + + fc = ngx_http_lua_create_fake_connection(NULL); + if (fc == NULL) { + goto failed; + } + + fc->log->handler = ngx_http_lua_log_ssl_verify_error; + fc->log->data = fc; + + fc->addr_text = c->addr_text; + fc->listening = c->listening; + + r = ngx_http_lua_create_fake_request(fc); + if (r == NULL) { + goto failed; + } + + r->main_conf = hc->conf_ctx->main_conf; + r->srv_conf = hc->conf_ctx->srv_conf; + r->loc_conf = hc->conf_ctx->loc_conf; + + fc->log->file = c->log->file; + fc->log->log_level = c->log->log_level; + fc->ssl = c->ssl; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + +#if nginx_version >= 1009000 + ngx_set_connection_log(fc, clcf->error_log); +#else + ngx_http_set_connection_log(fc, clcf->error_log); +#endif + + if (cctx == NULL) { + cctx = ngx_pcalloc(c->pool, sizeof(ngx_http_lua_ssl_ctx_t)); + if (cctx == NULL) { + goto failed; /* error */ + } + + cctx->ctx_ref = LUA_NOREF; + } + + cctx->exit_code = 1; /* successful by default */ + cctx->connection = c; + cctx->request = r; + cctx->x509_store = x509_store; + cctx->entered_ssl_verify_handler = 1; + cctx->done = 0; + + dd("setting cctx"); + + if (SSL_set_ex_data(c->ssl->connection, ngx_http_lua_ssl_ctx_index, cctx) + == 0) + { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed"); + goto failed; + } + + lscf = ngx_http_get_module_srv_conf(r, ngx_http_lua_module); + if (lscf->srv.skip_openssl_default_verify == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_verify_by_lua: openssl default verify"); + + rc = X509_verify_cert(x509_store); + if (rc == 0) { + goto failed; + } + } + + /* TODO honor lua_code_cache off */ + L = ngx_http_lua_get_lua_vm(r, NULL); + + c->log->action = "loading ssl verify by lua"; + + rc = lscf->srv.ssl_verify_handler(r, lscf, L); + + if (rc >= NGX_OK || rc == NGX_ERROR) { + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_verify_by_lua: handler return value: %i, " + "cert verify callback exit code: %d", rc, + cctx->exit_code); + + c->log->action = "SSL handshaking"; + return cctx->exit_code; + } + + /* rc == NGX_DONE */ + + cln = ngx_pool_cleanup_add(fc->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_http_lua_ssl_verify_done; + cln->data = cctx; + + if (cctx->cleanup == NULL) { + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->data = cctx; + cctx->cleanup = &cln->handler; + } + + *cctx->cleanup = ngx_http_lua_ssl_verify_aborted; + + return SSL_set_retry_verify(ssl_conn); + +failed: + + if (r && r->pool) { + ngx_http_lua_free_fake_request(r); + } + + if (fc) { + ngx_http_lua_close_fake_connection(fc); + } + + return 0; /* verify failure or error */ +} + + +static void +ngx_http_lua_ssl_verify_done(void *data) +{ + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx = data; + + dd("lua ssl verify done"); + + if (cctx->aborted) { + return; + } + + ngx_http_lua_assert(cctx->done == 0); + + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + c = cctx->connection; + + c->log->action = "SSL handshaking"; + + ngx_post_event(c->write, &ngx_posted_events); + +#if (HAVE_QUIC_SSL_LUA_YIELD_PATCH && NGX_HTTP_V3) +# if defined(SSL_ERROR_WANT_RETRY_VERIFY) +# if (NGX_QUIC_OPENSSL_COMPAT || NGX_QUIC_OPENSSL_API) + ngx_http_lua_resume_quic_ssl_handshake(c); +# endif +# endif +#endif +} + + +static void +ngx_http_lua_ssl_verify_aborted(void *data) +{ + ngx_http_lua_ssl_ctx_t *cctx = data; + + dd("lua ssl verify aborted"); + + if (cctx->done) { + /* completed successfully already */ + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cctx->connection->log, 0, + "ssl_verify_by_lua: cert verify callback aborted"); + + cctx->aborted = 1; + cctx->request->connection->ssl = NULL; + + ngx_http_lua_finalize_fake_request(cctx->request, NGX_ERROR); +} + + +static u_char * +ngx_http_lua_log_ssl_verify_error(ngx_log_t *log, + u_char *buf, size_t len) +{ + u_char *p; + ngx_connection_t *c; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + buf = p; + } + + p = ngx_snprintf(buf, len, ", context: ssl_verify_by_lua*"); + len -= p - buf; + buf = p; + + c = log->data; + + if (c && c->addr_text.len) { + p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); + len -= p - buf; + buf = p; + } + + if (c && c->listening && c->listening->addr_text.len) { + p = ngx_snprintf(buf, len, ", server: %V", &c->listening->addr_text); + /* len -= p - buf; */ + buf = p; + } + + return buf; +} + + +static ngx_int_t +ngx_http_lua_ssl_verify_by_chunk(lua_State *L, ngx_http_request_t *r) +{ + int co_ref; + ngx_int_t rc; + lua_State *co; + ngx_http_lua_ctx_t *ctx; + ngx_pool_cleanup_t *cln; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + if (ctx == NULL) { + ctx = ngx_http_lua_create_ctx(r); + if (ctx == NULL) { + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + } else { + dd("reset ctx"); + ngx_http_lua_reset_ctx(r, L, ctx); + } + + ctx->entered_content_phase = 1; + + /* {{{ new coroutine to handle request */ + co = ngx_http_lua_new_thread(r, L, &co_ref); + + if (co == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "lua: failed to create new coroutine to handle request"); + + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + /* move code closure to new coroutine */ + lua_xmove(L, co, 1); + +#ifndef OPENRESTY_LUAJIT + /* set closure's env table to new coroutine's globals table */ + ngx_http_lua_get_globals_table(co); + lua_setfenv(co, -2); +#endif + + /* save nginx request in coroutine globals table */ + ngx_http_lua_set_req(co, r); + + ctx->cur_co_ctx = &ctx->entry_co_ctx; + ctx->cur_co_ctx->co = co; + ctx->cur_co_ctx->co_ref = co_ref; +#ifdef NGX_LUA_USE_ASSERT + ctx->cur_co_ctx->co_top = 1; +#endif + + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + + /* register request cleanup hooks */ + if (ctx->cleanup == NULL) { + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + cln->handler = ngx_http_lua_request_cleanup_handler; + cln->data = ctx; + ctx->cleanup = &cln->handler; + } + + ctx->context = NGX_HTTP_LUA_CONTEXT_SSL_VERIFY; + + rc = ngx_http_lua_run_thread(L, r, ctx, 0); + + if (rc == NGX_ERROR || rc >= NGX_OK) { + /* do nothing */ + + } else if (rc == NGX_AGAIN) { + rc = ngx_http_lua_content_run_posted_threads(L, r, ctx, 0); + + } else if (rc == NGX_DONE) { + rc = ngx_http_lua_content_run_posted_threads(L, r, ctx, 1); + + } else { + rc = NGX_OK; + } + + ngx_http_lua_finalize_request(r, rc); + return rc; +} + + +/* + * openssl's doc of SSL_CTX_set_cert_verify_callback: + * In any case a viable verification result value must + * be reflected in the error member of x509_store_ctx, + * which can be done using X509_STORE_CTX_set_error. + */ +int +ngx_http_lua_ffi_ssl_set_verify_result(ngx_http_request_t *r, + int verify_result, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + *err = "LibreSSL does not support by ssl_verify_by_lua*"; + return NGX_ERROR; +#else + +#if defined(SSL_ERROR_WANT_RETRY_VERIFY) && \ + OPENSSL_VERSION_NUMBER >= 0x40100000L + ngx_ssl_conn_t *ssl_conn; + ngx_http_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + cctx = ngx_http_lua_ssl_get_ctx(ssl_conn); + if (cctx == NULL) { + *err = "bad lua context"; + return NGX_ERROR; + } + + x509_store = cctx->x509_store; + + X509_STORE_CTX_set_error(x509_store, verify_result); + + return NGX_OK; + +#else + *err = "OpenSSL too old to support this function"; + return NGX_ERROR; + +#endif + +#endif /* LIBRESSL_VERSION_NUMBER */ +} + + +int +ngx_http_lua_ffi_ssl_get_verify_result(ngx_http_request_t *r, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + *err = "LibreSSL does not support by ssl_verify_by_lua*"; + return NGX_ERROR; +#else + +#if defined(SSL_ERROR_WANT_RETRY_VERIFY) && \ + OPENSSL_VERSION_NUMBER >= 0x40100000L + ngx_ssl_conn_t *ssl_conn; + ngx_http_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + cctx = ngx_http_lua_ssl_get_ctx(ssl_conn); + if (cctx == NULL) { + *err = "bad lua context"; + return NGX_ERROR; + } + + x509_store = cctx->x509_store; + + return X509_STORE_CTX_get_error(x509_store); + +#else + *err = "OpenSSL too old to support this function"; + return NGX_ERROR; + +#endif + +#endif /* LIBRESSL_VERSION_NUMBER */ +} + + +void +ngx_http_lua_ffi_ssl_free_verify_cert(void *cdata) +{ + X509 *cert = cdata; + + X509_free(cert); +} + + +void * +ngx_http_lua_ffi_ssl_get_verify_cert(ngx_http_request_t *r, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + *err = "LibreSSL does not support by ssl_verify_by_lua*"; + return NULL; +#else + +#if defined(SSL_ERROR_WANT_RETRY_VERIFY) && \ + OPENSSL_VERSION_NUMBER >= 0x40100000L + ngx_ssl_conn_t *ssl_conn; + ngx_http_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + X509 *x509; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NULL; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NULL; + } + + cctx = ngx_http_lua_ssl_get_ctx(ssl_conn); + if (cctx == NULL) { + *err = "bad lua context"; + return NULL; + } + + x509_store = cctx->x509_store; + + x509 = X509_STORE_CTX_get0_cert(x509_store); + + if (!X509_up_ref(x509)) { + *err = "get verify cert failed"; + return NULL; + } + + return x509; + +#else + *err = "OpenSSL too old to support this function"; + return NULL; + +#endif + +#endif /* LIBRESSL_VERSION_NUMBER */ +} + + +#endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_ssl_verifyby.h b/src/ngx_http_lua_ssl_verifyby.h new file mode 100644 index 0000000000..22654d38c1 --- /dev/null +++ b/src/ngx_http_lua_ssl_verifyby.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef _NGX_HTTP_LUA_SSL_VERIFYBY_H_INCLUDED_ +#define _NGX_HTTP_LUA_SSL_VERIFYBY_H_INCLUDED_ + + +#include "ngx_http_lua_common.h" + + +#if (NGX_HTTP_SSL) + + +ngx_int_t ngx_http_lua_ssl_verify_handler_inline(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L); + +ngx_int_t ngx_http_lua_ssl_verify_handler_file(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L); + +char *ngx_http_lua_ssl_verify_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + +char *ngx_http_lua_ssl_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +int ngx_http_lua_ssl_verify_handler(X509_STORE_CTX *x509_store, void *arg); + + +#endif /* NGX_HTTP_SSL */ + + +#endif /* _NGX_HTTP_LUA_SSL_VERIFYBY_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_util.c b/src/ngx_http_lua_util.c index b6581f890a..1dae16d0f5 100644 --- a/src/ngx_http_lua_util.c +++ b/src/ngx_http_lua_util.c @@ -4673,6 +4673,9 @@ ngx_http_lua_resume_quic_ssl_handshake(ngx_connection_t *c) #endif #ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB && sslerr != SSL_ERROR_WANT_CLIENT_HELLO_CB +#endif +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + && sslerr != SSL_ERROR_WANT_RETRY_VERIFY #endif ) { /* If a fatal error occurs or lua script exits with error during quic diff --git a/src/ngx_http_lua_util.h b/src/ngx_http_lua_util.h index c8dc99d2fc..868e225606 100644 --- a/src/ngx_http_lua_util.h +++ b/src/ngx_http_lua_util.h @@ -42,7 +42,8 @@ | NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY \ | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO \ | NGX_HTTP_LUA_CONTEXT_SSL_CERT \ - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH) + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH \ + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY) /* key in Lua vm registry for all the "ngx.ctx" tables */ #define ngx_http_lua_ctx_tables_key "ngx_lua_ctx_tables" @@ -72,6 +73,7 @@ "ssl_session_store_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH ? \ "ssl_session_fetch_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_SSL_VERIFY ? "ssl_verify_by_lua*" \ : "(unknown)") #define ngx_http_lua_check_context(L, ctx, flags) \ diff --git a/t/172-ssl-verify.t b/t/172-ssl-verify.t new file mode 100644 index 0000000000..92ebe70cc1 --- /dev/null +++ b/t/172-ssl-verify.t @@ -0,0 +1,1274 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(3); + +# All these tests need to have new openssl +my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $openssl_version = eval { `$NginxBinary -V 2>&1` }; + +if ($openssl_version =~ m/built with OpenSSL (\d+)\.(\d+)\.(\d+)/) { + my ($major, $minor, $patch) = ($1, $2, $3); + + if ($major < 4 || ($major == 4 && $minor < 1)) { + plan(skip_all => "too old OpenSSL, need >= 4.1.0 was " . + "$major.$minor.$patch"); + } else { + plan tests => repeat_each() * (blocks() * 5 + 20); + } +} elsif ($openssl_version =~ m/running with BoringSSL/) { + plan(skip_all => "does not support BoringSSL"); +} else { + die "unknown SSL"; +} + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; +$ENV{TEST_NGINX_QUIC_IDLE_TIMEOUT} ||= 0.6; + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: ssl_verify_by_lua in http {} block so that all servers should be ssl configured +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("hello world") + } + + more_clear_headers Date; + } + } + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_log +no ssl configured for the server +--- must_die + + + +=== TEST 2: ssl_verify_by_lua in server {} block but ssl_verify_client off +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client off; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("hello world") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_log +ssl_verify_client can't be off when using ssl_verify_by_lua* +--- must_die + + + +=== TEST 3: simple logging +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "ssl verify by lua is running!") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging return") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +simple logging return +--- error_log +ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 4: sleep +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in ssl verify by lua: ", ngx.now() - begin) + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("sleep") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +sleep +--- error_log eval +qr/elapsed in ssl verify by lua: 0.(?:09|1\d)\d+,/, +--- no_error_log +[error] +[alert] + + + +=== TEST 5: timer +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local function f() + print("my timer run!") + end + local ok, err = ngx.timer.at(0, f) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("timer") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +timer +--- error_log +my timer run! +--- no_error_log +[error] +[alert] + + + +=== TEST 6: ngx.exit(0) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.exit(0) + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(0) no yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +ngx.exit(0) no yield +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 7: ngx.exit(ngx.ERROR) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.exit(ngx.ERROR) + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(ngx.ERROR) no yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'lua exit with code -1', +'ssl_verify_by_lua: handler return value: -1, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 8: ngx.exit(0) - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.sleep(0.001) + ngx.exit(0) + + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(0) yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +ngx.exit(0) yield +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 9: ngx.exit(ngx.ERROR) - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.sleep(0.001) + ngx.exit(ngx.ERROR) + + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(ngx.ERROR) yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'lua exit with code -1', +'ssl_verify_by_lua: cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 10: lua exception - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua exception - no yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'runtime error: ssl_verify_by_lua(nginx.conf:34):2: bad bad bad', +'ssl_verify_by_lua: handler return value: 500, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 11: lua exception - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.sleep(0.001) + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua exception - yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'runtime error: ssl_verify_by_lua(nginx.conf:34):3: bad bad bad', +'ssl_verify_by_lua: cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 12: get phase +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + print("get_phase: ", ngx.get_phase()) + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("get phase return") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +get phase return +--- error_log +get_phase: ssl_verify +--- no_error_log +[error] +[alert] + + + +=== TEST 13: subrequests disabled +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.location.capture("/foo") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("subrequests disabled") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'ssl_verify_by_lua(nginx.conf:34):2: API disabled in the context of ssl_verify_by_lua*', +'ssl_verify_by_lua: handler return value: 500, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +[alert] + + + +=== TEST 14: simple logging (by_lua_file) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_file html/a.lua; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging by lua file") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- user_files +>>> a.lua +print("ssl verify by lua is running!") + +--- request +GET /t +--- response_body +simple logging by lua file +--- error_log +a.lua:1: ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 15: coroutine API +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield + + local function f() + local cnt = 0 + for i = 1, 20 do + print("co yield: ", cnt) + cy() + cnt = cnt + 1 + end + end + + local c = cc(f) + for i = 1, 3 do + print("co resume, status: ", coroutine.status(c)) + cr(c) + end + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("coroutine API") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +coroutine API +--- grep_error_log eval: qr/co (?:yield: \d+|resume, status: \w+)/ +--- grep_error_log_out +co resume, status: suspended +co yield: 0 +co resume, status: suspended +co yield: 1 +co resume, status: suspended +co yield: 2 +--- no_error_log +[error] +[alert] + + + +=== TEST 16: simple user thread wait with yielding +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local function f() + ngx.sleep(0.01) + print("uthread: hello in thread") + return "done" + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "uthread: failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + print("uthread: thread created: ", coroutine.status(t)) + + local ok, res = ngx.thread.wait(t) + if not ok then + print("uthread: failed to wait thread: ", res) + return + end + + print("uthread: ", res) + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple user thread wait with yielding") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +simple user thread wait with yielding +--- no_error_log +[error] +[alert] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: thread created: running +uthread: hello in thread +uthread: done + + + +=== TEST 17: uthread (kill) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local function f() + ngx.log(ngx.INFO, "uthread: hello from f()") + ngx.sleep(1) + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + local ok, res = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.ERR, "failed to kill thread: ", res) + return + end + + ngx.log(ngx.INFO, "uthread: killed") + + local ok, err = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.INFO, "uthread: failed to kill: ", err) + end + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("uthread (kill)") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +uthread (kill) +--- no_error_log +[error] +[alert] +[emerg] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: hello from f() +uthread: killed +uthread: failed to kill: already waited or killed + + + +=== TEST 18: ngx.exit(ngx.OK) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.exit(ngx.OK) + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(ngx.OK) - no yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +ngx.exit(ngx.OK) - no yield +--- error_log eval +[ +'ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +'lua exit with code 0', +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 19: ssl_verify_by_lua* without yield API (simple logic) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + print("ssl verify: simple test start") + + -- Simple calculations without yield + local sum = 0 + for i = 1, 10 do + sum = sum + i + end + + print("ssl verify: calculated sum: ", sum) + + -- String operations + local str = "hello" + str = str .. " world" + print("ssl verify: concatenated string: ", str) + + -- Table operations + local t = {a = 1, b = 2, c = 3} + local count = 0 + for k, v in pairs(t) do + count = count + v + end + print("ssl verify: table sum: ", count) + + print("ssl verify: simple test done") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("without yield API, simple logic") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } + +--- request +GET /t +--- response_body +without yield API, simple logic +--- grep_error_log eval: qr/(ssl verify: simple test start|ssl verify: calculated sum: 55|ssl verify: concatenated string: hello world|ssl verify: table sum: 6|ssl verify: simple test done)/ +--- grep_error_log_out +ssl verify: simple test start +ssl verify: calculated sum: 55 +ssl verify: concatenated string: hello world +ssl verify: table sum: 6 +ssl verify: simple test done + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 20: lua_skip_openssl_default_verify default off +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "ssl verify by lua is running!") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua_skip_openssl_default_verify default off") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_log +ssl_verify_by_lua: openssl default verify +--- no_error_log +[error] +[alert] + + + +=== TEST 21: lua_skip_openssl_default_verify on +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + lua_skip_openssl_default_verify on; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "ssl verify by lua is running!") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua_skip_openssl_default_verify on") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +lua_skip_openssl_default_verify on +--- error_log +ssl verify by lua is running! +--- no_error_log +ssl_verify_by_lua: openssl default verify +[error] +[alert] + + + +=== TEST 22: cosocket +--- http_config + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1; + server_name test.com; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { + ngx.sleep(0.1) + + ngx.status = 201 + ngx.say("foo") + ngx.exit(201) + } + more_clear_headers Date; + } + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + do + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + if not ok then + ngx.log(ngx.ERR, "failed to connect: ", err) + return + end + + ngx.log(ngx.INFO, "connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.log(ngx.ERR, "failed to send http request: ", err) + return + end + + ngx.log(ngx.INFO, "sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.log(ngx.ERR, "failed to receive response status line: ", err) + break + end + + ngx.log(ngx.INFO, "received: ", line) + end + + local ok, err = sock:close() + ngx.log(ngx.INFO, "close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging return") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +simple logging return +--- error_log +connected: 1 +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: openresty +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil +--- no_error_log +[error] +[alert] From 5ca1974c87842ed4fdd1c885ee07a18e4588ad0b Mon Sep 17 00:00:00 2001 From: willmafh Date: Sat, 3 Oct 2026 20:02:44 +0800 Subject: [PATCH 2/4] feature: ssl_verify_by_lua* directives working after receiving client certificates, allowing us to use Lua to dynamically control their verification. --- README.markdown | 96 +++ config | 2 + src/ngx_http_lua_common.h | 8 + src/ngx_http_lua_control.c | 6 +- src/ngx_http_lua_ctx.c | 6 +- src/ngx_http_lua_module.c | 81 ++ src/ngx_http_lua_ssl.h | 3 +- src/ngx_http_lua_ssl_verifyby.c | 710 +++++++++++++++++ src/ngx_http_lua_ssl_verifyby.h | 35 + src/ngx_http_lua_util.c | 3 + src/ngx_http_lua_util.h | 4 +- t/172-ssl-verify.t | 1274 +++++++++++++++++++++++++++++++ 12 files changed, 2222 insertions(+), 6 deletions(-) create mode 100644 src/ngx_http_lua_ssl_verifyby.c create mode 100644 src/ngx_http_lua_ssl_verifyby.h create mode 100644 t/172-ssl-verify.t diff --git a/README.markdown b/README.markdown index 96c01ebba3..cd47d9874d 100644 --- a/README.markdown +++ b/README.markdown @@ -1177,11 +1177,14 @@ Directives * [ssl_session_fetch_by_lua_file](#ssl_session_fetch_by_lua_file) * [ssl_session_store_by_lua_block](#ssl_session_store_by_lua_block) * [ssl_session_store_by_lua_file](#ssl_session_store_by_lua_file) +* [ssl_verify_by_lua_block](#ssl_verify_by_lua_block) +* [ssl_verify_by_lua_file](#ssl_verify_by_lua_file) * [proxy_ssl_certificate_by_lua_block](#proxy_ssl_certificate_by_lua_block) * [proxy_ssl_certificate_by_lua_file](#proxy_ssl_certificate_by_lua_file) * [proxy_ssl_verify_by_lua_block](#proxy_ssl_verify_by_lua_block) * [proxy_ssl_verify_by_lua_file](#proxy_ssl_verify_by_lua_file) * [lua_shared_dict](#lua_shared_dict) +* [lua_skip_openssl_default_verify](#lua_skip_openssl_default_verify) * [lua_socket_connect_timeout](#lua_socket_connect_timeout) * [lua_socket_send_timeout](#lua_socket_send_timeout) * [lua_socket_send_lowat](#lua_socket_send_lowat) @@ -3241,6 +3244,84 @@ Note that: this directive is only allowed to used in **http context** from the ` [Back to TOC](#directives) +ssl_verify_by_lua_block +----------------------- + +**syntax:** *ssl_verify_by_lua_block { lua-script }* + +**context:** *http, server* + +**phase:** *right-after-client-certificate-message-was-processed* + +This directive runs user Lua code when Nginx is about to post-process the SSL client certificate message for the downstream SSL (https) connections. + +It is particularly useful to parse downstream client certificate and do some custom operations in pure lua. + +The [ngx.ssl.sslverify](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/sslverify.md) Lua module provided by the [lua-resty-core](https://github.com/openresty/lua-resty-core/#readme) library are particularly useful in this context. + +Below is a trivial example using the +[ngx.ssl.sslverify](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/sslverify.md) module +at the same time: + +```nginx + + server { + listen 443 ssl; + server_name test.com; + ssl_verify_client on; + ssl_certificate /path/to/cert.crt; + ssl_certificate_key /path/to/key.key; + + ssl_verify_by_lua_block { + local ssl_vfy = require "ngx.ssl.sslverify" + + local cert = ssl_vfy.get_verify_cert() + + -- ocsp to verify cert + -- check crl + + ssl_vfy.set_verify_result() + ... + } + + location /t { + proxy_ssl_certificate /path/to/cert.crt; + proxy_ssl_certificate_key /path/to/key.key; + proxy_pass https://upstream; + } + ... + } +``` + +See more information in the [ngx.ssl.sslverify](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/sslverify.md) +Lua module's official documentation. + +Uncaught Lua exceptions in the user Lua code immediately abort the current SSL session, so does the +[ngx.exit](#ngxexit) call with an error code like `ngx.ERROR`. + +This Lua code execution context *does* support yielding, so Lua APIs that may yield +(like cosockets, sleeping, and "light threads") +are enabled in this context + +This directive requires OpenSSL 4.1.0 or greater. + +[Back to TOC](#directives) + +ssl_verify_by_lua_file +---------------------- + +**syntax:** *ssl_verify_by_lua_file <path-to-lua-script-file>* + +**context:** *http, server* + +**phase:** *right-after-client-certificate-message-was-processed* + +Equivalent to [ssl_verify_by_lua_block](#ssl_verify_by_lua_block), except that the file specified by `` contains the Lua code, or, as from the `v0.5.0rc32` release, the [LuaJIT bytecode](#luajit-bytecode-support) to be executed. + +When a relative path like `foo/bar.lua` is given, they will be turned into the absolute path relative to the `server prefix` path determined by the `-p PATH` command-line option while starting the Nginx server. + +[Back to TOC](#directives) + proxy_ssl_certificate_by_lua_block ---------------------------------- @@ -3441,6 +3522,21 @@ This directive was first introduced in the `v0.3.1rc22` release. [Back to TOC](#directives) +lua_skip_openssl_default_verify +------------------------------- + +**syntax:** *lua_skip_openssl_default_verify on|off* + +**default:** *lua_skip_openssl_default_verify off* + +**context:** *http, server* + +When using ssl_verify_by_lua directive, `lua_skip_openssl_default_verify` controls whether to skip default openssl's verify function, that means using pure Lua code to verify downstream client certificate. + +This directive is turned `off` by default. + +[Back to TOC](#directives) + lua_socket_connect_timeout -------------------------- diff --git a/config b/config index 6de049665d..5417bc3b1a 100644 --- a/config +++ b/config @@ -296,6 +296,7 @@ HTTP_LUA_SRCS=" \ $ngx_addon_dir/src/ngx_http_lua_balancer.c \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_storeby.c \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_fetchby.c \ + $ngx_addon_dir/src/ngx_http_lua_ssl_verifyby.c \ $ngx_addon_dir/src/ngx_http_lua_ssl.c \ $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_certby.c \ $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_verifyby.c \ @@ -362,6 +363,7 @@ HTTP_LUA_DEPS=" \ $ngx_addon_dir/src/ngx_http_lua_ssl_export_keying_material.h \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_storeby.h \ $ngx_addon_dir/src/ngx_http_lua_ssl_session_fetchby.h \ + $ngx_addon_dir/src/ngx_http_lua_ssl_verifyby.h \ $ngx_addon_dir/src/ngx_http_lua_ssl.h \ $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_certby.h \ $ngx_addon_dir/src/ngx_http_lua_proxy_ssl_verifyby.h \ diff --git a/src/ngx_http_lua_common.h b/src/ngx_http_lua_common.h index 28538cc224..853e955da3 100644 --- a/src/ngx_http_lua_common.h +++ b/src/ngx_http_lua_common.h @@ -173,6 +173,7 @@ typedef struct { #define NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY 0x00010000 #define NGX_HTTP_LUA_CONTEXT_PRECONTENT 0x00020000 #define NGX_HTTP_LUA_CONTEXT_PROXY_SSL_CERT 0x00040000 +#define NGX_HTTP_LUA_CONTEXT_SSL_VERIFY 0x00080000 #define NGX_HTTP_LUA_FFI_NO_REQ_CTX -100 #define NGX_HTTP_LUA_FFI_BAD_CONTEXT -101 @@ -373,6 +374,13 @@ struct ngx_http_lua_srv_conf_s { u_char *ssl_client_hello_src_key; u_char *ssl_client_hello_chunkname; int ssl_client_hello_src_ref; + + ngx_http_lua_srv_conf_handler_pt ssl_verify_handler; + ngx_str_t ssl_verify_src; + u_char *ssl_verify_src_key; + u_char *ssl_verify_chunkname; + int ssl_verify_src_ref; + ngx_flag_t skip_openssl_default_verify; #endif ngx_http_lua_srv_conf_handler_pt server_rewrite_handler; diff --git a/src/ngx_http_lua_control.c b/src/ngx_http_lua_control.c index 895604c42c..8688dca1d5 100644 --- a/src/ngx_http_lua_control.c +++ b/src/ngx_http_lua_control.c @@ -394,7 +394,8 @@ ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, u_char *err, | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH, + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY, err, errlen) != NGX_OK) { @@ -408,7 +409,8 @@ ngx_http_lua_ffi_exit(ngx_http_request_t *r, int status, u_char *err, #endif | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH)) + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY)) { #if (NGX_HTTP_SSL) diff --git a/src/ngx_http_lua_ctx.c b/src/ngx_http_lua_ctx.c index 391de151c7..23b6c3e9b5 100644 --- a/src/ngx_http_lua_ctx.c +++ b/src/ngx_http_lua_ctx.c @@ -95,7 +95,8 @@ ngx_http_lua_ffi_get_ctx_ref(ngx_http_request_t *r, int *in_ssl_phase, *in_ssl_phase = ctx->context & (NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE); + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY); *ssl_ctx_ref = LUA_NOREF; #if (NGX_HTTP_SSL) @@ -133,7 +134,8 @@ ngx_http_lua_ffi_set_ctx_ref(ngx_http_request_t *r, int ref) if (ctx->context & (NGX_HTTP_LUA_CONTEXT_SSL_CERT | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE)) + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_STORE + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY)) { ssl_ctx = ngx_http_lua_ssl_get_ctx(r->connection->ssl->connection); if (ssl_ctx == NULL) { diff --git a/src/ngx_http_lua_module.c b/src/ngx_http_lua_module.c index 19880ff599..3f8749af03 100644 --- a/src/ngx_http_lua_module.c +++ b/src/ngx_http_lua_module.c @@ -32,6 +32,7 @@ #include "ngx_http_lua_ssl_certby.h" #include "ngx_http_lua_ssl_session_storeby.h" #include "ngx_http_lua_ssl_session_fetchby.h" +#include "ngx_http_lua_ssl_verifyby.h" #include "ngx_http_lua_proxy_ssl_certby.h" #include "ngx_http_lua_proxy_ssl_verifyby.h" @@ -696,6 +697,27 @@ static ngx_command_t ngx_http_lua_cmds[] = { 0, (void *) ngx_http_lua_ssl_sess_fetch_handler_file }, + { ngx_string("ssl_verify_by_lua_block"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_http_lua_ssl_verify_by_lua_block, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + (void *) ngx_http_lua_ssl_verify_handler_inline }, + + { ngx_string("ssl_verify_by_lua_file"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_lua_ssl_verify_by_lua, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + (void *) ngx_http_lua_ssl_verify_handler_file }, + + { ngx_string("lua_skip_openssl_default_verify"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_lua_srv_conf_t, srv.skip_openssl_default_verify), + NULL }, + #if HAVE_LUA_PROXY_SSL /* same context as proxy_pass directive */ { ngx_string("proxy_ssl_certificate_by_lua_block"), @@ -1305,6 +1327,11 @@ ngx_http_lua_create_srv_conf(ngx_conf_t *cf) * lscf->srv.ssl_sess_fetch_chunkname = NULL; * lscf->srv.ssl_sess_fetch_src_key = NULL; * + * lscf->srv.ssl_verify_handler = NULL; + * lscf->srv.ssl_verify_src = { 0, NULL }; + * lscf->srv.ssl_verify_chunkname = NULL; + * lscf->srv.ssl_verify_src_key = NULL; + * * lscf->srv.server_rewrite_handler = NULL; * lscf->srv.server_rewrite_src = { 0, NULL }; * lscf->srv.server_rewrite_chunkname = NULL; @@ -1323,6 +1350,8 @@ ngx_http_lua_create_srv_conf(ngx_conf_t *cf) lscf->srv.ssl_cert_src_ref = LUA_REFNIL; lscf->srv.ssl_sess_store_src_ref = LUA_REFNIL; lscf->srv.ssl_sess_fetch_src_ref = LUA_REFNIL; + lscf->srv.ssl_verify_src_ref = LUA_REFNIL; + lscf->srv.skip_openssl_default_verify = NGX_CONF_UNSET; #endif lscf->srv.server_rewrite_src_ref = LUA_REFNIL; @@ -1473,6 +1502,58 @@ ngx_http_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) } } + if (conf->srv.ssl_verify_src.len == 0) { + conf->srv.ssl_verify_src = prev->srv.ssl_verify_src; + conf->srv.ssl_verify_src_ref = prev->srv.ssl_verify_src_ref; + conf->srv.ssl_verify_src_key = prev->srv.ssl_verify_src_key; + conf->srv.ssl_verify_handler = prev->srv.ssl_verify_handler; + conf->srv.ssl_verify_chunkname + = prev->srv.ssl_verify_chunkname; + } + + if (conf->srv.ssl_verify_src.len) { + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); + if (sscf == NULL || sscf->ssl.ctx == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl configured for the server"); + + return NGX_CONF_ERROR; + } + + if (sscf->verify == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "ssl_verify_client can't be off " + "when using ssl_verify_by_lua*"); + + return NGX_CONF_ERROR; + } +#ifdef LIBRESSL_VERSION_NUMBER + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "LibreSSL does not support by ssl_verify_by_lua*"); + return NGX_CONF_ERROR; + +#else + +#if defined(SSL_ERROR_WANT_RETRY_VERIFY) && \ + OPENSSL_VERSION_NUMBER >= 0x40100000L + + SSL_CTX_set_cert_verify_callback(sscf->ssl.ctx, + ngx_http_lua_ssl_verify_handler, + NULL); + +#else + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "OpenSSL too old to support ssl_verify_by_lua*"); + return NGX_CONF_ERROR; + +#endif +#endif + } + + ngx_conf_merge_value(conf->srv.skip_openssl_default_verify, + prev->srv.skip_openssl_default_verify, 0); + #endif /* NGX_HTTP_SSL */ if (conf->srv.server_rewrite_src.value.len == 0) { diff --git a/src/ngx_http_lua_ssl.h b/src/ngx_http_lua_ssl.h index a7cdc6e633..51f8fc2d0b 100644 --- a/src/ngx_http_lua_ssl.h +++ b/src/ngx_http_lua_ssl.h @@ -24,8 +24,8 @@ typedef struct { ngx_str_t session_id; -#if HAVE_LUA_PROXY_SSL X509_STORE_CTX *x509_store; +#if HAVE_LUA_PROXY_SSL ngx_pool_t *pool; #endif @@ -48,6 +48,7 @@ typedef struct { unsigned entered_client_hello_handler:1; unsigned entered_cert_handler:1; unsigned entered_sess_fetch_handler:1; + unsigned entered_ssl_verify_handler:1; #if HAVE_LUA_PROXY_SSL unsigned entered_proxy_ssl_cert_handler:1; unsigned entered_proxy_ssl_verify_handler:1; diff --git a/src/ngx_http_lua_ssl_verifyby.c b/src/ngx_http_lua_ssl_verifyby.c new file mode 100644 index 0000000000..1493ac1646 --- /dev/null +++ b/src/ngx_http_lua_ssl_verifyby.c @@ -0,0 +1,710 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#if (NGX_HTTP_SSL) + + +#include "ngx_http_lua_cache.h" +#include "ngx_http_lua_initworkerby.h" +#include "ngx_http_lua_util.h" +#include "ngx_http_ssl_module.h" +#include "ngx_http_lua_contentby.h" +#include "ngx_http_lua_ssl_verifyby.h" +#include "ngx_http_lua_directive.h" +#include "ngx_http_lua_ssl.h" + + +static void ngx_http_lua_ssl_verify_done(void *data); +static void ngx_http_lua_ssl_verify_aborted(void *data); +static u_char *ngx_http_lua_log_ssl_verify_error(ngx_log_t *log, + u_char *buf, size_t len); +static ngx_int_t ngx_http_lua_ssl_verify_by_chunk(lua_State *L, + ngx_http_request_t *r); + + +ngx_int_t +ngx_http_lua_ssl_verify_handler_file(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_http_lua_cache_loadfile(r->connection->log, L, + lscf->srv.ssl_verify_src.data, + &lscf->srv.ssl_verify_src_ref, + lscf->srv.ssl_verify_src_key); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_http_lua_assert(lua_isfunction(L, -1)); + + return ngx_http_lua_ssl_verify_by_chunk(L, r); +} + + +ngx_int_t +ngx_http_lua_ssl_verify_handler_inline(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L, + lscf->srv.ssl_verify_src.data, + lscf->srv.ssl_verify_src.len, + &lscf->srv.ssl_verify_src_ref, + lscf->srv.ssl_verify_src_key, + (const char *) lscf->srv.ssl_verify_chunkname); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_http_lua_assert(lua_isfunction(L, -1)); + + return ngx_http_lua_ssl_verify_by_chunk(L, r); +} + + +char * +ngx_http_lua_ssl_verify_by_lua_block(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ + char *rv; + ngx_conf_t save; + + save = *cf; + cf->handler = ngx_http_lua_ssl_verify_by_lua; + cf->handler_conf = conf; + + rv = ngx_http_lua_conf_lua_block_parse(cf, cmd); + + *cf = save; + + return rv; +} + + +char * +ngx_http_lua_ssl_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#if !defined(SSL_ERROR_WANT_RETRY_VERIFY) || \ + OPENSSL_VERSION_NUMBER < 0x40100000L + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "at least OpenSSL 4.1.0 required but found " + OPENSSL_VERSION_TEXT); + + return NGX_CONF_ERROR; + +#else + + size_t chunkname_len; + u_char *chunkname; + u_char *cache_key = NULL; + u_char *name; + ngx_str_t *value; + ngx_http_lua_srv_conf_t *lscf = conf; + + /* must specify a concrete handler */ + if (cmd->post == NULL) { + return NGX_CONF_ERROR; + } + + if (lscf->srv.ssl_verify_handler) { + return "is duplicate"; + } + + if (ngx_http_lua_ssl_init(cf->log) != NGX_OK) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + lscf->srv.ssl_verify_handler = + (ngx_http_lua_srv_conf_handler_pt) cmd->post; + + if (cmd->post == ngx_http_lua_ssl_verify_handler_file) { + /* Lua code in an external file */ + + name = ngx_http_lua_rebase_path(cf->pool, value[1].data, + value[1].len); + if (name == NULL) { + return NGX_CONF_ERROR; + } + + cache_key = ngx_http_lua_gen_file_cache_key(cf, value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + + lscf->srv.ssl_verify_src.data = name; + lscf->srv.ssl_verify_src.len = ngx_strlen(name); + + } else { + cache_key = ngx_http_lua_gen_chunk_cache_key(cf, + "ssl_verify_by_lua", + value[1].data, + value[1].len); + if (cache_key == NULL) { + return NGX_CONF_ERROR; + } + + chunkname = ngx_http_lua_gen_chunk_name(cf, "ssl_verify_by_lua", + sizeof("ssl_verify_by_lua") - 1, + &chunkname_len); + if (chunkname == NULL) { + return NGX_CONF_ERROR; + } + + /* Don't eval nginx variables for inline lua code */ + lscf->srv.ssl_verify_src = value[1]; + lscf->srv.ssl_verify_chunkname = chunkname; + } + + lscf->srv.ssl_verify_src_key = cache_key; + + return NGX_CONF_OK; + +#endif +} + + +int +ngx_http_lua_ssl_verify_handler(X509_STORE_CTX *x509_store, void *arg) +{ + lua_State *L; + ngx_int_t rc; + ngx_connection_t *c, *fc; + ngx_http_request_t *r = NULL; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_lua_srv_conf_t *lscf; + ngx_http_core_loc_conf_t *clcf; + ngx_http_lua_ssl_ctx_t *cctx; + ngx_ssl_conn_t *ssl_conn; + + ssl_conn = X509_STORE_CTX_get_ex_data(x509_store, + SSL_get_ex_data_X509_STORE_CTX_idx()); + + c = ngx_ssl_get_connection(ssl_conn); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl verify: connection reusable: %ud", c->reusable); + + cctx = ngx_http_lua_ssl_get_ctx(c->ssl->connection); + + dd("ssl verify handler, cert-verify-ctx=%p", cctx); + + if (cctx && cctx->entered_ssl_verify_handler) { + /* not the first time */ + + if (cctx->done) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_verify_by_lua: " + "cert verify callback exit code: %d", + cctx->exit_code); + + dd("lua ssl verify done, finally"); + return cctx->exit_code; + } + + return SSL_set_retry_verify(ssl_conn); + } + + dd("first time"); + +#if (nginx_version < 1017009) + ngx_reusable_connection(c, 0); +#endif + + hc = c->data; + + fc = ngx_http_lua_create_fake_connection(NULL); + if (fc == NULL) { + goto failed; + } + + fc->log->handler = ngx_http_lua_log_ssl_verify_error; + fc->log->data = fc; + + fc->addr_text = c->addr_text; + fc->listening = c->listening; + + r = ngx_http_lua_create_fake_request(fc); + if (r == NULL) { + goto failed; + } + + r->main_conf = hc->conf_ctx->main_conf; + r->srv_conf = hc->conf_ctx->srv_conf; + r->loc_conf = hc->conf_ctx->loc_conf; + + fc->log->file = c->log->file; + fc->log->log_level = c->log->log_level; + fc->ssl = c->ssl; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + +#if nginx_version >= 1009000 + ngx_set_connection_log(fc, clcf->error_log); +#else + ngx_http_set_connection_log(fc, clcf->error_log); +#endif + + if (cctx == NULL) { + cctx = ngx_pcalloc(c->pool, sizeof(ngx_http_lua_ssl_ctx_t)); + if (cctx == NULL) { + goto failed; /* error */ + } + + cctx->ctx_ref = LUA_NOREF; + } + + cctx->exit_code = 1; /* successful by default */ + cctx->connection = c; + cctx->request = r; + cctx->x509_store = x509_store; + cctx->entered_ssl_verify_handler = 1; + cctx->done = 0; + + dd("setting cctx"); + + if (SSL_set_ex_data(c->ssl->connection, ngx_http_lua_ssl_ctx_index, cctx) + == 0) + { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed"); + goto failed; + } + + lscf = ngx_http_get_module_srv_conf(r, ngx_http_lua_module); + if (lscf->srv.skip_openssl_default_verify == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_verify_by_lua: openssl default verify"); + + rc = X509_verify_cert(x509_store); + if (rc == 0) { + goto failed; + } + } + + /* TODO honor lua_code_cache off */ + L = ngx_http_lua_get_lua_vm(r, NULL); + + c->log->action = "loading ssl verify by lua"; + + rc = lscf->srv.ssl_verify_handler(r, lscf, L); + + if (rc >= NGX_OK || rc == NGX_ERROR) { + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl_verify_by_lua: handler return value: %i, " + "cert verify callback exit code: %d", rc, + cctx->exit_code); + + c->log->action = "SSL handshaking"; + return cctx->exit_code; + } + + /* rc == NGX_DONE */ + + cln = ngx_pool_cleanup_add(fc->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_http_lua_ssl_verify_done; + cln->data = cctx; + + if (cctx->cleanup == NULL) { + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->data = cctx; + cctx->cleanup = &cln->handler; + } + + *cctx->cleanup = ngx_http_lua_ssl_verify_aborted; + + return SSL_set_retry_verify(ssl_conn); + +failed: + + if (r && r->pool) { + ngx_http_lua_free_fake_request(r); + } + + if (fc) { + ngx_http_lua_close_fake_connection(fc); + } + + return 0; /* verify failure or error */ +} + + +static void +ngx_http_lua_ssl_verify_done(void *data) +{ + ngx_connection_t *c; + ngx_http_lua_ssl_ctx_t *cctx = data; + + dd("lua ssl verify done"); + + if (cctx->aborted) { + return; + } + + ngx_http_lua_assert(cctx->done == 0); + + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + c = cctx->connection; + + c->log->action = "SSL handshaking"; + + ngx_post_event(c->write, &ngx_posted_events); + +#if (HAVE_QUIC_SSL_LUA_YIELD_PATCH && NGX_HTTP_V3) +# if defined(SSL_ERROR_WANT_RETRY_VERIFY) +# if (NGX_QUIC_OPENSSL_COMPAT || NGX_QUIC_OPENSSL_API) + ngx_http_lua_resume_quic_ssl_handshake(c); +# endif +# endif +#endif +} + + +static void +ngx_http_lua_ssl_verify_aborted(void *data) +{ + ngx_http_lua_ssl_ctx_t *cctx = data; + + dd("lua ssl verify aborted"); + + if (cctx->done) { + /* completed successfully already */ + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cctx->connection->log, 0, + "ssl_verify_by_lua: cert verify callback aborted"); + + cctx->aborted = 1; + cctx->request->connection->ssl = NULL; + + ngx_http_lua_finalize_fake_request(cctx->request, NGX_ERROR); +} + + +static u_char * +ngx_http_lua_log_ssl_verify_error(ngx_log_t *log, + u_char *buf, size_t len) +{ + u_char *p; + ngx_connection_t *c; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + buf = p; + } + + p = ngx_snprintf(buf, len, ", context: ssl_verify_by_lua*"); + len -= p - buf; + buf = p; + + c = log->data; + + if (c && c->addr_text.len) { + p = ngx_snprintf(buf, len, ", client: %V", &c->addr_text); + len -= p - buf; + buf = p; + } + + if (c && c->listening && c->listening->addr_text.len) { + p = ngx_snprintf(buf, len, ", server: %V", &c->listening->addr_text); + /* len -= p - buf; */ + buf = p; + } + + return buf; +} + + +static ngx_int_t +ngx_http_lua_ssl_verify_by_chunk(lua_State *L, ngx_http_request_t *r) +{ + int co_ref; + ngx_int_t rc; + lua_State *co; + ngx_http_lua_ctx_t *ctx; + ngx_pool_cleanup_t *cln; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + + if (ctx == NULL) { + ctx = ngx_http_lua_create_ctx(r); + if (ctx == NULL) { + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + } else { + dd("reset ctx"); + ngx_http_lua_reset_ctx(r, L, ctx); + } + + ctx->entered_content_phase = 1; + + /* {{{ new coroutine to handle request */ + co = ngx_http_lua_new_thread(r, L, &co_ref); + + if (co == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "lua: failed to create new coroutine to handle request"); + + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + /* move code closure to new coroutine */ + lua_xmove(L, co, 1); + +#ifndef OPENRESTY_LUAJIT + /* set closure's env table to new coroutine's globals table */ + ngx_http_lua_get_globals_table(co); + lua_setfenv(co, -2); +#endif + + /* save nginx request in coroutine globals table */ + ngx_http_lua_set_req(co, r); + + ctx->cur_co_ctx = &ctx->entry_co_ctx; + ctx->cur_co_ctx->co = co; + ctx->cur_co_ctx->co_ref = co_ref; +#ifdef NGX_LUA_USE_ASSERT + ctx->cur_co_ctx->co_top = 1; +#endif + + ngx_http_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + + /* register request cleanup hooks */ + if (ctx->cleanup == NULL) { + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + rc = NGX_ERROR; + ngx_http_lua_finalize_request(r, rc); + return rc; + } + + cln->handler = ngx_http_lua_request_cleanup_handler; + cln->data = ctx; + ctx->cleanup = &cln->handler; + } + + ctx->context = NGX_HTTP_LUA_CONTEXT_SSL_VERIFY; + + rc = ngx_http_lua_run_thread(L, r, ctx, 0); + + if (rc == NGX_ERROR || rc >= NGX_OK) { + /* do nothing */ + + } else if (rc == NGX_AGAIN) { + rc = ngx_http_lua_content_run_posted_threads(L, r, ctx, 0); + + } else if (rc == NGX_DONE) { + rc = ngx_http_lua_content_run_posted_threads(L, r, ctx, 1); + + } else { + rc = NGX_OK; + } + + ngx_http_lua_finalize_request(r, rc); + return rc; +} + + +/* + * openssl's doc of SSL_CTX_set_cert_verify_callback: + * In any case a viable verification result value must + * be reflected in the error member of x509_store_ctx, + * which can be done using X509_STORE_CTX_set_error. + */ +int +ngx_http_lua_ffi_ssl_set_verify_result(ngx_http_request_t *r, + int verify_result, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + *err = "LibreSSL does not support by ssl_verify_by_lua*"; + return NGX_ERROR; +#else + +#if defined(SSL_ERROR_WANT_RETRY_VERIFY) && \ + OPENSSL_VERSION_NUMBER >= 0x40100000L + ngx_ssl_conn_t *ssl_conn; + ngx_http_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + cctx = ngx_http_lua_ssl_get_ctx(ssl_conn); + if (cctx == NULL) { + *err = "bad lua context"; + return NGX_ERROR; + } + + x509_store = cctx->x509_store; + + X509_STORE_CTX_set_error(x509_store, verify_result); + + return NGX_OK; + +#else + *err = "OpenSSL too old to support this function"; + return NGX_ERROR; + +#endif + +#endif /* LIBRESSL_VERSION_NUMBER */ +} + + +int +ngx_http_lua_ffi_ssl_get_verify_result(ngx_http_request_t *r, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + *err = "LibreSSL does not support by ssl_verify_by_lua*"; + return NGX_ERROR; +#else + +#if defined(SSL_ERROR_WANT_RETRY_VERIFY) && \ + OPENSSL_VERSION_NUMBER >= 0x40100000L + ngx_ssl_conn_t *ssl_conn; + ngx_http_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + cctx = ngx_http_lua_ssl_get_ctx(ssl_conn); + if (cctx == NULL) { + *err = "bad lua context"; + return NGX_ERROR; + } + + x509_store = cctx->x509_store; + + return X509_STORE_CTX_get_error(x509_store); + +#else + *err = "OpenSSL too old to support this function"; + return NGX_ERROR; + +#endif + +#endif /* LIBRESSL_VERSION_NUMBER */ +} + + +void +ngx_http_lua_ffi_ssl_free_verify_cert(void *cdata) +{ + X509 *cert = cdata; + + X509_free(cert); +} + + +void * +ngx_http_lua_ffi_ssl_get_verify_cert(ngx_http_request_t *r, char **err) +{ +#ifdef LIBRESSL_VERSION_NUMBER + *err = "LibreSSL does not support by ssl_verify_by_lua*"; + return NULL; +#else + +#if defined(SSL_ERROR_WANT_RETRY_VERIFY) && \ + OPENSSL_VERSION_NUMBER >= 0x40100000L + ngx_ssl_conn_t *ssl_conn; + ngx_http_lua_ssl_ctx_t *cctx; + X509_STORE_CTX *x509_store; + X509 *x509; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NULL; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NULL; + } + + cctx = ngx_http_lua_ssl_get_ctx(ssl_conn); + if (cctx == NULL) { + *err = "bad lua context"; + return NULL; + } + + x509_store = cctx->x509_store; + + x509 = X509_STORE_CTX_get0_cert(x509_store); + + if (!X509_up_ref(x509)) { + *err = "get verify cert failed"; + return NULL; + } + + return x509; + +#else + *err = "OpenSSL too old to support this function"; + return NULL; + +#endif + +#endif /* LIBRESSL_VERSION_NUMBER */ +} + + +#endif /* NGX_HTTP_SSL */ diff --git a/src/ngx_http_lua_ssl_verifyby.h b/src/ngx_http_lua_ssl_verifyby.h new file mode 100644 index 0000000000..22654d38c1 --- /dev/null +++ b/src/ngx_http_lua_ssl_verifyby.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef _NGX_HTTP_LUA_SSL_VERIFYBY_H_INCLUDED_ +#define _NGX_HTTP_LUA_SSL_VERIFYBY_H_INCLUDED_ + + +#include "ngx_http_lua_common.h" + + +#if (NGX_HTTP_SSL) + + +ngx_int_t ngx_http_lua_ssl_verify_handler_inline(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L); + +ngx_int_t ngx_http_lua_ssl_verify_handler_file(ngx_http_request_t *r, + ngx_http_lua_srv_conf_t *lscf, lua_State *L); + +char *ngx_http_lua_ssl_verify_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + +char *ngx_http_lua_ssl_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +int ngx_http_lua_ssl_verify_handler(X509_STORE_CTX *x509_store, void *arg); + + +#endif /* NGX_HTTP_SSL */ + + +#endif /* _NGX_HTTP_LUA_SSL_VERIFYBY_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_http_lua_util.c b/src/ngx_http_lua_util.c index b6581f890a..1dae16d0f5 100644 --- a/src/ngx_http_lua_util.c +++ b/src/ngx_http_lua_util.c @@ -4673,6 +4673,9 @@ ngx_http_lua_resume_quic_ssl_handshake(ngx_connection_t *c) #endif #ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB && sslerr != SSL_ERROR_WANT_CLIENT_HELLO_CB +#endif +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + && sslerr != SSL_ERROR_WANT_RETRY_VERIFY #endif ) { /* If a fatal error occurs or lua script exits with error during quic diff --git a/src/ngx_http_lua_util.h b/src/ngx_http_lua_util.h index c8dc99d2fc..868e225606 100644 --- a/src/ngx_http_lua_util.h +++ b/src/ngx_http_lua_util.h @@ -42,7 +42,8 @@ | NGX_HTTP_LUA_CONTEXT_PROXY_SSL_VERIFY \ | NGX_HTTP_LUA_CONTEXT_SSL_CLIENT_HELLO \ | NGX_HTTP_LUA_CONTEXT_SSL_CERT \ - | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH) + | NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH \ + | NGX_HTTP_LUA_CONTEXT_SSL_VERIFY) /* key in Lua vm registry for all the "ngx.ctx" tables */ #define ngx_http_lua_ctx_tables_key "ngx_lua_ctx_tables" @@ -72,6 +73,7 @@ "ssl_session_store_by_lua*" \ : (c) == NGX_HTTP_LUA_CONTEXT_SSL_SESS_FETCH ? \ "ssl_session_fetch_by_lua*" \ + : (c) == NGX_HTTP_LUA_CONTEXT_SSL_VERIFY ? "ssl_verify_by_lua*" \ : "(unknown)") #define ngx_http_lua_check_context(L, ctx, flags) \ diff --git a/t/172-ssl-verify.t b/t/172-ssl-verify.t new file mode 100644 index 0000000000..92ebe70cc1 --- /dev/null +++ b/t/172-ssl-verify.t @@ -0,0 +1,1274 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(3); + +# All these tests need to have new openssl +my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $openssl_version = eval { `$NginxBinary -V 2>&1` }; + +if ($openssl_version =~ m/built with OpenSSL (\d+)\.(\d+)\.(\d+)/) { + my ($major, $minor, $patch) = ($1, $2, $3); + + if ($major < 4 || ($major == 4 && $minor < 1)) { + plan(skip_all => "too old OpenSSL, need >= 4.1.0 was " . + "$major.$minor.$patch"); + } else { + plan tests => repeat_each() * (blocks() * 5 + 20); + } +} elsif ($openssl_version =~ m/running with BoringSSL/) { + plan(skip_all => "does not support BoringSSL"); +} else { + die "unknown SSL"; +} + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; +$ENV{TEST_NGINX_QUIC_IDLE_TIMEOUT} ||= 0.6; + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: ssl_verify_by_lua in http {} block so that all servers should be ssl configured +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("hello world") + } + + more_clear_headers Date; + } + } + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_log +no ssl configured for the server +--- must_die + + + +=== TEST 2: ssl_verify_by_lua in server {} block but ssl_verify_client off +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client off; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("hello world") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_log +ssl_verify_client can't be off when using ssl_verify_by_lua* +--- must_die + + + +=== TEST 3: simple logging +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "ssl verify by lua is running!") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging return") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +simple logging return +--- error_log +ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 4: sleep +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in ssl verify by lua: ", ngx.now() - begin) + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("sleep") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +sleep +--- error_log eval +qr/elapsed in ssl verify by lua: 0.(?:09|1\d)\d+,/, +--- no_error_log +[error] +[alert] + + + +=== TEST 5: timer +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local function f() + print("my timer run!") + end + local ok, err = ngx.timer.at(0, f) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + return + end + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("timer") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +timer +--- error_log +my timer run! +--- no_error_log +[error] +[alert] + + + +=== TEST 6: ngx.exit(0) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.exit(0) + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(0) no yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +ngx.exit(0) no yield +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 7: ngx.exit(ngx.ERROR) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.exit(ngx.ERROR) + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(ngx.ERROR) no yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'lua exit with code -1', +'ssl_verify_by_lua: handler return value: -1, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 8: ngx.exit(0) - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.sleep(0.001) + ngx.exit(0) + + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(0) yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +ngx.exit(0) yield +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 9: ngx.exit(ngx.ERROR) - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.sleep(0.001) + ngx.exit(ngx.ERROR) + + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(ngx.ERROR) yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'lua exit with code -1', +'ssl_verify_by_lua: cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 10: lua exception - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua exception - no yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'runtime error: ssl_verify_by_lua(nginx.conf:34):2: bad bad bad', +'ssl_verify_by_lua: handler return value: 500, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 11: lua exception - yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.sleep(0.001) + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua exception - yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'runtime error: ssl_verify_by_lua(nginx.conf:34):3: bad bad bad', +'ssl_verify_by_lua: cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 12: get phase +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + print("get_phase: ", ngx.get_phase()) + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("get phase return") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +get phase return +--- error_log +get_phase: ssl_verify +--- no_error_log +[error] +[alert] + + + +=== TEST 13: subrequests disabled +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.location.capture("/foo") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("subrequests disabled") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_code: 502 +--- error_log eval +[ +'ssl_verify_by_lua(nginx.conf:34):2: API disabled in the context of ssl_verify_by_lua*', +'ssl_verify_by_lua: handler return value: 500, cert verify callback exit code: 0', +qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, +] +--- no_error_log +[alert] + + + +=== TEST 14: simple logging (by_lua_file) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_file html/a.lua; + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging by lua file") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- user_files +>>> a.lua +print("ssl verify by lua is running!") + +--- request +GET /t +--- response_body +simple logging by lua file +--- error_log +a.lua:1: ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 15: coroutine API +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local cc, cr, cy = coroutine.create, coroutine.resume, coroutine.yield + + local function f() + local cnt = 0 + for i = 1, 20 do + print("co yield: ", cnt) + cy() + cnt = cnt + 1 + end + end + + local c = cc(f) + for i = 1, 3 do + print("co resume, status: ", coroutine.status(c)) + cr(c) + end + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("coroutine API") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +coroutine API +--- grep_error_log eval: qr/co (?:yield: \d+|resume, status: \w+)/ +--- grep_error_log_out +co resume, status: suspended +co yield: 0 +co resume, status: suspended +co yield: 1 +co resume, status: suspended +co yield: 2 +--- no_error_log +[error] +[alert] + + + +=== TEST 16: simple user thread wait with yielding +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local function f() + ngx.sleep(0.01) + print("uthread: hello in thread") + return "done" + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "uthread: failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + print("uthread: thread created: ", coroutine.status(t)) + + local ok, res = ngx.thread.wait(t) + if not ok then + print("uthread: failed to wait thread: ", res) + return + end + + print("uthread: ", res) + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple user thread wait with yielding") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +simple user thread wait with yielding +--- no_error_log +[error] +[alert] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: thread created: running +uthread: hello in thread +uthread: done + + + +=== TEST 17: uthread (kill) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + local function f() + ngx.log(ngx.INFO, "uthread: hello from f()") + ngx.sleep(1) + end + + local t, err = ngx.thread.spawn(f) + if not t then + ngx.log(ngx.ERR, "failed to spawn thread: ", err) + return ngx.exit(ngx.ERROR) + end + + local ok, res = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.ERR, "failed to kill thread: ", res) + return + end + + ngx.log(ngx.INFO, "uthread: killed") + + local ok, err = ngx.thread.kill(t) + if not ok then + ngx.log(ngx.INFO, "uthread: failed to kill: ", err) + end + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("uthread (kill)") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +uthread (kill) +--- no_error_log +[error] +[alert] +[emerg] +--- grep_error_log eval: qr/uthread: [^.,]+/ +--- grep_error_log_out +uthread: hello from f() +uthread: killed +uthread: failed to kill: already waited or killed + + + +=== TEST 18: ngx.exit(ngx.OK) - no yield +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.exit(ngx.OK) + ngx.log(ngx.ERR, "should never reached here...") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("ngx.exit(ngx.OK) - no yield") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +ngx.exit(ngx.OK) - no yield +--- error_log eval +[ +'ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +'lua exit with code 0', +] +--- no_error_log +should never reached here +[alert] +[emerg] + + + +=== TEST 19: ssl_verify_by_lua* without yield API (simple logic) +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + print("ssl verify: simple test start") + + -- Simple calculations without yield + local sum = 0 + for i = 1, 10 do + sum = sum + i + end + + print("ssl verify: calculated sum: ", sum) + + -- String operations + local str = "hello" + str = str .. " world" + print("ssl verify: concatenated string: ", str) + + -- Table operations + local t = {a = 1, b = 2, c = 3} + local count = 0 + for k, v in pairs(t) do + count = count + v + end + print("ssl verify: table sum: ", count) + + print("ssl verify: simple test done") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("without yield API, simple logic") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } + +--- request +GET /t +--- response_body +without yield API, simple logic +--- grep_error_log eval: qr/(ssl verify: simple test start|ssl verify: calculated sum: 55|ssl verify: concatenated string: hello world|ssl verify: table sum: 6|ssl verify: simple test done)/ +--- grep_error_log_out +ssl verify: simple test start +ssl verify: calculated sum: 55 +ssl verify: concatenated string: hello world +ssl verify: table sum: 6 +ssl verify: simple test done + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 20: lua_skip_openssl_default_verify default off +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "ssl verify by lua is running!") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua_skip_openssl_default_verify default off") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- error_log +ssl_verify_by_lua: openssl default verify +--- no_error_log +[error] +[alert] + + + +=== TEST 21: lua_skip_openssl_default_verify on +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + lua_skip_openssl_default_verify on; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "ssl verify by lua is running!") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("lua_skip_openssl_default_verify on") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +lua_skip_openssl_default_verify on +--- error_log +ssl verify by lua is running! +--- no_error_log +ssl_verify_by_lua: openssl default verify +[error] +[alert] + + + +=== TEST 22: cosocket +--- http_config + server { + listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1; + server_name test.com; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { + ngx.sleep(0.1) + + ngx.status = 201 + ngx.say("foo") + ngx.exit(201) + } + more_clear_headers Date; + } + } + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client on; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + do + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_RAND_PORT_1) + if not ok then + ngx.log(ngx.ERR, "failed to connect: ", err) + return + end + + ngx.log(ngx.INFO, "connected: ", ok) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.log(ngx.ERR, "failed to send http request: ", err) + return + end + + ngx.log(ngx.INFO, "sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.log(ngx.ERR, "failed to receive response status line: ", err) + break + end + + ngx.log(ngx.INFO, "received: ", line) + end + + local ok, err = sock:close() + ngx.log(ngx.INFO, "close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging return") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +simple logging return +--- error_log +connected: 1 +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: openresty +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil +--- no_error_log +[error] +[alert] From e11c78d22de87537d60e8f282484fa92d7b7fb64 Mon Sep 17 00:00:00 2001 From: willmafh Date: Fri, 23 Oct 2026 22:33:17 +0800 Subject: [PATCH 3/4] more test cases about ssl_verify_client works with ssl_verify_by_lua* --- t/172-ssl-verify.t | 137 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 116 insertions(+), 21 deletions(-) diff --git a/t/172-ssl-verify.t b/t/172-ssl-verify.t index 92ebe70cc1..7abcbffffe 100644 --- a/t/172-ssl-verify.t +++ b/t/172-ssl-verify.t @@ -15,7 +15,7 @@ if ($openssl_version =~ m/built with OpenSSL (\d+)\.(\d+)\.(\d+)/) { plan(skip_all => "too old OpenSSL, need >= 4.1.0 was " . "$major.$minor.$patch"); } else { - plan tests => repeat_each() * (blocks() * 5 + 20); + plan tests => repeat_each() * (blocks() * 5 + 21); } } elsif ($openssl_version =~ m/running with BoringSSL/) { plan(skip_all => "does not support BoringSSL"); @@ -122,7 +122,7 @@ ssl_verify_client can't be off when using ssl_verify_by_lua* -=== TEST 3: simple logging +=== TEST 3: simple logging and ssl_verify_client on --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -164,12 +164,107 @@ simple logging return --- error_log ssl verify by lua is running! --- no_error_log +client sent no required SSL certificate [error] [alert] -=== TEST 4: sleep +=== TEST 4: simple logging and ssl_verify_client optional +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client optional; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "ssl verify by lua is running!") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging return") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +simple logging return +--- error_log +ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 5: simple logging and ssl_verify_client optional_no_ca +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_verify_client optional_no_ca; + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + ssl_client_certificate ../../cert/mtls_ca.crt; + + ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "ssl verify by lua is running!") + } + + location / { + default_type 'text/plain'; + + content_by_lua_block { + ngx.say("simple logging return") + } + + more_clear_headers Date; + } + } + +--- config + location /t { + proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl_name example.com; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_trusted_certificate ../../cert/mtls_ca.crt; + proxy_ssl_session_reuse off; + } +--- request +GET /t +--- response_body +simple logging return +--- error_log +ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 6: sleep --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -218,7 +313,7 @@ qr/elapsed in ssl verify by lua: 0.(?:09|1\d)\d+,/, -=== TEST 5: timer +=== TEST 7: timer --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -272,7 +367,7 @@ my timer run! -=== TEST 6: ngx.exit(0) - no yield +=== TEST 8: ngx.exit(0) - no yield --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -322,7 +417,7 @@ should never reached here -=== TEST 7: ngx.exit(ngx.ERROR) - no yield +=== TEST 9: ngx.exit(ngx.ERROR) - no yield --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -374,7 +469,7 @@ should never reached here -=== TEST 8: ngx.exit(0) - yield +=== TEST 10: ngx.exit(0) - yield --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -426,7 +521,7 @@ should never reached here -=== TEST 9: ngx.exit(ngx.ERROR) - yield +=== TEST 11: ngx.exit(ngx.ERROR) - yield --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -480,7 +575,7 @@ should never reached here -=== TEST 10: lua exception - no yield +=== TEST 12: lua exception - no yield --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -532,7 +627,7 @@ should never reached here -=== TEST 11: lua exception - yield +=== TEST 13: lua exception - yield --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -585,7 +680,7 @@ should never reached here -=== TEST 12: get phase +=== TEST 14: get phase --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -632,7 +727,7 @@ get_phase: ssl_verify -=== TEST 13: subrequests disabled +=== TEST 15: subrequests disabled --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -681,7 +776,7 @@ qr/.*? SSL_do_handshake\(\) failed .*?certificate verify failed/, -=== TEST 14: simple logging (by_lua_file) +=== TEST 16: simple logging (by_lua_file) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -730,7 +825,7 @@ a.lua:1: ssl verify by lua is running! -=== TEST 15: coroutine API +=== TEST 17: coroutine API --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -798,7 +893,7 @@ co yield: 2 -=== TEST 16: simple user thread wait with yielding +=== TEST 18: simple user thread wait with yielding --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -868,7 +963,7 @@ uthread: done -=== TEST 17: uthread (kill) +=== TEST 19: uthread (kill) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -941,7 +1036,7 @@ uthread: failed to kill: already waited or killed -=== TEST 18: ngx.exit(ngx.OK) - no yield +=== TEST 20: ngx.exit(ngx.OK) - no yield --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -994,7 +1089,7 @@ should never reached here -=== TEST 19: ssl_verify_by_lua* without yield API (simple logic) +=== TEST 21: ssl_verify_by_lua* without yield API (simple logic) --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -1072,7 +1167,7 @@ ssl verify: simple test done -=== TEST 20: lua_skip_openssl_default_verify default off +=== TEST 22: lua_skip_openssl_default_verify default off --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -1117,7 +1212,7 @@ ssl_verify_by_lua: openssl default verify -=== TEST 21: lua_skip_openssl_default_verify on +=== TEST 23: lua_skip_openssl_default_verify on --- http_config server { listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; @@ -1167,7 +1262,7 @@ ssl_verify_by_lua: openssl default verify -=== TEST 22: cosocket +=== TEST 24: cosocket --- http_config server { listen 127.0.0.1:$TEST_NGINX_RAND_PORT_1; From d1b038bbe59b7cfbb177a52d2f8fca7ddbcfab4d Mon Sep 17 00:00:00 2001 From: willmafh Date: Fri, 23 Oct 2026 22:51:16 +0800 Subject: [PATCH 4/4] doc about ssl_verify_by_lua works with ssl_verify_client and $ssl_client_verify --- README.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index cd47d9874d..af10c797dc 100644 --- a/README.markdown +++ b/README.markdown @@ -3257,6 +3257,8 @@ This directive runs user Lua code when Nginx is about to post-process the SSL cl It is particularly useful to parse downstream client certificate and do some custom operations in pure lua. +When using `ssl_verify_by_lua` directive, the `ssl_verify_client` directive should not be off, that is, it should be `on`/`optional`/`optional_no_ca`, and you can still use the `$ssl_client_verify` variable to get the verification result. + The [ngx.ssl.sslverify](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/ssl/sslverify.md) Lua module provided by the [lua-resty-core](https://github.com/openresty/lua-resty-core/#readme) library are particularly useful in this context. Below is a trivial example using the @@ -3531,7 +3533,7 @@ lua_skip_openssl_default_verify **context:** *http, server* -When using ssl_verify_by_lua directive, `lua_skip_openssl_default_verify` controls whether to skip default openssl's verify function, that means using pure Lua code to verify downstream client certificate. +When using `ssl_verify_by_lua` directive, `lua_skip_openssl_default_verify` controls whether to skip default openssl's verify function, that means using pure Lua code to verify downstream client certificate. This directive is turned `off` by default.