mirror of
https://github.com/RGBCube/serenity
synced 2025-06-01 08:28:11 +00:00
LibTLS: Add Elliptic Curve Diffie-Hellman Ephemeral (ECDHE) support
This adds support for the Elliptic Curve Diffie-Hellman Ephemeral key exchange, using the X25519 elliptic curve. This means that the ECDHE_RSA_WITH_AES_128_GCM_SHA256 and ECDHE_RSA_WITH_AES_256_GCM_SHA384 cipher suites are now supported. Currently, only the X25519 elliptic curve is supported in combination with the uncompressed elliptic curve point format. However, since the X25519 is the recommended curve, basically every server supports this. Furthermore, the uncompressed point format is required by the TLS specification, which means any server with EC support will support the uncompressed format. Like the implementation of the normal Diffie-Hellman Ephemeral key exchange, this implementation does not currently validate the signature of the public key sent by the server.
This commit is contained in:
parent
be07892fea
commit
7ab4337721
5 changed files with 176 additions and 10 deletions
|
@ -31,6 +31,10 @@ enum class CipherSuite {
|
|||
DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E,
|
||||
DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F,
|
||||
|
||||
// RFC 5289 - ECDHE for AES-GCM
|
||||
ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F,
|
||||
ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030,
|
||||
|
||||
// All recommended cipher suites (according to https://ciphersuite.info/cs/)
|
||||
|
||||
// RFC 5288 - DH, DHE and RSA for AES-GCM
|
||||
|
@ -185,4 +189,33 @@ constexpr size_t cipher_key_size(CipherAlgorithm algorithm)
|
|||
}
|
||||
}
|
||||
|
||||
enum class NamedCurve : u16 {
|
||||
secp256r1 = 23,
|
||||
secp384r1 = 24,
|
||||
secp521r1 = 25,
|
||||
x25519 = 29,
|
||||
x448 = 30,
|
||||
};
|
||||
|
||||
constexpr size_t named_curve_key_size(NamedCurve group)
|
||||
{
|
||||
switch (group) {
|
||||
case NamedCurve::secp256r1:
|
||||
case NamedCurve::secp384r1:
|
||||
case NamedCurve::secp521r1:
|
||||
// FIXME: Add the correct key size for these elliptic curves
|
||||
return 0;
|
||||
case NamedCurve::x25519:
|
||||
return 256;
|
||||
case NamedCurve::x448:
|
||||
return 448;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
enum class ECPointFormat : u8 {
|
||||
Uncompressed = 0,
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
* Copyright (c) 2022, Michiel Visser <opensource@webmichiel.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -71,12 +72,20 @@ ByteBuffer TLSv12::build_hello()
|
|||
if (!m_context.extensions.SNI.is_null() && 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;
|
||||
|
||||
// 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;
|
||||
|
||||
builder.append((u16)extension_length);
|
||||
|
||||
if (sni_length) {
|
||||
|
@ -105,6 +114,22 @@ ByteBuffer TLSv12::build_hello()
|
|||
builder.append((u8)entry.signature);
|
||||
}
|
||||
|
||||
if (supports_elliptic_curves) {
|
||||
// elliptic_curves extension
|
||||
builder.append((u16)HandshakeExtension::EllipticCurves);
|
||||
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)HandshakeExtension::ECPointFormats);
|
||||
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 (alpn_length) {
|
||||
// TODO
|
||||
VERIFY_NOT_REACHED();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
* Copyright (c) 2022, Michiel Visser <opensource@webmichiel.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -9,6 +10,7 @@
|
|||
#include <AK/Random.h>
|
||||
#include <LibCrypto/ASN1/DER.h>
|
||||
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
||||
#include <LibCrypto/Curves/X25519.h>
|
||||
#include <LibCrypto/NumberTheory/ModularFunctions.h>
|
||||
#include <LibCrypto/PK/Code/EMSA_PSS.h>
|
||||
#include <LibTLS/TLSv12.h>
|
||||
|
@ -292,6 +294,51 @@ void TLSv12::build_dhe_rsa_pre_master_secret(PacketBuilder& builder)
|
|||
builder.append(dh_Yc_bytes);
|
||||
}
|
||||
|
||||
void TLSv12::build_ecdhe_rsa_pre_master_secret(PacketBuilder& builder)
|
||||
{
|
||||
size_t const key_size = named_curve_key_size(NamedCurve::x25519) / 8;
|
||||
u8 generator_point[key_size] { 9 };
|
||||
ReadonlyBytes generator_point_bytes { generator_point, key_size };
|
||||
|
||||
// Create a random private key
|
||||
u8 private_key[key_size];
|
||||
fill_with_random(private_key, key_size);
|
||||
ReadonlyBytes private_key_bytes { private_key, key_size };
|
||||
|
||||
// Calculate the public key by multiplying the private key with 9
|
||||
auto public_key_result = Crypto::Curves::X25519::compute_coordinate(private_key_bytes, generator_point_bytes);
|
||||
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 pre master secret by multiplying the client private key and the server public key
|
||||
ReadonlyBytes server_public_key_bytes = m_context.server_diffie_hellman_params.p;
|
||||
auto pre_master_secret_result = Crypto::Curves::X25519::compute_coordinate(private_key_bytes, server_public_key_bytes);
|
||||
if (pre_master_secret_result.is_error()) {
|
||||
dbgln("Failed to build ECDHE_RSA premaster secret: not enough memory");
|
||||
return;
|
||||
}
|
||||
m_context.premaster_key = pre_master_secret_result.release_value();
|
||||
|
||||
if constexpr (TLS_DEBUG) {
|
||||
dbgln("Build ECDHE_RSA pre master secret");
|
||||
dbgln("client private key: {:hex-dump}", private_key_bytes);
|
||||
dbgln("client public key: {:hex-dump}", (ReadonlyBytes)public_key);
|
||||
dbgln("premaster key: {:hex-dump}", (ReadonlyBytes)m_context.premaster_key);
|
||||
}
|
||||
|
||||
if (!compute_master_secret_from_pre_master_secret(48)) {
|
||||
dbgln("oh noes we could not derive a master key :(");
|
||||
return;
|
||||
}
|
||||
|
||||
builder.append_u24(key_size + 1);
|
||||
builder.append((u8)key_size);
|
||||
builder.append(public_key);
|
||||
}
|
||||
|
||||
ByteBuffer TLSv12::build_certificate()
|
||||
{
|
||||
PacketBuilder builder { MessageType::Handshake, m_context.options.version };
|
||||
|
@ -371,6 +418,8 @@ ByteBuffer TLSv12::build_client_key_exchange()
|
|||
TODO();
|
||||
break;
|
||||
case KeyExchangeAlgorithm::ECDHE_RSA:
|
||||
build_ecdhe_rsa_pre_master_secret(builder);
|
||||
break;
|
||||
case KeyExchangeAlgorithm::ECDH_ECDSA:
|
||||
case KeyExchangeAlgorithm::ECDH_RSA:
|
||||
case KeyExchangeAlgorithm::ECDHE_ECDSA:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
* Copyright (c) 2022, Michiel Visser <opensource@webmichiel.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -171,6 +172,16 @@ ssize_t TLSv12::handle_server_hello(ReadonlyBytes buffer, WritePacketStage& writ
|
|||
print_buffer(buffer.slice(res, extension_length));
|
||||
res += extension_length;
|
||||
// FIXME: what are we supposed to do here?
|
||||
} else if (extension_type == HandshakeExtension::ECPointFormats) {
|
||||
// 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 {
|
||||
dbgln("Encountered unknown extension {} with length {}", (u16)extension_type, extension_length);
|
||||
res += extension_length;
|
||||
|
@ -221,6 +232,7 @@ ssize_t TLSv12::handle_server_key_exchange(ReadonlyBytes buffer)
|
|||
TODO();
|
||||
break;
|
||||
case KeyExchangeAlgorithm::ECDHE_RSA:
|
||||
return handle_ecdhe_rsa_server_key_exchange(buffer);
|
||||
case KeyExchangeAlgorithm::ECDH_ECDSA:
|
||||
case KeyExchangeAlgorithm::ECDH_RSA:
|
||||
case KeyExchangeAlgorithm::ECDHE_ECDSA:
|
||||
|
@ -276,4 +288,39 @@ ssize_t TLSv12::handle_dhe_rsa_server_key_exchange(ReadonlyBytes buffer)
|
|||
return 0;
|
||||
}
|
||||
|
||||
ssize_t TLSv12::handle_ecdhe_rsa_server_key_exchange(ReadonlyBytes buffer)
|
||||
{
|
||||
auto x25519_key_size_bytes = named_curve_key_size(NamedCurve::x25519) / 8;
|
||||
if (buffer.size() < x25519_key_size_bytes + 7)
|
||||
return (i8)Error::NeedMoreData;
|
||||
|
||||
auto curve_type = buffer[3];
|
||||
if (curve_type != (u8)ECCurveType::NamedCurve)
|
||||
return (i8)Error::FeatureNotSupported;
|
||||
|
||||
auto curve = AK::convert_between_host_and_network_endian(ByteReader::load16(buffer.offset_pointer(4)));
|
||||
if (curve != (u16)NamedCurve::x25519)
|
||||
return (i8)Error::FeatureNotSupported;
|
||||
|
||||
auto server_public_key_length = buffer[6];
|
||||
if (server_public_key_length != x25519_key_size_bytes)
|
||||
return (i8)Error::FeatureNotSupported;
|
||||
|
||||
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 0;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// FIXME: Validate signature of Elliptic Curve Diffie-Hellman public key
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -132,8 +132,10 @@ enum HandshakeType {
|
|||
|
||||
enum class HandshakeExtension : u16 {
|
||||
ServerName = 0x00,
|
||||
ApplicationLayerProtocolNegotiation = 0x10,
|
||||
EllipticCurves = 0x0a,
|
||||
ECPointFormats = 0x0b,
|
||||
SignatureAlgorithms = 0x0d,
|
||||
ApplicationLayerProtocolNegotiation = 0x10,
|
||||
};
|
||||
|
||||
enum class NameType : u8 {
|
||||
|
@ -160,19 +162,25 @@ enum ClientVerificationStaus {
|
|||
VerificationNeeded,
|
||||
};
|
||||
|
||||
enum class ECCurveType : u8 {
|
||||
NamedCurve = 3,
|
||||
};
|
||||
|
||||
// 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.
|
||||
#define ENUMERATE_CIPHERS(C) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_128_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA256, 16, false) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_256_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA256, 16, false) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||
C(true, CipherSuite::DHE_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||
C(true, CipherSuite::DHE_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true)
|
||||
#define ENUMERATE_CIPHERS(C) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_128_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_256_CBC_SHA, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA1, 16, false) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_128_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_CBC, Crypto::Hash::SHA256, 16, false) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_256_CBC_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_CBC, Crypto::Hash::SHA256, 16, false) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||
C(true, CipherSuite::RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||
C(true, CipherSuite::DHE_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||
C(true, CipherSuite::DHE_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::DHE_RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true) \
|
||||
C(true, CipherSuite::ECDHE_RSA_WITH_AES_128_GCM_SHA256, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_128_GCM, Crypto::Hash::SHA256, 8, true) \
|
||||
C(true, CipherSuite::ECDHE_RSA_WITH_AES_256_GCM_SHA384, KeyExchangeAlgorithm::ECDHE_RSA, CipherAlgorithm::AES_256_GCM, Crypto::Hash::SHA384, 8, true)
|
||||
|
||||
constexpr KeyExchangeAlgorithm get_key_exchange_algorithm(CipherSuite suite)
|
||||
{
|
||||
|
@ -233,6 +241,8 @@ struct Options {
|
|||
{ HashAlgorithm::SHA384, SignatureAlgorithm::RSA },
|
||||
{ HashAlgorithm::SHA256, SignatureAlgorithm::RSA },
|
||||
{ HashAlgorithm::SHA1, SignatureAlgorithm::RSA });
|
||||
OPTION_WITH_DEFAULTS(Vector<NamedCurve>, elliptic_curves, NamedCurve::x25519)
|
||||
OPTION_WITH_DEFAULTS(Vector<ECPointFormat>, supported_ec_point_formats, ECPointFormat::Uncompressed)
|
||||
|
||||
OPTION_WITH_DEFAULTS(bool, use_sni, true)
|
||||
OPTION_WITH_DEFAULTS(bool, use_compression, false)
|
||||
|
@ -448,6 +458,7 @@ private:
|
|||
ByteBuffer build_verify_request();
|
||||
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();
|
||||
|
@ -461,6 +472,7 @@ private:
|
|||
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_rsa_server_key_exchange(ReadonlyBytes);
|
||||
ssize_t handle_server_hello_done(ReadonlyBytes);
|
||||
ssize_t handle_certificate_verify(ReadonlyBytes);
|
||||
ssize_t handle_handshake_payload(ReadonlyBytes);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue