| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "TcpSslConn.hpp" |
| |
| #include <openssl/err.h> |
| #include <openssl/x509.h> |
| |
| #include <chrono> |
| #include <iostream> |
| #include <thread> |
| |
| #include <boost/exception/diagnostic_information.hpp> |
| #include <boost/optional.hpp> |
| |
| #include <geode/ExceptionTypes.hpp> |
| #include <geode/SystemProperties.hpp> |
| |
| #include "util/Log.hpp" |
| |
| namespace apache { |
| namespace geode { |
| namespace client { |
| |
| TcpSslConn::TcpSslConn(const std::string& hostname, uint16_t, |
| const std::string& sniProxyHostname, |
| uint16_t sniProxyPort, |
| std::chrono::microseconds connect_timeout, |
| int32_t maxBuffSizePool, const std::string& pubkeyfile, |
| const std::string& privkeyfile, |
| const std::string& pemPassword) |
| : TcpConn{sniProxyHostname, sniProxyPort, connect_timeout, maxBuffSizePool}, |
| ssl_context_{boost::asio::ssl::context::sslv23_client}, |
| strand_(io_context_) { |
| init(pubkeyfile, privkeyfile, pemPassword, hostname); |
| } |
| |
| TcpSslConn::TcpSslConn(const std::string& hostname, uint16_t port, |
| std::chrono::microseconds connect_timeout, |
| int32_t maxBuffSizePool, const std::string& pubkeyfile, |
| const std::string& privkeyfile, |
| const std::string& pemPassword) |
| : TcpConn{hostname, port, connect_timeout, maxBuffSizePool}, |
| ssl_context_{boost::asio::ssl::context::sslv23_client}, |
| strand_(io_context_) { |
| init(pubkeyfile, privkeyfile, pemPassword); |
| } |
| |
| TcpSslConn::TcpSslConn(const std::string& ipaddr, |
| std::chrono::microseconds connect_timeout, |
| int32_t maxBuffSizePool, const std::string& pubkeyfile, |
| const std::string& privkeyfile, |
| const std::string& pemPassword) |
| : TcpSslConn{ |
| ipaddr.substr(0, ipaddr.find(':')), |
| static_cast<uint16_t>(std::stoi(ipaddr.substr(ipaddr.find(':') + 1))), |
| connect_timeout, |
| maxBuffSizePool, |
| pubkeyfile, |
| privkeyfile, |
| pemPassword} {} |
| |
| TcpSslConn::TcpSslConn(const std::string& ipaddr, |
| std::chrono::microseconds connect_timeout, |
| int32_t maxBuffSizePool, |
| const std::string& sniProxyHostname, |
| uint16_t sniProxyPort, const std::string& pubkeyfile, |
| const std::string& privkeyfile, |
| const std::string& pemPassword) |
| : TcpSslConn{ |
| ipaddr.substr(0, ipaddr.find(':')), |
| static_cast<uint16_t>(std::stoi(ipaddr.substr(ipaddr.find(':') + 1))), |
| sniProxyHostname, |
| sniProxyPort, |
| connect_timeout, |
| maxBuffSizePool, |
| pubkeyfile, |
| privkeyfile, |
| pemPassword} {} |
| |
| void TcpSslConn::init(const std::string& pubkeyfile, |
| const std::string& privkeyfile, |
| const std::string& pemPassword, |
| const std::string& sniHostname) { |
| // Most of the SSL configuration provided *through* Asio is on the context. |
| // This configuration is copied into each SSL instance upon construction. |
| // That means you need to get your configuration in order before you |
| // construct the stream and connect the socket. |
| LOGDEBUG( |
| "*** TcpSslConn init, pubkeyfile = %s, pemPassword = %s, sniHostname = " |
| "%s", |
| pubkeyfile.c_str(), pemPassword.c_str(), sniHostname.c_str()); |
| |
| try { |
| ssl_context_.set_verify_mode(boost::asio::ssl::verify_peer); |
| ssl_context_.load_verify_file(pubkeyfile); |
| |
| ssl_context_.set_password_callback( |
| [pemPassword](std::size_t /*max_length*/, |
| boost::asio::ssl::context::password_purpose /*purpose*/) { |
| return pemPassword; |
| }); |
| |
| if (!privkeyfile.empty()) { |
| ssl_context_.use_certificate_chain_file(privkeyfile); |
| ssl_context_.use_private_key_file( |
| privkeyfile, boost::asio::ssl::context::file_format::pem); |
| } |
| |
| auto stream = std::unique_ptr<ssl_stream_type>( |
| new ssl_stream_type{socket_, ssl_context_}); |
| |
| SSL_set_tlsext_host_name(stream->native_handle(), sniHostname.c_str()); |
| |
| stream->handshake(ssl_stream_type::client); |
| |
| std::stringstream ss; |
| ss << "Setup SSL " << socket_.local_endpoint() << " -> " |
| << socket_.remote_endpoint(); |
| LOGINFO(ss.str()); |
| |
| ss.clear(); |
| ss << "SNI hostname: " << sniHostname; |
| LOGINFO(ss.str()); |
| |
| socket_stream_ = std::move(stream); |
| } catch (const boost::exception& ex) { |
| // error handling |
| std::string info = boost::diagnostic_information(ex); |
| LOGDEBUG("caught boost exception: %s", info.c_str()); |
| throw apache::geode::client::SslException(info.c_str()); |
| } |
| } |
| |
| TcpSslConn::~TcpSslConn() { |
| std::stringstream ss; |
| ss << "Teardown SSL " << socket_.local_endpoint() << " -> "; |
| try { |
| ss << socket_.remote_endpoint(); |
| } catch (...) { |
| } |
| LOGFINE(ss.str()); |
| } |
| |
| void TcpSslConn::prepareAsyncRead( |
| char* buff, size_t len, |
| boost::optional<boost::system::error_code>& read_result, |
| std::size_t& bytes_read) { |
| boost::asio::async_read( |
| *socket_stream_, boost::asio::buffer(buff, len), |
| boost::asio::bind_executor( |
| strand_, [&read_result, &bytes_read]( |
| const boost::system::error_code& ec, const size_t n) { |
| bytes_read = n; |
| |
| // EOF itself occurs when there is no data available on the socket |
| // at the time of the read. It may simply imply data has yet to |
| // arrive. Do nothing. Defer to timeout rather than assume a broken |
| // connection. |
| if (ec != boost::asio::error::eof && |
| ec != boost::asio::error::try_again) { |
| read_result = ec; |
| return; |
| } |
| })); |
| } |
| |
| void TcpSslConn::prepareAsyncWrite( |
| const char* buff, size_t len, |
| boost::optional<boost::system::error_code>& write_result, |
| std::size_t& bytes_written) { |
| boost::asio::async_write( |
| *socket_stream_, boost::asio::buffer(buff, len), |
| boost::asio::bind_executor( |
| strand_, [&write_result, &bytes_written]( |
| const boost::system::error_code& ec, const size_t n) { |
| bytes_written = n; |
| |
| if (ec != boost::asio::error::eof && |
| ec != boost::asio::error::try_again) { |
| write_result = ec; |
| return; |
| } |
| })); |
| } |
| |
| } // namespace client |
| } // namespace geode |
| } // namespace apache |