poco/NetSSL_OpenSSL/src/SecureSocketImpl.cpp
2024-11-28 08:19:45 +01:00

666 lines
14 KiB
C++

//
// SecureSocketImpl.cpp
//
// Library: NetSSL_OpenSSL
// Package: SSLSockets
// Module: SecureSocketImpl
//
// Copyright (c) 2006-2010, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/Net/SecureSocketImpl.h"
#include "Poco/Net/SSLException.h"
#include "Poco/Net/SSLManager.h"
#include "Poco/Net/Context.h"
#include "Poco/Net/X509Certificate.h"
#include "Poco/Net/Utility.h"
#include "Poco/Net/SecureStreamSocket.h"
#include "Poco/Net/SecureStreamSocketImpl.h"
#include "Poco/Net/StreamSocketImpl.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/NumberFormatter.h"
#include "Poco/Format.h"
#include <openssl/x509v3.h>
#include <openssl/err.h>
using Poco::IOException;
using Poco::TimeoutException;
using Poco::InvalidArgumentException;
using Poco::NumberFormatter;
using Poco::Timespan;
namespace Poco {
namespace Net {
SecureSocketImpl::SecureSocketImpl(Poco::AutoPtr<SocketImpl> pSocketImpl, Context::Ptr pContext):
_pSSL(nullptr),
_pSocket(pSocketImpl),
_pContext(pContext),
_needHandshake(false)
{
poco_check_ptr (_pSocket);
poco_check_ptr (_pContext);
}
SecureSocketImpl::~SecureSocketImpl()
{
try
{
close();
reset();
}
catch (...)
{
poco_unexpected();
}
}
SocketImpl* SecureSocketImpl::acceptConnection(SocketAddress& clientAddr)
{
poco_assert (!_pSSL);
StreamSocket ss = _pSocket->acceptConnection(clientAddr);
Poco::AutoPtr<SecureStreamSocketImpl> pSecureStreamSocketImpl = new SecureStreamSocketImpl(static_cast<StreamSocketImpl*>(ss.impl()), _pContext);
pSecureStreamSocketImpl->acceptSSL();
pSecureStreamSocketImpl->duplicate();
return pSecureStreamSocketImpl;
}
void SecureSocketImpl::acceptSSL()
{
poco_assert (!_pSSL);
LockT l(_mutex);
BIO* pBIO = ::BIO_new(BIO_s_socket());
if (!pBIO) throw SSLException("Cannot create BIO object");
BIO_set_fd(pBIO, static_cast<int>(_pSocket->sockfd()), BIO_NOCLOSE);
_pSSL = ::SSL_new(_pContext->sslContext());
if (!_pSSL)
{
::BIO_free(pBIO);
throw SSLException("Cannot create SSL object");
}
#if OPENSSL_VERSION_NUMBER >= 0x1010100fL
/* TLS 1.3 server sends session tickets after a handhake as part of
* the SSL_accept(). If a client finishes all its job before server
* sends the tickets, SSL_accept() fails with EPIPE errno. Since we
* are not interested in a session resumption, we can not to send the
* tickets. */
if (1 != SSL_set_num_tickets(_pSSL, 0))
{
::BIO_free(pBIO);
throw SSLException("Cannot create SSL object");
}
//Otherwise we can perform two-way shutdown. Client must call SSL_read() before the final SSL_shutdown().
#endif
::SSL_set_bio(_pSSL, pBIO, pBIO);
::SSL_set_accept_state(_pSSL);
::SSL_set_ex_data(_pSSL, SSLManager::instance().socketIndex(), this);
_needHandshake = true;
}
void SecureSocketImpl::connect(const SocketAddress& address, bool performHandshake)
{
if (_pSSL) reset();
poco_assert (!_pSSL);
_pSocket->connect(address);
connectSSL(performHandshake);
}
void SecureSocketImpl::connect(const SocketAddress& address, const Poco::Timespan& timeout, bool performHandshake)
{
if (_pSSL) reset();
poco_assert (!_pSSL);
_pSocket->connect(address, timeout);
Poco::Timespan receiveTimeout = _pSocket->getReceiveTimeout();
Poco::Timespan sendTimeout = _pSocket->getSendTimeout();
_pSocket->setReceiveTimeout(timeout);
_pSocket->setSendTimeout(timeout);
connectSSL(performHandshake);
_pSocket->setReceiveTimeout(receiveTimeout);
_pSocket->setSendTimeout(sendTimeout);
}
void SecureSocketImpl::connectNB(const SocketAddress& address)
{
if (_pSSL) reset();
poco_assert (!_pSSL);
_pSocket->connectNB(address);
connectSSL(false);
}
void SecureSocketImpl::connectSSL(bool performHandshake)
{
poco_assert (!_pSSL);
poco_assert (_pSocket->initialized());
LockT l(_mutex);
::BIO* pBIO = ::BIO_new(BIO_s_socket());
if (!pBIO) throw SSLException("Cannot create SSL BIO object");
BIO_set_fd(pBIO, static_cast<int>(_pSocket->sockfd()), BIO_NOCLOSE);
_pSSL = ::SSL_new(_pContext->sslContext());
if (!_pSSL)
{
::BIO_free(pBIO);
throw SSLException("Cannot create SSL object");
}
::SSL_set_bio(_pSSL, pBIO, pBIO);
::SSL_set_ex_data(_pSSL, SSLManager::instance().socketIndex(), this);
if (!_peerHostName.empty())
{
SSL_set_tlsext_host_name(_pSSL, _peerHostName.c_str());
}
#if OPENSSL_VERSION_NUMBER >= 0x10001000L
if(_pContext->ocspStaplingResponseVerificationEnabled())
{
SSL_set_tlsext_status_type(_pSSL, TLSEXT_STATUSTYPE_ocsp);
}
#endif
if (_pSession && _pSession->isResumable())
{
::SSL_set_session(_pSSL, _pSession->sslSession());
}
try
{
if (performHandshake && _pSocket->getBlocking())
{
int ret = ::SSL_connect(_pSSL);
handleError(ret);
verifyPeerCertificate();
}
else
{
::SSL_set_connect_state(_pSSL);
_needHandshake = true;
}
}
catch (...)
{
::SSL_free(_pSSL);
_pSSL = nullptr;
throw;
}
}
void SecureSocketImpl::bind(const SocketAddress& address, bool reuseAddress)
{
poco_check_ptr (_pSocket);
_pSocket->bind(address, reuseAddress);
}
void SecureSocketImpl::bind(const SocketAddress& address, bool reuseAddress, bool reusePort)
{
poco_check_ptr (_pSocket);
_pSocket->bind(address, reuseAddress, reusePort);
}
void SecureSocketImpl::bind6(const SocketAddress& address, bool reuseAddress, bool ipV6Only)
{
poco_check_ptr (_pSocket);
_pSocket->bind6(address, reuseAddress, ipV6Only);
}
void SecureSocketImpl::bind6(const SocketAddress& address, bool reuseAddress, bool reusePort, bool ipV6Only)
{
poco_check_ptr (_pSocket);
_pSocket->bind6(address, reuseAddress, reusePort, ipV6Only);
}
void SecureSocketImpl::listen(int backlog)
{
poco_check_ptr (_pSocket);
_pSocket->listen(backlog);
}
int SecureSocketImpl::shutdown()
{
if (_pSSL)
{
UnLockT l(_mutex);
int shutdownState = ::SSL_get_shutdown(_pSSL);
bool shutdownSent = (shutdownState & SSL_SENT_SHUTDOWN) == SSL_SENT_SHUTDOWN;
if (!shutdownSent)
{
int rc = ::SSL_shutdown(_pSSL);
if (rc < 0) rc = handleError(rc);
l.unlock();
if (rc >= 0)
{
_pSocket->shutdownSend();
}
return rc;
}
else
{
return (shutdownState & SSL_RECEIVED_SHUTDOWN) == SSL_RECEIVED_SHUTDOWN;
}
}
return 1;
}
void SecureSocketImpl::close()
{
try
{
shutdown();
}
catch (...)
{
}
_pSocket->close();
}
int SecureSocketImpl::sendBytes(const void* buffer, int length, int flags)
{
poco_assert (_pSocket->initialized());
poco_check_ptr (_pSSL);
int rc;
LockT l(_mutex);
if (_needHandshake)
{
rc = completeHandshake();
if (rc == 1)
verifyPeerCertificate();
else if (rc == 0)
throw SSLConnectionUnexpectedlyClosedException();
else
return rc;
}
const auto sendTimeout = _pSocket->getSendTimeout();
Poco::Timestamp tsStart;
while (true)
{
rc = ::SSL_write(_pSSL, buffer, length);
if (!mustRetry(rc))
break;
if (tsStart.isElapsed(sendTimeout.totalMicroseconds()))
throw Poco::TimeoutException();
};
if (rc <= 0)
{
rc = handleError(rc);
if (rc == 0) throw SSLConnectionUnexpectedlyClosedException();
}
return rc;
}
int SecureSocketImpl::receiveBytes(void* buffer, int length, int flags)
{
poco_assert (_pSocket->initialized());
poco_check_ptr (_pSSL);
int rc;
LockT l(_mutex);
if (_needHandshake)
{
rc = completeHandshake();
if (rc == 1)
verifyPeerCertificate();
else
return rc;
}
const auto recvTimeout = _pSocket->getReceiveTimeout();
Poco::Timestamp tsStart;
while (true)
{
if (flags & MSG_PEEK)
{
rc = SSL_peek(_pSSL, buffer, length);
}
else
{
rc = SSL_read(_pSSL, buffer, length);
}
if (!mustRetry(rc))
break;
if (tsStart.isElapsed(recvTimeout.totalMicroseconds()))
throw Poco::TimeoutException();
};
if (rc <= 0)
{
return handleError(rc);
}
return rc;
}
int SecureSocketImpl::available() const
{
poco_check_ptr (_pSSL);
LockT l(_mutex);
return ::SSL_pending(_pSSL);
}
int SecureSocketImpl::completeHandshake()
{
poco_assert (_pSocket->initialized());
poco_check_ptr (_pSSL);
int rc;
const auto recvTimeout = _pSocket->getReceiveTimeout();
Poco::Timestamp tsStart;
while (true)
{
rc = ::SSL_do_handshake(_pSSL);
if (!mustRetry(rc))
break;
if (tsStart.isElapsed(recvTimeout.totalMicroseconds()))
throw Poco::TimeoutException();
};
if (rc <= 0)
{
return handleError(rc);
}
_needHandshake = false;
return rc;
}
void SecureSocketImpl::verifyPeerCertificate()
{
if (_peerHostName.empty())
verifyPeerCertificate(_pSocket->peerAddress().host().toString());
else
verifyPeerCertificate(_peerHostName);
}
void SecureSocketImpl::verifyPeerCertificate(const std::string& hostName)
{
long certErr = verifyPeerCertificateImpl(hostName);
if (certErr != X509_V_OK)
{
std::string msg = Utility::convertCertificateError(certErr);
throw CertificateValidationException("Unacceptable certificate from " + hostName, msg);
}
}
long SecureSocketImpl::verifyPeerCertificateImpl(const std::string& hostName)
{
Context::VerificationMode mode = _pContext->verificationMode();
if (mode == Context::VERIFY_NONE || !_pContext->extendedCertificateVerificationEnabled() ||
(mode != Context::VERIFY_STRICT && isLocalHost(hostName)))
{
return X509_V_OK;
}
::X509* pCert = ::SSL_get_peer_certificate(_pSSL);
if (pCert)
{
X509Certificate cert(pCert);
return cert.verify(hostName) ? X509_V_OK : X509_V_ERR_APPLICATION_VERIFICATION;
}
else return X509_V_OK;
}
bool SecureSocketImpl::isLocalHost(const std::string& hostName)
{
try
{
SocketAddress addr(hostName, 0);
return addr.host().isLoopback();
}
catch (const Poco::Exception&)
{
return false;
}
}
X509* SecureSocketImpl::peerCertificate() const
{
LockT l(_mutex);
if (_pSSL)
return ::SSL_get_peer_certificate(_pSSL);
else
return nullptr;
}
bool SecureSocketImpl::mustRetry(int rc)
{
if (rc <= 0)
{
static const Poco::Timespan pollTimeout(0, 100000);
int sslError = ::SSL_get_error(_pSSL, rc);
int socketError = _pSocket->lastError();
switch (sslError)
{
case SSL_ERROR_WANT_READ:
if (_pSocket->getBlocking())
{
_pSocket->poll(pollTimeout, Poco::Net::Socket::SELECT_READ);
return true;
}
break;
case SSL_ERROR_WANT_WRITE:
if (_pSocket->getBlocking())
{
_pSocket->poll(pollTimeout, Poco::Net::Socket::SELECT_WRITE);
return true;
}
break;
case SSL_ERROR_SYSCALL:
return socketError == POCO_EAGAIN || socketError == POCO_EINTR;
default:
return socketError == POCO_EINTR;
}
}
return false;
}
int SecureSocketImpl::handleError(int rc)
{
if (rc > 0) return rc;
int sslError = ::SSL_get_error(_pSSL, rc);
int socketError = SocketImpl::lastError();
switch (sslError)
{
case SSL_ERROR_ZERO_RETURN:
return 0;
case SSL_ERROR_WANT_READ:
return SecureStreamSocket::ERR_SSL_WANT_READ;
case SSL_ERROR_WANT_WRITE:
return SecureStreamSocket::ERR_SSL_WANT_WRITE;
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
case SSL_ERROR_WANT_X509_LOOKUP:
// these should not occur
poco_bugcheck();
return rc;
// SSL_GET_ERROR(3ossl):
// On an unexpected EOF, versions before OpenSSL 3.0 returned
// SSL_ERROR_SYSCALL, nothing was added to the error stack, and
// errno was 0. Since OpenSSL 3.0 the returned error is
// SSL_ERROR_SSL with a meaningful error on the error stack.
// However, we still need to check for socket errors in both
// cases with OpenSSL 3.0 or later.
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
case SSL_ERROR_SSL:
// fallthrough to handle socket errors first
#endif
case SSL_ERROR_SYSCALL:
if (socketError)
{
SocketImpl::error(socketError);
}
// fallthrough
default:
{
long lastError = ::ERR_get_error();
std::string msg;
if (lastError)
{
char buffer[256];
::ERR_error_string_n(lastError, buffer, sizeof(buffer));
msg = buffer;
}
// SSL_GET_ERROR(3ossl):
// On an unexpected EOF, versions before OpenSSL 3.0 returned
// SSL_ERROR_SYSCALL, nothing was added to the error stack, and
// errno was 0. Since OpenSSL 3.0 the returned error is
// SSL_ERROR_SSL with a meaningful error on the error stack.
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
if (sslError == SSL_ERROR_SSL)
#else
if (lastError == 0)
#endif
{
if (rc == 0)
{
// Most web browsers do this, don't report an error
if (_pContext->isForServerUse())
return 0;
else
throw SSLConnectionUnexpectedlyClosedException(msg);
}
else if (rc == -1)
{
throw SSLConnectionUnexpectedlyClosedException(msg);
}
else
{
SecureStreamSocketImpl::error(Poco::format("The BIO reported an error: %d", rc));
}
}
else if (lastError)
{
throw SSLException(msg);
}
}
break;
}
return rc;
}
void SecureSocketImpl::setPeerHostName(const std::string& peerHostName)
{
_peerHostName = peerHostName;
}
void SecureSocketImpl::reset()
{
if (_pSSL)
{
LockT l(_mutex);
::SSL_set_ex_data(_pSSL, SSLManager::instance().socketIndex(), nullptr);
::SSL_free(_pSSL);
_pSSL = nullptr;
}
}
void SecureSocketImpl::abort()
{
_pSocket->shutdown();
}
Session::Ptr SecureSocketImpl::currentSession()
{
return _pSession;
}
void SecureSocketImpl::useSession(Session::Ptr pSession)
{
_pSession = pSession;
}
bool SecureSocketImpl::sessionWasReused()
{
if (_pSSL)
{
LockT l(_mutex);
return ::SSL_session_reused(_pSSL) != 0;
}
return false;
}
int SecureSocketImpl::onSessionCreated(SSL* pSSL, SSL_SESSION* pSession)
{
void* pEx = ::SSL_get_ex_data(pSSL, SSLManager::instance().socketIndex());
if (pEx)
{
SecureSocketImpl* pThis = reinterpret_cast<SecureSocketImpl*>(pEx);
pThis->_pSession = new Session(pSession);
return 1;
}
else return 0;
}
} } // namespace Poco::Net