Skip to content
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
27 changes: 27 additions & 0 deletions lib/ngx/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ local ngx_lua_ffi_ssl_session_master_key
local ngx_lua_ffi_ssl_export_keying_material
local ngx_lua_ffi_ssl_export_keying_material_early
local ngx_lua_ffi_get_req_ssl_pointer
local ngx_lua_ffi_get_req_ssl_ctx
local ngx_lua_ffi_get_upstream_ssl_pointer
local ngx_lua_ffi_req_shared_ssl_ciphers

Expand Down Expand Up @@ -108,6 +109,9 @@ if subsystem == 'http' then
void *ngx_http_lua_ffi_get_req_ssl_pointer(ngx_http_request_t *r,
char **err);

void *ngx_http_lua_ffi_get_req_ssl_ctx(ngx_http_request_t *r,
char **err);

void *ngx_http_lua_ffi_get_upstream_ssl_pointer(
ngx_http_request_t *r, char **err);

Expand Down Expand Up @@ -186,6 +190,7 @@ if subsystem == 'http' then
ngx_lua_ffi_ssl_export_keying_material_early =
C.ngx_http_lua_ffi_ssl_export_keying_material_early
ngx_lua_ffi_get_req_ssl_pointer = C.ngx_http_lua_ffi_get_req_ssl_pointer
ngx_lua_ffi_get_req_ssl_ctx = C.ngx_http_lua_ffi_get_req_ssl_ctx
ngx_lua_ffi_get_upstream_ssl_pointer
= C.ngx_http_lua_ffi_get_upstream_ssl_pointer
ngx_lua_ffi_req_shared_ssl_ciphers =
Expand Down Expand Up @@ -239,6 +244,9 @@ elseif subsystem == 'stream' then
void *ngx_stream_lua_ffi_get_req_ssl_pointer(ngx_stream_lua_request_t *r,
char **err);

void *ngx_stream_lua_ffi_get_req_ssl_ctx(ngx_stream_lua_request_t *r,
char **err);

void *ngx_stream_lua_ffi_get_upstream_ssl_pointer(
ngx_stream_lua_request_t *r, char **err);

Expand Down Expand Up @@ -285,6 +293,7 @@ elseif subsystem == 'stream' then
ngx_lua_ffi_ssl_verify_client = C.ngx_stream_lua_ffi_ssl_verify_client
ngx_lua_ffi_ssl_client_random = C.ngx_stream_lua_ffi_ssl_client_random
ngx_lua_ffi_get_req_ssl_pointer = C.ngx_stream_lua_ffi_get_req_ssl_pointer
ngx_lua_ffi_get_req_ssl_ctx = C.ngx_stream_lua_ffi_get_req_ssl_ctx
ngx_lua_ffi_get_upstream_ssl_pointer
= C.ngx_stream_lua_ffi_get_upstream_ssl_pointer
ngx_lua_ffi_req_shared_ssl_ciphers =
Expand Down Expand Up @@ -705,6 +714,24 @@ function _M.get_req_ssl_pointer()
end


local function get_req_ssl_ctx()
local r = get_request()
if not r then
error("no request found")
end

local ctx = ngx_lua_ffi_get_req_ssl_ctx(r, errmsg)
if ctx == nil then
return nil, ffi_str(errmsg[0])
end

return ctx
end


_M.get_req_ssl_ctx = get_req_ssl_ctx


function _M.get_upstream_ssl_pointer()
local r = get_request()
if not r then
Expand Down
24 changes: 24 additions & 0 deletions lib/ngx/ssl.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Table of Contents
* [get_session_master_key](#get_session_master_key)
* [get_shared_ssl_ciphers](#get_shared_ssl_ciphers)
* [get_req_ssl_pointer](#get_req_ssl_pointer)
* [get_req_ssl_ctx](#get_req_ssl_ctx)
* [get_upstream_ssl_pointer](#get_upstream_ssl_pointer)
* [Community](#community)
* [English Mailing List](#english-mailing-list)
Expand Down Expand Up @@ -761,6 +762,29 @@ This function was first added in version `0.1.16`.

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

get_req_ssl_ctx
---------------
**syntax:** *ctx_ptr, err = ssl.get_req_ssl_ctx()*

**context:** *any context with a downstream TLS connection*

Retrieves the OpenSSL `SSL_CTX*` stored in Nginx's downstream `session_ctx`,
in both HTTP and stream subsystems. This is the original session context,
which can differ from `SSL_get_SSL_CTX()` after SNI selects another server.

Returns a `void *` FFI pointer on success, or `nil` and an error string when
SSL state or the session context is unavailable. Raises `no request found`
when called without a request.

The pointer is borrowed from Nginx. Do not free it or attach a garbage
collector to it. If retaining it beyond the connection lifetime, obtain
and release a separate reference using the appropriate OpenSSL APIs.

This function requires lua-nginx-module or stream-lua-nginx-module with
the corresponding `get_req_ssl_ctx` FFI export.

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

get_upstream_ssl_pointer
------------
**syntax:** *ssl_ptr, err = ssl.get_upstream_ssl_pointer()*
Expand Down
196 changes: 196 additions & 0 deletions t/ssl-ctx.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# vim:set ft= ts=4 sw=4 et fdm=marker:

use Test::Nginx::Socket::Lua;

repeat_each(2);
plan tests => repeat_each() * blocks() * 3;

$ENV{TEST_NGINX_HTML_DIR} ||= html_dir();
no_long_string();
log_level 'info';

add_block_preprocessor(sub {
my $block = shift;
my $config = $block->http_config || '';
$config .= <<'_EOC_';
lua_package_path "$prefix/html/?.lua;./lib/?.lua;../lua-resty-lrucache/lib/?.lua;;";
_EOC_
$block->set_value("http_config", $config);
$block->set_value("user_files", <<'_EOC_');
>>> ssl_ctx.lua
local ffi = require "ffi"
local ssl = require "ngx.ssl"

ffi.cdef[[
typedef struct ssl_ctx_st SSL_CTX;
SSL_CTX *SSL_get_SSL_CTX(const void *ssl);
int SSL_CTX_get_verify_depth(const SSL_CTX *ctx);
]]

local C = ffi.C
local _M = {}

function _M.depths()
local ctx, err = ssl.get_req_ssl_ctx()
assert(ctx ~= nil, err)
assert(ffi.istype("void *", ctx))
assert(ctx == ssl.get_req_ssl_ctx())

local conn = assert(ssl.get_req_ssl_pointer())
return C.SSL_CTX_get_verify_depth(ffi.cast("SSL_CTX *", ctx)),
C.SSL_CTX_get_verify_depth(C.SSL_get_SSL_CTX(conn))
end

return _M
_EOC_
});

run_tests();

__DATA__

=== TEST 1: API reports a missing request
--- http_config
init_by_lua_block {
local ssl = require "ngx.ssl"
local ok, err = pcall(ssl.get_req_ssl_ctx)
_G.no_request_ok = ok
_G.no_request_err = err
}
--- config
location /t {
content_by_lua_block {
ngx.say(no_request_ok, ": ", no_request_err:sub(-16))
}
}
--- request
GET /t
--- response_body
false: no request found
--- no_error_log
[error]


=== TEST 2: plain connection returns an error
--- config
location /t {
content_by_lua_block {
local ctx, err = require("ngx.ssl").get_req_ssl_ctx()
ngx.say(ctx == nil, ": ", err)
}
}
--- request
GET /t
--- response_body
true: bad request
--- no_error_log
[error]


=== TEST 3: TLS context is readable during certificate and content phases
--- http_config
server {
listen unix:$TEST_NGINX_HTML_DIR/ctx-3.sock ssl;
server_name default.test;
ssl_certificate ../../cert/test.crt;
ssl_certificate_key ../../cert/test.key;
ssl_verify_depth 3;

ssl_certificate_by_lua_block {
local session_depth, active_depth = require("ssl_ctx").depths()
assert(session_depth == 3)
assert(active_depth == 3)
}

location / {
content_by_lua_block {
local session_depth, active_depth = require("ssl_ctx").depths()
ngx.say(session_depth, ":", active_depth)
}
}
}
--- config
location /t {
content_by_lua_block {
local sock = ngx.socket.tcp()
assert(sock:connect("unix:$TEST_NGINX_HTML_DIR/ctx-3.sock"))
assert(sock:sslhandshake(nil, "default.test", false))
assert(sock:send("GET / HTTP/1.0\r\nHost: default.test\r\n\r\n"))
local data = assert(sock:receive("*a"))
local _, header_end = data:find("\r\n\r\n", 1, true)
assert(header_end)
ngx.print(data:sub(header_end + 1))
assert(sock:close())
}
}
--- request
GET /t
--- response_body
3:3
--- no_error_log
[error]


=== TEST 4: SNI preserves the original session context
--- http_config
server {
listen unix:$TEST_NGINX_HTML_DIR/ctx-4.sock ssl;
server_name default.test;
ssl_certificate ../../cert/test.crt;
ssl_certificate_key ../../cert/test.key;
ssl_verify_depth 3;

ssl_certificate_by_lua_block {
local session_depth, active_depth = require("ssl_ctx").depths()
assert(session_depth == 3)
assert(active_depth == 3)
}

location / {
content_by_lua_block {
local session_depth, active_depth = require("ssl_ctx").depths()
ngx.say(session_depth, ":", active_depth)
}
}
}

server {
listen unix:$TEST_NGINX_HTML_DIR/ctx-4.sock ssl;
server_name selected.test;
ssl_certificate ../../cert/test.crt;
ssl_certificate_key ../../cert/test.key;
ssl_verify_depth 7;

ssl_certificate_by_lua_block {
local session_depth, active_depth = require("ssl_ctx").depths()
assert(session_depth == 3)
assert(active_depth == 7)
}

location / {
content_by_lua_block {
local session_depth, active_depth = require("ssl_ctx").depths()
ngx.say(session_depth, ":", active_depth)
}
}
}
--- config
location /t {
content_by_lua_block {
local sock = ngx.socket.tcp()
assert(sock:connect("unix:$TEST_NGINX_HTML_DIR/ctx-4.sock"))
assert(sock:sslhandshake(nil, "selected.test", false))
assert(sock:send("GET / HTTP/1.0\r\nHost: selected.test\r\n\r\n"))
local data = assert(sock:receive("*a"))
local _, header_end = data:find("\r\n\r\n", 1, true)
assert(header_end)
ngx.print(data:sub(header_end + 1))
assert(sock:close())
}
}
--- request
GET /t
--- response_body
3:7
--- no_error_log
[error]
Loading
Loading