From 0ab571f62c5a2b3f919af1a038a35c307679b580 Mon Sep 17 00:00:00 2001 From: Hannes Rantzsch Date: Tue, 8 Jun 2021 07:19:52 +0200 Subject: [PATCH] Limited TLS 1.3 client implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * replace handshake protocol internals Add TLS 1.3 specific Handshake_Layer, Handshake_State, Handshake_Transitions, Transcript_Hash to replace functionality of TLS 1.2 Handshake_State. Related refactorings and module rearrangements. * handling of Hello Retry Request * handling alerts * ensure handshake messages are not interleaved * implement Exporters (RFC8446 7.5) * Implement middlebox compatibility mode (RFC 8446 Appendix D.4) * handle protocol version downgrade * Post-Handshake-Message Key_Update * OCSP stapling * update of traffic secrets via a user-facing API * Record_Size_Limit extension for TLS 1.3 * BoGo Tests integration/fixes for TLS 1.3 ... we rebased the changes in jack/runner-20210401 to the current boringssl origin master (currently on reneme/boringssl) ... tests that are not applicable (yet) were disabled * prepend dummy ccs record for any second flight * too large decrypted plaintext * client hello version when renegotiating 1.2 * don't try 1.3 if we have a 1.2 session to resume * server selected version handling * ALPN handling in TLS 1.3 * segfault on empty certificate * user_canceled should be ignored * handle record padding * detect session ID downgrade attack * illegal compression method shall be 'decode error' * add missing check for unusable cipher suites * less scrutiny when checking version of initial rcv'd record * memory reservations for large records * detect unexpected extensions in EE * allow for better validation of OCSP responses * check for forbidden extensions in EE msg * validate allowed extensions in cert msg * empty Encrypted Extensions are not allowed * more explicit validation of Hello Retry Request * check signature algo in certificate * certificate constraint checking too loose * validate handshake type byte * refuse unprotected traffic after kex * detect bad alerts * support ALPN in TLS 1.3 * allow 1.2 warning alerts in 1.3 Co-authored-by: René Meusel --- src/bogo_shim/bogo_shim.cpp | 182 ++- src/bogo_shim/config.json | 148 ++- src/cli/tls_client.cpp | 25 +- src/cli/tls_utils.cpp | 1 + src/lib/tls/asio/asio_stream.h | 2 +- src/lib/tls/msg_cert_req.cpp | 1 - src/lib/tls/msg_cert_verify.cpp | 132 +- src/lib/tls/msg_certificate_13.cpp | 215 ++++ src/lib/tls/msg_client_hello.cpp | 122 +- src/lib/tls/msg_encrypted_extensions.cpp | 66 + src/lib/tls/msg_finished.cpp | 16 + src/lib/tls/msg_key_update.cpp | 46 + src/lib/tls/msg_server_hello.cpp | 337 ++++- src/lib/tls/msg_session_ticket.cpp | 14 + src/lib/tls/tls12/tls_channel_impl_12.cpp | 5 + src/lib/tls/tls12/tls_channel_impl_12.h | 8 + src/lib/tls/tls12/tls_client_impl_12.cpp | 23 + src/lib/tls/tls12/tls_client_impl_12.h | 2 + src/lib/tls/tls12/tls_handshake_state.cpp | 35 + src/lib/tls/tls12/tls_handshake_state.h | 1 - src/lib/tls/tls12/tls_server_impl_12.cpp | 1 + src/lib/tls/tls13/info.txt | 21 + src/lib/tls/tls13/tls_channel_impl_13.cpp | 398 ++++++ src/lib/tls/tls13/tls_channel_impl_13.h | 215 ++++ src/lib/tls/tls13/tls_cipher_state.cpp | 445 +++++++ src/lib/tls/tls13/tls_cipher_state.h | 287 +++++ src/lib/tls/tls13/tls_client_impl_13.cpp | 469 +++++++ src/lib/tls/tls13/tls_client_impl_13.h | 97 ++ src/lib/tls/tls13/tls_handshake_layer_13.cpp | 197 +++ src/lib/tls/tls13/tls_handshake_layer_13.h | 93 ++ src/lib/tls/tls13/tls_handshake_state_13.cpp | 64 + src/lib/tls/tls13/tls_handshake_state_13.h | 135 ++ src/lib/tls/tls13/tls_record_layer_13.cpp | 381 ++++++ src/lib/tls/tls13/tls_record_layer_13.h | 121 ++ src/lib/tls/tls13/tls_transcript_hash_13.cpp | 108 ++ src/lib/tls/tls13/tls_transcript_hash_13.h | 92 ++ src/lib/tls/tls_algos.cpp | 96 ++ src/lib/tls/tls_algos.h | 18 + src/lib/tls/tls_callbacks.h | 12 + src/lib/tls/tls_channel.h | 8 + src/lib/tls/tls_channel_impl.h | 69 + src/lib/tls/tls_ciphersuite.cpp | 13 +- src/lib/tls/tls_client.cpp | 45 +- src/lib/tls/tls_client.h | 2 + src/lib/tls/tls_extensions.cpp | 216 ++++ src/lib/tls/tls_extensions.h | 194 +++ .../tls/tls_extensions_cert_status_req.cpp | 32 +- src/lib/tls/tls_extensions_key_share.cpp | 553 ++++++++ src/lib/tls/tls_handshake_transitions.cpp | 17 +- src/lib/tls/tls_magic.h | 21 +- src/lib/tls/tls_messages.h | 322 ++++- src/lib/tls/tls_policy.cpp | 70 +- src/lib/tls/tls_policy.h | 76 ++ src/lib/tls/tls_server.cpp | 5 + src/lib/tls/tls_server.h | 2 + src/lib/tls/tls_suite_info.cpp | 9 +- src/lib/tls/tls_text_policy.cpp | 43 + src/lib/tls/tls_version.cpp | 3 + src/lib/tls/tls_version.h | 4 + src/scripts/ci/setup_gh_actions.sh | 3 +- src/scripts/ci_build.py | 2 +- src/scripts/test_cli.py | 3 +- src/scripts/tls_suite_info.py | 25 +- src/tests/data/tls-policy/bsi.txt | 1 + src/tests/data/tls-policy/compat.txt | 1 + src/tests/data/tls-policy/datagram.txt | 1 + src/tests/data/tls-policy/default.txt | 1 + src/tests/data/tls-policy/default_tls13.txt | 23 + src/tests/data/tls-policy/rfc8448_1rtt.txt | 27 + src/tests/data/tls-policy/rfc8448_compat.txt | 27 + src/tests/data/tls-policy/rfc8448_hrr.txt | 27 + src/tests/data/tls-policy/strict.txt | 1 + src/tests/data/tls-policy/strict_tls13.txt | 23 + src/tests/data/tls-policy/suiteb_128.txt | 1 + src/tests/data/tls-policy/suiteb_192.txt | 1 + src/tests/data/tls/client_hello.vec | 18 +- src/tests/data/tls_13/server_hello.vec | 72 ++ .../generation/key_share_CH_offers.vec | 81 ++ .../data/tls_extensions/parsing/cookie.vec | 18 + .../tls_extensions/parsing/key_share_CH.vec | 21 + .../tls_extensions/parsing/key_share_HRR.vec | 12 + .../tls_extensions/parsing/key_share_SH.vec | 17 + .../parsing/signature_algorithms_cert.vec | 27 + .../parsing/supported_groups.vec | 26 + .../parsing/supported_versions.vec | 23 + src/tests/test_tls.cpp | 7 +- src/tests/test_tls_cipher_state.cpp | 347 +++++ src/tests/test_tls_handshake_layer_13.cpp | 412 ++++++ src/tests/test_tls_handshake_state_13.cpp | 122 ++ src/tests/test_tls_messages.cpp | 281 +++++ src/tests/test_tls_record_layer_13.cpp | 978 +++++++++++++++ src/tests/test_tls_rfc8448.cpp | 1116 +++++++++++++++++ src/tests/test_tls_transcript_hash_13.cpp | 157 +++ src/tests/unit_tls_policy.cpp | 24 + 94 files changed, 10111 insertions(+), 150 deletions(-) create mode 100644 src/lib/tls/msg_certificate_13.cpp create mode 100644 src/lib/tls/msg_encrypted_extensions.cpp create mode 100644 src/lib/tls/msg_key_update.cpp create mode 100644 src/lib/tls/tls13/info.txt create mode 100644 src/lib/tls/tls13/tls_channel_impl_13.cpp create mode 100644 src/lib/tls/tls13/tls_channel_impl_13.h create mode 100644 src/lib/tls/tls13/tls_cipher_state.cpp create mode 100644 src/lib/tls/tls13/tls_cipher_state.h create mode 100644 src/lib/tls/tls13/tls_client_impl_13.cpp create mode 100644 src/lib/tls/tls13/tls_client_impl_13.h create mode 100644 src/lib/tls/tls13/tls_handshake_layer_13.cpp create mode 100644 src/lib/tls/tls13/tls_handshake_layer_13.h create mode 100644 src/lib/tls/tls13/tls_handshake_state_13.cpp create mode 100644 src/lib/tls/tls13/tls_handshake_state_13.h create mode 100644 src/lib/tls/tls13/tls_record_layer_13.cpp create mode 100644 src/lib/tls/tls13/tls_record_layer_13.h create mode 100644 src/lib/tls/tls13/tls_transcript_hash_13.cpp create mode 100644 src/lib/tls/tls13/tls_transcript_hash_13.h create mode 100644 src/lib/tls/tls_extensions_key_share.cpp create mode 100644 src/tests/data/tls-policy/default_tls13.txt create mode 100644 src/tests/data/tls-policy/rfc8448_1rtt.txt create mode 100644 src/tests/data/tls-policy/rfc8448_compat.txt create mode 100644 src/tests/data/tls-policy/rfc8448_hrr.txt create mode 100644 src/tests/data/tls-policy/strict_tls13.txt create mode 100644 src/tests/data/tls_13/server_hello.vec create mode 100644 src/tests/data/tls_extensions/generation/key_share_CH_offers.vec create mode 100644 src/tests/data/tls_extensions/parsing/cookie.vec create mode 100644 src/tests/data/tls_extensions/parsing/key_share_CH.vec create mode 100644 src/tests/data/tls_extensions/parsing/key_share_HRR.vec create mode 100644 src/tests/data/tls_extensions/parsing/key_share_SH.vec create mode 100644 src/tests/data/tls_extensions/parsing/signature_algorithms_cert.vec create mode 100644 src/tests/data/tls_extensions/parsing/supported_groups.vec create mode 100644 src/tests/data/tls_extensions/parsing/supported_versions.vec create mode 100644 src/tests/test_tls_cipher_state.cpp create mode 100644 src/tests/test_tls_handshake_layer_13.cpp create mode 100644 src/tests/test_tls_handshake_state_13.cpp create mode 100644 src/tests/test_tls_record_layer_13.cpp create mode 100644 src/tests/test_tls_rfc8448.cpp create mode 100644 src/tests/test_tls_transcript_hash_13.cpp diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp index 4cc7bca2d07..f1991ba2ad0 100644 --- a/src/bogo_shim/bogo_shim.cpp +++ b/src/bogo_shim/bogo_shim.cpp @@ -17,12 +17,16 @@ #include #include #include +#include #include #include #include #include #include #include +#include +#include + #include #include #include @@ -57,7 +61,7 @@ void shim_log(const std::string& s) static FILE* log = std::fopen("/tmp/bogo_shim.log", "w"); struct timeval tv; ::gettimeofday(&tv, nullptr); - std::fprintf(log, "%lld.%lu: %s\n", static_cast(tv.tv_sec), tv.tv_usec, s.c_str()); + std::fprintf(log, "%lld.%lu: %s\n", static_cast(tv.tv_sec), static_cast(tv.tv_usec), s.c_str()); std::fflush(log); } } @@ -82,9 +86,13 @@ std::string map_to_bogo_error(const std::string& e) { "Bad length in hello verify request", ":DECODE_ERROR:" }, { "Bad lengths in DTLS header", ":BAD_HANDSHAKE_RECORD:" }, { "Bad signature on server key exchange", ":BAD_SIGNATURE:" }, + { "Server certificate verification failed", ":BAD_SIGNATURE:" }, + { "compression is not supported in TLS 1.3", ":DECODE_ERROR:" }, + { "Cookie length must be at least 1 byte", ":DECODE_ERROR:" }, { "Bad size (1) for TLS alert message", ":BAD_ALERT:" }, { "Bad size (4) for TLS alert message", ":BAD_ALERT:" }, { "CERTIFICATE decoding failed with PEM: No PEM header found", ":CANNOT_PARSE_LEAF_CERT:" }, + { "Certificate usage constraints do not allow signing", ":KEY_USAGE_BIT_INCORRECT:" }, { "Can't agree on a ciphersuite with client", ":NO_SHARED_CIPHER:" }, { "Can't interleave application and handshake data", ":UNEXPECTED_RECORD:" }, { "Certificate chain exceeds policy specified maximum size", ":EXCESSIVE_MESSAGE_SIZE:" }, @@ -98,16 +106,18 @@ std::string map_to_bogo_error(const std::string& e) { "Client offered DTLS version with major version 0xFF", ":UNSUPPORTED_PROTOCOL:" }, { "Client offered SSLv3 which is not supported", ":UNSUPPORTED_PROTOCOL:" }, { "Client offered TLS version with major version under 3", ":UNSUPPORTED_PROTOCOL:" }, + { "Expected server hello of (D)TLS 1.2 or lower", ":UNSUPPORTED_PROTOCOL:" }, + { "Protocol version was not offered", ":UNSUPPORTED_PROTOCOL:" }, { "Client policy prohibits insecure renegotiation", ":RENEGOTIATION_MISMATCH:" }, { "Client policy prohibits renegotiation", ":NO_RENEGOTIATION:" }, { "Client resumed extended ms session without sending extension", ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:" }, { "Client sent plaintext HTTP proxy CONNECT request instead of TLS handshake", ":HTTPS_PROXY_REQUEST:" }, { "Client sent plaintext HTTP request instead of TLS handshake", ":HTTP_REQUEST:" }, { "Client signalled fallback SCSV, possible attack", ":INAPPROPRIATE_FALLBACK:" }, - { "Client version DTLS v1.0 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, - { "Client version TLS v1.0 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, { "Client version TLS v1.1 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, + { "No shared TLS version based on supported versions extension", ":UNSUPPORTED_PROTOCOL:" }, { "Client: No certificates sent by server", ":DECODE_ERROR:" }, + { "No certificates sent by server", ":PEER_DID_NOT_RETURN_A_CERTIFICATE:" }, { "Counterparty sent inconsistent key and sig types", ":WRONG_SIGNATURE_TYPE:" }, { "Downgrade attack detected", ":TLS13_DOWNGRADE:" }, { "Empty ALPN protocol not allowed", ":PARSE_TLSEXT:" }, @@ -117,9 +127,13 @@ std::string map_to_bogo_error(const std::string& e) { "Have data remaining in buffer after ClientHello", ":EXCESS_HANDSHAKE_DATA:" }, { "Have data remaining in buffer after Finished", ":EXCESS_HANDSHAKE_DATA:" }, { "Have data remaining in buffer after ServerHelloDone", ":EXCESS_HANDSHAKE_DATA:" }, + { "Hello Retry Request does not request any changes to Client Hello", ":EMPTY_HELLO_RETRY_REQUEST:" }, + { "Unexpected additional handshake message data found in record", ":EXCESS_HANDSHAKE_DATA:" }, { "Inconsistent length in certificate request", ":DECODE_ERROR:" }, + { "unexpected key_update parameter", ":DECODE_ERROR:" }, { "Inconsistent values in fragmented DTLS handshake header", ":FRAGMENT_MISMATCH:" }, { "Invalid CertificateRequest: Length field outside parameters", ":DECODE_ERROR:" }, + { "Invalid ServerHello: Length field outside parameters", ":DECODE_ERROR:" }, { "Invalid CertificateVerify: Extra bytes at end of message", ":DECODE_ERROR:" }, { "Invalid Certificate_Status: invalid length field", ":DECODE_ERROR:" }, { "Invalid ChangeCipherSpec", ":BAD_CHANGE_CIPHER_SPEC:" }, @@ -130,6 +144,7 @@ std::string map_to_bogo_error(const std::string& e) { "Invalid authentication tag: ChaCha20Poly1305 tag check failed", ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" }, { "Invalid authentication tag: GCM tag check failed", ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" }, { "Message authentication failure", ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" }, + { "No content type found in encrypted record", ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" }, { "No shared DTLS version", ":UNSUPPORTED_PROTOCOL:" }, { "No shared TLS version", ":UNSUPPORTED_PROTOCOL:" }, { "OS2ECP: Unknown format type 251", ":BAD_ECPOINT:" }, @@ -138,6 +153,7 @@ std::string map_to_bogo_error(const std::string& e) { "Policy refuses to accept signing with any hash supported by peer", ":NO_COMMON_SIGNATURE_ALGORITHMS:" }, { "Policy requires client send a certificate, but it did not", ":PEER_DID_NOT_RETURN_A_CERTIFICATE:" }, { "Received a record that exceeds maximum size", ":ENCRYPTED_LENGTH_TOO_LONG:" }, + { "Received an encrypted record that exceeds maximum size", ":ENCRYPTED_LENGTH_TOO_LONG:" }, { "Received application data after connection closure", ":APPLICATION_DATA_ON_SHUTDOWN:" }, { "Received handshake data after connection closure", ":NO_RENEGOTIATION:" }, { "Received unexpected record version in initial record", ":WRONG_VERSION_NUMBER:" }, @@ -148,13 +164,16 @@ std::string map_to_bogo_error(const std::string& e) { "Server changed its mind about extended master secret", ":RENEGOTIATION_EMS_MISMATCH:" }, { "Server changed its mind about secure renegotiation", ":RENEGOTIATION_MISMATCH:" }, { "Server changed version after renegotiation", ":WRONG_SSL_VERSION:" }, - { "Server downgraded version after renegotiation", ":WRONG_SSL_VERSION:" }, { "Server policy prohibits renegotiation", ":NO_RENEGOTIATION:" }, { "Server replied using a ciphersuite not allowed in version it offered", ":WRONG_CIPHER_RETURNED:" }, { "Server replied with an invalid version", ":UNSUPPORTED_PROTOCOL:" }, + { "server changed its chosen ciphersuite", ":WRONG_CIPHER_RETURNED:" }, { "Server replied with DTLS-SRTP alg we did not send", ":BAD_SRTP_PROTECTION_PROFILE_LIST:" }, { "Server replied with ciphersuite we didn't send", ":WRONG_CIPHER_RETURNED:" }, - { "Server replied with later version than client offered", ":UNSUPPORTED_PROTOCOL:" }, + { "Server replied with an invalid version", ":UNSUPPORTED_PROTOCOL:" }, // bogus version from "ServerBogusVersion" + { "Server version SSL v3 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, // "NoSSL3-Client-Unsolicited" + { "legacy_version 'TLS v1.4' is not allowed", ":DECODE_ERROR:" }, + { "legacy_version 'Unknown 18.52' is not allowed", ":UNSUPPORTED_PROTOCOL:" }, { "Server replied with non-null compression method", ":UNSUPPORTED_COMPRESSION_ALGORITHM:" }, { "Server replied with some unknown ciphersuite", ":UNKNOWN_CIPHER_RETURNED:" }, { "Server replied with unsupported extensions: 0", ":UNEXPECTED_EXTENSION:" }, @@ -166,7 +185,16 @@ std::string map_to_bogo_error(const std::string& e) { "Server resumed session but added extended master secret", ":RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION:" }, { "Server resumed session but with wrong version", ":OLD_SESSION_VERSION_NOT_RETURNED:" }, { "Server sent ECC curve prohibited by policy", ":WRONG_CURVE:" }, + { "group was not advertised as supported", ":WRONG_CURVE:" }, + { "group was already offered", ":WRONG_CURVE:" }, + { "Server selected an unexpected key exchange group.", ":WRONG_CURVE:" }, + { "TLS 1.3 Server Hello selected a different version", ":SECOND_SERVERHELLO_VERSION_MISMATCH:" }, + { "Version downgrade received after Hello Retry", ":SECOND_SERVERHELLO_VERSION_MISMATCH:" }, + { "protected change cipher spec received", ":UNEXPECTED_RECORD:" }, { "Server sent an unsupported extension", ":UNEXPECTED_EXTENSION:" }, + { "Unexpected extension received", ":UNEXPECTED_EXTENSION:" }, + { "server hello must contain key exchange information", ":MISSING_KEY_SHARE:"}, + { "Peer sent duplicated extensions", ":DUPLICATE_EXTENSION:" }, { "Server sent bad values for secure renegotiation", ":RENEGOTIATION_MISMATCH:" }, { "Server version DTLS v1.0 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, { "Server version TLS v1.0 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, @@ -176,6 +204,7 @@ std::string map_to_bogo_error(const std::string& e) { "Simulating cert verify callback failure", ":CERT_CB_ERROR:" }, { "Simulating failure from OCSP response callback", ":OCSP_CB_ERROR:" }, { "TLS plaintext record is larger than allowed maximum", ":DATA_LENGTH_TOO_LONG:" }, + { "Received an encrypted record that exceeds maximum plaintext size", ":DATA_LENGTH_TOO_LONG:" }, { "TLS record type had unexpected value", ":UNEXPECTED_RECORD:" }, { "TLS record version had unexpected value", ":WRONG_VERSION_NUMBER:" }, { "TLS signature extension did not allow for RSA/SHA-256 signature", ":WRONG_SIGNATURE_TYPE:", }, @@ -187,16 +216,21 @@ std::string map_to_bogo_error(const std::string& e) { "Unexpected state transition in handshake got a certificate_status expected certificate seen server_hello", ":UNEXPECTED_MESSAGE:" }, { "Unexpected state transition in handshake got a change_cipher_spec expected certificate_verify seen client_hello+certificate+client_key_exchange", ":UNEXPECTED_RECORD:" }, { "Unexpected state transition in handshake got a change_cipher_spec expected client_key_exchange seen client_hello", ":UNEXPECTED_RECORD:" }, - { "Unexpected state transition in handshake got a change_cipher_spec expected new_session_ticket seen server_hello+certificate+certificate_status+server_key_exchange+server_hello_done", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake got a change_cipher_spec expected new_session_ticket seen server_hello+certificate+server_key_exchange+server_hello_done", ":UNEXPECTED_RECORD:" }, { "Unexpected state transition in handshake got a client_key_exchange expected certificate seen client_hello", ":UNEXPECTED_MESSAGE:" }, { "Unexpected state transition in handshake got a finished expected change_cipher_spec seen client_hello", ":UNEXPECTED_RECORD:" }, { "Unexpected state transition in handshake got a finished expected change_cipher_spec seen client_hello+client_key_exchange", ":UNEXPECTED_RECORD:" }, { "Unexpected state transition in handshake got a finished expected change_cipher_spec seen server_hello", ":UNEXPECTED_RECORD:" }, - { "Unexpected state transition in handshake got a finished expected change_cipher_spec seen server_hello+certificate+certificate_status+server_key_exchange+server_hello_done+new_session_ticket", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake got a finished expected change_cipher_spec seen server_hello+certificate+server_key_exchange+server_hello_done+new_session_ticket", ":UNEXPECTED_RECORD:" }, { "Unexpected state transition in handshake got a hello_request expected server_hello", ":UNEXPECTED_MESSAGE:" }, { "Unexpected state transition in handshake got a server_hello_done expected server_key_exchange seen server_hello+certificate+certificate_status", ":UNEXPECTED_MESSAGE:" }, { "Unexpected state transition in handshake got a server_key_exchange expected certificate_request|server_hello_done seen server_hello+certificate+certificate_status", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake got a server_hello_done expected server_key_exchange seen server_hello+certificate", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake got a server_key_exchange expected certificate seen server_hello", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake got a server_key_exchange expected certificate_request|server_hello_done seen server_hello+certificate", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake got a hello_retry_request expected server_hello", ":UNEXPECTED_MESSAGE:" }, { "Unexpected state transition in handshake got a server_key_exchange not expecting messages", ":BAD_HELLO_REQUEST:" }, + { "Unexpected state transition in handshake got a finished expected certificate_verify seen server_hello+certificate+encrypted_extensions", ":BAD_HELLO_REQUEST:" }, { "Unknown TLS handshake message type 43", ":UNEXPECTED_MESSAGE:" }, { "Unknown TLS handshake message type 44", ":UNEXPECTED_MESSAGE:" }, { "Unknown TLS handshake message type 45", ":UNEXPECTED_MESSAGE:" }, @@ -210,7 +244,19 @@ std::string map_to_bogo_error(const std::string& e) { "Unknown TLS handshake message type 6", ":UNEXPECTED_MESSAGE:" }, { "Unknown TLS handshake message type 62", ":UNEXPECTED_MESSAGE:" }, { "Unknown TLS handshake message type 64", ":UNEXPECTED_MESSAGE:" }, + { "Unknown handshake message received", ":UNEXPECTED_MESSAGE:" }, + { "Unknown post-handshake message received", ":UNEXPECTED_MESSAGE:" }, { "signature_algorithm_of_scheme: Unknown signature algorithm enum", ":WRONG_SIGNATURE_TYPE:" }, + { "Unexpected session ID during downgrade", ":SERVER_ECHOED_INVALID_SESSION_ID:" }, + { "Encrypted Extensions contained an extension that is not allowed", ":ERROR_PARSING_EXTENSION:" }, + { "Encrypted Extensions contained an extension that was not offered", ":UNEXPECTED_EXTENSION:" }, + { "Certificate Entry contained an extension that is not allowed", ":UNEXPECTED_EXTENSION:" }, + { "Certificate Entry contained an extension that was not offered", ":UNEXPECTED_EXTENSION:" }, + { "Server Hello contained an extension that is not allowed", ":UNEXPECTED_EXTENSION:" }, + { "Hello Retry Request contained an extension that is not allowed", ":UNEXPECTED_EXTENSION:" }, + { "Signature algorithm does not match certificate's public key", ":WRONG_SIGNATURE_TYPE:" }, + { "unprotected record received where protected traffic was expected", ":INVALID_OUTER_RECORD_TYPE:" }, + { "Error alert not marked fatal", ":BAD_ALERT:" }, }; auto err_map_i = err_map.find(e); @@ -588,6 +634,7 @@ std::unique_ptr parse_options(char* argv[]) //"expect-tls13-downgrade", "expect-verify-result", "expect-no-hrr", + "expect-hrr", //"export-traffic-secrets", "fail-cert-callback", //"fail-ddos-callback", @@ -606,14 +653,14 @@ std::unique_ptr parse_options(char* argv[]) "install-ddos-callback", "is-handshaker-supported", //"jdk11-workaround", - //"key-update", + "key-update", "no-op-extra-handshake", "no-rsa-pss-rsae-certs", "no-ticket", "no-tls1", "no-tls11", "no-tls12", - "no-tls13", // implict due to 1.3 not being implemented + "no-tls13", "on-resume-no-ticket", //"on-resume-verify-fail", //"partial-write", @@ -675,7 +722,7 @@ std::unique_ptr parse_options(char* argv[]) "test-name", "use-client-ca-list", //"send-channel-id", - //"write-settings", + "write-settings", }; const std::set bogo_shim_base64_opts = { @@ -863,9 +910,16 @@ class Shim_Policy final : public Botan::TLS::Policy if(m_args.option_used("curves")) { std::vector groups; + + // upcall to base class to find the groups actually supported by + // this Botan build + const auto supported_groups = Botan::TLS::Policy::key_exchange_groups(); + for(size_t pref : m_args.get_int_vec_opt("curves")) { - groups.push_back(static_cast(pref)); + const auto group = static_cast(pref); + if(std::find(supported_groups.cbegin(), supported_groups.cend(), group) != supported_groups.end()) + groups.push_back(group); } return groups; @@ -942,6 +996,11 @@ class Shim_Policy final : public Botan::TLS::Policy return !m_args.flag_set("dtls") && !m_args.flag_set("no-tls12") && allow_version(Botan::TLS::Protocol_Version::TLS_V12); } + bool allow_tls13() const override + { + return !m_args.flag_set("dtls") && !m_args.flag_set("no-tls13") && allow_version(Botan::TLS::Protocol_Version::TLS_V13); + } + bool allow_dtls12() const override { return m_args.flag_set("dtls") && !m_args.flag_set("no-tls12") && allow_version(Botan::TLS::Protocol_Version::DTLS_V12); @@ -1000,6 +1059,10 @@ class Shim_Policy final : public Botan::TLS::Policy if(m_args.flag_set("decline-ocsp-callback")) return false; } + else + if(!m_args.flag_set("enable-ocsp-stapling")) + return false; + return true; } @@ -1032,12 +1095,33 @@ class Shim_Policy final : public Botan::TLS::Policy return m_args.get_int_opt_or_else("max-cert-list", 0); } + bool tls_13_middlebox_compatibility_mode() const override + { + // These tests expect the client to send an alert in return of a malformed TLS 1.2 server hello. + // However, our TLS 1.3 implementation produces an alert without downgrading to TLS 1.2 first. + // In compatibility mode this prepends a CCS, which BoGo does not expect to read. + const std::vector alert_after_server_hello = { + "DuplicateExtensionClient-TLS-TLS12", + "WrongMessageType-ServerHello-TLS", + "SendServerHelloAsHelloRetryRequest", + "TrailingMessageData-ServerHello-TLS", + "NoSSL3-Client-Unsolicited", + "Client-TooLongSessionID", + "MinimumVersion-Client-TLS13-TLS12-TLS", + "MinimumVersion-Client2-TLS13-TLS12-TLS", + }; + if(Botan::value_exists(alert_after_server_hello, m_args.test_name())) + return false; + + return true; + } + private: const Shim_Arguments& m_args; size_t m_sessions; }; -std::vector Shim_Policy::ciphersuite_list(Botan::TLS::Protocol_Version) const +std::vector Shim_Policy::ciphersuite_list(Botan::TLS::Protocol_Version version) const { std::vector ciphersuite_codes; @@ -1068,6 +1152,20 @@ std::vector Shim_Policy::ciphersuite_list(Botan::TLS::Protocol_Version for(auto i = ciphersuites.rbegin(); i != ciphersuites.rend(); ++i) { const auto suite = *i; + const bool is_tls13_suite = + suite.kex_method() == Botan::TLS::Kex_Algo::UNDEFINED && + suite.auth_method() == Botan::TLS::Auth_Method::UNDEFINED; + + const bool is_client = !m_args.flag_set("server"); + + // client should only offer suites appropriate to version + if(is_client && (version == Botan::TLS::Protocol_Version::TLS_V13) != is_tls13_suite) + continue; + + // tls 1.3 server is nyi + if (!is_client && is_tls13_suite) + continue; + // Can we use it? if(suite.valid() == false) continue; @@ -1225,6 +1323,14 @@ class Shim_Callbacks final : public Botan::TLS::Callbacks { shim_log("sending record of len " + std::to_string(size)); + if(m_args.option_used("write-settings")) + { + // TODO: the transcript option should probably be used differently + std::cout << ">>>" << std::endl + << Botan::hex_encode(data, size) << std::endl + << ">>>" << std::endl; + } + if(m_is_datagram) { std::vector packet(size + 5); @@ -1325,6 +1431,19 @@ class Shim_Callbacks final : public Botan::TLS::Callbacks } } + std::optional tls_parse_ocsp_response(const std::vector& raw_response) override + { + if(m_args.option_used("expect-ocsp-response") && + m_args.get_b64_opt("expect-ocsp-response") != raw_response) + { + shim_exit_with_error("unexpected OCSP response"); + } + + // Bogo uses invalid dummy OCSP responses. Don't even bother trying to + // decode them. + return std::nullopt; + } + std::string tls_server_choose_app_protocol(const std::vector& client_protos) override { if(client_protos.empty()) @@ -1491,6 +1610,12 @@ class Shim_Callbacks final : public Botan::TLS::Callbacks m_channel->close(); } + + if(m_args.flag_set("key-update")) + { + shim_log("Updating traffic keys without asking for reciprocation"); + m_channel->update_traffic_keys(false /* don't request reciprocal update */); + } } private: @@ -1522,7 +1647,6 @@ int main(int /*argc*/, char* argv[]) const size_t resume_count = args->get_int_opt_or_else("resume-count", 0); const bool is_server = args->flag_set("server"); const bool is_datagram = args->flag_set("dtls"); - const size_t buf_size = args->get_int_opt_or_else("read-size", 18*1024); Botan::ChaCha_RNG rng(Botan::secure_vector(64)); @@ -1531,7 +1655,9 @@ int main(int /*argc*/, char* argv[]) for(size_t i = 0; i != resume_count+1; ++i) { - Shim_Socket socket("localhost", port); + + auto execute_test = [&](const std::string& hostname) { + Shim_Socket socket(hostname, port); shim_log("Connection " + std::to_string(i+1) + "/" + std::to_string(resume_count+1)); @@ -1549,7 +1675,7 @@ int main(int /*argc*/, char* argv[]) Botan::TLS::Protocol_Version offer_version = policy.latest_supported_version(is_datagram); shim_log("Offering " + offer_version.to_string()); - std::string host_name = args->get_string_opt_or_else("host-name", "localhost"); + std::string host_name = args->get_string_opt_or_else("host-name", hostname); if(args->test_name().find("UnsolicitedServerNameAck") == 0) host_name = ""; // avoid sending SNI for this test @@ -1617,6 +1743,15 @@ int main(int /*argc*/, char* argv[]) shim_log("Got packet of " + std::to_string(got)); + + if(args->option_used("write-settings")) + { + // TODO: the transcript option should probably be used differently + std::cout << "<<<" << std::endl + << Botan::hex_encode(buf.data(), got) << std::endl + << "<<<" << std::endl; + } + if(args->flag_set("use-exporter-between-reads") && chan->is_active()) { chan->key_material_export("some label", "some context", 42); @@ -1644,8 +1779,23 @@ int main(int /*argc*/, char* argv[]) " exp " + std::to_string(exp)); } shim_log("End of resume loop"); + }; + try + { + execute_test("localhost"); + } + catch (const Shim_Exception& e) + { + if (std::string(e.what()) == "Failed to connect to host") + { + execute_test("::1"); + } + else + { + throw e; + } + } } - } catch(Shim_Exception& e) { diff --git a/src/bogo_shim/config.json b/src/bogo_shim/config.json index 6d235588995..c00f8a3098a 100644 --- a/src/bogo_shim/config.json +++ b/src/bogo_shim/config.json @@ -7,8 +7,12 @@ "Resume-Client-CipherMismatch": "Unexpected error", "InvalidECDHPoint-Server": "Unexpected error", "NoSharedCipher": "Unexpected error", + "NoSharedCipher-TLS13": "Unexpected error", - "PartialFinishedWithServerHelloDone": "Unexpected record vs excess handshake data" + "PartialFinishedWithServerHelloDone": "Unexpected record vs excess handshake data", + "HelloRetryRequest-DuplicateCurve-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'", + "HelloRetryRequest-DuplicateCookie-TLS13": "expects 'illegal parameter' but we want to stick with 'decode error'", + "EncryptedExtensionsWithKeyShare-TLS13": "expects 'unsupported extension' but RFC requires 'illegal parameter'" }, "DisabledTests": { @@ -27,21 +31,111 @@ "*SCSV*": "SCSV is meaningless without TLS 1.0/1.1 support", - "*KeyUpdate*": "No TLS 1.3", - "*TLS13*": "No TLS 1.3", - "Server-JDK11*": "No TLS 1.3", + "AllExtensions-*": "Not all extensions are implemented", + + "EchoTLS13CompatibilitySessionID": "Succeeds, but we prepend a TLS 1.3 CCS and BoGo doesn't like that", + + "Client-RejectJDK11DowngradeRandom": "We don't implement this workaround", + "ExportTrafficSecrets-*": "Exporting traffic secrets is not implemented", + "TooManyChangeCipherSpec-Client-TLS13": "Limits on the number of CCS are not implemented", + "TooManyKeyUpdates": "Limits on the number of KeyUpdates are not implemented", + + "CertificateVerificationSucceed-Client-TLS13-*" : "TLS 1.3 session resumption is NYI", + "Client-VerifyDefault-*-TLS13" : "TLS 1.3 session resumption is NYI", + "Client-Verify-*-TLS13" : "TLS 1.3 session resumption is NYI", + "ExportKeyingMaterial-TLS13" : "TLS 1.3 session resumption is NYI", + "InvalidPSKIdentity-TLS13" : "TLS 1.3 session resumption is NYI", + "NegotiatePSKResumption-TLS13" : "TLS 1.3 session resumption is NYI", + "Resume-Client-CipherMismatch-TLS13" : "TLS 1.3 session resumption is NYI", + "Resume-Client-Mismatch-TLS13-TLS12-TLS" : "TLS 1.3 session resumption is NYI", + "Resume-Client-TLS13-TLS13-TLS" : "TLS 1.3 session resumption is NYI", + "TLS-TLS13-AES_128_GCM_SHA256-client" : "TLS 1.3 session resumption is NYI", + "TLS-TLS13-AES_256_GCM_SHA384-client" : "TLS 1.3 session resumption is NYI", + "TLS-TLS13-CHACHA20_POLY1305_SHA256-client" : "TLS 1.3 session resumption is NYI", + "TLS12SessionID-TLS13" : "TLS 1.3 session resumption is NYI", + "TLS13-HonorServerSessionTicketLifetime" : "TLS 1.3 session resumption is NYI", + "TLS13-TestBadTicketAge-Client" : "TLS 1.3 session resumption is NYI", + "TLS13-TestValidTicketAge-Client" : "TLS 1.3 session resumption is NYI", + "TLS13SessionID-TLS13" : "TLS 1.3 session resumption is NYI", + "TolerateServerNameAck-TLS-TLS13" : "TLS 1.3 session resumption is NYI", + "OCSPStapling-Client-TLS13-*" : "TLS 1.3 session resumption is NYI", + "Resume-Client-NoResume-TLS12-TLS13-TLS": "TLS 1.3 session resumption is NYI", + "Resume-Client-Mismatch-TLS12-TLS13-TLS": "TLS 1.3 session resumption is NYI", + "Ticket-Forbidden-TLS13": "TLS 1.3 session resumption is NYI", + "Resume-Client-PRFMismatch-TLS13": "TLS 1.3 session resumption is NYI", + "TLS13-HelloRetryRequest-Client-*": "TLS 1.3 session resumption is NYI", + "TLS13-TicketAgeSkew-*": "TLS 1.3 session resumption is NYI", + "CurveID-Resume-Client-TLS13": "TLS 1.3 session resumption is NYI", + "ALPNClient-TLS-TLS13": "TLS 1.3 session resumption is NYI", + + "KeyUpdate-FromServer": "No TLS 1.3 server, yet", + "FragmentedClientVersion": "No TLS 1.3 server, yet", + "DelegatedCredentials*": "No TLS 1.3 server, yet", + "IgnoreClientVersionOrder": "No TLS 1.3 server, yet", + "Resume-Server-OmitPSKsOnSecondClientHello": "No TLS 1.3 server, yet", + "PartialClientFinishedWithSecondClientHello": "No TLS 1.3 server, yet", + "Server-JDK11*": "No TLS 1.3 server, yet", + + "CertCompression*-TLS13": "No TLS 1.3 server, yet", + "DuplicateKeyShares-TLS13": "No TLS 1.3 server, yet", + "ECDSACurveMismatch-Sign-TLS13": "No TLS 1.3 server, yet", + "MinimumVersion-Server*-TLS13*": "No TLS 1.3 server, yet", + "Resume-Server*TLS13*": "No TLS 1.3 server, yet", + "Server-Sign-*-TLS13": "No TLS 1.3 server, yet", + "Server-Verify-*-TLS13": "No TLS 1.3 server, yet", + "Server-VerifyDefault-*-TLS13": "No TLS 1.3 server, yet", + + "TLS-TLS13-*-server": "No TLS 1.3 server, yet", + "TLS13-Server-*": "No TLS 1.3 server, yet", + "TLS13-*-Server*": "No TLS 1.3 server, yet", + "*-Server-TLS13": "No TLS 1.3 server, yet", + "*-TLS13-Server": "No TLS 1.3 server, yet", + "RSAKeyUsage-Server-*-TLS13": "No TLS 1.3 server, yet", + + "ExportKeyingMaterial-Server-HalfRTT-TLS13": "No TLS 1.3 server, yet", + "ExtraClientEncryptedExtension-TLS-TLS13": "No TLS 1.3 server, yet", + "ExtraCompressionMethods-TLS13": "No TLS 1.3 server, yet", + "LooseInitialRecordVersion-TLS13": "No TLS 1.3 server, yet", + "NoSupportedCurves-TLS13": "No TLS 1.3 server, yet", + "RequireAnyClientCertificate-TLS13": "No TLS 1.3 server, yet", + "SecondClientHelloMissingKeyShare-TLS13": "No TLS 1.3 server, yet", + "SecondClientHelloWrongCurve-TLS13": "No TLS 1.3 server, yet", + "SendExtensionOnClientCertificate-TLS13": "No TLS 1.3 server, yet", + "ServerAuth-NoFallback-TLS13": "No TLS 1.3 server, yet", + "ServerSkipCertificateVerify-TLS13": "No TLS 1.3 server, yet", + "SkipClientCertificate-TLS13": "No TLS 1.3 server, yet", + "TLS13-NoTicket-NoMint": "No TLS 1.3 server, yet", + "TrailingKeyShareData-TLS13": "No TLS 1.3 server, yet", + "UnexpectedClientEncryptedExtensions-TLS-TLS13": "No TLS 1.3 server, yet", + "UnknownCipher-TLS13": "No TLS 1.3 server, yet", + "VersionTolerance-TLS13": "No TLS 1.3 server, yet", + + "*EarlyData*": "No TLS 1.3 Early Data, yet", + "TLS13-1RTT-Client-*": "No TLS 1.3 Early Data, yet", + + "FailCertCallback-Client-TLS13": "No client auth in TLS 1.3, yet", + "Client-Sign*-TLS13": "No client auth in TLS 1.3, yet", + "TLS13-Client-ClientAuth-": "No client auth in TLS 1.3, yet", + "ClientAuth-*-TLS13": "No client auth in TLS 1.3, yet", + "TLS13-Client-ClientAuth-*": "No client auth in TLS 1.3, yet", + "NoClientCertificate-TLS13": "No client auth in TLS 1.3, yet", + "NoCommonAlgorithms-TLS13": "No client auth in TLS 1.3, yet", + "ClientAuth-*-TLS13-*": "No client auth in TLS 1.3, yet", + "TrailingMessageData-TLS13-CertificateRequest-TLS": "No client auth in TLS 1.3, yet", + "RequestContextInHandshake-TLS13": "No client auth in TLS 1.3, yet", + "UnknownExtensionInCertificateRequest-TLS13": "No client auth in TLS 1.3, yet", + "MissingSignatureAlgorithmsInCertificateRequest-TLS13": "No client auth in TLS 1.3, yet", + "ClientSkipCertificateVerify-TLS13": "No client auth in TLS 1.3, yet", + "SendReceiveIntermediate-Client-TLS13": "No client auth in TLS 1.3, yet", + "TLS13-Client-CertReq-CA-List": "No client auth in TLS 1.3, yet", + "SendNoClientCertificateExtensions-TLS13": "No client auth in TLS 1.3, yet", + + "KeyUpdate-RequestACK-UnfinishedWrite": "-read-with-unfinished-write currently not supported in the shim", + "*Binder*": "No TLS 1.3", - "PartialEncryptedExtensionsWithServerHello": "No TLS 1.3", - "Client-RejectJDK11DowngradeRandom": "No TLS 1.3", - "FragmentedClientVersion": "No TLS 1.3", "NoExportEarlyKeyingMaterial*": "No TLS 1.3", "EarlyDataEnabled*": "No TLS 1.3", - "DelegatedCredentials*": "No TLS 1.3", - "ExportTrafficSecrets-*": "No TLS 1.3", - "IgnoreClientVersionOrder": "No TLS 1.3", - "Resume-Server-OmitPSKsOnSecondClientHello": "No TLS 1.3", - "PartialServerHelloWithHelloRetryRequest": "No TLS 1.3", - "PartialClientFinishedWithSecondClientHello": "No TLS 1.3", + "TLS-ECH*": "No ECH support", "ECH*": "No ECH support", "DuplicateCertCompressionExt*": "No support for 1.3 cert compression extension", @@ -73,6 +167,8 @@ "*SCT*": "No support for SCT", "Renegotiation-ChangeAuthProperties": "No support for SCT", "UnsolicitedCertificateExtensions-*": "No support for SCT", + "IgnoreExtensionsOnIntermediates-TLS13": "No support for SCT", + "SendNoExtensionsOnIntermediate-TLS13": "No support for SCT", "CertificateVerificationSoftFail*": "Fail, but don't fail... wtf?", @@ -156,6 +252,28 @@ "PartialClientFinishedWithClientHello": "Need to check for buffered messages when CCS (bug)", "SendUnencryptedFinished-DTLS": "Need to check for buffered messages when CCS (bug)", - "RSAKeyUsage-*-UnenforcedTLS*": "We always enforce key usage" - } + "RSAKeyUsage-*-UnenforcedTLS*": "We always enforce key usage", + + "Basic-Client-RenewTicket*" : "Needs investigation -- apparently Botan TLS 1.2 does not fully support renewing tickets after resumption?", + + "AllExtensions-Client-Permute-TLS-TLS12" : "Requires new shim flags that are NYI (as of March 2022)", + "AllExtensions-Client-Permute-DTLS-TLS12" : "Requires new shim flags that are NYI (as of March 2022)", + "EarlyData-WriteAfterEncryptedExtensions" : "Requires new shim flags that are NYI (as of March 2022)", + "EarlyData-WriteAfterServerHello" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-SignatureInput" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-KeyShare" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-HandshakerHelloRetryRequest" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-ShimHelloRetryRequest" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-SignatureAlgorithm" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-NoTickets1" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-NoTickets2" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Version2" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateRequest" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-HandshakerOnly" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-ShimOnly" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-AlgorithmMismatch" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-CertificateCompression-InputMismatch" : "Requires new shim flags that are NYI (as of March 2022)", + "TLS-HintMismatch-Version1" : "Requires new shim flags that are NYI (as of March 2022)" + + } } diff --git a/src/cli/tls_client.cpp b/src/cli/tls_client.cpp index 279549a9aaa..bdab8c9069f 100644 --- a/src/cli/tls_client.cpp +++ b/src/cli/tls_client.cpp @@ -2,6 +2,7 @@ * (C) 2014,2015 Jack Lloyd * 2016 Matthias Gierlings * 2017 René Korthaus, Rohde & Schwarz Cybersecurity +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -33,8 +34,8 @@ class TLS_Client final : public Command, public Botan::TLS::Callbacks { public: TLS_Client() - : Command("tls_client host --port=443 --print-certs --policy=default " - "--skip-system-cert-store --trusted-cas= " + : Command("tls_client host --port=443 --print-certs --debug --policy=default " + "--skip-system-cert-store --trusted-cas= --tls-version=default " "--session-db= --session-db-pass= --next-protocols= --type=tcp") { init_sockets(); @@ -73,6 +74,7 @@ class TLS_Client final : public Command, public Botan::TLS::Callbacks const std::string next_protos = get_arg("next-protocols"); const bool use_system_cert_store = flag_set("skip-system-cert-store") == false; const std::string trusted_CAs = get_arg("trusted-cas"); + const auto tls_version = get_arg("tls-version"); if(!sessions_db.empty()) { @@ -96,22 +98,25 @@ class TLS_Client final : public Command, public Botan::TLS::Callbacks throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS"); } - const bool use_tcp = (transport == "tcp"); - const std::vector protocols_to_offer = Command::split_on(next_protos, ','); - Botan::TLS::Protocol_Version version = - use_tcp ? Botan::TLS::Protocol_Version::TLS_V12 : Botan::TLS::Protocol_Version::DTLS_V12; - if(!policy) { policy.reset(new Botan::TLS::Policy); } - if(policy->acceptable_protocol_version(version) == false) - { - throw CLI_Usage_Error("The policy specified does not allow the requested TLS version"); + const bool use_tcp = (transport == "tcp"); + Botan::TLS::Protocol_Version version = policy->latest_supported_version(!use_tcp); + + if(tls_version != "default") { + if(tls_version == "1.2") { + version = Botan::TLS::Protocol_Version::TLS_V12; + } else if (tls_version == "1.3") { + version = Botan::TLS::Protocol_Version::TLS_V13; + } else { + error_output() << "Unknown TLS protocol version " << tls_version << '\n'; } + } struct sockaddr_storage addrbuf; std::string hostname; diff --git a/src/cli/tls_utils.cpp b/src/cli/tls_utils.cpp index 6d6356fd553..37f5de03d17 100644 --- a/src/cli/tls_utils.cpp +++ b/src/cli/tls_utils.cpp @@ -140,6 +140,7 @@ class TLS_Client_Hello_Reader final : public Command try { + // TODO: deal with Client_Hello_13 Botan::TLS::Client_Hello_12 hello(input); output() << format_hello(hello); diff --git a/src/lib/tls/asio/asio_stream.h b/src/lib/tls/asio/asio_stream.h index 2591408f650..469bce72f3d 100644 --- a/src/lib/tls/asio/asio_stream.h +++ b/src/lib/tls/asio/asio_stream.h @@ -716,7 +716,7 @@ class Stream m_context.m_policy, m_context.m_rng, m_context.m_server_info, - Protocol_Version::latest_tls_version())); + Protocol_Version::TLS_V12)); // TODO don't hardcode } else { diff --git a/src/lib/tls/msg_cert_req.cpp b/src/lib/tls/msg_cert_req.cpp index ed80d563185..aeb5c1a4e59 100644 --- a/src/lib/tls/msg_cert_req.cpp +++ b/src/lib/tls/msg_cert_req.cpp @@ -161,5 +161,4 @@ std::vector Certificate_Req::serialize() const return buf; } - } diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp index 3a69c2a3454..0803428e4a9 100644 --- a/src/lib/tls/msg_cert_verify.cpp +++ b/src/lib/tls/msg_cert_verify.cpp @@ -53,7 +53,6 @@ Certificate_Verify::Certificate_Verify(const std::vector& buf) if(m_scheme == Signature_Scheme::NONE) { throw Decoding_Error("Counterparty did not send hash/sig IDS"); } - } /* @@ -97,6 +96,135 @@ bool Certificate_Verify_12::verify(const X509_Certificate& cert, state.callbacks().tls_verify_message(*key, format.first, format.second, state.hash().get_contents(), m_signature); +#if defined(BOTAN_UNSAFE_FUZZER_MODE) + BOTAN_UNUSED(signature_valid); + return true; + +#else + return signature_valid; + +#endif + } + +#if defined(BOTAN_HAS_TLS_13) + +Certificate_Verify_13::Certificate_Verify_13(const std::vector& buf, + const Connection_Side side) + : Certificate_Verify(buf) + , m_side(side) {} + +namespace { + +std::pair +parse_sig_format(const std::string& key_type, + const Signature_Scheme scheme, + const std::vector& offered_schemes) + { + if(key_type != signature_algorithm_of_scheme(scheme)) + { throw Decoding_Error("Counterparty sent inconsistent key and sig types"); } + + if(!signature_scheme_is_known(scheme)) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Peer sent unknown signature scheme"); + + const std::string hash_algo = hash_function_of_scheme(scheme); + + // RFC 8446 4.4.3: + // The SHA-1 algorithm MUST NOT be used in any signatures of + // CertificateVerify messages. + if(scheme == Signature_Scheme::RSA_PKCS1_SHA1 + || scheme == Signature_Scheme::ECDSA_SHA1 + || scheme == Signature_Scheme::DSA_SHA1) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "SHA-1 algorithm must not be used"); + } + + // TODO this corresponds to supported_algos_include from tls_handshake_state.cpp + auto supported = [](const std::vector& schemes, + const std::string& key_algo, + const std::string& hash_type) + { + for(const Signature_Scheme& s : schemes) + { + if(signature_scheme_is_known(s) && + hash_function_of_scheme(s) == hash_type && + signature_algorithm_of_scheme(s) == key_algo) + { + return true; + } + } + + return false; + }; + + if(!supported(offered_schemes, key_type, hash_algo)) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "TLS signature extension did not allow for " + + key_type + "/" + hash_algo + " signature"); + } + + // RFC 8446 4.4.3: + // RSA signatures MUST use an RSASSA-PSS algorithm, regardless of whether + // RSASSA-PKCS1-v1_5 algorithms appear in "signature_algorithms". + if(key_type == "RSA" && + (scheme == Signature_Scheme::RSA_PKCS1_SHA1 + || scheme == Signature_Scheme::RSA_PKCS1_SHA256 + || scheme == Signature_Scheme::RSA_PKCS1_SHA384 + || scheme == Signature_Scheme::RSA_PKCS1_SHA512)) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "RSA signatures must use an RSASSA-PSS algorithm"); + } + + if(key_type == "RSA") + { + return std::make_pair(padding_string_for_scheme(scheme), IEEE_1363); + } + else if(key_type == "DSA" || key_type == "ECDSA") + { + return std::make_pair(padding_string_for_scheme(scheme), DER_SEQUENCE); + } + + throw Invalid_Argument(key_type + " is invalid/unknown for TLS signatures"); + } + +} + +/* +* Verify a Certificate Verify message +*/ +bool Certificate_Verify_13::verify(const X509_Certificate& cert, + const std::vector& offered_schemes, + Callbacks& callbacks, + const Transcript_Hash& transcript_hash) const + { + auto key = cert.load_subject_public_key(); + + // TODO: won't work for client auth + std::pair format = + parse_sig_format(key->algo_name(), m_scheme, offered_schemes); + + // RFC 8446 4.2.3 + // The keys found in certificates MUST [...] be of appropriate type for + // the signature algorithms they are used with. + if(algorithm_identifier_for_scheme(m_scheme) != cert.subject_public_key_algo()) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Signature algorithm does not match certificate's public key"); } + + std::vector msg(64, 0x20); + msg.reserve(64 + 32 + 1 + transcript_hash.size()); + + const std::string context_string = (m_side == Botan::TLS::Connection_Side::SERVER) + ? "TLS 1.3, server CertificateVerify" + : "TLS 1.3, client CertificateVerify"; + + msg.insert(msg.end(), context_string.cbegin(), context_string.cend()); + msg.push_back(0x00); + + msg.insert(msg.end(), transcript_hash.cbegin(), transcript_hash.cend()); + + const bool signature_valid = callbacks.tls_verify_message(*key, format.first, format.second, + msg, m_signature); + #if defined(BOTAN_UNSAFE_FUZZER_MODE) BOTAN_UNUSED(signature_valid); return true; @@ -105,4 +233,6 @@ bool Certificate_Verify_12::verify(const X509_Certificate& cert, #endif } +#endif // BOTAN_HAS_TLS_13 + } diff --git a/src/lib/tls/msg_certificate_13.cpp b/src/lib/tls/msg_certificate_13.cpp new file mode 100644 index 00000000000..a75df2613e6 --- /dev/null +++ b/src/lib/tls/msg_certificate_13.cpp @@ -0,0 +1,215 @@ +/* +* Certificate Message +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Botan::TLS { + +namespace { + +bool certificate_allows_signing(const X509_Certificate& cert) + { + const auto constraints = cert.constraints(); + if(constraints == NO_CONSTRAINTS) + return true; + + return constraints & DIGITAL_SIGNATURE || constraints & NON_REPUDIATION; + } + +} + +void Certificate_13::validate_extensions(const std::set& requested_extensions) const + { + // RFC 8446 4.4.2 + // Extensions in the Certificate message from the server MUST + // correspond to ones from the ClientHello message. Extensions in + // the Certificate message from the client MUST correspond to + // extensions in the CertificateRequest message from the server. + for(const auto& entry : m_entries) + if(entry.extensions.contains_other_than(requested_extensions)) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Certificate Entry contained an extension that was not offered"); } + } + +void Certificate_13::verify(Callbacks& callbacks, + const Policy& policy, + Credentials_Manager& creds, + const std::string& hostname, + bool use_ocsp) const + { + // RFC 8446 4.4.2.4 + // If the server supplies an empty Certificate message, the client + // MUST abort the handshake with a "decode_error" alert. + if(m_entries.empty()) + { throw TLS_Exception(Alert::DECODE_ERROR, "Client: No certificates sent by server"); } + + auto trusted_CAs = creds.trusted_certificate_authorities("tls-client", hostname); + + std::vector certs; + std::vector> ocsp_responses; + for(const auto& entry : m_entries) + { + certs.push_back(entry.certificate); + if(use_ocsp) + { + if(entry.extensions.has()) + ocsp_responses.push_back( + callbacks.tls_parse_ocsp_response( + entry.extensions.get()->get_ocsp_response())); + else + // Note: The make_optional instead of simply nullopt is necessary to work around a GCC <= 10.0 bug + // see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80635 + { ocsp_responses.push_back(std::make_optional()); } + } + } + + const auto& server_cert = m_entries.front().certificate; + if(!certificate_allows_signing(server_cert)) + { + throw TLS_Exception(Alert::BAD_CERTIFICATE, + "Certificate usage constraints do not allow signing"); + } + + const auto usage = (m_side == CLIENT) ? Usage_Type::TLS_CLIENT_AUTH : Usage_Type::TLS_SERVER_AUTH; + callbacks.tls_verify_cert_chain(certs, ocsp_responses, trusted_CAs, usage, hostname, policy); + } + +/** +* Deserialize a Certificate message +*/ +Certificate_13::Certificate_13(const std::vector& buf, + const Policy& policy, + const Connection_Side side) + : m_side(side) + { + TLS_Data_Reader reader("cert message reader", buf); + + m_request_context = reader.get_range(1, 0, 255); + + // RFC 8446 4.4.2 + // [...] in the case of server authentication, this field SHALL be zero length. + if(m_side == Connection_Side::SERVER && !m_request_context.empty()) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Server Certificate message must not contain a request context"); + } + + const auto cert_entries_len = reader.get_uint24_t(); + + if(reader.remaining_bytes() != cert_entries_len) + { + throw TLS_Exception(Alert::DECODE_ERROR, "Certificate: Message malformed"); + } + + const size_t max_size = policy.maximum_certificate_chain_size(); + if(max_size > 0 && cert_entries_len > max_size) + { throw Decoding_Error("Certificate chain exceeds policy specified maximum size"); } + + while(reader.has_remaining()) + { + Certificate_Entry entry; + entry.certificate = X509_Certificate(reader.get_tls_length_value(3)); + + // RFC 8446 4.4.2.2 + // The certificate type MUST be X.509v3 [RFC5280], unless explicitly + // negotiated otherwise (e.g., [RFC7250]). + // + // TLS 1.0 through 1.3 all seem to require that the certificate be + // precisely a v3 certificate. In fact the strict wording would seem + // to require that every certificate in the chain be v3. But often + // the intermediates are outside of the control of the server. + // But, require that the leaf certificate be v3. + if(m_entries.empty() && entry.certificate.x509_version() != 3) + { + throw TLS_Exception(Alert::BAD_CERTIFICATE, "The leaf certificate must be v3"); + } + + // Extensions are simply tacked at the end of the certificate entry. This + // is a departure from the typical "tag-length-value" in a sense that the + // Extensions deserializer needs the length value of the extensions. + const auto extensions_length = reader.peek_uint16_t(); + const auto exts_buf = reader.get_fixed(extensions_length + 2); + TLS_Data_Reader exts_reader("extensions reader", exts_buf); + entry.extensions.deserialize(exts_reader, m_side, type()); + + // RFC 8446 4.4.2 + // Valid extensions for server certificates at present include the + // OCSP Status extension [RFC6066] and the SignedCertificateTimestamp + // extension [RFC6962]; future extensions may be defined for this + // message as well. + if(entry.extensions.contains_implemented_extensions_other_than({ + TLSEXT_CERT_STATUS_REQUEST, + // SIGNED_CERTIFICATE_TIMESTAMP + })) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Certificate Entry contained an extension that is not allowed"); + } + + m_entries.push_back(std::move(entry)); + } + + // RFC 8446 4.4.2 + // The server's certificate_list MUST always be non-empty. A client + // will send an empty certificate_list if it does not have an + // appropriate certificate to send in response to the server's + // authentication request. + if(m_entries.empty()) + { + if(m_side == SERVER) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "No certificates sent by server"); + } + } + else + { + /* validation of provided certificate public key */ + auto key = m_entries.front().certificate.load_subject_public_key(); + + policy.check_peer_key_acceptable(*key); + + if(!policy.allowed_signature_method(key->algo_name())) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Rejecting " + key->algo_name() + " signature"); + } + } + } + +/** +* Serialize a Certificate message +*/ +std::vector Certificate_13::serialize() const + { + std::vector buf; + + append_tls_length_value(buf, m_request_context, 1); + + std::vector entries; + for(const auto& entry : m_entries) + { + append_tls_length_value(entries, entry.certificate.BER_encode(), 3); + append_tls_length_value(entries, entry.extensions.serialize(m_side), 2); + } + + append_tls_length_value(buf, entries, 3); + + return buf; + } + +} diff --git a/src/lib/tls/msg_client_hello.cpp b/src/lib/tls/msg_client_hello.cpp index c164f23f64d..6a41fab858b 100644 --- a/src/lib/tls/msg_client_hello.cpp +++ b/src/lib/tls/msg_client_hello.cpp @@ -45,7 +45,10 @@ std::vector make_hello_random(RandomNumberGenerator& rng, sha256->final(buf); } - if(policy.include_time_in_hello_random()) + // TLS 1.3 does not require the insertion of a timestamp in the client hello + // random. When offering both TLS 1.2 and 1.3 we nevertheless comply with the + // legacy specification. + if(policy.include_time_in_hello_random() && (policy.allow_tls12() || policy.allow_dtls12())) { const uint32_t time32 = static_cast( std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); @@ -91,6 +94,10 @@ Client_Hello::Client_Hello(const std::vector& buf) m_extensions.deserialize(reader, Connection_Side::CLIENT, type()); + // TODO: Reject oid_filters extension if found (which is the only known extension that + // must not occur in the TLS 1.3 client hello. + // RFC 8446 4.2.5 + // [The oid_filters extension] MUST only be sent in the CertificateRequest message. if(offered_suite(static_cast(TLS_EMPTY_RENEGOTIATION_INFO_SCSV))) { if(Renegotiation_Extension* reneg = m_extensions.get()) @@ -468,5 +475,118 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io, hash.update(io.send(*this)); } +#if defined(BOTAN_HAS_TLS_13) + +/* +* Create a new Client Hello message +*/ +Client_Hello_13::Client_Hello_13(const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::string& hostname, + const std::vector& next_protocols) + { + // RFC 8446 4.1.2 + // In TLS 1.3, the client indicates its version preferences in the + // "supported_versions" extension (Section 4.2.1) and the + // legacy_version field MUST be set to 0x0303, which is the version + // number for TLS 1.2. + m_legacy_version = Protocol_Version::TLS_V12; + m_random = make_hello_random(rng, policy); + m_suites = policy.ciphersuite_list(Protocol_Version::TLS_V13); + + if(policy.allow_tls12()) // Note: DTLS 1.3 is NYI, hence dtls_12 is not checked + { + const auto legacy_suites = policy.ciphersuite_list(Protocol_Version::TLS_V12); + m_suites.insert(m_suites.end(), legacy_suites.cbegin(), legacy_suites.cend()); + } + + if(policy.tls_13_middlebox_compatibility_mode()) + { + // RFC 8446 4.1.2 + // In compatibility mode (see Appendix D.4), this field MUST be non-empty, + // so a client not offering a pre-TLS 1.3 session MUST generate a new + // 32-byte value. + rng.random_vec(m_session_id, 32); + } + + if(!hostname.empty()) + m_extensions.add(new Server_Name_Indicator(hostname)); + + m_extensions.add(new Supported_Groups(policy.key_exchange_groups())); + + m_extensions.add(new Key_Share(policy, cb, rng)); + + m_extensions.add(new Supported_Versions(Protocol_Version::TLS_V13, policy)); + + m_extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes())); + + // TODO: Add a signature_algorithms_cert extension negotiating the acceptable + // signature algorithms in a server certificate chain's certificates. + + if(policy.support_cert_status_message()) + m_extensions.add(new Certificate_Status_Request({}, {})); + + // We currently support "record_size_limit" for TLS 1.3 exclusively. Hence, + // when TLS 1.2 is advertised as a supported protocol, we must not offer this + // extension. + if(policy.record_size_limit().has_value() && !policy.allow_tls12()) + m_extensions.add(new Record_Size_Limit(policy.record_size_limit().value())); + + if(!next_protocols.empty()) + m_extensions.add(new Application_Layer_Protocol_Notification(next_protocols)); + + if(policy.allow_tls12()) + { + m_extensions.add(new Renegotiation_Extension()); + m_extensions.add(new Session_Ticket()); + + // EMS must always be used with TLS 1.2, regardless of the policy + m_extensions.add(new Extended_Master_Secret); + + if(policy.negotiate_encrypt_then_mac()) + m_extensions.add(new Encrypt_then_MAC); + + if(m_extensions.has() && !m_extensions.get()->ec_groups().empty()) + m_extensions.add(new Supported_Point_Formats(policy.use_ecc_point_compression())); + } + + cb.tls_modify_extensions(m_extensions, CLIENT); + } + +void Client_Hello_13::retry(const Hello_Retry_Request& hrr, + Callbacks& cb, + RandomNumberGenerator& rng) + { + BOTAN_STATE_CHECK(m_extensions.has()); + BOTAN_STATE_CHECK(m_extensions.has()); + + auto hrr_ks = hrr.extensions().get(); + const auto& supported_groups = m_extensions.get()->groups(); + + if(hrr.extensions().has()) + m_extensions.get()->retry_offer(*hrr_ks, supported_groups, cb, rng); + + // RFC 8446 4.2.2 + // When sending the new ClientHello, the client MUST copy + // the contents of the extension received in the HelloRetryRequest into + // a "cookie" extension in the new ClientHello. + // + // RFC 8446 4.2.2 + // Clients MUST NOT use cookies in their initial ClientHello in subsequent + // connections. + if(hrr.extensions().has()) + { + BOTAN_STATE_CHECK(!m_extensions.has()); + m_extensions.add(new Cookie(hrr.extensions().get()->get_cookie())); + } + + // TODO: the consumer of the TLS implementation won't be able to distinguish + // invocations to this callback due to the first Client_Hello or the + // retried Client_Hello after receiving a Hello_Retry_Request. + cb.tls_modify_extensions(m_extensions, CLIENT); + } + +#endif // BOTAN_HAS_TLS_13 } diff --git a/src/lib/tls/msg_encrypted_extensions.cpp b/src/lib/tls/msg_encrypted_extensions.cpp new file mode 100644 index 00000000000..90f8182a5d3 --- /dev/null +++ b/src/lib/tls/msg_encrypted_extensions.cpp @@ -0,0 +1,66 @@ +/* +* TLS Hello Request and Client Hello Messages +* (C) 2022 Jack Lloyd +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include +#if defined(BOTAN_HAS_TLS_13) + +#include +#include +#include + +namespace Botan::TLS { + +Encrypted_Extensions::Encrypted_Extensions(const std::vector& buf) + { + TLS_Data_Reader reader("encrypted extensions reader", buf); + + // Encrypted Extensions contains a list of extensions. This list may legally + // be empty. However, in that case we should at least see a two-byte length + // field that reads 0x00 0x00. + if(buf.size() < 2) + { + throw TLS_Exception(Alert::DECODE_ERROR, + "Server sent an empty Encrypted Extensions message"); + } + + m_extensions.deserialize(reader, Connection_Side::SERVER, type()); + + // RFC 8446 4.2 + // If an implementation receives an extension which it recognizes and + // which is not specified for the message in which it appears, it MUST + // abort the handshake with an "illegal_parameter" alert. + // + // Note that we cannot encounter any extensions that we don't recognize here, + // since only extensions we previously offered are allowed in EE. + const auto allowed_exts = std::set + { + // Allowed extensions listed in RFC 8446 and implemented in Botan + Handshake_Extension_Type::TLSEXT_SERVER_NAME_INDICATION, + // MAX_FRAGMENT_LENGTH + Handshake_Extension_Type::TLSEXT_SUPPORTED_GROUPS, + Handshake_Extension_Type::TLSEXT_USE_SRTP, + // HEARTBEAT + Handshake_Extension_Type::TLSEXT_ALPN, + // CLIENT_CERTIFICATE_TYPE + // SERVER_CERTIFICATE_TYPE + // EARLY_DATA + + // Allowed extensions not listed in RFC 8446 but acceptable as Botan implements them + Handshake_Extension_Type::TLSEXT_RECORD_SIZE_LIMIT, + }; + if(m_extensions.contains_implemented_extensions_other_than(allowed_exts)) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Encrypted Extensions contained an extension that is not allowed"); + } + + } + +} + +#endif diff --git a/src/lib/tls/msg_finished.cpp b/src/lib/tls/msg_finished.cpp index 044bac060ee..4a1b6d63ec1 100644 --- a/src/lib/tls/msg_finished.cpp +++ b/src/lib/tls/msg_finished.cpp @@ -12,6 +12,10 @@ #include #include +#if defined(BOTAN_HAS_TLS_13) + #include +#endif + namespace Botan::TLS { namespace { @@ -82,4 +86,16 @@ bool Finished_12::verify(const Handshake_State& state, #endif } +#if defined(BOTAN_HAS_TLS_13) +Finished_13::Finished_13(Cipher_State* cipher_state, + const Transcript_Hash& transcript_hash) + { + m_verification_data = cipher_state->finished_mac(transcript_hash); + } + +bool Finished_13::verify(Cipher_State* cipher_state, const Transcript_Hash& transcript_hash) const + { + return cipher_state->verify_peer_finished_mac(transcript_hash, m_verification_data); + } +#endif } diff --git a/src/lib/tls/msg_key_update.cpp b/src/lib/tls/msg_key_update.cpp new file mode 100644 index 00000000000..7eaf5dc4a24 --- /dev/null +++ b/src/lib/tls/msg_key_update.cpp @@ -0,0 +1,46 @@ +/* +* Key Update message +* (C) 2022 Jack Lloyd +* 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include +#if defined(BOTAN_HAS_TLS_13) + +#include +#include + +namespace Botan::TLS { + +Key_Update::Key_Update(const bool request_peer_update) + : m_update_requested(request_peer_update) {} + +Key_Update::Key_Update(const std::vector& buf) + { + if(buf.size() != 1) + { + throw TLS_Exception(Alert::DECODE_ERROR, "malformed key_update"); + } + + // RFC 8446 4.6.3 + // If an implementation receives any other value [than 0 or 1], it MUST + // terminate the connection with an "illegal_parameter" alert. + const uint8_t update_requested = buf.at(0); + if(update_requested > 1) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "unexpected key_update parameter"); + } + + m_update_requested = update_requested == 1; + } + +std::vector Key_Update::serialize() const + { + return std::vector(1, (m_update_requested ? 1 : 0)); + } + +} + +#endif diff --git a/src/lib/tls/msg_server_hello.cpp b/src/lib/tls/msg_server_hello.cpp index 5c5ad902f47..7d3b0aa570c 100644 --- a/src/lib/tls/msg_server_hello.cpp +++ b/src/lib/tls/msg_server_hello.cpp @@ -29,6 +29,19 @@ namespace { const uint64_t DOWNGRADE_TLS11 = 0x444F574E47524400; const uint64_t DOWNGRADE_TLS12 = 0x444F574E47524401; +// SHA-256("HelloRetryRequest") +const std::array HELLO_RETRY_REQUEST_MARKER = + { + 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C, 0x02, + 0x1E, 0x65, 0xB8, 0x91, 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, + 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C + }; + +bool random_signals_hello_retry_request(const std::vector& random) + { + return constant_time_compare(random.data(), HELLO_RETRY_REQUEST_MARKER.data(), HELLO_RETRY_REQUEST_MARKER.size()); + } + std::vector make_server_hello_random(RandomNumberGenerator& rng, Protocol_Version offered_version, @@ -41,51 +54,98 @@ make_server_hello_random(RandomNumberGenerator& rng, } -/* -* Deserialize a Server Hello message +/** +* Version-agnostic internal server hello data container that allows +* parsing Server_Hello messages without prior knowledge of the contained +* protocol version. */ -Server_Hello::Internal::Internal(const std::vector& buf) +class Server_Hello_Internal { - if(buf.size() < 38) - { - throw Decoding_Error("Server_Hello: Packet corrupted"); - } - - TLS_Data_Reader reader("ServerHello", buf); - - const uint8_t major_version = reader.get_byte(); - const uint8_t minor_version = reader.get_byte(); - - legacy_version = Protocol_Version(major_version, minor_version); - - random = reader.get_fixed(32); + public: + /** + * Deserialize a Server Hello message + */ + Server_Hello_Internal(const std::vector& buf) + { + if(buf.size() < 38) + { + throw Decoding_Error("Server_Hello: Packet corrupted"); + } - session_id = reader.get_range(1, 0, 32); - ciphersuite = reader.get_uint16_t(); - comp_method = reader.get_byte(); + TLS_Data_Reader reader("ServerHello", buf); + + const uint8_t major_version = reader.get_byte(); + const uint8_t minor_version = reader.get_byte(); + + legacy_version = Protocol_Version(major_version, minor_version); + + // RFC 8446 4.1.3 + // Upon receiving a message with type server_hello, implementations MUST + // first examine the Random value and, if it matches this value, process + // it as described in Section 4.1.4 [Hello Retry Request]). + random = reader.get_fixed(32); + is_hello_retry_request = random_signals_hello_retry_request(random); + + session_id = reader.get_range(1, 0, 32); + ciphersuite = reader.get_uint16_t(); + comp_method = reader.get_byte(); + + // Note that this code path might parse a TLS 1.2 (or older) server hello message that + // is nevertheless marked as being a 'hello retry request' (potentially maliciously). + // Extension parsing will however not be affected by the associated flag. + // Only after parsing the extensions will the upstream code be able to decide + // whether we're dealing with TLS 1.3 or older. + extensions.deserialize(reader, Connection_Side::SERVER, + is_hello_retry_request + ? Handshake_Type::HELLO_RETRY_REQUEST + : Handshake_Type::SERVER_HELLO); + } - extensions.deserialize(reader, Connection_Side::SERVER, - Handshake_Type::SERVER_HELLO); - } + Server_Hello_Internal(Protocol_Version lv, + std::vector sid, + std::vector r, + const uint16_t cs, + const uint8_t cm) + : legacy_version(lv) + , session_id(std::move(sid)) + , random(std::move(r)) + , ciphersuite(cs) + , comp_method(cm) {} + + Protocol_Version version() const + { + // RFC 8446 4.2.1 + // A server which negotiates a version of TLS prior to TLS 1.3 MUST set + // ServerHello.version and MUST NOT send the "supported_versions" + // extension. A server which negotiates TLS 1.3 MUST respond by sending + // a "supported_versions" extension containing the selected version + // value (0x0304). + // + // Note: Here we just take a message parsing decision, further validation of + // the extension's contents is done later. + return (extensions.has()) + ? Protocol_Version::TLS_V13 + : legacy_version; + } + public: + Protocol_Version legacy_version; + std::vector session_id; + std::vector random; + bool is_hello_retry_request; + uint16_t ciphersuite; + uint8_t comp_method; -Server_Hello::Internal::Internal(Protocol_Version lv, - std::vector sid, - std::vector r, - const uint16_t cs, - const uint8_t cm) - : legacy_version(lv) - , session_id(std::move(sid)) - , random(std::move(r)) - , ciphersuite(cs) - , comp_method(cm) {} + Extensions extensions; + }; +Server_Hello::Server_Hello(std::unique_ptr data) + : m_data(std::move(data)) {} -Protocol_Version Server_Hello::Internal::version() const - { - return legacy_version; - } +Server_Hello::Server_Hello(Server_Hello&&) = default; +Server_Hello& Server_Hello::operator=(Server_Hello&&) = default; +Server_Hello::~Server_Hello() = default; /* * Serialize a Server Hello message @@ -161,7 +221,7 @@ Server_Hello_12::Server_Hello_12(Handshake_IO& io, const Client_Hello_12& client_hello, const Server_Hello_12::Settings& server_settings, const std::string& next_protocol) : - Server_Hello(std::make_unique( + Server_Hello(std::make_unique( server_settings.protocol_version(), server_settings.session_id(), make_server_hello_random(rng, server_settings.protocol_version(), policy), @@ -245,7 +305,7 @@ Server_Hello_12::Server_Hello_12(Handshake_IO& io, Session& resumed_session, bool offer_session_ticket, const std::string& next_protocol) : - Server_Hello(std::make_unique( + Server_Hello(std::make_unique( resumed_session.version(), client_hello.session_id(), make_hello_random(rng, policy), @@ -293,12 +353,16 @@ Server_Hello_12::Server_Hello_12(Handshake_IO& io, Server_Hello_12::Server_Hello_12(const std::vector& buf) - : Server_Hello_12(std::make_unique(buf)) + : Server_Hello_12(std::make_unique(buf)) {} -Server_Hello_12::Server_Hello_12(std::unique_ptr data) +Server_Hello_12::Server_Hello_12(std::unique_ptr data) : Server_Hello(std::move(data)) { + if(!m_data->version().is_pre_tls_13()) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, "Expected server hello of (D)TLS 1.2 or lower"); + } } Protocol_Version Server_Hello_12::selected_version() const @@ -407,4 +471,197 @@ std::vector Server_Hello_Done::serialize() const return std::vector(); } +#if defined(BOTAN_HAS_TLS_13) + +Server_Hello_13::Server_Hello_Tag Server_Hello_13::as_server_hello; +Server_Hello_13::Hello_Retry_Request_Tag Server_Hello_13::as_hello_retry_request; + +std::variant +Server_Hello_13::parse(const std::vector& buf) + { + TLS_Data_Reader reader("Server_Hello_13::parse", buf); + + auto data = std::make_unique(buf); + const auto version = data->version(); + + // server hello that appears to be pre-TLS 1.3, takes precedence over... + if(version.is_pre_tls_13()) + { return Server_Hello_12(std::move(data)); } + + // ... the TLS 1.3 "special case" aka. Hello_Retry_Request + if(version == Protocol_Version::TLS_V13) + { + if(data->is_hello_retry_request) + { return Hello_Retry_Request(std::move(data)); } + + return Server_Hello_13(std::move(data)); + } + + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "unexpected server hello version: " + version.to_string()); + } + +/** + * Validation that applies to both Server Hello and Hello Retry Request + */ +void Server_Hello_13::basic_validation() const + { + BOTAN_ASSERT_NOMSG(m_data->version() == Protocol_Version::TLS_V13); + + // Note: checks that cannot be performed without contextual information + // are done in the specific TLS client implementation. + // Note: The Supported_Version extension makes sure internally that + // exactly one entry is provided. + + // Note: Hello Retry Request basic validation is equivalent with the + // basic validations required for Server Hello + // + // RFC 8446 4.1.4 + // Upon receipt of a HelloRetryRequest, the client MUST check the + // legacy_version, [...], and legacy_compression_method as specified in + // Section 4.1.3 and then process the extensions, starting with determining + // the version using "supported_versions". + + // RFC 8446 4.1.3 + // In TLS 1.3, [...] the legacy_version field MUST be set to 0x0303 + if(legacy_version() != Protocol_Version::TLS_V12) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "legacy_version '" + legacy_version().to_string() + "' is not allowed"); + } + + // RFC 8446 4.1.3 + // legacy_compression_method: A single byte which MUST have the value 0. + if(compression_method() != 0x00) + { + throw TLS_Exception(Alert::DECODE_ERROR, "compression is not supported in TLS 1.3"); + } + + const auto& exts = extensions(); + + // RFC 8446 4.1.3 + // All TLS 1.3 ServerHello messages MUST contain the "supported_versions" extension. + if(!exts.has()) + { + throw TLS_Exception(Alert::MISSING_EXTENSION, + "server hello did not contain 'supported version' extension"); + } + + // RFC 8446 4.2.1 + // A server which negotiates TLS 1.3 MUST respond by sending + // a "supported_versions" extension containing the selected version + // value (0x0304). + if(selected_version() != Protocol_Version::TLS_V13) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "TLS 1.3 Server Hello selected a different version"); + } + } + +Server_Hello_13::Server_Hello_13(std::unique_ptr data, + Server_Hello_13::Server_Hello_Tag) + : Server_Hello(std::move(data)) + { + BOTAN_ASSERT_NOMSG(!m_data->is_hello_retry_request); + basic_validation(); + + const auto& exts = extensions(); + + // RFC 8446 4.1.3 + // The ServerHello MUST only include extensions which are required to + // establish the cryptographic context and negotiate the protocol version. + // [...] + // Other extensions (see Section 4.2) are sent separately in the + // EncryptedExtensions message. + // + // Note that further validation dependent on the client hello is done in the + // TLS client implementation. + std::set allowed = + { + TLSEXT_KEY_SHARE, + TLSEXT_PSK_KEY_EXCHANGE_MODES, + TLSEXT_SUPPORTED_VERSIONS, + }; + + // As the ServerHello shall only contain essential extensions, we don't give + // any slack for extensions not implemented by Botan here. + if(exts.contains_other_than(allowed)) + { + throw TLS_Exception(Alert::UNSUPPORTED_EXTENSION, + "Server Hello contained an extension that is not allowed"); + } + + // RFC 8446 4.1.3 + // Current ServerHello messages additionally contain + // either the "pre_shared_key" extension or the "key_share" + // extension, or both [...]. + if(!exts.has() && !exts.has()) + { + throw TLS_Exception(Alert::MISSING_EXTENSION, + "server hello must contain key exchange information"); + } + } + +Server_Hello_13::Server_Hello_13(std::unique_ptr data, Server_Hello_13::Hello_Retry_Request_Tag) + : Server_Hello(std::move(data)) + { + BOTAN_ASSERT_NOMSG(m_data->is_hello_retry_request); + basic_validation(); + + const auto& exts = extensions(); + + // RFC 8446 4.1.4 + // The HelloRetryRequest extensions defined in this specification are: + // - supported_versions (see Section 4.2.1) + // - cookie (see Section 4.2.2) + // - key_share (see Section 4.2.8) + std::set allowed = + { + TLSEXT_COOKIE, + TLSEXT_SUPPORTED_VERSIONS, + TLSEXT_KEY_SHARE, + }; + + // As the Hello Retry Request shall only contain essential extensions, we + // don't give any slack for extensions not implemented by Botan here. + if(extensions().contains_other_than(allowed)) + { + throw TLS_Exception(Alert::UNSUPPORTED_EXTENSION, + "Hello Retry Request contained an extension that is not allowed"); + } + + // RFC 8446 4.1.4 + // Clients MUST abort the handshake with an "illegal_parameter" alert if + // the HelloRetryRequest would not result in any change in the ClientHello. + if(!exts.has() && !exts.has()) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Hello Retry Request does not request any changes to Client Hello"); + } + } + +std::optional Server_Hello_13::random_signals_downgrade() const + { + const uint64_t last8 = load_be(m_data->random.data(), 3); + if(last8 == DOWNGRADE_TLS11) + { return Protocol_Version::TLS_V11; } + if(last8 == DOWNGRADE_TLS12) + { return Protocol_Version::TLS_V12; } + + return std::nullopt; + } + +Protocol_Version Server_Hello_13::selected_version() const + { + const auto versions_ext = m_data->extensions.get(); + BOTAN_ASSERT_NOMSG(versions_ext); + const auto& versions = versions_ext->versions(); + BOTAN_ASSERT_NOMSG(versions.size() == 1); + return versions.front(); + } + +Hello_Retry_Request::Hello_Retry_Request(std::unique_ptr data) + : Server_Hello_13(std::move(data), Server_Hello_13::as_hello_retry_request) {} + +#endif // BOTAN_HAS_TLS_13 + } diff --git a/src/lib/tls/msg_session_ticket.cpp b/src/lib/tls/msg_session_ticket.cpp index bec42a49d5f..c71f4e64cd7 100644 --- a/src/lib/tls/msg_session_ticket.cpp +++ b/src/lib/tls/msg_session_ticket.cpp @@ -49,4 +49,18 @@ std::vector New_Session_Ticket_12::serialize() const return buf; } +#if defined (BOTAN_HAS_TLS_13) + +New_Session_Ticket_13::New_Session_Ticket_13(const std::vector&) + { + // TODO: Implement + } + +std::vector New_Session_Ticket_13::serialize() const + { + return {}; + } + +#endif + } diff --git a/src/lib/tls/tls12/tls_channel_impl_12.cpp b/src/lib/tls/tls12/tls_channel_impl_12.cpp index ef9d8174cf2..2c3a3f8d061 100644 --- a/src/lib/tls/tls12/tls_channel_impl_12.cpp +++ b/src/lib/tls/tls12/tls_channel_impl_12.cpp @@ -181,6 +181,11 @@ void Channel_Impl_12::renegotiate(bool force_full_renegotiation) throw Invalid_State("Cannot renegotiate on inactive connection"); } +void Channel_Impl_12::update_traffic_keys(bool) + { + throw Invalid_Argument("cannot update traffic keys on a TLS 1.2 channel"); + } + void Channel_Impl_12::change_cipher_spec_reader(Connection_Side side) { auto pending = pending_state(); diff --git a/src/lib/tls/tls12/tls_channel_impl_12.h b/src/lib/tls/tls12/tls_channel_impl_12.h index 1e3013ddf7e..f371b692e97 100644 --- a/src/lib/tls/tls12/tls_channel_impl_12.h +++ b/src/lib/tls/tls12/tls_channel_impl_12.h @@ -122,6 +122,14 @@ class Channel_Impl_12 : public Channel_Impl */ void renegotiate(bool force_full_renegotiation = false) override; + /** + * Attempt to update the session's traffic key material + * Note that this is possible with a TLS 1.3 channel, only. + * + * @param request_peer_update if true, require a reciprocal key update + */ + void update_traffic_keys(bool request_peer_update = false) override; + /** * @return true iff the counterparty supports the secure * renegotiation extensions. diff --git a/src/lib/tls/tls12/tls_client_impl_12.cpp b/src/lib/tls/tls12/tls_client_impl_12.cpp index e859b18e10c..ae4d3181356 100644 --- a/src/lib/tls/tls12/tls_client_impl_12.cpp +++ b/src/lib/tls/tls12/tls_client_impl_12.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -76,6 +77,28 @@ Client_Impl_12::Client_Impl_12(Callbacks& callbacks, send_client_hello(state, false, version, next_protocols); } +Client_Impl_12::Client_Impl_12(const Channel_Impl::Downgrade_Information& downgrade_info) : + Channel_Impl_12(downgrade_info.callbacks, + downgrade_info.session_manager, + downgrade_info.rng, + downgrade_info.policy, + false /* is_server */, + false /* datagram -- not supported by Botan in TLS 1.3 */), + m_creds(downgrade_info.creds), + m_info(downgrade_info.server_info) + { + Handshake_State& state = create_handshake_state(Protocol_Version::TLS_V12); + + std::vector client_hello_msg(downgrade_info.client_hello_message.begin() + 4 /* handshake header length */, + downgrade_info.client_hello_message.end()); + + state.client_hello(new Client_Hello_12(client_hello_msg)); + state.hash().update(downgrade_info.client_hello_message); + + secure_renegotiation_check(state.client_hello()); + state.set_expected_next(SERVER_HELLO); + } + std::unique_ptr Client_Impl_12::new_handshake_state(std::unique_ptr io) { return std::make_unique(std::move(io), callbacks()); diff --git a/src/lib/tls/tls12/tls_client_impl_12.h b/src/lib/tls/tls12/tls_client_impl_12.h index a4538c0296e..a64f4f6c6af 100644 --- a/src/lib/tls/tls12/tls_client_impl_12.h +++ b/src/lib/tls/tls12/tls_client_impl_12.h @@ -62,6 +62,8 @@ class Client_Impl_12 : public Channel_Impl_12 size_t reserved_io_buffer_size = TLS::Channel::IO_BUF_DEFAULT_SIZE ); + explicit Client_Impl_12(const Channel_Impl::Downgrade_Information& downgrade_info); + /** * @return network protocol as advertised by the TLS server, if server sent the ALPN extension */ diff --git a/src/lib/tls/tls12/tls_handshake_state.cpp b/src/lib/tls/tls12/tls_handshake_state.cpp index b317b107523..3f100bc18b0 100644 --- a/src/lib/tls/tls12/tls_handshake_state.cpp +++ b/src/lib/tls/tls12/tls_handshake_state.cpp @@ -35,6 +35,9 @@ const char* handshake_type_to_string(Handshake_Type type) case SERVER_HELLO: return "server_hello"; + case HELLO_RETRY_REQUEST: + return "hello_retry_request"; + case CERTIFICATE: return "certificate"; @@ -68,6 +71,15 @@ const char* handshake_type_to_string(Handshake_Type type) case FINISHED: return "finished"; + case END_OF_EARLY_DATA: + return "end_of_early_data"; + + case ENCRYPTED_EXTENSIONS: + return "encrypted_extensions"; + + case KEY_UPDATE: + return "key_update"; + case HANDSHAKE_NONE: return "invalid"; } @@ -374,6 +386,16 @@ Handshake_State::parse_sig_format(const Public_Key& key, const std::string hash_algo = hash_function_of_scheme(scheme); + // RFC 8446 4.4.3: + // The SHA-1 algorithm MUST NOT be used in any signatures of + // CertificateVerify messages. + if(scheme == Signature_Scheme::RSA_PKCS1_SHA1 + || scheme == Signature_Scheme::ECDSA_SHA1 + || scheme == Signature_Scheme::DSA_SHA1) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "SHA-1 algorithm must not be used"); + } + if(!supported_algos_include(supported_algos, key_type, hash_algo)) { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, @@ -381,6 +403,19 @@ Handshake_State::parse_sig_format(const Public_Key& key, key_type + "/" + hash_algo + " signature"); } + + // RFC 8446 4.4.3: + // RSA signatures MUST use an RSASSA-PSS algorithm, regardless of whether + // RSASSA-PKCS1-v1_5 algorithms appear in "signature_algorithms". + if(version() == Protocol_Version::TLS_V13 && key_type == "RSA" && + (scheme == Signature_Scheme::RSA_PKCS1_SHA1 + || scheme == Signature_Scheme::RSA_PKCS1_SHA256 + || scheme == Signature_Scheme::RSA_PKCS1_SHA384 + || scheme == Signature_Scheme::RSA_PKCS1_SHA512)) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "RSA signatures must use an RSASSA-PSS algorithm"); + } + if(key_type == "RSA") { return std::make_pair(padding_string_for_scheme(scheme), IEEE_1363); diff --git a/src/lib/tls/tls12/tls_handshake_state.h b/src/lib/tls/tls12/tls_handshake_state.h index 34e16bc337c..97f1abf1b7e 100644 --- a/src/lib/tls/tls12/tls_handshake_state.h +++ b/src/lib/tls/tls12/tls_handshake_state.h @@ -114,7 +114,6 @@ class Handshake_State // we're taking the ownership void client_hello(Client_Hello_12* client_hello); void server_hello(Server_Hello_12* server_hello); - void server_cert_status(Certificate_Status* server_cert_status); void server_kex(Server_Key_Exchange* server_kex); void cert_req(Certificate_Req* cert_req); diff --git a/src/lib/tls/tls12/tls_server_impl_12.cpp b/src/lib/tls/tls12/tls_server_impl_12.cpp index 26bfce45de8..a7d7b7838db 100644 --- a/src/lib/tls/tls12/tls_server_impl_12.cpp +++ b/src/lib/tls/tls12/tls_server_impl_12.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/lib/tls/tls13/info.txt b/src/lib/tls/tls13/info.txt new file mode 100644 index 00000000000..bb7a4fe7642 --- /dev/null +++ b/src/lib/tls/tls13/info.txt @@ -0,0 +1,21 @@ + +TLS_13 -> 20210721 + + + + + + +tls_channel_impl_13.h +tls_cipher_state.h +tls_client_impl_13.h +tls_handshake_layer_13.h +tls_handshake_state_13.h +tls_record_layer_13.h +tls_transcript_hash_13.h + + + +hkdf +tls + diff --git a/src/lib/tls/tls13/tls_channel_impl_13.cpp b/src/lib/tls/tls13/tls_channel_impl_13.cpp new file mode 100644 index 00000000000..bdf1e7f6b65 --- /dev/null +++ b/src/lib/tls/tls13/tls_channel_impl_13.cpp @@ -0,0 +1,398 @@ +/* +* TLS Channel - implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { +bool is_user_canceled_alert(const Botan::TLS::Alert& alert) + { + return alert.type() == Botan::TLS::Alert::USER_CANCELED; + } + +bool is_close_notify_alert(const Botan::TLS::Alert& alert) + { + return alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY; + } + +bool is_error_alert(const Botan::TLS::Alert& alert) + { + // In TLS 1.3 all alerts except for closure alerts are considered error alerts. + // (RFC 8446 6.) + return !is_close_notify_alert(alert) && !is_user_canceled_alert(alert); + } +} + +namespace Botan::TLS { + +Channel_Impl_13::Channel_Impl_13(Callbacks& callbacks, + Session_Manager& session_manager, + Credentials_Manager& credentials_manager, + RandomNumberGenerator& rng, + const Policy& policy, + bool is_server) : + m_side(is_server ? Connection_Side::SERVER : Connection_Side::CLIENT), + m_callbacks(callbacks), + m_session_manager(session_manager), + m_credentials_manager(credentials_manager), + m_rng(rng), + m_policy(policy), + m_record_layer(m_side), + m_handshake_layer(m_side), + m_can_read(true), + m_can_write(true), + m_opportunistic_key_update(false) + { + } + +Channel_Impl_13::~Channel_Impl_13() = default; + +size_t Channel_Impl_13::received_data(const uint8_t input[], size_t input_size) + { + BOTAN_STATE_CHECK(!is_downgrading()); + + // RFC 8446 6.1 + // Any data received after a closure alert has been received MUST be ignored. + if(!m_can_read) + { return 0; } + + try + { + if(expects_downgrade()) + { preserve_peer_transcript(input, input_size); } + + m_record_layer.copy_data(input, input_size); + + while(true) + { + // RFC 8446 6.1 + // Any data received after a closure alert has been received MUST be ignored. + // + // ... this data might already be in the record layer's read buffer. + if(!m_can_read) + { return 0; } + + auto result = m_record_layer.next_record(m_cipher_state.get()); + + if(std::holds_alternative(result)) + { return std::get(result); } + + const auto& record = std::get(result); + + // RFC 8446 5.1 + // Handshake messages MUST NOT be interleaved with other record types. + if(record.type != HANDSHAKE && m_handshake_layer.has_pending_data()) + { throw Unexpected_Message("Expected remainder of a handshake message"); } + + if(record.type == HANDSHAKE) + { + m_handshake_layer.copy_data(unlock(record.fragment)); // TODO: record fragment should be an ordinary std::vector + + if(!handshake_finished()) + { + while(auto handshake_msg = m_handshake_layer.next_message(policy(), m_transcript_hash)) + { + // RFC 8446 5.1 + // Handshake messages MUST NOT span key changes. Implementations + // MUST verify that all messages immediately preceding a key change + // align with a record boundary; if not, then they MUST terminate the + // connection with an "unexpected_message" alert. Because the + // ClientHello, EndOfEarlyData, ServerHello, Finished, and KeyUpdate + // messages can immediately precede a key change, implementations + // MUST send these messages in alignment with a record boundary. + // + // Note: Hello_Retry_Request was added to the list below although it cannot immediately precede a key change. + // However, there cannot be any further sensible messages in the record after HRR. + // + // Note: Server_Hello_12 was deliberately not included in the check below because in TLS 1.2 Server Hello and + // other handshake messages can be legally coalesced in a single record. + // + if(holds_any_of + (handshake_msg.value()) + && m_handshake_layer.has_pending_data()) + { throw Unexpected_Message("Unexpected additional handshake message data found in record"); } + + const bool downgrade_requested = std::holds_alternative(handshake_msg.value()); + + process_handshake_msg(std::move(handshake_msg.value())); + + if(downgrade_requested) + { + // Downgrade to TLS 1.2 was detected. Stop everything we do and await being replaced by a 1.2 implementation. + BOTAN_STATE_CHECK(m_downgrade_info); + m_downgrade_info->will_downgrade = true; + return 0; + } + else if(m_downgrade_info != nullptr) + { + // We received a TLS 1.3 error alert that could have been a TLS 1.2 warning alert. + // Now that we know that we are talking to a TLS 1.3 server, shut down. + if(m_downgrade_info->received_tls_13_error_alert) + shutdown(); + + // Downgrade can only happen if the first received message is a Server_Hello_12. This was not the case. + m_downgrade_info.reset(); + } + } + } + else + { + while(auto handshake_msg = m_handshake_layer.next_post_handshake_message(policy())) + { + // make sure Key_Update appears only at the end of a record; see description above + if(std::holds_alternative(handshake_msg.value()) && m_handshake_layer.has_pending_data()) + { throw Unexpected_Message("Unexpected additional post-handshake message data found in record"); } + + process_post_handshake_msg(std::move(handshake_msg.value())); + } + } + } + else if(record.type == CHANGE_CIPHER_SPEC) + { + process_dummy_change_cipher_spec(); + } + else if(record.type == APPLICATION_DATA) + { + BOTAN_ASSERT(record.seq_no.has_value(), "decrypted application traffic had a sequence number"); + callbacks().tls_record_received(record.seq_no.value(), record.fragment.data(), record.fragment.size()); + } + else if(record.type == ALERT) + { + process_alert(record.fragment); + } + else + { throw Unexpected_Message("Unexpected record type " + std::to_string(record.type) + " from counterparty"); } + } + } + catch(TLS_Exception& e) + { + send_fatal_alert(e.type()); + throw; + } + catch(Invalid_Authentication_Tag&) + { + // RFC 8446 5.2 + // If the decryption fails, the receiver MUST terminate the connection + // with a "bad_record_mac" alert. + send_fatal_alert(Alert::BAD_RECORD_MAC); + throw; + } + catch(Decoding_Error&) + { + send_fatal_alert(Alert::DECODE_ERROR); + throw; + } + catch(...) + { + send_fatal_alert(Alert::INTERNAL_ERROR); + throw; + } + } + +void Channel_Impl_13::send_handshake_message(const Handshake_Message_13_Ref message) + { + std::visit([&](const auto msg) { callbacks().tls_inspect_handshake_msg(msg.get()); }, message); + + auto msg = m_handshake_layer.prepare_message(message, m_transcript_hash); + + if(expects_downgrade() && std::holds_alternative>(message)) + { preserve_client_hello(msg); } + + send_record(Record_Type::HANDSHAKE, msg); + } + +void Channel_Impl_13::send_post_handshake_message(const Post_Handshake_Message_13 message) + { + send_record(Record_Type::HANDSHAKE, m_handshake_layer.prepare_post_handshake_message(message)); + } + +void Channel_Impl_13::send_dummy_change_cipher_spec() + { + // RFC 8446 5. + // The change_cipher_spec record is used only for compatibility purposes + // (see Appendix D.4). + // + // The only allowed CCS message content is 0x01, all other CCS records MUST + // be rejected by TLS 1.3 implementations. + send_record(Record_Type::CHANGE_CIPHER_SPEC, {0x01}); + } + +void Channel_Impl_13::send(const uint8_t buf[], size_t buf_size) + { + if(!is_active()) + { throw Invalid_State("Data cannot be sent on inactive TLS connection"); } + + // RFC 8446 4.6.3 + // If the request_update field [of a received KeyUpdate] is set to + // "update_requested", then the receiver MUST send a KeyUpdate of its own + // with request_update set to "update_not_requested" prior to sending its + // next Application Data record. + // This mechanism allows either side to force an update to the entire + // connection, but causes an implementation which receives multiple + // KeyUpdates while it is silent to respond with a single update. + if(m_opportunistic_key_update) + { + update_traffic_keys(false /* update_requested */); + m_opportunistic_key_update = false; + } + + send_record(Record_Type::APPLICATION_DATA, {buf, buf+buf_size}); + } + +void Channel_Impl_13::send_alert(const Alert& alert) + { + if(alert.is_valid() && m_can_write) + { + try + { + send_record(Record_Type::ALERT, alert.serialize()); + } + catch(...) { /* swallow it */ } + } + + // Note: In TLS 1.3 sending a CLOSE_NOTIFY must not immediately lead to closing the reading end. + // RFC 8446 6.1 + // Each party MUST send a "close_notify" alert before closing its write + // side of the connection, unless it has already sent some error alert. + // This does not have any effect on its read side of the connection. + if(is_close_notify_alert(alert)) + { + m_can_write = false; + m_cipher_state->clear_write_keys(); + } + + if(is_error_alert(alert)) + { shutdown(); } + } + +bool Channel_Impl_13::is_active() const + { + return + m_cipher_state != nullptr && m_cipher_state->can_encrypt_application_traffic() // handshake done + && m_can_write; // close() hasn't been called + } + +SymmetricKey Channel_Impl_13::key_material_export(const std::string& label, + const std::string& context, + size_t length) const + { + BOTAN_STATE_CHECK(!is_downgrading()); + BOTAN_STATE_CHECK(m_cipher_state != nullptr && m_cipher_state->can_export_keys()); + return m_cipher_state->export_key(label, context, length); + } + +void Channel_Impl_13::update_traffic_keys(bool request_peer_update) + { + BOTAN_STATE_CHECK(!is_downgrading()); + BOTAN_STATE_CHECK(handshake_finished()); + send_post_handshake_message(Key_Update(request_peer_update)); + m_cipher_state->update_write_keys(); + } + +void Channel_Impl_13::send_record(uint8_t record_type, const std::vector& record) + { + BOTAN_STATE_CHECK(!is_downgrading()); + BOTAN_STATE_CHECK(m_can_write); + + auto to_write = m_record_layer.prepare_records(static_cast(record_type), record, m_cipher_state.get()); + + if(prepend_ccs()) + { + const auto ccs = m_record_layer.prepare_records(Record_Type::CHANGE_CIPHER_SPEC, {0x01}, m_cipher_state.get()); + to_write = concat(ccs, to_write); + } + + callbacks().tls_emit_data(to_write.data(), to_write.size()); + } + +void Channel_Impl_13::process_alert(const secure_vector& record) + { + Alert alert(record); + + if(is_close_notify_alert(alert)) + { + m_can_read = false; + m_cipher_state->clear_read_keys(); + m_record_layer.clear_read_buffer(); + } + + // user canceled alerts are ignored + + // TODO: the server doesn't have to expect downgrading; move this to the client + if(!expects_downgrade()) + { + // RFC 8446 5. + // All the alerts listed in Section 6.2 MUST be sent with + // AlertLevel=fatal and MUST be treated as error alerts when received + // regardless of the AlertLevel in the message. Unknown Alert types + // MUST be treated as error alerts. + if(is_error_alert(alert) && !alert.is_fatal()) + { + throw TLS_Exception(Alert::DECODE_ERROR, "Error alert not marked fatal"); // will shutdown in send_alert + } + } + else + { + // Don't immediately shut down in case we might be dealing with a TLS 1.2 server. In this case, + // we cannot immediately shut down on alerts that are warnings in TLS 1.2. + // However, if the server turns out to _not_ downgrade, treat this as an error and do shut down. + // Note that this should not happen with a valid implementation, as the TLS 1.3 server shouldn't + // send a SERVER HELLO after the alert. + if(is_error_alert(alert)) + m_downgrade_info->received_tls_13_error_alert = true; + } + + if(alert.is_fatal()) + shutdown(); + + callbacks().tls_alert(alert); + } + +void Channel_Impl_13::shutdown() + { + // RFC 8446 6.2 + // Upon transmission or receipt of a fatal alert message, both + // parties MUST immediately close the connection. + m_can_read = false; + m_can_write = false; + m_cipher_state.reset(); + } + +void Channel_Impl_13::expect_downgrade(const Server_Information& server_info) + { + Downgrade_Information di + { + {}, + {}, + server_info, + callbacks(), + session_manager(), + credentials_manager(), + rng(), + policy(), + false, // received_tls_13_error_alert + false // will_downgrade + }; + m_downgrade_info = std::make_unique(std::move(di)); + } + +void Channel_Impl_13::set_record_size_limits(const uint16_t outgoing_limit, + const uint16_t incoming_limit) + { + m_record_layer.set_record_size_limits(outgoing_limit, incoming_limit); + } + +} diff --git a/src/lib/tls/tls13/tls_channel_impl_13.h b/src/lib/tls/tls13/tls_channel_impl_13.h new file mode 100644 index 00000000000..b060beaeec7 --- /dev/null +++ b/src/lib/tls/tls13/tls_channel_impl_13.h @@ -0,0 +1,215 @@ +/* +* TLS Channel - implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_CHANNEL_IMPL_13_H_ +#define BOTAN_TLS_CHANNEL_IMPL_13_H_ + +#include +#include +#include +#include + +namespace Botan::TLS { + +/** +* Generic interface for TLS 1.3 endpoint +*/ +class Channel_Impl_13 : public Channel_Impl + { + public: + /** + * Set up a new TLS 1.3 session + * + * @param callbacks contains a set of callback function references + * required by the TLS endpoint. + * @param session_manager manages session state + * @param credentials_manager manages application/user credentials + * @param rng a random number generator + * @param policy specifies other connection policy information + * @param is_server whether this is a server session or not + */ + explicit Channel_Impl_13(Callbacks& callbacks, + Session_Manager& session_manager, + Credentials_Manager& credentials_manager, + RandomNumberGenerator& rng, + const Policy& policy, + bool is_server); + + explicit Channel_Impl_13(const Channel_Impl_13&) = delete; + + Channel_Impl_13& operator=(const Channel_Impl_13&) = delete; + + virtual ~Channel_Impl_13(); + + size_t received_data(const uint8_t buf[], size_t buf_size) override; + + /** + * Inject plaintext intended for counterparty + * Throws an exception if is_active() is false + */ + void send(const uint8_t buf[], size_t buf_size) override; + + /** + * 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) override; + + /** + * @return true iff the connection is active for sending application data + * + * Note that the connection is active until the application has called + * `close()`, even if a CLOSE_NOTIFY has been received from the peer. + */ + bool is_active() const override; + + /** + * @return true iff the connection has been closed, i.e. CLOSE_NOTIFY + * has been received from the peer. + */ + bool is_closed() const override { return !m_can_read; } + + /** + * 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 override; + + /** + * Attempt to renegotiate the session + */ + void renegotiate(bool/* unused */) override + { + throw Botan::Invalid_Argument("renegotiation is not allowed in TLS 1.3"); + } + + /** + * Attempt to update the session's traffic key material + * Note that this is possible with a TLS 1.3 channel, only. + * + * @param request_peer_update if true, require a reciprocal key update + */ + void update_traffic_keys(bool request_peer_update = false) override; + + /** + * @return true iff the counterparty supports the secure + * renegotiation extensions. + */ + bool secure_renegotiation_supported() const override + { + // No renegotiation supported in TLS 1.3 + return false; + } + + /** + * Perform a handshake timeout check. This does nothing unless + * this is a DTLS channel with a pending handshake state, in + * which case we check for timeout and potentially retransmit + * handshake packets. + * + * In the TLS 1.3 implementation, this always returns false. + */ + bool timeout_check() override { return false; } + + protected: + virtual void process_handshake_msg(Handshake_Message_13 msg) = 0; + virtual void process_post_handshake_msg(Post_Handshake_Message_13 msg) = 0; + virtual void process_dummy_change_cipher_spec() = 0; + virtual bool handshake_finished() const = 0; + + /** + * @return whether a change cipher spec record should be prepended _now_ + * + * This method can be used by subclasses to indicate that send_record + * should prepend a CCS before the actual record. This is useful for + * middlebox compatibility mode. See RFC 8446 D.4. + */ + virtual bool prepend_ccs() { return false; } + + /** + * Schedule a traffic key update to opportunistically happen before the + * channel sends application data the next time. Such a key update will + * never request a reciprocal key update from the peer. + */ + void opportunistically_update_traffic_keys() { m_opportunistic_key_update = true; } + + void send_handshake_message(const Handshake_Message_13_Ref message); + void send_post_handshake_message(const Post_Handshake_Message_13 message); + void send_dummy_change_cipher_spec(); + + Callbacks& callbacks() const { return m_callbacks; } + Session_Manager& session_manager() { return m_session_manager; } + Credentials_Manager& credentials_manager() { return m_credentials_manager; } + RandomNumberGenerator& rng() { return m_rng; } + const Policy& policy() const { return m_policy; } + + private: + void send_record(uint8_t record_type, const std::vector& record); + + void process_alert(const secure_vector& record); + + /** + * Terminate the connection (on sending or receiving an error alert) and + * clear secrets + */ + void shutdown(); + + protected: + const Connection_Side m_side; + Transcript_Hash_State m_transcript_hash; + std::unique_ptr m_cipher_state; + + /** + * Indicate that this (Client_Impl_13) instance has to expect a downgrade to TLS 1.2. + * + * This will prepare an internal structure where any information required to downgrade + * can be preserved. + * @sa `Channel_Impl::Downgrade_Information` + */ + void expect_downgrade(const Server_Information& server_info); + + /** + * Set the record size limits as negotiated by the "record_size_limit" + * extension (RFC 8449). + * + * @param outgoing_limit the maximal number of plaintext bytes to be + * sent in a protected record + * @param incoming_limit the maximal number of plaintext bytes to be + * accepted in a received protected record + */ + void set_record_size_limits(const uint16_t outgoing_limit, + const uint16_t incoming_limit); + private: + /* callbacks */ + Callbacks& m_callbacks; + + /* external state */ + Session_Manager& m_session_manager; + Credentials_Manager& m_credentials_manager; + RandomNumberGenerator& m_rng; + const Policy& m_policy; + + /* handshake state */ + Record_Layer m_record_layer; + Handshake_Layer m_handshake_layer; + + bool m_can_read; + bool m_can_write; + + bool m_opportunistic_key_update; + }; +} + +#endif diff --git a/src/lib/tls/tls13/tls_cipher_state.cpp b/src/lib/tls/tls13/tls_cipher_state.cpp new file mode 100644 index 00000000000..5e41102bf5e --- /dev/null +++ b/src/lib/tls/tls13/tls_cipher_state.cpp @@ -0,0 +1,445 @@ +/* +* TLS cipher state implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +/** + * Cipher_State state machine adapted from RFC 8446 7.1. + * + * 0 + * | + * v + * PSK -> HKDF-Extract = Early Secret + * | + * +-----> Derive-Secret(., "ext binder" | "res binder", "") + * | = binder_key + * | + * +-----> Derive-Secret(., "c e traffic", ClientHello) + * | = client_early_traffic_secret + * | + * +-----> Derive-Secret(., "e exp master", ClientHello) + * | = early_exporter_master_secret + * v + * Derive-Secret(., "derived", "") + * | + * * + * STATE EARLY TRAFFIC + * This state is reached by constructing Cipher_State using init_with_psk() (not yet implemented). + * The state can then be further advanced using advance_with_server_hello(). + * * + * | + * v + * (EC)DHE -> HKDF-Extract = Handshake Secret + * | + * +-----> Derive-Secret(., "c hs traffic", + * | ClientHello...ServerHello) + * | = client_handshake_traffic_secret + * | + * +-----> Derive-Secret(., "s hs traffic", + * | ClientHello...ServerHello) + * | = server_handshake_traffic_secret + * v + * Derive-Secret(., "derived", "") + * | + * * + * STATE HANDSHAKE TRAFFIC + * This state is reached by constructing Cipher_State using init_with_server_hello(). + * In this state the handshake traffic secrets are available. The state can then be further + * advanced using advance_with_server_finished(). + * * + * | + * v + * 0 -> HKDF-Extract = Master Secret + * | + * +-----> Derive-Secret(., "c ap traffic", + * | ClientHello...server Finished) + * | = client_application_traffic_secret_0 + * | + * +-----> Derive-Secret(., "s ap traffic", + * | ClientHello...server Finished) + * | = server_application_traffic_secret_0 + * | + * +-----> Derive-Secret(., "exp master", + * | ClientHello...server Finished) + * | = exporter_master_secret + * * + * STATE APPLICATION TRAFFIC + * This state is reached by calling advance_with_server_finished(). The state can then be further + * advanced using advance_with_client_finished(). + * * + * | + * +-----> Derive-Secret(., "res master", + * ClientHello...client Finished) + * = resumption_master_secret + * STATE COMPLETED + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace Botan; +using namespace Botan::TLS; + +namespace { +// RFC 8446 5.3 +// Each AEAD algorithm will specify a range of possible lengths for the +// per-record nonce, from N_MIN bytes to N_MAX bytes of input [RFC5116]. +// The length of the TLS per-record nonce (iv_length) is set to the +// larger of 8 bytes and N_MIN for the AEAD algorithm (see [RFC5116], +// Section 4). +// +// N_MIN is 12 for AES_GCM and AES_CCM as per RFC 5116 and also 12 for ChaCha20 per RFC 8439. +constexpr size_t NONCE_LENGTH = 12; +} + +std::unique_ptr Cipher_State::init_with_server_hello( + const Connection_Side side, + secure_vector&& shared_secret, + const Ciphersuite& cipher, + const Transcript_Hash& transcript_hash) + { + auto cs = std::unique_ptr(new Cipher_State(side, cipher)); + cs->advance_without_psk(); + cs->advance_with_server_hello(std::move(shared_secret), transcript_hash); + return cs; + } + +void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcript_hash) + { + BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic); + + zap(m_finished_key); + zap(m_peer_finished_key); + + const auto master_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); + + auto client_application_traffic_secret = derive_secret(master_secret, "c ap traffic", transcript_hash); + auto server_application_traffic_secret = derive_secret(master_secret, "s ap traffic", transcript_hash); + + if(m_connection_side == Connection_Side::SERVER) + { + derive_read_traffic_key(client_application_traffic_secret); + derive_write_traffic_key(server_application_traffic_secret); + m_read_application_traffic_secret = std::move(client_application_traffic_secret); + m_write_application_traffic_secret = std::move(server_application_traffic_secret); + } + else + { + derive_read_traffic_key(server_application_traffic_secret); + derive_write_traffic_key(client_application_traffic_secret); + m_read_application_traffic_secret = std::move(server_application_traffic_secret); + m_write_application_traffic_secret = std::move(client_application_traffic_secret); + } + + m_exporter_master_secret = derive_secret(master_secret, "exp master", transcript_hash); + + m_state = State::ApplicationTraffic; + } + +void Cipher_State::advance_with_client_finished(const Transcript_Hash& transcript_hash) + { + BOTAN_ASSERT_NOMSG(m_state == State::ApplicationTraffic); + + const auto master_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); + + m_resumption_master_secret = derive_secret(master_secret, "res master", transcript_hash); + + // This was the final state change; the salt is no longer needed. + zap(m_salt); + + m_state = State::Completed; + } + +std::vector Cipher_State::current_nonce(const uint64_t seq_no, const secure_vector& iv) const + { + // RFC 8446 5.3 + // The per-record nonce for the AEAD construction is formed as follows: + // + // 1. The 64-bit record sequence number is encoded in network byte + // order and padded to the left with zeros to iv_length. + // + // 2. The padded sequence number is XORed with either the static + // client_write_iv or server_write_iv (depending on the role). + std::vector nonce(NONCE_LENGTH); + store_be(seq_no, nonce.data() + (NONCE_LENGTH-sizeof(seq_no))); + xor_buf(nonce, iv.data(), iv.size()); + return nonce; + } + +uint64_t Cipher_State::encrypt_record_fragment(const std::vector& header, secure_vector& fragment) + { + m_encrypt->set_key(m_write_key); + m_encrypt->set_associated_data_vec(header); + m_encrypt->start(current_nonce(m_write_seq_no, m_write_iv)); + m_encrypt->finish(fragment); + + return m_write_seq_no++; + } + +uint64_t Cipher_State::decrypt_record_fragment(const std::vector& header, + secure_vector& encrypted_fragment) + { + BOTAN_ARG_CHECK(encrypted_fragment.size() >= m_decrypt->minimum_final_size(), + "fragment too short to decrypt"); + + m_decrypt->set_key(m_read_key); + m_decrypt->set_associated_data_vec(header); + m_decrypt->start(current_nonce(m_read_seq_no, m_read_iv)); + + m_decrypt->finish(encrypted_fragment); + + return m_read_seq_no++; + } + +size_t Cipher_State::encrypt_output_length(const size_t input_length) const + { + return m_encrypt->output_length(input_length); + } + +size_t Cipher_State::decrypt_output_length(const size_t input_length) const + { + return m_decrypt->output_length(input_length); + } + +size_t Cipher_State::minimum_decryption_input_length() const + { + return m_decrypt->minimum_final_size(); + } + +std::vector Cipher_State::finished_mac(const Transcript_Hash& transcript_hash) const + { + BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic); + + auto hmac = HMAC(m_hash->new_object()); + hmac.set_key(m_finished_key); + hmac.update(transcript_hash); + return hmac.final_stdvec(); + } + +bool Cipher_State::verify_peer_finished_mac(const Transcript_Hash& transcript_hash, + const std::vector& peer_mac) const + { + BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic); + + auto hmac = HMAC(m_hash->new_object()); + hmac.set_key(m_peer_finished_key); + hmac.update(transcript_hash); + return hmac.verify_mac(peer_mac); + } + +secure_vector Cipher_State::psk(const std::vector& nonce) const + { + BOTAN_ASSERT_NOMSG(m_state == State::Completed); + + return derive_secret(m_resumption_master_secret, "resumption", nonce); + } + + +secure_vector Cipher_State::export_key(const std::string& label, + const std::string& context, + size_t length) const + { + BOTAN_ASSERT_NOMSG(can_export_keys()); + + m_hash->update(context); + const auto context_hash = m_hash->final_stdvec(); + return hkdf_expand_label(derive_secret(m_exporter_master_secret, label, empty_hash()), + "exporter", context_hash, length); + } + + +namespace { + +std::unique_ptr create_hmac(const Ciphersuite& cipher) + { + return std::make_unique(HashFunction::create_or_throw(cipher.prf_algo())); + } + +} + +Cipher_State::Cipher_State(Connection_Side whoami, const Ciphersuite& cipher) + : m_state(State::Uninitialized) + , m_connection_side(whoami) + , m_encrypt(AEAD_Mode::create(cipher.cipher_algo(), ENCRYPTION)) + , m_decrypt(AEAD_Mode::create(cipher.cipher_algo(), DECRYPTION)) + , m_extract(std::make_unique(create_hmac(cipher))) + , m_expand(std::make_unique(create_hmac(cipher))) + , m_hash(HashFunction::create_or_throw(cipher.prf_algo())) + , m_salt(m_hash->output_length(), 0x00) + , m_write_seq_no(0) + , m_read_seq_no(0) {} + +Cipher_State::~Cipher_State() = default; + +void Cipher_State::advance_without_psk() + { + BOTAN_ASSERT_NOMSG(m_state == State::Uninitialized); + + const auto early_secret = hkdf_extract(secure_vector(m_hash->output_length(), 0x00)); + m_salt = derive_secret(early_secret, "derived", empty_hash()); + + m_state = State::EarlyTraffic; + } + +void Cipher_State::advance_with_server_hello(secure_vector&& shared_secret, + const Transcript_Hash& transcript_hash) + { + BOTAN_ASSERT_NOMSG(m_state == State::EarlyTraffic); + + const auto handshake_secret = hkdf_extract(std::move(shared_secret)); + + const auto client_handshake_traffic_secret = derive_secret(handshake_secret, "c hs traffic", transcript_hash); + const auto server_handshake_traffic_secret = derive_secret(handshake_secret, "s hs traffic", transcript_hash); + + if(m_connection_side == Connection_Side::SERVER) + { + derive_read_traffic_key(client_handshake_traffic_secret, true); + derive_write_traffic_key(server_handshake_traffic_secret, true); + } + else + { + derive_read_traffic_key(server_handshake_traffic_secret, true); + derive_write_traffic_key(client_handshake_traffic_secret, true); + } + + m_salt = derive_secret(handshake_secret, "derived", empty_hash()); + + m_state = State::HandshakeTraffic; + } + +void Cipher_State::derive_write_traffic_key(const secure_vector& traffic_secret, + const bool handshake_traffic_secret) + { + m_write_key = hkdf_expand_label(traffic_secret, "key", {}, m_encrypt->minimum_keylength()); + m_write_iv = hkdf_expand_label(traffic_secret, "iv", {}, NONCE_LENGTH); + m_write_seq_no = 0; + + if(handshake_traffic_secret) + { + // Key derivation for the MAC in the "Finished" handshake message as described in RFC 8446 4.4.4 + // (will be cleared in advance_with_server_finished()) + m_finished_key = hkdf_expand_label(traffic_secret, "finished", {}, m_hash->output_length()); + } + } + +void Cipher_State::derive_read_traffic_key(const secure_vector& traffic_secret, + const bool handshake_traffic_secret) + { + m_read_key = hkdf_expand_label(traffic_secret, "key", {}, m_encrypt->minimum_keylength()); + m_read_iv = hkdf_expand_label(traffic_secret, "iv", {}, NONCE_LENGTH); + m_read_seq_no = 0; + + if(handshake_traffic_secret) + { + // Key derivation for the MAC in the "Finished" handshake message as described in RFC 8446 4.4.4 + // (will be cleared in advance_with_server_finished()) + m_peer_finished_key = hkdf_expand_label(traffic_secret, "finished", {}, m_hash->output_length()); + } + } + +secure_vector Cipher_State::hkdf_extract(secure_vector&& ikm) const + { + return m_extract->derive_key(m_hash->output_length(), ikm, m_salt, std::vector()); + } + +secure_vector Cipher_State::hkdf_expand_label( + const secure_vector& secret, + const std::string& label, + const std::vector& context, + const size_t length) const + { + // assemble (serialized) HkdfLabel + secure_vector hkdf_label; + hkdf_label.reserve(2 /* length */ + + (label.size() + + 6 /* 'tls13 ' */ + + 1 /* length field*/) + + (context.size() + + 1 /* length field*/)); + + // length + BOTAN_ARG_CHECK(length <= std::numeric_limits::max(), "invalid length"); + const auto len = static_cast(length); + hkdf_label.push_back(get_byte<0>(len)); + hkdf_label.push_back(get_byte<1>(len)); + + // label + const std::string prefix = "tls13 "; + BOTAN_ARG_CHECK(prefix.size() + label.size() <= 255, "label too large"); + hkdf_label.push_back(static_cast(prefix.size() + label.size())); + hkdf_label.insert(hkdf_label.end(), prefix.cbegin(), prefix.cend()); + hkdf_label.insert(hkdf_label.end(), label.cbegin(), label.cend()); + + // context + BOTAN_ARG_CHECK(context.size() <= 255, "context too large"); + hkdf_label.push_back(static_cast(context.size())); + hkdf_label.insert(hkdf_label.end(), context.cbegin(), context.cend()); + + // HKDF-Expand + return m_expand->derive_key(length, secret, hkdf_label, std::vector() /* just pleasing botan's interface */); + } + +secure_vector Cipher_State::derive_secret( + const secure_vector& secret, + const std::string& label, + const Transcript_Hash& messages_hash) const + { + return hkdf_expand_label(secret, label, messages_hash, m_hash->output_length()); + } + +std::vector Cipher_State::empty_hash() const + { + m_hash->update(""); + return m_hash->final_stdvec(); + } + +void Cipher_State::update_read_keys() + { + BOTAN_ASSERT_NOMSG(m_state == State::ApplicationTraffic || + m_state == State::Completed); + + m_read_application_traffic_secret = + hkdf_expand_label(m_read_application_traffic_secret, "traffic upd", {}, m_hash->output_length()); + + derive_read_traffic_key(m_read_application_traffic_secret); + } + +void Cipher_State::update_write_keys() + { + BOTAN_ASSERT_NOMSG(m_state == State::ApplicationTraffic || + m_state == State::Completed); + m_write_application_traffic_secret = + hkdf_expand_label(m_write_application_traffic_secret, "traffic upd", {}, m_hash->output_length()); + + derive_write_traffic_key(m_write_application_traffic_secret); + } + +void Cipher_State::clear_read_keys() + { + zap(m_read_key); + zap(m_read_iv); + zap(m_read_application_traffic_secret); + } + +void Cipher_State::clear_write_keys() + { + zap(m_write_key); + zap(m_write_iv); + zap(m_write_application_traffic_secret); + } diff --git a/src/lib/tls/tls13/tls_cipher_state.h b/src/lib/tls/tls13/tls_cipher_state.h new file mode 100644 index 00000000000..ab0c7686165 --- /dev/null +++ b/src/lib/tls/tls13/tls_cipher_state.h @@ -0,0 +1,287 @@ +/* +* TLS cipher state implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_CIPHER_STATE_H_ +#define BOTAN_TLS_CIPHER_STATE_H_ + +#include +#include + +#include + +namespace Botan { + +class AEAD_Mode; +class HashFunction; +class HKDF_Extract; +class HKDF_Expand; + +namespace TLS { +class Ciphersuite; +} +} + +namespace Botan::TLS { + +/** + * This class implements the key schedule for TLS 1.3 as described in RFC 8446 7.1. + * + * Internally, it reflects the state machine pictured in the same RFC section. + * It provides the following entry points and state advancement methods that + * each facilitate certain cryptographic functionality: + * + * * init_with_psk() + * not yet implemented + * + * * init_with_server_hello() / advance_with_server_hello() + * allows encrypting and decrypting handshake traffic, as well as producing + * and validating the client/server handshake finished MACs + * + * * advance_with_server_finished() + * allows encrypting and decrypting application traffic + * + * * advance_with_client_finished() + * allows negotiation of resumption PSKs + * + * While encrypting and decrypting records (RFC 8446 5.2) Cipher_State + * internally keeps track of the current sequence numbers (RFC 8446 5.3) to + * calculate the correct Per-Record Nonce. Sequence numbers are reset + * appropriately, whenever traffic secrets change. + * + * Handshake finished MAC calculation and verification is described in RFC 8446 4.4.4. + * + * PSKs calculation is described in RFC 8446 4.6.1. + */ +class BOTAN_TEST_API Cipher_State + { + public: + ~Cipher_State(); + + /** + * Construct a Cipher_State after receiving a server hello message. + */ + static std::unique_ptr init_with_server_hello( + const Connection_Side side, + secure_vector&& shared_secret, + const Ciphersuite& cipher, + const Transcript_Hash& transcript_hash); + + /** + * Transition internal secrets/keys for transporting application data. + */ + void advance_with_server_finished(const Transcript_Hash& transcript_hash); + + /** + * Transition to the final internal state allowing to create resumptions. + */ + void advance_with_client_finished(const Transcript_Hash& transcript_hash); + + /** + * Encrypt a TLS record fragment (RFC 8446 5.2 -- TLSInnerPlaintext) using the + * currently available traffic secret keys and the current sequence number. + * This will internally increment the sequence number. Hence, multiple + * calls with the same input will not produce the same result. + * + * @returns the sequence number of the encrypted record + */ + uint64_t encrypt_record_fragment(const std::vector& header, secure_vector& fragment); + + /** + * Decrypt a TLS record fragment (RFC 8446 5.2 -- TLSCiphertext.encrypted_record) + * using the currently available traffic secret keys and the current sequence number. + * This will internally increment the sequence number. Hence, multiple + * calls with the same input will not produce the same result. + * + * @returns the sequence number of the decrypted record + */ + uint64_t decrypt_record_fragment(const std::vector& header, secure_vector& encrypted_fragment); + + /** + * @returns number of bytes needed to encrypt \p input_length bytes + */ + size_t encrypt_output_length(const size_t input_length) const; + + /** + * @returns number of bytes needed to decrypt \p input_length bytes + */ + size_t decrypt_output_length(const size_t input_length) const; + + /** + * @returns the minimum ciphertext length for decryption + */ + size_t minimum_decryption_input_length() const; + + /** + * Calculate the MAC for a TLS "Finished" handshake message (RFC 8446 4.4.4) + */ + std::vector finished_mac(const Transcript_Hash& transcript_hash) const; + + /** + * Validate a MAC received in a TLS "Finished" handshake message (RFC 8446 4.4.4) + */ + bool verify_peer_finished_mac(const Transcript_Hash& transcript_hash, + const std::vector& peer_mac) const; + + /** + * Calculate the PSK for the given nonce (RFC 8446 4.6.1) + */ + secure_vector psk(const std::vector& nonce) const; + + /** + * Derive key material to export (RFC 8446 7.5 and RFC 5705) + * + * TODO: this does not yet support key export based on the `early_exporter_master_secret`. + * + * RFC 8446 7.5 + * Implementations MUST use the exporter_master_secret unless explicitly + * specified by the application. The early_exporter_master_secret is + * defined for use in settings where an exporter is needed for 0-RTT data. + * A separate interface for the early exporter is RECOMMENDED [...]. + * + * @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 + */ + secure_vector export_key(const std::string& label, + const std::string& context, + size_t length) const; + + /** + * Indicates whether the appropriate secrets to export keys are available + */ + bool can_export_keys() const + { + return (m_state == State::ApplicationTraffic || m_state == State::Completed) && + !m_exporter_master_secret.empty(); + } + + /** + * Indicates whether the appropriate secrets to encrypt application traffic are available + */ + bool can_encrypt_application_traffic() const + { + return m_state != State::Uninitialized && m_state != State::HandshakeTraffic + && !m_write_key.empty() && !m_write_iv.empty(); + } + + /** + * Updates the key material used for decrypting data + * This is triggered after we received a Key_Update from the peer. + * + * Note that this must not be called before the connection is ready for + * application traffic. + */ + void update_read_keys(); + + /** + * Updates the key material used for encrypting data + * This is triggered after we send a Key_Update to the peer. + * + * Note that this must not be called before the connection is ready for + * application traffic. + */ + void update_write_keys(); + + /** + * Remove handshake/traffic secrets for decrypting data from peer + */ + void clear_read_keys(); + + /** + * Remove handshake/traffic secrets for encrypting data + */ + void clear_write_keys(); + + private: + /** + * @param cipher the negotiated cipher suite + * @param whoami whether we play the SERVER or CLIENT + */ + Cipher_State(Connection_Side whoami, const Ciphersuite& cipher); + + void advance_without_psk(); + + void advance_with_server_hello(secure_vector&& shared_secret, + const Transcript_Hash& transcript_hash); + + std::vector current_nonce(const uint64_t seq_no, + const secure_vector& iv) const; + + void derive_write_traffic_key(const secure_vector& traffic_secret, + const bool handshake_traffic_secret = false); + void derive_read_traffic_key(const secure_vector& traffic_secret, + const bool handshake_traffic_secret = false); + + /** + * HKDF-Extract from RFC 8446 7.1 + */ + secure_vector hkdf_extract(secure_vector&& ikm) const; + + /** + * HKDF-Expand-Label from RFC 8446 7.1 + */ + secure_vector hkdf_expand_label( + const secure_vector& secret, + const std::string& label, + const std::vector& context, + const size_t length) const; + + /** + * Derive-Secret from RFC 8446 7.1 + */ + secure_vector derive_secret( + const secure_vector& secret, + const std::string& label, + const Transcript_Hash& messages_hash) const; + + std::vector empty_hash() const; + + private: + enum class State + { + Uninitialized, + EarlyTraffic, + HandshakeTraffic, + ApplicationTraffic, + Completed + }; + + private: + State m_state; + Connection_Side m_connection_side; + + std::unique_ptr m_encrypt; + std::unique_ptr m_decrypt; + + std::unique_ptr m_extract; + std::unique_ptr m_expand; + std::unique_ptr m_hash; + + secure_vector m_salt; + + secure_vector m_write_application_traffic_secret; + secure_vector m_read_application_traffic_secret; + + secure_vector m_write_key; + secure_vector m_write_iv; + secure_vector m_read_key; + secure_vector m_read_iv; + + uint64_t m_write_seq_no; + uint64_t m_read_seq_no; + + secure_vector m_finished_key; + secure_vector m_peer_finished_key; + secure_vector m_exporter_master_secret; + secure_vector m_resumption_master_secret; + }; + +} + +#endif // BOTAN_TLS_CIPHER_STATE_H_ diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp new file mode 100644 index 00000000000..90fb701466a --- /dev/null +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -0,0 +1,469 @@ +/* +* TLS Client - implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Botan::TLS { + +Client_Impl_13::Client_Impl_13(Callbacks& callbacks, + Session_Manager& session_manager, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng, + const Server_Information& info, + const std::vector& next_protocols) : + Channel_Impl_13(callbacks, session_manager, creds, rng, policy, false /* is_server */), + m_info(info), + m_should_send_ccs(false) + { +#if defined(BOTAN_HAS_TLS_12) + if(policy.allow_tls12()) + { expect_downgrade(info); } +#endif + + send_handshake_message(m_handshake_state.sent(Client_Hello_13( + policy, + callbacks, + rng, + m_info.hostname(), + next_protocols))); + + // RFC 8446 Appendix D.4 + // If not offering early data, the client sends a dummy change_cipher_spec + // record [...] immediately before its second flight. This may either be before + // its second ClientHello or before its encrypted handshake flight. + // + // TODO: don't schedule ccs here when early data is used + if(policy.tls_13_middlebox_compatibility_mode()) + m_should_send_ccs = true; + + m_transitions.set_expected_next({SERVER_HELLO, HELLO_RETRY_REQUEST}); + } + +void Client_Impl_13::process_handshake_msg(Handshake_Message_13 message) + { + std::visit([&](auto msg) + { + // first verify that the message was expected by the state machine... + m_transitions.confirm_transition_to(msg.get().type()); + + // ... then allow the library user to abort on their discretion + callbacks().tls_inspect_handshake_msg(msg.get()); + + // ... finally handle the message + handle(msg.get()); + }, m_handshake_state.received(std::move(message))); + } + +void Client_Impl_13::process_post_handshake_msg(Post_Handshake_Message_13 message) + { + BOTAN_STATE_CHECK(handshake_finished()); + + std::visit([&](auto msg) + { + handle(msg); + }, std::move(message)); + } + +void Client_Impl_13::process_dummy_change_cipher_spec() + { + // RFC 8446 5. + // If an implementation detects a change_cipher_spec record received before + // the first ClientHello message or after the peer's Finished message, it MUST be + // treated as an unexpected record type [("unexpected_message" alert)]. + if(!m_handshake_state.has_client_hello() || m_handshake_state.has_server_finished()) + { + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Received an unexpected dummy Change Cipher Spec"); + } + + // RFC 8446 5. + // An implementation may receive an unencrypted record of type change_cipher_spec [...] + // at any time after the first ClientHello message has been sent or received + // and before the peer's Finished message has been received [...] + // and MUST simply drop it without further processing. + // + // ... no further processing. + } + +bool Client_Impl_13::handshake_finished() const + { + return m_handshake_state.handshake_finished(); + } + +void Client_Impl_13::handle(const Server_Hello_12& server_hello_msg) + { + if(m_handshake_state.has_hello_retry_request()) + { + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Version downgrade received after Hello Retry"); + } + + // RFC 8446 4.1.3 + // TLS 1.3 has a downgrade protection mechanism embedded in the server's + // random value. TLS 1.3 servers which negotiate TLS 1.2 or below in + // response to a ClientHello MUST set the last 8 bytes of their Random + // value specially in their ServerHello. + // + // TLS 1.3 clients receiving a ServerHello indicating TLS 1.2 or below + // MUST check that the [downgrade indication is not set]. [...] If a match + // is found, the client MUST abort the handshake with an + // "illegal_parameter" alert. + if(server_hello_msg.random_signals_downgrade().has_value()) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Downgrade attack detected"); + } + + // RFC 8446 4.2.1 + // A server which negotiates a version of TLS prior to TLS 1.3 [...] + // MUST NOT send the "supported_versions" extension. + // + // Note that this condition should never happen, as the Server_Hello parsing + // code decides to create a Server_Hello_12 based on the absense of this extension. + if(server_hello_msg.extensions().has()) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Unexpected extension received"); + } + + // RFC 8446 Appendix D.1 + // If the version chosen by the server is not supported by the client + // (or is not acceptable), the client MUST abort the handshake with a + // "protocol_version" alert. + const auto& client_hello_exts = m_handshake_state.client_hello().extensions(); + BOTAN_ASSERT_NOMSG(client_hello_exts.has()); + if(!client_hello_exts.get()->supports(server_hello_msg.selected_version())) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, "Protocol version was not offered"); + } + + if(policy().tls_13_middlebox_compatibility_mode() && + m_handshake_state.client_hello().session_id() == server_hello_msg.session_id()) + { + // In compatibility mode, the server will reflect the session ID we sent in the client hello. + // However, a TLS 1.2 server that wants to downgrade cannot have found the random session ID + // we sent. Therefore, we have to consider this as an attack. + // (Thanks BoGo test EchoTLS13CompatibilitySessionID!) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Unexpected session ID during downgrade"); + } + + BOTAN_ASSERT_NOMSG(expects_downgrade()); + + // After this, no further messages are expected here because this instance will be replaced + // by a Client_Impl_12. + m_transitions.set_expected_next({}); + } + +namespace { +// validate Server_Hello_13 and Hello_Retry_Request +void validate_server_hello_ish(const Client_Hello_13& ch, const Server_Hello_13& sh) + { + // RFC 8446 4.1.3 + // A client which receives a legacy_session_id_echo field that does not match what + // it sent in the ClientHello MUST abort the handshake with an "illegal_parameter" alert. + if(ch.session_id() != sh.session_id()) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "echoed session id did not match"); + } + + // RFC 8446 4.1.3 + // A client which receives a cipher suite that was not offered MUST abort the handshake + // with an "illegal_parameter" alert. + if(!ch.offered_suite(sh.ciphersuite())) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Server replied with ciphersuite we didn't send"); + } + + // RFC 8446 4.2.1 + // If the "supported_versions" extension in the ServerHello contains a + // version not offered by the client or contains a version prior to + // TLS 1.3, the client MUST abort the handshake with an "illegal_parameter" alert. + // + // Note: Server_Hello_13 parsing checks that its selected version is TLS 1.3 + BOTAN_ASSERT_NOMSG(ch.extensions().has()); + if(!ch.extensions().get()->supports(sh.selected_version())) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Protocol version was not offered"); + } + } +} + +void Client_Impl_13::handle(const Server_Hello_13& sh) + { + // Note: Basic checks (that do not require contextual information) were already + // performed during the construction of the Server_Hello_13 object. + + const auto& ch = m_handshake_state.client_hello(); + + validate_server_hello_ish(ch, sh); + + // RFC 8446 4.2 + // Implementations MUST NOT send extension responses if the remote + // endpoint did not send the corresponding extension requests, [...]. Upon + // receiving such an extension, an endpoint MUST abort the handshake + // with an "unsupported_extension" alert. + if(sh.extensions().contains_other_than(ch.extensions().extension_types())) + { + throw TLS_Exception(Alert::UNSUPPORTED_EXTENSION, "Unsupported extension found in Server Hello"); + } + + if(m_handshake_state.has_hello_retry_request()) + { + const auto& hrr = m_handshake_state.hello_retry_request(); + + // RFC 8446 4.1.4 + // Upon receiving the ServerHello, clients MUST check that the cipher suite + // supplied in the ServerHello is the same as that in the HelloRetryRequest + // and otherwise abort the handshake with an "illegal_parameter" alert. + if(hrr.ciphersuite() != sh.ciphersuite()) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "server changed its chosen ciphersuite"); + } + + // RFC 8446 4.1.4 + // The value of selected_version in the HelloRetryRequest "supported_versions" + // extension MUST be retained in the ServerHello, and a client MUST abort the + // handshake with an "illegal_parameter" alert if the value changes. + if(hrr.selected_version() != sh.selected_version()) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "server changed its chosen protocol version"); + } + } + + auto cipher = Ciphersuite::by_id(sh.ciphersuite()); + BOTAN_ASSERT_NOMSG(cipher.has_value()); // should work, since we offered this suite + + // RFC 8446 Appendix B.4 + // Although TLS 1.3 uses the same cipher suite space as previous versions + // of TLS [...] cipher suites for TLS 1.2 and lower cannot be used with + // TLS 1.3. + if(!cipher->usable_in_version(Protocol_Version::TLS_V13)) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Server replied using a ciphersuite not allowed in version it offered"); + } + + if(!sh.extensions().has()) + { + throw Not_Implemented("PSK mode (without key agreement) is NYI"); + } + + // TODO: this is assuming a standard handshake without any PSK mode! + BOTAN_ASSERT_NOMSG(ch.extensions().has()); + auto my_keyshare = ch.extensions().get(); + auto shared_secret = my_keyshare->exchange(*sh.extensions().get(), policy(), callbacks(), rng()); + my_keyshare->erase(); + + m_transcript_hash.set_algorithm(cipher.value().prf_algo()); + + m_cipher_state = Cipher_State::init_with_server_hello(m_side, + std::move(shared_secret), + cipher.value(), + m_transcript_hash.current()); + + callbacks().tls_examine_extensions(sh.extensions(), SERVER); + + m_transitions.set_expected_next(ENCRYPTED_EXTENSIONS); + } + +void Client_Impl_13::handle(const Hello_Retry_Request& hrr) + { + // Note: Basic checks (that do not require contextual information) were already + // performed during the construction of the Hello_Retry_Request object as + // a subclass of Server_Hello_13. + + auto& ch = m_handshake_state.client_hello(); + + validate_server_hello_ish(ch, hrr); + + // RFC 8446 4.1.4. + // A HelloRetryRequest MUST NOT contain any + // extensions that were not first offered by the client in its + // ClientHello, with the exception of optionally the "cookie". + auto allowed_exts = ch.extensions().extension_types(); + allowed_exts.insert(TLSEXT_COOKIE); + if(hrr.extensions().contains_other_than(allowed_exts)) + { + throw TLS_Exception(Alert::UNSUPPORTED_EXTENSION, "Unsupported extension found in Hello Retry Request"); + } + + auto cipher = Ciphersuite::by_id(hrr.ciphersuite()); + BOTAN_ASSERT_NOMSG(cipher.has_value()); // should work, since we offered this suite + + m_transcript_hash = Transcript_Hash_State::recreate_after_hello_retry_request(cipher.value().prf_algo(), + m_transcript_hash); + + ch.retry(hrr, callbacks(), rng()); + + callbacks().tls_examine_extensions(hrr.extensions(), SERVER); + + send_handshake_message(ch); + + // RFC 8446 4.1.4 + // If a client receives a second HelloRetryRequest in the same connection [...], + // it MUST abort the handshake with an "unexpected_message" alert. + m_transitions.set_expected_next(SERVER_HELLO); + } + +void Client_Impl_13::handle(const Encrypted_Extensions& encrypted_extensions_msg) + { + // RFC 8446 4.2 + // Implementations MUST NOT send extension responses if the remote + // endpoint did not send the corresponding extension requests, [...]. Upon + // receiving such an extension, an endpoint MUST abort the handshake + // with an "unsupported_extension" alert. + const auto& requested_exts = m_handshake_state.client_hello().extensions().extension_types(); + if(encrypted_extensions_msg.extensions().contains_other_than(requested_exts)) + { throw TLS_Exception(Alert::UNSUPPORTED_EXTENSION, + "Encrypted Extensions contained an extension that was not offered"); } + + // Note: As per RFC 6066 3. we can check for an empty SNI extensions to + // determine if the server used the SNI we sent here. + + if(encrypted_extensions_msg.extensions().has() && + m_handshake_state.client_hello().extensions().has()) + { + // RFC 8449 4. + // The record size limit only applies to records sent toward the + // endpoint that advertises the limit. An endpoint can send records + // that are larger than the limit it advertises as its own limit. + // + // Hence, the "outgoing" limit is what the server requested and the + // "incoming" limit is what we requested in the Client Hello. + const auto outgoing_limit = encrypted_extensions_msg.extensions().get(); + const auto incoming_limit = m_handshake_state.client_hello().extensions().get(); + set_record_size_limits(outgoing_limit->limit(), incoming_limit->limit()); + } + + callbacks().tls_examine_extensions(encrypted_extensions_msg.extensions(), SERVER); + + bool psk_mode = false; // TODO + if(psk_mode) + { + m_transitions.set_expected_next(FINISHED); + } + else + { + m_transitions.set_expected_next({CERTIFICATE, CERTIFICATE_REQUEST}); + } + } + +void Client_Impl_13::handle(const Certificate_13& certificate_msg) + { + certificate_msg.validate_extensions(m_handshake_state.client_hello().extensions().extension_types()); + certificate_msg.verify(callbacks(), + policy(), + credentials_manager(), + m_info.hostname(), + m_handshake_state.client_hello().extensions().has()); + + m_transitions.set_expected_next(CERTIFICATE_VERIFY); + } + +void Client_Impl_13::handle(const Certificate_Verify_13& certificate_verify_msg) + { + bool sig_valid = certificate_verify_msg.verify( + m_handshake_state.certificate().cert_chain().front().certificate, + m_handshake_state.client_hello().signature_schemes(), + callbacks(), + m_transcript_hash.previous()); + + if(!sig_valid) + { throw TLS_Exception(Alert::DECRYPT_ERROR, "Server certificate verification failed"); } + + m_transitions.set_expected_next(FINISHED); + } + +void Client_Impl_13::handle(const Finished_13& finished_msg) + { + // RFC 8446 4.4.4 + // Recipients of Finished messages MUST verify that the contents are + // correct and if incorrect MUST terminate the connection with a + // "decrypt_error" alert. + if(!finished_msg.verify(m_cipher_state.get(), + m_transcript_hash.previous())) + { throw TLS_Exception(Alert::DECRYPT_ERROR, "Finished message didn't verify"); } + + // send client finished handshake message (still using handshake traffic secrets) + send_handshake_message(m_handshake_state.sent(Finished_13(m_cipher_state.get(), + m_transcript_hash.current()))); + + // derives the application traffic secrets and _replaces_ the handshake traffic secrets + // Note: this MUST happen AFTER the client finished message was sent! + m_cipher_state->advance_with_server_finished(m_transcript_hash.previous()); + m_cipher_state->advance_with_client_finished(m_transcript_hash.current()); + + // TODO: save session and invoke tls_session_established callback + + // no more handshake messages expected + m_transitions.set_expected_next({}); + + callbacks().tls_session_activated(); + } + +void TLS::Client_Impl_13::handle(const New_Session_Ticket_13&) + { + // TODO: resumption is not yet implemented, hence we ignore session tickets + // received from the server. + } + +void TLS::Client_Impl_13::handle(const Key_Update& key_update) + { + m_cipher_state->update_read_keys(); + + // TODO: introduce some kind of rate limit of key updates, otherwise we + // might be forced into an endless loop of key updates. + + // RFC 8446 4.6.3 + // If the request_update field is set to "update_requested", then the + // receiver MUST send a KeyUpdate of its own with request_update set to + // "update_not_requested" prior to sending its next Application Data + // record. + if(key_update.expects_reciprocation()) + { + // RFC 8446 4.6.3 + // This mechanism allows either side to force an update to the + // multiple KeyUpdates while it is silent to respond with a single + // update. + opportunistically_update_traffic_keys(); + } + } + +std::vector Client_Impl_13::peer_cert_chain() const + { + throw Not_Implemented("peer cert chain is not implemented"); + return std::vector(); + } + +bool Client_Impl_13::prepend_ccs() + { + return std::exchange(m_should_send_ccs, false); // test-and-set + } + +std::string Client_Impl_13::application_protocol() const + { + if(m_handshake_state.handshake_finished()) + { + const auto& eee = m_handshake_state.encrypted_extensions().extensions(); + if(eee.has()) + { + return eee.get()->single_protocol(); + } + } + + return ""; + } + + +} diff --git a/src/lib/tls/tls13/tls_client_impl_13.h b/src/lib/tls/tls13/tls_client_impl_13.h new file mode 100644 index 00000000000..03b15cb3c11 --- /dev/null +++ b/src/lib/tls/tls13/tls_client_impl_13.h @@ -0,0 +1,97 @@ +/* +* TLS Client - implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2021 Elektrobit Automotive GmbH +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_CLIENT_IMPL_13_H_ +#define BOTAN_TLS_CLIENT_IMPL_13_H_ + +#include +#include +#include +#include + +namespace Botan { + +class Credentials_Manager; +namespace TLS { + +/** +* SSL/TLS Client 1.3 implementation +*/ +class Client_Impl_13 : public Channel_Impl_13 + { + public: + + /** + * Set up a new TLS client session + * + * @param callbacks contains a set of callback function references + * required by the TLS client. + * + * @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 next_protocols specifies protocols to advertise with ALPN + */ + explicit Client_Impl_13(Callbacks& callbacks, + Session_Manager& session_manager, + Credentials_Manager& creds, + const Policy& policy, + RandomNumberGenerator& rng, + const Server_Information& server_info = Server_Information(), + const std::vector& next_protocols = {}); + + /** + * @return network protocol as advertised by the TLS server, if server sent the ALPN extension + */ + std::string application_protocol() const override; + + /** + * @return certificate chain of the peer (may be empty) + */ + std::vector peer_cert_chain() const override; + + private: + void process_handshake_msg(Handshake_Message_13 msg) override; + void process_post_handshake_msg(Post_Handshake_Message_13 msg) override; + void process_dummy_change_cipher_spec() override; + + bool handshake_finished() const override; + bool prepend_ccs() override; + + void handle(const Server_Hello_12& server_hello_msg); + void handle(const Server_Hello_13& server_hello_msg); + void handle(const Hello_Retry_Request& hrr_msg); + void handle(const Encrypted_Extensions& encrypted_extensions_msg); + void handle(const Certificate_13& certificate_msg); + void handle(const Certificate_Verify_13& certificate_verify_msg); + void handle(const Finished_13& finished_msg); + void handle(const New_Session_Ticket_13& new_session_ticket); + void handle(const Key_Update& key_update); + + private: + const Server_Information m_info; + + Client_Handshake_State_13 m_handshake_state; + Handshake_Transitions m_transitions; + + bool m_should_send_ccs; + }; + +} + +} + +#endif diff --git a/src/lib/tls/tls13/tls_handshake_layer_13.cpp b/src/lib/tls/tls13/tls_handshake_layer_13.cpp new file mode 100644 index 00000000000..5b99839d100 --- /dev/null +++ b/src/lib/tls/tls13/tls_handshake_layer_13.cpp @@ -0,0 +1,197 @@ +/* +* TLS handshake state (machine) implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include +#include + +#include +#include +#include +#include + +namespace Botan::TLS { + +void Handshake_Layer::copy_data(const std::vector& data_from_peer) + { + m_read_buffer.insert(m_read_buffer.end(), data_from_peer.cbegin(), data_from_peer.cend()); + } + +namespace { + +constexpr size_t HEADER_LENGTH = 4; + +template +Handshake_Type handshake_type_from_byte(uint8_t type) + { + if constexpr(std::is_same_v) + { + switch(type) + { + case CLIENT_HELLO: + case SERVER_HELLO: + case END_OF_EARLY_DATA: + case ENCRYPTED_EXTENSIONS: + case CERTIFICATE: + case CERTIFICATE_REQUEST: + case CERTIFICATE_VERIFY: + case FINISHED: + return Handshake_Type(type); + } + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Unknown handshake message received"); + } + else + { + switch(type) + { + case NEW_SESSION_TICKET: + case KEY_UPDATE: + return Handshake_Type(type); + } + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Unknown post-handshake message received"); + } + } + +template +std::optional parse_message(TLS::TLS_Data_Reader& reader, const Policy& policy, + const Connection_Side peer_side) + { + // read the message header + if(reader.remaining_bytes() < HEADER_LENGTH) + { return std::nullopt; } + + Handshake_Type type = handshake_type_from_byte(reader.get_byte()); + + // make sure we have received the full message + const size_t msg_len = reader.get_uint24_t(); + if(reader.remaining_bytes() < msg_len) + { return std::nullopt; } + + // create the message + const auto msg = reader.get_fixed(msg_len); + if constexpr(std::is_same_v) + { + switch(type) + { + case CLIENT_HELLO: + return Client_Hello_13(msg); + case SERVER_HELLO: + // SERVER_HELLO might be either an actual server_hello (1.2 or 1.3) or a + // hello_retry_request. Hence, this construction is exceptionally + // funneled through a factory method and then transformed into a + // generic Handshake_Message_13. + return std::visit([](auto message) -> Handshake_Message_13 + { return message; }, Server_Hello_13::parse(msg)); + // case END_OF_EARLY_DATA: + // return End_Of_Early_Data(msg); + case ENCRYPTED_EXTENSIONS: + return Encrypted_Extensions(msg); + case CERTIFICATE: + return Certificate_13(msg, policy, peer_side); + // case CERTIFICATE_REQUEST: + // return Certificate_Req_13(msg); + case CERTIFICATE_VERIFY: + return Certificate_Verify_13(msg, peer_side); + case FINISHED: + return Finished_13(msg); + default: + BOTAN_ASSERT(false, "cannot be reached"); // make sure to update handshake_type_from_byte + } + } + else + { + BOTAN_UNUSED(peer_side); + + switch(type) + { + case NEW_SESSION_TICKET: + return New_Session_Ticket_13(msg); + case KEY_UPDATE: + return Key_Update(msg); + default: + BOTAN_ASSERT(false, "cannot be reached"); // make sure to update handshake_type_from_byte + } + } + } + +} // namespace + +std::optional Handshake_Layer::next_message(const Policy& policy, + Transcript_Hash_State& transcript_hash) + { + TLS::TLS_Data_Reader reader("handshake message", m_read_buffer); + + auto msg = parse_message(reader, policy, m_peer); + if(msg.has_value()) + { + BOTAN_ASSERT_NOMSG(m_read_buffer.size() >= reader.read_so_far()); + transcript_hash.update(m_read_buffer.data(), reader.read_so_far()); + m_read_buffer.erase(m_read_buffer.cbegin(), m_read_buffer.cbegin() + reader.read_so_far()); + } + + return msg; + } + +std::optional Handshake_Layer::next_post_handshake_message(const Policy& policy) + { + TLS::TLS_Data_Reader reader("post handshake message", m_read_buffer); + + auto msg = parse_message(reader, policy, m_peer); + if(msg.has_value()) + m_read_buffer.erase(m_read_buffer.cbegin(), m_read_buffer.cbegin() + reader.read_so_far()); + + return msg; + } + +namespace { + +template +const T& get(const std::reference_wrapper& v) + { return v.get(); } + +template +const T& get(const T& v) + { return v; } + +template +std::vector marshall_message(const T& message) + { + auto [type, serialized] = std::visit([](const auto& msg) + { + return std::pair(get(msg).wire_type(), get(msg).serialize()); + }, message); + + BOTAN_ASSERT_NOMSG(serialized.size() <= 0xFFFFFF); + const uint32_t msg_size = static_cast(serialized.size()); + + std::vector header + { + static_cast(type), + get_byte<1>(msg_size), + get_byte<2>(msg_size), + get_byte<3>(msg_size) + }; + + return concat(header, serialized); + } + +} //namespace + +std::vector Handshake_Layer::prepare_message(const Handshake_Message_13_Ref message, + Transcript_Hash_State& transcript_hash) + { + auto msg = marshall_message(message); + transcript_hash.update(msg); + return msg; + } + +std::vector Handshake_Layer::prepare_post_handshake_message(const Post_Handshake_Message_13& message) + { + return marshall_message(message); + } + +} // namespace Botan::TLS diff --git a/src/lib/tls/tls13/tls_handshake_layer_13.h b/src/lib/tls/tls13/tls_handshake_layer_13.h new file mode 100644 index 00000000000..c54d2e276ba --- /dev/null +++ b/src/lib/tls/tls13/tls_handshake_layer_13.h @@ -0,0 +1,93 @@ +/* +* TLS handshake layer implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_HANDSHAKE_LAYER_13_H_ +#define BOTAN_TLS_HANDSHAKE_LAYER_13_H_ + +#include +#include + +#include +#include + +namespace Botan::TLS { + +class Transcript_Hash_State; + +/** + * Implementation of the TLS 1.3 handshake protocol layer + * + * This component transforms payload bytes received in TLS records + * from the peer into parsed handshake messages and vice versa. + */ +class BOTAN_TEST_API Handshake_Layer + { + public: + Handshake_Layer(Connection_Side whoami) : m_peer(whoami == SERVER ? CLIENT : SERVER) {} + + /** + * Reads data that was received in handshake records and stores it internally for further + * processing during the invocation of `next_message()`. + * + * @param data_from_peer The data to be parsed. + */ + void copy_data(const std::vector& data_from_peer); + + /** + * Parses one handshake message off the internal buffer that is being filled using `copy_data`. + * + * @param policy the TLS policy + * @param transcript_hash the transcript hash state to be updated + * + * @return the parsed handshake message, or nullopt if more data is needed to complete the message + */ + std::optional next_message(const Policy& policy, Transcript_Hash_State& transcript_hash); + + /** + * Parses one post-handshake message off the internal buffer that is being filled using `copy_data`. + * + * @param policy the TLS policy + * + * @return the parsed post-handshake message, or nullopt if more data is needed to complete the message + */ + std::optional next_post_handshake_message(const Policy& policy); + + /** + * Marshalls one handshake message for sending in an (encrypted) record and updates the + * provided transcript hash state accordingly. + * + * @param message the handshake message to be marshalled + * @param transcript_hash the transcript hash state to be updated + * + * @return the marshalled handshake message + */ + std::vector prepare_message(const Handshake_Message_13_Ref message, Transcript_Hash_State& transcript_hash); + + /** + * Marshalls one post-handshake message for sending in an (encrypted) record. + * + * @param message the post handshake message to be marshalled + * + * @return the marshalled post-handshake message + */ + std::vector prepare_post_handshake_message(const Post_Handshake_Message_13& message); + + /** + * Check if the Handshake_Layer has stored a partial message in its internal buffer. + * This can happen if a handshake message spans multiple records. + */ + bool has_pending_data() const { return !m_read_buffer.empty(); } + + private: + std::vector m_read_buffer; + Connection_Side m_peer; + }; + +} + +#endif diff --git a/src/lib/tls/tls13/tls_handshake_state_13.cpp b/src/lib/tls/tls13/tls_handshake_state_13.cpp new file mode 100644 index 00000000000..8c086fd4fec --- /dev/null +++ b/src/lib/tls/tls13/tls_handshake_state_13.cpp @@ -0,0 +1,64 @@ +/* +* TLS handshake state (machine) implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +namespace Botan::TLS::Internal { + +Client_Hello_13& Handshake_State_13_Base::store(Client_Hello_13 client_hello, const bool) +{ + m_client_hello = std::move(client_hello); + return m_client_hello.value(); +} + +Server_Hello_13& Handshake_State_13_Base::store(Server_Hello_13 server_hello, const bool) +{ + m_server_hello = std::move(server_hello); + return m_server_hello.value(); +} + +Server_Hello_12& Handshake_State_13_Base::store(Server_Hello_12 server_hello, const bool) +{ + m_server_hello_12 = std::move(server_hello); + return m_server_hello_12.value(); +} + +Hello_Retry_Request& Handshake_State_13_Base::store(Hello_Retry_Request hello_retry_request, const bool) +{ + m_hello_retry_request = std::move(hello_retry_request); + return m_hello_retry_request.value(); +} + +Encrypted_Extensions& Handshake_State_13_Base::store(Encrypted_Extensions encrypted_extensions, const bool) +{ + m_encrypted_extensions = std::move(encrypted_extensions); + return m_encrypted_extensions.value(); +} + +Certificate_13& Handshake_State_13_Base::store(Certificate_13 certificate, const bool) +{ + m_server_certs = std::move(certificate); + return m_server_certs.value(); +} + +Certificate_Verify_13& Handshake_State_13_Base::store(Certificate_Verify_13 certificate_verify, const bool) +{ + m_server_verify = std::move(certificate_verify); + return m_server_verify.value(); +} + +Finished_13& Handshake_State_13_Base::store(Finished_13 finished, const bool from_peer) +{ + auto& target = ((m_side == CLIENT) == from_peer) + ? m_server_finished + : m_client_finished; + target = std::move(finished); + return target.value(); +} + +} diff --git a/src/lib/tls/tls13/tls_handshake_state_13.h b/src/lib/tls/tls13/tls_handshake_state_13.h new file mode 100644 index 00000000000..6abafb08d35 --- /dev/null +++ b/src/lib/tls/tls13/tls_handshake_state_13.h @@ -0,0 +1,135 @@ +/* +* TLS handshake state (machine) implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_HANDSHAKE_STATE_13_H_ +#define BOTAN_TLS_HANDSHAKE_STATE_13_H_ + +#include +#include +#include +#include + +#include +#include +#include + +namespace Botan::TLS { + +namespace Internal { +class BOTAN_TEST_API Handshake_State_13_Base + { + public: + bool has_client_hello() const { return m_client_hello.has_value(); } + bool has_hello_retry_request() const { return m_hello_retry_request.has_value(); } + bool has_server_finished() const { return m_server_finished.has_value(); } + bool has_client_finished() const { return m_client_finished.has_value(); } + + bool handshake_finished() const { return has_server_finished() && has_client_finished(); } + + // Client_Hello_13 cannot be const because it might need modification due to a Hello_Retry_Request + Client_Hello_13& client_hello() { return get(m_client_hello); } + const Server_Hello_13& server_hello() const { return get(m_server_hello); } + const Hello_Retry_Request& hello_retry_request() const { return get(m_hello_retry_request); } + const Encrypted_Extensions& encrypted_extensions() const { return get(m_encrypted_extensions); } + const Certificate_13& certificate() const { return get(m_server_certs); } + const Certificate_Verify_13& certificate_verify() const { return get(m_server_verify); } + const Finished_13& client_finished() const { return get(m_client_finished); } + const Finished_13& server_finished() const { return get(m_server_finished); } + + protected: + Handshake_State_13_Base(Connection_Side whoami) : m_side(whoami) {} + + Client_Hello_13& store(Client_Hello_13 client_hello, const bool from_peer); + Server_Hello_13& store(Server_Hello_13 server_hello, const bool from_peer); + Server_Hello_12& store(Server_Hello_12 server_hello, const bool from_peer); + Hello_Retry_Request& store(Hello_Retry_Request hello_retry_request, const bool from_peer); + Encrypted_Extensions& store(Encrypted_Extensions encrypted_extensions, const bool from_peer); + Certificate_13& store(Certificate_13 certificate, const bool from_peer); + Certificate_Verify_13& store(Certificate_Verify_13 certificate_verify, const bool from_peer); + Finished_13& store(Finished_13 finished, const bool from_peer); + + private: + template + const MessageT& get(const std::optional& opt) const + { + if(!opt.has_value()) + { throw Invalid_State("TLS handshake message not set"); } + return opt.value(); + } + + template + MessageT& get(std::optional& opt) + { + if(!opt.has_value()) + { throw Invalid_State("TLS handshake message not set"); } + return opt.value(); + } + + Connection_Side m_side; + + std::optional m_client_hello; + std::optional m_server_hello; + std::optional m_server_hello_12; + std::optional m_hello_retry_request; + std::optional m_encrypted_extensions; + std::optional m_server_certs; + std::optional m_server_verify; + std::optional m_server_finished; + std::optional m_client_finished; + }; +} + +/** + * Place to store TLS handshake messages + * + * This class is used to keep all handshake messages that have been received from and sent to + * the peer as part of the TLS 1.3 handshake. Getters are provided for all message types. + * Specializations for the client and server side provide specific setters in the form of + * `sent` and `received` that only allow those types of handshake messages that are sensible + * for the respective connection side. + * + * The handshake state machine as described in RFC 8446 Appendix A is NOT validated here. + */ +template +class BOTAN_TEST_API Handshake_State_13 : public Internal::Handshake_State_13_Base + { + public: + Handshake_State_13() : Handshake_State_13_Base(whoami) {} + + decltype(auto) sent(Outbound_Message_T message) + { + return std::visit([&](auto msg) -> Handshake_Message_13_Ref + { + return std::reference_wrapper(store(std::move(msg), false)); + }, std::move(message)); + } + + decltype(auto) received(Handshake_Message_13 message) + { + return std::visit([&](auto msg) -> as_wrapped_references_t + { + if constexpr(std::is_constructible_v) + { + return std::reference_wrapper(store(std::move(msg), true)); + } + + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "received an illegal handshake message"); + }, std::move(message)); + } + }; + +using Client_Handshake_State_13 = Handshake_State_13; + +using Server_Handshake_State_13 = Handshake_State_13; +} + +#endif diff --git a/src/lib/tls/tls13/tls_record_layer_13.cpp b/src/lib/tls/tls13/tls_record_layer_13.cpp new file mode 100644 index 00000000000..7ac9e92369f --- /dev/null +++ b/src/lib/tls/tls13/tls_record_layer_13.cpp @@ -0,0 +1,381 @@ +/* +* TLS record layer implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include +#include +#include + +#include +#include +#include + +namespace Botan::TLS { + +namespace { + +template +bool verify_change_cipher_spec(const IteratorT data, const size_t size) + { + // RFC 8446 5. + // An implementation may receive an unencrypted record of type + // change_cipher_spec consisting of the single byte value 0x01 + // at any time [...]. An implementation which receives any other + // change_cipher_spec value or which receives a protected + // change_cipher_spec record MUST abort the handshake [...]. + const size_t expected_fragment_length = 1; + const uint8_t expected_fragment_byte = 0x01; + return (size == expected_fragment_length && *data == expected_fragment_byte); + } + +Record_Type read_record_type(const uint8_t type_byte) + { + // RFC 8446 5. + // If a TLS implementation receives an unexpected record type, + // it MUST terminate the connection with an "unexpected_message" alert. + if(type_byte != Record_Type::APPLICATION_DATA + && type_byte != Record_Type::HANDSHAKE + && type_byte != Record_Type::ALERT + && type_byte != Record_Type::CHANGE_CIPHER_SPEC) + { + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "TLS record type had unexpected value"); + } + + return static_cast(type_byte); + } + +/** + * RFC 8446 5.1 `TLSPlaintext` without the `fragment` payload data + */ +struct TLSPlaintext_Header + { + TLSPlaintext_Header(std::vector hdr, const bool check_tls13_version) + { + type = read_record_type(hdr[0]); + legacy_version = Protocol_Version(make_uint16(hdr[1], hdr[2])); + fragment_length = make_uint16(hdr[3], hdr[4]); + serialized = std::move(hdr); + + // If no full version check is requested, we just verify the practically + // ossified major version number. + if(legacy_version.major_version() != 0x03) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Received unexpected record version"); } + + // RFC 8446 5.1 + // legacy_record_version: MUST be set to 0x0303 for all records + // generated by a TLS 1.3 implementation + if(check_tls13_version && legacy_version.version_code() != 0x0303) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Received unexpected record version"); } + + // RFC 8446 5.1 + // Implementations MUST NOT send zero-length fragments of Handshake + // types, even if those fragments contain padding. + // + // Zero-length fragments of Application Data MAY be sent, as they are + // potentially useful as a traffic analysis countermeasure. + if(fragment_length == 0 && type != Record_Type::APPLICATION_DATA) + { throw TLS_Exception(Alert::DECODE_ERROR, "empty record received"); } + + if(type == Record_Type::APPLICATION_DATA) + { + // RFC 8446 5.2 + // The length [...] is the sum of the lengths of the content and the + // padding, plus one for the inner content type, plus any expansion + // added by the AEAD algorithm. The length MUST NOT exceed 2^14 + 256 bytes. + // + // Note: Limits imposed by a "record_size_limit" extension do not come + // into play here, as those limits are on the plaintext _not_ the + // encrypted data. Constricted devices must be able to deal with + // data overhead inflicted by the AEAD. + if(fragment_length > MAX_CIPHERTEXT_SIZE_TLS13) + { throw TLS_Exception(Alert::RECORD_OVERFLOW, "Received an encrypted record that exceeds maximum size"); } + } + else + { + // RFC 8446 5.1 + // The length MUST NOT exceed 2^14 bytes. An endpoint that receives a record that + // exceeds this length MUST terminate the connection with a "record_overflow" alert. + // + // RFC 8449 4. + // When the "record_size_limit" extension is negotiated, an endpoint + // MUST NOT generate a protected record with plaintext that is larger + // than the RecordSizeLimit value it receives from its peer. + // -> Unprotected messages are not subject to this limit. <- + if(fragment_length > MAX_PLAINTEXT_SIZE) + { throw TLS_Exception(Alert::RECORD_OVERFLOW, "Received a record that exceeds maximum size"); } + } + } + + TLSPlaintext_Header(const Record_Type record_type, + const size_t frgmnt_length, + const bool use_compatibility_version) + : type(record_type) + , legacy_version(use_compatibility_version ? 0x0301 : 0x0303) // RFC 8446 5.1 + , fragment_length(static_cast(frgmnt_length)) + , serialized( + { + static_cast(type), + legacy_version.major_version(), legacy_version.minor_version(), + get_byte<0>(fragment_length), get_byte<1>(fragment_length), + }) + {} + + Record_Type type; + Protocol_Version legacy_version; + uint16_t fragment_length; + std::vector serialized; + }; + +} // namespace + +Record_Layer::Record_Layer(Connection_Side side) + : m_side(side) + , m_outgoing_record_size_limit(MAX_PLAINTEXT_SIZE + 1 /* content type byte */) + , m_incoming_record_size_limit(MAX_PLAINTEXT_SIZE + 1 /* content type byte */) + , m_first_sent_record(true) + , m_first_rcvd_record(true) {} + + +void Record_Layer::copy_data(const uint8_t data[], size_t size) + { + m_read_buffer.insert(m_read_buffer.end(), data, data+size); + } + +void Record_Layer::copy_data(const std::vector& data_from_peer) + { + copy_data(data_from_peer.data(), data_from_peer.size()); + } + +std::vector Record_Layer::prepare_records(const Record_Type type, + const std::vector& data, + Cipher_State* cipher_state) + { + // RFC 8446 5. + // Note that [change_cipher_spec records] may appear at a point at the + // handshake where the implementation is expecting protected records. + // + // RFC 8446 5. + // An implementation which receives [...] a protected change_cipher_spec + // record MUST abort the handshake [...]. + // + // ... hence, CHANGE_CIPHER_SPEC is never protected, even if a usable cipher + // state was passed to this method. + const bool protect = cipher_state != nullptr && type != Record_Type::CHANGE_CIPHER_SPEC; + + // RFC 8446 5.1 + BOTAN_ASSERT(protect || type != Record_Type::APPLICATION_DATA, + "Application Data records MUST NOT be written to the wire unprotected"); + + // RFC 8446 5.1 + // "MUST NOT sent zero-length fragments of Handshake types" + // "a record with an Alert type MUST contain exactly one message" [of non-zero length] + // "Zero-length fragments of Application Data MAY be sent" + BOTAN_ASSERT(!data.empty() || type == Record_Type::APPLICATION_DATA, + "zero-length fragments of types other than application data are not allowed"); + + if(type == Record_Type::CHANGE_CIPHER_SPEC && + !verify_change_cipher_spec(data.cbegin(), data.size())) + { + throw Invalid_Argument("TLS 1.3 deprecated CHANGE_CIPHER_SPEC"); + } + + std::vector output; + + // RFC 8446 5.2 + // type: The TLSPlaintext.type value containing the content type of the record. + constexpr size_t content_type_tag_length = 1; + + // RFC 8449 4. + // When the "record_size_limit" extension is negotiated, an endpoint + // MUST NOT generate a protected record with plaintext that is larger + // than the RecordSizeLimit value it receives from its peer. + // Unprotected messages are not subject to this limit. + const size_t max_plaintext_size = (protect) + ? m_outgoing_record_size_limit - content_type_tag_length + : static_cast(MAX_PLAINTEXT_SIZE); + + const auto records = std::max((data.size() + max_plaintext_size - 1) / max_plaintext_size, size_t(1)); + auto output_length = records * TLS_HEADER_SIZE; + if(protect) + { + // n-1 full records of size max_plaintext_size + output_length += + (records - 1) * cipher_state->encrypt_output_length(max_plaintext_size + content_type_tag_length); + // last record with size of remaining data + output_length += + cipher_state->encrypt_output_length(data.size() - ((records-1) * max_plaintext_size) + content_type_tag_length); + } + else + { + output_length += data.size(); + } + output.reserve(output_length); + + size_t pt_offset = 0; + size_t to_process = data.size(); + + // For protected records we need to write at least one encrypted fragment, + // even if the plaintext size is zero. This happens only for Application + // Data types. + BOTAN_ASSERT_NOMSG(to_process != 0 || protect); + do + { + const size_t pt_size = std::min(to_process, max_plaintext_size); + const size_t ct_size = (!protect) ? pt_size : cipher_state->encrypt_output_length(pt_size + content_type_tag_length); + const auto pt_type = (!protect) ? type : Record_Type::APPLICATION_DATA; + + // RFC 8446 5.1 + // MUST be set to 0x0303 for all records generated by a TLS 1.3 + // implementation other than an initial ClientHello [...], where + // it MAY also be 0x0301 for compatibility purposes. + const auto use_compatibility_version = m_side == Connection_Side::CLIENT && m_first_sent_record; + const auto record_header = TLSPlaintext_Header(pt_type, ct_size, use_compatibility_version).serialized; + m_first_sent_record = false; + + output.insert(output.end(), record_header.cbegin(), record_header.cend()); + + if(protect) + { + secure_vector fragment; + fragment.reserve(ct_size); + + // assemble TLSInnerPlaintext structure + fragment.insert(fragment.end(), data.cbegin() + pt_offset, data.cbegin() + pt_offset + pt_size); + fragment.push_back(static_cast(type)); + // TODO: zero padding could go here, see RFC 8446 5.4 + + cipher_state->encrypt_record_fragment(record_header, fragment); + BOTAN_ASSERT_NOMSG(fragment.size() == ct_size); + + output.insert(output.end(), fragment.cbegin(), fragment.cend()); + } + else + { + output.insert(output.end(), data.cbegin() + pt_offset, data.cbegin() + pt_offset + pt_size); + } + + pt_offset += pt_size; + to_process -= pt_size; + } + while(to_process > 0); + + BOTAN_ASSERT_NOMSG(output.size() == output_length); + return output; + } + + +Record_Layer::ReadResult Record_Layer::next_record(Cipher_State* cipher_state) + { + if(m_read_buffer.size() < TLS_HEADER_SIZE) + { + return TLS_HEADER_SIZE - m_read_buffer.size(); + } + + const auto header_begin = m_read_buffer.cbegin(); + const auto header_end = header_begin + TLS_HEADER_SIZE; + + // The first received record is likely a client or server hello. To be able to + // perform protocol downgrades we need must be less vigorous with the record's + // legacy version. Hence, `check_tls13_version` is `false` for the first record. + TLSPlaintext_Header plaintext_header({header_begin, header_end}, !m_first_rcvd_record); + + // After the key exchange phase of the handshake is completed and record protection is engaged, + // cipher_state is set. At this point, only protected traffic (and CCS) is allowed. + // + // RFC 8446 2. + // - Key Exchange: Establish shared keying material and select the + // cryptographic parameters. Everything after this phase is + // encrypted. + // RFC 8446 5. + // An implementation may receive an unencrypted [CCS] at any time + if(cipher_state != nullptr + && plaintext_header.type != APPLICATION_DATA + && plaintext_header.type != CHANGE_CIPHER_SPEC) + { + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, + "unprotected record received where protected traffic was expected"); + } + + if(m_read_buffer.size() < TLS_HEADER_SIZE + plaintext_header.fragment_length) + { + return TLS_HEADER_SIZE + plaintext_header.fragment_length - m_read_buffer.size(); + } + + const auto fragment_begin = header_end; + const auto fragment_end = fragment_begin + plaintext_header.fragment_length; + + if(plaintext_header.type == Record_Type::CHANGE_CIPHER_SPEC && + !verify_change_cipher_spec(fragment_begin, plaintext_header.fragment_length)) + { + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, + "malformed change cipher spec record received"); + } + + Record record(plaintext_header.type, secure_vector(fragment_begin, fragment_end)); + m_read_buffer.erase(header_begin, fragment_end); + + if(record.type == Record_Type::APPLICATION_DATA) + { + if(cipher_state == nullptr) + { + // This could also mean a misuse of the interface, i.e. failing to provide a valid + // cipher_state to parse_records when receiving valid (encrypted) Application Data. + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "premature Application Data received"); + } + + if(record.fragment.size() < cipher_state->minimum_decryption_input_length()) + { throw TLS_Exception(Alert::BAD_RECORD_MAC, "incomplete record mac received"); } + + if(cipher_state->decrypt_output_length(record.fragment.size()) > m_incoming_record_size_limit) + { throw TLS_Exception(Alert::RECORD_OVERFLOW, "Received an encrypted record that exceeds maximum plaintext size"); } + + record.seq_no = cipher_state->decrypt_record_fragment(plaintext_header.serialized, record.fragment); + + // Remove record padding (RFC 8446 5.4). + const auto end_of_content = std::find_if(record.fragment.crbegin(), record.fragment.crend(), [](auto byte) + { + return byte != 0x00; + }); + + if(end_of_content == record.fragment.crend()) + { throw TLS_Exception(Alert::DECODE_ERROR, "No content type found in encrypted record"); } + + // hydrate the actual content type from TLSInnerPlaintext + record.type = read_record_type(*end_of_content); + + if(record.type == Record_Type::CHANGE_CIPHER_SPEC) + { + // RFC 8446 5 + // An implementation [...] which receives a protected change_cipher_spec record MUST + // abort the handshake with an "unexpected_message" alert. + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "protected change cipher spec received"); + } + + // erase content type and padding + record.fragment.erase((end_of_content+1).base(), record.fragment.cend()); + } + + m_first_rcvd_record = false; + return record; + } + + +void Record_Layer::set_record_size_limits(const uint16_t outgoing_limit, + const uint16_t incoming_limit) + { + BOTAN_ARG_CHECK(outgoing_limit >= 64, "Invalid outgoing record size limit"); + BOTAN_ARG_CHECK(incoming_limit >= 64 && incoming_limit <= MAX_PLAINTEXT_SIZE + 1, "Invalid incoming record size limit"); + + // RFC 8449 4. + // Even if a larger record size limit is provided by a peer, an endpoint + // MUST NOT send records larger than the protocol-defined limit, unless + // explicitly allowed by a future TLS version or extension. + m_outgoing_record_size_limit = std::min(outgoing_limit, static_cast(MAX_PLAINTEXT_SIZE + 1)); + m_incoming_record_size_limit = incoming_limit; + } + +} // namespace Botan::TLS diff --git a/src/lib/tls/tls13/tls_record_layer_13.h b/src/lib/tls/tls13/tls_record_layer_13.h new file mode 100644 index 00000000000..1ef156ed8ce --- /dev/null +++ b/src/lib/tls/tls13/tls_record_layer_13.h @@ -0,0 +1,121 @@ +/* +* TLS record layer implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_RECORD_LAYER_13_H_ +#define BOTAN_TLS_RECORD_LAYER_13_H_ + +#include +#include +#include + +#include +#include + +namespace Botan::TLS { + +/** + * Resembles the `TLSPlaintext` structure in RFC 8446 5.1 + * minus the record protocol specifics and ossified bytes. + */ +struct Record + { + Record_Type type; + secure_vector fragment; + std::optional seq_no; // unprotected records have no sequence number + + Record(Record_Type record_type, secure_vector frgmnt) + : type(record_type) + , fragment(std::move(frgmnt)) + , seq_no(std::nullopt) {} + }; + +using BytesNeeded = size_t; + +class Cipher_State; + +/** + * Implementation of the TLS 1.3 record protocol layer + * + * This component transforms bytes received from the peer into bytes + * containing plaintext TLS messages and vice versa. + */ +class BOTAN_TEST_API Record_Layer + { + public: + Record_Layer(Connection_Side side); + + template + using ReadResult = std::variant; + + /** + * Reads data that was received by the peer and stores it internally for further + * processing during the invocation of `next_record()`. + * + * @param data_from_peer The data to be parsed. + */ + void copy_data(const std::vector& data_from_peer); + void copy_data(const uint8_t data[], size_t size); + + /** + * Parses one record off the internal buffer that is being filled using `copy_data`. + * + * Return value contains either the number of bytes (`size_t`) needed to proceed + * with processing TLS records or a single plaintext TLS record content containing + * higher level protocol or application data. + * + * @param cipher_state Optional pointer to a Cipher_State instance. If provided, the + * cipher_state should be ready to decrypt data. Pass nullptr to + * process plaintext data. + */ + ReadResult next_record(Cipher_State* cipher_state = nullptr); + + std::vector prepare_records(const Record_Type type, + const std::vector& data, + Cipher_State* cipher_state=nullptr); + + /** + * Clears any data currently stored in the read buffer. This is typically + * used for memory cleanup when the peer sent a CLOSE_NOTIFY alert. + */ + void clear_read_buffer() { zap(m_read_buffer); } + + /** + * Set the record size limits as negotiated by the "record_size_limit" + * extension (RFC 8449). The limits refer to the number of plaintext bytes + * to be encrypted/decrypted -- INCLUDING the encrypted content type byte + * introduced with TLS 1.3. The record size limit is _not_ applied to + * unprotected records. Incoming records that exceed the set limit will + * result in a fatal alert. + * + * @param outgoing_limit the maximal number of plaintext bytes to be + * sent in a protected record + * @param incoming_limit the maximal number of plaintext bytes to be + * accepted in a received protected record + */ + void set_record_size_limits(const uint16_t outgoing_limit, + const uint16_t incoming_limit); + + private: + std::vector m_read_buffer; + Connection_Side m_side; + + // Those are either the limits set by the TLS 1.3 specification (RFC 8446), + // or the ones negotiated via the "record_size_limit" extension (RFC 8449). + uint16_t m_outgoing_record_size_limit; + uint16_t m_incoming_record_size_limit; + + // Those status flags are required for version validation where the initial + // record for sending and receiving is handled differently for backward + // compatibility reasons. + bool m_first_sent_record; + bool m_first_rcvd_record; + }; + +} + +#endif diff --git a/src/lib/tls/tls13/tls_transcript_hash_13.cpp b/src/lib/tls/tls13/tls_transcript_hash_13.cpp new file mode 100644 index 00000000000..ad4d68e28d8 --- /dev/null +++ b/src/lib/tls/tls13/tls_transcript_hash_13.cpp @@ -0,0 +1,108 @@ +/* +* TLS transcript hash implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +namespace Botan::TLS { + +Transcript_Hash_State::Transcript_Hash_State(const std::string &algo_spec) + { + set_algorithm(algo_spec); + } + +Transcript_Hash_State::Transcript_Hash_State(const Transcript_Hash_State& other) + : m_hash((other.m_hash != nullptr) ? other.m_hash->copy_state() : nullptr) + , m_unprocessed_transcript(other.m_unprocessed_transcript) + , m_current(other.m_current) + , m_previous(other.m_previous) + {} + + +Transcript_Hash_State Transcript_Hash_State::recreate_after_hello_retry_request( + const std::string& algo_spec, + const Transcript_Hash_State& prev_transcript_hash_state) + { + // make sure that we have seen exactly 'client_hello' and 'hello_retry_request' + // before re-creating the transcript hash state + BOTAN_STATE_CHECK(prev_transcript_hash_state.m_hash == nullptr); + BOTAN_STATE_CHECK(prev_transcript_hash_state.m_unprocessed_transcript.size() == 2); + + Transcript_Hash_State ths(algo_spec); + + const auto& client_hello_1 = prev_transcript_hash_state.m_unprocessed_transcript.front(); + const auto& hello_retry_request = prev_transcript_hash_state.m_unprocessed_transcript.back(); + + const size_t hash_length = ths.m_hash->output_length(); + BOTAN_ASSERT_NOMSG(hash_length < 256); + + // RFC 8446 4.4.1 + // [...], when the server responds to a ClientHello with a HelloRetryRequest, + // the value of ClientHello1 is replaced with a special synthetic handshake + // message of handshake type "message_hash" [(0xFE)] containing: + std::vector message_hash; + message_hash.reserve(4 + hash_length); + message_hash.push_back(0xFE /* message type 'message_hash' RFC 8446 4. */); + message_hash.push_back(0x00); + message_hash.push_back(0x00); + message_hash.push_back(static_cast(hash_length)); + message_hash += ths.m_hash->process(client_hello_1); + + ths.update(message_hash); + ths.update(hello_retry_request); + + return ths; + } + +void Transcript_Hash_State::update(const uint8_t* serialized_message, const size_t serialized_message_length) + { + if(m_hash != nullptr) + { + // Botan does not support finalizing a HashFunction without resetting + // the internal state of the hash. Hence we first copy the internal + // state and then finalize the transient HashFunction. + m_hash->update(serialized_message, serialized_message_length); + m_previous = std::exchange(m_current, m_hash->copy_state()->final_stdvec()); + } + else + { + m_unprocessed_transcript.push_back(std::vector(serialized_message, serialized_message + serialized_message_length)); + } + } + +const Transcript_Hash& Transcript_Hash_State::current() const + { + BOTAN_STATE_CHECK(!m_current.empty()); + return m_current; + } + +const Transcript_Hash& Transcript_Hash_State::previous() const + { + BOTAN_STATE_CHECK(!m_previous.empty()); + return m_previous; + } + +void Transcript_Hash_State::set_algorithm(const std::string& algo_spec) + { + BOTAN_STATE_CHECK(m_hash == nullptr || m_hash->name() == algo_spec); + if(m_hash != nullptr) + return; + + m_hash = HashFunction::create_or_throw(algo_spec); + for(const auto& msg : m_unprocessed_transcript) + { + update(msg); + } + m_unprocessed_transcript.clear(); + } + +Transcript_Hash_State Transcript_Hash_State::clone() const + { + return *this; + } + +} diff --git a/src/lib/tls/tls13/tls_transcript_hash_13.h b/src/lib/tls/tls13/tls_transcript_hash_13.h new file mode 100644 index 00000000000..b20f6b142dd --- /dev/null +++ b/src/lib/tls/tls13/tls_transcript_hash_13.h @@ -0,0 +1,92 @@ +/* +* TLS transcript hash implementation for TLS 1.3 +* (C) 2022 Jack Lloyd +* 2022 Hannes Rantzsch, René Meusel - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TLS_TRANSCRIPT_HASH_13_H_ +#define BOTAN_TLS_TRANSCRIPT_HASH_13_H_ + +#include +#include +#include + +#include +#include + +namespace Botan::TLS { + +/** + * Wraps the behaviour of the TLS 1.3 transcript hash as described in + * RFC 8446 4.4.1. Particularly, it hides the complexity that the + * utilized hash algorithm might become evident only after receiving + * a server hello message. + */ +class BOTAN_TEST_API Transcript_Hash_State + { + public: + Transcript_Hash_State() = default; + Transcript_Hash_State(const std::string &algo_spec); + ~Transcript_Hash_State() = default; + + /** + * Recreates a Transcript_Hash_State after receiving a Hello Retry Request. + * Note that the `prev_transcript_hash_state` must not have an hash algorithm + * set, yet. Furthermore it must contain exactly TWO unprocessed messages: + * * Client Hello 1, and + * * Hello Retry Request + * The result of this function is an ordinary transcript hash that can replace + * the previously used object in client and server implementations. + */ + static Transcript_Hash_State recreate_after_hello_retry_request( + const std::string& algo_spec, + const Transcript_Hash_State& prev_transcript_hash_state); + + Transcript_Hash_State& operator=(const Transcript_Hash_State&) = delete; + + Transcript_Hash_State(Transcript_Hash_State&&) = default; + Transcript_Hash_State& operator=(Transcript_Hash_State&&) = default; + + void update(const std::vector& serialized_message) + { + update(serialized_message.data(), serialized_message.size()); + } + + // TODO: C++20 replace this C-style API with std::span + void update(const uint8_t* serialized_message, const size_t serialized_message_length); + + /** + * returns the latest transcript hash + * (given an algorithm was already specified and some data was provided to `update`) + */ + const Transcript_Hash& current() const; + + /** + * returns the second-latest transcript hash + * throws if no 'current' was ever replaced by a call to `update` + */ + const Transcript_Hash& previous() const; + + void set_algorithm(const std::string& algo_spec); + + Transcript_Hash_State clone() const; + + private: + Transcript_Hash_State(const Transcript_Hash_State& other); + + private: + std::unique_ptr m_hash; + + // This buffer is filled with the data that is passed into + // `update()` before `set_algorithm()` was called. + std::vector> m_unprocessed_transcript; + + Transcript_Hash m_current; + Transcript_Hash m_previous; + }; + +} + +#endif // BOTAN_TLS_TRANSCRIPT_HASH_13_H_ diff --git a/src/lib/tls/tls_algos.cpp b/src/lib/tls/tls_algos.cpp index db15b477174..81f174b7cb9 100644 --- a/src/lib/tls/tls_algos.cpp +++ b/src/lib/tls/tls_algos.cpp @@ -4,6 +4,8 @@ * Botan is released under the Simplified BSD License (see license.txt) */ +#include +#include #include #include @@ -40,6 +42,8 @@ std::string kex_method_to_string(Kex_Algo method) return "PSK"; case Kex_Algo::ECDHE_PSK: return "ECDHE_PSK"; + case Kex_Algo::UNDEFINED: + return "UNDEFINED"; } throw Invalid_State("kex_method_to_string unknown enum value"); @@ -65,6 +69,9 @@ Kex_Algo kex_method_from_string(const std::string& str) if(str == "ECDHE_PSK") return Kex_Algo::ECDHE_PSK; + if(str == "UNDEFINED") + return Kex_Algo::UNDEFINED; + throw Invalid_Argument("Unknown kex method " + str); } @@ -78,6 +85,8 @@ std::string auth_method_to_string(Auth_Method method) return "ECDSA"; case Auth_Method::IMPLICIT: return "IMPLICIT"; + case Auth_Method::UNDEFINED: + return "UNDEFINED"; } throw Invalid_State("auth_method_to_string unknown enum value"); @@ -91,6 +100,8 @@ Auth_Method auth_method_from_string(const std::string& str) return Auth_Method::ECDSA; if(str == "IMPLICIT") return Auth_Method::IMPLICIT; + if(str == "UNDEFINED") + return Auth_Method::UNDEFINED; throw Invalid_Argument("Bad signature method " + str); } @@ -167,6 +178,42 @@ std::string group_param_to_string(Group_Params group) } } +AlgorithmIdentifier algorithm_identifier_for_scheme(Signature_Scheme scheme) + { + switch(scheme) + { + case Signature_Scheme::ECDSA_SHA256: + return { "ECDSA", EC_Group("secp256r1").DER_encode(EC_Group_Encoding::NamedCurve) }; + case Signature_Scheme::ECDSA_SHA384: + return { "ECDSA", EC_Group("secp384r1").DER_encode(EC_Group_Encoding::NamedCurve) }; + case Signature_Scheme::ECDSA_SHA512: + return { "ECDSA", EC_Group("secp521r1").DER_encode(EC_Group_Encoding::NamedCurve) }; + + case Signature_Scheme::EDDSA_25519: + return { "Ed25519", AlgorithmIdentifier::USE_EMPTY_PARAM }; + + case Signature_Scheme::RSA_PKCS1_SHA256: + case Signature_Scheme::RSA_PKCS1_SHA384: + case Signature_Scheme::RSA_PKCS1_SHA512: + case Signature_Scheme::RSA_PSS_SHA256: + case Signature_Scheme::RSA_PSS_SHA384: + case Signature_Scheme::RSA_PSS_SHA512: + return { "RSA", AlgorithmIdentifier::USE_NULL_PARAM }; + + case Signature_Scheme::NONE: + case Signature_Scheme::EDDSA_448: + case Signature_Scheme::RSA_PKCS1_SHA1: + case Signature_Scheme::ECDSA_SHA1: + case Signature_Scheme::DSA_SHA1: + case Signature_Scheme::DSA_SHA256: + case Signature_Scheme::DSA_SHA384: + case Signature_Scheme::DSA_SHA512: + throw Invalid_State("oid_for_scheme: Unsupported signature scheme"); + } + + Botan::unreachable(); + } + std::string hash_function_of_scheme(Signature_Scheme scheme) { switch(scheme) @@ -190,6 +237,14 @@ std::string hash_function_of_scheme(Signature_Scheme scheme) case Signature_Scheme::EDDSA_448: return "Pure"; + case Signature_Scheme::RSA_PKCS1_SHA1: + case Signature_Scheme::ECDSA_SHA1: + case Signature_Scheme::DSA_SHA1: + case Signature_Scheme::DSA_SHA256: + case Signature_Scheme::DSA_SHA384: + case Signature_Scheme::DSA_SHA512: + throw Invalid_State("hash_function_of_scheme: Unsupported signature scheme"); + case Signature_Scheme::NONE: return ""; } @@ -238,6 +293,16 @@ bool signature_scheme_is_known(Signature_Scheme scheme) case Signature_Scheme::ECDSA_SHA512: return true; + // those schemes are added solely to please the TLS 1.3 + // integration tests based on RFC 8448 + case Signature_Scheme::RSA_PKCS1_SHA1: + case Signature_Scheme::ECDSA_SHA1: + case Signature_Scheme::DSA_SHA1: + case Signature_Scheme::DSA_SHA256: + case Signature_Scheme::DSA_SHA384: + case Signature_Scheme::DSA_SHA512: + return false; + default: return false; } @@ -267,6 +332,16 @@ std::string signature_algorithm_of_scheme(Signature_Scheme scheme) case Signature_Scheme::EDDSA_448: return "Ed448"; + case Signature_Scheme::DSA_SHA256: + case Signature_Scheme::DSA_SHA384: + case Signature_Scheme::DSA_SHA512: + throw Invalid_State("signature_algorithm_of_scheme: DSA signature scheme not supported"); + + case Signature_Scheme::DSA_SHA1: + case Signature_Scheme::ECDSA_SHA1: + case Signature_Scheme::RSA_PKCS1_SHA1: + throw Invalid_State("signature_algorithm_of_scheme: SHA1-based signature scheme not considered safe"); + case Signature_Scheme::NONE: return ""; } @@ -278,6 +353,8 @@ std::string sig_scheme_to_string(Signature_Scheme scheme) { switch(scheme) { + case Signature_Scheme::RSA_PKCS1_SHA1: + return "RSA_PKCS1_SHA1"; case Signature_Scheme::RSA_PKCS1_SHA256: return "RSA_PKCS1_SHA256"; case Signature_Scheme::RSA_PKCS1_SHA384: @@ -285,6 +362,8 @@ std::string sig_scheme_to_string(Signature_Scheme scheme) case Signature_Scheme::RSA_PKCS1_SHA512: return "RSA_PKCS1_SHA512"; + case Signature_Scheme::ECDSA_SHA1: + return "ECDSA_SHA1"; case Signature_Scheme::ECDSA_SHA256: return "ECDSA_SHA256"; case Signature_Scheme::ECDSA_SHA384: @@ -304,6 +383,15 @@ std::string sig_scheme_to_string(Signature_Scheme scheme) case Signature_Scheme::EDDSA_448: return "EDDSA_448"; + case Signature_Scheme::DSA_SHA1: + return "DSA_SHA1"; + case Signature_Scheme::DSA_SHA256: + return "DSA_SHA256"; + case Signature_Scheme::DSA_SHA384: + return "DSA_SHA384"; + case Signature_Scheme::DSA_SHA512: + return "DSA_SHA512"; + case Signature_Scheme::NONE: return ""; } @@ -341,6 +429,14 @@ std::string padding_string_for_scheme(Signature_Scheme scheme) case Signature_Scheme::EDDSA_448: return "Pure"; + case Signature_Scheme::RSA_PKCS1_SHA1: + case Signature_Scheme::ECDSA_SHA1: + case Signature_Scheme::DSA_SHA1: + case Signature_Scheme::DSA_SHA256: + case Signature_Scheme::DSA_SHA384: + case Signature_Scheme::DSA_SHA512: + throw Invalid_State("padding_string_for_scheme: Unsupported signature scheme"); + case Signature_Scheme::NONE: return ""; } diff --git a/src/lib/tls/tls_algos.h b/src/lib/tls/tls_algos.h index 32d1e7032ba..4e4388df15b 100644 --- a/src/lib/tls/tls_algos.h +++ b/src/lib/tls/tls_algos.h @@ -8,6 +8,7 @@ #define BOTAN_TLS_ALGO_IDS_H_ #include +#include #include #include @@ -65,6 +66,9 @@ enum class Auth_Method { RSA, ECDSA, + // To support TLS 1.3 ciphersuites, which do not determine the auth method + UNDEFINED, + // These are placed outside the encodable range IMPLICIT = 0x10000 }; @@ -92,6 +96,16 @@ enum class Signature_Scheme : uint16_t { EDDSA_25519 = 0x0807, EDDSA_448 = 0x0808, + + // provided but not actually supported + // required for TLS 1.3 test based on RFC 8448 + ECDSA_SHA1 = 0x0203, + RSA_PKCS1_SHA1 = 0x0201, + + DSA_SHA1 = 0x0202, + DSA_SHA256 = 0x0402, + DSA_SHA384 = 0x0502, + DSA_SHA512 = 0x0602 }; BOTAN_UNSTABLE_API const std::vector& all_signature_schemes(); @@ -101,6 +115,7 @@ std::string BOTAN_UNSTABLE_API sig_scheme_to_string(Signature_Scheme scheme); std::string BOTAN_UNSTABLE_API hash_function_of_scheme(Signature_Scheme scheme); std::string BOTAN_UNSTABLE_API padding_string_for_scheme(Signature_Scheme scheme); std::string signature_algorithm_of_scheme(Signature_Scheme scheme); +AlgorithmIdentifier algorithm_identifier_for_scheme(Signature_Scheme scheme); /* * Matches with wire encoding @@ -135,6 +150,9 @@ enum class Kex_Algo { CECPQ1, PSK, ECDHE_PSK, + + // To support TLS 1.3 ciphersuites, which do not determine the kex algo + UNDEFINED }; std::string BOTAN_TEST_API kex_method_to_string(Kex_Algo method); diff --git a/src/lib/tls/tls_callbacks.h b/src/lib/tls/tls_callbacks.h index 67da7c5a7b7..d2568bfd59e 100644 --- a/src/lib/tls/tls_callbacks.h +++ b/src/lib/tls/tls_callbacks.h @@ -220,6 +220,12 @@ class BOTAN_PUBLIC_API(2,0) Callbacks * @param rng a random number generator * * @return a pair consisting of the agreed raw secret and our public value + * + * TODO: Currently, this is called in TLS 1.2 only. The key agreement mechanics + * changed in TLS 1.3, so this callback would (at least) need to be aware + * of the negotiated protocol version. + * Suggestion: Lets think about a more generic interface for this and + * deprecate/remove this callback in Botan 3.0 */ virtual std::pair, std::vector> tls_dh_agree( const std::vector& modulus, @@ -241,6 +247,12 @@ class BOTAN_PUBLIC_API(2,0) Callbacks * @param compressed the compression preference for our public value * * @return a pair consisting of the agreed raw secret and our public value + * + * TODO: Currently, this is called in TLS 1.2 only. The key agreement mechanics + * changed in TLS 1.3, so this callback would (at least) need to be aware + * of the negotiated protocol version. + * Suggestion: Lets think about a more generic interface for this and + * deprecate/remove this callback in Botan 3.0 */ virtual std::pair, std::vector> tls_ecdh_agree( const std::string& curve_name, diff --git a/src/lib/tls/tls_channel.h b/src/lib/tls/tls_channel.h index 4cde18e52f2..5a8ed48fa69 100644 --- a/src/lib/tls/tls_channel.h +++ b/src/lib/tls/tls_channel.h @@ -129,6 +129,14 @@ class BOTAN_PUBLIC_API(2,0) Channel */ virtual void renegotiate(bool force_full_renegotiation = false) = 0; + /** + * Attempt to update the session's traffic key material + * Note that this is possible with a TLS 1.3 channel, only. + * + * @param request_peer_update if true, require a reciprocal key update + */ + virtual void update_traffic_keys(bool request_peer_update = false) = 0; + /** * @return true iff the counterparty supports the secure * renegotiation extensions. diff --git a/src/lib/tls/tls_channel_impl.h b/src/lib/tls/tls_channel_impl.h index f3edb735b0b..a9cf25c7d02 100644 --- a/src/lib/tls/tls_channel_impl.h +++ b/src/lib/tls/tls_channel_impl.h @@ -98,6 +98,14 @@ class Channel_Impl */ virtual void renegotiate(bool force_full_renegotiation = false) = 0; + /** + * Attempt to update the session's traffic key material + * Note that this is possible with a TLS 1.3 channel, only. + * + * @param request_peer_update if true, require a reciprocal key update + */ + virtual void update_traffic_keys(bool request_peer_update = false) = 0; + /** * @return true iff the counterparty supports the secure * renegotiation extensions. @@ -113,6 +121,67 @@ class Channel_Impl virtual bool timeout_check() = 0; virtual std::string application_protocol() const = 0; + + protected: + /** + * This struct collect all information required to perform a downgrade from TLS 1.3 to TLS 1.2. + * + * The downgrade process is (currently) triggered when a TLS 1.3 client receives a downgrade request + * in the server hello message (@sa `Client_Impl_13::handle(Server_Hello_12)`). As a result, + * `Client::received_data` should detect this condition and replace its `Channel_Impl_13` member by a + * `Channel_Impl_12`. + * + * Note that the downgrade process for the server implementation will likely differ. + */ + struct Downgrade_Information + { + /// The client hello message including the handshake header bytes as transferred to the peer. + std::vector client_hello_message; + + /// The full data transcript received from the peer. This will contain the server hello message that forced us to downgrade. + std::vector peer_transcript; + + Server_Information server_info; + + Callbacks& callbacks; + Session_Manager& session_manager; + Credentials_Manager& creds; + RandomNumberGenerator& rng; + const Policy& policy; + + bool received_tls_13_error_alert; + bool will_downgrade; + }; + + std::unique_ptr m_downgrade_info; + + void preserve_peer_transcript(const uint8_t input[], size_t input_size) + { + BOTAN_STATE_CHECK(m_downgrade_info); + m_downgrade_info->peer_transcript.insert(m_downgrade_info->peer_transcript.end(), + input, input+input_size); + } + + void preserve_client_hello(const std::vector& msg) + { + BOTAN_STATE_CHECK(m_downgrade_info); + m_downgrade_info->client_hello_message = msg; + } + + public: + /** + * Indicates whether a downgrade to TLS 1.2 or lower is in progress + * + * @sa Downgrade_Information + */ + bool is_downgrading() const { return m_downgrade_info && m_downgrade_info->will_downgrade; } + + /** + * @sa Downgrade_Information + */ + std::unique_ptr extract_downgrade_info() { return std::exchange(m_downgrade_info, {}); } + + bool expects_downgrade() const { return m_downgrade_info != nullptr; } }; } diff --git a/src/lib/tls/tls_ciphersuite.cpp b/src/lib/tls/tls_ciphersuite.cpp index 3af6c1f670d..aa8dea6e05e 100644 --- a/src/lib/tls/tls_ciphersuite.cpp +++ b/src/lib/tls/tls_ciphersuite.cpp @@ -72,8 +72,17 @@ bool Ciphersuite::ecc_ciphersuite() const bool Ciphersuite::usable_in_version(Protocol_Version version) const { - BOTAN_UNUSED(version); - return true; + // RFC 8446 B.4.: + // Although TLS 1.3 uses the same cipher suite space as previous + // versions of TLS, TLS 1.3 cipher suites are defined differently, only + // specifying the symmetric ciphers, and cannot be used for TLS 1.2. + // Similarly, cipher suites for TLS 1.2 and lower cannot be used with + // TLS 1.3. + // + // Currently cipher suite codes {0x13,0x01} through {0x13,0x05} are + // allowed for TLS 1.3. This may change in the future. + const auto is_legacy_suite = (ciphersuite_code() & 0xFF00) != 0x1300; + return version.is_pre_tls_13() == is_legacy_suite; } bool Ciphersuite::cbc_ciphersuite() const diff --git a/src/lib/tls/tls_client.cpp b/src/lib/tls/tls_client.cpp index 355f56f52bd..b4309a98a5c 100644 --- a/src/lib/tls/tls_client.cpp +++ b/src/lib/tls/tls_client.cpp @@ -14,6 +14,9 @@ #include #include +#if defined(BOTAN_HAS_TLS_13) + #include +#endif #include #include @@ -34,17 +37,44 @@ Client::Client(Callbacks& callbacks, size_t io_buf_sz) { Protocol_Version effective_version = offer_version; - m_impl = std::make_unique( - callbacks, session_manager, creds, policy, - rng, info, effective_version.is_datagram_protocol(), - next_protocols, io_buf_sz); +#if defined(BOTAN_HAS_TLS_13) + // downgrade to TLS 1.2 directly if we have a legacy session to resume + if(effective_version == Protocol_Version::TLS_V13) + { + Session session; + const bool found = session_manager.load_from_server_info(info, session); + if(found && session.version().is_pre_tls_13()) + effective_version = session.version(); + } + + if(effective_version == Protocol_Version::TLS_V13) + m_impl = std::make_unique( + callbacks, session_manager, creds, policy, + rng, info, next_protocols); + else +#endif + m_impl = std::make_unique( + callbacks, session_manager, creds, policy, + rng, info, effective_version.is_datagram_protocol(), + next_protocols, io_buf_sz); } Client::~Client() = default; size_t Client::received_data(const uint8_t buf[], size_t buf_size) { - return m_impl->received_data(buf, buf_size); + auto read = m_impl->received_data(buf, buf_size); + + if(m_impl->is_downgrading()) + { + auto info = m_impl->extract_downgrade_info(); + m_impl = std::make_unique(*info); + + // replay peer data received so far + read = m_impl->received_data(info->peer_transcript.data(), info->peer_transcript.size()); + } + + return read; } bool Client::is_active() const @@ -74,6 +104,11 @@ void Client::renegotiate(bool force_full_renegotiation) m_impl->renegotiate(force_full_renegotiation); } +void Client::update_traffic_keys(bool request_peer_update) + { + m_impl->update_traffic_keys(request_peer_update); + } + bool Client::secure_renegotiation_supported() const { return m_impl->secure_renegotiation_supported(); diff --git a/src/lib/tls/tls_client.h b/src/lib/tls/tls_client.h index e4157c6324b..858f34eebb4 100644 --- a/src/lib/tls/tls_client.h +++ b/src/lib/tls/tls_client.h @@ -90,6 +90,8 @@ class BOTAN_PUBLIC_API(2,0) Client final : public Channel void renegotiate(bool force_full_renegotiation = false) override; + void update_traffic_keys(bool request_peer_update = false) override; + bool secure_renegotiation_supported() const override; void send(const uint8_t buf[], size_t buf_size) override; diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp index b0890c1bdf0..9519ef05589 100644 --- a/src/lib/tls/tls_extensions.cpp +++ b/src/lib/tls/tls_extensions.cpp @@ -54,6 +54,9 @@ std::unique_ptr make_extension(TLS_Data_Reader& reader, case TLSEXT_EXTENDED_MASTER_SECRET: return std::make_unique(reader, size); + case TLSEXT_RECORD_SIZE_LIMIT: + return std::make_unique(reader, size, from); + case TLSEXT_ENCRYPT_THEN_MAC: return std::make_unique(reader, size); @@ -62,6 +65,20 @@ std::unique_ptr make_extension(TLS_Data_Reader& reader, case TLSEXT_SUPPORTED_VERSIONS: return std::make_unique(reader, size, from); + +#if defined(BOTAN_HAS_TLS_13) + case TLSEXT_COOKIE: + return std::make_unique(reader, size); + + case TLSEXT_PSK_KEY_EXCHANGE_MODES: + return std::make_unique(reader, size); + + case TLSEXT_SIGNATURE_ALGORITHMS_CERT: + return std::make_unique(reader, size); + + case TLSEXT_KEY_SHARE: + return std::make_unique(reader, size, message_type); +#endif } return std::make_unique(static_cast(code), @@ -107,6 +124,34 @@ void Extensions::deserialize(TLS_Data_Reader& reader, } } +bool Extensions::contains_other_than(const std::set& allowed_extensions, + const bool allow_unknown_extensions) const + { + const auto found = extension_types(); + + std::vector diff; + std::set_difference(found.cbegin(), found.end(), + allowed_extensions.cbegin(), allowed_extensions.cend(), + std::back_inserter(diff)); + + if(allow_unknown_extensions) + { + // Go through the found unexpected extensions whether any of those + // is known to this TLS implementation. + const auto itr = std::find_if(diff.cbegin(), diff.cend(), + [this](const auto ext_type) + { + const auto ext = get(ext_type); + return ext && ext->is_implemented(); + }); + + // ... if yes, `contains_other_than` is true + return itr != diff.cend(); + } + + return !diff.empty(); + } + std::unique_ptr Extensions::take(Handshake_Extension_Type type) { const auto i = std::find_if(m_extensions.begin(), m_extensions.end(), @@ -560,13 +605,21 @@ Supported_Versions::Supported_Versions(Protocol_Version offer, const Policy& pol { if(offer.is_datagram_protocol()) { +#if defined(BOTAN_HAS_TLS_12) if(offer >= Protocol_Version::DTLS_V12 && policy.allow_dtls12()) m_versions.push_back(Protocol_Version::DTLS_V12); +#endif } else { +#if defined(BOTAN_HAS_TLS_13) + if(offer >= Protocol_Version::TLS_V13 && policy.allow_tls13()) + m_versions.push_back(Protocol_Version::TLS_V13); +#endif +#if defined(BOTAN_HAS_TLS_12) if(offer >= Protocol_Version::TLS_V12 && policy.allow_tls12()) m_versions.push_back(Protocol_Version::TLS_V12); +#endif } } @@ -600,4 +653,167 @@ bool Supported_Versions::supports(Protocol_Version version) const return false; } + +Record_Size_Limit::Record_Size_Limit(const uint16_t limit) + : m_limit(limit) + { + BOTAN_ASSERT(limit >= 64, + "RFC 8449 does not allow record size limits smaller than 64 bytes"); + BOTAN_ASSERT(limit <= MAX_PLAINTEXT_SIZE + 1 /* encrypted content type byte */, + "RFC 8449 does not allow record size limits larger than 2^14+1"); + } + +Record_Size_Limit::Record_Size_Limit(TLS_Data_Reader& reader, + uint16_t extension_size, + Connection_Side from) + { + if(extension_size != 2) + { + throw TLS_Exception(Alert::DECODE_ERROR, "invalid record_size_limit extension"); + } + + m_limit = reader.get_uint16_t(); + + // RFC 8449 4. + // This value is the length of the plaintext of a protected record. + // The value includes the content type and padding added in TLS 1.3 (that + // is, the complete length of TLSInnerPlaintext). + // + // A server MUST NOT enforce this restriction; a client might advertise + // a higher limit that is enabled by an extension or version the server + // does not understand. A client MAY abort the handshake with an + // "illegal_parameter" alert. + // + // Note: We are currently supporting this extension in TLS 1.3 only, hence + // we check for the TLS 1.3 limit. The TLS 1.2 limit would not include + // the "content type byte" and hence be one byte less! + if(m_limit > MAX_PLAINTEXT_SIZE + 1 /* encrypted content type byte */ && from == SERVER) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Server requested a record size limit larger than the protocol's maximum"); + } + + // RFC 8449 4. + // Endpoints MUST NOT send a "record_size_limit" extension with a value + // smaller than 64. An endpoint MUST treat receipt of a smaller value + // as a fatal error and generate an "illegal_parameter" alert. + if(m_limit < 64) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Received a record size limit smaller than 64 bytes"); + } + } + +std::vector Record_Size_Limit::serialize(Connection_Side) const + { + std::vector buf; + + buf.push_back(get_byte<0>(m_limit)); + buf.push_back(get_byte<1>(m_limit)); + + return buf; + } + + +#if defined(BOTAN_HAS_TLS_13) +Cookie::Cookie(const std::vector& cookie) : + m_cookie(cookie) + { + } + +Cookie::Cookie(TLS_Data_Reader& reader, + uint16_t extension_size) + { + if (extension_size == 0) + { + return; + } + + const uint16_t len = reader.get_uint16_t(); + + if (len == 0) + { + // Based on RFC 8446 4.2.2, len of the Cookie buffer must be at least 1 + throw Decoding_Error("Cookie length must be at least 1 byte"); + } + + if (len > reader.remaining_bytes()) + { + throw Decoding_Error("Not enough bytes in the buffer to decode Cookie"); + } + + for (auto i = 0u; i < len; ++i) + { + m_cookie.push_back(reader.get_byte()); + } + } + +std::vector Cookie::serialize(Connection_Side /*whoami*/) const + { + std::vector buf; + + const uint16_t len = static_cast(m_cookie.size()); + + buf.push_back(get_byte<0>(len)); + buf.push_back(get_byte<1>(len)); + + for (const auto& cookie_byte : m_cookie) + { + buf.push_back(cookie_byte); + } + + return buf; + } + + +std::vector PSK_Key_Exchange_Modes::serialize(Connection_Side) const + { + std::vector buf; + + BOTAN_ASSERT_NOMSG(m_modes.size() < 256); + buf.push_back(static_cast(m_modes.size())); + for (const auto& mode : m_modes) + { + buf.push_back(static_cast(mode)); + } + + return buf; + } + +PSK_Key_Exchange_Modes::PSK_Key_Exchange_Modes(TLS_Data_Reader& reader, uint16_t extension_size) + { + if (extension_size < 2) + { + throw Decoding_Error("Empty psk_key_exchange_modes extension is illegal"); + } + + const auto mode_count = reader.get_byte(); + for(uint16_t i = 0; i < mode_count; ++i) + { + const uint8_t mode = reader.get_byte(); + if (mode != 0 && mode != 1) + { + throw Decoding_Error("Unexpected PSK mode: " + std::to_string(mode)); + } + + m_modes.push_back(PSK_Key_Exchange_Mode(mode)); + } + } + +Signature_Algorithms_Cert::Signature_Algorithms_Cert(const std::vector& schemes) + : m_siganture_algorithms(schemes) + { + } + +Signature_Algorithms_Cert::Signature_Algorithms_Cert(TLS_Data_Reader& reader, uint16_t extension_size) + : m_siganture_algorithms(reader, extension_size) + { + } + +std::vector Signature_Algorithms_Cert::serialize(Connection_Side whoami) const + { + return m_siganture_algorithms.serialize(whoami); + } + +#endif } diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h index f8eadb75501..ca534d0c4b6 100644 --- a/src/lib/tls/tls_extensions.h +++ b/src/lib/tls/tls_extensions.h @@ -31,6 +31,15 @@ class RandomNumberGenerator; namespace TLS { +#if defined(BOTAN_HAS_TLS_13) +class Callbacks; + +enum class PSK_Key_Exchange_Mode : uint8_t { + PSK_KE = 0, + PSK_DHE_KE = 1 +}; + +#endif class Policy; class TLS_Data_Reader; @@ -49,9 +58,19 @@ enum Handshake_Extension_Type { TLSEXT_ENCRYPT_THEN_MAC = 22, TLSEXT_EXTENDED_MASTER_SECRET = 23, + TLSEXT_RECORD_SIZE_LIMIT = 28, + TLSEXT_SESSION_TICKET = 35, TLSEXT_SUPPORTED_VERSIONS = 43, +#if defined(BOTAN_HAS_TLS_13) + TLSEXT_COOKIE = 44, + + TLSEXT_PSK_KEY_EXCHANGE_MODES = 45, + + TLSEXT_SIGNATURE_ALGORITHMS_CERT = 50, + TLSEXT_KEY_SHARE = 51, +#endif TLSEXT_SAFE_RENEGOTIATION = 65281, }; @@ -455,6 +474,163 @@ class BOTAN_UNSTABLE_API Supported_Versions final : public Extension using Named_Group = Group_Params; +/** +* Record Size Limit (RFC 8449) +* +* TODO: the record size limit will currently not be honored by the record protocol +*/ +class BOTAN_UNSTABLE_API Record_Size_Limit final : public Extension + { + public: + static Handshake_Extension_Type static_type() + { return TLSEXT_RECORD_SIZE_LIMIT; } + + Handshake_Extension_Type type() const override { return static_type(); } + + explicit Record_Size_Limit(const uint16_t limit); + + Record_Size_Limit(TLS_Data_Reader& reader, uint16_t extension_size, Connection_Side from); + + uint16_t limit() const { return m_limit; } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return m_limit == 0; } + + private: + uint16_t m_limit; + }; + +using Named_Group = Group_Params; + +#if defined(BOTAN_HAS_TLS_13) +/** +* Cookie from RFC 8446 4.2.2 +*/ +class BOTAN_UNSTABLE_API Cookie final : public Extension + { + public: + static Handshake_Extension_Type static_type() + { return TLSEXT_COOKIE; } + + Handshake_Extension_Type type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return m_cookie.empty(); } + + const std::vector& get_cookie() const { return m_cookie; } + + explicit Cookie(const std::vector& cookie); + + explicit Cookie(TLS_Data_Reader& reader, + uint16_t extension_size); + + private: + std::vector m_cookie; + }; + +/** +* Pre-Shared Key Exchange Modes from RFC 8446 4.2.9 +*/ +class BOTAN_UNSTABLE_API PSK_Key_Exchange_Modes final : public Extension + { + public: + static Handshake_Extension_Type static_type() + { return TLSEXT_PSK_KEY_EXCHANGE_MODES; } + + Handshake_Extension_Type type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return m_modes.empty(); } + + const std::vector& modes() const { return m_modes; } + + explicit PSK_Key_Exchange_Modes(std::vector modes) + : m_modes(std::move(modes)) {} + + explicit PSK_Key_Exchange_Modes(TLS_Data_Reader& reader, uint16_t extension_size); + + private: + std::vector m_modes; + }; + + +/** +* Signature_Algorithms_Cert from RFC 8446 +*/ +class BOTAN_UNSTABLE_API Signature_Algorithms_Cert final : public Extension + { + public: + static Handshake_Extension_Type static_type() + { return TLSEXT_SIGNATURE_ALGORITHMS_CERT; } + + Handshake_Extension_Type type() const override { return static_type(); } + + const std::vector& supported_schemes() const { return m_siganture_algorithms.supported_schemes(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override { return m_siganture_algorithms.empty(); } + + explicit Signature_Algorithms_Cert(const std::vector& schemes); + + Signature_Algorithms_Cert(TLS_Data_Reader& reader, uint16_t extension_size); + + private: + const Signature_Algorithms m_siganture_algorithms; + }; + +/** +* Key_Share from RFC 8446 4.2.8 +*/ +class BOTAN_UNSTABLE_API Key_Share final : public Extension + { + public: + static Handshake_Extension_Type static_type() + { return TLSEXT_KEY_SHARE; } + + Handshake_Extension_Type type() const override { return static_type(); } + + std::vector serialize(Connection_Side whoami) const override; + + bool empty() const override; + + /** + * Perform key exchange with the peer's key share. + * This method can be called on a ClientHello's Key_Share with a ServerHello's Key_Share or vice versa. + */ + secure_vector exchange(const Key_Share& peer_keyshare, const Policy& policy, Callbacks& cb, RandomNumberGenerator& rng) const; + + /** + * Update a ClientHello's Key_Share to comply with a HelloRetryRequest. + * + * This will create new Key_Share_Entries and should only be called on a ClientHello Key_Share with a HelloRetryRequest Key_Share. + */ + void retry_offer(const Key_Share& retry_request_keyshare, const std::vector& supported_groups, Callbacks& cb, RandomNumberGenerator& rng); + + /** + * Delete all private keys that might be contained in Key_Share_Entries in this extension. + */ + void erase(); + + explicit Key_Share(TLS_Data_Reader& reader, + uint16_t extension_size, + Handshake_Type message_type); + + // constuctor used for ClientHello msg + explicit Key_Share(const Policy& policy, Callbacks& cb, RandomNumberGenerator& rng); + + // destructor implemented in .cpp to hide Key_Share_Impl + ~Key_Share(); + + private: + class Key_Share_Impl; + std::unique_ptr m_impl; + }; +#endif + /** * Unknown extensions are deserialized as this type */ @@ -533,6 +709,24 @@ class BOTAN_UNSTABLE_API Extensions final const Connection_Side from, const Handshake_Type message_type); + /** + * @param allowed_extensions extension types that are allowed + * @param allow_unknown_extensions if true, ignores unrecognized extensions + * @returns true if this contains any extensions that are not contained in @p allowed_extensions. + */ + bool contains_other_than(const std::set& allowed_extensions, + const bool allow_unknown_extensions = false) const; + + /** + * @param allowed_extensions extension types that are allowed + * @returns true if this contains any extensions implemented by Botan that + * are not contained in @p allowed_extensions. + */ + bool contains_implemented_extensions_other_than(const std::set& allowed_extensions) const + { + return contains_other_than(allowed_extensions, true); + } + /** * Take the extension with the given type out of the extensions list. * Returns a nullptr if the extension didn't exist. diff --git a/src/lib/tls/tls_extensions_cert_status_req.cpp b/src/lib/tls/tls_extensions_cert_status_req.cpp index 55031a72e64..ba87c338d25 100644 --- a/src/lib/tls/tls_extensions_cert_status_req.cpp +++ b/src/lib/tls/tls_extensions_cert_status_req.cpp @@ -45,12 +45,38 @@ std::vector Certificate_Status_Request::serialize(Connection_Side whoam Certificate_Status_Request::Certificate_Status_Request(TLS_Data_Reader& reader, uint16_t extension_size, Connection_Side from, - Handshake_Type) + Handshake_Type message_type) { if(from == Connection_Side::SERVER) { - if(extension_size != 0) - throw Decoding_Error("Server sent non-empty Certificate_Status_Request extension in Server Hello"); + // RFC 8446 4.4.2.1 + // In TLS 1.2 and below, the server replies with an empty extension + // [in its Server Hello] [...]. In TLS 1.3, the server's OCSP information + // is carried in an extension in the [Certificate handshake message] + // containing the associated certificate. + // + // We use the `message_type` context information as an indication which + // type of Certificate_Status_Request extension to expect. + if(message_type == Handshake_Type::SERVER_HELLO) + { + // ... in a Server Hello the extension must have a zero-length body + if(extension_size != 0) + throw Decoding_Error("Server sent non-empty Certificate_Status_Request extension in Server Hello"); + } + else if(message_type == Handshake_Type::CERTIFICATE) + { + // RFC 8446 4.4.2.1 + // In TLS 1.3, the server's OCSP information is carried in an + // extension in the CertificateEntry [in a Certificate handshake + // message] [...]. Specifically, the body of the "status_request" + // extension from the server MUST be a CertificateStatus structure + // as defined in [RFC6066] [...]. + m_response = Certificate_Status(reader.get_fixed(extension_size)).response(); + } + else + { + throw TLS_Exception(Alert::UNSUPPORTED_EXTENSION, "Server sent a Certificate_Status_Request extension in an unsupported context"); + } } else if(extension_size > 0) { diff --git a/src/lib/tls/tls_extensions_key_share.cpp b/src/lib/tls/tls_extensions_key_share.cpp new file mode 100644 index 00000000000..a0d19bdbcba --- /dev/null +++ b/src/lib/tls/tls_extensions_key_share.cpp @@ -0,0 +1,553 @@ +/* +* TLS Extension Key Share +* (C) 2011,2012,2015,2016 Jack Lloyd +* 2016 Juraj Somorovsky +* 2021 Elektrobit Automotive GmbH +* 2022 Hannes Rantzsch, René Meusel, neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOTAN_HAS_CURVE_25519) + #include +#endif + +#include +#include + +#if defined(BOTAN_HAS_TLS_13) + +namespace Botan::TLS { + +namespace { + +[[maybe_unused]] constexpr bool is_x25519(const Group_Params group) + { + return group == Group_Params::X25519; + } + +[[maybe_unused]] constexpr bool is_ecdh(const Group_Params group) + { + return + group == Group_Params::SECP256R1 || + group == Group_Params::SECP384R1 || + group == Group_Params::SECP521R1 || + group == Group_Params::BRAINPOOL256R1 || + group == Group_Params::BRAINPOOL384R1 || + group == Group_Params::BRAINPOOL512R1; + } + +[[maybe_unused]] constexpr bool is_dh(const Group_Params group) + { + return + group == Group_Params::FFDHE_2048 || + group == Group_Params::FFDHE_3072 || + group == Group_Params::FFDHE_4096 || + group == Group_Params::FFDHE_6144 || + group == Group_Params::FFDHE_8192; + } + +class Key_Share_Entry + { + public: + Key_Share_Entry(TLS_Data_Reader& reader) + { + // TODO check that the group actually exists before casting... + m_group = static_cast(reader.get_uint16_t()); + const auto key_exchange_length = reader.get_uint16_t(); + m_key_exchange = reader.get_fixed(key_exchange_length); + } + + Key_Share_Entry(Named_Group group, std::vector key_exchange) + : m_group(group) + , m_key_exchange(std::move(key_exchange)) + { + if(m_key_exchange.empty()) + { + throw Decoding_Error("Size of key_exchange in KeyShareEntry must be at least 1 byte."); + } + } + + Key_Share_Entry(const TLS::Group_Params group, Callbacks& cb, RandomNumberGenerator& rng) + : m_group(group) + { + if(is_ecdh(group)) + { + const EC_Group ec_group(cb.tls_decode_group_param(group)); + auto skey = std::make_unique(rng, ec_group); + + // RFC 8446 Ch. 4.2.8.2 + // + // Note: Versions of TLS prior to 1.3 permitted point format + // negotiation; TLS 1.3 removes this feature in favor of a single point + // format for each curve. + // + // Hence, we neither need to take Policy::use_ecc_point_compression() nor + // ClientHello::prefers_compressed_ec_points() into account here. + m_key_exchange = skey->public_value(PointGFp::UNCOMPRESSED); + m_private_key = std::move(skey); + } + else if(is_dh(group)) + { + // RFC 8446 Ch. 4.2.8.1 + // + // The opaque value contains the Diffie-Hellman + // public value (Y = g^X mod p) for the specified group (see [RFC7919] + // for group definitions) encoded as a big-endian integer and padded to + // the left with zeros to the size of p in bytes. + auto skey = std::make_unique(rng, DL_Group(cb.tls_decode_group_param(group))); + m_key_exchange = skey->public_value(); + m_private_key = std::move(skey); +#if defined(BOTAN_HAS_CURVE_25519) + } + else if(is_x25519(group)) + { + auto skey = std::make_unique(rng); + m_key_exchange = skey->public_value(); + m_private_key = std::move(skey); +#endif + } + else + { + throw Decoding_Error("cannot create a key offering without a group definition"); + } + } + + bool empty() const { return (m_group == Group_Params::NONE) && m_key_exchange.empty(); } + + std::vector serialize() const + { + std::vector result; + result.reserve(m_key_exchange.size() + 2); + + const uint16_t named_curve_id = static_cast(m_group); + result.push_back(get_byte<0>(named_curve_id)); + result.push_back(get_byte<1>(named_curve_id)); + append_tls_length_value(result, m_key_exchange, 2); + + return result; + } + + Named_Group group() const { return m_group; } + + /** + * Perform key exchange with another Key_Share_Entry's public key + * + * The caller must ensure that both this and `received` have the same group. + * This method must not be called on Key_Share_Entries without a private key. + */ + secure_vector exchange(const Key_Share_Entry& received, const Policy& policy, Callbacks& cb, + RandomNumberGenerator& rng) const + { + BOTAN_ASSERT_NOMSG(m_private_key != nullptr); + BOTAN_ASSERT_NOMSG(m_group == received.m_group); + + PK_Key_Agreement ka(*m_private_key, rng, "Raw"); + + if(is_ecdh(m_group)) + { + const EC_Group ec_group(cb.tls_decode_group_param(m_group)); + ECDH_PublicKey peer_key(ec_group, ec_group.OS2ECP(received.m_key_exchange)); + policy.check_peer_key_acceptable(peer_key); + + return ka.derive_key(0, peer_key.public_value()).bits_of(); + } + + if(is_dh(m_group)) + { + const DL_Group dl_group(cb.tls_decode_group_param(m_group)); + + if(!dl_group.verify_group(rng, false)) + { throw TLS_Exception(Alert::INSUFFICIENT_SECURITY, "DH group validation failed"); } + + DH_PublicKey peer_key(dl_group, BigInt::decode(received.m_key_exchange)); + policy.check_peer_key_acceptable(peer_key); + + // Note: in contrast to TLS 1.2, no leading zeros are stripped here + // cf. RFC 8446 7.4.1 + return ka.derive_key(0, peer_key.public_value()).bits_of(); + } + +#if defined(BOTAN_HAS_CURVE_25519) + if(is_x25519(m_group)) + { + if(received.m_key_exchange.size() != 32) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Invalid X25519 key size"); + } + + Curve25519_PublicKey peer_key(received.m_key_exchange); + policy.check_peer_key_acceptable(peer_key); + + return ka.derive_key(0, peer_key.public_value()).bits_of(); + } +#endif + + BOTAN_ASSERT_NOMSG(false); + } + + void erase() + { + m_private_key.reset(); + } + + private: + Named_Group m_group; + std::vector m_key_exchange; + std::unique_ptr m_private_key; + }; + +class Key_Share_ServerHello + { + public: + Key_Share_ServerHello(TLS_Data_Reader& reader, uint16_t) + : m_server_share(reader) {} + ~Key_Share_ServerHello() = default; + + Key_Share_ServerHello(const Key_Share_ServerHello&) = delete; + Key_Share_ServerHello& operator=(const Key_Share_ServerHello&) = delete; + + Key_Share_ServerHello(Key_Share_ServerHello&&) = default; + Key_Share_ServerHello& operator=(Key_Share_ServerHello&&) = default; + + std::vector serialize() const + { + std::vector buf; + + const auto server_share_serialized = m_server_share.serialize(); + buf.insert(buf.end(), server_share_serialized.cbegin(), server_share_serialized.cend()); + + return buf; + } + + bool empty() const + { + return m_server_share.empty(); + } + + const Key_Share_Entry& get_singleton_entry() const + { + return m_server_share; + } + + void erase() + { + m_server_share.erase(); + } + + private: + Key_Share_Entry m_server_share; + }; + +class Key_Share_ClientHello + { + public: + Key_Share_ClientHello(TLS_Data_Reader& reader, uint16_t /* extension_size */) + { + const auto client_key_share_length = reader.get_uint16_t(); + const auto read_bytes_so_far_begin = reader.read_so_far(); + + while(reader.has_remaining() && ((reader.read_so_far() - read_bytes_so_far_begin) < client_key_share_length)) + { + const auto group = reader.get_uint16_t(); + const auto key_exchange_length = reader.get_uint16_t(); + + if(key_exchange_length > reader.remaining_bytes()) + { + throw Decoding_Error("Not enough bytes in the buffer to decode KeyShare (ClientHello) extension"); + } + + std::vector client_share; + client_share.reserve(key_exchange_length); + + for(auto i = 0u; i < key_exchange_length; ++i) + { + client_share.push_back(reader.get_byte()); + } + + m_client_shares.emplace_back(static_cast(group), client_share); + } + + if((reader.read_so_far() - read_bytes_so_far_begin) != client_key_share_length) + { + throw Decoding_Error("Read bytes are not equal client KeyShare length"); + } + } + + Key_Share_ClientHello(const Policy& policy, Callbacks& cb, RandomNumberGenerator& rng) + { + const auto supported = policy.key_exchange_groups(); + const auto offers = policy.key_exchange_groups_to_offer(); + + // RFC 8446 P. 48 + // + // This vector MAY be empty if the client is requesting a + // HelloRetryRequest. Each KeyShareEntry value MUST correspond to a + // group offered in the "supported_groups" extension and MUST appear in + // the same order. However, the values MAY be a non-contiguous subset + // of the "supported_groups" extension and MAY omit the most preferred + // groups. + // + // ... hence, we're going through the supported groups and find those that + // should be used to offer a key exchange. This will satisfy above spec. + for(const auto group : supported) + { + if(std::find(offers.begin(), offers.end(), group) == offers.end()) + { + continue; + } + m_client_shares.emplace_back(group, cb, rng); + } + } + ~Key_Share_ClientHello() = default; + + Key_Share_ClientHello(const Key_Share_ClientHello&) = delete; + Key_Share_ClientHello& operator=(const Key_Share_ClientHello&) = delete; + + Key_Share_ClientHello(Key_Share_ClientHello&&) = default; + Key_Share_ClientHello& operator=(Key_Share_ClientHello&&) = default; + + void retry_offer(const TLS::Group_Params to_offer, Callbacks& cb, RandomNumberGenerator& rng) + { + // RFC 8446 4.2.8 + // The selected_group field [MUST] not correspond to a group which was provided + // in the "key_share" extension in the original ClientHello. + if(std::find_if(m_client_shares.cbegin(), m_client_shares.cend(), + [&](const auto& kse) { return kse.group() == to_offer; }) != + m_client_shares.cend()) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "group was already offered"); + } + + m_client_shares.clear(); + m_client_shares.emplace_back(to_offer, cb, rng); + } + + std::vector serialize() const + { + std::vector shares; + for(const auto& share : m_client_shares) + { + const auto serialized_share = share.serialize(); + shares.insert(shares.end(), serialized_share.cbegin(), serialized_share.cend()); + } + + std::vector result; + append_tls_length_value(result, shares, 2); + return result; + } + + bool empty() const + { + // RFC 8446 4.2.8 + // Clients MAY send an empty client_shares vector in order to request + // group selection from the server, at the cost of an additional round + // trip [...]. + return false; + } + + secure_vector exchange(const Key_Share_ServerHello& server_share, const Policy& policy, Callbacks& cb, + RandomNumberGenerator& rng) const + { + const auto& server_selected = server_share.get_singleton_entry(); + + // find the client offer that matches the server offer + auto match = std::find_if(m_client_shares.cbegin(), m_client_shares.cend(), [&server_selected](const auto& offered) + { + return offered.group() == server_selected.group(); + }); + + // RFC 8446 4.2.8: + // [The KeyShareEntry in the ServerHello] MUST be in the same group + // as the KeyShareEntry value offered by the client that the server + // has selected for the negotiated key exchange. Servers MUST NOT + // send a KeyShareEntry for any group not indicated in the client's + // "supported_groups" extension [...] + if(!value_exists(policy.key_exchange_groups(), server_selected.group()) || + match == m_client_shares.cend()) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Server selected an unexpected key exchange group."); + } + + return match->exchange(server_selected, policy, cb, rng); + } + + void erase() + { + for(auto& s : m_client_shares) + { s.erase(); } + } + + private: + std::vector m_client_shares; + }; + +class Key_Share_HelloRetryRequest + { + public: + Key_Share_HelloRetryRequest(TLS_Data_Reader& reader, + uint16_t extension_size) + { + constexpr auto sizeof_uint16_t = sizeof(uint16_t); + + if(extension_size != sizeof_uint16_t) + { + throw Decoding_Error("Size of KeyShare extension in HelloRetryRequest must be " + + std::to_string(sizeof_uint16_t) + " bytes"); + } + + m_selected_group = static_cast(reader.get_uint16_t()); + } + + ~Key_Share_HelloRetryRequest() = default; + + Key_Share_HelloRetryRequest(const Key_Share_HelloRetryRequest&) = delete; + Key_Share_HelloRetryRequest& operator=(const Key_Share_HelloRetryRequest&) = delete; + + Key_Share_HelloRetryRequest(Key_Share_HelloRetryRequest&&) = default; + Key_Share_HelloRetryRequest& operator=(Key_Share_HelloRetryRequest&&) = default; + + std::vector serialize() const + { + return { get_byte<0>(static_cast(m_selected_group)), + get_byte<1>(static_cast(m_selected_group)) }; + } + + Named_Group get_selected_group() const + { + return m_selected_group; + } + + bool empty() const + { + return m_selected_group == Group_Params::NONE; + } + + void erase() {} + + private: + Named_Group m_selected_group; + }; + +} // namespace + +class Key_Share::Key_Share_Impl + { + public: + using Key_Share_Type = std::variant; + + Key_Share_Impl(Key_Share_Type ks) : key_share(std::move(ks)) {} + + Key_Share_Type key_share; + }; + +Key_Share::Key_Share(TLS_Data_Reader& reader, + uint16_t extension_size, + Handshake_Type message_type) + { + if(message_type == CLIENT_HELLO) + { + m_impl = std::make_unique(Key_Share_ClientHello(reader, extension_size)); + } + else if(message_type == HELLO_RETRY_REQUEST) // Connection_Side::SERVER + { + m_impl = std::make_unique(Key_Share_HelloRetryRequest(reader, extension_size)); + } + else if(message_type == SERVER_HELLO) // Connection_Side::SERVER + { + m_impl = std::make_unique(Key_Share_ServerHello(reader, extension_size)); + } + else + { + throw Invalid_Argument(std::string("cannot create a Key_Share extension for message of type: ") + + handshake_type_to_string(message_type)); + } + } + +// ClientHello +Key_Share::Key_Share(const Policy& policy, Callbacks& cb, RandomNumberGenerator& rng) : + m_impl(std::make_unique(Key_Share_ClientHello(policy, cb, rng))) {} + +Key_Share::~Key_Share() {} + +std::vector Key_Share::serialize(Connection_Side /*whoami*/) const + { + return std::visit([](const auto& key_share) { return key_share.serialize(); }, m_impl->key_share); + } + +bool Key_Share::empty() const + { + return std::visit([](const auto& key_share) { return key_share.empty(); }, m_impl->key_share); + } + +namespace { +// This is a helper utility to emulate pattern matching with std::visit. +// See https://en.cppreference.com/w/cpp/utility/variant/visit for more info. +template struct overloaded : Ts... { using Ts::operator()...; }; +// explicit deduction guide (not needed as of C++20) +template overloaded(Ts...) -> overloaded; +} + +secure_vector Key_Share::exchange(const Key_Share& peer_keyshare, + const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng) const + { + return std::visit(overloaded + { + [&](const Key_Share_ClientHello& ch, const Key_Share_ServerHello& sh) + { + return ch.exchange(sh, policy, cb, rng); + }, + [&](const Key_Share_ServerHello& sh, const Key_Share_ClientHello& ch) + { + return ch.exchange(sh, policy, cb, rng); + }, + [](const auto&, const auto&) -> secure_vector + { + throw Botan::Invalid_Argument("can only exchange with ServerHello and ClientHello Key_Share"); + } + }, m_impl->key_share, peer_keyshare.m_impl->key_share); + } + +void Key_Share::retry_offer(const Key_Share& retry_request_keyshare, + const std::vector& supported_groups, + Callbacks& cb, + RandomNumberGenerator& rng) + { + std::visit(overloaded + { + [&](Key_Share_ClientHello& ch, const Key_Share_HelloRetryRequest& hrr) + { + auto selected = hrr.get_selected_group(); + // RFC 8446 4.2.8 + // [T]he selected_group field [MUST correspond] to a group which was provided in + // the "supported_groups" extension in the original ClientHello + if(!value_exists(supported_groups, selected)) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "group was not advertised as supported"); } + + return ch.retry_offer(selected, cb, rng); + }, + [](const auto&, const auto&) + { + throw Botan::Invalid_Argument("can only retry with HelloRetryRequest on a ClientHello Key_Share"); + } + }, m_impl->key_share, retry_request_keyshare.m_impl->key_share); + } + +void Key_Share::erase() + { + std::visit([](auto& key_share) { key_share.erase(); }, m_impl->key_share); + } + +} // Botan::TLS + +#endif // HAS_TLS_13 diff --git a/src/lib/tls/tls_handshake_transitions.cpp b/src/lib/tls/tls_handshake_transitions.cpp index 9bf3611c1b9..db7f4b36bb6 100644 --- a/src/lib/tls/tls_handshake_transitions.cpp +++ b/src/lib/tls/tls_handshake_transitions.cpp @@ -65,6 +65,18 @@ uint32_t bitmask_for_handshake_type(Handshake_Type type) case FINISHED: return (1 << 14); + case END_OF_EARLY_DATA: // RFC 8446 + return (1 << 15); + + case ENCRYPTED_EXTENSIONS: // RFC 8446 + return (1 << 16); + + case KEY_UPDATE: // RFC 8446 + return (1 << 17); + + case HELLO_RETRY_REQUEST: // RFC 8446 + return (1 << 18); + // allow explicitly disabling new handshakes case HANDSHAKE_NONE: return 0; @@ -92,7 +104,10 @@ std::string handshake_mask_to_string(uint32_t mask, char combiner) CLIENT_KEX, NEW_SESSION_TICKET, HANDSHAKE_CCS, - FINISHED + FINISHED, + END_OF_EARLY_DATA, + ENCRYPTED_EXTENSIONS, + KEY_UPDATE }; std::ostringstream o; diff --git a/src/lib/tls/tls_magic.h b/src/lib/tls/tls_magic.h index b0da253adfb..a53c467054e 100644 --- a/src/lib/tls/tls_magic.h +++ b/src/lib/tls/tls_magic.h @@ -30,8 +30,16 @@ enum Size_Limits : size_t { // The "TLSInnerPlaintext" length, i.e. the maximum amount of plaintext // application data that can be transmitted in a single TLS record. MAX_PLAINTEXT_SIZE = 16*1024, + MAX_COMPRESSED_SIZE = MAX_PLAINTEXT_SIZE + 1024, MAX_CIPHERTEXT_SIZE = MAX_COMPRESSED_SIZE + 1024, + + // RFC 8446 5.2: + // This limit is derived from the maximum TLSInnerPlaintext length of 2^14 + // octets + 1 octet for ContentType + the maximum AEAD expansion of 255 + // octets. + MAX_AEAD_EXPANSION_SIZE_TLS13 = 255, + MAX_CIPHERTEXT_SIZE_TLS13 = MAX_PLAINTEXT_SIZE + MAX_AEAD_EXPANSION_SIZE_TLS13 + 1 }; // This will become an enum class in a future major release @@ -39,11 +47,15 @@ enum Connection_Side { CLIENT = 1, SERVER = 2 }; // This will become an enum class in a future major release enum Record_Type { + INVALID = 0, // RFC 8446 (TLS 1.3) + CHANGE_CIPHER_SPEC = 20, ALERT = 21, HANDSHAKE = 22, APPLICATION_DATA = 23, + HEARTBEAT = 24, // RFC 6520 (TLS 1.3) + NO_RECORD = 256 }; @@ -54,6 +66,10 @@ enum Handshake_Type { SERVER_HELLO = 2, HELLO_VERIFY_REQUEST = 3, NEW_SESSION_TICKET = 4, // RFC 5077 + + END_OF_EARLY_DATA = 5, // RFC 8446 (TLS 1.3) + ENCRYPTED_EXTENSIONS = 8, // RFC 8446 (TLS 1.3) + CERTIFICATE = 11, SERVER_KEX = 12, CERTIFICATE_REQUEST = 13, @@ -65,7 +81,10 @@ enum Handshake_Type { CERTIFICATE_URL = 21, CERTIFICATE_STATUS = 22, - HANDSHAKE_CCS = 254, // Not a wire value + KEY_UPDATE = 24, // RFC 8446 (TLS 1.3) + + HELLO_RETRY_REQUEST = 253, // Not a wire value (HRR appears as an ordinary Server Hello) + HANDSHAKE_CCS = 254, // Not a wire value (TLS 1.3 uses this value for 'message_hash' -- RFC 8446 4.4.1) HANDSHAKE_NONE = 255 // Null value }; diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h index c502961ba15..8cabb8c83d8 100644 --- a/src/lib/tls/tls_messages.h +++ b/src/lib/tls/tls_messages.h @@ -208,12 +208,44 @@ class BOTAN_UNSTABLE_API Client_Hello_12 final : public Client_Hello void update_hello_cookie(const Hello_Verify_Request& hello_verify); }; +#if defined(BOTAN_HAS_TLS_13) + +class BOTAN_UNSTABLE_API Client_Hello_13 final : public Client_Hello + { + public: + explicit Client_Hello_13(const std::vector& buf) : Client_Hello(buf) {} + + Client_Hello_13(const Policy& policy, + Callbacks& cb, + RandomNumberGenerator& rng, + const std::string& hostname, + const std::vector& next_protocols); + + + void retry(const Hello_Retry_Request& hrr, + Callbacks& cb, + RandomNumberGenerator& rng); + + std::vector supported_versions() const; + }; + +#endif // BOTAN_HAS_TLS_13 + +class Server_Hello_Internal; + /** * Server Hello Message */ class BOTAN_UNSTABLE_API Server_Hello : public Handshake_Message { public: + Server_Hello(const Server_Hello&) = delete; + Server_Hello& operator=(const Server_Hello&) = delete; + Server_Hello(Server_Hello&&); + Server_Hello& operator=(Server_Hello&&); + + ~Server_Hello(); + std::vector serialize() const override; Handshake_Type type() const override; @@ -226,38 +258,7 @@ class BOTAN_UNSTABLE_API Server_Hello : public Handshake_Message virtual Protocol_Version selected_version() const = 0; protected: - /** - * Version-agnostic internal server hello data container that allows - * parsing Server_Hello messages without prior knowledge of the contained - * protocol version. - */ - class Internal - { - public: - Internal(const std::vector& buf); - - Internal(Protocol_Version legacy_version, - std::vector session_id, - std::vector random, - const uint16_t ciphersuite, - const uint8_t comp_method); - - Protocol_Version version() const; - - public: - Protocol_Version legacy_version; - std::vector session_id; - std::vector random; - bool is_hello_retry_request; - uint16_t ciphersuite; - uint8_t comp_method; - - Extensions extensions; - }; - - protected: - explicit Server_Hello(std::unique_ptr data) - : m_data(std::move(data)) {} + explicit Server_Hello(std::unique_ptr data); // methods used internally and potentially exposed by one of the subclasses std::set extension_types() const; @@ -266,7 +267,7 @@ class BOTAN_UNSTABLE_API Server_Hello : public Handshake_Message Protocol_Version legacy_version() const; protected: - std::unique_ptr m_data; + std::unique_ptr m_data; }; class BOTAN_UNSTABLE_API Server_Hello_12 final : public Server_Hello @@ -321,7 +322,7 @@ class BOTAN_UNSTABLE_API Server_Hello_12 final : public Server_Hello protected: friend class Server_Hello_13; // to allow construction by Server_Hello_13::parse() - explicit Server_Hello_12(std::unique_ptr data); + explicit Server_Hello_12(std::unique_ptr data); public: using Server_Hello::random; @@ -357,6 +358,63 @@ class BOTAN_UNSTABLE_API Server_Hello_12 final : public Server_Hello std::optional random_signals_downgrade() const; }; +#if defined(BOTAN_HAS_TLS_13) + +class Hello_Retry_Request; + +class BOTAN_UNSTABLE_API Server_Hello_13 : public Server_Hello + { + protected: + static struct Server_Hello_Tag {} as_server_hello; + static struct Hello_Retry_Request_Tag {} as_hello_retry_request; + + explicit Server_Hello_13(std::unique_ptr data, Server_Hello_Tag tag = as_server_hello); + explicit Server_Hello_13(std::unique_ptr data, Hello_Retry_Request_Tag tag); + void basic_validation() const; + + public: + static std::variant + parse(const std::vector& buf); + + /** + * Return desired downgrade version indicated by hello random, if any. + */ + std::optional random_signals_downgrade() const; + + /** + * @returns the selected version as indicated by the supported_versions extension + */ + Protocol_Version selected_version() const override; + }; + +class BOTAN_UNSTABLE_API Hello_Retry_Request final : public Server_Hello_13 + { + protected: + friend class Server_Hello_13; // to allow construction by Server_Hello_13::parse() + explicit Hello_Retry_Request(std::unique_ptr data); + + public: + Handshake_Type type() const override { return HELLO_RETRY_REQUEST; } + Handshake_Type wire_type() const override { return SERVER_HELLO; } + }; + +#endif // BOTAN_HAS_TLS_13 + +class BOTAN_UNSTABLE_API Encrypted_Extensions final : public Handshake_Message + { + public: + explicit Encrypted_Extensions(const std::vector& buf); + + Handshake_Type type() const override { return Handshake_Type::ENCRYPTED_EXTENSIONS; } + + const Extensions& extensions() const { return m_extensions; } + + std::vector serialize() const override { return {}; } + + private: + Extensions m_extensions; + }; + /** * Client Key Exchange Message */ @@ -415,6 +473,70 @@ class BOTAN_UNSTABLE_API Certificate_12 final : public Handshake_Message std::vector m_certs; }; +/** +* Certificate Message of TLS 1.3 +*/ +class BOTAN_UNSTABLE_API Certificate_13 final : public Handshake_Message + { + public: + struct Certificate_Entry + { + // TODO: RFC 8446 4.4.2 specifies the possibility to negotiate the usage + // of a single raw public key in lieu of the X.509 certificate + // chain. This is left for future work. + X509_Certificate certificate; + Extensions extensions; + }; + + public: + Handshake_Type type() const override { return CERTIFICATE; } + const std::vector& cert_chain() const { return m_entries; } + + size_t count() const { return m_entries.size(); } + bool empty() const { return m_entries.empty(); } + + /** + * Deserialize a Certificate message + * @param buf the serialized message + * @param policy the TLS policy + * @param side is this a SERVER or CLIENT certificate message + */ + Certificate_13(const std::vector& buf, + const Policy& policy, + const Connection_Side side); + + /** + * Validate a Certificate message regarding what extensions are expected based on + * previous handshake messages. + * + * @param requested_extensions Extensions of Client_Hello or Certificate_Req messages + */ + void validate_extensions(const std::set& requested_extensions) const; + + /** + * Verify the certificate chain + * + * @throws if verification fails. + */ + void verify(Callbacks& callbacks, + const Policy& policy, + Credentials_Manager& creds, + const std::string& hostname, + bool use_ocsp) const; + + std::vector serialize() const override; + + private: + // RFC 8446 4.4.2 + // [...] (in the case of server authentication), + // this field SHALL be zero length. + // + // TODO: implement when adding support for client certificates + std::vector m_request_context; + std::vector m_entries; + Connection_Side m_side; + }; + /** * Certificate Status (RFC 6066) */ @@ -514,6 +636,33 @@ class BOTAN_UNSTABLE_API Certificate_Verify_12 final : public Certificate_Verify const Policy& policy) const; }; +#if defined(BOTAN_HAS_TLS_13) + +/** +* Certificate Verify Message +*/ +class BOTAN_UNSTABLE_API Certificate_Verify_13 final : public Certificate_Verify + { + public: + /** + * Deserialize a Certificate message + * @param buf the serialized message + * @param side is this a SERVER or CLIENT certificate message + */ + Certificate_Verify_13(const std::vector& buf, + const Connection_Side side); + + bool verify(const X509_Certificate& cert, + const std::vector& offered_schemes, + Callbacks& callbacks, + const Transcript_Hash& transcript_hash) const; + + private: + Connection_Side m_side; + }; + +#endif + /** * Finished Message */ @@ -544,6 +693,19 @@ class BOTAN_UNSTABLE_API Finished_12 final : public Finished bool verify(const Handshake_State& state, Connection_Side side) const; }; +#if defined(BOTAN_HAS_TLS_13) +class BOTAN_UNSTABLE_API Finished_13 final : public Finished + { + public: + using Finished::Finished; + Finished_13(Cipher_State* cipher_state, + const Transcript_Hash& transcript_hash); + + bool verify(Cipher_State* cipher_state, + const Transcript_Hash& transcript_hash) const; + }; +#endif + /** * Hello Request Message */ @@ -655,6 +817,24 @@ class BOTAN_UNSTABLE_API New_Session_Ticket_12 final : public Handshake_Message std::vector m_ticket; }; +#if defined(BOTAN_HAS_TLS_13) + +class BOTAN_UNSTABLE_API New_Session_Ticket_13 final : public Handshake_Message + { + public: + Handshake_Type type() const override { return NEW_SESSION_TICKET; } + + explicit New_Session_Ticket_13(const std::vector& buf); + + std::vector serialize() const override; + + private: + + // TODO: implement this message fully + }; + +#endif + /** * Change Cipher Spec */ @@ -667,6 +847,80 @@ class BOTAN_UNSTABLE_API Change_Cipher_Spec final : public Handshake_Message { return std::vector(1, 1); } }; +class BOTAN_UNSTABLE_API Key_Update final : public Handshake_Message + { + public: + Handshake_Type type() const override { return KEY_UPDATE; } + + explicit Key_Update(const bool request_peer_update); + explicit Key_Update(const std::vector& buf); + + std::vector serialize() const override; + + bool expects_reciprocation() const { return m_update_requested; } + + private: + bool m_update_requested; + }; + +#if defined(BOTAN_HAS_TLS_13) + +namespace { +template +struct as_wrapped_references + { + }; + +template +struct as_wrapped_references> + { + using type = std::variant...>; + }; + +template +using as_wrapped_references_t = typename as_wrapped_references::type; +} + +// Handshake message types from RFC 8446 4. +using Handshake_Message_13 = std::variant< + Client_Hello_13, + Server_Hello_13, + Server_Hello_12, + Hello_Retry_Request, + // End_Of_Early_Data, + Encrypted_Extensions, + Certificate_13, + // Certificate_Req_13, + Certificate_Verify_13, + Finished_13>; +using Handshake_Message_13_Ref = as_wrapped_references_t; + +using Post_Handshake_Message_13 = std::variant< + New_Session_Ticket_13, + Key_Update>; + +using Server_Handshake_13_Message = std::variant< + Server_Hello_13, + Server_Hello_12, // indicates a TLS version downgrade + Hello_Retry_Request, + Encrypted_Extensions, + Certificate_13, + Certificate_Verify_13, + Finished_13>; + // Post-Handshake Messages + // New_Session_Ticket_13, + // Key_Update>; +using Server_Handshake_13_Message_Ref = as_wrapped_references_t; + +using Client_Handshake_13_Message = std::variant< + Client_Hello_13, + Finished_13>; + // Post-Handshake Messages + // Key_Update>; +using Client_Handshake_13_Message_Ref = as_wrapped_references_t; + +#endif // BOTAN_HAS_TLS_13 + } } diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index a1f2e30046f..608a9201b34 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -180,6 +180,16 @@ std::vector Policy::key_exchange_groups() const }; } +std::vector Policy::key_exchange_groups_to_offer() const + { + // by default, we offer a key share for the most-preferred group, only + std::vector groups_to_offer; + const auto supported_groups = key_exchange_groups(); + if (!supported_groups.empty()) + groups_to_offer.push_back(supported_groups.front()); + return groups_to_offer; + } + size_t Policy::minimum_dh_group_size() const { return 2048; @@ -259,11 +269,18 @@ uint32_t Policy::session_ticket_lifetime() const bool Policy::acceptable_protocol_version(Protocol_Version version) const { +#if defined(BOTAN_HAS_TLS_13) + if(version == Protocol_Version::TLS_V13 && allow_tls13()) + return true; +#endif + +#if defined(BOTAN_HAS_TLS_12) if(version == Protocol_Version::TLS_V12 && allow_tls12()) return true; if(version == Protocol_Version::DTLS_V12 && allow_dtls12()) return true; +#endif return false; } @@ -278,6 +295,10 @@ Protocol_Version Policy::latest_supported_version(bool datagram) const } else { +#if defined(BOTAN_HAS_TLS_13) + if(acceptable_protocol_version(Protocol_Version::TLS_V13)) + return Protocol_Version::TLS_V13; +#endif if(acceptable_protocol_version(Protocol_Version::TLS_V12)) return Protocol_Version::TLS_V12; throw Invalid_State("Policy forbids all available TLS version"); @@ -293,14 +314,39 @@ bool Policy::acceptable_ciphersuite(const Ciphersuite& ciphersuite) const bool Policy::allow_client_initiated_renegotiation() const { return false; } bool Policy::allow_server_initiated_renegotiation() const { return false; } bool Policy::allow_insecure_renegotiation() const { return false; } -bool Policy::allow_tls12() const { return true; } -bool Policy::allow_dtls12() const { return true; } +bool Policy::allow_tls12() const + { +#if defined(BOTAN_HAS_TLS_12) + return true; +#else + return false; +#endif + } +bool Policy::allow_tls13() const + { +#if defined(BOTAN_HAS_TLS_13) + return true; +#else + return false; +#endif + } +bool Policy::allow_dtls12() const + { +#if defined(BOTAN_HAS_TLS_12) + return true; +#else + return false; +#endif + } bool Policy::include_time_in_hello_random() const { return true; } bool Policy::hide_unknown_users() const { return false; } bool Policy::server_uses_own_ciphersuite_preferences() const { return true; } bool Policy::negotiate_encrypt_then_mac() const { return true; } +bool Policy::use_extended_master_secret() const { return allow_tls12() || allow_dtls12(); } +std::optional Policy::record_size_limit() const { return std::nullopt; } bool Policy::support_cert_status_message() const { return true; } bool Policy::allow_resumption_for_renegotiation() const { return true; } +bool Policy::tls_13_middlebox_compatibility_mode() const { return true; } bool Policy::hash_hello_random() const { return true; } bool Policy::only_resume_with_exact_version() const { return true; } bool Policy::require_client_certificate_authentication() const { return false; } @@ -513,6 +559,7 @@ void print_bool(std::ostream& o, void Policy::print(std::ostream& o) const { print_bool(o, "allow_tls12", allow_tls12()); + print_bool(o, "allow_tls13", allow_tls13()); print_bool(o, "allow_dtls12", allow_dtls12()); print_vec(o, "ciphers", allowed_ciphers()); print_vec(o, "macs", allowed_macs()); @@ -520,14 +567,25 @@ void Policy::print(std::ostream& o) const print_vec(o, "signature_methods", allowed_signature_methods()); print_vec(o, "key_exchange_methods", allowed_key_exchange_methods()); print_vec(o, "key_exchange_groups", key_exchange_groups()); + const auto groups_to_offer = key_exchange_groups_to_offer(); + if (groups_to_offer.empty()) { + print_vec(o, "key_exchange_groups_to_offer", { std::string("none") }); + } else { + print_vec(o, "key_exchange_groups_to_offer", groups_to_offer); + } print_bool(o, "allow_insecure_renegotiation", allow_insecure_renegotiation()); print_bool(o, "include_time_in_hello_random", include_time_in_hello_random()); print_bool(o, "allow_server_initiated_renegotiation", allow_server_initiated_renegotiation()); print_bool(o, "hide_unknown_users", hide_unknown_users()); print_bool(o, "server_uses_own_ciphersuite_preferences", server_uses_own_ciphersuite_preferences()); print_bool(o, "negotiate_encrypt_then_mac", negotiate_encrypt_then_mac()); + print_bool(o, "use_extended_master_secret", use_extended_master_secret()); print_bool(o, "support_cert_status_message", support_cert_status_message()); + print_bool(o, "tls_13_middlebox_compatibility_mode", tls_13_middlebox_compatibility_mode()); print_bool(o, "hash_hello_random", hash_hello_random()); + if (record_size_limit().has_value()) { + o << "record_size_limit = " << record_size_limit().has_value() << '\n'; + } o << "session_ticket_lifetime = " << session_ticket_lifetime() << '\n'; o << "minimum_dh_group_size = " << minimum_dh_group_size() << '\n'; o << "minimum_ecdh_group_size = " << minimum_ecdh_group_size() << '\n'; @@ -563,6 +621,14 @@ std::vector Strict_Policy::allowed_key_exchange_methods() const } bool Strict_Policy::allow_tls12() const { return true; } +bool Strict_Policy::allow_tls13() const +{ +#if defined(BOTAN_HAS_TLS_13) + return true; +#else + return false; +#endif +} bool Strict_Policy::allow_dtls12() const { return true; } } diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index 3c5480bbf43..2aa901e0c5d 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -92,6 +92,17 @@ class BOTAN_PUBLIC_API(2,0) Policy */ virtual std::vector key_exchange_groups() const; + /** + * TLS 1.3 specific + * Return a list of groups to provide prepared key share offers in the + * initial client hello for. Groups in this list must be reflected in + * key_exchange_groups() and in the same order. By default this returns + * the most preferred group from key_exchange_groups(). + * If an empty list is returned, no prepared key share offers are sent + * and the decision of the group to use is left to the server. + */ + virtual std::vector key_exchange_groups_to_offer() const; + /** * Request that ECC curve points are sent compressed * This does not have an effect on TLS 1.3 as it always uses uncompressed ECC points. @@ -149,6 +160,11 @@ class BOTAN_PUBLIC_API(2,0) Policy */ virtual bool allow_tls12() const; + /** + * Allow TLS v1.3 + */ + virtual bool allow_tls13() const; + /** * Allow DTLS v1.2 */ @@ -258,6 +274,39 @@ class BOTAN_PUBLIC_API(2,0) Policy */ virtual bool negotiate_encrypt_then_mac() const; + /** + * TODO: This should probably be removed as it doesn't have an effect on either + * TLS 1.2 or 1.3. + * + * Indicates whether the extended master secret extension (RFC 7627) should be used. + * + * This is always enabled if the client supports TLS 1.2 (the option has no effect). + * For TLS 1.3 _only_ clients the extension is disabled by default. + * + * RFC 8446 Appendix D: + * TLS 1.2 and prior supported an "Extended Master Secret" [RFC7627] + * extension which digested large parts of the handshake transcript into + * the master secret. Because TLS 1.3 always hashes in the transcript + * up to the server Finished, implementations which support both TLS 1.3 + * and earlier versions SHOULD indicate the use of the Extended Master + * Secret extension in their APIs whenever TLS 1.3 is used. + */ + virtual bool use_extended_master_secret() const; + + /** + * Defines the maximum TLS record length for TLS connections. + * This is based on the Record Size Limit extension described in RFC 8449. + * By default (i.e. if std::nullopt is returned), TLS clients will omit + * this extension altogether. + * + * This value may be between 64 and 16385 (TLS 1.3) or 16384 (TLS 1.2). + * + * TODO: This is currently not implemented for TLS 1.2, hence the limit + * won't be negotiated by TLS 1.3 clients that support downgrading + * to TLS 1.2 (i.e. ::allow_tls12() returning true). + */ + virtual std::optional record_size_limit() const; + /** * Indicates whether certificate status messages should be supported */ @@ -314,6 +363,18 @@ class BOTAN_PUBLIC_API(2,0) Policy virtual bool allow_resumption_for_renegotiation() const; + /** + * Defines whether or not the middlebox compatibility mode should be + * used. + * + * RFC 8446 Appendix D.4 + * [This makes] the TLS 1.3 handshake resemble TLS 1.2 session resumption, + * which improves the chance of successfully connecting through middleboxes. + * + * Enabled by default. + */ + virtual bool tls_13_middlebox_compatibility_mode() const; + /** * Hash the RNG output for the client/server hello random. This is a pre-caution * to avoid writing "raw" RNG output to the wire. @@ -370,6 +431,7 @@ class BOTAN_PUBLIC_API(2,0) NSA_Suite_B_128 : public Policy size_t minimum_signature_strength() const override { return 128; } bool allow_tls12() const override { return true; } + bool allow_tls13() const override { return false; } bool allow_dtls12() const override { return false; } }; @@ -400,6 +462,7 @@ class BOTAN_PUBLIC_API(2,7) NSA_Suite_B_192 : public Policy size_t minimum_signature_strength() const override { return 192; } bool allow_tls12() const override { return true; } + bool allow_tls13() const override { return false; } bool allow_dtls12() const override { return false; } }; @@ -460,6 +523,7 @@ class BOTAN_PUBLIC_API(2,0) BSI_TR_02102_2 : public Policy size_t minimum_ecdsa_group_size() const override { return 250; } bool allow_tls12() const override { return true; } + bool allow_tls13() const override { return false; } bool allow_dtls12() const override { return false; } }; @@ -473,6 +537,7 @@ class BOTAN_PUBLIC_API(2,0) Datagram_Policy : public Policy { return std::vector({"AEAD"}); } bool allow_tls12() const override { return false; } + bool allow_tls13() const override { return false; } bool allow_dtls12() const override { return true; } }; @@ -495,6 +560,7 @@ class BOTAN_PUBLIC_API(2,0) Strict_Policy : public Policy std::vector allowed_key_exchange_methods() const override; bool allow_tls12() const override; + bool allow_tls13() const override; bool allow_dtls12() const override; }; @@ -514,10 +580,14 @@ class BOTAN_PUBLIC_API(2,0) Text_Policy : public Policy std::vector key_exchange_groups() const override; + std::vector key_exchange_groups_to_offer() const override; + bool use_ecc_point_compression() const override; bool allow_tls12() const override; + bool allow_tls13() const override; + bool allow_dtls12() const override; bool allow_insecure_renegotiation() const override; @@ -531,6 +601,10 @@ class BOTAN_PUBLIC_API(2,0) Text_Policy : public Policy bool negotiate_encrypt_then_mac() const override; + bool use_extended_master_secret() const override; + + std::optional record_size_limit() const override; + bool support_cert_status_message() const override; bool require_client_certificate_authentication() const override; @@ -557,6 +631,8 @@ class BOTAN_PUBLIC_API(2,0) Text_Policy : public Policy uint32_t session_ticket_lifetime() const override; + bool tls_13_middlebox_compatibility_mode() const override; + bool hash_hello_random() const override; std::vector srtp_profiles() const override; diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp index ce649b1a2cf..8b8b3237a1e 100644 --- a/src/lib/tls/tls_server.cpp +++ b/src/lib/tls/tls_server.cpp @@ -67,6 +67,11 @@ void Server::renegotiate(bool force_full_renegotiation) m_impl->channel().renegotiate(force_full_renegotiation); } +void Server::update_traffic_keys(bool request_peer_update) + { + m_impl->channel().update_traffic_keys(request_peer_update); + } + bool Server::secure_renegotiation_supported() const { return m_impl->channel().secure_renegotiation_supported(); diff --git a/src/lib/tls/tls_server.h b/src/lib/tls/tls_server.h index 1c566271e9c..067d5b83cb0 100644 --- a/src/lib/tls/tls_server.h +++ b/src/lib/tls/tls_server.h @@ -92,6 +92,8 @@ class BOTAN_PUBLIC_API(2,0) Server final : public Channel void renegotiate(bool force_full_renegotiation = false) override; + void update_traffic_keys(bool request_peer_update = false) override; + bool secure_renegotiation_supported() const override; void send(const uint8_t buf[], size_t buf_size) override; diff --git a/src/lib/tls/tls_suite_info.cpp b/src/lib/tls/tls_suite_info.cpp index 9f5a852572b..20d4654c837 100644 --- a/src/lib/tls/tls_suite_info.cpp +++ b/src/lib/tls/tls_suite_info.cpp @@ -2,8 +2,8 @@ * TLS cipher suite information * * This file was automatically generated from the IANA assignments -* (tls-parameters.txt sha256 6412d7a966151d409d463681e5427e706cd9066f13d34ca7a89f8cc2f7dff4b2) -* by ./src/scripts/tls_suite_info.py on 2020-11-24 +* (tls-parameters.txt sha256 ab105c99c5d0828af3742aaa6cf5a7ce8c890f8322f824ffa2abb41028a60d72) +* by tls_suite_info.py on 2021-07-29 * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -38,6 +38,11 @@ const std::vector& Ciphersuite::all_known_ciphersuites() Ciphersuite(0x00A9, "PSK_WITH_AES_256_GCM_SHA384", Auth_Method::IMPLICIT, Kex_Algo::PSK, "AES-256/GCM", 32, "AEAD", 0, KDF_Algo::SHA_384, Nonce_Format::AEAD_IMPLICIT_4), Ciphersuite(0x00AE, "PSK_WITH_AES_128_CBC_SHA256", Auth_Method::IMPLICIT, Kex_Algo::PSK, "AES-128", 16, "SHA-256", 32, KDF_Algo::SHA_256, Nonce_Format::CBC_MODE), Ciphersuite(0x00AF, "PSK_WITH_AES_256_CBC_SHA384", Auth_Method::IMPLICIT, Kex_Algo::PSK, "AES-256", 32, "SHA-384", 48, KDF_Algo::SHA_384, Nonce_Format::CBC_MODE), + Ciphersuite(0x1301, "AES_128_GCM_SHA256", Auth_Method::UNDEFINED, Kex_Algo::UNDEFINED, "AES-128/GCM", 16, "AEAD", 0, KDF_Algo::SHA_256, Nonce_Format::AEAD_IMPLICIT_4), + Ciphersuite(0x1302, "AES_256_GCM_SHA384", Auth_Method::UNDEFINED, Kex_Algo::UNDEFINED, "AES-256/GCM", 32, "AEAD", 0, KDF_Algo::SHA_384, Nonce_Format::AEAD_IMPLICIT_4), + Ciphersuite(0x1303, "CHACHA20_POLY1305_SHA256", Auth_Method::UNDEFINED, Kex_Algo::UNDEFINED, "ChaCha20Poly1305", 32, "AEAD", 0, KDF_Algo::SHA_256, Nonce_Format::AEAD_XOR_12), + Ciphersuite(0x1304, "AES_128_CCM_SHA256", Auth_Method::UNDEFINED, Kex_Algo::UNDEFINED, "AES-128/CCM", 16, "AEAD", 0, KDF_Algo::SHA_256, Nonce_Format::AEAD_IMPLICIT_4), + Ciphersuite(0x1305, "AES_128_CCM_8_SHA256", Auth_Method::UNDEFINED, Kex_Algo::UNDEFINED, "AES-128/CCM(8)", 16, "AEAD", 0, KDF_Algo::SHA_256, Nonce_Format::AEAD_IMPLICIT_4), Ciphersuite(0x16B7, "CECPQ1_RSA_WITH_CHACHA20_POLY1305_SHA256", Auth_Method::RSA, Kex_Algo::CECPQ1, "ChaCha20Poly1305", 32, "AEAD", 0, KDF_Algo::SHA_256, Nonce_Format::AEAD_XOR_12), Ciphersuite(0x16B8, "CECPQ1_ECDSA_WITH_CHACHA20_POLY1305_SHA256", Auth_Method::ECDSA, Kex_Algo::CECPQ1, "ChaCha20Poly1305", 32, "AEAD", 0, KDF_Algo::SHA_256, Nonce_Format::AEAD_XOR_12), Ciphersuite(0x16B9, "CECPQ1_RSA_WITH_AES_256_GCM_SHA384", Auth_Method::RSA, Kex_Algo::CECPQ1, "AES-256/GCM", 32, "AEAD", 0, KDF_Algo::SHA_384, Nonce_Format::AEAD_IMPLICIT_4), diff --git a/src/lib/tls/tls_text_policy.cpp b/src/lib/tls/tls_text_policy.cpp index 94fc07b0465..ce85e4c51bf 100644 --- a/src/lib/tls/tls_text_policy.cpp +++ b/src/lib/tls/tls_text_policy.cpp @@ -49,6 +49,11 @@ bool Text_Policy::allow_tls12() const return get_bool("allow_tls12", Policy::allow_tls12()); } +bool Text_Policy::allow_tls13() const + { + return get_bool("allow_tls13", Policy::allow_tls13()); + } + bool Text_Policy::allow_dtls12() const { return get_bool("allow_dtls12", Policy::allow_dtls12()); @@ -89,6 +94,20 @@ bool Text_Policy::negotiate_encrypt_then_mac() const return get_bool("negotiate_encrypt_then_mac", Policy::negotiate_encrypt_then_mac()); } +bool Text_Policy::use_extended_master_secret() const + { + return get_bool("use_extended_master_secret", Policy::use_extended_master_secret()); + } + +std::optional Text_Policy::record_size_limit() const + { + const auto limit = get_len("record_size_limit", 0); + // RFC 8449 4. + // TLS 1.3 uses a limit of 2^14+1 octets. + BOTAN_ARG_CHECK(limit <= 16385, "record size limit too large"); + return (limit > 0) ? std::make_optional(static_cast(limit)) : std::nullopt; + } + bool Text_Policy::support_cert_status_message() const { return get_bool("support_cert_status_message", Policy::support_cert_status_message()); @@ -113,6 +132,25 @@ std::vector Text_Policy::key_exchange_groups() const } +std::vector Text_Policy::key_exchange_groups_to_offer() const + { + std::string group_str = get_str("key_exchange_groups_to_offer", "notset"); + + if(group_str.empty() || group_str == "notset") + { + // policy was not set, fall back to default behaviour + return Policy::key_exchange_groups_to_offer(); + } + + if(group_str == "none") + { + return {}; + } + + return read_group_list(group_str); + } + + size_t Text_Policy::minimum_ecdh_group_size() const { return get_len("minimum_ecdh_group_size", Policy::minimum_ecdh_group_size()); @@ -178,6 +216,11 @@ std::vector Text_Policy::srtp_profiles() const return r; } +bool Text_Policy::tls_13_middlebox_compatibility_mode() const + { + return get_bool("tls_13_middlebox_compatibility_mode", Policy::tls_13_middlebox_compatibility_mode()); + } + bool Text_Policy::hash_hello_random() const { return get_bool("hash_hello_random", Policy::hash_hello_random()); diff --git a/src/lib/tls/tls_version.cpp b/src/lib/tls/tls_version.cpp index f7af6385d59..884437e1968 100644 --- a/src/lib/tls/tls_version.cpp +++ b/src/lib/tls/tls_version.cpp @@ -79,6 +79,9 @@ bool Protocol_Version::valid() const bool Protocol_Version::known_version() const { return (m_version == Protocol_Version::TLS_V12 || +#if defined(BOTAN_HAS_TLS_13) + m_version == Protocol_Version::TLS_V13 || +#endif m_version == Protocol_Version::DTLS_V12); } diff --git a/src/lib/tls/tls_version.h b/src/lib/tls/tls_version.h index b9eab6e059b..5ab4a31cf5e 100644 --- a/src/lib/tls/tls_version.h +++ b/src/lib/tls/tls_version.h @@ -35,7 +35,11 @@ class BOTAN_PUBLIC_API(2,0) Protocol_Version final */ static Protocol_Version latest_tls_version() { +#if defined(BOTAN_HAS_TLS_13) + return Protocol_Version(TLS_V13); +#else return Protocol_Version(TLS_V12); +#endif } /** diff --git a/src/scripts/ci/setup_gh_actions.sh b/src/scripts/ci/setup_gh_actions.sh index 8b4f016834e..7b8135b24cf 100755 --- a/src/scripts/ci/setup_gh_actions.sh +++ b/src/scripts/ci/setup_gh_actions.sh @@ -53,7 +53,8 @@ if type -p "apt-get"; then pip install --user codecov echo "$HOME/.local/bin" >> "$GITHUB_PATH" - git clone --depth 1 --branch jack/runner-20210401 https://github.com/randombit/boringssl.git + # TODO: merge changes to Botan's boring fork + git clone --depth 1 --branch rene/runner-20220322 https://github.com/reneme/boringssl.git sudo chgrp -R "$(id -g)" /var/lib/softhsm/ /etc/softhsm sudo chmod g+w /var/lib/softhsm/tokens diff --git a/src/scripts/ci_build.py b/src/scripts/ci_build.py index 9d7ee78631c..c09303852f1 100755 --- a/src/scripts/ci_build.py +++ b/src/scripts/ci_build.py @@ -134,7 +134,7 @@ def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, if target in ['bsi', 'nist']: # tls is optional for bsi/nist but add it so verify tests work with these minimized configs - flags += ['--module-policy=%s' % (target), '--enable-modules=tls'] + flags += ['--module-policy=%s' % (target), '--enable-modules=tls12'] if target == 'docs': flags += ['--with-doxygen', '--with-sphinx', '--with-rst2man'] diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py index 25001664b5f..e71fbb6a54f 100755 --- a/src/scripts/test_cli.py +++ b/src/scripts/test_cli.py @@ -887,7 +887,8 @@ def cli_tls_socket_tests(tmp_dir): time.sleep(wait_time) tls_client = subprocess.Popen([CLI_PATH, 'tls_client', 'localhost', - '--port=%d' % (server_port), '--trusted-cas=%s' % (ca_cert)], + '--port=%d' % (server_port), '--trusted-cas=%s' % (ca_cert), + '--tls-version=1.2'], # TODO: test TLS 1.3 once it becomes available stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(wait_time) diff --git a/src/scripts/tls_suite_info.py b/src/scripts/tls_suite_info.py index d655bbf4023..6420f4fd969 100755 --- a/src/scripts/tls_suite_info.py +++ b/src/scripts/tls_suite_info.py @@ -4,6 +4,7 @@ Used to generate lib/tls/tls_suite_info.cpp from IANA params (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017 Jack Lloyd +(C) 2021 Elektrobit Automotive GmbH Botan is released under the Simplified BSD License (see license.txt) """ @@ -16,9 +17,22 @@ def to_ciphersuite_info(code, name): - (sig_and_kex,cipher_and_mac) = name.split('_WITH_') + sig_and_kex = '' + cipher_and_mac = '' - if sig_and_kex == 'RSA': + with_substr = '_WITH_' + if with_substr in name: + # TLS 1.2 or earlier cipher suites + (sig_and_kex,cipher_and_mac) = name.split(with_substr) + else: + # TLS 1.3 cipher suites, no sig_and_kex + cipher_and_mac = name + + if sig_and_kex == '': + # UNDEFINED means that the information is not coded in the cipher suite + sig_algo = 'UNDEFINED' + kex_algo = 'UNDEFINED' + elif sig_and_kex == 'RSA': sig_algo = 'IMPLICIT' kex_algo = 'RSA' elif 'PSK' in sig_and_kex: @@ -67,6 +81,7 @@ def to_ciphersuite_info(code, name): } tls_to_botan_names = { + 'UNDEFINED': 'UNDEFINED', 'IMPLICIT': 'IMPLICIT', 'anon': 'ANONYMOUS', @@ -200,7 +215,9 @@ def main(args = None): removed_algos = ['SEED', 'CAMELLIA_128_CBC', 'CAMELLIA_256_CBC'] protocol_goop = ['SCSV', 'KRB5'] maybe_someday = ['RSA_PSK', 'ECCPWD'] - not_supported = weak_crypto + static_dh + protocol_goop + maybe_someday + removed_algos + macciphersuites = ['SHA256_SHA256', 'SHA384_SHA384'] + shang_mi = ['SM4_GCM_SM3', 'SM4_CCM_SM3'] # RFC8998 + not_supported = weak_crypto + static_dh + protocol_goop + maybe_someday + removed_algos + macciphersuites + shang_mi (options, args) = process_command_line(args) @@ -227,7 +244,7 @@ def main(args = None): if ns in name: should_use = False - if should_use and name.find('_WITH_') > 0: + if should_use:# and name.find('_WITH_') > 0: info = to_ciphersuite_info(code, name) if info is not None: suites[code] = info diff --git a/src/tests/data/tls-policy/bsi.txt b/src/tests/data/tls-policy/bsi.txt index e6ec84cd7e4..68b3cf3422b 100644 --- a/src/tests/data/tls-policy/bsi.txt +++ b/src/tests/data/tls-policy/bsi.txt @@ -1,6 +1,7 @@ allow_tls10 = false allow_tls11 = false allow_tls12 = true +allow_tls13 = false allow_dtls10 = false allow_dtls12 = false diff --git a/src/tests/data/tls-policy/compat.txt b/src/tests/data/tls-policy/compat.txt index 4de9a60a6fa..b79d69720f3 100644 --- a/src/tests/data/tls-policy/compat.txt +++ b/src/tests/data/tls-policy/compat.txt @@ -8,6 +8,7 @@ allow_tls10 = true allow_tls11 = true allow_tls12 = true +allow_tls13 = false allow_dtls10 = false allow_dtls12 = false ciphers = ChaCha20Poly1305 AES-256/GCM AES-128/GCM AES-256 AES-128 3DES diff --git a/src/tests/data/tls-policy/datagram.txt b/src/tests/data/tls-policy/datagram.txt index 8c787490db1..6f1802f52bc 100644 --- a/src/tests/data/tls-policy/datagram.txt +++ b/src/tests/data/tls-policy/datagram.txt @@ -1,6 +1,7 @@ allow_tls10 = false allow_tls11 = false allow_tls12 = false +allow_tls13 = false allow_dtls10 = false allow_dtls12 = true ciphers = ChaCha20Poly1305 AES-256/GCM AES-128/GCM diff --git a/src/tests/data/tls-policy/default.txt b/src/tests/data/tls-policy/default.txt index 4ff6c293d27..08ea86a78b5 100644 --- a/src/tests/data/tls-policy/default.txt +++ b/src/tests/data/tls-policy/default.txt @@ -1,6 +1,7 @@ allow_tls10 = false allow_tls11 = false allow_tls12 = true +allow_tls13 = false allow_dtls10 = false allow_dtls12 = true ciphers = ChaCha20Poly1305 AES-256/GCM AES-128/GCM diff --git a/src/tests/data/tls-policy/default_tls13.txt b/src/tests/data/tls-policy/default_tls13.txt new file mode 100644 index 00000000000..cf4b61bf0bd --- /dev/null +++ b/src/tests/data/tls-policy/default_tls13.txt @@ -0,0 +1,23 @@ +allow_tls10 = false +allow_tls11 = false +allow_tls12 = true +allow_tls13 = true +allow_dtls10 = false +allow_dtls12 = true +ciphers = ChaCha20Poly1305 AES-256/GCM AES-128/GCM +macs = AEAD SHA-256 SHA-384 SHA-1 +signature_hashes = SHA-512 SHA-384 SHA-256 +signature_methods = ECDSA RSA +key_exchange_methods = CECPQ1 ECDH DH +key_exchange_groups = x25519 secp256r1 brainpool256r1 secp384r1 brainpool384r1 secp521r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 +allow_insecure_renegotiation = false +include_time_in_hello_random = true +allow_server_initiated_renegotiation = false +hide_unknown_users = false +server_uses_own_ciphersuite_preferences = true +negotiate_encrypt_then_mac = true +session_ticket_lifetime = 86400 +minimum_dh_group_size = 2048 +minimum_ecdh_group_size = 255 +minimum_rsa_bits = 2048 +minimum_signature_strength = 110 diff --git a/src/tests/data/tls-policy/rfc8448_1rtt.txt b/src/tests/data/tls-policy/rfc8448_1rtt.txt new file mode 100644 index 00000000000..1019034df51 --- /dev/null +++ b/src/tests/data/tls-policy/rfc8448_1rtt.txt @@ -0,0 +1,27 @@ +allow_tls10 = false +allow_tls11 = false +allow_tls12 = false +allow_tls13 = true +allow_dtls10 = false +allow_dtls12 = false +ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM +macs = AEAD +signature_hashes = SHA-512 SHA-384 SHA-256 +signature_methods = ECDSA RSA +key_exchange_groups = x25519 secp256r1 secp384r1 secp521r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 +key_exchange_groups_to_offer = x25519 +allow_insecure_renegotiation = false +include_time_in_hello_random = false +allow_server_initiated_renegotiation = false +hide_unknown_users = false +server_uses_own_ciphersuite_preferences = true +negotiate_encrypt_then_mac = true +session_ticket_lifetime = 86400 +minimum_dh_group_size = 2048 +minimum_ecdh_group_size = 255 +minimum_rsa_bits = 1024 +minimum_signature_strength = 110 +record_size_limit = 16385 +tls_13_middlebox_compatibility_mode = false +hash_hello_random = false +support_cert_status_message = false diff --git a/src/tests/data/tls-policy/rfc8448_compat.txt b/src/tests/data/tls-policy/rfc8448_compat.txt new file mode 100644 index 00000000000..1cf11bfea69 --- /dev/null +++ b/src/tests/data/tls-policy/rfc8448_compat.txt @@ -0,0 +1,27 @@ +allow_tls10 = false +allow_tls11 = false +allow_tls12 = false +allow_tls13 = true +allow_dtls10 = false +allow_dtls12 = false +ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM +macs = AEAD +signature_hashes = SHA-512 SHA-384 SHA-256 +signature_methods = ECDSA RSA +key_exchange_groups = x25519 secp256r1 secp384r1 secp521r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 +key_exchange_groups_to_offer = x25519 +allow_insecure_renegotiation = false +include_time_in_hello_random = false +allow_server_initiated_renegotiation = false +hide_unknown_users = false +server_uses_own_ciphersuite_preferences = true +negotiate_encrypt_then_mac = true +session_ticket_lifetime = 86400 +minimum_dh_group_size = 2048 +minimum_ecdh_group_size = 255 +minimum_rsa_bits = 1024 +minimum_signature_strength = 110 +record_size_limit = 16385 +tls_13_middlebox_compatibility_mode = true +hash_hello_random = false +support_cert_status_message = false diff --git a/src/tests/data/tls-policy/rfc8448_hrr.txt b/src/tests/data/tls-policy/rfc8448_hrr.txt new file mode 100644 index 00000000000..d82118f73aa --- /dev/null +++ b/src/tests/data/tls-policy/rfc8448_hrr.txt @@ -0,0 +1,27 @@ +allow_tls10 = false +allow_tls11 = false +allow_tls12 = false +allow_tls13 = true +allow_dtls10 = false +allow_dtls12 = false +ciphers = AES-128/GCM ChaCha20Poly1305 AES-256/GCM +macs = AEAD +signature_hashes = SHA-512 SHA-384 SHA-256 +signature_methods = ECDSA RSA +key_exchange_groups = x25519 secp256r1 secp384r1 +key_exchange_groups_to_offer = x25519 +allow_insecure_renegotiation = false +include_time_in_hello_random = false +allow_server_initiated_renegotiation = false +hide_unknown_users = false +server_uses_own_ciphersuite_preferences = true +negotiate_encrypt_then_mac = true +session_ticket_lifetime = 86400 +minimum_dh_group_size = 2048 +minimum_ecdh_group_size = 255 +minimum_rsa_bits = 1024 +minimum_signature_strength = 110 +record_size_limit = 16385 +tls_13_middlebox_compatibility_mode = false +hash_hello_random = false +support_cert_status_message = false diff --git a/src/tests/data/tls-policy/strict.txt b/src/tests/data/tls-policy/strict.txt index a79f175f5b4..6e978d17848 100644 --- a/src/tests/data/tls-policy/strict.txt +++ b/src/tests/data/tls-policy/strict.txt @@ -1,6 +1,7 @@ allow_tls10 = false allow_tls11 = false allow_tls12 = true +allow_tls13 = false allow_dtls10 = false allow_dtls12 = true ciphers = ChaCha20Poly1305 AES-256/GCM AES-128/GCM diff --git a/src/tests/data/tls-policy/strict_tls13.txt b/src/tests/data/tls-policy/strict_tls13.txt new file mode 100644 index 00000000000..70912c036e1 --- /dev/null +++ b/src/tests/data/tls-policy/strict_tls13.txt @@ -0,0 +1,23 @@ +allow_tls10 = false +allow_tls11 = false +allow_tls12 = true +allow_tls13 = true +allow_dtls10 = false +allow_dtls12 = true +ciphers = ChaCha20Poly1305 AES-256/GCM AES-128/GCM +macs = AEAD +signature_hashes = SHA-512 SHA-384 +signature_methods = ECDSA RSA +key_exchange_methods = CECPQ1 ECDH +key_exchange_groups = x25519 secp256r1 brainpool256r1 secp384r1 brainpool384r1 secp521r1 brainpool512r1 ffdhe/ietf/2048 ffdhe/ietf/3072 ffdhe/ietf/4096 ffdhe/ietf/6144 ffdhe/ietf/8192 +allow_insecure_renegotiation = false +include_time_in_hello_random = true +allow_server_initiated_renegotiation = false +hide_unknown_users = false +server_uses_own_ciphersuite_preferences = true +negotiate_encrypt_then_mac = true +session_ticket_lifetime = 86400 +minimum_dh_group_size = 2048 +minimum_ecdh_group_size = 255 +minimum_rsa_bits = 2048 +minimum_signature_strength = 110 diff --git a/src/tests/data/tls-policy/suiteb_128.txt b/src/tests/data/tls-policy/suiteb_128.txt index 90ef68f4a8d..cabbbe48b10 100644 --- a/src/tests/data/tls-policy/suiteb_128.txt +++ b/src/tests/data/tls-policy/suiteb_128.txt @@ -1,6 +1,7 @@ allow_tls10 = false allow_tls11 = false allow_tls12 = true +allow_tls13 = false allow_dtls10 = false allow_dtls12 = false ciphers = AES-128/GCM diff --git a/src/tests/data/tls-policy/suiteb_192.txt b/src/tests/data/tls-policy/suiteb_192.txt index 5d80e64811c..740a957e3d2 100644 --- a/src/tests/data/tls-policy/suiteb_192.txt +++ b/src/tests/data/tls-policy/suiteb_192.txt @@ -1,6 +1,7 @@ allow_tls10 = false allow_tls11 = false allow_tls12 = true +allow_tls13 = false allow_dtls10 = false allow_dtls12 = false ciphers = AES-256/GCM diff --git a/src/tests/data/tls/client_hello.vec b/src/tests/data/tls/client_hello.vec index 40d883edee8..3853223c872 100644 --- a/src/tests/data/tls/client_hello.vec +++ b/src/tests/data/tls/client_hello.vec @@ -1,4 +1,4 @@ -# Tests generated partially with openssl 1.0.2g/1.1.0a and TLS-Attacker +# Tests generated partially with openssl 1.0.2g/1.1.0a, TLS-Attacker and taken from RFC 8448 (for TLS 1.3) # ClientHello message contains many fields, the following fields are checked: # - Protocol Version # - Extensions @@ -8,22 +8,28 @@ Buffer = 030320f3dc33f90be6509e6133a1819f2b80fe6ccc6268d9195ca4ead7504ffe7e2a0000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff01000000 Protocol = 0303 AdditionalData = FF01 -Exception = +Exception = # with extensions: point formats, ec curves, session ticket, signature algorithms, heartbeat (point formats and heartbeat not supported, empty renegotiation generated) Buffer = 0303871e18983024eaee1be8ae6607d5ecad941d33fd7fc1d8554a9e1fbfda8d30880000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff01000055000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101 Protocol = 0303 AdditionalData = 000A000B000D000F0023FF01 -Exception = +Exception = # with extensions: point formats, ec curves, session ticket, signature algorithms, heartbeat, Encrypt-then-MAC, Extended Master Secret (point formats and heartbeat not supported, empty renegotiation generated) Buffer = 0303e00da23523058b5dc9c445d97b2bb6315b019e97838ac4f16c23b2cb031b6a490000e2c0afc0adc030c02cc028c024c014c00ac0a3c09f00a500a300a1009f006b006a006900680039003800370036cca9cca8c077c073ccaa00c400c300c200c10088008700860085c032c02ec02ac026c00fc005c079c075c0a1c09d009d003d003500c00084c0aec0acc02fc02bc027c023c013c009c0a2c09e00a400a200a0009e00670040003f003e0033003200310030c076c07200be00bd00bc00bb009a0099009800970045004400430042c031c02dc029c025c00ec004c078c074c0a0c09c009c003c002f00ba009600410007c012c008001600130010000dc00dc003000a00ff0100005f000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d00220020060106020603050105020503040104020403030103020303020102020203eded000f0001010016000000170000 Protocol = 0303 AdditionalData = 000A000B000D000F001600170023FF01 -Exception = +Exception = + +# basic TLS 1.3 client hello from RFC 8448 +Buffer = 0303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010000910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001 +Protocol = 0303 +AdditionalData = 0000000A000D001C0023002B002D0033FF01 +Exception = # empty -Buffer = +Buffer = Protocol = 0303 Exception = Client_Hello: Packet corrupted @@ -34,7 +40,7 @@ Exception = Client_Hello: Packet corrupted # Invalid cipher suite length (0xf0e2 instead of 0x00e2) Buffer = 0303e00da23523058b5dc9c445d97b2bb6315b019e97838ac4f16c23b2cb031b6a4900f0e2c0afc0adc030c02cc028c024c014c00ac0a3c09f00a500a300a1009f006b006a006900680039003800370036cca9cca8c077c073ccaa00c400c300c200c10088008700860085c032c02ec02ac026c00fc005c079c075c0a1c09d009d003d003500c00084c0aec0acc02fc02bc027c023c013c009c0a2c09e00a400a200a0009e00670040003f003e0033003200310030c076c07200be00bd00bc00bb009a0099009800970045004400430042c031c02dc029c025c00ec004c078c074c0a0c09c009c003c002f00ba009600410007c012c008001600130010000dc00dc003000a00ff01000000 Protocol = 0303 -AdditionalData = +AdditionalData = Exception = Invalid ClientHello: Expected 61666 bytes remaining, only 230 left #invalid extensions length diff --git a/src/tests/data/tls_13/server_hello.vec b/src/tests/data/tls_13/server_hello.vec new file mode 100644 index 00000000000..fcafcb272fe --- /dev/null +++ b/src/tests/data/tls_13/server_hello.vec @@ -0,0 +1,72 @@ +# Tests generated partially with openssl 1.0.2g or taken from RFC 8448 +# ServerHello message contains many fields, the following fields are checked: +# - Protocol Version +# - Message Type +# - Cipher suite +# - Extensions + +[server_hello] +# correct, with session ticket and renegotiation info +Buffer = 0303ffea0bcfba564a4ce177c6a444b0ebdff5629b277293c618c1125f231e8628dd00c030000016ff01000100000b00040300010200230000000f000101 +Protocol = 0303 +Message_Type = server_hello_12 +Ciphersuite = C030 +AdditionalData = 000B000F0023FF01 +Exception = + +# correct, with session ticket, extended master secret, and renegotiation info +Buffer = 03019f9cafa88664d9095f85dd64a39e5dd5c09f5a4a5362938af3718ee4e818af6a00c03000001aff01000100000b00040300010200230000000f00010100170000 +Protocol = 0301 +Message_Type = server_hello_12 +Ciphersuite = C030 +AdditionalData = 000B000F00170023FF01 +Exception = + +# correct, TLS 1.3 (from RFC 8448) +Buffer = 0303a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155772ed3e2692800130100002e00330024001d0020c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f002b00020304 +Protocol = 0304 +Message_Type = server_hello_13 +Ciphersuite = 1301 +AdditionalData = 002B0033 +Exception = + +# correct, TLS 1.3 Hello Retry Request (from RFC 8448) +Buffer = 0303cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c001301000084003300020017002c0074007271dcd04bb88bc3189119398a00000000eefafc76c146b823b096f8aacad365dd0030953f4edf625636e5f21bb2e23fcc654b1b5b40318d10d137abcbb87574e36e8a1f025f7dfa5d6e50781b5eda4aa15b0c8be778257d16aa3030e9e7841dd9e4c0342267e8ca0caf571fb2b7cff0f934b0002b00020304 +Protocol = 0304 +Message_Type = hello_retry_request +Ciphersuite = 1301 +AdditionalData = 002B002C0033 +Exception = + + +# incorrect, corrupted +Buffer = +Protocol = 0303 +Message_Type = fail +Ciphersuite = C030 +AdditionalData = +Exception = Server_Hello: Packet corrupted + +# incorrect, corrupted +Buffer = 00 +Protocol = 0303 +Message_Type = fail +Ciphersuite = C030 +AdditionalData = +Exception = Server_Hello: Packet corrupted + +# invalid extensions length +Buffer = 03039f9cafa88664d9095f85dd64a39e5dd5c09f5a4a5362938af3718ee4e818af6a00c03000001cff01000100000b00040300010200230000000f00010100170000 +Protocol = 0303 +Message_Type = fail +Ciphersuite = C030 +AdditionalData = 00170023FF01 +Exception = Bad extension size + +# invalid extension length +Buffer = 03039f9cafa88664d9095f85dd64a39e5dd5c09f5a4a5362938af3718ee4e818af6a00c03000001aff01000100000b00040300010200230100000f00010100170000 +Protocol = 0303 +Message_Type = fail +Ciphersuite = C030 +AdditionalData = 00170023FF01 +Exception = Invalid ServerHello: Expected 256 bytes remaining, only 9 left diff --git a/src/tests/data/tls_extensions/generation/key_share_CH_offers.vec b/src/tests/data/tls_extensions/generation/key_share_CH_offers.vec new file mode 100644 index 00000000000..a5adf6ee8ca --- /dev/null +++ b/src/tests/data/tls_extensions/generation/key_share_CH_offers.vec @@ -0,0 +1,81 @@ +# KeyShareClientHello (variant of KeyShare extension) consists of: +# - Client Key Share Length (2 bytes) +# - vector of KeyShareEntry: +# - Group (2 bytes) +# - Key Exchange Length (2 bytes) +# - Key Exchange (vector of bytes[Key Exchange Length]) +# +# Groups - The list of groups to be supported +# Offered_Groups (opt) - The list of groups to be offered in key exchange +# Rng_Data - Pool of random number generator output (for key generation) +# Expected_Content - The expected serialized output of the Key_Share extension +# +# Note: secp256r1 should always come last! If it finds an RNG that is still seeded after key generation, +# it will opportunistically pull additional data for some blinding mechanism. That would otherwise +# screw up the test case. + +[key_share_CH_offers] + +# INDIVIDUAL GROUPS (supported and offered) + +Groups = x25519 +Rng_Data = 49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +Expected_Content = 0024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c + +Groups = secp256r1 +Rng_Data = 49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +Expected_Content = 0045001700410486E8631CECD233F133F6FC99156D8BB504DB91DEC753C31AEA8AEC3C874221653C986F7B1D00FD4EBFD3F48BCC2CDE3E9C94442B4F53BF2F906B3ECEE6EA12F0 + +Groups = ffdhe/ietf/2048 +Rng_Data = 49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +Expected_Content = 010401000100534C002FD3A1C9B25B664DC8CCAEB34857CABDA5BDF1EB5B99EEB8FF689EC6761746B54AA35B3AEECDA7708E0C4B046EBE6E275B5C4E1C02351DA5F432AEEF93DF3E3727CEE4868041A1CF5E35DF73750AA62D9B91F4785A2F7DC4D5304BFFB339B1193BDE6D0EE6F7698BD4C2871192A209ED34594B2A46925F064FA25CC56B858A05C205171DD7C7119FB8D27AAEC0CFE301F2E7F3AC7B4EDA614164F05E5AF88DAE6F07DA0455EFF704A83E496E86625CBADBA8DA9AC22EE9337AC891AC2F9F46A73BB3CDFF21DC9C2F3B120ED792E9C12BFC08E27854FD5F657B8E9EFC65549F82FF5F64C718A6829026F1D027F24F7296BD22038230EBB2F629B6885267 + +# this test data has four \0 bytes at the start of the 'public value' +# RFC 8446 Ch. 4.2.8.1: +# ... encoded as a big-endian integer and padded to +# the left with zeros to the size of p in bytes +Groups = ffdhe/ietf/2048 +Rng_Data = 317FEC44E299183D1A17F3F699E036620852EE1FA2C3B3E549900779B9CDC204 +Expected_Content = 010401000100000046016B7B5EB1A64DE87235279C07D3C47686454A9D6089D460FB6C0DD2F3DB7D2EF252A00DD6F1D432B4BB63FD757C2A9DBBE6C497C78A7C765C3F49B711D9E1A58199AB5DE61C963AA522DB8313DD39115BCF207485EADB816CCC08070CB8B200C25D8ECC0BD36ADBE0AAF278A0CCFE48A9BA7098B53AF2C3EC55147CE7114FAF1084A21743A3DB29E05B5874CAE917D7E5449478A37066B0D2A9F00E29777962CC7C3C3CE15F8C3DB118F69628FF71565E242B476407F55DD0B0DD9003134992E24ED52B45DEC5C4AD98300B4C0767A78C4612C1B3D1430060E56280942FA77407B282CC21349030DFD654EAFB76B0B63FAAA1814D0C20248AE13B0D1E + +# OFFER LESS GROUPS THAN SUPPORTED + +Groups = secp256r1 x25519 +Offered_Groups = secp256r1 +Rng_Data = 49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +Expected_Content = 0045001700410486E8631CECD233F133F6FC99156D8BB504DB91DEC753C31AEA8AEC3C874221653C986F7B1D00FD4EBFD3F48BCC2CDE3E9C94442B4F53BF2F906B3ECEE6EA12F0 + +Groups = secp256r1 x25519 +Offered_Groups = x25519 +Rng_Data = 49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +Expected_Content = 0024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c + +# OFFER MULTIPLE GROUPS (implicitly supported) + +Groups = x25519 secp256r1 +Rng_Data = 49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea500549af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +Expected_Content = 0069001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c001700410486E8631CECD233F133F6FC99156D8BB504DB91DEC753C31AEA8AEC3C874221653C986F7B1D00FD4EBFD3F48BCC2CDE3E9C94442B4F53BF2F906B3ECEE6EA12F0 + +Groups = x25519 ffdhe/ietf/2048 secp256r1 +Rng_Data = 49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea500549af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea500549af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +Expected_Content = 016d001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c01000100534C002FD3A1C9B25B664DC8CCAEB34857CABDA5BDF1EB5B99EEB8FF689EC6761746B54AA35B3AEECDA7708E0C4B046EBE6E275B5C4E1C02351DA5F432AEEF93DF3E3727CEE4868041A1CF5E35DF73750AA62D9B91F4785A2F7DC4D5304BFFB339B1193BDE6D0EE6F7698BD4C2871192A209ED34594B2A46925F064FA25CC56B858A05C205171DD7C7119FB8D27AAEC0CFE301F2E7F3AC7B4EDA614164F05E5AF88DAE6F07DA0455EFF704A83E496E86625CBADBA8DA9AC22EE9337AC891AC2F9F46A73BB3CDFF21DC9C2F3B120ED792E9C12BFC08E27854FD5F657B8E9EFC65549F82FF5F64C718A6829026F1D027F24F7296BD22038230EBB2F629B6885267001700410486E8631CECD233F133F6FC99156D8BB504DB91DEC753C31AEA8AEC3C874221653C986F7B1D00FD4EBFD3F48BCC2CDE3E9C94442B4F53BF2F906B3ECEE6EA12F0 + +Groups = x25519 ffdhe/ietf/2048 secp256r1 +Offered_Groups = x25519 secp256r1 ffdhe/ietf/2048 +Rng_Data = 49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea500549af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea500549af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +Expected_Content = 016d001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c01000100534C002FD3A1C9B25B664DC8CCAEB34857CABDA5BDF1EB5B99EEB8FF689EC6761746B54AA35B3AEECDA7708E0C4B046EBE6E275B5C4E1C02351DA5F432AEEF93DF3E3727CEE4868041A1CF5E35DF73750AA62D9B91F4785A2F7DC4D5304BFFB339B1193BDE6D0EE6F7698BD4C2871192A209ED34594B2A46925F064FA25CC56B858A05C205171DD7C7119FB8D27AAEC0CFE301F2E7F3AC7B4EDA614164F05E5AF88DAE6F07DA0455EFF704A83E496E86625CBADBA8DA9AC22EE9337AC891AC2F9F46A73BB3CDFF21DC9C2F3B120ED792E9C12BFC08E27854FD5F657B8E9EFC65549F82FF5F64C718A6829026F1D027F24F7296BD22038230EBB2F629B6885267001700410486E8631CECD233F133F6FC99156D8BB504DB91DEC753C31AEA8AEC3C874221653C986F7B1D00FD4EBFD3F48BCC2CDE3E9C94442B4F53BF2F906B3ECEE6EA12F0 + +# OFFER GROUPS THAT ARE NOT SUPPORTED +# expected: unsupported groups are silently ignored + +Groups = secp256r1 x25519 +Offered_Groups = x25519 secp384r1 +Rng_Data = 49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +Expected_Content = 0024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c + +# MAKE NO OFFERS + +Groups = x25519 secp256r1 +Offered_Groups = none +Rng_Data = +Expected_Content = 0000 diff --git a/src/tests/data/tls_extensions/parsing/cookie.vec b/src/tests/data/tls_extensions/parsing/cookie.vec new file mode 100644 index 00000000000..0c89ee4b2b2 --- /dev/null +++ b/src/tests/data/tls_extensions/parsing/cookie.vec @@ -0,0 +1,18 @@ +# Cookie extension consists of: +# - Cookie length (2 bytes) +# - Cookie content (vector of bytes[Cookie length]) + +[cookie] +Buffer = 00020304 +Expected_Content = 0304 +Exception = + +Buffer = 000401020304 +Expected_Content = 01020304 +Exception = + +Buffer = 00000304 +Exception = Cookie length must be bigger than 0 + +Buffer = 000203 +Exception = Not enough bytes in the buffer to decode Cookie \ No newline at end of file diff --git a/src/tests/data/tls_extensions/parsing/key_share_CH.vec b/src/tests/data/tls_extensions/parsing/key_share_CH.vec new file mode 100644 index 00000000000..7c5b60dcb03 --- /dev/null +++ b/src/tests/data/tls_extensions/parsing/key_share_CH.vec @@ -0,0 +1,21 @@ +# KeyShareClientHello (variant of KeyShare extension) consists of: +# - Client Key Share Length (2 bytes) +# - vector of KeyShareEntry: +# - Group (2 bytes) +# - Key Exchange Length (2 bytes) +# - Key Exchange (vector of bytes[Key Exchange Length]) + +[key_share_CH] +# correct extension content, one KeyShareEntry +Buffer = 0024001d00203d5d78dc1ec22555c34347869078ba092d75d93093f38a58d419595d171d0a3b +Expected_Content = 0024001d00203d5d78dc1ec22555c34347869078ba092d75d93093f38a58d419595d171d0a3b +Exception = + +# correct extension content, many KeyShareEntry objects +Buffer = 05ed413804929e4068e7259337c3811d3f1a75175ca5dfe023b91dfaf777a2e2b231ae52a371a970e0a0764a6ad3dd19de065cc6175dbdd9a2dc784a157b056fe083b51071350af24dab02aa57c00d21b4dd89cc2bac8dcd0ec36e896e5317967301ed529dcd851e00e3d30d53dde7b2b977a8c8061d614a6b518807e35636f5e63cbe6888027d1cffa24762ef52f96adb937436c87d3ae5b354e67d0b74f0c1a2329094f4019034f5f758eeb6c2f63bef8f4bdabe2308bb64a9cb22e5edeb71369eb70c1583b785d9ce4c4cdfd353e7e7c3d18c84f9a569b95d7f0ba1931a769253874b4c3978b029f7b0718d587b87f656f4993d8b7f6d74b0a23d6cbdad83d0056f7e989e0129157362433c1266245626bddcf03a6f8a1c0e30dc6b32e024c47dbc4f055909c262b736e2dea14d573d8de3f67a8e48a05211a937812a3943305cf0cbddcbe14cbf8a70fa49c35b476816c81354fdd64e5429a2ede7191e71fea92d0aec9a2aff1452facb29d8bc7d8c1b207398b7f83d7d6d1007bbe6c70d20d91ea2ab372d6412ce4318000e4e8fbc6ca7b5a3555ff0a63ab77d55fe451bbf803299b6cd7272813530c6add3d4cbbddf2b82f9d90119d8897b1c5fcedfe50875d2bc4ef7eabee6601627672d51d5712ff48550c45b334c5a4b9139f9bdaea8b03b7571262d0aa3fdfee96d27741639d90d1916b22b15c23535f607d58c796660fdc3630e33fd4ff6c894832b894fb5112ab0ffbe6ff39e0e3d4b6bdeb64a1ee32aec842251abc49ea623040b0891335a71f1dd1d9e0c6637220ec8cb70c3ea1056a9d4a8017e76af138e565beecf6c16b09631012f012ab78fa6472df9cde281810f7f7450fb761edf881b92a39a3019ee3d3a4c8ef700fa9337136c7e00d17fece18655a21115fa97144459a3a01b9703ae664a7b958fb7d67096973bea6f293060d3bfd54c8aeaeb038bae6810d42ee70a0ddf523fd7f2efbec2a0aab44ec66c1ceb55b537399b51cf17f46978ae5a81853942ef113623e8e8d38a7218c270db8d282f31a0412c2ad44279e649e4dd8bd3f3b93b37ed50e58980e7591b21a80605bac1631eaf2aad74edb236c0c6c195b2ded22ce11b4fa86afe1b8a46aab61ba9b0f7e74fd40dee1f61eab0bc313e53a1af1b63125146ae926eb9f5de9504a24ba941517008cea6655a0ba72ba543cf53bc7a225de9a8411fb1fe4dd2b7f57b36fd701fd6b4325d6aceaebcbc52e13725561255a5bcc0409460afaa6a0474f26647d886899a14d50fbebfa84e3017dc4194719869849b16b7aae368c3b78f9d8572a67fa822c3afa8339626efa285af7b811f06f0def09d07af29597cd0422353411d31fa45a963b89d6eabcf7727c8bf8723474ca1d7a6f067768e98a5494cf2c28ee4adb92e258b76483b8fd77d0da98eb00d79aab599cf8caaaf56d7c5bbb0e3284fa3b3b48ab8d5555f3bfd5faaf192e63b60458bb91b7e3aab636012080d5e4ce7ebee3c4dab20f2c5af143ffe88b8d7f44e82077a8280588aa991bad39779276e76448b545f1a7373be7ef9bb5ee3b8a2a8f22d591a4f6e2dd00e264b93a74c9514e2477799395338cb5e032fe8eeaa13ca119da6d95ce93db954f7440bf68bccc53910dea753976c6d3d24656fdbca9f02001d0020e1204d17000fef7312b84ed0297f34bc6881732c8945f83a7a1abcf0ac04e423001700410430cc12bcb2f1c20c357af1be37b15e75d1f1203671cd7f77d167f8416fbba1389389a5ae57495d09d0f1ce74e50ee5059df1e6abf3313fb720f2178af511be300018006104d0e3f9daf59fa5f02212ba3893a09f6617f40b916e0390ee83398ab0367555aab3d3001ff9dcca61f4861b04e3cd0ec763996ff274d9d56177fe8eee50791ef37279f5ed7305597c829c2b189f894804d2151eeea40acbf17ab47f3ef61628870019008504016a19509758d2bac50339a747d528f8780671c71fa31186d07d770e5aeb6f4497156c3c801e25bb5967996df01f967d0415b8e6719fc2b56ed8db99a6c165dc0ea6003b4b7b89bc2ddeaa98c79133b96f64ccc34d99c6a313a7671ac72bf0e56942eefa60008a2c769f3e6bcb0ffcd1319ad6ba2ff87ef1420ebdc44c3e0b30b43f9b6c +Expected_Content = 05ed413804929e4068e7259337c3811d3f1a75175ca5dfe023b91dfaf777a2e2b231ae52a371a970e0a0764a6ad3dd19de065cc6175dbdd9a2dc784a157b056fe083b51071350af24dab02aa57c00d21b4dd89cc2bac8dcd0ec36e896e5317967301ed529dcd851e00e3d30d53dde7b2b977a8c8061d614a6b518807e35636f5e63cbe6888027d1cffa24762ef52f96adb937436c87d3ae5b354e67d0b74f0c1a2329094f4019034f5f758eeb6c2f63bef8f4bdabe2308bb64a9cb22e5edeb71369eb70c1583b785d9ce4c4cdfd353e7e7c3d18c84f9a569b95d7f0ba1931a769253874b4c3978b029f7b0718d587b87f656f4993d8b7f6d74b0a23d6cbdad83d0056f7e989e0129157362433c1266245626bddcf03a6f8a1c0e30dc6b32e024c47dbc4f055909c262b736e2dea14d573d8de3f67a8e48a05211a937812a3943305cf0cbddcbe14cbf8a70fa49c35b476816c81354fdd64e5429a2ede7191e71fea92d0aec9a2aff1452facb29d8bc7d8c1b207398b7f83d7d6d1007bbe6c70d20d91ea2ab372d6412ce4318000e4e8fbc6ca7b5a3555ff0a63ab77d55fe451bbf803299b6cd7272813530c6add3d4cbbddf2b82f9d90119d8897b1c5fcedfe50875d2bc4ef7eabee6601627672d51d5712ff48550c45b334c5a4b9139f9bdaea8b03b7571262d0aa3fdfee96d27741639d90d1916b22b15c23535f607d58c796660fdc3630e33fd4ff6c894832b894fb5112ab0ffbe6ff39e0e3d4b6bdeb64a1ee32aec842251abc49ea623040b0891335a71f1dd1d9e0c6637220ec8cb70c3ea1056a9d4a8017e76af138e565beecf6c16b09631012f012ab78fa6472df9cde281810f7f7450fb761edf881b92a39a3019ee3d3a4c8ef700fa9337136c7e00d17fece18655a21115fa97144459a3a01b9703ae664a7b958fb7d67096973bea6f293060d3bfd54c8aeaeb038bae6810d42ee70a0ddf523fd7f2efbec2a0aab44ec66c1ceb55b537399b51cf17f46978ae5a81853942ef113623e8e8d38a7218c270db8d282f31a0412c2ad44279e649e4dd8bd3f3b93b37ed50e58980e7591b21a80605bac1631eaf2aad74edb236c0c6c195b2ded22ce11b4fa86afe1b8a46aab61ba9b0f7e74fd40dee1f61eab0bc313e53a1af1b63125146ae926eb9f5de9504a24ba941517008cea6655a0ba72ba543cf53bc7a225de9a8411fb1fe4dd2b7f57b36fd701fd6b4325d6aceaebcbc52e13725561255a5bcc0409460afaa6a0474f26647d886899a14d50fbebfa84e3017dc4194719869849b16b7aae368c3b78f9d8572a67fa822c3afa8339626efa285af7b811f06f0def09d07af29597cd0422353411d31fa45a963b89d6eabcf7727c8bf8723474ca1d7a6f067768e98a5494cf2c28ee4adb92e258b76483b8fd77d0da98eb00d79aab599cf8caaaf56d7c5bbb0e3284fa3b3b48ab8d5555f3bfd5faaf192e63b60458bb91b7e3aab636012080d5e4ce7ebee3c4dab20f2c5af143ffe88b8d7f44e82077a8280588aa991bad39779276e76448b545f1a7373be7ef9bb5ee3b8a2a8f22d591a4f6e2dd00e264b93a74c9514e2477799395338cb5e032fe8eeaa13ca119da6d95ce93db954f7440bf68bccc53910dea753976c6d3d24656fdbca9f02001d0020e1204d17000fef7312b84ed0297f34bc6881732c8945f83a7a1abcf0ac04e423001700410430cc12bcb2f1c20c357af1be37b15e75d1f1203671cd7f77d167f8416fbba1389389a5ae57495d09d0f1ce74e50ee5059df1e6abf3313fb720f2178af511be300018006104d0e3f9daf59fa5f02212ba3893a09f6617f40b916e0390ee83398ab0367555aab3d3001ff9dcca61f4861b04e3cd0ec763996ff274d9d56177fe8eee50791ef37279f5ed7305597c829c2b189f894804d2151eeea40acbf17ab47f3ef61628870019008504016a19509758d2bac50339a747d528f8780671c71fa31186d07d770e5aeb6f4497156c3c801e25bb5967996df01f967d0415b8e6719fc2b56ed8db99a6c165dc0ea6003b4b7b89bc2ddeaa98c79133b96f64ccc34d99c6a313a7671ac72bf0e56942eefa60008a2c769f3e6bcb0ffcd1319ad6ba2ff87ef1420ebdc44c3e0b30b43f9b6c +Exception = + +# not enough bytes in the buffer to decode the extension +Buffer = 05ed413804929e +Exception = Not enough bytes in the buffer to decode KeyShare (ClientHello) extension diff --git a/src/tests/data/tls_extensions/parsing/key_share_HRR.vec b/src/tests/data/tls_extensions/parsing/key_share_HRR.vec new file mode 100644 index 00000000000..ae7df888548 --- /dev/null +++ b/src/tests/data/tls_extensions/parsing/key_share_HRR.vec @@ -0,0 +1,12 @@ +# KeyShareHelloRetryRequest (variant of KeyShare extension) consists of: +# - Selected group (2 bytes) + +[key_share_HRR] +# correct extension content +Buffer = 0018 +Expected_Content = 0018 +Exception = + +# incorrect extension content (3 bytes instead of 2) +Buffer = 001877 +Exception = Size of KeyShare extension in HelloRetryRequest must be 2 bytes diff --git a/src/tests/data/tls_extensions/parsing/key_share_SH.vec b/src/tests/data/tls_extensions/parsing/key_share_SH.vec new file mode 100644 index 00000000000..48ac32d1991 --- /dev/null +++ b/src/tests/data/tls_extensions/parsing/key_share_SH.vec @@ -0,0 +1,17 @@ +# KeyShareServerKello (variant of KeyShare extension) consists of: +# - KeyShareEntry: +# - Group (2 bytes) +# - Key Exchange Length (2 bytes) +# - Key Exchange (vector of bytes[Key Exchange Length]) + + +[key_share_SH] +# correct extension content +Buffer = 001d0020f0dc1c73b9de09ca9a65dc7565b06e698c0a2ac27f5240026e56c5b2f8d88d4d +Expected_Content = 001d0020f0dc1c73b9de09ca9a65dc7565b06e698c0a2ac27f5240026e56c5b2f8d88d4d +Exception = + +# not enough bytes in the buffer to decode extension +Buffer = 001d0020f0dc1c73b9de09ca9a65dc7565b06e698c0a2ac27f5240026e56c5b2f8d88d +Expected_Content = +Exception = Not enough bytes in the buffer to decode KeyShare (ServerHello) extension diff --git a/src/tests/data/tls_extensions/parsing/signature_algorithms_cert.vec b/src/tests/data/tls_extensions/parsing/signature_algorithms_cert.vec new file mode 100644 index 00000000000..734ef9f2e80 --- /dev/null +++ b/src/tests/data/tls_extensions/parsing/signature_algorithms_cert.vec @@ -0,0 +1,27 @@ +# Signature_algorithms_cert extension consists of: +# - Signature_algorithms_cert length (2 bytes) +# - Signature_algorithms_cert content (vector of Signature_Scheme) + +[signature_algorithms_cert] +# correct extension content with only one signature scheme +Buffer = 00020401 +Expected_Content = 0401 +Exception = + +# correct extension content with many signature schemes +Buffer = 000E0401050106010403050306030804 +Expected_Content = 0401050106010403050306030804 +Exception = + +# incorrect extension missing content +Buffer = 000E0401 +Exception = Bad encoding on signature algorithms extension + +# incorrect extension content size 0 +Buffer = 00000304 +Exception = signature_algorithms_cert length must be bigger than 0 + +# incorrect extension content size 256 +Buffer = 0100040104030804 +Exception = Too many signature schemes + diff --git a/src/tests/data/tls_extensions/parsing/supported_groups.vec b/src/tests/data/tls_extensions/parsing/supported_groups.vec new file mode 100644 index 00000000000..52ce4bf425f --- /dev/null +++ b/src/tests/data/tls_extensions/parsing/supported_groups.vec @@ -0,0 +1,26 @@ +# Supported_groups extension consists of: +# - Supported_groups length (2 bytes) +# - Supported_groups content (vector of Group_Params) + +[supported_groups] +# correct extension content with only one group params +Buffer = 00020017 +Expected_Content = 0017 +Exception = + +# correct extension content with many group params +Buffer = 0012001700180019001D01000101010201030104 +Expected_Content = 001700180019001D01000101010201030104 +Exception = + +# incorrect extension wrong length +Buffer = 00040017 +Exception = Inconsistent length field in supported groups list + +# incorrect extension content size 0 +Buffer = 00000017 +Exception = Inconsistent length field in supported groups list + +# incorrect extension odd bytes number +Buffer = 0003001700 +Exception = Supported groups list of strange size diff --git a/src/tests/data/tls_extensions/parsing/supported_versions.vec b/src/tests/data/tls_extensions/parsing/supported_versions.vec new file mode 100644 index 00000000000..094f5842f9b --- /dev/null +++ b/src/tests/data/tls_extensions/parsing/supported_versions.vec @@ -0,0 +1,23 @@ +# Tests generated partially with openssl 1.0.2g +# ServerHello message contains many fields, the following fields are checked: +# - Protocol Version +# - Cipher suite +# - Extensions + + +#0000 00 2b 00 03 02 03 04 +#002b0003020304 + + +[supported_version] +# correct, with session ticket and renegotiation info +Buffer = 020304 +Expected_Content = 0304 +Exception = + +# test2 +Buffer = 0403040303 +Expected_Content = 0304, 0303 +Exception = + + diff --git a/src/tests/test_tls.cpp b/src/tests/test_tls.cpp index 6d0fb04edbf..31ff635fc56 100644 --- a/src/tests/test_tls.cpp +++ b/src/tests/test_tls.cpp @@ -327,7 +327,12 @@ class Test_TLS_Policy_Text : public Test for(const std::string& policy : policies) { const std::string from_policy_obj = tls_policy_string(policy); - std::string from_file = read_tls_policy(policy); + std::string from_file = +#if defined(BOTAN_HAS_TLS_13) + read_tls_policy(policy + (policy == "default" || policy == "strict" ? "_tls13" : "")); +#else + read_tls_policy(policy); +#endif result.test_eq("Values for TLS " + policy + " policy", from_file, from_policy_obj); } diff --git a/src/tests/test_tls_cipher_state.cpp b/src/tests/test_tls_cipher_state.cpp new file mode 100644 index 00000000000..bac94b9fadd --- /dev/null +++ b/src/tests/test_tls_cipher_state.cpp @@ -0,0 +1,347 @@ +/* +* (C) 2021 Jack Lloyd +* (C) 2021 Hannes Rantzsch, René Meusel - neXenio +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + +#if defined(BOTAN_HAS_TLS_13) + +#include +#include + +#include + +namespace { + +using Test = Botan_Tests::Test; +using namespace Botan; +using namespace Botan::TLS; + +class RFC8448_TestData + { +private: + const std::string name; + const std::vector record_header; + const secure_vector encrypted_fragment; + const secure_vector plaintext_fragment; + +public: + RFC8448_TestData(std::string n, + std::vector rh, + secure_vector ef, + secure_vector pf) + : name(std::move(n)) + , record_header(std::move(rh)) + , encrypted_fragment(std::move(ef)) + , plaintext_fragment(std::move(pf)) {} + + void encrypt(Test::Result &result, Cipher_State* cs) const + { + auto plaintext_fragment_copy = plaintext_fragment; + result.test_no_throw("encryption is successful for " + name, [&] + { + cs->encrypt_record_fragment(record_header, plaintext_fragment_copy); + }); + + result.test_eq("encrypted payload for " + name, plaintext_fragment_copy, encrypted_fragment); + } + + void decrypt(Test::Result &result, Cipher_State* cs) const + { + auto encrypted_fragment_copy = encrypted_fragment; + result.test_no_throw("decryption is successful for " + name, [&] + { + cs->decrypt_record_fragment(record_header, encrypted_fragment_copy); + }); + + result.test_eq("plaintext for " + name, encrypted_fragment_copy, plaintext_fragment); + } + }; + +std::vector test_secret_derivation_rfc8448_rtt1() + { + // shared secret + const auto shared_secret = Botan::hex_decode_locked( + "8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d" + "35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"); + + const auto expected_psk = Botan::hex_decode_locked( + "4e cd 0e b6 ec 3b 4d 87 f5 d6 02 8f 92 2c a4 c5" + "85 1a 27 7f d4 13 11 c9 e6 2d 2c 94 92 e1 c4 f3"); + + // this is not part of RFC 8448 + const std::string export_label = "export_test_label"; + const std::string export_context = "rfc8448_rtt1"; + const auto expected_key_export = Botan::hex_decode_locked( + "f2 00 58 a6 5c e0 43 0a 19 79 44 c8 12 43 1c 2d"); + + // transcript hash from client hello and server hello + const auto th_server_hello = Botan::hex_decode( + "86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed" + "d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"); + + // transcript hash from client hello up to (excluding) server finished + const auto th_pre_server_finished = Botan::hex_decode( + "ed b7 72 5f a7 a3 47 3b 03 1e c8 ef 65 a2 48 54" + "93 90 01 38 a2 b9 12 91 40 7d 79 51 a0 61 10 ed"); + + // transcript hash from client hello up to (including) server finished + const auto th_server_finished = Botan::hex_decode( + "96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a" + "00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"); + + // transcript hash from client hello up to (including) client finished + const auto th_client_finished = Botan::hex_decode( + "20 91 45 a9 6e e8 e2 a1 22 ff 81 00 47 cc 95 26" + "84 65 8d 60 49 e8 64 29 42 6d b8 7c 54 ad 14 3d"); + + // encrypted with server_handshake_traffic_secret + const auto encrypted_extensions = RFC8448_TestData + ( + "encrypted_extensions", + Botan::hex_decode("17 03 03 02 a2"), + Botan::hex_decode_locked("d1 ff 33 4a 56 f5 bf" + "f6 59 4a 07 cc 87 b5 80 23 3f 50 0f 45 e4 89 e7 f3 3a f3 5e df" + "78 69 fc f4 0a a4 0a a2 b8 ea 73 f8 48 a7 ca 07 61 2e f9 f9 45" + "cb 96 0b 40 68 90 51 23 ea 78 b1 11 b4 29 ba 91 91 cd 05 d2 a3" + "89 28 0f 52 61 34 aa dc 7f c7 8c 4b 72 9d f8 28 b5 ec f7 b1 3b" + "d9 ae fb 0e 57 f2 71 58 5b 8e a9 bb 35 5c 7c 79 02 07 16 cf b9" + "b1 18 3e f3 ab 20 e3 7d 57 a6 b9 d7 47 76 09 ae e6 e1 22 a4 cf" + "51 42 73 25 25 0c 7d 0e 50 92 89 44 4c 9b 3a 64 8f 1d 71 03 5d" + "2e d6 5b 0e 3c dd 0c ba e8 bf 2d 0b 22 78 12 cb b3 60 98 72 55" + "cc 74 41 10 c4 53 ba a4 fc d6 10 92 8d 80 98 10 e4 b7 ed 1a 8f" + "d9 91 f0 6a a6 24 82 04 79 7e 36 a6 a7 3b 70 a2 55 9c 09 ea d6" + "86 94 5b a2 46 ab 66 e5 ed d8 04 4b 4c 6d e3 fc f2 a8 94 41 ac" + "66 27 2f d8 fb 33 0e f8 19 05 79 b3 68 45 96 c9 60 bd 59 6e ea" + "52 0a 56 a8 d6 50 f5 63 aa d2 74 09 96 0d ca 63 d3 e6 88 61 1e" + "a5 e2 2f 44 15 cf 95 38 d5 1a 20 0c 27 03 42 72 96 8a 26 4e d6" + "54 0c 84 83 8d 89 f7 2c 24 46 1a ad 6d 26 f5 9e ca ba 9a cb bb" + "31 7b 66 d9 02 f4 f2 92 a3 6a c1 b6 39 c6 37 ce 34 31 17 b6 59" + "62 22 45 31 7b 49 ee da 0c 62 58 f1 00 d7 d9 61 ff b1 38 64 7e" + "92 ea 33 0f ae ea 6d fa 31 c7 a8 4d c3 bd 7e 1b 7a 6c 71 78 af" + "36 87 90 18 e3 f2 52 10 7f 24 3d 24 3d c7 33 9d 56 84 c8 b0 37" + "8b f3 02 44 da 8c 87 c8 43 f5 e5 6e b4 c5 e8 28 0a 2b 48 05 2c" + "f9 3b 16 49 9a 66 db 7c ca 71 e4 59 94 26 f7 d4 61 e6 6f 99 88" + "2b d8 9f c5 08 00 be cc a6 2d 6c 74 11 6d bd 29 72 fd a1 fa 80" + "f8 5d f8 81 ed be 5a 37 66 89 36 b3 35 58 3b 59 91 86 dc 5c 69" + "18 a3 96 fa 48 a1 81 d6 b6 fa 4f 9d 62 d5 13 af bb 99 2f 2b 99" + "2f 67 f8 af e6 7f 76 91 3f a3 88 cb 56 30 c8 ca 01 e0 c6 5d 11" + "c6 6a 1e 2a c4 c8 59 77 b7 c7 a6 99 9b bf 10 dc 35 ae 69 f5 51" + "56 14 63 6c 0b 9b 68 c1 9e d2 e3 1c 0b 3b 66 76 30 38 eb ba 42" + "f3 b3 8e dc 03 99 f3 a9 f2 3f aa 63 97 8c 31 7f c9 fa 66 a7 3f" + "60 f0 50 4d e9 3b 5b 84 5e 27 55 92 c1 23 35 ee 34 0b bc 4f dd" + "d5 02 78 40 16 e4 b3 be 7e f0 4d da 49 f4 b4 40 a3 0c b5 d2 af" + "93 98 28 fd 4a e3 79 4e 44 f9 4d f5 a6 31 ed e4 2c 17 19 bf da" + "bf 02 53 fe 51 75 be 89 8e 75 0e dc 53 37 0d 2b"), + Botan::hex_decode_locked( + "08 00 00 24 00 22 00 0a 00 14 00 12 00 1d" + "00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c 00 02 40" + "01 00 00 00 00 0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 01 ac 30" + "82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 86 f7 0d" + "01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 72 73 61" + "30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 0d 32 36" + "30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 03 55 04" + "03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 0d 01 01" + "01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f 82 79 30" + "3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 d3 90 1a" + "24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c 1a f1 9e" + "aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 4b 1b 01" + "8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 80 30 53" + "0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 ef f0 ab" + "9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 01 00 01" + "a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 03 55 1d" + "0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05" + "00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a 72 67 17" + "06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea e8 f8 a5" + "8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 51 56 72" + "60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be c1 fc 63" + "a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b 1c 3b 84" + "e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 96 12 29" + "ac 91 87 b4 2b 4d e1 00 00 0f 00 00 84 08 04 00 80 5a 74 7c 5d" + "88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a b3" + "ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 86" + "53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b be" + "8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 5c" + "9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a 3d" + "a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3 14 00" + "00 20 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 de da 4a b4 2c" + "30 95 72 cb 7f ff ee 54 54 b7 8f 07 18" + "16" /* to-be-encrypted content type */) + ); + + // encrypted with client_handshake_traffic_secret + const auto encrypted_client_finished_message = RFC8448_TestData + ( + "encrypted_client_finished_message", + Botan::hex_decode("17 03 03 00 35"), + Botan::hex_decode_locked("75 ec 4d c2 38 cc e6" + "0b 29 80 44 a7 1e 21 9c 56 cc 77 b0 51 7f e9 b9 3c 7a 4b fc 44" + "d8 7f 38 f8 03 38 ac 98 fc 46 de b3 84 bd 1c ae ac ab 68 67 d7" + "26 c4 05 46"), + Botan::hex_decode_locked("14 00 00 20 a8 ec 43 6d 67 76 34 ae 52 5a c1" + "fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61" + "16" /* to-be-encrypted content type */) + ); + + // encrypted with server_application_traffic_secret + const auto encrypted_new_session_ticket = RFC8448_TestData + ( + "encrypted_new_session_ticket", + Botan::hex_decode("17 03 03 00 de"), + Botan::hex_decode_locked( + "3a 6b 8f 90 41 4a 97" + "d6 95 9c 34 87 68 0d e5 13 4a 2b 24 0e 6c ff ac 11 6e 95 d4 1d" + "6a f8 f6 b5 80 dc f3 d1 1d 63 c7 58 db 28 9a 01 59 40 25 2f 55" + "71 3e 06 1d c1 3e 07 88 91 a3 8e fb cf 57 53 ad 8e f1 70 ad 3c" + "73 53 d1 6d 9d a7 73 b9 ca 7f 2b 9f a1 b6 c0 d4 a3 d0 3f 75 e0" + "9c 30 ba 1e 62 97 2a c4 6f 75 f7 b9 81 be 63 43 9b 29 99 ce 13" + "06 46 15 13 98 91 d5 e4 c5 b4 06 f1 6e 3f c1 81 a7 7c a4 75 84" + "00 25 db 2f 0a 77 f8 1b 5a b0 5b 94 c0 13 46 75 5f 69 23 2c 86" + "51 9d 86 cb ee ac 87 aa c3 47 d1 43 f9 60 5d 64 f6 50 db 4d 02" + "3e 70 e9 52 ca 49 fe 51 37 12 1c 74 bc 26 97 68 7e 24 87 46 d6" + "df 35 30 05 f3 bc e1 86 96 12 9c 81 53 55 6b 3b 6c 67 79 b3 7b" + "f1 59 85 68 4f"), + Botan::hex_decode_locked( + "04 00 00 c9 00 00 00 1e fa d6 aa c5 02 00" + "00 00 b2 2c 03 5d 82 93 59 ee 5f f7 af 4e c9 00 00 00 00 26 2a" + "64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf 1b 00 70 ad 3c 49 88 83" + "c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60 97 a3 a9 82 11 72 83 f8" + "2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61 be 7f d6 1d 28 27 db 27" + "9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4 d2 9e e0 37 25 a6 a4 da" + "fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2 67 7f a5 90 6c 5b 3f 7d" + "8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb f2 97 b5 ae a6 17 64 6f" + "ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41 ef 5f 7d e6 50 5e 5b fb" + "c3 88 e9 33 43 69 40 93 93 4a e4 d3 57 00 08 00 2a 00 04 00 00" + "04 00" + "16" /* to-be-encrypted content type */) + ); + + // encrypted with client_application_traffic_secret + const auto encrypted_application_data_client = RFC8448_TestData + ( + "encrypted_application_data_client", + Botan::hex_decode("17 03 03 00 43"), + Botan::hex_decode_locked("a2 3f 70 54 b6 2c 94" + "d0 af fa fe 82 28 ba 55 cb ef ac ea 42 f9 14 aa 66 bc ab 3f 2b" + "98 19 a8 a5 b4 6b 39 5b d5 4a 9a 20 44 1e 2b 62 97 4e 1f 5a 62" + "92 a2 97 70 14 bd 1e 3d ea e6 3a ee bb 21 69 49 15 e4"), + Botan::hex_decode_locked( + "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e" + "0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23" + "24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31" + "17" /* to-be-encrypted content type */) + ); + + // encrypted with server_application_traffic_secret + const auto encrypted_application_data_server = RFC8448_TestData + ( + "encrypted_application_data_server", + Botan::hex_decode("17 03 03 00 43"), + Botan::hex_decode_locked("2e 93 7e 11 ef 4a c7" + "40 e5 38 ad 36 00 5f c4 a4 69 32 fc 32 25 d0 5f 82 aa 1b 36 e3" + "0e fa f9 7d 90 e6 df fc 60 2d cb 50 1a 59 a8 fc c4 9c 4b f2 e5" + "f0 a2 1c 00 47 c2 ab f3 32 54 0d d0 32 e1 67 c2 95 5d"), + Botan::hex_decode_locked( + "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e" + "0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23" + "24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31" + "17" /* to-be-encrypted content type */) + ); + + auto cipher = Ciphersuite::from_name("AES_128_GCM_SHA256").value(); + + // initialize Cipher_State with client_hello...server_hello + auto my_shared_secret = shared_secret; + auto cs = Cipher_State::init_with_server_hello(Connection_Side::CLIENT, std::move(my_shared_secret), cipher, + th_server_hello); + + return + { + Botan_Tests::CHECK("handshake traffic without PSK (client side)", [&](Test::Result& result) + { + result.confirm("can not yet write application data", !cs->can_encrypt_application_traffic()); + result.confirm("can not yet export key material", !cs->can_export_keys()); + + // decrypt encrypted extensions from server + encrypted_extensions.decrypt(result, cs.get()); + + // validate the MAC we receive in server Finished message + const auto expected_server_mac = Botan::hex_decode("9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4" + "de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"); + result.confirm("expecting the correct MAC for server finished", cs->verify_peer_finished_mac(th_pre_server_finished, + expected_server_mac)); + + // generate the MAC for the client Finished message + const auto expected_client_mac = Botan::hex_decode("a8 ec 43 6d 67 76 34 ae 52 5a c1 fc eb e1 1a 03" + "9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61"); + result.test_eq("generating the correct MAC for client finished", cs->finished_mac(th_server_finished), expected_client_mac); + + // encrypt client Finished message by client + // (under the client handshake traffic secret) + encrypted_client_finished_message.encrypt(result, cs.get()); + + // advance Cipher_State with client_hello...server_Finished + // (allows sending of application data) + result.test_no_throw("state advancement is legal", [&] + { + cs->advance_with_server_finished(th_server_finished); + }); + + result.confirm("can write application data", cs->can_encrypt_application_traffic()); + result.confirm("can export key material", cs->can_export_keys()); + result.test_eq("key export produces expected result", cs->export_key(export_label, export_context, 16), expected_key_export); + + // decrypt "new session ticket" post-handshake message from server + // (encrypted under the application traffic secret) + encrypted_new_session_ticket.decrypt(result, cs.get()); + + // encrypt application data by client + encrypted_application_data_client.encrypt(result, cs.get()); + + // decrypt application data from server + // (encrypted under the application traffic secret -- and a new sequence number) + encrypted_application_data_server.decrypt(result, cs.get()); + + // advance Cipher_State with client_hello...client_Finished + // (allows generation of resumption PSKs) + result.test_no_throw("state advancement is legal", [&] + { + cs->advance_with_client_finished(th_client_finished); + }); + + result.confirm("can export key material still", cs->can_export_keys()); + result.test_eq("key export result did not change", cs->export_key(export_label, export_context, 16), expected_key_export); + }), + + Botan_Tests::CHECK("PSK", [&](Test::Result &result) { + // derive PSK for resumption + const auto psk = cs->psk({0x00, 0x00} /* ticket_nonce as defined in RFC 8448 */); + result.test_eq("PSK matches", psk, expected_psk); + + }), + + Botan_Tests::CHECK("key update", [&](Test::Result &result) { + cs->update_read_keys(); + cs->update_write_keys(); + + result.confirm("can encrypt application traffic", cs->can_encrypt_application_traffic()); + }), + + Botan_Tests::CHECK("cleanup", [&](Test::Result &result) { + // cleanup + cs->clear_write_keys(); + result.confirm("can no longer write application data", !cs->can_encrypt_application_traffic()); + }) + }; + } + +} // namespace + +namespace Botan_Tests { +BOTAN_REGISTER_TEST_FN("tls", "tls_cipher_state", test_secret_derivation_rfc8448_rtt1); +} + +#endif diff --git a/src/tests/test_tls_handshake_layer_13.cpp b/src/tests/test_tls_handshake_layer_13.cpp new file mode 100644 index 00000000000..2b018de9e3a --- /dev/null +++ b/src/tests/test_tls_handshake_layer_13.cpp @@ -0,0 +1,412 @@ +/* +* (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_13) + +#include +#include + +#include +#include +#include +#include + +using namespace Botan::TLS; + +namespace { + +using Test = Botan_Tests::Test; + +template +bool has_message( + Test::Result& test_result, + const std::optional& read_result) + { + test_result.require("has a message", read_result.has_value()); + return std::holds_alternative(read_result.value()); + } + +template +const Handshake_Message_13& get_message( + Test::Result& test_result, + const std::optional& read_result) + { + test_result.require("has the expected message", has_message(test_result, read_result)); + return std::get(read_result.value()); + } + +const auto client_hello_message = Botan::hex_decode( // from RFC 8448 + "01 00 00 c0 03 03 cb" + "34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12" + "ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00" + "00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01" + "00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02" + "01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d" + "e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d" + "54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e" + "04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02" + "01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"); + +const auto server_hello_message = Botan::hex_decode( + "02 00 00 56 03 03 a6" + "af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14" + "34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00" + "1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6" + "cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"); + +const auto server_hello_12_message = Botan::hex_decode( + "02 00 00 3e 03 03 ff ea 0b cf ba 56 4a 4c e1 77 c6 a4 44 b0 eb" + "df f5 62 9b 27 72 93 c6 18 c1 12 5f 23 1e 86 28 dd 00 c0 30 00" + "00 16 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 23 00 00 00 0f" + "00 01 01"); + +const auto encrypted_extensions = Botan::hex_decode( + "08 00 00 24 00 22 00 0a 00 14 00" + "12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c" + "00 02 40 01 00 00 00 00"); + +const auto server_handshake_messages = // except server hello + Botan::hex_decode( + "08 00 00 24 00 22 00 0a 00 14 00 12 00 1d" + "00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c 00 02 40" + "01 00 00 00 00 0b 00 01 b9 00 00 01 b5 00 01 b0 30 82 01 ac 30" + "82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48 86 f7 0d" + "01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03 72 73 61" + "30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17 0d 32 36" + "30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06 03 55 04" + "03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7 0d 01 01" + "01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f 82 79 30" + "3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26 d3 90 1a" + "24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c 1a f1 9e" + "aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52 4b 1b 01" + "8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74 80 30 53" + "0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93 ef f0 ab" + "9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03 01 00 01" + "a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06 03 55 1d" + "0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01 01 0b 05" + "00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a 72 67 17" + "06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea e8 f8 a5" + "8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01 51 56 72" + "60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be c1 fc 63" + "a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b 1c 3b 84" + "e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8 96 12 29" + "ac 91 87 b4 2b 4d e1 00 00 0f 00 00 84 08 04 00 80 5a 74 7c 5d" + "88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a b3" + "ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07 86" + "53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b be" + "8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44 5c" + "9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a 3d" + "a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3 14 00" + "00 20 9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 de da 4a b4 2c" + "30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"); + +const auto client_finished_message = Botan::hex_decode( + "14 00 00 20 a8 ec 43 6d 67 76 34 ae 52 5a c1" + "fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61"); + +// +// ################################################################### +// + +const auto hrr_client_hello_msg = Botan::hex_decode( + "01 00 00 b0 03 03 b0 b1 c5 a5 aa 37 c5" + "91 9f 2e d1 d5 c6 ff f7 fc b7 84 97 16 94 5a 2b 8c ee 92 58 a3" + "46 67 7b 6f 00 00 06 13 01 13 03 13 02 01 00 00 81 00 00 00 0b" + "00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 08 00" + "06 00 1d 00 17 00 18 00 33 00 26 00 24 00 1d 00 20 e8 e8 e3 f3" + "b9 3a 25 ed 97 a1 4a 7d ca cb 8a 27 2c 62 88 e5 85 c6 48 4d 05" + "26 2f ca d0 62 ad 1f 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04" + "03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01" + "04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"); + +const auto hrr_hello_retry_request_msg = Botan::hex_decode( + "02 00 00 ac 03 03 cf 21 ad 74 e5 9a 61" + "11 be 1d 8c 02 1e 65 b8 91 c2 a2 11 16 7a bb 8c 5e 07 9e 09 e2" + "c8 a8 33 9c 00 13 01 00 00 84 00 33 00 02 00 17 00 2c 00 74 00" + "72 71 dc d0 4b b8 8b c3 18 91 19 39 8a 00 00 00 00 ee fa fc 76" + "c1 46 b8 23 b0 96 f8 aa ca d3 65 dd 00 30 95 3f 4e df 62 56 36" + "e5 f2 1b b2 e2 3f cc 65 4b 1b 5b 40 31 8d 10 d1 37 ab cb b8 75" + "74 e3 6e 8a 1f 02 5f 7d fa 5d 6e 50 78 1b 5e da 4a a1 5b 0c 8b" + "e7 78 25 7d 16 aa 30 30 e9 e7 84 1d d9 e4 c0 34 22 67 e8 ca 0c" + "af 57 1f b2 b7 cf f0 f9 34 b0 00 2b 00 02 03 04"); + +const std::vector> tls_12_only_messages + { + {HELLO_REQUEST, 0x00, 0x00, 0x02, 0x42, 0x42}, + {HELLO_VERIFY_REQUEST, 0x00, 0x00, 0x02, 0x42, 0x42}, + {SERVER_KEX, 0x00, 0x00, 0x02, 0x42, 0x42}, + {SERVER_HELLO_DONE, 0x00, 0x00, 0x02, 0x42, 0x42}, + {CLIENT_KEX, 0x00, 0x00, 0x02, 0x42, 0x42}, + {CERTIFICATE_URL, 0x00, 0x00, 0x02, 0x42, 0x42}, + {CERTIFICATE_STATUS, 0x00, 0x00, 0x02, 0x42, 0x42} + }; + +void check_transcript_hash_empty(Test::Result& result, const Transcript_Hash_State& transcript_hash) + { + result.test_throws("empty transcript_hash throws", [&] { transcript_hash.current(); }); + } + +void check_transcript_hash_filled(Test::Result& result, const Transcript_Hash_State& transcript_hash) + { + result.test_no_throw("non-empty transcript_hash", [&] { transcript_hash.current(); }); + } + +std::vector read_handshake_messages() + { + return + { + Botan_Tests::CHECK("empty read", [&](auto& result) + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + result.confirm("needs header bytes", !hl.next_message(Policy(), th)); + check_transcript_hash_empty(result, th); + }), + + Botan_Tests::CHECK("read incomplete header", [&](auto& result) + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + hl.copy_data({0x00, 0x01, 0x02}); + result.confirm("needs more bytes", !hl.next_message(Policy(), th)); + check_transcript_hash_empty(result, th); + }), + + Botan_Tests::CHECK("read client hello", [&](auto& result) + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + hl.copy_data(client_hello_message); + result.confirm("is a client hello", has_message(result, hl.next_message(Policy(), th))); + check_transcript_hash_filled(result, th); + }), + + Botan_Tests::CHECK("read server hello", [&](auto& result) + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + hl.copy_data(server_hello_message); + result.confirm("is a server hello", has_message(result, hl.next_message(Policy(), th))); + check_transcript_hash_filled(result, th); + }), + + Botan_Tests::CHECK("read legacy server hello", [&](auto& result) + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + hl.copy_data(server_hello_12_message); + result.confirm("is a legacy server hello", has_message(result, hl.next_message(Policy(), th))); + check_transcript_hash_filled(result, th); + }), + + Botan_Tests::CHECK("read client hello in two steps", [&](auto& result) + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + + const std::vector partial_client_hello_message( + client_hello_message.cbegin(), client_hello_message.cend() - 15); + hl.copy_data(partial_client_hello_message); + result.confirm("needs more bytes", !hl.next_message(Policy(), th)); + result.confirm("holds pending message data", hl.has_pending_data()); + + const std::vector remaining_client_hello_message( + client_hello_message.cend() - 15, client_hello_message.cend()); + hl.copy_data(remaining_client_hello_message); + result.confirm("is a client hello", has_message(result, hl.next_message(Policy(), th))); + + check_transcript_hash_filled(result, th); + }), + + Botan_Tests::CHECK("read multiple messages", [&](auto& result) + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + hl.copy_data(Botan::concat(server_hello_message, encrypted_extensions)); + result.confirm("is a server hello", has_message(result, hl.next_message(Policy(), th))); + result.confirm("is encrypted extensions", has_message(result, hl.next_message(Policy(), th))); + check_transcript_hash_filled(result, th); + }), + + Botan_Tests::CHECK("reject TLS 1.2 messages", [&](auto& result) + { + for(const auto& msg : tls_12_only_messages) + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + + hl.copy_data(msg); + result.template test_throws("message is rejected", "Unknown handshake message received", + [&] { hl.next_message(Policy(), th); }); + + check_transcript_hash_empty(result, th); + } + }), + + Botan_Tests::CHECK("reject incomplete messages with invalid type", [&](auto& result) + { + // This is a regression test for BoGo "TLS13-WrongOuterRecord". We receive encrypted handshake messages + // with the record type set to Handshake rather than Application Data. This exposed our handshake layer + // to "random data" with an insensible type tag and a long (insensible) length field. + // This caused a deadlock as we waited to receive the complete message, rather than validating the type + // tag to exit early. + const auto data = Botan::hex_decode("D4B028717D0FA310FF8664127B9448D7952E06A4F9EA23"); + // data from the bogo test -- ~~~~~~ <- length + // ~~ <- bogus message type + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + hl.copy_data(data); + result.template test_throws("message is rejected", "Unknown handshake message received", + [&] { hl.next_message(Policy(), th); }); + }), + }; + } + +std::vector prepare_message() + { + return + { + Botan_Tests::CHECK("prepare client hello", [&](auto& result) + { + Client_Hello_13 hello({client_hello_message.cbegin()+4, client_hello_message.cend()}); + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th("SHA-256"); + result.test_eq("produces the same message", hl.prepare_message(hello, th), client_hello_message); + check_transcript_hash_filled(result, th); + }), + + Botan_Tests::CHECK("prepare server hello", [&](auto& result) + { + auto hello = std::get(Server_Hello_13::parse({server_hello_message.cbegin()+4, server_hello_message.cend()})); + Handshake_Layer hl(Connection_Side::SERVER); + Transcript_Hash_State th("SHA-256"); + result.test_eq("produces the same message", hl.prepare_message(hello, th), server_hello_message); + check_transcript_hash_filled(result, th); + }), + }; + } + +std::vector full_client_handshake() + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th; + + Text_Policy policy("minimum_rsa_bits = 1024"); + + return + { + Botan_Tests::CHECK("client hello", [&](auto& result) + { + Client_Hello_13 hello({client_hello_message.cbegin()+4, client_hello_message.cend()}); + hl.prepare_message(hello, th); + check_transcript_hash_empty(result, th); + }), + + Botan_Tests::CHECK("server hello", [&](auto& result) + { + hl.copy_data(server_hello_message); + + const auto server_hello = hl.next_message(policy, th); + result.confirm("is a Server Hello", has_message(result, server_hello)); + + // we now know the algorithm from the Server Hello + th.set_algorithm("SHA-256"); + + check_transcript_hash_filled(result, th); + + const auto expected_after_server_hello = Botan::hex_decode( + "86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"); + + result.test_eq("correct transcript hash produced after server hello", th.current(), expected_after_server_hello); + }), + + Botan_Tests::CHECK("server handshake messages", [&](auto& result) + { + hl.copy_data(server_handshake_messages); + + const auto enc_exts = hl.next_message(policy, th); + result.confirm("is Encrypted Extensions", has_message(result, enc_exts)); + + const auto cert = hl.next_message(policy, th); + result.confirm("is Certificate", has_message(result, cert)); + + const auto expected_after_certificate = Botan::hex_decode( + "76 4d 66 32 b3 c3 5c 3f 32 05 e3 49 9a c3 ed ba ab b8 82 95 fb a7 51 46 1d 36 78 e2 e5 ea 06 87"); + + const auto cert_verify = hl.next_message(policy, th); + result.confirm("is Certificate Verify", has_message(result, cert_verify)); + result.test_eq("hash before Cert Verify is still available", th.previous(), expected_after_certificate); + + const auto expected_after_server_finished = Botan::hex_decode( + "96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"); + + const auto server_finished = hl.next_message(policy, th); + result.confirm("is Finished", has_message(result, server_finished)); + result.test_eq("hash is updated after server Finished", th.current(), expected_after_server_finished); + }), + + Botan_Tests::CHECK("client finished", [&](auto& result) + { + const auto expected_after_client_finished = Botan::hex_decode( + "20 91 45 a9 6e e8 e2 a1 22 ff 81 00 47 cc 95 26 84 65 8d 60 49 e8 64 29 42 6d b8 7c 54 ad 14 3d"); + + Finished_13 client_finished({client_finished_message.cbegin()+4, client_finished_message.cend()}); + hl.prepare_message(client_finished, th); + result.test_eq("hash is updated after client Finished", th.current(), expected_after_client_finished); + }), + }; + } + +std::vector hello_retry_request_handshake() + { + Handshake_Layer hl(Connection_Side::CLIENT); + Transcript_Hash_State th; + + Text_Policy policy("minimum_rsa_bits = 1024"); + + return + { + Botan_Tests::CHECK("client hello 1", [&](auto& result) + { + Client_Hello_13 hello({hrr_client_hello_msg.cbegin()+4, hrr_client_hello_msg.cend()}); + hl.prepare_message(hello, th); + check_transcript_hash_empty(result, th); + }), + + Botan_Tests::CHECK("hello retry request", [&](auto& result) + { + hl.copy_data(hrr_hello_retry_request_msg); + + const auto hrr = hl.next_message(policy, th); + result.confirm("is a Hello Retry Request", has_message(result, hrr)); + + // we now know the algorithm from the Hello Retry Request + // which will not change with the future Server Hello anymore (RFC 8446 4.1.4) + th = Transcript_Hash_State::recreate_after_hello_retry_request("SHA-256", th); + + check_transcript_hash_filled(result, th); + + const auto expected_after_hello_retry_request = Botan::hex_decode( + "74EEC04D09C926E86C0647C37BA4DC18D277EEC3337E4608C4D829B77E2FD2B3"); + + result.test_eq("correct transcript hash produced after hello retry request", th.current(), expected_after_hello_retry_request); + }), + + // ... the rest of the handshake will work just like in full_client_handshake + }; + } + +} // namespace + +namespace Botan_Tests { +BOTAN_REGISTER_TEST_FN("tls", "tls_handshake_layer_13", + read_handshake_messages, prepare_message, full_client_handshake, hello_retry_request_handshake); +} + +#endif diff --git a/src/tests/test_tls_handshake_state_13.cpp b/src/tests/test_tls_handshake_state_13.cpp new file mode 100644 index 00000000000..23149911ee1 --- /dev/null +++ b/src/tests/test_tls_handshake_state_13.cpp @@ -0,0 +1,122 @@ +/* +* (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_13) + +#include +#include + +using namespace Botan::TLS; + +namespace { + +using Test = Botan_Tests::Test; + +const auto client_hello_message = Botan::hex_decode( // from RFC 8448 + "03 03 cb" + "34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12" + "ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00" + "00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01" + "00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02" + "01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d" + "e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d" + "54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e" + "04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02" + "01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"); + +const auto server_hello_message = Botan::hex_decode( + "03 03 a6" + "af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14" + "34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00" + "1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6" + "cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"); + +const auto server_finished_message = Botan::hex_decode( + "9b 9b 14 1d 90 63 37 fb" + "d2 cb dc e7 1d f4 de da 4a b4 2c 30" + "95 72 cb 7f ff ee 54 54 b7 8f 07 18"); + +const auto client_finished_message = Botan::hex_decode( + "a8 ec 43 6d 67 76 34 ae 52 5a c1" + "fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61"); + +std::vector finished_message_handling() + { + return + { + Botan_Tests::CHECK("Client sends and receives Finished messages", [&](auto& result) + { + Client_Handshake_State_13 state; + + Finished_13 client_finished(client_finished_message); + + auto client_fin = state.sent(std::move(client_finished)); + result.require("client can send client finished", + std::holds_alternative>(client_fin)); + result.test_throws("not stored as server Finished", [&] + { + state.server_finished(); + }); + result.test_eq("correct client Finished stored", state.client_finished().serialize(), client_finished_message); + + Finished_13 server_finished(server_finished_message); + + auto server_fin = state.received(std::move(server_finished)); + result.require("client can receive server finished", + std::holds_alternative>(server_fin)); + result.test_eq("correct client Finished stored", state.client_finished().serialize(), client_finished_message); + result.test_eq("correct server Finished stored", state.server_finished().serialize(), server_finished_message); + }), + }; + } + +std::vector handshake_message_filtering() + { + return + { + Botan_Tests::CHECK("Client with client hello", [&](auto& result) + { + Client_Handshake_State_13 state; + + auto filtered = state.sent(Client_Hello_13(client_hello_message)); + result.confirm("client can send client hello", + std::holds_alternative>(filtered)); + + result.test_eq("correct client hello stored", state.client_hello().serialize(), client_hello_message); + + result.template test_throws("client cannot receive client hello", + "received an illegal handshake message", [&] + { + state.received(Client_Hello_13(client_hello_message)); + }); + }), + Botan_Tests::CHECK("Client with server hello", [&](auto& result) + { + Client_Handshake_State_13 state; + + auto server_hello = std::get(Server_Hello_13::parse(server_hello_message)); + + auto filtered = state.received(std::move(server_hello)); + result.confirm("client can receive server hello", + std::holds_alternative>(filtered)); + + result.test_eq("correct server hello stored", state.server_hello().serialize(), server_hello_message); + }), + }; + } + +} // namespace + +namespace Botan_Tests { +BOTAN_REGISTER_TEST_FN("tls", "tls_handshake_state_13", + finished_message_handling, + handshake_message_filtering); +} + +#endif diff --git a/src/tests/test_tls_messages.cpp b/src/tests/test_tls_messages.cpp index 837660e9695..257930d8f01 100644 --- a/src/tests/test_tls_messages.cpp +++ b/src/tests/test_tls_messages.cpp @@ -11,6 +11,7 @@ #include #include #include + #include #include #include #include @@ -18,6 +19,11 @@ #include #include #include +#if defined(BOTAN_HAS_TLS_13) + #include "test_rng.h" + + #include +#endif #endif namespace Botan_Tests { @@ -47,6 +53,20 @@ Test::Result test_hello_verify_request() return result; } +class Test_Callbacks : public Botan::TLS::Callbacks { +public: + Test_Callbacks(Test::Result &result) : m_result(result) {} + +public: + void tls_emit_data(const uint8_t[], size_t) override { m_result.test_failure("unsolicited call to tls_emit_data"); } + void tls_record_received(uint64_t, const uint8_t[], size_t) override { m_result.test_failure("unsolicited call to tls_record_received"); } + void tls_alert(Botan::TLS::Alert) override { m_result.test_failure("unsolicited call to tls_alert"); } + bool tls_session_established(const Botan::TLS::Session&) override { m_result.test_failure("unsolicited call to tls_session_established"); return false; } + +private: + Test::Result &m_result; +}; + class TLS_Message_Parsing_Test final : public Text_Based_Test { public: @@ -229,6 +249,267 @@ class TLS_Message_Parsing_Test final : public Text_Based_Test BOTAN_REGISTER_TEST("tls", "tls_messages", TLS_Message_Parsing_Test); +#if defined(BOTAN_HAS_TLS_13) +class TLS_Key_Share_CH_Generation_Test final : public Text_Based_Test + { + public: + TLS_Key_Share_CH_Generation_Test() + : Text_Based_Test("tls_extensions/generation/key_share_CH_offers.vec", "Groups,Rng_Data,Expected_Content", "Offered_Groups") {} + + Test::Result run_one_test(const std::string& extension, const VarMap& vars) override + { + Test::Result result(extension + " generation"); + + const auto rng_data = vars.get_req_bin("Rng_Data"); + const auto groups = vars.get_req_str("Groups"); + const auto offered_groups = vars.get_opt_str("Offered_Groups", groups); + const auto expected_key_share = vars.get_req_bin("Expected_Content"); + + Test_Callbacks cb(result); + Botan::TLS::Text_Policy policy("key_exchange_groups = " + groups + "\n" + "key_exchange_groups_to_offer = " + offered_groups); + Botan_Tests::Fixed_Output_RNG rng; + rng.add_entropy(rng_data.data(), rng_data.size()); + + Botan::TLS::Key_Share share(policy, cb, rng); + const auto serialized_buffer = share.serialize(Botan::TLS::Connection_Side::CLIENT); + + result.test_eq("key_share_CH_offers test", serialized_buffer, expected_key_share); + + return result; + } + }; + +class TLS_Extension_Parsing_Test final : public Text_Based_Test + { + public: + TLS_Extension_Parsing_Test() + : Text_Based_Test("tls_extensions/parsing", "Buffer,Exception", + "Protocol,Ciphersuite,AdditionalData,Name,Expected_Content") {} + + Test::Result run_one_test(const std::string& extension, const VarMap& vars) override + { + const std::vector buffer = vars.get_req_bin("Buffer"); + const std::vector protocol = vars.get_opt_bin("Protocol"); + const std::vector ciphersuite = vars.get_opt_bin("Ciphersuite"); + const std::string exception = vars.get_req_str("Exception"); + const std::string expected_name = vars.get_opt_str("Name", ""); + const bool is_positive_test = exception.empty(); + + Test::Result result(extension + " parsing"); + + if(is_positive_test) + { + try + { + if(extension == "supported_version") + { + const std::string expected_buffer = Botan::hex_encode(buffer); + Botan::TLS::TLS_Data_Reader tls_data_reader("ClientHello", buffer); + Botan::TLS::Supported_Versions supported_versions(tls_data_reader, static_cast(buffer.size()), + Botan::TLS::Connection_Side::CLIENT); + const auto serialized_buffer = supported_versions.serialize(Botan::TLS::Connection_Side::CLIENT); + + const std::vector> expected_versions = vars.get_req_bin_list("Expected_Content"); + for (const auto& expected_version : expected_versions) + { + result.confirm("Expected_Content", + supported_versions.supports(Botan::TLS::Protocol_Version(expected_version[0], expected_version[1]))); + } + + result.test_eq("supported_version test 1", Botan::hex_encode(serialized_buffer), expected_buffer); + } + else if(extension == "supported_groups") + { + Botan::TLS::TLS_Data_Reader tls_data_reader("ClientHello", buffer); + Botan::TLS::Supported_Groups supp_groups_ext (tls_data_reader, static_cast(buffer.size())); + + const auto serialized_buffer = supp_groups_ext.serialize(Botan::TLS::Connection_Side::CLIENT); + const auto expected_content = vars.get_req_bin("Expected_Content"); + + const auto dh_groups = supp_groups_ext.dh_groups(); + const auto ec_groups = supp_groups_ext.ec_groups(); + + std::vector named_groupes; + std::merge(dh_groups.begin(), dh_groups.end(), ec_groups.begin(), ec_groups.end(), std::back_inserter(named_groupes)); + + result.confirm("supported_groups extension - size check", (named_groupes.size() * 2) == expected_content.size()); + + for(auto i = 0u; i < expected_content.size(); i+=2) { + + const auto expected_named_group = Botan::make_uint16(expected_content.at(i), expected_content.at(i+1)); + + result.confirm("signature_algorithms_cert extension - named group check", + std::any_of(named_groupes.cbegin(), named_groupes.cend(), [&expected_named_group](const Botan::TLS::Named_Group& named_group){ + return static_cast(expected_named_group) == named_group; + })); + } + + result.test_eq("supported_groups extension - serialization test", serialized_buffer, buffer); + } + else if(extension == "signature_algorithms_cert") + { + Botan::TLS::TLS_Data_Reader tls_data_reader("ClientHello", buffer); + Botan::TLS::Signature_Algorithms_Cert sig_algo_cert (tls_data_reader, static_cast(buffer.size())); + + const auto serialized_buffer = sig_algo_cert.serialize(Botan::TLS::Connection_Side::CLIENT); + const auto expected_content = vars.get_req_bin("Expected_Content"); + + result.confirm("signature_algorithms_cert extension - size check", + sig_algo_cert.supported_schemes().size() * 2 == expected_content.size()); + + auto offset = 0u; + for (const auto& sig_scheme : sig_algo_cert.supported_schemes()) { + + const auto expected_sig_scheme = Botan::make_uint16(expected_content.at(offset), expected_content.at(offset+1)); + + result.confirm("signature_algorithms_cert extension - sig scheme check", + static_cast(expected_sig_scheme) == sig_scheme); + + offset += 2; + } + + result.test_eq("signature_algorithms_cert extension - serialization test", serialized_buffer, buffer); + } + else if (extension == "cookie") + { + Botan::TLS::TLS_Data_Reader tls_data_reader("HelloRetryRequest", buffer); + Botan::TLS::Cookie cookie(tls_data_reader, static_cast(buffer.size())); + + const auto serialized_buffer = cookie.serialize(Botan::TLS::Connection_Side::SERVER); + const auto expected_cookie = vars.get_req_bin("Expected_Content"); + + result.test_eq("Cookie extension test", Botan::hex_encode(expected_cookie), Botan::hex_encode(cookie.get_cookie())); + } + else if (extension == "key_share_HRR") + { + Botan::TLS::TLS_Data_Reader tls_data_reader("HelloRetryRequest", buffer); + Botan::TLS::Key_Share key_share(tls_data_reader, static_cast(buffer.size()), Botan::TLS::Handshake_Type::HELLO_RETRY_REQUEST); + + const auto serialized_buffer = key_share.serialize(Botan::TLS::Connection_Side::CLIENT); + const auto expected_key_share = vars.get_req_bin("Expected_Content"); + + result.test_eq("key_share_HRR test", Botan::hex_encode(serialized_buffer), Botan::hex_encode(expected_key_share)); + } + else if (extension == "key_share_SH") + { + Botan::TLS::TLS_Data_Reader tls_data_reader("ServerHello", buffer); + Botan::TLS::Key_Share key_share(tls_data_reader, static_cast(buffer.size()), Botan::TLS::Handshake_Type::SERVER_HELLO); + + const auto serialized_buffer = key_share.serialize(Botan::TLS::Connection_Side::CLIENT); + const auto expected_key_share = vars.get_req_bin("Expected_Content"); + + result.test_eq("key_share_SH test", Botan::hex_encode(serialized_buffer), Botan::hex_encode(expected_key_share)); + } + else if (extension == "key_share_CH") + { + Botan::TLS::TLS_Data_Reader tls_data_reader("ClientHello", buffer); + Botan::TLS::Key_Share key_share(tls_data_reader, static_cast(buffer.size()), Botan::TLS::Handshake_Type::CLIENT_HELLO); + + const auto serialized_buffer = key_share.serialize(Botan::TLS::Connection_Side::SERVER); + const auto expected_key_share = vars.get_req_bin("Expected_Content"); + + result.test_eq("key_share_CH test", Botan::hex_encode(serialized_buffer), Botan::hex_encode(expected_key_share)); + } + else + { + throw Test_Error("Unknown extension type " + extension + " in TLS parsing tests"); + } + result.test_success("Correct parsing"); + } + catch(std::exception& e) + { + result.test_failure(e.what()); + } + } + else + { + } + + return result; + } + + std::vector run_final_tests() override + { + std::vector results; + + results.push_back(test_hello_verify_request()); + + return results; + } + }; + +BOTAN_REGISTER_TEST("tls_extensions", "tls_extensions_parsing", TLS_Extension_Parsing_Test); +BOTAN_REGISTER_TEST("tls_extensions", "tls_extensions_key_share_client_hello", TLS_Key_Share_CH_Generation_Test); + +class TLS_13_Message_Parsing_Test final : public Text_Based_Test + { + public: + TLS_13_Message_Parsing_Test() + : Text_Based_Test("tls_13", "Buffer,Exception", + "Protocol,Message_Type,AdditionalData,Ciphersuite,Name") {} + + Test::Result run_one_test(const std::string& algo, const VarMap& vars) override + { + const std::vector buffer = vars.get_req_bin("Buffer"); + const std::vector protocol = vars.get_opt_bin("Protocol"); + const std::string msg_type = vars.get_opt_str("Message_Type", ""); + const std::vector ciphersuite = vars.get_opt_bin("Ciphersuite"); + const std::string exception = vars.get_req_str("Exception"); + const bool is_positive_test = exception.empty(); + + Test::Result result("TLS 1.3 " + algo + " parsing"); + + if(algo == "server_hello") + { + const std::string extensions = vars.get_req_str("AdditionalData"); + const Botan::TLS::Ciphersuite cs = Botan::TLS::Ciphersuite::by_id(Botan::make_uint16(ciphersuite[0], ciphersuite[1])).value(); + const Botan::TLS::Protocol_Version pv(protocol[0], protocol[1]); + + try + { + std::visit([&](auto msg) { + if constexpr(std::is_same_v) + { + result.confirm("expected Server_Hello_12", msg_type == "server_hello_12"); + result.confirm("expected pre TLS 1.3 message", pv == msg.legacy_version()); + } + else if constexpr(std::is_same_v) + { + result.confirm("expected Server_Hello_13", msg_type == "server_hello_13"); + } + else if constexpr(std::is_same_v) + { + result.confirm("expected Hello_Retry_Request", msg_type == "hello_retry_request"); + } + + result.confirm("Ciphersuite", (msg.ciphersuite() == cs.ciphersuite_code())); + + std::vector buf; + for(Botan::TLS::Handshake_Extension_Type const& type : msg.extensions().extension_types()) + { + uint16_t u16type = static_cast(type); + buf.push_back(Botan::get_byte<0>(u16type)); + buf.push_back(Botan::get_byte<1>(u16type)); + } + result.test_eq("Hello extensions", Botan::hex_encode(buf), extensions); + }, Botan::TLS::Server_Hello_13::parse(buffer)); + } + catch(const std::exception &ex) + { + result.test_eq("correct error produced", ex.what(), exception); + result.require("negative test", !is_positive_test); + } + } + + return result; + } + }; + +BOTAN_REGISTER_TEST("tls", "tls_13_messages", TLS_13_Message_Parsing_Test); + +#endif + #endif } diff --git a/src/tests/test_tls_record_layer_13.cpp b/src/tests/test_tls_record_layer_13.cpp new file mode 100644 index 00000000000..5a26e0578f9 --- /dev/null +++ b/src/tests/test_tls_record_layer_13.cpp @@ -0,0 +1,978 @@ +/* +* (C) 2021 Jack Lloyd +* (C) 2021 Hannes Rantzsch, René Meusel - neXenio +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + +#if defined(BOTAN_HAS_TLS_13) + +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +namespace TLS = Botan::TLS; +using Test = Botan_Tests::Test; + +using Records = std::vector; + +TLS::Record_Layer record_layer_client(const bool skip_initial_record=false) + { + auto rl = TLS::Record_Layer(TLS::Connection_Side::CLIENT); + + // this is relevant for tests that rely on the legacy version in the record + if(skip_initial_record) + rl.prepare_records(TLS::Record_Type::HANDSHAKE, {0, 0, 0}); + + return rl; + } + +TLS::Record_Layer record_layer_server(const bool skip_initial_record=false) + { + auto rl = TLS::Record_Layer(TLS::Connection_Side::SERVER); + + // this is relevant for tests that rely on the legacy version in the record + if(skip_initial_record) + { + rl.copy_data(Botan::hex_decode("16 03 01 00 03 00 00 00")); + rl.next_record(); // result is ignored + } + + return rl; + } + +std::unique_ptr rfc8448_rtt1_handshake_traffic(Botan::TLS::Connection_Side side = Botan::TLS::Connection_Side::CLIENT) + { + const auto transcript_hash = Botan::hex_decode( + "86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed" + "d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8"); + auto shared_secret = Botan::hex_decode_locked( + "8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d" + "35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d"); + auto cipher = TLS::Ciphersuite::from_name("AES_128_GCM_SHA256").value(); + return TLS::Cipher_State::init_with_server_hello( + side, std::move(shared_secret), cipher, transcript_hash); + } + +std::vector read_full_records() + { + const auto client_hello_record = Botan::hex_decode( // from RFC 8448 + "16 03 01 00 c4 01 00 00 c0 03 03 cb" + "34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12" + "ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00" + "00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01" + "00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02" + "01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d" + "e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d" + "54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e" + "04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02" + "01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"); + const auto ccs_record = Botan::hex_decode("14 03 03 00 01 01"); + + return + { + Botan_Tests::CHECK("change cipher spec", [&](auto& result) + { + auto rl = record_layer_server(); + + rl.copy_data(ccs_record); + auto read = rl.next_record(); + result.require("received something", std::holds_alternative(read)); + + auto record = std::get(read); + result.confirm("received CCS", record.type == TLS::Record_Type::CHANGE_CIPHER_SPEC); + result.test_eq("CCS byte is 0x01", record.fragment, Botan::hex_decode("01")); + + result.confirm("no more records", std::holds_alternative(rl.next_record())); + }), + + Botan_Tests::CHECK("two CCS messages", [&](auto& result) + { + const auto two_ccs_records = Botan::concat(ccs_record, ccs_record); + + auto rl = record_layer_server(); + + rl.copy_data(two_ccs_records); + + auto read = rl.next_record(); + result.require("received something", std::holds_alternative(read)); + auto record = std::get(read); + + result.confirm("received CCS 1", record.type == TLS::Record_Type::CHANGE_CIPHER_SPEC); + result.test_eq("CCS byte is 0x01", record.fragment, Botan::hex_decode("01")); + + read = rl.next_record(); + result.require("received something", std::holds_alternative(read)); + record = std::get(read); + + result.confirm("received CCS 2", record.type == TLS::Record_Type::CHANGE_CIPHER_SPEC); + result.test_eq("CCS byte is 0x01", record.fragment, Botan::hex_decode("01")); + + result.confirm("no more records", std::holds_alternative(rl.next_record())); + }), + + Botan_Tests::CHECK("read full handshake message", [&](auto& result) + { + auto rl = record_layer_server(); + rl.copy_data(client_hello_record); + + auto read = rl.next_record(); + result.confirm("received something", std::holds_alternative(read)); + + auto rec = std::get(read); + result.confirm("received handshake record", rec.type == TLS::Record_Type::HANDSHAKE); + result.test_eq("contains the full handshake message", + Botan::secure_vector(client_hello_record.begin()+TLS::TLS_HEADER_SIZE, + client_hello_record.end()), rec.fragment); + + result.confirm("no more records", std::holds_alternative(rl.next_record())); + }), + + Botan_Tests::CHECK("read full handshake message followed by CCS", [&](auto& result) + { + const auto payload = Botan::concat(client_hello_record, ccs_record); + + auto rl = record_layer_server(); + rl.copy_data(payload); + + auto read = rl.next_record(); + result.require("received something", std::holds_alternative(read)); + + auto rec = std::get(read); + result.confirm("received handshake record", rec.type == TLS::Record_Type::HANDSHAKE); + result.test_eq("contains the full handshake message", + Botan::secure_vector(client_hello_record.begin()+TLS::TLS_HEADER_SIZE, + client_hello_record.end()), rec.fragment); + + read = rl.next_record(); + result.require("received something", std::holds_alternative(read)); + + rec = std::get(read); + result.confirm("received CCS record", rec.type == TLS::Record_Type::CHANGE_CIPHER_SPEC); + result.test_eq("CCS byte is 0x01", rec.fragment, Botan::hex_decode("01")); + + result.confirm("no more records", std::holds_alternative(rl.next_record())); + }) + }; + } + +std::vector basic_sanitization_parse_records(TLS::Connection_Side side) + { + auto parse_records = [side](const std::vector& data, TLS::Cipher_State* cs=nullptr) + { + auto rl = ((side == TLS::Connection_Side::CLIENT) ? record_layer_client(true) : record_layer_server()); + rl.copy_data(data); + return rl.next_record(cs); + }; + + return + { + Botan_Tests::CHECK("'receive' empty data", [&](auto& result) + { + auto read = parse_records({}); + result.require("needs bytes", std::holds_alternative(read)); + result.test_eq("need all the header bytes", + std::get(read), Botan::TLS::TLS_HEADER_SIZE); + }), + + Botan_Tests::CHECK("incomplete header asks for more data", [&](auto& result) + { + std::vector partial_header{'\x23', '\x03', '\x03'}; + auto read = parse_records(partial_header); + result.require("returned 'bytes needed'", std::holds_alternative(read)); + + result.test_eq("asks for some more bytes", std::get(read), + Botan::TLS::TLS_HEADER_SIZE - partial_header.size()); + }), + + Botan_Tests::CHECK("complete header asks for enough data to finish processing the record", [&](auto& result) + { + std::vector full_header{'\x17', '\x03', '\x03', '\x00', '\x42'}; + auto read = parse_records(full_header); + result.require("returned 'bytes needed'", std::holds_alternative(read)); + + result.test_eq("asks for many more bytes", std::get(read), 0x42); + }), + + Botan_Tests::CHECK("received an empty record (that is not application data)", [&](auto& result) + { + std::vector empty_record{'\x16', '\x03', '\x03', '\x00', '\x00'}; + result.test_throws("record empty", "empty record received", [&] + { + parse_records(empty_record); + }); + }), + + Botan_Tests::CHECK("received the maximum size of an unprotected record", [&](auto& result) + { + std::vector full_record{'\x16', '\x03', '\x03', '\x40', '\x00'}; + full_record.resize(TLS::MAX_PLAINTEXT_SIZE + TLS::TLS_HEADER_SIZE); + auto read = parse_records(full_record); + result.confirm("returned 'record'", !std::holds_alternative(read)); + }), + + Botan_Tests::CHECK("received too many bytes in one protected record", [&](auto& result) + { + std::vector huge_record{'\x17', '\x03', '\x03', '\x41', '\x01'}; + huge_record.resize(TLS::MAX_CIPHERTEXT_SIZE_TLS13 + TLS::TLS_HEADER_SIZE + 1); + result.test_throws("record too big", "Received an encrypted record that exceeds maximum size", [&] + { + parse_records(huge_record); + }); + }), + + Botan_Tests::CHECK("decryption would result in too large plaintext", [&](auto& result) + { + // In this case the ciphertext is within the allowed bounds, but the + // decrypted plaintext would be too large. + std::vector huge_record{'\x17', '\x03', '\x03', '\x40', '\x12'}; + huge_record.resize(TLS::MAX_PLAINTEXT_SIZE + + TLS::TLS_HEADER_SIZE + + 16 /* AES-GCM tag */ + + 1 /* encrypted type */ + + 1 /* illegal */); + + auto cs = rfc8448_rtt1_handshake_traffic(); + result.test_throws("record too big", "Received an encrypted record that exceeds maximum plaintext size", [&] + { + parse_records(huge_record, cs.get()); + }); + }), + + Botan_Tests::CHECK("received too many bytes in one unprotected record", [&](auto& result) + { + std::vector huge_record{'\x16', '\x03', '\x03', '\x40', '\x01'}; + huge_record.resize(TLS::MAX_PLAINTEXT_SIZE + TLS::TLS_HEADER_SIZE + 1); + result.test_throws("record too big", "Received a record that exceeds maximum size", [&] + { + parse_records(huge_record); + }); + }), + + Botan_Tests::CHECK("invalid record type", [&](auto& result) + { + std::vector invalid_record_type{'\x42', '\x03', '\x03', '\x41', '\x01'}; + result.test_throws("invalid record type", "TLS record type had unexpected value", [&] + { + parse_records(invalid_record_type); + }); + }), + + Botan_Tests::CHECK("invalid record version", [&](auto& result) + { + std::vector invalid_record_version{'\x17', '\x13', '\x37', '\x00', '\x01', '\x42'}; + result.test_throws("invalid record version", "Received unexpected record version", [&] + { + parse_records(invalid_record_version); + }); + }), + + Botan_Tests::CHECK("initial received record version might be 0x03XX ", [&](auto& result) + { + auto rl = record_layer_client(true); + rl.copy_data({0x16, 0x03, 0x00, 0x00, 0x01, 0x42}); + result.test_no_throw("0x03 0x00 should be fine for first record", [&] { rl.next_record(); }); + + rl.copy_data({0x16, 0x03, 0x00, 0x00, 0x01, 0x42}); + result.test_throws("0x03 0x00 not okay for any other record", [&] { rl.next_record(); }); + }), + + Botan_Tests::CHECK("malformed change cipher spec", [&](auto& result) + { + std::vector invalid_ccs_record{'\x14', '\x03', '\x03', '\x00', '\x01', '\x02'}; + result.test_throws("invalid CCS record", "malformed change cipher spec record received", [&] + { + parse_records(invalid_ccs_record); + }); + }) + + }; + } + +std::vector basic_sanitization_parse_records_client() + { + return basic_sanitization_parse_records(TLS::Connection_Side::CLIENT); + } + +std::vector basic_sanitization_parse_records_server() + { + return basic_sanitization_parse_records(TLS::Connection_Side::SERVER); + } + +std::vector read_fragmented_records() + { + TLS::Record_Layer rl = record_layer_client(true); + + auto wait_for_more_bytes = [](Botan::TLS::BytesNeeded bytes_needed, + auto& record_layer, + std::vector bytes, + auto& result) + { + record_layer.copy_data(bytes); + const auto rlr = record_layer.next_record(); + if(result.confirm("waiting for bytes", std::holds_alternative(rlr))) + { result.test_eq("right amount", std::get(rlr), bytes_needed); } + }; + + return + { + Botan_Tests::CHECK("change cipher spec in many small pieces", [&](auto& result) + { + std::vector ccs_record{'\x14', '\x03', '\x03', '\x00', '\x01', '\x01'}; + + wait_for_more_bytes(4, rl, {'\x14'}, result); + wait_for_more_bytes(3, rl, {'\x03'}, result); + wait_for_more_bytes(2, rl, {'\x03'}, result); + wait_for_more_bytes(1, rl, {'\x00'}, result); + wait_for_more_bytes(1, rl, {'\x01'}, result); + + rl.copy_data({'\x01'}); + auto res1 = rl.next_record(); + result.require("received something 1", std::holds_alternative(res1)); + + auto rec1 = std::get(res1); + result.confirm("received CCS", rec1.type == TLS::Record_Type::CHANGE_CIPHER_SPEC); + result.test_eq("CCS byte is 0x01", rec1.fragment, Botan::hex_decode("01")); + + result.confirm("no more records", std::holds_alternative(rl.next_record())); + }), + + Botan_Tests::CHECK("two change cipher specs in several pieces", [&](auto& result) + { + wait_for_more_bytes(1, rl, {'\x14', '\x03', '\x03', '\x00'}, result); + + rl.copy_data({'\x01', '\x01', /* second CCS starts here */ '\x14', '\x03'}); + + auto res2 = rl.next_record(); + result.require("received something 2", std::holds_alternative(res2)); + + auto rec2 = std::get(res2); + result.confirm("received CCS", rec2.type == TLS::Record_Type::CHANGE_CIPHER_SPEC); + result.confirm("demands more bytes", std::holds_alternative(rl.next_record())); + + wait_for_more_bytes(2, rl, {'\x03'}, result); + + rl.copy_data({'\x00', '\x01', '\x01'}); + auto res3 = rl.next_record(); + result.require("received something 3", std::holds_alternative(res3)); + + auto rec3 = std::get(res3); + result.confirm("received CCS", rec3.type == TLS::Record_Type::CHANGE_CIPHER_SPEC); + + result.confirm("no more records", std::holds_alternative(rl.next_record())); + }) + }; + } + +std::vector write_records() + { + auto cs = rfc8448_rtt1_handshake_traffic(); + return + { + Botan_Tests::CHECK("prepare an zero-length application data fragment", [&](auto& result) + { + auto record = record_layer_client().prepare_records(Botan::TLS::APPLICATION_DATA, {}, cs.get()); + + result.require("record header was added", record.size() > Botan::TLS::TLS_HEADER_SIZE + 1 /* encrypted content type */); + }), + Botan_Tests::CHECK("prepare a client hello", [&](auto& result) + { + const auto client_hello_msg = Botan::hex_decode( // from RFC 8448 + "01 00 00 c0 03 03 cb" + "34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12" + "ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00" + "00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01" + "00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02" + "01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d" + "e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d" + "54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e" + "04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02" + "01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"); + auto record = record_layer_client().prepare_records(Botan::TLS::HANDSHAKE, client_hello_msg); + + result.require("record header was added", record.size() == client_hello_msg.size() + Botan::TLS::TLS_HEADER_SIZE); + + const auto header = std::vector(record.cbegin(), record.cbegin() + Botan::TLS::TLS_HEADER_SIZE); + result.test_eq("record header is well-formed", header, Botan::hex_decode("16030100c4")); + }), + Botan_Tests::CHECK("prepare a dummy CCS", [&](auto& result) + { + auto record = record_layer_client(true).prepare_records(Botan::TLS::Record_Type::CHANGE_CIPHER_SPEC, {0x01});; + result.require("record was created", record.size() == Botan::TLS::TLS_HEADER_SIZE + 1); + + result.test_eq("CCS record is well-formed", record, Botan::hex_decode("140303000101")); + }), + Botan_Tests::CHECK("cannot prepare non-dummy CCS", [&](auto& result) + { + result.test_throws("cannot create non-dummy CCS", "TLS 1.3 deprecated CHANGE_CIPHER_SPEC", [] + { + const auto ccs_content = Botan::hex_decode("de ad be ef"); + record_layer_client().prepare_records(Botan::TLS::Record_Type::CHANGE_CIPHER_SPEC, ccs_content); + }); + }), + Botan_Tests::CHECK("large messages are sharded", [&](auto& result) + { + const std::vector large_client_hello(Botan::TLS::MAX_PLAINTEXT_SIZE + 4096); + auto record = record_layer_client().prepare_records(Botan::TLS::HANDSHAKE, large_client_hello); + + result.test_gte("produces at least two record headers", record.size(), + large_client_hello.size() + 2 * Botan::TLS::TLS_HEADER_SIZE); + }) + }; + } + +std::vector +read_encrypted_records() + { + // this is the "complete record" server hello portion + // from RFC 8448 page 7 + const auto server_hello = Botan::hex_decode( + "16 03 03 00 5a 02 00 00 56 03 03 a6" + "af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14" + "34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00" + "1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6" + "cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"); + + // this is the "complete record" encrypted server hello portion + // from RFC 8448 page 9 + const auto encrypted_record = Botan::hex_decode( + "17 03 03 02 a2 d1 ff 33 4a 56 f5 bf" + "f6 59 4a 07 cc 87 b5 80 23 3f 50 0f 45 e4 89 e7 f3 3a f3 5e df" + "78 69 fc f4 0a a4 0a a2 b8 ea 73 f8 48 a7 ca 07 61 2e f9 f9 45" + "cb 96 0b 40 68 90 51 23 ea 78 b1 11 b4 29 ba 91 91 cd 05 d2 a3" + "89 28 0f 52 61 34 aa dc 7f c7 8c 4b 72 9d f8 28 b5 ec f7 b1 3b" + "d9 ae fb 0e 57 f2 71 58 5b 8e a9 bb 35 5c 7c 79 02 07 16 cf b9" + "b1 18 3e f3 ab 20 e3 7d 57 a6 b9 d7 47 76 09 ae e6 e1 22 a4 cf" + "51 42 73 25 25 0c 7d 0e 50 92 89 44 4c 9b 3a 64 8f 1d 71 03 5d" + "2e d6 5b 0e 3c dd 0c ba e8 bf 2d 0b 22 78 12 cb b3 60 98 72 55" + "cc 74 41 10 c4 53 ba a4 fc d6 10 92 8d 80 98 10 e4 b7 ed 1a 8f" + "d9 91 f0 6a a6 24 82 04 79 7e 36 a6 a7 3b 70 a2 55 9c 09 ea d6" + "86 94 5b a2 46 ab 66 e5 ed d8 04 4b 4c 6d e3 fc f2 a8 94 41 ac" + "66 27 2f d8 fb 33 0e f8 19 05 79 b3 68 45 96 c9 60 bd 59 6e ea" + "52 0a 56 a8 d6 50 f5 63 aa d2 74 09 96 0d ca 63 d3 e6 88 61 1e" + "a5 e2 2f 44 15 cf 95 38 d5 1a 20 0c 27 03 42 72 96 8a 26 4e d6" + "54 0c 84 83 8d 89 f7 2c 24 46 1a ad 6d 26 f5 9e ca ba 9a cb bb" + "31 7b 66 d9 02 f4 f2 92 a3 6a c1 b6 39 c6 37 ce 34 31 17 b6 59" + "62 22 45 31 7b 49 ee da 0c 62 58 f1 00 d7 d9 61 ff b1 38 64 7e" + "92 ea 33 0f ae ea 6d fa 31 c7 a8 4d c3 bd 7e 1b 7a 6c 71 78 af" + "36 87 90 18 e3 f2 52 10 7f 24 3d 24 3d c7 33 9d 56 84 c8 b0 37" + "8b f3 02 44 da 8c 87 c8 43 f5 e5 6e b4 c5 e8 28 0a 2b 48 05 2c" + "f9 3b 16 49 9a 66 db 7c ca 71 e4 59 94 26 f7 d4 61 e6 6f 99 88" + "2b d8 9f c5 08 00 be cc a6 2d 6c 74 11 6d bd 29 72 fd a1 fa 80" + "f8 5d f8 81 ed be 5a 37 66 89 36 b3 35 58 3b 59 91 86 dc 5c 69" + "18 a3 96 fa 48 a1 81 d6 b6 fa 4f 9d 62 d5 13 af bb 99 2f 2b 99" + "2f 67 f8 af e6 7f 76 91 3f a3 88 cb 56 30 c8 ca 01 e0 c6 5d 11" + "c6 6a 1e 2a c4 c8 59 77 b7 c7 a6 99 9b bf 10 dc 35 ae 69 f5 51" + "56 14 63 6c 0b 9b 68 c1 9e d2 e3 1c 0b 3b 66 76 30 38 eb ba 42" + "f3 b3 8e dc 03 99 f3 a9 f2 3f aa 63 97 8c 31 7f c9 fa 66 a7 3f" + "60 f0 50 4d e9 3b 5b 84 5e 27 55 92 c1 23 35 ee 34 0b bc 4f dd" + "d5 02 78 40 16 e4 b3 be 7e f0 4d da 49 f4 b4 40 a3 0c b5 d2 af" + "93 98 28 fd 4a e3 79 4e 44 f9 4d f5 a6 31 ed e4 2c 17 19 bf da" + "bf 02 53 fe 51 75 be 89 8e 75 0e dc 53 37 0d 2b"); + + // the record above padded with 42 zeros + const auto encrypted_record_with_padding = Botan::hex_decode( + "17 03 03 02 cc d1 ff 33 4a 56 f5 bf f6 59 4a 07 cc 87 b5 80 23 3f 50 0f 45" + "e4 89 e7 f3 3a f3 5e df 78 69 fc f4 0a a4 0a a2 b8 ea 73 f8 48 a7 ca 07 61" + "2e f9 f9 45 cb 96 0b 40 68 90 51 23 ea 78 b1 11 b4 29 ba 91 91 cd 05 d2 a3" + "89 28 0f 52 61 34 aa dc 7f c7 8c 4b 72 9d f8 28 b5 ec f7 b1 3b d9 ae fb 0e" + "57 f2 71 58 5b 8e a9 bb 35 5c 7c 79 02 07 16 cf b9 b1 18 3e f3 ab 20 e3 7d" + "57 a6 b9 d7 47 76 09 ae e6 e1 22 a4 cf 51 42 73 25 25 0c 7d 0e 50 92 89 44" + "4c 9b 3a 64 8f 1d 71 03 5d 2e d6 5b 0e 3c dd 0c ba e8 bf 2d 0b 22 78 12 cb" + "b3 60 98 72 55 cc 74 41 10 c4 53 ba a4 fc d6 10 92 8d 80 98 10 e4 b7 ed 1a" + "8f d9 91 f0 6a a6 24 82 04 79 7e 36 a6 a7 3b 70 a2 55 9c 09 ea d6 86 94 5b" + "a2 46 ab 66 e5 ed d8 04 4b 4c 6d e3 fc f2 a8 94 41 ac 66 27 2f d8 fb 33 0e" + "f8 19 05 79 b3 68 45 96 c9 60 bd 59 6e ea 52 0a 56 a8 d6 50 f5 63 aa d2 74" + "09 96 0d ca 63 d3 e6 88 61 1e a5 e2 2f 44 15 cf 95 38 d5 1a 20 0c 27 03 42" + "72 96 8a 26 4e d6 54 0c 84 83 8d 89 f7 2c 24 46 1a ad 6d 26 f5 9e ca ba 9a" + "cb bb 31 7b 66 d9 02 f4 f2 92 a3 6a c1 b6 39 c6 37 ce 34 31 17 b6 59 62 22" + "45 31 7b 49 ee da 0c 62 58 f1 00 d7 d9 61 ff b1 38 64 7e 92 ea 33 0f ae ea" + "6d fa 31 c7 a8 4d c3 bd 7e 1b 7a 6c 71 78 af 36 87 90 18 e3 f2 52 10 7f 24" + "3d 24 3d c7 33 9d 56 84 c8 b0 37 8b f3 02 44 da 8c 87 c8 43 f5 e5 6e b4 c5" + "e8 28 0a 2b 48 05 2c f9 3b 16 49 9a 66 db 7c ca 71 e4 59 94 26 f7 d4 61 e6" + "6f 99 88 2b d8 9f c5 08 00 be cc a6 2d 6c 74 11 6d bd 29 72 fd a1 fa 80 f8" + "5d f8 81 ed be 5a 37 66 89 36 b3 35 58 3b 59 91 86 dc 5c 69 18 a3 96 fa 48" + "a1 81 d6 b6 fa 4f 9d 62 d5 13 af bb 99 2f 2b 99 2f 67 f8 af e6 7f 76 91 3f" + "a3 88 cb 56 30 c8 ca 01 e0 c6 5d 11 c6 6a 1e 2a c4 c8 59 77 b7 c7 a6 99 9b" + "bf 10 dc 35 ae 69 f5 51 56 14 63 6c 0b 9b 68 c1 9e d2 e3 1c 0b 3b 66 76 30" + "38 eb ba 42 f3 b3 8e dc 03 99 f3 a9 f2 3f aa 63 97 8c 31 7f c9 fa 66 a7 3f" + "60 f0 50 4d e9 3b 5b 84 5e 27 55 92 c1 23 35 ee 34 0b bc 4f dd d5 02 78 40" + "16 e4 b3 be 7e f0 4d da 49 f4 b4 40 a3 0c b5 d2 af 93 98 28 fd 4a e3 79 4e" + "44 f9 4d f5 a6 31 ed e4 2c 17 19 bf da 04 d8 68 77 bb e0 dc ce f9 01 ed 32" + "59 50 7a 0c d0 62 3f 90 1b 5c 89 d4 b4 f2 d1 56 f6 da 4f 3e c5 fd 2d e5 e2" + "fa 44 23 0a e0 c9 dd dd bb a8 be db d9 d7 f6 b8 3d 56 4c a5 47"); + + auto parse_records = [](const std::vector& data) + { + auto rl = record_layer_client(true); + rl.copy_data(data); + return rl; + }; + + return + { + Botan_Tests::CHECK("read encrypted server hello extensions", [&](Test::Result &result) + { + auto cs = rfc8448_rtt1_handshake_traffic(); + auto rl = parse_records(encrypted_record); + + auto res = rl.next_record(cs.get()); + result.require("some records decrypted", !std::holds_alternative(res)); + auto record = std::get(res); + + result.test_is_eq("inner type was 'HANDSHAKE'", record.type, Botan::TLS::Record_Type::HANDSHAKE); + result.test_eq("decrypted payload length", record.fragment.size(), 657 /* taken from RFC 8448 */); + + result.confirm("no more records", std::holds_alternative(rl.next_record())); + }), + + Botan_Tests::CHECK("premature application data", [&](Test::Result &result) + { + auto rl = record_layer_client(true); + rl.copy_data(encrypted_record); + + result.test_throws("cannot process encrypted data with uninitialized cipher state", + "premature Application Data received", [&] + { + auto res = rl.next_record(nullptr); + }); + }), + + Botan_Tests::CHECK("decryption fails due to bad MAC", [&](Test::Result &result) + { + auto tampered_encrypted_record = encrypted_record; + tampered_encrypted_record.back() = '\x42'; // changing one payload byte causes the MAC check to fails + + result.test_throws("broken record detected", [&] + { + auto cs = rfc8448_rtt1_handshake_traffic(); + auto rl = parse_records(tampered_encrypted_record); + rl.next_record(cs.get()); + }); + }), + + Botan_Tests::CHECK("decryption fails due to too short record", [&](Test::Result &result) + { + const auto short_record = Botan::hex_decode("17 03 03 00 08 de ad be ef ba ad f0 0d"); + + result.test_throws("too short to decrypt", [&] + { + auto cs = rfc8448_rtt1_handshake_traffic(); + auto rl = parse_records(short_record); + rl.next_record(cs.get()); + }); + }), + + Botan_Tests::CHECK("protected Change Cipher Spec message is illegal", [&](Test::Result& result) + { + // factored message, encrypted under the same key as `encrypted_record` + const auto protected_ccs = Botan::hex_decode("1703030012D8EBBBE055C8167D5690EC67DEA9A525B036"); + + result.test_throws("illegal state causes TLS alert", + "protected change cipher spec received", [&] + { + auto cs = rfc8448_rtt1_handshake_traffic(); + auto rl = parse_records(protected_ccs); + rl.next_record(cs.get()); + }); + }), + + Botan_Tests::CHECK("unprotected CCS is legal when encrypted traffic is expected", [&](Test::Result& result) + { + const auto ccs_record = Botan::hex_decode("14 03 03 00 01 01"); + + result.test_no_throw("CCS is acceptable", [&] + { + auto cs = rfc8448_rtt1_handshake_traffic(); // expect encrypted traffic + auto rl = parse_records(ccs_record); + rl.next_record(cs.get()); + }); + }), + + Botan_Tests::CHECK("unprotected traffic is illegal when encrypted traffic is expected", [&](Test::Result& result) + { + result.test_throws("unprotected record is unacceptable", [&] + { + auto cs = rfc8448_rtt1_handshake_traffic(); // expect encrypted traffic + auto rl = parse_records(server_hello ); + rl.next_record(cs.get()); + }); + }), + + Botan_Tests::CHECK("read fragmented application data", [&](Test::Result& result) + { + const auto encrypted = Botan::hex_decode( + "17 03 03 00 1A 90 78 6D 7E 6F A8 F7 67 1F 6D 05 F7 24 18 F5 DB 43 F7 0B 9E 48 A6 96 B6 5B EC" + "17 03 03 00 28 6C 21 B5 B8 D8 1B 85 5C 17 0E C7 9B 2C 28 85 85 51 29 2F 71 14 F3 D7 BD D5 D1" + "80 C2 E9 3D EC 84 3B 8D 41 30 D8 C8 C5 D8" + "17 03 03 00 21 29 9A B0 5A EA 3F 8A DE 05 12 E0 6B 4A 28 C3 E2 69 2F 58 82 F1 A3 45 04 EA 16" + "14 72 39 6F A1 F3 D3 "); + const std::vector> plaintext_records = + { + Botan::hex_decode("00 01 02 03 04 05 06 07 08"), + Botan::hex_decode("09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f"), + Botan::hex_decode("20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f") + }; + + auto cs = rfc8448_rtt1_handshake_traffic(); + // advance with arbitrary hashes that were used to produce the input data + cs->advance_with_server_finished( + Botan::hex_decode("e1935a480babfc4403b2517f0ad414bed0ca51fa671e2061804afa78fd71d55c")); + cs->advance_with_client_finished( + Botan::hex_decode("305e4a0a7cee581b282c571b251b20138a1a6a21918937a6bb95b1e9ba1b5cac")); + + auto rl = parse_records(encrypted); + auto res = rl.next_record(cs.get()); + result.require("decrypted a record", std::holds_alternative(res)); + auto records = std::get(res); + result.test_eq("first record", records.fragment, plaintext_records.at(0)); + + res = rl.next_record(cs.get()); + result.require("decrypted a record", std::holds_alternative(res)); + records = std::get(res); + result.test_eq("second record", records.fragment, plaintext_records.at(1)); + + res = rl.next_record(cs.get()); + result.require("decrypted a record", std::holds_alternative(res)); + records = std::get(res); + result.test_eq("third record", records.fragment, plaintext_records.at(2)); + + result.confirm("no more records", std::holds_alternative(rl.next_record())); + }), + + Botan_Tests::CHECK("read coalesced server hello and encrypted extensions", [&](Test::Result& result) + { + // contains the plaintext server hello and the encrypted extensions in one go + auto coalesced = server_hello; + coalesced.insert(coalesced.end(), encrypted_record.cbegin(), encrypted_record.cend()); + + auto client = record_layer_client(true); + client.copy_data(coalesced); + + const auto srv_hello = client.next_record(nullptr); + result.confirm("read a record", std::holds_alternative(srv_hello)); + result.confirm("is handshake record", std::get(srv_hello).type == TLS::HANDSHAKE); + + auto cs = rfc8448_rtt1_handshake_traffic(); + const auto enc_exts = client.next_record(cs.get()); + result.confirm("read a record", std::holds_alternative(enc_exts)); + result.confirm("is handshake record", std::get(enc_exts).type == TLS::HANDSHAKE); + }), + + Botan_Tests::CHECK("read a padded record", [&](Test::Result& result) + { + auto client = record_layer_client(true); + client.copy_data(encrypted_record_with_padding); + + auto cs = rfc8448_rtt1_handshake_traffic(); + const auto record = client.next_record(cs.get()); + result.confirm("read a record with padding", std::holds_alternative(record)); + }), + + Botan_Tests::CHECK("read an empty encrypted record", [&](Test::Result& result) + { + auto client = record_layer_client(true); + client.copy_data(Botan::hex_decode("1703030011CE43CA0D2F28336715E770071B2D5EE0FE")); + + auto cs = rfc8448_rtt1_handshake_traffic(); + const auto record = client.next_record(cs.get()); + result.confirm("read an empty record", std::holds_alternative(record)); + }) + }; + } + +std::vector write_encrypted_records() + { + auto plaintext_msg = Botan::hex_decode( + "14 00 00 20 a8 ec 43 6d 67 76 34 ae" + "52 5a c1 fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61"); + + auto cs = rfc8448_rtt1_handshake_traffic(); + return + { + Botan_Tests::CHECK("write encrypted client handshake finished", [&](Test::Result& result) + { + auto ct = record_layer_client(true).prepare_records(TLS::Record_Type::HANDSHAKE, + plaintext_msg, cs.get()); + auto expected_ct = + Botan::hex_decode("17 03 03 00 35 75 ec 4d c2 38 cc e6" + "0b 29 80 44 a7 1e 21 9c 56 cc 77 b0 51 7f e9 b9 3c 7a 4b fc 44 d8 7f" + "38 f8 03 38 ac 98 fc 46 de b3 84 bd 1c ae ac ab 68 67 d7 26 c4 05 46"); + result.test_eq("produced the expected ciphertext", ct, expected_ct); + }), + + Botan_Tests::CHECK("write a dummy CCS (that must not be encrypted)", [&](auto& result) + { + auto record = record_layer_client(true).prepare_records(Botan::TLS::Record_Type::CHANGE_CIPHER_SPEC, {0x01}, cs.get());; + result.require("record was created and not encrypted", record.size() == Botan::TLS::TLS_HEADER_SIZE + 1); + + result.test_eq("CCS record is well-formed", record, Botan::hex_decode("140303000101")); + }), + + Botan_Tests::CHECK("write a lot of data producing two protected records", [&](Test::Result& result) + { + std::vector big_data(TLS::MAX_PLAINTEXT_SIZE + TLS::MAX_PLAINTEXT_SIZE / 2); + auto ct = record_layer_client(true).prepare_records(TLS::Record_Type::APPLICATION_DATA, + big_data, cs.get()); + result.require("encryption added some MAC and record headers", + ct.size() > big_data.size() + Botan::TLS::TLS_HEADER_SIZE * 2); + + auto read_record_header = [&](auto &reader) + { + result.test_is_eq("APPLICATION_DATA", reader.get_byte(), static_cast(TLS::Record_Type::APPLICATION_DATA)); + result.test_is_eq("TLS legacy version", reader.get_uint16_t(), uint16_t(0x0303)); + + const auto fragment_length = reader.get_uint16_t(); + result.test_lte("TLS limts", fragment_length, TLS::MAX_CIPHERTEXT_SIZE_TLS13); + result.require("enough data", fragment_length + Botan::TLS::TLS_HEADER_SIZE < ct.size()); + return fragment_length; + }; + + TLS::TLS_Data_Reader reader("test reader", ct); + const auto fragment_length1 = read_record_header(reader); + reader.discard_next(fragment_length1); + + const auto fragment_length2 = read_record_header(reader); + reader.discard_next(fragment_length2); + + result.confirm("consumed all bytes", !reader.has_remaining()); + }) + }; + } + +std::vector legacy_version_handling() + { + // RFC 8446 5.1: + // legacy_record_version: MUST be set to 0x0303 for all records + // generated by a TLS 1.3 implementation other than an initial + // ClientHello (i.e., one not generated after a HelloRetryRequest), + // where it MAY also be 0x0301 for compatibility purposes. + + auto has_version = [](const auto& record, const uint16_t version) -> bool + { + TLS::TLS_Data_Reader dr("header reader", record); + dr.discard_next(1); + return dr.get_uint16_t() == version; + }; + + auto parse_record = [](auto& record_layer, const std::vector& data) + { + record_layer.copy_data(data); + return record_layer.next_record(); + }; + + return + { + Botan_Tests::CHECK("client side starts with version 0x0301", [&](Test::Result& result) + { + auto rl = record_layer_client(); + auto rec = rl.prepare_records(TLS::Record_Type::HANDSHAKE, std::vector(5)); + result.confirm("first record has version 0x0301", has_version(rec, 0x0301)); + + rec = rl.prepare_records(TLS::Record_Type::HANDSHAKE, std::vector(5)); + result.confirm("next record has version 0x0303", has_version(rec, 0x0303)); + }), + + Botan_Tests::CHECK("server side starts with version 0x0303", [&](Test::Result& result) + { + auto rl = record_layer_server(true); + auto rec = rl.prepare_records(TLS::Record_Type::HANDSHAKE, std::vector(5)); + result.confirm("first record has version 0x0303", has_version(rec, 0x0303)); + }), + + Botan_Tests::CHECK("server side accepts version 0x0301 for the first record", [&](Test::Result& result) + { + const auto first_record = Botan::hex_decode("16 03 01 00 05 00 00 00 00 00"); + const auto second_record = Botan::hex_decode("16 03 03 00 05 00 00 00 00 00"); + auto rl = record_layer_server(); + result.test_no_throw("parsing initial record", [&] { parse_record(rl, first_record);}); + result.test_no_throw("parsing second record", [&] { parse_record(rl, second_record);}); + }), + + Botan_Tests::CHECK("server side accepts version 0x0301 for the first record for partial records", [&](Test::Result& result) + { + const auto first_part = Botan::hex_decode("16 03 01"); + const auto second_part = Botan::hex_decode("00 05 00 00 00 00 00"); + auto rl = record_layer_server(); + result.test_no_throw("parsing initial part", [&] { parse_record(rl, first_part);}); + result.test_no_throw("parsing second part", [&] { parse_record(rl, second_part);}); + }), + + Botan_Tests::CHECK("server side accepts version 0x0303 for the first record", [&](Test::Result& result) + { + const auto first_record = Botan::hex_decode("16 03 03 00 05 00 00 00 00 00"); + auto rl = record_layer_server(); + result.test_no_throw("parsing initial record", [&] { parse_record(rl, first_record);}); + }), + + Botan_Tests::CHECK("server side does not accept version 0x0301 for the second record", [&](Test::Result& result) + { + const auto record = Botan::hex_decode("16 03 01 00 05 00 00 00 00 00"); + auto rl = record_layer_server(); + result.test_no_throw("parsing initial record", [&] { parse_record(rl, record);}); + result.test_throws("parsing second record", [&] { parse_record(rl, record);}); + }), + + Botan_Tests::CHECK("server side does not accept other versions (after the first record)", [&](Test::Result& result) + { + auto rl = record_layer_server(true); + result.test_throws("does not accept 0x0300", [&] { parse_record(rl, Botan::hex_decode("16 03 00 00 05 00 00 00 00 00"));}); + result.test_throws("does not accept 0x0302", [&] { parse_record(rl, Botan::hex_decode("16 03 02 00 05 00 00 00 00 00"));}); + result.test_throws("does not accept 0x0304", [&] { parse_record(rl, Botan::hex_decode("16 03 04 00 05 00 00 00 00 00"));}); + result.test_throws("does not accept 0x0305", [&] { parse_record(rl, Botan::hex_decode("16 03 05 00 05 00 00 00 00 00"));}); + }) + + }; + } + +std::vector record_size_limits() + { + const auto count_records = [](auto& records) + { + Botan::TLS::TLS_Data_Reader reader("record counter", records); + size_t record_count = 0; + + for(;reader.has_remaining(); ++record_count) + { + reader.discard_next(1); // record type + BOTAN_ASSERT_NOMSG(reader.get_uint16_t() == 0x0303); // record version + reader.get_tls_length_value(2); // record length/content + } + + return record_count; + }; + + const auto record_length = [](auto& result, auto record) + { + result.require("has record", std::holds_alternative(record)); + const auto& r = std::get(record); + return r.fragment.size(); + }; + + return + { + Botan_Tests::CHECK("no specified limits means protocol defaults", [&](Test::Result& result) + { + auto csc = rfc8448_rtt1_handshake_traffic(Botan::TLS::CLIENT); + auto rlc = record_layer_client(true); + + const auto r1 = rlc.prepare_records(TLS::Record_Type::APPLICATION_DATA, std::vector(Botan::TLS::MAX_PLAINTEXT_SIZE), csc.get()); + result.test_eq("one record generated", count_records(r1), 1); + + const auto r2 = rlc.prepare_records(TLS::Record_Type::APPLICATION_DATA, std::vector(Botan::TLS::MAX_PLAINTEXT_SIZE + 1), csc.get()); + result.test_eq("two records generated", count_records(r2), 2); + + auto css = rfc8448_rtt1_handshake_traffic(Botan::TLS::SERVER); + auto rls = record_layer_server(true); + rls.copy_data(r1); + + result.test_eq("correct length record", record_length(result, rls.next_record(css.get())), Botan::TLS::MAX_PLAINTEXT_SIZE); + }), + + Botan_Tests::CHECK("outgoing record size limit", [&](Test::Result& result) + { + auto cs = rfc8448_rtt1_handshake_traffic(); + auto rl = record_layer_client(true); + + rl.set_record_size_limits(127 + 1 /* content type byte */, Botan::TLS::MAX_PLAINTEXT_SIZE + 1); + + const auto r1 = rl.prepare_records(TLS::Record_Type::APPLICATION_DATA, std::vector(127), cs.get()); + result.test_eq("one record generated", count_records(r1), 1); + + const auto r2 = rl.prepare_records(TLS::Record_Type::APPLICATION_DATA, std::vector(128), cs.get()); + result.test_eq("two records generated", count_records(r2), 2); + }), + + Botan_Tests::CHECK("outgoing record size limit can be changed", [&](Test::Result& result) + { + auto cs = rfc8448_rtt1_handshake_traffic(); + auto rl = record_layer_client(true); + + const auto r1 = rl.prepare_records(TLS::Record_Type::APPLICATION_DATA, std::vector(Botan::TLS::MAX_PLAINTEXT_SIZE), cs.get()); + result.test_eq("one record generated", count_records(r1), 1); + + const auto r2 = rl.prepare_records(TLS::Record_Type::APPLICATION_DATA, std::vector(Botan::TLS::MAX_PLAINTEXT_SIZE + 1), cs.get()); + result.test_eq("two records generated", count_records(r2), 2); + + rl.set_record_size_limits(127 + 1 /* content type byte */, Botan::TLS::MAX_PLAINTEXT_SIZE + 1); + + const auto r3 = rl.prepare_records(TLS::Record_Type::APPLICATION_DATA, std::vector(127), cs.get()); + result.test_eq("one record generated", count_records(r3), 1); + + const auto r4 = rl.prepare_records(TLS::Record_Type::APPLICATION_DATA, std::vector(128), cs.get()); + result.test_eq("two records generated", count_records(r4), 2); + }), + + Botan_Tests::CHECK("outgoing record limit does not affect unencrypted records", [&](Test::Result& result) + { + auto rl = record_layer_client(true); + + rl.set_record_size_limits(127 + 1 /* content type byte */, Botan::TLS::MAX_PLAINTEXT_SIZE + 1); + + const auto r1 = rl.prepare_records(TLS::Record_Type::HANDSHAKE, std::vector(Botan::TLS::MAX_PLAINTEXT_SIZE)); + result.test_eq("one record generated", count_records(r1), 1); + + const auto r2 = rl.prepare_records(TLS::Record_Type::HANDSHAKE, std::vector(Botan::TLS::MAX_PLAINTEXT_SIZE + 1)); + result.test_eq("two records generated", count_records(r2), 2); + }), + + Botan_Tests::CHECK("incoming limit is not checked on unprotected records", [&](Test::Result& result) + { + auto rlc = record_layer_client(true); + + rlc.set_record_size_limits(Botan::TLS::MAX_PLAINTEXT_SIZE + 1, 95 + 1); + + rlc.copy_data(Botan::concat(Botan::hex_decode("16 03 03 00 80"), std::vector(128))); + result.test_eq("correct length record", record_length(result, rlc.next_record()), 128); + }), + + Botan_Tests::CHECK("incoming limit is checked on protected records", [&](Test::Result& result) + { + auto css = rfc8448_rtt1_handshake_traffic(Botan::TLS::SERVER); + auto rls = record_layer_server(true); + + rls.set_record_size_limits(Botan::TLS::MAX_PLAINTEXT_SIZE + 1, 127 + 1); + rls.copy_data(Botan::hex_decode( + "170303009061ec4de29020a5664ef670094c7b5daa2796aa52e128cfa8808d15c1" + "ffc97a0aeeed62f9ea690bb753a03d000c5efac53c619face25ad234dffb63e611" + "4619fb045e3a3a0dde4f22e2399b4891029eccb79ea4a29c45a999e72fc74157f0" + "21db0afa05601af25b61df82fb728c772ad860081d96c86008c08d0c21f991cf0d" + "4a0eadc840d1ea8fb1f5dd852980d78fcc")); + + result.test_eq("correct length record", record_length(result, rls.next_record(css.get())), 127); + + rls.copy_data(Botan::hex_decode( + "1703030091234d4a480092fa6a55f1443345ee8d2250cd9c676370be68f86234db" + "f5514c6dea8b3fa99c6146fefc780e36230858a53f4c0295b23a77dc5b495e0541" + "093aa05ee6cf6f4a4996d9ffc829b638c822e4c36e4da50f1cf2845c12e4388d58" + "e907e181f2dd38e61e78c13ebcbd562a23025fd327eb4db083330314e4641f3b4b" + "43bf11dbb09f7a82443193dc9ece34dabd15")); + + result.test_throws("overflow detected", "Received an encrypted record that exceeds maximum plaintext size", + [&] { rls.next_record(css.get()); }); + }), + }; + } + + +} // namespace + +namespace Botan_Tests { +BOTAN_REGISTER_TEST_FN("tls", "tls_record_layer_13", + basic_sanitization_parse_records_client, + basic_sanitization_parse_records_server, + read_full_records, read_fragmented_records, write_records, + read_encrypted_records, write_encrypted_records, + legacy_version_handling, record_size_limits); +} + +#endif diff --git a/src/tests/test_tls_rfc8448.cpp b/src/tests/test_tls_rfc8448.cpp new file mode 100644 index 00000000000..3c29f03bc83 --- /dev/null +++ b/src/tests/test_tls_rfc8448.cpp @@ -0,0 +1,1116 @@ +/* +* (C) 2021 Jack Lloyd +* (C) 2021 René Meusel +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" +#include +#include +// Since RFC 8448 uses a specific set of cipher suites we can only run this +// test if all of them are enabled. +#if defined(BOTAN_HAS_TLS_13) && \ + defined(BOTAN_HAS_AEAD_CHACHA20_POLY1305) && \ + defined(BOTAN_HAS_AEAD_GCM) && \ + defined(BOTAN_HAS_AES) && \ + defined(BOTAN_HAS_CURVE_25519) && \ + defined(BOTAN_HAS_SHA2_32) && \ + defined(BOTAN_HAS_SHA2_64) + #define BOTAN_CAN_RUN_TEST_TLS_RFC8448 +#endif + +#if defined(BOTAN_CAN_RUN_TEST_TLS_RFC8448) + #include "test_rng.h" + #include "test_tls_utils.h" + + #include // TODO: replace me, otherwise we depend on auto_rng module + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include + + #include +#endif + +namespace Botan_Tests { + +#if defined(BOTAN_CAN_RUN_TEST_TLS_RFC8448) + +namespace { +constexpr size_t RECORD_HEADER_SIZE = 5; + +template +decltype(auto) slice(Itr begin, Itr end) + { + return std::vector(begin, end); + } + +void add_entropy(Botan_Tests::Fixed_Output_RNG& rng, const std::string& hex) + { + std::vector in = Botan::hex_decode(hex); + rng.add_entropy(in.data(), in.size()); + } + +Botan::RSA_PrivateKey server_private_key() + { + return + { + Botan::BigInt("0xE435FB7CC83737756DACEA96AB7F59A2CC1069DB7DEB190E17E33A532B273F30A327AA0AAABC58CD67466AF9845FADC675FE094AF92C4BD1F2C1BC33DD2E0515"), + Botan::BigInt("0xCABD3BC0E0438664C8D4CC9F99977A94D9BBFEAD8E43870ABAE3F7EB8B4E0EEE8AF1D9B4719BA6196CF2CBBAEEEBF8B3490AFE9E9FFA74A88AA51FC645629303"), + Botan::BigInt("0x010001") + }; + } + +Botan::X509_Certificate server_certificate() + { + // self-signed certificate with an RSA1024 public key + // + // [...] + // Issuer: CN=rsa + // Validity + // Not Before: Jul 30 01:23:59 2016 GMT + // Not After : Jul 30 01:23:59 2026 GMT + // Subject: CN=rsa + // [...] + // X509v3 extensions: + // X509v3 Basic Constraints: + // CA:FALSE + // X509v3 Key Usage: + // Digital Signature, Key Encipherment + // [...] + return Botan::X509_Certificate( + Botan::hex_decode( + "308201ac30820115a003020102020102300d06092a864886f70d01010b050030" + "0e310c300a06035504031303727361301e170d3136303733303031323335395a" + "170d3236303733303031323335395a300e310c300a0603550403130372736130" + "819f300d06092a864886f70d010101050003818d0030818902818100b4bb498f" + "8279303d980836399b36c6988c0c68de55e1bdb826d3901a2461eafd2de49a91" + "d015abbc9a95137ace6c1af19eaa6af98c7ced43120998e187a80ee0ccb0524b" + "1b018c3e0b63264d449a6d38e22a5fda430846748030530ef0461c8ca9d9efbf" + "ae8ea6d1d03e2bd193eff0ab9a8002c47428a6d35a8d88d79f7f1e3f02030100" + "01a31a301830090603551d1304023000300b0603551d0f0404030205a0300d06" + "092a864886f70d01010b05000381810085aad2a0e5b9276b908c65f73a726717" + "0618a54c5f8a7b337d2df7a594365417f2eae8f8a58c8f8172f9319cf36b7fd6" + "c55b80f21a03015156726096fd335e5e67f2dbf102702e608ccae6bec1fc63a4" + "2a99be5c3eb7107c3c54e9b9eb2bd5203b1c3b84e0a8b2f759409ba3eac9d91d" + "402dcc0cc8f8961229ac9187b42b4de10000") + ); + } + + +/** +* Simple version of the Padding extension (RFC 7685) to reproduce the +* 2nd Client_Hello in RFC8448 Section 5 (HelloRetryRequest) +*/ +class Padding final : public Botan::TLS::Extension + { + public: + static Botan::TLS::Handshake_Extension_Type static_type() + { return Botan::TLS::Handshake_Extension_Type(21); } + + Botan::TLS::Handshake_Extension_Type type() const override { return static_type(); } + + explicit Padding(const size_t padding_bytes) : + m_padding_bytes(padding_bytes) {} + + std::vector serialize(Botan::TLS::Connection_Side) const override + { + return std::vector(m_padding_bytes, 0x00); + } + + bool empty() const override { return m_padding_bytes == 0; } + private: + size_t m_padding_bytes; + }; + +using namespace Botan; +using namespace Botan::TLS; + +using Modify_Exts_Fn = std::function; +class Test_TLS_13_Callbacks : public Botan::TLS::Callbacks + { + public: + Test_TLS_13_Callbacks(Modify_Exts_Fn modify_exts_cb) : + session_activated_called(false), m_modify_exts(std::move(modify_exts_cb)) + {} + + void tls_emit_data(const uint8_t data[], size_t size) override + { + count_callback_invocation("tls_emit_data"); + send_buffer.insert(send_buffer.end(), data, data + size); + } + + void tls_record_received(uint64_t seq_no, const uint8_t data[], size_t size) override + { + count_callback_invocation("tls_record_received"); + received_seq_no = seq_no; + receive_buffer.insert(receive_buffer.end(), data, data + size); + } + + void tls_alert(Botan::TLS::Alert alert) override + { + count_callback_invocation("tls_alert"); + BOTAN_UNUSED(alert); + // handle a tls alert received from the tls server + } + + bool tls_session_established(const Botan::TLS::Session& session) override + { + count_callback_invocation("tls_session_established"); + BOTAN_UNUSED(session); + // the session with the tls client was established + // return false to prevent the session from being cached, true to + // cache the session in the configured session manager + return false; + } + + void tls_session_activated() override + { + count_callback_invocation("tls_session_activated"); + session_activated_called = true; + } + + void tls_verify_cert_chain( + const std::vector& cert_chain, + const std::vector>&, + const std::vector&, + Botan::Usage_Type, + const std::string&, + const Botan::TLS::Policy&) override + { + count_callback_invocation("tls_verify_cert_chain"); + certificate_chain = cert_chain; + } + + std::chrono::milliseconds tls_verify_cert_chain_ocsp_timeout() const override + { + count_callback_invocation("tls_verify_cert_chain"); + return std::chrono::milliseconds(0); + } + + std::vector tls_provide_cert_status(const std::vector& chain, + const Certificate_Status_Request& csr) override + { + count_callback_invocation("tls_provide_cert_status"); + return Callbacks::tls_provide_cert_status(chain, csr); + } + + std::vector tls_sign_message( + const Private_Key& key, + RandomNumberGenerator& rng, + const std::string& emsa, + Signature_Format format, + const std::vector& msg) override + { + count_callback_invocation("tls_sign_message"); + return Callbacks::tls_sign_message(key, rng, emsa, format, msg); + } + + + bool tls_verify_message( + const Public_Key& key, + const std::string& emsa, + Signature_Format format, + const std::vector& msg, + const std::vector& sig) override + { + count_callback_invocation("tls_verify_message"); + return Callbacks::tls_verify_message(key, emsa, format, msg, sig); + } + + std::pair, std::vector> tls_dh_agree( + const std::vector& modulus, + const std::vector& generator, + const std::vector& peer_public_value, + const Policy& policy, + RandomNumberGenerator& rng) override + { + count_callback_invocation("tls_dh_agree"); + return Callbacks::tls_dh_agree(modulus, generator, peer_public_value, policy, rng); + } + + std::pair, std::vector> tls_ecdh_agree( + const std::string& curve_name, + const std::vector& peer_public_value, + const Policy& policy, + RandomNumberGenerator& rng, + bool compressed) override + { + count_callback_invocation("tls_ecdh_agree"); + return Callbacks::tls_ecdh_agree(curve_name, peer_public_value, policy, rng, compressed); + } + + void tls_inspect_handshake_msg(const Handshake_Message& message) override + { + count_callback_invocation("tls_inspect_handshake_msg_" + message.type_string()); + return Callbacks::tls_inspect_handshake_msg(message); + } + + std::string tls_server_choose_app_protocol(const std::vector& client_protos) override + { + count_callback_invocation("tls_server_choose_app_protocol"); + return Callbacks::tls_server_choose_app_protocol(client_protos); + } + + void tls_modify_extensions(Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side) override + { + count_callback_invocation("tls_modify_extensions"); + m_modify_exts(exts, side); + } + + void tls_examine_extensions(const Botan::TLS::Extensions& extn, Connection_Side which_side) override + { + count_callback_invocation("tls_examine_extensions"); + return Callbacks::tls_examine_extensions(extn, which_side); + } + + std::string tls_decode_group_param(Group_Params group_param) override + { + count_callback_invocation("tls_decode_group_param"); + return Callbacks::tls_decode_group_param(group_param); + } + + std::string tls_peer_network_identity() override + { + count_callback_invocation("tls_peer_network_identity"); + return Callbacks::tls_peer_network_identity(); + } + + std::vector pull_send_buffer() + { + return std::exchange(send_buffer, std::vector()); + } + + std::vector pull_receive_buffer() + { + return std::exchange(receive_buffer, std::vector()); + } + + uint64_t last_received_seq_no() const { return received_seq_no; } + + const std::map& callback_invocations() const + { + return m_callback_invocations; + } + + void reset_callback_invocation_counters() + { + m_callback_invocations.clear(); + } + + private: + void count_callback_invocation(const std::string& callback_name) const + { + if(m_callback_invocations.count(callback_name) == 0) + { m_callback_invocations[callback_name] = 0; } + + m_callback_invocations[callback_name]++; + } + + public: + bool session_activated_called; + + std::vector certificate_chain; + + private: + std::vector send_buffer; + std::vector receive_buffer; + uint64_t received_seq_no; + Modify_Exts_Fn m_modify_exts; + + mutable std::map m_callback_invocations; + }; + +class Test_Server_Credentials : public Botan::Credentials_Manager + { + public: + Test_Server_Credentials() : m_key(server_private_key()) {} + + std::vector + trusted_certificate_authorities(const std::string& type, const std::string& context) override + { + BOTAN_UNUSED(type, context); + return {}; + } + + std::vector cert_chain( + const std::vector& cert_key_types, + const std::string& type, + const std::string& context) override + { + BOTAN_UNUSED(cert_key_types, type, context); + return { server_certificate() }; + } + + Botan::Private_Key* private_key_for(const Botan::X509_Certificate& cert, + const std::string& type, + const std::string& context) override + { + BOTAN_UNUSED(cert, type, context); + // return the private key associated with the leaf certificate, + // in this case the one associated with "botan.randombit.net.crt" + return &m_key; + } + + private: + Botan::RSA_PrivateKey m_key; + }; + +class RFC8448_Text_Policy : public Botan::TLS::Text_Policy + { + public: + RFC8448_Text_Policy(const Botan::TLS::Text_Policy& other) + : Text_Policy(other) {} + + std::vector allowed_signature_schemes() const override + { + return + { + Botan::TLS::Signature_Scheme::ECDSA_SHA256, + Botan::TLS::Signature_Scheme::ECDSA_SHA384, + Botan::TLS::Signature_Scheme::ECDSA_SHA512, + Botan::TLS::Signature_Scheme::ECDSA_SHA1, // not actually supported + Botan::TLS::Signature_Scheme::RSA_PSS_SHA256, + Botan::TLS::Signature_Scheme::RSA_PSS_SHA384, + Botan::TLS::Signature_Scheme::RSA_PSS_SHA512, + Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256, + Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA384, + Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA512, + Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA1, // not actually supported + Botan::TLS::Signature_Scheme::DSA_SHA256, // not actually supported + Botan::TLS::Signature_Scheme::DSA_SHA384, // not actually supported + Botan::TLS::Signature_Scheme::DSA_SHA512, // not actually supported + Botan::TLS::Signature_Scheme::DSA_SHA1 // not actually supported + }; + } + }; + +class TLS_Context + { + protected: + TLS_Context(std::unique_ptr rng_in, + RFC8448_Text_Policy policy, + Modify_Exts_Fn modify_exts_cb) + : m_callbacks(std::move(modify_exts_cb)) + , m_rng(std::move(rng_in)) + , m_session_mgr(*m_rng) + , m_policy(std::move(policy)) + {} + + public: + virtual ~TLS_Context() = default; + + TLS_Context(TLS_Context&) = delete; + TLS_Context& operator=(const TLS_Context&) = delete; + + TLS_Context(TLS_Context&&) = delete; + TLS_Context& operator=(TLS_Context&&) = delete; + + std::vector pull_send_buffer() + { + return m_callbacks.pull_send_buffer(); + } + + std::vector pull_receive_buffer() + { + return m_callbacks.pull_receive_buffer(); + } + + uint64_t last_received_seq_no() const { return m_callbacks.last_received_seq_no(); } + + /** + * Checks that all of the listed callbacks were called at least once, no other + * callbacks were called in addition to the expected ones. After the checks are + * done, the callback invocation counters are reset. + */ + void check_callback_invocations(Test::Result& result, const std::string& context, + const std::vector& callback_names) + { + const auto& invokes = m_callbacks.callback_invocations(); + for(const auto& cbn : callback_names) + { + result.confirm(cbn + " was invoked (Context: " + context + ")", invokes.count(cbn) > 0 && invokes.at(cbn) > 0); + } + + for(const auto& invoke : invokes) + { + if(invoke.second == 0) + { continue; } + result.confirm(invoke.first + " was expected (Context: " + context + ")", std::find(callback_names.cbegin(), + callback_names.cend(), invoke.first) != callback_names.cend()); + } + + m_callbacks.reset_callback_invocation_counters(); + } + + const std::vector& certs_verified() const + { + return m_callbacks.certificate_chain; + } + + virtual void send(const std::vector& data) = 0; + + protected: + Test_TLS_13_Callbacks m_callbacks; + Test_Server_Credentials m_creds; + + std::unique_ptr m_rng; + Botan::TLS::Session_Manager_In_Memory m_session_mgr; + RFC8448_Text_Policy m_policy; + }; + +class Server_Context : public TLS_Context + { + public: + Server_Context(std::unique_ptr rng_in, + RFC8448_Text_Policy policy, + Modify_Exts_Fn modify_exts_cb) + : TLS_Context(std::move(rng_in), std::move(policy), std::move(modify_exts_cb)) + , server(m_callbacks, m_session_mgr, m_creds, m_policy, *m_rng) + {} + + void send(const std::vector& data) override + { + server.send(data.data(), data.size()); + } + + Botan::TLS::Server server; + }; + +class Client_Context : public TLS_Context + { + public: + Client_Context(std::unique_ptr rng_in, + RFC8448_Text_Policy policy, + Modify_Exts_Fn modify_exts_cb) + : TLS_Context(std::move(rng_in), std::move(policy), std::move(modify_exts_cb)) + , client(m_callbacks, m_session_mgr, m_creds, m_policy, *m_rng, + Botan::TLS::Server_Information("server"), + Botan::TLS::Protocol_Version::TLS_V13) + {} + + void send(const std::vector& data) override + { + client.send(data.data(), data.size()); + } + + Botan::TLS::Client client; + }; + +void sort_extensions(Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side) + { + if(side == Botan::TLS::Connection_Side::CLIENT) + { + const std::vector expected_order = + { + Botan::TLS::Handshake_Extension_Type::TLSEXT_SERVER_NAME_INDICATION, + Botan::TLS::Handshake_Extension_Type::TLSEXT_SAFE_RENEGOTIATION, + Botan::TLS::Handshake_Extension_Type::TLSEXT_SUPPORTED_GROUPS, + Botan::TLS::Handshake_Extension_Type::TLSEXT_SESSION_TICKET, + Botan::TLS::Handshake_Extension_Type::TLSEXT_KEY_SHARE, + Botan::TLS::Handshake_Extension_Type::TLSEXT_SUPPORTED_VERSIONS, + Botan::TLS::Handshake_Extension_Type::TLSEXT_SIGNATURE_ALGORITHMS, + Botan::TLS::Handshake_Extension_Type::TLSEXT_COOKIE, + Botan::TLS::Handshake_Extension_Type::TLSEXT_PSK_KEY_EXCHANGE_MODES, + Botan::TLS::Handshake_Extension_Type::TLSEXT_RECORD_SIZE_LIMIT, + Padding::static_type() + }; + + for(const auto ext_type : expected_order) + { + auto ext = exts.take(ext_type); + if(ext != nullptr) + { + exts.add(std::move(ext)); + } + } + } + } + +void add_psk_exchange_modes(Botan::TLS::Extensions& exts) + { + // Currently we do not support PSK and session resumption in TLS 1.3. + // Hence, we add this extension to please the test vector. The actual + // resumption is not exercised in this test, though. Once PSK is + // implemented, this should be removed and added in Client_Hello_13. + exts.add(new PSK_Key_Exchange_Modes({PSK_Key_Exchange_Mode::PSK_DHE_KE})); + } + +void add_renegotiation_extension(Botan::TLS::Extensions& exts) + { + // Renegotiation is not possible in TLS 1.3. Nevertheless, RFC 8448 requires + // to add this to the Client Hello for reasons. + exts.add(new Renegotiation_Extension()); + } + +} // namespace + +class Test_TLS_RFC8448 final : public Test + { + private: + static Test::Result simple_1_rtt_client_hello() + { + Test::Result result("Simple 1-RTT (Client side)"); + + // TODO: fixed output RNG is probably not needed as we cannot get the "right" + // client hello anyway -- revert + auto rng = std::make_unique(""); + rng->add_entropy(std::vector(32).data(), 32); // used by session mgr for session key + add_entropy(*rng, "cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7"); // for client hello random + + // for KeyShare extension (RFC 8448: "{client} create an ephemeral x25519 key pair") + add_entropy(*rng, "49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005"); + + auto add_extensions_and_sort = [](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side) + { + // For some reason, presumably checking compatibility, the RFC 8448 Client + // Hello includes a (TLS 1.2) Session_Ticket extension. We don't normally add + // this obsoleted extension in a TLS 1.3 client. + exts.add(new Botan::TLS::Session_Ticket()); + + add_psk_exchange_modes(exts); + add_renegotiation_extension(exts); + sort_extensions(exts, side); + }; + + Client_Context ctx(std::move(rng), read_tls_policy("rfc8448_1rtt"), add_extensions_and_sort); + result.confirm("client not closed", !ctx.client.is_closed()); + ctx.check_callback_invocations(result, "client hello prepared", { "tls_emit_data", "tls_inspect_handshake_msg_client_hello", "tls_modify_extensions" }); + + const auto client_hello_record = ctx.pull_send_buffer(); + result.test_gte("client hello written", client_hello_record.size(), RECORD_HEADER_SIZE); + + const auto client_hello_msg = slice(client_hello_record.begin() + RECORD_HEADER_SIZE, client_hello_record.end()); + + const auto expected_hello = Botan::hex_decode( + "16 03 01 00 c4 01 00 00 c0 03 03 cb" + "34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12" + "ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00" + "00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01" + "00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02" + "01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d" + "e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d" + "54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e" + "04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02" + "01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01"); + + result.test_eq("TLS client hello", client_hello_record, expected_hello); + + // header + // type: handshake, version: Tls12, len: 90 + // message + // version: Tls12, rand_time: 2796488356, rand_data: [...], + // session_id: None, cipher: 0x1301(AES_128_GCM_SHA256), + // compression: Null, ext: [...] + const auto server_hello_a = Botan::hex_decode( + "16 03 03 00 5a 02 00 00 56 03 03 a6" + "af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14"); + ctx.client.received_data(server_hello_a); + ctx.check_callback_invocations(result, "server hello partially received", { }); + + // splitting the input data to test partial reads + const auto server_hello_b = Botan::hex_decode( + "34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00" + "1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6" + "cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04"); + ctx.client.received_data(server_hello_b); + ctx.check_callback_invocations(result, "server hello received", { "tls_inspect_handshake_msg_server_hello", "tls_examine_extensions" }); + + result.confirm("client is not yet active", !ctx.client.is_active()); + + const auto server_handshake_messages = Botan::hex_decode( + "17 03 03 02 a2 d1 ff 33 4a 56 f5 bf" + "f6 59 4a 07 cc 87 b5 80 23 3f 50 0f 45 e4 89 e7 f3 3a f3 5e df" + "78 69 fc f4 0a a4 0a a2 b8 ea 73 f8 48 a7 ca 07 61 2e f9 f9 45" + "cb 96 0b 40 68 90 51 23 ea 78 b1 11 b4 29 ba 91 91 cd 05 d2 a3" + "89 28 0f 52 61 34 aa dc 7f c7 8c 4b 72 9d f8 28 b5 ec f7 b1 3b" + "d9 ae fb 0e 57 f2 71 58 5b 8e a9 bb 35 5c 7c 79 02 07 16 cf b9" + "b1 18 3e f3 ab 20 e3 7d 57 a6 b9 d7 47 76 09 ae e6 e1 22 a4 cf" + "51 42 73 25 25 0c 7d 0e 50 92 89 44 4c 9b 3a 64 8f 1d 71 03 5d" + "2e d6 5b 0e 3c dd 0c ba e8 bf 2d 0b 22 78 12 cb b3 60 98 72 55" + "cc 74 41 10 c4 53 ba a4 fc d6 10 92 8d 80 98 10 e4 b7 ed 1a 8f" + "d9 91 f0 6a a6 24 82 04 79 7e 36 a6 a7 3b 70 a2 55 9c 09 ea d6" + "86 94 5b a2 46 ab 66 e5 ed d8 04 4b 4c 6d e3 fc f2 a8 94 41 ac" + "66 27 2f d8 fb 33 0e f8 19 05 79 b3 68 45 96 c9 60 bd 59 6e ea" + "52 0a 56 a8 d6 50 f5 63 aa d2 74 09 96 0d ca 63 d3 e6 88 61 1e" + "a5 e2 2f 44 15 cf 95 38 d5 1a 20 0c 27 03 42 72 96 8a 26 4e d6" + "54 0c 84 83 8d 89 f7 2c 24 46 1a ad 6d 26 f5 9e ca ba 9a cb bb" + "31 7b 66 d9 02 f4 f2 92 a3 6a c1 b6 39 c6 37 ce 34 31 17 b6 59" + "62 22 45 31 7b 49 ee da 0c 62 58 f1 00 d7 d9 61 ff b1 38 64 7e" + "92 ea 33 0f ae ea 6d fa 31 c7 a8 4d c3 bd 7e 1b 7a 6c 71 78 af" + "36 87 90 18 e3 f2 52 10 7f 24 3d 24 3d c7 33 9d 56 84 c8 b0 37" + "8b f3 02 44 da 8c 87 c8 43 f5 e5 6e b4 c5 e8 28 0a 2b 48 05 2c" + "f9 3b 16 49 9a 66 db 7c ca 71 e4 59 94 26 f7 d4 61 e6 6f 99 88" + "2b d8 9f c5 08 00 be cc a6 2d 6c 74 11 6d bd 29 72 fd a1 fa 80" + "f8 5d f8 81 ed be 5a 37 66 89 36 b3 35 58 3b 59 91 86 dc 5c 69" + "18 a3 96 fa 48 a1 81 d6 b6 fa 4f 9d 62 d5 13 af bb 99 2f 2b 99" + "2f 67 f8 af e6 7f 76 91 3f a3 88 cb 56 30 c8 ca 01 e0 c6 5d 11" + "c6 6a 1e 2a c4 c8 59 77 b7 c7 a6 99 9b bf 10 dc 35 ae 69 f5 51" + "56 14 63 6c 0b 9b 68 c1 9e d2 e3 1c 0b 3b 66 76 30 38 eb ba 42" + "f3 b3 8e dc 03 99 f3 a9 f2 3f aa 63 97 8c 31 7f c9 fa 66 a7 3f" + "60 f0 50 4d e9 3b 5b 84 5e 27 55 92 c1 23 35 ee 34 0b bc 4f dd" + "d5 02 78 40 16 e4 b3 be 7e f0 4d da 49 f4 b4 40 a3 0c b5 d2 af" + "93 98 28 fd 4a e3 79 4e 44 f9 4d f5 a6 31 ed e4 2c 17 19 bf da" + "bf 02 53 fe 51 75 be 89 8e 75 0e dc 53 37 0d 2b"); + + ctx.client.received_data(server_handshake_messages); + + ctx.check_callback_invocations(result, "encrypted handshake messages received", + { + "tls_inspect_handshake_msg_encrypted_extensions", + "tls_inspect_handshake_msg_certificate", + "tls_inspect_handshake_msg_certificate_verify", + "tls_inspect_handshake_msg_finished", + "tls_examine_extensions", + "tls_emit_data", + "tls_session_activated", + "tls_verify_cert_chain", + "tls_verify_message" + }); + result.confirm("correct certificate", ctx.certs_verified().front() == server_certificate()); + result.confirm("client is active", ctx.client.is_active()); + + const auto expected_handshake_finished = Botan::hex_decode( + "17 03 03 00 35 75 ec 4d c2 38 cc e6" + "0b 29 80 44 a7 1e 21 9c 56 cc 77 b0 51 7f e9 b9 3c 7a 4b fc 44" + "d8 7f 38 f8 03 38 ac 98 fc 46 de b3 84 bd 1c ae ac ab 68 67 d7" + "26 c4 05 46"); + + const auto client_handshake_finished = ctx.pull_send_buffer(); + result.test_gte("client handshake finished written", client_handshake_finished.size(), + RECORD_HEADER_SIZE); + + result.test_eq("correct handshake finished", client_handshake_finished, + expected_handshake_finished); + + const auto server_new_session_ticket = Botan::hex_decode( + "17 03 03 00 de 3a 6b 8f 90 41 4a 97" + "d6 95 9c 34 87 68 0d e5 13 4a 2b 24 0e 6c ff ac 11 6e 95 d4 1d" + "6a f8 f6 b5 80 dc f3 d1 1d 63 c7 58 db 28 9a 01 59 40 25 2f 55" + "71 3e 06 1d c1 3e 07 88 91 a3 8e fb cf 57 53 ad 8e f1 70 ad 3c" + "73 53 d1 6d 9d a7 73 b9 ca 7f 2b 9f a1 b6 c0 d4 a3 d0 3f 75 e0" + "9c 30 ba 1e 62 97 2a c4 6f 75 f7 b9 81 be 63 43 9b 29 99 ce 13" + "06 46 15 13 98 91 d5 e4 c5 b4 06 f1 6e 3f c1 81 a7 7c a4 75 84" + "00 25 db 2f 0a 77 f8 1b 5a b0 5b 94 c0 13 46 75 5f 69 23 2c 86" + "51 9d 86 cb ee ac 87 aa c3 47 d1 43 f9 60 5d 64 f6 50 db 4d 02" + "3e 70 e9 52 ca 49 fe 51 37 12 1c 74 bc 26 97 68 7e 24 87 46 d6" + "df 35 30 05 f3 bc e1 86 96 12 9c 81 53 55 6b 3b 6c 67 79 b3 7b" + "f1 59 85 68 4f"); + + ctx.client.received_data(server_new_session_ticket); + + // TODO: once we implement session resumption, this should probably expect some callback + ctx.check_callback_invocations(result, "new session ticket received", { }); + + const auto client_application_payload = Botan::hex_decode( + "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e" + "0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23" + "24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31"); + ctx.send(client_application_payload); + + const auto expected_encrypted_application_data = Botan::hex_decode( + "17 03 03 00 43 a2 3f 70 54 b6 2c 94" + "d0 af fa fe 82 28 ba 55 cb ef ac ea 42 f9 14 aa 66 bc ab 3f 2b" + "98 19 a8 a5 b4 6b 39 5b d5 4a 9a 20 44 1e 2b 62 97 4e 1f 5a 62" + "92 a2 97 70 14 bd 1e 3d ea e6 3a ee bb 21 69 49 15 e4"); + + const auto encrypted_application_data = ctx.pull_send_buffer(); + result.test_gte("client application data written", encrypted_application_data.size(), + RECORD_HEADER_SIZE); + + ctx.check_callback_invocations(result, "application data sent", { "tls_emit_data" }); + + result.test_eq("correct client application data", encrypted_application_data, + expected_encrypted_application_data); + + const auto server_encrypted_payload = Botan::hex_decode( + "17 03 03 00 43 2e 93 7e 11 ef 4a c7" + "40 e5 38 ad 36 00 5f c4 a4 69 32 fc 32 25 d0 5f 82 aa 1b 36 e3" + "0e fa f9 7d 90 e6 df fc 60 2d cb 50 1a 59 a8 fc c4 9c 4b f2 e5" + "f0 a2 1c 00 47 c2 ab f3 32 54 0d d0 32 e1 67 c2 95 5d"); + + ctx.client.received_data(server_encrypted_payload); + + ctx.check_callback_invocations(result, "application data sent", { "tls_record_received" }); + + const auto rcvd = ctx.pull_receive_buffer(); + result.test_eq("decrypted application traffic", rcvd, client_application_payload /* echoed */); + result.test_is_eq("sequence number", ctx.last_received_seq_no(), uint64_t(1)); + + ctx.client.close(); + + const auto client_expected_alert = Botan::hex_decode( + "17 03 03 00 13 c9 87 27 60 65 56 66" + "b7 4d 7f f1 15 3e fd 6d b6 d0 b0 e3"); + const auto produced_alert = ctx.pull_send_buffer(); + result.test_eq("close payload", produced_alert, client_expected_alert); + + ctx.check_callback_invocations(result, "CLOSE_NOTIFY sent", { "tls_emit_data" }); + + const auto server_close_notify = Botan::hex_decode( + "17 03 03 00 13 b5 8f d6 71 66 eb f5" + "99 d2 47 20 cf be 7e fa 7a 88 64 a9"); + ctx.client.received_data(server_close_notify); + + ctx.check_callback_invocations(result, "CLOSE_NOTIFY received", { "tls_alert" }); + + result.confirm("connection is closed", ctx.client.is_closed()); + + return result; + } + + static Test::Result hello_retry_request() + { + Test::Result result("Handshake involving Hello Retry Request (Client side)"); + + auto add_extensions_and_sort = [flights = 0](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side) mutable + { + ++flights; + + if(flights == 1) + { + add_psk_exchange_modes(exts); + add_renegotiation_extension(exts); + } + + // For some reason RFC8448 decided to require this (fairly obscure) extension + // in the second flight of the Client_Hello. + if(flights == 2) + { + exts.add(new Padding(175)); + } + + sort_extensions(exts, side); + }; + + // Fallback RNG is required to for blinding in ECDH with P-256 + auto& fallback_rng = Test::rng(); + auto rng = std::make_unique(fallback_rng); + + rng->add_entropy(std::vector(32).data(), 32); // used by session mgr for session key + add_entropy(*rng, "b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f"); // for client hello random + + // for KeyShare extension (RFC 8448: "{client} create an ephemeral x25519 key pair") + add_entropy(*rng, "0ed02f8e8117efc75ca7ac32aa7e34eda64cdc0ddad154a5e85289f959f63204"); + + // for KeyShare extension (RFC 8448: "{client} create an ephemeral P-256 key pair") + add_entropy(*rng, "ab5473467e19346ceb0a0414e41da21d4d2445bc3025afe97c4e8dc8d513da39"); + + Client_Context ctx(std::move(rng), read_tls_policy("rfc8448_hrr"), add_extensions_and_sort); + result.confirm("client not closed", !ctx.client.is_closed()); + + const auto client_hello_record = ctx.pull_send_buffer(); + result.test_gte("client hello written", client_hello_record.size(), RECORD_HEADER_SIZE); + + ctx.check_callback_invocations(result, "client hello prepared", { "tls_emit_data", "tls_inspect_handshake_msg_client_hello", "tls_modify_extensions" }); + + const auto client_hello_msg = slice(client_hello_record.begin() + RECORD_HEADER_SIZE, client_hello_record.end()); + + const auto expected_hello_1 = Botan::hex_decode( + "16 03 01 00 b4 01 00 00 b0 03 03 b0" + "b1 c5 a5 aa 37 c5 91 9f 2e d1 d5 c6 ff f7 fc b7 84 97 16 94 5a" + "2b 8c ee 92 58 a3 46 67 7b 6f 00 00 06 13 01 13 03 13 02 01 00" + "00 81 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01" + "00 00 0a 00 08 00 06 00 1d 00 17 00 18 00 33 00 26 00 24 00 1d" + "00 20 e8 e8 e3 f3 b9 3a 25 ed 97 a1 4a 7d ca cb 8a 27 2c 62 88" + "e5 85 c6 48 4d 05 26 2f ca d0 62 ad 1f 00 2b 00 03 02 03 04 00" + "0d 00 20 00 1e 04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01" + "05 01 06 01 02 01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00" + "1c 00 02 40 01"); + + result.test_eq("TLS client hello (1)", client_hello_record, expected_hello_1); + + const auto server_retry_request = Botan::hex_decode( + "16 03 03 00 b0 02 00 00 ac 03 03 cf" + "21 ad 74 e5 9a 61 11 be 1d 8c 02 1e 65 b8 91 c2 a2 11 16 7a bb" + "8c 5e 07 9e 09 e2 c8 a8 33 9c 00 13 01 00 00 84 00 33 00 02 00" + "17 00 2c 00 74 00 72 71 dc d0 4b b8 8b c3 18 91 19 39 8a 00 00" + "00 00 ee fa fc 76 c1 46 b8 23 b0 96 f8 aa ca d3 65 dd 00 30 95" + "3f 4e df 62 56 36 e5 f2 1b b2 e2 3f cc 65 4b 1b 5b 40 31 8d 10" + "d1 37 ab cb b8 75 74 e3 6e 8a 1f 02 5f 7d fa 5d 6e 50 78 1b 5e" + "da 4a a1 5b 0c 8b e7 78 25 7d 16 aa 30 30 e9 e7 84 1d d9 e4 c0" + "34 22 67 e8 ca 0c af 57 1f b2 b7 cf f0 f9 34 b0 00 2b 00 02 03" + "04"); + ctx.client.received_data(server_retry_request); + + ctx.check_callback_invocations(result, "hello retry request received", + { + "tls_emit_data", + "tls_inspect_handshake_msg_hello_retry_request", + "tls_examine_extensions", + "tls_inspect_handshake_msg_client_hello", + "tls_modify_extensions", + "tls_decode_group_param" + }); + + const auto client_hello_2_record = ctx.pull_send_buffer(); + const auto expected_hello_2 = Botan::hex_decode( + "16 03 03 02 00 01 00 01 fc 03 03 b0" + "b1 c5 a5 aa 37 c5 91 9f 2e d1 d5 c6 ff f7 fc b7 84 97 16 94 5a" + "2b 8c ee 92 58 a3 46 67 7b 6f 00 00 06 13 01 13 03 13 02 01 00" + "01 cd 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01" + "00 00 0a 00 08 00 06 00 1d 00 17 00 18 00 33 00 47 00 45 00 17" + "00 41 04 a6 da 73 92 ec 59 1e 17 ab fd 53 59 64 b9 98 94 d1 3b" + "ef b2 21 b3 de f2 eb e3 83 0e ac 8f 01 51 81 26 77 c4 d6 d2 23" + "7e 85 cf 01 d6 91 0c fb 83 95 4e 76 ba 73 52 83 05 34 15 98 97" + "e8 06 57 80 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03" + "06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05" + "02 06 02 02 02 00 2c 00 74 00 72 71 dc d0 4b b8 8b c3 18 91 19" + "39 8a 00 00 00 00 ee fa fc 76 c1 46 b8 23 b0 96 f8 aa ca d3 65" + "dd 00 30 95 3f 4e df 62 56 36 e5 f2 1b b2 e2 3f cc 65 4b 1b 5b" + "40 31 8d 10 d1 37 ab cb b8 75 74 e3 6e 8a 1f 02 5f 7d fa 5d 6e" + "50 78 1b 5e da 4a a1 5b 0c 8b e7 78 25 7d 16 aa 30 30 e9 e7 84" + "1d d9 e4 c0 34 22 67 e8 ca 0c af 57 1f b2 b7 cf f0 f9 34 b0 00" + "2d 00 02 01 01 00 1c 00 02 40 01 00 15 00 af 00 00 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00"); + result.test_eq("TLS client hello (2)", client_hello_2_record, expected_hello_2); + + const auto server_hello = Botan::hex_decode( + "16 03 03 00 7b 02 00 00 77 03 03 bb" + "34 1d 84 7f d7 89 c4 7c 38 71 72 dc 0c 9b f1 47 fc ca cb 50 43" + "d8 6c a4 c5 98 d3 ff 57 1b 98 00 13 01 00 00 4f 00 33 00 45 00" + "17 00 41 04 58 3e 05 4b 7a 66 67 2a e0 20 ad 9d 26 86 fc c8 5b" + "5a d4 1a 13 4a 0f 03 ee 72 b8 93 05 2b d8 5b 4c 8d e6 77 6f 5b" + "04 ac 07 d8 35 40 ea b3 e3 d9 c5 47 bc 65 28 c4 31 7d 29 46 86" + "09 3a 6c ad 7d 00 2b 00 02 03 04"); + ctx.client.received_data(server_hello); + + ctx.check_callback_invocations(result, "server hello received", { "tls_inspect_handshake_msg_server_hello", "tls_examine_extensions", "tls_decode_group_param" }); + + const auto server_encrypted_handshake_messages = Botan::hex_decode( + "17 03 03 02 96 99 be e2 0b af 5b 7f" + "c7 27 bf ab 62 23 92 8a 38 1e 6d 0c f9 c4 da 65 3f 9d 2a 7b 23" + "f7 de 11 cc e8 42 d5 cf 75 63 17 63 45 0f fb 8b 0c c1 d2 38 e6" + "58 af 7a 12 ad c8 62 43 11 4a b1 4a 1d a2 fa e4 26 21 ce 48 3f" + "b6 24 2e ab fa ad 52 56 6b 02 b3 1d 2e dd ed ef eb 80 e6 6a 99" + "00 d5 f9 73 b4 0c 4f df 74 71 9e cf 1b 68 d7 f9 c3 b6 ce b9 03" + "ca 13 dd 1b b8 f8 18 7a e3 34 17 e1 d1 52 52 2c 58 22 a1 a0 3a" + "d5 2c 83 8c 55 95 3d 61 02 22 87 4c ce 8e 17 90 b2 29 a2 aa 0b" + "53 c8 d3 77 ee 72 01 82 95 1d c6 18 1d c5 d9 0b d1 f0 10 5e d1" + "e8 4a a5 f7 59 57 c6 66 18 97 07 9e 5e a5 00 74 49 e3 19 7b dc" + "7c 9b ee ed dd ea fd d8 44 af a5 c3 15 ec fe 65 e5 76 af e9 09" + "81 28 80 62 0e c7 04 8b 42 d7 f5 c7 8d 76 f2 99 d6 d8 25 34 bd" + "d8 f5 12 fe bc 0e d3 81 4a ca 47 0c d8 00 0d 3e 1c b9 96 2b 05" + "2f bb 95 0d f6 83 a5 2c 2b a7 7e d3 71 3b 12 29 37 a6 e5 17 09" + "64 e2 ab 79 69 dc d9 80 b3 db 9b 45 8d a7 60 31 24 d6 dc 00 5e" + "4d 6e 04 b4 d0 c4 ba f3 27 5d b8 27 db ba 0a 6d b0 96 72 17 1f" + "c0 57 b3 85 1d 7e 02 68 41 e2 97 8f bd 23 46 bb ef dd 03 76 bb" + "11 08 fe 9a cc 92 18 9f 56 50 aa 5e 85 d8 e8 c7 b6 7a c5 10 db" + "a0 03 d3 d7 e1 63 50 bb 66 d4 50 13 ef d4 4c 9b 60 7c 0d 31 8c" + "4c 7d 1a 1f 5c bc 57 e2 06 11 80 4e 37 87 d7 b4 a4 b5 f0 8e d8" + "fd 70 bd ae ad e0 22 60 b1 2a b8 42 ef 69 0b 4a 3e e7 91 1e 84" + "1b 37 4e cd 5e bb bc 2a 54 d0 47 b6 00 33 6d d7 d0 c8 8b 4b c1" + "0e 58 ee 6c b6 56 de 72 47 fa 20 d8 e9 1d eb 84 62 86 08 cf 80" + "61 5b 62 e9 6c 14 91 c7 ac 37 55 eb 69 01 40 5d 34 74 fe 1a c7" + "9d 10 6a 0c ee 56 c2 57 7f c8 84 80 f9 6c b6 b8 c6 81 b7 b6 8b" + "53 c1 46 09 39 08 f3 50 88 81 75 bd fb 0b 1e 31 ad 61 e3 0b a0" + "ad fe 6d 22 3a a0 3c 07 83 b5 00 1a 57 58 7c 32 8a 9a fc fc fb" + "97 8d 1c d4 32 8f 7d 9d 60 53 0e 63 0b ef d9 6c 0c 81 6e e2 0b" + "01 00 76 8a e2 a6 df 51 fc 68 f1 72 74 0a 79 af 11 39 8e e3 be" + "12 52 49 1f a9 c6 93 47 9e 87 7f 94 ab 7c 5f 8c ad 48 02 03 e6" + "ab 7b 87 dd 71 e8 a0 72 91 13 df 17 f5 ee e8 6c e1 08 d1 d7 20" + "07 ec 1c d1 3c 85 a6 c1 49 62 1e 77 b7 d7 8d 80 5a 30 f0 be 03" + "0c 31 5e 54"); + ctx.client.received_data(server_encrypted_handshake_messages); + + ctx.check_callback_invocations(result, "encrypted handshake messages received", + { + "tls_inspect_handshake_msg_encrypted_extensions", + "tls_inspect_handshake_msg_certificate", + "tls_inspect_handshake_msg_certificate_verify", + "tls_inspect_handshake_msg_finished", + "tls_examine_extensions", + "tls_emit_data", + "tls_session_activated", + "tls_verify_cert_chain", + "tls_verify_message" + }); + + const auto expected_client_finished = Botan::hex_decode( + "17 03 03 00 35 d7 4f 19 23 c6 62 fd" + "34 13 7c 6f 50 2f 3d d2 b9 3d 95 1d 1b 3b c9 7e 42 af e2 3c 31" + "ab ea 92 fe 91 b4 74 99 9e 85 e3 b7 91 ce 25 2f e8 c3 e9 f9 39" + "a4 12 0c b2"); + + const auto client_finished = ctx.pull_send_buffer(); + result.test_eq("client finished", client_finished, expected_client_finished); + + const auto expected_client_close_notify = Botan::hex_decode( + "17 03 03 00 13 2e a6 cd f7 49 19 60 23 e2 b3 a4 94 91 69 55 36 42 60 47"); + + ctx.client.close(); + + ctx.check_callback_invocations(result, "encrypted handshake messages received", { "tls_emit_data" }); + + result.test_eq("client close notify", ctx.pull_send_buffer(), expected_client_close_notify); + + const auto server_close_notify = Botan::hex_decode( + "17 03 03 00 13 51 9f c5 07 5c b0 88 43 49 75 9f f9 ef 6f 01 1b b4 c6 f2"); + + ctx.client.received_data(server_close_notify); + + ctx.check_callback_invocations(result, "encrypted handshake messages received", { "tls_alert" }); + + result.confirm("connection is closed", ctx.client.is_closed()); + + return result; + } + + static Test::Result middlebox_compatibility() + { + Test::Result result("Middlebox Compatibility Mode (Client side)"); + + auto rng = std::make_unique(""); + rng->add_entropy(std::vector(32).data(), 32); // used by session mgr for session key + + // for client hello random + add_entropy(*rng, "4e640a3f2c2738f09c9418bd78edccd7559d0531199276d4d92a0e9ee9d77d09"); + + // for legacy session ID + add_entropy(*rng, "a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6ef"); + + // for KeyShare extension (x25519 private key) + add_entropy(*rng, "dea00b45695dc781f19d34a62c1afd31ab4369af1e855a3bbb258d8442cde6d7"); + + auto add_extensions_and_sort = [&](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side) + { + add_renegotiation_extension(exts); + add_psk_exchange_modes(exts); + sort_extensions(exts, side); + }; + + Client_Context ctx(std::move(rng), read_tls_policy("rfc8448_compat"), add_extensions_and_sort); + + const auto client_hello = Botan::hex_decode( + "16 03 01 00 e0 01 00 00 dc 03 03 4e" + "64 0a 3f 2c 27 38 f0 9c 94 18 bd 78 ed cc d7 55 9d 05 31 19 92" + "76 d4 d9 2a 0e 9e e9 d7 7d 09 20 a8 0c 16 55 81 a8 e0 d0 6c 00" + "18 d5 4d 3a 06 dd 32 cf d4 05 1e b0 26 fa d3 fd 0b a9 92 69 e6" + "ef 00 06 13 01 13 03 13 02 01 00 00 8d 00 00 00 0b 00 09 00 00" + "06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12 00 1d 00" + "17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 33 00 26 00 24" + "00 1d 00 20 8e 72 92 cf 30 56 db b0 d2 5f cb e5 5c 10 7d c9 bb" + "f8 3d d9 70 8f 39 20 3b a3 41 24 9a 7d 9b 63 00 2b 00 03 02 03" + "04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 03 08 04 08 05 08 06" + "04 01 05 01 06 01 02 01 04 02 05 02 06 02 02 02 00 2d 00 02 01" + "01 00 1c 00 02 40 01"); + + result.test_eq("Client Hello", ctx.pull_send_buffer(), client_hello); + + const auto server_hello = Botan::hex_decode( + "16 03 03 00 7a 02 00 00 76 03 03 e5" + "dd 59 48 c4 35 f7 a3 8f 0f 01 30 70 8d c3 22 d9 df 09 ab d4 83" + "81 17 c1 83 a7 bb 6d 99 4f 2c 20 a8 0c 16 55 81 a8 e0 d0 6c 00" + "18 d5 4d 3a 06 dd 32 cf d4 05 1e b0 26 fa d3 fd 0b a9 92 69 e6" + "ef 13 01 00 00 2e 00 33 00 24 00 1d 00 20 3e 30 f0 f4 ba 55 1a" + "fd 62 76 83 41 17 5f 52 65 e4 da f0 c8 84 16 17 aa 4f af dd 21" + "42 32 0c 22 00 2b 00 02 03 04"); + const auto change_cipher_spec = Botan::hex_decode("14 03 03 00 01 01"); + const auto encrypted_server_handshake = Botan::hex_decode( + "17 03 03 02 a2 48 de 89 1d 9c 36 24" + "a6 7a 6c 6f 06 01 ab 7a c2 0c 1f 6a 9e 14 d2 e6 00 7e 99 9e 13" + "03 67 a8 af 1b cf ea 94 98 fb ce 19 df 45 05 ee ce 3a 25 da 52" + "3c be 55 ea 1b 3b da 4e 91 99 5e 45 5d 50 0a 4f aa 62 27 b7 11" + "1e 1c 85 47 e2 d7 c1 79 db 21 53 03 d2 58 27 f3 cd 18 f4 8f 64" + "91 32 8c f5 c0 f8 14 d3 88 15 0b d9 e9 26 4a ae 49 1d b6 99 50" + "69 be a1 76 65 d5 e0 c8 17 28 4d 4a c2 18 80 05 4c 36 57 33 1e" + "23 a9 30 4d c8 8a 15 c0 4e c8 0b d3 85 2b f7 f9 d3 c6 61 5b 15" + "fa c8 3b bc a0 31 c6 d2 31 0d 9f 5d 7a 4b 02 0a 4f 7c 19 06 2b" + "65 c0 5a 1d 32 64 b5 57 ec 9d 8e 0f 7c ee 27 e3 6f 79 30 39 de" + "8d d9 6e df ca 90 09 e0 65 10 34 bf f3 1d 7f 34 9e ec e0 1d 99" + "fc b5 fc ab 84 0d 77 07 c7 22 99 c3 b5 d0 45 64 e8 80 a3 3c 5e" + "84 6c 76 2e 3d 92 2b b5 53 03 d1 d8 7c c0 f0 65 73 f1 7d cb 9b" + "8f fd 35 bb d8 83 c1 cb 3a a2 4f cc 32 50 05 f7 68 ce 2f b6 24" + "ca 97 b6 c4 d9 8e 17 f3 5b c2 c7 94 0a 06 10 0c 2d 44 8d b7 18" + "0b 2d 86 21 64 43 5c 9c 21 0e 98 60 39 4e 05 aa b2 3f f1 b0 20" + "3f 66 2c 58 8d a5 bc 44 11 47 7a 30 b4 11 36 c4 88 a0 a6 3f ca" + "b5 c1 5a c6 13 22 6d ae 82 7a 1d 1f e9 5e ce 6b 30 bc ee 15 60" + "a8 d4 08 d2 64 55 5e 76 0f 9b fc 62 4c 2c 87 fd 04 56 c9 bf b4" + "1b cd 1a 7b 21 27 86 d2 b6 7f d5 78 04 fa cf a1 ee f7 cf 29 19" + "d8 b9 98 c9 78 9f 76 3b 4d 9c aa 09 3a 9d ed 43 17 5d 46 a7 6b" + "4d 54 f0 ce 0c 5d 22 59 b6 07 e3 0a 9d 24 12 63 87 4f a5 9d 6f" + "57 0d c4 0d 83 a2 d8 3b f9 e9 85 0d 45 4c 57 80 65 35 a8 99 8a" + "e0 35 7d f9 2f 00 b9 66 73 44 c2 41 14 cc c9 ef 53 91 24 b2 04" + "e7 e6 e7 48 c3 0a 28 a3 d1 d1 83 99 72 43 ea cc bb d3 3b 0c 11" + "15 a0 32 71 06 a1 e6 a7 52 71 d4 98 30 86 f6 32 ff 0e b8 b4 c6" + "31 02 cb ce f5 bb 72 da e1 27 9d 5d e8 eb 19 09 6d 8c db 07 fa" + "8e a9 89 78 8f ac 23 e6 6e 04 88 c1 93 f3 f3 fe a8 c8 83 88 96" + "bf 3a e4 b6 84 8d 42 ce d4 bd f4 1a be 6f c3 31 b4 42 25 e7 a1" + "f7 d3 56 41 47 d5 45 8e 71 aa 90 9c b0 2b e9 58 bb c4 2e 3a a5" + "a2 7c c6 ea f4 b6 fe 51 ae 44 95 69 4d 8a b6 32 0a ab 92 01 83" + "fd 5b 31 a3 59 04 2f bd 67 39 1e c5 e4 d1 89 2a 2e 52 10 14 1a" + "49 4e 93 01 b2 4a 11 3c 47 4c 7f 2a 73 45 78 47"); + ctx.client.received_data(Botan::concat(server_hello, + change_cipher_spec, + encrypted_server_handshake)); + + const auto encrypted_client_handshake = Botan::hex_decode( + "17 03 03 00 35 32 d0 30 e2 73 77 3a" + "86 96 c7 99 98 1a f6 ce d0 7f 87 48 2e 81 56 5e 39 4e 87 c8 67" + "f3 3d f3 d6 5b 75 06 f1 a6 26 af 91 d4 82 1d 5f 7a 1f 21 0e f8" + "dd 3c 6d 16"); + + result.test_eq("CCS + Client Finished", ctx.pull_send_buffer(), + Botan::concat(change_cipher_spec, + encrypted_client_handshake)); + + result.confirm("client is ready to send application traffic", ctx.client.is_active()); + + ctx.client.close(); + + const auto client_close_notify = Botan::hex_decode( + "17 03 03 00 13 0f 62 91 55 38 2d ba" + "23 c4 e2 c5 f7 f8 4e 6f 2e d3 08 3d"); + result.test_eq("Client close_notify", ctx.pull_send_buffer(), client_close_notify); + + result.confirm("client cannot send application traffic anymore", !ctx.client.is_active()); + result.confirm("client is not fully closed yet", !ctx.client.is_closed()); + + const auto server_close_notify = Botan::hex_decode( + "17 03 03 00 13 b7 25 7b 0f ec af 69" + "d4 f0 9e 3f 89 1e 2a 25 d1 e2 88 45"); + ctx.client.received_data(server_close_notify); + + result.confirm("client connection was terminated", ctx.client.is_closed()); + + return result; + } + + public: + std::vector run() override + { + return + { + simple_1_rtt_client_hello(), + hello_retry_request(), + middlebox_compatibility() + }; + } + }; + +BOTAN_REGISTER_TEST("tls", "tls_rfc8448", Test_TLS_RFC8448); + +#endif + +} diff --git a/src/tests/test_tls_transcript_hash_13.cpp b/src/tests/test_tls_transcript_hash_13.cpp new file mode 100644 index 00000000000..be9422b184e --- /dev/null +++ b/src/tests/test_tls_transcript_hash_13.cpp @@ -0,0 +1,157 @@ +/* +* (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_13) + +#include + +#include + +using namespace Botan::TLS; + +namespace { + +using Test = Botan_Tests::Test; + +std::vector transcript_hash() + { + auto sha256 = [](const auto& str) { + return Botan::unlock(Botan::HashFunction::create_or_throw("SHA-256")->process(Botan::hex_decode(str))); + }; + + return + { + Botan_Tests::CHECK("trying to get 'previous' or 'current' with invalid state", [](Test::Result& result) + { + result.test_throws("previous throws invalid state exception", + [] { Transcript_Hash_State().previous(); }); + + result.test_throws("current throws invalid state exception", + [] { Transcript_Hash_State().current(); }); + }), + + Botan_Tests::CHECK("update without an algorithm", [](Test::Result& result) + { + Transcript_Hash_State h; + result.test_no_throw("update is successful", [&] { h.update({0xba, 0xad, 0xbe, 0xef}); }); + result.test_throws("previous throws invalid state exception", + [&] { h.previous(); }); + result.test_throws("current throws invalid state exception", + [&] { h.current(); }); + }), + + Botan_Tests::CHECK("cannot change algorithm", [](Test::Result& result) + { + Transcript_Hash_State h; + result.test_no_throw("initial set is successful", [&] { h.set_algorithm("SHA-256"); }); + result.test_no_throw("resetting is successful (NOOP)", [&] { h.set_algorithm("SHA-256"); }); + result.test_throws("set_algorithm throws invalid state exception", + [&] { h.set_algorithm("SHA-384"); }); + + Transcript_Hash_State h2("SHA-256"); + result.test_no_throw("resetting is successful (NOOP)", [&] { h2.set_algorithm("SHA-256"); }); + result.test_throws("set_algorithm throws invalid state exception", + [&] { h2.set_algorithm("SHA-384"); }); + }), + + Botan_Tests::CHECK("update and result retrieval (algorithm is set)", [&](Test::Result& result) + { + Transcript_Hash_State h("SHA-256"); + + h.update({0xba, 0xad, 0xbe, 0xef}); + result.test_throws("previous throws invalid state exception", + [&] { h.previous(); }); + result.test_eq("c = SHA-256(baadbeef)", h.current(), sha256("baadbeef")); + + h.update({0x60, 0x0d, 0xf0, 0x0d}); + result.test_eq("p = SHA-256(baadbeef)", h.previous(), sha256("baadbeef")); + result.test_eq("c = SHA-256(deadbeef | goodfood)", h.current(), sha256("baadbeef600df00d")); + }), + + Botan_Tests::CHECK("update and result retrieval (deferred algorithm specification)", [&](Test::Result& result) + { + Transcript_Hash_State h; + + h.update({0xba, 0xad, 0xbe, 0xef}); + h.set_algorithm("SHA-256"); + + result.test_throws("previous throws invalid state exception", + [&] { h.previous(); }); + result.test_eq("c = SHA-256(baadbeef)", h.current(), sha256("baadbeef")); + }), + + Botan_Tests::CHECK("update and result retrieval (deferred algorithm specification multiple updates)", [&](Test::Result& result) + { + Transcript_Hash_State h; + + h.update({0xba, 0xad, 0xbe, 0xef}); + h.update({0x60, 0x0d, 0xf0, 0x0d}); + h.set_algorithm("SHA-256"); + + result.test_eq("c = SHA-256(baadbeef | goodfood)", h.current(), sha256("baadbeef600df00d")); + }), + + Botan_Tests::CHECK("C-style update interface", [&](Test::Result& result) + { + Transcript_Hash_State h; + + std::array baad{0xba, 0xad}; + h.update(baad.data(), baad.size()); + h.update({0xbe, 0xef}); + + h.set_algorithm("SHA-256"); + + std::array food{0xf0, 0x0d}; + h.update({0x60, 0x0d}); + h.update(food.data(), food.size()); + + result.test_eq("c = SHA-256(baadbeef | goodfood)", h.current(), sha256("baadbeef600df00d")); + }), + + Botan_Tests::CHECK("cloning creates independent transcript_hash instances", [&](Test::Result& result) + { + Transcript_Hash_State h1("SHA-256"); + + h1.update({0xba, 0xad, 0xbe, 0xef}); + h1.update({0x60, 0x0d, 0xf0, 0x0d}); + + auto h2 = h1.clone(); + result.test_eq("c1 = SHA-256(baadbeef | goodfood)", h1.current(), sha256("baadbeef600df00d")); + result.test_eq("c2 = SHA-256(baadbeef | goodfood)", h2.current(), sha256("baadbeef600df00d")); + + h1.update({0xca, 0xfe, 0xd0, 0x0d}); + result.test_eq("c1 = SHA-256(baadbeef | goodfood | cafedude)", h1.current(), sha256("baadbeef600df00dcafed00d")); + result.test_eq("c2 = SHA-256(baadbeef | goodfood)", h2.current(), sha256("baadbeef600df00d")); + }), + + Botan_Tests::CHECK("recreation after hello retry request", [&](Test::Result& result) + { + Transcript_Hash_State h1; + + h1.update({0xc0, 0xca, 0xc0, 0x1a} /* client hello 1 */); + h1.update({0xc0, 0x01, 0xf0, 0x0d} /* hello retry request */); + + auto h2 = Transcript_Hash_State::recreate_after_hello_retry_request("SHA-256", h1); + + // RFC 8446 4.4.1 + const std::string hash_of_client_hello = Botan::hex_encode(sha256("c0cac01a")); + const std::string transcript = "fe000020" + hash_of_client_hello + "c001f00d"; + result.test_eq("transcript hash of hello retry request", h2.current(), sha256(transcript)); + }), + }; + } + +} + +namespace Botan_Tests { + +BOTAN_REGISTER_TEST_FN("tls", "tls_transcript_hash_13", transcript_hash); + +} +#endif diff --git a/src/tests/unit_tls_policy.cpp b/src/tests/unit_tls_policy.cpp index 8a74975c3b3..8808246f5d9 100644 --- a/src/tests/unit_tls_policy.cpp +++ b/src/tests/unit_tls_policy.cpp @@ -45,6 +45,7 @@ class TLS_Policy_Unit_Tests final : public Test results.push_back(test_peer_key_acceptable_ecdh()); results.push_back(test_peer_key_acceptable_ecdsa()); results.push_back(test_peer_key_acceptable_dh()); + results.push_back(test_key_exchange_groups_to_offer()); return results; } @@ -148,6 +149,29 @@ class TLS_Policy_Unit_Tests final : public Test #endif return result; } + + static Test::Result test_key_exchange_groups_to_offer() + { + Test::Result result("TLS Policy key share offering"); + + Botan::TLS::Policy default_policy; + result.test_eq("default TLS Policy offers exactly one", default_policy.key_exchange_groups_to_offer().size(), 1); + result.confirm("default TLS Policy offers preferred group", default_policy.key_exchange_groups().front() == default_policy.key_exchange_groups_to_offer().front()); + + using TP = Botan::TLS::Text_Policy; + + result.test_eq("default behaviour from text policy (size)", TP("").key_exchange_groups_to_offer().size(), 1); + result.confirm("default behaviour from text policy (preferred)", TP("").key_exchange_groups().front() == TP("").key_exchange_groups_to_offer().front()); + + result.confirm("no offerings", TP("key_exchange_groups_to_offer = none").key_exchange_groups_to_offer().empty()); + + const auto two_groups = "key_exchange_groups_to_offer = secp256r1 ffdhe/ietf/4096"; + result.test_eq("list of offerings (size)", TP(two_groups).key_exchange_groups_to_offer().size(), 2); + result.confirm("list of offerings (0)", TP(two_groups).key_exchange_groups_to_offer()[0] == Botan::TLS::Group_Params::SECP256R1); + result.confirm("list of offerings (1)", TP(two_groups).key_exchange_groups_to_offer()[1] == Botan::TLS::Group_Params::FFDHE_4096); + + return result; + } }; BOTAN_REGISTER_TEST("tls", "tls_policy", TLS_Policy_Unit_Tests);