From f671dc171db726ac9f29d5bc32b8b7cbfec44cf4 Mon Sep 17 00:00:00 2001 From: Gabriel Clima Date: Thu, 9 Jul 2026 10:54:33 +0000 Subject: [PATCH] feature: ngx_http_lua_ffi_ssl_set_ciphers(). Sets the connection's TLS <= 1.2 cipher list and/or TLS 1.3 ciphersuites from ssl_client_hello_by_lua*, e.g. keyed on the client hello server name. Both strings are validated on a scratch SSL_CTX before being applied to the connection, because a failed SSL_set_cipher_list() still installs the parsed list on the connection (emptying the TLS <= 1.2 list on unknown tokens) and leaves the thread error queue dirty, which makes SSL_do_handshake() report failure after the callback returns. The guarantee: if ngx_http_lua_ffi_ssl_set_ciphers returns NGX_ERROR for an invalid ciphers and/or ciphersuites string, the connection's SSL object is left exactly as it was -- cipher list, ciphersuites, and thread error queue all untouched -- so the handshake proceeds with whatever the connection already had, which is the default list inherited from the vhost's SSL_CTX (ssl_ciphers/ssl_conf_command). See https://github.com/openssl/openssl/pull/7759 for the check-after- install behaviour of SSL_set_cipher_list() and https://docs.openssl.org/3.0/man3/SSL_get_error/ for the empty-queue requirement. --- src/ngx_http_lua_ssl_client_helloby.c | 81 +++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/ngx_http_lua_ssl_client_helloby.c b/src/ngx_http_lua_ssl_client_helloby.c index 0ca6afa1f1..002c1074d7 100644 --- a/src/ngx_http_lua_ssl_client_helloby.c +++ b/src/ngx_http_lua_ssl_client_helloby.c @@ -838,4 +838,85 @@ ngx_http_lua_ffi_ssl_set_protocols(ngx_http_request_t *r, return NGX_OK; } + +int +ngx_http_lua_ffi_ssl_set_ciphers(ngx_http_request_t *r, const char *ciphers, + const char *ciphersuites, char **err) +{ +#ifdef SSL_ERROR_WANT_CLIENT_HELLO_CB + ngx_ssl_conn_t *ssl_conn; + + static SSL_CTX *scratch_ctx; + + if (r->connection == NULL || r->connection->ssl == NULL) { + *err = "bad request"; + return NGX_ERROR; + } + + ssl_conn = r->connection->ssl->connection; + if (ssl_conn == NULL) { + *err = "bad ssl conn"; + return NGX_ERROR; + } + + /* + * a failed SSL_set_cipher_list() still installs the parsed list on the + * connection and leaves the error queue dirty, which aborts the + * handshake after the callback returns; validate on a scratch SSL_CTX + * so failure is side-effect-free and callers can fall back to the + * connection default + */ + + if (scratch_ctx == NULL) { + scratch_ctx = SSL_CTX_new(TLS_method()); + if (scratch_ctx == NULL) { + ERR_clear_error(); + *err = "SSL_CTX_new() failed"; + return NGX_ERROR; + } + } + + if (ciphers != NULL && *ciphers != '\0') { + if (SSL_CTX_set_cipher_list(scratch_ctx, ciphers) == 0) { + ERR_clear_error(); + *err = "SSL_set_cipher_list() failed"; + return NGX_ERROR; + } + } + +#ifdef TLS1_3_VERSION + if (ciphersuites != NULL && *ciphersuites != '\0') { + if (SSL_CTX_set_ciphersuites(scratch_ctx, ciphersuites) == 0) { + ERR_clear_error(); + *err = "SSL_set_ciphersuites() failed"; + return NGX_ERROR; + } + } +#endif + + if (ciphers != NULL && *ciphers != '\0') { + if (SSL_set_cipher_list(ssl_conn, ciphers) == 0) { + ERR_clear_error(); + *err = "SSL_set_cipher_list() failed"; + return NGX_ERROR; + } + } + +#ifdef TLS1_3_VERSION + if (ciphersuites != NULL && *ciphersuites != '\0') { + if (SSL_set_ciphersuites(ssl_conn, ciphersuites) == 0) { + ERR_clear_error(); + *err = "SSL_set_ciphersuites() failed"; + return NGX_ERROR; + } + } +#endif + + return NGX_OK; +#else + *err = "OpenSSL too old to support this function"; + return NGX_ERROR; +#endif +} + #endif /* NGX_HTTP_SSL */