diff options
Diffstat (limited to 'src/cmd')
-rw-r--r-- | src/cmd/apps.cpp | 27 | ||||
-rw-r--r-- | src/cmd/apps.h | 34 | ||||
-rw-r--r-- | src/cmd/asn1.cpp | 319 | ||||
-rw-r--r-- | src/cmd/base64.cpp | 85 | ||||
-rw-r--r-- | src/cmd/bcrypt.cpp | 32 | ||||
-rw-r--r-- | src/cmd/bzip.cpp | 115 | ||||
-rw-r--r-- | src/cmd/ca.cpp | 63 | ||||
-rw-r--r-- | src/cmd/cert_verify.cpp | 43 | ||||
-rw-r--r-- | src/cmd/credentials.h | 294 | ||||
-rw-r--r-- | src/cmd/dsa_sign.cpp | 71 | ||||
-rw-r--r-- | src/cmd/dsa_ver.cpp | 84 | ||||
-rw-r--r-- | src/cmd/factor.cpp | 153 | ||||
-rw-r--r-- | src/cmd/fpe.cpp | 143 | ||||
-rw-r--r-- | src/cmd/getopt.h | 129 | ||||
-rw-r--r-- | src/cmd/hash.cpp | 58 | ||||
-rw-r--r-- | src/cmd/keygen.cpp | 53 | ||||
-rw-r--r-- | src/cmd/main.cpp | 114 | ||||
-rw-r--r-- | src/cmd/ocsp.cpp | 38 | ||||
-rw-r--r-- | src/cmd/pkcs10.cpp | 48 | ||||
-rw-r--r-- | src/cmd/read_ssh.cpp | 134 | ||||
-rw-r--r-- | src/cmd/self_sig.cpp | 70 | ||||
-rw-r--r-- | src/cmd/speed/pk_bench.cpp | 755 | ||||
-rw-r--r-- | src/cmd/speed/speed.cpp | 229 | ||||
-rw-r--r-- | src/cmd/speed/speed.h | 25 | ||||
-rw-r--r-- | src/cmd/speed/timer.cpp | 64 | ||||
-rw-r--r-- | src/cmd/speed/timer.h | 50 | ||||
-rw-r--r-- | src/cmd/tls_client.cpp | 263 | ||||
-rw-r--r-- | src/cmd/tls_server.cpp | 262 | ||||
-rw-r--r-- | src/cmd/tls_server_asio.cpp | 315 | ||||
-rw-r--r-- | src/cmd/x509print.cpp | 17 |
30 files changed, 4087 insertions, 0 deletions
diff --git a/src/cmd/apps.cpp b/src/cmd/apps.cpp new file mode 100644 index 000000000..120457cb1 --- /dev/null +++ b/src/cmd/apps.cpp @@ -0,0 +1,27 @@ +#include "apps.h" + +int apps_main(const std::string& cmd, int argc, char* argv[]) + { +#define CALL_APP(cmdsym) \ + do { if(cmd == #cmdsym) { return cmdsym ##_main (argc - 1, argv + 1); } } while(0) + + CALL_APP(asn1); + CALL_APP(base64); + CALL_APP(bcrypt); + CALL_APP(bzip); + CALL_APP(ca); + CALL_APP(factor); + CALL_APP(fpe); + CALL_APP(hash); + CALL_APP(keygen); + CALL_APP(dsa_sign); + CALL_APP(dsa_verify); + CALL_APP(pkcs10); + CALL_APP(read_ssh); + CALL_APP(self_sig); + CALL_APP(tls_client); + CALL_APP(tls_server); + CALL_APP(x509); + + return -1; + } diff --git a/src/cmd/apps.h b/src/cmd/apps.h new file mode 100644 index 000000000..39665a720 --- /dev/null +++ b/src/cmd/apps.h @@ -0,0 +1,34 @@ + +#include <botan/auto_rng.h> +#include <botan/hex.h> +#include <iostream> + +#include "getopt.h" + +using namespace Botan; + +int apps_main(const std::string& cmd, int argc, char* argv[]); + +#define DEFINE_APP(cmd) int cmd ## _main(int argc, char* argv[]); + +DEFINE_APP(asn1); +DEFINE_APP(bcrypt); +DEFINE_APP(bzip); +DEFINE_APP(base64); +DEFINE_APP(ca); +DEFINE_APP(factor); +DEFINE_APP(fpe); +DEFINE_APP(hash); +DEFINE_APP(keygen); +DEFINE_APP(dsa_sign); +DEFINE_APP(dsa_verify); +DEFINE_APP(cert_verify); +DEFINE_APP(ocsp_check); +DEFINE_APP(pkcs10); +DEFINE_APP(read_ssh); +DEFINE_APP(self_sig); +DEFINE_APP(tls_client); +DEFINE_APP(tls_server); +DEFINE_APP(tls_server_asio); +DEFINE_APP(x509); +DEFINE_APP(speed); diff --git a/src/cmd/asn1.cpp b/src/cmd/asn1.cpp new file mode 100644 index 000000000..eeda6f809 --- /dev/null +++ b/src/cmd/asn1.cpp @@ -0,0 +1,319 @@ +#include "apps.h" + +#include <botan/bigint.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 <sstream> +#include <stdio.h> +#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 + +void decode(BER_Decoder&, size_t); +void emit(const std::string&, size_t, size_t, const std::string& = ""); +std::string type_name(ASN1_Tag); + +namespace { + +std::string url_encode(const std::vector<byte>& in) + { + std::ostringstream out; + + 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<char>(c) << std::dec; + } + return out.str(); + } + +} + +int asn1_main(int argc, char* argv[]) + { + if(argc != 2) + { + printf("Usage: %s <file>\n", argv[0]); + return 1; + } + + try { + DataSource_Stream in(argv[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) + { + printf("%s\n", e.what()); + return 1; + } + return 0; + } + +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() <= 16) + 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) + bit_set.push_back((bool)((bits[bits.size()-i-1] >> (7-j)) & 1)); + + 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 + fprintf(stderr, "Unknown tag: class=%02X, type=%02X\n", + class_tag, type_tag); + + obj = decoder.get_next_object(); + } + } + +void emit(const std::string& type, size_t level, size_t length, + const std::string& value) + { + const size_t LIMIT = 128; + const size_t BIN_LIMIT = 64; + + int written = 0; + written += printf(" d=%2d, l=%4d: ", (int)level, (int)length); + for(size_t i = INITIAL_LEVEL; i != level; ++i) + written += printf(" "); + written += printf("%s ", type.c_str()); + + 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(written % 2 == 0) printf(" "); + while(written < 50) written += printf(" "); + printf(":%s\n", value.c_str()); + } + else + printf("\n"); + } + +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)"; + } diff --git a/src/cmd/base64.cpp b/src/cmd/base64.cpp new file mode 100644 index 000000000..34711857c --- /dev/null +++ b/src/cmd/base64.cpp @@ -0,0 +1,85 @@ +/* +* Encode/decode base64 strings +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include "apps.h" +#include <fstream> +#include <iostream> +#include <string> +#include <vector> +#include <cstring> +#include <cstdlib> +#include <botan/b64_filt.h> +#include <botan/pipe.h> + +int base64_main(int argc, char* argv[]) + { + if(argc < 2) + { + std::cout << "Usage: " << argv[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\n"; + return 1; + } + + int column = 78; + bool wrap = false; + bool encoding = true; + std::vector<std::string> files; + + for(int j = 1; argv[j] != nullptr; j++) + { + std::string this_arg = argv[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(argv[j+1]) + { column = atoi(argv[j+1]); j++; } + else + { + std::cout << "No argument for -c option" << std::endl; + return 1; + } + } + else files.push_back(argv[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].c_str()); + + 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; + } diff --git a/src/cmd/bcrypt.cpp b/src/cmd/bcrypt.cpp new file mode 100644 index 000000000..2b0bfa132 --- /dev/null +++ b/src/cmd/bcrypt.cpp @@ -0,0 +1,32 @@ +#include "apps.h" +#include <botan/bcrypt.h> + +int bcrypt_main(int argc, char* argv[]) + { + if(argc == 2) + { + AutoSeeded_RNG rng; + + const std::string password = argv[1]; + + std::cout << generate_bcrypt(password, rng, 12) << "\n"; + return 0; + } + else if(argc == 3) + { + const std::string password = argv[1]; + const std::string hash = argv[2]; + + if(hash.length() != 60) + std::cout << "Note: bcrypt '" << hash << "' has wrong length and cannot be valid\n"; + + const bool ok = check_bcrypt(password, hash); + + std::cout << "Password is " << (ok ? "valid" : "NOT valid") << "\n"; + return (ok ? 0 : 1); + } + + std::cout << "Usage: " << argv[0] << " password\n" + << " " << argv[0] << " password passhash\n"; + return 1; + } diff --git a/src/cmd/bzip.cpp b/src/cmd/bzip.cpp new file mode 100644 index 000000000..3b8c7cdc2 --- /dev/null +++ b/src/cmd/bzip.cpp @@ -0,0 +1,115 @@ +/* +* Bzip2 Compression/Decompression +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include "apps.h" + +#include <string> +#include <cstring> +#include <vector> +#include <fstream> +#include <iostream> +#include <botan/botan.h> + +/* +* If Bzip2 isn't included, we know nothing works at compile time, but +* we wait to fail at runtime. Otherwise I would get 2-3 mails a month +* about how this was failing to compile (even with an informative +* #error message explaining the situation) because bzip2 wasn't +* included in the build. +*/ + +#if defined(BOTAN_HAS_COMPRESSOR_BZIP2) + #include <botan/bzip2.h> +#endif + +const std::string SUFFIX = ".bz2"; + +int bzip_main(int argc, char* argv[]) + { + if(argc < 2) + { + std::cout << "Usage: " << argv[0] + << " [-s] [-d] [-1...9] <filenames>" << std::endl; + return 1; + } + +#ifdef BOTAN_HAS_COMPRESSOR_BZIP2 + std::vector<std::string> files; + bool decompress = false, small = false; + int level = 9; + + for(int j = 1; argv[j] != 0; j++) + { + if(std::strcmp(argv[j], "-d") == 0) { decompress = true; continue; } + if(std::strcmp(argv[j], "-s") == 0) { small = true; continue; } + if(std::strcmp(argv[j], "-1") == 0) { level = 1; continue; } + if(std::strcmp(argv[j], "-2") == 0) { level = 2; continue; } + if(std::strcmp(argv[j], "-3") == 0) { level = 3; continue; } + if(std::strcmp(argv[j], "-4") == 0) { level = 4; continue; } + if(std::strcmp(argv[j], "-5") == 0) { level = 5; continue; } + if(std::strcmp(argv[j], "-6") == 0) { level = 6; continue; } + if(std::strcmp(argv[j], "-7") == 0) { level = 7; continue; } + if(std::strcmp(argv[j], "-8") == 0) { level = 8; continue; } + if(std::strcmp(argv[j], "-9") == 0) { level = 9; continue; } + files.push_back(argv[j]); + } + + try { + + Botan::Filter* bzip = 0; + if(decompress) + bzip = new Botan::Bzip_Decompression(small); + else + bzip = new Botan::Bzip_Compression(level); + + Botan::Pipe pipe(bzip); + + for(unsigned int j = 0; j != files.size(); j++) + { + std::string infile = files[j], outfile = files[j]; + if(!decompress) + outfile = outfile += SUFFIX; + else + outfile = outfile.replace(outfile.find(SUFFIX), + SUFFIX.length(), ""); + + std::ifstream in(infile.c_str(), std::ios::binary); + std::ofstream out(outfile.c_str(), std::ios::binary); + if(!in) + { + std::cout << "ERROR: could not read " << infile << std::endl; + continue; + } + if(!out) + { + std::cout << "ERROR: could not write " << outfile << std::endl; + continue; + } + + pipe.start_msg(); + in >> pipe; + pipe.end_msg(); + pipe.set_default_msg(j); + out << pipe; + + in.close(); + out.close(); + return 0; + } + } + catch(std::exception& e) + { + std::cout << "Exception caught: " << e.what() << std::endl; + return 1; + } +#else + + std::cout << "Sorry, support for bzip2 not compiled into Botan\n"; +#endif + + return 1; + } diff --git a/src/cmd/ca.cpp b/src/cmd/ca.cpp new file mode 100644 index 000000000..17d930358 --- /dev/null +++ b/src/cmd/ca.cpp @@ -0,0 +1,63 @@ + +#include "apps.h" +#include <botan/x509_ca.h> +using namespace Botan; + +#include <iostream> +#include <memory> +#include <chrono> + +int ca_main(int argc, char* argv[]) + { + if(argc != 5) + { + std::cout << "Usage: " << argv[0] << " <passphrase> " + << "<ca cert> <ca key> <pkcs10>" << std::endl; + return 1; + } + + try + { + const std::string arg_passphrase = argv[1]; + const std::string arg_ca_cert = argv[2]; + const std::string arg_ca_key = argv[3]; + const std::string arg_req_file = argv[4]; + + AutoSeeded_RNG rng; + + X509_Certificate ca_cert(arg_ca_cert); + + std::auto_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; + } diff --git a/src/cmd/cert_verify.cpp b/src/cmd/cert_verify.cpp new file mode 100644 index 000000000..154267fe1 --- /dev/null +++ b/src/cmd/cert_verify.cpp @@ -0,0 +1,43 @@ +/* +* Simple example of a certificate validation +* (C) 2010 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include "apps.h" +#include <botan/x509cert.h> +#include <botan/x509path.h> +#include <iostream> + +using namespace Botan; + +int cert_verify_main(int argc, char* argv[]) + { + if(argc <= 2) + { + std::cout << "Usage: " << argv[0] << " subject.pem [CA certificates...]\n"; + return 1; + } + + X509_Certificate subject_cert(argv[1]); + + Certificate_Store_In_Memory certs; + + for(size_t i = 2; argv[i]; ++i) + certs.add_certificate(X509_Certificate(argv[i])); + + Path_Validation_Restrictions restrictions; + + Path_Validation_Result result = + x509_path_validate(subject_cert, + restrictions, + certs); + + if(result.successful_validation()) + std::cout << "Certificate validated\n"; + else + std::cout << "Certificate did not validate - " << result.result_string() << "\n"; + + return 0; + } diff --git a/src/cmd/credentials.h b/src/cmd/credentials.h new file mode 100644 index 000000000..6c150f881 --- /dev/null +++ b/src/cmd/credentials.h @@ -0,0 +1,294 @@ + +#ifndef EXAMPLE_CREDENTIALS_MANAGER_H__ +#define EXAMPLE_CREDENTIALS_MANAGER_H__ + +#include <botan/credentials_manager.h> +#include <botan/x509self.h> +#include <botan/rsa.h> +#include <botan/dsa.h> +#include <botan/srp6.h> +#include <botan/srp6_files.h> +#include <botan/ecdsa.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 Credentials_Manager_Simple : public Botan::Credentials_Manager + { + public: + Credentials_Manager_Simple(Botan::RandomNumberGenerator& rng) : + rng(rng) + { + m_certstores.push_back(new Botan::Certificate_Store_In_Memory("/usr/share/ca-certificates")); + } + + std::string srp_identifier(const std::string& type, + const std::string& hostname) + { + if(type == "tls-client" && hostname == "srp-host") + return "user"; + return ""; + } + + bool attempt_srp(const std::string& type, + const std::string& hostname) + { + if(hostname == "srp-host" && (type == "tls-client" || type == "tls-server")) + return true; + return false; + } + + std::vector<Botan::Certificate_Store*> + trusted_certificate_authorities(const std::string& type, + const std::string& /*hostname*/) + { + // don't ask for client cert + if(type == "tls-server") + return std::vector<Botan::Certificate_Store*>(); + + return m_certstores; + } + + void verify_certificate_chain( + const std::string& type, + const std::string& purported_hostname, + const std::vector<Botan::X509_Certificate>& cert_chain) + { + try + { + Botan::Credentials_Manager::verify_certificate_chain(type, + purported_hostname, + cert_chain); + } + catch(std::exception& e) + { + std::cout << "Certificate verification failed - " << e.what() << " - but will ignore\n"; + } + } + + std::string srp_password(const std::string& type, + const std::string& hostname, + const std::string& identifier) + { + if(type == "tls-client" && hostname == "srp-host" && identifier == "user") + return "password"; + + return ""; + } + + bool srp_verifier(const std::string& /*type*/, + const std::string& context, + const std::string& identifier, + std::string& group_id, + Botan::BigInt& verifier, + std::vector<Botan::byte>& salt, + bool generate_fake_on_unknown) + { + + std::string pass = srp_password("tls-client", context, identifier); + if(pass == "") + { + if(!generate_fake_on_unknown) + return false; + + pass.resize(16); + rng.randomize(reinterpret_cast<byte*>(&pass[0]), pass.size()); + } + + group_id = "modp/srp/2048"; + + salt.resize(16); + rng.randomize(&salt[0], salt.size()); + + verifier = Botan::generate_srp6_verifier(identifier, + pass, + salt, + group_id, + "SHA-1"); + + return true; + } + + std::string psk_identity_hint(const std::string&, + const std::string&) + { + return ""; + } + + std::string psk_identity(const std::string&, const std::string&, + const std::string& identity_hint) + { + std::cout << "Server sent PSK identity_hint " << identity_hint << "\n"; + return "Client_identity"; + } + + Botan::SymmetricKey psk(const std::string& type, const std::string& context, + const std::string& identity) + { + if(type == "tls-server" && context == "session-ticket") + { + if(session_ticket_key.length() == 0) + session_ticket_key = Botan::SymmetricKey(rng, 32); + return session_ticket_key; + } + + if(identity == "Client_identity") + return Botan::SymmetricKey("b5a72e1387552e6dc10766dc0eda12961f5b21e17f98ef4c41e6572e53bd7527"); + if(identity == "lloyd") + return Botan::SymmetricKey("85b3c1b7dc62b507636ac767999c9630"); + + throw Botan::Internal_Error("No PSK set for " + identity); + } + + std::pair<Botan::X509_Certificate,Botan::Private_Key*> + load_or_make_cert(const std::string& hostname, + const std::string& key_type, + Botan::RandomNumberGenerator& rng) + { + using namespace Botan; + + const std::string key_fsname_prefix = hostname + "." + key_type + "."; + const std::string key_file_name = key_fsname_prefix + "key"; + const std::string cert_file_name = key_fsname_prefix + "crt"; + + try + { + X509_Certificate cert(cert_file_name); + Private_Key* key = PKCS8::load_key(key_file_name, rng); + + //std::cout << "Loaded existing key/cert from " << cert_file_name << " and " << key_file_name << "\n"; + + return std::make_pair(cert, key); + } + catch(...) {} + + // Failed. Instead, make a new one + + std::cout << "Creating new certificate for identifier '" << hostname << "'\n"; + + X509_Cert_Options opts; + + opts.common_name = hostname; + opts.country = "US"; + opts.email = "root@" + hostname; + opts.dns = hostname; + + std::unique_ptr<Private_Key> key; + if(key_type == "rsa") + key.reset(new RSA_PrivateKey(rng, 1024)); + else if(key_type == "dsa") + key.reset(new DSA_PrivateKey(rng, DL_Group("dsa/jce/1024"))); + else if(key_type == "ecdsa") + key.reset(new ECDSA_PrivateKey(rng, EC_Group("secp256r1"))); + else + throw std::runtime_error("Don't know what to do about key type '" + key_type + "'"); + + X509_Certificate cert = + X509::create_self_signed_cert(opts, *key, "SHA-1", rng); + + // Now save both + + std::cout << "Saving new " << key_type << " key to " << key_file_name << "\n"; + std::ofstream key_file(key_file_name.c_str()); + key_file << PKCS8::PEM_encode(*key, rng, ""); + key_file.close(); + + std::cout << "Saving new " << key_type << " cert to " << key_file_name << "\n"; + std::ofstream cert_file(cert_file_name.c_str()); + cert_file << cert.PEM_encode() << "\n"; + cert_file.close(); + + return std::make_pair(cert, key.release()); + } + + std::vector<Botan::X509_Certificate> cert_chain( + const std::vector<std::string>& cert_key_types, + const std::string& type, + const std::string& context) + { + using namespace Botan; + + std::vector<X509_Certificate> certs; + + try + { + if(type == "tls-server") + { + const std::string hostname = (context == "" ? "localhost" : context); + + if(hostname == "nosuchname") + return std::vector<Botan::X509_Certificate>(); + + for(auto i : certs_and_keys) + { + if(hostname != "" && !i.first.matches_dns_name(hostname)) + continue; + + if(!value_exists(cert_key_types, i.second->algo_name())) + continue; + + certs.push_back(i.first); + } + + if(!certs.empty()) + return certs; + + std::string key_name = ""; + + if(value_exists(cert_key_types, "RSA")) + key_name = "rsa"; + else if(value_exists(cert_key_types, "DSA")) + key_name = "dsa"; + else if(value_exists(cert_key_types, "ECDSA")) + key_name = "ecdsa"; + + std::pair<X509_Certificate, Private_Key*> cert_and_key = + load_or_make_cert(hostname, key_name, rng); + + certs_and_keys[cert_and_key.first] = cert_and_key.second; + certs.push_back(cert_and_key.first); + } + else if(type == "tls-client") + { + X509_Certificate cert("user-rsa.crt"); + Private_Key* key = PKCS8::load_key("user-rsa.key", rng); + + certs_and_keys[cert] = key; + certs.push_back(cert); + } + } + catch(std::exception& e) + { + std::cout << e.what() << "\n"; + } + + return certs; + } + + Botan::Private_Key* private_key_for(const Botan::X509_Certificate& cert, + const std::string& /*type*/, + const std::string& /*context*/) + { + return certs_and_keys[cert]; + } + + private: + Botan::RandomNumberGenerator& rng; + + Botan::SymmetricKey session_ticket_key; + + std::map<Botan::X509_Certificate, Botan::Private_Key*> certs_and_keys; + + std::vector<Botan::Certificate_Store*> m_certstores; + }; + +#endif diff --git a/src/cmd/dsa_sign.cpp b/src/cmd/dsa_sign.cpp new file mode 100644 index 000000000..308e68814 --- /dev/null +++ b/src/cmd/dsa_sign.cpp @@ -0,0 +1,71 @@ +#include "apps.h" +#include <iostream> +#include <iomanip> +#include <fstream> +#include <string> +#include <memory> + +#include <botan/pubkey.h> +#include <botan/dsa.h> +#include <botan/base64.h> +using namespace Botan; + +const std::string SUFFIX = ".sig"; + +int dsa_sign_main(int argc, char* argv[]) + { + if(argc != 4) + { + std::cout << "Usage: " << argv[0] << " keyfile messagefile passphrase" + << std::endl; + return 1; + } + + try { + std::string passphrase(argv[3]); + + std::ifstream message(argv[2], std::ios::binary); + if(!message) + { + std::cout << "Couldn't read the message file." << std::endl; + return 1; + } + + std::string outfile = argv[2] + SUFFIX; + std::ofstream sigfile(outfile.c_str()); + if(!sigfile) + { + std::cout << "Couldn't write the signature to " + << outfile << std::endl; + return 1; + } + + AutoSeeded_RNG rng; + + std::auto_ptr<PKCS8_PrivateKey> key( + PKCS8::load_key(argv[1], rng, passphrase) + ); + + DSA_PrivateKey* dsakey = dynamic_cast<DSA_PrivateKey*>(key.get()); + + if(!dsakey) + { + std::cout << "The loaded key is not a DSA key!\n"; + 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)) << "\n"; + } + catch(std::exception& e) + { + std::cout << "Exception caught: " << e.what() << std::endl; + } + return 0; + } diff --git a/src/cmd/dsa_ver.cpp b/src/cmd/dsa_ver.cpp new file mode 100644 index 000000000..a5d0ca271 --- /dev/null +++ b/src/cmd/dsa_ver.cpp @@ -0,0 +1,84 @@ +#include "apps.h" +#include <iostream> +#include <iomanip> +#include <fstream> +#include <cstdlib> +#include <string> +#include <memory> + +#include <botan/pubkey.h> +#include <botan/dsa.h> +#include <botan/b64_filt.h> +using namespace Botan; + +namespace { + +secure_vector<byte> b64_decode(const std::string& in) + { + Pipe pipe(new Base64_Decoder); + pipe.process_msg(in); + return pipe.read_all(); + } + +} + +int dsa_verify_main(int argc, char* argv[]) + { + if(argc != 4) + { + std::cout << "Usage: " << argv[0] + << " keyfile messagefile sigfile" << std::endl; + return 1; + } + + + try { + std::ifstream message(argv[2], std::ios::binary); + if(!message) + { + std::cout << "Couldn't read the message file." << std::endl; + return 1; + } + + std::ifstream sigfile(argv[3]); + if(!sigfile) + { + std::cout << "Couldn't read the signature file." << std::endl; + return 1; + } + + std::string sigstr; + getline(sigfile, sigstr); + + std::auto_ptr<X509_PublicKey> key(X509::load_key(argv[1])); + DSA_PublicKey* dsakey = dynamic_cast<DSA_PublicKey*>(key.get()); + + if(!dsakey) + { + std::cout << "The loaded key is not a DSA key!\n"; + return 1; + } + + secure_vector<byte> sig = b64_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\n"; + else + std::cout << "Signature did NOT verify\n"; + } + catch(std::exception& e) + { + std::cout << "Exception caught: " << e.what() << std::endl; + return 1; + } + return 0; + } diff --git a/src/cmd/factor.cpp b/src/cmd/factor.cpp new file mode 100644 index 000000000..7a5018d62 --- /dev/null +++ b/src/cmd/factor.cpp @@ -0,0 +1,153 @@ +/* +* (C) 2009-2010 Jack Lloyd +* +* Distributed under the terms of the Botan license +* +* Factor integers using a combination of trial division by small +* primes, and Pollard's Rho algorithm +*/ + +#include "apps.h" + +#include <botan/reducer.h> +#include <botan/numthry.h> +using namespace Botan; + +#include <algorithm> +#include <iostream> +#include <iterator> + +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(check_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_main(int argc, char* argv[]) + { + if(argc != 2) + { + std::cout << "Usage: " << argv[0] << " <integer>\n"; + return 1; + } + + try + { + BigInt n(argv[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 << "\n"; + } + catch(std::exception& e) + { + std::cout << e.what() << std::endl; + return 1; + } + return 0; + } diff --git a/src/cmd/fpe.cpp b/src/cmd/fpe.cpp new file mode 100644 index 000000000..e40db8a32 --- /dev/null +++ b/src/cmd/fpe.cpp @@ -0,0 +1,143 @@ +#include "apps.h" +#include <botan/fpe_fe1.h> +#include <botan/sha160.h> + +using namespace Botan; + +#include <iostream> +#include <stdexcept> + +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_main(int argc, char* argv[]) + { + if(argc != 4) + { + std::cout << "Usage: " << argv[0] << " cc-number acct-name passwd\n"; + return 1; + } + + u64bit cc_number = atoll(argv[1]); + std::string acct_name = argv[2]; + std::string passwd = argv[3]; + + std::cout << "Input was: " << cc_number << ' ' + << luhn_check(cc_number) << '\n'; + + /* + * 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) << '\n'; + + u64bit dec_cc = decrypt_cc_number(enc_cc, key, acct_name); + + std::cout << "Decrypted: " << dec_cc + << ' ' << luhn_check(dec_cc) << '\n'; + + if(dec_cc != cc_number) + { + std::cout << "Something went wrong :( Bad CC checksum?\n"; + return 2; + } + + return 0; + } diff --git a/src/cmd/getopt.h b/src/cmd/getopt.h new file mode 100644 index 000000000..a4f5f4d6f --- /dev/null +++ b/src/cmd/getopt.h @@ -0,0 +1,129 @@ +/* +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#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 found"); + 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; + } + + void parse(char* argv[]) + { + std::vector<std::string> args; + for(int j = 1; argv[j]; j++) + args.push_back(argv[j]); + + for(size_t j = 0; j != args.size(); j++) + { + std::string arg = args[j]; + + 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); + } + } + + 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/cmd/hash.cpp b/src/cmd/hash.cpp new file mode 100644 index 000000000..5c5705210 --- /dev/null +++ b/src/cmd/hash.cpp @@ -0,0 +1,58 @@ +/* +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include "apps.h" +#include <botan/lookup.h> +#include <iostream> +#include <fstream> + +using namespace Botan; + +int hash_main(int argc, char* argv[]) + { + if(argc < 3) + { + std::cout << "Usage: " << argv[0] << " digest <filenames>" << std::endl; + return 1; + } + + std::string hash = argv[1]; + /* a couple of special cases, kind of a crock */ + if(hash == "sha1") hash = "SHA-1"; + if(hash == "md5") hash = "MD5"; + + try { + if(!have_hash(hash)) + { + std::cout << "Unknown hash \"" << argv[1] << "\"" << std::endl; + return 1; + } + + Pipe pipe(new Hash_Filter(hash), new Hex_Encoder); + + int skipped = 0; + for(int j = 2; argv[j] != nullptr; j++) + { + std::ifstream file(argv[j], std::ios::binary); + if(!file) + { + std::cout << "ERROR: could not open " << argv[j] << std::endl; + skipped++; + continue; + } + pipe.start_msg(); + file >> pipe; + pipe.end_msg(); + pipe.set_default_msg(j-2-skipped); + std::cout << pipe << " " << argv[j] << std::endl; + } + } + catch(std::exception& e) + { + std::cout << "Exception caught: " << e.what() << std::endl; + } + return 0; + } diff --git a/src/cmd/keygen.cpp b/src/cmd/keygen.cpp new file mode 100644 index 000000000..40055f6cf --- /dev/null +++ b/src/cmd/keygen.cpp @@ -0,0 +1,53 @@ +#include "apps.h" +#include <iostream> +#include <fstream> +#include <string> +#include <cstdlib> +#include <memory> + +#include <botan/rsa.h> +using namespace Botan; + +int keygen_main(int argc, char* argv[]) + { + if(argc != 2 && argc != 3) + { + std::cout << "Usage: " << argv[0] << " bitsize [passphrase]" + << std::endl; + return 1; + } + + const size_t bits = std::atoi(argv[1]); + if(bits < 1024 || bits > 16384) + { + std::cout << "Invalid argument for bitsize" << std::endl; + return 1; + } + + try + { + std::ofstream pub("rsapub.pem"); + std::ofstream priv("rsapriv.pem"); + if(!priv || !pub) + { + std::cout << "Couldn't write output files" << std::endl; + return 1; + } + + AutoSeeded_RNG rng; + + RSA_PrivateKey key(rng, bits); + pub << X509::PEM_encode(key); + + if(argc == 2) + priv << PKCS8::PEM_encode(key); + else + priv << PKCS8::PEM_encode(key, rng, argv[2]); + } + catch(std::exception& e) + { + std::cout << "Exception caught: " << e.what() << std::endl; + } + + return 0; + } diff --git a/src/cmd/main.cpp b/src/cmd/main.cpp new file mode 100644 index 000000000..2d675d546 --- /dev/null +++ b/src/cmd/main.cpp @@ -0,0 +1,114 @@ +/* +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +/* + * Test Driver for Botan + */ + +#include <vector> +#include <string> + +#include <iostream> +#include <cstdlib> +#include <cstring> +#include <exception> +#include <limits> +#include <memory> + +#include <botan/init.h> +#include <botan/version.h> +#include <botan/auto_rng.h> +#include <botan/cpuid.h> +#include <botan/http_util.h> + +using namespace Botan; + +#include "apps.h" + +namespace { + +int help(int , char* argv[]) + { + std::cout << "Usage: " << argv[0] << " [subcommand]\n"; + std::cout << "version speed cpuid bcrypt x509 factor tls_client tls_server asn1 base64 hash self_sig ...\n"; + return 1; + } + +} + +int main(int argc, char* argv[]) + { + if(BOTAN_VERSION_MAJOR != version_major() || + BOTAN_VERSION_MINOR != version_minor() || + BOTAN_VERSION_PATCH != version_patch()) + { + std::cout << "Warning: linked version (" + << version_major() << '.' + << version_minor() << '.' + << version_patch() + << ") does not match version built against (" + << BOTAN_VERSION_MAJOR << '.' + << BOTAN_VERSION_MINOR << '.' + << BOTAN_VERSION_PATCH << ")\n"; + } + + try + { + Botan::LibraryInitializer init; + + if(argc < 2) + return help(argc, argv); + + const std::string cmd = argv[1]; + + if(cmd == "help") + return help(argc, argv); + + if(cmd == "version") + { + std::cout << Botan::version_string() << "\n"; + return 0; + } + + if(cmd == "cpuid") + { + CPUID::print(std::cout); + return 0; + } + + if(cmd == "speed") + return speed_main(argc - 1, argv + 1); + + if(cmd == "http_get") + { + auto resp = HTTP::GET_sync(argv[2]); + std::cout << resp << "\n"; + } + + int e = apps_main(cmd, argc - 1, argv + 1); + + if(e == -1) + { + std::cout << "Unknown command " << cmd << "\n"; + return help(argc, argv); + } + + return e; + } + catch(std::exception& e) + { + std::cerr << "Exception: " << e.what() << std::endl; + return 1; + } + catch(...) + { + std::cerr << "Unknown (...) exception caught" << std::endl; + return 1; + } + + return 0; + } + diff --git a/src/cmd/ocsp.cpp b/src/cmd/ocsp.cpp new file mode 100644 index 000000000..98324caff --- /dev/null +++ b/src/cmd/ocsp.cpp @@ -0,0 +1,38 @@ +#include "apps.h" +#include <botan/x509cert.h> +#include <botan/certstor.h> +#include <botan/x509path.h> +#include <botan/ocsp.h> + +#include <iostream> + +using namespace Botan; + +int ocsp_check_main(int argc, char* argv[]) + { + if(argc != 2) + { + std::cout << "Usage: ocsp subject.pem issuer.pem"; + return 2; + } + + X509_Certificate subject(argv[1]); + X509_Certificate issuer(argv[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\n"; + return 0; + } + else + { + std::cout << "OCSP check failed " << Path_Validation_Result::status_string(status) << "\n"; + return 1; + } + } diff --git a/src/cmd/pkcs10.cpp b/src/cmd/pkcs10.cpp new file mode 100644 index 000000000..cb1d44436 --- /dev/null +++ b/src/cmd/pkcs10.cpp @@ -0,0 +1,48 @@ +#include "apps.h" +#include <botan/x509self.h> +#include <botan/rsa.h> +#include <botan/dsa.h> +using namespace Botan; + +#include <iostream> +#include <fstream> +#include <memory> + +int pkcs10_main(int argc, char* argv[]) + { + if(argc != 6) + { + std::cout << "Usage: " << argv[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, argv[1]); + + X509_Cert_Options opts; + + opts.common_name = argv[2]; + opts.country = argv[3]; + opts.organization = argv[4]; + opts.email = argv[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; + } diff --git a/src/cmd/read_ssh.cpp b/src/cmd/read_ssh.cpp new file mode 100644 index 000000000..2745f3209 --- /dev/null +++ b/src/cmd/read_ssh.cpp @@ -0,0 +1,134 @@ +/* +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +/* +* Example of reading SSH2 format public keys (see RFC 4716) +*/ + +#include "apps.h" + +#include <botan/x509_key.h> +#include <botan/filters.h> +#include <botan/loadstor.h> +#include <botan/rsa.h> +#include <botan/dsa.h> +#include <fstream> +#include <memory> + +using namespace Botan; + +namespace { + +u32bit read_u32bit(Pipe& pipe) + { + byte out[4] = { 0 }; + pipe.read(out, 4); + u32bit len = load_be<u32bit>(out, 0); + if(len > 10000) + throw Decoding_Error("Huge size in read_u32bit, something went wrong"); + return len; + } + +std::string read_string(Pipe& pipe) + { + u32bit len = read_u32bit(pipe); + + std::string out(len, 'X'); + pipe.read(reinterpret_cast<byte*>(&out[0]), len); + return out; + } + +BigInt read_bigint(Pipe& pipe) + { + u32bit len = read_u32bit(pipe); + + secure_vector<byte> buf(len); + pipe.read(&buf[0], len); + return BigInt::decode(buf); + } + +Public_Key* read_ssh_pubkey(const std::string& file) + { + std::ifstream in(file.c_str()); + + const std::string ssh_header = "---- BEGIN SSH2 PUBLIC KEY ----"; + const std::string ssh_trailer = "---- END SSH2 PUBLIC KEY ----"; + + std::string hex_bits; + + std::string line; + std::getline(in, line); + + if(line != ssh_header) + return nullptr; + + while(in.good()) + { + std::getline(in, line); + + if(line.find("Comment: ") == 0) + { + while(line[line.size()-1] == '\\') + std::getline(in, line); + std::getline(in, line); + } + + if(line == ssh_trailer) + break; + + hex_bits += line; + } + + Pipe pipe(new Base64_Decoder); + pipe.process_msg(hex_bits); + + std::string key_type = read_string(pipe); + + if(key_type != "ssh-rsa" && key_type != "ssh-dss") + return nullptr; + + if(key_type == "ssh-rsa") + { + BigInt e = read_bigint(pipe); + BigInt n = read_bigint(pipe); + return new RSA_PublicKey(n, e); + } + else if(key_type == "ssh-dss") + { + BigInt p = read_bigint(pipe); + BigInt q = read_bigint(pipe); + BigInt g = read_bigint(pipe); + BigInt y = read_bigint(pipe); + + return new DSA_PublicKey(DL_Group(p, q, g), y); + } + + return nullptr; + } + +} + +int read_ssh_main(int argc, char* argv[]) + { + if(argc != 2) + { + std::cout << "Usage: " << argv[0] << " file"; + return 1; + } + + const std::string filename = argv[1]; + std::unique_ptr<Public_Key> key(read_ssh_pubkey(filename)); + + if(key == 0) + { + std::cout << "Failed to read" << filename << "\n"; + return 1; + } + + std::cout << X509::PEM_encode(*key); + + return 0; + } diff --git a/src/cmd/self_sig.cpp b/src/cmd/self_sig.cpp new file mode 100644 index 000000000..7d05aed60 --- /dev/null +++ b/src/cmd/self_sig.cpp @@ -0,0 +1,70 @@ +#include "apps.h" +#include <botan/x509self.h> +#include <botan/rsa.h> +#include <botan/dsa.h> +using namespace Botan; + +#include <iostream> +#include <fstream> +#include <memory> + +int self_sig_main(int argc, char* argv[]) + { + if(argc != 7) + { + std::cout << "Usage: " << argv[0] + << " passphrase [CA|user] name country_code organization email" + << std::endl; + return 1; + } + + std::string CA_flag = argv[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); + + //DSA_PrivateKey key(rng, group); + + std::ofstream priv_key("private.pem"); + priv_key << PKCS8::PEM_encode(key, rng, argv[1]); + + X509_Cert_Options opts; + + opts.common_name = argv[3]; + opts.country = argv[4]; + opts.organization = argv[5]; + opts.email = argv[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; + } diff --git a/src/cmd/speed/pk_bench.cpp b/src/cmd/speed/pk_bench.cpp new file mode 100644 index 000000000..6ebb366c5 --- /dev/null +++ b/src/cmd/speed/pk_bench.cpp @@ -0,0 +1,755 @@ +/* +* (C) 2009-2010 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include "speed.h" +#include "timer.h" + +#include <botan/pkcs8.h> +#include <botan/mem_ops.h> +#include <botan/parsing.h> +#include <botan/oids.h> +#include <map> + +#if defined(BOTAN_HAS_PUBLIC_KEY_CRYPTO) + #include <botan/x509_key.h> + #include <botan/pkcs8.h> + #include <botan/pubkey.h> +#endif + +#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_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) + #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 + +using namespace Botan; + +#include <iostream> +#include <fstream> +#include <string> +#include <memory> +#include <set> + +#define BENCH_FAULT_PROT DISABLE_FAULT_PROTECTION +//#define BENCH_FAULT_PROT ENABLE_FAULT_PROTECTION + +namespace { + +const char* ec_domains[] = { + "secp160r2", + "secp192r1", + "secp224r1", + "secp256r1", + "secp384r1", + "secp521r1", + 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!\n"; + } + } + } + } + +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()); + } + + 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\n"; + + 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)\n"; + } + } + } + } + +/* + 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(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 = "EMSA4(SHA-1)"; + //const std::string enc_padding = "EME1(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); + PK_Decryptor_EME dec(key, enc_padding); + + benchmark_enc_dec(enc, dec, enc_timer, dec_timer, + rng, 10000, seconds); + + PK_Signer sig(key, sig_padding); + PK_Verifier ver(key, sig_padding); + + 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() << "\n"; + } + } + + } +#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(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, BENCH_FAULT_PROT); + PK_Verifier ver(key, padding); + + benchmark_sig_ver(ver, sig, verify_timer, + sig_timer, rng, 1000, seconds); + } + + const std::string nm = "ECDSA-" + std::to_string(pbits); + + 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, BENCH_FAULT_PROT); + 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\n"; + } + } + + 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, BENCH_FAULT_PROT); + 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 + } + +#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\n"; + } + } + + 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) +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 + +} + +void bench_pk(RandomNumberGenerator& rng, + const std::string& algo, 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(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(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_DIFFIE_HELLMAN) + if(algo == "All" || algo == "DH") + benchmark_dh(rng, seconds, report); +#endif + +#if defined(BOTAN_HAS_DIFFIE_HELLMAN) && defined(BOTAN_HAS_DLIES) + 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 + } diff --git a/src/cmd/speed/speed.cpp b/src/cmd/speed/speed.cpp new file mode 100644 index 000000000..2e3d2c6fa --- /dev/null +++ b/src/cmd/speed/speed.cpp @@ -0,0 +1,229 @@ +/* +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include "speed.h" +#include <iostream> +#include <iomanip> + +#include <botan/benchmark.h> +#include <botan/aead.h> +#include <botan/auto_rng.h> +#include <botan/libstate.h> +#include <botan/pipe.h> +#include <botan/filters.h> +#include <botan/engine.h> +#include <botan/parsing.h> +#include <botan/symkey.h> +#include <botan/hex.h> + +#include <chrono> + +typedef std::chrono::high_resolution_clock benchmark_clock; + + +using namespace Botan; + +namespace { + +const std::string default_benchmark_list[] = { + + /* Block ciphers */ + "AES-128", + "AES-192", + "AES-256", + "Blowfish", + "CAST-128", + "CAST-256", + "DES", + "DESX", + "GOST", + "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 */ + "HAS-160", + "Keccak-1600(512)", + "MD4", + "MD5", + "RIPEMD-128", + "RIPEMD-160", + "SHA-160", + "SHA-256", + "SHA-384", + "SHA-512", + "Skein-512", + "Tiger", + "Whirlpool", + + /* MACs */ + "CMAC(AES-128)", + "HMAC(SHA-1)", + "", +}; + +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; + + for(auto i = results.rbegin(); i != results.rend(); ++i) + { + std::cout << " [" << i->second << "] " + << std::fixed << std::setprecision(2) << i->first; + } + std::cout << std::endl; + } + +void time_transform(std::unique_ptr<Transformation> tf, + RandomNumberGenerator& rng) + { + if(!tf) + return; + + if(tf->maximum_keylength() > 0) + tf->set_key(rng.random_vec(tf->maximum_keylength())); + + for(size_t buf_size : { 16, 64, 256, 1024, 8192 }) + { + secure_vector<byte> buffer(buf_size); + + double res = time_op(std::chrono::seconds(1), + [&tf,&buffer,buf_size,&rng]{ + tf->start_vec(rng.random_vec(tf->default_nonce_length())); + tf->finish(buffer); + buffer.resize(buf_size); + }); + + const double Mbytes = (res * buf_size) / 1024 / 1024; + + std::cout << tf->name() << " " << std::setprecision(4) << Mbytes + << " MiB / sec with " << buf_size << " byte blocks\n"; + } + } + +void time_transform(const std::string& algo, RandomNumberGenerator& rng) + { + std::unique_ptr<Transformation> tf; + tf.reset(get_aead(algo, ENCRYPTION)); + time_transform(std::move(tf), rng); + } + +} + +void bench_algo(const std::string& algo, + RandomNumberGenerator& rng, + double seconds, + size_t buf_size) + { + Algorithm_Factory& af = global_state().algorithm_factory(); + + std::chrono::milliseconds ms( + static_cast<std::chrono::milliseconds::rep>(seconds * 1000)); + + std::map<std::string, double> speeds = algorithm_benchmark(algo, af, rng, ms, buf_size); + + report_results(algo, speeds); + + if(speeds.empty()) + time_transform(algo, rng); + + if(speeds.empty()) + bench_pk(rng, algo, seconds); + } + +int speed_main(int argc, char* argv[]) + { + OptionParser opts("seconds=|buf-size="); + opts.parse(argv); + + 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\n"; + 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\n"; + return 2; + } + } + + const auto args = opts.arguments(); + + if(args.empty() || args[0] == "help" || args[0] == "-h") + { + std::cout << "Help!\n"; + return 1; + } + + AutoSeeded_RNG rng; + + for(auto alg: args) + bench_algo(alg, rng, seconds, buf_size); + + return 0; + } diff --git a/src/cmd/speed/speed.h b/src/cmd/speed/speed.h new file mode 100644 index 000000000..b4aabca8a --- /dev/null +++ b/src/cmd/speed/speed.h @@ -0,0 +1,25 @@ + +#ifndef BOTAN_CHECK_BENCHMARK_H__ +#define BOTAN_CHECK_BENCHMARK_H__ + +#include "../apps.h" +#include <botan/rng.h> +#include <botan/transform.h> +#include <string> + +using namespace Botan; + +int speed_main(int argc, char* argv[]); + +void benchmark(double seconds, + size_t buf_size); + +void bench_algo(const std::string& algo_name, + RandomNumberGenerator& rng, + double seconds, + size_t buf_size); + +void bench_pk(RandomNumberGenerator& rng, + const std::string& algo, double seconds); + +#endif diff --git a/src/cmd/speed/timer.cpp b/src/cmd/speed/timer.cpp new file mode 100644 index 000000000..9f3d34607 --- /dev/null +++ b/src/cmd/speed/timer.cpp @@ -0,0 +1,64 @@ +/* +* (C) 2009 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include "timer.h" +#include <chrono> +#include <iomanip> + +Timer::Timer(const std::string& n, u32bit e_mul) : + name(n), event_mult(e_mul) + { + time_used = 0; + timer_start = 0; + event_count = 0; + } + +void Timer::start() + { + stop(); + timer_start = get_clock(); + } + +void Timer::stop() + { + if(timer_start) + { + u64bit now = get_clock(); + + if(now > timer_start) + time_used += (now - timer_start); + + timer_start = 0; + ++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"; + + out << std::setprecision(2) << std::fixed + << timer.ms_per_event() << " ms/op" + << " (" << timer.events() << " " << op_or_ops << " in " + << timer.milliseconds() << " ms)"; + + return out; + } diff --git a/src/cmd/speed/timer.h b/src/cmd/speed/timer.h new file mode 100644 index 000000000..48d6b6805 --- /dev/null +++ b/src/cmd/speed/timer.h @@ -0,0 +1,50 @@ + +#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); + + void start(); + + void stop(); + + u64bit value() { stop(); return 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 event_count * event_mult; } + std::string get_name() const { return name; } + private: + std::string name; + u64bit time_used, timer_start; + u64bit event_count, event_mult; + }; + +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/cmd/tls_client.cpp b/src/cmd/tls_client.cpp new file mode 100644 index 000000000..7f201d5a9 --- /dev/null +++ b/src/cmd/tls_client.cpp @@ -0,0 +1,263 @@ +#include "apps.h" +#include <botan/tls_client.h> +#include <botan/pkcs8.h> +#include <botan/hex.h> +#include <stdio.h> +#include <string> +#include <iostream> +#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 + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + #include <botan/tls_session_manager_sqlite.h> +#endif + +#include "credentials.h" + +using namespace Botan; + +using namespace std::placeholders; + +namespace { + +int connect_to_host(const std::string& host, u16bit port, const std::string& transport) + { + hostent* host_addr = ::gethostbyname(host.c_str()); + + if(host_addr == 0) + throw std::runtime_error("gethostbyname failed for " + host); + + if(host_addr->h_addrtype != AF_INET) // FIXME + throw std::runtime_error(host + " has IPv6 address"); + + int type = (transport == "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 = *(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() << "\n"; + + if(!session.session_id().empty()) + std::cout << "Session ID " << hex_encode(session.session_id()) << "\n"; + + if(!session.session_ticket().empty()) + std::cout << "Session ticket " << hex_encode(session.session_ticket()) << "\n"; + + return true; + } + +void dgram_socket_write(int sockfd, const byte buf[], size_t length) + { + send(sockfd, buf, length, MSG_NOSIGNAL); + } + +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: Socket write failed"); + } + + offset += sent; + length -= sent; + } + } + +bool got_alert = false; + +void alert_received(TLS::Alert alert, const byte [], size_t ) + { + std::cout << "Alert: " << alert.type_string() << "\n"; + 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]; + } + +std::string protocol_chooser(const std::vector<std::string>& protocols) + { + for(size_t i = 0; i != protocols.size(); ++i) + std::cout << "Protocol " << i << " = " << protocols[i] << "\n"; + return "http/1.1"; + } + +} + +int tls_client_main(int argc, char* argv[]) + { + if(argc != 2 && argc != 3 && argc != 4) + { + std::cout << "Usage " << argv[0] << " host [port] [udp|tcp]\n"; + return 1; + } + + try + { + AutoSeeded_RNG rng; + TLS::Policy policy; + +#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER) + TLS::Session_Manager_SQLite session_manager("my secret passphrase", + rng, + "sessions.db"); +#else + TLS::Session_Manager_In_Memory session_manager(rng); +#endif + + Credentials_Manager_Simple creds(rng); + + std::string host = argv[1]; + u32bit port = argc >= 3 ? Botan::to_u32bit(argv[2]) : 443; + std::string transport = argc >= 4 ? argv[3] : "tcp"; + + int sockfd = connect_to_host(host, port, transport); + + auto socket_write = + (transport == "tcp") ? + std::bind(stream_socket_write, sockfd, _1, _2) : + std::bind(dgram_socket_write, sockfd, _1, _2); + + auto version = + (transport == "tcp") ? + TLS::Protocol_Version::latest_tls_version() : + TLS::Protocol_Version::latest_dtls_version(); + + TLS::Client client(socket_write, + process_data, + alert_received, + handshake_complete, + session_manager, + creds, + policy, + rng, + TLS::Server_Information(host, port), + version, + protocol_chooser); + + while(!client.is_closed()) + { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(sockfd, &readfds); + FD_SET(STDIN_FILENO, &readfds); + + ::select(sockfd + 1, &readfds, NULL, NULL, NULL); + + 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\n"; + break; + } + else if(got == -1) + { + std::cout << "Socket error: " << errno << " " << strerror(errno) << "\n"; + continue; + } + + //std::cout << "Socket - got " << got << " bytes\n"; + client.received_data(buf, got); + } + else 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\n"; + client.close(); + break; + } + else if(got == -1) + { + std::cout << "Stdin error: " << errno << " " << strerror(errno) << "\n"; + continue; + } + + if(got == 2 && buf[1] == '\n') + { + char cmd = buf[0]; + + if(cmd == 'R' || cmd == 'r') + { + std::cout << "Client initiated renegotiation\n"; + client.renegotiate(cmd == 'R'); + } + else if(cmd == 'Q') + { + std::cout << "Client initiated close\n"; + client.close(); + } + } + else if(buf[0] == 'H') + client.heartbeat(&buf[1], got-1); + else + client.send(buf, got); + } + } + + ::close(sockfd); + + } + catch(std::exception& e) + { + std::cout << "Exception: " << e.what() << "\n"; + return 1; + } + return 0; + } diff --git a/src/cmd/tls_server.cpp b/src/cmd/tls_server.cpp new file mode 100644 index 000000000..819b9f380 --- /dev/null +++ b/src/cmd/tls_server.cpp @@ -0,0 +1,262 @@ +#include "apps.h" +#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 <stdio.h> +#include <string> +#include <iostream> +#include <memory> +#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(const std::string& transport, u16bit port) + { + int type = (transport == "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(transport != "udp") + { + 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() << "\n"; + + if(!session.session_id().empty()) + std::cout << "Session ID " << hex_encode(session.session_id()) << "\n"; + + if(!session.session_ticket().empty()) + std::cout << "Session ticket " << hex_encode(session.session_ticket()) << "\n"; + + 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) << "\n"; + else if(sent != static_cast<ssize_t>(length)) + std::cout << "Packet of length " << length << " truncated to " << sent << "\n"; + } + +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: Socket write failed"); + } + + offset += sent; + length -= sent; + } + } + +void alert_received(TLS::Alert alert, const byte buf[], size_t buf_size) + { + std::cout << "Alert: " << alert.type_string() << "\n"; + } + +} + +int tls_server_main(int argc, char* argv[]) + { + int port = 4433; + std::string transport = "tcp"; + + if(argc >= 2) + port = to_u32bit(argv[1]); + if(argc >= 3) + transport = argv[2]; + + try + { + AutoSeeded_RNG rng; + + TLS::Policy policy; + + TLS::Session_Manager_In_Memory session_manager(rng); + + Credentials_Manager_Simple creds(rng); + + /* + * These are the protocols we advertise to the client, but the + * client will send back whatever it actually plans on talking, + * which may or may not take into account what we advertise. + */ + const std::vector<std::string> protocols = { "echo/1.0", "echo/1.1" }; + + std::cout << "Listening for new connections on " << transport << " port " << port << "\n"; + + int server_fd = make_server_socket(transport, port); + + while(true) + { + try + { + int fd; + + if(transport == "tcp") + fd = ::accept(server_fd, NULL, NULL); + else + { + struct sockaddr_in from; + socklen_t from_len = sizeof(sockaddr_in); + + if(::recvfrom(server_fd, NULL, 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\n"; + + auto socket_write = + (transport == "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; + + pending_output.push_back("Welcome to the best echo server evar\n"); + + auto proc_fn = [&](const byte input[], size_t input_len) + { + for(size_t i = 0; i != input_len; ++i) + { + char c = (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, + protocols); + + 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) << "\n"; + break; + } + + if(got == 0) + { + std::cout << "EOF on socket\n"; + 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(transport == "tcp") + ::close(fd); + + } + catch(std::exception& e) + { + std::cout << "Connection problem: " << e.what() << "\n"; + return 1; + } + } + } + catch(std::exception& e) + { + std::cout << e.what() << "\n"; + return 1; + } + + return 0; + } diff --git a/src/cmd/tls_server_asio.cpp b/src/cmd/tls_server_asio.cpp new file mode 100644 index 000000000..b49206136 --- /dev/null +++ b/src/cmd/tls_server_asio.cpp @@ -0,0 +1,315 @@ +#include "apps.h" +#include <iostream> +#include <string> +#include <vector> +#define _GLIBCXX_HAVE_GTHR_DEFAULT +#include <boost/asio.hpp> +#include <boost/bind.hpp> +#include <boost/thread.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> +#include <botan/init.h> + +#include "credentials.h" + +using Botan::byte; +using boost::asio::ip::tcp; + +namespace { + +class tls_server_session : public boost::enable_shared_from_this<tls_server_session> + { + public: + typedef boost::shared_ptr<tls_server_session> pointer; + + static pointer create(boost::asio::io_service& io_service, + Botan::TLS::Session_Manager& session_manager, + Botan::Credentials_Manager& credentials, + Botan::TLS::Policy& policy, + Botan::RandomNumberGenerator& rng) + { + return pointer( + new tls_server_session( + io_service, + session_manager, + credentials, + policy, + rng) + ); + } + + tcp::socket& socket() { return m_socket; } + + void start() + { + m_socket.async_read_some( + boost::asio::buffer(m_read_buf, sizeof(m_read_buf)), + m_strand.wrap( + boost::bind(&tls_server_session::handle_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + void stop() { m_socket.close(); } + + private: + tls_server_session(boost::asio::io_service& io_service, + Botan::TLS::Session_Manager& session_manager, + Botan::Credentials_Manager& credentials, + Botan::TLS::Policy& policy, + Botan::RandomNumberGenerator& rng) : + m_strand(io_service), + m_socket(io_service), + m_tls(boost::bind(&tls_server_session::tls_output_wanted, this, _1, _2), + boost::bind(&tls_server_session::tls_data_recv, this, _1, _2), + boost::bind(&tls_server_session::tls_alert_cb, this, _1, _2, _3), + boost::bind(&tls_server_session::tls_handshake_complete, this, _1), + session_manager, + credentials, + policy, + rng) + { + } + + void handle_read(const boost::system::error_code& error, + size_t bytes_transferred) + { + if(!error) + { + try + { + m_tls.received_data(m_read_buf, bytes_transferred); + } + catch(std::exception& e) + { + std::cout << "Read failed " << e.what() << "\n"; + stop(); + return; + } + + m_socket.async_read_some( + boost::asio::buffer(m_read_buf, sizeof(m_read_buf)), + m_strand.wrap(boost::bind(&tls_server_session::handle_read, shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + else + { + stop(); + } + } + + void handle_write(const boost::system::error_code& error) + { + if(!error) + { + m_write_buf.clear(); + + // initiate another write if needed + tls_output_wanted(NULL, 0); + } + else + { + stop(); + } + } + + void tls_output_wanted(const byte buf[], size_t buf_len) + { + if(buf_len > 0) + m_outbox.insert(m_outbox.end(), buf, buf + buf_len); + + // no write pending and have output pending + if(m_write_buf.empty() && !m_outbox.empty()) + { + std::swap(m_outbox, m_write_buf); + + boost::asio::async_write(m_socket, + boost::asio::buffer(&m_write_buf[0], m_write_buf.size()), + m_strand.wrap( + boost::bind(&tls_server_session::handle_write, + shared_from_this(), + boost::asio::placeholders::error))); + } + } + + void tls_alert_cb(Botan::TLS::Alert alert, const byte buf[], size_t buf_len) + { + if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY) + { + m_tls.close(); + return; + } + } + + void tls_data_recv(const byte buf[], size_t buf_len) + { + m_client_data.insert(m_client_data.end(), buf, buf + buf_len); + + if(ready_to_respond()) + write_response(); + } + + bool ready_to_respond() + { + return true; // parse headers? + } + + void write_response() + { + std::string out; + out += "\r\n"; + out += "HTTP/1.0 200 OK\r\n"; + out += "Server: Botan ASIO test server\r\n"; + if(m_hostname != "") + out += "Host: " + m_hostname + "\r\n"; + out += "Content-Type: text/html\r\n"; + out += "\r\n"; + out += "<html><body>Greets. You said: "; + out += std::string((const char*)&m_client_data[0], m_client_data.size()); + out += "</body></html>\r\n\r\n"; + + m_tls.send(out); + m_tls.close(); + } + + bool tls_handshake_complete(const Botan::TLS::Session& session) + { + m_hostname = session.server_info().hostname(); + return true; + } + + boost::asio::io_service::strand m_strand; // serialization + + tcp::socket m_socket; + Botan::TLS::Server m_tls; + std::string m_hostname; + + unsigned char m_read_buf[1024]; + + // used to hold the data currently being written by the system + std::vector<byte> m_write_buf; + + // used to hold data queued for writing + std::vector<byte> m_outbox; + + std::vector<byte> m_client_data; + }; + +class asio_tls_server + { + public: + typedef tls_server_session session; + + asio_tls_server(boost::asio::io_service& io_service, unsigned short port) : + m_acceptor(io_service, tcp::endpoint(tcp::v4(), port)), + m_session_manager(m_rng), + m_creds(m_rng) + { + session::pointer new_session = make_session(); + + m_acceptor.async_accept( + new_session->socket(), + boost::bind( + &asio_tls_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_rng + ); + } + + 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->socket(), + boost::bind( + &asio_tls_server::handle_accept, + this, + new_session, + boost::asio::placeholders::error) + ); + } + } + + tcp::acceptor m_acceptor; + + Botan::AutoSeeded_RNG m_rng; + Botan::TLS::Session_Manager_In_Memory m_session_manager; + Botan::TLS::Policy m_policy; + Credentials_Manager_Simple m_creds; + }; + +size_t choose_thread_count() + { + size_t result = boost::thread::hardware_concurrency(); + + if(result) + return result; + + return 2; + } + +} + +int tls_server_asio_main(int argc, char* argv[]) + { + try + { + Botan::LibraryInitializer init("thread_safe=true"); + boost::asio::io_service io_service; + + const unsigned short port = 4434; + asio_tls_server server(io_service, port); + + size_t num_threads = choose_thread_count(); + if(argc == 2) + std::istringstream(argv[1]) >> num_threads; + + std::cout << "Using " << num_threads << " threads\n"; + + std::vector<boost::shared_ptr<boost::thread> > threads; + + for(size_t i = 0; i != num_threads; ++i) + { + boost::shared_ptr<boost::thread> thread( + new boost::thread( + boost::bind(&boost::asio::io_service::run, &io_service))); + threads.push_back(thread); + } + + // Wait for all threads in the pool to exit. + for (size_t i = 0; i < threads.size(); ++i) + threads[i]->join(); + } + catch (std::exception& e) + { + std::cerr << e.what() << std::endl; + } + + return 0; + } + diff --git a/src/cmd/x509print.cpp b/src/cmd/x509print.cpp new file mode 100644 index 000000000..935b50668 --- /dev/null +++ b/src/cmd/x509print.cpp @@ -0,0 +1,17 @@ +#include "apps.h" +#include <botan/x509cert.h> + +int x509_main(int argc, char* argv[]) + { + if(argc < 1) + { + std::cout << "Usage: " << argv[0] << " cert.pem\n"; + return 1; + } + + X509_Certificate cert(argv[1]); + + std::cout << cert.to_string() << "\n"; + + return 0; + } |