aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJuraj Somorovsky <[email protected]>2016-05-09 00:48:13 +0200
committerJuraj Somorovsky <[email protected]>2016-05-11 07:55:02 +0200
commit7c7fcecbe6a94ffaba5752175d8da5e33fbf0d7b (patch)
tree5ed7b29e5d3441c26b8aa6b5520ab589f9e05377 /src
parent6d327f879c608908ca2c6b9b99f7fd74d498b4ef (diff)
Encrypt-then-MAC extension (RFC 7366)
Introduced a countermeasure against the logjam attack Short TLS records (AES-CBC) now return BAD_RECORD_MAC Fixed a compatibility problem with OpenSSL and TLS 1.0 (BEAST countermeasure)
Diffstat (limited to 'src')
-rw-r--r--src/lib/tls/msg_certificate.cpp14
-rw-r--r--src/lib/tls/msg_client_hello.cpp6
-rw-r--r--src/lib/tls/msg_client_kex.cpp7
-rw-r--r--src/lib/tls/msg_server_hello.cpp14
-rw-r--r--src/lib/tls/msg_server_kex.cpp18
-rw-r--r--src/lib/tls/tls_channel.cpp6
-rw-r--r--src/lib/tls/tls_ciphersuite.cpp6
-rw-r--r--src/lib/tls/tls_ciphersuite.h5
-rw-r--r--src/lib/tls/tls_client.cpp2
-rw-r--r--src/lib/tls/tls_extensions.cpp15
-rw-r--r--src/lib/tls/tls_extensions.h21
-rw-r--r--src/lib/tls/tls_messages.h11
-rw-r--r--src/lib/tls/tls_policy.cpp2
-rw-r--r--src/lib/tls/tls_policy.h9
-rw-r--r--src/lib/tls/tls_record.cpp265
-rw-r--r--src/lib/tls/tls_record.h7
-rw-r--r--src/lib/tls/tls_server.cpp14
-rw-r--r--src/lib/tls/tls_session.cpp4
-rw-r--r--src/lib/tls/tls_session.h7
-rw-r--r--src/tests/unit_tls.cpp51
20 files changed, 372 insertions, 112 deletions
diff --git a/src/lib/tls/msg_certificate.cpp b/src/lib/tls/msg_certificate.cpp
index fd998cd5e..dbf9dbe12 100644
--- a/src/lib/tls/msg_certificate.cpp
+++ b/src/lib/tls/msg_certificate.cpp
@@ -69,17 +69,7 @@ Certificate::Certificate(const std::vector<byte>& buf, const Policy &policy)
std::to_string(keylength) +
" bits, policy requires at least " +
std::to_string(expected_keylength));
- }
- else if(algo_name == "DH")
- {
- const size_t expected_keylength = policy.minimum_dh_group_size();
- if(keylength < expected_keylength)
- throw TLS_Exception(Alert::INSUFFICIENT_SECURITY,
- "The peer sent DH certificate of " +
- std::to_string(keylength) +
- " bits, policy requires at least " +
- std::to_string(expected_keylength));
- }
+ }
else if(algo_name == "ECDH")
{
const size_t expected_keylength = policy.minimum_ecdh_group_size();
@@ -90,7 +80,7 @@ Certificate::Certificate(const std::vector<byte>& buf, const Policy &policy)
" bits, policy requires at least " +
std::to_string(expected_keylength));
- }
+ }
m_certs.push_back(cert);
diff --git a/src/lib/tls/msg_client_hello.cpp b/src/lib/tls/msg_client_hello.cpp
index 23807215f..d2b1a166e 100644
--- a/src/lib/tls/msg_client_hello.cpp
+++ b/src/lib/tls/msg_client_hello.cpp
@@ -96,6 +96,9 @@ Client_Hello::Client_Hello(Handshake_IO& io,
if(reneg_info.empty() && !next_protocols.empty())
m_extensions.add(new Application_Layer_Protocol_Notification(next_protocols));
+
+ if(policy.negotiate_encrypt_then_mac())
+ m_extensions.add(new Encrypt_then_MAC);
#if defined(BOTAN_HAS_SRP6)
m_extensions.add(new SRP_Identifier(srp_identifier));
@@ -155,6 +158,9 @@ Client_Hello::Client_Hello(Handshake_IO& io,
if(reneg_info.empty() && !next_protocols.empty())
m_extensions.add(new Application_Layer_Protocol_Notification(next_protocols));
+
+ if(policy.negotiate_encrypt_then_mac())
+ m_extensions.add(new Encrypt_then_MAC);
#if defined(BOTAN_HAS_SRP6)
m_extensions.add(new SRP_Identifier(session.srp_identifier()));
diff --git a/src/lib/tls/msg_client_kex.cpp b/src/lib/tls/msg_client_kex.cpp
index 77e9795f4..192c774b8 100644
--- a/src/lib/tls/msg_client_kex.cpp
+++ b/src/lib/tls/msg_client_kex.cpp
@@ -92,13 +92,6 @@ Client_Key_Exchange::Client_Key_Exchange(Handshake_IO& io,
if(reader.remaining_bytes())
throw Decoding_Error("Bad params size for DH key exchange");
- if(p.bits() < policy.minimum_dh_group_size())
- throw TLS_Exception(Alert::INSUFFICIENT_SECURITY,
- "Server sent DH group of " +
- std::to_string(p.bits()) +
- " bits, policy requires at least " +
- std::to_string(policy.minimum_dh_group_size()));
-
/*
* A basic check for key validity. As we do not know q here we
* cannot check that Y is in the right subgroup. However since
diff --git a/src/lib/tls/msg_server_hello.cpp b/src/lib/tls/msg_server_hello.cpp
index f8d0c63c7..e309a7c91 100644
--- a/src/lib/tls/msg_server_hello.cpp
+++ b/src/lib/tls/msg_server_hello.cpp
@@ -38,6 +38,13 @@ Server_Hello::Server_Hello(Handshake_IO& io,
if(client_hello.supports_extended_master_secret())
m_extensions.add(new Extended_Master_Secret);
+ if(client_hello.supports_encrypt_then_mac() && policy.negotiate_encrypt_then_mac())
+ {
+ Ciphersuite c = Ciphersuite::by_id(m_ciphersuite);
+ if(c.cbc_ciphersuite())
+ m_extensions.add(new Encrypt_then_MAC);
+ }
+
if(client_hello.secure_renegotiation())
m_extensions.add(new Renegotiation_Extension(reneg_info));
@@ -90,6 +97,13 @@ Server_Hello::Server_Hello(Handshake_IO& io,
if(client_hello.supports_extended_master_secret())
m_extensions.add(new Extended_Master_Secret);
+ if(client_hello.supports_encrypt_then_mac() && policy.negotiate_encrypt_then_mac())
+ {
+ Ciphersuite c = resumed_session.ciphersuite();
+ if(c.cbc_ciphersuite())
+ m_extensions.add(new Encrypt_then_MAC);
+ }
+
if(client_hello.secure_renegotiation())
m_extensions.add(new Renegotiation_Extension(reneg_info));
diff --git a/src/lib/tls/msg_server_kex.cpp b/src/lib/tls/msg_server_kex.cpp
index 98e3ad1f0..99f0d0f09 100644
--- a/src/lib/tls/msg_server_kex.cpp
+++ b/src/lib/tls/msg_server_kex.cpp
@@ -147,6 +147,7 @@ Server_Key_Exchange::Server_Key_Exchange(Handshake_IO& io,
Server_Key_Exchange::Server_Key_Exchange(const std::vector<byte>& buf,
const std::string& kex_algo,
const std::string& sig_algo,
+ const Policy& policy,
Protocol_Version version)
{
TLS_Data_Reader reader("ServerKeyExchange", buf);
@@ -165,11 +166,18 @@ Server_Key_Exchange::Server_Key_Exchange(const std::vector<byte>& buf,
if(kex_algo == "DH" || kex_algo == "DHE_PSK")
{
// 3 bigints, DH p, g, Y
-
- for(size_t i = 0; i != 3; ++i)
- {
- reader.get_range<byte>(2, 1, 65535);
- }
+ std::vector<byte> p = reader.get_range<byte>(2, 1, 65535);
+ reader.get_range<byte>(2, 1, 65535);
+ reader.get_range<byte>(2, 1, 65535);
+
+ // protection against the LOGJAM attack
+ int key_size = p.size() * 8;
+ if(key_size < policy.minimum_dh_group_size())
+ throw TLS_Exception(Alert::INSUFFICIENT_SECURITY,
+ "Server sent DH group of " +
+ std::to_string(key_size) +
+ " bits, policy requires at least " +
+ std::to_string(policy.minimum_dh_group_size()));
}
else if(kex_algo == "ECDH" || kex_algo == "ECDHE_PSK")
{
diff --git a/src/lib/tls/tls_channel.cpp b/src/lib/tls/tls_channel.cpp
index 2cf351c80..cfaeefeb8 100644
--- a/src/lib/tls/tls_channel.cpp
+++ b/src/lib/tls/tls_channel.cpp
@@ -183,7 +183,8 @@ void Channel::change_cipher_spec_reader(Connection_Side side)
(side == CLIENT) ? SERVER : CLIENT,
false,
pending->ciphersuite(),
- pending->session_keys()));
+ pending->session_keys(),
+ pending->server_hello()->supports_encrypt_then_mac()));
m_read_cipher_states[epoch] = read_state;
}
@@ -210,7 +211,8 @@ void Channel::change_cipher_spec_writer(Connection_Side side)
side,
true,
pending->ciphersuite(),
- pending->session_keys()));
+ pending->session_keys(),
+ pending->server_hello()->supports_encrypt_then_mac()));
m_write_cipher_states[epoch] = write_state;
}
diff --git a/src/lib/tls/tls_ciphersuite.cpp b/src/lib/tls/tls_ciphersuite.cpp
index 20142adc5..1526c1059 100644
--- a/src/lib/tls/tls_ciphersuite.cpp
+++ b/src/lib/tls/tls_ciphersuite.cpp
@@ -100,6 +100,12 @@ bool Ciphersuite::ecc_ciphersuite() const
return (sig_algo() == "ECDSA" || kex_algo() == "ECDH" || kex_algo() == "ECDHE_PSK");
}
+bool Ciphersuite::cbc_ciphersuite() const
+ {
+ return (cipher_algo() == "3DES" || cipher_algo() == "AES-128" || cipher_algo() == "AES-256"
+ || cipher_algo() == "Camellia-128" || cipher_algo() == "Camellia-256");
+ }
+
namespace {
bool have_hash(const std::string& prf)
diff --git a/src/lib/tls/tls_ciphersuite.h b/src/lib/tls/tls_ciphersuite.h
index 355dd5a8f..47246ec11 100644
--- a/src/lib/tls/tls_ciphersuite.h
+++ b/src/lib/tls/tls_ciphersuite.h
@@ -72,6 +72,11 @@ class BOTAN_DLL Ciphersuite
bool ecc_ciphersuite() const;
/**
+ * @return true if this suite uses a CBC cipher
+ */
+ bool cbc_ciphersuite() const;
+
+ /**
* @return key exchange algorithm used by this ciphersuite
*/
const std::string& kex_algo() const { return m_kex_algo; }
diff --git a/src/lib/tls/tls_client.cpp b/src/lib/tls/tls_client.cpp
index 0423b6536..13dde99c4 100644
--- a/src/lib/tls/tls_client.cpp
+++ b/src/lib/tls/tls_client.cpp
@@ -387,6 +387,7 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
new Server_Key_Exchange(contents,
state.ciphersuite().kex_algo(),
state.ciphersuite().sig_algo(),
+ policy(),
state.version())
);
@@ -510,6 +511,7 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
state.server_hello()->compression_method(),
CLIENT,
state.server_hello()->supports_extended_master_secret(),
+ state.server_hello()->supports_encrypt_then_mac(),
get_peer_cert_chain(state),
session_ticket,
m_info,
diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp
index 8befb2fbc..76a4c8060 100644
--- a/src/lib/tls/tls_extensions.cpp
+++ b/src/lib/tls/tls_extensions.cpp
@@ -47,6 +47,9 @@ Extension* make_extension(TLS_Data_Reader& reader,
case TLSEXT_EXTENDED_MASTER_SECRET:
return new Extended_Master_Secret(reader, size);
+ case TLSEXT_ENCRYPT_THEN_MAC:
+ return new Encrypt_then_MAC(reader, size);
+
case TLSEXT_SESSION_TICKET:
return new Session_Ticket(reader, size);
@@ -519,6 +522,18 @@ std::vector<byte> Extended_Master_Secret::serialize() const
return std::vector<byte>();
}
+Encrypt_then_MAC::Encrypt_then_MAC(TLS_Data_Reader&,
+ u16bit extension_size)
+ {
+ if(extension_size != 0)
+ throw Decoding_Error("Invalid encrypt_then_mac extension");
+ }
+
+std::vector<byte> Encrypt_then_MAC::serialize() const
+ {
+ return std::vector<byte>();
+ }
+
}
}
diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h
index a5aac0020..e273ae096 100644
--- a/src/lib/tls/tls_extensions.h
+++ b/src/lib/tls/tls_extensions.h
@@ -37,6 +37,7 @@ enum Handshake_Extension_Type {
TLSEXT_HEARTBEAT_SUPPORT = 15,
TLSEXT_ALPN = 16,
+ TLSEXT_ENCRYPT_THEN_MAC = 22,
TLSEXT_EXTENDED_MASTER_SECRET = 23,
TLSEXT_SESSION_TICKET = 35,
@@ -341,6 +342,26 @@ class Extended_Master_Secret final : public Extension
};
/**
+* Encrypt-then-MAC Extension (RFC 7366)
+*/
+class Encrypt_then_MAC final : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_ENCRYPT_THEN_MAC; }
+
+ Handshake_Extension_Type type() const override { return static_type(); }
+
+ std::vector<byte> serialize() const override;
+
+ bool empty() const override { return false; }
+
+ Encrypt_then_MAC() {}
+
+ Encrypt_then_MAC(TLS_Data_Reader& reader, u16bit extension_size);
+ };
+
+/**
* Represents a block of extensions in a hello message
*/
class Extensions
diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h
index cbeb9ad05..a5ababc0e 100644
--- a/src/lib/tls/tls_messages.h
+++ b/src/lib/tls/tls_messages.h
@@ -141,6 +141,11 @@ class Client_Hello final : public Handshake_Message
return m_extensions.has<Extended_Master_Secret>();
}
+ bool supports_encrypt_then_mac() const
+ {
+ return m_extensions.has<Encrypt_then_MAC>();
+ }
+
std::vector<std::string> next_protocols() const
{
if(auto alpn = m_extensions.get<Application_Layer_Protocol_Notification>())
@@ -228,6 +233,11 @@ class Server_Hello final : public Handshake_Message
return m_extensions.has<Extended_Master_Secret>();
}
+ bool supports_encrypt_then_mac() const
+ {
+ return m_extensions.has<Encrypt_then_MAC>();
+ }
+
bool supports_session_ticket() const
{
return m_extensions.has<Session_Ticket>();
@@ -489,6 +499,7 @@ class Server_Key_Exchange final : public Handshake_Message
Server_Key_Exchange(const std::vector<byte>& buf,
const std::string& kex_alg,
const std::string& sig_alg,
+ const Policy& policy,
Protocol_Version version);
~Server_Key_Exchange();
diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp
index b98f1106c..fdc6ba862 100644
--- a/src/lib/tls/tls_policy.cpp
+++ b/src/lib/tls/tls_policy.cpp
@@ -194,6 +194,7 @@ bool Policy::allow_dtls12() const { return true; }
bool Policy::include_time_in_hello_random() const { return true; }
bool Policy::hide_unknown_users() const { return false; }
bool Policy::server_uses_own_ciphersuite_preferences() const { return true; }
+bool Policy::negotiate_encrypt_then_mac() const { return true; }
// 1 second initial timeout, 60 second max - see RFC 6347 sec 4.2.4.1
size_t Policy::dtls_initial_timeout() const { return 1*1000; }
@@ -379,6 +380,7 @@ void Policy::print(std::ostream& o) const
print_bool(o, "allow_server_initiated_renegotiation", allow_server_initiated_renegotiation());
print_bool(o, "hide_unknown_users", hide_unknown_users());
print_bool(o, "server_uses_own_ciphersuite_preferences", server_uses_own_ciphersuite_preferences());
+ print_bool(o, "negotiate_encrypt_then_mac", negotiate_encrypt_then_mac());
o << "session_ticket_lifetime = " << session_ticket_lifetime() << '\n';
o << "dh_group = " << dh_group() << '\n';
o << "minimum_dh_group_size = " << minimum_dh_group_size() << '\n';
diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h
index 4ae84b833..dc24d73e2 100644
--- a/src/lib/tls/tls_policy.h
+++ b/src/lib/tls/tls_policy.h
@@ -203,6 +203,12 @@ class BOTAN_DLL Policy
virtual bool server_uses_own_ciphersuite_preferences() const;
/**
+ * Indicates whether the encrypt-then-MAC extension should be negotiated
+ * (RFC 7366)
+ */
+ virtual bool negotiate_encrypt_then_mac() const;
+
+ /**
* Return allowed ciphersuites, in order of preference
*/
virtual std::vector<u16bit> ciphersuite_list(Protocol_Version version,
@@ -340,6 +346,9 @@ class BOTAN_DLL Text_Policy : public Policy
bool server_uses_own_ciphersuite_preferences() const override
{ return get_bool("server_uses_own_ciphersuite_preferences", Policy::server_uses_own_ciphersuite_preferences()); }
+ bool negotiate_encrypt_then_mac() const override
+ { return get_bool("negotiate_encrypt_then_mac", Policy::negotiate_encrypt_then_mac()); }
+
std::string dh_group() const override
{ return get_str("dh_group", Policy::dh_group()); }
diff --git a/src/lib/tls/tls_record.cpp b/src/lib/tls/tls_record.cpp
index 8af6587e3..438dce178 100644
--- a/src/lib/tls/tls_record.cpp
+++ b/src/lib/tls/tls_record.cpp
@@ -23,10 +23,12 @@ Connection_Cipher_State::Connection_Cipher_State(Protocol_Version version,
Connection_Side side,
bool our_side,
const Ciphersuite& suite,
- const Session_Keys& keys) :
+ const Session_Keys& keys,
+ bool uses_encrypt_then_mac) :
m_start_time(std::chrono::system_clock::now()),
m_nonce_bytes_from_handshake(suite.nonce_bytes_from_handshake()),
- m_nonce_bytes_from_record(suite.nonce_bytes_from_record())
+ m_nonce_bytes_from_record(suite.nonce_bytes_from_record()),
+ m_uses_encrypt_then_mac(uses_encrypt_then_mac)
{
SymmetricKey mac_key, cipher_key;
InitializationVector iv;
@@ -213,77 +215,151 @@ void write_record(secure_vector<byte>& output,
return;
}
- cs->mac()->update(cs->format_ad(seq, msg_type, version, msg_length));
-
- cs->mac()->update(msg, msg_length);
-
const size_t block_size = cs->block_size();
const size_t iv_size = cs->iv_size();
const size_t mac_size = cs->mac_size();
- const size_t buf_size = round_up(
- iv_size + msg_length + mac_size + (block_size ? 1 : 0),
- block_size);
+ if(!cs->uses_encrypt_then_mac())
+ {
+ cs->mac()->update(cs->format_ad(seq, msg_type, version, msg_length));
+ cs->mac()->update(msg, msg_length);
- if(buf_size > MAX_CIPHERTEXT_SIZE)
- throw Internal_Error("Output record is larger than allowed by protocol");
+ const size_t buf_size = round_up(
+ iv_size + msg_length + mac_size + (block_size ? 1 : 0),
+ block_size);
- output.push_back(get_byte<u16bit>(0, buf_size));
- output.push_back(get_byte<u16bit>(1, buf_size));
+ if(buf_size > MAX_CIPHERTEXT_SIZE)
+ throw Internal_Error("Output record is larger than allowed by protocol");
- const size_t header_size = output.size();
+ output.push_back(get_byte<u16bit>(0, buf_size));
+ output.push_back(get_byte<u16bit>(1, buf_size));
- if(iv_size)
- {
- output.resize(output.size() + iv_size);
- rng.randomize(&output[output.size() - iv_size], iv_size);
- }
+ const size_t header_size = output.size();
- output.insert(output.end(), msg, msg + msg_length);
+ if(iv_size)
+ {
+ output.resize(output.size() + iv_size);
+ rng.randomize(&output[output.size() - iv_size], iv_size);
+ }
- output.resize(output.size() + mac_size);
- cs->mac()->final(&output[output.size() - mac_size]);
+ output.insert(output.end(), msg, msg + msg_length);
- if(block_size)
- {
- const size_t pad_val =
- buf_size - (iv_size + msg_length + mac_size + 1);
+ output.resize(output.size() + mac_size);
+ cs->mac()->final(&output[output.size() - mac_size]);
- for(size_t i = 0; i != pad_val + 1; ++i)
- output.push_back(pad_val);
- }
+ if(block_size)
+ {
+ const size_t pad_val =
+ buf_size - (iv_size + msg_length + mac_size + 1);
+
+ for(size_t i = 0; i != pad_val + 1; ++i)
+ output.push_back(pad_val);
+ }
- if(buf_size > MAX_CIPHERTEXT_SIZE)
- throw Internal_Error("Produced ciphertext larger than protocol allows");
+ if(buf_size > MAX_CIPHERTEXT_SIZE)
+ throw Internal_Error("Produced ciphertext larger than protocol allows");
- BOTAN_ASSERT_EQUAL(buf_size + header_size, output.size(),
+ BOTAN_ASSERT_EQUAL(buf_size + header_size, output.size(),
"Output buffer is sized properly");
- if(BlockCipher* bc = cs->block_cipher())
- {
- secure_vector<byte>& cbc_state = cs->cbc_state();
+ if(BlockCipher* bc = cs->block_cipher())
+ {
+ secure_vector<byte>& cbc_state = cs->cbc_state();
- BOTAN_ASSERT(buf_size % block_size == 0,
+ BOTAN_ASSERT(buf_size % block_size == 0,
"Buffer is an even multiple of block size");
- byte* buf = &output[header_size];
+ byte* buf = &output[header_size];
+
+ const size_t blocks = buf_size / block_size;
+
+ xor_buf(buf, cbc_state.data(), block_size);
+ bc->encrypt(buf);
+
+ for(size_t i = 1; i < blocks; ++i)
+ {
+ xor_buf(&buf[block_size*i], &buf[block_size*(i-1)], block_size);
+ bc->encrypt(&buf[block_size*i]);
+ }
+
+ cbc_state.assign(&buf[block_size*(blocks-1)],
+ &buf[block_size*blocks]);
+ }
+ else
+ {
+ throw Internal_Error("NULL cipher not supported");
+ }
+ }
+ else
+ {
+ const size_t enc_size = round_up(
+ iv_size + msg_length + (block_size ? 1 : 0),
+ block_size);
+
+ const size_t buf_size = enc_size + mac_size;
- const size_t blocks = buf_size / block_size;
+ if(buf_size > MAX_CIPHERTEXT_SIZE)
+ throw Internal_Error("Output record is larger than allowed by protocol");
- xor_buf(buf, cbc_state.data(), block_size);
- bc->encrypt(buf);
+ output.push_back(get_byte<u16bit>(0, buf_size));
+ output.push_back(get_byte<u16bit>(1, buf_size));
+
+ const size_t header_size = output.size();
- for(size_t i = 1; i < blocks; ++i)
+ if(iv_size)
{
- xor_buf(&buf[block_size*i], &buf[block_size*(i-1)], block_size);
- bc->encrypt(&buf[block_size*i]);
+ output.resize(output.size() + iv_size);
+ rng.randomize(&output[output.size() - iv_size], iv_size);
+ }
+
+ output.insert(output.end(), msg, msg + msg_length);
+
+ if(block_size)
+ {
+ const size_t pad_val =
+ enc_size - (iv_size + msg_length + 1);
+
+ for(size_t i = 0; i != pad_val + 1; ++i)
+ output.push_back(pad_val);
}
- cbc_state.assign(&buf[block_size*(blocks-1)],
+ if(BlockCipher* bc = cs->block_cipher())
+ {
+ secure_vector<byte>& cbc_state = cs->cbc_state();
+
+ BOTAN_ASSERT( enc_size % block_size == 0,
+ "Buffer is an even multiple of block size");
+
+ byte* buf = &output[header_size];
+
+ const size_t blocks = enc_size / block_size;
+
+ xor_buf(buf, cbc_state.data(), block_size);
+ bc->encrypt(buf);
+
+ for(size_t i = 1; i < blocks; ++i)
+ {
+ xor_buf(&buf[block_size*i], &buf[block_size*(i-1)], block_size);
+ bc->encrypt(&buf[block_size*i]);
+ }
+
+ cbc_state.assign(&buf[block_size*(blocks-1)],
&buf[block_size*blocks]);
+
+ cs->mac()->update(cs->format_ad(seq, msg_type, version, enc_size));
+ cs->mac()->update(buf, enc_size);
+
+ output.resize(output.size() + mac_size);
+ cs->mac()->final(&output[output.size() - mac_size]);
+
+ BOTAN_ASSERT_EQUAL(buf_size + header_size, output.size(),
+ "Output buffer is sized properly");
+ }
+ else
+ {
+ throw Internal_Error("NULL cipher not supported");
+ }
}
- else
- throw Internal_Error("NULL cipher not supported");
}
namespace {
@@ -409,53 +485,90 @@ void decrypt_record(secure_vector<byte>& output,
const size_t mac_size = cs.mac_size();
const size_t iv_size = cs.iv_size();
- // This early exit does not leak info because all the values are public
- if((record_len < mac_size + iv_size) || (record_len % cs.block_size() != 0))
- throw Decoding_Error("Record sent with invalid length");
+ if(!cs.uses_encrypt_then_mac())
+ {
+ // This early exit does not leak info because all the values are public
+ if((record_len < mac_size + iv_size) || (record_len % cs.block_size() != 0))
+ throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure");
- CT::poison(record_contents, record_len);
+ CT::poison(record_contents, record_len);
- cbc_decrypt_record(record_contents, record_len, cs, *bc);
+ cbc_decrypt_record(record_contents, record_len, cs, *bc);
- // 0 if padding was invalid, otherwise 1 + padding_bytes
- u16bit pad_size = tls_padding_check(record_contents, record_len);
+ // 0 if padding was invalid, otherwise 1 + padding_bytes
+ u16bit pad_size = tls_padding_check(record_contents, record_len);
- // This mask is zero if there is not enough room in the packet
- const u16bit size_ok_mask = CT::is_lte<u16bit>(mac_size + pad_size + iv_size, record_len);
- pad_size &= size_ok_mask;
+ // This mask is zero if there is not enough room in the packet to get
+ // a valid MAC. We have to accept empty packets, since otherwise we
+ // are not compatible with the BEAST countermeasure (thus record_len+1).
+ const u16bit size_ok_mask = CT::is_less<u16bit>(mac_size + pad_size + iv_size, record_len + 1);
+ pad_size &= size_ok_mask;
- CT::unpoison(record_contents, record_len);
+ CT::unpoison(record_contents, record_len);
- /*
- This is unpoisoned sooner than it should. The pad_size leaks to plaintext_length and
- then to the timing channel in the MAC computation described in the Lucky 13 paper.
- */
- CT::unpoison(pad_size);
+ /*
+ This is unpoisoned sooner than it should. The pad_size leaks to plaintext_length and
+ then to the timing channel in the MAC computation described in the Lucky 13 paper.
+ */
+ CT::unpoison(pad_size);
- const byte* plaintext_block = &record_contents[iv_size];
- const u16bit plaintext_length = record_len - mac_size - iv_size - pad_size;
+ const byte* plaintext_block = &record_contents[iv_size];
+ const u16bit plaintext_length = record_len - mac_size - iv_size - pad_size;
- cs.mac()->update(cs.format_ad(record_sequence, record_type, record_version, plaintext_length));
- cs.mac()->update(plaintext_block, plaintext_length);
+ cs.mac()->update(cs.format_ad(record_sequence, record_type, record_version, plaintext_length));
+ cs.mac()->update(plaintext_block, plaintext_length);
- std::vector<byte> mac_buf(mac_size);
- cs.mac()->final(mac_buf.data());
+ std::vector<byte> mac_buf(mac_size);
+ cs.mac()->final(mac_buf.data());
- const size_t mac_offset = record_len - (mac_size + pad_size);
+ const size_t mac_offset = record_len - (mac_size + pad_size);
- const bool mac_ok = same_mem(&record_contents[mac_offset], mac_buf.data(), mac_size);
+ const bool mac_ok = same_mem(&record_contents[mac_offset], mac_buf.data(), mac_size);
- const u16bit ok_mask = size_ok_mask & CT::expand_mask<u16bit>(mac_ok) & CT::expand_mask<u16bit>(pad_size);
+ const u16bit ok_mask = size_ok_mask & CT::expand_mask<u16bit>(mac_ok) & CT::expand_mask<u16bit>(pad_size);
- CT::unpoison(ok_mask);
+ CT::unpoison(ok_mask);
- if(ok_mask)
- {
- output.assign(plaintext_block, plaintext_block + plaintext_length);
+ if(ok_mask)
+ {
+ output.assign(plaintext_block, plaintext_block + plaintext_length);
+ }
+ else
+ {
+ throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure");
+ }
}
else
{
- throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure");
+ const size_t enc_size = record_len - mac_size;
+ // This early exit does not leak info because all the values are public
+ if((record_len < mac_size + iv_size) || ( enc_size % cs.block_size() != 0))
+ throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure");
+
+ cs.mac()->update(cs.format_ad(record_sequence, record_type, record_version, enc_size));
+ cs.mac()->update(record_contents, enc_size);
+
+ std::vector<byte> mac_buf(mac_size);
+ cs.mac()->final(mac_buf.data());
+
+ const size_t mac_offset = enc_size;
+
+ const bool mac_ok = same_mem(&record_contents[mac_offset], mac_buf.data(), mac_size);
+
+ if(!mac_ok)
+ {
+ throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure");
+ }
+
+ cbc_decrypt_record(record_contents, enc_size, cs, *bc);
+
+ // 0 if padding was invalid, otherwise 1 + padding_bytes
+ u16bit pad_size = tls_padding_check(record_contents, enc_size);
+
+ const byte* plaintext_block = &record_contents[iv_size];
+ const u16bit plaintext_length = enc_size - iv_size - pad_size;
+
+ output.assign(plaintext_block, plaintext_block + plaintext_length);
}
}
}
diff --git a/src/lib/tls/tls_record.h b/src/lib/tls/tls_record.h
index e3b0b9b58..9180aa554 100644
--- a/src/lib/tls/tls_record.h
+++ b/src/lib/tls/tls_record.h
@@ -38,7 +38,8 @@ class Connection_Cipher_State
Connection_Side which_side,
bool is_our_side,
const Ciphersuite& suite,
- const Session_Keys& keys);
+ const Session_Keys& keys,
+ bool uses_encrypt_then_mac);
AEAD_Mode* aead() { return m_aead.get(); }
@@ -66,6 +67,8 @@ class Connection_Cipher_State
size_t nonce_bytes_from_handshake() const { return m_nonce_bytes_from_handshake; }
+ bool uses_encrypt_then_mac() const { return m_uses_encrypt_then_mac; }
+
bool cbc_without_explicit_iv() const
{ return (m_block_size > 0) && (m_iv_size == 0); }
@@ -88,6 +91,8 @@ class Connection_Cipher_State
size_t m_nonce_bytes_from_handshake;
size_t m_nonce_bytes_from_record;
size_t m_iv_size = 0;
+
+ bool m_uses_encrypt_then_mac;
};
/**
diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp
index 39b5afd65..78c9087e0 100644
--- a/src/lib/tls/tls_server.cpp
+++ b/src/lib/tls/tls_server.cpp
@@ -118,6 +118,19 @@ bool check_for_resume(Session& session_info,
}
}
+ // Checking encrypt_then_mac on resume (RFC 7366 section 3.1)
+ if( !client_hello->supports_encrypt_then_mac() && session_info.supports_encrypt_then_mac())
+ {
+
+ /*
+ Client previously negotiated session with Encrypt-then-MAC,
+ but has now attempted to resume without the extension: abort
+ */
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Client resumed Encrypt-then-MAC session without sending extension");
+
+ }
+
return true;
}
@@ -670,6 +683,7 @@ void Server::process_handshake_msg(const Handshake_State* active_state,
state.server_hello()->compression_method(),
SERVER,
state.server_hello()->supports_extended_master_secret(),
+ state.server_hello()->supports_encrypt_then_mac(),
get_peer_cert_chain(state),
std::vector<byte>(),
Server_Information(state.client_hello()->sni_hostname()),
diff --git a/src/lib/tls/tls_session.cpp b/src/lib/tls/tls_session.cpp
index 6d5fc1a7b..c02bbd9ab 100644
--- a/src/lib/tls/tls_session.cpp
+++ b/src/lib/tls/tls_session.cpp
@@ -24,6 +24,7 @@ Session::Session(const std::vector<byte>& session_identifier,
byte compression_method,
Connection_Side side,
bool extended_master_secret,
+ bool encrypt_then_mac,
const std::vector<X509_Certificate>& certs,
const std::vector<byte>& ticket,
const Server_Information& server_info,
@@ -39,6 +40,7 @@ Session::Session(const std::vector<byte>& session_identifier,
m_connection_side(side),
m_srtp_profile(srtp_profile),
m_extended_master_secret(extended_master_secret),
+ m_encrypt_then_mac(encrypt_then_mac),
m_peer_certs(certs),
m_server_info(server_info),
m_srp_identifier(srp_identifier)
@@ -83,6 +85,7 @@ Session::Session(const byte ber[], size_t ber_len)
.decode_integer_type(side_code)
.decode_integer_type(fragment_size)
.decode(m_extended_master_secret)
+ .decode(m_encrypt_then_mac)
.decode(m_master_secret, OCTET_STRING)
.decode(peer_cert_bits, OCTET_STRING)
.decode(server_hostname)
@@ -142,6 +145,7 @@ secure_vector<byte> Session::DER_encode() const
.encode(static_cast<size_t>(m_connection_side))
.encode(static_cast<size_t>(/*old fragment size*/0))
.encode(m_extended_master_secret)
+ .encode(m_encrypt_then_mac)
.encode(m_master_secret, OCTET_STRING)
.encode(peer_cert_bits, OCTET_STRING)
.encode(ASN1_String(m_server_info.hostname(), UTF8_STRING))
diff --git a/src/lib/tls/tls_session.h b/src/lib/tls/tls_session.h
index 8ca646cf2..15e79b811 100644
--- a/src/lib/tls/tls_session.h
+++ b/src/lib/tls/tls_session.h
@@ -38,7 +38,8 @@ class BOTAN_DLL Session
m_compression_method(0),
m_connection_side(static_cast<Connection_Side>(0)),
m_srtp_profile(0),
- m_extended_master_secret(false)
+ m_extended_master_secret(false),
+ m_encrypt_then_mac(false)
{}
/**
@@ -51,6 +52,7 @@ class BOTAN_DLL Session
byte compression_method,
Connection_Side side,
bool supports_extended_master_secret,
+ bool supports_encrypt_then_mac,
const std::vector<X509_Certificate>& peer_certs,
const std::vector<byte>& session_ticket,
const Server_Information& server_info,
@@ -157,6 +159,8 @@ class BOTAN_DLL Session
bool supports_extended_master_secret() const { return m_extended_master_secret; }
+ bool supports_encrypt_then_mac() const { return m_encrypt_then_mac; }
+
/**
* Return the certificate chain of the peer (possibly empty)
*/
@@ -194,6 +198,7 @@ class BOTAN_DLL Session
Connection_Side m_connection_side;
u16bit m_srtp_profile;
bool m_extended_master_secret;
+ bool m_encrypt_then_mac;
std::vector<X509_Certificate> m_peer_certs;
Server_Information m_server_info; // optional
diff --git a/src/tests/unit_tls.cpp b/src/tests/unit_tls.cpp
index f125bfcb5..91eca0d5a 100644
--- a/src/tests/unit_tls.cpp
+++ b/src/tests/unit_tls.cpp
@@ -167,7 +167,8 @@ void print_alert(Botan::TLS::Alert, const byte[], size_t)
Test::Result test_tls_handshake(Botan::TLS::Protocol_Version offer_version,
Botan::Credentials_Manager& creds,
- Botan::TLS::Policy& policy)
+ Botan::TLS::Policy& client_policy,
+ Botan::TLS::Policy& server_policy )
{
Botan::RandomNumberGenerator& rng = Test::rng();
@@ -224,7 +225,7 @@ Test::Result test_tls_handshake(Botan::TLS::Protocol_Version offer_version,
handshake_complete,
server_sessions,
creds,
- policy,
+ server_policy,
rng,
next_protocol_chooser,
false);
@@ -235,7 +236,7 @@ Test::Result test_tls_handshake(Botan::TLS::Protocol_Version offer_version,
handshake_complete,
client_sessions,
creds,
- policy,
+ client_policy,
rng,
Botan::TLS::Server_Information("server.example.com"),
offer_version,
@@ -397,9 +398,17 @@ Test::Result test_tls_handshake(Botan::TLS::Protocol_Version offer_version,
return result;
}
+Test::Result test_tls_handshake(Botan::TLS::Protocol_Version offer_version,
+ Botan::Credentials_Manager& creds,
+ Botan::TLS::Policy& policy )
+ {
+ return test_tls_handshake(offer_version, creds, policy, policy);
+ }
+
Test::Result test_dtls_handshake(Botan::TLS::Protocol_Version offer_version,
Botan::Credentials_Manager& creds,
- Botan::TLS::Policy& policy)
+ Botan::TLS::Policy& client_policy,
+ Botan::TLS::Policy& server_policy )
{
BOTAN_ASSERT(offer_version.is_datagram_protocol(), "Test is for datagram version");
@@ -450,7 +459,7 @@ Test::Result test_dtls_handshake(Botan::TLS::Protocol_Version offer_version,
handshake_complete,
server_sessions,
creds,
- policy,
+ server_policy,
rng,
next_protocol_chooser,
true);
@@ -461,7 +470,7 @@ Test::Result test_dtls_handshake(Botan::TLS::Protocol_Version offer_version,
handshake_complete,
client_sessions,
creds,
- policy,
+ client_policy,
rng,
Botan::TLS::Server_Information("server.example.com"),
offer_version,
@@ -527,7 +536,7 @@ Test::Result test_dtls_handshake(Botan::TLS::Protocol_Version offer_version,
{
input.resize(needed);
Test::rng().randomize(input.data(), input.size());
- client.received_data(input.data(), input.size());
+ needed = client.received_data(input.data(), input.size());
}
}
catch(std::exception&)
@@ -567,7 +576,7 @@ Test::Result test_dtls_handshake(Botan::TLS::Protocol_Version offer_version,
{
input.resize(needed);
Test::rng().randomize(input.data(), input.size());
- client.received_data(input.data(), input.size());
+ needed = client.received_data(input.data(), input.size());
}
}
catch(std::exception&)
@@ -641,6 +650,13 @@ Test::Result test_dtls_handshake(Botan::TLS::Protocol_Version offer_version,
return result;
}
+Test::Result test_dtls_handshake(Botan::TLS::Protocol_Version offer_version,
+ Botan::Credentials_Manager& creds,
+ Botan::TLS::Policy& policy)
+ {
+ return test_dtls_handshake(offer_version, creds, policy, policy);
+ }
+
class Test_Policy : public Botan::TLS::Text_Policy
{
public:
@@ -719,6 +735,25 @@ class TLS_Unit_Tests : public Test
results.push_back(test_tls_handshake(Botan::TLS::Protocol_Version::TLS_V12, *basic_creds, policy));
results.push_back(test_dtls_handshake(Botan::TLS::Protocol_Version::DTLS_V12, *basic_creds, policy));
+ policy.set("negotiate_encrypt_then_mac", "false");
+ policy.set("key_exchange_methods", "ECDH");
+ policy.set("ciphers", "AES-128");
+ Test_Policy server_policy;
+ server_policy.set("key_exchange_methods", "ECDH");
+ server_policy.set("ciphers", "AES-128");
+ server_policy.set("negotiate_encrypt_then_mac", "true");
+ results.push_back(test_tls_handshake(Botan::TLS::Protocol_Version::TLS_V10, *basic_creds, policy, server_policy));
+ results.push_back(test_tls_handshake(Botan::TLS::Protocol_Version::TLS_V11, *basic_creds, policy, server_policy));
+ results.push_back(test_tls_handshake(Botan::TLS::Protocol_Version::TLS_V12, *basic_creds, policy, server_policy));
+ results.push_back(test_dtls_handshake(Botan::TLS::Protocol_Version::DTLS_V10, *basic_creds, policy, server_policy));
+ results.push_back(test_dtls_handshake(Botan::TLS::Protocol_Version::DTLS_V12, *basic_creds, policy, server_policy));
+
+ policy.set("negotiate_encrypt_then_mac", "true");
+ policy.set("ciphers", "AES-128/GCM");
+ server_policy.set("ciphers", "AES-128/GCM");
+ results.push_back(test_tls_handshake(Botan::TLS::Protocol_Version::TLS_V12, *basic_creds, policy, server_policy));
+ results.push_back(test_dtls_handshake(Botan::TLS::Protocol_Version::DTLS_V12, *basic_creds, policy, server_policy));
+
return results;
}