diff options
Diffstat (limited to 'comm/third_party/botan/src/cli/tls_client.cpp')
-rw-r--r-- | comm/third_party/botan/src/cli/tls_client.cpp | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/comm/third_party/botan/src/cli/tls_client.cpp b/comm/third_party/botan/src/cli/tls_client.cpp new file mode 100644 index 0000000000..9541f8fbc4 --- /dev/null +++ b/comm/third_party/botan/src/cli/tls_client.cpp @@ -0,0 +1,436 @@ +/* +* (C) 2014,2015 Jack Lloyd +* 2016 Matthias Gierlings +* 2017 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_TARGET_OS_HAS_SOCKETS) + +#include <botan/tls_client.h> +#include <botan/tls_policy.h> +#include <botan/x509path.h> +#include <botan/ocsp.h> +#include <botan/hex.h> +#include <botan/parsing.h> +#include <fstream> + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + #include <botan/tls_session_manager_sqlite.h> +#endif + +#include <string> +#include <memory> + +#include "socket_utils.h" +#include "tls_helpers.h" + +namespace Botan_CLI { + +class CLI_Policy final : public Botan::TLS::Policy + { + public: + + CLI_Policy(Botan::TLS::Protocol_Version req_version) : m_version(req_version) {} + + std::vector<std::string> allowed_ciphers() const override + { + // Allow CBC mode only in versions which don't support AEADs + if(m_version.supports_aead_modes() == false) + { + return { "AES-256", "AES-128" }; + } + + return Botan::TLS::Policy::allowed_ciphers(); + } + + bool allow_tls10() const override { return m_version == Botan::TLS::Protocol_Version::TLS_V10; } + bool allow_tls11() const override { return m_version == Botan::TLS::Protocol_Version::TLS_V11; } + bool allow_tls12() const override { return m_version == Botan::TLS::Protocol_Version::TLS_V12; } + + private: + Botan::TLS::Protocol_Version m_version; + }; + +class TLS_Client final : public Command, public Botan::TLS::Callbacks + { + public: + TLS_Client() + : Command("tls_client host --port=443 --print-certs --policy=default " + "--tls1.0 --tls1.1 --tls1.2 " + "--skip-system-cert-store --trusted-cas= " + "--session-db= --session-db-pass= --next-protocols= --type=tcp") + { + init_sockets(); + } + + ~TLS_Client() + { + stop_sockets(); + } + + std::string group() const override + { + return "tls"; + } + + std::string description() const override + { + return "Connect to a host using TLS/DTLS"; + } + + void go() override + { + // TODO client cert auth + + std::unique_ptr<Botan::TLS::Session_Manager> session_mgr; + + const std::string sessions_db = get_arg("session-db"); + const std::string host = get_arg("host"); + const uint16_t port = get_arg_u16("port"); + const std::string transport = get_arg("type"); + const std::string next_protos = get_arg("next-protocols"); + const bool use_system_cert_store = flag_set("skip-system-cert-store") == false; + const std::string trusted_CAs = get_arg("trusted-cas"); + + if(!sessions_db.empty()) + { +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass"); + session_mgr.reset(new Botan::TLS::Session_Manager_SQLite(sessions_passphrase, rng(), sessions_db)); +#else + error_output() << "Ignoring session DB file, sqlite not enabled\n"; +#endif + } + + if(!session_mgr) + { + session_mgr.reset(new Botan::TLS::Session_Manager_In_Memory(rng())); + } + + auto policy = load_tls_policy(get_arg("policy")); + + if(transport != "tcp" && transport != "udp") + { + throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS"); + } + + const bool use_tcp = (transport == "tcp"); + + const std::vector<std::string> protocols_to_offer = Botan::split_on(next_protos, ','); + + Botan::TLS::Protocol_Version version = + use_tcp ? Botan::TLS::Protocol_Version::TLS_V12 : Botan::TLS::Protocol_Version::DTLS_V12; + + if(flag_set("tls1.0")) + { + version = Botan::TLS::Protocol_Version::TLS_V10; + if(!policy) + policy.reset(new CLI_Policy(version)); + } + else if(flag_set("tls1.1")) + { + version = Botan::TLS::Protocol_Version::TLS_V11; + if(!policy) + policy.reset(new CLI_Policy(version)); + } + else if(flag_set("tls1.2")) + { + version = Botan::TLS::Protocol_Version::TLS_V12; + if(!policy) + policy.reset(new CLI_Policy(version)); + } + else if(!policy) + { + policy.reset(new Botan::TLS::Policy); + } + + if(policy->acceptable_protocol_version(version) == false) + { + throw CLI_Usage_Error("The policy specified does not allow the requested TLS version"); + } + + struct sockaddr_storage addrbuf; + std::string hostname; + if(!host.empty() && + inet_pton(AF_INET, host.c_str(), &addrbuf) != 1 && + inet_pton(AF_INET6, host.c_str(), &addrbuf) != 1) + { + hostname = host; + } + + m_sockfd = connect_to_host(host, port, use_tcp); + + Basic_Credentials_Manager creds(use_system_cert_store, trusted_CAs); + + Botan::TLS::Client client(*this, *session_mgr, creds, *policy, rng(), + Botan::TLS::Server_Information(hostname, port), + version, protocols_to_offer); + + bool first_active = true; + + while(!client.is_closed()) + { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(m_sockfd, &readfds); + + if(client.is_active()) + { + FD_SET(STDIN_FILENO, &readfds); + if(first_active && !protocols_to_offer.empty()) + { + std::string app = client.application_protocol(); + if(app != "") + { + output() << "Server choose protocol: " << client.application_protocol() << "\n"; + } + first_active = false; + } + } + + struct timeval timeout = { 1, 0 }; + + ::select(static_cast<int>(m_sockfd + 1), &readfds, nullptr, nullptr, &timeout); + + if(FD_ISSET(m_sockfd, &readfds)) + { + uint8_t buf[4 * 1024] = { 0 }; + + ssize_t got = ::read(m_sockfd, buf, sizeof(buf)); + + if(got == 0) + { + output() << "EOF on socket\n"; + break; + } + else if(got == -1) + { + output() << "Socket error: " << errno << " " << err_to_string(errno) << "\n"; + continue; + } + + client.received_data(buf, got); + } + + if(FD_ISSET(STDIN_FILENO, &readfds)) + { + uint8_t buf[1024] = { 0 }; + ssize_t got = read(STDIN_FILENO, buf, sizeof(buf)); + + if(got == 0) + { + output() << "EOF on stdin\n"; + client.close(); + break; + } + else if(got == -1) + { + output() << "Stdin error: " << errno << " " << err_to_string(errno) << "\n"; + continue; + } + + if(got == 2 && buf[1] == '\n') + { + char cmd = buf[0]; + + if(cmd == 'R' || cmd == 'r') + { + output() << "Client initiated renegotiation\n"; + client.renegotiate(cmd == 'R'); + } + else if(cmd == 'Q') + { + output() << "Client initiated close\n"; + client.close(); + } + } + else + { + client.send(buf, got); + } + } + + if(client.timeout_check()) + { + output() << "Timeout detected\n"; + } + } + + ::close(m_sockfd); + } + + private: + socket_type connect_to_host(const std::string& host, uint16_t port, bool tcp) + { + addrinfo hints; + Botan::clear_mem(&hints, 1); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = tcp ? SOCK_STREAM : SOCK_DGRAM; + addrinfo* res, *rp = nullptr; + + if(::getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res) != 0) + { + throw CLI_Error("getaddrinfo failed for " + host); + } + + socket_type fd = 0; + + for(rp = res; rp != nullptr; rp = rp->ai_next) + { + fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + + if(fd == invalid_socket()) + { + continue; + } + + if(::connect(fd, rp->ai_addr, static_cast<socklen_t>(rp->ai_addrlen)) != 0) + { + ::close(fd); + continue; + } + + break; + } + + ::freeaddrinfo(res); + + if(rp == nullptr) // no address succeeded + { + throw CLI_Error("connect failed"); + } + + return fd; + } + + void tls_verify_cert_chain( + const std::vector<Botan::X509_Certificate>& cert_chain, + const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& ocsp, + const std::vector<Botan::Certificate_Store*>& trusted_roots, + Botan::Usage_Type usage, + const std::string& hostname, + const Botan::TLS::Policy& policy) override + { + if(cert_chain.empty()) + { + throw Botan::Invalid_Argument("Certificate chain was empty"); + } + + Botan::Path_Validation_Restrictions restrictions( + policy.require_cert_revocation_info(), + policy.minimum_signature_strength()); + + auto ocsp_timeout = std::chrono::milliseconds(1000); + + Botan::Path_Validation_Result result = Botan::x509_path_validate( + cert_chain, + restrictions, + trusted_roots, + hostname, + usage, + std::chrono::system_clock::now(), + ocsp_timeout, + ocsp); + + output() << "Certificate validation status: " << result.result_string() << "\n"; + if(result.successful_validation()) + { + auto status = result.all_statuses(); + + if(status.size() > 0 && status[0].count(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD)) + { + output() << "Valid OCSP response for this server\n"; + } + } + } + + bool tls_session_established(const Botan::TLS::Session& session) override + { + output() << "Handshake complete, " << session.version().to_string() + << " using " << session.ciphersuite().to_string() << "\n"; + + if(!session.session_id().empty()) + { + output() << "Session ID " << Botan::hex_encode(session.session_id()) << "\n"; + } + + if(!session.session_ticket().empty()) + { + output() << "Session ticket " << Botan::hex_encode(session.session_ticket()) << "\n"; + } + + if(flag_set("print-certs")) + { + const std::vector<Botan::X509_Certificate>& certs = session.peer_certs(); + + for(size_t i = 0; i != certs.size(); ++i) + { + output() << "Certificate " << i + 1 << "/" << certs.size() << "\n"; + output() << certs[i].to_string(); + output() << certs[i].PEM_encode(); + } + } + + return true; + } + + static void dgram_socket_write(int sockfd, const uint8_t buf[], size_t length) + { + int r = ::send(sockfd, buf, length, MSG_NOSIGNAL); + + if(r == -1) + { + throw CLI_Error("Socket write failed errno=" + std::to_string(errno)); + } + } + + void tls_emit_data(const uint8_t buf[], size_t length) override + { + size_t offset = 0; + + while(length) + { + ssize_t sent = ::send(m_sockfd, buf + offset, length, MSG_NOSIGNAL); + + if(sent == -1) + { + if(errno == EINTR) + { + sent = 0; + } + else + { + throw CLI_Error("Socket write failed errno=" + std::to_string(errno)); + } + } + + offset += sent; + length -= sent; + } + } + + void tls_alert(Botan::TLS::Alert alert) override + { + output() << "Alert: " << alert.type_string() << "\n"; + } + + void tls_record_received(uint64_t /*seq_no*/, const uint8_t buf[], size_t buf_size) override + { + for(size_t i = 0; i != buf_size; ++i) + { + output() << buf[i]; + } + } + + socket_type m_sockfd = invalid_socket(); + }; + +BOTAN_REGISTER_COMMAND("tls_client", TLS_Client); + +} + +#endif |