Skip to content

Commit 64617e2

Browse files
committed
feature: clienthello.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. On failure the connection's SSL object is left exactly as it was -- cipher list, ciphersuites, and error queue all untouched -- so the handshake proceeds with the default list inherited from the server block (ssl_ciphers/ssl_conf_command). A partially invalid string is not an error: OpenSSL's parsers drop unknown tokens and succeed if any token matches, so the surviving subset is applied with no fallback to the default list (covered by TEST 15). Requires ngx_http_lua_ffi_ssl_set_ciphers() from lua-nginx-module.
1 parent be42297 commit 64617e2

3 files changed

Lines changed: 489 additions & 0 deletions

File tree

lib/ngx/ssl/clienthello.lua

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ local ngx_lua_ffi_ssl_get_client_hello_ext
3232
local ngx_lua_ffi_ssl_set_protocols
3333
local ngx_lua_ffi_ssl_get_client_hello_ext_present
3434
local ngx_lua_ffi_ssl_get_client_hello_ciphers
35+
local ngx_lua_ffi_ssl_set_ciphers
3536

3637

3738
if subsystem == 'http' then
@@ -52,6 +53,9 @@ if subsystem == 'http' then
5253
int ngx_http_lua_ffi_ssl_get_client_hello_ciphers(ngx_http_request_t *r,
5354
unsigned short *ciphers, size_t ciphers_len, char **err);
5455
/* Undefined for the stream subsystem */
56+
int ngx_http_lua_ffi_ssl_set_ciphers(ngx_http_request_t *r,
57+
const char *ciphers, const char *ciphersuites, char **err);
58+
/* Undefined for the stream subsystem */
5559
]]
5660

5761
ngx_lua_ffi_ssl_get_client_hello_server_name =
@@ -63,6 +67,7 @@ if subsystem == 'http' then
6367
C.ngx_http_lua_ffi_ssl_get_client_hello_ext_present
6468
ngx_lua_ffi_ssl_get_client_hello_ciphers =
6569
C.ngx_http_lua_ffi_ssl_get_client_hello_ciphers
70+
ngx_lua_ffi_ssl_set_ciphers = C.ngx_http_lua_ffi_ssl_set_ciphers
6671

6772

6873

@@ -338,4 +343,34 @@ function _M.set_protocols(protocols)
338343
return nil, ffi_str(errmsg[0])
339344
end
340345

346+
347+
-- return ok, err
348+
-- on failure the connection's cipher list, ciphersuites and error queue
349+
-- are left untouched, so the handshake proceeds on the server defaults
350+
function _M.set_ciphers(ciphers, ciphersuites)
351+
local r = get_request()
352+
if not r then
353+
error("no request found")
354+
end
355+
356+
if ngx_phase() ~= "ssl_client_hello" then
357+
error("API disabled in the current context")
358+
end
359+
360+
if ciphers ~= nil and type(ciphers) ~= "string" then
361+
error("ciphers must be a string")
362+
end
363+
364+
if ciphersuites ~= nil and type(ciphersuites) ~= "string" then
365+
error("ciphersuites must be a string")
366+
end
367+
368+
local rc = ngx_lua_ffi_ssl_set_ciphers(r, ciphers, ciphersuites, errmsg)
369+
if rc == FFI_OK then
370+
return true
371+
end
372+
373+
return nil, ffi_str(errmsg[0])
374+
end
375+
341376
return _M

lib/ngx/ssl/clienthello.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Table of Contents
1717
* [get_client_hello_ext_present](#get_client_hello_ext_present)
1818
* [get_client_hello_ext](#get_client_hello_ext)
1919
* [set_protocols](#set_protocols)
20+
* [set_ciphers](#set_ciphers)
2021
* [Community](#community)
2122
* [English Mailing List](#english-mailing-list)
2223
* [Chinese Mailing List](#chinese-mailing-list)
@@ -294,6 +295,52 @@ Example: `ssl_clt.set_protocols({"TLSv1.1", "TLSv1.2", "TLSv1.3"})`
294295

295296
[Back to TOC](#table-of-contents)
296297

298+
set_ciphers
299+
----------------------
300+
**syntax:** *ok, err = ssl_clt.set_ciphers(ciphers, ciphersuites?)*
301+
302+
**context:** *ssl_client_hello_by_lua&#42;*
303+
304+
Sets the cipher list used by the current downstream SSL connection, e.g. keyed on the client hello server name.
305+
306+
The `ciphers` string configures the TLS &lt;= 1.2 cipher list in the OpenSSL cipher list format (see [ciphers(1)](https://docs.openssl.org/master/man1/openssl-ciphers/)), like the `ssl_ciphers` nginx directive. The optional `ciphersuites` string configures the TLS 1.3 ciphersuites, a colon-separated list like `TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256`, like the `ssl_conf_command Ciphersuites` nginx directive. Either argument may be `nil` to leave that part of the configuration unchanged.
307+
308+
Returns `true` on success, or a `nil` value and a string describing the error otherwise.
309+
310+
On failure the connection's SSL object is left exactly as it was -- cipher list, ciphersuites, and error queue all untouched -- so the handshake proceeds with whatever the connection already had, which is the default list inherited from the server block (`ssl_ciphers`/`ssl_conf_command`). Both strings are validated before either is applied, because a failed `SSL_set_cipher_list()` would otherwise still install the parsed list on the connection and leave the error queue dirty, aborting the handshake.
311+
312+
Note that a partially invalid string is not an error: OpenSSL's parsers are lenient, unknown tokens in either string are silently dropped and the call succeeds as long as at least one token matches, in which case the surviving subset is applied with no fallback to the default list. The failure path above only covers strings OpenSSL rejects outright.
313+
314+
Considering it is meaningless to set the cipher list after the cipher is negotiated,
315+
so this function may only be called in the context of [ssl_client_hello_by_lua*](https://github.com/openresty/lua-nginx-module/#ssl_client_hello_by_lua_block).
316+
317+
This function requires OpenSSL 1.1.1 or later.
318+
319+
Example:
320+
321+
```nginx
322+
# nginx.conf
323+
server {
324+
listen 443 ssl;
325+
server_name test.com;
326+
ssl_client_hello_by_lua_block {
327+
local ssl_clt = require "ngx.ssl.clienthello"
328+
local name = ssl_clt.get_client_hello_server_name()
329+
if name == "legacy.test.com" then
330+
local ok, err = ssl_clt.set_ciphers("HIGH:!aNULL:!MD5")
331+
if not ok then
332+
ngx.log(ngx.ERR, "failed to set ciphers: ", err)
333+
-- the handshake continues on the default ssl_ciphers
334+
end
335+
end
336+
}
337+
ssl_certificate test.crt;
338+
ssl_certificate_key test.key;
339+
}
340+
```
341+
342+
[Back to TOC](#table-of-contents)
343+
297344
Community
298345
=========
299346

0 commit comments

Comments
 (0)