summaryrefslogtreecommitdiffstats
path: root/comm/third_party/botan/src/lib/tls/tls_channel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/third_party/botan/src/lib/tls/tls_channel.cpp')
-rw-r--r--comm/third_party/botan/src/lib/tls/tls_channel.cpp795
1 files changed, 795 insertions, 0 deletions
diff --git a/comm/third_party/botan/src/lib/tls/tls_channel.cpp b/comm/third_party/botan/src/lib/tls/tls_channel.cpp
new file mode 100644
index 0000000000..897fed97c6
--- /dev/null
+++ b/comm/third_party/botan/src/lib/tls/tls_channel.cpp
@@ -0,0 +1,795 @@
+/*
+* TLS Channels
+* (C) 2011,2012,2014,2015,2016 Jack Lloyd
+* 2016 Matthias Gierlings
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/tls_channel.h>
+#include <botan/tls_policy.h>
+#include <botan/tls_messages.h>
+#include <botan/kdf.h>
+#include <botan/internal/tls_handshake_state.h>
+#include <botan/internal/tls_record.h>
+#include <botan/internal/tls_seq_numbers.h>
+#include <botan/internal/rounding.h>
+#include <botan/internal/stl_util.h>
+#include <botan/loadstor.h>
+
+namespace Botan {
+
+namespace TLS {
+
+size_t TLS::Channel::IO_BUF_DEFAULT_SIZE = 10*1024;
+
+Channel::Channel(Callbacks& callbacks,
+ Session_Manager& session_manager,
+ RandomNumberGenerator& rng,
+ const Policy& policy,
+ bool is_server,
+ bool is_datagram,
+ size_t reserved_io_buffer_size) :
+ m_is_server(is_server),
+ m_is_datagram(is_datagram),
+ m_callbacks(callbacks),
+ m_session_manager(session_manager),
+ m_policy(policy),
+ m_rng(rng),
+ m_has_been_closed(false)
+ {
+ init(reserved_io_buffer_size);
+ }
+
+Channel::Channel(output_fn out,
+ data_cb app_data_cb,
+ alert_cb recv_alert_cb,
+ handshake_cb hs_cb,
+ handshake_msg_cb hs_msg_cb,
+ Session_Manager& session_manager,
+ RandomNumberGenerator& rng,
+ const Policy& policy,
+ bool is_server,
+ bool is_datagram,
+ size_t io_buf_sz) :
+ m_is_server(is_server),
+ m_is_datagram(is_datagram),
+ m_compat_callbacks(new Compat_Callbacks(
+ /*
+ this Channel constructor is also deprecated so its ok that it
+ relies on a deprecated API
+ */
+ Compat_Callbacks::SILENCE_DEPRECATION_WARNING::PLEASE,
+ out, app_data_cb, recv_alert_cb, hs_cb, hs_msg_cb)),
+ m_callbacks(*m_compat_callbacks.get()),
+ m_session_manager(session_manager),
+ m_policy(policy),
+ m_rng(rng),
+ m_has_been_closed(false)
+ {
+ init(io_buf_sz);
+ }
+
+void Channel::init(size_t io_buf_sz)
+ {
+ /* epoch 0 is plaintext, thus null cipher state */
+ m_write_cipher_states[0] = nullptr;
+ m_read_cipher_states[0] = nullptr;
+
+ m_writebuf.reserve(io_buf_sz);
+ m_readbuf.reserve(io_buf_sz);
+ }
+
+void Channel::reset_state()
+ {
+ m_active_state.reset();
+ m_pending_state.reset();
+ m_readbuf.clear();
+ m_write_cipher_states.clear();
+ m_read_cipher_states.clear();
+ }
+
+void Channel::reset_active_association_state()
+ {
+ // This operation only makes sense for DTLS
+ BOTAN_ASSERT_NOMSG(m_is_datagram);
+ m_active_state.reset();
+ m_read_cipher_states.clear();
+ m_write_cipher_states.clear();
+
+ m_write_cipher_states[0] = nullptr;
+ m_read_cipher_states[0] = nullptr;
+
+ if(m_sequence_numbers)
+ m_sequence_numbers->reset();
+ }
+
+Channel::~Channel()
+ {
+ // So unique_ptr destructors run correctly
+ }
+
+Connection_Sequence_Numbers& Channel::sequence_numbers() const
+ {
+ BOTAN_ASSERT(m_sequence_numbers, "Have a sequence numbers object");
+ return *m_sequence_numbers;
+ }
+
+std::shared_ptr<Connection_Cipher_State> Channel::read_cipher_state_epoch(uint16_t epoch) const
+ {
+ auto i = m_read_cipher_states.find(epoch);
+ if(i == m_read_cipher_states.end())
+ throw Internal_Error("TLS::Channel No read cipherstate for epoch " + std::to_string(epoch));
+ return i->second;
+ }
+
+std::shared_ptr<Connection_Cipher_State> Channel::write_cipher_state_epoch(uint16_t epoch) const
+ {
+ auto i = m_write_cipher_states.find(epoch);
+ if(i == m_write_cipher_states.end())
+ throw Internal_Error("TLS::Channel No write cipherstate for epoch " + std::to_string(epoch));
+ return i->second;
+ }
+
+std::vector<X509_Certificate> Channel::peer_cert_chain() const
+ {
+ if(auto active = active_state())
+ return get_peer_cert_chain(*active);
+ return std::vector<X509_Certificate>();
+ }
+
+bool Channel::save_session(const Session& session)
+ {
+ return callbacks().tls_session_established(session);
+ }
+
+Handshake_State& Channel::create_handshake_state(Protocol_Version version)
+ {
+ if(pending_state())
+ throw Internal_Error("create_handshake_state called during handshake");
+
+ if(auto active = active_state())
+ {
+ Protocol_Version active_version = active->version();
+
+ if(active_version.is_datagram_protocol() != version.is_datagram_protocol())
+ {
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Active state using version " + active_version.to_string() +
+ " cannot change to " + version.to_string() + " in pending");
+ }
+ }
+
+ if(!m_sequence_numbers)
+ {
+ if(version.is_datagram_protocol())
+ m_sequence_numbers.reset(new Datagram_Sequence_Numbers);
+ else
+ m_sequence_numbers.reset(new Stream_Sequence_Numbers);
+ }
+
+ using namespace std::placeholders;
+
+ std::unique_ptr<Handshake_IO> io;
+ if(version.is_datagram_protocol())
+ {
+ io.reset(new Datagram_Handshake_IO(
+ std::bind(&Channel::send_record_under_epoch, this, _1, _2, _3),
+ sequence_numbers(),
+ static_cast<uint16_t>(m_policy.dtls_default_mtu()),
+ m_policy.dtls_initial_timeout(),
+ m_policy.dtls_maximum_timeout()));
+ }
+ else
+ {
+ io.reset(new Stream_Handshake_IO(std::bind(&Channel::send_record, this, _1, _2)));
+ }
+
+ m_pending_state.reset(new_handshake_state(io.release()));
+
+ if(auto active = active_state())
+ m_pending_state->set_version(active->version());
+
+ return *m_pending_state.get();
+ }
+
+bool Channel::timeout_check()
+ {
+ if(m_pending_state)
+ return m_pending_state->handshake_io().timeout_check();
+
+ //FIXME: scan cipher suites and remove epochs older than 2*MSL
+ return false;
+ }
+
+void Channel::renegotiate(bool force_full_renegotiation)
+ {
+ if(pending_state()) // currently in handshake?
+ return;
+
+ if(auto active = active_state())
+ {
+ if(force_full_renegotiation == false)
+ force_full_renegotiation = !policy().allow_resumption_for_renegotiation();
+
+ initiate_handshake(create_handshake_state(active->version()),
+ force_full_renegotiation);
+ }
+ else
+ throw Invalid_State("Cannot renegotiate on inactive connection");
+ }
+
+void Channel::change_cipher_spec_reader(Connection_Side side)
+ {
+ auto pending = pending_state();
+
+ BOTAN_ASSERT(pending && pending->server_hello(),
+ "Have received server hello");
+
+ if(pending->server_hello()->compression_method() != 0)
+ throw Internal_Error("Negotiated unknown compression algorithm");
+
+ sequence_numbers().new_read_cipher_state();
+
+ const uint16_t epoch = sequence_numbers().current_read_epoch();
+
+ BOTAN_ASSERT(m_read_cipher_states.count(epoch) == 0,
+ "No read cipher state currently set for next epoch");
+
+ // flip side as we are reading
+ std::shared_ptr<Connection_Cipher_State> read_state(
+ new Connection_Cipher_State(pending->version(),
+ (side == CLIENT) ? SERVER : CLIENT,
+ false,
+ pending->ciphersuite(),
+ pending->session_keys(),
+ pending->server_hello()->supports_encrypt_then_mac()));
+
+ m_read_cipher_states[epoch] = read_state;
+ }
+
+void Channel::change_cipher_spec_writer(Connection_Side side)
+ {
+ auto pending = pending_state();
+
+ BOTAN_ASSERT(pending && pending->server_hello(),
+ "Have received server hello");
+
+ if(pending->server_hello()->compression_method() != 0)
+ throw Internal_Error("Negotiated unknown compression algorithm");
+
+ sequence_numbers().new_write_cipher_state();
+
+ const uint16_t epoch = sequence_numbers().current_write_epoch();
+
+ BOTAN_ASSERT(m_write_cipher_states.count(epoch) == 0,
+ "No write cipher state currently set for next epoch");
+
+ std::shared_ptr<Connection_Cipher_State> write_state(
+ new Connection_Cipher_State(pending->version(),
+ side,
+ true,
+ pending->ciphersuite(),
+ pending->session_keys(),
+ pending->server_hello()->supports_encrypt_then_mac()));
+
+ m_write_cipher_states[epoch] = write_state;
+ }
+
+bool Channel::is_active() const
+ {
+ if(is_closed())
+ return false;
+ return (active_state() != nullptr);
+ }
+
+bool Channel::is_closed() const
+ {
+ return m_has_been_closed;
+ }
+
+void Channel::activate_session()
+ {
+ std::swap(m_active_state, m_pending_state);
+ m_pending_state.reset();
+
+ if(!m_active_state->version().is_datagram_protocol())
+ {
+ // TLS is easy just remove all but the current state
+ const uint16_t current_epoch = sequence_numbers().current_write_epoch();
+
+ const auto not_current_epoch =
+ [current_epoch](uint16_t epoch) { return (epoch != current_epoch); };
+
+ map_remove_if(not_current_epoch, m_write_cipher_states);
+ map_remove_if(not_current_epoch, m_read_cipher_states);
+ }
+
+ callbacks().tls_session_activated();
+ }
+
+size_t Channel::received_data(const std::vector<uint8_t>& buf)
+ {
+ return this->received_data(buf.data(), buf.size());
+ }
+
+size_t Channel::received_data(const uint8_t input[], size_t input_size)
+ {
+ const bool allow_epoch0_restart = m_is_datagram && m_is_server && policy().allow_dtls_epoch0_restart();
+
+ try
+ {
+ while(input_size)
+ {
+ size_t consumed = 0;
+
+ auto get_epoch = [this](uint16_t epoch) { return read_cipher_state_epoch(epoch); };
+
+ const Record_Header record =
+ read_record(m_is_datagram,
+ m_readbuf,
+ input,
+ input_size,
+ consumed,
+ m_record_buf,
+ m_sequence_numbers.get(),
+ get_epoch,
+ allow_epoch0_restart);
+
+ const size_t needed = record.needed();
+
+ BOTAN_ASSERT(consumed > 0, "Got to eat something");
+
+ BOTAN_ASSERT(consumed <= input_size,
+ "Record reader consumed sane amount");
+
+ input += consumed;
+ input_size -= consumed;
+
+ BOTAN_ASSERT(input_size == 0 || needed == 0,
+ "Got a full record or consumed all input");
+
+ if(input_size == 0 && needed != 0)
+ return needed; // need more data to complete record
+
+ // Ignore invalid records in DTLS
+ if(m_is_datagram && record.type() == NO_RECORD)
+ {
+ return 0;
+ }
+
+ if(m_record_buf.size() > MAX_PLAINTEXT_SIZE)
+ throw TLS_Exception(Alert::RECORD_OVERFLOW,
+ "TLS plaintext record is larger than allowed maximum");
+
+
+ const bool epoch0_restart = m_is_datagram && record.epoch() == 0 && active_state();
+ BOTAN_ASSERT_IMPLICATION(epoch0_restart, allow_epoch0_restart, "Allowed state");
+
+ const bool initial_record = epoch0_restart || (!pending_state() && !active_state());
+
+ if(record.type() != ALERT)
+ {
+ if(initial_record)
+ {
+ // For initial records just check for basic sanity
+ if(record.version().major_version() != 3 &&
+ record.version().major_version() != 0xFE)
+ {
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Received unexpected record version in initial record");
+ }
+ }
+ else if(auto pending = pending_state())
+ {
+ if(pending->server_hello() != nullptr && record.version() != pending->version())
+ {
+ if(record.version() != pending->version())
+ {
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Received unexpected record version");
+ }
+ }
+ }
+ else if(auto active = active_state())
+ {
+ if(record.version() != active->version())
+ {
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Received unexpected record version");
+ }
+ }
+ }
+
+ if(record.type() == HANDSHAKE || record.type() == CHANGE_CIPHER_SPEC)
+ {
+ if(m_has_been_closed)
+ throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Received handshake data after connection closure");
+ process_handshake_ccs(m_record_buf, record.sequence(), record.type(), record.version(), epoch0_restart);
+ }
+ else if(record.type() == APPLICATION_DATA)
+ {
+ if(m_has_been_closed)
+ throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Received application data after connection closure");
+ if(pending_state() != nullptr)
+ throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Can't interleave application and handshake data");
+ process_application_data(record.sequence(), m_record_buf);
+ }
+ else if(record.type() == ALERT)
+ {
+ process_alert(m_record_buf);
+ }
+ else if(record.type() != NO_RECORD)
+ throw Unexpected_Message("Unexpected record type " +
+ std::to_string(record.type()) +
+ " from counterparty");
+ }
+
+ return 0; // on a record boundary
+ }
+ catch(TLS_Exception& e)
+ {
+ send_fatal_alert(e.type());
+ throw;
+ }
+ catch(Invalid_Authentication_Tag&)
+ {
+ send_fatal_alert(Alert::BAD_RECORD_MAC);
+ throw;
+ }
+ catch(Decoding_Error&)
+ {
+ send_fatal_alert(Alert::DECODE_ERROR);
+ throw;
+ }
+ catch(...)
+ {
+ send_fatal_alert(Alert::INTERNAL_ERROR);
+ throw;
+ }
+ }
+
+void Channel::process_handshake_ccs(const secure_vector<uint8_t>& record,
+ uint64_t record_sequence,
+ Record_Type record_type,
+ Protocol_Version record_version,
+ bool epoch0_restart)
+ {
+ if(!m_pending_state)
+ {
+ // No pending handshake, possibly new:
+ if(record_version.is_datagram_protocol() && !epoch0_restart)
+ {
+ if(m_sequence_numbers)
+ {
+ /*
+ * Might be a peer retransmit under epoch - 1 in which
+ * case we must retransmit last flight
+ */
+ sequence_numbers().read_accept(record_sequence);
+
+ const uint16_t epoch = record_sequence >> 48;
+
+ if(epoch == sequence_numbers().current_read_epoch())
+ {
+ create_handshake_state(record_version);
+ }
+ else if(epoch == sequence_numbers().current_read_epoch() - 1)
+ {
+ BOTAN_ASSERT(m_active_state, "Have active state here");
+ m_active_state->handshake_io().add_record(record.data(),
+ record.size(),
+ record_type,
+ record_sequence);
+ }
+ }
+ else
+ {
+ create_handshake_state(record_version);
+ }
+ }
+ else
+ {
+ create_handshake_state(record_version);
+ }
+ }
+
+ // May have been created in above conditional
+ if(m_pending_state)
+ {
+ m_pending_state->handshake_io().add_record(record.data(),
+ record.size(),
+ record_type,
+ record_sequence);
+
+ while(auto pending = m_pending_state.get())
+ {
+ auto msg = pending->get_next_handshake_msg();
+
+ if(msg.first == HANDSHAKE_NONE) // no full handshake yet
+ break;
+
+ process_handshake_msg(active_state(), *pending,
+ msg.first, msg.second, epoch0_restart);
+
+ if(!m_pending_state)
+ break;
+ }
+ }
+ }
+
+void Channel::process_application_data(uint64_t seq_no, const secure_vector<uint8_t>& record)
+ {
+ if(!active_state())
+ throw Unexpected_Message("Application data before handshake done");
+
+ callbacks().tls_record_received(seq_no, record.data(), record.size());
+ }
+
+void Channel::process_alert(const secure_vector<uint8_t>& record)
+ {
+ Alert alert_msg(record);
+
+ if(alert_msg.type() == Alert::NO_RENEGOTIATION)
+ m_pending_state.reset();
+
+ callbacks().tls_alert(alert_msg);
+
+ if(alert_msg.is_fatal())
+ {
+ if(auto active = active_state())
+ m_session_manager.remove_entry(active->server_hello()->session_id());
+ }
+
+ if(alert_msg.type() == Alert::CLOSE_NOTIFY)
+ send_warning_alert(Alert::CLOSE_NOTIFY); // reply in kind
+
+ if(alert_msg.type() == Alert::CLOSE_NOTIFY || alert_msg.is_fatal())
+ {
+ m_has_been_closed = true;
+ }
+ }
+
+void Channel::write_record(Connection_Cipher_State* cipher_state, uint16_t epoch,
+ uint8_t record_type, const uint8_t input[], size_t length)
+ {
+ BOTAN_ASSERT(m_pending_state || m_active_state, "Some connection state exists");
+
+ const Protocol_Version record_version =
+ (m_pending_state) ? (m_pending_state->version()) : (m_active_state->version());
+
+ const uint64_t next_seq = sequence_numbers().next_write_sequence(epoch);
+
+ if(cipher_state == nullptr)
+ {
+ TLS::write_unencrypted_record(m_writebuf, record_type, record_version, next_seq,
+ input, length);
+ }
+ else
+ {
+ TLS::write_record(m_writebuf, record_type, record_version, next_seq,
+ input, length, *cipher_state, m_rng);
+ }
+
+ callbacks().tls_emit_data(m_writebuf.data(), m_writebuf.size());
+ }
+
+void Channel::send_record_array(uint16_t epoch, uint8_t type, const uint8_t input[], size_t length)
+ {
+ if(length == 0)
+ return;
+
+ /*
+ * In versions without an explicit IV field (only TLS v1.0 now that
+ * SSLv3 has been removed) send a single byte record first to randomize
+ * the following (implicit) IV of the following record.
+ *
+ * This isn't needed in TLS v1.1 or higher.
+ *
+ * An empty record also works but apparently some implementations do
+ * not like this (https://bugzilla.mozilla.org/show_bug.cgi?id=665814)
+ *
+ * See https://www.openssl.org/~bodo/tls-cbc.txt for background.
+ */
+
+ auto cipher_state = write_cipher_state_epoch(epoch);
+
+ if(type == APPLICATION_DATA && m_active_state->version().supports_explicit_cbc_ivs() == false)
+ {
+ while(length)
+ {
+ write_record(cipher_state.get(), epoch, type, input, 1);
+ input += 1;
+ length -= 1;
+
+ const size_t sending = std::min<size_t>(length, MAX_PLAINTEXT_SIZE);
+ write_record(cipher_state.get(), epoch, type, input, sending);
+
+ input += sending;
+ length -= sending;
+ }
+ }
+ else
+ {
+ while(length)
+ {
+ const size_t sending = std::min<size_t>(length, MAX_PLAINTEXT_SIZE);
+ write_record(cipher_state.get(), epoch, type, input, sending);
+
+ input += sending;
+ length -= sending;
+ }
+ }
+ }
+
+void Channel::send_record(uint8_t record_type, const std::vector<uint8_t>& record)
+ {
+ send_record_array(sequence_numbers().current_write_epoch(),
+ record_type, record.data(), record.size());
+ }
+
+void Channel::send_record_under_epoch(uint16_t epoch, uint8_t record_type,
+ const std::vector<uint8_t>& record)
+ {
+ send_record_array(epoch, record_type, record.data(), record.size());
+ }
+
+void Channel::send(const uint8_t buf[], size_t buf_size)
+ {
+ if(!is_active())
+ throw Invalid_State("Data cannot be sent on inactive TLS connection");
+
+ send_record_array(sequence_numbers().current_write_epoch(),
+ APPLICATION_DATA, buf, buf_size);
+ }
+
+void Channel::send(const std::string& string)
+ {
+ this->send(cast_char_ptr_to_uint8(string.data()), string.size());
+ }
+
+void Channel::send_alert(const Alert& alert)
+ {
+ if(alert.is_valid() && !is_closed())
+ {
+ try
+ {
+ send_record(ALERT, alert.serialize());
+ }
+ catch(...) { /* swallow it */ }
+ }
+
+ if(alert.type() == Alert::NO_RENEGOTIATION)
+ m_pending_state.reset();
+
+ if(alert.is_fatal())
+ {
+ if(auto active = active_state())
+ {
+ m_session_manager.remove_entry(active->server_hello()->session_id());
+ }
+ reset_state();
+ }
+
+ if(alert.type() == Alert::CLOSE_NOTIFY || alert.is_fatal())
+ {
+ m_has_been_closed = true;
+ }
+ }
+
+void Channel::secure_renegotiation_check(const Client_Hello* client_hello)
+ {
+ const bool secure_renegotiation = client_hello->secure_renegotiation();
+
+ if(auto active = active_state())
+ {
+ const bool active_sr = active->client_hello()->secure_renegotiation();
+
+ if(active_sr != secure_renegotiation)
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Client changed its mind about secure renegotiation");
+ }
+
+ if(secure_renegotiation)
+ {
+ const std::vector<uint8_t>& data = client_hello->renegotiation_info();
+
+ if(data != secure_renegotiation_data_for_client_hello())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Client sent bad values for secure renegotiation");
+ }
+ }
+
+void Channel::secure_renegotiation_check(const Server_Hello* server_hello)
+ {
+ const bool secure_renegotiation = server_hello->secure_renegotiation();
+
+ if(auto active = active_state())
+ {
+ const bool active_sr = active->server_hello()->secure_renegotiation();
+
+ if(active_sr != secure_renegotiation)
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Server changed its mind about secure renegotiation");
+ }
+
+ if(secure_renegotiation)
+ {
+ const std::vector<uint8_t>& data = server_hello->renegotiation_info();
+
+ if(data != secure_renegotiation_data_for_server_hello())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Server sent bad values for secure renegotiation");
+ }
+ }
+
+std::vector<uint8_t> Channel::secure_renegotiation_data_for_client_hello() const
+ {
+ if(auto active = active_state())
+ return active->client_finished()->verify_data();
+ return std::vector<uint8_t>();
+ }
+
+std::vector<uint8_t> Channel::secure_renegotiation_data_for_server_hello() const
+ {
+ if(auto active = active_state())
+ {
+ std::vector<uint8_t> buf = active->client_finished()->verify_data();
+ buf += active->server_finished()->verify_data();
+ return buf;
+ }
+
+ return std::vector<uint8_t>();
+ }
+
+bool Channel::secure_renegotiation_supported() const
+ {
+ if(auto active = active_state())
+ return active->server_hello()->secure_renegotiation();
+
+ if(auto pending = pending_state())
+ if(auto hello = pending->server_hello())
+ return hello->secure_renegotiation();
+
+ return false;
+ }
+
+SymmetricKey Channel::key_material_export(const std::string& label,
+ const std::string& context,
+ size_t length) const
+ {
+ if(auto active = active_state())
+ {
+ if(pending_state() != nullptr)
+ throw Invalid_State("Channel::key_material_export cannot export during renegotiation");
+
+ std::unique_ptr<KDF> prf(active->protocol_specific_prf());
+
+ const secure_vector<uint8_t>& master_secret =
+ active->session_keys().master_secret();
+
+ std::vector<uint8_t> salt;
+ salt += active->client_hello()->random();
+ salt += active->server_hello()->random();
+
+ if(context != "")
+ {
+ size_t context_size = context.length();
+ if(context_size > 0xFFFF)
+ throw Invalid_Argument("key_material_export context is too long");
+ salt.push_back(get_byte(0, static_cast<uint16_t>(context_size)));
+ salt.push_back(get_byte(1, static_cast<uint16_t>(context_size)));
+ salt += to_byte_vector(context);
+ }
+
+ return prf->derive_key(length, master_secret, salt, to_byte_vector(label));
+ }
+ else
+ {
+ throw Invalid_State("Channel::key_material_export connection not active");
+ }
+ }
+
+}
+
+}