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 : }
|