diff options
author | Jack Lloyd <[email protected]> | 2015-12-19 15:36:40 -0500 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2015-12-19 15:36:40 -0500 |
commit | b48e6fb097c62bb246629ee7a182c57e497e4130 (patch) | |
tree | 0cb8ea2d05a89f5e90467f323ae56268d4d3480e /src/cli/tls_client.cpp | |
parent | d774a9edc46ffcebb28205a678058f083ea75c28 (diff) |
CLI rewrite
The command line tools' origin as a collection of examples and test
programs glued together led to some unfortunate problems; lots of
hardcoded values, missing parameters, and obsolete crypto.
Adds a small library for writing command line programs of the sort
needed here (cli.h), which cuts the length of many of the commands in
half and makes commands more pleasant to write and extend.
Generalizes a lot of the commands also, eg previously only
signing/verification with DSA/SHA-1 was included!
Removes the fuzzer entry point since that's fairly useless outside of
an instrumented build.
Removes the in-library API for benchmarking.
Diffstat (limited to 'src/cli/tls_client.cpp')
-rw-r--r-- | src/cli/tls_client.cpp | 400 |
1 files changed, 199 insertions, 201 deletions
diff --git a/src/cli/tls_client.cpp b/src/cli/tls_client.cpp index 7f74e1a37..c13e9019e 100644 --- a/src/cli/tls_client.cpp +++ b/src/cli/tls_client.cpp @@ -1,19 +1,19 @@ /* -* (C) 2014 Jack Lloyd +* (C) 2014,2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ -#include "apps.h" +#include "cli.h" #if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_SOCKETS) #include <botan/tls_client.h> -#include <botan/pkcs8.h> +#include <botan/auto_rng.h> #include <botan/hex.h> #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) - #include <botan/tls_session_manager_sqlite.h> +#include <botan/tls_session_manager_sqlite.h> #endif #include <string> @@ -34,260 +34,258 @@ #include "credentials.h" -using namespace Botan; +namespace Botan_CLI { -using namespace std::placeholders; +class TLS_Client : public Command + { + public: + TLS_Client() : Command("tls_client host --port=443 --type=tcp " + "--session-db= --session-db-pass= --next-protocols=") {} -namespace { + void go() override + { + Botan::AutoSeeded_RNG rng; + Botan::TLS::Policy policy; // TODO read from a file -int connect_to_host(const std::string& host, u16bit port, bool tcp) - { - hostent* host_addr = ::gethostbyname(host.c_str()); + // TODO client cert auth - if(!host_addr) - throw std::runtime_error("gethostbyname failed for " + host); + std::unique_ptr<Botan::TLS::Session_Manager> session_mgr; - if(host_addr->h_addrtype != AF_INET) // FIXME - throw std::runtime_error(host + " has IPv6 address, not supported"); +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + const std::string sessions_passphrase = get_arg("session-db-pass"); + const std::string sessions_db = get_arg("session-db"); - int type = tcp ? SOCK_STREAM : SOCK_DGRAM; + if(!sessions_db.empty()) + { + session_mgr.reset(new Botan::TLS::Session_Manager_SQLite(sessions_passphrase, rng, sessions_db)); + } +#endif + if(!session_mgr) + { + session_mgr.reset(new Botan::TLS::Session_Manager_In_Memory(rng)); + } - int fd = ::socket(PF_INET, type, 0); - if(fd == -1) - throw std::runtime_error("Unable to acquire socket"); + Basic_Credentials_Manager creds; - sockaddr_in socket_info; - ::memset(&socket_info, 0, sizeof(socket_info)); - socket_info.sin_family = AF_INET; - socket_info.sin_port = htons(port); + const std::string host = get_arg("host"); + const uint16_t port = get_arg_sz("port"); + const std::string transport = get_arg("type"); - ::memcpy(&socket_info.sin_addr, - host_addr->h_addr, - host_addr->h_length); + if(transport != "tcp" && transport != "udp") + throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS"); - socket_info.sin_addr = *reinterpret_cast<struct in_addr*>(host_addr->h_addr); // FIXME + const bool use_tcp = (transport == "tcp"); - if(::connect(fd, (sockaddr*)&socket_info, sizeof(struct sockaddr)) != 0) - { - ::close(fd); - throw std::runtime_error("connect failed"); - } + const std::vector<std::string> protocols_to_offer = Botan::split_on("next-protocols", ','); - return fd; - } + int sockfd = connect_to_host(host, port, use_tcp); -bool handshake_complete(const TLS::Session& session) - { - std::cout << "Handshake complete, " << session.version().to_string() - << " using " << session.ciphersuite().to_string() << std::endl; + using namespace std::placeholders; - if(!session.session_id().empty()) - std::cout << "Session ID " << hex_encode(session.session_id()) << std::endl; + auto socket_write = + use_tcp ? + std::bind(stream_socket_write, sockfd, _1, _2) : + std::bind(dgram_socket_write, sockfd, _1, _2); - if(!session.session_ticket().empty()) - std::cout << "Session ticket " << hex_encode(session.session_ticket()) << std::endl; + auto version = policy.latest_supported_version(!use_tcp); - return true; - } + Botan::TLS::Client client(socket_write, + std::bind(&TLS_Client::process_data, this, _1, _2), + std::bind(&TLS_Client::alert_received, this, _1, _2, _3), + std::bind(&TLS_Client::handshake_complete, this, _1), + *session_mgr, + creds, + policy, + rng, + Botan::TLS::Server_Information(host, port), + version, + protocols_to_offer); -void dgram_socket_write(int sockfd, const byte buf[], size_t length) - { - int r = send(sockfd, buf, length, MSG_NOSIGNAL); + bool first_active = true; - if(r == -1) - throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); - } + while(!client.is_closed()) + { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sockfd, &readfds); -void stream_socket_write(int sockfd, const byte buf[], size_t length) - { - size_t offset = 0; + 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; + } + } - while(length) - { - ssize_t sent = ::send(sockfd, (const char*)buf + offset, - length, MSG_NOSIGNAL); + struct timeval timeout = { 1, 0 }; - if(sent == -1) - { - if(errno == EINTR) - sent = 0; - else - throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); - } + ::select(sockfd + 1, &readfds, nullptr, nullptr, &timeout); - offset += sent; - length -= sent; - } - } + if(FD_ISSET(sockfd, &readfds)) + { + uint8_t buf[4*1024] = { 0 }; -bool got_alert = false; + ssize_t got = ::read(sockfd, buf, sizeof(buf)); -void alert_received(TLS::Alert alert, const byte [], size_t ) - { - std::cout << "Alert: " << alert.type_string() << std::endl; - got_alert = true; - } + if(got == 0) + { + output() << "EOF on socket\n"; + break; + } + else if(got == -1) + { + output() << "Socket error: " << errno << " " << strerror(errno) << "\n"; + continue; + } -void process_data(const byte buf[], size_t buf_size) - { - for(size_t i = 0; i != buf_size; ++i) - std::cout << buf[i]; - } + client.received_data(buf, got); + } -int tls_client(const std::vector<std::string> &args) - { - if(args.size() != 2 && args.size() != 3 && args.size() != 4) - { - std::cout << "Usage " << args[0] << " host [port] [udp|tcp]" << std::endl; - return 1; - } + if(FD_ISSET(STDIN_FILENO, &readfds)) + { + uint8_t buf[1024] = { 0 }; + ssize_t got = read(STDIN_FILENO, buf, sizeof(buf)); - try - { - AutoSeeded_RNG rng; - TLS::Policy policy; + if(got == 0) + { + output() << "EOF on stdin\n"; + client.close(); + break; + } + else if(got == -1) + { + output() << "Stdin error: " << errno << " " << strerror(errno) << "\n"; + continue; + } -#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) - const std::string passphrase = "correct horse battery staple"; - const std::string sessions_db = "sessions.db"; - - TLS::Session_Manager_SQLite session_manager(passphrase, - rng, - sessions_db); -#else - TLS::Session_Manager_In_Memory session_manager(rng); -#endif + 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 if(buf[0] == 'H') + client.heartbeat(&buf[1], got-1); + else + client.send(buf, got); + } - Basic_Credentials_Manager creds; + if(client.timeout_check()) + { + output() << "Timeout detected\n"; + } + } - const std::string host = args[1]; - const u32bit port = args.size() >= 3 ? Botan::to_u32bit(args[2]) : 443; - const std::string transport = args.size() >= 4 ? args[3] : "tcp"; + ::close(sockfd); + } - const bool use_tcp = (transport == "tcp"); + private: + int connect_to_host(const std::string& host, uint16_t port, bool tcp) + { + hostent* host_addr = ::gethostbyname(host.c_str()); - const std::vector<std::string> protocols_to_offer = { "test/9.9", "http/1.1", "echo/9.1" }; + if(!host_addr) + throw std::runtime_error("gethostbyname failed for " + host); - int sockfd = connect_to_host(host, port, use_tcp); + if(host_addr->h_addrtype != AF_INET) // FIXME + throw std::runtime_error(host + " has IPv6 address, not supported"); - auto socket_write = - use_tcp ? - std::bind(stream_socket_write, sockfd, _1, _2) : - std::bind(dgram_socket_write, sockfd, _1, _2); + int type = tcp ? SOCK_STREAM : SOCK_DGRAM; - auto version = policy.latest_supported_version(!use_tcp); + int fd = ::socket(PF_INET, type, 0); + if(fd == -1) + throw std::runtime_error("Unable to acquire socket"); - TLS::Client client(socket_write, - process_data, - alert_received, - handshake_complete, - session_manager, - creds, - policy, - rng, - TLS::Server_Information(host, port), - version, - protocols_to_offer); + sockaddr_in socket_info; + ::memset(&socket_info, 0, sizeof(socket_info)); + socket_info.sin_family = AF_INET; + socket_info.sin_port = htons(port); - bool first_active = true; + ::memcpy(&socket_info.sin_addr, + host_addr->h_addr, + host_addr->h_length); - while(!client.is_closed()) - { - fd_set readfds; - FD_ZERO(&readfds); - FD_SET(sockfd, &readfds); + socket_info.sin_addr = *reinterpret_cast<struct in_addr*>(host_addr->h_addr); // FIXME - if(client.is_active()) + if(::connect(fd, (sockaddr*)&socket_info, sizeof(struct sockaddr)) != 0) { - FD_SET(STDIN_FILENO, &readfds); - if(first_active && !protocols_to_offer.empty()) - { - std::string app = client.application_protocol(); - if(app != "") - std::cout << "Server choose protocol: " << client.application_protocol() << std::endl; - first_active = false; - } + ::close(fd); + throw std::runtime_error("connect failed"); } - struct timeval timeout = { 1, 0 }; + return fd; + } + + bool handshake_complete(const Botan::TLS::Session& session) + { + output() << "Handshake complete, " << session.version().to_string() + << " using " << session.ciphersuite().to_string() << "\n"; - ::select(sockfd + 1, &readfds, nullptr, nullptr, &timeout); + if(!session.session_id().empty()) + output() << "Session ID " << Botan::hex_encode(session.session_id()) << "\n"; - if(FD_ISSET(sockfd, &readfds)) - { - byte buf[4*1024] = { 0 }; + if(!session.session_ticket().empty()) + output() << "Session ticket " << Botan::hex_encode(session.session_ticket()) << "\n"; - ssize_t got = ::read(sockfd, buf, sizeof(buf)); + return true; + } - if(got == 0) - { - std::cout << "EOF on socket" << std::endl; - break; - } - else if(got == -1) - { - std::cout << "Socket error: " << errno << " " << strerror(errno) << std::endl; - continue; - } + static void dgram_socket_write(int sockfd, const uint8_t buf[], size_t length) + { + int r = send(sockfd, buf, length, MSG_NOSIGNAL); - client.received_data(buf, got); - } + if(r == -1) + throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); + } + + static void stream_socket_write(int sockfd, const uint8_t buf[], size_t length) + { + size_t offset = 0; - if(FD_ISSET(STDIN_FILENO, &readfds)) + while(length) { - byte buf[1024] = { 0 }; - ssize_t got = read(STDIN_FILENO, buf, sizeof(buf)); + ssize_t sent = ::send(sockfd, (const char*)buf + offset, + length, MSG_NOSIGNAL); - if(got == 0) - { - std::cout << "EOF on stdin" << std::endl; - client.close(); - break; - } - else if(got == -1) + if(sent == -1) { - std::cout << "Stdin error: " << errno << " " << strerror(errno) << std::endl; - continue; + if(errno == EINTR) + sent = 0; + else + throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); } - if(got == 2 && buf[1] == '\n') - { - char cmd = buf[0]; - - if(cmd == 'R' || cmd == 'r') - { - std::cout << "Client initiated renegotiation" << std::endl; - client.renegotiate(cmd == 'R'); - } - else if(cmd == 'Q') - { - std::cout << "Client initiated close" << std::endl; - client.close(); - } - } - else if(buf[0] == 'H') - client.heartbeat(&buf[1], got-1); - else - client.send(buf, got); + offset += sent; + length -= sent; } + } - if(client.timeout_check()) - { - std::cout << "Timeout detected" << std::endl; - } + void alert_received(Botan::TLS::Alert alert, const uint8_t [], size_t ) + { + output() << "Alert: " << alert.type_string() << "\n"; + } + + void process_data(const uint8_t buf[], size_t buf_size) + { + for(size_t i = 0; i != buf_size; ++i) + output() << buf[i]; } + }; - ::close(sockfd); - } - catch(std::exception& e) - { - std::cout << "Exception: " << e.what() << std::endl; - return 1; - } - return 0; - } - -REGISTER_APP(tls_client); +BOTAN_REGISTER_COMMAND(TLS_Client); } |