diff --git a/.travis.yml b/.travis.yml index 127725613..cff7fb603 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,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 ssl-psk https://github.com/vartiait/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/lib/ngx/ssl.lua b/lib/ngx/ssl.lua index 89d42a533..ca82ed607 100644 --- a/lib/ngx/ssl.lua +++ b/lib/ngx/ssl.lua @@ -16,6 +16,7 @@ local get_string_buf = base.get_string_buf local get_size_ptr = base.get_size_ptr local FFI_DECLINED = base.FFI_DECLINED local FFI_OK = base.FFI_OK +local FFI_ERROR = base.FFI_ERROR ffi.cdef[[ @@ -58,6 +59,15 @@ int ngx_http_lua_ffi_set_priv_key(void *r, void *cdata, char **err); 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_set_psk_key(ngx_http_request_t *r, + const char *key, size_t len, char **err); + +int ngx_http_lua_ffi_ssl_get_psk_identity(ngx_http_request_t *r, + char *buf, char **err); + +int ngx_http_lua_ffi_ssl_get_psk_identity_size(ngx_http_request_t *r, + char **err); ]] @@ -261,6 +271,50 @@ function _M.set_priv_key(priv_key) end +function _M.set_psk_key(psk_key) + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local rc = C.ngx_http_lua_ffi_ssl_set_psk_key(r, psk_key, #psk_key, errmsg) + if rc == FFI_OK then + return true + end + + return nil, ffi_str(errmsg[0]) +end + + +-- return psk_identity, err +function _M.get_psk_identity() + local r = getfenv(0).__ngx_req + if not r then + return error("no request found") + end + + local len = C.ngx_http_lua_ffi_ssl_get_psk_identity_size(r, errmsg) + + if len < 0 then + return nil, ffi_str(errmsg[0]) + end + + if len > 4096 then + return nil, "psk identity too long" + end + + local buf = get_string_buf(len) + + local rc = C.ngx_http_lua_ffi_ssl_get_psk_identity(r, buf, errmsg) + + if rc == FFI_ERROR then + return nil, ffi_str(errmsg[0]) + end + + return ffi_str(buf, len) +end + + do _M.SSL3_VERSION = 0x0300 _M.TLS1_VERSION = 0x0301 diff --git a/lib/ngx/ssl.md b/lib/ngx/ssl.md index fc63c24e9..a0573f6d8 100644 --- a/lib/ngx/ssl.md +++ b/lib/ngx/ssl.md @@ -23,6 +23,8 @@ Table of Contents * [parse_pem_priv_key](#parse_pem_priv_key) * [set_cert](#set_cert) * [set_priv_key](#set_priv_key) + * [set_psk_key](#set_psk_key) + * [get_psk_identity](#get_psk_identity) * [Community](#community) * [English Mailing List](#english-mailing-list) * [Chinese Mailing List](#chinese-mailing-list) @@ -396,6 +398,33 @@ This function was first added in version `0.1.7`. [Back to TOC](#table-of-contents) +set_psk_key +------------ +**syntax:** *ok, err = ssl.set_psk_key(psk_key)* + +**context:** *ssl_certificate_by_lua** + +Sets the TLS-PSK key for the current SSL connection. + +Returns `true` on success, or a `nil` value and a string describing the error otherwise. + +This function was first added in version `xxx`. + +[Back to TOC](#table-of-contents) + +get_psk_identity +------------ +**syntax:** *identity, err = ssl.get_psk_identity()* + +**context:** *ssl_certificate_by_lua** + +Returns a TLS-PSK identity a client sent for the current SSL connection. Returns `nil` and +a string describing the error otherwise. + +This function was first added in version `xxx`. + +[Back to TOC](#table-of-contents) + Community ========= diff --git a/t/ssl-psk.t b/t/ssl-psk.t new file mode 100644 index 000000000..780e5dfa4 --- /dev/null +++ b/t/ssl-psk.t @@ -0,0 +1,272 @@ +# vim:set ft=ts=4 sw=4 et fdm=marker: + +use Test::Nginx::Socket::Lua; +use Cwd qw(abs_path realpath cwd); +use File::Basename; + +#worker_connections(10140); +#workers(1); +#log_level('warn'); + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 6 + 2); + +our $CWD = cwd(); + +no_long_string(); +#no_diff(); + +$ENV{TEST_NGINX_LUA_PACKAGE_PATH} = "$::CWD/lib/?.lua;;"; +$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); + +run_tests(); + +__DATA__ + +=== TEST 1: TLS-PSK +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_protocols TLSv1; + ssl_ciphers PSK; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local psk_key = "psk_test_key" + + local psk_identity, err = ssl.get_psk_identity() + if not psk_identity then + if err == "not in psk context" then + -- handler was not called by TLS-PSK callback + return + end + ngx.log(ngx.ERR, "failed to get psk identity: ", err) + return ngx.exit(ngx.ERROR) + end + + print("client psk identity: ", psk_identity) + + local ok, err = ssl.set_psk_key(psk_key) + if not ok then + ngx.log(ngx.ERR, "failed to set psk key: ", err) + return ngx.exit(ngx.ERROR) + end + } + + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + ssl_psk_identity_hint psk_test_identity_hint; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + + location /t { + lua_ssl_ciphers PSK; + lua_ssl_psk_identity psk_test_identity; + lua_ssl_psk_key psk_test_key; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.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.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +ssl handshake: userdata +sent http request: 56 bytes. +received: HTTP/1.1 201 Created +received: Server: nginx +received: Content-Type: text/plain +received: Content-Length: 4 +received: Connection: close +received: +received: foo +close: 1 nil + +--- error_log +lua ssl server name: "test.com" +client psk identity: psk_test_identity + +--- no_error_log +[alert] +[emerg] +[error] + + + +=== TEST 2: TLS-PSK mismatching key +--- http_config + lua_package_path "$TEST_NGINX_LUA_PACKAGE_PATH/?.lua;;"; + server { + listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl; + server_name test.com; + + ssl_protocols TLSv1; + ssl_ciphers PSK; + + ssl_certificate_by_lua_block { + local ssl = require "ngx.ssl" + + local psk_key = "psk_test_key2" + + local psk_identity, err = ssl.get_psk_identity() + if not psk_identity then + if err == "not in psk context" then + -- handler was not called by TLS-PSK callback + return + end + ngx.log(ngx.ERR, "failed to get psk identity: ", err) + return ngx.exit(ngx.ERROR) + end + + print("client psk identity: ", psk_identity) + + local ok, err = ssl.set_psk_key(psk_key) + if not ok then + ngx.log(ngx.ERR, "failed to set psk key: ", err) + return ngx.exit(ngx.ERROR) + end + } + + ssl_certificate ../../cert/test.crt; + ssl_certificate_key ../../cert/test.key; + + ssl_psk_identity_hint psk_test_identity_hint; + + server_tokens off; + location /foo { + default_type 'text/plain'; + content_by_lua_block { ngx.status = 201 ngx.say("foo") ngx.exit(201) } + more_clear_headers Date; + } + } +--- config + server_tokens off; + + location /t { + lua_ssl_ciphers PSK; + lua_ssl_psk_identity psk_test_identity; + lua_ssl_psk_key psk_test_key; + + content_by_lua_block { + do + local sock = ngx.socket.tcp() + + sock:settimeout(2000) + + local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock") + if not ok then + ngx.say("failed to connect: ", err) + return + end + + ngx.say("connected: ", ok) + + local sess, err = sock:sslhandshake(nil, "test.com", false) + if not sess then + ngx.say("failed to do SSL handshake: ", err) + return + end + + ngx.say("ssl handshake: ", type(sess)) + + local req = "GET /foo HTTP/1.0\r\nHost: test.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.") + + while true do + local line, err = sock:receive() + if not line then + -- ngx.say("failed to recieve response status line: ", err) + break + end + + ngx.say("received: ", line) + end + + local ok, err = sock:close() + ngx.say("close: ", ok, " ", err) + end -- do + -- collectgarbage() + } + } + +--- request +GET /t +--- response_body +connected: 1 +failed to do SSL handshake: handshake failed + +--- error_log eval +[ +qr/lua ssl server name: "test.com"/s, +qr/client psk identity: psk_test_identity/s, +qr/\[error\] .*? SSL_do_handshake\(\) failed .*? alert bad record mac/s, +] + +--- no_error_log +[alert] +[emerg]