aboutsummaryrefslogtreecommitdiffstats
path: root/src/tls/rec_read.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tls/rec_read.cpp')
-rw-r--r--src/tls/rec_read.cpp353
1 files changed, 353 insertions, 0 deletions
diff --git a/src/tls/rec_read.cpp b/src/tls/rec_read.cpp
new file mode 100644
index 000000000..b240f4703
--- /dev/null
+++ b/src/tls/rec_read.cpp
@@ -0,0 +1,353 @@
+/*
+* TLS Record Reading
+* (C) 2004-2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_record.h>
+#include <botan/lookup.h>
+#include <botan/loadstor.h>
+#include <botan/internal/tls_session_key.h>
+#include <botan/internal/rounding.h>
+#include <botan/internal/assert.h>
+
+namespace Botan {
+
+namespace TLS {
+
+Record_Reader::Record_Reader() :
+ m_readbuf(TLS_HEADER_SIZE + MAX_CIPHERTEXT_SIZE),
+ m_mac(0)
+ {
+ reset();
+ set_maximum_fragment_size(0);
+ }
+
+/*
+* Reset the state
+*/
+void Record_Reader::reset()
+ {
+ m_macbuf.clear();
+
+ zeroise(m_readbuf);
+ m_readbuf_pos = 0;
+
+ m_cipher.reset();
+
+ delete m_mac;
+ m_mac = 0;
+
+ m_block_size = 0;
+ m_iv_size = 0;
+ m_version = Protocol_Version();
+ m_seq_no = 0;
+ set_maximum_fragment_size(0);
+ }
+
+void Record_Reader::set_maximum_fragment_size(size_t max_fragment)
+ {
+ if(max_fragment == 0)
+ m_max_fragment = MAX_PLAINTEXT_SIZE;
+ else
+ m_max_fragment = clamp(max_fragment, 128, MAX_PLAINTEXT_SIZE);
+ }
+
+/*
+* Set the version to use
+*/
+void Record_Reader::set_version(Protocol_Version version)
+ {
+ m_version = version;
+ }
+
+/*
+* Set the keys for reading
+*/
+void Record_Reader::activate(Connection_Side side,
+ const Ciphersuite& suite,
+ const Session_Keys& keys,
+ byte compression_method)
+ {
+ m_cipher.reset();
+ delete m_mac;
+ m_mac = 0;
+ m_seq_no = 0;
+
+ if(compression_method != NO_COMPRESSION)
+ throw Internal_Error("Negotiated unknown compression algorithm");
+
+ SymmetricKey mac_key, cipher_key;
+ InitializationVector iv;
+
+ if(side == CLIENT)
+ {
+ cipher_key = keys.server_cipher_key();
+ iv = keys.server_iv();
+ mac_key = keys.server_mac_key();
+ }
+ else
+ {
+ cipher_key = keys.client_cipher_key();
+ iv = keys.client_iv();
+ mac_key = keys.client_mac_key();
+ }
+
+ const std::string cipher_algo = suite.cipher_algo();
+ const std::string mac_algo = suite.mac_algo();
+
+ if(have_block_cipher(cipher_algo))
+ {
+ m_cipher.append(get_cipher(
+ cipher_algo + "/CBC/NoPadding",
+ cipher_key, iv, DECRYPTION)
+ );
+ m_block_size = block_size_of(cipher_algo);
+
+ if(m_version >= Protocol_Version::TLS_V11)
+ m_iv_size = m_block_size;
+ else
+ m_iv_size = 0;
+ }
+ else if(have_stream_cipher(cipher_algo))
+ {
+ m_cipher.append(get_cipher(cipher_algo, cipher_key, DECRYPTION));
+ m_block_size = 0;
+ m_iv_size = 0;
+ }
+ else
+ throw Invalid_Argument("Record_Reader: Unknown cipher " + cipher_algo);
+
+ if(have_hash(mac_algo))
+ {
+ Algorithm_Factory& af = global_state().algorithm_factory();
+
+ if(m_version == Protocol_Version::SSL_V3)
+ m_mac = af.make_mac("SSL3-MAC(" + mac_algo + ")");
+ else
+ m_mac = af.make_mac("HMAC(" + mac_algo + ")");
+
+ m_mac->set_key(mac_key);
+ m_macbuf.resize(m_mac->output_length());
+ }
+ else
+ throw Invalid_Argument("Record_Reader: Unknown hash " + mac_algo);
+ }
+
+size_t Record_Reader::fill_buffer_to(const byte*& input,
+ size_t& input_size,
+ size_t& input_consumed,
+ size_t desired)
+ {
+ if(desired <= m_readbuf_pos)
+ return 0; // already have it
+
+ const size_t space_available = (m_readbuf.size() - m_readbuf_pos);
+ const size_t taken = std::min(input_size, desired - m_readbuf_pos);
+
+ if(taken > space_available)
+ throw TLS_Exception(Alert::RECORD_OVERFLOW,
+ "Record is larger than allowed maximum size");
+
+ copy_mem(&m_readbuf[m_readbuf_pos], input, taken);
+ m_readbuf_pos += taken;
+ input_consumed += taken;
+ input_size -= taken;
+ input += taken;
+
+ return (desired - m_readbuf_pos); // how many bytes do we still need?
+ }
+
+/*
+* Retrieve the next record
+*/
+size_t Record_Reader::add_input(const byte input_array[], size_t input_sz,
+ size_t& consumed,
+ byte& msg_type,
+ MemoryVector<byte>& msg)
+ {
+ const byte* input = &input_array[0];
+
+ consumed = 0;
+
+ if(m_readbuf_pos < TLS_HEADER_SIZE) // header incomplete?
+ {
+ if(size_t needed = fill_buffer_to(input, input_sz, consumed, TLS_HEADER_SIZE))
+ return needed;
+
+ BOTAN_ASSERT_EQUAL(m_readbuf_pos, TLS_HEADER_SIZE,
+ "Have an entire header");
+ }
+
+ // Possible SSLv2 format client hello
+ if((!m_mac) && (m_readbuf[0] & 0x80) && (m_readbuf[2] == 1))
+ {
+ if(m_readbuf[3] == 0 && m_readbuf[4] == 2)
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Client claims to only support SSLv2, rejecting");
+
+ if(m_readbuf[3] >= 3) // SSLv2 mapped TLS hello, then?
+ {
+ size_t record_len = make_u16bit(m_readbuf[0], m_readbuf[1]) & 0x7FFF;
+
+ if(size_t needed = fill_buffer_to(input, input_sz, consumed, record_len + 2))
+ return needed;
+
+ BOTAN_ASSERT_EQUAL(m_readbuf_pos, (record_len + 2),
+ "Have the entire SSLv2 hello");
+
+ msg_type = HANDSHAKE;
+
+ msg.resize(record_len + 4);
+
+ // Fake v3-style handshake message wrapper
+ msg[0] = CLIENT_HELLO_SSLV2;
+ msg[1] = 0;
+ msg[2] = m_readbuf[0] & 0x7F;
+ msg[3] = m_readbuf[1];
+
+ copy_mem(&msg[4], &m_readbuf[2], m_readbuf_pos - 2);
+ m_readbuf_pos = 0;
+ return 0;
+ }
+ }
+
+ if(m_readbuf[0] != CHANGE_CIPHER_SPEC &&
+ m_readbuf[0] != ALERT &&
+ m_readbuf[0] != HANDSHAKE &&
+ m_readbuf[0] != APPLICATION_DATA &&
+ m_readbuf[0] != HEARTBEAT)
+ {
+ throw Unexpected_Message(
+ "Unknown record type " + std::to_string(m_readbuf[0]) +
+ " from counterparty");
+ }
+
+ const size_t record_len = make_u16bit(m_readbuf[3], m_readbuf[4]);
+
+ if(m_version.major_version())
+ {
+ if(m_readbuf[1] != m_version.major_version() ||
+ m_readbuf[2] != m_version.minor_version())
+ {
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Got unexpected version from counterparty");
+ }
+ }
+
+ if(record_len > MAX_CIPHERTEXT_SIZE)
+ throw TLS_Exception(Alert::RECORD_OVERFLOW,
+ "Got message that exceeds maximum size");
+
+ if(size_t needed = fill_buffer_to(input, input_sz, consumed,
+ TLS_HEADER_SIZE + record_len))
+ return needed;
+
+ BOTAN_ASSERT_EQUAL(static_cast<size_t>(TLS_HEADER_SIZE) + record_len,
+ m_readbuf_pos,
+ "Have the full record");
+
+ // Null mac means no encryption either, only valid during handshake
+ if(!m_mac)
+ {
+ if(m_readbuf[0] != CHANGE_CIPHER_SPEC &&
+ m_readbuf[0] != ALERT &&
+ m_readbuf[0] != HANDSHAKE)
+ {
+ throw Decoding_Error("Invalid msg type received during handshake");
+ }
+
+ msg_type = m_readbuf[0];
+ msg.resize(record_len);
+ copy_mem(&msg[0], &m_readbuf[TLS_HEADER_SIZE], record_len);
+
+ m_readbuf_pos = 0;
+ return 0; // got a full record
+ }
+
+ // Otherwise, decrypt, check MAC, return plaintext
+
+ // FIXME: avoid memory allocation by processing in place
+ m_cipher.process_msg(&m_readbuf[TLS_HEADER_SIZE], record_len);
+ size_t got_back = m_cipher.read(&m_readbuf[TLS_HEADER_SIZE], record_len, Pipe::LAST_MESSAGE);
+ BOTAN_ASSERT_EQUAL(got_back, record_len, "Cipher encrypted full amount");
+
+ BOTAN_ASSERT_EQUAL(m_cipher.remaining(Pipe::LAST_MESSAGE), 0,
+ "Cipher had no remaining inputs");
+
+ size_t pad_size = 0;
+
+ if(m_block_size)
+ {
+ byte pad_value = m_readbuf[TLS_HEADER_SIZE + (record_len-1)];
+ pad_size = pad_value + 1;
+
+ /*
+ * Check the padding; if it is wrong, then say we have 0 bytes of
+ * padding, which should ensure that the MAC check below does not
+ * succeed. This hides a timing channel.
+ *
+ * This particular countermeasure is recommended in the TLS 1.2
+ * spec (RFC 5246) in section 6.2.3.2
+ */
+ if(m_version == Protocol_Version::SSL_V3)
+ {
+ if(pad_value > m_block_size)
+ pad_size = 0;
+ }
+ else
+ {
+ bool padding_good = true;
+
+ for(size_t i = 0; i != pad_size; ++i)
+ if(m_readbuf[TLS_HEADER_SIZE + (record_len-i-1)] != pad_value)
+ padding_good = false;
+
+ if(!padding_good)
+ pad_size = 0;
+ }
+ }
+
+ const size_t mac_pad_iv_size = m_macbuf.size() + pad_size + m_iv_size;
+
+ if(record_len < mac_pad_iv_size)
+ throw Decoding_Error("Record sent with invalid length");
+
+ const u16bit plain_length = record_len - mac_pad_iv_size;
+
+ if(plain_length > m_max_fragment)
+ throw TLS_Exception(Alert::RECORD_OVERFLOW, "Plaintext record is too large");
+
+ m_mac->update_be(m_seq_no);
+ m_mac->update(m_readbuf[0]); // msg_type
+
+ if(m_version != Protocol_Version::SSL_V3)
+ {
+ m_mac->update(m_version.major_version());
+ m_mac->update(m_version.minor_version());
+ }
+
+ m_mac->update_be(plain_length);
+ m_mac->update(&m_readbuf[TLS_HEADER_SIZE + m_iv_size], plain_length);
+
+ ++m_seq_no;
+
+ m_mac->final(m_macbuf);
+
+ const size_t mac_offset = record_len - (m_macbuf.size() + pad_size);
+
+ if(!same_mem(&m_readbuf[TLS_HEADER_SIZE + mac_offset], &m_macbuf[0], m_macbuf.size()))
+ throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure");
+
+ msg_type = m_readbuf[0];
+
+ msg.resize(plain_length);
+ copy_mem(&msg[0], &m_readbuf[TLS_HEADER_SIZE + m_iv_size], plain_length);
+ m_readbuf_pos = 0;
+ return 0;
+ }
+
+}
+
+}