1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
-- Prosody IM
-- Copyright (C) 2021 Prosody folks
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
--[[
This file provides a shim abstraction over LuaSec, consolidating some code
which was previously spread between net.server backends, portmanager and
certmanager.
The goal is to provide a more or less well-defined API on top of LuaSec which
abstracts away some of the things which are not needed and simplifies usage of
commonly used things (such as SNI contexts). Eventually, network backends
which do not rely on LuaSocket+LuaSec should be able to provide *this* API
instead of having to mimic LuaSec.
]]
local ssl = require "ssl";
local ssl_newcontext = ssl.newcontext;
local ssl_context = ssl.context or require "ssl.context";
local io_open = io.open;
local context_api = {};
local context_mt = {__index = context_api};
function context_api:set_sni_host(host, cert, key)
local ctx, err = self._builder:clone():apply({
certificate = cert,
key = key,
}):build();
if not ctx then
return false, err
end
self._sni_contexts[host] = ctx._inner
return true, nil
end
function context_api:remove_sni_host(host)
self._sni_contexts[host] = nil
end
function context_api:wrap(sock)
local ok, conn, err = pcall(ssl.wrap, sock, self._inner);
if not ok then
return nil, err
end
return conn, nil
end
local function new_context(cfg, builder)
-- LuaSec expects dhparam to be a callback that takes two arguments.
-- We ignore those because it is mostly used for having a separate
-- set of params for EXPORT ciphers, which we don't have by default.
if type(cfg.dhparam) == "string" and cfg.dhparam:sub(1, 10) == "-----BEGIN" then
local dhparam = cfg.dhparam;
cfg.dhparam = function() return dhparam; end
elseif type(cfg.dhparam) == "string" then
local f, err = io_open(cfg.dhparam);
if not f then return nil, "Could not open DH parameters: "..err end
local dhparam = f:read("*a");
f:close();
cfg.dhparam = function() return dhparam; end
end
local inner, err = ssl_newcontext(cfg);
if not inner then
return nil, err
end
-- COMPAT Older LuaSec ignores the cipher list from the config, so we have to take care
-- of it ourselves (W/A for #x)
if inner and cfg.ciphers then
local success;
success, err = ssl_context.setcipher(inner, cfg.ciphers);
if not success then
return nil, err
end
end
return setmetatable({
_inner = inner,
_builder = builder,
_sni_contexts = {},
}, context_mt), nil
end
-- Feature detection / guessing
local function test_option(option)
return not not ssl_newcontext({mode="server",protocol="sslv23",options={ option }});
end
local luasec_major, luasec_minor = ssl._VERSION:match("^(%d+)%.(%d+)");
local luasec_version = tonumber(luasec_major) * 100 + tonumber(luasec_minor);
local luasec_has = ssl.config or {
algorithms = {
ec = luasec_version >= 5;
};
capabilities = {
curves_list = luasec_version >= 7;
};
options = {
cipher_server_preference = test_option("cipher_server_preference");
no_ticket = test_option("no_ticket");
no_compression = test_option("no_compression");
single_dh_use = test_option("single_dh_use");
single_ecdh_use = test_option("single_ecdh_use");
no_renegotiation = test_option("no_renegotiation");
};
};
return {
features = luasec_has;
new_context = new_context,
load_certificate = ssl.loadcertificate;
};
|