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.cpp472
1 files changed, 472 insertions, 0 deletions
diff --git a/src/tls/tls_server.cpp b/src/tls/tls_server.cpp
new file mode 100644
index 000000000..30983f48f
--- /dev/null
+++ b/src/tls/tls_server.cpp
@@ -0,0 +1,472 @@
+/*
+* 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,
+ Client_Hello* client_hello)
+ {
+ MemoryVector<byte> client_session_id = client_hello->session_id();
+
+ 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;
+
+ // 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;
+ }
+
+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::tr1::function<void (const byte[], size_t)> output_fn,
+ std::tr1::function<void (const byte[], size_t, Alert)> proc_fn,
+ std::tr1::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()
+ {
+ if(state)
+ return; // currently in handshake
+
+ state = new Handshake_State;
+ 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;
+ 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);
+
+ 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->version = client_version;
+ else
+ state->version = policy.pref_version();
+
+ secure_renegotiation.update(state->client_hello);
+
+ writer.set_version(state->version);
+ reader.set_version(state->version);
+
+ Session session_info;
+ const bool resuming = check_for_resume(session_info,
+ session_manager,
+ state->client_hello);
+
+ if(resuming)
+ {
+ // resume session
+
+ state->server_hello = new Server_Hello(
+ writer,
+ state->hash,
+ session_info.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->next_protocol_notification(),
+ m_possible_protocols,
+ rng);
+
+ 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::lookup_ciphersuite(state->server_hello->ciphersuite());
+
+ state->keys = Session_Keys(state, session_info.master_secret(), true);
+
+ 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);
+
+ if(!handshake_fn(session_info))
+ session_manager.remove_entry(session_info.session_id());
+
+ 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);
+ }
+
+ std::vector<std::string> available_cert_types;
+
+ for(std::map<std::string, std::vector<X509_Certificate> >::const_iterator i = cert_chains.begin();
+ i != cert_chains.end(); ++i)
+ {
+ if(!i->second.empty())
+ available_cert_types.push_back(i->first);
+ }
+
+ state->server_hello = new Server_Hello(
+ writer,
+ state->hash,
+ state->version,
+ *(state->client_hello),
+ available_cert_types,
+ policy,
+ secure_renegotiation.supported(),
+ secure_renegotiation.for_server_hello(),
+ state->client_hello->next_protocol_notification(),
+ m_possible_protocols,
+ rng);
+
+ 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::lookup_ciphersuite(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);
+ }
+
+ secure_renegotiation.update(state->server_hello);
+
+ /*
+ * 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);
+
+ // Is this allowed by the protocol?
+ if(state->client_certs->count() > 1)
+ throw TLS_Exception(Alert::CERTIFICATE_UNKNOWN,
+ "Client sent more than one certificate");
+
+ 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);
+
+ 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(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(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);
+
+ 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");
+
+ // already sent it if resuming
+ if(!state->server_finished)
+ {
+ state->hash.update(type, contents);
+
+ 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);
+
+ if(state->client_certs && state->client_verify)
+ peer_certs = state->client_certs->cert_chain();
+ }
+
+ 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,
+ m_hostname,
+ ""
+ );
+
+ if(handshake_fn(session_info))
+ session_manager.save(session_info);
+ else
+ session_manager.remove_entry(session_info.session_id());
+
+ secure_renegotiation.update(state->client_finished,
+ state->server_finished);
+
+ delete state;
+ state = 0;
+ handshake_completed = true;
+ }
+ else
+ throw Unexpected_Message("Unknown handshake message received");
+ }
+
+}
+
+}