/* * TLS cipher state implementation for TLS 1.3 * (C) 2022 Jack Lloyd * 2022 Hannes Rantzsch, René Meusel - neXenio GmbH * * Botan is released under the Simplified BSD License (see license.txt) */ /** * Cipher_State state machine adapted from RFC 8446 7.1. * * 0 * | * v * PSK -> HKDF-Extract = Early Secret * | * +-----> Derive-Secret(., "ext binder" | "res binder", "") * | = binder_key * STATE PSK BINDER * This state is reached by constructing the Cipher_State using init_with_psk(). * The state can then be further advanced using advance_with_client_hello() once * the initial Client Hello is fully generated. * | * +-----> Derive-Secret(., "c e traffic", ClientHello) * | = client_early_traffic_secret * | * +-----> Derive-Secret(., "e exp master", ClientHello) * | = early_exporter_master_secret * v * Derive-Secret(., "derived", "") * | * * * STATE EARLY TRAFFIC * This state is reached by calling advance_with_client_hello(). * In this state the early data traffic secrets are available. TODO: implement early data. * The state can then be further advanced using advance_with_server_hello(). * * * | * v * (EC)DHE -> HKDF-Extract = Handshake Secret * | * +-----> Derive-Secret(., "c hs traffic", * | ClientHello...ServerHello) * | = client_handshake_traffic_secret * | * +-----> Derive-Secret(., "s hs traffic", * | ClientHello...ServerHello) * | = server_handshake_traffic_secret * v * Derive-Secret(., "derived", "") * | * * * STATE HANDSHAKE TRAFFIC * This state is reached by constructing Cipher_State using init_with_server_hello() or * advance_with_server_hello(). In this state the handshake traffic secrets are available. * The state can then be further advanced using advance_with_server_finished(). * * * | * v * 0 -> HKDF-Extract = Master Secret * | * +-----> Derive-Secret(., "c ap traffic", * | ClientHello...server Finished) * | = client_application_traffic_secret_0 * | * +-----> Derive-Secret(., "s ap traffic", * | ClientHello...server Finished) * | = server_application_traffic_secret_0 * | * +-----> Derive-Secret(., "exp master", * | ClientHello...server Finished) * | = exporter_master_secret * * * STATE SERVER APPLICATION TRAFFIC * This state is reached by calling advance_with_server_finished(). It allows the server * to send application traffic and the client to receive it. The opposite direction is not * yet possible in this state. The state can then be further advanced using * advance_with_client_finished(). * * * | * +-----> Derive-Secret(., "res master", * ClientHello...client Finished) * = resumption_master_secret * STATE COMPLETED * Once this state is reached the handshake is finished, both client and server can exchange * application data and no further cipher state advances are possible. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Botan::TLS { namespace { // RFC 8446 5.3 // Each AEAD algorithm will specify a range of possible lengths for the // per-record nonce, from N_MIN bytes to N_MAX bytes of input [RFC5116]. // The length of the TLS per-record nonce (iv_length) is set to the // larger of 8 bytes and N_MIN for the AEAD algorithm (see [RFC5116], // Section 4). // // N_MIN is 12 for AES_GCM and AES_CCM as per RFC 5116 and also 12 for ChaCha20 per RFC 8439. constexpr size_t NONCE_LENGTH = 12; } // namespace std::unique_ptr Cipher_State::init_with_server_hello(const Connection_Side side, secure_vector&& shared_secret, const Ciphersuite& cipher, const Transcript_Hash& transcript_hash, const Secret_Logger& loggger) { auto cs = std::unique_ptr(new Cipher_State(side, cipher.prf_algo())); cs->advance_without_psk(); cs->advance_with_server_hello(cipher, std::move(shared_secret), transcript_hash, loggger); return cs; } std::unique_ptr Cipher_State::init_with_psk(const Connection_Side side, const Cipher_State::PSK_Type type, secure_vector&& psk, std::string_view prf_algo) { auto cs = std::unique_ptr(new Cipher_State(side, prf_algo)); cs->advance_with_psk(type, std::move(psk)); return cs; } void Cipher_State::advance_with_client_hello(const Transcript_Hash& transcript_hash, const Secret_Logger& loggger) { BOTAN_ASSERT_NOMSG(m_state == State::PskBinder); zap(m_binder_key); // TODO: Currently 0-RTT is not yet implemented, hence we don't derive the // early traffic secret for now. // // const auto client_early_traffic_secret = derive_secret(m_early_secret, "c e traffic", transcript_hash); // derive_write_traffic_key(client_early_traffic_secret); m_exporter_master_secret = derive_secret(m_early_secret, "e exp master", transcript_hash); // draft-thomson-tls-keylogfile-00 Section 3.1 // An implementation of TLS 1.3 use the label // "EARLY_EXPORTER_MASTER_SECRET" to identify the secret that is using for // early exporters loggger.maybe_log_secret("EARLY_EXPORTER_MASTER_SECRET", m_exporter_master_secret); m_salt = derive_secret(m_early_secret, "derived", empty_hash()); zap(m_early_secret); m_state = State::EarlyTraffic; } void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcript_hash, const Secret_Logger& loggger) { BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic); const auto master_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); auto client_application_traffic_secret = derive_secret(master_secret, "c ap traffic", transcript_hash); auto server_application_traffic_secret = derive_secret(master_secret, "s ap traffic", transcript_hash); // draft-thomson-tls-keylogfile-00 Section 3.1 // An implementation of TLS 1.3 use the label "CLIENT_TRAFFIC_SECRET_0" // and "SERVER_TRAFFIC_SECRET_0" to identify the secrets are using to // protect the connection. loggger.maybe_log_secret("CLIENT_TRAFFIC_SECRET_0", client_application_traffic_secret); loggger.maybe_log_secret("SERVER_TRAFFIC_SECRET_0", server_application_traffic_secret); // Note: the secrets for processing client's application data // are not derived before the client's Finished message // was seen and the handshake can be considered finished. if(m_connection_side == Connection_Side::Server) { derive_write_traffic_key(server_application_traffic_secret); m_read_application_traffic_secret = std::move(client_application_traffic_secret); m_write_application_traffic_secret = std::move(server_application_traffic_secret); } else { derive_read_traffic_key(server_application_traffic_secret); m_read_application_traffic_secret = std::move(server_application_traffic_secret); m_write_application_traffic_secret = std::move(client_application_traffic_secret); } m_exporter_master_secret = derive_secret(master_secret, "exp master", transcript_hash); // draft-thomson-tls-keylogfile-00 Section 3.1 // An implementation of TLS 1.3 use the label "EXPORTER_SECRET" to // identify the secret that is used in generating exporters(rfc8446 // Section 7.5). loggger.maybe_log_secret("EXPORTER_SECRET", m_exporter_master_secret); m_state = State::ServerApplicationTraffic; } void Cipher_State::advance_with_client_finished(const Transcript_Hash& transcript_hash) { BOTAN_ASSERT_NOMSG(m_state == State::ServerApplicationTraffic); zap(m_finished_key); zap(m_peer_finished_key); // With the client's Finished message, the handshake is complete and // we can process client application data. if(m_connection_side == Connection_Side::Server) { derive_read_traffic_key(m_read_application_traffic_secret); } else { derive_write_traffic_key(m_write_application_traffic_secret); } const auto master_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); m_resumption_master_secret = derive_secret(master_secret, "res master", transcript_hash); // This was the final state change; the salt is no longer needed. zap(m_salt); m_state = State::Completed; } namespace { auto current_nonce(const uint64_t seq_no, std::span iv) { // RFC 8446 5.3 // The per-record nonce for the AEAD construction is formed as follows: // // 1. The 64-bit record sequence number is encoded in network byte // order and padded to the left with zeros to iv_length. // // 2. The padded sequence number is XORed with either the static // client_write_iv or server_write_iv (depending on the role). std::array nonce{}; store_be(std::span{nonce}.last(), seq_no); xor_buf(nonce, iv); return nonce; } } // namespace uint64_t Cipher_State::encrypt_record_fragment(const std::vector& header, secure_vector& fragment) { BOTAN_ASSERT_NONNULL(m_encrypt); m_encrypt->set_key(m_write_key); m_encrypt->set_associated_data(header); m_encrypt->start(current_nonce(m_write_seq_no, m_write_iv)); m_encrypt->finish(fragment); return m_write_seq_no++; } uint64_t Cipher_State::decrypt_record_fragment(const std::vector& header, secure_vector& encrypted_fragment) { BOTAN_ASSERT_NONNULL(m_decrypt); BOTAN_ARG_CHECK(encrypted_fragment.size() >= m_decrypt->minimum_final_size(), "fragment too short to decrypt"); m_decrypt->set_key(m_read_key); m_decrypt->set_associated_data(header); m_decrypt->start(current_nonce(m_read_seq_no, m_read_iv)); m_decrypt->finish(encrypted_fragment); return m_read_seq_no++; } size_t Cipher_State::encrypt_output_length(const size_t input_length) const { BOTAN_ASSERT_NONNULL(m_encrypt); return m_encrypt->output_length(input_length); } size_t Cipher_State::decrypt_output_length(const size_t input_length) const { BOTAN_ASSERT_NONNULL(m_decrypt); return m_decrypt->output_length(input_length); } size_t Cipher_State::minimum_decryption_input_length() const { BOTAN_ASSERT_NONNULL(m_decrypt); return m_decrypt->minimum_final_size(); } bool Cipher_State::must_expect_unprotected_alert_traffic() const { // Client side: // After successfully receiving a Server Hello we expect servers to send // alerts as protected records only, just like they start protecting their // handshake data at this point. if(m_connection_side == Connection_Side::Client && m_state == State::EarlyTraffic) { return true; } // Server side: // Servers must expect clients to send unprotected alerts during the hand- // shake. In particular, in the response to the server's first protected // flight. We don't expect the client to send alerts protected under the // early traffic secret. // // TODO: when implementing PSK and/or early data for the server, we might // need to reconsider this decision. if(m_connection_side == Connection_Side::Server && (m_state == State::HandshakeTraffic || m_state == State::ServerApplicationTraffic)) { return true; } return false; } bool Cipher_State::can_encrypt_application_traffic() const { // TODO: when implementing early traffic (0-RTT) this will likely need // to allow `State::EarlyTraffic`. if(m_connection_side == Connection_Side::Client && m_state != State::Completed) { return false; } if(m_connection_side == Connection_Side::Server && m_state != State::ServerApplicationTraffic && m_state != State::Completed) { return false; } return !m_write_key.empty() && !m_write_iv.empty(); } bool Cipher_State::can_decrypt_application_traffic() const { // TODO: when implementing early traffic (0-RTT) this will likely need // to allow `State::EarlyTraffic`. if(m_connection_side == Connection_Side::Client && m_state != State::ServerApplicationTraffic && m_state != State::Completed) { return false; } if(m_connection_side == Connection_Side::Server && m_state != State::Completed) { return false; } return !m_read_key.empty() && !m_read_iv.empty(); } std::string Cipher_State::hash_algorithm() const { BOTAN_ASSERT_NONNULL(m_hash); return m_hash->name(); } bool Cipher_State::is_compatible_with(const Ciphersuite& cipher) const { if(!cipher.usable_in_version(Protocol_Version::TLS_V13)) { return false; } if(hash_algorithm() != cipher.prf_algo()) { return false; } BOTAN_ASSERT_NOMSG((m_encrypt == nullptr) == (m_decrypt == nullptr)); // TODO: Find a better way to check that the instantiated cipher algorithm // is compatible with the one required by the cipher suite. // AEAD_Mode::create() sets defaults the tag length to 16 which is then // reported via AEAD_Mode::name() and hinders the trivial string comparison. if(m_encrypt && m_encrypt->name() != cipher.cipher_algo() && m_encrypt->name() != cipher.cipher_algo() + "(16)") { return false; } return true; } std::vector Cipher_State::psk_binder_mac( const Transcript_Hash& transcript_hash_with_truncated_client_hello) const { BOTAN_ASSERT_NOMSG(m_state == State::PskBinder); auto hmac = HMAC(m_hash->new_object()); hmac.set_key(m_binder_key); hmac.update(transcript_hash_with_truncated_client_hello); return hmac.final_stdvec(); } std::vector Cipher_State::finished_mac(const Transcript_Hash& transcript_hash) const { BOTAN_ASSERT_NOMSG(m_connection_side != Connection_Side::Server || m_state == State::HandshakeTraffic); BOTAN_ASSERT_NOMSG(m_connection_side != Connection_Side::Client || m_state == State::ServerApplicationTraffic); BOTAN_ASSERT_NOMSG(!m_finished_key.empty()); auto hmac = HMAC(m_hash->new_object()); hmac.set_key(m_finished_key); hmac.update(transcript_hash); return hmac.final_stdvec(); } bool Cipher_State::verify_peer_finished_mac(const Transcript_Hash& transcript_hash, const std::vector& peer_mac) const { BOTAN_ASSERT_NOMSG(m_connection_side != Connection_Side::Server || m_state == State::ServerApplicationTraffic); BOTAN_ASSERT_NOMSG(m_connection_side != Connection_Side::Client || m_state == State::HandshakeTraffic); BOTAN_ASSERT_NOMSG(!m_peer_finished_key.empty()); auto hmac = HMAC(m_hash->new_object()); hmac.set_key(m_peer_finished_key); hmac.update(transcript_hash); return hmac.verify_mac(peer_mac); } secure_vector Cipher_State::psk(const Ticket_Nonce& nonce) const { BOTAN_ASSERT_NOMSG(m_state == State::Completed); return derive_secret(m_resumption_master_secret, "resumption", nonce.get()); } Ticket_Nonce Cipher_State::next_ticket_nonce() { BOTAN_STATE_CHECK(m_state == State::Completed); if(m_ticket_nonce == std::numeric_limits::max()) { throw Botan::Invalid_State("ticket nonce pool exhausted"); } Ticket_Nonce retval(std::vector(sizeof(m_ticket_nonce))); store_be(m_ticket_nonce++, retval.data()); return retval; } secure_vector Cipher_State::export_key(std::string_view label, std::string_view context, size_t length) const { BOTAN_ASSERT_NOMSG(can_export_keys()); m_hash->update(context); const auto context_hash = m_hash->final_stdvec(); return hkdf_expand_label( derive_secret(m_exporter_master_secret, label, empty_hash()), "exporter", context_hash, length); } namespace { std::unique_ptr create_hmac(std::string_view hash) { return std::make_unique(HashFunction::create_or_throw(hash)); } } // namespace Cipher_State::Cipher_State(Connection_Side whoami, std::string_view hash_function) : m_state(State::Uninitialized), m_connection_side(whoami), m_extract(std::make_unique(create_hmac(hash_function))), m_expand(std::make_unique(create_hmac(hash_function))), m_hash(HashFunction::create_or_throw(hash_function)), m_salt(m_hash->output_length(), 0x00), m_write_seq_no(0), m_read_seq_no(0), m_write_key_update_count(0), m_read_key_update_count(0), m_ticket_nonce(0) {} Cipher_State::~Cipher_State() = default; void Cipher_State::advance_without_psk() { BOTAN_ASSERT_NOMSG(m_state == State::Uninitialized); // We are not using `m_early_secret` here because the secret won't be needed // in any further state advancement methods. const auto early_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); m_salt = derive_secret(early_secret, "derived", empty_hash()); // Without PSK we skip the `PskBinder` state and go right to `EarlyTraffic`. m_state = State::EarlyTraffic; } void Cipher_State::advance_with_psk(PSK_Type type, secure_vector&& psk) { BOTAN_ASSERT_NOMSG(m_state == State::Uninitialized); m_early_secret = hkdf_extract(std::move(psk)); const char* binder_label = (type == PSK_Type::Resumption) ? "res binder" : "ext binder"; // RFC 8446 4.2.11.2 // The PskBinderEntry is computed in the same way as the Finished message // [...] but with the BaseKey being the binder_key derived via the key // schedule from the corresponding PSK which is being offered. // // Hence we are doing the binder key derivation and expansion in one go. const auto binder_key = derive_secret(m_early_secret, binder_label, empty_hash()); m_binder_key = hkdf_expand_label(binder_key, "finished", {}, m_hash->output_length()); m_state = State::PskBinder; } void Cipher_State::advance_with_server_hello(const Ciphersuite& cipher, secure_vector&& shared_secret, const Transcript_Hash& transcript_hash, const Secret_Logger& loggger) { BOTAN_ASSERT_NOMSG(m_state == State::EarlyTraffic); BOTAN_ASSERT_NOMSG(!m_encrypt); BOTAN_ASSERT_NOMSG(!m_decrypt); BOTAN_STATE_CHECK(is_compatible_with(cipher)); m_encrypt = AEAD_Mode::create_or_throw(cipher.cipher_algo(), Cipher_Dir::Encryption); m_decrypt = AEAD_Mode::create_or_throw(cipher.cipher_algo(), Cipher_Dir::Decryption); const auto handshake_secret = hkdf_extract(std::move(shared_secret)); const auto client_handshake_traffic_secret = derive_secret(handshake_secret, "c hs traffic", transcript_hash); const auto server_handshake_traffic_secret = derive_secret(handshake_secret, "s hs traffic", transcript_hash); // draft-thomson-tls-keylogfile-00 Section 3.1 // An implementation of TLS 1.3 use the label // "CLIENT_HANDSHAKE_TRAFFIC_SECRET" and "SERVER_HANDSHAKE_TRAFFIC_SECRET" // to identify the secrets are using to protect handshake messages. loggger.maybe_log_secret("CLIENT_HANDSHAKE_TRAFFIC_SECRET", client_handshake_traffic_secret); loggger.maybe_log_secret("SERVER_HANDSHAKE_TRAFFIC_SECRET", server_handshake_traffic_secret); if(m_connection_side == Connection_Side::Server) { derive_read_traffic_key(client_handshake_traffic_secret, true); derive_write_traffic_key(server_handshake_traffic_secret, true); } else { derive_read_traffic_key(server_handshake_traffic_secret, true); derive_write_traffic_key(client_handshake_traffic_secret, true); } m_salt = derive_secret(handshake_secret, "derived", empty_hash()); m_state = State::HandshakeTraffic; } void Cipher_State::derive_write_traffic_key(const secure_vector& traffic_secret, const bool handshake_traffic_secret) { BOTAN_ASSERT_NONNULL(m_encrypt); m_write_key = hkdf_expand_label(traffic_secret, "key", {}, m_encrypt->minimum_keylength()); m_write_iv = hkdf_expand_label(traffic_secret, "iv", {}, NONCE_LENGTH); m_write_seq_no = 0; if(handshake_traffic_secret) { // Key derivation for the MAC in the "Finished" handshake message as described in RFC 8446 4.4.4 // (will be cleared in advance_with_server_finished()) m_finished_key = hkdf_expand_label(traffic_secret, "finished", {}, m_hash->output_length()); } } void Cipher_State::derive_read_traffic_key(const secure_vector& traffic_secret, const bool handshake_traffic_secret) { BOTAN_ASSERT_NONNULL(m_encrypt); m_read_key = hkdf_expand_label(traffic_secret, "key", {}, m_encrypt->minimum_keylength()); m_read_iv = hkdf_expand_label(traffic_secret, "iv", {}, NONCE_LENGTH); m_read_seq_no = 0; if(handshake_traffic_secret) { // Key derivation for the MAC in the "Finished" handshake message as described in RFC 8446 4.4.4 // (will be cleared in advance_with_client_finished()) m_peer_finished_key = hkdf_expand_label(traffic_secret, "finished", {}, m_hash->output_length()); } } secure_vector Cipher_State::hkdf_extract(std::span ikm) const { return m_extract->derive_key(m_hash->output_length(), ikm, m_salt, std::vector()); } secure_vector Cipher_State::hkdf_expand_label(const secure_vector& secret, std::string_view label, const std::vector& context, const size_t length) const { // assemble (serialized) HkdfLabel secure_vector hkdf_label; hkdf_label.reserve(2 /* length */ + (label.size() + 6 /* 'tls13 ' */ + 1 /* length field*/) + (context.size() + 1 /* length field*/)); // length BOTAN_ARG_CHECK(length <= std::numeric_limits::max(), "invalid length"); const auto len = static_cast(length); hkdf_label.push_back(get_byte<0>(len)); hkdf_label.push_back(get_byte<1>(len)); // label const std::string prefix = "tls13 "; BOTAN_ARG_CHECK(prefix.size() + label.size() <= 255, "label too large"); hkdf_label.push_back(static_cast(prefix.size() + label.size())); hkdf_label.insert(hkdf_label.end(), prefix.cbegin(), prefix.cend()); hkdf_label.insert(hkdf_label.end(), label.cbegin(), label.cend()); // context BOTAN_ARG_CHECK(context.size() <= 255, "context too large"); hkdf_label.push_back(static_cast(context.size())); hkdf_label.insert(hkdf_label.end(), context.cbegin(), context.cend()); // HKDF-Expand return m_expand->derive_key( length, secret, hkdf_label, std::vector() /* just pleasing botan's interface */); } secure_vector Cipher_State::derive_secret(const secure_vector& secret, std::string_view label, const Transcript_Hash& messages_hash) const { return hkdf_expand_label(secret, label, messages_hash, m_hash->output_length()); } std::vector Cipher_State::empty_hash() const { m_hash->update(""); return m_hash->final_stdvec(); } void Cipher_State::update_read_keys(const Secret_Logger& logger) { BOTAN_ASSERT_NOMSG(m_state == State::ServerApplicationTraffic || m_state == State::Completed); m_read_application_traffic_secret = hkdf_expand_label(m_read_application_traffic_secret, "traffic upd", {}, m_hash->output_length()); const auto secret_label = fmt("{}_TRAFFIC_SECRET_{}", m_connection_side == Connection_Side::Server ? "CLIENT" : "SERVER", ++m_read_key_update_count); logger.maybe_log_secret(secret_label, m_read_application_traffic_secret); derive_read_traffic_key(m_read_application_traffic_secret); } void Cipher_State::update_write_keys(const Secret_Logger& logger) { BOTAN_ASSERT_NOMSG(m_state == State::ServerApplicationTraffic || m_state == State::Completed); m_write_application_traffic_secret = hkdf_expand_label(m_write_application_traffic_secret, "traffic upd", {}, m_hash->output_length()); const auto secret_label = fmt("{}_TRAFFIC_SECRET_{}", m_connection_side == Connection_Side::Server ? "SERVER" : "CLIENT", ++m_write_key_update_count); logger.maybe_log_secret(secret_label, m_write_application_traffic_secret); derive_write_traffic_key(m_write_application_traffic_secret); } void Cipher_State::clear_read_keys() { zap(m_read_key); zap(m_read_iv); zap(m_read_application_traffic_secret); } void Cipher_State::clear_write_keys() { zap(m_write_key); zap(m_write_iv); zap(m_write_application_traffic_secret); } } // namespace Botan::TLS