aboutsummaryrefslogtreecommitdiffstats
path: root/src/cli
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2018-07-01 16:32:28 -0400
committerJack Lloyd <[email protected]>2018-07-04 10:22:56 -0400
commita2ce8a8ebd5abc25b97a5bc7312c47fcceba9fd1 (patch)
tree0f7aa5914ed51b5ad16f955569b42d74156890e9 /src/cli
parent0e8e8f112dfe714faba49176e1d3be97fd2e32dd (diff)
Add pk_encrypt/pk_decrypt commands
Diffstat (limited to 'src/cli')
-rw-r--r--src/cli/cli.cpp38
-rw-r--r--src/cli/cli.h2
-rw-r--r--src/cli/pk_crypt.cpp212
3 files changed, 250 insertions, 2 deletions
diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp
index e3ff2a75f..a7914ba92 100644
--- a/src/cli/cli.cpp
+++ b/src/cli/cli.cpp
@@ -11,6 +11,10 @@
#include <iostream>
#include <fstream>
+#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
+ #include <termios.h>
+#endif
+
namespace Botan_CLI {
Command::Command(const std::string& cmd_spec) : m_spec(cmd_spec)
@@ -182,8 +186,8 @@ void Command::read_file(const std::string& input_file,
}
void Command::do_read_file(std::istream& in,
- std::function<void (uint8_t[], size_t)> consumer_fn,
- size_t buf_size) const
+ std::function<void (uint8_t[], size_t)> consumer_fn,
+ size_t buf_size) const
{
// Avoid an infinite loop on --buf-size=0
std::vector<uint8_t> buf(buf_size == 0 ? 4096 : buf_size);
@@ -206,6 +210,36 @@ Botan::RandomNumberGenerator& Command::rng()
return *m_rng.get();
}
+std::string Command::get_passphrase(const std::string& prompt)
+ {
+ error_output() << prompt << ": " << std::flush;
+ std::string pass;
+
+#if defined(BOTAN_TARGET_OS_HAS_POSIX1)
+
+ struct termios old_flags;
+ ::tcgetattr(fileno(stdin), &old_flags);
+ struct termios noecho_flags = old_flags;
+ noecho_flags.c_lflag &= ~ECHO;
+ noecho_flags.c_lflag |= ECHONL;
+
+ if(::tcsetattr(fileno(stdin), TCSANOW, &noecho_flags) != 0)
+ throw CLI_Error("Clearing terminal echo bit failed");
+
+ std::getline(std::cin, pass);
+
+ if(::tcsetattr(::fileno(stdin), TCSANOW, &old_flags) != 0)
+ throw CLI_Error("Restoring terminal echo bit failed");
+#else
+
+ // TODO equivalent for Windows ...
+ std::getline(std::cin, pass);
+
+#endif
+
+ return pass;
+ }
+
// Registration code
Command::Registration::Registration(const std::string& name, Command::cmd_maker_fn maker_fn)
diff --git a/src/cli/cli.h b/src/cli/cli.h
index 97128a722..fca8e5225 100644
--- a/src/cli/cli.h
+++ b/src/cli/cli.h
@@ -118,6 +118,8 @@ class Command
return flag_set("verbose");
}
+ std::string get_passphrase(const std::string& prompt);
+
bool flag_set(const std::string& flag_name) const;
std::string get_arg(const std::string& opt_name) const;
diff --git a/src/cli/pk_crypt.cpp b/src/cli/pk_crypt.cpp
new file mode 100644
index 000000000..69a8fdf02
--- /dev/null
+++ b/src/cli/pk_crypt.cpp
@@ -0,0 +1,212 @@
+/*
+* (C) 2018 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include "cli.h"
+
+#if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_AEAD_MODES) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_SHA2_32) && defined(BOTAN_HAS_PEM_CODEC)
+
+#include <botan/pubkey.h>
+#include <botan/x509_key.h>
+#include <botan/pkcs8.h>
+#include <botan/der_enc.h>
+#include <botan/ber_dec.h>
+#include <botan/oids.h>
+#include <botan/aead.h>
+#include <botan/pem.h>
+
+namespace Botan_CLI {
+
+namespace {
+
+class PK_Encrypt final : public Command
+ {
+ public:
+ PK_Encrypt() : Command("pk_encrypt --aead=AES-256/GCM pubkey datafile") {}
+
+ std::string group() const override
+ {
+ return "pubkey";
+ }
+
+ std::string description() const override
+ {
+ return "Encrypt a file using a RSA public key";
+ }
+
+ void go() override
+ {
+ std::unique_ptr<Botan::Public_Key> key(Botan::X509::load_key(get_arg("pubkey")));
+ if(!key)
+ {
+ throw CLI_Error("Unable to load public key");
+ }
+
+ if(key->algo_name() != "RSA")
+ {
+ throw CLI_Usage_Error("This function requires an RSA key");
+ }
+
+ const std::string OAEP_HASH = "SHA-256";
+ const std::string aead_algo = get_arg("aead");
+
+ std::unique_ptr<Botan::AEAD_Mode> aead =
+ Botan::AEAD_Mode::create(aead_algo, Botan::ENCRYPTION);
+
+ if(!aead)
+ throw CLI_Usage_Error("The AEAD '" + aead_algo + "' is not available");
+
+ const Botan::OID aead_oid = Botan::OIDS::lookup(aead_algo);
+ if(aead_oid.empty())
+ throw CLI_Usage_Error("No OID defined for AEAD '" + aead_algo + "'");
+
+ Botan::secure_vector<uint8_t> data;
+ auto insert_fn = [&](const uint8_t b[], size_t l)
+ {
+ data.insert(data.end(), b, b + l);
+ };
+ this->read_file(get_arg("datafile"), insert_fn);
+
+ const Botan::AlgorithmIdentifier hash_id(OAEP_HASH, Botan::AlgorithmIdentifier::USE_NULL_PARAM);
+ const Botan::AlgorithmIdentifier pk_alg_id("RSA/OAEP", hash_id.BER_encode());
+
+ Botan::PK_Encryptor_EME enc(*key, rng(), "OAEP(" + OAEP_HASH + ")");
+
+ const Botan::secure_vector<uint8_t> file_key = rng().random_vec(aead->key_spec().maximum_keylength());
+
+ const std::vector<uint8_t> encrypted_key = enc.encrypt(file_key, rng());
+
+ const Botan::secure_vector<uint8_t> nonce = rng().random_vec(aead->default_nonce_length());
+ aead->set_key(file_key);
+ aead->set_associated_data_vec(encrypted_key);
+ aead->start(nonce);
+
+ aead->finish(data);
+
+ std::vector<uint8_t> buf;
+ Botan::DER_Encoder der(buf);
+
+ der.start_cons(Botan::SEQUENCE)
+ .encode(pk_alg_id)
+ .encode(encrypted_key, Botan::OCTET_STRING)
+ .encode(aead_oid)
+ .encode(nonce, Botan::OCTET_STRING)
+ .encode(data, Botan::OCTET_STRING)
+ .end_cons();
+
+ output() << Botan::PEM_Code::encode(buf, "PUBKEY ENCRYPTED MESSAGE", 72);
+ }
+ };
+
+BOTAN_REGISTER_COMMAND("pk_encrypt", PK_Encrypt);
+
+class PK_Decrypt final : public Command
+ {
+ public:
+ PK_Decrypt() : Command("pk_decrypt privkey datafile") {}
+
+ std::string group() const override
+ {
+ return "pubkey";
+ }
+
+ std::string description() const override
+ {
+ return "Decrypt a file using a RSA private key";
+ }
+
+ void go() override
+ {
+ Botan::DataSource_Stream input_stream(get_arg("privkey"));
+ auto get_pass = [this]() { return get_passphrase("Password"); };
+ std::unique_ptr<Botan::Private_Key> key = Botan::PKCS8::load_key(input_stream, get_pass);
+
+ if(!key)
+ {
+ throw CLI_Error("Unable to load public key");
+ }
+
+ if(key->algo_name() != "RSA")
+ {
+ throw CLI_Usage_Error("This function requires an RSA key");
+ }
+
+ Botan::secure_vector<uint8_t> data;
+ std::vector<uint8_t> encrypted_key;
+ std::vector<uint8_t> nonce;
+ Botan::AlgorithmIdentifier pk_alg_id;
+ Botan::OID aead_oid;
+
+ try
+ {
+ Botan::DataSource_Stream input(get_arg("datafile"));
+
+ Botan::BER_Decoder(Botan::PEM_Code::decode_check_label(input, "PUBKEY ENCRYPTED MESSAGE"))
+ .start_cons(Botan::SEQUENCE)
+ .decode(pk_alg_id)
+ .decode(encrypted_key, Botan::OCTET_STRING)
+ .decode(aead_oid)
+ .decode(nonce, Botan::OCTET_STRING)
+ .decode(data, Botan::OCTET_STRING)
+ .end_cons();
+ }
+ catch(Botan::Decoding_Error&)
+ {
+ output() << "Parsing input file failed: invalid format?\n";
+ set_return_code(1);
+ return;
+ }
+
+ const std::string aead_algo = Botan::OIDS::lookup(aead_oid);
+ if(aead_algo == "")
+ {
+ output() << "Ciphertext was encrypted with an unknown algorithm";
+ set_return_code(1);
+ return;
+ }
+
+ if(pk_alg_id.get_oid() != Botan::OIDS::lookup("RSA/OAEP"))
+ {
+ output() << "Ciphertext was encrypted with something other than RSA/OAEP";
+ set_return_code(1);
+ return;
+ }
+
+ Botan::AlgorithmIdentifier oaep_hash;
+ Botan::BER_Decoder(pk_alg_id.get_parameters()).decode(oaep_hash);
+
+ Botan::PK_Decryptor_EME dec(*key, rng(), "OAEP(" + Botan::OIDS::lookup(oaep_hash.get_oid()) + ")");
+
+ const Botan::secure_vector<uint8_t> file_key = dec.decrypt(encrypted_key);
+
+ std::unique_ptr<Botan::AEAD_Mode> aead =
+ Botan::AEAD_Mode::create_or_throw(aead_algo, Botan::DECRYPTION);
+
+ aead->set_key(file_key);
+ aead->set_associated_data_vec(encrypted_key);
+ aead->start(nonce);
+
+ try
+ {
+ aead->finish(data);
+
+ output().write(reinterpret_cast<const char*>(data.data()), data.size());
+ }
+ catch(Botan::Integrity_Failure&)
+ {
+ output() << "Message authentication failure, possible ciphertext tampering\n";
+ set_return_code(1);
+ return;
+ }
+ }
+ };
+
+BOTAN_REGISTER_COMMAND("pk_decrypt", PK_Decrypt);
+
+}
+
+}
+
+#endif