diff options
author | Jack Lloyd <[email protected]> | 2016-10-08 23:40:33 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2016-10-08 23:40:33 -0400 |
commit | 4d1f71b1aa66ec915dd7ce7eab462f1a1faa17b2 (patch) | |
tree | f5572e9db93c8ef51bee535a732885fbecbf1832 /src/lib/tls/tls_cbc | |
parent | 62cd6e3651711f759f870460599596ff5be904a5 (diff) |
Make TLS CBC optional
Diffstat (limited to 'src/lib/tls/tls_cbc')
-rw-r--r-- | src/lib/tls/tls_cbc/info.txt | 5 | ||||
-rw-r--r-- | src/lib/tls/tls_cbc/tls_cbc.cpp | 403 | ||||
-rw-r--r-- | src/lib/tls/tls_cbc/tls_cbc.h | 168 |
3 files changed, 576 insertions, 0 deletions
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 + +<header:internal> +tls_cbc.h +</header:internal> 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 <botan/internal/tls_cbc.h> +#include <botan/internal/rounding.h> +#include <botan/internal/ct_utils.h> +#include <botan/tls_alert.h> +#include <botan/tls_magic.h> +#include <botan/tls_exceptn.h> + +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<byte> TLS_CBC_HMAC_AEAD_Mode::assoc_data_with_len(uint16_t len) + { + std::vector<byte> 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<byte>& 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<uint16_t>(0, enc_size); + ad[12] = get_byte<uint16_t>(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<byte>& 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<byte>(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<byte>(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<u16bit>(static_cast<u16bit>(left), pad_byte) & 0xFF; + pad_invalid |= (delim_mask & (record[i] ^ pad_byte)); + } + + u16bit pad_invalid_mask = CT::expand_mask<u16bit>(pad_invalid); + return CT::select<u16bit>(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<byte> 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<byte> 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<byte>& 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<byte> 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<u16bit>(static_cast<u16bit>(tag_size() + pad_size), static_cast<u16bit>(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<u16bit>(record_len - tag_size() - pad_size); + + mac().update(assoc_data_with_len(plaintext_length)); + mac().update(plaintext_block, plaintext_length); + + std::vector<byte> 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<u16bit>(mac_ok) & CT::expand_mask<u16bit>(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 <botan/aead.h> +#include <botan/block_cipher.h> +#include <botan/mac.h> + +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<byte>& cbc_state() { return m_cbc_state; } + std::vector<byte>& assoc_data() { return m_ad; } + secure_vector<byte>& msg() { return m_msg; } + + std::vector<byte> 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<BlockCipher> m_cipher; + std::unique_ptr<MessageAuthenticationCode> m_mac; + + secure_vector<byte> m_cbc_state; + std::vector<byte> m_ad; + secure_vector<byte> 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<byte>& 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<byte>& final_block, size_t offset = 0) override; + + private: + void cbc_decrypt_record(byte record_contents[], size_t record_len); + }; + +} + +} + +#endif |