LibTLS: Add support for AEAD cipher suites

And integrate AES-GCM.
This commit is contained in:
AnotherTest 2020-11-13 01:59:36 +03:30 committed by Andreas Kling
parent d3c52cef86
commit 1172746633
Notes: sideshowbarker 2024-07-19 02:28:42 +09:00
4 changed files with 253 additions and 85 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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) {

View file

@ -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 };