Revert "LibTLS+Everywhere: Switch to using WolfSSL"

This reverts commit 8bb610b97a.
Linking wolfSSL seems to cause more legal trouble than it's worth due to
it being GPLv2, so let's undo this for now.
This commit is contained in:
Ali Mohammad Pur 2024-07-06 23:12:39 +02:00 committed by Andrew Kaster
parent 8d593bcfeb
commit e0465b8939
Notes: sideshowbarker 2024-07-17 03:25:24 +09:00
28 changed files with 3977 additions and 197 deletions

View file

@ -31,7 +31,8 @@ ErrorOr<ByteString> find_certificates(StringView serenity_resource_root)
ErrorOr<int> service_main(int ipc_socket) ErrorOr<int> service_main(int ipc_socket)
{ {
// Ensure the certificates are read out here. // Ensure the certificates are read out here.
TLS::WolfTLS::install_certificate_store_paths({ TRY(find_certificates(s_serenity_resource_root)) }); DefaultRootCACertificates::set_default_certificate_paths(Vector { TRY(find_certificates(s_serenity_resource_root)) });
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
Core::EventLoop event_loop; Core::EventLoop event_loop;

View file

@ -48,8 +48,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
// Ensure the certificates are read out here. // Ensure the certificates are read out here.
if (certificates.is_empty()) if (certificates.is_empty())
certificates.append(TRY(find_certificates(serenity_resource_root))); certificates.append(TRY(find_certificates(serenity_resource_root)));
DefaultRootCACertificates::set_default_certificate_paths(certificates.span());
TLS::WolfTLS::install_certificate_store_paths(move(certificates)); [[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
Core::EventLoop event_loop; Core::EventLoop event_loop;

View file

@ -530,6 +530,7 @@ if (BUILD_TESTING)
endforeach() endforeach()
# LibTLS needs a special working directory to find cacert.pem # LibTLS needs a special working directory to find cacert.pem
lagom_test(../../Tests/LibTLS/TestTLSHandshake.cpp LibTLS LIBS LibTLS LibCrypto)
lagom_test(../../Tests/LibTLS/TestTLSCertificateParser.cpp LibTLS LIBS LibTLS LibCrypto) lagom_test(../../Tests/LibTLS/TestTLSCertificateParser.cpp LibTLS LIBS LibTLS LibCrypto)
# The FLAC tests need a special working directory to find the test files # The FLAC tests need a special working directory to find the test files

View file

@ -21,8 +21,7 @@ At the moment, many core library support components are inherited from SerenityO
- LibWeb: Web rendering engine - LibWeb: Web rendering engine
- LibJS: JavaScript engine - LibJS: JavaScript engine
- LibWasm: WebAssembly implementation - LibWasm: WebAssembly implementation
- LibCrypto: Cryptography primitives - LibCrypto/LibTLS: Cryptography primitives and Transport Layer Security
- LibTLS: Some certificate parsing primitives
- LibHTTP: HTTP/1.1 client - LibHTTP: HTTP/1.1 client
- LibGfx: 2D Graphics Library, Image Decoding and Rendering - LibGfx: 2D Graphics Library, Image Decoding and Rendering
- LibArchive: Archive file format support - LibArchive: Archive file format support

View file

@ -1,5 +1,6 @@
set(TEST_SOURCES set(TEST_SOURCES
TestTLSCertificateParser.cpp TestTLSCertificateParser.cpp
TestTLSHandshake.cpp
) )
foreach(source IN LISTS TEST_SOURCES) foreach(source IN LISTS TEST_SOURCES)

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2021, Peter Bocan <me@pbocan.net>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Base64.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/EventLoop.h>
#include <LibCrypto/ASN1/ASN1.h>
#include <LibCrypto/ASN1/PEM.h>
#include <LibFileSystem/FileSystem.h>
#include <LibTLS/TLSv12.h>
#include <LibTest/TestCase.h>
static StringView ca_certs_file = "./cacert.pem"sv;
static int port = 443;
constexpr auto DEFAULT_SERVER = "www.google.com"sv;
static ByteBuffer operator""_b(char const* string, size_t length)
{
return ByteBuffer::copy(string, length).release_value();
}
ErrorOr<Vector<Certificate>> load_certificates();
ByteString locate_ca_certs_file();
ByteString locate_ca_certs_file()
{
if (FileSystem::exists(ca_certs_file)) {
return ca_certs_file;
}
auto on_target_path = ByteString("/etc/cacert.pem");
if (FileSystem::exists(on_target_path)) {
return on_target_path;
}
return "";
}
ErrorOr<Vector<Certificate>> load_certificates()
{
auto cacert_file = TRY(Core::File::open(locate_ca_certs_file(), Core::File::OpenMode::Read));
auto data = TRY(cacert_file->read_until_eof());
return TRY(DefaultRootCACertificates::parse_pem_root_certificate_authorities(data));
}
TEST_CASE(test_TLS_hello_handshake)
{
Core::EventLoop loop;
TLS::Options options;
options.set_root_certificates(TRY_OR_FAIL(load_certificates()));
options.set_alert_handler([&](TLS::AlertDescription) {
FAIL("Connection failure");
loop.quit(1);
});
options.set_finish_callback([&] {
loop.quit(0);
});
auto tls = TRY_OR_FAIL(TLS::TLSv12::connect(DEFAULT_SERVER, port, move(options)));
ByteBuffer contents;
tls->on_ready_to_read = [&] {
(void)TRY_OR_FAIL(tls->read_some(contents.must_get_bytes_for_writing(4 * KiB)));
loop.quit(0);
};
if (tls->write_until_depleted("GET / HTTP/1.1\r\nHost: "_b).is_error()) {
FAIL("write(0) failed");
return;
}
auto the_server = DEFAULT_SERVER;
if (tls->write_until_depleted(the_server.bytes()).is_error()) {
FAIL("write(1) failed");
return;
}
if (tls->write_until_depleted("\r\nConnection : close\r\n\r\n"_b).is_error()) {
FAIL("write(2) failed");
return;
}
loop.exec();
}

View file

@ -196,8 +196,6 @@ public:
virtual ~TCPSocket() override { close(); } virtual ~TCPSocket() override { close(); }
int fd() { return m_helper.fd(); }
private: private:
explicit TCPSocket(PreventSIGPIPE prevent_sigpipe = PreventSIGPIPE::Yes) explicit TCPSocket(PreventSIGPIPE prevent_sigpipe = PreventSIGPIPE::Yes)
: Socket(prevent_sigpipe) : Socket(prevent_sigpipe)

View file

@ -13,11 +13,11 @@
#include <AK/Types.h> #include <AK/Types.h>
#include <AK/Vector.h> #include <AK/Vector.h>
namespace Crypto::Authentication {
constexpr static auto IPAD = 0x36; constexpr static auto IPAD = 0x36;
constexpr static auto OPAD = 0x5c; constexpr static auto OPAD = 0x5c;
namespace Crypto::Authentication {
template<typename HashT> template<typename HashT>
class HMAC { class HMAC {
public: public:

View file

@ -10,8 +10,7 @@ namespace HTTP {
void HttpsJob::set_certificate(ByteString certificate, ByteString key) void HttpsJob::set_certificate(ByteString certificate, ByteString key)
{ {
(void)certificate; m_received_client_certificates = TLS::TLSv12::parse_pem_certificate(certificate.bytes(), key.bytes());
(void)key;
} }
} }

View file

@ -2,12 +2,16 @@ add_compile_options(-Wvla)
set(SOURCES set(SOURCES
Certificate.cpp Certificate.cpp
Handshake.cpp
HandshakeCertificate.cpp
HandshakeClient.cpp
HandshakeServer.cpp
Record.cpp
Socket.cpp
TLSv12.cpp TLSv12.cpp
) )
find_package(WolfSSL REQUIRED)
serenity_lib(LibTLS tls) serenity_lib(LibTLS tls)
target_link_libraries(LibTLS PRIVATE LibCore LibCrypto wolfssl::wolfssl) target_link_libraries(LibTLS PRIVATE LibCore LibCrypto LibFileSystem)
include(ca_certificates_data) include(ca_certificates_data)

View file

@ -293,6 +293,25 @@ public:
private: private:
Optional<bool> m_is_self_signed; Optional<bool> m_is_self_signed;
}; };
class DefaultRootCACertificates {
public:
DefaultRootCACertificates();
Vector<Certificate> const& certificates() const { return m_ca_certificates; }
static ErrorOr<Vector<Certificate>> parse_pem_root_certificate_authorities(ByteBuffer&);
static ErrorOr<Vector<Certificate>> load_certificates(Span<ByteString> custom_cert_paths = {});
static DefaultRootCACertificates& the();
static void set_default_certificate_paths(Span<ByteString> paths);
private:
Vector<Certificate> m_ca_certificates;
};
} }
using TLS::Certificate; using TLS::Certificate;
using TLS::DefaultRootCACertificates;

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Types.h>
#include <LibTLS/Extensions.h>
namespace TLS {
// Defined in RFC 5246 section 7.4.1.4.1
struct SignatureAndHashAlgorithm {
HashAlgorithm hash;
SignatureAlgorithm signature;
};
enum class KeyExchangeAlgorithm {
Invalid,
// Defined in RFC 5246 section 7.4.2 / RFC 4279 section 4
RSA_PSK,
// Defined in RFC 5246 section 7.4.3
DHE_DSS,
DHE_RSA,
DH_anon,
RSA,
DH_DSS,
DH_RSA,
// Defined in RFC 4492 section 2
ECDHE_RSA,
ECDH_ECDSA,
ECDH_RSA,
ECDHE_ECDSA,
ECDH_anon,
};
// Defined in RFC 5246 section 7.4.1.4.1
constexpr SignatureAlgorithm signature_for_key_exchange_algorithm(KeyExchangeAlgorithm algorithm)
{
switch (algorithm) {
case KeyExchangeAlgorithm::RSA:
case KeyExchangeAlgorithm::DHE_RSA:
case KeyExchangeAlgorithm::DH_RSA:
case KeyExchangeAlgorithm::RSA_PSK:
case KeyExchangeAlgorithm::ECDH_RSA:
case KeyExchangeAlgorithm::ECDHE_RSA:
return SignatureAlgorithm::RSA;
case KeyExchangeAlgorithm::DHE_DSS:
case KeyExchangeAlgorithm::DH_DSS:
return SignatureAlgorithm::DSA;
case KeyExchangeAlgorithm::ECDH_ECDSA:
case KeyExchangeAlgorithm::ECDHE_ECDSA:
return SignatureAlgorithm::ECDSA;
case KeyExchangeAlgorithm::DH_anon:
case KeyExchangeAlgorithm::ECDH_anon:
default:
return SignatureAlgorithm::ANONYMOUS;
}
}
enum class CipherAlgorithm {
Invalid,
AES_128_CBC,
AES_128_GCM,
AES_128_CCM,
AES_128_CCM_8,
AES_256_CBC,
AES_256_GCM,
};
constexpr size_t cipher_key_size(CipherAlgorithm algorithm)
{
switch (algorithm) {
case CipherAlgorithm::AES_128_CBC:
case CipherAlgorithm::AES_128_GCM:
case CipherAlgorithm::AES_128_CCM:
case CipherAlgorithm::AES_128_CCM_8:
return 128;
case CipherAlgorithm::AES_256_CBC:
case CipherAlgorithm::AES_256_GCM:
return 256;
case CipherAlgorithm::Invalid:
default:
return 0;
}
}
}

View file

@ -0,0 +1,559 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2022, Michiel Visser <opensource@webmichiel.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Endian.h>
#include <AK/Random.h>
#include <LibCore/Timer.h>
#include <LibCrypto/ASN1/DER.h>
#include <LibTLS/TLSv12.h>
namespace TLS {
ByteBuffer TLSv12::build_hello()
{
fill_with_random(m_context.local_random);
auto packet_version = (u16)m_context.options.version;
auto version = (u16)m_context.options.version;
PacketBuilder builder { ContentType::HANDSHAKE, packet_version };
builder.append(to_underlying(HandshakeType::CLIENT_HELLO));
// hello length (for later)
u8 dummy[3] = {};
builder.append(dummy, 3);
auto start_length = builder.length();
builder.append(version);
builder.append(m_context.local_random, sizeof(m_context.local_random));
builder.append(m_context.session_id_size);
if (m_context.session_id_size)
builder.append(m_context.session_id, m_context.session_id_size);
size_t extension_length = 0;
size_t alpn_length = 0;
size_t alpn_negotiated_length = 0;
// ALPN
if (!m_context.negotiated_alpn.is_empty()) {
alpn_negotiated_length = m_context.negotiated_alpn.length();
alpn_length = alpn_negotiated_length + 1;
extension_length += alpn_length + 6;
} else if (m_context.alpn.size()) {
for (auto& alpn : m_context.alpn) {
size_t length = alpn.length();
alpn_length += length + 1;
}
if (alpn_length)
extension_length += alpn_length + 6;
}
// Ciphers
builder.append((u16)(m_context.options.usable_cipher_suites.size() * sizeof(u16)));
for (auto suite : m_context.options.usable_cipher_suites)
builder.append((u16)suite);
// we don't like compression
VERIFY(!m_context.options.use_compression);
builder.append((u8)1);
builder.append((u8)m_context.options.use_compression);
// set SNI if we have one, and the user hasn't explicitly asked us to omit it.
auto sni_length = 0;
if (!m_context.extensions.SNI.is_empty() && m_context.options.use_sni)
sni_length = m_context.extensions.SNI.length();
auto elliptic_curves_length = 2 * m_context.options.elliptic_curves.size();
auto supported_ec_point_formats_length = m_context.options.supported_ec_point_formats.size();
bool supports_elliptic_curves = elliptic_curves_length && supported_ec_point_formats_length;
bool enable_extended_master_secret = m_context.options.enable_extended_master_secret;
// signature_algorithms: 2b extension ID, 2b extension length, 2b vector length, 2xN signatures and hashes
extension_length += 2 + 2 + 2 + 2 * m_context.options.supported_signature_algorithms.size();
if (sni_length)
extension_length += sni_length + 9;
// Only send elliptic_curves and ec_point_formats extensions if both are supported
if (supports_elliptic_curves)
extension_length += 6 + elliptic_curves_length + 5 + supported_ec_point_formats_length;
if (enable_extended_master_secret)
extension_length += 4;
builder.append((u16)extension_length);
if (sni_length) {
// SNI extension
builder.append((u16)ExtensionType::SERVER_NAME);
// extension length
builder.append((u16)(sni_length + 5));
// SNI length
builder.append((u16)(sni_length + 3));
// SNI type
builder.append((u8)0);
// SNI host length + value
builder.append((u16)sni_length);
builder.append((u8 const*)m_context.extensions.SNI.characters(), sni_length);
}
// signature_algorithms extension
builder.append((u16)ExtensionType::SIGNATURE_ALGORITHMS);
// Extension length
builder.append((u16)(2 + 2 * m_context.options.supported_signature_algorithms.size()));
// Vector count
builder.append((u16)(m_context.options.supported_signature_algorithms.size() * 2));
// Entries
for (auto& entry : m_context.options.supported_signature_algorithms) {
builder.append((u8)entry.hash);
builder.append((u8)entry.signature);
}
if (supports_elliptic_curves) {
// elliptic_curves extension
builder.append((u16)ExtensionType::SUPPORTED_GROUPS);
builder.append((u16)(2 + elliptic_curves_length));
builder.append((u16)elliptic_curves_length);
for (auto& curve : m_context.options.elliptic_curves)
builder.append((u16)curve);
// ec_point_formats extension
builder.append((u16)ExtensionType::EC_POINT_FORMATS);
builder.append((u16)(1 + supported_ec_point_formats_length));
builder.append((u8)supported_ec_point_formats_length);
for (auto& format : m_context.options.supported_ec_point_formats)
builder.append((u8)format);
}
if (enable_extended_master_secret) {
// extended_master_secret extension
builder.append((u16)ExtensionType::EXTENDED_MASTER_SECRET);
builder.append((u16)0);
}
if (alpn_length) {
// TODO
VERIFY_NOT_REACHED();
}
// set the "length" field of the packet
size_t remaining = builder.length() - start_length;
size_t payload_position = 6;
builder.set(payload_position, remaining / 0x10000);
remaining %= 0x10000;
builder.set(payload_position + 1, remaining / 0x100);
remaining %= 0x100;
builder.set(payload_position + 2, remaining);
auto packet = builder.build();
update_packet(packet);
return packet;
}
ByteBuffer TLSv12::build_change_cipher_spec()
{
PacketBuilder builder { ContentType::CHANGE_CIPHER_SPEC, m_context.options.version, 64 };
builder.append((u8)1);
auto packet = builder.build();
update_packet(packet);
m_context.local_sequence_number = 0;
return packet;
}
ByteBuffer TLSv12::build_handshake_finished()
{
PacketBuilder builder { ContentType::HANDSHAKE, m_context.options.version, 12 + 64 };
builder.append((u8)HandshakeType::FINISHED);
// RFC 5246 section 7.4.9: "In previous versions of TLS, the verify_data was always 12 octets
// long. In the current version of TLS, it depends on the cipher
// suite. Any cipher suite which does not explicitly specify
// verify_data_length has a verify_data_length equal to 12."
// Simplification: Assume that verify_data_length is always 12.
constexpr u32 verify_data_length = 12;
builder.append_u24(verify_data_length);
u8 out[verify_data_length];
auto outbuffer = Bytes { out, verify_data_length };
ByteBuffer dummy;
auto digest = m_context.handshake_hash.digest();
auto hashbuf = ReadonlyBytes { digest.immutable_data(), m_context.handshake_hash.digest_size() };
pseudorandom_function(outbuffer, m_context.master_key, (u8 const*)"client finished", 15, hashbuf, dummy);
builder.append(outbuffer);
auto packet = builder.build();
update_packet(packet);
return packet;
}
ssize_t TLSv12::handle_handshake_finished(ReadonlyBytes buffer, WritePacketStage& write_packets)
{
if (m_context.connection_status < ConnectionStatus::KeyExchange || m_context.connection_status == ConnectionStatus::Established) {
dbgln("unexpected finished message");
return (i8)Error::UnexpectedMessage;
}
write_packets = WritePacketStage::Initial;
if (buffer.size() < 3) {
return (i8)Error::NeedMoreData;
}
size_t index = 3;
u32 size = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
if (size < 12) {
dbgln_if(TLS_DEBUG, "finished packet smaller than minimum size: {}", size);
return (i8)Error::BrokenPacket;
}
if (size < buffer.size() - index) {
dbgln_if(TLS_DEBUG, "not enough data after length: {} > {}", size, buffer.size() - index);
return (i8)Error::NeedMoreData;
}
// TODO: Compare Hashes
dbgln_if(TLS_DEBUG, "FIXME: handle_handshake_finished :: Check message validity");
m_context.connection_status = ConnectionStatus::Established;
if (m_handshake_timeout_timer) {
// Disable the handshake timeout timer as handshake has been established.
m_handshake_timeout_timer->stop();
m_handshake_timeout_timer->remove_from_parent();
m_handshake_timeout_timer = nullptr;
}
if (on_connected)
on_connected();
return index + size;
}
ssize_t TLSv12::handle_handshake_payload(ReadonlyBytes vbuffer)
{
if (m_context.connection_status == ConnectionStatus::Established) {
dbgln_if(TLS_DEBUG, "Renegotiation attempt ignored");
// FIXME: We should properly say "NoRenegotiation", but that causes a handshake failure
// so we just roll with it and pretend that we _did_ renegotiate
// This will cause issues when we decide to have long-lasting connections, but
// we do not have those at the moment :^)
return 1;
}
auto buffer = vbuffer;
auto buffer_length = buffer.size();
auto original_length = buffer_length;
while (buffer_length >= 4 && !m_context.critical_error) {
ssize_t payload_res = 0;
if (buffer_length < 1)
return (i8)Error::NeedMoreData;
auto type = static_cast<HandshakeType>(buffer[0]);
auto write_packets { WritePacketStage::Initial };
size_t payload_size = buffer[1] * 0x10000 + buffer[2] * 0x100 + buffer[3] + 3;
dbgln_if(TLS_DEBUG, "payload size: {} buffer length: {}", payload_size, buffer_length);
if (payload_size + 1 > buffer_length)
return (i8)Error::NeedMoreData;
switch (type) {
case HandshakeType::HELLO_REQUEST_RESERVED:
if (m_context.handshake_messages[0] >= 1) {
dbgln("unexpected hello request message");
payload_res = (i8)Error::UnexpectedMessage;
break;
}
++m_context.handshake_messages[0];
dbgln("hello request (renegotiation?)");
if (m_context.connection_status == ConnectionStatus::Established) {
// renegotiation
payload_res = (i8)Error::NoRenegotiation;
} else {
// :shrug:
payload_res = (i8)Error::UnexpectedMessage;
}
break;
case HandshakeType::CLIENT_HELLO:
// FIXME: We only support client mode right now
if (m_context.is_server) {
VERIFY_NOT_REACHED();
}
payload_res = (i8)Error::UnexpectedMessage;
break;
case HandshakeType::SERVER_HELLO:
if (m_context.handshake_messages[2] >= 1) {
dbgln("unexpected server hello message");
payload_res = (i8)Error::UnexpectedMessage;
break;
}
++m_context.handshake_messages[2];
dbgln_if(TLS_DEBUG, "server hello");
if (m_context.is_server) {
dbgln("unsupported: server mode");
VERIFY_NOT_REACHED();
}
payload_res = handle_server_hello(buffer.slice(1, payload_size), write_packets);
break;
case HandshakeType::HELLO_VERIFY_REQUEST_RESERVED:
dbgln("unsupported: DTLS");
payload_res = (i8)Error::UnexpectedMessage;
break;
case HandshakeType::CERTIFICATE:
if (m_context.handshake_messages[4] >= 1) {
dbgln("unexpected certificate message");
payload_res = (i8)Error::UnexpectedMessage;
break;
}
++m_context.handshake_messages[4];
dbgln_if(TLS_DEBUG, "certificate");
if (m_context.connection_status == ConnectionStatus::Negotiating) {
if (m_context.is_server) {
dbgln("unsupported: server mode");
VERIFY_NOT_REACHED();
}
payload_res = handle_certificate(buffer.slice(1, payload_size));
} else {
payload_res = (i8)Error::UnexpectedMessage;
}
break;
case HandshakeType::SERVER_KEY_EXCHANGE_RESERVED:
if (m_context.handshake_messages[5] >= 1) {
dbgln("unexpected server key exchange message");
payload_res = (i8)Error::UnexpectedMessage;
break;
}
++m_context.handshake_messages[5];
dbgln_if(TLS_DEBUG, "server key exchange");
if (m_context.is_server) {
dbgln("unsupported: server mode");
VERIFY_NOT_REACHED();
} else {
payload_res = handle_server_key_exchange(buffer.slice(1, payload_size));
}
break;
case HandshakeType::CERTIFICATE_REQUEST:
if (m_context.handshake_messages[6] >= 1) {
dbgln("unexpected certificate request message");
payload_res = (i8)Error::UnexpectedMessage;
break;
}
++m_context.handshake_messages[6];
if (m_context.is_server) {
dbgln("invalid request");
dbgln("unsupported: server mode");
VERIFY_NOT_REACHED();
} else {
// we do not support "certificate request"
dbgln("certificate request");
if (on_tls_certificate_request)
on_tls_certificate_request(*this);
m_context.client_verified = VerificationNeeded;
}
break;
case HandshakeType::SERVER_HELLO_DONE_RESERVED:
if (m_context.handshake_messages[7] >= 1) {
dbgln("unexpected server hello done message");
payload_res = (i8)Error::UnexpectedMessage;
break;
}
++m_context.handshake_messages[7];
dbgln_if(TLS_DEBUG, "server hello done");
if (m_context.is_server) {
dbgln("unsupported: server mode");
VERIFY_NOT_REACHED();
} else {
payload_res = handle_server_hello_done(buffer.slice(1, payload_size));
if (payload_res > 0)
write_packets = WritePacketStage::ClientHandshake;
}
break;
case HandshakeType::CERTIFICATE_VERIFY:
if (m_context.handshake_messages[8] >= 1) {
dbgln("unexpected certificate verify message");
payload_res = (i8)Error::UnexpectedMessage;
break;
}
++m_context.handshake_messages[8];
dbgln_if(TLS_DEBUG, "certificate verify");
if (m_context.connection_status == ConnectionStatus::KeyExchange) {
payload_res = handle_certificate_verify(buffer.slice(1, payload_size));
} else {
payload_res = (i8)Error::UnexpectedMessage;
}
break;
case HandshakeType::CLIENT_KEY_EXCHANGE_RESERVED:
if (m_context.handshake_messages[9] >= 1) {
dbgln("unexpected client key exchange message");
payload_res = (i8)Error::UnexpectedMessage;
break;
}
++m_context.handshake_messages[9];
dbgln_if(TLS_DEBUG, "client key exchange");
if (m_context.is_server) {
dbgln("unsupported: server mode");
VERIFY_NOT_REACHED();
} else {
payload_res = (i8)Error::UnexpectedMessage;
}
break;
case HandshakeType::FINISHED:
m_context.cached_handshake.clear();
if (m_context.handshake_messages[10] >= 1) {
dbgln("unexpected finished message");
payload_res = (i8)Error::UnexpectedMessage;
break;
}
++m_context.handshake_messages[10];
dbgln_if(TLS_DEBUG, "finished");
payload_res = handle_handshake_finished(buffer.slice(1, payload_size), write_packets);
if (payload_res > 0) {
memset(m_context.handshake_messages, 0, sizeof(m_context.handshake_messages));
}
break;
default:
dbgln("message type not understood: {}", enum_to_string(type));
return (i8)Error::NotUnderstood;
}
if (type != HandshakeType::HELLO_REQUEST_RESERVED) {
update_hash(buffer.slice(0, payload_size + 1), 0);
}
// if something went wrong, send an alert about it
if (payload_res < 0) {
switch ((Error)payload_res) {
case Error::UnexpectedMessage: {
auto packet = build_alert(true, (u8)AlertDescription::UNEXPECTED_MESSAGE);
write_packet(packet);
break;
}
case Error::CompressionNotSupported: {
auto packet = build_alert(true, (u8)AlertDescription::DECOMPRESSION_FAILURE_RESERVED);
write_packet(packet);
break;
}
case Error::BrokenPacket: {
auto packet = build_alert(true, (u8)AlertDescription::DECODE_ERROR);
write_packet(packet);
break;
}
case Error::NotVerified: {
auto packet = build_alert(true, (u8)AlertDescription::BAD_RECORD_MAC);
write_packet(packet);
break;
}
case Error::BadCertificate: {
auto packet = build_alert(true, (u8)AlertDescription::BAD_CERTIFICATE);
write_packet(packet);
break;
}
case Error::UnsupportedCertificate: {
auto packet = build_alert(true, (u8)AlertDescription::UNSUPPORTED_CERTIFICATE);
write_packet(packet);
break;
}
case Error::NoCommonCipher: {
auto packet = build_alert(true, (u8)AlertDescription::INSUFFICIENT_SECURITY);
write_packet(packet);
break;
}
case Error::NotUnderstood:
case Error::OutOfMemory: {
auto packet = build_alert(true, (u8)AlertDescription::INTERNAL_ERROR);
write_packet(packet);
break;
}
case Error::NoRenegotiation: {
auto packet = build_alert(true, (u8)AlertDescription::NO_RENEGOTIATION_RESERVED);
write_packet(packet);
break;
}
case Error::DecryptionFailed: {
auto packet = build_alert(true, (u8)AlertDescription::DECRYPTION_FAILED_RESERVED);
write_packet(packet);
break;
}
case Error::NotSafe: {
auto packet = build_alert(true, (u8)AlertDescription::DECRYPT_ERROR);
write_packet(packet);
break;
}
case Error::NeedMoreData:
// Ignore this, as it's not an "error"
dbgln_if(TLS_DEBUG, "More data needed");
break;
default:
dbgln("Unknown TLS::Error with value {}", payload_res);
VERIFY_NOT_REACHED();
break;
}
if (payload_res < 0)
return payload_res;
}
switch (write_packets) {
case WritePacketStage::Initial:
// nothing to write
break;
case WritePacketStage::ClientHandshake:
if (m_context.client_verified == VerificationNeeded) {
dbgln_if(TLS_DEBUG, "> Client Certificate");
auto packet = build_certificate();
write_packet(packet);
m_context.client_verified = Verified;
}
{
dbgln_if(TLS_DEBUG, "> Key exchange");
auto packet = build_client_key_exchange();
write_packet(packet);
}
{
dbgln_if(TLS_DEBUG, "> change cipher spec");
auto packet = build_change_cipher_spec();
write_packet(packet);
}
m_context.cipher_spec_set = 1;
m_context.local_sequence_number = 0;
{
dbgln_if(TLS_DEBUG, "> client finished");
auto packet = build_handshake_finished();
write_packet(packet);
}
m_context.cipher_spec_set = 0;
break;
case WritePacketStage::ServerHandshake:
// server handshake
dbgln("UNSUPPORTED: Server mode");
VERIFY_NOT_REACHED();
break;
case WritePacketStage::Finished:
// finished
{
dbgln_if(TLS_DEBUG, "> change cipher spec");
auto packet = build_change_cipher_spec();
write_packet(packet);
}
{
dbgln_if(TLS_DEBUG, "> client finished");
auto packet = build_handshake_finished();
write_packet(packet);
}
m_context.connection_status = ConnectionStatus::Established;
break;
}
payload_size++;
buffer_length -= payload_size;
buffer = buffer.slice(payload_size, buffer_length);
}
return original_length;
}
}

View file

@ -0,0 +1,110 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Endian.h>
#include <AK/Random.h>
#include <LibCore/Timer.h>
#include <LibCrypto/ASN1/DER.h>
#include <LibTLS/TLSv12.h>
namespace TLS {
ssize_t TLSv12::handle_certificate(ReadonlyBytes buffer)
{
ssize_t res = 0;
if (buffer.size() < 3) {
dbgln_if(TLS_DEBUG, "not enough certificate header data");
return (i8)Error::NeedMoreData;
}
u32 certificate_total_length = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
dbgln_if(TLS_DEBUG, "total length: {}", certificate_total_length);
if (certificate_total_length <= 4)
return 3 * certificate_total_length;
res += 3;
if (certificate_total_length > buffer.size() - res) {
dbgln_if(TLS_DEBUG, "not enough data for claimed total cert length");
return (i8)Error::NeedMoreData;
}
size_t size = certificate_total_length;
bool valid_certificate = false;
while (size > 0) {
if (buffer.size() - res < 3) {
dbgln_if(TLS_DEBUG, "not enough data for certificate length");
return (i8)Error::NeedMoreData;
}
size_t certificate_size = buffer[res] * 0x10000 + buffer[res + 1] * 0x100 + buffer[res + 2];
res += 3;
if (buffer.size() - res < certificate_size) {
dbgln_if(TLS_DEBUG, "not enough data for certificate body");
return (i8)Error::NeedMoreData;
}
auto res_cert = res;
auto remaining = certificate_size;
do {
if (remaining <= 3) {
dbgln("Ran out of data");
break;
}
if (buffer.size() < (size_t)res_cert + 3) {
dbgln("not enough data to read cert size ({} < {})", buffer.size(), res_cert + 3);
break;
}
size_t certificate_size_specific = buffer[res_cert] * 0x10000 + buffer[res_cert + 1] * 0x100 + buffer[res_cert + 2];
res_cert += 3;
remaining -= 3;
if (certificate_size_specific > remaining) {
dbgln("invalid certificate size (expected {} but got {})", remaining, certificate_size_specific);
break;
}
remaining -= certificate_size_specific;
auto certificate = Certificate::parse_certificate(buffer.slice(res_cert, certificate_size_specific), false);
if (!certificate.is_error()) {
m_context.certificates.empend(certificate.value());
valid_certificate = true;
} else {
dbgln("Failed to parse client cert: {}", certificate.error());
dbgln("{:hex-dump}", buffer.slice(res_cert, certificate_size_specific));
dbgln("");
}
res_cert += certificate_size_specific;
} while (remaining > 0);
if (remaining) {
dbgln("extraneous {} bytes left over after parsing certificates", remaining);
}
size -= certificate_size + 3;
res += certificate_size;
}
if (!valid_certificate)
return (i8)Error::UnsupportedCertificate;
if ((size_t)res != buffer.size())
dbgln("some data left unread: {} bytes out of {}", res, buffer.size());
return res;
}
ssize_t TLSv12::handle_certificate_verify(ReadonlyBytes)
{
dbgln("FIXME: parse_verify");
return 0;
}
}

View file

@ -0,0 +1,412 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2022, Michiel Visser <opensource@webmichiel.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Hex.h>
#include <AK/Random.h>
#include <LibCrypto/ASN1/DER.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibCrypto/NumberTheory/ModularFunctions.h>
#include <LibTLS/TLSv12.h>
namespace TLS {
bool TLSv12::expand_key()
{
u8 key[192]; // soooooooo many constants
auto key_buffer = Bytes { key, sizeof(key) };
auto is_aead = this->is_aead();
if (m_context.master_key.size() == 0) {
dbgln("expand_key() with empty master key");
return false;
}
auto key_size = key_length();
VERIFY(key_size);
auto mac_size = mac_length();
auto iv_size = iv_length();
pseudorandom_function(
key_buffer,
m_context.master_key,
(u8 const*)"key expansion", 13,
ReadonlyBytes { m_context.remote_random, sizeof(m_context.remote_random) },
ReadonlyBytes { m_context.local_random, sizeof(m_context.local_random) });
size_t offset = 0;
if (is_aead) {
iv_size = 4; // Explicit IV size.
} else {
memcpy(m_context.crypto.local_mac, key + offset, mac_size);
offset += mac_size;
memcpy(m_context.crypto.remote_mac, key + offset, mac_size);
offset += mac_size;
}
auto client_key = key + offset;
offset += key_size;
auto server_key = key + offset;
offset += key_size;
auto client_iv = key + offset;
offset += iv_size;
auto server_iv = key + offset;
offset += iv_size;
if constexpr (TLS_DEBUG) {
dbgln("client key");
print_buffer(client_key, key_size);
dbgln("server key");
print_buffer(server_key, key_size);
dbgln("client iv");
print_buffer(client_iv, iv_size);
dbgln("server iv");
print_buffer(server_iv, iv_size);
if (!is_aead) {
dbgln("client mac key");
print_buffer(m_context.crypto.local_mac, mac_size);
dbgln("server mac key");
print_buffer(m_context.crypto.remote_mac, mac_size);
}
}
switch (get_cipher_algorithm(m_context.cipher)) {
case CipherAlgorithm::AES_128_CBC:
case CipherAlgorithm::AES_256_CBC: {
VERIFY(!is_aead);
memcpy(m_context.crypto.local_iv, client_iv, iv_size);
memcpy(m_context.crypto.remote_iv, server_iv, iv_size);
m_cipher_local = Crypto::Cipher::AESCipher::CBCMode(ReadonlyBytes { client_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246);
m_cipher_remote = Crypto::Cipher::AESCipher::CBCMode(ReadonlyBytes { server_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246);
break;
}
case CipherAlgorithm::AES_128_GCM:
case CipherAlgorithm::AES_256_GCM: {
VERIFY(is_aead);
memcpy(m_context.crypto.local_aead_iv, client_iv, iv_size);
memcpy(m_context.crypto.remote_aead_iv, server_iv, iv_size);
m_cipher_local = Crypto::Cipher::AESCipher::GCMMode(ReadonlyBytes { client_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246);
m_cipher_remote = Crypto::Cipher::AESCipher::GCMMode(ReadonlyBytes { server_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246);
break;
}
case CipherAlgorithm::AES_128_CCM:
dbgln("Requested unimplemented AES CCM cipher");
TODO();
case CipherAlgorithm::AES_128_CCM_8:
dbgln("Requested unimplemented AES CCM-8 block cipher");
TODO();
default:
dbgln("Requested unknown block cipher");
VERIFY_NOT_REACHED();
}
m_context.crypto.created = 1;
return true;
}
bool TLSv12::compute_master_secret_from_pre_master_secret(size_t length)
{
if (m_context.premaster_key.size() == 0 || length < 48) {
dbgln("there's no way I can make a master secret like this");
dbgln("I'd like to talk to your manager about this length of {}", length);
return false;
}
if (m_context.master_key.try_resize(length).is_error()) {
dbgln("Couldn't allocate enough space for the master key :(");
return false;
}
if (m_context.extensions.extended_master_secret) {
Crypto::Hash::Manager handshake_hash_copy = m_context.handshake_hash.copy();
auto digest = handshake_hash_copy.digest();
auto session_hash = ReadonlyBytes { digest.immutable_data(), handshake_hash_copy.digest_size() };
pseudorandom_function(
m_context.master_key,
m_context.premaster_key,
(u8 const*)"extended master secret", 22,
session_hash,
{});
} else {
pseudorandom_function(
m_context.master_key,
m_context.premaster_key,
(u8 const*)"master secret", 13,
ReadonlyBytes { m_context.local_random, sizeof(m_context.local_random) },
ReadonlyBytes { m_context.remote_random, sizeof(m_context.remote_random) });
}
m_context.premaster_key.clear();
if constexpr (TLS_DEBUG) {
dbgln("master key:");
print_buffer(m_context.master_key);
}
expand_key();
return true;
}
void TLSv12::build_rsa_pre_master_secret(PacketBuilder& builder)
{
u8 random_bytes[48];
size_t bytes = 48;
fill_with_random(random_bytes);
// remove zeros from the random bytes
for (size_t i = 0; i < bytes; ++i) {
if (!random_bytes[i])
random_bytes[i--] = get_random<u8>();
}
if (m_context.is_server) {
dbgln("Server mode not supported");
return;
} else {
*(u16*)random_bytes = AK::convert_between_host_and_network_endian((u16)ProtocolVersion::VERSION_1_2);
}
auto premaster_key_result = ByteBuffer::copy(random_bytes, bytes);
if (premaster_key_result.is_error()) {
dbgln("RSA premaster key generation failed, not enough memory");
return;
}
m_context.premaster_key = premaster_key_result.release_value();
// RFC5246 section 7.4.2: The sender's certificate MUST come first in the list.
auto& certificate = m_context.certificates.first();
if constexpr (TLS_DEBUG) {
dbgln("PreMaster secret");
print_buffer(m_context.premaster_key);
}
Crypto::PK::RSA_PKCS1_EME rsa(certificate.public_key.rsa.modulus(), 0, certificate.public_key.rsa.public_exponent());
Vector<u8, 32> out;
out.resize(rsa.output_size());
auto outbuf = out.span();
rsa.encrypt(m_context.premaster_key, outbuf);
if constexpr (TLS_DEBUG) {
dbgln("Encrypted: ");
print_buffer(outbuf);
}
builder.append_u24(outbuf.size() + 2);
builder.append((u16)outbuf.size());
builder.append(outbuf);
}
void TLSv12::build_dhe_rsa_pre_master_secret(PacketBuilder& builder)
{
auto& dh = m_context.server_diffie_hellman_params;
auto dh_p = Crypto::UnsignedBigInteger::import_data(dh.p.data(), dh.p.size());
auto dh_g = Crypto::UnsignedBigInteger::import_data(dh.g.data(), dh.g.size());
auto dh_Ys = Crypto::UnsignedBigInteger::import_data(dh.Ys.data(), dh.Ys.size());
auto dh_key_size = dh.p.size();
auto dh_random = Crypto::NumberTheory::random_number(0, dh_p);
auto dh_Yc = Crypto::NumberTheory::ModularPower(dh_g, dh_random, dh_p);
auto dh_Yc_bytes_result = ByteBuffer::create_uninitialized(dh_key_size);
if (dh_Yc_bytes_result.is_error()) {
dbgln("Failed to build DHE_RSA premaster secret: not enough memory");
return;
}
auto dh_Yc_bytes = dh_Yc_bytes_result.release_value();
dh_Yc.export_data(dh_Yc_bytes);
auto premaster_key = Crypto::NumberTheory::ModularPower(dh_Ys, dh_random, dh_p);
auto premaster_key_result = ByteBuffer::create_uninitialized(dh_key_size);
if (premaster_key_result.is_error()) {
dbgln("Failed to build DHE_RSA premaster secret: not enough memory");
return;
}
m_context.premaster_key = premaster_key_result.release_value();
premaster_key.export_data(m_context.premaster_key, true);
dh.p.clear();
dh.g.clear();
dh.Ys.clear();
if constexpr (TLS_DEBUG) {
dbgln("dh_random: {}", dh_random.to_base_deprecated(16));
dbgln("dh_Yc: {:hex-dump}", (ReadonlyBytes)dh_Yc_bytes);
dbgln("premaster key: {:hex-dump}", (ReadonlyBytes)m_context.premaster_key);
}
builder.append_u24(dh_key_size + 2);
builder.append((u16)dh_key_size);
builder.append(dh_Yc_bytes);
}
void TLSv12::build_ecdhe_rsa_pre_master_secret(PacketBuilder& builder)
{
// Create a random private key
auto private_key_result = m_context.server_key_exchange_curve->generate_private_key();
if (private_key_result.is_error()) {
dbgln("Failed to build ECDHE_RSA premaster secret: not enough memory");
return;
}
auto private_key = private_key_result.release_value();
// Calculate the public key from the private key
auto public_key_result = m_context.server_key_exchange_curve->generate_public_key(private_key);
if (public_key_result.is_error()) {
dbgln("Failed to build ECDHE_RSA premaster secret: not enough memory");
return;
}
auto public_key = public_key_result.release_value();
// Calculate the shared point by multiplying the client private key and the server public key
ReadonlyBytes server_public_key_bytes = m_context.server_diffie_hellman_params.p;
auto shared_point_result = m_context.server_key_exchange_curve->compute_coordinate(private_key, server_public_key_bytes);
if (shared_point_result.is_error()) {
dbgln("Failed to build ECDHE_RSA premaster secret: not enough memory");
return;
}
auto shared_point = shared_point_result.release_value();
// Derive the premaster key from the shared point
auto premaster_key_result = m_context.server_key_exchange_curve->derive_premaster_key(shared_point);
if (premaster_key_result.is_error()) {
dbgln("Failed to build ECDHE_RSA premaster secret: not enough memory");
return;
}
m_context.premaster_key = premaster_key_result.release_value();
if constexpr (TLS_DEBUG) {
dbgln("Build ECDHE_RSA pre master secret");
dbgln("client private key: {:hex-dump}", (ReadonlyBytes)private_key);
dbgln("client public key: {:hex-dump}", (ReadonlyBytes)public_key);
dbgln("premaster key: {:hex-dump}", (ReadonlyBytes)m_context.premaster_key);
}
builder.append_u24(public_key.size() + 1);
builder.append((u8)public_key.size());
builder.append(public_key);
}
ByteBuffer TLSv12::build_certificate()
{
PacketBuilder builder { ContentType::HANDSHAKE, m_context.options.version };
Vector<Certificate const&> certificates;
Vector<Certificate>* local_certificates = nullptr;
if (m_context.is_server) {
dbgln("Unsupported: Server mode");
VERIFY_NOT_REACHED();
} else {
local_certificates = &m_context.client_certificates;
}
constexpr size_t der_length_delta = 3;
constexpr size_t certificate_vector_header_size = 3;
size_t total_certificate_size = 0;
for (size_t i = 0; i < local_certificates->size(); ++i) {
auto& certificate = local_certificates->at(i);
if (!certificate.der.is_empty()) {
total_certificate_size += certificate.der.size() + der_length_delta;
// FIXME: Check for and respond with only the requested certificate types.
if (true) {
certificates.append(certificate);
}
}
}
builder.append((u8)HandshakeType::CERTIFICATE);
if (!total_certificate_size) {
dbgln_if(TLS_DEBUG, "No certificates, sending empty certificate message");
builder.append_u24(certificate_vector_header_size);
builder.append_u24(total_certificate_size);
} else {
builder.append_u24(total_certificate_size + certificate_vector_header_size); // 3 bytes for header
builder.append_u24(total_certificate_size);
for (auto& certificate : certificates) {
if (!certificate.der.is_empty()) {
builder.append_u24(certificate.der.size());
builder.append(certificate.der.bytes());
}
}
}
auto packet = builder.build();
update_packet(packet);
return packet;
}
ByteBuffer TLSv12::build_client_key_exchange()
{
bool chain_verified = m_context.verify_chain(m_context.extensions.SNI);
if (!chain_verified) {
dbgln("certificate verification failed :(");
alert(AlertLevel::FATAL, AlertDescription::BAD_CERTIFICATE);
return {};
}
PacketBuilder builder { ContentType::HANDSHAKE, m_context.options.version };
builder.append((u8)HandshakeType::CLIENT_KEY_EXCHANGE_RESERVED);
switch (get_key_exchange_algorithm(m_context.cipher)) {
case KeyExchangeAlgorithm::RSA:
build_rsa_pre_master_secret(builder);
break;
case KeyExchangeAlgorithm::DHE_DSS:
dbgln("Client key exchange for DHE_DSS is not implemented");
TODO();
break;
case KeyExchangeAlgorithm::DH_DSS:
case KeyExchangeAlgorithm::DH_RSA:
dbgln("Client key exchange for DH algorithms is not implemented");
TODO();
break;
case KeyExchangeAlgorithm::DHE_RSA:
build_dhe_rsa_pre_master_secret(builder);
break;
case KeyExchangeAlgorithm::DH_anon:
dbgln("Client key exchange for DH_anon is not implemented");
TODO();
break;
case KeyExchangeAlgorithm::ECDHE_RSA:
case KeyExchangeAlgorithm::ECDHE_ECDSA:
build_ecdhe_rsa_pre_master_secret(builder);
break;
case KeyExchangeAlgorithm::ECDH_ECDSA:
case KeyExchangeAlgorithm::ECDH_RSA:
case KeyExchangeAlgorithm::ECDH_anon:
dbgln("Client key exchange for ECDHE algorithms is not implemented");
TODO();
break;
default:
dbgln("Unknown client key exchange algorithm");
VERIFY_NOT_REACHED();
break;
}
m_context.connection_status = ConnectionStatus::KeyExchange;
auto packet = builder.build();
update_packet(packet);
if (!compute_master_secret_from_pre_master_secret(48)) {
dbgln("oh noes we could not derive a master key :(");
}
return packet;
}
}

View file

@ -0,0 +1,530 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2022, Michiel Visser <opensource@webmichiel.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Endian.h>
#include <AK/Random.h>
#include <LibCore/Timer.h>
#include <LibCrypto/ASN1/DER.h>
#include <LibCrypto/Curves/Ed25519.h>
#include <LibCrypto/Curves/EllipticCurve.h>
#include <LibCrypto/Curves/SECPxxxr1.h>
#include <LibCrypto/Curves/X25519.h>
#include <LibCrypto/Curves/X448.h>
#include <LibCrypto/PK/Code/EMSA_PKCS1_V1_5.h>
#include <LibTLS/TLSv12.h>
namespace TLS {
ssize_t TLSv12::handle_server_hello(ReadonlyBytes buffer, WritePacketStage& write_packets)
{
write_packets = WritePacketStage::Initial;
if (m_context.connection_status != ConnectionStatus::Disconnected && m_context.connection_status != ConnectionStatus::Renegotiating) {
dbgln("unexpected hello message");
return (i8)Error::UnexpectedMessage;
}
ssize_t res = 0;
size_t min_hello_size = 41;
if (min_hello_size > buffer.size()) {
dbgln("need more data");
return (i8)Error::NeedMoreData;
}
size_t following_bytes = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
res += 3;
if (buffer.size() - res < following_bytes) {
dbgln("not enough data after header: {} < {}", buffer.size() - res, following_bytes);
return (i8)Error::NeedMoreData;
}
if (buffer.size() - res < 2) {
dbgln("not enough data for version");
return (i8)Error::NeedMoreData;
}
auto version = static_cast<ProtocolVersion>(AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res))));
res += 2;
if (!supports_version(version))
return (i8)Error::NotSafe;
memcpy(m_context.remote_random, buffer.offset_pointer(res), sizeof(m_context.remote_random));
res += sizeof(m_context.remote_random);
u8 session_length = buffer[res++];
if (buffer.size() - res < session_length) {
dbgln("not enough data for session id");
return (i8)Error::NeedMoreData;
}
if (session_length && session_length <= 32) {
memcpy(m_context.session_id, buffer.offset_pointer(res), session_length);
m_context.session_id_size = session_length;
if constexpr (TLS_DEBUG) {
dbgln("Remote session ID:");
print_buffer(ReadonlyBytes { m_context.session_id, session_length });
}
} else {
m_context.session_id_size = 0;
}
res += session_length;
if (buffer.size() - res < 2) {
dbgln("not enough data for cipher suite listing");
return (i8)Error::NeedMoreData;
}
auto cipher = static_cast<CipherSuite>(AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res))));
res += 2;
if (!supports_cipher(cipher)) {
m_context.cipher = CipherSuite::TLS_NULL_WITH_NULL_NULL;
dbgln("No supported cipher could be agreed upon");
return (i8)Error::NoCommonCipher;
}
m_context.cipher = cipher;
dbgln_if(TLS_DEBUG, "Cipher: {}", enum_to_string(cipher));
// Simplification: We only support handshake hash functions via HMAC
m_context.handshake_hash.initialize(hmac_hash());
// Compression method
if (buffer.size() - res < 1)
return (i8)Error::NeedMoreData;
u8 compression = buffer[res++];
if (compression != 0)
return (i8)Error::CompressionNotSupported;
if (m_context.connection_status != ConnectionStatus::Renegotiating)
m_context.connection_status = ConnectionStatus::Negotiating;
if (m_context.is_server) {
dbgln("unsupported: server mode");
write_packets = WritePacketStage::ServerHandshake;
}
// Presence of extensions is determined by availability of bytes after compression_method
if (buffer.size() - res >= 2) {
auto extensions_bytes_total = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res += 2)));
dbgln_if(TLS_DEBUG, "Extensions bytes total: {}", extensions_bytes_total);
}
while (buffer.size() - res >= 4) {
auto extension_type = (ExtensionType)AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res)));
res += 2;
u16 extension_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res)));
res += 2;
dbgln_if(TLS_DEBUG, "Extension {} with length {}", enum_to_string(extension_type), extension_length);
if (buffer.size() - res < extension_length)
return (i8)Error::NeedMoreData;
if (extension_type == ExtensionType::SERVER_NAME) {
// RFC6066 section 3: SNI extension_data can be empty in the server hello
if (extension_length > 0) {
// ServerNameList total size
if (buffer.size() - res < 2)
return (i8)Error::NeedMoreData;
auto sni_name_list_bytes = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res += 2)));
dbgln_if(TLS_DEBUG, "SNI: expecting ServerNameList of {} bytes", sni_name_list_bytes);
// Exactly one ServerName should be present
if (buffer.size() - res < 3)
return (i8)Error::NeedMoreData;
auto sni_name_type = (NameType)(*(u8 const*)buffer.offset_pointer(res++));
auto sni_name_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res += 2)));
if (sni_name_type != NameType::HOST_NAME)
return (i8)Error::NotUnderstood;
if (sizeof(sni_name_type) + sizeof(sni_name_length) + sni_name_length != sni_name_list_bytes)
return (i8)Error::BrokenPacket;
// Read out the host_name
if (buffer.size() - res < sni_name_length)
return (i8)Error::NeedMoreData;
m_context.extensions.SNI = ByteString { (char const*)buffer.offset_pointer(res), sni_name_length };
res += sni_name_length;
dbgln("SNI host_name: {}", m_context.extensions.SNI);
}
} else if (extension_type == ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION && m_context.alpn.size()) {
if (buffer.size() - res > 2) {
auto alpn_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res)));
if (alpn_length && alpn_length <= extension_length - 2) {
u8 const* alpn = buffer.offset_pointer(res + 2);
size_t alpn_position = 0;
while (alpn_position < alpn_length) {
u8 alpn_size = alpn[alpn_position++];
if (alpn_size + alpn_position >= extension_length)
break;
ByteString alpn_str { (char const*)alpn + alpn_position, alpn_length };
if (alpn_size && m_context.alpn.contains_slow(alpn_str)) {
m_context.negotiated_alpn = alpn_str;
dbgln("negotiated alpn: {}", alpn_str);
break;
}
alpn_position += alpn_length;
if (!m_context.is_server) // server hello must contain one ALPN
break;
}
}
}
res += extension_length;
} else if (extension_type == ExtensionType::SIGNATURE_ALGORITHMS) {
dbgln("supported signatures: ");
print_buffer(buffer.slice(res, extension_length));
res += extension_length;
// FIXME: what are we supposed to do here?
} else if (extension_type == ExtensionType::EC_POINT_FORMATS) {
// RFC8422 section 5.2: A server that selects an ECC cipher suite in response to a ClientHello message
// including a Supported Point Formats Extension appends this extension (along with others) to its
// ServerHello message, enumerating the point formats it can parse. The Supported Point Formats Extension,
// when used, MUST contain the value 0 (uncompressed) as one of the items in the list of point formats.
//
// The current implementation only supports uncompressed points, and the server is required to support
// uncompressed points. Therefore, this extension can be safely ignored as it should always inform us
// that the server supports uncompressed points.
res += extension_length;
} else if (extension_type == ExtensionType::EXTENDED_MASTER_SECRET) {
m_context.extensions.extended_master_secret = true;
res += extension_length;
} else {
dbgln("Encountered unknown extension {} with length {}", enum_to_string(extension_type), extension_length);
res += extension_length;
}
}
return res;
}
ssize_t TLSv12::handle_server_hello_done(ReadonlyBytes buffer)
{
if (buffer.size() < 3)
return (i8)Error::NeedMoreData;
size_t size = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
if (buffer.size() - 3 < size)
return (i8)Error::NeedMoreData;
return size + 3;
}
ByteBuffer TLSv12::build_server_key_exchange()
{
dbgln("FIXME: build_server_key_exchange");
return {};
}
ssize_t TLSv12::handle_server_key_exchange(ReadonlyBytes buffer)
{
switch (get_key_exchange_algorithm(m_context.cipher)) {
case KeyExchangeAlgorithm::RSA:
case KeyExchangeAlgorithm::DH_DSS:
case KeyExchangeAlgorithm::DH_RSA:
// RFC 5246 section 7.4.3. Server Key Exchange Message
// It is not legal to send the server key exchange message for RSA, DH_DSS, DH_RSA
dbgln("Server key exchange received for RSA, DH_DSS or DH_RSA is not legal");
return (i8)Error::UnexpectedMessage;
case KeyExchangeAlgorithm::DHE_DSS:
dbgln("Server key exchange for DHE_DSS is not implemented");
TODO();
break;
case KeyExchangeAlgorithm::DHE_RSA:
return handle_dhe_rsa_server_key_exchange(buffer);
case KeyExchangeAlgorithm::DH_anon:
dbgln("Server key exchange for DH_anon is not implemented");
TODO();
break;
case KeyExchangeAlgorithm::ECDHE_RSA:
return handle_ecdhe_rsa_server_key_exchange(buffer);
case KeyExchangeAlgorithm::ECDHE_ECDSA:
return handle_ecdhe_ecdsa_server_key_exchange(buffer);
case KeyExchangeAlgorithm::ECDH_ECDSA:
case KeyExchangeAlgorithm::ECDH_RSA:
case KeyExchangeAlgorithm::ECDH_anon:
dbgln("Server key exchange for ECDHE algorithms is not implemented");
TODO();
break;
default:
dbgln("Unknown server key exchange algorithm");
VERIFY_NOT_REACHED();
break;
}
return 0;
}
ssize_t TLSv12::handle_dhe_rsa_server_key_exchange(ReadonlyBytes buffer)
{
auto dh_p_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(3)));
auto dh_p = buffer.slice(5, dh_p_length);
auto p_result = ByteBuffer::copy(dh_p);
if (p_result.is_error()) {
dbgln("dhe_rsa_server_key_exchange failed: Not enough memory");
return (i8)Error::OutOfMemory;
}
m_context.server_diffie_hellman_params.p = p_result.release_value();
auto dh_g_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(5 + dh_p_length)));
auto dh_g = buffer.slice(7 + dh_p_length, dh_g_length);
auto g_result = ByteBuffer::copy(dh_g);
if (g_result.is_error()) {
dbgln("dhe_rsa_server_key_exchange failed: Not enough memory");
return (i8)Error::OutOfMemory;
}
m_context.server_diffie_hellman_params.g = g_result.release_value();
auto dh_Ys_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(7 + dh_p_length + dh_g_length)));
auto dh_Ys = buffer.slice(9 + dh_p_length + dh_g_length, dh_Ys_length);
auto Ys_result = ByteBuffer::copy(dh_Ys);
if (Ys_result.is_error()) {
dbgln("dhe_rsa_server_key_exchange failed: Not enough memory");
return (i8)Error::OutOfMemory;
}
m_context.server_diffie_hellman_params.Ys = Ys_result.release_value();
if constexpr (TLS_DEBUG) {
dbgln("dh_p: {:hex-dump}", dh_p);
dbgln("dh_g: {:hex-dump}", dh_g);
dbgln("dh_Ys: {:hex-dump}", dh_Ys);
}
auto server_key_info = buffer.slice(3, 6 + dh_p_length + dh_g_length + dh_Ys_length);
auto signature = buffer.slice(9 + dh_p_length + dh_g_length + dh_Ys_length);
return verify_rsa_server_key_exchange(server_key_info, signature);
}
ssize_t TLSv12::handle_ecdhe_server_key_exchange(ReadonlyBytes buffer, u8& server_public_key_length)
{
if (buffer.size() < 7)
return (i8)Error::NeedMoreData;
auto curve_type = buffer[3];
if (curve_type != (u8)ECCurveType::NAMED_CURVE)
return (i8)Error::NotUnderstood;
auto curve = static_cast<SupportedGroup>(AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(4))));
if (!m_context.options.elliptic_curves.contains_slow(curve))
return (i8)Error::NotUnderstood;
switch ((SupportedGroup)curve) {
case SupportedGroup::X25519:
m_context.server_key_exchange_curve = make<Crypto::Curves::X25519>();
break;
case SupportedGroup::X448:
m_context.server_key_exchange_curve = make<Crypto::Curves::X448>();
break;
case SupportedGroup::SECP256R1:
m_context.server_key_exchange_curve = make<Crypto::Curves::SECP256r1>();
break;
case SupportedGroup::SECP384R1:
m_context.server_key_exchange_curve = make<Crypto::Curves::SECP384r1>();
break;
default:
return (i8)Error::NotUnderstood;
}
server_public_key_length = buffer[6];
if (server_public_key_length != m_context.server_key_exchange_curve->key_size())
return (i8)Error::NotUnderstood;
if (buffer.size() < 7u + server_public_key_length)
return (i8)Error::NeedMoreData;
auto server_public_key = buffer.slice(7, server_public_key_length);
auto server_public_key_copy_result = ByteBuffer::copy(server_public_key);
if (server_public_key_copy_result.is_error()) {
dbgln("ecdhe_rsa_server_key_exchange failed: Not enough memory");
return (i8)Error::OutOfMemory;
}
m_context.server_diffie_hellman_params.p = server_public_key_copy_result.release_value();
if constexpr (TLS_DEBUG) {
dbgln("ECDHE server public key: {:hex-dump}", server_public_key);
}
return 0;
}
ssize_t TLSv12::handle_ecdhe_rsa_server_key_exchange(ReadonlyBytes buffer)
{
u8 server_public_key_length;
if (auto result = handle_ecdhe_server_key_exchange(buffer, server_public_key_length)) {
return result;
}
auto server_key_info = buffer.slice(3, 4 + server_public_key_length);
auto signature = buffer.slice(7 + server_public_key_length);
return verify_rsa_server_key_exchange(server_key_info, signature);
}
ssize_t TLSv12::verify_rsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer)
{
auto signature_hash = signature_buffer[0];
auto signature_algorithm = static_cast<SignatureAlgorithm>(signature_buffer[1]);
if (signature_algorithm != SignatureAlgorithm::RSA) {
dbgln("verify_rsa_server_key_exchange failed: Signature algorithm is not RSA, instead {}", enum_to_string(signature_algorithm));
return (i8)Error::NotUnderstood;
}
auto signature_length = AK::convert_between_host_and_network_endian(ByteReader::load16(signature_buffer.offset_pointer(2)));
auto signature = signature_buffer.slice(4, signature_length);
if (m_context.certificates.is_empty()) {
dbgln("verify_rsa_server_key_exchange failed: Attempting to verify signature without certificates");
return (i8)Error::NotSafe;
}
// RFC5246 section 7.4.2: The sender's certificate MUST come first in the list.
auto certificate_public_key = m_context.certificates.first().public_key;
Crypto::PK::RSAPrivateKey dummy_private_key;
auto rsa = Crypto::PK::RSA(certificate_public_key.rsa, dummy_private_key);
auto signature_verify_buffer_result = ByteBuffer::create_uninitialized(signature_length);
if (signature_verify_buffer_result.is_error()) {
dbgln("verify_rsa_server_key_exchange failed: Not enough memory");
return (i8)Error::OutOfMemory;
}
auto signature_verify_buffer = signature_verify_buffer_result.release_value();
auto signature_verify_bytes = signature_verify_buffer.bytes();
rsa.verify(signature, signature_verify_bytes);
auto message_result = ByteBuffer::create_uninitialized(64 + server_key_info_buffer.size());
if (message_result.is_error()) {
dbgln("verify_rsa_server_key_exchange failed: Not enough memory");
return (i8)Error::OutOfMemory;
}
auto message = message_result.release_value();
message.overwrite(0, m_context.local_random, 32);
message.overwrite(32, m_context.remote_random, 32);
message.overwrite(64, server_key_info_buffer.data(), server_key_info_buffer.size());
Crypto::Hash::HashKind hash_kind;
switch ((HashAlgorithm)signature_hash) {
case HashAlgorithm::SHA1:
hash_kind = Crypto::Hash::HashKind::SHA1;
break;
case HashAlgorithm::SHA256:
hash_kind = Crypto::Hash::HashKind::SHA256;
break;
case HashAlgorithm::SHA384:
hash_kind = Crypto::Hash::HashKind::SHA384;
break;
case HashAlgorithm::SHA512:
hash_kind = Crypto::Hash::HashKind::SHA512;
break;
default:
dbgln("verify_rsa_server_key_exchange failed: Hash algorithm is not SHA1/256/384/512, instead {}", signature_hash);
return (i8)Error::NotUnderstood;
}
auto pkcs1 = Crypto::PK::EMSA_PKCS1_V1_5<Crypto::Hash::Manager>(hash_kind);
auto verification = pkcs1.verify(message, signature_verify_bytes, signature_length * 8);
if (verification == Crypto::VerificationConsistency::Inconsistent) {
dbgln("verify_rsa_server_key_exchange failed: Verification of signature inconsistent");
return (i8)Error::NotSafe;
}
return 0;
}
ssize_t TLSv12::handle_ecdhe_ecdsa_server_key_exchange(ReadonlyBytes buffer)
{
u8 server_public_key_length;
if (auto result = handle_ecdhe_server_key_exchange(buffer, server_public_key_length)) {
return result;
}
auto server_key_info = buffer.slice(3, 4 + server_public_key_length);
auto signature = buffer.slice(7 + server_public_key_length);
return verify_ecdsa_server_key_exchange(server_key_info, signature);
}
ssize_t TLSv12::verify_ecdsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer)
{
auto signature_hash = signature_buffer[0];
auto signature_algorithm = signature_buffer[1];
if (signature_algorithm != (u8)SignatureAlgorithm::ECDSA) {
dbgln("verify_ecdsa_server_key_exchange failed: Signature algorithm is not ECDSA, instead {}", signature_algorithm);
return (i8)Error::NotUnderstood;
}
auto signature_length = AK::convert_between_host_and_network_endian(ByteReader::load16(signature_buffer.offset_pointer(2)));
auto signature = signature_buffer.slice(4, signature_length);
if (m_context.certificates.is_empty()) {
dbgln("verify_ecdsa_server_key_exchange failed: Attempting to verify signature without certificates");
return (i8)Error::NotSafe;
}
ReadonlyBytes server_point = m_context.certificates.first().public_key.raw_key;
auto message_result = ByteBuffer::create_uninitialized(64 + server_key_info_buffer.size());
if (message_result.is_error()) {
dbgln("verify_ecdsa_server_key_exchange failed: Not enough memory");
return (i8)Error::OutOfMemory;
}
auto message = message_result.release_value();
message.overwrite(0, m_context.local_random, 32);
message.overwrite(32, m_context.remote_random, 32);
message.overwrite(64, server_key_info_buffer.data(), server_key_info_buffer.size());
Crypto::Hash::HashKind hash_kind;
switch ((HashAlgorithm)signature_hash) {
case HashAlgorithm::SHA256:
hash_kind = Crypto::Hash::HashKind::SHA256;
break;
case HashAlgorithm::SHA384:
hash_kind = Crypto::Hash::HashKind::SHA384;
break;
case HashAlgorithm::SHA512:
hash_kind = Crypto::Hash::HashKind::SHA512;
break;
default:
dbgln("verify_ecdsa_server_key_exchange failed: Hash algorithm is not SHA256/384/512, instead {}", signature_hash);
return (i8)Error::NotUnderstood;
}
ErrorOr<bool> res = AK::Error::from_errno(ENOTSUP);
auto& public_key = m_context.certificates.first().public_key;
switch (public_key.algorithm.ec_parameters) {
case SupportedGroup::SECP256R1: {
Crypto::Hash::Manager manager(hash_kind);
manager.update(message);
auto digest = manager.digest();
Crypto::Curves::SECP256r1 curve;
res = curve.verify(digest.bytes(), server_point, signature);
break;
}
case SupportedGroup::SECP384R1: {
Crypto::Hash::Manager manager(hash_kind);
manager.update(message);
auto digest = manager.digest();
Crypto::Curves::SECP384r1 curve;
res = curve.verify(digest.bytes(), server_point, signature);
break;
}
default: {
dbgln("verify_ecdsa_server_key_exchange failed: Server certificate public key algorithm is not supported: {}", to_underlying(public_key.algorithm.ec_parameters));
break;
}
}
if (res.is_error()) {
dbgln("verify_ecdsa_server_key_exchange failed: {}", res.error());
return (i8)Error::NotUnderstood;
}
bool verification_ok = res.release_value();
if (!verification_ok) {
dbgln("verify_ecdsa_server_key_exchange failed: Verification of signature failed");
return (i8)Error::NotSafe;
}
return 0;
}
}

View file

@ -0,0 +1,572 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/Endian.h>
#include <AK/MemoryStream.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Timer.h>
#include <LibTLS/TLSv12.h>
namespace TLS {
ByteBuffer TLSv12::build_alert(bool critical, u8 code)
{
PacketBuilder builder(ContentType::ALERT, (u16)m_context.options.version);
builder.append((u8)(critical ? AlertLevel::FATAL : AlertLevel::WARNING));
builder.append(code);
if (critical)
m_context.critical_error = code;
auto packet = builder.build();
update_packet(packet);
return packet;
}
void TLSv12::alert(AlertLevel level, AlertDescription code)
{
auto the_alert = build_alert(level == AlertLevel::FATAL, (u8)code);
write_packet(the_alert, true);
MUST(flush());
}
void TLSv12::write_packet(ByteBuffer& packet, bool immediately)
{
auto schedule_or_perform_flush = [&](bool immediate) {
if (m_context.connection_status > ConnectionStatus::Disconnected) {
if (!m_has_scheduled_write_flush && !immediate) {
dbgln_if(TLS_DEBUG, "Scheduling write of {}", m_context.tls_buffer.size());
Core::deferred_invoke([this] { write_into_socket(); });
m_has_scheduled_write_flush = true;
} else {
// multiple packet are available, let's flush some out
dbgln_if(TLS_DEBUG, "Flushing scheduled write of {}", m_context.tls_buffer.size());
write_into_socket();
// the deferred invoke is still in place
m_has_scheduled_write_flush = true;
}
}
};
// Record size limit is 18432 bytes, leave some headroom and flush at 16K.
if (m_context.tls_buffer.size() + packet.size() > 16 * KiB)
schedule_or_perform_flush(true);
if (m_context.tls_buffer.try_append(packet.data(), packet.size()).is_error()) {
// Toooooo bad, drop the record on the ground.
return;
}
schedule_or_perform_flush(immediately);
}
void TLSv12::update_packet(ByteBuffer& packet)
{
u32 header_size = 5;
ByteReader::store(packet.offset_pointer(3), AK::convert_between_host_and_network_endian((u16)(packet.size() - header_size)));
if (packet[0] != (u8)ContentType::CHANGE_CIPHER_SPEC) {
if (packet[0] == (u8)ContentType::HANDSHAKE && packet.size() > header_size) {
auto handshake_type = static_cast<HandshakeType>(packet[header_size]);
if (handshake_type != HandshakeType::HELLO_REQUEST_RESERVED && handshake_type != HandshakeType::HELLO_VERIFY_REQUEST_RESERVED) {
update_hash(packet.bytes(), header_size);
}
}
if (m_context.cipher_spec_set && m_context.crypto.created) {
size_t length = packet.size() - header_size;
size_t block_size = 0;
size_t padding = 0;
size_t mac_size = 0;
m_cipher_local.visit(
[&](Empty&) { VERIFY_NOT_REACHED(); },
[&](Crypto::Cipher::AESCipher::GCMMode& gcm) {
VERIFY(is_aead());
block_size = gcm.cipher().block_size();
padding = 0;
mac_size = 0; // AEAD provides its own authentication scheme.
},
[&](Crypto::Cipher::AESCipher::CBCMode& cbc) {
VERIFY(!is_aead());
block_size = cbc.cipher().block_size();
// If the length is already a multiple a block_size,
// an entire block of padding is added.
// In short, we _never_ have no padding.
mac_size = mac_length();
length += mac_size;
padding = block_size - length % block_size;
length += padding;
});
if (m_context.crypto.created == 1) {
// `buffer' will continue to be encrypted
auto buffer_result = ByteBuffer::create_uninitialized(length);
if (buffer_result.is_error()) {
dbgln("LibTLS: Failed to allocate enough memory");
VERIFY_NOT_REACHED();
}
auto buffer = buffer_result.release_value();
size_t buffer_position = 0;
auto iv_size = iv_length();
// copy the packet, sans the header
buffer.overwrite(buffer_position, packet.offset_pointer(header_size), packet.size() - header_size);
buffer_position += packet.size() - header_size;
ByteBuffer ct;
m_cipher_local.visit(
[&](Empty&) { VERIFY_NOT_REACHED(); },
[&](Crypto::Cipher::AESCipher::GCMMode& gcm) {
VERIFY(is_aead());
// We need enough space for a header, the data, a tag, and the IV
auto ct_buffer_result = ByteBuffer::create_uninitialized(length + header_size + iv_size + 16);
if (ct_buffer_result.is_error()) {
dbgln("LibTLS: Failed to allocate enough memory for the ciphertext");
VERIFY_NOT_REACHED();
}
ct = ct_buffer_result.release_value();
// copy the header over
ct.overwrite(0, packet.data(), header_size - 2);
// AEAD AAD (13)
// Seq. no (8)
// content type (1)
// version (2)
// length (2)
u8 aad[13];
Bytes aad_bytes { aad, 13 };
FixedMemoryStream aad_stream { aad_bytes };
u64 seq_no = AK::convert_between_host_and_network_endian(m_context.local_sequence_number);
u16 len = AK::convert_between_host_and_network_endian((u16)(packet.size() - header_size));
MUST(aad_stream.write_value(seq_no)); // sequence number
MUST(aad_stream.write_until_depleted(packet.bytes().slice(0, 3))); // content-type + version
MUST(aad_stream.write_value(len)); // length
VERIFY(MUST(aad_stream.tell()) == MUST(aad_stream.size()));
// AEAD IV (12)
// IV (4)
// (Nonce) (8)
// -- Our GCM impl takes 16 bytes
// zero (4)
u8 iv[16];
Bytes iv_bytes { iv, 16 };
Bytes { m_context.crypto.local_aead_iv, 4 }.copy_to(iv_bytes);
fill_with_random(iv_bytes.slice(4, 8));
memset(iv_bytes.offset(12), 0, 4);
// write the random part of the iv out
iv_bytes.slice(4, 8).copy_to(ct.bytes().slice(header_size));
// Write the encrypted data and the tag
gcm.encrypt(
packet.bytes().slice(header_size, length),
ct.bytes().slice(header_size + 8, length),
iv_bytes,
aad_bytes,
ct.bytes().slice(header_size + 8 + length, 16));
VERIFY(header_size + 8 + length + 16 == ct.size());
},
[&](Crypto::Cipher::AESCipher::CBCMode& cbc) {
VERIFY(!is_aead());
// We need enough space for a header, iv_length bytes of IV and whatever the packet contains
auto ct_buffer_result = ByteBuffer::create_uninitialized(length + header_size + iv_size);
if (ct_buffer_result.is_error()) {
dbgln("LibTLS: Failed to allocate enough memory for the ciphertext");
VERIFY_NOT_REACHED();
}
ct = ct_buffer_result.release_value();
// copy the header over
ct.overwrite(0, packet.data(), header_size - 2);
// get the appropriate HMAC value for the entire packet
auto mac = hmac_message(packet, {}, mac_size, true);
// write the MAC
buffer.overwrite(buffer_position, mac.data(), mac.size());
buffer_position += mac.size();
// Apply the padding (a packet MUST always be padded)
memset(buffer.offset_pointer(buffer_position), padding - 1, padding);
buffer_position += padding;
VERIFY(buffer_position == buffer.size());
auto iv_buffer_result = ByteBuffer::create_uninitialized(iv_size);
if (iv_buffer_result.is_error()) {
dbgln("LibTLS: Failed to allocate memory for IV");
VERIFY_NOT_REACHED();
}
auto iv = iv_buffer_result.release_value();
fill_with_random(iv);
// write it into the ciphertext portion of the message
ct.overwrite(header_size, iv.data(), iv.size());
VERIFY(header_size + iv_size + length == ct.size());
VERIFY(length % block_size == 0);
// get a block to encrypt into
auto view = ct.bytes().slice(header_size + iv_size, length);
cbc.encrypt(buffer, view, iv);
});
// store the correct ciphertext length into the packet
u16 ct_length = (u16)ct.size() - header_size;
ByteReader::store(ct.offset_pointer(header_size - 2), AK::convert_between_host_and_network_endian(ct_length));
// replace the packet with the ciphertext
packet = ct;
}
}
}
++m_context.local_sequence_number;
}
void TLSv12::update_hash(ReadonlyBytes message, size_t header_size)
{
dbgln_if(TLS_DEBUG, "Update hash with message of size {}", message.size());
m_context.handshake_hash.update(message.slice(header_size));
}
void TLSv12::ensure_hmac(size_t digest_size, bool local)
{
if (local && m_hmac_local)
return;
if (!local && m_hmac_remote)
return;
auto hash_kind = Crypto::Hash::HashKind::None;
switch (digest_size) {
case Crypto::Hash::SHA1::DigestSize:
hash_kind = Crypto::Hash::HashKind::SHA1;
break;
case Crypto::Hash::SHA256::DigestSize:
hash_kind = Crypto::Hash::HashKind::SHA256;
break;
case Crypto::Hash::SHA384::DigestSize:
hash_kind = Crypto::Hash::HashKind::SHA384;
break;
case Crypto::Hash::SHA512::DigestSize:
hash_kind = Crypto::Hash::HashKind::SHA512;
break;
default:
dbgln("Failed to find a suitable hash for size {}", digest_size);
break;
}
auto hmac = make<Crypto::Authentication::HMAC<Crypto::Hash::Manager>>(ReadonlyBytes { local ? m_context.crypto.local_mac : m_context.crypto.remote_mac, digest_size }, hash_kind);
if (local)
m_hmac_local = move(hmac);
else
m_hmac_remote = move(hmac);
}
ByteBuffer TLSv12::hmac_message(ReadonlyBytes buf, Optional<ReadonlyBytes> const buf2, size_t mac_length, bool local)
{
u64 sequence_number = AK::convert_between_host_and_network_endian(local ? m_context.local_sequence_number : m_context.remote_sequence_number);
ensure_hmac(mac_length, local);
auto& hmac = local ? *m_hmac_local : *m_hmac_remote;
if constexpr (TLS_DEBUG) {
dbgln("========================= PACKET DATA ==========================");
print_buffer((u8 const*)&sequence_number, sizeof(u64));
print_buffer(buf.data(), buf.size());
if (buf2.has_value())
print_buffer(buf2.value().data(), buf2.value().size());
dbgln("========================= PACKET DATA ==========================");
}
hmac.update((u8 const*)&sequence_number, sizeof(u64));
hmac.update(buf);
if (buf2.has_value() && buf2.value().size()) {
hmac.update(buf2.value());
}
auto digest = hmac.digest();
auto mac_result = ByteBuffer::copy(digest.immutable_data(), digest.data_length());
if (mac_result.is_error()) {
dbgln("Failed to calculate message HMAC: Not enough memory");
return {};
}
if constexpr (TLS_DEBUG) {
dbgln("HMAC of the block for sequence number {}", sequence_number);
print_buffer(mac_result.value());
}
return mac_result.release_value();
}
ssize_t TLSv12::handle_message(ReadonlyBytes buffer)
{
auto res { 5ll };
size_t header_size = res;
ssize_t payload_res = 0;
dbgln_if(TLS_DEBUG, "buffer size: {}", buffer.size());
if (buffer.size() < 5) {
return (i8)Error::NeedMoreData;
}
auto type = (ContentType)buffer[0];
size_t buffer_position { 1 };
// FIXME: Read the version and verify it
if constexpr (TLS_DEBUG) {
auto version = static_cast<ProtocolVersion>(ByteReader::load16(buffer.offset_pointer(buffer_position)));
dbgln("type={}, version={}", enum_to_string(type), enum_to_string(version));
}
buffer_position += 2;
auto length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(buffer_position)));
dbgln_if(TLS_DEBUG, "record length: {} at offset: {}", length, buffer_position);
buffer_position += 2;
if (buffer_position + length > buffer.size()) {
dbgln_if(TLS_DEBUG, "record length more than what we have: {}", buffer.size());
return (i8)Error::NeedMoreData;
}
dbgln_if(TLS_DEBUG, "message type: {}, length: {}", enum_to_string(type), length);
auto plain = buffer.slice(buffer_position, buffer.size() - buffer_position);
ByteBuffer decrypted;
if (m_context.cipher_spec_set && type != ContentType::CHANGE_CIPHER_SPEC) {
if constexpr (TLS_DEBUG) {
dbgln("Encrypted: ");
print_buffer(buffer.slice(header_size, length));
}
Error return_value = Error::NoError;
m_cipher_remote.visit(
[&](Empty&) { VERIFY_NOT_REACHED(); },
[&](Crypto::Cipher::AESCipher::GCMMode& gcm) {
VERIFY(is_aead());
if (length < 24) {
dbgln("Invalid packet length");
auto packet = build_alert(true, (u8)AlertDescription::DECRYPT_ERROR);
write_packet(packet);
return_value = Error::BrokenPacket;
return;
}
auto packet_length = length - iv_length() - 16;
auto payload = plain;
auto decrypted_result = ByteBuffer::create_uninitialized(packet_length);
if (decrypted_result.is_error()) {
dbgln("Failed to allocate memory for the packet");
return_value = Error::DecryptionFailed;
return;
}
decrypted = decrypted_result.release_value();
// AEAD AAD (13)
// Seq. no (8)
// content type (1)
// version (2)
// length (2)
u8 aad[13];
Bytes aad_bytes { aad, 13 };
FixedMemoryStream aad_stream { aad_bytes };
u64 seq_no = AK::convert_between_host_and_network_endian(m_context.remote_sequence_number);
u16 len = AK::convert_between_host_and_network_endian((u16)packet_length);
MUST(aad_stream.write_value(seq_no)); // sequence number
MUST(aad_stream.write_until_depleted(buffer.slice(0, header_size - 2))); // content-type + version
MUST(aad_stream.write_value(len)); // length
VERIFY(MUST(aad_stream.tell()) == MUST(aad_stream.size()));
auto nonce = payload.slice(0, iv_length());
payload = payload.slice(iv_length());
// AEAD IV (12)
// IV (4)
// (Nonce) (8)
// -- Our GCM impl takes 16 bytes
// zero (4)
u8 iv[16];
Bytes iv_bytes { iv, 16 };
Bytes { m_context.crypto.remote_aead_iv, 4 }.copy_to(iv_bytes);
nonce.copy_to(iv_bytes.slice(4));
memset(iv_bytes.offset(12), 0, 4);
auto ciphertext = payload.slice(0, payload.size() - 16);
auto tag = payload.slice(ciphertext.size());
auto consistency = gcm.decrypt(
ciphertext,
decrypted,
iv_bytes,
aad_bytes,
tag);
if (consistency != Crypto::VerificationConsistency::Consistent) {
dbgln("integrity check failed (tag length {})", tag.size());
auto packet = build_alert(true, (u8)AlertDescription::BAD_RECORD_MAC);
write_packet(packet);
return_value = Error::IntegrityCheckFailed;
return;
}
plain = decrypted;
},
[&](Crypto::Cipher::AESCipher::CBCMode& cbc) {
VERIFY(!is_aead());
auto iv_size = iv_length();
auto decrypted_result = cbc.create_aligned_buffer(length - iv_size);
if (decrypted_result.is_error()) {
dbgln("Failed to allocate memory for the packet");
return_value = Error::DecryptionFailed;
return;
}
decrypted = decrypted_result.release_value();
auto iv = buffer.slice(header_size, iv_size);
Bytes decrypted_span = decrypted;
cbc.decrypt(buffer.slice(header_size + iv_size, length - iv_size), decrypted_span, iv);
length = decrypted_span.size();
if constexpr (TLS_DEBUG) {
dbgln("Decrypted: ");
print_buffer(decrypted);
}
auto mac_size = mac_length();
if (length < mac_size) {
dbgln("broken packet");
auto packet = build_alert(true, (u8)AlertDescription::DECRYPT_ERROR);
write_packet(packet);
return_value = Error::BrokenPacket;
return;
}
length -= mac_size;
u8 const* message_hmac = decrypted_span.offset(length);
u8 temp_buf[5];
memcpy(temp_buf, buffer.offset_pointer(0), 3);
*(u16*)(temp_buf + 3) = AK::convert_between_host_and_network_endian(length);
auto hmac = hmac_message({ temp_buf, 5 }, decrypted_span.slice(0, length), mac_size);
auto message_mac = ReadonlyBytes { message_hmac, mac_size };
if (hmac != message_mac) {
dbgln("integrity check failed (mac length {})", mac_size);
dbgln("mac received:");
print_buffer(message_mac);
dbgln("mac computed:");
print_buffer(hmac);
auto packet = build_alert(true, (u8)AlertDescription::BAD_RECORD_MAC);
write_packet(packet);
return_value = Error::IntegrityCheckFailed;
return;
}
plain = decrypted.bytes().slice(0, length);
});
if (return_value != Error::NoError) {
return (i8)return_value;
}
}
m_context.remote_sequence_number++;
switch (type) {
case ContentType::APPLICATION_DATA:
if (m_context.connection_status != ConnectionStatus::Established) {
dbgln("unexpected application data");
payload_res = (i8)Error::UnexpectedMessage;
auto packet = build_alert(true, (u8)AlertDescription::UNEXPECTED_MESSAGE);
write_packet(packet);
} else {
dbgln_if(TLS_DEBUG, "application data message of size {}", plain.size());
if (m_context.application_buffer.try_append(plain).is_error()) {
payload_res = (i8)Error::DecryptionFailed;
auto packet = build_alert(true, (u8)AlertDescription::DECRYPTION_FAILED_RESERVED);
write_packet(packet);
} else {
notify_client_for_app_data();
}
}
break;
case ContentType::HANDSHAKE:
dbgln_if(TLS_DEBUG, "tls handshake message");
payload_res = handle_handshake_payload(plain);
break;
case ContentType::CHANGE_CIPHER_SPEC:
if (m_context.connection_status != ConnectionStatus::KeyExchange) {
dbgln("unexpected change cipher message");
auto packet = build_alert(true, (u8)AlertDescription::UNEXPECTED_MESSAGE);
write_packet(packet);
payload_res = (i8)Error::UnexpectedMessage;
} else {
dbgln_if(TLS_DEBUG, "change cipher spec message");
m_context.cipher_spec_set = true;
m_context.remote_sequence_number = 0;
}
break;
case ContentType::ALERT:
dbgln_if(TLS_DEBUG, "alert message of length {}", length);
if (length >= 2) {
if constexpr (TLS_DEBUG)
print_buffer(plain);
auto level = plain[0];
auto code = plain[1];
dbgln_if(TLS_DEBUG, "Alert received with level {}, code {}", level, code);
if (level == (u8)AlertLevel::FATAL) {
dbgln("We were alerted of a critical error: {} ({})", code, enum_to_string((AlertDescription)code));
m_context.critical_error = code;
try_disambiguate_error();
res = (i8)Error::UnknownError;
}
if (code == (u8)AlertDescription::CLOSE_NOTIFY) {
res += 2;
alert(AlertLevel::FATAL, AlertDescription::CLOSE_NOTIFY);
if (!m_context.cipher_spec_set) {
// AWS CloudFront hits this.
dbgln("Server sent a close notify and we haven't agreed on a cipher suite. Treating it as a handshake failure.");
m_context.critical_error = (u8)AlertDescription::HANDSHAKE_FAILURE;
try_disambiguate_error();
}
m_context.close_notify = true;
}
m_context.error_code = (Error)code;
check_connection_state(false);
notify_client_for_app_data(); // Give the user one more chance to observe the EOF
}
break;
default:
dbgln("message not understood");
return (i8)Error::NotUnderstood;
}
if (payload_res < 0)
return payload_res;
if (res > 0)
return header_size + length;
return res;
}
}

View file

@ -0,0 +1,323 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <LibCore/DateTime.h>
#include <LibCore/EventLoop.h>
#include <LibCore/Promise.h>
#include <LibCore/Timer.h>
#include <LibTLS/TLSv12.h>
// Each record can hold at most 18432 bytes, leaving some headroom and rounding down to
// a nice number gives us a maximum of 16 KiB for user-supplied application data,
// which will be sent as a single record containing a single ApplicationData message.
constexpr static size_t MaximumApplicationDataChunkSize = 16 * KiB;
namespace TLS {
ErrorOr<Bytes> TLSv12::read_some(Bytes bytes)
{
m_eof = false;
auto size_to_read = min(bytes.size(), m_context.application_buffer.size());
if (size_to_read == 0) {
m_eof = true;
return Bytes {};
}
m_context.application_buffer.transfer(bytes, size_to_read);
return Bytes { bytes.data(), size_to_read };
}
ErrorOr<size_t> TLSv12::write_some(ReadonlyBytes bytes)
{
if (m_context.connection_status != ConnectionStatus::Established) {
dbgln_if(TLS_DEBUG, "write request while not connected");
return AK::Error::from_string_literal("TLS write request while not connected");
}
for (size_t offset = 0; offset < bytes.size(); offset += MaximumApplicationDataChunkSize) {
PacketBuilder builder { ContentType::APPLICATION_DATA, m_context.options.version, bytes.size() - offset };
builder.append(bytes.slice(offset, min(bytes.size() - offset, MaximumApplicationDataChunkSize)));
auto packet = builder.build();
update_packet(packet);
write_packet(packet);
}
return bytes.size();
}
ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect(ByteString const& host, u16 port, Options options)
{
auto promise = Core::Promise<Empty>::construct();
OwnPtr<Core::Socket> tcp_socket = TRY(Core::TCPSocket::connect(host, port));
TRY(tcp_socket->set_blocking(false));
auto tls_socket = make<TLSv12>(move(tcp_socket), move(options));
tls_socket->set_sni(host);
tls_socket->on_connected = [&] {
promise->resolve({});
};
tls_socket->on_tls_error = [&](auto alert) {
tls_socket->try_disambiguate_error();
promise->reject(AK::Error::from_string_view(enum_to_string(alert)));
};
TRY(promise->await());
tls_socket->on_tls_error = nullptr;
tls_socket->on_connected = nullptr;
tls_socket->m_context.should_expect_successful_read = true;
return tls_socket;
}
ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect(ByteString const& host, Core::Socket& underlying_stream, Options options)
{
auto promise = Core::Promise<Empty>::construct();
TRY(underlying_stream.set_blocking(false));
auto tls_socket = make<TLSv12>(&underlying_stream, move(options));
tls_socket->set_sni(host);
tls_socket->on_connected = [&] {
promise->resolve({});
};
tls_socket->on_tls_error = [&](auto alert) {
tls_socket->try_disambiguate_error();
promise->reject(AK::Error::from_string_view(enum_to_string(alert)));
};
TRY(promise->await());
tls_socket->on_tls_error = nullptr;
tls_socket->on_connected = nullptr;
tls_socket->m_context.should_expect_successful_read = true;
return tls_socket;
}
void TLSv12::setup_connection()
{
Core::deferred_invoke([this] {
auto& stream = underlying_stream();
stream.on_ready_to_read = [this] {
auto result = read_from_socket();
if (result.is_error())
dbgln("Read error: {}", result.error());
};
m_handshake_timeout_timer = Core::Timer::create_single_shot(
m_max_wait_time_for_handshake_in_seconds * 1000, [&] {
dbgln("Handshake timeout :(");
auto timeout_diff = Core::DateTime::now().timestamp() - m_context.handshake_initiation_timestamp;
// If the timeout duration was actually within the max wait time (with a margin of error),
// we're not operating slow, so the server timed out.
// otherwise, it's our fault that the negotiation is taking too long, so extend the timer :P
if (timeout_diff < m_max_wait_time_for_handshake_in_seconds + 1) {
// The server did not respond fast enough,
// time the connection out.
alert(AlertLevel::FATAL, AlertDescription::USER_CANCELED);
m_context.tls_buffer.clear();
m_context.error_code = Error::TimedOut;
m_context.critical_error = (u8)Error::TimedOut;
check_connection_state(false); // Notify the client.
} else {
// Extend the timer, we are too slow.
m_handshake_timeout_timer->restart(m_max_wait_time_for_handshake_in_seconds * 1000);
}
});
auto packet = build_hello();
write_packet(packet);
write_into_socket();
m_handshake_timeout_timer->start();
m_context.handshake_initiation_timestamp = Core::DateTime::now().timestamp();
});
m_has_scheduled_write_flush = true;
}
void TLSv12::notify_client_for_app_data()
{
if (m_context.application_buffer.size() > 0) {
if (on_ready_to_read)
on_ready_to_read();
} else {
if (m_context.connection_finished && !m_context.has_invoked_finish_or_error_callback) {
m_context.has_invoked_finish_or_error_callback = true;
if (on_tls_finished)
on_tls_finished();
}
}
m_has_scheduled_app_data_flush = false;
}
ErrorOr<void> TLSv12::read_from_socket()
{
// If there's anything before we consume stuff, let the client know
// since we won't be consuming things if the connection is terminated.
notify_client_for_app_data();
ScopeGuard notify_guard {
[this] {
// If anything new shows up, tell the client about the event.
notify_client_for_app_data();
}
};
if (!check_connection_state(true))
return {};
u8 buffer[16 * KiB];
Bytes bytes { buffer, array_size(buffer) };
Bytes read_bytes {};
auto& stream = underlying_stream();
do {
auto result = stream.read_some(bytes);
if (result.is_error()) {
if (result.error().is_errno() && result.error().code() != EINTR) {
if (result.error().code() != EAGAIN)
dbgln("TLS Socket read failed, error: {}", result.error());
break;
}
continue;
}
read_bytes = result.release_value();
consume(read_bytes);
} while (!read_bytes.is_empty() && !m_context.critical_error);
if (m_context.should_expect_successful_read && read_bytes.is_empty()) {
// read_some() returned an empty span, this is either an EOF (from improper closure)
// or some sort of weird even that is showing itself as an EOF.
// To guard against servers closing the connection weirdly or just improperly, make sure
// to check the connection state here and send the appropriate notifications.
stream.close();
check_connection_state(true);
}
return {};
}
void TLSv12::write_into_socket()
{
dbgln_if(TLS_DEBUG, "Flushing cached records: {} established? {}", m_context.tls_buffer.size(), is_established());
m_has_scheduled_write_flush = false;
if (!check_connection_state(false))
return;
MUST(flush());
}
bool TLSv12::check_connection_state(bool read)
{
if (m_context.connection_finished)
return false;
if (m_context.close_notify)
m_context.connection_finished = true;
auto& stream = underlying_stream();
if (!stream.is_open()) {
// an abrupt closure (the server is a jerk)
dbgln_if(TLS_DEBUG, "Socket not open, assuming abrupt closure");
m_context.connection_finished = true;
m_context.connection_status = ConnectionStatus::Disconnected;
close();
m_context.has_invoked_finish_or_error_callback = true;
if (on_ready_to_read)
on_ready_to_read(); // Notify the client about the weird event.
if (on_tls_finished)
on_tls_finished();
return false;
}
if (read && stream.is_eof()) {
if (m_context.application_buffer.size() == 0 && m_context.connection_status != ConnectionStatus::Disconnected) {
m_context.has_invoked_finish_or_error_callback = true;
if (on_tls_finished)
on_tls_finished();
}
return false;
}
if (m_context.critical_error) {
dbgln_if(TLS_DEBUG, "CRITICAL ERROR {} :(", m_context.critical_error);
m_context.has_invoked_finish_or_error_callback = true;
if (on_tls_error)
on_tls_error((AlertDescription)m_context.critical_error);
m_context.connection_finished = true;
m_context.connection_status = ConnectionStatus::Disconnected;
close();
return false;
}
if (((read && m_context.application_buffer.size() == 0) || !read) && m_context.connection_finished) {
if (m_context.application_buffer.size() == 0 && m_context.connection_status != ConnectionStatus::Disconnected) {
m_context.has_invoked_finish_or_error_callback = true;
if (on_tls_finished)
on_tls_finished();
}
if (m_context.tls_buffer.size()) {
dbgln_if(TLS_DEBUG, "connection closed without finishing data transfer, {} bytes still in buffer and {} bytes in application buffer",
m_context.tls_buffer.size(),
m_context.application_buffer.size());
}
if (!m_context.application_buffer.size()) {
return false;
}
}
return true;
}
ErrorOr<bool> TLSv12::flush()
{
auto out_bytes = m_context.tls_buffer.bytes();
if (out_bytes.is_empty())
return true;
if constexpr (TLS_DEBUG) {
dbgln("SENDING...");
print_buffer(out_bytes);
}
auto& stream = underlying_stream();
Optional<AK::Error> error;
size_t written;
do {
auto result = stream.write_some(out_bytes);
if (result.is_error()) {
if (result.error().code() != EINTR && result.error().code() != EAGAIN) {
error = result.release_error();
dbgln("TLS Socket write error: {}", *error);
break;
}
continue;
}
written = result.value();
out_bytes = out_bytes.slice(written);
} while (!out_bytes.is_empty());
if (out_bytes.is_empty() && !error.has_value()) {
m_context.tls_buffer.clear();
return true;
}
if (m_context.send_retries++ == 10) {
// drop the records, we can't send
dbgln_if(TLS_DEBUG, "Dropping {} bytes worth of TLS records as max retries has been reached", m_context.tls_buffer.size());
m_context.tls_buffer.clear();
m_context.send_retries = 0;
}
return false;
}
void TLSv12::close()
{
if (underlying_stream().is_open())
alert(AlertLevel::FATAL, AlertDescription::CLOSE_NOTIFY);
// bye bye.
m_context.connection_status = ConnectionStatus::Disconnected;
}
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/ByteReader.h>
#include <AK/Endian.h>
#include <AK/Types.h>
#include <LibTLS/Extensions.h>
namespace TLS {
class PacketBuilder {
public:
PacketBuilder(ContentType type, u16 version, size_t size_hint = 0xfdf)
: PacketBuilder(type, (ProtocolVersion)version, size_hint)
{
}
PacketBuilder(ContentType type, ProtocolVersion version, size_t size_hint = 0xfdf)
{
// FIXME: Handle possible OOM situation.
m_packet_data = ByteBuffer::create_uninitialized(size_hint + 16).release_value_but_fixme_should_propagate_errors();
m_current_length = 5;
m_packet_data[0] = (u8)type;
ByteReader::store(m_packet_data.offset_pointer(1), AK::convert_between_host_and_network_endian((u16)version));
}
inline void append(u16 value)
{
value = AK::convert_between_host_and_network_endian(value);
append((u8 const*)&value, sizeof(value));
}
inline void append(u8 value)
{
append((u8 const*)&value, sizeof(value));
}
inline void append(ReadonlyBytes data)
{
append(data.data(), data.size());
}
inline void append_u24(u32 value)
{
u8 buf[3];
buf[0] = value / 0x10000;
value %= 0x10000;
buf[1] = value / 0x100;
value %= 0x100;
buf[2] = value;
append(buf, 3);
}
inline void append(u8 const* data, size_t bytes)
{
if (bytes == 0)
return;
auto old_length = m_current_length;
m_current_length += bytes;
if (m_packet_data.size() < m_current_length) {
m_packet_data.resize(m_current_length);
}
m_packet_data.overwrite(old_length, data, bytes);
}
inline ByteBuffer build()
{
auto length = m_current_length;
m_current_length = 0;
// FIXME: Propagate errors.
return MUST(m_packet_data.slice(0, length));
}
inline void set(size_t offset, u8 value)
{
VERIFY(offset < m_current_length);
m_packet_data[offset] = value;
}
size_t length() const { return m_current_length; }
private:
ByteBuffer m_packet_data;
size_t m_current_length;
};
}

View file

@ -4,162 +4,622 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/Base64.h>
#include <AK/Debug.h>
#include <AK/Endian.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/DateTime.h>
#include <LibCore/File.h>
#include <LibCore/StandardPaths.h>
#include <LibCore/Timer.h>
#include <LibCrypto/ASN1/ASN1.h>
#include <LibCrypto/ASN1/PEM.h>
#include <LibCrypto/Curves/Ed25519.h>
#include <LibCrypto/Curves/SECPxxxr1.h>
#include <LibCrypto/PK/Code/EMSA_PKCS1_V1_5.h>
#include <LibFileSystem/FileSystem.h>
#include <LibTLS/Certificate.h>
#include <LibTLS/TLSv12.h> #include <LibTLS/TLSv12.h>
#include <errno.h> #include <errno.h>
#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#ifndef SOCK_NONBLOCK #ifndef SOCK_NONBLOCK
# include <sys/ioctl.h> # include <sys/ioctl.h>
#endif #endif
static Vector<ByteString> s_certificate_store_paths;
namespace TLS { namespace TLS {
static thread_local bool g_initialized_wolfssl = false; void TLSv12::consume(ReadonlyBytes record)
static int errno_to_wolfssl_error(int error)
{ {
switch (error) { if (m_context.critical_error) {
case EAGAIN: dbgln("There has been a critical error ({}), refusing to continue", (i8)m_context.critical_error);
return WOLFSSL_CBIO_ERR_WANT_READ; return;
case ETIMEDOUT: }
return WOLFSSL_CBIO_ERR_TIMEOUT;
case ECONNRESET: if (record.size() == 0) {
return WOLFSSL_CBIO_ERR_CONN_RST; return;
case EINTR: }
return WOLFSSL_CBIO_ERR_ISR;
case ECONNREFUSED: dbgln_if(TLS_DEBUG, "Consuming {} bytes", record.size());
return WOLFSSL_CBIO_ERR_WANT_READ;
case ECONNABORTED: if (m_context.message_buffer.try_append(record).is_error()) {
return WOLFSSL_CBIO_ERR_CONN_CLOSE; dbgln("Not enough space in message buffer, dropping the record");
default: return;
return WOLFSSL_CBIO_ERR_GENERAL; }
size_t index { 0 };
size_t buffer_length = m_context.message_buffer.size();
size_t size_offset { 3 }; // read the common record header
size_t header_size { 5 };
dbgln_if(TLS_DEBUG, "message buffer length {}", buffer_length);
while (buffer_length >= 5) {
auto length = AK::convert_between_host_and_network_endian(ByteReader::load16(m_context.message_buffer.offset_pointer(index + size_offset))) + header_size;
if (length > buffer_length) {
dbgln_if(TLS_DEBUG, "Need more data: {} > {}", length, buffer_length);
break;
}
auto consumed = handle_message(m_context.message_buffer.bytes().slice(index, length));
if constexpr (TLS_DEBUG) {
if (consumed > 0)
dbgln("consumed {} bytes", consumed);
else
dbgln("error: {}", consumed);
}
if (consumed != (i8)Error::NeedMoreData) {
if (consumed < 0) {
dbgln("Consumed an error: {}", consumed);
if (!m_context.critical_error)
m_context.critical_error = (i8)consumed;
m_context.error_code = (Error)consumed;
break;
}
} else {
continue;
}
index += length;
buffer_length -= length;
if (m_context.critical_error) {
dbgln("Broken connection");
m_context.error_code = Error::BrokenConnection;
break;
}
}
if (m_context.error_code != Error::NoError && m_context.error_code != Error::NeedMoreData) {
dbgln("consume error: {}", (i8)m_context.error_code);
m_context.message_buffer.clear();
return;
}
if (index) {
// FIXME: Propagate errors.
m_context.message_buffer = MUST(m_context.message_buffer.slice(index, m_context.message_buffer.size() - index));
} }
} }
StringView WolfTLS::error_text(WOLFSSL* ssl, int error_code) bool Certificate::is_valid() const
{ {
auto error = wolfSSL_get_error(ssl, error_code); auto now = UnixDateTime::now();
static char buffer[WOLFSSL_MAX_ERROR_SZ];
auto* ptr = wolfSSL_ERR_error_string(error, buffer);
return StringView { ptr, strlen(ptr) };
}
ErrorOr<NonnullOwnPtr<WolfTLS>> WolfTLS::connect(const AK::ByteString& host, u16 port) if (now < validity.not_before) {
{ dbgln("certificate expired (not yet valid, signed for {})", Core::DateTime::from_timestamp(validity.not_before.seconds_since_epoch()));
if (!g_initialized_wolfssl) { return false;
wolfSSL_Init();
g_initialized_wolfssl = true;
} }
auto* context = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); if (validity.not_after < now) {
if (!context) dbgln("certificate expired (expiry date {})", Core::DateTime::from_timestamp(validity.not_after.seconds_since_epoch()));
return Error::from_string_literal("Failed to create a new TLS context"); return false;
if (s_certificate_store_paths.is_empty())
s_certificate_store_paths.append("/etc/ssl/cert.pem"); // We're just guessing this, the embedder should provide this.
for (auto& path : s_certificate_store_paths) {
if (wolfSSL_CTX_load_verify_locations(context, path.characters(), nullptr) != SSL_SUCCESS)
return Error::from_string_literal("Failed to load CA certificates");
} }
auto ssl = wolfSSL_new(context);
if (!ssl)
return Error::from_string_literal("Failed to create a new SSL object");
wolfSSL_SSLSetIOSend(ssl, [](WOLFSSL*, char* buf, int sz, void* ctx) -> int {
auto& self = *static_cast<WolfTLS*>(ctx);
auto result = self.m_underlying->write_some({ buf, static_cast<size_t>(sz) });
if (result.is_error() && result.error().is_errno())
return errno_to_wolfssl_error(result.error().code());
if (result.is_error())
return -1;
return static_cast<int>(result.value());
});
wolfSSL_SSLSetIORecv(ssl, [](WOLFSSL*, char* buf, int sz, void* ctx) -> int {
auto& self = *static_cast<WolfTLS*>(ctx);
auto result = self.m_underlying->read_some({ buf, static_cast<size_t>(sz) });
if (result.is_error() && result.error().is_errno())
return errno_to_wolfssl_error(result.error().code());
if (result.is_error())
return -1;
return static_cast<int>(result.value().size());
});
auto tcp_socket = TRY(Core::TCPSocket::connect(host, port));
auto object = make<WolfTLS>(context, ssl, move(tcp_socket));
wolfSSL_SetIOReadCtx(ssl, static_cast<void*>(object.ptr()));
wolfSSL_SetIOWriteCtx(ssl, static_cast<void*>(object.ptr()));
if (wolfSSL_CTX_UseSNI(context, WOLFSSL_SNI_HOST_NAME, host.bytes().data(), host.bytes().size()) != WOLFSSL_SUCCESS)
return Error::from_string_literal("Failed to set SNI hostname");
if (auto rc = wolfSSL_connect(ssl); rc != SSL_SUCCESS)
return Error::from_string_view(error_text(ssl, rc));
return object;
}
WolfTLS::~WolfTLS()
{
close();
wolfSSL_free(m_ssl);
wolfSSL_CTX_free(m_context);
}
ErrorOr<Bytes> WolfTLS::read_some(Bytes bytes)
{
auto result = wolfSSL_read(m_ssl, bytes.data(), bytes.size());
if (result < 0)
return Error::from_string_view(error_text(m_ssl, result));
return Bytes { bytes.data(), static_cast<size_t>(result) };
}
ErrorOr<size_t> WolfTLS::write_some(ReadonlyBytes bytes)
{
auto result = wolfSSL_write(m_ssl, bytes.data(), bytes.size());
if (result < 0)
return Error::from_string_view(error_text(m_ssl, result));
return static_cast<size_t>(result);
}
bool WolfTLS::is_eof() const
{
return m_underlying->is_eof() && wolfSSL_pending(m_ssl) == 0;
}
bool WolfTLS::is_open() const
{
return m_underlying->is_open();
}
void WolfTLS::close()
{
wolfSSL_shutdown(m_ssl);
}
ErrorOr<size_t> WolfTLS::pending_bytes() const
{
return wolfSSL_pending(m_ssl);
}
ErrorOr<bool> WolfTLS::can_read_without_blocking(int) const
{
return true; return true;
} }
ErrorOr<void> WolfTLS::set_blocking(bool) // https://www.ietf.org/rfc/rfc5280.html#page-12
bool Certificate::is_self_signed()
{ {
if (m_is_self_signed.has_value())
return *m_is_self_signed;
// Self-signed certificates are self-issued certificates where the digital
// signature may be verified by the public key bound into the certificate.
if (!this->is_self_issued)
m_is_self_signed.emplace(false);
// FIXME: Actually check if we sign ourself
m_is_self_signed.emplace(true);
return *m_is_self_signed;
}
void TLSv12::try_disambiguate_error() const
{
dbgln("Possible failure cause(s): ");
switch ((AlertDescription)m_context.critical_error) {
case AlertDescription::HANDSHAKE_FAILURE:
if (!m_context.cipher_spec_set) {
dbgln("- No cipher suite in common with {}", m_context.extensions.SNI);
} else {
dbgln("- Unknown internal issue");
}
break;
case AlertDescription::INSUFFICIENT_SECURITY:
dbgln("- No cipher suite in common with {} (the server is oh so secure)", m_context.extensions.SNI);
break;
case AlertDescription::PROTOCOL_VERSION:
dbgln("- The server refused to negotiate with TLS 1.2 :(");
break;
case AlertDescription::UNEXPECTED_MESSAGE:
dbgln("- We sent an invalid message for the state we're in.");
break;
case AlertDescription::BAD_RECORD_MAC:
dbgln("- Bad MAC record from our side.");
dbgln("- Ciphertext wasn't an even multiple of the block length.");
dbgln("- Bad block cipher padding.");
dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
break;
case AlertDescription::RECORD_OVERFLOW:
dbgln("- Sent a ciphertext record which has a length bigger than 18432 bytes.");
dbgln("- Sent record decrypted to a compressed record that has a length bigger than 18432 bytes.");
dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
break;
case AlertDescription::DECOMPRESSION_FAILURE_RESERVED:
dbgln("- We sent invalid input for decompression (e.g. data that would expand to excessive length)");
break;
case AlertDescription::ILLEGAL_PARAMETER:
dbgln("- We sent a parameter in the handshake that is out of range or inconsistent with the other parameters.");
break;
case AlertDescription::DECODE_ERROR:
dbgln("- The message we sent cannot be decoded because a field was out of range or the length was incorrect.");
dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
break;
case AlertDescription::DECRYPT_ERROR:
dbgln("- A handshake crypto operation failed. This includes signature verification and validating Finished.");
break;
case AlertDescription::ACCESS_DENIED:
dbgln("- The certificate is valid, but once access control was applied, the sender decided to stop negotiation.");
break;
case AlertDescription::INTERNAL_ERROR:
dbgln("- No one knows, but it isn't a protocol failure.");
break;
case AlertDescription::DECRYPTION_FAILED_RESERVED:
case AlertDescription::NO_CERTIFICATE_RESERVED:
case AlertDescription::EXPORT_RESTRICTION_RESERVED:
dbgln("- No one knows, the server sent a non-compliant alert.");
break;
default:
dbgln("- No one knows.");
break;
}
dbgln("- {}", enum_to_value((AlertDescription)m_context.critical_error));
}
void TLSv12::set_root_certificates(Vector<Certificate> certificates)
{
if (!m_context.root_certificates.is_empty()) {
dbgln("TLS warn: resetting root certificates!");
m_context.root_certificates.clear();
}
for (auto& cert : certificates) {
if (!cert.is_valid()) {
dbgln("Certificate for {} is invalid, things may or may not work!", cert.subject.to_string());
}
// FIXME: Figure out what we should do when our root certs are invalid.
m_context.root_certificates.set(MUST(cert.subject.to_string()).to_byte_string(), cert);
}
dbgln_if(TLS_DEBUG, "{}: Set {} root certificates", this, m_context.root_certificates.size());
}
static bool wildcard_matches(StringView host, StringView subject)
{
if (host == subject)
return true;
if (subject.starts_with("*."sv)) {
auto maybe_first_dot_index = host.find('.');
if (maybe_first_dot_index.has_value()) {
auto first_dot_index = maybe_first_dot_index.release_value();
return wildcard_matches(host.substring_view(first_dot_index + 1), subject.substring_view(2));
}
}
return false;
}
static bool certificate_subject_matches_host(Certificate const& cert, StringView host)
{
if (wildcard_matches(host, cert.subject.common_name()))
return true;
for (auto& san : cert.SAN) {
if (wildcard_matches(host, san))
return true;
}
return false;
}
bool Context::verify_chain(StringView host) const
{
if (!options.validate_certificates)
return true;
Vector<Certificate> const* local_chain = nullptr;
if (is_server) {
dbgln("Unsupported: Server mode");
TODO();
} else {
local_chain = &certificates;
}
if (local_chain->is_empty()) {
dbgln("verify_chain: Attempting to verify an empty chain");
return false;
}
// RFC5246 section 7.4.2: The sender's certificate MUST come first in the list. Each following certificate
// MUST directly certify the one preceding it. Because certificate validation requires that root keys be
// distributed independently, the self-signed certificate that specifies the root certificate authority MAY be
// omitted from the chain, under the assumption that the remote end must already possess it in order to validate
// it in any case.
if (!host.is_empty()) {
auto const& first_certificate = local_chain->first();
auto subject_matches = certificate_subject_matches_host(first_certificate, host);
if (!subject_matches) {
dbgln("verify_chain: First certificate does not match the hostname");
return false;
}
} else {
// FIXME: The host is taken from m_context.extensions.SNI, when is this empty?
dbgln("FIXME: verify_chain called without host");
return false;
}
for (size_t cert_index = 0; cert_index < local_chain->size(); ++cert_index) {
auto const& cert = local_chain->at(cert_index);
auto subject_string = MUST(cert.subject.to_string());
auto issuer_string = MUST(cert.issuer.to_string());
if (!cert.is_valid()) {
dbgln("verify_chain: Certificate is not valid {}", subject_string);
return false;
}
auto maybe_root_certificate = root_certificates.get(issuer_string.to_byte_string());
if (maybe_root_certificate.has_value()) {
auto& root_certificate = *maybe_root_certificate;
auto verification_correct = verify_certificate_pair(cert, root_certificate);
if (!verification_correct) {
dbgln("verify_chain: Signature inconsistent, {} was not signed by {} (root certificate)", subject_string, issuer_string);
return false;
}
// Root certificate reached, and correctly verified, so we can stop now
return true;
}
if (subject_string == issuer_string) {
dbgln("verify_chain: Non-root self-signed certificate");
return options.allow_self_signed_certificates;
}
if ((cert_index + 1) >= local_chain->size()) {
dbgln("verify_chain: No trusted root certificate found before end of certificate chain");
dbgln("verify_chain: Last certificate in chain was signed by {}", issuer_string);
return false;
}
auto const& parent_certificate = local_chain->at(cert_index + 1);
if (issuer_string != MUST(parent_certificate.subject.to_string())) {
dbgln("verify_chain: Next certificate in the chain is not the issuer of this certificate");
return false;
}
if (!(parent_certificate.is_allowed_to_sign_certificate && parent_certificate.is_certificate_authority)) {
dbgln("verify_chain: {} is not marked as certificate authority", issuer_string);
return false;
}
if (parent_certificate.path_length_constraint.has_value() && cert_index > parent_certificate.path_length_constraint.value()) {
dbgln("verify_chain: Path length for certificate exceeded");
return false;
}
bool verification_correct = verify_certificate_pair(cert, parent_certificate);
if (!verification_correct) {
dbgln("verify_chain: Signature inconsistent, {} was not signed by {}", subject_string, issuer_string);
return false;
}
}
// Either a root certificate is reached, or parent validation fails as the end of the local chain is reached
VERIFY_NOT_REACHED();
}
bool Context::verify_certificate_pair(Certificate const& subject, Certificate const& issuer) const
{
Crypto::Hash::HashKind kind = Crypto::Hash::HashKind::Unknown;
auto identifier = subject.signature_algorithm.identifier;
bool is_rsa = true;
if (identifier == rsa_encryption_oid) {
kind = Crypto::Hash::HashKind::None;
} else if (identifier == rsa_md5_encryption_oid) {
kind = Crypto::Hash::HashKind::MD5;
} else if (identifier == rsa_sha1_encryption_oid) {
kind = Crypto::Hash::HashKind::SHA1;
} else if (identifier == rsa_sha256_encryption_oid) {
kind = Crypto::Hash::HashKind::SHA256;
} else if (identifier == rsa_sha384_encryption_oid) {
kind = Crypto::Hash::HashKind::SHA384;
} else if (identifier == rsa_sha512_encryption_oid) {
kind = Crypto::Hash::HashKind::SHA512;
} else if (identifier == ecdsa_with_sha256_encryption_oid) {
kind = Crypto::Hash::HashKind::SHA256;
is_rsa = false;
} else if (identifier == ecdsa_with_sha384_encryption_oid) {
kind = Crypto::Hash::HashKind::SHA384;
is_rsa = false;
} else if (identifier == ecdsa_with_sha512_encryption_oid) {
kind = Crypto::Hash::HashKind::SHA512;
is_rsa = false;
}
if (kind == Crypto::Hash::HashKind::Unknown) {
dbgln("verify_certificate_pair: Unknown signature algorithm, expected RSA or ECDSA with SHA1/256/384/512, got OID {}", identifier);
return false;
}
if (is_rsa) {
Crypto::PK::RSAPrivateKey dummy_private_key;
Crypto::PK::RSAPublicKey public_key_copy { issuer.public_key.rsa };
auto rsa = Crypto::PK::RSA(public_key_copy, dummy_private_key);
auto verification_buffer_result = ByteBuffer::create_uninitialized(subject.signature_value.size());
if (verification_buffer_result.is_error()) {
dbgln("verify_certificate_pair: Unable to allocate buffer for verification");
return false;
}
auto verification_buffer = verification_buffer_result.release_value();
auto verification_buffer_bytes = verification_buffer.bytes();
rsa.verify(subject.signature_value, verification_buffer_bytes);
ReadonlyBytes message = subject.tbs_asn1.bytes();
auto pkcs1 = Crypto::PK::EMSA_PKCS1_V1_5<Crypto::Hash::Manager>(kind);
auto verification = pkcs1.verify(message, verification_buffer_bytes, subject.signature_value.size() * 8);
return verification == Crypto::VerificationConsistency::Consistent;
}
// ECDSA hash verification: hash, then check signature against the specific curve
switch (issuer.public_key.algorithm.ec_parameters) {
case SupportedGroup::SECP256R1: {
Crypto::Hash::Manager hasher(kind);
hasher.update(subject.tbs_asn1.bytes());
auto hash = hasher.digest();
Crypto::Curves::SECP256r1 curve;
auto result = curve.verify(hash.bytes(), issuer.public_key.raw_key, subject.signature_value);
if (result.is_error()) {
dbgln("verify_certificate_pair: Failed to check SECP256r1 signature {}", result.release_error());
return false;
}
return result.value();
}
case SupportedGroup::SECP384R1: {
Crypto::Hash::Manager hasher(kind);
hasher.update(subject.tbs_asn1.bytes());
auto hash = hasher.digest();
Crypto::Curves::SECP384r1 curve;
auto result = curve.verify(hash.bytes(), issuer.public_key.raw_key, subject.signature_value);
if (result.is_error()) {
dbgln("verify_certificate_pair: Failed to check SECP384r1 signature {}", result.release_error());
return false;
}
return result.value();
}
case SupportedGroup::X25519: {
Crypto::Curves::Ed25519 curve;
auto result = curve.verify(issuer.public_key.raw_key, subject.signature_value, subject.tbs_asn1.bytes());
if (!result) {
dbgln("verify_certificate_pair: Failed to check Ed25519 signature");
return false;
}
return result;
}
default:
dbgln("verify_certificate_pair: Don't know how to verify signature for curve {}", to_underlying(issuer.public_key.algorithm.ec_parameters));
return false;
}
}
template<typename HMACType>
static void hmac_pseudorandom_function(Bytes output, ReadonlyBytes secret, u8 const* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b)
{
if (!secret.size()) {
dbgln("null secret");
return;
}
auto append_label_seed = [&](auto& hmac) {
hmac.update(label, label_length);
hmac.update(seed);
if (seed_b.size() > 0)
hmac.update(seed_b);
};
HMACType hmac(secret);
append_label_seed(hmac);
constexpr auto digest_size = hmac.digest_size();
u8 digest[digest_size];
auto digest_0 = Bytes { digest, digest_size };
digest_0.overwrite(0, hmac.digest().immutable_data(), digest_size);
size_t index = 0;
while (index < output.size()) {
hmac.update(digest_0);
append_label_seed(hmac);
auto digest_1 = hmac.digest();
auto copy_size = min(digest_size, output.size() - index);
output.overwrite(index, digest_1.immutable_data(), copy_size);
index += copy_size;
digest_0.overwrite(0, hmac.process(digest_0).immutable_data(), digest_size);
}
}
void TLSv12::pseudorandom_function(Bytes output, ReadonlyBytes secret, u8 const* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b)
{
// Simplification: We only support the HMAC PRF with the hash function SHA-256 or stronger.
// RFC 5246: "In this section, we define one PRF, based on HMAC. This PRF with the
// SHA-256 hash function is used for all cipher suites defined in this
// document and in TLS documents published prior to this document when
// TLS 1.2 is negotiated. New cipher suites MUST explicitly specify a
// PRF and, in general, SHOULD use the TLS PRF with SHA-256 or a
// stronger standard hash function."
switch (hmac_hash()) {
case Crypto::Hash::HashKind::SHA512:
hmac_pseudorandom_function<Crypto::Authentication::HMAC<Crypto::Hash::SHA512>>(output, secret, label, label_length, seed, seed_b);
break;
case Crypto::Hash::HashKind::SHA384:
hmac_pseudorandom_function<Crypto::Authentication::HMAC<Crypto::Hash::SHA384>>(output, secret, label, label_length, seed, seed_b);
break;
case Crypto::Hash::HashKind::SHA256:
hmac_pseudorandom_function<Crypto::Authentication::HMAC<Crypto::Hash::SHA256>>(output, secret, label, label_length, seed, seed_b);
break;
default:
dbgln("Failed to find a suitable HMAC hash");
VERIFY_NOT_REACHED();
break;
}
}
TLSv12::TLSv12(StreamVariantType stream, Options options)
: m_stream(move(stream))
{
m_context.options = move(options);
m_context.is_server = false;
m_context.tls_buffer = {};
set_root_certificates(m_context.options.root_certificates.has_value()
? *m_context.options.root_certificates
: DefaultRootCACertificates::the().certificates());
setup_connection();
}
Vector<Certificate> TLSv12::parse_pem_certificate(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes rsa_key) // FIXME: This should not be bound to RSA
{
if (certificate_pem_buffer.is_empty() || rsa_key.is_empty()) {
return {}; return {};
}
auto decoded_certificate = Crypto::decode_pem(certificate_pem_buffer);
if (decoded_certificate.is_empty()) {
dbgln("Certificate not PEM");
return {};
}
auto maybe_certificate = Certificate::parse_certificate(decoded_certificate);
if (!maybe_certificate.is_error()) {
dbgln("Invalid certificate");
return {};
}
Crypto::PK::RSA rsa(rsa_key);
auto certificate = maybe_certificate.release_value();
certificate.private_key = rsa.private_key();
return { move(certificate) };
} }
void WolfTLS::install_certificate_store_paths(Vector<AK::ByteString> paths) static Vector<ByteString> s_default_ca_certificate_paths;
void DefaultRootCACertificates::set_default_certificate_paths(Span<ByteString> paths)
{ {
s_certificate_store_paths = move(paths); s_default_ca_certificate_paths.clear();
s_default_ca_certificate_paths.ensure_capacity(paths.size());
for (auto& path : paths)
s_default_ca_certificate_paths.unchecked_append(path);
} }
DefaultRootCACertificates::DefaultRootCACertificates()
{
auto load_result = load_certificates(s_default_ca_certificate_paths);
if (load_result.is_error()) {
dbgln("Failed to load CA Certificates: {}", load_result.error());
return;
}
m_ca_certificates = load_result.release_value();
}
DefaultRootCACertificates& DefaultRootCACertificates::the()
{
static thread_local DefaultRootCACertificates s_the;
return s_the;
}
ErrorOr<Vector<Certificate>> DefaultRootCACertificates::load_certificates(Span<ByteString> custom_cert_paths)
{
auto cacert_file_or_error = Core::File::open("/etc/cacert.pem"sv, Core::File::OpenMode::Read);
ByteBuffer data;
if (!cacert_file_or_error.is_error())
data = TRY(cacert_file_or_error.value()->read_until_eof());
auto user_cert_path = TRY(String::formatted("{}/.config/certs.pem", Core::StandardPaths::home_directory()));
if (FileSystem::exists(user_cert_path)) {
auto user_cert_file = TRY(Core::File::open(user_cert_path, Core::File::OpenMode::Read));
TRY(data.try_append(TRY(user_cert_file->read_until_eof())));
}
for (auto& custom_cert_path : custom_cert_paths) {
if (FileSystem::exists(custom_cert_path)) {
auto custom_cert_file = TRY(Core::File::open(custom_cert_path, Core::File::OpenMode::Read));
TRY(data.try_append(TRY(custom_cert_file->read_until_eof())));
}
}
return TRY(parse_pem_root_certificate_authorities(data));
}
ErrorOr<Vector<Certificate>> DefaultRootCACertificates::parse_pem_root_certificate_authorities(ByteBuffer& data)
{
Vector<Certificate> certificates;
auto certs = TRY(Crypto::decode_pems(data));
for (auto& cert : certs) {
auto certificate_result = Certificate::parse_certificate(cert.bytes());
if (certificate_result.is_error()) {
// FIXME: It would be nice to have more informations about the certificate we failed to parse.
// Like: Issuer, Algorithm, CN, etc
dbgln("Failed to load certificate: {}", certificate_result.error());
continue;
}
auto certificate = certificate_result.release_value();
if (certificate.is_certificate_authority && certificate.is_self_signed()) {
TRY(certificates.try_append(move(certificate)));
} else {
dbgln("Skipped '{}' because it is not a valid root CA", TRY(certificate.subject.to_string()));
}
}
dbgln_if(TLS_DEBUG, "Loaded {} of {} ({:.2}%) provided CA Certificates", certificates.size(), certs.size(), (certificates.size() * 100.0) / certs.size());
return certificates;
}
} }

View file

@ -6,48 +6,546 @@
#pragma once #pragma once
#include "Certificate.h"
#include <AK/IPv4Address.h> #include <AK/IPv4Address.h>
#include <AK/Queue.h>
#include <AK/WeakPtr.h>
#include <LibCore/Notifier.h>
#include <LibCore/Socket.h> #include <LibCore/Socket.h>
#include <LibTLS/Certificate.h> #include <LibCore/Timer.h>
#include <LibCrypto/Authentication/HMAC.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibCrypto/Cipher/AES.h>
#include <LibCrypto/Curves/EllipticCurve.h>
#include <LibCrypto/Hash/HashManager.h>
#include <LibCrypto/PK/RSA.h>
#include <LibTLS/CipherSuite.h>
#include <LibTLS/TLSPacketBuilder.h>
struct WOLFSSL_CTX;
struct WOLFSSL;
namespace TLS { namespace TLS {
class WolfTLS final : public Core::Socket { inline void print_buffer(ReadonlyBytes buffer)
public: {
WolfTLS(WOLFSSL_CTX* context, WOLFSSL* ssl, NonnullOwnPtr<Core::TCPSocket> underlying) dbgln("{:hex-dump}", buffer);
: m_context(context) }
, m_ssl(ssl)
, m_underlying(move(underlying)) inline void print_buffer(ByteBuffer const& buffer)
{
print_buffer(buffer.bytes());
}
inline void print_buffer(u8 const* buffer, size_t size)
{
print_buffer(ReadonlyBytes { buffer, size });
}
class Socket;
enum class Error : i8 {
NoError = 0,
UnknownError = -1,
BrokenPacket = -2,
NotUnderstood = -3,
NoCommonCipher = -5,
UnexpectedMessage = -6,
CloseConnection = -7,
CompressionNotSupported = -8,
NotVerified = -9,
NotSafe = -10,
IntegrityCheckFailed = -11,
ErrorAlert = -12,
BrokenConnection = -13,
BadCertificate = -14,
UnsupportedCertificate = -15,
NoRenegotiation = -16,
FeatureNotSupported = -17,
DecryptionFailed = -20,
NeedMoreData = -21,
TimedOut = -22,
OutOfMemory = -23,
};
enum class WritePacketStage {
Initial = 0,
ClientHandshake = 1,
ServerHandshake = 2,
Finished = 3,
};
enum class ConnectionStatus {
Disconnected,
Negotiating,
KeyExchange,
Renegotiating,
Established,
};
enum ClientVerificationStaus {
Verified,
VerificationNeeded,
};
// Note for the 16 iv length instead of 8:
// 4 bytes of fixed IV, 8 random (nonce) bytes, 4 bytes for counter
// GCM specifically asks us to transmit only the nonce, the counter is zero
// and the fixed IV is derived from the premaster key.
//
// The cipher suite list below is ordered based on the recommendations from Mozilla.
// When changing the supported cipher suites, please consult the webpage below for
// the preferred order.
//
// https://wiki.mozilla.org/Security/Server_Side_TLS
#define ENUMERATE_CIPHERS(C) \
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
C(true, CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
C(true, CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false) \
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false) \
C(true, CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
C(true, CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
C(true, CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA256, 16, false) \
C(true, CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA256, 16, false) \
C(true, CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
C(true, CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false)
constexpr KeyExchangeAlgorithm get_key_exchange_algorithm(CipherSuite suite)
{
switch (suite) {
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
case suite: \
return key_exchange;
ENUMERATE_CIPHERS(C)
#undef C
default:
return KeyExchangeAlgorithm::Invalid;
}
}
constexpr CipherAlgorithm get_cipher_algorithm(CipherSuite suite)
{
switch (suite) {
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
case suite: \
return cipher;
ENUMERATE_CIPHERS(C)
#undef C
default:
return CipherAlgorithm::Invalid;
}
}
struct Options {
static Vector<CipherSuite> default_usable_cipher_suites()
{ {
m_underlying->on_ready_to_read = [this] { Vector<CipherSuite> cipher_suites;
if (on_ready_to_read) #define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
on_ready_to_read(); if constexpr (is_supported) \
}; cipher_suites.empend(suite);
ENUMERATE_CIPHERS(C)
#undef C
return cipher_suites;
}
Vector<CipherSuite> usable_cipher_suites = default_usable_cipher_suites();
#define OPTION_WITH_DEFAULTS(typ, name, ...) \
static typ default_##name() \
{ \
return typ { __VA_ARGS__ }; \
} \
typ name = default_##name(); \
Options& set_##name(typ new_value)& \
{ \
name = move(new_value); \
return *this; \
} \
Options&& set_##name(typ new_value)&& \
{ \
name = move(new_value); \
return move(*this); \
} }
~WolfTLS(); OPTION_WITH_DEFAULTS(ProtocolVersion, version, ProtocolVersion::VERSION_1_2)
OPTION_WITH_DEFAULTS(Vector<SignatureAndHashAlgorithm>, supported_signature_algorithms,
{ HashAlgorithm::SHA512, SignatureAlgorithm::RSA },
{ HashAlgorithm::SHA384, SignatureAlgorithm::RSA },
{ HashAlgorithm::SHA256, SignatureAlgorithm::RSA },
{ HashAlgorithm::SHA1, SignatureAlgorithm::RSA },
{ HashAlgorithm::SHA256, SignatureAlgorithm::ECDSA },
{ HashAlgorithm::SHA384, SignatureAlgorithm::ECDSA },
{ HashAlgorithm::INTRINSIC, SignatureAlgorithm::ED25519 });
OPTION_WITH_DEFAULTS(Vector<SupportedGroup>, elliptic_curves,
SupportedGroup::X25519,
SupportedGroup::SECP256R1,
SupportedGroup::SECP384R1,
SupportedGroup::X448)
OPTION_WITH_DEFAULTS(Vector<ECPointFormat>, supported_ec_point_formats, ECPointFormat::UNCOMPRESSED)
static void install_certificate_store_paths(Vector<ByteString>); OPTION_WITH_DEFAULTS(bool, use_sni, true)
OPTION_WITH_DEFAULTS(bool, use_compression, false)
OPTION_WITH_DEFAULTS(bool, validate_certificates, true)
OPTION_WITH_DEFAULTS(bool, allow_self_signed_certificates, false)
OPTION_WITH_DEFAULTS(Optional<Vector<Certificate>>, root_certificates, )
OPTION_WITH_DEFAULTS(Function<void(AlertDescription)>, alert_handler, [](auto) {})
OPTION_WITH_DEFAULTS(Function<void()>, finish_callback, [] {})
OPTION_WITH_DEFAULTS(Function<Vector<Certificate>()>, certificate_provider, [] { return Vector<Certificate> {}; })
OPTION_WITH_DEFAULTS(bool, enable_extended_master_secret, true)
static ErrorOr<NonnullOwnPtr<WolfTLS>> connect(ByteString const& host, u16 port); #undef OPTION_WITH_DEFAULTS
ErrorOr<Bytes> read_some(Bytes bytes) override; };
ErrorOr<size_t> write_some(ReadonlyBytes bytes) override;
bool is_eof() const override; class SegmentedBuffer {
bool is_open() const override; public:
void close() override; [[nodiscard]] size_t size() const { return m_size; }
ErrorOr<size_t> pending_bytes() const override; [[nodiscard]] bool is_empty() const { return m_size == 0; }
ErrorOr<bool> can_read_without_blocking(int timeout) const override; void transfer(Bytes dest, size_t size)
ErrorOr<void> set_blocking(bool enabled) override; {
ErrorOr<void> set_close_on_exec(bool enabled) override { return m_underlying->set_close_on_exec(enabled); } VERIFY(size <= dest.size());
void set_notifications_enabled(bool enabled) override { m_underlying->set_notifications_enabled(enabled); } size_t transferred = 0;
while (transferred < size) {
auto& buffer = m_buffers.head();
size_t to_transfer = min(buffer.size() - m_offset_into_current_buffer, size - transferred);
memcpy(dest.offset(transferred), buffer.data() + m_offset_into_current_buffer, to_transfer);
transferred += to_transfer;
m_offset_into_current_buffer += to_transfer;
if (m_offset_into_current_buffer >= buffer.size()) {
m_buffers.dequeue();
m_offset_into_current_buffer = 0;
}
m_size -= to_transfer;
}
}
AK::ErrorOr<void> try_append(ReadonlyBytes data)
{
if (Checked<size_t>::addition_would_overflow(m_size, data.size()))
return AK::Error::from_errno(EOVERFLOW);
m_size += data.size();
m_buffers.enqueue(TRY(ByteBuffer::copy(data)));
return {};
}
private: private:
static StringView error_text(WOLFSSL*, int error_code); size_t m_size { 0 };
Queue<ByteBuffer> m_buffers;
WOLFSSL_CTX* m_context { nullptr }; size_t m_offset_into_current_buffer { 0 };
WOLFSSL* m_ssl { nullptr };
NonnullOwnPtr<Core::TCPSocket> m_underlying;
}; };
struct Context {
bool verify_chain(StringView host) const;
bool verify_certificate_pair(Certificate const& subject, Certificate const& issuer) const;
Options options;
u8 remote_random[32];
u8 local_random[32];
u8 session_id[32];
u8 session_id_size { 0 };
CipherSuite cipher;
bool is_server { false };
Vector<Certificate> certificates;
Certificate private_key;
Vector<Certificate> client_certificates;
ByteBuffer master_key;
ByteBuffer premaster_key;
u8 cipher_spec_set { 0 };
struct {
int created { 0 };
u8 remote_mac[32];
u8 local_mac[32];
u8 local_iv[16];
u8 remote_iv[16];
u8 local_aead_iv[4];
u8 remote_aead_iv[4];
} crypto;
Crypto::Hash::Manager handshake_hash;
ByteBuffer message_buffer;
u64 remote_sequence_number { 0 };
u64 local_sequence_number { 0 };
ConnectionStatus connection_status { ConnectionStatus::Disconnected };
bool should_expect_successful_read { false };
u8 critical_error { 0 };
Error error_code { Error::NoError };
ByteBuffer tls_buffer;
SegmentedBuffer application_buffer;
bool is_child { false };
struct {
// Server Name Indicator
ByteString SNI; // I hate your existence
bool extended_master_secret { false };
} extensions;
u8 request_client_certificate { 0 };
ByteBuffer cached_handshake;
ClientVerificationStaus client_verified { Verified };
bool connection_finished { false };
bool close_notify { false };
bool has_invoked_finish_or_error_callback { false };
// message flags
u8 handshake_messages[11] { 0 };
ByteBuffer user_data;
HashMap<ByteString, Certificate> root_certificates;
Vector<ByteString> alpn;
StringView negotiated_alpn;
size_t send_retries { 0 };
time_t handshake_initiation_timestamp { 0 };
struct {
ByteBuffer p;
ByteBuffer g;
ByteBuffer Ys;
} server_diffie_hellman_params;
OwnPtr<Crypto::Curves::EllipticCurve> server_key_exchange_curve;
};
class TLSv12 final : public Core::Socket {
private:
Core::Socket& underlying_stream()
{
return *m_stream.visit([&](auto& stream) -> Core::Socket* { return stream; });
}
Core::Socket const& underlying_stream() const
{
return *m_stream.visit([&](auto& stream) -> Core::Socket const* { return stream; });
}
public:
/// Reads into a buffer, with the maximum size being the size of the buffer.
/// The amount of bytes read can be smaller than the size of the buffer.
/// Returns either the bytes that were read, or an errno in the case of
/// failure.
virtual ErrorOr<Bytes> read_some(Bytes) override;
/// Tries to write the entire contents of the buffer. It is possible for
/// less than the full buffer to be written. Returns either the amount of
/// bytes written into the stream, or an errno in the case of failure.
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
virtual bool is_eof() const override { return m_context.application_buffer.is_empty() && (m_context.connection_finished || underlying_stream().is_eof()); }
virtual bool is_open() const override { return is_established(); }
virtual void close() override;
virtual ErrorOr<size_t> pending_bytes() const override { return m_context.application_buffer.size(); }
virtual ErrorOr<bool> can_read_without_blocking(int = 0) const override { return !m_context.application_buffer.is_empty(); }
virtual ErrorOr<void> set_blocking(bool block) override
{
VERIFY(!block);
return {};
}
virtual ErrorOr<void> set_close_on_exec(bool enabled) override { return underlying_stream().set_close_on_exec(enabled); }
virtual void set_notifications_enabled(bool enabled) override { underlying_stream().set_notifications_enabled(enabled); }
static ErrorOr<NonnullOwnPtr<TLSv12>> connect(ByteString const& host, u16 port, Options = {});
static ErrorOr<NonnullOwnPtr<TLSv12>> connect(ByteString const& host, Core::Socket& underlying_stream, Options = {});
using StreamVariantType = Variant<OwnPtr<Core::Socket>, Core::Socket*>;
explicit TLSv12(StreamVariantType, Options);
bool is_established() const { return m_context.connection_status == ConnectionStatus::Established; }
void set_sni(StringView sni)
{
if (m_context.is_server || m_context.critical_error || m_context.connection_status != ConnectionStatus::Disconnected) {
dbgln("invalid state for set_sni");
return;
}
m_context.extensions.SNI = sni;
}
void set_root_certificates(Vector<Certificate>);
static Vector<Certificate> parse_pem_certificate(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes key_pem_buffer);
StringView alpn() const { return m_context.negotiated_alpn; }
bool supports_cipher(CipherSuite suite) const
{
switch (suite) {
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
case suite: \
return is_supported;
ENUMERATE_CIPHERS(C)
#undef C
default:
return false;
}
}
bool supports_version(ProtocolVersion v) const
{
return v == ProtocolVersion::VERSION_1_2;
}
void alert(AlertLevel, AlertDescription);
Function<void(AlertDescription)> on_tls_error;
Function<void()> on_tls_finished;
Function<void(TLSv12&)> on_tls_certificate_request;
Function<void()> on_connected;
private:
void setup_connection();
void consume(ReadonlyBytes record);
ByteBuffer hmac_message(ReadonlyBytes buf, Optional<ReadonlyBytes> const buf2, size_t mac_length, bool local = false);
void ensure_hmac(size_t digest_size, bool local);
void update_packet(ByteBuffer& packet);
void update_hash(ReadonlyBytes in, size_t header_size);
void write_packet(ByteBuffer& packet, bool immediately = false);
ByteBuffer build_client_key_exchange();
ByteBuffer build_server_key_exchange();
ByteBuffer build_hello();
ByteBuffer build_handshake_finished();
ByteBuffer build_certificate();
ByteBuffer build_alert(bool critical, u8 code);
ByteBuffer build_change_cipher_spec();
void build_rsa_pre_master_secret(PacketBuilder&);
void build_dhe_rsa_pre_master_secret(PacketBuilder&);
void build_ecdhe_rsa_pre_master_secret(PacketBuilder&);
ErrorOr<bool> flush();
void write_into_socket();
ErrorOr<void> read_from_socket();
bool check_connection_state(bool read);
void notify_client_for_app_data();
ssize_t handle_server_hello(ReadonlyBytes, WritePacketStage&);
ssize_t handle_handshake_finished(ReadonlyBytes, WritePacketStage&);
ssize_t handle_certificate(ReadonlyBytes);
ssize_t handle_server_key_exchange(ReadonlyBytes);
ssize_t handle_dhe_rsa_server_key_exchange(ReadonlyBytes);
ssize_t handle_ecdhe_server_key_exchange(ReadonlyBytes, u8& server_public_key_length);
ssize_t handle_ecdhe_rsa_server_key_exchange(ReadonlyBytes);
ssize_t handle_ecdhe_ecdsa_server_key_exchange(ReadonlyBytes);
ssize_t handle_server_hello_done(ReadonlyBytes);
ssize_t handle_certificate_verify(ReadonlyBytes);
ssize_t handle_handshake_payload(ReadonlyBytes);
ssize_t handle_message(ReadonlyBytes);
void pseudorandom_function(Bytes output, ReadonlyBytes secret, u8 const* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b);
ssize_t verify_rsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer);
ssize_t verify_ecdsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer);
size_t key_length() const
{
switch (m_context.cipher) {
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
case suite: \
return cipher_key_size(cipher) / 8;
ENUMERATE_CIPHERS(C)
#undef C
default:
return 128 / 8;
}
}
size_t mac_length() const
{
switch (m_context.cipher) {
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
case suite: \
return hash ::digest_size();
ENUMERATE_CIPHERS(C)
#undef C
default:
return Crypto::Hash::SHA256::digest_size();
}
}
Crypto::Hash::HashKind hmac_hash() const
{
switch (mac_length()) {
case Crypto::Hash::SHA512::DigestSize:
return Crypto::Hash::HashKind::SHA512;
case Crypto::Hash::SHA384::DigestSize:
return Crypto::Hash::HashKind::SHA384;
case Crypto::Hash::SHA256::DigestSize:
case Crypto::Hash::SHA1::DigestSize:
default:
return Crypto::Hash::HashKind::SHA256;
}
}
size_t iv_length() const
{
switch (m_context.cipher) {
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
case suite: \
return iv_size;
ENUMERATE_CIPHERS(C)
#undef C
default:
return 16;
}
}
bool is_aead() const
{
switch (m_context.cipher) {
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
case suite: \
return is_aead;
ENUMERATE_CIPHERS(C)
#undef C
default:
return false;
}
}
bool expand_key();
bool compute_master_secret_from_pre_master_secret(size_t length);
void try_disambiguate_error() const;
bool m_eof { false };
StreamVariantType m_stream;
Context m_context;
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_local;
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_remote;
using CipherVariant = Variant<
Empty,
Crypto::Cipher::AESCipher::CBCMode,
Crypto::Cipher::AESCipher::GCMMode>;
CipherVariant m_cipher_local {};
CipherVariant m_cipher_remote {};
bool m_has_scheduled_write_flush { false };
bool m_has_scheduled_app_data_flush { false };
i32 m_max_wait_time_for_handshake_in_seconds { 10 };
RefPtr<Core::Timer> m_handshake_timeout_timer;
};
} }

View file

@ -44,15 +44,13 @@ void WebSocketImplSerenity::connect(ConnectionInfo const& connection_info)
auto socket_result = [&]() -> ErrorOr<NonnullOwnPtr<Core::BufferedSocketBase>> { auto socket_result = [&]() -> ErrorOr<NonnullOwnPtr<Core::BufferedSocketBase>> {
auto host = TRY(connection_info.url().serialized_host()).to_byte_string(); auto host = TRY(connection_info.url().serialized_host()).to_byte_string();
if (connection_info.is_secure()) { if (connection_info.is_secure()) {
auto result = TLS::WolfTLS::connect(host, connection_info.url().port_or_default()); TLS::Options options;
if (result.is_error()) { options.set_alert_handler([this](auto) {
Core::deferred_invoke([this] {
on_connection_error(); on_connection_error();
}); });
return result.release_error();
}
return TRY(Core::BufferedSocket<TLS::WolfTLS>::create(result.release_value())); return TRY(Core::BufferedSocket<TLS::TLSv12>::create(
TRY(TLS::TLSv12::connect(host, connection_info.url().port_or_default(), move(options)))));
} }
return TRY(Core::BufferedTCPSocket::create( return TRY(Core::BufferedTCPSocket::create(

View file

@ -616,8 +616,7 @@ void WebSocket::fatal_error(WebSocket::Error error)
void WebSocket::discard_connection() void WebSocket::discard_connection()
{ {
deferred_invoke([this] { deferred_invoke([this] {
if (!m_impl) VERIFY(m_impl);
return;
m_impl->discard_connection(); m_impl->discard_connection();
m_impl->on_connection_error = nullptr; m_impl->on_connection_error = nullptr;
m_impl->on_connected = nullptr; m_impl->on_connected = nullptr;

View file

@ -19,5 +19,4 @@ set(GENERATED_SOURCES
) )
serenity_bin(RequestServer) serenity_bin(RequestServer)
target_link_libraries(RequestServer PRIVATE LibCore LibCrypto LibIPC LibHTTP LibMain LibTLS LibWebSocket LibURL LibThreading) target_link_libraries(RequestServer PRIVATE LibCore LibCrypto LibIPC LibHTTP LibMain LibTLS LibWebSocket LibURL LibThreading)

View file

@ -12,7 +12,7 @@
namespace RequestServer::ConnectionCache { namespace RequestServer::ConnectionCache {
Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache {}; Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache {};
Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::WolfTLS>>>>>> g_tls_connection_cache {}; Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::TLSv12>>>>>> g_tls_connection_cache {};
Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties; Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties;
void request_did_finish(URL::URL const& url, Core::Socket const* socket) void request_did_finish(URL::URL const& url, Core::Socket const* socket)
@ -121,7 +121,7 @@ void request_did_finish(URL::URL const& url, Core::Socket const* socket)
} }
}; };
if (is<Core::BufferedSocket<TLS::WolfTLS>>(socket)) if (is<Core::BufferedSocket<TLS::TLSv12>>(socket))
fire_off_next_job(g_tls_connection_cache); fire_off_next_job(g_tls_connection_cache);
else if (is<Core::BufferedSocket<Core::Socket>>(socket)) else if (is<Core::BufferedSocket<Core::Socket>>(socket))
fire_off_next_job(g_tcp_connection_cache); fire_off_next_job(g_tcp_connection_cache);

View file

@ -57,6 +57,7 @@ struct Proxy {
struct JobData { struct JobData {
Function<void(Core::BufferedSocketBase&)> start {}; Function<void(Core::BufferedSocketBase&)> start {};
Function<void(Core::NetworkJob::Error)> fail {}; Function<void(Core::NetworkJob::Error)> fail {};
Function<Vector<TLS::Certificate>()> provide_client_certificates {};
struct TimingInfo { struct TimingInfo {
#if REQUESTSERVER_DEBUG #if REQUESTSERVER_DEBUG
bool valid { true }; bool valid { true };
@ -68,9 +69,10 @@ struct JobData {
#endif #endif
} timing_info {}; } timing_info {};
JobData(Function<void(Core::BufferedSocketBase&)> start, Function<void(Core::NetworkJob::Error)> fail, TimingInfo timing_info) JobData(Function<void(Core::BufferedSocketBase&)> start, Function<void(Core::NetworkJob::Error)> fail, Function<Vector<TLS::Certificate>()> provide_client_certificates, TimingInfo timing_info)
: start(move(start)) : start(move(start))
, fail(move(fail)) , fail(move(fail))
, provide_client_certificates(move(provide_client_certificates))
, timing_info(move(timing_info)) , timing_info(move(timing_info))
{ {
} }
@ -78,6 +80,7 @@ struct JobData {
JobData(JobData&& other) JobData(JobData&& other)
: start(move(other.start)) : start(move(other.start))
, fail(move(other.fail)) , fail(move(other.fail))
, provide_client_certificates(move(other.provide_client_certificates))
, timing_info(move(other.timing_info)) , timing_info(move(other.timing_info))
{ {
#if REQUESTSERVER_DEBUG #if REQUESTSERVER_DEBUG
@ -103,6 +106,16 @@ struct JobData {
return JobData { return JobData {
[job](auto& socket) { job->start(socket); }, [job](auto& socket) { job->start(socket); },
[job](auto error) { job->fail(error); }, [job](auto error) { job->fail(error); },
[job] {
if constexpr (requires { job->on_certificate_requested; }) {
if (job->on_certificate_requested)
return job->on_certificate_requested();
} else {
// "use" `job`, otherwise clang gets sad.
(void)job;
}
return Vector<TLS::Certificate> {};
},
{ {
#if REQUESTSERVER_DEBUG #if REQUESTSERVER_DEBUG
.timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise), .timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise),
@ -142,7 +155,7 @@ struct ConnectionKey {
bool operator==(ConnectionKey const&) const = default; bool operator==(ConnectionKey const&) const = default;
}; };
} };
template<> template<>
struct AK::Traits<RequestServer::ConnectionCache::ConnectionKey> : public AK::DefaultTraits<RequestServer::ConnectionCache::ConnectionKey> { struct AK::Traits<RequestServer::ConnectionCache::ConnectionKey> : public AK::DefaultTraits<RequestServer::ConnectionCache::ConnectionKey> {
@ -159,7 +172,7 @@ struct InferredServerProperties {
}; };
extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache; extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache;
extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::WolfTLS>>>>>> g_tls_connection_cache; extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::TLSv12>>>>>> g_tls_connection_cache;
extern Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties; extern Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties;
void request_did_finish(URL::URL const&, Core::Socket const*); void request_did_finish(URL::URL const&, Core::Socket const*);
@ -183,7 +196,29 @@ ErrorOr<void> recreate_socket_if_needed(T& connection, URL::URL const& url)
return {}; return {};
}; };
if constexpr (IsSame<TLS::TLSv12, SocketType>) {
TLS::Options options;
options.set_alert_handler([&connection](TLS::AlertDescription alert) {
Core::NetworkJob::Error reason;
if (alert == TLS::AlertDescription::HANDSHAKE_FAILURE)
reason = Core::NetworkJob::Error::ProtocolFailed;
else if (alert == TLS::AlertDescription::DECRYPT_ERROR)
reason = Core::NetworkJob::Error::ConnectionFailed;
else
reason = Core::NetworkJob::Error::TransmissionFailed;
if (connection.job_data->fail)
connection.job_data->fail(reason);
});
options.set_certificate_provider([&connection]() -> Vector<TLS::Certificate> {
if (connection.job_data->provide_client_certificates)
return connection.job_data->provide_client_certificates();
return {};
});
TRY(set_socket(TRY((connection.proxy.template tunnel<SocketType, SocketStorageType>(url, move(options))))));
} else {
TRY(set_socket(TRY((connection.proxy.template tunnel<SocketType, SocketStorageType>(url))))); TRY(set_socket(TRY((connection.proxy.template tunnel<SocketType, SocketStorageType>(url)))));
}
dbgln_if(REQUESTSERVER_DEBUG, "Creating a new socket for {} -> {}", url, connection.socket.ptr()); dbgln_if(REQUESTSERVER_DEBUG, "Creating a new socket for {} -> {}", url, connection.socket.ptr());
} }
return {}; return {};

View file

@ -26,6 +26,10 @@ ErrorOr<int> serenity_main(Main::Arguments)
TRY(Core::System::pledge("stdio inet accept thread unix rpath sendfd recvfd")); TRY(Core::System::pledge("stdio inet accept thread unix rpath sendfd recvfd"));
// Ensure the certificates are read out here.
// FIXME: Allow specifying extra certificates on the command line, or in other configuration.
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
Core::EventLoop event_loop; Core::EventLoop event_loop;
// FIXME: Establish a connection to LookupServer and then drop "unix"? // FIXME: Establish a connection to LookupServer and then drop "unix"?
TRY(Core::System::unveil("/tmp/portal/lookup", "rw")); TRY(Core::System::unveil("/tmp/portal/lookup", "rw"));

View file

@ -33,7 +33,6 @@
}, },
"sqlite3", "sqlite3",
"woff2", "woff2",
"wolfssl",
{ {
"name": "vulkan", "name": "vulkan",
"platform": "!android" "platform": "!android"
@ -71,10 +70,6 @@
{ {
"name": "woff2", "name": "woff2",
"version": "1.0.2#4" "version": "1.0.2#4"
},
{
"name": "wolfssl",
"version": "5.7.0#1"
} }
] ]
} }