diff options
author | lloyd <[email protected]> | 2012-03-22 18:41:39 +0000 |
---|---|---|
committer | lloyd <[email protected]> | 2012-03-22 18:41:39 +0000 |
commit | c670e02f82087945f2c96b7cf69177ba374b27fc (patch) | |
tree | b3cec42ca7917db3183b9d997fbacc5c793d1721 /src/tls | |
parent | 4b3a653d7522365fcff3cda0afa40aac269e5767 (diff) | |
parent | 4004e38a00f6bf5743af68d47e44f7adf3872d2b (diff) |
propagate from branch 'net.randombit.botan.tls-state-machine' (head 6bcbae3d22e4d873a8e941d0325ad666482ac4da)
to branch 'net.randombit.botan.tls-session-ticket' (head 9048722b5d18b39cf21f8542942dab94a9bd4e6b)
Diffstat (limited to 'src/tls')
-rw-r--r-- | src/tls/c_hello.cpp | 34 | ||||
-rw-r--r-- | src/tls/info.txt | 1 | ||||
-rw-r--r-- | src/tls/s_hello.cpp | 18 | ||||
-rw-r--r-- | src/tls/session_ticket.cpp | 57 | ||||
-rw-r--r-- | src/tls/sessions_sqlite/tls_sqlite_sess_mgr.cpp | 3 | ||||
-rw-r--r-- | src/tls/tls_client.cpp | 52 | ||||
-rw-r--r-- | src/tls/tls_extensions.cpp | 9 | ||||
-rw-r--r-- | src/tls/tls_extensions.h | 35 | ||||
-rw-r--r-- | src/tls/tls_handshake_state.cpp | 17 | ||||
-rw-r--r-- | src/tls/tls_handshake_state.h | 3 | ||||
-rw-r--r-- | src/tls/tls_messages.h | 36 | ||||
-rw-r--r-- | src/tls/tls_reader.h | 9 | ||||
-rw-r--r-- | src/tls/tls_server.cpp | 93 | ||||
-rw-r--r-- | src/tls/tls_session.cpp | 118 | ||||
-rw-r--r-- | src/tls/tls_session.h | 26 |
15 files changed, 455 insertions, 56 deletions
diff --git a/src/tls/c_hello.cpp b/src/tls/c_hello.cpp index 35389f37b..0798bfaf3 100644 --- a/src/tls/c_hello.cpp +++ b/src/tls/c_hello.cpp @@ -71,7 +71,8 @@ Client_Hello::Client_Hello(Record_Writer& writer, m_next_protocol(next_protocol), m_fragment_size(0), m_secure_renegotiation(true), - m_renegotiation_info(reneg_info) + m_renegotiation_info(reneg_info), + m_supports_session_ticket(true) { std::vector<std::string> hashes = policy.allowed_hashes(); std::vector<std::string> sigs = policy.allowed_signature_methods(); @@ -86,7 +87,7 @@ Client_Hello::Client_Hello(Record_Writer& writer, } /* -* Create a new Client Hello message +* Create a new Client Hello message (session resumption case) */ Client_Hello::Client_Hello(Record_Writer& writer, Handshake_Hash& hash, @@ -100,7 +101,9 @@ Client_Hello::Client_Hello(Record_Writer& writer, m_srp_identifier(session.srp_identifier()), m_next_protocol(next_protocol), m_fragment_size(session.fragment_size()), - m_secure_renegotiation(session.secure_renegotiation()) + m_secure_renegotiation(session.secure_renegotiation()), + m_supports_session_ticket(true), + m_session_ticket(session.session_ticket()) { m_suites.push_back(session.ciphersuite_code()); m_comp_methods.push_back(session.compression_method()); @@ -110,10 +113,14 @@ Client_Hello::Client_Hello(Record_Writer& writer, 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_fragment_size = 0; if(type == CLIENT_HELLO) @@ -159,11 +166,14 @@ MemoryVector<byte> Client_Hello::serialize() const if(m_next_protocol) extensions.add(new Next_Protocol_Notification()); + + extensions.add(new Session_Ticket(m_session_ticket)); } else { // renegotiation extensions.add(new Renegotation_Extension(m_renegotiation_info)); + extensions.add(new Session_Ticket(m_session_ticket)); } buf += extensions.serialize(); @@ -302,6 +312,24 @@ void Client_Hello::deserialize(const MemoryRegion<byte>& buf) } } + 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(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))) { /* diff --git a/src/tls/info.txt b/src/tls/info.txt index 822914a3d..9cc936471 100644 --- a/src/tls/info.txt +++ b/src/tls/info.txt @@ -45,6 +45,7 @@ rec_read.cpp rec_wri.cpp s_hello.cpp s_kex.cpp +session_ticket.cpp tls_channel.cpp tls_client.cpp tls_extensions.cpp diff --git a/src/tls/s_hello.cpp b/src/tls/s_hello.cpp index 9bcbdb5e9..4cbc69f30 100644 --- a/src/tls/s_hello.cpp +++ b/src/tls/s_hello.cpp @@ -37,7 +37,8 @@ Server_Hello::Server_Hello(Record_Writer& writer, m_secure_renegotiation(client_has_secure_renegotiation), m_renegotiation_info(reneg_info), m_next_protocol(client_has_npn), - m_next_protocols(next_protocols) + m_next_protocols(next_protocols), + m_supports_session_ticket(c_hello.supports_session_ticket()) { suite = policy.choose_suite( c_hello.ciphersuites(), @@ -66,6 +67,7 @@ Server_Hello::Server_Hello(Record_Writer& writer, size_t max_fragment_size, bool client_has_secure_renegotiation, const MemoryRegion<byte>& reneg_info, + bool client_supports_session_tickets, bool client_has_npn, const std::vector<std::string>& next_protocols, RandomNumberGenerator& rng) : @@ -78,7 +80,8 @@ Server_Hello::Server_Hello(Record_Writer& writer, m_secure_renegotiation(client_has_secure_renegotiation), m_renegotiation_info(reneg_info), m_next_protocol(client_has_npn), - m_next_protocols(next_protocols) + m_next_protocols(next_protocols), + m_supports_session_ticket(client_supports_session_tickets) { hash.update(writer.send(*this)); } @@ -89,6 +92,7 @@ Server_Hello::Server_Hello(Record_Writer& writer, 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) @@ -132,6 +136,13 @@ Server_Hello::Server_Hello(const MemoryRegion<byte>& buf) 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; + } } /* @@ -163,6 +174,9 @@ MemoryVector<byte> Server_Hello::serialize() const 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; diff --git a/src/tls/session_ticket.cpp b/src/tls/session_ticket.cpp new file mode 100644 index 000000000..47a8a5c32 --- /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) + { + 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/tls_sqlite_sess_mgr.cpp b/src/tls/sessions_sqlite/tls_sqlite_sess_mgr.cpp index b02dc95f6..3fea68a74 100644 --- a/src/tls/sessions_sqlite/tls_sqlite_sess_mgr.cpp +++ b/src/tls/sessions_sqlite/tls_sqlite_sess_mgr.cpp @@ -209,7 +209,8 @@ void Session_Manager_SQLite::remove_entry(const MemoryRegion<byte>& session_id) void Session_Manager_SQLite::save(const Session& session) { - sqlite3_statement stmt(m_db, "insert into " + m_table_name + " values(?1, ?2, ?3, ?4, ?5)"); + sqlite3_statement stmt(m_db, "insert or replace into " + m_table_name + + " values(?1, ?2, ?3, ?4, ?5)"); stmt.bind(1, hex_encode(session.session_id())); stmt.bind(2, session.start_time()); diff --git a/src/tls/tls_client.cpp b/src/tls/tls_client.cpp index 02e24a1c9..06a58385c 100644 --- a/src/tls/tls_client.cpp +++ b/src/tls/tls_client.cpp @@ -112,7 +112,7 @@ void Client::alert_notify(const Alert& alert) * Process a handshake message */ void Client::process_handshake_msg(Handshake_Type type, - const MemoryRegion<byte>& contents) + const MemoryRegion<byte>& contents) { if(state == 0) throw Unexpected_Message("Unexpected handshake message from server"); @@ -135,12 +135,12 @@ void Client::process_handshake_msg(Handshake_Type type, return; } - state->set_expected_next(SERVER_HELLO); state->client_hello = new Client_Hello(writer, state->hash, policy, rng, secure_renegotiation.for_client_hello()); - secure_renegotiation.update(state->client_hello); + state->set_expected_next(SERVER_HELLO); + return; } @@ -173,6 +173,13 @@ void Client::process_handshake_msg(Handshake_Type type, "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()); @@ -182,8 +189,11 @@ void Client::process_handshake_msg(Handshake_Type type, state->suite = Ciphersuite::by_id(state->server_hello->ciphersuite()); - if(!state->server_hello->session_id().empty() && - (state->server_hello->session_id() == state->client_hello->session_id())) + 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 @@ -199,7 +209,10 @@ void Client::process_handshake_msg(Handshake_Type type, state->resume_master_secret, true); - state->set_expected_next(HANDSHAKE_CCS); + if(state->server_hello->supports_session_ticket()) + state->set_expected_next(NEW_SESSION_TICKET); + else + state->set_expected_next(HANDSHAKE_CCS); } else { @@ -306,8 +319,6 @@ void Client::process_handshake_msg(Handshake_Type type, } else if(type == SERVER_HELLO_DONE) { - state->set_expected_next(HANDSHAKE_CCS); - state->server_hello_done = new Server_Hello_Done(contents); if(state->received_handshake_msg(CERTIFICATE_REQUEST)) @@ -364,6 +375,17 @@ void Client::process_handshake_msg(Handshake_Type type, } 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) { @@ -394,8 +416,17 @@ void Client::process_handshake_msg(Handshake_Type type, 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( - state->server_hello->session_id(), + session_id, state->keys.master_secret(), state->server_hello->version(), state->server_hello->ciphersuite(), @@ -404,6 +435,7 @@ void Client::process_handshake_msg(Handshake_Type type, secure_renegotiation.supported(), state->server_hello->fragment_size(), peer_certs, + session_ticket, state->client_hello->sni_hostname(), "" ); @@ -413,8 +445,6 @@ void Client::process_handshake_msg(Handshake_Type type, else session_manager.remove_entry(session_info.session_id()); - secure_renegotiation.update(state->client_finished, state->server_finished); - delete state; state = 0; handshake_completed = true; diff --git a/src/tls/tls_extensions.cpp b/src/tls/tls_extensions.cpp index 5d345cc9b..1ca4e3eb4 100644 --- a/src/tls/tls_extensions.cpp +++ b/src/tls/tls_extensions.cpp @@ -42,6 +42,9 @@ Extension* make_extension(TLS_Data_Reader& reader, case TLSEXT_NEXT_PROTOCOL: return new Next_Protocol_Notification(reader, size); + case TLSEXT_SESSION_TICKET: + return new Session_Ticket(reader, size); + default: return 0; // not known } @@ -501,6 +504,12 @@ Signature_Algorithms::Signature_Algorithms(TLS_Data_Reader& reader, } } +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 index 180216b8b..6a97d2560 100644 --- a/src/tls/tls_extensions.h +++ b/src/tls/tls_extensions.h @@ -1,6 +1,6 @@ /* * TLS Extensions -* (C) 2011 Jack Lloyd +* (C) 2011-2012 Jack Lloyd * * Released under the terms of the Botan license */ @@ -210,6 +210,39 @@ class Next_Protocol_Notification : public Extension 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) */ diff --git a/src/tls/tls_handshake_state.cpp b/src/tls/tls_handshake_state.cpp index 2db97db0a..6ddd8d346 100644 --- a/src/tls/tls_handshake_state.cpp +++ b/src/tls/tls_handshake_state.cpp @@ -54,12 +54,15 @@ u32bit bitmask_for_handshake_type(Handshake_Type type) case NEXT_PROTOCOL: return (1 << 9); - case HANDSHAKE_CCS: + case NEW_SESSION_TICKET: return (1 << 10); - case FINISHED: + case HANDSHAKE_CCS: return (1 << 11); + case FINISHED: + return (1 << 12); + // allow explicitly disabling new handshakes case HANDSHAKE_NONE: return 0; @@ -85,6 +88,7 @@ Handshake_State::Handshake_State(Handshake_Reader* reader) cert_req = 0; server_hello_done = 0; next_protocol = 0; + new_session_ticket = 0; client_certs = 0; client_kex = 0; @@ -139,6 +143,14 @@ bool Handshake_State::received_handshake_msg(Handshake_Type handshake_msg) const return (hand_received_mask & mask); } +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) @@ -287,6 +299,7 @@ Handshake_State::~Handshake_State() delete cert_req; delete server_hello_done; delete next_protocol; + delete new_session_ticket; delete client_certs; delete client_kex; diff --git a/src/tls/tls_handshake_state.h b/src/tls/tls_handshake_state.h index 206e19096..2a78d1d1e 100644 --- a/src/tls/tls_handshake_state.h +++ b/src/tls/tls_handshake_state.h @@ -50,6 +50,8 @@ class Handshake_State 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, @@ -80,6 +82,7 @@ class Handshake_State 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; diff --git a/src/tls/tls_messages.h b/src/tls/tls_messages.h index 513fdad70..78cb6f714 100644 --- a/src/tls/tls_messages.h +++ b/src/tls/tls_messages.h @@ -108,6 +108,11 @@ class Client_Hello : public Handshake_Message 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; } + Client_Hello(Record_Writer& writer, Handshake_Hash& hash, const Policy& policy, @@ -145,6 +150,9 @@ class Client_Hello : public Handshake_Message 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; }; /** @@ -170,6 +178,8 @@ class Server_Hello : public Handshake_Message 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; } @@ -201,6 +211,7 @@ class Server_Hello : public Handshake_Message size_t max_fragment_size, bool client_has_secure_renegotiation, const MemoryRegion<byte>& reneg_info, + bool client_supports_session_tickets, bool client_has_npn, const std::vector<std::string>& next_protocols, RandomNumberGenerator& rng); @@ -220,6 +231,7 @@ class Server_Hello : public Handshake_Message bool m_next_protocol; std::vector<std::string> m_next_protocols; + bool m_supports_session_ticket; }; /** @@ -454,6 +466,30 @@ class Next_Protocol : public Handshake_Message 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 = 0); + + 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; + }; + } } diff --git a/src/tls/tls_reader.h b/src/tls/tls_reader.h index 162f691aa..f6b0d4088 100644 --- a/src/tls/tls_reader.h +++ b/src/tls/tls_reader.h @@ -50,6 +50,15 @@ class TLS_Data_Reader 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); diff --git a/src/tls/tls_server.cpp b/src/tls/tls_server.cpp index eacbc02e0..7632dfcdd 100644 --- a/src/tls/tls_server.cpp +++ b/src/tls/tls_server.cpp @@ -22,14 +22,31 @@ bool check_for_resume(Session& session_info, Session_Manager& session_manager, Client_Hello* client_hello) { - MemoryVector<byte> client_session_id = client_hello->session_id(); + const MemoryVector<byte>& client_session_id = client_hello->session_id(); + const MemoryVector<byte>& session_ticket = client_hello->session_ticket(); - if(client_session_id.empty()) // not resuming - return false; + 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; + // 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 + { +#warning fixed key + session_info = Session::decrypt(session_ticket, SymmetricKey("ABCDEF")); + } + catch(std::exception& e) + { + return false; + } + } // wrong version if(client_hello->version() != session_info.version()) @@ -45,14 +62,14 @@ bool check_for_resume(Session& session_info, session_info.compression_method())) return false; - // client sent a different SRP identity (!!!) + // 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 (!!!) + // client sent a different SNI hostname if(client_hello->sni_hostname() != "") { if(client_hello->sni_hostname() != session_info.sni_hostname()) @@ -204,13 +221,14 @@ void Server::process_handshake_msg(Handshake_Type type, state->server_hello = new Server_Hello( writer, state->hash, - session_info.session_id(), + 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->next_protocol_notification(), m_possible_protocols, rng); @@ -225,6 +243,22 @@ void Server::process_handshake_msg(Handshake_Type type, state->keys = Session_Keys(state, session_info.master_secret(), true); + if(!handshake_fn(session_info)) + { + if(state->server_hello->supports_session_ticket()) + state->new_session_ticket = new New_Session_Ticket(writer, state->hash); + else + session_manager.remove_entry(session_info.session_id()); + } + + // Should only send a new ticket if we need too (eg old session) + if(state->server_hello->supports_session_ticket() && !state->new_session_ticket) + { + state->new_session_ticket = + new New_Session_Ticket(writer, state->hash, + session_info.encrypt(SymmetricKey("ABCDEF"), rng)); + } + writer.send(CHANGE_CIPHER_SPEC, 1); writer.activate(SERVER, state->suite, state->keys, @@ -232,8 +266,6 @@ void Server::process_handshake_msg(Handshake_Type type, state->server_finished = new Finished(writer, state, SERVER); - if(!handshake_fn(session_info)) - session_manager.remove_entry(session_info.session_id()); state->set_expected_next(HANDSHAKE_CCS); } @@ -423,20 +455,10 @@ void Server::process_handshake_msg(Handshake_Type type, if(!state->server_finished) { - state->hash.update(type, contents); - - writer.send(CHANGE_CIPHER_SPEC, 1); - - writer.activate(SERVER, state->suite, state->keys, - state->server_hello->compression_method()); - - state->server_finished = new Finished(writer, state, SERVER); - - if(state->client_certs && state->client_verify) - peer_certs = state->client_certs->cert_chain(); - // 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(), @@ -447,12 +469,35 @@ void Server::process_handshake_msg(Handshake_Type type, secure_renegotiation.supported(), state->server_hello->fragment_size(), peer_certs, + MemoryVector<byte>(), m_hostname, "" ); if(handshake_fn(session_info)) - session_manager.save(session_info); + { + if(state->server_hello->supports_session_ticket()) + { + state->new_session_ticket = + new New_Session_Ticket(writer, state->hash, + session_info.encrypt(SymmetricKey("ABCDEF"), rng)); + } + 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); + + if(state->client_certs && state->client_verify) + peer_certs = state->client_certs->cert_chain(); } secure_renegotiation.update(state->client_finished, diff --git a/src/tls/tls_session.cpp b/src/tls/tls_session.cpp index 7a32c33d8..a41112bf4 100644 --- a/src/tls/tls_session.cpp +++ b/src/tls/tls_session.cpp @@ -1,6 +1,6 @@ /* * TLS Session State -* (C) 2011 Jack Lloyd +* (C) 2011-2012 Jack Lloyd * * Released under the terms of the Botan license */ @@ -11,6 +11,9 @@ #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 { @@ -25,10 +28,12 @@ Session::Session(const MemoryRegion<byte>& session_identifier, 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), @@ -42,6 +47,13 @@ Session::Session(const MemoryRegion<byte>& session_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; @@ -56,10 +68,11 @@ Session::Session(const byte ber[], size_t ber_len) .start_cons(SEQUENCE) .decode_and_check(static_cast<size_t>(TLS_SESSION_PARAM_STRUCT_VERSION), "Unknown version in session structure") - .decode(m_identifier, OCTET_STRING) .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) @@ -86,13 +99,6 @@ Session::Session(const byte ber[], size_t ber_len) } } -Session::Session(const std::string& pem) - { - SecureVector<byte> der = PEM_Code::decode_check_label(pem, "SSL SESSION"); - - *this = Session(&der[0], der.size()); - } - SecureVector<byte> Session::DER_encode() const { MemoryVector<byte> peer_cert_bits; @@ -102,10 +108,11 @@ SecureVector<byte> Session::DER_encode() const return DER_Encoder() .start_cons(SEQUENCE) .encode(static_cast<size_t>(TLS_SESSION_PARAM_STRUCT_VERSION)) - .encode(m_identifier, OCTET_STRING) .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)) @@ -124,6 +131,97 @@ std::string Session::PEM_encode() const return PEM_Code::encode(this->DER_encode(), "SSL SESSION"); } +namespace { + +const u64bit ENCRYPTED_SESSION_MAGIC = 0xACE4480800000000; + +} + +MemoryVector<byte> +Session::encrypt(const SymmetricKey& master_key, + RandomNumberGenerator& rng) + { + std::auto_ptr<KDF> kdf(get_kdf("KDF2(SHA-256)")); + + SymmetricKey aes_key = kdf->derive_key(32, master_key.bits_of(), + "tls.session.cipher-key"); + + SymmetricKey hmac_key = kdf->derive_key(32, master_key.bits_of(), + "tls.session.mac-key"); + + InitializationVector aes_iv(rng, 16); + + std::auto_ptr<MessageAuthenticationCode> mac(get_mac("HMAC(SHA-256)")); + mac->set_key(hmac_key); + + Pipe pipe(get_cipher("AES-256/CBC", aes_key, aes_iv, ENCRYPTION)); + pipe.process_msg(this->DER_encode()); + MemoryVector<byte> ctext = pipe.read_all(0); + + MemoryVector<byte> out(8); + store_be(ENCRYPTED_SESSION_MAGIC, &out[0]); + out += aes_iv.bits_of(); + out += ctext; + + mac->update(out); + + out += mac->final(); + return out; + } + +Session Session::decrypt(const MemoryRegion<byte>& buf, + const SymmetricKey& master_key) + { + try + { + /* + 8 bytes header + 16 bytes IV + 32 bytes MAC + 16 bytes per AES block * 4 blocks (absolute min amount due to + 48 bytes master secret) + */ + if(buf.size() < (8 + 16 + 32 + 4*16)) + throw Decoding_Error("Encrypted TLS session too short to be valid"); + + std::auto_ptr<KDF> kdf(get_kdf("KDF2(SHA-256)")); + + SymmetricKey hmac_key = kdf->derive_key(32, master_key.bits_of(), + "tls.session.mac-key"); + + std::auto_ptr<MessageAuthenticationCode> mac(get_mac("HMAC(SHA-256)")); + mac->set_key(hmac_key); + + mac->update(&buf[0], buf.size() - 32); + MemoryVector<byte> computed_mac = mac->final(); + + if(!same_mem(&buf[buf.size() - 32], &computed_mac[0], computed_mac.size())) + throw Decoding_Error("MAC verification failed for encrypted session"); + + const u64bit header = load_be<u64bit>(buf, 0); + + if(header != ENCRYPTED_SESSION_MAGIC) + throw Decoding_Error("Unknown header value in encrypted session"); + + SymmetricKey aes_key = kdf->derive_key(32, master_key.bits_of(), + "tls.session.cipher-key"); + + InitializationVector aes_iv(&buf[8], 16); + + Pipe pipe(get_cipher("AES-256/CBC", aes_key, aes_iv, DECRYPTION)); + pipe.process_msg(&buf[8+16], buf.size() - (32 + 8 + 16)); + 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 index 96b6d6daf..ab11154c9 100644 --- a/src/tls/tls_session.h +++ b/src/tls/tls_session.h @@ -1,6 +1,6 @@ /* * TLS Session -* (C) 2011 Jack Lloyd +* (C) 2011-2012 Jack Lloyd * * Released under the terms of the Botan license */ @@ -13,6 +13,7 @@ #include <botan/tls_ciphersuite.h> #include <botan/tls_magic.h> #include <botan/secmem.h> +#include <botan/symkey.h> namespace Botan { @@ -50,6 +51,7 @@ class BOTAN_DLL Session 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 = ""); @@ -71,6 +73,20 @@ class BOTAN_DLL Session SecureVector<byte> DER_encode() const; /** + * Encrypt a session (useful for serialization or session tickets) + */ + MemoryVector<byte> encrypt(const SymmetricKey& key, + RandomNumberGenerator& rng); + + /** + * Decrypt a session created by encrypt + * @param ctext the ciphertext returned by encrypt + * @param key the same key used by the encrypting side + */ + static Session decrypt(const MemoryRegion<byte>& ctext, + const SymmetricKey& key); + + /** * Encode this session data for storage * @warning if the master secret is compromised so is the * session traffic @@ -146,12 +162,18 @@ class BOTAN_DLL Session */ u64bit start_time() const { return m_start_time; } + /** + * 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 = 1 }; + 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; |