Line data Source code
1 : #include "certificate_manager.hpp"
2 : #include "client_verify.hpp"
3 : #include "../../util/ascii.hpp"
4 : #include "../../util/logger.hpp"
5 : #include <openssl/err.h>
6 : #include <openssl/ssl.h>
7 : #include <openssl/x509.h>
8 : #include <limits>
9 : #include <string_view>
10 : #include <unordered_map>
11 :
12 : namespace thinger::asio {
13 :
14 : namespace detail {
15 :
16 : // Values by host name: exact names, and wildcards "*.domain" for one more label (not empty)
17 : // in front of domain, stored lowercase. A name is looked up case-insensitively, a trailing
18 : // dot ignored, without a copy, in two probes at most: the exact name, then the wildcard of
19 : // its domain (the name less its first label), so an exact name wins and the cost does not
20 : // grow with the names stored. Not synchronized: the manager's mutex guards it
21 : template<typename T>
22 : class host_table {
23 : // Case-insensitive hash (FNV-1a of the lowercase name) and equality, transparent so a
24 : // string_view is looked up as it is
25 : struct ihash {
26 : using is_transparent = void;
27 31844 : size_t operator()(std::string_view name) const noexcept {
28 31844 : uint64_t hash = 14695981039346656037ULL;
29 461925 : for (char c : name) hash = (hash ^ static_cast<unsigned char>(util::ascii::to_lower(c))) * 1099511628211ULL;
30 31844 : return static_cast<size_t>(hash);
31 : }
32 : };
33 : struct iequal {
34 : using is_transparent = void;
35 6657 : bool operator()(std::string_view a, std::string_view b) const noexcept { return util::ascii::iequals(a, b); }
36 : };
37 : using map = std::unordered_map<std::string, T, ihash, iequal>;
38 :
39 : static constexpr std::string_view WILDCARD = "*.";
40 :
41 : map exact_;
42 : // The wildcards, by their domain (the name less "*.")
43 : map wildcards_;
44 :
45 : // The map of a registered name, and its key there
46 6684 : std::pair<map*, std::string_view> place(std::string_view name) {
47 6684 : if (!name.starts_with(WILDCARD)) return {&exact_, name};
48 478 : return {&wildcards_, name.substr(WILDCARD.size())};
49 : }
50 :
51 : public:
52 : // The entry a name resolves to; its key is valid while the table is not changed
53 : struct match {
54 : const T* value = nullptr; // null: no entry
55 : bool wildcard = false;
56 : std::string_view key; // the exact name, or the domain of the wildcard
57 : };
58 :
59 : // `name` is lowercase, and a host name or a wildcard (see valid_host_name())
60 3898 : void set(std::string_view name, T value) {
61 3898 : auto [entries, key] = place(name);
62 3898 : if (auto it = entries->find(key); it != entries->end()) {
63 152 : it->second = std::move(value);
64 : } else {
65 11238 : entries->emplace(std::string(key), std::move(value));
66 : }
67 3898 : }
68 :
69 : // Remove the entry registered as `name` ("*.domain" for a wildcard): false if none
70 2786 : bool erase(std::string_view name) {
71 2786 : auto [entries, key] = place(name);
72 2786 : auto it = entries->find(key);
73 2786 : if (it == entries->end()) return false;
74 2759 : entries->erase(it);
75 2759 : return true;
76 : }
77 :
78 15044 : match find(std::string_view name) const {
79 15044 : if (name.ends_with('.')) name.remove_suffix(1);
80 15044 : if (auto it = exact_.find(name); it != exact_.end()) return {&it->second, false, it->first};
81 12053 : auto dot = name.find('.');
82 12053 : if (dot == std::string_view::npos || dot == 0) return {};
83 1827 : if (auto it = wildcards_.find(name.substr(dot + 1)); it != wildcards_.end()) {
84 174 : return {&it->second, true, it->first};
85 : }
86 1653 : return {};
87 : }
88 :
89 : // The names registered, "*.domain" for the wildcards
90 63 : std::set<std::string> names() const {
91 63 : std::set<std::string> result;
92 390 : for (const auto& entry : exact_) result.insert(entry.first);
93 87 : for (const auto& entry : wildcards_) result.insert(std::string(WILDCARD) + entry.first);
94 63 : return result;
95 0 : }
96 :
97 : template<typename F>
98 3786 : void for_each_value(F&& f) const {
99 4140 : for (const auto& entry : exact_) f(entry.second);
100 3858 : for (const auto& entry : wildcards_) f(entry.second);
101 3786 : }
102 : };
103 :
104 : }
105 :
106 : namespace {
107 :
108 : // Whether a context has some TLS 1.3 cipher suite (OpenSSL 3 drops the names it does not
109 : // know without failing)
110 7394 : bool has_tls13_suites(SSL_CTX* handle) {
111 7394 : auto* ciphers = SSL_CTX_get_ciphers(handle);
112 7394 : for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) {
113 7394 : if (std::string_view(SSL_CIPHER_get_version(sk_SSL_CIPHER_value(ciphers, i))) == "TLSv1.3") return true;
114 : }
115 0 : return false;
116 : }
117 :
118 14824 : int protocol_version(tls_version version) {
119 14824 : switch (version) {
120 39 : case tls_version::tls1_0: return TLS1_VERSION;
121 0 : case tls_version::tls1_1: return TLS1_1_VERSION;
122 7334 : case tls_version::tls1_2: return TLS1_2_VERSION;
123 7451 : case tls_version::tls1_3: return TLS1_3_VERSION;
124 : }
125 0 : return 0;
126 : }
127 :
128 : }
129 :
130 : namespace {
131 :
132 : // Set the options on a context, the security level first (it decides which ciphers and
133 : // certificates are allowed): false (logged) at the first one OpenSSL refuses
134 7415 : bool set_tls_options(SSL_CTX* handle, const tls_options& options) {
135 7415 : if (options.security_level) {
136 60 : if (*options.security_level < 0 || *options.security_level > 5) {
137 3 : LOG_ERROR("invalid OpenSSL security level: {}", *options.security_level);
138 3 : return false;
139 : }
140 57 : SSL_CTX_set_security_level(handle, *options.security_level);
141 : }
142 14824 : if (SSL_CTX_set_min_proto_version(handle, protocol_version(options.min_version)) != 1 ||
143 7412 : SSL_CTX_set_max_proto_version(handle, protocol_version(options.max_version)) != 1) {
144 0 : LOG_ERROR("invalid TLS protocol versions");
145 0 : return false;
146 : }
147 7412 : if (!options.ciphers.empty() && SSL_CTX_set_cipher_list(handle, options.ciphers.c_str()) != 1) {
148 15 : LOG_ERROR("invalid TLS cipher list: {}", options.ciphers);
149 15 : return false;
150 : }
151 14794 : if (!options.ciphersuites.empty() &&
152 7397 : (SSL_CTX_set_ciphersuites(handle, options.ciphersuites.c_str()) != 1 || !has_tls13_suites(handle))) {
153 3 : LOG_ERROR("invalid TLS 1.3 cipher suites: {}", options.ciphersuites);
154 3 : return false;
155 : }
156 7394 : if (!options.groups.empty() && SSL_CTX_set1_groups_list(handle, options.groups.c_str()) != 1) {
157 3 : LOG_ERROR("invalid TLS groups: {}", options.groups);
158 3 : return false;
159 : }
160 7391 : if (options.prefer_server_ciphers) {
161 45 : SSL_CTX_set_options(handle, SSL_OP_CIPHER_SERVER_PREFERENCE);
162 : } else {
163 7346 : SSL_CTX_clear_options(handle, SSL_OP_CIPHER_SERVER_PREFERENCE);
164 : }
165 7391 : return true;
166 : }
167 :
168 : // The TLS 1.2 (and older) ciphers of a handshake, in OpenSSL's format (empty if none)
169 626 : std::string tls12_cipher_list(const SSL* ssl) {
170 626 : std::string list;
171 626 : auto* ciphers = SSL_get_ciphers(ssl);
172 6719 : for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) {
173 6093 : const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i);
174 6093 : if (std::string_view(SSL_CIPHER_get_version(cipher)) == "TLSv1.3") continue;
175 4215 : if (!list.empty()) list += ':';
176 4215 : list += SSL_CIPHER_get_name(cipher);
177 : }
178 626 : return list;
179 0 : }
180 :
181 : // Whether OpenSSL takes the options, tried on a context of its own
182 3757 : bool valid_tls_options(const tls_options& options) {
183 3757 : boost::asio::ssl::context test(boost::asio::ssl::context::tls_server);
184 7514 : return set_tls_options(test.native_handle(), options);
185 3757 : }
186 :
187 : // The session id context of every context of the manager
188 : constexpr std::string_view SESSION_ID_CONTEXT = "thinger-http";
189 :
190 : // The argument of the SNI callback of a context: the manager it goes to
191 : using manager_ref = std::weak_ptr<certificate_manager>;
192 :
193 : // Where a context keeps its manager_ref (on the heap), freed with the context
194 10631 : int manager_index() {
195 23616 : static const int index = SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr,
196 : [](void* /*parent*/, void* ref, CRYPTO_EX_DATA* /*data*/, int /*index*/, long /*argl*/, void* /*argp*/) {
197 : delete static_cast<manager_ref*>(ref);
198 : });
199 10631 : return index;
200 : }
201 :
202 : // Whether two references are to the same manager (or both to none)
203 3186 : bool same_manager(const manager_ref& a, const manager_ref& b) {
204 3186 : return !a.owner_before(b) && !b.owner_before(a);
205 : }
206 :
207 : // The TLS 1.3 tickets OpenSSL sends after a handshake on a new context
208 : constexpr size_t DEFAULT_TICKETS = 2;
209 :
210 : // Write a session cache on a context, every part of it (re-enabled after "off", a context
211 : // is as OpenSSL creates it), only where it differs: other listeners may be handshaking on
212 : // it. Disabled needs the three: without a cache, TLS 1.3 tickets would still resume
213 4650 : void write_session_cache(SSL_CTX* handle, const session_cache_options& options) {
214 4650 : long mode = options.enabled ? SSL_SESS_CACHE_SERVER : SSL_SESS_CACHE_OFF;
215 4650 : if (SSL_CTX_get_session_cache_mode(handle) != mode) SSL_CTX_set_session_cache_mode(handle, mode);
216 4650 : if (options.enabled && SSL_CTX_sess_get_cache_size(handle) != static_cast<long>(options.size)) {
217 54 : SSL_CTX_sess_set_cache_size(handle, static_cast<long>(options.size));
218 : }
219 4650 : bool no_ticket = (SSL_CTX_get_options(handle) & SSL_OP_NO_TICKET) != 0;
220 4650 : if (options.enabled && no_ticket) SSL_CTX_clear_options(handle, SSL_OP_NO_TICKET);
221 4650 : if (!options.enabled && !no_ticket) SSL_CTX_set_options(handle, SSL_OP_NO_TICKET);
222 4650 : size_t tickets = options.enabled ? DEFAULT_TICKETS : 0;
223 4650 : if (SSL_CTX_get_num_tickets(handle) != tickets) SSL_CTX_set_num_tickets(handle, tickets);
224 4650 : }
225 :
226 : // The invalid_argument of the setters (the reason is logged where it is found)
227 225 : boost::system::error_code invalid_argument() {
228 225 : return boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
229 : }
230 :
231 : // Where an SSL keeps the record of its handshake (see tls_handshake_record), from its ClientHello
232 : // (see client_hello_callback()) until the connection takes it
233 30150 : int handshake_index() {
234 43935 : static const int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr,
235 : [](void* /*parent*/, void* record, CRYPTO_EX_DATA* /*data*/, int /*index*/, long /*argl*/, void* /*argp*/) {
236 : delete static_cast<tls_handshake_record*>(record);
237 : });
238 30150 : return index;
239 : }
240 :
241 20304 : tls_handshake_record* record_of(const SSL* ssl) {
242 20304 : return static_cast<tls_handshake_record*>(SSL_get_ex_data(ssl, handshake_index()));
243 : }
244 :
245 : // The first host name of the server_name extension of a ClientHello (RFC 6066, 3), if any
246 5271 : std::optional<std::string> client_hello_server_name(SSL* ssl) {
247 5271 : const unsigned char* data = nullptr;
248 5271 : size_t size = 0;
249 5271 : if (SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &data, &size) != 1 || size < 2) return std::nullopt;
250 3486 : if (((size_t(data[0]) << 8) | data[1]) != size - 2) return std::nullopt;
251 3486 : for (size_t i = 2; i + 3 <= size;) {
252 3486 : size_t length = (size_t(data[i + 1]) << 8) | data[i + 2];
253 3486 : if (i + 3 + length > size) return std::nullopt;
254 10458 : if (data[i] == TLSEXT_NAMETYPE_host_name) return std::string(reinterpret_cast<const char*>(data) + i + 3, length);
255 0 : i += 3 + length;
256 : }
257 0 : return std::nullopt;
258 : }
259 :
260 : // Keep the host name the client asks for, in the record of the handshake: the SNI callback
261 : // gets the one of the session instead when a TLS 1.2 session is resumed (see
262 : // SSL_get_servername()), and the mode of the host asked for must apply (see
263 : // certificate_manager::set_client_verify())
264 5271 : int client_hello_callback(SSL* ssl, int* alert, void* /*arg*/) {
265 : try {
266 5271 : auto name = client_hello_server_name(ssl);
267 : // Called again after a HelloRetryRequest: the same record
268 5271 : auto* record = record_of(ssl);
269 5271 : if (!record) {
270 5271 : auto fresh = std::make_unique<tls_handshake_record>();
271 5271 : if (SSL_set_ex_data(ssl, handshake_index(), fresh.get()) != 1) throw std::bad_alloc();
272 5271 : record = fresh.release();
273 5271 : }
274 5271 : record->has_name = name.has_value();
275 5271 : record->name = std::move(name).value_or(std::string());
276 5271 : util::ascii::to_lower(record->name);
277 5271 : if (record->name.ends_with('.')) record->name.pop_back();
278 5271 : return SSL_CLIENT_HELLO_SUCCESS;
279 5271 : } catch (const std::exception&) {
280 0 : if (alert) *alert = SSL_AD_INTERNAL_ERROR;
281 0 : return SSL_CLIENT_HELLO_ERROR;
282 0 : }
283 : }
284 :
285 : // The host name the client of a handshake asked for (none if none)
286 5229 : const char* requested_name(const SSL* ssl) {
287 5229 : if (const auto* record = record_of(ssl); record && record->has_name) return record->name.c_str();
288 1764 : return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
289 : }
290 :
291 : // Password callback of the keys: none, so an encrypted key fails
292 6 : int no_password(char* /*buffer*/, int /*size*/, int /*rwflag*/, void* /*user_data*/) {
293 6 : return -1;
294 : }
295 :
296 : // Whether an error loading a key says it is not the key of the certificate
297 27 : bool key_mismatch(const boost::system::error_code& ec) {
298 27 : auto error = static_cast<unsigned long>(ec.value());
299 42 : return ec.category() == boost::asio::error::get_ssl_category() && ERR_GET_LIB(error) == ERR_LIB_X509 &&
300 42 : (ERR_GET_REASON(error) == X509_R_KEY_VALUES_MISMATCH || ERR_GET_REASON(error) == X509_R_KEY_TYPE_MISMATCH);
301 : }
302 :
303 : // Letters, digits, hyphens and underscores
304 78494 : bool host_label_char(char c) {
305 78494 : c = util::ascii::to_lower(c);
306 78494 : return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_';
307 : }
308 :
309 : // A host name, or a wildcard (*.domain): labels of 1 to 63 characters, 253 at most
310 5222 : bool valid_host_name(std::string_view hostname) {
311 5222 : if (hostname.starts_with("*.")) hostname.remove_prefix(2);
312 5222 : if (hostname.empty() || hostname.size() > 253) return false;
313 5180 : size_t label = 0;
314 91839 : for (char c : hostname) {
315 86764 : if (c != '.') {
316 78494 : if (!host_label_char(c) || ++label > 63) return false;
317 8270 : } else if (std::exchange(label, 0) == 0) {
318 30 : return false;
319 : }
320 : }
321 5075 : return label > 0;
322 : }
323 :
324 : }
325 :
326 3384 : certificate_error use_certificate(boost::asio::ssl::context& context, std::string_view certificate_chain,
327 : std::string_view private_key) {
328 3384 : boost::system::error_code ec;
329 3384 : context.use_certificate_chain(boost::asio::buffer(certificate_chain.data(), certificate_chain.size()), ec);
330 3384 : if (ec) {
331 24 : LOG_ERROR("cannot read the certificate chain: {}", ec.message());
332 24 : return certificate_error::invalid_certificate;
333 : }
334 3360 : SSL_CTX_set_default_passwd_cb(context.native_handle(), no_password);
335 3360 : context.use_private_key(boost::asio::buffer(private_key.data(), private_key.size()),
336 : boost::asio::ssl::context::pem, ec);
337 3360 : if (ec && key_mismatch(ec)) {
338 15 : LOG_ERROR("the private key is not the one of the certificate");
339 15 : return certificate_error::key_mismatch;
340 : }
341 3345 : if (ec) {
342 12 : LOG_ERROR("cannot read the private key: {}", ec.message());
343 12 : return certificate_error::invalid_private_key;
344 : }
345 : // A key of another type than the certificate's is taken without error, for another
346 : // certificate: the key in use has no certificate then
347 3333 : if (SSL_CTX_check_private_key(context.native_handle()) != 1) {
348 6 : ERR_clear_error();
349 6 : LOG_ERROR("the private key is not the one of the certificate");
350 6 : return certificate_error::key_mismatch;
351 : }
352 3327 : return certificate_error::none;
353 : }
354 :
355 3661 : bool apply_tls_options(boost::asio::ssl::context& context, const tls_options& options) {
356 : // Tried first, so a context is never left half set
357 3661 : return valid_tls_options(options) && set_tls_options(context.native_handle(), options);
358 : }
359 :
360 2539 : certificate_manager::certificate_manager()
361 2539 : : ssl_contexts_(std::make_unique<detail::host_table<std::shared_ptr<boost::asio::ssl::context>>>())
362 5078 : , client_verify_(std::make_unique<detail::host_table<client_verify>>()) {
363 2539 : }
364 :
365 2539 : certificate_manager::~certificate_manager() = default;
366 :
367 6907 : void certificate_manager::prepare_context(boost::asio::ssl::context& context) {
368 6907 : SSL_CTX* handle = context.native_handle();
369 6907 : auto self = weak_from_this();
370 6907 : auto* ref = static_cast<manager_ref*>(SSL_CTX_get_ex_data(handle, manager_index()));
371 : // Prepared again for this manager, it keeps the reference handshakes may be reading
372 6907 : if (!ref || !same_manager(*ref, self)) {
373 3724 : auto* fresh = new manager_ref(std::move(self));
374 3724 : if (SSL_CTX_set_ex_data(handle, manager_index(), fresh) == 1) {
375 3724 : delete ref;
376 3724 : ref = fresh;
377 : } else {
378 0 : delete fresh;
379 0 : LOG_ERROR("cannot keep the certificate manager of a context: its SNI is left as it was");
380 : }
381 : }
382 6907 : SSL_CTX_set_tlsext_servername_callback(handle, sni_callback);
383 6907 : SSL_CTX_set_tlsext_servername_arg(handle, ref);
384 6907 : SSL_CTX_set_client_hello_cb(handle, client_hello_callback, nullptr);
385 6907 : SSL_CTX_set_session_id_context(handle,
386 6907 : reinterpret_cast<const unsigned char*>(SESSION_ID_CONTEXT.data()),
387 6907 : SESSION_ID_CONTEXT.size());
388 6907 : }
389 :
390 3652 : std::shared_ptr<boost::asio::ssl::context> certificate_manager::create_base_ssl_context(const tls_options& options) {
391 3652 : auto context = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tls_server);
392 3652 : context->set_options(boost::asio::ssl::context::single_dh_use);
393 : #ifdef SSL_OP_NO_RENEGOTIATION
394 : // No TLS 1.2 renegotiation (OpenSSL 1.1.1 allows it), as Go's servers: contexts passed in
395 : // ready-made keep their own options
396 3652 : SSL_CTX_set_options(context->native_handle(), SSL_OP_NO_RENEGOTIATION);
397 : #endif
398 3652 : prepare_context(*context);
399 : // Validated when set (see set_tls_options()), so this fails only if OpenSSL changed its mind
400 3652 : if (!apply_tls_options(*context, options)) {
401 0 : LOG_ERROR("cannot set the TLS options of a new context");
402 0 : return nullptr;
403 : }
404 3652 : return context;
405 3652 : }
406 :
407 3195 : std::shared_ptr<boost::asio::ssl::context> certificate_manager::create_ssl_context(
408 : const std::string& cert_chain,
409 : const std::string& private_key) {
410 :
411 3195 : tls_options options;
412 : {
413 3195 : std::lock_guard<std::mutex> lock(mutex_);
414 3195 : options = options_;
415 3195 : }
416 3195 : auto context = create_base_ssl_context(options);
417 3195 : if (!context) return nullptr;
418 3195 : if (use_certificate(*context, cert_chain, private_key) != certificate_error::none) return nullptr;
419 3174 : return context;
420 3195 : }
421 :
422 1210 : boost::system::error_code certificate_manager::set_certificate(const std::string& hostname,
423 : const std::string& certificate,
424 : const std::string& private_key) {
425 : // Checked first: a name refused costs no context
426 1210 : if (!valid_host_name(hostname)) {
427 51 : LOG_ERROR("cannot set the certificate of host '{}': not a host name", hostname);
428 51 : return invalid_argument();
429 : }
430 1159 : return set_certificate(hostname, create_ssl_context(certificate, private_key));
431 : }
432 :
433 1273 : boost::system::error_code certificate_manager::set_certificate(const std::string& hostname,
434 : std::shared_ptr<boost::asio::ssl::context> ssl_context) {
435 1273 : if (!valid_host_name(hostname) || !ssl_context) {
436 57 : LOG_ERROR("cannot set the certificate of host '{}': {}", hostname,
437 : ssl_context ? "not a host name" : "no usable certificate");
438 57 : return invalid_argument();
439 : }
440 1216 : std::string name = hostname;
441 1216 : util::ascii::to_lower(name);
442 1216 : LOG_INFO("Setting SSL certificate for domain: {}", name);
443 : // It may become the default one (default host)
444 1216 : prepare_context(*ssl_context);
445 :
446 1216 : std::lock_guard<std::mutex> lock(mutex_);
447 1216 : apply_session_cache(*ssl_context);
448 1216 : ssl_contexts_->set(name, ssl_context);
449 :
450 1216 : if (util::ascii::iequals(default_host_, name)) {
451 6 : LOG_INFO("Overriding default SSL certificate for domain: {}", name);
452 6 : default_context_ = std::move(ssl_context);
453 : }
454 :
455 1216 : return {};
456 1216 : }
457 :
458 3654 : std::shared_ptr<boost::asio::ssl::context> certificate_manager::get_certificate(const std::string& hostname) const {
459 3654 : std::lock_guard<std::mutex> lock(mutex_);
460 3654 : auto match = ssl_contexts_->find(hostname);
461 7308 : return match.value ? *match.value : nullptr;
462 3654 : }
463 :
464 87 : bool certificate_manager::has_certificate(const std::string& hostname) const {
465 87 : std::lock_guard<std::mutex> lock(mutex_);
466 174 : return ssl_contexts_->find(hostname).value != nullptr;
467 87 : }
468 :
469 287 : bool certificate_manager::remove_certificate(const std::string& hostname) {
470 287 : bool removed = false;
471 : {
472 287 : std::lock_guard<std::mutex> lock(mutex_);
473 287 : removed = ssl_contexts_->erase(hostname);
474 287 : }
475 287 : if (removed) LOG_INFO("Removed SSL certificate: {}", hostname);
476 287 : return removed;
477 : }
478 :
479 2045 : boost::system::error_code certificate_manager::set_default_certificate(
480 : std::shared_ptr<boost::asio::ssl::context> ssl_context) {
481 : // A null one is refused, not taken as "no default": a reload whose context failed to load
482 : // keeps the current certificate, and dropping it is remove_default_certificate()'s job
483 2045 : if (!ssl_context) {
484 6 : LOG_ERROR("cannot set the default certificate: no context (remove_default_certificate() drops it)");
485 6 : return invalid_argument();
486 : }
487 2039 : LOG_INFO("Setting default SSL certificate");
488 2039 : prepare_context(*ssl_context);
489 2039 : std::lock_guard<std::mutex> lock(mutex_);
490 2039 : apply_session_cache(*ssl_context);
491 2039 : default_context_ = std::move(ssl_context);
492 2039 : self_signed_default_ = true;
493 2039 : return {};
494 2039 : }
495 :
496 2036 : boost::system::error_code certificate_manager::set_default_certificate(const std::string& certificate,
497 : const std::string& private_key) {
498 2036 : auto context = create_ssl_context(certificate, private_key);
499 2036 : if (!context) return invalid_argument();
500 2024 : return set_default_certificate(std::move(context));
501 2036 : }
502 :
503 24 : void certificate_manager::remove_default_certificate() {
504 24 : LOG_INFO("Removing the default SSL certificate");
505 24 : std::lock_guard<std::mutex> lock(mutex_);
506 24 : default_context_.reset();
507 24 : self_signed_default_ = false;
508 24 : }
509 :
510 4014 : std::shared_ptr<boost::asio::ssl::context> certificate_manager::get_default_certificate() const {
511 4014 : std::lock_guard<std::mutex> lock(mutex_);
512 :
513 : // If no default certificate is set, generate a self-signed one
514 4014 : if (!default_context_ && self_signed_default_) {
515 457 : LOG_WARNING("No default SSL certificate configured, generating self-signed certificate for development use");
516 457 : const_cast<certificate_manager*>(this)->generate_self_signed_certificate();
517 : }
518 :
519 8028 : return default_context_;
520 4014 : }
521 :
522 9 : void certificate_manager::set_default_host(const std::string& host) {
523 9 : std::lock_guard<std::mutex> lock(mutex_);
524 9 : default_host_ = host;
525 9 : }
526 :
527 6 : std::string certificate_manager::get_default_host() const {
528 6 : std::lock_guard<std::mutex> lock(mutex_);
529 12 : return default_host_;
530 6 : }
531 :
532 63 : std::set<std::string> certificate_manager::get_registered_hosts() const {
533 63 : std::lock_guard<std::mutex> lock(mutex_);
534 126 : return ssl_contexts_->names();
535 63 : }
536 :
537 2739 : boost::system::error_code certificate_manager::set_client_verify(const std::string& hostname, client_verify mode) {
538 2739 : if (!valid_host_name(hostname)) {
539 57 : LOG_ERROR("invalid host name for client certificates: {}", hostname);
540 57 : return invalid_argument();
541 : }
542 2682 : std::string name = hostname;
543 2682 : util::ascii::to_lower(name);
544 2682 : LOG_INFO("Client certificates of {}: {}", name, static_cast<int>(mode));
545 2682 : std::lock_guard<std::mutex> lock(mutex_);
546 2682 : client_verify_->set(name, mode);
547 2682 : return {};
548 2682 : }
549 :
550 2499 : bool certificate_manager::remove_client_verify(const std::string& hostname) {
551 2499 : std::lock_guard<std::mutex> lock(mutex_);
552 4998 : return client_verify_->erase(hostname);
553 2499 : }
554 :
555 5973 : std::optional<client_verify> certificate_manager::get_client_verify(const std::string& hostname) const {
556 5973 : std::lock_guard<std::mutex> lock(mutex_);
557 5973 : auto match = client_verify_->find(hostname);
558 5973 : if (!match.value) return std::nullopt;
559 2586 : return *match.value;
560 5973 : }
561 :
562 5229 : void certificate_manager::apply_client_verify(SSL* ssl, const char* hostname) const {
563 8694 : auto mode = hostname ? get_client_verify(hostname) : std::nullopt;
564 5229 : const auto* policy = client_verify_policy::of(ssl);
565 5229 : if (auto* record = record_of(ssl)) {
566 5229 : record->listener_mode = policy ? policy->options().mode : client_verify::none;
567 5229 : record->mode = mode.value_or(record->listener_mode);
568 5229 : record->manager = weak_from_this();
569 : }
570 5229 : if (!policy && !mode) return;
571 336 : if (!policy) policy = &client_verify_policy::deferred();
572 336 : policy->set_mode(ssl, mode.value_or(policy->options().mode));
573 : }
574 :
575 1856 : bool certificate_manager::misdirected(const tls_handshake_record& handshake, std::string_view host) const {
576 1856 : std::lock_guard<std::mutex> lock(mutex_);
577 1856 : auto mode = client_verify_->find(host);
578 1856 : if ((mode.value ? *mode.value : handshake.listener_mode) != handshake.mode) return true;
579 : // Entries compared by their node: the same one for the same registered name, whatever
580 : // certificate it holds now (none for both: the default)
581 1790 : return handshake.has_name && ssl_contexts_->find(host).value != ssl_contexts_->find(handshake.name).value;
582 1856 : }
583 :
584 3932 : bool tls_handshake_record::misdirected(std::string_view host) const {
585 3932 : if (host.ends_with('.')) host.remove_suffix(1);
586 : // Served whatever changed since the handshake, without a lookup
587 3932 : if (has_name && util::ascii::iequals(host, name)) return false;
588 : // A manager gone since it chose leaves no table to compare: misdirected (fails closed)
589 1868 : static const std::weak_ptr<const certificate_manager> none;
590 1868 : bool chosen = manager.owner_before(none) || none.owner_before(manager);
591 1868 : auto owner = manager.lock();
592 1868 : if (owner ? !owner->misdirected(*this, host) : !chosen) return false;
593 : // At debug level: any client can send it
594 93 : LOG_DEBUG("misdirected request (421): host '{}' on a TLS connection for '{}'", host, has_name ? name : "(no SNI)");
595 81 : return true;
596 1868 : }
597 :
598 4575 : tls_handshake_record tls_handshake_record::take(SSL* ssl) {
599 4575 : std::unique_ptr<tls_handshake_record> record(record_of(ssl));
600 4575 : if (!record) return {};
601 4575 : SSL_set_ex_data(ssl, handshake_index(), nullptr);
602 4575 : return std::move(*record);
603 4575 : }
604 :
605 96 : bool certificate_manager::update_tls_options(const tls_options& options) {
606 : // Tried on a context first, so the contexts created afterwards cannot fail
607 96 : if (!valid_tls_options(options)) return false;
608 75 : options_ = options;
609 75 : return true;
610 : }
611 :
612 39 : boost::system::error_code certificate_manager::set_tls_options(const tls_options& options) {
613 39 : std::lock_guard<std::mutex> lock(mutex_);
614 39 : if (!update_tls_options(options)) return invalid_argument();
615 : // They replace what the setters below changed
616 24 : legacy_.reset();
617 24 : ciphers_chosen_ = false;
618 24 : return {};
619 39 : }
620 :
621 75 : tls_options certificate_manager::get_tls_options() const {
622 75 : std::lock_guard<std::mutex> lock(mutex_);
623 150 : return options_;
624 75 : }
625 :
626 21 : boost::system::error_code certificate_manager::set_server_ciphers(const std::string& ciphers,
627 : bool prefer_server_ciphers) {
628 21 : std::lock_guard<std::mutex> lock(mutex_);
629 21 : auto options = options_;
630 21 : options.ciphers = ciphers;
631 21 : options.prefer_server_ciphers = prefer_server_ciphers;
632 21 : if (!update_tls_options(options)) return invalid_argument();
633 15 : ciphers_chosen_ = true;
634 : // chosen here: the legacy protocols no longer decide them
635 15 : if (legacy_) legacy_->ciphers.reset();
636 15 : return {};
637 21 : }
638 :
639 158 : void certificate_manager::enable_legacy_protocols(bool enable) {
640 158 : std::lock_guard<std::mutex> lock(mutex_);
641 158 : if (enable == legacy_.has_value()) return;
642 36 : auto options = options_;
643 36 : if (enable) {
644 21 : legacy_change change{options.min_version, options.security_level, std::nullopt};
645 21 : options.min_version = tls_version::tls1_0;
646 21 : options.security_level = 0;
647 : // TLS 1.0 and 1.1 have no AEAD cipher: OpenSSL's defaults, unless they were chosen
648 21 : if (!ciphers_chosen_) {
649 15 : change.ciphers = options.ciphers;
650 15 : options.ciphers.clear();
651 : }
652 21 : if (update_tls_options(options)) legacy_ = std::move(change);
653 21 : } else {
654 : // What enabling them changed, and only that
655 15 : options.min_version = legacy_->min_version;
656 15 : options.security_level = legacy_->security_level;
657 15 : if (legacy_->ciphers) options.ciphers = *legacy_->ciphers;
658 15 : if (update_tls_options(options)) legacy_.reset();
659 : }
660 158 : }
661 :
662 7924 : void certificate_manager::apply_session_cache(boost::asio::ssl::context& context) const {
663 7924 : if (session_cache_) write_session_cache(context.native_handle(), session_cache_->options);
664 7924 : }
665 :
666 3807 : boost::system::error_code certificate_manager::claim_session_cache(const session_cache_options& options) {
667 7572 : if (options.enabled &&
668 3765 : (options.size == 0 || options.size > static_cast<size_t>(std::numeric_limits<long>::max()))) {
669 3 : LOG_ERROR("invalid TLS session cache size: {}", options.size);
670 3 : return invalid_argument();
671 : }
672 3804 : std::lock_guard<std::mutex> lock(mutex_);
673 3804 : if (session_cache_ && session_cache_->holders > 0 && session_cache_->options != options) {
674 18 : LOG_ERROR("the TLS session cache differs from the one of the other listeners of its certificate manager");
675 18 : return invalid_argument();
676 : }
677 3786 : size_t holders = session_cache_ ? session_cache_->holders : 0;
678 3786 : session_cache_ = session_cache_claim{options, holders + 1};
679 3786 : if (default_context_) apply_session_cache(*default_context_);
680 3786 : ssl_contexts_->for_each_value([this](const auto& context) {
681 426 : if (context) apply_session_cache(*context);
682 426 : });
683 3786 : return {};
684 3804 : }
685 :
686 3786 : void certificate_manager::release_session_cache() {
687 3786 : std::lock_guard<std::mutex> lock(mutex_);
688 3786 : if (session_cache_ && session_cache_->holders > 0) --session_cache_->holders;
689 3786 : }
690 :
691 457 : void certificate_manager::generate_self_signed_certificate() {
692 : // Generate RSA key
693 457 : EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
694 457 : EVP_PKEY* pkey = nullptr;
695 :
696 457 : if (ctx) {
697 457 : if (EVP_PKEY_keygen_init(ctx) > 0) {
698 457 : if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) > 0) {
699 457 : EVP_PKEY_keygen(ctx, &pkey);
700 : }
701 : }
702 457 : EVP_PKEY_CTX_free(ctx);
703 : }
704 :
705 457 : if (!pkey) {
706 0 : LOG_ERROR("Failed to generate RSA key for self-signed certificate");
707 0 : return;
708 : }
709 :
710 : // Generate certificate
711 457 : X509* x509 = X509_new();
712 457 : ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
713 457 : X509_gmtime_adj(X509_get_notBefore(x509), 0);
714 457 : X509_gmtime_adj(X509_get_notAfter(x509), 31536000L); // 1 year
715 :
716 457 : X509_set_pubkey(x509, pkey);
717 :
718 : // Subject, and issuer as it is self-signed: a name of its own, as recent OpenSSL versions
719 : // give the name of a certificate as const
720 457 : X509_NAME* name = X509_NAME_new();
721 457 : X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
722 : reinterpret_cast<const unsigned char*>("US"), -1, -1, 0);
723 457 : X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
724 : reinterpret_cast<const unsigned char*>("Thinger Development"), -1, -1, 0);
725 457 : X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
726 : reinterpret_cast<const unsigned char*>("localhost"), -1, -1, 0);
727 :
728 457 : X509_set_subject_name(x509, name);
729 457 : X509_set_issuer_name(x509, name);
730 457 : X509_NAME_free(name);
731 457 : X509_sign(x509, pkey, EVP_sha256());
732 :
733 : // Create SSL context
734 457 : auto context = create_base_ssl_context(options_);
735 457 : if (!context) {
736 0 : X509_free(x509);
737 0 : EVP_PKEY_free(pkey);
738 0 : return;
739 : }
740 :
741 : // Set certificate and key
742 457 : if (SSL_CTX_use_certificate(context->native_handle(), x509) != 1) {
743 0 : LOG_ERROR("Failed to use certificate for self-signed cert");
744 457 : } else if (SSL_CTX_use_PrivateKey(context->native_handle(), pkey) != 1) {
745 0 : LOG_ERROR("Failed to use private key for self-signed cert");
746 : } else {
747 457 : apply_session_cache(*context);
748 457 : default_context_ = context;
749 457 : LOG_INFO("Generated self-signed certificate for development use (CN=localhost)");
750 : }
751 :
752 : // Cleanup
753 457 : X509_free(x509);
754 457 : EVP_PKEY_free(pkey);
755 457 : }
756 :
757 5229 : std::shared_ptr<boost::asio::ssl::context> certificate_manager::select_context(const char* hostname) const {
758 5229 : if (hostname != nullptr) {
759 3465 : LOG_DEBUG("SNI connection for: {}", hostname);
760 6930 : if (auto certificate = get_certificate(hostname)) return certificate;
761 : // At debug level: any client (or scanner) can send names without a certificate
762 3138 : LOG_DEBUG("Using default server certificate for hostname: {}", hostname);
763 : }
764 4902 : std::lock_guard<std::mutex> lock(mutex_);
765 4902 : return default_context_;
766 4902 : }
767 :
768 : namespace {
769 :
770 : // Switch a handshake to the certificate of `context`. OpenSSL takes the certificate of the
771 : // context switched to, but also its cipher list and security level: the handshake keeps
772 : // those of the listener (as its versions and options), so a context given ready-made cannot
773 : // lower them. False if OpenSSL fails
774 626 : bool switch_context(SSL* ssl, SSL_CTX* context) {
775 626 : auto ciphers = tls12_cipher_list(ssl);
776 626 : int level = SSL_get_security_level(ssl);
777 1252 : if (SSL_set_SSL_CTX(ssl, context) == nullptr ||
778 626 : (!ciphers.empty() && SSL_set_cipher_list(ssl, ciphers.c_str()) != 1)) {
779 0 : return false;
780 : }
781 626 : SSL_set_security_level(ssl, level);
782 626 : return true;
783 626 : }
784 :
785 : }
786 :
787 : // SNI callback implementation
788 5262 : int certificate_manager::sni_callback(SSL* ssl, int* al, void* arg) {
789 5262 : if (!ssl || !arg) return SSL_TLSEXT_ERR_OK;
790 :
791 : // Get SNI server name (none if the client sent none)
792 5256 : const char* hostname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
793 :
794 : // Called from OpenSSL: nothing may be thrown through it
795 : try {
796 : // Kept alive while it chooses. Gone, nothing of it applies: the handshake stays on the
797 : // context it started with (that of its listener)
798 5256 : auto manager = static_cast<const manager_ref*>(arg)->lock();
799 5256 : if (!manager) return SSL_TLSEXT_ERR_OK;
800 : // The default certificate may have been replaced since the listener took it. The
801 : // connection holds a reference to the context it switches to, which outlives a later
802 : // replacement
803 5229 : auto context = manager->select_context(hostname);
804 5855 : if (context && context->native_handle() != SSL_get_SSL_CTX(ssl) &&
805 626 : !switch_context(ssl, context->native_handle())) {
806 0 : LOG_ERROR("cannot set the certificate for hostname {}", hostname ? hostname : "");
807 0 : if (al) *al = SSL_AD_INTERNAL_ERROR;
808 0 : return SSL_TLSEXT_ERR_ALERT_FATAL;
809 : }
810 5229 : manager->apply_client_verify(ssl, requested_name(ssl));
811 5256 : } catch (const std::exception& e) {
812 0 : LOG_ERROR("cannot select the certificate for hostname {}: {}", hostname ? hostname : "", e.what());
813 0 : return SSL_TLSEXT_ERR_ALERT_FATAL;
814 0 : }
815 :
816 5229 : return SSL_TLSEXT_ERR_OK;
817 : }
818 :
819 : } // namespace thinger::asio
|