diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/third_party/botan/src/lib/tls/tls_cbc/tls_cbc.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/third_party/botan/src/lib/tls/tls_cbc/tls_cbc.cpp')
-rw-r--r-- | comm/third_party/botan/src/lib/tls/tls_cbc/tls_cbc.cpp | 499 |
1 files changed, 499 insertions, 0 deletions
diff --git a/comm/third_party/botan/src/lib/tls/tls_cbc/tls_cbc.cpp b/comm/third_party/botan/src/lib/tls/tls_cbc/tls_cbc.cpp new file mode 100644 index 0000000000..3e3e4c2df0 --- /dev/null +++ b/comm/third_party/botan/src/lib/tls/tls_cbc/tls_cbc.cpp @@ -0,0 +1,499 @@ +/* +* TLS CBC Record Handling +* (C) 2012,2013,2014,2015,2016,2020 Jack Lloyd +* (C) 2016 Juraj Somorovsky +* (C) 2016 Matthias Gierlings +* (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/internal/tls_cbc.h> +#include <botan/cbc.h> + +#include <botan/internal/rounding.h> +#include <botan/internal/ct_utils.h> +#include <botan/loadstor.h> +#include <botan/tls_alert.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(Cipher_Dir dir, + std::unique_ptr<BlockCipher> cipher, + std::unique_ptr<MessageAuthenticationCode> mac, + size_t cipher_keylen, + size_t mac_keylen, + Protocol_Version version, + 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_tag_size = mac->output_length(); + m_block_size = cipher->block_size(); + + m_iv_size = version.supports_explicit_cbc_ivs() ? m_block_size : 0; + + m_is_datagram = version.is_datagram_protocol(); + + m_mac = std::move(mac); + + if(dir == ENCRYPTION) + m_cbc.reset(new CBC_Encryption(cipher.release(), new Null_Padding)); + else + m_cbc.reset(new CBC_Decryption(cipher.release(), new Null_Padding)); + } + +void TLS_CBC_HMAC_AEAD_Mode::clear() + { + cbc().clear(); + mac().clear(); + reset(); + } + +void TLS_CBC_HMAC_AEAD_Mode::reset() + { + cbc_state().clear(); + m_ad.clear(); + m_msg.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 uint8_t 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); + + mac().set_key(&key[0], m_mac_keylen); + cbc().set_key(&key[m_mac_keylen], m_cipher_keylen); + } + +void TLS_CBC_HMAC_AEAD_Mode::start_msg(const uint8_t 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(uint8_t buf[], size_t sz) + { + m_msg.insert(m_msg.end(), buf, buf + sz); + return 0; + } + +std::vector<uint8_t> TLS_CBC_HMAC_AEAD_Mode::assoc_data_with_len(uint16_t len) + { + std::vector<uint8_t> 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 uint8_t ad[], size_t ad_len) + { + if(ad_len != 13) + throw Invalid_Argument("Invalid TLS AEAD associated data length"); + m_ad.assign(ad, ad + ad_len); + } + +void TLS_CBC_HMAC_AEAD_Encryption::set_associated_data(const uint8_t ad[], size_t ad_len) + { + TLS_CBC_HMAC_AEAD_Mode::set_associated_data(ad, ad_len); + + if(use_encrypt_then_mac()) + { + // AAD hack for EtM + // EtM uses ciphertext size instead of plaintext size for AEAD input + const uint16_t pt_size = make_uint16(assoc_data()[11], assoc_data()[12]); + const uint16_t enc_size = static_cast<uint16_t>(round_up(iv_size() + pt_size + 1, block_size())); + assoc_data()[11] = get_byte<uint16_t>(0, enc_size); + assoc_data()[12] = get_byte<uint16_t>(1, enc_size); + } + } + +void TLS_CBC_HMAC_AEAD_Encryption::cbc_encrypt_record( + secure_vector<uint8_t>& buffer, size_t offset, size_t padding_length) + { + // We always do short padding: + BOTAN_ASSERT_NOMSG(padding_length <= 16); + + buffer.resize(buffer.size() + padding_length); + + const uint8_t padding_val = static_cast<uint8_t>(padding_length - 1); + + CT::poison(&padding_val, 1); + CT::poison(&padding_length, 1); + CT::poison(buffer.data(), buffer.size()); + + const size_t last_block_starts = buffer.size() - block_size(); + const size_t padding_starts = buffer.size() - padding_length; + for(size_t i = last_block_starts; i != buffer.size(); ++i) + { + auto add_padding = CT::Mask<uint8_t>(CT::Mask<size_t>::is_gte(i, padding_starts)); + buffer[i] = add_padding.select(padding_val, buffer[i]); + } + + CT::unpoison(padding_val); + CT::unpoison(padding_length); + CT::unpoison(buffer.data(), buffer.size()); + + cbc().start(cbc_state()); + cbc().process(&buffer[offset], buffer.size() - offset); + + cbc_state().assign(buffer.data() + (buffer.size() - block_size()), buffer.data() + buffer.size()); + } + +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<uint8_t>& buffer, size_t offset) + { + update(buffer, offset); + + const size_t msg_size = msg().size(); + + 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()); + BOTAN_DEBUG_ASSERT(enc_size % block_size() == 0); + + const uint8_t padding_val = static_cast<uint8_t>(enc_size - input_size); + const size_t padding_length = static_cast<size_t>(padding_val) + 1; + + buffer.reserve(offset + msg_size + padding_length + tag_size()); + buffer.resize(offset + msg_size); + copy_mem(&buffer[offset], msg().data(), msg_size); + + mac().update(assoc_data()); + + if(use_encrypt_then_mac()) + { + if(iv_size() > 0) + { + mac().update(cbc_state()); + } + + cbc_encrypt_record(buffer, offset, padding_length); + mac().update(&buffer[offset], enc_size); + buffer.resize(buffer.size() + tag_size()); + mac().final(&buffer[buffer.size() - tag_size()]); + } + else + { + mac().update(&buffer[offset], msg_size); + buffer.resize(buffer.size() + tag_size()); + mac().final(&buffer[buffer.size() - tag_size()]); + cbc_encrypt_record(buffer, offset, padding_length); + } + } + +/* +* 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. +*/ +uint16_t check_tls_cbc_padding(const uint8_t record[], size_t record_len) + { + if(record_len == 0 || record_len > 0xFFFF) + return 0; + + const uint16_t rec16 = static_cast<uint16_t>(record_len); + + /* + * TLS v1.0 and up require all the padding bytes be the same value + * and allows up to 255 bytes. + */ + + const uint16_t to_check = std::min<uint16_t>(256, static_cast<uint16_t>(record_len)); + const uint8_t pad_byte = record[record_len-1]; + const uint16_t pad_bytes = 1 + pad_byte; + + auto pad_invalid = CT::Mask<uint16_t>::is_lt(rec16, pad_bytes); + + for(uint16_t i = rec16 - to_check; i != rec16; ++i) + { + const uint16_t offset = rec16 - i; + const auto in_pad_range = CT::Mask<uint16_t>::is_lte(offset, pad_bytes); + const auto pad_correct = CT::Mask<uint16_t>::is_equal(record[i], pad_byte); + pad_invalid |= in_pad_range & ~pad_correct; + } + + return pad_invalid.if_not_set_return(pad_bytes); + } + +void TLS_CBC_HMAC_AEAD_Decryption::cbc_decrypt_record(uint8_t record_contents[], size_t record_len) + { + if(record_len == 0 || record_len % block_size() != 0) + throw Decoding_Error("Received TLS CBC ciphertext with invalid length"); + + cbc().start(cbc_state()); + cbc_state().assign(record_contents + record_len - block_size(), + record_contents + record_len); + + cbc().process(record_contents, record_len); + } + +size_t TLS_CBC_HMAC_AEAD_Decryption::output_length(size_t) const + { + /* + * We don't know this because the padding is arbitrary + */ + return 0; + } + +/* +* This function performs additional compression calls in order +* to protect from the Lucky 13 attack. It adds new compression +* function calls over dummy data, by computing additional HMAC updates. +* +* The countermeasure was described (in a similar way) in the Lucky 13 paper. +* +* Background: +* - One SHA-1/SHA-256 compression is performed with 64 bytes of data. +* - HMAC adds 8 byte length field and padding (at least 1 byte) so that we have: +* - 0 - 55 bytes: 1 compression +* - 56 - 55+64 bytes: 2 compressions +* - 56+64 - 55+2*64 bytes: 3 compressions ... +* - For SHA-384, this works similarly, but we have 128 byte blocks and 16 byte +* long length field. This results in: +* - 0 - 111 bytes: 1 compression +* - 112 - 111+128 bytes: 2 compressions ... +* +* The implemented countermeasure works as follows: +* 1) It computes max_compressions: number of maximum compressions performed on +* the decrypted data +* 2) It computes current_compressions: number of compressions performed on the +* decrypted data, after padding has been removed +* 3) If current_compressions != max_compressions: It invokes an HMAC update +* over dummy data so that (max_compressions - current_compressions) +* compressions are performed. Otherwise, it invokes an HMAC update so that +* no compressions are performed. +* +* Note that the padding validation in Botan is always performed over +* min(plen,256) bytes, see the function check_tls_cbc_padding. This differs +* from the countermeasure described in the paper. +* +* Note that the padding length padlen does also count the last byte +* of the decrypted plaintext. This is different from the Lucky 13 paper. +* +* This countermeasure leaves a difference of about 100 clock cycles (in +* comparison to >1000 clock cycles observed without it). +* +* plen represents the length of the decrypted plaintext message P +* padlen represents the padding length +* +*/ +void TLS_CBC_HMAC_AEAD_Decryption::perform_additional_compressions(size_t plen, size_t padlen) + { + uint16_t block_size; + uint16_t max_bytes_in_first_block; + if(mac().name() == "HMAC(SHA-384)") + { + block_size = 128; + max_bytes_in_first_block = 111; + } + else + { + block_size = 64; + max_bytes_in_first_block = 55; + } + // number of maximum MACed bytes + const uint16_t L1 = static_cast<uint16_t>(13 + plen - tag_size()); + // number of current MACed bytes (L1 - padlen) + // Here the Lucky 13 paper is different because the padlen length in the paper + // does not count the last message byte. + const uint16_t L2 = static_cast<uint16_t>(13 + plen - padlen - tag_size()); + // From the paper, for SHA-256/SHA-1 compute: ceil((L1-55)/64) and ceil((L2-55)/64) + // ceil((L1-55)/64) = floor((L1+64-1-55)/64) + // Here we compute number of compressions for SHA-* in general + const uint16_t max_compresssions = ( (L1 + block_size - 1 - max_bytes_in_first_block) / block_size); + const uint16_t current_compressions = ((L2 + block_size - 1 - max_bytes_in_first_block) / block_size); + // number of additional compressions we have to perform + const uint16_t add_compressions = max_compresssions - current_compressions; + const uint16_t equal = CT::Mask<uint16_t>::is_equal(max_compresssions, current_compressions).if_set_return(1); + // We compute the data length we need to achieve the number of compressions. + // If there are no compressions, we just add 55/111 dummy bytes so that no + // compression is performed. + const uint16_t data_len = block_size * add_compressions + equal * max_bytes_in_first_block; + std::vector<uint8_t> data(data_len); + mac().update(data); + // we do not need to clear the MAC since the connection is broken anyway + } + +void TLS_CBC_HMAC_AEAD_Decryption::finish(secure_vector<uint8_t>& buffer, size_t offset) + { + update(buffer, offset); + buffer.resize(offset); + + const size_t record_len = msg().size(); + uint8_t* 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(); + const size_t enc_iv_size = enc_size + iv_size(); + + BOTAN_ASSERT_NOMSG(enc_iv_size <= 0xFFFF); + + mac().update(assoc_data_with_len(static_cast<uint16_t>(enc_iv_size))); + if(iv_size() > 0) + { + mac().update(cbc_state()); + } + mac().update(record_contents, enc_size); + + std::vector<uint8_t> mac_buf(tag_size()); + mac().final(mac_buf.data()); + + const size_t mac_offset = enc_size; + + const bool mac_ok = constant_time_compare(&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 + const uint16_t pad_size = check_tls_cbc_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 uint8_t* plaintext_block = &record_contents[0]; + const size_t plaintext_length = enc_size - pad_size; + + buffer.insert(buffer.end(), plaintext_block, plaintext_block + plaintext_length); + } + else + { + cbc_decrypt_record(record_contents, record_len); + + CT::poison(record_contents, record_len); + + // 0 if padding was invalid, otherwise 1 + padding_bytes + uint16_t pad_size = check_tls_cbc_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) + */ + + // We know the cast cannot overflow as pad_size <= 256 && tag_size <= 32 + const auto size_ok_mask = CT::Mask<uint16_t>::is_lte( + static_cast<uint16_t>(tag_size() + pad_size), + static_cast<uint16_t>(record_len)); + + pad_size = size_ok_mask.if_set_return(pad_size); + + 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 uint8_t* plaintext_block = &record_contents[0]; + const uint16_t plaintext_length = static_cast<uint16_t>(record_len - tag_size() - pad_size); + + mac().update(assoc_data_with_len(plaintext_length)); + mac().update(plaintext_block, plaintext_length); + + std::vector<uint8_t> mac_buf(tag_size()); + mac().final(mac_buf.data()); + + const size_t mac_offset = record_len - (tag_size() + pad_size); + + const bool mac_ok = constant_time_compare(&record_contents[mac_offset], mac_buf.data(), tag_size()); + + const auto ok_mask = size_ok_mask & CT::Mask<uint16_t>::expand(mac_ok) & CT::Mask<uint16_t>::expand(pad_size); + + CT::unpoison(ok_mask); + + if(ok_mask.is_set()) + { + buffer.insert(buffer.end(), plaintext_block, plaintext_block + plaintext_length); + } + else + { + perform_additional_compressions(record_len, pad_size); + + /* + * In DTLS case we have to finish computing the MAC since we require the + * MAC state be reset for future packets. This extra timing channel may + * be exploitable in a Lucky13 variant. + */ + if(is_datagram_protocol()) + mac().final(mac_buf); + throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); + } + } + } + +} + +} |