LCOV - code coverage report
Current view: top level - asio/sockets - ssl_socket.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 88.5 % 96 85
Test Date: 2026-11-10 10:38:07 Functions: 81.5 % 27 22

            Line data    Source code
       1              : #include "ssl_socket.hpp"
       2              : #include <algorithm>
       3              : #include <array>
       4              : #include <cstring>
       5              : 
       6              : #include <boost/asio/ssl/host_name_verification.hpp>
       7              : #include <openssl/x509.h>
       8              : 
       9              : namespace thinger::asio {
      10              : 
      11              : namespace {
      12              : 
      13              :     // A certificate, DER encoded
      14          186 :     std::string to_der(X509* certificate) {
      15          186 :         int size = i2d_X509(certificate, nullptr);
      16          186 :         if (size <= 0) return {};
      17          186 :         std::string der(static_cast<size_t>(size), '\0');
      18          186 :         auto* data = reinterpret_cast<unsigned char*>(der.data());
      19          186 :         i2d_X509(certificate, &data);
      20          186 :         return der;
      21          186 :     }
      22              : 
      23              :     // An earlier session resumed without a client certificate, where the host name (SNI)
      24              :     // requires one: no certificate is asked for when a session is resumed, and the mode of the
      25              :     // host is set once OpenSSL decided to resume it
      26         4581 :     bool resumed_without_certificate(SSL* ssl) {
      27         4587 :         return SSL_session_reused(ssl) && (SSL_get_verify_mode(ssl) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) &&
      28         4587 :                SSL_get0_peer_certificate(ssl) == nullptr;
      29              :     }
      30              : 
      31              : }
      32              : 
      33         8941 : ssl_socket::ssl_socket(const std::string& context, boost::asio::io_context& io_context,
      34         8941 :                        const std::shared_ptr<boost::asio::ssl::context>& ssl_context)
      35              :     : tcp_socket(context, io_context)
      36         8941 :     , ssl_stream_(socket_, *ssl_context)
      37        17882 :     , ssl_context_(ssl_context) {
      38         8941 : }
      39              : 
      40          144 : ssl_socket::ssl_socket(const std::string& context, const std::shared_ptr<tcp_socket>& socket,
      41          144 :                        const std::shared_ptr<boost::asio::ssl::context>& ssl_context)
      42              :     : tcp_socket(context, socket)
      43          144 :     , ssl_stream_(socket_, *ssl_context)
      44          288 :     , ssl_context_(ssl_context) {
      45          144 : }
      46              : 
      47         9085 : ssl_socket::~ssl_socket() {
      48         9085 :     LOG_TRACE("releasing ssl connection");
      49         9085 : }
      50              : 
      51        11048 : void ssl_socket::close() {
      52              :     // close underlying TCP socket
      53        11048 :     tcp_socket::close();
      54        11048 :     read_ahead_.clear();
      55              : 
      56              :     // clear ssl session to allow reusing socket (if necessary)
      57              :     // From SSL_clear: If a session is still open, it is considered bad and will be removed
      58              :     // from the session cache, as required by RFC2246
      59        11048 :     SSL_clear(ssl_stream_.native_handle());
      60        11048 : }
      61              : 
      62         8901 : bool ssl_socket::requires_handshake() const {
      63         8901 :     return true;
      64              : }
      65              : 
      66         9006 : awaitable<boost::system::error_code> ssl_socket::handshake(const std::string& host) {
      67              :     if (!host.empty()) {
      68              :         // add support for SNI
      69              :         if (!SSL_set_tlsext_host_name(ssl_stream_.native_handle(), host.c_str())) {
      70              :             LOG_ERROR("SSL_set_tlsext_host_name failed. SNI will fail");
      71              :         }
      72              :         // verify the server certificate hostname when verification is enabled on the
      73              :         // context (verify_peer); the callback is not consulted when verify_mode is verify_none
      74              :         boost::system::error_code verify_ec;
      75              :         ssl_stream_.set_verify_callback(boost::asio::ssl::host_name_verification(host), verify_ec);
      76              :         if (verify_ec) {
      77              :             LOG_ERROR("failed to enable host name verification: {}", verify_ec.message());
      78              :         }
      79              :         // client handshake
      80              :         auto [ec] = co_await ssl_stream_.async_handshake(
      81              :             boost::asio::ssl::stream_base::client,
      82              :             use_nothrow_awaitable);
      83              :         co_return ec;
      84              :     } else {
      85              :         // server handshake
      86              :         auto [ec] = co_await ssl_stream_.async_handshake(
      87              :             boost::asio::ssl::stream_base::server,
      88              :             use_nothrow_awaitable);
      89              :         if (!ec && resumed_without_certificate(ssl_stream_.native_handle())) {
      90              :             close();
      91              :             co_return boost::asio::error::access_denied;
      92              :         }
      93              :         if (!ec) handshake_ = tls_handshake_record::take(ssl_stream_.native_handle());
      94              :         co_return ec;
      95              :     }
      96        18012 : }
      97              : 
      98         8292 : void ssl_socket::set_client_verify(std::shared_ptr<const client_verify_policy> policy) {
      99         8292 :     client_verify_ = std::move(policy);
     100         8292 :     if (client_verify_) client_verify_->apply(ssl_stream_.native_handle());
     101         8292 : }
     102              : 
     103          153 : X509* ssl_socket::peer_certificate() const {
     104          153 :     return SSL_get0_peer_certificate(const_cast<ssl_socket*>(this)->ssl_stream_.native_handle());
     105              : }
     106              : 
     107          153 : STACK_OF(X509)* ssl_socket::peer_chain() const {
     108          153 :     return SSL_get_peer_cert_chain(const_cast<ssl_socket*>(this)->ssl_stream_.native_handle());
     109              : }
     110              : 
     111          357 : std::optional<tls_session> ssl_socket::tls() const {
     112          357 :     SSL* ssl = const_cast<ssl_socket*>(this)->ssl_stream_.native_handle();
     113          357 :     if (!SSL_is_init_finished(ssl)) return std::nullopt;
     114          357 :     tls_session session;
     115          357 :     session.version = SSL_get_version(ssl);
     116          357 :     if (const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl)) session.cipher = SSL_CIPHER_get_name(cipher);
     117          357 :     if (const char* name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) session.server_name = name;
     118          357 :     session.resumed = SSL_session_reused(ssl) == 1;
     119          357 :     session.verify_result = SSL_get_verify_result(ssl);
     120          357 :     X509* leaf = SSL_get0_peer_certificate(ssl);
     121          357 :     if (!leaf) return session;
     122          150 :     session.peer_chain_der.push_back(to_der(leaf));
     123              :     // The chain of the client has no leaf; the one of the server does
     124          150 :     STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
     125          186 :     for (int i = 0; i < sk_X509_num(chain); ++i) {
     126           36 :         X509* certificate = sk_X509_value(chain, i);
     127           36 :         if (i == 0 && X509_cmp(certificate, leaf) == 0) continue;
     128           36 :         session.peer_chain_der.push_back(to_der(certificate));
     129              :     }
     130          150 :     return session;
     131          357 : }
     132              : 
     133           19 : size_t ssl_socket::take_read_ahead(uint8_t buffer[], size_t size) {
     134           19 :     auto taken = std::min(size, read_ahead_.size());
     135           19 :     std::memcpy(buffer, read_ahead_.data(), taken);
     136           19 :     read_ahead_.erase(0, taken);
     137           19 :     return taken;
     138              : }
     139              : 
     140            0 : void ssl_socket::take_read_ahead(boost::asio::streambuf& buffer, size_t max_size) {
     141            0 :     auto taken = std::min(max_size, read_ahead_.size());
     142            0 :     buffer.commit(boost::asio::buffer_copy(buffer.prepare(taken), boost::asio::buffer(read_ahead_, taken)));
     143            0 :     read_ahead_.erase(0, taken);
     144            0 : }
     145              : 
     146        89939 : awaitable<io_result> ssl_socket::read_some(uint8_t buffer[], size_t max_size) {
     147              :     if (!read_ahead_.empty()) {
     148              :         co_return io_result{boost::system::error_code{}, take_read_ahead(buffer, max_size)};
     149              :     }
     150              :     co_return co_await ssl_stream_.async_read_some(
     151              :         boost::asio::buffer(buffer, max_size),
     152              :         use_nothrow_awaitable);
     153       179878 : }
     154              : 
     155         6749 : awaitable<io_result> ssl_socket::read_some_cancellable(uint8_t buffer[], size_t max_size,
     156              :                                                        boost::asio::cancellation_slot slot) {
     157              :     if (!read_ahead_.empty()) {
     158              :         co_return io_result{boost::system::error_code{}, take_read_ahead(buffer, max_size)};
     159              :     }
     160              :     // (with detail::read_some_on_slot() this path measured 1% more cycles, not fewer)
     161              :     co_return co_await ssl_stream_.async_read_some(boost::asio::buffer(buffer, max_size),
     162              :                                                    boost::asio::bind_cancellation_slot(slot, use_nothrow_awaitable));
     163        13498 : }
     164              : 
     165            0 : awaitable<io_result> ssl_socket::read(uint8_t buffer[], size_t size) {
     166              :     auto taken = take_read_ahead(buffer, size);
     167              :     if (taken == size) co_return io_result{boost::system::error_code{}, size};
     168              :     auto [ec, bytes] = co_await boost::asio::async_read(
     169              :         ssl_stream_,
     170              :         boost::asio::buffer(buffer + taken, size - taken),
     171              :         boost::asio::transfer_exactly(size - taken),
     172              :         use_nothrow_awaitable);
     173              :     co_return io_result{ec, taken + bytes};
     174            0 : }
     175              : 
     176            0 : awaitable<io_result> ssl_socket::read(boost::asio::streambuf& buffer, size_t size) {
     177              :     auto before = buffer.size();
     178              :     take_read_ahead(buffer, size);
     179              :     auto taken = buffer.size() - before;
     180              :     if (taken == size) co_return io_result{boost::system::error_code{}, size};
     181              :     auto [ec, bytes] = co_await boost::asio::async_read(
     182              :         ssl_stream_,
     183              :         buffer,
     184              :         boost::asio::transfer_exactly(size - taken),
     185              :         use_nothrow_awaitable);
     186              :     co_return io_result{ec, taken + bytes};
     187            0 : }
     188              : 
     189            0 : awaitable<io_result> ssl_socket::read_until(boost::asio::streambuf& buffer, std::string_view delim) {
     190              :     // read_until() looks for the delimiter in what the buffer already holds first
     191              :     take_read_ahead(buffer, read_ahead_.size());
     192              :     co_return co_await boost::asio::async_read_until(
     193              :         ssl_stream_,
     194              :         buffer,
     195              :         std::string(delim),
     196              :         use_nothrow_awaitable);
     197            0 : }
     198              : 
     199         2210 : awaitable<io_result> ssl_socket::write(const uint8_t buffer[], size_t size) {
     200              :     co_return co_await boost::asio::async_write(
     201              :         ssl_stream_,
     202              :         boost::asio::buffer(buffer, size),
     203              :         use_nothrow_awaitable);
     204         4420 : }
     205              : 
     206         1829 : awaitable<io_result> ssl_socket::write(std::string_view str) {
     207              :     co_return co_await boost::asio::async_write(
     208              :         ssl_stream_,
     209              :         boost::asio::buffer(str.data(), str.size()),
     210              :         use_nothrow_awaitable);
     211         3658 : }
     212              : 
     213        25777 : awaitable<io_result> ssl_socket::write(const std::vector<boost::asio::const_buffer>& buffers) {
     214              :     co_return co_await boost::asio::async_write(
     215              :         ssl_stream_,
     216              :         buffers,
     217              :         use_nothrow_awaitable);
     218        51554 : }
     219              : 
     220          375 : awaitable<boost::system::error_code> ssl_socket::wait(boost::asio::socket_base::wait_type type) {
     221              :     if (type == boost::asio::socket_base::wait_read && !read_ahead_.empty()) {
     222              :         co_return boost::system::error_code{};
     223              :     }
     224              :     co_return co_await tcp_socket::wait(type);
     225          750 : }
     226              : 
     227           77 : awaitable<boost::system::error_code> ssl_socket::peer_closed() {
     228              :     if (!read_ahead_.empty()) co_return boost::system::error_code{};
     229              : 
     230              :     // The raw socket holds TLS records: a close_notify alert (sent before the FIN, so the
     231              :     // socket is not empty) cannot be told apart from data without decrypting it, as TLS
     232              :     // 1.3 encrypts alerts too. Read through the TLS stream and keep any data for the next
     233              :     // read. The end of the stream is eof, with a close_notify or without one (a FIN alone,
     234              :     // stream_truncated); any other error is the connection's (a reset)
     235              :     std::array<uint8_t, 4096> data;
     236              :     auto [ec, bytes] = co_await ssl_stream_.async_read_some(boost::asio::buffer(data), use_nothrow_awaitable);
     237              :     if (bytes > 0) {
     238              :         read_ahead_.append(reinterpret_cast<const char*>(data.data()), bytes);
     239              :         co_return boost::system::error_code{};
     240              :     }
     241              :     if (ec == boost::asio::ssl::error::stream_truncated) co_return boost::asio::error::eof;
     242              :     co_return ec;
     243          154 : }
     244              : 
     245          228 : bool ssl_socket::readable_now() const {
     246          228 :     if (!read_ahead_.empty()) return true;
     247          228 :     if (SSL_pending(const_cast<ssl_socket*>(this)->ssl_stream_.native_handle()) > 0) return true;
     248          228 :     return tcp_socket::readable_now();
     249              : }
     250              : 
     251        12605 : bool ssl_socket::is_secure() const {
     252        12605 :     return true;
     253              : }
     254              : 
     255              : }
        

Generated by: LCOV version 2.0-1