diff options
Diffstat (limited to 'comm/third_party/botan/src/cli')
36 files changed, 10372 insertions, 0 deletions
diff --git a/comm/third_party/botan/src/cli/argon2.cpp b/comm/third_party/botan/src/cli/argon2.cpp new file mode 100644 index 0000000000..2b07027c33 --- /dev/null +++ b/comm/third_party/botan/src/cli/argon2.cpp @@ -0,0 +1,78 @@ +/* +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_ARGON2) + #include <botan/argon2.h> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_ARGON2) + +class Generate_Argon2 final : public Command + { + public: + Generate_Argon2() : Command("gen_argon2 --mem=65536 --p=1 --t=1 password") {} + + std::string group() const override + { + return "passhash"; + } + + std::string description() const override + { + return "Calculate Argon2 password hash"; + } + + void go() override + { + const std::string password = get_passphrase_arg("Passphrase to hash", "password"); + const size_t M = get_arg_sz("mem"); + const size_t p = get_arg_sz("p"); + const size_t t = get_arg_sz("t"); + + output() << Botan::argon2_generate_pwhash(password.data(), password.size(), rng(), p, M, t) << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("gen_argon2", Generate_Argon2); + +class Check_Argon2 final : public Command + { + public: + Check_Argon2() : Command("check_argon2 password hash") {} + + std::string group() const override + { + return "passhash"; + } + + std::string description() const override + { + return "Verify Argon2 password hash"; + } + + void go() override + { + const std::string password = get_passphrase_arg("Password to check", "password"); + const std::string hash = get_arg("hash"); + + const bool ok = Botan::argon2_check_pwhash(password.data(), password.size(), hash); + + output() << "Password is " << (ok ? "valid" : "NOT valid") << std::endl; + + if(ok == false) + set_return_code(1); + } + }; + +BOTAN_REGISTER_COMMAND("check_argon2", Check_Argon2); + +#endif // argon2 + +} diff --git a/comm/third_party/botan/src/cli/argparse.h b/comm/third_party/botan/src/cli/argparse.h new file mode 100644 index 0000000000..4df4a10c83 --- /dev/null +++ b/comm/third_party/botan/src/cli/argparse.h @@ -0,0 +1,280 @@ +/* +* (C) 2015,2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CLI_ARGPARSE_H_ +#define BOTAN_CLI_ARGPARSE_H_ + +#include <string> +#include <map> +#include <set> +#include <vector> +#include <botan/parsing.h> +#include "cli_exceptions.h" + +namespace Botan_CLI { + +class Argument_Parser final + { + public: + Argument_Parser(const std::string& spec, + const std::vector<std::string>& extra_flags = {}, + const std::vector<std::string>& extra_opts = {}); + + void parse_args(const std::vector<std::string>& params); + + bool flag_set(const std::string& flag) const; + + bool has_arg(const std::string& opt_name) const; + std::string get_arg(const std::string& option) const; + + std::string get_arg_or(const std::string& option, const std::string& otherwise) const; + + size_t get_arg_sz(const std::string& option) const; + + std::vector<std::string> get_arg_list(const std::string& what) const; + + private: + // set in constructor + std::vector<std::string> m_spec_args; + std::set<std::string> m_spec_flags; + std::map<std::string, std::string> m_spec_opts; + std::string m_spec_rest; + + // set in parse_args() + std::map<std::string, std::string> m_user_args; + std::set<std::string> m_user_flags; + std::vector<std::string> m_user_rest; + }; + +bool Argument_Parser::flag_set(const std::string& flag_name) const + { + return m_user_flags.count(flag_name) > 0; + } + +bool Argument_Parser::has_arg(const std::string& opt_name) const + { + return m_user_args.count(opt_name) > 0; + } + +std::string Argument_Parser::get_arg(const std::string& opt_name) const + { + auto i = m_user_args.find(opt_name); + if(i == m_user_args.end()) + { + // this shouldn't occur unless you passed the wrong thing to get_arg + throw CLI_Error("Unknown option " + opt_name + " used (program bug)"); + } + return i->second; + } + +std::string Argument_Parser::get_arg_or(const std::string& opt_name, const std::string& otherwise) const + { + auto i = m_user_args.find(opt_name); + if(i == m_user_args.end() || i->second.empty()) + { + return otherwise; + } + return i->second; + } + +size_t Argument_Parser::get_arg_sz(const std::string& opt_name) const + { + const std::string s = get_arg(opt_name); + + try + { + return static_cast<size_t>(std::stoul(s)); + } + catch(std::exception&) + { + throw CLI_Usage_Error("Invalid integer value '" + s + "' for option " + opt_name); + } + } + +std::vector<std::string> Argument_Parser::get_arg_list(const std::string& what) const + { + if(what == m_spec_rest) + return m_user_rest; + + return Botan::split_on(get_arg(what), ','); + } + +void Argument_Parser::parse_args(const std::vector<std::string>& params) + { + std::vector<std::string> args; + for(auto const& param : params) + { + if(param.find("--") == 0) + { + // option + const auto eq = param.find('='); + + if(eq == std::string::npos) + { + const std::string opt_name = param.substr(2, std::string::npos); + + if(m_spec_flags.count(opt_name) == 0) + { + if(m_spec_opts.count(opt_name)) + { + throw CLI_Usage_Error("Invalid usage of option --" + opt_name + + " without value"); + } + else + { + throw CLI_Usage_Error("Unknown flag --" + opt_name); + } + } + m_user_flags.insert(opt_name); + } + else + { + const std::string opt_name = param.substr(2, eq - 2); + const std::string opt_val = param.substr(eq + 1, std::string::npos); + + if(m_spec_opts.count(opt_name) == 0) + { + throw CLI_Usage_Error("Unknown option --" + opt_name); + } + + m_user_args.insert(std::make_pair(opt_name, opt_val)); + } + } + else + { + // argument + args.push_back(param); + } + } + + if(flag_set("help")) + return; + + if(args.size() < m_spec_args.size()) + { + // not enough arguments + throw CLI_Usage_Error("Invalid argument count, got " + + std::to_string(args.size()) + + " expected " + + std::to_string(m_spec_args.size())); + } + + bool seen_stdin_flag = false; + size_t arg_i = 0; + for(auto const& arg : m_spec_args) + { + m_user_args.insert(std::make_pair(arg, args[arg_i])); + + if(args[arg_i] == "-") + { + if(seen_stdin_flag) + { + throw CLI_Usage_Error("Cannot specify '-' (stdin) more than once"); + } + seen_stdin_flag = true; + } + + ++arg_i; + } + + if(m_spec_rest.empty()) + { + if(arg_i != args.size()) + { + throw CLI_Usage_Error("Too many arguments"); + } + } + else + { + m_user_rest.assign(args.begin() + arg_i, args.end()); + } + + // Now insert any defaults for options not supplied by the user + for(auto const& opt : m_spec_opts) + { + if(m_user_args.count(opt.first) == 0) + { + m_user_args.insert(opt); + } + } + } + +Argument_Parser::Argument_Parser(const std::string& spec, + const std::vector<std::string>& extra_flags, + const std::vector<std::string>& extra_opts) + { + class CLI_Error_Invalid_Spec final : public CLI_Error + { + public: + explicit CLI_Error_Invalid_Spec(const std::string& bad_spec) + : CLI_Error("Invalid command spec '" + bad_spec + "'") {} + }; + + const std::vector<std::string> parts = Botan::split_on(spec, ' '); + + if(parts.size() == 0) + { + throw CLI_Error_Invalid_Spec(spec); + } + + for(size_t i = 1; i != parts.size(); ++i) + { + const std::string s = parts[i]; + + if(s.empty()) // ?!? (shouldn't happen) + { + throw CLI_Error_Invalid_Spec(spec); + } + + if(s.size() > 2 && s[0] == '-' && s[1] == '-') + { + // option or flag + + auto eq = s.find('='); + + if(eq == std::string::npos) + { + m_spec_flags.insert(s.substr(2, std::string::npos)); + } + else + { + m_spec_opts.insert(std::make_pair(s.substr(2, eq - 2), s.substr(eq + 1, std::string::npos))); + } + } + else if(s[0] == '*') + { + // rest argument + if(m_spec_rest.empty() && s.size() > 2) + { + m_spec_rest = s.substr(1, std::string::npos); + } + else + { + throw CLI_Error_Invalid_Spec(spec); + } + } + else + { + // named argument + if(!m_spec_rest.empty()) // rest arg wasn't last + { + throw CLI_Error_Invalid_Spec(spec); + } + + m_spec_args.push_back(s); + } + } + + for(std::string flag : extra_flags) + m_spec_flags.insert(flag); + for(std::string opt : extra_opts) + m_spec_opts.insert(std::make_pair(opt, "")); + } + + +} + +#endif diff --git a/comm/third_party/botan/src/cli/asn1.cpp b/comm/third_party/botan/src/cli/asn1.cpp new file mode 100644 index 0000000000..32cec2c253 --- /dev/null +++ b/comm/third_party/botan/src/cli/asn1.cpp @@ -0,0 +1,89 @@ +/* +* (C) 2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_ASN1) + +#include <botan/asn1_print.h> +#include <botan/data_src.h> + +#if defined(BOTAN_HAS_PEM_CODEC) + #include <botan/pem.h> +#endif + +namespace Botan_CLI { + +class ASN1_Printer final : public Command + { + public: + ASN1_Printer() : Command("asn1print --skip-context-specific --print-limit=4096 --bin-limit=2048 --max-depth=64 --pem file") {} + + std::string group() const override + { + return "codec"; + } + + std::string description() const override + { + return "Decode and print file with ASN.1 Basic Encoding Rules (BER)"; + } + + bool first_n(const std::vector<uint8_t>& data, size_t n, uint8_t b) + { + if(data.size() < n) + return false; + + for(size_t i = 0; i != n; ++i) + if(data[i] != b) + return false; + + return true; + } + + void go() override + { + const std::string input = get_arg("file"); + const size_t print_limit = get_arg_sz("print-limit"); + const size_t bin_limit = get_arg_sz("bin-limit"); + const bool print_context_specific = flag_set("skip-context-specific") == false; + const size_t max_depth = get_arg_sz("max-depth"); + + const size_t value_column = 60; + const size_t initial_level = 0; + + std::vector<uint8_t> file_contents = slurp_file(input); + std::vector<uint8_t> data; + + if(flag_set("pem") || + (input.size() > 4 && input.substr(input.size() - 4) == ".pem") || + (file_contents.size() > 20 && first_n(file_contents, 5, '-'))) + { +#if defined(BOTAN_HAS_PEM_CODEC) + std::string pem_label; + Botan::DataSource_Memory src(file_contents); + data = unlock(Botan::PEM_Code::decode(src, pem_label)); +#else + throw CLI_Error_Unsupported("PEM decoding not available in this build"); +#endif + } + else + { + data.swap(file_contents); + } + + Botan::ASN1_Pretty_Printer printer(print_limit, bin_limit, print_context_specific, + initial_level, value_column, max_depth); + + printer.print_to_stream(output(), data.data(), data.size()); + } + }; + +BOTAN_REGISTER_COMMAND("asn1print", ASN1_Printer); + +} + +#endif // BOTAN_HAS_ASN1 && BOTAN_HAS_PEM_CODEC diff --git a/comm/third_party/botan/src/cli/bcrypt.cpp b/comm/third_party/botan/src/cli/bcrypt.cpp new file mode 100644 index 0000000000..68e77b8e60 --- /dev/null +++ b/comm/third_party/botan/src/cli/bcrypt.cpp @@ -0,0 +1,89 @@ +/* +* (C) 2009,2010,2014,2015,2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_BCRYPT) + #include <botan/bcrypt.h> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_BCRYPT) + +class Generate_Bcrypt final : public Command + { + public: + Generate_Bcrypt() : Command("gen_bcrypt --work-factor=12 password") {} + + std::string group() const override + { + return "passhash"; + } + + std::string description() const override + { + return "Calculate bcrypt password hash"; + } + + void go() override + { + const std::string password = get_passphrase_arg("Passphrase to hash", "password"); + const size_t wf = get_arg_sz("work-factor"); + + if(wf < 4 || wf > 18) + { + error_output() << "Invalid bcrypt work factor\n"; + } + else + { + const uint16_t wf16 = static_cast<uint16_t>(wf); + output() << Botan::generate_bcrypt(password, rng(), wf16) << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("gen_bcrypt", Generate_Bcrypt); + +class Check_Bcrypt final : public Command + { + public: + Check_Bcrypt() : Command("check_bcrypt password hash") {} + + std::string group() const override + { + return "passhash"; + } + + std::string description() const override + { + return "Verify bcrypt password hash"; + } + + void go() override + { + const std::string password = get_passphrase_arg("Password to check", "password"); + const std::string hash = get_arg("hash"); + + if(hash.length() != 60) + { + error_output() << "Note: bcrypt '" << hash << "' has wrong length and cannot be valid\n"; + } + + const bool ok = Botan::check_bcrypt(password, hash); + + output() << "Password is " << (ok ? "valid" : "NOT valid") << std::endl; + + if(ok == false) + set_return_code(1); + } + }; + +BOTAN_REGISTER_COMMAND("check_bcrypt", Check_Bcrypt); + +#endif // bcrypt + +} diff --git a/comm/third_party/botan/src/cli/cc_enc.cpp b/comm/third_party/botan/src/cli/cc_enc.cpp new file mode 100644 index 0000000000..509b996012 --- /dev/null +++ b/comm/third_party/botan/src/cli/cc_enc.cpp @@ -0,0 +1,189 @@ +/* +* (C) 2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include <botan/hex.h> + +#if defined(BOTAN_HAS_FPE_FE1) && defined(BOTAN_HAS_PBKDF) + +#include <botan/fpe_fe1.h> +#include <botan/pbkdf.h> + +namespace Botan_CLI { + +namespace { + +uint8_t luhn_checksum(uint64_t cc_number) + { + uint8_t sum = 0; + + bool alt = false; + while(cc_number) + { + uint8_t digit = cc_number % 10; + if(alt) + { + digit *= 2; + if(digit > 9) + { + digit -= 9; + } + } + + sum += digit; + + cc_number /= 10; + alt = !alt; + } + + return (sum % 10); + } + +bool luhn_check(uint64_t cc_number) + { + return (luhn_checksum(cc_number) == 0); + } + +uint64_t cc_rank(uint64_t cc_number) + { + // Remove Luhn checksum + return cc_number / 10; + } + +uint64_t cc_derank(uint64_t cc_number) + { + for(size_t i = 0; i != 10; ++i) + { + if(luhn_check(cc_number * 10 + i)) + { + return (cc_number * 10 + i); + } + } + + return 0; + } + +uint64_t encrypt_cc_number(uint64_t cc_number, + const Botan::secure_vector<uint8_t>& key, + const std::vector<uint8_t>& tweak) + { + const Botan::BigInt n = 1000000000000000; + + const uint64_t cc_ranked = cc_rank(cc_number); + + const Botan::BigInt c = Botan::FPE::fe1_encrypt(n, cc_ranked, key, tweak); + + if(c.bits() > 50) + { + throw Botan::Internal_Error("FPE produced a number too large"); + } + + uint64_t enc_cc = 0; + for(size_t i = 0; i != 7; ++i) + { + enc_cc = (enc_cc << 8) | c.byte_at(6 - i); + } + return cc_derank(enc_cc); + } + +uint64_t decrypt_cc_number(uint64_t enc_cc, + const Botan::secure_vector<uint8_t>& key, + const std::vector<uint8_t>& tweak) + { + const Botan::BigInt n = 1000000000000000; + + const uint64_t cc_ranked = cc_rank(enc_cc); + + const Botan::BigInt c = Botan::FPE::fe1_decrypt(n, cc_ranked, key, tweak); + + if(c.bits() > 50) + { + throw CLI_Error("FPE produced a number too large"); + } + + uint64_t dec_cc = 0; + for(size_t i = 0; i != 7; ++i) + { + dec_cc = (dec_cc << 8) | c.byte_at(6 - i); + } + return cc_derank(dec_cc); + } + +} + +class CC_Encrypt final : public Command + { + public: + CC_Encrypt() : Command("cc_encrypt CC passphrase --tweak=") {} + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Encrypt the passed valid credit card number using FPE encryption"; + } + + void go() override + { + const uint64_t cc_number = std::stoull(get_arg("CC")); + const std::vector<uint8_t> tweak = Botan::hex_decode(get_arg("tweak")); + const std::string pass = get_arg("passphrase"); + + std::unique_ptr<Botan::PBKDF> pbkdf(Botan::PBKDF::create("PBKDF2(SHA-256)")); + if(!pbkdf) + { + throw CLI_Error_Unsupported("PBKDF", "PBKDF2(SHA-256)"); + } + + Botan::secure_vector<uint8_t> key = pbkdf->pbkdf_iterations(32, pass, tweak.data(), tweak.size(), 100000); + + output() << encrypt_cc_number(cc_number, key, tweak) << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("cc_encrypt", CC_Encrypt); + +class CC_Decrypt final : public Command + { + public: + CC_Decrypt() : Command("cc_decrypt CC passphrase --tweak=") {} + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Decrypt the passed valid ciphertext credit card number using FPE decryption"; + } + + void go() override + { + const uint64_t cc_number = std::stoull(get_arg("CC")); + const std::vector<uint8_t> tweak = Botan::hex_decode(get_arg("tweak")); + const std::string pass = get_arg("passphrase"); + + std::unique_ptr<Botan::PBKDF> pbkdf(Botan::PBKDF::create("PBKDF2(SHA-256)")); + if(!pbkdf) + { + throw CLI_Error_Unsupported("PBKDF", "PBKDF2(SHA-256)"); + } + + Botan::secure_vector<uint8_t> key = pbkdf->pbkdf_iterations(32, pass, tweak.data(), tweak.size(), 100000); + + output() << decrypt_cc_number(cc_number, key, tweak) << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("cc_decrypt", CC_Decrypt); + +} + +#endif // FPE && PBKDF diff --git a/comm/third_party/botan/src/cli/cli.cpp b/comm/third_party/botan/src/cli/cli.cpp new file mode 100644 index 0000000000..1fc5ed116e --- /dev/null +++ b/comm/third_party/botan/src/cli/cli.cpp @@ -0,0 +1,349 @@ +/* +* (C) 2015,2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include "argparse.h" +#include <botan/rng.h> +#include <botan/parsing.h> +#include <botan/internal/os_utils.h> +#include <iostream> +#include <fstream> + +#if defined(BOTAN_HAS_HEX_CODEC) + #include <botan/hex.h> +#endif + +#if defined(BOTAN_HAS_BASE64_CODEC) + #include <botan/base64.h> +#endif + +#if defined(BOTAN_HAS_BASE58_CODEC) + #include <botan/base58.h> +#endif + +namespace Botan_CLI { + +Command::Command(const std::string& cmd_spec) : m_spec(cmd_spec) + { + // for checking all spec strings at load time + //m_args.reset(new Argument_Parser(m_spec)); + } + +Command::~Command() { /* for unique_ptr */ } + +std::string Command::cmd_name() const + { + return m_spec.substr(0, m_spec.find(' ')); + } + +std::string Command::help_text() const + { + return "Usage: " + m_spec; + } + +int Command::run(const std::vector<std::string>& params) + { + try + { + m_args.reset(new Argument_Parser(m_spec, + {"verbose", "help"}, + {"output", "error-output", "rng-type", "drbg-seed"})); + + m_args->parse_args(params); + + if(m_args->has_arg("output")) + { + const std::string output_file = get_arg("output"); + + if(output_file != "") + { + m_output_stream.reset(new std::ofstream(output_file, std::ios::binary)); + if(!m_output_stream->good()) + throw CLI_IO_Error("opening", output_file); + } + } + + if(m_args->has_arg("error-output")) + { + const std::string output_file = get_arg("error-output"); + + if(output_file != "") + { + m_error_output_stream.reset(new std::ofstream(output_file, std::ios::binary)); + if(!m_error_output_stream->good()) + throw CLI_IO_Error("opening", output_file); + } + } + + if(flag_set("help")) + { + output() << help_text() << "\n"; + return 2; + } + + this->go(); + return m_return_code; + } + catch(CLI_Usage_Error& e) + { + error_output() << "Usage error: " << e.what() << "\n"; + error_output() << help_text() << "\n"; + return 1; + } + catch(std::exception& e) + { + error_output() << "Error: " << e.what() << "\n"; + return 2; + } + catch(...) + { + error_output() << "Error: unknown exception\n"; + return 2; + } + } + +bool Command::flag_set(const std::string& flag_name) const + { + return m_args->flag_set(flag_name); + } + +std::string Command::get_arg(const std::string& opt_name) const + { + return m_args->get_arg(opt_name); + } + +/* +* Like get_arg() but if the argument was not specified or is empty, returns otherwise +*/ +std::string Command::get_arg_or(const std::string& opt_name, const std::string& otherwise) const + { + return m_args->get_arg_or(opt_name, otherwise); + } + +size_t Command::get_arg_sz(const std::string& opt_name) const + { + return m_args->get_arg_sz(opt_name); + } + +uint16_t Command::get_arg_u16(const std::string& opt_name) const + { + const size_t val = get_arg_sz(opt_name); + if(static_cast<uint16_t>(val) != val) + throw CLI_Usage_Error("Argument " + opt_name + " has value out of allowed range"); + return static_cast<uint16_t>(val); + } + +uint32_t Command::get_arg_u32(const std::string& opt_name) const + { + const size_t val = get_arg_sz(opt_name); + if(static_cast<uint32_t>(val) != val) + throw CLI_Usage_Error("Argument " + opt_name + " has value out of allowed range"); + return static_cast<uint32_t>(val); + } + +std::vector<std::string> Command::get_arg_list(const std::string& what) const + { + return m_args->get_arg_list(what); + } + +std::ostream& Command::output() + { + if(m_output_stream.get()) + { + return *m_output_stream; + } + return std::cout; + } + +std::ostream& Command::error_output() + { + if(m_error_output_stream.get()) + { + return *m_error_output_stream; + } + return std::cerr; + } + +std::vector<uint8_t> Command::slurp_file(const std::string& input_file, + size_t buf_size) const + { + std::vector<uint8_t> buf; + auto insert_fn = [&](const uint8_t b[], size_t l) + { + buf.insert(buf.end(), b, b + l); + }; + this->read_file(input_file, insert_fn, buf_size); + return buf; + } + +std::string Command::slurp_file_as_str(const std::string& input_file, + size_t buf_size) const + { + std::string str; + auto insert_fn = [&](const uint8_t b[], size_t l) + { + str.append(reinterpret_cast<const char*>(b), l); + }; + this->read_file(input_file, insert_fn, buf_size); + return str; + } + +void Command::read_file(const std::string& input_file, + std::function<void (uint8_t[], size_t)> consumer_fn, + size_t buf_size) const + { + if(input_file == "-") + { + do_read_file(std::cin, consumer_fn, buf_size); + } + else + { + std::ifstream in(input_file, std::ios::binary); + if(!in) + { + throw CLI_IO_Error("reading file", input_file); + } + do_read_file(in, consumer_fn, buf_size); + } + } + +void Command::do_read_file(std::istream& in, + std::function<void (uint8_t[], size_t)> consumer_fn, + size_t buf_size) const + { + // Avoid an infinite loop on --buf-size=0 + std::vector<uint8_t> buf(buf_size == 0 ? 4096 : buf_size); + + while(in.good()) + { + in.read(reinterpret_cast<char*>(buf.data()), buf.size()); + const size_t got = static_cast<size_t>(in.gcount()); + consumer_fn(buf.data(), got); + } + } + +Botan::RandomNumberGenerator& Command::rng() + { + if(m_rng == nullptr) + { + m_rng = cli_make_rng(get_arg("rng-type"), get_arg("drbg-seed")); + } + + return *m_rng.get(); + } + +std::string Command::get_passphrase_arg(const std::string& prompt, const std::string& opt_name) + { + const std::string s = get_arg(opt_name); + if(s != "-") + return s; + return get_passphrase(prompt); + } + +namespace { + +bool echo_suppression_supported() + { + auto echo = Botan::OS::suppress_echo_on_terminal(); + return (echo != nullptr); + } + +} + +std::string Command::get_passphrase(const std::string& prompt) + { + if(echo_suppression_supported() == false) + error_output() << "Warning: terminal echo suppression not enabled for this platform\n"; + + error_output() << prompt << ": " << std::flush; + std::string pass; + + auto echo_suppress = Botan::OS::suppress_echo_on_terminal(); + + std::getline(std::cin, pass); + + return pass; + } + +//static +std::string Command::format_blob(const std::string& format, + const uint8_t bits[], size_t len) + { +#if defined(BOTAN_HAS_HEX_CODEC) + if(format == "hex") + { + return Botan::hex_encode(bits, len); + } +#endif + +#if defined(BOTAN_HAS_BASE64_CODEC) + if(format == "base64") + { + return Botan::base64_encode(bits, len); + } +#endif + +#if defined(BOTAN_HAS_BASE58_CODEC) + if(format == "base58") + { + return Botan::base58_encode(bits, len); + } + if(format == "base58check") + { + return Botan::base58_check_encode(bits, len); + } +#endif + + // If we supported format, we would have already returned + throw CLI_Usage_Error("Unknown or unsupported format type"); + } + +// Registration code + +Command::Registration::Registration(const std::string& name, Command::cmd_maker_fn maker_fn) + { + std::map<std::string, Command::cmd_maker_fn>& reg = Command::global_registry(); + + if(reg.count(name) > 0) + { + throw CLI_Error("Duplicated registration of command " + name); + } + + reg.insert(std::make_pair(name, maker_fn)); + } + +//static +std::map<std::string, Command::cmd_maker_fn>& Command::global_registry() + { + static std::map<std::string, Command::cmd_maker_fn> g_cmds; + return g_cmds; + } + +//static +std::vector<std::string> Command::registered_cmds() + { + std::vector<std::string> cmds; + for(auto& cmd : Command::global_registry()) + cmds.push_back(cmd.first); + return cmds; + } + +//static +std::unique_ptr<Command> Command::get_cmd(const std::string& name) + { + const std::map<std::string, Command::cmd_maker_fn>& reg = Command::global_registry(); + + std::unique_ptr<Command> r; + auto i = reg.find(name); + if(i != reg.end()) + { + r.reset(i->second()); + } + + return r; + } + +} diff --git a/comm/third_party/botan/src/cli/cli.h b/comm/third_party/botan/src/cli/cli.h new file mode 100644 index 0000000000..6ddf34d025 --- /dev/null +++ b/comm/third_party/botan/src/cli/cli.h @@ -0,0 +1,219 @@ +/* +* (C) 2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CLI_H_ +#define BOTAN_CLI_H_ + +#include <botan/build.h> +#include <functional> +#include <ostream> +#include <map> +#include <memory> +#include <string> +#include <vector> +#include "cli_exceptions.h" + +namespace Botan { + +class RandomNumberGenerator; + +} + +namespace Botan_CLI { + +class Argument_Parser; + +/* Declared in cli_rng.cpp */ +std::unique_ptr<Botan::RandomNumberGenerator> +cli_make_rng(const std::string& type = "", const std::string& hex_drbg_seed = ""); + +class Command + { + public: + + /** + * Get a registered command + */ + static std::unique_ptr<Command> get_cmd(const std::string& name); + + static std::vector<std::string> registered_cmds(); + + /** + * The spec string specifies the format of the command line, eg for + * a somewhat complicated command: + * cmd_name --flag --option1= --option2=opt2val input1 input2 *rest + * + * By default this is the value returned by help_text() + * + * The first value is always the command name. Options may appear + * in any order. Named arguments are taken from the command line + * in the order they appear in the spec. + * + * --flag can optionally be specified, and takes no value. + * Check for it in go() with flag_set() + * + * --option1 is an option whose default value (if the option + * does not appear on the command line) is the empty string. + * + * --option2 is an option whose default value is opt2val + * Read the values in go() using get_arg or get_arg_sz. + * + * The values input1 and input2 specify named arguments which must + * be provided. They are also access via get_arg/get_arg_sz + * Because options and arguments for a single command share the same + * namespace you can't have a spec like: + * cmd --input input + * but you hopefully didn't want to do that anyway. + * + * The leading '*' on '*rest' specifies that all remaining arguments + * should be packaged in a list which is available as get_arg_list("rest"). + * This can only appear on a single value and should be the final + * named argument. + * + * Every command has implicit flags --help, --verbose and implicit + * options --output= and --error-output= which override the default + * use of std::cout and std::cerr. + * + * Use of --help is captured in run() and returns help_text(). + * Use of --verbose can be checked with verbose() or flag_set("verbose") + */ + explicit Command(const std::string& cmd_spec); + + virtual ~Command(); + + int run(const std::vector<std::string>& params); + + virtual std::string group() const = 0; + + virtual std::string description() const = 0; + + virtual std::string help_text() const; + + const std::string& cmd_spec() const + { + return m_spec; + } + + std::string cmd_name() const; + + protected: + + /* + * The actual functionality of the cli command implemented in subclass. + * The return value from main will be zero. + */ + virtual void go() = 0; + + void set_return_code(int rc) { m_return_code = rc; } + + std::ostream& output(); + + std::ostream& error_output(); + + bool verbose() const + { + return flag_set("verbose"); + } + + std::string get_passphrase(const std::string& prompt); + + bool flag_set(const std::string& flag_name) const; + + static std::string format_blob(const std::string& format, const uint8_t bits[], size_t len); + + template<typename Alloc> + static std::string format_blob(const std::string& format, + const std::vector<uint8_t, Alloc>& vec) + { + return format_blob(format, vec.data(), vec.size()); + } + + std::string get_arg(const std::string& opt_name) const; + + /** + * Like get_arg but if the value is '-' then reads a passphrase from + * the terminal with echo suppressed. + */ + std::string get_passphrase_arg(const std::string& prompt, + const std::string& opt_name); + + /* + * Like get_arg() but if the argument was not specified or is empty, returns otherwise + */ + std::string get_arg_or(const std::string& opt_name, const std::string& otherwise) const; + + size_t get_arg_sz(const std::string& opt_name) const; + + uint16_t get_arg_u16(const std::string& opt_name) const; + + uint32_t get_arg_u32(const std::string& opt_name) const; + + std::vector<std::string> get_arg_list(const std::string& what) const; + + /* + * Read an entire file into memory and return the contents + */ + std::vector<uint8_t> slurp_file(const std::string& input_file, + size_t buf_size = 0) const; + + std::string slurp_file_as_str(const std::string& input_file, + size_t buf_size = 0) const; + + /* + * Read a file calling consumer_fn() with the inputs + */ + void read_file(const std::string& input_file, + std::function<void (uint8_t[], size_t)> consumer_fn, + size_t buf_size = 0) const; + + + void do_read_file(std::istream& in, + std::function<void (uint8_t[], size_t)> consumer_fn, + size_t buf_size = 0) const; + + template<typename Alloc> + void write_output(const std::vector<uint8_t, Alloc>& vec) + { + output().write(reinterpret_cast<const char*>(vec.data()), vec.size()); + } + + Botan::RandomNumberGenerator& rng(); + + private: + typedef std::function<Command* ()> cmd_maker_fn; + static std::map<std::string, cmd_maker_fn>& global_registry(); + + void parse_spec(); + + // set in constructor + std::string m_spec; + + std::unique_ptr<Argument_Parser> m_args; + std::unique_ptr<std::ostream> m_output_stream; + std::unique_ptr<std::ostream> m_error_output_stream; + + std::unique_ptr<Botan::RandomNumberGenerator> m_rng; + + // possibly set by calling set_return_code() + int m_return_code = 0; + + public: + // the registry interface: + + class Registration final + { + public: + Registration(const std::string& name, cmd_maker_fn maker_fn); + }; + }; + +#define BOTAN_REGISTER_COMMAND(name, CLI_Class) \ + Botan_CLI::Command::Registration reg_cmd_ ## CLI_Class(name, \ + []() -> Botan_CLI::Command* { return new CLI_Class; }) + +} + +#endif diff --git a/comm/third_party/botan/src/cli/cli_exceptions.h b/comm/third_party/botan/src/cli/cli_exceptions.h new file mode 100644 index 0000000000..c88d170271 --- /dev/null +++ b/comm/third_party/botan/src/cli/cli_exceptions.h @@ -0,0 +1,47 @@ +/* +* (C) 2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CLI_EXCEPTIONS_H_ +#define BOTAN_CLI_EXCEPTIONS_H_ + +namespace Botan_CLI { + +class CLI_Error : public std::runtime_error + { + public: + explicit CLI_Error(const std::string& s) : std::runtime_error(s) {} + }; + +class CLI_IO_Error final : public CLI_Error + { + public: + CLI_IO_Error(const std::string& op, const std::string& who) : + CLI_Error("Error " + op + " " + who) {} + }; + +class CLI_Usage_Error final : public CLI_Error + { + public: + explicit CLI_Usage_Error(const std::string& what) : CLI_Error(what) {} + }; + +/* Thrown eg when a requested feature was compiled out of the library + or is not available, eg hashing with MD2 +*/ +class CLI_Error_Unsupported final : public CLI_Error + { + public: + + CLI_Error_Unsupported(const std::string& msg) : CLI_Error(msg) {} + + CLI_Error_Unsupported(const std::string& what, + const std::string& who) + : CLI_Error(what + " with '" + who + "' unsupported or not available") {} + }; + +} + +#endif diff --git a/comm/third_party/botan/src/cli/cli_rng.cpp b/comm/third_party/botan/src/cli/cli_rng.cpp new file mode 100644 index 0000000000..e3eee0c035 --- /dev/null +++ b/comm/third_party/botan/src/cli/cli_rng.cpp @@ -0,0 +1,146 @@ +/* +* (C) 2015,2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include <botan/rng.h> +#include <botan/entropy_src.h> +#include <botan/hex.h> +#include <botan/parsing.h> + +#if defined(BOTAN_HAS_AUTO_SEEDING_RNG) + #include <botan/auto_rng.h> +#endif + +#if defined(BOTAN_HAS_SYSTEM_RNG) + #include <botan/system_rng.h> +#endif + +#if defined(BOTAN_HAS_PROCESSOR_RNG) + #include <botan/processor_rng.h> +#endif + +#if defined(BOTAN_HAS_HMAC_DRBG) + #include <botan/hmac_drbg.h> +#endif + +namespace Botan_CLI { + +std::unique_ptr<Botan::RandomNumberGenerator> +cli_make_rng(const std::string& rng_type, const std::string& hex_drbg_seed) + { +#if defined(BOTAN_HAS_SYSTEM_RNG) + if(rng_type == "system" || rng_type.empty()) + { + return std::unique_ptr<Botan::RandomNumberGenerator>(new Botan::System_RNG); + } +#endif + + const std::vector<uint8_t> drbg_seed = Botan::hex_decode(hex_drbg_seed); + +#if defined(BOTAN_HAS_AUTO_SEEDING_RNG) + if(rng_type == "auto" || rng_type == "entropy" || rng_type.empty()) + { + std::unique_ptr<Botan::RandomNumberGenerator> rng; + + if(rng_type == "entropy") + rng.reset(new Botan::AutoSeeded_RNG(Botan::Entropy_Sources::global_sources())); + else + rng.reset(new Botan::AutoSeeded_RNG); + + if(drbg_seed.size() > 0) + rng->add_entropy(drbg_seed.data(), drbg_seed.size()); + return rng; + } +#endif + +#if defined(BOTAN_HAS_HMAC_DRBG) && defined(BOTAN_HAS_SHA2_32) + if(rng_type == "drbg" || (rng_type.empty() && drbg_seed.empty() == false)) + { + std::unique_ptr<Botan::MessageAuthenticationCode> mac = + Botan::MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); + std::unique_ptr<Botan::Stateful_RNG> rng(new Botan::HMAC_DRBG(std::move(mac))); + rng->add_entropy(drbg_seed.data(), drbg_seed.size()); + + if(rng->is_seeded() == false) + throw CLI_Error("For " + rng->name() + " a seed of at least " + + std::to_string(rng->security_level()/8) + + " bytes must be provided"); + + return std::unique_ptr<Botan::RandomNumberGenerator>(rng.release()); + } +#endif + +#if defined(BOTAN_HAS_PROCESSOR_RNG) + if(rng_type == "rdrand" || rng_type == "cpu" || rng_type.empty()) + { + if(Botan::Processor_RNG::available()) + return std::unique_ptr<Botan::RandomNumberGenerator>(new Botan::Processor_RNG); + else if(rng_type.empty() == false) + throw CLI_Error("RNG instruction not supported on this processor"); + } +#endif + + if(rng_type.empty()) + throw CLI_Error_Unsupported("No random number generator seems to be available in the current build"); + else + throw CLI_Error_Unsupported("RNG", rng_type); + } + +class RNG final : public Command + { + public: + RNG() : Command("rng --format=hex --system --rdrand --auto --entropy --drbg --drbg-seed= *bytes") {} + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Sample random bytes from the specified rng"; + } + + void go() override + { + const std::string format = get_arg("format"); + std::string type = get_arg("rng-type"); + + if(type.empty()) + { + for(std::string flag : { "system", "rdrand", "auto", "entropy", "drbg" }) + { + if(flag_set(flag)) + { + type = flag; + break; + } + } + } + + const std::string drbg_seed = get_arg("drbg-seed"); + std::unique_ptr<Botan::RandomNumberGenerator> rng = cli_make_rng(type, drbg_seed); + + for(const std::string& req : get_arg_list("bytes")) + { + const size_t req_len = Botan::to_u32bit(req); + const auto blob = rng->random_vec(req_len); + + if(format == "binary" || format == "raw") + { + output().write(reinterpret_cast<const char*>(blob.data()), blob.size()); + } + else + { + output() << format_blob(format, blob) << "\n"; + } + } + } + }; + +BOTAN_REGISTER_COMMAND("rng", RNG); + +} diff --git a/comm/third_party/botan/src/cli/codec.cpp b/comm/third_party/botan/src/cli/codec.cpp new file mode 100644 index 0000000000..48388d1a7a --- /dev/null +++ b/comm/third_party/botan/src/cli/codec.cpp @@ -0,0 +1,268 @@ +/* +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_HEX_CODEC) + #include <botan/hex.h> +#endif + +#if defined(BOTAN_HAS_BASE32_CODEC) + #include <botan/base32.h> +#endif + +#if defined(BOTAN_HAS_BASE58_CODEC) + #include <botan/base58.h> +#endif + +#if defined(BOTAN_HAS_BASE64_CODEC) + #include <botan/base64.h> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_HEX_CODEC) + +class Hex_Encode final : public Command + { + public: + Hex_Encode() : Command("hex_enc file") {} + + std::string group() const override + { + return "codec"; + } + + std::string description() const override + { + return "Hex encode a given file"; + } + + void go() override + { + auto hex_enc_f = [&](const uint8_t b[], size_t l) { output() << Botan::hex_encode(b, l); }; + this->read_file(get_arg("file"), hex_enc_f, 2); + } + }; + +BOTAN_REGISTER_COMMAND("hex_enc", Hex_Encode); + +class Hex_Decode final : public Command + { + public: + Hex_Decode() : Command("hex_dec file") {} + + std::string group() const override + { + return "codec"; + } + + std::string description() const override + { + return "Hex decode a given file"; + } + + void go() override + { + auto hex_dec_f = [&](const uint8_t b[], size_t l) + { + std::vector<uint8_t> bin = Botan::hex_decode(reinterpret_cast<const char*>(b), l); + output().write(reinterpret_cast<const char*>(bin.data()), bin.size()); + }; + + this->read_file(get_arg("file"), hex_dec_f, 2); + } + }; + +BOTAN_REGISTER_COMMAND("hex_dec", Hex_Decode); + +#endif + +#if defined(BOTAN_HAS_BASE58_CODEC) + +class Base58_Encode final : public Command + { + public: + Base58_Encode() : Command("base58_enc --check file") {} + + std::string group() const override + { + return "codec"; + } + + std::string description() const override + { + return "Encode given file to Base58"; + } + + void go() override + { + auto data = slurp_file(get_arg("file")); + + if(flag_set("check")) + output() << Botan::base58_check_encode(data); + else + output() << Botan::base58_encode(data); + } + }; + +BOTAN_REGISTER_COMMAND("base58_enc", Base58_Encode); + +class Base58_Decode final : public Command + { + public: + Base58_Decode() : Command("base58_dec --check file") {} + + std::string group() const override + { + return "codec"; + } + + std::string description() const override + { + return "Decode Base58 encoded file"; + } + + void go() override + { + auto data = slurp_file_as_str(get_arg("file")); + + std::vector<uint8_t> bin; + + if(flag_set("check")) + bin = Botan::base58_check_decode(data); + else + bin = Botan::base58_decode(data); + + output().write(reinterpret_cast<const char*>(bin.data()), bin.size()); + } + }; + +BOTAN_REGISTER_COMMAND("base58_dec", Base58_Decode); + +#endif // base58 + +#if defined(BOTAN_HAS_BASE32_CODEC) + +class Base32_Encode final : public Command + { + public: + Base32_Encode() : Command("base32_enc file") {} + + std::string group() const override + { + return "codec"; + } + + std::string description() const override + { + return "Encode given file to Base32"; + } + + void go() override + { + auto onData = [&](const uint8_t b[], size_t l) + { + output() << Botan::base32_encode(b, l); + }; + this->read_file(get_arg("file"), onData, 768); + } + }; + +BOTAN_REGISTER_COMMAND("base32_enc", Base32_Encode); + +class Base32_Decode final : public Command + { + public: + Base32_Decode() : Command("base32_dec file") {} + + std::string group() const override + { + return "codec"; + } + + std::string description() const override + { + return "Decode Base32 encoded file"; + } + + void go() override + { + auto write_bin = [&](const uint8_t b[], size_t l) + { + Botan::secure_vector<uint8_t> bin = Botan::base32_decode(reinterpret_cast<const char*>(b), l); + output().write(reinterpret_cast<const char*>(bin.data()), bin.size()); + }; + + this->read_file(get_arg("file"), write_bin, 1024); + } + }; + +BOTAN_REGISTER_COMMAND("base32_dec", Base32_Decode); + +#endif // base32 + +#if defined(BOTAN_HAS_BASE64_CODEC) + +class Base64_Encode final : public Command + { + public: + Base64_Encode() : Command("base64_enc file") {} + + std::string group() const override + { + return "codec"; + } + + std::string description() const override + { + return "Encode given file to Base64"; + } + + void go() override + { + auto onData = [&](const uint8_t b[], size_t l) + { + output() << Botan::base64_encode(b, l); + }; + this->read_file(get_arg("file"), onData, 768); + } + }; + +BOTAN_REGISTER_COMMAND("base64_enc", Base64_Encode); + +class Base64_Decode final : public Command + { + public: + Base64_Decode() : Command("base64_dec file") {} + + std::string group() const override + { + return "codec"; + } + + std::string description() const override + { + return "Decode Base64 encoded file"; + } + + void go() override + { + auto write_bin = [&](const uint8_t b[], size_t l) + { + Botan::secure_vector<uint8_t> bin = Botan::base64_decode(reinterpret_cast<const char*>(b), l); + output().write(reinterpret_cast<const char*>(bin.data()), bin.size()); + }; + + this->read_file(get_arg("file"), write_bin, 1024); + } + }; + +BOTAN_REGISTER_COMMAND("base64_dec", Base64_Decode); + +#endif // base64 + +} diff --git a/comm/third_party/botan/src/cli/compress.cpp b/comm/third_party/botan/src/cli/compress.cpp new file mode 100644 index 0000000000..e62acd7636 --- /dev/null +++ b/comm/third_party/botan/src/cli/compress.cpp @@ -0,0 +1,190 @@ +/* +* (C) 2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_COMPRESSION) + #include <botan/compression.h> + #include <fstream> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_COMPRESSION) + +class Compress final : public Command + { + public: + Compress() : Command("compress --type=gzip --level=6 --buf-size=8192 file") {} + + std::string output_filename(const std::string& input_fsname, const std::string& comp_type) + { + const std::map<std::string, std::string> suffixes = + { + { "zlib", "zlib" }, + { "gzip", "gz" }, + { "bzip2", "bz2" }, + { "lzma", "xz" }, + }; + + auto suffix_info = suffixes.find(comp_type); + if(suffixes.count(comp_type) == 0) + { + throw CLI_Error_Unsupported("Compressing", comp_type); + } + + return input_fsname + "." + suffix_info->second; + } + + std::string group() const override + { + return "compression"; + } + + std::string description() const override + { + return "Compress a given file"; + } + + void go() override + { + const std::string comp_type = get_arg("type"); + const size_t buf_size = get_arg_sz("buf-size"); + const size_t comp_level = get_arg_sz("level"); + + std::unique_ptr<Botan::Compression_Algorithm> compress; + + compress.reset(Botan::make_compressor(comp_type)); + + if(!compress) + { + throw CLI_Error_Unsupported("Compression", comp_type); + } + + const std::string in_file = get_arg("file"); + std::ifstream in(in_file, std::ios::binary); + + if(!in.good()) + { + throw CLI_IO_Error("reading", in_file); + } + + const std::string out_file = output_filename(in_file, comp_type); + std::ofstream out(out_file, std::ios::binary); + if(!out.good()) + { + throw CLI_IO_Error("writing", out_file); + } + + Botan::secure_vector<uint8_t> buf; + + compress->start(comp_level); + + while(in.good()) + { + buf.resize(buf_size); + in.read(reinterpret_cast<char*>(buf.data()), buf.size()); + buf.resize(in.gcount()); + + compress->update(buf); + + out.write(reinterpret_cast<const char*>(buf.data()), buf.size()); + } + + buf.clear(); + compress->finish(buf); + out.write(reinterpret_cast<const char*>(buf.data()), buf.size()); + out.close(); + } + }; + +BOTAN_REGISTER_COMMAND("compress", Compress); + +class Decompress final : public Command + { + public: + Decompress() : Command("decompress --buf-size=8192 file") {} + + void parse_extension(const std::string& in_file, + std::string& out_file, + std::string& suffix) + { + auto last_dot = in_file.find_last_of('.'); + if(last_dot == std::string::npos || last_dot == 0) + { + throw CLI_Error("No extension detected in filename '" + in_file + "'"); + } + + out_file = in_file.substr(0, last_dot); + suffix = in_file.substr(last_dot + 1, std::string::npos); + } + + std::string group() const override + { + return "compression"; + } + + std::string description() const override + { + return "Decompress a given compressed archive"; + } + + void go() override + { + const size_t buf_size = get_arg_sz("buf-size"); + const std::string in_file = get_arg("file"); + std::string out_file, suffix; + parse_extension(in_file, out_file, suffix); + + std::ifstream in(in_file, std::ios::binary); + + if(!in.good()) + { + throw CLI_IO_Error("reading", in_file); + } + + std::unique_ptr<Botan::Decompression_Algorithm> decompress; + + decompress.reset(Botan::make_decompressor(suffix)); + + if(!decompress) + { + throw CLI_Error_Unsupported("Decompression", suffix); + } + + std::ofstream out(out_file, std::ios::binary); + if(!out.good()) + { + throw CLI_IO_Error("writing", out_file); + } + + Botan::secure_vector<uint8_t> buf; + + decompress->start(); + + while(in.good()) + { + buf.resize(buf_size); + in.read(reinterpret_cast<char*>(buf.data()), buf.size()); + buf.resize(in.gcount()); + + decompress->update(buf); + + out.write(reinterpret_cast<const char*>(buf.data()), buf.size()); + } + + buf.clear(); + decompress->finish(buf); + out.write(reinterpret_cast<const char*>(buf.data()), buf.size()); + out.close(); + } + }; + +BOTAN_REGISTER_COMMAND("decompress", Decompress); + +#endif + +} diff --git a/comm/third_party/botan/src/cli/encryption.cpp b/comm/third_party/botan/src/cli/encryption.cpp new file mode 100644 index 0000000000..fa8de7cfdb --- /dev/null +++ b/comm/third_party/botan/src/cli/encryption.cpp @@ -0,0 +1,127 @@ +/* +* (C) 2015,2017 Simon Warta (Kullo GmbH) +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if (defined(BOTAN_HAS_AES) || defined(BOTAN_HAS_AEAD_CHACHA20_POLY1305)) && defined(BOTAN_HAS_AEAD_MODES) + +#include <botan/aead.h> +#include <botan/hex.h> +#include <sstream> + +namespace Botan_CLI { + +namespace { + +auto VALID_MODES = std::map<std::string, std::string>{ + // Don't add algorithms here without extending tests + // in `src/scripts/test_cli_crypt.py` + { "aes-128-cfb", "AES-128/CFB" }, + { "aes-192-cfb", "AES-192/CFB" }, + { "aes-256-cfb", "AES-256/CFB" }, + { "aes-128-gcm", "AES-128/GCM" }, + { "aes-192-gcm", "AES-192/GCM" }, + { "aes-256-gcm", "AES-256/GCM" }, + { "aes-128-ocb", "AES-128/OCB" }, + { "aes-128-xts", "AES-128/XTS" }, + { "aes-256-xts", "AES-256/XTS" }, + { "chacha20poly1305", "ChaCha20Poly1305" }, +}; + +Botan::secure_vector<uint8_t> +do_crypt(const std::string &cipher, + const std::vector<uint8_t> &input, + const Botan::SymmetricKey &key, + const Botan::InitializationVector &iv, + const std::vector<uint8_t>& ad, + Botan::Cipher_Dir direction) + { + if(iv.size() == 0) + throw CLI_Usage_Error("IV must not be empty"); + + // TODO: implement streaming + + std::unique_ptr<Botan::Cipher_Mode> processor(Botan::Cipher_Mode::create(cipher, direction)); + if(!processor) + throw CLI_Error("Cipher algorithm not found"); + + // Set key + processor->set_key(key); + + if(Botan::AEAD_Mode* aead = dynamic_cast<Botan::AEAD_Mode*>(processor.get())) + { + aead->set_ad(ad); + } + else if(ad.size() != 0) + { + throw CLI_Usage_Error("Cannot specify associated data with non-AEAD mode"); + } + + // Set IV + processor->start(iv.bits_of()); + + Botan::secure_vector<uint8_t> buf(input.begin(), input.end()); + processor->finish(buf); + + return buf; + } + +} + +class Encryption final : public Command + { + public: + Encryption() : Command("encryption --buf-size=4096 --decrypt --mode= --key= --iv= --ad=") {} + + std::string group() const override + { + return "encryption"; + } + + std::string description() const override + { + return "Encrypt or decrypt a given file"; + } + + void go() override + { + std::string mode = get_arg_or("mode", ""); + if (!VALID_MODES.count(mode)) + { + std::ostringstream error; + error << "Invalid mode: '" << mode << "'\n" + << "valid modes are:"; + for (auto valid_mode : VALID_MODES) error << " " << valid_mode.first; + + throw CLI_Usage_Error(error.str()); + } + + const std::string key_hex = get_arg("key"); + const std::string iv_hex = get_arg("iv"); + const std::string ad_hex = get_arg_or("ad", ""); + const size_t buf_size = get_arg_sz("buf-size"); + + const std::vector<uint8_t> input = this->slurp_file("-", buf_size); + + if (verbose()) + { + error_output() << "Got " << input.size() << " bytes of input data.\n"; + } + + const Botan::SymmetricKey key(key_hex); + const Botan::InitializationVector iv(iv_hex); + const std::vector<uint8_t> ad = Botan::hex_decode(ad_hex); + + auto direction = flag_set("decrypt") ? Botan::Cipher_Dir::DECRYPTION : Botan::Cipher_Dir::ENCRYPTION; + write_output(do_crypt(VALID_MODES[mode], input, key, iv, ad, direction)); + } + }; + +BOTAN_REGISTER_COMMAND("encryption", Encryption); + +} + +#endif diff --git a/comm/third_party/botan/src/cli/entropy.cpp b/comm/third_party/botan/src/cli/entropy.cpp new file mode 100644 index 0000000000..0404afb995 --- /dev/null +++ b/comm/third_party/botan/src/cli/entropy.cpp @@ -0,0 +1,104 @@ +/* +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include "../tests/test_rng.h" // FIXME + +#include <botan/entropy_src.h> + +#if defined(BOTAN_HAS_COMPRESSION) +#include <botan/compression.h> +#endif + +namespace Botan_CLI { + +class Entropy final : public Command + { + public: + Entropy() : Command("entropy --truncate-at=128 source") {} + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Sample a raw entropy source"; + } + + void go() override + { + const std::string req_source = get_arg("source"); + const size_t truncate_sample = get_arg_sz("truncate-at"); + + auto& entropy_sources = Botan::Entropy_Sources::global_sources(); + + std::vector<std::string> sources; + if(req_source == "all") + sources = entropy_sources.enabled_sources(); + else + sources.push_back(req_source); + + for(std::string source : sources) + { + Botan_Tests::SeedCapturing_RNG rng; + const size_t entropy_estimate = entropy_sources.poll_just(rng, source); + + if(rng.samples() == 0) + { + output() << "Source " << source << " is unavailable\n"; + continue; + } + + const auto& sample = rng.seed_material(); + + output() << "Polling " << source << " gathered " << sample.size() + << " bytes in " << rng.samples() << " outputs with estimated entropy " + << entropy_estimate << "\n"; + +#if defined(BOTAN_HAS_COMPRESSION) + if(!sample.empty()) + { + std::unique_ptr<Botan::Compression_Algorithm> comp(Botan::make_compressor("zlib")); + if(comp) + { + try + { + Botan::secure_vector<uint8_t> compressed; + compressed.assign(sample.begin(), sample.end()); + comp->start(9); + comp->finish(compressed); + + if(compressed.size() < sample.size()) + { + output() << "Sample from " << source << " was zlib compressed from " << sample.size() + << " bytes to " << compressed.size() << " bytes\n"; + } + } + catch(std::exception& e) + { + error_output() << "Error while attempting to compress: " << e.what() << "\n"; + } + } + } +#endif + + if(sample.size() <= truncate_sample) + { + output() << Botan::hex_encode(sample) << "\n"; + } + else if(truncate_sample > 0) + { + output() << Botan::hex_encode(&sample[0], truncate_sample) << "...\n"; + } + } + } + }; + +BOTAN_REGISTER_COMMAND("entropy", Entropy); + +} diff --git a/comm/third_party/botan/src/cli/hash.cpp b/comm/third_party/botan/src/cli/hash.cpp new file mode 100644 index 0000000000..8e59b2ab56 --- /dev/null +++ b/comm/third_party/botan/src/cli/hash.cpp @@ -0,0 +1,78 @@ +/* +* (C) 2009,2010,2014,2015,2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_HASH) + #include <botan/hash.h> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_HASH) + +class Hash final : public Command + { + public: + Hash() : Command("hash --algo=SHA-256 --buf-size=4096 --no-fsname --format=hex *files") {} + + std::string group() const override + { + return "hash"; + } + + std::string description() const override + { + return "Compute the message digest of given file(s)"; + } + + void go() override + { + const std::string hash_algo = get_arg("algo"); + const std::string format = get_arg("format"); + const size_t buf_size = get_arg_sz("buf-size"); + const bool no_fsname = flag_set("no-fsname"); + + std::unique_ptr<Botan::HashFunction> hash_fn(Botan::HashFunction::create(hash_algo)); + + if(!hash_fn) + { + throw CLI_Error_Unsupported("hashing", hash_algo); + } + + std::vector<std::string> files = get_arg_list("files"); + if(files.empty()) + { + files.push_back("-"); + } // read stdin if no arguments on command line + + for(const std::string& fsname : files) + { + try + { + auto update_hash = [&](const uint8_t b[], size_t l) { hash_fn->update(b, l); }; + read_file(fsname, update_hash, buf_size); + + const std::string digest = format_blob(format, hash_fn->final()); + + if(no_fsname) + output() << digest << "\n"; + else + output() << digest << " " << fsname << "\n"; + } + catch(CLI_IO_Error& e) + { + error_output() << e.what() << "\n"; + } + } + } + }; + +BOTAN_REGISTER_COMMAND("hash", Hash); + +#endif + +} diff --git a/comm/third_party/botan/src/cli/hmac.cpp b/comm/third_party/botan/src/cli/hmac.cpp new file mode 100644 index 0000000000..5b7345c50e --- /dev/null +++ b/comm/third_party/botan/src/cli/hmac.cpp @@ -0,0 +1,78 @@ +/* +* (C) 2009,2010,2014,2015 Jack Lloyd +* (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#include <botan/hex.h> + +#if defined(BOTAN_HAS_MAC) + #include <botan/mac.h> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_HMAC) + +class HMAC final : public Command + { + public: + HMAC() : Command("hmac --hash=SHA-256 --buf-size=4096 --no-fsname key *files") {} + + std::string group() const override + { + return "hmac"; + } + + std::string description() const override + { + return "Compute the HMAC tag of given file(s)"; + } + + void go() override + { + const bool no_fsname = flag_set("no-fsname"); + const std::string hash_algo = get_arg("hash"); + std::unique_ptr<Botan::MessageAuthenticationCode> hmac = + Botan::MessageAuthenticationCode::create("HMAC(" + hash_algo + ")"); + + if(!hmac) + { throw CLI_Error_Unsupported("HMAC", hash_algo); } + + hmac->set_key(slurp_file(get_arg("key"))); + + const size_t buf_size = get_arg_sz("buf-size"); + + std::vector<std::string> files = get_arg_list("files"); + if(files.empty()) + { files.push_back("-"); } // read stdin if no arguments on command line + + for(const std::string& fsname : files) + { + try + { + auto update_hmac = [&](const uint8_t b[], size_t l) { hmac->update(b, l); }; + read_file(fsname, update_hmac, buf_size); + output() << Botan::hex_encode(hmac->final()); + + if(no_fsname == false) + output() << " " << fsname; + + output() << "\n"; + } + catch(CLI_IO_Error& e) + { + error_output() << e.what() << "\n"; + } + } + } + }; + +BOTAN_REGISTER_COMMAND("hmac", HMAC); + +#endif // hmac + +} diff --git a/comm/third_party/botan/src/cli/main.cpp b/comm/third_party/botan/src/cli/main.cpp new file mode 100644 index 0000000000..1f806a9066 --- /dev/null +++ b/comm/third_party/botan/src/cli/main.cpp @@ -0,0 +1,37 @@ +/* +* (C) 2009,2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include <botan/version.h> +#include <iostream> +#include <algorithm> + +int main(int argc, char* argv[]) + { + std::cerr << Botan::runtime_version_check(BOTAN_VERSION_MAJOR, BOTAN_VERSION_MINOR, BOTAN_VERSION_PATCH); + + std::string cmd_name = "help"; + + if(argc >= 2) + { + cmd_name = argv[1]; + if(cmd_name == "--help" || cmd_name == "-h") + cmd_name = "help"; + if(cmd_name == "--version" || cmd_name == "-V") + cmd_name = "version"; + } + + std::unique_ptr<Botan_CLI::Command> cmd(Botan_CLI::Command::get_cmd(cmd_name)); + + if(!cmd) + { + std::cout << "Unknown command " << cmd_name << " (try --help)\n"; + return 1; + } + + std::vector<std::string> args(argv + std::min(argc, 2), argv + argc); + return cmd->run(args); + } diff --git a/comm/third_party/botan/src/cli/math.cpp b/comm/third_party/botan/src/cli/math.cpp new file mode 100644 index 0000000000..1268cd3e53 --- /dev/null +++ b/comm/third_party/botan/src/cli/math.cpp @@ -0,0 +1,269 @@ +/* +* (C) 2009,2010,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_NUMBERTHEORY) + +#include <botan/numthry.h> +#include <botan/monty.h> +#include <iterator> + +namespace Botan_CLI { + +class Modular_Inverse final : public Command + { + public: + Modular_Inverse() : Command("mod_inverse n mod") {} + + std::string group() const override + { + return "numtheory"; + } + + std::string description() const override + { + return "Calculates a modular inverse"; + } + + void go() override + { + const Botan::BigInt n(get_arg("n")); + const Botan::BigInt mod(get_arg("mod")); + + output() << Botan::inverse_mod(n, mod) << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("mod_inverse", Modular_Inverse); + +class Gen_Prime final : public Command + { + public: + Gen_Prime() : Command("gen_prime --count=1 bits") {} + + std::string group() const override + { + return "numtheory"; + } + + std::string description() const override + { + return "Samples one or more primes"; + } + + void go() override + { + const size_t bits = get_arg_sz("bits"); + const size_t cnt = get_arg_sz("count"); + + for(size_t i = 0; i != cnt; ++i) + { + const Botan::BigInt p = Botan::random_prime(rng(), bits); + output() << p << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("gen_prime", Gen_Prime); + +class Is_Prime final : public Command + { + public: + Is_Prime() : Command("is_prime --prob=56 n") {} + + std::string group() const override + { + return "numtheory"; + } + + std::string description() const override + { + return "Test if the integer n is composite or prime"; + } + + void go() override + { + Botan::BigInt n(get_arg("n")); + const size_t prob = get_arg_sz("prob"); + const bool prime = Botan::is_prime(n, rng(), prob); + + output() << n << " is " << (prime ? "probably prime" : "composite") << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("is_prime", Is_Prime); + +/* +* Factor integers using a combination of trial division by small +* primes, and Pollard's Rho algorithm +*/ +class Factor final : public Command + { + public: + Factor() : Command("factor n") {} + + std::string group() const override + { + return "numtheory"; + } + + std::string description() const override + { + return "Factor a given integer"; + } + + void go() override + { + Botan::BigInt n(get_arg("n")); + + std::vector<Botan::BigInt> factors = factorize(n, rng()); + std::sort(factors.begin(), factors.end()); + + output() << n << ": "; + std::copy(factors.begin(), factors.end(), std::ostream_iterator<Botan::BigInt>(output(), " ")); + output() << std::endl; + } + + private: + + std::vector<Botan::BigInt> factorize(const Botan::BigInt& n_in, + Botan::RandomNumberGenerator& rng) + { + Botan::BigInt n = n_in; + std::vector<Botan::BigInt> factors = remove_small_factors(n); + + while(n != 1) + { + if(Botan::is_prime(n, rng)) + { + factors.push_back(n); + break; + } + + Botan::BigInt a_factor = 0; + while(a_factor == 0) + { + a_factor = rho(n, rng); + } + + std::vector<Botan::BigInt> rho_factored = factorize(a_factor, rng); + for(size_t j = 0; j != rho_factored.size(); j++) + { + factors.push_back(rho_factored[j]); + } + + n /= a_factor; + } + + return factors; + } + + /* + * Pollard's Rho algorithm, as described in the MIT algorithms book. + * Uses Brent's cycle finding + */ + Botan::BigInt rho(const Botan::BigInt& n, Botan::RandomNumberGenerator& rng) + { + auto monty_n = std::make_shared<Botan::Montgomery_Params>(n); + + const Botan::Montgomery_Int one(monty_n, monty_n->R1(), false); + + Botan::Montgomery_Int x(monty_n, Botan::BigInt::random_integer(rng, 2, n - 3), false); + Botan::Montgomery_Int y = x; + Botan::Montgomery_Int z = one; + Botan::Montgomery_Int t(monty_n); + Botan::BigInt d; + + Botan::secure_vector<Botan::word> ws; + + size_t i = 1, k = 2; + + while(true) + { + i++; + + if(i >= 0xFFFF0000) // bad seed? too slow? bail out + { + break; + } + + x.square_this(ws); // x = x^2 + x.add(one, ws); + + t = y; + t.sub(x, ws); + + z.mul_by(t, ws); + + if(i == k || i % 128 == 0) + { + d = Botan::gcd(z.value(), n); + z = one; + + if(d == n) + { + // TODO Should rewind here + break; + } + + if(d != 1) + return d; + } + + if(i == k) + { + y = x; + k = 2 * k; + } + } + + // failed + return 0; + } + + // Remove (and return) any small (< 2^16) factors + std::vector<Botan::BigInt> remove_small_factors(Botan::BigInt& n) + { + std::vector<Botan::BigInt> factors; + + while(n.is_even()) + { + factors.push_back(2); + n /= 2; + } + + for(size_t j = 0; j != Botan::PRIME_TABLE_SIZE; j++) + { + uint16_t prime = Botan::PRIMES[j]; + if(n < prime) + { + break; + } + + Botan::BigInt x = Botan::gcd(n, prime); + + if(x != 1) + { + n /= x; + + while(x != 1) + { + x /= prime; + factors.push_back(prime); + } + } + } + + return factors; + } + }; + +BOTAN_REGISTER_COMMAND("factor", Factor); + +} + +#endif diff --git a/comm/third_party/botan/src/cli/pbkdf.cpp b/comm/third_party/botan/src/cli/pbkdf.cpp new file mode 100644 index 0000000000..d17c492203 --- /dev/null +++ b/comm/third_party/botan/src/cli/pbkdf.cpp @@ -0,0 +1,99 @@ +/* +* (C) 2018 Ribose Inc +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_PBKDF) + #include <botan/pwdhash.h> + #include <botan/internal/os_utils.h> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_PBKDF) + +class PBKDF_Tune final : public Command + { + public: + PBKDF_Tune() : Command("pbkdf_tune --algo=Scrypt --max-mem=256 --output-len=32 --check *times") {} + + std::string group() const override + { + return "passhash"; + } + + std::string description() const override + { + return "Tune a PBKDF algo"; + } + + void go() override + { + const size_t output_len = get_arg_sz("output-len"); + const std::string algo = get_arg("algo"); + const size_t max_mem = get_arg_sz("max-mem"); + const bool check_time = flag_set("check"); + + std::unique_ptr<Botan::PasswordHashFamily> pwdhash_fam = + Botan::PasswordHashFamily::create(algo); + + if(!pwdhash_fam) + throw CLI_Error_Unsupported("Password hashing", algo); + + for(const std::string& time : get_arg_list("times")) + { + std::unique_ptr<Botan::PasswordHash> pwhash; + + if(time == "default") + { + pwhash = pwdhash_fam->default_params(); + } + else + { + size_t msec = 0; + try + { + msec = std::stoul(time); + } + catch(std::exception&) + { + throw CLI_Usage_Error("Unknown time value '" + time + "' for pbkdf_tune"); + } + + pwhash = pwdhash_fam->tune(output_len, std::chrono::milliseconds(msec), max_mem); + } + + output() << "For " << time << " ms selected " << pwhash->to_string(); + + if(pwhash->total_memory_usage() > 0) + { + output() << " using " << pwhash->total_memory_usage()/(1024*1024) << " MiB"; + } + + if(check_time) + { + std::vector<uint8_t> outbuf(output_len); + const uint8_t salt[8] = { 0 }; + + const uint64_t start_ns = Botan::OS::get_system_timestamp_ns(); + pwhash->derive_key(outbuf.data(), outbuf.size(), + "test", 4, salt, sizeof(salt)); + const uint64_t end_ns = Botan::OS::get_system_timestamp_ns(); + const uint64_t dur_ns = end_ns - start_ns; + + output() << " took " << (dur_ns / 1000000.0) << " msec to compute"; + } + + output() << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("pbkdf_tune", PBKDF_Tune); + +#endif + +} diff --git a/comm/third_party/botan/src/cli/pk_crypt.cpp b/comm/third_party/botan/src/cli/pk_crypt.cpp new file mode 100644 index 0000000000..111a60129f --- /dev/null +++ b/comm/third_party/botan/src/cli/pk_crypt.cpp @@ -0,0 +1,229 @@ +/* +* (C) 2018 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_AEAD_MODES) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_SHA2_32) && defined(BOTAN_HAS_PEM_CODEC) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + +#include <botan/pubkey.h> +#include <botan/x509_key.h> +#include <botan/pkcs8.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/oids.h> +#include <botan/aead.h> +#include <botan/pem.h> +#include <botan/rng.h> + +namespace Botan_CLI { + +namespace { + +class PK_Encrypt final : public Command + { + public: + PK_Encrypt() : Command("pk_encrypt --aead=AES-256/GCM pubkey datafile") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Encrypt a file using a RSA public key"; + } + + void go() override + { + std::unique_ptr<Botan::Public_Key> key(Botan::X509::load_key(get_arg("pubkey"))); + if(!key) + { + throw CLI_Error("Unable to load public key"); + } + + if(key->algo_name() != "RSA") + { + throw CLI_Usage_Error("This function requires an RSA key"); + } + + const std::string OAEP_HASH = "SHA-256"; + const std::string aead_algo = get_arg("aead"); + + std::unique_ptr<Botan::AEAD_Mode> aead = + Botan::AEAD_Mode::create(aead_algo, Botan::ENCRYPTION); + + if(!aead) + throw CLI_Usage_Error("The AEAD '" + aead_algo + "' is not available"); + + const Botan::OID aead_oid = Botan::OID::from_string(aead_algo); + if(aead_oid.empty()) + throw CLI_Usage_Error("No OID defined for AEAD '" + aead_algo + "'"); + + Botan::secure_vector<uint8_t> data; + auto insert_fn = [&](const uint8_t b[], size_t l) + { + data.insert(data.end(), b, b + l); + }; + this->read_file(get_arg("datafile"), insert_fn); + + const Botan::AlgorithmIdentifier hash_id(OAEP_HASH, Botan::AlgorithmIdentifier::USE_EMPTY_PARAM); + const Botan::AlgorithmIdentifier pk_alg_id("RSA/OAEP", hash_id.BER_encode()); + + Botan::PK_Encryptor_EME enc(*key, rng(), "OAEP(" + OAEP_HASH + ")"); + + const Botan::secure_vector<uint8_t> file_key = rng().random_vec(aead->key_spec().maximum_keylength()); + + const std::vector<uint8_t> encrypted_key = enc.encrypt(file_key, rng()); + + const Botan::secure_vector<uint8_t> nonce = rng().random_vec(aead->default_nonce_length()); + aead->set_key(file_key); + aead->set_associated_data_vec(encrypted_key); + aead->start(nonce); + + aead->finish(data); + + std::vector<uint8_t> buf; + Botan::DER_Encoder der(buf); + + der.start_cons(Botan::SEQUENCE) + .encode(pk_alg_id) + .encode(encrypted_key, Botan::OCTET_STRING) + .encode(aead_oid) + .encode(nonce, Botan::OCTET_STRING) + .encode(data, Botan::OCTET_STRING) + .end_cons(); + + output() << Botan::PEM_Code::encode(buf, "PUBKEY ENCRYPTED MESSAGE", 72); + } + }; + +BOTAN_REGISTER_COMMAND("pk_encrypt", PK_Encrypt); + +class PK_Decrypt final : public Command + { + public: + PK_Decrypt() : Command("pk_decrypt privkey datafile") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Decrypt a file using a RSA private key"; + } + + void go() override + { + Botan::DataSource_Stream input_stream(get_arg("privkey")); + auto get_pass = [this]() { return get_passphrase("Password"); }; + std::unique_ptr<Botan::Private_Key> key = Botan::PKCS8::load_key(input_stream, get_pass); + + if(!key) + { + throw CLI_Error("Unable to load public key"); + } + + if(key->algo_name() != "RSA") + { + throw CLI_Usage_Error("This function requires an RSA key"); + } + + Botan::secure_vector<uint8_t> data; + std::vector<uint8_t> encrypted_key; + std::vector<uint8_t> nonce; + Botan::AlgorithmIdentifier pk_alg_id; + Botan::OID aead_oid; + + try + { + Botan::DataSource_Stream input(get_arg("datafile")); + + Botan::BER_Decoder(Botan::PEM_Code::decode_check_label(input, "PUBKEY ENCRYPTED MESSAGE")) + .start_cons(Botan::SEQUENCE) + .decode(pk_alg_id) + .decode(encrypted_key, Botan::OCTET_STRING) + .decode(aead_oid) + .decode(nonce, Botan::OCTET_STRING) + .decode(data, Botan::OCTET_STRING) + .end_cons(); + } + catch(Botan::Decoding_Error&) + { + error_output() << "Parsing input file failed: invalid format?\n"; + return set_return_code(1); + } + + const std::string aead_algo = Botan::OIDS::oid2str_or_empty(aead_oid); + if(aead_algo == "") + { + error_output() << "Ciphertext was encrypted with an unknown algorithm"; + return set_return_code(1); + } + + if(pk_alg_id.get_oid() != Botan::OID::from_string("RSA/OAEP")) + { + error_output() << "Ciphertext was encrypted with something other than RSA/OAEP"; + return set_return_code(1); + } + + Botan::AlgorithmIdentifier oaep_hash_id; + Botan::BER_Decoder(pk_alg_id.get_parameters()).decode(oaep_hash_id); + + const std::string oaep_hash = Botan::OIDS::oid2str_or_empty(oaep_hash_id.get_oid()); + + if(oaep_hash.empty()) + { + error_output() << "Unknown hash function used with OAEP, OID " << oaep_hash_id.get_oid().to_string() << "\n"; + return set_return_code(1); + } + + if(oaep_hash_id.get_parameters().empty() == false) + { + error_output() << "Unknown OAEP parameters used\n"; + return set_return_code(1); + } + + std::unique_ptr<Botan::AEAD_Mode> aead = + Botan::AEAD_Mode::create_or_throw(aead_algo, Botan::DECRYPTION); + + const size_t expected_keylen = aead->key_spec().maximum_keylength(); + + Botan::PK_Decryptor_EME dec(*key, rng(), "OAEP(" + oaep_hash + ")"); + + const Botan::secure_vector<uint8_t> file_key = + dec.decrypt_or_random(encrypted_key.data(), + encrypted_key.size(), + expected_keylen, + rng()); + + aead->set_key(file_key); + aead->set_associated_data_vec(encrypted_key); + aead->start(nonce); + + try + { + aead->finish(data); + + output().write(reinterpret_cast<const char*>(data.data()), data.size()); + } + catch(Botan::Integrity_Failure&) + { + error_output() << "Message authentication failure, possible ciphertext tampering\n"; + return set_return_code(1); + } + } + }; + +BOTAN_REGISTER_COMMAND("pk_decrypt", PK_Decrypt); + +} + +} + +#endif diff --git a/comm/third_party/botan/src/cli/psk.cpp b/comm/third_party/botan/src/cli/psk.cpp new file mode 100644 index 0000000000..35e38292d9 --- /dev/null +++ b/comm/third_party/botan/src/cli/psk.cpp @@ -0,0 +1,106 @@ +/* +* (C) 2017 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_PSK_DB) && defined(BOTAN_HAS_SQLITE3) + +#include <botan/psk_db.h> +#include <botan/sqlite3.h> +#include <botan/hex.h> + +namespace Botan_CLI { + +class PSK_Tool_Base : public Command + { + public: + PSK_Tool_Base(const std::string& spec) : Command(spec) {} + + std::string group() const override + { + return "psk"; + } + + void go() override + { + const std::string db_filename = get_arg("db"); + const Botan::secure_vector<uint8_t> db_key = Botan::hex_decode_locked(get_passphrase_arg("Database key", "db_key")); + + std::shared_ptr<Botan::SQL_Database> db = std::make_shared<Botan::Sqlite3_Database>(db_filename); + Botan::Encrypted_PSK_Database_SQL psk(db_key, db, "psk"); + + psk_operation(psk); + } + + private: + virtual void psk_operation(Botan::PSK_Database& db) = 0; + }; + +class PSK_Tool_Set final : public PSK_Tool_Base + { + public: + PSK_Tool_Set() : PSK_Tool_Base("psk_set db db_key name psk") {} + + std::string description() const override + { + return "Save a PSK encrypted in the database"; + } + + private: + void psk_operation(Botan::PSK_Database& db) override + { + const std::string name = get_arg("name"); + const Botan::secure_vector<uint8_t> psk = Botan::hex_decode_locked(get_passphrase_arg("PSK", "psk")); + db.set_vec(name, psk); + } + }; + +class PSK_Tool_Get final : public PSK_Tool_Base + { + public: + PSK_Tool_Get() : PSK_Tool_Base("psk_get db db_key name") {} + + std::string description() const override + { + return "Read a value saved with psk_set"; + } + + private: + void psk_operation(Botan::PSK_Database& db) override + { + const std::string name = get_arg("name"); + const Botan::secure_vector<uint8_t> val = db.get(name); + output() << Botan::hex_encode(val) << "\n"; + } + }; + +class PSK_Tool_List final : public PSK_Tool_Base + { + public: + PSK_Tool_List() : PSK_Tool_Base("psk_list db db_key") {} + + std::string description() const override + { + return "List all values saved to the database"; + } + + private: + void psk_operation(Botan::PSK_Database& db) override + { + const std::set<std::string> names = db.list_names(); + + for(std::string name : names) + output() << name << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("psk_set", PSK_Tool_Set); +BOTAN_REGISTER_COMMAND("psk_get", PSK_Tool_Get); +BOTAN_REGISTER_COMMAND("psk_list", PSK_Tool_List); + +} + +#endif diff --git a/comm/third_party/botan/src/cli/pubkey.cpp b/comm/third_party/botan/src/cli/pubkey.cpp new file mode 100644 index 0000000000..7c7e1bfc0d --- /dev/null +++ b/comm/third_party/botan/src/cli/pubkey.cpp @@ -0,0 +1,554 @@ +/* +* (C) 2010,2014,2015,2019 Jack Lloyd +* (C) 2019 Matthias Gierlings +* (C) 2015 René Korthaus +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) + +#include <botan/base64.h> +#include <botan/hex.h> +#include <botan/rng.h> + +#include <botan/pk_keys.h> +#include <botan/x509_key.h> +#include <botan/pk_algs.h> +#include <botan/pkcs8.h> +#include <botan/pubkey.h> +#include <botan/workfactor.h> +#include <botan/data_src.h> + +#include <fstream> + +#if defined(BOTAN_HAS_DL_GROUP) + #include <botan/dl_group.h> +#endif + +#if defined(BOTAN_HAS_ECC_GROUP) + #include <botan/ec_group.h> +#endif + +namespace Botan_CLI { + +class PK_Keygen final : public Command + { + public: + PK_Keygen() : Command("keygen --algo=RSA --params= --passphrase= --pbe= --pbe-millis=300 --provider= --der-out") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Generate a PKCS #8 private key"; + } + + void go() override + { + const std::string algo = get_arg("algo"); + const std::string params = get_arg("params"); + const std::string provider = get_arg("provider"); + + std::unique_ptr<Botan::Private_Key> key = + Botan::create_private_key(algo, rng(), params, provider); + + if(!key) + { + throw CLI_Error_Unsupported("keygen", algo); + } + + const std::string pass = get_passphrase_arg("Key passphrase", "passphrase"); + const bool der_out = flag_set("der-out"); + + const std::chrono::milliseconds pbe_millis(get_arg_sz("pbe-millis")); + const std::string pbe = get_arg("pbe"); + + if(der_out) + { + if(pass.empty()) + { + write_output(Botan::PKCS8::BER_encode(*key)); + } + else + { + write_output(Botan::PKCS8::BER_encode(*key, rng(), pass, pbe_millis, pbe)); + } + } + else + { + if(pass.empty()) + { + output() << Botan::PKCS8::PEM_encode(*key); + } + else + { + output() << Botan::PKCS8::PEM_encode(*key, rng(), pass, pbe_millis, pbe); + } + } + } + }; + +BOTAN_REGISTER_COMMAND("keygen", PK_Keygen); + +#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + +namespace { + +std::string choose_sig_padding(const std::string& key, const std::string& emsa, const std::string& hash) + { + std::string emsa_or_default = [&]() -> std::string + { + if(!emsa.empty()) + { + return emsa; + } + + if(key == "RSA") + { + return "EMSA4"; + } // PSS + else if(key == "ECDSA" || key == "DSA") + { + return "EMSA1"; + } + else if(key == "Ed25519") + { + return ""; + } + else + { + return "EMSA1"; + } + }(); + + if(emsa_or_default.empty()) + { + return hash; + } + + return emsa_or_default + "(" + hash + ")"; + } + +} + +class PK_Fingerprint final : public Command + { + public: + PK_Fingerprint() : Command("fingerprint --no-fsname --algo=SHA-256 *keys") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Calculate a public key fingerprint"; + } + + void go() override + { + const std::string hash_algo = get_arg("algo"); + const bool no_fsname = flag_set("no-fsname"); + + for(std::string key_file : get_arg_list("keys")) + { + std::unique_ptr<Botan::Public_Key> key( + key_file == "-" + ? Botan::X509::load_key(this->slurp_file("-", 4096)) + : Botan::X509::load_key(key_file)); + + const std::string fprint = key->fingerprint_public(hash_algo); + + if(no_fsname || key_file == "-") + { output() << fprint << "\n"; } + else + { output() << key_file << ": " << fprint << "\n"; } + } + } + }; + +BOTAN_REGISTER_COMMAND("fingerprint", PK_Fingerprint); + +class PK_Sign final : public Command + { + public: + PK_Sign() : Command("sign --der-format --passphrase= --hash=SHA-256 --emsa= --provider= key file") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Sign arbitrary data"; + } + + void go() override + { + const std::string key_file = get_arg("key"); + const std::string passphrase = get_passphrase_arg("Passphrase for " + key_file, "passphrase"); + + Botan::DataSource_Stream input(key_file); + std::unique_ptr<Botan::Private_Key> key = Botan::PKCS8::load_key(input, passphrase); + + if(!key) + { + throw CLI_Error("Unable to load private key"); + } + + const std::string sig_padding = + choose_sig_padding(key->algo_name(), get_arg("emsa"), get_arg("hash")); + + const Botan::Signature_Format format = + flag_set("der-format") ? Botan::DER_SEQUENCE : Botan::IEEE_1363; + + const std::string provider = get_arg("provider"); + + Botan::PK_Signer signer(*key, rng(), sig_padding, format, provider); + + auto onData = [&signer](const uint8_t b[], size_t l) + { + signer.update(b, l); + }; + this->read_file(get_arg("file"), onData); + + std::vector<uint8_t> sig { signer.signature(rng()) }; + + if(key->stateful_operation()) + { + std::ofstream updated_key(key_file); + if(passphrase.empty()) + { updated_key << Botan::PKCS8::PEM_encode(*key); } + else + { updated_key << Botan::PKCS8::PEM_encode(*key, rng(), passphrase); } + } + + output() << Botan::base64_encode(sig) << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("sign", PK_Sign); + +class PK_Verify final : public Command + { + public: + PK_Verify() : Command("verify --der-format --hash=SHA-256 --emsa= pubkey file signature") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Verify the authenticity of the given file with the provided signature"; + } + + void go() override + { + std::unique_ptr<Botan::Public_Key> key(Botan::X509::load_key(get_arg("pubkey"))); + if(!key) + { + throw CLI_Error("Unable to load public key"); + } + + const std::string sig_padding = + choose_sig_padding(key->algo_name(), get_arg("emsa"), get_arg("hash")); + + const Botan::Signature_Format format = + flag_set("der-format") ? Botan::DER_SEQUENCE : Botan::IEEE_1363; + + Botan::PK_Verifier verifier(*key, sig_padding, format); + auto onData = [&verifier](const uint8_t b[], size_t l) + { + verifier.update(b, l); + }; + this->read_file(get_arg("file"), onData); + + const Botan::secure_vector<uint8_t> signature = + Botan::base64_decode(this->slurp_file_as_str(get_arg("signature"))); + + const bool valid = verifier.check_signature(signature); + + output() << "Signature is " << (valid ? "valid" : "invalid") << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("verify", PK_Verify); + +class PKCS8_Tool final : public Command + { + public: + PKCS8_Tool() : Command("pkcs8 --pass-in= --pub-out --der-out --pass-out= --pbe= --pbe-millis=300 key") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Open a PKCS #8 formatted key"; + } + + void go() override + { + const std::string key_file = get_arg("key"); + const std::string pass_in = get_passphrase_arg("Password for " + key_file, "pass-in"); + + Botan::DataSource_Memory key_src(slurp_file(key_file)); + std::unique_ptr<Botan::Private_Key> key; + + if(pass_in.empty()) + { + key.reset(Botan::PKCS8::load_key(key_src, rng())); + } + else + { + key.reset(Botan::PKCS8::load_key(key_src, rng(), pass_in)); + } + + const std::chrono::milliseconds pbe_millis(get_arg_sz("pbe-millis")); + const std::string pbe = get_arg("pbe"); + const bool der_out = flag_set("der-out"); + + if(flag_set("pub-out")) + { + if(der_out) + { + write_output(Botan::X509::BER_encode(*key)); + } + else + { + output() << Botan::X509::PEM_encode(*key); + } + } + else + { + const std::string pass_out = get_passphrase_arg("Passphrase to encrypt key", "pass-out"); + + if(der_out) + { + if(pass_out.empty()) + { + write_output(Botan::PKCS8::BER_encode(*key)); + } + else + { + write_output(Botan::PKCS8::BER_encode(*key, rng(), pass_out, pbe_millis, pbe)); + } + } + else + { + if(pass_out.empty()) + { + output() << Botan::PKCS8::PEM_encode(*key); + } + else + { + output() << Botan::PKCS8::PEM_encode(*key, rng(), pass_out, pbe_millis, pbe); + } + } + } + } + }; + +BOTAN_REGISTER_COMMAND("pkcs8", PKCS8_Tool); + +#endif + +#if defined(BOTAN_HAS_ECC_GROUP) + +class EC_Group_Info final : public Command + { + public: + EC_Group_Info() : Command("ec_group_info --pem name") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Print raw elliptic curve domain parameters of the standardized curve name"; + } + + void go() override + { + Botan::EC_Group ec_group(get_arg("name")); + + if(flag_set("pem")) + { + output() << ec_group.PEM_encode(); + } + else + { + output() << "P = " << std::hex << ec_group.get_p() << "\n" + << "A = " << std::hex << ec_group.get_a() << "\n" + << "B = " << std::hex << ec_group.get_b() << "\n" + << "N = " << std::hex << ec_group.get_order() << "\n" + << "G = " << ec_group.get_g_x() << "," << ec_group.get_g_y() << "\n"; + } + + } + }; + +BOTAN_REGISTER_COMMAND("ec_group_info", EC_Group_Info); + +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + +class DL_Group_Info final : public Command + { + public: + DL_Group_Info() : Command("dl_group_info --pem name") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Print raw Diffie-Hellman parameters (p,g) of the standardized DH group name"; + } + + void go() override + { + Botan::DL_Group dl_group(get_arg("name")); + + if(flag_set("pem")) + { + output() << dl_group.PEM_encode(Botan::DL_Group::ANSI_X9_42_DH_PARAMETERS); + } + else + { + output() << "P = " << std::hex << dl_group.get_p() << "\n" + << "G = " << dl_group.get_g() << "\n"; + } + + } + }; + +BOTAN_REGISTER_COMMAND("dl_group_info", DL_Group_Info); + +class PK_Workfactor final : public Command + { + public: + PK_Workfactor() : Command("pk_workfactor --type=rsa bits") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Provide estimate of strength of public key based on size"; + } + + void go() override + { + const size_t bits = get_arg_sz("bits"); + const std::string type = get_arg("type"); + + if(type == "rsa") + { output() << Botan::if_work_factor(bits) << "\n"; } + else if(type == "dl") + { output() << Botan::dl_work_factor(bits) << "\n"; } + else if(type == "dl_exp") + { output() << Botan::dl_exponent_size(bits) << "\n"; } + else + { throw CLI_Usage_Error("Unknown type for pk_workfactor"); } + } + }; + +BOTAN_REGISTER_COMMAND("pk_workfactor", PK_Workfactor); + +class Gen_DL_Group final : public Command + { + public: + Gen_DL_Group() : Command("gen_dl_group --pbits=1024 --qbits=0 --seed= --type=subgroup") {} + + std::string group() const override + { + return "pubkey"; + } + + std::string description() const override + { + return "Generate ANSI X9.42 encoded Diffie-Hellman group parameters"; + } + + void go() override + { + const size_t pbits = get_arg_sz("pbits"); + const size_t qbits = get_arg_sz("qbits"); + + const std::string type = get_arg("type"); + const std::string seed_str = get_arg("seed"); + + if(type == "strong") + { + if(seed_str.size() > 0) + { throw CLI_Usage_Error("Seed only supported for DSA param gen"); } + Botan::DL_Group grp(rng(), Botan::DL_Group::Strong, pbits); + output() << grp.PEM_encode(Botan::DL_Group::ANSI_X9_42); + } + else if(type == "subgroup") + { + if(seed_str.size() > 0) + { throw CLI_Usage_Error("Seed only supported for DSA param gen"); } + Botan::DL_Group grp(rng(), Botan::DL_Group::Prime_Subgroup, pbits, qbits); + output() << grp.PEM_encode(Botan::DL_Group::ANSI_X9_42); + } + else if(type == "dsa") + { + size_t dsa_qbits = qbits; + if(dsa_qbits == 0) + { + if(pbits == 1024) + { dsa_qbits = 160; } + else if(pbits == 2048 || pbits == 3072) + { dsa_qbits = 256; } + else + { throw CLI_Usage_Error("Invalid DSA p/q sizes"); } + } + + if(seed_str.empty()) + { + Botan::DL_Group grp(rng(), Botan::DL_Group::DSA_Kosherizer, pbits, dsa_qbits); + output() << grp.PEM_encode(Botan::DL_Group::ANSI_X9_42); + } + else + { + const std::vector<uint8_t> seed = Botan::hex_decode(seed_str); + Botan::DL_Group grp(rng(), seed, pbits, dsa_qbits); + output() << grp.PEM_encode(Botan::DL_Group::ANSI_X9_42); + } + + } + else + { + throw CLI_Usage_Error("Invalid DL type '" + type + "'"); + } + } + }; + +BOTAN_REGISTER_COMMAND("gen_dl_group", Gen_DL_Group); + +#endif + +} + +#endif diff --git a/comm/third_party/botan/src/cli/roughtime.cpp b/comm/third_party/botan/src/cli/roughtime.cpp new file mode 100644 index 0000000000..ff38fe1c43 --- /dev/null +++ b/comm/third_party/botan/src/cli/roughtime.cpp @@ -0,0 +1,215 @@ +/* +* Roughtime +* (C) 2019 Nuno Goncalves <nunojpg@gmail.com> +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_ROUGHTIME) + +#include <botan/roughtime.h> +#include <botan/hex.h> +#include <botan/rng.h> +#include <botan/base64.h> +#include <botan/ed25519.h> +#include <botan/hash.h> +#include <botan/calendar.h> + +#include <iomanip> +#include <fstream> + +namespace Botan_CLI { + +class RoughtimeCheck final : public Command + { + public: + RoughtimeCheck() : Command("roughtime_check --raw-time chain-file") {} + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Parse and validate Roughtime chain file"; + } + + void go() override + { + const auto chain = Botan::Roughtime::Chain(slurp_file_as_str(get_arg("chain-file"))); + unsigned i = 0; + for(const auto& response : chain.responses()) + { + output() << std::setw(3) << ++i << ": UTC "; + if(flag_set("raw-time")) + { output() << Botan::Roughtime::Response::sys_microseconds64(response.utc_midpoint()).time_since_epoch().count(); } + else + { output() << Botan::calendar_value(response.utc_midpoint()).to_string(); } + output() << " (+-" << Botan::Roughtime::Response::microseconds32(response.utc_radius()).count() << "us)\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("roughtime_check", RoughtimeCheck); + +class Roughtime final : public Command + { + public: + Roughtime() : + Command("roughtime --raw-time --chain-file=roughtime-chain --max-chain-size=128 --check-local-clock=60 --host= --pubkey= --servers-file=") {} + + std::string help_text() const override + { + return Command::help_text() + R"( + +--servers-file=<filename> + List of servers that will queried in sequence. + + File contents syntax: + <name> <key type> <base 64 encoded public key> <protocol> <host:port> + + Example servers: + Cloudflare-Roughtime ed25519 gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo= udp roughtime.cloudflare.com:2002 + Google-Sandbox-Roughtime ed25519 etPaaIxcBMY1oUeGpwvPMCJMwlRVNxv51KK/tktoJTQ= udp roughtime.sandbox.google.com:2002 + +--chain-file=<filename> + Succesfull queries are appended to this file. + If limit of --max-chain-size records is reached, the oldest records are truncated. + This queries records can be replayed using command roughtime_check <chain-file>. + + File contents syntax: + <key type> <base 64 encoded public key> <base 64 encoded blind or nonce> <base 64 encoded server response> +)"; + } + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Retrieve time from Roughtime server"; + } + + void query(std::unique_ptr<Botan::Roughtime::Chain>& chain, + const size_t max_chain_size, + const std::string& address, + const Botan::Ed25519_PublicKey& public_key) + { + Botan::Roughtime::Nonce nonce; + Botan::Roughtime::Nonce blind; + if(chain) + { + blind = Botan::Roughtime::Nonce(rng()); + nonce = chain->next_nonce(blind); + } + else + { + nonce = Botan::Roughtime::Nonce(rng()); + } + const auto response_raw = Botan::Roughtime::online_request(address, nonce, std::chrono::seconds(5)); + const auto response = Botan::Roughtime::Response::from_bits(response_raw, nonce); + if(flag_set("raw-time")) + { output() << "UTC " << Botan::Roughtime::Response::sys_microseconds64(response.utc_midpoint()).time_since_epoch().count(); } + else + { output() << "UTC " << Botan::calendar_value(response.utc_midpoint()).to_string(); } + output() << " (+-" << Botan::Roughtime::Response::microseconds32(response.utc_radius()).count() << "us)"; + if(!response.validate(public_key)) + { + error_output() << "ERROR: Public key does not match!\n"; + set_return_code(1); + return; + } + const auto tolerance = get_arg_sz("check-local-clock"); + if(tolerance) + { + const auto now = std::chrono::system_clock::now(); + const auto diff_abs = now >= response.utc_midpoint() ? now - response.utc_midpoint() : response.utc_midpoint() - now; + if(diff_abs > (response.utc_radius() + std::chrono::seconds(tolerance))) + { + error_output() << "ERROR: Local clock mismatch\n"; + set_return_code(1); + return; + } + output() << " Local clock match"; + } + if(chain) + chain->append({response_raw, public_key, blind}, max_chain_size); + output() << '\n'; + } + + void go() override + { + + const auto max_chain_size = get_arg_sz("max-chain-size"); + const auto chain_file = get_arg("chain-file"); + const auto servers_file = get_arg_or("servers-file", ""); + const auto host = get_arg_or("host", ""); + const auto pk = get_arg_or("pubkey", ""); + + std::unique_ptr<Botan::Roughtime::Chain> chain; + if(!chain_file.empty() && max_chain_size >= 1) + { + try + { + chain.reset(new Botan::Roughtime::Chain(slurp_file_as_str(chain_file))); + } + catch(const CLI_IO_Error&) + { + chain.reset(new Botan::Roughtime::Chain()); //file is to still be created + } + } + + const bool from_servers_file = !servers_file.empty(); + const bool from_host_and_pk = !host.empty() && !pk.empty(); + if(from_servers_file == from_host_and_pk) + { + error_output() << "Please specify either --servers-file or --host and --pubkey\n"; + set_return_code(1); + return; + } + + if(!servers_file.empty()) + { + const auto servers = Botan::Roughtime::servers_from_str(slurp_file_as_str(servers_file)); + + for(const auto& s : servers) + { + output() << std::setw(25) << std::left << s.name() << ": "; + for(const auto& a : s.addresses()) + { + try + { + query(chain, max_chain_size, a, s.public_key()); + break; + } + catch(const std::exception& ex) //network error, try next address + { + error_output() << ex.what() << '\n'; + } + } + } + + } + else + { + query(chain, max_chain_size, host, Botan::Ed25519_PublicKey(Botan::base64_decode(pk))); + } + + if(chain) + { + std::ofstream out(chain_file); + out << chain->to_string(); + } + } + }; + +BOTAN_REGISTER_COMMAND("roughtime", Roughtime); + +} + +#endif diff --git a/comm/third_party/botan/src/cli/sandbox.cpp b/comm/third_party/botan/src/cli/sandbox.cpp new file mode 100644 index 0000000000..6ac8007af2 --- /dev/null +++ b/comm/third_party/botan/src/cli/sandbox.cpp @@ -0,0 +1,115 @@ +/* +* (C) 2019 David Carlier <devnexen@gmail.com> +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "sandbox.h" +#include <botan/mem_ops.h> + +#if defined(BOTAN_TARGET_OS_HAS_PLEDGE) + #include <unistd.h> +#elif defined(BOTAN_TARGET_OS_HAS_CAP_ENTER) + #include <sys/capsicum.h> + #include <unistd.h> +#elif defined(BOTAN_TARGET_OS_HAS_SETPPRIV) + #include <priv.h> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_TARGET_OS_HAS_SETPPRIV) +struct SandboxPrivDelete { + void operator()(priv_set_t *ps) + { + ::priv_emptyset(ps); + ::priv_freeset(ps); + } +}; +#endif + +Sandbox::Sandbox() + { +#if defined(BOTAN_TARGET_OS_HAS_PLEDGE) + m_name = "pledge"; +#elif defined(BOTAN_TARGET_OS_HAS_CAP_ENTER) + m_name = "capsicum"; +#elif defined(BOTAN_TARGET_OS_HAS_SETPPRIV) + m_name = "privilege"; +#else + m_name = "<none>"; +#endif + } + +bool Sandbox::init() + { + Botan::initialize_allocator(); + +#if defined(BOTAN_TARGET_OS_HAS_PLEDGE) + const static char *opts = "stdio rpath inet error"; + return (::pledge(opts, nullptr) == 0); +#elif defined(BOTAN_TARGET_OS_HAS_CAP_ENTER) + cap_rights_t wt, rd; + + if (::cap_rights_init(&wt, CAP_READ, CAP_WRITE) == nullptr) + { + return false; + } + + if (::cap_rights_init(&rd, CAP_FCNTL, CAP_EVENT, CAP_READ) == nullptr) + { + return false; + } + + if (::cap_rights_limit(STDOUT_FILENO, &wt) == -1) + { + return false; + } + + if (::cap_rights_limit(STDERR_FILENO, &wt) == -1) + { + return false; + } + + if (::cap_rights_limit(STDIN_FILENO, &rd) == -1) + { + return false; + } + + return (::cap_enter() == 0); +#elif defined(BOTAN_TARGET_OS_HAS_SETPPRIV) + priv_set_t *tmp; + std::unique_ptr<priv_set_t, SandboxPrivDelete> ps; + const char *const priv_perms[] = { + PRIV_PROC_FORK, + PRIV_PROC_EXEC, + PRIV_PROC_INFO, + PRIV_PROC_SESSION, + }; + + if ((tmp = ::priv_allocset()) == nullptr) + { + return false; + } + + ps = std::unique_ptr<priv_set_t, SandboxPrivDelete>(tmp); + ::priv_basicset(ps.get()); + + for (auto perm: priv_perms) + { + if (::priv_delset(ps.get(), perm) == -1) + { + return false; + } + } + + return true; +#else + return true; +#endif + } + +Sandbox::~Sandbox() + { + } +} diff --git a/comm/third_party/botan/src/cli/sandbox.h b/comm/third_party/botan/src/cli/sandbox.h new file mode 100644 index 0000000000..2d6f5c4df8 --- /dev/null +++ b/comm/third_party/botan/src/cli/sandbox.h @@ -0,0 +1,32 @@ +/* +* (C) 2019 David Carlier <devnexen@gmail.com> +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CLI_SANDBOX_H_ +#define BOTAN_CLI_SANDBOX_H_ + +#include <string> + +namespace Botan_CLI { + +class Sandbox + { + public: + explicit Sandbox(); + virtual ~Sandbox(); + + bool init(); + + const std::string& name() const + { + return m_name; + } + + private: + std::string m_name; + }; +} + +#endif diff --git a/comm/third_party/botan/src/cli/socket_utils.h b/comm/third_party/botan/src/cli/socket_utils.h new file mode 100644 index 0000000000..d52b5a0e7c --- /dev/null +++ b/comm/third_party/botan/src/cli/socket_utils.h @@ -0,0 +1,105 @@ +/* +* (C) 2014,2017 Jack Lloyd +* 2017 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CLI_SOCKET_UTILS_H_ +#define BOTAN_CLI_SOCKET_UTILS_H_ + +#include <botan/build.h> +#include "cli_exceptions.h" + +#if defined(BOTAN_TARGET_OS_HAS_WINSOCK2) + +#include <winsock2.h> +#include <WS2tcpip.h> + +typedef SOCKET socket_type; + +inline socket_type invalid_socket() { return INVALID_SOCKET; } + +typedef size_t ssize_t; +typedef int sendrecv_len_type; + +inline void close_socket(socket_type s) { ::closesocket(s); } + +#define STDIN_FILENO _fileno(stdin) + +inline void init_sockets() + { + WSAData wsa_data; + WORD wsa_version = MAKEWORD(2, 2); + + if(::WSAStartup(wsa_version, &wsa_data) != 0) + { + throw Botan_CLI::CLI_Error("WSAStartup() failed: " + std::to_string(WSAGetLastError())); + } + + if(LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2) + { + ::WSACleanup(); + throw Botan_CLI::CLI_Error("Could not find a usable version of Winsock.dll"); + } + } + +inline void stop_sockets() + { + ::WSACleanup(); + } + +inline std::string err_to_string(int e) + { + // TODO use strerror_s here + return "Error code " + std::to_string(e); + } + +inline int close(int fd) + { + return ::closesocket(fd); + } + +inline int read(int s, void* buf, size_t len) + { + return ::recv(s, reinterpret_cast<char*>(buf), static_cast<int>(len), 0); + } + +inline int send(int s, const uint8_t* buf, size_t len, int flags) + { + return ::send(s, reinterpret_cast<const char*>(buf), static_cast<int>(len), flags); + } + +#elif defined(BOTAN_TARGET_OS_HAS_POSIX1) + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <netdb.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +typedef int socket_type; +typedef size_t sendrecv_len_type; + +inline socket_type invalid_socket() { return -1; } +inline void close_socket(socket_type s) { ::close(s); } + +inline void init_sockets() {} +inline void stop_sockets() {} + +inline std::string err_to_string(int e) + { + return std::strerror(e); + } + +#endif + +#if !defined(MSG_NOSIGNAL) + #define MSG_NOSIGNAL 0 +#endif + +#endif diff --git a/comm/third_party/botan/src/cli/speed.cpp b/comm/third_party/botan/src/cli/speed.cpp new file mode 100644 index 0000000000..b8454d2a7a --- /dev/null +++ b/comm/third_party/botan/src/cli/speed.cpp @@ -0,0 +1,2342 @@ +/* +* (C) 2009,2010,2014,2015,2017,2018 Jack Lloyd +* (C) 2015 Simon Warta (Kullo GmbH) +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include "../tests/test_rng.h" // FIXME + +#include <sstream> +#include <iomanip> +#include <chrono> +#include <functional> +#include <algorithm> +#include <map> +#include <set> + +// Always available: +#include <botan/entropy_src.h> +#include <botan/parsing.h> +#include <botan/cpuid.h> +#include <botan/internal/os_utils.h> +#include <botan/internal/timer.h> +#include <botan/version.h> + +#if defined(BOTAN_HAS_BIGINT) + #include <botan/bigint.h> + #include <botan/divide.h> +#endif + +#if defined(BOTAN_HAS_BLOCK_CIPHER) + #include <botan/block_cipher.h> +#endif + +#if defined(BOTAN_HAS_STREAM_CIPHER) + #include <botan/stream_cipher.h> +#endif + +#if defined(BOTAN_HAS_HASH) + #include <botan/hash.h> +#endif + +#if defined(BOTAN_HAS_CIPHER_MODES) + #include <botan/cipher_mode.h> +#endif + +#if defined(BOTAN_HAS_MAC) + #include <botan/mac.h> +#endif + +#if defined(BOTAN_HAS_AUTO_SEEDING_RNG) + #include <botan/auto_rng.h> +#endif + +#if defined(BOTAN_HAS_SYSTEM_RNG) + #include <botan/system_rng.h> +#endif + +#if defined(BOTAN_HAS_HMAC_DRBG) + #include <botan/hmac_drbg.h> +#endif + +#if defined(BOTAN_HAS_PROCESSOR_RNG) + #include <botan/processor_rng.h> +#endif + +#if defined(BOTAN_HAS_CHACHA_RNG) + #include <botan/chacha_rng.h> +#endif + +#if defined(BOTAN_HAS_FPE_FE1) + #include <botan/fpe_fe1.h> +#endif + +#if defined(BOTAN_HAS_RFC3394_KEYWRAP) + #include <botan/rfc3394.h> +#endif + +#if defined(BOTAN_HAS_COMPRESSION) + #include <botan/compression.h> +#endif + +#if defined(BOTAN_HAS_POLY_DBL) + #include <botan/internal/poly_dbl.h> +#endif + +#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) + #include <botan/pkcs8.h> + #include <botan/pubkey.h> + #include <botan/pk_algs.h> + #include <botan/x509_key.h> + #include <botan/workfactor.h> +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + #include <botan/numthry.h> + #include <botan/reducer.h> + #include <botan/curve_nistp.h> + #include <botan/internal/primality.h> +#endif + +#if defined(BOTAN_HAS_ECC_GROUP) + #include <botan/ec_group.h> +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + #include <botan/dl_group.h> +#endif + +#if defined(BOTAN_HAS_MCELIECE) + #include <botan/mceliece.h> +#endif + +#if defined(BOTAN_HAS_ECDSA) + #include <botan/ecdsa.h> +#endif + +#if defined(BOTAN_HAS_NEWHOPE) + #include <botan/newhope.h> +#endif + +#if defined(BOTAN_HAS_SCRYPT) + #include <botan/scrypt.h> +#endif + +#if defined(BOTAN_HAS_ARGON2) + #include <botan/argon2.h> +#endif + +#if defined(BOTAN_HAS_BCRYPT) + #include <botan/bcrypt.h> +#endif + +#if defined(BOTAN_HAS_PASSHASH9) + #include <botan/passhash9.h> +#endif + +namespace Botan_CLI { + +using Botan::Timer; + +namespace { + +class JSON_Output final + { + public: + void add(const Timer& timer) { m_results.push_back(timer); } + + std::string print() const + { + std::ostringstream out; + + out << "[\n"; + + for(size_t i = 0; i != m_results.size(); ++i) + { + if(i != 0) + out << ","; + + const Timer& t = m_results[i]; + + out << '{'; + out << "\"algo\": \"" << t.get_name() << "\", "; + out << "\"op\": \"" << t.doing() << "\", "; + + out << "\"events\": " << t.events() << ", "; + if(t.cycles_consumed() > 0) + out << "\"cycles\": " << t.cycles_consumed() << ", "; + if(t.buf_size() > 0) + { + out << "\"bps\": " << static_cast<uint64_t>(t.events() / (t.value() / 1000000000.0)) << ", "; + out << "\"buf_size\": " << t.buf_size() << ", "; + } + + out << "\"nanos\": " << t.value(); + + out << "}\n"; + } + out << "]\n"; + + return out.str(); + } + private: + std::vector<Timer> m_results; + }; + +class Summary final + { + public: + Summary() {} + + void add(const Timer& t) + { + if(t.buf_size() == 0) + { + m_ops_entries.push_back(t); + } + else + { + m_bps_entries[std::make_pair(t.doing(), t.get_name())].push_back(t); + } + } + + std::string print() + { + const size_t name_padding = 35; + const size_t op_name_padding = 16; + const size_t op_padding = 16; + + std::ostringstream result_ss; + result_ss << std::fixed; + + if(m_bps_entries.size() > 0) + { + result_ss << "\n"; + + // add table header + result_ss << std::setw(name_padding) << std::left << "algo" + << std::setw(op_name_padding) << std::left << "operation"; + + for(const Timer& t : m_bps_entries.begin()->second) + { + result_ss << std::setw(op_padding) << std::right << (std::to_string(t.buf_size()) + " bytes"); + } + result_ss << "\n"; + + // add table entries + for(const auto& entry : m_bps_entries) + { + if(entry.second.empty()) + continue; + + result_ss << std::setw(name_padding) << std::left << (entry.first.second) + << std::setw(op_name_padding) << std::left << (entry.first.first); + + for(const Timer& t : entry.second) + { + + if(t.events() == 0) + { + result_ss << std::setw(op_padding) << std::right << "N/A"; + } + else + { + result_ss << std::setw(op_padding) << std::right + << std::setprecision(2) << (t.bytes_per_second() / 1000.0); + } + } + + result_ss << "\n"; + } + + result_ss << "\n[results are the number of 1000s bytes processed per second]\n"; + } + + if(m_ops_entries.size() > 0) + { + result_ss << std::setprecision(6) << "\n"; + + // sort entries + std::sort(m_ops_entries.begin(), m_ops_entries.end()); + + // add table header + result_ss << std::setw(name_padding) << std::left << "algo" + << std::setw(op_name_padding) << std::left << "operation" + << std::setw(op_padding) << std::right << "sec/op" + << std::setw(op_padding) << std::right << "op/sec" + << "\n"; + + // add table entries + for(const Timer& entry : m_ops_entries) + { + result_ss << std::setw(name_padding) << std::left << entry.get_name() + << std::setw(op_name_padding) << std::left << entry.doing() + << std::setw(op_padding) << std::right << entry.seconds_per_event() + << std::setw(op_padding) << std::right << entry.events_per_second() + << "\n"; + } + } + + return result_ss.str(); + } + + private: + std::map<std::pair<std::string, std::string>, std::vector<Timer>> m_bps_entries; + std::vector<Timer> m_ops_entries; + }; + +std::vector<size_t> unique_buffer_sizes(const std::string& cmdline_arg) + { + const size_t MAX_BUF_SIZE = 64*1024*1024; + + std::set<size_t> buf; + for(std::string size_str : Botan::split_on(cmdline_arg, ',')) + { + size_t x = 0; + try + { + size_t converted = 0; + x = static_cast<size_t>(std::stoul(size_str, &converted, 0)); + + if(converted != size_str.size()) + throw CLI_Usage_Error("Invalid integer"); + } + catch(std::exception&) + { + throw CLI_Usage_Error("Invalid integer value '" + size_str + "' for option buf-size"); + } + + if(x == 0) + throw CLI_Usage_Error("Cannot have a zero-sized buffer"); + + if(x > MAX_BUF_SIZE) + throw CLI_Usage_Error("Specified buffer size is too large"); + + buf.insert(x); + } + + return std::vector<size_t>(buf.begin(), buf.end()); + } + +} + +class Speed final : public Command + { + public: + Speed() + : Command("speed --msec=500 --format=default --ecc-groups= --provider= --buf-size=1024 --clear-cpuid= --cpu-clock-speed=0 --cpu-clock-ratio=1.0 *algos") {} + + std::vector<std::string> default_benchmark_list() + { + /* + This is not intended to be exhaustive: it just hits the high + points of the most interesting or widely used algorithms. + */ + + return { + /* Block ciphers */ + "AES-128", + "AES-192", + "AES-256", + "ARIA-128", + "ARIA-192", + "ARIA-256", + "Blowfish", + "CAST-128", + "CAST-256", + "Camellia-128", + "Camellia-192", + "Camellia-256", + "DES", + "TripleDES", + "GOST-28147-89", + "IDEA", + "KASUMI", + "MISTY1", + "Noekeon", + "SHACAL2", + "SM4", + "Serpent", + "Threefish-512", + "Twofish", + "XTEA", + + /* Cipher modes */ + "AES-128/CBC", + "AES-128/CTR-BE", + "AES-128/EAX", + "AES-128/OCB", + "AES-128/GCM", + "AES-128/XTS", + "AES-128/SIV", + + "Serpent/CBC", + "Serpent/CTR-BE", + "Serpent/EAX", + "Serpent/OCB", + "Serpent/GCM", + "Serpent/XTS", + "Serpent/SIV", + + "ChaCha20Poly1305", + + /* Stream ciphers */ + "RC4", + "Salsa20", + "ChaCha20", + + /* Hashes */ + "SHA-160", + "SHA-256", + "SHA-512", + "SHA-3(256)", + "SHA-3(512)", + "RIPEMD-160", + "Skein-512", + "Blake2b", + "Tiger", + "Whirlpool", + + /* MACs */ + "CMAC(AES-128)", + "HMAC(SHA-256)", + + /* pubkey */ + "RSA", + "DH", + "ECDH", + "ECDSA", + "Ed25519", + "Curve25519", + "NEWHOPE", + "McEliece", + }; + } + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Measures the speed of algorithms"; + } + + void go() override + { + std::chrono::milliseconds msec(get_arg_sz("msec")); + const std::string provider = get_arg("provider"); + std::vector<std::string> ecc_groups = Botan::split_on(get_arg("ecc-groups"), ','); + const std::string format = get_arg("format"); + const std::string clock_ratio = get_arg("cpu-clock-ratio"); + m_clock_speed = get_arg_sz("cpu-clock-speed"); + + m_clock_cycle_ratio = std::strtod(clock_ratio.c_str(), nullptr); + + /* + * This argument is intended to be the ratio between the cycle counter + * and the actual machine cycles. It is extremely unlikely that there is + * any machine where the cycle counter increments faster than the actual + * clock. + */ + if(m_clock_cycle_ratio < 0.0 || m_clock_cycle_ratio > 1.0) + throw CLI_Usage_Error("Unlikely CPU clock ratio of " + clock_ratio); + + m_clock_cycle_ratio = 1.0 / m_clock_cycle_ratio; + + if(m_clock_speed != 0 && Botan::OS::get_cpu_cycle_counter() != 0) + { + error_output() << "The --cpu-clock-speed option is only intended to be used on " + "platforms without access to a cycle counter.\n" + "Expected incorrect results\n\n"; + } + + if(format == "table") + m_summary.reset(new Summary); + else if(format == "json") + m_json.reset(new JSON_Output); + else if(format != "default") + throw CLI_Usage_Error("Unknown --format type '" + format + "'"); + +#if defined(BOTAN_HAS_ECC_GROUP) + if(ecc_groups.empty()) + { + ecc_groups = { "secp256r1", "brainpool256r1", + "secp384r1", "brainpool384r1", + "secp521r1", "brainpool512r1" }; + } + else if(ecc_groups.size() == 1 && ecc_groups[0] == "all") + { + auto all = Botan::EC_Group::known_named_groups(); + ecc_groups.assign(all.begin(), all.end()); + } +#endif + + std::vector<std::string> algos = get_arg_list("algos"); + + const std::vector<size_t> buf_sizes = unique_buffer_sizes(get_arg("buf-size")); + + for(std::string cpuid_to_clear : Botan::split_on(get_arg("clear-cpuid"), ',')) + { + auto bits = Botan::CPUID::bit_from_string(cpuid_to_clear); + if(bits.empty()) + { + error_output() << "Warning don't know CPUID flag '" << cpuid_to_clear << "'\n"; + } + + for(auto bit : bits) + { + Botan::CPUID::clear_cpuid_bit(bit); + } + } + + if(verbose() || m_summary) + { + output() << Botan::version_string() << "\n" + << "CPUID: " << Botan::CPUID::to_string() << "\n\n"; + } + + const bool using_defaults = (algos.empty()); + if(using_defaults) + { + algos = default_benchmark_list(); + } + + for(auto algo : algos) + { + using namespace std::placeholders; + + if(false) + { + // Since everything might be disabled, need a block to else if from + } +#if defined(BOTAN_HAS_HASH) + else if(Botan::HashFunction::providers(algo).size() > 0) + { + bench_providers_of<Botan::HashFunction>( + algo, provider, msec, buf_sizes, + std::bind(&Speed::bench_hash, this, _1, _2, _3, _4)); + } +#endif +#if defined(BOTAN_HAS_BLOCK_CIPHER) + else if(Botan::BlockCipher::providers(algo).size() > 0) + { + bench_providers_of<Botan::BlockCipher>( + algo, provider, msec, buf_sizes, + std::bind(&Speed::bench_block_cipher, this, _1, _2, _3, _4)); + } +#endif +#if defined(BOTAN_HAS_STREAM_CIPHER) + else if(Botan::StreamCipher::providers(algo).size() > 0) + { + bench_providers_of<Botan::StreamCipher>( + algo, provider, msec, buf_sizes, + std::bind(&Speed::bench_stream_cipher, this, _1, _2, _3, _4)); + } +#endif +#if defined(BOTAN_HAS_CIPHER_MODES) + else if(auto enc = Botan::Cipher_Mode::create(algo, Botan::ENCRYPTION, provider)) + { + auto dec = Botan::Cipher_Mode::create_or_throw(algo, Botan::DECRYPTION, provider); + bench_cipher_mode(*enc, *dec, msec, buf_sizes); + } +#endif +#if defined(BOTAN_HAS_MAC) + else if(Botan::MessageAuthenticationCode::providers(algo).size() > 0) + { + bench_providers_of<Botan::MessageAuthenticationCode>( + algo, provider, msec, buf_sizes, + std::bind(&Speed::bench_mac, this, _1, _2, _3, _4)); + } +#endif +#if defined(BOTAN_HAS_RSA) + else if(algo == "RSA") + { + bench_rsa(provider, msec); + } + else if(algo == "RSA_keygen") + { + bench_rsa_keygen(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECDSA) + else if(algo == "ECDSA") + { + bench_ecdsa(ecc_groups, provider, msec); + } + else if(algo == "ecdsa_recovery") + { + bench_ecdsa_recovery(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_SM2) + else if(algo == "SM2") + { + bench_sm2(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECKCDSA) + else if(algo == "ECKCDSA") + { + bench_eckcdsa(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_GOST_34_10_2001) + else if(algo == "GOST-34.10") + { + bench_gost_3410(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECGDSA) + else if(algo == "ECGDSA") + { + bench_ecgdsa(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_ED25519) + else if(algo == "Ed25519") + { + bench_ed25519(provider, msec); + } +#endif +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + else if(algo == "DH") + { + bench_dh(provider, msec); + } +#endif +#if defined(BOTAN_HAS_DSA) + else if(algo == "DSA") + { + bench_dsa(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ELGAMAL) + else if(algo == "ElGamal") + { + bench_elgamal(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECDH) + else if(algo == "ECDH") + { + bench_ecdh(ecc_groups, provider, msec); + } +#endif +#if defined(BOTAN_HAS_CURVE_25519) + else if(algo == "Curve25519") + { + bench_curve25519(provider, msec); + } +#endif +#if defined(BOTAN_HAS_MCELIECE) + else if(algo == "McEliece") + { + bench_mceliece(provider, msec); + } +#endif +#if defined(BOTAN_HAS_XMSS_RFC8391) + else if(algo == "XMSS") + { + bench_xmss(provider, msec); + } +#endif +#if defined(BOTAN_HAS_NEWHOPE) && defined(BOTAN_HAS_CHACHA_RNG) + else if(algo == "NEWHOPE") + { + bench_newhope(provider, msec); + } +#endif +#if defined(BOTAN_HAS_SCRYPT) + else if(algo == "scrypt") + { + bench_scrypt(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ARGON2) + else if(algo == "argon2") + { + bench_argon2(provider, msec); + } +#endif +#if defined(BOTAN_HAS_BCRYPT) + else if(algo == "bcrypt") + { + bench_bcrypt(); + } +#endif +#if defined(BOTAN_HAS_PASSHASH9) + else if(algo == "passhash9") + { + bench_passhash9(); + } +#endif +#if defined(BOTAN_HAS_POLY_DBL) + else if(algo == "poly_dbl") + { + bench_poly_dbl(msec); + } +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + else if(algo == "modexp") + { + bench_modexp(msec); + } +#endif + +#if defined(BOTAN_HAS_BIGINT) + else if(algo == "mp_mul") + { + bench_mp_mul(msec); + } + else if(algo == "mp_div") + { + bench_mp_div(msec); + } + else if(algo == "mp_div10") + { + bench_mp_div10(msec); + } +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + else if(algo == "primality_test") + { + bench_primality_tests(msec); + } + else if(algo == "random_prime") + { + bench_random_prime(msec); + } + else if(algo == "inverse_mod") + { + bench_inverse_mod(msec); + } + else if(algo == "bn_redc") + { + bench_bn_redc(msec); + } + else if(algo == "nistp_redc") + { + bench_nistp_redc(msec); + } +#endif + +#if defined(BOTAN_HAS_FPE_FE1) + else if(algo == "fpe_fe1") + { + bench_fpe_fe1(msec); + } +#endif + +#if defined(BOTAN_HAS_RFC3394_KEYWRAP) + else if(algo == "rfc3394") + { + bench_rfc3394(msec); + } +#endif + +#if defined(BOTAN_HAS_ECC_GROUP) + else if(algo == "ecc_mult") + { + bench_ecc_mult(ecc_groups, msec); + } + else if(algo == "ecc_ops") + { + bench_ecc_ops(ecc_groups, msec); + } + else if(algo == "ecc_init") + { + bench_ecc_init(ecc_groups, msec); + } + else if(algo == "os2ecp") + { + bench_os2ecp(ecc_groups, msec); + } +#endif + else if(algo == "RNG") + { +#if defined(BOTAN_HAS_AUTO_SEEDING_RNG) + Botan::AutoSeeded_RNG auto_rng; + bench_rng(auto_rng, "AutoSeeded_RNG (with reseed)", msec, buf_sizes); +#endif + +#if defined(BOTAN_HAS_SYSTEM_RNG) + bench_rng(Botan::system_rng(), "System_RNG", msec, buf_sizes); +#endif + +#if defined(BOTAN_HAS_PROCESSOR_RNG) + if(Botan::Processor_RNG::available()) + { + Botan::Processor_RNG hwrng; + bench_rng(hwrng, "Processor_RNG", msec, buf_sizes); + } +#endif + +#if defined(BOTAN_HAS_HMAC_DRBG) + for(std::string hash : { "SHA-256", "SHA-384", "SHA-512" }) + { + Botan::HMAC_DRBG hmac_drbg(hash); + bench_rng(hmac_drbg, hmac_drbg.name(), msec, buf_sizes); + } +#endif + +#if defined(BOTAN_HAS_CHACHA_RNG) + // Provide a dummy seed + Botan::ChaCha_RNG chacha_rng(Botan::secure_vector<uint8_t>(32)); + bench_rng(chacha_rng, "ChaCha_RNG", msec, buf_sizes); +#endif + + } + else if(algo == "entropy") + { + bench_entropy_sources(msec); + } + else + { + if(verbose() || !using_defaults) + { + error_output() << "Unknown algorithm '" << algo << "'\n"; + } + } + } + + if(m_json) + { + output() << m_json->print(); + } + if(m_summary) + { + output() << m_summary->print() << "\n"; + } + + if(verbose() && m_clock_speed == 0 && m_cycles_consumed > 0 && m_ns_taken > 0) + { + const double seconds = static_cast<double>(m_ns_taken) / 1000000000; + const double Hz = static_cast<double>(m_cycles_consumed) / seconds; + const double MHz = Hz / 1000000; + output() << "\nEstimated clock speed " << MHz << " MHz\n"; + } + } + + private: + + size_t m_clock_speed = 0; + double m_clock_cycle_ratio = 0.0; + uint64_t m_cycles_consumed = 0; + uint64_t m_ns_taken = 0; + std::unique_ptr<Summary> m_summary; + std::unique_ptr<JSON_Output> m_json; + + void record_result(const std::unique_ptr<Timer>& t) + { + m_ns_taken += t->value(); + m_cycles_consumed += t->cycles_consumed(); + if(m_json) + { + m_json->add(*t); + } + else + { + output() << t->to_string() << std::flush; + if(m_summary) + m_summary->add(*t); + } + } + + template<typename T> + using bench_fn = std::function<void (T&, + std::string, + std::chrono::milliseconds, + const std::vector<size_t>&)>; + + template<typename T> + void bench_providers_of(const std::string& algo, + const std::string& provider, /* user request, if any */ + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes, + bench_fn<T> bench_one) + { + for(auto const& prov : T::providers(algo)) + { + if(provider.empty() || provider == prov) + { + auto p = T::create(algo, prov); + + if(p) + { + bench_one(*p, prov, runtime, buf_sizes); + } + } + } + } + + std::unique_ptr<Timer> make_timer(const std::string& name, + uint64_t event_mult = 1, + const std::string& what = "", + const std::string& provider = "", + size_t buf_size = 0) + { + return std::unique_ptr<Timer>( + new Timer(name, provider, what, event_mult, buf_size, + m_clock_cycle_ratio, m_clock_speed)); + } + + std::unique_ptr<Timer> make_timer(const std::string& algo, + const std::string& provider, + const std::string& what) + { + return make_timer(algo, 1, what, provider, 0); + } + +#if defined(BOTAN_HAS_BLOCK_CIPHER) + void bench_block_cipher(Botan::BlockCipher& cipher, + const std::string& provider, + std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + std::unique_ptr<Timer> ks_timer = make_timer(cipher.name(), provider, "key schedule"); + + const Botan::SymmetricKey key(rng(), cipher.maximum_keylength()); + ks_timer->run([&]() { cipher.set_key(key); }); + + const size_t bs = cipher.block_size(); + std::set<size_t> buf_sizes_in_blocks; + for(size_t buf_size : buf_sizes) + { + if(buf_size % bs == 0) + buf_sizes_in_blocks.insert(buf_size); + else + buf_sizes_in_blocks.insert(buf_size + bs - (buf_size % bs)); + } + + for(size_t buf_size : buf_sizes_in_blocks) + { + std::vector<uint8_t> buffer(buf_size); + const size_t blocks = buf_size / bs; + + std::unique_ptr<Timer> encrypt_timer = make_timer(cipher.name(), buffer.size(), "encrypt", provider, buf_size); + std::unique_ptr<Timer> decrypt_timer = make_timer(cipher.name(), buffer.size(), "decrypt", provider, buf_size); + + encrypt_timer->run_until_elapsed(runtime, [&]() { cipher.encrypt_n(&buffer[0], &buffer[0], blocks); }); + record_result(encrypt_timer); + + decrypt_timer->run_until_elapsed(runtime, [&]() { cipher.decrypt_n(&buffer[0], &buffer[0], blocks); }); + record_result(decrypt_timer); + } + } +#endif + +#if defined(BOTAN_HAS_STREAM_CIPHER) + void bench_stream_cipher( + Botan::StreamCipher& cipher, + const std::string& provider, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size); + + std::unique_ptr<Timer> encrypt_timer = make_timer(cipher.name(), buffer.size(), "encrypt", provider, buf_size); + + const Botan::SymmetricKey key(rng(), cipher.maximum_keylength()); + cipher.set_key(key); + + if(cipher.valid_iv_length(12)) + { + const Botan::InitializationVector iv(rng(), 12); + cipher.set_iv(iv.begin(), iv.size()); + } + + while(encrypt_timer->under(runtime)) + { + encrypt_timer->run([&]() { cipher.encipher(buffer); }); + } + + record_result(encrypt_timer); + } + } +#endif + +#if defined(BOTAN_HAS_HASH) + void bench_hash( + Botan::HashFunction& hash, + const std::string& provider, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + std::vector<uint8_t> output(hash.output_length()); + + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size); + + std::unique_ptr<Timer> timer = make_timer(hash.name(), buffer.size(), "hash", provider, buf_size); + timer->run_until_elapsed(runtime, [&]() { hash.update(buffer); hash.final(output.data()); }); + record_result(timer); + } + } +#endif + +#if defined(BOTAN_HAS_MAC) + void bench_mac( + Botan::MessageAuthenticationCode& mac, + const std::string& provider, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + std::vector<uint8_t> output(mac.output_length()); + + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size); + + const Botan::SymmetricKey key(rng(), mac.maximum_keylength()); + mac.set_key(key); + mac.start(nullptr, 0); + + std::unique_ptr<Timer> timer = make_timer(mac.name(), buffer.size(), "mac", provider, buf_size); + timer->run_until_elapsed(runtime, [&]() { mac.update(buffer); }); + timer->run([&]() { mac.final(output.data()); }); + record_result(timer); + } + } +#endif + +#if defined(BOTAN_HAS_CIPHER_MODES) + void bench_cipher_mode( + Botan::Cipher_Mode& enc, + Botan::Cipher_Mode& dec, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + std::unique_ptr<Timer> ks_timer = make_timer(enc.name(), enc.provider(), "key schedule"); + + const Botan::SymmetricKey key(rng(), enc.key_spec().maximum_keylength()); + + ks_timer->run([&]() { enc.set_key(key); }); + ks_timer->run([&]() { dec.set_key(key); }); + + record_result(ks_timer); + + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size); + + std::unique_ptr<Timer> encrypt_timer = make_timer(enc.name(), buffer.size(), "encrypt", enc.provider(), buf_size); + std::unique_ptr<Timer> decrypt_timer = make_timer(dec.name(), buffer.size(), "decrypt", dec.provider(), buf_size); + + Botan::secure_vector<uint8_t> iv = rng().random_vec(enc.default_nonce_length()); + + if(buf_size >= enc.minimum_final_size()) + { + while(encrypt_timer->under(runtime) && decrypt_timer->under(runtime)) + { + // Must run in this order, or AEADs will reject the ciphertext + encrypt_timer->run([&]() { enc.start(iv); enc.finish(buffer); }); + + decrypt_timer->run([&]() { dec.start(iv); dec.finish(buffer); }); + + if(iv.size() > 0) + { + iv[iv.size()-1] += 1; + } + } + } + + record_result(encrypt_timer); + record_result(decrypt_timer); + } + } +#endif + + void bench_rng( + Botan::RandomNumberGenerator& rng, + const std::string& rng_name, + const std::chrono::milliseconds runtime, + const std::vector<size_t>& buf_sizes) + { + for(auto buf_size : buf_sizes) + { + Botan::secure_vector<uint8_t> buffer(buf_size); + +#if defined(BOTAN_HAS_SYSTEM_RNG) + rng.reseed_from_rng(Botan::system_rng(), 256); +#endif + + std::unique_ptr<Timer> timer = make_timer(rng_name, buffer.size(), "generate", "", buf_size); + timer->run_until_elapsed(runtime, [&]() { rng.randomize(buffer.data(), buffer.size()); }); + record_result(timer); + } + } + + void bench_entropy_sources(const std::chrono::milliseconds) + { + Botan::Entropy_Sources& srcs = Botan::Entropy_Sources::global_sources(); + + for(auto src : srcs.enabled_sources()) + { + size_t entropy_bits = 0; + Botan_Tests::SeedCapturing_RNG rng; + + std::unique_ptr<Timer> timer = make_timer(src, "", "bytes"); + timer->run([&]() { entropy_bits = srcs.poll_just(rng, src); }); + + size_t compressed_size = 0; + +#if defined(BOTAN_HAS_ZLIB) + std::unique_ptr<Botan::Compression_Algorithm> comp(Botan::make_compressor("zlib")); + + if(comp) + { + Botan::secure_vector<uint8_t> compressed; + compressed.assign(rng.seed_material().begin(), rng.seed_material().end()); + comp->start(9); + comp->finish(compressed); + + compressed_size = compressed.size(); + } +#endif + + std::ostringstream msg; + + msg << "Entropy source " << src << " output " << rng.seed_material().size() << " bytes" + << " estimated entropy " << entropy_bits << " in " << timer->milliseconds() << " ms"; + + if(compressed_size > 0) + { + msg << " output compressed to " << compressed_size << " bytes"; + } + + msg << " total samples " << rng.samples() << "\n"; + + timer->set_custom_msg(msg.str()); + + record_result(timer); + } + } + +#if defined(BOTAN_HAS_ECC_GROUP) + void bench_ecc_ops(const std::vector<std::string>& groups, const std::chrono::milliseconds runtime) + { + for(std::string group_name : groups) + { + const Botan::EC_Group ec_group(group_name); + + std::unique_ptr<Timer> add_timer = make_timer(group_name + " add"); + std::unique_ptr<Timer> addf_timer = make_timer(group_name + " addf"); + std::unique_ptr<Timer> dbl_timer = make_timer(group_name + " dbl"); + + const Botan::PointGFp& base_point = ec_group.get_base_point(); + Botan::PointGFp non_affine_pt = ec_group.get_base_point() * 1776; // create a non-affine point + Botan::PointGFp pt = ec_group.get_base_point(); + + std::vector<Botan::BigInt> ws(Botan::PointGFp::WORKSPACE_SIZE); + + while(add_timer->under(runtime) && addf_timer->under(runtime) && dbl_timer->under(runtime)) + { + dbl_timer->run([&]() { pt.mult2(ws); }); + add_timer->run([&]() { pt.add(non_affine_pt, ws); }); + addf_timer->run([&]() { pt.add_affine(base_point, ws); }); + } + + record_result(dbl_timer); + record_result(add_timer); + record_result(addf_timer); + } + } + + void bench_ecc_init(const std::vector<std::string>& groups, const std::chrono::milliseconds runtime) + { + for(std::string group_name : groups) + { + std::unique_ptr<Timer> timer = make_timer(group_name + " initialization"); + + while(timer->under(runtime)) + { + Botan::EC_Group::clear_registered_curve_data(); + timer->run([&]() { Botan::EC_Group group(group_name); }); + } + + record_result(timer); + } + } + + void bench_ecc_mult(const std::vector<std::string>& groups, const std::chrono::milliseconds runtime) + { + for(std::string group_name : groups) + { + const Botan::EC_Group ec_group(group_name); + + std::unique_ptr<Timer> mult_timer = make_timer(group_name + " Montgomery ladder"); + std::unique_ptr<Timer> blinded_mult_timer = make_timer(group_name + " blinded comb"); + std::unique_ptr<Timer> blinded_var_mult_timer = make_timer(group_name + " blinded window"); + + const Botan::PointGFp& base_point = ec_group.get_base_point(); + + std::vector<Botan::BigInt> ws; + + while(mult_timer->under(runtime) && + blinded_mult_timer->under(runtime) && + blinded_var_mult_timer->under(runtime)) + { + const Botan::BigInt scalar(rng(), ec_group.get_p_bits()); + + const Botan::PointGFp r1 = mult_timer->run([&]() { return base_point * scalar; }); + + const Botan::PointGFp r2 = blinded_mult_timer->run( + [&]() { return ec_group.blinded_base_point_multiply(scalar, rng(), ws); }); + + const Botan::PointGFp r3 = blinded_var_mult_timer->run( + [&]() { return ec_group.blinded_var_point_multiply(base_point, scalar, rng(), ws); }); + + BOTAN_ASSERT_EQUAL(r1, r2, "Same point computed by Montgomery and comb"); + BOTAN_ASSERT_EQUAL(r1, r3, "Same point computed by Montgomery and window"); + } + + record_result(mult_timer); + record_result(blinded_mult_timer); + record_result(blinded_var_mult_timer); + } + } + + void bench_os2ecp(const std::vector<std::string>& groups, const std::chrono::milliseconds runtime) + { + std::unique_ptr<Timer> uncmp_timer = make_timer("OS2ECP uncompressed"); + std::unique_ptr<Timer> cmp_timer = make_timer("OS2ECP compressed"); + + for(std::string group_name : groups) + { + const Botan::EC_Group ec_group(group_name); + + while(uncmp_timer->under(runtime) && cmp_timer->under(runtime)) + { + const Botan::BigInt k(rng(), 256); + const Botan::PointGFp p = ec_group.get_base_point() * k; + const std::vector<uint8_t> os_cmp = p.encode(Botan::PointGFp::COMPRESSED); + const std::vector<uint8_t> os_uncmp = p.encode(Botan::PointGFp::UNCOMPRESSED); + + uncmp_timer->run([&]() { ec_group.OS2ECP(os_uncmp); }); + cmp_timer->run([&]() { ec_group.OS2ECP(os_cmp); }); + } + + record_result(uncmp_timer); + record_result(cmp_timer); + } + } + +#endif + +#if defined(BOTAN_HAS_FPE_FE1) + + void bench_fpe_fe1(const std::chrono::milliseconds runtime) + { + const Botan::BigInt n = 1000000000000000; + + std::unique_ptr<Timer> enc_timer = make_timer("FPE_FE1 encrypt"); + std::unique_ptr<Timer> dec_timer = make_timer("FPE_FE1 decrypt"); + + const Botan::SymmetricKey key(rng(), 32); + const std::vector<uint8_t> tweak(8); // 8 zeros + + Botan::BigInt x = 1; + + Botan::FPE_FE1 fpe_fe1(n); + fpe_fe1.set_key(key); + + while(enc_timer->under(runtime)) + { + enc_timer->start(); + x = fpe_fe1.encrypt(x, tweak.data(), tweak.size()); + enc_timer->stop(); + } + + for(size_t i = 0; i != enc_timer->events(); ++i) + { + dec_timer->start(); + x = fpe_fe1.decrypt(x, tweak.data(), tweak.size()); + dec_timer->stop(); + } + + BOTAN_ASSERT(x == 1, "FPE works"); + + record_result(enc_timer); + record_result(dec_timer); + } +#endif + +#if defined(BOTAN_HAS_RFC3394_KEYWRAP) + + void bench_rfc3394(const std::chrono::milliseconds runtime) + { + std::unique_ptr<Timer> wrap_timer = make_timer("RFC3394 AES-256 key wrap"); + std::unique_ptr<Timer> unwrap_timer = make_timer("RFC3394 AES-256 key unwrap"); + + const Botan::SymmetricKey kek(rng(), 32); + Botan::secure_vector<uint8_t> key(64, 0); + + while(wrap_timer->under(runtime)) + { + wrap_timer->start(); + key = Botan::rfc3394_keywrap(key, kek); + wrap_timer->stop(); + + unwrap_timer->start(); + key = Botan::rfc3394_keyunwrap(key, kek); + unwrap_timer->stop(); + + key[0] += 1; + } + + record_result(wrap_timer); + record_result(unwrap_timer); + } +#endif + +#if defined(BOTAN_HAS_BIGINT) + + void bench_mp_mul(const std::chrono::milliseconds runtime) + { + std::chrono::milliseconds runtime_per_size = runtime; + for(size_t bits : { 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096 }) + { + std::unique_ptr<Timer> mul_timer = make_timer("BigInt mul " + std::to_string(bits)); + std::unique_ptr<Timer> sqr_timer = make_timer("BigInt sqr " + std::to_string(bits)); + + const Botan::BigInt y(rng(), bits); + Botan::secure_vector<Botan::word> ws; + + while(mul_timer->under(runtime_per_size)) + { + Botan::BigInt x(rng(), bits); + + sqr_timer->start(); + x.square(ws); + sqr_timer->stop(); + + x.mask_bits(bits); + + mul_timer->start(); + x.mul(y, ws); + mul_timer->stop(); + } + + record_result(mul_timer); + record_result(sqr_timer); + } + + } + + void bench_mp_div(const std::chrono::milliseconds runtime) + { + std::chrono::milliseconds runtime_per_size = runtime; + + for(size_t n_bits : { 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096 }) + { + const size_t q_bits = n_bits / 2; + const std::string bit_descr = std::to_string(n_bits) + "/" + std::to_string(q_bits); + + std::unique_ptr<Timer> div_timer = make_timer("BigInt div " + bit_descr); + std::unique_ptr<Timer> ct_div_timer = make_timer("BigInt ct_div " + bit_descr); + + Botan::BigInt y; + Botan::BigInt x; + Botan::secure_vector<Botan::word> ws; + + Botan::BigInt q1, r1, q2, r2; + + while(ct_div_timer->under(runtime_per_size)) + { + x.randomize(rng(), n_bits); + y.randomize(rng(), q_bits); + + div_timer->start(); + Botan::vartime_divide(x, y, q1, r1); + div_timer->stop(); + + ct_div_timer->start(); + Botan::ct_divide(x, y, q2, r2); + ct_div_timer->stop(); + + BOTAN_ASSERT_EQUAL(q1, q2, "Quotient ok"); + BOTAN_ASSERT_EQUAL(r1, r2, "Remainder ok"); + } + + record_result(div_timer); + record_result(ct_div_timer); + } + } + + void bench_mp_div10(const std::chrono::milliseconds runtime) + { + std::chrono::milliseconds runtime_per_size = runtime; + + for(size_t n_bits : { 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096 }) + { + const std::string bit_descr = std::to_string(n_bits) + "/10"; + + std::unique_ptr<Timer> div_timer = make_timer("BigInt div " + bit_descr); + std::unique_ptr<Timer> ct_div_timer = make_timer("BigInt ct_div " + bit_descr); + + Botan::BigInt x; + Botan::secure_vector<Botan::word> ws; + + const Botan::BigInt ten(10); + Botan::BigInt q1, r1, q2; + uint8_t r2; + + while(ct_div_timer->under(runtime_per_size)) + { + x.randomize(rng(), n_bits); + + div_timer->start(); + Botan::vartime_divide(x, ten, q1, r1); + div_timer->stop(); + + ct_div_timer->start(); + Botan::ct_divide_u8(x, 10, q2, r2); + ct_div_timer->stop(); + + BOTAN_ASSERT_EQUAL(q1, q2, "Quotient ok"); + BOTAN_ASSERT_EQUAL(r1, r2, "Remainder ok"); + } + + record_result(div_timer); + record_result(ct_div_timer); + } + } + +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + + void bench_modexp(const std::chrono::milliseconds runtime) + { + for(size_t group_bits : { 1024, 1536, 2048, 3072, 4096 }) + { + const std::string group_bits_str = std::to_string(group_bits); + const Botan::DL_Group group("modp/srp/" + group_bits_str); + + const size_t e_bits = Botan::dl_exponent_size(group_bits); + const size_t f_bits = group_bits - 1; + + const Botan::BigInt random_e(rng(), e_bits); + const Botan::BigInt random_f(rng(), f_bits); + + std::unique_ptr<Timer> e_timer = make_timer(group_bits_str + " short exponent"); + std::unique_ptr<Timer> f_timer = make_timer(group_bits_str + " full exponent"); + + while(f_timer->under(runtime)) + { + e_timer->run([&]() { group.power_g_p(random_e); }); + f_timer->run([&]() { group.power_g_p(random_f); }); + } + + record_result(e_timer); + record_result(f_timer); + } + } +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + void bench_nistp_redc(const std::chrono::milliseconds runtime) + { + Botan::secure_vector<Botan::word> ws; + + std::unique_ptr<Timer> p192_timer = make_timer("P-192 redc"); + Botan::BigInt r192(rng(), 192*2 - 1); + while(p192_timer->under(runtime)) + { + Botan::BigInt r = r192; + p192_timer->run([&]() { Botan::redc_p192(r, ws); }); + r192 += 1; + } + record_result(p192_timer); + + std::unique_ptr<Timer> p224_timer = make_timer("P-224 redc"); + Botan::BigInt r224(rng(), 224*2 - 1); + while(p224_timer->under(runtime)) + { + Botan::BigInt r = r224; + p224_timer->run([&]() { Botan::redc_p224(r, ws); }); + r224 += 1; + } + record_result(p224_timer); + + std::unique_ptr<Timer> p256_timer = make_timer("P-256 redc"); + Botan::BigInt r256(rng(), 256*2 - 1); + while(p256_timer->under(runtime)) + { + Botan::BigInt r = r256; + p256_timer->run([&]() { Botan::redc_p256(r, ws); }); + r256 += 1; + } + record_result(p256_timer); + + std::unique_ptr<Timer> p384_timer = make_timer("P-384 redc"); + Botan::BigInt r384(rng(), 384*2 - 1); + while(p384_timer->under(runtime)) + { + Botan::BigInt r = r384; + p384_timer->run([&]() { Botan::redc_p384(r384, ws); }); + r384 += 1; + } + record_result(p384_timer); + + std::unique_ptr<Timer> p521_timer = make_timer("P-521 redc"); + Botan::BigInt r521(rng(), 521*2 - 1); + while(p521_timer->under(runtime)) + { + Botan::BigInt r = r521; + p521_timer->run([&]() { Botan::redc_p521(r521, ws); }); + r521 += 1; + } + record_result(p521_timer); + } + + void bench_bn_redc(const std::chrono::milliseconds runtime) + { + for(size_t bitsize : { 512, 1024, 2048, 4096 }) + { + Botan::BigInt p(rng(), bitsize); + + std::string bit_str = std::to_string(bitsize); + std::unique_ptr<Timer> barrett_timer = make_timer("Barrett-" + bit_str); + std::unique_ptr<Timer> schoolbook_timer = make_timer("Schoolbook-" + bit_str); + + Botan::Modular_Reducer mod_p(p); + + while(schoolbook_timer->under(runtime)) + { + const Botan::BigInt x(rng(), p.bits() * 2 - 2); + + const Botan::BigInt r1 = barrett_timer->run( + [&] { return mod_p.reduce(x); }); + const Botan::BigInt r2 = schoolbook_timer->run( + [&] { return x % p; }); + + BOTAN_ASSERT(r1 == r2, "Computed different results"); + } + + record_result(barrett_timer); + record_result(schoolbook_timer); + } + } + + void bench_inverse_mod(const std::chrono::milliseconds runtime) + { + for(size_t bits : { 256, 384, 512, 1024, 2048 }) + { + const std::string bit_str = std::to_string(bits); + + std::unique_ptr<Timer> timer = make_timer("inverse_mod-" + bit_str); + + while(timer->under(runtime)) + { + const Botan::BigInt x(rng(), bits - 1); + Botan::BigInt mod(rng(), bits); + + const Botan::BigInt x_inv = timer->run( + [&] { return Botan::inverse_mod(x, mod); }); + + if(x_inv == 0) + { + const Botan::BigInt g = gcd(x, mod); + BOTAN_ASSERT(g != 1, "Inversion only fails if gcd(x, mod) > 1"); + } + else + { + const Botan::BigInt check = (x_inv*x) % mod; + BOTAN_ASSERT_EQUAL(check, 1, "Const time inversion correct"); + } + } + + record_result(timer); + } + } + + void bench_primality_tests(const std::chrono::milliseconds runtime) + { + for(size_t bits : { 256, 512, 1024 }) + { + std::unique_ptr<Timer> mr_timer = make_timer("Miller-Rabin-" + std::to_string(bits)); + std::unique_ptr<Timer> bpsw_timer = make_timer("Bailie-PSW-" + std::to_string(bits)); + std::unique_ptr<Timer> lucas_timer = make_timer("Lucas-" + std::to_string(bits)); + + Botan::BigInt n = Botan::random_prime(rng(), bits); + + while(lucas_timer->under(runtime)) + { + Botan::Modular_Reducer mod_n(n); + + mr_timer->run([&]() { + return Botan::is_miller_rabin_probable_prime(n, mod_n, rng(), 2); }); + + bpsw_timer->run([&]() { + return Botan::is_bailie_psw_probable_prime(n, mod_n); }); + + lucas_timer->run([&]() { + return Botan::is_lucas_probable_prime(n, mod_n); }); + + n += 2; + } + + record_result(mr_timer); + record_result(bpsw_timer); + record_result(lucas_timer); + } + } + + void bench_random_prime(const std::chrono::milliseconds runtime) + { + const size_t coprime = 65537; // simulates RSA key gen + + for(size_t bits : { 256, 384, 512, 768, 1024, 1536 }) + { + std::unique_ptr<Timer> genprime_timer = make_timer("random_prime " + std::to_string(bits)); + std::unique_ptr<Timer> gensafe_timer = make_timer("random_safe_prime " + std::to_string(bits)); + std::unique_ptr<Timer> is_prime_timer = make_timer("is_prime " + std::to_string(bits)); + + while(gensafe_timer->under(runtime)) + { + const Botan::BigInt p = genprime_timer->run([&] + { + return Botan::random_prime(rng(), bits, coprime); + }); + + if(!is_prime_timer->run([&] { return Botan::is_prime(p, rng(), 64, true); })) + { + error_output() << "Generated prime " << p << " which failed a primality test"; + } + + const Botan::BigInt sg = gensafe_timer->run([&] + { + return Botan::random_safe_prime(rng(), bits); + }); + + if(!is_prime_timer->run([&] { return Botan::is_prime(sg, rng(), 64, true); })) + { + error_output() << "Generated safe prime " << sg << " which failed a primality test"; + } + + if(!is_prime_timer->run([&] { return Botan::is_prime(sg / 2, rng(), 64, true); })) + { + error_output() << "Generated prime " << sg/2 << " which failed a primality test"; + } + + // Now test p+2, p+4, ... which may or may not be prime + for(size_t i = 2; i <= 64; i += 2) + { + is_prime_timer->run([&]() { Botan::is_prime(p + i, rng(), 64, true); }); + } + } + + record_result(genprime_timer); + record_result(gensafe_timer); + record_result(is_prime_timer); + } + } +#endif + +#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) + void bench_pk_enc( + const Botan::Private_Key& key, + const std::string& nm, + const std::string& provider, + const std::string& padding, + std::chrono::milliseconds msec) + { + std::vector<uint8_t> plaintext, ciphertext; + + Botan::PK_Encryptor_EME enc(key, rng(), padding, provider); + Botan::PK_Decryptor_EME dec(key, rng(), padding, provider); + + std::unique_ptr<Timer> enc_timer = make_timer(nm + " " + padding, provider, "encrypt"); + std::unique_ptr<Timer> dec_timer = make_timer(nm + " " + padding, provider, "decrypt"); + + while(enc_timer->under(msec) || dec_timer->under(msec)) + { + // Generate a new random ciphertext to decrypt + if(ciphertext.empty() || enc_timer->under(msec)) + { + rng().random_vec(plaintext, enc.maximum_input_size()); + ciphertext = enc_timer->run([&]() { return enc.encrypt(plaintext, rng()); }); + } + + if(dec_timer->under(msec)) + { + const auto dec_pt = dec_timer->run([&]() { return dec.decrypt(ciphertext); }); + + if(!(dec_pt == plaintext)) // sanity check + { + error_output() << "Bad roundtrip in PK encrypt/decrypt bench\n"; + } + } + } + + record_result(enc_timer); + record_result(dec_timer); + } + + void bench_pk_ka(const std::string& algo, + const std::string& nm, + const std::string& params, + const std::string& provider, + std::chrono::milliseconds msec) + { + const std::string kdf = "KDF2(SHA-256)"; // arbitrary choice + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key1(keygen_timer->run([&] + { + return Botan::create_private_key(algo, rng(), params); + })); + std::unique_ptr<Botan::Private_Key> key2(keygen_timer->run([&] + { + return Botan::create_private_key(algo, rng(), params); + })); + + record_result(keygen_timer); + + const Botan::PK_Key_Agreement_Key& ka_key1 = dynamic_cast<const Botan::PK_Key_Agreement_Key&>(*key1); + const Botan::PK_Key_Agreement_Key& ka_key2 = dynamic_cast<const Botan::PK_Key_Agreement_Key&>(*key2); + + Botan::PK_Key_Agreement ka1(ka_key1, rng(), kdf, provider); + Botan::PK_Key_Agreement ka2(ka_key2, rng(), kdf, provider); + + const std::vector<uint8_t> ka1_pub = ka_key1.public_value(); + const std::vector<uint8_t> ka2_pub = ka_key2.public_value(); + + std::unique_ptr<Timer> ka_timer = make_timer(nm, provider, "key agreements"); + + while(ka_timer->under(msec)) + { + Botan::SymmetricKey symkey1 = ka_timer->run([&]() { return ka1.derive_key(32, ka2_pub); }); + Botan::SymmetricKey symkey2 = ka_timer->run([&]() { return ka2.derive_key(32, ka1_pub); }); + + if(symkey1 != symkey2) + { + error_output() << "Key agreement mismatch in PK bench\n"; + } + } + + record_result(ka_timer); + } + + void bench_pk_kem(const Botan::Private_Key& key, + const std::string& nm, + const std::string& provider, + const std::string& kdf, + std::chrono::milliseconds msec) + { + Botan::PK_KEM_Decryptor dec(key, rng(), kdf, provider); + Botan::PK_KEM_Encryptor enc(key, rng(), kdf, provider); + + std::unique_ptr<Timer> kem_enc_timer = make_timer(nm, provider, "KEM encrypt"); + std::unique_ptr<Timer> kem_dec_timer = make_timer(nm, provider, "KEM decrypt"); + + while(kem_enc_timer->under(msec) && kem_dec_timer->under(msec)) + { + Botan::secure_vector<uint8_t> encap_key, enc_shared_key; + Botan::secure_vector<uint8_t> salt = rng().random_vec(16); + + kem_enc_timer->start(); + enc.encrypt(encap_key, enc_shared_key, 64, rng(), salt); + kem_enc_timer->stop(); + + kem_dec_timer->start(); + Botan::secure_vector<uint8_t> dec_shared_key = dec.decrypt(encap_key, 64, salt); + kem_dec_timer->stop(); + + if(enc_shared_key != dec_shared_key) + { + error_output() << "KEM mismatch in PK bench\n"; + } + } + + record_result(kem_enc_timer); + record_result(kem_dec_timer); + } + + void bench_pk_sig_ecc(const std::string& algo, + const std::string& emsa, + const std::string& provider, + const std::vector<std::string>& params, + std::chrono::milliseconds msec) + { + for(std::string grp : params) + { + const std::string nm = grp.empty() ? algo : (algo + "-" + grp); + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key(algo, rng(), grp); + })); + + record_result(keygen_timer); + bench_pk_sig(*key, nm, provider, emsa, msec); + } + } + + size_t bench_pk_sig(const Botan::Private_Key& key, + const std::string& nm, + const std::string& provider, + const std::string& padding, + std::chrono::milliseconds msec) + { + std::vector<uint8_t> message, signature, bad_signature; + + Botan::PK_Signer sig(key, rng(), padding, Botan::IEEE_1363, provider); + Botan::PK_Verifier ver(key, padding, Botan::IEEE_1363, provider); + + std::unique_ptr<Timer> sig_timer = make_timer(nm + " " + padding, provider, "sign"); + std::unique_ptr<Timer> ver_timer = make_timer(nm + " " + padding, provider, "verify"); + + size_t invalid_sigs = 0; + + while(ver_timer->under(msec) || sig_timer->under(msec)) + { + if(signature.empty() || sig_timer->under(msec)) + { + /* + Length here is kind of arbitrary, but 48 bytes fits into a single + hash block so minimizes hashing overhead versus the PK op itself. + */ + rng().random_vec(message, 48); + + signature = sig_timer->run([&]() { return sig.sign_message(message, rng()); }); + + bad_signature = signature; + bad_signature[rng().next_byte() % bad_signature.size()] ^= rng().next_nonzero_byte(); + } + + if(ver_timer->under(msec)) + { + const bool verified = ver_timer->run([&] + { + return ver.verify_message(message, signature); + }); + + if(!verified) + { + invalid_sigs += 1; + } + + const bool verified_bad = ver_timer->run([&] + { + return ver.verify_message(message, bad_signature); + }); + + if(verified_bad) + { + error_output() << "Bad signature accepted in PK signature bench\n"; + } + } + } + + if(invalid_sigs > 0) + error_output() << invalid_sigs << " generated signatures rejected in PK signature bench\n"; + + const size_t events = static_cast<size_t>(std::min(sig_timer->events(), ver_timer->events())); + + record_result(sig_timer); + record_result(ver_timer); + + return events; + } +#endif + +#if defined(BOTAN_HAS_RSA) + void bench_rsa_keygen(const std::string& provider, + std::chrono::milliseconds msec) + { + for(size_t keylen : { 1024, 2048, 3072, 4096 }) + { + const std::string nm = "RSA-" + std::to_string(keylen); + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + while(keygen_timer->under(msec)) + { + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] { + return Botan::create_private_key("RSA", rng(), std::to_string(keylen)); + })); + + BOTAN_ASSERT(key->check_key(rng(), true), "Key is ok"); + } + + record_result(keygen_timer); + } + } + + void bench_rsa(const std::string& provider, + std::chrono::milliseconds msec) + { + for(size_t keylen : { 1024, 2048, 3072, 4096 }) + { + const std::string nm = "RSA-" + std::to_string(keylen); + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key("RSA", rng(), std::to_string(keylen)); + })); + + record_result(keygen_timer); + + // Using PKCS #1 padding so OpenSSL provider can play along + bench_pk_sig(*key, nm, provider, "EMSA-PKCS1-v1_5(SHA-256)", msec); + + //bench_pk_sig(*key, nm, provider, "PSSR(SHA-256)", msec); + //bench_pk_enc(*key, nm, provider, "EME-PKCS1-v1_5", msec); + //bench_pk_enc(*key, nm, provider, "OAEP(SHA-1)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_ECDSA) + void bench_ecdsa(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("ECDSA", "EMSA1(SHA-256)", provider, groups, msec); + } + + void bench_ecdsa_recovery(const std::vector<std::string>& groups, + const std::string&, + std::chrono::milliseconds msec) + { + for(std::string group_name : groups) + { + Botan::EC_Group group(group_name); + std::unique_ptr<Timer> recovery_timer = make_timer("ECDSA recovery " + group_name); + + while(recovery_timer->under(msec)) + { + Botan::ECDSA_PrivateKey key(rng(), group); + + std::vector<uint8_t> message(group.get_order_bits() / 8); + rng().randomize(message.data(), message.size()); + + Botan::PK_Signer signer(key, rng(), "Raw"); + signer.update(message); + std::vector<uint8_t> signature = signer.signature(rng()); + + Botan::PK_Verifier verifier(key, "Raw", Botan::IEEE_1363, "base"); + verifier.update(message); + BOTAN_ASSERT(verifier.check_signature(signature), "Valid signature"); + + Botan::BigInt r(signature.data(), signature.size()/2); + Botan::BigInt s(signature.data() + signature.size()/2, signature.size()/2); + + const uint8_t v = key.recovery_param(message, r, s); + + recovery_timer->run([&]() { + Botan::ECDSA_PublicKey pubkey(group, message, r, s, v); + BOTAN_ASSERT(pubkey.public_point() == key.public_point(), "Recovered public key"); + }); + } + + record_result(recovery_timer); + } + + } + +#endif + +#if defined(BOTAN_HAS_ECKCDSA) + void bench_eckcdsa(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("ECKCDSA", "EMSA1(SHA-256)", provider, groups, msec); + } +#endif + +#if defined(BOTAN_HAS_GOST_34_10_2001) + void bench_gost_3410(const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("GOST-34.10", "EMSA1(GOST-34.11)", provider, {"gost_256A"}, msec); + } +#endif + +#if defined(BOTAN_HAS_SM2) + void bench_sm2(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("SM2_Sig", "SM3", provider, groups, msec); + } +#endif + +#if defined(BOTAN_HAS_ECGDSA) + void bench_ecgdsa(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("ECGDSA", "EMSA1(SHA-256)", provider, groups, msec); + } +#endif + +#if defined(BOTAN_HAS_ED25519) + void bench_ed25519(const std::string& provider, + std::chrono::milliseconds msec) + { + return bench_pk_sig_ecc("Ed25519", "Pure", provider, std::vector<std::string>{""}, msec); + } +#endif + +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + void bench_dh(const std::string& provider, + std::chrono::milliseconds msec) + { + for(size_t bits : { 1024, 1536, 2048, 3072, 4096, 6144, 8192 }) + { + bench_pk_ka("DH", + "DH-" + std::to_string(bits), + "modp/ietf/" + std::to_string(bits), + provider, msec); + } + } +#endif + +#if defined(BOTAN_HAS_DSA) + void bench_dsa(const std::string& provider, std::chrono::milliseconds msec) + { + for(size_t bits : { 1024, 2048, 3072 }) + { + const std::string nm = "DSA-" + std::to_string(bits); + + const std::string params = + (bits == 1024) ? "dsa/jce/1024" : ("dsa/botan/" + std::to_string(bits)); + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key("DSA", rng(), params); + })); + + record_result(keygen_timer); + + bench_pk_sig(*key, nm, provider, "EMSA1(SHA-256)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_ELGAMAL) + void bench_elgamal(const std::string& provider, std::chrono::milliseconds msec) + { + for(size_t keylen : { 1024, 2048, 3072, 4096 }) + { + const std::string nm = "ElGamal-" + std::to_string(keylen); + + const std::string params = "modp/ietf/" + std::to_string(keylen); + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key("ElGamal", rng(), params); + })); + + record_result(keygen_timer); + + bench_pk_enc(*key, nm, provider, "EME-PKCS1-v1_5", msec); + } + } +#endif + +#if defined(BOTAN_HAS_ECDH) + void bench_ecdh(const std::vector<std::string>& groups, + const std::string& provider, + std::chrono::milliseconds msec) + { + for(std::string grp : groups) + { + bench_pk_ka("ECDH", "ECDH-" + grp, grp, provider, msec); + } + } +#endif + +#if defined(BOTAN_HAS_CURVE_25519) + void bench_curve25519(const std::string& provider, + std::chrono::milliseconds msec) + { + bench_pk_ka("Curve25519", "Curve25519", "", provider, msec); + } +#endif + +#if defined(BOTAN_HAS_MCELIECE) + void bench_mceliece(const std::string& provider, + std::chrono::milliseconds msec) + { + /* + SL=80 n=1632 t=33 - 59 KB pubkey 140 KB privkey + SL=107 n=2480 t=45 - 128 KB pubkey 300 KB privkey + SL=128 n=2960 t=57 - 195 KB pubkey 459 KB privkey + SL=147 n=3408 t=67 - 265 KB pubkey 622 KB privkey + SL=191 n=4624 t=95 - 516 KB pubkey 1234 KB privkey + SL=256 n=6624 t=115 - 942 KB pubkey 2184 KB privkey + */ + + const std::vector<std::pair<size_t, size_t>> mce_params = + { + { 2480, 45 }, + { 2960, 57 }, + { 3408, 67 }, + { 4624, 95 }, + { 6624, 115 } + }; + + for(auto params : mce_params) + { + size_t n = params.first; + size_t t = params.second; + + const std::string nm = "McEliece-" + std::to_string(n) + "," + std::to_string(t) + + " (WF=" + std::to_string(Botan::mceliece_work_factor(n, t)) + ")"; + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return new Botan::McEliece_PrivateKey(rng(), n, t); + })); + + record_result(keygen_timer); + bench_pk_kem(*key, nm, provider, "KDF2(SHA-256)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_XMSS_RFC8391) + void bench_xmss(const std::string& provider, + std::chrono::milliseconds msec) + { + /* + We only test H10 signatures here since already they are quite slow (a + few seconds per signature). On a fast machine, H16 signatures take 1-2 + minutes to generate and H20 signatures take 5-10 minutes to generate + */ + std::vector<std::string> xmss_params + { + "XMSS-SHA2_10_256", + "XMSS-SHAKE_10_256", + "XMSS-SHA2_10_512", + "XMSS-SHAKE_10_512", + }; + + for(std::string params : xmss_params) + { + std::unique_ptr<Timer> keygen_timer = make_timer(params, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer->run([&] + { + return Botan::create_private_key("XMSS", rng(), params); + })); + + record_result(keygen_timer); + if(bench_pk_sig(*key, params, provider, "", msec) == 1) + break; + } + } +#endif + +#if defined(BOTAN_HAS_POLY_DBL) + void bench_poly_dbl(std::chrono::milliseconds msec) + { + for(size_t sz : { 8, 16, 24, 32, 64, 128 }) + { + std::unique_ptr<Timer> be_timer = make_timer("poly_dbl_be_" + std::to_string(sz)); + std::unique_ptr<Timer> le_timer = make_timer("poly_dbl_le_" + std::to_string(sz)); + + std::vector<uint8_t> buf(sz); + rng().randomize(buf.data(), sz); + + be_timer->run_until_elapsed(msec, [&]() { Botan::poly_double_n(buf.data(), buf.data(), sz); }); + le_timer->run_until_elapsed(msec, [&]() { Botan::poly_double_n_le(buf.data(), buf.data(), sz); }); + + record_result(be_timer); + record_result(le_timer); + } + } +#endif + +#if defined(BOTAN_HAS_BCRYPT) + + void bench_bcrypt() + { + const std::string password = "not a very good password"; + + for(uint8_t work_factor = 4; work_factor <= 14; ++work_factor) + { + std::unique_ptr<Timer> timer = make_timer("bcrypt wf=" + std::to_string(work_factor)); + + timer->run([&] { + Botan::generate_bcrypt(password, rng(), work_factor); + }); + + record_result(timer); + } + } +#endif + +#if defined(BOTAN_HAS_PASSHASH9) + + void bench_passhash9() + { + const std::string password = "not a very good password"; + + for(uint8_t alg = 0; alg <= 4; ++alg) + { + if(Botan::is_passhash9_alg_supported(alg) == false) + continue; + + for(auto work_factor : { 10, 15 }) + { + std::unique_ptr<Timer> timer = make_timer("passhash9 alg=" + std::to_string(alg) + + " wf=" + std::to_string(work_factor)); + + timer->run([&] { + Botan::generate_passhash9(password, rng(), static_cast<uint8_t>(work_factor), alg); + }); + + record_result(timer); + } + } + } +#endif + +#if defined(BOTAN_HAS_SCRYPT) + + void bench_scrypt(const std::string& /*provider*/, + std::chrono::milliseconds msec) + { + + for(size_t N : { 8192, 16384, 32768, 65536 }) + { + for(size_t r : { 1, 8, 16 }) + { + for(size_t p : { 1, 4 }) + { + std::unique_ptr<Timer> scrypt_timer = make_timer( + "scrypt-" + std::to_string(N) + "-" + + std::to_string(r) + "-" + std::to_string(p) + + " (" + std::to_string(Botan::scrypt_memory_usage(N, r, p) / (1024*1024)) + " MiB)"); + + uint8_t out[64]; + uint8_t salt[8]; + rng().randomize(salt, sizeof(salt)); + + while(scrypt_timer->under(msec)) + { + scrypt_timer->run([&] { + Botan::scrypt(out, sizeof(out), "password", + salt, sizeof(salt), N, r, p); + }); + } + + record_result(scrypt_timer); + + if(scrypt_timer->events() == 1) + break; + } + } + } + + } + +#endif + +#if defined(BOTAN_HAS_ARGON2) + + void bench_argon2(const std::string& /*provider*/, + std::chrono::milliseconds msec) + { + const uint8_t mode = 2; // Argon2id + + for(size_t M : { 8*1024, 64*1024, 256*1024 }) + { + for(size_t t : { 1, 2, 4 }) + { + for(size_t p : { 1 }) + { + std::unique_ptr<Timer> timer = make_timer( + "Argon2id M=" + std::to_string(M) + " t=" + std::to_string(t) + " p=" + std::to_string(p)); + + uint8_t out[64]; + uint8_t salt[16]; + rng().randomize(salt, sizeof(salt)); + + while(timer->under(msec)) + { + timer->run([&] { + Botan::argon2(out, sizeof(out), "password", 8, + salt, sizeof(salt), nullptr, 0, nullptr, 0, + mode, p, M, t); + }); + } + + record_result(timer); + } + } + } + } + +#endif + +#if defined(BOTAN_HAS_NEWHOPE) && defined(BOTAN_HAS_CHACHA_RNG) + void bench_newhope(const std::string& /*provider*/, + std::chrono::milliseconds msec) + { + const std::string nm = "NEWHOPE"; + + std::unique_ptr<Timer> keygen_timer = make_timer(nm, "", "keygen"); + std::unique_ptr<Timer> shareda_timer = make_timer(nm, "", "shareda"); + std::unique_ptr<Timer> sharedb_timer = make_timer(nm, "", "sharedb"); + + Botan::ChaCha_RNG nh_rng(Botan::secure_vector<uint8_t>(32)); + + while(sharedb_timer->under(msec)) + { + std::vector<uint8_t> send_a(Botan::NEWHOPE_SENDABYTES), send_b(Botan::NEWHOPE_SENDBBYTES); + std::vector<uint8_t> shared_a(32), shared_b(32); + + Botan::newhope_poly sk_a; + + keygen_timer->start(); + Botan::newhope_keygen(send_a.data(), &sk_a, nh_rng); + keygen_timer->stop(); + + sharedb_timer->start(); + Botan::newhope_sharedb(shared_b.data(), send_b.data(), send_a.data(), nh_rng); + sharedb_timer->stop(); + + shareda_timer->start(); + Botan::newhope_shareda(shared_a.data(), &sk_a, send_b.data()); + shareda_timer->stop(); + + BOTAN_ASSERT(shared_a == shared_b, "Same derived key"); + } + + record_result(keygen_timer); + record_result(shareda_timer); + record_result(sharedb_timer); + } +#endif + + }; + +BOTAN_REGISTER_COMMAND("speed", Speed); + +} diff --git a/comm/third_party/botan/src/cli/timing_tests.cpp b/comm/third_party/botan/src/cli/timing_tests.cpp new file mode 100644 index 0000000000..a9904ae2e8 --- /dev/null +++ b/comm/third_party/botan/src/cli/timing_tests.cpp @@ -0,0 +1,617 @@ +/* +* Timing Analysis Tests +* +* These tests are not for performance, but verifying that two inputs are not handled +* in a way that is vulnerable to simple timing attacks. +* +* Produces output which can be analyzed with the Mona reporting library +* +* $ git clone https://github.com/seecurity/mona-timing-report.git +* $ cd mona-timing-report && ant +* $ java -jar ReportingTool.jar --lowerBound=0.4 --upperBound=0.5 --inputFile=$file --name=$file +* +* (C) 2016 Juraj Somorovsky - juraj.somorovsky@hackmanit.de +* (C) 2017 Neverhub +* (C) 2017,2018,2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include <botan/hex.h> +#include <sstream> +#include <fstream> + +#include <botan/rng.h> +#include <botan/internal/os_utils.h> + +#if defined(BOTAN_HAS_BIGINT) + #include <botan/bigint.h> +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + #include <botan/numthry.h> +#endif + +#if defined(BOTAN_HAS_ECC_GROUP) + #include <botan/ec_group.h> +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + #include <botan/dl_group.h> +#endif + +#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_RAW) + #include <botan/pubkey.h> + #include <botan/rsa.h> +#endif + +#if defined(BOTAN_HAS_TLS_CBC) + #include <botan/internal/tls_cbc.h> + #include <botan/tls_exceptn.h> +#endif + +#if defined(BOTAN_HAS_ECDSA) + #include <botan/pubkey.h> + #include <botan/ecdsa.h> +#endif + +namespace Botan_CLI { + +typedef uint64_t ticks; + +class Timing_Test + { + public: + Timing_Test() + { + /* + A constant seed is ok here since the timing test rng just needs to be + "random" but not cryptographically secure - even std::rand() would be ok. + */ + const std::string drbg_seed(64, 'A'); + m_rng = cli_make_rng("", drbg_seed); // throws if it can't find anything to use + } + + virtual ~Timing_Test() = default; + + std::vector<std::vector<ticks>> execute_evaluation( + const std::vector<std::string>& inputs, + size_t warmup_runs, + size_t measurement_runs); + + virtual std::vector<uint8_t> prepare_input(const std::string& input) + { + return Botan::hex_decode(input); + } + + virtual ticks measure_critical_function(const std::vector<uint8_t>& input) = 0; + + protected: + static ticks get_ticks() + { + // Returns CPU counter or best approximation (monotonic clock of some kind) + //return Botan::OS::get_high_resolution_clock(); + return Botan::OS::get_system_timestamp_ns(); + } + + Botan::RandomNumberGenerator& timing_test_rng() + { + return (*m_rng); + } + + private: + std::unique_ptr<Botan::RandomNumberGenerator> m_rng; + }; + +#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_HAS_EME_RAW) + +class Bleichenbacker_Timing_Test final : public Timing_Test + { + public: + Bleichenbacker_Timing_Test(size_t keysize) + : m_privkey(timing_test_rng(), keysize) + , m_pubkey(m_privkey) + , m_enc(m_pubkey, timing_test_rng(), "Raw") + , m_dec(m_privkey, timing_test_rng(), "PKCS1v15") {} + + std::vector<uint8_t> prepare_input(const std::string& input) override + { + const std::vector<uint8_t> input_vector = Botan::hex_decode(input); + const std::vector<uint8_t> encrypted = m_enc.encrypt(input_vector, timing_test_rng()); + return encrypted; + } + + ticks measure_critical_function(const std::vector<uint8_t>& input) override + { + const ticks start = get_ticks(); + m_dec.decrypt_or_random(input.data(), m_ctext_length, m_expected_content_size, timing_test_rng()); + const ticks end = get_ticks(); + return (end - start); + } + + private: + const size_t m_expected_content_size = 48; + const size_t m_ctext_length = 256; + Botan::RSA_PrivateKey m_privkey; + Botan::RSA_PublicKey m_pubkey; + Botan::PK_Encryptor_EME m_enc; + Botan::PK_Decryptor_EME m_dec; + }; + +#endif + +#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_EME_RAW) + +/* +* Test Manger OAEP side channel +* +* "A Chosen Ciphertext Attack on RSA Optimal Asymmetric Encryption +* Padding (OAEP) as Standardized in PKCS #1 v2.0" James Manger +* http://archiv.infsec.ethz.ch/education/fs08/secsem/Manger01.pdf +*/ +class Manger_Timing_Test final : public Timing_Test + { + public: + Manger_Timing_Test(size_t keysize) + : m_privkey(timing_test_rng(), keysize) + , m_pubkey(m_privkey) + , m_enc(m_pubkey, timing_test_rng(), m_encrypt_padding) + , m_dec(m_privkey, timing_test_rng(), m_decrypt_padding) {} + + std::vector<uint8_t> prepare_input(const std::string& input) override + { + const std::vector<uint8_t> input_vector = Botan::hex_decode(input); + const std::vector<uint8_t> encrypted = m_enc.encrypt(input_vector, timing_test_rng()); + return encrypted; + } + + ticks measure_critical_function(const std::vector<uint8_t>& input) override + { + ticks start = get_ticks(); + try + { + m_dec.decrypt(input.data(), m_ctext_length); + } + catch(Botan::Decoding_Error&) + { + } + ticks end = get_ticks(); + + return (end - start); + } + + private: + const std::string m_encrypt_padding = "Raw"; + const std::string m_decrypt_padding = "EME1(SHA-256)"; + const size_t m_ctext_length = 256; + Botan::RSA_PrivateKey m_privkey; + Botan::RSA_PublicKey m_pubkey; + Botan::PK_Encryptor_EME m_enc; + Botan::PK_Decryptor_EME m_dec; + }; + +#endif + +#if defined(BOTAN_HAS_TLS_CBC) + +/* +* Test handling of countermeasure to the Lucky13 attack +*/ +class Lucky13_Timing_Test final : public Timing_Test + { + public: + Lucky13_Timing_Test(const std::string& mac_name, size_t mac_keylen) + : m_mac_algo(mac_name) + , m_mac_keylen(mac_keylen) + , m_dec(Botan::BlockCipher::create_or_throw("AES-128"), + Botan::MessageAuthenticationCode::create_or_throw("HMAC(" + m_mac_algo + ")"), + 16, m_mac_keylen, Botan::TLS::Protocol_Version::TLS_V11, false) {} + + std::vector<uint8_t> prepare_input(const std::string& input) override; + ticks measure_critical_function(const std::vector<uint8_t>& input) override; + + private: + const std::string m_mac_algo; + const size_t m_mac_keylen; + Botan::TLS::TLS_CBC_HMAC_AEAD_Decryption m_dec; + }; + +std::vector<uint8_t> Lucky13_Timing_Test::prepare_input(const std::string& input) + { + const std::vector<uint8_t> input_vector = Botan::hex_decode(input); + const std::vector<uint8_t> key(16); + const std::vector<uint8_t> iv(16); + + std::unique_ptr<Botan::Cipher_Mode> enc(Botan::Cipher_Mode::create("AES-128/CBC/NoPadding", Botan::ENCRYPTION)); + enc->set_key(key); + enc->start(iv); + Botan::secure_vector<uint8_t> buf(input_vector.begin(), input_vector.end()); + enc->finish(buf); + + return unlock(buf); + } + +ticks Lucky13_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) + { + Botan::secure_vector<uint8_t> data(input.begin(), input.end()); + Botan::secure_vector<uint8_t> aad(13); + const Botan::secure_vector<uint8_t> iv(16); + Botan::secure_vector<uint8_t> key(16 + m_mac_keylen); + + m_dec.set_key(unlock(key)); + m_dec.set_ad(unlock(aad)); + m_dec.start(unlock(iv)); + + ticks start = get_ticks(); + try + { + m_dec.finish(data); + } + catch(Botan::TLS::TLS_Exception&) + { + } + ticks end = get_ticks(); + return (end - start); + } + +#endif + +#if defined(BOTAN_HAS_ECDSA) + +class ECDSA_Timing_Test final : public Timing_Test + { + public: + ECDSA_Timing_Test(std::string ecgroup); + + ticks measure_critical_function(const std::vector<uint8_t>& input) override; + + private: + const Botan::EC_Group m_group; + const Botan::ECDSA_PrivateKey m_privkey; + const Botan::BigInt& m_x; + std::vector<Botan::BigInt> m_ws; + Botan::BigInt m_b, m_b_inv; + }; + +ECDSA_Timing_Test::ECDSA_Timing_Test(std::string ecgroup) + : m_group(ecgroup) + , m_privkey(timing_test_rng(), m_group) + , m_x(m_privkey.private_value()) + { + m_b = m_group.random_scalar(timing_test_rng()); + m_b_inv = m_group.inverse_mod_order(m_b); + } + +ticks ECDSA_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) + { + const Botan::BigInt k(input.data(), input.size()); + Botan::BigInt m(5); // fixed message to minimize noise + + ticks start = get_ticks(); + + // the following ECDSA operations involve and should not leak any information about k + const Botan::BigInt r = m_group.mod_order( + m_group.blinded_base_point_multiply_x(k, timing_test_rng(), m_ws)); + const Botan::BigInt k_inv = m_group.inverse_mod_order(k); + + m_b = m_group.square_mod_order(m_b); + m_b_inv = m_group.square_mod_order(m_b_inv); + + m = m_group.multiply_mod_order(m_b, m_group.mod_order(m)); + const Botan::BigInt xr_m = m_group.mod_order(m_group.multiply_mod_order(m_x, m_b, r) + m); + + const Botan::BigInt s = m_group.multiply_mod_order(k_inv, xr_m, m_b_inv); + + BOTAN_UNUSED(r, s); + + ticks end = get_ticks(); + + return (end - start); + } + +#endif + +#if defined(BOTAN_HAS_ECC_GROUP) + +class ECC_Mul_Timing_Test final : public Timing_Test + { + public: + ECC_Mul_Timing_Test(std::string ecgroup) : + m_group(ecgroup) + {} + + ticks measure_critical_function(const std::vector<uint8_t>& input) override; + + private: + const Botan::EC_Group m_group; + std::vector<Botan::BigInt> m_ws; + }; + +ticks ECC_Mul_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) + { + const Botan::BigInt k(input.data(), input.size()); + + ticks start = get_ticks(); + + const Botan::PointGFp k_times_P = m_group.blinded_base_point_multiply(k, timing_test_rng(), m_ws); + + ticks end = get_ticks(); + + return (end - start); + } + +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + +class Powmod_Timing_Test final : public Timing_Test + { + public: + Powmod_Timing_Test(const std::string& dl_group) : m_group(dl_group) + { + } + + ticks measure_critical_function(const std::vector<uint8_t>& input) override; + private: + Botan::DL_Group m_group; + }; + +ticks Powmod_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) + { + const Botan::BigInt x(input.data(), input.size()); + const size_t max_x_bits = m_group.p_bits(); + + ticks start = get_ticks(); + + const Botan::BigInt g_x_p = m_group.power_g_p(x, max_x_bits); + + ticks end = get_ticks(); + + return (end - start); + } + +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + +class Invmod_Timing_Test final : public Timing_Test + { + public: + Invmod_Timing_Test(size_t p_bits) + { + m_p = Botan::random_prime(timing_test_rng(), p_bits); + } + + ticks measure_critical_function(const std::vector<uint8_t>& input) override; + + private: + Botan::BigInt m_p; + }; + +ticks Invmod_Timing_Test::measure_critical_function(const std::vector<uint8_t>& input) + { + const Botan::BigInt k(input.data(), input.size()); + + ticks start = get_ticks(); + + const Botan::BigInt inv = inverse_mod(k, m_p); + + ticks end = get_ticks(); + + return (end - start); + } + +#endif + +std::vector<std::vector<ticks>> Timing_Test::execute_evaluation( + const std::vector<std::string>& raw_inputs, + size_t warmup_runs, size_t measurement_runs) + { + std::vector<std::vector<ticks>> all_results(raw_inputs.size()); + std::vector<std::vector<uint8_t>> inputs(raw_inputs.size()); + + for(auto& result : all_results) + { + result.reserve(measurement_runs); + } + + for(size_t i = 0; i != inputs.size(); ++i) + { + inputs[i] = prepare_input(raw_inputs[i]); + } + + // arbitrary upper bounds of 1 and 10 million resp + if(warmup_runs > 1000000 || measurement_runs > 100000000) + { + throw CLI_Error("Requested execution counts too large, rejecting"); + } + + size_t total_runs = 0; + std::vector<ticks> results(inputs.size()); + + while(total_runs < (warmup_runs + measurement_runs)) + { + for(size_t i = 0; i != inputs.size(); ++i) + { + results[i] = measure_critical_function(inputs[i]); + } + + total_runs++; + + if(total_runs > warmup_runs) + { + for(size_t i = 0; i != results.size(); ++i) + { + all_results[i].push_back(results[i]); + } + } + } + + return all_results; + } + +class Timing_Test_Command final : public Command + { + public: + Timing_Test_Command() + : Command("timing_test test_type --test-data-file= --test-data-dir=src/tests/data/timing " + "--warmup-runs=5000 --measurement-runs=50000") {} + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Run various timing side channel tests"; + } + + void go() override + { + const std::string test_type = get_arg("test_type"); + const size_t warmup_runs = get_arg_sz("warmup-runs"); + const size_t measurement_runs = get_arg_sz("measurement-runs"); + + std::unique_ptr<Timing_Test> test = lookup_timing_test(test_type); + + if(!test) + { + throw CLI_Error("Unknown or unavailable test type '" + test_type + "'"); + } + + std::string filename = get_arg_or("test-data-file", ""); + + if(filename.empty()) + { + const std::string test_data_dir = get_arg("test-data-dir"); + filename = test_data_dir + "/" + test_type + ".vec"; + } + + std::vector<std::string> lines = read_testdata(filename); + + std::vector<std::vector<ticks>> results = test->execute_evaluation(lines, warmup_runs, measurement_runs); + + size_t unique_id = 0; + std::ostringstream oss; + for(size_t secret_id = 0; secret_id != results.size(); ++secret_id) + { + for(size_t i = 0; i != results[secret_id].size(); ++i) + { + oss << unique_id++ << ";" << secret_id << ";" << results[secret_id][i] << "\n"; + } + } + + output() << oss.str(); + } + private: + + std::vector<std::string> read_testdata(const std::string& filename) + { + std::vector<std::string> lines; + std::ifstream infile(filename); + if(infile.good() == false) + { + throw CLI_Error("Error reading test data from '" + filename + "'"); + } + std::string line; + while(std::getline(infile, line)) + { + if(line.size() > 0 && line.at(0) != '#') + { + lines.push_back(line); + } + } + return lines; + } + + std::unique_ptr<Timing_Test> lookup_timing_test(const std::string& test_type); + + std::string help_text() const override + { + // TODO check feature macros + return (Command::help_text() + + "\ntest_type can take on values " + "bleichenbacher " + "manger " + "ecdsa " + "ecc_mul " + "inverse_mod " + "pow_mod " + "lucky13sec3 " + "lucky13sec4sha1 " + "lucky13sec4sha256 " + "lucky13sec4sha384 " + ); + } + }; + +BOTAN_REGISTER_COMMAND("timing_test", Timing_Test_Command); + +std::unique_ptr<Timing_Test> Timing_Test_Command::lookup_timing_test(const std::string& test_type) + { +#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_PKCS1) && defined(BOTAN_HAS_EME_RAW) + if(test_type == "bleichenbacher") + { + return std::unique_ptr<Timing_Test>(new Bleichenbacker_Timing_Test(2048)); + } +#endif + +#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_EME_RAW) + if(test_type == "manger") + { + return std::unique_ptr<Timing_Test>(new Manger_Timing_Test(2048)); + } +#endif + +#if defined(BOTAN_HAS_ECDSA) + if(test_type == "ecdsa") + { + return std::unique_ptr<Timing_Test>(new ECDSA_Timing_Test("secp384r1")); + } +#endif + +#if defined(BOTAN_HAS_ECC_GROUP) + if(test_type == "ecc_mul") + { + return std::unique_ptr<Timing_Test>(new ECC_Mul_Timing_Test("brainpool512r1")); + } +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + if(test_type == "inverse_mod") + { + return std::unique_ptr<Timing_Test>(new Invmod_Timing_Test(512)); + } +#endif + +#if defined(BOTAN_HAS_DL_GROUP) + if(test_type == "pow_mod") + { + return std::unique_ptr<Timing_Test>(new Powmod_Timing_Test("modp/ietf/1024")); + } +#endif + +#if defined(BOTAN_HAS_TLS_CBC) + if(test_type == "lucky13sec3" || test_type == "lucky13sec4sha1") + { + return std::unique_ptr<Timing_Test>(new Lucky13_Timing_Test("SHA-1", 20)); + } + if(test_type == "lucky13sec4sha256") + { + return std::unique_ptr<Timing_Test>(new Lucky13_Timing_Test("SHA-256", 32)); + } + if(test_type == "lucky13sec4sha384") + { + return std::unique_ptr<Timing_Test>(new Lucky13_Timing_Test("SHA-384", 48)); + } +#endif + + BOTAN_UNUSED(test_type); + + return nullptr; + } + + +} 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 diff --git a/comm/third_party/botan/src/cli/tls_helpers.h b/comm/third_party/botan/src/cli/tls_helpers.h new file mode 100644 index 0000000000..653a106e0b --- /dev/null +++ b/comm/third_party/botan/src/cli/tls_helpers.h @@ -0,0 +1,244 @@ +/* +* (C) 2014,2015,2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_CLI_TLS_HELPERS_H_ +#define BOTAN_CLI_TLS_HELPERS_H_ + +#include <botan/pkcs8.h> +#include <botan/credentials_manager.h> +#include <botan/tls_policy.h> +#include <botan/x509self.h> +#include <botan/data_src.h> +#include <memory> +#include <fstream> + +#include "cli_exceptions.h" + +#if defined(BOTAN_HAS_CERTSTOR_SYSTEM) + #include <botan/certstor_system.h> +#endif + +inline bool value_exists(const std::vector<std::string>& vec, + const std::string& val) + { + for(size_t i = 0; i != vec.size(); ++i) + { + if(vec[i] == val) + { + return true; + } + } + return false; + } + +class Basic_Credentials_Manager : public Botan::Credentials_Manager + { + public: + Basic_Credentials_Manager(bool use_system_store, + const std::string& ca_path) + { + if(ca_path.empty() == false) + { + m_certstores.push_back(std::make_shared<Botan::Certificate_Store_In_Memory>(ca_path)); + } + +#if defined(BOTAN_HAS_CERTSTOR_SYSTEM) + if(use_system_store) + { + m_certstores.push_back(std::make_shared<Botan::System_Certificate_Store>()); + } +#else + BOTAN_UNUSED(use_system_store); +#endif + } + + Basic_Credentials_Manager(Botan::RandomNumberGenerator& rng, + const std::string& server_crt, + const std::string& server_key) + { + Certificate_Info cert; + + cert.key.reset(Botan::PKCS8::load_key(server_key, rng)); + + Botan::DataSource_Stream in(server_crt); + while(!in.end_of_data()) + { + try + { + cert.certs.push_back(Botan::X509_Certificate(in)); + } + catch(std::exception&) + { + } + } + + // TODO: attempt to validate chain ourselves + + m_creds.push_back(cert); + } + + std::vector<Botan::Certificate_Store*> + trusted_certificate_authorities(const std::string& type, + const std::string& /*hostname*/) override + { + std::vector<Botan::Certificate_Store*> v; + + // don't ask for client certs + if(type == "tls-server") + { + return v; + } + + for(auto const& cs : m_certstores) + { + v.push_back(cs.get()); + } + + return v; + } + + std::vector<Botan::X509_Certificate> cert_chain( + const std::vector<std::string>& algos, + const std::string& type, + const std::string& hostname) override + { + BOTAN_UNUSED(type); + + for(auto const& i : m_creds) + { + if(std::find(algos.begin(), algos.end(), i.key->algo_name()) == algos.end()) + { + continue; + } + + if(hostname != "" && !i.certs[0].matches_dns_name(hostname)) + { + continue; + } + + return i.certs; + } + + return std::vector<Botan::X509_Certificate>(); + } + + Botan::Private_Key* private_key_for(const Botan::X509_Certificate& cert, + const std::string& /*type*/, + const std::string& /*context*/) override + { + for(auto const& i : m_creds) + { + if(cert == i.certs[0]) + { + return i.key.get(); + } + } + + return nullptr; + } + + private: + struct Certificate_Info + { + std::vector<Botan::X509_Certificate> certs; + std::shared_ptr<Botan::Private_Key> key; + }; + + std::vector<Certificate_Info> m_creds; + std::vector<std::shared_ptr<Botan::Certificate_Store>> m_certstores; + }; + +class TLS_All_Policy final : public Botan::TLS::Policy + { + public: + std::vector<std::string> allowed_ciphers() const override + { + return std::vector<std::string> + { + "ChaCha20Poly1305", + "AES-256/OCB(12)", + "AES-128/OCB(12)", + "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_key_exchange_methods() const override + { + return { "SRP_SHA", "ECDHE_PSK", "DHE_PSK", "PSK", "CECPQ1", "ECDH", "DH", "RSA" }; + } + + std::vector<std::string> allowed_signature_methods() const override + { + return { "ECDSA", "RSA", "DSA", "IMPLICIT" }; + } + + bool allow_tls10() const override { return true; } + bool allow_tls11() const override { return true; } + bool allow_tls12() const override { return true; } + }; + +inline std::unique_ptr<Botan::TLS::Policy> load_tls_policy(const std::string policy_type) + { + std::unique_ptr<Botan::TLS::Policy> policy; + + if(policy_type == "default" || policy_type == "") + { + policy.reset(new Botan::TLS::Policy); + } + else if(policy_type == "suiteb_128") + { + policy.reset(new Botan::TLS::NSA_Suite_B_128); + } + else if(policy_type == "suiteb_192" || policy_type == "suiteb") + { + policy.reset(new Botan::TLS::NSA_Suite_B_192); + } + else if(policy_type == "strict") + { + policy.reset(new Botan::TLS::Strict_Policy); + } + else if(policy_type == "bsi") + { + policy.reset(new Botan::TLS::BSI_TR_02102_2); + } + else if(policy_type == "datagram") + { + policy.reset(new Botan::TLS::Strict_Policy); + } + else if(policy_type == "all" || policy_type == "everything") + { + policy.reset(new TLS_All_Policy); + } + else + { + // assume it's a file + std::ifstream policy_stream(policy_type); + if(!policy_stream.good()) + { + throw Botan_CLI::CLI_Usage_Error("Unknown TLS policy: not a file or known short name"); + } + policy.reset(new Botan::TLS::Text_Policy(policy_stream)); + } + + return policy; + } + +#endif diff --git a/comm/third_party/botan/src/cli/tls_http_server.cpp b/comm/third_party/botan/src/cli/tls_http_server.cpp new file mode 100644 index 0000000000..aaf740fcf3 --- /dev/null +++ b/comm/third_party/botan/src/cli/tls_http_server.cpp @@ -0,0 +1,579 @@ +/* +* (C) 2014,2015,2017,2019 Jack Lloyd +* (C) 2016 Matthias Gierlings +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_BOOST_ASIO) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + +#include <iostream> +#include <fstream> +#include <iomanip> +#include <string> +#include <vector> +#include <thread> +#include <atomic> + +#define _GLIBCXX_HAVE_GTHR_DEFAULT +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <botan/internal/os_utils.h> + +#include <botan/tls_server.h> +#include <botan/tls_messages.h> +#include <botan/x509cert.h> +#include <botan/pkcs8.h> +#include <botan/version.h> +#include <botan/hex.h> +#include <botan/rng.h> + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + #include <botan/tls_session_manager_sqlite.h> +#endif + +#include "tls_helpers.h" + +#if BOOST_VERSION >= 107000 +#define GET_IO_SERVICE(s) (static_cast<boost::asio::io_context&>((s).get_executor().context())) +#else +#define GET_IO_SERVICE(s) ((s).get_io_service()) +#endif + +namespace Botan_CLI { + +namespace { + +using boost::asio::ip::tcp; + +inline void log_exception(const char* where, const std::exception& e) + { + std::cout << where << ' ' << e.what() << std::endl; + } + +class ServerStatus + { + public: + ServerStatus(size_t max_clients) : m_max_clients(max_clients), m_clients_serviced(0) {} + + bool should_exit() const + { + if(m_max_clients == 0) + return false; + + return clients_serviced() >= m_max_clients; + } + + void client_serviced() { m_clients_serviced++; } + + size_t clients_serviced() const { return m_clients_serviced.load(); } + private: + size_t m_max_clients; + std::atomic<size_t> m_clients_serviced; + }; + +/* +* This is an incomplete and highly buggy HTTP request parser. It is just +* barely sufficient to handle a GET request sent by a browser. +*/ +class HTTP_Parser final + { + public: + class Request + { + public: + const std::string& verb() const { return m_verb; } + const std::string& location() const { return m_location; } + const std::map<std::string, std::string>& headers() const { return m_headers; } + + Request(const std::string& verb, + const std::string& location, + const std::map<std::string, std::string>& headers) : + m_verb(verb), + m_location(location), + m_headers(headers) + {} + + private: + std::string m_verb; + std::string m_location; + std::map<std::string, std::string> m_headers; + }; + + class Callbacks + { + public: + virtual void handle_http_request(const Request& request) = 0; + virtual ~Callbacks() = default; + }; + + HTTP_Parser(Callbacks& cb) : m_cb(cb) {} + + void consume_input(const uint8_t buf[], size_t buf_len) + { + m_req_buf.append(reinterpret_cast<const char*>(buf), buf_len); + + std::istringstream strm(m_req_buf); + + std::string http_version; + std::string verb; + std::string location; + std::map<std::string, std::string> headers; + + strm >> verb >> location >> http_version; + + if(verb.empty() || location.empty()) + return; + + while(true) + { + std::string header_line; + std::getline(strm, header_line); + + if(header_line == "\r") + { + continue; + } + + auto delim = header_line.find(": "); + if(delim == std::string::npos) + { + break; + } + + const std::string hdr_name = header_line.substr(0, delim); + const std::string hdr_val = header_line.substr(delim + 2, std::string::npos); + + headers[hdr_name] = hdr_val; + + if(headers.size() > 1024) + throw Botan::Invalid_Argument("Too many HTTP headers sent in request"); + } + + if(verb != "" && location != "") + { + Request req(verb, location, headers); + m_cb.handle_http_request(req); + m_req_buf.clear(); + } + else + printf("ignoring\n"); + } + private: + Callbacks& m_cb; + std::string m_req_buf; + }; + +static const size_t READBUF_SIZE = 4096; + +class TLS_Asio_HTTP_Session final : public std::enable_shared_from_this<TLS_Asio_HTTP_Session>, + public Botan::TLS::Callbacks, + public HTTP_Parser::Callbacks + { + public: + typedef std::shared_ptr<TLS_Asio_HTTP_Session> pointer; + + static pointer create( + boost::asio::io_service& io, + Botan::TLS::Session_Manager& session_manager, + Botan::Credentials_Manager& credentials, + Botan::TLS::Policy& policy) + { + return pointer(new TLS_Asio_HTTP_Session(io, session_manager, credentials, policy)); + } + + tcp::socket& client_socket() + { + return m_client_socket; + } + + void start() + { + m_c2s.resize(READBUF_SIZE); + client_read(boost::system::error_code(), 0); // start read loop + } + + void stop() + { + m_tls.close(); + } + + private: + TLS_Asio_HTTP_Session(boost::asio::io_service& io, + Botan::TLS::Session_Manager& session_manager, + Botan::Credentials_Manager& credentials, + Botan::TLS::Policy& policy) + : m_strand(io) + , m_client_socket(io) + , m_rng(cli_make_rng()) + , m_tls(*this, session_manager, credentials, policy, *m_rng) {} + + void client_read(const boost::system::error_code& error, + size_t bytes_transferred) + { + if(error) + { + return stop(); + } + + try + { + m_tls.received_data(&m_c2s[0], bytes_transferred); + } + catch(Botan::Exception& e) + { + log_exception("TLS connection failed", e); + return stop(); + } + + m_client_socket.async_read_some( + boost::asio::buffer(&m_c2s[0], m_c2s.size()), + m_strand.wrap( + boost::bind( + &TLS_Asio_HTTP_Session::client_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + void handle_client_write_completion(const boost::system::error_code& error) + { + if(error) + { + return stop(); + } + + m_s2c.clear(); + + if(m_s2c_pending.empty() && m_tls.is_closed()) + { + m_client_socket.close(); + } + tls_emit_data(nullptr, 0); // initiate another write if needed + } + + std::string tls_server_choose_app_protocol(const std::vector<std::string>& /*client_protos*/) override + { + return "http/1.1"; + } + + void tls_record_received(uint64_t /*rec_no*/, const uint8_t buf[], size_t buf_len) override + { + if(!m_http_parser) + m_http_parser.reset(new HTTP_Parser(*this)); + + m_http_parser->consume_input(buf, buf_len); + } + + std::string summarize_request(const HTTP_Parser::Request& request) + { + std::ostringstream strm; + + strm << "Client " << client_socket().remote_endpoint().address().to_string() + << " requested " << request.verb() << " " << request.location() << "\n"; + + if(request.headers().empty() == false) + { + strm << "Client HTTP headers:\n"; + for(auto kv : request.headers()) + strm << " " << kv.first << ": " << kv.second << "\n"; + } + + return strm.str(); + } + + void handle_http_request(const HTTP_Parser::Request& request) override + { + std::ostringstream response; + if(request.verb() == "GET") + { + if(request.location() == "/" || request.location() == "/status") + { + const std::string http_summary = summarize_request(request); + + const std::string report = m_session_summary + m_chello_summary + http_summary; + + response << "HTTP/1.0 200 OK\r\n"; + response << "Server: " << Botan::version_string() << "\r\n"; + response << "Content-Type: text/plain\r\n"; + response << "Content-Length: " << report.size() << "\r\n"; + response << "\r\n"; + + response << report; + } + else + { + response << "HTTP/1.0 404 Not Found\r\n\r\n"; + } + } + else + { + response << "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; + } + + const std::string response_str = response.str(); + m_tls.send(response_str); + m_tls.close(); + } + + void tls_emit_data(const uint8_t buf[], size_t buf_len) override + { + if(buf_len > 0) + { + m_s2c_pending.insert(m_s2c_pending.end(), buf, buf + buf_len); + } + + // no write now active and we still have output pending + if(m_s2c.empty() && !m_s2c_pending.empty()) + { + std::swap(m_s2c_pending, m_s2c); + + boost::asio::async_write( + m_client_socket, + boost::asio::buffer(&m_s2c[0], m_s2c.size()), + m_strand.wrap( + boost::bind( + &TLS_Asio_HTTP_Session::handle_client_write_completion, + shared_from_this(), + boost::asio::placeholders::error))); + } + } + + bool tls_session_established(const Botan::TLS::Session& session) override + { + std::ostringstream strm; + + strm << "TLS negotiation with " << Botan::version_string() << " test server\n\n"; + + strm << "Version: " << session.version().to_string() << "\n"; + strm << "Ciphersuite: " << session.ciphersuite().to_string() << "\n"; + if(session.session_id().empty() == false) + { + strm << "SessionID: " << Botan::hex_encode(session.session_id()) << "\n"; + } + if(session.server_info().hostname() != "") + { + strm << "SNI: " << session.server_info().hostname() << "\n"; + } + + m_session_summary = strm.str(); + return true; + } + + void tls_inspect_handshake_msg(const Botan::TLS::Handshake_Message& message) override + { + if(message.type() == Botan::TLS::CLIENT_HELLO) + { + const Botan::TLS::Client_Hello& client_hello = dynamic_cast<const Botan::TLS::Client_Hello&>(message); + + std::ostringstream strm; + + strm << "Client random: " << Botan::hex_encode(client_hello.random()) << "\n"; + + strm << "Client offered following ciphersuites:\n"; + for(uint16_t suite_id : client_hello.ciphersuites()) + { + Botan::TLS::Ciphersuite ciphersuite = Botan::TLS::Ciphersuite::by_id(suite_id); + + strm << " - 0x" + << std::hex << std::setfill('0') << std::setw(4) << suite_id + << std::dec << std::setfill(' ') << std::setw(0) << " "; + + if(ciphersuite.valid()) + strm << ciphersuite.to_string() << "\n"; + else if(suite_id == 0x00FF) + strm << "Renegotiation SCSV\n"; + else + strm << "Unknown ciphersuite\n"; + } + + m_chello_summary = strm.str(); + } + + } + + void tls_alert(Botan::TLS::Alert alert) override + { + if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY) + { + m_tls.close(); + return; + } + else + { + std::cout << "Alert " << alert.type_string() << std::endl; + } + } + + boost::asio::io_service::strand m_strand; + + tcp::socket m_client_socket; + + std::unique_ptr<Botan::RandomNumberGenerator> m_rng; + Botan::TLS::Server m_tls; + std::string m_chello_summary; + std::string m_session_summary; + std::unique_ptr<HTTP_Parser> m_http_parser; + + std::vector<uint8_t> m_c2s; + std::vector<uint8_t> m_s2c; + std::vector<uint8_t> m_s2c_pending; + }; + +class TLS_Asio_HTTP_Server final + { + public: + typedef TLS_Asio_HTTP_Session session; + + TLS_Asio_HTTP_Server( + boost::asio::io_service& io, unsigned short port, + Botan::Credentials_Manager& creds, + Botan::TLS::Policy& policy, + Botan::TLS::Session_Manager& session_mgr, + size_t max_clients) + : m_acceptor(io, tcp::endpoint(tcp::v4(), port)) + , m_creds(creds) + , m_policy(policy) + , m_session_manager(session_mgr) + , m_status(max_clients) + { + session::pointer new_session = make_session(); + + m_acceptor.async_accept( + new_session->client_socket(), + boost::bind( + &TLS_Asio_HTTP_Server::handle_accept, + this, + new_session, + boost::asio::placeholders::error)); + } + + private: + session::pointer make_session() + { + return session::create( + GET_IO_SERVICE(m_acceptor), + m_session_manager, + m_creds, + m_policy); + } + + void handle_accept(session::pointer new_session, + const boost::system::error_code& error) + { + if(!error) + { + new_session->start(); + new_session = make_session(); + + m_status.client_serviced(); + + if(m_status.should_exit() == false) + { + m_acceptor.async_accept( + new_session->client_socket(), + boost::bind( + &TLS_Asio_HTTP_Server::handle_accept, + this, + new_session, + boost::asio::placeholders::error)); + } + } + } + + tcp::acceptor m_acceptor; + + Botan::Credentials_Manager& m_creds; + Botan::TLS::Policy& m_policy; + Botan::TLS::Session_Manager& m_session_manager; + ServerStatus m_status; + }; + +} + +class TLS_HTTP_Server final : public Command + { + public: + TLS_HTTP_Server() : Command("tls_http_server server_cert server_key " + "--port=443 --policy=default --threads=0 --max-clients=0 " + "--session-db= --session-db-pass=") {} + + std::string group() const override + { + return "tls"; + } + + std::string description() const override + { + return "Provides a simple HTTP server"; + } + + size_t thread_count() const + { + if(size_t t = get_arg_sz("threads")) + return t; + if(size_t t = Botan::OS::get_cpu_available()) + return t; + return 2; + } + + void go() override + { + const uint16_t listen_port = get_arg_u16("port"); + + const std::string server_crt = get_arg("server_cert"); + const std::string server_key = get_arg("server_key"); + + const size_t num_threads = thread_count(); + const size_t max_clients = get_arg_sz("max-clients"); + + Basic_Credentials_Manager creds(rng(), server_crt, server_key); + + auto policy = load_tls_policy(get_arg("policy")); + + std::unique_ptr<Botan::TLS::Session_Manager> session_mgr; + + const std::string sessions_db = get_arg("session-db"); + + 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 + throw CLI_Error_Unsupported("Sqlite3 support not available"); +#endif + } + + if(!session_mgr) + { + session_mgr.reset(new Botan::TLS::Session_Manager_In_Memory(rng())); + } + + boost::asio::io_service io; + + TLS_Asio_HTTP_Server server(io, listen_port, creds, *policy, *session_mgr, max_clients); + + std::vector<std::shared_ptr<std::thread>> threads; + + // run forever... first thread is main calling io.run below + for(size_t i = 2; i <= num_threads; ++i) + { + threads.push_back(std::make_shared<std::thread>([&io]() { io.run(); })); + } + + io.run(); + + for(size_t i = 0; i < threads.size(); ++i) + { + threads[i]->join(); + } + } + }; + +BOTAN_REGISTER_COMMAND("tls_http_server", TLS_HTTP_Server); + +} + +#endif diff --git a/comm/third_party/botan/src/cli/tls_proxy.cpp b/comm/third_party/botan/src/cli/tls_proxy.cpp new file mode 100644 index 0000000000..bd96530c20 --- /dev/null +++ b/comm/third_party/botan/src/cli/tls_proxy.cpp @@ -0,0 +1,526 @@ +/* +* TLS Server Proxy +* (C) 2014,2015,2019 Jack Lloyd +* (C) 2016 Matthias Gierlings +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_BOOST_ASIO) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + +#include <iostream> +#include <string> +#include <vector> +#include <thread> +#include <atomic> + +#define _GLIBCXX_HAVE_GTHR_DEFAULT +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <botan/internal/os_utils.h> + +#include <botan/tls_server.h> +#include <botan/x509cert.h> +#include <botan/pkcs8.h> +#include <botan/hex.h> +#include <botan/rng.h> + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + #include <botan/tls_session_manager_sqlite.h> +#endif + +#include "tls_helpers.h" + +#if BOOST_VERSION >= 107000 +#define GET_IO_SERVICE(s) (static_cast<boost::asio::io_context&>((s).get_executor().context())) +#else +#define GET_IO_SERVICE(s) ((s).get_io_service()) +#endif + +namespace Botan_CLI { + +namespace { + +using boost::asio::ip::tcp; + +void log_exception(const char* where, const std::exception& e) + { + std::cout << where << ' ' << e.what() << std::endl; + } + +void log_error(const char* where, const boost::system::error_code& error) + { + std::cout << where << ' ' << error.message() << std::endl; + } + +void log_binary_message(const char* where, const uint8_t buf[], size_t buf_len) + { + BOTAN_UNUSED(where, buf, buf_len); + //std::cout << where << ' ' << Botan::hex_encode(buf, buf_len) << std::endl; + } + +void log_text_message(const char* where, const uint8_t buf[], size_t buf_len) + { + BOTAN_UNUSED(where, buf, buf_len); + //const char* c = reinterpret_cast<const char*>(buf); + //std::cout << where << ' ' << std::string(c, c + buf_len) << std::endl; + } + +class ServerStatus + { + public: + ServerStatus(size_t max_clients) : m_max_clients(max_clients), m_clients_serviced(0) {} + + bool should_exit() const + { + if(m_max_clients == 0) + return false; + + return clients_serviced() >= m_max_clients; + } + + void client_serviced() { m_clients_serviced++; } + + size_t clients_serviced() const { return m_clients_serviced.load(); } + private: + size_t m_max_clients; + std::atomic<size_t> m_clients_serviced; + }; + +class tls_proxy_session final : public std::enable_shared_from_this<tls_proxy_session>, + public Botan::TLS::Callbacks + { + public: + enum { readbuf_size = 17 * 1024 }; + + typedef std::shared_ptr<tls_proxy_session> pointer; + + static pointer create( + boost::asio::io_service& io, + Botan::TLS::Session_Manager& session_manager, + Botan::Credentials_Manager& credentials, + Botan::TLS::Policy& policy, + tcp::resolver::iterator endpoints) + { + return pointer( + new tls_proxy_session( + io, + session_manager, + credentials, + policy, + endpoints) + ); + } + + tcp::socket& client_socket() + { + return m_client_socket; + } + + void start() + { + m_c2p.resize(readbuf_size); + client_read(boost::system::error_code(), 0); // start read loop + } + + void stop() + { + if(m_is_closed == false) + { + /* + Don't need to talk to the server anymore + Client socket is closed during write callback + */ + m_server_socket.close(); + m_tls.close(); + m_is_closed = true; + } + } + + private: + tls_proxy_session( + boost::asio::io_service& io, + Botan::TLS::Session_Manager& session_manager, + Botan::Credentials_Manager& credentials, + Botan::TLS::Policy& policy, + tcp::resolver::iterator endpoints) + : m_strand(io) + , m_server_endpoints(endpoints) + , m_client_socket(io) + , m_server_socket(io) + , m_rng(cli_make_rng()) + , m_tls(*this, + session_manager, + credentials, + policy, + *m_rng) {} + + void client_read(const boost::system::error_code& error, + size_t bytes_transferred) + { + if(error) + { + log_error("Read failed", error); + stop(); + return; + } + + try + { + if(!m_tls.is_active()) + { + log_binary_message("From client", &m_c2p[0], bytes_transferred); + } + m_tls.received_data(&m_c2p[0], bytes_transferred); + } + catch(Botan::Exception& e) + { + log_exception("TLS connection failed", e); + stop(); + return; + } + + m_client_socket.async_read_some( + boost::asio::buffer(&m_c2p[0], m_c2p.size()), + m_strand.wrap( + boost::bind( + &tls_proxy_session::client_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + void handle_client_write_completion(const boost::system::error_code& error) + { + if(error) + { + log_error("Client write", error); + stop(); + return; + } + + m_p2c.clear(); + + if(m_p2c_pending.empty() && m_tls.is_closed()) + { + m_client_socket.close(); + } + tls_emit_data(nullptr, 0); // initiate another write if needed + } + + void handle_server_write_completion(const boost::system::error_code& error) + { + if(error) + { + log_error("Server write", error); + stop(); + return; + } + + m_p2s.clear(); + proxy_write_to_server(nullptr, 0); // initiate another write if needed + } + + void tls_record_received(uint64_t /*rec_no*/, const uint8_t buf[], size_t buf_len) override + { + // Immediately bounce message to server + proxy_write_to_server(buf, buf_len); + } + + void tls_emit_data(const uint8_t buf[], size_t buf_len) override + { + if(buf_len > 0) + { + m_p2c_pending.insert(m_p2c_pending.end(), buf, buf + buf_len); + } + + // no write now active and we still have output pending + if(m_p2c.empty() && !m_p2c_pending.empty()) + { + std::swap(m_p2c_pending, m_p2c); + + log_binary_message("To Client", &m_p2c[0], m_p2c.size()); + + boost::asio::async_write( + m_client_socket, + boost::asio::buffer(&m_p2c[0], m_p2c.size()), + m_strand.wrap( + boost::bind( + &tls_proxy_session::handle_client_write_completion, + shared_from_this(), + boost::asio::placeholders::error))); + } + } + + void proxy_write_to_server(const uint8_t buf[], size_t buf_len) + { + if(buf_len > 0) + { + m_p2s_pending.insert(m_p2s_pending.end(), buf, buf + buf_len); + } + + // no write now active and we still have output pending + if(m_p2s.empty() && !m_p2s_pending.empty()) + { + std::swap(m_p2s_pending, m_p2s); + + log_text_message("To Server", &m_p2s[0], m_p2s.size()); + + boost::asio::async_write( + m_server_socket, + boost::asio::buffer(&m_p2s[0], m_p2s.size()), + m_strand.wrap( + boost::bind( + &tls_proxy_session::handle_server_write_completion, + shared_from_this(), + boost::asio::placeholders::error))); + } + } + + void server_read(const boost::system::error_code& error, + size_t bytes_transferred) + { + if(error) + { + log_error("Server read failed", error); + stop(); + return; + } + + try + { + if(bytes_transferred) + { + log_text_message("Server to client", &m_s2p[0], m_s2p.size()); + log_binary_message("Server to client", &m_s2p[0], m_s2p.size()); + m_tls.send(&m_s2p[0], bytes_transferred); + } + } + catch(Botan::Exception& e) + { + log_exception("TLS connection failed", e); + stop(); + return; + } + + m_s2p.resize(readbuf_size); + + m_server_socket.async_read_some( + boost::asio::buffer(&m_s2p[0], m_s2p.size()), + m_strand.wrap( + boost::bind(&tls_proxy_session::server_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + bool tls_session_established(const Botan::TLS::Session& session) override + { + m_hostname = session.server_info().hostname(); + + auto onConnect = [this](boost::system::error_code ec, tcp::resolver::iterator /*endpoint*/) + { + if(ec) + { + log_error("Server connection", ec); + return; + } + server_read(boost::system::error_code(), 0); // start read loop + proxy_write_to_server(nullptr, 0); + }; + async_connect(m_server_socket, m_server_endpoints, onConnect); + return true; + } + + void tls_alert(Botan::TLS::Alert alert) override + { + if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY) + { + m_tls.close(); + return; + } + } + + boost::asio::io_service::strand m_strand; + + tcp::resolver::iterator m_server_endpoints; + + tcp::socket m_client_socket; + tcp::socket m_server_socket; + + std::unique_ptr<Botan::RandomNumberGenerator> m_rng; + Botan::TLS::Server m_tls; + std::string m_hostname; + + std::vector<uint8_t> m_c2p; + std::vector<uint8_t> m_p2c; + std::vector<uint8_t> m_p2c_pending; + + std::vector<uint8_t> m_s2p; + std::vector<uint8_t> m_p2s; + std::vector<uint8_t> m_p2s_pending; + + bool m_is_closed = false; + }; + +class tls_proxy_server final + { + public: + typedef tls_proxy_session session; + + tls_proxy_server( + boost::asio::io_service& io, unsigned short port, + tcp::resolver::iterator endpoints, + Botan::Credentials_Manager& creds, + Botan::TLS::Policy& policy, + Botan::TLS::Session_Manager& session_mgr, + size_t max_clients) + : m_acceptor(io, tcp::endpoint(tcp::v4(), port)) + , m_server_endpoints(endpoints) + , m_creds(creds) + , m_policy(policy) + , m_session_manager(session_mgr) + , m_status(max_clients) + { + session::pointer new_session = make_session(); + + m_acceptor.async_accept( + new_session->client_socket(), + boost::bind( + &tls_proxy_server::handle_accept, + this, + new_session, + boost::asio::placeholders::error)); + } + + private: + session::pointer make_session() + { + return session::create( + GET_IO_SERVICE(m_acceptor), + m_session_manager, + m_creds, + m_policy, + m_server_endpoints); + } + + void handle_accept(session::pointer new_session, + const boost::system::error_code& error) + { + if(!error) + { + new_session->start(); + new_session = make_session(); + + m_status.client_serviced(); + + if(m_status.should_exit() == false) + { + m_acceptor.async_accept( + new_session->client_socket(), + boost::bind( + &tls_proxy_server::handle_accept, + this, + new_session, + boost::asio::placeholders::error)); + } + } + } + + tcp::acceptor m_acceptor; + tcp::resolver::iterator m_server_endpoints; + + Botan::Credentials_Manager& m_creds; + Botan::TLS::Policy& m_policy; + Botan::TLS::Session_Manager& m_session_manager; + ServerStatus m_status; + }; + +} + +class TLS_Proxy final : public Command + { + public: + TLS_Proxy() : Command("tls_proxy listen_port target_host target_port server_cert server_key " + "--policy=default --threads=0 --max-clients=0 --session-db= --session-db-pass=") {} + + std::string group() const override + { + return "tls"; + } + + std::string description() const override + { + return "Proxies requests between a TLS client and a TLS server"; + } + + size_t thread_count() const + { + if(size_t t = get_arg_sz("threads")) + return t; + if(size_t t = Botan::OS::get_cpu_available()) + return t; + return 2; + } + + void go() override + { + const uint16_t listen_port = get_arg_u16("listen_port"); + const std::string target = get_arg("target_host"); + const std::string target_port = get_arg("target_port"); + + const std::string server_crt = get_arg("server_cert"); + const std::string server_key = get_arg("server_key"); + + const size_t num_threads = thread_count(); + const size_t max_clients = get_arg_sz("max-clients"); + + Basic_Credentials_Manager creds(rng(), server_crt, server_key); + + auto policy = load_tls_policy(get_arg("policy")); + + boost::asio::io_service io; + + tcp::resolver resolver(io); + auto server_endpoint_iterator = resolver.resolve({ target, target_port }); + + std::unique_ptr<Botan::TLS::Session_Manager> session_mgr; + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass"); + const std::string sessions_db = get_arg("session-db"); + + 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())); + } + + tls_proxy_server server(io, listen_port, server_endpoint_iterator, creds, *policy, *session_mgr, max_clients); + + std::vector<std::shared_ptr<std::thread>> threads; + + // run forever... first thread is main calling io.run below + for(size_t i = 2; i <= num_threads; ++i) + { + threads.push_back(std::make_shared<std::thread>([&io]() { io.run(); })); + } + + io.run(); + + for(size_t i = 0; i < threads.size(); ++i) + { + threads[i]->join(); + } + } + }; + +BOTAN_REGISTER_COMMAND("tls_proxy", TLS_Proxy); + +} + +#endif diff --git a/comm/third_party/botan/src/cli/tls_server.cpp b/comm/third_party/botan/src/cli/tls_server.cpp new file mode 100644 index 0000000000..c39061e64d --- /dev/null +++ b/comm/third_party/botan/src/cli/tls_server.cpp @@ -0,0 +1,364 @@ +/* +* TLS echo server using BSD sockets +* (C) 2014 Jack Lloyd +* 2017 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include "sandbox.h" + +#if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && \ + defined(BOTAN_TARGET_OS_HAS_SOCKETS) + +#if defined(SO_USER_COOKIE) +#define SOCKET_ID 1 +#else +#define SOCKET_ID 0 +#endif + +#include <botan/tls_server.h> +#include <botan/tls_policy.h> +#include <botan/hex.h> +#include <botan/internal/os_utils.h> +#include <botan/mem_ops.h> + +#include <list> +#include <fstream> + +#include "tls_helpers.h" +#include "socket_utils.h" + +namespace Botan_CLI { + +class TLS_Server final : public Command, public Botan::TLS::Callbacks + { + public: +#if SOCKET_ID + TLS_Server() : Command("tls_server cert key --port=443 --type=tcp --policy=default --dump-traces= --max-clients=0 --socket-id=0") +#else + TLS_Server() : Command("tls_server cert key --port=443 --type=tcp --policy=default --dump-traces= --max-clients=0") +#endif + { + init_sockets(); + } + + ~TLS_Server() + { + stop_sockets(); + } + + std::string group() const override + { + return "tls"; + } + + std::string description() const override + { + return "Accept TLS/DTLS connections from TLS/DTLS clients"; + } + + void go() override + { + const std::string server_crt = get_arg("cert"); + const std::string server_key = get_arg("key"); + const uint16_t port = get_arg_u16("port"); + const size_t max_clients = get_arg_sz("max-clients"); + const std::string transport = get_arg("type"); + const std::string dump_traces_to = get_arg("dump-traces"); +#if SOCKET_ID + m_socket_id = get_arg_sz("socket-id"); +#endif + + if(transport != "tcp" && transport != "udp") + { + throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS"); + } + + m_is_tcp = (transport == "tcp"); + + auto policy = load_tls_policy(get_arg("policy")); + + Botan::TLS::Session_Manager_In_Memory session_manager(rng()); // TODO sqlite3 + + Basic_Credentials_Manager creds(rng(), server_crt, server_key); + + output() << "Listening for new connections on " << transport << " port " << port << std::endl; + + if(!m_sandbox.init()) + { + error_output() << "Failed sandboxing\n"; + return; + } + + socket_type server_fd = make_server_socket(port); + size_t clients_served = 0; + + while(true) + { + if(max_clients > 0 && clients_served >= max_clients) + break; + + if(m_is_tcp) + { + m_socket = ::accept(server_fd, nullptr, nullptr); + } + else + { + struct sockaddr_in from; + socklen_t from_len = sizeof(sockaddr_in); + + void* peek_buf = nullptr; + size_t peek_len = 0; + +#if defined(BOTAN_TARGET_OS_IS_MACOS) + // macOS handles zero size buffers differently - it will return 0 even if there's no incoming data, + // and after that connect() will fail as sockaddr_in from is not initialized + int dummy; + peek_buf = &dummy; + peek_len = sizeof(dummy); +#endif + + if(::recvfrom(server_fd, static_cast<char*>(peek_buf), static_cast<sendrecv_len_type>(peek_len), + MSG_PEEK, reinterpret_cast<struct sockaddr*>(&from), &from_len) != 0) + { + throw CLI_Error("Could not peek next packet"); + } + + if(::connect(server_fd, reinterpret_cast<struct sockaddr*>(&from), from_len) != 0) + { + throw CLI_Error("Could not connect UDP socket"); + } + m_socket = server_fd; + } + + clients_served++; + + Botan::TLS::Server server( + *this, + session_manager, + creds, + *policy, + rng(), + m_is_tcp == false); + + std::unique_ptr<std::ostream> dump_stream; + + if(!dump_traces_to.empty()) + { + uint64_t timestamp = Botan::OS::get_high_resolution_clock(); + const std::string dump_file = + dump_traces_to + "/tls_" + std::to_string(timestamp) + ".bin"; + dump_stream.reset(new std::ofstream(dump_file.c_str())); + } + + try + { + while(!server.is_closed()) + { + try + { + uint8_t buf[4 * 1024] = { 0 }; + ssize_t got = ::recv(m_socket, Botan::cast_uint8_ptr_to_char(buf), sizeof(buf), 0); + + if(got == -1) + { + error_output() << "Error in socket read - " << err_to_string(errno) << std::endl; + break; + } + + if(got == 0) + { + error_output() << "EOF on socket" << std::endl; + break; + } + + if(dump_stream) + { + dump_stream->write(reinterpret_cast<const char*>(buf), got); + } + + server.received_data(buf, got); + + while(server.is_active() && !m_pending_output.empty()) + { + std::string output = m_pending_output.front(); + m_pending_output.pop_front(); + server.send(output); + + if(output == "quit\n") + { + server.close(); + } + } + } + catch(std::exception& e) + { + error_output() << "Connection problem: " << e.what() << std::endl; + if(m_is_tcp) + { + close_socket(m_socket); + m_socket = invalid_socket(); + } + } + } + } + catch(Botan::Exception& e) + { + error_output() << "Connection failed: " << e.what() << "\n"; + } + + if(m_is_tcp) + { + close_socket(m_socket); + m_socket = invalid_socket(); + } + } + + close_socket(server_fd); + } + private: + socket_type make_server_socket(uint16_t port) + { + const int type = m_is_tcp ? SOCK_STREAM : SOCK_DGRAM; + + socket_type fd = ::socket(PF_INET, type, 0); + if(fd == invalid_socket()) + { + throw CLI_Error("Unable to acquire socket"); + } + + sockaddr_in socket_info; + Botan::clear_mem(&socket_info, 1); + socket_info.sin_family = AF_INET; + socket_info.sin_port = htons(port); + + // FIXME: support limiting listeners + socket_info.sin_addr.s_addr = INADDR_ANY; + + if(::bind(fd, reinterpret_cast<struct sockaddr*>(&socket_info), sizeof(struct sockaddr)) != 0) + { + close_socket(fd); + throw CLI_Error("server bind failed"); + } + + if(m_is_tcp) + { + if(::listen(fd, 100) != 0) + { + close_socket(fd); + throw CLI_Error("listen failed"); + } + } + if(m_socket_id > 0) + { +#if SOCKET_ID + // Other oses could have other means to trace sockets +#if defined(SO_USER_COOKIE) + if(::setsockopt(fd, SOL_SOCKET, SO_USER_COOKIE, reinterpret_cast<const void *>(&m_socket_id), sizeof(m_socket_id)) != 0) + { + // Failed but not world-ending issue + output() << "set socket cookie id failed" << std::endl; + } +#endif +#endif + } + return fd; + } + + bool tls_session_established(const Botan::TLS::Session& session) override + { + output() << "Handshake complete, " << session.version().to_string() + << " using " << session.ciphersuite().to_string() << std::endl; + + if(!session.session_id().empty()) + { + output() << "Session ID " << Botan::hex_encode(session.session_id()) << std::endl; + } + + if(!session.session_ticket().empty()) + { + output() << "Session ticket " << Botan::hex_encode(session.session_ticket()) << std::endl; + } + + return true; + } + + void tls_record_received(uint64_t, const uint8_t input[], size_t input_len) override + { + for(size_t i = 0; i != input_len; ++i) + { + const char c = static_cast<char>(input[i]); + m_line_buf += c; + if(c == '\n') + { + m_pending_output.push_back(m_line_buf); + m_line_buf.clear(); + } + } + } + + void tls_emit_data(const uint8_t buf[], size_t length) override + { + if(m_is_tcp) + { + ssize_t sent = ::send(m_socket, buf, static_cast<sendrecv_len_type>(length), MSG_NOSIGNAL); + + if(sent == -1) + { + error_output() << "Error writing to socket - " << err_to_string(errno) << std::endl; + } + else if(sent != static_cast<ssize_t>(length)) + { + error_output() << "Packet of length " << length << " truncated to " << sent << std::endl; + } + } + else + { + while(length) + { + ssize_t sent = ::send(m_socket, buf, static_cast<sendrecv_len_type>(length), MSG_NOSIGNAL); + + if(sent == -1) + { + if(errno == EINTR) + { + sent = 0; + } + else + { + throw CLI_Error("Socket write failed"); + } + } + + buf += sent; + length -= sent; + } + } + } + + void tls_alert(Botan::TLS::Alert alert) override + { + output() << "Alert: " << alert.type_string() << std::endl; + } + + std::string tls_server_choose_app_protocol(const std::vector<std::string>&) override + { + // we ignore whatever the client sends here + return "echo/0.1"; + } + + socket_type m_socket = invalid_socket(); + bool m_is_tcp = false; + uint32_t m_socket_id = 0; + std::string m_line_buf; + std::list<std::string> m_pending_output; + Sandbox m_sandbox; + }; + +BOTAN_REGISTER_COMMAND("tls_server", TLS_Server); + +} + +#endif diff --git a/comm/third_party/botan/src/cli/tls_utils.cpp b/comm/third_party/botan/src/cli/tls_utils.cpp new file mode 100644 index 0000000000..c98cbc50f4 --- /dev/null +++ b/comm/third_party/botan/src/cli/tls_utils.cpp @@ -0,0 +1,226 @@ +/* +* (C) 2016 Jack Lloyd +* +* 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) + +#include <botan/tls_policy.h> +#include <botan/tls_version.h> +#include <botan/tls_messages.h> +#include <botan/loadstor.h> +#include <botan/hex.h> +#include <sstream> + +#include "tls_helpers.h" + +namespace Botan_CLI { + +class TLS_Ciphersuites final : public Command + { + public: + TLS_Ciphersuites() + : Command("tls_ciphers --policy=default --version=tls1.2") {} + + static Botan::TLS::Protocol_Version::Version_Code tls_version_from_str(const std::string& str) + { + if(str == "tls1.2" || str == "TLS1.2" || str == "TLS-1.2") + { + return Botan::TLS::Protocol_Version::TLS_V12; + } + else if(str == "tls1.1" || str == "TLS1.1" || str == "TLS-1.1") + { + return Botan::TLS::Protocol_Version::TLS_V11; + } + else if(str == "tls1.0" || str == "TLS1.1" || str == "TLS-1.1") + { + return Botan::TLS::Protocol_Version::TLS_V10; + } + if(str == "dtls1.2" || str == "DTLS1.2" || str == "DTLS-1.2") + { + return Botan::TLS::Protocol_Version::DTLS_V12; + } + else if(str == "dtls1.0" || str == "DTLS1.0" || str == "DTLS-1.0") + { + return Botan::TLS::Protocol_Version::DTLS_V10; + } + else + { + throw CLI_Error("Unknown TLS version '" + str + "'"); + } + } + + std::string group() const override + { + return "tls"; + } + + std::string description() const override + { + return "Lists all ciphersuites for a policy and TLS version"; + } + + void go() override + { + const std::string policy_type = get_arg("policy"); + const Botan::TLS::Protocol_Version version(tls_version_from_str(get_arg("version"))); + const bool with_srp = false; // fixme + + auto policy = load_tls_policy(policy_type); + + if(policy->acceptable_protocol_version(version) == false) + { + error_output() << "Error: the policy specified does not allow the given TLS version\n"; + return; + } + + for(uint16_t suite_id : policy->ciphersuite_list(version, with_srp)) + { + const Botan::TLS::Ciphersuite suite(Botan::TLS::Ciphersuite::by_id(suite_id)); + output() << suite.to_string() << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("tls_ciphers", TLS_Ciphersuites); + +class TLS_Client_Hello_Reader final : public Command + { + public: + TLS_Client_Hello_Reader() + : Command("tls_client_hello --hex input") {} + + std::string group() const override + { + return "tls"; + } + + std::string description() const override + { + return "Parse a TLS client hello message"; + } + + void go() override + { + const std::string input_file = get_arg("input"); + std::vector<uint8_t> input; + + if(flag_set("hex")) + { + input = Botan::hex_decode(slurp_file_as_str(input_file)); + } + else + { + input = slurp_file(input_file); + } + + if(input.size() < 45) + { + error_output() << "Input too short to be valid\n"; + return; + } + + // Input also contains the record layer header, strip it + if(input[0] == 22) + { + const size_t len = Botan::make_uint16(input[3], input[4]); + + if(input.size() != len + 5) + { + error_output() << "Record layer length invalid\n"; + return; + } + + input = std::vector<uint8_t>(input.begin() + 5, input.end()); + } + + // Assume the handshake header is there, strip it + if(input[0] == 1) + { + const size_t hs_len = Botan::make_uint32(0, input[1], input[2], input[3]); + + if(input.size() != hs_len + 4) + { + error_output() << "Handshake layer length invalid\n"; + return; + } + + input = std::vector<uint8_t>(input.begin() + 4, input.end()); + } + + try + { + Botan::TLS::Client_Hello hello(input); + + output() << format_hello(hello); + } + catch(std::exception& e) + { + error_output() << "Parsing client hello failed: " << e.what() << "\n"; + } + } + + private: + std::string format_hello(const Botan::TLS::Client_Hello& hello) + { + std::ostringstream oss; + oss << "Version: " << hello.version().to_string() << "\n" + << "Random: " << Botan::hex_encode(hello.random()) << "\n"; + + if(!hello.session_id().empty()) + oss << "SessionID: " << Botan::hex_encode(hello.session_id()) << "\n"; + for(uint16_t csuite_id : hello.ciphersuites()) + { + auto csuite = Botan::TLS::Ciphersuite::by_id(csuite_id); + if(csuite.valid()) + oss << "Cipher: " << csuite.to_string() << "\n"; + else if(csuite_id == 0x00FF) + oss << "Cipher: EMPTY_RENEGOTIATION_INFO_SCSV\n"; + else + oss << "Cipher: Unknown (" << std::hex << csuite_id << ")\n"; + } + + oss << "Supported signature schemes: "; + + if(hello.signature_schemes().empty()) + { + oss << "Did not send signature_algorithms extension\n"; + } + else + { + for(Botan::TLS::Signature_Scheme scheme : hello.signature_schemes()) + { + try + { + auto s = sig_scheme_to_string(scheme); + oss << s << " "; + } + catch(...) + { + oss << "(" << std::hex << static_cast<uint16_t>(scheme) << ") "; + } + } + oss << "\n"; + } + + std::map<std::string, bool> hello_flags; + hello_flags["ALPN"] = hello.supports_alpn(); + hello_flags["Encrypt Then Mac"] = hello.supports_encrypt_then_mac(); + hello_flags["Extended Master Secret"] = hello.supports_extended_master_secret(); + hello_flags["Session Ticket"] = hello.supports_session_ticket(); + + for(auto&& i : hello_flags) + oss << "Supports " << i.first << "? " << (i.second ? "yes" : "no") << "\n"; + + return oss.str(); + } + }; + +BOTAN_REGISTER_COMMAND("tls_client_hello", TLS_Client_Hello_Reader); + +} + +#endif diff --git a/comm/third_party/botan/src/cli/tss.cpp b/comm/third_party/botan/src/cli/tss.cpp new file mode 100644 index 0000000000..0756616b2a --- /dev/null +++ b/comm/third_party/botan/src/cli/tss.cpp @@ -0,0 +1,138 @@ +/* +* (C) 2018 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_THRESHOLD_SECRET_SHARING) + #include <botan/tss.h> + #include <botan/hex.h> + #include <botan/rng.h> + #include <fstream> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_THRESHOLD_SECRET_SHARING) + +class TSS_Split final : public Command + { + public: + TSS_Split() : Command("tss_split M N input --id= --share-prefix=share --share-suffix=tss --hash=SHA-256") {} + + std::string group() const override + { + return "tss"; + } + + std::string description() const override + { + return "Split a secret into parts"; + } + + void go() override + { + const std::string hash_algo = get_arg("hash"); + const std::string input = get_arg("input"); + const std::string id_str = get_arg("id"); + const std::string share_prefix = get_arg("share-prefix"); + const std::string share_suffix = get_arg("share-suffix"); + const size_t N = get_arg_sz("N"); + const size_t M = get_arg_sz("M"); + + if(M <= 1 || N <= 1 || M > N || N >= 255) + throw CLI_Usage_Error("Invalid N/M parameters for secret splitting"); + + Botan::secure_vector<uint8_t> secret = slurp_file_lvec(input); + + if(secret.size() > 0xFFFF) + throw CLI_Usage_Error("Secret is too large for this TSS format"); + + std::vector<uint8_t> id = Botan::hex_decode(id_str); + + if(id.empty()) + { + id.resize(16); + rng().randomize(id.data(), id.size()); + } + + std::vector<Botan::RTSS_Share> shares = + Botan::RTSS_Share::split(static_cast<uint8_t>(M), static_cast<uint8_t>(N), + secret.data(), static_cast<uint16_t>(secret.size()), + id, hash_algo, rng()); + + for(size_t i = 0; i != shares.size(); ++i) + { + const std::string share_name = share_prefix + std::to_string(i + 1) + "." + share_suffix; + std::ofstream out(share_name.c_str()); + if(!out) + throw CLI_Error("Failed to open output file " + share_name); + + out.write(reinterpret_cast<const char*>(shares[i].data().data()), shares[i].data().size()); + } + + } + + private: + Botan::secure_vector<uint8_t> slurp_file_lvec(const std::string& input_file) + { + Botan::secure_vector<uint8_t> buf; + auto insert_fn = [&](const uint8_t b[], size_t l) + { + buf.insert(buf.end(), b, b + l); + }; + this->read_file(input_file, insert_fn, 4096); + return buf; + } + }; + +BOTAN_REGISTER_COMMAND("tss_split", TSS_Split); + +class TSS_Recover final : public Command + { + public: + TSS_Recover() : Command("tss_recover *shares") {} + + std::string group() const override + { + return "tss"; + } + + std::string description() const override + { + return "Recover a split secret"; + } + + void go() override + { + const std::vector<std::string> share_names = get_arg_list("shares"); + + if(share_names.empty()) + { + output() << help_text() << "\n"; + this->set_return_code(1); + return; + } + + std::vector<Botan::RTSS_Share> shares; + + for(std::string share_fsname : get_arg_list("shares")) + { + auto v = slurp_file(share_fsname); + shares.push_back(Botan::RTSS_Share(v.data(), v.size())); + } + + Botan::secure_vector<uint8_t> rec = Botan::RTSS_Share::reconstruct(shares); + + output().write(Botan::cast_uint8_ptr_to_char(rec.data()), rec.size()); + } + }; + +BOTAN_REGISTER_COMMAND("tss_recover", TSS_Recover); + +#endif + +} + diff --git a/comm/third_party/botan/src/cli/utils.cpp b/comm/third_party/botan/src/cli/utils.cpp new file mode 100644 index 0000000000..c6d013029a --- /dev/null +++ b/comm/third_party/botan/src/cli/utils.cpp @@ -0,0 +1,391 @@ +/* +* (C) 2009,2010,2014,2015 Jack Lloyd +* (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#include <botan/version.h> +#include <botan/cpuid.h> +#include <botan/internal/stl_util.h> +#include <botan/internal/os_utils.h> +#include <sstream> +#include <iomanip> + +#if defined(BOTAN_HAS_HTTP_UTIL) + #include <botan/http_util.h> +#endif + +#if defined(BOTAN_HAS_UUID) + #include <botan/uuid.h> +#endif + +namespace Botan_CLI { + +class Print_Help final : public Command + { + public: + Print_Help() : Command("help") {} + + std::string help_text() const override + { + std::map<std::string, std::vector<std::unique_ptr<Command>>> grouped_commands; + + auto reg_commands = Command::registered_cmds(); + for(const auto& cmd_name : reg_commands) + { + auto cmd = Command::get_cmd(cmd_name); + if(cmd) + { + grouped_commands[cmd->group()].push_back(std::move(cmd)); + } + } + + const std::map<std::string, std::string> groups_description { + { "encryption", "Encryption" }, + { "compression", "Compression" }, + { "codec", "Encoders/Decoders" }, + { "hash", "Hash Functions" }, + { "hmac", "HMAC" }, + { "info", "Informational" }, + { "numtheory", "Number Theory" }, + { "passhash", "Password Hashing" }, + { "psk", "PSK Database" }, + { "pubkey", "Public Key Cryptography" }, + { "tls", "TLS" }, + { "tss", "Secret Sharing" }, + { "x509", "X.509" }, + { "misc", "Miscellaneous" } + }; + + std::ostringstream oss; + + oss << "Usage: botan <cmd> <cmd-options>\n"; + oss << "All commands support --verbose --help --output= --error-output= --rng-type= --drbg-seed=\n\n"; + oss << "Available commands:\n\n"; + + for(const auto& commands : grouped_commands) + { + std::string desc = commands.first; + if(desc.empty()) + { + continue; + } + + oss << Botan::search_map(groups_description, desc, desc) << ":\n"; + for(auto& cmd : commands.second) + { + oss << " " << std::setw(16) << std::left << cmd->cmd_name() << " " << cmd->description() << "\n"; + } + oss << "\n"; + } + + return oss.str(); + } + + std::string group() const override + { + return ""; + } + + std::string description() const override + { + return "Prints a help string"; + } + + void go() override + { + this->set_return_code(1); + output() << help_text(); + } + }; + +BOTAN_REGISTER_COMMAND("help", Print_Help); + +class Has_Command final : public Command + { + public: + Has_Command() : Command("has_command cmd") {} + + std::string group() const override + { + return "info"; + } + + std::string description() const override + { + return "Test if a command is available"; + } + + void go() override + { + const std::string cmd = get_arg("cmd"); + + bool exists = false; + for(auto registered_cmd : Command::registered_cmds()) + { + if(cmd == registered_cmd) + { + exists = true; + break; + } + } + + if(verbose()) + { + output() << "Command '" << cmd << "' is " + << (exists ? "": "not ") << "available\n"; + } + + if(exists == false) + this->set_return_code(1); + } + }; + +BOTAN_REGISTER_COMMAND("has_command", Has_Command); + +class Config_Info final : public Command + { + public: + Config_Info() : Command("config info_type") {} + + std::string help_text() const override + { + return "Usage: config info_type\n" + " prefix: Print install prefix\n" + " cflags: Print include params\n" + " ldflags: Print linker params\n" + " libs: Print libraries\n"; + } + + std::string group() const override + { + return "info"; + } + + std::string description() const override + { + return "Print the used prefix, cflags, ldflags or libs"; + } + + void go() override + { + const std::string arg = get_arg("info_type"); + + if(arg == "prefix") + { + output() << BOTAN_INSTALL_PREFIX << "\n"; + } + else if(arg == "cflags") + { + output() << "-I" << BOTAN_INSTALL_PREFIX << "/" << BOTAN_INSTALL_HEADER_DIR << "\n"; + } + else if(arg == "ldflags") + { + if(*BOTAN_LINK_FLAGS) + output() << BOTAN_LINK_FLAGS << ' '; + output() << "-L" << BOTAN_INSTALL_LIB_DIR << "\n"; + } + else if(arg == "libs") + { + output() << "-lbotan-" << Botan::version_major() << " " << BOTAN_LIB_LINK << "\n"; + } + else + { + throw CLI_Usage_Error("Unknown option to botan config " + arg); + } + } + }; + +BOTAN_REGISTER_COMMAND("config", Config_Info); + +class Version_Info final : public Command + { + public: + Version_Info() : Command("version --full") {} + + std::string group() const override + { + return "info"; + } + + std::string description() const override + { + return "Print version info"; + } + + void go() override + { + if(flag_set("full")) + { + output() << Botan::version_string() << "\n"; + } + else + { + output() << Botan::short_version_string() << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("version", Version_Info); + +class Print_Cpuid final : public Command + { + public: + Print_Cpuid() : Command("cpuid") {} + + std::string group() const override + { + return "info"; + } + + std::string description() const override + { + return "List available processor flags (aes_ni, SIMD extensions, ...)"; + } + + void go() override + { + output() << "CPUID flags: " << Botan::CPUID::to_string() << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("cpuid", Print_Cpuid); + +class Cycle_Counter final : public Command + { + public: + Cycle_Counter() : Command("cpu_clock --test-duration=500") {} + + std::string group() const override + { + return "info"; + } + + std::string description() const override + { + return "Estimate the speed of the CPU cycle counter"; + } + + void go() override + { + if(Botan::OS::get_cpu_cycle_counter() == 0) + { + output() << "No CPU cycle counter on this machine\n"; + return; + } + + const uint64_t test_duration_ns = get_arg_sz("test-duration") * 1000000; + + if(test_duration_ns == 0) + { + output() << "Invalid test duration\n"; + return; + } + + const uint64_t cc_start = Botan::OS::get_cpu_cycle_counter(); + const uint64_t ns_start = Botan::OS::get_system_timestamp_ns(); + + uint64_t cc_end = 0; + uint64_t ns_end = ns_start; + + while((ns_end - ns_start) < test_duration_ns) + { + ns_end = Botan::OS::get_system_timestamp_ns(); + cc_end = Botan::OS::get_cpu_cycle_counter(); + } + + if(cc_end <= cc_start) + { + output() << "Cycle counter seems to have wrapped, try again\n"; + return; + } + + if(ns_end <= ns_start) + { + output() << "System clock seems to have wrapped (?!?)\n"; + return; + } + + const uint64_t ns_duration = ns_end - ns_start; + const uint64_t cc_duration = cc_end - cc_start; + + const double ratio = static_cast<double>(cc_duration) / ns_duration; + + if(ratio >= 1.0) + { + // GHz + output() << "Estimated CPU clock " << std::setprecision(2) << ratio << " GHz\n"; + } + else + { + // MHz + output() << "Estimated CPU clock " << static_cast<size_t>(ratio * 1000) << " MHz\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("cpu_clock", Cycle_Counter); + +#if defined(BOTAN_HAS_UUID) + +class Print_UUID final : public Command + { + public: + Print_UUID() : Command("uuid") {} + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Print a random UUID"; + } + + void go() override + { + Botan::UUID uuid(rng()); + output() << uuid.to_string() << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("uuid", Print_UUID); + +#endif + +#if defined(BOTAN_HAS_HTTP_UTIL) + +class HTTP_Get final : public Command + { + public: + HTTP_Get() : Command("http_get --redirects=1 --timeout=3000 url") {} + + std::string group() const override + { + return "misc"; + } + + std::string description() const override + { + return "Retrieve resource from the passed http/https url"; + } + + void go() override + { + const std::string url = get_arg("url"); + const std::chrono::milliseconds timeout(get_arg_sz("timeout")); + const size_t redirects = get_arg_sz("redirects"); + + output() << Botan::HTTP::GET_sync(url, redirects, timeout) << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND("http_get", HTTP_Get); + +#endif // http_util + +} diff --git a/comm/third_party/botan/src/cli/x509.cpp b/comm/third_party/botan/src/cli/x509.cpp new file mode 100644 index 0000000000..a92ec1309e --- /dev/null +++ b/comm/third_party/botan/src/cli/x509.cpp @@ -0,0 +1,417 @@ +/* +* (C) 2010,2014,2015,2018 Jack Lloyd +* (C) 2017 René Korthaus, Rohde & Schwarz Cybersecurity +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) + +#include <botan/certstor.h> +#include <botan/pk_keys.h> +#include <botan/pkcs8.h> +#include <botan/x509_ca.h> +#include <botan/x509cert.h> +#include <botan/x509path.h> +#include <botan/x509self.h> +#include <botan/data_src.h> +#include <botan/parsing.h> + +#if defined(BOTAN_HAS_OCSP) + #include <botan/ocsp.h> +#endif + +#if defined(BOTAN_HAS_CERTSTOR_SYSTEM) + #include <botan/certstor_system.h> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_CERTSTOR_SYSTEM) + +class Trust_Root_Info final : public Command + { + public: + Trust_Root_Info() : Command("trust_roots --dn --dn-only --display") {} + + std::string group() const override + { + return "x509"; + } + + std::string description() const override + { + return "List certs in the system trust store"; + } + + void go() override + { + Botan::System_Certificate_Store trust_roots; + + const auto dn_list = trust_roots.all_subjects(); + + if(flag_set("dn-only")) + { + for(auto dn : dn_list) + output() << dn << "\n"; + } + else + { + for(auto dn : dn_list) + { + // Some certstores have more than one cert with a particular DN + for(auto cert : trust_roots.find_all_certs(dn, std::vector<uint8_t>())) + { + if(flag_set("dn")) + output() << "# " << dn << "\n"; + + if(flag_set("display")) + output() << cert->to_string() << "\n"; + + output() << cert->PEM_encode() << "\n"; + } + } + + } + } + + }; + +BOTAN_REGISTER_COMMAND("trust_roots", Trust_Root_Info); + +#endif + +class Sign_Cert final : public Command + { + public: + Sign_Cert() + : Command("sign_cert --ca-key-pass= --hash=SHA-256 " + "--duration=365 --emsa= ca_cert ca_key pkcs10_req") {} + + std::string group() const override + { + return "x509"; + } + + std::string description() const override + { + return "Create a CA-signed X.509 certificate from a PKCS #10 CSR"; + } + + void go() override + { + Botan::X509_Certificate ca_cert(get_arg("ca_cert")); + + const std::string key_file = get_arg("ca_key"); + const std::string pass = get_passphrase_arg("Password for " + key_file, "ca-key-pass"); + const std::string emsa = get_arg("emsa"); + const std::string hash = get_arg("hash"); + + std::unique_ptr<Botan::Private_Key> key; + if(!pass.empty()) + { + key.reset(Botan::PKCS8::load_key(key_file, rng(), pass)); + } + else + { + key.reset(Botan::PKCS8::load_key(key_file, rng())); + } + + if(!key) + { + throw CLI_Error("Failed to load key from " + key_file); + } + + std::map<std::string, std::string> options; + if(emsa.empty() == false) + options["padding"] = emsa; + + Botan::X509_CA ca(ca_cert, *key, options, hash, rng()); + + Botan::PKCS10_Request req(get_arg("pkcs10_req")); + + auto now = std::chrono::system_clock::now(); + + Botan::X509_Time start_time(now); + + typedef std::chrono::duration<int, std::ratio<86400>> days; + + Botan::X509_Time end_time(now + days(get_arg_sz("duration"))); + + Botan::X509_Certificate new_cert = ca.sign_request(req, rng(), start_time, end_time); + + output() << new_cert.PEM_encode(); + } + }; + +BOTAN_REGISTER_COMMAND("sign_cert", Sign_Cert); + +class Cert_Info final : public Command + { + public: + Cert_Info() : Command("cert_info --fingerprint file") {} + + std::string group() const override + { + return "x509"; + } + + std::string description() const override + { + return "Parse X.509 certificate and display data fields"; + } + + void go() override + { + const std::string arg_file = get_arg("file"); + + std::vector<uint8_t> data = slurp_file(get_arg("file")); + + Botan::DataSource_Memory in(data); + + while(!in.end_of_data()) + { + try + { + Botan::X509_Certificate cert(in); + + try + { + output() << cert.to_string() << std::endl; + } + catch(Botan::Exception& e) + { + // to_string failed - report the exception and continue + output() << "X509_Certificate::to_string failed: " << e.what() << "\n"; + } + + if(flag_set("fingerprint")) + output() << "Fingerprint: " << cert.fingerprint("SHA-256") << std::endl; + } + catch(Botan::Exception& e) + { + if(!in.end_of_data()) + { + output() << "X509_Certificate parsing failed " << e.what() << "\n"; + } + } + } + } + }; + +BOTAN_REGISTER_COMMAND("cert_info", Cert_Info); + +#if defined(BOTAN_HAS_OCSP) && defined(BOTAN_HAS_HTTP_UTIL) + +class OCSP_Check final : public Command + { + public: + OCSP_Check() : Command("ocsp_check --timeout=3000 subject issuer") {} + + std::string group() const override + { + return "x509"; + } + + std::string description() const override + { + return "Verify an X.509 certificate against the issuers OCSP responder"; + } + + void go() override + { + Botan::X509_Certificate subject(get_arg("subject")); + Botan::X509_Certificate issuer(get_arg("issuer")); + std::chrono::milliseconds timeout(get_arg_sz("timeout")); + + Botan::Certificate_Store_In_Memory cas; + cas.add_certificate(issuer); + Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, &cas, timeout); + + auto status = resp.status_for(issuer, subject, std::chrono::system_clock::now()); + + if(status == Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD) + { + output() << "OCSP check OK\n"; + } + else + { + output() << "OCSP check failed " << Botan::Path_Validation_Result::status_string(status) << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("ocsp_check", OCSP_Check); + +#endif // OCSP && HTTP + +class Cert_Verify final : public Command + { + public: + Cert_Verify() : Command("cert_verify subject *ca_certs") {} + + std::string group() const override + { + return "x509"; + } + + std::string description() const override + { + return "Verify if the passed X.509 certificate passes path validation"; + } + + void go() override + { + Botan::X509_Certificate subject_cert(get_arg("subject")); + Botan::Certificate_Store_In_Memory trusted; + + for(auto const& certfile : get_arg_list("ca_certs")) + { + trusted.add_certificate(Botan::X509_Certificate(certfile)); + } + + Botan::Path_Validation_Restrictions restrictions; + + Botan::Path_Validation_Result result = + Botan::x509_path_validate(subject_cert, + restrictions, + trusted); + + if(result.successful_validation()) + { + output() << "Certificate passes validation checks\n"; + } + else + { + output() << "Certificate did not validate - " << result.result_string() << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("cert_verify", Cert_Verify); + +class Gen_Self_Signed final : public Command + { + public: + Gen_Self_Signed() + : Command("gen_self_signed key CN --country= --dns= " + "--organization= --email= --path-limit=1 --days=365 --key-pass= --ca --hash=SHA-256 --emsa= --der") {} + + std::string group() const override + { + return "x509"; + } + + std::string description() const override + { + return "Generate a self signed X.509 certificate"; + } + + void go() override + { + const std::string key_file = get_arg("key"); + const std::string passphrase = get_passphrase_arg("Passphrase for " + key_file, "key-pass"); + std::unique_ptr<Botan::Private_Key> key(Botan::PKCS8::load_key(key_file, rng(), passphrase)); + + if(!key) + { + throw CLI_Error("Failed to load key from " + get_arg("key")); + } + + const uint32_t lifetime = static_cast<uint32_t>(get_arg_sz("days") * 24 * 60 * 60); + + Botan::X509_Cert_Options opts("", lifetime); + + opts.common_name = get_arg("CN"); + opts.country = get_arg("country"); + opts.organization = get_arg("organization"); + opts.email = get_arg("email"); + opts.more_dns = Botan::split_on(get_arg("dns"), ','); + const bool der_format = flag_set("der"); + + std::string emsa = get_arg("emsa"); + + if(emsa.empty() == false) + opts.set_padding_scheme(emsa); + + if(flag_set("ca")) + { + opts.CA_key(get_arg_sz("path-limit")); + } + + Botan::X509_Certificate cert = Botan::X509::create_self_signed_cert(opts, *key, get_arg("hash"), rng()); + + if(der_format) + { + auto der = cert.BER_encode(); + output().write(reinterpret_cast<const char*>(der.data()), der.size()); + } + else + output() << cert.PEM_encode(); + } + }; + +BOTAN_REGISTER_COMMAND("gen_self_signed", Gen_Self_Signed); + +class Generate_PKCS10 final : public Command + { + public: + Generate_PKCS10() + : Command("gen_pkcs10 key CN --country= --organization= " + "--ca --path-limit=1 --email= --dns= --ext-ku= --key-pass= --hash=SHA-256 --emsa=") {} + + std::string group() const override + { + return "x509"; + } + + std::string description() const override + { + return "Generate a PKCS #10 certificate signing request (CSR)"; + } + + void go() override + { + std::unique_ptr<Botan::Private_Key> key(Botan::PKCS8::load_key(get_arg("key"), rng(), get_arg("key-pass"))); + + if(!key) + { + throw CLI_Error("Failed to load key from " + get_arg("key")); + } + + Botan::X509_Cert_Options opts; + + opts.common_name = get_arg("CN"); + opts.country = get_arg("country"); + opts.organization = get_arg("organization"); + opts.email = get_arg("email"); + opts.more_dns = Botan::split_on(get_arg("dns"), ','); + + if(flag_set("ca")) + { + opts.CA_key(get_arg_sz("path-limit")); + } + + for(std::string ext_ku : Botan::split_on(get_arg("ext-ku"), ',')) + { + opts.add_ex_constraint(ext_ku); + } + + std::string emsa = get_arg("emsa"); + + if(emsa.empty() == false) + opts.set_padding_scheme(emsa); + + Botan::PKCS10_Request req = Botan::X509::create_cert_req(opts, *key, get_arg("hash"), rng()); + + output() << req.PEM_encode(); + } + }; + +BOTAN_REGISTER_COMMAND("gen_pkcs10", Generate_PKCS10); + +} + +#endif |