diff options
-rw-r--r-- | src/lib/tls/info.txt | 1 | ||||
-rw-r--r-- | src/lib/tls/tls_handshake_state.cpp | 140 | ||||
-rw-r--r-- | src/lib/tls/tls_handshake_state.h | 4 | ||||
-rw-r--r-- | src/lib/tls/tls_handshake_transitions.cpp | 176 | ||||
-rw-r--r-- | src/lib/tls/tls_handshake_transitions.h | 66 | ||||
-rw-r--r-- | src/tests/test_tls_handshake_transitions.cpp | 89 |
6 files changed, 338 insertions, 138 deletions
diff --git a/src/lib/tls/info.txt b/src/lib/tls/info.txt index 3ed102e9c..2cebae346 100644 --- a/src/lib/tls/info.txt +++ b/src/lib/tls/info.txt @@ -27,6 +27,7 @@ tls_version.h tls_handshake_hash.h tls_handshake_io.h tls_handshake_state.h +tls_handshake_transitions.h tls_reader.h tls_record.h tls_seq_numbers.h diff --git a/src/lib/tls/tls_handshake_state.cpp b/src/lib/tls/tls_handshake_state.cpp index 56c3b1b21..0fbce3750 100644 --- a/src/lib/tls/tls_handshake_state.cpp +++ b/src/lib/tls/tls_handshake_state.cpp @@ -76,104 +76,6 @@ const char* handshake_type_to_string(Handshake_Type type) "Unknown TLS handshake message type " + std::to_string(type)); } -namespace { - -uint32_t bitmask_for_handshake_type(Handshake_Type type) - { - switch(type) - { - case HELLO_VERIFY_REQUEST: - return (1 << 0); - - case HELLO_REQUEST: - return (1 << 1); - - case CLIENT_HELLO: - 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 NEW_SESSION_TICKET: - return (1 << 12); - - case HANDSHAKE_CCS: - return (1 << 13); - - case FINISHED: - return (1 << 14); - - // allow explicitly disabling new handshakes - case HANDSHAKE_NONE: - return 0; - } - - throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, - "Unknown TLS handshake message type " + std::to_string(type)); - } - -std::string handshake_mask_to_string(uint32_t mask, char combiner) - { - const Handshake_Type types[] = { - HELLO_VERIFY_REQUEST, - HELLO_REQUEST, - CLIENT_HELLO, - SERVER_HELLO, - CERTIFICATE, - CERTIFICATE_URL, - CERTIFICATE_STATUS, - SERVER_KEX, - CERTIFICATE_REQUEST, - SERVER_HELLO_DONE, - CERTIFICATE_VERIFY, - CLIENT_KEX, - NEW_SESSION_TICKET, - HANDSHAKE_CCS, - FINISHED - }; - - std::ostringstream o; - bool empty = true; - - for(auto&& t : types) - { - if(mask & bitmask_for_handshake_type(t)) - { - if(!empty) - o << combiner; - o << handshake_type_to_string(t); - empty = false; - } - } - - return o.str(); - } - -} /* * Initialize the SSL/TLS Handshake State @@ -313,57 +215,23 @@ void Handshake_State::compute_session_keys(const secure_vector<uint8_t>& resume_ void Handshake_State::confirm_transition_to(Handshake_Type handshake_msg) { - const uint32_t mask = bitmask_for_handshake_type(handshake_msg); - - m_hand_received_mask |= mask; - - const bool ok = (m_hand_expecting_mask & mask) != 0; // overlap? - - if(!ok) - { - const uint32_t seen_so_far = m_hand_received_mask & ~mask; - - std::ostringstream msg; - - msg << "Unexpected state transition in handshake got a " << handshake_type_to_string(handshake_msg); - - if(m_hand_expecting_mask == 0) - msg << " not expecting messages"; - else - msg << " expected " << handshake_mask_to_string(m_hand_expecting_mask, '|'); - - if(seen_so_far != 0) - msg << " seen " << handshake_mask_to_string(seen_so_far, '+'); - - throw Unexpected_Message(msg.str()); - } - - /* 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; + m_transitions.confirm_transition_to(handshake_msg); } void Handshake_State::set_expected_next(Handshake_Type handshake_msg) { - m_hand_expecting_mask |= bitmask_for_handshake_type(handshake_msg); + m_transitions.set_expected_next(handshake_msg); } bool Handshake_State::received_handshake_msg(Handshake_Type handshake_msg) const { - const uint32_t mask = bitmask_for_handshake_type(handshake_msg); - - return (m_hand_received_mask & mask) != 0; + return m_transitions.received_handshake_msg(handshake_msg); } std::pair<Handshake_Type, std::vector<uint8_t>> Handshake_State::get_next_handshake_msg() { - const bool expecting_ccs = - (bitmask_for_handshake_type(HANDSHAKE_CCS) & m_hand_expecting_mask) != 0; - - return m_handshake_io->get_next_record(expecting_ccs); + return m_handshake_io->get_next_record(m_transitions.change_cipher_spec_expected()); } std::vector<uint8_t> Handshake_State::session_ticket() const diff --git a/src/lib/tls/tls_handshake_state.h b/src/lib/tls/tls_handshake_state.h index 1cc22d029..ebd267677 100644 --- a/src/lib/tls/tls_handshake_state.h +++ b/src/lib/tls/tls_handshake_state.h @@ -11,6 +11,7 @@ #include <botan/internal/tls_handshake_hash.h> #include <botan/internal/tls_handshake_io.h> +#include <botan/internal/tls_handshake_transitions.h> #include <botan/internal/tls_session_key.h> #include <botan/tls_ciphersuite.h> #include <botan/tls_exceptn.h> @@ -176,8 +177,7 @@ class Handshake_State std::unique_ptr<Handshake_IO> m_handshake_io; - uint32_t m_hand_expecting_mask = 0; - uint32_t m_hand_received_mask = 0; + Handshake_Transitions m_transitions; Protocol_Version m_version; std::optional<Ciphersuite> m_ciphersuite; Session_Keys m_session_keys; diff --git a/src/lib/tls/tls_handshake_transitions.cpp b/src/lib/tls/tls_handshake_transitions.cpp new file mode 100644 index 000000000..9bf3611c1 --- /dev/null +++ b/src/lib/tls/tls_handshake_transitions.cpp @@ -0,0 +1,176 @@ +/* +* TLS Handshake State Transitions +* (C) 2004-2006,2011,2012 Jack Lloyd +* 2017 Harry Reimann, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/internal/tls_handshake_transitions.h> + +#include <botan/tls_exceptn.h> + +#include <sstream> + +namespace Botan::TLS { + +namespace { + +uint32_t bitmask_for_handshake_type(Handshake_Type type) + { + switch(type) + { + case HELLO_VERIFY_REQUEST: + return (1 << 0); + + case HELLO_REQUEST: + return (1 << 1); + + case CLIENT_HELLO: + 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 NEW_SESSION_TICKET: + return (1 << 12); + + case HANDSHAKE_CCS: + return (1 << 13); + + case FINISHED: + return (1 << 14); + + // allow explicitly disabling new handshakes + case HANDSHAKE_NONE: + return 0; + } + + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, + "Unknown TLS handshake message type " + std::to_string(type)); + } + +std::string handshake_mask_to_string(uint32_t mask, char combiner) + { + const Handshake_Type types[] = + { + HELLO_VERIFY_REQUEST, + HELLO_REQUEST, + CLIENT_HELLO, + SERVER_HELLO, + CERTIFICATE, + CERTIFICATE_URL, + CERTIFICATE_STATUS, + SERVER_KEX, + CERTIFICATE_REQUEST, + SERVER_HELLO_DONE, + CERTIFICATE_VERIFY, + CLIENT_KEX, + NEW_SESSION_TICKET, + HANDSHAKE_CCS, + FINISHED + }; + + std::ostringstream o; + bool empty = true; + + for(auto&& t : types) + { + if(mask & bitmask_for_handshake_type(t)) + { + if(!empty) + { o << combiner; } + o << handshake_type_to_string(t); + empty = false; + } + } + + return o.str(); + } + +} + +bool Handshake_Transitions::received_handshake_msg(Handshake_Type msg_type) const + { + const uint32_t mask = bitmask_for_handshake_type(msg_type); + + return (m_hand_received_mask & mask) != 0; + } + +void Handshake_Transitions::confirm_transition_to(Handshake_Type msg_type) + { + const uint32_t mask = bitmask_for_handshake_type(msg_type); + + m_hand_received_mask |= mask; + + const bool ok = (m_hand_expecting_mask & mask) != 0; // overlap? + + if(!ok) + { + const uint32_t seen_so_far = m_hand_received_mask & ~mask; + + std::ostringstream msg; + + msg << "Unexpected state transition in handshake got a " << handshake_type_to_string(msg_type); + + if(m_hand_expecting_mask == 0) + { msg << " not expecting messages"; } + else + { msg << " expected " << handshake_mask_to_string(m_hand_expecting_mask, '|'); } + + if(seen_so_far != 0) + { msg << " seen " << handshake_mask_to_string(seen_so_far, '+'); } + + throw Unexpected_Message(msg.str()); + } + + /* 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_Transitions::set_expected_next(Handshake_Type msg_type) + { + m_hand_expecting_mask |= bitmask_for_handshake_type(msg_type); + } + +void Handshake_Transitions::set_expected_next(const std::vector<Handshake_Type>& msg_types) + { + for (const auto type : msg_types) + { + set_expected_next(type); + } + } + +bool Handshake_Transitions::change_cipher_spec_expected() const + { + return (bitmask_for_handshake_type(HANDSHAKE_CCS) & m_hand_expecting_mask) != 0; + } + +} diff --git a/src/lib/tls/tls_handshake_transitions.h b/src/lib/tls/tls_handshake_transitions.h new file mode 100644 index 000000000..019ec3ca5 --- /dev/null +++ b/src/lib/tls/tls_handshake_transitions.h @@ -0,0 +1,66 @@ +/* +* TLS Handshake State Transitions +* (C) 2004-2006,2011,2012 Jack Lloyd +* 2017 Harry Reimann, Rohde & Schwarz Cybersecurity +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_HANDSHAKE_TRANSITIONS_H_ +#define BOTAN_TLS_HANDSHAKE_TRANSITIONS_H_ + +#include <vector> + +#include <botan/tls_magic.h> + +namespace Botan::TLS { + +/** + * Manages the expectations for incoming handshake messages in both TLS 1.2 and 1.3. + * This does not bear any knowledge about the actual state machine but is a mere + * helper to implement state transition validation. + */ +class BOTAN_TEST_API Handshake_Transitions + { + public: + /** + * 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); + + /** + * Record that we are expecting one of the enumerated message types next. + * Note that receiving any of the expected messages in `confirm_transition_to` + * resets _all_ the expectations. + * + * @param msg_types the message types + */ + void set_expected_next(const std::vector<Handshake_Type>& msg_types); + + /** + * Check whether a Change Cipher Spec must be expected + */ + bool change_cipher_spec_expected() const; + + private: + uint32_t m_hand_expecting_mask = 0; + uint32_t m_hand_received_mask = 0; + }; + +} + +#endif diff --git a/src/tests/test_tls_handshake_transitions.cpp b/src/tests/test_tls_handshake_transitions.cpp new file mode 100644 index 000000000..17415f159 --- /dev/null +++ b/src/tests/test_tls_handshake_transitions.cpp @@ -0,0 +1,89 @@ +/* +* (C) 2022 Jack Lloyd +* (C) 2022 Hannes Rantzsch, René Meusel - neXenio +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + +#if defined(BOTAN_HAS_TLS) + +#include <botan/internal/tls_handshake_transitions.h> + +namespace Botan_Tests { + +namespace { + +std::vector<Test::Result> test_handshake_state_transitions() + { + return { + CHECK("uninitialized expects nothing", [](Test::Result& result) { + Botan::TLS::Handshake_Transitions ht; + result.confirm("CCS is not expected by default", !ht.change_cipher_spec_expected()); + + result.confirm("no messages were received", !ht.received_handshake_msg(Botan::TLS::Handshake_Type::CLIENT_HELLO)); + result.test_throws("no expectations set, always throws", [&] { + ht.confirm_transition_to(Botan::TLS::Handshake_Type::CLIENT_HELLO); + }); + }), + + CHECK("expect exactly one message", [](Test::Result& result) { + Botan::TLS::Handshake_Transitions ht; + ht.set_expected_next(Botan::TLS::Handshake_Type::CLIENT_HELLO); + + result.test_no_throw("client hello met expectation", [&] { + ht.confirm_transition_to(Botan::TLS::Handshake_Type::CLIENT_HELLO); + }); + + result.confirm("received client hello", ht.received_handshake_msg(Botan::TLS::Handshake_Type::CLIENT_HELLO)); + + result.test_throws("confirmation resets expectations", [&] { + ht.confirm_transition_to(Botan::TLS::Handshake_Type::CLIENT_HELLO); + }); + }), + + CHECK("expect exactly one message but don't satisfy it", [](Test::Result& result) + { + Botan::TLS::Handshake_Transitions ht; + ht.set_expected_next(Botan::TLS::Handshake_Type::CLIENT_HELLO); + + result.test_throws("server hello does not meet expectation", [&]{ + ht.confirm_transition_to(Botan::TLS::Handshake_Type::SERVER_HELLO); + }); + }), + + CHECK("two expectations can be fulfilled", [](Test::Result& result) + { + Botan::TLS::Handshake_Transitions ht; + ht.set_expected_next({Botan::TLS::Handshake_Type::CERTIFICATE_REQUEST,Botan::TLS::Handshake_Type::CERTIFICATE}); + + auto ht2 = ht; // copying, as confirmation reset the object's superposition + + result.test_no_throw("CERTIFICATE", [&] { + ht.confirm_transition_to(Botan::TLS::Handshake_Type::CERTIFICATE); + }); + result.confirm("received CERTIFICATE", ht.received_handshake_msg(Botan::TLS::Handshake_Type::CERTIFICATE)); + + result.test_no_throw("CERTIFICATE_REQUEST", [&] { + ht2.confirm_transition_to(Botan::TLS::Handshake_Type::CERTIFICATE_REQUEST); + }); + result.confirm("received CERTIFICATE_REQUEST", ht2.received_handshake_msg(Botan::TLS::Handshake_Type::CERTIFICATE_REQUEST)); + }), + + CHECK("expect CCS", [](Test::Result& result) + { + Botan::TLS::Handshake_Transitions ht; + ht.set_expected_next(Botan::TLS::Handshake_Type::HANDSHAKE_CCS); + result.confirm("CCS expected", ht.change_cipher_spec_expected()); + }), + }; + } + +} // namespace + +BOTAN_REGISTER_TEST_FN("tls", "tls_handshake_transitions", + test_handshake_state_transitions); +} + +#endif |