Skip to content

Commit 0cfd7d3

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). Requires ngx_http_lua_ffi_ssl_set_ciphers() from lua-nginx-module.
1 parent be42297 commit 0cfd7d3

3 files changed

Lines changed: 372 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: 45 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,50 @@ 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+
Considering it is meaningless to set the cipher list after the cipher is negotiated,
313+
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).
314+
315+
This function requires OpenSSL 1.1.1 or later.
316+
317+
Example:
318+
319+
```nginx
320+
# nginx.conf
321+
server {
322+
listen 443 ssl;
323+
server_name test.com;
324+
ssl_client_hello_by_lua_block {
325+
local ssl_clt = require "ngx.ssl.clienthello"
326+
local name = ssl_clt.get_client_hello_server_name()
327+
if name == "legacy.test.com" then
328+
local ok, err = ssl_clt.set_ciphers("HIGH:!aNULL:!MD5")
329+
if not ok then
330+
ngx.log(ngx.ERR, "failed to set ciphers: ", err)
331+
-- the handshake continues on the default ssl_ciphers
332+
end
333+
end
334+
}
335+
ssl_certificate test.crt;
336+
ssl_certificate_key test.key;
337+
}
338+
```
339+
340+
[Back to TOC](#table-of-contents)
341+
297342
Community
298343
=========
299344

0 commit comments

Comments
 (0)