diff options
Diffstat (limited to 'src/cli')
38 files changed, 5280 insertions, 0 deletions
diff --git a/src/cli/apps.h b/src/cli/apps.h new file mode 100644 index 000000000..9f1f00ba2 --- /dev/null +++ b/src/cli/apps.h @@ -0,0 +1,77 @@ +/* +* (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 new file mode 100644 index 000000000..2aa94cc39 --- /dev/null +++ b/src/cli/asn1.cpp @@ -0,0 +1,356 @@ +/* +* (C) 2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "apps.h" + +#if defined(BOTAN_HAS_ASN1) && defined(BOTAN_HAS_PEM_CODEC) + +#include <botan/bigint.h> +#include <botan/hex.h> +#include <botan/der_enc.h> +#include <botan/ber_dec.h> +#include <botan/asn1_time.h> +#include <botan/asn1_str.h> +#include <botan/oids.h> +#include <botan/pem.h> +#include <botan/charset.h> +using namespace Botan; + +#include <iostream> +#include <iomanip> +#include <sstream> +#include <ctype.h> + +// Set this if your terminal understands UTF-8; otherwise output is in Latin-1 +#define UTF8_TERMINAL 1 + +/* + What level the outermost layer of stuff is at. Probably 0 or 1; asn1parse + uses 0 as the outermost, while 1 makes more sense to me. 2+ doesn't make + much sense at all. +*/ +#define INITIAL_LEVEL 0 + +namespace { + +std::string url_encode(const std::vector<byte>& in) + { + std::ostringstream out; + + size_t unprintable = 0; + + for(size_t i = 0; i != in.size(); ++i) + { + const int c = in[i]; + if(::isprint(c)) + out << static_cast<char>(c); + else + { + out << "%" << std::hex << static_cast<int>(c) << std::dec; + ++unprintable; + } + } + + if(unprintable >= in.size() / 4) + return hex_encode(in); + + return out.str(); + } + +void emit(const std::string& type, size_t level, size_t length, const std::string& value = "") + { + const size_t LIMIT = 4*1024; + const size_t BIN_LIMIT = 1024; + + std::ostringstream out; + + out << " d=" << std::setw(2) << level + << ", l=" << std::setw(4) << length << ": "; + + for(size_t i = INITIAL_LEVEL; i != level; ++i) + out << ' '; + + out << type; + + bool should_skip = false; + + if(value.length() > LIMIT) + should_skip = true; + + if((type == "OCTET STRING" || type == "BIT STRING") && value.length() > BIN_LIMIT) + should_skip = true; + + if(value != "" && !should_skip) + { + if(out.tellp() % 2 == 0) out << ' '; + + while(out.tellp() < 50) out << ' '; + + out << value; + } + + std::cout << out.str() << std::endl; + } + +std::string type_name(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"; + return "(UNKNOWN)"; + } + +void decode(BER_Decoder& decoder, size_t level) + { + BER_Object obj = decoder.get_next_object(); + + while(obj.type_tag != NO_OBJECT) + { + const ASN1_Tag type_tag = obj.type_tag; + const 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; + encoder.add_object(type_tag, class_tag, obj.value); + std::vector<byte> bits = encoder.get_contents_unlocked(); + + BER_Decoder data(bits); + + if(class_tag & CONSTRUCTED) + { + BER_Decoder cons_info(obj.value); + if(type_tag == SEQUENCE) + { + emit("SEQUENCE", level, length); + decode(cons_info, level+1); + } + else if(type_tag == SET) + { + emit("SET", level, length); + decode(cons_info, level+1); + } + else + { + std::string name; + + if((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC)) + { + name = "cons [" + std::to_string(type_tag) + "]"; + + if(class_tag & APPLICATION) + name += " appl"; + if(class_tag & CONTEXT_SPECIFIC) + name += " context"; + } + else + name = type_name(type_tag) + " (cons)"; + + emit(name, level, length); + decode(cons_info, level+1); + } + } + else if((class_tag & APPLICATION) || (class_tag & CONTEXT_SPECIFIC)) + { +#if 0 + std::vector<byte> bits; + data.decode(bits, type_tag); + + try + { + BER_Decoder inner(bits); + decode(inner, level + 1); + } + catch(...) + { + emit("[" + std::to_string(type_tag) + "]", level, length, + url_encode(bits)); + } +#else + emit("[" + std::to_string(type_tag) + "]", level, length, + url_encode(bits)); +#endif + } + else if(type_tag == OBJECT_ID) + { + OID oid; + data.decode(oid); + + std::string out = 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) + { + BigInt number; + + if(type_tag == INTEGER) + data.decode(number); + else if(type_tag == ENUMERATED) + data.decode(number, ENUMERATED, class_tag); + + std::vector<byte> rep; + + /* If it's small, it's probably a number, not a hash */ + if(number.bits() <= 20) + rep = BigInt::encode(number, BigInt::Decimal); + else + rep = BigInt::encode(number, BigInt::Hexadecimal); + + std::string str; + for(size_t i = 0; i != rep.size(); ++i) + str += static_cast<char>(rep[i]); + + emit(type_name(type_tag), level, length, str); + } + else if(type_tag == BOOLEAN) + { + bool boolean; + data.decode(boolean); + emit(type_name(type_tag), + level, length, (boolean ? "true" : "false")); + } + else if(type_tag == NULL_TAG) + { + emit(type_name(type_tag), level, length); + } + else if(type_tag == OCTET_STRING) + { + std::vector<byte> bits; + data.decode(bits, type_tag); + + try + { + BER_Decoder inner(bits); + decode(inner, level + 1); + } + catch(...) + { + emit(type_name(type_tag), level, length, + url_encode(bits)); + } + } + else if(type_tag == BIT_STRING) + { + std::vector<byte> bits; + data.decode(bits, type_tag); + + std::vector<bool> bit_set; + + for(size_t i = 0; i != bits.size(); ++i) + for(size_t j = 0; j != 8; ++j) + { + const bool bit = static_cast<bool>((bits[bits.size()-i-1] >> (7-j)) & 1); + bit_set.push_back(bit); + } + + std::string bit_str; + for(size_t i = 0; i != bit_set.size(); ++i) + { + bool the_bit = bit_set[bit_set.size()-i-1]; + + if(!the_bit && bit_str.size() == 0) + continue; + bit_str += (the_bit ? "1" : "0"); + } + + 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) + { + 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)); + } + else + { + emit(type_name(type_tag), level, length, str.iso_8859()); + } + } + else if(type_tag == UTC_TIME || type_tag == GENERALIZED_TIME) + { + X509_Time time; + data.decode(time); + emit(type_name(type_tag), level, length, time.readable_string()); + } + else + { + std::cout << "Unknown ASN.1 tag class=" + << static_cast<int>(class_tag) + << " type=" + << static_cast<int>(type_tag) << std::endl; + } + + obj = decoder.get_next_object(); + } + } + +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]); + + if(!PEM_Code::matches(in)) + { + 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; + } + + return 0; + } + +REGISTER_APP(asn1); + +} + +#endif // BOTAN_HAS_ASN1 && BOTAN_HAS_PEM_CODEC diff --git a/src/cli/base64.cpp b/src/cli/base64.cpp new file mode 100644 index 000000000..d2a9a1853 --- /dev/null +++ b/src/cli/base64.cpp @@ -0,0 +1,97 @@ +/* +* 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 new file mode 100644 index 000000000..81f7c536e --- /dev/null +++ b/src/cli/bcrypt.cpp @@ -0,0 +1,49 @@ +/* +* (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/ca.cpp b/src/cli/ca.cpp new file mode 100644 index 000000000..fb6d9582a --- /dev/null +++ b/src/cli/ca.cpp @@ -0,0 +1,77 @@ +/* +* (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/cert_verify.cpp b/src/cli/cert_verify.cpp new file mode 100644 index 000000000..7a1bec983 --- /dev/null +++ b/src/cli/cert_verify.cpp @@ -0,0 +1,54 @@ +/* +* 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() <= 2) + { + std::cout << "Usage: " << args[0] << " subject.pem [CA certificates...]" << 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/compress.cpp b/src/cli/compress.cpp new file mode 100644 index 000000000..93bc76eb4 --- /dev/null +++ b/src/cli/compress.cpp @@ -0,0 +1,126 @@ +/* +* (C) 2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "apps.h" + +#if defined(BOTAN_HAS_COMPRESSION) + +#include <botan/compression.h> +#include <fstream> + +namespace { + +void do_compress(Transform& comp, std::ifstream& in, std::ostream& out) + { + secure_vector<byte> buf; + + comp.start(); + + while(in.good()) + { + buf.resize(64*1024); + in.read(reinterpret_cast<char*>(&buf[0]), buf.size()); + buf.resize(in.gcount()); + + comp.update(buf); + + out.write(reinterpret_cast<const char*>(&buf[0]), buf.size()); + } + + buf.clear(); + comp.finish(buf); + out.write(reinterpret_cast<const char*>(&buf[0]), buf.size()); + } + +int compress(const std::vector<std::string> &args) + { + if(args.size() != 2 && args.size() != 3 && 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); + + if(!in.good()) + { + std::cout << "Couldn't read " << in_file << std::endl; + return 1; + } + + const std::string suffix = args.size() >= 3 ? args[2] : "gz"; + const size_t level = args.size() >= 4 ? to_u32bit(args[3]) : 9; + + std::unique_ptr<Transform> compress(make_compressor(suffix, level)); + + if(!compress) + { + std::cout << suffix << " compression not supported" << std::endl; + return 1; + } + + const std::string out_file = in_file + "." + suffix; + std::ofstream out(out_file); + + do_compress(*compress, in, out); + + return 0; + } + +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 std::runtime_error("No extension detected in filename '" + in_file + "'"); + + out_file = in_file.substr(0, last_dot); + suffix = in_file.substr(last_dot+1, std::string::npos); + } + +int uncompress(const std::vector<std::string> &args) + { + if(args.size() != 2) + { + std::cout << "Usage: " << args[0] << " <file>" << std::endl; + return 1; + } + + const std::string in_file = args[1]; + std::ifstream in(in_file); + + if(!in.good()) + { + std::cout << "Couldn't read '" << args[1] << "'" << std::endl; + return 1; + } + + std::string out_file, suffix; + parse_extension(in_file, out_file, suffix); + + std::ofstream out(out_file); + + std::unique_ptr<Transform> decompress(make_decompressor(suffix)); + + if(!decompress) + { + std::cout << suffix << " decompression not supported" << std::endl; + return 1; + } + + do_compress(*decompress, in, out); + + return 0; + } + +REGISTER_APP(compress); +REGISTER_APP(uncompress); + +} + +#endif // BOTAN_HAS_COMPRESSION diff --git a/src/cli/credentials.h b/src/cli/credentials.h new file mode 100644 index 000000000..06349657d --- /dev/null +++ b/src/cli/credentials.h @@ -0,0 +1,158 @@ +/* +* (C) 2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef EXAMPLE_CREDENTIALS_MANAGER_H__ +#define EXAMPLE_CREDENTIALS_MANAGER_H__ + +#include <botan/pkcs8.h> +#include <botan/credentials_manager.h> +#include <botan/x509self.h> +#include <iostream> +#include <fstream> +#include <memory> + +inline bool value_exists(const std::vector<std::string>& vec, + const std::string& val) + { + for(size_t i = 0; i != vec.size(); ++i) + if(vec[i] == val) + return true; + return false; + } + +class Basic_Credentials_Manager : public Credentials_Manager + { + public: + Basic_Credentials_Manager() + { + load_certstores(); + } + + Basic_Credentials_Manager(RandomNumberGenerator& rng, + const std::string& server_crt, + const std::string& server_key) + { + Certificate_Info cert; + + cert.key.reset(PKCS8::load_key(server_key, rng)); + + DataSource_Stream in(server_crt); + while(!in.end_of_data()) + { + try + { + cert.certs.push_back(X509_Certificate(in)); + } + catch(std::exception& e) + { + + } + } + + // TODO: attempt to validate chain ourselves + + m_creds.push_back(cert); + } + + void load_certstores() + { + try + { + // TODO: make path configurable + const std::vector<std::string> paths = { "/usr/share/ca-certificates" }; + + for(auto&& path : paths) + { + std::shared_ptr<Certificate_Store> cs(new Certificate_Store_In_Memory(path)); + m_certstores.push_back(cs); + } + } + catch(std::exception& e) + { + //std::cout << e.what() << "\n"; + } + } + + std::vector<Botan::Certificate_Store*> + trusted_certificate_authorities(const std::string& type, + const std::string& /*hostname*/) override + { + std::vector<Botan::Certificate_Store*> v; + + // don't ask for client certs + if(type == "tls-server") + return v; + + for(auto&& cs : m_certstores) + v.push_back(cs.get()); + + return v; + } + + 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::cout << e.what() << std::endl; + //throw; + } + } + + std::vector<X509_Certificate> cert_chain( + const std::vector<std::string>& algos, + const std::string& type, + const std::string& hostname) override + { + BOTAN_UNUSED(type); + + for(auto&& i : m_creds) + { + if(std::find(algos.begin(), algos.end(), i.key->algo_name()) == algos.end()) + continue; + + if(hostname != "" && !i.certs[0].matches_dns_name(hostname)) + continue; + + return i.certs; + } + + return std::vector<X509_Certificate>(); + } + + Private_Key* private_key_for(const X509_Certificate& cert, + const std::string& /*type*/, + const std::string& /*context*/) override + { + for(auto&& i : m_creds) + { + if(cert == i.certs[0]) + return i.key.get(); + } + + return nullptr; + } + + private: + struct Certificate_Info + { + std::vector<X509_Certificate> certs; + std::shared_ptr<Private_Key> key; + }; + + std::vector<Certificate_Info> m_creds; + std::vector<std::shared_ptr<Certificate_Store>> m_certstores; + }; + +#endif diff --git a/src/cli/dl_group.cpp b/src/cli/dl_group.cpp new file mode 100644 index 000000000..e9a4f3fd4 --- /dev/null +++ b/src/cli/dl_group.cpp @@ -0,0 +1,85 @@ +/* +* (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 new file mode 100644 index 000000000..03aede585 --- /dev/null +++ b/src/cli/dsa_sign.cpp @@ -0,0 +1,85 @@ +/* +* (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 new file mode 100644 index 000000000..64d60a5cf --- /dev/null +++ b/src/cli/dsa_ver.cpp @@ -0,0 +1,91 @@ +/* +* (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 new file mode 100644 index 000000000..d2c0a2df5 --- /dev/null +++ b/src/cli/factor.cpp @@ -0,0 +1,160 @@ +/* +* (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 new file mode 100644 index 000000000..97ca34b24 --- /dev/null +++ b/src/cli/fpe.cpp @@ -0,0 +1,154 @@ +/* +* (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> +#include <stdexcept> + +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 new file mode 100644 index 000000000..35adb9711 --- /dev/null +++ b/src/cli/fuzzer.cpp @@ -0,0 +1,141 @@ +/* +* (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 new file mode 100644 index 000000000..7b7e14932 --- /dev/null +++ b/src/cli/getopt.cpp @@ -0,0 +1,53 @@ +/* +* (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]; + + if(arg == "help" || arg == "--help" || arg == "-h") + return help(std::cout, appname); + + 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 new file mode 100644 index 000000000..f683159d0 --- /dev/null +++ b/src/cli/getopt.h @@ -0,0 +1,107 @@ +/* +* (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 <stdexcept> +#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 new file mode 100644 index 000000000..81a72ca17 --- /dev/null +++ b/src/cli/hash.cpp @@ -0,0 +1,63 @@ +/* +* (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] << " digest <filenames>" << 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 new file mode 100644 index 000000000..3cfd0ef61 --- /dev/null +++ b/src/cli/implementation/speed.h @@ -0,0 +1,30 @@ +/* +* (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 new file mode 100644 index 000000000..a7a344bef --- /dev/null +++ b/src/cli/implementation/speed_prime.cpp @@ -0,0 +1,96 @@ +/* +* (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 new file mode 100644 index 000000000..2ff49bd15 --- /dev/null +++ b/src/cli/implementation/speed_public_key.cpp @@ -0,0 +1,888 @@ +/* +* (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 new file mode 100644 index 000000000..2db5cdd70 --- /dev/null +++ b/src/cli/implementation/speed_transform.cpp @@ -0,0 +1,67 @@ +/* +* (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 new file mode 100644 index 000000000..14e55316b --- /dev/null +++ b/src/cli/implementation/timer.cpp @@ -0,0 +1,60 @@ +/* +* (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 new file mode 100644 index 000000000..ac5bd5cef --- /dev/null +++ b/src/cli/implementation/timer.h @@ -0,0 +1,55 @@ +/* +* (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 new file mode 100644 index 000000000..71fec730b --- /dev/null +++ b/src/cli/is_prime.cpp @@ -0,0 +1,50 @@ +/* +* (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 new file mode 100644 index 000000000..168b27a4a --- /dev/null +++ b/src/cli/keygen.cpp @@ -0,0 +1,128 @@ +/* +* (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 new file mode 100644 index 000000000..8d229ce0e --- /dev/null +++ b/src/cli/main.cpp @@ -0,0 +1,187 @@ +/* +* (C) 2009,2014 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 <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" + +namespace { + +int help(const std::vector<std::string> &args) + { + std::cout << "Usage: " << args[0] << " [subcommand] [subcommand-options]" << std::endl; + + std::set<std::string> apps = AppRegistrations::instance().all_appnames(); + + std::cout << "Available commands:" << std::endl; + + 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; + } + +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); + + 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())); + + std::cerr << "Unknown command " << cmd << std::endl; + return help(args); + } + catch(std::exception& e) + { + std::cerr << e.what() << std::endl; + return 1; + } + catch(...) + { + std::cerr << "Unknown exception caught" << std::endl; + return 1; + } + + return 0; + } diff --git a/src/cli/mce.cpp b/src/cli/mce.cpp new file mode 100644 index 000000000..226f21e9c --- /dev/null +++ b/src/cli/mce.cpp @@ -0,0 +1,124 @@ +/* +* (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 new file mode 100644 index 000000000..e5b42b076 --- /dev/null +++ b/src/cli/ocsp.cpp @@ -0,0 +1,53 @@ +/* +* (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 new file mode 100644 index 000000000..106fe2c24 --- /dev/null +++ b/src/cli/pkcs10.cpp @@ -0,0 +1,64 @@ +/* +* (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 new file mode 100644 index 000000000..7bc1c2561 --- /dev/null +++ b/src/cli/pkcs8.cpp @@ -0,0 +1,76 @@ +/* +* (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 new file mode 100644 index 000000000..82efa75d2 --- /dev/null +++ b/src/cli/prime.cpp @@ -0,0 +1,48 @@ +/* +* (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) + { + 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/rng.cpp b/src/cli/rng.cpp new file mode 100644 index 000000000..3fe8719ce --- /dev/null +++ b/src/cli/rng.cpp @@ -0,0 +1,68 @@ +/* +* (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() == 1) + { + 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 new file mode 100644 index 000000000..2c43f7acc --- /dev/null +++ b/src/cli/self_sig.cpp @@ -0,0 +1,84 @@ +/* +* (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 new file mode 100644 index 000000000..e8d30c6f1 --- /dev/null +++ b/src/cli/speed.cpp @@ -0,0 +1,214 @@ +/* +* (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 new file mode 100644 index 000000000..7f74e1a37 --- /dev/null +++ b/src/cli/tls_client.cpp @@ -0,0 +1,294 @@ +/* +* (C) 2014 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "apps.h" + +#if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_SOCKETS) + +#include <botan/tls_client.h> +#include <botan/pkcs8.h> +#include <botan/hex.h> + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + #include <botan/tls_session_manager_sqlite.h> +#endif + +#include <string> +#include <memory> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#if !defined(MSG_NOSIGNAL) + #define MSG_NOSIGNAL 0 +#endif + +#include "credentials.h" + +using namespace Botan; + +using namespace std::placeholders; + +namespace { + +int connect_to_host(const std::string& host, u16bit port, bool tcp) + { + hostent* host_addr = ::gethostbyname(host.c_str()); + + if(!host_addr) + throw std::runtime_error("gethostbyname failed for " + host); + + if(host_addr->h_addrtype != AF_INET) // FIXME + throw std::runtime_error(host + " has IPv6 address, not supported"); + + int type = 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); + + ::memcpy(&socket_info.sin_addr, + host_addr->h_addr, + host_addr->h_length); + + socket_info.sin_addr = *reinterpret_cast<struct in_addr*>(host_addr->h_addr); // FIXME + + if(::connect(fd, (sockaddr*)&socket_info, sizeof(struct sockaddr)) != 0) + { + ::close(fd); + throw std::runtime_error("connect failed"); + } + + return fd; + } + +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; + + if(!session.session_ticket().empty()) + std::cout << "Session ticket " << hex_encode(session.session_ticket()) << std::endl; + + return true; + } + +void dgram_socket_write(int sockfd, const byte buf[], size_t length) + { + int r = send(sockfd, buf, length, MSG_NOSIGNAL); + + if(r == -1) + throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); + } + +void stream_socket_write(int sockfd, const byte buf[], size_t length) + { + size_t offset = 0; + + while(length) + { + ssize_t sent = ::send(sockfd, (const char*)buf + offset, + length, MSG_NOSIGNAL); + + if(sent == -1) + { + if(errno == EINTR) + sent = 0; + else + throw std::runtime_error("Socket write failed errno=" + std::to_string(errno)); + } + + offset += sent; + length -= sent; + } + } + +bool got_alert = false; + +void alert_received(TLS::Alert alert, const byte [], size_t ) + { + std::cout << "Alert: " << alert.type_string() << std::endl; + got_alert = true; + } + +void process_data(const byte buf[], size_t buf_size) + { + for(size_t i = 0; i != buf_size; ++i) + std::cout << buf[i]; + } + +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; + } + + try + { + AutoSeeded_RNG rng; + TLS::Policy policy; + +#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 + + Basic_Credentials_Manager creds; + + 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"; + + const bool use_tcp = (transport == "tcp"); + + const std::vector<std::string> protocols_to_offer = { "test/9.9", "http/1.1", "echo/9.1" }; + + int sockfd = connect_to_host(host, port, use_tcp); + + auto socket_write = + use_tcp ? + std::bind(stream_socket_write, sockfd, _1, _2) : + std::bind(dgram_socket_write, sockfd, _1, _2); + + auto version = policy.latest_supported_version(!use_tcp); + + 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); + + bool first_active = true; + + while(!client.is_closed()) + { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sockfd, &readfds); + + if(client.is_active()) + { + FD_SET(STDIN_FILENO, &readfds); + if(first_active && !protocols_to_offer.empty()) + { + std::string app = client.application_protocol(); + if(app != "") + std::cout << "Server choose protocol: " << client.application_protocol() << std::endl; + first_active = false; + } + } + + struct timeval timeout = { 1, 0 }; + + ::select(sockfd + 1, &readfds, nullptr, nullptr, &timeout); + + if(FD_ISSET(sockfd, &readfds)) + { + byte buf[4*1024] = { 0 }; + + ssize_t got = ::read(sockfd, buf, sizeof(buf)); + + 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; + } + + client.received_data(buf, got); + } + + if(FD_ISSET(STDIN_FILENO, &readfds)) + { + byte buf[1024] = { 0 }; + ssize_t got = read(STDIN_FILENO, buf, sizeof(buf)); + + if(got == 0) + { + std::cout << "EOF on stdin" << std::endl; + client.close(); + break; + } + else if(got == -1) + { + std::cout << "Stdin error: " << errno << " " << strerror(errno) << std::endl; + continue; + } + + 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); + } + + if(client.timeout_check()) + { + std::cout << "Timeout detected" << std::endl; + } + } + + ::close(sockfd); + } + catch(std::exception& e) + { + std::cout << "Exception: " << e.what() << std::endl; + return 1; + } + return 0; + } + +REGISTER_APP(tls_client); + +} + +#endif diff --git a/src/cli/tls_proxy.cpp b/src/cli/tls_proxy.cpp new file mode 100644 index 000000000..5071cb8bb --- /dev/null +++ b/src/cli/tls_proxy.cpp @@ -0,0 +1,459 @@ +/* +* TLS Server Proxy +* (C) 2014,2015 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "apps.h" + +#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_BOOST_ASIO) + +#include <iostream> +#include <string> +#include <vector> +#include <thread> + +#define _GLIBCXX_HAVE_GTHR_DEFAULT +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/enable_shared_from_this.hpp> + +#include <botan/tls_server.h> +#include <botan/x509cert.h> +#include <botan/pkcs8.h> +#include <botan/auto_rng.h> + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + #include <botan/tls_session_manager_sqlite.h> +#endif + +#include "credentials.h" + +using boost::asio::ip::tcp; + +namespace Botan { + +namespace { + +inline void log_exception(const char* where, const std::exception& e) + { + std::cout << where << ' ' << e.what() << std::endl; + } + +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) + { + //std::cout << where << ' ' << hex_encode(buf, buf_len) << std::endl; + } + +void log_text_message(const char* where, const byte buf[], size_t buf_len) + { + //const char* c = reinterpret_cast<const char*>(buf); + //std::cout << where << ' ' << std::string(c, c + buf_len) << std::endl; + } + +class tls_proxy_session : public boost::enable_shared_from_this<tls_proxy_session> + { + public: + enum { readbuf_size = 4 * 1024 }; + + 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, + tcp::resolver::iterator endpoints) + { + return pointer( + new tls_proxy_session( + io, + session_manager, + credentials, + policy, + endpoints) + ); + } + + tcp::socket& client_socket() { return m_client_socket; } + + void start() + { + m_c2p.resize(readbuf_size); + + client_read(boost::system::error_code(), 0); // start read loop + } + + void stop() + { + m_tls.close(); + m_client_socket.close(); + m_server_socket.close(); + } + + private: + tls_proxy_session(boost::asio::io_service& io, + TLS::Session_Manager& session_manager, + Credentials_Manager& credentials, + TLS::Policy& policy, + tcp::resolver::iterator endpoints) : + m_strand(io), + m_server_endpoints(endpoints), + m_client_socket(io), + m_server_socket(io), + m_tls(boost::bind(&tls_proxy_session::tls_proxy_write_to_client, this, _1, _2), + boost::bind(&tls_proxy_session::tls_client_write_to_proxy, this, _1, _2), + boost::bind(&tls_proxy_session::tls_alert_cb, this, _1, _2, _3), + boost::bind(&tls_proxy_session::tls_handshake_complete, this, _1), + session_manager, + credentials, + policy, + m_rng) + { + } + + void client_read(const boost::system::error_code& error, + size_t bytes_transferred) + { + if(error) + { + log_error("Read failed", error); + stop(); + return; + } + + try + { + if(!m_tls.is_active()) + log_binary_message("From client", &m_c2p[0], bytes_transferred); + m_tls.received_data(&m_c2p[0], bytes_transferred); + } + catch(std::exception& e) + { + log_exception("TLS connection failed", e); + stop(); + return; + } + + m_client_socket.async_read_some( + boost::asio::buffer(&m_c2p[0], m_c2p.size()), + m_strand.wrap(boost::bind(&tls_proxy_session::client_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + void handle_client_write_completion(const boost::system::error_code& error) + { + if(error) + { + log_error("Client write", error); + stop(); + return; + } + + m_p2c.clear(); + tls_proxy_write_to_client(nullptr, 0); // initiate another write if needed + } + + void handle_server_write_completion(const boost::system::error_code& error) + { + if(error) + { + log_error("Server write", error); + stop(); + return; + } + + m_p2s.clear(); + proxy_write_to_server(nullptr, 0); // initiate another write if needed + } + + void tls_client_write_to_proxy(const byte 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) + { + if(buf_len > 0) + m_p2c_pending.insert(m_p2c_pending.end(), buf, buf + buf_len); + + // no write now active and we still have output pending + if(m_p2c.empty() && !m_p2c_pending.empty()) + { + std::swap(m_p2c_pending, m_p2c); + + //log_binary_message("To Client", &m_p2c[0], m_p2c.size()); + + boost::asio::async_write( + m_client_socket, + boost::asio::buffer(&m_p2c[0], m_p2c.size()), + m_strand.wrap(boost::bind( + &tls_proxy_session::handle_client_write_completion, + shared_from_this(), + boost::asio::placeholders::error))); + } + } + + void proxy_write_to_server(const byte buf[], size_t buf_len) + { + if(buf_len > 0) + m_p2s_pending.insert(m_p2s_pending.end(), buf, buf + buf_len); + + // no write now active and we still have output pending + if(m_p2s.empty() && !m_p2s_pending.empty()) + { + std::swap(m_p2s_pending, m_p2s); + + log_text_message("To Server", &m_p2s[0], m_p2s.size()); + + boost::asio::async_write( + m_server_socket, + boost::asio::buffer(&m_p2s[0], m_p2s.size()), + m_strand.wrap(boost::bind( + &tls_proxy_session::handle_server_write_completion, + shared_from_this(), + boost::asio::placeholders::error))); + } + } + + void server_read(const boost::system::error_code& error, + size_t bytes_transferred) + { + if(error) + { + log_error("Server read failed", error); + stop(); + return; + } + + try + { + if(bytes_transferred) + { + log_text_message("Server to client", &m_s2p[0], m_s2p.size()); + log_binary_message("Server to client", &m_s2p[0], m_s2p.size()); + m_tls.send(&m_s2p[0], bytes_transferred); + } + } + catch(std::exception& e) + { + log_exception("TLS connection failed", e); + stop(); + return; + } + + m_s2p.resize(readbuf_size); + + m_server_socket.async_read_some( + boost::asio::buffer(&m_s2p[0], m_s2p.size()), + m_strand.wrap(boost::bind(&tls_proxy_session::server_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + bool tls_handshake_complete(const TLS::Session& session) + { + //std::cout << "Handshake from client complete" << std::endl; + + m_hostname = session.server_info().hostname(); + + if(m_hostname != "") + std::cout << "Client requested hostname '" << m_hostname << "'" << std::endl; + + async_connect(m_server_socket, m_server_endpoints, + [this](boost::system::error_code ec, tcp::resolver::iterator endpoint) + { + if(ec) + { + log_error("Server connection", ec); + return; + } + + server_read(boost::system::error_code(), 0); // start read loop + proxy_write_to_server(nullptr, 0); + }); + return true; + } + + void tls_alert_cb(TLS::Alert alert, const byte[], size_t) + { + if(alert.type() == TLS::Alert::CLOSE_NOTIFY) + { + m_tls.close(); + return; + } + else + std::cout << "Alert " << alert.type_string() << std::endl; + } + + boost::asio::io_service::strand m_strand; + + tcp::resolver::iterator m_server_endpoints; + + tcp::socket m_client_socket; + tcp::socket m_server_socket; + + AutoSeeded_RNG m_rng; + 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<byte> m_s2p; + std::vector<byte> m_p2s; + std::vector<byte> m_p2s_pending; + }; + +class tls_proxy_server + { + public: + typedef tls_proxy_session session; + + 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) : + m_acceptor(io, tcp::endpoint(tcp::v4(), port)), + m_server_endpoints(endpoints), + m_creds(creds), + m_policy(policy), + m_session_manager(session_mgr) + { + session::pointer new_session = make_session(); + + m_acceptor.async_accept( + new_session->client_socket(), + boost::bind( + &tls_proxy_server::handle_accept, + this, + new_session, + boost::asio::placeholders::error) + ); + } + + private: + session::pointer make_session() + { + return session::create( + m_acceptor.get_io_service(), + m_session_manager, + m_creds, + m_policy, + m_server_endpoints + ); + } + + void handle_accept(session::pointer new_session, + const boost::system::error_code& error) + { + if (!error) + { + new_session->start(); + + new_session = make_session(); + + m_acceptor.async_accept( + new_session->client_socket(), + boost::bind( + &tls_proxy_server::handle_accept, + this, + new_session, + boost::asio::placeholders::error) + ); + } + } + + tcp::acceptor m_acceptor; + tcp::resolver::iterator m_server_endpoints; + + Credentials_Manager& m_creds; + TLS::Policy& m_policy; + 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) + { + if(args.size() != 6) + { + std::cout << "Usage: " << args[0] << " listen_port target_host target_port server_cert server_key" << std::endl; + return 1; + } + + 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 = args[4]; + const std::string server_key = args[5]; + + const size_t num_threads = choose_thread_count(); // make configurable + + AutoSeeded_RNG rng; + Basic_Credentials_Manager creds(rng, server_crt, server_key); + + TLS::Policy policy; // TODO: Read policy from text file + + try + { + boost::asio::io_service io; + + tcp::resolver resolver(io); + auto server_endpoint_iterator = resolver.resolve({ target, target_port }); + +#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 + + tls_proxy_server server(io, listen_port, server_endpoint_iterator, creds, policy, sessions); + + std::vector<std::shared_ptr<std::thread>> threads; + + for(size_t i = 2; i <= num_threads; ++i) + threads.push_back(std::make_shared<std::thread>([&io]() { io.run(); })); + + io.run(); + + for (size_t i = 0; i < threads.size(); ++i) + threads[i]->join(); + } + catch (std::exception& e) + { + std::cerr << e.what() << std::endl; + } + + return 0; + } + +} + +} + +REGISTER_APP(tls_proxy); + +#endif diff --git a/src/cli/tls_server.cpp b/src/cli/tls_server.cpp new file mode 100644 index 000000000..ea68208b6 --- /dev/null +++ b/src/cli/tls_server.cpp @@ -0,0 +1,270 @@ +/* +* TLS echo server using BSD sockets +* (C) 2014 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "apps.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 "credentials.h" + +using namespace Botan; + +using namespace std::placeholders; + +#include <list> + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#if !defined(MSG_NOSIGNAL) + #define MSG_NOSIGNAL 0 +#endif + +namespace { + +int make_server_socket(bool is_tcp, u16bit 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"); + } + } + + return fd; + } + +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; + + if(!session.session_ticket().empty()) + std::cout << "Session ticket " << hex_encode(session.session_ticket()) << std::endl; + + return true; + } + +void dgram_socket_write(int sockfd, const byte 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; + } + +void stream_socket_write(int sockfd, const byte buf[], size_t length) + { + while(length) + { + 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; + } + } + +void alert_received(TLS::Alert alert, const byte[], size_t) + { + std::cout << "Alert: " << alert.type_string() << std::endl; + } + +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 + { + int fd; + + if(is_tcp) + fd = ::accept(server_fd, nullptr, nullptr); + else + { + struct sockaddr_in from; + socklen_t from_len = sizeof(sockaddr_in); + + if(::recvfrom(server_fd, nullptr, 0, MSG_PEEK, + (struct sockaddr*)&from, &from_len) != 0) + throw std::runtime_error("Could not peek next packet"); + + if(::connect(server_fd, (struct sockaddr*)&from, from_len) != 0) + throw std::runtime_error("Could not connect UDP socket"); + + fd = server_fd; + } + + std::cout << "New connection received" << std::endl; + + 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) + { + for(size_t i = 0; i != input_len; ++i) + { + 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); + + while(!server.is_closed()) + { + byte buf[4*1024] = { 0 }; + ssize_t got = ::read(fd, buf, sizeof(buf)); + + if(got == -1) + { + std::cout << "Error in socket read - " << strerror(errno) << std::endl; + break; + } + + if(got == 0) + { + std::cout << "EOF on socket" << std::endl; + break; + } + + server.received_data(buf, got); + + while(server.is_active() && !pending_output.empty()) + { + std::string s = pending_output.front(); + pending_output.pop_front(); + server.send(s); + + if(s == "quit\n") + server.close(); + } + } + + if(is_tcp) + ::close(fd); + + } + catch(std::exception& e) + { + std::cout << "Connection problem: " << e.what() << std::endl; + return 1; + } + } + } + catch(std::exception& e) + { + std::cout << e.what() << std::endl; + return 1; + } + + return 0; + } + +REGISTER_APP(tls_server); + +} + +#endif diff --git a/src/cli/x509print.cpp b/src/cli/x509print.cpp new file mode 100644 index 000000000..e583c91c2 --- /dev/null +++ b/src/cli/x509print.cpp @@ -0,0 +1,32 @@ +/* +* (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() != 1) + { + 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 |