Skip to content

Commit fa672ab

Browse files
authored
feature: implemented the new ssl.verify_client() API to require a client certificate during TLS handshake (#289)
1 parent 68f2b22 commit fa672ab

File tree

4 files changed

+374
-0
lines changed

4 files changed

+374
-0
lines changed

lib/ngx/ssl.lua

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ local ngx_lua_ffi_set_cert
3535
local ngx_lua_ffi_set_priv_key
3636
local ngx_lua_ffi_free_cert
3737
local ngx_lua_ffi_free_priv_key
38+
local ngx_lua_ffi_ssl_verify_client
3839

3940

4041
if subsystem == 'http' then
@@ -78,6 +79,9 @@ if subsystem == 'http' then
7879
void ngx_http_lua_ffi_free_cert(void *cdata);
7980

8081
void ngx_http_lua_ffi_free_priv_key(void *cdata);
82+
83+
int ngx_http_lua_ffi_ssl_verify_client(void *r,
84+
void *cdata, int depth, char **err);
8185
]]
8286

8387
ngx_lua_ffi_ssl_set_der_certificate =
@@ -97,6 +101,7 @@ if subsystem == 'http' then
97101
ngx_lua_ffi_set_priv_key = C.ngx_http_lua_ffi_set_priv_key
98102
ngx_lua_ffi_free_cert = C.ngx_http_lua_ffi_free_cert
99103
ngx_lua_ffi_free_priv_key = C.ngx_http_lua_ffi_free_priv_key
104+
ngx_lua_ffi_ssl_verify_client = C.ngx_http_lua_ffi_ssl_verify_client
100105

101106
elseif subsystem == 'stream' then
102107
ffi.cdef[[
@@ -140,6 +145,9 @@ elseif subsystem == 'stream' then
140145
void ngx_stream_lua_ffi_free_cert(void *cdata);
141146

142147
void ngx_stream_lua_ffi_free_priv_key(void *cdata);
148+
149+
int ngx_stream_lua_ffi_ssl_verify_client(void *r,
150+
void *cdata, int depth, char **err);
143151
]]
144152

145153
ngx_lua_ffi_ssl_set_der_certificate =
@@ -159,6 +167,7 @@ elseif subsystem == 'stream' then
159167
ngx_lua_ffi_set_priv_key = C.ngx_stream_lua_ffi_set_priv_key
160168
ngx_lua_ffi_free_cert = C.ngx_stream_lua_ffi_free_cert
161169
ngx_lua_ffi_free_priv_key = C.ngx_stream_lua_ffi_free_priv_key
170+
ngx_lua_ffi_ssl_verify_client = C.ngx_stream_lua_ffi_ssl_verify_client
162171
end
163172

164173

@@ -380,6 +389,25 @@ function _M.set_priv_key(priv_key)
380389
end
381390

382391

392+
function _M.verify_client(ca_certs, depth)
393+
local r = get_request()
394+
if not r then
395+
error("no request found")
396+
end
397+
398+
if not depth then
399+
depth = -1
400+
end
401+
402+
local rc = ngx_lua_ffi_ssl_verify_client(r, ca_certs, depth, errmsg)
403+
if rc == FFI_OK then
404+
return true
405+
end
406+
407+
return nil, ffi_str(errmsg[0])
408+
end
409+
410+
383411
do
384412
_M.SSL3_VERSION = 0x0300
385413
_M.TLS1_VERSION = 0x0301

lib/ngx/ssl.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,31 @@ This function was first added in version `0.1.7`.
475475

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

478+
verify_client
479+
------------
480+
**syntax:** *ok, err = ssl.verify_client(ca_certs?, depth?)*
481+
482+
**context:** *ssl_certificate_by_lua**
483+
484+
Requires a client certificate during TLS handshake.
485+
486+
The `ca_certs` is the CA certificate chain opaque pointer returned by the
487+
[parse_pem_cert](#parse_pem_cert) function for the current SSL connection.
488+
The list of certificates will be sent to clients. Also, they will be added to trusted store.
489+
If omitted, will not send any CA certificate to clients.
490+
491+
The `depth` is the verification depth in the client certificates chain.
492+
If omitted, will use the value specified by `ssl_verify_depth`.
493+
494+
Returns `true` on success, or a `nil` value and a string describing the error otherwise.
495+
496+
Note that TLS is not terminated when verification fails. You need to examine Nginx variable `$ssl_client_verify`
497+
later to determine next steps.
498+
499+
This function was first added in version `0.1.20`.
500+
501+
[Back to TOC](#table-of-contents)
502+
478503
Community
479504
=========
480505

t/ssl.t

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,3 +2330,177 @@ got TLS1 version: TLSv1.3,
23302330
[error]
23312331
[alert]
23322332
[emerg]
2333+
2334+
2335+
2336+
=== TEST 23: verify client with CA certificates
2337+
--- http_config
2338+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
2339+
2340+
server {
2341+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
2342+
server_name test.com;
2343+
ssl_certificate_by_lua_block {
2344+
local ssl = require "ngx.ssl"
2345+
2346+
local f = assert(io.open("t/cert/test.crt"))
2347+
local cert_data = f:read("*a")
2348+
f:close()
2349+
2350+
local cert, err = ssl.parse_pem_cert(cert_data)
2351+
if not cert then
2352+
ngx.log(ngx.ERR, "failed to parse pem cert: ", err)
2353+
return
2354+
end
2355+
2356+
local ok, err = ssl.verify_client(cert, 1)
2357+
if not ok then
2358+
ngx.log(ngx.ERR, "failed to verify client: ", err)
2359+
return
2360+
end
2361+
}
2362+
2363+
ssl_certificate ../../cert/test2.crt;
2364+
ssl_certificate_key ../../cert/test2.key;
2365+
2366+
location / {
2367+
default_type 'text/plain';
2368+
content_by_lua_block {
2369+
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
2370+
ngx.say(ngx.var.ssl_client_verify)
2371+
}
2372+
more_clear_headers Date;
2373+
}
2374+
}
2375+
--- config
2376+
location /t {
2377+
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock;
2378+
proxy_ssl_certificate ../../cert/test.crt;
2379+
proxy_ssl_certificate_key ../../cert/test.key;
2380+
proxy_ssl_session_reuse off;
2381+
}
2382+
2383+
--- request
2384+
GET /t
2385+
--- response_body
2386+
SUCCESS
2387+
2388+
--- error_log
2389+
client certificate subject: [email protected],CN=test.com
2390+
2391+
--- no_error_log
2392+
[error]
2393+
[alert]
2394+
[emerg]
2395+
2396+
2397+
2398+
=== TEST 24: verify client without CA certificates
2399+
--- http_config
2400+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
2401+
2402+
server {
2403+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
2404+
server_name test.com;
2405+
ssl_certificate_by_lua_block {
2406+
local ssl = require "ngx.ssl"
2407+
2408+
local ok, err = ssl.verify_client()
2409+
if not ok then
2410+
ngx.log(ngx.ERR, "failed to verify client: ", err)
2411+
return
2412+
end
2413+
}
2414+
2415+
ssl_certificate ../../cert/test2.crt;
2416+
ssl_certificate_key ../../cert/test2.key;
2417+
2418+
location / {
2419+
default_type 'text/plain';
2420+
content_by_lua_block {
2421+
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
2422+
ngx.say(ngx.var.ssl_client_verify)
2423+
}
2424+
more_clear_headers Date;
2425+
}
2426+
}
2427+
--- config
2428+
location /t {
2429+
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock;
2430+
proxy_ssl_certificate ../../cert/test.crt;
2431+
proxy_ssl_certificate_key ../../cert/test.key;
2432+
proxy_ssl_session_reuse off;
2433+
}
2434+
2435+
--- request
2436+
GET /t
2437+
--- response_body
2438+
FAILED:self signed certificate
2439+
2440+
--- error_log
2441+
client certificate subject: [email protected],CN=test.com
2442+
2443+
--- no_error_log
2444+
[error]
2445+
[alert]
2446+
[emerg]
2447+
2448+
2449+
2450+
=== TEST 25: verify client but client provides no certificate
2451+
--- http_config
2452+
lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH";
2453+
2454+
server {
2455+
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
2456+
server_name test.com;
2457+
ssl_certificate_by_lua_block {
2458+
local ssl = require "ngx.ssl"
2459+
2460+
local f = assert(io.open("t/cert/test.crt"))
2461+
local cert_data = f:read("*a")
2462+
f:close()
2463+
2464+
local cert, err = ssl.parse_pem_cert(cert_data)
2465+
if not cert then
2466+
ngx.log(ngx.ERR, "failed to parse pem cert: ", err)
2467+
return
2468+
end
2469+
2470+
local ok, err = ssl.verify_client(cert, 1)
2471+
if not ok then
2472+
ngx.log(ngx.ERR, "failed to verify client: ", err)
2473+
return
2474+
end
2475+
}
2476+
2477+
ssl_certificate ../../cert/test2.crt;
2478+
ssl_certificate_key ../../cert/test2.key;
2479+
2480+
location / {
2481+
default_type 'text/plain';
2482+
content_by_lua_block {
2483+
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
2484+
ngx.say(ngx.var.ssl_client_verify)
2485+
}
2486+
more_clear_headers Date;
2487+
}
2488+
}
2489+
--- config
2490+
location /t {
2491+
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock;
2492+
proxy_ssl_session_reuse off;
2493+
}
2494+
2495+
--- request
2496+
GET /t
2497+
--- response_body
2498+
NONE
2499+
2500+
--- error_log
2501+
client certificate subject: nil
2502+
2503+
--- no_error_log
2504+
[error]
2505+
[alert]
2506+
[emerg]

0 commit comments

Comments
 (0)