Skip to content

feature: ssl.get_shared_ssl_ciphers() #505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions lib/ngx/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ local ffi = require "ffi"
local C = ffi.C
local ffi_str = ffi.string
local ffi_gc = ffi.gc
local ffi_copy = ffi.copy
local ffi_sizeof = ffi.sizeof
local ffi_typeof = ffi.typeof
local ffi_new = ffi.new
local get_request = base.get_request
local error = error
local tonumber = tonumber
local format = string.format
local concat = table.concat
local errmsg = base.get_errmsg_ptr()
local get_string_buf = base.get_string_buf
local get_size_ptr = base.get_size_ptr
Expand Down Expand Up @@ -43,6 +49,7 @@ local ngx_lua_ffi_ssl_client_random
local ngx_lua_ffi_ssl_export_keying_material
local ngx_lua_ffi_ssl_export_keying_material_early
local ngx_lua_ffi_get_req_ssl_pointer
local ngx_lua_ffi_req_shared_ssl_ciphers


if subsystem == 'http' then
Expand Down Expand Up @@ -114,6 +121,14 @@ if subsystem == 'http' then
unsigned char *out, size_t out_size,
const char *label, size_t llen,
const unsigned char *ctx, size_t ctxlen, char **err);

int ngx_http_lua_ffi_req_shared_ssl_ciphers(ngx_http_request_t *r,
unsigned short *ciphers, unsigned short *nciphers, int filter_grease, char **err);

typedef struct {
uint16_t nciphers;
uint16_t ciphers[?];
} ngx_lua_ssl_ciphers;
]]

ngx_lua_ffi_ssl_set_der_certificate =
Expand Down Expand Up @@ -143,6 +158,8 @@ if subsystem == 'http' then
ngx_lua_ffi_ssl_export_keying_material_early =
C.ngx_http_lua_ffi_ssl_export_keying_material_early
ngx_lua_ffi_get_req_ssl_pointer = C.ngx_http_lua_ffi_get_req_ssl_pointer
ngx_lua_ffi_req_shared_ssl_ciphers =
C.ngx_http_lua_ffi_req_shared_ssl_ciphers

elseif subsystem == 'stream' then
ffi.cdef[[
Expand Down Expand Up @@ -237,6 +254,37 @@ local charpp = ffi.new("char*[1]")
local intp = ffi.new("int[1]")
local ushortp = ffi.new("unsigned short[1]")

do
local ciphers_buf = ffi_new("uint16_t [?]", 256)

function _M.get_shared_ssl_ciphers(filter_grease)
local r = get_request()
if not r then
error("no request found")
end

if filter_grease == nil then
filter_grease = true -- Default to filter GREASE
end

ciphers_buf[0] = 255 -- Set max number of ciphers we can hold
local filter_flag = filter_grease and 1 or 0
local rc = ngx_lua_ffi_req_shared_ssl_ciphers(r, ciphers_buf + 1,
ciphers_buf, filter_flag, errmsg)
if rc ~= FFI_OK then
return nil, ffi_str(errmsg[0])
end

-- Build result table
local result = {}
for i = 1, ciphers_buf[0] do
local cipher_id = ciphers_buf[i]
result[#result + 1] = cipher_id
end

return result
end
end

function _M.clear_certs()
local r = get_request()
Expand Down
47 changes: 39 additions & 8 deletions lib/ngx/ssl.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Table of Contents
* [set_priv_key](#set_priv_key)
* [verify_client](#verify_client)
* [get_client_random](#get_client_random)
* [get_shared_ssl_ciphers](#get_shared_ssl_ciphers)
* [get_req_ssl_pointer](#get_req_ssl_pointer)
* [Community](#community)
* [English Mailing List](#english-mailing-list)
Expand Down Expand Up @@ -651,20 +652,50 @@ This function can be called in any context where downstream https is used, but i
[Back to TOC](#table-of-contents)


get_req_ssl_pointer
------------
**syntax:** *ssl_ptr, err = ssl.get_req_ssl_pointer()*
get_shared_ssl_ciphers
-----------
**syntax:** *ciphers = ssl.get_shared_ssl_ciphers()*

**context:** *any*

Retrieves the OpenSSL `SSL*` object for the current downstream connection.
Returns a structured object containing the cipher suite information that are supported by both the server and the client for the current SSL connection.

This function returns the intersection of server-supported ciphers and client-offered ciphers, representing the ciphers that can actually be used for the connection.

The returned object is a structured FFI object with the following characteristics:

Returns an FFI pointer on success, or a `nil` value and a string describing the error otherwise.
- `ciphers.nciphers`: The number of shared ciphers
- Supports `ipairs()` iteration to access detailed cipher information
- Supports `tostring()` to get a formatted cipher list
- Each cipher entry contains:
- `iana_name`: The IANA standard name (e.g., "TLS_AES_128_GCM_SHA256")
- `tls_version`: The TLS version (1.2, 1.3, etc.)
- `kex`: Key exchange algorithm (e.g., "ECDHE")
- `auth`: Authentication method (e.g., "RSA", "ECDSA")
- `enc`: Encryption algorithm (e.g., "AES 128 GCM")
- `hash`: Hash algorithm (e.g., "SHA256")

If you need to retain the pointer beyond the current phase then you will need to use OpenSSL's `SSL_up_ref` to increase the reference count.
If you do, ensure that your reference is released with `SSL_free`.
Example usage:
```lua
local ciphers = ssl.get_shared_ssl_ciphers()
if ciphers then
ngx.log(ngx.INFO, "Found ", ciphers.nciphers, " shared ciphers")
for i, cipher in ipairs(ciphers) do
ngx.log(ngx.INFO, "Cipher: ", cipher.iana_name,
" (TLS ", cipher.tls_version, ")")
end
end
```

This function was first added in version `0.1.16`.
GREASE (Generate Random Extensions And Sustain Extensibility) cipher values are automatically filtered out from the results.

Returns `nil` and an error string on failure.

This function can be called in any context where downstream https is used.

This function was first added in version `0.1.29`.

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

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

Expand Down
62 changes: 60 additions & 2 deletions t/ssl.t
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use t::TestCore;

repeat_each(2);

plan tests => repeat_each() * (blocks() * 6 - 1);
plan tests => repeat_each() * (blocks() * 6 );

no_long_string();
#no_diff();
Expand Down Expand Up @@ -110,7 +110,7 @@ failed to do SSL handshake: handshake failed

--- error_log eval
['lua ssl server name: "test.com"',
qr/routines::no suitable signature algorithm|sslv3 alert handshake failure|routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE:SSL alert number 40/]
qr/sslv3 alert handshake failure|routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE:SSL alert number 40/]

--- no_error_log
[alert]
Expand Down Expand Up @@ -3419,3 +3419,61 @@ SUCCESS
[error]
[alert]
[emerg]



=== TEST 35: get shared SSL ciphers
--- http_config
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
server {
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
server_name test.com;
ssl_protocols TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;

ssl_certificate_by_lua_block {
local ssl = require "ngx.ssl"
local ciphers, err = ssl.get_shared_ssl_ciphers()
if not err and ciphers then
ngx.log(ngx.INFO, "shared ciphers count: ", #ciphers)
local count = 0
for i, cipher_id in ipairs(ciphers) do
count = count + 1
ngx.log(ngx.INFO, string.format("%d: SHARED_CIPHER 0x%04x", i, cipher_id))
if count >= 3 then -- log only first 3 to avoid too much output
break
end
end
else
ngx.log(ngx.ERR, "failed to get shared ciphers: ", err)
end
}
ssl_certificate ../../cert/test.crt;
ssl_certificate_key ../../cert/test.key;

server_tokens off;
location /foo {
default_type 'text/plain';
content_by_lua_block {ngx.status = 200 ngx.say("foo") ngx.exit(200)}
more_clear_headers Date;
}
}
--- config
location /t {
proxy_ssl_protocols TLSv1.2;
proxy_ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256;
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock:/foo;
proxy_ssl_session_reuse off;
}

--- request
GET /t
--- response_body
foo
--- error_log eval
[qr/shared ciphers count: \d+/,
qr/1: SHARED_CIPHER 0x/]
--- no_error_log
[alert]
[crit]
[error]