aboutsummaryrefslogtreecommitdiffstats
path: root/src/tls
diff options
context:
space:
mode:
Diffstat (limited to 'src/tls')
-rw-r--r--src/tls/c_hello.cpp391
-rw-r--r--src/tls/c_kex.cpp393
-rw-r--r--src/tls/cert_req.cpp241
-rw-r--r--src/tls/cert_ver.cpp117
-rw-r--r--src/tls/finished.cpp104
-rw-r--r--src/tls/hello_verify.cpp61
-rw-r--r--src/tls/info.txt97
-rw-r--r--src/tls/next_protocol.cpp55
-rw-r--r--src/tls/rec_read.cpp352
-rw-r--r--src/tls/rec_wri.cpp317
-rw-r--r--src/tls/s_hello.cpp186
-rw-r--r--src/tls/s_kex.cpp292
-rw-r--r--src/tls/session_ticket.cpp57
-rw-r--r--src/tls/sessions_sqlite/info.txt11
-rw-r--r--src/tls/sessions_sqlite/tls_sqlite_sess_mgr.cpp343
-rw-r--r--src/tls/sessions_sqlite/tls_sqlite_sess_mgr.h71
-rw-r--r--src/tls/tls_alert.cpp123
-rw-r--r--src/tls/tls_alert.h100
-rw-r--r--src/tls/tls_channel.cpp325
-rw-r--r--src/tls/tls_channel.h155
-rw-r--r--src/tls/tls_ciphersuite.cpp103
-rw-r--r--src/tls/tls_ciphersuite.h73
-rw-r--r--src/tls/tls_client.cpp486
-rw-r--r--src/tls/tls_client.h74
-rw-r--r--src/tls/tls_exceptn.h47
-rw-r--r--src/tls/tls_extensions.cpp523
-rw-r--r--src/tls/tls_extensions.h381
-rw-r--r--src/tls/tls_handshake_hash.cpp103
-rw-r--r--src/tls/tls_handshake_hash.h55
-rw-r--r--src/tls/tls_handshake_reader.cpp66
-rw-r--r--src/tls/tls_handshake_reader.h58
-rw-r--r--src/tls/tls_handshake_state.cpp329
-rw-r--r--src/tls/tls_handshake_state.h125
-rw-r--r--src/tls/tls_heartbeats.cpp78
-rw-r--r--src/tls/tls_heartbeats.h40
-rw-r--r--src/tls/tls_magic.h69
-rw-r--r--src/tls/tls_messages.h500
-rw-r--r--src/tls/tls_policy.cpp265
-rw-r--r--src/tls/tls_policy.h127
-rw-r--r--src/tls/tls_reader.h233
-rw-r--r--src/tls/tls_record.h149
-rw-r--r--src/tls/tls_server.cpp620
-rw-r--r--src/tls/tls_server.h74
-rw-r--r--src/tls/tls_session.cpp248
-rw-r--r--src/tls/tls_session.h215
-rw-r--r--src/tls/tls_session_key.cpp89
-rw-r--r--src/tls/tls_session_key.h52
-rw-r--r--src/tls/tls_session_manager.cpp96
-rw-r--r--src/tls/tls_session_manager.h125
-rw-r--r--src/tls/tls_suite_info.cpp317
-rw-r--r--src/tls/tls_version.cpp33
-rw-r--r--src/tls/tls_version.h87
52 files changed, 9631 insertions, 0 deletions
diff --git a/src/tls/c_hello.cpp b/src/tls/c_hello.cpp
new file mode 100644
index 000000000..056a7550f
--- /dev/null
+++ b/src/tls/c_hello.cpp
@@ -0,0 +1,391 @@
+/*
+* TLS Hello Request and Client Hello Messages
+* (C) 2004-2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/internal/tls_session_key.h>
+#include <botan/internal/tls_extensions.h>
+#include <botan/tls_record.h>
+#include <botan/internal/stl_util.h>
+#include <botan/time.h>
+
+namespace Botan {
+
+namespace TLS {
+
+enum {
+ TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF
+};
+
+MemoryVector<byte> make_hello_random(RandomNumberGenerator& rng)
+ {
+ MemoryVector<byte> buf(32);
+ const u32bit time32 = system_time();
+ store_be(time32, buf);
+ rng.randomize(&buf[4], buf.size() - 4);
+ return buf;
+ }
+
+/*
+* Create a new Hello Request message
+*/
+Hello_Request::Hello_Request(Record_Writer& writer)
+ {
+ writer.send(*this);
+ }
+
+/*
+* Deserialize a Hello Request message
+*/
+Hello_Request::Hello_Request(const MemoryRegion<byte>& buf)
+ {
+ if(buf.size())
+ throw Decoding_Error("Bad Hello_Request, has non-zero size");
+ }
+
+/*
+* Serialize a Hello Request message
+*/
+MemoryVector<byte> Hello_Request::serialize() const
+ {
+ return MemoryVector<byte>();
+ }
+
+/*
+* Create a new Client Hello message
+*/
+Client_Hello::Client_Hello(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const Policy& policy,
+ RandomNumberGenerator& rng,
+ const MemoryRegion<byte>& reneg_info,
+ bool next_protocol,
+ const std::string& hostname,
+ const std::string& srp_identifier) :
+ m_version(policy.pref_version()),
+ m_random(make_hello_random(rng)),
+ m_suites(ciphersuite_list(policy, (srp_identifier != ""))),
+ m_comp_methods(policy.compression()),
+ m_hostname(hostname),
+ m_srp_identifier(srp_identifier),
+ m_next_protocol(next_protocol),
+ m_fragment_size(0),
+ m_secure_renegotiation(true),
+ m_renegotiation_info(reneg_info),
+ m_supported_curves(policy.allowed_ecc_curves()),
+ m_supports_session_ticket(true),
+ m_supports_heartbeats(true),
+ m_peer_can_send_heartbeats(true)
+ {
+ std::vector<std::string> hashes = policy.allowed_hashes();
+ std::vector<std::string> sigs = policy.allowed_signature_methods();
+
+ for(size_t i = 0; i != hashes.size(); ++i)
+ for(size_t j = 0; j != sigs.size(); ++j)
+ m_supported_algos.push_back(std::make_pair(hashes[i], sigs[j]));
+
+ hash.update(writer.send(*this));
+ }
+
+/*
+* Create a new Client Hello message (session resumption case)
+*/
+Client_Hello::Client_Hello(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const Policy& policy,
+ RandomNumberGenerator& rng,
+ const MemoryRegion<byte>& reneg_info,
+ const Session& session,
+ bool next_protocol) :
+ m_version(session.version()),
+ m_session_id(session.session_id()),
+ m_random(make_hello_random(rng)),
+ m_suites(ciphersuite_list(policy, (session.srp_identifier() != ""))),
+ m_comp_methods(policy.compression()),
+ m_hostname(session.sni_hostname()),
+ m_srp_identifier(session.srp_identifier()),
+ m_next_protocol(next_protocol),
+ m_fragment_size(session.fragment_size()),
+ m_secure_renegotiation(session.secure_renegotiation()),
+ m_renegotiation_info(reneg_info),
+ m_supported_curves(policy.allowed_ecc_curves()),
+ m_supports_session_ticket(true),
+ m_session_ticket(session.session_ticket()),
+ m_supports_heartbeats(true),
+ m_peer_can_send_heartbeats(true)
+ {
+ if(!value_exists(m_suites, session.ciphersuite_code()))
+ m_suites.push_back(session.ciphersuite_code());
+
+ if(!value_exists(m_comp_methods, session.compression_method()))
+ m_comp_methods.push_back(session.compression_method());
+
+ std::vector<std::string> hashes = policy.allowed_hashes();
+ std::vector<std::string> sigs = policy.allowed_signature_methods();
+
+ for(size_t i = 0; i != hashes.size(); ++i)
+ for(size_t j = 0; j != sigs.size(); ++j)
+ m_supported_algos.push_back(std::make_pair(hashes[i], sigs[j]));
+
+ hash.update(writer.send(*this));
+ }
+
+/*
+* Read a counterparty client hello
+*/
+Client_Hello::Client_Hello(const MemoryRegion<byte>& buf, Handshake_Type type)
+ {
+ m_next_protocol = false;
+ m_secure_renegotiation = false;
+ m_supports_session_ticket = false;
+ m_supports_heartbeats = false;
+ m_peer_can_send_heartbeats = false;
+ m_fragment_size = 0;
+
+ if(type == CLIENT_HELLO)
+ deserialize(buf);
+ else
+ deserialize_sslv2(buf);
+ }
+
+/*
+* Serialize a Client Hello message
+*/
+MemoryVector<byte> Client_Hello::serialize() const
+ {
+ MemoryVector<byte> buf;
+
+ buf.push_back(m_version.major_version());
+ buf.push_back(m_version.minor_version());
+ buf += m_random;
+
+ append_tls_length_value(buf, m_session_id, 1);
+ append_tls_length_value(buf, m_suites, 2);
+ append_tls_length_value(buf, m_comp_methods, 1);
+
+ /*
+ * May not want to send extensions at all in some cases.
+ * If so, should include SCSV value (if reneg info is empty, if
+ * not we are renegotiating with a modern server and should only
+ * send that extension.
+ */
+
+ Extensions extensions;
+
+ if(m_secure_renegotiation)
+ extensions.add(new Renegotation_Extension(m_renegotiation_info));
+
+ extensions.add(new Session_Ticket(m_session_ticket));
+
+ extensions.add(new Server_Name_Indicator(m_hostname));
+ extensions.add(new SRP_Identifier(m_srp_identifier));
+
+ extensions.add(new Supported_Elliptic_Curves(m_supported_curves));
+
+ if(m_version >= Protocol_Version::TLS_V12)
+ extensions.add(new Signature_Algorithms(m_supported_algos));
+
+ extensions.add(new Heartbeat_Support_Indicator(true));
+
+ if(m_renegotiation_info.empty() && m_next_protocol)
+ extensions.add(new Next_Protocol_Notification());
+
+ buf += extensions.serialize();
+
+ return buf;
+ }
+
+void Client_Hello::deserialize_sslv2(const MemoryRegion<byte>& buf)
+ {
+ if(buf.size() < 12 || buf[0] != 1)
+ throw Decoding_Error("Client_Hello: SSLv2 hello corrupted");
+
+ const size_t cipher_spec_len = make_u16bit(buf[3], buf[4]);
+ const size_t m_session_id_len = make_u16bit(buf[5], buf[6]);
+ const size_t challenge_len = make_u16bit(buf[7], buf[8]);
+
+ const size_t expected_size =
+ (9 + m_session_id_len + cipher_spec_len + challenge_len);
+
+ if(buf.size() != expected_size)
+ throw Decoding_Error("Client_Hello: SSLv2 hello corrupted");
+
+ if(m_session_id_len != 0 || cipher_spec_len % 3 != 0 ||
+ (challenge_len < 16 || challenge_len > 32))
+ {
+ throw Decoding_Error("Client_Hello: SSLv2 hello corrupted");
+ }
+
+ for(size_t i = 9; i != 9 + cipher_spec_len; i += 3)
+ {
+ if(buf[i] != 0) // a SSLv2 cipherspec; ignore it
+ continue;
+
+ m_suites.push_back(make_u16bit(buf[i+1], buf[i+2]));
+ }
+
+ m_version = Protocol_Version(buf[1], buf[2]);
+
+ m_random.resize(challenge_len);
+ copy_mem(&m_random[0], &buf[9+cipher_spec_len+m_session_id_len], challenge_len);
+
+ m_secure_renegotiation =
+ value_exists(m_suites, static_cast<u16bit>(TLS_EMPTY_RENEGOTIATION_INFO_SCSV));
+ }
+
+/*
+* Deserialize a Client Hello message
+*/
+void Client_Hello::deserialize(const MemoryRegion<byte>& buf)
+ {
+ if(buf.size() == 0)
+ throw Decoding_Error("Client_Hello: Packet corrupted");
+
+ if(buf.size() < 41)
+ throw Decoding_Error("Client_Hello: Packet corrupted");
+
+ TLS_Data_Reader reader(buf);
+
+ const byte major_version = reader.get_byte();
+ const byte minor_version = reader.get_byte();
+
+ m_version = Protocol_Version(major_version, minor_version);
+
+ m_random = reader.get_fixed<byte>(32);
+
+ m_session_id = reader.get_range<byte>(1, 0, 32);
+
+ m_suites = reader.get_range_vector<u16bit>(2, 1, 32767);
+
+ m_comp_methods = reader.get_range_vector<byte>(1, 1, 255);
+
+ Extensions extensions(reader);
+
+ if(Server_Name_Indicator* sni = extensions.get<Server_Name_Indicator>())
+ {
+ m_hostname = sni->host_name();
+ }
+
+ if(SRP_Identifier* srp = extensions.get<SRP_Identifier>())
+ {
+ m_srp_identifier = srp->identifier();
+ }
+
+ if(Next_Protocol_Notification* npn = extensions.get<Next_Protocol_Notification>())
+ {
+ if(!npn->protocols().empty())
+ throw Decoding_Error("Client sent non-empty NPN extension");
+
+ m_next_protocol = true;
+ }
+
+ if(Maximum_Fragment_Length* frag = extensions.get<Maximum_Fragment_Length>())
+ {
+ m_fragment_size = frag->fragment_size();
+ }
+
+ if(Renegotation_Extension* reneg = extensions.get<Renegotation_Extension>())
+ {
+ // checked by Client / Server as they know the handshake state
+ m_secure_renegotiation = true;
+ m_renegotiation_info = reneg->renegotiation_info();
+ }
+
+ if(Supported_Elliptic_Curves* ecc = extensions.get<Supported_Elliptic_Curves>())
+ m_supported_curves = ecc->curves();
+
+ if(Signature_Algorithms* sigs = extensions.get<Signature_Algorithms>())
+ {
+ m_supported_algos = sigs->supported_signature_algorthms();
+ }
+ else
+ {
+ if(m_version >= Protocol_Version::TLS_V12)
+ {
+ /*
+ The rule for when a TLS 1.2 client not sending the extension
+ is strange; in theory, the server is supposed to act as if
+ the client had sent only SHA-1 using whatever signature
+ algorithm we end up negotiating. Right here, we don't know
+ what we'll end up negotiating (depends on policy), but we do
+ know that we'll only negotiate something the client sent, so
+ we can safely say it supports everything here and know that
+ we'll filter it out later.
+ */
+ m_supported_algos.push_back(std::make_pair("SHA-1", "RSA"));
+ m_supported_algos.push_back(std::make_pair("SHA-1", "DSA"));
+ m_supported_algos.push_back(std::make_pair("SHA-1", "ECDSA"));
+ }
+ else
+ {
+ // For versions before TLS 1.2, insert fake values for the old defaults
+
+ m_supported_algos.push_back(std::make_pair("TLS.Digest.0", "RSA"));
+ m_supported_algos.push_back(std::make_pair("SHA-1", "DSA"));
+ m_supported_algos.push_back(std::make_pair("SHA-1", "ECDSA"));
+ }
+ }
+
+ if(Maximum_Fragment_Length* frag = extensions.get<Maximum_Fragment_Length>())
+ {
+ m_fragment_size = frag->fragment_size();
+ }
+
+ if(Session_Ticket* ticket = extensions.get<Session_Ticket>())
+ {
+ m_supports_session_ticket = true;
+ m_session_ticket = ticket->contents();
+ }
+
+ if(Heartbeat_Support_Indicator* hb = extensions.get<Heartbeat_Support_Indicator>())
+ {
+ m_supports_heartbeats = true;
+ m_peer_can_send_heartbeats = hb->peer_allowed_to_send();
+ }
+
+ if(Renegotation_Extension* reneg = extensions.get<Renegotation_Extension>())
+ {
+ // checked by TLS_Client / TLS_Server as they know the handshake state
+ m_secure_renegotiation = true;
+ m_renegotiation_info = reneg->renegotiation_info();
+ }
+
+ if(value_exists(m_suites, static_cast<u16bit>(TLS_EMPTY_RENEGOTIATION_INFO_SCSV)))
+ {
+ /*
+ * Clients are allowed to send both the extension and the SCSV
+ * though it is not recommended. If it did, require that the
+ * extension value be empty.
+ */
+ if(m_secure_renegotiation)
+ {
+ if(!m_renegotiation_info.empty())
+ {
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Client send SCSV and non-empty extension");
+ }
+ }
+
+ m_secure_renegotiation = true;
+ m_renegotiation_info.clear();
+ }
+ }
+
+/*
+* Check if we offered this ciphersuite
+*/
+bool Client_Hello::offered_suite(u16bit ciphersuite) const
+ {
+ for(size_t i = 0; i != m_suites.size(); ++i)
+ if(m_suites[i] == ciphersuite)
+ return true;
+ return false;
+ }
+
+}
+
+}
diff --git a/src/tls/c_kex.cpp b/src/tls/c_kex.cpp
new file mode 100644
index 000000000..13925a482
--- /dev/null
+++ b/src/tls/c_kex.cpp
@@ -0,0 +1,393 @@
+/*
+* Client Key Exchange Message
+* (C) 2004-2010 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/internal/tls_extensions.h>
+#include <botan/tls_record.h>
+#include <botan/internal/assert.h>
+#include <botan/credentials_manager.h>
+#include <botan/pubkey.h>
+#include <botan/dh.h>
+#include <botan/ecdh.h>
+#include <botan/rsa.h>
+#include <botan/srp6.h>
+#include <botan/rng.h>
+#include <botan/loadstor.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+namespace {
+
+SecureVector<byte> strip_leading_zeros(const MemoryRegion<byte>& input)
+ {
+ size_t leading_zeros = 0;
+
+ for(size_t i = 0; i != input.size(); ++i)
+ {
+ if(input[i] != 0)
+ break;
+ ++leading_zeros;
+ }
+
+ SecureVector<byte> output(&input[leading_zeros],
+ input.size() - leading_zeros);
+ return output;
+ }
+
+}
+
+/*
+* Create a new Client Key Exchange message
+*/
+Client_Key_Exchange::Client_Key_Exchange(Record_Writer& writer,
+ Handshake_State* state,
+ Credentials_Manager& creds,
+ const std::vector<X509_Certificate>& peer_certs,
+ const std::string& hostname,
+ RandomNumberGenerator& rng)
+ {
+ const std::string kex_algo = state->suite.kex_algo();
+
+ if(kex_algo == "PSK")
+ {
+ std::string identity_hint = "";
+
+ if(state->server_kex)
+ {
+ TLS_Data_Reader reader(state->server_kex->params());
+ identity_hint = reader.get_string(2, 0, 65535);
+ }
+
+ const std::string hostname = state->client_hello->sni_hostname();
+
+ const std::string psk_identity = creds.psk_identity("tls-client",
+ hostname,
+ identity_hint);
+
+ append_tls_length_value(key_material, psk_identity, 2);
+
+ SymmetricKey psk = creds.psk("tls-client", hostname, psk_identity);
+
+ MemoryVector<byte> zeros(psk.length());
+
+ append_tls_length_value(pre_master, zeros, 2);
+ append_tls_length_value(pre_master, psk.bits_of(), 2);
+ }
+ else if(state->server_kex)
+ {
+ TLS_Data_Reader reader(state->server_kex->params());
+
+ SymmetricKey psk;
+
+ if(kex_algo == "DHE_PSK" || kex_algo == "ECDHE_PSK")
+ {
+ std::string identity_hint = reader.get_string(2, 0, 65535);
+
+ const std::string hostname = state->client_hello->sni_hostname();
+
+ const std::string psk_identity = creds.psk_identity("tls-client",
+ hostname,
+ identity_hint);
+
+ append_tls_length_value(key_material, psk_identity, 2);
+
+ psk = creds.psk("tls-client", hostname, psk_identity);
+ }
+
+ if(kex_algo == "DH" || kex_algo == "DHE_PSK")
+ {
+ BigInt p = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+ BigInt g = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+ BigInt Y = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+
+ if(reader.remaining_bytes())
+ throw Decoding_Error("Bad params size for DH key exchange");
+
+ DL_Group group(p, g);
+
+ if(!group.verify_group(rng, true))
+ throw Internal_Error("DH group failed validation, possible attack");
+
+ DH_PublicKey counterparty_key(group, Y);
+
+ // FIXME Check that public key is residue?
+
+ DH_PrivateKey priv_key(rng, group);
+
+ PK_Key_Agreement ka(priv_key, "Raw");
+
+ SecureVector<byte> dh_secret = strip_leading_zeros(
+ ka.derive_key(0, counterparty_key.public_value()).bits_of());
+
+ if(kex_algo == "DH")
+ pre_master = dh_secret;
+ else
+ {
+ append_tls_length_value(pre_master, dh_secret, 2);
+ append_tls_length_value(pre_master, psk.bits_of(), 2);
+ }
+
+ append_tls_length_value(key_material, priv_key.public_value(), 2);
+ }
+ else if(kex_algo == "ECDH" || kex_algo == "ECDHE_PSK")
+ {
+ const byte curve_type = reader.get_byte();
+
+ if(curve_type != 3)
+ throw Decoding_Error("Server sent non-named ECC curve");
+
+ const u16bit curve_id = reader.get_u16bit();
+
+ const std::string name = Supported_Elliptic_Curves::curve_id_to_name(curve_id);
+
+ if(name == "")
+ throw Decoding_Error("Server sent unknown named curve " + to_string(curve_id));
+
+ EC_Group group(name);
+
+ MemoryVector<byte> ecdh_key = reader.get_range<byte>(1, 1, 255);
+
+ ECDH_PublicKey counterparty_key(group, OS2ECP(ecdh_key, group.get_curve()));
+
+ ECDH_PrivateKey priv_key(rng, group);
+
+ PK_Key_Agreement ka(priv_key, "Raw");
+
+ SecureVector<byte> ecdh_secret = ka.derive_key(0, counterparty_key.public_value()).bits_of();
+
+ if(kex_algo == "ECDH")
+ pre_master = ecdh_secret;
+ else
+ {
+ append_tls_length_value(pre_master, ecdh_secret, 2);
+ append_tls_length_value(pre_master, psk.bits_of(), 2);
+ }
+
+ append_tls_length_value(key_material, priv_key.public_value(), 1);
+ }
+ else if(kex_algo == "SRP_SHA")
+ {
+ const BigInt N = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+ const BigInt g = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+ MemoryVector<byte> salt = reader.get_range<byte>(1, 1, 255);
+ const BigInt B = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+
+ const std::string srp_group = srp6_group_identifier(N, g);
+
+ const std::string srp_identifier =
+ creds.srp_identifier("tls-client", hostname);
+
+ const std::string srp_password =
+ creds.srp_password("tls-client", hostname, srp_identifier);
+
+ std::pair<BigInt, SymmetricKey> srp_vals =
+ srp6_client_agree(srp_identifier,
+ srp_password,
+ srp_group,
+ "SHA-1",
+ salt,
+ B,
+ rng);
+
+ append_tls_length_value(key_material, BigInt::encode(srp_vals.first), 2);
+ pre_master = srp_vals.second.bits_of();
+ }
+ else
+ {
+ throw Internal_Error("Client_Key_Exchange: Unknown kex " +
+ kex_algo);
+ }
+ }
+ else
+ {
+ // No server key exchange msg better mean RSA kex + RSA key in cert
+
+ if(kex_algo != "RSA")
+ throw Unexpected_Message("No server kex but negotiated kex " + kex_algo);
+
+ if(peer_certs.empty())
+ throw Internal_Error("No certificate and no server key exchange");
+
+ std::auto_ptr<Public_Key> pub_key(peer_certs[0].subject_public_key());
+
+ if(const RSA_PublicKey* rsa_pub = dynamic_cast<const RSA_PublicKey*>(pub_key.get()))
+ {
+ const Protocol_Version pref_version = state->client_hello->version();
+
+ pre_master = rng.random_vec(48);
+ pre_master[0] = pref_version.major_version();
+ pre_master[1] = pref_version.minor_version();
+
+ PK_Encryptor_EME encryptor(*rsa_pub, "PKCS1v15");
+
+ MemoryVector<byte> encrypted_key = encryptor.encrypt(pre_master, rng);
+
+ if(state->version() == Protocol_Version::SSL_V3)
+ key_material = encrypted_key; // no length field
+ else
+ append_tls_length_value(key_material, encrypted_key, 2);
+ }
+ else
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Expected a RSA key in server cert but got " +
+ pub_key->algo_name());
+ }
+
+ state->hash.update(writer.send(*this));
+ }
+
+/*
+* Read a Client Key Exchange message
+*/
+Client_Key_Exchange::Client_Key_Exchange(const MemoryRegion<byte>& contents,
+ const Handshake_State* state,
+ Credentials_Manager& creds,
+ const Policy& policy,
+ RandomNumberGenerator& rng)
+ {
+ const std::string kex_algo = state->suite.kex_algo();
+
+ if(kex_algo == "RSA")
+ {
+ BOTAN_ASSERT(state->server_certs && !state->server_certs->cert_chain().empty(),
+ "No server certificate to use for RSA");
+
+ const Private_Key* private_key = state->server_rsa_kex_key;
+
+ if(!private_key)
+ throw Internal_Error("Expected RSA kex but no server kex key set");
+
+ if(!dynamic_cast<const RSA_PrivateKey*>(private_key))
+ throw Internal_Error("Expected RSA key but got " + private_key->algo_name());
+
+ PK_Decryptor_EME decryptor(*private_key, "PKCS1v15");
+
+ Protocol_Version client_version = state->client_hello->version();
+
+ try
+ {
+ if(state->version() == Protocol_Version::SSL_V3)
+ {
+ pre_master = decryptor.decrypt(contents);
+ }
+ else
+ {
+ TLS_Data_Reader reader(contents);
+ pre_master = decryptor.decrypt(reader.get_range<byte>(2, 0, 65535));
+ }
+
+ if(pre_master.size() != 48 ||
+ client_version.major_version() != pre_master[0] ||
+ client_version.minor_version() != pre_master[1])
+ {
+ throw Decoding_Error("Client_Key_Exchange: Secret corrupted");
+ }
+ }
+ catch(...)
+ {
+ // Randomize the hide timing channel
+ pre_master = rng.random_vec(48);
+ pre_master[0] = client_version.major_version();
+ pre_master[1] = client_version.minor_version();
+ }
+ }
+ else
+ {
+ TLS_Data_Reader reader(contents);
+
+ SymmetricKey psk;
+
+ if(kex_algo == "PSK" || kex_algo == "DHE_PSK" || kex_algo == "ECDHE_PSK")
+ {
+ const std::string psk_identity = reader.get_string(2, 0, 65535);
+
+ psk = creds.psk("tls-server",
+ state->client_hello->sni_hostname(),
+ psk_identity);
+
+ if(psk.length() == 0)
+ {
+ if(policy.hide_unknown_users())
+ psk = SymmetricKey(rng, 16);
+ else
+ throw TLS_Exception(Alert::UNKNOWN_PSK_IDENTITY,
+ "No PSK for identifier " + psk_identity);
+ }
+
+ }
+
+ if(kex_algo == "PSK")
+ {
+ MemoryVector<byte> zeros(psk.length());
+ append_tls_length_value(pre_master, zeros, 2);
+ append_tls_length_value(pre_master, psk.bits_of(), 2);
+ }
+ else if(kex_algo == "SRP_SHA")
+ {
+ SRP6_Server_Session& srp = state->server_kex->server_srp_params();
+
+ pre_master = srp.step2(BigInt::decode(reader.get_range<byte>(2, 0, 65535))).bits_of();
+ }
+ else if(kex_algo == "DH" || kex_algo == "DHE_PSK" ||
+ kex_algo == "ECDH" || kex_algo == "ECDHE_PSK")
+ {
+ const Private_Key& private_key = state->server_kex->server_kex_key();
+
+ const PK_Key_Agreement_Key* ka_key =
+ dynamic_cast<const PK_Key_Agreement_Key*>(&private_key);
+
+ if(!ka_key)
+ throw Internal_Error("Expected key agreement key type but got " +
+ private_key.algo_name());
+
+ try
+ {
+ PK_Key_Agreement ka(*ka_key, "Raw");
+
+ MemoryVector<byte> client_pubkey;
+
+ if(ka_key->algo_name() == "DH")
+ client_pubkey = reader.get_range<byte>(2, 0, 65535);
+ else
+ client_pubkey = reader.get_range<byte>(1, 0, 255);
+
+ SecureVector<byte> shared_secret = ka.derive_key(0, client_pubkey).bits_of();
+
+ if(ka_key->algo_name() == "DH")
+ shared_secret = strip_leading_zeros(shared_secret);
+
+ if(kex_algo == "DHE_PSK" || kex_algo == "ECDHE_PSK")
+ {
+ append_tls_length_value(pre_master, shared_secret, 2);
+ append_tls_length_value(pre_master, psk.bits_of(), 2);
+ }
+ else
+ pre_master = shared_secret;
+ }
+ catch(std::exception &e)
+ {
+ /*
+ * Something failed in the DH computation. To avoid possible
+ * timing attacks, randomize the pre-master output and carry
+ * on, allowing the protocol to fail later in the finished
+ * checks.
+ */
+ pre_master = rng.random_vec(ka_key->public_value().size());
+ }
+ }
+ else
+ throw Internal_Error("Client_Key_Exchange: Unknown kex type " + kex_algo);
+ }
+ }
+
+}
+
+}
diff --git a/src/tls/cert_req.cpp b/src/tls/cert_req.cpp
new file mode 100644
index 000000000..1b686c1c4
--- /dev/null
+++ b/src/tls/cert_req.cpp
@@ -0,0 +1,241 @@
+/*
+* Certificate Request Message
+* (C) 2004-2006,2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/internal/tls_extensions.h>
+#include <botan/tls_record.h>
+#include <botan/der_enc.h>
+#include <botan/ber_dec.h>
+#include <botan/loadstor.h>
+
+namespace Botan {
+
+namespace TLS {
+
+namespace {
+
+std::string cert_type_code_to_name(byte code)
+ {
+ switch(code)
+ {
+ case 1:
+ return "RSA";
+ case 2:
+ return "DSA";
+ case 64:
+ return "ECDSA";
+ default:
+ return ""; // DH or something else
+ }
+ }
+
+byte cert_type_name_to_code(const std::string& name)
+ {
+ if(name == "RSA")
+ return 1;
+ if(name == "DSA")
+ return 2;
+ if(name == "ECDSA")
+ return 64;
+
+ throw Invalid_Argument("Unknown cert type " + name);
+ }
+
+}
+
+/**
+* Create a new Certificate Request message
+*/
+Certificate_Req::Certificate_Req(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const Policy& policy,
+ const std::vector<X509_Certificate>& ca_certs,
+ Protocol_Version version)
+ {
+ for(size_t i = 0; i != ca_certs.size(); ++i)
+ names.push_back(ca_certs[i].subject_dn());
+
+ cert_key_types.push_back("RSA");
+ cert_key_types.push_back("DSA");
+ cert_key_types.push_back("ECDSA");
+
+ if(version >= Protocol_Version::TLS_V12)
+ {
+ std::vector<std::string> hashes = policy.allowed_hashes();
+ std::vector<std::string> sigs = policy.allowed_signature_methods();
+
+ for(size_t i = 0; i != hashes.size(); ++i)
+ for(size_t j = 0; j != sigs.size(); ++j)
+ m_supported_algos.push_back(std::make_pair(hashes[i], sigs[j]));
+ }
+
+ hash.update(writer.send(*this));
+ }
+
+/**
+* Deserialize a Certificate Request message
+*/
+Certificate_Req::Certificate_Req(const MemoryRegion<byte>& buf,
+ Protocol_Version version)
+ {
+ if(buf.size() < 4)
+ throw Decoding_Error("Certificate_Req: Bad certificate request");
+
+ TLS_Data_Reader reader(buf);
+
+ std::vector<byte> cert_type_codes = reader.get_range_vector<byte>(1, 1, 255);
+
+ for(size_t i = 0; i != cert_type_codes.size(); ++i)
+ {
+ const std::string cert_type_name = cert_type_code_to_name(cert_type_codes[i]);
+
+ if(cert_type_name == "") // something we don't know
+ continue;
+
+ cert_key_types.push_back(cert_type_name);
+ }
+
+ if(version >= Protocol_Version::TLS_V12)
+ {
+ std::vector<byte> sig_hash_algs = reader.get_range_vector<byte>(2, 2, 65534);
+
+ if(sig_hash_algs.size() % 2 != 0)
+ throw Decoding_Error("Bad length for signature IDs in certificate request");
+
+ for(size_t i = 0; i != sig_hash_algs.size(); i += 2)
+ {
+ std::string hash = Signature_Algorithms::hash_algo_name(sig_hash_algs[i]);
+ std::string sig = Signature_Algorithms::sig_algo_name(sig_hash_algs[i+1]);
+ m_supported_algos.push_back(std::make_pair(hash, sig));
+ }
+ }
+ else
+ {
+ // The hardcoded settings from previous protocol versions
+ m_supported_algos.push_back(std::make_pair("TLS.Digest.0", "RSA"));
+ m_supported_algos.push_back(std::make_pair("SHA-1", "DSA"));
+ m_supported_algos.push_back(std::make_pair("SHA-1", "ECDSA"));
+ }
+
+ const u16bit purported_size = reader.get_u16bit();
+
+ if(reader.remaining_bytes() != purported_size)
+ throw Decoding_Error("Inconsistent length in certificate request");
+
+ while(reader.has_remaining())
+ {
+ std::vector<byte> name_bits = reader.get_range_vector<byte>(2, 0, 65535);
+
+ BER_Decoder decoder(&name_bits[0], name_bits.size());
+ X509_DN name;
+ decoder.decode(name);
+ names.push_back(name);
+ }
+ }
+
+/**
+* Serialize a Certificate Request message
+*/
+MemoryVector<byte> Certificate_Req::serialize() const
+ {
+ MemoryVector<byte> buf;
+
+ std::vector<byte> cert_types;
+
+ for(size_t i = 0; i != cert_key_types.size(); ++i)
+ cert_types.push_back(cert_type_name_to_code(cert_key_types[i]));
+
+ append_tls_length_value(buf, cert_types, 1);
+
+ if(!m_supported_algos.empty())
+ buf += Signature_Algorithms(m_supported_algos).serialize();
+
+ MemoryVector<byte> encoded_names;
+
+ for(size_t i = 0; i != names.size(); ++i)
+ {
+ DER_Encoder encoder;
+ encoder.encode(names[i]);
+
+ append_tls_length_value(encoded_names, encoder.get_contents(), 2);
+ }
+
+ append_tls_length_value(buf, encoded_names, 2);
+
+ return buf;
+ }
+
+/**
+* Create a new Certificate message
+*/
+Certificate::Certificate(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const std::vector<X509_Certificate>& cert_list) :
+ m_certs(cert_list)
+ {
+ hash.update(writer.send(*this));
+ }
+
+/**
+* Deserialize a Certificate message
+*/
+Certificate::Certificate(const MemoryRegion<byte>& buf)
+ {
+ if(buf.size() < 3)
+ throw Decoding_Error("Certificate: Message malformed");
+
+ const size_t total_size = make_u32bit(0, buf[0], buf[1], buf[2]);
+
+ if(total_size != buf.size() - 3)
+ throw Decoding_Error("Certificate: Message malformed");
+
+ const byte* certs = &buf[3];
+
+ while(certs != buf.end())
+ {
+ if(buf.end() - certs < 3)
+ throw Decoding_Error("Certificate: Message malformed");
+
+ const size_t cert_size = make_u32bit(0, certs[0], certs[1], certs[2]);
+
+ if(buf.end() - certs < (3 + cert_size))
+ throw Decoding_Error("Certificate: Message malformed");
+
+ DataSource_Memory cert_buf(&certs[3], cert_size);
+ m_certs.push_back(X509_Certificate(cert_buf));
+
+ certs += cert_size + 3;
+ }
+ }
+
+/**
+* Serialize a Certificate message
+*/
+MemoryVector<byte> Certificate::serialize() const
+ {
+ MemoryVector<byte> buf(3);
+
+ for(size_t i = 0; i != m_certs.size(); ++i)
+ {
+ MemoryVector<byte> raw_cert = m_certs[i].BER_encode();
+ const size_t cert_size = raw_cert.size();
+ for(size_t i = 0; i != 3; ++i)
+ buf.push_back(get_byte<u32bit>(i+1, cert_size));
+ buf += raw_cert;
+ }
+
+ const size_t buf_size = buf.size() - 3;
+ for(size_t i = 0; i != 3; ++i)
+ buf[i] = get_byte<u32bit>(i+1, buf_size);
+
+ return buf;
+ }
+
+}
+
+}
diff --git a/src/tls/cert_ver.cpp b/src/tls/cert_ver.cpp
new file mode 100644
index 000000000..73acf3de1
--- /dev/null
+++ b/src/tls/cert_ver.cpp
@@ -0,0 +1,117 @@
+/*
+* Certificate Verify Message
+* (C) 2004,2006,2011,2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/internal/tls_extensions.h>
+#include <botan/tls_record.h>
+#include <botan/internal/assert.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+/*
+* Create a new Certificate Verify message
+*/
+Certificate_Verify::Certificate_Verify(Record_Writer& writer,
+ Handshake_State* state,
+ RandomNumberGenerator& rng,
+ const Private_Key* priv_key)
+ {
+ BOTAN_ASSERT_NONNULL(priv_key);
+
+ std::pair<std::string, Signature_Format> format =
+ state->choose_sig_format(priv_key, hash_algo, sig_algo, true);
+
+ PK_Signer signer(*priv_key, format.first, format.second);
+
+ if(state->version() == Protocol_Version::SSL_V3)
+ {
+ SecureVector<byte> md5_sha = state->hash.final_ssl3(
+ state->keys.master_secret());
+
+ if(priv_key->algo_name() == "DSA")
+ signature = signer.sign_message(&md5_sha[16], md5_sha.size()-16, rng);
+ else
+ signature = signer.sign_message(md5_sha, rng);
+ }
+ else
+ {
+ signature = signer.sign_message(state->hash.get_contents(), rng);
+ }
+
+ state->hash.update(writer.send(*this));
+ }
+
+/*
+* Deserialize a Certificate Verify message
+*/
+Certificate_Verify::Certificate_Verify(const MemoryRegion<byte>& buf,
+ Protocol_Version version)
+ {
+ TLS_Data_Reader reader(buf);
+
+ if(version >= Protocol_Version::TLS_V12)
+ {
+ hash_algo = Signature_Algorithms::hash_algo_name(reader.get_byte());
+ sig_algo = Signature_Algorithms::sig_algo_name(reader.get_byte());
+ }
+
+ signature = reader.get_range<byte>(2, 0, 65535);
+ }
+
+/*
+* Serialize a Certificate Verify message
+*/
+MemoryVector<byte> Certificate_Verify::serialize() const
+ {
+ MemoryVector<byte> buf;
+
+ if(hash_algo != "" && sig_algo != "")
+ {
+ buf.push_back(Signature_Algorithms::hash_algo_code(hash_algo));
+ buf.push_back(Signature_Algorithms::sig_algo_code(sig_algo));
+ }
+
+ const u16bit sig_len = signature.size();
+ buf.push_back(get_byte(0, sig_len));
+ buf.push_back(get_byte(1, sig_len));
+ buf += signature;
+
+ return buf;
+ }
+
+/*
+* Verify a Certificate Verify message
+*/
+bool Certificate_Verify::verify(const X509_Certificate& cert,
+ Handshake_State* state)
+ {
+ std::auto_ptr<Public_Key> key(cert.subject_public_key());
+
+ std::pair<std::string, Signature_Format> format =
+ state->understand_sig_format(key.get(), hash_algo, sig_algo, true);
+
+ PK_Verifier verifier(*key, format.first, format.second);
+
+ if(state->version() == Protocol_Version::SSL_V3)
+ {
+ SecureVector<byte> md5_sha = state->hash.final_ssl3(
+ state->keys.master_secret());
+
+ return verifier.verify_message(&md5_sha[16], md5_sha.size()-16,
+ &signature[0], signature.size());
+ }
+
+ return verifier.verify_message(state->hash.get_contents(), signature);
+ }
+
+}
+
+}
diff --git a/src/tls/finished.cpp b/src/tls/finished.cpp
new file mode 100644
index 000000000..a494bf932
--- /dev/null
+++ b/src/tls/finished.cpp
@@ -0,0 +1,104 @@
+/*
+* Finished Message
+* (C) 2004-2006,2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/tls_record.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+namespace {
+
+/*
+* Compute the verify_data
+*/
+MemoryVector<byte> finished_compute_verify(Handshake_State* state,
+ Connection_Side side)
+ {
+ if(state->version() == Protocol_Version::SSL_V3)
+ {
+ const byte SSL_CLIENT_LABEL[] = { 0x43, 0x4C, 0x4E, 0x54 };
+ const byte SSL_SERVER_LABEL[] = { 0x53, 0x52, 0x56, 0x52 };
+
+ Handshake_Hash hash = state->hash; // don't modify state
+
+ MemoryVector<byte> ssl3_finished;
+
+ if(side == CLIENT)
+ hash.update(SSL_CLIENT_LABEL, sizeof(SSL_CLIENT_LABEL));
+ else
+ hash.update(SSL_SERVER_LABEL, sizeof(SSL_SERVER_LABEL));
+
+ return hash.final_ssl3(state->keys.master_secret());
+ }
+ else
+ {
+ const byte TLS_CLIENT_LABEL[] = {
+ 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x66, 0x69, 0x6E, 0x69,
+ 0x73, 0x68, 0x65, 0x64 };
+
+ const byte TLS_SERVER_LABEL[] = {
+ 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x66, 0x69, 0x6E, 0x69,
+ 0x73, 0x68, 0x65, 0x64 };
+
+ std::auto_ptr<KDF> prf(state->protocol_specific_prf());
+
+ MemoryVector<byte> input;
+ if(side == CLIENT)
+ input += std::make_pair(TLS_CLIENT_LABEL, sizeof(TLS_CLIENT_LABEL));
+ else
+ input += std::make_pair(TLS_SERVER_LABEL, sizeof(TLS_SERVER_LABEL));
+
+ input += state->hash.final(state->version(), state->suite.mac_algo());
+
+ return prf->derive_key(12, state->keys.master_secret(), input);
+ }
+ }
+
+}
+
+/*
+* Create a new Finished message
+*/
+Finished::Finished(Record_Writer& writer,
+ Handshake_State* state,
+ Connection_Side side)
+ {
+ verification_data = finished_compute_verify(state, side);
+ state->hash.update(writer.send(*this));
+ }
+
+/*
+* Serialize a Finished message
+*/
+MemoryVector<byte> Finished::serialize() const
+ {
+ return verification_data;
+ }
+
+/*
+* Deserialize a Finished message
+*/
+Finished::Finished(const MemoryRegion<byte>& buf)
+ {
+ verification_data = buf;
+ }
+
+/*
+* Verify a Finished message
+*/
+bool Finished::verify(Handshake_State* state,
+ Connection_Side side)
+ {
+ return (verification_data == finished_compute_verify(state, side));
+ }
+
+}
+
+}
diff --git a/src/tls/hello_verify.cpp b/src/tls/hello_verify.cpp
new file mode 100644
index 000000000..c7aae94a1
--- /dev/null
+++ b/src/tls/hello_verify.cpp
@@ -0,0 +1,61 @@
+/*
+* DTLS Hello Verify Request
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/lookup.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+Hello_Verify_Request::Hello_Verify_Request(const MemoryRegion<byte>& buf)
+ {
+ if(buf.size() < 3)
+ throw Decoding_Error("Hello verify request too small");
+
+ if(buf[0] != 254 || (buf[1] != 255 && buf[1] != 253))
+ throw Decoding_Error("Unknown version from server in hello verify request");
+
+ m_cookie.resize(buf.size() - 2);
+ copy_mem(&m_cookie[0], &buf[2], buf.size() - 2);
+ }
+
+Hello_Verify_Request::Hello_Verify_Request(const MemoryVector<byte>& client_hello_bits,
+ const std::string& client_identity,
+ const SymmetricKey& secret_key)
+ {
+ std::auto_ptr<MessageAuthenticationCode> hmac(get_mac("HMAC(SHA-256)"));
+ hmac->set_key(secret_key);
+
+ hmac->update_be(client_hello_bits.size());
+ hmac->update(client_hello_bits);
+ hmac->update_be(client_identity.size());
+ hmac->update(client_identity);
+
+ m_cookie = hmac->final();
+ }
+
+MemoryVector<byte> Hello_Verify_Request::serialize() const
+ {
+ /* DTLS 1.2 server implementations SHOULD use DTLS version 1.0
+ regardless of the version of TLS that is expected to be
+ negotiated (RFC 6347, section 4.2.1)
+ */
+
+ Protocol_Version format_version(Protocol_Version::TLS_V11);
+
+ MemoryVector<byte> bits;
+ bits.push_back(format_version.major_version());
+ bits.push_back(format_version.minor_version());
+ bits += m_cookie;
+ return bits;
+ }
+
+}
+
+}
diff --git a/src/tls/info.txt b/src/tls/info.txt
new file mode 100644
index 000000000..6b309cdfc
--- /dev/null
+++ b/src/tls/info.txt
@@ -0,0 +1,97 @@
+define TLS
+
+load_on auto
+
+<comment>
+The TLS code is complex, new, and not yet reviewed, there may be
+serious bugs or security issues.
+</comment>
+
+uses_tr1 yes
+
+<header:public>
+tls_alert.h
+tls_channel.h
+tls_ciphersuite.h
+tls_client.h
+tls_exceptn.h
+tls_magic.h
+tls_policy.h
+tls_record.h
+tls_server.h
+tls_session.h
+tls_session_manager.h
+tls_version.h
+</header:public>
+
+<header:internal>
+tls_extensions.h
+tls_handshake_hash.h
+tls_handshake_reader.h
+tls_handshake_state.h
+tls_heartbeats.h
+tls_messages.h
+tls_reader.h
+tls_session_key.h
+</header:internal>
+
+<source>
+c_hello.cpp
+c_kex.cpp
+cert_req.cpp
+cert_ver.cpp
+finished.cpp
+hello_verify.cpp
+next_protocol.cpp
+rec_read.cpp
+rec_wri.cpp
+s_hello.cpp
+s_kex.cpp
+session_ticket.cpp
+tls_alert.cpp
+tls_channel.cpp
+tls_ciphersuite.cpp
+tls_client.cpp
+tls_extensions.cpp
+tls_handshake_hash.cpp
+tls_handshake_reader.cpp
+tls_handshake_state.cpp
+tls_heartbeats.cpp
+tls_policy.cpp
+tls_server.cpp
+tls_session.cpp
+tls_session_key.cpp
+tls_session_manager.cpp
+tls_suite_info.cpp
+tls_version.cpp
+</source>
+
+<requires>
+aes
+arc4
+asn1
+camellia
+cbc
+credentials
+des
+dh
+dsa
+ecdh
+ecdsa
+eme_pkcs
+emsa3
+filters
+hmac
+kdf2
+md5
+prf_ssl3
+prf_tls
+rng
+rsa
+seed
+srp6
+sha1
+sha2_32
+ssl3mac
+x509cert
+</requires>
diff --git a/src/tls/next_protocol.cpp b/src/tls/next_protocol.cpp
new file mode 100644
index 000000000..17b77fb6e
--- /dev/null
+++ b/src/tls/next_protocol.cpp
@@ -0,0 +1,55 @@
+/*
+* Next Protocol Negotation
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_extensions.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/tls_record.h>
+
+namespace Botan {
+
+namespace TLS {
+
+Next_Protocol::Next_Protocol(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const std::string& protocol) :
+ m_protocol(protocol)
+ {
+ hash.update(writer.send(*this));
+ }
+
+Next_Protocol::Next_Protocol(const MemoryRegion<byte>& buf)
+ {
+ TLS_Data_Reader reader(buf);
+
+ m_protocol = reader.get_string(1, 0, 255);
+
+ reader.get_range_vector<byte>(1, 0, 255); // padding, ignored
+ }
+
+MemoryVector<byte> Next_Protocol::serialize() const
+ {
+ MemoryVector<byte> buf;
+
+ append_tls_length_value(buf,
+ reinterpret_cast<const byte*>(m_protocol.data()),
+ m_protocol.size(),
+ 1);
+
+ const byte padding_len = 32 - ((m_protocol.size() + 2) % 32);
+
+ buf.push_back(padding_len);
+
+ for(size_t i = 0; i != padding_len; ++i)
+ buf.push_back(0);
+
+ return buf;
+ }
+
+}
+
+}
diff --git a/src/tls/rec_read.cpp b/src/tls/rec_read.cpp
new file mode 100644
index 000000000..5d46ec1fa
--- /dev/null
+++ b/src/tls/rec_read.cpp
@@ -0,0 +1,352 @@
+/*
+* TLS Record Reading
+* (C) 2004-2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_record.h>
+#include <botan/lookup.h>
+#include <botan/loadstor.h>
+#include <botan/internal/tls_session_key.h>
+#include <botan/internal/rounding.h>
+#include <botan/internal/assert.h>
+
+namespace Botan {
+
+namespace TLS {
+
+Record_Reader::Record_Reader() :
+ m_readbuf(TLS_HEADER_SIZE + MAX_CIPHERTEXT_SIZE),
+ m_mac(0)
+ {
+ reset();
+ set_maximum_fragment_size(0);
+ }
+
+/*
+* Reset the state
+*/
+void Record_Reader::reset()
+ {
+ m_macbuf.clear();
+
+ zeroise(m_readbuf);
+ m_readbuf_pos = 0;
+
+ m_cipher.reset();
+
+ delete m_mac;
+ m_mac = 0;
+
+ m_block_size = 0;
+ m_iv_size = 0;
+ m_version = Protocol_Version();
+ m_seq_no = 0;
+ set_maximum_fragment_size(0);
+ }
+
+void Record_Reader::set_maximum_fragment_size(size_t max_fragment)
+ {
+ if(max_fragment == 0)
+ m_max_fragment = MAX_PLAINTEXT_SIZE;
+ else
+ m_max_fragment = clamp(max_fragment, 128, MAX_PLAINTEXT_SIZE);
+ }
+
+/*
+* Set the version to use
+*/
+void Record_Reader::set_version(Protocol_Version version)
+ {
+ m_version = version;
+ }
+
+/*
+* Set the keys for reading
+*/
+void Record_Reader::activate(Connection_Side side,
+ const Ciphersuite& suite,
+ const Session_Keys& keys,
+ byte compression_method)
+ {
+ m_cipher.reset();
+ delete m_mac;
+ m_mac = 0;
+ m_seq_no = 0;
+
+ if(compression_method != NO_COMPRESSION)
+ throw Internal_Error("Negotiated unknown compression algorithm");
+
+ SymmetricKey mac_key, cipher_key;
+ InitializationVector iv;
+
+ if(side == CLIENT)
+ {
+ cipher_key = keys.server_cipher_key();
+ iv = keys.server_iv();
+ mac_key = keys.server_mac_key();
+ }
+ else
+ {
+ cipher_key = keys.client_cipher_key();
+ iv = keys.client_iv();
+ mac_key = keys.client_mac_key();
+ }
+
+ const std::string cipher_algo = suite.cipher_algo();
+ const std::string mac_algo = suite.mac_algo();
+
+ if(have_block_cipher(cipher_algo))
+ {
+ m_cipher.append(get_cipher(
+ cipher_algo + "/CBC/NoPadding",
+ cipher_key, iv, DECRYPTION)
+ );
+ m_block_size = block_size_of(cipher_algo);
+
+ if(m_version >= Protocol_Version::TLS_V11)
+ m_iv_size = m_block_size;
+ else
+ m_iv_size = 0;
+ }
+ else if(have_stream_cipher(cipher_algo))
+ {
+ m_cipher.append(get_cipher(cipher_algo, cipher_key, DECRYPTION));
+ m_block_size = 0;
+ m_iv_size = 0;
+ }
+ else
+ throw Invalid_Argument("Record_Reader: Unknown cipher " + cipher_algo);
+
+ if(have_hash(mac_algo))
+ {
+ Algorithm_Factory& af = global_state().algorithm_factory();
+
+ if(m_version == Protocol_Version::SSL_V3)
+ m_mac = af.make_mac("SSL3-MAC(" + mac_algo + ")");
+ else
+ m_mac = af.make_mac("HMAC(" + mac_algo + ")");
+
+ m_mac->set_key(mac_key);
+ m_macbuf.resize(m_mac->output_length());
+ }
+ else
+ throw Invalid_Argument("Record_Reader: Unknown hash " + mac_algo);
+ }
+
+size_t Record_Reader::fill_buffer_to(const byte*& input,
+ size_t& input_size,
+ size_t& input_consumed,
+ size_t desired)
+ {
+ if(desired <= m_readbuf_pos)
+ return 0; // already have it
+
+ const size_t space_available = (m_readbuf.size() - m_readbuf_pos);
+ const size_t taken = std::min(input_size, desired - m_readbuf_pos);
+
+ if(taken > space_available)
+ throw TLS_Exception(Alert::RECORD_OVERFLOW,
+ "Record is larger than allowed maximum size");
+
+ copy_mem(&m_readbuf[m_readbuf_pos], input, taken);
+ m_readbuf_pos += taken;
+ input_consumed += taken;
+ input_size -= taken;
+ input += taken;
+
+ return (desired - m_readbuf_pos); // how many bytes do we still need?
+ }
+
+/*
+* Retrieve the next record
+*/
+size_t Record_Reader::add_input(const byte input_array[], size_t input_sz,
+ size_t& consumed,
+ byte& msg_type,
+ MemoryVector<byte>& msg)
+ {
+ const byte* input = &input_array[0];
+
+ consumed = 0;
+
+ if(m_readbuf_pos < TLS_HEADER_SIZE) // header incomplete?
+ {
+ if(size_t needed = fill_buffer_to(input, input_sz, consumed, TLS_HEADER_SIZE))
+ return needed;
+
+ BOTAN_ASSERT_EQUAL(m_readbuf_pos, TLS_HEADER_SIZE,
+ "Have an entire header");
+ }
+
+ // Possible SSLv2 format client hello
+ if((!m_mac) && (m_readbuf[0] & 0x80) && (m_readbuf[2] == 1))
+ {
+ if(m_readbuf[3] == 0 && m_readbuf[4] == 2)
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Client claims to only support SSLv2, rejecting");
+
+ if(m_readbuf[3] >= 3) // SSLv2 mapped TLS hello, then?
+ {
+ size_t record_len = make_u16bit(m_readbuf[0], m_readbuf[1]) & 0x7FFF;
+
+ if(size_t needed = fill_buffer_to(input, input_sz, consumed, record_len + 2))
+ return needed;
+
+ BOTAN_ASSERT_EQUAL(m_readbuf_pos, (record_len + 2),
+ "Have the entire SSLv2 hello");
+
+ msg_type = HANDSHAKE;
+
+ msg.resize(record_len + 4);
+
+ // Fake v3-style handshake message wrapper
+ msg[0] = CLIENT_HELLO_SSLV2;
+ msg[1] = 0;
+ msg[2] = m_readbuf[0] & 0x7F;
+ msg[3] = m_readbuf[1];
+
+ copy_mem(&msg[4], &m_readbuf[2], m_readbuf_pos - 2);
+ m_readbuf_pos = 0;
+ return 0;
+ }
+ }
+
+ if(m_readbuf[0] != CHANGE_CIPHER_SPEC &&
+ m_readbuf[0] != ALERT &&
+ m_readbuf[0] != HANDSHAKE &&
+ m_readbuf[0] != APPLICATION_DATA &&
+ m_readbuf[0] != HEARTBEAT)
+ {
+ throw Unexpected_Message(
+ "Unknown record type " + to_string(m_readbuf[0]) + " from counterparty");
+ }
+
+ const size_t record_len = make_u16bit(m_readbuf[3], m_readbuf[4]);
+
+ if(m_version.major_version())
+ {
+ if(m_readbuf[1] != m_version.major_version() ||
+ m_readbuf[2] != m_version.minor_version())
+ {
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Got unexpected version from counterparty");
+ }
+ }
+
+ if(record_len > MAX_CIPHERTEXT_SIZE)
+ throw TLS_Exception(Alert::RECORD_OVERFLOW,
+ "Got message that exceeds maximum size");
+
+ if(size_t needed = fill_buffer_to(input, input_sz, consumed,
+ TLS_HEADER_SIZE + record_len))
+ return needed;
+
+ BOTAN_ASSERT_EQUAL(static_cast<size_t>(TLS_HEADER_SIZE) + record_len,
+ m_readbuf_pos,
+ "Have the full record");
+
+ // Null mac means no encryption either, only valid during handshake
+ if(!m_mac)
+ {
+ if(m_readbuf[0] != CHANGE_CIPHER_SPEC &&
+ m_readbuf[0] != ALERT &&
+ m_readbuf[0] != HANDSHAKE)
+ {
+ throw Decoding_Error("Invalid msg type received during handshake");
+ }
+
+ msg_type = m_readbuf[0];
+ msg.resize(record_len);
+ copy_mem(&msg[0], &m_readbuf[TLS_HEADER_SIZE], record_len);
+
+ m_readbuf_pos = 0;
+ return 0; // got a full record
+ }
+
+ // Otherwise, decrypt, check MAC, return plaintext
+
+ // FIXME: avoid memory allocation by processing in place
+ m_cipher.process_msg(&m_readbuf[TLS_HEADER_SIZE], record_len);
+ size_t got_back = m_cipher.read(&m_readbuf[TLS_HEADER_SIZE], record_len, Pipe::LAST_MESSAGE);
+ BOTAN_ASSERT_EQUAL(got_back, record_len, "Cipher encrypted full amount");
+
+ BOTAN_ASSERT_EQUAL(m_cipher.remaining(Pipe::LAST_MESSAGE), 0,
+ "Cipher had no remaining inputs");
+
+ size_t pad_size = 0;
+
+ if(m_block_size)
+ {
+ byte pad_value = m_readbuf[TLS_HEADER_SIZE + (record_len-1)];
+ pad_size = pad_value + 1;
+
+ /*
+ * Check the padding; if it is wrong, then say we have 0 bytes of
+ * padding, which should ensure that the MAC check below does not
+ * succeed. This hides a timing channel.
+ *
+ * This particular countermeasure is recommended in the TLS 1.2
+ * spec (RFC 5246) in section 6.2.3.2
+ */
+ if(m_version == Protocol_Version::SSL_V3)
+ {
+ if(pad_value > m_block_size)
+ pad_size = 0;
+ }
+ else
+ {
+ bool padding_good = true;
+
+ for(size_t i = 0; i != pad_size; ++i)
+ if(m_readbuf[TLS_HEADER_SIZE + (record_len-i-1)] != pad_value)
+ padding_good = false;
+
+ if(!padding_good)
+ pad_size = 0;
+ }
+ }
+
+ const size_t mac_pad_iv_size = m_macbuf.size() + pad_size + m_iv_size;
+
+ if(record_len < mac_pad_iv_size)
+ throw Decoding_Error("Record sent with invalid length");
+
+ const u16bit plain_length = record_len - mac_pad_iv_size;
+
+ if(plain_length > m_max_fragment)
+ throw TLS_Exception(Alert::RECORD_OVERFLOW, "Plaintext record is too large");
+
+ m_mac->update_be(m_seq_no);
+ m_mac->update(m_readbuf[0]); // msg_type
+
+ if(m_version != Protocol_Version::SSL_V3)
+ {
+ m_mac->update(m_version.major_version());
+ m_mac->update(m_version.minor_version());
+ }
+
+ m_mac->update_be(plain_length);
+ m_mac->update(&m_readbuf[TLS_HEADER_SIZE + m_iv_size], plain_length);
+
+ ++m_seq_no;
+
+ m_mac->final(m_macbuf);
+
+ const size_t mac_offset = record_len - (m_macbuf.size() + pad_size);
+
+ if(!same_mem(&m_readbuf[TLS_HEADER_SIZE + mac_offset], &m_macbuf[0], m_macbuf.size()))
+ throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure");
+
+ msg_type = m_readbuf[0];
+
+ msg.resize(plain_length);
+ copy_mem(&msg[0], &m_readbuf[TLS_HEADER_SIZE + m_iv_size], plain_length);
+ m_readbuf_pos = 0;
+ return 0;
+ }
+
+}
+
+}
diff --git a/src/tls/rec_wri.cpp b/src/tls/rec_wri.cpp
new file mode 100644
index 000000000..85f178ffe
--- /dev/null
+++ b/src/tls/rec_wri.cpp
@@ -0,0 +1,317 @@
+/*
+* TLS Record Writing
+* (C) 2004-2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_record.h>
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_session_key.h>
+#include <botan/internal/tls_handshake_hash.h>
+#include <botan/lookup.h>
+#include <botan/internal/rounding.h>
+#include <botan/internal/assert.h>
+#include <botan/loadstor.h>
+#include <botan/libstate.h>
+
+namespace Botan {
+
+namespace TLS {
+
+/*
+* Record_Writer Constructor
+*/
+Record_Writer::Record_Writer(std::tr1::function<void (const byte[], size_t)> out) :
+ m_output_fn(out),
+ m_writebuf(TLS_HEADER_SIZE + MAX_CIPHERTEXT_SIZE),
+ m_mac(0)
+ {
+ reset();
+ set_maximum_fragment_size(0);
+ }
+
+void Record_Writer::set_maximum_fragment_size(size_t max_fragment)
+ {
+ if(max_fragment == 0)
+ m_max_fragment = MAX_PLAINTEXT_SIZE;
+ else
+ m_max_fragment = clamp(max_fragment, 128, MAX_PLAINTEXT_SIZE);
+ }
+
+/*
+* Reset the state
+*/
+void Record_Writer::reset()
+ {
+ set_maximum_fragment_size(0);
+ m_cipher.reset();
+
+ delete m_mac;
+ m_mac = 0;
+
+ m_version = Protocol_Version();
+ m_block_size = 0;
+ m_mac_size = 0;
+ m_iv_size = 0;
+
+ m_seq_no = 0;
+ }
+
+/*
+* Set the version to use
+*/
+void Record_Writer::set_version(Protocol_Version version)
+ {
+ m_version = version;
+ }
+
+/*
+* Set the keys for writing
+*/
+void Record_Writer::activate(Connection_Side side,
+ const Ciphersuite& suite,
+ const Session_Keys& keys,
+ byte compression_method)
+ {
+ m_cipher.reset();
+ delete m_mac;
+ m_mac = 0;
+
+ if(compression_method != NO_COMPRESSION)
+ throw Internal_Error("Negotiated unknown compression algorithm");
+
+ /*
+ RFC 4346:
+ A sequence number is incremented after each record: specifically,
+ the first record transmitted under a particular connection state
+ MUST use sequence number 0
+ */
+ m_seq_no = 0;
+
+ SymmetricKey mac_key, cipher_key;
+ InitializationVector iv;
+
+ if(side == CLIENT)
+ {
+ cipher_key = keys.client_cipher_key();
+ iv = keys.client_iv();
+ mac_key = keys.client_mac_key();
+ }
+ else
+ {
+ cipher_key = keys.server_cipher_key();
+ iv = keys.server_iv();
+ mac_key = keys.server_mac_key();
+ }
+
+ const std::string cipher_algo = suite.cipher_algo();
+ const std::string mac_algo = suite.mac_algo();
+
+ if(have_block_cipher(cipher_algo))
+ {
+ m_cipher.append(get_cipher(
+ cipher_algo + "/CBC/NoPadding",
+ cipher_key, iv, ENCRYPTION)
+ );
+ m_block_size = block_size_of(cipher_algo);
+
+ if(m_version >= Protocol_Version::TLS_V11)
+ m_iv_size = m_block_size;
+ else
+ m_iv_size = 0;
+ }
+ else if(have_stream_cipher(cipher_algo))
+ {
+ m_cipher.append(get_cipher(cipher_algo, cipher_key, ENCRYPTION));
+ m_block_size = 0;
+ m_iv_size = 0;
+ }
+ else
+ throw Invalid_Argument("Record_Writer: Unknown cipher " + cipher_algo);
+
+ if(have_hash(mac_algo))
+ {
+ Algorithm_Factory& af = global_state().algorithm_factory();
+
+ if(m_version == Protocol_Version::SSL_V3)
+ m_mac = af.make_mac("SSL3-MAC(" + mac_algo + ")");
+ else
+ m_mac = af.make_mac("HMAC(" + mac_algo + ")");
+
+ m_mac->set_key(mac_key);
+ m_mac_size = m_mac->output_length();
+ }
+ else
+ throw Invalid_Argument("Record_Writer: Unknown hash " + mac_algo);
+ }
+
+MemoryVector<byte> Record_Writer::send(Handshake_Message& msg)
+ {
+ const MemoryVector<byte> buf = msg.serialize();
+ MemoryVector<byte> send_buf(4);
+
+ const size_t buf_size = buf.size();
+
+ send_buf[0] = msg.type();
+
+ for(size_t i = 1; i != 4; ++i)
+ send_buf[i] = get_byte<u32bit>(i, buf_size);
+
+ send_buf += buf;
+
+ send(HANDSHAKE, &send_buf[0], send_buf.size());
+
+ return send_buf;
+ }
+
+/*
+* Send one or more records to the other side
+*/
+void Record_Writer::send(byte type, const byte input[], size_t length)
+ {
+ if(length == 0)
+ return;
+
+ /*
+ * If using CBC mode in SSLv3/TLS v1.0, send a single byte of
+ * plaintext to randomize the (implicit) IV of the following main
+ * block. If using a stream cipher, or TLS v1.1, this isn't
+ * necessary.
+ *
+ * An empty record also works but apparently some implementations do
+ * not like this (https://bugzilla.mozilla.org/show_bug.cgi?id=665814)
+ *
+ * See http://www.openssl.org/~bodo/tls-cbc.txt for background.
+ */
+ if((type == APPLICATION) && (m_block_size > 0) && (m_iv_size == 0))
+ {
+ send_record(type, &input[0], 1);
+ input += 1;
+ length -= 1;
+ }
+
+ while(length)
+ {
+ const size_t sending = std::min(length, m_max_fragment);
+ send_record(type, &input[0], sending);
+
+ input += sending;
+ length -= sending;
+ }
+ }
+
+/*
+* Encrypt and send the record
+*/
+void Record_Writer::send_record(byte type, const byte input[], size_t length)
+ {
+ if(length >= MAX_PLAINTEXT_SIZE)
+ throw Internal_Error("Record_Writer: Compressed packet is too big");
+
+ if(m_mac_size == 0) // initial unencrypted handshake records
+ {
+ m_writebuf[0] = type;
+ m_writebuf[1] = m_version.major_version();
+ m_writebuf[2] = m_version.minor_version();
+ m_writebuf[3] = get_byte<u16bit>(0, length);
+ m_writebuf[4] = get_byte<u16bit>(1, length);
+
+ copy_mem(&m_writebuf[TLS_HEADER_SIZE], input, length);
+
+ m_output_fn(&m_writebuf[0], TLS_HEADER_SIZE + length);
+ return;
+ }
+
+ m_mac->update_be(m_seq_no);
+ m_mac->update(type);
+
+ if(m_version != Protocol_Version::SSL_V3)
+ {
+ m_mac->update(m_version.major_version());
+ m_mac->update(m_version.minor_version());
+ }
+
+ m_mac->update(get_byte<u16bit>(0, length));
+ m_mac->update(get_byte<u16bit>(1, length));
+ m_mac->update(input, length);
+
+ const size_t buf_size = round_up(m_iv_size + length +
+ m_mac->output_length() +
+ (m_block_size ? 1 : 0),
+ m_block_size);
+
+ if(buf_size >= MAX_CIPHERTEXT_SIZE)
+ throw Internal_Error("Record_Writer: Record is too big");
+
+ BOTAN_ASSERT(m_writebuf.size() >= TLS_HEADER_SIZE + MAX_CIPHERTEXT_SIZE,
+ "Write buffer is big enough");
+
+ // TLS record header
+ m_writebuf[0] = type;
+ m_writebuf[1] = m_version.major_version();
+ m_writebuf[2] = m_version.minor_version();
+ m_writebuf[3] = get_byte<u16bit>(0, buf_size);
+ m_writebuf[4] = get_byte<u16bit>(1, buf_size);
+
+ byte* buf_write_ptr = &m_writebuf[TLS_HEADER_SIZE];
+
+ if(m_iv_size)
+ {
+ RandomNumberGenerator& rng = global_state().global_rng();
+ rng.randomize(buf_write_ptr, m_iv_size);
+ buf_write_ptr += m_iv_size;
+ }
+
+ copy_mem(buf_write_ptr, input, length);
+ buf_write_ptr += length;
+
+ m_mac->final(buf_write_ptr);
+ buf_write_ptr += m_mac->output_length();
+
+ if(m_block_size)
+ {
+ const size_t pad_val =
+ buf_size - (m_iv_size + length + m_mac->output_length() + 1);
+
+ for(size_t i = 0; i != pad_val + 1; ++i)
+ {
+ *buf_write_ptr = pad_val;
+ buf_write_ptr += 1;
+ }
+ }
+
+ // FIXME: this could be done in-place without copying
+ m_cipher.process_msg(&m_writebuf[TLS_HEADER_SIZE], buf_size);
+
+ const size_t ctext_size = m_cipher.remaining(Pipe::LAST_MESSAGE);
+
+ BOTAN_ASSERT_EQUAL(ctext_size, buf_size, "Cipher encrypted full amount");
+
+ if(ctext_size > MAX_CIPHERTEXT_SIZE)
+ throw Internal_Error("Produced ciphertext larger than protocol allows");
+
+ m_cipher.read(&m_writebuf[TLS_HEADER_SIZE], ctext_size, Pipe::LAST_MESSAGE);
+
+ BOTAN_ASSERT_EQUAL(m_cipher.remaining(Pipe::LAST_MESSAGE), 0,
+ "No data remains in pipe");
+
+ m_output_fn(&m_writebuf[0], TLS_HEADER_SIZE + buf_size);
+
+ m_seq_no++;
+ }
+
+/*
+* Send an alert
+*/
+void Record_Writer::send_alert(const Alert& alert)
+ {
+ const byte alert_bits[2] = { alert.is_fatal() ? 2 : 1,
+ alert.type() };
+
+ send(ALERT, alert_bits, sizeof(alert_bits));
+ }
+
+}
+
+}
diff --git a/src/tls/s_hello.cpp b/src/tls/s_hello.cpp
new file mode 100644
index 000000000..1244dd2d8
--- /dev/null
+++ b/src/tls/s_hello.cpp
@@ -0,0 +1,186 @@
+;/*
+* TLS Server Hello and Server Hello Done
+* (C) 2004-2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/internal/tls_session_key.h>
+#include <botan/internal/tls_extensions.h>
+#include <botan/tls_record.h>
+#include <botan/internal/stl_util.h>
+
+namespace Botan {
+
+namespace TLS {
+
+/*
+* Create a new Server Hello message
+*/
+Server_Hello::Server_Hello(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const MemoryRegion<byte>& session_id,
+ Protocol_Version ver,
+ u16bit ciphersuite,
+ byte compression,
+ size_t max_fragment_size,
+ bool client_has_secure_renegotiation,
+ const MemoryRegion<byte>& reneg_info,
+ bool offer_session_ticket,
+ bool client_has_npn,
+ const std::vector<std::string>& next_protocols,
+ bool client_has_heartbeat,
+ RandomNumberGenerator& rng) :
+ m_version(ver),
+ m_session_id(session_id),
+ m_random(make_hello_random(rng)),
+ m_ciphersuite(ciphersuite),
+ m_comp_method(compression),
+ m_fragment_size(max_fragment_size),
+ m_secure_renegotiation(client_has_secure_renegotiation),
+ m_renegotiation_info(reneg_info),
+ m_next_protocol(client_has_npn),
+ m_next_protocols(next_protocols),
+ m_supports_session_ticket(offer_session_ticket),
+ m_supports_heartbeats(client_has_heartbeat),
+ m_peer_can_send_heartbeats(true)
+ {
+ hash.update(writer.send(*this));
+ }
+
+/*
+* Deserialize a Server Hello message
+*/
+Server_Hello::Server_Hello(const MemoryRegion<byte>& buf)
+ {
+ m_secure_renegotiation = false;
+ m_supports_session_ticket = false;
+ m_next_protocol = false;
+
+ if(buf.size() < 38)
+ throw Decoding_Error("Server_Hello: Packet corrupted");
+
+ TLS_Data_Reader reader(buf);
+
+ const byte major_version = reader.get_byte();
+ const byte minor_version = reader.get_byte();
+
+ m_version = Protocol_Version(major_version, minor_version);
+
+ if(m_version != Protocol_Version::SSL_V3 &&
+ m_version != Protocol_Version::TLS_V10 &&
+ m_version != Protocol_Version::TLS_V11 &&
+ m_version != Protocol_Version::TLS_V12)
+ {
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Server_Hello: Unsupported server version");
+ }
+
+ m_random = reader.get_fixed<byte>(32);
+
+ m_session_id = reader.get_range<byte>(1, 0, 32);
+
+ m_ciphersuite = reader.get_u16bit();
+
+ m_comp_method = reader.get_byte();
+
+ Extensions extensions(reader);
+
+ if(Renegotation_Extension* reneg = extensions.get<Renegotation_Extension>())
+ {
+ // checked by Client / Server as they know the handshake state
+ m_secure_renegotiation = true;
+ m_renegotiation_info = reneg->renegotiation_info();
+ }
+
+ if(Next_Protocol_Notification* npn = extensions.get<Next_Protocol_Notification>())
+ {
+ m_next_protocols = npn->protocols();
+ m_next_protocol = true;
+ }
+
+ if(Session_Ticket* ticket = extensions.get<Session_Ticket>())
+ {
+ if(!ticket->contents().empty())
+ throw Decoding_Error("TLS server sent non-empty session ticket extension");
+ m_supports_session_ticket = true;
+ }
+
+ if(Heartbeat_Support_Indicator* hb = extensions.get<Heartbeat_Support_Indicator>())
+ {
+ m_supports_heartbeats = true;
+ m_peer_can_send_heartbeats = hb->peer_allowed_to_send();
+ }
+ }
+
+/*
+* Serialize a Server Hello message
+*/
+MemoryVector<byte> Server_Hello::serialize() const
+ {
+ MemoryVector<byte> buf;
+
+ buf.push_back(m_version.major_version());
+ buf.push_back(m_version.minor_version());
+ buf += m_random;
+
+ append_tls_length_value(buf, m_session_id, 1);
+
+ buf.push_back(get_byte(0, m_ciphersuite));
+ buf.push_back(get_byte(1, m_ciphersuite));
+
+ buf.push_back(m_comp_method);
+
+ Extensions extensions;
+
+ if(m_supports_heartbeats)
+ extensions.add(new Heartbeat_Support_Indicator(m_peer_can_send_heartbeats));
+
+ if(m_secure_renegotiation)
+ extensions.add(new Renegotation_Extension(m_renegotiation_info));
+
+ if(m_fragment_size != 0)
+ extensions.add(new Maximum_Fragment_Length(m_fragment_size));
+
+ if(m_next_protocol)
+ extensions.add(new Next_Protocol_Notification(m_next_protocols));
+
+ if(m_supports_session_ticket)
+ extensions.add(new Session_Ticket());
+
+ buf += extensions.serialize();
+
+ return buf;
+ }
+
+/*
+* Create a new Server Hello Done message
+*/
+Server_Hello_Done::Server_Hello_Done(Record_Writer& writer,
+ Handshake_Hash& hash)
+ {
+ hash.update(writer.send(*this));
+ }
+
+/*
+* Deserialize a Server Hello Done message
+*/
+Server_Hello_Done::Server_Hello_Done(const MemoryRegion<byte>& buf)
+ {
+ if(buf.size())
+ throw Decoding_Error("Server_Hello_Done: Must be empty, and is not");
+ }
+
+/*
+* Serialize a Server Hello Done message
+*/
+MemoryVector<byte> Server_Hello_Done::serialize() const
+ {
+ return MemoryVector<byte>();
+ }
+
+}
+
+}
diff --git a/src/tls/s_kex.cpp b/src/tls/s_kex.cpp
new file mode 100644
index 000000000..68a5c16db
--- /dev/null
+++ b/src/tls/s_kex.cpp
@@ -0,0 +1,292 @@
+/*
+* Server Key Exchange Message
+* (C) 2004-2010,2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/internal/tls_extensions.h>
+#include <botan/tls_record.h>
+#include <botan/internal/assert.h>
+#include <botan/credentials_manager.h>
+#include <botan/loadstor.h>
+#include <botan/pubkey.h>
+#include <botan/dh.h>
+#include <botan/ecdh.h>
+#include <botan/rsa.h>
+#include <botan/srp6.h>
+#include <botan/oids.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Create a new Server Key Exchange message
+*/
+Server_Key_Exchange::Server_Key_Exchange(Record_Writer& writer,
+ Handshake_State* state,
+ const Policy& policy,
+ Credentials_Manager& creds,
+ RandomNumberGenerator& rng,
+ const Private_Key* signing_key) :
+ m_kex_key(0), m_srp_params(0)
+ {
+ const std::string hostname = state->client_hello->sni_hostname();
+ const std::string kex_algo = state->suite.kex_algo();
+
+ if(kex_algo == "PSK" || kex_algo == "DHE_PSK" || kex_algo == "ECDHE_PSK")
+ {
+ std::string identity_hint =
+ creds.psk_identity_hint("tls-server", hostname);
+
+ append_tls_length_value(m_params, identity_hint, 2);
+ }
+
+ if(kex_algo == "DH" || kex_algo == "DHE_PSK")
+ {
+ std::auto_ptr<DH_PrivateKey> dh(new DH_PrivateKey(rng, policy.dh_group()));
+
+ append_tls_length_value(m_params, BigInt::encode(dh->get_domain().get_p()), 2);
+ append_tls_length_value(m_params, BigInt::encode(dh->get_domain().get_g()), 2);
+ append_tls_length_value(m_params, dh->public_value(), 2);
+ m_kex_key = dh.release();
+ }
+ else if(kex_algo == "ECDH" || kex_algo == "ECDHE_PSK")
+ {
+ const std::vector<std::string>& curves =
+ state->client_hello->supported_ecc_curves();
+
+ if(curves.empty())
+ throw Internal_Error("Client sent no ECC extension but we negotiated ECDH");
+
+ const std::string curve_name = policy.choose_curve(curves);
+
+ if(curve_name == "")
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Could not agree on an ECC curve with the client");
+
+ EC_Group ec_group(curve_name);
+
+ std::auto_ptr<ECDH_PrivateKey> ecdh(new ECDH_PrivateKey(rng, ec_group));
+
+ const std::string ecdh_domain_oid = ecdh->domain().get_oid();
+ const std::string domain = OIDS::lookup(OID(ecdh_domain_oid));
+
+ if(domain == "")
+ throw Internal_Error("Could not find name of ECDH domain " + ecdh_domain_oid);
+
+ const u16bit named_curve_id = Supported_Elliptic_Curves::name_to_curve_id(domain);
+
+ m_params.push_back(3); // named curve
+ m_params.push_back(get_byte(0, named_curve_id));
+ m_params.push_back(get_byte(1, named_curve_id));
+
+ append_tls_length_value(m_params, ecdh->public_value(), 1);
+
+ m_kex_key = ecdh.release();
+ }
+ else if(kex_algo == "SRP_SHA")
+ {
+ const std::string srp_identifier = state->client_hello->srp_identifier();
+
+ std::string group_id;
+ BigInt v;
+ MemoryVector<byte> salt;
+
+ const bool found = creds.srp_verifier("tls-server", hostname,
+ srp_identifier,
+ group_id, v, salt,
+ policy.hide_unknown_users());
+
+ if(!found)
+ throw TLS_Exception(Alert::UNKNOWN_PSK_IDENTITY,
+ "Unknown SRP user " + srp_identifier);
+
+ m_srp_params = new SRP6_Server_Session;
+
+ BigInt B = m_srp_params->step1(v, group_id,
+ "SHA-1", rng);
+
+ DL_Group group(group_id);
+
+ append_tls_length_value(m_params, BigInt::encode(group.get_p()), 2);
+ append_tls_length_value(m_params, BigInt::encode(group.get_g()), 2);
+ append_tls_length_value(m_params, salt, 1);
+ append_tls_length_value(m_params, BigInt::encode(B), 2);
+ }
+ else if(kex_algo != "PSK")
+ throw Internal_Error("Server_Key_Exchange: Unknown kex type " + kex_algo);
+
+ if(state->suite.sig_algo() != "")
+ {
+ BOTAN_ASSERT(signing_key, "No signing key set");
+
+ std::pair<std::string, Signature_Format> format =
+ state->choose_sig_format(signing_key, m_hash_algo, m_sig_algo, false);
+
+ PK_Signer signer(*signing_key, format.first, format.second);
+
+ signer.update(state->client_hello->random());
+ signer.update(state->server_hello->random());
+ signer.update(params());
+ m_signature = signer.signature(rng);
+ }
+
+ state->hash.update(writer.send(*this));
+ }
+
+/**
+* Deserialize a Server Key Exchange message
+*/
+Server_Key_Exchange::Server_Key_Exchange(const MemoryRegion<byte>& buf,
+ const std::string& kex_algo,
+ const std::string& sig_algo,
+ Protocol_Version version) :
+ m_kex_key(0), m_srp_params(0)
+ {
+ if(buf.size() < 6)
+ throw Decoding_Error("Server_Key_Exchange: Packet corrupted");
+
+ TLS_Data_Reader reader(buf);
+
+ /*
+ * We really are just serializing things back to what they were
+ * before, but unfortunately to know where the signature is we need
+ * to be able to parse the whole thing anyway.
+ */
+
+ if(kex_algo == "PSK" || kex_algo == "DHE_PSK" || kex_algo == "ECDHE_PSK")
+ {
+ const std::string identity_hint = reader.get_string(2, 0, 65535);
+ append_tls_length_value(m_params, identity_hint, 2);
+ }
+
+ if(kex_algo == "DH" || kex_algo == "DHE_PSK")
+ {
+ // 3 bigints, DH p, g, Y
+
+ for(size_t i = 0; i != 3; ++i)
+ {
+ BigInt v = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+ append_tls_length_value(m_params, BigInt::encode(v), 2);
+ }
+ }
+ else if(kex_algo == "ECDH" || kex_algo == "ECDHE_PSK")
+ {
+ const byte curve_type = reader.get_byte();
+
+ if(curve_type != 3)
+ throw Decoding_Error("Server_Key_Exchange: Server sent non-named ECC curve");
+
+ const u16bit curve_id = reader.get_u16bit();
+
+ const std::string name = Supported_Elliptic_Curves::curve_id_to_name(curve_id);
+
+ MemoryVector<byte> ecdh_key = reader.get_range<byte>(1, 1, 255);
+
+ if(name == "")
+ throw Decoding_Error("Server_Key_Exchange: Server sent unknown named curve " +
+ to_string(curve_id));
+
+ m_params.push_back(curve_type);
+ m_params.push_back(get_byte(0, curve_id));
+ m_params.push_back(get_byte(1, curve_id));
+ append_tls_length_value(m_params, ecdh_key, 1);
+ }
+ else if(kex_algo == "SRP_SHA")
+ {
+ // 2 bigints (N,g) then salt, then server B
+
+ const BigInt N = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+ const BigInt g = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+ MemoryVector<byte> salt = reader.get_range<byte>(1, 1, 255);
+ const BigInt B = BigInt::decode(reader.get_range<byte>(2, 1, 65535));
+
+ append_tls_length_value(m_params, BigInt::encode(N), 2);
+ append_tls_length_value(m_params, BigInt::encode(g), 2);
+ append_tls_length_value(m_params, salt, 1);
+ append_tls_length_value(m_params, BigInt::encode(B), 2);
+ }
+ else if(kex_algo != "PSK")
+ throw Decoding_Error("Server_Key_Exchange: Unsupported kex type " + kex_algo);
+
+ if(sig_algo != "")
+ {
+ if(version >= Protocol_Version::TLS_V12)
+ {
+ m_hash_algo = Signature_Algorithms::hash_algo_name(reader.get_byte());
+ m_sig_algo = Signature_Algorithms::sig_algo_name(reader.get_byte());
+ }
+
+ m_signature = reader.get_range<byte>(2, 0, 65535);
+ }
+ }
+
+Server_Key_Exchange::~Server_Key_Exchange()
+ {
+ delete m_kex_key;
+ delete m_srp_params;
+ }
+
+/**
+* Serialize a Server Key Exchange message
+*/
+MemoryVector<byte> Server_Key_Exchange::serialize() const
+ {
+ MemoryVector<byte> buf = params();
+
+ if(m_signature.size())
+ {
+ // This should be an explicit version check
+ if(m_hash_algo != "" && m_sig_algo != "")
+ {
+ buf.push_back(Signature_Algorithms::hash_algo_code(m_hash_algo));
+ buf.push_back(Signature_Algorithms::sig_algo_code(m_sig_algo));
+ }
+
+ append_tls_length_value(buf, m_signature, 2);
+ }
+
+ return buf;
+ }
+
+/**
+* Verify a Server Key Exchange message
+*/
+bool Server_Key_Exchange::verify(const X509_Certificate& cert,
+ Handshake_State* state) const
+ {
+ std::auto_ptr<Public_Key> key(cert.subject_public_key());
+
+ std::pair<std::string, Signature_Format> format =
+ state->understand_sig_format(key.get(), m_hash_algo, m_sig_algo, false);
+
+ PK_Verifier verifier(*key, format.first, format.second);
+
+ verifier.update(state->client_hello->random());
+ verifier.update(state->server_hello->random());
+ verifier.update(params());
+
+ return verifier.check_signature(m_signature);
+ }
+
+const Private_Key& Server_Key_Exchange::server_kex_key() const
+ {
+ BOTAN_ASSERT(m_kex_key, "Key is non-NULL");
+ return *m_kex_key;
+ }
+
+// Only valid for SRP negotiation
+SRP6_Server_Session& Server_Key_Exchange::server_srp_params()
+ {
+ BOTAN_ASSERT(m_srp_params, "SRP params are non-NULL");
+ return *m_srp_params;
+ }
+
+}
+
+}
diff --git a/src/tls/session_ticket.cpp b/src/tls/session_ticket.cpp
new file mode 100644
index 000000000..273996a16
--- /dev/null
+++ b/src/tls/session_ticket.cpp
@@ -0,0 +1,57 @@
+/*
+* Session Tickets
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_extensions.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/tls_record.h>
+#include <botan/loadstor.h>
+
+namespace Botan {
+
+namespace TLS {
+
+New_Session_Ticket::New_Session_Ticket(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const MemoryRegion<byte>& ticket,
+ u32bit lifetime) :
+ m_ticket_lifetime_hint(lifetime),
+ m_ticket(ticket)
+ {
+ hash.update(writer.send(*this));
+ }
+
+New_Session_Ticket::New_Session_Ticket(Record_Writer& writer,
+ Handshake_Hash& hash) :
+ m_ticket_lifetime_hint(0)
+ {
+ hash.update(writer.send(*this));
+ }
+
+New_Session_Ticket::New_Session_Ticket(const MemoryRegion<byte>& buf) :
+ m_ticket_lifetime_hint(0)
+ {
+ if(buf.size() < 6)
+ throw Decoding_Error("Session ticket message too short to be valid");
+
+ TLS_Data_Reader reader(buf);
+
+ m_ticket_lifetime_hint = reader.get_u32bit();
+ m_ticket = reader.get_range<byte>(2, 0, 65535);
+ }
+
+MemoryVector<byte> New_Session_Ticket::serialize() const
+ {
+ MemoryVector<byte> buf(4);
+ store_be(m_ticket_lifetime_hint, &buf[0]);
+ append_tls_length_value(buf, m_ticket, 2);
+ return buf;
+ }
+
+}
+
+}
diff --git a/src/tls/sessions_sqlite/info.txt b/src/tls/sessions_sqlite/info.txt
new file mode 100644
index 000000000..c5fc35952
--- /dev/null
+++ b/src/tls/sessions_sqlite/info.txt
@@ -0,0 +1,11 @@
+define TLS_SQLITE_SESSION_MANAGER
+
+load_on request
+
+<libs>
+all -> sqlite3
+</libs>
+
+<requires>
+pbkdf2
+</requires>
diff --git a/src/tls/sessions_sqlite/tls_sqlite_sess_mgr.cpp b/src/tls/sessions_sqlite/tls_sqlite_sess_mgr.cpp
new file mode 100644
index 000000000..cb831aadf
--- /dev/null
+++ b/src/tls/sessions_sqlite/tls_sqlite_sess_mgr.cpp
@@ -0,0 +1,343 @@
+/*
+* SQLite TLS Session Manager
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_sqlite_sess_mgr.h>
+#include <botan/internal/assert.h>
+#include <botan/lookup.h>
+#include <botan/hex.h>
+#include <botan/time.h>
+#include <botan/loadstor.h>
+#include <memory>
+
+#include <sqlite3.h>
+
+namespace Botan {
+
+namespace TLS {
+
+namespace {
+
+class sqlite3_statement
+ {
+ public:
+ sqlite3_statement(sqlite3* db, const std::string& base_sql)
+ {
+ int rc = sqlite3_prepare_v2(db, base_sql.c_str(), -1, &m_stmt, 0);
+
+ if(rc != SQLITE_OK)
+ throw std::runtime_error("sqlite3_prepare failed " + base_sql + ", code " + to_string(rc));
+ }
+
+ void bind(int column, const std::string& val)
+ {
+ int rc = sqlite3_bind_text(m_stmt, column, val.c_str(), -1, SQLITE_TRANSIENT);
+ if(rc != SQLITE_OK)
+ throw std::runtime_error("sqlite3_bind_text failed, code " + to_string(rc));
+ }
+
+ void bind(int column, int val)
+ {
+ int rc = sqlite3_bind_int(m_stmt, column, val);
+ if(rc != SQLITE_OK)
+ throw std::runtime_error("sqlite3_bind_int failed, code " + to_string(rc));
+ }
+
+ void bind(int column, const MemoryRegion<byte>& val)
+ {
+ int rc = sqlite3_bind_blob(m_stmt, column, &val[0], val.size(), SQLITE_TRANSIENT);
+ if(rc != SQLITE_OK)
+ throw std::runtime_error("sqlite3_bind_text failed, code " + to_string(rc));
+ }
+
+ std::pair<const byte*, size_t> get_blob(int column)
+ {
+ BOTAN_ASSERT(sqlite3_column_type(m_stmt, 0) == SQLITE_BLOB,
+ "Return value is a blob");
+
+ const void* session_blob = sqlite3_column_blob(m_stmt, column);
+ const int session_blob_size = sqlite3_column_bytes(m_stmt, column);
+
+ BOTAN_ASSERT(session_blob_size >= 0, "Blob size is non-negative");
+
+ return std::make_pair(static_cast<const byte*>(session_blob),
+ static_cast<size_t>(session_blob_size));
+ }
+
+ size_t get_size_t(int column)
+ {
+ BOTAN_ASSERT(sqlite3_column_type(m_stmt, column) == SQLITE_INTEGER,
+ "Return count is an integer");
+
+ const int sessions_int = sqlite3_column_int(m_stmt, column);
+
+ BOTAN_ASSERT(sessions_int >= 0, "Expected size_t is non-negative");
+
+ return static_cast<size_t>(sessions_int);
+ }
+
+ void spin()
+ {
+ while(sqlite3_step(m_stmt) == SQLITE_ROW)
+ {}
+ }
+
+ int step()
+ {
+ return sqlite3_step(m_stmt);
+ }
+
+ sqlite3_stmt* stmt() { return m_stmt; }
+
+ ~sqlite3_statement() { sqlite3_finalize(m_stmt); }
+ private:
+ sqlite3_stmt* m_stmt;
+ };
+
+size_t row_count(sqlite3* db, const std::string& table_name)
+ {
+ sqlite3_statement stmt(db, "select count(*) from " + table_name);
+
+ if(stmt.step() == SQLITE_ROW)
+ return stmt.get_size_t(0);
+ else
+ throw std::runtime_error("Querying size of table " + table_name + " failed");
+ }
+
+void create_table(sqlite3* db, const char* table_schema)
+ {
+ char* errmsg = 0;
+ int rc = sqlite3_exec(db, table_schema, 0, 0, &errmsg);
+
+ if(rc != SQLITE_OK)
+ {
+ const std::string err_msg = errmsg;
+ sqlite3_free(errmsg);
+ sqlite3_close(db);
+ throw std::runtime_error("sqlite3_exec for table failed - " + err_msg);
+ }
+ }
+
+
+SymmetricKey derive_key(const std::string& passphrase,
+ const byte salt[],
+ size_t salt_len,
+ size_t iterations,
+ size_t& check_val)
+ {
+ std::auto_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(SHA-512)"));
+
+ SecureVector<byte> x = pbkdf->derive_key(32 + 3,
+ passphrase,
+ salt, salt_len,
+ iterations).bits_of();
+
+ check_val = make_u32bit(0, x[0], x[1], x[2]);
+ return SymmetricKey(&x[3], x.size() - 3);
+ }
+
+}
+
+Session_Manager_SQLite::Session_Manager_SQLite(const std::string& passphrase,
+ RandomNumberGenerator& rng,
+ const std::string& db_filename,
+ size_t max_sessions,
+ u32bit session_lifetime) :
+ m_rng(rng),
+ m_max_sessions(max_sessions),
+ m_session_lifetime(session_lifetime)
+ {
+ int rc = sqlite3_open(db_filename.c_str(), &m_db);
+
+ if(rc)
+ {
+ const std::string err_msg = sqlite3_errmsg(m_db);
+ sqlite3_close(m_db);
+ throw std::runtime_error("sqlite3_open failed - " + err_msg);
+ }
+
+ create_table(m_db,
+ "create table if not exists tls_sessions "
+ "("
+ "session_id TEXT PRIMARY KEY, "
+ "session_start INTEGER, "
+ "hostname TEXT, "
+ "hostport INTEGER, "
+ "session BLOB"
+ ")");
+
+ create_table(m_db,
+ "create table if not exists tls_sessions_metadata "
+ "("
+ "passphrase_salt BLOB, "
+ "passphrase_iterations INTEGER, "
+ "passphrase_check INTEGER "
+ ")");
+
+ const size_t salts = row_count(m_db, "tls_sessions_metadata");
+
+ if(salts == 1)
+ {
+ // existing db
+ sqlite3_statement stmt(m_db, "select * from tls_sessions_metadata");
+
+ int rc = stmt.step();
+ if(rc == SQLITE_ROW)
+ {
+ std::pair<const byte*, size_t> salt = stmt.get_blob(0);
+ const size_t iterations = stmt.get_size_t(1);
+ const size_t check_val_db = stmt.get_size_t(2);
+
+ size_t check_val_created;
+ m_session_key = derive_key(passphrase,
+ salt.first,
+ salt.second,
+ iterations,
+ check_val_created);
+
+ if(check_val_created != check_val_db)
+ throw std::runtime_error("Session database password not valid");
+ }
+ }
+ else
+ {
+ // maybe just zap the salts + sessions tables in this case?
+ if(salts != 0)
+ throw std::runtime_error("Seemingly corrupted database, multiple salts found");
+
+ // new database case
+
+ MemoryVector<byte> salt = rng.random_vec(16);
+ const size_t iterations = 64 * 1024;
+ size_t check_val = 0;
+
+ m_session_key = derive_key(passphrase, &salt[0], salt.size(),
+ iterations, check_val);
+
+ sqlite3_statement stmt(m_db, "insert into tls_sessions_metadata"
+ " values(?1, ?2, ?3)");
+
+ stmt.bind(1, salt);
+ stmt.bind(2, iterations);
+ stmt.bind(3, check_val);
+
+ stmt.spin();
+ }
+ }
+
+Session_Manager_SQLite::~Session_Manager_SQLite()
+ {
+ sqlite3_close(m_db);
+ }
+
+bool Session_Manager_SQLite::load_from_session_id(const MemoryRegion<byte>& session_id,
+ Session& session)
+ {
+ sqlite3_statement stmt(m_db, "select session from tls_sessions where session_id = ?1");
+
+ stmt.bind(1, hex_encode(session_id));
+
+ int rc = stmt.step();
+
+ while(rc == SQLITE_ROW)
+ {
+ std::pair<const byte*, size_t> blob = stmt.get_blob(0);
+
+ try
+ {
+ session = Session::decrypt(blob.first, blob.second, m_session_key);
+ return true;
+ }
+ catch(...)
+ {
+ }
+
+ rc = stmt.step();
+ }
+
+ return false;
+ }
+
+bool Session_Manager_SQLite::load_from_host_info(const std::string& hostname,
+ u16bit port,
+ Session& session)
+ {
+ sqlite3_statement stmt(m_db, "select session from tls_sessions"
+ " where hostname = ?1 and hostport = ?2"
+ " order by session_start desc");
+
+ stmt.bind(1, hostname);
+ stmt.bind(2, port);
+
+ int rc = stmt.step();
+
+ while(rc == SQLITE_ROW)
+ {
+ std::pair<const byte*, size_t> blob = stmt.get_blob(0);
+
+ try
+ {
+ session = Session::decrypt(blob.first, blob.second, m_session_key);
+ return true;
+ }
+ catch(...)
+ {
+ }
+
+ rc = stmt.step();
+ }
+
+ return false;
+ }
+
+void Session_Manager_SQLite::remove_entry(const MemoryRegion<byte>& session_id)
+ {
+ sqlite3_statement stmt(m_db, "delete from tls_sessions where session_id = ?1");
+
+ stmt.bind(1, hex_encode(session_id));
+
+ stmt.spin();
+ }
+
+void Session_Manager_SQLite::save(const Session& session)
+ {
+ sqlite3_statement stmt(m_db, "insert or replace into tls_sessions"
+ " values(?1, ?2, ?3, ?4, ?5)");
+
+ stmt.bind(1, hex_encode(session.session_id()));
+ stmt.bind(2, session.start_time());
+ stmt.bind(3, session.sni_hostname());
+ stmt.bind(4, 0);
+ stmt.bind(5, session.encrypt(m_session_key, m_rng));
+
+ stmt.spin();
+
+ prune_session_cache();
+ }
+
+void Session_Manager_SQLite::prune_session_cache()
+ {
+ sqlite3_statement remove_expired(m_db, "delete from tls_sessions where session_start <= ?1");
+
+ remove_expired.bind(1, system_time() - m_session_lifetime);
+
+ remove_expired.spin();
+
+ const size_t sessions = row_count(m_db, "tls_sessions");
+
+ if(sessions > m_max_sessions)
+ {
+ sqlite3_statement remove_some(m_db, "delete from tls_sessions where session_id in "
+ "(select session_id from tls_sessions limit ?1)");
+
+ remove_some.bind(1, sessions - m_max_sessions);
+ remove_some.spin();
+ }
+ }
+
+}
+
+}
diff --git a/src/tls/sessions_sqlite/tls_sqlite_sess_mgr.h b/src/tls/sessions_sqlite/tls_sqlite_sess_mgr.h
new file mode 100644
index 000000000..57e5a58f6
--- /dev/null
+++ b/src/tls/sessions_sqlite/tls_sqlite_sess_mgr.h
@@ -0,0 +1,71 @@
+/*
+* SQLite TLS Session Manager
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_SQLITE_SESSION_MANAGER_H__
+#define BOTAN_TLS_SQLITE_SESSION_MANAGER_H__
+
+#include <botan/tls_session_manager.h>
+#include <botan/rng.h>
+
+class sqlite3;
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+*/
+class BOTAN_DLL Session_Manager_SQLite : public Session_Manager
+ {
+ public:
+ /**
+ * @param passphrase used to encrypt the session data
+ * @param db_filename filename of the SQLite database file.
+ The table names tls_sessions and tls_sessions_metadata
+ will be used
+ * @param max_sessions a hint on the maximum number of sessions
+ * to keep in memory at any one time. (If zero, don't cap)
+ * @param session_lifetime sessions are expired after this many
+ * seconds have elapsed from initial handshake.
+ */
+ Session_Manager_SQLite(const std::string& passphrase,
+ RandomNumberGenerator& rng,
+ const std::string& db_filename,
+ size_t max_sessions = 1000,
+ u32bit session_lifetime = 7200);
+
+ ~Session_Manager_SQLite();
+
+ bool load_from_session_id(const MemoryRegion<byte>& session_id,
+ Session& session);
+
+ bool load_from_host_info(const std::string& hostname, u16bit port,
+ Session& session);
+
+ void remove_entry(const MemoryRegion<byte>& session_id);
+
+ void save(const Session& session_data);
+
+ u32bit session_lifetime() const { return m_session_lifetime; }
+ private:
+ Session_Manager_SQLite(const Session_Manager_SQLite&);
+ Session_Manager_SQLite& operator=(const Session_Manager_SQLite&);
+
+ void prune_session_cache();
+
+ SymmetricKey m_session_key;
+ RandomNumberGenerator& m_rng;
+ size_t m_max_sessions;
+ u32bit m_session_lifetime;
+ class sqlite3* m_db;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_alert.cpp b/src/tls/tls_alert.cpp
new file mode 100644
index 000000000..83205bb74
--- /dev/null
+++ b/src/tls/tls_alert.cpp
@@ -0,0 +1,123 @@
+/*
+* Alert Message
+* (C) 2004-2006,2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_alert.h>
+#include <botan/exceptn.h>
+
+namespace Botan {
+
+namespace TLS {
+
+Alert::Alert(const MemoryRegion<byte>& buf)
+ {
+ if(buf.size() != 2)
+ throw Decoding_Error("Alert: Bad size " + to_string(buf.size()) +
+ " for alert message");
+
+ if(buf[0] == 1) fatal = false;
+ else if(buf[0] == 2) fatal = true;
+ else
+ throw Decoding_Error("Alert: Bad code for alert level");
+
+ const byte dc = buf[1];
+
+ /*
+ * This is allowed by the specification but is not allocated and we're
+ * using it internally as a special 'no alert' type.
+ */
+ if(dc == 255)
+ throw Internal_Error("Alert: description code 255, rejecting");
+
+ type_code = static_cast<Type>(dc);
+ }
+
+std::string Alert::type_string() const
+ {
+ switch(type())
+ {
+ case CLOSE_NOTIFY:
+ return "close_notify";
+ case UNEXPECTED_MESSAGE:
+ return "unexpected_message";
+ case BAD_RECORD_MAC:
+ return "bad_record_mac";
+ case DECRYPTION_FAILED:
+ return "decryption_failed";
+ case RECORD_OVERFLOW:
+ return "record_overflow";
+ case DECOMPRESSION_FAILURE:
+ return "decompression_failure";
+ case HANDSHAKE_FAILURE:
+ return "handshake_failure";
+ case NO_CERTIFICATE:
+ return "no_certificate";
+ case BAD_CERTIFICATE:
+ return "bad_certificate";
+ case UNSUPPORTED_CERTIFICATE:
+ return "unsupported_certificate";
+ case CERTIFICATE_REVOKED:
+ return "certificate_revoked";
+ case CERTIFICATE_EXPIRED:
+ return "certificate_expired";
+ case CERTIFICATE_UNKNOWN:
+ return "certificate_unknown";
+ case ILLEGAL_PARAMETER:
+ return "illegal_parameter";
+ case UNKNOWN_CA:
+ return "unknown_ca";
+ case ACCESS_DENIED:
+ return "access_denied";
+ case DECODE_ERROR:
+ return "decode_error";
+ case DECRYPT_ERROR:
+ return "decrypt_error";
+ case EXPORT_RESTRICTION:
+ return "export_restriction";
+ case PROTOCOL_VERSION:
+ return "protocol_version";
+ case INSUFFICIENT_SECURITY:
+ return "insufficient_security";
+ case INTERNAL_ERROR:
+ return "internal_error";
+ case USER_CANCELED:
+ return "user_canceled";
+ case NO_RENEGOTIATION:
+ return "no_renegotiation";
+
+ case UNSUPPORTED_EXTENSION:
+ return "unsupported_extension";
+ case CERTIFICATE_UNOBTAINABLE:
+ return "certificate_unobtainable";
+ case UNRECOGNIZED_NAME:
+ return "unrecognized_name";
+ case BAD_CERTIFICATE_STATUS_RESPONSE:
+ return "bad_certificate_status_response";
+ case BAD_CERTIFICATE_HASH_VALUE:
+ return "bad_certificate_hash_value";
+ case UNKNOWN_PSK_IDENTITY:
+ return "unknown_psk_identity";
+
+ case NULL_ALERT:
+ return "none";
+
+ case HEARTBEAT_PAYLOAD:
+ return "heartbeat_payload";
+ }
+
+ /*
+ * This is effectively the default case for the switch above, but we
+ * leave it out so that when an alert type is added to the enum the
+ * compiler can warn us that it is not included in the switch
+ * statement.
+ */
+ return "unrecognized_alert_" + to_string(type());
+ }
+
+
+}
+
+}
diff --git a/src/tls/tls_alert.h b/src/tls/tls_alert.h
new file mode 100644
index 000000000..3dfff3d29
--- /dev/null
+++ b/src/tls/tls_alert.h
@@ -0,0 +1,100 @@
+/*
+* Alert Message
+* (C) 2004-2006,2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_ALERT_H__
+#define BOTAN_TLS_ALERT_H__
+
+#include <botan/secmem.h>
+#include <string>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* SSL/TLS Alert Message
+*/
+class BOTAN_DLL Alert
+ {
+ public:
+ enum Type {
+ CLOSE_NOTIFY = 0,
+ UNEXPECTED_MESSAGE = 10,
+ BAD_RECORD_MAC = 20,
+ DECRYPTION_FAILED = 21,
+ RECORD_OVERFLOW = 22,
+ DECOMPRESSION_FAILURE = 30,
+ HANDSHAKE_FAILURE = 40,
+ NO_CERTIFICATE = 41, // SSLv3 only
+ BAD_CERTIFICATE = 42,
+ UNSUPPORTED_CERTIFICATE = 43,
+ CERTIFICATE_REVOKED = 44,
+ CERTIFICATE_EXPIRED = 45,
+ CERTIFICATE_UNKNOWN = 46,
+ ILLEGAL_PARAMETER = 47,
+ UNKNOWN_CA = 48,
+ ACCESS_DENIED = 49,
+ DECODE_ERROR = 50,
+ DECRYPT_ERROR = 51,
+ EXPORT_RESTRICTION = 60,
+ PROTOCOL_VERSION = 70,
+ INSUFFICIENT_SECURITY = 71,
+ INTERNAL_ERROR = 80,
+ USER_CANCELED = 90,
+ NO_RENEGOTIATION = 100,
+ UNSUPPORTED_EXTENSION = 110,
+ CERTIFICATE_UNOBTAINABLE = 111,
+ UNRECOGNIZED_NAME = 112,
+ BAD_CERTIFICATE_STATUS_RESPONSE = 113,
+ BAD_CERTIFICATE_HASH_VALUE = 114,
+ UNKNOWN_PSK_IDENTITY = 115,
+
+ NULL_ALERT = 255,
+
+ HEARTBEAT_PAYLOAD = 256
+ };
+
+ /**
+ * @return true iff this alert is non-empty
+ */
+ bool is_valid() const { return (type_code != NULL_ALERT); }
+
+ /**
+ * @return if this alert is a fatal one or not
+ */
+ bool is_fatal() const { return fatal; }
+
+ /**
+ * @return type of alert
+ */
+ Type type() const { return type_code; }
+
+ /**
+ * @return type of alert
+ */
+ std::string type_string() const;
+
+ /**
+ * Deserialize an Alert message
+ * @param buf the serialized alert
+ */
+ Alert(const MemoryRegion<byte>& buf);
+
+ Alert(Type alert_type, bool is_fatal = false) :
+ fatal(is_fatal), type_code(alert_type) {}
+
+ Alert() : fatal(false), type_code(NULL_ALERT) {}
+ private:
+ bool fatal;
+ Type type_code;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_channel.cpp b/src/tls/tls_channel.cpp
new file mode 100644
index 000000000..2464d339a
--- /dev/null
+++ b/src/tls/tls_channel.cpp
@@ -0,0 +1,325 @@
+/*
+* TLS Channels
+* (C) 2011-2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_channel.h>
+#include <botan/internal/tls_handshake_state.h>
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/tls_heartbeats.h>
+#include <botan/internal/assert.h>
+#include <botan/loadstor.h>
+
+namespace Botan {
+
+namespace TLS {
+
+Channel::Channel(std::tr1::function<void (const byte[], size_t)> socket_output_fn,
+ std::tr1::function<void (const byte[], size_t, Alert)> proc_fn,
+ std::tr1::function<bool (const Session&)> handshake_complete) :
+ proc_fn(proc_fn),
+ handshake_fn(handshake_complete),
+ writer(socket_output_fn),
+ state(0),
+ handshake_completed(false),
+ connection_closed(false),
+ m_peer_supports_heartbeats(false),
+ m_heartbeat_sending_allowed(false)
+ {
+ }
+
+Channel::~Channel()
+ {
+ delete state;
+ state = 0;
+ }
+
+size_t Channel::received_data(const byte buf[], size_t buf_size)
+ {
+ try
+ {
+ while(buf_size)
+ {
+ byte rec_type = CONNECTION_CLOSED;
+ MemoryVector<byte> record;
+ size_t consumed = 0;
+
+ const size_t needed = reader.add_input(buf, buf_size,
+ consumed,
+ rec_type, record);
+
+ buf += consumed;
+ buf_size -= consumed;
+
+ BOTAN_ASSERT(buf_size == 0 || needed == 0,
+ "Got a full record or consumed all input");
+
+ if(buf_size == 0 && needed != 0)
+ return needed; // need more data to complete record
+
+ if(rec_type == HANDSHAKE || rec_type == CHANGE_CIPHER_SPEC)
+ {
+ read_handshake(rec_type, record);
+ }
+ else if(rec_type == HEARTBEAT && m_peer_supports_heartbeats)
+ {
+ Heartbeat_Message heartbeat(record);
+
+ const MemoryRegion<byte>& payload = heartbeat.payload();
+
+ if(heartbeat.is_request() && !state)
+ {
+ Heartbeat_Message response(Heartbeat_Message::RESPONSE,
+ payload, payload.size());
+
+ writer.send(HEARTBEAT, response.contents());
+ }
+ else
+ {
+ // pass up to the application
+ proc_fn(&payload[0], payload.size(), Alert(Alert::HEARTBEAT_PAYLOAD));
+ }
+ }
+ else if(rec_type == APPLICATION_DATA)
+ {
+ if(handshake_completed)
+ {
+ /*
+ * OpenSSL among others sends empty records in versions
+ * before TLS v1.1 in order to randomize the IV of the
+ * following record. Avoid spurious callbacks.
+ */
+ if(record.size() > 0)
+ proc_fn(&record[0], record.size(), Alert());
+ }
+ else
+ {
+ throw Unexpected_Message("Application data before handshake done");
+ }
+ }
+ else if(rec_type == ALERT)
+ {
+ Alert alert_msg(record);
+
+ alert_notify(alert_msg);
+
+ proc_fn(0, 0, alert_msg);
+
+ if(alert_msg.type() == Alert::CLOSE_NOTIFY)
+ {
+ if(connection_closed)
+ reader.reset();
+ else
+ send_alert(Alert(Alert::CLOSE_NOTIFY)); // reply in kind
+ }
+ else if(alert_msg.is_fatal())
+ {
+ // delete state immediately
+ connection_closed = true;
+
+ delete state;
+ state = 0;
+
+ writer.reset();
+ reader.reset();
+ }
+ }
+ else
+ throw Unexpected_Message("Unknown TLS message type " +
+ to_string(rec_type) + " received");
+ }
+
+ return 0; // on a record boundary
+ }
+ catch(TLS_Exception& e)
+ {
+ send_alert(Alert(e.type(), true));
+ throw;
+ }
+ catch(Decoding_Error& e)
+ {
+ send_alert(Alert(Alert::DECODE_ERROR, true));
+ throw;
+ }
+ catch(Internal_Error& e)
+ {
+ send_alert(Alert(Alert::INTERNAL_ERROR, true));
+ throw;
+ }
+ catch(std::exception& e)
+ {
+ send_alert(Alert(Alert::INTERNAL_ERROR, true));
+ throw;
+ }
+ }
+
+/*
+* Split up and process handshake messages
+*/
+void Channel::read_handshake(byte rec_type,
+ const MemoryRegion<byte>& rec_buf)
+ {
+ if(rec_type == HANDSHAKE)
+ {
+ if(!state)
+ state = new Handshake_State(new Stream_Handshake_Reader);
+ state->handshake_reader()->add_input(&rec_buf[0], rec_buf.size());
+ }
+
+ BOTAN_ASSERT(state, "Handshake message recieved without state in place");
+
+ while(true)
+ {
+ Handshake_Type type = HANDSHAKE_NONE;
+
+ if(rec_type == HANDSHAKE)
+ {
+ if(state->handshake_reader()->have_full_record())
+ {
+ std::pair<Handshake_Type, MemoryVector<byte> > msg =
+ state->handshake_reader()->get_next_record();
+ process_handshake_msg(msg.first, msg.second);
+ }
+ else
+ break;
+ }
+ else if(rec_type == CHANGE_CIPHER_SPEC)
+ {
+ if(state->handshake_reader()->empty() && rec_buf.size() == 1 && rec_buf[0] == 1)
+ process_handshake_msg(HANDSHAKE_CCS, MemoryVector<byte>());
+ else
+ throw Decoding_Error("Malformed ChangeCipherSpec message");
+ }
+ else
+ throw Decoding_Error("Unknown message type in handshake processing");
+
+ if(type == HANDSHAKE_CCS || !state || !state->handshake_reader()->have_full_record())
+ break;
+ }
+ }
+
+void Channel::heartbeat(const byte payload[], size_t payload_size)
+ {
+ if(!is_active())
+ throw std::runtime_error("Heartbeat cannot be sent on inactive TLS connection");
+
+ if(m_heartbeat_sending_allowed)
+ {
+ Heartbeat_Message heartbeat(Heartbeat_Message::REQUEST,
+ payload, payload_size);
+
+ writer.send(HEARTBEAT, heartbeat.contents());
+ }
+ }
+
+void Channel::send(const byte buf[], size_t buf_size)
+ {
+ if(!is_active())
+ throw std::runtime_error("Data cannot be sent on inactive TLS connection");
+
+ writer.send(APPLICATION_DATA, buf, buf_size);
+ }
+
+void Channel::send_alert(const Alert& alert)
+ {
+ if(alert.is_valid() && !connection_closed)
+ {
+ try
+ {
+ writer.send_alert(alert);
+ }
+ catch(...) { /* swallow it */ }
+ }
+
+ if(!connection_closed && (alert.type() == Alert::CLOSE_NOTIFY || alert.is_fatal()))
+ {
+ connection_closed = true;
+
+ delete state;
+ state = 0;
+
+ writer.reset();
+ }
+ }
+
+void Channel::Secure_Renegotiation_State::update(Client_Hello* client_hello)
+ {
+ if(initial_handshake)
+ {
+ secure_renegotiation = client_hello->secure_renegotiation();
+ }
+ else
+ {
+ if(secure_renegotiation != client_hello->secure_renegotiation())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Client changed its mind about secure renegotiation");
+ }
+
+ if(client_hello->secure_renegotiation())
+ {
+ const MemoryVector<byte>& data = client_hello->renegotiation_info();
+
+ if(initial_handshake)
+ {
+ if(!data.empty())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Client sent renegotiation data on initial handshake");
+ }
+ else
+ {
+ if(data != for_client_hello())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Client sent bad renegotiation data");
+ }
+ }
+ }
+
+void Channel::Secure_Renegotiation_State::update(Server_Hello* server_hello)
+ {
+ if(initial_handshake)
+ {
+ /* If the client offered but server rejected, then this toggles
+ * secure_renegotiation to off
+ */
+ secure_renegotiation = server_hello->secure_renegotiation();
+ }
+ else
+ {
+ if(secure_renegotiation != server_hello->secure_renegotiation())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Server changed its mind about secure renegotiation");
+ }
+
+ if(secure_renegotiation)
+ {
+ const MemoryVector<byte>& data = server_hello->renegotiation_info();
+
+ if(initial_handshake)
+ {
+ if(!data.empty())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Server sent renegotiation data on initial handshake");
+ }
+ else
+ {
+ if(data != for_server_hello())
+ throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+ "Server sent bad renegotiation data");
+ }
+ }
+
+ initial_handshake = false;
+ }
+
+void Channel::Secure_Renegotiation_State::update(Finished* client_finished,
+ Finished* server_finished)
+ {
+ client_verify = client_finished->verify_data();
+ server_verify = server_finished->verify_data();
+ }
+
+}
+
+}
diff --git a/src/tls/tls_channel.h b/src/tls/tls_channel.h
new file mode 100644
index 000000000..257745d80
--- /dev/null
+++ b/src/tls/tls_channel.h
@@ -0,0 +1,155 @@
+/*
+* TLS Channel
+* (C) 2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_CHANNEL_H__
+#define BOTAN_TLS_CHANNEL_H__
+
+#include <botan/tls_policy.h>
+#include <botan/tls_record.h>
+#include <botan/tls_session.h>
+#include <botan/tls_alert.h>
+#include <botan/x509cert.h>
+#include <vector>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Generic interface for TLS endpoint
+*/
+class BOTAN_DLL Channel
+ {
+ public:
+ /**
+ * Inject TLS traffic received from counterparty
+ * @return a hint as the how many more bytes we need to process the
+ * current record (this may be 0 if on a record boundary)
+ */
+ virtual size_t received_data(const byte buf[], size_t buf_size);
+
+ /**
+ * Inject plaintext intended for counterparty
+ */
+ virtual void send(const byte buf[], size_t buf_size);
+
+ /**
+ * Send a close notification alert
+ */
+ void close() { send_alert(Alert(Alert::CLOSE_NOTIFY)); }
+
+ /**
+ * @return true iff the connection is active for sending application data
+ */
+ bool is_active() const { return handshake_completed && !is_closed(); }
+
+ /**
+ * @return true iff the connection has been definitely closed
+ */
+ bool is_closed() const { return connection_closed; }
+
+ /**
+ * Attempt to renegotiate the session
+ * @param force_full_renegotiation if true, require a full renegotiation,
+ * otherwise allow session resumption
+ */
+ virtual void renegotiate(bool force_full_renegotiation) = 0;
+
+ /**
+ * Attempt to send a heartbeat message (if negotiated with counterparty)
+ * @param payload will be echoed back
+ * @param countents_size size of payload in bytes
+ */
+ void heartbeat(const byte payload[], size_t payload_size);
+
+ /**
+ * Attempt to send a heartbeat message (if negotiated with counterparty)
+ */
+ void heartbeat() { heartbeat(0, 0); }
+
+ /**
+ * @return certificate chain of the peer (may be empty)
+ */
+ std::vector<X509_Certificate> peer_cert_chain() const { return peer_certs; }
+
+ Channel(std::tr1::function<void (const byte[], size_t)> socket_output_fn,
+ std::tr1::function<void (const byte[], size_t, Alert)> proc_fn,
+ std::tr1::function<bool (const Session&)> handshake_complete);
+
+ virtual ~Channel();
+ protected:
+
+ /**
+ * Send a TLS alert message. If the alert is fatal, the
+ * internal state (keys, etc) will be reset
+ * @param level is warning or fatal
+ * @param type is the type of alert
+ */
+ void send_alert(const Alert& alert);
+
+ virtual void read_handshake(byte rec_type,
+ const MemoryRegion<byte>& rec_buf);
+
+ virtual void process_handshake_msg(Handshake_Type type,
+ const MemoryRegion<byte>& contents) = 0;
+
+ virtual void alert_notify(const Alert& alert) = 0;
+
+ std::tr1::function<void (const byte[], size_t, Alert)> proc_fn;
+ std::tr1::function<bool (const Session&)> handshake_fn;
+
+ Record_Writer writer;
+ Record_Reader reader;
+
+ std::vector<X509_Certificate> peer_certs;
+
+ class Handshake_State* state;
+
+ class Secure_Renegotiation_State
+ {
+ public:
+ Secure_Renegotiation_State() : initial_handshake(true),
+ secure_renegotiation(false)
+ {}
+
+ void update(class Client_Hello* client_hello);
+ void update(class Server_Hello* server_hello);
+
+ void update(class Finished* client_finished,
+ class Finished* server_finished);
+
+ const MemoryVector<byte>& for_client_hello() const
+ { return client_verify; }
+
+ MemoryVector<byte> for_server_hello() const
+ {
+ MemoryVector<byte> buf = client_verify;
+ buf += server_verify;
+ return buf;
+ }
+
+ bool supported() const { return secure_renegotiation; }
+ bool renegotiation() const { return !initial_handshake; }
+ private:
+ bool initial_handshake;
+ bool secure_renegotiation;
+ MemoryVector<byte> client_verify, server_verify;
+ };
+
+ Secure_Renegotiation_State secure_renegotiation;
+
+ bool handshake_completed;
+ bool connection_closed;
+ bool m_peer_supports_heartbeats;
+ bool m_heartbeat_sending_allowed;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_ciphersuite.cpp b/src/tls/tls_ciphersuite.cpp
new file mode 100644
index 000000000..afe0e68ee
--- /dev/null
+++ b/src/tls/tls_ciphersuite.cpp
@@ -0,0 +1,103 @@
+/*
+* TLS Cipher Suite
+* (C) 2004-2010,2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_ciphersuite.h>
+#include <botan/parsing.h>
+#include <sstream>
+#include <stdexcept>
+
+namespace Botan {
+
+namespace TLS {
+
+Ciphersuite Ciphersuite::by_name(const std::string& name)
+ {
+ for(size_t i = 0; i != 65536; ++i)
+ {
+ Ciphersuite suite = Ciphersuite::by_id(i);
+
+ if(!suite.valid())
+ continue; // not a ciphersuite we know, skip
+
+ if(suite.to_string() == name)
+ return suite;
+ }
+
+ return Ciphersuite(); // some unknown ciphersuite
+ }
+
+bool Ciphersuite::psk_ciphersuite() const
+ {
+ return (kex_algo() == "PSK" ||
+ kex_algo() == "DHE_PSK" ||
+ kex_algo() == "ECDHE_PSK");
+ }
+
+bool Ciphersuite::ecc_ciphersuite() const
+ {
+ return (kex_algo() == "ECDH" || sig_algo() == "ECDSA");
+ }
+
+std::string Ciphersuite::to_string() const
+ {
+ if(m_cipher_keylen == 0)
+ throw std::runtime_error("Ciphersuite::to_string - no value set");
+
+ std::ostringstream out;
+
+ out << "TLS_";
+
+ if(kex_algo() != "RSA")
+ {
+ if(kex_algo() == "DH")
+ out << "DHE";
+ else if(kex_algo() == "ECDH")
+ out << "ECDHE";
+ else
+ out << kex_algo();
+
+ out << '_';
+ }
+
+ if(sig_algo() == "DSA")
+ out << "DSS_";
+ else if(sig_algo() != "")
+ out << sig_algo() << '_';
+
+ out << "WITH_";
+
+ if(cipher_algo() == "ARC4")
+ {
+ out << "RC4_128_";
+ }
+ else
+ {
+ if(cipher_algo() == "3DES")
+ out << "3DES_EDE";
+ else if(cipher_algo() == "Camellia")
+ out << "CAMELLIA_" << Botan::to_string(8*cipher_keylen());
+ else
+ out << replace_char(cipher_algo(), '-', '_');
+
+ out << "_CBC_";
+ }
+
+ if(mac_algo() == "SHA-1")
+ out << "SHA";
+ else if(mac_algo() == "SHA-256")
+ out << "SHA256";
+ else if(mac_algo() == "SHA-384")
+ out << "SHA384";
+ else
+ out << mac_algo();
+
+ return out.str();
+ }
+
+}
+
+}
diff --git a/src/tls/tls_ciphersuite.h b/src/tls/tls_ciphersuite.h
new file mode 100644
index 000000000..dcb4b6a6f
--- /dev/null
+++ b/src/tls/tls_ciphersuite.h
@@ -0,0 +1,73 @@
+/*
+* TLS Cipher Suites
+* (C) 2004-2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_CIPHER_SUITES_H__
+#define BOTAN_TLS_CIPHER_SUITES_H__
+
+#include <botan/types.h>
+#include <string>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Ciphersuite Information
+*/
+class BOTAN_DLL Ciphersuite
+ {
+ public:
+ /**
+ * Convert an SSL/TLS ciphersuite to algorithm fields
+ */
+ static Ciphersuite by_id(u16bit suite);
+
+ static Ciphersuite by_name(const std::string& name);
+
+ /**
+ * Formats the ciphersuite back to an RFC-style ciphersuite string
+ */
+ std::string to_string() const;
+
+ bool psk_ciphersuite() const;
+ bool ecc_ciphersuite() const;
+
+ std::string kex_algo() const { return m_kex_algo; }
+ std::string sig_algo() const { return m_sig_algo; }
+
+ std::string cipher_algo() const { return m_cipher_algo; }
+ std::string mac_algo() const { return m_mac_algo; }
+
+ size_t cipher_keylen() const { return m_cipher_keylen; }
+
+ bool valid() const { return (m_cipher_keylen > 0); }
+
+ Ciphersuite() : m_cipher_keylen(0) {}
+
+ Ciphersuite(const std::string& sig_algo,
+ const std::string& kex_algo,
+ const std::string& mac_algo,
+ const std::string& cipher_algo,
+ size_t cipher_algo_keylen) :
+ m_sig_algo(sig_algo),
+ m_kex_algo(kex_algo),
+ m_mac_algo(mac_algo),
+ m_cipher_algo(cipher_algo),
+ m_cipher_keylen(cipher_algo_keylen)
+ {
+ }
+
+ private:
+ std::string m_sig_algo, m_kex_algo, m_mac_algo, m_cipher_algo;
+ size_t m_cipher_keylen;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_client.cpp b/src/tls/tls_client.cpp
new file mode 100644
index 000000000..63d0ee148
--- /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::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::string& hostname,
+ std::tr1::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 = 0;
+ }
+ }
+ }
+
+/*
+* Process a handshake message
+*/
+void Client::process_handshake_msg(Handshake_Type type,
+ const MemoryRegion<byte>& contents)
+ {
+ if(state == 0)
+ 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 = 0;
+
+ // 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::auto_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);
+
+ MemoryVector<byte> session_id = state->server_hello->session_id();
+
+ const MemoryRegion<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 = 0;
+ handshake_completed = true;
+ }
+ else
+ throw Unexpected_Message("Unknown handshake message received");
+ }
+
+}
+
+}
diff --git a/src/tls/tls_client.h b/src/tls/tls_client.h
new file mode 100644
index 000000000..4efe2a2df
--- /dev/null
+++ b/src/tls/tls_client.h
@@ -0,0 +1,74 @@
+/*
+* TLS Client
+* (C) 2004-2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_CLIENT_H__
+#define BOTAN_TLS_CLIENT_H__
+
+#include <botan/tls_channel.h>
+#include <botan/tls_session_manager.h>
+#include <botan/credentials_manager.h>
+#include <vector>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* SSL/TLS Client
+*/
+class BOTAN_DLL Client : public Channel
+ {
+ public:
+ /**
+ * Set up a new TLS client session
+ * @param socket_output_fn is called with data for the outbound socket
+ * @param proc_fn is called when new data (application or alerts) is received
+ * @param handshake_complete is called when a handshake is completed
+ * @param session_manager manages session state
+ * @param creds manages application/user credentials
+ * @param policy specifies other connection policy information
+ * @param rng a random number generator
+ * @param servername the server's DNS name, if known
+ * @param next_protocol allows the client to specify what the next
+ * protocol will be. For more information read
+ * http://technotes.googlecode.com/git/nextprotoneg.html.
+ *
+ * If the function is not empty, NPN will be negotiated
+ * and if the server supports NPN the function will be
+ * called with the list of protocols the server advertised;
+ * the client should return the protocol it would like to use.
+ */
+ Client(std::tr1::function<void (const byte[], size_t)> socket_output_fn,
+ std::tr1::function<void (const byte[], size_t, Alert)> proc_fn,
+ std::tr1::function<bool (const Session&)> handshake_complete,
+ Session_Manager& session_manager,
+ Credentials_Manager& creds,
+ const Policy& policy,
+ RandomNumberGenerator& rng,
+ const std::string& servername = "",
+ std::tr1::function<std::string (std::vector<std::string>)> next_protocol =
+ std::tr1::function<std::string (std::vector<std::string>)>());
+
+ void renegotiate(bool force_full_renegotiation);
+ private:
+ void process_handshake_msg(Handshake_Type type,
+ const MemoryRegion<byte>& contents);
+
+ void alert_notify(const Alert& alert);
+
+ const Policy& policy;
+ RandomNumberGenerator& rng;
+ Session_Manager& session_manager;
+ Credentials_Manager& creds;
+ const std::string m_hostname;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_exceptn.h b/src/tls/tls_exceptn.h
new file mode 100644
index 000000000..ad19c6c9d
--- /dev/null
+++ b/src/tls/tls_exceptn.h
@@ -0,0 +1,47 @@
+/*
+* Exceptions
+* (C) 2004-2006 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_EXCEPTION_H__
+#define BOTAN_TLS_EXCEPTION_H__
+
+#include <botan/exceptn.h>
+#include <botan/tls_alert.h>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Exception Base Class
+*/
+class BOTAN_DLL TLS_Exception : public Exception
+ {
+ public:
+ Alert::Type type() const throw() { return alert_type; }
+
+ TLS_Exception(Alert::Type type,
+ const std::string& err_msg = "Unknown error") :
+ Exception(err_msg), alert_type(type) {}
+
+ private:
+ Alert::Type alert_type;
+ };
+
+/**
+* Unexpected_Message Exception
+*/
+struct BOTAN_DLL Unexpected_Message : public TLS_Exception
+ {
+ Unexpected_Message(const std::string& err) :
+ TLS_Exception(Alert::UNEXPECTED_MESSAGE, err) {}
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_extensions.cpp b/src/tls/tls_extensions.cpp
new file mode 100644
index 000000000..f1361bbb9
--- /dev/null
+++ b/src/tls/tls_extensions.cpp
@@ -0,0 +1,523 @@
+/*
+* TLS Extensions
+* (C) 2011,2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_extensions.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/tls_exceptn.h>
+
+namespace Botan {
+
+namespace TLS {
+
+namespace {
+
+Extension* make_extension(TLS_Data_Reader& reader,
+ u16bit code,
+ u16bit size)
+ {
+ switch(code)
+ {
+ case TLSEXT_SERVER_NAME_INDICATION:
+ return new Server_Name_Indicator(reader, size);
+
+ case TLSEXT_MAX_FRAGMENT_LENGTH:
+ return new Maximum_Fragment_Length(reader, size);
+
+ case TLSEXT_SRP_IDENTIFIER:
+ return new SRP_Identifier(reader, size);
+
+ case TLSEXT_USABLE_ELLIPTIC_CURVES:
+ return new Supported_Elliptic_Curves(reader, size);
+
+ case TLSEXT_SAFE_RENEGOTIATION:
+ return new Renegotation_Extension(reader, size);
+
+ case TLSEXT_SIGNATURE_ALGORITHMS:
+ return new Signature_Algorithms(reader, size);
+
+ case TLSEXT_NEXT_PROTOCOL:
+ return new Next_Protocol_Notification(reader, size);
+
+ case TLSEXT_HEARTBEAT_SUPPORT:
+ return new Heartbeat_Support_Indicator(reader, size);
+
+ case TLSEXT_SESSION_TICKET:
+ return new Session_Ticket(reader, size);
+
+ default:
+ return 0; // not known
+ }
+ }
+
+}
+
+Extensions::Extensions(TLS_Data_Reader& reader)
+ {
+ if(reader.has_remaining())
+ {
+ const u16bit all_extn_size = reader.get_u16bit();
+
+ if(reader.remaining_bytes() != all_extn_size)
+ throw Decoding_Error("Bad extension size");
+
+ while(reader.has_remaining())
+ {
+ const u16bit extension_code = reader.get_u16bit();
+ const u16bit extension_size = reader.get_u16bit();
+
+ Extension* extn = make_extension(reader,
+ extension_code,
+ extension_size);
+
+ if(extn)
+ this->add(extn);
+ else // unknown/unhandled extension
+ reader.discard_next(extension_size);
+ }
+ }
+ }
+
+MemoryVector<byte> Extensions::serialize() const
+ {
+ MemoryVector<byte> buf(2); // 2 bytes for length field
+
+ for(std::map<Handshake_Extension_Type, Extension*>::const_iterator i = extensions.begin();
+ i != extensions.end(); ++i)
+ {
+ if(i->second->empty())
+ continue;
+
+ const u16bit extn_code = i->second->type();
+
+ MemoryVector<byte> extn_val = i->second->serialize();
+
+ buf.push_back(get_byte(0, extn_code));
+ buf.push_back(get_byte(1, extn_code));
+
+ buf.push_back(get_byte<u16bit>(0, extn_val.size()));
+ buf.push_back(get_byte<u16bit>(1, extn_val.size()));
+
+ buf += extn_val;
+ }
+
+ const u16bit extn_size = buf.size() - 2;
+
+ buf[0] = get_byte(0, extn_size);
+ buf[1] = get_byte(1, extn_size);
+
+ // avoid sending a completely empty extensions block
+ if(buf.size() == 2)
+ return MemoryVector<byte>();
+
+ return buf;
+ }
+
+Extensions::~Extensions()
+ {
+ for(std::map<Handshake_Extension_Type, Extension*>::const_iterator i = extensions.begin();
+ i != extensions.end(); ++i)
+ {
+ delete i->second;
+ }
+
+ extensions.clear();
+ }
+
+Server_Name_Indicator::Server_Name_Indicator(TLS_Data_Reader& reader,
+ u16bit extension_size)
+ {
+ /*
+ * This is used by the server to confirm that it knew the name
+ */
+ if(extension_size == 0)
+ return;
+
+ u16bit name_bytes = reader.get_u16bit();
+
+ if(name_bytes + 2 != extension_size)
+ throw Decoding_Error("Bad encoding of SNI extension");
+
+ while(name_bytes)
+ {
+ byte name_type = reader.get_byte();
+ name_bytes--;
+
+ if(name_type == 0) // DNS
+ {
+ sni_host_name = reader.get_string(2, 1, 65535);
+ name_bytes -= (2 + sni_host_name.size());
+ }
+ else // some other unknown name type
+ {
+ reader.discard_next(name_bytes);
+ name_bytes = 0;
+ }
+ }
+ }
+
+MemoryVector<byte> Server_Name_Indicator::serialize() const
+ {
+ MemoryVector<byte> buf;
+
+ size_t name_len = sni_host_name.size();
+
+ buf.push_back(get_byte<u16bit>(0, name_len+3));
+ buf.push_back(get_byte<u16bit>(1, name_len+3));
+ buf.push_back(0); // DNS
+
+ buf.push_back(get_byte<u16bit>(0, name_len));
+ buf.push_back(get_byte<u16bit>(1, name_len));
+
+ buf += std::make_pair(
+ reinterpret_cast<const byte*>(sni_host_name.data()),
+ sni_host_name.size());
+
+ return buf;
+ }
+
+SRP_Identifier::SRP_Identifier(TLS_Data_Reader& reader,
+ u16bit extension_size)
+ {
+ srp_identifier = reader.get_string(1, 1, 255);
+
+ if(srp_identifier.size() + 1 != extension_size)
+ throw Decoding_Error("Bad encoding for SRP identifier extension");
+ }
+
+MemoryVector<byte> SRP_Identifier::serialize() const
+ {
+ MemoryVector<byte> buf;
+
+ const byte* srp_bytes =
+ reinterpret_cast<const byte*>(srp_identifier.data());
+
+ append_tls_length_value(buf, srp_bytes, srp_identifier.size(), 1);
+
+ return buf;
+ }
+
+Renegotation_Extension::Renegotation_Extension(TLS_Data_Reader& reader,
+ u16bit extension_size)
+ {
+ reneg_data = reader.get_range<byte>(1, 0, 255);
+
+ if(reneg_data.size() + 1 != extension_size)
+ throw Decoding_Error("Bad encoding for secure renegotiation extn");
+ }
+
+MemoryVector<byte> Renegotation_Extension::serialize() const
+ {
+ MemoryVector<byte> buf;
+ append_tls_length_value(buf, reneg_data, 1);
+ return buf;
+ }
+
+size_t Maximum_Fragment_Length::fragment_size() const
+ {
+ switch(val)
+ {
+ case 1:
+ return 512;
+ case 2:
+ return 1024;
+ case 3:
+ return 2048;
+ case 4:
+ return 4096;
+ default:
+ throw TLS_Exception(Alert::ILLEGAL_PARAMETER,
+ "Bad value in maximum fragment extension");
+ }
+ }
+
+Maximum_Fragment_Length::Maximum_Fragment_Length(size_t max_fragment)
+ {
+ if(max_fragment == 512)
+ val = 1;
+ else if(max_fragment == 1024)
+ val = 2;
+ else if(max_fragment == 2048)
+ val = 3;
+ else if(max_fragment == 4096)
+ val = 4;
+ else
+ throw std::invalid_argument("Bad setting " + to_string(max_fragment) +
+ " for maximum fragment size");
+ }
+
+Maximum_Fragment_Length::Maximum_Fragment_Length(TLS_Data_Reader& reader,
+ u16bit extension_size)
+ {
+ if(extension_size != 1)
+ throw Decoding_Error("Bad size for maximum fragment extension");
+ val = reader.get_byte();
+ }
+
+Next_Protocol_Notification::Next_Protocol_Notification(TLS_Data_Reader& reader,
+ u16bit extension_size)
+ {
+ if(extension_size == 0)
+ return; // empty extension
+
+ size_t bytes_remaining = extension_size;
+
+ while(bytes_remaining)
+ {
+ const std::string p = reader.get_string(1, 0, 255);
+
+ if(bytes_remaining < p.size() + 1)
+ throw Decoding_Error("Bad encoding for next protocol extension");
+
+ bytes_remaining -= (p.size() + 1);
+
+ m_protocols.push_back(p);
+ }
+ }
+
+MemoryVector<byte> Next_Protocol_Notification::serialize() const
+ {
+ MemoryVector<byte> buf;
+
+ for(size_t i = 0; i != m_protocols.size(); ++i)
+ {
+ const std::string p = m_protocols[i];
+
+ if(p != "")
+ append_tls_length_value(buf,
+ reinterpret_cast<const byte*>(p.data()),
+ p.size(),
+ 1);
+ }
+
+ return buf;
+ }
+
+std::string Supported_Elliptic_Curves::curve_id_to_name(u16bit id)
+ {
+ switch(id)
+ {
+ case 15:
+ return "secp160k1";
+ case 16:
+ return "secp160r1";
+ case 17:
+ return "secp160r2";
+ case 18:
+ return "secp192k1";
+ case 19:
+ return "secp192r1";
+ case 20:
+ return "secp224k1";
+ case 21:
+ return "secp224r1";
+ case 22:
+ return "secp256k1";
+ case 23:
+ return "secp256r1";
+ case 24:
+ return "secp384r1";
+ case 25:
+ return "secp521r1";
+ default:
+ return ""; // something we don't know or support
+ }
+ }
+
+u16bit Supported_Elliptic_Curves::name_to_curve_id(const std::string& name)
+ {
+ if(name == "secp160k1")
+ return 15;
+ if(name == "secp160r1")
+ return 16;
+ if(name == "secp160r2")
+ return 17;
+ if(name == "secp192k1")
+ return 18;
+ if(name == "secp192r1")
+ return 19;
+ if(name == "secp224k1")
+ return 20;
+ if(name == "secp224r1")
+ return 21;
+ if(name == "secp256k1")
+ return 22;
+ if(name == "secp256r1")
+ return 23;
+ if(name == "secp384r1")
+ return 24;
+ if(name == "secp521r1")
+ return 25;
+
+ throw Invalid_Argument("name_to_curve_id unknown name " + name);
+ }
+
+MemoryVector<byte> Supported_Elliptic_Curves::serialize() const
+ {
+ MemoryVector<byte> buf(2);
+
+ for(size_t i = 0; i != m_curves.size(); ++i)
+ {
+ const u16bit id = name_to_curve_id(m_curves[i]);
+ buf.push_back(get_byte(0, id));
+ buf.push_back(get_byte(1, id));
+ }
+
+ buf[0] = get_byte<u16bit>(0, buf.size()-2);
+ buf[1] = get_byte<u16bit>(1, buf.size()-2);
+
+ return buf;
+ }
+
+Supported_Elliptic_Curves::Supported_Elliptic_Curves(TLS_Data_Reader& reader,
+ u16bit extension_size)
+ {
+ u16bit len = reader.get_u16bit();
+
+ if(len + 2 != extension_size)
+ throw Decoding_Error("Inconsistent length field in elliptic curve list");
+
+ if(len % 2 == 1)
+ throw Decoding_Error("Elliptic curve list of strange size");
+
+ len /= 2;
+
+ for(size_t i = 0; i != len; ++i)
+ {
+ const u16bit id = reader.get_u16bit();
+ const std::string name = curve_id_to_name(id);
+
+ if(name != "")
+ m_curves.push_back(name);
+ }
+ }
+
+std::string Signature_Algorithms::hash_algo_name(byte code)
+ {
+ switch(code)
+ {
+ // code 1 is MD5 - ignore it
+
+ case 2:
+ return "SHA-1";
+ case 3:
+ return "SHA-224";
+ case 4:
+ return "SHA-256";
+ case 5:
+ return "SHA-384";
+ case 6:
+ return "SHA-512";
+ default:
+ return "";
+ }
+ }
+
+byte Signature_Algorithms::hash_algo_code(const std::string& name)
+ {
+ if(name == "SHA-1")
+ return 2;
+
+ if(name == "SHA-224")
+ return 3;
+
+ if(name == "SHA-256")
+ return 4;
+
+ if(name == "SHA-384")
+ return 5;
+
+ if(name == "SHA-512")
+ return 6;
+
+ throw Internal_Error("Unknown hash ID " + name + " for signature_algorithms");
+ }
+
+std::string Signature_Algorithms::sig_algo_name(byte code)
+ {
+ switch(code)
+ {
+ case 1:
+ return "RSA";
+ case 2:
+ return "DSA";
+ case 3:
+ return "ECDSA";
+ default:
+ return "";
+ }
+ }
+
+byte Signature_Algorithms::sig_algo_code(const std::string& name)
+ {
+ if(name == "RSA")
+ return 1;
+
+ if(name == "DSA")
+ return 2;
+
+ if(name == "ECDSA")
+ return 3;
+
+ throw Internal_Error("Unknown sig ID " + name + " for signature_algorithms");
+ }
+
+MemoryVector<byte> Signature_Algorithms::serialize() const
+ {
+ MemoryVector<byte> buf(2);
+
+ for(size_t i = 0; i != m_supported_algos.size(); ++i)
+ {
+ try
+ {
+ const byte hash_code = hash_algo_code(m_supported_algos[i].first);
+ const byte sig_code = sig_algo_code(m_supported_algos[i].second);
+
+ buf.push_back(hash_code);
+ buf.push_back(sig_code);
+ }
+ catch(...)
+ {}
+ }
+
+ buf[0] = get_byte<u16bit>(0, buf.size()-2);
+ buf[1] = get_byte<u16bit>(1, buf.size()-2);
+
+ return buf;
+ }
+
+Signature_Algorithms::Signature_Algorithms(TLS_Data_Reader& reader,
+ u16bit extension_size)
+ {
+ u16bit len = reader.get_u16bit();
+
+ if(len + 2 != extension_size)
+ throw Decoding_Error("Bad encoding on signature algorithms extension");
+
+ while(len)
+ {
+ const std::string hash_code = hash_algo_name(reader.get_byte());
+ const std::string sig_code = sig_algo_name(reader.get_byte());
+
+ len -= 2;
+
+ // If not something we know, ignore it completely
+ if(hash_code == "" || sig_code == "")
+ continue;
+
+ m_supported_algos.push_back(std::make_pair(hash_code, sig_code));
+ }
+ }
+
+Session_Ticket::Session_Ticket(TLS_Data_Reader& reader,
+ u16bit extension_size)
+ {
+ m_ticket = reader.get_elem<byte, MemoryVector<byte> >(extension_size);
+ }
+
+}
+
+}
diff --git a/src/tls/tls_extensions.h b/src/tls/tls_extensions.h
new file mode 100644
index 000000000..3fe3f7399
--- /dev/null
+++ b/src/tls/tls_extensions.h
@@ -0,0 +1,381 @@
+/*
+* TLS Extensions
+* (C) 2011-2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_EXTENSIONS_H__
+#define BOTAN_TLS_EXTENSIONS_H__
+
+#include <botan/secmem.h>
+#include <botan/tls_magic.h>
+#include <vector>
+#include <string>
+#include <map>
+
+namespace Botan {
+
+namespace TLS {
+
+class TLS_Data_Reader;
+
+enum Handshake_Extension_Type {
+ TLSEXT_SERVER_NAME_INDICATION = 0,
+ TLSEXT_MAX_FRAGMENT_LENGTH = 1,
+ TLSEXT_CLIENT_CERT_URL = 2,
+ TLSEXT_TRUSTED_CA_KEYS = 3,
+ TLSEXT_TRUNCATED_HMAC = 4,
+
+ TLSEXT_CERTIFICATE_TYPES = 9,
+ TLSEXT_USABLE_ELLIPTIC_CURVES = 10,
+ TLSEXT_EC_POINT_FORMATS = 11,
+ TLSEXT_SRP_IDENTIFIER = 12,
+ TLSEXT_SIGNATURE_ALGORITHMS = 13,
+ TLSEXT_HEARTBEAT_SUPPORT = 15,
+
+ TLSEXT_SESSION_TICKET = 35,
+
+ TLSEXT_NEXT_PROTOCOL = 13172,
+
+ TLSEXT_SAFE_RENEGOTIATION = 65281,
+};
+
+/**
+* Base class representing a TLS extension of some kind
+*/
+class Extension
+ {
+ public:
+ virtual Handshake_Extension_Type type() const = 0;
+
+ virtual MemoryVector<byte> serialize() const = 0;
+
+ virtual bool empty() const = 0;
+
+ virtual ~Extension() {}
+ };
+
+/**
+* Server Name Indicator extension (RFC 3546)
+*/
+class Server_Name_Indicator : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_SERVER_NAME_INDICATION; }
+
+ Handshake_Extension_Type type() const { return static_type(); }
+
+ Server_Name_Indicator(const std::string& host_name) :
+ sni_host_name(host_name) {}
+
+ Server_Name_Indicator(TLS_Data_Reader& reader,
+ u16bit extension_size);
+
+ std::string host_name() const { return sni_host_name; }
+
+ MemoryVector<byte> serialize() const;
+
+ bool empty() const { return sni_host_name == ""; }
+ private:
+ std::string sni_host_name;
+ };
+
+/**
+* SRP identifier extension (RFC 5054)
+*/
+class SRP_Identifier : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_SRP_IDENTIFIER; }
+
+ Handshake_Extension_Type type() const { return static_type(); }
+
+ SRP_Identifier(const std::string& identifier) :
+ srp_identifier(identifier) {}
+
+ SRP_Identifier(TLS_Data_Reader& reader,
+ u16bit extension_size);
+
+ std::string identifier() const { return srp_identifier; }
+
+ MemoryVector<byte> serialize() const;
+
+ bool empty() const { return srp_identifier == ""; }
+ private:
+ std::string srp_identifier;
+ };
+
+/**
+* Renegotiation Indication Extension (RFC 5746)
+*/
+class Renegotation_Extension : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_SAFE_RENEGOTIATION; }
+
+ Handshake_Extension_Type type() const { return static_type(); }
+
+ Renegotation_Extension() {}
+
+ Renegotation_Extension(const MemoryRegion<byte>& bits) :
+ reneg_data(bits) {}
+
+ Renegotation_Extension(TLS_Data_Reader& reader,
+ u16bit extension_size);
+
+ const MemoryVector<byte>& renegotiation_info() const
+ { return reneg_data; }
+
+ MemoryVector<byte> serialize() const;
+
+ bool empty() const { return false; } // always send this
+ private:
+ MemoryVector<byte> reneg_data;
+ };
+
+/**
+* Maximum Fragment Length Negotiation Extension (RFC 4366 sec 3.2)
+*/
+class Maximum_Fragment_Length : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_MAX_FRAGMENT_LENGTH; }
+
+ Handshake_Extension_Type type() const { return static_type(); }
+
+ bool empty() const { return val != 0; }
+
+ size_t fragment_size() const;
+
+ MemoryVector<byte> serialize() const
+ {
+ return MemoryVector<byte>(&val, 1);
+ }
+
+ /**
+ * @param max_fragment specifies what maximum fragment size to
+ * advertise. Currently must be one of 512, 1024, 2048, or
+ * 4096.
+ */
+ Maximum_Fragment_Length(size_t max_fragment);
+
+ Maximum_Fragment_Length(TLS_Data_Reader& reader,
+ u16bit extension_size);
+
+ private:
+ byte val;
+ };
+
+/**
+* Next Protocol Negotiation
+* http://technotes.googlecode.com/git/nextprotoneg.html
+*
+* This implementation requires the semantics defined in the Google
+* spec (implemented in Chromium); the internet draft leaves the format
+* unspecified.
+*/
+class Next_Protocol_Notification : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_NEXT_PROTOCOL; }
+
+ Handshake_Extension_Type type() const { return static_type(); }
+
+ const std::vector<std::string>& protocols() const
+ { return m_protocols; }
+
+ /**
+ * Empty extension, used by client
+ */
+ Next_Protocol_Notification() {}
+
+ /**
+ * List of protocols, used by server
+ */
+ Next_Protocol_Notification(const std::vector<std::string>& protocols) :
+ m_protocols(protocols) {}
+
+ Next_Protocol_Notification(TLS_Data_Reader& reader,
+ u16bit extension_size);
+
+ MemoryVector<byte> serialize() const;
+
+ bool empty() const { return false; }
+ private:
+ std::vector<std::string> m_protocols;
+ };
+
+class Session_Ticket : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_SESSION_TICKET; }
+
+ Handshake_Extension_Type type() const { return static_type(); }
+
+ const MemoryVector<byte>& contents() const { return m_ticket; }
+
+ /**
+ * Create empty extension, used by both client and server
+ */
+ Session_Ticket() {}
+
+ /**
+ * Extension with ticket, used by client
+ */
+ Session_Ticket(const MemoryRegion<byte>& session_ticket) :
+ m_ticket(session_ticket) {}
+
+ /**
+ * Deserialize a session ticket
+ */
+ Session_Ticket(TLS_Data_Reader& reader, u16bit extension_size);
+
+ MemoryVector<byte> serialize() const { return m_ticket; }
+
+ bool empty() const { return false; }
+ private:
+ MemoryVector<byte> m_ticket;
+ };
+
+/**
+* Supported Elliptic Curves Extension (RFC 4492)
+*/
+class Supported_Elliptic_Curves : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_USABLE_ELLIPTIC_CURVES; }
+
+ Handshake_Extension_Type type() const { return static_type(); }
+
+ static std::string curve_id_to_name(u16bit id);
+ static u16bit name_to_curve_id(const std::string& name);
+
+ const std::vector<std::string>& curves() const { return m_curves; }
+
+ MemoryVector<byte> serialize() const;
+
+ Supported_Elliptic_Curves(const std::vector<std::string>& curves) :
+ m_curves(curves) {}
+
+ Supported_Elliptic_Curves(TLS_Data_Reader& reader,
+ u16bit extension_size);
+
+ bool empty() const { return m_curves.empty(); }
+ private:
+ std::vector<std::string> m_curves;
+ };
+
+/**
+* Signature Algorithms Extension for TLS 1.2 (RFC 5246)
+*/
+class Signature_Algorithms : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_SIGNATURE_ALGORITHMS; }
+
+ Handshake_Extension_Type type() const { return static_type(); }
+
+ static std::string hash_algo_name(byte code);
+ static byte hash_algo_code(const std::string& name);
+
+ static std::string sig_algo_name(byte code);
+ static byte sig_algo_code(const std::string& name);
+
+ std::vector<std::pair<std::string, std::string> >
+ supported_signature_algorthms() const
+ {
+ return m_supported_algos;
+ }
+
+ MemoryVector<byte> serialize() const;
+
+ bool empty() const { return false; }
+
+ Signature_Algorithms(const std::vector<std::pair<std::string, std::string> >& algos) :
+ m_supported_algos(algos) {}
+
+ Signature_Algorithms(TLS_Data_Reader& reader,
+ u16bit extension_size);
+ private:
+ std::vector<std::pair<std::string, std::string> > m_supported_algos;
+ };
+
+/**
+* Heartbeat Extension (RFC 6520)
+*/
+class Heartbeat_Support_Indicator : public Extension
+ {
+ public:
+ static Handshake_Extension_Type static_type()
+ { return TLSEXT_HEARTBEAT_SUPPORT; }
+
+ Handshake_Extension_Type type() const { return static_type(); }
+
+ bool peer_allowed_to_send() const { return m_peer_allowed_to_send; }
+
+ MemoryVector<byte> serialize() const;
+
+ bool empty() const { return false; }
+
+ Heartbeat_Support_Indicator(bool peer_allowed_to_send) :
+ m_peer_allowed_to_send(peer_allowed_to_send) {}
+
+ Heartbeat_Support_Indicator(TLS_Data_Reader& reader, u16bit extension_size);
+
+ private:
+ bool m_peer_allowed_to_send;
+ };
+
+/**
+* Represents a block of extensions in a hello message
+*/
+class Extensions
+ {
+ public:
+ template<typename T>
+ T* get() const
+ {
+ Handshake_Extension_Type type = T::static_type();
+
+ std::map<Handshake_Extension_Type, Extension*>::const_iterator i =
+ extensions.find(type);
+
+ if(i != extensions.end())
+ return dynamic_cast<T*>(i->second);
+ return 0;
+ }
+
+ void add(Extension* extn)
+ {
+ delete extensions[extn->type()]; // or hard error if already exists?
+ extensions[extn->type()] = extn;
+ }
+
+ MemoryVector<byte> serialize() const;
+
+ Extensions() {}
+
+ Extensions(TLS_Data_Reader& reader); // deserialize
+
+ ~Extensions();
+ private:
+ Extensions(const Extensions&) {}
+ Extensions& operator=(const Extensions&) { return (*this); }
+
+ std::map<Handshake_Extension_Type, Extension*> extensions;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_handshake_hash.cpp b/src/tls/tls_handshake_hash.cpp
new file mode 100644
index 000000000..d0c74136b
--- /dev/null
+++ b/src/tls/tls_handshake_hash.cpp
@@ -0,0 +1,103 @@
+/*
+* TLS Handshake Hash
+* (C) 2004-2006,2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_handshake_hash.h>
+#include <botan/tls_exceptn.h>
+#include <botan/libstate.h>
+#include <botan/hash.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+void Handshake_Hash::update(Handshake_Type handshake_type,
+ const MemoryRegion<byte>& handshake_msg)
+ {
+ update(static_cast<byte>(handshake_type));
+
+ const size_t record_length = handshake_msg.size();
+ for(size_t i = 0; i != 3; i++)
+ update(get_byte<u32bit>(i+1, record_length));
+
+ update(handshake_msg);
+ }
+
+/**
+* Return a TLS Handshake Hash
+*/
+SecureVector<byte> Handshake_Hash::final(Protocol_Version version,
+ const std::string& mac_algo)
+ {
+ Algorithm_Factory& af = global_state().algorithm_factory();
+
+ std::auto_ptr<HashFunction> hash;
+
+ if(version == Protocol_Version::TLS_V10 || version == Protocol_Version::TLS_V11)
+ {
+ hash.reset(af.make_hash_function("TLS.Digest.0"));
+ }
+ else if(version == Protocol_Version::TLS_V12)
+ {
+ if(mac_algo == "MD5" || mac_algo == "SHA-1" || mac_algo == "SHA-256")
+ hash.reset(af.make_hash_function("SHA-256"));
+ else
+ hash.reset(af.make_hash_function(mac_algo));
+ }
+ else
+ throw TLS_Exception(Alert::PROTOCOL_VERSION,
+ "Unknown version for handshake hashes");
+
+ hash->update(data);
+ return hash->final();
+ }
+
+/**
+* Return a SSLv3 Handshake Hash
+*/
+SecureVector<byte> Handshake_Hash::final_ssl3(const MemoryRegion<byte>& secret)
+ {
+ const byte PAD_INNER = 0x36, PAD_OUTER = 0x5C;
+
+ Algorithm_Factory& af = global_state().algorithm_factory();
+
+ std::auto_ptr<HashFunction> md5(af.make_hash_function("MD5"));
+ std::auto_ptr<HashFunction> sha1(af.make_hash_function("SHA-1"));
+
+ md5->update(data);
+ sha1->update(data);
+
+ md5->update(secret);
+ sha1->update(secret);
+
+ for(size_t i = 0; i != 48; ++i)
+ md5->update(PAD_INNER);
+ for(size_t i = 0; i != 40; ++i)
+ sha1->update(PAD_INNER);
+
+ SecureVector<byte> inner_md5 = md5->final(), inner_sha1 = sha1->final();
+
+ md5->update(secret);
+ sha1->update(secret);
+
+ for(size_t i = 0; i != 48; ++i)
+ md5->update(PAD_OUTER);
+ for(size_t i = 0; i != 40; ++i)
+ sha1->update(PAD_OUTER);
+
+ md5->update(inner_md5);
+ sha1->update(inner_sha1);
+
+ SecureVector<byte> output;
+ output += md5->final();
+ output += sha1->final();
+ return output;
+ }
+
+}
+
+}
diff --git a/src/tls/tls_handshake_hash.h b/src/tls/tls_handshake_hash.h
new file mode 100644
index 000000000..c13f97aa8
--- /dev/null
+++ b/src/tls/tls_handshake_hash.h
@@ -0,0 +1,55 @@
+/*
+* TLS Handshake Hash
+* (C) 2004-2006,2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_HANDSHAKE_HASH_H__
+#define BOTAN_TLS_HANDSHAKE_HASH_H__
+
+#include <botan/secmem.h>
+#include <botan/tls_version.h>
+#include <botan/tls_magic.h>
+
+namespace Botan {
+
+namespace TLS {
+
+using namespace Botan;
+
+/**
+* TLS Handshake Hash
+*/
+class Handshake_Hash
+ {
+ public:
+ void update(const byte in[], size_t length)
+ { data += std::make_pair(in, length); }
+
+ void update(const MemoryRegion<byte>& in)
+ { data += in; }
+
+ void update(byte in)
+ { data.push_back(in); }
+
+ void update(Handshake_Type handshake_type,
+ const MemoryRegion<byte>& handshake_msg);
+
+ SecureVector<byte> final(Protocol_Version version,
+ const std::string& mac_algo);
+
+ SecureVector<byte> final_ssl3(const MemoryRegion<byte>& master_secret);
+
+ const SecureVector<byte>& get_contents() const
+ { return data; }
+
+ private:
+ SecureVector<byte> data;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_handshake_reader.cpp b/src/tls/tls_handshake_reader.cpp
new file mode 100644
index 000000000..8278a2296
--- /dev/null
+++ b/src/tls/tls_handshake_reader.cpp
@@ -0,0 +1,66 @@
+/*
+* TLS Handshake Reader
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_handshake_reader.h>
+#include <botan/exceptn.h>
+
+namespace Botan {
+
+namespace TLS {
+
+void Stream_Handshake_Reader::add_input(const byte record[],
+ size_t record_size)
+ {
+ m_queue.write(record, record_size);
+ }
+
+bool Stream_Handshake_Reader::empty() const
+ {
+ return m_queue.empty();
+ }
+
+bool Stream_Handshake_Reader::have_full_record() const
+ {
+ if(m_queue.size() >= 4)
+ {
+ byte head[4] = { 0 };
+ m_queue.peek(head, 4);
+
+ const size_t length = make_u32bit(0, head[1], head[2], head[3]);
+
+ return (m_queue.size() >= length + 4);
+ }
+
+ return false;
+ }
+
+std::pair<Handshake_Type, MemoryVector<byte> > Stream_Handshake_Reader::get_next_record()
+ {
+ if(m_queue.size() >= 4)
+ {
+ byte head[4] = { 0 };
+ m_queue.peek(head, 4);
+
+ const size_t length = make_u32bit(0, head[1], head[2], head[3]);
+
+ if(m_queue.size() >= length + 4)
+ {
+ Handshake_Type type = static_cast<Handshake_Type>(head[0]);
+ MemoryVector<byte> contents(length);
+ m_queue.read(head, 4); // discard
+ m_queue.read(&contents[0], contents.size());
+
+ return std::make_pair(type, contents);
+ }
+ }
+
+ throw Internal_Error("Stream_Handshake_Reader::get_next_record called without a full record");
+ }
+
+}
+
+}
diff --git a/src/tls/tls_handshake_reader.h b/src/tls/tls_handshake_reader.h
new file mode 100644
index 000000000..06a273ced
--- /dev/null
+++ b/src/tls/tls_handshake_reader.h
@@ -0,0 +1,58 @@
+/*
+* TLS Handshake Reader
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_HANDSHAKE_READER_H__
+#define BOTAN_TLS_HANDSHAKE_READER_H__
+
+#include <botan/tls_magic.h>
+#include <botan/secqueue.h>
+#include <botan/loadstor.h>
+#include <utility>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Handshake Reader Interface
+*/
+class Handshake_Reader
+ {
+ public:
+ virtual void add_input(const byte record[], size_t record_size) = 0;
+
+ virtual bool empty() const = 0;
+
+ virtual bool have_full_record() const = 0;
+
+ virtual std::pair<Handshake_Type, MemoryVector<byte> > get_next_record() = 0;
+
+ virtual ~Handshake_Reader() {}
+ };
+
+/**
+* Reader of TLS handshake messages
+*/
+class Stream_Handshake_Reader : public Handshake_Reader
+ {
+ public:
+ void add_input(const byte record[], size_t record_size);
+
+ bool empty() const;
+
+ bool have_full_record() const;
+
+ std::pair<Handshake_Type, MemoryVector<byte> > get_next_record();
+ private:
+ SecureQueue m_queue;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_handshake_state.cpp b/src/tls/tls_handshake_state.cpp
new file mode 100644
index 000000000..1a55305e3
--- /dev/null
+++ b/src/tls/tls_handshake_state.cpp
@@ -0,0 +1,329 @@
+/*
+* TLS Handshaking
+* (C) 2004-2006,2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_handshake_state.h>
+#include <botan/internal/tls_messages.h>
+#include <botan/internal/assert.h>
+#include <botan/lookup.h>
+
+namespace Botan {
+
+namespace TLS {
+
+namespace {
+
+u32bit bitmask_for_handshake_type(Handshake_Type type)
+ {
+ switch(type)
+ {
+ case HELLO_REQUEST:
+ return (1 << 0);
+
+ /*
+ * Same code point for both client hello styles
+ */
+ case CLIENT_HELLO:
+ case CLIENT_HELLO_SSLV2:
+ return (1 << 1);
+
+ case SERVER_HELLO:
+ return (1 << 2);
+
+ case CERTIFICATE:
+ return (1 << 3);
+
+ case SERVER_KEX:
+ return (1 << 4);
+
+ case CERTIFICATE_REQUEST:
+ return (1 << 5);
+
+ case SERVER_HELLO_DONE:
+ return (1 << 6);
+
+ case CERTIFICATE_VERIFY:
+ return (1 << 7);
+
+ case CLIENT_KEX:
+ return (1 << 8);
+
+ case NEXT_PROTOCOL:
+ return (1 << 9);
+
+ case NEW_SESSION_TICKET:
+ return (1 << 10);
+
+ case HANDSHAKE_CCS:
+ return (1 << 11);
+
+ case FINISHED:
+ return (1 << 12);
+
+ // allow explicitly disabling new handshakes
+ case HANDSHAKE_NONE:
+ return 0;
+
+ default:
+ throw Internal_Error("Unknown handshake type " + to_string(type));
+ }
+
+ return 0;
+ }
+
+}
+
+/*
+* Initialize the SSL/TLS Handshake State
+*/
+Handshake_State::Handshake_State(Handshake_Reader* reader)
+ {
+ client_hello = 0;
+ server_hello = 0;
+ server_certs = 0;
+ server_kex = 0;
+ cert_req = 0;
+ server_hello_done = 0;
+ next_protocol = 0;
+ new_session_ticket = 0;
+
+ client_certs = 0;
+ client_kex = 0;
+ client_verify = 0;
+ client_finished = 0;
+ server_finished = 0;
+
+ m_handshake_reader = reader;
+
+ server_rsa_kex_key = 0;
+
+ m_version = Protocol_Version::SSL_V3;
+
+ hand_expecting_mask = 0;
+ hand_received_mask = 0;
+
+ allow_session_resumption = true;
+ }
+
+void Handshake_State::set_version(const Protocol_Version& version)
+ {
+ m_version = version;
+ }
+
+void Handshake_State::confirm_transition_to(Handshake_Type handshake_msg)
+ {
+ const u32bit mask = bitmask_for_handshake_type(handshake_msg);
+
+ hand_received_mask |= mask;
+
+ const bool ok = (hand_expecting_mask & mask); // overlap?
+
+ if(!ok)
+ throw Unexpected_Message("Unexpected state transition in handshake, got " +
+ to_string(handshake_msg) + " mask is " +
+ to_string(hand_expecting_mask));
+
+ /* We don't know what to expect next, so force a call to
+ set_expected_next; if it doesn't happen, the next transition
+ check will always fail which is what we want.
+ */
+ hand_expecting_mask = 0;
+ }
+
+void Handshake_State::set_expected_next(Handshake_Type handshake_msg)
+ {
+ hand_expecting_mask |= bitmask_for_handshake_type(handshake_msg);
+ }
+
+bool Handshake_State::received_handshake_msg(Handshake_Type handshake_msg) const
+ {
+ const u32bit mask = bitmask_for_handshake_type(handshake_msg);
+
+ return (hand_received_mask & mask);
+ }
+
+std::string Handshake_State::srp_identifier() const
+ {
+ if(suite.valid() && suite.kex_algo() == "SRP_SHA")
+ return client_hello->srp_identifier();
+
+ return "";
+ }
+
+const MemoryRegion<byte>& Handshake_State::session_ticket() const
+ {
+ if(new_session_ticket && !new_session_ticket->ticket().empty())
+ return new_session_ticket->ticket();
+
+ return client_hello->session_ticket();
+ }
+
+KDF* Handshake_State::protocol_specific_prf()
+ {
+ if(version() == Protocol_Version::SSL_V3)
+ {
+ return get_kdf("SSL3-PRF");
+ }
+ else if(version() == Protocol_Version::TLS_V10 || version() == Protocol_Version::TLS_V11)
+ {
+ return get_kdf("TLS-PRF");
+ }
+ else if(version() == Protocol_Version::TLS_V12)
+ {
+ if(suite.mac_algo() == "MD5" ||
+ suite.mac_algo() == "SHA-1" ||
+ suite.mac_algo() == "SHA-256")
+ {
+ return get_kdf("TLS-12-PRF(SHA-256)");
+ }
+
+ return get_kdf("TLS-12-PRF(" + suite.mac_algo() + ")");
+ }
+
+ throw Internal_Error("Unknown version code " + version().to_string());
+ }
+
+std::pair<std::string, Signature_Format>
+Handshake_State::choose_sig_format(const Private_Key* key,
+ std::string& hash_algo_out,
+ std::string& sig_algo_out,
+ bool for_client_auth)
+ {
+ const std::string sig_algo = key->algo_name();
+
+ const std::vector<std::pair<std::string, std::string> > supported_algos =
+ (for_client_auth) ? cert_req->supported_algos() : client_hello->supported_algos();
+
+ std::string hash_algo;
+
+ for(size_t i = 0; i != supported_algos.size(); ++i)
+ {
+ if(supported_algos[i].second == sig_algo)
+ {
+ hash_algo = supported_algos[i].first;
+ break;
+ }
+ }
+
+ if(for_client_auth && this->version() == Protocol_Version::SSL_V3)
+ hash_algo = "Raw";
+
+ if(hash_algo == "" && this->version() == Protocol_Version::TLS_V12)
+ hash_algo = "SHA-1"; // TLS 1.2 but no compatible hashes set (?)
+
+ BOTAN_ASSERT(hash_algo != "", "Couldn't figure out hash to use");
+
+ if(this->version() >= Protocol_Version::TLS_V12)
+ {
+ hash_algo_out = hash_algo;
+ sig_algo_out = sig_algo;
+ }
+
+ if(sig_algo == "RSA")
+ {
+ const std::string padding = "EMSA3(" + hash_algo + ")";
+
+ return std::make_pair(padding, IEEE_1363);
+ }
+ else if(sig_algo == "DSA" || sig_algo == "ECDSA")
+ {
+ const std::string padding = "EMSA1(" + hash_algo + ")";
+
+ return std::make_pair(padding, DER_SEQUENCE);
+ }
+
+ throw Invalid_Argument(sig_algo + " is invalid/unknown for TLS signatures");
+ }
+
+std::pair<std::string, Signature_Format>
+Handshake_State::understand_sig_format(const Public_Key* key,
+ std::string hash_algo,
+ std::string sig_algo,
+ bool for_client_auth)
+ {
+ const std::string algo_name = key->algo_name();
+
+ /*
+ FIXME: This should check what was sent against the client hello
+ preferences, or the certificate request, to ensure it was allowed
+ by those restrictions.
+
+ Or not?
+ */
+
+ if(this->version() < Protocol_Version::TLS_V12)
+ {
+ if(hash_algo != "" || sig_algo != "")
+ throw Decoding_Error("Counterparty sent hash/sig IDs with old version");
+ }
+ else
+ {
+ if(hash_algo == "")
+ throw Decoding_Error("Counterparty did not send hash/sig IDS");
+
+ if(sig_algo != algo_name)
+ throw Decoding_Error("Counterparty sent inconsistent key and sig types");
+ }
+
+ if(algo_name == "RSA")
+ {
+ if(for_client_auth && this->version() == Protocol_Version::SSL_V3)
+ {
+ hash_algo = "Raw";
+ }
+ else if(this->version() < Protocol_Version::TLS_V12)
+ {
+ hash_algo = "TLS.Digest.0";
+ }
+
+ const std::string padding = "EMSA3(" + hash_algo + ")";
+ return std::make_pair(padding, IEEE_1363);
+ }
+ else if(algo_name == "DSA" || algo_name == "ECDSA")
+ {
+ if(algo_name == "DSA" && for_client_auth && this->version() == Protocol_Version::SSL_V3)
+ {
+ hash_algo = "Raw";
+ }
+ else if(this->version() < Protocol_Version::TLS_V12)
+ {
+ hash_algo = "SHA-1";
+ }
+
+ const std::string padding = "EMSA1(" + hash_algo + ")";
+
+ return std::make_pair(padding, DER_SEQUENCE);
+ }
+
+ throw Invalid_Argument(algo_name + " is invalid/unknown for TLS signatures");
+ }
+
+/*
+* Destroy the SSL/TLS Handshake State
+*/
+Handshake_State::~Handshake_State()
+ {
+ delete client_hello;
+ delete server_hello;
+ delete server_certs;
+ delete server_kex;
+ delete cert_req;
+ delete server_hello_done;
+ delete next_protocol;
+ delete new_session_ticket;
+
+ delete client_certs;
+ delete client_kex;
+ delete client_verify;
+ delete client_finished;
+ delete server_finished;
+
+ delete m_handshake_reader;
+ }
+
+}
+
+}
diff --git a/src/tls/tls_handshake_state.h b/src/tls/tls_handshake_state.h
new file mode 100644
index 000000000..ec4c2fea8
--- /dev/null
+++ b/src/tls/tls_handshake_state.h
@@ -0,0 +1,125 @@
+/*
+* TLS Handshake State
+* (C) 2004-2006 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_HANDSHAKE_STATE_H__
+#define BOTAN_TLS_HANDSHAKE_STATE_H__
+
+#include <botan/internal/tls_handshake_hash.h>
+#include <botan/internal/tls_handshake_reader.h>
+#include <botan/internal/tls_session_key.h>
+#include <botan/pk_keys.h>
+#include <botan/pubkey.h>
+
+#include <utility>
+
+#if defined(BOTAN_USE_STD_TR1)
+
+#if defined(BOTAN_BUILD_COMPILER_IS_MSVC)
+ #include <functional>
+#else
+ #include <tr1/functional>
+#endif
+
+#elif defined(BOTAN_USE_BOOST_TR1)
+ #include <boost/tr1/functional.hpp>
+#else
+ #error "No TR1 library defined for use"
+#endif
+
+namespace Botan {
+
+class KDF;
+
+namespace TLS {
+
+/**
+* SSL/TLS Handshake State
+*/
+class Handshake_State
+ {
+ public:
+ Handshake_State(Handshake_Reader* reader);
+ ~Handshake_State();
+
+ bool received_handshake_msg(Handshake_Type handshake_msg) const;
+
+ void confirm_transition_to(Handshake_Type handshake_msg);
+ void set_expected_next(Handshake_Type handshake_msg);
+
+ const MemoryRegion<byte>& session_ticket() const;
+
+ std::pair<std::string, Signature_Format>
+ understand_sig_format(const Public_Key* key,
+ std::string hash_algo,
+ std::string sig_algo,
+ bool for_client_auth);
+
+ std::pair<std::string, Signature_Format>
+ choose_sig_format(const Private_Key* key,
+ std::string& hash_algo,
+ std::string& sig_algo,
+ bool for_client_auth);
+
+ std::string srp_identifier() const;
+
+ KDF* protocol_specific_prf();
+
+ Protocol_Version version() const { return m_version; }
+
+ void set_version(const Protocol_Version& version);
+
+ class Client_Hello* client_hello;
+ class Server_Hello* server_hello;
+ class Certificate* server_certs;
+ class Server_Key_Exchange* server_kex;
+ class Certificate_Req* cert_req;
+ class Server_Hello_Done* server_hello_done;
+
+ class Certificate* client_certs;
+ class Client_Key_Exchange* client_kex;
+ class Certificate_Verify* client_verify;
+
+ class Next_Protocol* next_protocol;
+ class New_Session_Ticket* new_session_ticket;
+
+ class Finished* client_finished;
+ class Finished* server_finished;
+
+ // Used by the server only, in case of RSA key exchange
+ Private_Key* server_rsa_kex_key;
+
+ Ciphersuite suite;
+ Session_Keys keys;
+ Handshake_Hash hash;
+
+ /*
+ * Only used by clients for session resumption
+ */
+ SecureVector<byte> resume_master_secret;
+
+ /*
+ *
+ */
+ bool allow_session_resumption;
+
+ /**
+ * Used by client using NPN
+ */
+ std::tr1::function<std::string (std::vector<std::string>)> client_npn_cb;
+
+ Handshake_Reader* handshake_reader() { return m_handshake_reader; }
+ private:
+ Handshake_Reader* m_handshake_reader;
+ u32bit hand_expecting_mask, hand_received_mask;
+ Protocol_Version m_version;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_heartbeats.cpp b/src/tls/tls_heartbeats.cpp
new file mode 100644
index 000000000..a77d23534
--- /dev/null
+++ b/src/tls/tls_heartbeats.cpp
@@ -0,0 +1,78 @@
+/*
+* TLS Heartbeats
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_heartbeats.h>
+#include <botan/internal/tls_extensions.h>
+#include <botan/internal/tls_reader.h>
+#include <botan/tls_exceptn.h>
+
+namespace Botan {
+
+namespace TLS {
+
+Heartbeat_Message::Heartbeat_Message(const MemoryRegion<byte>& buf)
+ {
+ TLS_Data_Reader reader(buf);
+
+ const byte type = reader.get_byte();
+
+ if(type != 1 && type != 2)
+ throw TLS_Exception(Alert::ILLEGAL_PARAMETER,
+ "Unknown heartbeat message type");
+
+ m_type = static_cast<Type>(type);
+
+ m_payload = reader.get_range<byte>(2, 0, 16*1024);
+
+ // padding follows and is ignored
+ }
+
+Heartbeat_Message::Heartbeat_Message(Type type,
+ const byte payload[],
+ size_t payload_len) :
+ m_type(type),
+ m_payload(payload, payload_len)
+ {
+ }
+
+MemoryVector<byte> Heartbeat_Message::contents() const
+ {
+ MemoryVector<byte> send_buf(3 + m_payload.size() + 16);
+ send_buf[0] = m_type;
+ send_buf[1] = get_byte<u16bit>(0, m_payload.size());
+ send_buf[2] = get_byte<u16bit>(1, m_payload.size());
+ copy_mem(&send_buf[3], &m_payload[0], m_payload.size());
+ // leave padding as all zeros
+
+ return send_buf;
+ }
+
+MemoryVector<byte> Heartbeat_Support_Indicator::serialize() const
+ {
+ MemoryVector<byte> heartbeat(1);
+ heartbeat[0] = (m_peer_allowed_to_send ? 1 : 2);
+ return heartbeat;
+ }
+
+Heartbeat_Support_Indicator::Heartbeat_Support_Indicator(TLS_Data_Reader& reader,
+ u16bit extension_size)
+ {
+ if(extension_size != 1)
+ throw Decoding_Error("Strange size for heartbeat extension");
+
+ const byte code = reader.get_byte();
+
+ if(code != 1 && code != 2)
+ throw TLS_Exception(Alert::ILLEGAL_PARAMETER,
+ "Unknown heartbeat code " + to_string(code));
+
+ m_peer_allowed_to_send = (code == 1);
+ }
+
+}
+
+}
diff --git a/src/tls/tls_heartbeats.h b/src/tls/tls_heartbeats.h
new file mode 100644
index 000000000..4fa49501b
--- /dev/null
+++ b/src/tls/tls_heartbeats.h
@@ -0,0 +1,40 @@
+/*
+* TLS Heartbeats
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_HEARTBEATS_H__
+#define BOTAN_TLS_HEARTBEATS_H__
+
+#include <botan/secmem.h>
+
+namespace Botan {
+
+namespace TLS {
+
+class Heartbeat_Message
+ {
+ public:
+ enum Type { REQUEST = 1, RESPONSE = 2 };
+
+ MemoryVector<byte> contents() const;
+
+ const MemoryRegion<byte>& payload() const { return m_payload; }
+
+ bool is_request() const { return m_type == REQUEST; }
+
+ Heartbeat_Message(const MemoryRegion<byte>& buf);
+
+ Heartbeat_Message(Type type, const byte payload[], size_t payload_len);
+ private:
+ Type m_type;
+ MemoryVector<byte> m_payload;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_magic.h b/src/tls/tls_magic.h
new file mode 100644
index 000000000..2972321c9
--- /dev/null
+++ b/src/tls/tls_magic.h
@@ -0,0 +1,69 @@
+/*
+* SSL/TLS Protocol Constants
+* (C) 2004-2010 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_PROTOCOL_MAGIC_H__
+#define BOTAN_TLS_PROTOCOL_MAGIC_H__
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Protocol Constants for SSL/TLS
+*/
+enum Size_Limits {
+ TLS_HEADER_SIZE = 5,
+ MAX_PLAINTEXT_SIZE = 16*1024,
+ MAX_COMPRESSED_SIZE = MAX_PLAINTEXT_SIZE + 1024,
+ MAX_CIPHERTEXT_SIZE = MAX_COMPRESSED_SIZE + 1024,
+
+ MAX_TLS_RECORD_SIZE = MAX_CIPHERTEXT_SIZE + TLS_HEADER_SIZE,
+};
+
+enum Connection_Side { CLIENT = 1, SERVER = 2 };
+
+enum Record_Type {
+ CONNECTION_CLOSED = 0,
+
+ CHANGE_CIPHER_SPEC = 20,
+ ALERT = 21,
+ HANDSHAKE = 22,
+ APPLICATION_DATA = 23,
+ HEARTBEAT = 24,
+};
+
+enum Handshake_Type {
+ HELLO_REQUEST = 0,
+ CLIENT_HELLO = 1,
+ CLIENT_HELLO_SSLV2 = 253, // Not a wire value
+ SERVER_HELLO = 2,
+ HELLO_VERIFY_REQUEST = 3,
+ NEW_SESSION_TICKET = 4, // RFC 5077
+ CERTIFICATE = 11,
+ SERVER_KEX = 12,
+ CERTIFICATE_REQUEST = 13,
+ SERVER_HELLO_DONE = 14,
+ CERTIFICATE_VERIFY = 15,
+ CLIENT_KEX = 16,
+ FINISHED = 20,
+
+ NEXT_PROTOCOL = 67,
+
+ HANDSHAKE_CCS = 254, // Not a wire value
+ HANDSHAKE_NONE = 255 // Null value
+};
+
+enum Compression_Method {
+ NO_COMPRESSION = 0x00,
+ DEFLATE_COMPRESSION = 0x01
+};
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_messages.h b/src/tls/tls_messages.h
new file mode 100644
index 000000000..d9146dda1
--- /dev/null
+++ b/src/tls/tls_messages.h
@@ -0,0 +1,500 @@
+/*
+* TLS Messages
+* (C) 2004-2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_MESSAGES_H__
+#define BOTAN_TLS_MESSAGES_H__
+
+#include <botan/internal/tls_handshake_state.h>
+#include <botan/tls_session.h>
+#include <botan/tls_policy.h>
+#include <botan/tls_magic.h>
+#include <botan/tls_ciphersuite.h>
+#include <botan/bigint.h>
+#include <botan/pkcs8.h>
+#include <botan/x509cert.h>
+#include <vector>
+
+namespace Botan {
+
+class Credentials_Manager;
+class SRP6_Server_Session;
+
+namespace TLS {
+
+class Record_Writer;
+class Record_Reader;
+
+/**
+* TLS Handshake Message Base Class
+*/
+class Handshake_Message
+ {
+ public:
+ virtual MemoryVector<byte> serialize() const = 0;
+ virtual Handshake_Type type() const = 0;
+
+ Handshake_Message() {}
+ virtual ~Handshake_Message() {}
+ private:
+ Handshake_Message(const Handshake_Message&) {}
+ Handshake_Message& operator=(const Handshake_Message&) { return (*this); }
+ };
+
+MemoryVector<byte> make_hello_random(RandomNumberGenerator& rng);
+
+/**
+* DTLS Hello Verify Request
+*/
+class Hello_Verify_Request : public Handshake_Message
+ {
+ public:
+ MemoryVector<byte> serialize() const;
+ Handshake_Type type() const { return HELLO_VERIFY_REQUEST; }
+
+ MemoryVector<byte> cookie() const { return m_cookie; }
+
+ Hello_Verify_Request(const MemoryRegion<byte>& buf);
+
+ Hello_Verify_Request(const MemoryVector<byte>& client_hello_bits,
+ const std::string& client_identity,
+ const SymmetricKey& secret_key);
+ private:
+ MemoryVector<byte> m_cookie;
+ };
+
+/**
+* Client Hello Message
+*/
+class Client_Hello : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return CLIENT_HELLO; }
+
+ Protocol_Version version() const { return m_version; }
+
+ const MemoryVector<byte>& session_id() const { return m_session_id; }
+
+ const std::vector<std::pair<std::string, std::string> >& supported_algos() const
+ { return m_supported_algos; }
+
+ const std::vector<std::string>& supported_ecc_curves() const
+ { return m_supported_curves; }
+
+ std::vector<u16bit> ciphersuites() const { return m_suites; }
+ std::vector<byte> compression_methods() const { return m_comp_methods; }
+
+ const MemoryVector<byte>& random() const { return m_random; }
+
+ std::string sni_hostname() const { return m_hostname; }
+
+ std::string srp_identifier() const { return m_srp_identifier; }
+
+ bool secure_renegotiation() const { return m_secure_renegotiation; }
+
+ const MemoryVector<byte>& renegotiation_info()
+ { return m_renegotiation_info; }
+
+ bool offered_suite(u16bit ciphersuite) const;
+
+ bool next_protocol_notification() const { return m_next_protocol; }
+
+ size_t fragment_size() const { return m_fragment_size; }
+
+ bool supports_session_ticket() const { return m_supports_session_ticket; }
+
+ const MemoryRegion<byte>& session_ticket() const
+ { return m_session_ticket; }
+
+ bool supports_heartbeats() const { return m_supports_heartbeats; }
+
+ bool peer_can_send_heartbeats() const { return m_peer_can_send_heartbeats; }
+
+ Client_Hello(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const Policy& policy,
+ RandomNumberGenerator& rng,
+ const MemoryRegion<byte>& reneg_info,
+ bool next_protocol = false,
+ const std::string& hostname = "",
+ const std::string& srp_identifier = "");
+
+ Client_Hello(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const Policy& policy,
+ RandomNumberGenerator& rng,
+ const MemoryRegion<byte>& reneg_info,
+ const Session& resumed_session,
+ bool next_protocol = false);
+
+ Client_Hello(const MemoryRegion<byte>& buf,
+ Handshake_Type type);
+
+ private:
+ MemoryVector<byte> serialize() const;
+ void deserialize(const MemoryRegion<byte>& buf);
+ void deserialize_sslv2(const MemoryRegion<byte>& buf);
+
+ Protocol_Version m_version;
+ MemoryVector<byte> m_session_id, m_random;
+ std::vector<u16bit> m_suites;
+ std::vector<byte> m_comp_methods;
+ std::string m_hostname;
+ std::string m_srp_identifier;
+ bool m_next_protocol;
+
+ size_t m_fragment_size;
+ bool m_secure_renegotiation;
+ MemoryVector<byte> m_renegotiation_info;
+
+ std::vector<std::pair<std::string, std::string> > m_supported_algos;
+ std::vector<std::string> m_supported_curves;
+
+ bool m_supports_session_ticket;
+ MemoryVector<byte> m_session_ticket;
+
+ bool m_supports_heartbeats;
+ bool m_peer_can_send_heartbeats;
+ };
+
+/**
+* Server Hello Message
+*/
+class Server_Hello : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return SERVER_HELLO; }
+
+ Protocol_Version version() { return m_version; }
+
+ const MemoryVector<byte>& random() const { return m_random; }
+
+ const MemoryVector<byte>& session_id() const { return m_session_id; }
+
+ u16bit ciphersuite() const { return m_ciphersuite; }
+
+ byte compression_method() const { return m_comp_method; }
+
+ bool secure_renegotiation() const { return m_secure_renegotiation; }
+
+ bool next_protocol_notification() const { return m_next_protocol; }
+
+ bool supports_session_ticket() const { return m_supports_session_ticket; }
+
+ const std::vector<std::string>& next_protocols() const
+ { return m_next_protocols; }
+
+ size_t fragment_size() const { return m_fragment_size; }
+
+ const MemoryVector<byte>& renegotiation_info()
+ { return m_renegotiation_info; }
+
+ bool supports_heartbeats() const { return m_supports_heartbeats; }
+
+ bool peer_can_send_heartbeats() const { return m_peer_can_send_heartbeats; }
+
+ Server_Hello(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const MemoryRegion<byte>& session_id,
+ Protocol_Version ver,
+ u16bit ciphersuite,
+ byte compression,
+ size_t max_fragment_size,
+ bool client_has_secure_renegotiation,
+ const MemoryRegion<byte>& reneg_info,
+ bool offer_session_ticket,
+ bool client_has_npn,
+ const std::vector<std::string>& next_protocols,
+ bool client_has_heartbeat,
+ RandomNumberGenerator& rng);
+
+ Server_Hello(const MemoryRegion<byte>& buf);
+ private:
+ MemoryVector<byte> serialize() const;
+
+ Protocol_Version m_version;
+ MemoryVector<byte> m_session_id, m_random;
+ u16bit m_ciphersuite;
+ byte m_comp_method;
+
+ size_t m_fragment_size;
+ bool m_secure_renegotiation;
+ MemoryVector<byte> m_renegotiation_info;
+
+ bool m_next_protocol;
+ std::vector<std::string> m_next_protocols;
+ bool m_supports_session_ticket;
+
+ bool m_supports_heartbeats;
+ bool m_peer_can_send_heartbeats;
+ };
+
+/**
+* Client Key Exchange Message
+*/
+class Client_Key_Exchange : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return CLIENT_KEX; }
+
+ const SecureVector<byte>& pre_master_secret() const
+ { return pre_master; }
+
+ Client_Key_Exchange(Record_Writer& output,
+ Handshake_State* state,
+ Credentials_Manager& creds,
+ const std::vector<X509_Certificate>& peer_certs,
+ const std::string& hostname,
+ RandomNumberGenerator& rng);
+
+ Client_Key_Exchange(const MemoryRegion<byte>& buf,
+ const Handshake_State* state,
+ Credentials_Manager& creds,
+ const Policy& policy,
+ RandomNumberGenerator& rng);
+
+ private:
+ MemoryVector<byte> serialize() const { return key_material; }
+
+ SecureVector<byte> key_material, pre_master;
+ };
+
+/**
+* Certificate Message
+*/
+class Certificate : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return CERTIFICATE; }
+ const std::vector<X509_Certificate>& cert_chain() const { return m_certs; }
+
+ size_t count() const { return m_certs.size(); }
+ bool empty() const { return m_certs.empty(); }
+
+ Certificate(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const std::vector<X509_Certificate>& certs);
+
+ Certificate(const MemoryRegion<byte>& buf);
+ private:
+ MemoryVector<byte> serialize() const;
+
+ std::vector<X509_Certificate> m_certs;
+ };
+
+/**
+* Certificate Request Message
+*/
+class Certificate_Req : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return CERTIFICATE_REQUEST; }
+
+ const std::vector<std::string>& acceptable_cert_types() const
+ { return cert_key_types; }
+
+ std::vector<X509_DN> acceptable_CAs() const { return names; }
+
+ std::vector<std::pair<std::string, std::string> > supported_algos() const
+ { return m_supported_algos; }
+
+ Certificate_Req(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const Policy& policy,
+ const std::vector<X509_Certificate>& allowed_cas,
+ Protocol_Version version);
+
+ Certificate_Req(const MemoryRegion<byte>& buf,
+ Protocol_Version version);
+ private:
+ MemoryVector<byte> serialize() const;
+
+ std::vector<X509_DN> names;
+ std::vector<std::string> cert_key_types;
+
+ std::vector<std::pair<std::string, std::string> > m_supported_algos;
+ };
+
+/**
+* Certificate Verify Message
+*/
+class Certificate_Verify : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return CERTIFICATE_VERIFY; }
+
+ /**
+ * Check the signature on a certificate verify message
+ * @param cert the purported certificate
+ * @param state the handshake state
+ */
+ bool verify(const X509_Certificate& cert,
+ Handshake_State* state);
+
+ Certificate_Verify(Record_Writer& writer,
+ Handshake_State* state,
+ RandomNumberGenerator& rng,
+ const Private_Key* key);
+
+ Certificate_Verify(const MemoryRegion<byte>& buf,
+ Protocol_Version version);
+ private:
+ MemoryVector<byte> serialize() const;
+
+ std::string sig_algo; // sig algo used to create signature
+ std::string hash_algo; // hash used to create signature
+ MemoryVector<byte> signature;
+ };
+
+/**
+* Finished Message
+*/
+class Finished : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return FINISHED; }
+
+ MemoryVector<byte> verify_data() const
+ { return verification_data; }
+
+ bool verify(Handshake_State* state,
+ Connection_Side side);
+
+ Finished(Record_Writer& writer,
+ Handshake_State* state,
+ Connection_Side side);
+
+ Finished(const MemoryRegion<byte>& buf);
+ private:
+ MemoryVector<byte> serialize() const;
+
+ Connection_Side side;
+ MemoryVector<byte> verification_data;
+ };
+
+/**
+* Hello Request Message
+*/
+class Hello_Request : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return HELLO_REQUEST; }
+
+ Hello_Request(Record_Writer& writer);
+ Hello_Request(const MemoryRegion<byte>& buf);
+ private:
+ MemoryVector<byte> serialize() const;
+ };
+
+/**
+* Server Key Exchange Message
+*/
+class Server_Key_Exchange : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return SERVER_KEX; }
+
+ const MemoryVector<byte>& params() const { return m_params; }
+
+ bool verify(const X509_Certificate& cert,
+ Handshake_State* state) const;
+
+ // Only valid for certain kex types
+ const Private_Key& server_kex_key() const;
+
+ // Only valid for SRP negotiation
+ SRP6_Server_Session& server_srp_params();
+
+ Server_Key_Exchange(Record_Writer& writer,
+ Handshake_State* state,
+ const Policy& policy,
+ Credentials_Manager& creds,
+ RandomNumberGenerator& rng,
+ const Private_Key* signing_key = 0);
+
+ Server_Key_Exchange(const MemoryRegion<byte>& buf,
+ const std::string& kex_alg,
+ const std::string& sig_alg,
+ Protocol_Version version);
+
+ ~Server_Key_Exchange();
+ private:
+ MemoryVector<byte> serialize() const;
+
+ Private_Key* m_kex_key;
+ SRP6_Server_Session* m_srp_params;
+
+ MemoryVector<byte> m_params;
+
+ std::string m_sig_algo; // sig algo used to create signature
+ std::string m_hash_algo; // hash used to create signature
+ MemoryVector<byte> m_signature;
+ };
+
+/**
+* Server Hello Done Message
+*/
+class Server_Hello_Done : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return SERVER_HELLO_DONE; }
+
+ Server_Hello_Done(Record_Writer& writer, Handshake_Hash& hash);
+ Server_Hello_Done(const MemoryRegion<byte>& buf);
+ private:
+ MemoryVector<byte> serialize() const;
+ };
+
+/**
+* Next Protocol Message
+*/
+class Next_Protocol : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return NEXT_PROTOCOL; }
+
+ std::string protocol() const { return m_protocol; }
+
+ Next_Protocol(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const std::string& protocol);
+
+ Next_Protocol(const MemoryRegion<byte>& buf);
+ private:
+ MemoryVector<byte> serialize() const;
+
+ std::string m_protocol;
+ };
+
+class New_Session_Ticket : public Handshake_Message
+ {
+ public:
+ Handshake_Type type() const { return NEW_SESSION_TICKET; }
+
+ u32bit ticket_lifetime_hint() const { return m_ticket_lifetime_hint; }
+ const MemoryVector<byte>& ticket() const { return m_ticket; }
+
+ New_Session_Ticket(Record_Writer& writer,
+ Handshake_Hash& hash,
+ const MemoryRegion<byte>& ticket,
+ u32bit lifetime);
+
+ New_Session_Ticket(Record_Writer& writer,
+ Handshake_Hash& hash);
+
+ New_Session_Ticket(const MemoryRegion<byte>& buf);
+ private:
+ MemoryVector<byte> serialize() const;
+
+ u32bit m_ticket_lifetime_hint;
+ MemoryVector<byte> m_ticket;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_policy.cpp b/src/tls/tls_policy.cpp
new file mode 100644
index 000000000..f240bebac
--- /dev/null
+++ b/src/tls/tls_policy.cpp
@@ -0,0 +1,265 @@
+/*
+* Policies for TLS
+* (C) 2004-2010,2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_policy.h>
+#include <botan/tls_ciphersuite.h>
+#include <botan/tls_magic.h>
+#include <botan/tls_exceptn.h>
+#include <botan/internal/stl_util.h>
+
+namespace Botan {
+
+namespace TLS {
+
+std::vector<std::string> Policy::allowed_ciphers() const
+ {
+ std::vector<std::string> allowed;
+
+ allowed.push_back("AES-256");
+ allowed.push_back("AES-128");
+ allowed.push_back("3DES");
+ allowed.push_back("ARC4");
+ //allowed.push_back("Camellia");
+ //allowed.push_back("SEED");
+
+ return allowed;
+ }
+
+std::vector<std::string> Policy::allowed_hashes() const
+ {
+ std::vector<std::string> allowed;
+
+ allowed.push_back("SHA-512");
+ allowed.push_back("SHA-384");
+ allowed.push_back("SHA-256");
+ allowed.push_back("SHA-224");
+ allowed.push_back("SHA-1");
+ //allowed.push_back("MD5");
+
+ return allowed;
+ }
+
+std::vector<std::string> Policy::allowed_key_exchange_methods() const
+ {
+ std::vector<std::string> allowed;
+
+ allowed.push_back("SRP_SHA");
+ //allowed.push_back("ECDHE_PSK");
+ //allowed.push_back("DHE_PSK");
+ //allowed.push_back("PSK");
+
+ allowed.push_back("ECDH");
+ allowed.push_back("DH");
+ allowed.push_back("RSA");
+
+ return allowed;
+ }
+
+std::vector<std::string> Policy::allowed_signature_methods() const
+ {
+ std::vector<std::string> allowed;
+
+ allowed.push_back("ECDSA");
+ allowed.push_back("RSA");
+ allowed.push_back("DSA");
+ //allowed.push_back("");
+
+ return allowed;
+ }
+
+std::vector<std::string> Policy::allowed_ecc_curves() const
+ {
+ std::vector<std::string> curves;
+ curves.push_back("secp521r1");
+ curves.push_back("secp384r1");
+ curves.push_back("secp256r1");
+ curves.push_back("secp256k1");
+ curves.push_back("secp224r1");
+ curves.push_back("secp224k1");
+ curves.push_back("secp192r1");
+ curves.push_back("secp192k1");
+ curves.push_back("secp160r2");
+ curves.push_back("secp160r1");
+ curves.push_back("secp160k1");
+ return curves;
+ }
+
+/*
+* Choose an ECC curve to use
+*/
+std::string Policy::choose_curve(const std::vector<std::string>& curve_names) const
+ {
+ const std::vector<std::string> our_curves = allowed_ecc_curves();
+
+ for(size_t i = 0; i != our_curves.size(); ++i)
+ if(value_exists(curve_names, our_curves[i]))
+ return our_curves[i];
+
+ return ""; // no shared curve
+ }
+
+DL_Group Policy::dh_group() const
+ {
+ return DL_Group("modp/ietf/2048");
+ }
+
+/*
+* Return allowed compression algorithms
+*/
+std::vector<byte> Policy::compression() const
+ {
+ std::vector<byte> algs;
+ algs.push_back(NO_COMPRESSION);
+ return algs;
+ }
+
+u32bit Policy::session_ticket_lifetime() const
+ {
+ return 86400; // 1 day
+ }
+
+Protocol_Version Policy::min_version() const
+ {
+ return Protocol_Version::SSL_V3;
+ }
+
+Protocol_Version Policy::pref_version() const
+ {
+ return Protocol_Version::TLS_V12;
+ }
+
+namespace {
+
+class Ciphersuite_Preference_Ordering
+ {
+ public:
+ Ciphersuite_Preference_Ordering(const std::vector<std::string>& ciphers,
+ const std::vector<std::string>& hashes,
+ const std::vector<std::string>& kex,
+ const std::vector<std::string>& sigs) :
+ m_ciphers(ciphers), m_hashes(hashes), m_kex(kex), m_sigs(sigs) {}
+
+ bool operator()(const Ciphersuite& a, const Ciphersuite& b) const
+ {
+ if(a.kex_algo() != b.kex_algo())
+ {
+ for(size_t i = 0; i != m_kex.size(); ++i)
+ {
+ if(a.kex_algo() == m_kex[i])
+ return true;
+ if(b.kex_algo() == m_kex[i])
+ return false;
+ }
+ }
+
+ if(a.cipher_algo() != b.cipher_algo())
+ {
+ for(size_t i = 0; i != m_ciphers.size(); ++i)
+ {
+ if(a.cipher_algo() == m_ciphers[i])
+ return true;
+ if(b.cipher_algo() == m_ciphers[i])
+ return false;
+ }
+ }
+
+ if(a.cipher_keylen() != b.cipher_keylen())
+ {
+ if(a.cipher_keylen() < b.cipher_keylen())
+ return false;
+ if(a.cipher_keylen() > b.cipher_keylen())
+ return true;
+ }
+
+ if(a.sig_algo() != b.sig_algo())
+ {
+ for(size_t i = 0; i != m_sigs.size(); ++i)
+ {
+ if(a.sig_algo() == m_sigs[i])
+ return true;
+ if(b.sig_algo() == m_sigs[i])
+ return false;
+ }
+ }
+
+ if(a.mac_algo() != b.mac_algo())
+ {
+ for(size_t i = 0; i != m_hashes.size(); ++i)
+ {
+ if(a.mac_algo() == m_hashes[i])
+ return true;
+ if(b.mac_algo() == m_hashes[i])
+ return false;
+ }
+ }
+
+ return false; // equal (?!?)
+ }
+ private:
+ std::vector<std::string> m_ciphers, m_hashes, m_kex, m_sigs;
+
+ };
+
+}
+
+std::vector<u16bit> ciphersuite_list(const Policy& policy,
+ bool have_srp)
+ {
+ const std::vector<std::string> ciphers = policy.allowed_ciphers();
+ const std::vector<std::string> hashes = policy.allowed_hashes();
+ const std::vector<std::string> kex = policy.allowed_key_exchange_methods();
+ const std::vector<std::string> sigs = policy.allowed_signature_methods();
+
+ Ciphersuite_Preference_Ordering order(ciphers, hashes, kex, sigs);
+
+ std::map<Ciphersuite, u16bit, Ciphersuite_Preference_Ordering>
+ ciphersuites(order);
+
+ for(size_t i = 0; i != 65536; ++i)
+ {
+ Ciphersuite suite = Ciphersuite::by_id(i);
+
+ if(!suite.valid())
+ continue; // not a ciphersuite we know, skip
+
+ if(!have_srp && suite.kex_algo() == "SRP_SHA")
+ continue;
+
+ if(!value_exists(kex, suite.kex_algo()))
+ continue; // unsupported key exchange
+
+ if(!value_exists(ciphers, suite.cipher_algo()))
+ continue; // unsupported cipher
+
+ if(!value_exists(hashes, suite.mac_algo()))
+ continue; // unsupported MAC algo
+
+ if(!value_exists(sigs, suite.sig_algo()))
+ {
+ // allow if it's an empty sig algo and we want to use PSK
+ if(suite.sig_algo() != "" || !suite.psk_ciphersuite())
+ continue;
+ }
+
+ // OK, allow it:
+ ciphersuites[suite] = i;
+ }
+
+ std::vector<u16bit> ciphersuite_codes;
+
+ for(std::map<Ciphersuite, u16bit, Ciphersuite_Preference_Ordering>::iterator i = ciphersuites.begin();
+ i != ciphersuites.end(); ++i)
+ {
+ ciphersuite_codes.push_back(i->second);
+ }
+
+ return ciphersuite_codes;
+ }
+
+}
+
+}
diff --git a/src/tls/tls_policy.h b/src/tls/tls_policy.h
new file mode 100644
index 000000000..c3a0fc29e
--- /dev/null
+++ b/src/tls/tls_policy.h
@@ -0,0 +1,127 @@
+/*
+* Policies
+* (C) 2004-2006 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_POLICY_H__
+#define BOTAN_TLS_POLICY_H__
+
+#include <botan/tls_version.h>
+#include <botan/x509cert.h>
+#include <botan/dl_group.h>
+#include <vector>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* TLS Policy Base Class
+* Inherit and overload as desired to suit local policy concerns
+*/
+class BOTAN_DLL Policy
+ {
+ public:
+
+ /**
+ * Returns a list of ciphers we are willing to negotiate, in
+ * order of preference. Allowed values: any block cipher name, or
+ * ARC4.
+ */
+ virtual std::vector<std::string> allowed_ciphers() const;
+
+ /**
+ * Returns a list of hash algorithms we are willing to use, in
+ * order of preference. This is used for both MACs and signatures.
+ * Allowed values: any hash name, though currently only MD5,
+ * SHA-1, and the SHA-2 variants are used.
+ */
+ virtual std::vector<std::string> allowed_hashes() const;
+
+ /**
+ * Returns a list of key exchange algorithms we are willing to
+ * use, in order of preference. Allowed values: DH, empty string
+ * (representing RSA using server certificate key)
+ */
+ virtual std::vector<std::string> allowed_key_exchange_methods() const;
+
+ /**
+ * Returns a list of signature algorithms we are willing to
+ * use, in order of preference. Allowed values RSA and DSA.
+ */
+ virtual std::vector<std::string> allowed_signature_methods() const;
+
+ /**
+ * Return list of ECC curves we are willing to use in order of preference
+ */
+ virtual std::vector<std::string> allowed_ecc_curves() const;
+
+ /**
+ * Returns a list of signature algorithms we are willing to use,
+ * in order of preference. Allowed values any value of
+ * Compression_Method.
+ */
+ virtual std::vector<byte> compression() const;
+
+ /**
+ * Choose an elliptic curve to use
+ */
+ virtual std::string choose_curve(const std::vector<std::string>& curve_names) const;
+
+ /**
+ * Require support for RFC 5746 extensions to enable
+ * renegotiation.
+ *
+ * @warning Changing this to false exposes you to injected
+ * plaintext attacks. Read the RFC for background.
+ */
+ virtual bool require_secure_renegotiation() const { return true; }
+
+ /**
+ * Return the group to use for ephemeral Diffie-Hellman key agreement
+ */
+ virtual DL_Group dh_group() const;
+
+ /**
+ * If this function returns false, unknown SRP/PSK identifiers
+ * will be rejected with an unknown_psk_identifier alert as soon
+ * as the non-existence is identified. Otherwise, a false
+ * identifier value will be used and the protocol allowed to
+ * proceed, causing the handshake to eventually fail without
+ * revealing that the username does not exist on this system.
+ */
+ virtual bool hide_unknown_users() const { return false; }
+
+ /**
+ * Return the allowed lifetime of a session ticket. If 0, session
+ * tickets do not expire until the session ticket key rolls over.
+ * Expired session tickets cannot be used to resume a session.
+ */
+ virtual u32bit session_ticket_lifetime() const;
+
+ /**
+ * @return the minimum version that we are willing to negotiate
+ */
+ virtual Protocol_Version min_version() const;
+
+ /**
+ * @return the version we would prefer to negotiate
+ */
+ virtual Protocol_Version pref_version() const;
+
+ virtual ~Policy() {}
+ };
+
+/**
+* Return allowed ciphersuites, in order of preference
+*/
+std::vector<u16bit> ciphersuite_list(const Policy& policy,
+ bool have_srp);
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_reader.h b/src/tls/tls_reader.h
new file mode 100644
index 000000000..f6b0d4088
--- /dev/null
+++ b/src/tls/tls_reader.h
@@ -0,0 +1,233 @@
+/*
+* TLS Data Reader
+* (C) 2010-2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_READER_H__
+#define BOTAN_TLS_READER_H__
+
+#include <botan/exceptn.h>
+#include <botan/secmem.h>
+#include <botan/loadstor.h>
+#include <string>
+#include <vector>
+#include <stdexcept>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Helper class for decoding TLS protocol messages
+*/
+class TLS_Data_Reader
+ {
+ public:
+ TLS_Data_Reader(const MemoryRegion<byte>& buf_in) :
+ buf(buf_in), offset(0) {}
+
+ void assert_done() const
+ {
+ if(has_remaining())
+ throw Decoding_Error("Extra bytes at end of message");
+ }
+
+ size_t remaining_bytes() const
+ {
+ return buf.size() - offset;
+ }
+
+ bool has_remaining() const
+ {
+ return (remaining_bytes() > 0);
+ }
+
+ void discard_next(size_t bytes)
+ {
+ assert_at_least(bytes);
+ offset += bytes;
+ }
+
+ u16bit get_u32bit()
+ {
+ assert_at_least(4);
+ u16bit result = make_u32bit(buf[offset ], buf[offset+1],
+ buf[offset+2], buf[offset+3]);
+ offset += 4;
+ return result;
+ }
+
+ u16bit get_u16bit()
+ {
+ assert_at_least(2);
+ u16bit result = make_u16bit(buf[offset], buf[offset+1]);
+ offset += 2;
+ return result;
+ }
+
+ byte get_byte()
+ {
+ assert_at_least(1);
+ byte result = buf[offset];
+ offset += 1;
+ return result;
+ }
+
+ template<typename T, typename Container>
+ Container get_elem(size_t num_elems)
+ {
+ assert_at_least(num_elems * sizeof(T));
+
+ Container result(num_elems);
+
+ for(size_t i = 0; i != num_elems; ++i)
+ result[i] = load_be<T>(&buf[offset], i);
+
+ offset += num_elems * sizeof(T);
+
+ return result;
+ }
+
+ template<typename T>
+ SecureVector<T> get_range(size_t len_bytes,
+ size_t min_elems,
+ size_t max_elems)
+ {
+ const size_t num_elems =
+ get_num_elems(len_bytes, sizeof(T), min_elems, max_elems);
+
+ return get_elem<T, SecureVector<T> >(num_elems);
+ }
+
+ template<typename T>
+ std::vector<T> get_range_vector(size_t len_bytes,
+ size_t min_elems,
+ size_t max_elems)
+ {
+ const size_t num_elems =
+ get_num_elems(len_bytes, sizeof(T), min_elems, max_elems);
+
+ return get_elem<T, std::vector<T> >(num_elems);
+ }
+
+ std::string get_string(size_t len_bytes,
+ size_t min_bytes,
+ size_t max_bytes)
+ {
+ std::vector<byte> v =
+ get_range_vector<byte>(len_bytes, min_bytes, max_bytes);
+
+ return std::string(reinterpret_cast<char*>(&v[0]), v.size());
+ }
+
+ template<typename T>
+ SecureVector<T> get_fixed(size_t size)
+ {
+ return get_elem<T, SecureVector<T> >(size);
+ }
+
+ private:
+ size_t get_length_field(size_t len_bytes)
+ {
+ assert_at_least(len_bytes);
+
+ if(len_bytes == 1)
+ return get_byte();
+ else if(len_bytes == 2)
+ return get_u16bit();
+
+ throw Decoding_Error("TLS_Data_Reader: Bad length size");
+ }
+
+ size_t get_num_elems(size_t len_bytes,
+ size_t T_size,
+ size_t min_elems,
+ size_t max_elems)
+ {
+ const size_t byte_length = get_length_field(len_bytes);
+
+ if(byte_length % T_size != 0)
+ throw Decoding_Error("TLS_Data_Reader: Size isn't multiple of T");
+
+ const size_t num_elems = byte_length / T_size;
+
+ if(num_elems < min_elems || num_elems > max_elems)
+ throw Decoding_Error("TLS_Data_Reader: Range outside paramaters");
+
+ return num_elems;
+ }
+
+ void assert_at_least(size_t n) const
+ {
+ if(buf.size() - offset < n)
+ {
+ throw Decoding_Error("TLS_Data_Reader: Expected " + to_string(n) +
+ " bytes remaining, only " + to_string(buf.size()-offset) +
+ " left");
+ }
+ }
+
+ const MemoryRegion<byte>& buf;
+ size_t offset;
+ };
+
+/**
+* Helper function for encoding length-tagged vectors
+*/
+template<typename T>
+void append_tls_length_value(MemoryRegion<byte>& buf,
+ const T* vals,
+ size_t vals_size,
+ size_t tag_size)
+ {
+ const size_t T_size = sizeof(T);
+ const size_t val_bytes = T_size * vals_size;
+
+ if(tag_size != 1 && tag_size != 2)
+ throw std::invalid_argument("append_tls_length_value: invalid tag size");
+
+ if((tag_size == 1 && val_bytes > 255) ||
+ (tag_size == 2 && val_bytes > 65535))
+ throw std::invalid_argument("append_tls_length_value: value too large");
+
+ for(size_t i = 0; i != tag_size; ++i)
+ buf.push_back(get_byte(sizeof(val_bytes)-tag_size+i, val_bytes));
+
+ for(size_t i = 0; i != vals_size; ++i)
+ for(size_t j = 0; j != T_size; ++j)
+ buf.push_back(get_byte(j, vals[i]));
+ }
+
+template<typename T>
+void append_tls_length_value(MemoryRegion<byte>& buf,
+ const MemoryRegion<T>& vals,
+ size_t tag_size)
+ {
+ append_tls_length_value(buf, &vals[0], vals.size(), tag_size);
+ }
+
+template<typename T>
+void append_tls_length_value(MemoryRegion<byte>& buf,
+ const std::vector<T>& vals,
+ size_t tag_size)
+ {
+ append_tls_length_value(buf, &vals[0], vals.size(), tag_size);
+ }
+
+inline void append_tls_length_value(MemoryRegion<byte>& buf,
+ const std::string& str,
+ size_t tag_size)
+ {
+ append_tls_length_value(buf,
+ reinterpret_cast<const byte*>(&str[0]),
+ str.size(),
+ tag_size);
+ }
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_record.h b/src/tls/tls_record.h
new file mode 100644
index 000000000..38eae7823
--- /dev/null
+++ b/src/tls/tls_record.h
@@ -0,0 +1,149 @@
+/*
+* TLS Record Handling
+* (C) 2004-2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_RECORDS_H__
+#define BOTAN_TLS_RECORDS_H__
+
+#include <botan/tls_ciphersuite.h>
+#include <botan/tls_alert.h>
+#include <botan/tls_magic.h>
+#include <botan/tls_version.h>
+#include <botan/pipe.h>
+#include <botan/mac.h>
+#include <botan/secqueue.h>
+#include <vector>
+
+#if defined(BOTAN_USE_STD_TR1)
+
+#if defined(BOTAN_BUILD_COMPILER_IS_MSVC)
+ #include <functional>
+#else
+ #include <tr1/functional>
+#endif
+
+#elif defined(BOTAN_USE_BOOST_TR1)
+ #include <boost/tr1/functional.hpp>
+#else
+ #error "No TR1 library defined for use"
+#endif
+
+namespace Botan {
+
+namespace TLS {
+
+class Session_Keys;
+
+/**
+* TLS Record Writer
+*/
+class BOTAN_DLL Record_Writer
+ {
+ public:
+ void send(byte type, const byte input[], size_t length);
+ void send(byte type, byte val) { send(type, &val, 1); }
+
+ void send(byte type, const MemoryRegion<byte>& input)
+ { send(type, &input[0], input.size()); }
+
+ MemoryVector<byte> send(class Handshake_Message& msg);
+
+ void send_alert(const Alert& alert);
+
+ void activate(Connection_Side side,
+ const Ciphersuite& suite,
+ const Session_Keys& keys,
+ byte compression_method);
+
+ void set_version(Protocol_Version version);
+
+ void reset();
+
+ void set_maximum_fragment_size(size_t max_fragment);
+
+ Record_Writer(std::tr1::function<void (const byte[], size_t)> output_fn);
+
+ ~Record_Writer() { delete m_mac; }
+ private:
+ Record_Writer(const Record_Writer&) {}
+ Record_Writer& operator=(const Record_Writer&) { return (*this); }
+
+ void send_record(byte type, const byte input[], size_t length);
+
+ std::tr1::function<void (const byte[], size_t)> m_output_fn;
+
+ MemoryVector<byte> m_writebuf;
+
+ Pipe m_cipher;
+ MessageAuthenticationCode* m_mac;
+
+ size_t m_block_size, m_mac_size, m_iv_size, m_max_fragment;
+
+ u64bit m_seq_no;
+ Protocol_Version m_version;
+ };
+
+/**
+* TLS Record Reader
+*/
+class BOTAN_DLL Record_Reader
+ {
+ public:
+
+ /**
+ * @param input new input data (may be NULL if input_size == 0)
+ * @param input_size size of input in bytes
+ * @param input_consumed is set to the number of bytes of input
+ * that were consumed
+ * @param msg_type is set to the type of the message just read if
+ * this function returns 0
+ * @param msg is set to the contents of the record
+ * @return number of bytes still needed (minimum), or 0 if success
+ */
+ size_t add_input(const byte input[], size_t input_size,
+ size_t& input_consumed,
+ byte& msg_type,
+ MemoryVector<byte>& msg);
+
+ void activate(Connection_Side side,
+ const Ciphersuite& suite,
+ const Session_Keys& keys,
+ byte compression_method);
+
+ void set_version(Protocol_Version version);
+
+ void reset();
+
+ void set_maximum_fragment_size(size_t max_fragment);
+
+ Record_Reader();
+
+ ~Record_Reader() { delete m_mac; }
+ private:
+ Record_Reader(const Record_Reader&) {}
+ Record_Reader& operator=(const Record_Reader&) { return (*this); }
+
+ size_t fill_buffer_to(const byte*& input,
+ size_t& input_size,
+ size_t& input_consumed,
+ size_t desired);
+
+ MemoryVector<byte> m_readbuf;
+ MemoryVector<byte> m_macbuf;
+ size_t m_readbuf_pos;
+
+ Pipe m_cipher;
+ MessageAuthenticationCode* m_mac;
+ size_t m_block_size, m_iv_size, m_max_fragment;
+ u64bit m_seq_no;
+ Protocol_Version m_version;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_server.cpp b/src/tls/tls_server.cpp
new file mode 100644
index 000000000..9da4ca3b8
--- /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,
+ u32bit 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 &&
+ 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::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(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,
+ 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");
+ }
+
+}
+
+}
diff --git a/src/tls/tls_server.h b/src/tls/tls_server.h
new file mode 100644
index 000000000..6ade91afc
--- /dev/null
+++ b/src/tls/tls_server.h
@@ -0,0 +1,74 @@
+/*
+* TLS Server
+* (C) 2004-2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_SERVER_H__
+#define BOTAN_TLS_SERVER_H__
+
+#include <botan/tls_channel.h>
+#include <botan/tls_session_manager.h>
+#include <botan/credentials_manager.h>
+#include <vector>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* TLS Server
+*/
+class BOTAN_DLL Server : public Channel
+ {
+ public:
+ /**
+ * Server initialization
+ */
+ Server(std::tr1::function<void (const byte[], size_t)> socket_output_fn,
+ std::tr1::function<void (const byte[], size_t, Alert)> proc_fn,
+ std::tr1::function<bool (const Session&)> handshake_complete,
+ Session_Manager& session_manager,
+ Credentials_Manager& creds,
+ const Policy& policy,
+ RandomNumberGenerator& rng,
+ const std::vector<std::string>& protocols =
+ std::vector<std::string>());
+
+ void renegotiate(bool force_full_renegotiation);
+
+ /**
+ * Return the server name indicator, if sent by the client
+ */
+ std::string server_name_indicator() const
+ { return m_hostname; }
+
+ /**
+ * Return the protocol negotiated with NPN extension
+ */
+ std::string next_protocol() const
+ { return m_next_protocol; }
+
+ private:
+ void read_handshake(byte, const MemoryRegion<byte>&);
+
+ void process_handshake_msg(Handshake_Type, const MemoryRegion<byte>&);
+
+ void alert_notify(const Alert& alert);
+
+ const Policy& policy;
+ RandomNumberGenerator& rng;
+ Session_Manager& session_manager;
+ Credentials_Manager& creds;
+
+ std::vector<std::string> m_possible_protocols;
+ std::string m_hostname;
+ std::string m_next_protocol;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_session.cpp b/src/tls/tls_session.cpp
new file mode 100644
index 000000000..0e8bf3051
--- /dev/null
+++ b/src/tls/tls_session.cpp
@@ -0,0 +1,248 @@
+/*
+* TLS Session State
+* (C) 2011-2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_session.h>
+#include <botan/der_enc.h>
+#include <botan/ber_dec.h>
+#include <botan/asn1_str.h>
+#include <botan/pem.h>
+#include <botan/time.h>
+#include <botan/lookup.h>
+#include <botan/loadstor.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+Session::Session(const MemoryRegion<byte>& session_identifier,
+ const MemoryRegion<byte>& master_secret,
+ Protocol_Version version,
+ u16bit ciphersuite,
+ byte compression_method,
+ Connection_Side side,
+ bool secure_renegotiation_supported,
+ size_t fragment_size,
+ const std::vector<X509_Certificate>& certs,
+ const MemoryRegion<byte>& ticket,
+ const std::string& sni_hostname,
+ const std::string& srp_identifier) :
+ m_start_time(system_time()),
+ m_identifier(session_identifier),
+ m_session_ticket(ticket),
+ m_master_secret(master_secret),
+ m_version(version),
+ m_ciphersuite(ciphersuite),
+ m_compression_method(compression_method),
+ m_connection_side(side),
+ m_secure_renegotiation_supported(secure_renegotiation_supported),
+ m_fragment_size(fragment_size),
+ m_peer_certs(certs),
+ m_sni_hostname(sni_hostname),
+ m_srp_identifier(srp_identifier)
+ {
+ }
+
+Session::Session(const std::string& pem)
+ {
+ SecureVector<byte> der = PEM_Code::decode_check_label(pem, "SSL SESSION");
+
+ *this = Session(&der[0], der.size());
+ }
+
+Session::Session(const byte ber[], size_t ber_len)
+ {
+ byte side_code = 0;
+ ASN1_String sni_hostname_str;
+ ASN1_String srp_identifier_str;
+
+ byte major_version = 0, minor_version = 0;
+
+ MemoryVector<byte> peer_cert_bits;
+
+ BER_Decoder(ber, ber_len)
+ .start_cons(SEQUENCE)
+ .decode_and_check(static_cast<size_t>(TLS_SESSION_PARAM_STRUCT_VERSION),
+ "Unknown version in session structure")
+ .decode_integer_type(m_start_time)
+ .decode_integer_type(major_version)
+ .decode_integer_type(minor_version)
+ .decode(m_identifier, OCTET_STRING)
+ .decode(m_session_ticket, OCTET_STRING)
+ .decode_integer_type(m_ciphersuite)
+ .decode_integer_type(m_compression_method)
+ .decode_integer_type(side_code)
+ .decode_integer_type(m_fragment_size)
+ .decode(m_secure_renegotiation_supported)
+ .decode(m_master_secret, OCTET_STRING)
+ .decode(peer_cert_bits, OCTET_STRING)
+ .decode(sni_hostname_str)
+ .decode(srp_identifier_str)
+ .end_cons()
+ .verify_end();
+
+ m_version = Protocol_Version(major_version, minor_version);
+ m_sni_hostname = sni_hostname_str.value();
+ m_srp_identifier = srp_identifier_str.value();
+ m_connection_side = static_cast<Connection_Side>(side_code);
+
+ if(!peer_cert_bits.empty())
+ {
+ DataSource_Memory certs(peer_cert_bits);
+
+ while(!certs.end_of_data())
+ m_peer_certs.push_back(X509_Certificate(certs));
+ }
+ }
+
+SecureVector<byte> Session::DER_encode() const
+ {
+ MemoryVector<byte> peer_cert_bits;
+ for(size_t i = 0; i != m_peer_certs.size(); ++i)
+ peer_cert_bits += m_peer_certs[i].BER_encode();
+
+ return DER_Encoder()
+ .start_cons(SEQUENCE)
+ .encode(static_cast<size_t>(TLS_SESSION_PARAM_STRUCT_VERSION))
+ .encode(static_cast<size_t>(m_start_time))
+ .encode(static_cast<size_t>(m_version.major_version()))
+ .encode(static_cast<size_t>(m_version.minor_version()))
+ .encode(m_identifier, OCTET_STRING)
+ .encode(m_session_ticket, OCTET_STRING)
+ .encode(static_cast<size_t>(m_ciphersuite))
+ .encode(static_cast<size_t>(m_compression_method))
+ .encode(static_cast<size_t>(m_connection_side))
+ .encode(static_cast<size_t>(m_fragment_size))
+ .encode(m_secure_renegotiation_supported)
+ .encode(m_master_secret, OCTET_STRING)
+ .encode(peer_cert_bits, OCTET_STRING)
+ .encode(ASN1_String(m_sni_hostname, UTF8_STRING))
+ .encode(ASN1_String(m_srp_identifier, UTF8_STRING))
+ .end_cons()
+ .get_contents();
+ }
+
+std::string Session::PEM_encode() const
+ {
+ return PEM_Code::encode(this->DER_encode(), "SSL SESSION");
+ }
+
+u32bit Session::session_age() const
+ {
+ return (system_time() - m_start_time);
+ }
+
+namespace {
+
+const u32bit SESSION_CRYPTO_MAGIC = 0x571B0E4E;
+const std::string SESSION_CRYPTO_CIPHER = "AES-256/CBC";
+const std::string SESSION_CRYPTO_MAC = "HMAC(SHA-256)";
+const std::string SESSION_CRYPTO_KDF = "KDF2(SHA-256)";
+
+const size_t MAGIC_LENGTH = 4;
+const size_t MAC_KEY_LENGTH = 32;
+const size_t CIPHER_KEY_LENGTH = 32;
+const size_t CIPHER_IV_LENGTH = 16;
+const size_t MAC_OUTPUT_LENGTH = 32;
+
+}
+
+MemoryVector<byte>
+Session::encrypt(const SymmetricKey& master_key,
+ RandomNumberGenerator& rng) const
+ {
+ std::auto_ptr<KDF> kdf(get_kdf(SESSION_CRYPTO_KDF));
+
+ SymmetricKey cipher_key =
+ kdf->derive_key(CIPHER_KEY_LENGTH,
+ master_key.bits_of(),
+ "tls.session.cipher-key");
+
+ SymmetricKey mac_key =
+ kdf->derive_key(MAC_KEY_LENGTH,
+ master_key.bits_of(),
+ "tls.session.mac-key");
+
+ InitializationVector cipher_iv(rng, 16);
+
+ std::auto_ptr<MessageAuthenticationCode> mac(get_mac(SESSION_CRYPTO_MAC));
+ mac->set_key(mac_key);
+
+ Pipe pipe(get_cipher(SESSION_CRYPTO_CIPHER, cipher_key, cipher_iv, ENCRYPTION));
+ pipe.process_msg(this->DER_encode());
+ MemoryVector<byte> ctext = pipe.read_all(0);
+
+ MemoryVector<byte> out(MAGIC_LENGTH);
+ store_be(SESSION_CRYPTO_MAGIC, &out[0]);
+ out += cipher_iv.bits_of();
+ out += ctext;
+
+ mac->update(out);
+
+ out += mac->final();
+ return out;
+ }
+
+Session Session::decrypt(const byte buf[], size_t buf_len,
+ const SymmetricKey& master_key)
+ {
+ try
+ {
+ const size_t MIN_CTEXT_SIZE = 4 * 16; // due to 48 byte master secret
+
+ if(buf_len < (MAGIC_LENGTH +
+ CIPHER_IV_LENGTH +
+ MIN_CTEXT_SIZE +
+ MAC_OUTPUT_LENGTH))
+ throw Decoding_Error("Encrypted TLS session too short to be valid");
+
+ if(load_be<u32bit>(buf, 0) != SESSION_CRYPTO_MAGIC)
+ throw Decoding_Error("Unknown header value in encrypted session");
+
+ std::auto_ptr<KDF> kdf(get_kdf(SESSION_CRYPTO_KDF));
+
+ SymmetricKey mac_key =
+ kdf->derive_key(MAC_KEY_LENGTH,
+ master_key.bits_of(),
+ "tls.session.mac-key");
+
+ std::auto_ptr<MessageAuthenticationCode> mac(get_mac(SESSION_CRYPTO_MAC));
+ mac->set_key(mac_key);
+
+ mac->update(&buf[0], buf_len - MAC_OUTPUT_LENGTH);
+ MemoryVector<byte> computed_mac = mac->final();
+
+ if(!same_mem(&buf[buf_len - MAC_OUTPUT_LENGTH], &computed_mac[0], computed_mac.size()))
+ throw Decoding_Error("MAC verification failed for encrypted session");
+
+ SymmetricKey cipher_key =
+ kdf->derive_key(CIPHER_KEY_LENGTH,
+ master_key.bits_of(),
+ "tls.session.cipher-key");
+
+ InitializationVector cipher_iv(&buf[MAGIC_LENGTH], CIPHER_IV_LENGTH);
+
+ const size_t CTEXT_OFFSET = MAGIC_LENGTH + CIPHER_IV_LENGTH;
+
+ Pipe pipe(get_cipher(SESSION_CRYPTO_CIPHER, cipher_key, cipher_iv, DECRYPTION));
+ pipe.process_msg(&buf[CTEXT_OFFSET],
+ buf_len - (MAC_OUTPUT_LENGTH + CTEXT_OFFSET));
+ SecureVector<byte> ber = pipe.read_all();
+
+ return Session(&ber[0], ber.size());
+ }
+ catch(std::exception& e)
+ {
+ throw Decoding_Error("Failed to decrypt encrypted session -" +
+ std::string(e.what()));
+ }
+ }
+
+}
+
+}
+
diff --git a/src/tls/tls_session.h b/src/tls/tls_session.h
new file mode 100644
index 000000000..290ee6dcc
--- /dev/null
+++ b/src/tls/tls_session.h
@@ -0,0 +1,215 @@
+/*
+* TLS Session
+* (C) 2011-2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_SESSION_STATE_H__
+#define BOTAN_TLS_SESSION_STATE_H__
+
+#include <botan/x509cert.h>
+#include <botan/tls_version.h>
+#include <botan/tls_ciphersuite.h>
+#include <botan/tls_magic.h>
+#include <botan/secmem.h>
+#include <botan/symkey.h>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Class representing a TLS session state
+*/
+class BOTAN_DLL Session
+ {
+ public:
+
+ /**
+ * Uninitialized session
+ */
+ Session() :
+ m_start_time(0),
+ m_version(),
+ m_ciphersuite(0),
+ m_compression_method(0),
+ m_connection_side(static_cast<Connection_Side>(0)),
+ m_secure_renegotiation_supported(false),
+ m_fragment_size(0)
+ {}
+
+ /**
+ * New session (sets session start time)
+ */
+ Session(const MemoryRegion<byte>& session_id,
+ const MemoryRegion<byte>& master_secret,
+ Protocol_Version version,
+ u16bit ciphersuite,
+ byte compression_method,
+ Connection_Side side,
+ bool secure_renegotiation_supported,
+ size_t fragment_size,
+ const std::vector<X509_Certificate>& peer_certs,
+ const MemoryRegion<byte>& session_ticket,
+ const std::string& sni_hostname = "",
+ const std::string& srp_identifier = "");
+
+ /**
+ * Load a session from DER representation (created by DER_encode)
+ */
+ Session(const byte ber[], size_t ber_len);
+
+ /**
+ * Load a session from PEM representation (created by PEM_encode)
+ */
+ Session(const std::string& pem);
+
+ /**
+ * Encode this session data for storage
+ * @warning if the master secret is compromised so is the
+ * session traffic
+ */
+ SecureVector<byte> DER_encode() const;
+
+ /**
+ * Encrypt a session (useful for serialization or session tickets)
+ */
+ MemoryVector<byte> encrypt(const SymmetricKey& key,
+ RandomNumberGenerator& rng) const;
+
+
+ /**
+ * Decrypt a session created by encrypt
+ * @param ctext the ciphertext returned by encrypt
+ * @param ctext_size the size of ctext in bytes
+ * @param key the same key used by the encrypting side
+ */
+ static Session decrypt(const byte ctext[],
+ size_t ctext_size,
+ const SymmetricKey& key);
+
+ /**
+ * Decrypt a session created by encrypt
+ * @param ctext the ciphertext returned by encrypt
+ * @param key the same key used by the encrypting side
+ */
+ static inline Session decrypt(const MemoryRegion<byte>& ctext,
+ const SymmetricKey& key)
+ {
+ return Session::decrypt(&ctext[0], ctext.size(), key);
+ }
+
+ /**
+ * Encode this session data for storage
+ * @warning if the master secret is compromised so is the
+ * session traffic
+ */
+ std::string PEM_encode() const;
+
+ /**
+ * Get the version of the saved session
+ */
+ Protocol_Version version() const { return m_version; }
+
+ /**
+ * Get the ciphersuite code of the saved session
+ */
+ u16bit ciphersuite_code() const { return m_ciphersuite; }
+
+ /**
+ * Get the ciphersuite info of the saved session
+ */
+ Ciphersuite ciphersuite() const { return Ciphersuite::by_id(m_ciphersuite); }
+
+ /**
+ * Get the compression method used in the saved session
+ */
+ byte compression_method() const { return m_compression_method; }
+
+ /**
+ * Get which side of the connection the resumed session we are/were
+ * acting as.
+ */
+ Connection_Side side() const { return m_connection_side; }
+
+ /**
+ * Get the SNI hostname (if sent by the client in the initial handshake)
+ */
+ std::string sni_hostname() const { return m_sni_hostname; }
+
+ /**
+ * Get the SRP identity (if sent by the client in the initial handshake)
+ */
+ std::string srp_identifier() const { return m_srp_identifier; }
+
+ /**
+ * Get the saved master secret
+ */
+ const SecureVector<byte>& master_secret() const
+ { return m_master_secret; }
+
+ /**
+ * Get the session identifier
+ */
+ const MemoryVector<byte>& session_id() const
+ { return m_identifier; }
+
+ /**
+ * Get the negotiated maximum fragment size (or 0 if default)
+ */
+ size_t fragment_size() const { return m_fragment_size; }
+
+ /**
+ * Is secure renegotiation supported?
+ */
+ bool secure_renegotiation() const
+ { return m_secure_renegotiation_supported; }
+
+ /**
+ * Return the certificate chain of the peer (possibly empty)
+ */
+ std::vector<X509_Certificate> peer_certs() const { return m_peer_certs; }
+
+ /**
+ * Get the time this session began (seconds since Epoch)
+ */
+ u64bit start_time() const { return m_start_time; }
+
+ /**
+ * Return how long this session has existed (in seconds)
+ */
+ u32bit session_age() const;
+
+ /**
+ * Return the session ticket the server gave us
+ */
+ const MemoryVector<byte>& session_ticket() const { return m_session_ticket; }
+
+ private:
+ enum { TLS_SESSION_PARAM_STRUCT_VERSION = 0x2994e300 };
+
+ u64bit m_start_time;
+
+ MemoryVector<byte> m_identifier;
+ MemoryVector<byte> m_session_ticket; // only used by client side
+ SecureVector<byte> m_master_secret;
+
+ Protocol_Version m_version;
+ u16bit m_ciphersuite;
+ byte m_compression_method;
+ Connection_Side m_connection_side;
+
+ bool m_secure_renegotiation_supported;
+ size_t m_fragment_size;
+
+ std::vector<X509_Certificate> m_peer_certs;
+ std::string m_sni_hostname; // optional
+ std::string m_srp_identifier; // optional
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_session_key.cpp b/src/tls/tls_session_key.cpp
new file mode 100644
index 000000000..edd0617bc
--- /dev/null
+++ b/src/tls/tls_session_key.cpp
@@ -0,0 +1,89 @@
+/*
+* TLS Session Key
+* (C) 2004-2006,2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/internal/tls_session_key.h>
+#include <botan/internal/tls_handshake_state.h>
+#include <botan/internal/tls_messages.h>
+#include <botan/lookup.h>
+#include <memory>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Session_Keys Constructor
+*/
+Session_Keys::Session_Keys(Handshake_State* state,
+ const MemoryRegion<byte>& pre_master_secret,
+ bool resuming)
+ {
+ const size_t mac_keylen = output_length_of(state->suite.mac_algo());
+ const size_t cipher_keylen = state->suite.cipher_keylen();
+
+ size_t cipher_ivlen = 0;
+ if(have_block_cipher(state->suite.cipher_algo()))
+ cipher_ivlen = block_size_of(state->suite.cipher_algo());
+
+ const size_t prf_gen = 2 * (mac_keylen + cipher_keylen + cipher_ivlen);
+
+ const byte MASTER_SECRET_MAGIC[] = {
+ 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74 };
+
+ const byte KEY_GEN_MAGIC[] = {
+ 0x6B, 0x65, 0x79, 0x20, 0x65, 0x78, 0x70, 0x61, 0x6E, 0x73, 0x69, 0x6F, 0x6E };
+
+ std::auto_ptr<KDF> prf(state->protocol_specific_prf());
+
+ if(resuming)
+ {
+ master_sec = pre_master_secret;
+ }
+ else
+ {
+ SecureVector<byte> salt;
+
+ if(state->version() != Protocol_Version::SSL_V3)
+ salt += std::make_pair(MASTER_SECRET_MAGIC, sizeof(MASTER_SECRET_MAGIC));
+
+ salt += state->client_hello->random();
+ salt += state->server_hello->random();
+
+ master_sec = prf->derive_key(48, pre_master_secret, salt);
+ }
+
+ SecureVector<byte> salt;
+ if(state->version() != Protocol_Version::SSL_V3)
+ salt += std::make_pair(KEY_GEN_MAGIC, sizeof(KEY_GEN_MAGIC));
+ salt += state->server_hello->random();
+ salt += state->client_hello->random();
+
+ SymmetricKey keyblock = prf->derive_key(prf_gen, master_sec, salt);
+
+ const byte* key_data = keyblock.begin();
+
+ c_mac = SymmetricKey(key_data, mac_keylen);
+ key_data += mac_keylen;
+
+ s_mac = SymmetricKey(key_data, mac_keylen);
+ key_data += mac_keylen;
+
+ c_cipher = SymmetricKey(key_data, cipher_keylen);
+ key_data += cipher_keylen;
+
+ s_cipher = SymmetricKey(key_data, cipher_keylen);
+ key_data += cipher_keylen;
+
+ c_iv = InitializationVector(key_data, cipher_ivlen);
+ key_data += cipher_ivlen;
+
+ s_iv = InitializationVector(key_data, cipher_ivlen);
+ }
+
+}
+
+}
diff --git a/src/tls/tls_session_key.h b/src/tls/tls_session_key.h
new file mode 100644
index 000000000..25de56aea
--- /dev/null
+++ b/src/tls/tls_session_key.h
@@ -0,0 +1,52 @@
+/*
+* TLS Session Key
+* (C) 2004-2006,2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_SESSION_KEYS_H__
+#define BOTAN_TLS_SESSION_KEYS_H__
+
+#include <botan/tls_ciphersuite.h>
+#include <botan/tls_exceptn.h>
+#include <botan/symkey.h>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* TLS Session Keys
+*/
+class Session_Keys
+ {
+ public:
+ SymmetricKey client_cipher_key() const { return c_cipher; }
+ SymmetricKey server_cipher_key() const { return s_cipher; }
+
+ SymmetricKey client_mac_key() const { return c_mac; }
+ SymmetricKey server_mac_key() const { return s_mac; }
+
+ InitializationVector client_iv() const { return c_iv; }
+ InitializationVector server_iv() const { return s_iv; }
+
+ const SecureVector<byte>& master_secret() const { return master_sec; }
+
+ Session_Keys() {}
+
+ Session_Keys(class Handshake_State* state,
+ const MemoryRegion<byte>& pre_master,
+ bool resuming);
+
+ private:
+ SecureVector<byte> master_sec;
+ SymmetricKey c_cipher, s_cipher, c_mac, s_mac;
+ InitializationVector c_iv, s_iv;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_session_manager.cpp b/src/tls/tls_session_manager.cpp
new file mode 100644
index 000000000..812525d69
--- /dev/null
+++ b/src/tls/tls_session_manager.cpp
@@ -0,0 +1,96 @@
+/*
+* TLS Session Management
+* (C) 2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_session_manager.h>
+#include <botan/hex.h>
+#include <botan/time.h>
+
+namespace Botan {
+
+namespace TLS {
+
+bool Session_Manager_In_Memory::load_from_session_str(
+ const std::string& session_str, Session& session)
+ {
+ std::map<std::string, Session>::iterator i = m_sessions.find(session_str);
+
+ if(i == m_sessions.end())
+ return false;
+
+ // session has expired, remove it
+ const u64bit now = system_time();
+ if(i->second.start_time() + session_lifetime() < now)
+ {
+ m_sessions.erase(i);
+ return false;
+ }
+
+ session = i->second;
+ return true;
+ }
+
+bool Session_Manager_In_Memory::load_from_session_id(
+ const MemoryRegion<byte>& session_id, Session& session)
+ {
+ return load_from_session_str(hex_encode(session_id), session);
+ }
+
+bool Session_Manager_In_Memory::load_from_host_info(
+ const std::string& hostname, u16bit port, Session& session)
+ {
+ std::map<std::string, std::string>::iterator i;
+
+ if(port > 0)
+ i = m_host_sessions.find(hostname + ":" + to_string(port));
+ else
+ i = m_host_sessions.find(hostname);
+
+ if(i == m_host_sessions.end())
+ return false;
+
+ if(load_from_session_str(i->second, session))
+ return true;
+
+ // was removed from m_sessions map, remove m_host_sessions entry
+ m_host_sessions.erase(i);
+
+ return false;
+ }
+
+void Session_Manager_In_Memory::remove_entry(
+ const MemoryRegion<byte>& session_id)
+ {
+ std::map<std::string, Session>::iterator i =
+ m_sessions.find(hex_encode(session_id));
+
+ if(i != m_sessions.end())
+ m_sessions.erase(i);
+ }
+
+void Session_Manager_In_Memory::save(const Session& session)
+ {
+ if(m_max_sessions != 0)
+ {
+ /*
+ This removes randomly based on ordering of session ids.
+ Instead, remove oldest first?
+ */
+ while(m_sessions.size() >= m_max_sessions)
+ m_sessions.erase(m_sessions.begin());
+ }
+
+ const std::string session_id_str = hex_encode(session.session_id());
+
+ m_sessions[session_id_str] = session;
+
+ if(session.side() == CLIENT && session.sni_hostname() != "")
+ m_host_sessions[session.sni_hostname()] = session_id_str;
+ }
+
+}
+
+}
diff --git a/src/tls/tls_session_manager.h b/src/tls/tls_session_manager.h
new file mode 100644
index 000000000..bb0524a52
--- /dev/null
+++ b/src/tls/tls_session_manager.h
@@ -0,0 +1,125 @@
+/*
+* TLS Session Manager
+* (C) 2011 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_SESSION_MANAGER_H__
+#define BOTAN_TLS_SESSION_MANAGER_H__
+
+#include <botan/tls_session.h>
+#include <map>
+
+namespace Botan {
+
+namespace TLS {
+
+/**
+* Session_Manager is an interface to systems which can save
+* session parameters for supporting session resumption.
+*
+* Saving sessions is done on a best-effort basis; an implementation is
+* allowed to drop sessions due to space constraints.
+*
+* Implementations should strive to be thread safe
+*/
+class BOTAN_DLL Session_Manager
+ {
+ public:
+ /**
+ * Try to load a saved session (server side)
+ * @param session_id the session identifier we are trying to resume
+ * @param session will be set to the saved session data (if found),
+ or not modified if not found
+ * @return true if session was modified
+ */
+ virtual bool load_from_session_id(const MemoryRegion<byte>& session_id,
+ Session& session) = 0;
+
+ /**
+ * Try to load a saved session (client side)
+ * @param hostname of the host we are connecting to
+ * @param port the port number if we know it, or 0 if unknown
+ * @param session will be set to the saved session data (if found),
+ or not modified if not found
+ * @return true if session was modified
+ */
+ virtual bool load_from_host_info(const std::string& hostname, u16bit port,
+ Session& session) = 0;
+
+ /**
+ * Remove this session id from the cache, if it exists
+ */
+ virtual void remove_entry(const MemoryRegion<byte>& session_id) = 0;
+
+ /**
+ * Save a session on a best effort basis; the manager may not in
+ * fact be able to save the session for whatever reason; this is
+ * not an error. Caller cannot assume that calling save followed
+ * immediately by load_from_* will result in a successful lookup.
+ *
+ * @param session to save
+ */
+ virtual void save(const Session& session) = 0;
+
+ /**
+ * Return the allowed lifetime of a session; beyond this time,
+ * sessions are not resumed. Returns 0 if unknown/no explicit
+ * expiration policy.
+ */
+ virtual u32bit session_lifetime() const = 0;
+
+ virtual ~Session_Manager() {}
+ };
+
+/**
+* A simple implementation of Session_Manager that just saves
+* values in memory, with no persistance abilities
+*
+* @todo add locking
+*/
+class BOTAN_DLL Session_Manager_In_Memory : public Session_Manager
+ {
+ public:
+ /**
+ * @param max_sessions a hint on the maximum number of sessions
+ * to keep in memory at any one time. (If zero, don't cap)
+ * @param session_lifetime sessions are expired after this many
+ * seconds have elapsed from initial handshake.
+ */
+ Session_Manager_In_Memory(size_t max_sessions = 1000,
+ u32bit session_lifetime = 7200) :
+ m_max_sessions(max_sessions),
+ m_session_lifetime(session_lifetime)
+ {}
+
+ bool load_from_session_id(const MemoryRegion<byte>& session_id,
+ Session& session);
+
+ bool load_from_host_info(const std::string& hostname, u16bit port,
+ Session& session);
+
+ void remove_entry(const MemoryRegion<byte>& session_id);
+
+ void save(const Session& session_data);
+
+ u32bit session_lifetime() const { return m_session_lifetime; }
+
+ private:
+ bool load_from_session_str(const std::string& session_str,
+ Session& session);
+
+ size_t m_max_sessions;
+
+ u32bit m_session_lifetime;
+
+ std::map<std::string, Session> m_sessions; // hex(session_id) -> session
+ std::map<std::string, std::string> m_host_sessions;
+ };
+
+}
+
+}
+
+#endif
diff --git a/src/tls/tls_suite_info.cpp b/src/tls/tls_suite_info.cpp
new file mode 100644
index 000000000..0b76842af
--- /dev/null
+++ b/src/tls/tls_suite_info.cpp
@@ -0,0 +1,317 @@
+/*
+* TLS Cipher Suite
+* (C) 2004-2010,2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_ciphersuite.h>
+
+namespace Botan {
+
+namespace TLS {
+
+Ciphersuite Ciphersuite::by_id(u16bit suite)
+ {
+ // Automatically generated by a Python script from the IANA values
+
+ switch(suite)
+ {
+ case 0x0013: // DHE_DSS_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("DSA", "DH", "SHA-1", "3DES", 24);
+
+ case 0x0032: // DHE_DSS_WITH_AES_128_CBC_SHA
+ return Ciphersuite("DSA", "DH", "SHA-1", "AES-128", 16);
+
+ case 0x0040: // DHE_DSS_WITH_AES_128_CBC_SHA256
+ return Ciphersuite("DSA", "DH", "SHA-256", "AES-128", 16);
+
+ case 0x0038: // DHE_DSS_WITH_AES_256_CBC_SHA
+ return Ciphersuite("DSA", "DH", "SHA-1", "AES-256", 32);
+
+ case 0x006A: // DHE_DSS_WITH_AES_256_CBC_SHA256
+ return Ciphersuite("DSA", "DH", "SHA-256", "AES-256", 32);
+
+ case 0x0044: // DHE_DSS_WITH_CAMELLIA_128_CBC_SHA
+ return Ciphersuite("DSA", "DH", "SHA-1", "Camellia", 16);
+
+ case 0x00BD: // DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256
+ return Ciphersuite("DSA", "DH", "SHA-256", "Camellia", 16);
+
+ case 0x0087: // DHE_DSS_WITH_CAMELLIA_256_CBC_SHA
+ return Ciphersuite("DSA", "DH", "SHA-1", "Camellia", 32);
+
+ case 0x00C3: // DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256
+ return Ciphersuite("DSA", "DH", "SHA-256", "Camellia", 32);
+
+ case 0x0066: // DHE_DSS_WITH_RC4_128_SHA
+ return Ciphersuite("DSA", "DH", "SHA-1", "ARC4", 16);
+
+ case 0x0099: // DHE_DSS_WITH_SEED_CBC_SHA
+ return Ciphersuite("DSA", "DH", "SHA-1", "SEED", 16);
+
+ case 0x008F: // DHE_PSK_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("", "DHE_PSK", "SHA-1", "3DES", 24);
+
+ case 0x0090: // DHE_PSK_WITH_AES_128_CBC_SHA
+ return Ciphersuite("", "DHE_PSK", "SHA-1", "AES-128", 16);
+
+ case 0x00B2: // DHE_PSK_WITH_AES_128_CBC_SHA256
+ return Ciphersuite("", "DHE_PSK", "SHA-256", "AES-128", 16);
+
+ case 0x0091: // DHE_PSK_WITH_AES_256_CBC_SHA
+ return Ciphersuite("", "DHE_PSK", "SHA-1", "AES-256", 32);
+
+ case 0x00B3: // DHE_PSK_WITH_AES_256_CBC_SHA384
+ return Ciphersuite("", "DHE_PSK", "SHA-384", "AES-256", 32);
+
+ case 0xC096: // DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256
+ return Ciphersuite("", "DHE_PSK", "SHA-256", "Camellia", 16);
+
+ case 0xC097: // DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384
+ return Ciphersuite("", "DHE_PSK", "SHA-384", "Camellia", 32);
+
+ case 0x008E: // DHE_PSK_WITH_RC4_128_SHA
+ return Ciphersuite("", "DHE_PSK", "SHA-1", "ARC4", 16);
+
+ case 0x0016: // DHE_RSA_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("RSA", "DH", "SHA-1", "3DES", 24);
+
+ case 0x0033: // DHE_RSA_WITH_AES_128_CBC_SHA
+ return Ciphersuite("RSA", "DH", "SHA-1", "AES-128", 16);
+
+ case 0x0067: // DHE_RSA_WITH_AES_128_CBC_SHA256
+ return Ciphersuite("RSA", "DH", "SHA-256", "AES-128", 16);
+
+ case 0x0039: // DHE_RSA_WITH_AES_256_CBC_SHA
+ return Ciphersuite("RSA", "DH", "SHA-1", "AES-256", 32);
+
+ case 0x006B: // DHE_RSA_WITH_AES_256_CBC_SHA256
+ return Ciphersuite("RSA", "DH", "SHA-256", "AES-256", 32);
+
+ case 0x0045: // DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
+ return Ciphersuite("RSA", "DH", "SHA-1", "Camellia", 16);
+
+ case 0x00BE: // DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ return Ciphersuite("RSA", "DH", "SHA-256", "Camellia", 16);
+
+ case 0x0088: // DHE_RSA_WITH_CAMELLIA_256_CBC_SHA
+ return Ciphersuite("RSA", "DH", "SHA-1", "Camellia", 32);
+
+ case 0x00C4: // DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256
+ return Ciphersuite("RSA", "DH", "SHA-256", "Camellia", 32);
+
+ case 0x009A: // DHE_RSA_WITH_SEED_CBC_SHA
+ return Ciphersuite("RSA", "DH", "SHA-1", "SEED", 16);
+
+ case 0x001B: // DH_anon_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("", "DH", "SHA-1", "3DES", 24);
+
+ case 0x0034: // DH_anon_WITH_AES_128_CBC_SHA
+ return Ciphersuite("", "DH", "SHA-1", "AES-128", 16);
+
+ case 0x006C: // DH_anon_WITH_AES_128_CBC_SHA256
+ return Ciphersuite("", "DH", "SHA-256", "AES-128", 16);
+
+ case 0x003A: // DH_anon_WITH_AES_256_CBC_SHA
+ return Ciphersuite("", "DH", "SHA-1", "AES-256", 32);
+
+ case 0x006D: // DH_anon_WITH_AES_256_CBC_SHA256
+ return Ciphersuite("", "DH", "SHA-256", "AES-256", 32);
+
+ case 0x0046: // DH_anon_WITH_CAMELLIA_128_CBC_SHA
+ return Ciphersuite("", "DH", "SHA-1", "Camellia", 16);
+
+ case 0x00BF: // DH_anon_WITH_CAMELLIA_128_CBC_SHA256
+ return Ciphersuite("", "DH", "SHA-256", "Camellia", 16);
+
+ case 0x0089: // DH_anon_WITH_CAMELLIA_256_CBC_SHA
+ return Ciphersuite("", "DH", "SHA-1", "Camellia", 32);
+
+ case 0x00C5: // DH_anon_WITH_CAMELLIA_256_CBC_SHA256
+ return Ciphersuite("", "DH", "SHA-256", "Camellia", 32);
+
+ case 0x0018: // DH_anon_WITH_RC4_128_MD5
+ return Ciphersuite("", "DH", "MD5", "ARC4", 16);
+
+ case 0x009B: // DH_anon_WITH_SEED_CBC_SHA
+ return Ciphersuite("", "DH", "SHA-1", "SEED", 16);
+
+ case 0xC008: // ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("ECDSA", "ECDH", "SHA-1", "3DES", 24);
+
+ case 0xC009: // ECDHE_ECDSA_WITH_AES_128_CBC_SHA
+ return Ciphersuite("ECDSA", "ECDH", "SHA-1", "AES-128", 16);
+
+ case 0xC023: // ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
+ return Ciphersuite("ECDSA", "ECDH", "SHA-256", "AES-128", 16);
+
+ case 0xC00A: // ECDHE_ECDSA_WITH_AES_256_CBC_SHA
+ return Ciphersuite("ECDSA", "ECDH", "SHA-1", "AES-256", 32);
+
+ case 0xC024: // ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
+ return Ciphersuite("ECDSA", "ECDH", "SHA-384", "AES-256", 32);
+
+ case 0xC072: // ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256
+ return Ciphersuite("ECDSA", "ECDH", "SHA-256", "Camellia", 16);
+
+ case 0xC073: // ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384
+ return Ciphersuite("ECDSA", "ECDH", "SHA-384", "Camellia", 32);
+
+ case 0xC007: // ECDHE_ECDSA_WITH_RC4_128_SHA
+ return Ciphersuite("ECDSA", "ECDH", "SHA-1", "ARC4", 16);
+
+ case 0xC034: // ECDHE_PSK_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("", "ECDHE_PSK", "SHA-1", "3DES", 24);
+
+ case 0xC035: // ECDHE_PSK_WITH_AES_128_CBC_SHA
+ return Ciphersuite("", "ECDHE_PSK", "SHA-1", "AES-128", 16);
+
+ case 0xC037: // ECDHE_PSK_WITH_AES_128_CBC_SHA256
+ return Ciphersuite("", "ECDHE_PSK", "SHA-256", "AES-128", 16);
+
+ case 0xC036: // ECDHE_PSK_WITH_AES_256_CBC_SHA
+ return Ciphersuite("", "ECDHE_PSK", "SHA-1", "AES-256", 32);
+
+ case 0xC038: // ECDHE_PSK_WITH_AES_256_CBC_SHA384
+ return Ciphersuite("", "ECDHE_PSK", "SHA-384", "AES-256", 32);
+
+ case 0xC09A: // ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256
+ return Ciphersuite("", "ECDHE_PSK", "SHA-256", "Camellia", 16);
+
+ case 0xC09B: // ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384
+ return Ciphersuite("", "ECDHE_PSK", "SHA-384", "Camellia", 32);
+
+ case 0xC033: // ECDHE_PSK_WITH_RC4_128_SHA
+ return Ciphersuite("", "ECDHE_PSK", "SHA-1", "ARC4", 16);
+
+ case 0xC012: // ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("RSA", "ECDH", "SHA-1", "3DES", 24);
+
+ case 0xC013: // ECDHE_RSA_WITH_AES_128_CBC_SHA
+ return Ciphersuite("RSA", "ECDH", "SHA-1", "AES-128", 16);
+
+ case 0xC027: // ECDHE_RSA_WITH_AES_128_CBC_SHA256
+ return Ciphersuite("RSA", "ECDH", "SHA-256", "AES-128", 16);
+
+ case 0xC014: // ECDHE_RSA_WITH_AES_256_CBC_SHA
+ return Ciphersuite("RSA", "ECDH", "SHA-1", "AES-256", 32);
+
+ case 0xC028: // ECDHE_RSA_WITH_AES_256_CBC_SHA384
+ return Ciphersuite("RSA", "ECDH", "SHA-384", "AES-256", 32);
+
+ case 0xC076: // ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256
+ return Ciphersuite("RSA", "ECDH", "SHA-256", "Camellia", 16);
+
+ case 0xC077: // ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384
+ return Ciphersuite("RSA", "ECDH", "SHA-384", "Camellia", 32);
+
+ case 0xC011: // ECDHE_RSA_WITH_RC4_128_SHA
+ return Ciphersuite("RSA", "ECDH", "SHA-1", "ARC4", 16);
+
+ case 0xC017: // ECDH_anon_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("", "ECDH", "SHA-1", "3DES", 24);
+
+ case 0xC018: // ECDH_anon_WITH_AES_128_CBC_SHA
+ return Ciphersuite("", "ECDH", "SHA-1", "AES-128", 16);
+
+ case 0xC019: // ECDH_anon_WITH_AES_256_CBC_SHA
+ return Ciphersuite("", "ECDH", "SHA-1", "AES-256", 32);
+
+ case 0xC016: // ECDH_anon_WITH_RC4_128_SHA
+ return Ciphersuite("", "ECDH", "SHA-1", "ARC4", 16);
+
+ case 0x008B: // PSK_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("", "PSK", "SHA-1", "3DES", 24);
+
+ case 0x008C: // PSK_WITH_AES_128_CBC_SHA
+ return Ciphersuite("", "PSK", "SHA-1", "AES-128", 16);
+
+ case 0x00AE: // PSK_WITH_AES_128_CBC_SHA256
+ return Ciphersuite("", "PSK", "SHA-256", "AES-128", 16);
+
+ case 0x008D: // PSK_WITH_AES_256_CBC_SHA
+ return Ciphersuite("", "PSK", "SHA-1", "AES-256", 32);
+
+ case 0x00AF: // PSK_WITH_AES_256_CBC_SHA384
+ return Ciphersuite("", "PSK", "SHA-384", "AES-256", 32);
+
+ case 0xC094: // PSK_WITH_CAMELLIA_128_CBC_SHA256
+ return Ciphersuite("", "PSK", "SHA-256", "Camellia", 16);
+
+ case 0xC095: // PSK_WITH_CAMELLIA_256_CBC_SHA384
+ return Ciphersuite("", "PSK", "SHA-384", "Camellia", 32);
+
+ case 0x008A: // PSK_WITH_RC4_128_SHA
+ return Ciphersuite("", "PSK", "SHA-1", "ARC4", 16);
+
+ case 0x000A: // RSA_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("RSA", "RSA", "SHA-1", "3DES", 24);
+
+ case 0x002F: // RSA_WITH_AES_128_CBC_SHA
+ return Ciphersuite("RSA", "RSA", "SHA-1", "AES-128", 16);
+
+ case 0x003C: // RSA_WITH_AES_128_CBC_SHA256
+ return Ciphersuite("RSA", "RSA", "SHA-256", "AES-128", 16);
+
+ case 0x0035: // RSA_WITH_AES_256_CBC_SHA
+ return Ciphersuite("RSA", "RSA", "SHA-1", "AES-256", 32);
+
+ case 0x003D: // RSA_WITH_AES_256_CBC_SHA256
+ return Ciphersuite("RSA", "RSA", "SHA-256", "AES-256", 32);
+
+ case 0x0041: // RSA_WITH_CAMELLIA_128_CBC_SHA
+ return Ciphersuite("RSA", "RSA", "SHA-1", "Camellia", 16);
+
+ case 0x00BA: // RSA_WITH_CAMELLIA_128_CBC_SHA256
+ return Ciphersuite("RSA", "RSA", "SHA-256", "Camellia", 16);
+
+ case 0x0084: // RSA_WITH_CAMELLIA_256_CBC_SHA
+ return Ciphersuite("RSA", "RSA", "SHA-1", "Camellia", 32);
+
+ case 0x00C0: // RSA_WITH_CAMELLIA_256_CBC_SHA256
+ return Ciphersuite("RSA", "RSA", "SHA-256", "Camellia", 32);
+
+ case 0x0004: // RSA_WITH_RC4_128_MD5
+ return Ciphersuite("RSA", "RSA", "MD5", "ARC4", 16);
+
+ case 0x0005: // RSA_WITH_RC4_128_SHA
+ return Ciphersuite("RSA", "RSA", "SHA-1", "ARC4", 16);
+
+ case 0x0096: // RSA_WITH_SEED_CBC_SHA
+ return Ciphersuite("RSA", "RSA", "SHA-1", "SEED", 16);
+
+ case 0xC01C: // SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("DSA", "SRP_SHA", "SHA-1", "3DES", 24);
+
+ case 0xC01F: // SRP_SHA_DSS_WITH_AES_128_CBC_SHA
+ return Ciphersuite("DSA", "SRP_SHA", "SHA-1", "AES-128", 16);
+
+ case 0xC022: // SRP_SHA_DSS_WITH_AES_256_CBC_SHA
+ return Ciphersuite("DSA", "SRP_SHA", "SHA-1", "AES-256", 32);
+
+ case 0xC01B: // SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("RSA", "SRP_SHA", "SHA-1", "3DES", 24);
+
+ case 0xC01E: // SRP_SHA_RSA_WITH_AES_128_CBC_SHA
+ return Ciphersuite("RSA", "SRP_SHA", "SHA-1", "AES-128", 16);
+
+ case 0xC021: // SRP_SHA_RSA_WITH_AES_256_CBC_SHA
+ return Ciphersuite("RSA", "SRP_SHA", "SHA-1", "AES-256", 32);
+
+ case 0xC01A: // SRP_SHA_WITH_3DES_EDE_CBC_SHA
+ return Ciphersuite("", "SRP_SHA", "SHA-1", "3DES", 24);
+
+ case 0xC01D: // SRP_SHA_WITH_AES_128_CBC_SHA
+ return Ciphersuite("", "SRP_SHA", "SHA-1", "AES-128", 16);
+
+ case 0xC020: // SRP_SHA_WITH_AES_256_CBC_SHA
+ return Ciphersuite("", "SRP_SHA", "SHA-1", "AES-256", 32);
+ }
+
+ return Ciphersuite(); // some unknown ciphersuite
+ }
+
+}
+
+}
diff --git a/src/tls/tls_version.cpp b/src/tls/tls_version.cpp
new file mode 100644
index 000000000..4445998eb
--- /dev/null
+++ b/src/tls/tls_version.cpp
@@ -0,0 +1,33 @@
+/*
+* TLS Protocol Version Management
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#include <botan/tls_version.h>
+#include <botan/parsing.h>
+
+namespace Botan {
+
+namespace TLS {
+
+std::string Protocol_Version::to_string() const
+ {
+ const byte maj = major_version();
+ const byte min = minor_version();
+
+ // Some very new or very old protocol?
+ if(maj != 3)
+ return "Protocol " + Botan::to_string(maj) + "." + Botan::to_string(min);
+
+ if(maj == 3 && min == 0)
+ return "SSL v3";
+
+ // The TLS v1.[0123...] case
+ return "TLS v1." + Botan::to_string(min-1);
+ }
+
+}
+
+}
diff --git a/src/tls/tls_version.h b/src/tls/tls_version.h
new file mode 100644
index 000000000..aa689b300
--- /dev/null
+++ b/src/tls/tls_version.h
@@ -0,0 +1,87 @@
+/*
+* TLS Protocol Version Management
+* (C) 2012 Jack Lloyd
+*
+* Released under the terms of the Botan license
+*/
+
+#ifndef BOTAN_TLS_PROTOCOL_VERSION_H__
+#define BOTAN_TLS_PROTOCOL_VERSION_H__
+
+#include <botan/get_byte.h>
+#include <string>
+
+namespace Botan {
+
+namespace TLS {
+
+class BOTAN_DLL Protocol_Version
+ {
+ public:
+ enum Version_Code {
+ SSL_V3 = 0x0300,
+ TLS_V10 = 0x0301,
+ TLS_V11 = 0x0302,
+ TLS_V12 = 0x0303
+ };
+
+ Protocol_Version() : m_version(0) {}
+
+ Protocol_Version(Version_Code named_version) :
+ m_version(static_cast<u16bit>(named_version)) {}
+
+ Protocol_Version(byte major, byte minor) :
+ m_version((static_cast<u16bit>(major) << 8) | minor) {}
+
+ /**
+ * Get the major version of the protocol version
+ */
+ byte major_version() const { return get_byte(0, m_version); }
+
+ /**
+ * Get the minor version of the protocol version
+ */
+ byte minor_version() const { return get_byte(1, m_version); }
+
+ bool operator==(const Protocol_Version& other) const
+ {
+ return (m_version == other.m_version);
+ }
+
+ bool operator!=(const Protocol_Version& other) const
+ {
+ return (m_version != other.m_version);
+ }
+
+ bool operator>=(const Protocol_Version& other) const
+ {
+ return (m_version >= other.m_version);
+ }
+
+ bool operator>(const Protocol_Version& other) const
+ {
+ return (m_version > other.m_version);
+ }
+
+ bool operator<=(const Protocol_Version& other) const
+ {
+ return (m_version <= other.m_version);
+ }
+
+ bool operator<(const Protocol_Version& other) const
+ {
+ return (m_version < other.m_version);
+ }
+
+ std::string to_string() const;
+
+ private:
+ u16bit m_version;
+ };
+
+}
+
+}
+
+#endif
+