diff options
Diffstat (limited to 'src/lib/tls/tls_server.cpp')
-rw-r--r-- | src/lib/tls/tls_server.cpp | 154 |
1 files changed, 89 insertions, 65 deletions
diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp index 2b1885e45..d0c3baa2e 100644 --- a/src/lib/tls/tls_server.cpp +++ b/src/lib/tls/tls_server.cpp @@ -153,7 +153,7 @@ uint16_t choose_ciphersuite( const Policy& policy, Protocol_Version version, Credentials_Manager& creds, - const std::map<std::string, std::vector<X509_Certificate> >& cert_chains, + 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(); @@ -264,17 +264,17 @@ uint16_t choose_ciphersuite( "Can't agree on a ciphersuite with client"); } -std::map<std::string, std::vector<X509_Certificate> > +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 }; + const char* cert_types[] = { "RSA", "ECDSA", "DSA", nullptr }; - std::map<std::string, std::vector<X509_Certificate> > cert_chains; + std::map<std::string, std::vector<X509_Certificate>> cert_chains; for(size_t i = 0; cert_types[i]; ++i) { - std::vector<X509_Certificate> certs = + const std::vector<X509_Certificate> certs = creds.cert_chain_single_type(cert_types[i], "tls-server", hostname); if(!certs.empty()) @@ -321,7 +321,6 @@ Server::Server(output_fn output, { } - Server::Server(output_fn output, data_cb got_data_cb, alert_cb recv_alert_cb, @@ -372,53 +371,29 @@ void Server::initiate_handshake(Handshake_State& state, Hello_Request hello_req(state.handshake_io()); } -/* -* Process a CLIENT HELLO Message -*/ -void Server::process_client_hello_msg(const Handshake_State* active_state, - Server_Handshake_State& pending_state, - const std::vector<uint8_t>& contents) - { - const bool initial_handshake = !active_state; +namespace { - if(initial_handshake == false && policy().allow_client_initiated_renegotiation() == false) - { - send_warning_alert(Alert::NO_RENEGOTIATION); - return; - } +Protocol_Version select_version(const Botan::TLS::Policy& policy, + Protocol_Version client_offer, + Protocol_Version active_version, + bool is_fallback) + { + const Protocol_Version latest_supported = + policy.latest_supported_version(client_offer.is_datagram_protocol()); - if(!policy().allow_insecure_renegotiation() && - !(initial_handshake || secure_renegotiation_supported())) + if(is_fallback) { - send_warning_alert(Alert::NO_RENEGOTIATION); - return; + if(latest_supported > client_offer) + throw TLS_Exception(Alert::INAPPROPRIATE_FALLBACK, + "Client signalled fallback SCSV, possible attack"); } - pending_state.client_hello(new Client_Hello(contents)); - const Protocol_Version client_version = pending_state.client_hello()->version(); - - if(client_version.major_version() < 3) - throw TLS_Exception(Alert::PROTOCOL_VERSION, "Client offered version with major version under 3"); - if(client_version.major_version() == 3 && client_version.minor_version() == 0) - throw TLS_Exception(Alert::PROTOCOL_VERSION, "SSLv3 is not supported"); + const bool initial_handshake = (active_version.valid() == false); - Protocol_Version negotiated_version; - - const Protocol_Version latest_supported = - policy().latest_supported_version(client_version.is_datagram_protocol()); + const bool client_offer_acceptable = + client_offer.known_version() && policy.acceptable_protocol_version(client_offer); - 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(!initial_handshake) { /* * If this is a renegotiation, and the client has offered a @@ -427,40 +402,80 @@ void Server::process_client_hello_msg(const Handshake_State* active_state, * client is offering a version earlier than what it initially * negotiated, reject as a probable attack. */ - if(active_state->version() > client_version) + if(active_version > client_offer) { throw TLS_Exception(Alert::PROTOCOL_VERSION, "Client negotiated " + - active_state->version().to_string() + + active_version.to_string() + " then renegotiated with " + - client_version.to_string()); + client_offer.to_string()); } else - negotiated_version = active_state->version(); + { + return active_version; + } } - else + else if(client_offer_acceptable) + { + return client_offer; + } + else if(!client_offer.known_version() || client_offer > latest_supported) { /* - New negotiation using a version we don't know. Offer them the - best we currently know and support + The client offered some version newer than the latest we + support. Offer them the best we know. */ - negotiated_version = latest_supported; + return latest_supported; } - - if(!policy().acceptable_protocol_version(negotiated_version)) + else { throw TLS_Exception(Alert::PROTOCOL_VERSION, - "Client version " + negotiated_version.to_string() + + "Client version " + client_offer.to_string() + " is unacceptable by policy"); } + } + +} - if(pending_state.client_hello()->sent_fallback_scsv()) +/* +* Process a CLIENT HELLO Message +*/ +void Server::process_client_hello_msg(const Handshake_State* active_state, + Server_Handshake_State& pending_state, + const std::vector<uint8_t>& contents) + { + const bool initial_handshake = !active_state; + + if(initial_handshake == false && policy().allow_client_initiated_renegotiation() == false) { - if(latest_supported > client_version) - throw TLS_Exception(Alert::INAPPROPRIATE_FALLBACK, - "Client signalled fallback SCSV, possible attack"); + send_warning_alert(Alert::NO_RENEGOTIATION); + return; } + if(!policy().allow_insecure_renegotiation() && + !(initial_handshake || secure_renegotiation_supported())) + { + send_warning_alert(Alert::NO_RENEGOTIATION); + return; + } + + pending_state.client_hello(new Client_Hello(contents)); + const Protocol_Version client_offer = pending_state.client_hello()->version(); + + if(client_offer.major_version() < 3) + throw TLS_Exception(Alert::PROTOCOL_VERSION, "Client offered version with major version under 3"); + if(client_offer.major_version() == 3 && client_offer.minor_version() == 0) + throw TLS_Exception(Alert::PROTOCOL_VERSION, "SSLv3 is not supported"); + + const Protocol_Version negotiated_version = + select_version(policy(), client_offer, + active_state ? active_state->version() : Protocol_Version(), + pending_state.client_hello()->sent_fallback_scsv()); + + const auto compression_methods = pending_state.client_hello()->compression_methods(); + if(!value_exists(compression_methods, uint8_t(0))) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Client did not offer NULL compression"); + secure_renegotiation_check(pending_state.client_hello()); pending_state.set_version(negotiated_version); @@ -511,6 +526,11 @@ void Server::process_certificate_msg(Server_Handshake_State& pending_state, const std::vector<uint8_t>& contents) { pending_state.client_certs(new Certificate(contents, policy())); + + // CERTIFICATE_REQUIRED would make more sense but BoGo expects handshake failure alert + if(pending_state.client_certs()->empty() && policy().require_client_certificate_authentication()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Policy requires client send a certificate, but it did not"); + pending_state.set_expected_next(CLIENT_KEX); } @@ -767,7 +787,7 @@ void Server::session_resume(Server_Handshake_State& pending_state, void Server::session_create(Server_Handshake_State& pending_state, bool have_session_ticket_key) { - std::map<std::string, std::vector<X509_Certificate> > cert_chains; + std::map<std::string, std::vector<X509_Certificate>> cert_chains; const std::string sni_hostname = pending_state.client_hello()->sni_hostname(); @@ -782,10 +802,10 @@ void Server::session_create(Server_Handshake_State& pending_state, * 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. + * anonymous/PSK operation. */ if(!cert_chains.empty()) - send_alert(Alert(Alert::UNRECOGNIZED_NAME)); + send_warning_alert(Alert::UNRECOGNIZED_NAME); } const uint16_t ciphersuite = choose_ciphersuite(policy(), pending_state.version(), @@ -857,7 +877,11 @@ void Server::session_create(Server_Handshake_State& pending_state, client_auth_CAs.insert(client_auth_CAs.end(), subjects.begin(), subjects.end()); } - if(!client_auth_CAs.empty() && pending_state.ciphersuite().signature_used()) + const bool request_cert = + (client_auth_CAs.empty() == false) || + policy().request_client_certificate_authentication(); + + if(request_cert && pending_state.ciphersuite().signature_used()) { pending_state.cert_req( new Certificate_Req(pending_state.handshake_io(), |