local lu = require 'luaunit' local ok, uv = pcall(require, 'luv') local math = require 'math' local openssl = require "openssl" local helper = require 'helper' local bio, ssl = openssl.bio, openssl.ssl local unpack = table.unpack or unpack if not ok then uv = nil print('skip SSL, bacause luv not avalible') end TestSSL = {} local LUA if arg then local i = 0 repeat LUA = arg[i] print(i, LUA) i = i - 1 until not arg[i] assert(LUA) end if uv then math.randomseed(os.time()) local function set_timeout(timeout, callback) local timer = uv.new_timer() local function ontimeout() uv.timer_stop(timer) uv.close(timer) callback() end uv.timer_start(timer, timeout, 0, ontimeout) return timer end function TestSSL:testMisc() assert(ssl.alert_type(1) == 'W') assert(ssl.alert_type(1, true) == 'warning') assert(ssl.alert_type(2) == 'F') assert(ssl.alert_type(2, true) == 'fatal') assert(ssl.alert_type(3) == 'U') assert(ssl.alert_type(3, true) == 'unknown') local list = {10, 20, 21, 22, 30, 40, 50, 60, 70, 80, 90, 100} for _, i in pairs(list) do assert(ssl.alert_desc(i) ~= 'U', i) assert(ssl.alert_desc(i, true) ~= 'unknown', i) end end function TestSSL:testUV_1SSL() local port = math.random(8000, 9000) helper.spawn(LUA, {"8.ssl_s.lua", '127.0.0.1', port}, 'accpeting...', function() print('started') helper.spawn(LUA, {"8.ssl_c.lua", '127.0.0.1', port} ) end ) uv.run() end function TestSSL:testUV_2BIO() local port = math.random(8000, 9000) helper.spawn(LUA, {"8.bio_s.lua", '127.0.0.1', port}, 'accpeting...', function() print('started') helper.spawn(LUA, {"8.bio_c.lua", '127.0.0.1', port} ) end ) uv.run() end function TestSSL:testUV_3SSLCBIO() local port = math.random(8000, 9000) helper.spawn(LUA, {"8.bio_s.lua", '127.0.0.1', port}, 'accpeting...', function() print('started') helper.spawn(LUA, {"8.ssl_c.lua", '127.0.0.1', port} ) end ) uv.run() end function TestSSL:testUV_4BIOCSSL() local port = math.random(8000, 9000) helper.spawn(LUA, {"8.ssl_s.lua", '127.0.0.1', port}, 'accpeting...', function() print('started') helper.spawn(LUA, {"8.bio_c.lua", '127.0.0.1', port} ) end ) uv.run() end function TestSSL:testUV_SSL4UV() local port = math.random(8000, 9000) helper.spawn(LUA, {"8.ssl_uv_s.lua", '127.0.0.1', port}, 'accpeting...', function() print('started') helper.spawn(LUA, {"8.bio_uv_c.lua", '127.0.0.1', port} ) end ) uv.run() end function TestSSL:testUV_DTLS() if helper.opensslv:match('1.0.2') then return end local port = math.random(8000, 9000) helper.spawn(LUA, {"8.bio_dtls_s.lua", '127.0.0.1', port}, 'accpeting...', function() print('started') helper.spawn(LUA, {"8.bio_dtls_c.lua", '127.0.0.1', port} ) end ) uv.run() end end local luv ok, luv = pcall(require, 'lluv') if not ok then luv = nil end local lua_spawn do local function P(pipe, read) return { stream = pipe, flags = luv.CREATE_PIPE + (read and luv.READABLE_PIPE or luv.WRITABLE_PIPE) } end lua_spawn = function(f, o, e, c) return luv.spawn({ file = LUA, args = {f}, stdio = {{}, P(o, false), P(e, false)} }, c) end end local function onread(pipe, err, chunk) if err then if err:name() ~= 'EOF' then assert(not err, tostring(err)) end pipe:close() end if chunk then print(chunk) else print("end") end end local function onclose(child, err, status) if err then return print("Error spawn:", err) end lu.assertEquals(status, 0) child:close() end if luv then function TestSSL:testLUVSSL() local stdout1 = luv.pipe() local stderr1 = luv.pipe() local stdout2 = luv.pipe() local stderr2 = luv.pipe() lua_spawn("8.ssl_s.lua", stdout1, stderr1, onclose) os.execute('ping -n 3 127.0.0.1') lua_spawn("8.ssl_c.lua", stdout2, stderr2, onclose) stdout1:start_read(onread) stderr1:start_read(onread) stdout2:start_read(onread) stderr2:start_read(onread) luv.run() luv.close() end function TestSSL:testLUVBio() local stdout1 = luv.pipe() local stderr1 = luv.pipe() local stdout2 = luv.pipe() local stderr2 = luv.pipe() lua_spawn("8.bio_s.lua", stdout1, stderr1, onclose) os.execute('ping -n 3 127.0.0.1') lua_spawn("8.bio_c.lua", stdout2, stderr2, onclose) stdout1:start_read(onread) stderr1:start_read(onread) stdout2:start_read(onread) stderr2:start_read(onread) luv.run() luv.close() end function TestSSL:testLUVsslconnectbio() local stdout1 = luv.pipe() local stderr1 = luv.pipe() local stdout2 = luv.pipe() local stderr2 = luv.pipe() lua_spawn("8.bio_s.lua", stdout1, stderr1, onclose) os.execute('ping -n 3 127.0.0.1') lua_spawn("8.ssl_c.lua", stdout2, stderr2, onclose) stdout1:start_read(onread) stderr1:start_read(onread) stdout2:start_read(onread) stderr2:start_read(onread) luv.run() luv.close() end function TestSSL:testLUVbioconnectssl() local stdout1 = luv.pipe() local stderr1 = luv.pipe() local stdout2 = luv.pipe() local stderr2 = luv.pipe() lua_spawn("8.ssl_s.lua", stdout1, stderr1, onclose) os.execute('ping -n 3 127.0.0.1') lua_spawn("8.bio_c.lua", stdout2, stderr2, onclose) stdout1:start_read(onread) stderr1:start_read(onread) stdout2:start_read(onread) stderr2:start_read(onread) luv.run() luv.close() end end function TestSSL:testCTX() local protocols = { "TLS", "DTLS", "SSLv23", "DTLSv1_2", "DTLSv1", "TLSv1_2", "TLSv1_1", "TLSv1", "SSLv3" } local mode = {"client", "server", ""} local num = 0 for _, v in pairs(protocols) do for _, m in pairs(mode) do local ctx if #m > 0 then v = string.format('%s_%s', v, m) end ctx = pcall(ssl.ctx_new, v) if ctx then num = num + 1 end end end assert(num > 0) end function TestSSL:testSNI() local ca = helper.get_ca() local store = ca:get_store() assert(store:trust(true)) store:add(ca.cacert) store:add(ca.crl) local certs = {} local session_cache = {} local function create_ctx(dn, mode) mode = mode or '_server' local ctx = ssl.ctx_new(ssl.default .. mode) if dn then local cert, pkey = helper.sign(dn) assert(ctx:use(pkey, cert)) certs[#certs + 1] = cert end if ctx.version then ctx:version(0x301, 0x303) end ctx:set_session_callback( function(s, ss) -- add assert(tostring(s):match('openssl.ssl ')) assert(tostring(ss):match('openssl.ssl_session')) local id = ss:id() session_cache[id] = ss return true end ,function(s, id) -- get assert(tostring(s):match('openssl.ssl ')) assert(type(id)=='string') return assert(session_cache[id]) end, function(id) session_cache[id] = nil end ) ctx:session_cache_mode('both', 'no_internal') -- warning: https://stackoverflow.com/questions/14397917/reuse-ssl-session-on-c-client-server-application if ssl.no_ticket then ctx:options(ssl.no_ticket) end return ctx end local function create_srv_ctx(tab) local ctx = create_ctx({{CN = "server"}, {C = "CN"}}) local sni_ctx = { ["serverA"] = create_ctx {{CN = "serverA"}, {C = "CN"}}, ["serverB"] = create_ctx {{CN = "serverB"}, {C = "CN"}} } if tab then ctx:set_servername_callback(sni_ctx) else local sni_cb = function(name) return sni_ctx[name] end ctx:set_servername_callback(sni_cb) end if store then ctx:cert_store(store) end ctx:set_cert_verify() ctx:set_cert_verify({always_continue = true, verify_depth = 4}) return ctx end local function create_cli_ctx() local ctx = create_ctx(nil, '_client') if store then ctx:cert_store(store) end ctx:set_cert_verify({always_continue = true, verify_depth = 4}) return ctx end local bs, bc = bio.pair() local rs, cs, es, ec, i, o, sess local srv_ctx = create_srv_ctx() local ss = assert(srv_ctx:ssl()) assert(ss:dup()) local sbio = bio.filter('ssl', ss, 0) sbio:close() local cli_ctx = create_cli_ctx() local srv = assert(srv_ctx:ssl(bs, bs, true)) local cli = assert(cli_ctx:ssl(bc, bc, false)) srv_ctx:add(ca.cacert, certs) if openssl.engine then srv_ctx:set_engine(openssl.engine('openssl')) end srv_ctx:timeout(500) assert(srv_ctx:timeout() == 500) local t = assert(srv_ctx:session_cache_mode()) srv_ctx:mode(true, "enable_partial_write", "accept_moving_write_buffer", "auto_retry", "no_auto_chain") srv_ctx:mode(false, "enable_partial_write", "accept_moving_write_buffer", "auto_retry", "no_auto_chain") srv_ctx:flush_sessions(10000) repeat cs, ec = cli:handshake() rs, es = srv:handshake() until (rs and cs) or (rs == nil or cs == nil) assert(rs and cs) i, o = cli:pending() local msg = openssl.random(20) cli:write(msg) srv:write(srv:read()) local got = cli:read() assert(got == msg) local peer = cli:peer() assert(peer:subject():oneline() == "/CN=server/C=CN") sess = cli:session() cli:shutdown() srv:shutdown() bs:destroy_pair() bs:close() bc:close() -- SSL session reuse, sni not change servername bs, bc = bio.pair() srv = assert(srv_ctx:ssl(bs, true)) cli = assert(cli_ctx:ssl(bc, false)) cli:set('hostname', 'serverB') cli:session(sess) repeat cs, ec = cli:handshake() rs, es = srv:handshake() until (rs and cs) or (rs == nil or cs == nil) assert(rs and cs) peer = cli:peer() if helper.libressl then assert(peer:subject():oneline()=="/CN=serverB/C=CN") else assert(peer:subject():oneline()=="/CN=server/C=CN") end cli:shutdown() srv:shutdown() bs:destroy_pair() bs:close() bc:close() -- SSL session not reuse, sni change servername srv_ctx = create_srv_ctx(true) bs, bc = bio.pair() srv = assert(srv_ctx:ssl(bs, true)) cli = assert(cli_ctx:ssl(bc, false)) cli:set('hostname', 'serverB') repeat cs, ec = cli:handshake() rs, es = srv:handshake() until (rs and cs) or (rs == nil or cs == nil) assert(rs and cs) peer = cli:peer() assert(peer:subject():oneline() == "/CN=serverB/C=CN") if not helper.libressl then rc, ec = cli:renegotiate() rs, es = srv:renegotiate_abbreviated() cli:renegotiate_pending() assert(cli:read() == false) assert(srv:read() == false) repeat cs, ec = cli:handshake() rs, es = srv:handshake() until (rs and cs) or (rs == nil or cs == nil) assert(rs and cs) end cli:write(msg) srv:write(srv:read()) got = cli:read() assert(got == msg) peer = cli:peer() cli:shutdown('read') cli:shutdown('write') cli:shutdown('quiet') cli:shutdown('noquiet') cli:shutdown(true) cli:shutdown(false) local oneline = peer:subject():oneline() assert(oneline == "/CN=server/C=CN" or oneline == "/CN=serverB/C=CN") bs:destroy_pair() bs:close() bc:close() local cert, pkey = helper.sign({{CN = "server"}, {C = "CN"}}) bs, bc = bio.pair() srv = assert(srv_ctx:ssl(bs)) srv:set_accept_state() cli = assert(cli_ctx:ssl(bc)) cli:use(cert, pkey) cli:set_connect_state() cli:set('hostname', 'serverB') repeat cs, ec = cli:handshake() rs, es = srv:handshake() srv:want() until (rs and cs) or (rs == nil or cs == nil) assert(rs and cs) cli:write(msg) srv:write(srv:read()) got = cli:peek() assert(got == msg) got = cli:read() assert(got == msg) peer = cli:peer() if cli.current_compression then cli:current_compression() end assert(peer:subject():oneline() == "/CN=serverB/C=CN") assert(cli:get('hostname') == 'serverB') sess = cli:session() local S = sess:export() S = ssl.session_read(S) assert(S) if sess.has_ticket then assert(type(sess:has_ticket()) == 'boolean') end if sess.is_resumable then assert(sess:is_resumable()) end assert(sess:peer()) assert(sess:compress_id()) assert(sess:timeout()) assert(sess:timeout(500)) assert(sess:time()) assert(sess:time(50)) assert(sess:id()) local id = assert(sess:id()) sess:id(id) cli:getpeerverification() cli:get('version') cli:get('certificate') cli:get('client_CA_list') cli:set('fd', cli:get('fd')) cli:set('rfd', cli:get('rfd')) cli:set('wfd', cli:get('wfd')) cli:set('client_CA', ca.cacert) cli:set('read_ahead', cli:get('read_ahead')) cli:get('shared_ciphers') cli:set('cipher_list', cli:get('cipher_list')) cli:get('verify_mode') cli:set('verify_depth', cli:get('verify_depth')) cli:set('purpose', 1) cli:set('trust', 1) cli:get('state_string') cli:get('state_string_long') cli:get('rstate_string') cli:get('rstate_string_long') cli:get('iversion') cli:get('version') cli:get('default_timeout') cli:set('verify_result', cli:get('verify_result')) cli:get('state') cli:get('state_string') cli:get('side') cli:set('hostname', cli:get('hostname')) cli:cache_hit() cli:session_reused() local ret, msg = cli:dup() assert(ret==nil) assert(msg:match("^invalid state:")) local ctx = cli:ctx() assert(ctx) assert(cli:ctx(ctx)) srv_ctx:session(sess, true) srv_ctx:session(sess, false) srv_ctx:session(sess:id(), false) srv_ctx:quiet_shutdown(1) assert(srv_ctx:quiet_shutdown()==1) srv_ctx:verify_locations('certs/ca1-cert.pem') assert(srv_ctx:cert_store()) assert(srv_ctx:verify_depth(9)) assert(srv_ctx:verify_mode()) srv_ctx:verify_mode(ssl.peer, function() return true end) if srv_ctx.num_tickets then srv_ctx:num_tickets(assert(srv_ctx:num_tickets())) end local cache_mode = { 'client', 'server', 'no_auto_clear', 'no_internal_lookup', 'no_internal_store' } local old = srv_ctx:session_cache_mode() srv_ctx:session_cache_mode(0) t = srv_ctx:session_cache_mode() assert(#t==1 and t[1]=='off') srv_ctx:session_cache_mode('client', 'no_internal_lookup') t = srv_ctx:session_cache_mode() assert(#t==2 and t[1]=='client' and t[2]=='no_internal_lookup') srv_ctx:session_cache_mode('server', 'no_internal_store') t = srv_ctx:session_cache_mode() assert(#t==2 and t[1]=='server' and t[2]=='no_internal_store') srv_ctx:session_cache_mode(unpack(cache_mode)) t = srv_ctx:session_cache_mode() assert(#t==3 and t[1]=='no_auto_clear' and t[2]=='both' and t[3]=='no_internal') srv_ctx:session_cache_mode(unpack(old)) lu.assertEquals(old, srv_ctx:session_cache_mode()) if openssl.engine then local eng = openssl.engine('openssl') eng:load_ssl_client_cert(cli) end cli:clear() cli:shutdown() bs:destroy_pair() bs:close() bc:close() sess = ssl.session_new() sess:id(id) bs, bc = bio.pair() srv = assert(srv_ctx:ssl(bs)) srv:set_accept_state() cli = assert(cli_ctx:ssl(bc)) cli:use(cert, pkey) cli:set_connect_state() cli:set('hostname', 'serverB') repeat cs, ec = cli:handshake() rs, es = srv:handshake() srv:want() until (rs and cs) or (rs == nil or cs == nil) cli:clear() cli:shutdown() bs:destroy_pair() bs:close() bc:close() end