From 4d1f71b1aa66ec915dd7ce7eab462f1a1faa17b2 Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Sat, 8 Oct 2016 23:40:33 -0400 Subject: Make TLS CBC optional --- src/lib/tls/info.txt | 1 - src/lib/tls/tls_cbc.cpp | 403 ---------------------------------------- src/lib/tls/tls_cbc.h | 168 ----------------- src/lib/tls/tls_cbc/info.txt | 5 + src/lib/tls/tls_cbc/tls_cbc.cpp | 403 ++++++++++++++++++++++++++++++++++++++++ src/lib/tls/tls_cbc/tls_cbc.h | 168 +++++++++++++++++ src/lib/tls/tls_ciphersuite.cpp | 5 + src/lib/tls/tls_record.cpp | 9 +- src/tests/unit_tls.cpp | 5 + 9 files changed, 594 insertions(+), 573 deletions(-) delete mode 100644 src/lib/tls/tls_cbc.cpp delete mode 100644 src/lib/tls/tls_cbc.h create mode 100644 src/lib/tls/tls_cbc/info.txt create mode 100644 src/lib/tls/tls_cbc/tls_cbc.cpp create mode 100644 src/lib/tls/tls_cbc/tls_cbc.h (limited to 'src') diff --git a/src/lib/tls/info.txt b/src/lib/tls/info.txt index ad0d266fa..667726318 100644 --- a/src/lib/tls/info.txt +++ b/src/lib/tls/info.txt @@ -22,7 +22,6 @@ tls_version.h -tls_cbc.h tls_extensions.h tls_handshake_hash.h tls_handshake_io.h diff --git a/src/lib/tls/tls_cbc.cpp b/src/lib/tls/tls_cbc.cpp deleted file mode 100644 index c7203003b..000000000 --- a/src/lib/tls/tls_cbc.cpp +++ /dev/null @@ -1,403 +0,0 @@ -/* -* TLS CBC Record Handling -* (C) 2012,2013,2014,2015,2016 Jack Lloyd -* 2016 Juraj Somorovsky -* 2016 Matthias Gierlings -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include -#include -#include -#include -#include -#include - -namespace Botan { - -namespace TLS { - -/* -* TLS_CBC_HMAC_AEAD_Mode Constructor -*/ -TLS_CBC_HMAC_AEAD_Mode::TLS_CBC_HMAC_AEAD_Mode(const std::string& cipher_name, - size_t cipher_keylen, - const std::string& mac_name, - size_t mac_keylen, - bool use_explicit_iv, - bool use_encrypt_then_mac) : - m_cipher_name(cipher_name), - m_mac_name(mac_name), - m_cipher_keylen(cipher_keylen), - m_mac_keylen(mac_keylen), - m_use_encrypt_then_mac(use_encrypt_then_mac) - { - m_cipher = BlockCipher::create(m_cipher_name); - if(!m_cipher) - throw Algorithm_Not_Found(m_cipher_name); - - m_mac = MessageAuthenticationCode::create("HMAC(" + m_mac_name + ")"); - if(!m_mac) - throw Algorithm_Not_Found("HMAC(" + m_mac_name + ")"); - - m_tag_size = m_mac->output_length(); - m_block_size = m_cipher->block_size(); - - m_iv_size = use_explicit_iv ? m_block_size : 0; - } - -void TLS_CBC_HMAC_AEAD_Mode::clear() - { - cipher().clear(); - mac().clear(); - cbc_state().clear(); - } - -std::string TLS_CBC_HMAC_AEAD_Mode::name() const - { - return "TLS_CBC(" + m_cipher_name + "," + m_mac_name + ")"; - } - -size_t TLS_CBC_HMAC_AEAD_Mode::update_granularity() const - { - return 1; // just buffers anyway - } - -bool TLS_CBC_HMAC_AEAD_Mode::valid_nonce_length(size_t nl) const - { - if(m_cbc_state.empty()) - return nl == block_size(); - return nl == iv_size(); - } - -Key_Length_Specification TLS_CBC_HMAC_AEAD_Mode::key_spec() const - { - return Key_Length_Specification(m_cipher_keylen + m_mac_keylen); - } - -void TLS_CBC_HMAC_AEAD_Mode::key_schedule(const byte key[], size_t keylen) - { - // Both keys are of fixed length specified by the ciphersuite - - if(keylen != m_cipher_keylen + m_mac_keylen) - throw Invalid_Key_Length(name(), keylen); - - cipher().set_key(&key[0], m_cipher_keylen); - mac().set_key(&key[m_cipher_keylen], m_mac_keylen); - } - -void TLS_CBC_HMAC_AEAD_Mode::start_msg(const byte nonce[], size_t nonce_len) - { - if(!valid_nonce_length(nonce_len)) - { - throw Invalid_IV_Length(name(), nonce_len); - } - - m_msg.clear(); - - if(nonce_len > 0) - { - m_cbc_state.assign(nonce, nonce + nonce_len); - } - } - -size_t TLS_CBC_HMAC_AEAD_Mode::process(byte buf[], size_t sz) - { - m_msg.insert(m_msg.end(), buf, buf + sz); - return 0; - } - -std::vector TLS_CBC_HMAC_AEAD_Mode::assoc_data_with_len(uint16_t len) - { - std::vector ad = m_ad; - BOTAN_ASSERT(ad.size() == 13, "Expected AAD size"); - ad[11] = get_byte(0, len); - ad[12] = get_byte(1, len); - return ad; - } - -void TLS_CBC_HMAC_AEAD_Mode::set_associated_data(const byte ad[], size_t ad_len) - { - if(ad_len != 13) - throw Exception("Invalid TLS AEAD associated data length"); - m_ad.assign(ad, ad + ad_len); - } - -void TLS_CBC_HMAC_AEAD_Encryption::set_associated_data(const byte ad[], size_t ad_len) - { - TLS_CBC_HMAC_AEAD_Mode::set_associated_data(ad, ad_len); - - if(use_encrypt_then_mac()) - { - std::vector& ad = assoc_data(); - // AAD hack for EtM - size_t pt_size = make_u16bit(ad[11], ad[12]); - size_t enc_size = round_up(iv_size() + pt_size + 1, block_size()); - ad[11] = get_byte(0, enc_size); - ad[12] = get_byte(1, enc_size); - } - } - -void TLS_CBC_HMAC_AEAD_Encryption::cbc_encrypt_record(byte buf[], size_t buf_size) - { - const size_t blocks = buf_size / block_size(); - BOTAN_ASSERT(buf_size % block_size() == 0, "Valid CBC input"); - - xor_buf(buf, cbc_state().data(), block_size()); - cipher().encrypt(buf); - - for(size_t i = 1; i < blocks; ++i) - { - xor_buf(&buf[block_size()*i], &buf[block_size()*(i-1)], block_size()); - cipher().encrypt(&buf[block_size()*i]); - } - - cbc_state().assign(&buf[block_size()*(blocks-1)], - &buf[block_size()*blocks]); - } - -size_t TLS_CBC_HMAC_AEAD_Encryption::output_length(size_t input_length) const - { - return round_up(input_length + 1 + (use_encrypt_then_mac() ? 0 : tag_size()), block_size()) + - (use_encrypt_then_mac() ? tag_size() : 0); - } - -void TLS_CBC_HMAC_AEAD_Encryption::finish(secure_vector& buffer, size_t offset) - { - update(buffer, offset); - buffer.resize(offset); // truncate, leaving just header - const size_t header_size = offset; - - buffer.insert(buffer.end(), msg().begin(), msg().end()); - - const size_t input_size = msg().size() + 1 + (use_encrypt_then_mac() ? 0 : tag_size()); - const size_t enc_size = round_up(input_size, block_size()); - const size_t pad_val = enc_size - input_size; - const size_t buf_size = enc_size + (use_encrypt_then_mac() ? tag_size() : 0); - - BOTAN_ASSERT(enc_size % block_size() == 0, - "Buffer is an even multiple of block size"); - - mac().update(assoc_data()); - - if(use_encrypt_then_mac()) - { - if(iv_size() > 0) - { - mac().update(cbc_state()); - } - - for(size_t i = 0; i != pad_val + 1; ++i) - buffer.push_back(static_cast(pad_val)); - cbc_encrypt_record(&buffer[header_size], enc_size); - } - - // EtM also uses ciphertext size instead of plaintext size for AEAD input - const byte* mac_input = (use_encrypt_then_mac() ? &buffer[header_size] : msg().data()); - const size_t mac_input_len = (use_encrypt_then_mac() ? enc_size : msg().size()); - - mac().update(mac_input, mac_input_len); - - buffer.resize(buffer.size() + tag_size()); - mac().final(&buffer[buffer.size() - tag_size()]); - - if(use_encrypt_then_mac() == false) - { - for(size_t i = 0; i != pad_val + 1; ++i) - buffer.push_back(static_cast(pad_val)); - cbc_encrypt_record(&buffer[header_size], buf_size); - } - } - -namespace { - - -/* -* Checks the TLS padding. Returns 0 if the padding is invalid (we -* count the padding_length field as part of the padding size so a -* valid padding will always be at least one byte long), or the length -* of the padding otherwise. This is actually padding_length + 1 -* because both the padding and padding_length fields are padding from -* our perspective. -* -* Returning 0 in the error case should ensure the MAC check will fail. -* This approach is suggested in section 6.2.3.2 of RFC 5246. -*/ -u16bit check_tls_padding(const byte record[], size_t record_len) - { - /* - * TLS v1.0 and up require all the padding bytes be the same value - * and allows up to 255 bytes. - */ - - const byte pad_byte = record[(record_len-1)]; - - byte pad_invalid = 0; - for(size_t i = 0; i != record_len; ++i) - { - const size_t left = record_len - i - 2; - const byte delim_mask = CT::is_less(static_cast(left), pad_byte) & 0xFF; - pad_invalid |= (delim_mask & (record[i] ^ pad_byte)); - } - - u16bit pad_invalid_mask = CT::expand_mask(pad_invalid); - return CT::select(pad_invalid_mask, 0, pad_byte + 1); - } - -} - -void TLS_CBC_HMAC_AEAD_Decryption::cbc_decrypt_record(byte record_contents[], size_t record_len) - { - BOTAN_ASSERT(record_len % block_size() == 0, - "Buffer is an even multiple of block size"); - - const size_t blocks = record_len / block_size(); - - BOTAN_ASSERT(blocks >= 1, "At least one ciphertext block"); - - byte* buf = record_contents; - - secure_vector last_ciphertext(block_size()); - copy_mem(last_ciphertext.data(), buf, block_size()); - - cipher().decrypt(buf); - xor_buf(buf, cbc_state().data(), block_size()); - - secure_vector last_ciphertext2; - - for(size_t i = 1; i < blocks; ++i) - { - last_ciphertext2.assign(&buf[block_size()*i], &buf[block_size()*(i+1)]); - cipher().decrypt(&buf[block_size()*i]); - xor_buf(&buf[block_size()*i], last_ciphertext.data(), block_size()); - std::swap(last_ciphertext, last_ciphertext2); - } - - cbc_state().assign(last_ciphertext.begin(), last_ciphertext.end()); - } - -size_t TLS_CBC_HMAC_AEAD_Decryption::output_length(size_t) const - { - /* - * We don't know this because the padding is arbitrary - */ - return 0; - } - -void TLS_CBC_HMAC_AEAD_Decryption::finish(secure_vector& buffer, size_t offset) - { - update(buffer, offset); - buffer.resize(offset); - - const size_t record_len = msg().size(); - byte* record_contents = msg().data(); - - // This early exit does not leak info because all the values compared are public - if(record_len < tag_size() || - (record_len - (use_encrypt_then_mac() ? tag_size() : 0)) % block_size() != 0) - { - throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); - } - - if(use_encrypt_then_mac()) - { - const size_t enc_size = record_len - tag_size(); - - mac().update(assoc_data_with_len(iv_size() + enc_size)); - if(iv_size() > 0) - { - mac().update(cbc_state()); - } - mac().update(record_contents, enc_size); - - std::vector mac_buf(tag_size()); - 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(), tag_size()); - - if(!mac_ok) - { - throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); - } - - cbc_decrypt_record(record_contents, enc_size); - - // 0 if padding was invalid, otherwise 1 + padding_bytes - u16bit pad_size = check_tls_padding(record_contents, enc_size); - - // No oracle here, whoever sent us this had the key since MAC check passed - if(pad_size == 0) - { - throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); - } - - const byte* plaintext_block = &record_contents[0]; - const u16bit plaintext_length = enc_size - pad_size; - - buffer.insert(buffer.end(), plaintext_block, plaintext_block + plaintext_length); - } - else - { - uint8_t* record_contents = msg().data(); - const size_t record_len = msg().size(); - - CT::poison(record_contents, record_len); - - cbc_decrypt_record(record_contents, record_len); - - // 0 if padding was invalid, otherwise 1 + padding_bytes - u16bit pad_size = check_tls_padding(record_contents, record_len); - - /* - 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 how OpenSSL's countermeasure for fixing BEAST in TLS 1.0 CBC works - (sending empty records, instead of 1/(n-1) splitting) - */ - - const u16bit size_ok_mask = CT::is_lte(static_cast(tag_size() + pad_size), static_cast(record_len + 1)); - pad_size &= size_ok_mask; - - 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); - - const byte* plaintext_block = &record_contents[0]; - const u16bit plaintext_length = static_cast(record_len - tag_size() - pad_size); - - mac().update(assoc_data_with_len(plaintext_length)); - mac().update(plaintext_block, plaintext_length); - - std::vector mac_buf(tag_size()); - mac().final(mac_buf.data()); - - const size_t mac_offset = record_len - (tag_size() + pad_size); - - const bool mac_ok = same_mem(&record_contents[mac_offset], mac_buf.data(), tag_size()); - - const u16bit ok_mask = size_ok_mask & CT::expand_mask(mac_ok) & CT::expand_mask(pad_size); - - CT::unpoison(ok_mask); - - if(ok_mask) - { - buffer.insert(buffer.end(), plaintext_block, plaintext_block + plaintext_length); - } - else - { - throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); - } - } - } - -} - -} diff --git a/src/lib/tls/tls_cbc.h b/src/lib/tls/tls_cbc.h deleted file mode 100644 index 90b54bb5a..000000000 --- a/src/lib/tls/tls_cbc.h +++ /dev/null @@ -1,168 +0,0 @@ -/* -* TLS CBC+HMAC AEAD -* (C) 2016 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_TLS_CBC_HMAC_AEAD_H__ -#define BOTAN_TLS_CBC_HMAC_AEAD_H__ - -#include -#include -#include - -namespace Botan { - -namespace TLS { - -/** -* TLS CBC+HMAC AEAD base class (GenericBlockCipher in TLS spec) -* This is the weird TLS-specific mode, not for general consumption. -*/ -class TLS_CBC_HMAC_AEAD_Mode : public AEAD_Mode - { - public: - size_t process(uint8_t buf[], size_t sz) override final; - - std::string name() const override final; - - void set_associated_data(const byte ad[], size_t ad_len) override; - - size_t update_granularity() const override final; - - Key_Length_Specification key_spec() const override final; - - bool valid_nonce_length(size_t nl) const override final; - - size_t tag_size() const override final { return m_tag_size; } - - size_t default_nonce_length() const override final { return m_iv_size; } - - void clear() override final; - - protected: - TLS_CBC_HMAC_AEAD_Mode(const std::string& cipher_name, - size_t cipher_keylen, - const std::string& mac_name, - size_t mac_keylen, - bool use_explicit_iv, - bool use_encrypt_then_mac); - - size_t cipher_keylen() const { return m_cipher_keylen; } - size_t mac_keylen() const { return m_mac_keylen; } - size_t iv_size() const { return m_iv_size; } - size_t block_size() const { return m_block_size; } - - bool use_encrypt_then_mac() const { return m_use_encrypt_then_mac; } - - BlockCipher& cipher() const - { - BOTAN_ASSERT_NONNULL(m_cipher); - return *m_cipher; - } - - MessageAuthenticationCode& mac() const - { - BOTAN_ASSERT_NONNULL(m_mac); - return *m_mac; - } - - secure_vector& cbc_state() { return m_cbc_state; } - std::vector& assoc_data() { return m_ad; } - secure_vector& msg() { return m_msg; } - - std::vector assoc_data_with_len(uint16_t len); - - private: - void start_msg(const byte nonce[], size_t nonce_len) override final; - - void key_schedule(const byte key[], size_t length) override final; - - const std::string m_cipher_name; - const std::string m_mac_name; - size_t m_cipher_keylen; - size_t m_mac_keylen; - size_t m_iv_size; - size_t m_tag_size; - size_t m_block_size; - bool m_use_encrypt_then_mac; - - std::unique_ptr m_cipher; - std::unique_ptr m_mac; - - secure_vector m_cbc_state; - std::vector m_ad; - secure_vector m_msg; - }; - -/** -* TLS_CBC_HMAC_AEAD Encryption -*/ -class BOTAN_DLL TLS_CBC_HMAC_AEAD_Encryption final : public TLS_CBC_HMAC_AEAD_Mode - { - public: - /** - */ - TLS_CBC_HMAC_AEAD_Encryption(const std::string& cipher_algo, - const size_t cipher_keylen, - const std::string& mac_algo, - const size_t mac_keylen, - bool use_explicit_iv, - bool use_encrypt_then_mac) : - TLS_CBC_HMAC_AEAD_Mode(cipher_algo, - cipher_keylen, - mac_algo, - mac_keylen, - use_explicit_iv, - use_encrypt_then_mac) - {} - - void set_associated_data(const byte ad[], size_t ad_len) override; - - size_t output_length(size_t input_length) const override; - - size_t minimum_final_size() const override { return 0; } - - void finish(secure_vector& final_block, size_t offset = 0) override; - private: - void cbc_encrypt_record(byte record_contents[], size_t record_len); - }; - -/** -* TLS_CBC_HMAC_AEAD Decryption -*/ -class BOTAN_DLL TLS_CBC_HMAC_AEAD_Decryption final : public TLS_CBC_HMAC_AEAD_Mode - { - public: - /** - */ - TLS_CBC_HMAC_AEAD_Decryption(const std::string& cipher_algo, - const size_t cipher_keylen, - const std::string& mac_algo, - const size_t mac_keylen, - bool use_explicit_iv, - bool use_encrypt_then_mac) : - TLS_CBC_HMAC_AEAD_Mode(cipher_algo, - cipher_keylen, - mac_algo, - mac_keylen, - use_explicit_iv, - use_encrypt_then_mac) - {} - - size_t output_length(size_t input_length) const override; - - size_t minimum_final_size() const override { return tag_size(); } - - void finish(secure_vector& final_block, size_t offset = 0) override; - - private: - void cbc_decrypt_record(byte record_contents[], size_t record_len); - }; - -} - -} - -#endif diff --git a/src/lib/tls/tls_cbc/info.txt b/src/lib/tls/tls_cbc/info.txt new file mode 100644 index 000000000..0a2827e71 --- /dev/null +++ b/src/lib/tls/tls_cbc/info.txt @@ -0,0 +1,5 @@ +define TLS_CBC 20161008 + + +tls_cbc.h + diff --git a/src/lib/tls/tls_cbc/tls_cbc.cpp b/src/lib/tls/tls_cbc/tls_cbc.cpp new file mode 100644 index 000000000..c7203003b --- /dev/null +++ b/src/lib/tls/tls_cbc/tls_cbc.cpp @@ -0,0 +1,403 @@ +/* +* TLS CBC Record Handling +* (C) 2012,2013,2014,2015,2016 Jack Lloyd +* 2016 Juraj Somorovsky +* 2016 Matthias Gierlings +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include +#include +#include +#include +#include +#include + +namespace Botan { + +namespace TLS { + +/* +* TLS_CBC_HMAC_AEAD_Mode Constructor +*/ +TLS_CBC_HMAC_AEAD_Mode::TLS_CBC_HMAC_AEAD_Mode(const std::string& cipher_name, + size_t cipher_keylen, + const std::string& mac_name, + size_t mac_keylen, + bool use_explicit_iv, + bool use_encrypt_then_mac) : + m_cipher_name(cipher_name), + m_mac_name(mac_name), + m_cipher_keylen(cipher_keylen), + m_mac_keylen(mac_keylen), + m_use_encrypt_then_mac(use_encrypt_then_mac) + { + m_cipher = BlockCipher::create(m_cipher_name); + if(!m_cipher) + throw Algorithm_Not_Found(m_cipher_name); + + m_mac = MessageAuthenticationCode::create("HMAC(" + m_mac_name + ")"); + if(!m_mac) + throw Algorithm_Not_Found("HMAC(" + m_mac_name + ")"); + + m_tag_size = m_mac->output_length(); + m_block_size = m_cipher->block_size(); + + m_iv_size = use_explicit_iv ? m_block_size : 0; + } + +void TLS_CBC_HMAC_AEAD_Mode::clear() + { + cipher().clear(); + mac().clear(); + cbc_state().clear(); + } + +std::string TLS_CBC_HMAC_AEAD_Mode::name() const + { + return "TLS_CBC(" + m_cipher_name + "," + m_mac_name + ")"; + } + +size_t TLS_CBC_HMAC_AEAD_Mode::update_granularity() const + { + return 1; // just buffers anyway + } + +bool TLS_CBC_HMAC_AEAD_Mode::valid_nonce_length(size_t nl) const + { + if(m_cbc_state.empty()) + return nl == block_size(); + return nl == iv_size(); + } + +Key_Length_Specification TLS_CBC_HMAC_AEAD_Mode::key_spec() const + { + return Key_Length_Specification(m_cipher_keylen + m_mac_keylen); + } + +void TLS_CBC_HMAC_AEAD_Mode::key_schedule(const byte key[], size_t keylen) + { + // Both keys are of fixed length specified by the ciphersuite + + if(keylen != m_cipher_keylen + m_mac_keylen) + throw Invalid_Key_Length(name(), keylen); + + cipher().set_key(&key[0], m_cipher_keylen); + mac().set_key(&key[m_cipher_keylen], m_mac_keylen); + } + +void TLS_CBC_HMAC_AEAD_Mode::start_msg(const byte nonce[], size_t nonce_len) + { + if(!valid_nonce_length(nonce_len)) + { + throw Invalid_IV_Length(name(), nonce_len); + } + + m_msg.clear(); + + if(nonce_len > 0) + { + m_cbc_state.assign(nonce, nonce + nonce_len); + } + } + +size_t TLS_CBC_HMAC_AEAD_Mode::process(byte buf[], size_t sz) + { + m_msg.insert(m_msg.end(), buf, buf + sz); + return 0; + } + +std::vector TLS_CBC_HMAC_AEAD_Mode::assoc_data_with_len(uint16_t len) + { + std::vector ad = m_ad; + BOTAN_ASSERT(ad.size() == 13, "Expected AAD size"); + ad[11] = get_byte(0, len); + ad[12] = get_byte(1, len); + return ad; + } + +void TLS_CBC_HMAC_AEAD_Mode::set_associated_data(const byte ad[], size_t ad_len) + { + if(ad_len != 13) + throw Exception("Invalid TLS AEAD associated data length"); + m_ad.assign(ad, ad + ad_len); + } + +void TLS_CBC_HMAC_AEAD_Encryption::set_associated_data(const byte ad[], size_t ad_len) + { + TLS_CBC_HMAC_AEAD_Mode::set_associated_data(ad, ad_len); + + if(use_encrypt_then_mac()) + { + std::vector& ad = assoc_data(); + // AAD hack for EtM + size_t pt_size = make_u16bit(ad[11], ad[12]); + size_t enc_size = round_up(iv_size() + pt_size + 1, block_size()); + ad[11] = get_byte(0, enc_size); + ad[12] = get_byte(1, enc_size); + } + } + +void TLS_CBC_HMAC_AEAD_Encryption::cbc_encrypt_record(byte buf[], size_t buf_size) + { + const size_t blocks = buf_size / block_size(); + BOTAN_ASSERT(buf_size % block_size() == 0, "Valid CBC input"); + + xor_buf(buf, cbc_state().data(), block_size()); + cipher().encrypt(buf); + + for(size_t i = 1; i < blocks; ++i) + { + xor_buf(&buf[block_size()*i], &buf[block_size()*(i-1)], block_size()); + cipher().encrypt(&buf[block_size()*i]); + } + + cbc_state().assign(&buf[block_size()*(blocks-1)], + &buf[block_size()*blocks]); + } + +size_t TLS_CBC_HMAC_AEAD_Encryption::output_length(size_t input_length) const + { + return round_up(input_length + 1 + (use_encrypt_then_mac() ? 0 : tag_size()), block_size()) + + (use_encrypt_then_mac() ? tag_size() : 0); + } + +void TLS_CBC_HMAC_AEAD_Encryption::finish(secure_vector& buffer, size_t offset) + { + update(buffer, offset); + buffer.resize(offset); // truncate, leaving just header + const size_t header_size = offset; + + buffer.insert(buffer.end(), msg().begin(), msg().end()); + + const size_t input_size = msg().size() + 1 + (use_encrypt_then_mac() ? 0 : tag_size()); + const size_t enc_size = round_up(input_size, block_size()); + const size_t pad_val = enc_size - input_size; + const size_t buf_size = enc_size + (use_encrypt_then_mac() ? tag_size() : 0); + + BOTAN_ASSERT(enc_size % block_size() == 0, + "Buffer is an even multiple of block size"); + + mac().update(assoc_data()); + + if(use_encrypt_then_mac()) + { + if(iv_size() > 0) + { + mac().update(cbc_state()); + } + + for(size_t i = 0; i != pad_val + 1; ++i) + buffer.push_back(static_cast(pad_val)); + cbc_encrypt_record(&buffer[header_size], enc_size); + } + + // EtM also uses ciphertext size instead of plaintext size for AEAD input + const byte* mac_input = (use_encrypt_then_mac() ? &buffer[header_size] : msg().data()); + const size_t mac_input_len = (use_encrypt_then_mac() ? enc_size : msg().size()); + + mac().update(mac_input, mac_input_len); + + buffer.resize(buffer.size() + tag_size()); + mac().final(&buffer[buffer.size() - tag_size()]); + + if(use_encrypt_then_mac() == false) + { + for(size_t i = 0; i != pad_val + 1; ++i) + buffer.push_back(static_cast(pad_val)); + cbc_encrypt_record(&buffer[header_size], buf_size); + } + } + +namespace { + + +/* +* Checks the TLS padding. Returns 0 if the padding is invalid (we +* count the padding_length field as part of the padding size so a +* valid padding will always be at least one byte long), or the length +* of the padding otherwise. This is actually padding_length + 1 +* because both the padding and padding_length fields are padding from +* our perspective. +* +* Returning 0 in the error case should ensure the MAC check will fail. +* This approach is suggested in section 6.2.3.2 of RFC 5246. +*/ +u16bit check_tls_padding(const byte record[], size_t record_len) + { + /* + * TLS v1.0 and up require all the padding bytes be the same value + * and allows up to 255 bytes. + */ + + const byte pad_byte = record[(record_len-1)]; + + byte pad_invalid = 0; + for(size_t i = 0; i != record_len; ++i) + { + const size_t left = record_len - i - 2; + const byte delim_mask = CT::is_less(static_cast(left), pad_byte) & 0xFF; + pad_invalid |= (delim_mask & (record[i] ^ pad_byte)); + } + + u16bit pad_invalid_mask = CT::expand_mask(pad_invalid); + return CT::select(pad_invalid_mask, 0, pad_byte + 1); + } + +} + +void TLS_CBC_HMAC_AEAD_Decryption::cbc_decrypt_record(byte record_contents[], size_t record_len) + { + BOTAN_ASSERT(record_len % block_size() == 0, + "Buffer is an even multiple of block size"); + + const size_t blocks = record_len / block_size(); + + BOTAN_ASSERT(blocks >= 1, "At least one ciphertext block"); + + byte* buf = record_contents; + + secure_vector last_ciphertext(block_size()); + copy_mem(last_ciphertext.data(), buf, block_size()); + + cipher().decrypt(buf); + xor_buf(buf, cbc_state().data(), block_size()); + + secure_vector last_ciphertext2; + + for(size_t i = 1; i < blocks; ++i) + { + last_ciphertext2.assign(&buf[block_size()*i], &buf[block_size()*(i+1)]); + cipher().decrypt(&buf[block_size()*i]); + xor_buf(&buf[block_size()*i], last_ciphertext.data(), block_size()); + std::swap(last_ciphertext, last_ciphertext2); + } + + cbc_state().assign(last_ciphertext.begin(), last_ciphertext.end()); + } + +size_t TLS_CBC_HMAC_AEAD_Decryption::output_length(size_t) const + { + /* + * We don't know this because the padding is arbitrary + */ + return 0; + } + +void TLS_CBC_HMAC_AEAD_Decryption::finish(secure_vector& buffer, size_t offset) + { + update(buffer, offset); + buffer.resize(offset); + + const size_t record_len = msg().size(); + byte* record_contents = msg().data(); + + // This early exit does not leak info because all the values compared are public + if(record_len < tag_size() || + (record_len - (use_encrypt_then_mac() ? tag_size() : 0)) % block_size() != 0) + { + throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); + } + + if(use_encrypt_then_mac()) + { + const size_t enc_size = record_len - tag_size(); + + mac().update(assoc_data_with_len(iv_size() + enc_size)); + if(iv_size() > 0) + { + mac().update(cbc_state()); + } + mac().update(record_contents, enc_size); + + std::vector mac_buf(tag_size()); + 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(), tag_size()); + + if(!mac_ok) + { + throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); + } + + cbc_decrypt_record(record_contents, enc_size); + + // 0 if padding was invalid, otherwise 1 + padding_bytes + u16bit pad_size = check_tls_padding(record_contents, enc_size); + + // No oracle here, whoever sent us this had the key since MAC check passed + if(pad_size == 0) + { + throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); + } + + const byte* plaintext_block = &record_contents[0]; + const u16bit plaintext_length = enc_size - pad_size; + + buffer.insert(buffer.end(), plaintext_block, plaintext_block + plaintext_length); + } + else + { + uint8_t* record_contents = msg().data(); + const size_t record_len = msg().size(); + + CT::poison(record_contents, record_len); + + cbc_decrypt_record(record_contents, record_len); + + // 0 if padding was invalid, otherwise 1 + padding_bytes + u16bit pad_size = check_tls_padding(record_contents, record_len); + + /* + 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 how OpenSSL's countermeasure for fixing BEAST in TLS 1.0 CBC works + (sending empty records, instead of 1/(n-1) splitting) + */ + + const u16bit size_ok_mask = CT::is_lte(static_cast(tag_size() + pad_size), static_cast(record_len + 1)); + pad_size &= size_ok_mask; + + 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); + + const byte* plaintext_block = &record_contents[0]; + const u16bit plaintext_length = static_cast(record_len - tag_size() - pad_size); + + mac().update(assoc_data_with_len(plaintext_length)); + mac().update(plaintext_block, plaintext_length); + + std::vector mac_buf(tag_size()); + mac().final(mac_buf.data()); + + const size_t mac_offset = record_len - (tag_size() + pad_size); + + const bool mac_ok = same_mem(&record_contents[mac_offset], mac_buf.data(), tag_size()); + + const u16bit ok_mask = size_ok_mask & CT::expand_mask(mac_ok) & CT::expand_mask(pad_size); + + CT::unpoison(ok_mask); + + if(ok_mask) + { + buffer.insert(buffer.end(), plaintext_block, plaintext_block + plaintext_length); + } + else + { + throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); + } + } + } + +} + +} diff --git a/src/lib/tls/tls_cbc/tls_cbc.h b/src/lib/tls/tls_cbc/tls_cbc.h new file mode 100644 index 000000000..90b54bb5a --- /dev/null +++ b/src/lib/tls/tls_cbc/tls_cbc.h @@ -0,0 +1,168 @@ +/* +* TLS CBC+HMAC AEAD +* (C) 2016 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_CBC_HMAC_AEAD_H__ +#define BOTAN_TLS_CBC_HMAC_AEAD_H__ + +#include +#include +#include + +namespace Botan { + +namespace TLS { + +/** +* TLS CBC+HMAC AEAD base class (GenericBlockCipher in TLS spec) +* This is the weird TLS-specific mode, not for general consumption. +*/ +class TLS_CBC_HMAC_AEAD_Mode : public AEAD_Mode + { + public: + size_t process(uint8_t buf[], size_t sz) override final; + + std::string name() const override final; + + void set_associated_data(const byte ad[], size_t ad_len) override; + + size_t update_granularity() const override final; + + Key_Length_Specification key_spec() const override final; + + bool valid_nonce_length(size_t nl) const override final; + + size_t tag_size() const override final { return m_tag_size; } + + size_t default_nonce_length() const override final { return m_iv_size; } + + void clear() override final; + + protected: + TLS_CBC_HMAC_AEAD_Mode(const std::string& cipher_name, + size_t cipher_keylen, + const std::string& mac_name, + size_t mac_keylen, + bool use_explicit_iv, + bool use_encrypt_then_mac); + + size_t cipher_keylen() const { return m_cipher_keylen; } + size_t mac_keylen() const { return m_mac_keylen; } + size_t iv_size() const { return m_iv_size; } + size_t block_size() const { return m_block_size; } + + bool use_encrypt_then_mac() const { return m_use_encrypt_then_mac; } + + BlockCipher& cipher() const + { + BOTAN_ASSERT_NONNULL(m_cipher); + return *m_cipher; + } + + MessageAuthenticationCode& mac() const + { + BOTAN_ASSERT_NONNULL(m_mac); + return *m_mac; + } + + secure_vector& cbc_state() { return m_cbc_state; } + std::vector& assoc_data() { return m_ad; } + secure_vector& msg() { return m_msg; } + + std::vector assoc_data_with_len(uint16_t len); + + private: + void start_msg(const byte nonce[], size_t nonce_len) override final; + + void key_schedule(const byte key[], size_t length) override final; + + const std::string m_cipher_name; + const std::string m_mac_name; + size_t m_cipher_keylen; + size_t m_mac_keylen; + size_t m_iv_size; + size_t m_tag_size; + size_t m_block_size; + bool m_use_encrypt_then_mac; + + std::unique_ptr m_cipher; + std::unique_ptr m_mac; + + secure_vector m_cbc_state; + std::vector m_ad; + secure_vector m_msg; + }; + +/** +* TLS_CBC_HMAC_AEAD Encryption +*/ +class BOTAN_DLL TLS_CBC_HMAC_AEAD_Encryption final : public TLS_CBC_HMAC_AEAD_Mode + { + public: + /** + */ + TLS_CBC_HMAC_AEAD_Encryption(const std::string& cipher_algo, + const size_t cipher_keylen, + const std::string& mac_algo, + const size_t mac_keylen, + bool use_explicit_iv, + bool use_encrypt_then_mac) : + TLS_CBC_HMAC_AEAD_Mode(cipher_algo, + cipher_keylen, + mac_algo, + mac_keylen, + use_explicit_iv, + use_encrypt_then_mac) + {} + + void set_associated_data(const byte ad[], size_t ad_len) override; + + size_t output_length(size_t input_length) const override; + + size_t minimum_final_size() const override { return 0; } + + void finish(secure_vector& final_block, size_t offset = 0) override; + private: + void cbc_encrypt_record(byte record_contents[], size_t record_len); + }; + +/** +* TLS_CBC_HMAC_AEAD Decryption +*/ +class BOTAN_DLL TLS_CBC_HMAC_AEAD_Decryption final : public TLS_CBC_HMAC_AEAD_Mode + { + public: + /** + */ + TLS_CBC_HMAC_AEAD_Decryption(const std::string& cipher_algo, + const size_t cipher_keylen, + const std::string& mac_algo, + const size_t mac_keylen, + bool use_explicit_iv, + bool use_encrypt_then_mac) : + TLS_CBC_HMAC_AEAD_Mode(cipher_algo, + cipher_keylen, + mac_algo, + mac_keylen, + use_explicit_iv, + use_encrypt_then_mac) + {} + + size_t output_length(size_t input_length) const override; + + size_t minimum_final_size() const override { return tag_size(); } + + void finish(secure_vector& final_block, size_t offset = 0) override; + + private: + void cbc_decrypt_record(byte record_contents[], size_t record_len); + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_ciphersuite.cpp b/src/lib/tls/tls_ciphersuite.cpp index 9a52e0e0e..aa00334c5 100644 --- a/src/lib/tls/tls_ciphersuite.cpp +++ b/src/lib/tls/tls_ciphersuite.cpp @@ -78,6 +78,11 @@ bool Ciphersuite::is_usable() const if(!have_hash(prf_algo())) return false; +#if !defined(BOTAN_HAS_TLS_CBC) + if(cbc_ciphersuite()) + return false; +#endif + if(mac_algo() == "AEAD") { if(cipher_algo() == "ChaCha20Poly1305") diff --git a/src/lib/tls/tls_record.cpp b/src/lib/tls/tls_record.cpp index 0bee24e34..5eef2b4e2 100644 --- a/src/lib/tls/tls_record.cpp +++ b/src/lib/tls/tls_record.cpp @@ -13,11 +13,14 @@ #include #include #include -#include #include #include #include +#if defined(BOTAN_HAS_TLS_CBC) + #include +#endif + namespace Botan { namespace TLS { @@ -70,6 +73,7 @@ Connection_Cipher_State::Connection_Cipher_State(Protocol_Version version, } else { +#if defined(BOTAN_HAS_TLS_CBC) // legacy CBC+HMAC mode if(our_side) { @@ -99,6 +103,9 @@ Connection_Cipher_State::Connection_Cipher_State(Protocol_Version version, m_nonce_bytes_from_record = m_nonce_bytes_from_handshake; else if(our_side == false) m_aead->start(iv.bits_of()); +#else + throw Exception("Negotiated disabled TLS CBC+HMAC ciphersuite"); +#endif } } diff --git a/src/tests/unit_tls.cpp b/src/tests/unit_tls.cpp index f869f426b..49fb07131 100644 --- a/src/tests/unit_tls.cpp +++ b/src/tests/unit_tls.cpp @@ -880,6 +880,7 @@ class TLS_Unit_Tests : public Test std::unique_ptr creds(create_creds(rng)); std::vector results; +#if defined(BOTAN_HAS_TLS_CBC) for(std::string etm_setting : { "true", "false" }) { test_all_versions(results, *creds, "RSA", "AES-128", "SHA-256 SHA-1", etm_setting); @@ -904,6 +905,8 @@ class TLS_Unit_Tests : public Test } test_modern_versions(results, *creds, "DH", "AES-128", "SHA-256"); +#endif + test_modern_versions(results, *creds, "RSA", "AES-128/GCM"); test_modern_versions(results, *creds, "ECDH", "AES-128/GCM"); test_modern_versions(results, *creds, "ECDH", "AES-128/GCM", "AEAD", @@ -927,9 +930,11 @@ class TLS_Unit_Tests : public Test test_modern_versions(results, *creds, "PSK", "AES-128/CCM(8)"); #endif +#if defined(BOTAN_HAS_TLS_CBC) // For whatever reason no (EC)DHE_PSK GCM ciphersuites are defined test_modern_versions(results, *creds, "ECDHE_PSK", "AES-128", "SHA-256"); test_modern_versions(results, *creds, "DHE_PSK", "AES-128", "SHA-1"); +#endif return results; } -- cgit v1.2.3