summaryrefslogtreecommitdiffstats
path: root/comm/third_party/botan/src/cli/tls_client.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/third_party/botan/src/cli/tls_client.cpp')
-rw-r--r--comm/third_party/botan/src/cli/tls_client.cpp436
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