diff --git a/Ladybird/Android/src/main/cpp/RequestServerService.cpp b/Ladybird/Android/src/main/cpp/RequestServerService.cpp index 75278a5801d..b72361994b4 100644 --- a/Ladybird/Android/src/main/cpp/RequestServerService.cpp +++ b/Ladybird/Android/src/main/cpp/RequestServerService.cpp @@ -31,7 +31,8 @@ ErrorOr find_certificates(StringView serenity_resource_root) ErrorOr service_main(int ipc_socket) { // 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; diff --git a/Ladybird/RequestServer/main.cpp b/Ladybird/RequestServer/main.cpp index 420d789f4c0..90544511bcd 100644 --- a/Ladybird/RequestServer/main.cpp +++ b/Ladybird/RequestServer/main.cpp @@ -48,8 +48,8 @@ ErrorOr serenity_main(Main::Arguments arguments) // Ensure the certificates are read out here. if (certificates.is_empty()) certificates.append(TRY(find_certificates(serenity_resource_root))); - - TLS::WolfTLS::install_certificate_store_paths(move(certificates)); + DefaultRootCACertificates::set_default_certificate_paths(certificates.span()); + [[maybe_unused]] auto& certs = DefaultRootCACertificates::the(); Core::EventLoop event_loop; diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index 2f2a98baae0..3f9a30ad765 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -530,6 +530,7 @@ if (BUILD_TESTING) endforeach() # 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) # The FLAC tests need a special working directory to find the test files diff --git a/README.md b/README.md index 938722ec906..d6577596e36 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,7 @@ At the moment, many core library support components are inherited from SerenityO - LibWeb: Web rendering engine - LibJS: JavaScript engine - LibWasm: WebAssembly implementation -- LibCrypto: Cryptography primitives -- LibTLS: Some certificate parsing primitives +- LibCrypto/LibTLS: Cryptography primitives and Transport Layer Security - LibHTTP: HTTP/1.1 client - LibGfx: 2D Graphics Library, Image Decoding and Rendering - LibArchive: Archive file format support diff --git a/Tests/LibTLS/CMakeLists.txt b/Tests/LibTLS/CMakeLists.txt index ce4eaa9ffcc..7badc93a4f2 100644 --- a/Tests/LibTLS/CMakeLists.txt +++ b/Tests/LibTLS/CMakeLists.txt @@ -1,5 +1,6 @@ set(TEST_SOURCES TestTLSCertificateParser.cpp + TestTLSHandshake.cpp ) foreach(source IN LISTS TEST_SOURCES) diff --git a/Tests/LibTLS/TestTLSHandshake.cpp b/Tests/LibTLS/TestTLSHandshake.cpp new file mode 100644 index 00000000000..2f42c29fbb6 --- /dev/null +++ b/Tests/LibTLS/TestTLSHandshake.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021, Peter Bocan + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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> 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> 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(); +} diff --git a/Userland/Libraries/LibCore/Socket.h b/Userland/Libraries/LibCore/Socket.h index df8f622dd1c..bcac9803ea7 100644 --- a/Userland/Libraries/LibCore/Socket.h +++ b/Userland/Libraries/LibCore/Socket.h @@ -196,8 +196,6 @@ public: virtual ~TCPSocket() override { close(); } - int fd() { return m_helper.fd(); } - private: explicit TCPSocket(PreventSIGPIPE prevent_sigpipe = PreventSIGPIPE::Yes) : Socket(prevent_sigpipe) diff --git a/Userland/Libraries/LibCrypto/Authentication/HMAC.h b/Userland/Libraries/LibCrypto/Authentication/HMAC.h index 45262d6bf5c..c9baee936c9 100644 --- a/Userland/Libraries/LibCrypto/Authentication/HMAC.h +++ b/Userland/Libraries/LibCrypto/Authentication/HMAC.h @@ -13,11 +13,11 @@ #include #include -namespace Crypto::Authentication { - constexpr static auto IPAD = 0x36; constexpr static auto OPAD = 0x5c; +namespace Crypto::Authentication { + template class HMAC { public: diff --git a/Userland/Libraries/LibHTTP/HttpsJob.cpp b/Userland/Libraries/LibHTTP/HttpsJob.cpp index 2c158bef656..28a2a5f6010 100644 --- a/Userland/Libraries/LibHTTP/HttpsJob.cpp +++ b/Userland/Libraries/LibHTTP/HttpsJob.cpp @@ -10,8 +10,7 @@ namespace HTTP { void HttpsJob::set_certificate(ByteString certificate, ByteString key) { - (void)certificate; - (void)key; + m_received_client_certificates = TLS::TLSv12::parse_pem_certificate(certificate.bytes(), key.bytes()); } } diff --git a/Userland/Libraries/LibTLS/CMakeLists.txt b/Userland/Libraries/LibTLS/CMakeLists.txt index 02dfd4bc761..13ce1c6ba16 100644 --- a/Userland/Libraries/LibTLS/CMakeLists.txt +++ b/Userland/Libraries/LibTLS/CMakeLists.txt @@ -2,12 +2,16 @@ add_compile_options(-Wvla) set(SOURCES Certificate.cpp + Handshake.cpp + HandshakeCertificate.cpp + HandshakeClient.cpp + HandshakeServer.cpp + Record.cpp + Socket.cpp TLSv12.cpp ) -find_package(WolfSSL REQUIRED) - 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) diff --git a/Userland/Libraries/LibTLS/Certificate.h b/Userland/Libraries/LibTLS/Certificate.h index d57665c9640..69559b4482f 100644 --- a/Userland/Libraries/LibTLS/Certificate.h +++ b/Userland/Libraries/LibTLS/Certificate.h @@ -293,6 +293,25 @@ public: private: Optional m_is_self_signed; }; + +class DefaultRootCACertificates { +public: + DefaultRootCACertificates(); + + Vector const& certificates() const { return m_ca_certificates; } + + static ErrorOr> parse_pem_root_certificate_authorities(ByteBuffer&); + static ErrorOr> load_certificates(Span custom_cert_paths = {}); + + static DefaultRootCACertificates& the(); + + static void set_default_certificate_paths(Span paths); + +private: + Vector m_ca_certificates; +}; + } using TLS::Certificate; +using TLS::DefaultRootCACertificates; diff --git a/Userland/Libraries/LibTLS/CipherSuite.h b/Userland/Libraries/LibTLS/CipherSuite.h new file mode 100644 index 00000000000..8f446b58116 --- /dev/null +++ b/Userland/Libraries/LibTLS/CipherSuite.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +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; + } +} + +} diff --git a/Userland/Libraries/LibTLS/Handshake.cpp b/Userland/Libraries/LibTLS/Handshake.cpp new file mode 100644 index 00000000000..72f5993d966 --- /dev/null +++ b/Userland/Libraries/LibTLS/Handshake.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * Copyright (c) 2022, Michiel Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include +#include +#include + +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(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; +} +} diff --git a/Userland/Libraries/LibTLS/HandshakeCertificate.cpp b/Userland/Libraries/LibTLS/HandshakeCertificate.cpp new file mode 100644 index 00000000000..8c79e4afc84 --- /dev/null +++ b/Userland/Libraries/LibTLS/HandshakeCertificate.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include +#include +#include + +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; +} + +} diff --git a/Userland/Libraries/LibTLS/HandshakeClient.cpp b/Userland/Libraries/LibTLS/HandshakeClient.cpp new file mode 100644 index 00000000000..7a62a75062c --- /dev/null +++ b/Userland/Libraries/LibTLS/HandshakeClient.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * Copyright (c) 2022, Michiel Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +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(); + } + + 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 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 certificates; + Vector* 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; +} + +} diff --git a/Userland/Libraries/LibTLS/HandshakeServer.cpp b/Userland/Libraries/LibTLS/HandshakeServer.cpp new file mode 100644 index 00000000000..4f94b95f460 --- /dev/null +++ b/Userland/Libraries/LibTLS/HandshakeServer.cpp @@ -0,0 +1,530 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * Copyright (c) 2022, Michiel Visser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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(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(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(); + break; + case SupportedGroup::X448: + m_context.server_key_exchange_curve = make(); + break; + case SupportedGroup::SECP256R1: + m_context.server_key_exchange_curve = make(); + break; + case SupportedGroup::SECP384R1: + m_context.server_key_exchange_curve = make(); + 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(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(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 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; +} + +} diff --git a/Userland/Libraries/LibTLS/Record.cpp b/Userland/Libraries/LibTLS/Record.cpp new file mode 100644 index 00000000000..be65ba54e7a --- /dev/null +++ b/Userland/Libraries/LibTLS/Record.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +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(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>(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 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(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; +} + +} diff --git a/Userland/Libraries/LibTLS/Socket.cpp b/Userland/Libraries/LibTLS/Socket.cpp new file mode 100644 index 00000000000..495d0e0d553 --- /dev/null +++ b/Userland/Libraries/LibTLS/Socket.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +// 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 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 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> TLSv12::connect(ByteString const& host, u16 port, Options options) +{ + auto promise = Core::Promise::construct(); + OwnPtr tcp_socket = TRY(Core::TCPSocket::connect(host, port)); + TRY(tcp_socket->set_blocking(false)); + auto tls_socket = make(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> TLSv12::connect(ByteString const& host, Core::Socket& underlying_stream, Options options) +{ + auto promise = Core::Promise::construct(); + TRY(underlying_stream.set_blocking(false)); + auto tls_socket = make(&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 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 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 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; +} + +} diff --git a/Userland/Libraries/LibTLS/TLSPacketBuilder.h b/Userland/Libraries/LibTLS/TLSPacketBuilder.h new file mode 100644 index 00000000000..07076638024 --- /dev/null +++ b/Userland/Libraries/LibTLS/TLSPacketBuilder.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020, Ali Mohammad Pur + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +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; +}; + +} diff --git a/Userland/Libraries/LibTLS/TLSv12.cpp b/Userland/Libraries/LibTLS/TLSv12.cpp index b1b5328b82c..c49a3056ddb 100644 --- a/Userland/Libraries/LibTLS/TLSv12.cpp +++ b/Userland/Libraries/LibTLS/TLSv12.cpp @@ -4,162 +4,622 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include #ifndef SOCK_NONBLOCK # include #endif -static Vector s_certificate_store_paths; - namespace TLS { -static thread_local bool g_initialized_wolfssl = false; - -static int errno_to_wolfssl_error(int error) +void TLSv12::consume(ReadonlyBytes record) { - switch (error) { - case EAGAIN: - return WOLFSSL_CBIO_ERR_WANT_READ; - case ETIMEDOUT: - return WOLFSSL_CBIO_ERR_TIMEOUT; - case ECONNRESET: - return WOLFSSL_CBIO_ERR_CONN_RST; - case EINTR: - return WOLFSSL_CBIO_ERR_ISR; - case ECONNREFUSED: - return WOLFSSL_CBIO_ERR_WANT_READ; - case ECONNABORTED: - return WOLFSSL_CBIO_ERR_CONN_CLOSE; - default: - return WOLFSSL_CBIO_ERR_GENERAL; + if (m_context.critical_error) { + dbgln("There has been a critical error ({}), refusing to continue", (i8)m_context.critical_error); + return; + } + + if (record.size() == 0) { + return; + } + + dbgln_if(TLS_DEBUG, "Consuming {} bytes", record.size()); + + if (m_context.message_buffer.try_append(record).is_error()) { + dbgln("Not enough space in message buffer, dropping the record"); + return; + } + + 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); - static char buffer[WOLFSSL_MAX_ERROR_SZ]; - auto* ptr = wolfSSL_ERR_error_string(error, buffer); - return StringView { ptr, strlen(ptr) }; -} + auto now = UnixDateTime::now(); -ErrorOr> WolfTLS::connect(const AK::ByteString& host, u16 port) -{ - if (!g_initialized_wolfssl) { - wolfSSL_Init(); - g_initialized_wolfssl = true; + if (now < validity.not_before) { + dbgln("certificate expired (not yet valid, signed for {})", Core::DateTime::from_timestamp(validity.not_before.seconds_since_epoch())); + return false; } - auto* context = wolfSSL_CTX_new(wolfTLSv1_2_client_method()); - if (!context) - return Error::from_string_literal("Failed to create a new TLS context"); - - 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"); + if (validity.not_after < now) { + dbgln("certificate expired (expiry date {})", Core::DateTime::from_timestamp(validity.not_after.seconds_since_epoch())); + return false; } - 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(ctx); - auto result = self.m_underlying->write_some({ buf, static_cast(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(result.value()); - }); - wolfSSL_SSLSetIORecv(ssl, [](WOLFSSL*, char* buf, int sz, void* ctx) -> int { - auto& self = *static_cast(ctx); - auto result = self.m_underlying->read_some({ buf, static_cast(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(result.value().size()); - }); - - auto tcp_socket = TRY(Core::TCPSocket::connect(host, port)); - - auto object = make(context, ssl, move(tcp_socket)); - wolfSSL_SetIOReadCtx(ssl, static_cast(object.ptr())); - wolfSSL_SetIOWriteCtx(ssl, static_cast(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 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(result) }; -} - -ErrorOr 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(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 WolfTLS::pending_bytes() const -{ - return wolfSSL_pending(m_ssl); -} - -ErrorOr WolfTLS::can_read_without_blocking(int) const -{ return true; } -ErrorOr WolfTLS::set_blocking(bool) +// https://www.ietf.org/rfc/rfc5280.html#page-12 +bool Certificate::is_self_signed() { - return {}; + 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 WolfTLS::install_certificate_store_paths(Vector paths) +void TLSv12::try_disambiguate_error() const { - s_certificate_store_paths = move(paths); + 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 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 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(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 +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>(output, secret, label, label_length, seed, seed_b); + break; + case Crypto::Hash::HashKind::SHA384: + hmac_pseudorandom_function>(output, secret, label, label_length, seed, seed_b); + break; + case Crypto::Hash::HashKind::SHA256: + hmac_pseudorandom_function>(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 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 {}; + } + + 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) }; +} + +static Vector s_default_ca_certificate_paths; + +void DefaultRootCACertificates::set_default_certificate_paths(Span 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> DefaultRootCACertificates::load_certificates(Span 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> DefaultRootCACertificates::parse_pem_root_certificate_authorities(ByteBuffer& data) +{ + Vector 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; +} } diff --git a/Userland/Libraries/LibTLS/TLSv12.h b/Userland/Libraries/LibTLS/TLSv12.h index 7f57c7799ee..806fd9ff9b0 100644 --- a/Userland/Libraries/LibTLS/TLSv12.h +++ b/Userland/Libraries/LibTLS/TLSv12.h @@ -6,48 +6,546 @@ #pragma once +#include "Certificate.h" #include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -struct WOLFSSL_CTX; -struct WOLFSSL; namespace TLS { -class WolfTLS final : public Core::Socket { -public: - WolfTLS(WOLFSSL_CTX* context, WOLFSSL* ssl, NonnullOwnPtr underlying) - : m_context(context) - , m_ssl(ssl) - , m_underlying(move(underlying)) +inline void print_buffer(ReadonlyBytes buffer) +{ + dbgln("{:hex-dump}", buffer); +} + +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 default_usable_cipher_suites() { - m_underlying->on_ready_to_read = [this] { - if (on_ready_to_read) - on_ready_to_read(); - }; + Vector cipher_suites; +#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \ + if constexpr (is_supported) \ + cipher_suites.empend(suite); + ENUMERATE_CIPHERS(C) +#undef C + return cipher_suites; + } + Vector 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, 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, elliptic_curves, + SupportedGroup::X25519, + SupportedGroup::SECP256R1, + SupportedGroup::SECP384R1, + SupportedGroup::X448) + OPTION_WITH_DEFAULTS(Vector, supported_ec_point_formats, ECPointFormat::UNCOMPRESSED) - static void install_certificate_store_paths(Vector); + 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>, root_certificates, ) + OPTION_WITH_DEFAULTS(Function, alert_handler, [](auto) {}) + OPTION_WITH_DEFAULTS(Function, finish_callback, [] {}) + OPTION_WITH_DEFAULTS(Function()>, certificate_provider, [] { return Vector {}; }) + OPTION_WITH_DEFAULTS(bool, enable_extended_master_secret, true) - static ErrorOr> connect(ByteString const& host, u16 port); - ErrorOr read_some(Bytes bytes) override; - ErrorOr write_some(ReadonlyBytes bytes) override; - bool is_eof() const override; - bool is_open() const override; - void close() override; - ErrorOr pending_bytes() const override; - ErrorOr can_read_without_blocking(int timeout) const override; - ErrorOr set_blocking(bool enabled) override; - ErrorOr set_close_on_exec(bool enabled) override { return m_underlying->set_close_on_exec(enabled); } - void set_notifications_enabled(bool enabled) override { m_underlying->set_notifications_enabled(enabled); } +#undef OPTION_WITH_DEFAULTS +}; + +class SegmentedBuffer { +public: + [[nodiscard]] size_t size() const { return m_size; } + [[nodiscard]] bool is_empty() const { return m_size == 0; } + void transfer(Bytes dest, size_t size) + { + VERIFY(size <= dest.size()); + 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 try_append(ReadonlyBytes data) + { + if (Checked::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: - static StringView error_text(WOLFSSL*, int error_code); - - WOLFSSL_CTX* m_context { nullptr }; - WOLFSSL* m_ssl { nullptr }; - NonnullOwnPtr m_underlying; + size_t m_size { 0 }; + Queue m_buffers; + size_t m_offset_into_current_buffer { 0 }; }; + +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 certificates; + Certificate private_key; + Vector 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 root_certificates; + + Vector 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 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 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 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 pending_bytes() const override { return m_context.application_buffer.size(); } + virtual ErrorOr can_read_without_blocking(int = 0) const override { return !m_context.application_buffer.is_empty(); } + virtual ErrorOr set_blocking(bool block) override + { + VERIFY(!block); + return {}; + } + virtual ErrorOr 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> connect(ByteString const& host, u16 port, Options = {}); + static ErrorOr> connect(ByteString const& host, Core::Socket& underlying_stream, Options = {}); + + using StreamVariantType = Variant, 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); + + static Vector 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 on_tls_error; + Function on_tls_finished; + Function on_tls_certificate_request; + Function on_connected; + +private: + void setup_connection(); + + void consume(ReadonlyBytes record); + + ByteBuffer hmac_message(ReadonlyBytes buf, Optional 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 flush(); + void write_into_socket(); + ErrorOr 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> m_hmac_local; + OwnPtr> 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 m_handshake_timeout_timer; +}; + } diff --git a/Userland/Libraries/LibWebSocket/Impl/WebSocketImplSerenity.cpp b/Userland/Libraries/LibWebSocket/Impl/WebSocketImplSerenity.cpp index d8e20cf768d..3aa0d5cf416 100644 --- a/Userland/Libraries/LibWebSocket/Impl/WebSocketImplSerenity.cpp +++ b/Userland/Libraries/LibWebSocket/Impl/WebSocketImplSerenity.cpp @@ -44,15 +44,13 @@ void WebSocketImplSerenity::connect(ConnectionInfo const& connection_info) auto socket_result = [&]() -> ErrorOr> { auto host = TRY(connection_info.url().serialized_host()).to_byte_string(); if (connection_info.is_secure()) { - auto result = TLS::WolfTLS::connect(host, connection_info.url().port_or_default()); - if (result.is_error()) { - Core::deferred_invoke([this] { - on_connection_error(); - }); - return result.release_error(); - } + TLS::Options options; + options.set_alert_handler([this](auto) { + on_connection_error(); + }); - return TRY(Core::BufferedSocket::create(result.release_value())); + return TRY(Core::BufferedSocket::create( + TRY(TLS::TLSv12::connect(host, connection_info.url().port_or_default(), move(options))))); } return TRY(Core::BufferedTCPSocket::create( diff --git a/Userland/Libraries/LibWebSocket/WebSocket.cpp b/Userland/Libraries/LibWebSocket/WebSocket.cpp index 6b8c31d1578..3720c3e99cc 100644 --- a/Userland/Libraries/LibWebSocket/WebSocket.cpp +++ b/Userland/Libraries/LibWebSocket/WebSocket.cpp @@ -616,8 +616,7 @@ void WebSocket::fatal_error(WebSocket::Error error) void WebSocket::discard_connection() { deferred_invoke([this] { - if (!m_impl) - return; + VERIFY(m_impl); m_impl->discard_connection(); m_impl->on_connection_error = nullptr; m_impl->on_connected = nullptr; diff --git a/Userland/Services/RequestServer/CMakeLists.txt b/Userland/Services/RequestServer/CMakeLists.txt index 09032cf394d..e4a4b01dd60 100644 --- a/Userland/Services/RequestServer/CMakeLists.txt +++ b/Userland/Services/RequestServer/CMakeLists.txt @@ -19,5 +19,4 @@ set(GENERATED_SOURCES ) serenity_bin(RequestServer) - target_link_libraries(RequestServer PRIVATE LibCore LibCrypto LibIPC LibHTTP LibMain LibTLS LibWebSocket LibURL LibThreading) diff --git a/Userland/Services/RequestServer/ConnectionCache.cpp b/Userland/Services/RequestServer/ConnectionCache.cpp index 5f7f8f0007c..c7dfdd13538 100644 --- a/Userland/Services/RequestServer/ConnectionCache.cpp +++ b/Userland/Services/RequestServer/ConnectionCache.cpp @@ -12,7 +12,7 @@ namespace RequestServer::ConnectionCache { Threading::RWLockProtected>>>>> g_tcp_connection_cache {}; -Threading::RWLockProtected>>>>> g_tls_connection_cache {}; +Threading::RWLockProtected>>>>> g_tls_connection_cache {}; Threading::RWLockProtected> g_inferred_server_properties; 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>(socket)) + if (is>(socket)) fire_off_next_job(g_tls_connection_cache); else if (is>(socket)) fire_off_next_job(g_tcp_connection_cache); diff --git a/Userland/Services/RequestServer/ConnectionCache.h b/Userland/Services/RequestServer/ConnectionCache.h index 7807bdb85ea..8a5482c8205 100644 --- a/Userland/Services/RequestServer/ConnectionCache.h +++ b/Userland/Services/RequestServer/ConnectionCache.h @@ -57,6 +57,7 @@ struct Proxy { struct JobData { Function start {}; Function fail {}; + Function()> provide_client_certificates {}; struct TimingInfo { #if REQUESTSERVER_DEBUG bool valid { true }; @@ -68,9 +69,10 @@ struct JobData { #endif } timing_info {}; - JobData(Function start, Function fail, TimingInfo timing_info) + JobData(Function start, Function fail, Function()> provide_client_certificates, TimingInfo timing_info) : start(move(start)) , fail(move(fail)) + , provide_client_certificates(move(provide_client_certificates)) , timing_info(move(timing_info)) { } @@ -78,6 +80,7 @@ struct JobData { JobData(JobData&& other) : start(move(other.start)) , fail(move(other.fail)) + , provide_client_certificates(move(other.provide_client_certificates)) , timing_info(move(other.timing_info)) { #if REQUESTSERVER_DEBUG @@ -103,6 +106,16 @@ struct JobData { return JobData { [job](auto& socket) { job->start(socket); }, [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 {}; + }, { #if REQUESTSERVER_DEBUG .timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise), @@ -142,7 +155,7 @@ struct ConnectionKey { bool operator==(ConnectionKey const&) const = default; }; -} +}; template<> struct AK::Traits : public AK::DefaultTraits { @@ -159,7 +172,7 @@ struct InferredServerProperties { }; extern Threading::RWLockProtected>>>>> g_tcp_connection_cache; -extern Threading::RWLockProtected>>>>> g_tls_connection_cache; +extern Threading::RWLockProtected>>>>> g_tls_connection_cache; extern Threading::RWLockProtected> g_inferred_server_properties; void request_did_finish(URL::URL const&, Core::Socket const*); @@ -183,7 +196,29 @@ ErrorOr recreate_socket_if_needed(T& connection, URL::URL const& url) return {}; }; - TRY(set_socket(TRY((connection.proxy.template tunnel(url))))); + if constexpr (IsSame) { + 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 { + if (connection.job_data->provide_client_certificates) + return connection.job_data->provide_client_certificates(); + return {}; + }); + TRY(set_socket(TRY((connection.proxy.template tunnel(url, move(options)))))); + } else { + TRY(set_socket(TRY((connection.proxy.template tunnel(url))))); + } dbgln_if(REQUESTSERVER_DEBUG, "Creating a new socket for {} -> {}", url, connection.socket.ptr()); } return {}; diff --git a/Userland/Services/RequestServer/main.cpp b/Userland/Services/RequestServer/main.cpp index 8a956160ff0..b1164bf374c 100644 --- a/Userland/Services/RequestServer/main.cpp +++ b/Userland/Services/RequestServer/main.cpp @@ -26,6 +26,10 @@ ErrorOr serenity_main(Main::Arguments) 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; // FIXME: Establish a connection to LookupServer and then drop "unix"? TRY(Core::System::unveil("/tmp/portal/lookup", "rw")); diff --git a/vcpkg.json b/vcpkg.json index 679a4007db8..b6b76ea3f38 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -33,7 +33,6 @@ }, "sqlite3", "woff2", - "wolfssl", { "name": "vulkan", "platform": "!android" @@ -71,10 +70,6 @@ { "name": "woff2", "version": "1.0.2#4" - }, - { - "name": "wolfssl", - "version": "5.7.0#1" } ] }