From f01f6de44a53931bf4a805e554ded8fa9550a668 Mon Sep 17 00:00:00 2001 From: willmafh Date: Mon, 21 Jul 2025 20:40:59 +0800 Subject: [PATCH 1/7] feature: proxy_ssl_verify_by_lua* directives working after receiving server certificates, allowing us to control upstream ssl handshake dynamically with Lua --- README.md | 2 + config | 2 + src/ngx_stream_lua_common.h | 9 + src/ngx_stream_lua_control.c | 6 +- src/ngx_stream_lua_coroutine.c | 3 + src/ngx_stream_lua_ctx.c | 6 +- src/ngx_stream_lua_module.c | 43 ++ src/ngx_stream_lua_phase.c | 4 + src/ngx_stream_lua_proxy_ssl_verifyby.c | 773 ++++++++++++++++++++ src/ngx_stream_lua_proxy_ssl_verifyby.h | 37 + src/ngx_stream_lua_ssl.h | 6 +- src/ngx_stream_lua_uthread.c | 1 + src/ngx_stream_lua_util.h | 5 +- src/ngx_stream_lua_variable.c | 3 +- t/164-proxy-ssl-verify-by.t | 889 ++++++++++++++++++++++++ 15 files changed, 1782 insertions(+), 7 deletions(-) create mode 100644 src/ngx_stream_lua_proxy_ssl_verifyby.c create mode 100644 src/ngx_stream_lua_proxy_ssl_verifyby.h create mode 100644 t/164-proxy-ssl-verify-by.t diff --git a/README.md b/README.md index 97e05cfa..d8cfdfb3 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,8 @@ behavior. * [ssl_client_hello_by_lua_file](https://github.com/openresty/lua-nginx-module#ssl_client_hello_by_lua_file) * [ssl_certificate_by_lua_block](https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_block) * [ssl_certificate_by_lua_file](https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_file) +* [proxy_ssl_verify_by_lua_block](https://github.com/openresty/lua-nginx-module#proxy_ssl_verify_by_lua_block) +* [proxy_ssl_verify_by_lua_file](https://github.com/openresty/lua-nginx-module#proxy_ssl_verify_by_lua_file) * [lua_shared_dict](https://github.com/openresty/lua-nginx-module#lua_shared_dict) * [lua_socket_connect_timeout](https://github.com/openresty/lua-nginx-module#lua_socket_connect_timeout) * [lua_socket_buffer_size](https://github.com/openresty/lua-nginx-module#lua_socket_buffer_size) diff --git a/config b/config index e1470b7a..a6214034 100644 --- a/config +++ b/config @@ -278,6 +278,7 @@ STREAM_LUA_SRCS=" \ $ngx_addon_dir/src/ngx_stream_lua_semaphore.c \ $ngx_addon_dir/src/ngx_stream_lua_ssl_client_helloby.c \ $ngx_addon_dir/src/ngx_stream_lua_ssl_certby.c \ + $ngx_addon_dir/src/ngx_stream_lua_proxy_ssl_verifyby.c \ $ngx_addon_dir/src/ngx_stream_lua_log_ringbuf.c \ $ngx_addon_dir/src/ngx_stream_lua_input_filters.c \ " @@ -322,6 +323,7 @@ STREAM_LUA_DEPS=" \ $ngx_addon_dir/src/ngx_stream_lua_semaphore.h \ $ngx_addon_dir/src/ngx_stream_lua_ssl_client_helloby.h \ $ngx_addon_dir/src/ngx_stream_lua_ssl_certby.h \ + $ngx_addon_dir/src/ngx_stream_lua_proxy_ssl_verifyby.h \ $ngx_addon_dir/src/ngx_stream_lua_log_ringbuf.h \ $ngx_addon_dir/src/ngx_stream_lua_input_filters.h \ " diff --git a/src/ngx_stream_lua_common.h b/src/ngx_stream_lua_common.h index 751a0f3b..f9681af9 100644 --- a/src/ngx_stream_lua_common.h +++ b/src/ngx_stream_lua_common.h @@ -135,6 +135,7 @@ #define NGX_STREAM_LUA_CONTEXT_PREREAD 0x0020 #define NGX_STREAM_LUA_CONTEXT_SSL_CERT 0x0040 #define NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO 0x0080 +#define NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY 0x0100 #define NGX_STREAM_LUA_FFI_NO_REQ_CTX -100 @@ -269,6 +270,14 @@ struct ngx_stream_lua_srv_conf_s { ngx_str_t ssl_client_hello_src; u_char *ssl_client_hello_src_key; } srv; + + struct { + ngx_stream_lua_srv_conf_handler_pt proxy_ssl_verify_handler; + ngx_str_t proxy_ssl_verify_src; + u_char *proxy_ssl_verify_src_key; + + ngx_flag_t upstream_skip_openssl_default_verify; + } ups; #endif ngx_flag_t enable_code_cache; /* whether to enable diff --git a/src/ngx_stream_lua_control.c b/src/ngx_stream_lua_control.c index c3657007..245f2c59 100644 --- a/src/ngx_stream_lua_control.c +++ b/src/ngx_stream_lua_control.c @@ -116,14 +116,16 @@ ngx_stream_lua_ffi_exit(ngx_stream_lua_request_t *r, int status, u_char *err, | NGX_STREAM_LUA_CONTEXT_BALANCER | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_PREREAD, + | NGX_STREAM_LUA_CONTEXT_PREREAD + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY, err, errlen) != NGX_OK) { return NGX_ERROR; } if (ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO )) + | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY )) { #if (NGX_STREAM_SSL) diff --git a/src/ngx_stream_lua_coroutine.c b/src/ngx_stream_lua_coroutine.c index d4863dd0..4334fc45 100644 --- a/src/ngx_stream_lua_coroutine.c +++ b/src/ngx_stream_lua_coroutine.c @@ -205,6 +205,7 @@ ngx_stream_lua_coroutine_resume(lua_State *L) | NGX_STREAM_LUA_CONTEXT_TIMER | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY | NGX_STREAM_LUA_CONTEXT_PREREAD ); @@ -266,6 +267,7 @@ ngx_stream_lua_coroutine_yield(lua_State *L) | NGX_STREAM_LUA_CONTEXT_TIMER | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY | NGX_STREAM_LUA_CONTEXT_PREREAD ); @@ -426,6 +428,7 @@ ngx_stream_lua_coroutine_status(lua_State *L) | NGX_STREAM_LUA_CONTEXT_TIMER | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY | NGX_STREAM_LUA_CONTEXT_PREREAD ); diff --git a/src/ngx_stream_lua_ctx.c b/src/ngx_stream_lua_ctx.c index a8f9d5b9..fceca8af 100644 --- a/src/ngx_stream_lua_ctx.c +++ b/src/ngx_stream_lua_ctx.c @@ -97,7 +97,8 @@ ngx_stream_lua_ffi_get_ctx_ref(ngx_stream_lua_request_t *r, int *in_ssl_phase, } *in_ssl_phase = ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO); + | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY); *ssl_ctx_ref = LUA_NOREF; #if (NGX_STREAM_SSL) @@ -131,7 +132,8 @@ ngx_stream_lua_ffi_set_ctx_ref(ngx_stream_lua_request_t *r, int ref) #if (NGX_STREAM_SSL) if (ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO)) + | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY)) { ssl_ctx = ngx_stream_lua_ssl_get_ctx(r->connection->ssl->connection); if (ssl_ctx == NULL) { diff --git a/src/ngx_stream_lua_module.c b/src/ngx_stream_lua_module.c index 2aa41ad5..89ceb5d2 100644 --- a/src/ngx_stream_lua_module.c +++ b/src/ngx_stream_lua_module.c @@ -30,6 +30,7 @@ #include "ngx_stream_lua_semaphore.h" #include "ngx_stream_lua_ssl_client_helloby.h" #include "ngx_stream_lua_ssl_certby.h" +#include "ngx_stream_lua_proxy_ssl_verifyby.h" #include "ngx_stream_lua_prereadby.h" @@ -417,6 +418,28 @@ static ngx_command_t ngx_stream_lua_cmds[] = { 0, (void *) ngx_stream_lua_ssl_cert_handler_file }, + /* same context as proxy_pass directive */ + { ngx_string("proxy_ssl_verify_by_lua_block"), + NGX_STREAM_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, + ngx_stream_lua_proxy_ssl_verify_by_lua_block, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + (void *) ngx_stream_lua_proxy_ssl_verify_handler_inline }, + + { ngx_string("proxy_ssl_verify_by_lua_file"), + NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_lua_proxy_ssl_verify_by_lua, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + (void *) ngx_stream_lua_proxy_ssl_verify_handler_file }, + + { ngx_string("lua_upstream_skip_openssl_default_verify"), + NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_lua_srv_conf_t, + ups.upstream_skip_openssl_default_verify), + NULL }, { ngx_string("lua_ssl_verify_depth"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, @@ -813,6 +836,10 @@ ngx_stream_lua_create_srv_conf(ngx_conf_t *cf) * lscf->srv.ssl_client_hello_src = { 0, NULL }; * lscf->srv.ssl_client_hello_src_key = NULL; * + * lscf->ups.proxy_ssl_verify_handler = NULL; + * lscf->ups.proxy_ssl_verify_src = { 0, NULL }; + * lscf->ups.proxy_ssl_verify_src_key = NULL; + * * lscf->srv.ssl_cert_handler = NULL; * lscf->srv.ssl_cert_src = { 0, NULL }; * lscf->srv.ssl_cert_src_key = NULL; @@ -847,6 +874,7 @@ ngx_stream_lua_create_srv_conf(ngx_conf_t *cf) conf->ssl_verify_depth = NGX_CONF_UNSET_UINT; conf->ssl_certificates = NGX_CONF_UNSET_PTR; conf->ssl_certificate_keys = NGX_CONF_UNSET_PTR; + conf->ups.upstream_skip_openssl_default_verify = NGX_CONF_UNSET; #endif return conf; @@ -980,6 +1008,21 @@ ngx_stream_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) NULL); #endif + if (conf->ups.proxy_ssl_verify_src.len == 0) { + conf->ups.proxy_ssl_verify_src = prev->ups.proxy_ssl_verify_src; + conf->ups.proxy_ssl_verify_handler = prev->ups.proxy_ssl_verify_handler; + conf->ups.proxy_ssl_verify_src_key = prev->ups.proxy_ssl_verify_src_key; + } + + if (conf->ups.proxy_ssl_verify_src.len) { + if (ngx_stream_lua_proxy_ssl_verify_set_callback(cf) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + ngx_conf_merge_value(conf->ups.upstream_skip_openssl_default_verify, + prev->ups.upstream_skip_openssl_default_verify, 0); + if (ngx_stream_lua_set_ssl(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; } diff --git a/src/ngx_stream_lua_phase.c b/src/ngx_stream_lua_phase.c index 9ab0cfcf..6a299768 100644 --- a/src/ngx_stream_lua_phase.c +++ b/src/ngx_stream_lua_phase.c @@ -66,6 +66,10 @@ ngx_stream_lua_ngx_get_phase(lua_State *L) lua_pushliteral(L, "content"); break; + case NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY: + lua_pushliteral(L, "proxy_ssl_verify"); + break; + case NGX_STREAM_LUA_CONTEXT_LOG: lua_pushliteral(L, "log"); break; diff --git a/src/ngx_stream_lua_proxy_ssl_verifyby.c b/src/ngx_stream_lua_proxy_ssl_verifyby.c new file mode 100644 index 00000000..5d440203 --- /dev/null +++ b/src/ngx_stream_lua_proxy_ssl_verifyby.c @@ -0,0 +1,773 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef DDEBUG +#define DDEBUG 0 +#endif +#include "ddebug.h" + + +#if (NGX_STREAM_SSL) + +#include "ngx_stream_lua_cache.h" +#include "ngx_stream_lua_initworkerby.h" +#include "ngx_stream_lua_util.h" +#include "ngx_stream_ssl_module.h" +#include "ngx_stream_lua_contentby.h" +#include "ngx_stream_lua_proxy_ssl_verifyby.h" +#include "ngx_stream_lua_directive.h" +#include "ngx_stream_lua_ssl.h" + + +static void ngx_stream_lua_proxy_ssl_verify_done(void *data); +static void ngx_stream_lua_proxy_ssl_verify_aborted(void *data); +static u_char *ngx_stream_lua_log_proxy_ssl_verify_error(ngx_log_t *log, + u_char *buf, size_t len); +static ngx_int_t ngx_stream_lua_proxy_ssl_verify_by_chunk(lua_State *L, + ngx_stream_lua_request_t *r); + + +ngx_int_t +ngx_stream_lua_proxy_ssl_verify_set_callback(ngx_conf_t *cf) +{ + +#ifdef LIBRESSL_VERSION_NUMBER + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "LibreSSL does not support by proxy_ssl_verify_by_lua*"); + + return NGX_ERROR; + +#else + + ngx_flag_t proxy_ssl = 0; + ngx_pool_cleanup_t *cln; + ngx_ssl_t *ssl; + void *pscf; + + /* + * Nginx doesn't export ngx_stream_proxy_srv_conf_t, so we can't directly + * get pscf here, and we also don't want to change ngx_stream_proxy_module's + * code organization, since that it means to add a header file to Nginx. + * I know it's a bit clumsy here, anyway the solution is good enough + */ + for (cln = cf->pool->cleanup; cln; cln = cln->next) { + if (cln->handler != ngx_ssl_cleanup_ctx) { + continue; + } + + ssl = cln->data; + + pscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_proxy_module); + if (pscf == ngx_ssl_get_server_conf(ssl->ctx)) { + /* here we make sure that ssl is pscf->ssl */ + proxy_ssl = 1; + + break; + } + } + + if (!proxy_ssl) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "proxy_ssl_verify_by_lua* " + "should be used with proxy_ssl directive"); + + return NGX_ERROR; + } + +#if (!defined SSL_ERROR_WANT_RETRY_VERIFY \ + || OPENSSL_VERSION_NUMBER < 0x30000020L) + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "OpenSSL too old to support " + "proxy_ssl_verify_by_lua*"); + + return NGX_ERROR; + +#else + + SSL_CTX_set_cert_verify_callback(ssl->ctx, + ngx_stream_lua_proxy_ssl_verify_handler, + NULL); + return NGX_OK; + +#endif + +#endif +} + + +ngx_int_t +ngx_stream_lua_proxy_ssl_verify_handler_file(ngx_stream_lua_request_t *r, + ngx_stream_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_stream_lua_cache_loadfile(r->connection->log, L, + lscf->ups.proxy_ssl_verify_src.data, + lscf->ups.proxy_ssl_verify_src_key); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_stream_lua_assert(lua_isfunction(L, -1)); + + return ngx_stream_lua_proxy_ssl_verify_by_chunk(L, r); +} + + +ngx_int_t +ngx_stream_lua_proxy_ssl_verify_handler_inline(ngx_stream_lua_request_t *r, + ngx_stream_lua_srv_conf_t *lscf, lua_State *L) +{ + ngx_int_t rc; + + rc = ngx_stream_lua_cache_loadbuffer(r->connection->log, L, + lscf->ups.proxy_ssl_verify_src.data, + lscf->ups.proxy_ssl_verify_src.len, + lscf->ups.proxy_ssl_verify_src_key, + "=proxy_ssl_verify_by_lua"); + if (rc != NGX_OK) { + return rc; + } + + /* make sure we have a valid code chunk */ + ngx_stream_lua_assert(lua_isfunction(L, -1)); + + return ngx_stream_lua_proxy_ssl_verify_by_chunk(L, r); +} + + +char * +ngx_stream_lua_proxy_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_stream_lua_proxy_ssl_verify_by_lua; + cf->handler_conf = conf; + + rv = ngx_stream_lua_conf_lua_block_parse(cf, cmd); + + *cf = save; + + return rv; +} + + +char * +ngx_stream_lua_proxy_ssl_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf) +{ +#if (!defined SSL_ERROR_WANT_RETRY_VERIFY \ + || OPENSSL_VERSION_NUMBER < 0x30000020L) + + /* SSL_set_retry_verify() was added in OpenSSL 3.0.2 */ + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "at least OpenSSL 3.0.2 required but found " + OPENSSL_VERSION_TEXT); + + return NGX_CONF_ERROR; + +#else + + u_char *p; + u_char *name; + ngx_str_t *value; + ngx_stream_lua_srv_conf_t *lscf = conf; + + /* must specify a concrete handler */ + if (cmd->post == NULL) { + return NGX_CONF_ERROR; + } + + if (lscf->ups.proxy_ssl_verify_handler) { + return "is duplicate"; + } + + if (ngx_stream_lua_ssl_init(cf->log) != NGX_OK) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + lscf->ups.proxy_ssl_verify_handler = + (ngx_stream_lua_srv_conf_handler_pt) cmd->post; + + if (cmd->post == ngx_stream_lua_proxy_ssl_verify_handler_file) { + /* Lua code in an external file */ + + name = ngx_stream_lua_rebase_path(cf->pool, value[1].data, + value[1].len); + if (name == NULL) { + return NGX_CONF_ERROR; + } + + lscf->ups.proxy_ssl_verify_src.data = name; + lscf->ups.proxy_ssl_verify_src.len = ngx_strlen(name); + + p = ngx_palloc(cf->pool, NGX_STREAM_LUA_FILE_KEY_LEN + 1); + if (p == NULL) { + return NGX_CONF_ERROR; + } + + lscf->ups.proxy_ssl_verify_src_key = p; + + p = ngx_copy(p, NGX_STREAM_LUA_FILE_TAG, NGX_STREAM_LUA_FILE_TAG_LEN); + p = ngx_stream_lua_digest_hex(p, value[1].data, value[1].len); + *p = '\0'; + + } else { + /* inlined Lua code */ + + lscf->ups.proxy_ssl_verify_src = value[1]; + + p = ngx_palloc(cf->pool, + sizeof("proxy_ssl_verify_by_lua") + + NGX_STREAM_LUA_INLINE_KEY_LEN); + if (p == NULL) { + return NGX_CONF_ERROR; + } + + lscf->ups.proxy_ssl_verify_src_key = p; + + p = ngx_copy(p, "proxy_ssl_verify_by_lua", + sizeof("proxy_ssl_verify_by_lua") - 1); + p = ngx_copy(p, NGX_STREAM_LUA_INLINE_TAG, + NGX_STREAM_LUA_INLINE_TAG_LEN); + p = ngx_stream_lua_digest_hex(p, value[1].data, value[1].len); + *p = '\0'; + } + + return NGX_CONF_OK; + +#endif /* SSL_ERROR_WANT_RETRY_VERIFY */ +} + + +int +ngx_stream_lua_proxy_ssl_verify_handler(X509_STORE_CTX *x509_store, void *arg) +{ + lua_State *L; + ngx_int_t rc; + ngx_connection_t *c, *fc; + ngx_stream_lua_request_t *r = NULL; + ngx_pool_cleanup_t *cln; + ngx_stream_lua_srv_conf_t *lscf; + ngx_stream_lua_ssl_ctx_t *cctx; + ngx_stream_core_srv_conf_t *cscf; + ngx_stream_session_t *s, *fs; + 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); /* connection to upstream */ + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy ssl verify: connection reusable: %ud", c->reusable); + + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + + dd("proxy ssl verify handler, cert-verify-ctx=%p", cctx); + + if (cctx && cctx->entered_proxy_ssl_verify_handler) { + /* not the first time */ + + if (cctx->done) { + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy_ssl_verify_by_lua: " + "cert verify callback exit code: %d", + cctx->exit_code); + + dd("lua proxy ssl verify done, finally"); + return cctx->exit_code; + } + + return SSL_set_retry_verify(ssl_conn); + } + + dd("first time"); + + ngx_reusable_connection(c, 0); + + s = c->data; + + fc = ngx_stream_lua_create_fake_connection(NULL); + if (fc == NULL) { + goto failed; + } + + fc->log->handler = ngx_stream_lua_log_proxy_ssl_verify_error; + fc->log->data = fc; + + fc->addr_text = c->addr_text; + fc->listening = c->listening; + + fs = ngx_stream_lua_create_fake_session(fc); + if (fs == NULL) { + goto failed; + } + + fs->main_conf = s->main_conf; + fs->srv_conf = s->srv_conf; + + r = ngx_stream_lua_create_fake_request(fs); + if (r == NULL) { + goto failed; + } + + fc->log->file = c->log->file; + fc->log->log_level = c->log->log_level; + fc->ssl = c->ssl; + + cscf = ngx_stream_get_module_srv_conf(fs, ngx_stream_core_module); + +#if defined(nginx_version) && nginx_version >= 1027001 + ngx_set_connection_log(fc, cscf->error_log); + +#else +#error "stream proxy_ssl_verify_by_lua only supports nginx >= 1.27.1" +#endif + + if (cctx == NULL) { + cctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_lua_ssl_ctx_t)); + if (cctx == NULL) { + goto failed; /* error */ + } + + cctx->ctx_ref = LUA_NOREF; + } + + cctx->exit_code = 1; /* successful by default */ + cctx->x509_store = x509_store; + cctx->connection = c; + cctx->request = r; + cctx->entered_proxy_ssl_verify_handler = 1; + cctx->done = 0; + + dd("setting cctx"); + + if (SSL_set_ex_data(c->ssl->connection, ngx_stream_lua_ssl_ctx_index, + cctx) == 0) + { + ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed"); + goto failed; + } + + lscf = ngx_stream_lua_get_module_srv_conf(r, ngx_stream_lua_module); + + if (lscf->ups.upstream_skip_openssl_default_verify == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "proxy_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_stream_lua_get_lua_vm(r, NULL); + + c->log->action = "loading proxy ssl verify by lua"; + + if (lscf->ups.proxy_ssl_verify_handler == NULL) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "no proxy_ssl_verify_by_lua* defined in " + "server %s:%ui", &cscf->file_name, &cscf->line); + + goto failed; + } + + rc = lscf->ups.proxy_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_STREAM, c->log, 0, + "proxy_ssl_verify_by_lua: handler return value: %i, " + "cert verify callback exit code: %d", rc, cctx->exit_code); + + c->log->action = "proxy pass 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_stream_lua_proxy_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_stream_lua_proxy_ssl_verify_aborted; + + return SSL_set_retry_verify(ssl_conn); + +#if 1 +failed: + + if (r && r->pool) { + ngx_stream_lua_free_fake_request(r); + } + + if (fc) { + ngx_stream_lua_close_fake_connection(fc); + } + + return 0; /* verify failure or error */ +#endif +} + + +static void +ngx_stream_lua_proxy_ssl_verify_done(void *data) +{ + ngx_connection_t *c; + ngx_stream_lua_ssl_ctx_t *cctx = data; + + dd("lua proxy ssl verify done"); + + if (cctx->aborted) { + return; + } + + ngx_stream_lua_assert(cctx->done == 0); + + cctx->done = 1; + + if (cctx->cleanup) { + *cctx->cleanup = NULL; + } + + c = cctx->connection; + + c->log->action = "proxy pass SSL handshaking"; + + ngx_post_event(c->write, &ngx_posted_events); +} + + +static void +ngx_stream_lua_proxy_ssl_verify_aborted(void *data) +{ + ngx_stream_lua_ssl_ctx_t *cctx = data; + + dd("lua proxy ssl verify aborted"); + + if (cctx->done) { + /* completed successfully already */ + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, cctx->connection->log, 0, + "proxy_ssl_verify_by_lua: cert verify callback aborted"); + + cctx->aborted = 1; + cctx->request->connection->ssl = NULL; + + ngx_stream_lua_finalize_fake_request(cctx->request, NGX_ERROR); +} + + +static u_char * +ngx_stream_lua_log_proxy_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: proxy_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_stream_lua_proxy_ssl_verify_by_chunk(lua_State *L, + ngx_stream_lua_request_t *r) +{ + int co_ref; + ngx_int_t rc; + lua_State *co; + ngx_stream_lua_ctx_t *ctx; + ngx_stream_lua_cleanup_t *cln; + + ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module); + + if (ctx == NULL) { + ctx = ngx_stream_lua_create_ctx(r->session); + if (ctx == NULL) { + rc = NGX_ERROR; + ngx_stream_lua_finalize_request(r, rc); + return rc; + } + + } else { + dd("reset ctx"); + ngx_stream_lua_reset_ctx(r, L, ctx); + } + + ctx->entered_content_phase = 1; + + /* {{{ new coroutine to handle request */ + co = ngx_stream_lua_new_thread(r, L, &co_ref); + + if (co == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "stream failed to create new" + " coroutine to handle request"); + + rc = NGX_ERROR; + ngx_stream_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_stream_lua_get_globals_table(co); + lua_setfenv(co, -2); +#endif + + /* save nginx request in coroutine globals table */ + ngx_stream_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_stream_lua_attach_co_ctx_to_L(co, ctx->cur_co_ctx); + + /* register request cleanup hooks */ + if (ctx->cleanup == NULL) { + cln = ngx_stream_lua_cleanup_add(r, 0); + if (cln == NULL) { + rc = NGX_ERROR; + ngx_stream_lua_finalize_request(r, rc); + return rc; + } + + cln->handler = ngx_stream_lua_request_cleanup_handler; + cln->data = ctx; + ctx->cleanup = &cln->handler; + } + + ctx->context = NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY; + + rc = ngx_stream_lua_run_thread(L, r, ctx, 0); + + if (rc == NGX_ERROR || rc >= NGX_OK) { + /* do nothing */ + + } else if (rc == NGX_AGAIN) { + rc = ngx_stream_lua_content_run_posted_threads(L, r, ctx, 0); + + } else if (rc == NGX_DONE) { + rc = ngx_stream_lua_content_run_posted_threads(L, r, ctx, 1); + + } else { + rc = NGX_OK; + } + + ngx_stream_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_stream_lua_ffi_ssl_set_verify_result(ngx_stream_lua_request_t *r, + int verify_result, char **err) +{ +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_stream_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; + } + + dd("get cctx session"); + + c = ngx_ssl_get_connection(ssl_conn); + + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + 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 +} + + +int +ngx_stream_lua_ffi_ssl_get_verify_result(ngx_stream_lua_request_t *r, + char **err) +{ +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_stream_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; + } + + dd("get cctx session"); + + c = ngx_ssl_get_connection(ssl_conn); + + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + 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 +} + + +void +ngx_stream_lua_ffi_ssl_free_verify_cert(void *cdata) +{ + X509 *cert = cdata; + + X509_free(cert); +} + + +void * +ngx_stream_lua_ffi_ssl_get_verify_cert(ngx_stream_lua_request_t *r, char **err) +{ +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_stream_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; + } + + dd("get cctx session"); + + c = ngx_ssl_get_connection(ssl_conn); + + cctx = ngx_stream_lua_ssl_get_ctx(c->ssl->connection); + 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 result failed"; + return NULL; + } + + return x509; +#else + *err = "OpenSSL too old to support this function"; + + return NULL; +#endif +} + +#endif /* NGX_STREAM_SSL */ diff --git a/src/ngx_stream_lua_proxy_ssl_verifyby.h b/src/ngx_stream_lua_proxy_ssl_verifyby.h new file mode 100644 index 00000000..4b6f516f --- /dev/null +++ b/src/ngx_stream_lua_proxy_ssl_verifyby.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) Yichun Zhang (agentzh) + */ + +#ifndef _NGX_STREAM_LUA_PROXY_SSL_VERIFYBY_H_INCLUDED_ +#define _NGX_STREAM_LUA_PROXY_SSL_VERIFYBY_H_INCLUDED_ + + +#include "ngx_stream_lua_common.h" + + +#if (NGX_STREAM_SSL) + +/* do not introduce ngx_stream_proxy_module to pollute ngx_stream_lua_module.c */ +extern ngx_module_t ngx_stream_proxy_module; + +ngx_int_t ngx_stream_lua_proxy_ssl_verify_handler_inline( + ngx_stream_lua_request_t *r, ngx_stream_lua_srv_conf_t *lscf, lua_State *L); + +ngx_int_t ngx_stream_lua_proxy_ssl_verify_handler_file( + ngx_stream_lua_request_t *r, ngx_stream_lua_srv_conf_t *lscf, lua_State *L); + +char *ngx_stream_lua_proxy_ssl_verify_by_lua_block(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + +char *ngx_stream_lua_proxy_ssl_verify_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +int ngx_stream_lua_proxy_ssl_verify_handler(X509_STORE_CTX *x509_store, + void *arg); + +ngx_int_t ngx_stream_lua_proxy_ssl_verify_set_callback(ngx_conf_t *cf); + +#endif /* NGX_STREAM_SSL */ +#endif /* _NGX_STREAM_LUA_PROXY_SSL_VERIFYBY_H_INCLUDED_ */ + +/* vi:set ft=c ts=4 sw=4 et fdm=marker: */ diff --git a/src/ngx_stream_lua_ssl.h b/src/ngx_stream_lua_ssl.h index 352b709e..336aa901 100644 --- a/src/ngx_stream_lua_ssl.h +++ b/src/ngx_stream_lua_ssl.h @@ -32,9 +32,12 @@ typedef struct { ngx_str_t session_id; + X509_STORE_CTX *x509_store; + int exit_code; /* exit code for openssl's set_client_hello_cb or - set_cert_cb callback */ + set_cert_cb callback or + SSL_CTX_set_cert_verify_callback */ int ctx_ref; /* reference to anchor request ctx data in lua @@ -46,6 +49,7 @@ typedef struct { unsigned entered_client_hello_handler:1; unsigned entered_cert_handler:1; unsigned entered_sess_fetch_handler:1; + unsigned entered_proxy_ssl_verify_handler:1; } ngx_stream_lua_ssl_ctx_t; diff --git a/src/ngx_stream_lua_uthread.c b/src/ngx_stream_lua_uthread.c index 8d906de5..f66bb532 100644 --- a/src/ngx_stream_lua_uthread.c +++ b/src/ngx_stream_lua_uthread.c @@ -235,6 +235,7 @@ ngx_stream_lua_uthread_kill(lua_State *L) | NGX_STREAM_LUA_CONTEXT_PREREAD | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO | NGX_STREAM_LUA_CONTEXT_SSL_CERT + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY | NGX_STREAM_LUA_CONTEXT_TIMER); coctx = ctx->cur_co_ctx; diff --git a/src/ngx_stream_lua_util.h b/src/ngx_stream_lua_util.h index 9c3a14eb..bde8716b 100644 --- a/src/ngx_stream_lua_util.h +++ b/src/ngx_stream_lua_util.h @@ -77,7 +77,8 @@ extern char ngx_stream_lua_headers_metatable_key; | NGX_STREAM_LUA_CONTEXT_CONTENT \ | NGX_STREAM_LUA_CONTEXT_TIMER \ | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO \ - | NGX_STREAM_LUA_CONTEXT_SSL_CERT) + | NGX_STREAM_LUA_CONTEXT_SSL_CERT \ + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY) #define ngx_stream_lua_context_name(c) \ @@ -90,6 +91,8 @@ extern char ngx_stream_lua_headers_metatable_key; : (c) == NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO ? \ "ssl_client_hello_by_lua*" \ : (c) == NGX_STREAM_LUA_CONTEXT_SSL_CERT ? "ssl_certificate_by_lua*" \ + : (c) == NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY ? \ + "proxy_ssl_verify_by_lua*" \ : "(unknown)") diff --git a/src/ngx_stream_lua_variable.c b/src/ngx_stream_lua_variable.c index 47eadc8d..d87ec018 100644 --- a/src/ngx_stream_lua_variable.c +++ b/src/ngx_stream_lua_variable.c @@ -49,7 +49,8 @@ ngx_stream_lua_ffi_var_get(ngx_stream_lua_request_t *r, u_char *name_data, if ((r)->connection->fd == (ngx_socket_t) -1) { ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module); if (ctx->context & (NGX_STREAM_LUA_CONTEXT_SSL_CERT - | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO)) + | NGX_STREAM_LUA_CONTEXT_SSL_CLIENT_HELLO + | NGX_STREAM_LUA_CONTEXT_PROXY_SSL_VERIFY)) { cctx = ngx_stream_lua_ssl_get_ctx(r->connection->ssl->connection); session = cctx->connection->data; diff --git a/t/164-proxy-ssl-verify-by.t b/t/164-proxy-ssl-verify-by.t new file mode 100644 index 00000000..243800c3 --- /dev/null +++ b/t/164-proxy-ssl-verify-by.t @@ -0,0 +1,889 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua::Stream; +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 (0\S*|1\.0\S*|1\.1\.0\S*)/) { + plan(skip_all => "too old OpenSSL, need 1.1.1, was $1"); +} else { + plan tests => repeat_each() * (blocks() * 6 + 5); +} + +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); +$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; + +#log_level 'warn'; +log_level 'debug'; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ + +=== TEST 1: without proxy_ssl on +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } +--- error_log +proxy_ssl_verify_by_lua* should be used with proxy_ssl directive +--- no_error_log +[error] +[alert] +--- must_die + + + +=== TEST 2: proxy_ssl_verify_by_lua in stream {} block +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + return 'it works!\n'; + } + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "hello world") + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; +--- error_log +"proxy_ssl_verify_by_lua_block" directive is not allowed here +--- no_error_log +[error] +[alert] +--- must_die + + + +=== TEST 3: simple logging +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "proxy_ssl_verify_by_lua is running!") + } +--- stream_response +it works! +--- error_log +proxy_ssl_verify_by_lua is running! +proxy_ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1 +--- no_error_log +[error] +[alert] + + + +=== TEST 4: sleep +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_ssl_verify_by_lua_block { + local begin = ngx.now() + ngx.sleep(0.1) + print("elapsed in proxy ssl verify by lua: ", ngx.now() - begin) + } +--- stream_response +it works! +--- error_log eval +qr/elapsed in proxy ssl verify by lua: 0.(?:09|1\d)\d+,/, +--- no_error_log +[error] +[alert] + + + +=== TEST 5: timer +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_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 + } +--- stream_response +it works! +--- error_log +my timer run! +proxy_ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1 +--- no_error_log +[error] +[alert] + + + +=== TEST 6: cosocket +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_ssl_verify_by_lua_block { + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_MEMCACHED_PORT) + if not ok then + ngx.log(ngx.ERR, "failed to connect to memc: ", err) + return + end + + local bytes, err = sock:send("flush_all\r\n") + if not bytes then + ngx.log(ngx.ERR, "failed to send flush_all command: ", err) + return + end + + local res, err = sock:receive() + if not res then + ngx.log(ngx.ERR, "failed to receive memc reply: ", err) + return + end + + print("received memc reply: ", res) + } +--- stream_response +it works! +--- error_log +received memc reply: OK +--- no_error_log +[error] +[alert] +--- SKIP + + + +=== TEST 7: ngx.exit(0) - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_ssl_verify_by_lua_block { + ngx.exit(0) + ngx.log(ngx.ERR, "should never reached here...") + } +--- stream_response +it works! +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 8: ngx.exit(ngx.ERROR) - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.exit(ngx.ERROR) + ngx.log(ngx.ERR, "should never reached here...") + } +--- error_log eval +[ +'lua exit with code -1', +'proxy_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 +[error] +[alert] +[emerg] + + + +=== TEST 9: ngx.exit(0) - yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_ssl_verify_by_lua_block { + ngx.sleep(0.001) + ngx.exit(0) + + ngx.log(ngx.ERR, "should never reached here...") + } +--- stream_response +it works! +--- error_log +lua exit with code 0 +--- no_error_log +should never reached here +[error] +[alert] +[emerg] + + + +=== TEST 10: ngx.exit(ngx.ERROR) - yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.sleep(0.001) + ngx.exit(ngx.ERROR) + + ngx.log(ngx.ERR, "should never reached here...") + } +--- error_log eval +[ +'lua exit with code -1', +'proxy_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 +[error] +[alert] +[emerg] + + + +=== TEST 11: lua exception - no yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } +--- error_log eval +[ +'runtime error: proxy_ssl_verify_by_lua:2: bad bad bad', +'proxy_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 12: lua exception - yield +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.sleep(0.001) + error("bad bad bad") + ngx.log(ngx.ERR, "should never reached here...") + } +--- error_log eval +[ +'runtime error: proxy_ssl_verify_by_lua:3: bad bad bad', +'proxy_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 13: get phase +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_ssl_verify_by_lua_block { + print("get_phase: ", ngx.get_phase()) + } +--- stream_response +it works! +--- error_log +get_phase: proxy_ssl_verify +--- no_error_log +[error] +[alert] + + + +=== TEST 14: simple logging (by_lua_file) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_file html/a.lua; +--- stream_response +it works! +--- user_files +>>> a.lua +print("proxy ssl verify by lua is running!") + +--- error_log +a.lua:1: proxy ssl verify by lua is running! +--- no_error_log +[error] +[alert] + + + +=== TEST 15: coroutine API +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_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 + } +--- stream_response +it works! +--- 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 +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_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) + } +--- stream_response +it works! +--- 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) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_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 + } +--- stream_response +it works! +--- 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 +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_ssl_verify_by_lua_block { + ngx.exit(ngx.OK) + ngx.log(ngx.ERR, "should never reached here...") + } +--- stream_response +it works! +--- error_log eval +[ +'proxy_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: proxy_ssl_verify_by_lua* without yield API (simple logic) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + + proxy_ssl_verify_by_lua_block { + print("proxy ssl verify: simple test start") + + -- Simple calculations without yield + local sum = 0 + for i = 1, 10 do + sum = sum + i + end + + print("proxy ssl verify: calculated sum: ", sum) + + -- String operations + local str = "hello" + str = str .. " world" + print("proxy 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("proxy ssl verify: table sum: ", count) + + print("proxy ssl verify: simple test done") + } +--- stream_response +it works! +--- grep_error_log eval: qr/(proxy ssl verify: simple test start|proxy ssl verify: calculated sum: 55|proxy ssl verify: concatenated string: hello world|proxy ssl verify: table sum: 6|proxy ssl verify: simple test done)/ +--- grep_error_log_out +proxy ssl verify: simple test start +proxy ssl verify: calculated sum: 55 +proxy ssl verify: concatenated string: hello world +proxy ssl verify: table sum: 6 +proxy ssl verify: simple test done + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 20: lua_upstream_skip_openssl_default_verify default off +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + proxy_ssl_conf_command VerifyMode Peer; + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "proxy ssl verify by lua is running!") + } +--- stream_response +it works! +--- error_log eval +[ +'proxy_ssl_verify_by_lua: openssl default verify', +'proxy_ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +] +--- no_error_log +[error] +[alert] + + + +=== TEST 21: lua_upstream_skip_openssl_default_verify on +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate ../../cert/mtls_server.crt; + ssl_certificate_key ../../cert/mtls_server.key; + + return 'it works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_verify on; + 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; + proxy_ssl_conf_command VerifyMode Peer; + + lua_upstream_skip_openssl_default_verify on; + + proxy_ssl_verify_by_lua_block { + ngx.log(ngx.INFO, "proxy ssl verify by lua is running!") + } +--- stream_response +it works! +--- error_log eval +[ +'proxy ssl verify by lua is running!', +'proxy_ssl_verify_by_lua: handler return value: 0, cert verify callback exit code: 1', +qr/\[debug\] .*? SSL_do_handshake: 1/, +] +--- no_error_log +proxy_ssl_verify_by_lua: openssl default verify +[error] +[alert] From 6e8327576e145aeb1231fddc468cb2665bd3e6c7 Mon Sep 17 00:00:00 2001 From: willmafh Date: Mon, 21 Jul 2025 20:44:05 +0800 Subject: [PATCH 2/7] doc: add lua_upstream_skip_openssl_default_verify description --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d8cfdfb3..048b74de 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,7 @@ behavior. * [lua_ssl_trusted_certificate](https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate) * [lua_ssl_verify_depth](https://github.com/openresty/lua-nginx-module#lua_ssl_verify_depth) * [lua_ssl_conf_command](https://github.com/openresty/lua-nginx-module#lua_ssl_conf_command) +* [lua_upstream_skip_openssl_default_verify](https://github.com/openresty/lua-nginx-module#lua_upstream_skip_openssl_default_verify) * [lua_check_client_abort](https://github.com/openresty/lua-nginx-module#lua_check_client_abort) * [lua_max_pending_timers](https://github.com/openresty/lua-nginx-module#lua_max_pending_timers) * [lua_max_running_timers](https://github.com/openresty/lua-nginx-module#lua_max_running_timers) From 020f2da1ff0934cda90455678691bc3b64b1b8c9 Mon Sep 17 00:00:00 2001 From: willmafh Date: Mon, 21 Jul 2025 18:13:02 +0800 Subject: [PATCH 3/7] chore: typo fixes. --- src/ngx_stream_lua_ssl_client_helloby.c | 6 +++--- t/162-ssl-client-hello-by.t | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ngx_stream_lua_ssl_client_helloby.c b/src/ngx_stream_lua_ssl_client_helloby.c index 57b5913f..f9cafaec 100644 --- a/src/ngx_stream_lua_ssl_client_helloby.c +++ b/src/ngx_stream_lua_ssl_client_helloby.c @@ -207,7 +207,7 @@ ngx_stream_lua_ssl_client_hello_handler(ngx_ssl_conn_t *ssl_conn, if (cctx->done) { ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, - "stream lua_client_hello_by_lua:" + "stream ssl_client_hello_by_lua:" " client hello cb exit code: %d", cctx->exit_code); @@ -311,7 +311,7 @@ ngx_stream_lua_ssl_client_hello_handler(ngx_ssl_conn_t *ssl_conn, } ngx_log_debug2(NGX_LOG_DEBUG_STREAM, c->log, 0, - "stream lua_client_hello_by_lua:" + "stream ssl_client_hello_by_lua:" " handler return value: %i, " "client hello cb exit code: %d", rc, cctx->exit_code); @@ -400,7 +400,7 @@ ngx_stream_lua_ssl_client_hello_aborted(void *data) } ngx_log_debug0(NGX_LOG_DEBUG_STREAM, cctx->connection->log, 0, - "stream lua_client_hello_by_lua: client hello cb aborted"); + "stream ssl_client_hello_by_lua: client hello cb aborted"); cctx->aborted = 1; cctx->request->connection->ssl = NULL; diff --git a/t/162-ssl-client-hello-by.t b/t/162-ssl-client-hello-by.t index 121cdf50..1ad43e88 100644 --- a/t/162-ssl-client-hello-by.t +++ b/t/162-ssl-client-hello-by.t @@ -448,7 +448,7 @@ failed to do SSL handshake: handshake failed --- error_log eval [ -'lua_client_hello_by_lua: handler return value: -1, client hello cb exit code: 0', +'ssl_client_hello_by_lua: handler return value: -1, client hello cb exit code: 0', qr/\[info\] .*? SSL_do_handshake\(\) failed .*?callback failed/, 'lua exit with code -1', ] @@ -567,7 +567,7 @@ failed to do SSL handshake: handshake failed --- error_log eval [ -'lua_client_hello_by_lua: client hello cb exit code: 0', +'ssl_client_hello_by_lua: client hello cb exit code: 0', qr/\[info\] .*? SSL_do_handshake\(\) failed .*?callback failed/, 'lua exit with code -1', ] @@ -627,7 +627,7 @@ failed to do SSL handshake: handshake failed --- error_log eval [ 'runtime error: ssl_client_hello_by_lua:2: bad bad bad', -'lua_client_hello_by_lua: handler return value: 500, client hello cb exit code: 0', +'ssl_client_hello_by_lua: handler return value: 500, client hello cb exit code: 0', qr/\[info\] .*? SSL_do_handshake\(\) failed .*?callback failed/, qr/context: ssl_client_hello_by_lua\*, client: \d+\.\d+\.\d+\.\d+, server: \d+\.\d+\.\d+\.\d+:\d+/, ] @@ -688,7 +688,7 @@ failed to do SSL handshake: handshake failed --- error_log eval [ 'runtime error: ssl_client_hello_by_lua:3: bad bad bad', -'lua_client_hello_by_lua: client hello cb exit code: 0', +'ssl_client_hello_by_lua: client hello cb exit code: 0', qr/\[info\] .*? SSL_do_handshake\(\) failed .*?callback failed/, ] From 825bc31a5f311be178f884074e64c9ea7a9298db Mon Sep 17 00:00:00 2001 From: Ri Shen Chen Date: Mon, 21 Jul 2025 18:14:02 +0800 Subject: [PATCH 4/7] feature: ngx_stream_lua_ffi_req_shared_ssl_ciphers(). --- src/ngx_stream_lua_ssl_certby.c | 75 ++++++++++++ t/140-ssl-c-api.t | 195 ++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) diff --git a/src/ngx_stream_lua_ssl_certby.c b/src/ngx_stream_lua_ssl_certby.c index 19a554d8..7b7d20d2 100644 --- a/src/ngx_stream_lua_ssl_certby.c +++ b/src/ngx_stream_lua_ssl_certby.c @@ -44,6 +44,7 @@ static u_char *ngx_stream_lua_log_ssl_cert_error(ngx_log_t *log, u_char *buf, size_t len); static ngx_int_t ngx_stream_lua_ssl_cert_by_chunk(lua_State *L, ngx_stream_lua_request_t *r); +static int ngx_stream_lua_is_grease_cipher(uint16_t cipher_id); ngx_int_t @@ -986,6 +987,80 @@ ngx_stream_lua_ffi_ssl_raw_client_addr(ngx_stream_lua_request_t *r, char **addr, } +static int +ngx_stream_lua_is_grease_cipher(uint16_t cipher_id) +{ + /* GREASE values follow pattern: 0x?A?A where ? can be any hex digit */ + /* and both ? must be the same */ + /* Check if both bytes follow ?A pattern and high nibbles match */ + return (cipher_id & 0x0F0F) == 0x0A0A; +} + + +int +ngx_stream_lua_ffi_req_shared_ssl_ciphers(ngx_stream_lua_request_t *r, + uint16_t *ciphers, uint16_t *nciphers, int filter_grease, char **err) +{ +#ifdef OPENSSL_IS_BORINGSSL + + *err = "BoringSSL is not supported for SSL cipher operations"; + return NGX_ERROR; + +#else + ngx_ssl_conn_t *ssl_conn; + STACK_OF(SSL_CIPHER) *sk, *ck; + int sn, cn, i, n; + uint16_t cipher; + + if (r == NULL || 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; + } + + sk = SSL_get1_supported_ciphers(ssl_conn); + ck = SSL_get_client_ciphers(ssl_conn); + sn = sk_SSL_CIPHER_num(sk); + cn = sk_SSL_CIPHER_num(ck); + + if (sn > *nciphers) { + *err = "buffer too small"; + *nciphers = 0; + sk_SSL_CIPHER_free(sk); + return NGX_ERROR; + } + + for (*nciphers = 0, i = 0; i < sn; i++) { + cipher = SSL_CIPHER_get_protocol_id(sk_SSL_CIPHER_value(sk, i)); + + /* Skip GREASE ciphers if filtering is enabled */ + if (filter_grease && ngx_stream_lua_is_grease_cipher(cipher)) { + continue; + } + + for (n = 0; n < cn; n++) { + if (SSL_CIPHER_get_protocol_id(sk_SSL_CIPHER_value(ck, n)) + == cipher) + { + ciphers[(*nciphers)++] = cipher; + break; + } + } + } + + sk_SSL_CIPHER_free(sk); + + return NGX_OK; +#endif + +} + + int ngx_stream_lua_ffi_cert_pem_to_der(const u_char *pem, size_t pem_len, u_char *der, char **err) diff --git a/t/140-ssl-c-api.t b/t/140-ssl-c-api.t index e52202cf..3401dace 100644 --- a/t/140-ssl-c-api.t +++ b/t/140-ssl-c-api.t @@ -72,6 +72,9 @@ ffi.cdef[[ int ngx_stream_lua_ffi_ssl_client_random(ngx_stream_lua_request_t *r, unsigned char *out, size_t *outlen, char **err); + int ngx_stream_lua_ffi_req_shared_ssl_ciphers(void *r, uint16_t *ciphers, + uint16_t *nciphers, int filter_grease, char **err); + ]] _EOC_ } @@ -1374,3 +1377,195 @@ SUCCESS --- no_error_log [error] [alert] + + + +=== TEST 14: Get supported ciphers +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate_by_lua_block { + collectgarbage() + require "defines" + local ffi = require "ffi" + local cjson = require "cjson.safe" + + local MAX_CIPHERS = 64 + local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS) + local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS) + local err = ffi.new("char*[1]") + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + local ret = ffi.C.ngx_stream_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err) + + if ret ~= 0 then + ngx.log(ngx.ERR, "error getting ciphers: ", ffi.string(err[0])) + else + local res = {} + for i = 0, nciphers[0] - 1 do + local cipher_id = string.format("%04x", ciphers[i]) + table.insert(res, cipher_id) + end + ngx.log(ngx.INFO, "supported ciphers: ", cjson.encode(res)) + end + } + + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384; + + return 'cipher test works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_session_reuse off; + proxy_ssl_protocols TLSv1.2; + proxy_ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256; +--- stream_response +cipher test works! +--- error_log +supported ciphers: ["c02f","c02b"] +--- no_error_log +[error] +[alert] + + + +=== TEST 15: Get supported ciphers with GREASE filtering +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + + ssl_certificate_by_lua_block { + collectgarbage() + require "defines" + local ffi = require "ffi" + local cjson = require "cjson.safe" + local MAX_CIPHERS = 64 + local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS) + local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS) + local err = ffi.new("char*[1]") + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + + -- Test without GREASE filtering + local ret = ffi.C.ngx_stream_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err) + local res_no_filter = {} + if ret == 0 then + for i = 0, nciphers[0] - 1 do + local cipher_id = string.format("%04x", ciphers[i]) + table.insert(res_no_filter, cipher_id) + end + end + + -- Reset buffer for next test + nciphers[0] = MAX_CIPHERS + + -- Test with GREASE filtering + local ret = ffi.C.ngx_stream_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 1, err) + local res_with_filter = {} + if ret == 0 then + for i = 0, nciphers[0] - 1 do + local cipher_id = string.format("%04x", ciphers[i]) + table.insert(res_with_filter, cipher_id) + end + end + + ngx.log(ngx.INFO, "without_filter: ", cjson.encode(res_no_filter)) + ngx.log(ngx.INFO, "with_filter: ", cjson.encode(res_with_filter)) + } + + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + ssl_protocols TLSv1.2; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384; + + return 'grease filter test works!\n'; + } +--- stream_server_config + proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock; + proxy_ssl on; + proxy_ssl_certificate ../../cert/mtls_client.crt; + proxy_ssl_certificate_key ../../cert/mtls_client.key; + proxy_ssl_session_reuse off; + +--- stream_response +grease filter test works! +--- error_log +without_filter: +with_filter: +--- no_error_log +[error] +[alert] + + + +=== TEST 16: SSL cipher API error handling (no SSL) +--- stream_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock; + + content_by_lua_block { + require "defines" + local ffi = require "ffi" + + local MAX_CIPHERS = 64 + local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS) + local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS) + local err = ffi.new("char*[1]") + + local r = require "resty.core.base" .get_request() + if not r then + ngx.log(ngx.ERR, "no request found") + return + end + local ret = ffi.C.ngx_stream_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err) + + if ret ~= 0 then + ngx.say("error: ", ffi.string(err[0])) + else + ngx.say("unexpected success") + end + } + } +--- stream_server_config + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.log(ngx.ERR, "failed to connect: ", err) + return + end + + local line, err = sock:receive() + if not line then + ngx.log(ngx.ERR, "failed to receive: ", err) + return + end + + ngx.say("received: ", line) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + } + +--- stream_response +received: error: bad request +close: 1 nil + +--- no_error_log +[error] +[alert] From c4c4328ff3d674f74300c7db92e218b774e6ba9a Mon Sep 17 00:00:00 2001 From: willmafh Date: Mon, 21 Jul 2025 18:16:37 +0800 Subject: [PATCH 5/7] style: fixed coding style. --- src/ngx_stream_lua_common.h | 34 ++++++++++------------------------ src/ngx_stream_lua_module.c | 21 +++++++++++++++------ 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/ngx_stream_lua_common.h b/src/ngx_stream_lua_common.h index f9681af9..3f025226 100644 --- a/src/ngx_stream_lua_common.h +++ b/src/ngx_stream_lua_common.h @@ -284,33 +284,19 @@ struct ngx_stream_lua_srv_conf_s { code cache */ ngx_stream_lua_handler_pt preread_handler; + u_char *preread_chunkname; + ngx_stream_complex_value_t preread_src; + u_char *preread_src_key; ngx_stream_lua_handler_pt content_handler; - ngx_stream_lua_handler_pt log_handler; - - u_char *preread_chunkname; - ngx_stream_complex_value_t preread_src; /* access_by_lua - inline script/script - file path */ - - u_char *preread_src_key; /* cached key for access_src */ - - u_char *content_chunkname; + u_char *content_chunkname; + ngx_stream_complex_value_t content_src; + u_char *content_src_key; - ngx_stream_complex_value_t content_src; - /* content_by_lua - * inline script/script - * file path */ - - u_char *content_src_key; /* cached key for content_src */ - - u_char *log_chunkname; - ngx_stream_complex_value_t log_src; - /* log_by_lua inline script/script - * file path */ - - u_char *log_src_key; - /* cached key for log_src */ + ngx_stream_lua_handler_pt log_handler; + u_char *log_chunkname; + ngx_stream_complex_value_t log_src; + u_char *log_src_key; ngx_msec_t keepalive_timeout; diff --git a/src/ngx_stream_lua_module.c b/src/ngx_stream_lua_module.c index 89ceb5d2..b0a20355 100644 --- a/src/ngx_stream_lua_module.c +++ b/src/ngx_stream_lua_module.c @@ -741,6 +741,8 @@ ngx_stream_lua_create_main_conf(ngx_conf_t *cf) * lmcf->shm_zones = NULL; * lmcf->init_handler = NULL; * lmcf->init_src = { 0, NULL }; + * lmcf->init_worker_handler = NULL; + * lmcf->init_worker_src = { 0, NULL }; * lmcf->shm_zones_inited = 0; * lmcf->shdict_zones = NULL; * lmcf->preload_hooks = NULL; @@ -844,13 +846,20 @@ ngx_stream_lua_create_srv_conf(ngx_conf_t *cf) * lscf->srv.ssl_cert_src = { 0, NULL }; * lscf->srv.ssl_cert_src_key = NULL; * - * lscf->srv.ssl_session_store_handler = NULL; - * lscf->srv.ssl_session_store_src = { 0, NULL }; - * lscf->srv.ssl_session_store_src_key = NULL; + * lscf->preread_handler = NULL; + * lscf->preread_chunkname = NULL; + * lscf->preread_src = {{ 0, NULL }, NULL, NULL, NULL}; + * lscf->preread_src_key = NULL; * - * lscf->srv.ssl_session_fetch_handler = NULL; - * lscf->srv.ssl_session_fetch_src = { 0, NULL }; - * lscf->srv.ssl_session_fetch_src_key = NULL; + * lscf->content_handler = NULL; + * lscf->content_chunkname = NULL; + * lscf->content_src = {{ 0, NULL }, NULL, NULL, NULL}; + * lscf->content_src_key = NULL; + + * lscf->log_handler = NULL; + * lscf->log_chunkname = NULL; + * lscf->log_src = {{ 0, NULL }, NULL, NULL, NULL}; + * lscf->log_src_key = NULL; * * lscf->balancer.handler = NULL; * lscf->balancer.src = { 0, NULL }; From 3cedd2d339309eaaaef37ec2195968d728cbea1b Mon Sep 17 00:00:00 2001 From: Fahnenfluchtige <59973652+Fahnenfluchtige@users.noreply.github.com> Date: Mon, 21 Jul 2025 13:18:53 +0300 Subject: [PATCH 6/7] optimize: checked r before using it. --- src/ngx_stream_lua_util.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ngx_stream_lua_util.c b/src/ngx_stream_lua_util.c index 2bf96f8e..87a94a85 100644 --- a/src/ngx_stream_lua_util.c +++ b/src/ngx_stream_lua_util.c @@ -1935,6 +1935,10 @@ ngx_stream_lua_req_socket(lua_State *L) r = ngx_stream_lua_get_req(L); + if (r == NULL) { + return luaL_error(L, "no request found"); + } + ctx = ngx_stream_lua_get_module_ctx(r, ngx_stream_lua_module); if (ctx == NULL) { return luaL_error(L, "no ctx found"); From d3d3d867e0585118dccadde52a592c1086837f7c Mon Sep 17 00:00:00 2001 From: willmafh Date: Tue, 1 Jul 2025 16:27:00 +0800 Subject: [PATCH 7/7] feature: add lua_ssl_key_log directive to log client connection SSL keys in the tcpsock:sslhandshake method. Keys are logged in the SSLKEYLOGFILE format compatible with Wireshark. --- README.md | 1 + src/ngx_stream_lua_common.h | 1 + src/ngx_stream_lua_module.c | 114 ++++++++++++++++++++++++++++++++++++ src/ngx_stream_lua_ssl.c | 14 +++++ src/ngx_stream_lua_ssl.h | 8 +++ t/129-ssl-socket.t | 107 ++++++++++++++++++++++++++++++++- 6 files changed, 244 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 048b74de..ea99e310 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ behavior. * [lua_ssl_certificate_key](https://github.com/openresty/lua-nginx-module#lua_ssl_certificate_key) * [lua_ssl_trusted_certificate](https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate) * [lua_ssl_verify_depth](https://github.com/openresty/lua-nginx-module#lua_ssl_verify_depth) +* [lua_ssl_key_log](https://github.com/openresty/lua-nginx-module#lua_ssl_key_log) * [lua_ssl_conf_command](https://github.com/openresty/lua-nginx-module#lua_ssl_conf_command) * [lua_upstream_skip_openssl_default_verify](https://github.com/openresty/lua-nginx-module#lua_upstream_skip_openssl_default_verify) * [lua_check_client_abort](https://github.com/openresty/lua-nginx-module#lua_check_client_abort) diff --git a/src/ngx_stream_lua_common.h b/src/ngx_stream_lua_common.h index 3f025226..8951d01e 100644 --- a/src/ngx_stream_lua_common.h +++ b/src/ngx_stream_lua_common.h @@ -257,6 +257,7 @@ struct ngx_stream_lua_srv_conf_s { ngx_uint_t ssl_verify_depth; ngx_str_t ssl_trusted_certificate; ngx_str_t ssl_crl; + ngx_str_t ssl_key_log; #if (nginx_version >= 1019004) ngx_array_t *ssl_conf_commands; #endif diff --git a/src/ngx_stream_lua_module.c b/src/ngx_stream_lua_module.c index b0a20355..709eb290 100644 --- a/src/ngx_stream_lua_module.c +++ b/src/ngx_stream_lua_module.c @@ -50,6 +50,11 @@ static char *ngx_stream_lua_lowat_check(ngx_conf_t *cf, void *post, void *data); #if (NGX_STREAM_SSL) static ngx_int_t ngx_stream_lua_set_ssl(ngx_conf_t *cf, ngx_stream_lua_loc_conf_t *llcf); +static void key_log_callback(const ngx_ssl_conn_t *ssl_conn, + const char *line); +static void ngx_stream_lua_ssl_cleanup_key_log(void *data); +static ngx_int_t ngx_stream_lua_ssl_key_log(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_str_t *file); #if (nginx_version >= 1019004) static char *ngx_stream_lua_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); @@ -476,6 +481,13 @@ static ngx_command_t ngx_stream_lua_cmds[] = { offsetof(ngx_stream_lua_srv_conf_t, ssl_crl), NULL }, + { ngx_string("lua_ssl_key_log"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_lua_srv_conf_t, ssl_key_log), + NULL }, + #if (nginx_version >= 1019004) { ngx_string("lua_ssl_conf_command"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, @@ -1012,6 +1024,7 @@ ngx_stream_lua_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->ssl_trusted_certificate, prev->ssl_trusted_certificate, ""); ngx_conf_merge_str_value(conf->ssl_crl, prev->ssl_crl, ""); + ngx_conf_merge_str_value(conf->ssl_key_log, prev->ssl_key_log, ""); #if (nginx_version >= 1019004) ngx_conf_merge_ptr_value(conf->ssl_conf_commands, prev->ssl_conf_commands, NULL); @@ -1157,6 +1170,12 @@ ngx_stream_lua_set_ssl(ngx_conf_t *cf, ngx_stream_lua_srv_conf_t *lscf) return NGX_ERROR; } + if (ngx_stream_lua_ssl_key_log(cf, lscf->ssl, &lscf->ssl_key_log) + != NGX_OK) + { + return NGX_ERROR; + } + #if (nginx_version >= 1019004) if (ngx_ssl_conf_commands(cf, lscf->ssl, lscf->ssl_conf_commands) != NGX_OK) @@ -1169,6 +1188,101 @@ ngx_stream_lua_set_ssl(ngx_conf_t *cf, ngx_stream_lua_srv_conf_t *lscf) } +static void +key_log_callback(const ngx_ssl_conn_t *ssl_conn, const char *line) +{ + ngx_stream_lua_ssl_key_log_t *ssl_key_log; + ngx_connection_t *c; + + ssl_key_log = SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl_conn), + ngx_stream_lua_ssl_key_log_index); + if (ssl_key_log == NULL) { + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + ngx_ssl_error(NGX_LOG_DEBUG, c->log, 0, "get ssl key log failed"); + + return; + } + + (void) ngx_write_fd(ssl_key_log->fd, (void *) line, ngx_strlen(line)); + (void) ngx_write_fd(ssl_key_log->fd, (void *) "\n", 1); +} + + +static void +ngx_stream_lua_ssl_cleanup_key_log(void *data) +{ + ngx_stream_lua_ssl_key_log_t *ssl_key_log = data; + + if (ngx_close_file(ssl_key_log->fd) == NGX_FILE_ERROR) { + ngx_ssl_error(NGX_LOG_ALERT, ssl_key_log->ssl->log, 0, + ngx_close_file_n "(\"%V\") failed", ssl_key_log->name); + } +} + + +static ngx_int_t +ngx_stream_lua_ssl_key_log(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) +{ + ngx_fd_t fd; + ngx_stream_lua_ssl_key_log_t *ssl_key_log; + ngx_pool_cleanup_t *cln; + + if (!file->len) { + return NGX_OK; + } + + if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_stream_lua_ssl_init(cf->log) != NGX_OK) { + return NGX_ERROR; + } + + /* + * append so that existing keylog file contents can be preserved + */ + fd = ngx_open_file(file->data, NGX_FILE_APPEND, NGX_FILE_CREATE_OR_OPEN, + NGX_FILE_DEFAULT_ACCESS); + if (fd == NGX_INVALID_FILE) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, ngx_open_file_n + "(\"%V\") failed", file); + return NGX_ERROR; + } + + ssl_key_log = ngx_palloc(cf->pool, sizeof(ngx_stream_lua_ssl_key_log_t)); + if (ssl_key_log == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "ngx_pcalloc() failed"); + return NGX_ERROR; + } + + ssl_key_log->ssl = ssl; + ssl_key_log->fd = fd; + ssl_key_log->name = *file; + + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_stream_lua_ssl_key_log_index, + ssl_key_log) == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + ngx_stream_lua_ssl_cleanup_key_log(ssl_key_log); + return NGX_ERROR; + } + + cln->handler = ngx_stream_lua_ssl_cleanup_key_log; + cln->data = ssl_key_log; + + SSL_CTX_set_keylog_callback(ssl->ctx, key_log_callback); + + return NGX_OK; +} + + #if (nginx_version >= 1019004) static char * ngx_stream_lua_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) diff --git a/src/ngx_stream_lua_ssl.c b/src/ngx_stream_lua_ssl.c index 2a6a951d..dfb96c60 100644 --- a/src/ngx_stream_lua_ssl.c +++ b/src/ngx_stream_lua_ssl.c @@ -22,6 +22,7 @@ int ngx_stream_lua_ssl_ctx_index = -1; +int ngx_stream_lua_ssl_key_log_index = -1; ngx_int_t @@ -40,6 +41,19 @@ ngx_stream_lua_ssl_init(ngx_log_t *log) } } + if (ngx_stream_lua_ssl_key_log_index == -1) { + ngx_stream_lua_ssl_key_log_index = SSL_get_ex_new_index(0, NULL, + NULL, + NULL, + NULL); + + if (ngx_stream_lua_ssl_key_log_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "lua: SSL_get_ex_new_index() for key log failed"); + return NGX_ERROR; + } + } + return NGX_OK; } diff --git a/src/ngx_stream_lua_ssl.h b/src/ngx_stream_lua_ssl.h index 336aa901..f4101b64 100644 --- a/src/ngx_stream_lua_ssl.h +++ b/src/ngx_stream_lua_ssl.h @@ -53,10 +53,18 @@ typedef struct { } ngx_stream_lua_ssl_ctx_t; +typedef struct { + ngx_ssl_t *ssl; + ngx_fd_t fd; + ngx_str_t name; +} ngx_stream_lua_ssl_key_log_t; + + ngx_int_t ngx_stream_lua_ssl_init(ngx_log_t *log); extern int ngx_stream_lua_ssl_ctx_index; +extern int ngx_stream_lua_ssl_key_log_index; #endif diff --git a/t/129-ssl-socket.t b/t/129-ssl-socket.t index 79611cba..997c9773 100644 --- a/t/129-ssl-socket.t +++ b/t/129-ssl-socket.t @@ -6,7 +6,7 @@ use File::Basename; repeat_each(2); -plan tests => repeat_each() * (blocks() * 7 + 2); +plan tests => repeat_each() * (blocks() * 7 + 3); my $NginxBinary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; my $openssl_version = eval { `$NginxBinary -V 2>&1` }; @@ -3012,3 +3012,108 @@ handshake rejected while SSL handshaking [crit] --- timeout: 5 --- skip_nginx: 7: < 1.25.4 + + + +=== TEST 37: lua_ssl_key_log directive +--- skip_openssl: 8: < 1.1.1 +--- http_config + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt; + ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key; + ssl_protocols TLSv1.3; + + location / { + content_by_lua_block { + ngx.exit(200) + } + } + } +--- stream_server_config + lua_ssl_protocols TLSv1.3; + lua_ssl_key_log sslkey.log; + + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + do + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local session, err = sock:sslhandshake(nil, "test.com") + if not session then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(session)) + + local req = "GET / HTTP/1.1\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send stream request: ", err) + return + end + + ngx.say("sent stream request: ", bytes, " bytes.") + + local line, err = sock:receive() + if not line then + ngx.say("failed to recieve response status line: ", err) + return + end + + ngx.say("received: ", line) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + + local f, err = io.open("$TEST_NGINX_SERVER_ROOT/conf/sslkey.log", "r") + if not f then + ngx.log(ngx.ERR, "failed to open sslkey.log: ", err) + return + end + + local key_log = f:read("*a") + ngx.say(key_log) + f:close() + end -- do + collectgarbage() + } + +--- stream_response_like +connected: 1 +ssl handshake: userdata +sent stream request: 53 bytes. +received: HTTP/1.1 200 OK +close: 1 nil +SERVER_HANDSHAKE_TRAFFIC_SECRET [0-9a-z\s]+ +EXPORTER_SECRET [0-9a-z\s]+ +SERVER_TRAFFIC_SECRET_0 [0-9a-z\s]+ +CLIENT_HANDSHAKE_TRAFFIC_SECRET [0-9a-z\s]+ +CLIENT_TRAFFIC_SECRET_0 [0-9a-z\s]+ + +--- log_level: debug +--- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/ +--- grep_error_log_out eval +qr/^lua ssl save session: ([0-9A-F]+) +lua ssl free session: ([0-9A-F]+) +$/ +--- error_log eval +[ +'lua ssl server name: "test.com"', +qr/SSL: TLSv1.3, cipher: "(TLS_AES_256_GCM_SHA384 TLSv1.3|TLS_AES_128_GCM_SHA256 Kx=GENERIC Au=GENERIC Enc=AESGCM\(128\) Mac=AEAD)/, +] +--- no_error_log +SSL reused session +[error] +[alert] +--- timeout: 10