Comparison

core/certmanager.lua @ 11532:c0c859425c22

core.certmanager: Build an index over certificates
author Kim Alvefur <zash@zash.se>
date Sat, 10 Apr 2021 14:45:40 +0200
parent 11531:2bd91d4a0fcf
child 11533:f97592336399
comparison
equal deleted inserted replaced
11531:2bd91d4a0fcf 11532:c0c859425c22
22 local ssl_context = ssl.context or softreq"ssl.context"; 22 local ssl_context = ssl.context or softreq"ssl.context";
23 local ssl_newcontext = ssl.newcontext; 23 local ssl_newcontext = ssl.newcontext;
24 local new_config = require"util.sslconfig".new; 24 local new_config = require"util.sslconfig".new;
25 local stat = require "lfs".attributes; 25 local stat = require "lfs".attributes;
26 26
27 local x509 = require "util.x509";
28 local lfs = require "lfs";
29
27 local tonumber, tostring = tonumber, tostring; 30 local tonumber, tostring = tonumber, tostring;
28 local pairs = pairs; 31 local pairs = pairs;
29 local t_remove = table.remove; 32 local t_remove = table.remove;
30 local type = type; 33 local type = type;
31 local io_open = io.open; 34 local io_open = io.open;
32 local select = select; 35 local select = select;
36 local now = os.time;
37 local next = next;
33 38
34 local prosody = prosody; 39 local prosody = prosody;
35 local resolve_path = require"util.paths".resolve_relative_path; 40 local resolve_path = require"util.paths".resolve_relative_path;
36 local config_path = prosody.paths.config or "."; 41 local config_path = prosody.paths.config or ".";
37 42
90 end 95 end
91 end 96 end
92 log("debug", "No certificate/key found for %s", name); 97 log("debug", "No certificate/key found for %s", name);
93 end 98 end
94 99
100 local function index_certs(dir, files_by_name, depth_limit)
101 files_by_name = files_by_name or {};
102 depth_limit = depth_limit or 3;
103 if depth_limit <= 0 then return files_by_name; end
104
105 for file in lfs.dir(dir) do
106 local full = dir.."/"..file
107 if lfs.attributes(full, "mode") == "directory" then
108 if file:sub(1,1) ~= "." then
109 index_certs(full, files_by_name, depth_limit-1);
110 end
111 -- TODO support more filename patterns?
112 elseif full:match("%.crt$") or full:match("/fullchain%.pem$") then
113 local f = io_open(full);
114 if f then
115 -- TODO look for chained certificates
116 local firstline = f:read();
117 if firstline == "-----BEGIN CERTIFICATE-----" then
118 f:seek("set")
119 local cert = ssl.loadcertificate(f:read("*a"))
120 -- TODO if more than one cert is found for a name, the most recently
121 -- issued one should be used.
122 -- for now, just filter out expired certs
123 -- TODO also check if there's a corresponding key
124 if cert:validat(now()) then
125 local names = x509.get_identities(cert);
126 log("debug", "Found certificate %s with identities %q", full, names);
127 for name, services in pairs(names) do
128 -- TODO check services
129 if files_by_name[name] then
130 files_by_name[name][full] = services;
131 else
132 files_by_name[name] = { [full] = services; };
133 end
134 end
135 end
136 end
137 f:close();
138 end
139 end
140 end
141 log("debug", "Certificate index: %q", files_by_name);
142 -- | hostname | filename | service |
143 return files_by_name;
144 end
145
146 local cert_index;
147
95 local function find_host_cert(host) 148 local function find_host_cert(host)
96 if not host then return nil; end 149 if not host then return nil; end
150 if not cert_index then
151 cert_index = index_certs(global_certificates);
152 end
153 local certs = cert_index[host];
154 if certs then
155 local cert_filename, services = next(certs);
156 if services["*"] then
157 log("debug", "Using cert %q from index", cert_filename);
158 return find_cert(cert_filename, host);
159 end
160 end
161
97 return find_cert(configmanager.get(host, "certificate"), host) or find_host_cert(host:match("%.(.+)$")); 162 return find_cert(configmanager.get(host, "certificate"), host) or find_host_cert(host:match("%.(.+)$"));
98 end 163 end
99 164
100 local function find_service_cert(service, port) 165 local function find_service_cert(service, port)
166 if not cert_index then
167 cert_index = index_certs(global_certificates);
168 end
169 for _, certs in pairs(cert_index) do
170 for cert_filename, services in pairs(certs) do
171 if services[service] or services["*"] then
172 log("debug", "Using cert %q from index", cert_filename);
173 return find_cert(cert_filename, service);
174 end
175 end
176 end
101 local cert_config = configmanager.get("*", service.."_certificate"); 177 local cert_config = configmanager.get("*", service.."_certificate");
102 if type(cert_config) == "table" then 178 if type(cert_config) == "table" then
103 cert_config = cert_config[port] or cert_config.default; 179 cert_config = cert_config[port] or cert_config.default;
104 end 180 end
105 return find_cert(cert_config, service); 181 return find_cert(cert_config, service);
158 local function create_context(host, mode, ...) 234 local function create_context(host, mode, ...)
159 local cfg = new_config(); 235 local cfg = new_config();
160 cfg:apply(core_defaults); 236 cfg:apply(core_defaults);
161 local service_name, port = host:match("^(%S+) port (%d+)$"); 237 local service_name, port = host:match("^(%S+) port (%d+)$");
162 if service_name then 238 if service_name then
239 log("debug", "Automatically locating certs for service %s on port %s", service_name, port);
163 cfg:apply(find_service_cert(service_name, tonumber(port))); 240 cfg:apply(find_service_cert(service_name, tonumber(port)));
164 else 241 else
242 log("debug", "Automatically locating certs for host %s", host);
165 cfg:apply(find_host_cert(host)); 243 cfg:apply(find_host_cert(host));
166 end 244 end
167 cfg:apply({ 245 cfg:apply({
168 mode = mode, 246 mode = mode,
169 -- We can't read the password interactively when daemonized 247 -- We can't read the password interactively when daemonized
250 global_ssl_config = configmanager.get("*", "ssl"); 328 global_ssl_config = configmanager.get("*", "ssl");
251 global_certificates = configmanager.get("*", "certificates") or "certs"; 329 global_certificates = configmanager.get("*", "certificates") or "certs";
252 if luasec_has.options.no_compression then 330 if luasec_has.options.no_compression then
253 core_defaults.options.no_compression = configmanager.get("*", "ssl_compression") ~= true; 331 core_defaults.options.no_compression = configmanager.get("*", "ssl_compression") ~= true;
254 end 332 end
333 cert_index = index_certs(global_certificates);
255 end 334 end
256 335
257 prosody.events.add_handler("config-reloaded", reload_ssl_config); 336 prosody.events.add_handler("config-reloaded", reload_ssl_config);
258 337
259 return { 338 return {