mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-25 14:05:15 +00:00
LibTLS: Add support for AEAD cipher suites
And integrate AES-GCM.
This commit is contained in:
parent
d3c52cef86
commit
1172746633
Notes:
sideshowbarker
2024-07-19 02:28:42 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/11727466339 Pull-request: https://github.com/SerenityOS/serenity/pull/4067
4 changed files with 253 additions and 85 deletions
|
@ -35,6 +35,8 @@ bool TLSv12::expand_key()
|
||||||
u8 key[192]; // soooooooo many constants
|
u8 key[192]; // soooooooo many constants
|
||||||
auto key_buffer = ByteBuffer::wrap(key, 192);
|
auto key_buffer = ByteBuffer::wrap(key, 192);
|
||||||
|
|
||||||
|
auto is_aead = this->is_aead();
|
||||||
|
|
||||||
if (m_context.master_key.size() == 0) {
|
if (m_context.master_key.size() == 0) {
|
||||||
dbg() << "expand_key() with empty master key";
|
dbg() << "expand_key() with empty master key";
|
||||||
return false;
|
return false;
|
||||||
|
@ -52,10 +54,14 @@ bool TLSv12::expand_key()
|
||||||
ByteBuffer::wrap(m_context.local_random, 32));
|
ByteBuffer::wrap(m_context.local_random, 32));
|
||||||
|
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
|
if (is_aead) {
|
||||||
|
iv_size = 4; // Explicit IV size.
|
||||||
|
} else {
|
||||||
memcpy(m_context.crypto.local_mac, key + offset, mac_size);
|
memcpy(m_context.crypto.local_mac, key + offset, mac_size);
|
||||||
offset += mac_size;
|
offset += mac_size;
|
||||||
memcpy(m_context.crypto.remote_mac, key + offset, mac_size);
|
memcpy(m_context.crypto.remote_mac, key + offset, mac_size);
|
||||||
offset += mac_size;
|
offset += mac_size;
|
||||||
|
}
|
||||||
|
|
||||||
auto client_key = key + offset;
|
auto client_key = key + offset;
|
||||||
offset += key_size;
|
offset += key_size;
|
||||||
|
@ -75,17 +81,27 @@ bool TLSv12::expand_key()
|
||||||
print_buffer(client_iv, iv_size);
|
print_buffer(client_iv, iv_size);
|
||||||
dbg() << "server iv";
|
dbg() << "server iv";
|
||||||
print_buffer(server_iv, iv_size);
|
print_buffer(server_iv, iv_size);
|
||||||
|
if (!is_aead) {
|
||||||
dbg() << "client mac key";
|
dbg() << "client mac key";
|
||||||
print_buffer(m_context.crypto.local_mac, mac_size);
|
print_buffer(m_context.crypto.local_mac, mac_size);
|
||||||
dbg() << "server mac key";
|
dbg() << "server mac key";
|
||||||
print_buffer(m_context.crypto.remote_mac, mac_size);
|
print_buffer(m_context.crypto.remote_mac, mac_size);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (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_aes_local.gcm = make<Crypto::Cipher::AESCipher::GCMMode>(ByteBuffer::wrap(client_key, key_size), key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246);
|
||||||
|
m_aes_remote.gcm = make<Crypto::Cipher::AESCipher::GCMMode>(ByteBuffer::wrap(server_key, key_size), key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246);
|
||||||
|
} else {
|
||||||
memcpy(m_context.crypto.local_iv, client_iv, iv_size);
|
memcpy(m_context.crypto.local_iv, client_iv, iv_size);
|
||||||
memcpy(m_context.crypto.remote_iv, server_iv, iv_size);
|
memcpy(m_context.crypto.remote_iv, server_iv, iv_size);
|
||||||
|
|
||||||
m_aes_local = make<Crypto::Cipher::AESCipher::CBCMode>(ByteBuffer::wrap(client_key, key_size), key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246);
|
m_aes_local.cbc = make<Crypto::Cipher::AESCipher::CBCMode>(ByteBuffer::wrap(client_key, key_size), key_size * 8, Crypto::Cipher::Intent::Encryption, Crypto::Cipher::PaddingMode::RFC5246);
|
||||||
m_aes_remote = make<Crypto::Cipher::AESCipher::CBCMode>(ByteBuffer::wrap(server_key, key_size), key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246);
|
m_aes_remote.cbc = make<Crypto::Cipher::AESCipher::CBCMode>(ByteBuffer::wrap(server_key, key_size), key_size * 8, Crypto::Cipher::Intent::Decryption, Crypto::Cipher::PaddingMode::RFC5246);
|
||||||
|
}
|
||||||
|
|
||||||
m_context.crypto.created = 1;
|
m_context.crypto.created = 1;
|
||||||
|
|
||||||
|
|
|
@ -73,11 +73,12 @@ ByteBuffer TLSv12::build_hello()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ciphers
|
// Ciphers
|
||||||
builder.append((u16)(4 * sizeof(u16)));
|
builder.append((u16)(5 * sizeof(u16)));
|
||||||
builder.append((u16)CipherSuite::RSA_WITH_AES_128_CBC_SHA256);
|
builder.append((u16)CipherSuite::RSA_WITH_AES_128_CBC_SHA256);
|
||||||
builder.append((u16)CipherSuite::RSA_WITH_AES_256_CBC_SHA256);
|
builder.append((u16)CipherSuite::RSA_WITH_AES_256_CBC_SHA256);
|
||||||
builder.append((u16)CipherSuite::RSA_WITH_AES_128_CBC_SHA);
|
builder.append((u16)CipherSuite::RSA_WITH_AES_128_CBC_SHA);
|
||||||
builder.append((u16)CipherSuite::RSA_WITH_AES_256_CBC_SHA);
|
builder.append((u16)CipherSuite::RSA_WITH_AES_256_CBC_SHA);
|
||||||
|
builder.append((u16)CipherSuite::RSA_WITH_AES_128_GCM_SHA256);
|
||||||
|
|
||||||
// we don't like compression
|
// we don't like compression
|
||||||
builder.append((u8)1);
|
builder.append((u8)1);
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include <AK/Endian.h>
|
#include <AK/Endian.h>
|
||||||
|
|
||||||
|
#include <AK/MemoryStream.h>
|
||||||
#include <LibCore/Timer.h>
|
#include <LibCore/Timer.h>
|
||||||
#include <LibCrypto/ASN1/DER.h>
|
#include <LibCrypto/ASN1/DER.h>
|
||||||
#include <LibCrypto/PK/Code/EMSA_PSS.h>
|
#include <LibCrypto/PK/Code/EMSA_PSS.h>
|
||||||
|
@ -68,14 +69,23 @@ void TLSv12::update_packet(ByteBuffer& packet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_context.cipher_spec_set && m_context.crypto.created) {
|
if (m_context.cipher_spec_set && m_context.crypto.created) {
|
||||||
size_t length = packet.size() - header_size + mac_length();
|
size_t length = packet.size() - header_size;
|
||||||
auto block_size = m_aes_local->cipher().block_size();
|
size_t block_size, padding, mac_size;
|
||||||
|
|
||||||
|
if (!is_aead()) {
|
||||||
|
block_size = m_aes_local.cbc->cipher().block_size();
|
||||||
// If the length is already a multiple a block_size,
|
// If the length is already a multiple a block_size,
|
||||||
// an entire block of padding is added.
|
// an entire block of padding is added.
|
||||||
// In short, we _never_ have no padding.
|
// In short, we _never_ have no padding.
|
||||||
size_t padding = block_size - length % block_size;
|
padding = block_size - length % block_size;
|
||||||
length += padding;
|
length += padding;
|
||||||
size_t mac_size = mac_length();
|
mac_size = mac_length();
|
||||||
|
length += mac_size;
|
||||||
|
} else {
|
||||||
|
block_size = m_aes_local.gcm->cipher().block_size();
|
||||||
|
padding = 0;
|
||||||
|
mac_size = 0; // AEAD provides its own authentication scheme.
|
||||||
|
}
|
||||||
|
|
||||||
if (m_context.crypto.created == 1) {
|
if (m_context.crypto.created == 1) {
|
||||||
// `buffer' will continue to be encrypted
|
// `buffer' will continue to be encrypted
|
||||||
|
@ -83,15 +93,66 @@ void TLSv12::update_packet(ByteBuffer& packet)
|
||||||
size_t buffer_position = 0;
|
size_t buffer_position = 0;
|
||||||
auto iv_size = iv_length();
|
auto iv_size = iv_length();
|
||||||
|
|
||||||
// We need enough space for a header, iv_length bytes of IV and whatever the packet contains
|
// copy the packet, sans the header
|
||||||
auto ct = ByteBuffer::create_uninitialized(length + header_size + iv_size);
|
buffer.overwrite(buffer_position, packet.offset_pointer(header_size), packet.size() - header_size);
|
||||||
|
buffer_position += packet.size() - header_size;
|
||||||
|
|
||||||
|
ByteBuffer ct;
|
||||||
|
|
||||||
|
if (is_aead()) {
|
||||||
|
// We need enough space for a header, the data, a tag, and the IV
|
||||||
|
ct = ByteBuffer::create_uninitialized(length + header_size + iv_size + 16);
|
||||||
|
|
||||||
// copy the header over
|
// copy the header over
|
||||||
ct.overwrite(0, packet.data(), header_size - 2);
|
ct.overwrite(0, packet.data(), header_size - 2);
|
||||||
|
|
||||||
// copy the packet, sans the header
|
// AEAD AAD (13)
|
||||||
buffer.overwrite(buffer_position, packet.offset_pointer(header_size), packet.size() - header_size);
|
// Seq. no (8)
|
||||||
buffer_position += packet.size() - header_size;
|
// content type (1)
|
||||||
|
// version (2)
|
||||||
|
// length (2)
|
||||||
|
u8 aad[13];
|
||||||
|
Bytes aad_bytes { aad, 13 };
|
||||||
|
OutputMemoryStream 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));
|
||||||
|
|
||||||
|
aad_stream.write({ &seq_no, sizeof(seq_no) });
|
||||||
|
aad_stream.write(packet.bytes().slice(0, 3)); // content-type + version
|
||||||
|
aad_stream.write({ &len, sizeof(len) }); // length
|
||||||
|
ASSERT(aad_stream.is_end());
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
AK::fill_with_random(iv_bytes.offset(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
|
||||||
|
m_aes_local.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));
|
||||||
|
|
||||||
|
ASSERT(header_size + 8 + length + 16 == ct.size());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// We need enough space for a header, iv_length bytes of IV and whatever the packet contains
|
||||||
|
ct = ByteBuffer::create_uninitialized(length + header_size + iv_size);
|
||||||
|
|
||||||
|
// copy the header over
|
||||||
|
ct.overwrite(0, packet.data(), header_size - 2);
|
||||||
|
|
||||||
// get the appropricate HMAC value for the entire packet
|
// get the appropricate HMAC value for the entire packet
|
||||||
auto mac = hmac_message(packet, {}, mac_size, true);
|
auto mac = hmac_message(packet, {}, mac_size, true);
|
||||||
|
@ -117,7 +178,8 @@ void TLSv12::update_packet(ByteBuffer& packet)
|
||||||
|
|
||||||
// get a block to encrypt into
|
// get a block to encrypt into
|
||||||
auto view = ct.bytes().slice(header_size + iv_size, length);
|
auto view = ct.bytes().slice(header_size + iv_size, length);
|
||||||
m_aes_local->encrypt(buffer, view, iv);
|
m_aes_local.cbc->encrypt(buffer, view, iv);
|
||||||
|
}
|
||||||
|
|
||||||
// store the correct ciphertext length into the packet
|
// store the correct ciphertext length into the packet
|
||||||
u16 ct_length = (u16)ct.size() - header_size;
|
u16 ct_length = (u16)ct.size() - header_size;
|
||||||
|
@ -211,14 +273,79 @@ ssize_t TLSv12::handle_message(const ByteBuffer& buffer)
|
||||||
print_buffer(buffer.slice_view(header_size, length));
|
print_buffer(buffer.slice_view(header_size, length));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ASSERT(m_aes_remote);
|
if (is_aead()) {
|
||||||
|
ASSERT(m_aes_remote.gcm);
|
||||||
|
|
||||||
|
if (length < 24) {
|
||||||
|
dbg() << "Invalid packet length";
|
||||||
|
auto packet = build_alert(true, (u8)AlertDescription::DecryptError);
|
||||||
|
write_packet(packet);
|
||||||
|
return (i8)Error::BrokenPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto packet_length = length - iv_length() - 16;
|
||||||
|
auto payload = plain.bytes();
|
||||||
|
auto decrypted = ByteBuffer::create_uninitialized(packet_length);
|
||||||
|
|
||||||
|
// AEAD AAD (13)
|
||||||
|
// Seq. no (8)
|
||||||
|
// content type (1)
|
||||||
|
// version (2)
|
||||||
|
// length (2)
|
||||||
|
u8 aad[13];
|
||||||
|
Bytes aad_bytes { aad, 13 };
|
||||||
|
OutputMemoryStream 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);
|
||||||
|
|
||||||
|
aad_stream.write({ &seq_no, sizeof(seq_no) }); // Sequence number
|
||||||
|
aad_stream.write(buffer.bytes().slice(0, header_size - 2)); // content-type + version
|
||||||
|
aad_stream.write({ &len, sizeof(u16) });
|
||||||
|
ASSERT(aad_stream.is_end());
|
||||||
|
|
||||||
|
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 = m_aes_remote.gcm->decrypt(
|
||||||
|
ciphertext,
|
||||||
|
decrypted,
|
||||||
|
iv_bytes,
|
||||||
|
aad_bytes,
|
||||||
|
tag);
|
||||||
|
|
||||||
|
if (consistency != Crypto::VerificationConsistency::Consistent) {
|
||||||
|
dbg() << "integrity check failed (tag length " << tag.size() << ")";
|
||||||
|
auto packet = build_alert(true, (u8)AlertDescription::BadRecordMAC);
|
||||||
|
write_packet(packet);
|
||||||
|
|
||||||
|
return (i8)Error::IntegrityCheckFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
plain = decrypted;
|
||||||
|
} else {
|
||||||
|
ASSERT(m_aes_remote.cbc);
|
||||||
auto iv_size = iv_length();
|
auto iv_size = iv_length();
|
||||||
|
|
||||||
auto decrypted = m_aes_remote->create_aligned_buffer(length - iv_size);
|
auto decrypted = m_aes_remote.cbc->create_aligned_buffer(length - iv_size);
|
||||||
auto iv = buffer.slice_view(header_size, iv_size);
|
auto iv = buffer.slice_view(header_size, iv_size);
|
||||||
|
|
||||||
Bytes decrypted_span = decrypted;
|
Bytes decrypted_span = decrypted;
|
||||||
m_aes_remote->decrypt(buffer.bytes().slice(header_size + iv_size, length - iv_size), decrypted_span, iv);
|
m_aes_remote.cbc->decrypt(buffer.bytes().slice(header_size + iv_size, length - iv_size), decrypted_span, iv);
|
||||||
|
|
||||||
length = decrypted_span.size();
|
length = decrypted_span.size();
|
||||||
|
|
||||||
|
@ -256,6 +383,7 @@ ssize_t TLSv12::handle_message(const ByteBuffer& buffer)
|
||||||
}
|
}
|
||||||
plain = decrypted.slice(0, length);
|
plain = decrypted.slice(0, length);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
m_context.remote_sequence_number++;
|
m_context.remote_sequence_number++;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
|
@ -218,6 +218,8 @@ struct Context {
|
||||||
u8 local_mac[32];
|
u8 local_mac[32];
|
||||||
u8 local_iv[16];
|
u8 local_iv[16];
|
||||||
u8 remote_iv[16];
|
u8 remote_iv[16];
|
||||||
|
u8 local_aead_iv[4];
|
||||||
|
u8 remote_aead_iv[4];
|
||||||
} crypto;
|
} crypto;
|
||||||
|
|
||||||
Crypto::Hash::Manager handshake_hash;
|
Crypto::Hash::Manager handshake_hash;
|
||||||
|
@ -296,7 +298,11 @@ public:
|
||||||
|
|
||||||
bool supports_cipher(CipherSuite suite) const
|
bool supports_cipher(CipherSuite suite) const
|
||||||
{
|
{
|
||||||
return suite == CipherSuite::RSA_WITH_AES_128_CBC_SHA256 || suite == CipherSuite::RSA_WITH_AES_256_CBC_SHA256 || suite == CipherSuite::RSA_WITH_AES_128_CBC_SHA || suite == CipherSuite::RSA_WITH_AES_256_CBC_SHA;
|
return suite == CipherSuite::RSA_WITH_AES_128_CBC_SHA256
|
||||||
|
|| suite == CipherSuite::RSA_WITH_AES_256_CBC_SHA256
|
||||||
|
|| suite == CipherSuite::RSA_WITH_AES_128_CBC_SHA
|
||||||
|
|| suite == CipherSuite::RSA_WITH_AES_256_CBC_SHA
|
||||||
|
|| suite == CipherSuite::RSA_WITH_AES_128_GCM_SHA256;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool supports_version(Version v) const
|
bool supports_version(Version v) const
|
||||||
|
@ -423,7 +429,22 @@ private:
|
||||||
case CipherSuite::AES_256_GCM_SHA384:
|
case CipherSuite::AES_256_GCM_SHA384:
|
||||||
case CipherSuite::RSA_WITH_AES_128_GCM_SHA256:
|
case CipherSuite::RSA_WITH_AES_128_GCM_SHA256:
|
||||||
case CipherSuite::RSA_WITH_AES_256_GCM_SHA384:
|
case CipherSuite::RSA_WITH_AES_256_GCM_SHA384:
|
||||||
return 12;
|
return 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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_aead() const
|
||||||
|
{
|
||||||
|
switch (m_context.cipher) {
|
||||||
|
case CipherSuite::AES_128_GCM_SHA256:
|
||||||
|
case CipherSuite::AES_256_GCM_SHA384:
|
||||||
|
case CipherSuite::RSA_WITH_AES_128_GCM_SHA256:
|
||||||
|
case CipherSuite::RSA_WITH_AES_256_GCM_SHA384:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,8 +461,10 @@ private:
|
||||||
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_local;
|
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_local;
|
||||||
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_remote;
|
OwnPtr<Crypto::Authentication::HMAC<Crypto::Hash::Manager>> m_hmac_remote;
|
||||||
|
|
||||||
OwnPtr<Crypto::Cipher::AESCipher::CBCMode> m_aes_local;
|
struct {
|
||||||
OwnPtr<Crypto::Cipher::AESCipher::CBCMode> m_aes_remote;
|
OwnPtr<Crypto::Cipher::AESCipher::CBCMode> cbc;
|
||||||
|
OwnPtr<Crypto::Cipher::AESCipher::GCMMode> gcm;
|
||||||
|
} m_aes_local, m_aes_remote;
|
||||||
|
|
||||||
bool m_has_scheduled_write_flush { false };
|
bool m_has_scheduled_write_flush { false };
|
||||||
i32 m_max_wait_time_for_handshake_in_seconds { 10 };
|
i32 m_max_wait_time_for_handshake_in_seconds { 10 };
|
||||||
|
|
Loading…
Add table
Reference in a new issue