aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/tls/tls_server.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/tls/tls_server.cpp')
-rw-r--r--src/lib/tls/tls_server.cpp725
1 files changed, 725 insertions, 0 deletions
diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp
new file mode 100644
index 000000000..bc518571b
--- /dev/null
+++ b/src/lib/tls/tls_server.cpp
@@ -0,0 +1,725 @@
+/*
+* TLS Server
+* (C) 2004-2011,2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_server.h>
+#include <botan/internal/tls_handshake_state.h>
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/stl_util.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+namespace {
+
+class Server_Handshake_State : public Handshake_State
+ {
+ public:
+ // using Handshake_State::Handshake_State;
+
+ Server_Handshake_State(Handshake_IO* io) : Handshake_State(io) {}
+
+ // Used by the server only, in case of RSA key exchange. Not owned
+ Private_Key* server_rsa_kex_key = nullptr;
+
+ /*
+ * Used by the server to know if resumption should be allowed on
+ * a server-initiated renegotiation
+ */
+ bool allow_session_resumption = true;
+ };
+
+bool check_for_resume(Session& session_info,
+ Session_Manager& session_manager,
+ Credentials_Manager& credentials,
+ const Client_Hello* client_hello,
+ std::chrono::seconds session_ticket_lifetime)
+ {
+ const std::vector<byte>& client_session_id = client_hello->session_id();
+ const std::vector<byte>& session_ticket = client_hello->session_ticket();
+
+ if(session_ticket.empty())
+ {
+ if(client_session_id.empty()) // not resuming
+ return false;
+
+ // not found
+ if(!session_manager.load_from_session_id(client_session_id, session_info))
+ return false;
+ }
+ else
+ {
+ // If a session ticket was sent, ignore client session ID
+ try
+ {
+ session_info = Session::decrypt(
+ session_ticket,
+ credentials.psk("tls-server", "session-ticket", ""));
+
+ if(session_ticket_lifetime != std::chrono::seconds(0) &&
+ session_info.session_age() > session_ticket_lifetime)
+ return false; // ticket has expired
+ }
+ catch(...)
+ {
+ return false;
+ }
+ }
+
+ // wrong version
+ if(client_hello->version() != session_info.version())
+ return false;
+
+ // client didn't send original ciphersuite
+ if(!value_exists(client_hello->ciphersuites(),
+ session_info.ciphersuite_code()))
+ return false;
+
+ // client didn't send original compression method
+ if(!value_exists(client_hello->compression_methods(),
+ session_info.compression_method()))
+ return false;
+
+ // client sent a different SRP identity
+ if(client_hello->srp_identifier() != "")
+ {
+ if(client_hello->srp_identifier() != session_info.srp_identifier())
+ return false;
+ }
+
+ // client sent a different SNI hostname
+ if(client_hello->sni_hostname() != "")
+ {
+ if(client_hello->sni_hostname() != session_info.server_info().hostname())
+ return false;
+ }
+
+ return true;
+ }
+
+/*
+* Choose which ciphersuite to use
+*/
+u16bit choose_ciphersuite(
+ const Policy& policy,
+ Protocol_Version version,
+ Credentials_Manager& creds,
+ const std::map<std::string, std::vector<X509_Certificate> >& cert_chains,
+ const Client_Hello* client_hello)
+ {
+ const bool our_choice = policy.server_uses_own_ciphersuite_preferences();
+
+ const bool have_srp = creds.attempt_srp("tls-server",
+ client_hello->sni_hostname());
+
+ const std::vector<u16bit> client_suites = client_hello->ciphersuites();
+
+ const std::vector<u16bit> server_suites = policy.ciphersuite_list(version, have_srp);
+
+ if(server_suites.empty())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Policy forbids us from negotiating any ciphersuite");
+
+ const bool have_shared_ecc_curve =
+ (policy.choose_curve(client_hello->supported_ecc_curves()) != "");
+
+ std::vector<u16bit> pref_list = server_suites;
+ std::vector<u16bit> other_list = client_suites;
+
+ if(!our_choice)
+ std::swap(pref_list, other_list);
+
+ for(auto suite_id : pref_list)
+ {
+ if(!value_exists(other_list, suite_id))
+ continue;
+
+ Ciphersuite suite = Ciphersuite::by_id(suite_id);
+
+ if(!have_shared_ecc_curve && suite.ecc_ciphersuite())
+ continue;
+
+ if(suite.sig_algo() != "" && cert_chains.count(suite.sig_algo()) == 0)
+ continue;
+
+ /*
+ The client may offer SRP cipher suites in the hello message but
+ omit the SRP extension. If the server would like to select an
+ SRP cipher suite in this case, the server SHOULD return a fatal
+ "unknown_psk_identity" alert immediately after processing the
+ client hello message.
+ - RFC 5054 section 2.5.1.2
+ */
+ if(suite.kex_algo() == "SRP_SHA" && client_hello->srp_identifier() == "")
+ throw TLS_Exception(Alert::UNKNOWN_PSK_IDENTITY,
+ "Client wanted SRP but did not send username");
+
+ return suite_id;
+ }
+
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Can't agree on a ciphersuite with client");
+ }
+
+
+/*
+* Choose which compression algorithm to use
+*/
+byte choose_compression(const Policy& policy,
+ const std::vector<byte>& c_comp)
+ {
+ std::vector<byte> s_comp = policy.compression();
+
+ for(size_t i = 0; i != s_comp.size(); ++i)
+ for(size_t j = 0; j != c_comp.size(); ++j)
+ if(s_comp[i] == c_comp[j])
+ return s_comp[i];
+
+ return NO_COMPRESSION;
+ }
+
+std::map<std::string, std::vector<X509_Certificate> >
+get_server_certs(const std::string& hostname,
+ Credentials_Manager& creds)
+ {
+ const char* cert_types[] = { "RSA", "DSA", "ECDSA", nullptr };
+
+ std::map<std::string, std::vector<X509_Certificate> > cert_chains;
+
+ for(size_t i = 0; cert_types[i]; ++i)
+ {
+ std::vector<X509_Certificate> certs =
+ creds.cert_chain_single_type(cert_types[i], "tls-server", hostname);
+
+ if(!certs.empty())
+ cert_chains[cert_types[i]] = certs;
+ }
+
+ return cert_chains;
+ }
+
+}
+
+/*
+* TLS Server Constructor
+*/
+Server::Server(std::function<void (const byte[], size_t)> output_fn,
+ std::function<void (const byte[], size_t)> data_cb,
+ std::function<void (Alert, const byte[], size_t)> alert_cb,
+ std::function<bool (const Session&)> handshake_cb,
+ Session_Manager& session_manager,
+ Credentials_Manager& creds,
+ const Policy& policy,
+ RandomNumberGenerator& rng,
+ const std::vector<std::string>& next_protocols,
+ size_t io_buf_sz) :
+ Channel(output_fn, data_cb, alert_cb, handshake_cb, session_manager, rng, io_buf_sz),
+ m_policy(policy),
+ m_creds(creds),
+ m_possible_protocols(next_protocols)
+ {
+ }
+
+Handshake_State* Server::new_handshake_state(Handshake_IO* io)
+ {
+ std::unique_ptr<Handshake_State> state(new Server_Handshake_State(io));
+ state->set_expected_next(CLIENT_HELLO);
+ return state.release();
+ }
+
+std::vector<X509_Certificate>
+Server::get_peer_cert_chain(const Handshake_State& state) const
+ {
+ if(state.client_certs())
+ return state.client_certs()->cert_chain();
+ return std::vector<X509_Certificate>();
+ }
+
+/*
+* Send a hello request to the client
+*/
+void Server::initiate_handshake(Handshake_State& state,
+ bool force_full_renegotiation)
+ {
+ dynamic_cast<Server_Handshake_State&>(state).allow_session_resumption =
+ !force_full_renegotiation;
+
+ Hello_Request hello_req(state.handshake_io());
+ }
+
+/*
+* Process a handshake message
+*/
+void Server::process_handshake_msg(const Handshake_State* active_state,
+ Handshake_State& state_base,
+ Handshake_Type type,
+ const std::vector<byte>& contents)
+ {
+ Server_Handshake_State& state = dynamic_cast<Server_Handshake_State&>(state_base);
+
+ state.confirm_transition_to(type);
+
+ /*
+ * The change cipher spec message isn't technically a handshake
+ * message so it's not included in the hash. The finished and
+ * certificate verify messages are verified based on the current
+ * state of the hash *before* this message so we delay adding them
+ * to the hash computation until we've processed them below.
+ */
+ if(type != HANDSHAKE_CCS && type != FINISHED && type != CERTIFICATE_VERIFY)
+ {
+ if(type == CLIENT_HELLO_SSLV2)
+ state.hash().update(contents);
+ else
+ state.hash().update(state.handshake_io().format(contents, type));
+ }
+
+ if(type == CLIENT_HELLO || type == CLIENT_HELLO_SSLV2)
+ {
+ const bool initial_handshake = !active_state;
+
+ if(!m_policy.allow_insecure_renegotiation() &&
+ !(initial_handshake || secure_renegotiation_supported()))
+ {
+ send_warning_alert(Alert::NO_RENEGOTIATION);
+ return;
+ }
+
+ state.client_hello(new Client_Hello(contents, type));
+
+ Protocol_Version client_version = state.client_hello()->version();
+
+ Protocol_Version negotiated_version;
+
+ if((initial_handshake && client_version.known_version()) ||
+ (!initial_handshake && client_version == active_state->version()))
+ {
+ /*
+ Common cases: new client hello with some known version, or a
+ renegotiation using the same version as previously
+ negotiated.
+ */
+
+ negotiated_version = client_version;
+ }
+ else if(!initial_handshake && (client_version != active_state->version()))
+ {
+ /*
+ * If this is a renegotiation, and the client has offered a
+ * later version than what it initially negotiated, negotiate
+ * the old version. This matches OpenSSL's behavior. If the
+ * client is offering a version earlier than what it initially
+ * negotiated, reject as a probable attack.
+ */
+ if(active_state->version() > client_version)
+ {
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Client negotiated " +
+ active_state->version().to_string() +
+ " then renegotiated with " +
+ client_version.to_string());
+ }
+ else
+ negotiated_version = active_state->version();
+ }
+ else
+ {
+ /*
+ New negotiation using a version we don't know. Offer
+ them the best we currently know.
+ */
+ negotiated_version = client_version.best_known_match();
+ }
+
+ if(!m_policy.acceptable_protocol_version(negotiated_version))
+ {
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Client version is unacceptable by policy");
+ }
+
+ if(!initial_handshake && state.client_hello()->next_protocol_notification())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Client included NPN extension for renegotiation");
+
+ secure_renegotiation_check(state.client_hello());
+
+ state.set_version(negotiated_version);
+
+ Session session_info;
+ const bool resuming =
+ state.allow_session_resumption &&
+ check_for_resume(session_info,
+ session_manager(),
+ m_creds,
+ state.client_hello(),
+ std::chrono::seconds(m_policy.session_ticket_lifetime()));
+
+ bool have_session_ticket_key = false;
+
+ try
+ {
+ have_session_ticket_key =
+ m_creds.psk("tls-server", "session-ticket", "").length() > 0;
+ }
+ catch(...) {}
+
+ if(resuming)
+ {
+ // resume session
+
+ const bool offer_new_session_ticket =
+ (state.client_hello()->supports_session_ticket() &&
+ state.client_hello()->session_ticket().empty() &&
+ have_session_ticket_key);
+
+ state.server_hello(
+ new Server_Hello(
+ state.handshake_io(),
+ state.hash(),
+ m_policy,
+ state.client_hello()->session_id(),
+ Protocol_Version(session_info.version()),
+ session_info.ciphersuite_code(),
+ session_info.compression_method(),
+ session_info.fragment_size(),
+ state.client_hello()->secure_renegotiation(),
+ secure_renegotiation_data_for_server_hello(),
+ offer_new_session_ticket,
+ state.client_hello()->next_protocol_notification(),
+ m_possible_protocols,
+ state.client_hello()->supports_heartbeats(),
+ rng())
+ );
+
+ secure_renegotiation_check(state.server_hello());
+
+ state.compute_session_keys(session_info.master_secret());
+
+ if(!save_session(session_info))
+ {
+ session_manager().remove_entry(session_info.session_id());
+
+ if(state.server_hello()->supports_session_ticket()) // send an empty ticket
+ {
+ state.new_session_ticket(
+ new New_Session_Ticket(state.handshake_io(),
+ state.hash())
+ );
+ }
+ }
+
+ if(state.server_hello()->supports_session_ticket() && !state.new_session_ticket())
+ {
+ try
+ {
+ const SymmetricKey ticket_key = m_creds.psk("tls-server", "session-ticket", "");
+
+ state.new_session_ticket(
+ new New_Session_Ticket(state.handshake_io(),
+ state.hash(),
+ session_info.encrypt(ticket_key, rng()),
+ m_policy.session_ticket_lifetime())
+ );
+ }
+ catch(...) {}
+
+ if(!state.new_session_ticket())
+ {
+ state.new_session_ticket(
+ new New_Session_Ticket(state.handshake_io(), state.hash())
+ );
+ }
+ }
+
+ state.handshake_io().send(Change_Cipher_Spec());
+
+ change_cipher_spec_writer(SERVER);
+
+ state.server_finished(
+ new Finished(state.handshake_io(), state, SERVER)
+ );
+
+ state.set_expected_next(HANDSHAKE_CCS);
+ }
+ else // new session
+ {
+ std::map<std::string, std::vector<X509_Certificate> > cert_chains;
+
+ const std::string sni_hostname = state.client_hello()->sni_hostname();
+
+ cert_chains = get_server_certs(sni_hostname, m_creds);
+
+ if(sni_hostname != "" && cert_chains.empty())
+ {
+ cert_chains = get_server_certs("", m_creds);
+
+ /*
+ * Only send the unrecognized_name alert if we couldn't
+ * find any certs for the requested name but did find at
+ * least one cert to use in general. That avoids sending an
+ * unrecognized_name when a server is configured for purely
+ * anonymous operation.
+ */
+ if(!cert_chains.empty())
+ send_alert(Alert(Alert::UNRECOGNIZED_NAME));
+ }
+
+ state.server_hello(
+ new Server_Hello(
+ state.handshake_io(),
+ state.hash(),
+ m_policy,
+ make_hello_random(rng()), // new session ID
+ state.version(),
+ choose_ciphersuite(m_policy,
+ state.version(),
+ m_creds,
+ cert_chains,
+ state.client_hello()),
+ choose_compression(m_policy, state.client_hello()->compression_methods()),
+ state.client_hello()->fragment_size(),
+ state.client_hello()->secure_renegotiation(),
+ secure_renegotiation_data_for_server_hello(),
+ 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())
+ );
+
+ secure_renegotiation_check(state.server_hello());
+
+ const std::string sig_algo = state.ciphersuite().sig_algo();
+ const std::string kex_algo = state.ciphersuite().kex_algo();
+
+ if(sig_algo != "")
+ {
+ BOTAN_ASSERT(!cert_chains[sig_algo].empty(),
+ "Attempting to send empty certificate chain");
+
+ state.server_certs(
+ new Certificate(state.handshake_io(),
+ state.hash(),
+ cert_chains[sig_algo])
+ );
+ }
+
+ Private_Key* private_key = nullptr;
+
+ if(kex_algo == "RSA" || sig_algo != "")
+ {
+ private_key = m_creds.private_key_for(
+ state.server_certs()->cert_chain()[0],
+ "tls-server",
+ sni_hostname);
+
+ if(!private_key)
+ throw Internal_Error("No private key located for associated server cert");
+ }
+
+ if(kex_algo == "RSA")
+ {
+ state.server_rsa_kex_key = private_key;
+ }
+ else
+ {
+ state.server_kex(
+ new Server_Key_Exchange(state.handshake_io(),
+ state,
+ m_policy,
+ m_creds,
+ rng(),
+ private_key)
+ );
+ }
+
+ auto trusted_CAs =
+ m_creds.trusted_certificate_authorities("tls-server", sni_hostname);
+
+ std::vector<X509_DN> client_auth_CAs;
+
+ for(auto store : trusted_CAs)
+ {
+ auto subjects = store->all_subjects();
+ client_auth_CAs.insert(client_auth_CAs.end(),
+ subjects.begin(),
+ subjects.end());
+ }
+
+ if(!client_auth_CAs.empty() && state.ciphersuite().sig_algo() != "")
+ {
+ state.cert_req(
+ new Certificate_Req(state.handshake_io(),
+ state.hash(),
+ m_policy,
+ client_auth_CAs,
+ state.version())
+ );
+
+ state.set_expected_next(CERTIFICATE);
+ }
+
+ /*
+ * If the client doesn't have a cert they want to use they are
+ * allowed to send either an empty cert message or proceed
+ * directly to the client key exchange, so allow either case.
+ */
+ state.set_expected_next(CLIENT_KEX);
+
+ state.server_hello_done(
+ new Server_Hello_Done(state.handshake_io(), state.hash())
+ );
+ }
+ }
+ else if(type == CERTIFICATE)
+ {
+ state.client_certs(new Certificate(contents));
+
+ state.set_expected_next(CLIENT_KEX);
+ }
+ else if(type == CLIENT_KEX)
+ {
+ if(state.received_handshake_msg(CERTIFICATE) && !state.client_certs()->empty())
+ state.set_expected_next(CERTIFICATE_VERIFY);
+ else
+ state.set_expected_next(HANDSHAKE_CCS);
+
+ state.client_kex(
+ new Client_Key_Exchange(contents, state,
+ state.server_rsa_kex_key,
+ m_creds, m_policy, rng())
+ );
+
+ state.compute_session_keys();
+ }
+ else if(type == CERTIFICATE_VERIFY)
+ {
+ state.client_verify(new Certificate_Verify(contents, state.version()));
+
+ const std::vector<X509_Certificate>& client_certs =
+ state.client_certs()->cert_chain();
+
+ const bool sig_valid =
+ state.client_verify()->verify(client_certs[0], state);
+
+ state.hash().update(state.handshake_io().format(contents, type));
+
+ /*
+ * Using DECRYPT_ERROR looks weird here, but per RFC 4346 is for
+ * "A handshake cryptographic operation failed, including being
+ * unable to correctly verify a signature, ..."
+ */
+ if(!sig_valid)
+ throw TLS_Exception(Alert::DECRYPT_ERROR, "Client cert verify failed");
+
+ try
+ {
+ m_creds.verify_certificate_chain("tls-server", "", client_certs);
+ }
+ catch(std::exception& e)
+ {
+ throw TLS_Exception(Alert::BAD_CERTIFICATE, e.what());
+ }
+
+ state.set_expected_next(HANDSHAKE_CCS);
+ }
+ else if(type == HANDSHAKE_CCS)
+ {
+ if(state.server_hello()->next_protocol_notification())
+ state.set_expected_next(NEXT_PROTOCOL);
+ else
+ state.set_expected_next(FINISHED);
+
+ change_cipher_spec_reader(SERVER);
+ }
+ else if(type == NEXT_PROTOCOL)
+ {
+ state.set_expected_next(FINISHED);
+
+ state.next_protocol(new Next_Protocol(contents));
+
+ // should this be a callback?
+ m_next_protocol = state.next_protocol()->protocol();
+ }
+ else if(type == FINISHED)
+ {
+ state.set_expected_next(HANDSHAKE_NONE);
+
+ state.client_finished(new Finished(contents));
+
+ if(!state.client_finished()->verify(state, CLIENT))
+ throw TLS_Exception(Alert::DECRYPT_ERROR,
+ "Finished message didn't verify");
+
+ if(!state.server_finished())
+ {
+ // already sent finished if resuming, so this is a new session
+
+ state.hash().update(state.handshake_io().format(contents, type));
+
+ Session session_info(
+ state.server_hello()->session_id(),
+ state.session_keys().master_secret(),
+ state.server_hello()->version(),
+ state.server_hello()->ciphersuite(),
+ state.server_hello()->compression_method(),
+ SERVER,
+ state.server_hello()->fragment_size(),
+ get_peer_cert_chain(state),
+ std::vector<byte>(),
+ Server_Information(state.client_hello()->sni_hostname()),
+ state.srp_identifier()
+ );
+
+ if(save_session(session_info))
+ {
+ if(state.server_hello()->supports_session_ticket())
+ {
+ try
+ {
+ const SymmetricKey ticket_key = m_creds.psk("tls-server", "session-ticket", "");
+
+ state.new_session_ticket(
+ new New_Session_Ticket(state.handshake_io(),
+ state.hash(),
+ session_info.encrypt(ticket_key, rng()),
+ m_policy.session_ticket_lifetime())
+ );
+ }
+ catch(...) {}
+ }
+ else
+ session_manager().save(session_info);
+ }
+
+ if(!state.new_session_ticket() &&
+ state.server_hello()->supports_session_ticket())
+ {
+ state.new_session_ticket(
+ new New_Session_Ticket(state.handshake_io(), state.hash())
+ );
+ }
+
+ state.handshake_io().send(Change_Cipher_Spec());
+
+ change_cipher_spec_writer(SERVER);
+
+ state.server_finished(
+ new Finished(state.handshake_io(), state, SERVER)
+ );
+ }
+
+ activate_session();
+ }
+ else
+ throw Unexpected_Message("Unknown handshake message received");
+ }
+
+}
+
+}