Skip to content

bugfix: reject settrustedstore on already-handshaked cosocket#2505

Merged
zhuizhuhaomeng merged 2 commits into
openresty:masterfrom
perfgao:fix/settrustedstore-after-handshake
May 29, 2026
Merged

bugfix: reject settrustedstore on already-handshaked cosocket#2505
zhuizhuhaomeng merged 2 commits into
openresty:masterfrom
perfgao:fix/settrustedstore-after-handshake

Conversation

@perfgao
Copy link
Copy Markdown
Contributor

@perfgao perfgao commented May 29, 2026

Summary

tcpsock:settrustedstore() called on a cosocket whose TLS handshake has already completed silently became a no-op, while appearing to succeed. The new X509_STORE was stashed into u->ssl_trusted_store, but a subsequent tcpsock:sslhandshake() short-circuits via the c->ssl->handshaked early return in ngx_http_lua_ffi_socket_tcp_sslhandshake and never reaches the SSL_set1_verify_cert_store() block at src/ngx_http_lua_socket_tcp.c:1942. As a result the connection continued to verify the peer against the trust anchor configured before the first handshake — users believed they had swapped trust anchors, but they had not.

Fix

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 swap.

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;
}
  • Scope is intentionally narrow: only ngx_http_lua_ffi_socket_tcp_settrustedstore is touched.
  • The handshake state machine and session-reuse semantics are unchanged.
  • Returning NGX_ERROR here is the existing FFI argument-validation path — it does not close the connection; the Lua wrapper just surfaces nil, err to the caller.

Reproduce (before patch)

local sock = ngx.socket.tcp()
sock:connect(host, 443)
sock:settrustedstore(store_a)
sock:sslhandshake(false, host, true)   -- verified against store_a

sock:settrustedstore(store_b)          -- silently stored
sock:sslhandshake(false, host, true)   -- returns OK, still verified against store_a

After the patch the second settrustedstore returns nil, "ssl handshake already done; trusted store cannot be changed on an established TLS connection".

Notes

The same early-return path in ffi_socket_tcp_sslhandshake also ignores re-supplied verify, SNI, OCSP and client cert/key when the cosocket is already handshaked. Those are out of scope for this PR — happy to do a follow-up if maintainers want symmetric protection at the same layer.

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.
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.
@zhuizhuhaomeng zhuizhuhaomeng merged commit 7b0d59c into openresty:master May 29, 2026
4 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants