aboutsummaryrefslogtreecommitdiffstats
path: root/src/tls/tls_client.cpp
diff options
context:
space:
mode:
authorlloyd <[email protected]>2011-12-23 16:17:29 +0000
committerlloyd <[email protected]>2011-12-23 16:17:29 +0000
commit67c1645ae151f5dd0f2bafce926ff8690fd97f19 (patch)
tree9af9c1c22ab58093328cdfd00dbe42292d8b5ed6 /src/tls/tls_client.cpp
parentd363602f95f1514b4b595d9912fba2e503edcb21 (diff)
Rename ssl module to tls
Diffstat (limited to 'src/tls/tls_client.cpp')
-rw-r--r--src/tls/tls_client.cpp499
1 files changed, 499 insertions, 0 deletions
diff --git a/src/tls/tls_client.cpp b/src/tls/tls_client.cpp
new file mode 100644
index 000000000..cfa86881c
--- /dev/null
+++ b/src/tls/tls_client.cpp
@@ -0,0 +1,499 @@
+/*
+* TLS Client
+* (C) 2004-2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_client.h>
+#include <botan/internal/tls_alerts.h>
+#include <botan/internal/tls_state.h>
+#include <botan/loadstor.h>
+#include <botan/rsa.h>
+#include <botan/dsa.h>
+#include <botan/dh.h>
+
+namespace Botan {
+
+namespace {
+
+/**
+* Verify the state transition is allowed
+* FIXME: checks are wrong for session reuse (add a flag for that)
+*/
+void client_check_state(Handshake_Type new_msg, Handshake_State* state)
+ {
+ class State_Transition_Error : public Unexpected_Message
+ {
+ public:
+ State_Transition_Error(const std::string& err) :
+ Unexpected_Message("State transition error from " + err) {}
+ };
+
+ if(new_msg == HELLO_REQUEST)
+ {
+ if(state->client_hello)
+ throw State_Transition_Error("HelloRequest");
+ }
+ else if(new_msg == SERVER_HELLO)
+ {
+ if(!state->client_hello || state->server_hello)
+ throw State_Transition_Error("ServerHello");
+ }
+ else if(new_msg == CERTIFICATE)
+ {
+ if(!state->server_hello || state->server_kex ||
+ state->cert_req || state->server_hello_done)
+ throw State_Transition_Error("ServerCertificate");
+ }
+ else if(new_msg == SERVER_KEX)
+ {
+ if(!state->server_hello || state->server_kex ||
+ state->cert_req || state->server_hello_done)
+ throw State_Transition_Error("ServerKeyExchange");
+ }
+ else if(new_msg == CERTIFICATE_REQUEST)
+ {
+ if(!state->server_certs || state->cert_req || state->server_hello_done)
+ throw State_Transition_Error("CertificateRequest");
+ }
+ else if(new_msg == SERVER_HELLO_DONE)
+ {
+ if(!state->server_hello || state->server_hello_done)
+ throw State_Transition_Error("ServerHelloDone");
+ }
+ else if(new_msg == HANDSHAKE_CCS)
+ {
+ if(!state->client_finished || state->server_finished)
+ throw State_Transition_Error("ServerChangeCipherSpec");
+ }
+ else if(new_msg == FINISHED)
+ {
+ if(!state->got_server_ccs)
+ throw State_Transition_Error("ServerFinished");
+ }
+ else
+ throw Unexpected_Message("Unexpected message in handshake");
+ }
+
+}
+
+/**
+* TLS Client Constructor
+*/
+TLS_Client::TLS_Client(std::tr1::function<void (const byte[], size_t)> socket_output_fn,
+ std::tr1::function<void (const byte[], size_t, u16bit)> process_fn,
+ const TLS_Policy& policy,
+ RandomNumberGenerator& rng) :
+ policy(policy),
+ rng(rng),
+ proc_fn(process_fn),
+ writer(socket_output_fn),
+ state(0),
+ active(false)
+ {
+ writer.set_version(policy.pref_version());
+
+ state = new Handshake_State;
+ state->client_hello = new Client_Hello(rng, writer, policy, state->hash);
+ }
+
+void TLS_Client::add_client_cert(const X509_Certificate& cert,
+ Private_Key* cert_key)
+ {
+ certs.push_back(std::make_pair(cert, cert_key));
+ }
+
+/**
+* TLS Client Destructor
+*/
+TLS_Client::~TLS_Client()
+ {
+ close();
+ for(size_t i = 0; i != certs.size(); i++)
+ delete certs[i].second;
+ delete state;
+ }
+
+size_t TLS_Client::received_data(const byte buf[], size_t buf_size)
+ {
+ try
+ {
+ reader.add_input(buf, buf_size);
+
+ byte rec_type = CONNECTION_CLOSED;
+ SecureVector<byte> record;
+
+ while(!reader.currently_empty())
+ {
+ const size_t bytes_needed = reader.get_record(rec_type, record);
+
+ if(bytes_needed > 0)
+ return bytes_needed;
+
+ if(rec_type == APPLICATION_DATA)
+ {
+ if(active)
+ {
+ proc_fn(&record[0], record.size(), NO_ALERT_TYPE);
+ }
+ else
+ {
+ throw Unexpected_Message("Application data before handshake done");
+ }
+ }
+ else if(rec_type == HANDSHAKE || rec_type == CHANGE_CIPHER_SPEC)
+ {
+ read_handshake(rec_type, record);
+ }
+ else if(rec_type == ALERT)
+ {
+ Alert alert(record);
+
+ proc_fn(0, 0, alert.type());
+
+ if(alert.is_fatal() || alert.type() == CLOSE_NOTIFY)
+ {
+ if(alert.type() == CLOSE_NOTIFY)
+ {
+ writer.alert(WARNING, CLOSE_NOTIFY);
+ }
+
+ close(FATAL, NO_ALERT_TYPE);
+ }
+ }
+ else
+ throw Unexpected_Message("Unknown message type received");
+ }
+
+ return 0; // on a record boundary
+ }
+ catch(TLS_Exception& e)
+ {
+ close(FATAL, e.type());
+ throw;
+ }
+ catch(std::exception& e)
+ {
+ close(FATAL, INTERNAL_ERROR);
+ throw;
+ }
+ }
+
+void TLS_Client::queue_for_sending(const byte buf[], size_t buf_size)
+ {
+ if(active)
+ {
+ while(!pre_handshake_write_queue.end_of_data())
+ {
+ SecureVector<byte> q_buf(1024);
+ const size_t got = pre_handshake_write_queue.read(&q_buf[0], q_buf.size());
+ writer.send(APPLICATION_DATA, &q_buf[0], got);
+ }
+
+ writer.send(APPLICATION_DATA, buf, buf_size);
+ writer.flush();
+ }
+ else
+ pre_handshake_write_queue.write(buf, buf_size);
+ }
+
+/**
+* Close a TLS connection
+*/
+void TLS_Client::close()
+ {
+ close(WARNING, CLOSE_NOTIFY);
+ }
+
+/**
+* Close a TLS connection
+*/
+void TLS_Client::close(Alert_Level level, Alert_Type alert_code)
+ {
+ if(active)
+ {
+ active = false;
+
+ if(alert_code != NO_ALERT_TYPE)
+ {
+ try
+ {
+ writer.alert(level, alert_code);
+ writer.flush();
+ }
+ catch(...) { /* swallow it */ }
+ }
+
+ reader.reset();
+ writer.reset();
+ }
+ }
+
+/**
+* Split up and process handshake messages
+*/
+void TLS_Client::read_handshake(byte rec_type,
+ const MemoryRegion<byte>& rec_buf)
+ {
+ if(rec_type == HANDSHAKE)
+ state->queue.write(&rec_buf[0], rec_buf.size());
+
+ while(true)
+ {
+ Handshake_Type type = HANDSHAKE_NONE;
+ SecureVector<byte> contents;
+
+ if(rec_type == HANDSHAKE)
+ {
+ if(state->queue.size() >= 4)
+ {
+ byte head[4] = { 0 };
+ state->queue.peek(head, 4);
+
+ const size_t length = make_u32bit(0, head[1], head[2], head[3]);
+
+ if(state->queue.size() >= length + 4)
+ {
+ type = static_cast<Handshake_Type>(head[0]);
+ contents.resize(length);
+ state->queue.read(head, 4);
+ state->queue.read(&contents[0], contents.size());
+ }
+ }
+ }
+ else if(rec_type == CHANGE_CIPHER_SPEC)
+ {
+ if(state->queue.size() == 0 && rec_buf.size() == 1 && rec_buf[0] == 1)
+ type = HANDSHAKE_CCS;
+ else
+ throw Decoding_Error("Malformed ChangeCipherSpec message");
+ }
+ else
+ throw Decoding_Error("Unknown message type in handshake processing");
+
+ if(type == HANDSHAKE_NONE)
+ break;
+
+ process_handshake_msg(type, contents);
+
+ if(type == HANDSHAKE_CCS || !state)
+ break;
+ }
+ }
+
+/**
+* Process a handshake message
+*/
+void TLS_Client::process_handshake_msg(Handshake_Type type,
+ const MemoryRegion<byte>& contents)
+ {
+ rng.add_entropy(&contents[0], contents.size());
+
+ if(type == HELLO_REQUEST)
+ {
+ if(state == 0)
+ state = new Handshake_State();
+ else
+ return;
+ }
+
+ if(state == 0)
+ throw Unexpected_Message("Unexpected handshake message");
+
+ if(type != HANDSHAKE_CCS && type != HELLO_REQUEST && type != FINISHED)
+ {
+ state->hash.update(static_cast<byte>(type));
+ const size_t record_length = contents.size();
+ for(size_t i = 0; i != 3; i++)
+ state->hash.update(get_byte<u32bit>(i+1, record_length));
+ state->hash.update(contents);
+ }
+
+ if(type == HELLO_REQUEST)
+ {
+ client_check_state(type, state);
+
+ Hello_Request hello_request(contents);
+ state->client_hello = new Client_Hello(rng, writer, policy, state->hash);
+ }
+ else if(type == SERVER_HELLO)
+ {
+ client_check_state(type, state);
+
+ state->server_hello = new Server_Hello(contents);
+
+ if(!state->client_hello->offered_suite(
+ state->server_hello->ciphersuite()
+ )
+ )
+ throw TLS_Exception(HANDSHAKE_FAILURE,
+ "TLS_Client: Server replied with bad ciphersuite");
+
+ state->version = state->server_hello->version();
+
+ if(state->version > state->client_hello->version())
+ throw TLS_Exception(HANDSHAKE_FAILURE,
+ "TLS_Client: Server replied with bad version");
+
+ if(state->version < policy.min_version())
+ throw TLS_Exception(PROTOCOL_VERSION,
+ "TLS_Client: Server is too old for specified policy");
+
+ writer.set_version(state->version);
+ reader.set_version(state->version);
+
+ state->suite = CipherSuite(state->server_hello->ciphersuite());
+ }
+ else if(type == CERTIFICATE)
+ {
+ client_check_state(type, state);
+
+ if(state->suite.sig_type() == TLS_ALGO_SIGNER_ANON)
+ throw Unexpected_Message("Recived certificate from anonymous server");
+
+ state->server_certs = new Certificate(contents);
+
+ peer_certs = state->server_certs->cert_chain();
+ if(peer_certs.size() == 0)
+ throw TLS_Exception(HANDSHAKE_FAILURE,
+ "TLS_Client: No certificates sent by server");
+
+ if(!policy.check_cert(peer_certs))
+ throw TLS_Exception(BAD_CERTIFICATE,
+ "TLS_Client: Server certificate is not valid");
+
+ state->kex_pub = peer_certs[0].subject_public_key();
+
+ bool is_dsa = false, is_rsa = false;
+
+ if(dynamic_cast<DSA_PublicKey*>(state->kex_pub))
+ is_dsa = true;
+ else if(dynamic_cast<RSA_PublicKey*>(state->kex_pub))
+ is_rsa = true;
+ else
+ throw TLS_Exception(UNSUPPORTED_CERTIFICATE,
+ "Unknown key type received in server kex");
+
+ if((is_dsa && state->suite.sig_type() != TLS_ALGO_SIGNER_DSA) ||
+ (is_rsa && state->suite.sig_type() != TLS_ALGO_SIGNER_RSA))
+ throw TLS_Exception(ILLEGAL_PARAMETER,
+ "Certificate key type did not match ciphersuite");
+ }
+ else if(type == SERVER_KEX)
+ {
+ client_check_state(type, state);
+
+ if(state->suite.kex_type() == TLS_ALGO_KEYEXCH_NOKEX)
+ throw Unexpected_Message("Unexpected key exchange from server");
+
+ state->server_kex = new Server_Key_Exchange(contents);
+
+ if(state->kex_pub)
+ delete state->kex_pub;
+
+ state->kex_pub = state->server_kex->key();
+
+ bool is_dh = false, is_rsa = false;
+
+ if(dynamic_cast<DH_PublicKey*>(state->kex_pub))
+ is_dh = true;
+ else if(dynamic_cast<RSA_PublicKey*>(state->kex_pub))
+ is_rsa = true;
+ else
+ throw TLS_Exception(HANDSHAKE_FAILURE,
+ "Unknown key type received in server kex");
+
+ if((is_dh && state->suite.kex_type() != TLS_ALGO_KEYEXCH_DH) ||
+ (is_rsa && state->suite.kex_type() != TLS_ALGO_KEYEXCH_RSA))
+ throw TLS_Exception(ILLEGAL_PARAMETER,
+ "Certificate key type did not match ciphersuite");
+
+ if(state->suite.sig_type() != TLS_ALGO_SIGNER_ANON)
+ {
+ if(!state->server_kex->verify(peer_certs[0],
+ state->client_hello->random(),
+ state->server_hello->random()))
+ throw TLS_Exception(DECRYPT_ERROR,
+ "Bad signature on server key exchange");
+ }
+ }
+ else if(type == CERTIFICATE_REQUEST)
+ {
+ client_check_state(type, state);
+
+ state->cert_req = new Certificate_Req(contents);
+ state->do_client_auth = true;
+ }
+ else if(type == SERVER_HELLO_DONE)
+ {
+ client_check_state(type, state);
+
+ state->server_hello_done = new Server_Hello_Done(contents);
+
+ if(state->do_client_auth)
+ {
+ std::vector<X509_Certificate> send_certs;
+
+ std::vector<Certificate_Type> types =
+ state->cert_req->acceptable_types();
+
+ // FIXME: Fill in useful certs here, if any
+ state->client_certs = new Certificate(writer, send_certs,
+ state->hash);
+ }
+
+ state->client_kex =
+ new Client_Key_Exchange(rng, writer, state->hash,
+ state->kex_pub, state->version,
+ state->client_hello->version());
+
+ if(state->do_client_auth)
+ {
+ Private_Key* key_matching_cert = 0; // FIXME
+ state->client_verify = new Certificate_Verify(rng,
+ writer, state->hash,
+ key_matching_cert);
+ }
+
+ state->keys = SessionKeys(state->suite, state->version,
+ state->client_kex->pre_master_secret(),
+ state->client_hello->random(),
+ state->server_hello->random());
+
+ writer.send(CHANGE_CIPHER_SPEC, 1);
+ writer.flush();
+
+ writer.set_keys(state->suite, state->keys, CLIENT);
+
+ state->client_finished = new Finished(writer, state->version, CLIENT,
+ state->keys.master_secret(),
+ state->hash);
+ }
+ else if(type == HANDSHAKE_CCS)
+ {
+ client_check_state(type, state);
+
+ reader.set_keys(state->suite, state->keys, CLIENT);
+ state->got_server_ccs = true;
+ }
+ else if(type == FINISHED)
+ {
+ client_check_state(type, state);
+
+ state->server_finished = new Finished(contents);
+
+ if(!state->server_finished->verify(state->keys.master_secret(),
+ state->version, state->hash, SERVER))
+ throw TLS_Exception(DECRYPT_ERROR,
+ "Finished message didn't verify");
+
+ delete state;
+ state = 0;
+ active = true;
+ }
+ else
+ throw Unexpected_Message("Unknown handshake message received");
+ }
+
+}