From f3163ec1be3bcfcf03f33389a6dde7f65af4bf75 Mon Sep 17 00:00:00 2001 From: mengxiangwei Date: Tue, 24 Mar 2026 11:01:14 +0800 Subject: [PATCH] feat: add fetch server random and master key lua api --- .travis.yml | 2 +- lib/ngx/ssl.lua | 65 ++++++++++++++ lib/ngx/ssl.md | 36 ++++++++ t/ssl.t | 222 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 323 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c8cad8e31..d49dba118 100644 --- a/.travis.yml +++ b/.travis.yml @@ -74,7 +74,7 @@ install: - git clone https://github.com/openresty/openresty.git ../openresty - git clone https://github.com/openresty/openresty-devel-utils.git - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module - - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module + - git clone https://github.com/theweakgod/lua-nginx-module.git ../lua-nginx-module # TODO: revert to openresty/lua-nginx-module once the companion lua-nginx-module PR is merged - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module - git clone https://github.com/openresty/lua-resty-lrucache.git diff --git a/lib/ngx/ssl.lua b/lib/ngx/ssl.lua index 153a0c159..027ab8aed 100644 --- a/lib/ngx/ssl.lua +++ b/lib/ngx/ssl.lua @@ -41,6 +41,8 @@ local ngx_lua_ffi_free_cert local ngx_lua_ffi_free_priv_key local ngx_lua_ffi_ssl_verify_client local ngx_lua_ffi_ssl_client_random +local ngx_lua_ffi_ssl_server_random +local ngx_lua_ffi_ssl_session_master_key local ngx_lua_ffi_ssl_export_keying_material local ngx_lua_ffi_ssl_export_keying_material_early local ngx_lua_ffi_get_req_ssl_pointer @@ -117,6 +119,12 @@ if subsystem == 'http' then int ngx_http_lua_ffi_ssl_client_random(ngx_http_request_t *r, const unsigned char *out, size_t *outlen, char **err); + int ngx_http_lua_ffi_ssl_server_random(ngx_http_request_t *r, + const unsigned char *out, size_t *outlen, char **err); + + int ngx_http_lua_ffi_ssl_session_master_key(ngx_http_request_t *r, + const unsigned char *out, size_t *outlen, char **err); + int ngx_http_lua_ffi_ssl_export_keying_material(void *r, unsigned char *out, size_t out_size, const char *label, size_t llen, @@ -154,6 +162,9 @@ if subsystem == 'http' then ngx_lua_ffi_free_priv_key = C.ngx_http_lua_ffi_free_priv_key ngx_lua_ffi_ssl_verify_client = C.ngx_http_lua_ffi_ssl_verify_client ngx_lua_ffi_ssl_client_random = C.ngx_http_lua_ffi_ssl_client_random + ngx_lua_ffi_ssl_server_random = C.ngx_http_lua_ffi_ssl_server_random + ngx_lua_ffi_ssl_session_master_key = + C.ngx_http_lua_ffi_ssl_session_master_key ngx_lua_ffi_ssl_export_keying_material = C.ngx_http_lua_ffi_ssl_export_keying_material ngx_lua_ffi_ssl_export_keying_material_early = @@ -716,4 +727,58 @@ function _M.get_client_random(outlen) end +function _M.get_server_random(outlen) + local r = get_request() + if not r then + error("no request found") + end + + if outlen == nil then + outlen = 32 + end + + local out = get_string_buf(outlen) + local sizep = get_size_ptr() + sizep[0] = outlen + + local rc = ngx_lua_ffi_ssl_server_random(r, out, sizep, errmsg) + if rc == FFI_OK then + if outlen == 0 then + return tonumber(sizep[0]) + end + + return ffi_str(out, sizep[0]) + end + + return nil, ffi_str(errmsg[0]) +end + + +function _M.get_session_master_key(outlen) + local r = get_request() + if not r then + error("no request found") + end + + if outlen == nil then + outlen = 48 + end + + local out = get_string_buf(outlen) + local sizep = get_size_ptr() + sizep[0] = outlen + + local rc = ngx_lua_ffi_ssl_session_master_key(r, out, sizep, errmsg) + if rc == FFI_OK then + if outlen == 0 then + return tonumber(sizep[0]) + end + + return ffi_str(out, sizep[0]) + end + + return nil, ffi_str(errmsg[0]) +end + + return _M diff --git a/lib/ngx/ssl.md b/lib/ngx/ssl.md index 2c7453b30..325ff8673 100644 --- a/lib/ngx/ssl.md +++ b/lib/ngx/ssl.md @@ -32,6 +32,8 @@ Table of Contents * [set_priv_key](#set_priv_key) * [verify_client](#verify_client) * [get_client_random](#get_client_random) + * [get_server_random](#get_server_random) + * [get_session_master_key](#get_session_master_key) * [get_shared_ssl_ciphers](#get_shared_ssl_ciphers) * [get_req_ssl_pointer](#get_req_ssl_pointer) * [get_upstream_ssl_pointer](#get_upstream_ssl_pointer) @@ -652,6 +654,40 @@ This function can be called in any context where downstream https is used, but i [Back to TOC](#table-of-contents) +get_server_random +----------- +**syntax:** *server_random = ssl.get_server_random(outlen?)* + +**context:** *any* + +Returns the random value sent from the server to the client during the initial SSL/TLS handshake. This wraps OpenSSL's `SSL_get_server_random()`. + +The `outlen` parameter indicates the maximum length of the server_random value returned. +If the `outlen` is zero, this function returns the total length of the server_random value. +If omitted, will use the value 32. + +This function can be called in any context where downstream https is used. + +[Back to TOC](#table-of-contents) + +get_session_master_key +----------- +**syntax:** *master_key = ssl.get_session_master_key(outlen?)* + +**context:** *any* + +Returns the master secret from the current SSL session. This wraps OpenSSL's `SSL_SESSION_get_master_key()`. + +The `outlen` parameter indicates the maximum length of the master key value returned. +If the `outlen` is zero, this function returns the total length of the master key value. +If omitted, will use the value 48 (i.e. `SSL_MAX_MASTER_KEY_LENGTH`). + +Note that in TLS 1.3, the master secret is not used in the same way as in TLS 1.2, so this function is primarily useful for TLS 1.2 connections. + +This function can be called in any context where downstream https is used. + +[Back to TOC](#table-of-contents) + get_req_ssl_pointer ------------ **syntax:** *ssl_ptr, err = ssl.get_req_ssl_pointer()* diff --git a/t/ssl.t b/t/ssl.t index 3303d6217..6560d6b5a 100644 --- a/t/ssl.t +++ b/t/ssl.t @@ -8,7 +8,7 @@ use t::TestCore; repeat_each(2); -plan tests => repeat_each() * (blocks() * 6 ); +plan tests => repeat_each() * (blocks() * 6); no_long_string(); #no_diff(); @@ -3642,3 +3642,223 @@ lua ssl server name: "test.com" [error] [emerg] [crit] + + + +=== TEST 38: read server-random via ssl.get_server_random() +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + local server_random_len = ssl.get_server_random(0) + print("server-random length: ", server_random_len) + + local init_v = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + local server_random = ssl.get_server_random() + if server_random == init_v then + print("maybe the server random value is incorrect") + end + } + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block {ngx.status = 201 ngx.say("foo") ngx.exit(201)} + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to receive response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +server-random length: 32 + +--- no_error_log +[error] +[alert] +[emerg] + + + +=== TEST 39: read session master key via ssl.get_session_master_key() +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH"; + + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + ssl_protocols TLSv1.2; + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + ssl.clear_certs() + ssl.set_der_cert(ssl.cert_pem_to_der( + assert(io.open("t/cert/test.crt")):read("*a"))) + ssl.set_der_priv_key(ssl.priv_key_pem_to_der( + assert(io.open("t/cert/test.key")):read("*a"))) + } + + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { + local ssl = require "ngx.ssl" + local mk_len = ssl.get_session_master_key(0) + ngx.say("master-key length: ", mk_len) + + local mk = ssl.get_session_master_key() + if mk and #mk > 0 then + ngx.say("got master key of length: ", + #mk) + end + } + more_clear_headers Date; + } + } +--- config + server_tokens off; + lua_ssl_trusted_certificate ../../cert/test.crt; + + location /t { + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(3000) + + 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 sess, err = sock:sslhandshake(nil, "test.com", true) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + while true do + local line, err = sock:receive() + if not line then + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: cdata +sent http request: 56 bytes. +received: HTTP/1.1 200 OK +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 51 +received: Connection: close +received: +received: master-key length: 48 +received: got master key of length: 48 +close: 1 nil + +--- error_log +lua ssl server name: "test.com" + +--- no_error_log +[error] +[alert] +[emerg]