diff options
Diffstat (limited to 'src/cli')
45 files changed, 3067 insertions, 4221 deletions
diff --git a/src/cli/apps.h b/src/cli/apps.h deleted file mode 100644 index 9f1f00ba2..000000000 --- a/src/cli/apps.h +++ /dev/null @@ -1,77 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include <iostream> -#include <functional> -#include <string> -#include <set> -#include <botan/build.h> -#include <botan/hex.h> -#include <botan/auto_rng.h> -#include "getopt.h" - -using namespace Botan; - -typedef std::function<int (std::vector<std::string>)> app_fn; - -class AppRegistrations - { - public: - void add(const std::string& name, app_fn fn) - { - m_apps[name] = fn; - } - - bool has(const std::string& cmd) const - { - return m_apps.count(cmd) > 0; - } - - std::set<std::string> all_appnames() const - { - std::set<std::string> apps; - for(auto i : m_apps) - apps.insert(i.first); - return apps; - } - - // TODO: Remove redundancy cmd == args[0] - int run(const std::string& cmd, std::vector<std::string> args) const - { - const auto app = m_apps.find(cmd); - if(app != m_apps.end()) - return app->second(args); - return -1; - } - - static AppRegistrations& instance() - { - static AppRegistrations s_apps; - return s_apps; - } - - class AppRegistration - { - public: - AppRegistration(const std::string& name, app_fn fn) - { - AppRegistrations::instance().add(name, fn); - } - }; - - private: - AppRegistrations() {} - - std::map<std::string, app_fn> m_apps; - }; - -#define REGISTER_APP(nm) AppRegistrations::AppRegistration g_ ## nm ## _registration(#nm, nm) - -#if defined(BOTAN_TARGET_OS_IS_WINDOWS) || defined(BOTAN_TARGET_OS_IS_MINGW) - #undef BOTAN_TARGET_OS_HAS_SOCKETS -#else - #define BOTAN_TARGET_OS_HAS_SOCKETS -#endif diff --git a/src/cli/asn1.cpp b/src/cli/asn1.cpp index 2aa94cc39..8096f2d19 100644 --- a/src/cli/asn1.cpp +++ b/src/cli/asn1.cpp @@ -4,7 +4,7 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include "apps.h" +#include "cli.h" #if defined(BOTAN_HAS_ASN1) && defined(BOTAN_HAS_PEM_CODEC) @@ -17,7 +17,6 @@ #include <botan/oids.h> #include <botan/pem.h> #include <botan/charset.h> -using namespace Botan; #include <iostream> #include <iomanip> @@ -34,9 +33,11 @@ using namespace Botan; */ #define INITIAL_LEVEL 0 +namespace Botan_CLI { + namespace { -std::string url_encode(const std::vector<byte>& in) +std::string url_encode(const std::vector<uint8_t>& in) { std::ostringstream out; @@ -55,7 +56,7 @@ std::string url_encode(const std::vector<byte>& in) } if(unprintable >= in.size() / 4) - return hex_encode(in); + return Botan::hex_encode(in); return out.str(); } @@ -95,57 +96,89 @@ void emit(const std::string& type, size_t level, size_t length, const std::strin std::cout << out.str() << std::endl; } -std::string type_name(ASN1_Tag type) +std::string type_name(Botan::ASN1_Tag type) { - if(type == PRINTABLE_STRING) return "PRINTABLE STRING"; - if(type == NUMERIC_STRING) return "NUMERIC STRING"; - if(type == IA5_STRING) return "IA5 STRING"; - if(type == T61_STRING) return "T61 STRING"; - if(type == UTF8_STRING) return "UTF8 STRING"; - if(type == VISIBLE_STRING) return "VISIBLE STRING"; - if(type == BMP_STRING) return "BMP STRING"; - - if(type == UTC_TIME) return "UTC TIME"; - if(type == GENERALIZED_TIME) return "GENERALIZED TIME"; - - if(type == OCTET_STRING) return "OCTET STRING"; - if(type == BIT_STRING) return "BIT STRING"; - - if(type == ENUMERATED) return "ENUMERATED"; - if(type == INTEGER) return "INTEGER"; - if(type == NULL_TAG) return "NULL"; - if(type == OBJECT_ID) return "OBJECT"; - if(type == BOOLEAN) return "BOOLEAN"; + switch(type) + { + case Botan::PRINTABLE_STRING: + return "PRINTABLE STRING"; + + case Botan::NUMERIC_STRING: + return "NUMERIC STRING"; + + case Botan::IA5_STRING: + return "IA5 STRING"; + + case Botan::T61_STRING: + return "T61 STRING"; + + case Botan::UTF8_STRING: + return "UTF8 STRING"; + + case Botan::VISIBLE_STRING: + return "VISIBLE STRING"; + + case Botan::BMP_STRING: + return "BMP STRING"; + + case Botan::UTC_TIME: + return "UTC TIME"; + + case Botan::GENERALIZED_TIME: + return "GENERALIZED TIME"; + + case Botan::OCTET_STRING: + return "OCTET STRING"; + + case Botan::BIT_STRING: + return "BIT STRING"; + + case Botan::ENUMERATED: + return "ENUMERATED"; + + case Botan::INTEGER: + return "INTEGER"; + + case Botan::NULL_TAG: + return "NULL"; + + case Botan::OBJECT_ID: + return "OBJECT"; + + case Botan::BOOLEAN: + return "BOOLEAN"; + } + return "(UNKNOWN)"; } -void decode(BER_Decoder& decoder, size_t level) +void decode(Botan::BER_Decoder& decoder, size_t level) { - BER_Object obj = decoder.get_next_object(); + Botan::BER_Object obj = decoder.get_next_object(); - while(obj.type_tag != NO_OBJECT) + while(obj.type_tag != Botan::NO_OBJECT) { - const ASN1_Tag type_tag = obj.type_tag; - const ASN1_Tag class_tag = obj.class_tag; + const Botan::ASN1_Tag type_tag = obj.type_tag; + const Botan::ASN1_Tag class_tag = obj.class_tag; const size_t length = obj.value.size(); /* hack to insert the tag+length back in front of the stuff now that we've gotten the type info */ - DER_Encoder encoder; + Botan::DER_Encoder encoder; encoder.add_object(type_tag, class_tag, obj.value); - std::vector<byte> bits = encoder.get_contents_unlocked(); + std::vector<uint8_t> bits = encoder.get_contents_unlocked(); - BER_Decoder data(bits); + Botan::BER_Decoder data(bits); - if(class_tag & CONSTRUCTED) + if(class_tag & Botan::CONSTRUCTED) { - BER_Decoder cons_info(obj.value); - if(type_tag == SEQUENCE) + Botan::BER_Decoder cons_info(obj.value); + if(type_tag == Botan::SEQUENCE) { emit("SEQUENCE", level, length); decode(cons_info, level+1); } - else if(type_tag == SET) + else if(type_tag == Botan::SET) { emit("SET", level, length); decode(cons_info, level+1); @@ -154,13 +187,13 @@ void decode(BER_Decoder& decoder, size_t level) { std::string name; - if((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC)) + if((class_tag & Botan::APPLICATION) || (class_tag & Botan::CONTEXT_SPECIFIC)) { name = "cons [" + std::to_string(type_tag) + "]"; - if(class_tag & APPLICATION) + if(class_tag & Botan::APPLICATION) name += " appl"; - if(class_tag & CONTEXT_SPECIFIC) + if(class_tag & Botan::CONTEXT_SPECIFIC) name += " context"; } else @@ -170,15 +203,15 @@ void decode(BER_Decoder& decoder, size_t level) decode(cons_info, level+1); } } - else if((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC)) + else if((class_tag & Botan::APPLICATION) || (class_tag & Botan::CONTEXT_SPECIFIC)) { #if 0 - std::vector<byte> bits; + std::vector<uint8_t> bits; data.decode(bits, type_tag); try { - BER_Decoder inner(bits); + Botan::BER_Decoder inner(bits); decode(inner, level + 1); } catch(...) @@ -191,33 +224,33 @@ void decode(BER_Decoder& decoder, size_t level) url_encode(bits)); #endif } - else if(type_tag == OBJECT_ID) + else if(type_tag == Botan::OBJECT_ID) { - OID oid; + Botan::OID oid; data.decode(oid); - std::string out = OIDS::lookup(oid); + std::string out = Botan::OIDS::lookup(oid); if(out != oid.as_string()) out += " [" + oid.as_string() + "]"; emit(type_name(type_tag), level, length, out); } - else if(type_tag == INTEGER || type_tag == ENUMERATED) + else if(type_tag == Botan::INTEGER || type_tag == Botan::ENUMERATED) { - BigInt number; + Botan::BigInt number; - if(type_tag == INTEGER) + if(type_tag == Botan::INTEGER) data.decode(number); - else if(type_tag == ENUMERATED) - data.decode(number, ENUMERATED, class_tag); + else if(type_tag == Botan::ENUMERATED) + data.decode(number, Botan::ENUMERATED, class_tag); - std::vector<byte> rep; + std::vector<uint8_t> rep; /* If it's small, it's probably a number, not a hash */ if(number.bits() <= 20) - rep = BigInt::encode(number, BigInt::Decimal); + rep = Botan::BigInt::encode(number, Botan::BigInt::Decimal); else - rep = BigInt::encode(number, BigInt::Hexadecimal); + rep = Botan::BigInt::encode(number, Botan::BigInt::Hexadecimal); std::string str; for(size_t i = 0; i != rep.size(); ++i) @@ -225,25 +258,25 @@ void decode(BER_Decoder& decoder, size_t level) emit(type_name(type_tag), level, length, str); } - else if(type_tag == BOOLEAN) + else if(type_tag == Botan::BOOLEAN) { bool boolean; data.decode(boolean); emit(type_name(type_tag), level, length, (boolean ? "true" : "false")); } - else if(type_tag == NULL_TAG) + else if(type_tag == Botan::NULL_TAG) { emit(type_name(type_tag), level, length); } - else if(type_tag == OCTET_STRING) + else if(type_tag == Botan::OCTET_STRING) { - std::vector<byte> bits; + std::vector<uint8_t> bits; data.decode(bits, type_tag); try { - BER_Decoder inner(bits); + Botan::BER_Decoder inner(bits); decode(inner, level + 1); } catch(...) @@ -252,9 +285,9 @@ void decode(BER_Decoder& decoder, size_t level) url_encode(bits)); } } - else if(type_tag == BIT_STRING) + else if(type_tag == Botan::BIT_STRING) { - std::vector<byte> bits; + std::vector<uint8_t> bits; data.decode(bits, type_tag); std::vector<bool> bit_set; @@ -278,30 +311,31 @@ void decode(BER_Decoder& decoder, size_t level) emit(type_name(type_tag), level, length, bit_str); } - else if(type_tag == PRINTABLE_STRING || - type_tag == NUMERIC_STRING || - type_tag == IA5_STRING || - type_tag == T61_STRING || - type_tag == VISIBLE_STRING || - type_tag == UTF8_STRING || - type_tag == BMP_STRING) + else if(type_tag == Botan::PRINTABLE_STRING || + type_tag == Botan::NUMERIC_STRING || + type_tag == Botan::IA5_STRING || + type_tag == Botan::T61_STRING || + type_tag == Botan::VISIBLE_STRING || + type_tag == Botan::UTF8_STRING || + type_tag == Botan::BMP_STRING) { - ASN1_String str; + Botan::ASN1_String str; data.decode(str); if(UTF8_TERMINAL) { emit(type_name(type_tag), level, length, - Charset::transcode(str.iso_8859(), - LATIN1_CHARSET, UTF8_CHARSET)); + Botan::Charset::transcode(str.iso_8859(), + Botan::LATIN1_CHARSET, + Botan::UTF8_CHARSET)); } else { emit(type_name(type_tag), level, length, str.iso_8859()); } } - else if(type_tag == UTC_TIME || type_tag == GENERALIZED_TIME) + else if(type_tag == Botan::UTC_TIME || type_tag == Botan::GENERALIZED_TIME) { - X509_Time time; + Botan::X509_Time time; data.decode(time); emit(type_name(type_tag), level, length, time.readable_string()); } @@ -317,39 +351,32 @@ void decode(BER_Decoder& decoder, size_t level) } } -int asn1(const std::vector<std::string> &args) - { - if(args.size() != 2) - { - std::cout << "Usage: " << args[0] << " <file>" << std::endl; - return 1; - } +} - try { - DataSource_Stream in(args[1]); +class ASN1_Printer : public Command + { + public: + ASN1_Printer() : Command("asn1print file") {} - if(!PEM_Code::matches(in)) + void go() { - BER_Decoder decoder(in); - decode(decoder, INITIAL_LEVEL); - } - else - { - std::string label; // ignored - BER_Decoder decoder(PEM_Code::decode(in, label)); - decode(decoder, INITIAL_LEVEL); - } - } - catch(std::exception& e) - { - std::cout << "Error: " << e.what() << std::endl; - return 2; - } + Botan::DataSource_Stream in(get_arg("file")); - return 0; - } + if(!Botan::PEM_Code::matches(in)) + { + Botan::BER_Decoder decoder(in); + decode(decoder, INITIAL_LEVEL); + } + else + { + std::string label; // ignored + Botan::BER_Decoder decoder(Botan::PEM_Code::decode(in, label)); + decode(decoder, INITIAL_LEVEL); + } + } + }; -REGISTER_APP(asn1); +BOTAN_REGISTER_COMMAND(ASN1_Printer); } diff --git a/src/cli/base64.cpp b/src/cli/base64.cpp deleted file mode 100644 index d2a9a1853..000000000 --- a/src/cli/base64.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* -* Encode/decode base64 strings -* (C) 2009 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_CODEC_FILTERS) - -#include <fstream> -#include <iostream> -#include <string> -#include <vector> -#include <cstdlib> -#include <botan/b64_filt.h> -#include <botan/pipe.h> - -namespace { - -int base64(const std::vector<std::string> &args) - { - if(args.size() < 2) - { - std::cout << "Usage: " << args[0] << " [-w] [-c n] [-e|-d] files...\n" - " -e : Encode input to base64 strings (default)\n" - " -d : Decode base64 input\n" - " -w : Wrap lines\n" - " -c n: Wrap lines at column n, default 78" << std::endl; - return 1; - } - - u32bit column = 78; - bool wrap = false; - bool encoding = true; - std::vector<std::string> files; - - for(int j = 1; j < args.size(); j++) - { - const std::string this_arg = args[j]; - - if(this_arg == "-w") - wrap = true; - else if(this_arg == "-e"); - else if(this_arg == "-d") - encoding = false; - else if(this_arg == "-c") - { - if(j+1 < args.size()) - { - column = to_u32bit(args[j+1]); - j++; - } - else - { - std::cout << "No argument for -c option" << std::endl; - return 1; - } - } - else files.push_back(args[j]); - } - - for(unsigned int j = 0; j != files.size(); j++) - { - std::istream* stream; - if(files[j] == "-") stream = &std::cin; - else stream = new std::ifstream(files[j]); - - if(!*stream) - { - std::cout << "ERROR, couldn't open " << files[j] << std::endl; - continue; - } - - Botan::Filter* f = nullptr; - - if(encoding) - f = new Botan::Base64_Encoder(wrap, column); - else - f = new Botan::Base64_Decoder; - - Botan::Pipe pipe(f); - pipe.start_msg(); - *stream >> pipe; - pipe.end_msg(); - pipe.set_default_msg(j); - std::cout << pipe; - if(files[j] != "-") delete stream; - } - return 0; - } - -REGISTER_APP(base64); - -} -#endif // BOTAN_HAS_CODEC_FILTERS diff --git a/src/cli/bcrypt.cpp b/src/cli/bcrypt.cpp deleted file mode 100644 index 81f7c536e..000000000 --- a/src/cli/bcrypt.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_BCRYPT) - -#include <botan/bcrypt.h> - -namespace { - -int bcrypt(const std::vector<std::string> &args) - { - if(args.size() == 2) - { - AutoSeeded_RNG rng; - - const std::string password = args[1]; - - std::cout << generate_bcrypt(password, rng, 12) << std::endl; - return 0; - } - else if(args.size() == 3) - { - const std::string password = args[1]; - const std::string hash = args[2]; - - if(hash.length() != 60) - std::cout << "Note: bcrypt '" << hash << "' has wrong length and cannot be valid" << std::endl; - - const bool ok = check_bcrypt(password, hash); - - std::cout << "Password is " << (ok ? "valid" : "NOT valid") << std::endl; - return (ok ? 0 : 1); - } - - std::cout << "Usage: " << args[0] << " password\n" - << " " << args[0] << " password passhash" << std::endl; - return 1; - } - -REGISTER_APP(bcrypt); - -} - -#endif diff --git a/src/cli/bench.cpp b/src/cli/bench.cpp new file mode 100644 index 000000000..d2688421c --- /dev/null +++ b/src/cli/bench.cpp @@ -0,0 +1,767 @@ +/* +* (C) 2009,2010,2014,2015 Jack Lloyd +* (C) 2015 Simon Warta (Kullo GmbH) +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" +#include <sstream> +#include <iomanip> +#include <chrono> +#include <functional> + +// Always available: +#include <botan/block_cipher.h> +#include <botan/stream_cipher.h> +#include <botan/hash.h> +#include <botan/mac.h> +#include <botan/cipher_mode.h> +#include <botan/auto_rng.h> + +#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) + #include <botan/pkcs8.h> + #include <botan/pubkey.h> + #include <botan/x509_key.h> +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + #include <botan/numthry.h> +#endif + +#if defined(BOTAN_HAS_RSA) + #include <botan/rsa.h> +#endif + +#if defined(BOTAN_HAS_ECDSA) + #include <botan/ecdsa.h> +#endif + +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + #include <botan/dh.h> +#endif + +#if defined(BOTAN_HAS_CURVE_25519) + #include <botan/curve25519.h> +#endif + +#if defined(BOTAN_HAS_ECDH) + #include <botan/ecdh.h> +#endif + +#if defined(BOTAN_HAS_MCELIECE) + #include <botan/mceliece.h> + #include <botan/mce_kem.h> +#endif + +namespace Botan_CLI { + +namespace { + +class Timer + { + public: + static uint64_t get_clock() // returns nanoseconds with arbitrary epoch + { + auto now = std::chrono::high_resolution_clock::now().time_since_epoch(); + return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count(); + } + + Timer(const std::string& name, uint64_t event_mult = 1) : + m_name(name), m_event_mult(event_mult) {} + + Timer(const std::string& what, + const std::string& provider, + const std::string& doing, + uint64_t event_mult = 1) : + m_name(what + (provider.empty() ? provider : " [" + provider + "]")), + m_doing(doing), + m_event_mult(event_mult) {} + + void start() { stop(); m_timer_start = get_clock(); } + + void stop() + { + if(m_timer_start) + { + const uint64_t now = get_clock(); + + if(now > m_timer_start) + m_time_used += (now - m_timer_start); + + m_timer_start = 0; + ++m_event_count; + } + } + + bool under(std::chrono::milliseconds msec) + { + return (milliseconds() < msec.count()); + } + + struct Timer_Scope + { + public: + Timer_Scope(Timer& timer) : m_timer(timer) { m_timer.start(); } + ~Timer_Scope() { m_timer.stop(); } + private: + Timer& m_timer; + }; + + template<typename F> + auto run(F f) -> decltype(f()) + { + Timer_Scope timer(*this); + return f(); + } + + uint64_t value() { stop(); return m_time_used; } + double seconds() { return milliseconds() / 1000.0; } + double milliseconds() { return value() / 1000000.0; } + + double ms_per_event() { return milliseconds() / events(); } + double seconds_per_event() { return seconds() / events(); } + + uint64_t event_mult() const { return m_event_mult; } + uint64_t events() const { return m_event_count * m_event_mult; } + std::string get_name() const { return m_name; } + std::string doing() const { return m_doing.empty() ? m_doing : " " + m_doing; } + private: + std::string m_name, m_doing; + uint64_t m_time_used = 0, m_timer_start = 0; + uint64_t m_event_count = 0, m_event_mult = 0; + }; + +inline bool operator<(const Timer& x, const Timer& y) + { + return (x.get_name() < y.get_name()); + } + +inline bool operator==(const Timer& x, const Timer& y) + { + return (x.get_name() == y.get_name()); + } + +std::ostream& operator<<(std::ostream& out, Timer& timer) + { + const double events_per_second = timer.events() / timer.seconds(); + + // use ostringstream to avoid messing with flags on the ostream& itself + + std::ostringstream oss; + + if(timer.event_mult() % 1024 == 0) + { + // assumed to be a byte count + const size_t MiB = 1024*1024; + + const double MiB_total = static_cast<double>(timer.events()) / MiB; + const double MiB_per_sec = MiB_total / timer.seconds(); + + oss << timer.get_name() << timer.doing() << " " + << std::fixed << std::setprecision(3) << MiB_per_sec << " MiB/sec" + << " (" << MiB_total << " MiB in " << timer.milliseconds() << " ms)\n"; + } + else + { + // general event counter + oss << timer.get_name() << " " + << static_cast<uint64_t>(events_per_second) + << timer.doing() << "/sec; " + << std::setprecision(2) << std::fixed + << timer.ms_per_event() << " ms/op" + << " (" << timer.events() << " " << (timer.events() == 1 ? "op" : "ops") + << " in " << timer.milliseconds() << " ms)\n"; + } + + out << oss.str(); + return out; + } + +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", + "Blowfish", + "CAST-128", + "CAST-256", + "DES", + "TripleDES", + "IDEA", + "KASUMI", + "Noekeon", + "Serpent", + "Threefish-512", + "Twofish", + + /* Cipher modes */ + "AES-128/CBC", + "AES-128/CTR-BE", + "AES-128/EAX", + "AES-128/OCB", + "AES-128/GCM", + "AES-128/XTS", + + "Serpent/CBC", + "Serpent/CTR-BE", + "Serpent/EAX", + "Serpent/OCB", + "Serpent/GCM", + "Serpent/XTS", + + "ChaCha20Poly1305", + + /* Stream ciphers */ + "RC4", + "Salsa20", + + /* Hashes */ + "Tiger", + "RIPEMD-160", + "SHA-160", + "SHA-256", + "SHA-512", + "Skein-512", + "Keccak-1600(512)", + "Whirlpool", + + /* MACs */ + "CMAC(AES-128)", + "HMAC(SHA-256)", + + /* Misc */ + "random_prime" + + /* pubkey */ + "RSA", + "DH", + "ECDH", + "ECDSA", + "Curve25519", + "McEliece", + }; + } + +} + +class Benchmark : public Command + { + public: + Benchmark() : Command("bench --msec=1000 --provider= --buf-size=8 *algos") {} + + void go() + { + std::chrono::milliseconds msec(get_arg_sz("msec")); + const size_t buf_size = get_arg_sz("buf-size"); + const std::string provider = get_arg("provider"); + + std::vector<std::string> algos = get_arg_list("algos"); + const bool using_defaults = (algos.empty()); + if(using_defaults) + algos = default_benchmark_list(); + + for(auto algo : algos) + { + using namespace std::placeholders; + + if(auto enc = Botan::get_cipher_mode(algo, Botan::ENCRYPTION)) + { + auto dec = Botan::get_cipher_mode(algo, Botan::DECRYPTION); + bench_cipher_mode(*enc, *dec, msec, buf_size); + } + else if(Botan::BlockCipher::providers(algo).size() > 0) + { + bench_providers_of<Botan::BlockCipher>( + algo, provider, msec, buf_size, + std::bind(&Benchmark::bench_block_cipher, this, _1, _2, _3, _4)); + } + else if(Botan::StreamCipher::providers(algo).size() > 0) + { + bench_providers_of<Botan::StreamCipher>( + algo, provider, msec, buf_size, + std::bind(&Benchmark::bench_stream_cipher, this, _1, _2, _3, _4)); + } + else if(Botan::HashFunction::providers(algo).size() > 0) + { + bench_providers_of<Botan::HashFunction>( + algo, provider, msec, buf_size, + std::bind(&Benchmark::bench_hash, this, _1, _2, _3, _4)); + } + else if(Botan::MessageAuthenticationCode::providers(algo).size() > 0) + { + bench_providers_of<Botan::MessageAuthenticationCode>( + algo, provider, msec, buf_size, + std::bind(&Benchmark::bench_mac, this, _1, _2, _3, _4)); + } +#if defined(BOTAN_HAS_RSA) + else if(algo == "RSA") + { + bench_rsa(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECDSA) + else if(algo == "ECDSA") + { + bench_ecdsa(provider, msec); + } +#endif +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + else if(algo == "DH") + { + bench_dh(provider, msec); + } +#endif +#if defined(BOTAN_HAS_ECDH) + else if(algo == "ECDH") + { + bench_ecdh(provider, msec); + } +#endif +#if defined(BOTAN_HAS_CURVE_25519) + else if(algo == "Curve25519") + { + bench_curve25519(provider, msec); + } +#endif + +#if defined(BOTAN_HAS_NUMBERTHEORY) + else if(algo == "random_prime") + { + bench_random_prime(msec); + } +#endif + else + { + if(verbose() || !using_defaults) + { + error_output() << "Unknown algorithm to benchmark '" << algo << "'\n"; + } + } + } + } + + private: + + Botan::AutoSeeded_RNG m_rng; + Botan::RandomNumberGenerator& rng() { return m_rng; } + + template<typename T> + using bench_fn = std::function<void (T&, + std::string, + std::chrono::milliseconds, + 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, + size_t buf_size, + bench_fn<T> bench_one) + { + for(auto&& prov : T::providers(algo)) + { + if(provider == "" || provider == prov) + { + auto p = T::create(algo, prov); + + if(p) + { + bench_one(*p, prov, runtime, buf_size); + } + } + } + } + + void bench_block_cipher(Botan::BlockCipher& cipher, + const std::string& provider, + const std::chrono::milliseconds runtime, + size_t buf_size) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size * 1024); + + Timer encrypt_timer(cipher.name(), provider, "encrypt", buffer.size()); + Timer decrypt_timer(cipher.name(), provider, "decrypt", buffer.size()); + + while(encrypt_timer.under(runtime) && decrypt_timer.under(runtime)) + { + const Botan::SymmetricKey key(rng(), cipher.maximum_keylength()); + + cipher.set_key(key); + encrypt_timer.run([&] { cipher.encrypt(buffer); }); + decrypt_timer.run([&] { cipher.decrypt(buffer); }); + } + + output() << encrypt_timer << decrypt_timer; + } + + void bench_stream_cipher(Botan::StreamCipher& cipher, + const std::string& provider, + const std::chrono::milliseconds runtime, + size_t buf_size) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size * 1024); + + Timer encrypt_timer(cipher.name(), provider, "encrypt", buffer.size()); + + while(encrypt_timer.under(runtime)) + { + const Botan::SymmetricKey key(rng(), cipher.maximum_keylength()); + cipher.set_key(key); + encrypt_timer.run([&] { cipher.encipher(buffer); }); + } + + output() << encrypt_timer; + } + + void bench_hash(Botan::HashFunction& hash, + const std::string& provider, + const std::chrono::milliseconds runtime, + size_t buf_size) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size * 1024); + + Timer timer(hash.name(), provider, "hashing", buffer.size()); + + while(timer.under(runtime)) + { + timer.run([&] { hash.update(buffer); }); + } + + output() << timer; + } + + void bench_mac(Botan::MessageAuthenticationCode& mac, + const std::string& provider, + const std::chrono::milliseconds runtime, + size_t buf_size) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size * 1024); + + Timer timer(mac.name(), provider, "processing", buffer.size()); + + while(timer.under(runtime)) + { + const Botan::SymmetricKey key(rng(), mac.maximum_keylength()); + mac.set_key(key); + timer.run([&] { mac.update(buffer); }); + } + + output() << timer; + } + + void bench_cipher_mode(Botan::Cipher_Mode& enc, + Botan::Cipher_Mode& dec, + const std::chrono::milliseconds runtime, + size_t buf_size) + { + Botan::secure_vector<uint8_t> buffer = rng().random_vec(buf_size * 1024); + + Timer encrypt_timer(enc.name(), "", "encrypt", buffer.size()); + Timer decrypt_timer(enc.name(), "", "decrypt", buffer.size()); + + while(encrypt_timer.under(runtime) && decrypt_timer.under(runtime)) + { + const Botan::SymmetricKey key(rng(), enc.key_spec().maximum_keylength()); + const Botan::secure_vector<uint8_t> iv = rng().random_vec(enc.default_nonce_length()); + + enc.set_key(key); + dec.set_key(key); + + enc.start(iv); + dec.start(iv); + + // Must run in this order, or AEADs will reject the ciphertext + encrypt_timer.run([&] { enc.finish(buffer); }); + decrypt_timer.run([&] { dec.finish(buffer); }); + } + + output() << encrypt_timer << decrypt_timer; + } + +#if defined(BOTAN_HAS_NUMBERTHEORY) + void bench_random_prime(const std::chrono::milliseconds runtime) + { + const size_t coprime = 65537; // simulates RSA key gen + + for(size_t bits : { 1024, 1536 }) + { + Timer genprime_timer("random_prime " + std::to_string(bits)); + Timer is_prime_timer("is_prime " + std::to_string(bits)); + + while(genprime_timer.under(runtime) && is_prime_timer.under(runtime)) + { + const Botan::BigInt p = genprime_timer.run([&] { + return Botan::random_prime(rng(), bits, coprime); }); + + const bool ok = is_prime_timer.run([&] { + return Botan::is_prime(p, rng(), 64, true); + }); + + if(!ok) + { + error_output() << "Generated prime " << p + << " which then failed 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, rng(), 64, true); }); + } + } + + output() << genprime_timer << 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, padding, provider); + Botan::PK_Decryptor_EME dec(key, padding, provider); + + Timer enc_timer(nm, provider, "encrypt"); + Timer dec_timer(nm, 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)) + { + plaintext = unlock(rng().random_vec(enc.maximum_input_size())); + ciphertext = enc_timer.run([&] { return enc.encrypt(plaintext, rng()); }); + } + + if(dec_timer.under(msec)) + { + 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"; + } + } + } + + output() << enc_timer; + output() << dec_timer; + } + + void bench_pk_ka(const Botan::PK_Key_Agreement_Key& key1, + const Botan::PK_Key_Agreement_Key& key2, + const std::string& nm, + const std::string& provider, + const std::string& kdf, + std::chrono::milliseconds msec) + { + Botan::PK_Key_Agreement ka1(key1, kdf /*, provider */); + Botan::PK_Key_Agreement ka2(key2, kdf /*, provider */); + + const std::vector<uint8_t> ka1_pub = key1.public_value(); + const std::vector<uint8_t> ka2_pub = key2.public_value(); + + Timer ka_timer(nm, provider, "key agreements"); + + while(ka_timer.under(msec)) + { + Botan::SymmetricKey key1 = ka_timer.run([&] { return ka1.derive_key(32, ka2_pub); }); + Botan::SymmetricKey key2 = ka_timer.run([&] { return ka2.derive_key(32, ka1_pub); }); + + if(key1 != key2) + { + error_output() << "Key agreement mismatch in PK bench\n"; + } + } + + output() << ka_timer; + } + + void 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, padding, Botan::IEEE_1363, provider); + Botan::PK_Verifier ver(key, padding, Botan::IEEE_1363, provider); + + Timer sig_timer(nm, provider, "sign"); + Timer ver_timer(nm, provider, "verify"); + + 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. + */ + message = unlock(rng().random_vec(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) + { + error_output() << "Correct signature rejected in PK signature bench\n"; + } + + 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"; + } + } + } + + output() << sig_timer; + output() << ver_timer; + } +#endif + +#if defined(BOTAN_HAS_RSA) + 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); + + Timer keygen_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer.run([&] { + return new Botan::RSA_PrivateKey(rng(), keylen); + })); + + output() << keygen_timer; + + // Using PKCS #1 padding so OpenSSL provider can play along + bench_pk_enc(*key, nm, provider, "EME-PKCS1-v1_5", msec); + bench_pk_sig(*key, nm, provider, "EMSA-PKCS1-v1_5(SHA-1)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_ECDSA) + void bench_ecdsa(const std::string& provider, + std::chrono::milliseconds msec) + { + for(std::string grp : { "secp256r1", "secp384r1", "secp521r1" }) + { + const std::string nm = "ECDSA-" + grp; + + Timer keygen_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::Private_Key> key(keygen_timer.run([&] { + return new Botan::ECDSA_PrivateKey(rng(), grp); + })); + + output() << keygen_timer; + bench_pk_sig(*key, nm, provider, "EMSA1(SHA-256)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) + void bench_dh(const std::string& provider, + std::chrono::milliseconds msec) + { + for(size_t bits : { 1024, 2048, 3072 }) + { + const std::string grp = "modp/ietf/" + std::to_string(bits); + const std::string nm = "DH-" + std::to_string(bits); + + Timer keygen_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::PK_Key_Agreement_Key> key1(keygen_timer.run([&] { + return new Botan::DH_PrivateKey(rng(), grp); + })); + std::unique_ptr<Botan::PK_Key_Agreement_Key> key2(keygen_timer.run([&] { + return new Botan::DH_PrivateKey(rng(), grp); + })); + + output() << keygen_timer; + + bench_pk_ka(*key1, *key2, nm, provider, "KDF2(SHA-256)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_ECDH) + void bench_ecdh(const std::string& provider, + std::chrono::milliseconds msec) + { + for(std::string grp : { "secp256r1", "secp384r1", "secp521r1" }) + { + const std::string nm = "ECDH-" + grp; + + Timer keygen_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::PK_Key_Agreement_Key> key1(keygen_timer.run([&] { + return new Botan::ECDH_PrivateKey(rng(), grp); + })); + std::unique_ptr<Botan::PK_Key_Agreement_Key> key2(keygen_timer.run([&] { + return new Botan::ECDH_PrivateKey(rng(), grp); + })); + + output() << keygen_timer; + + bench_pk_ka(*key1, *key2, nm, provider, "KDF2(SHA-256)", msec); + } + } +#endif + +#if defined(BOTAN_HAS_CURVE_25519) + void bench_curve25519(const std::string& provider, + std::chrono::milliseconds msec) + { + const std::string nm = "Curve25519"; + + Timer keygen_timer(nm, provider, "keygen"); + + std::unique_ptr<Botan::PK_Key_Agreement_Key> key1(keygen_timer.run([&] { + return new Botan::Curve25519_PrivateKey(rng()); + })); + std::unique_ptr<Botan::PK_Key_Agreement_Key> key2(keygen_timer.run([&] { + return new Botan::Curve25519_PrivateKey(rng()); + })); + + output() << keygen_timer; + + bench_pk_ka(*key1, *key2, nm, provider, "KDF2(SHA-256)", msec); + } +#endif + + + }; + +BOTAN_REGISTER_COMMAND(Benchmark); + +} diff --git a/src/cli/ca.cpp b/src/cli/ca.cpp deleted file mode 100644 index c62b4f231..000000000 --- a/src/cli/ca.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_X509_CERTIFICATES) - -#include <botan/x509_ca.h> -#include <botan/pkcs8.h> - -namespace { - -int ca(const std::vector<std::string> &args) - { - using namespace Botan; - - if(args.size() != 5) - { - std::cout << "Usage: " << args[0] << " " - << "passphrase ca_cert ca_key pkcs10" - << std::endl; - return 1; - } - - const std::string arg_passphrase = args[1]; - const std::string arg_ca_cert = args[2]; - const std::string arg_ca_key = args[3]; - const std::string arg_req_file = args[4]; - - try - { - AutoSeeded_RNG rng; - - X509_Certificate ca_cert(arg_ca_cert); - - std::unique_ptr<PKCS8_PrivateKey> privkey( - PKCS8::load_key(arg_ca_key, rng, arg_passphrase) - ); - - X509_CA ca(ca_cert, *privkey, "SHA-256"); - - // got a request - PKCS10_Request req(arg_req_file); - - // you would insert checks here, and perhaps modify the request - // (this example should be extended to show how) - - // now sign the request - auto now = std::chrono::system_clock::now(); - - X509_Time start_time(now); - - typedef std::chrono::duration<int, std::ratio<31556926>> years; - - X509_Time end_time(now + years(1)); - - X509_Certificate new_cert = ca.sign_request(req, rng, - start_time, end_time); - - // send the new cert back to the requestor - std::cout << new_cert.PEM_encode(); - } - catch(std::exception& e) - { - std::cout << e.what() << std::endl; - return 1; - } - return 0; - } - -REGISTER_APP(ca); - -} - -#endif diff --git a/src/cli/cc_enc.cpp b/src/cli/cc_enc.cpp new file mode 100644 index 000000000..25c9b960b --- /dev/null +++ b/src/cli/cc_enc.cpp @@ -0,0 +1,161 @@ +/* +* (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 std::runtime_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 : public Command + { + public: + CC_Encrypt() : Command("cc_encrypt CC passphrase --tweak=") {} + + void go() + { + 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); + +class CC_Decrypt : public Command + { + public: + CC_Decrypt() : Command("cc_decrypt CC passphrase --tweak=") {} + + 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); + +} + +#endif // FPE && PBKDF diff --git a/src/cli/cert_verify.cpp b/src/cli/cert_verify.cpp deleted file mode 100644 index 7e03c81ab..000000000 --- a/src/cli/cert_verify.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* -* Simple example of a certificate validation -* (C) 2010 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_X509_CERTIFICATES) - -#include <botan/x509path.h> - -namespace { - -int cert_verify(const std::vector<std::string> &args) - { - using namespace Botan; - - if(args.size() < 3) - { - std::cout << "Usage: " << args[0] << " subject.pem CA_certificate [CA_certificate ...]" - << std::endl; - return 1; - } - - X509_Certificate subject_cert(args[1]); - - Certificate_Store_In_Memory certs; - - for(const auto certfile : std::vector<std::string>(args.begin()+2, args.end())) - { - certs.add_certificate(X509_Certificate(certfile)); - } - - Path_Validation_Restrictions restrictions; - - Path_Validation_Result result = - x509_path_validate(subject_cert, - restrictions, - certs); - - if(result.successful_validation()) - std::cout << "Certificate validated" << std::endl; - else - std::cout << "Certificate did not validate - " << result.result_string() << std::endl; - - return 0; - } - -REGISTER_APP(cert_verify); - -} - -#endif // BOTAN_HAS_X509_CERTIFICATES diff --git a/src/cli/cli.h b/src/cli/cli.h new file mode 100644 index 000000000..b2be2bd39 --- /dev/null +++ b/src/cli/cli.h @@ -0,0 +1,508 @@ +/* +* (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 <botan/parsing.h> +#include <fstream> +#include <iostream> +#include <functional> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +namespace Botan_CLI { + +class CLI_Error : public std::runtime_error + { + public: + CLI_Error(const std::string& s) : std::runtime_error(s) {} + }; + +class CLI_IO_Error : public CLI_Error + { + public: + CLI_IO_Error(const std::string& op, const std::string& who) : + CLI_Error("Error " + op + " " + who) {} + }; + +class CLI_Usage_Error : public CLI_Error + { + public: + 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 +*/ +class CLI_Error_Unsupported : public CLI_Error + { + public: + CLI_Error_Unsupported(const std::string& what, + const std::string& who) : + CLI_Error(what + " with '" + who + "' unsupported or not available") {} + }; + +struct CLI_Error_Invalid_Spec : public CLI_Error + { + public: + CLI_Error_Invalid_Spec(const std::string& spec) : + CLI_Error("Invalid command spec '" + spec + "'") {} + }; + +class Command + { + public: + /** + * 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") + */ + Command(const std::string& cmd_spec) : m_spec(cmd_spec) + { + // for checking all spec strings at load time + //parse_spec(); + } + + int run(const std::vector<std::string>& params) + { + try + { + // avoid parsing specs except for the command actually running + parse_spec(); + + std::vector<std::string> args; + for(auto&& 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); + } + } + + size_t arg_i = 0; + for(auto&& arg : m_spec_args) + { + if(arg_i >= 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())); + } + + m_user_args.insert(std::make_pair(arg, args[arg_i])); + + ++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()); + } + + if(flag_set("help")) + { + output() << help_text() << "\n"; + return 1; + } + + if(m_user_args.count("output")) + { + m_output_stream.reset(new std::ofstream(get_arg("output"))); + } + + if(m_user_args.count("error_output")) + { + m_error_output_stream.reset(new std::ofstream(get_arg("error_output"))); + } + + // Now insert any defaults for options not supplied by the user + for(auto&& opt : m_spec_opts) + { + if(m_user_args.count(opt.first) == 0) + { + m_user_args.insert(opt); + } + } + + this->go(); + return 0; + } + 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; + } + } + + virtual std::string help_text() const + { + return "Usage: " + m_spec; + } + + std::string cmd_name() const + { + return m_spec.substr(0, m_spec.find(' ')); + } + + protected: + + void parse_spec() + { + const std::vector<std::string> parts = Botan::split_on(m_spec, ' '); + + if(parts.size() == 0) + throw CLI_Error_Invalid_Spec(m_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(m_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(m_spec); + } + } + else + { + // named argument + if(!m_spec_rest.empty()) // rest arg wasn't last + throw CLI_Error("Invalid command spec " + m_spec); + + m_spec_args.push_back(s); + } + } + + m_spec_flags.insert("verbose"); + m_spec_flags.insert("help"); + m_spec_opts.insert(std::make_pair("output", "")); + m_spec_opts.insert(std::make_pair("error-output", "")); + } + + /* + * The actual functionality of the cli command implemented in subclas + */ + virtual void go() = 0; + + std::ostream& output() + { + if(m_output_stream.get()) + return *m_output_stream; + return std::cout; + } + + std::ostream& error_output() + { + if(m_error_output_stream.get()) + return *m_error_output_stream; + return std::cerr; + } + + bool verbose() const + { + return flag_set("verbose"); + } + + bool flag_set(const std::string& flag_name) const + { + return m_user_flags.count(flag_name) > 0; + } + + std::string 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; + } + + /* + * 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 + { + auto i = m_user_args.find(opt_name); + if(i == m_user_args.end() || i->second.empty()) + { + return otherwise; + } + return i->second; + } + + size_t 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& e) + { + throw CLI_Usage_Error("Invalid integer value '" + s + "' for option " + opt_name); + } + } + + std::vector<std::string> get_arg_list(const std::string& what) const + { + if(what != m_spec_rest) + throw CLI_Error("Unexpected list name '" + what + "'"); + + return m_user_rest; + } + + /* + * Read an entire file into memory and return the contents + */ + std::vector<uint8_t> slurp_file(const std::string& input_file) 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); + return buf; + } + + std::string slurp_file_as_str(const std::string& input_file) + { + 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); + return str; + } + + /* + * 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 + { + // Any need to support non-binary files here? + std::ifstream in(input_file, std::ios::binary); + + // 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()); + consumer_fn(buf.data(), in.gcount()); + } + } + + template<typename Alloc> + void write_output_file(const std::string& who, + const std::vector<uint8_t, Alloc>& vec) const + { + write_output_file(who, vec.begin(), vec.size()); + } + + void write_output_file(const std::string& output_file, + const uint8_t buf[], size_t buf_len) const + { + std::ofstream out(output_file, std::ios::binary); + if(!out.good()) + throw CLI_IO_Error("writing", output_file); + + out.write(reinterpret_cast<const char*>(buf), buf_len); + out.close(); + } + + void write_output_file(const std::string& output_file, const std::string& outstr) const + { + std::ofstream out(output_file); + if(!out.good()) + throw CLI_IO_Error("writing", output_file); + + out.write(outstr.data(), outstr.size()); + out.close(); + } + + private: + // set in constructor + std::string m_spec; + + // set in parse_spec() from m_spec + 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 run() from user args + std::map<std::string, std::string> m_user_args; + std::set<std::string> m_user_flags; + std::vector<std::string> m_user_rest; + + std::unique_ptr<std::ofstream> m_output_stream; + std::unique_ptr<std::ofstream> m_error_output_stream; + + + public: + // the registry interface: + + static std::map<std::string, std::unique_ptr<Command>>& global_registry() + { + static std::map<std::string, std::unique_ptr<Command>> g_cmds; + return g_cmds; + } + + static Command* get_cmd(const std::string& name) + { + auto& reg = Command::global_registry(); + auto i = reg.find(name); + if(i == reg.end()) + { + return nullptr; + } + + return i->second.get(); + } + + class Registration + { + public: + Registration(Command* cmd) + { + const std::string name = cmd->cmd_name(); + auto& reg = Command::global_registry(); + + if(reg.count(name) > 0) + { + throw std::logic_error("Duplicated registration of command " + name); + } + + Command::global_registry().insert(std::make_pair(name, std::unique_ptr<Command>(cmd))); + } + }; + }; + +#define BOTAN_REGISTER_COMMAND(CLI_Class) \ + namespace { Botan_CLI::Command::Registration reg_cmd_ ## CLI_Class(new CLI_Class); } + +} + +#endif diff --git a/src/cli/compress.cpp b/src/cli/compress.cpp index b0d0144d5..5d32267ce 100644 --- a/src/cli/compress.cpp +++ b/src/cli/compress.cpp @@ -4,24 +4,30 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include "apps.h" +#include "cli.h" + +#include <botan/transform.h> #if defined(BOTAN_HAS_COMPRESSION) + #include <botan/compression.h> +#endif -#include <botan/compression.h> -#include <fstream> +namespace Botan_CLI { namespace { -void do_compress(Transform& comp, std::ifstream& in, std::ostream& out) +void do_compress(Botan::Transform& comp, + std::ifstream& in, + std::ostream& out, + size_t buf_size) { - secure_vector<byte> buf; + Botan::secure_vector<uint8_t> buf; comp.start(); while(in.good()) { - buf.resize(64*1024); + buf.resize(buf_size); in.read(reinterpret_cast<char*>(&buf[0]), buf.size()); buf.resize(in.gcount()); @@ -35,92 +41,114 @@ void do_compress(Transform& comp, std::ifstream& in, std::ostream& out) out.write(reinterpret_cast<const char*>(&buf[0]), buf.size()); } -int compress(const std::vector<std::string> &args) - { - if(args.size() < 2 || args.size() > 4) - { - std::cout << "Usage: " << args[0] << " input [type] [level]" << std::endl; - return 1; - } +} - const std::string in_file = args[1]; - std::ifstream in(in_file); +class Compress : 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; + } + + void go() override + { + const std::string comp_type = get_arg("type"); + + std::unique_ptr<Botan::Transform> compress; - if(!in.good()) - { - std::cout << "Couldn't read " << in_file << std::endl; - return 1; - } +#if defined(BOTAN_HAS_COMPRESSION) + compress.reset(Botan::make_compressor(comp_type, get_arg_sz("level"))); +#endif - const std::string suffix = args.size() >= 3 ? args[2] : "gz"; - const size_t level = args.size() >= 4 ? to_u32bit(args[3]) : 9; + if(!compress) + { + throw CLI_Error_Unsupported("Compression", comp_type); + } - std::unique_ptr<Transform> compress(make_compressor(suffix, level)); + const std::string in_file = get_arg("file"); + std::ifstream in(in_file); - if(!compress) - { - std::cout << suffix << " compression not supported" << std::endl; - return 1; - } + if(!in.good()) + { + throw CLI_IO_Error("reading", in_file); + } - const std::string out_file = in_file + "." + suffix; - std::ofstream out(out_file); + const std::string out_file = output_filename(in_file, comp_type); + std::ofstream out(out_file); + if(!in.good()) + { + throw CLI_IO_Error("writing", out_file); + } - do_compress(*compress, in, out); + do_compress(*compress, in, out, get_arg_sz("buf_size")); + } + }; - return 0; - } +BOTAN_REGISTER_COMMAND(Compress); -void parse_extension(const std::string& in_file, - std::string& out_file, - std::string& suffix) +class Decompress : public Command { - auto last_dot = in_file.find_last_of('.'); - if(last_dot == std::string::npos || last_dot == 0) - throw std::runtime_error("No extension detected in filename '" + in_file + "'"); + public: + Decompress() : Command("decompress file") {} - out_file = in_file.substr(0, last_dot); - suffix = in_file.substr(last_dot+1, std::string::npos); - } + 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 + "'"); -int uncompress(const std::vector<std::string> &args) - { - if(args.size() != 2) - { - std::cout << "Usage: " << args[0] << " <file>" << std::endl; - return 1; - } + out_file = in_file.substr(0, last_dot); + suffix = in_file.substr(last_dot+1, std::string::npos); + } - const std::string in_file = args[1]; - std::ifstream in(in_file); + void go() override + { + const std::string in_file = get_arg("file"); + std::string out_file, suffix; + parse_extension(in_file, out_file, suffix); - if(!in.good()) - { - std::cout << "Couldn't read '" << args[1] << "'" << std::endl; - return 1; - } + std::ifstream in(in_file); - std::string out_file, suffix; - parse_extension(in_file, out_file, suffix); + if(!in.good()) + throw CLI_IO_Error("reading", in_file); - std::ofstream out(out_file); + std::unique_ptr<Botan::Transform> decompress; - std::unique_ptr<Transform> decompress(make_decompressor(suffix)); +#if defined(BOTAN_HAS_COMPRESSION) + decompress.reset(Botan::make_decompressor(suffix)); +#endif - if(!decompress) - { - std::cout << suffix << " decompression not supported" << std::endl; - return 1; - } + if(!decompress) + throw CLI_Error_Unsupported("Decompression", suffix); - do_compress(*decompress, in, out); + std::ofstream out(out_file); + if(!out.good()) + throw CLI_IO_Error("writing", out_file); - return 0; - } + do_compress(*decompress, in, out, get_arg_sz("buf_size")); -REGISTER_APP(compress); -REGISTER_APP(uncompress); + } + }; -} +BOTAN_REGISTER_COMMAND(Decompress); -#endif // BOTAN_HAS_COMPRESSION +} diff --git a/src/cli/credentials.h b/src/cli/credentials.h index 06349657d..11bfd3de1 100644 --- a/src/cli/credentials.h +++ b/src/cli/credentials.h @@ -23,7 +23,7 @@ inline bool value_exists(const std::vector<std::string>& vec, return false; } -class Basic_Credentials_Manager : public Credentials_Manager +class Basic_Credentials_Manager : public Botan::Credentials_Manager { public: Basic_Credentials_Manager() @@ -31,20 +31,20 @@ class Basic_Credentials_Manager : public Credentials_Manager load_certstores(); } - Basic_Credentials_Manager(RandomNumberGenerator& rng, + Basic_Credentials_Manager(Botan::RandomNumberGenerator& rng, const std::string& server_crt, const std::string& server_key) { Certificate_Info cert; - cert.key.reset(PKCS8::load_key(server_key, rng)); + cert.key.reset(Botan::PKCS8::load_key(server_key, rng)); - DataSource_Stream in(server_crt); + Botan::DataSource_Stream in(server_crt); while(!in.end_of_data()) { try { - cert.certs.push_back(X509_Certificate(in)); + cert.certs.push_back(Botan::X509_Certificate(in)); } catch(std::exception& e) { @@ -66,7 +66,7 @@ class Basic_Credentials_Manager : public Credentials_Manager for(auto&& path : paths) { - std::shared_ptr<Certificate_Store> cs(new Certificate_Store_In_Memory(path)); + std::shared_ptr<Botan::Certificate_Store> cs(new Botan::Certificate_Store_In_Memory(path)); m_certstores.push_back(cs); } } @@ -95,7 +95,7 @@ class Basic_Credentials_Manager : public Credentials_Manager void verify_certificate_chain( const std::string& type, const std::string& purported_hostname, - const std::vector<X509_Certificate>& cert_chain) override + const std::vector<Botan::X509_Certificate>& cert_chain) override { try { @@ -110,7 +110,7 @@ class Basic_Credentials_Manager : public Credentials_Manager } } - std::vector<X509_Certificate> cert_chain( + std::vector<Botan::X509_Certificate> cert_chain( const std::vector<std::string>& algos, const std::string& type, const std::string& hostname) override @@ -128,12 +128,12 @@ class Basic_Credentials_Manager : public Credentials_Manager return i.certs; } - return std::vector<X509_Certificate>(); + return std::vector<Botan::X509_Certificate>(); } - Private_Key* private_key_for(const X509_Certificate& cert, - const std::string& /*type*/, - const std::string& /*context*/) override + Botan::Private_Key* private_key_for(const Botan::X509_Certificate& cert, + const std::string& /*type*/, + const std::string& /*context*/) override { for(auto&& i : m_creds) { @@ -147,12 +147,12 @@ class Basic_Credentials_Manager : public Credentials_Manager private: struct Certificate_Info { - std::vector<X509_Certificate> certs; - std::shared_ptr<Private_Key> key; + std::vector<Botan::X509_Certificate> certs; + std::shared_ptr<Botan::Private_Key> key; }; std::vector<Certificate_Info> m_creds; - std::vector<std::shared_ptr<Certificate_Store>> m_certstores; + std::vector<std::shared_ptr<Botan::Certificate_Store>> m_certstores; }; #endif diff --git a/src/cli/dl_group.cpp b/src/cli/dl_group.cpp deleted file mode 100644 index e9a4f3fd4..000000000 --- a/src/cli/dl_group.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* -* (C) 2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_DL_GROUP) - -#include <botan/dl_group.h> -#include <fstream> - -namespace { - -std::string read_file_contents(const std::string& filename) - { - std::ifstream in(filename.c_str()); - if(!in.good()) - throw std::runtime_error("Failure reading " + filename); - - std::vector<std::string> contents; - size_t total_size = 0; - while(in.good()) - { - std::string line; - std::getline(in, line); - total_size += line.size(); - contents.push_back(std::move(line)); - } - - std::string res; - contents.reserve(total_size); - for(auto&& line : contents) - res += line; - return res; - } - -int dl_group(const std::vector<std::string> &args) - { - if(args.size() < 2) - { - std::cout << "Usage: " << args[0] << " [create bits|info file]" << std::endl; - return 1; - } - - const std::string cmd = args[1]; - - if(cmd == "create") - { - AutoSeeded_RNG rng; - const size_t bits = to_u32bit(args[2]); - - const DL_Group::PrimeType prime_type = DL_Group::Strong; - //const DL_Group::PrimeType prime_type = DL_Group::Prime_Subgroup; - - DL_Group grp(rng, prime_type, bits); - - std::cout << grp.PEM_encode(DL_Group::DSA_PARAMETERS); - } - else if(cmd == "info") - { - DL_Group grp; - std::string pem = read_file_contents(args[2]); - std::cout << pem << "\n"; - - std::cout << "DL_Group " << grp.get_p().bits() << " bits\n"; - std::cout << "p=" << grp.get_p() << "\n"; - std::cout << "q=" << grp.get_q() << "\n"; - std::cout << "g=" << grp.get_g() << "\n"; - } - else - { - std::cout << "ERROR: Unknown command\n"; - return 1; - } - - return 0; - } - -REGISTER_APP(dl_group); - -} - -#endif diff --git a/src/cli/dsa_sign.cpp b/src/cli/dsa_sign.cpp deleted file mode 100644 index 03aede585..000000000 --- a/src/cli/dsa_sign.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_DSA) - -#include <botan/dsa.h> -#include <botan/pubkey.h> -#include <botan/pkcs8.h> -#include <botan/base64.h> -#include <fstream> - -namespace { - -int dsa_sign(const std::vector<std::string> &args) - { - using namespace Botan; - - const std::string SUFFIX = ".sig"; - - if(args.size() != 4) - { - std::cout << "Usage: " << args[0] << " keyfile messagefile passphrase" - << std::endl; - return 1; - } - - try { - std::string passphrase(args[3]); - - std::ifstream message(args[2], std::ios::binary); - if(!message) - { - std::cout << "Couldn't read the message file." << std::endl; - return 1; - } - - std::string outfile = args[2] + SUFFIX; - std::ofstream sigfile(outfile); - if(!sigfile) - { - std::cout << "Couldn't write the signature to " - << outfile << std::endl; - return 1; - } - - AutoSeeded_RNG rng; - - std::unique_ptr<PKCS8_PrivateKey> key( - PKCS8::load_key(args[1], rng, passphrase) - ); - - DSA_PrivateKey* dsakey = dynamic_cast<DSA_PrivateKey*>(key.get()); - - if(!dsakey) - { - std::cout << "The loaded key is not a DSA key!" << std::endl; - return 1; - } - - PK_Signer signer(*dsakey, "EMSA1(SHA-1)"); - - DataSource_Stream in(message); - byte buf[4096] = { 0 }; - while(size_t got = in.read(buf, sizeof(buf))) - signer.update(buf, got); - - sigfile << base64_encode(signer.signature(rng)) << std::endl; - } - catch(std::exception& e) - { - std::cout << "Exception caught: " << e.what() << std::endl; - } - return 0; - } - -REGISTER_APP(dsa_sign); - -} - -#endif diff --git a/src/cli/dsa_ver.cpp b/src/cli/dsa_ver.cpp deleted file mode 100644 index 13f71b59d..000000000 --- a/src/cli/dsa_ver.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_DSA) - -#include <fstream> -#include <botan/pubkey.h> -#include <botan/dsa.h> -#include <botan/base64.h> - -using namespace Botan; - -namespace { - -int dsa_verify(const std::vector<std::string> &args) - { - if(args.size() != 4) - { - std::cout << "Usage: " << args[0] << " " - << "keyfile messagefile sigfile" - << std::endl; - return 1; - } - - - try { - std::ifstream message(args[2], std::ios::binary); - if(!message) - { - std::cout << "Couldn't read the message file." << std::endl; - return 1; - } - - std::ifstream sigfile(args[3]); - if(!sigfile) - { - std::cout << "Couldn't read the signature file." << std::endl; - return 1; - } - - std::string sigstr; - getline(sigfile, sigstr); - - std::unique_ptr<X509_PublicKey> key(X509::load_key(args[1])); - DSA_PublicKey* dsakey = dynamic_cast<DSA_PublicKey*>(key.get()); - - if(!dsakey) - { - std::cout << "The loaded key is not a DSA key!" << std::endl; - return 1; - } - - secure_vector<byte> sig = base64_decode(sigstr); - - PK_Verifier ver(*dsakey, "EMSA1(SHA-1)"); - - DataSource_Stream in(message); - byte buf[4096] = { 0 }; - while(size_t got = in.read(buf, sizeof(buf))) - ver.update(buf, got); - - const bool ok = ver.check_signature(sig); - - if(ok) - { - std::cout << "Signature verified" << std::endl; - return 0; - } - else - { - std::cout << "Signature did NOT verify" << std::endl; - return 1; - } - } - catch(std::exception& e) - { - std::cout << "Exception caught: " << e.what() << std::endl; - return 2; - } - } - -REGISTER_APP(dsa_verify); - -} - -#endif - diff --git a/src/cli/factor.cpp b/src/cli/factor.cpp deleted file mode 100644 index 52da46153..000000000 --- a/src/cli/factor.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* -* (C) 2009-2010 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -* -* Factor integers using a combination of trial division by small -* primes, and Pollard's Rho algorithm -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_NUMBERTHEORY) - -#include <botan/reducer.h> -#include <botan/numthry.h> - -#include <algorithm> -#include <iostream> -#include <iterator> - -using namespace Botan; - -namespace { - -/* -* Pollard's Rho algorithm, as described in the MIT algorithms book. We -* use (x^2+x) mod n instead of (x*2-1) mod n as the random function, -* it _seems_ to lead to faster factorization for the values I tried. -*/ -BigInt rho(const BigInt& n, RandomNumberGenerator& rng) - { - BigInt x = BigInt::random_integer(rng, 0, n-1); - BigInt y = x; - BigInt d = 0; - - Modular_Reducer mod_n(n); - - u32bit i = 1, k = 2; - while(true) - { - i++; - - if(i == 0) // overflow, bail out - break; - - x = mod_n.multiply((x + 1), x); - - d = gcd(y - x, n); - if(d != 1 && d != n) - return d; - - if(i == k) - { - y = x; - k = 2*k; - } - } - return 0; - } - -// Remove (and return) any small (< 2^16) factors -std::vector<BigInt> remove_small_factors(BigInt& n) - { - std::vector<BigInt> factors; - - while(n.is_even()) - { - factors.push_back(2); - n /= 2; - } - - for(u32bit j = 0; j != PRIME_TABLE_SIZE; j++) - { - if(n < PRIMES[j]) - break; - - BigInt x = gcd(n, PRIMES[j]); - - if(x != 1) - { - n /= x; - - u32bit occurs = 0; - while(x != 1) - { - x /= PRIMES[j]; - occurs++; - } - - for(u32bit k = 0; k != occurs; k++) - factors.push_back(PRIMES[j]); - } - } - - return factors; - } - -std::vector<BigInt> factorize(const BigInt& n_in, - RandomNumberGenerator& rng) - { - BigInt n = n_in; - std::vector<BigInt> factors = remove_small_factors(n); - - while(n != 1) - { - if(is_prime(n, rng)) - { - factors.push_back(n); - break; - } - - BigInt a_factor = 0; - while(a_factor == 0) - a_factor = rho(n, rng); - - std::vector<BigInt> rho_factored = factorize(a_factor, rng); - for(u32bit j = 0; j != rho_factored.size(); j++) - factors.push_back(rho_factored[j]); - - n /= a_factor; - } - return factors; - } - -int factor(const std::vector<std::string> &args) - { - if(args.size() != 2) - { - std::cout << "Usage: " << args[0] << " integer" << std::endl; - return 1; - } - - try - { - BigInt n(args[1]); - - AutoSeeded_RNG rng; - - std::vector<BigInt> factors = factorize(n, rng); - std::sort(factors.begin(), factors.end()); - - std::cout << n << ": "; - std::copy(factors.begin(), - factors.end(), - std::ostream_iterator<BigInt>(std::cout, " ")); - std::cout << std::endl; - } - catch(std::exception& e) - { - std::cout << e.what() << std::endl; - return 1; - } - return 0; - } - -REGISTER_APP(factor); - -} - -#endif // BOTAN_HAS_NUMBERTHEORY diff --git a/src/cli/fpe.cpp b/src/cli/fpe.cpp deleted file mode 100644 index 10b3096bf..000000000 --- a/src/cli/fpe.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_FPE_FE1) && defined(BOTAN_HAS_SHA1) - -#include <botan/fpe_fe1.h> -#include <botan/sha160.h> - -using namespace Botan; - -namespace { - -byte luhn_checksum(u64bit cc_number) - { - byte sum = 0; - - bool alt = false; - while(cc_number) - { - byte 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(u64bit cc_number) - { - return (luhn_checksum(cc_number) == 0); - } - -u64bit cc_rank(u64bit cc_number) - { - // Remove Luhn checksum - return cc_number / 10; - } - -u64bit cc_derank(u64bit cc_number) - { - for(u32bit i = 0; i != 10; ++i) - if(luhn_check(cc_number * 10 + i)) - return (cc_number * 10 + i); - return 0; - } - -/* -* Use the SHA-1 hash of the account name or ID as a tweak -*/ -std::vector<byte> sha1(const std::string& acct_name) - { - SHA_160 hash; - hash.update(acct_name); - return unlock(hash.final()); - } - -u64bit encrypt_cc_number(u64bit cc_number, - const SymmetricKey& key, - const std::string& acct_name) - { - BigInt n = 1000000000000000; - - u64bit cc_ranked = cc_rank(cc_number); - - BigInt c = FPE::fe1_encrypt(n, cc_ranked, key, sha1(acct_name)); - - if(c.bits() > 50) - throw std::runtime_error("FPE produced a number too large"); - - u64bit enc_cc = 0; - for(u32bit i = 0; i != 7; ++i) - enc_cc = (enc_cc << 8) | c.byte_at(6-i); - return cc_derank(enc_cc); - } - -u64bit decrypt_cc_number(u64bit enc_cc, - const SymmetricKey& key, - const std::string& acct_name) - { - BigInt n = 1000000000000000; - - u64bit cc_ranked = cc_rank(enc_cc); - - BigInt c = FPE::fe1_decrypt(n, cc_ranked, key, sha1(acct_name)); - - if(c.bits() > 50) - throw std::runtime_error("FPE produced a number too large"); - - u64bit dec_cc = 0; - for(u32bit i = 0; i != 7; ++i) - dec_cc = (dec_cc << 8) | c.byte_at(6-i); - return cc_derank(dec_cc); - } - -int fpe(const std::vector<std::string> &args) - { - if(args.size() != 4) - { - std::cout << "Usage: " << args[0] << " cc-number acct-name passwd" << std::endl; - return 1; - } - - u64bit cc_number = atoll(args[1].c_str()); - std::string acct_name = args[2]; - std::string passwd = args[3]; - - std::cout << "Input was: " << cc_number << ' ' - << luhn_check(cc_number) << std::endl; - - /* - * In practice something like PBKDF2 with a salt and high iteration - * count would be a good idea. - */ - SymmetricKey key(sha1(passwd)); - - u64bit enc_cc = encrypt_cc_number(cc_number, key, acct_name); - - std::cout << "Encrypted: " << enc_cc - << ' ' << luhn_check(enc_cc) << std::endl; - - u64bit dec_cc = decrypt_cc_number(enc_cc, key, acct_name); - - std::cout << "Decrypted: " << dec_cc - << ' ' << luhn_check(dec_cc) << std::endl; - - if(dec_cc != cc_number) - { - std::cout << "Something went wrong :( Bad CC checksum?" << std::endl; - return 2; - } - - return 0; - } - -REGISTER_APP(fpe); - -} - -#endif // BOTAN_HAS_FPE_FE1 && BOTAN_HAS_SHA1 diff --git a/src/cli/fuzzer.cpp b/src/cli/fuzzer.cpp deleted file mode 100644 index 35adb9711..000000000 --- a/src/cli/fuzzer.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* -* (C) 2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" -#include <fstream> -#include <botan/auto_rng.h> - -#if defined(BOTAN_HAS_X509_CERTIFICATES) -#include <botan/x509cert.h> -#include <botan/x509_crl.h> -#include <botan/pkcs8.h> -#endif - -#if defined(BOTAN_HAS_TLS) -#include <botan/tls_client.h> -#endif - -namespace { - -#if defined(BOTAN_HAS_TLS) - -class Fuzzer_Creds : public Credentials_Manager - { - public: - void verify_certificate_chain(const std::string& type, - const std::string& purported_hostname, - const std::vector<X509_Certificate>& cert_chain) override - { - try - { - Credentials_Manager::verify_certificate_chain(type, - purported_hostname, - cert_chain); - } - catch(std::exception& e) {} - } - - std::string psk_identity_hint(const std::string&, const std::string&) override { return "psk_hint"; } - std::string psk_identity(const std::string&, const std::string&, const std::string&) override { return "psk_id"; } - SymmetricKey psk(const std::string&, const std::string&, const std::string&) override - { - return SymmetricKey("AABBCCDDEEFF00112233445566778899"); - } - }; - -#endif - -int fuzzer(const std::vector<std::string> &args) - { - if(args.size() != 3) - { - std::cout << "Usage: " << args[0] << " [type] [input_file]\n" - << "Hook for fuzzers such as afl (produces no output)\n" - << "Types: cert crl privkey tls_client" << std::endl; - return 1; - } - - const std::string type = args[1]; - const std::string input = args[2]; - - AutoSeeded_RNG rng; - -#if defined(BOTAN_HAS_X509_CERTIFICATES) - if(type == "cert") - { - X509_Certificate cert(input); - return 0; - } - - if(type == "crl") - { - X509_CRL crl(input); - return 0; - } - - if(type == "privkey") - { - std::unique_ptr<Private_Key>(PKCS8::load_key(input, rng)); - return 0; - } - -#endif - -#if defined(BOTAN_HAS_TLS) - if(type == "tls_client") - { - auto dev_null = [](const byte[], size_t) {}; - - auto ignore_alerts = [](TLS::Alert, const byte[], size_t) {}; - auto ignore_hs = [](const TLS::Session&) { return true; }; - - TLS::Session_Manager_In_Memory session_manager(rng); - TLS::Policy policy; - TLS::Protocol_Version client_offer = TLS::Protocol_Version::TLS_V12; - TLS::Server_Information info("server.name", 443); - const std::vector<std::string> protocols_to_offer = { "fuzz/1.0", "http/1.1", "bunny/1.21.3" }; - Fuzzer_Creds creds; - - TLS::Client client(dev_null, - dev_null, - ignore_alerts, - ignore_hs, - session_manager, - creds, - policy, - rng, - info, - client_offer, - protocols_to_offer); - - std::ifstream in(input.c_str()); - - std::vector<byte> buf(1024); - - try - { - while(in.good()) - { - in.read((char*)&buf[0], buf.size()); - size_t got = in.gcount(); - client.received_data(&buf[0], got); - } - } - catch(std::exception& e) - { - return 0; - } - return 0; - } -#endif - - std::cout << "Unknown type '" << type << "'" << std::endl; - return 1; - } - -REGISTER_APP(fuzzer); - -} diff --git a/src/cli/getopt.cpp b/src/cli/getopt.cpp deleted file mode 100644 index fde3b5ce9..000000000 --- a/src/cli/getopt.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* -* (C) 2009 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "getopt.h" -#include <iostream> - -void OptionParser::parse(const std::vector<std::string> &args) - { - const std::string appname = args[0]; - - // ship first, args[0] is the app name - for(size_t j = 1; j != args.size(); j++) - { - std::string arg = args[j]; - - // FIXME: cli app must manually query if user requested help - // in order to be able to stop the cpp. At the moment e.g. - // `./botan keygen --help` generates keys. - if(arg == "help" || arg == "--help" || arg == "-h") - { - help(std::cout, appname); - return; - } - - if(arg.size() > 2 && arg[0] == '-' && arg[1] == '-') - { - const std::string opt_name = arg.substr(0, arg.find('=')); - - arg = arg.substr(2); - - std::string::size_type mark = arg.find('='); - OptionFlag opt = find_option(arg.substr(0, mark)); - - if(opt.takes_arg()) - { - if(mark == std::string::npos) - throw std::runtime_error("Option " + opt_name + - " requires an argument"); - - std::string name = arg.substr(0, mark); - std::string value = arg.substr(mark+1); - - options[name] = value; - } - else - { - if(mark != std::string::npos) - throw std::runtime_error("Option " + opt_name + " does not take an argument"); - - options[arg] = ""; - } - } - else - leftover.push_back(arg); - } - } diff --git a/src/cli/getopt.h b/src/cli/getopt.h deleted file mode 100644 index 21a12b1dc..000000000 --- a/src/cli/getopt.h +++ /dev/null @@ -1,106 +0,0 @@ -/* -* (C) 2009 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_CHECK_GETOPT_H__ -#define BOTAN_CHECK_GETOPT_H__ - -#include <string> -#include <vector> -#include <map> - -#include <botan/parsing.h> - -class OptionParser - { - public: - std::vector<std::string> arguments() const { return leftover; } - - bool is_set(const std::string& key) const - { - return (options.find(key) != options.end()); - } - - std::string value(const std::string& key) const - { - std::map<std::string, std::string>::const_iterator i = options.find(key); - if(i == options.end()) - throw std::runtime_error("Option '" + key + "' not set"); - return i->second; - } - - std::string value_if_set(const std::string& key) const - { - return value_or_else(key, ""); - } - - std::string value_or_else(const std::string& key, - const std::string& or_else) const - { - return is_set(key) ? value(key) : or_else; - } - - size_t int_value_or_else(const std::string& key, size_t or_else) const - { - return is_set(key) ? Botan::to_u32bit(value(key)) : or_else; - } - - void help(std::ostream& o, const std::string &appname) - { - o << "Usage: " << appname << " "; - - for(auto flag : flags) - { - o << "--" << flag.name(); - if(flag.takes_arg()) - o << "="; - o << " "; - } - - o << std::endl; - } - - void parse(const std::vector<std::string> &args); - - OptionParser(const std::string& opt_string) - { - std::vector<std::string> opts = Botan::split_on(opt_string, '|'); - - for(size_t j = 0; j != opts.size(); j++) - flags.push_back(OptionFlag(opts[j])); - } - - private: - class OptionFlag - { - public: - std::string name() const { return opt_name; } - bool takes_arg() const { return opt_takes_arg; } - - OptionFlag(const std::string& opt_string) - { - std::string::size_type mark = opt_string.find('='); - opt_name = opt_string.substr(0, mark); - opt_takes_arg = (mark != std::string::npos); - } - private: - std::string opt_name; - bool opt_takes_arg; - }; - - OptionFlag find_option(const std::string& name) const - { - for(size_t j = 0; j != flags.size(); j++) - if(flags[j].name() == name) - return flags[j]; - throw std::runtime_error("Unknown option " + name); - } - - std::vector<OptionFlag> flags; - std::map<std::string, std::string> options; - std::vector<std::string> leftover; - }; - -#endif diff --git a/src/cli/hash.cpp b/src/cli/hash.cpp deleted file mode 100644 index 9dd07d488..000000000 --- a/src/cli/hash.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* -* (C) 2009 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_CODEC_FILTERS) - -#include <botan/filters.h> -#include <iostream> -#include <fstream> - -using namespace Botan; - -namespace { - -int hash(const std::vector<std::string> &args) - { - if(args.size() < 3) - { - std::cout << "Usage: " << args[0] << " algorithm filename [filename ...]" << std::endl; - return 1; - } - - std::string hash = args[1]; - /* a couple of special cases, kind of a crock */ - if(hash == "sha1") hash = "SHA-1"; - if(hash == "md5") hash = "MD5"; - - try { - Pipe pipe(new Hash_Filter(hash), new Hex_Encoder); - - int skipped = 0; - for(int j = 2; j < args.size(); j++) - { - std::ifstream file(args[j], std::ios::binary); - if(!file) - { - std::cout << "ERROR: could not open " << args[j] << std::endl; - skipped++; - continue; - } - pipe.start_msg(); - file >> pipe; - pipe.end_msg(); - pipe.set_default_msg(j-2-skipped); - std::cout << pipe << " " << args[j] << std::endl; - } - } - catch(std::exception& e) - { - std::cout << "Exception caught: " << e.what() << std::endl; - } - return 0; - } - -REGISTER_APP(hash); - -} - -#endif // BOTAN_HAS_CODEC_FILTERS diff --git a/src/cli/implementation/speed.h b/src/cli/implementation/speed.h deleted file mode 100644 index 3cfd0ef61..000000000 --- a/src/cli/implementation/speed.h +++ /dev/null @@ -1,30 +0,0 @@ -/* -* (C) 2014 Jack Lloyd -* (C) 2015 Simon Warta (Kullo GmbH) -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_CHECK_BENCHMARK_H__ -#define BOTAN_CHECK_BENCHMARK_H__ - -#include <botan/rng.h> -#include <string> -#include <chrono> - -void benchmark_public_key(Botan::RandomNumberGenerator& rng, - const std::string& algo, - const std::string& provider, - double seconds); - -std::map<std::string, double> benchmark_is_prime(Botan::RandomNumberGenerator &rng, - const std::chrono::milliseconds runtime); - -std::map<std::string, double> benchmark_random_prime(Botan::RandomNumberGenerator &rng, - const std::chrono::milliseconds runtime); - -bool benchmark_transform(Botan::RandomNumberGenerator& rng, const std::string& algo_name, - const std::chrono::milliseconds runtime); - - -#endif diff --git a/src/cli/implementation/speed_prime.cpp b/src/cli/implementation/speed_prime.cpp deleted file mode 100644 index a7a344bef..000000000 --- a/src/cli/implementation/speed_prime.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* -* (C) 2015 Simon Warta (Kullo GmbH) -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "speed.h" - -using namespace Botan; - -#if defined(BOTAN_HAS_NUMBERTHEORY) - -#include <botan/numthry.h> -#include <chrono> - -namespace { -const size_t RSA_EXP = 65537; -const size_t RSA_BITS = 2048; - -const auto SAMPLE_DATA = std::vector<BigInt>{ - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895363"), - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895377"), - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895381"), - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895389"), - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895399"), - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895401"), - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895431"), - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895441"), - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895461"), - BigInt("147572674850319649408425528388978190129147580193826207917614581140087181349813950149403694089438460074197915858493514824951402666113125007396293585089750170962882245605820159076002066981429137353341175732273168874025070408827561035943771554301600092787967626039381375658829078877390735440179859333066156895471"), - }; -} - -#endif // BOTAN_HAS_NUMBERTHEORY - -std::map<std::string, double> benchmark_is_prime(RandomNumberGenerator& rng, - const std::chrono::milliseconds runtime) - { - std::map<std::string, double> speeds; - -#if defined(BOTAN_HAS_NUMBERTHEORY) - - std::chrono::nanoseconds time_used(0); - size_t reps = 0; - - auto start = std::chrono::high_resolution_clock::now(); - - while(time_used < runtime) - { - // main work - for (const BigInt &p : SAMPLE_DATA) - { - is_prime(p, rng, 64, true); - } - - ++reps; - time_used = std::chrono::high_resolution_clock::now() - start; - } - - const double seconds_used = static_cast<double>(time_used.count()) / 1000000000; - speeds["base"] = reps / seconds_used; // ie, return ops per second - -#endif // BOTAN_HAS_NUMBERTHEORY - - return speeds; - } - -std::map<std::string, double> benchmark_random_prime(RandomNumberGenerator& rng, - const std::chrono::milliseconds runtime) - { - std::map<std::string, double> speeds; - -#if defined(BOTAN_HAS_NUMBERTHEORY) - - std::chrono::nanoseconds time_used(0); - size_t reps = 0; - - auto start = std::chrono::high_resolution_clock::now(); - - while(time_used < runtime) - { - // main work - random_prime(rng, (RSA_BITS + 1) / 2, RSA_EXP); - - ++reps; - time_used = std::chrono::high_resolution_clock::now() - start; - } - - const double seconds_used = static_cast<double>(time_used.count()) / 1000000000; - speeds["base"] = reps / seconds_used; // ie, return ops per second - -#endif // BOTAN_HAS_NUMBERTHEORY - - return speeds; - } - diff --git a/src/cli/implementation/speed_public_key.cpp b/src/cli/implementation/speed_public_key.cpp deleted file mode 100644 index 2ff49bd15..000000000 --- a/src/cli/implementation/speed_public_key.cpp +++ /dev/null @@ -1,888 +0,0 @@ -/* -* (C) 2009-2010 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "../apps.h" - -#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) - -#include "speed.h" -#include "timer.h" - -#include <map> -#include <sstream> -#include <botan/mem_ops.h> -#include <botan/parsing.h> -#include <botan/pkcs8.h> -#include <botan/pubkey.h> -#include <botan/x509_key.h> - -#if defined(BOTAN_HAS_RSA) - #include <botan/rsa.h> -#endif - -#if defined(BOTAN_HAS_DSA) - #include <botan/dsa.h> -#endif - -#if defined(BOTAN_HAS_DIFFIE_HELLMAN) - #include <botan/dh.h> -#endif - -#if defined(BOTAN_HAS_CURVE_25519) - #include <botan/curve25519.h> -#endif - -#if defined(BOTAN_HAS_NYBERG_RUEPPEL) - #include <botan/nr.h> -#endif - -#if defined(BOTAN_HAS_RW) - #include <botan/rw.h> -#endif - -#if defined(BOTAN_HAS_ELGAMAL) - #include <botan/elgamal.h> -#endif - -#if defined(BOTAN_HAS_DLIES) && defined(BOTAN_HAS_KDF2) - #include <botan/dlies.h> - #include <botan/kdf2.h> - #include <botan/hmac.h> - #include <botan/sha160.h> -#endif - -#if defined(BOTAN_HAS_ECDSA) - #include <botan/ecdsa.h> -#endif - -#if defined(BOTAN_HAS_ECDH) - #include <botan/ecdh.h> -#endif - -#if defined(BOTAN_HAS_GOST_34_10_2001) - #include <botan/gost_3410.h> -#endif - -#if defined(BOTAN_HAS_MCELIECE) - #include <botan/mceliece.h> - #include <botan/mce_kem.h> -#endif - -using namespace Botan; - -#include <iostream> -#include <fstream> -#include <string> -#include <memory> -#include <set> - -namespace { - -const char* ec_domains[] = { - // "secp160r2", - // "secp192r1", - // "secp224r1", - "secp256r1", - "secp384r1", - "secp521r1", - //"brainpool256r1", - //"brainpool384r1", - //"brainpool512r1", - nullptr -}; - -class Benchmark_Report - { - public: - void report(const std::string& name, Timer timer) - { - std::cout << name << " " << timer << std::endl; - data[name].insert(timer); - } - - private: - std::map<std::string, std::set<Timer> > data; - }; - -void benchmark_enc_dec(PK_Encryptor& enc, PK_Decryptor& dec, - Timer& enc_timer, Timer& dec_timer, - RandomNumberGenerator& rng, - u32bit runs, double seconds) - { - std::vector<byte> plaintext, ciphertext; - - for(u32bit i = 0; i != runs; ++i) - { - if(enc_timer.seconds() < seconds || ciphertext.size() == 0) - { - plaintext.resize(enc.maximum_input_size()); - - // Ensure for Raw, etc, it stays large - if((i % 100) == 0) - { - rng.randomize(&plaintext[0], plaintext.size()); - plaintext[0] |= 0x80; - } - - enc_timer.start(); - ciphertext = enc.encrypt(plaintext, rng); - enc_timer.stop(); - } - - if(dec_timer.seconds() < seconds) - { - dec_timer.start(); - std::vector<byte> plaintext_out = unlock(dec.decrypt(ciphertext)); - dec_timer.stop(); - - if(plaintext_out != plaintext) - { // has never happened... - std::cerr << "Contents mismatched on decryption during benchmark!" << std::endl; - } - } - } - } - -void benchmark_sig_ver(PK_Verifier& ver, PK_Signer& sig, - Timer& verify_timer, Timer& sig_timer, - RandomNumberGenerator& rng, - u32bit runs, double seconds) - { - std::vector<byte> message, signature, sig_random; - - for(u32bit i = 0; i != runs; ++i) - { - if(sig_timer.seconds() < seconds || signature.size() == 0) - { - if((i % 100) == 0) - { - message.resize(48); - rng.randomize(&message[0], message.size()); - } - else - message[0]++; - - sig_timer.start(); - signature = sig.sign_message(message, rng); - sig_timer.stop(); - } - - if(verify_timer.seconds() < seconds) - { - verify_timer.start(); - const bool verified = ver.verify_message(message, signature); - verify_timer.stop(); - - if(!verified) - std::cerr << "Signature verification failure" << std::endl; - - if((i % 100) == 0) - { - sig_random = unlock(rng.random_vec(signature.size())); - - verify_timer.start(); - const bool verified_bad = ver.verify_message(message, sig_random); - verify_timer.stop(); - - if(verified_bad) - std::cerr << "Signature verification failure (bad sig OK)" << std::endl; - } - } - } - } - -/* - Between benchmark_rsa_rw + benchmark_dsa_nr: - Type of the key - Arguments to the constructor (A list of some arbitrary type?) - Type of padding -*/ - -#if defined(BOTAN_HAS_RSA) -void benchmark_rsa(const std::string& provider, - RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - - size_t keylens[] = { 1024, 2048, 4096, 6144, 0 }; - - for(size_t i = 0; keylens[i]; ++i) - { - size_t keylen = keylens[i]; - - //const std::string sig_padding = "PSSR(SHA-256)"; - //const std::string enc_padding = "OAEP(SHA-1)"; - const std::string sig_padding = "EMSA-PKCS1-v1_5(SHA-1)"; - const std::string enc_padding = "EME-PKCS1-v1_5"; - - Timer keygen_timer("keygen"); - Timer verify_timer(sig_padding + " verify"); - Timer sig_timer(sig_padding + " signature"); - Timer enc_timer(enc_padding + " encrypt"); - Timer dec_timer(enc_padding + " decrypt"); - - try - { - -#if 0 - // for profiling - PKCS8_PrivateKey* pkcs8_key = - PKCS8::load_key("rsa/" + to_string(keylen) + ".pem", rng); - RSA_PrivateKey* key_ptr = dynamic_cast<RSA_PrivateKey*>(pkcs8_key); - - RSA_PrivateKey key = *key_ptr; -#else - keygen_timer.start(); - RSA_PrivateKey key(rng, keylen); - keygen_timer.stop(); -#endif - - while(verify_timer.seconds() < seconds || - sig_timer.seconds() < seconds) - { - PK_Encryptor_EME enc(key, enc_padding, provider); - PK_Decryptor_EME dec(key, enc_padding, provider); - - benchmark_enc_dec(enc, dec, enc_timer, dec_timer, - rng, 10000, seconds); - - PK_Signer sig(key, sig_padding, IEEE_1363, provider); - PK_Verifier ver(key, sig_padding, IEEE_1363, provider); - - benchmark_sig_ver(ver, sig, verify_timer, - sig_timer, rng, 10000, seconds); - } - - const std::string rsa_keylen = "RSA-" + std::to_string(keylen); - - report.report(rsa_keylen, keygen_timer); - report.report(rsa_keylen, verify_timer); - report.report(rsa_keylen, sig_timer); - report.report(rsa_keylen, enc_timer); - report.report(rsa_keylen, dec_timer); - } - catch(Stream_IO_Error) - { - } - catch(Exception& e) - { - std::cout << e.what() << std::endl; - } - } - - } -#endif - -#if defined(BOTAN_HAS_RW) -void benchmark_rw(RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - - const u32bit keylens[] = { 1024, 2048, 4096, 6144, 0 }; - - for(size_t j = 0; keylens[j]; j++) - { - u32bit keylen = keylens[j]; - - std::string padding = "EMSA2(SHA-256)"; - - Timer keygen_timer("keygen"); - Timer verify_timer(padding + " verify"); - Timer sig_timer(padding + " signature"); - - while(verify_timer.seconds() < seconds || - sig_timer.seconds() < seconds) - { - keygen_timer.start(); - RW_PrivateKey key(rng, keylen); - keygen_timer.stop(); - - PK_Signer sig(key, padding); - PK_Verifier ver(key, padding); - - benchmark_sig_ver(ver, sig, verify_timer, sig_timer, - rng, 10000, seconds); - } - - const std::string nm = "RW-" + std::to_string(keylen); - report.report(nm, keygen_timer); - report.report(nm, verify_timer); - report.report(nm, sig_timer); - } - } -#endif - -#if defined(BOTAN_HAS_ECDSA) - -void benchmark_ecdsa(const std::string& provider, - RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - for(size_t j = 0; ec_domains[j]; j++) - { - EC_Group params(ec_domains[j]); - - const size_t pbits = params.get_curve().get_p().bits(); - - size_t hashbits = pbits; - - if(hashbits <= 192) - hashbits = 160; - if(hashbits == 521) - hashbits = 512; - - const std::string padding = "EMSA1(SHA-" + std::to_string(hashbits) + ")"; - - Timer keygen_timer("keygen"); - Timer verify_timer(padding + " verify"); - Timer sig_timer(padding + " signature"); - - while(verify_timer.seconds() < seconds || - sig_timer.seconds() < seconds) - { - keygen_timer.start(); - ECDSA_PrivateKey key(rng, params); - keygen_timer.stop(); - - PK_Signer sig(key, padding, IEEE_1363, provider); - PK_Verifier ver(key, padding, IEEE_1363, provider); - - benchmark_sig_ver(ver, sig, verify_timer, - sig_timer, rng, 1000, seconds); - } - - const std::string nm = std::string("ECDSA ") + ec_domains[j]; - - report.report(nm, keygen_timer); - report.report(nm, verify_timer); - report.report(nm, sig_timer); - } - } - -#endif - -#if defined(BOTAN_HAS_GOST_34_10_2001) - -void benchmark_gost_3410(RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - for(size_t j = 0; ec_domains[j]; j++) - { - EC_Group params(ec_domains[j]); - - const size_t pbits = params.get_curve().get_p().bits(); - - const std::string padding = "EMSA1(GOST-34.11)"; - - Timer keygen_timer("keygen"); - Timer verify_timer(padding + " verify"); - Timer sig_timer(padding + " signature"); - - while(verify_timer.seconds() < seconds || - sig_timer.seconds() < seconds) - { - keygen_timer.start(); - GOST_3410_PrivateKey key(rng, params); - keygen_timer.stop(); - - PK_Signer sig(key, padding, IEEE_1363); - PK_Verifier ver(key, padding); - - benchmark_sig_ver(ver, sig, verify_timer, - sig_timer, rng, 1000, seconds); - } - - const std::string nm = "GOST-34.10-" + std::to_string(pbits); - - report.report(nm, keygen_timer); - report.report(nm, verify_timer); - report.report(nm, sig_timer); - } - } - -#endif - -#if defined(BOTAN_HAS_ECDH) - -void benchmark_ecdh(RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - for(size_t j = 0; ec_domains[j]; j++) - { - EC_Group params(ec_domains[j]); - - size_t pbits = params.get_curve().get_p().bits(); - - Timer keygen_timer("keygen"); - Timer kex_timer("key exchange"); - - while(kex_timer.seconds() < seconds) - { - keygen_timer.start(); - ECDH_PrivateKey ecdh1(rng, params); - keygen_timer.stop(); - - keygen_timer.start(); - ECDH_PrivateKey ecdh2(rng, params); - keygen_timer.stop(); - - PK_Key_Agreement ka1(ecdh1, "KDF2(SHA-1)"); - PK_Key_Agreement ka2(ecdh2, "KDF2(SHA-1)"); - - SymmetricKey secret1, secret2; - - for(size_t i = 0; i != 1000; ++i) - { - if(kex_timer.seconds() > seconds) - break; - - kex_timer.start(); - secret1 = ka1.derive_key(32, ecdh2.public_value()); - kex_timer.stop(); - - kex_timer.start(); - secret2 = ka2.derive_key(32, ecdh1.public_value()); - kex_timer.stop(); - - if(secret1 != secret2) - std::cerr << "ECDH secrets did not match" << std::endl; - } - } - - const std::string nm = "ECDH-" + std::to_string(pbits); - report.report(nm, keygen_timer); - report.report(nm, kex_timer); - } - } - -#endif - -template<typename PRIV_KEY_TYPE> -void benchmark_dsa_nr(RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { -#if defined(BOTAN_HAS_NYBERG_RUEPPEL) || defined(BOTAN_HAS_DSA) - const char* domains[] = { "dsa/jce/1024", - "dsa/botan/2048", - "dsa/botan/3072", - nullptr }; - - std::string algo_name; - - for(size_t j = 0; domains[j]; j++) - { - size_t pbits = to_u32bit(split_on(domains[j], '/')[2]); - size_t qbits = (pbits <= 1024) ? 160 : 256; - - const std::string padding = "EMSA1(SHA-" + std::to_string(qbits) + ")"; - - Timer keygen_timer("keygen"); - Timer verify_timer(padding + " verify"); - Timer sig_timer(padding + " signature"); - - while(verify_timer.seconds() < seconds || - sig_timer.seconds() < seconds) - { - DL_Group group(domains[j]); - - keygen_timer.start(); - PRIV_KEY_TYPE key(rng, group); - algo_name = key.algo_name(); - keygen_timer.stop(); - - PK_Signer sig(key, padding, IEEE_1363); - PK_Verifier ver(key, padding); - - benchmark_sig_ver(ver, sig, verify_timer, - sig_timer, rng, 1000, seconds); - } - - const std::string nm = algo_name + "-" + std::to_string(pbits); - report.report(nm, keygen_timer); - report.report(nm, verify_timer); - report.report(nm, sig_timer); - } -#endif - } - -#if defined(BOTAN_HAS_CURVE_25519) -void benchmark_curve25519(RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - Timer keygen_timer("keygen"); - Timer kex_timer("key exchange"); - - while(kex_timer.seconds() < seconds) - { - keygen_timer.start(); - Curve25519_PrivateKey key1(rng); - keygen_timer.stop(); - - keygen_timer.start(); - Curve25519_PrivateKey key2(rng); - keygen_timer.stop(); - - PK_Key_Agreement ka1(key1, "KDF2(SHA-256)"); - PK_Key_Agreement ka2(key2, "KDF2(SHA-256)"); - - SymmetricKey secret1, secret2; - - for(size_t i = 0; i != 1000; ++i) - { - if(kex_timer.seconds() > seconds) - break; - - kex_timer.start(); - secret1 = ka1.derive_key(32, key2.public_value()); - kex_timer.stop(); - - kex_timer.start(); - secret2 = ka2.derive_key(32, key1.public_value()); - kex_timer.stop(); - - if(secret1 != secret2) - std::cerr << "Curve25519 secrets did not match" << std::endl; - } - } - - const std::string nm = "Curve25519"; - report.report(nm, keygen_timer); - report.report(nm, kex_timer); - } -#endif - -#ifdef BOTAN_HAS_DIFFIE_HELLMAN -void benchmark_dh(RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - const char* domains[] = { "modp/ietf/1024", - "modp/ietf/2048", - "modp/ietf/3072", - "modp/ietf/4096", - "modp/ietf/6144", - "modp/ietf/8192", - nullptr }; - - for(size_t j = 0; domains[j]; j++) - { - Timer keygen_timer("keygen"); - Timer kex_timer("key exchange"); - - while(kex_timer.seconds() < seconds) - { - DL_Group group(domains[j]); - - keygen_timer.start(); - DH_PrivateKey dh1(rng, group); - keygen_timer.stop(); - - keygen_timer.start(); - DH_PrivateKey dh2(rng, group); - keygen_timer.stop(); - - PK_Key_Agreement ka1(dh1, "KDF2(SHA-1)"); - PK_Key_Agreement ka2(dh2, "KDF2(SHA-1)"); - - SymmetricKey secret1, secret2; - - for(size_t i = 0; i != 1000; ++i) - { - if(kex_timer.seconds() > seconds) - break; - - kex_timer.start(); - secret1 = ka1.derive_key(32, dh2.public_value()); - kex_timer.stop(); - - kex_timer.start(); - secret2 = ka2.derive_key(32, dh1.public_value()); - kex_timer.stop(); - - if(secret1 != secret2) - std::cerr << "DH secrets did not match" << std::endl; - } - } - - const std::string nm = "DH-" + split_on(domains[j], '/')[2]; - report.report(nm, keygen_timer); - report.report(nm, kex_timer); - } - } -#endif - -#if defined(BOTAN_HAS_DIFFIE_HELLMAN) && defined(BOTAN_HAS_DLIES) && defined(BOTAN_HAS_KDF2) -void benchmark_dlies(RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - const char* domains[] = { "modp/ietf/768", - "modp/ietf/1024", - "modp/ietf/2048", - "modp/ietf/3072", - "modp/ietf/4096", - "modp/ietf/6144", - "modp/ietf/8192", - nullptr }; - - for(size_t j = 0; domains[j]; j++) - { - Timer keygen_timer("keygen"); - Timer kex_timer("key exchange"); - - Timer enc_timer("encrypt"); - Timer dec_timer("decrypt"); - - while(enc_timer.seconds() < seconds || dec_timer.seconds() < seconds) - { - DL_Group group(domains[j]); - - keygen_timer.start(); - DH_PrivateKey dh1_priv(rng, group); - keygen_timer.stop(); - - keygen_timer.start(); - DH_PrivateKey dh2_priv(rng, group); - keygen_timer.stop(); - - DH_PublicKey dh2_pub(dh2_priv); - - DLIES_Encryptor dlies_enc(dh1_priv, - new KDF2(new SHA_160), - new HMAC(new SHA_160)); - - dlies_enc.set_other_key(dh2_pub.public_value()); - - DLIES_Decryptor dlies_dec(dh2_priv, - new KDF2(new SHA_160), - new HMAC(new SHA_160)); - - benchmark_enc_dec(dlies_enc, dlies_dec, - enc_timer, dec_timer, rng, - 1000, seconds); - } - - const std::string nm = "DLIES-" + split_on(domains[j], '/')[2]; - report.report(nm, keygen_timer); - report.report(nm, enc_timer); - report.report(nm, dec_timer); - } - } -#endif - -#ifdef BOTAN_HAS_ELGAMAL -void benchmark_elg(RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - const char* domains[] = { "modp/ietf/768", - "modp/ietf/1024", - "modp/ietf/2048", - "modp/ietf/3072", - "modp/ietf/4096", - "modp/ietf/6144", - "modp/ietf/8192", - nullptr }; - - const std::string algo_name = "ElGamal"; - - for(size_t j = 0; domains[j]; j++) - { - size_t pbits = to_u32bit(split_on(domains[j], '/')[2]); - - const std::string padding = "EME1(SHA-1)"; - - Timer keygen_timer("keygen"); - Timer enc_timer(padding + " encrypt"); - Timer dec_timer(padding + " decrypt"); - - while(enc_timer.seconds() < seconds || - dec_timer.seconds() < seconds) - { - DL_Group group(domains[j]); - - keygen_timer.start(); - ElGamal_PrivateKey key(rng, group); - keygen_timer.stop(); - - PK_Decryptor_EME dec(key, padding); - PK_Encryptor_EME enc(key, padding); - - benchmark_enc_dec(enc, dec, enc_timer, dec_timer, - rng, 1000, seconds); - } - - const std::string nm = algo_name + "-" + std::to_string(pbits); - report.report(nm, keygen_timer); - report.report(nm, enc_timer); - report.report(nm, dec_timer); - } - } -#endif - -#if defined(BOTAN_HAS_MCELIECE) -void benchmark_mce(RandomNumberGenerator& rng, - double seconds, - Benchmark_Report& report) - { - const std::vector<std::pair<size_t, size_t>> params = { - { 1632, 33 }, - { 2480, 45 }, - { 2960, 57 }, - { 3408, 67 }, - { 4264, 95 }, - { 6624, 115 } - }; - - const std::string algo_name = "McEliece"; - - for(auto& param : params) - { - Timer keygen_timer("keygen"); - Timer enc_timer("encrypt"); - Timer dec_timer("decrypt"); - - keygen_timer.start(); - McEliece_PrivateKey priv_key(rng, param.first, param.second); - McEliece_PublicKey pub_key(priv_key.x509_subject_public_key()); - keygen_timer.stop(); - - McEliece_KEM_Encryptor enc_kem(pub_key); - McEliece_KEM_Decryptor dec_kem(priv_key); - - while(enc_timer.seconds() < seconds || - dec_timer.seconds() < seconds) - { - enc_timer.start(); - auto enc_pair = enc_kem.encrypt(rng); - enc_timer.stop(); - - dec_timer.start(); - auto dec_key = dec_kem.decrypt_vec(enc_pair.first); - dec_timer.stop(); - - BOTAN_ASSERT_EQUAL(enc_pair.second, dec_key, "KEM result matches"); - } - - const std::string nm = algo_name + "-" + - std::to_string(param.first) + "," + - std::to_string(param.second); - - std::ostringstream keysize_report; - keysize_report << "(work factor " << pub_key.estimated_strength() << ", " - << "pub bytes " << pub_key.x509_subject_public_key().size() << " " - << "priv bytes " << priv_key.pkcs8_private_key().size() << ")"; - - report.report(nm + " " + keysize_report.str(), keygen_timer); - report.report(nm, enc_timer); - report.report(nm, dec_timer); - } - } -#endif - -} - -void benchmark_public_key(RandomNumberGenerator& rng, - const std::string& algo, - const std::string& provider, - double seconds) - { - /* - There is some strangeness going on here. It looks like algorithms - at the end take some kind of penalty. For example, running the RW tests - first got a result of: - RW-1024: 148.14 ms / private operation - but running them last output: - RW-1024: 363.54 ms / private operation - - I think it's from memory fragmentation in the allocators, but I'm - not really sure. Need to investigate. - - Until then, I've basically ordered the tests in order of most important - algorithms (RSA, DSA) to least important (NR, RW). - - This strange behaviour does not seem to occur with DH (?) - - To get more accurate runs, use --bench-algo (RSA|DSA|DH|ELG|NR); in this - case the distortion is less than 5%, which is good enough. - - We do random keys with the DL schemes, since it's so easy and fast to - generate keys for them. For RSA and RW, we load the keys from a file. The - RSA keys are stored in a PKCS #8 structure, while RW is stored in a more - ad-hoc format (the RW algorithm has no assigned OID that I know of, so - there is no way to encode a RW key into a PKCS #8 structure). - */ - - Benchmark_Report report; - -#if defined(BOTAN_HAS_RSA) - if(algo == "All" || algo == "RSA") - benchmark_rsa(provider, rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_DSA) - if(algo == "All" || algo == "DSA") - benchmark_dsa_nr<DSA_PrivateKey>(rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_ECDSA) - if(algo == "All" || algo == "ECDSA") - benchmark_ecdsa(provider, rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_ECDH) - if(algo == "All" || algo == "ECDH") - benchmark_ecdh(rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_GOST_34_10_2001) - if(algo == "All" || algo == "GOST-34.10") - benchmark_gost_3410(rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_CURVE_25519) - if(algo == "All" || algo == "Curve25519") - benchmark_curve25519(rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_DIFFIE_HELLMAN) - if(algo == "All" || algo == "DH") - benchmark_dh(rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_DIFFIE_HELLMAN) && defined(BOTAN_HAS_DLIES) && defined(BOTAN_HAS_KDF2) - if(algo == "All" || algo == "DLIES") - benchmark_dlies(rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_ELGAMAL) - if(algo == "All" || algo == "ELG" || algo == "ElGamal") - benchmark_elg(rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_NYBERG_RUEPPEL) - if(algo == "All" || algo == "NR") - benchmark_dsa_nr<NR_PrivateKey>(rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_RW) - if(algo == "All" || algo == "RW") - benchmark_rw(rng, seconds, report); -#endif - -#if defined(BOTAN_HAS_MCELIECE) - if(algo == "All" || algo == "McEliece") - benchmark_mce(rng, seconds, report); -#endif - } -#endif // BOTAN_HAS_PUBLIC_KEY_CRYPTO diff --git a/src/cli/implementation/speed_transform.cpp b/src/cli/implementation/speed_transform.cpp deleted file mode 100644 index 2db5cdd70..000000000 --- a/src/cli/implementation/speed_transform.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* -* (C) 2009 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "speed.h" - -#include <iostream> -#include <iomanip> - -#include <botan/cipher_mode.h> -#include <botan/transform.h> - -using namespace Botan; - -namespace { -void benchmark_transform(std::unique_ptr<Transform> tf, - RandomNumberGenerator& rng, - const std::chrono::milliseconds runtime) - { - for(size_t buf_size : { 16, 64, 256, 1024, 8192 }) - { - secure_vector<byte> buffer(buf_size); - - std::chrono::nanoseconds time_used(0); - - tf->start(rng.random_vec(tf->default_nonce_length())); - - auto start = std::chrono::high_resolution_clock::now(); - - secure_vector<byte> buf(buf_size); - size_t reps = 0; - while(time_used < runtime) - { - tf->update(buf); - buf.resize(buf_size); - ++reps; - time_used = std::chrono::high_resolution_clock::now() - start; - } - - const u64bit nsec_used = std::chrono::duration_cast<std::chrono::nanoseconds>(time_used).count(); - - const double seconds_used = static_cast<double>(nsec_used) / 1000000000; - - const double Mbps = ((reps / seconds_used) * buf_size) / 1024 / 1024; - - std::cout << tf->name() << " " << std::setprecision(4) << Mbps - << " MiB / sec with " << buf_size << " byte blocks" << std::endl; - } - } -} - -bool benchmark_transform(RandomNumberGenerator& rng, const std::string& algo_name, - const std::chrono::milliseconds runtime) - { - std::unique_ptr<Transform> tf; - tf.reset(get_cipher_mode(algo_name, ENCRYPTION)); - if(!tf) - return false; - - if(Keyed_Transform* keyed = dynamic_cast<Keyed_Transform*>(tf.get())) - keyed->set_key(rng.random_vec(keyed->key_spec().maximum_keylength())); - - benchmark_transform(std::move(tf), rng, runtime); - return true; - } diff --git a/src/cli/implementation/timer.cpp b/src/cli/implementation/timer.cpp deleted file mode 100644 index 14e55316b..000000000 --- a/src/cli/implementation/timer.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* -* (C) 2009 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "timer.h" -#include <chrono> -#include <iomanip> - -void Timer::start() - { - stop(); - m_timer_start = get_clock(); - } - -void Timer::stop() - { - if(m_timer_start) - { - const u64bit now = get_clock(); - - if(now > m_timer_start) - m_time_used += (now - m_timer_start); - - m_timer_start = 0; - ++m_event_count; - } - } - -u64bit Timer::get_clock() - { - auto now = std::chrono::high_resolution_clock::now().time_since_epoch(); - return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count(); - } - -std::ostream& operator<<(std::ostream& out, Timer& timer) - { - //out << timer.value() << " "; - - double events_per_second_fl = - static_cast<double>(timer.events() / timer.seconds()); - - u64bit events_per_second = static_cast<u64bit>(events_per_second_fl); - - out << events_per_second << " " << timer.get_name() << " per second; "; - - std::string op_or_ops = (timer.events() == 1) ? "op" : "ops"; - - const std::ios::fmtflags flags = out.flags(); - - out << std::setprecision(2) << std::fixed - << timer.ms_per_event() << " ms/op" - << " (" << timer.events() << " " << op_or_ops << " in " - << timer.milliseconds() << " ms)"; - - out.flags(flags); - - return out; - } diff --git a/src/cli/implementation/timer.h b/src/cli/implementation/timer.h deleted file mode 100644 index ac5bd5cef..000000000 --- a/src/cli/implementation/timer.h +++ /dev/null @@ -1,55 +0,0 @@ -/* -* (C) 2014 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_BENCHMARK_TIMER_H__ -#define BOTAN_BENCHMARK_TIMER_H__ - -#include <botan/types.h> -#include <ostream> -#include <string> - -using Botan::u64bit; -using Botan::u32bit; - -class Timer - { - public: - static u64bit get_clock(); - - Timer(const std::string& name, u32bit event_mult = 1) : - m_name(name), m_event_mult(event_mult) {} - - void start(); - void stop(); - - u64bit value() { stop(); return m_time_used; } - double seconds() { return milliseconds() / 1000.0; } - double milliseconds() { return value() / 1000000.0; } - - double ms_per_event() { return milliseconds() / events(); } - double seconds_per_event() { return seconds() / events(); } - - u64bit events() const { return m_event_count * m_event_mult; } - std::string get_name() const { return m_name; } - private: - std::string m_name; - u64bit m_time_used = 0, m_timer_start = 0; - u64bit m_event_count = 0, m_event_mult = 0; - }; - -inline bool operator<(const Timer& x, const Timer& y) - { - return (x.get_name() < y.get_name()); - } - -inline bool operator==(const Timer& x, const Timer& y) - { - return (x.get_name() == y.get_name()); - } - -std::ostream& operator<<(std::ostream&, Timer&); - -#endif diff --git a/src/cli/is_prime.cpp b/src/cli/is_prime.cpp deleted file mode 100644 index 71fec730b..000000000 --- a/src/cli/is_prime.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_NUMBERTHEORY) - -#include <botan/numthry.h> - -namespace { - -int is_prime(const std::vector<std::string> &args) - { - if(args.size() != 2 && args.size() != 3) - { - std::cerr << "Usage: " << args[0] << " n <prob>" << std::endl; - return 2; - } - - BigInt n(args[1]); - - size_t prob = 56; - - if(args.size() == 3) - prob = to_u32bit(args[2]); - - AutoSeeded_RNG rng; - - const bool prime = is_prime(n, rng, prob); - - if(prime) - { - std::cout << n << " is prime" << std::endl; - return 0; - } - else - { - std::cout << n << " is not prime" << std::endl; - return 1; - } - } - -REGISTER_APP(is_prime); - -} - -#endif // BOTAN_HAS_NUMBERTHEORY diff --git a/src/cli/keygen.cpp b/src/cli/keygen.cpp deleted file mode 100644 index 168b27a4a..000000000 --- a/src/cli/keygen.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) && defined(BOTAN_HAS_X509_CERTIFICATES) - -#include <iostream> -#include <fstream> -#include <string> -#include <cstdlib> -#include <memory> -#include <botan/pk_keys.h> -#include <botan/pkcs8.h> - -#if defined(BOTAN_HAS_RSA) -#include <botan/rsa.h> -#endif - -#if defined(BOTAN_HAS_DSA) -#include <botan/dsa.h> -#endif - -#if defined(BOTAN_HAS_ECDSA) -#include <botan/ecdsa.h> -#endif - -#if defined(BOTAN_HAS_CURVE_25519) -#include <botan/curve25519.h> -#endif - -using namespace Botan; - -namespace { - -std::string dsa_group_for(size_t bits) - { - if(bits == 1024) - return "dsa/jce/1024"; - if(bits == 2048) - return "dsa/botan/2048"; - if(bits == 3072) - return "dsa/botan/3072"; - throw std::runtime_error("No registered DSA group for " + std::to_string(bits) + " bits"); - } - -Private_Key* gen_key(RandomNumberGenerator& rng, const std::string& algo, size_t bits) - { -#if defined(BOTAN_HAS_RSA) - if(algo == "rsa") - return new RSA_PrivateKey(rng, bits); -#endif - -#if defined(BOTAN_HAS_DSA) - if(algo == "dsa") - { - DL_Group grp(dsa_group_for(bits)); - return new DSA_PrivateKey(rng, grp); - } -#endif - -#if defined(BOTAN_HAS_ECDSA) - if(algo == "ecdsa") - { - EC_Group grp("secp" + std::to_string(bits) + "r1"); - return new ECDSA_PrivateKey(rng, grp); - } -#endif - -#if defined(BOTAN_HAS_CURVE_25519) - if(algo == "curve25519") - return new Curve25519_PrivateKey(rng); -#endif - - throw std::runtime_error("Unknown algorithm " + algo); - } - - -int keygen(const std::vector<std::string> &args) - { - OptionParser opts("algo=|bits=|passphrase=|pbe="); - opts.parse(args); - - const std::string algo = opts.value_or_else("algo", "rsa"); - const size_t bits = opts.int_value_or_else("bits", 2048); - const std::string pass = opts.value_or_else("passphrase", ""); - const std::string pbe = opts.value_or_else("pbe", ""); - - try - { - std::ofstream pub("public.pem"); - std::ofstream priv("private.pem"); - - if(!priv || !pub) - { - std::cout << "Couldn't write output files" << std::endl; - return 1; - } - - AutoSeeded_RNG rng; - - std::unique_ptr<Private_Key> key(gen_key(rng, algo, bits)); - - pub << X509::PEM_encode(*key); - - if(pass == "") - priv << PKCS8::PEM_encode(*key); - else - priv << PKCS8::PEM_encode(*key, rng, pass, std::chrono::milliseconds(300), pbe); - - std::cout << "Wrote " << bits << " bit " << algo << " key to public.pem / private.pem" << std::endl; - } - catch(std::exception& e) - { - std::cout << "Exception caught: " << e.what() << std::endl; - } - - return 0; - } - -REGISTER_APP(keygen); - -} - -#endif // BOTAN_HAS_PUBLIC_KEY_CRYPTO && BOTAN_HAS_X509_CERTIFICATES diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 8d229ce0e..f054e6005 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -1,187 +1,59 @@ /* -* (C) 2009,2014 Jack Lloyd +* (C) 2009,2014,2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ -#include <vector> -#include <string> - -#include <iostream> -#include <cstdlib> -#include <exception> -#include <limits> -#include <memory> +#include "cli.h" #include <botan/version.h> -#include <botan/cpuid.h> - -#if defined(BOTAN_HAS_HTTP_UTIL) -#include <botan/http_util.h> -#endif - -using namespace Botan; - -#include "apps.h" +#include <botan/internal/stl_util.h> +#include <iterator> +#include <sstream> namespace { -int help(const std::vector<std::string> &args) +std::string main_help() { - std::cout << "Usage: " << args[0] << " [subcommand] [subcommand-options]" << std::endl; + const std::set<std::string> avail_commands = + Botan::map_keys_as_set(Botan_CLI::Command::global_registry()); - std::set<std::string> apps = AppRegistrations::instance().all_appnames(); + std::ostringstream oss; - std::cout << "Available commands:" << std::endl; + oss << "Usage: botan <cmd> <cmd-options>\n"; + oss << "Available commands: "; + std::copy(avail_commands.begin(), + avail_commands.end(), + std::ostream_iterator<std::string>(oss, " ")); + oss << "\n"; - size_t idx = 1; - for(auto&& app: apps) - { - std::cout << app; - - if(idx % 3 == 0) - std::cout << std::endl; - else - std::cout << std::string(18-app.size(), ' '); - - ++idx; - } - std::cout << std::endl; - - return 1; + return oss.str(); } -int config(const std::vector<std::string> &args) - { - if(args.size() != 2) - { - std::cout << "Usage: " << args[0] << " <what>\n" - << " prefix: Print install prefix\n" - << " cflags: Print include params\n" - << " ldflags: Print linker params\n" - << " libs: Print libraries" << std::endl; - return 1; - } - - const std::string arg = args[1]; - - if(arg == "prefix") - std::cout << BOTAN_INSTALL_PREFIX << std::endl; - - else if(arg == "cflags") - std::cout << "-I" << BOTAN_INSTALL_PREFIX << "/" << BOTAN_INSTALL_HEADER_DIR << std::endl; - - else if(arg == "ldflags") - std::cout << "-L" << BOTAN_INSTALL_PREFIX << "/" << BOTAN_INSTALL_LIB_DIR << std::endl; - - else if(arg == "libs") - std::cout << "-lbotan-" << version_major() << "." << version_minor() - << " " << BOTAN_LIB_LINK << std::endl; - - else - { - std::cerr << "Unknown option " << arg << " to botan config" << std::endl; - return 1; - } - - return 0; - } -REGISTER_APP(config); - -int version(const std::vector<std::string> &args) - { - if(BOTAN_VERSION_MAJOR != version_major() || - BOTAN_VERSION_MINOR != version_minor() || - BOTAN_VERSION_PATCH != version_patch()) - { - std::cerr << "Warning: linked version (" - << version_major() << '.' - << version_minor() << '.' - << version_patch() - << ") does not match version built against (" - << BOTAN_VERSION_MAJOR << '.' - << BOTAN_VERSION_MINOR << '.' - << BOTAN_VERSION_PATCH << ")" << std::endl; - } - - if(args.size() == 1) - { - std::cout << Botan::version_major() << "." - << Botan::version_minor() << "." - << Botan::version_patch() << std::endl; - } - else if(args.size() == 2 && args[1] == "--full") - { - std::cout << Botan::version_string() << std::endl; - } - else - { - std::cout << "Usage: " << args[0] << " version [--full]" << std::endl; - return 1; - } - - return 0; - } -REGISTER_APP(version); - -int cpuid(const std::vector<std::string> &args) - { - BOTAN_UNUSED(args); - CPUID::print(std::cout); - return 0; - } -REGISTER_APP(cpuid); - -#if defined(BOTAN_HAS_HTTP_UTIL) -int http_get(const std::vector<std::string> &args) - { - if(args.size() != 2) - { - std::cout << "Usage " << args[0] << " <url>" << std::endl; - return 1; - } - - auto resp = HTTP::GET_sync(args[1]); - std::cout << resp << std::endl; - return 0; - } -REGISTER_APP(http_get); - -#endif - } int main(int argc, char* argv[]) { - const std::vector<std::string> args(argv, argv + argc); + std::cerr << Botan::runtime_version_check(BOTAN_VERSION_MAJOR, + BOTAN_VERSION_MINOR, + BOTAN_VERSION_PATCH); - try - { - if(args.size() < 2) - return help(args); - - const std::string cmd = args[1]; - - if(cmd == "help" || cmd == "-h") - return help(args); - - AppRegistrations& apps = AppRegistrations::instance(); - if(apps.has(cmd)) - return apps.run(cmd, std::vector<std::string>(args.begin()+1, args.end())); + const std::string cmd_name = (argc <= 1) ? "help" : argv[1]; - std::cerr << "Unknown command " << cmd << std::endl; - return help(args); - } - catch(std::exception& e) + if(cmd_name == "help" || cmd_name == "--help") { - std::cerr << e.what() << std::endl; + std::cout << main_help(); return 1; } - catch(...) + + Botan_CLI::Command* cmd = Botan_CLI::Command::get_cmd(cmd_name); + + if(!cmd) { - std::cerr << "Unknown exception caught" << std::endl; + std::cout << "Unknown command " << cmd_name << " (try --help)\n"; return 1; } - return 0; + std::vector<std::string> args(argv + 2, argv + argc); + return cmd->run(args); } diff --git a/src/cli/math.cpp b/src/cli/math.cpp new file mode 100644 index 000000000..c6f40e785 --- /dev/null +++ b/src/cli/math.cpp @@ -0,0 +1,188 @@ +/* +* (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/reducer.h> +#include <botan/numthry.h> +#include <botan/auto_rng.h> +#include <iterator> + +namespace Botan_CLI { + +class Gen_Prime : public Command + { + public: + Gen_Prime() : Command("gen_prime --count=1 bits") {} + + void go() override + { + Botan::AutoSeeded_RNG rng; + + 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); + +class Is_Prime : public Command + { + public: + Is_Prime() : Command("is_prime --prob=56 n") {} + + void go() override + { + Botan::BigInt n(get_arg("n")); + const size_t prob = get_arg_sz("prob"); + Botan::AutoSeeded_RNG rng; + const bool prime = Botan::is_prime(n, rng, prob); + + output() << n << " is " << (prime ? "probably prime" : "composite") << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND(Is_Prime); + +/* +* Factor integers using a combination of trial division by small +* primes, and Pollard's Rho algorithm +*/ +class Factor : public Command + { + public: + Factor() : Command("factor n") {} + + void go() override + { + Botan::BigInt n(get_arg("n")); + + Botan::AutoSeeded_RNG rng; + + 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. We + * use (x^2+x) mod n instead of (x*2-1) mod n as the random function, + * it _seems_ to lead to faster factorization for the values I tried. + */ + Botan::BigInt rho(const Botan::BigInt& n, Botan::RandomNumberGenerator& rng) + { + Botan::BigInt x = Botan::BigInt::random_integer(rng, 0, n-1); + Botan::BigInt y = x; + Botan::BigInt d = 0; + + Botan::Modular_Reducer mod_n(n); + + size_t i = 1, k = 2; + while(true) + { + i++; + + if(i == 0) // overflow, bail out + break; + + x = mod_n.multiply((x + 1), x); + + d = Botan::gcd(y - x, n); + if(d != 1 && d != n) + return d; + + if(i == k) + { + y = x; + k = 2*k; + } + } + 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); + +} + +#endif diff --git a/src/cli/mce.cpp b/src/cli/mce.cpp deleted file mode 100644 index 226f21e9c..000000000 --- a/src/cli/mce.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* -* (C) 2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_MCELIECE) - -#include <botan/mceliece.h> -#include <botan/mceies.h> -#include <botan/pkcs8.h> -#include <fstream> - -namespace { - -int mce(const std::vector<std::string> &args) - { - if(args.size() < 4) - { - std::cout << "Usage: " << args[0] << " [keygen n t pass|keybits n t|encrypt file key|decrypt file key pass]" - << std::endl; - return 1; - } - - const std::string cmd = args[1]; - - AutoSeeded_RNG rng; - - if(cmd == "keygen") - { - const u32bit n = to_u32bit(args[2]); - const u32bit t = to_u32bit(args[3]); - const std::string pass = args[4]; - - McEliece_PrivateKey pk(rng, n, t); - - bool ok = pk.check_key(rng, true); - - if(!ok) - { - std::cout << "Keygen failed self-test\n"; - return 2; - } - - /* - secure_vector<byte> priv = PKCS8::BER_encode(pk); - std::vector<byte> pub = X509::BER_encode(pk); - std::cout << priv.size()/1024.0 << " " << pub.size()/1024.0 << "\n"; - */ - - std::ofstream pub_file("mce.pub"); - pub_file << X509::PEM_encode(pk); - pub_file.close(); - - std::ofstream priv_file("mce.priv"); - priv_file << PKCS8::PEM_encode(pk, rng, pass); - priv_file.close(); - } - else if(cmd == "keybits") - { - const u32bit n = to_u32bit(args[2]); - const u32bit t = to_u32bit(args[3]); - std::cout << "McEliece key with params (" << n << "," << t << ") has " - << mceliece_work_factor(n, t) << " bit security\n"; - } - else if(cmd == "encrypt") - { - std::unique_ptr<Public_Key> p8(X509::load_key(args[3])); - const McEliece_PublicKey* key = dynamic_cast<McEliece_PublicKey*>(p8.get()); - - if(!key) - { - throw std::runtime_error("Loading McEliece public key failed"); - } - - const std::string input_path = args[2]; - std::ifstream in(input_path, std::ios::binary); - std::string pt((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()); - - secure_vector<byte> ct = mceies_encrypt(*key, - reinterpret_cast<const byte*>(pt.data()), - pt.size(), - nullptr, 0, rng, "AES-128/GCM"); - - std::cout << pt.size() << " -> " << ct.size() << std::endl; - - std::ofstream out(std::string(input_path) + ".ct", std::ios::binary); - out.write(reinterpret_cast<const char*>(ct.data()), ct.size()); - out.close(); - } - else if(cmd == "decrypt") - { - const std::string key_file = args[3]; - const std::string pass = args[4]; - std::unique_ptr<Private_Key> p8(PKCS8::load_key(key_file, rng, pass)); - const McEliece_PrivateKey* key = dynamic_cast<McEliece_PrivateKey*>(p8.get()); - - if(!key) - { - throw std::runtime_error("Loading McEliece private key failed"); - } - - std::ifstream in(args[2], std::ios::binary); - std::string ct((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>()); - - secure_vector<byte> pt = mceies_decrypt(*key, - reinterpret_cast<const byte*>(ct.data()), - ct.size(), - nullptr, 0, "AES-128/GCM"); - - std::ofstream out("mce.plaintext", std::ios::binary); - out.write(reinterpret_cast<const char*>(pt.data()), pt.size()); - out.close(); - } - return 0; - } - -} - -REGISTER_APP(mce); - -#endif diff --git a/src/cli/ocsp.cpp b/src/cli/ocsp.cpp deleted file mode 100644 index e5b42b076..000000000 --- a/src/cli/ocsp.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_OCSP) - -#include <botan/x509cert.h> -#include <botan/certstor.h> -#include <botan/x509path.h> -#include <botan/ocsp.h> - -using namespace Botan; - -namespace { - -int ocsp_check(const std::vector<std::string> &args) - { - if(args.size() != 2) - { - std::cout << "Usage: " << args[0] << " subject.pem issuer.pem" << std::endl; - return 2; - } - - X509_Certificate subject(args[1]); - X509_Certificate issuer(args[2]); - - Certificate_Store_In_Memory cas; - cas.add_certificate(issuer); - OCSP::Response resp = OCSP::online_check(issuer, subject, &cas); - - auto status = resp.status_for(issuer, subject); - - if(status == Certificate_Status_Code::VERIFIED) - { - std::cout << "OCSP check OK" << std::endl; - return 0; - } - else - { - std::cout << "OCSP check failed " << Path_Validation_Result::status_string(status) << std::endl; - return 1; - } - } - -REGISTER_APP(ocsp_check); - -} - -#endif diff --git a/src/cli/pkcs10.cpp b/src/cli/pkcs10.cpp deleted file mode 100644 index 710020666..000000000 --- a/src/cli/pkcs10.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_HAS_RSA) - -#include <botan/pkcs8.h> -#include <botan/x509self.h> -#include <botan/rsa.h> -#include <fstream> -#include <memory> - -using namespace Botan; - -namespace { - -int pkcs10(const std::vector<std::string> &args) - { - if(args.size() != 6) - { - std::cout << "Usage: " << args[0] << " " - << "passphrase name country_code organization email" << std::endl; - return 1; - } - - try - { - AutoSeeded_RNG rng; - - RSA_PrivateKey priv_key(rng, 1024); - - std::ofstream key_file("private.pem"); - key_file << PKCS8::PEM_encode(priv_key, rng, args[1]); - - X509_Cert_Options opts; - - opts.common_name = args[2]; - opts.country = args[3]; - opts.organization = args[4]; - opts.email = args[5]; - - PKCS10_Request req = X509::create_cert_req(opts, priv_key, - "SHA-256", rng); - - std::ofstream req_file("req.pem"); - req_file << req.PEM_encode(); - } - catch(std::exception& e) - { - std::cout << e.what() << std::endl; - return 1; - } - return 0; - } - -REGISTER_APP(pkcs10); - -} - -#endif // BOTAN_HAS_X509_CERTIFICATES && BOTAN_HAS_RSA diff --git a/src/cli/pkcs8.cpp b/src/cli/pkcs8.cpp deleted file mode 100644 index 7bc1c2561..000000000 --- a/src/cli/pkcs8.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* -* (C) 2015 René Korthaus -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" -#include <iostream> -#include <fstream> -#include <string> -#include <memory> -#include <chrono> - -#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) -#include <botan/pk_keys.h> -#include <botan/pkcs8.h> -#include <botan/x509_key.h> - -using namespace Botan; - -namespace { - -int pkcs8(const std::vector<std::string> &args) - { - OptionParser opts("in=|out=|passin=|passout=|pbe=|pubout"); - opts.parse(args); - - const std::string passin = opts.value_or_else("passin", ""); - const std::string passout = opts.value_or_else("passout", ""); - const std::string pbe = opts.value_or_else("pbe", ""); - - if(args.size() < 3) - { - opts.help(std::cout, "pkcs8"); - return 1; - } - - try - { - std::ofstream out_key(opts.value("out")); - - if (!out_key) - { - std::cout << "Couldn't write key" << std::endl; - return 1; - } - - AutoSeeded_RNG rng; - std::unique_ptr<Private_Key> key(PKCS8::load_key(opts.value("in"), rng, passin)); - - if(opts.is_set("pubout")) - { - out_key << X509::PEM_encode(*key); - } - else - { - if(passout.empty()) - out_key << PKCS8::PEM_encode(*key); - else - out_key << PKCS8::PEM_encode(*key, rng, passout, std::chrono::milliseconds(300), pbe); - } - } - catch(std::exception& e) - { - std::cout << "Exception caught: " << e.what() << std::endl; - return 2; - } - - return 0; - } - -REGISTER_APP(pkcs8); - -} - -#endif diff --git a/src/cli/prime.cpp b/src/cli/prime.cpp deleted file mode 100644 index c7c9d1ffe..000000000 --- a/src/cli/prime.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* -* (C) 2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_NUMBERTHEORY) - -#include <botan/numthry.h> - -#include <algorithm> -#include <iostream> - -namespace { - -int prime(const std::vector<std::string> &args) - { - if(args.size() < 2 || args.size() > 3) - { - std::cout << "Usage: " << args[0] << " bits [count]" << std::endl; - return 1; - } - - AutoSeeded_RNG rng; - const size_t bits = to_u32bit(args[1]); - const size_t cnt = args.size() >= 3 ? to_u32bit(args[2]) : 1; - - for(size_t i = 0; i != cnt; ++i) - { - const BigInt p = random_prime(rng, bits); - std::cout << p << std::endl; - - if(p.bits() != bits) - { - std::cout << "Result not exactly requested bit size, got " << p.bits() << std::endl; - } - } - - return 0; - } - -} - -REGISTER_APP(prime); - -#endif diff --git a/src/cli/pubkey.cpp b/src/cli/pubkey.cpp new file mode 100644 index 000000000..35d50592f --- /dev/null +++ b/src/cli/pubkey.cpp @@ -0,0 +1,278 @@ +/* +* (C) 2010,2014,2015 Jack Lloyd +* (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/auto_rng.h> +#include <botan/base64.h> + +#include <botan/pk_keys.h> +#include <botan/pkcs8.h> +#include <botan/pubkey.h> + +#if defined(BOTAN_HAS_DL_GROUP) + #include <botan/dl_group.h> +#endif + +#if defined(BOTAN_HAS_RSA) + #include <botan/rsa.h> +#endif + +#if defined(BOTAN_HAS_ECDSA) + #include <botan/ecdsa.h> +#endif + +#if defined(BOTAN_HAS_CURVE_25519) + #include <botan/curve25519.h> +#endif + +#if defined(BOTAN_HAS_MCELIECE) + #include <botan/mceliece.h> +#endif + +namespace Botan_CLI { + +class PK_Keygen : public Command + { + public: + PK_Keygen() : Command("keygen --algo=RSA --params= --passphrase= --pbe= --pbe-millis=300") {} + + std::unique_ptr<Botan::Private_Key> do_keygen(const std::string& algo, + const std::string& params, + Botan::RandomNumberGenerator& rng) + { + typedef std::function<std::unique_ptr<Botan::Private_Key> (std::string)> gen_fn; + std::map<std::string, gen_fn> generators; + +#if defined(BOTAN_HAS_RSA) + generators["RSA"] = [&rng](std::string param) -> std::unique_ptr<Botan::Private_Key> { + if(param.empty()) + param = "2048"; + return std::unique_ptr<Botan::Private_Key>( + new Botan::RSA_PrivateKey(rng, Botan::to_u32bit(param))); + }; +#endif + +#if defined(BOTAN_HAS_ECDSA) + generators["ECDSA"] = [&rng](std::string param) { + if(param.empty()) + param = "secp256r1"; + Botan::EC_Group grp(param); + return std::unique_ptr<Botan::Private_Key>( + new Botan::ECDSA_PrivateKey(rng, grp)); + }; +#endif + +#if defined(BOTAN_HAS_CURVE_25519) + generators["Curve25519"] = [&rng](std::string /*ignored*/) { + return std::unique_ptr<Botan::Private_Key>( + new Botan::Curve25519_PrivateKey(rng)); + }; +#endif + +#if defined(BOTAN_HAS_MCELIECE) + generators["McEliece"] = [&rng](std::string param) { + if(param.empty()) + param = "2280,45"; + std::vector<std::string> param_parts = Botan::split_on(param, ','); + if(param_parts.size() != 2) + throw CLI_Usage_Error("Bad McEliece parameters " + param); + return std::unique_ptr<Botan::Private_Key>( + new Botan::McEliece_PrivateKey(rng, + Botan::to_u32bit(param_parts[0]), + Botan::to_u32bit(param_parts[1]))); + }; +#endif + + auto gen = generators.find(algo); + if(gen == generators.end()) + { + throw CLI_Error_Unsupported("keygen", algo); + } + + return gen->second(params); + } + + void go() override + { + Botan::AutoSeeded_RNG rng; + std::unique_ptr<Botan::Private_Key> key(do_keygen(get_arg("algo"), get_arg("params"), rng)); + + const std::string pass = get_arg("passphrase"); + + if(pass.empty()) + { + output() << Botan::PKCS8::PEM_encode(*key); + } + else + { + output() << Botan::PKCS8::PEM_encode(*key, + rng, + pass, + std::chrono::milliseconds(get_arg_sz("pbe-millis")), + get_arg("pbe")); + } + } + }; + +BOTAN_REGISTER_COMMAND(PK_Keygen); + +namespace { + +std::string algo_default_emsa(const std::string& key) + { + if(key == "RSA") + return "EMSA4"; // PSS + else if(key == "ECDSA" || key == "DSA") + return "EMSA1"; + else if(key == "RW") + return "EMSA2"; + else + return "EMSA1"; + } + +} + +class PK_Sign : public Command + { + public: + PK_Sign() : Command("sign --passphrase= --hash=SHA-256 --emsa= key file") {} + + void go() override + { + Botan::AutoSeeded_RNG rng; + + std::unique_ptr<Botan::Private_Key> key(Botan::PKCS8::load_key(get_arg("key"), + rng, + get_arg("passphrase"))); + + if(!key) + throw CLI_Error("Unable to load private key"); + + const std::string sig_padding = + get_arg_or("emsa", algo_default_emsa(key->algo_name())) + "(" + get_arg("hash") + ")"; + + Botan::PK_Signer signer(*key, sig_padding); + + this->read_file(get_arg("file"), + [&signer](const uint8_t b[], size_t l) { signer.update(b, l); }); + + output() << Botan::base64_encode(signer.signature(rng)) << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND(PK_Sign); + +class PK_Verify : public Command + { + public: + PK_Verify() : Command("verify --hash=SHA-256 --emsa= pubkey file 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 = + get_arg_or("emsa", algo_default_emsa(key->algo_name())) + "(" + get_arg("hash") + ")"; + + Botan::PK_Verifier verifier(*key, sig_padding); + this->read_file(get_arg("file"), + [&verifier](const uint8_t b[], size_t l) { verifier.update(b, l); }); + + 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(PK_Verify); + +#if defined(BOTAN_HAS_DL_GROUP) + +class Gen_DL_Group : public Command + { + public: + Gen_DL_Group() : Command("gen_dl_group --pbits=1024 --qbits=0 --type=subgroup") {} + + void go() override + { + Botan::AutoSeeded_RNG rng; + + const size_t pbits = get_arg_sz("pbits"); + + const std::string type = get_arg("type"); + + if(type == "strong") + { + Botan::DL_Group grp(rng, Botan::DL_Group::Strong, pbits); + output() << grp.PEM_encode(Botan::DL_Group::ANSI_X9_42); + } + else if(type == "subgroup") + { + Botan::DL_Group grp(rng, Botan::DL_Group::Prime_Subgroup, pbits, get_arg_sz("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); + +#endif + +class PKCS8_Tool : public Command + { + public: + PKCS8_Tool() : Command("pkcs8 --pass-in= --pub-out --pass-out= --pbe= --pbe-millis=300 key") {} + + void go() override + { + Botan::AutoSeeded_RNG rng; + + std::unique_ptr<Botan::Private_Key> key(Botan::PKCS8::load_key(get_arg("key"), + rng, + get_arg("pass-in"))); + + if(flag_set("pub-out")) + { + output() << Botan::X509::PEM_encode(*key); + } + else + { + const std::string pass = get_arg("pass-out"); + + if(pass.empty()) + { + output() << Botan::PKCS8::PEM_encode(*key); + } + else + { + output() << Botan::PKCS8::PEM_encode(*key, + rng, + pass, + std::chrono::milliseconds(get_arg_sz("pbe-millis")), + get_arg("pbe")); + } + + } + + } + }; + +BOTAN_REGISTER_COMMAND(PKCS8_Tool); + +} + +#endif diff --git a/src/cli/rng.cpp b/src/cli/rng.cpp deleted file mode 100644 index ef7b3dc6b..000000000 --- a/src/cli/rng.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* -* (C) 2014 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" -#include <botan/entropy_src.h> -#include <botan/auto_rng.h> - -#if defined(BOTAN_HAS_SYSTEM_RNG) - #include <botan/system_rng.h> -#endif - -namespace { - -int rng(const std::vector<std::string> &args) - { - if(args.size() < 2 || args.size() > 3) - { - std::cout << "Usage: " << args[0] << " [--raw-entropy] n\n" - << "n: number of bytes" - << std::endl; - return 1; - } - - try - { - const size_t bytes_count = to_u32bit(args.back()); - const bool raw = (args.size() == 3 && args[1] == "--raw-entropy"); - -#if defined(BOTAN_HAS_SYSTEM_RNG) - std::cout << "System " << hex_encode(system_rng().random_vec(bytes_count)) << std::endl; -#endif - - if(!raw) - { - AutoSeeded_RNG rng; - std::cout << hex_encode(rng.random_vec(bytes_count)) << std::endl; - } - else - { - double total_collected = 0; - - Entropy_Accumulator accum( - [bytes_count,&total_collected](const byte in[], size_t in_len, double entropy_estimate) - { - std::cout << "Collected estimated "<< entropy_estimate << " bits in " - << hex_encode(in, in_len) << std::endl; - total_collected += entropy_estimate; - return total_collected >= bytes_count; - }); - - Entropy_Sources::global_sources().poll(accum); - } - } - catch(std::exception& e) - { - std::cout << "Error: " << e.what() << std::endl; - return 1; - } - - return 0; - } - -REGISTER_APP(rng); - -} diff --git a/src/cli/self_sig.cpp b/src/cli/self_sig.cpp deleted file mode 100644 index dea18420e..000000000 --- a/src/cli/self_sig.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* -* (C) 2014,2015 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_HAS_RSA) - -#include <botan/pkcs8.h> -#include <botan/x509self.h> -#include <botan/rsa.h> -#include <fstream> -#include <memory> - -using namespace Botan; - -namespace { - -int self_sig(const std::vector<std::string> &args) - { - if(args.size() != 7) - { - std::cout << "Usage: " << args[0] << " " - << "passphrase [CA|user] name country_code organization email" - << std::endl; - return 1; - } - - std::string CA_flag = args[2]; - bool do_CA = false; - - if(CA_flag == "CA") do_CA = true; - else if(CA_flag == "user") do_CA = false; - else - { - std::cout << "Bad flag for CA/user switch: " << CA_flag << std::endl; - return 1; - } - - try - { - AutoSeeded_RNG rng; - - RSA_PrivateKey key(rng, 2048); - //DL_Group group(rng, DL_Group::DSA_Kosherizer, 2048, 256); - - - std::ofstream priv_key("private.pem"); - priv_key << PKCS8::PEM_encode(key, rng, args[1]); - - X509_Cert_Options opts; - opts.common_name = args[3]; - opts.country = args[4]; - opts.organization = args[5]; - opts.email = args[6]; - /* Fill in other values of opts here */ - - //opts.xmpp = "[email protected]"; - - if(do_CA) - opts.CA_key(); - - X509_Certificate cert = - X509::create_self_signed_cert(opts, key, "SHA-256", rng); - - std::ofstream cert_file("cert.pem"); - cert_file << cert.PEM_encode(); - } - catch(std::exception& e) - { - std::cout << "Exception: " << e.what() << std::endl; - return 1; - } - - return 0; - } - -REGISTER_APP(self_sig); - -} - -#endif // BOTAN_HAS_X509_CERTIFICATES && BOTAN_HAS_RSA diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp deleted file mode 100644 index e8d30c6f1..000000000 --- a/src/cli/speed.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* -* (C) 2009 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" - -#if defined(BOTAN_HAS_RUNTIME_BENCHMARKING) - -#include "implementation/speed.h" - -#include <iostream> -#include <iomanip> - -#include <botan/benchmark.h> -#include <botan/auto_rng.h> - -using namespace Botan; - -namespace { -const std::vector<std::string> default_benchmark_list = { - /* Block ciphers */ - "AES-128", - "AES-192", - "AES-256", - "Blowfish", - "CAST-128", - "CAST-256", - "DES", - "IDEA", - "KASUMI", - "MARS", - "MISTY1", - "Noekeon", - "RC2", - "RC5(16)", - "RC6", - "SAFER-SK(10)", - "SEED", - "Serpent", - "Skipjack", - "Square", - "TEA", - "TripleDES", - "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", - - "Serpent/CBC", - "Serpent/CTR-BE", - "Serpent/EAX", - "Serpent/OCB", - "Serpent/GCM", - "Serpent/XTS", - - /* Stream ciphers */ - "RC4", - "Salsa20", - - /* Hashes */ - "Keccak-1600(512)", - "MD5", - "RIPEMD-160", - "SHA-160", - "SHA-256", - "SHA-384", - "SHA-512", - "Skein-512", - "Tiger", - "Whirlpool", - - /* MACs */ - "CMAC(AES-128)", - "HMAC(SHA-1)", - - /* Misc */ - "is_prime", - "random_prime" -}; - -void report_results(const std::string& algo, - const std::map<std::string, double>& speeds) - { - if(speeds.empty()) - return; - - // invert, showing fastest impl first - std::map<double, std::string> results; - - for(auto i = speeds.begin(); i != speeds.end(); ++i) - { - // Speeds might collide, tweak slightly to handle this - if(results[i->second] == "") - results[i->second] = i->first; - else - results[i->second - .01] = i->first; - } - - std::cout << algo; - - const std::ios::fmtflags flags = std::cout.flags(); - for(auto i = results.rbegin(); i != results.rend(); ++i) - { - std::cout << " [" << i->second << "] " - << std::fixed << std::setprecision(2) << i->first; - } - std::cout << std::endl; - std::cout.flags(flags); - } - -void bench_algo(const std::string& algo, - const std::string& provider, - RandomNumberGenerator& rng, - double seconds, - size_t buf_size) - { - std::chrono::milliseconds runtime( - static_cast<std::chrono::milliseconds::rep>(seconds * 1000)); - - if (algo == "random_prime") - { - auto speeds = benchmark_random_prime(rng, runtime); - report_results(algo, speeds); - return; - } - - if (algo == "is_prime") - { - auto speeds = benchmark_is_prime(rng, runtime); - report_results(algo, speeds); - return; - } - - // This does report itself - if (benchmark_transform(rng, algo, runtime)) - return; - - try - { - auto speeds = algorithm_benchmark(algo, rng, runtime, buf_size); - report_results(algo, speeds); - } - catch (No_Provider_Found) - { - #if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) - benchmark_public_key(rng, algo, provider, seconds); - #endif - } - } - -int speed(const std::vector<std::string> &args) - { - OptionParser opts("seconds=|buf-size=|provider="); - opts.parse(args); - - double seconds = .5; - u32bit buf_size = 16; - - if(opts.is_set("seconds")) - { - seconds = std::atof(opts.value("seconds").c_str()); - if(seconds < 0.1 || seconds > (5 * 60)) - { - std::cout << "Invalid argument to --seconds" << std::endl; - return 2; - } - } - - if(opts.is_set("buf-size")) - { - buf_size = std::atoi(opts.value("buf-size").c_str()); - if(buf_size == 0 || buf_size > 1024) - { - std::cout << "Invalid argument to --buf-size" << std::endl; - return 2; - } - } - - const std::string provider = opts.value_if_set("provider"); - - std::vector<std::string> opt_args = opts.arguments(); - - if(opt_args.empty()) - opt_args = default_benchmark_list; - - if(opt_args[0] == "help" || opt_args[0] == "-h") - { - std::cout << "Usage: " << args[0] << " [algo name...]" << std::endl; - return 1; - } - - AutoSeeded_RNG rng; - - for(auto alg: opt_args) - { - bench_algo(alg, provider, rng, seconds, buf_size); - } - - return 0; - } - -REGISTER_APP(speed); - -} -#endif // BOTAN_HAS_RUNTIME_BENCHMARKING diff --git a/src/cli/tls_client.cpp b/src/cli/tls_client.cpp index 7f74e1a37..c13e9019e 100644 --- a/src/cli/tls_client.cpp +++ b/src/cli/tls_client.cpp @@ -1,19 +1,19 @@ /* -* (C) 2014 Jack Lloyd +* (C) 2014,2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ -#include "apps.h" +#include "cli.h" #if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_SOCKETS) #include <botan/tls_client.h> -#include <botan/pkcs8.h> +#include <botan/auto_rng.h> #include <botan/hex.h> #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) - #include <botan/tls_session_manager_sqlite.h> +#include <botan/tls_session_manager_sqlite.h> #endif #include <string> @@ -34,260 +34,258 @@ #include "credentials.h" -using namespace Botan; +namespace Botan_CLI { -using namespace std::placeholders; +class TLS_Client : public Command + { + public: + TLS_Client() : Command("tls_client host --port=443 --type=tcp " + "--session-db= --session-db-pass= --next-protocols=") {} -namespace { + void go() override + { + Botan::AutoSeeded_RNG rng; + Botan::TLS::Policy policy; // TODO read from a file -int connect_to_host(const std::string& host, u16bit port, bool tcp) - { - hostent* host_addr = ::gethostbyname(host.c_str()); + // TODO client cert auth - if(!host_addr) - throw std::runtime_error("gethostbyname failed for " + host); + std::unique_ptr<Botan::TLS::Session_Manager> session_mgr; - if(host_addr->h_addrtype != AF_INET) // FIXME - throw std::runtime_error(host + " has IPv6 address, not supported"); +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + const std::string sessions_passphrase = get_arg("session-db-pass"); + const std::string sessions_db = get_arg("session-db"); - int type = tcp ? SOCK_STREAM : SOCK_DGRAM; + if(!sessions_db.empty()) + { + session_mgr.reset(new Botan::TLS::Session_Manager_SQLite(sessions_passphrase, rng, sessions_db)); + } +#endif + if(!session_mgr) + { + session_mgr.reset(new Botan::TLS::Session_Manager_In_Memory(rng)); + } - int fd = ::socket(PF_INET, type, 0); - if(fd == -1) - throw std::runtime_error("Unable to acquire socket"); + Basic_Credentials_Manager creds; - sockaddr_in socket_info; - ::memset(&socket_info, 0, sizeof(socket_info)); - socket_info.sin_family = AF_INET; - socket_info.sin_port = htons(port); + const std::string host = get_arg("host"); + const uint16_t port = get_arg_sz("port"); + const std::string transport = get_arg("type"); - ::memcpy(&socket_info.sin_addr, - host_addr->h_addr, - host_addr->h_length); + if(transport != "tcp" && transport != "udp") + throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS"); - socket_info.sin_addr = *reinterpret_cast<struct in_addr*>(host_addr->h_addr); // FIXME + const bool use_tcp = (transport == "tcp"); - if(::connect(fd, (sockaddr*)&socket_info, sizeof(struct sockaddr)) != 0) - { - ::close(fd); - throw std::runtime_error("connect failed"); - } + const std::vector<std::string> protocols_to_offer = Botan::split_on("next-protocols", ','); - return fd; - } + int sockfd = connect_to_host(host, port, use_tcp); -bool handshake_complete(const TLS::Session& session) - { - std::cout << "Handshake complete, " << session.version().to_string() - << " using " << session.ciphersuite().to_string() << std::endl; + using namespace std::placeholders; - if(!session.session_id().empty()) - std::cout << "Session ID " << hex_encode(session.session_id()) << std::endl; + auto socket_write = + use_tcp ? + std::bind(stream_socket_write, sockfd, _1, _2) : + std::bind(dgram_socket_write, sockfd, _1, _2); - if(!session.session_ticket().empty()) - std::cout << "Session ticket " << hex_encode(session.session_ticket()) << std::endl; + auto version = policy.latest_supported_version(!use_tcp); - return true; - } + Botan::TLS::Client client(socket_write, + std::bind(&TLS_Client::process_data, this, _1, _2), + std::bind(&TLS_Client::alert_received, this, _1, _2, _3), + std::bind(&TLS_Client::handshake_complete, this, _1), + *session_mgr, + creds, + policy, + rng, + Botan::TLS::Server_Information(host, port), + version, + protocols_to_offer); -void dgram_socket_write(int sockfd, const byte buf[], size_t length) - { - int r = send(sockfd, buf, length, MSG_NOSIGNAL); + bool first_active = true; - if(r == -1) - throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); - } + while(!client.is_closed()) + { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sockfd, &readfds); -void stream_socket_write(int sockfd, const byte buf[], size_t length) - { - size_t offset = 0; + if(client.is_active()) + { + FD_SET(STDIN_FILENO, &readfds); + if(first_active && !protocols_to_offer.empty()) + { + std::string app = client.application_protocol(); + if(app != "") + output() << "Server choose protocol: " << client.application_protocol() << "\n"; + first_active = false; + } + } - while(length) - { - ssize_t sent = ::send(sockfd, (const char*)buf + offset, - length, MSG_NOSIGNAL); + struct timeval timeout = { 1, 0 }; - if(sent == -1) - { - if(errno == EINTR) - sent = 0; - else - throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); - } + ::select(sockfd + 1, &readfds, nullptr, nullptr, &timeout); - offset += sent; - length -= sent; - } - } + if(FD_ISSET(sockfd, &readfds)) + { + uint8_t buf[4*1024] = { 0 }; -bool got_alert = false; + ssize_t got = ::read(sockfd, buf, sizeof(buf)); -void alert_received(TLS::Alert alert, const byte [], size_t ) - { - std::cout << "Alert: " << alert.type_string() << std::endl; - got_alert = true; - } + if(got == 0) + { + output() << "EOF on socket\n"; + break; + } + else if(got == -1) + { + output() << "Socket error: " << errno << " " << strerror(errno) << "\n"; + continue; + } -void process_data(const byte buf[], size_t buf_size) - { - for(size_t i = 0; i != buf_size; ++i) - std::cout << buf[i]; - } + client.received_data(buf, got); + } -int tls_client(const std::vector<std::string> &args) - { - if(args.size() != 2 && args.size() != 3 && args.size() != 4) - { - std::cout << "Usage " << args[0] << " host [port] [udp|tcp]" << std::endl; - return 1; - } + if(FD_ISSET(STDIN_FILENO, &readfds)) + { + uint8_t buf[1024] = { 0 }; + ssize_t got = read(STDIN_FILENO, buf, sizeof(buf)); - try - { - AutoSeeded_RNG rng; - TLS::Policy policy; + if(got == 0) + { + output() << "EOF on stdin\n"; + client.close(); + break; + } + else if(got == -1) + { + output() << "Stdin error: " << errno << " " << strerror(errno) << "\n"; + continue; + } -#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) - const std::string passphrase = "correct horse battery staple"; - const std::string sessions_db = "sessions.db"; - - TLS::Session_Manager_SQLite session_manager(passphrase, - rng, - sessions_db); -#else - TLS::Session_Manager_In_Memory session_manager(rng); -#endif + if(got == 2 && buf[1] == '\n') + { + char cmd = buf[0]; + + if(cmd == 'R' || cmd == 'r') + { + output() << "Client initiated renegotiation\n"; + client.renegotiate(cmd == 'R'); + } + else if(cmd == 'Q') + { + output() << "Client initiated close\n"; + client.close(); + } + } + else if(buf[0] == 'H') + client.heartbeat(&buf[1], got-1); + else + client.send(buf, got); + } - Basic_Credentials_Manager creds; + if(client.timeout_check()) + { + output() << "Timeout detected\n"; + } + } - const std::string host = args[1]; - const u32bit port = args.size() >= 3 ? Botan::to_u32bit(args[2]) : 443; - const std::string transport = args.size() >= 4 ? args[3] : "tcp"; + ::close(sockfd); + } - const bool use_tcp = (transport == "tcp"); + private: + int connect_to_host(const std::string& host, uint16_t port, bool tcp) + { + hostent* host_addr = ::gethostbyname(host.c_str()); - const std::vector<std::string> protocols_to_offer = { "test/9.9", "http/1.1", "echo/9.1" }; + if(!host_addr) + throw std::runtime_error("gethostbyname failed for " + host); - int sockfd = connect_to_host(host, port, use_tcp); + if(host_addr->h_addrtype != AF_INET) // FIXME + throw std::runtime_error(host + " has IPv6 address, not supported"); - auto socket_write = - use_tcp ? - std::bind(stream_socket_write, sockfd, _1, _2) : - std::bind(dgram_socket_write, sockfd, _1, _2); + int type = tcp ? SOCK_STREAM : SOCK_DGRAM; - auto version = policy.latest_supported_version(!use_tcp); + int fd = ::socket(PF_INET, type, 0); + if(fd == -1) + throw std::runtime_error("Unable to acquire socket"); - TLS::Client client(socket_write, - process_data, - alert_received, - handshake_complete, - session_manager, - creds, - policy, - rng, - TLS::Server_Information(host, port), - version, - protocols_to_offer); + sockaddr_in socket_info; + ::memset(&socket_info, 0, sizeof(socket_info)); + socket_info.sin_family = AF_INET; + socket_info.sin_port = htons(port); - bool first_active = true; + ::memcpy(&socket_info.sin_addr, + host_addr->h_addr, + host_addr->h_length); - while(!client.is_closed()) - { - fd_set readfds; - FD_ZERO(&readfds); - FD_SET(sockfd, &readfds); + socket_info.sin_addr = *reinterpret_cast<struct in_addr*>(host_addr->h_addr); // FIXME - if(client.is_active()) + if(::connect(fd, (sockaddr*)&socket_info, sizeof(struct sockaddr)) != 0) { - FD_SET(STDIN_FILENO, &readfds); - if(first_active && !protocols_to_offer.empty()) - { - std::string app = client.application_protocol(); - if(app != "") - std::cout << "Server choose protocol: " << client.application_protocol() << std::endl; - first_active = false; - } + ::close(fd); + throw std::runtime_error("connect failed"); } - struct timeval timeout = { 1, 0 }; + return fd; + } + + bool handshake_complete(const Botan::TLS::Session& session) + { + output() << "Handshake complete, " << session.version().to_string() + << " using " << session.ciphersuite().to_string() << "\n"; - ::select(sockfd + 1, &readfds, nullptr, nullptr, &timeout); + if(!session.session_id().empty()) + output() << "Session ID " << Botan::hex_encode(session.session_id()) << "\n"; - if(FD_ISSET(sockfd, &readfds)) - { - byte buf[4*1024] = { 0 }; + if(!session.session_ticket().empty()) + output() << "Session ticket " << Botan::hex_encode(session.session_ticket()) << "\n"; - ssize_t got = ::read(sockfd, buf, sizeof(buf)); + return true; + } - if(got == 0) - { - std::cout << "EOF on socket" << std::endl; - break; - } - else if(got == -1) - { - std::cout << "Socket error: " << errno << " " << strerror(errno) << std::endl; - continue; - } + static void dgram_socket_write(int sockfd, const uint8_t buf[], size_t length) + { + int r = send(sockfd, buf, length, MSG_NOSIGNAL); - client.received_data(buf, got); - } + if(r == -1) + throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); + } + + static void stream_socket_write(int sockfd, const uint8_t buf[], size_t length) + { + size_t offset = 0; - if(FD_ISSET(STDIN_FILENO, &readfds)) + while(length) { - byte buf[1024] = { 0 }; - ssize_t got = read(STDIN_FILENO, buf, sizeof(buf)); + ssize_t sent = ::send(sockfd, (const char*)buf + offset, + length, MSG_NOSIGNAL); - if(got == 0) - { - std::cout << "EOF on stdin" << std::endl; - client.close(); - break; - } - else if(got == -1) + if(sent == -1) { - std::cout << "Stdin error: " << errno << " " << strerror(errno) << std::endl; - continue; + if(errno == EINTR) + sent = 0; + else + throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); } - if(got == 2 && buf[1] == '\n') - { - char cmd = buf[0]; - - if(cmd == 'R' || cmd == 'r') - { - std::cout << "Client initiated renegotiation" << std::endl; - client.renegotiate(cmd == 'R'); - } - else if(cmd == 'Q') - { - std::cout << "Client initiated close" << std::endl; - client.close(); - } - } - else if(buf[0] == 'H') - client.heartbeat(&buf[1], got-1); - else - client.send(buf, got); + offset += sent; + length -= sent; } + } - if(client.timeout_check()) - { - std::cout << "Timeout detected" << std::endl; - } + void alert_received(Botan::TLS::Alert alert, const uint8_t [], size_t ) + { + output() << "Alert: " << alert.type_string() << "\n"; + } + + void process_data(const uint8_t buf[], size_t buf_size) + { + for(size_t i = 0; i != buf_size; ++i) + output() << buf[i]; } + }; - ::close(sockfd); - } - catch(std::exception& e) - { - std::cout << "Exception: " << e.what() << std::endl; - return 1; - } - return 0; - } - -REGISTER_APP(tls_client); +BOTAN_REGISTER_COMMAND(TLS_Client); } diff --git a/src/cli/tls_proxy.cpp b/src/cli/tls_proxy.cpp index 5071cb8bb..e28ef14bc 100644 --- a/src/cli/tls_proxy.cpp +++ b/src/cli/tls_proxy.cpp @@ -5,7 +5,7 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include "apps.h" +#include "cli.h" #if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_BOOST_ASIO) @@ -31,12 +31,12 @@ #include "credentials.h" -using boost::asio::ip::tcp; - -namespace Botan { +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; @@ -47,12 +47,12 @@ inline void log_error(const char* where, const boost::system::error_code& error) //std::cout << where << ' ' << error.message() << std::endl; } -inline void log_binary_message(const char* where, const byte buf[], size_t buf_len) +inline void log_binary_message(const char* where, const uint8_t buf[], size_t buf_len) { //std::cout << where << ' ' << hex_encode(buf, buf_len) << std::endl; } -void log_text_message(const char* where, const byte buf[], size_t buf_len) +void log_text_message(const char* where, const uint8_t buf[], size_t buf_len) { //const char* c = reinterpret_cast<const char*>(buf); //std::cout << where << ' ' << std::string(c, c + buf_len) << std::endl; @@ -66,9 +66,9 @@ class tls_proxy_session : public boost::enable_shared_from_this<tls_proxy_sessio typedef boost::shared_ptr<tls_proxy_session> pointer; static pointer create(boost::asio::io_service& io, - TLS::Session_Manager& session_manager, - Credentials_Manager& credentials, - TLS::Policy& policy, + Botan::TLS::Session_Manager& session_manager, + Botan::Credentials_Manager& credentials, + Botan::TLS::Policy& policy, tcp::resolver::iterator endpoints) { return pointer( @@ -99,9 +99,9 @@ class tls_proxy_session : public boost::enable_shared_from_this<tls_proxy_sessio private: tls_proxy_session(boost::asio::io_service& io, - TLS::Session_Manager& session_manager, - Credentials_Manager& credentials, - TLS::Policy& policy, + 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), @@ -174,13 +174,13 @@ class tls_proxy_session : public boost::enable_shared_from_this<tls_proxy_sessio proxy_write_to_server(nullptr, 0); // initiate another write if needed } - void tls_client_write_to_proxy(const byte buf[], size_t buf_len) + void tls_client_write_to_proxy(const uint8_t buf[], size_t buf_len) { // Immediately bounce message to server proxy_write_to_server(buf, buf_len); } - void tls_proxy_write_to_client(const byte buf[], size_t buf_len) + void tls_proxy_write_to_client(const uint8_t buf[], size_t buf_len) { if(buf_len > 0) m_p2c_pending.insert(m_p2c_pending.end(), buf, buf + buf_len); @@ -202,7 +202,7 @@ class tls_proxy_session : public boost::enable_shared_from_this<tls_proxy_sessio } } - void proxy_write_to_server(const byte buf[], size_t buf_len) + 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); @@ -259,7 +259,7 @@ class tls_proxy_session : public boost::enable_shared_from_this<tls_proxy_sessio boost::asio::placeholders::bytes_transferred))); } - bool tls_handshake_complete(const TLS::Session& session) + bool tls_handshake_complete(const Botan::TLS::Session& session) { //std::cout << "Handshake from client complete" << std::endl; @@ -283,9 +283,9 @@ class tls_proxy_session : public boost::enable_shared_from_this<tls_proxy_sessio return true; } - void tls_alert_cb(TLS::Alert alert, const byte[], size_t) + void tls_alert_cb(Botan::TLS::Alert alert, const uint8_t[], size_t) { - if(alert.type() == TLS::Alert::CLOSE_NOTIFY) + if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY) { m_tls.close(); return; @@ -301,17 +301,17 @@ class tls_proxy_session : public boost::enable_shared_from_this<tls_proxy_sessio tcp::socket m_client_socket; tcp::socket m_server_socket; - AutoSeeded_RNG m_rng; - TLS::Server m_tls; + Botan::AutoSeeded_RNG m_rng; // RNG per connection + Botan::TLS::Server m_tls; std::string m_hostname; - std::vector<byte> m_c2p; - std::vector<byte> m_p2c; - std::vector<byte> m_p2c_pending; + std::vector<uint8_t> m_c2p; + std::vector<uint8_t> m_p2c; + std::vector<uint8_t> m_p2c_pending; - std::vector<byte> m_s2p; - std::vector<byte> m_p2s; - std::vector<byte> m_p2s_pending; + std::vector<uint8_t> m_s2p; + std::vector<uint8_t> m_p2s; + std::vector<uint8_t> m_p2s_pending; }; class tls_proxy_server @@ -321,9 +321,9 @@ class tls_proxy_server tls_proxy_server(boost::asio::io_service& io, unsigned short port, tcp::resolver::iterator endpoints, - Credentials_Manager& creds, - TLS::Policy& policy, - TLS::Session_Manager& session_mgr) : + Botan::Credentials_Manager& creds, + Botan::TLS::Policy& policy, + Botan::TLS::Session_Manager& session_mgr) : m_acceptor(io, tcp::endpoint(tcp::v4(), port)), m_server_endpoints(endpoints), m_creds(creds), @@ -377,83 +377,73 @@ class tls_proxy_server tcp::acceptor m_acceptor; tcp::resolver::iterator m_server_endpoints; - Credentials_Manager& m_creds; - TLS::Policy& m_policy; - TLS::Session_Manager& m_session_manager; + Botan::Credentials_Manager& m_creds; + Botan::TLS::Policy& m_policy; + Botan::TLS::Session_Manager& m_session_manager; }; -size_t choose_thread_count() - { - size_t result = std::thread::hardware_concurrency(); - - if(result) - return result; - - return 2; - } +} -int tls_proxy(const std::vector<std::string> &args) +class TLS_Proxy : public Command { - if(args.size() != 6) - { - std::cout << "Usage: " << args[0] << " listen_port target_host target_port server_cert server_key" << std::endl; - return 1; - } + public: + TLS_Proxy() : Command("tls_proxy listen_port target_host target_port server_cert server_key " + "--threads=0 --session-db= --session-db-pass=") {} + + void go() + { + const size_t listen_port = get_arg_sz("listen_port"); + const std::string target = get_arg("target_host"); + const std::string target_port = get_arg("target_port"); - const size_t listen_port = to_u32bit(args[1]); - const std::string target = args[2]; - const std::string target_port = args[3]; + const std::string server_crt = get_arg("server_cert"); + const std::string server_key = get_arg("server_key"); - const std::string server_crt = args[4]; - const std::string server_key = args[5]; + const size_t num_threads = get_arg_sz("threads") || std::thread::hardware_concurrency() || 2; - const size_t num_threads = choose_thread_count(); // make configurable + Botan::AutoSeeded_RNG rng; + Basic_Credentials_Manager creds(rng, server_crt, server_key); - AutoSeeded_RNG rng; - Basic_Credentials_Manager creds(rng, server_crt, server_key); + Botan::TLS::Policy policy; // TODO: Read policy from text file - TLS::Policy policy; // TODO: Read policy from text file + boost::asio::io_service io; - try - { - boost::asio::io_service io; + tcp::resolver resolver(io); + auto server_endpoint_iterator = resolver.resolve({ target, target_port }); - 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) - // Todo: make configurable - const std::string sessions_passphrase = "correct horse battery staple"; - const std::string sessions_db = "sessions.db"; - TLS::Session_Manager_SQLite sessions(sessions_passphrase, rng, sessions_db); -#else - TLS::Session_Manager_In_Memory sessions(rng); -#endif + const std::string sessions_passphrase = get_arg("session-db-pass"); + const std::string sessions_db = get_arg("session-db"); - tls_proxy_server server(io, listen_port, server_endpoint_iterator, creds, policy, sessions); + 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)); + } - std::vector<std::shared_ptr<std::thread>> threads; + tls_proxy_server server(io, listen_port, server_endpoint_iterator, creds, policy, *session_mgr); - for(size_t i = 2; i <= num_threads; ++i) - threads.push_back(std::make_shared<std::thread>([&io]() { io.run(); })); + std::vector<std::shared_ptr<std::thread>> threads; - io.run(); + // 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(); })); - for (size_t i = 0; i < threads.size(); ++i) - threads[i]->join(); - } - catch (std::exception& e) - { - std::cerr << e.what() << std::endl; - } + io.run(); - return 0; - } + for (size_t i = 0; i < threads.size(); ++i) + threads[i]->join(); + } + }; -} +BOTAN_REGISTER_COMMAND(TLS_Proxy); } -REGISTER_APP(tls_proxy); - #endif diff --git a/src/cli/tls_server.cpp b/src/cli/tls_server.cpp index ea68208b6..6a5b4e812 100644 --- a/src/cli/tls_server.cpp +++ b/src/cli/tls_server.cpp @@ -5,24 +5,15 @@ * Botan is released under the Simplified BSD License (see license.txt) */ -#include "apps.h" +#include "cli.h" #if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_SOCKETS) #include <botan/tls_server.h> #include <botan/hex.h> - -#include <botan/rsa.h> -#include <botan/dsa.h> -#include <botan/x509self.h> -#include <botan/secqueue.h> - +#include <botan/auto_rng.h> #include "credentials.h" -using namespace Botan; - -using namespace std::placeholders; - #include <list> #include <sys/types.h> @@ -38,128 +29,44 @@ using namespace std::placeholders; #define MSG_NOSIGNAL 0 #endif -namespace { +namespace Botan_CLI { -int make_server_socket(bool is_tcp, u16bit port) +class TLS_Server : public Command { - const int type = is_tcp ? SOCK_STREAM : SOCK_DGRAM; - - int fd = ::socket(PF_INET, type, 0); - if(fd == -1) - throw std::runtime_error("Unable to acquire socket"); + public: + TLS_Server() : Command("tls_server cert key --port=443 --type=tcp") {} - sockaddr_in socket_info; - ::memset(&socket_info, 0, sizeof(socket_info)); - 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, (sockaddr*)&socket_info, sizeof(struct sockaddr)) != 0) - { - ::close(fd); - throw std::runtime_error("server bind failed"); - } - - if(is_tcp) - { - if(::listen(fd, 100) != 0) + void go() override { - ::close(fd); - throw std::runtime_error("listen failed"); - } - } + const std::string server_crt = get_arg("cert"); + const std::string server_key = get_arg("key"); + const int port = get_arg_sz("port"); + const std::string transport = get_arg("type"); - return fd; - } + if(transport != "tcp" && transport != "udp") + throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS"); -bool handshake_complete(const TLS::Session& session) - { - std::cout << "Handshake complete, " << session.version().to_string() - << " using " << session.ciphersuite().to_string() << std::endl; - - if(!session.session_id().empty()) - std::cout << "Session ID " << hex_encode(session.session_id()) << std::endl; + const bool is_tcp = (transport == "tcp"); - if(!session.session_ticket().empty()) - std::cout << "Session ticket " << hex_encode(session.session_ticket()) << std::endl; + Botan::AutoSeeded_RNG rng; - return true; - } - -void dgram_socket_write(int sockfd, const byte buf[], size_t length) - { - ssize_t sent = ::send(sockfd, buf, length, MSG_NOSIGNAL); + Botan::TLS::Policy policy; // TODO read policy from file - if(sent == -1) - std::cout << "Error writing to socket - " << strerror(errno) << std::endl; - else if(sent != static_cast<ssize_t>(length)) - std::cout << "Packet of length " << length << " truncated to " << sent << std::endl; - } + Botan::TLS::Session_Manager_In_Memory session_manager(rng); // TODO sqlite3 -void stream_socket_write(int sockfd, const byte buf[], size_t length) - { - while(length) - { - ssize_t sent = ::send(sockfd, buf, length, MSG_NOSIGNAL); + Basic_Credentials_Manager creds(rng, server_crt, server_key); - if(sent == -1) - { - if(errno == EINTR) - sent = 0; - else - throw std::runtime_error("Socket write failed"); - } + auto protocol_chooser = [](const std::vector<std::string>& protocols) -> std::string { + for(size_t i = 0; i != protocols.size(); ++i) + std::cout << "Client offered protocol " << i << " = " << protocols[i] << std::endl; + return "echo/1.0"; // too bad + }; - buf += sent; - length -= sent; - } - } + output() << "Listening for new connections on " << transport << " port " << port << std::endl; -void alert_received(TLS::Alert alert, const byte[], size_t) - { - std::cout << "Alert: " << alert.type_string() << std::endl; - } + int server_fd = make_server_socket(is_tcp, port); -int tls_server(const std::vector<std::string> &args) - { - if(args.size() != 4 && args.size() != 5) - { - std::cout << "Usage: " << args[0] << " server.crt server.key port [tcp|udp]" << std::endl; - return 1; - } - - const std::string server_crt = args[1]; - const std::string server_key = args[2]; - const int port = to_u32bit(args[3]); - const std::string transport = (args.size() >= 5) ? args[4] : "tcp"; - - const bool is_tcp = (transport == "tcp"); - - try - { - AutoSeeded_RNG rng; - - TLS::Policy policy; - - TLS::Session_Manager_In_Memory session_manager(rng); - - Basic_Credentials_Manager creds(rng, server_crt, server_key); - - auto protocol_chooser = [](const std::vector<std::string>& protocols) -> std::string { - for(size_t i = 0; i != protocols.size(); ++i) - std::cout << "Client offered protocol " << i << " = " << protocols[i] << std::endl; - return "echo/1.0"; // too bad - }; - - std::cout << "Listening for new connections on " << transport << " port " << port << std::endl; - - int server_fd = make_server_socket(is_tcp, port); - - while(true) - { - try + while(true) { int fd; @@ -180,42 +87,42 @@ int tls_server(const std::vector<std::string> &args) fd = server_fd; } - std::cout << "New connection received" << std::endl; + using namespace std::placeholders; - auto socket_write = is_tcp ? std::bind(stream_socket_write, fd, _1, _2) : - std::bind(dgram_socket_write, fd, _1, _2); + auto socket_write = is_tcp ? std::bind(&stream_socket_write, fd, _1, _2) : + std::bind(&dgram_socket_write, fd, _1, _2); std::string s; std::list<std::string> pending_output; - auto proc_fn = [&](const byte input[], size_t input_len) + auto proc_fn = [&](const uint8_t input[], size_t input_len) { - for(size_t i = 0; i != input_len; ++i) - { - const char c = static_cast<char>(input[i]); - s += c; - if(c == '\n') + for(size_t i = 0; i != input_len; ++i) { - pending_output.push_back(s); - s.clear(); + const char c = static_cast<char>(input[i]); + s += c; + if(c == '\n') + { + pending_output.push_back(s); + s.clear(); + } } - } }; - TLS::Server server(socket_write, - proc_fn, - alert_received, - handshake_complete, - session_manager, - creds, - policy, - rng, - protocol_chooser, - !is_tcp); + Botan::TLS::Server server(socket_write, + proc_fn, + std::bind(&TLS_Server::alert_received, this, _1, _2, _3), + std::bind(&TLS_Server::handshake_complete, this, _1), + session_manager, + creds, + policy, + rng, + protocol_chooser, + !is_tcp); while(!server.is_closed()) { - byte buf[4*1024] = { 0 }; + uint8_t buf[4*1024] = { 0 }; ssize_t got = ::read(fd, buf, sizeof(buf)); if(got == -1) @@ -245,25 +152,94 @@ int tls_server(const std::vector<std::string> &args) if(is_tcp) ::close(fd); + } + } + private: + int make_server_socket(bool is_tcp, uint16_t port) + { + const int type = is_tcp ? SOCK_STREAM : SOCK_DGRAM; + + int fd = ::socket(PF_INET, type, 0); + if(fd == -1) + throw std::runtime_error("Unable to acquire socket"); + + sockaddr_in socket_info; + ::memset(&socket_info, 0, sizeof(socket_info)); + 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, (sockaddr*)&socket_info, sizeof(struct sockaddr)) != 0) + { + ::close(fd); + throw std::runtime_error("server bind failed"); + } + + if(is_tcp) + { + if(::listen(fd, 100) != 0) + { + ::close(fd); + throw std::runtime_error("listen failed"); + } } - catch(std::exception& e) + + return fd; + } + + bool handshake_complete(const Botan::TLS::Session& session) + { + std::cout << "Handshake complete, " << session.version().to_string() + << " using " << session.ciphersuite().to_string() << std::endl; + + if(!session.session_id().empty()) + std::cout << "Session ID " << Botan::hex_encode(session.session_id()) << std::endl; + + if(!session.session_ticket().empty()) + std::cout << "Session ticket " << Botan::hex_encode(session.session_ticket()) << std::endl; + + return true; + } + + static void dgram_socket_write(int sockfd, const uint8_t buf[], size_t length) + { + ssize_t sent = ::send(sockfd, buf, length, MSG_NOSIGNAL); + + if(sent == -1) + std::cout << "Error writing to socket - " << strerror(errno) << std::endl; + else if(sent != static_cast<ssize_t>(length)) + std::cout << "Packet of length " << length << " truncated to " << sent << std::endl; + } + + static void stream_socket_write(int sockfd, const uint8_t buf[], size_t length) + { + while(length) { - std::cout << "Connection problem: " << e.what() << std::endl; - return 1; + ssize_t sent = ::send(sockfd, buf, length, MSG_NOSIGNAL); + + if(sent == -1) + { + if(errno == EINTR) + sent = 0; + else + throw std::runtime_error("Socket write failed"); + } + + buf += sent; + length -= sent; } } - } - catch(std::exception& e) - { - std::cout << e.what() << std::endl; - return 1; - } - return 0; - } + void alert_received(Botan::TLS::Alert alert, const uint8_t[], size_t) + { + std::cout << "Alert: " << alert.type_string() << std::endl; + } + + }; -REGISTER_APP(tls_server); +BOTAN_REGISTER_COMMAND(TLS_Server); } diff --git a/src/cli/utils.cpp b/src/cli/utils.cpp new file mode 100644 index 000000000..f3ce5f0f9 --- /dev/null +++ b/src/cli/utils.cpp @@ -0,0 +1,266 @@ +/* +* (C) 2009,2010,2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#include <botan/version.h> +#include <botan/auto_rng.h> +#include <botan/hash.h> +#include <botan/cpuid.h> +#include <botan/hex.h> + +#if defined(BOTAN_HAS_BASE64_CODEC) + #include <botan/base64.h> +#endif + +#if defined(BOTAN_HAS_SYSTEM_RNG) + #include <botan/system_rng.h> +#endif + +#if defined(BOTAN_HAS_HTTP_UTIL) + #include <botan/http_util.h> +#endif + +#if defined(BOTAN_HAS_BCRYPT) + #include <botan/bcrypt.h> +#endif + +namespace Botan_CLI { + +class Config_Info : 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"; + } + + 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") + { + output() << "-L" << BOTAN_INSTALL_PREFIX << "/" << BOTAN_INSTALL_LIB_DIR << "\n"; + } + else if(arg == "libs") + { + output() << "-lbotan-" << Botan::version_major() << "." << Botan::version_minor() + << " " << BOTAN_LIB_LINK << "\n"; + } + else + { + throw CLI_Usage_Error("Unknown option to botan config " + arg); + } + } + }; + +BOTAN_REGISTER_COMMAND(Config_Info); + +class Version_Info : public Command + { + public: + Version_Info() : Command("version --full") {} + + void go() override + { + if(flag_set("full")) + { + output() << Botan::version_string() << "\n"; + } + else + { + output() << Botan::version_major() << "." + << Botan::version_minor() << "." + << Botan::version_patch() << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND(Version_Info); + +class Print_Cpuid : public Command + { + public: + Print_Cpuid() : Command("cpuid") {} + + void go() override + { + Botan::CPUID::print(output()); + } + }; + +BOTAN_REGISTER_COMMAND(Print_Cpuid); + +class Hash : public Command + { + public: + Hash() : Command("hash --algo=SHA-256 --buf-size=4096 *files") {} + + void go() override + { + const std::string hash_algo = get_arg("algo"); + std::unique_ptr<Botan::HashFunction> hash_fn(Botan::HashFunction::create(hash_algo)); + + if(!hash_fn) + throw CLI_Error_Unsupported("hashing", hash_algo); + + const size_t buf_size = get_arg_sz("buf-size"); + + for(auto fsname : get_arg_list("files")) + { + auto update_hash = [&](const uint8_t b[], size_t l) { hash_fn->update(b, l); }; + read_file(fsname, update_hash, buf_size); + output() << Botan::hex_encode(hash_fn->final()) << " " << fsname << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND(Hash); + +class RNG : public Command + { + public: + RNG() : Command("rng bytes --system") {} + + void go() override + { + const size_t bytes = get_arg_sz("bytes"); + + if(flag_set("system")) + { +#if defined(BOTAN_HAS_SYSTEM_RNG) + output() << Botan::hex_encode(Botan::system_rng().random_vec(bytes)) << "\n"; +#else + error_output() << "system_rng disabled in build\n"; +#endif + } + else + { + Botan::AutoSeeded_RNG rng; + output() << Botan::hex_encode(rng.random_vec(bytes)) << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND(RNG); + +#if defined(BOTAN_HAS_HTTP_UTIL) + +class HTTP_Get : public Command + { + public: + HTTP_Get() : Command("http_get url") {} + + void go() override + { + output() << Botan::HTTP::GET_sync(get_arg("url")) << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND(HTTP_Get); + +#endif // http_util + +#if defined(BOTAN_HAS_BASE64_CODEC) + +class Base64_Encode : public Command + { + public: + Base64_Encode() : Command("base64_enc file") {} + + void go() override + { + this->read_file(get_arg("file"), + [&](const uint8_t b[], size_t l) { output() << Botan::base64_encode(b, l); }, + 768); + } + }; + +BOTAN_REGISTER_COMMAND(Base64_Encode); + +class Base64_Decode : public Command + { + public: + Base64_Decode() : Command("base64_dec 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_Decode); + +#endif // base64 + +#if defined(BOTAN_HAS_BCRYPT) + +class Generate_Bcrypt : public Command + { + public: + Generate_Bcrypt() : Command("gen_bcrypt --work-factor=12 password") {} + + void go() + { + const std::string password = get_arg("password"); + const size_t wf = get_arg_sz("work_factor"); + + Botan::AutoSeeded_RNG rng; + output() << Botan::generate_bcrypt(password, rng, wf) << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND(Generate_Bcrypt); + +class Check_Bcrypt : public Command + { + public: + Check_Bcrypt() : Command("check_bcrypt password hash") {} + + void go() + { + const std::string password = get_arg("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; + } + }; + +BOTAN_REGISTER_COMMAND(Check_Bcrypt); + +#endif // bcrypt + +} diff --git a/src/cli/x509.cpp b/src/cli/x509.cpp new file mode 100644 index 000000000..add73a466 --- /dev/null +++ b/src/cli/x509.cpp @@ -0,0 +1,224 @@ +/* +* (C) 2010,2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +#include <botan/auto_rng.h> +#include <botan/certstor.h> +#include <botan/pkcs8.h> +#include <botan/x509_ca.h> +#include <botan/x509cert.h> +#include <botan/x509path.h> +#include <botan/x509self.h> + +#if defined(BOTAN_HAS_OCSP) + #include <botan/ocsp.h> +#endif + +namespace Botan_CLI { + +class Sign_Cert : public Command + { + public: + Sign_Cert() : Command("sign_cert --ca-key-pass= --hash=SHA-256 " + "--duration=365 ca_cert ca_key pkcs10_req") {} + + void go() override + { + Botan::AutoSeeded_RNG rng; + + Botan::X509_Certificate ca_cert(get_arg("ca_cert")); + + std::unique_ptr<Botan::PKCS8_PrivateKey> key( + Botan::PKCS8::load_key(get_arg("ca_key"), + rng, + get_arg("ca_key_pass"))); + + if(!key) + throw CLI_Error("Failed to load key from " + get_arg("ca_key")); + + Botan::X509_CA ca(ca_cert, *key, get_arg("hash")); + + 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); + +class Cert_Info : public Command + { + public: + Cert_Info() : Command("cert_info file") {} + + void go() override + { + Botan::X509_Certificate cert(get_arg("file")); + output() << cert.to_string() << "\n"; + } + }; + +BOTAN_REGISTER_COMMAND(Cert_Info); + +#if defined(BOTAN_HAS_OCSP) +class OCSP_Check : public Command + { + public: + OCSP_Check() : Command("ocsp_check subject issuer") {} + + void go() override + { + Botan::X509_Certificate subject(get_arg("subject")); + Botan::X509_Certificate issuer(get_arg("issuer")); + + Botan::Certificate_Store_In_Memory cas; + cas.add_certificate(issuer); + Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, &cas); + + auto status = resp.status_for(issuer, subject); + + if(status == Botan::Certificate_Status_Code::VERIFIED) + { + output() << "OCSP check OK\n"; + } + else + { + output() << "OCSP check failed " << + Botan::Path_Validation_Result::status_string(status) << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND(OCSP_Check); + +#endif // OCSP + +class Cert_Verify : public Command + { + public: + Cert_Verify() : Command("cert_verify subject *ca_certs") {} + + void go() override + { + Botan::X509_Certificate subject_cert(get_arg("subject")); + Botan::Certificate_Store_In_Memory trusted; + + for(auto&& 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); + +class Gen_Self_Signed : public Command + { + public: + Gen_Self_Signed() : Command("gen_self_signed key CN --country= --dns= " + "--organization= --email= --key-pass= --ca --hash=SHA-256") {} + + void go() override + { + Botan::AutoSeeded_RNG rng; + + 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.dns = get_arg("dns"); + + if(flag_set("ca")) + opts.CA_key(); + + Botan::X509_Certificate cert = + Botan::X509::create_self_signed_cert(opts, *key, get_arg("hash"), rng); + + output() << cert.PEM_encode(); + } + }; + +BOTAN_REGISTER_COMMAND(Gen_Self_Signed); + +class Generate_PKCS10 : public Command + { + public: + Generate_PKCS10() : Command("gen_pkcs10 key CN --country= --organization= " + "--email= --key-pass= --hash=SHA-256") {} + + void go() override + { + Botan::AutoSeeded_RNG rng; + + 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"); + + Botan::PKCS10_Request req = + Botan::X509::create_cert_req(opts, *key, + get_arg("hash"), + rng); + + output() << req.PEM_encode(); + } + }; + +BOTAN_REGISTER_COMMAND(Generate_PKCS10); + +} + +#endif diff --git a/src/cli/x509print.cpp b/src/cli/x509print.cpp deleted file mode 100644 index 5c069ca77..000000000 --- a/src/cli/x509print.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* -* (C) 2014 Jack Lloyd -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#include "apps.h" -#if defined(BOTAN_HAS_X509_CERTIFICATES) -#include <botan/x509cert.h> - -namespace { - -int x509print(const std::vector<std::string> &args) - { - if(args.size() != 2) - { - std::cout << "Usage: " << args[0] << " cert.pem" << std::endl; - return 1; - } - - X509_Certificate cert(args[1]); - - std::cout << cert.to_string() << std::endl; - - return 0; - } - -REGISTER_APP(x509print); - -} - -#endif |