diff --git a/README.md b/README.md index 97e05cfa..89af1c70 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,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_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) diff --git a/src/ngx_stream_lua_common.h b/src/ngx_stream_lua_common.h index 751a0f3b..beb6041f 100644 --- a/src/ngx_stream_lua_common.h +++ b/src/ngx_stream_lua_common.h @@ -256,6 +256,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 2aa41ad5..d2cb619c 100644 --- a/src/ngx_stream_lua_module.c +++ b/src/ngx_stream_lua_module.c @@ -49,6 +49,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); @@ -453,6 +458,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, @@ -975,6 +987,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); @@ -1105,6 +1118,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) @@ -1117,6 +1136,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 352b709e..a7aa40b8 100644 --- a/src/ngx_stream_lua_ssl.h +++ b/src/ngx_stream_lua_ssl.h @@ -49,10 +49,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..13fc9e84 100644 --- a/t/129-ssl-socket.t +++ b/t/129-ssl-socket.t @@ -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