/*
* TLS Client
* (C) 2004-2011,2012,2015,2016 Jack Lloyd
*     2016 Matthias Gierlings
*     2017 Harry Reimann, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/internal/tls_client_impl_12.h>

#include <botan/ocsp.h>
#include <botan/tls_callbacks.h>
#include <botan/tls_messages_12.h>
#include <botan/tls_policy.h>
#include <botan/internal/stl_util.h>
#include <botan/internal/tls_handshake_state.h>
#include <algorithm>
#include <optional>
#include <sstream>
#include <utility>

namespace Botan::TLS {

namespace {

class Client_Handshake_State_12 final : public Handshake_State {
   public:
      Client_Handshake_State_12(std::unique_ptr<Handshake_IO> io, Callbacks& cb) :
            Handshake_State(std::move(io), cb), m_is_reneg(false) {}

      const Public_Key& server_public_key() const {
         BOTAN_ASSERT(m_server_public_key, "Server sent us a certificate");
         return *m_server_public_key;
      }

      const Public_Key* maybe_server_public_key() const { return m_server_public_key.get(); }

      void record_server_public_key(std::unique_ptr<Public_Key> spk) {
         BOTAN_STATE_CHECK(!m_server_public_key);
         m_server_public_key = std::move(spk);
      }

      bool is_a_resumption() const { return m_resumed_session.has_value(); }

      void discard_resumption_state() { m_resumed_session.reset(); }

      void record_resumption_info(std::optional<Session> session_info) {
         BOTAN_STATE_CHECK(!m_resumed_session.has_value());
         m_resumed_session = std::move(session_info);
      }

      bool is_a_renegotiation() const { return m_is_reneg; }

      void mark_as_renegotiation() { m_is_reneg = true; }

      const secure_vector<uint8_t>& resume_master_secret() const {
         BOTAN_STATE_CHECK(is_a_resumption());
         return m_resumed_session->master_secret();
      }

      const std::vector<X509_Certificate>& resume_peer_certs() const {
         BOTAN_STATE_CHECK(is_a_resumption());
         return m_resumed_session->peer_certs();
      }

      bool resumed_session_supports_extended_master_secret() const {
         BOTAN_STATE_CHECK(is_a_resumption());
         return m_resumed_session->supports_extended_master_secret();
      }

   private:
      std::unique_ptr<Public_Key> m_server_public_key;

      // Used during session resumption
      std::optional<Session> m_resumed_session;
      bool m_is_reneg = false;
};

}  // namespace

/*
* TLS 1.2 Client  Constructor
*/
Client_Impl_12::Client_Impl_12(const std::shared_ptr<Callbacks>& callbacks,
                               const std::shared_ptr<Session_Manager>& session_manager,
                               const std::shared_ptr<Credentials_Manager>& creds,
                               const std::shared_ptr<const Policy>& policy,
                               const std::shared_ptr<RandomNumberGenerator>& rng,
                               Server_Information info,
                               bool datagram,
                               const std::vector<std::string>& next_protocols,
                               size_t io_buf_sz) :
      Channel_Impl_12(callbacks, session_manager, rng, policy, false, datagram, io_buf_sz),
      m_creds(creds),
      m_info(std::move(info)) {
   BOTAN_ASSERT_NONNULL(m_creds);
   const auto version = datagram ? Protocol_Version::DTLS_V12 : Protocol_Version::TLS_V12;
   Handshake_State& state = create_handshake_state(version);
   send_client_hello(state, false, version, std::nullopt /* no a-priori session to resume */, next_protocols);
}

Client_Impl_12::Client_Impl_12(const Channel_Impl::Downgrade_Information& downgrade_info) :
      Channel_Impl_12(downgrade_info.callbacks,
                      downgrade_info.session_manager,
                      downgrade_info.rng,
                      downgrade_info.policy,
                      false /* is_server */,
                      false /* datagram -- not supported by Botan in TLS 1.3 */,
                      downgrade_info.io_buffer_size),
      m_creds(downgrade_info.creds),
      m_info(downgrade_info.server_info) {
   Handshake_State& state = create_handshake_state(Protocol_Version::TLS_V12);

   if(!downgrade_info.client_hello_message.empty()) {
      // Downgrade detected after receiving a TLS 1.2 server hello. We need to
      // recreate the state as if this implementation issued the client hello.
      const std::vector<uint8_t> client_hello_msg(
         downgrade_info.client_hello_message.begin() + 4 /* handshake header length */,
         downgrade_info.client_hello_message.end());

      state.client_hello(std::make_unique<Client_Hello_12>(client_hello_msg));
      state.hash().update(downgrade_info.client_hello_message);

      secure_renegotiation_check(state.client_hello());
      state.set_expected_next(Handshake_Type::ServerHello);
   } else {
      // Downgrade initiated after a TLS 1.2 session was found. No communication
      // has happened yet but the found session should be used for resumption.
      BOTAN_ASSERT_NOMSG(downgrade_info.tls12_session.has_value() &&
                         downgrade_info.tls12_session->session.version().is_pre_tls_13());
      send_client_hello(state,
                        false,
                        downgrade_info.tls12_session->session.version(),
                        downgrade_info.tls12_session,
                        downgrade_info.next_protocols);
   }
}

std::unique_ptr<Handshake_State> Client_Impl_12::new_handshake_state(std::unique_ptr<Handshake_IO> io) {
   return std::make_unique<Client_Handshake_State_12>(std::move(io), callbacks());
}

std::vector<X509_Certificate> Client_Impl_12::get_peer_cert_chain(const Handshake_State& state) const {
   const Client_Handshake_State_12& cstate = dynamic_cast<const Client_Handshake_State_12&>(state);

   if(cstate.is_a_resumption()) {
      return cstate.resume_peer_certs();
   }

   if(state.server_certs() != nullptr) {
      return state.server_certs()->cert_chain();
   }
   return std::vector<X509_Certificate>();
}

/*
* Send a new client hello to renegotiate
*/
void Client_Impl_12::initiate_handshake(Handshake_State& state, bool force_full_renegotiation) {
   // we don't support TLS < 1.2 anymore and TLS 1.3 should not use this client impl
   const auto version = state.version().is_datagram_protocol() ? Protocol_Version::DTLS_V12 : Protocol_Version::TLS_V12;
   send_client_hello(state, force_full_renegotiation, version);
}

void Client_Impl_12::send_client_hello(Handshake_State& state_base,
                                       bool force_full_renegotiation,
                                       Protocol_Version version,
                                       std::optional<Session_with_Handle> session_and_handle,
                                       const std::vector<std::string>& next_protocols) {
   Client_Handshake_State_12& state = dynamic_cast<Client_Handshake_State_12&>(state_base);

   if(state.version().is_datagram_protocol()) {
      state.set_expected_next(Handshake_Type::HelloVerifyRequest);  // optional
   }
   state.set_expected_next(Handshake_Type::ServerHello);

   if(!force_full_renegotiation) {
      // if no session is provided, we need to try and find one opportunistically
      if(!session_and_handle.has_value() && !m_info.empty()) {
         if(auto sessions = session_manager().find(m_info, callbacks(), policy()); !sessions.empty()) {
            session_and_handle = std::move(sessions.front());
         }
      }

      if(session_and_handle.has_value()) {
         /*
         Ensure that the session protocol cipher and version are acceptable
         If not skip the resume and establish a new session
         */
         auto& session_info = session_and_handle->session;
         const bool exact_version = session_info.version() == version;
         const bool ok_version = (session_info.version().is_datagram_protocol() == version.is_datagram_protocol()) &&
                                 policy().acceptable_protocol_version(session_info.version());

         const bool session_version_ok = policy().only_resume_with_exact_version() ? exact_version : ok_version;

         if(policy().acceptable_ciphersuite(session_info.ciphersuite()) && session_version_ok) {
            state.client_hello(std::make_unique<Client_Hello_12>(state.handshake_io(),
                                                                 state.hash(),
                                                                 policy(),
                                                                 callbacks(),
                                                                 rng(),
                                                                 secure_renegotiation_data_for_client_hello(),
                                                                 session_and_handle.value(),
                                                                 next_protocols));

            state.record_resumption_info(std::move(session_info));
         }
      }
   }

   if(state.client_hello() == nullptr) {
      // not resuming
      const Client_Hello_12::Settings client_settings(version, m_info.hostname());
      state.client_hello(std::make_unique<Client_Hello_12>(state.handshake_io(),
                                                           state.hash(),
                                                           policy(),
                                                           callbacks(),
                                                           rng(),
                                                           secure_renegotiation_data_for_client_hello(),
                                                           client_settings,
                                                           next_protocols));
   }

   secure_renegotiation_check(state.client_hello());
}

namespace {

bool key_usage_matches_ciphersuite(Key_Constraints usage, const Ciphersuite& suite) {
   if(usage == Key_Constraints::None) {
      return true;  // anything goes ...
   }

   if(suite.kex_method() == Kex_Algo::STATIC_RSA) {
      return usage.includes_any(Key_Constraints::KeyEncipherment, Key_Constraints::DataEncipherment);
   } else {
      return usage.includes_any(Key_Constraints::DigitalSignature, Key_Constraints::NonRepudiation);
   }
}

}  // namespace

/*
* Process a handshake message
*/
void Client_Impl_12::process_handshake_msg(const Handshake_State* active_state,
                                           Handshake_State& state_base,
                                           Handshake_Type type,
                                           const std::vector<uint8_t>& contents,
                                           bool epoch0_restart) {
   BOTAN_ASSERT_NOMSG(epoch0_restart == false);  // only happens on server side

   Client_Handshake_State_12& state = dynamic_cast<Client_Handshake_State_12&>(state_base);

   if(type == Handshake_Type::HelloRequest && active_state != nullptr) {
      const Hello_Request hello_request(contents);

      if(state.client_hello() != nullptr) {
         throw TLS_Exception(Alert::HandshakeFailure, "Cannot renegotiate during a handshake");
      }

      if(policy().allow_server_initiated_renegotiation()) {
         if(secure_renegotiation_supported() || policy().allow_insecure_renegotiation()) {
            state.mark_as_renegotiation();
            initiate_handshake(state, true /* force_full_renegotiation */);
         } else {
            throw TLS_Exception(Alert::HandshakeFailure, "Client policy prohibits insecure renegotiation");
         }
      } else {
         if(policy().abort_connection_on_undesired_renegotiation()) {
            throw TLS_Exception(Alert::NoRenegotiation, "Client policy prohibits renegotiation");
         } else {
            // RFC 5746 section 4.2
            send_warning_alert(Alert::NoRenegotiation);
         }
      }

      return;
   }

   state.confirm_transition_to(type);

   if(type != Handshake_Type::HandshakeCCS && type != Handshake_Type::Finished &&
      type != Handshake_Type::HelloVerifyRequest) {
      state.hash().update(state.handshake_io().format(contents, type));
   }

   if(type == Handshake_Type::HelloVerifyRequest) {
      state.set_expected_next(Handshake_Type::ServerHello);
      state.set_expected_next(Handshake_Type::HelloVerifyRequest);  // might get it again

      const Hello_Verify_Request hello_verify_request(contents);
      state.hello_verify_request(hello_verify_request);
   } else if(type == Handshake_Type::ServerHello) {
      state.server_hello(std::make_unique<Server_Hello_12>(contents));

      if(!state.server_hello()->legacy_version().valid()) {
         throw TLS_Exception(Alert::ProtocolVersion, "Server replied with an invalid version");
      }

      if(!state.client_hello()->offered_suite(state.server_hello()->ciphersuite())) {
         throw TLS_Exception(Alert::HandshakeFailure, "Server replied with ciphersuite we didn't send");
      }

      const auto suite = Ciphersuite::by_id(state.server_hello()->ciphersuite());
      if(!suite || !suite->usable_in_version(state.server_hello()->legacy_version())) {
         throw TLS_Exception(Alert::HandshakeFailure,
                             "Server replied using a ciphersuite not allowed in version it offered");
      }

      // RFC 7366 3.:
      //   If a server receives an encrypt-then-MAC request extension from a client
      //   and then selects a stream or Authenticated Encryption with Associated
      //   Data (AEAD) ciphersuite, it MUST NOT send an encrypt-then-MAC
      //   response extension back to the client.
      if(suite->aead_ciphersuite() && state.server_hello()->supports_encrypt_then_mac()) {
         throw TLS_Exception(Alert::IllegalParameter,
                             "Server replied using an AEAD ciphersuite and an encrypt-then-MAC response extension");
      }

      if(Ciphersuite::is_scsv(state.server_hello()->ciphersuite())) {
         throw TLS_Exception(Alert::HandshakeFailure, "Server replied with a signaling ciphersuite");
      }

      if(state.server_hello()->compression_method() != 0) {
         throw TLS_Exception(Alert::IllegalParameter, "Server replied with non-null compression method");
      }

      if(state.client_hello()->legacy_version() > state.server_hello()->legacy_version()) {
         // check for downgrade attacks
         //
         // RFC 8446 4.1.3.:
         //   TLS 1.2 clients SHOULD also check that the last 8 bytes are
         //   not equal to the [magic value DOWNGRADE_TLS11] if the ServerHello
         //   indicates TLS 1.1 or below.  If a match is found, the client MUST
         //   abort the handshake with an "illegal_parameter" alert.
         //
         // TLS 1.3 servers will still set the magic string to DOWNGRADE_TLS12. Don't abort in this case.
         if(auto requested = state.server_hello()->random_signals_downgrade();
            requested.has_value() && requested.value() <= Protocol_Version::TLS_V11) {
            throw TLS_Exception(Alert::IllegalParameter, "Downgrade attack detected");
         }
      }

      auto client_extn = state.client_hello()->extension_types();
      auto server_extn = state.server_hello()->extension_types();

      std::vector<Extension_Code> diff;

      std::set_difference(
         server_extn.begin(), server_extn.end(), client_extn.begin(), client_extn.end(), std::back_inserter(diff));

      if(!diff.empty()) {
         // Server sent us back an extension we did not send!

         std::ostringstream msg;
         msg << "Server replied with unsupported extensions:";
         for(auto&& d : diff) {
            msg << " " << static_cast<int>(d);
         }
         throw TLS_Exception(Alert::UnsupportedExtension, msg.str());
      }

      if(const uint16_t srtp = state.server_hello()->srtp_profile()) {
         if(!value_exists(state.client_hello()->srtp_profiles(), srtp)) {
            throw TLS_Exception(Alert::HandshakeFailure, "Server replied with DTLS-SRTP alg we did not send");
         }
      }

      callbacks().tls_examine_extensions(
         state.server_hello()->extensions(), Connection_Side::Server, Handshake_Type::ServerHello);

      state.set_version(state.server_hello()->legacy_version());
      m_application_protocol = state.server_hello()->next_protocol();

      secure_renegotiation_check(state.server_hello());

      const bool server_returned_same_session_id =
         !state.server_hello()->session_id().empty() &&
         (state.server_hello()->session_id() == state.client_hello()->session_id());

      if(server_returned_same_session_id) {
         // successful resumption
         BOTAN_ASSERT_NOMSG(state.is_a_resumption());

         /*
         * In this case, we offered the version used in the original
         * session, and the server must resume with the same version.
         */
         if(state.server_hello()->legacy_version() != state.client_hello()->legacy_version()) {
            throw TLS_Exception(Alert::HandshakeFailure, "Server resumed session but with wrong version");
         }

         if(state.server_hello()->supports_extended_master_secret() &&
            !state.resumed_session_supports_extended_master_secret()) {
            throw TLS_Exception(Alert::HandshakeFailure, "Server resumed session but added extended master secret");
         }

         if(!state.server_hello()->supports_extended_master_secret() &&
            state.resumed_session_supports_extended_master_secret()) {
            throw TLS_Exception(Alert::HandshakeFailure, "Server resumed session and removed extended master secret");
         }

         state.compute_session_keys(state.resume_master_secret());
         if(policy().allow_ssl_key_log_file()) {
            // draft-thomson-tls-keylogfile-00 Section 3.2
            //    An implementation of TLS 1.2 (and also earlier versions) use
            //    the label "CLIENT_RANDOM" to identify the "master" secret for
            //    the connection.
            callbacks().tls_ssl_key_log_data(
               "CLIENT_RANDOM", state.client_hello()->random(), state.session_keys().master_secret());
         }

         if(state.server_hello()->supports_session_ticket()) {
            state.set_expected_next(Handshake_Type::NewSessionTicket);
         } else {
            state.set_expected_next(Handshake_Type::HandshakeCCS);
         }
      } else {
         // new session

         if(active_state != nullptr) {
            // Here we are testing things that should not change during a renegotiation,
            // even if the server creates a new session. However they might change
            // in a resumption scenario.

            if(active_state->version() != state.server_hello()->legacy_version()) {
               throw TLS_Exception(Alert::ProtocolVersion, "Server changed version after renegotiation");
            }

            if(state.server_hello()->supports_extended_master_secret() !=
               active_state->server_hello()->supports_extended_master_secret()) {
               throw TLS_Exception(Alert::HandshakeFailure, "Server changed its mind about extended master secret");
            }
         }

         state.discard_resumption_state();

         if(state.client_hello()->legacy_version().is_datagram_protocol() !=
            state.server_hello()->legacy_version().is_datagram_protocol()) {
            throw TLS_Exception(Alert::ProtocolVersion, "Server replied with different protocol type than we offered");
         }

         if(state.version() > state.client_hello()->legacy_version()) {
            throw TLS_Exception(Alert::HandshakeFailure, "Server replied with later version than client offered");
         }

         if(state.version().major_version() == 3 && state.version().minor_version() == 0) {
            throw TLS_Exception(Alert::ProtocolVersion, "Server attempting to negotiate SSLv3 which is not supported");
         }

         if(!policy().acceptable_protocol_version(state.version())) {
            throw TLS_Exception(Alert::ProtocolVersion,
                                "Server version " + state.version().to_string() + " is unacceptable by policy");
         }

         if(state.ciphersuite().signature_used() || state.ciphersuite().kex_method() == Kex_Algo::STATIC_RSA) {
            state.set_expected_next(Handshake_Type::Certificate);
         } else if(state.ciphersuite().kex_method() == Kex_Algo::PSK) {
            /* PSK is anonymous so no certificate/cert req message is
               ever sent. The server may or may not send a server kex,
               depending on if it has an identity hint for us.

               (EC)DHE_PSK always sends a server key exchange for the
               DH exchange portion, and is covered by block below
            */

            state.set_expected_next(Handshake_Type::ServerKeyExchange);
            state.set_expected_next(Handshake_Type::ServerHelloDone);
         } else if(state.ciphersuite().kex_method() != Kex_Algo::STATIC_RSA) {
            state.set_expected_next(Handshake_Type::ServerKeyExchange);
         } else {
            state.set_expected_next(Handshake_Type::CertificateRequest);  // optional
            state.set_expected_next(Handshake_Type::ServerHelloDone);
         }
      }
   } else if(type == Handshake_Type::Certificate) {
      state.server_certs(std::make_unique<Certificate_12>(contents, policy()));

      const std::vector<X509_Certificate>& server_certs = state.server_certs()->cert_chain();

      if(server_certs.empty()) {
         throw TLS_Exception(Alert::HandshakeFailure, "Client: No certificates sent by server");
      }

      /*
      If the server supports certificate status messages,
      certificate verification happens after we receive the server hello done,
      in case an OCSP response was also available
      */

      const X509_Certificate server_cert = server_certs[0];

      if(active_state != nullptr && active_state->server_certs() != nullptr) {
         const X509_Certificate current_cert = active_state->server_certs()->cert_chain().at(0);

         if(current_cert != server_cert) {
            throw TLS_Exception(Alert::BadCertificate, "Server certificate changed during renegotiation");
         }
      }

      auto peer_key = server_cert.subject_public_key();

      const std::string expected_key_type =
         state.ciphersuite().signature_used() ? state.ciphersuite().sig_algo() : "RSA";

      if(peer_key->algo_name() != expected_key_type) {
         throw TLS_Exception(Alert::IllegalParameter, "Certificate key type did not match ciphersuite");
      }

      if(!key_usage_matches_ciphersuite(server_cert.constraints(), state.ciphersuite())) {
         throw TLS_Exception(Alert::BadCertificate, "Certificate usage constraints do not allow this ciphersuite");
      }

      state.record_server_public_key(std::move(peer_key));

      if(state.ciphersuite().kex_method() != Kex_Algo::STATIC_RSA) {
         state.set_expected_next(Handshake_Type::ServerKeyExchange);
      } else {
         state.set_expected_next(Handshake_Type::CertificateRequest);  // optional
         state.set_expected_next(Handshake_Type::ServerHelloDone);
      }

      if(state.server_hello()->supports_certificate_status_message()) {
         state.set_expected_next(Handshake_Type::CertificateStatus);  // optional
      } else {
         try {
            auto trusted_CAs = m_creds->trusted_certificate_authorities("tls-client", m_info.hostname());

            callbacks().tls_verify_cert_chain(
               server_certs, {}, trusted_CAs, Usage_Type::TLS_SERVER_AUTH, m_info.hostname(), policy());
         } catch(TLS_Exception&) {
            throw;
         } catch(std::exception& e) {
            throw TLS_Exception(Alert::InternalError, e.what());
         }
      }
   } else if(type == Handshake_Type::CertificateStatus) {
      state.server_cert_status(std::make_unique<Certificate_Status>(contents, Connection_Side::Server));

      if(state.ciphersuite().kex_method() != Kex_Algo::STATIC_RSA) {
         state.set_expected_next(Handshake_Type::ServerKeyExchange);
      } else {
         state.set_expected_next(Handshake_Type::CertificateRequest);  // optional
         state.set_expected_next(Handshake_Type::ServerHelloDone);
      }
   } else if(type == Handshake_Type::ServerKeyExchange) {
      if(!state.ciphersuite().psk_ciphersuite()) {
         state.set_expected_next(Handshake_Type::CertificateRequest);  // optional
      }
      state.set_expected_next(Handshake_Type::ServerHelloDone);

      state.server_kex(std::make_unique<Server_Key_Exchange>(
         contents, state.ciphersuite().kex_method(), state.ciphersuite().auth_method(), state.version()));

      if(state.ciphersuite().signature_used()) {
         const Public_Key& server_key = state.server_public_key();

         if(!state.server_kex()->verify(server_key, state, policy())) {
            throw TLS_Exception(Alert::DecryptError, "Bad signature on server key exchange");
         }
      }
   } else if(type == Handshake_Type::CertificateRequest) {
      state.set_expected_next(Handshake_Type::ServerHelloDone);
      state.cert_req(std::make_unique<Certificate_Request_12>(contents));
   } else if(type == Handshake_Type::ServerHelloDone) {
      state.server_hello_done(std::make_unique<Server_Hello_Done>(contents));

      if(state.handshake_io().have_more_data()) {
         throw TLS_Exception(Alert::UnexpectedMessage, "Have data remaining in buffer after ServerHelloDone");
      }

      if(state.server_certs() != nullptr && state.server_hello()->supports_certificate_status_message()) {
         try {
            auto trusted_CAs = m_creds->trusted_certificate_authorities("tls-client", m_info.hostname());

            std::vector<std::optional<OCSP::Response>> ocsp;
            if(state.server_cert_status() != nullptr) {
               ocsp.emplace_back(callbacks().tls_parse_ocsp_response(state.server_cert_status()->response()));
            }

            callbacks().tls_verify_cert_chain(state.server_certs()->cert_chain(),
                                              ocsp,
                                              trusted_CAs,
                                              Usage_Type::TLS_SERVER_AUTH,
                                              m_info.hostname(),
                                              policy());
         } catch(TLS_Exception&) {
            throw;
         } catch(std::exception& e) {
            throw TLS_Exception(Alert::InternalError, e.what());
         }
      }

      if(state.received_handshake_msg(Handshake_Type::CertificateRequest)) {
         const auto& types = state.cert_req()->acceptable_cert_types();

         const std::vector<X509_Certificate> client_certs =
            m_creds->find_cert_chain(types, {}, state.cert_req()->acceptable_CAs(), "tls-client", m_info.hostname());

         state.client_certs(std::make_unique<Certificate_12>(state.handshake_io(), state.hash(), client_certs));
      }

      state.client_kex(std::make_unique<Client_Key_Exchange>(
         state.handshake_io(), state, policy(), *m_creds, state.maybe_server_public_key(), m_info.hostname(), rng()));

      state.compute_session_keys();
      if(policy().allow_ssl_key_log_file()) {
         // draft-thomson-tls-keylogfile-00 Section 3.2
         //    An implementation of TLS 1.2 (and also earlier versions) use
         //    the label "CLIENT_RANDOM" to identify the "master" secret for
         //    the connection.
         callbacks().tls_ssl_key_log_data(
            "CLIENT_RANDOM", state.client_hello()->random(), state.session_keys().master_secret());
      }

      if(state.received_handshake_msg(Handshake_Type::CertificateRequest) && !state.client_certs()->empty()) {
         auto private_key =
            m_creds->private_key_for(state.client_certs()->cert_chain()[0], "tls-client", m_info.hostname());

         if(!private_key) {
            throw TLS_Exception(Alert::InternalError, "Failed to get private key for signing");
         }

         state.client_verify(
            std::make_unique<Certificate_Verify_12>(state.handshake_io(), state, policy(), rng(), private_key.get()));
      }

      state.handshake_io().send(Change_Cipher_Spec());

      change_cipher_spec_writer(Connection_Side::Client);

      state.client_finished(std::make_unique<Finished_12>(state.handshake_io(), state, Connection_Side::Client));

      if(state.server_hello()->supports_session_ticket()) {
         state.set_expected_next(Handshake_Type::NewSessionTicket);
      } else {
         state.set_expected_next(Handshake_Type::HandshakeCCS);
      }
   } else if(type == Handshake_Type::NewSessionTicket) {
      state.new_session_ticket(std::make_unique<New_Session_Ticket_12>(contents));

      state.set_expected_next(Handshake_Type::HandshakeCCS);
   } else if(type == Handshake_Type::HandshakeCCS) {
      state.set_expected_next(Handshake_Type::Finished);

      change_cipher_spec_reader(Connection_Side::Client);
   } else if(type == Handshake_Type::Finished) {
      if(state.handshake_io().have_more_data()) {
         throw TLS_Exception(Alert::UnexpectedMessage, "Have data remaining in buffer after Finished");
      }

      state.server_finished(std::make_unique<Finished_12>(contents));

      if(!state.server_finished()->verify(state, Connection_Side::Server)) {
         throw TLS_Exception(Alert::DecryptError, "Finished message didn't verify");
      }

      state.hash().update(state.handshake_io().format(contents, type));

      if(state.client_finished() == nullptr) {
         // session resume case
         state.handshake_io().send(Change_Cipher_Spec());
         change_cipher_spec_writer(Connection_Side::Client);
         state.client_finished(std::make_unique<Finished_12>(state.handshake_io(), state, Connection_Side::Client));
      }

      // Session Tickets (as defined in RFC 5077) contain a lifetime_hint,
      // sessions identified via a Session_ID do not.
      const std::chrono::seconds session_lifetime_hint = [&] {
         if(state.new_session_ticket() != nullptr) {
            return std::chrono::seconds(state.new_session_ticket()->ticket_lifetime_hint());
         } else {
            return std::chrono::seconds::max();
         }
      }();

      Session session_info(state.session_keys().master_secret(),
                           state.server_hello()->legacy_version(),
                           state.server_hello()->ciphersuite(),
                           Connection_Side::Client,
                           state.server_hello()->supports_extended_master_secret(),
                           state.server_hello()->supports_encrypt_then_mac(),
                           get_peer_cert_chain(state),
                           m_info,
                           state.server_hello()->srtp_profile(),
                           callbacks().tls_current_timestamp(),
                           session_lifetime_hint);

      // RFC 5077 3.4
      //    If the client receives a session ticket from the server, then it
      //    discards any Session ID that was sent in the ServerHello.
      const auto handle = [&]() -> std::optional<Session_Handle> {
         if(const auto& session_ticket = state.session_ticket(); !session_ticket.empty()) {
            return Session_Handle(session_ticket);
         } else if(const auto& session_id = state.server_hello()->session_id(); !session_id.empty()) {
            return Session_Handle(session_id);
         } else {
            return std::nullopt;
         }
      }();

      // Give the application a chance for a final veto before fully
      // establishing the connection.
      callbacks().tls_session_established([&, this] {
         Session_Summary summary(session_info, state.is_a_resumption(), external_psk_identity());
         summary.set_session_id(state.server_hello()->session_id());
         if(const auto* nst = state.new_session_ticket()) {
            summary.set_session_ticket(nst->ticket());
         }
         return summary;
      }());

      if(handle.has_value()) {
         const bool should_save = callbacks().tls_should_persist_resumption_information(session_info);

         // RFC 5077 3.3
         //    If the server successfully verifies the client's ticket, then it
         //    MAY renew the ticket by including a NewSessionTicket handshake
         //    message after the ServerHello in the abbreviated handshake. The
         //    client should start using the new ticket as soon as possible
         //    after it verifies the server's Finished message for new
         //    connections.
         if(state.is_a_resumption() && !state.client_hello()->session_ticket().empty() && handle->is_ticket() &&
            should_save) {
            // renew the session ticket by removing the one we used to establish
            // this connection and replace it with the one we just received
            session_manager().remove(Session_Handle(state.client_hello()->session_ticket()));
            session_manager().store(session_info, handle.value());
         }

         if(!state.is_a_resumption()) {
            if(should_save) {
               session_manager().store(session_info, handle.value());
            } else {
               session_manager().remove(handle.value());
            }
         }
      }

      activate_session();
   } else {
      throw Unexpected_Message("Unknown handshake message received");
   }
}

}  // namespace Botan::TLS
