LCOV - code coverage report
Current view: top level - asio/ssl - certificate_manager.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 94.0 % 497 467
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 73 73

            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
        

Generated by: LCOV version 2.0-1