From 0fd5ba1dc9cf23aa7c81588b068b6056a33d39f3 Mon Sep 17 00:00:00 2001 From: Hannes Rantzsch Date: Tue, 8 Jun 2021 07:19:52 +0200 Subject: [PATCH 01/20] 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 | 23 +- 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, 10110 insertions(+), 149 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 3512141031e..feeac4f956d 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; @@ -1228,6 +1326,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); @@ -1328,6 +1434,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()) @@ -1494,6 +1613,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 */); + } } std::chrono::system_clock::time_point tls_current_timestamp() override @@ -1536,7 +1661,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)); @@ -1545,7 +1669,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)); @@ -1569,7 +1695,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 @@ -1637,6 +1763,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); @@ -1664,8 +1799,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 9d18c7a1666..d2449fdaadf 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) */ @@ -34,7 +35,7 @@ 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= " + "--skip-system-cert-store --trusted-cas= --tls-version=default " "--session-db= --session-db-pass= --next-protocols= --type=tcp " "--client-cert= --client-cert-key=") { @@ -72,6 +73,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()) { @@ -95,22 +97,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 6b56056b907..b87cfde5bf3 100644 --- a/src/lib/tls/msg_client_hello.cpp +++ b/src/lib/tls/msg_client_hello.cpp @@ -46,7 +46,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(cb.tls_current_timestamp())); @@ -92,6 +95,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()) @@ -469,5 +476,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, cb, 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 b5f996f3d9e..27b20f313e1 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, @@ -42,51 +55,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 @@ -162,7 +222,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(), cb, policy), @@ -246,7 +306,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, cb, policy), @@ -294,12 +354,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 @@ -408,4 +472,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 280601abb9f..bb5916e5508 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 06fc7dac165..36d5a3cddef 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 dd52f848ec9..a6fa6302597 100644 --- a/src/lib/tls/tls_callbacks.h +++ b/src/lib/tls/tls_callbacks.h @@ -221,6 +221,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, @@ -242,6 +248,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 4a69cf428e3..eb00413b164 100644 --- a/src/lib/tls/tls_messages.h +++ b/src/lib/tls/tls_messages.h @@ -209,12 +209,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; @@ -227,38 +259,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; @@ -267,7 +268,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 @@ -322,7 +323,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; @@ -358,6 +359,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 */ @@ -416,6 +474,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) */ @@ -515,6 +637,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 */ @@ -545,6 +694,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 */ @@ -656,6 +818,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 */ @@ -668,6 +848,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 d6a288716da..4f4217b4582 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 8c92913adc8..fe7fb5dc5a5 100644 --- a/src/tests/test_tls.cpp +++ b/src/tests/test_tls.cpp @@ -329,7 +329,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); From 651cc2b6a14ddbdb461396b8093e709d27ad847f Mon Sep 17 00:00:00 2001 From: Hannes Rantzsch Date: Tue, 12 Apr 2022 15:19:13 +0200 Subject: [PATCH 02/20] refactor sig scheme validation in cert-verify msg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: René Meusel --- src/bogo_shim/bogo_shim.cpp | 3 +- src/lib/tls/msg_cert_verify.cpp | 93 +++++------------------- src/lib/tls/tls13/tls_client_impl_13.cpp | 19 ++++- src/lib/tls/tls_algos.cpp | 34 ++++++++- src/lib/tls/tls_algos.h | 2 + src/lib/tls/tls_messages.h | 3 +- 6 files changed, 75 insertions(+), 79 deletions(-) diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp index feeac4f956d..78de1970d72 100644 --- a/src/bogo_shim/bogo_shim.cpp +++ b/src/bogo_shim/bogo_shim.cpp @@ -207,7 +207,6 @@ std::string map_to_bogo_error(const std::string& e) { "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:", }, { "Test requires rejecting cert", ":CERTIFICATE_VERIFY_FAILED:" }, { "Unexpected ALPN protocol", ":INVALID_ALPN_PROTOCOL:" }, { "Unexpected record type 42 from counterparty", ":UNEXPECTED_RECORD:" }, @@ -257,6 +256,8 @@ std::string map_to_bogo_error(const std::string& e) { "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:" }, + { "Peer sent unknown signature scheme", ":WRONG_SIGNATURE_TYPE:" }, + { "We did not offer the usage of RSA_PSS_SHA256 as a signature scheme", ":WRONG_SIGNATURE_TYPE:" }, }; auto err_map_i = err_map.find(e); diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp index 0803428e4a9..d2f76276b16 100644 --- a/src/lib/tls/msg_cert_verify.cpp +++ b/src/lib/tls/msg_cert_verify.cpp @@ -111,99 +111,38 @@ bool Certificate_Verify_12::verify(const X509_Certificate& cert, 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) + , m_side(side) { - 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); + if(!signature_scheme_is_known(m_scheme)) + { throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Peer sent unknown signature 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"); - } + if(m_scheme == Signature_Scheme::RSA_PKCS1_SHA1 + || m_scheme == Signature_Scheme::ECDSA_SHA1 + || m_scheme == Signature_Scheme::DSA_SHA1) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "SHA-1 algorithm must not be used"); } // 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"); + if(m_scheme == Signature_Scheme::RSA_PKCS1_SHA1 + || m_scheme == Signature_Scheme::RSA_PKCS1_SHA256 + || m_scheme == Signature_Scheme::RSA_PKCS1_SHA384 + || m_scheme == Signature_Scheme::RSA_PKCS1_SHA512) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "RSA signatures must use an RSASSA-PSS algorithm"); } } -} - /* * 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. @@ -222,8 +161,12 @@ bool Certificate_Verify_13::verify(const X509_Certificate& cert, 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); + const bool signature_valid = + callbacks.tls_verify_message(*key, + padding_string_for_scheme(m_scheme), + signature_format_of_scheme(m_scheme), + msg, + m_signature); #if defined(BOTAN_UNSAFE_FUZZER_MODE) BOTAN_UNUSED(signature_valid); diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index 90fb701466a..30865e28a35 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -373,9 +374,25 @@ void Client_Impl_13::handle(const Certificate_13& certificate_msg) void Client_Impl_13::handle(const Certificate_Verify_13& certificate_verify_msg) { + // RFC 8446 4.4.3 + // If the CertificateVerify message is sent by a server, the signature + // algorithm MUST be one offered in the client's "signature_algorithms" + // extension unless no valid certificate chain can be produced without + // unsupported algorithms. + // + // Note: if the server failed to produce a certificate chain without using + // an unsupported signature scheme, we opt to abort the handshake. + const auto offered = m_handshake_state.client_hello().signature_schemes(); + if(!value_exists(offered, certificate_verify_msg.signature_scheme())) + { + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "We did not offer the usage of " + + sig_scheme_to_string(certificate_verify_msg.signature_scheme()) + + " as a signature scheme"); + } + 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()); diff --git a/src/lib/tls/tls_algos.cpp b/src/lib/tls/tls_algos.cpp index 81f174b7cb9..be437257c65 100644 --- a/src/lib/tls/tls_algos.cpp +++ b/src/lib/tls/tls_algos.cpp @@ -208,7 +208,39 @@ AlgorithmIdentifier algorithm_identifier_for_scheme(Signature_Scheme scheme) case Signature_Scheme::DSA_SHA256: case Signature_Scheme::DSA_SHA384: case Signature_Scheme::DSA_SHA512: - throw Invalid_State("oid_for_scheme: Unsupported signature scheme"); + throw Decoding_Error("oid_for_scheme: Unsupported signature scheme"); + } + + Botan::unreachable(); + } + +Signature_Format signature_format_of_scheme(Signature_Scheme scheme) + { + switch(scheme) + { + case Signature_Scheme::RSA_PKCS1_SHA1: + 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 IEEE_1363; + + case Signature_Scheme::ECDSA_SHA1: + case Signature_Scheme::ECDSA_SHA256: + case Signature_Scheme::ECDSA_SHA384: + case Signature_Scheme::ECDSA_SHA512: + case Signature_Scheme::EDDSA_25519: + case Signature_Scheme::EDDSA_448: + case Signature_Scheme::DSA_SHA1: + case Signature_Scheme::DSA_SHA256: + case Signature_Scheme::DSA_SHA384: + case Signature_Scheme::DSA_SHA512: + return DER_SEQUENCE; + + case Signature_Scheme::NONE: + throw Invalid_State("signature_format_of_scheme: Unsupported signature scheme"); } Botan::unreachable(); diff --git a/src/lib/tls/tls_algos.h b/src/lib/tls/tls_algos.h index 4e4388df15b..bc19807a2bf 100644 --- a/src/lib/tls/tls_algos.h +++ b/src/lib/tls/tls_algos.h @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -116,6 +117,7 @@ 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); +Signature_Format signature_format_of_scheme(Signature_Scheme scheme); /* * Matches with wire encoding diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h index eb00413b164..63324d9e1da 100644 --- a/src/lib/tls/tls_messages.h +++ b/src/lib/tls/tls_messages.h @@ -603,6 +603,8 @@ class BOTAN_UNSTABLE_API Certificate_Verify : public Handshake_Message public: Handshake_Type type() const override { return CERTIFICATE_VERIFY; } + Signature_Scheme signature_scheme() const { return m_scheme; } + Certificate_Verify(Handshake_IO& io, Handshake_State& state, const Policy& policy, @@ -654,7 +656,6 @@ class BOTAN_UNSTABLE_API Certificate_Verify_13 final : public Certificate_Verify const Connection_Side side); bool verify(const X509_Certificate& cert, - const std::vector& offered_schemes, Callbacks& callbacks, const Transcript_Hash& transcript_hash) const; From 0cb56ddafed94ba3b8070a664b0e5da8ea3c292a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 13 Apr 2022 11:09:47 +0530 Subject: [PATCH 03/20] minor code fixes --- src/lib/tls/asio/asio_stream.h | 2 +- src/lib/tls/tls_extensions_key_share.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/tls/asio/asio_stream.h b/src/lib/tls/asio/asio_stream.h index 469bce72f3d..2591408f650 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::TLS_V12)); // TODO don't hardcode + Protocol_Version::latest_tls_version())); } else { diff --git a/src/lib/tls/tls_extensions_key_share.cpp b/src/lib/tls/tls_extensions_key_share.cpp index a0d19bdbcba..5e194061d48 100644 --- a/src/lib/tls/tls_extensions_key_share.cpp +++ b/src/lib/tls/tls_extensions_key_share.cpp @@ -106,15 +106,15 @@ class Key_Share_Entry 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) } +#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 } +#endif else { throw Decoding_Error("cannot create a key offering without a group definition"); From 4f874ba5fc95d5d70b0642326237b5a9715dbe6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 13 Apr 2022 11:10:39 +0530 Subject: [PATCH 04/20] FIX: handle unimplemented handshake messages more gracefully --- src/lib/tls/tls13/tls_handshake_layer_13.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/tls/tls13/tls_handshake_layer_13.cpp b/src/lib/tls/tls13/tls_handshake_layer_13.cpp index 5b99839d100..12336284674 100644 --- a/src/lib/tls/tls13/tls_handshake_layer_13.cpp +++ b/src/lib/tls/tls13/tls_handshake_layer_13.cpp @@ -34,10 +34,10 @@ Handshake_Type handshake_type_from_byte(uint8_t type) { case CLIENT_HELLO: case SERVER_HELLO: - case END_OF_EARLY_DATA: + // case END_OF_EARLY_DATA: // NYI: needs PSK/resumption support -- won't be offered in Client Hello for now case ENCRYPTED_EXTENSIONS: case CERTIFICATE: - case CERTIFICATE_REQUEST: + // case CERTIFICATE_REQUEST: // NYI: client auth -- server might still request, resulting in handshake failure case CERTIFICATE_VERIFY: case FINISHED: return Handshake_Type(type); @@ -50,6 +50,7 @@ Handshake_Type handshake_type_from_byte(uint8_t type) { case NEW_SESSION_TICKET: case KEY_UPDATE: + // case CERTIFICATE_REQUEST: // NYI: post-handshake client auth (RFC 8446 4.6.2) -- won't be offered in Client Hello for now return Handshake_Type(type); } throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Unknown post-handshake message received"); From a9115608f25683e7a9a6b3491519cafd588cccd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Tue, 26 Apr 2022 22:19:20 +0530 Subject: [PATCH 05/20] FIX: potential segfault on API misuse in TLS 1.3 If the user calls Channel::close() _after_ the channel had encountered a protocol error the library might segfault. Any protocol error causes the cipher state object (with the key material) to be destroyed. Calling ::close() on a Channel in this state caused a method call on said destroyed object and hence segfault. --- src/lib/tls/tls13/tls_channel_impl_13.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/tls/tls13/tls_channel_impl_13.cpp b/src/lib/tls/tls13/tls_channel_impl_13.cpp index bdf1e7f6b65..725f3950803 100644 --- a/src/lib/tls/tls13/tls_channel_impl_13.cpp +++ b/src/lib/tls/tls13/tls_channel_impl_13.cpp @@ -268,8 +268,9 @@ void Channel_Impl_13::send_alert(const Alert& alert) // 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)) + if(is_close_notify_alert(alert) && m_can_write) { + BOTAN_ASSERT_NONNULL(m_cipher_state); m_can_write = false; m_cipher_state->clear_write_keys(); } From 651a853a53b7148b63a650d127800a26e6920306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Tue, 26 Apr 2022 19:34:25 +0530 Subject: [PATCH 06/20] Clean up RFC 8448 test case * move record transcripts into .vec file * Separate test cases into CHECK scopes * add documentation * remove unused stuff --- .../tls_13_rfc8448/client_certificate.pem | 11 + .../tls_13_rfc8448/server_certificate.pem | 11 + src/tests/data/tls_13_rfc8448/server_key.pem | 16 + src/tests/data/tls_13_rfc8448/transcripts.vec | 36 + src/tests/test_tls_rfc8448.cpp | 905 ++++++------------ 5 files changed, 387 insertions(+), 592 deletions(-) create mode 100644 src/tests/data/tls_13_rfc8448/client_certificate.pem create mode 100644 src/tests/data/tls_13_rfc8448/server_certificate.pem create mode 100644 src/tests/data/tls_13_rfc8448/server_key.pem create mode 100644 src/tests/data/tls_13_rfc8448/transcripts.vec diff --git a/src/tests/data/tls_13_rfc8448/client_certificate.pem b/src/tests/data/tls_13_rfc8448/client_certificate.pem new file mode 100644 index 00000000000..1823b38d4f2 --- /dev/null +++ b/src/tests/data/tls_13_rfc8448/client_certificate.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBsjCCARugAwIBAgIBATANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDEwZjbGllbnQwH +hcNMTYwNzMwMDEyMzU5WhcNMjYwNzMwMDEyMzU5WjARMQ8wDQYDVQQDEwZjbGllbnQwgZ +8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMOBdeAEpo0JP4I7nDedIB+8C7ehx5GQXj+ +/doR+ROdR67zTYL2UXIHlIivMiEbTqKD5Ppv1vrq9ku3x3h/xkCFwPnq2wJAVE/l+ObER +8JyTSJcceyEZhKdUzUX+CVrw6kI2gpvM96f+myiI54q0d2kKW54cy+kcakoPl6fgKEIBA +gMBAAGjGjAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgeAMA0GCSqGSIb3DQEBCwUAA4GBAB +p6WgGFMrAirwdn1IYWDP8tFnoZFdI4NbVFlJFtxoC+XS5iYHbF1Sci68x3XX2Z+YC+L8l +NNKz2zAC6kMvPsGCKoefjlx7wwHpB1HrYNF0fgf5Bihz0EFRCn9IXvXd9wc8I8F35B5nG +WTYeDxqO5KwPeJdCC9vII9qAovK6Iwgc +-----END CERTIFICATE----- diff --git a/src/tests/data/tls_13_rfc8448/server_certificate.pem b/src/tests/data/tls_13_rfc8448/server_certificate.pem new file mode 100644 index 00000000000..a652ea6f772 --- /dev/null +++ b/src/tests/data/tls_13_rfc8448/server_certificate.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBrDCCARWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDEwNyc2EwHhcNM +TYwNzMwMDEyMzU5WhcNMjYwNzMwMDEyMzU5WjAOMQwwCgYDVQQDEwNyc2EwgZ8wDQYJKo +ZIhvcNAQEBBQADgY0AMIGJAoGBALS7SY+CeTA9mAg2OZs2xpiMDGjeVeG9uCbTkBokYer +9LeSakdAVq7yalRN6zmwa8Z6qavmMfO1DEgmY4YeoDuDMsFJLGwGMPgtjJk1Emm044ipf +2kMIRnSAMFMO8EYcjKnZ77+ujqbR0D4r0ZPv8KuagALEdCim01qNiNeffx4/AgMBAAGjG +jAYMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4GBAIWq0qDluS +drkIxl9zpyZxcGGKVMX4p7M30t96WUNlQX8uro+KWMj4Fy+TGc82t/1sVbgPIaAwFRVnJ +glv0zXl5n8tvxAnAuYIzK5r7B/GOkKpm+XD63EHw8VOm56yvVIDscO4TgqLL3WUCbo+rJ +2R1ALcwMyPiWEimskYe0K03hAAA= +-----END CERTIFICATE----- diff --git a/src/tests/data/tls_13_rfc8448/server_key.pem b/src/tests/data/tls_13_rfc8448/server_key.pem new file mode 100644 index 00000000000..e1a0e265096 --- /dev/null +++ b/src/tests/data/tls_13_rfc8448/server_key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALS7SY+CeTA9mAg2 +OZs2xpiMDGjeVeG9uCbTkBokYer9LeSakdAVq7yalRN6zmwa8Z6qavmMfO1DEgmY +4YeoDuDMsFJLGwGMPgtjJk1Emm044ipf2kMIRnSAMFMO8EYcjKnZ77+ujqbR0D4r +0ZPv8KuagALEdCim01qNiNeffx4/AgMBAAECgYAE3qcF1DpupyCd2AchEag8geMi +pZJ4szSAZB6vfApphbjjHET23mLhtMIwn2Em53t8QekjMUu/o4gTBdwSF/FsgZzl +OOki82mCjQ5XGV2MhIhGAgey+qcmvPcIu9fbf2efiTSS/CpiLgiXCqxEHOTgwwiN +8lrmeSM9+KO9ov+ZQQJBAOQ1+3zINzd1bazqlqt/WaLMEGnbfesZDhfjOlMrJz8w +oyeqCqq8WM1nRmr5hF+txnX+CUr5LEvR8sG8M90uBRUCQQDKvTvA4EOGZMjUzJ+Z +l3qU2bv+rY5Dhwq64/fri04O7orx2bRxm6YZbPLLuu7r+LNJCv6en/p0qIqlH8ZF +YpMDAkA/VzRcJ/4baH5udhYnt4sbgmQz3XYPoL6mpqzzlJCqG0fNpIadaPWE3VtQ +Kb0yCTuCWGYf5xUCXl1wpFoI09MZAkAYPaATY70vKIXKy9yZZL9HZPFRdjb4ZAEo +b3GJPFLM/kCmwj0NCGtHxvsQ2P0QQeBN736aQM6VfEF3lOEEEtE5AkEAg5ypoIXk +KGsskORmmXosaB8hM5qjR3gU5N7BGDMFDtUN0TzAOASKQ8WbKsxBaInAN2Zf5a+m +BZafjAHfpcqWnQ== +-----END PRIVATE KEY----- diff --git a/src/tests/data/tls_13_rfc8448/transcripts.vec b/src/tests/data/tls_13_rfc8448/transcripts.vec new file mode 100644 index 00000000000..da4d4bb68f0 --- /dev/null +++ b/src/tests/data/tls_13_rfc8448/transcripts.vec @@ -0,0 +1,36 @@ +[Simple_1RTT_Handshake] +RNG_Pool = cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece749af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005 +CurrentTimestamp = 1651826546000 +ClientHello_1 = 16030100c4010000c00303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010000910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001 +ServerHello = 160303005a020000560303a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155772ed3e2692800130100002e00330024001d0020c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f002b00020304 +ServerHandshakeMessages = 17030302a2d1ff334a56f5bff6594a07cc87b580233f500f45e489e7f33af35edf7869fcf40aa40aa2b8ea73f848a7ca07612ef9f945cb960b4068905123ea78b111b429ba9191cd05d2a389280f526134aadc7fc78c4b729df828b5ecf7b13bd9aefb0e57f271585b8ea9bb355c7c79020716cfb9b1183ef3ab20e37d57a6b9d7477609aee6e122a4cf51427325250c7d0e509289444c9b3a648f1d71035d2ed65b0e3cdd0cbae8bf2d0b227812cbb360987255cc744110c453baa4fcd610928d809810e4b7ed1a8fd991f06aa6248204797e36a6a73b70a2559c09ead686945ba246ab66e5edd8044b4c6de3fcf2a89441ac66272fd8fb330ef8190579b3684596c960bd596eea520a56a8d650f563aad27409960dca63d3e688611ea5e22f4415cf9538d51a200c27034272968a264ed6540c84838d89f72c24461aad6d26f59ecaba9acbbb317b66d902f4f292a36ac1b639c637ce343117b659622245317b49eeda0c6258f100d7d961ffb138647e92ea330faeea6dfa31c7a84dc3bd7e1b7a6c7178af36879018e3f252107f243d243dc7339d5684c8b0378bf30244da8c87c843f5e56eb4c5e8280a2b48052cf93b16499a66db7cca71e4599426f7d461e66f99882bd89fc50800becca62d6c74116dbd2972fda1fa80f85df881edbe5a37668936b335583b599186dc5c6918a396fa48a181d6b6fa4f9d62d513afbb992f2b992f67f8afe67f76913fa388cb5630c8ca01e0c65d11c66a1e2ac4c85977b7c7a6999bbf10dc35ae69f5515614636c0b9b68c19ed2e31c0b3b66763038ebba42f3b38edc0399f3a9f23faa63978c317fc9fa66a73f60f0504de93b5b845e275592c12335ee340bbc4fddd502784016e4b3be7ef04dda49f4b440a30cb5d2af939828fd4ae3794e44f94df5a631ede42c1719bfdabf0253fe5175be898e750edc53370d2b +ClientFinished = 170303003575ec4dc238cce60b298044a71e219c56cc77b0517fe9b93c7a4bfc44d87f38f80338ac98fc46deb384bd1caeacab6867d726c40546 +NewSessionTicket = 17030300de3a6b8f90414a97d6959c3487680de5134a2b240e6cffac116e95d41d6af8f6b580dcf3d11d63c758db289a015940252f55713e061dc13e078891a38efbcf5753ad8ef170ad3c7353d16d9da773b9ca7f2b9fa1b6c0d4a3d03f75e09c30ba1e62972ac46f75f7b981be63439b2999ce13064615139891d5e4c5b406f16e3fc181a77ca475840025db2f0a77f81b5ab05b94c01346755f69232c86519d86cbeeac87aac347d143f9605d64f650db4d023e70e952ca49fe5137121c74bc2697687e248746d6df353005f3bce18696129c8153556b3b6c6779b37bf15985684f +Client_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031 +Client_AppData_Record = 1703030043a23f7054b62c94d0affafe8228ba55cbefacea42f914aa66bcab3f2b9819a8a5b46b395bd54a9a20441e2b62974e1f5a6292a2977014bd1e3deae63aeebb21694915e4 +Server_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031 +Server_AppData_Record = 17030300432e937e11ef4ac740e538ad36005fc4a46932fc3225d05f82aa1b36e30efaf97d90e6dffc602dcb501a59a8fcc49c4bf2e5f0a21c0047c2abf332540dd032e167c2955d +Client_CloseNotify = 1703030013c9872760655666b74d7ff1153efd6db6d0b0e3 +Server_CloseNotify = 1703030013b58fd67166ebf599d24720cfbe7efa7a8864a9 + +[HelloRetryRequest_Handshake] +RNG_Pool = b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f0ed02f8e8117efc75ca7ac32aa7e34eda64cdc0ddad154a5e85289f959f63204ab5473467e19346ceb0a0414e41da21d4d2445bc3025afe97c4e8dc8d513da39 +CurrentTimestamp = 1651826546000 +ClientHello_1 = 16030100b4010000b00303b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f000006130113031302010000810000000b0009000006736572766572ff01000100000a00080006001d00170018003300260024001d0020e8e8e3f3b93a25ed97a14a7dcacb8a272c6288e585c6484d05262fcad062ad1f002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001 +HelloRetryRequest = 16030300b0020000ac0303cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c001301000084003300020017002c0074007271dcd04bb88bc3189119398a00000000eefafc76c146b823b096f8aacad365dd0030953f4edf625636e5f21bb2e23fcc654b1b5b40318d10d137abcbb87574e36e8a1f025f7dfa5d6e50781b5eda4aa15b0c8be778257d16aa3030e9e7841dd9e4c0342267e8ca0caf571fb2b7cff0f934b0002b00020304 +ClientHello_2 = 1603030200010001fc0303b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f000006130113031302010001cd0000000b0009000006736572766572ff01000100000a00080006001d001700180033004700450017004104a6da7392ec591e17abfd535964b99894d13befb221b3def2ebe3830eac8f0151812677c4d6d2237e85cf01d6910cfb83954e76ba7352830534159897e8065780002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002c0074007271dcd04bb88bc3189119398a00000000eefafc76c146b823b096f8aacad365dd0030953f4edf625636e5f21bb2e23fcc654b1b5b40318d10d137abcbb87574e36e8a1f025f7dfa5d6e50781b5eda4aa15b0c8be778257d16aa3030e9e7841dd9e4c0342267e8ca0caf571fb2b7cff0f934b0002d00020101001c00024001001500af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +ServerHello = 160303007b020000770303bb341d847fd789c47c387172dc0c9bf147fccacb5043d86ca4c598d3ff571b9800130100004f003300450017004104583e054b7a66672ae020ad9d2686fcc85b5ad41a134a0f03ee72b893052bd85b4c8de6776f5b04ac07d83540eab3e3d9c547bc6528c4317d294686093a6cad7d002b00020304 +ServerHandshakeMessages = 170303029699bee20baf5b7fc727bfab6223928a381e6d0cf9c4da653f9d2a7b23f7de11cce842d5cf75631763450ffb8b0cc1d238e658af7a12adc86243114ab14a1da2fae42621ce483fb6242eabfaad52566b02b31d2eddedefeb80e66a9900d5f973b40c4fdf74719ecf1b68d7f9c3b6ceb903ca13dd1bb8f8187ae33417e1d152522c5822a1a03ad52c838c55953d610222874cce8e1790b229a2aa0b53c8d377ee720182951dc6181dc5d90bd1f0105ed1e84aa5f75957c6661897079e5ea5007449e3197bdc7c9beeedddeafdd844afa5c315ecfe65e576afe909812880620ec7048b42d7f5c78d76f299d6d82534bdd8f512febc0ed3814aca470cd8000d3e1cb9962b052fbb950df683a52c2ba77ed3713b122937a6e5170964e2ab7969dcd980b3db9b458da7603124d6dc005e4d6e04b4d0c4baf3275db827dbba0a6db09672171fc057b3851d7e026841e2978fbd2346bbefdd0376bb1108fe9acc92189f5650aa5e85d8e8c7b67ac510dba003d3d7e16350bb66d45013efd44c9b607c0d318c4c7d1a1f5cbc57e20611804e3787d7b4a4b5f08ed8fd70bdaeade02260b12ab842ef690b4a3ee7911e841b374ecd5ebbbc2a54d047b600336dd7d0c88b4bc10e58ee6cb656de7247fa20d8e91deb84628608cf80615b62e96c1491c7ac3755eb6901405d3474fe1ac79d106a0cee56c2577fc88480f96cb6b8c681b7b68b53c146093908f350888175bdfb0b1e31ad61e30ba0adfe6d223aa03c0783b5001a57587c328a9afcfcfb978d1cd4328f7d9d60530e630befd96c0c816ee20b0100768ae2a6df51fc68f172740a79af11398ee3be1252491fa9c693479e877f94ab7c5f8cad480203e6ab7b87dd71e8a0729113df17f5eee86ce108d1d72007ec1cd13c85a6c149621e77b7d78d805a30f0be030c315e54 +ClientFinished = 1703030035d74f1923c662fd34137c6f502f3dd2b93d951d1b3bc97e42afe23c31abea92fe91b474999e85e3b791ce252fe8c3e9f939a4120cb2 +Client_CloseNotify = 17030300132ea6cdf749196023e2b3a49491695536426047 +Server_CloseNotify = 1703030013519fc5075cb0884349759ff9ef6f011bb4c6f2 + +[Middlebox_Compatibility_Mode] +RNG_Pool = 4e640a3f2c2738f09c9418bd78edccd7559d0531199276d4d92a0e9ee9d77d09a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6efdea00b45695dc781f19d34a62c1afd31ab4369af1e855a3bbb258d8442cde6d7 +CurrentTimestamp = 1651826546000 +ClientHello_1 = 16030100e0010000dc03034e640a3f2c2738f09c9418bd78edccd7559d0531199276d4d92a0e9ee9d77d0920a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6ef00061301130313020100008d0000000b0009000006736572766572ff01000100000a00140012001d00170018001901000101010201030104003300260024001d00208e7292cf3056dbb0d25fcbe55c107dc9bbf83dd9708f39203ba341249a7d9b63002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001 +ServerHello = 160303007a020000760303e5dd5948c435f7a38f0f0130708dc322d9df09abd4838117c183a7bb6d994f2c20a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6ef130100002e00330024001d00203e30f0f4ba551afd62768341175f5265e4daf0c8841617aa4fafdd2142320c22002b00020304 +ServerHandshakeMessages = 14030300010117030302a248de891d9c3624a67a6c6f0601ab7ac20c1f6a9e14d2e6007e999e130367a8af1bcfea9498fbce19df4505eece3a25da523cbe55ea1b3bda4e91995e455d500a4faa6227b7111e1c8547e2d7c179db215303d25827f3cd18f48f6491328cf5c0f814d388150bd9e9264aae491db6995069bea17665d5e0c817284d4ac21880054c3657331e23a9304dc88a15c04ec80bd3852bf7f9d3c6615b15fac83bbca031c6d2310d9f5d7a4b020a4f7c19062b65c05a1d3264b557ec9d8e0f7cee27e36f793039de8dd96edfca9009e0651034bff31d7f349eece01d99fcb5fcab840d7707c72299c3b5d04564e880a33c5e846c762e3d922bb55303d1d87cc0f06573f17dcb9b8ffd35bbd883c1cb3aa24fcc325005f768ce2fb624ca97b6c4d98e17f35bc2c7940a06100c2d448db7180b2d862164435c9c210e9860394e05aab23ff1b0203f662c588da5bc4411477a30b41136c488a0a63fcab5c15ac613226dae827a1d1fe95ece6b30bcee1560a8d408d264555e760f9bfc624c2c87fd0456c9bfb41bcd1a7b212786d2b67fd57804facfa1eef7cf2919d8b998c9789f763b4d9caa093a9ded43175d46a76b4d54f0ce0c5d2259b607e30a9d241263874fa59d6f570dc40d83a2d83bf9e9850d454c57806535a8998ae0357df92f00b9667344c24114ccc9ef539124b204e7e6e748c30a28a3d1d183997243eaccbbd33b0c1115a0327106a1e6a75271d4983086f632ff0eb8b4c63102cbcef5bb72dae1279d5de8eb19096d8cdb07fa8ea989788fac23e66e0488c193f3f3fea8c8838896bf3ae4b6848d42ced4bdf41abe6fc331b44225e7a1f7d3564147d5458e71aa909cb02be958bbc42e3aa5a27cc6eaf4b6fe51ae4495694d8ab6320aab920183fd5b31a359042fbd67391ec5e4d1892a2e5210141a494e9301b24a113c474c7f2a73457847 +ClientFinished = 140303000101170303003532d030e273773a8696c799981af6ced07f87482e81565e394e87c867f33df3d65b7506f1a626af91d4821d5f7a1f210ef8dd3c6d16 +Client_CloseNotify = 17030300130f629155382dba23c4e2c5f7f84e6f2ed3083d +Server_CloseNotify = 1703030013b7257b0fecaf69d4f09e3f891e2a25d1e28845 diff --git a/src/tests/test_tls_rfc8448.cpp b/src/tests/test_tls_rfc8448.cpp index 3c29f03bc83..2792e99d3ca 100644 --- a/src/tests/test_tls_rfc8448.cpp +++ b/src/tests/test_tls_rfc8448.cpp @@ -43,6 +43,9 @@ #include #include + + #include + #include #endif namespace Botan_Tests { @@ -50,67 +53,27 @@ 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) +void add_entropy(Botan_Tests::Fixed_Output_RNG& rng, const std::vector& bin) { - std::vector in = Botan::hex_decode(hex); - rng.add_entropy(in.data(), in.size()); + rng.add_entropy(bin.data(), bin.size()); } -Botan::RSA_PrivateKey server_private_key() - { - return - { - Botan::BigInt("0xE435FB7CC83737756DACEA96AB7F59A2CC1069DB7DEB190E17E33A532B273F30A327AA0AAABC58CD67466AF9845FADC675FE094AF92C4BD1F2C1BC33DD2E0515"), - Botan::BigInt("0xCABD3BC0E0438664C8D4CC9F99977A94D9BBFEAD8E43870ABAE3F7EB8B4E0EEE8AF1D9B4719BA6196CF2CBBAEEEBF8B3490AFE9E9FFA74A88AA51FC645629303"), - Botan::BigInt("0x010001") - }; - } +// TODO: use this once the server side is being implemented +// std::unique_ptr server_private_key() +// { +// Botan::DataSource_Memory in(Test::read_data_file("tls_13_rfc8448/server_key.pem")); +// return Botan::PKCS8::load_key(in); +// } 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") - ); + // self-signed certificate with an RSA1024 public key valid until: + // Jul 30 01:23:59 2026 GMT + Botan::DataSource_Memory in(Test::read_data_file("tls_13_rfc8448/server_certificate.pem")); + return Botan::X509_Certificate(in); } - /** * Simple version of the Padding extension (RFC 7685) to reproduce the * 2nd Client_Hello in RFC8448 Section 5 (HelloRetryRequest) @@ -139,12 +102,30 @@ class Padding final : public Botan::TLS::Extension using namespace Botan; using namespace Botan::TLS; +std::chrono::system_clock::time_point from_milliseconds_since_epoch(uint64_t msecs) + { + const int64_t secs_since_epoch = msecs / 1000; + const uint32_t additional_millis = msecs % 1000; + + BOTAN_ASSERT_NOMSG(secs_since_epoch <= std::numeric_limits::max()); + return std::chrono::system_clock::from_time_t(static_cast(secs_since_epoch)) + + std::chrono::milliseconds(additional_millis); + } + +/** + * Subclass of the Botan::TLS::Callbacks instrumenting all available callbacks. + * The invocation counts can be checked in the integration tests to make sure + * all expected callbacks are hit. Furthermore collects the received application + * data and sent record bytes for further inspection by the test cases. + */ 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)) + Test_TLS_13_Callbacks(Modify_Exts_Fn modify_exts_cb, uint64_t timestamp) : + session_activated_called(false), + m_modify_exts(std::move(modify_exts_cb)), + m_timestamp(from_milliseconds_since_epoch(timestamp)) {} void tls_emit_data(const uint8_t data[], size_t size) override @@ -289,6 +270,12 @@ class Test_TLS_13_Callbacks : public Botan::TLS::Callbacks return Callbacks::tls_peer_network_identity(); } + std::chrono::system_clock::time_point tls_current_timestamp() override + { + count_callback_invocation("tls_current_timestamp"); + return m_timestamp; + } + std::vector pull_send_buffer() { return std::exchange(send_buffer, std::vector()); @@ -326,49 +313,15 @@ class Test_TLS_13_Callbacks : public Botan::TLS::Callbacks std::vector certificate_chain; private: - std::vector send_buffer; - std::vector receive_buffer; - uint64_t received_seq_no; - Modify_Exts_Fn m_modify_exts; + std::vector send_buffer; + std::vector receive_buffer; + uint64_t received_seq_no; + Modify_Exts_Fn m_modify_exts; + std::chrono::system_clock::time_point m_timestamp; 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: @@ -377,6 +330,10 @@ class RFC8448_Text_Policy : public Botan::TLS::Text_Policy std::vector allowed_signature_schemes() const override { + // We extend the allowed signature schemes with algorithms that we don't + // actually support. The nature of the RFC 8448 test forces us to generate + // bit-compatible TLS messages. Unfortunately, the test data offers all + // those algorithms in its Client Hellos. return { Botan::TLS::Signature_Scheme::ECDSA_SHA256, @@ -398,13 +355,20 @@ class RFC8448_Text_Policy : public Botan::TLS::Text_Policy } }; +/** + * This steers the TLS client handle and is the central entry point for the + * test cases to interact with the TLS 1.3 implementation. + * + * Note: This class is abstract to be subclassed for both client and server tests. + */ 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)) + Modify_Exts_Fn modify_exts_cb, + uint64_t timestamp) + : m_callbacks(std::move(modify_exts_cb), timestamp) , m_rng(std::move(rng_in)) , m_session_mgr(*m_rng) , m_policy(std::move(policy)) @@ -464,39 +428,22 @@ class TLS_Context virtual void send(const std::vector& data) = 0; protected: - Test_TLS_13_Callbacks m_callbacks; - Test_Server_Credentials m_creds; + Test_TLS_13_Callbacks m_callbacks; + Botan::Credentials_Manager 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)) + Modify_Exts_Fn modify_exts_cb, + uint64_t timestamp) + : TLS_Context(std::move(rng_in), std::move(policy), std::move(modify_exts_cb), timestamp) , client(m_callbacks, m_session_mgr, m_creds, m_policy, *m_rng, Botan::TLS::Server_Information("server"), Botan::TLS::Protocol_Version::TLS_V13) @@ -510,6 +457,10 @@ class Client_Context : public TLS_Context Botan::TLS::Client client; }; +/** + * Because of the nature of the RFC 8448 test data we need to produce bit-compatible + * TLS messages. Hence we sort the generated TLS extensions exactly as expected. + */ void sort_extensions(Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side) { if(side == Botan::TLS::Connection_Side::CLIENT) @@ -558,21 +509,57 @@ void add_renegotiation_extension(Botan::TLS::Extensions& exts) } // namespace -class Test_TLS_RFC8448 final : public Test +/** + * Traffic transcripts and supporting data for the TLS RFC 8448 and TLS policy + * configuration is kept in data files (accessible via `Test:::data_file()`). + * + * tls_13_rfc8448/transcripts.vec + * The record transcripts and RNG outputs as defined/required in RFC 8448 in + * Botan's Text_Based_Test vector format. Data from each RFC 8448 section is + * placed in a sub-section of the *.vec file. Each of those sections needs a + * specific test case implementation that is dispatched in `run_one_test()`. + * + * tls_13_rfc8448/client_certificate.pem + * The client certificate provided in RFC 8448 used to perform client auth. + * Note that RFC 8448 _does not_ provide the associated private key but only + * the resulting signature in the client's CertificateVerify message. + * + * tls_13_rfc8448/server_certificate.pem + * tls_13_rfc8448/server_key.pem + * The server certificate and its associated private key. + * + * tls-policy/rfc8448_*.txt + * Each RFC 8448 section test required a slightly adapted Botan TLS policy + * to enable/disable certain features under test. + */ +class Test_TLS_RFC8448 final : public Text_Based_Test { + public: + Test_TLS_RFC8448() + : Text_Based_Test("tls_13_rfc8448/transcripts.vec", "RNG_Pool,CurrentTimestamp,ClientHello_1,ServerHello,ServerHandshakeMessages,ClientFinished,Client_CloseNotify,Server_CloseNotify", "HelloRetryRequest,ClientHello_2,NewSessionTicket,Client_AppData,Client_AppData_Record,Server_AppData,Server_AppData_Record") {} + + Test::Result run_one_test(const std::string& header, + const VarMap& vars) override + { + if(header == "Simple_1RTT_Handshake") + return Test::Result("Simple 1-RTT (Client side)", simple_1_rtt_client_hello(vars)); + else if(header == "HelloRetryRequest_Handshake") + return Test::Result("Handshake involving Hello Retry Request (Client side)", hello_retry_request(vars)); + else if(header == "Middlebox_Compatibility_Mode") + return Test::Result("Middlebox Compatibility Mode (Client side)", middlebox_compatibility(vars)); + else + return Test::Result::Failure("test dispatcher", "unknown sub-test: " + header); + } + private: - static Test::Result simple_1_rtt_client_hello() + static std::vector simple_1_rtt_client_hello(const VarMap& vars) { - 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"); + // 32 - for client hello random + // 32 - for KeyShare (eph. x25519 key pair) + add_entropy(*rng, vars.get_req_bin("RNG_Pool")); auto add_extensions_and_sort = [](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side) { @@ -586,196 +573,104 @@ class Test_TLS_RFC8448 final : public Test 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; + std::unique_ptr ctx; + + return { + Botan_Tests::CHECK("Client Hello", [&](Test::Result& result) + { + ctx = std::make_unique(std::move(rng), read_tls_policy("rfc8448_1rtt"), add_extensions_and_sort, vars.get_req_u64("CurrentTimestamp")); + + 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" }); + + result.test_eq("TLS client hello", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_1")); + }), + + Botan_Tests::CHECK("Server Hello", [&](Test::Result& result) + { + const auto server_hello = vars.get_req_bin("ServerHello"); + // splitting the input data to test partial reads + const std::vector server_hello_a(server_hello.begin(), server_hello.begin() + 20); + const std::vector server_hello_b(server_hello.begin() + 20, server_hello.end()); + + ctx->client.received_data(server_hello_a); + ctx->check_callback_invocations(result, "server hello partially received", { }); + + 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()); + }), + + Botan_Tests::CHECK("Server HS messages .. Client Finished", [&](Test::Result& result) + { + ctx->client.received_data(vars.get_req_bin("ServerHandshakeMessages")); + + 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.require("correct certificate", ctx->certs_verified().front() == server_certificate()); + result.require("client is active", ctx->client.is_active()); + + result.test_eq("correct handshake finished", ctx->pull_send_buffer(), + vars.get_req_bin("ClientFinished")); + }), + + Botan_Tests::CHECK("Post-Handshake: NewSessionTicket", [&](Test::Result& result) + { + ctx->client.received_data(vars.get_req_bin("NewSessionTicket")); + + // TODO: once we implement session resumption, this should probably expect some callback + ctx->check_callback_invocations(result, "new session ticket received", { }); + }), + + Botan_Tests::CHECK("Send Application Data", [&](Test::Result& result) + { + ctx->send(vars.get_req_bin("Client_AppData")); + + ctx->check_callback_invocations(result, "application data sent", { "tls_emit_data" }); + + result.test_eq("correct client application data", ctx->pull_send_buffer(), + vars.get_req_bin("Client_AppData_Record")); + }), + + Botan_Tests::CHECK("Receive Application Data", [&](Test::Result& result) + { + ctx->client.received_data(vars.get_req_bin("Server_AppData_Record")); + + 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, vars.get_req_bin("Server_AppData")); + result.test_is_eq("sequence number", ctx->last_received_seq_no(), uint64_t(1)); + }), + + Botan_Tests::CHECK("Close Connection", [&](Test::Result& result) + { + ctx->client.close(); + + result.test_eq("close payload", ctx->pull_send_buffer(), vars.get_req_bin("Client_CloseNotify")); + ctx->check_callback_invocations(result, "CLOSE_NOTIFY sent", { "tls_emit_data" }); + + ctx->client.received_data(vars.get_req_bin("Server_CloseNotify")); + ctx->check_callback_invocations(result, "CLOSE_NOTIFY received", { "tls_alert" }); + + result.confirm("connection is closed", ctx->client.is_closed()); + }), + }; } - static Test::Result hello_retry_request() + static std::vector hello_retry_request(const VarMap& vars) { - 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; @@ -801,197 +696,92 @@ class Test_TLS_RFC8448 final : public Test 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"); + // 32 - client hello random + // 32 - eph. x25519 key pair + // 32 - eph. P-256 key pair + add_entropy(*rng, vars.get_req_bin("RNG_Pool")); - const auto client_finished = ctx.pull_send_buffer(); - result.test_eq("client finished", client_finished, expected_client_finished); + std::unique_ptr ctx; - 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(); + return { + Botan_Tests::CHECK("Client Hello", [&](Test::Result& result) + { + ctx = std::make_unique(std::move(rng), read_tls_policy("rfc8448_hrr"), add_extensions_and_sort, vars.get_req_u64("CurrentTimestamp")); + result.confirm("client not closed", !ctx->client.is_closed()); - ctx.check_callback_invocations(result, "encrypted handshake messages received", { "tls_emit_data" }); + ctx->check_callback_invocations(result, "client hello prepared", { "tls_emit_data", "tls_inspect_handshake_msg_client_hello", "tls_modify_extensions" }); - result.test_eq("client close notify", ctx.pull_send_buffer(), expected_client_close_notify); + result.test_eq("TLS client hello (1)", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_1")); + }), - 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"); + Botan_Tests::CHECK("Hello Retry Request .. second Client Hello", [&](Test::Result& result) + { + ctx->client.received_data(vars.get_req_bin("HelloRetryRequest")); + + 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" + }); + + result.test_eq("TLS client hello (2)", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_2")); + }), + + Botan_Tests::CHECK("Server Hello", [&](Test::Result& result) + { + ctx->client.received_data(vars.get_req_bin("ServerHello")); - ctx.client.received_data(server_close_notify); + ctx->check_callback_invocations(result, "server hello received", { "tls_inspect_handshake_msg_server_hello", "tls_examine_extensions", "tls_decode_group_param" }); + }), - ctx.check_callback_invocations(result, "encrypted handshake messages received", { "tls_alert" }); + Botan_Tests::CHECK("Server HS Messages .. Client Finished", [&](Test::Result& result) + { + ctx->client.received_data(vars.get_req_bin("ServerHandshakeMessages")); + + 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.test_eq("client finished", ctx->pull_send_buffer(), vars.get_req_bin("ClientFinished")); + }), + + Botan_Tests::CHECK("Close Connection", [&](Test::Result& result) + { + 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(), vars.get_req_bin("Client_CloseNotify")); - result.confirm("connection is closed", ctx.client.is_closed()); + ctx->client.received_data(vars.get_req_bin("Server_CloseNotify")); + ctx->check_callback_invocations(result, "encrypted handshake messages received", { "tls_alert" }); - return result; + result.confirm("connection is closed", ctx->client.is_closed()); + }), + }; } - static Test::Result middlebox_compatibility() + static std::vector middlebox_compatibility(const VarMap& vars) { - 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"); + // 32 - client hello random + // 32 - legacy session ID + // 32 - eph. x25519 key pair + add_entropy(*rng, vars.get_req_bin("RNG_Pool")); auto add_extensions_and_sort = [&](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side) { @@ -1000,111 +790,42 @@ class Test_TLS_RFC8448 final : public Test 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; - } + std::unique_ptr ctx; - public: - std::vector run() override - { - return - { - simple_1_rtt_client_hello(), - hello_retry_request(), - middlebox_compatibility() + return { + Botan_Tests::CHECK("Client Hello", [&](Test::Result& result) + { + ctx = std::make_unique(std::move(rng), read_tls_policy("rfc8448_compat"), add_extensions_and_sort, vars.get_req_u64("CurrentTimestamp")); + + result.test_eq("Client Hello", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_1")); + }), + + Botan_Tests::CHECK("Server Hello + other handshake messages", [&](Test::Result& result) + { + ctx->client.received_data(Botan::concat(vars.get_req_bin("ServerHello"), + // ServerHandshakeMessages contains the expected ChangeCipherSpec record + vars.get_req_bin("ServerHandshakeMessages"))); + + result.test_eq("CCS + Client Finished", ctx->pull_send_buffer(), + // ClientFinished contains the expected ChangeCipherSpec record + vars.get_req_bin("ClientFinished")); + + result.confirm("client is ready to send application traffic", ctx->client.is_active()); + }), + + Botan_Tests::CHECK("Close connection", [&](Test::Result& result) + { + ctx->client.close(); + + result.test_eq("Client close_notify", ctx->pull_send_buffer(), vars.get_req_bin("Client_CloseNotify")); + + result.require("client cannot send application traffic anymore", !ctx->client.is_active()); + result.require("client is not fully closed yet", !ctx->client.is_closed()); + + ctx->client.received_data(vars.get_req_bin("Server_CloseNotify")); + + result.confirm("client connection was terminated", ctx->client.is_closed()); + }), }; } }; From 27abd98dd381eb12b49df80f42b8b90114a06e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 27 Apr 2022 17:42:50 +0530 Subject: [PATCH 07/20] remove dead code --- src/bogo_shim/bogo_shim.cpp | 18 ------------------ src/lib/tls/msg_certificate_13.cpp | 16 ++-------------- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp index 78de1970d72..4581e978823 100644 --- a/src/bogo_shim/bogo_shim.cpp +++ b/src/bogo_shim/bogo_shim.cpp @@ -840,19 +840,6 @@ class Shim_Policy final : public Botan::TLS::Policy //std::vector allowed_macs() const override; - std::vector allowed_key_exchange_methods() const override - { - return { - "ECDHE_PSK", - "DHE_PSK", - "PSK", - "CECPQ1", - "ECDH", - "DH", - "RSA", - }; - } - std::vector allowed_signature_methods() const override { return { @@ -1078,11 +1065,6 @@ class Shim_Policy final : public Botan::TLS::Policy //size_t dtls_maximum_timeout() const override; - bool allow_resumption_for_renegotiation() const override - { - return false; // BoGo expects this - } - bool abort_connection_on_undesired_renegotiation() const override { if(m_args.flag_set("renegotiate-ignore")) diff --git a/src/lib/tls/msg_certificate_13.cpp b/src/lib/tls/msg_certificate_13.cpp index a75df2613e6..b568f27e0bd 100644 --- a/src/lib/tls/msg_certificate_13.cpp +++ b/src/lib/tls/msg_certificate_13.cpp @@ -196,20 +196,8 @@ Certificate_13::Certificate_13(const std::vector& buf, */ 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; + // Needed only for server implementation or client authentication + throw Not_Implemented("NYI"); } } From 70ac48a599f6393fc49ee775321ee82dc62a6487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Mon, 30 May 2022 16:44:31 +0200 Subject: [PATCH 08/20] FIX: unguarded nullptr when receiving an early close_notify --- src/lib/tls/tls13/tls_channel_impl_13.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/tls/tls13/tls_channel_impl_13.cpp b/src/lib/tls/tls13/tls_channel_impl_13.cpp index 725f3950803..682fd86d0d5 100644 --- a/src/lib/tls/tls13/tls_channel_impl_13.cpp +++ b/src/lib/tls/tls13/tls_channel_impl_13.cpp @@ -299,6 +299,7 @@ void Channel_Impl_13::update_traffic_keys(bool request_peer_update) { BOTAN_STATE_CHECK(!is_downgrading()); BOTAN_STATE_CHECK(handshake_finished()); + BOTAN_ASSERT_NONNULL(m_cipher_state); send_post_handshake_message(Key_Update(request_peer_update)); m_cipher_state->update_write_keys(); } @@ -326,7 +327,8 @@ void Channel_Impl_13::process_alert(const secure_vector& record) if(is_close_notify_alert(alert)) { m_can_read = false; - m_cipher_state->clear_read_keys(); + if(m_cipher_state) + m_cipher_state->clear_read_keys(); m_record_layer.clear_read_buffer(); } From afaf8bc7113567e7c6f9a0ca4877f471df3334b8 Mon Sep 17 00:00:00 2001 From: Hannes Rantzsch Date: Thu, 14 Apr 2022 12:00:22 +0200 Subject: [PATCH 09/20] make Signature_Scheme a class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note that this affects the public API of TLS::Policy and Signature_Scheme Co-authored-by: René Meusel --- src/bogo_shim/bogo_shim.cpp | 31 ++- src/cli/tls_utils.cpp | 4 +- src/lib/tls/info.txt | 1 + src/lib/tls/msg_cert_req.cpp | 2 +- src/lib/tls/msg_cert_verify.cpp | 32 +-- src/lib/tls/tls12/msg_server_kex.cpp | 9 +- src/lib/tls/tls12/tls_handshake_state.cpp | 73 ++---- src/lib/tls/tls12/tls_handshake_state.h | 1 + src/lib/tls/tls12/tls_server_impl_12.cpp | 6 +- src/lib/tls/tls13/tls_client_impl_13.cpp | 2 +- src/lib/tls/tls_algos.cpp | 298 ---------------------- src/lib/tls/tls_algos.h | 42 --- src/lib/tls/tls_extensions.cpp | 9 +- src/lib/tls/tls_extensions.h | 1 + src/lib/tls/tls_messages.h | 4 +- src/lib/tls/tls_policy.cpp | 8 +- src/lib/tls/tls_policy.h | 2 +- src/lib/tls/tls_signature_scheme.cpp | 297 +++++++++++++++++++++ src/lib/tls/tls_signature_scheme.h | 100 ++++++++ src/tests/test_tls.cpp | 4 +- src/tests/test_tls_messages.cpp | 2 +- src/tests/test_tls_signature_scheme.cpp | 74 ++++++ src/tests/unit_tls.cpp | 2 +- 23 files changed, 551 insertions(+), 453 deletions(-) create mode 100644 src/lib/tls/tls_signature_scheme.cpp create mode 100644 src/lib/tls/tls_signature_scheme.h create mode 100644 src/tests/test_tls_signature_scheme.cpp diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp index 4581e978823..b80bf4976db 100644 --- a/src/bogo_shim/bogo_shim.cpp +++ b/src/bogo_shim/bogo_shim.cpp @@ -822,10 +822,10 @@ class Shim_Policy final : public Botan::TLS::Policy std::vector pref_hash; for(size_t pref : m_args.get_int_vec_opt("signing-prefs")) { - const auto scheme = static_cast(pref); - if(Botan::TLS::signature_scheme_is_known(scheme) == false) + const Botan::TLS::Signature_Scheme scheme(pref); + if(!scheme.is_available()) continue; - pref_hash.push_back(Botan::TLS::hash_function_of_scheme(scheme)); + pref_hash.push_back(scheme.hash_function_name()); } if(m_args.flag_set("server")) @@ -857,7 +857,7 @@ class Shim_Policy final : public Botan::TLS::Policy std::vector schemes; for(size_t pref : m_args.get_int_vec_opt("verify-prefs")) { - schemes.push_back(static_cast(pref)); + schemes.emplace_back(static_cast(pref)); } return schemes; @@ -873,14 +873,14 @@ class Shim_Policy final : public Botan::TLS::Policy std::vector schemes; for(size_t pref : m_args.get_int_vec_opt("signing-prefs")) { - schemes.push_back(static_cast(pref)); + schemes.emplace_back(static_cast(pref)); } // BoGo gets sad if these are not included in our signature_algorithms extension if(!m_args.flag_set("server")) { - schemes.push_back(Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256); - schemes.push_back(Botan::TLS::Signature_Scheme::ECDSA_SHA256); + schemes.emplace_back(Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256); + schemes.emplace_back(Botan::TLS::Signature_Scheme::ECDSA_SHA256); } return schemes; @@ -1381,14 +1381,17 @@ class Shim_Callbacks final : public Botan::TLS::Callbacks { if(m_args.option_used("expect-peer-signature-algorithm")) { - const auto scheme = static_cast(m_args.get_int_opt("expect-peer-signature-algorithm")); - if(scheme != Botan::TLS::Signature_Scheme::NONE) - { - const std::string exp_emsa = Botan::TLS::padding_string_for_scheme(scheme); - if(emsa != exp_emsa) - shim_exit_with_error("Unexpected signature scheme got " + emsa + " expected " + exp_emsa); - } + const Botan::TLS::Signature_Scheme scheme( + static_cast(m_args.get_int_opt("expect-peer-signature-algorithm"))); + + if(!scheme.is_available()) + shim_exit_with_error(std::string("Unsupported signature scheme provided by BoGo: ") + scheme.to_string()); + + const std::string exp_emsa = scheme.padding_string(); + if(emsa != exp_emsa) + shim_exit_with_error("Unexpected signature scheme got " + emsa + " expected " + exp_emsa); } + return Botan::TLS::Callbacks::tls_verify_message(key, emsa, format, msg, sig); } diff --git a/src/cli/tls_utils.cpp b/src/cli/tls_utils.cpp index 37f5de03d17..591c20c9f41 100644 --- a/src/cli/tls_utils.cpp +++ b/src/cli/tls_utils.cpp @@ -183,12 +183,12 @@ class TLS_Client_Hello_Reader final : public Command { try { - auto s = sig_scheme_to_string(scheme); + auto s = scheme.to_string(); oss << s << " "; } catch(...) { - oss << "(" << std::hex << static_cast(scheme) << ") "; + oss << "(" << std::hex << scheme.wire_code() << ") "; } } oss << "\n"; diff --git a/src/lib/tls/info.txt b/src/lib/tls/info.txt index 65f6b2f9760..f0dd17a7e98 100644 --- a/src/lib/tls/info.txt +++ b/src/lib/tls/info.txt @@ -20,6 +20,7 @@ tls_policy.h tls_server.h tls_session.h tls_session_manager.h +tls_signature_scheme.h tls_version.h diff --git a/src/lib/tls/msg_cert_req.cpp b/src/lib/tls/msg_cert_req.cpp index aeb5c1a4e59..241bfe1e5c3 100644 --- a/src/lib/tls/msg_cert_req.cpp +++ b/src/lib/tls/msg_cert_req.cpp @@ -96,7 +96,7 @@ Certificate_Req::Certificate_Req(const std::vector& buf) for(size_t i = 0; i != algs.size(); i += 2) { - m_schemes.push_back(static_cast(make_uint16(algs[i], algs[i+1]))); + m_schemes.emplace_back(make_uint16(algs[i], algs[i+1])); } const uint16_t purported_size = reader.get_uint16_t(); diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp index d2f76276b16..453166787e2 100644 --- a/src/lib/tls/msg_cert_verify.cpp +++ b/src/lib/tls/msg_cert_verify.cpp @@ -47,11 +47,11 @@ Certificate_Verify::Certificate_Verify(const std::vector& buf) { TLS_Data_Reader reader("CertificateVerify", buf); - m_scheme = static_cast(reader.get_uint16_t()); + m_scheme = Signature_Scheme(reader.get_uint16_t()); m_signature = reader.get_range(2, 0, 65535); reader.assert_done(); - if(m_scheme == Signature_Scheme::NONE) + if(!m_scheme.is_set()) { throw Decoding_Error("Counterparty did not send hash/sig IDS"); } } @@ -62,11 +62,11 @@ std::vector Certificate_Verify::serialize() const { std::vector buf; - if(m_scheme != Signature_Scheme::NONE) + // TODO: both for TLS 1.2 and 1.3 this should never happen and the message would be invalid + if(m_scheme.is_set()) { - const uint16_t scheme_code = static_cast(m_scheme); - buf.push_back(get_byte<0>(scheme_code)); - buf.push_back(get_byte<1>(scheme_code)); + buf.push_back(get_byte<0>(m_scheme.wire_code())); + buf.push_back(get_byte<1>(m_scheme.wire_code())); } if(m_signature.size() > 0xFFFF) @@ -113,24 +113,19 @@ Certificate_Verify_13::Certificate_Verify_13(const std::vector& buf, : Certificate_Verify(buf) , m_side(side) { - if(!signature_scheme_is_known(m_scheme)) + if(!m_scheme.is_available()) { throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Peer sent unknown signature scheme"); } // RFC 8446 4.4.3: // The SHA-1 algorithm MUST NOT be used in any signatures of // CertificateVerify messages. - if(m_scheme == Signature_Scheme::RSA_PKCS1_SHA1 - || m_scheme == Signature_Scheme::ECDSA_SHA1 - || m_scheme == Signature_Scheme::DSA_SHA1) + if(m_scheme.is_sha1()) { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "SHA-1 algorithm must not be used"); } // 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(m_scheme == Signature_Scheme::RSA_PKCS1_SHA1 - || m_scheme == Signature_Scheme::RSA_PKCS1_SHA256 - || m_scheme == Signature_Scheme::RSA_PKCS1_SHA384 - || m_scheme == Signature_Scheme::RSA_PKCS1_SHA512) + if(m_scheme.is_pkcs1()) { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "RSA signatures must use an RSASSA-PSS algorithm"); } } @@ -141,12 +136,12 @@ bool Certificate_Verify_13::verify(const X509_Certificate& cert, Callbacks& callbacks, const Transcript_Hash& transcript_hash) const { - auto key = cert.load_subject_public_key(); + BOTAN_ASSERT_NOMSG(m_scheme.is_available()); // 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()) + if(m_scheme.algorithm_identifier() != 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); @@ -161,10 +156,11 @@ bool Certificate_Verify_13::verify(const X509_Certificate& cert, msg.insert(msg.end(), transcript_hash.cbegin(), transcript_hash.cend()); + const auto key = cert.load_subject_public_key(); const bool signature_valid = callbacks.tls_verify_message(*key, - padding_string_for_scheme(m_scheme), - signature_format_of_scheme(m_scheme), + m_scheme.padding_string(), + m_scheme.format().value(), msg, m_signature); diff --git a/src/lib/tls/tls12/msg_server_kex.cpp b/src/lib/tls/tls12/msg_server_kex.cpp index f65c71ee9c9..2856a715c4c 100644 --- a/src/lib/tls/tls12/msg_server_kex.cpp +++ b/src/lib/tls/tls12/msg_server_kex.cpp @@ -216,7 +216,7 @@ Server_Key_Exchange::Server_Key_Exchange(const std::vector& buf, if(auth_method != Auth_Method::IMPLICIT) { - m_scheme = static_cast(reader.get_uint16_t()); + m_scheme = Signature_Scheme(reader.get_uint16_t()); m_signature = reader.get_range(2, 0, 65535); } @@ -232,11 +232,10 @@ std::vector Server_Key_Exchange::serialize() const if(!m_signature.empty()) { - if(m_scheme != Signature_Scheme::NONE) + if(m_scheme.is_set()) { - const uint16_t scheme_code = static_cast(m_scheme); - buf.push_back(get_byte<0>(scheme_code)); - buf.push_back(get_byte<1>(scheme_code)); + buf.push_back(get_byte<0>(m_scheme.wire_code())); + buf.push_back(get_byte<1>(m_scheme.wire_code())); } append_tls_length_value(buf, m_signature, 2); diff --git a/src/lib/tls/tls12/tls_handshake_state.cpp b/src/lib/tls/tls12/tls_handshake_state.cpp index 3f100bc18b0..2ce835318e0 100644 --- a/src/lib/tls/tls12/tls_handshake_state.cpp +++ b/src/lib/tls/tls12/tls_handshake_state.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -287,12 +288,12 @@ Handshake_State::choose_sig_format(const Private_Key& key, for(Signature_Scheme scheme : allowed) { - if(signature_scheme_is_known(scheme) == false) + if(!scheme.is_available()) { continue; } - if(signature_algorithm_of_scheme(scheme) == sig_algo) + if(scheme.algorithm_name() == sig_algo) { if(std::find(requested.begin(), requested.end(), scheme) != requested.end()) { @@ -302,7 +303,7 @@ Handshake_State::choose_sig_format(const Private_Key& key, } } - const std::string hash = hash_function_of_scheme(chosen_scheme); + const std::string hash = chosen_scheme.hash_function_name(); if(!policy.allowed_signature_hash(hash)) { @@ -310,16 +311,10 @@ Handshake_State::choose_sig_format(const Private_Key& key, "Policy refuses to accept signing with any hash supported by peer"); } - if(sig_algo == "RSA") - { - return std::make_pair(padding_string_for_scheme(chosen_scheme), IEEE_1363); - } - else if(sig_algo == "DSA" || sig_algo == "ECDSA") - { - return std::make_pair(padding_string_for_scheme(chosen_scheme), DER_SEQUENCE); - } + if(!chosen_scheme.format().has_value()) + { throw Invalid_Argument(sig_algo + " is invalid/unknown for TLS signatures"); } - throw Invalid_Argument(sig_algo + " is invalid/unknown for TLS signatures"); + return std::make_pair(chosen_scheme.padding_string(), chosen_scheme.format().value()); } namespace { @@ -331,9 +326,9 @@ bool supported_algos_include( { for(Signature_Scheme scheme : schemes) { - if(signature_scheme_is_known(scheme) && - hash_function_of_scheme(scheme) == hash_type && - signature_algorithm_of_scheme(scheme) == key_type) + if(scheme.is_available() && + hash_type == scheme.hash_function_name() && + key_type == scheme.algorithm_name()) { return true; } @@ -359,10 +354,13 @@ Handshake_State::parse_sig_format(const Public_Key& key, "Rejecting " + key_type + " signature"); } - if(scheme == Signature_Scheme::NONE) - { throw Decoding_Error("Counterparty did not send hash/sig IDS"); } + if(!scheme.is_available()) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Peer sent unknown signature scheme"); + } - if(key_type != signature_algorithm_of_scheme(scheme)) + if(key_type != scheme.algorithm_name()) { throw Decoding_Error("Counterparty sent inconsistent key and sig types"); } if(for_client_auth && !cert_req()) @@ -380,21 +378,13 @@ Handshake_State::parse_sig_format(const Public_Key& key, for_client_auth ? cert_req()->signature_schemes() : offered_schemes; - 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); + const std::string hash_algo = scheme.hash_function_name(); // 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(scheme.is_sha1()) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "SHA-1 algorithm must not be used"); } if(!supported_algos_include(supported_algos, key_type, hash_algo)) { @@ -403,29 +393,10 @@ Handshake_State::parse_sig_format(const Public_Key& key, key_type + "/" + hash_algo + " signature"); } + if(!scheme.format().has_value()) + { throw Invalid_Argument(key_type + " is invalid/unknown for TLS signatures"); } - // 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); - } - 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"); + return std::make_pair(scheme.padding_string(), scheme.format().value()); } } diff --git a/src/lib/tls/tls12/tls_handshake_state.h b/src/lib/tls/tls12/tls_handshake_state.h index 97f1abf1b7e..a249940f7ab 100644 --- a/src/lib/tls/tls12/tls_handshake_state.h +++ b/src/lib/tls/tls12/tls_handshake_state.h @@ -30,6 +30,7 @@ namespace TLS { class Callbacks; class Policy; +class Signature_Scheme; class Hello_Verify_Request; class Client_Hello_12; diff --git a/src/lib/tls/tls12/tls_server_impl_12.cpp b/src/lib/tls/tls12/tls_server_impl_12.cpp index 36d5a3cddef..ce307e70725 100644 --- a/src/lib/tls/tls12/tls_server_impl_12.cpp +++ b/src/lib/tls/tls12/tls_server_impl_12.cpp @@ -216,11 +216,11 @@ uint16_t choose_ciphersuite( for(Signature_Scheme scheme : client_sig_methods) { - if(signature_scheme_is_known(scheme) == false) + if(!scheme.is_available()) continue; - if(signature_algorithm_of_scheme(scheme) == suite->sig_algo() && - policy.allowed_signature_hash(hash_function_of_scheme(scheme))) + if(scheme.algorithm_name() == suite->sig_algo() && + policy.allowed_signature_hash(scheme.hash_function_name())) { we_support_some_hash_by_client = true; } diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index 30865e28a35..13809d86c21 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -387,7 +387,7 @@ void Client_Impl_13::handle(const Certificate_Verify_13& certificate_verify_msg) { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "We did not offer the usage of " + - sig_scheme_to_string(certificate_verify_msg.signature_scheme()) + + certificate_verify_msg.signature_scheme().to_string() + " as a signature scheme"); } diff --git a/src/lib/tls/tls_algos.cpp b/src/lib/tls/tls_algos.cpp index be437257c65..93ccac8b4b2 100644 --- a/src/lib/tls/tls_algos.cpp +++ b/src/lib/tls/tls_algos.cpp @@ -178,302 +178,4 @@ 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 Decoding_Error("oid_for_scheme: Unsupported signature scheme"); - } - - Botan::unreachable(); - } - -Signature_Format signature_format_of_scheme(Signature_Scheme scheme) - { - switch(scheme) - { - case Signature_Scheme::RSA_PKCS1_SHA1: - 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 IEEE_1363; - - case Signature_Scheme::ECDSA_SHA1: - case Signature_Scheme::ECDSA_SHA256: - case Signature_Scheme::ECDSA_SHA384: - case Signature_Scheme::ECDSA_SHA512: - case Signature_Scheme::EDDSA_25519: - case Signature_Scheme::EDDSA_448: - case Signature_Scheme::DSA_SHA1: - case Signature_Scheme::DSA_SHA256: - case Signature_Scheme::DSA_SHA384: - case Signature_Scheme::DSA_SHA512: - return DER_SEQUENCE; - - case Signature_Scheme::NONE: - throw Invalid_State("signature_format_of_scheme: Unsupported signature scheme"); - } - - Botan::unreachable(); - } - -std::string hash_function_of_scheme(Signature_Scheme scheme) - { - switch(scheme) - { - case Signature_Scheme::ECDSA_SHA256: - case Signature_Scheme::RSA_PKCS1_SHA256: - case Signature_Scheme::RSA_PSS_SHA256: - return "SHA-256"; - - case Signature_Scheme::ECDSA_SHA384: - case Signature_Scheme::RSA_PKCS1_SHA384: - case Signature_Scheme::RSA_PSS_SHA384: - return "SHA-384"; - - case Signature_Scheme::ECDSA_SHA512: - case Signature_Scheme::RSA_PKCS1_SHA512: - case Signature_Scheme::RSA_PSS_SHA512: - return "SHA-512"; - - case Signature_Scheme::EDDSA_25519: - 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 ""; - } - - throw Invalid_State("hash_function_of_scheme: Unknown signature algorithm enum"); - } - -const std::vector& all_signature_schemes() - { - /* - * This is ordered in some approximate order of preference - */ - static const std::vector all_schemes = { - //Signature_Scheme::EDDSA_448, - //Signature_Scheme::EDDSA_25519, - - Signature_Scheme::RSA_PSS_SHA384, - Signature_Scheme::RSA_PSS_SHA256, - Signature_Scheme::RSA_PSS_SHA512, - - Signature_Scheme::RSA_PKCS1_SHA384, - Signature_Scheme::RSA_PKCS1_SHA512, - Signature_Scheme::RSA_PKCS1_SHA256, - - Signature_Scheme::ECDSA_SHA384, - Signature_Scheme::ECDSA_SHA512, - Signature_Scheme::ECDSA_SHA256, - }; - - return all_schemes; - } - -bool signature_scheme_is_known(Signature_Scheme scheme) - { - switch(scheme) - { - 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: - - case Signature_Scheme::ECDSA_SHA256: - case Signature_Scheme::ECDSA_SHA384: - 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; - } - - } - -std::string signature_algorithm_of_scheme(Signature_Scheme scheme) - { - switch(scheme) - { - 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"; - - case Signature_Scheme::ECDSA_SHA256: - case Signature_Scheme::ECDSA_SHA384: - case Signature_Scheme::ECDSA_SHA512: - return "ECDSA"; - - case Signature_Scheme::EDDSA_25519: - return "Ed25519"; - - 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 ""; - } - - throw Invalid_State("signature_algorithm_of_scheme: Unknown signature algorithm enum"); - } - -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: - return "RSA_PKCS1_SHA384"; - 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: - return "ECDSA_SHA384"; - case Signature_Scheme::ECDSA_SHA512: - return "ECDSA_SHA512"; - - case Signature_Scheme::RSA_PSS_SHA256: - return "RSA_PSS_SHA256"; - case Signature_Scheme::RSA_PSS_SHA384: - return "RSA_PSS_SHA384"; - case Signature_Scheme::RSA_PSS_SHA512: - return "RSA_PSS_SHA512"; - - case Signature_Scheme::EDDSA_25519: - return "EDDSA_25519"; - 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 ""; - } - - throw Invalid_State("sig_scheme_to_string: Unknown signature algorithm enum"); - } - -std::string padding_string_for_scheme(Signature_Scheme scheme) - { - switch(scheme) - { - case Signature_Scheme::RSA_PKCS1_SHA256: - return "EMSA_PKCS1(SHA-256)"; - case Signature_Scheme::RSA_PKCS1_SHA384: - return "EMSA_PKCS1(SHA-384)"; - case Signature_Scheme::RSA_PKCS1_SHA512: - return "EMSA_PKCS1(SHA-512)"; - - case Signature_Scheme::ECDSA_SHA256: - return "EMSA1(SHA-256)"; - case Signature_Scheme::ECDSA_SHA384: - return "EMSA1(SHA-384)"; - case Signature_Scheme::ECDSA_SHA512: - return "EMSA1(SHA-512)"; - - case Signature_Scheme::RSA_PSS_SHA256: - return "PSSR(SHA-256,MGF1,32)"; - case Signature_Scheme::RSA_PSS_SHA384: - return "PSSR(SHA-384,MGF1,48)"; - case Signature_Scheme::RSA_PSS_SHA512: - return "PSSR(SHA-512,MGF1,64)"; - - case Signature_Scheme::EDDSA_25519: - return "Pure"; - 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 ""; - } - - throw Invalid_State("padding_string_for_scheme: Unknown signature algorithm enum"); - } - } diff --git a/src/lib/tls/tls_algos.h b/src/lib/tls/tls_algos.h index bc19807a2bf..9ba5e5eef06 100644 --- a/src/lib/tls/tls_algos.h +++ b/src/lib/tls/tls_algos.h @@ -77,48 +77,6 @@ enum class Auth_Method { std::string BOTAN_TEST_API auth_method_to_string(Auth_Method method); Auth_Method BOTAN_TEST_API auth_method_from_string(const std::string& str); -/* -* This matches the wire encoding -*/ -enum class Signature_Scheme : uint16_t { - NONE = 0x0000, - - RSA_PKCS1_SHA256 = 0x0401, - RSA_PKCS1_SHA384 = 0x0501, - RSA_PKCS1_SHA512 = 0x0601, - - ECDSA_SHA256 = 0x0403, - ECDSA_SHA384 = 0x0503, - ECDSA_SHA512 = 0x0603, - - RSA_PSS_SHA256 = 0x0804, - RSA_PSS_SHA384 = 0x0805, - RSA_PSS_SHA512 = 0x0806, - - 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(); - -bool BOTAN_UNSTABLE_API signature_scheme_is_known(Signature_Scheme scheme); -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); -Signature_Format signature_format_of_scheme(Signature_Scheme scheme); - /* * Matches with wire encoding */ diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp index 9519ef05589..23a0655534e 100644 --- a/src/lib/tls/tls_extensions.cpp +++ b/src/lib/tls/tls_extensions.cpp @@ -487,10 +487,8 @@ std::vector Signature_Algorithms::serialize(Connection_Side /*whoami*/) for(Signature_Scheme scheme : m_schemes) { - const uint16_t scheme_code = static_cast(scheme); - - buf.push_back(get_byte<0>(scheme_code)); - buf.push_back(get_byte<1>(scheme_code)); + buf.push_back(get_byte<0>(scheme.wire_code())); + buf.push_back(get_byte<1>(scheme.wire_code())); } return buf; @@ -508,8 +506,7 @@ Signature_Algorithms::Signature_Algorithms(TLS_Data_Reader& reader, while(len) { - const uint16_t scheme_code = reader.get_uint16_t(); - m_schemes.push_back(static_cast(scheme_code)); + m_schemes.emplace_back(reader.get_uint16_t()); len -= 2; } } diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h index ca534d0c4b6..00368bdb7a6 100644 --- a/src/lib/tls/tls_extensions.h +++ b/src/lib/tls/tls_extensions.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h index 63324d9e1da..1acb3e1c98f 100644 --- a/src/lib/tls/tls_messages.h +++ b/src/lib/tls/tls_messages.h @@ -617,7 +617,7 @@ class BOTAN_UNSTABLE_API Certificate_Verify : public Handshake_Message protected: std::vector m_signature; - Signature_Scheme m_scheme = Signature_Scheme::NONE; + Signature_Scheme m_scheme; }; /** @@ -773,7 +773,7 @@ class BOTAN_UNSTABLE_API Server_Key_Exchange final : public Handshake_Message std::vector m_params; std::vector m_signature; - Signature_Scheme m_scheme = Signature_Scheme::NONE; + Signature_Scheme m_scheme; }; /** diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index 608a9201b34..3e2e4476b19 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -23,12 +23,10 @@ std::vector Policy::allowed_signature_schemes() const { std::vector schemes; - for(Signature_Scheme scheme : all_signature_schemes()) + for(Signature_Scheme scheme : Signature_Scheme::all_available_schemes()) { - if(signature_scheme_is_known(scheme) == false) - continue; - const bool sig_allowed = allowed_signature_method(signature_algorithm_of_scheme(scheme)); - const bool hash_allowed = allowed_signature_hash(hash_function_of_scheme(scheme)); + const bool sig_allowed = allowed_signature_method(scheme.algorithm_name()); + const bool hash_allowed = allowed_signature_hash(scheme.hash_function_name()); if(sig_allowed && hash_allowed) { diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index 2aa901e0c5d..ee19fcf709a 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -10,8 +10,8 @@ #define BOTAN_TLS_POLICY_H_ #include -#include #include +#include #include #include #include diff --git a/src/lib/tls/tls_signature_scheme.cpp b/src/lib/tls/tls_signature_scheme.cpp new file mode 100644 index 00000000000..ae0fd0abcff --- /dev/null +++ b/src/lib/tls/tls_signature_scheme.cpp @@ -0,0 +1,297 @@ +/* +* (C) 2022 Jack Lloyd +* (C) 2022 René Meusel, Hannes Rantzsch - neXenio GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include + +namespace Botan::TLS { + +const std::vector& Signature_Scheme::all_available_schemes() + { + /* + * This is ordered in some approximate order of preference + */ + static const std::vector all_schemes = { + +#if defined(BOTAN_HAS_ED25519) + EDDSA_25519, +#endif + + RSA_PSS_SHA384, + RSA_PSS_SHA256, + RSA_PSS_SHA512, + + RSA_PKCS1_SHA384, + RSA_PKCS1_SHA512, + RSA_PKCS1_SHA256, + + ECDSA_SHA384, + ECDSA_SHA512, + ECDSA_SHA256, + }; + + return all_schemes; + } + + +Signature_Scheme::Signature_Scheme() + : m_code(NONE) + {} + +Signature_Scheme::Signature_Scheme(uint16_t wire_code) + : Signature_Scheme(Signature_Scheme::Code(wire_code)) + {} + +Signature_Scheme::Signature_Scheme(Signature_Scheme::Code wire_code) + : m_code(wire_code) + {} + +bool Signature_Scheme::is_available() const noexcept + { + return value_exists(Signature_Scheme::all_available_schemes(), *this); + } + +bool Signature_Scheme::is_set() const noexcept + { + return m_code != NONE; + } + +std::string Signature_Scheme::to_string() const noexcept + { + switch(m_code) + { + case RSA_PKCS1_SHA1: + return "RSA_PKCS1_SHA1"; + case RSA_PKCS1_SHA256: + return "RSA_PKCS1_SHA256"; + case RSA_PKCS1_SHA384: + return "RSA_PKCS1_SHA384"; + case RSA_PKCS1_SHA512: + return "RSA_PKCS1_SHA512"; + + case ECDSA_SHA1: + return "ECDSA_SHA1"; + case ECDSA_SHA256: + return "ECDSA_SHA256"; + case ECDSA_SHA384: + return "ECDSA_SHA384"; + case ECDSA_SHA512: + return "ECDSA_SHA512"; + + case RSA_PSS_SHA256: + return "RSA_PSS_SHA256"; + case RSA_PSS_SHA384: + return "RSA_PSS_SHA384"; + case RSA_PSS_SHA512: + return "RSA_PSS_SHA512"; + + case EDDSA_25519: + return "EDDSA_25519"; + case EDDSA_448: + return "EDDSA_448"; + + case DSA_SHA1: + return "DSA_SHA1"; + case DSA_SHA256: + return "DSA_SHA256"; + case DSA_SHA384: + return "DSA_SHA384"; + case DSA_SHA512: + return "DSA_SHA512"; + + default: + return "Unknown signature scheme: " + std::to_string(m_code); + } + } + +std::string Signature_Scheme::hash_function_name() const noexcept + { + switch(m_code) + { + case RSA_PKCS1_SHA1: + case ECDSA_SHA1: + case DSA_SHA1: + return "SHA-1"; + + case ECDSA_SHA256: + case RSA_PKCS1_SHA256: + case RSA_PSS_SHA256: + case DSA_SHA256: + return "SHA-256"; + + case ECDSA_SHA384: + case RSA_PKCS1_SHA384: + case RSA_PSS_SHA384: + case DSA_SHA384: + return "SHA-384"; + + case ECDSA_SHA512: + case RSA_PKCS1_SHA512: + case RSA_PSS_SHA512: + case DSA_SHA512: + return "SHA-512"; + + case EDDSA_25519: + case EDDSA_448: + return "Pure"; + + default: + return "Unknown hash function"; + } + } + +std::string Signature_Scheme::padding_string() const noexcept + { + switch(m_code) + { + case RSA_PKCS1_SHA1: + return "EMSA_PKCS1(SHA-1)"; + case RSA_PKCS1_SHA256: + return "EMSA_PKCS1(SHA-256)"; + case RSA_PKCS1_SHA384: + return "EMSA_PKCS1(SHA-384)"; + case RSA_PKCS1_SHA512: + return "EMSA_PKCS1(SHA-512)"; + + case ECDSA_SHA1: + return "EMSA1(SHA-1)"; + case ECDSA_SHA256: + return "EMSA1(SHA-256)"; + case ECDSA_SHA384: + return "EMSA1(SHA-384)"; + case ECDSA_SHA512: + return "EMSA1(SHA-512)"; + + case RSA_PSS_SHA256: + return "PSSR(SHA-256,MGF1,32)"; + case RSA_PSS_SHA384: + return "PSSR(SHA-384,MGF1,48)"; + case RSA_PSS_SHA512: + return "PSSR(SHA-512,MGF1,64)"; + + case EDDSA_25519: + return "Pure"; + case EDDSA_448: + return "Pure"; + + default: + return "Unknown padding"; + } + } + +std::string Signature_Scheme::algorithm_name() const noexcept + { + switch(m_code) + { + case RSA_PKCS1_SHA1: + case RSA_PKCS1_SHA256: + case RSA_PKCS1_SHA384: + case RSA_PKCS1_SHA512: + case RSA_PSS_SHA256: + case RSA_PSS_SHA384: + case RSA_PSS_SHA512: + return "RSA"; + + case ECDSA_SHA1: + case ECDSA_SHA256: + case ECDSA_SHA384: + case ECDSA_SHA512: + return "ECDSA"; + + case EDDSA_25519: + return "Ed25519"; + + case EDDSA_448: + return "Ed448"; + + case DSA_SHA1: + case DSA_SHA256: + case DSA_SHA384: + case DSA_SHA512: + return "DSA"; + + default: + return "Unknown algorithm"; + } + } + +AlgorithmIdentifier Signature_Scheme::algorithm_identifier() const noexcept + { + switch(m_code) + { + // case ECDSA_SHA1: not defined + case ECDSA_SHA256: + return { "ECDSA", Botan::EC_Group("secp256r1").DER_encode(Botan::EC_Group_Encoding::NamedCurve) }; + case ECDSA_SHA384: + return { "ECDSA", Botan::EC_Group("secp384r1").DER_encode(Botan::EC_Group_Encoding::NamedCurve) }; + case ECDSA_SHA512: + return { "ECDSA", Botan::EC_Group("secp521r1").DER_encode(Botan::EC_Group_Encoding::NamedCurve) }; + + case EDDSA_25519: + return { "Ed25519", AlgorithmIdentifier::USE_EMPTY_PARAM }; + + case RSA_PKCS1_SHA1: + case RSA_PKCS1_SHA256: + case RSA_PKCS1_SHA384: + case RSA_PKCS1_SHA512: + case RSA_PSS_SHA256: + case RSA_PSS_SHA384: + case RSA_PSS_SHA512: + return { "RSA", AlgorithmIdentifier::USE_NULL_PARAM }; + + default: + return AlgorithmIdentifier(); + } + } + +std::optional Signature_Scheme::format() const noexcept + { + switch(m_code) + { + case RSA_PKCS1_SHA1: + case RSA_PKCS1_SHA256: + case RSA_PKCS1_SHA384: + case RSA_PKCS1_SHA512: + case RSA_PSS_SHA256: + case RSA_PSS_SHA384: + case RSA_PSS_SHA512: + return IEEE_1363; + + case ECDSA_SHA1: + case ECDSA_SHA256: + case ECDSA_SHA384: + case ECDSA_SHA512: + case EDDSA_25519: + case EDDSA_448: + case DSA_SHA1: + case DSA_SHA256: + case DSA_SHA384: + case DSA_SHA512: + return DER_SEQUENCE; + + default: + return std::nullopt; + } + } + +bool Signature_Scheme::is_sha1() const noexcept + { + return hash_function_name() == "SHA-1"; + } + +bool Signature_Scheme::is_pkcs1() const noexcept + { + return + m_code == RSA_PKCS1_SHA1 || + m_code == RSA_PKCS1_SHA256 || + m_code == RSA_PKCS1_SHA384 || + m_code == RSA_PKCS1_SHA512; + } + +} // Botan::TLS diff --git a/src/lib/tls/tls_signature_scheme.h b/src/lib/tls/tls_signature_scheme.h new file mode 100644 index 00000000000..5b34391e309 --- /dev/null +++ b/src/lib/tls/tls_signature_scheme.h @@ -0,0 +1,100 @@ +/* +* TLS Signature Scheme +* (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_SIGNATURE_SCHEME_H_ +#define BOTAN_TLS_SIGNATURE_SCHEME_H_ + +#include +#include +#include + +#include +#include + +namespace Botan::TLS { + +class BOTAN_PUBLIC_API(3,0) Signature_Scheme +{ +public: +/* +* Matches with wire encoding +*/ +enum Code : uint16_t { + NONE = 0x0000, + + RSA_PKCS1_SHA1 = 0x0201, // not implemented + RSA_PKCS1_SHA256 = 0x0401, + RSA_PKCS1_SHA384 = 0x0501, + RSA_PKCS1_SHA512 = 0x0601, + + ECDSA_SHA1 = 0x0203, // not implemented + ECDSA_SHA256 = 0x0403, + ECDSA_SHA384 = 0x0503, + ECDSA_SHA512 = 0x0603, + + RSA_PSS_SHA256 = 0x0804, + RSA_PSS_SHA384 = 0x0805, + RSA_PSS_SHA512 = 0x0806, + + EDDSA_25519 = 0x0807, + EDDSA_448 = 0x0808, // not implemented + + // not implemented + DSA_SHA1 = 0x0202, + DSA_SHA256 = 0x0402, + DSA_SHA384 = 0x0502, + DSA_SHA512 = 0x0602 +}; + +public: + /** + * @return all available signature schemes + */ + static const std::vector& all_available_schemes(); + + /** + * Construct an uninitialized / invalid scheme + */ + Signature_Scheme(); + + Signature_Scheme(uint16_t wire_code); + + Signature_Scheme(Signature_Scheme::Code wire_code); + + Signature_Scheme::Code wire_code() const noexcept { return m_code; } + + /** + * @return true if support for this scheme is implemented in this Botan build + */ + bool is_available() const noexcept; + + /** + * @return true if the wire_code is set to any value other than `NONE` + */ + bool is_set() const noexcept; + + std::string to_string() const noexcept; + std::string hash_function_name() const noexcept; + std::string padding_string() const noexcept; + std::string algorithm_name() const noexcept; + AlgorithmIdentifier algorithm_identifier() const noexcept; + std::optional format() const noexcept; + + bool is_sha1() const noexcept; + bool is_pkcs1() const noexcept; + + bool operator==(const Signature_Scheme& rhs) const { return m_code == rhs.m_code; } + bool operator!=(const Signature_Scheme& rhs) const { return !(*this == rhs); } + +private: + Signature_Scheme::Code m_code; +}; + +} // namespace Botan::TLS + +#endif // BOTAN_TLS_SIGNATURE_SCHEME_H_ diff --git a/src/tests/test_tls.cpp b/src/tests/test_tls.cpp index fe7fb5dc5a5..7652bb235fd 100644 --- a/src/tests/test_tls.cpp +++ b/src/tests/test_tls.cpp @@ -450,12 +450,12 @@ class Test_TLS_Algo_Strings : public Test { Test::Result result("TLS::Signature_Scheme"); - std::vector schemes = Botan::TLS::all_signature_schemes(); + std::vector schemes = Botan::TLS::Signature_Scheme::all_available_schemes(); std::set scheme_strs; for(auto scheme : schemes) { - std::string scheme_str = Botan::TLS::sig_scheme_to_string(scheme); + std::string scheme_str = scheme.to_string(); result.test_eq("Scheme strings unique", scheme_strs.count(scheme_str), 0); diff --git a/src/tests/test_tls_messages.cpp b/src/tests/test_tls_messages.cpp index 257930d8f01..da8518ab981 100644 --- a/src/tests/test_tls_messages.cpp +++ b/src/tests/test_tls_messages.cpp @@ -364,7 +364,7 @@ class TLS_Extension_Parsing_Test final : public Text_Based_Test 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); + Botan::TLS::Signature_Scheme(expected_sig_scheme) == sig_scheme); offset += 2; } diff --git a/src/tests/test_tls_signature_scheme.cpp b/src/tests/test_tls_signature_scheme.cpp new file mode 100644 index 00000000000..5a57483b268 --- /dev/null +++ b/src/tests/test_tls_signature_scheme.cpp @@ -0,0 +1,74 @@ +/* +* (C) 2022 Jack Lloyd +* (C) 2022 Hannes Rantzsch, René Meusel - neXenio +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + +#if defined(BOTAN_HAS_TLS) + +#include + +#include + +namespace { + +using namespace Botan::TLS; +using Test = Botan_Tests::Test; + +std::vector test_signature_scheme() + { + std::vector results; + + auto not_unknown = [](const std::string& s) + { return s.find("Unknown") == std::string::npos; }; + + for (const auto& s : Signature_Scheme::all_available_schemes()) + { + results.push_back( + Botan_Tests::CHECK(s.to_string().c_str(), [&](auto& result) + { + result.confirm("is_set handles all cases", s.is_set()); + result.confirm("is_available handles all cases", s.is_available()); + + result.confirm("to_string handles all cases", not_unknown(s.to_string())); + result.confirm("hash_function_name handles all cases", not_unknown(s.hash_function_name())); + result.confirm("padding_string handles all cases", not_unknown(s.padding_string())); + result.confirm("algorithm_name handles all cases", not_unknown(s.algorithm_name())); + + result.confirm("format handles all cases", s.format().has_value()); + result.confirm("algorithm_identifier handles all cases", + Botan::AlgorithmIdentifier() != s.algorithm_identifier()); + }) + ); + } + + Signature_Scheme bogus(0x1337); + results.push_back(Botan_Tests::CHECK("bogus scheme", [&](auto& result) + { + result.confirm("is_set still works", bogus.is_set()); + result.confirm("is not available", !bogus.is_available()); + + result.confirm("to_string deals with bogus schemes", !not_unknown(bogus.to_string())); + result.confirm("hash_function_name deals with bogus schemes", !not_unknown(bogus.hash_function_name())); + result.confirm("padding_string deals with bogus schemes", !not_unknown(bogus.padding_string())); + result.confirm("algorithm_name deals with bogus schemes", !not_unknown(bogus.algorithm_name())); + + result.confirm("format deals with bogus schemes", !bogus.format().has_value()); + result.confirm("algorithm_identifier deals with bogus schemes", + Botan::AlgorithmIdentifier() == bogus.algorithm_identifier()); + }) + ); + + return results; + } + +} // namespace + +namespace Botan_Tests { +BOTAN_REGISTER_TEST_FN("tls", "tls_signature_scheme", test_signature_scheme); +} + +#endif // BOTAN_HAS_TLS diff --git a/src/tests/unit_tls.cpp b/src/tests/unit_tls.cpp index afa4bc68df7..dedab36bb06 100644 --- a/src/tests/unit_tls.cpp +++ b/src/tests/unit_tls.cpp @@ -333,7 +333,7 @@ class TLS_Handshake_Test final { std::vector schemes = sig_algs->supported_schemes(); // 0x0301 is RSA PKCS1/SHA-224, which is not supported anymore - schemes.insert(schemes.begin(), static_cast(0x0301)); + schemes.insert(schemes.begin(), 0x0301); // This replaces the previous extension value extn.add(new Botan::TLS::Signature_Algorithms(schemes)); } From f03cfd9cac7c05c5933c9e2e1e6c7054c687ba92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Sun, 17 Apr 2022 13:42:53 +0530 Subject: [PATCH 10/20] review comments --- src/lib/tls/msg_cert_verify.cpp | 12 +++++------- src/lib/tls/tls_signature_scheme.cpp | 12 ++++++++---- src/lib/tls/tls_signature_scheme.h | 12 ++++++++---- src/tests/test_tls_signature_scheme.cpp | 5 ++--- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp index 453166787e2..1a1af9a72aa 100644 --- a/src/lib/tls/msg_cert_verify.cpp +++ b/src/lib/tls/msg_cert_verify.cpp @@ -60,14 +60,12 @@ Certificate_Verify::Certificate_Verify(const std::vector& buf) */ std::vector Certificate_Verify::serialize() const { + BOTAN_ASSERT_NOMSG(m_scheme.is_set()); std::vector buf; - // TODO: both for TLS 1.2 and 1.3 this should never happen and the message would be invalid - if(m_scheme.is_set()) - { - buf.push_back(get_byte<0>(m_scheme.wire_code())); - buf.push_back(get_byte<1>(m_scheme.wire_code())); - } + const auto code = m_scheme.wire_code(); + buf.push_back(get_byte<0>(code)); + buf.push_back(get_byte<1>(code)); if(m_signature.size() > 0xFFFF) { throw Encoding_Error("Certificate_Verify signature too long to encode"); } @@ -125,7 +123,7 @@ Certificate_Verify_13::Certificate_Verify_13(const std::vector& buf, // 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(m_scheme.is_pkcs1()) + if(m_scheme.is_rsa_pkcs1()) { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "RSA signatures must use an RSASSA-PSS algorithm"); } } diff --git a/src/lib/tls/tls_signature_scheme.cpp b/src/lib/tls/tls_signature_scheme.cpp index ae0fd0abcff..a1cccb95496 100644 --- a/src/lib/tls/tls_signature_scheme.cpp +++ b/src/lib/tls/tls_signature_scheme.cpp @@ -19,9 +19,13 @@ const std::vector& Signature_Scheme::all_available_schemes() */ static const std::vector all_schemes = { -#if defined(BOTAN_HAS_ED25519) - EDDSA_25519, -#endif +// EdDSA 25519 is currently not supported as a signature scheme for certificates +// certificate authentication. +// See: https://github.com/randombit/botan/pull/2958#discussion_r851294715 +// +// #if defined(BOTAN_HAS_ED25519) +// EDDSA_25519, +// #endif RSA_PSS_SHA384, RSA_PSS_SHA256, @@ -285,7 +289,7 @@ bool Signature_Scheme::is_sha1() const noexcept return hash_function_name() == "SHA-1"; } -bool Signature_Scheme::is_pkcs1() const noexcept +bool Signature_Scheme::is_rsa_pkcs1() const noexcept { return m_code == RSA_PKCS1_SHA1 || diff --git a/src/lib/tls/tls_signature_scheme.h b/src/lib/tls/tls_signature_scheme.h index 5b34391e309..78931188100 100644 --- a/src/lib/tls/tls_signature_scheme.h +++ b/src/lib/tls/tls_signature_scheme.h @@ -21,9 +21,13 @@ namespace Botan::TLS { class BOTAN_PUBLIC_API(3,0) Signature_Scheme { public: -/* -* Matches with wire encoding -*/ +/** + * Matches with wire encoding + * + * Note that this is intentionally left as a bare enum. It emulates the Botan 2 + * API where `Signature_Scheme` was an enum class with associated free-standing + * functions. Leaving it as a bare enum resembles the legacy user-facing API. + */ enum Code : uint16_t { NONE = 0x0000, @@ -86,7 +90,7 @@ enum Code : uint16_t { std::optional format() const noexcept; bool is_sha1() const noexcept; - bool is_pkcs1() const noexcept; + bool is_rsa_pkcs1() const noexcept; bool operator==(const Signature_Scheme& rhs) const { return m_code == rhs.m_code; } bool operator!=(const Signature_Scheme& rhs) const { return !(*this == rhs); } diff --git a/src/tests/test_tls_signature_scheme.cpp b/src/tests/test_tls_signature_scheme.cpp index 5a57483b268..921a5595d12 100644 --- a/src/tests/test_tls_signature_scheme.cpp +++ b/src/tests/test_tls_signature_scheme.cpp @@ -15,7 +15,6 @@ namespace { -using namespace Botan::TLS; using Test = Botan_Tests::Test; std::vector test_signature_scheme() @@ -25,7 +24,7 @@ std::vector test_signature_scheme() auto not_unknown = [](const std::string& s) { return s.find("Unknown") == std::string::npos; }; - for (const auto& s : Signature_Scheme::all_available_schemes()) + for (const auto& s : Botan::TLS::Signature_Scheme::all_available_schemes()) { results.push_back( Botan_Tests::CHECK(s.to_string().c_str(), [&](auto& result) @@ -45,7 +44,7 @@ std::vector test_signature_scheme() ); } - Signature_Scheme bogus(0x1337); + Botan::TLS::Signature_Scheme bogus(0x1337); results.push_back(Botan_Tests::CHECK("bogus scheme", [&](auto& result) { result.confirm("is_set still works", bogus.is_set()); From 2ed9cf0f79f416f20f5347b26474911ecbff25a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Mon, 18 Apr 2022 15:04:22 +0530 Subject: [PATCH 11/20] Signature_Scheme::is_suitable_for and ::is_compatible_with --- src/lib/tls/msg_cert_verify.cpp | 13 +----- src/lib/tls/tls12/tls_handshake_state.cpp | 7 +--- src/lib/tls/tls_signature_scheme.cpp | 51 +++++++++++++++++++---- src/lib/tls/tls_signature_scheme.h | 6 ++- 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp index 1a1af9a72aa..17404e90e23 100644 --- a/src/lib/tls/msg_cert_verify.cpp +++ b/src/lib/tls/msg_cert_verify.cpp @@ -114,17 +114,8 @@ Certificate_Verify_13::Certificate_Verify_13(const std::vector& buf, if(!m_scheme.is_available()) { throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Peer sent unknown signature scheme"); } - // RFC 8446 4.4.3: - // The SHA-1 algorithm MUST NOT be used in any signatures of - // CertificateVerify messages. - if(m_scheme.is_sha1()) - { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "SHA-1 algorithm must not be used"); } - - // 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(m_scheme.is_rsa_pkcs1()) - { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "RSA signatures must use an RSASSA-PSS algorithm"); } + if(!m_scheme.is_compatible_with(Protocol_Version::TLS_V13)) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Peer sent signature algorithm that is not suitable for TLS 1.3"); } } /* diff --git a/src/lib/tls/tls12/tls_handshake_state.cpp b/src/lib/tls/tls12/tls_handshake_state.cpp index 2ce835318e0..54a27c63d14 100644 --- a/src/lib/tls/tls12/tls_handshake_state.cpp +++ b/src/lib/tls/tls12/tls_handshake_state.cpp @@ -380,11 +380,8 @@ Handshake_State::parse_sig_format(const Public_Key& key, const std::string hash_algo = scheme.hash_function_name(); - // RFC 8446 4.4.3: - // The SHA-1 algorithm MUST NOT be used in any signatures of - // CertificateVerify messages. - if(scheme.is_sha1()) - { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "SHA-1 algorithm must not be used"); } + if(!scheme.is_compatible_with(Protocol_Version::TLS_V12)) + { throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Peer sent unexceptable signature scheme"); } if(!supported_algos_include(supported_algos, key_type, hash_algo)) { diff --git a/src/lib/tls/tls_signature_scheme.cpp b/src/lib/tls/tls_signature_scheme.cpp index a1cccb95496..4d0109804c9 100644 --- a/src/lib/tls/tls_signature_scheme.cpp +++ b/src/lib/tls/tls_signature_scheme.cpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include namespace Botan::TLS { @@ -284,18 +286,51 @@ std::optional Signature_Scheme::format() const noexcept } } -bool Signature_Scheme::is_sha1() const noexcept +bool Signature_Scheme::is_compatible_with(const Protocol_Version& protocol_version) const noexcept { - return hash_function_name() == "SHA-1"; + // RFC 8446 4.4.3: + // The SHA-1 algorithm MUST NOT be used in any signatures of + // CertificateVerify messages. + // + // Note that Botan enforces that for TLS 1.2 as well. + if(hash_function_name() == "SHA-1") + return false; + + // 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". + // + // Note that this is enforced for TLS 1.3 and above only. + if(!protocol_version.is_pre_tls_13() && + (m_code == RSA_PKCS1_SHA1 || + m_code == RSA_PKCS1_SHA256 || + m_code == RSA_PKCS1_SHA384 || + m_code == RSA_PKCS1_SHA512)) + return false; + + return true; } -bool Signature_Scheme::is_rsa_pkcs1() const noexcept +bool Signature_Scheme::is_suitable_for(const Private_Key &private_key) const noexcept { - return - m_code == RSA_PKCS1_SHA1 || - m_code == RSA_PKCS1_SHA256 || - m_code == RSA_PKCS1_SHA384 || - m_code == RSA_PKCS1_SHA512; + if(algorithm_name() != private_key.algo_name()) + return false; + + // The ECDSA private key length must match the utilized hash output length. + const auto keylen = private_key.key_length(); + if(keylen <= 250) + return false; + + if(m_code == ECDSA_SHA256 && !(keylen >= 250 && keylen <= 350)) + return false; + + if(m_code == ECDSA_SHA384 && !(keylen >= 350 && keylen <= 450)) + return false; + + if(m_code == ECDSA_SHA512 && !(keylen >= 450 && keylen <= 550)) + return false; + + return true; } } // Botan::TLS diff --git a/src/lib/tls/tls_signature_scheme.h b/src/lib/tls/tls_signature_scheme.h index 78931188100..179ff602a7b 100644 --- a/src/lib/tls/tls_signature_scheme.h +++ b/src/lib/tls/tls_signature_scheme.h @@ -18,6 +18,8 @@ namespace Botan::TLS { +class Protocol_Version; + class BOTAN_PUBLIC_API(3,0) Signature_Scheme { public: @@ -89,8 +91,8 @@ enum Code : uint16_t { AlgorithmIdentifier algorithm_identifier() const noexcept; std::optional format() const noexcept; - bool is_sha1() const noexcept; - bool is_rsa_pkcs1() const noexcept; + bool is_compatible_with(const Protocol_Version& protocol_version) const noexcept; + bool is_suitable_for(const Private_Key& private_key) const noexcept; bool operator==(const Signature_Scheme& rhs) const { return m_code == rhs.m_code; } bool operator!=(const Signature_Scheme& rhs) const { return !(*this == rhs); } From 37eaa8949343c017ca8453f98c9d760d2d3a9157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Fri, 27 May 2022 11:28:01 +0200 Subject: [PATCH 12/20] FIX: disambiguate invocation of stream operator --- src/cli/tls_utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/tls_utils.cpp b/src/cli/tls_utils.cpp index 591c20c9f41..41957b5a766 100644 --- a/src/cli/tls_utils.cpp +++ b/src/cli/tls_utils.cpp @@ -188,7 +188,7 @@ class TLS_Client_Hello_Reader final : public Command } catch(...) { - oss << "(" << std::hex << scheme.wire_code() << ") "; + oss << "(" << std::hex << static_cast(scheme.wire_code()) << ") "; } } oss << "\n"; From b92d50e5ec552dfc8d53e0ed52552b76c77058eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Thu, 2 Jun 2022 09:21:37 +0200 Subject: [PATCH 13/20] Review Comment: move version specific implementations --- src/lib/tls/{ => tls12}/msg_certificate_12.cpp | 0 src/lib/tls/{ => tls13}/msg_certificate_13.cpp | 0 src/lib/tls/{ => tls13}/msg_encrypted_extensions.cpp | 5 ----- src/lib/tls/{ => tls13}/msg_key_update.cpp | 5 ----- src/lib/tls/{ => tls13}/tls_extensions_key_share.cpp | 4 ---- 5 files changed, 14 deletions(-) rename src/lib/tls/{ => tls12}/msg_certificate_12.cpp (100%) rename src/lib/tls/{ => tls13}/msg_certificate_13.cpp (100%) rename src/lib/tls/{ => tls13}/msg_encrypted_extensions.cpp (97%) rename src/lib/tls/{ => tls13}/msg_key_update.cpp (94%) rename src/lib/tls/{ => tls13}/tls_extensions_key_share.cpp (99%) diff --git a/src/lib/tls/msg_certificate_12.cpp b/src/lib/tls/tls12/msg_certificate_12.cpp similarity index 100% rename from src/lib/tls/msg_certificate_12.cpp rename to src/lib/tls/tls12/msg_certificate_12.cpp diff --git a/src/lib/tls/msg_certificate_13.cpp b/src/lib/tls/tls13/msg_certificate_13.cpp similarity index 100% rename from src/lib/tls/msg_certificate_13.cpp rename to src/lib/tls/tls13/msg_certificate_13.cpp diff --git a/src/lib/tls/msg_encrypted_extensions.cpp b/src/lib/tls/tls13/msg_encrypted_extensions.cpp similarity index 97% rename from src/lib/tls/msg_encrypted_extensions.cpp rename to src/lib/tls/tls13/msg_encrypted_extensions.cpp index 90f8182a5d3..6c856e5de7e 100644 --- a/src/lib/tls/msg_encrypted_extensions.cpp +++ b/src/lib/tls/tls13/msg_encrypted_extensions.cpp @@ -6,9 +6,6 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include -#if defined(BOTAN_HAS_TLS_13) - #include #include #include @@ -62,5 +59,3 @@ Encrypted_Extensions::Encrypted_Extensions(const std::vector& buf) } } - -#endif diff --git a/src/lib/tls/msg_key_update.cpp b/src/lib/tls/tls13/msg_key_update.cpp similarity index 94% rename from src/lib/tls/msg_key_update.cpp rename to src/lib/tls/tls13/msg_key_update.cpp index 7eaf5dc4a24..00c1d938b6a 100644 --- a/src/lib/tls/msg_key_update.cpp +++ b/src/lib/tls/tls13/msg_key_update.cpp @@ -6,9 +6,6 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include -#if defined(BOTAN_HAS_TLS_13) - #include #include @@ -42,5 +39,3 @@ std::vector Key_Update::serialize() const } } - -#endif diff --git a/src/lib/tls/tls_extensions_key_share.cpp b/src/lib/tls/tls13/tls_extensions_key_share.cpp similarity index 99% rename from src/lib/tls/tls_extensions_key_share.cpp rename to src/lib/tls/tls13/tls_extensions_key_share.cpp index 5e194061d48..39954ef1b13 100644 --- a/src/lib/tls/tls_extensions_key_share.cpp +++ b/src/lib/tls/tls13/tls_extensions_key_share.cpp @@ -23,8 +23,6 @@ #include #include -#if defined(BOTAN_HAS_TLS_13) - namespace Botan::TLS { namespace { @@ -549,5 +547,3 @@ void Key_Share::erase() } } // Botan::TLS - -#endif // HAS_TLS_13 From cd16328b0b77de07cf0e8fb1a8135c52759ecc6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Thu, 2 Jun 2022 09:52:41 +0200 Subject: [PATCH 14/20] Review Comments: fix style nits --- src/lib/tls/msg_server_hello.cpp | 10 ++++------ src/lib/tls/tls13/msg_certificate_13.cpp | 8 +++++++- src/lib/tls/tls13/tls_cipher_state.cpp | 5 +++-- src/lib/tls/tls13/tls_cipher_state.h | 5 ++--- src/lib/tls/tls_policy.cpp | 11 ----------- src/lib/tls/tls_policy.h | 4 ---- src/tests/test_tls_record_layer_13.cpp | 7 ++++--- src/tests/test_tls_signature_scheme.cpp | 8 ++++---- src/tests/test_tls_transcript_hash_13.cpp | 11 +++++------ 9 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/lib/tls/msg_server_hello.cpp b/src/lib/tls/msg_server_hello.cpp index 27b20f313e1..dd9c6bb1bc9 100644 --- a/src/lib/tls/msg_server_hello.cpp +++ b/src/lib/tls/msg_server_hello.cpp @@ -538,11 +538,9 @@ void Server_Hello_13::basic_validation() const 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()) + if(!extensions().has()) { throw TLS_Exception(Alert::MISSING_EXTENSION, "server hello did not contain 'supported version' extension"); @@ -576,7 +574,7 @@ Server_Hello_13::Server_Hello_13(std::unique_ptr data, // // Note that further validation dependent on the client hello is done in the // TLS client implementation. - std::set allowed = + const std::set allowed = { TLSEXT_KEY_SHARE, TLSEXT_PSK_KEY_EXCHANGE_MODES, @@ -615,7 +613,7 @@ Server_Hello_13::Server_Hello_13(std::unique_ptr data, Se // - supported_versions (see Section 4.2.1) // - cookie (see Section 4.2.2) // - key_share (see Section 4.2.8) - std::set allowed = + const std::set allowed = { TLSEXT_COOKIE, TLSEXT_SUPPORTED_VERSIONS, @@ -624,7 +622,7 @@ Server_Hello_13::Server_Hello_13(std::unique_ptr data, Se // 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)) + if(exts.contains_other_than(allowed)) { throw TLS_Exception(Alert::UNSUPPORTED_EXTENSION, "Hello Retry Request contained an extension that is not allowed"); diff --git a/src/lib/tls/tls13/msg_certificate_13.cpp b/src/lib/tls/tls13/msg_certificate_13.cpp index b568f27e0bd..55f7359f554 100644 --- a/src/lib/tls/tls13/msg_certificate_13.cpp +++ b/src/lib/tls/tls13/msg_certificate_13.cpp @@ -43,8 +43,10 @@ void Certificate_13::validate_extensions(const std::set()) + { 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()); } + ocsp_responses.push_back(std::make_optional()); + } } } diff --git a/src/lib/tls/tls13/tls_cipher_state.cpp b/src/lib/tls/tls13/tls_cipher_state.cpp index 5e41102bf5e..a73b0505e9e 100644 --- a/src/lib/tls/tls13/tls_cipher_state.cpp +++ b/src/lib/tls/tls13/tls_cipher_state.cpp @@ -93,8 +93,7 @@ #include #include -using namespace Botan; -using namespace Botan::TLS; +namespace Botan::TLS { namespace { // RFC 8446 5.3 @@ -443,3 +442,5 @@ void Cipher_State::clear_write_keys() zap(m_write_iv); zap(m_write_application_traffic_secret); } + +} // namespace Botan::TLS diff --git a/src/lib/tls/tls13/tls_cipher_state.h b/src/lib/tls/tls13/tls_cipher_state.h index ab0c7686165..33f005be6a8 100644 --- a/src/lib/tls/tls13/tls_cipher_state.h +++ b/src/lib/tls/tls13/tls_cipher_state.h @@ -21,13 +21,12 @@ class HashFunction; class HKDF_Extract; class HKDF_Expand; -namespace TLS { -class Ciphersuite; -} } namespace Botan::TLS { +class Ciphersuite; + /** * This class implements the key schedule for TLS 1.3 as described in RFC 8446 7.1. * diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index 3e2e4476b19..76794a67523 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -618,15 +618,4 @@ std::vector Strict_Policy::allowed_key_exchange_methods() const return { "CECPQ1", "ECDH" }; } -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 ee19fcf709a..9f61b0729f6 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -558,10 +558,6 @@ class BOTAN_PUBLIC_API(2,0) Strict_Policy : public Policy std::vector allowed_macs() const override; std::vector allowed_key_exchange_methods() const override; - - bool allow_tls12() const override; - bool allow_tls13() const override; - bool allow_dtls12() const override; }; class BOTAN_PUBLIC_API(2,0) Text_Policy : public Policy diff --git a/src/tests/test_tls_record_layer_13.cpp b/src/tests/test_tls_record_layer_13.cpp index 5a26e0578f9..d0064b12c05 100644 --- a/src/tests/test_tls_record_layer_13.cpp +++ b/src/tests/test_tls_record_layer_13.cpp @@ -18,10 +18,11 @@ #include +namespace Botan_Tests { + namespace { namespace TLS = Botan::TLS; -using Test = Botan_Tests::Test; using Records = std::vector; @@ -966,13 +967,13 @@ std::vector record_size_limits() } // 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); -} + +} // namespace Botan_Tests #endif diff --git a/src/tests/test_tls_signature_scheme.cpp b/src/tests/test_tls_signature_scheme.cpp index 921a5595d12..d087aa6abda 100644 --- a/src/tests/test_tls_signature_scheme.cpp +++ b/src/tests/test_tls_signature_scheme.cpp @@ -13,9 +13,9 @@ #include -namespace { +namespace Botan_Tests { -using Test = Botan_Tests::Test; +namespace { std::vector test_signature_scheme() { @@ -66,8 +66,8 @@ std::vector test_signature_scheme() } // namespace -namespace Botan_Tests { BOTAN_REGISTER_TEST_FN("tls", "tls_signature_scheme", test_signature_scheme); -} + +} // namespace Botan_Tests #endif // BOTAN_HAS_TLS diff --git a/src/tests/test_tls_transcript_hash_13.cpp b/src/tests/test_tls_transcript_hash_13.cpp index be9422b184e..39bd0cf4272 100644 --- a/src/tests/test_tls_transcript_hash_13.cpp +++ b/src/tests/test_tls_transcript_hash_13.cpp @@ -15,9 +15,9 @@ using namespace Botan::TLS; -namespace { +namespace Botan_Tests { -using Test = Botan_Tests::Test; +namespace { std::vector transcript_hash() { @@ -147,11 +147,10 @@ std::vector transcript_hash() }; } -} - -namespace Botan_Tests { +} // namespace BOTAN_REGISTER_TEST_FN("tls", "tls_transcript_hash_13", transcript_hash); -} +} // namespace Botan_Tests + #endif From 9e1735683033c3ef1428f4268a318c677f5cf127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 1 Jun 2022 07:26:58 +0200 Subject: [PATCH 15/20] missing includes --- src/lib/tls/tls13/tls_client_impl_13.cpp | 1 + src/lib/tls/tls_channel_impl.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp index 13809d86c21..3993d579db6 100644 --- a/src/lib/tls/tls13/tls_client_impl_13.cpp +++ b/src/lib/tls/tls13/tls_client_impl_13.cpp @@ -18,6 +18,7 @@ #include #include +#include namespace Botan::TLS { diff --git a/src/lib/tls/tls_channel_impl.h b/src/lib/tls/tls_channel_impl.h index a9cf25c7d02..ab2feaf3b25 100644 --- a/src/lib/tls/tls_channel_impl.h +++ b/src/lib/tls/tls_channel_impl.h @@ -17,6 +17,7 @@ #include #include +#include namespace Botan { From 0a7ad40792dcc0c279900d12b47748cb686fd7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Thu, 2 Jun 2022 10:11:44 +0200 Subject: [PATCH 16/20] silence LGTM for readability --- src/lib/tls/tls_signature_scheme.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/tls/tls_signature_scheme.cpp b/src/lib/tls/tls_signature_scheme.cpp index 4d0109804c9..ac77dc57889 100644 --- a/src/lib/tls/tls_signature_scheme.cpp +++ b/src/lib/tls/tls_signature_scheme.cpp @@ -321,9 +321,9 @@ bool Signature_Scheme::is_suitable_for(const Private_Key &private_key) const noe if(keylen <= 250) return false; - if(m_code == ECDSA_SHA256 && !(keylen >= 250 && keylen <= 350)) - return false; - + if(m_code == ECDSA_SHA256 && !(keylen >= 250 && keylen <= 350)) // lgtm [cpp/constant-comparison] + return false; // `keylen >= 250` will always be true, because keylen <= 250 + // was checked before. Leaving it in for readability. if(m_code == ECDSA_SHA384 && !(keylen >= 350 && keylen <= 450)) return false; From 574b8f733ff034059ebd159ef24faca42b53cc4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Thu, 2 Jun 2022 10:38:20 +0200 Subject: [PATCH 17/20] Handshake_Layer::copy_data() takes a secure_vector<> --- src/lib/tls/tls13/tls_channel_impl_13.cpp | 2 +- src/lib/tls/tls13/tls_handshake_layer_13.cpp | 2 +- src/lib/tls/tls13/tls_handshake_layer_13.h | 2 +- src/tests/test_tls_handshake_layer_13.cpp | 24 ++++++++++---------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lib/tls/tls13/tls_channel_impl_13.cpp b/src/lib/tls/tls13/tls_channel_impl_13.cpp index 682fd86d0d5..1baa32a6366 100644 --- a/src/lib/tls/tls13/tls_channel_impl_13.cpp +++ b/src/lib/tls/tls13/tls_channel_impl_13.cpp @@ -99,7 +99,7 @@ size_t Channel_Impl_13::received_data(const uint8_t input[], size_t input_size) if(record.type == HANDSHAKE) { - m_handshake_layer.copy_data(unlock(record.fragment)); // TODO: record fragment should be an ordinary std::vector + m_handshake_layer.copy_data(record.fragment); if(!handshake_finished()) { diff --git a/src/lib/tls/tls13/tls_handshake_layer_13.cpp b/src/lib/tls/tls13/tls_handshake_layer_13.cpp index 12336284674..946d0fc4a72 100644 --- a/src/lib/tls/tls13/tls_handshake_layer_13.cpp +++ b/src/lib/tls/tls13/tls_handshake_layer_13.cpp @@ -16,7 +16,7 @@ namespace Botan::TLS { -void Handshake_Layer::copy_data(const std::vector& data_from_peer) +void Handshake_Layer::copy_data(const secure_vector& data_from_peer) { m_read_buffer.insert(m_read_buffer.end(), data_from_peer.cbegin(), data_from_peer.cend()); } diff --git a/src/lib/tls/tls13/tls_handshake_layer_13.h b/src/lib/tls/tls13/tls_handshake_layer_13.h index c54d2e276ba..2431c583a6f 100644 --- a/src/lib/tls/tls13/tls_handshake_layer_13.h +++ b/src/lib/tls/tls13/tls_handshake_layer_13.h @@ -36,7 +36,7 @@ class BOTAN_TEST_API Handshake_Layer * * @param data_from_peer The data to be parsed. */ - void copy_data(const std::vector& data_from_peer); + void copy_data(const secure_vector& data_from_peer); /** * Parses one handshake message off the internal buffer that is being filled using `copy_data`. diff --git a/src/tests/test_tls_handshake_layer_13.cpp b/src/tests/test_tls_handshake_layer_13.cpp index 2b018de9e3a..337fdbd9455 100644 --- a/src/tests/test_tls_handshake_layer_13.cpp +++ b/src/tests/test_tls_handshake_layer_13.cpp @@ -41,7 +41,7 @@ const Handshake_Message_13& get_message( return std::get(read_result.value()); } -const auto client_hello_message = Botan::hex_decode( // from RFC 8448 +const auto client_hello_message = Botan::hex_decode_locked( // 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" @@ -53,26 +53,26 @@ const auto client_hello_message = Botan::hex_decode( // from RFC 8448 "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( +const auto server_hello_message = Botan::hex_decode_locked( "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( +const auto server_hello_12_message = Botan::hex_decode_locked( "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( +const auto encrypted_extensions = 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"); const auto server_handshake_messages = // except server hello - Botan::hex_decode( + 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" @@ -106,7 +106,7 @@ const auto server_handshake_messages = // except server hello "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( +const auto client_finished_message = 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"); @@ -114,7 +114,7 @@ const auto client_finished_message = Botan::hex_decode( // ################################################################### // -const auto hrr_client_hello_msg = Botan::hex_decode( +const auto hrr_client_hello_msg = Botan::hex_decode_locked( "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" @@ -125,7 +125,7 @@ const auto hrr_client_hello_msg = Botan::hex_decode( "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( +const auto hrr_hello_retry_request_msg = Botan::hex_decode_locked( "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" @@ -136,7 +136,7 @@ const auto hrr_hello_retry_request_msg = Botan::hex_decode( "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 +const std::vector> tls_12_only_messages { {HELLO_REQUEST, 0x00, 0x00, 0x02, 0x42, 0x42}, {HELLO_VERIFY_REQUEST, 0x00, 0x00, 0x02, 0x42, 0x42}, @@ -210,13 +210,13 @@ std::vector read_handshake_messages() Handshake_Layer hl(Connection_Side::CLIENT); Transcript_Hash_State th("SHA-256"); - const std::vector partial_client_hello_message( + const Botan::secure_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( + const Botan::secure_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))); @@ -256,7 +256,7 @@ std::vector read_handshake_messages() // 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"); + const auto data = Botan::hex_decode_locked("D4B028717D0FA310FF8664127B9448D7952E06A4F9EA23"); // data from the bogo test -- ~~~~~~ <- length // ~~ <- bogus message type Handshake_Layer hl(Connection_Side::CLIENT); From 57c70e4fe6098319af8c2e7bb1af277f6542b299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 8 Jun 2022 09:39:17 +0200 Subject: [PATCH 18/20] Simplify keyshare extension parsing --- .../tls/tls13/tls_extensions_key_share.cpp | 40 ++----------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/src/lib/tls/tls13/tls_extensions_key_share.cpp b/src/lib/tls/tls13/tls_extensions_key_share.cpp index 39954ef1b13..8431b3aa5bd 100644 --- a/src/lib/tls/tls13/tls_extensions_key_share.cpp +++ b/src/lib/tls/tls13/tls_extensions_key_share.cpp @@ -60,18 +60,7 @@ class Key_Share_Entry { // 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."); - } + m_key_exchange = reader.get_tls_length_value(2); } Key_Share_Entry(const TLS::Group_Params group, Callbacks& cb, RandomNumberGenerator& rng) @@ -218,12 +207,7 @@ class Key_Share_ServerHello 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; + return m_server_share.serialize(); } bool empty() const @@ -255,23 +239,7 @@ class Key_Share_ClientHello 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); + m_client_shares.emplace_back(reader); } if((reader.read_so_far() - read_bytes_so_far_begin) != client_key_share_length) @@ -474,7 +442,7 @@ Key_Share::Key_Share(TLS_Data_Reader& reader, 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() {} +Key_Share::~Key_Share() = default; std::vector Key_Share::serialize(Connection_Side /*whoami*/) const { From ebc62be9e9290d6bf124f9e035dd2230390930a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 8 Jun 2022 09:43:17 +0200 Subject: [PATCH 19/20] FIX: missing include --- src/lib/tls/tls13/tls_transcript_hash_13.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/tls/tls13/tls_transcript_hash_13.cpp b/src/lib/tls/tls13/tls_transcript_hash_13.cpp index ad4d68e28d8..a3f82f759ef 100644 --- a/src/lib/tls/tls13/tls_transcript_hash_13.cpp +++ b/src/lib/tls/tls13/tls_transcript_hash_13.cpp @@ -8,6 +8,8 @@ #include +#include + namespace Botan::TLS { Transcript_Hash_State::Transcript_Hash_State(const std::string &algo_spec) From a89c53980c20a0e62ae6b0108e4f454ad75a917e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Thu, 9 Jun 2022 13:41:56 +0200 Subject: [PATCH 20/20] FIX: outdated TODO --- src/lib/tls/tls_extensions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h index 00368bdb7a6..57c5a8bb885 100644 --- a/src/lib/tls/tls_extensions.h +++ b/src/lib/tls/tls_extensions.h @@ -478,7 +478,7 @@ using Named_Group = Group_Params; /** * Record Size Limit (RFC 8449) * -* TODO: the record size limit will currently not be honored by the record protocol +* TODO: the record size limit is currently not honored by the TLS 1.2 stack */ class BOTAN_UNSTABLE_API Record_Size_Limit final : public Extension {