diff options
-rw-r--r-- | src/tls/c_hello.cpp | 31 | ||||
-rw-r--r-- | src/tls/info.txt | 2 | ||||
-rw-r--r-- | src/tls/rec_read.cpp | 3 | ||||
-rw-r--r-- | src/tls/s_hello.cpp | 14 | ||||
-rw-r--r-- | src/tls/tls_alert.cpp | 5 | ||||
-rw-r--r-- | src/tls/tls_alert.h | 4 | ||||
-rw-r--r-- | src/tls/tls_channel.cpp | 48 | ||||
-rw-r--r-- | src/tls/tls_channel.h | 14 | ||||
-rw-r--r-- | src/tls/tls_client.cpp | 3 | ||||
-rw-r--r-- | src/tls/tls_extensions.cpp | 3 | ||||
-rw-r--r-- | src/tls/tls_extensions.h | 27 | ||||
-rw-r--r-- | src/tls/tls_heartbeats.cpp | 78 | ||||
-rw-r--r-- | src/tls/tls_heartbeats.h | 40 | ||||
-rw-r--r-- | src/tls/tls_magic.h | 3 | ||||
-rw-r--r-- | src/tls/tls_messages.h | 37 | ||||
-rw-r--r-- | src/tls/tls_record.h | 3 | ||||
-rw-r--r-- | src/tls/tls_server.cpp | 5 |
17 files changed, 282 insertions, 38 deletions
diff --git a/src/tls/c_hello.cpp b/src/tls/c_hello.cpp index d3fff8b00..e52d34bc6 100644 --- a/src/tls/c_hello.cpp +++ b/src/tls/c_hello.cpp @@ -77,7 +77,9 @@ Client_Hello::Client_Hello(Record_Writer& writer, m_secure_renegotiation(true), m_renegotiation_info(reneg_info), m_supported_curves(policy.allowed_ecc_curves()), - m_supports_session_ticket(true) + m_supports_session_ticket(true), + m_supports_heartbeats(true), + m_peer_can_send_heartbeats(true) { std::vector<std::string> hashes = policy.allowed_hashes(); std::vector<std::string> sigs = policy.allowed_signature_methods(); @@ -110,7 +112,9 @@ Client_Hello::Client_Hello(Record_Writer& writer, m_secure_renegotiation(session.secure_renegotiation()), m_supported_curves(policy.allowed_ecc_curves()), m_supports_session_ticket(true), - m_session_ticket(session.session_ticket()) + m_session_ticket(session.session_ticket()), + m_supports_heartbeats(true), + m_peer_can_send_heartbeats(true) { if(!value_exists(m_suites, session.ciphersuite_code())) m_suites.push_back(session.ciphersuite_code()); @@ -136,6 +140,8 @@ Client_Hello::Client_Hello(const MemoryRegion<byte>& buf, Handshake_Type type) m_next_protocol = false; m_secure_renegotiation = false; m_supports_session_ticket = false; + m_supports_heartbeats = false; + m_peer_can_send_heartbeats = false; m_fragment_size = 0; if(type == CLIENT_HELLO) @@ -168,29 +174,24 @@ MemoryVector<byte> Client_Hello::serialize() const Extensions extensions; - // Initial handshake + // Extensions only used on an initial handshake if(m_renegotiation_info.empty()) { - extensions.add(new Renegotation_Extension(m_renegotiation_info)); extensions.add(new Server_Name_Indicator(m_hostname)); extensions.add(new SRP_Identifier(m_srp_identifier)); extensions.add(new Supported_Elliptic_Curves(m_supported_curves)); + extensions.add(new Heartbeat_Support_Indicator(true)); if(m_version >= Protocol_Version::TLS_V12) extensions.add(new Signature_Algorithms(m_supported_algos)); if(m_next_protocol) extensions.add(new Next_Protocol_Notification()); - - extensions.add(new Session_Ticket(m_session_ticket)); - } - else - { - // renegotiation - extensions.add(new Renegotation_Extension(m_renegotiation_info)); - extensions.add(new Session_Ticket(m_session_ticket)); } + extensions.add(new Renegotation_Extension(m_renegotiation_info)); + extensions.add(new Session_Ticket(m_session_ticket)); + buf += extensions.serialize(); return buf; @@ -338,6 +339,12 @@ void Client_Hello::deserialize(const MemoryRegion<byte>& buf) m_session_ticket = ticket->contents(); } + if(Heartbeat_Support_Indicator* hb = extensions.get<Heartbeat_Support_Indicator>()) + { + m_supports_heartbeats = true; + m_peer_can_send_heartbeats = hb->peer_allowed_to_send(); + } + if(Renegotation_Extension* reneg = extensions.get<Renegotation_Extension>()) { // checked by TLS_Client / TLS_Server as they know the handshake state diff --git a/src/tls/info.txt b/src/tls/info.txt index 229cf658f..77ffb4585 100644 --- a/src/tls/info.txt +++ b/src/tls/info.txt @@ -29,6 +29,7 @@ tls_extensions.h tls_handshake_hash.h tls_handshake_reader.h tls_handshake_state.h +tls_heartbeats.h tls_messages.h tls_reader.h tls_session_key.h @@ -55,6 +56,7 @@ tls_extensions.cpp tls_handshake_hash.cpp tls_handshake_reader.cpp tls_handshake_state.cpp +tls_heartbeats.cpp tls_policy.cpp tls_server.cpp tls_session.cpp diff --git a/src/tls/rec_read.cpp b/src/tls/rec_read.cpp index d1fab4692..5d46ec1fa 100644 --- a/src/tls/rec_read.cpp +++ b/src/tls/rec_read.cpp @@ -216,7 +216,8 @@ size_t Record_Reader::add_input(const byte input_array[], size_t input_sz, if(m_readbuf[0] != CHANGE_CIPHER_SPEC && m_readbuf[0] != ALERT && m_readbuf[0] != HANDSHAKE && - m_readbuf[0] != APPLICATION_DATA) + m_readbuf[0] != APPLICATION_DATA && + m_readbuf[0] != HEARTBEAT) { throw Unexpected_Message( "Unknown record type " + to_string(m_readbuf[0]) + " from counterparty"); diff --git a/src/tls/s_hello.cpp b/src/tls/s_hello.cpp index bb93108d9..1244dd2d8 100644 --- a/src/tls/s_hello.cpp +++ b/src/tls/s_hello.cpp @@ -31,6 +31,7 @@ Server_Hello::Server_Hello(Record_Writer& writer, bool offer_session_ticket, bool client_has_npn, const std::vector<std::string>& next_protocols, + bool client_has_heartbeat, RandomNumberGenerator& rng) : m_version(ver), m_session_id(session_id), @@ -42,7 +43,9 @@ Server_Hello::Server_Hello(Record_Writer& writer, m_renegotiation_info(reneg_info), m_next_protocol(client_has_npn), m_next_protocols(next_protocols), - m_supports_session_ticket(offer_session_ticket) + m_supports_session_ticket(offer_session_ticket), + m_supports_heartbeats(client_has_heartbeat), + m_peer_can_send_heartbeats(true) { hash.update(writer.send(*this)); } @@ -104,6 +107,12 @@ Server_Hello::Server_Hello(const MemoryRegion<byte>& buf) throw Decoding_Error("TLS server sent non-empty session ticket extension"); m_supports_session_ticket = true; } + + if(Heartbeat_Support_Indicator* hb = extensions.get<Heartbeat_Support_Indicator>()) + { + m_supports_heartbeats = true; + m_peer_can_send_heartbeats = hb->peer_allowed_to_send(); + } } /* @@ -126,6 +135,9 @@ MemoryVector<byte> Server_Hello::serialize() const Extensions extensions; + if(m_supports_heartbeats) + extensions.add(new Heartbeat_Support_Indicator(m_peer_can_send_heartbeats)); + if(m_secure_renegotiation) extensions.add(new Renegotation_Extension(m_renegotiation_info)); diff --git a/src/tls/tls_alert.cpp b/src/tls/tls_alert.cpp index 2461854e1..83205bb74 100644 --- a/src/tls/tls_alert.cpp +++ b/src/tls/tls_alert.cpp @@ -102,7 +102,10 @@ std::string Alert::type_string() const return "unknown_psk_identity"; case NULL_ALERT: - return ""; + return "none"; + + case HEARTBEAT_PAYLOAD: + return "heartbeat_payload"; } /* diff --git a/src/tls/tls_alert.h b/src/tls/tls_alert.h index 8b4b963f0..3dfff3d29 100644 --- a/src/tls/tls_alert.h +++ b/src/tls/tls_alert.h @@ -53,7 +53,9 @@ class BOTAN_DLL Alert BAD_CERTIFICATE_HASH_VALUE = 114, UNKNOWN_PSK_IDENTITY = 115, - NULL_ALERT = 255 + NULL_ALERT = 255, + + HEARTBEAT_PAYLOAD = 256 }; /** diff --git a/src/tls/tls_channel.cpp b/src/tls/tls_channel.cpp index f45ce4bda..2464d339a 100644 --- a/src/tls/tls_channel.cpp +++ b/src/tls/tls_channel.cpp @@ -8,6 +8,7 @@ #include <botan/tls_channel.h> #include <botan/internal/tls_handshake_state.h> #include <botan/internal/tls_messages.h> +#include <botan/internal/tls_heartbeats.h> #include <botan/internal/assert.h> #include <botan/loadstor.h> @@ -23,7 +24,9 @@ Channel::Channel(std::tr1::function<void (const byte[], size_t)> socket_output_f writer(socket_output_fn), state(0), handshake_completed(false), - connection_closed(false) + connection_closed(false), + m_peer_supports_heartbeats(false), + m_heartbeat_sending_allowed(false) { } @@ -56,7 +59,30 @@ size_t Channel::received_data(const byte buf[], size_t buf_size) if(buf_size == 0 && needed != 0) return needed; // need more data to complete record - if(rec_type == APPLICATION_DATA) + if(rec_type == HANDSHAKE || rec_type == CHANGE_CIPHER_SPEC) + { + read_handshake(rec_type, record); + } + else if(rec_type == HEARTBEAT && m_peer_supports_heartbeats) + { + Heartbeat_Message heartbeat(record); + + const MemoryRegion<byte>& payload = heartbeat.payload(); + + if(heartbeat.is_request() && !state) + { + Heartbeat_Message response(Heartbeat_Message::RESPONSE, + payload, payload.size()); + + writer.send(HEARTBEAT, response.contents()); + } + else + { + // pass up to the application + proc_fn(&payload[0], payload.size(), Alert(Alert::HEARTBEAT_PAYLOAD)); + } + } + else if(rec_type == APPLICATION_DATA) { if(handshake_completed) { @@ -73,10 +99,6 @@ size_t Channel::received_data(const byte buf[], size_t buf_size) throw Unexpected_Message("Application data before handshake done"); } } - else if(rec_type == HANDSHAKE || rec_type == CHANGE_CIPHER_SPEC) - { - read_handshake(rec_type, record); - } else if(rec_type == ALERT) { Alert alert_msg(record); @@ -178,6 +200,20 @@ void Channel::read_handshake(byte rec_type, } } +void Channel::heartbeat(const byte payload[], size_t payload_size) + { + if(!is_active()) + throw std::runtime_error("Heartbeat cannot be sent on inactive TLS connection"); + + if(m_heartbeat_sending_allowed) + { + Heartbeat_Message heartbeat(Heartbeat_Message::REQUEST, + payload, payload_size); + + writer.send(HEARTBEAT, heartbeat.contents()); + } + } + void Channel::send(const byte buf[], size_t buf_size) { if(!is_active()) diff --git a/src/tls/tls_channel.h b/src/tls/tls_channel.h index 53af0bdfc..05fb7e82f 100644 --- a/src/tls/tls_channel.h +++ b/src/tls/tls_channel.h @@ -58,6 +58,18 @@ class BOTAN_DLL Channel virtual void renegotiate() = 0; /** + * Attempt to send a heartbeat message (if negotiated with counterparty) + * @param payload will be echoed back + * @param countents_size size of payload in bytes + */ + void heartbeat(const byte payload[], size_t payload_size); + + /** + * Attempt to send a heartbeat message (if negotiated with counterparty) + */ + void heartbeat() { heartbeat(0, 0); } + + /** * @return certificate chain of the peer (may be empty) */ std::vector<X509_Certificate> peer_cert_chain() const { return peer_certs; } @@ -130,6 +142,8 @@ class BOTAN_DLL Channel bool handshake_completed; bool connection_closed; + bool m_peer_supports_heartbeats; + bool m_heartbeat_sending_allowed; }; } diff --git a/src/tls/tls_client.cpp b/src/tls/tls_client.cpp index 850d053a2..f6266ee6a 100644 --- a/src/tls/tls_client.cpp +++ b/src/tls/tls_client.cpp @@ -188,6 +188,9 @@ void Client::process_handshake_msg(Handshake_Type type, secure_renegotiation.update(state->server_hello); + m_peer_supports_heartbeats = state->server_hello->supports_heartbeats(); + m_heartbeat_sending_allowed = state->server_hello->peer_can_send_heartbeats(); + state->suite = Ciphersuite::by_id(state->server_hello->ciphersuite()); const bool server_returned_same_session_id = diff --git a/src/tls/tls_extensions.cpp b/src/tls/tls_extensions.cpp index 59ab64374..f1361bbb9 100644 --- a/src/tls/tls_extensions.cpp +++ b/src/tls/tls_extensions.cpp @@ -42,6 +42,9 @@ Extension* make_extension(TLS_Data_Reader& reader, case TLSEXT_NEXT_PROTOCOL: return new Next_Protocol_Notification(reader, size); + case TLSEXT_HEARTBEAT_SUPPORT: + return new Heartbeat_Support_Indicator(reader, size); + case TLSEXT_SESSION_TICKET: return new Session_Ticket(reader, size); diff --git a/src/tls/tls_extensions.h b/src/tls/tls_extensions.h index 6a97d2560..3fe3f7399 100644 --- a/src/tls/tls_extensions.h +++ b/src/tls/tls_extensions.h @@ -32,6 +32,7 @@ enum Handshake_Extension_Type { TLSEXT_EC_POINT_FORMATS = 11, TLSEXT_SRP_IDENTIFIER = 12, TLSEXT_SIGNATURE_ALGORITHMS = 13, + TLSEXT_HEARTBEAT_SUPPORT = 15, TLSEXT_SESSION_TICKET = 35, @@ -309,6 +310,32 @@ class Signature_Algorithms : public Extension }; /** +* Heartbeat Extension (RFC 6520) +*/ +class Heartbeat_Support_Indicator : public Extension + { + public: + static Handshake_Extension_Type static_type() + { return TLSEXT_HEARTBEAT_SUPPORT; } + + Handshake_Extension_Type type() const { return static_type(); } + + bool peer_allowed_to_send() const { return m_peer_allowed_to_send; } + + MemoryVector<byte> serialize() const; + + bool empty() const { return false; } + + Heartbeat_Support_Indicator(bool peer_allowed_to_send) : + m_peer_allowed_to_send(peer_allowed_to_send) {} + + Heartbeat_Support_Indicator(TLS_Data_Reader& reader, u16bit extension_size); + + private: + bool m_peer_allowed_to_send; + }; + +/** * Represents a block of extensions in a hello message */ class Extensions diff --git a/src/tls/tls_heartbeats.cpp b/src/tls/tls_heartbeats.cpp new file mode 100644 index 000000000..a77d23534 --- /dev/null +++ b/src/tls/tls_heartbeats.cpp @@ -0,0 +1,78 @@ +/* +* TLS Heartbeats +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/internal/tls_heartbeats.h> +#include <botan/internal/tls_extensions.h> +#include <botan/internal/tls_reader.h> +#include <botan/tls_exceptn.h> + +namespace Botan { + +namespace TLS { + +Heartbeat_Message::Heartbeat_Message(const MemoryRegion<byte>& buf) + { + TLS_Data_Reader reader(buf); + + const byte type = reader.get_byte(); + + if(type != 1 && type != 2) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Unknown heartbeat message type"); + + m_type = static_cast<Type>(type); + + m_payload = reader.get_range<byte>(2, 0, 16*1024); + + // padding follows and is ignored + } + +Heartbeat_Message::Heartbeat_Message(Type type, + const byte payload[], + size_t payload_len) : + m_type(type), + m_payload(payload, payload_len) + { + } + +MemoryVector<byte> Heartbeat_Message::contents() const + { + MemoryVector<byte> send_buf(3 + m_payload.size() + 16); + send_buf[0] = m_type; + send_buf[1] = get_byte<u16bit>(0, m_payload.size()); + send_buf[2] = get_byte<u16bit>(1, m_payload.size()); + copy_mem(&send_buf[3], &m_payload[0], m_payload.size()); + // leave padding as all zeros + + return send_buf; + } + +MemoryVector<byte> Heartbeat_Support_Indicator::serialize() const + { + MemoryVector<byte> heartbeat(1); + heartbeat[0] = (m_peer_allowed_to_send ? 1 : 2); + return heartbeat; + } + +Heartbeat_Support_Indicator::Heartbeat_Support_Indicator(TLS_Data_Reader& reader, + u16bit extension_size) + { + if(extension_size != 1) + throw Decoding_Error("Strange size for heartbeat extension"); + + const byte code = reader.get_byte(); + + if(code != 1 && code != 2) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Unknown heartbeat code " + to_string(code)); + + m_peer_allowed_to_send = (code == 1); + } + +} + +} diff --git a/src/tls/tls_heartbeats.h b/src/tls/tls_heartbeats.h new file mode 100644 index 000000000..4fa49501b --- /dev/null +++ b/src/tls/tls_heartbeats.h @@ -0,0 +1,40 @@ +/* +* TLS Heartbeats +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#ifndef BOTAN_TLS_HEARTBEATS_H__ +#define BOTAN_TLS_HEARTBEATS_H__ + +#include <botan/secmem.h> + +namespace Botan { + +namespace TLS { + +class Heartbeat_Message + { + public: + enum Type { REQUEST = 1, RESPONSE = 2 }; + + MemoryVector<byte> contents() const; + + const MemoryRegion<byte>& payload() const { return m_payload; } + + bool is_request() const { return m_type == REQUEST; } + + Heartbeat_Message(const MemoryRegion<byte>& buf); + + Heartbeat_Message(Type type, const byte payload[], size_t payload_len); + private: + Type m_type; + MemoryVector<byte> m_payload; + }; + +} + +} + +#endif diff --git a/src/tls/tls_magic.h b/src/tls/tls_magic.h index 6dd50ead2..2972321c9 100644 --- a/src/tls/tls_magic.h +++ b/src/tls/tls_magic.h @@ -32,7 +32,8 @@ enum Record_Type { CHANGE_CIPHER_SPEC = 20, ALERT = 21, HANDSHAKE = 22, - APPLICATION_DATA = 23 + APPLICATION_DATA = 23, + HEARTBEAT = 24, }; enum Handshake_Type { diff --git a/src/tls/tls_messages.h b/src/tls/tls_messages.h index c8a9382d6..4c2b1b797 100644 --- a/src/tls/tls_messages.h +++ b/src/tls/tls_messages.h @@ -73,15 +73,10 @@ class Client_Hello : public Handshake_Message { public: Handshake_Type type() const { return CLIENT_HELLO; } + Protocol_Version version() const { return m_version; } - const MemoryVector<byte>& session_id() const { return m_session_id; } - std::vector<byte> session_id_vector() const - { - std::vector<byte> v; - v.insert(v.begin(), &m_session_id[0], &m_session_id[m_session_id.size()]); - return v; - } + const MemoryVector<byte>& session_id() const { return m_session_id; } const std::vector<std::pair<std::string, std::string> >& supported_algos() const { return m_supported_algos; } @@ -114,6 +109,10 @@ class Client_Hello : public Handshake_Message const MemoryRegion<byte>& session_ticket() const { return m_session_ticket; } + bool supports_heartbeats() const { return m_supports_heartbeats; } + + bool peer_can_send_heartbeats() const { return m_peer_can_send_heartbeats; } + Client_Hello(Record_Writer& writer, Handshake_Hash& hash, const Policy& policy, @@ -155,6 +154,9 @@ class Client_Hello : public Handshake_Message bool m_supports_session_ticket; MemoryVector<byte> m_session_ticket; + + bool m_supports_heartbeats; + bool m_peer_can_send_heartbeats; }; /** @@ -164,17 +166,16 @@ class Server_Hello : public Handshake_Message { public: Handshake_Type type() const { return SERVER_HELLO; } + Protocol_Version version() { return m_version; } + + const MemoryVector<byte>& random() const { return m_random; } + const MemoryVector<byte>& session_id() const { return m_session_id; } + u16bit ciphersuite() const { return m_ciphersuite; } - byte compression_method() const { return m_comp_method; } - std::vector<byte> session_id_vector() const - { - std::vector<byte> v; - v.insert(v.begin(), &m_session_id[0], &m_session_id[m_session_id.size()]); - return v; - } + byte compression_method() const { return m_comp_method; } bool secure_renegotiation() const { return m_secure_renegotiation; } @@ -190,7 +191,9 @@ class Server_Hello : public Handshake_Message const MemoryVector<byte>& renegotiation_info() { return m_renegotiation_info; } - const MemoryVector<byte>& random() const { return m_random; } + bool supports_heartbeats() const { return m_supports_heartbeats; } + + bool peer_can_send_heartbeats() const { return m_peer_can_send_heartbeats; } Server_Hello(Record_Writer& writer, Handshake_Hash& hash, @@ -204,6 +207,7 @@ class Server_Hello : public Handshake_Message bool offer_session_ticket, bool client_has_npn, const std::vector<std::string>& next_protocols, + bool client_has_heartbeat, RandomNumberGenerator& rng); Server_Hello(const MemoryRegion<byte>& buf); @@ -222,6 +226,9 @@ class Server_Hello : public Handshake_Message bool m_next_protocol; std::vector<std::string> m_next_protocols; bool m_supports_session_ticket; + + bool m_supports_heartbeats; + bool m_peer_can_send_heartbeats; }; /** diff --git a/src/tls/tls_record.h b/src/tls/tls_record.h index fb27db5e2..38eae7823 100644 --- a/src/tls/tls_record.h +++ b/src/tls/tls_record.h @@ -46,6 +46,9 @@ class BOTAN_DLL Record_Writer void send(byte type, const byte input[], size_t length); void send(byte type, byte val) { send(type, &val, 1); } + void send(byte type, const MemoryRegion<byte>& input) + { send(type, &input[0], input.size()); } + MemoryVector<byte> send(class Handshake_Message& msg); void send_alert(const Alert& alert); diff --git a/src/tls/tls_server.cpp b/src/tls/tls_server.cpp index 10872a825..c591a45c5 100644 --- a/src/tls/tls_server.cpp +++ b/src/tls/tls_server.cpp @@ -286,6 +286,9 @@ void Server::process_handshake_msg(Handshake_Type type, secure_renegotiation.update(state->client_hello); + m_peer_supports_heartbeats = state->client_hello->supports_heartbeats(); + m_heartbeat_sending_allowed = state->client_hello->peer_can_send_heartbeats(); + writer.set_version(state->version()); reader.set_version(state->version()); @@ -324,6 +327,7 @@ void Server::process_handshake_msg(Handshake_Type type, have_session_ticket_key), state->client_hello->next_protocol_notification(), m_possible_protocols, + state->client_hello->supports_heartbeats(), rng); if(session_info.fragment_size()) @@ -395,6 +399,7 @@ void Server::process_handshake_msg(Handshake_Type type, state->client_hello->supports_session_ticket() && have_session_ticket_key, state->client_hello->next_protocol_notification(), m_possible_protocols, + state->client_hello->supports_heartbeats(), rng); if(state->client_hello->fragment_size()) |