Skip to content

Commit 94e91ae

Browse files
authored
feature: ngx_stream_lua_ffi_req_shared_ssl_ciphers().
1 parent de6820b commit 94e91ae

File tree

2 files changed

+270
-0
lines changed

2 files changed

+270
-0
lines changed

src/ngx_stream_lua_ssl_certby.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ static u_char *ngx_stream_lua_log_ssl_cert_error(ngx_log_t *log, u_char *buf,
4444
size_t len);
4545
static ngx_int_t ngx_stream_lua_ssl_cert_by_chunk(lua_State *L,
4646
ngx_stream_lua_request_t *r);
47+
static int ngx_stream_lua_is_grease_cipher(uint16_t cipher_id);
4748

4849

4950
ngx_int_t
@@ -986,6 +987,80 @@ ngx_stream_lua_ffi_ssl_raw_client_addr(ngx_stream_lua_request_t *r, char **addr,
986987
}
987988

988989

990+
static int
991+
ngx_stream_lua_is_grease_cipher(uint16_t cipher_id)
992+
{
993+
/* GREASE values follow pattern: 0x?A?A where ? can be any hex digit */
994+
/* and both ? must be the same */
995+
/* Check if both bytes follow ?A pattern and high nibbles match */
996+
return (cipher_id & 0x0F0F) == 0x0A0A;
997+
}
998+
999+
1000+
int
1001+
ngx_stream_lua_ffi_req_shared_ssl_ciphers(ngx_stream_lua_request_t *r,
1002+
uint16_t *ciphers, uint16_t *nciphers, int filter_grease, char **err)
1003+
{
1004+
#ifdef OPENSSL_IS_BORINGSSL
1005+
1006+
*err = "BoringSSL is not supported for SSL cipher operations";
1007+
return NGX_ERROR;
1008+
1009+
#else
1010+
ngx_ssl_conn_t *ssl_conn;
1011+
STACK_OF(SSL_CIPHER) *sk, *ck;
1012+
int sn, cn, i, n;
1013+
uint16_t cipher;
1014+
1015+
if (r == NULL || r->connection == NULL || r->connection->ssl == NULL) {
1016+
*err = "bad request";
1017+
return NGX_ERROR;
1018+
}
1019+
1020+
ssl_conn = r->connection->ssl->connection;
1021+
if (ssl_conn == NULL) {
1022+
*err = "bad ssl conn";
1023+
return NGX_ERROR;
1024+
}
1025+
1026+
sk = SSL_get1_supported_ciphers(ssl_conn);
1027+
ck = SSL_get_client_ciphers(ssl_conn);
1028+
sn = sk_SSL_CIPHER_num(sk);
1029+
cn = sk_SSL_CIPHER_num(ck);
1030+
1031+
if (sn > *nciphers) {
1032+
*err = "buffer too small";
1033+
*nciphers = 0;
1034+
sk_SSL_CIPHER_free(sk);
1035+
return NGX_ERROR;
1036+
}
1037+
1038+
for (*nciphers = 0, i = 0; i < sn; i++) {
1039+
cipher = SSL_CIPHER_get_protocol_id(sk_SSL_CIPHER_value(sk, i));
1040+
1041+
/* Skip GREASE ciphers if filtering is enabled */
1042+
if (filter_grease && ngx_stream_lua_is_grease_cipher(cipher)) {
1043+
continue;
1044+
}
1045+
1046+
for (n = 0; n < cn; n++) {
1047+
if (SSL_CIPHER_get_protocol_id(sk_SSL_CIPHER_value(ck, n))
1048+
== cipher)
1049+
{
1050+
ciphers[(*nciphers)++] = cipher;
1051+
break;
1052+
}
1053+
}
1054+
}
1055+
1056+
sk_SSL_CIPHER_free(sk);
1057+
1058+
return NGX_OK;
1059+
#endif
1060+
1061+
}
1062+
1063+
9891064
int
9901065
ngx_stream_lua_ffi_cert_pem_to_der(const u_char *pem, size_t pem_len,
9911066
u_char *der, char **err)

t/140-ssl-c-api.t

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ ffi.cdef[[
7272
int ngx_stream_lua_ffi_ssl_client_random(ngx_stream_lua_request_t *r,
7373
unsigned char *out, size_t *outlen, char **err);
7474
75+
int ngx_stream_lua_ffi_req_shared_ssl_ciphers(void *r, uint16_t *ciphers,
76+
uint16_t *nciphers, int filter_grease, char **err);
77+
7578
]]
7679
_EOC_
7780
}
@@ -1374,3 +1377,195 @@ SUCCESS
13741377
--- no_error_log
13751378
[error]
13761379
[alert]
1380+
1381+
1382+
1383+
=== TEST 14: Get supported ciphers
1384+
--- stream_config
1385+
server {
1386+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
1387+
1388+
ssl_certificate_by_lua_block {
1389+
collectgarbage()
1390+
require "defines"
1391+
local ffi = require "ffi"
1392+
local cjson = require "cjson.safe"
1393+
1394+
local MAX_CIPHERS = 64
1395+
local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS)
1396+
local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS)
1397+
local err = ffi.new("char*[1]")
1398+
1399+
local r = require "resty.core.base" .get_request()
1400+
if not r then
1401+
ngx.log(ngx.ERR, "no request found")
1402+
return
1403+
end
1404+
local ret = ffi.C.ngx_stream_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err)
1405+
1406+
if ret ~= 0 then
1407+
ngx.log(ngx.ERR, "error getting ciphers: ", ffi.string(err[0]))
1408+
else
1409+
local res = {}
1410+
for i = 0, nciphers[0] - 1 do
1411+
local cipher_id = string.format("%04x", ciphers[i])
1412+
table.insert(res, cipher_id)
1413+
end
1414+
ngx.log(ngx.INFO, "supported ciphers: ", cjson.encode(res))
1415+
end
1416+
}
1417+
1418+
ssl_certificate ../../cert/test.crt;
1419+
ssl_certificate_key ../../cert/test.key;
1420+
ssl_protocols TLSv1.2;
1421+
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
1422+
1423+
return 'cipher test works!\n';
1424+
}
1425+
--- stream_server_config
1426+
proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock;
1427+
proxy_ssl on;
1428+
proxy_ssl_session_reuse off;
1429+
proxy_ssl_protocols TLSv1.2;
1430+
proxy_ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256;
1431+
--- stream_response
1432+
cipher test works!
1433+
--- error_log
1434+
supported ciphers: ["c02f","c02b"]
1435+
--- no_error_log
1436+
[error]
1437+
[alert]
1438+
1439+
1440+
1441+
=== TEST 15: Get supported ciphers with GREASE filtering
1442+
--- stream_config
1443+
server {
1444+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
1445+
1446+
ssl_certificate_by_lua_block {
1447+
collectgarbage()
1448+
require "defines"
1449+
local ffi = require "ffi"
1450+
local cjson = require "cjson.safe"
1451+
local MAX_CIPHERS = 64
1452+
local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS)
1453+
local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS)
1454+
local err = ffi.new("char*[1]")
1455+
1456+
local r = require "resty.core.base" .get_request()
1457+
if not r then
1458+
ngx.log(ngx.ERR, "no request found")
1459+
return
1460+
end
1461+
1462+
-- Test without GREASE filtering
1463+
local ret = ffi.C.ngx_stream_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err)
1464+
local res_no_filter = {}
1465+
if ret == 0 then
1466+
for i = 0, nciphers[0] - 1 do
1467+
local cipher_id = string.format("%04x", ciphers[i])
1468+
table.insert(res_no_filter, cipher_id)
1469+
end
1470+
end
1471+
1472+
-- Reset buffer for next test
1473+
nciphers[0] = MAX_CIPHERS
1474+
1475+
-- Test with GREASE filtering
1476+
local ret = ffi.C.ngx_stream_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 1, err)
1477+
local res_with_filter = {}
1478+
if ret == 0 then
1479+
for i = 0, nciphers[0] - 1 do
1480+
local cipher_id = string.format("%04x", ciphers[i])
1481+
table.insert(res_with_filter, cipher_id)
1482+
end
1483+
end
1484+
1485+
ngx.log(ngx.INFO, "without_filter: ", cjson.encode(res_no_filter))
1486+
ngx.log(ngx.INFO, "with_filter: ", cjson.encode(res_with_filter))
1487+
}
1488+
1489+
ssl_certificate ../../cert/test.crt;
1490+
ssl_certificate_key ../../cert/test.key;
1491+
ssl_protocols TLSv1.2;
1492+
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
1493+
1494+
return 'grease filter test works!\n';
1495+
}
1496+
--- stream_server_config
1497+
proxy_pass unix:$TEST_NGINX_HTML_DIR/nginx.sock;
1498+
proxy_ssl on;
1499+
proxy_ssl_certificate ../../cert/mtls_client.crt;
1500+
proxy_ssl_certificate_key ../../cert/mtls_client.key;
1501+
proxy_ssl_session_reuse off;
1502+
1503+
--- stream_response
1504+
grease filter test works!
1505+
--- error_log
1506+
without_filter:
1507+
with_filter:
1508+
--- no_error_log
1509+
[error]
1510+
[alert]
1511+
1512+
1513+
1514+
=== TEST 16: SSL cipher API error handling (no SSL)
1515+
--- stream_config
1516+
server {
1517+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock;
1518+
1519+
content_by_lua_block {
1520+
require "defines"
1521+
local ffi = require "ffi"
1522+
1523+
local MAX_CIPHERS = 64
1524+
local ciphers = ffi.new("uint16_t[?]", MAX_CIPHERS)
1525+
local nciphers = ffi.new("uint16_t[1]", MAX_CIPHERS)
1526+
local err = ffi.new("char*[1]")
1527+
1528+
local r = require "resty.core.base" .get_request()
1529+
if not r then
1530+
ngx.log(ngx.ERR, "no request found")
1531+
return
1532+
end
1533+
local ret = ffi.C.ngx_stream_lua_ffi_req_shared_ssl_ciphers(r, ciphers, nciphers, 0, err)
1534+
1535+
if ret ~= 0 then
1536+
ngx.say("error: ", ffi.string(err[0]))
1537+
else
1538+
ngx.say("unexpected success")
1539+
end
1540+
}
1541+
}
1542+
--- stream_server_config
1543+
content_by_lua_block {
1544+
local sock = ngx.socket.tcp()
1545+
sock:settimeout(2000)
1546+
1547+
local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock")
1548+
if not ok then
1549+
ngx.log(ngx.ERR, "failed to connect: ", err)
1550+
return
1551+
end
1552+
1553+
local line, err = sock:receive()
1554+
if not line then
1555+
ngx.log(ngx.ERR, "failed to receive: ", err)
1556+
return
1557+
end
1558+
1559+
ngx.say("received: ", line)
1560+
1561+
local ok, err = sock:close()
1562+
ngx.say("close: ", ok, " ", err)
1563+
}
1564+
1565+
--- stream_response
1566+
received: error: bad request
1567+
close: 1 nil
1568+
1569+
--- no_error_log
1570+
[error]
1571+
[alert]

0 commit comments

Comments
 (0)