reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
AsyncTlsSocketBase.cxx
Go to the documentation of this file.
1
2#if defined(HAVE_CONFIG_H)
3 #include "config.h"
4#endif
5
6#ifdef USE_SSL
7#include <functional>
8
9#include <openssl/err.h>
10#include <openssl/opensslv.h>
11#include <openssl/x509.h>
12#include <openssl/x509v3.h>
13
16#include <rutil/DnsUtil.hxx>
17#include <rutil/Logger.hxx>
18#include "ReTurnSubsystem.hxx"
19
20#define RESIPROCATE_SUBSYSTEM ReTurnSubsystem::RETURN
21
22using namespace std;
23
24namespace reTurn {
25
26AsyncTlsSocketBase::AsyncTlsSocketBase(asio::io_context& ioService, asio::ssl::context& context, bool validateServerCertificateHostname)
27 : AsyncSocketBase(ioService),
28 mSocket(ioService, context),
29 mResolver(ioService),
30 mHandshakeTimer(ioService),
31 mServerHandshakeDone(false),
32 mValidateServerCertificateHostname(validateServerCertificateHostname)
33{
34}
35
36unsigned int
38{
39 return (unsigned int)mSocket.lowest_layer().native_handle();
40}
41
42asio::error_code
43AsyncTlsSocketBase::bind(const asio::ip::address& address, unsigned short port)
44{
45 // connect() binds the socket again, for each address it tries
46 mLocalTcpEndpoint = asio::ip::tcp::endpoint(address, port);
47 return openAndBind(mSocket.next_layer());
48}
49
50void
51AsyncTlsSocketBase::connect(const std::string& address, unsigned short port)
52{
53 mHostname = address;
54
55 // Start an asynchronous resolve to translate the address
56 // into a list of endpoints.
57 resip::Data service(port);
58 mResolver.async_resolve(mLocalTcpEndpoint.protocol(), address, service.c_str(),
59 std::bind(&AsyncSocketBase::handleTcpResolve, shared_from_this(),
60 std::placeholders::_1,
61 std::placeholders::_2));
62}
63
64void
65AsyncTlsSocketBase::handleTcpResolve(const asio::error_code& ec,
66 const asio::ip::tcp::resolver::results_type& endpoints)
67{
68 if (!ec)
69 {
70 // Attempt a connection to the first endpoint in the list. Each endpoint
71 // will be tried until we successfully establish a connection (the handshake is with the
72 // first that accepts)
73 asyncConnect(mSocket.next_layer(), endpoints);
74 }
75 else
76 {
78 }
79}
80
81bool
82AsyncTlsSocketBase::setupClientSsl(SSL* ssl, const std::string& hostname, bool validateHostname)
83{
84 const bool isIpAddress = resip::DnsUtil::isIpAddress(hostname.c_str());
85
86 // RFC 6066 section 3: SNI carries a host name, never an IP address
87 if (!hostname.empty() && !isIpAddress && SSL_set_tlsext_host_name(ssl, hostname.c_str()) != 1)
88 {
89 WarningLog(<< "Failed to set SNI " << hostname << ", continuing without it");
90 }
91
92 bool ok = true;
93 if (validateHostname)
94 {
95 // OpenSSL checks the server certificate against the name during the handshake, as RFC 6125
96 // describes: a DNS or IP address subjectAltName, or the common name if there are no DNS
97 // subjectAltNames, with wildcards only as a whole left-most label
98 if (isIpAddress)
99 {
100 ok = X509_VERIFY_PARAM_set1_ip_asc(SSL_get0_param(ssl), hostname.c_str()) == 1;
101 }
102 else
103 {
104 SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
105 ok = !hostname.empty() && SSL_set1_host(ssl, hostname.c_str()) == 1;
106 }
107 if (!ok)
108 {
109 ErrLog(<< "Unable to validate the server certificate against <" << hostname << ">");
110 }
111 }
112
113 // Leave nothing on the error queue for the handshake: SSL_get_error() checks it first
114 ERR_clear_error();
115 return ok;
116}
117
118void
119AsyncTlsSocketBase::logClientHandshakeResult(SSL* ssl, const std::string& hostname, const asio::error_code& ec)
120{
121 if (!ec)
122 {
123 const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl);
124 InfoLog(<< "TLS session set up with " << hostname << ": " << SSL_get_version(ssl)
125 << " " << (cipher ? SSL_CIPHER_get_name(cipher) : "unknown cipher"));
126 }
127 else if (SSL_get_verify_result(ssl) != X509_V_OK)
128 {
129 // eg. X509_V_ERR_HOSTNAME_MISMATCH when the certificate isn't for hostname
130 WarningLog(<< "TLS handshake with " << hostname << " failed, server certificate rejected: "
131 << X509_verify_cert_error_string(SSL_get_verify_result(ssl)));
132 }
133}
134
135void
136AsyncTlsSocketBase::handleConnect(const asio::error_code& ec,
137 const asio::ip::tcp::endpoint& endpoint)
138{
139 if (!ec)
140 {
141 if (!setupClientSsl(mSocket.native_handle(), mHostname, mValidateServerCertificateHostname))
142 {
143 onConnectFailure(asio::error::invalid_argument);
144 return;
145 }
146
147 // The connection was successful - now do handshake.
148 mSocket.async_handshake(asio::ssl::stream_base::client,
149 std::bind(&AsyncSocketBase::handleClientHandshake, shared_from_this(),
150 std::placeholders::_1, endpoint));
151 }
152 else
153 {
155 }
156}
157
158void
160 const asio::ip::tcp::endpoint& endpoint)
161{
162 logClientHandshakeResult(mSocket.native_handle(), mHostname, ec);
163 if (!ec)
164 {
165 // The handshake was successful - and so was the check of the server certificate's name,
166 // when that was asked for
167 mConnected = true;
168 mConnectedAddress = endpoint.address();
169 mConnectedPort = endpoint.port();
171 }
172 else
173 {
175 }
176}
177
178void
179AsyncTlsSocketBase::doHandshake(unsigned int timeoutSeconds)
180{
181 if (timeoutSeconds > 0)
182 {
183 // Don't let a client hold the connection open without ever completing the handshake
184 std::shared_ptr<AsyncSocketBase> self = shared_from_this();
185 mHandshakeTimer.expires_after(std::chrono::seconds(timeoutSeconds));
186 mHandshakeTimer.async_wait([this, self](const asio::error_code& e)
187 {
188 if (e != asio::error::operation_aborted && !mServerHandshakeDone)
189 {
190 WarningLog(<< "TLS handshake not completed in time, closing the connection");
191 asio::error_code ec;
192 mSocket.lowest_layer().close(ec); // the handshake then fails with operation_aborted
193 }
194 });
195 }
196 mSocket.async_handshake(asio::ssl::stream_base::server,
197 std::bind(&AsyncSocketBase::handleServerHandshake, shared_from_this(), std::placeholders::_1));
198}
199
200void
202{
203 mServerHandshakeDone = true;
204 mHandshakeTimer.cancel();
205 if(e)
206 {
208 }
209 else
210 {
211 asio::error_code ec;
212 mConnectedAddress = mSocket.lowest_layer().remote_endpoint(ec).address();
213 mConnectedPort = mSocket.lowest_layer().remote_endpoint(ec).port();
214
216 }
217}
218
219asio::ip::address
224
225unsigned short
230
231void
232AsyncTlsSocketBase::transportSend(const StunTuple& destination, std::vector<asio::const_buffer>& buffers)
233{
234 // Note: destination is ignored for TLS
235 asio::async_write(mSocket, buffers,
236 std::bind(&AsyncTlsSocketBase::handleSend, shared_from_this(), std::placeholders::_1));
237}
238
239void
241{
242 mSocket.async_read_some(asio::buffer((void*)mReceiveBuffer->data(), RECEIVE_BUFFER_SIZE),
243 std::bind(&AsyncTlsSocketBase::handleReceive, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
244
245}
246
247void
249{
250 asio::async_read(mSocket, asio::buffer((void*)mReceiveBuffer->data(), 4),
251 std::bind(&AsyncSocketBase::handleReadHeader, shared_from_this(), std::placeholders::_1));
252}
253
254void
256{
258 {
259 mOnBeforeSocketCloseFp((unsigned int)mSocket.lowest_layer().native_handle());
260 }
261
262 asio::error_code ec;
263 //mSocket.shutdown(ec); // ?slg? Should we use async_shutdown? !slg! note: this fn gives a stack overflow since ASIO 1.0.0 for some reason
264 mSocket.lowest_layer().close(ec);
265}
266
267void
268AsyncTlsSocketBase::handleReadHeader(const asio::error_code& e)
269{
270 if (!e)
271 {
272 /*
273 std::cout << "Read header from tls socket: " << std::endl;
274 for(unsigned int i = 0; i < 4; i++)
275 {
276 std::cout << (char)(*mReceiveBuffer)[i] << "(" << (int)(*mReceiveBuffer)[i] << ") ";
277 }
278 std::cout << std::endl;
279 */
280
281 // Note: For both StunMessages and ChannelData messages the length in bytes 3 and 4
282 uint16_t dataLen;
283 memcpy(&dataLen, &(*mReceiveBuffer)[2], 2);
284 dataLen = ntohs(dataLen);
285
286 // ChannelData's padding is read with it, but not passed on
287 size_t padding = 0;
288 if(((*mReceiveBuffer)[0] & 0xC0) == 0) // If first 2 bits are 00 then this is a stun message
289 {
290 dataLen += 16; // There are 20 bytes in total in the header, and we have already read 4 - read the rest of the header + the body
291 }
292 else
293 {
294 padding = channelDataPadding(dataLen);
295 }
296
297 if (dataLen + padding + 4U < RECEIVE_BUFFER_SIZE)
298 {
299 asio::async_read(mSocket, asio::buffer(&(*mReceiveBuffer)[4], dataLen + padding),
300 std::bind(&AsyncTlsSocketBase::handleReceive, shared_from_this(), std::placeholders::_1, dataLen+4));
301 }
302 else
303 {
304 WarningLog(<< "Receive buffer (" << RECEIVE_BUFFER_SIZE << ") is not large enough to accommodate incoming framed data (" << dataLen+4 << ") closing connection.");
305 close();
306 }
307 }
308 else if (e != asio::error::operation_aborted)
309 {
310 if(e != asio::error::eof && e != asio::error::connection_reset)
311 {
312 WarningLog(<< "Read header error: " << e.value() << "-" << e.message());
313 }
314 close();
315 }
316}
317
318}
319#endif
320
321/* ====================================================================
322
323 Copyright (c) 2008-2026 SIP Spectrum, Inc. https://www.sipspectrum.com
324 Copyright (c) 2007-2008, Plantronics, Inc.
325 All rights reserved.
326
327 Redistribution and use in source and binary forms, with or without
328 modification, are permitted provided that the following conditions are
329 met:
330
331 1. Redistributions of source code must retain the above copyright
332 notice, this list of conditions and the following disclaimer.
333
334 2. Redistributions in binary form must reproduce the above copyright
335 notice, this list of conditions and the following disclaimer in the
336 documentation and/or other materials provided with the distribution.
337
338 3. Neither the name of Plantronics nor the names of its contributors
339 may be used to endorse or promote products derived from this
340 software without specific prior written permission.
341
342 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
343 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
344 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
345 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
346 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
347 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
348 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
349 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
350 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
351 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
352 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
353
354 ==================================================================== */
355
constexpr size_t RECEIVE_BUFFER_SIZE
struct ssl_st SSL
#define InfoLog(args_)
Definition Logger.hxx:131
#define WarningLog(args_)
Definition Logger.hxx:134
#define ErrLog(args_)
Definition Logger.hxx:137
virtual void handleReceive(const asio::error_code &e, size_t bytesTransferred)
asio::ip::tcp::endpoint mLocalTcpEndpoint
asio::error_code openAndBind(asio::ip::tcp::socket &socket)
For TCP and TLS client sockets.
virtual void handleTcpResolve(const asio::error_code &ec, const asio::ip::tcp::resolver::results_type &endpoints)
BeforeClosedHandler mOnBeforeSocketCloseFp
Provides an opportunity for the app to clean up, e.g., QoS-related data or resources just before the ...
static size_t channelDataPadding(size_t length)
Over TCP and TLS, a TURN ChannelData message is padded to a multiple of 4 bytes, the padding not coun...
virtual void handleClientHandshake(const asio::error_code &ec, const asio::ip::tcp::endpoint &endpoint)
virtual void onConnectFailure(const asio::error_code &e)
virtual void onConnectSuccess()
Class override callbacks.
virtual void handleSend(const asio::error_code &e)
Handle completion of a sendData operation.
virtual void handleReadHeader(const asio::error_code &e)
void asyncConnect(asio::ip::tcp::socket &socket, const asio::ip::tcp::resolver::results_type &endpoints)
std::shared_ptr< DataBuffer > mReceiveBuffer
Receive Buffer and state.
virtual void handleServerHandshake(const asio::error_code &e)
asio::ip::address mConnectedAddress
Connected Info and State.
asio::ip::address getSenderEndpointAddress() override
asio::ip::tcp::resolver mResolver
asio::ssl::stream< asio::ip::tcp::socket > mSocket
void transportSend(const StunTuple &destination, std::vector< asio::const_buffer > &buffers) override
void handleServerHandshake(const asio::error_code &e) override
void handleReadHeader(const asio::error_code &e) override
virtual void onServerHandshakeFailure(const asio::error_code &e)
static bool setupClientSsl(SSL *ssl, const std::string &hostname, bool validateHostname)
Sets up a client SSL to connect to hostname (a host name or IP address): SNI for a host name,...
unsigned int getSocketDescriptor() override
void handleConnect(const asio::error_code &ec, const asio::ip::tcp::endpoint &endpoint) override
unsigned short getSenderEndpointPort() override
void handleTcpResolve(const asio::error_code &ec, const asio::ip::tcp::resolver::results_type &endpoints) override
static void logClientHandshakeResult(SSL *ssl, const std::string &hostname, const asio::error_code &ec)
Logs a client handshake's outcome: the TLS version and cipher, or why the server certificate was reje...
void doHandshake(unsigned int timeoutSeconds=0)
Starts the server side handshake.
AsyncTlsSocketBase(asio::io_context &ioService, asio::ssl::context &context, bool validateServerCertificateHostname)
void connect(const std::string &address, unsigned short port) override
asio::error_code bind(const asio::ip::address &address, unsigned short port) override
Note: The following API's are thread safe and queue the request to be handled by the ioService thread...
void handleClientHandshake(const asio::error_code &ec, const asio::ip::tcp::endpoint &endpoint) override
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
const char * c_str() const
Returns a null-terminated string representing.
Definition Data.cxx:1097
static bool isIpAddress(const Data &ipAddress)
Definition DnsUtil.cxx:453
unsigned short uint16_t
Definition stdint.h:84