Software /
code /
prosody
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 { |