Skip to content

Commit a5e0484

Browse files
committed
api: support ssl_verify_client option
This patch allows to set a new `ssl_verify_client` option. It uses in pair with `ssl_ca_file` option and needs for client validation. It could have following values: * `off` (default one) means that no client's certs will be verified; * `on` means that server will verify client's certs; * `optional` means that server will verify client's certs only if it exist. In effect, this option forces the server to work with mutual TLS. Part of #207
1 parent acf615a commit a5e0484

File tree

9 files changed

+189
-1
lines changed

9 files changed

+189
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
88

99
## Added
1010

11+
- `ssl_verify_client` option (#207).
12+
1113
## Changed
1214

1315
## Fixed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ httpd = require('http.server').new(host, port[, { options } ])
158158
* `ssl_ciphers` is a colon-separated list of SSL ciphers, optional;
159159
* `ssl_password` is a password for decrypting SSL private key, optional;
160160
* `ssl_password_file` is a SSL file with key for decrypting SSL private key, optional.
161+
* `ssl_verify_client` is an option that allows to verify client. It has following values:
162+
* `off` (default) means that no client's certs will be verified;
163+
* `on` means that server will verify client's certs;
164+
* `optional` means that server will verify client's certs only if it exist.
161165

162166
## Using routes
163167

http/server.lua

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1296,6 +1296,12 @@ local function url_for_httpd(httpd, name, args, query)
12961296
end
12971297
end
12981298

1299+
local VERIFY_CLIENT_OPTS = {
1300+
off = sslsocket.SET_VERIFY_FLAGS.SSL_VERIFY_NONE,
1301+
optional = sslsocket.SET_VERIFY_FLAGS.SSL_VERIFY_PEER,
1302+
on = sslsocket.SET_VERIFY_FLAGS.SSL_VERIFY_PEER + sslsocket.SET_VERIFY_FLAGS.SSL_VERIFY_FAIL_IF_NO_PEER,
1303+
}
1304+
12991305
local function create_ssl_ctx(host, port, opts)
13001306
local ok, ctx = pcall(sslsocket.ctx, sslsocket.tls_server_method())
13011307
if ok ~= true then
@@ -1328,7 +1334,8 @@ local function create_ssl_ctx(host, port, opts)
13281334
)
13291335
end
13301336

1331-
sslsocket.ctx_set_verify(ctx, 0x00)
1337+
local set_verify_flag = opts.ssl_verify_client and VERIFY_CLIENT_OPTS[opts.ssl_verify_client] or VERIFY_CLIENT_OPTS.off
1338+
sslsocket.ctx_set_verify(ctx, set_verify_flag)
13321339
end
13331340

13341341
if opts.ssl_ciphers ~= nil then
@@ -1383,6 +1390,12 @@ local function validate_ssl_opts(opts)
13831390
errorf("%s option must be a string", key)
13841391
end
13851392

1393+
if key == 'ssl_verify_client' then
1394+
if VERIFY_CLIENT_OPTS[value] == nil then
1395+
errorf('%q option not exists. Available options: "on", "off", "optional"', value)
1396+
end
1397+
end
1398+
13861399
if string.find(key, 'file') ~= nil and fio.path.exists(value) ~= true then
13871400
errorf("file %q not exists", value)
13881401
end
@@ -1429,6 +1442,7 @@ local exports = {
14291442
ssl_password_file = options.ssl_password_file,
14301443
ssl_ca_file = options.ssl_ca_file,
14311444
ssl_ciphers = options.ssl_ciphers,
1445+
ssl_verify_client = options.ssl_verify_client,
14321446
})
14331447

14341448
local default = {
@@ -1499,6 +1513,7 @@ local exports = {
14991513
ssl_password_file = self.options.ssl_password_file,
15001514
ssl_ca_file = self.options.ssl_ca_file,
15011515
ssl_ciphers = self.options.ssl_ciphers,
1516+
ssl_verify_client = self.options.ssl_verify_client,
15021517
})
15031518
return sslsocket.tcp_server(host, port, handler, timeout, ssl_ctx)
15041519
end

http/sslsocket.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ pcall(ffi.cdef, [[
5656
const void *needle, size_t needlelen);
5757
]])
5858

59+
local SET_VERIFY_FLAGS = {
60+
SSL_VERIFY_NONE = 0x00,
61+
SSL_VERIFY_PEER = 0x01,
62+
SSL_VERIFY_FAIL_IF_NO_PEER = 0x02,
63+
}
64+
5965
local function slice_wait(timeout, starttime)
6066
if timeout == nil then
6167
return nil
@@ -452,6 +458,8 @@ local function tcp_server(host, port, handler, timeout, sslctx)
452458
end
453459

454460
return {
461+
SET_VERIFY_FLAGS = SET_VERIFY_FLAGS,
462+
455463
tls_server_method = tls_server_method,
456464

457465
ctx = ctx,

test/helpers.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ helpers.base_host = '127.0.0.1'
1212
helpers.base_uri = ('http://%s:%s'):format(helpers.base_host, helpers.base_port)
1313
helpers.tls_uri = ('https://%s:%s'):format('localhost', helpers.base_port)
1414

15+
helpers.CONNECTION_REFUSED_ERR_MSG = "Failure when receiving data from the peer: Connection refused"
16+
1517
helpers.get_testdir_path = function()
1618
local path = os.getenv('LUA_SOURCE_DIR') or './'
1719
return fio.pathjoin(path, 'test')

test/integration/http_tls_enabled_test.lua

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,73 @@ local client_test_cases = {
145145
},
146146
expected_err_msg = "curl: Problem with the local SSL certificate",
147147
},
148+
test_verify_client_optional_with_certs_valid = {
149+
ssl_opts = {
150+
ssl_verify_client = "optional",
151+
ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'),
152+
ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'),
153+
ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'),
154+
},
155+
request_opts = {
156+
ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'),
157+
ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'),
158+
},
159+
},
160+
test_verify_client_optional_with_certs_invalid = {
161+
ssl_opts = {
162+
ssl_verify_client = "optional",
163+
ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'),
164+
ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'),
165+
ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'),
166+
},
167+
request_opts = {
168+
ssl_cert = fio.pathjoin(ssl_data_dir, 'bad_client.crt'),
169+
ssl_key = fio.pathjoin(ssl_data_dir, 'bad_client.key'),
170+
},
171+
expected_err_msg = helpers.CONNECTION_REFUSED_ERR_MSG,
172+
},
173+
test_verify_client_optional_withouts_certs = {
174+
ssl_opts = {
175+
ssl_verify_client = "optional",
176+
ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'),
177+
ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'),
178+
ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'),
179+
},
180+
},
181+
test_verify_client_on_valid = {
182+
ssl_opts = {
183+
ssl_verify_client = "on",
184+
ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'),
185+
ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'),
186+
ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'),
187+
},
188+
request_opts = {
189+
ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'),
190+
ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'),
191+
},
192+
},
193+
test_verify_client_on_invalid = {
194+
ssl_opts = {
195+
ssl_verify_client = "on",
196+
ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'),
197+
ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'),
198+
ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'),
199+
},
200+
request_opts = {
201+
ssl_cert = fio.pathjoin(ssl_data_dir, 'bad_client.crt'),
202+
ssl_key = fio.pathjoin(ssl_data_dir, 'bad_client.key'),
203+
},
204+
expected_err_msg = helpers.CONNECTION_REFUSED_ERR_MSG,
205+
},
206+
test_verify_client_on_certs_missing = {
207+
ssl_opts = {
208+
ssl_verify_client = "on",
209+
ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'),
210+
ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'),
211+
ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'),
212+
},
213+
expected_err_msg = helpers.CONNECTION_REFUSED_ERR_MSG,
214+
},
148215
}
149216

150217
for name, tc in pairs(client_test_cases) do

test/integration/http_tls_enabled_validate_test.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ local test_cases = {
105105
},
106106
expected_err_msg = "ssl_ciphers option must be a string",
107107
},
108+
ssl_verify_client_incorrect_value = {
109+
opts = {
110+
ssl_verify_client = "unknown",
111+
},
112+
expected_err_msg = '"unknown" option not exists. Available options: "on", "off", "optional"'
113+
},
108114
}
109115

110116
for name, case in pairs(test_cases) do

test/ssl_data/bad_client.crt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-----BEGIN CERTIFICATE-----
2+
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
32+
-----END CERTIFICATE-----

test/ssl_data/bad_client.key

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
-----BEGIN PRIVATE KEY-----
2+
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
51+
4xz9jwcKbEzsHdwVB+OoRs47xcWsd923
52+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)