ladybird/Libraries/LibTLS/TLSv12.cpp
rmg-x 555c8139d8 LibTLS: Call close() when we receive SSL_ERROR_ZERO_RETURN
If we don't do this, then we endlessly spin trying to read data which
ends up in a deadlock.

The description for SSL_ERROR_ZERO_RETURN states:
> The TLS/SSL connection has been closed. If the protocol version is SSL
> 3.0 or TLS 1.0, this result code is returned only if a closure alert
> has occurred in the protocol, i.e., if the connection has been closed
> cleanly. Note that in this case SSL_ERROR_ZERO_RETURN does not
> necessarily indicate that the underlying transport has been closed.
2025-04-14 21:07:42 +02:00

252 lines
7.5 KiB
C++

/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/Promise.h>
#include <LibCrypto/OpenSSL.h>
#include <LibTLS/TLSv12.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
namespace TLS {
ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect(ByteString const& host, u16 port, Options options)
{
auto tcp_socket = TRY(Core::TCPSocket::connect(host, port));
return connect_internal(move(tcp_socket), host, move(options));
}
ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect(Core::SocketAddress const& address, ByteString const& host, Options options)
{
auto tcp_socket = TRY(Core::TCPSocket::connect(address));
return connect_internal(move(tcp_socket), host, move(options));
}
static void wait_for_activity(int sock, bool read)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
if (read)
select(sock + 1, &fds, nullptr, nullptr, nullptr);
else
select(sock + 1, nullptr, &fds, nullptr, nullptr);
}
void TLSv12::handle_fatal_error()
{
// If this error occurs then no further I/O operations should be performed
// on the connection and SSL_shutdown() must not be called.
SSL_free(m_ssl);
m_ssl = nullptr;
m_socket->close();
}
ErrorOr<Bytes> TLSv12::read_some(Bytes bytes)
{
if (!m_ssl)
return Error::from_string_literal("SSL connection is closed");
auto ret = SSL_read(m_ssl, bytes.data(), bytes.size());
if (ret <= 0) {
switch (SSL_get_error(m_ssl, ret)) {
case SSL_ERROR_ZERO_RETURN:
if (auto const pending = TRY(pending_bytes()); pending > 0)
return Error::from_errno(EAGAIN);
close();
return Bytes { bytes.data(), 0 };
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
return Error::from_errno(EAGAIN);
case SSL_ERROR_SSL:
handle_fatal_error();
ERR_print_errors_cb(openssl_print_errors, nullptr);
return Error::from_string_literal("Fatal SSL error reading from SSL connection");
case SSL_ERROR_SYSCALL:
handle_fatal_error();
return Error::from_errno(errno);
default:
return Error::from_string_literal("Failed reading from SSL connection");
}
}
return Bytes { bytes.data(), static_cast<unsigned long>(ret) };
}
ErrorOr<size_t> TLSv12::write_some(ReadonlyBytes bytes)
{
if (!m_ssl)
return Error::from_string_literal("SSL connection is closed");
auto ret = SSL_write(m_ssl, bytes.data(), bytes.size());
if (ret <= 0) {
switch (SSL_get_error(m_ssl, ret)) {
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
return Error::from_errno(EAGAIN);
case SSL_ERROR_SSL:
handle_fatal_error();
ERR_print_errors_cb(openssl_print_errors, nullptr);
return Error::from_string_literal("Fatal SSL error writing to SSL connection");
case SSL_ERROR_SYSCALL:
handle_fatal_error();
return Error::from_errno(errno);
default:
return Error::from_string_literal("Failed writing to SSL connection");
}
}
return ret;
}
bool TLSv12::is_eof() const
{
return m_socket->is_eof();
}
bool TLSv12::is_open() const
{
return m_socket->is_open();
}
void TLSv12::close()
{
if (m_ssl)
SSL_shutdown(m_ssl);
m_socket->close();
}
ErrorOr<size_t> TLSv12::pending_bytes() const
{
if (!m_ssl)
return Error::from_string_literal("SSL connection is closed");
return SSL_pending(m_ssl);
}
ErrorOr<bool> TLSv12::can_read_without_blocking(int timeout) const
{
if (!m_ssl)
return Error::from_string_literal("SSL connection is closed");
return m_socket->can_read_without_blocking(timeout);
}
ErrorOr<void> TLSv12::set_blocking(bool)
{
return Error::from_string_literal("Blocking mode cannot be changed after the SSL connection is established");
}
ErrorOr<void> TLSv12::set_close_on_exec(bool enabled)
{
return m_socket->set_close_on_exec(enabled);
}
TLSv12::TLSv12(NonnullOwnPtr<Core::TCPSocket> socket, SSL_CTX* ssl_ctx, SSL* ssl)
: m_ssl_ctx(ssl_ctx)
, m_ssl(ssl)
, m_socket(move(socket))
{
m_socket->on_ready_to_read = [this] {
// There is something to read on the underlying TCP connection. This doesn't mean there is actual data to read from the SSL connection.
// For example, we might have received an alert or a connection reset.
char buffer[1];
auto ret = SSL_peek(m_ssl, buffer, 1);
if (ret <= 0) {
switch (SSL_get_error(m_ssl, ret)) {
case SSL_ERROR_SSL:
case SSL_ERROR_SYSCALL:
handle_fatal_error();
break;
default:
break;
}
}
// Now that we handled possible fatal errors, we can notify the user that there is data to read.
if (on_ready_to_read)
on_ready_to_read();
};
}
TLSv12::~TLSv12()
{
SSL_free(m_ssl);
SSL_CTX_free(m_ssl_ctx);
}
ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect_internal(NonnullOwnPtr<Core::TCPSocket> socket, ByteString const& host, Options options)
{
TRY(socket->set_blocking(options.blocking));
auto* ssl_ctx = OPENSSL_TRY_PTR(SSL_CTX_new(TLS_client_method()));
ArmedScopeGuard free_ssl_ctx = [&] { SSL_CTX_free(ssl_ctx); };
// Configure the client to abort the handshake if certificate verification fails.
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
if (options.root_certificates_path.has_value()) {
auto path = options.root_certificates_path.value();
SSL_CTX_load_verify_file(ssl_ctx, path.characters());
} else {
// Use the default trusted certificate store
OPENSSL_TRY(SSL_CTX_set_default_verify_paths(ssl_ctx));
}
// Require a minimum TLS version of TLSv1.2.
OPENSSL_TRY(SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION));
auto* ssl = OPENSSL_TRY_PTR(SSL_new(ssl_ctx));
ArmedScopeGuard free_ssl = [&] { SSL_free(ssl); };
// Tell the server which hostname we are attempting to connect to in case the server supports multiple hosts.
OPENSSL_TRY(SSL_set_tlsext_host_name(ssl, host.characters()));
// Ensure we check that the server has supplied a certificate for the hostname that we were expecting.
OPENSSL_TRY(SSL_set1_host(ssl, host.characters()));
auto* bio = OPENSSL_TRY_PTR(BIO_new_socket(socket->fd(), 0));
// SSL takes ownership of the BIO and will handle freeing it
SSL_set_bio(ssl, bio, bio);
while (true) {
auto ret = SSL_connect(ssl);
if (ret == 1) {
// Successfully connected
break;
}
switch (SSL_get_error(ssl, ret)) {
case SSL_ERROR_WANT_READ:
wait_for_activity(socket->fd(), true);
break;
case SSL_ERROR_WANT_WRITE:
wait_for_activity(socket->fd(), false);
break;
case SSL_ERROR_SSL:
ERR_print_errors_cb(openssl_print_errors, nullptr);
return Error::from_string_literal("Fatal SSL error connecting to SSL server");
case SSL_ERROR_SYSCALL:
return Error::from_errno(errno);
default:
return Error::from_string_literal("Failed connecting to SSL server");
}
}
free_ssl.disarm();
free_ssl_ctx.disarm();
return adopt_own(*new TLSv12(move(socket), ssl_ctx, ssl));
}
}