From 68bf4bf01cb205b73741fc0a4c9ec91ac591785f Mon Sep 17 00:00:00 2001 From: Datong Sun Date: Wed, 18 Sep 2019 16:41:56 -0700 Subject: [PATCH] cosocket: add function tcpsock:setclientcert, reimplemented `tcpsock:sslhandshake` with FFI This adds support for setting client certificate/private key that will be used later for mutual TLS handshake with a server. Also, the `tcpsock:sslhandshake` implementation has been rewritten to use FFI C API to be more performant and easier to maintain. Also see: https://github.com/openresty/lua-nginx-module/pull/1602 Co-authored-by: Chrono Law --- .travis.yml | 2 +- README.markdown | 2 + lib/resty/core/socket.lua | 172 +++++++++++++++++++++++++++++++++++--- t/ocsp.t | 30 +++---- t/ssl-client-hello.t | 12 +-- t/ssl-session-fetch.t | 10 +-- t/ssl-session-store.t | 6 +- t/ssl.t | 28 +++---- 8 files changed, 206 insertions(+), 56 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9a1518c03..2d1fec687 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,7 +65,7 @@ install: - git clone https://github.com/openresty/openresty.git ../openresty - git clone https://github.com/openresty/openresty-devel-utils.git - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module - - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module + - git clone -b feat/cosocket_tlshandshake https://github.com/dndx/lua-nginx-module.git ../lua-nginx-module - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module - git clone https://github.com/openresty/lua-resty-lrucache.git diff --git a/README.markdown b/README.markdown index 6f819af31..35dff078b 100644 --- a/README.markdown +++ b/README.markdown @@ -271,6 +271,8 @@ in the current request before you reusing the `ctx` table in some other place. ## resty.core.socket * [socket.setoption](https://github.com/openresty/lua-nginx-module#tcpsocksetoption) +* [socket.setclientcert](https://github.com/openresty/lua-nginx-module#tcpsocksetclientcert) +* [socket.sslhandshake](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake) [Back to TOC](#table-of-contents) diff --git a/lib/resty/core/socket.lua b/lib/resty/core/socket.lua index 1a504ecfc..ede5b663f 100644 --- a/lib/resty/core/socket.lua +++ b/lib/resty/core/socket.lua @@ -1,18 +1,27 @@ local base = require "resty.core.base" -base.allows_subsystem('http') -local debug = require 'debug' -local ffi = require 'ffi' +base.allows_subsystem("http") +local debug = require "debug" +local ffi = require "ffi" -local error = error +local error = error +local assert = assert local tonumber = tonumber +local tostring = tostring +local type = type +local select = select local registry = debug.getregistry() + +local C = ffi.C local ffi_new = ffi.new -local ffi_string = ffi.string -local C = ffi.C +local ffi_str = ffi.string +local ffi_gc = ffi.gc + local get_string_buf = base.get_string_buf -local get_size_ptr = base.get_size_ptr -local tostring = tostring +local get_size_ptr = base.get_size_ptr +local get_request = base.get_request + +local co_yield = coroutine._yield local option_index = { @@ -35,14 +44,36 @@ ngx_http_lua_ffi_socket_tcp_getoption(ngx_http_lua_socket_tcp_upstream_t *u, int ngx_http_lua_ffi_socket_tcp_setoption(ngx_http_lua_socket_tcp_upstream_t *u, int opt, int val, unsigned char *err, size_t *errlen); + +int +ngx_http_lua_ffi_socket_tcp_sslhandshake(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, void *sess, + int enable_session_reuse, ngx_str_t *server_name, int verify, + int ocsp_status_req, void *chain, void *pkey, char **errmsg); + +int +ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, void **sess, char **errmsg, + int *openssl_error_code); + +void +ngx_http_lua_ffi_ssl_free_session(void *sess); ]] local output_value_buf = ffi_new("int[1]") -local FFI_OK = base.FFI_OK -local SOCKET_CTX_INDEX = 1 local ERR_BUF_SIZE = 4096 +local FFI_OK = base.FFI_OK +local FFI_ERROR = base.FFI_ERROR +local FFI_DONE = base.FFI_DONE +local FFI_AGAIN = base.FFI_AGAIN +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX + +local SOCKET_CTX_INDEX = 1 +local SOCKET_CLIENT_CERT_INDEX = 6 +local SOCKET_CLIENT_PKEY_INDEX = 7 + local function get_tcp_socket(cosocket) local tcp_socket = cosocket[SOCKET_CTX_INDEX] @@ -75,7 +106,7 @@ local function getoption(cosocket, option) err, errlen) if rc ~= FFI_OK then - return nil, ffi_string(err, errlen[0]) + return nil, ffi_str(err, errlen[0]) end return tonumber(output_value_buf[0]) @@ -107,17 +138,134 @@ local function setoption(cosocket, option, value) err, errlen) if rc ~= FFI_OK then - return nil, ffi_string(err, errlen[0]) + return nil, ffi_str(err, errlen[0]) end return true end +local errmsg = base.get_errmsg_ptr() +local session_ptr = ffi_new("void *[1]") +local server_name_str = ffi_new("ngx_str_t[1]") +local openssl_error_code = ffi_new("int[1]") + + +local function setclientcert(cosocket, cert, pkey) + if not cert and not pkey then + cosocket[SOCKET_CLIENT_CERT_INDEX] = nil + cosocket[SOCKET_CLIENT_PKEY_INDEX] = nil + return true + end + + if not cert or not pkey then + return nil, + "client certificate must be supplied with corresponding " .. + "private key" + end + + if type(cert) ~= "cdata" then + return nil, "bad cert arg: cdata expected, got " .. type(cert) + end + + if type(pkey) ~= "cdata" then + return nil, "bad pkey arg: cdata expected, got " .. type(pkey) + end + + cosocket[SOCKET_CLIENT_CERT_INDEX] = cert + cosocket[SOCKET_CLIENT_PKEY_INDEX] = pkey + + return true +end + + +local function sslhandshake(cosocket, reused_session, server_name, ssl_verify, + send_status_req, ...) + + local n = select("#", ...) + if not cosocket or n > 0 then + error("ngx.socket sslhandshake: expecting 1 ~ 5 arguments " .. + "(including the object), but seen " .. (cosocket and 5 + n or 0)) + end + + local r = get_request() + if not r then + error("no request found", 2) + end + + session_ptr[0] = type(reused_session) == "cdata" and reused_session or nil + + if server_name then + server_name_str[0].data = server_name + server_name_str[0].len = #server_name + + else + server_name_str[0].data = nil + server_name_str[0].len = 0 + end + + local u = get_tcp_socket(cosocket) + + local rc = C.ngx_http_lua_ffi_socket_tcp_sslhandshake(r, u, + session_ptr[0], + reused_session ~= false, + server_name_str, + ssl_verify and 1 or 0, + send_status_req and 1 or 0, + cosocket[SOCKET_CLIENT_CERT_INDEX], + cosocket[SOCKET_CLIENT_PKEY_INDEX], + errmsg) + + if rc == FFI_NO_REQ_CTX then + error("no request ctx found", 2) + end + + while true do + if rc == FFI_ERROR then + if openssl_error_code[0] ~= 0 then + return nil, openssl_error_code[0] .. ": " .. ffi_str(errmsg[0]) + end + + return nil, ffi_str(errmsg[0]) + end + + if rc == FFI_DONE then + return reused_session + end + + if rc == FFI_OK then + if reused_session == false then + return true + end + + rc = C.ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(r, u, + session_ptr, errmsg, openssl_error_code) + + assert(rc == FFI_OK) + + if session_ptr[0] == nil then + return session_ptr[0] + end + + return ffi_gc(session_ptr[0], C.ngx_http_lua_ffi_ssl_free_session) + end + + assert(rc == FFI_AGAIN) + + co_yield() + + rc = C.ngx_http_lua_ffi_socket_tcp_get_sslhandshake_result(r, u, + session_ptr, errmsg, openssl_error_code) + end +end + + do local method_table = registry.__tcp_cosocket_mt method_table.getoption = getoption method_table.setoption = setoption + method_table.setclientcert = setclientcert + method_table.sslhandshake = sslhandshake end diff --git a/t/ocsp.t b/t/ocsp.t index 36af8b7c7..23a51017c 100644 --- a/t/ocsp.t +++ b/t/ocsp.t @@ -97,7 +97,7 @@ __DATA__ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -189,7 +189,7 @@ OCSP url found: http://127.0.0.1:8888/ocsp?foo=1, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -281,7 +281,7 @@ OCSP responder not found GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -372,7 +372,7 @@ failed to get OCSP responder: no issuer certificate in chain GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -467,7 +467,7 @@ failed to get OCSP responder: issuer certificate not next to leaf GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -564,7 +564,7 @@ still get an error: truncated GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -657,7 +657,7 @@ OCSP request created with length 68 GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -831,7 +831,7 @@ failed to create OCSP request: d2i_X509_bio() failed GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -922,7 +922,7 @@ failed to create OCSP request: no issuer certificate in chain GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -1015,7 +1015,7 @@ OCSP response validation ok GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -1107,7 +1107,7 @@ OCSP response validation ok GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -1202,7 +1202,7 @@ FIXME: we should complain in this case. GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -1296,7 +1296,7 @@ OCSP response validation ok GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -1472,7 +1472,7 @@ FIXME: check the OCSP staple actually received by the ssl client GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" @@ -1554,7 +1554,7 @@ ocsp status resp set ok: nil, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata --- error_log lua ssl server name: "test.com" diff --git a/t/ssl-client-hello.t b/t/ssl-client-hello.t index 7dcf9e74c..f15e0d923 100644 --- a/t/ssl-client-hello.t +++ b/t/ssl-client-hello.t @@ -108,7 +108,7 @@ __DATA__ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -209,7 +209,7 @@ read SNI name from Lua: test.com GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -340,7 +340,7 @@ read SNI name from Lua: nil, type: nil GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -442,7 +442,7 @@ read SNI name from Lua: test.com GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -548,7 +548,7 @@ read SNI name from Lua: nil, type: nil GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -654,7 +654,7 @@ close: 1 nil GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx diff --git a/t/ssl-session-fetch.t b/t/ssl-session-fetch.t index 9677ca70f..879f464d9 100644 --- a/t/ssl-session-fetch.t +++ b/t/ssl-session-fetch.t @@ -89,7 +89,7 @@ __DATA__ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -176,7 +176,7 @@ qr/ssl_session_fetch_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-f\d]+/s, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -279,7 +279,7 @@ In practice, never store session in plaintext on persistent storage. GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -375,7 +375,7 @@ able to carry on and negotiate a new session. GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- grep_error_log eval @@ -582,7 +582,7 @@ $/s, GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- no_error_log [alert] diff --git a/t/ssl-session-store.t b/t/ssl-session-store.t index 8a24a8ec1..04b9c49ba 100644 --- a/t/ssl-session-store.t +++ b/t/ssl-session-store.t @@ -85,7 +85,7 @@ __DATA__ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log eval @@ -157,7 +157,7 @@ qr/ssl_session_store_by_lua\(nginx.conf:\d+\):4: session size: \d+/s GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log eval @@ -265,7 +265,7 @@ qr/ssl_session_store_by_lua\(nginx.conf:\d+\):4: session id: [a-fA-f\d]+/s GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata close: 1 nil --- error_log eval diff --git a/t/ssl.t b/t/ssl.t index e5444b2a1..78a6387e5 100644 --- a/t/ssl.t +++ b/t/ssl.t @@ -210,7 +210,7 @@ sslv3 alert handshake failure GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -309,7 +309,7 @@ lua ssl server name: "test.com" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -409,7 +409,7 @@ read SNI name from Lua: test.com GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -525,7 +525,7 @@ read SNI name from Lua: nil, type: nil GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -646,7 +646,7 @@ qr/Using unix socket file .*?nginx\.sock/ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -765,7 +765,7 @@ Using IPv4 address: 127.0.0.1 GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -888,7 +888,7 @@ Using IPv6 address: 0.0.0.1 GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1015,7 +1015,7 @@ lua ssl server name: "test.com" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1540,7 +1540,7 @@ ssl cert by lua done GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -1673,7 +1673,7 @@ lua ssl server name: "test.com" GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -2148,7 +2148,7 @@ qr/\[error\] .*? failed to parse pem key: PEM_read_bio_PrivateKey\(\) failed/ GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -2587,7 +2587,7 @@ client certificate subject: nil GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -2687,7 +2687,7 @@ read server port from Lua: 12345 GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx @@ -2819,7 +2819,7 @@ read server port from Lua: nilunix domain has no port GET /t --- response_body connected: 1 -ssl handshake: userdata +ssl handshake: cdata sent http request: 56 bytes. received: HTTP/1.1 201 Created received: Server: nginx