Skip to content

feature: implement ssl.verify_client() #289

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

Merged
merged 6 commits into from
Jul 30, 2020
Merged
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
28 changes: 28 additions & 0 deletions lib/ngx/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ local ngx_lua_ffi_set_cert
local ngx_lua_ffi_set_priv_key
local ngx_lua_ffi_free_cert
local ngx_lua_ffi_free_priv_key
local ngx_lua_ffi_ssl_verify_client


if subsystem == 'http' then
Expand Down Expand Up @@ -78,6 +79,9 @@ if subsystem == 'http' then
void ngx_http_lua_ffi_free_cert(void *cdata);

void ngx_http_lua_ffi_free_priv_key(void *cdata);

int ngx_http_lua_ffi_ssl_verify_client(void *r,
void *cdata, int depth, char **err);
]]

ngx_lua_ffi_ssl_set_der_certificate =
Expand All @@ -97,6 +101,7 @@ if subsystem == 'http' then
ngx_lua_ffi_set_priv_key = C.ngx_http_lua_ffi_set_priv_key
ngx_lua_ffi_free_cert = C.ngx_http_lua_ffi_free_cert
ngx_lua_ffi_free_priv_key = C.ngx_http_lua_ffi_free_priv_key
ngx_lua_ffi_ssl_verify_client = C.ngx_http_lua_ffi_ssl_verify_client

elseif subsystem == 'stream' then
ffi.cdef[[
Expand Down Expand Up @@ -140,6 +145,9 @@ elseif subsystem == 'stream' then
void ngx_stream_lua_ffi_free_cert(void *cdata);

void ngx_stream_lua_ffi_free_priv_key(void *cdata);

int ngx_stream_lua_ffi_ssl_verify_client(void *r,
void *cdata, int depth, char **err);
]]

ngx_lua_ffi_ssl_set_der_certificate =
Expand All @@ -159,6 +167,7 @@ elseif subsystem == 'stream' then
ngx_lua_ffi_set_priv_key = C.ngx_stream_lua_ffi_set_priv_key
ngx_lua_ffi_free_cert = C.ngx_stream_lua_ffi_free_cert
ngx_lua_ffi_free_priv_key = C.ngx_stream_lua_ffi_free_priv_key
ngx_lua_ffi_ssl_verify_client = C.ngx_stream_lua_ffi_ssl_verify_client
end


Expand Down Expand Up @@ -380,6 +389,25 @@ function _M.set_priv_key(priv_key)
end


function _M.verify_client(ca_certs, depth)
local r = get_request()
if not r then
error("no request found")
end

if not depth then
depth = -1
end

local rc = ngx_lua_ffi_ssl_verify_client(r, ca_certs, depth, errmsg)
if rc == FFI_OK then
return true
end

return nil, ffi_str(errmsg[0])
end


do
_M.SSL3_VERSION = 0x0300
_M.TLS1_VERSION = 0x0301
Expand Down
25 changes: 25 additions & 0 deletions lib/ngx/ssl.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,31 @@ This function was first added in version `0.1.7`.

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

verify_client
------------
**syntax:** *ok, err = ssl.verify_client(ca_certs?, depth?)*

**context:** *ssl_certificate_by_lua**

Requires a client certificate during TLS handshake.

The `ca_certs` is the CA certificate chain opaque pointer returned by the
[parse_pem_cert](#parse_pem_cert) function for the current SSL connection.
The list of certificates will be sent to clients. Also, they will be added to trusted store.
If omitted, will not send any CA certificate to clients.

The `depth` is the verification depth in the client certificates chain.
If omitted, will use the value specified by `ssl_verify_depth`.

Returns `true` on success, or a `nil` value and a string describing the error otherwise.

Note that TLS is not terminated when verification fails. You need to examine Nginx variable `$ssl_client_verify`
later to determine next steps.

This function was first added in version `0.1.20`.

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

Community
=========

Expand Down
174 changes: 174 additions & 0 deletions t/ssl.t
Original file line number Diff line number Diff line change
Expand Up @@ -2330,3 +2330,177 @@ got TLS1 version: TLSv1.3,
[error]
[alert]
[emerg]



=== TEST 23: verify client with CA certificates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case fails with the following leak error in the valgrind test mode on my side using OpenSSL 1.1.1g:

t/ssl.t .. # I found ONLY: maybe you're debugging?
TEST 23: verify client with CA certificates
t/ssl.t .. 1/14 ==29716== 5,491 (648 direct, 4,843 indirect) bytes in 1 blocks are definitely lost in loss record 43 of 48
==29716==    at 0x4C2EE3B: malloc (vg_replace_malloc.c:309)
==29716==    by 0x6338608: CRYPTO_zalloc (mem.c:230)
==29716==    by 0x5F62C98: SSL_SESSION_new (ssl_sess.c:74)
==29716==    by 0x5F63462: ssl_get_new_session (ssl_sess.c:370)
==29716==    by 0x5F71319: tls_construct_client_hello (statem_clnt.c:1121)
==29716==    by 0x5F7064E: write_state_machine (statem.c:843)
==29716==    by 0x5F7064E: state_machine.part.5 (statem.c:443)
==29716==    by 0x5F5BEE3: SSL_do_handshake (ssl_lib.c:3663)
==29716==    by 0x454067: ngx_ssl_handshake (ngx_event_openssl.c:1606)
==29716==    by 0x454067: ngx_ssl_handshake (ngx_event_openssl.c:1593)
==29716==    by 0x47C30C: ngx_http_upstream_ssl_init_connection (ngx_http_upstream.c:1721)
==29716==    by 0x47C30C: ngx_http_upstream_ssl_init_connection (ngx_http_upstream.c:1668)
==29716==    by 0x47D2CB: ngx_http_upstream_init_request (ngx_http_upstream.c:813)
==29716==    by 0x470959: ngx_http_read_client_request_body (ngx_http_request_body.c:77)
==29716==    by 0x470959: ngx_http_read_client_request_body (ngx_http_request_body.c:30)
==29716==    by 0x4B2400: ngx_http_proxy_handler (ngx_http_proxy_module.c:937)
==29716==    by 0x4B2400: ngx_http_proxy_handler (ngx_http_proxy_module.c:849)
==29716==    by 0x4624DE: ngx_http_core_content_phase (ngx_http_core_module.c:1179)
==29716==    by 0x45D354: ngx_http_core_run_phases (ngx_http_core_module.c:868)
==29716==    by 0x468C5F: ngx_http_process_request_headers (ngx_http_request.c:1480)
==29716==    by 0x469095: ngx_http_process_request_line (ngx_http_request.c:1151)
==29716==    by 0x44F067: ngx_epoll_process_events (ngx_epoll_module.c:901)
==29716==    by 0x444591: ngx_process_events_and_timers (ngx_event.c:257)
==29716==    by 0x44E6A2: ngx_single_process_cycle (ngx_process_cycle.c:333)
==29716==    by 0x422CDE: main (nginx.c:382)
==29716==
{
   <insert_a_suppression_name_here>
   Memcheck:Leak
   match-leak-kinds: definite
   fun:malloc
   fun:CRYPTO_zalloc
   fun:SSL_SESSION_new
   fun:ssl_get_new_session
   fun:tls_construct_client_hello
   fun:write_state_machine
   fun:state_machine.part.5
   fun:SSL_do_handshake
   fun:ngx_ssl_handshake
   fun:ngx_ssl_handshake
   fun:ngx_http_upstream_ssl_init_connection
   fun:ngx_http_upstream_ssl_init_connection
   fun:ngx_http_upstream_init_request
   fun:ngx_http_read_client_request_body
   fun:ngx_http_read_client_request_body
   fun:ngx_http_proxy_handler
   fun:ngx_http_proxy_handler
   fun:ngx_http_core_content_phase
   fun:ngx_http_core_run_phases
   fun:ngx_http_process_request_headers
   fun:ngx_http_process_request_line
   fun:ngx_epoll_process_events
   fun:ngx_process_events_and_timers
   fun:ngx_single_process_cycle
   fun:main
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by adding proxy_ssl_session_reuse: off.

--- 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_certificate_by_lua_block {
local ssl = require "ngx.ssl"

local f = assert(io.open("t/cert/test.crt"))
local cert_data = f:read("*a")
f:close()

local cert, err = ssl.parse_pem_cert(cert_data)
if not cert then
ngx.log(ngx.ERR, "failed to parse pem cert: ", err)
return
end

local ok, err = ssl.verify_client(cert, 1)
if not ok then
ngx.log(ngx.ERR, "failed to verify client: ", err)
return
end
}

ssl_certificate ../../cert/test2.crt;
ssl_certificate_key ../../cert/test2.key;

location / {
default_type 'text/plain';
content_by_lua_block {
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
ngx.say(ngx.var.ssl_client_verify)
}
more_clear_headers Date;
}
}
--- config
location /t {
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock;
proxy_ssl_certificate ../../cert/test.crt;
proxy_ssl_certificate_key ../../cert/test.key;
proxy_ssl_session_reuse off;
}

--- request
GET /t
--- response_body
SUCCESS

--- error_log
client certificate subject: [email protected],CN=test.com

--- no_error_log
[error]
[alert]
[emerg]



=== TEST 24: verify client without CA certificates
--- 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_certificate_by_lua_block {
local ssl = require "ngx.ssl"

local ok, err = ssl.verify_client()
if not ok then
ngx.log(ngx.ERR, "failed to verify client: ", err)
return
end
}

ssl_certificate ../../cert/test2.crt;
ssl_certificate_key ../../cert/test2.key;

location / {
default_type 'text/plain';
content_by_lua_block {
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
ngx.say(ngx.var.ssl_client_verify)
}
more_clear_headers Date;
}
}
--- config
location /t {
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock;
proxy_ssl_certificate ../../cert/test.crt;
proxy_ssl_certificate_key ../../cert/test.key;
proxy_ssl_session_reuse off;
}

--- request
GET /t
--- response_body
FAILED:self signed certificate

--- error_log
client certificate subject: [email protected],CN=test.com

--- no_error_log
[error]
[alert]
[emerg]



=== TEST 25: verify client but client provides no certificate
--- 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_certificate_by_lua_block {
local ssl = require "ngx.ssl"

local f = assert(io.open("t/cert/test.crt"))
local cert_data = f:read("*a")
f:close()

local cert, err = ssl.parse_pem_cert(cert_data)
if not cert then
ngx.log(ngx.ERR, "failed to parse pem cert: ", err)
return
end

local ok, err = ssl.verify_client(cert, 1)
if not ok then
ngx.log(ngx.ERR, "failed to verify client: ", err)
return
end
}

ssl_certificate ../../cert/test2.crt;
ssl_certificate_key ../../cert/test2.key;

location / {
default_type 'text/plain';
content_by_lua_block {
print('client certificate subject: ', ngx.var.ssl_client_s_dn)
ngx.say(ngx.var.ssl_client_verify)
}
more_clear_headers Date;
}
}
--- config
location /t {
proxy_pass https://unix:$TEST_NGINX_HTML_DIR/nginx.sock;
proxy_ssl_session_reuse off;
}

--- request
GET /t
--- response_body
NONE

--- error_log
client certificate subject: nil

--- no_error_log
[error]
[alert]
[emerg]
Loading