From 596850ca9a765ceda97f7a611d729ed822317856 Mon Sep 17 00:00:00 2001 From: themilchenko Date: Fri, 8 Nov 2024 11:11:21 +0300 Subject: [PATCH] api: add SSL support It wasn't SSL support. After the patch it was added there are several options to configure SSL: * `use_tls` is a boolean param to enable tls with tls_options provied below (`false` by default); * `ssl_cert_file` is a path to the SSL cert file; * `ssl_key_file` is a path to the SSL key file; * `ssl_ca_file` is a path to the SSL CA file; * `ssl_ciphers` is a colon-separated list of SSL ciphers; * `ssl_password` is a password for decrypting SSL private key; * `ssl_password_file` is a SSL file with key for decrypting SSL private key. Closes #35 --- CHANGELOG.md | 2 + README.md | 9 + http/server.lua | 133 +++- http/sslsocket.lua | 599 ++++++++++++++++++ test/helpers.lua | 9 +- test/integration/http_tls_enabled_test.lua | 188 ++++++ .../http_tls_enabled_validate_test.lua | 119 ++++ test/ssl_data/ca.crt | 32 + test/ssl_data/ca.key | 52 ++ test/ssl_data/ca.srl | 1 + test/ssl_data/client.crt | 32 + test/ssl_data/client.enc.key | 54 ++ test/ssl_data/client.key | 52 ++ test/ssl_data/generate.sh | 68 ++ test/ssl_data/passwd | 1 + test/ssl_data/passwords | 2 + test/ssl_data/server.crt | 32 + test/ssl_data/server.enc.key | 54 ++ test/ssl_data/server.key | 52 ++ 19 files changed, 1487 insertions(+), 4 deletions(-) create mode 100644 http/sslsocket.lua create mode 100644 test/integration/http_tls_enabled_test.lua create mode 100644 test/integration/http_tls_enabled_validate_test.lua create mode 100644 test/ssl_data/ca.crt create mode 100644 test/ssl_data/ca.key create mode 100644 test/ssl_data/ca.srl create mode 100644 test/ssl_data/client.crt create mode 100644 test/ssl_data/client.enc.key create mode 100644 test/ssl_data/client.key create mode 100755 test/ssl_data/generate.sh create mode 100644 test/ssl_data/passwd create mode 100644 test/ssl_data/passwords create mode 100644 test/ssl_data/server.crt create mode 100644 test/ssl_data/server.enc.key create mode 100644 test/ssl_data/server.key diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c28fc9..5a195e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added +- SSL support (#199). + ### Changed ### Fixed diff --git a/README.md b/README.md index c2ee039..0c3ae51 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,15 @@ httpd = require('http.server').new(host, port[, { options } ]) * `idle_timeout` - maximum amount of time an idle (keep-alive) connection will remain idle before closing. When the idle timeout is exceeded, HTTP server closes the keepalive connection. Default value: 0 seconds (disabled). +* `socket_timeout` - host resolving timeout in seconds (default 60). +* TLS options: + * `use_tls` is a boolean param to enable tls with tls_options provied below (`false` by default); + * `ssl_cert_file` is a path to the SSL cert file; + * `ssl_key_file` is a path to the SSL key file; + * `ssl_ca_file` is a path to the SSL CA file; + * `ssl_ciphers` is a colon-separated list of SSL ciphers; + * `ssl_password` is a password for decrypting SSL private key; + * `ssl_password_file` is a SSL file with key for decrypting SSL private key. ## Using routes diff --git a/http/server.lua b/http/server.lua index facee94..ea5380a 100644 --- a/http/server.lua +++ b/http/server.lua @@ -1,6 +1,7 @@ -- http.server local lib = require('http.lib') +local _, sslsocket = pcall(require, 'http.sslsocket') local fio = require('fio') local require = require @@ -9,6 +10,7 @@ local mime_types = require('http.mime_types') local codes = require('http.codes') local log = require('log') +local ffi = require('ffi') local socket = require('socket') local json = require('json') local errno = require 'errno' @@ -1295,11 +1297,72 @@ local function url_for_httpd(httpd, name, args, query) end end +local function enable_tls(host, port, ssl_opts) + local ok, ctx = pcall(sslsocket.ctx, ffi.C.TLS_server_method()) + if ok ~= true then + return nil, error(ctx) + end + + local rc = sslsocket.ctx_use_private_key_file(ctx, ssl_opts.ssl_key_file, + ssl_opts.ssl_password, ssl_opts.ssl_password_file) + if rc == false then + errorf( + "Can't start server on %s:%s: %s %s", + host, port, 'Private key is invalid or password mismatch', ssl_opts.ssl_key_file + ) + end + + rc = sslsocket.ctx_use_certificate_file(ctx, ssl_opts.ssl_cert_file) + if rc == false then + errorf( + "Can't start server on %s:%s: %s %s", + host, port, 'Certificate is invalid', ssl_opts.ssl_cert_file + ) + end + + if ssl_opts.ssl_ca_file ~= nil then + rc = sslsocket.ctx_load_verify_locations(ctx, ssl_opts.ssl_ca_file) + if rc == false then + errorf( + "Can't start server on %s:%s: %s", + host, port, 'CA file is invalid' + ) + end + + sslsocket.ctx_set_verify(ctx, 0x01 + 0x02) + end + + if ssl_opts.ssl_ciphers ~= nil then + rc = sslsocket.ctx_set_cipher_list(ctx, ssl_opts.ssl_ciphers) + if rc == false then + errorf( + "Can't start server on %s:%s: %s", + host, port, 'Ciphers is invalid' + ) + end + end + + return ctx +end + local function httpd_start(self) if type(self) ~= 'table' then error("httpd: usage: httpd:start()") end + local ssl_ctx + if self.options.use_tls then + ssl_ctx = enable_tls(self.host, self.port, { + ssl_cert_file = self.options.ssl_cert_file, + ssl_key_file = self.options.ssl_key_file, + ssl_password = self.options.ssl_password, + ssl_password_file = self.options.ssl_password_file, + ssl_ca_file = self.options.ssl_ca_file, + ssl_ciphers = self.options.ssl_ciphers, + }) + self.tcp_server_f = sslsocket.tcp_server + end + local server = self.tcp_server_f(self.host, self.port, { name = 'http', handler = function(...) @@ -1308,7 +1371,7 @@ local function httpd_start(self) self.internal.postprocess_client_handler() end, http_server = self, - }) + }, self.options.socket_timeout, ssl_ctx) if server == nil then error(sprintf("Can't create tcp_server: %s", errno.strerror())) @@ -1321,6 +1384,56 @@ local function httpd_start(self) return self end +local function validate_ssl_opts(ssl_opts) + if ssl_opts.ssl_cert_file ~= nil then + if type(ssl_opts.ssl_cert_file) ~= 'string' then + error("ssl_cert_file option must be a string") + end + if fio.path.exists(ssl_opts.ssl_cert_file) ~= true then + errorf("file %q not exists", ssl_opts.ssl_cert_file) + end + end + + if ssl_opts.ssl_key_file ~= nil then + if type(ssl_opts.ssl_key_file) ~= 'string' then + error("ssl_key_file option must be a string") + end + if fio.path.exists(ssl_opts.ssl_key_file) ~= true then + errorf("file %q not exists", ssl_opts.ssl_key_file) + end + end + + if ssl_opts.ssl_password ~= nil then + if type(ssl_opts.ssl_password) ~= 'string' then + error("ssl_password option must be a string") + end + end + + if ssl_opts.ssl_password_file then + if type(ssl_opts.ssl_password_file) ~= 'string' then + error("ssl_password_file option must be a string") + end + if fio.path.exists(ssl_opts.ssl_password_file) ~= true then + errorf("file %q not exists", ssl_opts.ssl_password_file) + end + end + + if ssl_opts.ssl_ca_file ~= nil then + if type(ssl_opts.ssl_ca_file) ~= 'string' then + error("ssl_ca_file option must be a string") + end + if fio.path.exists(ssl_opts.ssl_ca_file) ~= true then + errorf("file %q not exists", ssl_opts.ssl_ca_file) + end + end + + if ssl_opts.ssl_ciphers ~= nil then + if type(ssl_opts.ssl_ciphers) ~= 'string' then + error("ssl_ciphers option must be a string") + end + end +end + local exports = { _VERSION = require('http.version'), DETACHED = DETACHED, @@ -1340,6 +1453,20 @@ local exports = { type(options.idle_timeout) ~= 'number' then error('Option idle_timeout must be a number.') end + if options.socket_timeout ~= nil and type(options.socket_timeout) ~= 'number' then + error('Option socket_timeout must be a number') + end + + if options.use_tls == true then + validate_ssl_opts({ + ssl_cert_file = options.ssl_cert_file, + ssl_key_file = options.ssl_key_file, + ssl_password = options.ssl_password, + ssl_password_file = options.ssl_password_file, + ssl_ca_file = options.ssl_ca_file, + ssl_ciphers = options.ssl_ciphers, + }) + end local default = { max_header_size = 4096, @@ -1355,6 +1482,8 @@ local exports = { display_errors = false, disable_keepalive = {}, idle_timeout = 0, -- no timeout, option is disabled + socket_timeout = 60, + use_tls = false, } local self = { @@ -1363,7 +1492,7 @@ local exports = { is_run = false, stop = httpd_stop, start = httpd_start, - options = extend(default, options, true), + options = extend(default, options, false), routes = { }, iroutes = { }, diff --git a/http/sslsocket.lua b/http/sslsocket.lua new file mode 100644 index 0000000..2807b8c --- /dev/null +++ b/http/sslsocket.lua @@ -0,0 +1,599 @@ +local TIMEOUT_INFINITY = 500 * 365 * 86400 +local LIMIT_INFINITY = 2147483647 + +local log = require('log') +local ffi = require('ffi') +local fio = require('fio') +local socket = require('socket') +local buffer = require('buffer') +local clock = require('clock') +local errno = require('errno') + +pcall( + function() + ffi.cdef[[ + typedef struct SSL_METHOD {} SSL_METHOD; + typedef struct SSL_CTX {} SSL_CTX; + typedef struct SSL {} SSL; + + const SSL_METHOD *TLS_server_method(void); + const SSL_METHOD *TLS_client_method(void); + + SSL_CTX *SSL_CTX_new(const SSL_METHOD *method); + void SSL_CTX_free(SSL_CTX *); + + int SSL_shutdown(SSL *ssl); + + int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type); + int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type); + void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx, void *u); + typedef int (*pem_passwd_cb)(char *buf, int size, int rwflag, void *userdata); + + void SSL_CTX_set_default_passwd_cb(SSL_CTX *ctx, pem_passwd_cb cb); + + int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, + const char *CApath); + int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str); + void SSL_CTX_set_verify(SSL_CTX *ctx, int mode, + int (*verify_callback)(int, void *)); + + SSL *SSL_new(SSL_CTX *ctx); + void SSL_free(SSL *ssl); + + int SSL_set_fd(SSL *s, int fd); + + void SSL_set_connect_state(SSL *s); + void SSL_set_accept_state(SSL *s); + + int SSL_write(SSL *ssl, const void *buf, int num); + int SSL_read(SSL *ssl, void *buf, int num); + + int SSL_pending(const SSL *ssl); + + void ERR_clear_error(void); + char *ERR_error_string(unsigned long e, char *buf); + unsigned long ERR_peek_last_error(void); + + int SSL_get_error(const SSL *s, int ret_code); + + typedef socklen_t uint32; + int getsockopt(int sockfd, int level, int optname, void *optval, + socklen_t *optlen); + + int setsockopt(int sockfd, int level, int optname, + const void *optval, socklen_t optlen); + + void *memmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); + ]] + end) + +local function slice_wait(timeout, starttime) + if timeout == nil then + return nil + end + + return timeout - (clock.time() - starttime) +end + +local X509_FILETYPE_PEM = 1 + +local function ctx(method) + ffi.C.ERR_clear_error() + local newctx = + ffi.gc(ffi.C.SSL_CTX_new(method), ffi.C.SSL_CTX_free) + + return newctx +end + +local function ctx_use_private_key_file(ctx, pem_file, password, password_file) + ffi.C.SSL_CTX_set_default_passwd_cb(ctx, box.NULL); + + if password ~= nil then + log.info('set private key password') + + ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, + ffi.cast('void*', ffi.cast('char*', password))) + local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM) + ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL); + + if rc == 1 then + return true + end + end + + if password_file ~= nil then + local fh = fio.open(password_file, {'O_RDONLY'}) + if fh == nil then + ffi.C.ERR_clear_error() + return false + end + + local is_loaded = false + local password_from_file = "" + local char + while char ~= '' do + while true do + char = fh:read(1) + if char == '\n' or char == '' then + break + end + password_from_file = password_from_file .. char + end + + ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, + ffi.cast('void*', ffi.cast('char*', password_from_file))) + local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM) + ffi.C.SSL_CTX_set_default_passwd_cb_userdata(ctx, box.NULL); + + if rc == 1 then + is_loaded = true + break + end + + ffi.C.ERR_clear_error() + password_from_file = '' + end + + fh:close() + + if is_loaded == true then + return true + else + return false + end + end + + local rc = ffi.C.SSL_CTX_use_PrivateKey_file(ctx, pem_file, X509_FILETYPE_PEM) + if rc ~= 1 then + ffi.C.ERR_clear_error() + return false + end + + return true +end + +local function ctx_use_certificate_file(ctx, pem_file) + if ffi.C.SSL_CTX_use_certificate_file(ctx, pem_file, X509_FILETYPE_PEM) ~= 1 then + ffi.C.ERR_clear_error() + return false + end + return true +end + +local function ctx_load_verify_locations(ctx, ca_file) + if ffi.C.SSL_CTX_load_verify_locations(ctx, ca_file, box.NULL) ~= 1 then + ffi.C.ERR_clear_error() + return false + end + return true +end + +local function ctx_set_cipher_list(ctx, str) + if ffi.C.SSL_CTX_set_cipher_list(ctx, str) ~= 1 then + return false + end + return true +end + +local function ctx_set_verify(ctx, flags) + ffi.C.SSL_CTX_set_verify(ctx, flags, box.NULL) +end + +local default_ctx = ctx(ffi.C.TLS_server_method()) + +local SSL_ERROR_WANT_READ =2 +local SSL_ERROR_WANT_WRITE =3 +local SSL_ERROR_SYSCALL =5 -- look at error stack/return value/errno +local SSL_ERROR_ZERO_RETURN =6 + +local sslsocket = { +} +sslsocket.__index = sslsocket + +local WAIT_FOR_READ =1 +local WAIT_FOR_WRITE =2 + +function sslsocket.write(self, data, timeout) + local start = clock.time() + + local size = #data + local s = ffi.cast('const char *', data) + + local mode = WAIT_FOR_WRITE + + while true do + local rc = nil + if mode == WAIT_FOR_READ then + rc = self.sock:readable(slice_wait(timeout, start)) + elseif mode == WAIT_FOR_WRITE then + rc = self.sock:writable(slice_wait(timeout, start)) + else + assert(false) + end + + if not rc then + self.sock._errno = errno.ETIMEDOUT + return nil, 'Timeout exceeded' + end + + ffi.C.ERR_clear_error() + local num = ffi.C.SSL_write(self.ssl, s, size); + if num <= 0 then + local ssl_error = ffi.C.SSL_get_error(self.ssl, num); + if ssl_error == SSL_ERROR_WANT_WRITE then + mode = WAIT_FOR_WRITE + elseif ssl_error == SSL_ERROR_WANT_READ then + mode = WAIT_FOR_READ + elseif ssl_error == SSL_ERROR_SYSCALL then + return nil, self.sock:error() + elseif ssl_error == SSL_ERROR_ZERO_RETURN then + return 0 + else + local error_string = ffi.string(ffi.C.ERR_error_string(ssl_error, nil)) + log.info(error_string) + return nil, error_string + end + else + return num + end + end +end + +function sslsocket.shutdown(self, timeout) + local start = clock.time() + + ffi.C.ERR_clear_error() + local rc = ffi.C.SSL_shutdown(self.ssl) -- ignore result + while rc < 0 do + local mode + local ssl_error = ffi.C.SSL_get_error(self.ssl, rc); + if ssl_error == SSL_ERROR_WANT_WRITE then + mode = WAIT_FOR_WRITE + elseif ssl_error == SSL_ERROR_WANT_READ then + mode = WAIT_FOR_READ + elseif ssl_error == SSL_ERROR_SYSCALL then + return nil, self.sock:error() + elseif ssl_error == SSL_ERROR_ZERO_RETURN then + return 0 + else + local error_string = ffi.string(ffi.C.ERR_error_string(ssl_error, nil)) + log.info(error_string) + return nil, error_string + end + + local waited = nil + if mode == WAIT_FOR_READ then + waited = self.sock:readable(slice_wait(timeout, start)) + elseif mode == WAIT_FOR_WRITE then + waited = self.sock:writable(slice_wait(timeout, start)) + else + assert(false) + end + + if not waited then + self.sock._errno = errno.ETIMEDOUT + return nil, 'Timeout exceeded' + end + + rc = ffi.C.SSL_shutdown(self.ssl) -- ignore result + end + -- TODO Is this possible case for async socket? + if rc == 0 then + ffi.C.SSL_shutdown(self.ssl) + end + + self.sock:shutdown(socket.SHUT_RDWR) + return true +end + +function sslsocket.close(self) + return self.sock:close() +end + +function sslsocket.error(self) + local error_string = + ffi.string(ffi.C.ERR_error_string(ffi.C.ERR_peek_last_error(), nil)) + + return self.sock:error() or error_string +end + +function sslsocket.errno(self) + return self.sock:errno() or ffi.C.ERR_peek_last_error() +end + +function sslsocket.setsockopt(self, level, name, value) + return self.sock:setsockopt(level, name, value) +end + +function sslsocket.getsockopt(self, level, name) + return self.sock:getsockopt(level, name) +end + +function sslsocket.name(self) + return self.sock:name() +end + +function sslsocket.peer(self) + return self.sock:peer() +end + +function sslsocket.fd(self) + return self.sock:fd() +end + +function sslsocket.nonblock(self, nb) + return self.sock:nonblock(nb) +end + +local function sysread(self, charptr, size, timeout) + local start = clock.time() + + local mode = rawget(self, 'first_state') or WAIT_FOR_READ + rawset(self, 'first_state', nil) + + while true do + local rc = nil + if mode == WAIT_FOR_READ then + if ffi.C.SSL_pending(self.ssl) > 0 then + rc = true + else + rc = self.sock:readable(slice_wait(timeout, start)) + end + elseif mode == WAIT_FOR_WRITE then + rc = self.sock:writable(slice_wait(timeout, start)) + else + assert(false) + end + + if not rc then + self.sock._errno = errno.ETIMEDOUT + return nil, 'Timeout exceeded' + end + + ffi.C.ERR_clear_error() + local num = ffi.C.SSL_read(self.ssl, charptr, size); + if num <= 0 then + local ssl_error = ffi.C.SSL_get_error(self.ssl, num); + if ssl_error == SSL_ERROR_WANT_WRITE then + mode = WAIT_FOR_WRITE + elseif ssl_error == SSL_ERROR_WANT_READ then + mode = WAIT_FOR_READ + elseif ssl_error == SSL_ERROR_SYSCALL then + return nil, self.sock:error() + elseif ssl_error == SSL_ERROR_ZERO_RETURN then + return 0 + else + local error_string = ffi.string(ffi.C.ERR_error_string(ssl_error, nil)) + log.info(error_string) + return nil, error_string + end + else + return num + end + end +end + +local function recv(self, limit) + local buffer = ffi.new('char[?]', limit) + local readsize = sysread(self, buffer, limit, 60) + return ffi.string(buffer, readsize) +end + +local function read(self, limit, timeout, check, ...) + assert(limit >= 0) + + local start = clock.time() + + limit = math.min(limit, LIMIT_INFINITY) + local rbuf = self.rbuf + if rbuf == nil then + rbuf = buffer.ibuf() + rawset(self, 'rbuf', rbuf) + end + + local len = check(self, limit, ...) + if len ~= nil then + local data = ffi.string(rbuf.rpos, len) + rbuf.rpos = rbuf.rpos + len + return data + end + + while true do + assert(rbuf:size() < limit) + local to_read = math.min(limit - rbuf:size(), buffer.READAHEAD) + local data = rbuf:reserve(to_read) + assert(rbuf:unused() >= to_read) + + local res, err = sysread(self, data, rbuf:unused(), slice_wait(timeout, start)) + if res == 0 then -- eof + local len = rbuf:size() + local data = ffi.string(rbuf.rpos, len) + rbuf.rpos = rbuf.rpos + len + return data + elseif res ~= nil then + rbuf.wpos = rbuf.wpos + res + local len = check(self, limit, ...) + if len ~= nil then + local data = ffi.string(rbuf.rpos, len) + rbuf.rpos = rbuf.rpos + len + return data + end + else + return nil, err + end + end + + -- not reached +end + +local function check_limit(self, limit) + if self.rbuf:size() >= limit then + return limit + end + return nil +end + +local function check_delimiter(self, limit, eols) + if limit == 0 then + return 0 + end + local rbuf = self.rbuf + if rbuf:size() == 0 then + return nil + end + + local shortest + for _, eol in ipairs(eols) do + local data = ffi.C.memmem(rbuf.rpos, rbuf:size(), eol, #eol) + if data ~= nil then + local len = ffi.cast('char *', data) - rbuf.rpos + #eol + if shortest == nil or shortest > len then + shortest = len + end + end + end + if shortest ~= nil and shortest <= limit then + return shortest + elseif limit <= rbuf:size() then + return limit + end + return nil +end + +function sslsocket.recv(self, limit) + return recv(self, limit) +end + +function sslsocket.read(self, opts, timeout) + timeout = timeout or TIMEOUT_INFINITY + if type(opts) == 'number' then + return read(self, opts, timeout, check_limit) + elseif type(opts) == 'string' then + return read(self, LIMIT_INFINITY, timeout, check_delimiter, { opts }) + elseif type(opts) == 'table' then + local chunk = opts.chunk or opts.size or LIMIT_INFINITY + local delimiter = opts.delimiter or opts.line + if delimiter == nil then + return read(self, chunk, timeout, check_limit) + elseif type(delimiter) == 'string' then + return read(self, chunk, timeout, check_delimiter, { delimiter }) + elseif type(delimiter) == 'table' then + return read(self, chunk, timeout, check_delimiter, delimiter) + end + end + error('Usage: s:read(delimiter|chunk|{delimiter = x, chunk = x}, timeout)') +end + +function sslsocket.readable(self, timeout) + return self.sock:readable(timeout) +end + +local function tcp_connect(host, port, timeout, sslctx) + sslctx = sslctx or default_ctx + + ffi.C.ERR_clear_error() + local ssl = ffi.gc(ffi.C.SSL_new(sslctx), + ffi.C.SSL_free) + if ssl == nil then + return nil, 'SSL_new failed' + end + + local sock = socket.tcp_connect(host, port, timeout) + if not sock then + return nil, 'tcp connect failed' + end + + sock:nonblock(true) + + ffi.C.ERR_clear_error() + local rc = ffi.C.SSL_set_fd(ssl, sock:fd()) + if rc == 0 then + sock:close() + return nil, 'SSL_set_fd failed' + end + + ffi.C.ERR_clear_error() + ffi.C.SSL_set_connect_state(ssl); + + local self = setmetatable({}, sslsocket) + rawset(self, 'sock', sock) + rawset(self, 'ctx', sslctx) + rawset(self, 'ssl', ssl) + rawset(self, 'first_state', WAIT_FOR_WRITE) + + return self +end + +local function wrap_accepted_socket(sock, sslctx) + sslctx = sslctx or default_ctx + + ffi.C.ERR_clear_error() + local ssl = ffi.gc(ffi.C.SSL_new(sslctx), + ffi.C.SSL_free) + if ssl == nil then + sock:close() + return nil, 'SSL_new failed' + end + + sock:nonblock(true) + + ffi.C.ERR_clear_error() + local rc = ffi.C.SSL_set_fd(ssl, sock:fd()) + if rc == 0 then + sock:close() + return nil, 'SSL_set_fd failed' + end + + ffi.C.ERR_clear_error() + ffi.C.SSL_set_accept_state(ssl); + + local self = setmetatable({}, sslsocket) + rawset(self, 'sock', sock) + rawset(self, 'ctx', sslctx) + rawset(self, 'ssl', ssl) + return self +end + +local function tcp_server(host, port, handler, timeout, sslctx) + sslctx = sslctx or default_ctx + + local handler_function = handler.handler + + local wrapper = function (sock, from) + local self, err = wrap_accepted_socket(sock, sslctx) + if not self then + log.info('sslsocket.tcp_server error: %s ', err) + else + handler_function(self, from) + end + end + + handler.handler = wrapper + + return socket.tcp_server(host, port, handler, timeout) +end + +local function accept(server, sslctx) + local sock = server:accept() + if sock == nil then + return nil + end + + return wrap_accepted_socket(sock, sslctx) +end + +return { + ctx = ctx, + ctx_use_private_key_file = ctx_use_private_key_file, + ctx_use_certificate_file = ctx_use_certificate_file, + ctx_load_verify_locations = ctx_load_verify_locations, + ctx_set_cipher_list = ctx_set_cipher_list, + ctx_set_verify = ctx_set_verify, + + tcp_connect = tcp_connect, + tcp_server = tcp_server, + + wrap_accepted_socket = wrap_accepted_socket, + accept = accept, +} diff --git a/test/helpers.lua b/test/helpers.lua index 4170e40..2e59b3e 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -10,10 +10,15 @@ local luatest_utils = require('luatest.utils') helpers.base_port = 12345 helpers.base_host = '127.0.0.1' helpers.base_uri = ('http://%s:%s'):format(helpers.base_host, helpers.base_port) +helpers.tls_uri = ('https://%s:%s'):format('localhost', helpers.base_port) -helpers.cfgserv = function(opts) +helpers.get_testdir_path = function () local path = os.getenv('LUA_SOURCE_DIR') or './' - path = fio.pathjoin(path, 'test') + return fio.pathjoin(path, 'test') +end + +helpers.cfgserv = function(opts) + local path = helpers.get_testdir_path() local opts = opts or {} local opts = http_server.internal.extend({ diff --git a/test/integration/http_tls_enabled_test.lua b/test/integration/http_tls_enabled_test.lua new file mode 100644 index 0000000..db1343b --- /dev/null +++ b/test/integration/http_tls_enabled_test.lua @@ -0,0 +1,188 @@ +local t = require('luatest') +local http_server = require('http.server') +local http_client = require('http.client').new() +local fio = require('fio') + +local helpers = require('test.helpers') + +local g = t.group('ssl') + +local ssl_data_dir = fio.pathjoin(helpers.get_testdir_path(), "ssl_data") + +local server_test_cases = { + test_certificate_missing = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + }, + expected_err_msg = 'Certificate is invalid', + }, + test_key_missing = { + ssl_opts = { + use_tls = true, + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + }, + expected_err_msg = 'Private key is invalid or password mismatch', + }, + test_key_password_missing = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.enc.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + }, + expected_err_msg = 'Private key is invalid or password mismatch', + }, + test_incorrect_key_password = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.enc.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_password = 'incorrect_password', + }, + expected_err_msg = 'Private key is invalid or password mismatch', + }, + test_invalid_ciphers_server = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + ssl_ciphers = "INVALID", + }, + expected_err_msg = "Ciphers is invalid", + }, +} + +for name, tc in pairs(server_test_cases) do + g[name] = function () + g.httpd = helpers.cfgserv(tc.ssl_opts) + + t.assert_error_msg_contains(tc.expected_err_msg, function () + g.httpd:start() + end) + end +end + +local client_test_cases = { + test_basic = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + }, + }, + test_encrypted_key_ok = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.enc.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_password = '1q2w3e', + }, + }, + test_encrypted_key_password_file = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.enc.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_password_file = fio.pathjoin(ssl_data_dir, 'passwd'), + }, + }, + test_encrypted_key_many_passwords_file = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.enc.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_password_file = fio.pathjoin(ssl_data_dir, 'passwords'), + }, + }, + test_key_crt_ca_server_key_crt_client = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + request_opts = { + ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'), + }, + }, + test_client_password_key_missing = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + request_opts = { + ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'client.enc.key'), + }, + expected_err_msg = "Problem with the local SSL certificate: Connection refused", + }, + test_ciphers_server = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + ssl_ciphers = "ECDHE-RSA-AES256-GCM-SHA384", + }, + request_opts = { + ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'), + }, + }, + test_invalid_key_path = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + request_opts = { + ssl_cert = fio.pathjoin(ssl_data_dir, 'client.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'invalid.key'), + }, + expected_err_msg = "Problem with the local SSL certificate: Connection refused", + }, + test_invalid_cert_path = { + ssl_opts = { + use_tls = true, + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + }, + request_opts = { + ssl_cert = fio.pathjoin(ssl_data_dir, 'invalid.crt'), + ssl_key = fio.pathjoin(ssl_data_dir, 'client.key'), + }, + expected_err_msg = "Problem with the local SSL certificate: Connection refused", + }, +} + +for name, tc in pairs(client_test_cases) do + g[name] = function () + g.httpd = helpers.cfgserv(tc.ssl_opts) + g.httpd:start() + + local req_opts = http_server.internal.extend({ + -- We need to provide ca_file by default due to curl uses the + -- system native CA store for verification. + -- See: https://curl.se/docs/sslcerts.html + ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + verbose = true, + }, tc.request_opts or {}) + + if tc.expected_err_msg ~= nil then + t.assert_error_msg_contains(tc.expected_err_msg, function () + http_client:get(helpers.tls_uri .. '/test', req_opts) + end) + else + local r = http_client:get(helpers.tls_uri .. '/test', req_opts) + t.assert_equals(r.status, 200, 'response not 200') + end + + helpers.teardown(g.httpd) + end +end diff --git a/test/integration/http_tls_enabled_validate_test.lua b/test/integration/http_tls_enabled_validate_test.lua new file mode 100644 index 0000000..d149ccd --- /dev/null +++ b/test/integration/http_tls_enabled_validate_test.lua @@ -0,0 +1,119 @@ +local t = require('luatest') +local fio = require("fio") + +local http_server = require('http.server') +local helpers = require('test.helpers') + +local g = t.group() + +local ssl_data_dir = fio.pathjoin(helpers.get_testdir_path(), "ssl_data") + +local test_cases = { + cert_file_incorrect_type = { + opts = { + use_tls = true, + ssl_cert_file = 1, + }, + expected_err_msg = "ssl_cert_file option must be a string", + }, + cert_file_not_exists = { + opts = { + use_tls = true, + ssl_cert_file = "some/path", + }, + expected_err_msg = "file \"some/path\" not exists", + }, + ssl_key_file_incorrect_type = { + opts = { + use_tls = true, + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_key_file = 1, + }, + expected_err_msg = "ssl_key_file option must be a string", + }, + ssl_key_file_not_exists = { + opts = { + use_tls = true, + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_key_file = "some/path", + }, + expected_err_msg = "file \"some/path\" not exists", + }, + ssl_password_incorrect_type = { + opts = { + use_tls = true, + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_password = 1, + }, + expected_err_msg = "ssl_password option must be a string", + }, + ssl_password_file_incorrect_type = { + opts = { + use_tls = true, + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_password = "password", + ssl_password_file = 1, + }, + expected_err_msg = "ssl_password_file option must be a string", + }, + ssl_password_file_not_exists = { + opts = { + use_tls = true, + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_password = "password", + ssl_password_file = "some/path", + }, + expected_err_msg = "file \"some/path\" not exists", + }, + ssl_ca_file_incorrect_type = { + opts = { + use_tls = true, + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_password = "password", + ssl_password_file = fio.pathjoin(ssl_data_dir, 'passwords'), + ssl_ca_file = 1, + }, + expected_err_msg = "ssl_ca_file option must be a string", + }, + ssl_ca_file_not_exists = { + opts = { + use_tls = true, + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_password = "password", + ssl_password_file = fio.pathjoin(ssl_data_dir, 'passwords'), + ssl_ca_file = "some/path", + }, + expected_err_msg = "file \"some/path\" not exists", + }, + ssl_ciphers_incorrect_type = { + opts = { + use_tls = true, + ssl_cert_file = fio.pathjoin(ssl_data_dir, 'server.crt'), + ssl_key_file = fio.pathjoin(ssl_data_dir, 'server.key'), + ssl_password = "password", + ssl_password_file = fio.pathjoin(ssl_data_dir, 'passwords'), + ssl_ca_file = fio.pathjoin(ssl_data_dir, 'ca.crt'), + ssl_ciphers = 1, + }, + expected_err_msg = "ssl_ciphers option must be a string", + }, + socket_timeout_option = { + opts = { + socket_timeout = '60', + }, + expected_err_msg = "Option socket_timeout must be a number", + }, +} + +for name, case in pairs(test_cases) do + g['test_ssl_option_' .. name] = function () + t.assert_error_msg_contains(case.expected_err_msg, function () + http_server.new('host', 8080, case.opts) + end) + end +end diff --git a/test/ssl_data/ca.crt b/test/ssl_data/ca.crt new file mode 100644 index 0000000..e7821a0 --- /dev/null +++ b/test/ssl_data/ca.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIUKt6wkxNs0A+pD2Tq2r3XOHea0KYwDQYJKoZIhvcNAQEL +BQAwVTEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UE +BwwHVW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwIBcNMjQx +MTA4MDczODI5WhgPMjEyNDEwMTUwNzM4MjlaMFUxEDAOBgNVBAsMB1Vua25vd24x +EDAOBgNVBAoMB1Vua25vd24xEDAOBgNVBAcMB1Vua25vd24xEDAOBgNVBAgMB3Vu +a25vd24xCzAJBgNVBAYTAkFVMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC +AgEAttzAMqqr8rUlafG9xK7oXtS/VKTdYdeJ1dYBI9mBSIzcy1HaX/I4CjAdXkYp +fcSqSnfIF54OsRpEEKkMNp6Wn9Yw9YYhHMmF+ABPYgROG6rMqnVhE+1195eB51E4 +QS+785cx6SSQqNyO5OHmXnveMnmnbb6TA+X6pPbAqsjI+/w7E4rlENVdnFh8BE32 +TanSSB68r5A2ZHuNJa7VYxUOqGad20x2TTU75Pv7adEMdnPB77r7eIHpolS6ITfl +MlZ3c9Jcs8H6wUdPhJAAvrUJgvKzeXMlrYpE0Fyqg+rW8qjRaams5H33GKF37lt7 +KsWgeDlCYUHB15SgvWhkHhOFqevcNoTU4U2e9ba4K88O+eNdHK02Ut471chkVMtU +9rz5KHNvgzvQqEa4vuOK0RuI1m9RzlGkNwZYiK8NvYDaWClc80TOJfZMjk5/7QIY +K71R+lzfPNk63cuFkPSuCHX0ZLup1xXS1CsqJJOdof8E6YcODJ8ywzwi0o+J+qUM +tSnsMVUL4uAT3VVM3muw1c0GLPNl+QOp3w3pmBedjZcogYOeewEKsGZ3DynPG0Ey +kP4qgMeP//j+gErfcEiZgZphv5YJ7tqNI3cV6rRX0qXhySwO+pJYk9khQyqTZfmN +gbTob0q1rH+EudvYafShtg4QhfJSezdLISOqMj/Ajp1J72ECAwEAAaNTMFEwHQYD +VR0OBBYEFHXVwjnoqUxDVULH/d3Gpu5mSFtPMB8GA1UdIwQYMBaAFHXVwjnoqUxD +VULH/d3Gpu5mSFtPMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB +ALRB2B/xOQfhIQrr9+YxG2AxsOHBTJ8XGAA6uTC70/mpt4xKahxrhs8XSUz+sBXQ +Ubi3P9drt+FSFETUE8rc4FvLpAvRf33VyRKKAi5AFuYCiv0q1IFFnn5IMeURipba +i5gZkAv+XztLxz3JvqiSvphQvOuR24JaZOItIx4oUYtgaCBy2030oyU9CH9kgkOv +bzt1rVGFDmv+2nOk1M7PYmXo8wcyk9w7SG5+E18bQNhO5eXwQF7+fJxat+Ek/Icj +dETaO29cXhZZHMAXjaZ+UKO+wFrnhbaeNCQMMHMXjnhZMX9+E9dJcyj8eVHYMvq/ +wWOO4f7kCAc2NTM74AiNZY9mjDzJEll+WLiMBjqXMHxDu/hp/eXvzqVQYhqHBRrP +kQInnHRtQOwCEtqV1MbfCMoXNeCFUGqSm+AK7l+7OjIghfRPJPTzhuDOqBGJyrfe +MRyqwyJG/0btoR6E/hubby/DvU8KOcPQTwykv94mqfxFacg/8q0fqnIGabnkulvW +VNTeAzSIwDQ5UGscHKcJuSIXvVq1FWeh/iG6giOcc07wmLcAf0BTz6OORGl8t2zA +I/L27alInyyy+l496LZvV839aGgDXwXj0v9sjryoG4NF+xGezPPvPvOOM6Rxb45C +i74if6n++SnWvLaE3HbgZ0DAg+azjvYtZXe8ppnJKKrj +-----END CERTIFICATE----- diff --git a/test/ssl_data/ca.key b/test/ssl_data/ca.key new file mode 100644 index 0000000..229bf18 --- /dev/null +++ b/test/ssl_data/ca.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +4TlYf5C8oriuXlIl/xjIG4HwVqCadYY= +-----END PRIVATE KEY----- diff --git a/test/ssl_data/ca.srl b/test/ssl_data/ca.srl new file mode 100644 index 0000000..ad6d7c3 --- /dev/null +++ b/test/ssl_data/ca.srl @@ -0,0 +1 @@ +74C2F45BD69A6CF5D81316DE585538739B3BFD95 diff --git a/test/ssl_data/client.crt b/test/ssl_data/client.crt new file mode 100644 index 0000000..f21c209 --- /dev/null +++ b/test/ssl_data/client.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIUdML0W9aabPXYExbeWFU4c5s7/ZUwDQYJKoZIhvcNAQEL +BQAwVTEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UE +BwwHVW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwIBcNMjQx +MTA4MDczODMwWhgPMjEyNDEwMTUwNzM4MzBaMGkxEjAQBgNVBAMMCWxvY2FsaG9z +dDEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UEBwwH +VW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDSboDBNNB1tNcBZabnihhzScKykxhhToA+ +3vAkyOslp7Z1DeQGZ/jt0IFyyDXTLKRFRu2U8vMlxnxKrpwUD3MMFyJFDg9p7oHF +ds6wNGlm79cF+WsTzSYg3Ep0egku8SpvnvMxRS/gJXn3w/QCK3sUj7J8f81fh4Dp +zmzBB6+vf3Roryp745GTeYuJaV0pKZ/cZexpTPw4kvHWgq1TDeIV76rTyPXY0dYn +dtXqzCEm2TCMj2Rek/YPHlM219p6XWisuOMHqIH4/LZOMPElTkccajPxWy2C/kUC +USKA5A98011xXUn+BnNs2HEIOFJQhJDL+xEizNFbP2Ca/xhRdDt+0xX0I3313gmy +qfnof6mivWUsb6EG8l1Py9Nrr4MEzU8917i4hCU9WitL8XJbGjJi8O2n9KuNoRPs +skHkbtJ1Fv2FAVt5pKLNYYolknJVQ2xSlgEaBbQqhXM4xM23hCAe5jTk6MqwUeYM +HZri9LnQ3Chvfw/qWGGt82mQewcueMZV0FPwOomKctnTwCYdImam/YDV9JE8dAXh +t7mLNJufPXRXZt6Tp4XWDc8img8o9d1ExapqyCXxxb4h/BptwTeZMSNH0RD+nvDw +T82CGRIzuN6gODqesUu8BTB84W1xIM+yylfeOFiXIGJD0oo+cBLn9QDHLqDrFgJ2 +ZChOH9/lqQIDAQABo0IwQDAdBgNVHQ4EFgQU1mlkt5xLVzigVk5d78ClvrPTfu0w +HwYDVR0jBBgwFoAUddXCOeipTENVQsf93cam7mZIW08wDQYJKoZIhvcNAQELBQAD +ggIBAGte0IqifuuwfHjhLS6mypTBiVMcrotwHPHguZL1gCg5xPpVRre8MhE802BN +e4Uz1dH7Ri2IvW7QQ2XmeZVKs6wxrnD6Ubt8Hx6HlumdesJKRTlXzOUUqMUZM/um +18wjIo5i+sIzsFTUicXNwc0SpeT1k8J+lWW2QxAIVxdv9HRI+l+RRRJbxZWjUoDK +nOKRCbDN125f1NcpU1Nqh6dLwg0hv86T+CJmGT8GADmJ/CakcHZw8PFWoWAGVRep +HlaRjyD/nHDmkYFmJCFyYI4gDThnbb7Ke1EKXlxc6gWpCMhLvWsGiZwFqGauK3Mp +Bh8LE1XCXJ7d510Ea/7fkiz4qdhVs3wDIWg9TmydVLPd7/VmTa0pHmrldaiCmQhu +JoxXTTpY8zRRe5kd7mdqoSZB0qwfn3kouT8FK5cOghXGaSqqskhzEGr/RuyAsYMA +jx0PW6PLZ1+pGG2d7kAL1ReKwmz1UaGyTl7ntGle1doWwpMPQGf9TlXvscNhCDoP +cDBYOJkyEfFVDYYhHUg9GpDSpFONWNKxMFynO8I5NPovnY1j4VJ5GmvnQPXmTvt8 +KfbPFUuaTsywMoyPzwcMPxRDdcz6QSLaWHXPqoa7sVToC3zn5ra8/PFeU/JoPjKS +U0EpGjnWv3QqEz19YkejePKguVqGaa9AMsMAe0ZT4UqTIj3N +-----END CERTIFICATE----- diff --git a/test/ssl_data/client.enc.key b/test/ssl_data/client.enc.key new file mode 100644 index 0000000..4c0f0f2 --- /dev/null +++ b/test/ssl_data/client.enc.key @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/ssl_data/client.key b/test/ssl_data/client.key new file mode 100644 index 0000000..d6eaff9 --- /dev/null +++ b/test/ssl_data/client.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDSboDBNNB1tNcB +ZabnihhzScKykxhhToA+3vAkyOslp7Z1DeQGZ/jt0IFyyDXTLKRFRu2U8vMlxnxK +rpwUD3MMFyJFDg9p7oHFds6wNGlm79cF+WsTzSYg3Ep0egku8SpvnvMxRS/gJXn3 +w/QCK3sUj7J8f81fh4DpzmzBB6+vf3Roryp745GTeYuJaV0pKZ/cZexpTPw4kvHW +gq1TDeIV76rTyPXY0dYndtXqzCEm2TCMj2Rek/YPHlM219p6XWisuOMHqIH4/LZO +MPElTkccajPxWy2C/kUCUSKA5A98011xXUn+BnNs2HEIOFJQhJDL+xEizNFbP2Ca +/xhRdDt+0xX0I3313gmyqfnof6mivWUsb6EG8l1Py9Nrr4MEzU8917i4hCU9WitL +8XJbGjJi8O2n9KuNoRPsskHkbtJ1Fv2FAVt5pKLNYYolknJVQ2xSlgEaBbQqhXM4 +xM23hCAe5jTk6MqwUeYMHZri9LnQ3Chvfw/qWGGt82mQewcueMZV0FPwOomKctnT +wCYdImam/YDV9JE8dAXht7mLNJufPXRXZt6Tp4XWDc8img8o9d1ExapqyCXxxb4h +/BptwTeZMSNH0RD+nvDwT82CGRIzuN6gODqesUu8BTB84W1xIM+yylfeOFiXIGJD +0oo+cBLn9QDHLqDrFgJ2ZChOH9/lqQIDAQABAoICAAdL9qH8c1gJh8UQIcv4kWV6 +AsrPZ/KD1tWXRGt6HhFFsgF4FFaWh16zsrFouNkUPLP8RCO8kurV6ZxrVpUpfftG +2BTd6nHpZ82Rk5QvlRIRMfsOjYR3wiE0kk5cpvHeQfLx54vnUsQqeIK7ZDwpBtEN +NIq1ocj0uWciFcpRumlS+ZXhsQ7vsq4S8mA265iQTW9Gh36VQU+y5Ljj+h+dpR/O +mjVSzBeTGyJuL/e+0U14DYNqO3g+GDOpAQivTm+cypLmrFSpJqycErQ+ZTY+cx/M +nPV7DGZ0666rYo9mmRTifWR/cB/jWGBHVxAKZ+xL1HuGPq9eu8m2tmJZgx3b8m4g +vm2giIT6wfvProKS8IfOJcHn/wNpi8Zl3ngDkBRFANLwyLN/+eJG3XijXR7DXkUb +VuBHPKCaMLfnTUXu7DBAfiu39vIpVWK/62PLsWo+tYq+0WjRmNbEFNdvGCr3M8b9 +XxRRTMHiIdhpmTVHO6q91fgJRJdVNx7GRYBqVhym/cWfm7cCHP0QbAtMN+VMgFee +yjmsa8WaDH8JllXT4MChKCWwGT8dtyL9JFgG6xk4eAMTr9YsnwII/A6gAA26Trwu +sa7mVoBlH1hZOhXOnDoO3dV6EJ9Sz4HC/T7jLSthqHq8G0j9jgP+0TZ1tsLuDWSs +g+Q1NMjE/zo5Qmk2MjljAoIBAQD6JYHO/QuhhZjdMfIc1vJLdkhDyFLtLSBkxZ57 +PGDr9UDgV5MnIrCk8CZrZxGOtT3Tj2ohbTIhaOrpUN2E9rqBLSXn3Dn3FSAeZ8X/ +5dBz5F6NdyTGf+M2vYgm/dg96RToJPY/vgEqte4Mm4RFZ5/mboIgxIeU8sOnrtH9 +2bWqdnW8A8rAzequFDVvueaGsB26yskObOCQoLkQp6HaTcmbZU8Ie0v/iOlf7kXL +FXYEWaRvKg4O2jigdnx9d1iGF2deIouH4bW63BXcpYXvzPnB1V4848lIWC3aB+9m +nZ7RV7130mj0X1g9zKAp4VvfIhi4YqkGygMzOW8KRJkAsBdDAoIBAQDXWxXncM3u +AJKZi38JpdOSe3lZbEljoA3fYWbpzY8WOxJbn4XUQ7Cdmke7WvlIO3jbzpkPWonR +pFmYJMOGp7h7EWddPP7fOjFeJ6TJjijCtnu80d7UHIZJ2oZvjqPr9DcXrr/Pn8k+ +iY65V11IiG+Jjq79OycUfbrhv/YZwoLDj/YA5iDgTV/2NrIODKB0qdllpoI1dGUl +V2fXL7emRz5aRMjrGLymGcvsMYB2FFNLfw8NO0QrrDEB5aCk6xkFOe/DIlnwXswd +noQ1CVcMe/S68uoDNq8sI4lhn90+3/r/OGDzcQA9KFQGqnLVMDglUoJOaMCWdsK8 +ULE7EG7tkjKjAoIBAQCcRzKCDrVlhAGsr7eDLQbS/mLHdi/Y3YiPbKdGdsJWqDKP +9iaJHLMfWKmoEAx4C+NEeSTlHUNkfBfHDC5ZE4wRiBNWd8/+/cPDOzIIXZuNy+8G +kpj3Ko7ZdC/LrGucwjG9ltoBmMNB28eNONu6QLM1U3UY46+Q7totuJqY7ZsBlGCZ +xgS1z+/+McHwu0O6ge0Q9gAGcx8ZPFBih1gm+tIps3Fc6yrfyrmCpWoVJqNEtHx2 +tt9xiAQ4u82q1RUJMTXzKcHicrEGvNkrsH2tA3JGFvd5MxZdjDmZLbvzcCX4w8gr +Q9kuUyLd+SlXAORU0wh+qaTQCQVWy1sEHzc3psvhAoIBAQCLs6jn9IOCS5jORnHo +vkwbkEHOQrLxD9kv+a2bKiASWcu06C0W37po4rZ50bA4rWvfm4wrK56QAr+kNOUq +Kw8/trCJCZKFGOkBnVIG9lN2zI5ElRiqHL74levz3mJ0JH7AvDnt5EfWa8HMdeIr +tWY1o/vchkz5u/5JiA+L8mSFnJQHTUIyf78qp5ymBIbqZ2yBxpxdNN6QdL0GGQxX +r4vBXzG/YNKwJbflxs8AynqmVQxclv/IHPHFu0KU+XXHsCfbPCOADN74r+YvyZlQ +nfDKfd5Uq1rDlWH/lIcfzIi0m8w44Cs5gTnRAS1xItCpVXb2inm0oeH965KtMCHl +t5tlAoIBABNNooLxOAIfTvUX8A9CtWPhBqZzlCrMwRFx4MmiPLW071oMBTyD/NKh +eb5bT/BEaJaL0nHWLK+C77XDWJqegE103mD+sdrtrNqyQKS0hyvLgsCf+did74K1 +lLLLpm1kTmeivQQb4mIEm5IKwzatNI3jn8nuVKu1svETzkwLLZtRu8rFckbreKMT +IoYwRTEEdkQanhg8/U/zqGyUDx3QjIR7kssJ+hBeOu5WjwA0HJktvYDIRf8f6ZQk +HdrxGU81BGwyUValjZ6zJje6hg73O4p0QQ4dfJA6EPzqTfJdhh2OR99RqQFA6Vdr +9eIgheeP8x6NoIy/1o+oiZGoFOlitIY= +-----END PRIVATE KEY----- diff --git a/test/ssl_data/generate.sh b/test/ssl_data/generate.sh new file mode 100755 index 0000000..f771d2e --- /dev/null +++ b/test/ssl_data/generate.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# +# This script generates SSL keys and certificates used for testing. + +HOST=localhost +NEWKEY_ARG=rsa:4096 +DAYS_ARG=36500 + +# +# Generates new CA. +# +# The new private key and certificate are written to files "${ca}.key" and +# "${ca}.crt" respectively where "${ca}" is the name of the new CA as given +# in the first argument. +# +gen_ca() +{ + local ca="${1}" + openssl req -new -nodes -newkey "${NEWKEY_ARG}" -days "${DAYS_ARG}" -x509 \ + -subj "/OU=Unknown/O=Unknown/L=Unknown/ST=unknown/C=AU" \ + -keyout "${ca}.key" -out "${ca}.crt" +} + +# +# Generates new certificate and private key signed by the given CA. +# +# The new private key and certificate are written to files "${cert}.key" and +# "${cert}.crt" respectively where "${cert}" is the certificate name as given +# in the first argument. The CA and private key used for signing the new +# certificate should be located in "${ca}.cert" and "${ca}.key" where "${ca}" +# is the value of the second argument. +# +gen_cert() +{ + local cert="${1}" + local ca="${2}" + openssl req -new -nodes -newkey "${NEWKEY_ARG}" \ + -subj "/CN=${HOST}/OU=Unknown/O=Unknown/L=Unknown/ST=unknown/C=AU" \ + -keyout "${cert}.key" -out "${cert}.csr" + openssl x509 -req -days "${DAYS_ARG}" \ + -CAcreateserial -CA "${ca}.crt" -CAkey "${ca}.key" \ + -in "${cert}.csr" -out "${cert}.crt" + rm -f "${cert}.csr" +} + +# +# Encrypt private key file. +# $1 - file name without extension +# $2 - pass phrase +# +# Encrypted key is written to ${1}.enc.key +# +encrypt_key() +{ + local key="${1}" + local pass="${2}" + openssl rsa -aes256 -passout "pass:${pass}" \ + -in "${key}.key" -out "${key}.enc.key" +} + +gen_ca ca +gen_cert server ca +gen_cert client ca +encrypt_key server 1q2w3e +encrypt_key client 123qwe + +echo '1q2w3e' > passwd +echo $'incorrect_password\n1q2w3e' > passwords diff --git a/test/ssl_data/passwd b/test/ssl_data/passwd new file mode 100644 index 0000000..449f56a --- /dev/null +++ b/test/ssl_data/passwd @@ -0,0 +1 @@ +1q2w3e diff --git a/test/ssl_data/passwords b/test/ssl_data/passwords new file mode 100644 index 0000000..4e893a2 --- /dev/null +++ b/test/ssl_data/passwords @@ -0,0 +1,2 @@ +incorrect_password +1q2w3e diff --git a/test/ssl_data/server.crt b/test/ssl_data/server.crt new file mode 100644 index 0000000..f10dbeb --- /dev/null +++ b/test/ssl_data/server.crt @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIUdML0W9aabPXYExbeWFU4c5s7/ZQwDQYJKoZIhvcNAQEL +BQAwVTEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UE +BwwHVW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwIBcNMjQx +MTA4MDczODMwWhgPMjEyNDEwMTUwNzM4MzBaMGkxEjAQBgNVBAMMCWxvY2FsaG9z +dDEQMA4GA1UECwwHVW5rbm93bjEQMA4GA1UECgwHVW5rbm93bjEQMA4GA1UEBwwH +VW5rbm93bjEQMA4GA1UECAwHdW5rbm93bjELMAkGA1UEBhMCQVUwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC1ld4oG80WdjiDYhM5j2ydE11QkDK2zJld +gyG86je3sH2O7pFbe3ehE6xxKHm3BSiGk7XvAnRxGvNXfabH71bKymox87KaeCSr +g1RuRdsHC7KHheZkPHQQYJb0GhW01xvvo127Ctxo38dE54y0aakx9huixVxdjoW3 +VxaJb6Ytjy4L59BDRoV9YwwX9F1fCJM+DLzzRCGxPCQEL10E2mHe2nsJPoWXxWTN +4UvKv6VUjpSKGcCVRf7V/Syfdbxi4OGndXwoFdEFDyJ8rAfhO2vWOtdNIsG6xyeo +HiLnR58MUfHLYTEEI0mBoWPQfXwSC0dyanNKy98V3bWUR9rFjfwDrYkZOjS477xE +M4A1zQ8CRsgWLxlaQGgb/BuH4Q6l2OnDvbFCcTxazNCiyhLFo5VFFQbk0Bng4GQl +3LCEc4/dmUsVYU3jEuIdKGpWkWDvWRjMFGmmVfiS0BlfCPhpLiuI487EyTzv7+5D +vnszxvmjgIbp5zHHJYS/QU1/KSP+fOeN88cH7jkdle197BaZq1egY4SsAbamqSsu +X0v0Bg6p3VncaQ9oh2nGgt8+FSlfTUFvFMAmC5K+u/H1UWMrLZlxvm+sK6E812pA +6DoeUMTpxSmeMurKn231wnlp1ZakDDu+vz0S3i0yjFmKt8VjZ8TYfpFJpRqiGHQD +Uj/pNbwRtQIDAQABo0IwQDAdBgNVHQ4EFgQUB3J+hmjSiFvPUSCJBDq+xpv0chsw +HwYDVR0jBBgwFoAUddXCOeipTENVQsf93cam7mZIW08wDQYJKoZIhvcNAQELBQAD +ggIBAKyrGUn+xhWlfqThhFaJJR9ANlt3LRfFn1fp5K08jtWrErM/GxUB9X0IhwIK +iKoP84rhWxTnidkaIfwKJxRLuouV9JADA84Xa4hu6pLP2F6+i9fZ09eFzuAYrWXC +jL187vZvPNVqSY7ZRqu160bhqxTe76cFbixtREHmv2frDkQ+ZjxF5CkaVcDIjvIF +FGmtj21mF1cEgzQTl/QocjpFrH2YVaAe8CxklhcMU4EHIta+TiNeGEJlR3J7xt/+ +4/ywvhm5Eyykqv5kqmIq1aa2wn7tPXH3nI+bToCx+e4xQZpHrKV/+bcqHARRDLyp +0w9aZcFItR3ZI3zw+x/Preay6AkjA+pc3RXFdCpiWDGTJ9F5DE9JyZ2luIIm3d7c +a2kbPnMy6V55y/kLaiSMyReTIJybd67hFfvFxLy4hj4I6YX5z9hx4gS7sdsi6ZqJ +DVjVlko3yzQbj5s2vZjEPfXkxAz1eEJRI2a3g50f6x+IZ2j9bwhihITGPDyAjsGV +9J/Dso98klq5vULA6emQoN0XKzazzLgA0/alXWzFtbqw6AMN89kyetkOCxmAzIz8 +2DpJ/sDskd3B6ptszC6QF3OI1FwyhM5qP/M8GcACEXMFDiXu8NatbjjHAgwQhNAW +Q2kupe6//oqKoBiHED8OO2f7c2nP91PnlZH2M8YkcQO4Ixx9 +-----END CERTIFICATE----- diff --git a/test/ssl_data/server.enc.key b/test/ssl_data/server.enc.key new file mode 100644 index 0000000..b2211ed --- /dev/null +++ b/test/ssl_data/server.enc.key @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/test/ssl_data/server.key b/test/ssl_data/server.key new file mode 100644 index 0000000..15f0005 --- /dev/null +++ b/test/ssl_data/server.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +OKeTgVk//S5ZTzfk3kynPNbepCk9Gbo= +-----END PRIVATE KEY-----