Kurlyk
Loading...
Searching...
No Matches
SocketClient.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef KURLYK_HEADER_KURLYK_WEBSOCKET_CLIENT_SIMPLE_WEB_SOCKET_CLIENT_HPP_INCLUDED
3#define KURLYK_HEADER_KURLYK_WEBSOCKET_CLIENT_SIMPLE_WEB_SOCKET_CLIENT_HPP_INCLUDED
4
13
14#include "../../../config.hpp"
15
16#if !KURLYK_USE_BOOST_ASIO
17# ifndef ASIO_STANDALONE
18# define ASIO_STANDALONE
19# endif
20#endif
21
23
24#ifdef ASIO_STANDALONE
25#include <asio/ssl.hpp>
26#else
27#include <boost/asio/ssl.hpp>
28#endif
29
30#ifdef _WIN32
31#ifndef WIN32_LEAN_AND_MEAN
32#define WIN32_LEAN_AND_MEAN
33#endif
34#include <windows.h>
35#include <wincrypt.h>
36#endif
37
38namespace SimpleWeb {
39 using WSS = asio::ssl::stream<asio::ip::tcp::socket>;
40
46 template <>
47 class SocketClient<WSS> : public SocketClientBase<WSS> {
48 public:
49
57 SocketClient(const std::string &server_port_path, bool verify_certificate = true,
58 const std::string &certification_file = std::string(), const std::string &private_key_file = std::string(),
59 const std::string &verify_file = std::string())
60 : SocketClientBase<WSS>::SocketClientBase(server_port_path, 443),
61# if (defined(ASIO_STANDALONE) && ASIO_VERSION >= 103300) || BOOST_ASIO_VERSION >= 103300
62 context(asio::ssl::context::tls_client) {
63 // Disabling TLS 1.0 and 1.1 (see RFC 8996)
64 context.set_options(asio::ssl::context::no_tlsv1);
65 context.set_options(asio::ssl::context::no_tlsv1_1);
66# else
67 context(asio::ssl::context::tlsv12) {
68# endif
69
70 if(certification_file.size() > 0 && private_key_file.size() > 0) {
71 context.use_certificate_chain_file(certification_file);
72 context.use_private_key_file(private_key_file, asio::ssl::context::pem);
73 }
74
75 if(verify_certificate) {
76# if (defined(ASIO_STANDALONE) && ASIO_VERSION >= 103300) || BOOST_ASIO_VERSION >= 103300
77 context.set_verify_callback(asio::ssl::host_name_verification(host));
78# else
79 context.set_verify_callback(asio::ssl::rfc2818_verification(host));
80# endif
81 }
82
83 if(verify_file.size() > 0) {
84 context.load_verify_file(verify_file);
85 } else {
87 }
88
89 if(verify_certificate) {
90 context.set_verify_mode(asio::ssl::verify_peer);
91 } else {
92 context.set_verify_mode(asio::ssl::verify_none);
93 }
94 };
95
96 private:
97
103#ifdef _WIN32
104 X509_STORE* store = ::SSL_CTX_get_cert_store(context.native_handle());
105 HCERTSTORE hCertStore = CertOpenSystemStore(0LL, "ROOT");
106 if (!hCertStore) {
107 return;
108 }
109
110 PCCERT_CONTEXT certContext = nullptr;
111 while (true) {
112 certContext = CertEnumCertificatesInStore(hCertStore, certContext);
113 if (!certContext) {
114 break;
115 }
116 const unsigned char* encoded = certContext->pbCertEncoded;
117 X509* x509 = d2i_X509(nullptr, &encoded, certContext->cbCertEncoded);
118 if (x509) {
119 X509_STORE_add_cert(store, x509);
120 X509_free(x509);
121 }
122 }
123 CertCloseStore(hCertStore, 0);
124#else
125 context.set_default_verify_paths();
126#endif
127 }
128
129 protected:
130 asio::ssl::context context;
131
137 void connect() override {
138 LockGuard connection_lock(connection_mutex);
139 auto connection = this->connection = std::shared_ptr<Connection>(new Connection(
140 handler_runner, config.timeout_idle, config.max_handshake_size, *io_service, context));
141 connection_lock.unlock();
142
143 std::pair<std::string, std::string> host_port;
144 if(config.proxy_server.empty())
145 host_port = {host, std::to_string(port)};
146 else {
147 auto proxy_host_port = parse_host_port(config.proxy_server, 8080);
148 host_port = {proxy_host_port.first, std::to_string(proxy_host_port.second)};
149 }
150
151 auto resolver = std::make_shared<asio::ip::tcp::resolver>(*io_service);
152 connection->set_timeout(config.timeout_request);
153 async_resolve(*resolver, host_port, [this, connection, resolver](const error_code &ec, resolver_results results) {
154 connection->cancel_timeout();
155 auto lock = connection->handler_runner->continue_lock();
156 if(!lock)
157 return;
158 if(!ec) {
159 connection->set_timeout(this->config.timeout_request);
160 asio::async_connect(connection->socket->lowest_layer(), results, [this, connection, resolver](const error_code &ec, async_connect_endpoint /*endpoint*/) {
161 connection->cancel_timeout();
162 auto lock = connection->handler_runner->continue_lock();
163 if(!lock)
164 return;
165 if(!ec) {
166 asio::ip::tcp::no_delay option(true);
167 error_code ec;
168 connection->socket->lowest_layer().set_option(option, ec);
169
170 if(!this->config.proxy_server.empty()) {
171 auto streambuf = std::make_shared<asio::streambuf>();
172 std::ostream ostream(streambuf.get());
173 auto host_port = this->host + ':' + std::to_string(this->port);
174 ostream << "CONNECT " + host_port + " HTTP/1.1\r\n"
175 << "Host: " << host_port << "\r\n";
176 if(!this->config.proxy_auth.empty())
177 ostream << "Proxy-Authorization: Basic " << Crypto::Base64::encode(this->config.proxy_auth) << "\r\n";
178 ostream << "\r\n";
179 connection->set_timeout(this->config.timeout_request);
180 asio::async_write(connection->socket->next_layer(), *streambuf, [this, connection, streambuf](const error_code &ec, std::size_t /*bytes_transferred*/) {
181 connection->cancel_timeout();
182 auto lock = connection->handler_runner->continue_lock();
183 if(!lock)
184 return;
185 if(!ec) {
186 connection->set_timeout(this->config.timeout_request);
187 connection->in_message = std::shared_ptr<InMessage>(new InMessage());
188 asio::async_read_until(connection->socket->next_layer(), connection->in_message->streambuf, "\r\n\r\n", [this, connection](const error_code &ec, std::size_t /*bytes_transferred*/) {
189 connection->cancel_timeout();
190 auto lock = connection->handler_runner->continue_lock();
191 if(!lock)
192 return;
193 if(!ec) {
194 if(!ResponseMessage::parse(*connection->in_message, connection->http_version, connection->status_code, connection->header))
195 this->connection_error(connection, make_error_code::make_error_code(errc::protocol_error));
196 else {
197 if(connection->status_code.compare(0, 3, "200") != 0)
198 this->connection_error(connection, make_error_code::make_error_code(errc::permission_denied));
199 else
200 this->handshake(connection);
201 }
202 } else
203 this->connection_error(connection, ec);
204 });
205 } else
206 this->connection_error(connection, ec);
207 });
208 } else
209 this->handshake(connection);
210 } else
211 this->connection_error(connection, ec);
212 });
213 } else
214 this->connection_error(connection, ec);
215 });
216 }
217
225 void handshake(const std::shared_ptr<Connection> &connection) {
226 SSL_set_tlsext_host_name(connection->socket->native_handle(), this->host.c_str());
227
228 connection->set_timeout(this->config.timeout_request);
229 connection->socket->async_handshake(asio::ssl::stream_base::client, [this, connection](const error_code &ec) {
230 connection->cancel_timeout();
231 auto lock = connection->handler_runner->continue_lock();
232 if(!lock)
233 return;
234 if(!ec)
235 upgrade(connection);
236 else
237 this->connection_error(connection, ec);
238 });
239 }
240 }; // SocketClient
241
242} // namespace SimpleWeb
243
244#endif // KURLYK_HEADER_KURLYK_WEBSOCKET_CLIENT_SIMPLE_WEB_SOCKET_CLIENT_HPP_INCLUDED
#define ASIO_STANDALONE
void add_root_certificates()
Adds root certificates from the system to the SSL context.
void connect() override
Initiates the connection process.
void handshake(const std::shared_ptr< Connection > &connection)
Performs the SSL/TLS handshake.
SocketClient(const std::string &server_port_path, bool verify_certificate=true, const std::string &certification_file=std::string(), const std::string &private_key_file=std::string(), const std::string &verify_file=std::string())
Constructs a WebSocket client with SSL/TLS support.
Defines public compile-time configuration for the native Asio backend.
asio::ssl::stream< asio::ip::tcp::socket > WSS
Includes the Simple-WebSocket-Server client header from a source checkout.