diff options
author | Juraj Somorovsky <[email protected]> | 2016-05-09 00:48:13 +0200 |
---|---|---|
committer | Juraj Somorovsky <[email protected]> | 2016-05-11 07:55:02 +0200 |
commit | 7c7fcecbe6a94ffaba5752175d8da5e33fbf0d7b (patch) | |
tree | 5ed7b29e5d3441c26b8aa6b5520ab589f9e05377 /src | |
parent | 6d327f879c608908ca2c6b9b99f7fd74d498b4ef (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.cpp | 14 | ||||
-rw-r--r-- | src/lib/tls/msg_client_hello.cpp | 6 | ||||
-rw-r--r-- | src/lib/tls/msg_client_kex.cpp | 7 | ||||
-rw-r--r-- | src/lib/tls/msg_server_hello.cpp | 14 | ||||
-rw-r--r-- | src/lib/tls/msg_server_kex.cpp | 18 | ||||
-rw-r--r-- | src/lib/tls/tls_channel.cpp | 6 | ||||
-rw-r--r-- | src/lib/tls/tls_ciphersuite.cpp | 6 | ||||
-rw-r--r-- | src/lib/tls/tls_ciphersuite.h | 5 | ||||
-rw-r--r-- | src/lib/tls/tls_client.cpp | 2 | ||||
-rw-r--r-- | src/lib/tls/tls_extensions.cpp | 15 | ||||
-rw-r--r-- | src/lib/tls/tls_extensions.h | 21 | ||||
-rw-r--r-- | src/lib/tls/tls_messages.h | 11 | ||||
-rw-r--r-- | src/lib/tls/tls_policy.cpp | 2 | ||||
-rw-r--r-- | src/lib/tls/tls_policy.h | 9 | ||||
-rw-r--r-- | src/lib/tls/tls_record.cpp | 265 | ||||
-rw-r--r-- | src/lib/tls/tls_record.h | 7 | ||||
-rw-r--r-- | src/lib/tls/tls_server.cpp | 14 | ||||
-rw-r--r-- | src/lib/tls/tls_session.cpp | 4 | ||||
-rw-r--r-- | src/lib/tls/tls_session.h | 7 | ||||
-rw-r--r-- | src/tests/unit_tls.cpp | 51 |
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; } |