Skip to content

feature: lua_ssl_key_log directive #2433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,7 @@ Directives
* [lua_ssl_certificate_key](#lua_ssl_certificate_key)
* [lua_ssl_trusted_certificate](#lua_ssl_trusted_certificate)
* [lua_ssl_verify_depth](#lua_ssl_verify_depth)
* [lua_ssl_key_log](#lua_ssl_key_log)
* [lua_ssl_conf_command](#lua_ssl_conf_command)
* [lua_http10_buffering](#lua_http10_buffering)
* [rewrite_by_lua_no_postpone](#rewrite_by_lua_no_postpone)
Expand Down Expand Up @@ -3430,6 +3431,19 @@ See also [lua_ssl_certificate](#lua_ssl_certificate), [lua_ssl_certificate_key](

[Back to TOC](#directives)

lua_ssl_key_log
---------------

**syntax:** *lua_ssl_key_log <file>*

**default:** *none*

**context:** *http, server, location*

Enables logging of client connection SSL keys in the [tcpsock:sslhandshake](#tcpsocksslhandshake) method and specifies the path to the key log file. Keys are logged in the SSLKEYLOGFILE format compatible with Wireshark.

[Back to TOC](#directives)

lua_ssl_conf_command
--------------------

Expand Down
10 changes: 10 additions & 0 deletions doc/HttpLuaModule.wiki
Original file line number Diff line number Diff line change
Expand Up @@ -2925,6 +2925,16 @@ This directive was first introduced in the <code>v0.9.11</code> release.

See also [[#lua_ssl_certificate|lua_ssl_certificate]], [[#lua_ssl_certificate_key|lua_ssl_certificate_key]] and [[#lua_ssl_trusted_certificate|lua_ssl_trusted_certificate]].

== lua_ssl_key_log ==

'''syntax:''' ''lua_ssl_key_log <file>''

'''default:''' ''none''

'''context:''' ''http, server, location''

Enables logging of client connection SSL keys in the [[#tcpsock:sslhandshake|tcpsock:sslhandshake]] method and specifies the path to the key log file. Keys are logged in the SSLKEYLOGFILE format compatible with Wireshark.

== lua_ssl_conf_command ==

'''syntax:''' ''lua_ssl_conf_command <command>''
Expand Down
1 change: 1 addition & 0 deletions src/ngx_http_lua_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ typedef struct {
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
Expand Down
117 changes: 117 additions & 0 deletions src/ngx_http_lua_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ static ngx_int_t ngx_http_lua_merge_ssl(ngx_conf_t *cf,
ngx_http_lua_loc_conf_t *conf, ngx_http_lua_loc_conf_t *prev);
static ngx_int_t ngx_http_lua_set_ssl(ngx_conf_t *cf,
ngx_http_lua_loc_conf_t *llcf);
static void key_log_callback(const ngx_ssl_conn_t *ssl_conn,
const char *line);
static void ngx_http_lua_ssl_cleanup_key_log(void *data);
static ngx_int_t ngx_http_lua_ssl_key_log(ngx_conf_t *cf, ngx_ssl_t *ssl,
ngx_str_t *file);
#if (nginx_version >= 1019004)
static char *ngx_http_lua_ssl_conf_command_check(ngx_conf_t *cf, void *post,
void *data);
Expand Down Expand Up @@ -690,6 +695,13 @@ static ngx_command_t ngx_http_lua_cmds[] = {
offsetof(ngx_http_lua_loc_conf_t, ssl_crl),
NULL },

{ ngx_string("lua_ssl_key_log"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_lua_loc_conf_t, ssl_key_log),
NULL },

#if (nginx_version >= 1019004)
{ ngx_string("lua_ssl_conf_command"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
Expand Down Expand Up @@ -1433,6 +1445,7 @@ ngx_http_lua_create_loc_conf(ngx_conf_t *cf)
* conf->ssl_ciphers = { 0, NULL };
* conf->ssl_trusted_certificate = { 0, NULL };
* conf->ssl_crl = { 0, NULL };
* conf->ssl_key_log = { 0, NULL };
*/

conf->force_read_body = NGX_CONF_UNSET;
Expand Down Expand Up @@ -1553,6 +1566,7 @@ ngx_http_lua_merge_loc_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,
Expand Down Expand Up @@ -1616,6 +1630,7 @@ ngx_http_lua_merge_ssl(ngx_conf_t *cf,
&& conf->ssl_certificate_keys == NGX_CONF_UNSET_PTR
&& conf->ssl_trusted_certificate.data == NULL
&& conf->ssl_crl.data == NULL
&& conf->ssl_key_log.data == NULL
#if (nginx_version >= 1019004)
&& conf->ssl_conf_commands == NGX_CONF_UNSET_PTR
#endif
Expand Down Expand Up @@ -1723,6 +1738,12 @@ ngx_http_lua_set_ssl(ngx_conf_t *cf, ngx_http_lua_loc_conf_t *llcf)
return NGX_ERROR;
}

if (ngx_http_lua_ssl_key_log(cf, llcf->ssl, &llcf->ssl_key_log)
!= NGX_OK)
{
return NGX_ERROR;
}

#if (nginx_version >= 1019004)
if (ngx_ssl_conf_commands(cf, llcf->ssl, llcf->ssl_conf_commands)
!= NGX_OK)
Expand All @@ -1734,6 +1755,102 @@ ngx_http_lua_set_ssl(ngx_conf_t *cf, ngx_http_lua_loc_conf_t *llcf)
return NGX_OK;
}


static void
key_log_callback(const ngx_ssl_conn_t *ssl_conn, const char *line)
{
ngx_http_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_http_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_http_lua_ssl_cleanup_key_log(void *data)
{
ngx_http_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_http_lua_ssl_key_log(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file)
{
ngx_fd_t fd;
ngx_http_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_http_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_http_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_http_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_http_lua_ssl_cleanup_key_log(ssl_key_log);
return NGX_ERROR;
}

cln->handler = ngx_http_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_http_lua_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data)
Expand Down
12 changes: 12 additions & 0 deletions src/ngx_http_lua_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@


int ngx_http_lua_ssl_ctx_index = -1;
int ngx_http_lua_ssl_key_log_index = -1;


ngx_int_t
Expand All @@ -30,6 +31,17 @@ ngx_http_lua_ssl_init(ngx_log_t *log)
}
}

if (ngx_http_lua_ssl_key_log_index == -1) {
ngx_http_lua_ssl_key_log_index = SSL_get_ex_new_index(0, NULL, NULL,
NULL, NULL);

if (ngx_http_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;
}

Expand Down
8 changes: 8 additions & 0 deletions src/ngx_http_lua_ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,18 @@ typedef struct {
} ngx_http_lua_ssl_ctx_t;


typedef struct {
ngx_ssl_t *ssl;
ngx_fd_t fd;
ngx_str_t name;
} ngx_http_lua_ssl_key_log_t;


ngx_int_t ngx_http_lua_ssl_init(ngx_log_t *log);


extern int ngx_http_lua_ssl_ctx_index;
extern int ngx_http_lua_ssl_key_log_index;


#endif
Expand Down
102 changes: 102 additions & 0 deletions t/129-ssl-socket.t
Original file line number Diff line number Diff line change
Expand Up @@ -2944,3 +2944,105 @@ SSL reused session
[alert]
[emerg]
--- timeout: 10



=== TEST 35: lua_ssl_key_log directive
--- skip_openssl: 8: < 1.1.1
--- http_config
server {
listen $TEST_NGINX_SERVER_SSL_PORT 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)
}
}
}
--- config
server_tokens off;
lua_ssl_protocols TLSv1.3;
lua_ssl_key_log sslkey.log;

location /t {
content_by_lua_block {
local sock = ngx.socket.tcp()
sock:settimeout(2000)

do
local ok, err = sock:connect("127.0.0.1", $TEST_NGINX_SERVER_SSL_PORT)
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 http request: ", err)
return
end

ngx.say("sent http request: ", bytes, " bytes.")

local line, err = sock:receive()
if not line then
ngx.say("failed to receive 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()
}
}
--- request
GET /t
--- response_body_like
connected: 1
ssl handshake: cdata
sent http 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
--- error_log eval
['lua ssl server name: "test.com"',
qr/SSL: TLSv1.3, cipher: "TLS_AES_256_GCM_SHA384 TLSv1.3/]
--- no_error_log
SSL reused session
[error]
[alert]
--- timeout: 10