summaryrefslogtreecommitdiffstats
path: root/comm/third_party/botan/src/cli
diff options
context:
space:
mode:
Diffstat (limited to 'comm/third_party/botan/src/cli')
-rw-r--r--comm/third_party/botan/src/cli/argon2.cpp78
-rw-r--r--comm/third_party/botan/src/cli/argparse.h280
-rw-r--r--comm/third_party/botan/src/cli/asn1.cpp89
-rw-r--r--comm/third_party/botan/src/cli/bcrypt.cpp89
-rw-r--r--comm/third_party/botan/src/cli/cc_enc.cpp189
-rw-r--r--comm/third_party/botan/src/cli/cli.cpp349
-rw-r--r--comm/third_party/botan/src/cli/cli.h219
-rw-r--r--comm/third_party/botan/src/cli/cli_exceptions.h47
-rw-r--r--comm/third_party/botan/src/cli/cli_rng.cpp146
-rw-r--r--comm/third_party/botan/src/cli/codec.cpp268
-rw-r--r--comm/third_party/botan/src/cli/compress.cpp190
-rw-r--r--comm/third_party/botan/src/cli/encryption.cpp127
-rw-r--r--comm/third_party/botan/src/cli/entropy.cpp104
-rw-r--r--comm/third_party/botan/src/cli/hash.cpp78
-rw-r--r--comm/third_party/botan/src/cli/hmac.cpp78
-rw-r--r--comm/third_party/botan/src/cli/main.cpp37
-rw-r--r--comm/third_party/botan/src/cli/math.cpp269
-rw-r--r--comm/third_party/botan/src/cli/pbkdf.cpp99
-rw-r--r--comm/third_party/botan/src/cli/pk_crypt.cpp229
-rw-r--r--comm/third_party/botan/src/cli/psk.cpp106
-rw-r--r--comm/third_party/botan/src/cli/pubkey.cpp554
-rw-r--r--comm/third_party/botan/src/cli/roughtime.cpp215
-rw-r--r--comm/third_party/botan/src/cli/sandbox.cpp115
-rw-r--r--comm/third_party/botan/src/cli/sandbox.h32
-rw-r--r--comm/third_party/botan/src/cli/socket_utils.h105
-rw-r--r--comm/third_party/botan/src/cli/speed.cpp2342
-rw-r--r--comm/third_party/botan/src/cli/timing_tests.cpp617
-rw-r--r--comm/third_party/botan/src/cli/tls_client.cpp436
-rw-r--r--comm/third_party/botan/src/cli/tls_helpers.h244
-rw-r--r--comm/third_party/botan/src/cli/tls_http_server.cpp579
-rw-r--r--comm/third_party/botan/src/cli/tls_proxy.cpp526
-rw-r--r--comm/third_party/botan/src/cli/tls_server.cpp364
-rw-r--r--comm/third_party/botan/src/cli/tls_utils.cpp226
-rw-r--r--comm/third_party/botan/src/cli/tss.cpp138
-rw-r--r--comm/third_party/botan/src/cli/utils.cpp391
-rw-r--r--comm/third_party/botan/src/cli/x509.cpp417
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