diff options
Diffstat (limited to 'src/tls/tls_client.cpp')
-rw-r--r-- | src/tls/tls_client.cpp | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/src/tls/tls_client.cpp b/src/tls/tls_client.cpp new file mode 100644 index 000000000..1ca256f3e --- /dev/null +++ b/src/tls/tls_client.cpp @@ -0,0 +1,486 @@ +/* +* TLS Client +* (C) 2004-2011 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/tls_client.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 { + +/* +* TLS Client Constructor +*/ +Client::Client(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::string& hostname, + std::function<std::string (std::vector<std::string>)> next_protocol) : + Channel(output_fn, proc_fn, handshake_fn), + policy(policy), + rng(rng), + session_manager(session_manager), + creds(creds), + m_hostname(hostname) + { + writer.set_version(Protocol_Version::SSL_V3); + + state = new Handshake_State(new Stream_Handshake_Reader); + state->set_expected_next(SERVER_HELLO); + + state->client_npn_cb = next_protocol; + + const std::string srp_identifier = creds.srp_identifier("tls-client", hostname); + + const bool send_npn_request = static_cast<bool>(next_protocol); + + if(hostname != "") + { + Session session_info; + if(session_manager.load_from_host_info(hostname, 0, session_info)) + { + if(session_info.srp_identifier() == srp_identifier) + { + state->client_hello = new Client_Hello( + writer, + state->hash, + policy, + rng, + secure_renegotiation.for_client_hello(), + session_info, + send_npn_request); + + state->resume_master_secret = session_info.master_secret(); + } + } + } + + if(!state->client_hello) // not resuming + { + state->client_hello = new Client_Hello( + writer, + state->hash, + policy, + rng, + secure_renegotiation.for_client_hello(), + send_npn_request, + hostname, + srp_identifier); + } + + secure_renegotiation.update(state->client_hello); + } + +/* +* Send a new client hello to renegotiate +*/ +void Client::renegotiate(bool force_full_renegotiation) + { + if(state && state->client_hello) + return; // currently in active handshake + + delete state; + state = new Handshake_State(new Stream_Handshake_Reader); + + state->set_expected_next(SERVER_HELLO); + + if(!force_full_renegotiation) + { + Session session_info; + if(session_manager.load_from_host_info(m_hostname, 0, session_info)) + { + state->client_hello = new Client_Hello( + writer, + state->hash, + policy, + rng, + secure_renegotiation.for_client_hello(), + session_info); + + state->resume_master_secret = session_info.master_secret(); + } + } + + if(!state->client_hello) + { + state->client_hello = new Client_Hello( + writer, + state->hash, + policy, + rng, + secure_renegotiation.for_client_hello()); + } + + secure_renegotiation.update(state->client_hello); + } + +void Client::alert_notify(const Alert& alert) + { + if(alert.type() == Alert::NO_RENEGOTIATION) + { + if(handshake_completed && state) + { + delete state; + state = nullptr; + } + } + } + +/* +* Process a handshake message +*/ +void Client::process_handshake_msg(Handshake_Type type, + const std::vector<byte>& contents) + { + if(!state) + throw Unexpected_Message("Unexpected handshake message from server"); + + if(type == HELLO_REQUEST) + { + Hello_Request hello_request(contents); + + // Ignore request entirely if we are currently negotiating a handshake + if(state->client_hello) + return; + + if(!secure_renegotiation.supported() && policy.require_secure_renegotiation()) + { + delete state; + state = nullptr; + + // RFC 5746 section 4.2 + send_alert(Alert(Alert::NO_RENEGOTIATION)); + return; + } + + renegotiate(false); + + return; + } + + state->confirm_transition_to(type); + + if(type != HANDSHAKE_CCS && type != FINISHED) + state->hash.update(type, contents); + + if(type == SERVER_HELLO) + { + state->server_hello = new Server_Hello(contents); + + if(!state->client_hello->offered_suite(state->server_hello->ciphersuite())) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server replied with ciphersuite we didn't send"); + } + + if(!value_exists(state->client_hello->compression_methods(), + state->server_hello->compression_method())) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server replied with compression method we didn't send"); + } + + if(!state->client_hello->next_protocol_notification() && + state->server_hello->next_protocol_notification()) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server sent next protocol but we didn't request it"); + } + + if(state->server_hello->supports_session_ticket()) + { + if(!state->client_hello->supports_session_ticket()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server sent session ticket extension but we did not"); + } + + state->set_version(state->server_hello->version()); + + writer.set_version(state->version()); + reader.set_version(state->version()); + + secure_renegotiation.update(state->server_hello); + + m_peer_supports_heartbeats = state->server_hello->supports_heartbeats(); + m_heartbeat_sending_allowed = state->server_hello->peer_can_send_heartbeats(); + + state->suite = Ciphersuite::by_id(state->server_hello->ciphersuite()); + + 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 + + /* + * 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->version() != state->client_hello->version()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server resumed session but with wrong version"); + + state->keys = Session_Keys(state, + state->resume_master_secret, + true); + + if(state->server_hello->supports_session_ticket()) + state->set_expected_next(NEW_SESSION_TICKET); + else + state->set_expected_next(HANDSHAKE_CCS); + } + else + { + // new session + + if(state->version() > state->client_hello->version()) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Client: Server replied with bad version"); + } + + if(state->version() < policy.min_version()) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Client: Server is too old for specified policy"); + } + + if(state->suite.sig_algo() != "") + { + state->set_expected_next(CERTIFICATE); + } + else if(state->suite.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. + */ + + state->set_expected_next(SERVER_KEX); + state->set_expected_next(SERVER_HELLO_DONE); + } + else if(state->suite.kex_algo() != "RSA") + { + state->set_expected_next(SERVER_KEX); + } + else + { + state->set_expected_next(CERTIFICATE_REQUEST); // optional + state->set_expected_next(SERVER_HELLO_DONE); + } + } + } + else if(type == CERTIFICATE) + { + if(state->suite.kex_algo() != "RSA") + { + state->set_expected_next(SERVER_KEX); + } + else + { + state->set_expected_next(CERTIFICATE_REQUEST); // optional + state->set_expected_next(SERVER_HELLO_DONE); + } + + state->server_certs = new Certificate(contents); + + peer_certs = state->server_certs->cert_chain(); + if(peer_certs.size() == 0) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Client: No certificates sent by server"); + + try + { + creds.verify_certificate_chain("tls-client", m_hostname, peer_certs); + } + catch(std::exception& e) + { + throw TLS_Exception(Alert::BAD_CERTIFICATE, e.what()); + } + + std::unique_ptr<Public_Key> peer_key(peer_certs[0].subject_public_key()); + + if(peer_key->algo_name() != state->suite.sig_algo()) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Certificate key type did not match ciphersuite"); + } + else if(type == SERVER_KEX) + { + state->set_expected_next(CERTIFICATE_REQUEST); // optional + state->set_expected_next(SERVER_HELLO_DONE); + + state->server_kex = new Server_Key_Exchange(contents, + state->suite.kex_algo(), + state->suite.sig_algo(), + state->version()); + + if(state->suite.sig_algo() != "") + { + if(!state->server_kex->verify(peer_certs[0], state)) + { + throw TLS_Exception(Alert::DECRYPT_ERROR, + "Bad signature on server key exchange"); + } + } + } + else if(type == CERTIFICATE_REQUEST) + { + state->set_expected_next(SERVER_HELLO_DONE); + state->cert_req = new Certificate_Req(contents, state->version()); + } + else if(type == SERVER_HELLO_DONE) + { + state->server_hello_done = new Server_Hello_Done(contents); + + if(state->received_handshake_msg(CERTIFICATE_REQUEST)) + { + const std::vector<std::string>& types = + state->cert_req->acceptable_cert_types(); + + std::vector<X509_Certificate> client_certs = + creds.cert_chain(types, + "tls-client", + m_hostname); + + state->client_certs = new Certificate(writer, + state->hash, + client_certs); + } + + state->client_kex = + new Client_Key_Exchange(writer, + state, + creds, + peer_certs, + m_hostname, + rng); + + state->keys = Session_Keys(state, + state->client_kex->pre_master_secret(), + false); + + if(state->received_handshake_msg(CERTIFICATE_REQUEST) && + !state->client_certs->empty()) + { + Private_Key* private_key = + creds.private_key_for(state->client_certs->cert_chain()[0], + "tls-client", + m_hostname); + + state->client_verify = new Certificate_Verify(writer, + state, + rng, + private_key); + } + + writer.send(CHANGE_CIPHER_SPEC, 1); + + writer.activate(CLIENT, state->suite, state->keys, + state->server_hello->compression_method()); + + if(state->server_hello->next_protocol_notification()) + { + const std::string protocol = + state->client_npn_cb(state->server_hello->next_protocols()); + + state->next_protocol = new Next_Protocol(writer, state->hash, protocol); + } + + state->client_finished = new Finished(writer, state, CLIENT); + + if(state->server_hello->supports_session_ticket()) + state->set_expected_next(NEW_SESSION_TICKET); + else + state->set_expected_next(HANDSHAKE_CCS); + } + else if(type == NEW_SESSION_TICKET) + { + state->new_session_ticket = new New_Session_Ticket(contents); + + state->set_expected_next(HANDSHAKE_CCS); + } + else if(type == HANDSHAKE_CCS) + { + state->set_expected_next(FINISHED); + + reader.activate(CLIENT, state->suite, state->keys, + state->server_hello->compression_method()); + } + else if(type == FINISHED) + { + state->set_expected_next(HELLO_REQUEST); + + state->server_finished = new Finished(contents); + + if(!state->server_finished->verify(state, SERVER)) + throw TLS_Exception(Alert::DECRYPT_ERROR, + "Finished message didn't verify"); + + state->hash.update(type, contents); + + if(!state->client_finished) // session resume case + { + writer.send(CHANGE_CIPHER_SPEC, 1); + + writer.activate(CLIENT, state->suite, state->keys, + state->server_hello->compression_method()); + + state->client_finished = new Finished(writer, state, CLIENT); + } + + secure_renegotiation.update(state->client_finished, state->server_finished); + + std::vector<byte> session_id = state->server_hello->session_id(); + + const std::vector<byte>& session_ticket = state->session_ticket(); + + if(session_id.empty() && !session_ticket.empty()) + session_id = make_hello_random(rng); + + Session session_info( + session_id, + state->keys.master_secret(), + state->server_hello->version(), + state->server_hello->ciphersuite(), + state->server_hello->compression_method(), + CLIENT, + secure_renegotiation.supported(), + state->server_hello->fragment_size(), + peer_certs, + session_ticket, + m_hostname, + "" + ); + + if(handshake_fn(session_info)) + session_manager.save(session_info); + else + session_manager.remove_entry(session_info.session_id()); + + delete state; + state = nullptr; + handshake_completed = true; + } + else + throw Unexpected_Message("Unknown handshake message received"); + } + +} + +} |