2017-02-21 19:54:13 -06:00
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
|
|
/*
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
*/
|
|
|
|
|
2020-04-18 03:39:50 -05:00
|
|
|
#pragma once
|
2017-02-21 19:54:13 -06:00
|
|
|
|
|
|
|
#include <cerrno>
|
|
|
|
|
|
|
|
#include "Ssl.hpp"
|
|
|
|
#include "Socket.hpp"
|
|
|
|
|
|
|
|
/// An SSL/TSL, non-blocking, data streaming socket.
|
2017-04-09 15:33:28 -05:00
|
|
|
class SslStreamSocket final : public StreamSocket
|
2017-02-21 19:54:13 -06:00
|
|
|
{
|
|
|
|
public:
|
2018-05-03 11:52:35 -05:00
|
|
|
SslStreamSocket(const int fd, bool isClient,
|
2020-04-23 05:35:42 -05:00
|
|
|
std::shared_ptr<ProtocolHandlerInterface> responseClient,
|
|
|
|
ReadType readType = NormalRead) :
|
|
|
|
StreamSocket(fd, isClient, std::move(responseClient), readType),
|
2019-12-10 05:11:20 -06:00
|
|
|
_bio(nullptr),
|
2017-02-22 10:12:12 -06:00
|
|
|
_ssl(nullptr),
|
2017-03-04 11:08:02 -06:00
|
|
|
_sslWantsTo(SslWantsTo::Neither),
|
2017-02-22 10:12:12 -06:00
|
|
|
_doHandshake(true)
|
|
|
|
{
|
2017-03-04 11:06:23 -06:00
|
|
|
LOG_DBG("SslStreamSocket ctor #" << fd);
|
|
|
|
|
2019-12-10 05:11:20 -06:00
|
|
|
_bio = BIO_new(BIO_s_socket());
|
|
|
|
if (_bio == nullptr)
|
2017-02-22 10:12:12 -06:00
|
|
|
{
|
|
|
|
throw std::runtime_error("Failed to create SSL BIO.");
|
|
|
|
}
|
|
|
|
|
2019-12-10 05:11:20 -06:00
|
|
|
BIO_set_fd(_bio, fd, BIO_NOCLOSE);
|
2017-02-22 10:12:12 -06:00
|
|
|
|
|
|
|
_ssl = SslContext::newSsl();
|
|
|
|
if (!_ssl)
|
|
|
|
{
|
2019-12-10 05:11:20 -06:00
|
|
|
BIO_free(_bio);
|
|
|
|
_bio = nullptr;
|
2017-02-22 10:12:12 -06:00
|
|
|
throw std::runtime_error("Failed to create SSL.");
|
|
|
|
}
|
|
|
|
|
2019-12-10 05:11:20 -06:00
|
|
|
SSL_set_bio(_ssl, _bio, _bio);
|
2017-02-22 10:12:12 -06:00
|
|
|
|
2018-05-03 11:52:35 -05:00
|
|
|
if (isClient)
|
2018-05-03 12:03:56 -05:00
|
|
|
{
|
2018-05-03 11:52:35 -05:00
|
|
|
SSL_set_connect_state(_ssl);
|
2018-05-03 12:03:56 -05:00
|
|
|
if (SSL_connect(_ssl) == 0)
|
|
|
|
LOG_DBG("SslStreamSocket connect #" << getFD() << " failed ");
|
|
|
|
// else -1 is quite possibly SSL_ERROR_WANT_READ
|
|
|
|
}
|
2018-05-03 11:52:35 -05:00
|
|
|
else // We are a server-side socket.
|
|
|
|
SSL_set_accept_state(_ssl);
|
2017-02-22 10:12:12 -06:00
|
|
|
}
|
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
~SslStreamSocket()
|
|
|
|
{
|
2017-03-04 11:06:23 -06:00
|
|
|
LOG_DBG("SslStreamSocket dtor #" << getFD());
|
|
|
|
|
2018-10-26 02:07:07 -05:00
|
|
|
if (!isShutdownSignalled())
|
2017-03-09 10:20:34 -06:00
|
|
|
{
|
2020-04-19 14:44:12 -05:00
|
|
|
setShutdownSignalled();
|
2017-04-09 15:33:28 -05:00
|
|
|
SslStreamSocket::closeConnection();
|
2017-03-09 10:20:34 -06:00
|
|
|
}
|
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
SSL_free(_ssl);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Shutdown the TLS/SSL connection properly.
|
2017-03-09 10:20:34 -06:00
|
|
|
void closeConnection() override
|
2017-02-21 19:54:13 -06:00
|
|
|
{
|
2017-03-25 20:50:24 -05:00
|
|
|
LOG_DBG("SslStreamSocket::closeConnection() #" << getFD());
|
2017-02-21 19:54:13 -06:00
|
|
|
if (SSL_shutdown(_ssl) == 0)
|
|
|
|
{
|
|
|
|
// Complete the bidirectional shutdown.
|
|
|
|
SSL_shutdown(_ssl);
|
|
|
|
}
|
2017-03-19 18:07:42 -05:00
|
|
|
|
|
|
|
// Close the TCP Socket.
|
|
|
|
Socket::shutdown();
|
2017-02-21 19:54:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
bool readIncomingData() override
|
|
|
|
{
|
2017-04-05 07:48:49 -05:00
|
|
|
assertCorrectThread();
|
2017-03-08 12:14:53 -06:00
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
const int rc = doHandshake();
|
|
|
|
if (rc <= 0)
|
2017-03-30 04:15:28 -05:00
|
|
|
return rc != 0;
|
2017-02-21 19:54:13 -06:00
|
|
|
|
|
|
|
// Default implementation.
|
2017-02-22 08:45:41 -06:00
|
|
|
return StreamSocket::readIncomingData();
|
2017-02-21 19:54:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void writeOutgoingData() override
|
|
|
|
{
|
2017-04-05 07:48:49 -05:00
|
|
|
assertCorrectThread();
|
2017-03-08 12:14:53 -06:00
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
const int rc = doHandshake();
|
|
|
|
if (rc <= 0)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default implementation.
|
2017-02-22 08:45:41 -06:00
|
|
|
StreamSocket::writeOutgoingData();
|
2017-02-21 19:54:13 -06:00
|
|
|
}
|
|
|
|
|
2017-03-10 04:11:58 -06:00
|
|
|
virtual int readData(char* buf, int len) override
|
2017-02-21 19:54:13 -06:00
|
|
|
{
|
2017-04-05 07:48:49 -05:00
|
|
|
assertCorrectThread();
|
2017-03-08 12:14:53 -06:00
|
|
|
|
2020-12-08 07:21:32 -06:00
|
|
|
#if ENABLE_DEBUG
|
|
|
|
if (simulateSocketError(true))
|
|
|
|
return -1;
|
|
|
|
#endif
|
2017-02-21 19:54:13 -06:00
|
|
|
return handleSslState(SSL_read(_ssl, buf, len));
|
|
|
|
}
|
|
|
|
|
2017-03-10 04:11:58 -06:00
|
|
|
virtual int writeData(const char* buf, const int len) override
|
2017-02-21 19:54:13 -06:00
|
|
|
{
|
2017-04-05 07:48:49 -05:00
|
|
|
assertCorrectThread();
|
2017-03-08 12:14:53 -06:00
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
assert (len > 0); // Never write 0 bytes.
|
2020-12-08 07:21:32 -06:00
|
|
|
|
|
|
|
#if ENABLE_DEBUG
|
|
|
|
if (simulateSocketError(false))
|
|
|
|
return -1;
|
|
|
|
#endif
|
2017-02-21 19:54:13 -06:00
|
|
|
return handleSslState(SSL_write(_ssl, buf, len));
|
|
|
|
}
|
|
|
|
|
2020-04-10 06:34:15 -05:00
|
|
|
int getPollEvents(std::chrono::steady_clock::time_point now,
|
|
|
|
int64_t & timeoutMaxMicroS) override
|
2017-02-21 19:54:13 -06:00
|
|
|
{
|
2017-04-05 07:48:49 -05:00
|
|
|
assertCorrectThread();
|
2020-12-10 08:45:59 -06:00
|
|
|
int events = StreamSocket::getPollEvents(now, timeoutMaxMicroS); // Default to base.
|
|
|
|
if (_sslWantsTo == SslWantsTo::Write) // If OpenSSL wants to write (and we don't).
|
2017-03-17 16:59:09 -05:00
|
|
|
events |= POLLOUT;
|
|
|
|
|
|
|
|
return events;
|
2017-02-21 19:54:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2020-12-08 07:21:32 -06:00
|
|
|
#if ENABLE_DEBUG
|
|
|
|
/// Return true and set errno to simulate an error
|
|
|
|
virtual bool simulateSocketError(bool read) override;
|
|
|
|
#endif
|
2017-02-21 19:54:13 -06:00
|
|
|
|
|
|
|
/// The possible next I/O operation that SSL want to do.
|
|
|
|
enum class SslWantsTo
|
|
|
|
{
|
2017-03-04 11:08:02 -06:00
|
|
|
Neither,
|
2017-02-21 19:54:13 -06:00
|
|
|
Read,
|
|
|
|
Write
|
|
|
|
};
|
|
|
|
|
|
|
|
int doHandshake()
|
|
|
|
{
|
2017-04-05 07:48:49 -05:00
|
|
|
assertCorrectThread();
|
2017-03-08 12:14:53 -06:00
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
if (_doHandshake)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
rc = SSL_do_handshake(_ssl);
|
|
|
|
}
|
|
|
|
while (rc < 0 && errno == EINTR);
|
|
|
|
|
|
|
|
if (rc <= 0)
|
|
|
|
{
|
|
|
|
rc = handleSslState(rc);
|
|
|
|
if (rc <= 0)
|
2017-03-30 04:15:28 -05:00
|
|
|
return rc != 0;
|
2017-02-21 19:54:13 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
_doHandshake = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handshake complete.
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Handles the state of SSL after read or write.
|
|
|
|
int handleSslState(const int rc)
|
|
|
|
{
|
2017-04-05 07:48:49 -05:00
|
|
|
assertCorrectThread();
|
2017-03-08 12:14:53 -06:00
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
if (rc > 0)
|
|
|
|
{
|
|
|
|
// Success: Reset so we can do either.
|
2017-03-04 11:08:02 -06:00
|
|
|
_sslWantsTo = SslWantsTo::Neither;
|
2017-02-21 19:54:13 -06:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Last operation failed. Find out if SSL was trying
|
|
|
|
// to do something different that failed, or not.
|
|
|
|
const int sslError = SSL_get_error(_ssl, rc);
|
|
|
|
switch (sslError)
|
|
|
|
{
|
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
|
|
// Shutdown complete, we're disconnected.
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: ZERO_RETURN (" << sslError << ").");
|
2017-02-21 19:54:13 -06:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
case SSL_ERROR_WANT_READ:
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: WANT_READ (" << sslError << ").");
|
2017-02-21 19:54:13 -06:00
|
|
|
_sslWantsTo = SslWantsTo::Read;
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: WANT_WRITE (" << sslError << ").");
|
2017-02-21 19:54:13 -06:00
|
|
|
_sslWantsTo = SslWantsTo::Write;
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
case SSL_ERROR_WANT_CONNECT:
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: WANT_CONNECT (" << sslError << ").");
|
|
|
|
return rc;
|
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
case SSL_ERROR_WANT_ACCEPT:
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: WANT_ACCEPT (" << sslError << ").");
|
|
|
|
return rc;
|
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: WANT_X509_LOOKUP (" << sslError << ").");
|
2017-02-21 19:54:13 -06:00
|
|
|
// Unexpected.
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
case SSL_ERROR_SYSCALL:
|
|
|
|
if (errno != 0)
|
|
|
|
{
|
|
|
|
// Posix API error, let the caller handle.
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_SYS("Socket #" << getFD() << " SSL error: SYSCALL (" << sslError << ").");
|
2017-02-21 19:54:13 -06:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallthrough...
|
|
|
|
default:
|
|
|
|
{
|
2019-12-10 05:11:20 -06:00
|
|
|
// Effectively an EAGAIN error at the BIO layer
|
|
|
|
if (BIO_should_retry(_bio))
|
|
|
|
{
|
|
|
|
LOG_TRC("Socket #" << getFD() << " BIO asks for retry - underlying EAGAIN? " <<
|
|
|
|
SSL_get_error(_ssl, rc));
|
|
|
|
return -1; // poll is used to detect real errors.
|
|
|
|
}
|
|
|
|
|
2017-03-12 13:33:16 -05:00
|
|
|
if (sslError == SSL_ERROR_SSL)
|
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: SSL (" << sslError << ").");
|
2017-03-14 20:13:36 -05:00
|
|
|
else if (sslError == SSL_ERROR_SYSCALL)
|
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: SYSCALL (" << sslError << ").");
|
2017-03-12 13:33:16 -05:00
|
|
|
#if 0 // Recent OpenSSL only
|
|
|
|
else if (sslError == SSL_ERROR_WANT_ASYNC)
|
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: WANT_ASYNC (" << sslError << ").");
|
|
|
|
else if (sslError == SSL_ERROR_WANT_ASYNC_JOB)
|
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: WANT_ASYNC_JOB (" << sslError << ").");
|
|
|
|
#endif
|
|
|
|
else
|
2017-03-14 20:13:36 -05:00
|
|
|
LOG_TRC("Socket #" << getFD() << " SSL error: UNKNOWN (" << sslError << ").");
|
2017-03-12 13:33:16 -05:00
|
|
|
|
2017-02-21 19:54:13 -06:00
|
|
|
// The error is comming from BIO. Find out what happened.
|
|
|
|
const long bioError = ERR_get_error();
|
|
|
|
if (bioError == 0)
|
|
|
|
{
|
|
|
|
if (rc == 0)
|
|
|
|
{
|
2018-01-21 19:34:38 -06:00
|
|
|
// Socket closed. Not an error.
|
|
|
|
LOG_INF("Socket #" << getFD() << " SSL BIO error: closed (0).");
|
2017-02-21 19:54:13 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else if (rc == -1)
|
|
|
|
{
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_SYS("Socket #" << getFD() << " SSL BIO error: closed unexpectedly (-1).");
|
2017-02-21 19:54:13 -06:00
|
|
|
throw std::runtime_error("SSL Socket closed unexpectedly.");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_SYS("Socket #" << getFD() << " SSL BIO error: unknown (" << rc << ").");
|
2017-02-21 19:54:13 -06:00
|
|
|
throw std::runtime_error("SSL BIO reported error [" + std::to_string(rc) + "].");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char buf[512];
|
|
|
|
ERR_error_string_n(bioError, buf, sizeof(buf));
|
2017-03-12 13:33:16 -05:00
|
|
|
LOG_SYS("Socket #" << getFD() << " SSL BIO error: " << buf);
|
2017-02-21 19:54:13 -06:00
|
|
|
throw std::runtime_error(buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2019-12-10 05:11:20 -06:00
|
|
|
BIO* _bio;
|
2017-02-21 19:54:13 -06:00
|
|
|
SSL* _ssl;
|
|
|
|
/// During handshake SSL might want to read
|
|
|
|
/// on write, or write on read.
|
|
|
|
SslWantsTo _sslWantsTo;
|
|
|
|
/// We must do the handshake during the first
|
|
|
|
/// read or write in non-blocking.
|
|
|
|
bool _doHandshake;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|