diff options
author | Jack Lloyd <[email protected]> | 2019-05-20 16:15:16 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2019-05-20 16:15:16 -0400 |
commit | 4658a632c1a2e01c9b4487aa19269fc627108ce4 (patch) | |
tree | 9a4bdb9fa1caa6681d670e5b4b8b68d57008ccea /src | |
parent | 81ea951957a133fcb7c8a6645312edf7904b26e9 (diff) | |
parent | 67df17d31d61f013d537abc7744f707435351125 (diff) |
Merge GH #1954 Add BoGo test shim
Diffstat (limited to 'src')
32 files changed, 2082 insertions, 201 deletions
diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp new file mode 100644 index 000000000..9266fe5a5 --- /dev/null +++ b/src/bogo_shim/bogo_shim.cpp @@ -0,0 +1,1487 @@ +/* +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +/* +* This is a shim for testing Botan against BoringSSL's test TLS stack (BoGo). +* +* Instructions on use should go here. +*/ + +#include <botan/tls_client.h> +#include <botan/tls_server.h> +#include <botan/tls_exceptn.h> +#include <botan/tls_algos.h> +#include <botan/data_src.h> +#include <botan/pkcs8.h> +#include <botan/oids.h> +#include <botan/chacha_rng.h> +#include <botan/base64.h> +#include <botan/hex.h> +#include <botan/parsing.h> +#include <botan/mem_ops.h> +#include <iostream> +#include <vector> +#include <string> +#include <map> +#include <set> +#include <ctime> +#include <unordered_map> + +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + #include <sys/socket.h> + #include <sys/time.h> + #include <netinet/in.h> + #include <netdb.h> + #include <string.h> + #include <unistd.h> + #include <errno.h> + #include <fcntl.h> +#endif + +namespace { + +int shim_output(const std::string& s, int rc = 0) + { + std::cout << s << "\n"; + return rc; + } + +void shim_exit_with_error(const std::string& s, int rc = 1) + { + std::cerr << s << "\n"; + std::exit(rc); + } + +void shim_log(const std::string& s) + { + static FILE* f = fopen("/tmp/bogo_shim.log", "w"); + fprintf(f, "%d: %s\n", (int)time(nullptr), s.c_str()); + fflush(f); + } + +std::string map_to_bogo_error(const std::string& e) + { + static const std::unordered_map<std::string, std::string> err_map + { + { "Application data before handshake done", ":APPLICATION_DATA_INSTEAD_OF_HANDSHAKE:" }, + { "Bad Hello_Request, has non-zero size", ":BAD_HELLO_REQUEST:" }, + { "Bad code for TLS alert level", ":UNKNOWN_ALERT_TYPE:" }, + { "Bad extension size", ":DECODE_ERROR:" }, + { "Bad signature on server key exchange", ":BAD_SIGNATURE:" }, + { "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:" }, + { "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:" }, + { "Certificate key type did not match ciphersuite", ":WRONG_CERTIFICATE_TYPE:" }, + { "Certificate: Message malformed", ":DECODE_ERROR:" }, + { "Client cert verify failed", ":BAD_SIGNATURE:" }, + { "Client did not offer NULL compression", ":INVALID_COMPRESSION_LIST:" }, + { "Client offered version with major version under 3", ":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 signalled fallback SCSV, possible attack", ":INAPPROPRIATE_FALLBACK:" }, + { "Client version TLS v1.0 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, + { "Client version TLS v1.1 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, + { "Client: No certificates sent by server", ":DECODE_ERROR:" }, + { "Counterparty sent inconsistent key and sig types", ":WRONG_SIGNATURE_TYPE:" }, + { "Empty ALPN protocol not allowed", ":PARSE_TLSEXT:" }, + { "Encoding error: Cannot encode PSS string, output length too small", ":NO_COMMON_SIGNATURE_ALGORITHMS:" }, + { "Finished message didn't verify", ":DIGEST_CHECK_FAILED:" }, + { "Inconsistent length in certificate request", ":DECODE_ERROR:" }, + { "Invalid CertificateRequest: 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:" }, + { "Invalid ClientKeyExchange: Extra bytes at end of message", ":DECODE_ERROR:" }, + { "Invalid ServerKeyExchange: Extra bytes at end of message", ":DECODE_ERROR:" }, + { "Invalid SessionTicket: Extra bytes at end of message", ":DECODE_ERROR:" }, + { "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:" }, + { "OS2ECP: Unknown format type 251", ":BAD_ECPOINT:" }, + { "Policy forbids all available TLS version", ":NO_SUPPORTED_VERSIONS_ENABLED:" }, + { "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 unexpected record version in initial record", ":WRONG_VERSION_NUMBER:" }, + { "Received unexpected record version", ":WRONG_VERSION_NUMBER:" }, + { "Server certificate changed during renegotiation", ":SERVER_CERT_CHANGED:" }, + { "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 replied using a ciphersuite not allowed in version it offered", ":WRONG_CIPHER_RETURNED:" }, + { "Server replied with ciphersuite we didn't send", ":WRONG_CIPHER_RETURNED:" }, + { "Server replied with later version than client offered", ":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:" }, + { "Server replied with unsupported extensions: 1234", ":UNEXPECTED_EXTENSION:" }, + { "Server replied with unsupported extensions: 16", ":UNEXPECTED_EXTENSION:" }, + { "Server replied with unsupported extensions: 43", ":UNEXPECTED_EXTENSION:" }, + { "Server replied with unsupported extensions: 5", ":UNEXPECTED_EXTENSION:" }, + { "Server resumed session and removed extended master secret", ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:" }, + { "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:" }, + { "Server sent an unsupported extension", ":UNEXPECTED_EXTENSION:" }, + { "Server sent bad values for secure renegotiation", ":RENEGOTIATION_MISMATCH:" }, + { "Server version TLS v1.0 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, + { "Server version TLS v1.1 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" }, + { "Server_Hello_Done: Must be empty, and is not", ":DECODE_ERROR:" }, + { "Simulated OCSP callback failure", ":OCSP_CB_ERROR:" }, + { "Simulating cert verify callback failure", ":CERT_CB_ERROR:" }, + { "TLS plaintext record is larger than allowed maximum", ":DATA_LENGTH_TOO_LONG:" }, + { "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:" }, + { "Unexpected state transition in handshake, expected received hello_request", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake, expected received server_key_exchange", ":BAD_HELLO_REQUEST:" }, + { "Unexpected state transition in handshake, expected certificate received certificate_status", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake, expected certificate received client_hello+client_key_exchange", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake, expected certificate_request|server_hello_done received certificate+certificate_status+server_key_exchange", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake, expected certificate_verify received client_hello+certificate+client_key_exchange+change_cipher_spec", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake, expected change_cipher_spec received certificate+certificate_status+server_hello_done+new_session_ticket+finished", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake, expected change_cipher_spec received certificate+certificate_status+server_key_exchange+server_hello_done+new_session_ticket+finished", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake, expected change_cipher_spec received client_hello+client_key_exchange+finished", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake, expected change_cipher_spec received client_hello+finished", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake, expected change_cipher_spec received finished", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake, expected client_key_exchange received client_hello+change_cipher_spec", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake, expected new_session_ticket received certificate+certificate_status+server_hello_done+change_cipher_spec", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake, expected new_session_ticket received certificate+certificate_status+server_key_exchange+server_hello_done+change_cipher_spec", ":UNEXPECTED_RECORD:" }, + { "Unexpected state transition in handshake, expected server_hello_done received server_key_exchange+certificate_request", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake, expected server_key_exchange received certificate", ":WRONG_CIPHER_RETURNED:" }, + { "Unexpected state transition in handshake, expected server_key_exchange received certificate+certificate_status+server_hello_done", ":UNEXPECTED_MESSAGE:" }, + { "Unexpected state transition in handshake, expected server_key_exchange|server_hello_done received certificate_request", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 43", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 44", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 46", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 53", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 54", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 55", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 56", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 57", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 58", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 6", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 62", ":UNEXPECTED_MESSAGE:" }, + { "Unknown TLS handshake message type 64", ":UNEXPECTED_MESSAGE:" }, + { "signature_algorithm_of_scheme: Unknown signature algorithm enum", ":WRONG_SIGNATURE_TYPE:" }, + }; + + auto err_map_i = err_map.find(e); + if(err_map_i != err_map.end()) + return err_map_i->second; + + return "Unmapped error: " + e; + } + +class Shim_Exception final : public std::exception + { + public: + Shim_Exception(const std::string& msg, int rc = 1) : + m_msg(msg), m_rc(rc) {} + + const char* what() const noexcept { return m_msg.c_str(); } + + int rc() const { return m_rc; } + private: + const std::string m_msg; + int m_rc; + }; + +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) + +class Shim_Socket final + { + private: + typedef int socket_type; + typedef ssize_t socket_op_ret_type; + static void close_socket(socket_type s) { ::close(s); } + static std::string get_last_socket_error() { return ::strerror(errno); } + + public: + Shim_Socket(const std::string& hostname, int port) : m_socket(-1) + { + addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + addrinfo* res; + + const std::string service = std::to_string(port); + int rc = ::getaddrinfo(hostname.c_str(), service.c_str(), &hints, &res); + shim_log("Connecting " + hostname + ":" + service); + + if(rc != 0) + { + throw Shim_Exception("Name resolution failed for " + hostname); + } + + for(addrinfo* rp = res; (m_socket == -1) && (rp != nullptr); rp = rp->ai_next) + { + if(rp->ai_family != AF_INET && rp->ai_family != AF_INET6) + continue; + + m_socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if(m_socket == -1) + { + // unsupported socket type? + continue; + } + + int err = ::connect(m_socket, rp->ai_addr, rp->ai_addrlen); + + if(err != 0) + { + ::close(m_socket); + m_socket = -1; + } + } + + if(m_socket < 0) + throw Shim_Exception("Failed to connect to host"); + } + + ~Shim_Socket() + { + ::close(m_socket); + m_socket = -1; + } + + void write(const uint8_t buf[], size_t len) + { + if(m_socket < 0) + throw Shim_Exception("Socket was bad on write"); + size_t sent_so_far = 0; + while(sent_so_far != len) + { + const size_t left = len - sent_so_far; + socket_op_ret_type sent = ::send(m_socket, Botan::cast_uint8_ptr_to_char(&buf[sent_so_far]), left, 0); + if(sent < 0) + throw Shim_Exception("Socket write failed", errno); + else + sent_so_far += static_cast<size_t>(sent); + } + } + + size_t read(uint8_t buf[], size_t len) + { + if(m_socket < 0) + throw Shim_Exception("Socket was bad on read"); + socket_op_ret_type got = ::recv(m_socket, Botan::cast_uint8_ptr_to_char(buf), len, 0); + //shim_log("Read returned " + std::to_string(got)); + + if(got < 0) + { + if(errno == ECONNRESET) + return 0; + throw Shim_Exception("Socket read failed: " + std::string(strerror(errno))); + } + + return static_cast<size_t>(got); + } + + private: + socket_type m_socket; + }; + +#endif + +std::set<std::string> combine_options( + const std::set<std::string>& a, + const std::set<std::string>& b, + const std::set<std::string>& c, + const std::set<std::string>& d) + { + std::set<std::string> combined; + + for(auto i : a) + combined.insert(i); + for(auto i : b) + combined.insert(i); + for(auto i : c) + combined.insert(i); + for(auto i : d) + combined.insert(i); + + return combined; + } + +class Shim_Arguments final + { + public: + Shim_Arguments(const std::set<std::string>& flags, + const std::set<std::string>& string_opts, + const std::set<std::string>& base64_opts, + const std::set<std::string>& int_opts, + const std::set<std::string>& int_vec_opts) : + m_flags(flags), + m_string_opts(string_opts), + m_base64_opts(base64_opts), + m_int_opts(int_opts), + m_int_vec_opts(int_vec_opts), + m_all_options(combine_options(string_opts, base64_opts, int_opts, int_vec_opts)) + {} + + void parse_args(char* argv[]); + + bool flag_set(const std::string& flag) const + { + if(m_flags.count(flag) == 0) + throw Shim_Exception("Unknown bool flag " + flag); + + return m_parsed_flags.count(flag); + } + + std::string get_string_opt(const std::string& key) const + { + if(m_string_opts.count(key) == 0) + throw Shim_Exception("Unknown string key " + key); + return get_opt(key); + } + + std::string get_string_opt_or_else(const std::string& key, const std::string& def) const + { + if(m_string_opts.count(key) == 0) + throw Shim_Exception("Unknown string key " + key); + if(!option_used(key)) + return def; + return get_opt(key); + } + + std::vector<uint8_t> get_b64_opt(const std::string& key) const + { + if(m_base64_opts.count(key) == 0) + throw Shim_Exception("Unknown base64 key " + key); + return Botan::unlock(Botan::base64_decode(get_opt(key))); + } + + size_t get_int_opt(const std::string& key) const + { + if(m_int_opts.count(key) == 0) + throw Shim_Exception("Unknown int key " + key); + return Botan::to_u32bit(get_opt(key)); + } + + size_t get_int_opt_or_else(const std::string& key, size_t def) const + { + if(m_int_opts.count(key) == 0) + throw Shim_Exception("Unknown int key " + key); + if(!option_used(key)) + return def; + + return Botan::to_u32bit(get_opt(key)); + } + + std::vector<size_t> get_int_vec_opt(const std::string& key) const + { + if(m_int_vec_opts.count(key) == 0) + throw Shim_Exception("Unknown int vec key " + key); + + auto i = m_parsed_int_vec_opts.find(key); + if(i == m_parsed_int_vec_opts.end()) + return std::vector<size_t>(); + else + return i->second; + } + + std::vector<std::string> get_alpn_string_vec_opt(const std::string& option) const + { + // hack used for alpn list (relies on all ALPNs being 3 chars long...) + char delim = 0x03; + + if(option_used(option)) + return Botan::split_on(get_string_opt(option), delim); + else + return std::vector<std::string>(); + } + + bool option_used(const std::string& key) const + { + if(m_parsed_opts.find(key) != m_parsed_opts.end()) + return true; + if(m_parsed_int_vec_opts.find(key) != m_parsed_int_vec_opts.end()) + return true; + return false; + } + + private: + std::string get_opt(const std::string& key) const + { + auto i = m_parsed_opts.find(key); + if(i == m_parsed_opts.end()) + throw Shim_Exception("Option " + key + " was not provided"); + return i->second; + } + + const std::set<std::string> m_flags; + const std::set<std::string> m_string_opts; + const std::set<std::string> m_base64_opts; + const std::set<std::string> m_int_opts; + const std::set<std::string> m_int_vec_opts; + const std::set<std::string> m_all_options; + + std::set<std::string> m_parsed_flags; + std::map<std::string, std::string> m_parsed_opts; + std::map<std::string, std::vector<size_t>> m_parsed_int_vec_opts; + }; + +void Shim_Arguments::parse_args(char* argv[]) + { + int i = 1; // skip argv[0] + + while(argv[i] != nullptr) + { + const std::string param(argv[i]); + + if(param.find("-") == 0) + { + const std::string flag_name = param.substr(1, std::string::npos); + + if(m_flags.count(flag_name)) + { + shim_log("flag " + flag_name); + m_parsed_flags.insert(flag_name); + i += 1; + } + else if(m_all_options.count(flag_name)) + { + if(argv[i+1] == nullptr) + throw Shim_Exception("Expected argument following " + param); + std::string val(argv[i+1]); + shim_log("param " + flag_name + "=" + val); + + if(m_int_vec_opts.count(flag_name)) + { + const size_t v = Botan::to_u32bit(val); + m_parsed_int_vec_opts[flag_name].push_back(v); + } + else + { + m_parsed_opts[flag_name] = val; + } + i += 2; + } + else + { + shim_log("Unknown option " + param); + throw Shim_Exception("Unknown option " + param, 89); + } + } + else + { + shim_log("Unknown option " + param); + throw Shim_Exception("Unknown option " + param, 89); + } + } + } + +std::unique_ptr<Shim_Arguments> parse_options(char* argv[]) + { + const std::set<std::string> bogo_shim_flags = { + "allow-false-start-without-alpn", + "allow-unknown-alpn-protos", + "async", + "cbc-record-splitting", + "check-close-notify", + "decline-alpn", + "decline-ocsp-callback", + "dtls", + "enable-all-curves", + "enable-channel-id", + "enable-early-data", + "enable-ed25519", + "enable-grease", + "enable-ocsp-stapling", + "enable-signed-cert-timestamps", + //"enforce-rsa-key-usage", + //"expect-accept-early-data", + "expect-extended-master-secret", + "expect-no-offer-early-data", + "expect-no-secure-renegotiation", + "expect-no-session", + "expect-no-session-id", + //"expect-reject-early-data", + "expect-secure-renegotiation", + "expect-session-id", + "expect-session-miss", + "expect-sha256-client-cert", + "expect-ticket-renewal", + "expect-ticket-supports-early-data", + //"expect-tls13-downgrade", + "expect-verify-result", + //"export-traffic-secrets", + "fail-cert-callback", + //"fail-ddos-callback", + //"fail-early-callback", + "fail-ocsp-callback", + "fallback-scsv", + //"false-start", + "forbid-renegotiation-after-handshake", + "handoff", + "handshake-never-done", + "handshake-twice", + "handshaker-resume", + //"ignore-tls13-downgrade", + "implicit-handshake", + "install-cert-compression-algs", + "install-ddos-callback", + "is-handshaker-supported", + //"jdk11-workaround", + //"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 + //"on-resume-verify-fail", + //"partial-write", + //"peek-then-read", + //"read-with-unfinished-write", + "renegotiate-freely", + "renegotiate-ignore", + "renegotiate-once", + //"renew-ticket", + "require-any-client-certificate", + "retain-only-sha256-client-cert", + //"reverify-on-resume", + "select-empty-alpn", + "send-alert", + "server", + "server-preference", + //"set-ocsp-in-callback", + //"shim-shuts-down", + "shim-writes-first", + //"tls-unique", + "use-custom-verify-callback", + //"use-early-callback", + "use-export-context", + //"use-exporter-between-reads", + "use-ocsp-callback", + //"use-old-client-cert-callback", + //"use-ticket-callback", + "verify-fail", + "verify-peer", + //"verify-peer-if-no-obc", + "write-different-record-sizes", + }; + + const std::set<std::string> bogo_shim_string_opts = { + "advertise-alpn", + //"advertise-npn", + "cert-file", + "cipher", + //"delegated-credential", + "expect-advertised-alpn", + "expect-alpn", + "expect-client-ca-list", + "expect-late-alpn", + "expect-msg-callback", + //"expect-next-proto", + "expect-peer-cert-file", + "expect-server-name", + "export-context", + "export-label", + "handshaker-path", + "host-name", + "key-file", + "psk", + "psk-identity", + "select-alpn", + "select-next-proto", + //"send-channel-id", + "srtp-profiles", + "use-client-ca-list", + //"write-settings", + }; + + const std::set<std::string> bogo_shim_base64_opts = { + "expect-certificate-types", + //"expect-channel-id", + "expect-ocsp-response", + //"expect-quic-transport-params", + //"expect-signed-cert-timestamps", + //"ocsp-response", + //"quic-transport-params", + //"signed-cert-timestamps", + //"ticket-key", /* we use a different ticket format from Boring */ + //"token-binding-params", + }; + + const std::set<std::string> bogo_shim_int_opts { + "expect-cipher-aes", + "expect-cipher-no-aes", + "expect-curve-id", + "expect-peer-signature-algorithm", + "expect-ticket-age-skew", + "expect-token-binding-param", + "expect-total-renegotiations", + "expect-version", + //"export-early-keying-material", + "export-keying-material", + "initial-timeout-duration-ms", + "max-cert-list", + //"max-send-fragment", + "max-version", + "min-version", + //"mtu", + "port", + "read-size", + "resume-count", + "resumption-delay", + }; + + const std::set<std::string> bogo_shim_int_vec_opts { + "curves", + "expect-peer-verify-pref", + "signing-prefs", + "verify-prefs", + }; + + std::unique_ptr<Shim_Arguments> args( + new Shim_Arguments(bogo_shim_flags, + bogo_shim_string_opts, + bogo_shim_base64_opts, + bogo_shim_int_opts, + bogo_shim_int_vec_opts)); + + // may throw: + args->parse_args(argv); + + return args; + } + +class Shim_Policy final : public Botan::TLS::Policy + { + public: + Shim_Policy(const Shim_Arguments& args) : m_args(args), m_sessions(0) {} + + void incr_session_established() { m_sessions += 1; } + size_t sessions_established() const { return m_sessions; } + + std::vector<std::string> allowed_ciphers() const override + { + return { + "AES-256/OCB(12)", + "AES-128/OCB(12)", + "ChaCha20Poly1305", + "AES-256/GCM", + "AES-128/GCM", + "AES-256/CCM", + "AES-128/CCM", + "AES-256/CCM(8)", + "AES-128/CCM(8)", + "Camellia-256/GCM", + "Camellia-128/GCM", + "ARIA-256/GCM", + "ARIA-128/GCM", + "AES-256", + "AES-128", + "Camellia-256", + "Camellia-128", + "SEED", + "3DES", + }; + + } + + std::vector<std::string> allowed_signature_hashes() const override + { + if(m_args.option_used("signing-prefs")) + { + std::vector<std::string> pref_hash; + for(size_t pref : m_args.get_int_vec_opt("signing-prefs")) + { + const auto scheme = static_cast<Botan::TLS::Signature_Scheme>(pref); + if(Botan::TLS::signature_scheme_is_known(scheme) == false) + continue; + pref_hash.push_back(Botan::TLS::hash_function_of_scheme(scheme)); + } + + if(m_args.flag_set("server")) + pref_hash.push_back("SHA-256"); + return pref_hash; + } + else + { + return { "SHA-512", "SHA-384", "SHA-256", "SHA-1" }; + } + } + + //std::vector<std::string> allowed_macs() const override; + + std::vector<std::string> allowed_key_exchange_methods() const override + { + return { + "ECDHE_PSK", + "DHE_PSK", + "PSK", + "CECPQ1", + "ECDH", + "DH", + "RSA", + }; + } + + std::vector<std::string> allowed_signature_methods() const override + { + return { + "ECDSA", + "RSA", + "IMPLICIT", + }; + + } + + std::vector<Botan::TLS::Signature_Scheme> allowed_signature_schemes() const override + { + if(m_args.option_used("signing-prefs")) + { + std::vector<Botan::TLS::Signature_Scheme> schemes; + for(size_t pref : m_args.get_int_vec_opt("signing-prefs")) + { + schemes.push_back(static_cast<Botan::TLS::Signature_Scheme>(pref)); + } + + // BoGo gets sad if these are not included in our signature_algorithms extension + + if(!m_args.flag_set("server") && false) + { + schemes.push_back(Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256); + schemes.push_back(Botan::TLS::Signature_Scheme::ECDSA_SHA256); + } + + return schemes; + } + + if(m_args.option_used("verify-prefs")) + { + std::vector<Botan::TLS::Signature_Scheme> schemes; + for(size_t pref : m_args.get_int_vec_opt("verify-prefs")) + { + schemes.push_back(static_cast<Botan::TLS::Signature_Scheme>(pref)); + } + + // BoGo gets sad if these are not included in our signature_algorithms extension + + if(!m_args.flag_set("server") && false) + { + schemes.push_back(Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256); + schemes.push_back(Botan::TLS::Signature_Scheme::ECDSA_SHA256); + } + + return schemes; + } + + return Botan::TLS::Policy::allowed_signature_schemes(); + } + + //size_t minimum_signature_strength() const override; + + //bool require_cert_revocation_info() const override; + + std::vector<Botan::TLS::Group_Params> key_exchange_groups() const override + { + if(m_args.option_used("curves")) + { + std::vector<Botan::TLS::Group_Params> groups; + for(size_t pref : m_args.get_int_vec_opt("curves")) + { + groups.push_back(static_cast<Botan::TLS::Group_Params>(pref)); + } + + return groups; + } + + return Botan::TLS::Policy::key_exchange_groups(); + } + + bool use_ecc_point_compression() const override { return false; } // BoGo expects this + + //Botan::TLS::Group_Params choose_key_exchange_group(const std::vector<Botan::TLS::Group_Params>& peer_groups) const override; + + bool require_client_certificate_authentication() const + { + return m_args.flag_set("require-any-client-certificate"); + } + + bool request_client_certificate_authentication() const + { + return m_args.flag_set("verify-peer") || + m_args.flag_set("fail-cert-callback") || + require_client_certificate_authentication(); + } + + bool allow_insecure_renegotiation() const override + { + if(m_args.flag_set("expect-no-secure-renegotiation")) + return true; + else + return false; + } + + //bool include_time_in_hello_random() const override; + + bool allow_client_initiated_renegotiation() const override + { + if(m_args.flag_set("renegotiate-freely")) + return true; + + if(m_args.flag_set("renegotiate-once") && m_sessions <= 1) + return true; + + return false; + } + + bool allow_server_initiated_renegotiation() const override + { + return allow_client_initiated_renegotiation(); // same logic + } + + bool allow_tls10() const override + { + return (!m_args.flag_set("no-tls1")); + } + + bool allow_tls11() const override + { + return (!m_args.flag_set("no-tls11")); + } + + bool allow_tls12() const override + { + return (!m_args.flag_set("no-tls12")); + } + + //bool allow_dtls10() const override; + + //bool allow_dtls12() const override; + + //Botan::TLS::Group_Params default_dh_group() const override; + + //size_t minimum_dh_group_size() const override; + + size_t minimum_ecdsa_group_size() const override { return 224; } + + size_t minimum_ecdh_group_size() const override { return 224; } + + //size_t minimum_rsa_bits() const override; + + //size_t minimum_dsa_group_size() const override; + + //void check_peer_key_acceptable(const Botan::Public_Key& public_key) const override; + + //bool hide_unknown_users() const override; + + //uint32_t session_ticket_lifetime() const override; + + //std::vector<uint16_t> srtp_profiles() const override; + + bool only_resume_with_exact_version() const override + { + return false; + } + + bool acceptable_protocol_version(Botan::TLS::Protocol_Version version) const override + { + if(!Botan::TLS::Policy::acceptable_protocol_version(version)) + return false; + + if(m_args.option_used("min-version")) + { + const uint16_t min_version_16 = static_cast<uint16_t>(m_args.get_int_opt("min-version")); + Botan::TLS::Protocol_Version min_version(min_version_16 >> 8, min_version_16 & 0xFF); + if(min_version > version) + return false; + } + + if(m_args.option_used("max-version")) + { + const uint16_t max_version_16 = static_cast<uint16_t>(m_args.get_int_opt("max-version")); + Botan::TLS::Protocol_Version max_version(max_version_16 >> 8, max_version_16 & 0xFF); + if(version > max_version) + return false; + } + + return version.known_version(); + } + + bool send_fallback_scsv(Botan::TLS::Protocol_Version) const override + { + return m_args.flag_set("fallback-scsv"); + } + + //bool server_uses_own_ciphersuite_preferences() const override; + + //bool negotiate_encrypt_then_mac() const override; + + bool support_cert_status_message() const override + { + if(m_args.flag_set("server")) + return false; + return true; + } + + std::vector<uint16_t> ciphersuite_list(Botan::TLS::Protocol_Version version, + bool have_srp) const override; + + //size_t dtls_default_mtu() const override; + + //size_t dtls_initial_timeout() const override; + + //size_t dtls_maximum_timeout() const override; + + bool allow_resumption_for_renegotiation() const + { + return false; // BoGo expects this + } + + bool abort_connection_on_undesired_renegotiation() const + { + if(m_args.flag_set("renegotiate-ignore")) + return false; + else + return true; + } + + size_t maximum_certificate_chain_size() const + { + return m_args.get_int_opt_or_else("max-cert-list", 0); + } + + private: + const Shim_Arguments& m_args; + size_t m_sessions; + }; + +std::vector<uint16_t> Shim_Policy::ciphersuite_list(Botan::TLS::Protocol_Version version, + bool have_srp) const + { + std::vector<uint16_t> ciphersuite_codes; + + const std::string cipher_limit = m_args.get_string_opt_or_else("cipher", ""); + if(cipher_limit == "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:[TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384|TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256|TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CBC_SHA:[TLS_RSA_WITH_AES_256_GCM_SHA384|TLS_RSA_WITH_AES_256_CBC_SHA]") + { + std::vector<std::string> suites = { + "ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "ECDHE_RSA_WITH_AES_256_CBC_SHA", + "RSA_WITH_AES_256_GCM_SHA384", + "RSA_WITH_AES_256_CBC_SHA", + }; + + for(auto suite_name : suites) + { + const auto suite = Botan::TLS::Ciphersuite::from_name(suite_name); + if(suite.valid() == false) + shim_exit_with_error("Bad ciphersuite name " + suite_name); + ciphersuite_codes.push_back(suite.ciphersuite_code()); + } + } + else + { + // Hack: go in reverse order to avoid preferring 3DES + auto ciphersuites = Botan::TLS::Ciphersuite::all_known_ciphersuites(); + for(auto i = ciphersuites.rbegin(); i != ciphersuites.rend(); ++i) + { + const auto suite = *i; + // Can we use it? + if(suite.valid() == false) + continue; + + // Are we doing SRP? + if(!have_srp && suite.kex_method() == Botan::TLS::Kex_Algo::SRP_SHA) + continue; + + if(cipher_limit != "") + { + if(cipher_limit == "DEFAULT:!AES") + { + const std::string suite_algo = suite.cipher_algo(); + + if(suite_algo == "AES-128" || suite_algo == "AES-256" || + suite_algo == "AES-128/GCM" || suite_algo == "AES-256/GCM" || + suite_algo == "AES-128/CCM" || suite_algo == "AES-256/CCM" || + suite_algo == "AES-128/CCM(8)" || suite_algo == "AES-256/CCM(8)" || + suite_algo == "AES-128/OCB(12)" || suite_algo == "AES-256/OCB(12)") + { + continue; + } + } + else + { + shim_exit_with_error("Unknown cipher " + cipher_limit); + } + } + + if(!version.supports_aead_modes()) + { + // Are we doing AEAD in a non-AEAD version? + if(suite.mac_algo() == "AEAD") + continue; + + // Older (v1.0/v1.1) versions also do not support any hash but SHA-1 + if(suite.mac_algo() != "SHA-1") + continue; + } + + ciphersuite_codes.push_back(suite.ciphersuite_code()); + } + } + + return ciphersuite_codes; + } + +class Shim_Credentials final : public Botan::Credentials_Manager + { + public: + Shim_Credentials(const Shim_Arguments& args) : m_args(args) + { + m_psk_identity = m_args.get_string_opt_or_else("psk-identity", ""); + + const std::string psk_str = m_args.get_string_opt_or_else("psk", ""); + m_psk = Botan::SymmetricKey(reinterpret_cast<const uint8_t*>(psk_str.data()), psk_str.size()); + + if(m_args.option_used("key-file") && m_args.option_used("cert-file")) + { + Botan::DataSource_Stream key_stream(m_args.get_string_opt("key-file")); + m_key = Botan::PKCS8::load_key(key_stream); + + Botan::DataSource_Stream cert_stream(m_args.get_string_opt("cert-file")); + + while(!cert_stream.end_of_data()) + { + try + { + m_cert_chain.push_back(Botan::X509_Certificate(cert_stream)); + } + catch(...) {} + } + } + } + + std::string psk_identity(const std::string& /*type*/, + const std::string& /*context*/, + const std::string& /*identity_hint*/) override + { + return m_psk_identity; + } + + std::string psk_identity_hint(const std::string& /*type*/, + const std::string& /*context*/) + { + return m_psk_identity; + } + + Botan::SymmetricKey psk(const std::string& type, + const std::string& context, + const std::string& identity) override + { + if(!m_args.flag_set("no-ticket") && type == "tls-server" && context == "session-ticket") + return Botan::SymmetricKey("ABCDEF0123456789"); + + if(identity != m_psk_identity) + throw Shim_Exception("Unexpected PSK identity"); + return m_psk; + } + + std::vector<Botan::X509_Certificate> cert_chain( + const std::vector<std::string>& cert_key_types, + const std::string& /*type*/, + const std::string& /*context*/) + { + if(m_args.flag_set("fail-cert-callback")) + throw std::runtime_error("Simulating cert verify callback failure"); + + if(m_key != nullptr && m_cert_chain.size() > 0) + { + for(std::string t : cert_key_types) + { + if(t == m_key->algo_name()) + return m_cert_chain; + } + } + + return {}; + } + + Botan::Private_Key* private_key_for(const Botan::X509_Certificate& /*cert*/, + const std::string& /*type*/, + const std::string& /*context*/) override + { + // assumes cert == m_cert + return m_key.get(); + } + + private: + const Shim_Arguments& m_args; + Botan::SymmetricKey m_psk; + std::string m_psk_identity; + std::unique_ptr<Botan::Private_Key> m_key; + std::vector<Botan::X509_Certificate> m_cert_chain; + }; + +class Shim_Callbacks final : public Botan::TLS::Callbacks + { + public: + Shim_Callbacks(const Shim_Arguments& args, Shim_Socket& socket, Shim_Policy& policy) : + m_channel(nullptr), + m_args(args), + m_policy(policy), + m_socket(socket), + m_is_datagram(args.flag_set("dtls")), + m_warning_alerts(0), + m_empty_records(0), + m_should_exit(false), + m_first_write_pending(true), + m_sent_close(false) + {} + + void set_channel(Botan::TLS::Channel* channel) + { + m_channel = channel; + } + + void tls_emit_data(const uint8_t data[], size_t size) override + { + //shim_log("Emit data len" + std::to_string(size)); + m_socket.write(data, size); + } + + void tls_record_received(uint64_t /*seq_no*/, const uint8_t data[], size_t size) override + { + + if(size == 0) + { + m_empty_records += 1; + if(m_empty_records > 32) + shim_exit_with_error(":TOO_MANY_EMPTY_FRAGMENTS:"); + } + else + { + m_empty_records = 0; + } + shim_log("Got a record len " + std::to_string(size)); + if(m_first_write_pending) + { + + //m_channel->send("hello"); + m_first_write_pending = false; + } + + std::vector<uint8_t> buf(data, data + size); + for(size_t i = 0; i != size; ++i) + buf[i] ^= 0xFF; + + shim_log("Sending " + std::to_string(size) + " bytes of blob"); + m_channel->send(buf); + } + + bool tls_verify_message(const Botan::Public_Key& key, + const std::string& emsa, + Botan::Signature_Format format, + const std::vector<uint8_t>& msg, + const std::vector<uint8_t>& sig) + { + if(m_args.option_used("expect-peer-signature-algorithm")) + { + const auto scheme = static_cast<Botan::TLS::Signature_Scheme>(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); + } + } + return Botan::TLS::Callbacks::tls_verify_message(key, emsa, format, msg, sig); + } + + void tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& /*cert_chain*/, + const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& /*ocsp_responses*/, + const std::vector<Botan::Certificate_Store*>& /*trusted_roots*/, + Botan::Usage_Type /*usage*/, + const std::string& /*hostname*/, + const Botan::TLS::Policy& /*policy*/) override + { + if(m_args.flag_set("enable-ocsp-stapling") && + m_args.flag_set("use-ocsp-callback") && + m_args.flag_set("fail-ocsp-callback")) + { + throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::BAD_CERTIFICATE_STATUS_RESPONSE, + "Simulated OCSP callback failure"); + } + + if(m_args.flag_set("verify-peer") && m_args.flag_set("verify-fail")) + { + throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::BAD_CERTIFICATE, + "Test requires rejecting cert"); + } + } + + std::string tls_server_choose_app_protocol(const std::vector<std::string>& client_protos) + { + if(client_protos.empty()) + return ""; // shouldn't happen? + + if(m_args.flag_set("decline-alpn")) + return ""; + + if(m_args.option_used("expect-advertised-alpn")) + { + const std::vector<std::string> expected = m_args.get_alpn_string_vec_opt("expect-advertised-alpn"); + + if(client_protos != expected) + shim_exit_with_error("Bad ALPN from client"); + } + + if(m_args.option_used("select-alpn")) + return m_args.get_string_opt("select-alpn"); + + return client_protos[0]; // if not configured just pick something + } + + void tls_alert(Botan::TLS::Alert alert) override + { + shim_log("Got an alert " + alert.type_string()); + + if(alert.type() == Botan::TLS::Alert::RECORD_OVERFLOW) + { + shim_exit_with_error(":TLSV1_ALERT_RECORD_OVERFLOW:"); + } + + if(!alert.is_fatal()) + { + m_warning_alerts++; + if(m_warning_alerts > 5) + shim_exit_with_error(":TOO_MANY_WARNING_ALERTS:"); + } + + if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY && m_sent_close == false) + { + m_channel->send_alert(alert); + m_sent_close = true; + } + } + + bool tls_session_established(const Botan::TLS::Session& session) override + { + shim_log("Session established: " + Botan::hex_encode(session.session_id()) + + " version " + session.version().to_string() + + " cipher " + session.ciphersuite().to_string() + + " EMS " + std::to_string(session.supports_extended_master_secret())); + // probably need tests here? + + m_policy.incr_session_established(); + + if(m_args.option_used("expect-no-session-id")) + { + if(session.session_id().size() > 0) + shim_exit_with_error("GOT UNEXPECTED SESSION ID"); + } + + if(m_args.option_used("expect-version")) + { + if(session.version().version_code() != m_args.get_int_opt("expect-version")) + shim_exit_with_error("UNEXPECTED VERSION"); + } + + if(m_args.flag_set("expect-secure-renegotiation")) + { + if(m_channel->secure_renegotiation_supported() == false) + shim_exit_with_error("EXPECTED SECURE RENEGOTIATION"); + } + else if(m_args.flag_set("expect-no-secure-renegotiation")) + { + if(m_channel->secure_renegotiation_supported() == true) + shim_exit_with_error("EXPECTED NO SECURE RENEGOTIATION"); + } + + if(m_args.flag_set("expect-extended-master-secret")) + { + if(session.supports_extended_master_secret() == false) + shim_exit_with_error("NO ETM"); + } + + return true; + } + + void tls_session_activated() override + { + if(m_args.flag_set("send-alert")) + { + m_channel->send_fatal_alert(Botan::TLS::Alert::DECOMPRESSION_FAILURE); + return; + } + + if(size_t length = m_args.get_int_opt_or_else("export-keying-material", 0)) + { + const std::string label = m_args.get_string_opt("export-label"); + const std::string context = m_args.get_string_opt("export-context"); + const auto exported = m_channel->key_material_export(label, context, length); + m_channel->send(exported.bits_of()); + shim_log("Sending " + std::to_string(length) + " bytes of export"); + } + + const std::string alpn = m_channel->application_protocol(); + + if(m_args.option_used("expect-alpn")) + { + if(alpn != m_args.get_string_opt("expect-alpn")) + shim_exit_with_error("Got unexpected ALPN"); + } + + if(alpn == "baz" && !m_args.flag_set("allow-unknown-alpn-protos")) + { + throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::ILLEGAL_PARAMETER, + "Unexpected ALPN protocol"); + } + + if(m_args.flag_set("write-different-record-sizes")) + { + static const size_t record_sizes[] = { + 0, 1, 255, 256, 257, 16383, 16384, 16385, 32767, 32768, 32769 + }; + + std::vector<uint8_t> buf(32769, 0x42); + + for(size_t sz : record_sizes) + { + m_channel->send(buf.data(), sz); + } + + m_channel->close(); + } + } + + bool should_exit() const + { + return m_should_exit; + } + + private: + Botan::TLS::Channel* m_channel; + const Shim_Arguments& m_args; + Shim_Policy& m_policy; + Shim_Socket& m_socket; + const bool m_is_datagram; + size_t m_warning_alerts; + size_t m_empty_records; + bool m_should_exit; + bool m_first_write_pending; + bool m_sent_close; + }; + +} + +int main(int /*argc*/, char* argv[]) + { + const std::chrono::milliseconds timeout(1000); // ?? + + try + { + std::unique_ptr<Shim_Arguments> args = parse_options(argv); + + if(args->flag_set("is-handshaker-supported")) + { + return shim_output("No\n"); + } + + const uint16_t port = static_cast<uint16_t>(args->get_int_opt("port")); + 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"); + + /* + if(is_server) + throw Shim_Exception("No support for server yet", 89); + */ + if(is_datagram) + throw Shim_Exception("No support for DTLS yet", 89); + + Botan::ChaCha_RNG rng(Botan::secure_vector<uint8_t>(64)); + Botan::TLS::Session_Manager_In_Memory session_manager(rng, 1024); + Shim_Credentials creds(*args); + + for(size_t i = 0; i != resume_count+1; ++i) + { + Shim_Socket socket("localhost", port); + + Shim_Policy policy(*args); + Shim_Callbacks callbacks(*args, socket, policy); + + std::unique_ptr<Botan::TLS::Channel> chan; + + if(is_server) + { + chan.reset(new Botan::TLS::Server(callbacks, session_manager, creds, policy, rng, is_datagram)); + } + else + { + Botan::TLS::Protocol_Version offer_version = policy.latest_supported_version(is_datagram); + shim_log("Offering " + offer_version.to_string()); + Botan::TLS::Server_Information server_info(args->get_string_opt_or_else("host-name", "localhost"), port); + const std::vector<std::string> next_protocols = args->get_alpn_string_vec_opt("advertise-alpn"); + chan.reset(new Botan::TLS::Client(callbacks, session_manager, creds, policy, rng, + server_info, offer_version, next_protocols)); + } + + callbacks.set_channel(chan.get()); + + shim_log("Starting read loop"); + + std::vector<uint8_t> buf(args->get_int_opt_or_else("read-size", 18*1024)); + size_t need_to_read = 5; // header len + + while(!callbacks.should_exit()) + { + size_t got = socket.read(buf.data(), buf.size()); + if(got == 0) + { + shim_log("EOF on socket"); + break; + } + + need_to_read = chan->received_data(buf.data(), got); + + if(need_to_read == 0) + need_to_read = 5; + } + + if(args->option_used("expect-total-renegotiations")) + { + const size_t exp = args->get_int_opt("expect-total-renegotiations"); + + if(exp != policy.sessions_established() - 1) + throw Shim_Exception("Unexpected number of renegotiations: saw " + + std::to_string(policy.sessions_established() - 1) + + " exp " + std::to_string(exp)); + } + } + + } + catch(Shim_Exception& e) + { + shim_exit_with_error(e.what(), e.rc()); + } + catch(std::exception& e) + { + shim_exit_with_error(map_to_bogo_error(e.what())); + } + catch(...) + { + shim_exit_with_error("Unknown exception", 3); + } + return 0; + } diff --git a/src/bogo_shim/config.json b/src/bogo_shim/config.json new file mode 100644 index 000000000..57ddb04d2 --- /dev/null +++ b/src/bogo_shim/config.json @@ -0,0 +1,104 @@ +{ + "LooseErrorTests": { + "AppDataBeforeHandshake": "BoGo expects different error before vs after CCS", + "AppDataBeforeHandshake-Empty": "Invalid record message", + "ServerHelloBogusCipher": "Unexpected error", + "Garbage": "Decoding error", + "Resume-Client-CipherMismatch": "Unexpected error", + "InvalidECDHPoint-Server": "Unexpected error", + "NoSharedCipher": "Unexpected error" + }, + + "DisabledTests": { + "TicketSessionIDLength-33-TLS*": "This test seems to be broken? Ask BoGo people", + "Renegotiate-SameClientVersion": "BoGo requires resumption use wrong version (?)", + "ClientAuth-Sign-Negotiate-*": "BoGo seems to be behaving badly here", + "Renegotiate-Server-Forbidden": "Is this restriction BoringSSL specific?", + "Resume-Client-Mismatch-*": "Need to investigate", + "Resume-Client-NoResume-TLS1-TLS11": "Need to investigate", + "Resume-Client-NoResume-TLS1-TLS12": "Need to investigate", + "Resume-Client-NoResume-TLS11-TLS12": "Need to investigate", + + "*KeyUpdate*": "No TLS 1.3", + "*TLS13*": "No TLS 1.3", + "Server-JDK11*": "No TLS 1.3", + "*Binder*": "No TLS 1.3", + "PartialEncryptedExtensionsWithServerHello": "No TLS 1.3", + "Client-RejectJDK11DowngradeRandom": "No TLS 1.3", + "FragmentedClientVersion": "No TLS 1.3", + + "ConflictingVersionNegotiation*": "No support for 1.3 version extension", + "VersionNegotiationExtension*": "No support for 1.3 version extension", + "IgnoreClientVersionOrder": "No support for 1.3 version extension", + "NoSupportedVersions": "No support for 1.3 version extension", + + "DuplicateCertCompressionExt*": "No support for 1.3 cert compression extension", + + "Downgrade*": "The 1.3 downgrade indicator is not implemented", + + "*SSL3*": "No SSLv3", + "*SSLv3*": "No SSLv3", + + "*NPN*": "No support for NPN", + "ALPNServer-Preferred-*": "No support for NPN", + + "*SignedCertificateTimestamp*": "No support for SCT", + "*SCT*": "No support for SCT", + + "*NULL-SHA*": "No support for NULL ciphers", + "*GREASE*": "No support for GREASE", + "QUICTransportParams*": "No support for QUIC", + "*ChannelID*": "No support for ChannelID", + "*TokenBinding*": "No support for Token Binding", + "ClientHelloPadding": "No support for client hello padding extension", + "TLSUnique*": "Not supported", + "*CECPQ2*": "Not implemented", + "*P-224*": "P-224 not supported in TLS", + "*V2ClientHello*": "No support for SSLv2 client hellos", + "*Ed25519*": "Ed25519 not implemented in TLS", + "Http*": "Stack does not have detection logic for HTTP", + "*FalseStart*": "Botan doesn't do false start", + "MaxSendFragment*": "Maximum fragment extension not supported", + "ExportKeyingMaterial-EmptyContext*": "No support for this", + + "CheckLeafCurve": "Botan ignores this", + + "OCSPStapling-Server-*": "Server doesn't support OCSP stapling currently", + + "CipherNegotiation-2": "No support for cipher equivalence classes", + "CipherNegotiation-3": "No support for cipher equivalence classes", + "CipherNegotiation-4": "No support for cipher equivalence classes", + "CipherNegotiation-5": "No support for cipher equivalence classes", + "CipherNegotiation-8": "No support for cipher equivalence classes", + + "ALPNServer-SelectEmpty-*": "Botan treats empty ALPN from callback as a decline", + + "ServerAuth-Verify-ECDSA-P521-SHA512-TLS12": "BoringSSL will sign SHA-1 and SHA-512 with ECDSA but not accept them.", + "ServerAuth-Verify-ECDSA-SHA1-TLS12": "BoringSSL will sign SHA-1 and SHA-512 with ECDSA but not accept them.", + "ClientAuth-Verify-ECDSA-P521-SHA512-TLS12": "BoringSSL will sign SHA-1 and SHA-512 with ECDSA but not accept them.", + "ClientAuth-Verify-ECDSA-SHA1-TLS12": "BoringSSL will sign SHA-1 and SHA-512 with ECDSA but not accept them.", + + "CurveTest-Client-Compressed*": "Point compression is supported, which BoGo doesn't expect", + "PointFormat-Client-MissingUncompressed": "Point compression is supported, which BoGo doesn't expect", + "CurveTest-Server-Compressed*": "Point compression is supported, which BoGo doesn't expect", + "PointFormat-Server-MissingUncompressed": "Point compression is supported, which BoGo doesn't expect", + + "RSAPSSSupport-ConfigNoPSS-NoCerts-TLS12-Client": "Not possible to disable PSS", + "RSAPSSSupport-ConfigNoPSS-TLS12-Client": "Not possible to disable PSS", + "RSAPSSSupport-ConfigPSS-NoCerts-TLS12-Client": "Not possible to disable PSS", + "RSAPSSSupport-Default-NoCerts-TLS12-Client": "Not possible to disable PSS", + "RSAPSSSupport-ConfigNoPSS-NoCerts-TLS12-Server": "Not possible to disable PSS", + "RSAPSSSupport-ConfigNoPSS-TLS12-Server": "Not possible to disable PSS", + "RSAPSSSupport-ConfigPSS-NoCerts-TLS12-Server": "Not possible to disable PSS", + "RSAPSSSupport-Default-NoCerts-TLS12-Server": "Not possible to disable PSS", + + "*DTLS*": "Shim needs to get support for packeted BIO", + "UnsolicitedServerNameAck*": "The shim always sends SNI so test doesn't work", + + "Renegotiate-Client-Packed": "Packing HelloRequest with Finished loses the HelloRequest (bug)", + "SendHalfHelloRequest*PackHandshake": "Packing HelloRequest with Finished loses the HelloRequest (bug)", + "PartialClientFinishedWithClientHello": "Need to check for buffered messages when CCS (bug)", + "SendOCSPResponseOnResume-TLS12": "Not supported by Botan (bug)", + "ECDSAKeyUsage-TLS12": "Botan ignores KeyUsage (bug)" + } +} diff --git a/src/build-data/makefile.in b/src/build-data/makefile.in index 7111c7b19..55e7a3546 100644 --- a/src/build-data/makefile.in +++ b/src/build-data/makefile.in @@ -27,7 +27,7 @@ SCRIPTS_DIR = %{scripts_dir} INSTALLED_LIB_DIR = %{prefix}/%{libdir} # The primary target -all: libs cli tests docs +all: %{all_targets} # Executable targets CLI = %{cli_exe} @@ -78,7 +78,7 @@ $(TEST): $(LIBRARIES) $(TESTOBJS) FUZZERS = %{fuzzer_bin} -fuzzers: libs $(FUZZERS) +fuzzers: $(LIBRARIES) $(FUZZERS) fuzzer_corpus: git clone --depth=1 https://github.com/randombit/crypto-corpus.git fuzzer_corpus @@ -88,6 +88,16 @@ fuzzer_corpus_zip: fuzzer_corpus %{endif} +%{if build_bogo_shim} + +bogo_shim: %{out_dir}/botan_bogo_shim + +# BoGo shim +%{out_dir}/botan_bogo_shim: %{bogo_shim_src} $(LIBRARIES) + $(CXX) $(BUILD_FLAGS) %{include_paths} %{bogo_shim_src} $(EXE_LINKS_TO) $(LDFLAGS) %{output_to_exe}$@ + +%{endif} + # Library targets %{if build_static_lib} diff --git a/src/lib/tls/msg_cert_req.cpp b/src/lib/tls/msg_cert_req.cpp index 3bbdcb1f1..b6fc3825b 100644 --- a/src/lib/tls/msg_cert_req.cpp +++ b/src/lib/tls/msg_cert_req.cpp @@ -57,7 +57,7 @@ Certificate_Req::Certificate_Req(Handshake_IO& io, const std::vector<X509_DN>& ca_certs, Protocol_Version version) : m_names(ca_certs), - m_cert_key_types({ "RSA", "DSA", "ECDSA" }) + m_cert_key_types({ "RSA", "ECDSA", "DSA" }) { if(version.supports_negotiable_signature_algorithms()) { diff --git a/src/lib/tls/msg_cert_status.cpp b/src/lib/tls/msg_cert_status.cpp index 8ad37336b..c0cd82a28 100644 --- a/src/lib/tls/msg_cert_status.cpp +++ b/src/lib/tls/msg_cert_status.cpp @@ -22,8 +22,8 @@ Certificate_Status::Certificate_Status(const std::vector<uint8_t>& buf) if(buf.size() < 5) throw Decoding_Error("Invalid Certificate_Status message: too small"); - if(buf[0] != 1) - throw Decoding_Error("Unexpected Certificate_Status message: unexpected message type"); + if(buf[0] != 1) // not OCSP + throw Decoding_Error("Unexpected Certificate_Status message: unexpected response type"); size_t len = make_uint32(0, buf[1], buf[2], buf[3]); @@ -31,33 +31,30 @@ Certificate_Status::Certificate_Status(const std::vector<uint8_t>& buf) if(buf.size() != len + 4) throw Decoding_Error("Invalid Certificate_Status: invalid length field"); - m_response = std::make_shared<OCSP::Response>(buf.data() + 4, buf.size() - 4); + m_response.assign(buf.begin() + 4, buf.end()); } Certificate_Status::Certificate_Status(Handshake_IO& io, Handshake_Hash& hash, std::shared_ptr<const OCSP::Response> ocsp) : - m_response(ocsp) + m_response(ocsp->raw_bits()) { hash.update(io.send(*this)); } std::vector<uint8_t> Certificate_Status::serialize() const { - BOTAN_ASSERT_NONNULL(m_response); - const std::vector<uint8_t>& m_resp_bits = m_response->raw_bits(); - - if(m_resp_bits.size() > 0xFFFFFF) // unlikely + if(m_response.size() > 0xFFFFFF) // unlikely throw Encoding_Error("OCSP response too long to encode in TLS"); - const uint32_t m_resp_bits_len = static_cast<uint32_t>(m_resp_bits.size()); + const uint32_t m_response_len = static_cast<uint32_t>(m_response.size()); std::vector<uint8_t> buf; buf.push_back(1); // type OCSP for(size_t i = 1; i < 4; ++i) - buf[i] = get_byte(i, m_resp_bits_len); + buf[i] = get_byte(i, m_response_len); - buf += m_resp_bits; + buf += m_response; return buf; } diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp index 230474e7a..021185003 100644 --- a/src/lib/tls/msg_cert_verify.cpp +++ b/src/lib/tls/msg_cert_verify.cpp @@ -51,6 +51,7 @@ Certificate_Verify::Certificate_Verify(const std::vector<uint8_t>& buf, } m_signature = reader.get_range<uint8_t>(2, 0, 65535); + reader.assert_done(); } /* diff --git a/src/lib/tls/msg_certificate.cpp b/src/lib/tls/msg_certificate.cpp index a2a48ceea..1ec0766ce 100644 --- a/src/lib/tls/msg_certificate.cpp +++ b/src/lib/tls/msg_certificate.cpp @@ -31,7 +31,7 @@ Certificate::Certificate(Handshake_IO& io, /** * Deserialize a Certificate message */ -Certificate::Certificate(const std::vector<uint8_t>& buf, const Policy& /*policy_currently_unused*/) +Certificate::Certificate(const std::vector<uint8_t>& buf, const Policy& policy) { if(buf.size() < 3) throw Decoding_Error("Certificate: Message malformed"); @@ -41,6 +41,10 @@ Certificate::Certificate(const std::vector<uint8_t>& buf, const Policy& /*policy if(total_size != buf.size() - 3) throw Decoding_Error("Certificate: Message malformed"); + const size_t max_size = policy.maximum_certificate_chain_size(); + if(max_size > 0 && total_size > max_size) + throw Decoding_Error("Certificate chain exceeds policy specified maximum size"); + const uint8_t* certs = buf.data() + 3; while(size_t remaining_bytes = buf.data() + buf.size() - certs) diff --git a/src/lib/tls/msg_client_hello.cpp b/src/lib/tls/msg_client_hello.cpp index 2d303a77e..539e2a780 100644 --- a/src/lib/tls/msg_client_hello.cpp +++ b/src/lib/tls/msg_client_hello.cpp @@ -92,8 +92,9 @@ Client_Hello::Client_Hello(Handshake_IO& io, m_suites(policy.ciphersuite_list(m_version, !client_settings.srp_identifier().empty())), m_comp_methods(1) { - BOTAN_ASSERT(policy.acceptable_protocol_version(client_settings.protocol_version()), - "Our policy accepts the version we are offering"); + if(!policy.acceptable_protocol_version(m_version)) + throw Internal_Error("Offering " + m_version.to_string() + + " but our own policy does not accept it"); /* * Place all empty extensions in front to avoid a bug in some systems @@ -106,7 +107,9 @@ Client_Hello::Client_Hello(Handshake_IO& io, m_extensions.add(new Encrypt_then_MAC); m_extensions.add(new Renegotiation_Extension(reneg_info)); - m_extensions.add(new Server_Name_Indicator(client_settings.hostname())); + + if(client_settings.hostname() != "") + m_extensions.add(new Server_Name_Indicator(client_settings.hostname())); if(policy.support_cert_status_message()) m_extensions.add(new Certificate_Status_Request({}, {})); @@ -163,6 +166,10 @@ Client_Hello::Client_Hello(Handshake_IO& io, m_suites(policy.ciphersuite_list(m_version, (session.srp_identifier() != ""))), m_comp_methods(1) { + if(!policy.acceptable_protocol_version(m_version)) + throw Internal_Error("Offering " + m_version.to_string() + + " but our own policy does not accept it"); + if(!value_exists(m_suites, session.ciphersuite_code())) m_suites.push_back(session.ciphersuite_code()); @@ -273,7 +280,7 @@ Client_Hello::Client_Hello(const std::vector<uint8_t>& buf) m_comp_methods = reader.get_range_vector<uint8_t>(1, 1, 255); - m_extensions.deserialize(reader); + m_extensions.deserialize(reader, Connection_Side::SERVER); if(offered_suite(static_cast<uint16_t>(TLS_EMPTY_RENEGOTIATION_INFO_SCSV))) { diff --git a/src/lib/tls/msg_client_kex.cpp b/src/lib/tls/msg_client_kex.cpp index b3dff072e..f55568c8e 100644 --- a/src/lib/tls/msg_client_kex.cpp +++ b/src/lib/tls/msg_client_kex.cpp @@ -256,6 +256,7 @@ Client_Key_Exchange::Client_Key_Exchange(const std::vector<uint8_t>& contents, TLS_Data_Reader reader("ClientKeyExchange", contents); const std::vector<uint8_t> encrypted_pre_master = reader.get_range<uint8_t>(2, 0, 65535); + reader.assert_done(); PK_Decryptor_EME decryptor(*server_rsa_kex_key, rng, "PKCS1v15"); @@ -386,6 +387,8 @@ Client_Key_Exchange::Client_Key_Exchange(const std::vector<uint8_t>& contents, */ m_pre_master = rng.random_vec(ka_key->public_value().size()); } + + reader.assert_done(); } else throw Internal_Error("Client_Key_Exchange: Unknown key exchange negotiated"); diff --git a/src/lib/tls/msg_server_hello.cpp b/src/lib/tls/msg_server_hello.cpp index 223bddde5..b4c47f516 100644 --- a/src/lib/tls/msg_server_hello.cpp +++ b/src/lib/tls/msg_server_hello.cpp @@ -163,7 +163,7 @@ Server_Hello::Server_Hello(const std::vector<uint8_t>& buf) m_comp_method = reader.get_byte(); - m_extensions.deserialize(reader); + m_extensions.deserialize(reader, Connection_Side::CLIENT); } /* diff --git a/src/lib/tls/msg_session_ticket.cpp b/src/lib/tls/msg_session_ticket.cpp index b9bf7e94d..bd0d74ceb 100644 --- a/src/lib/tls/msg_session_ticket.cpp +++ b/src/lib/tls/msg_session_ticket.cpp @@ -40,6 +40,7 @@ New_Session_Ticket::New_Session_Ticket(const std::vector<uint8_t>& buf) m_ticket_lifetime_hint = reader.get_uint32_t(); m_ticket = reader.get_range<uint8_t>(2, 0, 65535); + reader.assert_done(); } std::vector<uint8_t> New_Session_Ticket::serialize() const diff --git a/src/lib/tls/tls_alert.cpp b/src/lib/tls/tls_alert.cpp index e1e8c6eb6..60c9c4b98 100644 --- a/src/lib/tls/tls_alert.cpp +++ b/src/lib/tls/tls_alert.cpp @@ -6,7 +6,7 @@ */ #include <botan/tls_alert.h> -#include <botan/exceptn.h> +#include <botan/tls_exceptn.h> namespace Botan { @@ -15,13 +15,13 @@ namespace TLS { Alert::Alert(const secure_vector<uint8_t>& buf) { if(buf.size() != 2) - throw Decoding_Error("Alert: Bad size " + std::to_string(buf.size()) + - " for alert message"); + throw Decoding_Error("Bad size (" + std::to_string(buf.size()) + + ") for TLS alert message"); if(buf[0] == 1) m_fatal = false; else if(buf[0] == 2) m_fatal = true; else - throw Decoding_Error("Alert: Bad code for alert level"); + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Bad code for TLS alert level"); const uint8_t dc = buf[1]; @@ -103,6 +103,8 @@ std::string Alert::type_string() const return "bad_certificate_hash_value"; case UNKNOWN_PSK_IDENTITY: return "unknown_psk_identity"; + case CERTIFICATE_REQUIRED: + return "certificate_required"; case NO_APPLICATION_PROTOCOL: return "no_application_protocol"; diff --git a/src/lib/tls/tls_alert.h b/src/lib/tls/tls_alert.h index 89530e238..d9d3fe313 100644 --- a/src/lib/tls/tls_alert.h +++ b/src/lib/tls/tls_alert.h @@ -56,6 +56,7 @@ class BOTAN_PUBLIC_API(2,0) Alert final BAD_CERTIFICATE_STATUS_RESPONSE = 113, BAD_CERTIFICATE_HASH_VALUE = 114, UNKNOWN_PSK_IDENTITY = 115, + CERTIFICATE_REQUIRED = 116, // RFC 8446 NO_APPLICATION_PROTOCOL = 120, // RFC 7301 diff --git a/src/lib/tls/tls_algos.h b/src/lib/tls/tls_algos.h index 19612be2e..4852a4349 100644 --- a/src/lib/tls/tls_algos.h +++ b/src/lib/tls/tls_algos.h @@ -109,10 +109,10 @@ enum class Signature_Scheme : uint16_t { BOTAN_UNSTABLE_API const std::vector<Signature_Scheme>& all_signature_schemes(); -bool signature_scheme_is_known(Signature_Scheme scheme); +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 hash_function_of_scheme(Signature_Scheme scheme); -std::string padding_string_for_scheme(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); /* diff --git a/src/lib/tls/tls_channel.cpp b/src/lib/tls/tls_channel.cpp index e021be518..0ee77c83c 100644 --- a/src/lib/tls/tls_channel.cpp +++ b/src/lib/tls/tls_channel.cpp @@ -187,8 +187,13 @@ void Channel::renegotiate(bool force_full_renegotiation) return; if(auto active = active_state()) + { + if(force_full_renegotiation == false) + force_full_renegotiation = !policy().allow_resumption_for_renegotiation(); + initiate_handshake(create_handshake_state(active->version()), force_full_renegotiation); + } else throw Invalid_State("Cannot renegotiate on inactive connection"); } @@ -334,12 +339,41 @@ size_t Channel::received_data(const uint8_t input[], size_t input_size) throw TLS_Exception(Alert::RECORD_OVERFLOW, "TLS plaintext record is larger than allowed maximum"); + if(auto pending = pending_state()) + { + if(pending->server_hello() != nullptr && record_version != pending->version()) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Received unexpected record version"); + } + } + else if(auto active = active_state()) + { + if(record_version != active->version()) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Received unexpected record version"); + } + } + else + { + // For initial records just check for basic sanity + if(record_version.major_version() != 3 && + record_version.major_version() != 0xFE) + { + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Received unexpected record version in initial record"); + } + } + if(record_type == HANDSHAKE || record_type == CHANGE_CIPHER_SPEC) { process_handshake_ccs(record_data, record_sequence, record_type, record_version); } else if(record_type == APPLICATION_DATA) { + if(pending_state() != nullptr) + throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Can't interleave application and handshake data"); process_application_data(record_sequence, record_data); } else if(record_type == ALERT) @@ -444,13 +478,7 @@ void Channel::process_application_data(uint64_t seq_no, const secure_vector<uint if(!active_state()) throw Unexpected_Message("Application data before handshake done"); - /* - * OpenSSL among others sends empty records in versions - * before TLS v1.1 in order to randomize the IV of the - * following record. Avoid spurious callbacks. - */ - if(record.size() > 0) - callbacks().tls_record_received(seq_no, record.data(), record.size()); + callbacks().tls_record_received(seq_no, record.data(), record.size()); } void Channel::process_alert(const secure_vector<uint8_t>& record) @@ -483,7 +511,7 @@ void Channel::write_record(Connection_Cipher_State* cipher_state, uint16_t epoch { BOTAN_ASSERT(m_pending_state || m_active_state, "Some connection state exists"); - Protocol_Version record_version = + const Protocol_Version record_version = (m_pending_state) ? (m_pending_state->version()) : (m_active_state->version()); Record_Message record_message(record_type, 0, input, length); @@ -520,18 +548,29 @@ void Channel::send_record_array(uint16_t epoch, uint8_t type, const uint8_t inpu if(type == APPLICATION_DATA && m_active_state->version().supports_explicit_cbc_ivs() == false) { - write_record(cipher_state.get(), epoch, type, input, 1); - input += 1; - length -= 1; - } + while(length) + { + write_record(cipher_state.get(), epoch, type, input, 1); + input += 1; + length -= 1; - while(length) + const size_t sending = std::min<size_t>(length, MAX_PLAINTEXT_SIZE); + write_record(cipher_state.get(), epoch, type, input, sending); + + input += sending; + length -= sending; + } + } + else { - const size_t sending = std::min<size_t>(length, MAX_PLAINTEXT_SIZE); - write_record(cipher_state.get(), epoch, type, input, sending); + while(length) + { + const size_t sending = std::min<size_t>(length, MAX_PLAINTEXT_SIZE); + write_record(cipher_state.get(), epoch, type, input, sending); - input += sending; - length -= sending; + input += sending; + length -= sending; + } } } diff --git a/src/lib/tls/tls_channel.h b/src/lib/tls/tls_channel.h index 0362faaa8..63cbcf0fc 100644 --- a/src/lib/tls/tls_channel.h +++ b/src/lib/tls/tls_channel.h @@ -198,6 +198,8 @@ class BOTAN_PUBLIC_API(2,0) Channel */ bool timeout_check(); + virtual std::string application_protocol() const = 0; + protected: virtual void process_handshake_msg(const Handshake_State* active_state, diff --git a/src/lib/tls/tls_ciphersuite.cpp b/src/lib/tls/tls_ciphersuite.cpp index b8a7e70d7..88837387e 100644 --- a/src/lib/tls/tls_ciphersuite.cpp +++ b/src/lib/tls/tls_ciphersuite.cpp @@ -57,6 +57,18 @@ bool Ciphersuite::ecc_ciphersuite() const auth_method() == Auth_Method::ECDSA; } +bool Ciphersuite::usable_in_version(Protocol_Version version) const + { + if(!version.supports_aead_modes()) + { + // Old versions do not support AEAD, or any MAC but SHA-1 + if(mac_algo() != "SHA-1") + return false; + } + + return true; + } + bool Ciphersuite::cbc_ciphersuite() const { return (mac_algo() != "AEAD"); @@ -81,6 +93,19 @@ Ciphersuite Ciphersuite::by_id(uint16_t suite) return Ciphersuite(); // some unknown ciphersuite } +Ciphersuite Ciphersuite::from_name(const std::string& name) + { + const std::vector<Ciphersuite>& all_suites = all_known_ciphersuites(); + + for(auto suite : all_suites) + { + if(suite.to_string() == name) + return suite; + } + + return Ciphersuite(); // some unknown ciphersuite + } + namespace { bool have_hash(const std::string& prf) diff --git a/src/lib/tls/tls_ciphersuite.h b/src/lib/tls/tls_ciphersuite.h index 2ee3df20e..7ef7623bb 100644 --- a/src/lib/tls/tls_ciphersuite.h +++ b/src/lib/tls/tls_ciphersuite.h @@ -10,6 +10,7 @@ #include <botan/types.h> #include <botan/tls_algos.h> +#include <botan/tls_version.h> #include <string> #include <vector> @@ -31,6 +32,13 @@ class BOTAN_PUBLIC_API(2,0) Ciphersuite final static Ciphersuite by_id(uint16_t suite); /** + * Convert an SSL/TLS ciphersuite name to algorithm fields + * @param name the IANA name for the desired ciphersuite + * @return ciphersuite object + */ + static Ciphersuite from_name(const std::string& name); + + /** * Returns true iff this suite is a known SCSV */ static bool is_scsv(uint16_t suite); @@ -115,6 +123,8 @@ class BOTAN_PUBLIC_API(2,0) Ciphersuite final */ bool valid() const { return m_usable; } + bool usable_in_version(Protocol_Version version) const; + bool operator<(const Ciphersuite& o) const { return ciphersuite_code() < o.ciphersuite_code(); } bool operator<(const uint16_t c) const { return ciphersuite_code() < c; } diff --git a/src/lib/tls/tls_client.cpp b/src/lib/tls/tls_client.cpp index 94616c60b..eb6d21b14 100644 --- a/src/lib/tls/tls_client.cpp +++ b/src/lib/tls/tls_client.cpp @@ -23,9 +23,10 @@ namespace { class Client_Handshake_State final : public Handshake_State { public: - // using Handshake_State::Handshake_State; - - Client_Handshake_State(Handshake_IO* io, Callbacks& cb) : Handshake_State(io, cb) {} + Client_Handshake_State(Handshake_IO* io, Callbacks& cb) : + Handshake_State(io, cb), + m_is_reneg(false) + {} const Public_Key& get_server_public_key() const { @@ -33,13 +34,26 @@ class Client_Handshake_State final : public Handshake_State return *server_public_key.get(); } - bool is_a_resumption() const { return (resume_master_secret.empty() == false); } + bool is_a_resumption() const { return (resumed_session != nullptr); } - std::unique_ptr<Public_Key> server_public_key; + bool is_a_renegotiation() const { return m_is_reneg; } + + const secure_vector<uint8_t>& resume_master_secret() const + { + BOTAN_STATE_CHECK(is_a_resumption()); + return resumed_session->master_secret(); + } + + const std::vector<X509_Certificate>& resume_peer_certs() const + { + BOTAN_STATE_CHECK(is_a_resumption()); + return resumed_session->peer_certs(); + } + std::unique_ptr<Public_Key> server_public_key; // Used during session resumption - secure_vector<uint8_t> resume_master_secret; - std::vector<X509_Certificate> resume_peer_certs; + std::unique_ptr<Session> resumed_session; + bool m_is_reneg = false; }; } @@ -123,8 +137,9 @@ std::vector<X509_Certificate> Client::get_peer_cert_chain(const Handshake_State& state) const { const Client_Handshake_State& cstate = dynamic_cast<const Client_Handshake_State&>(state); - if(cstate.resume_peer_certs.size() > 0) - return cstate.resume_peer_certs; + + if(cstate.is_a_resumption()) + return cstate.resume_peer_certs(); if(state.server_certs()) return state.server_certs()->cert_chain(); @@ -137,7 +152,8 @@ Client::get_peer_cert_chain(const Handshake_State& state) const void Client::initiate_handshake(Handshake_State& state, bool force_full_renegotiation) { - send_client_hello(state, force_full_renegotiation, state.version()); + send_client_hello(state, force_full_renegotiation, + policy().latest_supported_version(state.version().is_datagram_protocol())); } void Client::send_client_hello(Handshake_State& state_base, @@ -154,16 +170,23 @@ void Client::send_client_hello(Handshake_State& state_base, if(!force_full_renegotiation && !m_info.empty()) { - Session session_info; - if(session_manager().load_from_server_info(m_info, session_info)) + std::unique_ptr<Session> session_info(new Session);; + if(session_manager().load_from_server_info(m_info, *session_info)) { /* - Ensure that the session protocol type matches what we want to use + Ensure that the session protocol cipher and version are acceptable If not skip the resume and establish a new session */ - if(version == session_info.version() && policy().acceptable_ciphersuite(session_info.ciphersuite())) + const bool exact_version = session_info->version() == version; + const bool ok_version = + (session_info->version().is_datagram_protocol() == version.is_datagram_protocol()) && + policy().acceptable_protocol_version(session_info->version()); + + const bool session_version_ok = policy().only_resume_with_exact_version() ? exact_version : ok_version; + + if(policy().acceptable_ciphersuite(session_info->ciphersuite()) && session_version_ok) { - if(srp_identifier == "" || session_info.srp_identifier() == srp_identifier) + if(srp_identifier == "" || session_info->srp_identifier() == srp_identifier) { state.client_hello( new Client_Hello(state.handshake_io(), @@ -172,11 +195,10 @@ void Client::send_client_hello(Handshake_State& state_base, callbacks(), rng(), secure_renegotiation_data_for_client_hello(), - session_info, + *session_info, next_protocols)); - state.resume_master_secret = session_info.master_secret(); - state.resume_peer_certs = session_info.peer_certs(); + state.resumed_session = std::move(session_info); } } } @@ -215,19 +237,33 @@ void Client::process_handshake_msg(const Handshake_State* active_state, // Ignore request entirely if we are currently negotiating a handshake if(state.client_hello()) - return; + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Cannot renegotiate during a handshake"); + } if(policy().allow_server_initiated_renegotiation()) { - if(!secure_renegotiation_supported() && policy().allow_insecure_renegotiation() == false) - send_warning_alert(Alert::NO_RENEGOTIATION); + if(secure_renegotiation_supported() || policy().allow_insecure_renegotiation()) + { + state.m_is_reneg = true; + this->initiate_handshake(state, true); + } else - this->initiate_handshake(state, false); + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Client policy prohibits insecure renegotiation"); + } } else { - // RFC 5746 section 4.2 - send_warning_alert(Alert::NO_RENEGOTIATION); + if(policy().abort_connection_on_undesired_renegotiation()) + { + throw TLS_Exception(Alert::NO_RENEGOTIATION, "Client policy prohibits renegotiation"); + } + else + { + // RFC 5746 section 4.2 + send_warning_alert(Alert::NO_RENEGOTIATION); + } } return; @@ -257,6 +293,12 @@ void Client::process_handshake_msg(const Handshake_State* active_state, "Server replied with ciphersuite we didn't send"); } + if(!Ciphersuite::by_id(state.server_hello()->ciphersuite()).usable_in_version(state.server_hello()->version())) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server replied using a ciphersuite not allowed in version it offered"); + } + if(Ciphersuite::is_scsv(state.server_hello()->ciphersuite())) { throw TLS_Exception(Alert::HANDSHAKE_FAILURE, @@ -265,7 +307,7 @@ void Client::process_handshake_msg(const Handshake_State* active_state, if(state.server_hello()->compression_method() != 0) { - throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Server replied with non-null compression method"); } @@ -283,10 +325,10 @@ void Client::process_handshake_msg(const Handshake_State* active_state, // Server sent us back an extension we did not send! std::ostringstream msg; - msg << "Server replied with " << diff.size() << " unsupported extensions:"; + msg << "Server replied with unsupported extensions:"; for(auto&& d : diff) msg << " " << static_cast<int>(d); - throw TLS_Exception(Alert::HANDSHAKE_FAILURE, msg.str()); + throw TLS_Exception(Alert::UNSUPPORTED_EXTENSION, msg.str()); } if(uint16_t srtp = state.server_hello()->srtp_profile()) @@ -319,10 +361,26 @@ void Client::process_handshake_msg(const Handshake_State* active_state, throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Server resumed session but with wrong version"); - state.compute_session_keys(state.resume_master_secret); + if(state.server_hello()->supports_extended_master_secret() && + !state.resumed_session->supports_extended_master_secret()) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server resumed session but added extended master secret"); + } + + if(!state.server_hello()->supports_extended_master_secret() && + state.resumed_session->supports_extended_master_secret()) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server resumed session and removed extended master secret"); + } + + state.compute_session_keys(state.resume_master_secret()); if(state.server_hello()->supports_session_ticket()) + { state.set_expected_next(NEW_SESSION_TICKET); + } else { state.set_expected_next(HANDSHAKE_CCS); @@ -332,8 +390,25 @@ void Client::process_handshake_msg(const Handshake_State* active_state, { // new session - state.resume_master_secret.clear(); - state.resume_peer_certs.clear(); + if(active_state) + { + // Here we are testing things that should not change during a renegotation, + // even if the server creates a new session. Howerver they might change + // in a resumption scenario. + + if(active_state->version() != state.server_hello()->version()) + throw TLS_Exception(Alert::PROTOCOL_VERSION, + "Server changed version after renegotiation"); + + if(state.server_hello()->supports_extended_master_secret() != + active_state->server_hello()->supports_extended_master_secret()) + { + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + "Server changed its mind about extended master secret"); + } + } + + state.resumed_session.reset(); // non-null if we were attempting a resumption if(state.client_hello()->version().is_datagram_protocol() != state.server_hello()->version().is_datagram_protocol()) @@ -372,7 +447,7 @@ void Client::process_handshake_msg(const Handshake_State* active_state, depending on if it has an identity hint for us. (EC)DHE_PSK always sends a server key exchange for the - DH exchange portion. + DH exchange portion, and is covered by block below */ state.set_expected_next(SERVER_KEX); @@ -406,7 +481,17 @@ void Client::process_handshake_msg(const Handshake_State* active_state, in case an OCSP response was also available */ - std::unique_ptr<Public_Key> peer_key(server_certs[0].subject_public_key()); + X509_Certificate server_cert = server_certs[0]; + + if(active_state && active_state->server_certs()) + { + X509_Certificate current_cert = active_state->server_certs()->cert_chain().at(0); + + if(current_cert != server_cert) + throw TLS_Exception(Alert::BAD_CERTIFICATE, "Server certificate changed during renegotiation"); + } + + std::unique_ptr<Public_Key> peer_key(server_cert.subject_public_key()); const std::string expected_key_type = state.ciphersuite().signature_used() ? state.ciphersuite().sig_algo() : "RSA"; @@ -444,9 +529,13 @@ void Client::process_handshake_msg(const Handshake_State* active_state, m_info.hostname(), policy()); } + catch(TLS_Exception& e) + { + throw; + } catch(std::exception& e) { - throw TLS_Exception(Alert::BAD_CERTIFICATE, e.what()); + throw TLS_Exception(Alert::INTERNAL_ERROR, e.what()); } } } @@ -466,7 +555,8 @@ void Client::process_handshake_msg(const Handshake_State* active_state, } else if(type == SERVER_KEX) { - state.set_expected_next(CERTIFICATE_REQUEST); // optional + if(state.ciphersuite().psk_ciphersuite() == false) + state.set_expected_next(CERTIFICATE_REQUEST); // optional state.set_expected_next(SERVER_HELLO_DONE); state.server_kex( @@ -505,7 +595,15 @@ void Client::process_handshake_msg(const Handshake_State* active_state, std::vector<std::shared_ptr<const OCSP::Response>> ocsp; if(state.server_cert_status() != nullptr) - ocsp.push_back(state.server_cert_status()->response()); + { + try { + ocsp.push_back(std::make_shared<OCSP::Response>(state.server_cert_status()->response())); + } + catch(Decoding_Error&) + { + // ignore it here because it might be our fault + } + } callbacks().tls_verify_cert_chain(state.server_certs()->cert_chain(), ocsp, @@ -514,9 +612,13 @@ void Client::process_handshake_msg(const Handshake_State* active_state, m_info.hostname(), policy()); } + catch(TLS_Exception& e) + { + throw; + } catch(std::exception& e) { - throw TLS_Exception(Alert::BAD_CERTIFICATE, e.what()); + throw TLS_Exception(Alert::INTERNAL_ERROR, e.what()); } } diff --git a/src/lib/tls/tls_client.h b/src/lib/tls/tls_client.h index 63c26b9cd..005370e78 100644 --- a/src/lib/tls/tls_client.h +++ b/src/lib/tls/tls_client.h @@ -132,7 +132,7 @@ class BOTAN_PUBLIC_API(2,0) Client final : public Channel /** * @return network protocol as advertised by the TLS server, if server sent the ALPN extension */ - const std::string& application_protocol() const { return m_application_protocol; } + std::string application_protocol() const override { return m_application_protocol; } private: void init(const Protocol_Version& protocol_version, const std::vector<std::string>& next_protocols); diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp index c5e6f2831..a673f867b 100644 --- a/src/lib/tls/tls_extensions.cpp +++ b/src/lib/tls/tls_extensions.cpp @@ -16,8 +16,10 @@ namespace TLS { namespace { -Extension* make_extension(TLS_Data_Reader& reader, uint16_t code, uint16_t size) +Extension* make_extension(TLS_Data_Reader& reader, uint16_t code, uint16_t size, Connection_Side side) { + BOTAN_UNUSED(side); + switch(code) { case TLSEXT_SERVER_NAME_INDICATION: @@ -65,7 +67,7 @@ Extension* make_extension(TLS_Data_Reader& reader, uint16_t code, uint16_t size) } -void Extensions::deserialize(TLS_Data_Reader& reader) +void Extensions::deserialize(TLS_Data_Reader& reader, Connection_Side side) { if(reader.has_remaining()) { @@ -79,9 +81,14 @@ void Extensions::deserialize(TLS_Data_Reader& reader) const uint16_t extension_code = reader.get_uint16_t(); const uint16_t extension_size = reader.get_uint16_t(); - Extension* extn = make_extension(reader, - extension_code, - extension_size); + const auto type = static_cast<Handshake_Extension_Type>(extension_code); + + if(m_extensions.find(type) != m_extensions.end()) + throw TLS_Exception(TLS::Alert::DECODE_ERROR, + "Peer sent duplicated extensions"); + + Extension* extn = make_extension( + reader, extension_code, extension_size, side); this->add(extn); } @@ -259,6 +266,9 @@ Application_Layer_Protocol_Notification::Application_Layer_Protocol_Notification if(bytes_remaining < p.size() + 1) throw Decoding_Error("Bad encoding of ALPN, length field too long"); + if(p.empty()) + throw Decoding_Error("Empty ALPN protocol not allowed"); + bytes_remaining -= (p.size() + 1); m_protocols.push_back(p); diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h index 98856b951..3ecfb7c0f 100644 --- a/src/lib/tls/tls_extensions.h +++ b/src/lib/tls/tls_extensions.h @@ -1,6 +1,6 @@ /* * TLS Extensions -* (C) 2011,2012,2016,2018 Jack Lloyd +* (C) 2011,2012,2016,2018,2019 Jack Lloyd * (C) 2016 Juraj Somorovsky * (C) 2016 Matthias Gierlings * @@ -11,6 +11,7 @@ #define BOTAN_TLS_EXTENSIONS_H_ #include <botan/tls_algos.h> +#include <botan/tls_magic.h> #include <botan/secmem.h> #include <botan/x509_dn.h> #include <vector> @@ -471,7 +472,7 @@ class BOTAN_UNSTABLE_API Extensions final std::vector<uint8_t> serialize() const; - void deserialize(TLS_Data_Reader& reader); + void deserialize(TLS_Data_Reader& reader, Connection_Side side); /** * Remvoe an extension from this extensions object, if it exists. @@ -482,7 +483,10 @@ class BOTAN_UNSTABLE_API Extensions final Extensions() = default; - explicit Extensions(TLS_Data_Reader& reader) { deserialize(reader); } + Extensions(TLS_Data_Reader& reader, Connection_Side side) + { + deserialize(reader, side); + } private: Extensions(const Extensions&) = delete; diff --git a/src/lib/tls/tls_handshake_state.cpp b/src/lib/tls/tls_handshake_state.cpp index a9c7514c1..8bc603a43 100644 --- a/src/lib/tls/tls_handshake_state.cpp +++ b/src/lib/tls/tls_handshake_state.cpp @@ -138,7 +138,7 @@ uint32_t bitmask_for_handshake_type(Handshake_Type type) "Unknown TLS handshake message type " + std::to_string(type)); } -std::string handshake_mask_to_string(uint32_t mask) +std::string handshake_mask_to_string(uint32_t mask, char combiner) { const Handshake_Type types[] = { HELLO_VERIFY_REQUEST, @@ -165,7 +165,7 @@ std::string handshake_mask_to_string(uint32_t mask) if(mask & bitmask_for_handshake_type(t)) { if(!empty) - o << ","; + o << combiner; o << handshake_type_to_string(t); empty = false; } @@ -304,10 +304,9 @@ void Handshake_State::confirm_transition_to(Handshake_Type handshake_msg) const bool ok = (m_hand_expecting_mask & mask) != 0; // overlap? if(!ok) - throw Unexpected_Message("Unexpected state transition in handshake, got type " + - std::to_string(handshake_msg) + - " expected " + handshake_mask_to_string(m_hand_expecting_mask) + - " received " + handshake_mask_to_string(m_hand_received_mask)); + throw Unexpected_Message("Unexpected state transition in handshake, expected " + + handshake_mask_to_string(m_hand_expecting_mask, '|') + + " received " + handshake_mask_to_string(m_hand_received_mask, '+')); /* We don't know what to expect next, so force a call to set_expected_next; if it doesn't happen, the next transition @@ -385,18 +384,18 @@ Handshake_State::choose_sig_format(const Private_Key& key, { const std::vector<Signature_Scheme> allowed = policy.allowed_signature_schemes(); - std::vector<Signature_Scheme> schemes = + std::vector<Signature_Scheme> requested = (for_client_auth) ? cert_req()->signature_schemes() : client_hello()->signature_schemes(); - if(schemes.empty()) + if(requested.empty()) { // Implicit SHA-1 - schemes.push_back(Signature_Scheme::RSA_PKCS1_SHA1); - schemes.push_back(Signature_Scheme::ECDSA_SHA1); - schemes.push_back(Signature_Scheme::DSA_SHA1); + requested.push_back(Signature_Scheme::RSA_PKCS1_SHA1); + requested.push_back(Signature_Scheme::ECDSA_SHA1); + requested.push_back(Signature_Scheme::DSA_SHA1); } - for(Signature_Scheme scheme : schemes) + for(Signature_Scheme scheme : allowed) { if(signature_scheme_is_known(scheme) == false) { @@ -405,7 +404,7 @@ Handshake_State::choose_sig_format(const Private_Key& key, if(signature_algorithm_of_scheme(scheme) == sig_algo) { - if(std::find(allowed.begin(), allowed.end(), scheme) != allowed.end()) + if(std::find(requested.begin(), requested.end(), scheme) != requested.end()) { chosen_scheme = scheme; break; @@ -528,11 +527,15 @@ Handshake_State::parse_sig_format(const Public_Key& key, for_client_auth ? cert_req()->signature_schemes() : client_hello()->signature_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); if(!supported_algos_include(supported_algos, key_type, hash_algo)) { - throw TLS_Exception(Alert::HANDSHAKE_FAILURE, + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "TLS signature extension did not allow for " + key_type + "/" + hash_algo + " signature"); } diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h index 98c46dfa8..0549a66a6 100644 --- a/src/lib/tls/tls_messages.h +++ b/src/lib/tls/tls_messages.h @@ -98,6 +98,8 @@ class BOTAN_UNSTABLE_API Client_Hello final : public Handshake_Message const std::vector<uint8_t>& session_id() const { return m_session_id; } + const std::vector<uint8_t>& compression_methods() const { return m_comp_methods; } + const std::vector<uint16_t>& ciphersuites() const { return m_suites; } bool offered_suite(uint16_t ciphersuite) const; @@ -387,7 +389,9 @@ class BOTAN_UNSTABLE_API Certificate_Status final : public Handshake_Message public: Handshake_Type type() const override { return CERTIFICATE_STATUS; } - std::shared_ptr<const OCSP::Response> response() const { return m_response; } + //std::shared_ptr<const OCSP::Response> response() const { return m_response; } + + const std::vector<uint8_t>& response() const { return m_response; } Certificate_Status(const std::vector<uint8_t>& buf); @@ -397,7 +401,7 @@ class BOTAN_UNSTABLE_API Certificate_Status final : public Handshake_Message private: std::vector<uint8_t> serialize() const override; - std::shared_ptr<const OCSP::Response> m_response; + std::vector<uint8_t> m_response; }; /** diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp index 4caaf623a..58ba73ade 100644 --- a/src/lib/tls/tls_policy.cpp +++ b/src/lib/tls/tls_policy.cpp @@ -25,6 +25,8 @@ std::vector<Signature_Scheme> Policy::allowed_signature_schemes() const for(Signature_Scheme scheme : all_signature_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)); @@ -57,7 +59,7 @@ std::vector<std::string> Policy::allowed_ciphers() const //"AES-128", //"Camellia-256", //"Camellia-128", - //"SEED" + //"SEED", //"3DES", }; } @@ -292,19 +294,19 @@ Protocol_Version Policy::latest_supported_version(bool datagram) const { if(datagram) { - if(allow_dtls12()) + if(acceptable_protocol_version(Protocol_Version::DTLS_V12)) return Protocol_Version::DTLS_V12; - if(allow_dtls10()) + if(acceptable_protocol_version(Protocol_Version::DTLS_V10)) return Protocol_Version::DTLS_V10; throw Invalid_State("Policy forbids all available DTLS version"); } else { - if(allow_tls12()) + if(acceptable_protocol_version(Protocol_Version::TLS_V12)) return Protocol_Version::TLS_V12; - if(allow_tls11()) + if(acceptable_protocol_version(Protocol_Version::TLS_V11)) return Protocol_Version::TLS_V11; - if(allow_tls10()) + if(acceptable_protocol_version(Protocol_Version::TLS_V10)) return Protocol_Version::TLS_V10; throw Invalid_State("Policy forbids all available TLS version"); } @@ -329,6 +331,13 @@ 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::support_cert_status_message() const { return true; } +bool Policy::allow_resumption_for_renegotiation() const { return true; } +bool Policy::only_resume_with_exact_version() const { return true; } +bool Policy::require_client_certificate_authentication() const { return false; } +bool Policy::request_client_certificate_authentication() const { return require_client_certificate_authentication(); } +bool Policy::abort_connection_on_undesired_renegotiation() const { return false; } + +size_t Policy::maximum_certificate_chain_size() const { return 0; } // 1 second initial timeout, 60 second max - see RFC 6347 sec 4.2.4.1 size_t Policy::dtls_initial_timeout() const { return 1*1000; } @@ -431,7 +440,11 @@ std::vector<uint16_t> Policy::ciphersuite_list(Protocol_Version version, for(auto&& suite : Ciphersuite::all_known_ciphersuites()) { // Can we use it? - if(suite.valid() == false) + if(!suite.valid()) + continue; + + // Can we use it in this version? + if(!suite.usable_in_version(version)) continue; // Is it acceptable to the policy? @@ -442,17 +455,6 @@ std::vector<uint16_t> Policy::ciphersuite_list(Protocol_Version version, if(!have_srp && suite.kex_method() == Kex_Algo::SRP_SHA) continue; - if(!version.supports_aead_modes()) - { - // Are we doing AEAD in a non-AEAD version? - if(suite.mac_algo() == "AEAD") - continue; - - // Older (v1.0/v1.1) versions also do not support any hash but SHA-1 - if(suite.mac_algo() != "SHA-1") - continue; - } - if(!value_exists(kex, suite.kex_algo())) continue; // unsupported key exchange diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h index 7b00b2e01..3a5be83d9 100644 --- a/src/lib/tls/tls_policy.h +++ b/src/lib/tls/tls_policy.h @@ -125,6 +125,14 @@ class BOTAN_PUBLIC_API(2,0) Policy virtual bool allow_server_initiated_renegotiation() const; /** + * If true, a request to renegotiate will close the connection with + * a fatal alert. Otherwise, a warning alert is sent. + */ + virtual bool abort_connection_on_undesired_renegotiation() const; + + virtual bool only_resume_with_exact_version() const; + + /** * Allow TLS v1.0 */ virtual bool allow_tls10() const; @@ -271,6 +279,19 @@ class BOTAN_PUBLIC_API(2,0) Policy virtual bool support_cert_status_message() const; /** + * Indicate if client certificate authentication is required. + * If true, then a cert will be requested and if the client does + * not send a certificate the connection will be closed. + */ + virtual bool require_client_certificate_authentication() const; + + /** + * Indicate if client certificate authentication is requested. + * If true, then a cert will be requested. + */ + virtual bool request_client_certificate_authentication() const; + + /** * Return allowed ciphersuites, in order of preference */ virtual std::vector<uint16_t> ciphersuite_list(Protocol_Version version, @@ -292,6 +313,14 @@ class BOTAN_PUBLIC_API(2,0) Policy virtual size_t dtls_maximum_timeout() const; /** + * @return the maximum size of the certificate chain, in bytes. + * Return 0 to disable this and accept any size. + */ + virtual size_t maximum_certificate_chain_size() const; + + virtual bool allow_resumption_for_renegotiation() const; + + /** * Convert this policy to a printable format. * @param o stream to be printed to */ @@ -525,6 +554,8 @@ class BOTAN_PUBLIC_API(2,0) Text_Policy : public Policy bool support_cert_status_message() const override; + bool require_client_certificate_authentication() const override; + size_t minimum_ecdh_group_size() const override; size_t minimum_ecdsa_group_size() const override; diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp index 2b1885e45..d0c3baa2e 100644 --- a/src/lib/tls/tls_server.cpp +++ b/src/lib/tls/tls_server.cpp @@ -153,7 +153,7 @@ uint16_t choose_ciphersuite( const Policy& policy, Protocol_Version version, Credentials_Manager& creds, - const std::map<std::string, std::vector<X509_Certificate> >& cert_chains, + const std::map<std::string, std::vector<X509_Certificate>>& cert_chains, const Client_Hello& client_hello) { const bool our_choice = policy.server_uses_own_ciphersuite_preferences(); @@ -264,17 +264,17 @@ uint16_t choose_ciphersuite( "Can't agree on a ciphersuite with client"); } -std::map<std::string, std::vector<X509_Certificate> > +std::map<std::string, std::vector<X509_Certificate>> get_server_certs(const std::string& hostname, Credentials_Manager& creds) { - const char* cert_types[] = { "RSA", "DSA", "ECDSA", nullptr }; + const char* cert_types[] = { "RSA", "ECDSA", "DSA", nullptr }; - std::map<std::string, std::vector<X509_Certificate> > cert_chains; + std::map<std::string, std::vector<X509_Certificate>> cert_chains; for(size_t i = 0; cert_types[i]; ++i) { - std::vector<X509_Certificate> certs = + const std::vector<X509_Certificate> certs = creds.cert_chain_single_type(cert_types[i], "tls-server", hostname); if(!certs.empty()) @@ -321,7 +321,6 @@ Server::Server(output_fn output, { } - Server::Server(output_fn output, data_cb got_data_cb, alert_cb recv_alert_cb, @@ -372,53 +371,29 @@ void Server::initiate_handshake(Handshake_State& state, Hello_Request hello_req(state.handshake_io()); } -/* -* Process a CLIENT HELLO Message -*/ -void Server::process_client_hello_msg(const Handshake_State* active_state, - Server_Handshake_State& pending_state, - const std::vector<uint8_t>& contents) - { - const bool initial_handshake = !active_state; +namespace { - if(initial_handshake == false && policy().allow_client_initiated_renegotiation() == false) - { - send_warning_alert(Alert::NO_RENEGOTIATION); - return; - } +Protocol_Version select_version(const Botan::TLS::Policy& policy, + Protocol_Version client_offer, + Protocol_Version active_version, + bool is_fallback) + { + const Protocol_Version latest_supported = + policy.latest_supported_version(client_offer.is_datagram_protocol()); - if(!policy().allow_insecure_renegotiation() && - !(initial_handshake || secure_renegotiation_supported())) + if(is_fallback) { - send_warning_alert(Alert::NO_RENEGOTIATION); - return; + if(latest_supported > client_offer) + throw TLS_Exception(Alert::INAPPROPRIATE_FALLBACK, + "Client signalled fallback SCSV, possible attack"); } - pending_state.client_hello(new Client_Hello(contents)); - const Protocol_Version client_version = pending_state.client_hello()->version(); - - if(client_version.major_version() < 3) - throw TLS_Exception(Alert::PROTOCOL_VERSION, "Client offered version with major version under 3"); - if(client_version.major_version() == 3 && client_version.minor_version() == 0) - throw TLS_Exception(Alert::PROTOCOL_VERSION, "SSLv3 is not supported"); + const bool initial_handshake = (active_version.valid() == false); - Protocol_Version negotiated_version; - - const Protocol_Version latest_supported = - policy().latest_supported_version(client_version.is_datagram_protocol()); + const bool client_offer_acceptable = + client_offer.known_version() && policy.acceptable_protocol_version(client_offer); - if((initial_handshake && client_version.known_version()) || - (!initial_handshake && client_version == active_state->version())) - { - /* - Common cases: new client hello with some known version, or a - renegotiation using the same version as previously - negotiated. - */ - - negotiated_version = client_version; - } - else if(!initial_handshake && (client_version != active_state->version())) + if(!initial_handshake) { /* * If this is a renegotiation, and the client has offered a @@ -427,40 +402,80 @@ void Server::process_client_hello_msg(const Handshake_State* active_state, * client is offering a version earlier than what it initially * negotiated, reject as a probable attack. */ - if(active_state->version() > client_version) + if(active_version > client_offer) { throw TLS_Exception(Alert::PROTOCOL_VERSION, "Client negotiated " + - active_state->version().to_string() + + active_version.to_string() + " then renegotiated with " + - client_version.to_string()); + client_offer.to_string()); } else - negotiated_version = active_state->version(); + { + return active_version; + } } - else + else if(client_offer_acceptable) + { + return client_offer; + } + else if(!client_offer.known_version() || client_offer > latest_supported) { /* - New negotiation using a version we don't know. Offer them the - best we currently know and support + The client offered some version newer than the latest we + support. Offer them the best we know. */ - negotiated_version = latest_supported; + return latest_supported; } - - if(!policy().acceptable_protocol_version(negotiated_version)) + else { throw TLS_Exception(Alert::PROTOCOL_VERSION, - "Client version " + negotiated_version.to_string() + + "Client version " + client_offer.to_string() + " is unacceptable by policy"); } + } + +} - if(pending_state.client_hello()->sent_fallback_scsv()) +/* +* Process a CLIENT HELLO Message +*/ +void Server::process_client_hello_msg(const Handshake_State* active_state, + Server_Handshake_State& pending_state, + const std::vector<uint8_t>& contents) + { + const bool initial_handshake = !active_state; + + if(initial_handshake == false && policy().allow_client_initiated_renegotiation() == false) { - if(latest_supported > client_version) - throw TLS_Exception(Alert::INAPPROPRIATE_FALLBACK, - "Client signalled fallback SCSV, possible attack"); + send_warning_alert(Alert::NO_RENEGOTIATION); + return; } + if(!policy().allow_insecure_renegotiation() && + !(initial_handshake || secure_renegotiation_supported())) + { + send_warning_alert(Alert::NO_RENEGOTIATION); + return; + } + + pending_state.client_hello(new Client_Hello(contents)); + const Protocol_Version client_offer = pending_state.client_hello()->version(); + + if(client_offer.major_version() < 3) + throw TLS_Exception(Alert::PROTOCOL_VERSION, "Client offered version with major version under 3"); + if(client_offer.major_version() == 3 && client_offer.minor_version() == 0) + throw TLS_Exception(Alert::PROTOCOL_VERSION, "SSLv3 is not supported"); + + const Protocol_Version negotiated_version = + select_version(policy(), client_offer, + active_state ? active_state->version() : Protocol_Version(), + pending_state.client_hello()->sent_fallback_scsv()); + + const auto compression_methods = pending_state.client_hello()->compression_methods(); + if(!value_exists(compression_methods, uint8_t(0))) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Client did not offer NULL compression"); + secure_renegotiation_check(pending_state.client_hello()); pending_state.set_version(negotiated_version); @@ -511,6 +526,11 @@ void Server::process_certificate_msg(Server_Handshake_State& pending_state, const std::vector<uint8_t>& contents) { pending_state.client_certs(new Certificate(contents, policy())); + + // CERTIFICATE_REQUIRED would make more sense but BoGo expects handshake failure alert + if(pending_state.client_certs()->empty() && policy().require_client_certificate_authentication()) + throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Policy requires client send a certificate, but it did not"); + pending_state.set_expected_next(CLIENT_KEX); } @@ -767,7 +787,7 @@ void Server::session_resume(Server_Handshake_State& pending_state, void Server::session_create(Server_Handshake_State& pending_state, bool have_session_ticket_key) { - std::map<std::string, std::vector<X509_Certificate> > cert_chains; + std::map<std::string, std::vector<X509_Certificate>> cert_chains; const std::string sni_hostname = pending_state.client_hello()->sni_hostname(); @@ -782,10 +802,10 @@ void Server::session_create(Server_Handshake_State& pending_state, * find any certs for the requested name but did find at * least one cert to use in general. That avoids sending an * unrecognized_name when a server is configured for purely - * anonymous operation. + * anonymous/PSK operation. */ if(!cert_chains.empty()) - send_alert(Alert(Alert::UNRECOGNIZED_NAME)); + send_warning_alert(Alert::UNRECOGNIZED_NAME); } const uint16_t ciphersuite = choose_ciphersuite(policy(), pending_state.version(), @@ -857,7 +877,11 @@ void Server::session_create(Server_Handshake_State& pending_state, client_auth_CAs.insert(client_auth_CAs.end(), subjects.begin(), subjects.end()); } - if(!client_auth_CAs.empty() && pending_state.ciphersuite().signature_used()) + const bool request_cert = + (client_auth_CAs.empty() == false) || + policy().request_client_certificate_authentication(); + + if(request_cert && pending_state.ciphersuite().signature_used()) { pending_state.cert_req( new Certificate_Req(pending_state.handshake_io(), diff --git a/src/lib/tls/tls_server.h b/src/lib/tls/tls_server.h index c55c3f93d..e6536934a 100644 --- a/src/lib/tls/tls_server.h +++ b/src/lib/tls/tls_server.h @@ -110,7 +110,7 @@ class BOTAN_PUBLIC_API(2,0) Server final : public Channel * tied to the session and a later renegotiation of the same * session can choose a new protocol. */ - std::string application_protocol() const { return m_next_protocol; } + std::string application_protocol() const override { return m_next_protocol; } private: std::vector<X509_Certificate> diff --git a/src/lib/tls/tls_text_policy.cpp b/src/lib/tls/tls_text_policy.cpp index 829899fbc..1b83f0dbd 100644 --- a/src/lib/tls/tls_text_policy.cpp +++ b/src/lib/tls/tls_text_policy.cpp @@ -80,6 +80,11 @@ bool Text_Policy::include_time_in_hello_random() const return get_bool("include_time_in_hello_random", Policy::include_time_in_hello_random()); } +bool Text_Policy::require_client_certificate_authentication() const + { + return get_bool("require_client_certificate_authentication", Policy::require_client_certificate_authentication()); + } + bool Text_Policy::allow_client_initiated_renegotiation() const { return get_bool("allow_client_initiated_renegotiation", Policy::allow_client_initiated_renegotiation()); diff --git a/src/lib/tls/tls_version.h b/src/lib/tls/tls_version.h index 569030085..13be64316 100644 --- a/src/lib/tls/tls_version.h +++ b/src/lib/tls/tls_version.h @@ -82,6 +82,11 @@ class BOTAN_PUBLIC_API(2,0) Protocol_Version final uint8_t minor_version() const { return get_byte(1, m_version); } /** + * @return the version code + */ + uint16_t version_code() const { return m_version; } + + /** * @return human-readable description of this version */ std::string to_string() const; diff --git a/src/tests/data/tls/alert.vec b/src/tests/data/tls/alert.vec index cd5058212..e58b2cd1e 100644 --- a/src/tests/data/tls/alert.vec +++ b/src/tests/data/tls/alert.vec @@ -14,11 +14,11 @@ Exception = Buffer = 0030 Protocol = 0303 -Exception = Alert: Bad code for alert level +Exception = Bad code for TLS alert level Buffer = 02 -Exception = Alert: Bad size 1 for alert message +Exception = Bad size (1) for TLS alert message Buffer = 020101 -Exception = Alert: Bad size 3 for alert message +Exception = Bad size (3) for TLS alert message diff --git a/src/tests/test_tls_messages.cpp b/src/tests/test_tls_messages.cpp index 364fdc0bf..a79f5b42e 100644 --- a/src/tests/test_tls_messages.cpp +++ b/src/tests/test_tls_messages.cpp @@ -123,17 +123,15 @@ class TLS_Message_Parsing_Test final : public Text_Based_Test else if(algo == "cert_status") { Botan::TLS::Certificate_Status message(buffer); - std::shared_ptr<const Botan::OCSP::Response> resp = message.response(); - if(result.confirm("Decoded response", resp != nullptr)) - { - const std::vector<std::string> CNs = resp->signer_name().get_attribute("CN"); + Botan::OCSP::Response resp(message.response()); + + const std::vector<std::string> CNs = resp.signer_name().get_attribute("CN"); - // This is not requird by OCSP protocol, we are just using it as a test here - if(result.test_eq("OCSP response has signer name", CNs.size(), 1)) - { - result.test_eq("Expected name", CNs[0], expected_name); - } + // This is not requird by OCSP protocol, we are just using it as a test here + if(result.test_eq("OCSP response has signer name", CNs.size(), 1)) + { + result.test_eq("Expected name", CNs[0], expected_name); } } else |