FETPAPI 0.6.0.0
This project provides C++ classes which facilitate the developement of ETP1.2 clients and servers.
Loading...
Searching...
No Matches
SslClientSession.h
1/*-----------------------------------------------------------------------
2Licensed to the Apache Software Foundation (ASF) under one
3or more contributor license agreements. See the NOTICE file
4distributed with this work for additional information
5regarding copyright ownership. The ASF licenses this file
6to you under the Apache License, Version 2.0 (the
7"License"; you may not use this file except in compliance
8with the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing,
13software distributed under the License is distributed on an
14"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15KIND, either express or implied. See the License for the
16specific language governing permissions and limitations
17under the License.
18-----------------------------------------------------------------------*/
19#pragma once
20
21#include "../AbstractClientSessionCRTP.h"
22
23#include <boost/version.hpp>
24#if USE_WINTLS_INSTEAD_OF_OPENSSL
25#include <wintls.hpp>
26#include <wintls/beast.hpp>
27#elif BOOST_VERSION < 106800
28#include <boost/asio/ssl/rfc2818_verification.hpp>
29#include "ssl_stream.h"
30#elif BOOST_VERSION < 107000
31#include <boost/asio/ssl/rfc2818_verification.hpp>
32#include <boost/beast/experimental/core/ssl_stream.hpp>
33#elif BOOST_VERSION < 108600
34#if BOOST_VERSION < 107300
35#include <boost/asio/ssl/rfc2818_verification.hpp>
36#else
37#include <boost/asio/ssl/host_name_verification.hpp>
38#endif
39#include <boost/beast/http.hpp>
40#include <boost/beast/ssl.hpp>
41#include <boost/beast/websocket/ssl.hpp>
42#else
43#include <boost/asio/ssl.hpp>
44#include <boost/beast/websocket/ssl.hpp>
45#endif
46
47namespace beast = boost::beast; // from <boost/beast.hpp>
48namespace http = beast::http; // from <boost/beast/http.hpp>
49namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
50
51namespace ETP_NS
52{
53 class SslClientSession : public AbstractClientSessionCRTP<SslClientSession>
54 {
55 public:
56 /*
57 * @param frameSize Sets the size of the write buffer used by the implementation to send frames : https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/write_buffer_bytes/overload1.html.
58 */
59 FETPAPI_DLL_IMPORT_OR_EXPORT SslClientSession(
60#if USE_WINTLS_INSTEAD_OF_OPENSSL
61 wintls::context&& ctx,
62#else
63 ssl::context&& ctx,
64#endif
65 InitializationParameters const* initializationParams, const std::string& target, const std::string& authorization, const std::string& proxyAuthorization = "",
66 const std::map<std::string, std::string>& additionalHandshakeHeaderFields = {}, std::size_t frameSize = 4096);
67
68 virtual ~SslClientSession() {}
69
70 // Called by the base class
71#if USE_WINTLS_INSTEAD_OF_OPENSSL
72 FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr<websocket::stream<wintls::stream<beast::tcp_stream>>>& ws() { return ws_; }
73#elif BOOST_VERSION < 107000
74 FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr<websocket::stream<beast::ssl_stream<tcp::socket>>>& ws() { return ws_; }
75#elif BOOST_VERSION < 108600
76 FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr<websocket::stream<beast::ssl_stream<beast::tcp_stream>>>& ws() { return ws_; }
77#else
78 FETPAPI_DLL_IMPORT_OR_EXPORT std::unique_ptr< websocket::stream<ssl::stream<beast::tcp_stream>>>& ws() { return ws_; }
79#endif
80
81 bool isTls() const final { return true; }
82
83 void asyncConnect(const tcp::resolver::results_type& results)
84 {
85#if USE_WINTLS_INSTEAD_OF_OPENSSL
86 ws_.reset(new websocket::stream<wintls::stream<beast::tcp_stream>>(ioc, sslContext_));
87#elif BOOST_VERSION < 107000
88 ws_.reset(new websocket::stream<beast::ssl_stream<tcp::socket>>(ioc, sslContext_));
89#elif BOOST_VERSION < 108600
90 ws_.reset(new websocket::stream<beast::ssl_stream<beast::tcp_stream>>(ioc, sslContext_));
91#else
92 ws_.reset(new websocket::stream<ssl::stream<beast::tcp_stream>>(ioc, sslContext_));
93#endif
94
95 ws_->binary(true);
96#if BOOST_VERSION < 107000
97 ws_->write_buffer_size(frameSize_);
98#else
99 ws_->write_buffer_bytes(frameSize_);
100#endif
101
102#if USE_WINTLS_INSTEAD_OF_OPENSSL
103 // Set SNI hostname (many hosts need this to handshake successfully)
104 ws_->next_layer().set_server_hostname(etpServerHost);
105
106 // Enable Check whether the Server Certificate was revoked
107 ws_->next_layer().set_certificate_revocation_check(true);
108#else
109 // Set SNI Hostname (many hosts need this to handshake successfully)
110 if (!SSL_set_tlsext_host_name(ws_->next_layer().native_handle(), etpServerHost.data()))
111 {
112 boost::system::error_code ecSNI{ static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
113 std::cerr << "Websocket on connect (SNI): " << ecSNI.message() << std::endl;
114 }
115
116 // Set the expected hostname in the peer certificate for verification
117
118#if BOOST_VERSION < 107300
119 ws_->next_layer().set_verify_callback(ssl::rfc2818_verification(etpServerHost));
120#else
121 ws_->next_layer().set_verify_callback(ssl::host_name_verification(etpServerHost));
122#endif
123#endif
124
125 // Reality check: IPv6 is unlikely to be available yet
126 std::vector<tcp::endpoint> endpoints = std::vector<tcp::endpoint>(results.begin(), results.end());
127 std::stable_partition(endpoints.begin(), endpoints.end(), [](auto entry) {return entry.protocol() == tcp::v4(); });
128
129 // Make the connection on the IP address we get from a lookup
130#if BOOST_VERSION < 107000
131 boost::asio::async_connect(
132 ws_->next_layer().next_layer(),
133 endpoints.begin(),
134 endpoints.end(),
135 std::bind(
136 &SslClientSession::on_ssl_connect,
137 std::static_pointer_cast<SslClientSession>(shared_from_this()),
138 std::placeholders::_1));
139#else
140 beast::get_lowest_layer(*ws_).async_connect(
141 endpoints,
142 beast::bind_front_handler(
143 &SslClientSession::on_ssl_connect,
144 std::static_pointer_cast<SslClientSession>(shared_from_this())));
145#endif
146 }
147
148#if BOOST_VERSION < 107000
149 void on_ssl_connect(boost::system::error_code ec)
150#else
151 void on_ssl_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
152#endif
153 {
154 if (ec) {
155 std::cerr << "on_ssl_connect : " << ec.message() << std::endl;
156 }
157
158 if (!proxyHost.empty()) {
159 proxyHandshake.version(11);
160 proxyHandshake.method(http::verb::connect);
161 proxyHandshake.target(etpServerHost + ':' + etpServerPort);
162 proxyHandshake.set(http::field::host, etpServerHost + ':' + etpServerPort);
163 if (!etpServerAuthorization.empty()) {
164 proxyHandshake.set(http::field::authorization, etpServerAuthorization);
165 }
166 if (!proxyAuthorization.empty()) {
167 proxyHandshake.set(http::field::proxy_authorization, proxyAuthorization);
168 }
169
170 // Send the handshake to the proxy
171 http::async_write(ws_->next_layer().next_layer(), proxyHandshake,
172 std::bind(
174 std::static_pointer_cast<SslClientSession>(shared_from_this()),
175 std::placeholders::_1,
176 std::placeholders::_2));
177 }
178 else {
179 // Perform the SSL handshake
180 ws_->next_layer().async_handshake(
181#if USE_WINTLS_INSTEAD_OF_OPENSSL
182 wintls::handshake_type::client,
183#else
184 ssl::stream_base::client,
185#endif
186 std::bind(
187 &AbstractClientSessionCRTP::on_ssl_handshake,
188 std::static_pointer_cast<AbstractClientSessionCRTP>(shared_from_this()),
189 std::placeholders::_1));
190 }
191 }
192
193 void
195 boost::system::error_code ec,
196 std::size_t bytes_transferred)
197 {
198 boost::ignore_unused(bytes_transferred);
199
200 if (ec) {
201 std::cerr << "Proxy handshake write : " << ec.message() << std::endl;
202 return;
203 }
204
217 http_proxy_handshake_parser.skip(true); // see https://stackoverflow.com/a/49837467/10904212
218
219 // Receive the HTTP response
220 http::async_read(ws_->next_layer().next_layer(), receivedBuffer, http_proxy_handshake_parser,
221 std::bind(
222 &SslClientSession::on_proxy_handshake_read,
223 std::static_pointer_cast<SslClientSession>(shared_from_this()),
224 std::placeholders::_1,
225 std::placeholders::_2));
226 }
227
228 void
229 on_proxy_handshake_read(
230 boost::system::error_code ec,
231 std::size_t bytes_transferred)
232 {
233 boost::ignore_unused(bytes_transferred);
234 if (ec) {
235 std::cerr << "read : " << ec.message() << std::endl;
236 return;
237 }
238
239 // Perform the SSL handshake
240 ws_->next_layer().async_handshake(
241#if USE_WINTLS_INSTEAD_OF_OPENSSL
242 wintls::handshake_type::client,
243#else
244 ssl::stream_base::client,
245#endif
246 std::bind(
247 &AbstractClientSessionCRTP::on_ssl_handshake,
248 std::static_pointer_cast<AbstractClientSessionCRTP>(shared_from_this()),
249 std::placeholders::_1));
250 }
251
252 private:
253#if USE_WINTLS_INSTEAD_OF_OPENSSL
254 wintls::context sslContext_;
255#else
256 ssl::context sslContext_;
257#endif
258#if USE_WINTLS_INSTEAD_OF_OPENSSL
259 std::unique_ptr<websocket::stream<wintls::stream<beast::tcp_stream>>> ws_;
260#elif BOOST_VERSION < 107000
261 std::unique_ptr<websocket::stream<beast::ssl_stream<tcp::socket>>> ws_;
262#elif BOOST_VERSION < 108600
263 std::unique_ptr<websocket::stream<beast::ssl_stream<beast::tcp_stream>>> ws_;
264#else
265 std::unique_ptr<websocket::stream<ssl::stream<beast::tcp_stream>>> ws_;
266#endif
267 http::request<http::empty_body> proxyHandshake;
268 http::response<http::empty_body> proxyHandshakeResponse;
269 // use own response parser
270 // NOTE: 200 response to a CONNECT request from a tunneling proxy do not carry a body
271 http::response_parser<http::empty_body> http_proxy_handshake_parser;
272 std::size_t frameSize_;
273 };
274}
Definition AbstractClientSessionCRTP.h:28
Definition InitializationParameters.h:41
void on_proxy_handshake_write(boost::system::error_code ec, std::size_t bytes_transferred)
Definition SslClientSession.h:194