aboutsummaryrefslogtreecommitdiffstats
path: root/src/tls/tls_server.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tls/tls_server.cpp')
-rw-r--r--src/tls/tls_server.cpp620
1 files changed, 620 insertions, 0 deletions
diff --git a/src/tls/tls_server.cpp b/src/tls/tls_server.cpp
new file mode 100644
index 000000000..1e8c73ec3
--- /dev/null
+++ b/src/tls/tls_server.cpp
@@ -0,0 +1,620 @@
+/*
+* 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 <botan/internal/assert.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+namespace {
+
+bool check_for_resume(Session& session_info,
+ Session_Manager& session_manager,
+ Credentials_Manager& credentials,
+ Client_Hello* client_hello,
+ std::chrono::seconds session_ticket_lifetime)
+ {
+ const MemoryVector<byte>& client_session_id = client_hello->session_id();
+ const MemoryVector<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.sni_hostname())
+ return false;
+ }
+
+ return true;
+ }
+
+/*
+* Choose which ciphersuite to use
+*/
+u16bit choose_ciphersuite(
+ const Policy& policy,
+ Credentials_Manager& creds,
+ const std::map<std::string, std::vector<X509_Certificate> >& cert_chains,
+ const Client_Hello* client_hello)
+ {
+ 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 = ciphersuite_list(policy, have_srp);
+
+ if(server_suites.empty())
+ throw Internal_Error("Policy forbids us from negotiating any ciphersuite");
+
+ const bool have_shared_ecc_curve =
+ (policy.choose_curve(client_hello->supported_ecc_curves()) != "");
+
+ // Ordering by our preferences rather than by clients
+ for(size_t i = 0; i != server_suites.size(); ++i)
+ {
+ const u16bit suite_id = server_suites[i];
+
+ if(!value_exists(client_suites, suite_id))
+ continue;
+
+ Ciphersuite suite = Ciphersuite::by_id(suite_id);
+
+ if(!have_shared_ecc_curve && suite.ecc_ciphersuite())
+ continue;
+
+ if(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", 0 };
+
+ 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, Alert)> proc_fn,
+ std::function<bool (const Session&)> handshake_fn,
+ Session_Manager& session_manager,
+ Credentials_Manager& creds,
+ const Policy& policy,
+ RandomNumberGenerator& rng,
+ const std::vector<std::string>& next_protocols) :
+ Channel(output_fn, proc_fn, handshake_fn),
+ policy(policy),
+ rng(rng),
+ session_manager(session_manager),
+ creds(creds),
+ m_possible_protocols(next_protocols)
+ {
+ }
+
+/*
+* Send a hello request to the client
+*/
+void Server::renegotiate(bool force_full_renegotiation)
+ {
+ if(state)
+ return; // currently in handshake
+
+ state = new Handshake_State(new Stream_Handshake_Reader);
+
+ state->allow_session_resumption = !force_full_renegotiation;
+ state->set_expected_next(CLIENT_HELLO);
+ Hello_Request hello_req(writer);
+ }
+
+void Server::alert_notify(const Alert& alert)
+ {
+ if(alert.type() == Alert::NO_RENEGOTIATION)
+ {
+ if(handshake_completed && state)
+ {
+ delete state;
+ state = 0;
+ }
+ }
+ }
+
+/*
+* Split up and process handshake messages
+*/
+void Server::read_handshake(byte rec_type,
+ const MemoryRegion<byte>& rec_buf)
+ {
+ if(rec_type == HANDSHAKE && !state)
+ {
+ state = new Handshake_State(new Stream_Handshake_Reader);
+ state->set_expected_next(CLIENT_HELLO);
+ }
+
+ Channel::read_handshake(rec_type, rec_buf);
+ }
+
+/*
+* Process a handshake message
+*/
+void Server::process_handshake_msg(Handshake_Type type,
+ const MemoryRegion<byte>& contents)
+ {
+ if(state == 0)
+ throw Unexpected_Message("Unexpected handshake message from client");
+
+ 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(type, contents);
+ }
+
+ if(type == CLIENT_HELLO || type == CLIENT_HELLO_SSLV2)
+ {
+ state->client_hello = new Client_Hello(contents, type);
+
+ if(state->client_hello->sni_hostname() != "")
+ m_hostname = state->client_hello->sni_hostname();
+
+ Protocol_Version client_version = state->client_hello->version();
+
+ if(client_version < policy.min_version())
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Client version is unacceptable by policy");
+
+ if(client_version <= policy.pref_version())
+ state->set_version(client_version);
+ else
+ state->set_version(policy.pref_version());
+
+ secure_renegotiation.update(state->client_hello);
+
+ m_peer_supports_heartbeats = state->client_hello->supports_heartbeats();
+ m_heartbeat_sending_allowed = state->client_hello->peer_can_send_heartbeats();
+
+ writer.set_version(state->version());
+ reader.set_version(state->version());
+
+ Session session_info;
+ const bool resuming =
+ state->allow_session_resumption &&
+ check_for_resume(session_info,
+ session_manager,
+ creds,
+ state->client_hello,
+ std::chrono::seconds(policy.session_ticket_lifetime()));
+
+ bool have_session_ticket_key = false;
+
+ try
+ {
+ have_session_ticket_key =
+ creds.psk("tls-server", "session-ticket", "").length() > 0;
+ }
+ catch(...) {}
+
+ if(resuming)
+ {
+ // resume session
+
+ state->server_hello = new Server_Hello(
+ writer,
+ state->hash,
+ state->client_hello->session_id(),
+ Protocol_Version(session_info.version()),
+ session_info.ciphersuite_code(),
+ session_info.compression_method(),
+ session_info.fragment_size(),
+ secure_renegotiation.supported(),
+ secure_renegotiation.for_server_hello(),
+ (state->client_hello->supports_session_ticket() &&
+ state->client_hello->session_ticket().empty() &&
+ have_session_ticket_key),
+ state->client_hello->next_protocol_notification(),
+ m_possible_protocols,
+ state->client_hello->supports_heartbeats(),
+ rng);
+
+ secure_renegotiation.update(state->server_hello);
+
+ if(session_info.fragment_size())
+ {
+ reader.set_maximum_fragment_size(session_info.fragment_size());
+ writer.set_maximum_fragment_size(session_info.fragment_size());
+ }
+
+ state->suite = Ciphersuite::by_id(state->server_hello->ciphersuite());
+
+ state->keys = Session_Keys(state, session_info.master_secret(), true);
+
+ if(!handshake_fn(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(writer, state->hash);
+ }
+
+ if(state->server_hello->supports_session_ticket() && !state->new_session_ticket)
+ {
+ try
+ {
+ const SymmetricKey ticket_key = creds.psk("tls-server", "session-ticket", "");
+
+ state->new_session_ticket =
+ new New_Session_Ticket(writer, state->hash,
+ session_info.encrypt(ticket_key, rng),
+ policy.session_ticket_lifetime());
+ }
+ catch(...) {}
+
+ if(!state->new_session_ticket)
+ state->new_session_ticket = new New_Session_Ticket(writer, state->hash);
+ }
+
+ writer.send(CHANGE_CIPHER_SPEC, 1);
+
+ writer.activate(SERVER, state->suite, state->keys,
+ state->server_hello->compression_method());
+
+ state->server_finished = new Finished(writer, state, SERVER);
+
+ state->set_expected_next(HANDSHAKE_CCS);
+ }
+ else // new session
+ {
+ std::map<std::string, std::vector<X509_Certificate> > cert_chains;
+
+ cert_chains = get_server_certs(m_hostname, creds);
+
+ if(m_hostname != "" && cert_chains.empty())
+ {
+ send_alert(Alert(Alert::UNRECOGNIZED_NAME));
+ cert_chains = get_server_certs("", creds);
+ }
+
+ state->server_hello = new Server_Hello(
+ writer,
+ state->hash,
+ rng.random_vec(32), // new session ID
+ state->version(),
+ choose_ciphersuite(policy, creds, cert_chains, state->client_hello),
+ choose_compression(policy, state->client_hello->compression_methods()),
+ state->client_hello->fragment_size(),
+ secure_renegotiation.supported(),
+ secure_renegotiation.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.update(state->server_hello);
+
+ if(state->client_hello->fragment_size())
+ {
+ reader.set_maximum_fragment_size(state->client_hello->fragment_size());
+ writer.set_maximum_fragment_size(state->client_hello->fragment_size());
+ }
+
+ state->suite = Ciphersuite::by_id(state->server_hello->ciphersuite());
+
+ const std::string sig_algo = state->suite.sig_algo();
+ const std::string kex_algo = state->suite.kex_algo();
+
+ if(sig_algo != "")
+ {
+ BOTAN_ASSERT(!cert_chains[sig_algo].empty(),
+ "Attempting to send empty certificate chain");
+
+ state->server_certs = new Certificate(writer,
+ state->hash,
+ cert_chains[sig_algo]);
+ }
+
+ Private_Key* private_key = 0;
+
+ if(kex_algo == "RSA" || sig_algo != "")
+ {
+ private_key = creds.private_key_for(state->server_certs->cert_chain()[0],
+ "tls-server",
+ m_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(writer, state, policy, creds, rng, private_key);
+ }
+
+ std::vector<X509_Certificate> client_auth_CAs =
+ creds.trusted_certificate_authorities("tls-server", m_hostname);
+
+ if(!client_auth_CAs.empty() && state->suite.sig_algo() != "")
+ {
+ state->cert_req = new Certificate_Req(writer,
+ state->hash,
+ 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(writer, 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, creds, policy, rng);
+
+ state->keys = Session_Keys(state, state->client_kex->pre_master_secret(), false);
+ }
+ else if(type == CERTIFICATE_VERIFY)
+ {
+ state->client_verify = new Certificate_Verify(contents, state->version());
+
+ peer_certs = state->client_certs->cert_chain();
+
+ const bool sig_valid =
+ state->client_verify->verify(peer_certs[0], state);
+
+ state->hash.update(type, contents);
+
+ /*
+ * 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
+ {
+ creds.verify_certificate_chain("tls-server", "", peer_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);
+
+ reader.activate(SERVER, state->suite, state->keys,
+ state->server_hello->compression_method());
+ }
+ else if(type == NEXT_PROTOCOL)
+ {
+ state->set_expected_next(FINISHED);
+
+ state->next_protocol = new Next_Protocol(contents);
+
+ 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(type, contents);
+
+ Session session_info(
+ state->server_hello->session_id(),
+ state->keys.master_secret(),
+ state->server_hello->version(),
+ state->server_hello->ciphersuite(),
+ state->server_hello->compression_method(),
+ SERVER,
+ secure_renegotiation.supported(),
+ state->server_hello->fragment_size(),
+ peer_certs,
+ MemoryVector<byte>(),
+ m_hostname,
+ state->srp_identifier()
+ );
+
+ if(handshake_fn(session_info))
+ {
+ if(state->server_hello->supports_session_ticket())
+ {
+ try
+ {
+ const SymmetricKey ticket_key = creds.psk("tls-server", "session-ticket", "");
+
+ state->new_session_ticket =
+ new New_Session_Ticket(writer, state->hash,
+ session_info.encrypt(ticket_key, rng),
+ policy.session_ticket_lifetime());
+ }
+ catch(...) {}
+ }
+ else
+ session_manager.save(session_info);
+ }
+
+ if(state->server_hello->supports_session_ticket() && !state->new_session_ticket)
+ state->new_session_ticket = new New_Session_Ticket(writer, state->hash);
+
+ writer.send(CHANGE_CIPHER_SPEC, 1);
+
+ writer.activate(SERVER, state->suite, state->keys,
+ state->server_hello->compression_method());
+
+ state->server_finished = new Finished(writer, state, SERVER);
+ }
+
+ secure_renegotiation.update(state->client_finished,
+ state->server_finished);
+
+ delete state;
+ state = 0;
+ handshake_completed = true;
+ }
+ else
+ throw Unexpected_Message("Unknown handshake message received");
+ }
+
+}
+
+}