Mercurial > prosody-hg
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 |
| parents | 2bd91d4a0fcf |
| children | 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 { |
