mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 17:10:23 +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)
|
ErrorOr<int> service_main(int ipc_socket)
|
||||||
{
|
{
|
||||||
// Ensure the certificates are read out here.
|
// Ensure the certificates are read out here.
|
||||||
TLS::WolfTLS::install_certificate_store_paths({ TRY(find_certificates(s_serenity_resource_root)) });
|
DefaultRootCACertificates::set_default_certificate_paths(Vector { TRY(find_certificates(s_serenity_resource_root)) });
|
||||||
|
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
|
||||||
|
|
||||||
Core::EventLoop event_loop;
|
Core::EventLoop event_loop;
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,8 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
// Ensure the certificates are read out here.
|
// Ensure the certificates are read out here.
|
||||||
if (certificates.is_empty())
|
if (certificates.is_empty())
|
||||||
certificates.append(TRY(find_certificates(serenity_resource_root)));
|
certificates.append(TRY(find_certificates(serenity_resource_root)));
|
||||||
|
DefaultRootCACertificates::set_default_certificate_paths(certificates.span());
|
||||||
TLS::WolfTLS::install_certificate_store_paths(move(certificates));
|
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
|
||||||
|
|
||||||
Core::EventLoop event_loop;
|
Core::EventLoop event_loop;
|
||||||
|
|
||||||
|
|
|
@ -530,6 +530,7 @@ if (BUILD_TESTING)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# LibTLS needs a special working directory to find cacert.pem
|
# LibTLS needs a special working directory to find cacert.pem
|
||||||
|
lagom_test(../../Tests/LibTLS/TestTLSHandshake.cpp LibTLS LIBS LibTLS LibCrypto)
|
||||||
lagom_test(../../Tests/LibTLS/TestTLSCertificateParser.cpp LibTLS LIBS LibTLS LibCrypto)
|
lagom_test(../../Tests/LibTLS/TestTLSCertificateParser.cpp LibTLS LIBS LibTLS LibCrypto)
|
||||||
|
|
||||||
# The FLAC tests need a special working directory to find the test files
|
# The FLAC tests need a special working directory to find the test files
|
||||||
|
|
|
@ -21,8 +21,7 @@ At the moment, many core library support components are inherited from SerenityO
|
||||||
- LibWeb: Web rendering engine
|
- LibWeb: Web rendering engine
|
||||||
- LibJS: JavaScript engine
|
- LibJS: JavaScript engine
|
||||||
- LibWasm: WebAssembly implementation
|
- LibWasm: WebAssembly implementation
|
||||||
- LibCrypto: Cryptography primitives
|
- LibCrypto/LibTLS: Cryptography primitives and Transport Layer Security
|
||||||
- LibTLS: Some certificate parsing primitives
|
|
||||||
- LibHTTP: HTTP/1.1 client
|
- LibHTTP: HTTP/1.1 client
|
||||||
- LibGfx: 2D Graphics Library, Image Decoding and Rendering
|
- LibGfx: 2D Graphics Library, Image Decoding and Rendering
|
||||||
- LibArchive: Archive file format support
|
- LibArchive: Archive file format support
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
set(TEST_SOURCES
|
set(TEST_SOURCES
|
||||||
TestTLSCertificateParser.cpp
|
TestTLSCertificateParser.cpp
|
||||||
|
TestTLSHandshake.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach(source IN LISTS TEST_SOURCES)
|
foreach(source IN LISTS TEST_SOURCES)
|
||||||
|
|
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(); }
|
virtual ~TCPSocket() override { close(); }
|
||||||
|
|
||||||
int fd() { return m_helper.fd(); }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit TCPSocket(PreventSIGPIPE prevent_sigpipe = PreventSIGPIPE::Yes)
|
explicit TCPSocket(PreventSIGPIPE prevent_sigpipe = PreventSIGPIPE::Yes)
|
||||||
: Socket(prevent_sigpipe)
|
: Socket(prevent_sigpipe)
|
||||||
|
|
|
@ -13,11 +13,11 @@
|
||||||
#include <AK/Types.h>
|
#include <AK/Types.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
|
||||||
namespace Crypto::Authentication {
|
|
||||||
|
|
||||||
constexpr static auto IPAD = 0x36;
|
constexpr static auto IPAD = 0x36;
|
||||||
constexpr static auto OPAD = 0x5c;
|
constexpr static auto OPAD = 0x5c;
|
||||||
|
|
||||||
|
namespace Crypto::Authentication {
|
||||||
|
|
||||||
template<typename HashT>
|
template<typename HashT>
|
||||||
class HMAC {
|
class HMAC {
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -10,8 +10,7 @@ namespace HTTP {
|
||||||
|
|
||||||
void HttpsJob::set_certificate(ByteString certificate, ByteString key)
|
void HttpsJob::set_certificate(ByteString certificate, ByteString key)
|
||||||
{
|
{
|
||||||
(void)certificate;
|
m_received_client_certificates = TLS::TLSv12::parse_pem_certificate(certificate.bytes(), key.bytes());
|
||||||
(void)key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,16 @@ add_compile_options(-Wvla)
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
Certificate.cpp
|
Certificate.cpp
|
||||||
|
Handshake.cpp
|
||||||
|
HandshakeCertificate.cpp
|
||||||
|
HandshakeClient.cpp
|
||||||
|
HandshakeServer.cpp
|
||||||
|
Record.cpp
|
||||||
|
Socket.cpp
|
||||||
TLSv12.cpp
|
TLSv12.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(WolfSSL REQUIRED)
|
|
||||||
|
|
||||||
serenity_lib(LibTLS tls)
|
serenity_lib(LibTLS tls)
|
||||||
target_link_libraries(LibTLS PRIVATE LibCore LibCrypto wolfssl::wolfssl)
|
target_link_libraries(LibTLS PRIVATE LibCore LibCrypto LibFileSystem)
|
||||||
|
|
||||||
include(ca_certificates_data)
|
include(ca_certificates_data)
|
||||||
|
|
|
@ -293,6 +293,25 @@ public:
|
||||||
private:
|
private:
|
||||||
Optional<bool> m_is_self_signed;
|
Optional<bool> m_is_self_signed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DefaultRootCACertificates {
|
||||||
|
public:
|
||||||
|
DefaultRootCACertificates();
|
||||||
|
|
||||||
|
Vector<Certificate> const& certificates() const { return m_ca_certificates; }
|
||||||
|
|
||||||
|
static ErrorOr<Vector<Certificate>> parse_pem_root_certificate_authorities(ByteBuffer&);
|
||||||
|
static ErrorOr<Vector<Certificate>> load_certificates(Span<ByteString> custom_cert_paths = {});
|
||||||
|
|
||||||
|
static DefaultRootCACertificates& the();
|
||||||
|
|
||||||
|
static void set_default_certificate_paths(Span<ByteString> paths);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vector<Certificate> m_ca_certificates;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using TLS::Certificate;
|
using TLS::Certificate;
|
||||||
|
using TLS::DefaultRootCACertificates;
|
||||||
|
|
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
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/Base64.h>
|
||||||
|
#include <AK/Debug.h>
|
||||||
|
#include <AK/Endian.h>
|
||||||
|
#include <LibCore/ConfigFile.h>
|
||||||
|
#include <LibCore/DateTime.h>
|
||||||
|
#include <LibCore/File.h>
|
||||||
|
#include <LibCore/StandardPaths.h>
|
||||||
|
#include <LibCore/Timer.h>
|
||||||
|
#include <LibCrypto/ASN1/ASN1.h>
|
||||||
|
#include <LibCrypto/ASN1/PEM.h>
|
||||||
|
#include <LibCrypto/Curves/Ed25519.h>
|
||||||
|
#include <LibCrypto/Curves/SECPxxxr1.h>
|
||||||
|
#include <LibCrypto/PK/Code/EMSA_PKCS1_V1_5.h>
|
||||||
|
#include <LibFileSystem/FileSystem.h>
|
||||||
|
#include <LibTLS/Certificate.h>
|
||||||
#include <LibTLS/TLSv12.h>
|
#include <LibTLS/TLSv12.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <wolfssl/options.h>
|
|
||||||
#include <wolfssl/ssl.h>
|
|
||||||
|
|
||||||
#ifndef SOCK_NONBLOCK
|
#ifndef SOCK_NONBLOCK
|
||||||
# include <sys/ioctl.h>
|
# include <sys/ioctl.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static Vector<ByteString> s_certificate_store_paths;
|
|
||||||
|
|
||||||
namespace TLS {
|
namespace TLS {
|
||||||
|
|
||||||
static thread_local bool g_initialized_wolfssl = false;
|
void TLSv12::consume(ReadonlyBytes record)
|
||||||
|
|
||||||
static int errno_to_wolfssl_error(int error)
|
|
||||||
{
|
{
|
||||||
switch (error) {
|
if (m_context.critical_error) {
|
||||||
case EAGAIN:
|
dbgln("There has been a critical error ({}), refusing to continue", (i8)m_context.critical_error);
|
||||||
return WOLFSSL_CBIO_ERR_WANT_READ;
|
return;
|
||||||
case ETIMEDOUT:
|
}
|
||||||
return WOLFSSL_CBIO_ERR_TIMEOUT;
|
|
||||||
case ECONNRESET:
|
if (record.size() == 0) {
|
||||||
return WOLFSSL_CBIO_ERR_CONN_RST;
|
return;
|
||||||
case EINTR:
|
}
|
||||||
return WOLFSSL_CBIO_ERR_ISR;
|
|
||||||
case ECONNREFUSED:
|
dbgln_if(TLS_DEBUG, "Consuming {} bytes", record.size());
|
||||||
return WOLFSSL_CBIO_ERR_WANT_READ;
|
|
||||||
case ECONNABORTED:
|
if (m_context.message_buffer.try_append(record).is_error()) {
|
||||||
return WOLFSSL_CBIO_ERR_CONN_CLOSE;
|
dbgln("Not enough space in message buffer, dropping the record");
|
||||||
default:
|
return;
|
||||||
return WOLFSSL_CBIO_ERR_GENERAL;
|
}
|
||||||
|
|
||||||
|
size_t index { 0 };
|
||||||
|
size_t buffer_length = m_context.message_buffer.size();
|
||||||
|
|
||||||
|
size_t size_offset { 3 }; // read the common record header
|
||||||
|
size_t header_size { 5 };
|
||||||
|
|
||||||
|
dbgln_if(TLS_DEBUG, "message buffer length {}", buffer_length);
|
||||||
|
|
||||||
|
while (buffer_length >= 5) {
|
||||||
|
auto length = AK::convert_between_host_and_network_endian(ByteReader::load16(m_context.message_buffer.offset_pointer(index + size_offset))) + header_size;
|
||||||
|
if (length > buffer_length) {
|
||||||
|
dbgln_if(TLS_DEBUG, "Need more data: {} > {}", length, buffer_length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto consumed = handle_message(m_context.message_buffer.bytes().slice(index, length));
|
||||||
|
|
||||||
|
if constexpr (TLS_DEBUG) {
|
||||||
|
if (consumed > 0)
|
||||||
|
dbgln("consumed {} bytes", consumed);
|
||||||
|
else
|
||||||
|
dbgln("error: {}", consumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consumed != (i8)Error::NeedMoreData) {
|
||||||
|
if (consumed < 0) {
|
||||||
|
dbgln("Consumed an error: {}", consumed);
|
||||||
|
if (!m_context.critical_error)
|
||||||
|
m_context.critical_error = (i8)consumed;
|
||||||
|
m_context.error_code = (Error)consumed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += length;
|
||||||
|
buffer_length -= length;
|
||||||
|
if (m_context.critical_error) {
|
||||||
|
dbgln("Broken connection");
|
||||||
|
m_context.error_code = Error::BrokenConnection;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_context.error_code != Error::NoError && m_context.error_code != Error::NeedMoreData) {
|
||||||
|
dbgln("consume error: {}", (i8)m_context.error_code);
|
||||||
|
m_context.message_buffer.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index) {
|
||||||
|
// FIXME: Propagate errors.
|
||||||
|
m_context.message_buffer = MUST(m_context.message_buffer.slice(index, m_context.message_buffer.size() - index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StringView WolfTLS::error_text(WOLFSSL* ssl, int error_code)
|
bool Certificate::is_valid() const
|
||||||
{
|
{
|
||||||
auto error = wolfSSL_get_error(ssl, error_code);
|
auto now = UnixDateTime::now();
|
||||||
static char buffer[WOLFSSL_MAX_ERROR_SZ];
|
|
||||||
auto* ptr = wolfSSL_ERR_error_string(error, buffer);
|
|
||||||
return StringView { ptr, strlen(ptr) };
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<NonnullOwnPtr<WolfTLS>> WolfTLS::connect(const AK::ByteString& host, u16 port)
|
if (now < validity.not_before) {
|
||||||
{
|
dbgln("certificate expired (not yet valid, signed for {})", Core::DateTime::from_timestamp(validity.not_before.seconds_since_epoch()));
|
||||||
if (!g_initialized_wolfssl) {
|
return false;
|
||||||
wolfSSL_Init();
|
|
||||||
g_initialized_wolfssl = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* context = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
|
if (validity.not_after < now) {
|
||||||
if (!context)
|
dbgln("certificate expired (expiry date {})", Core::DateTime::from_timestamp(validity.not_after.seconds_since_epoch()));
|
||||||
return Error::from_string_literal("Failed to create a new TLS context");
|
return false;
|
||||||
|
|
||||||
if (s_certificate_store_paths.is_empty())
|
|
||||||
s_certificate_store_paths.append("/etc/ssl/cert.pem"); // We're just guessing this, the embedder should provide this.
|
|
||||||
|
|
||||||
for (auto& path : s_certificate_store_paths) {
|
|
||||||
if (wolfSSL_CTX_load_verify_locations(context, path.characters(), nullptr) != SSL_SUCCESS)
|
|
||||||
return Error::from_string_literal("Failed to load CA certificates");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ssl = wolfSSL_new(context);
|
|
||||||
if (!ssl)
|
|
||||||
return Error::from_string_literal("Failed to create a new SSL object");
|
|
||||||
|
|
||||||
wolfSSL_SSLSetIOSend(ssl, [](WOLFSSL*, char* buf, int sz, void* ctx) -> int {
|
|
||||||
auto& self = *static_cast<WolfTLS*>(ctx);
|
|
||||||
auto result = self.m_underlying->write_some({ buf, static_cast<size_t>(sz) });
|
|
||||||
if (result.is_error() && result.error().is_errno())
|
|
||||||
return errno_to_wolfssl_error(result.error().code());
|
|
||||||
if (result.is_error())
|
|
||||||
return -1;
|
|
||||||
return static_cast<int>(result.value());
|
|
||||||
});
|
|
||||||
wolfSSL_SSLSetIORecv(ssl, [](WOLFSSL*, char* buf, int sz, void* ctx) -> int {
|
|
||||||
auto& self = *static_cast<WolfTLS*>(ctx);
|
|
||||||
auto result = self.m_underlying->read_some({ buf, static_cast<size_t>(sz) });
|
|
||||||
if (result.is_error() && result.error().is_errno())
|
|
||||||
return errno_to_wolfssl_error(result.error().code());
|
|
||||||
if (result.is_error())
|
|
||||||
return -1;
|
|
||||||
return static_cast<int>(result.value().size());
|
|
||||||
});
|
|
||||||
|
|
||||||
auto tcp_socket = TRY(Core::TCPSocket::connect(host, port));
|
|
||||||
|
|
||||||
auto object = make<WolfTLS>(context, ssl, move(tcp_socket));
|
|
||||||
wolfSSL_SetIOReadCtx(ssl, static_cast<void*>(object.ptr()));
|
|
||||||
wolfSSL_SetIOWriteCtx(ssl, static_cast<void*>(object.ptr()));
|
|
||||||
|
|
||||||
if (wolfSSL_CTX_UseSNI(context, WOLFSSL_SNI_HOST_NAME, host.bytes().data(), host.bytes().size()) != WOLFSSL_SUCCESS)
|
|
||||||
return Error::from_string_literal("Failed to set SNI hostname");
|
|
||||||
|
|
||||||
if (auto rc = wolfSSL_connect(ssl); rc != SSL_SUCCESS)
|
|
||||||
return Error::from_string_view(error_text(ssl, rc));
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
WolfTLS::~WolfTLS()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
wolfSSL_free(m_ssl);
|
|
||||||
wolfSSL_CTX_free(m_context);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<Bytes> WolfTLS::read_some(Bytes bytes)
|
|
||||||
{
|
|
||||||
auto result = wolfSSL_read(m_ssl, bytes.data(), bytes.size());
|
|
||||||
if (result < 0)
|
|
||||||
return Error::from_string_view(error_text(m_ssl, result));
|
|
||||||
return Bytes { bytes.data(), static_cast<size_t>(result) };
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<size_t> WolfTLS::write_some(ReadonlyBytes bytes)
|
|
||||||
{
|
|
||||||
auto result = wolfSSL_write(m_ssl, bytes.data(), bytes.size());
|
|
||||||
if (result < 0)
|
|
||||||
return Error::from_string_view(error_text(m_ssl, result));
|
|
||||||
return static_cast<size_t>(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WolfTLS::is_eof() const
|
|
||||||
{
|
|
||||||
return m_underlying->is_eof() && wolfSSL_pending(m_ssl) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WolfTLS::is_open() const
|
|
||||||
{
|
|
||||||
return m_underlying->is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WolfTLS::close()
|
|
||||||
{
|
|
||||||
wolfSSL_shutdown(m_ssl);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<size_t> WolfTLS::pending_bytes() const
|
|
||||||
{
|
|
||||||
return wolfSSL_pending(m_ssl);
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorOr<bool> WolfTLS::can_read_without_blocking(int) const
|
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<void> WolfTLS::set_blocking(bool)
|
// https://www.ietf.org/rfc/rfc5280.html#page-12
|
||||||
|
bool Certificate::is_self_signed()
|
||||||
{
|
{
|
||||||
|
if (m_is_self_signed.has_value())
|
||||||
|
return *m_is_self_signed;
|
||||||
|
|
||||||
|
// Self-signed certificates are self-issued certificates where the digital
|
||||||
|
// signature may be verified by the public key bound into the certificate.
|
||||||
|
if (!this->is_self_issued)
|
||||||
|
m_is_self_signed.emplace(false);
|
||||||
|
|
||||||
|
// FIXME: Actually check if we sign ourself
|
||||||
|
|
||||||
|
m_is_self_signed.emplace(true);
|
||||||
|
return *m_is_self_signed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLSv12::try_disambiguate_error() const
|
||||||
|
{
|
||||||
|
dbgln("Possible failure cause(s): ");
|
||||||
|
switch ((AlertDescription)m_context.critical_error) {
|
||||||
|
case AlertDescription::HANDSHAKE_FAILURE:
|
||||||
|
if (!m_context.cipher_spec_set) {
|
||||||
|
dbgln("- No cipher suite in common with {}", m_context.extensions.SNI);
|
||||||
|
} else {
|
||||||
|
dbgln("- Unknown internal issue");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AlertDescription::INSUFFICIENT_SECURITY:
|
||||||
|
dbgln("- No cipher suite in common with {} (the server is oh so secure)", m_context.extensions.SNI);
|
||||||
|
break;
|
||||||
|
case AlertDescription::PROTOCOL_VERSION:
|
||||||
|
dbgln("- The server refused to negotiate with TLS 1.2 :(");
|
||||||
|
break;
|
||||||
|
case AlertDescription::UNEXPECTED_MESSAGE:
|
||||||
|
dbgln("- We sent an invalid message for the state we're in.");
|
||||||
|
break;
|
||||||
|
case AlertDescription::BAD_RECORD_MAC:
|
||||||
|
dbgln("- Bad MAC record from our side.");
|
||||||
|
dbgln("- Ciphertext wasn't an even multiple of the block length.");
|
||||||
|
dbgln("- Bad block cipher padding.");
|
||||||
|
dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
|
||||||
|
break;
|
||||||
|
case AlertDescription::RECORD_OVERFLOW:
|
||||||
|
dbgln("- Sent a ciphertext record which has a length bigger than 18432 bytes.");
|
||||||
|
dbgln("- Sent record decrypted to a compressed record that has a length bigger than 18432 bytes.");
|
||||||
|
dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
|
||||||
|
break;
|
||||||
|
case AlertDescription::DECOMPRESSION_FAILURE_RESERVED:
|
||||||
|
dbgln("- We sent invalid input for decompression (e.g. data that would expand to excessive length)");
|
||||||
|
break;
|
||||||
|
case AlertDescription::ILLEGAL_PARAMETER:
|
||||||
|
dbgln("- We sent a parameter in the handshake that is out of range or inconsistent with the other parameters.");
|
||||||
|
break;
|
||||||
|
case AlertDescription::DECODE_ERROR:
|
||||||
|
dbgln("- The message we sent cannot be decoded because a field was out of range or the length was incorrect.");
|
||||||
|
dbgln("- If both sides are compliant, the only cause is messages being corrupted in the network.");
|
||||||
|
break;
|
||||||
|
case AlertDescription::DECRYPT_ERROR:
|
||||||
|
dbgln("- A handshake crypto operation failed. This includes signature verification and validating Finished.");
|
||||||
|
break;
|
||||||
|
case AlertDescription::ACCESS_DENIED:
|
||||||
|
dbgln("- The certificate is valid, but once access control was applied, the sender decided to stop negotiation.");
|
||||||
|
break;
|
||||||
|
case AlertDescription::INTERNAL_ERROR:
|
||||||
|
dbgln("- No one knows, but it isn't a protocol failure.");
|
||||||
|
break;
|
||||||
|
case AlertDescription::DECRYPTION_FAILED_RESERVED:
|
||||||
|
case AlertDescription::NO_CERTIFICATE_RESERVED:
|
||||||
|
case AlertDescription::EXPORT_RESTRICTION_RESERVED:
|
||||||
|
dbgln("- No one knows, the server sent a non-compliant alert.");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dbgln("- No one knows.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbgln("- {}", enum_to_value((AlertDescription)m_context.critical_error));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLSv12::set_root_certificates(Vector<Certificate> certificates)
|
||||||
|
{
|
||||||
|
if (!m_context.root_certificates.is_empty()) {
|
||||||
|
dbgln("TLS warn: resetting root certificates!");
|
||||||
|
m_context.root_certificates.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& cert : certificates) {
|
||||||
|
if (!cert.is_valid()) {
|
||||||
|
dbgln("Certificate for {} is invalid, things may or may not work!", cert.subject.to_string());
|
||||||
|
}
|
||||||
|
// FIXME: Figure out what we should do when our root certs are invalid.
|
||||||
|
|
||||||
|
m_context.root_certificates.set(MUST(cert.subject.to_string()).to_byte_string(), cert);
|
||||||
|
}
|
||||||
|
dbgln_if(TLS_DEBUG, "{}: Set {} root certificates", this, m_context.root_certificates.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool wildcard_matches(StringView host, StringView subject)
|
||||||
|
{
|
||||||
|
if (host == subject)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (subject.starts_with("*."sv)) {
|
||||||
|
auto maybe_first_dot_index = host.find('.');
|
||||||
|
if (maybe_first_dot_index.has_value()) {
|
||||||
|
auto first_dot_index = maybe_first_dot_index.release_value();
|
||||||
|
return wildcard_matches(host.substring_view(first_dot_index + 1), subject.substring_view(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool certificate_subject_matches_host(Certificate const& cert, StringView host)
|
||||||
|
{
|
||||||
|
if (wildcard_matches(host, cert.subject.common_name()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& san : cert.SAN) {
|
||||||
|
if (wildcard_matches(host, san))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Context::verify_chain(StringView host) const
|
||||||
|
{
|
||||||
|
if (!options.validate_certificates)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Vector<Certificate> const* local_chain = nullptr;
|
||||||
|
if (is_server) {
|
||||||
|
dbgln("Unsupported: Server mode");
|
||||||
|
TODO();
|
||||||
|
} else {
|
||||||
|
local_chain = &certificates;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (local_chain->is_empty()) {
|
||||||
|
dbgln("verify_chain: Attempting to verify an empty chain");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC5246 section 7.4.2: The sender's certificate MUST come first in the list. Each following certificate
|
||||||
|
// MUST directly certify the one preceding it. Because certificate validation requires that root keys be
|
||||||
|
// distributed independently, the self-signed certificate that specifies the root certificate authority MAY be
|
||||||
|
// omitted from the chain, under the assumption that the remote end must already possess it in order to validate
|
||||||
|
// it in any case.
|
||||||
|
|
||||||
|
if (!host.is_empty()) {
|
||||||
|
auto const& first_certificate = local_chain->first();
|
||||||
|
auto subject_matches = certificate_subject_matches_host(first_certificate, host);
|
||||||
|
if (!subject_matches) {
|
||||||
|
dbgln("verify_chain: First certificate does not match the hostname");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// FIXME: The host is taken from m_context.extensions.SNI, when is this empty?
|
||||||
|
dbgln("FIXME: verify_chain called without host");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t cert_index = 0; cert_index < local_chain->size(); ++cert_index) {
|
||||||
|
auto const& cert = local_chain->at(cert_index);
|
||||||
|
|
||||||
|
auto subject_string = MUST(cert.subject.to_string());
|
||||||
|
auto issuer_string = MUST(cert.issuer.to_string());
|
||||||
|
|
||||||
|
if (!cert.is_valid()) {
|
||||||
|
dbgln("verify_chain: Certificate is not valid {}", subject_string);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maybe_root_certificate = root_certificates.get(issuer_string.to_byte_string());
|
||||||
|
if (maybe_root_certificate.has_value()) {
|
||||||
|
auto& root_certificate = *maybe_root_certificate;
|
||||||
|
auto verification_correct = verify_certificate_pair(cert, root_certificate);
|
||||||
|
|
||||||
|
if (!verification_correct) {
|
||||||
|
dbgln("verify_chain: Signature inconsistent, {} was not signed by {} (root certificate)", subject_string, issuer_string);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root certificate reached, and correctly verified, so we can stop now
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subject_string == issuer_string) {
|
||||||
|
dbgln("verify_chain: Non-root self-signed certificate");
|
||||||
|
return options.allow_self_signed_certificates;
|
||||||
|
}
|
||||||
|
if ((cert_index + 1) >= local_chain->size()) {
|
||||||
|
dbgln("verify_chain: No trusted root certificate found before end of certificate chain");
|
||||||
|
dbgln("verify_chain: Last certificate in chain was signed by {}", issuer_string);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto const& parent_certificate = local_chain->at(cert_index + 1);
|
||||||
|
if (issuer_string != MUST(parent_certificate.subject.to_string())) {
|
||||||
|
dbgln("verify_chain: Next certificate in the chain is not the issuer of this certificate");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(parent_certificate.is_allowed_to_sign_certificate && parent_certificate.is_certificate_authority)) {
|
||||||
|
dbgln("verify_chain: {} is not marked as certificate authority", issuer_string);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (parent_certificate.path_length_constraint.has_value() && cert_index > parent_certificate.path_length_constraint.value()) {
|
||||||
|
dbgln("verify_chain: Path length for certificate exceeded");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool verification_correct = verify_certificate_pair(cert, parent_certificate);
|
||||||
|
if (!verification_correct) {
|
||||||
|
dbgln("verify_chain: Signature inconsistent, {} was not signed by {}", subject_string, issuer_string);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either a root certificate is reached, or parent validation fails as the end of the local chain is reached
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Context::verify_certificate_pair(Certificate const& subject, Certificate const& issuer) const
|
||||||
|
{
|
||||||
|
Crypto::Hash::HashKind kind = Crypto::Hash::HashKind::Unknown;
|
||||||
|
auto identifier = subject.signature_algorithm.identifier;
|
||||||
|
|
||||||
|
bool is_rsa = true;
|
||||||
|
|
||||||
|
if (identifier == rsa_encryption_oid) {
|
||||||
|
kind = Crypto::Hash::HashKind::None;
|
||||||
|
} else if (identifier == rsa_md5_encryption_oid) {
|
||||||
|
kind = Crypto::Hash::HashKind::MD5;
|
||||||
|
} else if (identifier == rsa_sha1_encryption_oid) {
|
||||||
|
kind = Crypto::Hash::HashKind::SHA1;
|
||||||
|
} else if (identifier == rsa_sha256_encryption_oid) {
|
||||||
|
kind = Crypto::Hash::HashKind::SHA256;
|
||||||
|
} else if (identifier == rsa_sha384_encryption_oid) {
|
||||||
|
kind = Crypto::Hash::HashKind::SHA384;
|
||||||
|
} else if (identifier == rsa_sha512_encryption_oid) {
|
||||||
|
kind = Crypto::Hash::HashKind::SHA512;
|
||||||
|
} else if (identifier == ecdsa_with_sha256_encryption_oid) {
|
||||||
|
kind = Crypto::Hash::HashKind::SHA256;
|
||||||
|
is_rsa = false;
|
||||||
|
} else if (identifier == ecdsa_with_sha384_encryption_oid) {
|
||||||
|
kind = Crypto::Hash::HashKind::SHA384;
|
||||||
|
is_rsa = false;
|
||||||
|
} else if (identifier == ecdsa_with_sha512_encryption_oid) {
|
||||||
|
kind = Crypto::Hash::HashKind::SHA512;
|
||||||
|
is_rsa = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind == Crypto::Hash::HashKind::Unknown) {
|
||||||
|
dbgln("verify_certificate_pair: Unknown signature algorithm, expected RSA or ECDSA with SHA1/256/384/512, got OID {}", identifier);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_rsa) {
|
||||||
|
Crypto::PK::RSAPrivateKey dummy_private_key;
|
||||||
|
Crypto::PK::RSAPublicKey public_key_copy { issuer.public_key.rsa };
|
||||||
|
auto rsa = Crypto::PK::RSA(public_key_copy, dummy_private_key);
|
||||||
|
auto verification_buffer_result = ByteBuffer::create_uninitialized(subject.signature_value.size());
|
||||||
|
if (verification_buffer_result.is_error()) {
|
||||||
|
dbgln("verify_certificate_pair: Unable to allocate buffer for verification");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto verification_buffer = verification_buffer_result.release_value();
|
||||||
|
auto verification_buffer_bytes = verification_buffer.bytes();
|
||||||
|
rsa.verify(subject.signature_value, verification_buffer_bytes);
|
||||||
|
|
||||||
|
ReadonlyBytes message = subject.tbs_asn1.bytes();
|
||||||
|
auto pkcs1 = Crypto::PK::EMSA_PKCS1_V1_5<Crypto::Hash::Manager>(kind);
|
||||||
|
auto verification = pkcs1.verify(message, verification_buffer_bytes, subject.signature_value.size() * 8);
|
||||||
|
return verification == Crypto::VerificationConsistency::Consistent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ECDSA hash verification: hash, then check signature against the specific curve
|
||||||
|
switch (issuer.public_key.algorithm.ec_parameters) {
|
||||||
|
case SupportedGroup::SECP256R1: {
|
||||||
|
Crypto::Hash::Manager hasher(kind);
|
||||||
|
hasher.update(subject.tbs_asn1.bytes());
|
||||||
|
auto hash = hasher.digest();
|
||||||
|
|
||||||
|
Crypto::Curves::SECP256r1 curve;
|
||||||
|
auto result = curve.verify(hash.bytes(), issuer.public_key.raw_key, subject.signature_value);
|
||||||
|
if (result.is_error()) {
|
||||||
|
dbgln("verify_certificate_pair: Failed to check SECP256r1 signature {}", result.release_error());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return result.value();
|
||||||
|
}
|
||||||
|
case SupportedGroup::SECP384R1: {
|
||||||
|
Crypto::Hash::Manager hasher(kind);
|
||||||
|
hasher.update(subject.tbs_asn1.bytes());
|
||||||
|
auto hash = hasher.digest();
|
||||||
|
|
||||||
|
Crypto::Curves::SECP384r1 curve;
|
||||||
|
auto result = curve.verify(hash.bytes(), issuer.public_key.raw_key, subject.signature_value);
|
||||||
|
if (result.is_error()) {
|
||||||
|
dbgln("verify_certificate_pair: Failed to check SECP384r1 signature {}", result.release_error());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return result.value();
|
||||||
|
}
|
||||||
|
case SupportedGroup::X25519: {
|
||||||
|
Crypto::Curves::Ed25519 curve;
|
||||||
|
auto result = curve.verify(issuer.public_key.raw_key, subject.signature_value, subject.tbs_asn1.bytes());
|
||||||
|
if (!result) {
|
||||||
|
dbgln("verify_certificate_pair: Failed to check Ed25519 signature");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
dbgln("verify_certificate_pair: Don't know how to verify signature for curve {}", to_underlying(issuer.public_key.algorithm.ec_parameters));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename HMACType>
|
||||||
|
static void hmac_pseudorandom_function(Bytes output, ReadonlyBytes secret, u8 const* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b)
|
||||||
|
{
|
||||||
|
if (!secret.size()) {
|
||||||
|
dbgln("null secret");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto append_label_seed = [&](auto& hmac) {
|
||||||
|
hmac.update(label, label_length);
|
||||||
|
hmac.update(seed);
|
||||||
|
if (seed_b.size() > 0)
|
||||||
|
hmac.update(seed_b);
|
||||||
|
};
|
||||||
|
|
||||||
|
HMACType hmac(secret);
|
||||||
|
append_label_seed(hmac);
|
||||||
|
|
||||||
|
constexpr auto digest_size = hmac.digest_size();
|
||||||
|
u8 digest[digest_size];
|
||||||
|
auto digest_0 = Bytes { digest, digest_size };
|
||||||
|
|
||||||
|
digest_0.overwrite(0, hmac.digest().immutable_data(), digest_size);
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
while (index < output.size()) {
|
||||||
|
hmac.update(digest_0);
|
||||||
|
append_label_seed(hmac);
|
||||||
|
auto digest_1 = hmac.digest();
|
||||||
|
|
||||||
|
auto copy_size = min(digest_size, output.size() - index);
|
||||||
|
|
||||||
|
output.overwrite(index, digest_1.immutable_data(), copy_size);
|
||||||
|
index += copy_size;
|
||||||
|
|
||||||
|
digest_0.overwrite(0, hmac.process(digest_0).immutable_data(), digest_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLSv12::pseudorandom_function(Bytes output, ReadonlyBytes secret, u8 const* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b)
|
||||||
|
{
|
||||||
|
// Simplification: We only support the HMAC PRF with the hash function SHA-256 or stronger.
|
||||||
|
|
||||||
|
// RFC 5246: "In this section, we define one PRF, based on HMAC. This PRF with the
|
||||||
|
// SHA-256 hash function is used for all cipher suites defined in this
|
||||||
|
// document and in TLS documents published prior to this document when
|
||||||
|
// TLS 1.2 is negotiated. New cipher suites MUST explicitly specify a
|
||||||
|
// PRF and, in general, SHOULD use the TLS PRF with SHA-256 or a
|
||||||
|
// stronger standard hash function."
|
||||||
|
|
||||||
|
switch (hmac_hash()) {
|
||||||
|
case Crypto::Hash::HashKind::SHA512:
|
||||||
|
hmac_pseudorandom_function<Crypto::Authentication::HMAC<Crypto::Hash::SHA512>>(output, secret, label, label_length, seed, seed_b);
|
||||||
|
break;
|
||||||
|
case Crypto::Hash::HashKind::SHA384:
|
||||||
|
hmac_pseudorandom_function<Crypto::Authentication::HMAC<Crypto::Hash::SHA384>>(output, secret, label, label_length, seed, seed_b);
|
||||||
|
break;
|
||||||
|
case Crypto::Hash::HashKind::SHA256:
|
||||||
|
hmac_pseudorandom_function<Crypto::Authentication::HMAC<Crypto::Hash::SHA256>>(output, secret, label, label_length, seed, seed_b);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dbgln("Failed to find a suitable HMAC hash");
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TLSv12::TLSv12(StreamVariantType stream, Options options)
|
||||||
|
: m_stream(move(stream))
|
||||||
|
{
|
||||||
|
m_context.options = move(options);
|
||||||
|
m_context.is_server = false;
|
||||||
|
m_context.tls_buffer = {};
|
||||||
|
|
||||||
|
set_root_certificates(m_context.options.root_certificates.has_value()
|
||||||
|
? *m_context.options.root_certificates
|
||||||
|
: DefaultRootCACertificates::the().certificates());
|
||||||
|
|
||||||
|
setup_connection();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector<Certificate> TLSv12::parse_pem_certificate(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes rsa_key) // FIXME: This should not be bound to RSA
|
||||||
|
{
|
||||||
|
if (certificate_pem_buffer.is_empty() || rsa_key.is_empty()) {
|
||||||
return {};
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto decoded_certificate = Crypto::decode_pem(certificate_pem_buffer);
|
||||||
|
if (decoded_certificate.is_empty()) {
|
||||||
|
dbgln("Certificate not PEM");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto maybe_certificate = Certificate::parse_certificate(decoded_certificate);
|
||||||
|
if (!maybe_certificate.is_error()) {
|
||||||
|
dbgln("Invalid certificate");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Crypto::PK::RSA rsa(rsa_key);
|
||||||
|
auto certificate = maybe_certificate.release_value();
|
||||||
|
certificate.private_key = rsa.private_key();
|
||||||
|
|
||||||
|
return { move(certificate) };
|
||||||
}
|
}
|
||||||
|
|
||||||
void WolfTLS::install_certificate_store_paths(Vector<AK::ByteString> paths)
|
static Vector<ByteString> s_default_ca_certificate_paths;
|
||||||
|
|
||||||
|
void DefaultRootCACertificates::set_default_certificate_paths(Span<ByteString> paths)
|
||||||
{
|
{
|
||||||
s_certificate_store_paths = move(paths);
|
s_default_ca_certificate_paths.clear();
|
||||||
|
s_default_ca_certificate_paths.ensure_capacity(paths.size());
|
||||||
|
for (auto& path : paths)
|
||||||
|
s_default_ca_certificate_paths.unchecked_append(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DefaultRootCACertificates::DefaultRootCACertificates()
|
||||||
|
{
|
||||||
|
auto load_result = load_certificates(s_default_ca_certificate_paths);
|
||||||
|
if (load_result.is_error()) {
|
||||||
|
dbgln("Failed to load CA Certificates: {}", load_result.error());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ca_certificates = load_result.release_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultRootCACertificates& DefaultRootCACertificates::the()
|
||||||
|
{
|
||||||
|
static thread_local DefaultRootCACertificates s_the;
|
||||||
|
return s_the;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorOr<Vector<Certificate>> DefaultRootCACertificates::load_certificates(Span<ByteString> custom_cert_paths)
|
||||||
|
{
|
||||||
|
auto cacert_file_or_error = Core::File::open("/etc/cacert.pem"sv, Core::File::OpenMode::Read);
|
||||||
|
ByteBuffer data;
|
||||||
|
if (!cacert_file_or_error.is_error())
|
||||||
|
data = TRY(cacert_file_or_error.value()->read_until_eof());
|
||||||
|
|
||||||
|
auto user_cert_path = TRY(String::formatted("{}/.config/certs.pem", Core::StandardPaths::home_directory()));
|
||||||
|
if (FileSystem::exists(user_cert_path)) {
|
||||||
|
auto user_cert_file = TRY(Core::File::open(user_cert_path, Core::File::OpenMode::Read));
|
||||||
|
TRY(data.try_append(TRY(user_cert_file->read_until_eof())));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& custom_cert_path : custom_cert_paths) {
|
||||||
|
if (FileSystem::exists(custom_cert_path)) {
|
||||||
|
auto custom_cert_file = TRY(Core::File::open(custom_cert_path, Core::File::OpenMode::Read));
|
||||||
|
TRY(data.try_append(TRY(custom_cert_file->read_until_eof())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRY(parse_pem_root_certificate_authorities(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorOr<Vector<Certificate>> DefaultRootCACertificates::parse_pem_root_certificate_authorities(ByteBuffer& data)
|
||||||
|
{
|
||||||
|
Vector<Certificate> certificates;
|
||||||
|
|
||||||
|
auto certs = TRY(Crypto::decode_pems(data));
|
||||||
|
|
||||||
|
for (auto& cert : certs) {
|
||||||
|
auto certificate_result = Certificate::parse_certificate(cert.bytes());
|
||||||
|
if (certificate_result.is_error()) {
|
||||||
|
// FIXME: It would be nice to have more informations about the certificate we failed to parse.
|
||||||
|
// Like: Issuer, Algorithm, CN, etc
|
||||||
|
dbgln("Failed to load certificate: {}", certificate_result.error());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto certificate = certificate_result.release_value();
|
||||||
|
if (certificate.is_certificate_authority && certificate.is_self_signed()) {
|
||||||
|
TRY(certificates.try_append(move(certificate)));
|
||||||
|
} else {
|
||||||
|
dbgln("Skipped '{}' because it is not a valid root CA", TRY(certificate.subject.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbgln_if(TLS_DEBUG, "Loaded {} of {} ({:.2}%) provided CA Certificates", certificates.size(), certs.size(), (certificates.size() * 100.0) / certs.size());
|
||||||
|
|
||||||
|
return certificates;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,48 +6,546 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Certificate.h"
|
||||||
#include <AK/IPv4Address.h>
|
#include <AK/IPv4Address.h>
|
||||||
|
#include <AK/Queue.h>
|
||||||
|
#include <AK/WeakPtr.h>
|
||||||
|
#include <LibCore/Notifier.h>
|
||||||
#include <LibCore/Socket.h>
|
#include <LibCore/Socket.h>
|
||||||
#include <LibTLS/Certificate.h>
|
#include <LibCore/Timer.h>
|
||||||
|
#include <LibCrypto/Authentication/HMAC.h>
|
||||||
|
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
||||||
|
#include <LibCrypto/Cipher/AES.h>
|
||||||
|
#include <LibCrypto/Curves/EllipticCurve.h>
|
||||||
|
#include <LibCrypto/Hash/HashManager.h>
|
||||||
|
#include <LibCrypto/PK/RSA.h>
|
||||||
|
#include <LibTLS/CipherSuite.h>
|
||||||
|
#include <LibTLS/TLSPacketBuilder.h>
|
||||||
|
|
||||||
struct WOLFSSL_CTX;
|
|
||||||
struct WOLFSSL;
|
|
||||||
namespace TLS {
|
namespace TLS {
|
||||||
|
|
||||||
class WolfTLS final : public Core::Socket {
|
inline void print_buffer(ReadonlyBytes buffer)
|
||||||
public:
|
{
|
||||||
WolfTLS(WOLFSSL_CTX* context, WOLFSSL* ssl, NonnullOwnPtr<Core::TCPSocket> underlying)
|
dbgln("{:hex-dump}", buffer);
|
||||||
: m_context(context)
|
}
|
||||||
, m_ssl(ssl)
|
|
||||||
, m_underlying(move(underlying))
|
inline void print_buffer(ByteBuffer const& buffer)
|
||||||
|
{
|
||||||
|
print_buffer(buffer.bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void print_buffer(u8 const* buffer, size_t size)
|
||||||
|
{
|
||||||
|
print_buffer(ReadonlyBytes { buffer, size });
|
||||||
|
}
|
||||||
|
|
||||||
|
class Socket;
|
||||||
|
|
||||||
|
enum class Error : i8 {
|
||||||
|
NoError = 0,
|
||||||
|
UnknownError = -1,
|
||||||
|
BrokenPacket = -2,
|
||||||
|
NotUnderstood = -3,
|
||||||
|
NoCommonCipher = -5,
|
||||||
|
UnexpectedMessage = -6,
|
||||||
|
CloseConnection = -7,
|
||||||
|
CompressionNotSupported = -8,
|
||||||
|
NotVerified = -9,
|
||||||
|
NotSafe = -10,
|
||||||
|
IntegrityCheckFailed = -11,
|
||||||
|
ErrorAlert = -12,
|
||||||
|
BrokenConnection = -13,
|
||||||
|
BadCertificate = -14,
|
||||||
|
UnsupportedCertificate = -15,
|
||||||
|
NoRenegotiation = -16,
|
||||||
|
FeatureNotSupported = -17,
|
||||||
|
DecryptionFailed = -20,
|
||||||
|
NeedMoreData = -21,
|
||||||
|
TimedOut = -22,
|
||||||
|
OutOfMemory = -23,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class WritePacketStage {
|
||||||
|
Initial = 0,
|
||||||
|
ClientHandshake = 1,
|
||||||
|
ServerHandshake = 2,
|
||||||
|
Finished = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ConnectionStatus {
|
||||||
|
Disconnected,
|
||||||
|
Negotiating,
|
||||||
|
KeyExchange,
|
||||||
|
Renegotiating,
|
||||||
|
Established,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ClientVerificationStaus {
|
||||||
|
Verified,
|
||||||
|
VerificationNeeded,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note for the 16 iv length instead of 8:
|
||||||
|
// 4 bytes of fixed IV, 8 random (nonce) bytes, 4 bytes for counter
|
||||||
|
// GCM specifically asks us to transmit only the nonce, the counter is zero
|
||||||
|
// and the fixed IV is derived from the premaster key.
|
||||||
|
//
|
||||||
|
// The cipher suite list below is ordered based on the recommendations from Mozilla.
|
||||||
|
// When changing the supported cipher suites, please consult the webpage below for
|
||||||
|
// the preferred order.
|
||||||
|
//
|
||||||
|
// https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||||
|
#define ENUMERATE_CIPHERS(C) \
|
||||||
|
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||||
|
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||||
|
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||||
|
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||||
|
C(true, CipherSuite::TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||||
|
C(true, CipherSuite::TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||||
|
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||||
|
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||||
|
C(true, CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::ECDHE_ECDSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||||
|
C(true, CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||||
|
C(true, CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||||
|
C(true, CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||||
|
C(true, CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA256, 16, false) \
|
||||||
|
C(true, CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA256, 16, false) \
|
||||||
|
C(true, CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||||
|
C(true, CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false)
|
||||||
|
|
||||||
|
constexpr KeyExchangeAlgorithm get_key_exchange_algorithm(CipherSuite suite)
|
||||||
|
{
|
||||||
|
switch (suite) {
|
||||||
|
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||||
|
case suite: \
|
||||||
|
return key_exchange;
|
||||||
|
ENUMERATE_CIPHERS(C)
|
||||||
|
#undef C
|
||||||
|
default:
|
||||||
|
return KeyExchangeAlgorithm::Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr CipherAlgorithm get_cipher_algorithm(CipherSuite suite)
|
||||||
|
{
|
||||||
|
switch (suite) {
|
||||||
|
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||||
|
case suite: \
|
||||||
|
return cipher;
|
||||||
|
ENUMERATE_CIPHERS(C)
|
||||||
|
#undef C
|
||||||
|
default:
|
||||||
|
return CipherAlgorithm::Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Options {
|
||||||
|
static Vector<CipherSuite> default_usable_cipher_suites()
|
||||||
{
|
{
|
||||||
m_underlying->on_ready_to_read = [this] {
|
Vector<CipherSuite> cipher_suites;
|
||||||
if (on_ready_to_read)
|
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||||
on_ready_to_read();
|
if constexpr (is_supported) \
|
||||||
};
|
cipher_suites.empend(suite);
|
||||||
|
ENUMERATE_CIPHERS(C)
|
||||||
|
#undef C
|
||||||
|
return cipher_suites;
|
||||||
|
}
|
||||||
|
Vector<CipherSuite> usable_cipher_suites = default_usable_cipher_suites();
|
||||||
|
|
||||||
|
#define OPTION_WITH_DEFAULTS(typ, name, ...) \
|
||||||
|
static typ default_##name() \
|
||||||
|
{ \
|
||||||
|
return typ { __VA_ARGS__ }; \
|
||||||
|
} \
|
||||||
|
typ name = default_##name(); \
|
||||||
|
Options& set_##name(typ new_value)& \
|
||||||
|
{ \
|
||||||
|
name = move(new_value); \
|
||||||
|
return *this; \
|
||||||
|
} \
|
||||||
|
Options&& set_##name(typ new_value)&& \
|
||||||
|
{ \
|
||||||
|
name = move(new_value); \
|
||||||
|
return move(*this); \
|
||||||
}
|
}
|
||||||
|
|
||||||
~WolfTLS();
|
OPTION_WITH_DEFAULTS(ProtocolVersion, version, ProtocolVersion::VERSION_1_2)
|
||||||
|
OPTION_WITH_DEFAULTS(Vector<SignatureAndHashAlgorithm>, supported_signature_algorithms,
|
||||||
|
{ HashAlgorithm::SHA512, SignatureAlgorithm::RSA },
|
||||||
|
{ HashAlgorithm::SHA384, SignatureAlgorithm::RSA },
|
||||||
|
{ HashAlgorithm::SHA256, SignatureAlgorithm::RSA },
|
||||||
|
{ HashAlgorithm::SHA1, SignatureAlgorithm::RSA },
|
||||||
|
{ HashAlgorithm::SHA256, SignatureAlgorithm::ECDSA },
|
||||||
|
{ HashAlgorithm::SHA384, SignatureAlgorithm::ECDSA },
|
||||||
|
{ HashAlgorithm::INTRINSIC, SignatureAlgorithm::ED25519 });
|
||||||
|
OPTION_WITH_DEFAULTS(Vector<SupportedGroup>, elliptic_curves,
|
||||||
|
SupportedGroup::X25519,
|
||||||
|
SupportedGroup::SECP256R1,
|
||||||
|
SupportedGroup::SECP384R1,
|
||||||
|
SupportedGroup::X448)
|
||||||
|
OPTION_WITH_DEFAULTS(Vector<ECPointFormat>, supported_ec_point_formats, ECPointFormat::UNCOMPRESSED)
|
||||||
|
|
||||||
static void install_certificate_store_paths(Vector<ByteString>);
|
OPTION_WITH_DEFAULTS(bool, use_sni, true)
|
||||||
|
OPTION_WITH_DEFAULTS(bool, use_compression, false)
|
||||||
|
OPTION_WITH_DEFAULTS(bool, validate_certificates, true)
|
||||||
|
OPTION_WITH_DEFAULTS(bool, allow_self_signed_certificates, false)
|
||||||
|
OPTION_WITH_DEFAULTS(Optional<Vector<Certificate>>, root_certificates, )
|
||||||
|
OPTION_WITH_DEFAULTS(Function<void(AlertDescription)>, alert_handler, [](auto) {})
|
||||||
|
OPTION_WITH_DEFAULTS(Function<void()>, finish_callback, [] {})
|
||||||
|
OPTION_WITH_DEFAULTS(Function<Vector<Certificate>()>, certificate_provider, [] { return Vector<Certificate> {}; })
|
||||||
|
OPTION_WITH_DEFAULTS(bool, enable_extended_master_secret, true)
|
||||||
|
|
||||||
static ErrorOr<NonnullOwnPtr<WolfTLS>> connect(ByteString const& host, u16 port);
|
#undef OPTION_WITH_DEFAULTS
|
||||||
ErrorOr<Bytes> read_some(Bytes bytes) override;
|
};
|
||||||
ErrorOr<size_t> write_some(ReadonlyBytes bytes) override;
|
|
||||||
bool is_eof() const override;
|
class SegmentedBuffer {
|
||||||
bool is_open() const override;
|
public:
|
||||||
void close() override;
|
[[nodiscard]] size_t size() const { return m_size; }
|
||||||
ErrorOr<size_t> pending_bytes() const override;
|
[[nodiscard]] bool is_empty() const { return m_size == 0; }
|
||||||
ErrorOr<bool> can_read_without_blocking(int timeout) const override;
|
void transfer(Bytes dest, size_t size)
|
||||||
ErrorOr<void> set_blocking(bool enabled) override;
|
{
|
||||||
ErrorOr<void> set_close_on_exec(bool enabled) override { return m_underlying->set_close_on_exec(enabled); }
|
VERIFY(size <= dest.size());
|
||||||
void set_notifications_enabled(bool enabled) override { m_underlying->set_notifications_enabled(enabled); }
|
size_t transferred = 0;
|
||||||
|
while (transferred < size) {
|
||||||
|
auto& buffer = m_buffers.head();
|
||||||
|
size_t to_transfer = min(buffer.size() - m_offset_into_current_buffer, size - transferred);
|
||||||
|
memcpy(dest.offset(transferred), buffer.data() + m_offset_into_current_buffer, to_transfer);
|
||||||
|
transferred += to_transfer;
|
||||||
|
m_offset_into_current_buffer += to_transfer;
|
||||||
|
if (m_offset_into_current_buffer >= buffer.size()) {
|
||||||
|
m_buffers.dequeue();
|
||||||
|
m_offset_into_current_buffer = 0;
|
||||||
|
}
|
||||||
|
m_size -= to_transfer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AK::ErrorOr<void> try_append(ReadonlyBytes data)
|
||||||
|
{
|
||||||
|
if (Checked<size_t>::addition_would_overflow(m_size, data.size()))
|
||||||
|
return AK::Error::from_errno(EOVERFLOW);
|
||||||
|
|
||||||
|
m_size += data.size();
|
||||||
|
m_buffers.enqueue(TRY(ByteBuffer::copy(data)));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static StringView error_text(WOLFSSL*, int error_code);
|
size_t m_size { 0 };
|
||||||
|
Queue<ByteBuffer> m_buffers;
|
||||||
WOLFSSL_CTX* m_context { nullptr };
|
size_t m_offset_into_current_buffer { 0 };
|
||||||
WOLFSSL* m_ssl { nullptr };
|
|
||||||
NonnullOwnPtr<Core::TCPSocket> m_underlying;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
bool verify_chain(StringView host) const;
|
||||||
|
bool verify_certificate_pair(Certificate const& subject, Certificate const& issuer) const;
|
||||||
|
|
||||||
|
Options options;
|
||||||
|
|
||||||
|
u8 remote_random[32];
|
||||||
|
u8 local_random[32];
|
||||||
|
u8 session_id[32];
|
||||||
|
u8 session_id_size { 0 };
|
||||||
|
CipherSuite cipher;
|
||||||
|
bool is_server { false };
|
||||||
|
Vector<Certificate> certificates;
|
||||||
|
Certificate private_key;
|
||||||
|
Vector<Certificate> client_certificates;
|
||||||
|
ByteBuffer master_key;
|
||||||
|
ByteBuffer premaster_key;
|
||||||
|
u8 cipher_spec_set { 0 };
|
||||||
|
struct {
|
||||||
|
int created { 0 };
|
||||||
|
u8 remote_mac[32];
|
||||||
|
u8 local_mac[32];
|
||||||
|
u8 local_iv[16];
|
||||||
|
u8 remote_iv[16];
|
||||||
|
u8 local_aead_iv[4];
|
||||||
|
u8 remote_aead_iv[4];
|
||||||
|
} crypto;
|
||||||
|
|
||||||
|
Crypto::Hash::Manager handshake_hash;
|
||||||
|
|
||||||
|
ByteBuffer message_buffer;
|
||||||
|
u64 remote_sequence_number { 0 };
|
||||||
|
u64 local_sequence_number { 0 };
|
||||||
|
|
||||||
|
ConnectionStatus connection_status { ConnectionStatus::Disconnected };
|
||||||
|
bool should_expect_successful_read { false };
|
||||||
|
u8 critical_error { 0 };
|
||||||
|
Error error_code { Error::NoError };
|
||||||
|
|
||||||
|
ByteBuffer tls_buffer;
|
||||||
|
|
||||||
|
SegmentedBuffer application_buffer;
|
||||||
|
|
||||||
|
bool is_child { false };
|
||||||
|
|
||||||
|
struct {
|
||||||
|
// Server Name Indicator
|
||||||
|
ByteString SNI; // I hate your existence
|
||||||
|
bool extended_master_secret { false };
|
||||||
|
} extensions;
|
||||||
|
|
||||||
|
u8 request_client_certificate { 0 };
|
||||||
|
|
||||||
|
ByteBuffer cached_handshake;
|
||||||
|
|
||||||
|
ClientVerificationStaus client_verified { Verified };
|
||||||
|
|
||||||
|
bool connection_finished { false };
|
||||||
|
bool close_notify { false };
|
||||||
|
bool has_invoked_finish_or_error_callback { false };
|
||||||
|
|
||||||
|
// message flags
|
||||||
|
u8 handshake_messages[11] { 0 };
|
||||||
|
ByteBuffer user_data;
|
||||||
|
HashMap<ByteString, Certificate> root_certificates;
|
||||||
|
|
||||||
|
Vector<ByteString> alpn;
|
||||||
|
StringView negotiated_alpn;
|
||||||
|
|
||||||
|
size_t send_retries { 0 };
|
||||||
|
|
||||||
|
time_t handshake_initiation_timestamp { 0 };
|
||||||
|
|
||||||
|
struct {
|
||||||
|
ByteBuffer p;
|
||||||
|
ByteBuffer g;
|
||||||
|
ByteBuffer Ys;
|
||||||
|
} server_diffie_hellman_params;
|
||||||
|
|
||||||
|
OwnPtr<Crypto::Curves::EllipticCurve> server_key_exchange_curve;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TLSv12 final : public Core::Socket {
|
||||||
|
private:
|
||||||
|
Core::Socket& underlying_stream()
|
||||||
|
{
|
||||||
|
return *m_stream.visit([&](auto& stream) -> Core::Socket* { return stream; });
|
||||||
|
}
|
||||||
|
Core::Socket const& underlying_stream() const
|
||||||
|
{
|
||||||
|
return *m_stream.visit([&](auto& stream) -> Core::Socket const* { return stream; });
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Reads into a buffer, with the maximum size being the size of the buffer.
|
||||||
|
/// The amount of bytes read can be smaller than the size of the buffer.
|
||||||
|
/// Returns either the bytes that were read, or an errno in the case of
|
||||||
|
/// failure.
|
||||||
|
virtual ErrorOr<Bytes> read_some(Bytes) override;
|
||||||
|
|
||||||
|
/// Tries to write the entire contents of the buffer. It is possible for
|
||||||
|
/// less than the full buffer to be written. Returns either the amount of
|
||||||
|
/// bytes written into the stream, or an errno in the case of failure.
|
||||||
|
virtual ErrorOr<size_t> write_some(ReadonlyBytes) override;
|
||||||
|
|
||||||
|
virtual bool is_eof() const override { return m_context.application_buffer.is_empty() && (m_context.connection_finished || underlying_stream().is_eof()); }
|
||||||
|
|
||||||
|
virtual bool is_open() const override { return is_established(); }
|
||||||
|
virtual void close() override;
|
||||||
|
|
||||||
|
virtual ErrorOr<size_t> pending_bytes() const override { return m_context.application_buffer.size(); }
|
||||||
|
virtual ErrorOr<bool> can_read_without_blocking(int = 0) const override { return !m_context.application_buffer.is_empty(); }
|
||||||
|
virtual ErrorOr<void> set_blocking(bool block) override
|
||||||
|
{
|
||||||
|
VERIFY(!block);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
virtual ErrorOr<void> set_close_on_exec(bool enabled) override { return underlying_stream().set_close_on_exec(enabled); }
|
||||||
|
|
||||||
|
virtual void set_notifications_enabled(bool enabled) override { underlying_stream().set_notifications_enabled(enabled); }
|
||||||
|
|
||||||
|
static ErrorOr<NonnullOwnPtr<TLSv12>> connect(ByteString const& host, u16 port, Options = {});
|
||||||
|
static ErrorOr<NonnullOwnPtr<TLSv12>> connect(ByteString const& host, Core::Socket& underlying_stream, Options = {});
|
||||||
|
|
||||||
|
using StreamVariantType = Variant<OwnPtr<Core::Socket>, Core::Socket*>;
|
||||||
|
explicit TLSv12(StreamVariantType, Options);
|
||||||
|
|
||||||
|
bool is_established() const { return m_context.connection_status == ConnectionStatus::Established; }
|
||||||
|
|
||||||
|
void set_sni(StringView sni)
|
||||||
|
{
|
||||||
|
if (m_context.is_server || m_context.critical_error || m_context.connection_status != ConnectionStatus::Disconnected) {
|
||||||
|
dbgln("invalid state for set_sni");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_context.extensions.SNI = sni;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_root_certificates(Vector<Certificate>);
|
||||||
|
|
||||||
|
static Vector<Certificate> parse_pem_certificate(ReadonlyBytes certificate_pem_buffer, ReadonlyBytes key_pem_buffer);
|
||||||
|
|
||||||
|
StringView alpn() const { return m_context.negotiated_alpn; }
|
||||||
|
|
||||||
|
bool supports_cipher(CipherSuite suite) const
|
||||||
|
{
|
||||||
|
switch (suite) {
|
||||||
|
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||||
|
case suite: \
|
||||||
|
return is_supported;
|
||||||
|
ENUMERATE_CIPHERS(C)
|
||||||
|
#undef C
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool supports_version(ProtocolVersion v) const
|
||||||
|
{
|
||||||
|
return v == ProtocolVersion::VERSION_1_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void alert(AlertLevel, AlertDescription);
|
||||||
|
|
||||||
|
Function<void(AlertDescription)> on_tls_error;
|
||||||
|
Function<void()> on_tls_finished;
|
||||||
|
Function<void(TLSv12&)> on_tls_certificate_request;
|
||||||
|
Function<void()> on_connected;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setup_connection();
|
||||||
|
|
||||||
|
void consume(ReadonlyBytes record);
|
||||||
|
|
||||||
|
ByteBuffer hmac_message(ReadonlyBytes buf, Optional<ReadonlyBytes> const buf2, size_t mac_length, bool local = false);
|
||||||
|
void ensure_hmac(size_t digest_size, bool local);
|
||||||
|
|
||||||
|
void update_packet(ByteBuffer& packet);
|
||||||
|
void update_hash(ReadonlyBytes in, size_t header_size);
|
||||||
|
|
||||||
|
void write_packet(ByteBuffer& packet, bool immediately = false);
|
||||||
|
|
||||||
|
ByteBuffer build_client_key_exchange();
|
||||||
|
ByteBuffer build_server_key_exchange();
|
||||||
|
|
||||||
|
ByteBuffer build_hello();
|
||||||
|
ByteBuffer build_handshake_finished();
|
||||||
|
ByteBuffer build_certificate();
|
||||||
|
ByteBuffer build_alert(bool critical, u8 code);
|
||||||
|
ByteBuffer build_change_cipher_spec();
|
||||||
|
void build_rsa_pre_master_secret(PacketBuilder&);
|
||||||
|
void build_dhe_rsa_pre_master_secret(PacketBuilder&);
|
||||||
|
void build_ecdhe_rsa_pre_master_secret(PacketBuilder&);
|
||||||
|
|
||||||
|
ErrorOr<bool> flush();
|
||||||
|
void write_into_socket();
|
||||||
|
ErrorOr<void> read_from_socket();
|
||||||
|
|
||||||
|
bool check_connection_state(bool read);
|
||||||
|
void notify_client_for_app_data();
|
||||||
|
|
||||||
|
ssize_t handle_server_hello(ReadonlyBytes, WritePacketStage&);
|
||||||
|
ssize_t handle_handshake_finished(ReadonlyBytes, WritePacketStage&);
|
||||||
|
ssize_t handle_certificate(ReadonlyBytes);
|
||||||
|
ssize_t handle_server_key_exchange(ReadonlyBytes);
|
||||||
|
ssize_t handle_dhe_rsa_server_key_exchange(ReadonlyBytes);
|
||||||
|
ssize_t handle_ecdhe_server_key_exchange(ReadonlyBytes, u8& server_public_key_length);
|
||||||
|
ssize_t handle_ecdhe_rsa_server_key_exchange(ReadonlyBytes);
|
||||||
|
ssize_t handle_ecdhe_ecdsa_server_key_exchange(ReadonlyBytes);
|
||||||
|
ssize_t handle_server_hello_done(ReadonlyBytes);
|
||||||
|
ssize_t handle_certificate_verify(ReadonlyBytes);
|
||||||
|
ssize_t handle_handshake_payload(ReadonlyBytes);
|
||||||
|
ssize_t handle_message(ReadonlyBytes);
|
||||||
|
|
||||||
|
void pseudorandom_function(Bytes output, ReadonlyBytes secret, u8 const* label, size_t label_length, ReadonlyBytes seed, ReadonlyBytes seed_b);
|
||||||
|
|
||||||
|
ssize_t verify_rsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer);
|
||||||
|
ssize_t verify_ecdsa_server_key_exchange(ReadonlyBytes server_key_info_buffer, ReadonlyBytes signature_buffer);
|
||||||
|
|
||||||
|
size_t key_length() const
|
||||||
|
{
|
||||||
|
switch (m_context.cipher) {
|
||||||
|
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||||
|
case suite: \
|
||||||
|
return cipher_key_size(cipher) / 8;
|
||||||
|
ENUMERATE_CIPHERS(C)
|
||||||
|
#undef C
|
||||||
|
default:
|
||||||
|
return 128 / 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t mac_length() const
|
||||||
|
{
|
||||||
|
switch (m_context.cipher) {
|
||||||
|
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||||
|
case suite: \
|
||||||
|
return hash ::digest_size();
|
||||||
|
ENUMERATE_CIPHERS(C)
|
||||||
|
#undef C
|
||||||
|
default:
|
||||||
|
return Crypto::Hash::SHA256::digest_size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Crypto::Hash::HashKind hmac_hash() const
|
||||||
|
{
|
||||||
|
switch (mac_length()) {
|
||||||
|
case Crypto::Hash::SHA512::DigestSize:
|
||||||
|
return Crypto::Hash::HashKind::SHA512;
|
||||||
|
case Crypto::Hash::SHA384::DigestSize:
|
||||||
|
return Crypto::Hash::HashKind::SHA384;
|
||||||
|
case Crypto::Hash::SHA256::DigestSize:
|
||||||
|
case Crypto::Hash::SHA1::DigestSize:
|
||||||
|
default:
|
||||||
|
return Crypto::Hash::HashKind::SHA256;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t iv_length() const
|
||||||
|
{
|
||||||
|
switch (m_context.cipher) {
|
||||||
|
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||||
|
case suite: \
|
||||||
|
return iv_size;
|
||||||
|
ENUMERATE_CIPHERS(C)
|
||||||
|
#undef C
|
||||||
|
default:
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_aead() const
|
||||||
|
{
|
||||||
|
switch (m_context.cipher) {
|
||||||
|
#define C(is_supported, suite, key_exchange, cipher, hash, iv_size, is_aead) \
|
||||||
|
case suite: \
|
||||||
|
return is_aead;
|
||||||
|
ENUMERATE_CIPHERS(C)
|
||||||
|
#undef C
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool expand_key();
|
||||||
|
|
||||||
|
bool compute_master_secret_from_pre_master_secret(size_t length);
|
||||||
|
|
||||||
|
void try_disambiguate_error() const;
|
||||||
|
|
||||||
|
bool m_eof { false };
|
||||||
|
StreamVariantType m_stream;
|
||||||
|
Context m_context;
|
||||||
|
|
||||||
|
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_local;
|
||||||
|
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_remote;
|
||||||
|
|
||||||
|
using CipherVariant = Variant<
|
||||||
|
Empty,
|
||||||
|
Crypto::Cipher::AESCipher::CBCMode,
|
||||||
|
Crypto::Cipher::AESCipher::GCMMode>;
|
||||||
|
CipherVariant m_cipher_local {};
|
||||||
|
CipherVariant m_cipher_remote {};
|
||||||
|
|
||||||
|
bool m_has_scheduled_write_flush { false };
|
||||||
|
bool m_has_scheduled_app_data_flush { false };
|
||||||
|
i32 m_max_wait_time_for_handshake_in_seconds { 10 };
|
||||||
|
|
||||||
|
RefPtr<Core::Timer> m_handshake_timeout_timer;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,15 +44,13 @@ void WebSocketImplSerenity::connect(ConnectionInfo const& connection_info)
|
||||||
auto socket_result = [&]() -> ErrorOr<NonnullOwnPtr<Core::BufferedSocketBase>> {
|
auto socket_result = [&]() -> ErrorOr<NonnullOwnPtr<Core::BufferedSocketBase>> {
|
||||||
auto host = TRY(connection_info.url().serialized_host()).to_byte_string();
|
auto host = TRY(connection_info.url().serialized_host()).to_byte_string();
|
||||||
if (connection_info.is_secure()) {
|
if (connection_info.is_secure()) {
|
||||||
auto result = TLS::WolfTLS::connect(host, connection_info.url().port_or_default());
|
TLS::Options options;
|
||||||
if (result.is_error()) {
|
options.set_alert_handler([this](auto) {
|
||||||
Core::deferred_invoke([this] {
|
|
||||||
on_connection_error();
|
on_connection_error();
|
||||||
});
|
});
|
||||||
return result.release_error();
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRY(Core::BufferedSocket<TLS::WolfTLS>::create(result.release_value()));
|
return TRY(Core::BufferedSocket<TLS::TLSv12>::create(
|
||||||
|
TRY(TLS::TLSv12::connect(host, connection_info.url().port_or_default(), move(options)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRY(Core::BufferedTCPSocket::create(
|
return TRY(Core::BufferedTCPSocket::create(
|
||||||
|
|
|
@ -616,8 +616,7 @@ void WebSocket::fatal_error(WebSocket::Error error)
|
||||||
void WebSocket::discard_connection()
|
void WebSocket::discard_connection()
|
||||||
{
|
{
|
||||||
deferred_invoke([this] {
|
deferred_invoke([this] {
|
||||||
if (!m_impl)
|
VERIFY(m_impl);
|
||||||
return;
|
|
||||||
m_impl->discard_connection();
|
m_impl->discard_connection();
|
||||||
m_impl->on_connection_error = nullptr;
|
m_impl->on_connection_error = nullptr;
|
||||||
m_impl->on_connected = nullptr;
|
m_impl->on_connected = nullptr;
|
||||||
|
|
|
@ -19,5 +19,4 @@ set(GENERATED_SOURCES
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_bin(RequestServer)
|
serenity_bin(RequestServer)
|
||||||
|
|
||||||
target_link_libraries(RequestServer PRIVATE LibCore LibCrypto LibIPC LibHTTP LibMain LibTLS LibWebSocket LibURL LibThreading)
|
target_link_libraries(RequestServer PRIVATE LibCore LibCrypto LibIPC LibHTTP LibMain LibTLS LibWebSocket LibURL LibThreading)
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
namespace RequestServer::ConnectionCache {
|
namespace RequestServer::ConnectionCache {
|
||||||
|
|
||||||
Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache {};
|
Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache {};
|
||||||
Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::WolfTLS>>>>>> g_tls_connection_cache {};
|
Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::TLSv12>>>>>> g_tls_connection_cache {};
|
||||||
Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties;
|
Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties;
|
||||||
|
|
||||||
void request_did_finish(URL::URL const& url, Core::Socket const* socket)
|
void request_did_finish(URL::URL const& url, Core::Socket const* socket)
|
||||||
|
@ -121,7 +121,7 @@ void request_did_finish(URL::URL const& url, Core::Socket const* socket)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (is<Core::BufferedSocket<TLS::WolfTLS>>(socket))
|
if (is<Core::BufferedSocket<TLS::TLSv12>>(socket))
|
||||||
fire_off_next_job(g_tls_connection_cache);
|
fire_off_next_job(g_tls_connection_cache);
|
||||||
else if (is<Core::BufferedSocket<Core::Socket>>(socket))
|
else if (is<Core::BufferedSocket<Core::Socket>>(socket))
|
||||||
fire_off_next_job(g_tcp_connection_cache);
|
fire_off_next_job(g_tcp_connection_cache);
|
||||||
|
|
|
@ -57,6 +57,7 @@ struct Proxy {
|
||||||
struct JobData {
|
struct JobData {
|
||||||
Function<void(Core::BufferedSocketBase&)> start {};
|
Function<void(Core::BufferedSocketBase&)> start {};
|
||||||
Function<void(Core::NetworkJob::Error)> fail {};
|
Function<void(Core::NetworkJob::Error)> fail {};
|
||||||
|
Function<Vector<TLS::Certificate>()> provide_client_certificates {};
|
||||||
struct TimingInfo {
|
struct TimingInfo {
|
||||||
#if REQUESTSERVER_DEBUG
|
#if REQUESTSERVER_DEBUG
|
||||||
bool valid { true };
|
bool valid { true };
|
||||||
|
@ -68,9 +69,10 @@ struct JobData {
|
||||||
#endif
|
#endif
|
||||||
} timing_info {};
|
} timing_info {};
|
||||||
|
|
||||||
JobData(Function<void(Core::BufferedSocketBase&)> start, Function<void(Core::NetworkJob::Error)> fail, TimingInfo timing_info)
|
JobData(Function<void(Core::BufferedSocketBase&)> start, Function<void(Core::NetworkJob::Error)> fail, Function<Vector<TLS::Certificate>()> provide_client_certificates, TimingInfo timing_info)
|
||||||
: start(move(start))
|
: start(move(start))
|
||||||
, fail(move(fail))
|
, fail(move(fail))
|
||||||
|
, provide_client_certificates(move(provide_client_certificates))
|
||||||
, timing_info(move(timing_info))
|
, timing_info(move(timing_info))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -78,6 +80,7 @@ struct JobData {
|
||||||
JobData(JobData&& other)
|
JobData(JobData&& other)
|
||||||
: start(move(other.start))
|
: start(move(other.start))
|
||||||
, fail(move(other.fail))
|
, fail(move(other.fail))
|
||||||
|
, provide_client_certificates(move(other.provide_client_certificates))
|
||||||
, timing_info(move(other.timing_info))
|
, timing_info(move(other.timing_info))
|
||||||
{
|
{
|
||||||
#if REQUESTSERVER_DEBUG
|
#if REQUESTSERVER_DEBUG
|
||||||
|
@ -103,6 +106,16 @@ struct JobData {
|
||||||
return JobData {
|
return JobData {
|
||||||
[job](auto& socket) { job->start(socket); },
|
[job](auto& socket) { job->start(socket); },
|
||||||
[job](auto error) { job->fail(error); },
|
[job](auto error) { job->fail(error); },
|
||||||
|
[job] {
|
||||||
|
if constexpr (requires { job->on_certificate_requested; }) {
|
||||||
|
if (job->on_certificate_requested)
|
||||||
|
return job->on_certificate_requested();
|
||||||
|
} else {
|
||||||
|
// "use" `job`, otherwise clang gets sad.
|
||||||
|
(void)job;
|
||||||
|
}
|
||||||
|
return Vector<TLS::Certificate> {};
|
||||||
|
},
|
||||||
{
|
{
|
||||||
#if REQUESTSERVER_DEBUG
|
#if REQUESTSERVER_DEBUG
|
||||||
.timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise),
|
.timer = Core::ElapsedTimer::start_new(Core::TimerType::Precise),
|
||||||
|
@ -142,7 +155,7 @@ struct ConnectionKey {
|
||||||
bool operator==(ConnectionKey const&) const = default;
|
bool operator==(ConnectionKey const&) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
struct AK::Traits<RequestServer::ConnectionCache::ConnectionKey> : public AK::DefaultTraits<RequestServer::ConnectionCache::ConnectionKey> {
|
struct AK::Traits<RequestServer::ConnectionCache::ConnectionKey> : public AK::DefaultTraits<RequestServer::ConnectionCache::ConnectionKey> {
|
||||||
|
@ -159,7 +172,7 @@ struct InferredServerProperties {
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache;
|
extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<Core::TCPSocket, Core::Socket>>>>>> g_tcp_connection_cache;
|
||||||
extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::WolfTLS>>>>>> g_tls_connection_cache;
|
extern Threading::RWLockProtected<HashMap<ConnectionKey, NonnullOwnPtr<Vector<NonnullOwnPtr<Connection<TLS::TLSv12>>>>>> g_tls_connection_cache;
|
||||||
extern Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties;
|
extern Threading::RWLockProtected<HashMap<ByteString, InferredServerProperties>> g_inferred_server_properties;
|
||||||
|
|
||||||
void request_did_finish(URL::URL const&, Core::Socket const*);
|
void request_did_finish(URL::URL const&, Core::Socket const*);
|
||||||
|
@ -183,7 +196,29 @@ ErrorOr<void> recreate_socket_if_needed(T& connection, URL::URL const& url)
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if constexpr (IsSame<TLS::TLSv12, SocketType>) {
|
||||||
|
TLS::Options options;
|
||||||
|
options.set_alert_handler([&connection](TLS::AlertDescription alert) {
|
||||||
|
Core::NetworkJob::Error reason;
|
||||||
|
if (alert == TLS::AlertDescription::HANDSHAKE_FAILURE)
|
||||||
|
reason = Core::NetworkJob::Error::ProtocolFailed;
|
||||||
|
else if (alert == TLS::AlertDescription::DECRYPT_ERROR)
|
||||||
|
reason = Core::NetworkJob::Error::ConnectionFailed;
|
||||||
|
else
|
||||||
|
reason = Core::NetworkJob::Error::TransmissionFailed;
|
||||||
|
|
||||||
|
if (connection.job_data->fail)
|
||||||
|
connection.job_data->fail(reason);
|
||||||
|
});
|
||||||
|
options.set_certificate_provider([&connection]() -> Vector<TLS::Certificate> {
|
||||||
|
if (connection.job_data->provide_client_certificates)
|
||||||
|
return connection.job_data->provide_client_certificates();
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
TRY(set_socket(TRY((connection.proxy.template tunnel<SocketType, SocketStorageType>(url, move(options))))));
|
||||||
|
} else {
|
||||||
TRY(set_socket(TRY((connection.proxy.template tunnel<SocketType, SocketStorageType>(url)))));
|
TRY(set_socket(TRY((connection.proxy.template tunnel<SocketType, SocketStorageType>(url)))));
|
||||||
|
}
|
||||||
dbgln_if(REQUESTSERVER_DEBUG, "Creating a new socket for {} -> {}", url, connection.socket.ptr());
|
dbgln_if(REQUESTSERVER_DEBUG, "Creating a new socket for {} -> {}", url, connection.socket.ptr());
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -26,6 +26,10 @@ ErrorOr<int> serenity_main(Main::Arguments)
|
||||||
|
|
||||||
TRY(Core::System::pledge("stdio inet accept thread unix rpath sendfd recvfd"));
|
TRY(Core::System::pledge("stdio inet accept thread unix rpath sendfd recvfd"));
|
||||||
|
|
||||||
|
// Ensure the certificates are read out here.
|
||||||
|
// FIXME: Allow specifying extra certificates on the command line, or in other configuration.
|
||||||
|
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
|
||||||
|
|
||||||
Core::EventLoop event_loop;
|
Core::EventLoop event_loop;
|
||||||
// FIXME: Establish a connection to LookupServer and then drop "unix"?
|
// FIXME: Establish a connection to LookupServer and then drop "unix"?
|
||||||
TRY(Core::System::unveil("/tmp/portal/lookup", "rw"));
|
TRY(Core::System::unveil("/tmp/portal/lookup", "rw"));
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
},
|
},
|
||||||
"sqlite3",
|
"sqlite3",
|
||||||
"woff2",
|
"woff2",
|
||||||
"wolfssl",
|
|
||||||
{
|
{
|
||||||
"name": "vulkan",
|
"name": "vulkan",
|
||||||
"platform": "!android"
|
"platform": "!android"
|
||||||
|
@ -71,10 +70,6 @@
|
||||||
{
|
{
|
||||||
"name": "woff2",
|
"name": "woff2",
|
||||||
"version": "1.0.2#4"
|
"version": "1.0.2#4"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "wolfssl",
|
|
||||||
"version": "5.7.0#1"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue