diff options
Diffstat (limited to 'src/lib/tls')
57 files changed, 11634 insertions, 0 deletions
diff --git a/src/lib/tls/info.txt b/src/lib/tls/info.txt new file mode 100644 index 000000000..adae12cb2 --- /dev/null +++ b/src/lib/tls/info.txt @@ -0,0 +1,90 @@ +define TLS 20131128 + +load_on auto + +<header:public> +tls_alert.h +tls_blocking.h +tls_channel.h +tls_ciphersuite.h +tls_client.h +tls_exceptn.h +tls_handshake_msg.h +tls_magic.h +tls_server_info.h +tls_policy.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_io.h +tls_handshake_state.h +tls_heartbeats.h +tls_messages.h +tls_reader.h +tls_record.h +tls_seq_numbers.h +tls_session_key.h +</header:internal> + +<source> +msg_cert_req.cpp +msg_cert_verify.cpp +msg_certificate.cpp +msg_client_hello.cpp +msg_client_kex.cpp +msg_finished.cpp +msg_hello_verify.cpp +msg_next_protocol.cpp +msg_server_hello.cpp +msg_server_kex.cpp +msg_session_ticket.cpp +tls_alert.cpp +tls_blocking.cpp +tls_channel.cpp +tls_ciphersuite.cpp +tls_client.cpp +tls_extensions.cpp +tls_handshake_hash.cpp +tls_handshake_io.cpp +tls_handshake_state.cpp +tls_heartbeats.cpp +tls_policy.cpp +tls_server.cpp +tls_session.cpp +tls_session_key.cpp +tls_session_manager_memory.cpp +tls_suite_info.cpp +tls_record.cpp +tls_version.cpp +</source> + +<requires> +aead +aes +asn1 +cbc +credentials +cryptobox_psk +dh +ecdh +eme_pkcs +emsa3 +hmac +kdf2 +md5 +prf_ssl3 +prf_tls +rng +rsa +sha1 +sha2_32 +srp6 +ssl3mac +x509 +</requires> diff --git a/src/lib/tls/msg_cert_req.cpp b/src/lib/tls/msg_cert_req.cpp new file mode 100644 index 000000000..23d59c6d4 --- /dev/null +++ b/src/lib/tls/msg_cert_req.cpp @@ -0,0 +1,163 @@ +/* +* 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/internal/tls_handshake_io.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(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + const std::vector<X509_DN>& ca_certs, + Protocol_Version version) : + m_names(ca_certs), + m_cert_key_types({ "RSA", "DSA", "ECDSA" }) + { + if(version.supports_negotiable_signature_algorithms()) + { + std::vector<std::string> hashes = policy.allowed_signature_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(io.send(*this)); + } + +/** +* Deserialize a Certificate Request message +*/ +Certificate_Req::Certificate_Req(const std::vector<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; + + m_cert_key_types.push_back(cert_type_name); + } + + if(version.supports_negotiable_signature_algorithms()) + { + 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)); + } + } + + 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); + m_names.push_back(name); + } + } + +/** +* Serialize a Certificate Request message +*/ +std::vector<byte> Certificate_Req::serialize() const + { + std::vector<byte> buf; + + std::vector<byte> cert_types; + + for(size_t i = 0; i != m_cert_key_types.size(); ++i) + cert_types.push_back(cert_type_name_to_code(m_cert_key_types[i])); + + append_tls_length_value(buf, cert_types, 1); + + if(!m_supported_algos.empty()) + buf += Signature_Algorithms(m_supported_algos).serialize(); + + std::vector<byte> encoded_names; + + for(size_t i = 0; i != m_names.size(); ++i) + { + DER_Encoder encoder; + encoder.encode(m_names[i]); + + append_tls_length_value(encoded_names, encoder.get_contents(), 2); + } + + append_tls_length_value(buf, encoded_names, 2); + + return buf; + } + +} + +} diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp new file mode 100644 index 000000000..436b84c24 --- /dev/null +++ b/src/lib/tls/msg_cert_verify.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/internal/tls_handshake_io.h> +#include <memory> + +namespace Botan { + +namespace TLS { + +/* +* Create a new Certificate Verify message +*/ +Certificate_Verify::Certificate_Verify(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + 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, m_hash_algo, m_sig_algo, true, policy); + + PK_Signer signer(*priv_key, format.first, format.second); + + if(state.version() == Protocol_Version::SSL_V3) + { + secure_vector<byte> md5_sha = state.hash().final_ssl3( + state.session_keys().master_secret()); + + if(priv_key->algo_name() == "DSA") + m_signature = signer.sign_message(&md5_sha[16], md5_sha.size()-16, rng); + else + m_signature = signer.sign_message(md5_sha, rng); + } + else + { + m_signature = signer.sign_message(state.hash().get_contents(), rng); + } + + state.hash().update(io.send(*this)); + } + +/* +* Deserialize a Certificate Verify message +*/ +Certificate_Verify::Certificate_Verify(const std::vector<byte>& buf, + Protocol_Version version) + { + TLS_Data_Reader reader(buf); + + if(version.supports_negotiable_signature_algorithms()) + { + 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); + } + +/* +* Serialize a Certificate Verify message +*/ +std::vector<byte> Certificate_Verify::serialize() const + { + std::vector<byte> buf; + + 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)); + } + + const u16bit sig_len = m_signature.size(); + buf.push_back(get_byte(0, sig_len)); + buf.push_back(get_byte(1, sig_len)); + buf += m_signature; + + return buf; + } + +/* +* Verify a Certificate Verify message +*/ +bool Certificate_Verify::verify(const X509_Certificate& cert, + const Handshake_State& state) const + { + std::unique_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, true); + + PK_Verifier verifier(*key, format.first, format.second); + + if(state.version() == Protocol_Version::SSL_V3) + { + secure_vector<byte> md5_sha = state.hash().final_ssl3( + state.session_keys().master_secret()); + + return verifier.verify_message(&md5_sha[16], md5_sha.size()-16, + &m_signature[0], m_signature.size()); + } + + return verifier.verify_message(state.hash().get_contents(), m_signature); + } + +} + +} diff --git a/src/lib/tls/msg_certificate.cpp b/src/lib/tls/msg_certificate.cpp new file mode 100644 index 000000000..417ad34ce --- /dev/null +++ b/src/lib/tls/msg_certificate.cpp @@ -0,0 +1,88 @@ +/* +* Certificate 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/internal/tls_handshake_io.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/loadstor.h> + +namespace Botan { + +namespace TLS { + +/** +* Create a new Certificate message +*/ +Certificate::Certificate(Handshake_IO& io, + Handshake_Hash& hash, + const std::vector<X509_Certificate>& cert_list) : + m_certs(cert_list) + { + hash.update(io.send(*this)); + } + +/** +* Deserialize a Certificate message +*/ +Certificate::Certificate(const std::vector<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(size_t remaining_bytes = &buf[buf.size()] - certs) + { + if(remaining_bytes < 3) + throw Decoding_Error("Certificate: Message malformed"); + + const size_t cert_size = make_u32bit(0, certs[0], certs[1], certs[2]); + + if(remaining_bytes < (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 +*/ +std::vector<byte> Certificate::serialize() const + { + std::vector<byte> buf(3); + + for(size_t i = 0; i != m_certs.size(); ++i) + { + std::vector<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/lib/tls/msg_client_hello.cpp b/src/lib/tls/msg_client_hello.cpp new file mode 100644 index 000000000..0d91af472 --- /dev/null +++ b/src/lib/tls/msg_client_hello.cpp @@ -0,0 +1,287 @@ +/* +* 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_handshake_io.h> +#include <botan/internal/stl_util.h> +#include <chrono> + +namespace Botan { + +namespace TLS { + +enum { + TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF +}; + +std::vector<byte> make_hello_random(RandomNumberGenerator& rng) + { + std::vector<byte> buf(32); + + const u32bit time32 = static_cast<u32bit>( + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); + + store_be(time32, &buf[0]); + rng.randomize(&buf[4], buf.size() - 4); + return buf; + } + +/* +* Create a new Hello Request message +*/ +Hello_Request::Hello_Request(Handshake_IO& io) + { + io.send(*this); + } + +/* +* Deserialize a Hello Request message +*/ +Hello_Request::Hello_Request(const std::vector<byte>& buf) + { + if(buf.size()) + throw Decoding_Error("Bad Hello_Request, has non-zero size"); + } + +/* +* Serialize a Hello Request message +*/ +std::vector<byte> Hello_Request::serialize() const + { + return std::vector<byte>(); + } + +/* +* Create a new Client Hello message +*/ +Client_Hello::Client_Hello(Handshake_IO& io, + Handshake_Hash& hash, + Protocol_Version version, + const Policy& policy, + RandomNumberGenerator& rng, + const std::vector<byte>& reneg_info, + bool next_protocol, + const std::string& hostname, + const std::string& srp_identifier) : + m_version(version), + m_random(make_hello_random(rng)), + m_suites(policy.ciphersuite_list(m_version, (srp_identifier != ""))), + m_comp_methods(policy.compression()) + { + m_extensions.add(new Renegotiation_Extension(reneg_info)); + m_extensions.add(new SRP_Identifier(srp_identifier)); + m_extensions.add(new Server_Name_Indicator(hostname)); + m_extensions.add(new Session_Ticket()); + m_extensions.add(new Supported_Elliptic_Curves(policy.allowed_ecc_curves())); + + if(policy.negotiate_heartbeat_support()) + m_extensions.add(new Heartbeat_Support_Indicator(true)); + + if(m_version.supports_negotiable_signature_algorithms()) + m_extensions.add(new Signature_Algorithms(policy.allowed_signature_hashes(), + policy.allowed_signature_methods())); + + if(reneg_info.empty() && next_protocol) + m_extensions.add(new Next_Protocol_Notification()); + + hash.update(io.send(*this)); + } + +/* +* Create a new Client Hello message (session resumption case) +*/ +Client_Hello::Client_Hello(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + RandomNumberGenerator& rng, + const std::vector<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(policy.ciphersuite_list(m_version, (session.srp_identifier() != ""))), + m_comp_methods(policy.compression()) + { + 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()); + + m_extensions.add(new Renegotiation_Extension(reneg_info)); + m_extensions.add(new SRP_Identifier(session.srp_identifier())); + m_extensions.add(new Server_Name_Indicator(session.server_info().hostname())); + m_extensions.add(new Session_Ticket(session.session_ticket())); + m_extensions.add(new Supported_Elliptic_Curves(policy.allowed_ecc_curves())); + + if(policy.negotiate_heartbeat_support()) + m_extensions.add(new Heartbeat_Support_Indicator(true)); + + if(session.fragment_size() != 0) + m_extensions.add(new Maximum_Fragment_Length(session.fragment_size())); + + if(m_version.supports_negotiable_signature_algorithms()) + m_extensions.add(new Signature_Algorithms(policy.allowed_signature_hashes(), + policy.allowed_signature_methods())); + + if(reneg_info.empty() && next_protocol) + m_extensions.add(new Next_Protocol_Notification()); + + hash.update(io.send(*this)); + } + +/* +* Read a counterparty client hello +*/ +Client_Hello::Client_Hello(const std::vector<byte>& buf, Handshake_Type type) + { + if(type == CLIENT_HELLO) + deserialize(buf); + else + deserialize_sslv2(buf); + } + +void Client_Hello::update_hello_cookie(const Hello_Verify_Request& hello_verify) + { + if(!m_version.is_datagram_protocol()) + throw std::runtime_error("Cannot use hello cookie with stream protocol"); + + m_hello_cookie = hello_verify.cookie(); + } + +/* +* Serialize a Client Hello message +*/ +std::vector<byte> Client_Hello::serialize() const + { + std::vector<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); + + if(m_version.is_datagram_protocol()) + append_tls_length_value(buf, m_hello_cookie, 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) + */ + + buf += m_extensions.serialize(); + + return buf; + } + +void Client_Hello::deserialize_sslv2(const std::vector<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"); + } + + m_version = Protocol_Version(buf[1], buf[2]); + + 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_random.resize(challenge_len); + copy_mem(&m_random[0], &buf[9+cipher_spec_len+m_session_id_len], challenge_len); + + if(offered_suite(static_cast<u16bit>(TLS_EMPTY_RENEGOTIATION_INFO_SCSV))) + m_extensions.add(new Renegotiation_Extension()); + } + +/* +* Deserialize a Client Hello message +*/ +void Client_Hello::deserialize(const std::vector<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); + + if(m_version.is_datagram_protocol()) + m_hello_cookie = reader.get_range<byte>(1, 0, 255); + + 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); + + m_extensions.deserialize(reader); + + if(offered_suite(static_cast<u16bit>(TLS_EMPTY_RENEGOTIATION_INFO_SCSV))) + { + if(Renegotiation_Extension* reneg = m_extensions.get<Renegotiation_Extension>()) + { + if(!reneg->renegotiation_info().empty()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Client send renegotiation SCSV and non-empty extension"); + } + else + { + // add fake extension + m_extensions.add(new Renegotiation_Extension()); + } + } + } + +/* +* 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/lib/tls/msg_client_kex.cpp b/src/lib/tls/msg_client_kex.cpp new file mode 100644 index 000000000..ae8b82fd4 --- /dev/null +++ b/src/lib/tls/msg_client_kex.cpp @@ -0,0 +1,419 @@ +/* +* 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/internal/tls_handshake_io.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 { + +secure_vector<byte> strip_leading_zeros(const secure_vector<byte>& input) + { + size_t leading_zeros = 0; + + for(size_t i = 0; i != input.size(); ++i) + { + if(input[i] != 0) + break; + ++leading_zeros; + } + + secure_vector<byte> output(&input[leading_zeros], + &input[input.size()]); + return output; + } + +} + +/* +* Create a new Client Key Exchange message +*/ +Client_Key_Exchange::Client_Key_Exchange(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + Credentials_Manager& creds, + const Public_Key* server_public_key, + const std::string& hostname, + RandomNumberGenerator& rng) + { + const std::string kex_algo = state.ciphersuite().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(m_key_material, psk_identity, 2); + + SymmetricKey psk = creds.psk("tls-client", hostname, psk_identity); + + std::vector<byte> zeros(psk.length()); + + append_tls_length_value(m_pre_master, zeros, 2); + append_tls_length_value(m_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(m_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"); + + if(p.bits() < policy.minimum_dh_group_size()) + throw TLS_Exception(Alert::INSUFFICIENT_SECURITY, + "Server sent DH group of " + + std::to_string(p.bits()) + + " bits, policy requires at least " + + std::to_string(policy.minimum_dh_group_size())); + + /* + * A basic check for key validity. As we do not know q here we + * cannot check that Y is in the right subgroup. However since + * our key is ephemeral there does not seem to be any + * advantage to bogus keys anyway. + */ + if(Y <= 1 || Y >= p - 1) + throw TLS_Exception(Alert::INSUFFICIENT_SECURITY, + "Server sent bad DH key for DHE 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); + + DH_PrivateKey priv_key(rng, group); + + PK_Key_Agreement ka(priv_key, "Raw"); + + secure_vector<byte> dh_secret = strip_leading_zeros( + ka.derive_key(0, counterparty_key.public_value()).bits_of()); + + if(kex_algo == "DH") + m_pre_master = dh_secret; + else + { + append_tls_length_value(m_pre_master, dh_secret, 2); + append_tls_length_value(m_pre_master, psk.bits_of(), 2); + } + + append_tls_length_value(m_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 " + std::to_string(curve_id)); + + EC_Group group(name); + + std::vector<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"); + + secure_vector<byte> ecdh_secret = + ka.derive_key(0, counterparty_key.public_value()).bits_of(); + + if(kex_algo == "ECDH") + m_pre_master = ecdh_secret; + else + { + append_tls_length_value(m_pre_master, ecdh_secret, 2); + append_tls_length_value(m_pre_master, psk.bits_of(), 2); + } + + append_tls_length_value(m_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)); + std::vector<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(m_key_material, BigInt::encode(srp_vals.first), 2); + m_pre_master = srp_vals.second.bits_of(); + } + else + { + throw Internal_Error("Client_Key_Exchange: Unknown kex " + + kex_algo); + } + + reader.assert_done(); + } + 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(!server_public_key) + throw Internal_Error("No server public key for RSA exchange"); + + if(auto rsa_pub = dynamic_cast<const RSA_PublicKey*>(server_public_key)) + { + const Protocol_Version offered_version = state.client_hello()->version(); + + m_pre_master = rng.random_vec(48); + m_pre_master[0] = offered_version.major_version(); + m_pre_master[1] = offered_version.minor_version(); + + PK_Encryptor_EME encryptor(*rsa_pub, "PKCS1v15"); + + std::vector<byte> encrypted_key = encryptor.encrypt(m_pre_master, rng); + + if(state.version() == Protocol_Version::SSL_V3) + m_key_material = encrypted_key; // no length field + else + append_tls_length_value(m_key_material, encrypted_key, 2); + } + else + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Expected a RSA key in server cert but got " + + server_public_key->algo_name()); + } + + state.hash().update(io.send(*this)); + } + +/* +* Read a Client Key Exchange message +*/ +Client_Key_Exchange::Client_Key_Exchange(const std::vector<byte>& contents, + const Handshake_State& state, + const Private_Key* server_rsa_kex_key, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng) + { + const std::string kex_algo = state.ciphersuite().kex_algo(); + + if(kex_algo == "RSA") + { + BOTAN_ASSERT(state.server_certs() && !state.server_certs()->cert_chain().empty(), + "RSA key exchange negotiated so server sent a certificate"); + + if(!server_rsa_kex_key) + throw Internal_Error("Expected RSA kex but no server kex key set"); + + if(!dynamic_cast<const RSA_PrivateKey*>(server_rsa_kex_key)) + throw Internal_Error("Expected RSA key but got " + server_rsa_kex_key->algo_name()); + + PK_Decryptor_EME decryptor(*server_rsa_kex_key, "PKCS1v15"); + + Protocol_Version client_version = state.client_hello()->version(); + + /* + * This is used as the pre-master if RSA decryption fails. + * Otherwise we can be used as an oracle. See Bleichenbacher + * "Chosen Ciphertext Attacks against Protocols Based on RSA + * Encryption Standard PKCS #1", Crypto 98 + * + * Create it here instead if in the catch clause as otherwise we + * expose a timing channel WRT the generation of the fake value. + * Some timing channel likely remains due to exception handling + * and the like. + */ + secure_vector<byte> fake_pre_master = rng.random_vec(48); + fake_pre_master[0] = client_version.major_version(); + fake_pre_master[1] = client_version.minor_version(); + + try + { + if(state.version() == Protocol_Version::SSL_V3) + { + m_pre_master = decryptor.decrypt(contents); + } + else + { + TLS_Data_Reader reader(contents); + m_pre_master = decryptor.decrypt(reader.get_range<byte>(2, 0, 65535)); + } + + if(m_pre_master.size() != 48 || + client_version.major_version() != m_pre_master[0] || + client_version.minor_version() != m_pre_master[1]) + { + throw Decoding_Error("Client_Key_Exchange: Secret corrupted"); + } + } + catch(...) + { + m_pre_master = fake_pre_master; + } + } + 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") + { + std::vector<byte> zeros(psk.length()); + append_tls_length_value(m_pre_master, zeros, 2); + append_tls_length_value(m_pre_master, psk.bits_of(), 2); + } + else if(kex_algo == "SRP_SHA") + { + SRP6_Server_Session& srp = state.server_kex()->server_srp_params(); + + m_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"); + + std::vector<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); + + secure_vector<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(m_pre_master, shared_secret, 2); + append_tls_length_value(m_pre_master, psk.bits_of(), 2); + } + else + m_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. + */ + m_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/lib/tls/msg_finished.cpp b/src/lib/tls/msg_finished.cpp new file mode 100644 index 000000000..c018497c8 --- /dev/null +++ b/src/lib/tls/msg_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/internal/tls_handshake_io.h> +#include <memory> + +namespace Botan { + +namespace TLS { + +namespace { + +/* +* Compute the verify_data +*/ +std::vector<byte> finished_compute_verify(const 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 + + std::vector<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 unlock(hash.final_ssl3(state.session_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::unique_ptr<KDF> prf(state.protocol_specific_prf()); + + std::vector<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.ciphersuite().prf_algo()); + + return unlock(prf->derive_key(12, state.session_keys().master_secret(), input)); + } + } + +} + +/* +* Create a new Finished message +*/ +Finished::Finished(Handshake_IO& io, + Handshake_State& state, + Connection_Side side) + { + m_verification_data = finished_compute_verify(state, side); + state.hash().update(io.send(*this)); + } + +/* +* Serialize a Finished message +*/ +std::vector<byte> Finished::serialize() const + { + return m_verification_data; + } + +/* +* Deserialize a Finished message +*/ +Finished::Finished(const std::vector<byte>& buf) + { + m_verification_data = buf; + } + +/* +* Verify a Finished message +*/ +bool Finished::verify(const Handshake_State& state, + Connection_Side side) const + { + return (m_verification_data == finished_compute_verify(state, side)); + } + +} + +} diff --git a/src/lib/tls/msg_hello_verify.cpp b/src/lib/tls/msg_hello_verify.cpp new file mode 100644 index 000000000..f8a117c03 --- /dev/null +++ b/src/lib/tls/msg_hello_verify.cpp @@ -0,0 +1,69 @@ +/* +* 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 std::vector<byte>& buf) + { + if(buf.size() < 3) + throw Decoding_Error("Hello verify request too small"); + + Protocol_Version version(buf[0], buf[1]); + + if(version != Protocol_Version::DTLS_V10 && + version != Protocol_Version::DTLS_V12) + { + throw Decoding_Error("Unknown version from server in hello verify request"); + } + + if(static_cast<size_t>(buf[2]) + 3 != buf.size()) + throw Decoding_Error("Bad length in hello verify request"); + + m_cookie.assign(&buf[3], &buf[buf.size()]); + } + +Hello_Verify_Request::Hello_Verify_Request(const std::vector<byte>& client_hello_bits, + const std::string& client_identity, + const SymmetricKey& secret_key) + { + std::unique_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 = unlock(hmac->final()); + } + +std::vector<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::DTLS_V10); + + std::vector<byte> bits; + bits.push_back(format_version.major_version()); + bits.push_back(format_version.minor_version()); + bits.push_back(static_cast<byte>(m_cookie.size())); + bits += m_cookie; + return bits; + } + +} + +} diff --git a/src/lib/tls/msg_next_protocol.cpp b/src/lib/tls/msg_next_protocol.cpp new file mode 100644 index 000000000..a09fd02d1 --- /dev/null +++ b/src/lib/tls/msg_next_protocol.cpp @@ -0,0 +1,55 @@ +/* +* Next Protocol Negotiation +* (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/internal/tls_handshake_io.h> + +namespace Botan { + +namespace TLS { + +Next_Protocol::Next_Protocol(Handshake_IO& io, + Handshake_Hash& hash, + const std::string& protocol) : + m_protocol(protocol) + { + hash.update(io.send(*this)); + } + +Next_Protocol::Next_Protocol(const std::vector<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 + } + +std::vector<byte> Next_Protocol::serialize() const + { + std::vector<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/lib/tls/msg_server_hello.cpp b/src/lib/tls/msg_server_hello.cpp new file mode 100644 index 000000000..a775e0b4b --- /dev/null +++ b/src/lib/tls/msg_server_hello.cpp @@ -0,0 +1,142 @@ +;/* +* 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/internal/tls_handshake_io.h> +#include <botan/internal/stl_util.h> + +namespace Botan { + +namespace TLS { + +/* +* Create a new Server Hello message +*/ +Server_Hello::Server_Hello(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + const std::vector<byte>& session_id, + Protocol_Version ver, + u16bit ciphersuite, + byte compression, + size_t max_fragment_size, + bool client_has_secure_renegotiation, + const std::vector<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) + { + if(client_has_heartbeat && policy.negotiate_heartbeat_support()) + m_extensions.add(new Heartbeat_Support_Indicator(true)); + + /* + * Even a client that offered SSLv3 and sent the SCSV will get an + * extension back. This is probably the right thing to do. + */ + if(client_has_secure_renegotiation) + m_extensions.add(new Renegotiation_Extension(reneg_info)); + + if(max_fragment_size) + m_extensions.add(new Maximum_Fragment_Length(max_fragment_size)); + + if(client_has_npn) + m_extensions.add(new Next_Protocol_Notification(next_protocols)); + + if(offer_session_ticket) + m_extensions.add(new Session_Ticket()); + + hash.update(io.send(*this)); + } + +/* +* Deserialize a Server Hello message +*/ +Server_Hello::Server_Hello(const std::vector<byte>& buf) + { + 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); + + 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(); + + m_extensions.deserialize(reader); + } + +/* +* Serialize a Server Hello message +*/ +std::vector<byte> Server_Hello::serialize() const + { + std::vector<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); + + buf += m_extensions.serialize(); + + return buf; + } + +/* +* Create a new Server Hello Done message +*/ +Server_Hello_Done::Server_Hello_Done(Handshake_IO& io, + Handshake_Hash& hash) + { + hash.update(io.send(*this)); + } + +/* +* Deserialize a Server Hello Done message +*/ +Server_Hello_Done::Server_Hello_Done(const std::vector<byte>& buf) + { + if(buf.size()) + throw Decoding_Error("Server_Hello_Done: Must be empty, and is not"); + } + +/* +* Serialize a Server Hello Done message +*/ +std::vector<byte> Server_Hello_Done::serialize() const + { + return std::vector<byte>(); + } + +} + +} diff --git a/src/lib/tls/msg_server_kex.cpp b/src/lib/tls/msg_server_kex.cpp new file mode 100644 index 000000000..b8293d3e8 --- /dev/null +++ b/src/lib/tls/msg_server_kex.cpp @@ -0,0 +1,285 @@ +/* +* 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/internal/tls_handshake_io.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(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + Credentials_Manager& creds, + RandomNumberGenerator& rng, + const Private_Key* signing_key) + { + const std::string hostname = state.client_hello()->sni_hostname(); + const std::string kex_algo = state.ciphersuite().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::unique_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.reset(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::unique_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.reset(ecdh.release()); + } + else if(kex_algo == "SRP_SHA") + { + const std::string srp_identifier = state.client_hello()->srp_identifier(); + + std::string group_id; + BigInt v; + std::vector<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.reset(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.ciphersuite().sig_algo() != "") + { + BOTAN_ASSERT(signing_key, "Signing key was set"); + + std::pair<std::string, Signature_Format> format = + state.choose_sig_format(*signing_key, m_hash_algo, m_sig_algo, false, policy); + + 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(io.send(*this)); + } + +/** +* Deserialize a Server Key Exchange message +*/ +Server_Key_Exchange::Server_Key_Exchange(const std::vector<byte>& buf, + const std::string& kex_algo, + const std::string& sig_algo, + Protocol_Version version) : + m_kex_key(nullptr), m_srp_params(nullptr) + { + 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); + + std::vector<byte> ecdh_key = reader.get_range<byte>(1, 1, 255); + + if(name == "") + throw Decoding_Error("Server_Key_Exchange: Server sent unknown named curve " + + std::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)); + std::vector<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.supports_negotiable_signature_algorithms()) + { + 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); + } + + reader.assert_done(); + } + +Server_Key_Exchange::~Server_Key_Exchange() {} + +/** +* Serialize a Server Key Exchange message +*/ +std::vector<byte> Server_Key_Exchange::serialize() const + { + std::vector<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 Public_Key& server_key, + const Handshake_State& state) const + { + std::pair<std::string, Signature_Format> format = + state.understand_sig_format(server_key, m_hash_algo, m_sig_algo, false); + + PK_Verifier verifier(server_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_NONNULL(m_kex_key); + return *m_kex_key; + } + +// Only valid for SRP negotiation +SRP6_Server_Session& Server_Key_Exchange::server_srp_params() const + { + BOTAN_ASSERT_NONNULL(m_srp_params); + return *m_srp_params; + } +} + +} diff --git a/src/lib/tls/msg_session_ticket.cpp b/src/lib/tls/msg_session_ticket.cpp new file mode 100644 index 000000000..2bb9987a9 --- /dev/null +++ b/src/lib/tls/msg_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/internal/tls_handshake_io.h> +#include <botan/loadstor.h> + +namespace Botan { + +namespace TLS { + +New_Session_Ticket::New_Session_Ticket(Handshake_IO& io, + Handshake_Hash& hash, + const std::vector<byte>& ticket, + u32bit lifetime) : + m_ticket_lifetime_hint(lifetime), + m_ticket(ticket) + { + hash.update(io.send(*this)); + } + +New_Session_Ticket::New_Session_Ticket(Handshake_IO& io, + Handshake_Hash& hash) : + m_ticket_lifetime_hint(0) + { + hash.update(io.send(*this)); + } + +New_Session_Ticket::New_Session_Ticket(const std::vector<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); + } + +std::vector<byte> New_Session_Ticket::serialize() const + { + std::vector<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/lib/tls/sessions_sqlite/info.txt b/src/lib/tls/sessions_sqlite/info.txt new file mode 100644 index 000000000..76d53f995 --- /dev/null +++ b/src/lib/tls/sessions_sqlite/info.txt @@ -0,0 +1,6 @@ +define TLS_SQLITE3_SESSION_MANAGER 20131128 + +<requires> +pbkdf2 +sqlite3 +</requires> diff --git a/src/lib/tls/sessions_sqlite/tls_session_manager_sqlite.cpp b/src/lib/tls/sessions_sqlite/tls_session_manager_sqlite.cpp new file mode 100644 index 000000000..d4f286a8d --- /dev/null +++ b/src/lib/tls/sessions_sqlite/tls_session_manager_sqlite.cpp @@ -0,0 +1,223 @@ +/* +* SQLite TLS Session Manager +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/tls_session_manager_sqlite.h> +#include <botan/internal/sqlite3.h> +#include <botan/lookup.h> +#include <botan/hex.h> +#include <botan/loadstor.h> +#include <memory> +#include <chrono> + +namespace Botan { + +namespace TLS { + +namespace { + +SymmetricKey derive_key(const std::string& passphrase, + const byte salt[], + size_t salt_len, + size_t iterations, + size_t& check_val) + { + std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(SHA-512)")); + + secure_vector<byte> x = pbkdf->derive_key(32 + 2, + passphrase, + salt, salt_len, + iterations).bits_of(); + + check_val = make_u16bit(x[0], x[1]); + return SymmetricKey(&x[2], x.size() - 2); + } + +} + +Session_Manager_SQLite::Session_Manager_SQLite(const std::string& passphrase, + RandomNumberGenerator& rng, + const std::string& db_filename, + size_t max_sessions, + std::chrono::seconds session_lifetime) : + m_rng(rng), + m_max_sessions(max_sessions), + m_session_lifetime(session_lifetime) + { + m_db = new sqlite3_database(db_filename); + + m_db->create_table( + "create table if not exists tls_sessions " + "(" + "session_id TEXT PRIMARY KEY, " + "session_start INTEGER, " + "hostname TEXT, " + "hostport INTEGER, " + "session BLOB" + ")"); + + m_db->create_table( + "create table if not exists tls_sessions_metadata " + "(" + "passphrase_salt BLOB, " + "passphrase_iterations INTEGER, " + "passphrase_check INTEGER " + ")"); + + const size_t salts = m_db->row_count("tls_sessions_metadata"); + + if(salts == 1) + { + // existing db + sqlite3_statement stmt(m_db, "select * from tls_sessions_metadata"); + + if(stmt.step()) + { + 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 + + std::vector<byte> salt = unlock(rng.random_vec(16)); + const size_t iterations = 256 * 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() + { + delete m_db; + } + +bool Session_Manager_SQLite::load_from_session_id(const std::vector<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)); + + while(stmt.step()) + { + 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(...) + { + } + } + + return false; + } + +bool Session_Manager_SQLite::load_from_server_info(const Server_Information& server, + 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, server.hostname()); + stmt.bind(2, server.port()); + + while(stmt.step()) + { + 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(...) + { + } + } + + return false; + } + +void Session_Manager_SQLite::remove_entry(const std::vector<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.server_info().hostname()); + stmt.bind(4, session.server_info().port()); + 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, std::chrono::system_clock::now() - m_session_lifetime); + + remove_expired.spin(); + + const size_t sessions = m_db->row_count("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/lib/tls/sessions_sqlite/tls_session_manager_sqlite.h b/src/lib/tls/sessions_sqlite/tls_session_manager_sqlite.h new file mode 100644 index 000000000..7892ccd6a --- /dev/null +++ b/src/lib/tls/sessions_sqlite/tls_session_manager_sqlite.h @@ -0,0 +1,80 @@ +/* +* SQLite3 TLS Session Manager +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#ifndef BOTAN_TLS_SQLITE3_SESSION_MANAGER_H__ +#define BOTAN_TLS_SQLITE3_SESSION_MANAGER_H__ + +#include <botan/tls_session_manager.h> +#include <botan/rng.h> + +namespace Botan { + +class sqlite3_database; + +namespace TLS { + +/** +* An implementation of Session_Manager that saves values in a SQLite3 +* database file, with the session data encrypted using a passphrase. +* +* @warning For clients, the hostnames associated with the saved +* sessions are stored in the database in plaintext. This may be a +* serious privacy risk in some situations. +*/ +class BOTAN_DLL Session_Manager_SQLite : public Session_Manager + { + public: + /** + * @param passphrase used to encrypt the session data + * @param rng a random number generator + * @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, + std::chrono::seconds session_lifetime = std::chrono::seconds(7200)); + + ~Session_Manager_SQLite(); + + bool load_from_session_id(const std::vector<byte>& session_id, + Session& session) override; + + bool load_from_server_info(const Server_Information& info, + Session& session) override; + + void remove_entry(const std::vector<byte>& session_id) override; + + void save(const Session& session_data) override; + + std::chrono::seconds session_lifetime() const override + { 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; + std::chrono::seconds m_session_lifetime; + sqlite3_database* m_db; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_alert.cpp b/src/lib/tls/tls_alert.cpp new file mode 100644 index 000000000..15bb2a2dc --- /dev/null +++ b/src/lib/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 secure_vector<byte>& buf) + { + if(buf.size() != 2) + throw Decoding_Error("Alert: Bad size " + std::to_string(buf.size()) + + " for alert message"); + + if(buf[0] == 1) m_fatal = false; + else if(buf[0] == 2) m_fatal = true; + else + throw Decoding_Error("Alert: Bad code for alert level"); + + const byte dc = buf[1]; + + m_type_code = static_cast<Type>(dc); + } + +std::vector<byte> Alert::serialize() const + { + return std::vector<byte>({ + static_cast<byte>(is_fatal() ? 2 : 1), + static_cast<byte>(type()) + }); + } + +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_" + std::to_string(type()); + } + +} + +} diff --git a/src/lib/tls/tls_alert.h b/src/lib/tls/tls_alert.h new file mode 100644 index 000000000..bf32178ee --- /dev/null +++ b/src/lib/tls/tls_alert.h @@ -0,0 +1,113 @@ +/* +* Alert Message +* (C) 2004-2006,2011,2012 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: + /** + * Type codes for TLS alerts + */ + 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, + + // pseudo alert values + NULL_ALERT = 256, + HEARTBEAT_PAYLOAD = 257 + }; + + /** + * @return true iff this alert is non-empty + */ + bool is_valid() const { return (m_type_code != NULL_ALERT); } + + /** + * @return if this alert is a fatal one or not + */ + bool is_fatal() const { return m_fatal; } + + /** + * @return type of alert + */ + Type type() const { return m_type_code; } + + /** + * @return type of alert + */ + std::string type_string() const; + + /** + * Serialize an alert + */ + std::vector<byte> serialize() const; + + /** + * Deserialize an Alert message + * @param buf the serialized alert + */ + Alert(const secure_vector<byte>& buf); + + /** + * Create a new Alert + * @param type_code the type of alert + * @param fatal specifies if this is a fatal alert + */ + Alert(Type type_code, bool fatal = false) : + m_fatal(fatal), m_type_code(type_code) {} + + Alert() : m_fatal(false), m_type_code(NULL_ALERT) {} + private: + bool m_fatal; + Type m_type_code; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_blocking.cpp b/src/lib/tls/tls_blocking.cpp new file mode 100644 index 000000000..4b33ba926 --- /dev/null +++ b/src/lib/tls/tls_blocking.cpp @@ -0,0 +1,90 @@ +/* +* TLS Blocking API +* (C) 2013 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/tls_blocking.h> + +namespace Botan { + +namespace TLS { + +using namespace std::placeholders; + +Blocking_Client::Blocking_Client(std::function<size_t (byte[], size_t)> read_fn, + std::function<void (const byte[], size_t)> write_fn, + Session_Manager& session_manager, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng, + const Server_Information& server_info, + const Protocol_Version offer_version, + std::function<std::string (std::vector<std::string>)> next_protocol) : + m_read_fn(read_fn), + m_channel(write_fn, + std::bind(&Blocking_Client::data_cb, this, _1, _2), + std::bind(&Blocking_Client::alert_cb, this, _1, _2, _3), + std::bind(&Blocking_Client::handshake_cb, this, _1), + session_manager, + creds, + policy, + rng, + server_info, + offer_version, + next_protocol) + { + } + +bool Blocking_Client::handshake_cb(const Session& session) + { + return this->handshake_complete(session); + } + +void Blocking_Client::alert_cb(const Alert alert, const byte[], size_t) + { + this->alert_notification(alert); + } + +void Blocking_Client::data_cb(const byte data[], size_t data_len) + { + m_plaintext.insert(m_plaintext.end(), data, data + data_len); + } + +void Blocking_Client::do_handshake() + { + std::vector<byte> readbuf(4096); + + while(!m_channel.is_closed() && !m_channel.is_active()) + { + const size_t from_socket = m_read_fn(&readbuf[0], readbuf.size()); + m_channel.received_data(&readbuf[0], from_socket); + } + } + +size_t Blocking_Client::read(byte buf[], size_t buf_len) + { + std::vector<byte> readbuf(4096); + + while(m_plaintext.empty() && !m_channel.is_closed()) + { + const size_t from_socket = m_read_fn(&readbuf[0], readbuf.size()); + m_channel.received_data(&readbuf[0], from_socket); + } + + const size_t returned = std::min(buf_len, m_plaintext.size()); + + for(size_t i = 0; i != returned; ++i) + buf[i] = m_plaintext[i]; + m_plaintext.erase(m_plaintext.begin(), m_plaintext.begin() + returned); + + BOTAN_ASSERT_IMPLICATION(returned == 0, m_channel.is_closed(), + "Only return zero if channel is closed"); + + return returned; + } + +} + +} diff --git a/src/lib/tls/tls_blocking.h b/src/lib/tls/tls_blocking.h new file mode 100644 index 000000000..cfa96ce8d --- /dev/null +++ b/src/lib/tls/tls_blocking.h @@ -0,0 +1,97 @@ +/* +* TLS Blocking API +* (C) 2013 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#ifndef BOTAN_TLS_BLOCKING_CHANNELS_H__ +#define BOTAN_TLS_BLOCKING_CHANNELS_H__ + +#include <botan/tls_client.h> +#include <botan/tls_server.h> +#include <deque> + +namespace Botan { + +template<typename T> using secure_deque = std::vector<T, secure_allocator<T>>; + +namespace TLS { + +/** +* Blocking TLS Client +*/ +class BOTAN_DLL Blocking_Client + { + public: + + Blocking_Client(std::function<size_t (byte[], size_t)> read_fn, + std::function<void (const byte[], size_t)> write_fn, + Session_Manager& session_manager, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng, + const Server_Information& server_info = Server_Information(), + const Protocol_Version offer_version = Protocol_Version::latest_tls_version(), + std::function<std::string (std::vector<std::string>)> next_protocol = + std::function<std::string (std::vector<std::string>)>()); + + /** + * Completes full handshake then returns + */ + void do_handshake(); + + /** + * Number of bytes pending read in the plaintext buffer (bytes + * readable without blocking) + */ + size_t pending() const { return m_plaintext.size(); } + + /** + * Blocking read, will return at least 1 byte or 0 on connection close + */ + size_t read(byte buf[], size_t buf_len); + + void write(const byte buf[], size_t buf_len) { m_channel.send(buf, buf_len); } + + const TLS::Channel& underlying_channel() const { return m_channel; } + TLS::Channel& underlying_channel() { return m_channel; } + + void close() { m_channel.close(); } + + bool is_closed() const { return m_channel.is_closed(); } + + std::vector<X509_Certificate> peer_cert_chain() const + { return m_channel.peer_cert_chain(); } + + virtual ~Blocking_Client() {} + + protected: + /** + * Can override to get the handshake complete notification + */ + virtual bool handshake_complete(const Session&) { return true; } + + /** + * Can override to get notification of alerts + */ + virtual void alert_notification(const Alert&) {} + + private: + + bool handshake_cb(const Session&); + + void data_cb(const byte data[], size_t data_len); + + void alert_cb(const Alert alert, const byte data[], size_t data_len); + + std::function<size_t (byte[], size_t)> m_read_fn; + TLS::Client m_channel; + secure_deque<byte> m_plaintext; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_channel.cpp b/src/lib/tls/tls_channel.cpp new file mode 100644 index 000000000..8ed876b3f --- /dev/null +++ b/src/lib/tls/tls_channel.cpp @@ -0,0 +1,668 @@ +/* +* 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/tls_record.h> +#include <botan/internal/tls_seq_numbers.h> +#include <botan/internal/rounding.h> +#include <botan/internal/stl_util.h> +#include <botan/loadstor.h> + +namespace Botan { + +namespace TLS { + +Channel::Channel(std::function<void (const byte[], size_t)> output_fn, + std::function<void (const byte[], size_t)> data_cb, + std::function<void (Alert, const byte[], size_t)> alert_cb, + std::function<bool (const Session&)> handshake_cb, + Session_Manager& session_manager, + RandomNumberGenerator& rng, + size_t reserved_io_buffer_size) : + m_handshake_cb(handshake_cb), + m_data_cb(data_cb), + m_alert_cb(alert_cb), + m_output_fn(output_fn), + m_rng(rng), + m_session_manager(session_manager) + { + m_writebuf.reserve(reserved_io_buffer_size); + m_readbuf.reserve(reserved_io_buffer_size); + } + +void Channel::reset_state() + { + m_active_state.reset(); + m_pending_state.reset(); + m_readbuf.clear(); + m_write_cipher_states.clear(); + m_read_cipher_states.clear(); + } + +Channel::~Channel() + { + // So unique_ptr destructors run correctly + } + +Connection_Sequence_Numbers& Channel::sequence_numbers() const + { + BOTAN_ASSERT(m_sequence_numbers, "Have a sequence numbers object"); + return *m_sequence_numbers; + } + +std::shared_ptr<Connection_Cipher_State> Channel::read_cipher_state_epoch(u16bit epoch) const + { + auto i = m_read_cipher_states.find(epoch); + + BOTAN_ASSERT(i != m_read_cipher_states.end(), + "Have a cipher state for the specified epoch"); + + return i->second; + } + +std::shared_ptr<Connection_Cipher_State> Channel::write_cipher_state_epoch(u16bit epoch) const + { + auto i = m_write_cipher_states.find(epoch); + + BOTAN_ASSERT(i != m_write_cipher_states.end(), + "Have a cipher state for the specified epoch"); + + return i->second; + } + +std::vector<X509_Certificate> Channel::peer_cert_chain() const + { + if(auto active = active_state()) + return get_peer_cert_chain(*active); + return std::vector<X509_Certificate>(); + } + +Handshake_State& Channel::create_handshake_state(Protocol_Version version) + { + if(pending_state()) + throw Internal_Error("create_handshake_state called during handshake"); + + if(auto active = active_state()) + { + Protocol_Version active_version = active->version(); + + if(active_version.is_datagram_protocol() != version.is_datagram_protocol()) + throw std::runtime_error("Active state using version " + + active_version.to_string() + + " cannot change to " + + version.to_string() + + " in pending"); + } + + if(!m_sequence_numbers) + { + if(version.is_datagram_protocol()) + m_sequence_numbers.reset(new Datagram_Sequence_Numbers); + else + m_sequence_numbers.reset(new Stream_Sequence_Numbers); + } + + std::unique_ptr<Handshake_IO> io; + if(version.is_datagram_protocol()) + io.reset(new Datagram_Handshake_IO( + sequence_numbers(), + std::bind(&Channel::send_record_under_epoch, this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3))); + else + io.reset(new Stream_Handshake_IO( + std::bind(&Channel::send_record, this, + std::placeholders::_1, + std::placeholders::_2))); + + m_pending_state.reset(new_handshake_state(io.release())); + + if(auto active = active_state()) + m_pending_state->set_version(active->version()); + + return *m_pending_state.get(); + } + +void Channel::renegotiate(bool force_full_renegotiation) + { + if(pending_state()) // currently in handshake? + return; + + if(auto active = active_state()) + initiate_handshake(create_handshake_state(active->version()), + force_full_renegotiation); + else + throw std::runtime_error("Cannot renegotiate on inactive connection"); + } + +size_t Channel::maximum_fragment_size() const + { + // should we be caching this value? + + if(auto pending = pending_state()) + if(auto server_hello = pending->server_hello()) + if(size_t frag = server_hello->fragment_size()) + return frag; + + if(auto active = active_state()) + if(size_t frag = active->server_hello()->fragment_size()) + return frag; + + return MAX_PLAINTEXT_SIZE; + } + +void Channel::change_cipher_spec_reader(Connection_Side side) + { + auto pending = pending_state(); + + BOTAN_ASSERT(pending && pending->server_hello(), + "Have received server hello"); + + if(pending->server_hello()->compression_method() != NO_COMPRESSION) + throw Internal_Error("Negotiated unknown compression algorithm"); + + sequence_numbers().new_read_cipher_state(); + + const u16bit epoch = sequence_numbers().current_read_epoch(); + + BOTAN_ASSERT(m_read_cipher_states.count(epoch) == 0, + "No read cipher state currently set for next epoch"); + + // flip side as we are reading + std::shared_ptr<Connection_Cipher_State> read_state( + new Connection_Cipher_State(pending->version(), + (side == CLIENT) ? SERVER : CLIENT, + false, + pending->ciphersuite(), + pending->session_keys())); + + m_read_cipher_states[epoch] = read_state; + } + +void Channel::change_cipher_spec_writer(Connection_Side side) + { + auto pending = pending_state(); + + BOTAN_ASSERT(pending && pending->server_hello(), + "Have received server hello"); + + if(pending->server_hello()->compression_method() != NO_COMPRESSION) + throw Internal_Error("Negotiated unknown compression algorithm"); + + sequence_numbers().new_write_cipher_state(); + + const u16bit epoch = sequence_numbers().current_write_epoch(); + + BOTAN_ASSERT(m_write_cipher_states.count(epoch) == 0, + "No write cipher state currently set for next epoch"); + + std::shared_ptr<Connection_Cipher_State> write_state( + new Connection_Cipher_State(pending->version(), + side, + true, + pending->ciphersuite(), + pending->session_keys())); + + m_write_cipher_states[epoch] = write_state; + } + +bool Channel::is_active() const + { + return (active_state() != nullptr); + } + +bool Channel::is_closed() const + { + if(active_state() || pending_state()) + return false; + + /* + * If no active or pending state, then either we had a connection + * and it has been closed, or we are a server which has never + * received a connection. This case is detectable by also lacking + * m_sequence_numbers + */ + return (m_sequence_numbers != nullptr); + } + +void Channel::activate_session() + { + std::swap(m_active_state, m_pending_state); + m_pending_state.reset(); + + if(m_active_state->version().is_datagram_protocol()) + { + // FIXME, remove old states when we are sure not needed anymore + } + else + { + // TLS is easy just remove all but the current state + auto current_epoch = sequence_numbers().current_write_epoch(); + + const auto not_current_epoch = + [current_epoch](u16bit epoch) { return (epoch != current_epoch); }; + + map_remove_if(not_current_epoch, m_write_cipher_states); + map_remove_if(not_current_epoch, m_read_cipher_states); + } + } + +bool Channel::peer_supports_heartbeats() const + { + if(auto active = active_state()) + return active->server_hello()->supports_heartbeats(); + return false; + } + +bool Channel::heartbeat_sending_allowed() const + { + if(auto active = active_state()) + return active->server_hello()->peer_can_send_heartbeats(); + return false; + } + +size_t Channel::received_data(const std::vector<byte>& buf) + { + return this->received_data(&buf[0], buf.size()); + } + +size_t Channel::received_data(const byte input[], size_t input_size) + { + const auto get_cipherstate = [this](u16bit epoch) + { return this->read_cipher_state_epoch(epoch).get(); }; + + const size_t max_fragment_size = maximum_fragment_size(); + + try + { + while(!is_closed() && input_size) + { + secure_vector<byte> record; + u64bit record_sequence = 0; + Record_Type record_type = NO_RECORD; + Protocol_Version record_version; + + size_t consumed = 0; + + const size_t needed = + read_record(m_readbuf, + input, + input_size, + consumed, + record, + &record_sequence, + &record_version, + &record_type, + m_sequence_numbers.get(), + get_cipherstate); + + BOTAN_ASSERT(consumed <= input_size, + "Record reader consumed sane amount"); + + input += consumed; + input_size -= consumed; + + BOTAN_ASSERT(input_size == 0 || needed == 0, + "Got a full record or consumed all input"); + + if(input_size == 0 && needed != 0) + return needed; // need more data to complete record + + if(record.size() > max_fragment_size) + throw TLS_Exception(Alert::RECORD_OVERFLOW, + "Plaintext record is too large"); + + if(record_type == HANDSHAKE || record_type == CHANGE_CIPHER_SPEC) + { + if(!m_pending_state) + { + create_handshake_state(record_version); + if(record_version.is_datagram_protocol()) + sequence_numbers().read_accept(record_sequence); + } + + m_pending_state->handshake_io().add_record(unlock(record), + record_type, + record_sequence); + + while(auto pending = m_pending_state.get()) + { + auto msg = pending->get_next_handshake_msg(); + + if(msg.first == HANDSHAKE_NONE) // no full handshake yet + break; + + process_handshake_msg(active_state(), *pending, + msg.first, msg.second); + } + } + else if(record_type == HEARTBEAT && peer_supports_heartbeats()) + { + if(!active_state()) + throw Unexpected_Message("Heartbeat sent before handshake done"); + + Heartbeat_Message heartbeat(unlock(record)); + + const std::vector<byte>& payload = heartbeat.payload(); + + if(heartbeat.is_request()) + { + if(!pending_state()) + { + Heartbeat_Message response(Heartbeat_Message::RESPONSE, + &payload[0], payload.size()); + + send_record(HEARTBEAT, response.contents()); + } + } + else + { + m_alert_cb(Alert(Alert::HEARTBEAT_PAYLOAD), &payload[0], payload.size()); + } + } + else if(record_type == APPLICATION_DATA) + { + if(!active_state()) + throw Unexpected_Message("Application data before handshake done"); + + /* + * 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) + m_data_cb(&record[0], record.size()); + } + else if(record_type == ALERT) + { + Alert alert_msg(record); + + if(alert_msg.type() == Alert::NO_RENEGOTIATION) + m_pending_state.reset(); + + m_alert_cb(alert_msg, nullptr, 0); + + if(alert_msg.is_fatal()) + { + if(auto active = active_state()) + m_session_manager.remove_entry(active->server_hello()->session_id()); + } + + if(alert_msg.type() == Alert::CLOSE_NOTIFY) + send_warning_alert(Alert::CLOSE_NOTIFY); // reply in kind + + if(alert_msg.type() == Alert::CLOSE_NOTIFY || alert_msg.is_fatal()) + { + reset_state(); + return 0; + } + } + else + throw Unexpected_Message("Unexpected record type " + + std::to_string(record_type) + + " from counterparty"); + } + + return 0; // on a record boundary + } + catch(TLS_Exception& e) + { + send_fatal_alert(e.type()); + throw; + } + catch(Integrity_Failure& e) + { + send_fatal_alert(Alert::BAD_RECORD_MAC); + throw; + } + catch(Decoding_Error& e) + { + send_fatal_alert(Alert::DECODE_ERROR); + throw; + } + catch(...) + { + send_fatal_alert(Alert::INTERNAL_ERROR); + throw; + } + } + +void Channel::heartbeat(const byte payload[], size_t payload_size) + { + if(heartbeat_sending_allowed()) + { + Heartbeat_Message heartbeat(Heartbeat_Message::REQUEST, + payload, payload_size); + + send_record(HEARTBEAT, heartbeat.contents()); + } + } + +void Channel::write_record(Connection_Cipher_State* cipher_state, + byte record_type, const byte input[], size_t length) + { + BOTAN_ASSERT(m_pending_state || m_active_state, + "Some connection state exists"); + + Protocol_Version record_version = + (m_pending_state) ? (m_pending_state->version()) : (m_active_state->version()); + + TLS::write_record(m_writebuf, + record_type, + input, + length, + record_version, + sequence_numbers().next_write_sequence(), + cipher_state, + m_rng); + + m_output_fn(&m_writebuf[0], m_writebuf.size()); + } + +void Channel::send_record_array(u16bit epoch, byte type, const byte input[], size_t length) + { + if(length == 0) + return; + + /* + * If using CBC mode without an explicit IV (SSL v3 or 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 + * or higher, 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. + */ + + auto cipher_state = write_cipher_state_epoch(epoch); + + if(type == APPLICATION_DATA && cipher_state->cbc_without_explicit_iv()) + { + write_record(cipher_state.get(), type, &input[0], 1); + input += 1; + length -= 1; + } + + const size_t max_fragment_size = maximum_fragment_size(); + + while(length) + { + const size_t sending = std::min(length, max_fragment_size); + write_record(cipher_state.get(), type, &input[0], sending); + + input += sending; + length -= sending; + } + } + +void Channel::send_record(byte record_type, const std::vector<byte>& record) + { + send_record_array(sequence_numbers().current_write_epoch(), + record_type, &record[0], record.size()); + } + +void Channel::send_record_under_epoch(u16bit epoch, byte record_type, + const std::vector<byte>& record) + { + send_record_array(epoch, record_type, &record[0], record.size()); + } + +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"); + + send_record_array(sequence_numbers().current_write_epoch(), + APPLICATION_DATA, buf, buf_size); + } + +void Channel::send(const std::string& string) + { + this->send(reinterpret_cast<const byte*>(string.c_str()), string.size()); + } + +void Channel::send_alert(const Alert& alert) + { + if(alert.is_valid() && !is_closed()) + { + try + { + send_record(ALERT, alert.serialize()); + } + catch(...) { /* swallow it */ } + } + + if(alert.type() == Alert::NO_RENEGOTIATION) + m_pending_state.reset(); + + if(alert.is_fatal()) + if(auto active = active_state()) + m_session_manager.remove_entry(active->server_hello()->session_id()); + + if(alert.type() == Alert::CLOSE_NOTIFY || alert.is_fatal()) + reset_state(); + } + +void Channel::secure_renegotiation_check(const Client_Hello* client_hello) + { + const bool secure_renegotiation = client_hello->secure_renegotiation(); + + if(auto active = active_state()) + { + const bool active_sr = active->client_hello()->secure_renegotiation(); + + if(active_sr != secure_renegotiation) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Client changed its mind about secure renegotiation"); + } + + if(secure_renegotiation) + { + const std::vector<byte>& data = client_hello->renegotiation_info(); + + if(data != secure_renegotiation_data_for_client_hello()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Client sent bad values for secure renegotiation"); + } + } + +void Channel::secure_renegotiation_check(const Server_Hello* server_hello) + { + const bool secure_renegotiation = server_hello->secure_renegotiation(); + + if(auto active = active_state()) + { + const bool active_sr = active->client_hello()->secure_renegotiation(); + + if(active_sr != secure_renegotiation) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server changed its mind about secure renegotiation"); + } + + if(secure_renegotiation) + { + const std::vector<byte>& data = server_hello->renegotiation_info(); + + if(data != secure_renegotiation_data_for_server_hello()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server sent bad values for secure renegotiation"); + } + } + +std::vector<byte> Channel::secure_renegotiation_data_for_client_hello() const + { + if(auto active = active_state()) + return active->client_finished()->verify_data(); + return std::vector<byte>(); + } + +std::vector<byte> Channel::secure_renegotiation_data_for_server_hello() const + { + if(auto active = active_state()) + { + std::vector<byte> buf = active->client_finished()->verify_data(); + buf += active->server_finished()->verify_data(); + return buf; + } + + return std::vector<byte>(); + } + +bool Channel::secure_renegotiation_supported() const + { + if(auto active = active_state()) + return active->server_hello()->secure_renegotiation(); + + if(auto pending = pending_state()) + if(auto hello = pending->server_hello()) + return hello->secure_renegotiation(); + + return false; + } + +SymmetricKey Channel::key_material_export(const std::string& label, + const std::string& context, + size_t length) const + { + if(auto active = active_state()) + { + std::unique_ptr<KDF> prf(active->protocol_specific_prf()); + + const secure_vector<byte>& master_secret = + active->session_keys().master_secret(); + + std::vector<byte> salt; + salt += to_byte_vector(label); + salt += active->client_hello()->random(); + salt += active->server_hello()->random(); + + if(context != "") + { + size_t context_size = context.length(); + if(context_size > 0xFFFF) + throw std::runtime_error("key_material_export context is too long"); + salt.push_back(get_byte<u16bit>(0, context_size)); + salt.push_back(get_byte<u16bit>(1, context_size)); + salt += to_byte_vector(context); + } + + return prf->derive_key(length, master_secret, salt); + } + else + throw std::runtime_error("Channel::key_material_export connection not active"); + } + +} + +} + diff --git a/src/lib/tls/tls_channel.h b/src/lib/tls/tls_channel.h new file mode 100644 index 000000000..e7b53f563 --- /dev/null +++ b/src/lib/tls/tls_channel.h @@ -0,0 +1,259 @@ +/* +* TLS Channel +* (C) 2011,2012 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_session.h> +#include <botan/tls_alert.h> +#include <botan/tls_session_manager.h> +#include <botan/x509cert.h> +#include <vector> +#include <string> +#include <memory> +#include <map> + +namespace Botan { + +namespace TLS { + +class Connection_Cipher_State; +class Connection_Sequence_Numbers; +class Handshake_State; + +/** +* 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) + */ + size_t received_data(const byte buf[], size_t buf_size); + + /** + * 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) + */ + size_t received_data(const std::vector<byte>& buf); + + /** + * Inject plaintext intended for counterparty + */ + void send(const byte buf[], size_t buf_size); + + /** + * Inject plaintext intended for counterparty + */ + void send(const std::string& val); + + /** + * Inject plaintext intended for counterparty + */ + template<typename Alloc> + void send(const std::vector<unsigned char, Alloc>& val) + { + send(&val[0], val.size()); + } + + /** + * Send a TLS alert message. If the alert is fatal, the internal + * state (keys, etc) will be reset. + * @param alert the Alert to send + */ + void send_alert(const Alert& alert); + + /** + * Send a warning alert + */ + void send_warning_alert(Alert::Type type) { send_alert(Alert(type, false)); } + + /** + * Send a fatal alert + */ + void send_fatal_alert(Alert::Type type) { send_alert(Alert(type, true)); } + + /** + * Send a close notification alert + */ + void close() { send_warning_alert(Alert::CLOSE_NOTIFY); } + + /** + * @return true iff the connection is active for sending application data + */ + bool is_active() const; + + /** + * @return true iff the connection has been definitely closed + */ + bool is_closed() const; + + /** + * Attempt to renegotiate the session + * @param force_full_renegotiation if true, require a full renegotiation, + * otherwise allow session resumption + */ + void renegotiate(bool force_full_renegotiation = false); + + /** + * @return true iff the peer supports heartbeat messages + */ + bool peer_supports_heartbeats() const; + + /** + * @return true iff we are allowed to send heartbeat messages + */ + bool heartbeat_sending_allowed() const; + + /** + * @return true iff the counterparty supports the secure + * renegotiation extensions. + */ + bool secure_renegotiation_supported() const; + + /** + * Attempt to send a heartbeat message (if negotiated with counterparty) + * @param payload will be echoed back + * @param payload_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(nullptr, 0); } + + /** + * @return certificate chain of the peer (may be empty) + */ + std::vector<X509_Certificate> peer_cert_chain() const; + + /** + * Key material export (RFC 5705) + * @param label a disambiguating label string + * @param context a per-association context value + * @param length the length of the desired key in bytes + * @return key of length bytes + */ + SymmetricKey key_material_export(const std::string& label, + const std::string& context, + size_t length) const; + + Channel(std::function<void (const byte[], size_t)> socket_output_fn, + std::function<void (const byte[], size_t)> data_cb, + std::function<void (Alert, const byte[], size_t)> alert_cb, + std::function<bool (const Session&)> handshake_cb, + Session_Manager& session_manager, + RandomNumberGenerator& rng, + size_t reserved_io_buffer_size); + + Channel(const Channel&) = delete; + + Channel& operator=(const Channel&) = delete; + + virtual ~Channel(); + protected: + + virtual void process_handshake_msg(const Handshake_State* active_state, + Handshake_State& pending_state, + Handshake_Type type, + const std::vector<byte>& contents) = 0; + + virtual void initiate_handshake(Handshake_State& state, + bool force_full_renegotiation) = 0; + + virtual std::vector<X509_Certificate> + get_peer_cert_chain(const Handshake_State& state) const = 0; + + virtual Handshake_State* new_handshake_state(class Handshake_IO* io) = 0; + + Handshake_State& create_handshake_state(Protocol_Version version); + + void activate_session(); + + void change_cipher_spec_reader(Connection_Side side); + + void change_cipher_spec_writer(Connection_Side side); + + /* secure renegotiation handling */ + + void secure_renegotiation_check(const class Client_Hello* client_hello); + void secure_renegotiation_check(const class Server_Hello* server_hello); + + std::vector<byte> secure_renegotiation_data_for_client_hello() const; + std::vector<byte> secure_renegotiation_data_for_server_hello() const; + + RandomNumberGenerator& rng() { return m_rng; } + + Session_Manager& session_manager() { return m_session_manager; } + + bool save_session(const Session& session) const { return m_handshake_cb(session); } + + private: + size_t maximum_fragment_size() const; + + void send_record(byte record_type, const std::vector<byte>& record); + + void send_record_under_epoch(u16bit epoch, byte record_type, + const std::vector<byte>& record); + + void send_record_array(u16bit epoch, byte record_type, + const byte input[], size_t length); + + void write_record(Connection_Cipher_State* cipher_state, + byte type, const byte input[], size_t length); + + Connection_Sequence_Numbers& sequence_numbers() const; + + std::shared_ptr<Connection_Cipher_State> read_cipher_state_epoch(u16bit epoch) const; + + std::shared_ptr<Connection_Cipher_State> write_cipher_state_epoch(u16bit epoch) const; + + void reset_state(); + + const Handshake_State* active_state() const { return m_active_state.get(); } + + const Handshake_State* pending_state() const { return m_pending_state.get(); } + + /* callbacks */ + std::function<bool (const Session&)> m_handshake_cb; + std::function<void (const byte[], size_t)> m_data_cb; + std::function<void (Alert, const byte[], size_t)> m_alert_cb; + std::function<void (const byte[], size_t)> m_output_fn; + + /* external state */ + RandomNumberGenerator& m_rng; + Session_Manager& m_session_manager; + + /* sequence number state */ + std::unique_ptr<Connection_Sequence_Numbers> m_sequence_numbers; + + /* pending and active connection states */ + std::unique_ptr<Handshake_State> m_active_state; + std::unique_ptr<Handshake_State> m_pending_state; + + /* cipher states for each epoch - epoch 0 is plaintext, thus null cipher state */ + std::map<u16bit, std::shared_ptr<Connection_Cipher_State>> m_write_cipher_states = + { { 0, nullptr } }; + std::map<u16bit, std::shared_ptr<Connection_Cipher_State>> m_read_cipher_states = + { { 0, nullptr } }; + + /* I/O buffers */ + secure_vector<byte> m_writebuf; + secure_vector<byte> m_readbuf; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_ciphersuite.cpp b/src/lib/tls/tls_ciphersuite.cpp new file mode 100644 index 000000000..e8c551b01 --- /dev/null +++ b/src/lib/tls/tls_ciphersuite.cpp @@ -0,0 +1,236 @@ +/* +* TLS Cipher Suite +* (C) 2004-2010,2012,2013 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/tls_ciphersuite.h> +#include <botan/libstate.h> +#include <botan/parsing.h> +#include <sstream> +#include <stdexcept> + +namespace Botan { + +namespace TLS { + +namespace { + +/* +* This way all work happens at the constuctor call, and we can +* rely on that happening only once in C++11. +*/ +std::vector<Ciphersuite> gather_known_ciphersuites() + { + std::vector<Ciphersuite> ciphersuites; + + for(size_t i = 0; i <= 0xFFFF; ++i) + { + Ciphersuite suite = Ciphersuite::by_id(i); + + if(suite.valid()) + ciphersuites.push_back(suite); + } + + return ciphersuites; + } + +} + +const std::vector<Ciphersuite>& Ciphersuite::all_known_ciphersuites() + { + static std::vector<Ciphersuite> all_ciphersuites(gather_known_ciphersuites()); + return all_ciphersuites; + } + +Ciphersuite Ciphersuite::by_name(const std::string& name) + { + for(auto suite : all_known_ciphersuites()) + { + if(suite.to_string() == name) + return suite; + } + + return Ciphersuite(); // some unknown ciphersuite + } + +Ciphersuite::Ciphersuite(u16bit ciphersuite_code, + const char* sig_algo, + const char* kex_algo, + const char* cipher_algo, + size_t cipher_keylen, + size_t cipher_ivlen, + const char* mac_algo, + size_t mac_keylen, + const char* prf_algo) : + m_ciphersuite_code(ciphersuite_code), + m_sig_algo(sig_algo), + m_kex_algo(kex_algo), + m_cipher_algo(cipher_algo), + m_mac_algo(mac_algo), + m_prf_algo(prf_algo), + m_cipher_keylen(cipher_keylen), + m_cipher_ivlen(cipher_ivlen), + m_mac_keylen(mac_keylen) + { + } + +bool Ciphersuite::psk_ciphersuite() const + { + return (kex_algo() == "PSK" || + kex_algo() == "DHE_PSK" || + kex_algo() == "ECDHE_PSK"); + } + +bool Ciphersuite::ecc_ciphersuite() const + { + return (sig_algo() == "ECDSA" || kex_algo() == "ECDH" || kex_algo() == "ECDHE_PSK"); + } + +bool Ciphersuite::valid() const + { + if(!m_cipher_keylen) // uninitialized object + return false; + + Algorithm_Factory& af = global_state().algorithm_factory(); + + if(!af.prototype_hash_function(prf_algo())) + return false; + + if(mac_algo() == "AEAD") + { + auto cipher_and_mode = split_on(cipher_algo(), '/'); + BOTAN_ASSERT(cipher_and_mode.size() == 2, "Expected format for AEAD algo"); + if(!af.prototype_block_cipher(cipher_and_mode[0])) + return false; + + const auto mode = cipher_and_mode[1]; + +#if !defined(BOTAN_HAS_AEAD_CCM) + if(mode == "CCM" || mode == "CCM-8") + return false; +#endif + +#if !defined(BOTAN_HAS_AEAD_GCM) + if(mode == "GCM") + return false; +#endif + +#if !defined(BOTAN_HAS_AEAD_OCB) + if(mode == "OCB") + return false; +#endif + } + else + { + if(!af.prototype_block_cipher(cipher_algo()) && + !af.prototype_stream_cipher(cipher_algo())) + return false; + + if(!af.prototype_hash_function(mac_algo())) + return false; + } + + if(kex_algo() == "SRP_SHA") + { +#if !defined(BOTAN_HAS_SRP6) + return false; +#endif + } + else if(kex_algo() == "ECDH" || kex_algo() == "ECDHE_PSK") + { +#if !defined(BOTAN_HAS_ECDH) + return false; +#endif + } + else if(kex_algo() == "DH" || kex_algo() == "DHE_PSK") + { +#if !defined(BOTAN_HAS_DIFFIE_HELLMAN) + return false; +#endif + } + + if(sig_algo() == "DSA") + { +#if !defined(BOTAN_HAS_DSA) + return false; +#endif + } + else if(sig_algo() == "ECDSA") + { +#if !defined(BOTAN_HAS_ECDSA) + return false; +#endif + } + else if(sig_algo() == "RSA") + { +#if !defined(BOTAN_HAS_RSA) + return false; +#endif + } + + return true; + } + +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() == "RC4") + { + out << "RC4_128_"; + } + else + { + if(cipher_algo() == "3DES") + out << "3DES_EDE"; + else if(cipher_algo().find("Camellia") == 0) + out << "CAMELLIA_" << std::to_string(8*cipher_keylen()); + else + out << replace_chars(cipher_algo(), {'-', '/'}, '_'); + + if(cipher_algo().find("/") != std::string::npos) + out << "_"; // some explicit mode already included + else + out << "_CBC_"; + } + + if(mac_algo() == "SHA-1") + out << "SHA"; + else if(mac_algo() == "AEAD") + out << erase_chars(prf_algo(), {'-'}); + else + out << erase_chars(mac_algo(), {'-'}); + + return out.str(); + } + +} + +} + diff --git a/src/lib/tls/tls_ciphersuite.h b/src/lib/tls/tls_ciphersuite.h new file mode 100644 index 000000000..865e66abb --- /dev/null +++ b/src/lib/tls/tls_ciphersuite.h @@ -0,0 +1,137 @@ +/* +* TLS Cipher Suites +* (C) 2004-2011,2012 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> +#include <vector> + +namespace Botan { + +namespace TLS { + +/** +* Ciphersuite Information +*/ +class BOTAN_DLL Ciphersuite + { + public: + /** + * Convert an SSL/TLS ciphersuite to algorithm fields + * @param suite the ciphersuite code number + * @return ciphersuite object + */ + static Ciphersuite by_id(u16bit suite); + + /** + * Lookup a ciphersuite by name + * @param name the name (eg TLS_RSA_WITH_RC4_128_SHA) + * @return ciphersuite object + */ + static Ciphersuite by_name(const std::string& name); + + /** + * Generate a static list of all known ciphersuites and return it. + * + * @return list of all known ciphersuites + */ + static const std::vector<Ciphersuite>& all_known_ciphersuites(); + + /** + * Formats the ciphersuite back to an RFC-style ciphersuite string + * @return RFC ciphersuite string identifier + */ + std::string to_string() const; + + /** + * @return ciphersuite number + */ + u16bit ciphersuite_code() const { return m_ciphersuite_code; } + + /** + * @return true if this is a PSK ciphersuite + */ + bool psk_ciphersuite() const; + + /** + * @return true if this is an ECC ciphersuite + */ + bool ecc_ciphersuite() const; + + /** + * @return key exchange algorithm used by this ciphersuite + */ + std::string kex_algo() const { return m_kex_algo; } + + /** + * @return signature algorithm used by this ciphersuite + */ + std::string sig_algo() const { return m_sig_algo; } + + /** + * @return symmetric cipher algorithm used by this ciphersuite + */ + std::string cipher_algo() const { return m_cipher_algo; } + + /** + * @return message authentication algorithm used by this ciphersuite + */ + std::string mac_algo() const { return m_mac_algo; } + + std::string prf_algo() const + { + return (m_prf_algo != "") ? m_prf_algo : m_mac_algo; + } + + /** + * @return cipher key length used by this ciphersuite + */ + size_t cipher_keylen() const { return m_cipher_keylen; } + + size_t cipher_ivlen() const { return m_cipher_ivlen; } + + size_t mac_keylen() const { return m_mac_keylen; } + + /** + * @return true if this is a valid/known ciphersuite + */ + bool valid() const; + + Ciphersuite() {} + + private: + + Ciphersuite(u16bit ciphersuite_code, + const char* sig_algo, + const char* kex_algo, + const char* cipher_algo, + size_t cipher_keylen, + size_t cipher_ivlen, + const char* mac_algo, + size_t mac_keylen, + const char* prf_algo = ""); + + u16bit m_ciphersuite_code = 0; + + std::string m_sig_algo; + std::string m_kex_algo; + std::string m_cipher_algo; + std::string m_mac_algo; + std::string m_prf_algo; + + size_t m_cipher_keylen = 0; + size_t m_cipher_ivlen = 0; + size_t m_mac_keylen = 0; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_client.cpp b/src/lib/tls/tls_client.cpp new file mode 100644 index 000000000..f17247c16 --- /dev/null +++ b/src/lib/tls/tls_client.cpp @@ -0,0 +1,530 @@ +/* +* TLS Client +* (C) 2004-2011,2012 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 { + +namespace { + +class Client_Handshake_State : public Handshake_State + { + public: + // using Handshake_State::Handshake_State; + + Client_Handshake_State(Handshake_IO* io, + std::function<void (const Handshake_Message&)> msg_callback = + std::function<void (const Handshake_Message&)>()) : + Handshake_State(io, msg_callback) {} + + const Public_Key& get_server_public_Key() const + { + BOTAN_ASSERT(server_public_key, "Server sent us a certificate"); + return *server_public_key.get(); + } + + // Used during session resumption + secure_vector<byte> resume_master_secret; + + std::unique_ptr<Public_Key> server_public_key; + + // Used by client using NPN + std::function<std::string (std::vector<std::string>)> client_npn_cb; + }; + +} + +/* +* TLS Client Constructor +*/ +Client::Client(std::function<void (const byte[], size_t)> output_fn, + std::function<void (const byte[], size_t)> proc_cb, + std::function<void (Alert, const byte[], size_t)> alert_cb, + std::function<bool (const Session&)> handshake_cb, + Session_Manager& session_manager, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng, + const Server_Information& info, + const Protocol_Version offer_version, + std::function<std::string (std::vector<std::string>)> next_protocol, + size_t io_buf_sz) : + Channel(output_fn, proc_cb, alert_cb, handshake_cb, session_manager, rng, io_buf_sz), + m_policy(policy), + m_creds(creds), + m_info(info) + { + const std::string srp_identifier = m_creds.srp_identifier("tls-client", m_info.hostname()); + + Handshake_State& state = create_handshake_state(offer_version); + send_client_hello(state, false, offer_version, srp_identifier, next_protocol); + } + +Handshake_State* Client::new_handshake_state(Handshake_IO* io) + { + return new Client_Handshake_State(io); + } + +std::vector<X509_Certificate> +Client::get_peer_cert_chain(const Handshake_State& state) const + { + if(state.server_certs()) + return state.server_certs()->cert_chain(); + return std::vector<X509_Certificate>(); + } + +/* +* Send a new client hello to renegotiate +*/ +void Client::initiate_handshake(Handshake_State& state, + bool force_full_renegotiation) + { + send_client_hello(state, + force_full_renegotiation, + state.version()); + } + +void Client::send_client_hello(Handshake_State& state_base, + bool force_full_renegotiation, + Protocol_Version version, + const std::string& srp_identifier, + std::function<std::string (std::vector<std::string>)> next_protocol) + { + Client_Handshake_State& state = dynamic_cast<Client_Handshake_State&>(state_base); + + if(state.version().is_datagram_protocol()) + state.set_expected_next(HELLO_VERIFY_REQUEST); // optional + state.set_expected_next(SERVER_HELLO); + + state.client_npn_cb = next_protocol; + + const bool send_npn_request = static_cast<bool>(next_protocol); + + if(!force_full_renegotiation && !m_info.empty()) + { + Session session_info; + if(session_manager().load_from_server_info(m_info, session_info)) + { + if(srp_identifier == "" || session_info.srp_identifier() == srp_identifier) + { + state.client_hello(new Client_Hello( + state.handshake_io(), + state.hash(), + m_policy, + rng(), + secure_renegotiation_data_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( + state.handshake_io(), + state.hash(), + version, + m_policy, + rng(), + secure_renegotiation_data_for_client_hello(), + send_npn_request, + m_info.hostname(), + srp_identifier)); + } + + secure_renegotiation_check(state.client_hello()); + } + +/* +* Process a handshake message +*/ +void Client::process_handshake_msg(const Handshake_State* active_state, + Handshake_State& state_base, + Handshake_Type type, + const std::vector<byte>& contents) + { + Client_Handshake_State& state = dynamic_cast<Client_Handshake_State&>(state_base); + + if(type == HELLO_REQUEST && active_state) + { + Hello_Request hello_request(contents); + + // Ignore request entirely if we are currently negotiating a handshake + if(state.client_hello()) + return; + + if(!m_policy.allow_server_initiated_renegotiation() || + (!m_policy.allow_insecure_renegotiation() && !secure_renegotiation_supported())) + { + // RFC 5746 section 4.2 + send_warning_alert(Alert::NO_RENEGOTIATION); + return; + } + + this->initiate_handshake(state, false); + + return; + } + + state.confirm_transition_to(type); + + if(type != HANDSHAKE_CCS && type != FINISHED && type != HELLO_VERIFY_REQUEST) + state.hash().update(state.handshake_io().format(contents, type)); + + if(type == HELLO_VERIFY_REQUEST) + { + state.set_expected_next(SERVER_HELLO); + state.set_expected_next(HELLO_VERIFY_REQUEST); // might get it again + + Hello_Verify_Request hello_verify_request(contents); + + state.hello_verify_request(hello_verify_request); + } + else 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()); + + secure_renegotiation_check(state.server_hello()); + + 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.compute_session_keys(state.resume_master_secret); + + 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.client_hello()->version().is_datagram_protocol() != + state.server_hello()->version().is_datagram_protocol()) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Server replied with different protocol type than we offered"); + } + + if(state.version() > state.client_hello()->version()) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server replied with later version than in hello"); + } + + if(!m_policy.acceptable_protocol_version(state.version())) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Server version is unacceptable by policy"); + } + + if(state.ciphersuite().sig_algo() != "") + { + state.set_expected_next(CERTIFICATE); + } + else if(state.ciphersuite().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.ciphersuite().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.ciphersuite().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)); + + const std::vector<X509_Certificate>& server_certs = + state.server_certs()->cert_chain(); + + if(server_certs.empty()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Client: No certificates sent by server"); + + try + { + m_creds.verify_certificate_chain("tls-client", m_info.hostname(), server_certs); + } + catch(std::exception& e) + { + throw TLS_Exception(Alert::BAD_CERTIFICATE, e.what()); + } + + std::unique_ptr<Public_Key> peer_key(server_certs[0].subject_public_key()); + + if(peer_key->algo_name() != state.ciphersuite().sig_algo()) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Certificate key type did not match ciphersuite"); + + state.server_public_key.reset(peer_key.release()); + } + 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.ciphersuite().kex_algo(), + state.ciphersuite().sig_algo(), + state.version()) + ); + + if(state.ciphersuite().sig_algo() != "") + { + const Public_Key& server_key = state.get_server_public_Key(); + + if(!state.server_kex()->verify(server_key, 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 = + m_creds.cert_chain(types, + "tls-client", + m_info.hostname()); + + state.client_certs( + new Certificate(state.handshake_io(), + state.hash(), + client_certs) + ); + } + + state.client_kex( + new Client_Key_Exchange(state.handshake_io(), + state, + m_policy, + m_creds, + state.server_public_key.get(), + m_info.hostname(), + rng()) + ); + + state.compute_session_keys(); + + if(state.received_handshake_msg(CERTIFICATE_REQUEST) && + !state.client_certs()->empty()) + { + Private_Key* private_key = + m_creds.private_key_for(state.client_certs()->cert_chain()[0], + "tls-client", + m_info.hostname()); + + state.client_verify( + new Certificate_Verify(state.handshake_io(), + state, + m_policy, + rng(), + private_key) + ); + } + + state.handshake_io().send(Change_Cipher_Spec()); + + change_cipher_spec_writer(CLIENT); + + 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(state.handshake_io(), state.hash(), protocol) + ); + } + + state.client_finished( + new Finished(state.handshake_io(), 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); + + change_cipher_spec_reader(CLIENT); + } + else if(type == FINISHED) + { + 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(state.handshake_io().format(contents, type)); + + if(!state.client_finished()) // session resume case + { + state.handshake_io().send(Change_Cipher_Spec()); + + change_cipher_spec_writer(CLIENT); + + 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(state.handshake_io(), state.hash(), protocol) + ); + } + + state.client_finished( + new Finished(state.handshake_io(), state, CLIENT) + ); + } + + std::vector<byte> session_id = state.server_hello()->session_id(); + + const std::vector<byte>& session_ticket = state.session_ticket(); + + if(session_id.empty() && !session_ticket.empty()) + session_id = make_hello_random(rng()); + + Session session_info( + session_id, + state.session_keys().master_secret(), + state.server_hello()->version(), + state.server_hello()->ciphersuite(), + state.server_hello()->compression_method(), + CLIENT, + state.server_hello()->fragment_size(), + get_peer_cert_chain(state), + session_ticket, + m_info, + "" + ); + + const bool should_save = save_session(session_info); + + if(!session_id.empty()) + { + if(should_save) + session_manager().save(session_info); + else + session_manager().remove_entry(session_info.session_id()); + } + + activate_session(); + } + else + throw Unexpected_Message("Unknown handshake message received"); + } + +} + +} diff --git a/src/lib/tls/tls_client.h b/src/lib/tls/tls_client.h new file mode 100644 index 000000000..c4440c7ac --- /dev/null +++ b/src/lib/tls/tls_client.h @@ -0,0 +1,106 @@ +/* +* 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/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_cb is called when new application data is received + * + * @param alert_cb is called when a TLS alert is received + * + * @param handshake_cb 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 server_info is identifying information about the TLS server + * + * @param offer_version specifies which version we will offer + * to the TLS server. + * + * @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. + * + * @param reserved_io_buffer_size This many bytes of memory will + * be preallocated for the read and write buffers. Smaller + * values just mean reallocations and copies are more likely. + */ + Client(std::function<void (const byte[], size_t)> socket_output_fn, + std::function<void (const byte[], size_t)> data_cb, + std::function<void (Alert, const byte[], size_t)> alert_cb, + std::function<bool (const Session&)> handshake_cb, + Session_Manager& session_manager, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng, + const Server_Information& server_info = Server_Information(), + const Protocol_Version offer_version = Protocol_Version::latest_tls_version(), + std::function<std::string (std::vector<std::string>)> next_protocol = + std::function<std::string (std::vector<std::string>)>(), + size_t reserved_io_buffer_size = 16*1024 + ); + private: + std::vector<X509_Certificate> + get_peer_cert_chain(const Handshake_State& state) const override; + + void initiate_handshake(Handshake_State& state, + bool force_full_renegotiation) override; + + void send_client_hello(Handshake_State& state, + bool force_full_renegotiation, + Protocol_Version version, + const std::string& srp_identifier = "", + std::function<std::string (std::vector<std::string>)> next_protocol = + std::function<std::string (std::vector<std::string>)>()); + + void process_handshake_msg(const Handshake_State* active_state, + Handshake_State& pending_state, + Handshake_Type type, + const std::vector<byte>& contents) override; + + Handshake_State* new_handshake_state(Handshake_IO* io) override; + + const Policy& m_policy; + Credentials_Manager& m_creds; + const Server_Information m_info; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_exceptn.h b/src/lib/tls/tls_exceptn.h new file mode 100644 index 000000000..529d1f315 --- /dev/null +++ b/src/lib/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 noexcept { 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/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp new file mode 100644 index 000000000..1ae9f1749 --- /dev/null +++ b/src/lib/tls/tls_extensions.cpp @@ -0,0 +1,533 @@ +/* +* 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 Renegotiation_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 nullptr; // not known + } + } + +} + +void Extensions::deserialize(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); + } + } + } + +std::vector<byte> Extensions::serialize() const + { + std::vector<byte> buf(2); // 2 bytes for length field + + for(auto& extn : extensions) + { + if(extn.second->empty()) + continue; + + const u16bit extn_code = extn.second->type(); + + std::vector<byte> extn_val = extn.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 std::vector<byte>(); + + return buf; + } + +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; + } + } + } + +std::vector<byte> Server_Name_Indicator::serialize() const + { + std::vector<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"); + } + +std::vector<byte> SRP_Identifier::serialize() const + { + std::vector<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; + } + +Renegotiation_Extension::Renegotiation_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"); + } + +std::vector<byte> Renegotiation_Extension::serialize() const + { + std::vector<byte> buf; + append_tls_length_value(buf, reneg_data, 1); + return buf; + } + +std::vector<byte> Maximum_Fragment_Length::serialize() const + { + const std::map<size_t, byte> fragment_to_code = { { 512, 1 }, + { 1024, 2 }, + { 2048, 3 }, + { 4096, 4 } }; + + auto i = fragment_to_code.find(m_max_fragment); + + if(i == fragment_to_code.end()) + throw std::invalid_argument("Bad setting " + + std::to_string(m_max_fragment) + + " for maximum fragment size"); + + return std::vector<byte>(1, i->second); + } + +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"); + byte val = reader.get_byte(); + + const std::map<byte, size_t> code_to_fragment = { { 1, 512 }, + { 2, 1024 }, + { 3, 2048 }, + { 4, 4096 } }; + + auto i = code_to_fragment.find(val); + + if(i == code_to_fragment.end()) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Bad value in maximum fragment extension"); + + m_max_fragment = i->second; + } + +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); + } + } + +std::vector<byte> Next_Protocol_Notification::serialize() const + { + std::vector<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"; + case 26: + return "brainpool256r1"; + case 27: + return "brainpool384r1"; + case 28: + return "brainpool512r1"; + 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; + if(name == "brainpool256r1") + return 26; + if(name == "brainpool384r1") + return 27; + if(name == "brainpool512r1") + return 28; + + throw Invalid_Argument("name_to_curve_id unknown name " + name); + } + +std::vector<byte> Supported_Elliptic_Curves::serialize() const + { + std::vector<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) + { + case 1: + return "MD5"; + // 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 == "MD5") + return 1; + + 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"); + } + +std::vector<byte> Signature_Algorithms::serialize() const + { + std::vector<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(const std::vector<std::string>& hashes, + const std::vector<std::string>& sigs) + { + 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])); + } + +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, std::vector<byte> >(extension_size); + } + +} + +} diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h new file mode 100644 index 000000000..de3654fd3 --- /dev/null +++ b/src/lib/tls/tls_extensions.h @@ -0,0 +1,397 @@ +/* +* 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 <memory> +#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: + /** + * @return code number of the extension + */ + virtual Handshake_Extension_Type type() const = 0; + + /** + * @return serialized binary for the extension + */ + virtual std::vector<byte> serialize() const = 0; + + /** + * @return if we should encode this extension or not + */ + 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; } + + std::vector<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; } + + std::vector<byte> serialize() const; + + bool empty() const { return srp_identifier == ""; } + private: + std::string srp_identifier; + }; + +/** +* Renegotiation Indication Extension (RFC 5746) +*/ +class Renegotiation_Extension : public Extension + { + public: + static Handshake_Extension_Type static_type() + { return TLSEXT_SAFE_RENEGOTIATION; } + + Handshake_Extension_Type type() const { return static_type(); } + + Renegotiation_Extension() {} + + Renegotiation_Extension(const std::vector<byte>& bits) : + reneg_data(bits) {} + + Renegotiation_Extension(TLS_Data_Reader& reader, + u16bit extension_size); + + const std::vector<byte>& renegotiation_info() const + { return reneg_data; } + + std::vector<byte> serialize() const; + + bool empty() const { return false; } // always send this + private: + std::vector<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 false; } + + size_t fragment_size() const { return m_max_fragment; } + + std::vector<byte> serialize() const; + + /** + * @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) : + m_max_fragment(max_fragment) {} + + Maximum_Fragment_Length(TLS_Data_Reader& reader, + u16bit extension_size); + + private: + size_t m_max_fragment; + }; + +/** +* 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); + + std::vector<byte> serialize() const; + + bool empty() const { return false; } + private: + std::vector<std::string> m_protocols; + }; + +/** +* Session Ticket Extension (RFC 5077) +*/ +class Session_Ticket : public Extension + { + public: + static Handshake_Extension_Type static_type() + { return TLSEXT_SESSION_TICKET; } + + Handshake_Extension_Type type() const { return static_type(); } + + /** + * @return contents of the session ticket + */ + const std::vector<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 std::vector<byte>& session_ticket) : + m_ticket(session_ticket) {} + + /** + * Deserialize a session ticket + */ + Session_Ticket(TLS_Data_Reader& reader, u16bit extension_size); + + std::vector<byte> serialize() const { return m_ticket; } + + bool empty() const { return false; } + private: + std::vector<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; } + + std::vector<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; + } + + std::vector<byte> serialize() const; + + bool empty() const { return false; } + + Signature_Algorithms(const std::vector<std::string>& hashes, + const std::vector<std::string>& sig_algos); + + 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; } + + std::vector<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(); + + auto i = extensions.find(type); + + if(i != extensions.end()) + return dynamic_cast<T*>(i->second.get()); + return nullptr; + } + + void add(Extension* extn) + { + extensions[extn->type()].reset(extn); + } + + std::vector<byte> serialize() const; + + void deserialize(TLS_Data_Reader& reader); + + Extensions() {} + + Extensions(TLS_Data_Reader& reader) { deserialize(reader); } + + private: + Extensions(const Extensions&) {} + Extensions& operator=(const Extensions&) { return (*this); } + + std::map<Handshake_Extension_Type, std::unique_ptr<Extension>> extensions; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_handshake_hash.cpp b/src/lib/tls/tls_handshake_hash.cpp new file mode 100644 index 000000000..4e7a0b9b7 --- /dev/null +++ b/src/lib/tls/tls_handshake_hash.cpp @@ -0,0 +1,86 @@ +/* +* TLS Handshake Hash +* (C) 2004-2006,2011,2012 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 { + +/** +* Return a TLS Handshake Hash +*/ +secure_vector<byte> Handshake_Hash::final(Protocol_Version version, + const std::string& mac_algo) const + { + Algorithm_Factory& af = global_state().algorithm_factory(); + + std::unique_ptr<HashFunction> hash; + + if(version.supports_ciphersuite_specific_prf()) + { + if(mac_algo == "MD5" || mac_algo == "SHA-1") + hash.reset(af.make_hash_function("SHA-256")); + else + hash.reset(af.make_hash_function(mac_algo)); + } + else + hash.reset(af.make_hash_function("Parallel(MD5,SHA-160)")); + + hash->update(data); + return hash->final(); + } + +/** +* Return a SSLv3 Handshake Hash +*/ +secure_vector<byte> Handshake_Hash::final_ssl3(const secure_vector<byte>& secret) const + { + const byte PAD_INNER = 0x36, PAD_OUTER = 0x5C; + + Algorithm_Factory& af = global_state().algorithm_factory(); + + std::unique_ptr<HashFunction> md5(af.make_hash_function("MD5")); + std::unique_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); + + secure_vector<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); + + secure_vector<byte> output; + output += md5->final(); + output += sha1->final(); + return output; + } + +} + +} diff --git a/src/lib/tls/tls_handshake_hash.h b/src/lib/tls/tls_handshake_hash.h new file mode 100644 index 000000000..840895963 --- /dev/null +++ b/src/lib/tls/tls_handshake_hash.h @@ -0,0 +1,50 @@ +/* +* TLS Handshake Hash +* (C) 2004-2006,2011,2012 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 std::vector<byte>& in) + { data += in; } + + secure_vector<byte> final(Protocol_Version version, + const std::string& mac_algo) const; + + secure_vector<byte> final_ssl3(const secure_vector<byte>& master_secret) const; + + const std::vector<byte>& get_contents() const + { return data; } + + void reset() { data.clear(); } + private: + std::vector<byte> data; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_handshake_io.cpp b/src/lib/tls/tls_handshake_io.cpp new file mode 100644 index 000000000..38def13a2 --- /dev/null +++ b/src/lib/tls/tls_handshake_io.cpp @@ -0,0 +1,381 @@ +/* +* TLS Handshake IO +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/internal/tls_handshake_io.h> +#include <botan/internal/tls_messages.h> +#include <botan/internal/tls_record.h> +#include <botan/internal/tls_seq_numbers.h> +#include <botan/exceptn.h> + +namespace Botan { + +namespace TLS { + +namespace { + +inline size_t load_be24(const byte q[3]) + { + return make_u32bit(0, + q[0], + q[1], + q[2]); + } + +void store_be24(byte out[3], size_t val) + { + out[0] = get_byte<u32bit>(1, val); + out[1] = get_byte<u32bit>(2, val); + out[2] = get_byte<u32bit>(3, val); + } + +} + +Protocol_Version Stream_Handshake_IO::initial_record_version() const + { + return Protocol_Version::TLS_V10; + } + +void Stream_Handshake_IO::add_record(const std::vector<byte>& record, + Record_Type record_type, u64bit) + { + if(record_type == HANDSHAKE) + { + m_queue.insert(m_queue.end(), record.begin(), record.end()); + } + else if(record_type == CHANGE_CIPHER_SPEC) + { + if(record.size() != 1 || record[0] != 1) + throw Decoding_Error("Invalid ChangeCipherSpec"); + + // Pretend it's a regular handshake message of zero length + const byte ccs_hs[] = { HANDSHAKE_CCS, 0, 0, 0 }; + m_queue.insert(m_queue.end(), ccs_hs, ccs_hs + sizeof(ccs_hs)); + } + else + throw Decoding_Error("Unknown message type in handshake processing"); + } + +std::pair<Handshake_Type, std::vector<byte>> +Stream_Handshake_IO::get_next_record(bool) + { + if(m_queue.size() >= 4) + { + const size_t length = load_be24(&m_queue[1]); + + if(m_queue.size() >= length + 4) + { + Handshake_Type type = static_cast<Handshake_Type>(m_queue[0]); + + std::vector<byte> contents(m_queue.begin() + 4, + m_queue.begin() + 4 + length); + + m_queue.erase(m_queue.begin(), m_queue.begin() + 4 + length); + + return std::make_pair(type, contents); + } + } + + return std::make_pair(HANDSHAKE_NONE, std::vector<byte>()); + } + +std::vector<byte> +Stream_Handshake_IO::format(const std::vector<byte>& msg, + Handshake_Type type) const + { + std::vector<byte> send_buf(4 + msg.size()); + + const size_t buf_size = msg.size(); + + send_buf[0] = type; + + store_be24(&send_buf[1], buf_size); + + copy_mem(&send_buf[4], &msg[0], msg.size()); + + return send_buf; + } + +std::vector<byte> Stream_Handshake_IO::send(const Handshake_Message& msg) + { + const std::vector<byte> msg_bits = msg.serialize(); + + if(msg.type() == HANDSHAKE_CCS) + { + m_send_hs(CHANGE_CIPHER_SPEC, msg_bits); + return std::vector<byte>(); // not included in handshake hashes + } + + const std::vector<byte> buf = format(msg_bits, msg.type()); + m_send_hs(HANDSHAKE, buf); + return buf; + } + +Protocol_Version Datagram_Handshake_IO::initial_record_version() const + { + return Protocol_Version::DTLS_V10; + } + +void Datagram_Handshake_IO::add_record(const std::vector<byte>& record, + Record_Type record_type, + u64bit record_sequence) + { + const u16bit epoch = static_cast<u16bit>(record_sequence >> 48); + + if(record_type == CHANGE_CIPHER_SPEC) + { + m_ccs_epochs.insert(epoch); + return; + } + + const size_t DTLS_HANDSHAKE_HEADER_LEN = 12; + + const byte* record_bits = &record[0]; + size_t record_size = record.size(); + + while(record_size) + { + if(record_size < DTLS_HANDSHAKE_HEADER_LEN) + return; // completely bogus? at least degenerate/weird + + const byte msg_type = record_bits[0]; + const size_t msg_len = load_be24(&record_bits[1]); + const u16bit message_seq = load_be<u16bit>(&record_bits[4], 0); + const size_t fragment_offset = load_be24(&record_bits[6]); + const size_t fragment_length = load_be24(&record_bits[9]); + + const size_t total_size = DTLS_HANDSHAKE_HEADER_LEN + fragment_length; + + if(record_size < total_size) + throw Decoding_Error("Bad lengths in DTLS header"); + + if(message_seq >= m_in_message_seq) + { + m_messages[message_seq].add_fragment(&record_bits[DTLS_HANDSHAKE_HEADER_LEN], + fragment_length, + fragment_offset, + epoch, + msg_type, + msg_len); + } + + record_bits += total_size; + record_size -= total_size; + } + } + +std::pair<Handshake_Type, std::vector<byte>> +Datagram_Handshake_IO::get_next_record(bool expecting_ccs) + { + if(!m_flights.rbegin()->empty()) + m_flights.push_back(std::vector<u16bit>()); + + if(expecting_ccs) + { + if(!m_messages.empty()) + { + const u16bit current_epoch = m_messages.begin()->second.epoch(); + + if(m_ccs_epochs.count(current_epoch)) + return std::make_pair(HANDSHAKE_CCS, std::vector<byte>()); + } + + return std::make_pair(HANDSHAKE_NONE, std::vector<byte>()); + } + + auto i = m_messages.find(m_in_message_seq); + + if(i == m_messages.end() || !i->second.complete()) + return std::make_pair(HANDSHAKE_NONE, std::vector<byte>()); + + m_in_message_seq += 1; + + return i->second.message(); + } + +void Datagram_Handshake_IO::Handshake_Reassembly::add_fragment( + const byte fragment[], + size_t fragment_length, + size_t fragment_offset, + u16bit epoch, + byte msg_type, + size_t msg_length) + { + if(complete()) + return; // already have entire message, ignore this + + if(m_msg_type == HANDSHAKE_NONE) + { + m_epoch = epoch; + m_msg_type = msg_type; + m_msg_length = msg_length; + } + + if(msg_type != m_msg_type || msg_length != m_msg_length || epoch != m_epoch) + throw Decoding_Error("Inconsistent values in DTLS handshake header"); + + if(fragment_offset > m_msg_length) + throw Decoding_Error("Fragment offset past end of message"); + + if(fragment_offset + fragment_length > m_msg_length) + throw Decoding_Error("Fragment overlaps past end of message"); + + if(fragment_offset == 0 && fragment_length == m_msg_length) + { + m_fragments.clear(); + m_message.assign(fragment, fragment+fragment_length); + } + else + { + /* + * FIXME. This is a pretty lame way to do defragmentation, huge + * overhead with a tree node per byte. + * + * Also should confirm that all overlaps have no changes, + * otherwise we expose ourselves to the classic fingerprinting + * and IDS evasion attacks on IP fragmentation. + */ + for(size_t i = 0; i != fragment_length; ++i) + m_fragments[fragment_offset+i] = fragment[i]; + + if(m_fragments.size() == m_msg_length) + { + m_message.resize(m_msg_length); + for(size_t i = 0; i != m_msg_length; ++i) + m_message[i] = m_fragments[i]; + m_fragments.clear(); + } + } + } + +bool Datagram_Handshake_IO::Handshake_Reassembly::complete() const + { + return (m_msg_type != HANDSHAKE_NONE && m_message.size() == m_msg_length); + } + +std::pair<Handshake_Type, std::vector<byte>> +Datagram_Handshake_IO::Handshake_Reassembly::message() const + { + if(!complete()) + throw Internal_Error("Datagram_Handshake_IO - message not complete"); + + return std::make_pair(static_cast<Handshake_Type>(m_msg_type), m_message); + } + +std::vector<byte> +Datagram_Handshake_IO::format_fragment(const byte fragment[], + size_t frag_len, + u16bit frag_offset, + u16bit msg_len, + Handshake_Type type, + u16bit msg_sequence) const + { + std::vector<byte> send_buf(12 + frag_len); + + send_buf[0] = type; + + store_be24(&send_buf[1], msg_len); + + store_be(msg_sequence, &send_buf[4]); + + store_be24(&send_buf[6], frag_offset); + store_be24(&send_buf[9], frag_len); + + copy_mem(&send_buf[12], &fragment[0], frag_len); + + return send_buf; + } + +std::vector<byte> +Datagram_Handshake_IO::format_w_seq(const std::vector<byte>& msg, + Handshake_Type type, + u16bit msg_sequence) const + { + return format_fragment(&msg[0], msg.size(), 0, msg.size(), type, msg_sequence); + } + +std::vector<byte> +Datagram_Handshake_IO::format(const std::vector<byte>& msg, + Handshake_Type type) const + { + return format_w_seq(msg, type, m_in_message_seq - 1); + } + +namespace { + +size_t split_for_mtu(size_t mtu, size_t msg_size) + { + const size_t DTLS_HEADERS_SIZE = 25; // DTLS record+handshake headers + + const size_t parts = (msg_size + mtu) / mtu; + + if(parts + DTLS_HEADERS_SIZE > mtu) + return parts + 1; + + return parts; + } + +} + +std::vector<byte> +Datagram_Handshake_IO::send(const Handshake_Message& msg) + { + const std::vector<byte> msg_bits = msg.serialize(); + const u16bit epoch = m_seqs.current_write_epoch(); + const Handshake_Type msg_type = msg.type(); + + std::tuple<u16bit, byte, std::vector<byte>> msg_info(epoch, msg_type, msg_bits); + + if(msg_type == HANDSHAKE_CCS) + { + m_send_hs(epoch, CHANGE_CIPHER_SPEC, msg_bits); + return std::vector<byte>(); // not included in handshake hashes + } + + const std::vector<byte> no_fragment = + format_w_seq(msg_bits, msg_type, m_out_message_seq); + + if(no_fragment.size() + DTLS_HEADER_SIZE <= m_mtu) + m_send_hs(epoch, HANDSHAKE, no_fragment); + else + { + const size_t parts = split_for_mtu(m_mtu, msg_bits.size()); + + const size_t parts_size = (msg_bits.size() + parts) / parts; + + size_t frag_offset = 0; + + while(frag_offset != msg_bits.size()) + { + const size_t frag_len = + std::min<size_t>(msg_bits.size() - frag_offset, + parts_size); + + m_send_hs(epoch, + HANDSHAKE, + format_fragment(&msg_bits[frag_offset], + frag_len, + frag_offset, + msg_bits.size(), + msg_type, + m_out_message_seq)); + + frag_offset += frag_len; + } + } + + // Note: not saving CCS, instead we know it was there due to change in epoch + m_flights.rbegin()->push_back(m_out_message_seq); + m_flight_data[m_out_message_seq] = msg_info; + + m_out_message_seq += 1; + + return no_fragment; + } + +} + +} diff --git a/src/lib/tls/tls_handshake_io.h b/src/lib/tls/tls_handshake_io.h new file mode 100644 index 000000000..36c605c30 --- /dev/null +++ b/src/lib/tls/tls_handshake_io.h @@ -0,0 +1,168 @@ +/* +* TLS Handshake Serialization +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#ifndef BOTAN_TLS_HANDSHAKE_IO_H__ +#define BOTAN_TLS_HANDSHAKE_IO_H__ + +#include <botan/tls_magic.h> +#include <botan/tls_version.h> +#include <botan/loadstor.h> +#include <functional> +#include <vector> +#include <deque> +#include <map> +#include <set> +#include <utility> +#include <tuple> + +namespace Botan { + +namespace TLS { + +class Handshake_Message; + +/** +* Handshake IO Interface +*/ +class Handshake_IO + { + public: + virtual Protocol_Version initial_record_version() const = 0; + + virtual std::vector<byte> send(const Handshake_Message& msg) = 0; + + virtual std::vector<byte> format( + const std::vector<byte>& handshake_msg, + Handshake_Type handshake_type) const = 0; + + virtual void add_record(const std::vector<byte>& record, + Record_Type type, + u64bit sequence_number) = 0; + + /** + * Returns (HANDSHAKE_NONE, std::vector<>()) if no message currently available + */ + virtual std::pair<Handshake_Type, std::vector<byte>> + get_next_record(bool expecting_ccs) = 0; + + Handshake_IO() {} + + Handshake_IO(const Handshake_IO&) = delete; + + Handshake_IO& operator=(const Handshake_IO&) = delete; + + virtual ~Handshake_IO() {} + }; + +/** +* Handshake IO for stream-based handshakes +*/ +class Stream_Handshake_IO : public Handshake_IO + { + public: + Stream_Handshake_IO(std::function<void (byte, const std::vector<byte>&)> writer) : + m_send_hs(writer) {} + + Protocol_Version initial_record_version() const override; + + std::vector<byte> send(const Handshake_Message& msg) override; + + std::vector<byte> format( + const std::vector<byte>& handshake_msg, + Handshake_Type handshake_type) const override; + + void add_record(const std::vector<byte>& record, + Record_Type type, + u64bit sequence_number) override; + + std::pair<Handshake_Type, std::vector<byte>> + get_next_record(bool expecting_ccs) override; + private: + std::deque<byte> m_queue; + std::function<void (byte, const std::vector<byte>&)> m_send_hs; + }; + +/** +* Handshake IO for datagram-based handshakes +*/ +class Datagram_Handshake_IO : public Handshake_IO + { + public: + Datagram_Handshake_IO(class Connection_Sequence_Numbers& seq, + std::function<void (u16bit, byte, const std::vector<byte>&)> writer) : + m_seqs(seq), m_flights(1), m_send_hs(writer) {} + + Protocol_Version initial_record_version() const override; + + std::vector<byte> send(const Handshake_Message& msg) override; + + std::vector<byte> format( + const std::vector<byte>& handshake_msg, + Handshake_Type handshake_type) const override; + + void add_record(const std::vector<byte>& record, + Record_Type type, + u64bit sequence_number) override; + + std::pair<Handshake_Type, std::vector<byte>> + get_next_record(bool expecting_ccs) override; + private: + std::vector<byte> format_fragment( + const byte fragment[], + size_t fragment_len, + u16bit frag_offset, + u16bit msg_len, + Handshake_Type type, + u16bit msg_sequence) const; + + std::vector<byte> format_w_seq( + const std::vector<byte>& handshake_msg, + Handshake_Type handshake_type, + u16bit msg_sequence) const; + + class Handshake_Reassembly + { + public: + void add_fragment(const byte fragment[], + size_t fragment_length, + size_t fragment_offset, + u16bit epoch, + byte msg_type, + size_t msg_length); + + bool complete() const; + + u16bit epoch() const { return m_epoch; } + + std::pair<Handshake_Type, std::vector<byte>> message() const; + private: + byte m_msg_type = HANDSHAKE_NONE; + size_t m_msg_length = 0; + u16bit m_epoch = 0; + + std::map<size_t, byte> m_fragments; + std::vector<byte> m_message; + }; + + class Connection_Sequence_Numbers& m_seqs; + std::map<u16bit, Handshake_Reassembly> m_messages; + std::set<u16bit> m_ccs_epochs; + std::vector<std::vector<u16bit>> m_flights; + std::map<u16bit, std::tuple<u16bit, byte, std::vector<byte>>> m_flight_data; + + // default MTU is IPv6 min MTU minus UDP/IP headers + u16bit m_mtu = 1280 - 40 - 8; + u16bit m_in_message_seq = 0; + u16bit m_out_message_seq = 0; + std::function<void (u16bit, byte, const std::vector<byte>&)> m_send_hs; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_handshake_msg.h b/src/lib/tls/tls_handshake_msg.h new file mode 100644 index 000000000..1c44554d3 --- /dev/null +++ b/src/lib/tls/tls_handshake_msg.h @@ -0,0 +1,36 @@ +/* +* TLS Handshake Message +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#ifndef BOTAN_TLS_HANDSHAKE_MSG_H__ +#define BOTAN_TLS_HANDSHAKE_MSG_H__ + +#include <botan/tls_magic.h> +#include <vector> +#include <string> + +namespace Botan { + +namespace TLS { + +/** +* TLS Handshake Message Base Class +*/ +class BOTAN_DLL Handshake_Message + { + public: + virtual Handshake_Type type() const = 0; + + virtual std::vector<byte> serialize() const = 0; + + virtual ~Handshake_Message() {} + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_handshake_state.cpp b/src/lib/tls/tls_handshake_state.cpp new file mode 100644 index 000000000..84b22cc09 --- /dev/null +++ b/src/lib/tls/tls_handshake_state.cpp @@ -0,0 +1,442 @@ +/* +* TLS Handshaking +* (C) 2004-2006,2011,2012 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/tls_record.h> +#include <botan/lookup.h> + +namespace Botan { + +namespace TLS { + +namespace { + +u32bit bitmask_for_handshake_type(Handshake_Type type) + { + switch(type) + { + case HELLO_VERIFY_REQUEST: + return (1 << 0); + + case HELLO_REQUEST: + return (1 << 1); + + /* + * Same code point for both client hello styles + */ + case CLIENT_HELLO: + case CLIENT_HELLO_SSLV2: + return (1 << 2); + + case SERVER_HELLO: + return (1 << 3); + + case CERTIFICATE: + return (1 << 4); + + case CERTIFICATE_URL: + return (1 << 5); + + case CERTIFICATE_STATUS: + return (1 << 6); + + case SERVER_KEX: + return (1 << 7); + + case CERTIFICATE_REQUEST: + return (1 << 8); + + case SERVER_HELLO_DONE: + return (1 << 9); + + case CERTIFICATE_VERIFY: + return (1 << 10); + + case CLIENT_KEX: + return (1 << 11); + + case NEXT_PROTOCOL: + return (1 << 12); + + case NEW_SESSION_TICKET: + return (1 << 13); + + case HANDSHAKE_CCS: + return (1 << 14); + + case FINISHED: + return (1 << 15); + + // allow explicitly disabling new handshakes + case HANDSHAKE_NONE: + return 0; + } + + throw Internal_Error("Unknown handshake type " + std::to_string(type)); + } + +} + +/* +* Initialize the SSL/TLS Handshake State +*/ +Handshake_State::Handshake_State(Handshake_IO* io, + std::function<void (const Handshake_Message&)> msg_callback) : + m_msg_callback(msg_callback), + m_handshake_io(io), + m_version(m_handshake_io->initial_record_version()) + { + } + +Handshake_State::~Handshake_State() {} + +void Handshake_State::hello_verify_request(const Hello_Verify_Request& hello_verify) + { + note_message(hello_verify); + + m_client_hello->update_hello_cookie(hello_verify); + hash().reset(); + hash().update(handshake_io().send(*m_client_hello)); + note_message(*m_client_hello); + } + +void Handshake_State::client_hello(Client_Hello* client_hello) + { + m_client_hello.reset(client_hello); + note_message(*m_client_hello); + } + +void Handshake_State::server_hello(Server_Hello* server_hello) + { + m_server_hello.reset(server_hello); + m_ciphersuite = Ciphersuite::by_id(m_server_hello->ciphersuite()); + note_message(*m_server_hello); + } + +void Handshake_State::server_certs(Certificate* server_certs) + { + m_server_certs.reset(server_certs); + note_message(*m_server_certs); + } + +void Handshake_State::server_kex(Server_Key_Exchange* server_kex) + { + m_server_kex.reset(server_kex); + note_message(*m_server_kex); + } + +void Handshake_State::cert_req(Certificate_Req* cert_req) + { + m_cert_req.reset(cert_req); + note_message(*m_cert_req); + } + +void Handshake_State::server_hello_done(Server_Hello_Done* server_hello_done) + { + m_server_hello_done.reset(server_hello_done); + note_message(*m_server_hello_done); + } + +void Handshake_State::client_certs(Certificate* client_certs) + { + m_client_certs.reset(client_certs); + note_message(*m_client_certs); + } + +void Handshake_State::client_kex(Client_Key_Exchange* client_kex) + { + m_client_kex.reset(client_kex); + note_message(*m_client_kex); + } + +void Handshake_State::client_verify(Certificate_Verify* client_verify) + { + m_client_verify.reset(client_verify); + note_message(*m_client_verify); + } + +void Handshake_State::next_protocol(Next_Protocol* next_protocol) + { + m_next_protocol.reset(next_protocol); + note_message(*m_next_protocol); + } + +void Handshake_State::new_session_ticket(New_Session_Ticket* new_session_ticket) + { + m_new_session_ticket.reset(new_session_ticket); + note_message(*m_new_session_ticket); + } + +void Handshake_State::server_finished(Finished* server_finished) + { + m_server_finished.reset(server_finished); + note_message(*m_server_finished); + } + +void Handshake_State::client_finished(Finished* client_finished) + { + m_client_finished.reset(client_finished); + note_message(*m_client_finished); + } + +void Handshake_State::set_version(const Protocol_Version& version) + { + m_version = version; + } + +void Handshake_State::compute_session_keys() + { + m_session_keys = Session_Keys(this, client_kex()->pre_master_secret(), false); + } + +void Handshake_State::compute_session_keys(const secure_vector<byte>& resume_master_secret) + { + m_session_keys = Session_Keys(this, resume_master_secret, true); + } + +void Handshake_State::confirm_transition_to(Handshake_Type handshake_msg) + { + const u32bit mask = bitmask_for_handshake_type(handshake_msg); + + m_hand_received_mask |= mask; + + const bool ok = (m_hand_expecting_mask & mask); // overlap? + + if(!ok) + throw Unexpected_Message("Unexpected state transition in handshake, got " + + std::to_string(handshake_msg) + + " expected " + std::to_string(m_hand_expecting_mask) + + " received " + std::to_string(m_hand_received_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. + */ + m_hand_expecting_mask = 0; + } + +void Handshake_State::set_expected_next(Handshake_Type handshake_msg) + { + m_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 (m_hand_received_mask & mask); + } + +std::pair<Handshake_Type, std::vector<byte>> +Handshake_State::get_next_handshake_msg() + { + const bool expecting_ccs = + (bitmask_for_handshake_type(HANDSHAKE_CCS) & m_hand_expecting_mask); + + return m_handshake_io->get_next_record(expecting_ccs); + } + +std::string Handshake_State::srp_identifier() const + { + if(ciphersuite().valid() && ciphersuite().kex_algo() == "SRP_SHA") + return client_hello()->srp_identifier(); + + return ""; + } + +std::vector<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() const + { + if(version() == Protocol_Version::SSL_V3) + { + return get_kdf("SSL3-PRF"); + } + else if(version().supports_ciphersuite_specific_prf()) + { + const std::string prf_algo = ciphersuite().prf_algo(); + + if(prf_algo == "MD5" || prf_algo == "SHA-1") + return get_kdf("TLS-12-PRF(SHA-256)"); + + return get_kdf("TLS-12-PRF(" + prf_algo + ")"); + } + else + { + // TLS v1.0, v1.1 and DTLS v1.0 + return get_kdf("TLS-PRF"); + } + + throw Internal_Error("Unknown version code " + version().to_string()); + } + +namespace { + +std::string choose_hash(const std::string& sig_algo, + Protocol_Version negotiated_version, + const Policy& policy, + bool for_client_auth, + const Client_Hello* client_hello, + const Certificate_Req* cert_req) + { + if(!negotiated_version.supports_negotiable_signature_algorithms()) + { + if(for_client_auth && negotiated_version == Protocol_Version::SSL_V3) + return "Raw"; + + if(sig_algo == "RSA") + return "Parallel(MD5,SHA-160)"; + + if(sig_algo == "DSA") + return "SHA-1"; + + if(sig_algo == "ECDSA") + return "SHA-1"; + + throw Internal_Error("Unknown TLS signature algo " + sig_algo); + } + + const auto supported_algos = for_client_auth ? + cert_req->supported_algos() : + client_hello->supported_algos(); + + if(!supported_algos.empty()) + { + const auto hashes = policy.allowed_signature_hashes(); + + /* + * Choose our most preferred hash that the counterparty supports + * in pairing with the signature algorithm we want to use. + */ + for(auto hash : hashes) + { + for(auto algo : supported_algos) + { + if(algo.first == hash && algo.second == sig_algo) + return hash; + } + } + } + + // TLS v1.2 default hash if the counterparty sent nothing + return "SHA-1"; + } + +} + +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 Policy& policy) const + { + const std::string sig_algo = key.algo_name(); + + const std::string hash_algo = + choose_hash(sig_algo, + this->version(), + policy, + for_client_auth, + client_hello(), + cert_req()); + + if(this->version().supports_negotiable_signature_algorithms()) + { + 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 + { + 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().supports_negotiable_signature_algorithms()) + { + 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"); + } + else + { + if(hash_algo != "" || sig_algo != "") + throw Decoding_Error("Counterparty sent hash/sig IDs with old version"); + } + + if(algo_name == "RSA") + { + if(for_client_auth && this->version() == Protocol_Version::SSL_V3) + { + hash_algo = "Raw"; + } + else if(!this->version().supports_negotiable_signature_algorithms()) + { + hash_algo = "Parallel(MD5,SHA-160)"; + } + + 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().supports_negotiable_signature_algorithms()) + { + 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"); + } + +} + +} diff --git a/src/lib/tls/tls_handshake_state.h b/src/lib/tls/tls_handshake_state.h new file mode 100644 index 000000000..9afcd0374 --- /dev/null +++ b/src/lib/tls/tls_handshake_state.h @@ -0,0 +1,210 @@ +/* +* TLS Handshake State +* (C) 2004-2006,2011,2012 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_io.h> +#include <botan/internal/tls_session_key.h> +#include <botan/tls_ciphersuite.h> +#include <botan/tls_exceptn.h> +#include <botan/tls_handshake_msg.h> +#include <botan/pk_keys.h> +#include <botan/pubkey.h> +#include <functional> +#include <memory> + +namespace Botan { + +class KDF; + +namespace TLS { + +class Policy; + +class Hello_Verify_Request; +class Client_Hello; +class Server_Hello; +class Certificate; +class Server_Key_Exchange; +class Certificate_Req; +class Server_Hello_Done; +class Certificate; +class Client_Key_Exchange; +class Certificate_Verify; +class Next_Protocol; +class New_Session_Ticket; +class Finished; + +/** +* SSL/TLS Handshake State +*/ +class Handshake_State + { + public: + Handshake_State(Handshake_IO* io, + std::function<void (const Handshake_Message&)> msg_callback = + std::function<void (const Handshake_Message&)>()); + + virtual ~Handshake_State(); + + Handshake_State(const Handshake_State&) = delete; + Handshake_State& operator=(const Handshake_State&) = delete; + + Handshake_IO& handshake_io() { return *m_handshake_io; } + + /** + * Return true iff we have received a particular message already + * @param msg_type the message type + */ + bool received_handshake_msg(Handshake_Type msg_type) const; + + /** + * Confirm that we were expecting this message type + * @param msg_type the message type + */ + void confirm_transition_to(Handshake_Type msg_type); + + /** + * Record that we are expecting a particular message type next + * @param msg_type the message type + */ + void set_expected_next(Handshake_Type msg_type); + + std::pair<Handshake_Type, std::vector<byte>> + get_next_handshake_msg(); + + std::vector<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) const; + + 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, + const Policy& policy) const; + + std::string srp_identifier() const; + + KDF* protocol_specific_prf() const; + + Protocol_Version version() const { return m_version; } + + void set_version(const Protocol_Version& version); + + void hello_verify_request(const Hello_Verify_Request& hello_verify); + + void client_hello(Client_Hello* client_hello); + void server_hello(Server_Hello* server_hello); + void server_certs(Certificate* server_certs); + void server_kex(Server_Key_Exchange* server_kex); + void cert_req(Certificate_Req* cert_req); + void server_hello_done(Server_Hello_Done* server_hello_done); + void client_certs(Certificate* client_certs); + void client_kex(Client_Key_Exchange* client_kex); + void client_verify(Certificate_Verify* client_verify); + void next_protocol(Next_Protocol* next_protocol); + void new_session_ticket(New_Session_Ticket* new_session_ticket); + void server_finished(Finished* server_finished); + void client_finished(Finished* client_finished); + + const Client_Hello* client_hello() const + { return m_client_hello.get(); } + + const Server_Hello* server_hello() const + { return m_server_hello.get(); } + + const Certificate* server_certs() const + { return m_server_certs.get(); } + + const Server_Key_Exchange* server_kex() const + { return m_server_kex.get(); } + + const Certificate_Req* cert_req() const + { return m_cert_req.get(); } + + const Server_Hello_Done* server_hello_done() const + { return m_server_hello_done.get(); } + + const Certificate* client_certs() const + { return m_client_certs.get(); } + + const Client_Key_Exchange* client_kex() const + { return m_client_kex.get(); } + + const Certificate_Verify* client_verify() const + { return m_client_verify.get(); } + + const Next_Protocol* next_protocol() const + { return m_next_protocol.get(); } + + const New_Session_Ticket* new_session_ticket() const + { return m_new_session_ticket.get(); } + + const Finished* server_finished() const + { return m_server_finished.get(); } + + const Finished* client_finished() const + { return m_client_finished.get(); } + + const Ciphersuite& ciphersuite() const { return m_ciphersuite; } + + const Session_Keys& session_keys() const { return m_session_keys; } + + void compute_session_keys(); + + void compute_session_keys(const secure_vector<byte>& resume_master_secret); + + Handshake_Hash& hash() { return m_handshake_hash; } + + const Handshake_Hash& hash() const { return m_handshake_hash; } + + void note_message(const Handshake_Message& msg) + { + if(m_msg_callback) + m_msg_callback(msg); + } + + private: + + std::function<void (const Handshake_Message&)> m_msg_callback; + + std::unique_ptr<Handshake_IO> m_handshake_io; + + u32bit m_hand_expecting_mask = 0; + u32bit m_hand_received_mask = 0; + Protocol_Version m_version; + Ciphersuite m_ciphersuite; + Session_Keys m_session_keys; + Handshake_Hash m_handshake_hash; + + std::unique_ptr<Client_Hello> m_client_hello; + std::unique_ptr<Server_Hello> m_server_hello; + std::unique_ptr<Certificate> m_server_certs; + std::unique_ptr<Server_Key_Exchange> m_server_kex; + std::unique_ptr<Certificate_Req> m_cert_req; + std::unique_ptr<Server_Hello_Done> m_server_hello_done; + std::unique_ptr<Certificate> m_client_certs; + std::unique_ptr<Client_Key_Exchange> m_client_kex; + std::unique_ptr<Certificate_Verify> m_client_verify; + std::unique_ptr<Next_Protocol> m_next_protocol; + std::unique_ptr<New_Session_Ticket> m_new_session_ticket; + std::unique_ptr<Finished> m_server_finished; + std::unique_ptr<Finished> m_client_finished; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_heartbeats.cpp b/src/lib/tls/tls_heartbeats.cpp new file mode 100644 index 000000000..8c129858e --- /dev/null +++ b/src/lib/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 std::vector<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 + payload_len) + { + } + +std::vector<byte> Heartbeat_Message::contents() const + { + std::vector<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; + } + +std::vector<byte> Heartbeat_Support_Indicator::serialize() const + { + std::vector<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 " + std::to_string(code)); + + m_peer_allowed_to_send = (code == 1); + } + +} + +} diff --git a/src/lib/tls/tls_heartbeats.h b/src/lib/tls/tls_heartbeats.h new file mode 100644 index 000000000..00b197089 --- /dev/null +++ b/src/lib/tls/tls_heartbeats.h @@ -0,0 +1,43 @@ +/* +* 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 { + +/** +* TLS Heartbeat message +*/ +class Heartbeat_Message + { + public: + enum Type { REQUEST = 1, RESPONSE = 2 }; + + std::vector<byte> contents() const; + + const std::vector<byte>& payload() const { return m_payload; } + + bool is_request() const { return m_type == REQUEST; } + + Heartbeat_Message(const std::vector<byte>& buf); + + Heartbeat_Message(Type type, const byte payload[], size_t payload_len); + private: + Type m_type; + std::vector<byte> m_payload; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_magic.h b/src/lib/tls/tls_magic.h new file mode 100644 index 000000000..51f1fce47 --- /dev/null +++ b/src/lib/tls/tls_magic.h @@ -0,0 +1,72 @@ +/* +* 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, + DTLS_HEADER_SIZE = TLS_HEADER_SIZE + 8, + + MAX_PLAINTEXT_SIZE = 16*1024, + MAX_COMPRESSED_SIZE = MAX_PLAINTEXT_SIZE + 1024, + MAX_CIPHERTEXT_SIZE = MAX_COMPRESSED_SIZE + 1024, +}; + +enum Connection_Side { CLIENT = 1, SERVER = 2 }; + +enum Record_Type { + NO_RECORD = 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, + + CERTIFICATE_URL = 21, + CERTIFICATE_STATUS = 22, + + 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/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h new file mode 100644 index 000000000..155d2427b --- /dev/null +++ b/src/lib/tls/tls_messages.h @@ -0,0 +1,567 @@ +/* +* 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/internal/tls_extensions.h> +#include <botan/tls_handshake_msg.h> +#include <botan/tls_session.h> +#include <botan/tls_policy.h> +#include <botan/tls_ciphersuite.h> +#include <botan/bigint.h> +#include <botan/pkcs8.h> +#include <botan/x509cert.h> +#include <vector> +#include <memory> +#include <string> + +namespace Botan { + +class Credentials_Manager; +class SRP6_Server_Session; + +namespace TLS { + +class Handshake_IO; + +std::vector<byte> make_hello_random(RandomNumberGenerator& rng); + +/** +* DTLS Hello Verify Request +*/ +class Hello_Verify_Request : public Handshake_Message + { + public: + std::vector<byte> serialize() const override; + Handshake_Type type() const override { return HELLO_VERIFY_REQUEST; } + + std::vector<byte> cookie() const { return m_cookie; } + + Hello_Verify_Request(const std::vector<byte>& buf); + + Hello_Verify_Request(const std::vector<byte>& client_hello_bits, + const std::string& client_identity, + const SymmetricKey& secret_key); + private: + std::vector<byte> m_cookie; + }; + +/** +* Client Hello Message +*/ +class Client_Hello : public Handshake_Message + { + public: + Handshake_Type type() const override { return CLIENT_HELLO; } + + Protocol_Version version() const { return m_version; } + + const std::vector<byte>& random() const { return m_random; } + + const std::vector<byte>& session_id() const { return m_session_id; } + + std::vector<u16bit> ciphersuites() const { return m_suites; } + + std::vector<byte> compression_methods() const { return m_comp_methods; } + + bool offered_suite(u16bit ciphersuite) const; + + std::vector<std::pair<std::string, std::string>> supported_algos() const + { + if(Signature_Algorithms* sigs = m_extensions.get<Signature_Algorithms>()) + return sigs->supported_signature_algorthms(); + return std::vector<std::pair<std::string, std::string>>(); + } + + std::vector<std::string> supported_ecc_curves() const + { + if(Supported_Elliptic_Curves* ecc = m_extensions.get<Supported_Elliptic_Curves>()) + return ecc->curves(); + return std::vector<std::string>(); + } + + std::string sni_hostname() const + { + if(Server_Name_Indicator* sni = m_extensions.get<Server_Name_Indicator>()) + return sni->host_name(); + return ""; + } + + std::string srp_identifier() const + { + if(SRP_Identifier* srp = m_extensions.get<SRP_Identifier>()) + return srp->identifier(); + return ""; + } + + bool secure_renegotiation() const + { + return m_extensions.get<Renegotiation_Extension>(); + } + + std::vector<byte> renegotiation_info() const + { + if(Renegotiation_Extension* reneg = m_extensions.get<Renegotiation_Extension>()) + return reneg->renegotiation_info(); + return std::vector<byte>(); + } + + bool next_protocol_notification() const + { + return m_extensions.get<Next_Protocol_Notification>(); + } + + size_t fragment_size() const + { + if(Maximum_Fragment_Length* frag = m_extensions.get<Maximum_Fragment_Length>()) + return frag->fragment_size(); + return 0; + } + + bool supports_session_ticket() const + { + return m_extensions.get<Session_Ticket>(); + } + + std::vector<byte> session_ticket() const + { + if(Session_Ticket* ticket = m_extensions.get<Session_Ticket>()) + return ticket->contents(); + return std::vector<byte>(); + } + + bool supports_heartbeats() const + { + return m_extensions.get<Heartbeat_Support_Indicator>(); + } + + bool peer_can_send_heartbeats() const + { + if(Heartbeat_Support_Indicator* hb = m_extensions.get<Heartbeat_Support_Indicator>()) + return hb->peer_allowed_to_send(); + return false; + } + + void update_hello_cookie(const Hello_Verify_Request& hello_verify); + + Client_Hello(Handshake_IO& io, + Handshake_Hash& hash, + Protocol_Version version, + const Policy& policy, + RandomNumberGenerator& rng, + const std::vector<byte>& reneg_info, + bool next_protocol = false, + const std::string& hostname = "", + const std::string& srp_identifier = ""); + + Client_Hello(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + RandomNumberGenerator& rng, + const std::vector<byte>& reneg_info, + const Session& resumed_session, + bool next_protocol = false); + + Client_Hello(const std::vector<byte>& buf, + Handshake_Type type); + + private: + std::vector<byte> serialize() const override; + void deserialize(const std::vector<byte>& buf); + void deserialize_sslv2(const std::vector<byte>& buf); + + Protocol_Version m_version; + std::vector<byte> m_session_id; + std::vector<byte> m_random; + std::vector<u16bit> m_suites; + std::vector<byte> m_comp_methods; + std::vector<byte> m_hello_cookie; // DTLS only + + Extensions m_extensions; + }; + +/** +* Server Hello Message +*/ +class Server_Hello : public Handshake_Message + { + public: + Handshake_Type type() const override { return SERVER_HELLO; } + + Protocol_Version version() const { return m_version; } + + const std::vector<byte>& random() const { return m_random; } + + const std::vector<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_extensions.get<Renegotiation_Extension>(); + } + + std::vector<byte> renegotiation_info() const + { + if(Renegotiation_Extension* reneg = m_extensions.get<Renegotiation_Extension>()) + return reneg->renegotiation_info(); + return std::vector<byte>(); + } + + bool next_protocol_notification() const + { + return m_extensions.get<Next_Protocol_Notification>(); + } + + std::vector<std::string> next_protocols() const + { + if(Next_Protocol_Notification* npn = m_extensions.get<Next_Protocol_Notification>()) + return npn->protocols(); + return std::vector<std::string>(); + } + + size_t fragment_size() const + { + if(Maximum_Fragment_Length* frag = m_extensions.get<Maximum_Fragment_Length>()) + return frag->fragment_size(); + return 0; + } + + bool supports_session_ticket() const + { + return m_extensions.get<Session_Ticket>(); + } + + bool supports_heartbeats() const + { + return m_extensions.get<Heartbeat_Support_Indicator>(); + } + + bool peer_can_send_heartbeats() const + { + if(Heartbeat_Support_Indicator* hb = m_extensions.get<Heartbeat_Support_Indicator>()) + return hb->peer_allowed_to_send(); + return false; + } + + Server_Hello(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + const std::vector<byte>& session_id, + Protocol_Version ver, + u16bit ciphersuite, + byte compression, + size_t max_fragment_size, + bool client_has_secure_renegotiation, + const std::vector<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 std::vector<byte>& buf); + private: + std::vector<byte> serialize() const override; + + Protocol_Version m_version; + std::vector<byte> m_session_id, m_random; + u16bit m_ciphersuite; + byte m_comp_method; + + Extensions m_extensions; + }; + +/** +* Client Key Exchange Message +*/ +class Client_Key_Exchange : public Handshake_Message + { + public: + Handshake_Type type() const override { return CLIENT_KEX; } + + const secure_vector<byte>& pre_master_secret() const + { return m_pre_master; } + + Client_Key_Exchange(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + Credentials_Manager& creds, + const Public_Key* server_public_key, + const std::string& hostname, + RandomNumberGenerator& rng); + + Client_Key_Exchange(const std::vector<byte>& buf, + const Handshake_State& state, + const Private_Key* server_rsa_kex_key, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng); + + private: + std::vector<byte> serialize() const override + { return m_key_material; } + + std::vector<byte> m_key_material; + secure_vector<byte> m_pre_master; + }; + +/** +* Certificate Message +*/ +class Certificate : public Handshake_Message + { + public: + Handshake_Type type() const override { 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(Handshake_IO& io, + Handshake_Hash& hash, + const std::vector<X509_Certificate>& certs); + + Certificate(const std::vector<byte>& buf); + private: + std::vector<byte> serialize() const override; + + std::vector<X509_Certificate> m_certs; + }; + +/** +* Certificate Request Message +*/ +class Certificate_Req : public Handshake_Message + { + public: + Handshake_Type type() const override { return CERTIFICATE_REQUEST; } + + const std::vector<std::string>& acceptable_cert_types() const + { return m_cert_key_types; } + + std::vector<X509_DN> acceptable_CAs() const { return m_names; } + + std::vector<std::pair<std::string, std::string> > supported_algos() const + { return m_supported_algos; } + + Certificate_Req(Handshake_IO& io, + Handshake_Hash& hash, + const Policy& policy, + const std::vector<X509_DN>& allowed_cas, + Protocol_Version version); + + Certificate_Req(const std::vector<byte>& buf, + Protocol_Version version); + private: + std::vector<byte> serialize() const override; + + std::vector<X509_DN> m_names; + std::vector<std::string> m_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 override { 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, + const Handshake_State& state) const; + + Certificate_Verify(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + RandomNumberGenerator& rng, + const Private_Key* key); + + Certificate_Verify(const std::vector<byte>& buf, + Protocol_Version version); + private: + std::vector<byte> serialize() const override; + + std::string m_sig_algo; // sig algo used to create signature + std::string m_hash_algo; // hash used to create signature + std::vector<byte> m_signature; + }; + +/** +* Finished Message +*/ +class Finished : public Handshake_Message + { + public: + Handshake_Type type() const override { return FINISHED; } + + std::vector<byte> verify_data() const + { return m_verification_data; } + + bool verify(const Handshake_State& state, + Connection_Side side) const; + + Finished(Handshake_IO& io, + Handshake_State& state, + Connection_Side side); + + Finished(const std::vector<byte>& buf); + private: + std::vector<byte> serialize() const override; + + std::vector<byte> m_verification_data; + }; + +/** +* Hello Request Message +*/ +class Hello_Request : public Handshake_Message + { + public: + Handshake_Type type() const override { return HELLO_REQUEST; } + + Hello_Request(Handshake_IO& io); + Hello_Request(const std::vector<byte>& buf); + private: + std::vector<byte> serialize() const override; + }; + +/** +* Server Key Exchange Message +*/ +class Server_Key_Exchange : public Handshake_Message + { + public: + Handshake_Type type() const override { return SERVER_KEX; } + + const std::vector<byte>& params() const { return m_params; } + + bool verify(const Public_Key& server_key, + const 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() const; + + Server_Key_Exchange(Handshake_IO& io, + Handshake_State& state, + const Policy& policy, + Credentials_Manager& creds, + RandomNumberGenerator& rng, + const Private_Key* signing_key = nullptr); + + Server_Key_Exchange(const std::vector<byte>& buf, + const std::string& kex_alg, + const std::string& sig_alg, + Protocol_Version version); + + ~Server_Key_Exchange(); + private: + std::vector<byte> serialize() const override; + + std::unique_ptr<Private_Key> m_kex_key; + std::unique_ptr<SRP6_Server_Session> m_srp_params; + + std::vector<byte> m_params; + + std::string m_sig_algo; // sig algo used to create signature + std::string m_hash_algo; // hash used to create signature + std::vector<byte> m_signature; + }; + +/** +* Server Hello Done Message +*/ +class Server_Hello_Done : public Handshake_Message + { + public: + Handshake_Type type() const override { return SERVER_HELLO_DONE; } + + Server_Hello_Done(Handshake_IO& io, Handshake_Hash& hash); + Server_Hello_Done(const std::vector<byte>& buf); + private: + std::vector<byte> serialize() const override; + }; + +/** +* Next Protocol Message +*/ +class Next_Protocol : public Handshake_Message + { + public: + Handshake_Type type() const override { return NEXT_PROTOCOL; } + + std::string protocol() const { return m_protocol; } + + Next_Protocol(Handshake_IO& io, + Handshake_Hash& hash, + const std::string& protocol); + + Next_Protocol(const std::vector<byte>& buf); + private: + std::vector<byte> serialize() const override; + + std::string m_protocol; + }; + +/** +* New Session Ticket Message +*/ +class New_Session_Ticket : public Handshake_Message + { + public: + Handshake_Type type() const override { return NEW_SESSION_TICKET; } + + u32bit ticket_lifetime_hint() const { return m_ticket_lifetime_hint; } + const std::vector<byte>& ticket() const { return m_ticket; } + + New_Session_Ticket(Handshake_IO& io, + Handshake_Hash& hash, + const std::vector<byte>& ticket, + u32bit lifetime); + + New_Session_Ticket(Handshake_IO& io, + Handshake_Hash& hash); + + New_Session_Ticket(const std::vector<byte>& buf); + private: + std::vector<byte> serialize() const override; + + u32bit m_ticket_lifetime_hint; + std::vector<byte> m_ticket; + }; + +/** +* Change Cipher Spec +*/ +class Change_Cipher_Spec : public Handshake_Message + { + public: + Handshake_Type type() const override { return HANDSHAKE_CCS; } + + std::vector<byte> serialize() const override + { return std::vector<byte>(1, 1); } + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp new file mode 100644 index 000000000..05251e186 --- /dev/null +++ b/src/lib/tls/tls_policy.cpp @@ -0,0 +1,286 @@ +/* +* 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 + { + return std::vector<std::string>({ + "AES-256/GCM", + "AES-128/GCM", + "AES-256/CCM", + "AES-128/CCM", + "AES-256/CCM-8", + "AES-128/CCM-8", + //"Camellia-256/GCM", + //"Camellia-128/GCM", + "AES-256", + "AES-128", + //"Camellia-256", + //"Camellia-128", + //"SEED" + //"3DES", + //"RC4", + }); + } + +std::vector<std::string> Policy::allowed_signature_hashes() const + { + return std::vector<std::string>({ + "SHA-512", + "SHA-384", + "SHA-256", + "SHA-224", + //"SHA-1", + //"MD5", + }); + } + +std::vector<std::string> Policy::allowed_macs() const + { + return std::vector<std::string>({ + "AEAD", + "SHA-384", + "SHA-256", + "SHA-1", + //"MD5", + }); + } + +std::vector<std::string> Policy::allowed_key_exchange_methods() const + { + return std::vector<std::string>({ + "SRP_SHA", + //"ECDHE_PSK", + //"DHE_PSK", + //"PSK", + "ECDH", + "DH", + "RSA", + }); + } + +std::vector<std::string> Policy::allowed_signature_methods() const + { + return std::vector<std::string>({ + "ECDSA", + "RSA", + "DSA", + //"" + }); + } + +std::vector<std::string> Policy::allowed_ecc_curves() const + { + return std::vector<std::string>({ + "brainpool512r1", + "brainpool384r1", + "brainpool256r1", + "secp521r1", + "secp384r1", + "secp256r1", + "secp256k1", + "secp224r1", + "secp224k1", + //"secp192r1", + //"secp192k1", + //"secp160r2", + //"secp160r1", + //"secp160k1", + }); + } + +/* +* 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"); + } + +size_t Policy::minimum_dh_group_size() const + { + return 1024; + } + +/* +* Return allowed compression algorithms +*/ +std::vector<byte> Policy::compression() const + { + return std::vector<byte>{ NO_COMPRESSION }; + } + +u32bit Policy::session_ticket_lifetime() const + { + return 86400; // 1 day + } + +bool Policy::acceptable_protocol_version(Protocol_Version version) const + { + // By default require TLS to minimize surprise + if(version.is_datagram_protocol()) + return false; + + return (version > Protocol_Version::SSL_V3); + } + +bool Policy::acceptable_ciphersuite(const Ciphersuite&) const + { + return true; + } + +namespace { + +class Ciphersuite_Preference_Ordering + { + public: + Ciphersuite_Preference_Ordering(const std::vector<std::string>& ciphers, + const std::vector<std::string>& macs, + const std::vector<std::string>& kex, + const std::vector<std::string>& sigs) : + m_ciphers(ciphers), m_macs(macs), 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_macs.size(); ++i) + { + if(a.mac_algo() == m_macs[i]) + return true; + if(b.mac_algo() == m_macs[i]) + return false; + } + } + + return false; // equal (?!?) + } + private: + std::vector<std::string> m_ciphers, m_macs, m_kex, m_sigs; + }; + +} + +std::vector<u16bit> Policy::ciphersuite_list(Protocol_Version version, + bool have_srp) const + { + const std::vector<std::string> ciphers = allowed_ciphers(); + const std::vector<std::string> macs = allowed_macs(); + const std::vector<std::string> kex = allowed_key_exchange_methods(); + const std::vector<std::string> sigs = allowed_signature_methods(); + + Ciphersuite_Preference_Ordering order(ciphers, macs, kex, sigs); + + std::set<Ciphersuite, Ciphersuite_Preference_Ordering> ciphersuites(order); + + for(auto suite : Ciphersuite::all_known_ciphersuites()) + { + if(!acceptable_ciphersuite(suite)) + continue; + + if(!have_srp && suite.kex_algo() == "SRP_SHA") + continue; + + if(version.is_datagram_protocol() && suite.cipher_algo() == "RC4") + continue; + + if(!version.supports_aead_modes() && suite.mac_algo() == "AEAD") + 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(macs, 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.insert(suite); + } + + if(ciphersuites.empty()) + throw std::logic_error("Policy does not allow any available cipher suite"); + + std::vector<u16bit> ciphersuite_codes; + for(auto i : ciphersuites) + ciphersuite_codes.push_back(i.ciphersuite_code()); + return ciphersuite_codes; + } + +} + +} diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h new file mode 100644 index 000000000..5b205dfeb --- /dev/null +++ b/src/lib/tls/tls_policy.h @@ -0,0 +1,194 @@ +/* +* Hooks for application level policies on TLS connections +* (C) 2004-2006,2013 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/tls_ciphersuite.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. + */ + virtual std::vector<std::string> allowed_ciphers() const; + + /** + * Returns a list of hash algorithms we are willing to use for + * signatures, in order of preference. + */ + virtual std::vector<std::string> allowed_signature_hashes() const; + + /** + * Returns a list of MAC algorithms we are willing to use. + */ + virtual std::vector<std::string> allowed_macs() 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 compression algorithms we are willing to use, + * in order of preference. Allowed values any value of + * Compression_Method. + * + * @note Compression is not currently supported + */ + 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; + + /** + * Attempt to negotiate the use of the heartbeat extension + */ + virtual bool negotiate_heartbeat_support() const { return false; } + + /** + * Allow renegotiation even if the counterparty doesn't + * support the secure renegotiation extension. + * + * @warning Changing this to true exposes you to injected + * plaintext attacks. Read RFC 5746 for background. + */ + virtual bool allow_insecure_renegotiation() const { return false; } + + /** + * Allow servers to initiate a new handshake + */ + virtual bool allow_server_initiated_renegotiation() const { return true; } + + /** + * Return the group to use for ephemeral Diffie-Hellman key agreement + */ + virtual DL_Group dh_group() const; + + /** + * Return the minimum DH group size we're willing to use + */ + virtual size_t minimum_dh_group_size() 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 true if and only if we are willing to accept this version + * Default accepts only TLS, so override if you want to enable DTLS + * in your application. + */ + virtual bool acceptable_protocol_version(Protocol_Version version) const; + + virtual bool acceptable_ciphersuite(const Ciphersuite& suite) const; + + /** + * @return true if servers should choose the ciphersuite matching + * their highest preference, rather than the clients. + * Has no effect on client side. + */ + virtual bool server_uses_own_ciphersuite_preferences() const { return true; } + + /** + * Return allowed ciphersuites, in order of preference + */ + virtual std::vector<u16bit> ciphersuite_list(Protocol_Version version, + bool have_srp) const; + + virtual ~Policy() {} + }; + +/** +* NSA Suite B 128-bit security level (see @rfc 6460) +*/ +class BOTAN_DLL NSA_Suite_B_128 : public Policy + { + public: + std::vector<std::string> allowed_ciphers() const override + { return std::vector<std::string>({"AES-128/GCM"}); } + + std::vector<std::string> allowed_signature_hashes() const override + { return std::vector<std::string>({"SHA-256"}); } + + std::vector<std::string> allowed_macs() const override + { return std::vector<std::string>({"AEAD"}); } + + std::vector<std::string> allowed_key_exchange_methods() const override + { return std::vector<std::string>({"ECDH"}); } + + std::vector<std::string> allowed_signature_methods() const override + { return std::vector<std::string>({"ECDSA"}); } + + std::vector<std::string> allowed_ecc_curves() const override + { return std::vector<std::string>({"secp256r1"}); } + + bool acceptable_protocol_version(Protocol_Version version) const override + { return version == Protocol_Version::TLS_V12; } + }; + +/** +* Policy for DTLS. We require DTLS v1.2 and an AEAD mode +*/ +class BOTAN_DLL Datagram_Policy : public Policy + { + public: + std::vector<std::string> allowed_macs() const override + { return std::vector<std::string>({"AEAD"}); } + + bool acceptable_protocol_version(Protocol_Version version) const override + { return version == Protocol_Version::DTLS_V12; } + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_reader.h b/src/lib/tls/tls_reader.h new file mode 100644 index 000000000..7440e16b7 --- /dev/null +++ b/src/lib/tls/tls_reader.h @@ -0,0 +1,226 @@ +/* +* 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 std::vector<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> + std::vector<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, std::vector<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> + std::vector<T> get_fixed(size_t size) + { + return get_elem<T, std::vector<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 " + std::to_string(n) + + " bytes remaining, only " + std::to_string(buf.size()-offset) + + " left"); + } + } + + const std::vector<byte>& buf; + size_t offset; + }; + +/** +* Helper function for encoding length-tagged vectors +*/ +template<typename T, typename Alloc> +void append_tls_length_value(std::vector<byte, Alloc>& 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, typename Alloc, typename Alloc2> +void append_tls_length_value(std::vector<byte, Alloc>& buf, + const std::vector<T, Alloc2>& vals, + size_t tag_size) + { + append_tls_length_value(buf, &vals[0], vals.size(), tag_size); + } + +template<typename Alloc> +void append_tls_length_value(std::vector<byte, Alloc>& 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/lib/tls/tls_record.cpp b/src/lib/tls/tls_record.cpp new file mode 100644 index 000000000..fc4908dc5 --- /dev/null +++ b/src/lib/tls/tls_record.cpp @@ -0,0 +1,622 @@ +/* +* TLS Record Handling +* (C) 2012,2013 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/internal/tls_record.h> +#include <botan/tls_ciphersuite.h> +#include <botan/tls_exceptn.h> +#include <botan/libstate.h> +#include <botan/loadstor.h> +#include <botan/internal/tls_seq_numbers.h> +#include <botan/internal/tls_session_key.h> +#include <botan/internal/rounding.h> +#include <botan/internal/xor_buf.h> + +namespace Botan { + +namespace TLS { + +Connection_Cipher_State::Connection_Cipher_State(Protocol_Version version, + Connection_Side side, + bool our_side, + const Ciphersuite& suite, + const Session_Keys& keys) : + m_start_time(std::chrono::system_clock::now()), + m_is_ssl3(version == Protocol_Version::SSL_V3) + { + 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(AEAD_Mode* aead = get_aead(cipher_algo, our_side ? ENCRYPTION : DECRYPTION)) + { + m_aead.reset(aead); + m_aead->set_key(cipher_key + mac_key); + + BOTAN_ASSERT(iv.length() == 4, "Using 4/8 partial implicit nonce"); + m_nonce = iv.bits_of(); + m_nonce.resize(12); + return; + } + + Algorithm_Factory& af = global_state().algorithm_factory(); + + if(const BlockCipher* bc = af.prototype_block_cipher(cipher_algo)) + { + m_block_cipher.reset(bc->clone()); + m_block_cipher->set_key(cipher_key); + m_block_cipher_cbc_state = iv.bits_of(); + m_block_size = bc->block_size(); + + if(version.supports_explicit_cbc_ivs()) + m_iv_size = m_block_size; + } + else if(const StreamCipher* sc = af.prototype_stream_cipher(cipher_algo)) + { + m_stream_cipher.reset(sc->clone()); + m_stream_cipher->set_key(cipher_key); + } + else + throw Invalid_Argument("Unknown TLS cipher " + cipher_algo); + + if(version == Protocol_Version::SSL_V3) + m_mac.reset(af.make_mac("SSL3-MAC(" + mac_algo + ")")); + else + m_mac.reset(af.make_mac("HMAC(" + mac_algo + ")")); + + m_mac->set_key(mac_key); + } + +const secure_vector<byte>& Connection_Cipher_State::aead_nonce(u64bit seq) + { + BOTAN_ASSERT(m_aead, "Using AEAD mode"); + BOTAN_ASSERT(m_nonce.size() == 12, "Expected nonce size"); + store_be(seq, &m_nonce[4]); + return m_nonce; + } + +const secure_vector<byte>& +Connection_Cipher_State::aead_nonce(const byte record[], size_t record_len) + { + BOTAN_ASSERT(m_aead, "Using AEAD mode"); + BOTAN_ASSERT(m_nonce.size() == 12, "Expected nonce size"); + BOTAN_ASSERT(record_len >= 8, "Record includes nonce"); + copy_mem(&m_nonce[4], record, 8); + return m_nonce; + } + +const secure_vector<byte>& +Connection_Cipher_State::format_ad(u64bit msg_sequence, + byte msg_type, + Protocol_Version version, + u16bit msg_length) + { + m_ad.clear(); + for(size_t i = 0; i != 8; ++i) + m_ad.push_back(get_byte(i, msg_sequence)); + m_ad.push_back(msg_type); + + if(version != Protocol_Version::SSL_V3) + { + m_ad.push_back(version.major_version()); + m_ad.push_back(version.minor_version()); + } + + m_ad.push_back(get_byte(0, msg_length)); + m_ad.push_back(get_byte(1, msg_length)); + + return m_ad; + } + +void write_record(secure_vector<byte>& output, + byte msg_type, const byte msg[], size_t msg_length, + Protocol_Version version, + u64bit msg_sequence, + Connection_Cipher_State* cipherstate, + RandomNumberGenerator& rng) + { + output.clear(); + + output.push_back(msg_type); + output.push_back(version.major_version()); + output.push_back(version.minor_version()); + + if(version.is_datagram_protocol()) + { + for(size_t i = 0; i != 8; ++i) + output.push_back(get_byte(i, msg_sequence)); + } + + if(!cipherstate) // initial unencrypted handshake records + { + output.push_back(get_byte<u16bit>(0, msg_length)); + output.push_back(get_byte<u16bit>(1, msg_length)); + + output.insert(output.end(), &msg[0], &msg[msg_length]); + + return; + } + + if(AEAD_Mode* aead = cipherstate->aead()) + { + const size_t ctext_size = aead->output_length(msg_length); + + auto nonce = cipherstate->aead_nonce(msg_sequence); + const size_t implicit_nonce_bytes = 4; // FIXME, take from ciphersuite + const size_t explicit_nonce_bytes = 8; + + BOTAN_ASSERT(nonce.size() == implicit_nonce_bytes + explicit_nonce_bytes, + "Expected nonce size"); + + // wrong if start_vec returns something + const size_t rec_size = ctext_size + explicit_nonce_bytes; + + BOTAN_ASSERT(rec_size <= 0xFFFF, "Ciphertext length fits in field"); + + output.push_back(get_byte<u16bit>(0, rec_size)); + output.push_back(get_byte<u16bit>(1, rec_size)); + + aead->set_associated_data_vec( + cipherstate->format_ad(msg_sequence, msg_type, version, msg_length) + ); + + output += std::make_pair(&nonce[implicit_nonce_bytes], explicit_nonce_bytes); + output += aead->start_vec(nonce); + + const size_t offset = output.size(); + output += std::make_pair(&msg[0], msg_length); + aead->finish(output, offset); + + BOTAN_ASSERT(output.size() == offset + ctext_size, "Expected size"); + + BOTAN_ASSERT(output.size() < MAX_CIPHERTEXT_SIZE, + "Produced ciphertext larger than protocol allows"); + return; + } + + cipherstate->mac()->update( + cipherstate->format_ad(msg_sequence, msg_type, version, msg_length) + ); + + cipherstate->mac()->update(msg, msg_length); + + const size_t block_size = cipherstate->block_size(); + const size_t iv_size = cipherstate->iv_size(); + const size_t mac_size = cipherstate->mac_size(); + + const size_t buf_size = round_up( + iv_size + msg_length + mac_size + (block_size ? 1 : 0), + block_size); + + if(buf_size > MAX_CIPHERTEXT_SIZE) + throw Internal_Error("Output record is larger than allowed by protocol"); + + output.push_back(get_byte<u16bit>(0, buf_size)); + output.push_back(get_byte<u16bit>(1, buf_size)); + + const size_t header_size = output.size(); + + if(iv_size) + { + output.resize(output.size() + iv_size); + rng.randomize(&output[output.size() - iv_size], iv_size); + } + + output.insert(output.end(), &msg[0], &msg[msg_length]); + + output.resize(output.size() + mac_size); + cipherstate->mac()->final(&output[output.size() - mac_size]); + + if(block_size) + { + const size_t pad_val = + buf_size - (iv_size + msg_length + mac_size + 1); + + for(size_t i = 0; i != pad_val + 1; ++i) + output.push_back(pad_val); + } + + if(buf_size > MAX_CIPHERTEXT_SIZE) + throw Internal_Error("Produced ciphertext larger than protocol allows"); + + BOTAN_ASSERT(buf_size + header_size == output.size(), + "Output buffer is sized properly"); + + if(StreamCipher* sc = cipherstate->stream_cipher()) + { + sc->cipher1(&output[header_size], buf_size); + } + else if(BlockCipher* bc = cipherstate->block_cipher()) + { + secure_vector<byte>& cbc_state = cipherstate->cbc_state(); + + BOTAN_ASSERT(buf_size % block_size == 0, + "Buffer is an even multiple of block size"); + + byte* buf = &output[header_size]; + + const size_t blocks = buf_size / block_size; + + xor_buf(&buf[0], &cbc_state[0], block_size); + bc->encrypt(&buf[0]); + + for(size_t i = 1; i < blocks; ++i) + { + xor_buf(&buf[block_size*i], &buf[block_size*(i-1)], block_size); + bc->encrypt(&buf[block_size*i]); + } + + cbc_state.assign(&buf[block_size*(blocks-1)], + &buf[block_size*blocks]); + } + else + throw Internal_Error("NULL cipher not supported"); + } + +namespace { + +size_t fill_buffer_to(secure_vector<byte>& readbuf, + const byte*& input, + size_t& input_size, + size_t& input_consumed, + size_t desired) + { + if(readbuf.size() >= desired) + return 0; // already have it + + const size_t taken = std::min(input_size, desired - readbuf.size()); + + readbuf.insert(readbuf.end(), &input[0], &input[taken]); + input_consumed += taken; + input_size -= taken; + input += taken; + + return (desired - readbuf.size()); // how many bytes do we still need? + } + +/* +* Checks the TLS padding. Returns 0 if the padding is invalid (we +* count the padding_length field as part of the padding size so a +* valid padding will always be at least one byte long), or the length +* of the padding otherwise. This is actually padding_length + 1 +* because both the padding and padding_length fields are padding from +* our perspective. +* +* Returning 0 in the error case should ensure the MAC check will fail. +* This approach is suggested in section 6.2.3.2 of RFC 5246. +* +* Also returns 0 if block_size == 0, so can be safely called with a +* stream cipher in use. +* +* @fixme This should run in constant time +*/ +size_t tls_padding_check(bool sslv3_padding, + size_t block_size, + const byte record[], + size_t record_len) + { + const size_t padding_length = record[(record_len-1)]; + + if(padding_length >= record_len) + return 0; + + /* + * SSL v3 requires that the padding be less than the block size + * but not does specify the value of the padding bytes. + */ + if(sslv3_padding) + { + if(padding_length > 0 && padding_length < block_size) + return (padding_length + 1); + else + return 0; + } + + /* + * TLS v1.0 and up require all the padding bytes be the same value + * and allows up to 255 bytes. + */ + const size_t pad_start = record_len - padding_length - 1; + + volatile size_t cmp = 0; + + for(size_t i = 0; i != padding_length; ++i) + cmp += record[pad_start + i] ^ padding_length; + + return cmp ? 0 : padding_length + 1; + } + +void cbc_decrypt_record(byte record_contents[], size_t record_len, + Connection_Cipher_State& cipherstate, + const BlockCipher& bc) + { + const size_t block_size = cipherstate.block_size(); + + BOTAN_ASSERT(record_len % block_size == 0, + "Buffer is an even multiple of block size"); + + const size_t blocks = record_len / block_size; + + BOTAN_ASSERT(blocks >= 1, "At least one ciphertext block"); + + byte* buf = record_contents; + + secure_vector<byte> last_ciphertext(block_size); + copy_mem(&last_ciphertext[0], &buf[0], block_size); + + bc.decrypt(&buf[0]); + xor_buf(&buf[0], &cipherstate.cbc_state()[0], block_size); + + secure_vector<byte> last_ciphertext2; + + for(size_t i = 1; i < blocks; ++i) + { + last_ciphertext2.assign(&buf[block_size*i], &buf[block_size*(i+1)]); + bc.decrypt(&buf[block_size*i]); + xor_buf(&buf[block_size*i], &last_ciphertext[0], block_size); + std::swap(last_ciphertext, last_ciphertext2); + } + + cipherstate.cbc_state() = last_ciphertext; + } + +void decrypt_record(secure_vector<byte>& output, + byte record_contents[], size_t record_len, + u64bit record_sequence, + Protocol_Version record_version, + Record_Type record_type, + Connection_Cipher_State& cipherstate) + { + if(AEAD_Mode* aead = cipherstate.aead()) + { + auto nonce = cipherstate.aead_nonce(record_contents, record_len); + const size_t nonce_length = 8; // fixme, take from ciphersuite + + BOTAN_ASSERT(record_len > nonce_length, "Have data past the nonce"); + const byte* msg = &record_contents[nonce_length]; + const size_t msg_length = record_len - nonce_length; + + const size_t ptext_size = aead->output_length(msg_length); + + aead->set_associated_data_vec( + cipherstate.format_ad(record_sequence, record_type, record_version, ptext_size) + ); + + output += aead->start_vec(nonce); + + const size_t offset = output.size(); + output += std::make_pair(&msg[0], msg_length); + aead->finish(output, offset); + + BOTAN_ASSERT(output.size() == ptext_size + offset, "Produced expected size"); + } + else + { + // GenericBlockCipher / GenericStreamCipher case + + volatile bool padding_bad = false; + size_t pad_size = 0; + + if(StreamCipher* sc = cipherstate.stream_cipher()) + { + sc->cipher1(record_contents, record_len); + // no padding to check or remove + } + else if(BlockCipher* bc = cipherstate.block_cipher()) + { + cbc_decrypt_record(record_contents, record_len, cipherstate, *bc); + + pad_size = tls_padding_check(cipherstate.cipher_padding_single_byte(), + cipherstate.block_size(), + record_contents, record_len); + + padding_bad = (pad_size == 0); + } + else + { + throw Internal_Error("No cipher state set but needed to decrypt"); + } + + const size_t mac_size = cipherstate.mac_size(); + const size_t iv_size = cipherstate.iv_size(); + + const size_t mac_pad_iv_size = mac_size + pad_size + iv_size; + + if(record_len < mac_pad_iv_size) + throw Decoding_Error("Record sent with invalid length"); + + const byte* plaintext_block = &record_contents[iv_size]; + const u16bit plaintext_length = record_len - mac_pad_iv_size; + + cipherstate.mac()->update( + cipherstate.format_ad(record_sequence, record_type, record_version, plaintext_length) + ); + + cipherstate.mac()->update(plaintext_block, plaintext_length); + + std::vector<byte> mac_buf(mac_size); + cipherstate.mac()->final(&mac_buf[0]); + + const size_t mac_offset = record_len - (mac_size + pad_size); + + const bool mac_bad = !same_mem(&record_contents[mac_offset], &mac_buf[0], mac_size); + + if(mac_bad || padding_bad) + throw TLS_Exception(Alert::BAD_RECORD_MAC, "Message authentication failure"); + + output.assign(plaintext_block, plaintext_block + plaintext_length); + } + } + +} + +size_t read_record(secure_vector<byte>& readbuf, + const byte input[], + size_t input_sz, + size_t& consumed, + secure_vector<byte>& record, + u64bit* record_sequence, + Protocol_Version* record_version, + Record_Type* record_type, + Connection_Sequence_Numbers* sequence_numbers, + std::function<Connection_Cipher_State* (u16bit)> get_cipherstate) + { + consumed = 0; + + if(readbuf.size() < TLS_HEADER_SIZE) // header incomplete? + { + if(size_t needed = fill_buffer_to(readbuf, + input, input_sz, consumed, + TLS_HEADER_SIZE)) + return needed; + + BOTAN_ASSERT_EQUAL(readbuf.size(), TLS_HEADER_SIZE, + "Have an entire header"); + } + + // Possible SSLv2 format client hello + if(!sequence_numbers && (readbuf[0] & 0x80) && (readbuf[2] == 1)) + { + if(readbuf[3] == 0 && readbuf[4] == 2) + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Client claims to only support SSLv2, rejecting"); + + if(readbuf[3] >= 3) // SSLv2 mapped TLS hello, then? + { + const size_t record_len = make_u16bit(readbuf[0], readbuf[1]) & 0x7FFF; + + if(size_t needed = fill_buffer_to(readbuf, + input, input_sz, consumed, + record_len + 2)) + return needed; + + BOTAN_ASSERT_EQUAL(readbuf.size(), (record_len + 2), + "Have the entire SSLv2 hello"); + + // Fake v3-style handshake message wrapper + *record_version = Protocol_Version::TLS_V10; + *record_sequence = 0; + *record_type = HANDSHAKE; + + record.resize(4 + readbuf.size() - 2); + + record[0] = CLIENT_HELLO_SSLV2; + record[1] = 0; + record[2] = readbuf[0] & 0x7F; + record[3] = readbuf[1]; + copy_mem(&record[4], &readbuf[2], readbuf.size() - 2); + + readbuf.clear(); + return 0; + } + } + + *record_version = Protocol_Version(readbuf[1], readbuf[2]); + + const bool is_dtls = record_version->is_datagram_protocol(); + + if(is_dtls && readbuf.size() < DTLS_HEADER_SIZE) + { + if(size_t needed = fill_buffer_to(readbuf, + input, input_sz, consumed, + DTLS_HEADER_SIZE)) + return needed; + + BOTAN_ASSERT_EQUAL(readbuf.size(), DTLS_HEADER_SIZE, + "Have an entire header"); + } + + const size_t header_size = (is_dtls) ? DTLS_HEADER_SIZE : TLS_HEADER_SIZE; + + const size_t record_len = make_u16bit(readbuf[header_size-2], + readbuf[header_size-1]); + + 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(readbuf, + input, input_sz, consumed, + header_size + record_len)) + return needed; // wrong for DTLS? + + BOTAN_ASSERT_EQUAL(static_cast<size_t>(header_size) + record_len, + readbuf.size(), + "Have the full record"); + + *record_type = static_cast<Record_Type>(readbuf[0]); + + u16bit epoch = 0; + + if(is_dtls) + { + *record_sequence = load_be<u64bit>(&readbuf[3], 0); + epoch = (*record_sequence >> 48); + } + else if(sequence_numbers) + { + *record_sequence = sequence_numbers->next_read_sequence(); + epoch = sequence_numbers->current_read_epoch(); + } + else + { + // server initial handshake case + *record_sequence = 0; + epoch = 0; + } + + if(sequence_numbers && sequence_numbers->already_seen(*record_sequence)) + return 0; + + byte* record_contents = &readbuf[header_size]; + + if(epoch == 0) // Unencrypted initial handshake + { + record.assign(&readbuf[header_size], &readbuf[header_size + record_len]); + readbuf.clear(); + return 0; // got a full record + } + + // Otherwise, decrypt, check MAC, return plaintext + Connection_Cipher_State* cipherstate = get_cipherstate(epoch); + + // FIXME: DTLS reordering might cause us not to have the cipher state + + BOTAN_ASSERT(cipherstate, "Have cipherstate for this epoch"); + + decrypt_record(record, + record_contents, + record_len, + *record_sequence, + *record_version, + *record_type, + *cipherstate); + + if(sequence_numbers) + sequence_numbers->read_accept(*record_sequence); + + readbuf.clear(); + return 0; + } + +} + +} diff --git a/src/lib/tls/tls_record.h b/src/lib/tls/tls_record.h new file mode 100644 index 000000000..ef27a0a02 --- /dev/null +++ b/src/lib/tls/tls_record.h @@ -0,0 +1,135 @@ +/* +* 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_magic.h> +#include <botan/tls_version.h> +#include <botan/aead.h> +#include <botan/block_cipher.h> +#include <botan/stream_cipher.h> +#include <botan/mac.h> +#include <vector> +#include <memory> +#include <chrono> + +namespace Botan { + +namespace TLS { + +class Ciphersuite; +class Session_Keys; + +class Connection_Sequence_Numbers; + +/** +* TLS Cipher State +*/ +class Connection_Cipher_State + { + public: + /** + * Initialize a new cipher state + */ + Connection_Cipher_State(Protocol_Version version, + Connection_Side which_side, + bool is_our_side, + const Ciphersuite& suite, + const Session_Keys& keys); + + AEAD_Mode* aead() { return m_aead.get(); } + + const secure_vector<byte>& aead_nonce(u64bit seq); + + const secure_vector<byte>& aead_nonce(const byte record[], size_t record_len); + + const secure_vector<byte>& format_ad(u64bit seq, byte type, + Protocol_Version version, + u16bit ptext_length); + + BlockCipher* block_cipher() { return m_block_cipher.get(); } + + StreamCipher* stream_cipher() { return m_stream_cipher.get(); } + + MessageAuthenticationCode* mac() { return m_mac.get(); } + + secure_vector<byte>& cbc_state() { return m_block_cipher_cbc_state; } + + size_t block_size() const { return m_block_size; } + + size_t mac_size() const { return m_mac->output_length(); } + + size_t iv_size() const { return m_iv_size; } + + bool mac_includes_record_version() const { return !m_is_ssl3; } + + bool cipher_padding_single_byte() const { return m_is_ssl3; } + + bool cbc_without_explicit_iv() const + { return (m_block_size > 0) && (m_iv_size == 0); } + + std::chrono::seconds age() const + { + return std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now() - m_start_time); + } + + private: + std::chrono::system_clock::time_point m_start_time; + std::unique_ptr<BlockCipher> m_block_cipher; + secure_vector<byte> m_block_cipher_cbc_state; + std::unique_ptr<StreamCipher> m_stream_cipher; + std::unique_ptr<MessageAuthenticationCode> m_mac; + + std::unique_ptr<AEAD_Mode> m_aead; + secure_vector<byte> m_nonce, m_ad; + + size_t m_block_size = 0; + size_t m_iv_size = 0; + bool m_is_ssl3 = false; + }; + +/** +* Create a TLS record +* @param write_buffer the output record is placed here +* @param msg_type is the type of the message (handshake, alert, ...) +* @param msg is the plaintext message +* @param msg_length is the length of msg +* @param msg_sequence is the sequence number +* @param version is the protocol version +* @param cipherstate is the writing cipher state +* @param rng is a random number generator +* @return number of bytes written to write_buffer +*/ +void write_record(secure_vector<byte>& write_buffer, + byte msg_type, const byte msg[], size_t msg_length, + Protocol_Version version, + u64bit msg_sequence, + Connection_Cipher_State* cipherstate, + RandomNumberGenerator& rng); + +/** +* Decode a TLS record +* @return zero if full message, else number of bytes still needed +*/ +size_t read_record(secure_vector<byte>& read_buffer, + const byte input[], + size_t input_length, + size_t& input_consumed, + secure_vector<byte>& record, + u64bit* record_sequence, + Protocol_Version* record_version, + Record_Type* record_type, + Connection_Sequence_Numbers* sequence_numbers, + std::function<Connection_Cipher_State* (u16bit)> get_cipherstate); + +} + +} + +#endif diff --git a/src/lib/tls/tls_seq_numbers.h b/src/lib/tls/tls_seq_numbers.h new file mode 100644 index 000000000..87edf3130 --- /dev/null +++ b/src/lib/tls/tls_seq_numbers.h @@ -0,0 +1,125 @@ +/* +* TLS Sequence Number Handling +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#ifndef BOTAN_TLS_SEQ_NUMBERS_H__ +#define BOTAN_TLS_SEQ_NUMBERS_H__ + +#include <botan/types.h> +#include <stdexcept> + +namespace Botan { + +namespace TLS { + +class Connection_Sequence_Numbers + { + public: + virtual void new_read_cipher_state() = 0; + virtual void new_write_cipher_state() = 0; + + virtual u16bit current_read_epoch() const = 0; + virtual u16bit current_write_epoch() const = 0; + + virtual u64bit next_write_sequence() = 0; + virtual u64bit next_read_sequence() = 0; + + virtual bool already_seen(u64bit seq) const = 0; + virtual void read_accept(u64bit seq) = 0; + }; + +class Stream_Sequence_Numbers : public Connection_Sequence_Numbers + { + public: + void new_read_cipher_state() override { m_read_seq_no = 0; m_read_epoch += 1; } + void new_write_cipher_state() override { m_write_seq_no = 0; m_write_epoch += 1; } + + u16bit current_read_epoch() const override { return m_read_epoch; } + u16bit current_write_epoch() const override { return m_write_epoch; } + + u64bit next_write_sequence() override { return m_write_seq_no++; } + u64bit next_read_sequence() override { return m_read_seq_no; } + + bool already_seen(u64bit) const override { return false; } + void read_accept(u64bit) override { m_read_seq_no++; } + private: + u64bit m_write_seq_no = 0; + u64bit m_read_seq_no = 0; + u16bit m_read_epoch = 0; + u16bit m_write_epoch = 0; + }; + +class Datagram_Sequence_Numbers : public Connection_Sequence_Numbers + { + public: + void new_read_cipher_state() override { m_read_epoch += 1; } + + void new_write_cipher_state() override + { + // increment epoch + m_write_seq_no = ((m_write_seq_no >> 48) + 1) << 48; + } + + u16bit current_read_epoch() const override { return m_read_epoch; } + u16bit current_write_epoch() const override { return (m_write_seq_no >> 48); } + + u64bit next_write_sequence() override { return m_write_seq_no++; } + + u64bit next_read_sequence() override + { + throw std::runtime_error("DTLS uses explicit sequence numbers"); + } + + bool already_seen(u64bit sequence) const override + { + const size_t window_size = sizeof(m_window_bits) * 8; + + if(sequence > m_window_highest) + return false; + + const u64bit offset = m_window_highest - sequence; + + if(offset >= window_size) + return true; // really old? + + return (((m_window_bits >> offset) & 1) == 1); + } + + void read_accept(u64bit sequence) override + { + const size_t window_size = sizeof(m_window_bits) * 8; + + if(sequence > m_window_highest) + { + const size_t offset = sequence - m_window_highest; + m_window_highest += offset; + + if(offset >= window_size) + m_window_bits = 0; + else + m_window_bits <<= offset; + + m_window_bits |= 0x01; + } + else + { + const u64bit offset = m_window_highest - sequence; + m_window_bits |= (static_cast<u64bit>(1) << offset); + } + } + + private: + u64bit m_write_seq_no = 0; + u16bit m_read_epoch = 0; + u64bit m_window_highest = 0; + u64bit m_window_bits = 0; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp new file mode 100644 index 000000000..bc518571b --- /dev/null +++ b/src/lib/tls/tls_server.cpp @@ -0,0 +1,725 @@ +/* +* 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 <memory> + +namespace Botan { + +namespace TLS { + +namespace { + +class Server_Handshake_State : public Handshake_State + { + public: + // using Handshake_State::Handshake_State; + + Server_Handshake_State(Handshake_IO* io) : Handshake_State(io) {} + + // Used by the server only, in case of RSA key exchange. Not owned + Private_Key* server_rsa_kex_key = nullptr; + + /* + * Used by the server to know if resumption should be allowed on + * a server-initiated renegotiation + */ + bool allow_session_resumption = true; + }; + +bool check_for_resume(Session& session_info, + Session_Manager& session_manager, + Credentials_Manager& credentials, + const Client_Hello* client_hello, + std::chrono::seconds session_ticket_lifetime) + { + const std::vector<byte>& client_session_id = client_hello->session_id(); + const std::vector<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 != std::chrono::seconds(0) && + 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.server_info().hostname()) + return false; + } + + return true; + } + +/* +* Choose which ciphersuite to use +*/ +u16bit choose_ciphersuite( + const Policy& policy, + Protocol_Version version, + Credentials_Manager& creds, + const std::map<std::string, std::vector<X509_Certificate> >& cert_chains, + const Client_Hello* client_hello) + { + const bool our_choice = policy.server_uses_own_ciphersuite_preferences(); + + 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 = policy.ciphersuite_list(version, have_srp); + + if(server_suites.empty()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Policy forbids us from negotiating any ciphersuite"); + + const bool have_shared_ecc_curve = + (policy.choose_curve(client_hello->supported_ecc_curves()) != ""); + + std::vector<u16bit> pref_list = server_suites; + std::vector<u16bit> other_list = client_suites; + + if(!our_choice) + std::swap(pref_list, other_list); + + for(auto suite_id : pref_list) + { + if(!value_exists(other_list, suite_id)) + continue; + + Ciphersuite suite = Ciphersuite::by_id(suite_id); + + if(!have_shared_ecc_curve && suite.ecc_ciphersuite()) + continue; + + if(suite.sig_algo() != "" && 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", nullptr }; + + 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::function<void (const byte[], size_t)> output_fn, + std::function<void (const byte[], size_t)> data_cb, + std::function<void (Alert, const byte[], size_t)> alert_cb, + std::function<bool (const Session&)> handshake_cb, + Session_Manager& session_manager, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng, + const std::vector<std::string>& next_protocols, + size_t io_buf_sz) : + Channel(output_fn, data_cb, alert_cb, handshake_cb, session_manager, rng, io_buf_sz), + m_policy(policy), + m_creds(creds), + m_possible_protocols(next_protocols) + { + } + +Handshake_State* Server::new_handshake_state(Handshake_IO* io) + { + std::unique_ptr<Handshake_State> state(new Server_Handshake_State(io)); + state->set_expected_next(CLIENT_HELLO); + return state.release(); + } + +std::vector<X509_Certificate> +Server::get_peer_cert_chain(const Handshake_State& state) const + { + if(state.client_certs()) + return state.client_certs()->cert_chain(); + return std::vector<X509_Certificate>(); + } + +/* +* Send a hello request to the client +*/ +void Server::initiate_handshake(Handshake_State& state, + bool force_full_renegotiation) + { + dynamic_cast<Server_Handshake_State&>(state).allow_session_resumption = + !force_full_renegotiation; + + Hello_Request hello_req(state.handshake_io()); + } + +/* +* Process a handshake message +*/ +void Server::process_handshake_msg(const Handshake_State* active_state, + Handshake_State& state_base, + Handshake_Type type, + const std::vector<byte>& contents) + { + Server_Handshake_State& state = dynamic_cast<Server_Handshake_State&>(state_base); + + 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(state.handshake_io().format(contents, type)); + } + + if(type == CLIENT_HELLO || type == CLIENT_HELLO_SSLV2) + { + const bool initial_handshake = !active_state; + + if(!m_policy.allow_insecure_renegotiation() && + !(initial_handshake || secure_renegotiation_supported())) + { + send_warning_alert(Alert::NO_RENEGOTIATION); + return; + } + + state.client_hello(new Client_Hello(contents, type)); + + Protocol_Version client_version = state.client_hello()->version(); + + Protocol_Version negotiated_version; + + if((initial_handshake && client_version.known_version()) || + (!initial_handshake && client_version == active_state->version())) + { + /* + Common cases: new client hello with some known version, or a + renegotiation using the same version as previously + negotiated. + */ + + negotiated_version = client_version; + } + else if(!initial_handshake && (client_version != active_state->version())) + { + /* + * If this is a renegotiation, and the client has offered a + * later version than what it initially negotiated, negotiate + * the old version. This matches OpenSSL's behavior. If the + * client is offering a version earlier than what it initially + * negotiated, reject as a probable attack. + */ + if(active_state->version() > client_version) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Client negotiated " + + active_state->version().to_string() + + " then renegotiated with " + + client_version.to_string()); + } + else + negotiated_version = active_state->version(); + } + else + { + /* + New negotiation using a version we don't know. Offer + them the best we currently know. + */ + negotiated_version = client_version.best_known_match(); + } + + if(!m_policy.acceptable_protocol_version(negotiated_version)) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Client version is unacceptable by policy"); + } + + if(!initial_handshake && state.client_hello()->next_protocol_notification()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Client included NPN extension for renegotiation"); + + secure_renegotiation_check(state.client_hello()); + + state.set_version(negotiated_version); + + Session session_info; + const bool resuming = + state.allow_session_resumption && + check_for_resume(session_info, + session_manager(), + m_creds, + state.client_hello(), + std::chrono::seconds(m_policy.session_ticket_lifetime())); + + bool have_session_ticket_key = false; + + try + { + have_session_ticket_key = + m_creds.psk("tls-server", "session-ticket", "").length() > 0; + } + catch(...) {} + + if(resuming) + { + // resume session + + const bool offer_new_session_ticket = + (state.client_hello()->supports_session_ticket() && + state.client_hello()->session_ticket().empty() && + have_session_ticket_key); + + state.server_hello( + new Server_Hello( + state.handshake_io(), + state.hash(), + m_policy, + state.client_hello()->session_id(), + Protocol_Version(session_info.version()), + session_info.ciphersuite_code(), + session_info.compression_method(), + session_info.fragment_size(), + state.client_hello()->secure_renegotiation(), + secure_renegotiation_data_for_server_hello(), + offer_new_session_ticket, + state.client_hello()->next_protocol_notification(), + m_possible_protocols, + state.client_hello()->supports_heartbeats(), + rng()) + ); + + secure_renegotiation_check(state.server_hello()); + + state.compute_session_keys(session_info.master_secret()); + + if(!save_session(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(state.handshake_io(), + state.hash()) + ); + } + } + + if(state.server_hello()->supports_session_ticket() && !state.new_session_ticket()) + { + try + { + const SymmetricKey ticket_key = m_creds.psk("tls-server", "session-ticket", ""); + + state.new_session_ticket( + new New_Session_Ticket(state.handshake_io(), + state.hash(), + session_info.encrypt(ticket_key, rng()), + m_policy.session_ticket_lifetime()) + ); + } + catch(...) {} + + if(!state.new_session_ticket()) + { + state.new_session_ticket( + new New_Session_Ticket(state.handshake_io(), state.hash()) + ); + } + } + + state.handshake_io().send(Change_Cipher_Spec()); + + change_cipher_spec_writer(SERVER); + + state.server_finished( + new Finished(state.handshake_io(), state, SERVER) + ); + + state.set_expected_next(HANDSHAKE_CCS); + } + else // new session + { + std::map<std::string, std::vector<X509_Certificate> > cert_chains; + + const std::string sni_hostname = state.client_hello()->sni_hostname(); + + cert_chains = get_server_certs(sni_hostname, m_creds); + + if(sni_hostname != "" && cert_chains.empty()) + { + cert_chains = get_server_certs("", m_creds); + + /* + * Only send the unrecognized_name alert if we couldn't + * find any certs for the requested name but did find at + * least one cert to use in general. That avoids sending an + * unrecognized_name when a server is configured for purely + * anonymous operation. + */ + if(!cert_chains.empty()) + send_alert(Alert(Alert::UNRECOGNIZED_NAME)); + } + + state.server_hello( + new Server_Hello( + state.handshake_io(), + state.hash(), + m_policy, + make_hello_random(rng()), // new session ID + state.version(), + choose_ciphersuite(m_policy, + state.version(), + m_creds, + cert_chains, + state.client_hello()), + choose_compression(m_policy, state.client_hello()->compression_methods()), + state.client_hello()->fragment_size(), + state.client_hello()->secure_renegotiation(), + secure_renegotiation_data_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_check(state.server_hello()); + + const std::string sig_algo = state.ciphersuite().sig_algo(); + const std::string kex_algo = state.ciphersuite().kex_algo(); + + if(sig_algo != "") + { + BOTAN_ASSERT(!cert_chains[sig_algo].empty(), + "Attempting to send empty certificate chain"); + + state.server_certs( + new Certificate(state.handshake_io(), + state.hash(), + cert_chains[sig_algo]) + ); + } + + Private_Key* private_key = nullptr; + + if(kex_algo == "RSA" || sig_algo != "") + { + private_key = m_creds.private_key_for( + state.server_certs()->cert_chain()[0], + "tls-server", + sni_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(state.handshake_io(), + state, + m_policy, + m_creds, + rng(), + private_key) + ); + } + + auto trusted_CAs = + m_creds.trusted_certificate_authorities("tls-server", sni_hostname); + + std::vector<X509_DN> client_auth_CAs; + + for(auto store : trusted_CAs) + { + auto subjects = store->all_subjects(); + client_auth_CAs.insert(client_auth_CAs.end(), + subjects.begin(), + subjects.end()); + } + + if(!client_auth_CAs.empty() && state.ciphersuite().sig_algo() != "") + { + state.cert_req( + new Certificate_Req(state.handshake_io(), + state.hash(), + m_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(state.handshake_io(), 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, + state.server_rsa_kex_key, + m_creds, m_policy, rng()) + ); + + state.compute_session_keys(); + } + else if(type == CERTIFICATE_VERIFY) + { + state.client_verify(new Certificate_Verify(contents, state.version())); + + const std::vector<X509_Certificate>& client_certs = + state.client_certs()->cert_chain(); + + const bool sig_valid = + state.client_verify()->verify(client_certs[0], state); + + state.hash().update(state.handshake_io().format(contents, type)); + + /* + * 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 + { + m_creds.verify_certificate_chain("tls-server", "", client_certs); + } + catch(std::exception& e) + { + throw TLS_Exception(Alert::BAD_CERTIFICATE, e.what()); + } + + state.set_expected_next(HANDSHAKE_CCS); + } + else if(type == HANDSHAKE_CCS) + { + if(state.server_hello()->next_protocol_notification()) + state.set_expected_next(NEXT_PROTOCOL); + else + state.set_expected_next(FINISHED); + + change_cipher_spec_reader(SERVER); + } + else if(type == NEXT_PROTOCOL) + { + state.set_expected_next(FINISHED); + + state.next_protocol(new Next_Protocol(contents)); + + // should this be a callback? + 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(state.handshake_io().format(contents, type)); + + Session session_info( + state.server_hello()->session_id(), + state.session_keys().master_secret(), + state.server_hello()->version(), + state.server_hello()->ciphersuite(), + state.server_hello()->compression_method(), + SERVER, + state.server_hello()->fragment_size(), + get_peer_cert_chain(state), + std::vector<byte>(), + Server_Information(state.client_hello()->sni_hostname()), + state.srp_identifier() + ); + + if(save_session(session_info)) + { + if(state.server_hello()->supports_session_ticket()) + { + try + { + const SymmetricKey ticket_key = m_creds.psk("tls-server", "session-ticket", ""); + + state.new_session_ticket( + new New_Session_Ticket(state.handshake_io(), + state.hash(), + session_info.encrypt(ticket_key, rng()), + m_policy.session_ticket_lifetime()) + ); + } + catch(...) {} + } + else + session_manager().save(session_info); + } + + if(!state.new_session_ticket() && + state.server_hello()->supports_session_ticket()) + { + state.new_session_ticket( + new New_Session_Ticket(state.handshake_io(), state.hash()) + ); + } + + state.handshake_io().send(Change_Cipher_Spec()); + + change_cipher_spec_writer(SERVER); + + state.server_finished( + new Finished(state.handshake_io(), state, SERVER) + ); + } + + activate_session(); + } + else + throw Unexpected_Message("Unknown handshake message received"); + } + +} + +} diff --git a/src/lib/tls/tls_server.h b/src/lib/tls/tls_server.h new file mode 100644 index 000000000..a514607ba --- /dev/null +++ b/src/lib/tls/tls_server.h @@ -0,0 +1,71 @@ +/* +* 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/credentials_manager.h> +#include <vector> + +namespace Botan { + +namespace TLS { + +/** +* TLS Server +*/ +class BOTAN_DLL Server : public Channel + { + public: + /** + * Server initialization + */ + Server(std::function<void (const byte[], size_t)> socket_output_fn, + std::function<void (const byte[], size_t)> data_cb, + std::function<void (Alert, const byte[], size_t)> alert_cb, + std::function<bool (const Session&)> handshake_cb, + Session_Manager& session_manager, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng, + const std::vector<std::string>& protocols = std::vector<std::string>(), + size_t reserved_io_buffer_size = 16*1024 + ); + + /** + * Return the protocol notification set by the client (using the + * NPN extension) for this connection, if any + */ + std::string next_protocol() const { return m_next_protocol; } + + private: + std::vector<X509_Certificate> + get_peer_cert_chain(const Handshake_State& state) const override; + + void initiate_handshake(Handshake_State& state, + bool force_full_renegotiation) override; + + void process_handshake_msg(const Handshake_State* active_state, + Handshake_State& pending_state, + Handshake_Type type, + const std::vector<byte>& contents) override; + + Handshake_State* new_handshake_state(Handshake_IO* io) override; + + const Policy& m_policy; + Credentials_Manager& m_creds; + + std::vector<std::string> m_possible_protocols; + std::string m_next_protocol; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_server_info.h b/src/lib/tls/tls_server_info.h new file mode 100644 index 000000000..773296eaf --- /dev/null +++ b/src/lib/tls/tls_server_info.h @@ -0,0 +1,91 @@ +/* +* TLS Server Information +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#ifndef BOTAN_TLS_SERVER_INFO_H__ +#define BOTAN_TLS_SERVER_INFO_H__ + +#include <botan/types.h> +#include <string> + +namespace Botan { + +namespace TLS { + +/** +* Represents information known about a TLS server. +*/ +class BOTAN_DLL Server_Information + { + public: + /** + * An empty server info - nothing known + */ + Server_Information() : m_hostname(""), m_service(""), m_port(0) {} + + /** + * @param hostname the host's DNS name, if known + * @param port specifies the protocol port of the server (eg for + * TCP/UDP). Zero represents unknown. + */ + Server_Information(const std::string& hostname, + u16bit port = 0) : + m_hostname(hostname), m_service(""), m_port(port) {} + + /** + * @param hostname the host's DNS name, if known + * @param service is a text string of the service type + * (eg "https", "tor", or "git") + * @param port specifies the protocol port of the server (eg for + * TCP/UDP). Zero represents unknown. + */ + Server_Information(const std::string& hostname, + const std::string& service, + u16bit port = 0) : + m_hostname(hostname), m_service(service), m_port(port) {} + + std::string hostname() const { return m_hostname; } + + std::string service() const { return m_service; } + + u16bit port() const { return m_port; } + + bool empty() const { return m_hostname.empty(); } + + private: + std::string m_hostname, m_service; + u16bit m_port; + }; + +inline bool operator==(const Server_Information& a, const Server_Information& b) + { + return (a.hostname() == b.hostname()) && + (a.service() == b.service()) && + (a.port() == b.port()); + + } + +inline bool operator!=(const Server_Information& a, const Server_Information& b) + { + return !(a == b); + } + +inline bool operator<(const Server_Information& a, const Server_Information& b) + { + if(a.hostname() != b.hostname()) + return (a.hostname() < b.hostname()); + if(a.service() != b.service()) + return (a.service() < b.service()); + if(a.port() != b.port()) + return (a.port() < b.port()); + return false; // equal + } + +} + +} + +#endif diff --git a/src/lib/tls/tls_session.cpp b/src/lib/tls/tls_session.cpp new file mode 100644 index 000000000..6596804b5 --- /dev/null +++ b/src/lib/tls/tls_session.cpp @@ -0,0 +1,177 @@ +/* +* 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/cryptobox_psk.h> +#include <memory> + +namespace Botan { + +namespace TLS { + +Session::Session(const std::vector<byte>& session_identifier, + const secure_vector<byte>& master_secret, + Protocol_Version version, + u16bit ciphersuite, + byte compression_method, + Connection_Side side, + size_t fragment_size, + const std::vector<X509_Certificate>& certs, + const std::vector<byte>& ticket, + const Server_Information& server_info, + const std::string& srp_identifier) : + m_start_time(std::chrono::system_clock::now()), + 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_fragment_size(fragment_size), + m_peer_certs(certs), + m_server_info(server_info), + m_srp_identifier(srp_identifier) + { + } + +Session::Session(const std::string& pem) + { + secure_vector<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 server_hostname; + ASN1_String server_service; + size_t server_port; + + ASN1_String srp_identifier_str; + + byte major_version = 0, minor_version = 0; + + std::vector<byte> peer_cert_bits; + + size_t start_time = 0; + + 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(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_master_secret, OCTET_STRING) + .decode(peer_cert_bits, OCTET_STRING) + .decode(server_hostname) + .decode(server_service) + .decode(server_port) + .decode(srp_identifier_str) + .end_cons() + .verify_end(); + + m_version = Protocol_Version(major_version, minor_version); + m_start_time = std::chrono::system_clock::from_time_t(start_time); + m_connection_side = static_cast<Connection_Side>(side_code); + + m_server_info = Server_Information(server_hostname.value(), + server_service.value(), + server_port); + + m_srp_identifier = srp_identifier_str.value(); + + if(!peer_cert_bits.empty()) + { + DataSource_Memory certs(&peer_cert_bits[0], peer_cert_bits.size()); + + while(!certs.end_of_data()) + m_peer_certs.push_back(X509_Certificate(certs)); + } + } + +secure_vector<byte> Session::DER_encode() const + { + std::vector<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>(std::chrono::system_clock::to_time_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_master_secret, OCTET_STRING) + .encode(peer_cert_bits, OCTET_STRING) + .encode(ASN1_String(m_server_info.hostname(), UTF8_STRING)) + .encode(ASN1_String(m_server_info.service(), UTF8_STRING)) + .encode(static_cast<size_t>(m_server_info.port())) + .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"); + } + +std::chrono::seconds Session::session_age() const + { + return std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now() - m_start_time); + } + +std::vector<byte> +Session::encrypt(const SymmetricKey& master_key, + RandomNumberGenerator& rng) const + { + const auto der = this->DER_encode(); + + return CryptoBox::encrypt(&der[0], der.size(), master_key, rng); + } + +Session Session::decrypt(const byte buf[], size_t buf_len, + const SymmetricKey& master_key) + { + try + { + const auto ber = CryptoBox::decrypt(buf, buf_len, master_key); + + 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/lib/tls/tls_session.h b/src/lib/tls/tls_session.h new file mode 100644 index 000000000..65154dfce --- /dev/null +++ b/src/lib/tls/tls_session.h @@ -0,0 +1,206 @@ +/* +* 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/tls_server_info.h> +#include <botan/secmem.h> +#include <botan/symkey.h> +#include <chrono> + +namespace Botan { + +namespace TLS { + +/** +* Class representing a TLS session state +*/ +class BOTAN_DLL Session + { + public: + + /** + * Uninitialized session + */ + Session() : + m_start_time(std::chrono::system_clock::time_point::min()), + m_version(), + m_ciphersuite(0), + m_compression_method(0), + m_connection_side(static_cast<Connection_Side>(0)), + m_fragment_size(0) + {} + + /** + * New session (sets session start time) + */ + Session(const std::vector<byte>& session_id, + const secure_vector<byte>& master_secret, + Protocol_Version version, + u16bit ciphersuite, + byte compression_method, + Connection_Side side, + size_t fragment_size, + const std::vector<X509_Certificate>& peer_certs, + const std::vector<byte>& session_ticket, + const Server_Information& server_info, + 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 + */ + secure_vector<byte> DER_encode() const; + + /** + * Encrypt a session (useful for serialization or session tickets) + */ + std::vector<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 std::vector<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 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 secure_vector<byte>& master_secret() const + { return m_master_secret; } + + /** + * Get the session identifier + */ + const std::vector<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; } + + /** + * Return the certificate chain of the peer (possibly empty) + */ + std::vector<X509_Certificate> peer_certs() const { return m_peer_certs; } + + /** + * Get the wall clock time this session began + */ + std::chrono::system_clock::time_point start_time() const + { return m_start_time; } + + /** + * Return how long this session has existed (in seconds) + */ + std::chrono::seconds session_age() const; + + /** + * Return the session ticket the server gave us + */ + const std::vector<byte>& session_ticket() const { return m_session_ticket; } + + Server_Information server_info() const { return m_server_info; } + + private: + enum { TLS_SESSION_PARAM_STRUCT_VERSION = 0x2994e301 }; + + std::chrono::system_clock::time_point m_start_time; + + std::vector<byte> m_identifier; + std::vector<byte> m_session_ticket; // only used by client side + secure_vector<byte> m_master_secret; + + Protocol_Version m_version; + u16bit m_ciphersuite; + byte m_compression_method; + Connection_Side m_connection_side; + + size_t m_fragment_size; + + std::vector<X509_Certificate> m_peer_certs; + Server_Information m_server_info; // optional + std::string m_srp_identifier; // optional + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_session_key.cpp b/src/lib/tls/tls_session_key.cpp new file mode 100644 index 000000000..06cd1d0a1 --- /dev/null +++ b/src/lib/tls/tls_session_key.cpp @@ -0,0 +1,86 @@ +/* +* 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(const Handshake_State* state, + const secure_vector<byte>& pre_master_secret, + bool resuming) + { + const size_t cipher_keylen = state->ciphersuite().cipher_keylen(); + const size_t mac_keylen = state->ciphersuite().mac_keylen(); + const size_t cipher_ivlen = state->ciphersuite().cipher_ivlen(); + + 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::unique_ptr<KDF> prf(state->protocol_specific_prf()); + + if(resuming) + { + master_sec = pre_master_secret; + } + else + { + secure_vector<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); + } + + secure_vector<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/lib/tls/tls_session_key.h b/src/lib/tls/tls_session_key.h new file mode 100644 index 000000000..d62e3400d --- /dev/null +++ b/src/lib/tls/tls_session_key.h @@ -0,0 +1,50 @@ +/* +* 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/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 secure_vector<byte>& master_secret() const { return master_sec; } + + Session_Keys() {} + + Session_Keys(const class Handshake_State* state, + const secure_vector<byte>& pre_master, + bool resuming); + + private: + secure_vector<byte> master_sec; + SymmetricKey c_cipher, s_cipher, c_mac, s_mac; + InitializationVector c_iv, s_iv; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_session_manager.h b/src/lib/tls/tls_session_manager.h new file mode 100644 index 000000000..e6eacc88c --- /dev/null +++ b/src/lib/tls/tls_session_manager.h @@ -0,0 +1,149 @@ +/* +* 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 <mutex> +#include <chrono> +#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 (using session ID) + * @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 std::vector<byte>& session_id, + Session& session) = 0; + + /** + * Try to load a saved session (using info about server) + * @param info the information about the server + * @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_server_info(const Server_Information& info, + Session& session) = 0; + + /** + * Remove this session id from the cache, if it exists + */ + virtual void remove_entry(const std::vector<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 std::chrono::seconds session_lifetime() const = 0; + + virtual ~Session_Manager() {} + }; + +/** +* An implementation of Session_Manager that does not save sessions at +* all, preventing session resumption. +*/ +class BOTAN_DLL Session_Manager_Noop : public Session_Manager + { + public: + bool load_from_session_id(const std::vector<byte>&, Session&) override + { return false; } + + bool load_from_server_info(const Server_Information&, Session&) override + { return false; } + + void remove_entry(const std::vector<byte>&) override {} + + void save(const Session&) override {} + + std::chrono::seconds session_lifetime() const override + { return std::chrono::seconds(0); } + }; + +/** +* An implementation of Session_Manager that saves values in memory. +*/ +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(RandomNumberGenerator& rng, + size_t max_sessions = 1000, + std::chrono::seconds session_lifetime = + std::chrono::seconds(7200)); + + bool load_from_session_id(const std::vector<byte>& session_id, + Session& session) override; + + bool load_from_server_info(const Server_Information& info, + Session& session) override; + + void remove_entry(const std::vector<byte>& session_id) override; + + void save(const Session& session_data) override; + + std::chrono::seconds session_lifetime() const override + { return m_session_lifetime; } + + private: + bool load_from_session_str(const std::string& session_str, + Session& session); + + std::mutex m_mutex; + + size_t m_max_sessions; + + std::chrono::seconds m_session_lifetime; + + RandomNumberGenerator& m_rng; + SymmetricKey m_session_key; + + std::map<std::string, std::vector<byte>> m_sessions; // hex(session_id) -> session + std::map<Server_Information, std::string> m_info_sessions; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls_session_manager_memory.cpp b/src/lib/tls/tls_session_manager_memory.cpp new file mode 100644 index 000000000..24ede276c --- /dev/null +++ b/src/lib/tls/tls_session_manager_memory.cpp @@ -0,0 +1,122 @@ +/* +* TLS Session Management +* (C) 2011,2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/tls_session_manager.h> +#include <botan/hex.h> +#include <chrono> + +namespace Botan { + +namespace TLS { + +Session_Manager_In_Memory::Session_Manager_In_Memory( + RandomNumberGenerator& rng, + size_t max_sessions, + std::chrono::seconds session_lifetime) : + m_max_sessions(max_sessions), + m_session_lifetime(session_lifetime), + m_rng(rng), + m_session_key(m_rng, 32) + {} + +bool Session_Manager_In_Memory::load_from_session_str( + const std::string& session_str, Session& session) + { + // assert(lock is held) + + auto i = m_sessions.find(session_str); + + if(i == m_sessions.end()) + return false; + + try + { + session = Session::decrypt(i->second, m_session_key); + } + catch(...) + { + return false; + } + + // if session has expired, remove it + const auto now = std::chrono::system_clock::now(); + + if(session.start_time() + session_lifetime() < now) + { + m_sessions.erase(i); + return false; + } + + return true; + } + +bool Session_Manager_In_Memory::load_from_session_id( + const std::vector<byte>& session_id, Session& session) + { + std::lock_guard<std::mutex> lock(m_mutex); + + return load_from_session_str(hex_encode(session_id), session); + } + +bool Session_Manager_In_Memory::load_from_server_info( + const Server_Information& info, Session& session) + { + std::lock_guard<std::mutex> lock(m_mutex); + + auto i = m_info_sessions.find(info); + + if(i == m_info_sessions.end()) + return false; + + if(load_from_session_str(i->second, session)) + return true; + + /* + * It existed at one point but was removed from the sessions map, + * remove m_info_sessions entry as well + */ + m_info_sessions.erase(i); + + return false; + } + +void Session_Manager_In_Memory::remove_entry( + const std::vector<byte>& session_id) + { + std::lock_guard<std::mutex> lock(m_mutex); + + auto 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) + { + std::lock_guard<std::mutex> lock(m_mutex); + + if(m_max_sessions != 0) + { + /* + We generate new session IDs with the first 4 bytes being a + timestamp, so this actually removes the oldest sessions 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.encrypt(m_session_key, m_rng); + + if(session.side() == CLIENT && !session.server_info().empty()) + m_info_sessions[session.server_info()] = session_id_str; + } + +} + +} diff --git a/src/lib/tls/tls_suite_info.cpp b/src/lib/tls/tls_suite_info.cpp new file mode 100644 index 000000000..6d6e348e8 --- /dev/null +++ b/src/lib/tls/tls_suite_info.cpp @@ -0,0 +1,463 @@ +/* +* TLS cipher suite information +* +* This file was automatically generated from the IANA assignments +* (tls-parameters.txt hash a794db70c6546a47e3bc3181dc0fd908a322e50c) +* by ./src/scripts/tls_suite_info.py on 2014-01-07 +* +* Released under the terms of the Botan license +*/ + +#include <botan/tls_ciphersuite.h> + +namespace Botan { + +namespace TLS { + +Ciphersuite Ciphersuite::by_id(u16bit suite) + { + switch(suite) + { + case 0x0013: // DHE_DSS_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0x0013, "DSA", "DH", "3DES", 24, 8, "SHA-1", 20); + + case 0x0032: // DHE_DSS_WITH_AES_128_CBC_SHA + return Ciphersuite(0x0032, "DSA", "DH", "AES-128", 16, 16, "SHA-1", 20); + + case 0x0040: // DHE_DSS_WITH_AES_128_CBC_SHA256 + return Ciphersuite(0x0040, "DSA", "DH", "AES-128", 16, 16, "SHA-256", 32); + + case 0x00A2: // DHE_DSS_WITH_AES_128_GCM_SHA256 + return Ciphersuite(0x00A2, "DSA", "DH", "AES-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x0038: // DHE_DSS_WITH_AES_256_CBC_SHA + return Ciphersuite(0x0038, "DSA", "DH", "AES-256", 32, 16, "SHA-1", 20); + + case 0x006A: // DHE_DSS_WITH_AES_256_CBC_SHA256 + return Ciphersuite(0x006A, "DSA", "DH", "AES-256", 32, 16, "SHA-256", 32); + + case 0x00A3: // DHE_DSS_WITH_AES_256_GCM_SHA384 + return Ciphersuite(0x00A3, "DSA", "DH", "AES-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x0044: // DHE_DSS_WITH_CAMELLIA_128_CBC_SHA + return Ciphersuite(0x0044, "DSA", "DH", "Camellia-128", 16, 16, "SHA-1", 20); + + case 0x00BD: // DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 + return Ciphersuite(0x00BD, "DSA", "DH", "Camellia-128", 16, 16, "SHA-256", 32); + + case 0xC080: // DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 + return Ciphersuite(0xC080, "DSA", "DH", "Camellia-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x0087: // DHE_DSS_WITH_CAMELLIA_256_CBC_SHA + return Ciphersuite(0x0087, "DSA", "DH", "Camellia-256", 32, 16, "SHA-1", 20); + + case 0x00C3: // DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 + return Ciphersuite(0x00C3, "DSA", "DH", "Camellia-256", 32, 16, "SHA-256", 32); + + case 0xC081: // DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 + return Ciphersuite(0xC081, "DSA", "DH", "Camellia-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x0066: // DHE_DSS_WITH_RC4_128_SHA + return Ciphersuite(0x0066, "DSA", "DH", "RC4", 16, 0, "SHA-1", 20); + + case 0x0099: // DHE_DSS_WITH_SEED_CBC_SHA + return Ciphersuite(0x0099, "DSA", "DH", "SEED", 16, 16, "SHA-1", 20); + + case 0x008F: // DHE_PSK_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0x008F, "", "DHE_PSK", "3DES", 24, 8, "SHA-1", 20); + + case 0x0090: // DHE_PSK_WITH_AES_128_CBC_SHA + return Ciphersuite(0x0090, "", "DHE_PSK", "AES-128", 16, 16, "SHA-1", 20); + + case 0x00B2: // DHE_PSK_WITH_AES_128_CBC_SHA256 + return Ciphersuite(0x00B2, "", "DHE_PSK", "AES-128", 16, 16, "SHA-256", 32); + + case 0xC0A6: // DHE_PSK_WITH_AES_128_CCM + return Ciphersuite(0xC0A6, "", "DHE_PSK", "AES-128/CCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x00AA: // DHE_PSK_WITH_AES_128_GCM_SHA256 + return Ciphersuite(0x00AA, "", "DHE_PSK", "AES-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x0091: // DHE_PSK_WITH_AES_256_CBC_SHA + return Ciphersuite(0x0091, "", "DHE_PSK", "AES-256", 32, 16, "SHA-1", 20); + + case 0x00B3: // DHE_PSK_WITH_AES_256_CBC_SHA384 + return Ciphersuite(0x00B3, "", "DHE_PSK", "AES-256", 32, 16, "SHA-384", 48); + + case 0xC0A7: // DHE_PSK_WITH_AES_256_CCM + return Ciphersuite(0xC0A7, "", "DHE_PSK", "AES-256/CCM", 32, 4, "AEAD", 0, "SHA-256"); + + case 0x00AB: // DHE_PSK_WITH_AES_256_GCM_SHA384 + return Ciphersuite(0x00AB, "", "DHE_PSK", "AES-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0xC096: // DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 + return Ciphersuite(0xC096, "", "DHE_PSK", "Camellia-128", 16, 16, "SHA-256", 32); + + case 0xC090: // DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 + return Ciphersuite(0xC090, "", "DHE_PSK", "Camellia-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC097: // DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 + return Ciphersuite(0xC097, "", "DHE_PSK", "Camellia-256", 32, 16, "SHA-384", 48); + + case 0xC091: // DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 + return Ciphersuite(0xC091, "", "DHE_PSK", "Camellia-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x008E: // DHE_PSK_WITH_RC4_128_SHA + return Ciphersuite(0x008E, "", "DHE_PSK", "RC4", 16, 0, "SHA-1", 20); + + case 0x0016: // DHE_RSA_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0x0016, "RSA", "DH", "3DES", 24, 8, "SHA-1", 20); + + case 0x0033: // DHE_RSA_WITH_AES_128_CBC_SHA + return Ciphersuite(0x0033, "RSA", "DH", "AES-128", 16, 16, "SHA-1", 20); + + case 0x0067: // DHE_RSA_WITH_AES_128_CBC_SHA256 + return Ciphersuite(0x0067, "RSA", "DH", "AES-128", 16, 16, "SHA-256", 32); + + case 0xC09E: // DHE_RSA_WITH_AES_128_CCM + return Ciphersuite(0xC09E, "RSA", "DH", "AES-128/CCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC0A2: // DHE_RSA_WITH_AES_128_CCM_8 + return Ciphersuite(0xC0A2, "RSA", "DH", "AES-128/CCM-8", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x009E: // DHE_RSA_WITH_AES_128_GCM_SHA256 + return Ciphersuite(0x009E, "RSA", "DH", "AES-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x0039: // DHE_RSA_WITH_AES_256_CBC_SHA + return Ciphersuite(0x0039, "RSA", "DH", "AES-256", 32, 16, "SHA-1", 20); + + case 0x006B: // DHE_RSA_WITH_AES_256_CBC_SHA256 + return Ciphersuite(0x006B, "RSA", "DH", "AES-256", 32, 16, "SHA-256", 32); + + case 0xC09F: // DHE_RSA_WITH_AES_256_CCM + return Ciphersuite(0xC09F, "RSA", "DH", "AES-256/CCM", 32, 4, "AEAD", 0, "SHA-256"); + + case 0xC0A3: // DHE_RSA_WITH_AES_256_CCM_8 + return Ciphersuite(0xC0A3, "RSA", "DH", "AES-256/CCM-8", 32, 4, "AEAD", 0, "SHA-256"); + + case 0x009F: // DHE_RSA_WITH_AES_256_GCM_SHA384 + return Ciphersuite(0x009F, "RSA", "DH", "AES-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x0045: // DHE_RSA_WITH_CAMELLIA_128_CBC_SHA + return Ciphersuite(0x0045, "RSA", "DH", "Camellia-128", 16, 16, "SHA-1", 20); + + case 0x00BE: // DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 + return Ciphersuite(0x00BE, "RSA", "DH", "Camellia-128", 16, 16, "SHA-256", 32); + + case 0xC07C: // DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 + return Ciphersuite(0xC07C, "RSA", "DH", "Camellia-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x0088: // DHE_RSA_WITH_CAMELLIA_256_CBC_SHA + return Ciphersuite(0x0088, "RSA", "DH", "Camellia-256", 32, 16, "SHA-1", 20); + + case 0x00C4: // DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 + return Ciphersuite(0x00C4, "RSA", "DH", "Camellia-256", 32, 16, "SHA-256", 32); + + case 0xC07D: // DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 + return Ciphersuite(0xC07D, "RSA", "DH", "Camellia-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x009A: // DHE_RSA_WITH_SEED_CBC_SHA + return Ciphersuite(0x009A, "RSA", "DH", "SEED", 16, 16, "SHA-1", 20); + + case 0x001B: // DH_anon_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0x001B, "", "DH", "3DES", 24, 8, "SHA-1", 20); + + case 0x0034: // DH_anon_WITH_AES_128_CBC_SHA + return Ciphersuite(0x0034, "", "DH", "AES-128", 16, 16, "SHA-1", 20); + + case 0x006C: // DH_anon_WITH_AES_128_CBC_SHA256 + return Ciphersuite(0x006C, "", "DH", "AES-128", 16, 16, "SHA-256", 32); + + case 0x00A6: // DH_anon_WITH_AES_128_GCM_SHA256 + return Ciphersuite(0x00A6, "", "DH", "AES-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x003A: // DH_anon_WITH_AES_256_CBC_SHA + return Ciphersuite(0x003A, "", "DH", "AES-256", 32, 16, "SHA-1", 20); + + case 0x006D: // DH_anon_WITH_AES_256_CBC_SHA256 + return Ciphersuite(0x006D, "", "DH", "AES-256", 32, 16, "SHA-256", 32); + + case 0x00A7: // DH_anon_WITH_AES_256_GCM_SHA384 + return Ciphersuite(0x00A7, "", "DH", "AES-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x0046: // DH_anon_WITH_CAMELLIA_128_CBC_SHA + return Ciphersuite(0x0046, "", "DH", "Camellia-128", 16, 16, "SHA-1", 20); + + case 0x00BF: // DH_anon_WITH_CAMELLIA_128_CBC_SHA256 + return Ciphersuite(0x00BF, "", "DH", "Camellia-128", 16, 16, "SHA-256", 32); + + case 0xC084: // DH_anon_WITH_CAMELLIA_128_GCM_SHA256 + return Ciphersuite(0xC084, "", "DH", "Camellia-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x0089: // DH_anon_WITH_CAMELLIA_256_CBC_SHA + return Ciphersuite(0x0089, "", "DH", "Camellia-256", 32, 16, "SHA-1", 20); + + case 0x00C5: // DH_anon_WITH_CAMELLIA_256_CBC_SHA256 + return Ciphersuite(0x00C5, "", "DH", "Camellia-256", 32, 16, "SHA-256", 32); + + case 0xC085: // DH_anon_WITH_CAMELLIA_256_GCM_SHA384 + return Ciphersuite(0xC085, "", "DH", "Camellia-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x0018: // DH_anon_WITH_RC4_128_MD5 + return Ciphersuite(0x0018, "", "DH", "RC4", 16, 0, "MD5", 16); + + case 0x009B: // DH_anon_WITH_SEED_CBC_SHA + return Ciphersuite(0x009B, "", "DH", "SEED", 16, 16, "SHA-1", 20); + + case 0xC008: // ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0xC008, "ECDSA", "ECDH", "3DES", 24, 8, "SHA-1", 20); + + case 0xC009: // ECDHE_ECDSA_WITH_AES_128_CBC_SHA + return Ciphersuite(0xC009, "ECDSA", "ECDH", "AES-128", 16, 16, "SHA-1", 20); + + case 0xC023: // ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + return Ciphersuite(0xC023, "ECDSA", "ECDH", "AES-128", 16, 16, "SHA-256", 32); + + case 0xC02B: // ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + return Ciphersuite(0xC02B, "ECDSA", "ECDH", "AES-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC00A: // ECDHE_ECDSA_WITH_AES_256_CBC_SHA + return Ciphersuite(0xC00A, "ECDSA", "ECDH", "AES-256", 32, 16, "SHA-1", 20); + + case 0xC024: // ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 + return Ciphersuite(0xC024, "ECDSA", "ECDH", "AES-256", 32, 16, "SHA-384", 48); + + case 0xC02C: // ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + return Ciphersuite(0xC02C, "ECDSA", "ECDH", "AES-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0xC072: // ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 + return Ciphersuite(0xC072, "ECDSA", "ECDH", "Camellia-128", 16, 16, "SHA-256", 32); + + case 0xC086: // ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 + return Ciphersuite(0xC086, "ECDSA", "ECDH", "Camellia-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC073: // ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 + return Ciphersuite(0xC073, "ECDSA", "ECDH", "Camellia-256", 32, 16, "SHA-384", 48); + + case 0xC087: // ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 + return Ciphersuite(0xC087, "ECDSA", "ECDH", "Camellia-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0xC007: // ECDHE_ECDSA_WITH_RC4_128_SHA + return Ciphersuite(0xC007, "ECDSA", "ECDH", "RC4", 16, 0, "SHA-1", 20); + + case 0xC034: // ECDHE_PSK_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0xC034, "", "ECDHE_PSK", "3DES", 24, 8, "SHA-1", 20); + + case 0xC035: // ECDHE_PSK_WITH_AES_128_CBC_SHA + return Ciphersuite(0xC035, "", "ECDHE_PSK", "AES-128", 16, 16, "SHA-1", 20); + + case 0xC037: // ECDHE_PSK_WITH_AES_128_CBC_SHA256 + return Ciphersuite(0xC037, "", "ECDHE_PSK", "AES-128", 16, 16, "SHA-256", 32); + + case 0xC036: // ECDHE_PSK_WITH_AES_256_CBC_SHA + return Ciphersuite(0xC036, "", "ECDHE_PSK", "AES-256", 32, 16, "SHA-1", 20); + + case 0xC038: // ECDHE_PSK_WITH_AES_256_CBC_SHA384 + return Ciphersuite(0xC038, "", "ECDHE_PSK", "AES-256", 32, 16, "SHA-384", 48); + + case 0xC09A: // ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 + return Ciphersuite(0xC09A, "", "ECDHE_PSK", "Camellia-128", 16, 16, "SHA-256", 32); + + case 0xC09B: // ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 + return Ciphersuite(0xC09B, "", "ECDHE_PSK", "Camellia-256", 32, 16, "SHA-384", 48); + + case 0xC033: // ECDHE_PSK_WITH_RC4_128_SHA + return Ciphersuite(0xC033, "", "ECDHE_PSK", "RC4", 16, 0, "SHA-1", 20); + + case 0xC012: // ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0xC012, "RSA", "ECDH", "3DES", 24, 8, "SHA-1", 20); + + case 0xC013: // ECDHE_RSA_WITH_AES_128_CBC_SHA + return Ciphersuite(0xC013, "RSA", "ECDH", "AES-128", 16, 16, "SHA-1", 20); + + case 0xC027: // ECDHE_RSA_WITH_AES_128_CBC_SHA256 + return Ciphersuite(0xC027, "RSA", "ECDH", "AES-128", 16, 16, "SHA-256", 32); + + case 0xC02F: // ECDHE_RSA_WITH_AES_128_GCM_SHA256 + return Ciphersuite(0xC02F, "RSA", "ECDH", "AES-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC014: // ECDHE_RSA_WITH_AES_256_CBC_SHA + return Ciphersuite(0xC014, "RSA", "ECDH", "AES-256", 32, 16, "SHA-1", 20); + + case 0xC028: // ECDHE_RSA_WITH_AES_256_CBC_SHA384 + return Ciphersuite(0xC028, "RSA", "ECDH", "AES-256", 32, 16, "SHA-384", 48); + + case 0xC030: // ECDHE_RSA_WITH_AES_256_GCM_SHA384 + return Ciphersuite(0xC030, "RSA", "ECDH", "AES-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0xC076: // ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 + return Ciphersuite(0xC076, "RSA", "ECDH", "Camellia-128", 16, 16, "SHA-256", 32); + + case 0xC08A: // ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 + return Ciphersuite(0xC08A, "RSA", "ECDH", "Camellia-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC077: // ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 + return Ciphersuite(0xC077, "RSA", "ECDH", "Camellia-256", 32, 16, "SHA-384", 48); + + case 0xC08B: // ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 + return Ciphersuite(0xC08B, "RSA", "ECDH", "Camellia-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0xC011: // ECDHE_RSA_WITH_RC4_128_SHA + return Ciphersuite(0xC011, "RSA", "ECDH", "RC4", 16, 0, "SHA-1", 20); + + case 0xC017: // ECDH_anon_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0xC017, "", "ECDH", "3DES", 24, 8, "SHA-1", 20); + + case 0xC018: // ECDH_anon_WITH_AES_128_CBC_SHA + return Ciphersuite(0xC018, "", "ECDH", "AES-128", 16, 16, "SHA-1", 20); + + case 0xC019: // ECDH_anon_WITH_AES_256_CBC_SHA + return Ciphersuite(0xC019, "", "ECDH", "AES-256", 32, 16, "SHA-1", 20); + + case 0xC016: // ECDH_anon_WITH_RC4_128_SHA + return Ciphersuite(0xC016, "", "ECDH", "RC4", 16, 0, "SHA-1", 20); + + case 0xC0AA: // PSK_DHE_WITH_AES_128_CCM_8 + return Ciphersuite(0xC0AA, "", "DHE_PSK", "AES-128/CCM-8", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC0AB: // PSK_DHE_WITH_AES_256_CCM_8 + return Ciphersuite(0xC0AB, "", "DHE_PSK", "AES-256/CCM-8", 32, 4, "AEAD", 0, "SHA-256"); + + case 0x008B: // PSK_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0x008B, "", "PSK", "3DES", 24, 8, "SHA-1", 20); + + case 0x008C: // PSK_WITH_AES_128_CBC_SHA + return Ciphersuite(0x008C, "", "PSK", "AES-128", 16, 16, "SHA-1", 20); + + case 0x00AE: // PSK_WITH_AES_128_CBC_SHA256 + return Ciphersuite(0x00AE, "", "PSK", "AES-128", 16, 16, "SHA-256", 32); + + case 0xC0A4: // PSK_WITH_AES_128_CCM + return Ciphersuite(0xC0A4, "", "PSK", "AES-128/CCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC0A8: // PSK_WITH_AES_128_CCM_8 + return Ciphersuite(0xC0A8, "", "PSK", "AES-128/CCM-8", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x00A8: // PSK_WITH_AES_128_GCM_SHA256 + return Ciphersuite(0x00A8, "", "PSK", "AES-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x008D: // PSK_WITH_AES_256_CBC_SHA + return Ciphersuite(0x008D, "", "PSK", "AES-256", 32, 16, "SHA-1", 20); + + case 0x00AF: // PSK_WITH_AES_256_CBC_SHA384 + return Ciphersuite(0x00AF, "", "PSK", "AES-256", 32, 16, "SHA-384", 48); + + case 0xC0A5: // PSK_WITH_AES_256_CCM + return Ciphersuite(0xC0A5, "", "PSK", "AES-256/CCM", 32, 4, "AEAD", 0, "SHA-256"); + + case 0xC0A9: // PSK_WITH_AES_256_CCM_8 + return Ciphersuite(0xC0A9, "", "PSK", "AES-256/CCM-8", 32, 4, "AEAD", 0, "SHA-256"); + + case 0x00A9: // PSK_WITH_AES_256_GCM_SHA384 + return Ciphersuite(0x00A9, "", "PSK", "AES-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0xC094: // PSK_WITH_CAMELLIA_128_CBC_SHA256 + return Ciphersuite(0xC094, "", "PSK", "Camellia-128", 16, 16, "SHA-256", 32); + + case 0xC08E: // PSK_WITH_CAMELLIA_128_GCM_SHA256 + return Ciphersuite(0xC08E, "", "PSK", "Camellia-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC095: // PSK_WITH_CAMELLIA_256_CBC_SHA384 + return Ciphersuite(0xC095, "", "PSK", "Camellia-256", 32, 16, "SHA-384", 48); + + case 0xC08F: // PSK_WITH_CAMELLIA_256_GCM_SHA384 + return Ciphersuite(0xC08F, "", "PSK", "Camellia-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x008A: // PSK_WITH_RC4_128_SHA + return Ciphersuite(0x008A, "", "PSK", "RC4", 16, 0, "SHA-1", 20); + + case 0x000A: // RSA_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0x000A, "RSA", "RSA", "3DES", 24, 8, "SHA-1", 20); + + case 0x002F: // RSA_WITH_AES_128_CBC_SHA + return Ciphersuite(0x002F, "RSA", "RSA", "AES-128", 16, 16, "SHA-1", 20); + + case 0x003C: // RSA_WITH_AES_128_CBC_SHA256 + return Ciphersuite(0x003C, "RSA", "RSA", "AES-128", 16, 16, "SHA-256", 32); + + case 0xC09C: // RSA_WITH_AES_128_CCM + return Ciphersuite(0xC09C, "RSA", "RSA", "AES-128/CCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0xC0A0: // RSA_WITH_AES_128_CCM_8 + return Ciphersuite(0xC0A0, "RSA", "RSA", "AES-128/CCM-8", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x009C: // RSA_WITH_AES_128_GCM_SHA256 + return Ciphersuite(0x009C, "RSA", "RSA", "AES-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x0035: // RSA_WITH_AES_256_CBC_SHA + return Ciphersuite(0x0035, "RSA", "RSA", "AES-256", 32, 16, "SHA-1", 20); + + case 0x003D: // RSA_WITH_AES_256_CBC_SHA256 + return Ciphersuite(0x003D, "RSA", "RSA", "AES-256", 32, 16, "SHA-256", 32); + + case 0xC09D: // RSA_WITH_AES_256_CCM + return Ciphersuite(0xC09D, "RSA", "RSA", "AES-256/CCM", 32, 4, "AEAD", 0, "SHA-256"); + + case 0xC0A1: // RSA_WITH_AES_256_CCM_8 + return Ciphersuite(0xC0A1, "RSA", "RSA", "AES-256/CCM-8", 32, 4, "AEAD", 0, "SHA-256"); + + case 0x009D: // RSA_WITH_AES_256_GCM_SHA384 + return Ciphersuite(0x009D, "RSA", "RSA", "AES-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x0041: // RSA_WITH_CAMELLIA_128_CBC_SHA + return Ciphersuite(0x0041, "RSA", "RSA", "Camellia-128", 16, 16, "SHA-1", 20); + + case 0x00BA: // RSA_WITH_CAMELLIA_128_CBC_SHA256 + return Ciphersuite(0x00BA, "RSA", "RSA", "Camellia-128", 16, 16, "SHA-256", 32); + + case 0xC07A: // RSA_WITH_CAMELLIA_128_GCM_SHA256 + return Ciphersuite(0xC07A, "RSA", "RSA", "Camellia-128/GCM", 16, 4, "AEAD", 0, "SHA-256"); + + case 0x0084: // RSA_WITH_CAMELLIA_256_CBC_SHA + return Ciphersuite(0x0084, "RSA", "RSA", "Camellia-256", 32, 16, "SHA-1", 20); + + case 0x00C0: // RSA_WITH_CAMELLIA_256_CBC_SHA256 + return Ciphersuite(0x00C0, "RSA", "RSA", "Camellia-256", 32, 16, "SHA-256", 32); + + case 0xC07B: // RSA_WITH_CAMELLIA_256_GCM_SHA384 + return Ciphersuite(0xC07B, "RSA", "RSA", "Camellia-256/GCM", 32, 4, "AEAD", 0, "SHA-384"); + + case 0x0004: // RSA_WITH_RC4_128_MD5 + return Ciphersuite(0x0004, "RSA", "RSA", "RC4", 16, 0, "MD5", 16); + + case 0x0005: // RSA_WITH_RC4_128_SHA + return Ciphersuite(0x0005, "RSA", "RSA", "RC4", 16, 0, "SHA-1", 20); + + case 0x0096: // RSA_WITH_SEED_CBC_SHA + return Ciphersuite(0x0096, "RSA", "RSA", "SEED", 16, 16, "SHA-1", 20); + + case 0xC01C: // SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0xC01C, "DSA", "SRP_SHA", "3DES", 24, 8, "SHA-1", 20); + + case 0xC01F: // SRP_SHA_DSS_WITH_AES_128_CBC_SHA + return Ciphersuite(0xC01F, "DSA", "SRP_SHA", "AES-128", 16, 16, "SHA-1", 20); + + case 0xC022: // SRP_SHA_DSS_WITH_AES_256_CBC_SHA + return Ciphersuite(0xC022, "DSA", "SRP_SHA", "AES-256", 32, 16, "SHA-1", 20); + + case 0xC01B: // SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0xC01B, "RSA", "SRP_SHA", "3DES", 24, 8, "SHA-1", 20); + + case 0xC01E: // SRP_SHA_RSA_WITH_AES_128_CBC_SHA + return Ciphersuite(0xC01E, "RSA", "SRP_SHA", "AES-128", 16, 16, "SHA-1", 20); + + case 0xC021: // SRP_SHA_RSA_WITH_AES_256_CBC_SHA + return Ciphersuite(0xC021, "RSA", "SRP_SHA", "AES-256", 32, 16, "SHA-1", 20); + + case 0xC01A: // SRP_SHA_WITH_3DES_EDE_CBC_SHA + return Ciphersuite(0xC01A, "", "SRP_SHA", "3DES", 24, 8, "SHA-1", 20); + + case 0xC01D: // SRP_SHA_WITH_AES_128_CBC_SHA + return Ciphersuite(0xC01D, "", "SRP_SHA", "AES-128", 16, 16, "SHA-1", 20); + + case 0xC020: // SRP_SHA_WITH_AES_256_CBC_SHA + return Ciphersuite(0xC020, "", "SRP_SHA", "AES-256", 32, 16, "SHA-1", 20); + + } + + return Ciphersuite(); // some unknown ciphersuite + } + +} + +} diff --git a/src/lib/tls/tls_version.cpp b/src/lib/tls/tls_version.cpp new file mode 100644 index 000000000..7b880d98c --- /dev/null +++ b/src/lib/tls/tls_version.cpp @@ -0,0 +1,101 @@ +/* +* TLS Protocol Version Management +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/tls_version.h> +#include <botan/tls_exceptn.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(); + + if(maj == 3 && min == 0) + return "SSL v3"; + + if(maj == 3 && min >= 1) // TLS v1.x + return "TLS v1." + std::to_string(min-1); + + if(maj == 254) // DTLS 1.x + return "DTLS v1." + std::to_string(255 - min); + + // Some very new or very old protocol (or bogus data) + return "Unknown " + std::to_string(maj) + "." + std::to_string(min); + } + +bool Protocol_Version::is_datagram_protocol() const + { + return major_version() == 254; + } + +bool Protocol_Version::operator>(const Protocol_Version& other) const + { + if(this->is_datagram_protocol() != other.is_datagram_protocol()) + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Version comparing " + to_string() + + " with " + other.to_string()); + + if(this->is_datagram_protocol()) + return m_version < other.m_version; // goes backwards + + return m_version > other.m_version; + } + +Protocol_Version Protocol_Version::best_known_match() const + { + if(known_version()) + return *this; // known version is its own best match + + if(is_datagram_protocol()) + return Protocol_Version::DTLS_V12; + else + return Protocol_Version::TLS_V12; + } + +bool Protocol_Version::known_version() const + { + return (m_version == Protocol_Version::SSL_V3 || + m_version == Protocol_Version::TLS_V10 || + m_version == Protocol_Version::TLS_V11 || + m_version == Protocol_Version::TLS_V12 || + m_version == Protocol_Version::DTLS_V10 || + m_version == Protocol_Version::DTLS_V12); + } + +bool Protocol_Version::supports_negotiable_signature_algorithms() const + { + return (m_version == Protocol_Version::TLS_V12 || + m_version == Protocol_Version::DTLS_V12); + } + +bool Protocol_Version::supports_explicit_cbc_ivs() const + { + return (m_version == Protocol_Version::TLS_V11 || + m_version == Protocol_Version::TLS_V12 || + m_version == Protocol_Version::DTLS_V10 || + m_version == Protocol_Version::DTLS_V12); + } + +bool Protocol_Version::supports_ciphersuite_specific_prf() const + { + return (m_version == Protocol_Version::TLS_V12 || + m_version == Protocol_Version::DTLS_V12); + } + +bool Protocol_Version::supports_aead_modes() const + { + return (m_version == Protocol_Version::TLS_V12 || + m_version == Protocol_Version::DTLS_V12); + } + +} + +} diff --git a/src/lib/tls/tls_version.h b/src/lib/tls/tls_version.h new file mode 100644 index 000000000..9fd71b629 --- /dev/null +++ b/src/lib/tls/tls_version.h @@ -0,0 +1,151 @@ +/* +* 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 { + +/** +* TLS Protocol Version +*/ +class BOTAN_DLL Protocol_Version + { + public: + enum Version_Code { + SSL_V3 = 0x0300, + TLS_V10 = 0x0301, + TLS_V11 = 0x0302, + TLS_V12 = 0x0303, + + DTLS_V10 = 0xFEFF, + DTLS_V12 = 0xFEFD + }; + + static Protocol_Version latest_tls_version() + { + return Protocol_Version(TLS_V12); + } + + static Protocol_Version latest_dtls_version() + { + return Protocol_Version(DTLS_V12); + } + + Protocol_Version() : m_version(0) {} + + /** + * @param named_version a specific named version of the protocol + */ + Protocol_Version(Version_Code named_version) : + m_version(static_cast<u16bit>(named_version)) {} + + /** + * @param major the major version + * @param minor the minor version + */ + Protocol_Version(byte major, byte minor) : + m_version((static_cast<u16bit>(major) << 8) | minor) {} + + /** + * @return true if this is a valid protocol version + */ + bool valid() const { return (m_version != 0); } + + /** + * @return true if this is a protocol version we know about + */ + bool known_version() const; + + /** + * @return major version of the protocol version + */ + byte major_version() const { return get_byte(0, m_version); } + + /** + * @return minor version of the protocol version + */ + byte minor_version() const { return get_byte(1, m_version); } + + /** + * @return human-readable description of this version + */ + std::string to_string() const; + + /** + * If this version is known, return that. Otherwise return the + * best (most recent) version we know of. + * @return best matching protocol version + */ + Protocol_Version best_known_match() const; + + /** + * @return true iff this is a DTLS version + */ + bool is_datagram_protocol() const; + + /** + * @return true if this version supports negotiable signature algorithms + */ + bool supports_negotiable_signature_algorithms() const; + + /** + * @return true if this version uses explicit IVs for block ciphers + */ + bool supports_explicit_cbc_ivs() const; + + /** + * @return true if this version uses a ciphersuite specific PRF + */ + bool supports_ciphersuite_specific_prf() const; + + bool supports_aead_modes() const; + + /** + * @return if this version is equal to other + */ + bool operator==(const Protocol_Version& other) const + { + return (m_version == other.m_version); + } + + /** + * @return if this version is not equal to other + */ + bool operator!=(const Protocol_Version& other) const + { + return (m_version != other.m_version); + } + + /** + * @return if this version is later than other + */ + bool operator>(const Protocol_Version& other) const; + + /** + * @return if this version is later than or equal to other + */ + bool operator>=(const Protocol_Version& other) const + { + return (*this == other || *this > other); + } + + private: + u16bit m_version; + }; + +} + +} + +#endif + |