diff --git a/.ci/setup_env.sh b/.ci/setup_env.sh index 22d70b87..7c3329a3 100644 --- a/.ci/setup_env.sh +++ b/.ci/setup_env.sh @@ -20,7 +20,15 @@ BUILD_TOOLS_DOWNLOAD=$DOWNLOAD_ROOT/kong-build-tools BUILD_TOOLS_BRANCH=${BUILD_TOOLS_BRANCH:=master} if [ ! -d $BUILD_TOOLS_DOWNLOAD ]; then - git clone -b $BUILD_TOOLS_BRANCH https://github.com/Kong/kong-build-tools.git $BUILD_TOOLS_DOWNLOAD + #git clone -b $BUILD_TOOLS_BRANCH https://github.com/Kong/kong-build-tools.git $BUILD_TOOLS_DOWNLOAD + + # temporary operation, get pr430 + git clone https://github.com/Kong/kong-build-tools.git $BUILD_TOOLS_DOWNLOAD + + pushd $BUILD_TOOLS_DOWNLOAD + git fetch origin pull/430/head:pr430 + git checkout pr430 + popd else pushd $BUILD_TOOLS_DOWNLOAD git fetch diff --git a/.requirements b/.requirements index fd6c5d65..7a825690 100644 --- a/.requirements +++ b/.requirements @@ -1,3 +1,3 @@ -RESTY_VERSION=1.19.3.2 +RESTY_VERSION=1.19.9.1 RESTY_LUAROCKS_VERSION=3.7.0 RESTY_OPENSSL_VERSION=1.1.1k diff --git a/config b/config index c901d549..cf834d0b 100644 --- a/config +++ b/config @@ -1,6 +1,26 @@ +# search ngx_lua's src path +for inc in $CORE_INCS +do + ngx_lua_path=${inc%/api} + + if [ "$inc" != "$ngx_lua_path" ]; then + + ngx_lua_header="$ngx_lua_path/ngx_http_lua_common.h" + + if [ -f "$ngx_lua_header" ]; then + CORE_INCS="$CORE_INCS $ngx_lua_path" + break + fi + + fi +done + ngx_module_type=HTTP ngx_module_name=ngx_http_lua_kong_module -ngx_module_srcs="$ngx_addon_dir/src/ngx_http_lua_kong_module.c" +ngx_module_srcs=" \ + $ngx_addon_dir/src/ngx_http_lua_kong_module.c \ + $ngx_addon_dir/src/ngx_http_lua_kong_socket_tcp.c \ + " ngx_module_incs="$ngx_addon_dir/src" . auto/module diff --git a/lualib/resty/kong/socket_tcp.lua b/lualib/resty/kong/socket_tcp.lua new file mode 100644 index 00000000..4cd2d77a --- /dev/null +++ b/lualib/resty/kong/socket_tcp.lua @@ -0,0 +1,190 @@ +local ffi = require("ffi") +local base = require("resty.core.base") + +local C = ffi.C +local ffi_string = ffi.string +local ffi_gc = ffi.gc +local FFI_ERROR = base.FFI_ERROR +local FFI_DONE = base.FFI_DONE +local FFI_OK = base.FFI_OK +local FFI_AGAIN = base.FFI_AGAIN +local FFI_NO_REQ_CTX = base.FFI_NO_REQ_CTX +local get_request = base.get_request +local error = error +local assert = assert +local type = type +local pcall = pcall +local select = select +local co_yield = coroutine._yield +local table_new = require("table.new") +local table_clear = require("table.clear") + +ffi.cdef[[ +typedef struct ngx_http_lua_socket_tcp_upstream_s + ngx_http_lua_socket_tcp_upstream_t; + +int ngx_http_lua_ffi_socket_tcp_tlshandshake(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_tlshandshake_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_tls_free_session(void *sess); +]] + + +local SOCKET_CTX_INDEX = 1 + + +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 cached_options = table_new(0, 4) + + +local function tlshandshake(self, options) + if not options then + table_clear(cached_options) + options = cached_options + + elseif type(options) ~= "table" then + error("bad options table type") + end + + local r = get_request() + + if not r then + error("no request found") + end + + local reused_session = options.reused_session + session_ptr[0] = type(reused_session) == "cdata" and reused_session or nil + + if options.server_name then + server_name_str[0].data = options.server_name + server_name_str[0].len = #options.server_name + + else + server_name_str[0].data = nil + server_name_str[0].len = 0 + end + + local client_cert = options.client_cert + local client_priv_key = options.client_priv_key + if client_cert then + if not client_priv_key then + error("client certificate supplied without " + .. "corresponding private key", 2) + end + + if type(client_cert) ~= "cdata" + or type(client_priv_key) ~= "cdata" + then + error("wrong type of client certificate or private key supplied", 2) + end + end + + local rc = + C.ngx_http_lua_ffi_socket_tcp_tlshandshake(r, self[SOCKET_CTX_INDEX], + session_ptr[0], + reused_session ~= false, + server_name_str, + options.verify and 1 or 0, + options.ocsp_status_req + and 1 or 0, + client_cert, + client_priv_key, + errmsg) + + if rc == FFI_NO_REQ_CTX then + error("no request ctx found", 2) + end + +::again:: + + if rc == FFI_ERROR then + if openssl_error_code[0] ~= 0 then + return nil, openssl_error_code[0] .. ": " .. ffi_string(errmsg[0]) + end + + return nil, ffi_string(errmsg[0]) + end + + if rc == FFI_DONE then + return options.reused_session + end + + if rc == FFI_OK then + if options.reused_session == false then + return true + end + + rc = C.ngx_http_lua_ffi_socket_tcp_get_tlshandshake_result(r, + self[SOCKET_CTX_INDEX], 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_tls_free_session) + end + + assert(rc == FFI_AGAIN) + + co_yield() + + rc = C.ngx_http_lua_ffi_socket_tcp_get_tlshandshake_result(r, + self[SOCKET_CTX_INDEX], session_ptr, errmsg, openssl_error_code) + + assert(rc == FFI_OK or rc == FFI_ERROR) + + goto again +end + + +local function sslhandshake(self, reused_session, server_name, ssl_verify, + send_status_req, ...) + + local n = select("#", ...) + if not self or n > 1 then + error("ngx.socket sslhandshake: expecting 1 ~ 5 " + .. "arguments (including the object), but seen " .. n) + end + + cached_options.reused_session = reused_session + cached_options.server_name = server_name + cached_options.verify = ssl_verify + cached_options.ocsp_status_req = send_status_req + + local res, err = tlshandshake(self, cached_options) + table_clear(cached_options) + + return res, err +end + + +do + local old_socket_tcp = ngx.socket.tcp + + function ngx.socket.tcp() + local ok, sock = pcall(old_socket_tcp) + if not ok then + error(sock, 2) + end + + sock.tlshandshake = tlshandshake + sock.sslhandshake = sslhandshake + + return sock + end +end + + +return { + version = base.version +} diff --git a/src/ngx_http_lua_kong_socket_tcp.c b/src/ngx_http_lua_kong_socket_tcp.c new file mode 100644 index 00000000..21e073ba --- /dev/null +++ b/src/ngx_http_lua_kong_socket_tcp.c @@ -0,0 +1,493 @@ +/** + * Copyright 2019-2021 Kong Inc. + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#if 1 + +#include "ngx_http_lua_socket_tcp.h" +#include "ngx_http_lua_util.h" +#include "ngx_http_lua_contentby.h" + +enum { + SOCKET_OP_CONNECT, + SOCKET_OP_READ, + SOCKET_OP_WRITE, + SOCKET_OP_RESUME_CONN, +}; + +void ngx_http_lua_coctx_cleanup(void *data); +void ngx_http_lua_socket_tcp_handler(ngx_event_t *ev); +void ngx_http_lua_socket_handle_conn_success(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u); +int ngx_http_lua_socket_conn_error_retval_handler(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); +void ngx_http_lua_socket_handle_conn_error(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, ngx_uint_t ft_type); + +#endif + +#if (NGX_HTTP_SSL) + +static void ngx_http_lua_tls_handshake_handler(ngx_connection_t *c); +static int ngx_http_lua_tls_handshake_retval_handler(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L); + +static const char * +ngx_http_lua_socket_tcp_check_busy(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, unsigned int ops) +{ + if (ops & SOCKET_OP_CONNECT && u->conn_waiting) { + return "socket busy connecting"; + } + + if (ops & SOCKET_OP_READ && u->read_waiting) { + return "socket busy reading"; + } + + if (ops & SOCKET_OP_WRITE + && (u->write_waiting + || (u->raw_downstream + && (r->connection->buffered & NGX_HTTP_LOWLEVEL_BUFFERED)))) + { + return "socket busy writing"; + } + + return NULL; +} + + +int +ngx_http_lua_ffi_socket_tcp_tlshandshake(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, ngx_ssl_session_t *sess, + int enable_session_reuse, ngx_str_t *server_name, int verify, + int ocsp_status_req, STACK_OF(X509) *chain, EVP_PKEY *pkey, + const char **errmsg) +{ + ngx_int_t rc, i; + ngx_connection_t *c; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_co_ctx_t *coctx; + const char *busy_rc; + ngx_ssl_conn_t *ssl_conn; + X509 *x509; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tcp socket tls handshake"); + + if (u == NULL + || u->peer.connection == NULL + || u->read_closed + || u->write_closed) + { + *errmsg = "closed"; + return NGX_ERROR; + } + + if (u->request != r) { + *errmsg = "bad request"; + return NGX_ERROR; + } + + busy_rc = ngx_http_lua_socket_tcp_check_busy(r, u, SOCKET_OP_CONNECT + | SOCKET_OP_READ + | SOCKET_OP_WRITE); + if (busy_rc != NULL) { + *errmsg = busy_rc; + return NGX_ERROR; + } + + if (u->raw_downstream || u->body_downstream) { + *errmsg = "not supported for downstream sockets"; + return NGX_ERROR; + } + + c = u->peer.connection; + + u->ssl_session_reuse = 1; + + if (c->ssl && c->ssl->handshaked) { + if (sess != NULL) { + return NGX_DONE; + } + + u->ssl_session_reuse = enable_session_reuse; + + (void) ngx_http_lua_tls_handshake_retval_handler(r, u, NULL); + + return NGX_OK; + } + + if (ngx_ssl_create_connection(u->conf->ssl, c, + NGX_SSL_BUFFER|NGX_SSL_CLIENT) + != NGX_OK) + { + *errmsg = "failed to create ssl connection"; + return NGX_ERROR; + } + + ssl_conn = c->ssl->connection; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return NGX_HTTP_LUA_FFI_NO_REQ_CTX; + } + + coctx = ctx->cur_co_ctx; + + c->sendfile = 0; + + if (sess != NULL) { + if (ngx_ssl_set_session(c, sess) != NGX_OK) { + *errmsg = "tls set session failed"; + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "lua tls set session: %p", sess); + + } else { + u->ssl_session_reuse = enable_session_reuse; + } + + if (chain != NULL) { + ngx_http_lua_assert(pkey != NULL); /* ensured by resty.core */ + + if (sk_X509_num(chain) < 1) { + ERR_clear_error(); + *errmsg = "invalid client certificate chain"; + return NGX_ERROR; + } + + x509 = sk_X509_value(chain, 0); + if (x509 == NULL) { + ERR_clear_error(); + *errmsg = "tls fetch client certificate from chain failed"; + return NGX_ERROR; + } + + if (SSL_use_certificate(ssl_conn, x509) == 0) { + ERR_clear_error(); + *errmsg = "tls set client certificate failed"; + return NGX_ERROR; + } + + /* read rest of the chain */ + + for (i = 1; i < sk_X509_num(chain); i++) { + x509 = sk_X509_value(chain, i); + if (x509 == NULL) { + ERR_clear_error(); + *errmsg = "tls fetch client intermediate certificate from " + "chain failed"; + return NGX_ERROR; + } + + if (SSL_add1_chain_cert(ssl_conn, x509) == 0) { + ERR_clear_error(); + *errmsg = "tls set client intermediate certificate failed"; + return NGX_ERROR; + } + } + + if (SSL_use_PrivateKey(ssl_conn, pkey) == 0) { + ERR_clear_error(); + *errmsg = "tls set client private key failed"; + return NGX_ERROR; + } + } + + if (server_name != NULL && server_name->data != NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua tls server name: \"%V\"", server_name); + +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if (SSL_set_tlsext_host_name(c->ssl->connection, + (char *) server_name->data) + == 0) + { + *errmsg = "SSL_set_tlsext_host_name failed"; + return NGX_ERROR; + } + +#else + *errmsg = "no TLS extension support"; + return NGX_ERROR; +#endif + } + + u->ssl_verify = verify; + + if (ocsp_status_req) { +#ifdef NGX_HTTP_LUA_USE_OCSP + SSL_set_tlsext_status_type(c->ssl->connection, + TLSEXT_STATUSTYPE_ocsp); + +#else + *errmsg = "no OCSP support"; + return NGX_ERROR; +#endif + } + + if (server_name->len == 0) { + u->ssl_name.len = 0; + + } else { + if (u->ssl_name.data) { + /* buffer already allocated */ + + if (u->ssl_name.len >= server_name->len) { + /* reuse it */ + ngx_memcpy(u->ssl_name.data, server_name->data, + server_name->len); + u->ssl_name.len = server_name->len; + + } else { + ngx_free(u->ssl_name.data); + goto new_ssl_name; + } + + } else { + +new_ssl_name: + + u->ssl_name.data = ngx_alloc(server_name->len, ngx_cycle->log); + if (u->ssl_name.data == NULL) { + u->ssl_name.len = 0; + *errmsg = "no memory"; + return NGX_ERROR; + } + + ngx_memcpy(u->ssl_name.data, server_name->data, server_name->len); + u->ssl_name.len = server_name->len; + } + } + + u->write_co_ctx = coctx; + +#if 0 +#ifdef NGX_HTTP_LUA_USE_OCSP + SSL_set_tlsext_status_type(c->ssl->connection, TLSEXT_STATUSTYPE_ocsp); +#endif +#endif + + rc = ngx_ssl_handshake(c); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "ngx_ssl_handshake returned: %d", rc); + + if (rc == NGX_AGAIN) { + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + ngx_add_timer(c->read, u->connect_timeout); + + u->conn_waiting = 1; + u->write_prepare_retvals = ngx_http_lua_tls_handshake_retval_handler; + + ngx_http_lua_cleanup_pending_operation(coctx); + coctx->cleanup = ngx_http_lua_coctx_cleanup; + coctx->data = u; + + c->ssl->handler = ngx_http_lua_tls_handshake_handler; + + if (ctx->entered_content_phase) { + r->write_event_handler = ngx_http_lua_content_wev_handler; + + } else { + r->write_event_handler = ngx_http_core_run_phases; + } + + return NGX_AGAIN; + } + + ngx_http_lua_tls_handshake_handler(c); + + if (rc == NGX_ERROR) { + *errmsg = u->error_ret; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_lua_tls_handshake_handler(ngx_connection_t *c) +{ + int waiting; + ngx_int_t rc; + ngx_connection_t *dc; /* downstream connection */ + ngx_http_request_t *r; + ngx_http_lua_ctx_t *ctx; + ngx_http_lua_loc_conf_t *llcf; + + ngx_http_lua_socket_tcp_upstream_t *u; + + u = c->data; + r = u->request; + + ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module); + if (ctx == NULL) { + return; + } + + c->write->handler = ngx_http_lua_socket_tcp_handler; + c->read->handler = ngx_http_lua_socket_tcp_handler; + + waiting = u->conn_waiting; + + dc = r->connection; + + if (c->read->timedout) { + u->error_ret = "timeout"; + goto failed; + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (c->ssl->handshaked) { + if (u->ssl_verify) { + rc = SSL_get_verify_result(c->ssl->connection); + + if (rc != X509_V_OK) { + u->error_ret = X509_verify_cert_error_string(rc); + u->openssl_error_code_ret = rc; + + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + if (llcf->log_socket_errors) { + ngx_log_error(NGX_LOG_ERR, dc->log, 0, "lua tls " + "certificate verify error: (%d: %s)", + rc, u->error_ret); + } + + goto failed; + } + +#if (nginx_version >= 1007000) + + if (u->ssl_name.len + && ngx_ssl_check_host(c, &u->ssl_name) != NGX_OK) + { + u->error_ret = "certificate host mismatch"; + + llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module); + if (llcf->log_socket_errors) { + ngx_log_error(NGX_LOG_ERR, dc->log, 0, "lua tls " + "certificate does not match host \"%V\"", + &u->ssl_name); + } + + goto failed; + } + +#endif + } + + if (waiting) { + ngx_http_lua_socket_handle_conn_success(r, u); + + } else { + (void) ngx_http_lua_tls_handshake_retval_handler(r, u, NULL); + } + + if (waiting) { + ngx_http_run_posted_requests(dc); + } + + return; + } + + u->error_ret = "handshake failed"; + +failed: + + if (waiting) { + u->write_prepare_retvals = + ngx_http_lua_socket_conn_error_retval_handler; + ngx_http_lua_socket_handle_conn_error(r, u, NGX_HTTP_LUA_SOCKET_FT_SSL); + ngx_http_run_posted_requests(dc); + + } else { + u->ft_type |= NGX_HTTP_LUA_SOCKET_FT_SSL; + + (void) ngx_http_lua_socket_conn_error_retval_handler(r, u, NULL); + } +} + + +int +ngx_http_lua_ffi_socket_tcp_get_tlshandshake_result(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, ngx_ssl_session_t **sess, + const char **errmsg, int *openssl_error_code) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "lua cosocket get TLS handshake result for upstream: %p", u); + + if (u->error_ret != NULL) { + *errmsg = u->error_ret; + *openssl_error_code = u->openssl_error_code_ret; + + return NGX_ERROR; + } + + *sess = u->ssl_session_ret; + + return NGX_OK; +} + + +static int +ngx_http_lua_tls_handshake_retval_handler(ngx_http_request_t *r, + ngx_http_lua_socket_tcp_upstream_t *u, lua_State *L) +{ + ngx_connection_t *c; + ngx_ssl_session_t *ssl_session; + + if (!u->ssl_session_reuse) { + return 0; + } + + c = u->peer.connection; + + ssl_session = ngx_ssl_get_session(c); + if (ssl_session == NULL) { + u->ssl_session_ret = NULL; + + } else { + u->ssl_session_ret = ssl_session; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "lua tls save session: %p", ssl_session); + } + + return 0; +} + + +void +ngx_http_lua_ffi_tls_free_session(ngx_ssl_session_t *sess) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0, + "lua tls free session: %p", sess); + + ngx_ssl_free_session(sess); +} + + +#endif /* NGX_HTTP_SSL */ diff --git a/t/006-mtls-cosocket.t b/t/006-mtls-cosocket.t new file mode 100644 index 00000000..80263258 --- /dev/null +++ b/t/006-mtls-cosocket.t @@ -0,0 +1,449 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; + +repeat_each(2); + +plan tests => repeat_each() * 42; + +$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8'; + +log_level 'debug'; + +no_long_string(); +#no_diff(); + +sub read_file { + my $infile = shift; + open my $in, $infile + or die "cannot open $infile for reading: $!"; + my $cert = do { local $/; <$in> }; + close $in; + $cert; +} + +our $MTLSCA = read_file("t/cert/mtls_ca.crt"); +our $MTLSClient = read_file("t/cert/mtls_client.crt"); +our $MTLSClientKey = read_file("t/cert/mtls_client.key"); +our $MTLSServer = read_file("t/cert/mtls_server.crt"); +our $MTLSServerKey = read_file("t/cert/mtls_server.key"); + +our $HtmlDir = html_dir; + +our $mtls_http_config = <<"_EOC_"; +init_by_lua_block { require "resty.kong.socket_tcp" } +server { + listen unix:$::HtmlDir/mtls.sock ssl; + + ssl_certificate $::HtmlDir/mtls_server.crt; + ssl_certificate_key $::HtmlDir/mtls_server.key; + ssl_client_certificate $::HtmlDir/mtls_ca.crt; + ssl_verify_client on; + server_tokens off; + + location / { + return 200 "hello, \$ssl_client_s_dn"; + } +} +_EOC_ + +our $mtls_user_files = <<"_EOC_"; +>>> mtls_server.key +$::MTLSServerKey +>>> mtls_server.crt +$::MTLSServer +>>> mtls_ca.crt +$::MTLSCA +>>> mtls_client.key +$::MTLSClientKey +>>> mtls_client.crt +$::MTLSClient +_EOC_ + +run_tests(); + +__DATA__ + +=== TEST 1: sanity: www.google.com +--- http_config + init_by_lua_block { require "resty.kong.socket_tcp" } +--- config + server_tokens off; + resolver $TEST_NGINX_RESOLVER ipv6=off; + + location /t { + content_by_lua_block { + -- avoid flushing google in "check leak" testing mode: + local counter = package.loaded.counter + if not counter then + counter = 1 + elseif counter >= 2 then + return ngx.exit(503) + else + counter = counter + 1 + end + + package.loaded.counter = counter + + do + local sock = ngx.socket.tcp() + sock:settimeout(2000) + + local ok, err = sock:connect("www.google.com", 443) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:tlshandshake() + if not sess then + ngx.say("failed to do TLS handshake: ", err) + return + end + + ngx.say("tls handshake: ", type(sess)) + + local req = "GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n" + local bytes, err = sock:send(req) + if not bytes then + ngx.say("failed to send http request: ", err) + return + end + + ngx.say("sent http request: ", bytes, " bytes.") + + local line, err = sock:receive() + if not line then + ngx.say("failed to receive response status line: ", err) + return + end + + ngx.say("received: ", line) + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + + collectgarbage() + } + } +--- request +GET /t +--- response_body_like chop +\Aconnected: 1 +tls handshake: cdata +sent http request: 59 bytes. +received: HTTP/1.1 (?:200 OK|302 Found) +close: 1 nil +\z +--- grep_error_log eval: qr/lua tls (?:set|save|free) session: [0-9A-F]+/ +--- grep_error_log_out eval +qr/^lua tls save session: ([0-9A-F]+) +lua tls free session: ([0-9A-F]+) +$/ +--- no_error_log +lua tls server name: +SSL reused session +[error] +[alert] +--- timeout: 5 + + + +=== TEST 2: bad options table +--- http_config + init_by_lua_block { require "resty.kong.socket_tcp" } +--- config + resolver $TEST_NGINX_RESOLVER ipv6=off; + + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + sock:settimeout(7000) + + local ok, err = sock:connect("openresty.org", 443) + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local session, err = sock:tlshandshake("foo") + } + } +--- request +GET /t +--- ignore_response +--- error_log eval +qr/\[error\] .* bad options table type/ +--- no_error_log +[alert] +--- timeout: 10 + + + +=== TEST 3: mutual TLS handshake, upstream is not accessible without client certs (no options table) +--- http_config eval: $::mtls_http_config +--- config eval +" + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect('unix:$::HtmlDir/mtls.sock') + if not ok then + ngx.say('failed to connect: ', err) + end + + assert(sock:tlshandshake()) + + ngx.say('connected: ', ok) + + local req = 'GET /\\r\\n' + + local bytes, err = sock:send(req) + if not bytes then + ngx.say('failed to send request: ', err) + return + end + + ngx.say('request sent: ', bytes) + + ngx.say(sock:receive('*a')) + + assert(sock:close()) + } + } +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- response_body_like: 400 No required SSL certificate was sent +--- no_error_log +[alert] +[error] +[crit] +[emerg] + + + +=== TEST 4: mutual TLS handshake, upstream is not accessible without client certs (empty options table) +--- http_config eval: $::mtls_http_config +--- config eval +" + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect('unix:$::HtmlDir/mtls.sock') + if not ok then + ngx.say('failed to connect: ', err) + end + + assert(sock:tlshandshake({})) + + ngx.say('connected: ', ok) + + local req = 'GET /\\r\\n' + + local bytes, err = sock:send(req) + if not bytes then + ngx.say('failed to send request: ', err) + return + end + + ngx.say('request sent: ', bytes) + + ngx.say(sock:receive('*a')) + + assert(sock:close()) + } + } +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- response_body_like: 400 No required SSL certificate was sent +--- no_error_log +[alert] +[error] +[crit] +[emerg] + + + +=== TEST 5: mutual TLS handshake, upstream is accessible with client certs +--- http_config eval: $::mtls_http_config +--- config eval +" + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect('unix:$::HtmlDir/mtls.sock') + if not ok then + ngx.say('failed to connect: ', err) + end + + local f = assert(io.open('$::HtmlDir/mtls_client.crt')) + local cert_data = f:read('*a') + f:close() + + f = assert(io.open('$::HtmlDir/mtls_client.key')) + local key_data = f:read('*a') + f:close() + + local ssl = require('ngx.ssl') + + local chain = assert(ssl.parse_pem_cert(cert_data)) + local priv = assert(ssl.parse_pem_priv_key(key_data)) + + assert(sock:tlshandshake({ client_cert = chain, client_priv_key = priv })) + + ngx.say('connected: ', ok) + + local req = 'GET /\\r\\n' + + local bytes, err = sock:send(req) + if not bytes then + ngx.say('failed to send request: ', err) + return + end + + ngx.say('request sent: ', bytes) + + ngx.say(sock:receive('*a')) + + assert(sock:close()) + } + } +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- response_body +connected: 1 +request sent: 7 +hello, CN=foo@example.com,O=OpenResty,ST=California,C=US +--- no_error_log +[alert] +[error] +[crit] +[emerg] + + + +=== TEST 6: incorrect type of client cert +--- http_config + init_by_lua_block { require "resty.kong.socket_tcp" } +--- config + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect("127.0.0.1", ngx.var.server_port) + if not ok then + ngx.say("failed to connect: ", err) + end + + ok, err = sock:tlshandshake({ client_cert = "doesnt", client_priv_key = "work" }) + if not ok then + ngx.say("failed to handshake: ", err) + end + + assert(sock:close()) + } + } +--- request +GET /t +--- error_code: 500 +--- no_error_log +[alert] +[crit] +[emerg] +--- error_log +wrong type of client certificate or private key supplied + + +=== TEST 7: incorrect type of client key +--- http_config + init_by_lua_block { require "resty.kong.socket_tcp" } +--- config eval +" + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect('127.0.0.1', ngx.var.server_port) + if not ok then + ngx.say('failed to connect: ', err) + end + + local f = assert(io.open('$::HtmlDir/mtls_client.crt')) + local cert_data = f:read('*a') + f:close() + + local ssl = require('ngx.ssl') + + local chain = assert(ssl.parse_pem_cert(cert_data)) + + ok, err = sock:tlshandshake({ client_cert = chain, client_priv_key = 'work' }) + if not ok then + ngx.say('failed to handshake: ', err) + end + + assert(sock:close()) + } + } +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- error_code: 500 +--- no_error_log +[alert] +[crit] +[emerg] +--- error_log +wrong type of client certificate or private key supplied + + + +=== TEST 8: missing private key +--- http_config + init_by_lua_block { require "resty.kong.socket_tcp" } +--- config eval +" + location /t { + content_by_lua_block { + local sock = ngx.socket.tcp() + local ok, err = sock:connect('127.0.0.1', ngx.var.server_port) + if not ok then + ngx.say('failed to connect: ', err) + end + + local f = assert(io.open('$::HtmlDir/mtls_client.crt')) + local cert_data = f:read('*a') + f:close() + + local ssl = require('ngx.ssl') + + local chain = assert(ssl.parse_pem_cert(cert_data)) + + ok, err = sock:tlshandshake({ client_cert = chain }) + if not ok then + ngx.say('failed to handshake: ', err) + end + + assert(sock:close()) + } + } +" +--- user_files eval: $::mtls_user_files +--- request +GET /t +--- error_code: 500 +--- no_error_log +[alert] +[crit] +[emerg] +--- error_log +client certificate supplied without corresponding private key diff --git a/t/cert/mtls_ca.crt b/t/cert/mtls_ca.crt new file mode 100644 index 00000000..99c8ec83 --- /dev/null +++ b/t/cert/mtls_ca.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUfTh89NyxxmbVwNZ/YFddssWc+WkwDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoM +CU9wZW5SZXN0eTEiMCAGA1UEAwwZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAe +Fw0xOTA5MTMyMjI4MTJaFw0zOTA5MDgyMjI4MTJaMFoxCzAJBgNVBAYTAlVTMRMw +EQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQKDAlPcGVuUmVzdHkxIjAgBgNVBAMM +GU9wZW5SZXN0eSBUZXN0aW5nIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDcMg2DeV8z+0E2ZiXUax111lKzhAbMCK0RJlV9tAi+YdcsDR/t +zvvAZNGONUoewUuz/7E88oweh+Xi1GJtvd0DjB70y7tgpf5PUXovstWVwy7s5jZo +kgn62yi9ZOOZpjwnYTBviirtRTnZRwkzL6wF0xMyJjAbKBJuPMrMiyFdh82lt7wI +NS4mhyEdM0UiVVxfC2uzsddTOcOJURfGbW7UZm4Xohzq4QZ8geQj2OT5YTqw7dZ7 +Xxre5H7IcNcAh+vIk5SEBV1WE+S5MnFly7gaLYNc49OSfz5Hcpv59Vr+4bZ+olbW +nQ/uU8BQovtkW6pjuT8nC4OKs2e8osoAZuk0rFS1uC501C+yES48mzaU8ttAidu6 +nb/JgsdkrnJQeTc5rAoER4M2ne5kqtEXN8wzf3/sazo2PLywbfrUXUTV6kJilrGr +RkBN+fr6HTBkf+ooQMBOQPTojUdwbR86CLCyiJov2bzmBfGcOgSakv59S+uvUZFp +FLTiahuzLfcgYsG3UKQA47pYlNdUqP8vCCaf1nwmqjx2KS3Z/YFnO/gQgtY+f0Bh +UpnUDv+zBxpVFfVCyxByEsDPdwDkqLSwB6+YZINl36S48iXpoPhNXIYmO6GnhNWV +k2/RyCDTxEO+MbXHVg6iyIVHJWth7m18vl4uuSK/LbJHV9Q9Z7G99DQ0NwIDAQAB +o2MwYTAdBgNVHQ4EFgQUuoo+ehdlDFcQU+j5qONMKh0NtFQwHwYDVR0jBBgwFoAU +uoo+ehdlDFcQU+j5qONMKh0NtFQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQELBQADggIBAM7c9q41K8306lfAVLqtbtaeETy+xxYG +XE2HfFW1IuukrXQ8d/JH4stL/HcHJzzhHPf5p3ja3Snu9zPmTk3pgUPDYPZf57tR +NCqwxjn6blwXWlzQqSavto9KAx3IWHuj0OTrZz/a1KPb9NGvatBhgthyRCRTbvhL +OA5tveuYSHb724cp3NZ1xaTQmDZsSgHCoCJ/7RnlbcJ7RsKCOzCWNFRomH410vdv +TajkUBlEC4OC1RIvxuVePHHb1ogbbe93SA/9mzw/E5SfoeF3mvByN4Ay8awXbNlH +26RfuIdGc4fZRc/87s4yPwhYScZBG+pHO0gn42E0FyiG6Jp3rhHMH5Sa2hNlPMpn +hYMaA6zQI4n/3AeFNM0VGxA+Yg/Al2WpXEJARrZqMW/qcrdMcPj5WeY6Tb6er04S +kfImwhMIajl3nNc9tHoad8r2VuMWMltH/dnWuEdo+pPdIY3fdJdyQeoLQDDLEQwL +AYrFy4uzKfQogfQBIHRdIMZTJh5v3mAFDpK59I5yzSt1GtUnFMC5MVOg+LbOo5UW +FCtwaW5EZiTszmakvvWMMZe9HwZMYNCeSGGtiPA/GA2zNci/n2TEcB11HgiY52y2 +E/40nS61oL81zMwhV7l5psgJxQ2ORsKRJPHjADwvwh3xyCEJgVyBRCDX7J3PpAUO +79DprjVU8t7p +-----END CERTIFICATE----- diff --git a/t/cert/mtls_ca.key b/t/cert/mtls_ca.key new file mode 100644 index 00000000..d741b5b0 --- /dev/null +++ b/t/cert/mtls_ca.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/t/cert/mtls_client.crt b/t/cert/mtls_client.crt new file mode 100644 index 00000000..c6ba07cc --- /dev/null +++ b/t/cert/mtls_client.crt @@ -0,0 +1,111 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=California, O=OpenResty, CN=OpenResty Testing Root CA + Validity + Not Before: Sep 13 22:30:49 2019 GMT + Not After : Sep 10 22:30:49 2029 GMT + Subject: C=US, ST=California, O=OpenResty, CN=foo@example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:d0:8c:1e:2c:25:7f:00:9d:8a:3d:8a:f3:b5:1d: + 6b:24:f5:ac:35:7d:cd:b7:d0:af:db:88:7c:ee:82: + 46:16:47:f3:43:08:a1:04:6e:0d:3e:ce:69:fa:d5: + 89:14:82:20:f1:47:f2:38:c8:ab:ea:2f:1e:f0:15: + 04:c0:f4:8b:3c:c3:d4:78:56:87:4c:f1:70:ac:11: + 86:2e:c4:6a:6d:10:84:27:81:ca:2a:8b:85:3e:62: + 13:5e:40:6c:19:e4:49:3d:f3:de:aa:e8:5e:11:a1: + f2:66:83:6a:40:d1:34:c5:bf:b8:cb:97:7c:6a:ea: + 46:bf:17:be:32:8d:a8:31:56:e5:8b:6d:08:03:d0: + 44:69:b9:af:1e:15:1d:a5:64:9e:12:84:83:db:d9: + c6:71:90:3b:c2:7b:41:21:57:af:70:15:0b:56:59: + 21:a6:4e:46:71:66:90:f1:ef:bc:b2:48:f9:8b:ea: + e5:72:4a:ba:4a:ae:2d:74:0b:33:03:f6:2e:47:0f: + 56:a4:00:e8:1e:62:cb:b8:af:9c:98:1a:89:7c:d0: + a3:7a:5a:e1:84:50:64:e4:5d:a5:70:a4:69:54:c4: + f4:76:44:a2:be:1b:ef:dc:a3:d8:1d:0d:30:a2:d4: + 79:fb:39:76:ab:b7:18:f2:f7:92:f8:81:83:94:b8: + 11:b1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Cert Type: + SSL Client, S/MIME + Netscape Comment: + OpenSSL Generated Client Certificate + X509v3 Subject Key Identifier: + D2:E4:F5:21:1C:17:A4:FF:13:F4:1A:28:A8:A7:DC:C6:DE:89:A0:31 + X509v3 Authority Key Identifier: + keyid:BA:8A:3E:7A:17:65:0C:57:10:53:E8:F9:A8:E3:4C:2A:1D:0D:B4:54 + + X509v3 Key Usage: critical + Digital Signature, Non Repudiation, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Client Authentication, E-mail Protection + X509v3 Subject Alternative Name: + email:foo@example.com, email:bar@example.com + Signature Algorithm: sha256WithRSAEncryption + 47:77:51:37:00:f7:28:da:c4:d9:4e:38:c4:ec:ea:24:c9:83: + 36:4c:90:93:a7:b2:2b:10:bf:75:df:0b:72:d8:e7:4f:4f:68: + e5:32:2f:35:89:17:95:5c:bb:43:fc:70:89:46:08:43:61:ac: + 41:62:84:01:94:88:d1:dc:8a:bd:30:2c:18:eb:51:79:0b:b7: + 1b:b6:49:df:c9:85:55:f6:73:9f:b7:83:99:52:23:fe:e6:bd: + 09:da:90:b9:e2:9b:68:c4:df:bd:fe:23:94:55:34:be:0d:7d: + 84:0c:53:69:2a:0f:3c:47:68:34:3f:2a:3f:89:3f:3e:d3:26: + ce:b7:58:bc:d0:6f:ee:f8:bd:5d:c6:48:ae:a0:6c:1f:6d:e0: + 66:93:7d:db:3c:07:e6:15:ae:aa:e3:d0:3d:ef:04:b6:dd:53: + 16:93:61:70:e9:af:c0:e9:1d:ff:2b:e5:0a:03:56:48:3f:1c: + dc:fe:1b:a6:6d:f6:54:ab:41:e5:3b:5b:ab:f5:81:10:46:26: + bb:ea:d7:0e:33:b1:5e:30:4d:81:86:63:9a:4a:4f:1e:44:b9: + c2:c6:08:4e:da:fa:3a:55:da:96:7c:01:f6:d5:e8:3b:ba:e9: + 31:3b:1c:51:39:1a:59:f0:e0:c7:17:2e:f6:18:9d:ec:a7:48: + 30:b8:4c:6d:e5:4a:4f:43:41:cb:0e:6b:ac:ad:87:44:90:76: + 85:23:2b:eb:8f:97:4b:22:13:60:20:3a:37:a4:dc:74:7d:85: + 3d:a1:f5:1a:03:f6:d5:78:c7:bc:9b:09:f2:c8:05:27:43:2a: + ac:50:21:3a:ee:83:2d:db:02:6f:c7:91:de:63:d6:36:7d:7a: + 9f:1f:fb:48:62:f4:fb:8e:3a:ea:61:9b:3c:03:f9:f8:a5:df: + 1b:02:14:2c:de:e6:e3:47:d2:44:65:94:1a:c6:e1:fd:ba:8d: + b6:f8:93:a9:46:46:26:79:b0:bf:57:a8:a2:20:66:56:7e:c9: + f5:a4:0b:5e:76:70:0a:47:a4:db:45:2e:15:99:69:f9:6b:14: + 93:2a:0a:b6:ee:53:a6:b9:02:9b:a2:25:37:1e:37:70:a2:7c: + 7f:c3:ce:98:17:2f:9b:5b:fa:6f:ae:d8:0e:d4:6a:b2:03:5a: + fe:ba:4b:7f:f6:98:20:ea:cb:be:17:34:e0:43:74:d1:0c:e5: + d4:cc:5d:13:41:d3:5e:a4:f6:94:f7:15:b8:15:a9:65:f8:28: + 3f:da:ef:b2:30:34:6d:96:3a:7a:f4:20:ec:9e:62:13:36:f1: + a7:04:e1:7a:d2:33:20:f6:61:4a:68:44:cb:92:d7:62:f0:e4: + 70:f0:a5:e3:dd:2f:e2:a3 +-----BEGIN CERTIFICATE----- +MIIFGTCCAwGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEiMCAGA1UE +AwwZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAeFw0xOTA5MTMyMjMwNDlaFw0y +OTA5MTAyMjMwNDlaMFAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh +MRIwEAYDVQQKDAlPcGVuUmVzdHkxGDAWBgNVBAMMD2Zvb0BleGFtcGxlLmNvbTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCMHiwlfwCdij2K87UdayT1 +rDV9zbfQr9uIfO6CRhZH80MIoQRuDT7OafrViRSCIPFH8jjIq+ovHvAVBMD0izzD +1HhWh0zxcKwRhi7Eam0QhCeByiqLhT5iE15AbBnkST3z3qroXhGh8maDakDRNMW/ +uMuXfGrqRr8XvjKNqDFW5YttCAPQRGm5rx4VHaVknhKEg9vZxnGQO8J7QSFXr3AV +C1ZZIaZORnFmkPHvvLJI+Yvq5XJKukquLXQLMwP2LkcPVqQA6B5iy7ivnJgaiXzQ +o3pa4YRQZORdpXCkaVTE9HZEor4b79yj2B0NMKLUefs5dqu3GPL3kviBg5S4EbEC +AwEAAaOB8jCB7zAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAzBglghkgB +hvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRpZmljYXRlMB0G +A1UdDgQWBBTS5PUhHBek/xP0Giiop9zG3omgMTAfBgNVHSMEGDAWgBS6ij56F2UM +VxBT6Pmo40wqHQ20VDAOBgNVHQ8BAf8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH +AwIGCCsGAQUFBwMEMCsGA1UdEQQkMCKBD2Zvb0BleGFtcGxlLmNvbYEPYmFyQGV4 +YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4ICAQBHd1E3APco2sTZTjjE7OokyYM2 +TJCTp7IrEL913wty2OdPT2jlMi81iReVXLtD/HCJRghDYaxBYoQBlIjR3Iq9MCwY +61F5C7cbtknfyYVV9nOft4OZUiP+5r0J2pC54ptoxN+9/iOUVTS+DX2EDFNpKg88 +R2g0Pyo/iT8+0ybOt1i80G/u+L1dxkiuoGwfbeBmk33bPAfmFa6q49A97wS23VMW +k2Fw6a/A6R3/K+UKA1ZIPxzc/humbfZUq0HlO1ur9YEQRia76tcOM7FeME2BhmOa +Sk8eRLnCxghO2vo6VdqWfAH21eg7uukxOxxRORpZ8ODHFy72GJ3sp0gwuExt5UpP +Q0HLDmusrYdEkHaFIyvrj5dLIhNgIDo3pNx0fYU9ofUaA/bVeMe8mwnyyAUnQyqs +UCE67oMt2wJvx5HeY9Y2fXqfH/tIYvT7jjrqYZs8A/n4pd8bAhQs3ubjR9JEZZQa +xuH9uo22+JOpRkYmebC/V6iiIGZWfsn1pAtednAKR6TbRS4VmWn5axSTKgq27lOm +uQKboiU3Hjdwonx/w86YFy+bW/pvrtgO1GqyA1r+ukt/9pgg6su+FzTgQ3TRDOXU +zF0TQdNepPaU9xW4Fall+Cg/2u+yMDRtljp69CDsnmITNvGnBOF60jMg9mFKaETL +ktdi8ORw8KXj3S/iow== +-----END CERTIFICATE----- diff --git a/t/cert/mtls_client.key b/t/cert/mtls_client.key new file mode 100644 index 00000000..34b301f0 --- /dev/null +++ b/t/cert/mtls_client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA0IweLCV/AJ2KPYrztR1rJPWsNX3Nt9Cv24h87oJGFkfzQwih +BG4NPs5p+tWJFIIg8UfyOMir6i8e8BUEwPSLPMPUeFaHTPFwrBGGLsRqbRCEJ4HK +KouFPmITXkBsGeRJPfPequheEaHyZoNqQNE0xb+4y5d8aupGvxe+Mo2oMVbli20I +A9BEabmvHhUdpWSeEoSD29nGcZA7wntBIVevcBULVlkhpk5GcWaQ8e+8skj5i+rl +ckq6Sq4tdAszA/YuRw9WpADoHmLLuK+cmBqJfNCjelrhhFBk5F2lcKRpVMT0dkSi +vhvv3KPYHQ0wotR5+zl2q7cY8veS+IGDlLgRsQIDAQABAoIBAEpoY++OZVT74LH6 +nN+XIn5qZUokm7yk6cnjVefndUhH3aSiNIkXFwS8sxV7ENDPaR+NcwANoUEKFPjG +Fw8dcXx5xpo1DUtHrdLG4eBX1j0Zsn1CErbBVwYeChkL1UYbrII9O8ow5DdYV9t5 +sfR0cGbJ9A43+31OH3XY69SvtD39xItgxK0Wg4Ciz475kCvG51Q1iBWAkm4koXhz +VCha4wghs81wJ28HRMFZAFf2C+72rk6EypMUX2dYirvW/+7zONirk298NDMAOSBh +mRWyPV8qipYx42hBQ9vSVm0UVb0ZbqVomKKUZfj11LL6Ad/OzCyVAiNLXyZREV6r +d324Bu0CgYEA/YPsE6p6H3MTPIoVTnsyyQw0pNmXtIAuYTgSveeuPoTYn3ZBWoGN +iLpbnW4EH3xNKfrdMjbqqLls1iwm7/ZAP5klAuL4s10onrcjMt65fyfa3Lw1gavG +SUFFdsueH2k3FohqNsbQUSXZILVQnXsRoldi38b7NKrAqABcEMAIqXMCgYEA0pde +nt4aMmrGBRPLnjCs1UlC5PbXzCE8XxxQ7HZKx4Sy5ErQ0EW1wzF6c0fEYI7i+j1/ +ESKqekzc5ue0T8acoioB+VUybO1oxQZsZUPY7roqXOYwZH9LQOdPYUOh9k33CZHw +6KFfx8bKCpdXn7FkwR2UUtCSp/6CZcyYr89Qn0sCgYAQ0L5I86bUDTL6cgJFyWAt ++7RGNvScEWCCLFD57bMeDHu93/8nvK4hopLPF2wIlpsbrLsdSI06EcqJTjZq9j9+ +uG6/CUULyKMYG/emuSU+rOsUdxtpdXZah4zO+2SKmtT/lp7M8VUB/OuxArXNLEuY +JAm35B/nd2f9/MAekE5CxwKBgQCV660w7G0590mB09XhiEWCkeVNm22FpSOVklMK +BCy4XX/9hkWh//6mN1M1PqJPG2n7PEx5pnQ3HQEmYU28fWiFCeLd3glIArvTh/8j +GGoXifEescFByl2IlyOr2roy3s4/weX/tuK5Fow/ff6jcWaJFMXDLzk437d1QXJx +tuVugQKBgByfr2eakXFQvAVGJUfVXA3M2BoBODZEPYTgryVMoEEduFy0HZiw4xKi +Dngwewy6/UJMAGA+8ak9Ca367FxnegZU9knm6ujYVyhU5WzbKpR8v7OaUP8d5icq +rCZZtglG0c8XfVpJjR4FsKA/qrFvKZpu5NdEw3o5/LSrV4HjqZQ6 +-----END RSA PRIVATE KEY----- diff --git a/t/cert/mtls_server.crt b/t/cert/mtls_server.crt new file mode 100644 index 00000000..c4d320f4 --- /dev/null +++ b/t/cert/mtls_server.crt @@ -0,0 +1,112 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4097 (0x1001) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=California, O=OpenResty, CN=OpenResty Testing Root CA + Validity + Not Before: Sep 13 22:32:03 2019 GMT + Not After : Sep 10 22:32:03 2029 GMT + Subject: C=US, ST=California, O=OpenResty, CN=example.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:bb:69:2b:30:43:b0:b4:cc:84:3f:22:39:65:65: + 6a:bd:75:a4:2b:7d:f7:ec:e4:12:e8:d1:c4:ce:7e: + 4e:54:5c:22:cc:d2:18:7f:3b:9e:cb:70:d9:7d:79: + 8f:05:93:b6:9f:2f:d5:33:d7:98:a2:ed:c5:00:93: + e4:ca:bc:cb:f0:e1:63:3e:07:6b:38:6f:4d:09:45: + f1:a1:3b:a3:ca:c0:47:c1:a1:0a:f8:c9:bb:c7:da: + 26:9d:d3:0b:35:24:01:3e:16:14:2e:44:38:8c:c9: + 09:02:41:9e:b6:fb:0c:aa:fc:d6:44:5e:27:ab:aa: + d5:c3:68:e1:dd:57:06:6c:4f:f6:24:33:a8:2b:49: + 60:82:0e:15:aa:55:9f:61:cc:74:39:7e:9f:a6:4f: + 71:4a:8b:eb:43:dd:c2:f7:90:38:df:a6:a6:a8:f6: + 77:bc:9e:54:69:30:83:4c:2a:eb:b8:62:7c:c7:14: + 84:9e:f3:e1:4a:15:33:51:65:a3:af:9d:09:c6:b8: + 89:30:a3:d2:18:e9:dc:5d:6b:ea:68:ca:8b:5c:e4: + 3b:fe:32:7f:48:c3:4c:f0:b5:06:f6:23:97:3e:f2: + 50:90:68:26:39:6d:b2:e2:53:89:71:6a:48:f0:f1: + fc:89:3c:6d:db:87:6c:79:23:ed:87:5d:c5:fa:8a: + 0d:b9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Cert Type: + SSL Server + Netscape Comment: + OpenSSL Generated Server Certificate + X509v3 Subject Key Identifier: + 8C:8E:18:2C:13:84:C9:2A:61:6B:73:3F:18:76:A4:85:55:5F:5C:5F + X509v3 Authority Key Identifier: + keyid:BA:8A:3E:7A:17:65:0C:57:10:53:E8:F9:A8:E3:4C:2A:1D:0D:B4:54 + DirName:/C=US/ST=California/O=OpenResty/CN=OpenResty Testing Root CA + serial:7D:38:7C:F4:DC:B1:C6:66:D5:C0:D6:7F:60:57:5D:B2:C5:9C:F9:69 + + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication + Signature Algorithm: sha256WithRSAEncryption + 80:9f:3e:f5:8b:50:ee:cb:e4:c2:f0:16:01:07:a1:af:76:bc: + da:c8:cd:24:e9:63:df:d3:47:28:8c:7f:58:14:5d:d4:fd:44: + 16:c3:06:15:be:90:ec:1c:8f:78:34:11:e7:cc:86:d8:2a:a2: + e5:99:70:83:76:4a:65:a4:e1:9a:68:20:29:c0:7a:c6:4a:08: + b3:74:c3:53:b5:7b:79:92:f1:99:b5:a1:a3:90:ce:9a:cb:26: + a5:a6:33:de:74:98:99:ec:18:d1:1e:41:be:f8:c3:d2:8d:aa: + 07:de:9a:97:28:0d:bf:70:ac:2b:cf:b7:ff:bc:ac:e4:16:0c: + 1c:03:a7:5a:2d:64:0d:90:16:bd:97:c3:1f:f5:bf:a9:fa:15: + d1:e0:d4:0d:f7:b3:51:23:ce:ad:16:4f:41:72:17:aa:01:d5: + 44:e2:9e:d5:ce:ea:54:98:04:43:14:2e:51:4b:c7:d9:21:4f: + e1:a4:fa:dd:e0:f0:82:ec:6f:9f:be:a2:3c:3b:85:f7:6d:96: + ee:0d:e6:08:2b:1b:be:06:a4:b7:5f:a3:f2:f2:b9:d0:5a:8f: + 90:86:1a:f4:7a:9f:c8:ae:09:1d:60:a2:8b:e0:0b:f6:00:21: + d9:df:33:4b:39:75:b6:64:9b:c7:df:e4:85:7a:ae:df:72:8c: + 8b:7e:98:8e:47:0a:27:1f:8e:2c:11:7f:7b:fc:a0:db:1b:6e: + f6:de:4e:85:ac:30:e6:e8:6a:7a:e6:f9:f4:18:0a:c6:ad:1c: + e1:0c:dd:e0:e0:8d:5a:d7:08:34:e7:22:b4:44:bd:99:39:b1: + 71:74:3f:7c:aa:65:f5:37:46:85:d3:79:f7:a8:35:8d:2b:30: + 99:d2:47:ce:a6:74:eb:f3:9f:d3:9a:4e:99:96:50:7b:ba:22: + c8:72:47:d4:da:6e:9a:73:01:3c:89:e9:3f:56:17:b7:ba:22: + 71:db:66:a2:d2:fb:33:51:36:f6:b6:f2:5b:32:70:9d:e7:e3: + 36:d6:ae:cb:9b:62:ef:69:c7:f7:ba:95:49:16:f5:7c:d9:29: + bb:0a:02:b1:6b:72:15:ab:2c:27:7b:c8:bc:f6:15:1f:fa:ae: + 08:fd:e0:11:36:b1:ab:9c:c8:11:d1:d3:0d:7d:49:4e:ca:e6: + 73:ee:0d:c3:8d:6f:f5:a4:fe:a1:af:6b:91:f7:53:fd:10:df: + 77:dd:ef:ec:7b:cf:32:75:df:04:8a:d1:a1:f7:36:68:ee:65: + e3:43:90:37:43:e8:d1:a8:e2:90:5c:1c:75:0a:29:94:4a:6a: + 9b:89:28:43:bd:85:56:0d:f1:2b:44:bd:e6:7a:4c:b7:85:10: + 77:b7:a8:0f:33:29:a7:26 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEiMCAGA1UE +AwwZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAeFw0xOTA5MTMyMjMyMDNaFw0y +OTA5MTAyMjMyMDNaMEwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh +MRIwEAYDVQQKDAlPcGVuUmVzdHkxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu2krMEOwtMyEPyI5ZWVqvXWkK333 +7OQS6NHEzn5OVFwizNIYfzuey3DZfXmPBZO2ny/VM9eYou3FAJPkyrzL8OFjPgdr +OG9NCUXxoTujysBHwaEK+Mm7x9omndMLNSQBPhYULkQ4jMkJAkGetvsMqvzWRF4n +q6rVw2jh3VcGbE/2JDOoK0lggg4VqlWfYcx0OX6fpk9xSovrQ93C95A436amqPZ3 +vJ5UaTCDTCrruGJ8xxSEnvPhShUzUWWjr50JxriJMKPSGOncXWvqaMqLXOQ7/jJ/ +SMNM8LUG9iOXPvJQkGgmOW2y4lOJcWpI8PH8iTxt24dseSPth13F+ooNuQIDAQAB +o4IBNTCCATEwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4 +QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNV +HQ4EFgQUjI4YLBOEySpha3M/GHakhVVfXF8wgZcGA1UdIwSBjzCBjIAUuoo+ehdl +DFcQU+j5qONMKh0NtFShXqRcMFoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp +Zm9ybmlhMRIwEAYDVQQKDAlPcGVuUmVzdHkxIjAgBgNVBAMMGU9wZW5SZXN0eSBU +ZXN0aW5nIFJvb3QgQ0GCFH04fPTcscZm1cDWf2BXXbLFnPlpMA4GA1UdDwEB/wQE +AwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAgJ8+ +9YtQ7svkwvAWAQehr3a82sjNJOlj39NHKIx/WBRd1P1EFsMGFb6Q7ByPeDQR58yG +2Cqi5Zlwg3ZKZaThmmggKcB6xkoIs3TDU7V7eZLxmbWho5DOmssmpaYz3nSYmewY +0R5BvvjD0o2qB96alygNv3CsK8+3/7ys5BYMHAOnWi1kDZAWvZfDH/W/qfoV0eDU +DfezUSPOrRZPQXIXqgHVROKe1c7qVJgEQxQuUUvH2SFP4aT63eDwguxvn76iPDuF +922W7g3mCCsbvgakt1+j8vK50FqPkIYa9HqfyK4JHWCii+AL9gAh2d8zSzl1tmSb +x9/khXqu33KMi36YjkcKJx+OLBF/e/yg2xtu9t5Ohaww5uhqeub59BgKxq0c4Qzd +4OCNWtcINOcitES9mTmxcXQ/fKpl9TdGhdN596g1jSswmdJHzqZ06/Of05pOmZZQ +e7oiyHJH1NpumnMBPInpP1YXt7oicdtmotL7M1E29rbyWzJwnefjNtauy5ti72nH +97qVSRb1fNkpuwoCsWtyFassJ3vIvPYVH/quCP3gETaxq5zIEdHTDX1JTsrmc+4N +w41v9aT+oa9rkfdT/RDfd93v7HvPMnXfBIrRofc2aO5l40OQN0Po0ajikFwcdQop +lEpqm4koQ72FVg3xK0S95npMt4UQd7eoDzMppyY= +-----END CERTIFICATE----- diff --git a/t/cert/mtls_server.key b/t/cert/mtls_server.key new file mode 100644 index 00000000..3509beb6 --- /dev/null +++ b/t/cert/mtls_server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAu2krMEOwtMyEPyI5ZWVqvXWkK3337OQS6NHEzn5OVFwizNIY +fzuey3DZfXmPBZO2ny/VM9eYou3FAJPkyrzL8OFjPgdrOG9NCUXxoTujysBHwaEK ++Mm7x9omndMLNSQBPhYULkQ4jMkJAkGetvsMqvzWRF4nq6rVw2jh3VcGbE/2JDOo +K0lggg4VqlWfYcx0OX6fpk9xSovrQ93C95A436amqPZ3vJ5UaTCDTCrruGJ8xxSE +nvPhShUzUWWjr50JxriJMKPSGOncXWvqaMqLXOQ7/jJ/SMNM8LUG9iOXPvJQkGgm +OW2y4lOJcWpI8PH8iTxt24dseSPth13F+ooNuQIDAQABAoIBABnT/KfCLHA+X1t0 +FATtXTCPLfjwe2KibBi6EC2FKrZlnEYuDkI6rT/MZaztO9DA8sItjWx/ogGSUzwp +JbbrHhAsf8jkrNoyPKOyiAJ4fbJLnZgJ4cE3zDFW10uY8kp4k9NCp7VYoZKFgkBV +WtJM9wn5nm39q+n0uVEc+0PN4oy6m54Aqb1HqVCyXFp+/pVhL6PtgaClbqJd3oKV +0/HLWfWaI3nvV6ltAphUfPoCmYIUtSl90sRgSeEaJ61UZXh0OkhhtD7Iw/JUlHDk +a0J7owrh0Wf1kDsaSn+j1ba8MELsspFYYVm0gAMKAvRXbVrYgUMdb+HVdZ0odULl +ezFWeAECgYEA9nnoZs+PzKWNxTzYPtgvvrSLmpXLzNrs/p41JUhy6GjQIixTSBFy +WHkjwu0k2fvRgODfcaetAyK6sV4uTpRgqUhtFSeyNMelZ2yiIEqvhUrtHoVov1C+ +BqwwlUnmkQZNQODXOpKCvnqnOaPwMILKLtxDGmPtW0tCTR2dVVaht6ECgYEAwqb/ +h0Fh3YtykOnhV8wOZRrpVr8jS1RIgg/hklt2xh6+OYtL16sKFaLBF/BhzZRBapqd +fB2Cx3B6rxZ5PLTse8yjEvjt6Ly7TusYWpaKbYKFnnEbmdsm5sBepuLUv4AoMYbk +99ZejFcQI2gNbzX7eIrFitCQGxT+Wu7Gncv+vxkCgYBvAYCVrS2KcZVkG38Y7qyy +KwYk3QoofQD3u7Eb1YFLAsmaWnQ3pQPmrMhaZguO0UcN0DlSKr5VBzMl5tDcOx89 +noziVjqAYtovtlFeUcSzN4eLk3IVl/u9bZeD5QCemEP60Eie7JVNzFe8MgVfE8iT +Skg+fnrL/x0hNhFB+f5jgQKBgDgOEX4o5P8A3nA++gbnm6mgE1xI1OgnkG3sFuCn ++E9boRo/NAsalV/fq82yCuhB7oi9l+abNQMsMBhl12oVDBkmuDuJdjHUz/gNGclU +mu6obMRQ/ErVYqGG+nsCzZOMW4bPuvZoRHgTxnD70QqauB1hkTvFjgpOhGU5Z/cf +PPBZAoGBAJQK7NF6VoF9nm5CT8vufrQ2vvp2aiLdOLLx5JXt/6seEnPZWtEvjp8/ ++ExIsfOIaU5elhv8ze8iKmRP9f04XdWpbRm6k6AR5cOkkQQ1oO7N9abU7KbD/gqX +pJIWOlaUrbKO4Dprx7HyMYYPs9mu/UoF0Dvd/+bYXM5ZKiFrQ3Ly +-----END RSA PRIVATE KEY-----