mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
Revert "LibTLS+Everywhere: Switch to using WolfSSL"
This reverts commit 8bb610b97a
.
Linking wolfSSL seems to cause more legal trouble than it's worth due to
it being GPLv2, so let's undo this for now.
This commit is contained in:
parent
8d593bcfeb
commit
e0465b8939
Notes:
sideshowbarker
2024-07-17 03:25:24 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/LadybirdBrowser/ladybird/commit/e0465b8939 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/469
28 changed files with 3977 additions and 197 deletions
|
@ -31,7 +31,8 @@ ErrorOr<ByteString> find_certificates(StringView serenity_resource_root)
|
|||
ErrorOr<int> 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;
|
||||
|
||||
|
|
|
@ -48,8 +48,8 @@ ErrorOr<int> 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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
set(TEST_SOURCES
|
||||
TestTLSCertificateParser.cpp
|
||||
TestTLSHandshake.cpp
|
||||
)
|
||||
|
||||
foreach(source IN LISTS TEST_SOURCES)
|
||||
|
|
84
Tests/LibTLS/TestTLSHandshake.cpp
Normal file
84
Tests/LibTLS/TestTLSHandshake.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Peter Bocan <me@pbocan.net>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Base64.h>
|
||||
#include <LibCore/ConfigFile.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCrypto/ASN1/ASN1.h>
|
||||
#include <LibCrypto/ASN1/PEM.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
#include <LibTest/TestCase.h>
|
||||
|
||||
static StringView ca_certs_file = "./cacert.pem"sv;
|
||||
static int port = 443;
|
||||
|
||||
constexpr auto DEFAULT_SERVER = "www.google.com"sv;
|
||||
|
||||
static ByteBuffer operator""_b(char const* string, size_t length)
|
||||
{
|
||||
return ByteBuffer::copy(string, length).release_value();
|
||||
}
|
||||
|
||||
ErrorOr<Vector<Certificate>> load_certificates();
|
||||
ByteString locate_ca_certs_file();
|
||||
|
||||
ByteString locate_ca_certs_file()
|
||||
{
|
||||
if (FileSystem::exists(ca_certs_file)) {
|
||||
return ca_certs_file;
|
||||
}
|
||||
auto on_target_path = ByteString("/etc/cacert.pem");
|
||||
if (FileSystem::exists(on_target_path)) {
|
||||
return on_target_path;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
ErrorOr<Vector<Certificate>> load_certificates()
|
||||
{
|
||||
auto cacert_file = TRY(Core::File::open(locate_ca_certs_file(), Core::File::OpenMode::Read));
|
||||
auto data = TRY(cacert_file->read_until_eof());
|
||||
return TRY(DefaultRootCACertificates::parse_pem_root_certificate_authorities(data));
|
||||
}
|
||||
|
||||
TEST_CASE(test_TLS_hello_handshake)
|
||||
{
|
||||
Core::EventLoop loop;
|
||||
TLS::Options options;
|
||||
options.set_root_certificates(TRY_OR_FAIL(load_certificates()));
|
||||
options.set_alert_handler([&](TLS::AlertDescription) {
|
||||
FAIL("Connection failure");
|
||||
loop.quit(1);
|
||||
});
|
||||
options.set_finish_callback([&] {
|
||||
loop.quit(0);
|
||||
});
|
||||
|
||||
auto tls = TRY_OR_FAIL(TLS::TLSv12::connect(DEFAULT_SERVER, port, move(options)));
|
||||
ByteBuffer contents;
|
||||
tls->on_ready_to_read = [&] {
|
||||
(void)TRY_OR_FAIL(tls->read_some(contents.must_get_bytes_for_writing(4 * KiB)));
|
||||
loop.quit(0);
|
||||
};
|
||||
|
||||
if (tls->write_until_depleted("GET / HTTP/1.1\r\nHost: "_b).is_error()) {
|
||||
FAIL("write(0) failed");
|
||||
return;
|
||||
}
|
||||
|
||||
auto the_server = DEFAULT_SERVER;
|
||||
if (tls->write_until_depleted(the_server.bytes()).is_error()) {
|
||||
FAIL("write(1) failed");
|
||||
return;
|
||||
}
|
||||
if (tls->write_until_depleted("\r\nConnection : close\r\n\r\n"_b).is_error()) {
|
||||
FAIL("write(2) failed");
|
||||
return;
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace Crypto::Authentication {
|
||||
|
||||
constexpr static auto IPAD = 0x36;
|
||||
constexpr static auto OPAD = 0x5c;
|
||||
|
||||
namespace Crypto::Authentication {
|
||||
|
||||
template<typename HashT>
|
||||
class HMAC {
|
||||
public:
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -293,6 +293,25 @@ public:
|
|||
private:
|
||||
Optional<bool> m_is_self_signed;
|
||||
};
|
||||
|
||||
class DefaultRootCACertificates {
|
||||
public:
|
||||
DefaultRootCACertificates();
|
||||
|
||||
Vector<Certificate> const& certificates() const { return m_ca_certificates; }
|
||||
|
||||
static ErrorOr<Vector<Certificate>> parse_pem_root_certificate_authorities(ByteBuffer&);
|
||||
static ErrorOr<Vector<Certificate>> load_certificates(Span<ByteString> custom_cert_paths = {});
|
||||
|
||||
static DefaultRootCACertificates& the();
|
||||
|
||||
static void set_default_certificate_paths(Span<ByteString> paths);
|
||||
|
||||
private:
|
||||
Vector<Certificate> m_ca_certificates;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using TLS::Certificate;
|
||||
using TLS::DefaultRootCACertificates;
|
||||
|
|
90
Userland/Libraries/LibTLS/CipherSuite.h
Normal file
90
Userland/Libraries/LibTLS/CipherSuite.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <LibTLS/Extensions.h>
|
||||
|
||||
namespace TLS {
|
||||
|
||||
// Defined in RFC 5246 section 7.4.1.4.1
|
||||
struct SignatureAndHashAlgorithm {
|
||||
HashAlgorithm hash;
|
||||
SignatureAlgorithm signature;
|
||||
};
|
||||
|
||||
enum class KeyExchangeAlgorithm {
|
||||
Invalid,
|
||||
// Defined in RFC 5246 section 7.4.2 / RFC 4279 section 4
|
||||
RSA_PSK,
|
||||
// Defined in RFC 5246 section 7.4.3
|
||||
DHE_DSS,
|
||||
DHE_RSA,
|
||||
DH_anon,
|
||||
RSA,
|
||||
DH_DSS,
|
||||
DH_RSA,
|
||||
// Defined in RFC 4492 section 2
|
||||
ECDHE_RSA,
|
||||
ECDH_ECDSA,
|
||||
ECDH_RSA,
|
||||
ECDHE_ECDSA,
|
||||
ECDH_anon,
|
||||
};
|
||||
|
||||
// Defined in RFC 5246 section 7.4.1.4.1
|
||||
constexpr SignatureAlgorithm signature_for_key_exchange_algorithm(KeyExchangeAlgorithm algorithm)
|
||||
{
|
||||
switch (algorithm) {
|
||||
case KeyExchangeAlgorithm::RSA:
|
||||
case KeyExchangeAlgorithm::DHE_RSA:
|
||||
case KeyExchangeAlgorithm::DH_RSA:
|
||||
case KeyExchangeAlgorithm::RSA_PSK:
|
||||
case KeyExchangeAlgorithm::ECDH_RSA:
|
||||
case KeyExchangeAlgorithm::ECDHE_RSA:
|
||||
return SignatureAlgorithm::RSA;
|
||||
case KeyExchangeAlgorithm::DHE_DSS:
|
||||
case KeyExchangeAlgorithm::DH_DSS:
|
||||
return SignatureAlgorithm::DSA;
|
||||
case KeyExchangeAlgorithm::ECDH_ECDSA:
|
||||
case KeyExchangeAlgorithm::ECDHE_ECDSA:
|
||||
return SignatureAlgorithm::ECDSA;
|
||||
case KeyExchangeAlgorithm::DH_anon:
|
||||
case KeyExchangeAlgorithm::ECDH_anon:
|
||||
default:
|
||||
return SignatureAlgorithm::ANONYMOUS;
|
||||
}
|
||||
}
|
||||
|
||||
enum class CipherAlgorithm {
|
||||
Invalid,
|
||||
AES_128_CBC,
|
||||
AES_128_GCM,
|
||||
AES_128_CCM,
|
||||
AES_128_CCM_8,
|
||||
AES_256_CBC,
|
||||
AES_256_GCM,
|
||||
};
|
||||
|
||||
constexpr size_t cipher_key_size(CipherAlgorithm algorithm)
|
||||
{
|
||||
switch (algorithm) {
|
||||
case CipherAlgorithm::AES_128_CBC:
|
||||
case CipherAlgorithm::AES_128_GCM:
|
||||
case CipherAlgorithm::AES_128_CCM:
|
||||
case CipherAlgorithm::AES_128_CCM_8:
|
||||
return 128;
|
||||
case CipherAlgorithm::AES_256_CBC:
|
||||
case CipherAlgorithm::AES_256_GCM:
|
||||
return 256;
|
||||
case CipherAlgorithm::Invalid:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
559
Userland/Libraries/LibTLS/Handshake.cpp
Normal file
559
Userland/Libraries/LibTLS/Handshake.cpp
Normal file
|
@ -0,0 +1,559 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
* Copyright (c) 2022, Michiel Visser <opensource@webmichiel.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/Random.h>
|
||||
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibCrypto/ASN1/DER.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
|
||||
namespace TLS {
|
||||
|
||||
ByteBuffer TLSv12::build_hello()
|
||||
{
|
||||
fill_with_random(m_context.local_random);
|
||||
|
||||
auto packet_version = (u16)m_context.options.version;
|
||||
auto version = (u16)m_context.options.version;
|
||||
PacketBuilder builder { ContentType::HANDSHAKE, packet_version };
|
||||
|
||||
builder.append(to_underlying(HandshakeType::CLIENT_HELLO));
|
||||
|
||||
// hello length (for later)
|
||||
u8 dummy[3] = {};
|
||||
builder.append(dummy, 3);
|
||||
|
||||
auto start_length = builder.length();
|
||||
|
||||
builder.append(version);
|
||||
builder.append(m_context.local_random, sizeof(m_context.local_random));
|
||||
|
||||
builder.append(m_context.session_id_size);
|
||||
if (m_context.session_id_size)
|
||||
builder.append(m_context.session_id, m_context.session_id_size);
|
||||
|
||||
size_t extension_length = 0;
|
||||
size_t alpn_length = 0;
|
||||
size_t alpn_negotiated_length = 0;
|
||||
|
||||
// ALPN
|
||||
if (!m_context.negotiated_alpn.is_empty()) {
|
||||
alpn_negotiated_length = m_context.negotiated_alpn.length();
|
||||
alpn_length = alpn_negotiated_length + 1;
|
||||
extension_length += alpn_length + 6;
|
||||
} else if (m_context.alpn.size()) {
|
||||
for (auto& alpn : m_context.alpn) {
|
||||
size_t length = alpn.length();
|
||||
alpn_length += length + 1;
|
||||
}
|
||||
if (alpn_length)
|
||||
extension_length += alpn_length + 6;
|
||||
}
|
||||
|
||||
// Ciphers
|
||||
builder.append((u16)(m_context.options.usable_cipher_suites.size() * sizeof(u16)));
|
||||
for (auto suite : m_context.options.usable_cipher_suites)
|
||||
builder.append((u16)suite);
|
||||
|
||||
// we don't like compression
|
||||
VERIFY(!m_context.options.use_compression);
|
||||
builder.append((u8)1);
|
||||
builder.append((u8)m_context.options.use_compression);
|
||||
|
||||
// set SNI if we have one, and the user hasn't explicitly asked us to omit it.
|
||||
auto sni_length = 0;
|
||||
if (!m_context.extensions.SNI.is_empty() && m_context.options.use_sni)
|
||||
sni_length = m_context.extensions.SNI.length();
|
||||
|
||||
auto elliptic_curves_length = 2 * m_context.options.elliptic_curves.size();
|
||||
auto supported_ec_point_formats_length = m_context.options.supported_ec_point_formats.size();
|
||||
bool supports_elliptic_curves = elliptic_curves_length && supported_ec_point_formats_length;
|
||||
bool enable_extended_master_secret = m_context.options.enable_extended_master_secret;
|
||||
|
||||
// signature_algorithms: 2b extension ID, 2b extension length, 2b vector length, 2xN signatures and hashes
|
||||
extension_length += 2 + 2 + 2 + 2 * m_context.options.supported_signature_algorithms.size();
|
||||
|
||||
if (sni_length)
|
||||
extension_length += sni_length + 9;
|
||||
|
||||
// Only send elliptic_curves and ec_point_formats extensions if both are supported
|
||||
if (supports_elliptic_curves)
|
||||
extension_length += 6 + elliptic_curves_length + 5 + supported_ec_point_formats_length;
|
||||
|
||||
if (enable_extended_master_secret)
|
||||
extension_length += 4;
|
||||
|
||||
builder.append((u16)extension_length);
|
||||
|
||||
if (sni_length) {
|
||||
// SNI extension
|
||||
builder.append((u16)ExtensionType::SERVER_NAME);
|
||||
// extension length
|
||||
builder.append((u16)(sni_length + 5));
|
||||
// SNI length
|
||||
builder.append((u16)(sni_length + 3));
|
||||
// SNI type
|
||||
builder.append((u8)0);
|
||||
// SNI host length + value
|
||||
builder.append((u16)sni_length);
|
||||
builder.append((u8 const*)m_context.extensions.SNI.characters(), sni_length);
|
||||
}
|
||||
|
||||
// signature_algorithms extension
|
||||
builder.append((u16)ExtensionType::SIGNATURE_ALGORITHMS);
|
||||
// Extension length
|
||||
builder.append((u16)(2 + 2 * m_context.options.supported_signature_algorithms.size()));
|
||||
// Vector count
|
||||
builder.append((u16)(m_context.options.supported_signature_algorithms.size() * 2));
|
||||
// Entries
|
||||
for (auto& entry : m_context.options.supported_signature_algorithms) {
|
||||
builder.append((u8)entry.hash);
|
||||
builder.append((u8)entry.signature);
|
||||
}
|
||||
|
||||
if (supports_elliptic_curves) {
|
||||
// elliptic_curves extension
|
||||
builder.append((u16)ExtensionType::SUPPORTED_GROUPS);
|
||||
builder.append((u16)(2 + elliptic_curves_length));
|
||||
builder.append((u16)elliptic_curves_length);
|
||||
for (auto& curve : m_context.options.elliptic_curves)
|
||||
builder.append((u16)curve);
|
||||
|
||||
// ec_point_formats extension
|
||||
builder.append((u16)ExtensionType::EC_POINT_FORMATS);
|
||||
builder.append((u16)(1 + supported_ec_point_formats_length));
|
||||
builder.append((u8)supported_ec_point_formats_length);
|
||||
for (auto& format : m_context.options.supported_ec_point_formats)
|
||||
builder.append((u8)format);
|
||||
}
|
||||
|
||||
if (enable_extended_master_secret) {
|
||||
// extended_master_secret extension
|
||||
builder.append((u16)ExtensionType::EXTENDED_MASTER_SECRET);
|
||||
builder.append((u16)0);
|
||||
}
|
||||
|
||||
if (alpn_length) {
|
||||
// TODO
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// set the "length" field of the packet
|
||||
size_t remaining = builder.length() - start_length;
|
||||
size_t payload_position = 6;
|
||||
builder.set(payload_position, remaining / 0x10000);
|
||||
remaining %= 0x10000;
|
||||
builder.set(payload_position + 1, remaining / 0x100);
|
||||
remaining %= 0x100;
|
||||
builder.set(payload_position + 2, remaining);
|
||||
|
||||
auto packet = builder.build();
|
||||
update_packet(packet);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
ByteBuffer TLSv12::build_change_cipher_spec()
|
||||
{
|
||||
PacketBuilder builder { ContentType::CHANGE_CIPHER_SPEC, m_context.options.version, 64 };
|
||||
builder.append((u8)1);
|
||||
auto packet = builder.build();
|
||||
update_packet(packet);
|
||||
m_context.local_sequence_number = 0;
|
||||
return packet;
|
||||
}
|
||||
|
||||
ByteBuffer TLSv12::build_handshake_finished()
|
||||
{
|
||||
PacketBuilder builder { ContentType::HANDSHAKE, m_context.options.version, 12 + 64 };
|
||||
builder.append((u8)HandshakeType::FINISHED);
|
||||
|
||||
// RFC 5246 section 7.4.9: "In previous versions of TLS, the verify_data was always 12 octets
|
||||
// long. In the current version of TLS, it depends on the cipher
|
||||
// suite. Any cipher suite which does not explicitly specify
|
||||
// verify_data_length has a verify_data_length equal to 12."
|
||||
// Simplification: Assume that verify_data_length is always 12.
|
||||
constexpr u32 verify_data_length = 12;
|
||||
|
||||
builder.append_u24(verify_data_length);
|
||||
|
||||
u8 out[verify_data_length];
|
||||
auto outbuffer = Bytes { out, verify_data_length };
|
||||
ByteBuffer dummy;
|
||||
|
||||
auto digest = m_context.handshake_hash.digest();
|
||||
auto hashbuf = ReadonlyBytes { digest.immutable_data(), m_context.handshake_hash.digest_size() };
|
||||
pseudorandom_function(outbuffer, m_context.master_key, (u8 const*)"client finished", 15, hashbuf, dummy);
|
||||
|
||||
builder.append(outbuffer);
|
||||
auto packet = builder.build();
|
||||
update_packet(packet);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_handshake_finished(ReadonlyBytes buffer, WritePacketStage& write_packets)
|
||||
{
|
||||
if (m_context.connection_status < ConnectionStatus::KeyExchange || m_context.connection_status == ConnectionStatus::Established) {
|
||||
dbgln("unexpected finished message");
|
||||
return (i8)Error::UnexpectedMessage;
|
||||
}
|
||||
|
||||
write_packets = WritePacketStage::Initial;
|
||||
|
||||
if (buffer.size() < 3) {
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
|
||||
size_t index = 3;
|
||||
|
||||
u32 size = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
|
||||
|
||||
if (size < 12) {
|
||||
dbgln_if(TLS_DEBUG, "finished packet smaller than minimum size: {}", size);
|
||||
return (i8)Error::BrokenPacket;
|
||||
}
|
||||
|
||||
if (size < buffer.size() - index) {
|
||||
dbgln_if(TLS_DEBUG, "not enough data after length: {} > {}", size, buffer.size() - index);
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
|
||||
// TODO: Compare Hashes
|
||||
dbgln_if(TLS_DEBUG, "FIXME: handle_handshake_finished :: Check message validity");
|
||||
m_context.connection_status = ConnectionStatus::Established;
|
||||
|
||||
if (m_handshake_timeout_timer) {
|
||||
// Disable the handshake timeout timer as handshake has been established.
|
||||
m_handshake_timeout_timer->stop();
|
||||
m_handshake_timeout_timer->remove_from_parent();
|
||||
m_handshake_timeout_timer = nullptr;
|
||||
}
|
||||
|
||||
if (on_connected)
|
||||
on_connected();
|
||||
|
||||
return index + size;
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_handshake_payload(ReadonlyBytes vbuffer)
|
||||
{
|
||||
if (m_context.connection_status == ConnectionStatus::Established) {
|
||||
dbgln_if(TLS_DEBUG, "Renegotiation attempt ignored");
|
||||
// FIXME: We should properly say "NoRenegotiation", but that causes a handshake failure
|
||||
// so we just roll with it and pretend that we _did_ renegotiate
|
||||
// This will cause issues when we decide to have long-lasting connections, but
|
||||
// we do not have those at the moment :^)
|
||||
return 1;
|
||||
}
|
||||
auto buffer = vbuffer;
|
||||
auto buffer_length = buffer.size();
|
||||
auto original_length = buffer_length;
|
||||
while (buffer_length >= 4 && !m_context.critical_error) {
|
||||
ssize_t payload_res = 0;
|
||||
if (buffer_length < 1)
|
||||
return (i8)Error::NeedMoreData;
|
||||
auto type = static_cast<HandshakeType>(buffer[0]);
|
||||
auto write_packets { WritePacketStage::Initial };
|
||||
size_t payload_size = buffer[1] * 0x10000 + buffer[2] * 0x100 + buffer[3] + 3;
|
||||
dbgln_if(TLS_DEBUG, "payload size: {} buffer length: {}", payload_size, buffer_length);
|
||||
if (payload_size + 1 > buffer_length)
|
||||
return (i8)Error::NeedMoreData;
|
||||
|
||||
switch (type) {
|
||||
case HandshakeType::HELLO_REQUEST_RESERVED:
|
||||
if (m_context.handshake_messages[0] >= 1) {
|
||||
dbgln("unexpected hello request message");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
}
|
||||
++m_context.handshake_messages[0];
|
||||
dbgln("hello request (renegotiation?)");
|
||||
if (m_context.connection_status == ConnectionStatus::Established) {
|
||||
// renegotiation
|
||||
payload_res = (i8)Error::NoRenegotiation;
|
||||
} else {
|
||||
// :shrug:
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
}
|
||||
break;
|
||||
case HandshakeType::CLIENT_HELLO:
|
||||
// FIXME: We only support client mode right now
|
||||
if (m_context.is_server) {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
case HandshakeType::SERVER_HELLO:
|
||||
if (m_context.handshake_messages[2] >= 1) {
|
||||
dbgln("unexpected server hello message");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
}
|
||||
++m_context.handshake_messages[2];
|
||||
dbgln_if(TLS_DEBUG, "server hello");
|
||||
if (m_context.is_server) {
|
||||
dbgln("unsupported: server mode");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
payload_res = handle_server_hello(buffer.slice(1, payload_size), write_packets);
|
||||
break;
|
||||
case HandshakeType::HELLO_VERIFY_REQUEST_RESERVED:
|
||||
dbgln("unsupported: DTLS");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
case HandshakeType::CERTIFICATE:
|
||||
if (m_context.handshake_messages[4] >= 1) {
|
||||
dbgln("unexpected certificate message");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
}
|
||||
++m_context.handshake_messages[4];
|
||||
dbgln_if(TLS_DEBUG, "certificate");
|
||||
if (m_context.connection_status == ConnectionStatus::Negotiating) {
|
||||
if (m_context.is_server) {
|
||||
dbgln("unsupported: server mode");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
payload_res = handle_certificate(buffer.slice(1, payload_size));
|
||||
} else {
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
}
|
||||
break;
|
||||
case HandshakeType::SERVER_KEY_EXCHANGE_RESERVED:
|
||||
if (m_context.handshake_messages[5] >= 1) {
|
||||
dbgln("unexpected server key exchange message");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
}
|
||||
++m_context.handshake_messages[5];
|
||||
dbgln_if(TLS_DEBUG, "server key exchange");
|
||||
if (m_context.is_server) {
|
||||
dbgln("unsupported: server mode");
|
||||
VERIFY_NOT_REACHED();
|
||||
} else {
|
||||
payload_res = handle_server_key_exchange(buffer.slice(1, payload_size));
|
||||
}
|
||||
break;
|
||||
case HandshakeType::CERTIFICATE_REQUEST:
|
||||
if (m_context.handshake_messages[6] >= 1) {
|
||||
dbgln("unexpected certificate request message");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
}
|
||||
++m_context.handshake_messages[6];
|
||||
if (m_context.is_server) {
|
||||
dbgln("invalid request");
|
||||
dbgln("unsupported: server mode");
|
||||
VERIFY_NOT_REACHED();
|
||||
} else {
|
||||
// we do not support "certificate request"
|
||||
dbgln("certificate request");
|
||||
if (on_tls_certificate_request)
|
||||
on_tls_certificate_request(*this);
|
||||
m_context.client_verified = VerificationNeeded;
|
||||
}
|
||||
break;
|
||||
case HandshakeType::SERVER_HELLO_DONE_RESERVED:
|
||||
if (m_context.handshake_messages[7] >= 1) {
|
||||
dbgln("unexpected server hello done message");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
}
|
||||
++m_context.handshake_messages[7];
|
||||
dbgln_if(TLS_DEBUG, "server hello done");
|
||||
if (m_context.is_server) {
|
||||
dbgln("unsupported: server mode");
|
||||
VERIFY_NOT_REACHED();
|
||||
} else {
|
||||
payload_res = handle_server_hello_done(buffer.slice(1, payload_size));
|
||||
if (payload_res > 0)
|
||||
write_packets = WritePacketStage::ClientHandshake;
|
||||
}
|
||||
break;
|
||||
case HandshakeType::CERTIFICATE_VERIFY:
|
||||
if (m_context.handshake_messages[8] >= 1) {
|
||||
dbgln("unexpected certificate verify message");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
}
|
||||
++m_context.handshake_messages[8];
|
||||
dbgln_if(TLS_DEBUG, "certificate verify");
|
||||
if (m_context.connection_status == ConnectionStatus::KeyExchange) {
|
||||
payload_res = handle_certificate_verify(buffer.slice(1, payload_size));
|
||||
} else {
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
}
|
||||
break;
|
||||
case HandshakeType::CLIENT_KEY_EXCHANGE_RESERVED:
|
||||
if (m_context.handshake_messages[9] >= 1) {
|
||||
dbgln("unexpected client key exchange message");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
}
|
||||
++m_context.handshake_messages[9];
|
||||
dbgln_if(TLS_DEBUG, "client key exchange");
|
||||
if (m_context.is_server) {
|
||||
dbgln("unsupported: server mode");
|
||||
VERIFY_NOT_REACHED();
|
||||
} else {
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
}
|
||||
break;
|
||||
case HandshakeType::FINISHED:
|
||||
m_context.cached_handshake.clear();
|
||||
if (m_context.handshake_messages[10] >= 1) {
|
||||
dbgln("unexpected finished message");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
break;
|
||||
}
|
||||
++m_context.handshake_messages[10];
|
||||
dbgln_if(TLS_DEBUG, "finished");
|
||||
payload_res = handle_handshake_finished(buffer.slice(1, payload_size), write_packets);
|
||||
if (payload_res > 0) {
|
||||
memset(m_context.handshake_messages, 0, sizeof(m_context.handshake_messages));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dbgln("message type not understood: {}", enum_to_string(type));
|
||||
return (i8)Error::NotUnderstood;
|
||||
}
|
||||
|
||||
if (type != HandshakeType::HELLO_REQUEST_RESERVED) {
|
||||
update_hash(buffer.slice(0, payload_size + 1), 0);
|
||||
}
|
||||
|
||||
// if something went wrong, send an alert about it
|
||||
if (payload_res < 0) {
|
||||
switch ((Error)payload_res) {
|
||||
case Error::UnexpectedMessage: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::UNEXPECTED_MESSAGE);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::CompressionNotSupported: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::DECOMPRESSION_FAILURE_RESERVED);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::BrokenPacket: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::DECODE_ERROR);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::NotVerified: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::BAD_RECORD_MAC);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::BadCertificate: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::BAD_CERTIFICATE);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::UnsupportedCertificate: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::UNSUPPORTED_CERTIFICATE);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::NoCommonCipher: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::INSUFFICIENT_SECURITY);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::NotUnderstood:
|
||||
case Error::OutOfMemory: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::INTERNAL_ERROR);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::NoRenegotiation: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::NO_RENEGOTIATION_RESERVED);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::DecryptionFailed: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::DECRYPTION_FAILED_RESERVED);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::NotSafe: {
|
||||
auto packet = build_alert(true, (u8)AlertDescription::DECRYPT_ERROR);
|
||||
write_packet(packet);
|
||||
break;
|
||||
}
|
||||
case Error::NeedMoreData:
|
||||
// Ignore this, as it's not an "error"
|
||||
dbgln_if(TLS_DEBUG, "More data needed");
|
||||
break;
|
||||
default:
|
||||
dbgln("Unknown TLS::Error with value {}", payload_res);
|
||||
VERIFY_NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
if (payload_res < 0)
|
||||
return payload_res;
|
||||
}
|
||||
switch (write_packets) {
|
||||
case WritePacketStage::Initial:
|
||||
// nothing to write
|
||||
break;
|
||||
case WritePacketStage::ClientHandshake:
|
||||
if (m_context.client_verified == VerificationNeeded) {
|
||||
dbgln_if(TLS_DEBUG, "> Client Certificate");
|
||||
auto packet = build_certificate();
|
||||
write_packet(packet);
|
||||
m_context.client_verified = Verified;
|
||||
}
|
||||
{
|
||||
dbgln_if(TLS_DEBUG, "> Key exchange");
|
||||
auto packet = build_client_key_exchange();
|
||||
write_packet(packet);
|
||||
}
|
||||
{
|
||||
dbgln_if(TLS_DEBUG, "> change cipher spec");
|
||||
auto packet = build_change_cipher_spec();
|
||||
write_packet(packet);
|
||||
}
|
||||
m_context.cipher_spec_set = 1;
|
||||
m_context.local_sequence_number = 0;
|
||||
{
|
||||
dbgln_if(TLS_DEBUG, "> client finished");
|
||||
auto packet = build_handshake_finished();
|
||||
write_packet(packet);
|
||||
}
|
||||
m_context.cipher_spec_set = 0;
|
||||
break;
|
||||
case WritePacketStage::ServerHandshake:
|
||||
// server handshake
|
||||
dbgln("UNSUPPORTED: Server mode");
|
||||
VERIFY_NOT_REACHED();
|
||||
break;
|
||||
case WritePacketStage::Finished:
|
||||
// finished
|
||||
{
|
||||
dbgln_if(TLS_DEBUG, "> change cipher spec");
|
||||
auto packet = build_change_cipher_spec();
|
||||
write_packet(packet);
|
||||
}
|
||||
{
|
||||
dbgln_if(TLS_DEBUG, "> client finished");
|
||||
auto packet = build_handshake_finished();
|
||||
write_packet(packet);
|
||||
}
|
||||
m_context.connection_status = ConnectionStatus::Established;
|
||||
break;
|
||||
}
|
||||
payload_size++;
|
||||
buffer_length -= payload_size;
|
||||
buffer = buffer.slice(payload_size, buffer_length);
|
||||
}
|
||||
return original_length;
|
||||
}
|
||||
}
|
110
Userland/Libraries/LibTLS/HandshakeCertificate.cpp
Normal file
110
Userland/Libraries/LibTLS/HandshakeCertificate.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/Random.h>
|
||||
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibCrypto/ASN1/DER.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
|
||||
namespace TLS {
|
||||
|
||||
ssize_t TLSv12::handle_certificate(ReadonlyBytes buffer)
|
||||
{
|
||||
ssize_t res = 0;
|
||||
|
||||
if (buffer.size() < 3) {
|
||||
dbgln_if(TLS_DEBUG, "not enough certificate header data");
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
|
||||
u32 certificate_total_length = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
|
||||
|
||||
dbgln_if(TLS_DEBUG, "total length: {}", certificate_total_length);
|
||||
|
||||
if (certificate_total_length <= 4)
|
||||
return 3 * certificate_total_length;
|
||||
|
||||
res += 3;
|
||||
|
||||
if (certificate_total_length > buffer.size() - res) {
|
||||
dbgln_if(TLS_DEBUG, "not enough data for claimed total cert length");
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
size_t size = certificate_total_length;
|
||||
|
||||
bool valid_certificate = false;
|
||||
|
||||
while (size > 0) {
|
||||
if (buffer.size() - res < 3) {
|
||||
dbgln_if(TLS_DEBUG, "not enough data for certificate length");
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
size_t certificate_size = buffer[res] * 0x10000 + buffer[res + 1] * 0x100 + buffer[res + 2];
|
||||
res += 3;
|
||||
|
||||
if (buffer.size() - res < certificate_size) {
|
||||
dbgln_if(TLS_DEBUG, "not enough data for certificate body");
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
|
||||
auto res_cert = res;
|
||||
auto remaining = certificate_size;
|
||||
|
||||
do {
|
||||
if (remaining <= 3) {
|
||||
dbgln("Ran out of data");
|
||||
break;
|
||||
}
|
||||
if (buffer.size() < (size_t)res_cert + 3) {
|
||||
dbgln("not enough data to read cert size ({} < {})", buffer.size(), res_cert + 3);
|
||||
break;
|
||||
}
|
||||
size_t certificate_size_specific = buffer[res_cert] * 0x10000 + buffer[res_cert + 1] * 0x100 + buffer[res_cert + 2];
|
||||
res_cert += 3;
|
||||
remaining -= 3;
|
||||
|
||||
if (certificate_size_specific > remaining) {
|
||||
dbgln("invalid certificate size (expected {} but got {})", remaining, certificate_size_specific);
|
||||
break;
|
||||
}
|
||||
remaining -= certificate_size_specific;
|
||||
|
||||
auto certificate = Certificate::parse_certificate(buffer.slice(res_cert, certificate_size_specific), false);
|
||||
if (!certificate.is_error()) {
|
||||
m_context.certificates.empend(certificate.value());
|
||||
valid_certificate = true;
|
||||
} else {
|
||||
dbgln("Failed to parse client cert: {}", certificate.error());
|
||||
dbgln("{:hex-dump}", buffer.slice(res_cert, certificate_size_specific));
|
||||
dbgln("");
|
||||
}
|
||||
res_cert += certificate_size_specific;
|
||||
} while (remaining > 0);
|
||||
if (remaining) {
|
||||
dbgln("extraneous {} bytes left over after parsing certificates", remaining);
|
||||
}
|
||||
size -= certificate_size + 3;
|
||||
res += certificate_size;
|
||||
}
|
||||
if (!valid_certificate)
|
||||
return (i8)Error::UnsupportedCertificate;
|
||||
|
||||
if ((size_t)res != buffer.size())
|
||||
dbgln("some data left unread: {} bytes out of {}", res, buffer.size());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_certificate_verify(ReadonlyBytes)
|
||||
{
|
||||
dbgln("FIXME: parse_verify");
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
412
Userland/Libraries/LibTLS/HandshakeClient.cpp
Normal file
412
Userland/Libraries/LibTLS/HandshakeClient.cpp
Normal file
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
* Copyright (c) 2022, Michiel Visser <opensource@webmichiel.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Hex.h>
|
||||
#include <AK/Random.h>
|
||||
#include <LibCrypto/ASN1/DER.h>
|
||||
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
||||
#include <LibCrypto/NumberTheory/ModularFunctions.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
|
||||
namespace TLS {
|
||||
|
||||
bool TLSv12::expand_key()
|
||||
{
|
||||
u8 key[192]; // soooooooo many constants
|
||||
auto key_buffer = Bytes { key, sizeof(key) };
|
||||
|
||||
auto is_aead = this->is_aead();
|
||||
|
||||
if (m_context.master_key.size() == 0) {
|
||||
dbgln("expand_key() with empty master key");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto key_size = key_length();
|
||||
VERIFY(key_size);
|
||||
auto mac_size = mac_length();
|
||||
auto iv_size = iv_length();
|
||||
|
||||
pseudorandom_function(
|
||||
key_buffer,
|
||||
m_context.master_key,
|
||||
(u8 const*)"key expansion", 13,
|
||||
ReadonlyBytes { m_context.remote_random, sizeof(m_context.remote_random) },
|
||||
ReadonlyBytes { m_context.local_random, sizeof(m_context.local_random) });
|
||||
|
||||
size_t offset = 0;
|
||||
if (is_aead) {
|
||||
iv_size = 4; // Explicit IV size.
|
||||
} else {
|
||||
memcpy(m_context.crypto.local_mac, key + offset, mac_size);
|
||||
offset += mac_size;
|
||||
memcpy(m_context.crypto.remote_mac, key + offset, mac_size);
|
||||
offset += mac_size;
|
||||
}
|
||||
|
||||
auto client_key = key + offset;
|
||||
offset += key_size;
|
||||
auto server_key = key + offset;
|
||||
offset += key_size;
|
||||
auto client_iv = key + offset;
|
||||
offset += iv_size;
|
||||
auto server_iv = key + offset;
|
||||
offset += iv_size;
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("client key");
|
||||
print_buffer(client_key, key_size);
|
||||
dbgln("server key");
|
||||
print_buffer(server_key, key_size);
|
||||
dbgln("client iv");
|
||||
print_buffer(client_iv, iv_size);
|
||||
dbgln("server iv");
|
||||
print_buffer(server_iv, iv_size);
|
||||
if (!is_aead) {
|
||||
dbgln("client mac key");
|
||||
print_buffer(m_context.crypto.local_mac, mac_size);
|
||||
dbgln("server mac key");
|
||||
print_buffer(m_context.crypto.remote_mac, mac_size);
|
||||
}
|
||||
}
|
||||
|
||||
switch (get_cipher_algorithm(m_context.cipher)) {
|
||||
case CipherAlgorithm::AES_128_CBC:
|
||||
case CipherAlgorithm::AES_256_CBC: {
|
||||
VERIFY(!is_aead);
|
||||
memcpy(m_context.crypto.local_iv, client_iv, iv_size);
|
||||
memcpy(m_context.crypto.remote_iv, server_iv, iv_size);
|
||||
|
||||
m_cipher_local = Crypto::Cipher::AESCipher::CBCMode(ReadonlyBytes { client_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246);
|
||||
m_cipher_remote = Crypto::Cipher::AESCipher::CBCMode(ReadonlyBytes { server_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246);
|
||||
break;
|
||||
}
|
||||
case CipherAlgorithm::AES_128_GCM:
|
||||
case CipherAlgorithm::AES_256_GCM: {
|
||||
VERIFY(is_aead);
|
||||
memcpy(m_context.crypto.local_aead_iv, client_iv, iv_size);
|
||||
memcpy(m_context.crypto.remote_aead_iv, server_iv, iv_size);
|
||||
|
||||
m_cipher_local = Crypto::Cipher::AESCipher::GCMMode(ReadonlyBytes { client_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246);
|
||||
m_cipher_remote = Crypto::Cipher::AESCipher::GCMMode(ReadonlyBytes { server_key, key_size }, key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246);
|
||||
break;
|
||||
}
|
||||
case CipherAlgorithm::AES_128_CCM:
|
||||
dbgln("Requested unimplemented AES CCM cipher");
|
||||
TODO();
|
||||
case CipherAlgorithm::AES_128_CCM_8:
|
||||
dbgln("Requested unimplemented AES CCM-8 block cipher");
|
||||
TODO();
|
||||
default:
|
||||
dbgln("Requested unknown block cipher");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
m_context.crypto.created = 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TLSv12::compute_master_secret_from_pre_master_secret(size_t length)
|
||||
{
|
||||
if (m_context.premaster_key.size() == 0 || length < 48) {
|
||||
dbgln("there's no way I can make a master secret like this");
|
||||
dbgln("I'd like to talk to your manager about this length of {}", length);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_context.master_key.try_resize(length).is_error()) {
|
||||
dbgln("Couldn't allocate enough space for the master key :(");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_context.extensions.extended_master_secret) {
|
||||
Crypto::Hash::Manager handshake_hash_copy = m_context.handshake_hash.copy();
|
||||
auto digest = handshake_hash_copy.digest();
|
||||
auto session_hash = ReadonlyBytes { digest.immutable_data(), handshake_hash_copy.digest_size() };
|
||||
|
||||
pseudorandom_function(
|
||||
m_context.master_key,
|
||||
m_context.premaster_key,
|
||||
(u8 const*)"extended master secret", 22,
|
||||
session_hash,
|
||||
{});
|
||||
} else {
|
||||
pseudorandom_function(
|
||||
m_context.master_key,
|
||||
m_context.premaster_key,
|
||||
(u8 const*)"master secret", 13,
|
||||
ReadonlyBytes { m_context.local_random, sizeof(m_context.local_random) },
|
||||
ReadonlyBytes { m_context.remote_random, sizeof(m_context.remote_random) });
|
||||
}
|
||||
|
||||
m_context.premaster_key.clear();
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("master key:");
|
||||
print_buffer(m_context.master_key);
|
||||
}
|
||||
|
||||
expand_key();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TLSv12::build_rsa_pre_master_secret(PacketBuilder& builder)
|
||||
{
|
||||
u8 random_bytes[48];
|
||||
size_t bytes = 48;
|
||||
|
||||
fill_with_random(random_bytes);
|
||||
|
||||
// remove zeros from the random bytes
|
||||
for (size_t i = 0; i < bytes; ++i) {
|
||||
if (!random_bytes[i])
|
||||
random_bytes[i--] = get_random<u8>();
|
||||
}
|
||||
|
||||
if (m_context.is_server) {
|
||||
dbgln("Server mode not supported");
|
||||
return;
|
||||
} else {
|
||||
*(u16*)random_bytes = AK::convert_between_host_and_network_endian((u16)ProtocolVersion::VERSION_1_2);
|
||||
}
|
||||
|
||||
auto premaster_key_result = ByteBuffer::copy(random_bytes, bytes);
|
||||
if (premaster_key_result.is_error()) {
|
||||
dbgln("RSA premaster key generation failed, not enough memory");
|
||||
return;
|
||||
}
|
||||
m_context.premaster_key = premaster_key_result.release_value();
|
||||
|
||||
// RFC5246 section 7.4.2: The sender's certificate MUST come first in the list.
|
||||
auto& certificate = m_context.certificates.first();
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("PreMaster secret");
|
||||
print_buffer(m_context.premaster_key);
|
||||
}
|
||||
|
||||
Crypto::PK::RSA_PKCS1_EME rsa(certificate.public_key.rsa.modulus(), 0, certificate.public_key.rsa.public_exponent());
|
||||
|
||||
Vector<u8, 32> out;
|
||||
out.resize(rsa.output_size());
|
||||
auto outbuf = out.span();
|
||||
rsa.encrypt(m_context.premaster_key, outbuf);
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("Encrypted: ");
|
||||
print_buffer(outbuf);
|
||||
}
|
||||
|
||||
builder.append_u24(outbuf.size() + 2);
|
||||
builder.append((u16)outbuf.size());
|
||||
builder.append(outbuf);
|
||||
}
|
||||
|
||||
void TLSv12::build_dhe_rsa_pre_master_secret(PacketBuilder& builder)
|
||||
{
|
||||
auto& dh = m_context.server_diffie_hellman_params;
|
||||
auto dh_p = Crypto::UnsignedBigInteger::import_data(dh.p.data(), dh.p.size());
|
||||
auto dh_g = Crypto::UnsignedBigInteger::import_data(dh.g.data(), dh.g.size());
|
||||
auto dh_Ys = Crypto::UnsignedBigInteger::import_data(dh.Ys.data(), dh.Ys.size());
|
||||
auto dh_key_size = dh.p.size();
|
||||
|
||||
auto dh_random = Crypto::NumberTheory::random_number(0, dh_p);
|
||||
auto dh_Yc = Crypto::NumberTheory::ModularPower(dh_g, dh_random, dh_p);
|
||||
auto dh_Yc_bytes_result = ByteBuffer::create_uninitialized(dh_key_size);
|
||||
if (dh_Yc_bytes_result.is_error()) {
|
||||
dbgln("Failed to build DHE_RSA premaster secret: not enough memory");
|
||||
return;
|
||||
}
|
||||
auto dh_Yc_bytes = dh_Yc_bytes_result.release_value();
|
||||
dh_Yc.export_data(dh_Yc_bytes);
|
||||
|
||||
auto premaster_key = Crypto::NumberTheory::ModularPower(dh_Ys, dh_random, dh_p);
|
||||
auto premaster_key_result = ByteBuffer::create_uninitialized(dh_key_size);
|
||||
if (premaster_key_result.is_error()) {
|
||||
dbgln("Failed to build DHE_RSA premaster secret: not enough memory");
|
||||
return;
|
||||
}
|
||||
m_context.premaster_key = premaster_key_result.release_value();
|
||||
premaster_key.export_data(m_context.premaster_key, true);
|
||||
|
||||
dh.p.clear();
|
||||
dh.g.clear();
|
||||
dh.Ys.clear();
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("dh_random: {}", dh_random.to_base_deprecated(16));
|
||||
dbgln("dh_Yc: {:hex-dump}", (ReadonlyBytes)dh_Yc_bytes);
|
||||
dbgln("premaster key: {:hex-dump}", (ReadonlyBytes)m_context.premaster_key);
|
||||
}
|
||||
|
||||
builder.append_u24(dh_key_size + 2);
|
||||
builder.append((u16)dh_key_size);
|
||||
builder.append(dh_Yc_bytes);
|
||||
}
|
||||
|
||||
void TLSv12::build_ecdhe_rsa_pre_master_secret(PacketBuilder& builder)
|
||||
{
|
||||
// Create a random private key
|
||||
auto private_key_result = m_context.server_key_exchange_curve->generate_private_key();
|
||||
if (private_key_result.is_error()) {
|
||||
dbgln("Failed to build ECDHE_RSA premaster secret: not enough memory");
|
||||
return;
|
||||
}
|
||||
auto private_key = private_key_result.release_value();
|
||||
|
||||
// Calculate the public key from the private key
|
||||
auto public_key_result = m_context.server_key_exchange_curve->generate_public_key(private_key);
|
||||
if (public_key_result.is_error()) {
|
||||
dbgln("Failed to build ECDHE_RSA premaster secret: not enough memory");
|
||||
return;
|
||||
}
|
||||
auto public_key = public_key_result.release_value();
|
||||
|
||||
// Calculate the shared point by multiplying the client private key and the server public key
|
||||
ReadonlyBytes server_public_key_bytes = m_context.server_diffie_hellman_params.p;
|
||||
auto shared_point_result = m_context.server_key_exchange_curve->compute_coordinate(private_key, server_public_key_bytes);
|
||||
if (shared_point_result.is_error()) {
|
||||
dbgln("Failed to build ECDHE_RSA premaster secret: not enough memory");
|
||||
return;
|
||||
}
|
||||
auto shared_point = shared_point_result.release_value();
|
||||
|
||||
// Derive the premaster key from the shared point
|
||||
auto premaster_key_result = m_context.server_key_exchange_curve->derive_premaster_key(shared_point);
|
||||
if (premaster_key_result.is_error()) {
|
||||
dbgln("Failed to build ECDHE_RSA premaster secret: not enough memory");
|
||||
return;
|
||||
}
|
||||
m_context.premaster_key = premaster_key_result.release_value();
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("Build ECDHE_RSA pre master secret");
|
||||
dbgln("client private key: {:hex-dump}", (ReadonlyBytes)private_key);
|
||||
dbgln("client public key: {:hex-dump}", (ReadonlyBytes)public_key);
|
||||
dbgln("premaster key: {:hex-dump}", (ReadonlyBytes)m_context.premaster_key);
|
||||
}
|
||||
|
||||
builder.append_u24(public_key.size() + 1);
|
||||
builder.append((u8)public_key.size());
|
||||
builder.append(public_key);
|
||||
}
|
||||
|
||||
ByteBuffer TLSv12::build_certificate()
|
||||
{
|
||||
PacketBuilder builder { ContentType::HANDSHAKE, m_context.options.version };
|
||||
|
||||
Vector<Certificate const&> certificates;
|
||||
Vector<Certificate>* local_certificates = nullptr;
|
||||
|
||||
if (m_context.is_server) {
|
||||
dbgln("Unsupported: Server mode");
|
||||
VERIFY_NOT_REACHED();
|
||||
} else {
|
||||
local_certificates = &m_context.client_certificates;
|
||||
}
|
||||
|
||||
constexpr size_t der_length_delta = 3;
|
||||
constexpr size_t certificate_vector_header_size = 3;
|
||||
|
||||
size_t total_certificate_size = 0;
|
||||
|
||||
for (size_t i = 0; i < local_certificates->size(); ++i) {
|
||||
auto& certificate = local_certificates->at(i);
|
||||
if (!certificate.der.is_empty()) {
|
||||
total_certificate_size += certificate.der.size() + der_length_delta;
|
||||
|
||||
// FIXME: Check for and respond with only the requested certificate types.
|
||||
if (true) {
|
||||
certificates.append(certificate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.append((u8)HandshakeType::CERTIFICATE);
|
||||
|
||||
if (!total_certificate_size) {
|
||||
dbgln_if(TLS_DEBUG, "No certificates, sending empty certificate message");
|
||||
builder.append_u24(certificate_vector_header_size);
|
||||
builder.append_u24(total_certificate_size);
|
||||
} else {
|
||||
builder.append_u24(total_certificate_size + certificate_vector_header_size); // 3 bytes for header
|
||||
builder.append_u24(total_certificate_size);
|
||||
|
||||
for (auto& certificate : certificates) {
|
||||
if (!certificate.der.is_empty()) {
|
||||
builder.append_u24(certificate.der.size());
|
||||
builder.append(certificate.der.bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
auto packet = builder.build();
|
||||
update_packet(packet);
|
||||
return packet;
|
||||
}
|
||||
|
||||
ByteBuffer TLSv12::build_client_key_exchange()
|
||||
{
|
||||
bool chain_verified = m_context.verify_chain(m_context.extensions.SNI);
|
||||
if (!chain_verified) {
|
||||
dbgln("certificate verification failed :(");
|
||||
alert(AlertLevel::FATAL, AlertDescription::BAD_CERTIFICATE);
|
||||
return {};
|
||||
}
|
||||
|
||||
PacketBuilder builder { ContentType::HANDSHAKE, m_context.options.version };
|
||||
builder.append((u8)HandshakeType::CLIENT_KEY_EXCHANGE_RESERVED);
|
||||
|
||||
switch (get_key_exchange_algorithm(m_context.cipher)) {
|
||||
case KeyExchangeAlgorithm::RSA:
|
||||
build_rsa_pre_master_secret(builder);
|
||||
break;
|
||||
case KeyExchangeAlgorithm::DHE_DSS:
|
||||
dbgln("Client key exchange for DHE_DSS is not implemented");
|
||||
TODO();
|
||||
break;
|
||||
case KeyExchangeAlgorithm::DH_DSS:
|
||||
case KeyExchangeAlgorithm::DH_RSA:
|
||||
dbgln("Client key exchange for DH algorithms is not implemented");
|
||||
TODO();
|
||||
break;
|
||||
case KeyExchangeAlgorithm::DHE_RSA:
|
||||
build_dhe_rsa_pre_master_secret(builder);
|
||||
break;
|
||||
case KeyExchangeAlgorithm::DH_anon:
|
||||
dbgln("Client key exchange for DH_anon is not implemented");
|
||||
TODO();
|
||||
break;
|
||||
case KeyExchangeAlgorithm::ECDHE_RSA:
|
||||
case KeyExchangeAlgorithm::ECDHE_ECDSA:
|
||||
build_ecdhe_rsa_pre_master_secret(builder);
|
||||
break;
|
||||
case KeyExchangeAlgorithm::ECDH_ECDSA:
|
||||
case KeyExchangeAlgorithm::ECDH_RSA:
|
||||
case KeyExchangeAlgorithm::ECDH_anon:
|
||||
dbgln("Client key exchange for ECDHE algorithms is not implemented");
|
||||
TODO();
|
||||
break;
|
||||
default:
|
||||
dbgln("Unknown client key exchange algorithm");
|
||||
VERIFY_NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
|
||||
m_context.connection_status = ConnectionStatus::KeyExchange;
|
||||
|
||||
auto packet = builder.build();
|
||||
|
||||
update_packet(packet);
|
||||
|
||||
if (!compute_master_secret_from_pre_master_secret(48)) {
|
||||
dbgln("oh noes we could not derive a master key :(");
|
||||
}
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
}
|
530
Userland/Libraries/LibTLS/HandshakeServer.cpp
Normal file
530
Userland/Libraries/LibTLS/HandshakeServer.cpp
Normal file
|
@ -0,0 +1,530 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
* Copyright (c) 2022, Michiel Visser <opensource@webmichiel.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/Random.h>
|
||||
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibCrypto/ASN1/DER.h>
|
||||
#include <LibCrypto/Curves/Ed25519.h>
|
||||
#include <LibCrypto/Curves/EllipticCurve.h>
|
||||
#include <LibCrypto/Curves/SECPxxxr1.h>
|
||||
#include <LibCrypto/Curves/X25519.h>
|
||||
#include <LibCrypto/Curves/X448.h>
|
||||
#include <LibCrypto/PK/Code/EMSA_PKCS1_V1_5.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
|
||||
namespace TLS {
|
||||
|
||||
ssize_t TLSv12::handle_server_hello(ReadonlyBytes buffer, WritePacketStage& write_packets)
|
||||
{
|
||||
write_packets = WritePacketStage::Initial;
|
||||
if (m_context.connection_status != ConnectionStatus::Disconnected && m_context.connection_status != ConnectionStatus::Renegotiating) {
|
||||
dbgln("unexpected hello message");
|
||||
return (i8)Error::UnexpectedMessage;
|
||||
}
|
||||
ssize_t res = 0;
|
||||
size_t min_hello_size = 41;
|
||||
|
||||
if (min_hello_size > buffer.size()) {
|
||||
dbgln("need more data");
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
size_t following_bytes = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
|
||||
res += 3;
|
||||
if (buffer.size() - res < following_bytes) {
|
||||
dbgln("not enough data after header: {} < {}", buffer.size() - res, following_bytes);
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
|
||||
if (buffer.size() - res < 2) {
|
||||
dbgln("not enough data for version");
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
auto version = static_cast<ProtocolVersion>(AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res))));
|
||||
|
||||
res += 2;
|
||||
if (!supports_version(version))
|
||||
return (i8)Error::NotSafe;
|
||||
|
||||
memcpy(m_context.remote_random, buffer.offset_pointer(res), sizeof(m_context.remote_random));
|
||||
res += sizeof(m_context.remote_random);
|
||||
|
||||
u8 session_length = buffer[res++];
|
||||
if (buffer.size() - res < session_length) {
|
||||
dbgln("not enough data for session id");
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
|
||||
if (session_length && session_length <= 32) {
|
||||
memcpy(m_context.session_id, buffer.offset_pointer(res), session_length);
|
||||
m_context.session_id_size = session_length;
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("Remote session ID:");
|
||||
print_buffer(ReadonlyBytes { m_context.session_id, session_length });
|
||||
}
|
||||
} else {
|
||||
m_context.session_id_size = 0;
|
||||
}
|
||||
res += session_length;
|
||||
|
||||
if (buffer.size() - res < 2) {
|
||||
dbgln("not enough data for cipher suite listing");
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
auto cipher = static_cast<CipherSuite>(AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res))));
|
||||
res += 2;
|
||||
if (!supports_cipher(cipher)) {
|
||||
m_context.cipher = CipherSuite::TLS_NULL_WITH_NULL_NULL;
|
||||
dbgln("No supported cipher could be agreed upon");
|
||||
return (i8)Error::NoCommonCipher;
|
||||
}
|
||||
m_context.cipher = cipher;
|
||||
dbgln_if(TLS_DEBUG, "Cipher: {}", enum_to_string(cipher));
|
||||
|
||||
// Simplification: We only support handshake hash functions via HMAC
|
||||
m_context.handshake_hash.initialize(hmac_hash());
|
||||
|
||||
// Compression method
|
||||
if (buffer.size() - res < 1)
|
||||
return (i8)Error::NeedMoreData;
|
||||
u8 compression = buffer[res++];
|
||||
if (compression != 0)
|
||||
return (i8)Error::CompressionNotSupported;
|
||||
|
||||
if (m_context.connection_status != ConnectionStatus::Renegotiating)
|
||||
m_context.connection_status = ConnectionStatus::Negotiating;
|
||||
if (m_context.is_server) {
|
||||
dbgln("unsupported: server mode");
|
||||
write_packets = WritePacketStage::ServerHandshake;
|
||||
}
|
||||
|
||||
// Presence of extensions is determined by availability of bytes after compression_method
|
||||
if (buffer.size() - res >= 2) {
|
||||
auto extensions_bytes_total = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res += 2)));
|
||||
dbgln_if(TLS_DEBUG, "Extensions bytes total: {}", extensions_bytes_total);
|
||||
}
|
||||
|
||||
while (buffer.size() - res >= 4) {
|
||||
auto extension_type = (ExtensionType)AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res)));
|
||||
res += 2;
|
||||
u16 extension_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res)));
|
||||
res += 2;
|
||||
|
||||
dbgln_if(TLS_DEBUG, "Extension {} with length {}", enum_to_string(extension_type), extension_length);
|
||||
|
||||
if (buffer.size() - res < extension_length)
|
||||
return (i8)Error::NeedMoreData;
|
||||
|
||||
if (extension_type == ExtensionType::SERVER_NAME) {
|
||||
// RFC6066 section 3: SNI extension_data can be empty in the server hello
|
||||
if (extension_length > 0) {
|
||||
// ServerNameList total size
|
||||
if (buffer.size() - res < 2)
|
||||
return (i8)Error::NeedMoreData;
|
||||
auto sni_name_list_bytes = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res += 2)));
|
||||
dbgln_if(TLS_DEBUG, "SNI: expecting ServerNameList of {} bytes", sni_name_list_bytes);
|
||||
|
||||
// Exactly one ServerName should be present
|
||||
if (buffer.size() - res < 3)
|
||||
return (i8)Error::NeedMoreData;
|
||||
auto sni_name_type = (NameType)(*(u8 const*)buffer.offset_pointer(res++));
|
||||
auto sni_name_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res += 2)));
|
||||
|
||||
if (sni_name_type != NameType::HOST_NAME)
|
||||
return (i8)Error::NotUnderstood;
|
||||
|
||||
if (sizeof(sni_name_type) + sizeof(sni_name_length) + sni_name_length != sni_name_list_bytes)
|
||||
return (i8)Error::BrokenPacket;
|
||||
|
||||
// Read out the host_name
|
||||
if (buffer.size() - res < sni_name_length)
|
||||
return (i8)Error::NeedMoreData;
|
||||
m_context.extensions.SNI = ByteString { (char const*)buffer.offset_pointer(res), sni_name_length };
|
||||
res += sni_name_length;
|
||||
dbgln("SNI host_name: {}", m_context.extensions.SNI);
|
||||
}
|
||||
} else if (extension_type == ExtensionType::APPLICATION_LAYER_PROTOCOL_NEGOTIATION && m_context.alpn.size()) {
|
||||
if (buffer.size() - res > 2) {
|
||||
auto alpn_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(res)));
|
||||
if (alpn_length && alpn_length <= extension_length - 2) {
|
||||
u8 const* alpn = buffer.offset_pointer(res + 2);
|
||||
size_t alpn_position = 0;
|
||||
while (alpn_position < alpn_length) {
|
||||
u8 alpn_size = alpn[alpn_position++];
|
||||
if (alpn_size + alpn_position >= extension_length)
|
||||
break;
|
||||
ByteString alpn_str { (char const*)alpn + alpn_position, alpn_length };
|
||||
if (alpn_size && m_context.alpn.contains_slow(alpn_str)) {
|
||||
m_context.negotiated_alpn = alpn_str;
|
||||
dbgln("negotiated alpn: {}", alpn_str);
|
||||
break;
|
||||
}
|
||||
alpn_position += alpn_length;
|
||||
if (!m_context.is_server) // server hello must contain one ALPN
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
res += extension_length;
|
||||
} else if (extension_type == ExtensionType::SIGNATURE_ALGORITHMS) {
|
||||
dbgln("supported signatures: ");
|
||||
print_buffer(buffer.slice(res, extension_length));
|
||||
res += extension_length;
|
||||
// FIXME: what are we supposed to do here?
|
||||
} else if (extension_type == ExtensionType::EC_POINT_FORMATS) {
|
||||
// RFC8422 section 5.2: A server that selects an ECC cipher suite in response to a ClientHello message
|
||||
// including a Supported Point Formats Extension appends this extension (along with others) to its
|
||||
// ServerHello message, enumerating the point formats it can parse. The Supported Point Formats Extension,
|
||||
// when used, MUST contain the value 0 (uncompressed) as one of the items in the list of point formats.
|
||||
//
|
||||
// The current implementation only supports uncompressed points, and the server is required to support
|
||||
// uncompressed points. Therefore, this extension can be safely ignored as it should always inform us
|
||||
// that the server supports uncompressed points.
|
||||
res += extension_length;
|
||||
} else if (extension_type == ExtensionType::EXTENDED_MASTER_SECRET) {
|
||||
m_context.extensions.extended_master_secret = true;
|
||||
res += extension_length;
|
||||
} else {
|
||||
dbgln("Encountered unknown extension {} with length {}", enum_to_string(extension_type), extension_length);
|
||||
res += extension_length;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_server_hello_done(ReadonlyBytes buffer)
|
||||
{
|
||||
if (buffer.size() < 3)
|
||||
return (i8)Error::NeedMoreData;
|
||||
|
||||
size_t size = buffer[0] * 0x10000 + buffer[1] * 0x100 + buffer[2];
|
||||
|
||||
if (buffer.size() - 3 < size)
|
||||
return (i8)Error::NeedMoreData;
|
||||
|
||||
return size + 3;
|
||||
}
|
||||
|
||||
ByteBuffer TLSv12::build_server_key_exchange()
|
||||
{
|
||||
dbgln("FIXME: build_server_key_exchange");
|
||||
return {};
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_server_key_exchange(ReadonlyBytes buffer)
|
||||
{
|
||||
switch (get_key_exchange_algorithm(m_context.cipher)) {
|
||||
case KeyExchangeAlgorithm::RSA:
|
||||
case KeyExchangeAlgorithm::DH_DSS:
|
||||
case KeyExchangeAlgorithm::DH_RSA:
|
||||
// RFC 5246 section 7.4.3. Server Key Exchange Message
|
||||
// It is not legal to send the server key exchange message for RSA, DH_DSS, DH_RSA
|
||||
dbgln("Server key exchange received for RSA, DH_DSS or DH_RSA is not legal");
|
||||
return (i8)Error::UnexpectedMessage;
|
||||
case KeyExchangeAlgorithm::DHE_DSS:
|
||||
dbgln("Server key exchange for DHE_DSS is not implemented");
|
||||
TODO();
|
||||
break;
|
||||
case KeyExchangeAlgorithm::DHE_RSA:
|
||||
return handle_dhe_rsa_server_key_exchange(buffer);
|
||||
case KeyExchangeAlgorithm::DH_anon:
|
||||
dbgln("Server key exchange for DH_anon is not implemented");
|
||||
TODO();
|
||||
break;
|
||||
case KeyExchangeAlgorithm::ECDHE_RSA:
|
||||
return handle_ecdhe_rsa_server_key_exchange(buffer);
|
||||
case KeyExchangeAlgorithm::ECDHE_ECDSA:
|
||||
return handle_ecdhe_ecdsa_server_key_exchange(buffer);
|
||||
case KeyExchangeAlgorithm::ECDH_ECDSA:
|
||||
case KeyExchangeAlgorithm::ECDH_RSA:
|
||||
case KeyExchangeAlgorithm::ECDH_anon:
|
||||
dbgln("Server key exchange for ECDHE algorithms is not implemented");
|
||||
TODO();
|
||||
break;
|
||||
default:
|
||||
dbgln("Unknown server key exchange algorithm");
|
||||
VERIFY_NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_dhe_rsa_server_key_exchange(ReadonlyBytes buffer)
|
||||
{
|
||||
auto dh_p_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(3)));
|
||||
auto dh_p = buffer.slice(5, dh_p_length);
|
||||
auto p_result = ByteBuffer::copy(dh_p);
|
||||
if (p_result.is_error()) {
|
||||
dbgln("dhe_rsa_server_key_exchange failed: Not enough memory");
|
||||
return (i8)Error::OutOfMemory;
|
||||
}
|
||||
m_context.server_diffie_hellman_params.p = p_result.release_value();
|
||||
|
||||
auto dh_g_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(5 + dh_p_length)));
|
||||
auto dh_g = buffer.slice(7 + dh_p_length, dh_g_length);
|
||||
auto g_result = ByteBuffer::copy(dh_g);
|
||||
if (g_result.is_error()) {
|
||||
dbgln("dhe_rsa_server_key_exchange failed: Not enough memory");
|
||||
return (i8)Error::OutOfMemory;
|
||||
}
|
||||
m_context.server_diffie_hellman_params.g = g_result.release_value();
|
||||
|
||||
auto dh_Ys_length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(7 + dh_p_length + dh_g_length)));
|
||||
auto dh_Ys = buffer.slice(9 + dh_p_length + dh_g_length, dh_Ys_length);
|
||||
auto Ys_result = ByteBuffer::copy(dh_Ys);
|
||||
if (Ys_result.is_error()) {
|
||||
dbgln("dhe_rsa_server_key_exchange failed: Not enough memory");
|
||||
return (i8)Error::OutOfMemory;
|
||||
}
|
||||
m_context.server_diffie_hellman_params.Ys = Ys_result.release_value();
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("dh_p: {:hex-dump}", dh_p);
|
||||
dbgln("dh_g: {:hex-dump}", dh_g);
|
||||
dbgln("dh_Ys: {:hex-dump}", dh_Ys);
|
||||
}
|
||||
|
||||
auto server_key_info = buffer.slice(3, 6 + dh_p_length + dh_g_length + dh_Ys_length);
|
||||
auto signature = buffer.slice(9 + dh_p_length + dh_g_length + dh_Ys_length);
|
||||
return verify_rsa_server_key_exchange(server_key_info, signature);
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_ecdhe_server_key_exchange(ReadonlyBytes buffer, u8& server_public_key_length)
|
||||
{
|
||||
if (buffer.size() < 7)
|
||||
return (i8)Error::NeedMoreData;
|
||||
|
||||
auto curve_type = buffer[3];
|
||||
if (curve_type != (u8)ECCurveType::NAMED_CURVE)
|
||||
return (i8)Error::NotUnderstood;
|
||||
|
||||
auto curve = static_cast<SupportedGroup>(AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(4))));
|
||||
if (!m_context.options.elliptic_curves.contains_slow(curve))
|
||||
return (i8)Error::NotUnderstood;
|
||||
|
||||
switch ((SupportedGroup)curve) {
|
||||
case SupportedGroup::X25519:
|
||||
m_context.server_key_exchange_curve = make<Crypto::Curves::X25519>();
|
||||
break;
|
||||
case SupportedGroup::X448:
|
||||
m_context.server_key_exchange_curve = make<Crypto::Curves::X448>();
|
||||
break;
|
||||
case SupportedGroup::SECP256R1:
|
||||
m_context.server_key_exchange_curve = make<Crypto::Curves::SECP256r1>();
|
||||
break;
|
||||
case SupportedGroup::SECP384R1:
|
||||
m_context.server_key_exchange_curve = make<Crypto::Curves::SECP384r1>();
|
||||
break;
|
||||
default:
|
||||
return (i8)Error::NotUnderstood;
|
||||
}
|
||||
|
||||
server_public_key_length = buffer[6];
|
||||
if (server_public_key_length != m_context.server_key_exchange_curve->key_size())
|
||||
return (i8)Error::NotUnderstood;
|
||||
|
||||
if (buffer.size() < 7u + server_public_key_length)
|
||||
return (i8)Error::NeedMoreData;
|
||||
|
||||
auto server_public_key = buffer.slice(7, server_public_key_length);
|
||||
auto server_public_key_copy_result = ByteBuffer::copy(server_public_key);
|
||||
if (server_public_key_copy_result.is_error()) {
|
||||
dbgln("ecdhe_rsa_server_key_exchange failed: Not enough memory");
|
||||
return (i8)Error::OutOfMemory;
|
||||
}
|
||||
m_context.server_diffie_hellman_params.p = server_public_key_copy_result.release_value();
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("ECDHE server public key: {:hex-dump}", server_public_key);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_ecdhe_rsa_server_key_exchange(ReadonlyBytes buffer)
|
||||
{
|
||||
u8 server_public_key_length;
|
||||
if (auto result = handle_ecdhe_server_key_exchange(buffer, server_public_key_length)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto server_key_info = buffer.slice(3, 4 + server_public_key_length);
|
||||
auto signature = buffer.slice(7 + server_public_key_length);
|
||||
return verify_rsa_server_key_exchange(server_key_info, signature);
|
||||
}
|
||||
|
||||
ssize_t TLSv12::verify_rsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer)
|
||||
{
|
||||
auto signature_hash = signature_buffer[0];
|
||||
auto signature_algorithm = static_cast<SignatureAlgorithm>(signature_buffer[1]);
|
||||
if (signature_algorithm != SignatureAlgorithm::RSA) {
|
||||
dbgln("verify_rsa_server_key_exchange failed: Signature algorithm is not RSA, instead {}", enum_to_string(signature_algorithm));
|
||||
return (i8)Error::NotUnderstood;
|
||||
}
|
||||
|
||||
auto signature_length = AK::convert_between_host_and_network_endian(ByteReader::load16(signature_buffer.offset_pointer(2)));
|
||||
auto signature = signature_buffer.slice(4, signature_length);
|
||||
|
||||
if (m_context.certificates.is_empty()) {
|
||||
dbgln("verify_rsa_server_key_exchange failed: Attempting to verify signature without certificates");
|
||||
return (i8)Error::NotSafe;
|
||||
}
|
||||
// RFC5246 section 7.4.2: The sender's certificate MUST come first in the list.
|
||||
auto certificate_public_key = m_context.certificates.first().public_key;
|
||||
Crypto::PK::RSAPrivateKey dummy_private_key;
|
||||
auto rsa = Crypto::PK::RSA(certificate_public_key.rsa, dummy_private_key);
|
||||
|
||||
auto signature_verify_buffer_result = ByteBuffer::create_uninitialized(signature_length);
|
||||
if (signature_verify_buffer_result.is_error()) {
|
||||
dbgln("verify_rsa_server_key_exchange failed: Not enough memory");
|
||||
return (i8)Error::OutOfMemory;
|
||||
}
|
||||
auto signature_verify_buffer = signature_verify_buffer_result.release_value();
|
||||
auto signature_verify_bytes = signature_verify_buffer.bytes();
|
||||
rsa.verify(signature, signature_verify_bytes);
|
||||
|
||||
auto message_result = ByteBuffer::create_uninitialized(64 + server_key_info_buffer.size());
|
||||
if (message_result.is_error()) {
|
||||
dbgln("verify_rsa_server_key_exchange failed: Not enough memory");
|
||||
return (i8)Error::OutOfMemory;
|
||||
}
|
||||
auto message = message_result.release_value();
|
||||
message.overwrite(0, m_context.local_random, 32);
|
||||
message.overwrite(32, m_context.remote_random, 32);
|
||||
message.overwrite(64, server_key_info_buffer.data(), server_key_info_buffer.size());
|
||||
|
||||
Crypto::Hash::HashKind hash_kind;
|
||||
switch ((HashAlgorithm)signature_hash) {
|
||||
case HashAlgorithm::SHA1:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA1;
|
||||
break;
|
||||
case HashAlgorithm::SHA256:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA256;
|
||||
break;
|
||||
case HashAlgorithm::SHA384:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA384;
|
||||
break;
|
||||
case HashAlgorithm::SHA512:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA512;
|
||||
break;
|
||||
default:
|
||||
dbgln("verify_rsa_server_key_exchange failed: Hash algorithm is not SHA1/256/384/512, instead {}", signature_hash);
|
||||
return (i8)Error::NotUnderstood;
|
||||
}
|
||||
|
||||
auto pkcs1 = Crypto::PK::EMSA_PKCS1_V1_5<Crypto::Hash::Manager>(hash_kind);
|
||||
auto verification = pkcs1.verify(message, signature_verify_bytes, signature_length * 8);
|
||||
|
||||
if (verification == Crypto::VerificationConsistency::Inconsistent) {
|
||||
dbgln("verify_rsa_server_key_exchange failed: Verification of signature inconsistent");
|
||||
return (i8)Error::NotSafe;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_ecdhe_ecdsa_server_key_exchange(ReadonlyBytes buffer)
|
||||
{
|
||||
u8 server_public_key_length;
|
||||
if (auto result = handle_ecdhe_server_key_exchange(buffer, server_public_key_length)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto server_key_info = buffer.slice(3, 4 + server_public_key_length);
|
||||
auto signature = buffer.slice(7 + server_public_key_length);
|
||||
return verify_ecdsa_server_key_exchange(server_key_info, signature);
|
||||
}
|
||||
|
||||
ssize_t TLSv12::verify_ecdsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer)
|
||||
{
|
||||
auto signature_hash = signature_buffer[0];
|
||||
auto signature_algorithm = signature_buffer[1];
|
||||
if (signature_algorithm != (u8)SignatureAlgorithm::ECDSA) {
|
||||
dbgln("verify_ecdsa_server_key_exchange failed: Signature algorithm is not ECDSA, instead {}", signature_algorithm);
|
||||
return (i8)Error::NotUnderstood;
|
||||
}
|
||||
|
||||
auto signature_length = AK::convert_between_host_and_network_endian(ByteReader::load16(signature_buffer.offset_pointer(2)));
|
||||
auto signature = signature_buffer.slice(4, signature_length);
|
||||
|
||||
if (m_context.certificates.is_empty()) {
|
||||
dbgln("verify_ecdsa_server_key_exchange failed: Attempting to verify signature without certificates");
|
||||
return (i8)Error::NotSafe;
|
||||
}
|
||||
ReadonlyBytes server_point = m_context.certificates.first().public_key.raw_key;
|
||||
|
||||
auto message_result = ByteBuffer::create_uninitialized(64 + server_key_info_buffer.size());
|
||||
if (message_result.is_error()) {
|
||||
dbgln("verify_ecdsa_server_key_exchange failed: Not enough memory");
|
||||
return (i8)Error::OutOfMemory;
|
||||
}
|
||||
auto message = message_result.release_value();
|
||||
message.overwrite(0, m_context.local_random, 32);
|
||||
message.overwrite(32, m_context.remote_random, 32);
|
||||
message.overwrite(64, server_key_info_buffer.data(), server_key_info_buffer.size());
|
||||
|
||||
Crypto::Hash::HashKind hash_kind;
|
||||
switch ((HashAlgorithm)signature_hash) {
|
||||
case HashAlgorithm::SHA256:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA256;
|
||||
break;
|
||||
case HashAlgorithm::SHA384:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA384;
|
||||
break;
|
||||
case HashAlgorithm::SHA512:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA512;
|
||||
break;
|
||||
default:
|
||||
dbgln("verify_ecdsa_server_key_exchange failed: Hash algorithm is not SHA256/384/512, instead {}", signature_hash);
|
||||
return (i8)Error::NotUnderstood;
|
||||
}
|
||||
|
||||
ErrorOr<bool> res = AK::Error::from_errno(ENOTSUP);
|
||||
auto& public_key = m_context.certificates.first().public_key;
|
||||
switch (public_key.algorithm.ec_parameters) {
|
||||
case SupportedGroup::SECP256R1: {
|
||||
Crypto::Hash::Manager manager(hash_kind);
|
||||
manager.update(message);
|
||||
auto digest = manager.digest();
|
||||
|
||||
Crypto::Curves::SECP256r1 curve;
|
||||
res = curve.verify(digest.bytes(), server_point, signature);
|
||||
break;
|
||||
}
|
||||
case SupportedGroup::SECP384R1: {
|
||||
Crypto::Hash::Manager manager(hash_kind);
|
||||
manager.update(message);
|
||||
auto digest = manager.digest();
|
||||
|
||||
Crypto::Curves::SECP384r1 curve;
|
||||
res = curve.verify(digest.bytes(), server_point, signature);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
dbgln("verify_ecdsa_server_key_exchange failed: Server certificate public key algorithm is not supported: {}", to_underlying(public_key.algorithm.ec_parameters));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (res.is_error()) {
|
||||
dbgln("verify_ecdsa_server_key_exchange failed: {}", res.error());
|
||||
return (i8)Error::NotUnderstood;
|
||||
}
|
||||
|
||||
bool verification_ok = res.release_value();
|
||||
if (!verification_ok) {
|
||||
dbgln("verify_ecdsa_server_key_exchange failed: Verification of signature failed");
|
||||
return (i8)Error::NotSafe;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
572
Userland/Libraries/LibTLS/Record.cpp
Normal file
572
Userland/Libraries/LibTLS/Record.cpp
Normal file
|
@ -0,0 +1,572 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
|
||||
namespace TLS {
|
||||
|
||||
ByteBuffer TLSv12::build_alert(bool critical, u8 code)
|
||||
{
|
||||
PacketBuilder builder(ContentType::ALERT, (u16)m_context.options.version);
|
||||
builder.append((u8)(critical ? AlertLevel::FATAL : AlertLevel::WARNING));
|
||||
builder.append(code);
|
||||
|
||||
if (critical)
|
||||
m_context.critical_error = code;
|
||||
|
||||
auto packet = builder.build();
|
||||
update_packet(packet);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
void TLSv12::alert(AlertLevel level, AlertDescription code)
|
||||
{
|
||||
auto the_alert = build_alert(level == AlertLevel::FATAL, (u8)code);
|
||||
write_packet(the_alert, true);
|
||||
MUST(flush());
|
||||
}
|
||||
|
||||
void TLSv12::write_packet(ByteBuffer& packet, bool immediately)
|
||||
{
|
||||
auto schedule_or_perform_flush = [&](bool immediate) {
|
||||
if (m_context.connection_status > ConnectionStatus::Disconnected) {
|
||||
if (!m_has_scheduled_write_flush && !immediate) {
|
||||
dbgln_if(TLS_DEBUG, "Scheduling write of {}", m_context.tls_buffer.size());
|
||||
Core::deferred_invoke([this] { write_into_socket(); });
|
||||
m_has_scheduled_write_flush = true;
|
||||
} else {
|
||||
// multiple packet are available, let's flush some out
|
||||
dbgln_if(TLS_DEBUG, "Flushing scheduled write of {}", m_context.tls_buffer.size());
|
||||
write_into_socket();
|
||||
// the deferred invoke is still in place
|
||||
m_has_scheduled_write_flush = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
// Record size limit is 18432 bytes, leave some headroom and flush at 16K.
|
||||
if (m_context.tls_buffer.size() + packet.size() > 16 * KiB)
|
||||
schedule_or_perform_flush(true);
|
||||
|
||||
if (m_context.tls_buffer.try_append(packet.data(), packet.size()).is_error()) {
|
||||
// Toooooo bad, drop the record on the ground.
|
||||
return;
|
||||
}
|
||||
schedule_or_perform_flush(immediately);
|
||||
}
|
||||
|
||||
void TLSv12::update_packet(ByteBuffer& packet)
|
||||
{
|
||||
u32 header_size = 5;
|
||||
ByteReader::store(packet.offset_pointer(3), AK::convert_between_host_and_network_endian((u16)(packet.size() - header_size)));
|
||||
|
||||
if (packet[0] != (u8)ContentType::CHANGE_CIPHER_SPEC) {
|
||||
if (packet[0] == (u8)ContentType::HANDSHAKE && packet.size() > header_size) {
|
||||
auto handshake_type = static_cast<HandshakeType>(packet[header_size]);
|
||||
if (handshake_type != HandshakeType::HELLO_REQUEST_RESERVED && handshake_type != HandshakeType::HELLO_VERIFY_REQUEST_RESERVED) {
|
||||
update_hash(packet.bytes(), header_size);
|
||||
}
|
||||
}
|
||||
if (m_context.cipher_spec_set && m_context.crypto.created) {
|
||||
size_t length = packet.size() - header_size;
|
||||
size_t block_size = 0;
|
||||
size_t padding = 0;
|
||||
size_t mac_size = 0;
|
||||
|
||||
m_cipher_local.visit(
|
||||
[&](Empty&) { VERIFY_NOT_REACHED(); },
|
||||
[&](Crypto::Cipher::AESCipher::GCMMode& gcm) {
|
||||
VERIFY(is_aead());
|
||||
block_size = gcm.cipher().block_size();
|
||||
padding = 0;
|
||||
mac_size = 0; // AEAD provides its own authentication scheme.
|
||||
},
|
||||
[&](Crypto::Cipher::AESCipher::CBCMode& cbc) {
|
||||
VERIFY(!is_aead());
|
||||
block_size = cbc.cipher().block_size();
|
||||
// If the length is already a multiple a block_size,
|
||||
// an entire block of padding is added.
|
||||
// In short, we _never_ have no padding.
|
||||
mac_size = mac_length();
|
||||
length += mac_size;
|
||||
padding = block_size - length % block_size;
|
||||
length += padding;
|
||||
});
|
||||
|
||||
if (m_context.crypto.created == 1) {
|
||||
// `buffer' will continue to be encrypted
|
||||
auto buffer_result = ByteBuffer::create_uninitialized(length);
|
||||
if (buffer_result.is_error()) {
|
||||
dbgln("LibTLS: Failed to allocate enough memory");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
auto buffer = buffer_result.release_value();
|
||||
size_t buffer_position = 0;
|
||||
auto iv_size = iv_length();
|
||||
|
||||
// copy the packet, sans the header
|
||||
buffer.overwrite(buffer_position, packet.offset_pointer(header_size), packet.size() - header_size);
|
||||
buffer_position += packet.size() - header_size;
|
||||
|
||||
ByteBuffer ct;
|
||||
|
||||
m_cipher_local.visit(
|
||||
[&](Empty&) { VERIFY_NOT_REACHED(); },
|
||||
[&](Crypto::Cipher::AESCipher::GCMMode& gcm) {
|
||||
VERIFY(is_aead());
|
||||
// We need enough space for a header, the data, a tag, and the IV
|
||||
auto ct_buffer_result = ByteBuffer::create_uninitialized(length + header_size + iv_size + 16);
|
||||
if (ct_buffer_result.is_error()) {
|
||||
dbgln("LibTLS: Failed to allocate enough memory for the ciphertext");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
ct = ct_buffer_result.release_value();
|
||||
|
||||
// copy the header over
|
||||
ct.overwrite(0, packet.data(), header_size - 2);
|
||||
|
||||
// AEAD AAD (13)
|
||||
// Seq. no (8)
|
||||
// content type (1)
|
||||
// version (2)
|
||||
// length (2)
|
||||
u8 aad[13];
|
||||
Bytes aad_bytes { aad, 13 };
|
||||
FixedMemoryStream aad_stream { aad_bytes };
|
||||
|
||||
u64 seq_no = AK::convert_between_host_and_network_endian(m_context.local_sequence_number);
|
||||
u16 len = AK::convert_between_host_and_network_endian((u16)(packet.size() - header_size));
|
||||
|
||||
MUST(aad_stream.write_value(seq_no)); // sequence number
|
||||
MUST(aad_stream.write_until_depleted(packet.bytes().slice(0, 3))); // content-type + version
|
||||
MUST(aad_stream.write_value(len)); // length
|
||||
VERIFY(MUST(aad_stream.tell()) == MUST(aad_stream.size()));
|
||||
|
||||
// AEAD IV (12)
|
||||
// IV (4)
|
||||
// (Nonce) (8)
|
||||
// -- Our GCM impl takes 16 bytes
|
||||
// zero (4)
|
||||
u8 iv[16];
|
||||
Bytes iv_bytes { iv, 16 };
|
||||
Bytes { m_context.crypto.local_aead_iv, 4 }.copy_to(iv_bytes);
|
||||
fill_with_random(iv_bytes.slice(4, 8));
|
||||
memset(iv_bytes.offset(12), 0, 4);
|
||||
|
||||
// write the random part of the iv out
|
||||
iv_bytes.slice(4, 8).copy_to(ct.bytes().slice(header_size));
|
||||
|
||||
// Write the encrypted data and the tag
|
||||
gcm.encrypt(
|
||||
packet.bytes().slice(header_size, length),
|
||||
ct.bytes().slice(header_size + 8, length),
|
||||
iv_bytes,
|
||||
aad_bytes,
|
||||
ct.bytes().slice(header_size + 8 + length, 16));
|
||||
|
||||
VERIFY(header_size + 8 + length + 16 == ct.size());
|
||||
},
|
||||
[&](Crypto::Cipher::AESCipher::CBCMode& cbc) {
|
||||
VERIFY(!is_aead());
|
||||
// We need enough space for a header, iv_length bytes of IV and whatever the packet contains
|
||||
auto ct_buffer_result = ByteBuffer::create_uninitialized(length + header_size + iv_size);
|
||||
if (ct_buffer_result.is_error()) {
|
||||
dbgln("LibTLS: Failed to allocate enough memory for the ciphertext");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
ct = ct_buffer_result.release_value();
|
||||
|
||||
// copy the header over
|
||||
ct.overwrite(0, packet.data(), header_size - 2);
|
||||
|
||||
// get the appropriate HMAC value for the entire packet
|
||||
auto mac = hmac_message(packet, {}, mac_size, true);
|
||||
|
||||
// write the MAC
|
||||
buffer.overwrite(buffer_position, mac.data(), mac.size());
|
||||
buffer_position += mac.size();
|
||||
|
||||
// Apply the padding (a packet MUST always be padded)
|
||||
memset(buffer.offset_pointer(buffer_position), padding - 1, padding);
|
||||
buffer_position += padding;
|
||||
|
||||
VERIFY(buffer_position == buffer.size());
|
||||
|
||||
auto iv_buffer_result = ByteBuffer::create_uninitialized(iv_size);
|
||||
if (iv_buffer_result.is_error()) {
|
||||
dbgln("LibTLS: Failed to allocate memory for IV");
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
auto iv = iv_buffer_result.release_value();
|
||||
fill_with_random(iv);
|
||||
|
||||
// write it into the ciphertext portion of the message
|
||||
ct.overwrite(header_size, iv.data(), iv.size());
|
||||
|
||||
VERIFY(header_size + iv_size + length == ct.size());
|
||||
VERIFY(length % block_size == 0);
|
||||
|
||||
// get a block to encrypt into
|
||||
auto view = ct.bytes().slice(header_size + iv_size, length);
|
||||
cbc.encrypt(buffer, view, iv);
|
||||
});
|
||||
|
||||
// store the correct ciphertext length into the packet
|
||||
u16 ct_length = (u16)ct.size() - header_size;
|
||||
|
||||
ByteReader::store(ct.offset_pointer(header_size - 2), AK::convert_between_host_and_network_endian(ct_length));
|
||||
|
||||
// replace the packet with the ciphertext
|
||||
packet = ct;
|
||||
}
|
||||
}
|
||||
}
|
||||
++m_context.local_sequence_number;
|
||||
}
|
||||
|
||||
void TLSv12::update_hash(ReadonlyBytes message, size_t header_size)
|
||||
{
|
||||
dbgln_if(TLS_DEBUG, "Update hash with message of size {}", message.size());
|
||||
m_context.handshake_hash.update(message.slice(header_size));
|
||||
}
|
||||
|
||||
void TLSv12::ensure_hmac(size_t digest_size, bool local)
|
||||
{
|
||||
if (local && m_hmac_local)
|
||||
return;
|
||||
|
||||
if (!local && m_hmac_remote)
|
||||
return;
|
||||
|
||||
auto hash_kind = Crypto::Hash::HashKind::None;
|
||||
|
||||
switch (digest_size) {
|
||||
case Crypto::Hash::SHA1::DigestSize:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA1;
|
||||
break;
|
||||
case Crypto::Hash::SHA256::DigestSize:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA256;
|
||||
break;
|
||||
case Crypto::Hash::SHA384::DigestSize:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA384;
|
||||
break;
|
||||
case Crypto::Hash::SHA512::DigestSize:
|
||||
hash_kind = Crypto::Hash::HashKind::SHA512;
|
||||
break;
|
||||
default:
|
||||
dbgln("Failed to find a suitable hash for size {}", digest_size);
|
||||
break;
|
||||
}
|
||||
|
||||
auto hmac = make<Crypto::Authentication::HMAC<Crypto::Hash::Manager>>(ReadonlyBytes { local ? m_context.crypto.local_mac : m_context.crypto.remote_mac, digest_size }, hash_kind);
|
||||
if (local)
|
||||
m_hmac_local = move(hmac);
|
||||
else
|
||||
m_hmac_remote = move(hmac);
|
||||
}
|
||||
|
||||
ByteBuffer TLSv12::hmac_message(ReadonlyBytes buf, Optional<ReadonlyBytes> const buf2, size_t mac_length, bool local)
|
||||
{
|
||||
u64 sequence_number = AK::convert_between_host_and_network_endian(local ? m_context.local_sequence_number : m_context.remote_sequence_number);
|
||||
ensure_hmac(mac_length, local);
|
||||
auto& hmac = local ? *m_hmac_local : *m_hmac_remote;
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("========================= PACKET DATA ==========================");
|
||||
print_buffer((u8 const*)&sequence_number, sizeof(u64));
|
||||
print_buffer(buf.data(), buf.size());
|
||||
if (buf2.has_value())
|
||||
print_buffer(buf2.value().data(), buf2.value().size());
|
||||
dbgln("========================= PACKET DATA ==========================");
|
||||
}
|
||||
hmac.update((u8 const*)&sequence_number, sizeof(u64));
|
||||
hmac.update(buf);
|
||||
if (buf2.has_value() && buf2.value().size()) {
|
||||
hmac.update(buf2.value());
|
||||
}
|
||||
auto digest = hmac.digest();
|
||||
auto mac_result = ByteBuffer::copy(digest.immutable_data(), digest.data_length());
|
||||
if (mac_result.is_error()) {
|
||||
dbgln("Failed to calculate message HMAC: Not enough memory");
|
||||
return {};
|
||||
}
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("HMAC of the block for sequence number {}", sequence_number);
|
||||
print_buffer(mac_result.value());
|
||||
}
|
||||
|
||||
return mac_result.release_value();
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_message(ReadonlyBytes buffer)
|
||||
{
|
||||
auto res { 5ll };
|
||||
size_t header_size = res;
|
||||
ssize_t payload_res = 0;
|
||||
|
||||
dbgln_if(TLS_DEBUG, "buffer size: {}", buffer.size());
|
||||
|
||||
if (buffer.size() < 5) {
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
|
||||
auto type = (ContentType)buffer[0];
|
||||
size_t buffer_position { 1 };
|
||||
|
||||
// FIXME: Read the version and verify it
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
auto version = static_cast<ProtocolVersion>(ByteReader::load16(buffer.offset_pointer(buffer_position)));
|
||||
dbgln("type={}, version={}", enum_to_string(type), enum_to_string(version));
|
||||
}
|
||||
|
||||
buffer_position += 2;
|
||||
|
||||
auto length = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(buffer_position)));
|
||||
|
||||
dbgln_if(TLS_DEBUG, "record length: {} at offset: {}", length, buffer_position);
|
||||
buffer_position += 2;
|
||||
|
||||
if (buffer_position + length > buffer.size()) {
|
||||
dbgln_if(TLS_DEBUG, "record length more than what we have: {}", buffer.size());
|
||||
return (i8)Error::NeedMoreData;
|
||||
}
|
||||
|
||||
dbgln_if(TLS_DEBUG, "message type: {}, length: {}", enum_to_string(type), length);
|
||||
auto plain = buffer.slice(buffer_position, buffer.size() - buffer_position);
|
||||
|
||||
ByteBuffer decrypted;
|
||||
|
||||
if (m_context.cipher_spec_set && type != ContentType::CHANGE_CIPHER_SPEC) {
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("Encrypted: ");
|
||||
print_buffer(buffer.slice(header_size, length));
|
||||
}
|
||||
|
||||
Error return_value = Error::NoError;
|
||||
m_cipher_remote.visit(
|
||||
[&](Empty&) { VERIFY_NOT_REACHED(); },
|
||||
[&](Crypto::Cipher::AESCipher::GCMMode& gcm) {
|
||||
VERIFY(is_aead());
|
||||
if (length < 24) {
|
||||
dbgln("Invalid packet length");
|
||||
auto packet = build_alert(true, (u8)AlertDescription::DECRYPT_ERROR);
|
||||
write_packet(packet);
|
||||
return_value = Error::BrokenPacket;
|
||||
return;
|
||||
}
|
||||
|
||||
auto packet_length = length - iv_length() - 16;
|
||||
auto payload = plain;
|
||||
auto decrypted_result = ByteBuffer::create_uninitialized(packet_length);
|
||||
if (decrypted_result.is_error()) {
|
||||
dbgln("Failed to allocate memory for the packet");
|
||||
return_value = Error::DecryptionFailed;
|
||||
return;
|
||||
}
|
||||
decrypted = decrypted_result.release_value();
|
||||
|
||||
// AEAD AAD (13)
|
||||
// Seq. no (8)
|
||||
// content type (1)
|
||||
// version (2)
|
||||
// length (2)
|
||||
u8 aad[13];
|
||||
Bytes aad_bytes { aad, 13 };
|
||||
FixedMemoryStream aad_stream { aad_bytes };
|
||||
|
||||
u64 seq_no = AK::convert_between_host_and_network_endian(m_context.remote_sequence_number);
|
||||
u16 len = AK::convert_between_host_and_network_endian((u16)packet_length);
|
||||
|
||||
MUST(aad_stream.write_value(seq_no)); // sequence number
|
||||
MUST(aad_stream.write_until_depleted(buffer.slice(0, header_size - 2))); // content-type + version
|
||||
MUST(aad_stream.write_value(len)); // length
|
||||
VERIFY(MUST(aad_stream.tell()) == MUST(aad_stream.size()));
|
||||
|
||||
auto nonce = payload.slice(0, iv_length());
|
||||
payload = payload.slice(iv_length());
|
||||
|
||||
// AEAD IV (12)
|
||||
// IV (4)
|
||||
// (Nonce) (8)
|
||||
// -- Our GCM impl takes 16 bytes
|
||||
// zero (4)
|
||||
u8 iv[16];
|
||||
Bytes iv_bytes { iv, 16 };
|
||||
Bytes { m_context.crypto.remote_aead_iv, 4 }.copy_to(iv_bytes);
|
||||
nonce.copy_to(iv_bytes.slice(4));
|
||||
memset(iv_bytes.offset(12), 0, 4);
|
||||
|
||||
auto ciphertext = payload.slice(0, payload.size() - 16);
|
||||
auto tag = payload.slice(ciphertext.size());
|
||||
|
||||
auto consistency = gcm.decrypt(
|
||||
ciphertext,
|
||||
decrypted,
|
||||
iv_bytes,
|
||||
aad_bytes,
|
||||
tag);
|
||||
|
||||
if (consistency != Crypto::VerificationConsistency::Consistent) {
|
||||
dbgln("integrity check failed (tag length {})", tag.size());
|
||||
auto packet = build_alert(true, (u8)AlertDescription::BAD_RECORD_MAC);
|
||||
write_packet(packet);
|
||||
|
||||
return_value = Error::IntegrityCheckFailed;
|
||||
return;
|
||||
}
|
||||
|
||||
plain = decrypted;
|
||||
},
|
||||
[&](Crypto::Cipher::AESCipher::CBCMode& cbc) {
|
||||
VERIFY(!is_aead());
|
||||
auto iv_size = iv_length();
|
||||
|
||||
auto decrypted_result = cbc.create_aligned_buffer(length - iv_size);
|
||||
if (decrypted_result.is_error()) {
|
||||
dbgln("Failed to allocate memory for the packet");
|
||||
return_value = Error::DecryptionFailed;
|
||||
return;
|
||||
}
|
||||
decrypted = decrypted_result.release_value();
|
||||
auto iv = buffer.slice(header_size, iv_size);
|
||||
|
||||
Bytes decrypted_span = decrypted;
|
||||
cbc.decrypt(buffer.slice(header_size + iv_size, length - iv_size), decrypted_span, iv);
|
||||
|
||||
length = decrypted_span.size();
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("Decrypted: ");
|
||||
print_buffer(decrypted);
|
||||
}
|
||||
|
||||
auto mac_size = mac_length();
|
||||
if (length < mac_size) {
|
||||
dbgln("broken packet");
|
||||
auto packet = build_alert(true, (u8)AlertDescription::DECRYPT_ERROR);
|
||||
write_packet(packet);
|
||||
return_value = Error::BrokenPacket;
|
||||
return;
|
||||
}
|
||||
|
||||
length -= mac_size;
|
||||
|
||||
u8 const* message_hmac = decrypted_span.offset(length);
|
||||
u8 temp_buf[5];
|
||||
memcpy(temp_buf, buffer.offset_pointer(0), 3);
|
||||
*(u16*)(temp_buf + 3) = AK::convert_between_host_and_network_endian(length);
|
||||
auto hmac = hmac_message({ temp_buf, 5 }, decrypted_span.slice(0, length), mac_size);
|
||||
auto message_mac = ReadonlyBytes { message_hmac, mac_size };
|
||||
if (hmac != message_mac) {
|
||||
dbgln("integrity check failed (mac length {})", mac_size);
|
||||
dbgln("mac received:");
|
||||
print_buffer(message_mac);
|
||||
dbgln("mac computed:");
|
||||
print_buffer(hmac);
|
||||
auto packet = build_alert(true, (u8)AlertDescription::BAD_RECORD_MAC);
|
||||
write_packet(packet);
|
||||
|
||||
return_value = Error::IntegrityCheckFailed;
|
||||
return;
|
||||
}
|
||||
plain = decrypted.bytes().slice(0, length);
|
||||
});
|
||||
|
||||
if (return_value != Error::NoError) {
|
||||
return (i8)return_value;
|
||||
}
|
||||
}
|
||||
m_context.remote_sequence_number++;
|
||||
|
||||
switch (type) {
|
||||
case ContentType::APPLICATION_DATA:
|
||||
if (m_context.connection_status != ConnectionStatus::Established) {
|
||||
dbgln("unexpected application data");
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
auto packet = build_alert(true, (u8)AlertDescription::UNEXPECTED_MESSAGE);
|
||||
write_packet(packet);
|
||||
} else {
|
||||
dbgln_if(TLS_DEBUG, "application data message of size {}", plain.size());
|
||||
|
||||
if (m_context.application_buffer.try_append(plain).is_error()) {
|
||||
payload_res = (i8)Error::DecryptionFailed;
|
||||
auto packet = build_alert(true, (u8)AlertDescription::DECRYPTION_FAILED_RESERVED);
|
||||
write_packet(packet);
|
||||
} else {
|
||||
notify_client_for_app_data();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ContentType::HANDSHAKE:
|
||||
dbgln_if(TLS_DEBUG, "tls handshake message");
|
||||
payload_res = handle_handshake_payload(plain);
|
||||
break;
|
||||
case ContentType::CHANGE_CIPHER_SPEC:
|
||||
if (m_context.connection_status != ConnectionStatus::KeyExchange) {
|
||||
dbgln("unexpected change cipher message");
|
||||
auto packet = build_alert(true, (u8)AlertDescription::UNEXPECTED_MESSAGE);
|
||||
write_packet(packet);
|
||||
payload_res = (i8)Error::UnexpectedMessage;
|
||||
} else {
|
||||
dbgln_if(TLS_DEBUG, "change cipher spec message");
|
||||
m_context.cipher_spec_set = true;
|
||||
m_context.remote_sequence_number = 0;
|
||||
}
|
||||
break;
|
||||
case ContentType::ALERT:
|
||||
dbgln_if(TLS_DEBUG, "alert message of length {}", length);
|
||||
if (length >= 2) {
|
||||
if constexpr (TLS_DEBUG)
|
||||
print_buffer(plain);
|
||||
|
||||
auto level = plain[0];
|
||||
auto code = plain[1];
|
||||
dbgln_if(TLS_DEBUG, "Alert received with level {}, code {}", level, code);
|
||||
|
||||
if (level == (u8)AlertLevel::FATAL) {
|
||||
dbgln("We were alerted of a critical error: {} ({})", code, enum_to_string((AlertDescription)code));
|
||||
m_context.critical_error = code;
|
||||
try_disambiguate_error();
|
||||
res = (i8)Error::UnknownError;
|
||||
}
|
||||
|
||||
if (code == (u8)AlertDescription::CLOSE_NOTIFY) {
|
||||
res += 2;
|
||||
alert(AlertLevel::FATAL, AlertDescription::CLOSE_NOTIFY);
|
||||
if (!m_context.cipher_spec_set) {
|
||||
// AWS CloudFront hits this.
|
||||
dbgln("Server sent a close notify and we haven't agreed on a cipher suite. Treating it as a handshake failure.");
|
||||
m_context.critical_error = (u8)AlertDescription::HANDSHAKE_FAILURE;
|
||||
try_disambiguate_error();
|
||||
}
|
||||
m_context.close_notify = true;
|
||||
}
|
||||
m_context.error_code = (Error)code;
|
||||
check_connection_state(false);
|
||||
notify_client_for_app_data(); // Give the user one more chance to observe the EOF
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dbgln("message not understood");
|
||||
return (i8)Error::NotUnderstood;
|
||||
}
|
||||
|
||||
if (payload_res < 0)
|
||||
return payload_res;
|
||||
|
||||
if (res > 0)
|
||||
return header_size + length;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
323
Userland/Libraries/LibTLS/Socket.cpp
Normal file
323
Userland/Libraries/LibTLS/Socket.cpp
Normal file
|
@ -0,0 +1,323 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Debug.h>
|
||||
#include <LibCore/DateTime.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/Promise.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
|
||||
// Each record can hold at most 18432 bytes, leaving some headroom and rounding down to
|
||||
// a nice number gives us a maximum of 16 KiB for user-supplied application data,
|
||||
// which will be sent as a single record containing a single ApplicationData message.
|
||||
constexpr static size_t MaximumApplicationDataChunkSize = 16 * KiB;
|
||||
|
||||
namespace TLS {
|
||||
|
||||
ErrorOr<Bytes> TLSv12::read_some(Bytes bytes)
|
||||
{
|
||||
m_eof = false;
|
||||
auto size_to_read = min(bytes.size(), m_context.application_buffer.size());
|
||||
if (size_to_read == 0) {
|
||||
m_eof = true;
|
||||
return Bytes {};
|
||||
}
|
||||
|
||||
m_context.application_buffer.transfer(bytes, size_to_read);
|
||||
return Bytes { bytes.data(), size_to_read };
|
||||
}
|
||||
|
||||
ErrorOr<size_t> TLSv12::write_some(ReadonlyBytes bytes)
|
||||
{
|
||||
if (m_context.connection_status != ConnectionStatus::Established) {
|
||||
dbgln_if(TLS_DEBUG, "write request while not connected");
|
||||
return AK::Error::from_string_literal("TLS write request while not connected");
|
||||
}
|
||||
|
||||
for (size_t offset = 0; offset < bytes.size(); offset += MaximumApplicationDataChunkSize) {
|
||||
PacketBuilder builder { ContentType::APPLICATION_DATA, m_context.options.version, bytes.size() - offset };
|
||||
builder.append(bytes.slice(offset, min(bytes.size() - offset, MaximumApplicationDataChunkSize)));
|
||||
auto packet = builder.build();
|
||||
|
||||
update_packet(packet);
|
||||
write_packet(packet);
|
||||
}
|
||||
|
||||
return bytes.size();
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect(ByteString const& host, u16 port, Options options)
|
||||
{
|
||||
auto promise = Core::Promise<Empty>::construct();
|
||||
OwnPtr<Core::Socket> tcp_socket = TRY(Core::TCPSocket::connect(host, port));
|
||||
TRY(tcp_socket->set_blocking(false));
|
||||
auto tls_socket = make<TLSv12>(move(tcp_socket), move(options));
|
||||
tls_socket->set_sni(host);
|
||||
tls_socket->on_connected = [&] {
|
||||
promise->resolve({});
|
||||
};
|
||||
tls_socket->on_tls_error = [&](auto alert) {
|
||||
tls_socket->try_disambiguate_error();
|
||||
promise->reject(AK::Error::from_string_view(enum_to_string(alert)));
|
||||
};
|
||||
|
||||
TRY(promise->await());
|
||||
|
||||
tls_socket->on_tls_error = nullptr;
|
||||
tls_socket->on_connected = nullptr;
|
||||
tls_socket->m_context.should_expect_successful_read = true;
|
||||
return tls_socket;
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<TLSv12>> TLSv12::connect(ByteString const& host, Core::Socket& underlying_stream, Options options)
|
||||
{
|
||||
auto promise = Core::Promise<Empty>::construct();
|
||||
TRY(underlying_stream.set_blocking(false));
|
||||
auto tls_socket = make<TLSv12>(&underlying_stream, move(options));
|
||||
tls_socket->set_sni(host);
|
||||
tls_socket->on_connected = [&] {
|
||||
promise->resolve({});
|
||||
};
|
||||
tls_socket->on_tls_error = [&](auto alert) {
|
||||
tls_socket->try_disambiguate_error();
|
||||
promise->reject(AK::Error::from_string_view(enum_to_string(alert)));
|
||||
};
|
||||
TRY(promise->await());
|
||||
|
||||
tls_socket->on_tls_error = nullptr;
|
||||
tls_socket->on_connected = nullptr;
|
||||
tls_socket->m_context.should_expect_successful_read = true;
|
||||
return tls_socket;
|
||||
}
|
||||
|
||||
void TLSv12::setup_connection()
|
||||
{
|
||||
Core::deferred_invoke([this] {
|
||||
auto& stream = underlying_stream();
|
||||
stream.on_ready_to_read = [this] {
|
||||
auto result = read_from_socket();
|
||||
if (result.is_error())
|
||||
dbgln("Read error: {}", result.error());
|
||||
};
|
||||
|
||||
m_handshake_timeout_timer = Core::Timer::create_single_shot(
|
||||
m_max_wait_time_for_handshake_in_seconds * 1000, [&] {
|
||||
dbgln("Handshake timeout :(");
|
||||
auto timeout_diff = Core::DateTime::now().timestamp() - m_context.handshake_initiation_timestamp;
|
||||
// If the timeout duration was actually within the max wait time (with a margin of error),
|
||||
// we're not operating slow, so the server timed out.
|
||||
// otherwise, it's our fault that the negotiation is taking too long, so extend the timer :P
|
||||
if (timeout_diff < m_max_wait_time_for_handshake_in_seconds + 1) {
|
||||
// The server did not respond fast enough,
|
||||
// time the connection out.
|
||||
alert(AlertLevel::FATAL, AlertDescription::USER_CANCELED);
|
||||
m_context.tls_buffer.clear();
|
||||
m_context.error_code = Error::TimedOut;
|
||||
m_context.critical_error = (u8)Error::TimedOut;
|
||||
check_connection_state(false); // Notify the client.
|
||||
} else {
|
||||
// Extend the timer, we are too slow.
|
||||
m_handshake_timeout_timer->restart(m_max_wait_time_for_handshake_in_seconds * 1000);
|
||||
}
|
||||
});
|
||||
auto packet = build_hello();
|
||||
write_packet(packet);
|
||||
write_into_socket();
|
||||
m_handshake_timeout_timer->start();
|
||||
m_context.handshake_initiation_timestamp = Core::DateTime::now().timestamp();
|
||||
});
|
||||
m_has_scheduled_write_flush = true;
|
||||
}
|
||||
|
||||
void TLSv12::notify_client_for_app_data()
|
||||
{
|
||||
if (m_context.application_buffer.size() > 0) {
|
||||
if (on_ready_to_read)
|
||||
on_ready_to_read();
|
||||
} else {
|
||||
if (m_context.connection_finished && !m_context.has_invoked_finish_or_error_callback) {
|
||||
m_context.has_invoked_finish_or_error_callback = true;
|
||||
if (on_tls_finished)
|
||||
on_tls_finished();
|
||||
}
|
||||
}
|
||||
m_has_scheduled_app_data_flush = false;
|
||||
}
|
||||
|
||||
ErrorOr<void> TLSv12::read_from_socket()
|
||||
{
|
||||
// If there's anything before we consume stuff, let the client know
|
||||
// since we won't be consuming things if the connection is terminated.
|
||||
notify_client_for_app_data();
|
||||
|
||||
ScopeGuard notify_guard {
|
||||
[this] {
|
||||
// If anything new shows up, tell the client about the event.
|
||||
notify_client_for_app_data();
|
||||
}
|
||||
};
|
||||
|
||||
if (!check_connection_state(true))
|
||||
return {};
|
||||
|
||||
u8 buffer[16 * KiB];
|
||||
Bytes bytes { buffer, array_size(buffer) };
|
||||
Bytes read_bytes {};
|
||||
auto& stream = underlying_stream();
|
||||
do {
|
||||
auto result = stream.read_some(bytes);
|
||||
if (result.is_error()) {
|
||||
if (result.error().is_errno() && result.error().code() != EINTR) {
|
||||
if (result.error().code() != EAGAIN)
|
||||
dbgln("TLS Socket read failed, error: {}", result.error());
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
read_bytes = result.release_value();
|
||||
consume(read_bytes);
|
||||
} while (!read_bytes.is_empty() && !m_context.critical_error);
|
||||
|
||||
if (m_context.should_expect_successful_read && read_bytes.is_empty()) {
|
||||
// read_some() returned an empty span, this is either an EOF (from improper closure)
|
||||
// or some sort of weird even that is showing itself as an EOF.
|
||||
// To guard against servers closing the connection weirdly or just improperly, make sure
|
||||
// to check the connection state here and send the appropriate notifications.
|
||||
stream.close();
|
||||
|
||||
check_connection_state(true);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void TLSv12::write_into_socket()
|
||||
{
|
||||
dbgln_if(TLS_DEBUG, "Flushing cached records: {} established? {}", m_context.tls_buffer.size(), is_established());
|
||||
|
||||
m_has_scheduled_write_flush = false;
|
||||
if (!check_connection_state(false))
|
||||
return;
|
||||
|
||||
MUST(flush());
|
||||
}
|
||||
|
||||
bool TLSv12::check_connection_state(bool read)
|
||||
{
|
||||
if (m_context.connection_finished)
|
||||
return false;
|
||||
|
||||
if (m_context.close_notify)
|
||||
m_context.connection_finished = true;
|
||||
|
||||
auto& stream = underlying_stream();
|
||||
|
||||
if (!stream.is_open()) {
|
||||
// an abrupt closure (the server is a jerk)
|
||||
dbgln_if(TLS_DEBUG, "Socket not open, assuming abrupt closure");
|
||||
m_context.connection_finished = true;
|
||||
m_context.connection_status = ConnectionStatus::Disconnected;
|
||||
close();
|
||||
m_context.has_invoked_finish_or_error_callback = true;
|
||||
if (on_ready_to_read)
|
||||
on_ready_to_read(); // Notify the client about the weird event.
|
||||
if (on_tls_finished)
|
||||
on_tls_finished();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read && stream.is_eof()) {
|
||||
if (m_context.application_buffer.size() == 0 && m_context.connection_status != ConnectionStatus::Disconnected) {
|
||||
m_context.has_invoked_finish_or_error_callback = true;
|
||||
if (on_tls_finished)
|
||||
on_tls_finished();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_context.critical_error) {
|
||||
dbgln_if(TLS_DEBUG, "CRITICAL ERROR {} :(", m_context.critical_error);
|
||||
|
||||
m_context.has_invoked_finish_or_error_callback = true;
|
||||
if (on_tls_error)
|
||||
on_tls_error((AlertDescription)m_context.critical_error);
|
||||
m_context.connection_finished = true;
|
||||
m_context.connection_status = ConnectionStatus::Disconnected;
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (((read && m_context.application_buffer.size() == 0) || !read) && m_context.connection_finished) {
|
||||
if (m_context.application_buffer.size() == 0 && m_context.connection_status != ConnectionStatus::Disconnected) {
|
||||
m_context.has_invoked_finish_or_error_callback = true;
|
||||
if (on_tls_finished)
|
||||
on_tls_finished();
|
||||
}
|
||||
if (m_context.tls_buffer.size()) {
|
||||
dbgln_if(TLS_DEBUG, "connection closed without finishing data transfer, {} bytes still in buffer and {} bytes in application buffer",
|
||||
m_context.tls_buffer.size(),
|
||||
m_context.application_buffer.size());
|
||||
}
|
||||
if (!m_context.application_buffer.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ErrorOr<bool> TLSv12::flush()
|
||||
{
|
||||
auto out_bytes = m_context.tls_buffer.bytes();
|
||||
|
||||
if (out_bytes.is_empty())
|
||||
return true;
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("SENDING...");
|
||||
print_buffer(out_bytes);
|
||||
}
|
||||
|
||||
auto& stream = underlying_stream();
|
||||
Optional<AK::Error> error;
|
||||
size_t written;
|
||||
do {
|
||||
auto result = stream.write_some(out_bytes);
|
||||
if (result.is_error()) {
|
||||
if (result.error().code() != EINTR && result.error().code() != EAGAIN) {
|
||||
error = result.release_error();
|
||||
dbgln("TLS Socket write error: {}", *error);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
written = result.value();
|
||||
out_bytes = out_bytes.slice(written);
|
||||
} while (!out_bytes.is_empty());
|
||||
|
||||
if (out_bytes.is_empty() && !error.has_value()) {
|
||||
m_context.tls_buffer.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_context.send_retries++ == 10) {
|
||||
// drop the records, we can't send
|
||||
dbgln_if(TLS_DEBUG, "Dropping {} bytes worth of TLS records as max retries has been reached", m_context.tls_buffer.size());
|
||||
m_context.tls_buffer.clear();
|
||||
m_context.send_retries = 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TLSv12::close()
|
||||
{
|
||||
if (underlying_stream().is_open())
|
||||
alert(AlertLevel::FATAL, AlertDescription::CLOSE_NOTIFY);
|
||||
// bye bye.
|
||||
m_context.connection_status = ConnectionStatus::Disconnected;
|
||||
}
|
||||
|
||||
}
|
90
Userland/Libraries/LibTLS/TLSPacketBuilder.h
Normal file
90
Userland/Libraries/LibTLS/TLSPacketBuilder.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/ByteReader.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibTLS/Extensions.h>
|
||||
|
||||
namespace TLS {
|
||||
|
||||
class PacketBuilder {
|
||||
public:
|
||||
PacketBuilder(ContentType type, u16 version, size_t size_hint = 0xfdf)
|
||||
: PacketBuilder(type, (ProtocolVersion)version, size_hint)
|
||||
{
|
||||
}
|
||||
|
||||
PacketBuilder(ContentType type, ProtocolVersion version, size_t size_hint = 0xfdf)
|
||||
{
|
||||
// FIXME: Handle possible OOM situation.
|
||||
m_packet_data = ByteBuffer::create_uninitialized(size_hint + 16).release_value_but_fixme_should_propagate_errors();
|
||||
m_current_length = 5;
|
||||
m_packet_data[0] = (u8)type;
|
||||
ByteReader::store(m_packet_data.offset_pointer(1), AK::convert_between_host_and_network_endian((u16)version));
|
||||
}
|
||||
|
||||
inline void append(u16 value)
|
||||
{
|
||||
value = AK::convert_between_host_and_network_endian(value);
|
||||
append((u8 const*)&value, sizeof(value));
|
||||
}
|
||||
inline void append(u8 value)
|
||||
{
|
||||
append((u8 const*)&value, sizeof(value));
|
||||
}
|
||||
inline void append(ReadonlyBytes data)
|
||||
{
|
||||
append(data.data(), data.size());
|
||||
}
|
||||
inline void append_u24(u32 value)
|
||||
{
|
||||
u8 buf[3];
|
||||
buf[0] = value / 0x10000;
|
||||
value %= 0x10000;
|
||||
buf[1] = value / 0x100;
|
||||
value %= 0x100;
|
||||
buf[2] = value;
|
||||
|
||||
append(buf, 3);
|
||||
}
|
||||
inline void append(u8 const* data, size_t bytes)
|
||||
{
|
||||
if (bytes == 0)
|
||||
return;
|
||||
|
||||
auto old_length = m_current_length;
|
||||
m_current_length += bytes;
|
||||
|
||||
if (m_packet_data.size() < m_current_length) {
|
||||
m_packet_data.resize(m_current_length);
|
||||
}
|
||||
|
||||
m_packet_data.overwrite(old_length, data, bytes);
|
||||
}
|
||||
inline ByteBuffer build()
|
||||
{
|
||||
auto length = m_current_length;
|
||||
m_current_length = 0;
|
||||
// FIXME: Propagate errors.
|
||||
return MUST(m_packet_data.slice(0, length));
|
||||
}
|
||||
inline void set(size_t offset, u8 value)
|
||||
{
|
||||
VERIFY(offset < m_current_length);
|
||||
m_packet_data[offset] = value;
|
||||
}
|
||||
size_t length() const { return m_current_length; }
|
||||
|
||||
private:
|
||||
ByteBuffer m_packet_data;
|
||||
size_t m_current_length;
|
||||
};
|
||||
|
||||
}
|
|
@ -4,162 +4,622 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Base64.h>
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <LibCore/ConfigFile.h>
|
||||
#include <LibCore/DateTime.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibCrypto/ASN1/ASN1.h>
|
||||
#include <LibCrypto/ASN1/PEM.h>
|
||||
#include <LibCrypto/Curves/Ed25519.h>
|
||||
#include <LibCrypto/Curves/SECPxxxr1.h>
|
||||
#include <LibCrypto/PK/Code/EMSA_PKCS1_V1_5.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibTLS/Certificate.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
#include <errno.h>
|
||||
#include <wolfssl/options.h>
|
||||
#include <wolfssl/ssl.h>
|
||||
|
||||
#ifndef SOCK_NONBLOCK
|
||||
# include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
static Vector<ByteString> 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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ErrorOr<NonnullOwnPtr<WolfTLS>> WolfTLS::connect(const AK::ByteString& host, u16 port)
|
||||
{
|
||||
if (!g_initialized_wolfssl) {
|
||||
wolfSSL_Init();
|
||||
g_initialized_wolfssl = true;
|
||||
if (validity.not_after < now) {
|
||||
dbgln("certificate expired (expiry date {})", Core::DateTime::from_timestamp(validity.not_after.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");
|
||||
}
|
||||
|
||||
auto ssl = wolfSSL_new(context);
|
||||
if (!ssl)
|
||||
return Error::from_string_literal("Failed to create a new SSL object");
|
||||
|
||||
wolfSSL_SSLSetIOSend(ssl, [](WOLFSSL*, char* buf, int sz, void* ctx) -> int {
|
||||
auto& self = *static_cast<WolfTLS*>(ctx);
|
||||
auto result = self.m_underlying->write_some({ buf, static_cast<size_t>(sz) });
|
||||
if (result.is_error() && result.error().is_errno())
|
||||
return errno_to_wolfssl_error(result.error().code());
|
||||
if (result.is_error())
|
||||
return -1;
|
||||
return static_cast<int>(result.value());
|
||||
});
|
||||
wolfSSL_SSLSetIORecv(ssl, [](WOLFSSL*, char* buf, int sz, void* ctx) -> int {
|
||||
auto& self = *static_cast<WolfTLS*>(ctx);
|
||||
auto result = self.m_underlying->read_some({ buf, static_cast<size_t>(sz) });
|
||||
if (result.is_error() && result.error().is_errno())
|
||||
return errno_to_wolfssl_error(result.error().code());
|
||||
if (result.is_error())
|
||||
return -1;
|
||||
return static_cast<int>(result.value().size());
|
||||
});
|
||||
|
||||
auto tcp_socket = TRY(Core::TCPSocket::connect(host, port));
|
||||
|
||||
auto object = make<WolfTLS>(context, ssl, move(tcp_socket));
|
||||
wolfSSL_SetIOReadCtx(ssl, static_cast<void*>(object.ptr()));
|
||||
wolfSSL_SetIOWriteCtx(ssl, static_cast<void*>(object.ptr()));
|
||||
|
||||
if (wolfSSL_CTX_UseSNI(context, WOLFSSL_SNI_HOST_NAME, host.bytes().data(), host.bytes().size()) != WOLFSSL_SUCCESS)
|
||||
return Error::from_string_literal("Failed to set SNI hostname");
|
||||
|
||||
if (auto rc = wolfSSL_connect(ssl); rc != SSL_SUCCESS)
|
||||
return Error::from_string_view(error_text(ssl, rc));
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
WolfTLS::~WolfTLS()
|
||||
{
|
||||
close();
|
||||
wolfSSL_free(m_ssl);
|
||||
wolfSSL_CTX_free(m_context);
|
||||
}
|
||||
|
||||
ErrorOr<Bytes> WolfTLS::read_some(Bytes bytes)
|
||||
{
|
||||
auto result = wolfSSL_read(m_ssl, bytes.data(), bytes.size());
|
||||
if (result < 0)
|
||||
return Error::from_string_view(error_text(m_ssl, result));
|
||||
return Bytes { bytes.data(), static_cast<size_t>(result) };
|
||||
}
|
||||
|
||||
ErrorOr<size_t> WolfTLS::write_some(ReadonlyBytes bytes)
|
||||
{
|
||||
auto result = wolfSSL_write(m_ssl, bytes.data(), bytes.size());
|
||||
if (result < 0)
|
||||
return Error::from_string_view(error_text(m_ssl, result));
|
||||
return static_cast<size_t>(result);
|
||||
}
|
||||
|
||||
bool WolfTLS::is_eof() const
|
||||
{
|
||||
return m_underlying->is_eof() && wolfSSL_pending(m_ssl) == 0;
|
||||
}
|
||||
|
||||
bool WolfTLS::is_open() const
|
||||
{
|
||||
return m_underlying->is_open();
|
||||
}
|
||||
|
||||
void WolfTLS::close()
|
||||
{
|
||||
wolfSSL_shutdown(m_ssl);
|
||||
}
|
||||
|
||||
ErrorOr<size_t> WolfTLS::pending_bytes() const
|
||||
{
|
||||
return wolfSSL_pending(m_ssl);
|
||||
}
|
||||
|
||||
ErrorOr<bool> WolfTLS::can_read_without_blocking(int) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ErrorOr<void> WolfTLS::set_blocking(bool)
|
||||
// https://www.ietf.org/rfc/rfc5280.html#page-12
|
||||
bool Certificate::is_self_signed()
|
||||
{
|
||||
if (m_is_self_signed.has_value())
|
||||
return *m_is_self_signed;
|
||||
|
||||
// Self-signed certificates are self-issued certificates where the digital
|
||||
// signature may be verified by the public key bound into the certificate.
|
||||
if (!this->is_self_issued)
|
||||
m_is_self_signed.emplace(false);
|
||||
|
||||
// FIXME: Actually check if we sign ourself
|
||||
|
||||
m_is_self_signed.emplace(true);
|
||||
return *m_is_self_signed;
|
||||
}
|
||||
|
||||
void TLSv12::try_disambiguate_error() const
|
||||
{
|
||||
dbgln("Possible failure cause(s): ");
|
||||
switch ((AlertDescription)m_context.critical_error) {
|
||||
case AlertDescription::HANDSHAKE_FAILURE:
|
||||
if (!m_context.cipher_spec_set) {
|
||||
dbgln("- No cipher suite in common with {}", m_context.extensions.SNI);
|
||||
} else {
|
||||
dbgln("- Unknown internal issue");
|
||||
}
|
||||
break;
|
||||
case AlertDescription::INSUFFICIENT_SECURITY:
|
||||
dbgln("- No cipher suite in common with {} (the server is oh so secure)", m_context.extensions.SNI);
|
||||
break;
|
||||
case AlertDescription::PROTOCOL_VERSION:
|
||||
dbgln("- The server refused to negotiate with TLS 1.2 :(");
|
||||
break;
|
||||
case AlertDescription::UNEXPECTED_MESSAGE:
|
||||
dbgln("- We sent an invalid message for the state we're in.");
|
||||
break;
|
||||
case AlertDescription::BAD_RECORD_MAC:
|
||||
dbgln("- Bad MAC record from our side.");
|
||||
dbgln("- Ciphertext wasn't an even multiple of the block length.");
|
||||
dbgln("- Bad block cipher padding.");
|
||||
dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
|
||||
break;
|
||||
case AlertDescription::RECORD_OVERFLOW:
|
||||
dbgln("- Sent a ciphertext record which has a length bigger than 18432 bytes.");
|
||||
dbgln("- Sent record decrypted to a compressed record that has a length bigger than 18432 bytes.");
|
||||
dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
|
||||
break;
|
||||
case AlertDescription::DECOMPRESSION_FAILURE_RESERVED:
|
||||
dbgln("- We sent invalid input for decompression (e.g. data that would expand to excessive length)");
|
||||
break;
|
||||
case AlertDescription::ILLEGAL_PARAMETER:
|
||||
dbgln("- We sent a parameter in the handshake that is out of range or inconsistent with the other parameters.");
|
||||
break;
|
||||
case AlertDescription::DECODE_ERROR:
|
||||
dbgln("- The message we sent cannot be decoded because a field was out of range or the length was incorrect.");
|
||||
dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
|
||||
break;
|
||||
case AlertDescription::DECRYPT_ERROR:
|
||||
dbgln("- A handshake crypto operation failed. This includes signature verification and validating Finished.");
|
||||
break;
|
||||
case AlertDescription::ACCESS_DENIED:
|
||||
dbgln("- The certificate is valid, but once access control was applied, the sender decided to stop negotiation.");
|
||||
break;
|
||||
case AlertDescription::INTERNAL_ERROR:
|
||||
dbgln("- No one knows, but it isn't a protocol failure.");
|
||||
break;
|
||||
case AlertDescription::DECRYPTION_FAILED_RESERVED:
|
||||
case AlertDescription::NO_CERTIFICATE_RESERVED:
|
||||
case AlertDescription::EXPORT_RESTRICTION_RESERVED:
|
||||
dbgln("- No one knows, the server sent a non-compliant alert.");
|
||||
break;
|
||||
default:
|
||||
dbgln("- No one knows.");
|
||||
break;
|
||||
}
|
||||
|
||||
dbgln("- {}", enum_to_value((AlertDescription)m_context.critical_error));
|
||||
}
|
||||
|
||||
void TLSv12::set_root_certificates(Vector<Certificate> certificates)
|
||||
{
|
||||
if (!m_context.root_certificates.is_empty()) {
|
||||
dbgln("TLS warn: resetting root certificates!");
|
||||
m_context.root_certificates.clear();
|
||||
}
|
||||
|
||||
for (auto& cert : certificates) {
|
||||
if (!cert.is_valid()) {
|
||||
dbgln("Certificate for {} is invalid, things may or may not work!", cert.subject.to_string());
|
||||
}
|
||||
// FIXME: Figure out what we should do when our root certs are invalid.
|
||||
|
||||
m_context.root_certificates.set(MUST(cert.subject.to_string()).to_byte_string(), cert);
|
||||
}
|
||||
dbgln_if(TLS_DEBUG, "{}: Set {} root certificates", this, m_context.root_certificates.size());
|
||||
}
|
||||
|
||||
static bool wildcard_matches(StringView host, StringView subject)
|
||||
{
|
||||
if (host == subject)
|
||||
return true;
|
||||
|
||||
if (subject.starts_with("*."sv)) {
|
||||
auto maybe_first_dot_index = host.find('.');
|
||||
if (maybe_first_dot_index.has_value()) {
|
||||
auto first_dot_index = maybe_first_dot_index.release_value();
|
||||
return wildcard_matches(host.substring_view(first_dot_index + 1), subject.substring_view(2));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool certificate_subject_matches_host(Certificate const& cert, StringView host)
|
||||
{
|
||||
if (wildcard_matches(host, cert.subject.common_name()))
|
||||
return true;
|
||||
|
||||
for (auto& san : cert.SAN) {
|
||||
if (wildcard_matches(host, san))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Context::verify_chain(StringView host) const
|
||||
{
|
||||
if (!options.validate_certificates)
|
||||
return true;
|
||||
|
||||
Vector<Certificate> const* local_chain = nullptr;
|
||||
if (is_server) {
|
||||
dbgln("Unsupported: Server mode");
|
||||
TODO();
|
||||
} else {
|
||||
local_chain = &certificates;
|
||||
}
|
||||
|
||||
if (local_chain->is_empty()) {
|
||||
dbgln("verify_chain: Attempting to verify an empty chain");
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC5246 section 7.4.2: The sender's certificate MUST come first in the list. Each following certificate
|
||||
// MUST directly certify the one preceding it. Because certificate validation requires that root keys be
|
||||
// distributed independently, the self-signed certificate that specifies the root certificate authority MAY be
|
||||
// omitted from the chain, under the assumption that the remote end must already possess it in order to validate
|
||||
// it in any case.
|
||||
|
||||
if (!host.is_empty()) {
|
||||
auto const& first_certificate = local_chain->first();
|
||||
auto subject_matches = certificate_subject_matches_host(first_certificate, host);
|
||||
if (!subject_matches) {
|
||||
dbgln("verify_chain: First certificate does not match the hostname");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// FIXME: The host is taken from m_context.extensions.SNI, when is this empty?
|
||||
dbgln("FIXME: verify_chain called without host");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t cert_index = 0; cert_index < local_chain->size(); ++cert_index) {
|
||||
auto const& cert = local_chain->at(cert_index);
|
||||
|
||||
auto subject_string = MUST(cert.subject.to_string());
|
||||
auto issuer_string = MUST(cert.issuer.to_string());
|
||||
|
||||
if (!cert.is_valid()) {
|
||||
dbgln("verify_chain: Certificate is not valid {}", subject_string);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto maybe_root_certificate = root_certificates.get(issuer_string.to_byte_string());
|
||||
if (maybe_root_certificate.has_value()) {
|
||||
auto& root_certificate = *maybe_root_certificate;
|
||||
auto verification_correct = verify_certificate_pair(cert, root_certificate);
|
||||
|
||||
if (!verification_correct) {
|
||||
dbgln("verify_chain: Signature inconsistent, {} was not signed by {} (root certificate)", subject_string, issuer_string);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Root certificate reached, and correctly verified, so we can stop now
|
||||
return true;
|
||||
}
|
||||
|
||||
if (subject_string == issuer_string) {
|
||||
dbgln("verify_chain: Non-root self-signed certificate");
|
||||
return options.allow_self_signed_certificates;
|
||||
}
|
||||
if ((cert_index + 1) >= local_chain->size()) {
|
||||
dbgln("verify_chain: No trusted root certificate found before end of certificate chain");
|
||||
dbgln("verify_chain: Last certificate in chain was signed by {}", issuer_string);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const& parent_certificate = local_chain->at(cert_index + 1);
|
||||
if (issuer_string != MUST(parent_certificate.subject.to_string())) {
|
||||
dbgln("verify_chain: Next certificate in the chain is not the issuer of this certificate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(parent_certificate.is_allowed_to_sign_certificate && parent_certificate.is_certificate_authority)) {
|
||||
dbgln("verify_chain: {} is not marked as certificate authority", issuer_string);
|
||||
return false;
|
||||
}
|
||||
if (parent_certificate.path_length_constraint.has_value() && cert_index > parent_certificate.path_length_constraint.value()) {
|
||||
dbgln("verify_chain: Path length for certificate exceeded");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool verification_correct = verify_certificate_pair(cert, parent_certificate);
|
||||
if (!verification_correct) {
|
||||
dbgln("verify_chain: Signature inconsistent, {} was not signed by {}", subject_string, issuer_string);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Either a root certificate is reached, or parent validation fails as the end of the local chain is reached
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
bool Context::verify_certificate_pair(Certificate const& subject, Certificate const& issuer) const
|
||||
{
|
||||
Crypto::Hash::HashKind kind = Crypto::Hash::HashKind::Unknown;
|
||||
auto identifier = subject.signature_algorithm.identifier;
|
||||
|
||||
bool is_rsa = true;
|
||||
|
||||
if (identifier == rsa_encryption_oid) {
|
||||
kind = Crypto::Hash::HashKind::None;
|
||||
} else if (identifier == rsa_md5_encryption_oid) {
|
||||
kind = Crypto::Hash::HashKind::MD5;
|
||||
} else if (identifier == rsa_sha1_encryption_oid) {
|
||||
kind = Crypto::Hash::HashKind::SHA1;
|
||||
} else if (identifier == rsa_sha256_encryption_oid) {
|
||||
kind = Crypto::Hash::HashKind::SHA256;
|
||||
} else if (identifier == rsa_sha384_encryption_oid) {
|
||||
kind = Crypto::Hash::HashKind::SHA384;
|
||||
} else if (identifier == rsa_sha512_encryption_oid) {
|
||||
kind = Crypto::Hash::HashKind::SHA512;
|
||||
} else if (identifier == ecdsa_with_sha256_encryption_oid) {
|
||||
kind = Crypto::Hash::HashKind::SHA256;
|
||||
is_rsa = false;
|
||||
} else if (identifier == ecdsa_with_sha384_encryption_oid) {
|
||||
kind = Crypto::Hash::HashKind::SHA384;
|
||||
is_rsa = false;
|
||||
} else if (identifier == ecdsa_with_sha512_encryption_oid) {
|
||||
kind = Crypto::Hash::HashKind::SHA512;
|
||||
is_rsa = false;
|
||||
}
|
||||
|
||||
if (kind == Crypto::Hash::HashKind::Unknown) {
|
||||
dbgln("verify_certificate_pair: Unknown signature algorithm, expected RSA or ECDSA with SHA1/256/384/512, got OID {}", identifier);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_rsa) {
|
||||
Crypto::PK::RSAPrivateKey dummy_private_key;
|
||||
Crypto::PK::RSAPublicKey public_key_copy { issuer.public_key.rsa };
|
||||
auto rsa = Crypto::PK::RSA(public_key_copy, dummy_private_key);
|
||||
auto verification_buffer_result = ByteBuffer::create_uninitialized(subject.signature_value.size());
|
||||
if (verification_buffer_result.is_error()) {
|
||||
dbgln("verify_certificate_pair: Unable to allocate buffer for verification");
|
||||
return false;
|
||||
}
|
||||
auto verification_buffer = verification_buffer_result.release_value();
|
||||
auto verification_buffer_bytes = verification_buffer.bytes();
|
||||
rsa.verify(subject.signature_value, verification_buffer_bytes);
|
||||
|
||||
ReadonlyBytes message = subject.tbs_asn1.bytes();
|
||||
auto pkcs1 = Crypto::PK::EMSA_PKCS1_V1_5<Crypto::Hash::Manager>(kind);
|
||||
auto verification = pkcs1.verify(message, verification_buffer_bytes, subject.signature_value.size() * 8);
|
||||
return verification == Crypto::VerificationConsistency::Consistent;
|
||||
}
|
||||
|
||||
// ECDSA hash verification: hash, then check signature against the specific curve
|
||||
switch (issuer.public_key.algorithm.ec_parameters) {
|
||||
case SupportedGroup::SECP256R1: {
|
||||
Crypto::Hash::Manager hasher(kind);
|
||||
hasher.update(subject.tbs_asn1.bytes());
|
||||
auto hash = hasher.digest();
|
||||
|
||||
Crypto::Curves::SECP256r1 curve;
|
||||
auto result = curve.verify(hash.bytes(), issuer.public_key.raw_key, subject.signature_value);
|
||||
if (result.is_error()) {
|
||||
dbgln("verify_certificate_pair: Failed to check SECP256r1 signature {}", result.release_error());
|
||||
return false;
|
||||
}
|
||||
return result.value();
|
||||
}
|
||||
case SupportedGroup::SECP384R1: {
|
||||
Crypto::Hash::Manager hasher(kind);
|
||||
hasher.update(subject.tbs_asn1.bytes());
|
||||
auto hash = hasher.digest();
|
||||
|
||||
Crypto::Curves::SECP384r1 curve;
|
||||
auto result = curve.verify(hash.bytes(), issuer.public_key.raw_key, subject.signature_value);
|
||||
if (result.is_error()) {
|
||||
dbgln("verify_certificate_pair: Failed to check SECP384r1 signature {}", result.release_error());
|
||||
return false;
|
||||
}
|
||||
return result.value();
|
||||
}
|
||||
case SupportedGroup::X25519: {
|
||||
Crypto::Curves::Ed25519 curve;
|
||||
auto result = curve.verify(issuer.public_key.raw_key, subject.signature_value, subject.tbs_asn1.bytes());
|
||||
if (!result) {
|
||||
dbgln("verify_certificate_pair: Failed to check Ed25519 signature");
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
default:
|
||||
dbgln("verify_certificate_pair: Don't know how to verify signature for curve {}", to_underlying(issuer.public_key.algorithm.ec_parameters));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename HMACType>
|
||||
static void hmac_pseudorandom_function(Bytes output, ReadonlyBytes secret, u8 const* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b)
|
||||
{
|
||||
if (!secret.size()) {
|
||||
dbgln("null secret");
|
||||
return;
|
||||
}
|
||||
|
||||
auto append_label_seed = [&](auto& hmac) {
|
||||
hmac.update(label, label_length);
|
||||
hmac.update(seed);
|
||||
if (seed_b.size() > 0)
|
||||
hmac.update(seed_b);
|
||||
};
|
||||
|
||||
HMACType hmac(secret);
|
||||
append_label_seed(hmac);
|
||||
|
||||
constexpr auto digest_size = hmac.digest_size();
|
||||
u8 digest[digest_size];
|
||||
auto digest_0 = Bytes { digest, digest_size };
|
||||
|
||||
digest_0.overwrite(0, hmac.digest().immutable_data(), digest_size);
|
||||
|
||||
size_t index = 0;
|
||||
while (index < output.size()) {
|
||||
hmac.update(digest_0);
|
||||
append_label_seed(hmac);
|
||||
auto digest_1 = hmac.digest();
|
||||
|
||||
auto copy_size = min(digest_size, output.size() - index);
|
||||
|
||||
output.overwrite(index, digest_1.immutable_data(), copy_size);
|
||||
index += copy_size;
|
||||
|
||||
digest_0.overwrite(0, hmac.process(digest_0).immutable_data(), digest_size);
|
||||
}
|
||||
}
|
||||
|
||||
void TLSv12::pseudorandom_function(Bytes output, ReadonlyBytes secret, u8 const* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b)
|
||||
{
|
||||
// Simplification: We only support the HMAC PRF with the hash function SHA-256 or stronger.
|
||||
|
||||
// RFC 5246: "In this section, we define one PRF, based on HMAC. This PRF with the
|
||||
// SHA-256 hash function is used for all cipher suites defined in this
|
||||
// document and in TLS documents published prior to this document when
|
||||
// TLS 1.2 is negotiated. New cipher suites MUST explicitly specify a
|
||||
// PRF and, in general, SHOULD use the TLS PRF with SHA-256 or a
|
||||
// stronger standard hash function."
|
||||
|
||||
switch (hmac_hash()) {
|
||||
case Crypto::Hash::HashKind::SHA512:
|
||||
hmac_pseudorandom_function<Crypto::Authentication::HMAC<Crypto::Hash::SHA512>>(output, secret, label, label_length, seed, seed_b);
|
||||
break;
|
||||
case Crypto::Hash::HashKind::SHA384:
|
||||
hmac_pseudorandom_function<Crypto::Authentication::HMAC<Crypto::Hash::SHA384>>(output, secret, label, label_length, seed, seed_b);
|
||||
break;
|
||||
case Crypto::Hash::HashKind::SHA256:
|
||||
hmac_pseudorandom_function<Crypto::Authentication::HMAC<Crypto::Hash::SHA256>>(output, secret, label, label_length, seed, seed_b);
|
||||
break;
|
||||
default:
|
||||
dbgln("Failed to find a suitable HMAC hash");
|
||||
VERIFY_NOT_REACHED();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TLSv12::TLSv12(StreamVariantType stream, Options options)
|
||||
: m_stream(move(stream))
|
||||
{
|
||||
m_context.options = move(options);
|
||||
m_context.is_server = false;
|
||||
m_context.tls_buffer = {};
|
||||
|
||||
set_root_certificates(m_context.options.root_certificates.has_value()
|
||||
? *m_context.options.root_certificates
|
||||
: DefaultRootCACertificates::the().certificates());
|
||||
|
||||
setup_connection();
|
||||
}
|
||||
|
||||
Vector<Certificate> TLSv12::parse_pem_certificate(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes rsa_key) // FIXME: This should not be bound to RSA
|
||||
{
|
||||
if (certificate_pem_buffer.is_empty() || rsa_key.is_empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
void WolfTLS::install_certificate_store_paths(Vector<AK::ByteString> paths)
|
||||
{
|
||||
s_certificate_store_paths = move(paths);
|
||||
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<ByteString> s_default_ca_certificate_paths;
|
||||
|
||||
void DefaultRootCACertificates::set_default_certificate_paths(Span<ByteString> paths)
|
||||
{
|
||||
s_default_ca_certificate_paths.clear();
|
||||
s_default_ca_certificate_paths.ensure_capacity(paths.size());
|
||||
for (auto& path : paths)
|
||||
s_default_ca_certificate_paths.unchecked_append(path);
|
||||
}
|
||||
|
||||
DefaultRootCACertificates::DefaultRootCACertificates()
|
||||
{
|
||||
auto load_result = load_certificates(s_default_ca_certificate_paths);
|
||||
if (load_result.is_error()) {
|
||||
dbgln("Failed to load CA Certificates: {}", load_result.error());
|
||||
return;
|
||||
}
|
||||
|
||||
m_ca_certificates = load_result.release_value();
|
||||
}
|
||||
|
||||
DefaultRootCACertificates& DefaultRootCACertificates::the()
|
||||
{
|
||||
static thread_local DefaultRootCACertificates s_the;
|
||||
return s_the;
|
||||
}
|
||||
|
||||
ErrorOr<Vector<Certificate>> DefaultRootCACertificates::load_certificates(Span<ByteString> custom_cert_paths)
|
||||
{
|
||||
auto cacert_file_or_error = Core::File::open("/etc/cacert.pem"sv, Core::File::OpenMode::Read);
|
||||
ByteBuffer data;
|
||||
if (!cacert_file_or_error.is_error())
|
||||
data = TRY(cacert_file_or_error.value()->read_until_eof());
|
||||
|
||||
auto user_cert_path = TRY(String::formatted("{}/.config/certs.pem", Core::StandardPaths::home_directory()));
|
||||
if (FileSystem::exists(user_cert_path)) {
|
||||
auto user_cert_file = TRY(Core::File::open(user_cert_path, Core::File::OpenMode::Read));
|
||||
TRY(data.try_append(TRY(user_cert_file->read_until_eof())));
|
||||
}
|
||||
|
||||
for (auto& custom_cert_path : custom_cert_paths) {
|
||||
if (FileSystem::exists(custom_cert_path)) {
|
||||
auto custom_cert_file = TRY(Core::File::open(custom_cert_path, Core::File::OpenMode::Read));
|
||||
TRY(data.try_append(TRY(custom_cert_file->read_until_eof())));
|
||||
}
|
||||
}
|
||||
|
||||
return TRY(parse_pem_root_certificate_authorities(data));
|
||||
}
|
||||
|
||||
ErrorOr<Vector<Certificate>> DefaultRootCACertificates::parse_pem_root_certificate_authorities(ByteBuffer& data)
|
||||
{
|
||||
Vector<Certificate> certificates;
|
||||
|
||||
auto certs = TRY(Crypto::decode_pems(data));
|
||||
|
||||
for (auto& cert : certs) {
|
||||
auto certificate_result = Certificate::parse_certificate(cert.bytes());
|
||||
if (certificate_result.is_error()) {
|
||||
// FIXME: It would be nice to have more informations about the certificate we failed to parse.
|
||||
// Like: Issuer, Algorithm, CN, etc
|
||||
dbgln("Failed to load certificate: {}", certificate_result.error());
|
||||
continue;
|
||||
}
|
||||
auto certificate = certificate_result.release_value();
|
||||
if (certificate.is_certificate_authority && certificate.is_self_signed()) {
|
||||
TRY(certificates.try_append(move(certificate)));
|
||||
} else {
|
||||
dbgln("Skipped '{}' because it is not a valid root CA", TRY(certificate.subject.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
dbgln_if(TLS_DEBUG, "Loaded {} of {} ({:.2}%) provided CA Certificates", certificates.size(), certs.size(), (certificates.size() * 100.0) / certs.size());
|
||||
|
||||
return certificates;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,48 +6,546 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Certificate.h"
|
||||
#include <AK/IPv4Address.h>
|
||||
#include <AK/Queue.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibCore/Notifier.h>
|
||||
#include <LibCore/Socket.h>
|
||||
#include <LibTLS/Certificate.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibCrypto/Authentication/HMAC.h>
|
||||
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
||||
#include <LibCrypto/Cipher/AES.h>
|
||||
#include <LibCrypto/Curves/EllipticCurve.h>
|
||||
#include <LibCrypto/Hash/HashManager.h>
|
||||
#include <LibCrypto/PK/RSA.h>
|
||||
#include <LibTLS/CipherSuite.h>
|
||||
#include <LibTLS/TLSPacketBuilder.h>
|
||||
|
||||
struct WOLFSSL_CTX;
|
||||
struct WOLFSSL;
|
||||
namespace TLS {
|
||||
|
||||
class WolfTLS final : public Core::Socket {
|
||||
public:
|
||||
WolfTLS(WOLFSSL_CTX* context, WOLFSSL* ssl, NonnullOwnPtr<Core::TCPSocket> underlying)
|
||||
: m_context(context)
|
||||
, m_ssl(ssl)
|
||||
, m_underlying(move(underlying))
|
||||
inline void print_buffer(ReadonlyBytes buffer)
|
||||
{
|
||||
m_underlying->on_ready_to_read = [this] {
|
||||
if (on_ready_to_read)
|
||||
on_ready_to_read();
|
||||
};
|
||||
dbgln("{:hex-dump}", buffer);
|
||||
}
|
||||
|
||||
~WolfTLS();
|
||||
inline void print_buffer(ByteBuffer const& buffer)
|
||||
{
|
||||
print_buffer(buffer.bytes());
|
||||
}
|
||||
|
||||
static void install_certificate_store_paths(Vector<ByteString>);
|
||||
inline void print_buffer(u8 const* buffer, size_t size)
|
||||
{
|
||||
print_buffer(ReadonlyBytes { buffer, size });
|
||||
}
|
||||
|
||||
static ErrorOr<NonnullOwnPtr<WolfTLS>> connect(ByteString const& host, u16 port);
|
||||
ErrorOr<Bytes> read_some(Bytes bytes) override;
|
||||
ErrorOr<size_t> write_some(ReadonlyBytes bytes) override;
|
||||
bool is_eof() const override;
|
||||
bool is_open() const override;
|
||||
void close() override;
|
||||
ErrorOr<size_t> pending_bytes() const override;
|
||||
ErrorOr<bool> can_read_without_blocking(int timeout) const override;
|
||||
ErrorOr<void> set_blocking(bool enabled) override;
|
||||
ErrorOr<void> 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); }
|
||||
class Socket;
|
||||
|
||||
enum class Error : i8 {
|
||||
NoError = 0,
|
||||
UnknownError = -1,
|
||||
BrokenPacket = -2,
|
||||
NotUnderstood = -3,
|
||||
NoCommonCipher = -5,
|
||||
UnexpectedMessage = -6,
|
||||
CloseConnection = -7,
|
||||
CompressionNotSupported = -8,
|
||||
NotVerified = -9,
|
||||
NotSafe = -10,
|
||||
IntegrityCheckFailed = -11,
|
||||
ErrorAlert = -12,
|
||||
BrokenConnection = -13,
|
||||
BadCertificate = -14,
|
||||
UnsupportedCertificate = -15,
|
||||
NoRenegotiation = -16,
|
||||
FeatureNotSupported = -17,
|
||||
DecryptionFailed = -20,
|
||||
NeedMoreData = -21,
|
||||
TimedOut = -22,
|
||||
OutOfMemory = -23,
|
||||
};
|
||||
|
||||
enum class WritePacketStage {
|
||||
Initial = 0,
|
||||
ClientHandshake = 1,
|
||||
ServerHandshake = 2,
|
||||
Finished = 3,
|
||||
};
|
||||
|
||||
enum class ConnectionStatus {
|
||||
Disconnected,
|
||||
Negotiating,
|
||||
KeyExchange,
|
||||
Renegotiating,
|
||||
Established,
|
||||
};
|
||||
|
||||
enum ClientVerificationStaus {
|
||||
Verified,
|
||||
VerificationNeeded,
|
||||
};
|
||||
|
||||
// Note for the 16 iv length instead of 8:
|
||||
// 4 bytes of fixed IV, 8 random (nonce) bytes, 4 bytes for counter
|
||||
// GCM specifically asks us to transmit only the nonce, the counter is zero
|
||||
// and the fixed IV is derived from the premaster key.
|
||||
//
|
||||
// The cipher suite list below is ordered based on the recommendations from Mozilla.
|
||||
// When changing the supported cipher suites, please consult the webpage below for
|
||||
// the preferred order.
|
||||
//
|
||||
// https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
#define ENUMERATE_CIPHERS(C) \
|
||||
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||
C(true, CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||
C(true, CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||
C(true, CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||
C(true, CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||
C(true, CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA256, 16, false) \
|
||||
C(true, CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA256, 16, false) \
|
||||
C(true, CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||
C(true, CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false)
|
||||
|
||||
constexpr KeyExchangeAlgorithm get_key_exchange_algorithm(CipherSuite suite)
|
||||
{
|
||||
switch (suite) {
|
||||
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||
case suite: \
|
||||
return key_exchange;
|
||||
ENUMERATE_CIPHERS(C)
|
||||
#undef C
|
||||
default:
|
||||
return KeyExchangeAlgorithm::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr CipherAlgorithm get_cipher_algorithm(CipherSuite suite)
|
||||
{
|
||||
switch (suite) {
|
||||
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||
case suite: \
|
||||
return cipher;
|
||||
ENUMERATE_CIPHERS(C)
|
||||
#undef C
|
||||
default:
|
||||
return CipherAlgorithm::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
struct Options {
|
||||
static Vector<CipherSuite> default_usable_cipher_suites()
|
||||
{
|
||||
Vector<CipherSuite> 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<CipherSuite> usable_cipher_suites = default_usable_cipher_suites();
|
||||
|
||||
#define OPTION_WITH_DEFAULTS(typ, name, ...) \
|
||||
static typ default_##name() \
|
||||
{ \
|
||||
return typ { __VA_ARGS__ }; \
|
||||
} \
|
||||
typ name = default_##name(); \
|
||||
Options& set_##name(typ new_value)& \
|
||||
{ \
|
||||
name = move(new_value); \
|
||||
return *this; \
|
||||
} \
|
||||
Options&& set_##name(typ new_value)&& \
|
||||
{ \
|
||||
name = move(new_value); \
|
||||
return move(*this); \
|
||||
}
|
||||
|
||||
OPTION_WITH_DEFAULTS(ProtocolVersion, version, ProtocolVersion::VERSION_1_2)
|
||||
OPTION_WITH_DEFAULTS(Vector<SignatureAndHashAlgorithm>, supported_signature_algorithms,
|
||||
{ HashAlgorithm::SHA512, SignatureAlgorithm::RSA },
|
||||
{ HashAlgorithm::SHA384, SignatureAlgorithm::RSA },
|
||||
{ HashAlgorithm::SHA256, SignatureAlgorithm::RSA },
|
||||
{ HashAlgorithm::SHA1, SignatureAlgorithm::RSA },
|
||||
{ HashAlgorithm::SHA256, SignatureAlgorithm::ECDSA },
|
||||
{ HashAlgorithm::SHA384, SignatureAlgorithm::ECDSA },
|
||||
{ HashAlgorithm::INTRINSIC, SignatureAlgorithm::ED25519 });
|
||||
OPTION_WITH_DEFAULTS(Vector<SupportedGroup>, elliptic_curves,
|
||||
SupportedGroup::X25519,
|
||||
SupportedGroup::SECP256R1,
|
||||
SupportedGroup::SECP384R1,
|
||||
SupportedGroup::X448)
|
||||
OPTION_WITH_DEFAULTS(Vector<ECPointFormat>, supported_ec_point_formats, ECPointFormat::UNCOMPRESSED)
|
||||
|
||||
OPTION_WITH_DEFAULTS(bool, use_sni, true)
|
||||
OPTION_WITH_DEFAULTS(bool, use_compression, false)
|
||||
OPTION_WITH_DEFAULTS(bool, validate_certificates, true)
|
||||
OPTION_WITH_DEFAULTS(bool, allow_self_signed_certificates, false)
|
||||
OPTION_WITH_DEFAULTS(Optional<Vector<Certificate>>, root_certificates, )
|
||||
OPTION_WITH_DEFAULTS(Function<void(AlertDescription)>, alert_handler, [](auto) {})
|
||||
OPTION_WITH_DEFAULTS(Function<void()>, finish_callback, [] {})
|
||||
OPTION_WITH_DEFAULTS(Function<Vector<Certificate>()>, certificate_provider, [] { return Vector<Certificate> {}; })
|
||||
OPTION_WITH_DEFAULTS(bool, enable_extended_master_secret, true)
|
||||
|
||||
#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<void> try_append(ReadonlyBytes data)
|
||||
{
|
||||
if (Checked<size_t>::addition_would_overflow(m_size, data.size()))
|
||||
return AK::Error::from_errno(EOVERFLOW);
|
||||
|
||||
m_size += data.size();
|
||||
m_buffers.enqueue(TRY(ByteBuffer::copy(data)));
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
static StringView error_text(WOLFSSL*, int error_code);
|
||||
|
||||
WOLFSSL_CTX* m_context { nullptr };
|
||||
WOLFSSL* m_ssl { nullptr };
|
||||
NonnullOwnPtr<Core::TCPSocket> m_underlying;
|
||||
size_t m_size { 0 };
|
||||
Queue<ByteBuffer> 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<Certificate> certificates;
|
||||
Certificate private_key;
|
||||
Vector<Certificate> client_certificates;
|
||||
ByteBuffer master_key;
|
||||
ByteBuffer premaster_key;
|
||||
u8 cipher_spec_set { 0 };
|
||||
struct {
|
||||
int created { 0 };
|
||||
u8 remote_mac[32];
|
||||
u8 local_mac[32];
|
||||
u8 local_iv[16];
|
||||
u8 remote_iv[16];
|
||||
u8 local_aead_iv[4];
|
||||
u8 remote_aead_iv[4];
|
||||
} crypto;
|
||||
|
||||
Crypto::Hash::Manager handshake_hash;
|
||||
|
||||
ByteBuffer message_buffer;
|
||||
u64 remote_sequence_number { 0 };
|
||||
u64 local_sequence_number { 0 };
|
||||
|
||||
ConnectionStatus connection_status { ConnectionStatus::Disconnected };
|
||||
bool should_expect_successful_read { false };
|
||||
u8 critical_error { 0 };
|
||||
Error error_code { Error::NoError };
|
||||
|
||||
ByteBuffer tls_buffer;
|
||||
|
||||
SegmentedBuffer application_buffer;
|
||||
|
||||
bool is_child { false };
|
||||
|
||||
struct {
|
||||
// Server Name Indicator
|
||||
ByteString SNI; // I hate your existence
|
||||
bool extended_master_secret { false };
|
||||
} extensions;
|
||||
|
||||
u8 request_client_certificate { 0 };
|
||||
|
||||
ByteBuffer cached_handshake;
|
||||
|
||||
ClientVerificationStaus client_verified { Verified };
|
||||
|
||||
bool connection_finished { false };
|
||||
bool close_notify { false };
|
||||
bool has_invoked_finish_or_error_callback { false };
|
||||
|
||||
// message flags
|
||||
u8 handshake_messages[11] { 0 };
|
||||
ByteBuffer user_data;
|
||||
HashMap<ByteString, Certificate> root_certificates;
|
||||
|
||||
Vector<ByteString> alpn;
|
||||
StringView negotiated_alpn;
|
||||
|
||||
size_t send_retries { 0 };
|
||||
|
||||
time_t handshake_initiation_timestamp { 0 };
|
||||
|
||||
struct {
|
||||
ByteBuffer p;
|
||||
ByteBuffer g;
|
||||
ByteBuffer Ys;
|
||||
} server_diffie_hellman_params;
|
||||
|
||||
OwnPtr<Crypto::Curves::EllipticCurve> server_key_exchange_curve;
|
||||
};
|
||||
|
||||
class TLSv12 final : public Core::Socket {
|
||||
private:
|
||||
Core::Socket& underlying_stream()
|
||||
{
|
||||
return *m_stream.visit([&](auto& stream) -> Core::Socket* { return stream; });
|
||||
}
|
||||
Core::Socket const& underlying_stream() const
|
||||
{
|
||||
return *m_stream.visit([&](auto& stream) -> Core::Socket const* { return stream; });
|
||||
}
|
||||
|
||||
public:
|
||||
/// Reads into a buffer, with the maximum size being the size of the buffer.
|
||||
/// The amount of bytes read can be smaller than the size of the buffer.
|
||||
/// Returns either the bytes that were read, or an errno in the case of
|
||||
/// failure.
|
||||
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||
|
||||
/// Tries to write the entire contents of the buffer. It is possible for
|
||||
/// less than the full buffer to be written. Returns either the amount of
|
||||
/// bytes written into the stream, or an errno in the case of failure.
|
||||
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||
|
||||
virtual bool is_eof() const override { return m_context.application_buffer.is_empty() && (m_context.connection_finished || underlying_stream().is_eof()); }
|
||||
|
||||
virtual bool is_open() const override { return is_established(); }
|
||||
virtual void close() override;
|
||||
|
||||
virtual ErrorOr<size_t> pending_bytes() const override { return m_context.application_buffer.size(); }
|
||||
virtual ErrorOr<bool> can_read_without_blocking(int = 0) const override { return !m_context.application_buffer.is_empty(); }
|
||||
virtual ErrorOr<void> set_blocking(bool block) override
|
||||
{
|
||||
VERIFY(!block);
|
||||
return {};
|
||||
}
|
||||
virtual ErrorOr<void> set_close_on_exec(bool enabled) override { return underlying_stream().set_close_on_exec(enabled); }
|
||||
|
||||
virtual void set_notifications_enabled(bool enabled) override { underlying_stream().set_notifications_enabled(enabled); }
|
||||
|
||||
static ErrorOr<NonnullOwnPtr<TLSv12>> connect(ByteString const& host, u16 port, Options = {});
|
||||
static ErrorOr<NonnullOwnPtr<TLSv12>> connect(ByteString const& host, Core::Socket& underlying_stream, Options = {});
|
||||
|
||||
using StreamVariantType = Variant<OwnPtr<Core::Socket>, Core::Socket*>;
|
||||
explicit TLSv12(StreamVariantType, Options);
|
||||
|
||||
bool is_established() const { return m_context.connection_status == ConnectionStatus::Established; }
|
||||
|
||||
void set_sni(StringView sni)
|
||||
{
|
||||
if (m_context.is_server || m_context.critical_error || m_context.connection_status != ConnectionStatus::Disconnected) {
|
||||
dbgln("invalid state for set_sni");
|
||||
return;
|
||||
}
|
||||
m_context.extensions.SNI = sni;
|
||||
}
|
||||
|
||||
void set_root_certificates(Vector<Certificate>);
|
||||
|
||||
static Vector<Certificate> parse_pem_certificate(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes key_pem_buffer);
|
||||
|
||||
StringView alpn() const { return m_context.negotiated_alpn; }
|
||||
|
||||
bool supports_cipher(CipherSuite suite) const
|
||||
{
|
||||
switch (suite) {
|
||||
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||
case suite: \
|
||||
return is_supported;
|
||||
ENUMERATE_CIPHERS(C)
|
||||
#undef C
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool supports_version(ProtocolVersion v) const
|
||||
{
|
||||
return v == ProtocolVersion::VERSION_1_2;
|
||||
}
|
||||
|
||||
void alert(AlertLevel, AlertDescription);
|
||||
|
||||
Function<void(AlertDescription)> on_tls_error;
|
||||
Function<void()> on_tls_finished;
|
||||
Function<void(TLSv12&)> on_tls_certificate_request;
|
||||
Function<void()> on_connected;
|
||||
|
||||
private:
|
||||
void setup_connection();
|
||||
|
||||
void consume(ReadonlyBytes record);
|
||||
|
||||
ByteBuffer hmac_message(ReadonlyBytes buf, Optional<ReadonlyBytes> const buf2, size_t mac_length, bool local = false);
|
||||
void ensure_hmac(size_t digest_size, bool local);
|
||||
|
||||
void update_packet(ByteBuffer& packet);
|
||||
void update_hash(ReadonlyBytes in, size_t header_size);
|
||||
|
||||
void write_packet(ByteBuffer& packet, bool immediately = false);
|
||||
|
||||
ByteBuffer build_client_key_exchange();
|
||||
ByteBuffer build_server_key_exchange();
|
||||
|
||||
ByteBuffer build_hello();
|
||||
ByteBuffer build_handshake_finished();
|
||||
ByteBuffer build_certificate();
|
||||
ByteBuffer build_alert(bool critical, u8 code);
|
||||
ByteBuffer build_change_cipher_spec();
|
||||
void build_rsa_pre_master_secret(PacketBuilder&);
|
||||
void build_dhe_rsa_pre_master_secret(PacketBuilder&);
|
||||
void build_ecdhe_rsa_pre_master_secret(PacketBuilder&);
|
||||
|
||||
ErrorOr<bool> flush();
|
||||
void write_into_socket();
|
||||
ErrorOr<void> read_from_socket();
|
||||
|
||||
bool check_connection_state(bool read);
|
||||
void notify_client_for_app_data();
|
||||
|
||||
ssize_t handle_server_hello(ReadonlyBytes, WritePacketStage&);
|
||||
ssize_t handle_handshake_finished(ReadonlyBytes, WritePacketStage&);
|
||||
ssize_t handle_certificate(ReadonlyBytes);
|
||||
ssize_t handle_server_key_exchange(ReadonlyBytes);
|
||||
ssize_t handle_dhe_rsa_server_key_exchange(ReadonlyBytes);
|
||||
ssize_t handle_ecdhe_server_key_exchange(ReadonlyBytes, u8& server_public_key_length);
|
||||
ssize_t handle_ecdhe_rsa_server_key_exchange(ReadonlyBytes);
|
||||
ssize_t handle_ecdhe_ecdsa_server_key_exchange(ReadonlyBytes);
|
||||
ssize_t handle_server_hello_done(ReadonlyBytes);
|
||||
ssize_t handle_certificate_verify(ReadonlyBytes);
|
||||
ssize_t handle_handshake_payload(ReadonlyBytes);
|
||||
ssize_t handle_message(ReadonlyBytes);
|
||||
|
||||
void pseudorandom_function(Bytes output, ReadonlyBytes secret, u8 const* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b);
|
||||
|
||||
ssize_t verify_rsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer);
|
||||
ssize_t verify_ecdsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer);
|
||||
|
||||
size_t key_length() const
|
||||
{
|
||||
switch (m_context.cipher) {
|
||||
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||
case suite: \
|
||||
return cipher_key_size(cipher) / 8;
|
||||
ENUMERATE_CIPHERS(C)
|
||||
#undef C
|
||||
default:
|
||||
return 128 / 8;
|
||||
}
|
||||
}
|
||||
|
||||
size_t mac_length() const
|
||||
{
|
||||
switch (m_context.cipher) {
|
||||
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||
case suite: \
|
||||
return hash ::digest_size();
|
||||
ENUMERATE_CIPHERS(C)
|
||||
#undef C
|
||||
default:
|
||||
return Crypto::Hash::SHA256::digest_size();
|
||||
}
|
||||
}
|
||||
|
||||
Crypto::Hash::HashKind hmac_hash() const
|
||||
{
|
||||
switch (mac_length()) {
|
||||
case Crypto::Hash::SHA512::DigestSize:
|
||||
return Crypto::Hash::HashKind::SHA512;
|
||||
case Crypto::Hash::SHA384::DigestSize:
|
||||
return Crypto::Hash::HashKind::SHA384;
|
||||
case Crypto::Hash::SHA256::DigestSize:
|
||||
case Crypto::Hash::SHA1::DigestSize:
|
||||
default:
|
||||
return Crypto::Hash::HashKind::SHA256;
|
||||
}
|
||||
}
|
||||
|
||||
size_t iv_length() const
|
||||
{
|
||||
switch (m_context.cipher) {
|
||||
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||
case suite: \
|
||||
return iv_size;
|
||||
ENUMERATE_CIPHERS(C)
|
||||
#undef C
|
||||
default:
|
||||
return 16;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_aead() const
|
||||
{
|
||||
switch (m_context.cipher) {
|
||||
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||
case suite: \
|
||||
return is_aead;
|
||||
ENUMERATE_CIPHERS(C)
|
||||
#undef C
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool expand_key();
|
||||
|
||||
bool compute_master_secret_from_pre_master_secret(size_t length);
|
||||
|
||||
void try_disambiguate_error() const;
|
||||
|
||||
bool m_eof { false };
|
||||
StreamVariantType m_stream;
|
||||
Context m_context;
|
||||
|
||||
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_local;
|
||||
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_remote;
|
||||
|
||||
using CipherVariant = Variant<
|
||||
Empty,
|
||||
Crypto::Cipher::AESCipher::CBCMode,
|
||||
Crypto::Cipher::AESCipher::GCMMode>;
|
||||
CipherVariant m_cipher_local {};
|
||||
CipherVariant m_cipher_remote {};
|
||||
|
||||
bool m_has_scheduled_write_flush { false };
|
||||
bool m_has_scheduled_app_data_flush { false };
|
||||
i32 m_max_wait_time_for_handshake_in_seconds { 10 };
|
||||
|
||||
RefPtr<Core::Timer> m_handshake_timeout_timer;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -44,15 +44,13 @@ void WebSocketImplSerenity::connect(ConnectionInfo const& connection_info)
|
|||
auto socket_result = [&]() -> ErrorOr<NonnullOwnPtr<Core::BufferedSocketBase>> {
|
||||
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] {
|
||||
TLS::Options options;
|
||||
options.set_alert_handler([this](auto) {
|
||||
on_connection_error();
|
||||
});
|
||||
return result.release_error();
|
||||
}
|
||||
|
||||
return TRY(Core::BufferedSocket<TLS::WolfTLS>::create(result.release_value()));
|
||||
return TRY(Core::BufferedSocket<TLS::TLSv12>::create(
|
||||
TRY(TLS::TLSv12::connect(host, connection_info.url().port_or_default(), move(options)))));
|
||||
}
|
||||
|
||||
return TRY(Core::BufferedTCPSocket::create(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -19,5 +19,4 @@ set(GENERATED_SOURCES
|
|||
)
|
||||
|
||||
serenity_bin(RequestServer)
|
||||
|
||||
target_link_libraries(RequestServer PRIVATE LibCore LibCrypto LibIPC LibHTTP LibMain LibTLS LibWebSocket LibURL LibThreading)
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
namespace RequestServer::ConnectionCache {
|
||||
|
||||
Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache {};
|
||||
Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::WolfTLS>>>>>> g_tls_connection_cache {};
|
||||
Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::TLSv12>>>>>> g_tls_connection_cache {};
|
||||
Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties;
|
||||
|
||||
void request_did_finish(URL::URL const& url, Core::Socket const* socket)
|
||||
|
@ -121,7 +121,7 @@ void request_did_finish(URL::URL const& url, Core::Socket const* socket)
|
|||
}
|
||||
};
|
||||
|
||||
if (is<Core::BufferedSocket<TLS::WolfTLS>>(socket))
|
||||
if (is<Core::BufferedSocket<TLS::TLSv12>>(socket))
|
||||
fire_off_next_job(g_tls_connection_cache);
|
||||
else if (is<Core::BufferedSocket<Core::Socket>>(socket))
|
||||
fire_off_next_job(g_tcp_connection_cache);
|
||||
|
|
|
@ -57,6 +57,7 @@ struct Proxy {
|
|||
struct JobData {
|
||||
Function<void(Core::BufferedSocketBase&)> start {};
|
||||
Function<void(Core::NetworkJob::Error)> fail {};
|
||||
Function<Vector<TLS::Certificate>()> provide_client_certificates {};
|
||||
struct TimingInfo {
|
||||
#if REQUESTSERVER_DEBUG
|
||||
bool valid { true };
|
||||
|
@ -68,9 +69,10 @@ struct JobData {
|
|||
#endif
|
||||
} timing_info {};
|
||||
|
||||
JobData(Function<void(Core::BufferedSocketBase&)> start, Function<void(Core::NetworkJob::Error)> fail, TimingInfo timing_info)
|
||||
JobData(Function<void(Core::BufferedSocketBase&)> start, Function<void(Core::NetworkJob::Error)> fail, Function<Vector<TLS::Certificate>()> provide_client_certificates, TimingInfo timing_info)
|
||||
: start(move(start))
|
||||
, 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<TLS::Certificate> {};
|
||||
},
|
||||
{
|
||||
#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<RequestServer::ConnectionCache::ConnectionKey> : public AK::DefaultTraits<RequestServer::ConnectionCache::ConnectionKey> {
|
||||
|
@ -159,7 +172,7 @@ struct InferredServerProperties {
|
|||
};
|
||||
|
||||
extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache;
|
||||
extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::WolfTLS>>>>>> g_tls_connection_cache;
|
||||
extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::TLSv12>>>>>> g_tls_connection_cache;
|
||||
extern Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties;
|
||||
|
||||
void request_did_finish(URL::URL const&, Core::Socket const*);
|
||||
|
@ -183,7 +196,29 @@ ErrorOr<void> recreate_socket_if_needed(T& connection, URL::URL const& url)
|
|||
return {};
|
||||
};
|
||||
|
||||
if constexpr (IsSame<TLS::TLSv12, SocketType>) {
|
||||
TLS::Options options;
|
||||
options.set_alert_handler([&connection](TLS::AlertDescription alert) {
|
||||
Core::NetworkJob::Error reason;
|
||||
if (alert == TLS::AlertDescription::HANDSHAKE_FAILURE)
|
||||
reason = Core::NetworkJob::Error::ProtocolFailed;
|
||||
else if (alert == TLS::AlertDescription::DECRYPT_ERROR)
|
||||
reason = Core::NetworkJob::Error::ConnectionFailed;
|
||||
else
|
||||
reason = Core::NetworkJob::Error::TransmissionFailed;
|
||||
|
||||
if (connection.job_data->fail)
|
||||
connection.job_data->fail(reason);
|
||||
});
|
||||
options.set_certificate_provider([&connection]() -> Vector<TLS::Certificate> {
|
||||
if (connection.job_data->provide_client_certificates)
|
||||
return connection.job_data->provide_client_certificates();
|
||||
return {};
|
||||
});
|
||||
TRY(set_socket(TRY((connection.proxy.template tunnel<SocketType, SocketStorageType>(url, move(options))))));
|
||||
} else {
|
||||
TRY(set_socket(TRY((connection.proxy.template tunnel<SocketType, SocketStorageType>(url)))));
|
||||
}
|
||||
dbgln_if(REQUESTSERVER_DEBUG, "Creating a new socket for {} -> {}", url, connection.socket.ptr());
|
||||
}
|
||||
return {};
|
||||
|
|
|
@ -26,6 +26,10 @@ ErrorOr<int> 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"));
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue