5 #include "util/http/https_client.h" 7 #include "base/logging.h" 8 #include "util/asio/io_context.h" 10 #ifdef BOOST_ASIO_SEPARATE_COMPILATION 11 #include <boost/asio/ssl/impl/src.hpp> 16 #if OPENSSL_VERSION_NUMBER < 0x10100000L 17 #warning Please update your libssl to libssl1.1 - install libssl-dev 20 DEFINE_bool(https_client_disable_ssl,
false,
"");
25 using namespace boost;
27 namespace h2 = beast::http;
30 constexpr
const char kPort[] =
"443";
32 ::boost::system::error_code SslConnect(SslStream* stream,
unsigned ms) {
33 system::error_code ec;
34 for (
unsigned i = 0; i < 2; ++i) {
35 ec = stream->next_layer().ClientWaitToConnect(ms);
37 VLOG(1) <<
"Error " << i <<
": " << ec <<
"/" << ec.message() <<
" for socket " 38 << stream->next_layer().native_handle();
43 stream->handshake(asio::ssl::stream_base::client, ec);
45 auto* cipher = SSL_get_current_cipher(stream->native_handle());
46 VLOG(1) <<
"SSL handshake success " << i <<
", chosen " << SSL_CIPHER_get_name(cipher) <<
"/" 47 << SSL_CIPHER_get_version(cipher);
57 SslContextResult CreateClientSslContext(absl::string_view cert_string) {
58 system::error_code ec;
59 asio::ssl::context cntx{asio::ssl::context::tlsv12_client};
60 cntx.set_options(boost::asio::ssl::context::default_workarounds |
61 boost::asio::ssl::context::no_compression | boost::asio::ssl::context::no_sslv2 |
62 boost::asio::ssl::context::no_sslv3 | boost::asio::ssl::context::no_tlsv1 |
63 boost::asio::ssl::context::no_tlsv1_1);
64 cntx.set_verify_mode(asio::ssl::verify_peer, ec);
66 return SslContextResult(ec);
68 cntx.add_certificate_authority(asio::buffer(cert_string.data(), cert_string.size()), ec);
70 return SslContextResult(ec);
72 SSL_CTX* ssl_cntx = cntx.native_handle();
74 long flags = SSL_CTX_get_options(ssl_cntx);
75 flags |= SSL_OP_CIPHER_SERVER_PREFERENCE;
76 SSL_CTX_set_options(ssl_cntx, flags);
78 constexpr
char kCiphers[] =
"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256";
79 CHECK_EQ(1, SSL_CTX_set_cipher_list(ssl_cntx, kCiphers));
80 CHECK_EQ(1, SSL_CTX_set_ecdh_auto(ssl_cntx, 1));
82 return SslContextResult(std::move(cntx));
86 ::boost::asio::ssl::context* ssl_ctx)
87 : io_context_(*context), ssl_cntx_(*ssl_ctx), host_name_(host) {
90 auto HttpsClient::Connect(
unsigned msec) -> error_code {
91 CHECK(io_context_.InContextThread());
93 reconnect_msec_ = msec;
95 return InitSslClient();
98 auto HttpsClient::InitSslClient() -> error_code {
99 VLOG(2) <<
"Https::InitSslClient " << reconnect_needed_;
102 if (!reconnect_needed_)
104 if (FLAGS_https_client_disable_ssl) {
106 socket_->set_keep_alive(
true);
107 ec = socket_->ClientWaitToConnect(reconnect_msec_);
109 VLOG(1) <<
"Error " <<
": " << ec <<
"/" << ec.message() <<
" for socket " 110 << socket_->native_handle();
113 client_.reset(
new SslStream(FiberSyncSocket{host_name_, kPort, &io_context_}, ssl_cntx_));
114 client_->next_layer().set_keep_alive(
true);
116 if (SSL_set_tlsext_host_name(client_->native_handle(), host_name_.c_str()) != 1) {
118 ERR_error_string_n(::ERR_peek_last_error(), buf,
sizeof(buf));
120 LOG(FATAL) <<
"Could not set hostname: " << buf;
123 ec = SslConnect(client_.get(), reconnect_msec_);
125 reconnect_needed_ =
false;
127 VLOG(1) <<
"Error connecting " << ec <<
", socket " << client_->next_layer().native_handle();
133 auto HttpsClient::DrainResponse(h2::response_parser<h2::buffer_body>* parser) -> error_code {
134 if (parser->is_done())
137 constexpr
size_t kBufSize = 1 << 16;
138 std::unique_ptr<uint8_t[]> buf(
new uint8_t[kBufSize]);
139 auto& body = parser->get().body();
144 VLOG(1) <<
"parser: " << parser->got_some();
145 while (!parser->is_done()) {
146 body.data = buf.get();
147 body.size = kBufSize;
151 raw_bytes = h2::read(*client_, tmp_buffer_, *parser, ec);
153 raw_bytes = h2::read(*socket_, tmp_buffer_, *parser, ec);
155 if (ec && ec != h2::error::need_buffer) {
156 VLOG(1) <<
"Error " << ec <<
"/" << ec.message();
161 VLOG(1) <<
"DrainResp: " << raw_bytes <<
"/" << body.size;
163 VLOG_IF(1, sz > 0) <<
"Drained " << sz <<
" bytes";
168 bool HttpsClient::HandleWriteError(
const error_code& ec) {
172 LOG(WARNING) <<
"Write returned error " << ec <<
"/" << ec.message();
173 reconnect_needed_ =
true;
HttpsClient(absl::string_view host, IoContext *io_context, ::boost::asio::ssl::context *ssl_ctx)
Construct a new Https Client object.