ssl_stream.cc
1 // Copyright 2019, Beeri 15. All rights reserved.
2 // Author: Roman Gershman (romange@gmail.com)
3 //
4 
5 #include <boost/asio/ssl/error.hpp>
6 
7 #include "base/logging.h"
8 #include "util/http/ssl_stream.h"
9 
10 namespace util {
11 
12 namespace http {
13 
14 using namespace boost;
15 using asio::ssl::detail::stream_core;
16 
17 namespace detail {
18 
19 using asio::ssl::stream_base;
20 using asio::ssl::detail::engine;
21 
22 Engine::Engine(SSL_CTX* context) : ssl_(::SSL_new(context)) {
23  CHECK(ssl_);
24 
25  ::SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE);
26  ::SSL_set_mode(ssl_, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
27  ::SSL_set_mode(ssl_, SSL_MODE_RELEASE_BUFFERS);
28 
29  ::BIO* int_bio = 0;
30  ::BIO_new_bio_pair(&int_bio, 0, &ext_bio_, 0);
31  ::SSL_set_bio(ssl_, int_bio, int_bio);
32 }
33 
34 Engine::~Engine() {
35  CHECK(!SSL_get_app_data(ssl_));
36 
37  ::BIO_free(ext_bio_);
38  ::SSL_free(ssl_);
39 }
40 
41 system::error_code Engine::set_verify_mode(verify_mode v, system::error_code& ec) {
42  ::SSL_set_verify(ssl_, v, ::SSL_get_verify_callback(ssl_));
43 
44  ec = system::error_code();
45  return ec;
46 }
47 
48 engine::want Engine::perform(int (Engine::*op)(void*, std::size_t), void* data, std::size_t length,
49  system::error_code& ec, std::size_t* bytes_transferred) {
50  std::size_t pending_output_before = ::BIO_ctrl_pending(ext_bio_);
51  ::ERR_clear_error();
52  int result = (this->*op)(data, length);
53  int ssl_error = ::SSL_get_error(ssl_, result);
54  int sys_error = static_cast<int>(::ERR_get_error());
55  std::size_t pending_output_after = ::BIO_ctrl_pending(ext_bio_);
56 
57  if (ssl_error == SSL_ERROR_SSL) {
58  ec = system::error_code(sys_error, asio::error::get_ssl_category());
59  return pending_output_after > pending_output_before ? engine::want_output
60  : engine::want_nothing;
61  }
62 
63  if (ssl_error == SSL_ERROR_SYSCALL) {
64  if (sys_error == 0) {
65  ec = asio::ssl::error::unspecified_system_error;
66  } else {
67  ec = system::error_code(sys_error, asio::error::get_ssl_category());
68  }
69  return pending_output_after > pending_output_before ? engine::want_output
70  : engine::want_nothing;
71  }
72 
73  if (result > 0 && bytes_transferred)
74  *bytes_transferred = static_cast<std::size_t>(result);
75 
76  if (ssl_error == SSL_ERROR_WANT_WRITE) {
77  ec = system::error_code();
78  return engine::want_output_and_retry;
79  } else if (pending_output_after > pending_output_before) {
80  ec = system::error_code();
81  return result > 0 ? engine::want_output : engine::want_output_and_retry;
82  } else if (ssl_error == SSL_ERROR_WANT_READ) {
83  ec = system::error_code();
84  return engine::want_input_and_retry;
85  } else if (ssl_error == SSL_ERROR_ZERO_RETURN) {
86  ec = asio::error::eof;
87  return engine::want_nothing;
88  } else if (ssl_error == SSL_ERROR_NONE) {
89  ec = system::error_code();
90  return engine::want_nothing;
91  } else {
92  ec = asio::ssl::error::unexpected_result;
93  return engine::want_nothing;
94  }
95 }
96 
97 int Engine::do_connect(void*, std::size_t) {
98  return ::SSL_connect(ssl_);
99 }
100 
101 int Engine::do_shutdown(void*, std::size_t) {
102  int result = ::SSL_shutdown(ssl_);
103  if (result == 0)
104  result = ::SSL_shutdown(ssl_);
105  return result;
106 }
107 
108 int Engine::do_read(void* data, std::size_t length) {
109  return ::SSL_read(ssl_, data, length < INT_MAX ? static_cast<int>(length) : INT_MAX);
110 }
111 
112 int Engine::do_write(void* data, std::size_t length) {
113  return ::SSL_write(ssl_, data, length < INT_MAX ? static_cast<int>(length) : INT_MAX);
114 }
115 
116 Engine::want Engine::handshake(stream_base::handshake_type type, system::error_code& ec) {
117  CHECK_EQ(stream_base::client, type);
118 
119  return perform(&Engine::do_connect, 0, 0, ec, 0);
120 }
121 
122 Engine::want Engine::shutdown(system::error_code& ec) {
123  return perform(&Engine::do_shutdown, 0, 0, ec, 0);
124 }
125 
126 Engine::want Engine::write(const asio::const_buffer& data, system::error_code& ec,
127  std::size_t& bytes_transferred) {
128  if (data.size() == 0) {
129  ec = system::error_code();
130  return engine::want_nothing;
131  }
132 
133  return perform(&Engine::do_write, const_cast<void*>(data.data()), data.size(), ec,
134  &bytes_transferred);
135 }
136 
137 Engine::want Engine::read(const asio::mutable_buffer& data, system::error_code& ec,
138  std::size_t& bytes_transferred) {
139  if (data.size() == 0) {
140  ec = system::error_code();
141  return engine::want_nothing;
142  }
143 
144  return perform(&Engine::do_read, data.data(), data.size(), ec, &bytes_transferred);
145 }
146 
147 void Engine::GetWriteBuf(asio::mutable_buffer* mbuf) {
148  char* buf = nullptr;
149 
150  int res = BIO_nwrite0(ext_bio_, &buf);
151  CHECK_GE(res, 0);
152  *mbuf = asio::mutable_buffer{buf, size_t(res)};
153 }
154 
155 void Engine::CommitWriteBuf(size_t sz) {
156  CHECK_EQ(sz, BIO_nwrite(ext_bio_, nullptr, sz));
157 }
158 
159 void Engine::GetReadBuf(asio::const_buffer* cbuf) {
160  char* buf = nullptr;
161 
162  int res = BIO_nread0(ext_bio_, &buf);
163  CHECK_GE(res, 0);
164  *cbuf = asio::const_buffer{buf, size_t(res)};
165 }
166 
167 void Engine::AdvanceRead(size_t sz) {
168  CHECK_EQ(sz, BIO_nread(ext_bio_, nullptr, sz));
169 }
170 
171 const system::error_code& Engine::map_error_code(system::error_code& ec) const {
172  // We only want to map the error::eof code.
173  if (ec != asio::error::eof)
174  return ec;
175 
176  // If there's data yet to be read, it's an error.
177  if (BIO_wpending(ext_bio_)) {
178  ec = asio::ssl::error::stream_truncated;
179  return ec;
180  }
181 
182  // SSL v2 doesn't provide a protocol-level shutdown, so an eof on the
183  // underlying transport is passed through.
184 #if (OPENSSL_VERSION_NUMBER < 0x10100000L)
185  if (SSL_version(ssl_) == SSL2_VERSION)
186  return ec;
187 #endif // (OPENSSL_VERSION_NUMBER < 0x10100000L)
188 
189  // Otherwise, the peer should have negotiated a proper shutdown.
190  if ((::SSL_get_shutdown(ssl_) & SSL_RECEIVED_SHUTDOWN) == 0) {
191  ec = asio::ssl::error::stream_truncated;
192  }
193 
194  return ec;
195 }
196 
197 } // namespace detail
198 
199 SslStream::SslStream(FiberSyncSocket&& arg, asio::ssl::context& ctx)
200  : engine_(ctx.native_handle()), next_layer_(std::move(arg)) {
201 }
202 
203 void SslStream::handshake(Impl::handshake_type type, error_code& ec) {
204  namespace a = ::boost::asio;
205  auto cb = [&](detail::Engine& eng, error_code& ec, size_t& bytes_transferred) {
206  bytes_transferred = 0;
207  return eng.handshake(type, ec);
208  };
209 
210  IoLoop(cb, ec);
211 }
212 
213 void SslStream::IoHandler(want op_code, system::error_code& ec) {
214  using asio::ssl::detail::engine;
215  DVLOG(1) << "io_fun::start";
216  asio::mutable_buffer mb;
217  asio::const_buffer cbuf;
218  size_t buf_size;
219 
220  switch (op_code) {
221  case engine::want_input_and_retry:
222  DVLOG(2) << "want_input_and_retry";
223  engine_.GetWriteBuf(&mb);
224  buf_size = next_layer_.read_some(mb, ec);
225  if (!ec) {
226  engine_.CommitWriteBuf(buf_size);
227  }
228  break;
229 
230  case engine::want_output_and_retry:
231  case engine::want_output:
232 
233  DVLOG(2) << "engine::want_output"
234  << (op_code == engine::want_output_and_retry ? "_and_retry" : "");
235  engine_.GetReadBuf(&cbuf);
236 
237  // Get output data from the engine and write it to the underlying
238  // transport.
239 
240  asio::write(next_layer_, cbuf, ec);
241  if (!ec) {
242  engine_.AdvanceRead(cbuf.size());
243  }
244  break;
245 
246  default:;
247  }
248 }
249 
250 } // namespace http
251 } // namespace util