From 16418ce10e61559ce36f9ef1f1fd1e39b7d3a4d0 Mon Sep 17 00:00:00 2001 From: gresham Date: Fri, 29 May 2026 10:51:02 +0800 Subject: [PATCH 1/2] bugfix: reject settrustedstore on already-handshaked cosocket. Calling tcpsock:settrustedstore() after sslhandshake() has completed silently stashed the new X509_STORE into u->ssl_trusted_store, but the next sslhandshake() short-circuits via the c->ssl->handshaked check and never reaches the SSL_set1_verify_cert_store() block, so peer verification kept using the original trust anchor. Reject the call at the FFI entry when the underlying TLS connection is already established so callers get a clear error instead of a silently ineffective trust-anchor swap. The fix is contained to the FFI argument validation path and does not touch the handshake state machine. --- src/ngx_http_lua_socket_tcp.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ngx_http_lua_socket_tcp.c b/src/ngx_http_lua_socket_tcp.c index 027d534a5e..1822ae55c9 100644 --- a/src/ngx_http_lua_socket_tcp.c +++ b/src/ngx_http_lua_socket_tcp.c @@ -2283,6 +2283,14 @@ ngx_http_lua_ffi_socket_tcp_settrustedstore(ngx_http_request_t *r, return NGX_ERROR; } + if (u->peer.connection->ssl + && u->peer.connection->ssl->handshaked) + { + *errmsg = "ssl handshake already done; trusted store cannot be " + "changed on an established TLS connection"; + return NGX_ERROR; + } + u->ssl_trusted_store = store; return NGX_OK; From c5e0f261ec6170a7d8f8d9fd678021b64ba16e8d Mon Sep 17 00:00:00 2001 From: gresham Date: Fri, 29 May 2026 10:56:02 +0800 Subject: [PATCH 2/2] tests: cover settrustedstore rejection after handshake. Verifies that calling tcpsock:settrustedstore() on an already-handshaked cosocket now fails with a clear error message instead of silently stashing an X509_STORE that never takes effect, and that the rejected call leaves the existing TLS connection usable for subsequent I/O. --- t/193-ssl-trusted-store.t | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/t/193-ssl-trusted-store.t b/t/193-ssl-trusted-store.t index 4b937b4907..92e5ba3d3c 100644 --- a/t/193-ssl-trusted-store.t +++ b/t/193-ssl-trusted-store.t @@ -352,3 +352,68 @@ settrustedstore: true nil [alert] [crit] [emerg] + + + +=== TEST 6: settrustedstore is rejected after the TLS handshake has completed +--- http_config eval: $::tls_http_config +--- config eval +" + location /t { + content_by_lua_block { + local f = assert(io.open('$::HtmlDir/mtls_ca.crt')) + local ca_pem = f:read('*a') + f:close() + + local store1 = assert(load_store_from_pem(ca_pem)) + + local sock = ngx.socket.tcp() + assert(sock:connect('unix:$::HtmlDir/tls.sock')) + + assert(settrustedstore(sock, store1)) + + local sess, err = sock:sslhandshake(nil, 'example.com', true) + if not sess then + ngx.say('failed to do SSL handshake: ', err) + return + end + + -- second settrustedstore on an already-handshaked cosocket + -- must fail with a clear error rather than silently no-op. + local f2 = assert(io.open('$::HtmlDir/unrelated_ca.crt')) + local ca_pem2 = f2:read('*a') + f2:close() + local store2 = assert(load_store_from_pem(ca_pem2)) + + local ok, err = settrustedstore(sock, store2) + ngx.say('settrustedstore after handshake: ', ok, ' ', err) + + -- connection must still be usable after the rejected call. + local bytes, err = sock:send('GET / HTTP/1.0\\r\\nHost: example.com\\r\\n\\r\\n') + if not bytes then + ngx.say('failed to send: ', err) + return + end + + local line, err = sock:receive('*l') + if not line then + ngx.say('failed to receive: ', err) + return + end + + ngx.say('received: ', line) + sock:close() + } + } +" +--- user_files eval: $::tls_user_files +--- request +GET /t +--- response_body_like +^settrustedstore after handshake: nil ssl handshake already done; trusted store cannot be changed on an established TLS connection +received: HTTP/1\.[01] 200 OK$ +--- no_error_log +[error] +[alert] +[crit] +[emerg]