diff --git a/bare_minimum_crypto/cpp/bmc_main.cpp b/bare_minimum_crypto/cpp/bmc_main.cpp index 2ebed4a..8582d9a 100644 --- a/bare_minimum_crypto/cpp/bmc_main.cpp +++ b/bare_minimum_crypto/cpp/bmc_main.cpp @@ -25,25 +25,8 @@ namespace fs = std::filesystem; int file (const string & file_path) { cout << "Reading `" << file_path << "`" << endl; - ifstream f; - f.open (file_path, ifstream::binary); - if (! f.is_open ()) { - cerr << "Can't open file." << endl; - return 1; - } - f.seekg (0, ifstream::end); - const auto len = f.tellg (); - f.seekg (0, ifstream::beg); - - Bytes bytes; - bytes.resize (len); - - f.read ((char *)bytes.data (), bytes.size ()); - - // All our files are msgpack, so parse it first. - - const auto j = json::from_msgpack (bytes); + const auto j = std::move (*try_load_msgpack_file (file_path)); const string schema = j ["schema"]; @@ -164,13 +147,18 @@ int test () { int main (int argc, char ** argv) { cxxopts::Options options ("BareMinimumCrypto", "Simple crypto things you might need."); options.add_options () + // Commands + ("help", "Print usage") + ("file", "Print info about any file generated by BMC", cxxopts::value ()) ("generate-human-key", "Generate a passphrase-protected key for human use", cxxopts::value ()) ("generate-machine-key", "Generate a key for machine use, with no passphrase", cxxopts::value ()) - ("generate-key-cert", "Certify a key for 1 month and save the cert here", cxxopts::value ()) + ("generate-key-cert", "Certify a key for 3 months and save the cert here", cxxopts::value ()) + + // cxxopts nonsense ("using-key", "Key to load for other operations", cxxopts::value ()) - ("file", "Print info about any file generated by BMC", cxxopts::value ()) + + // Other stuff ("test", "Run self-test") - ("help", "Print usage") ; auto result = options.parse (argc, argv); @@ -178,11 +166,13 @@ int main (int argc, char ** argv) { if (result.count ("help")) { cout << options.help () << endl; } + else if (result.count ("file")) { + const auto file_path = result ["file"].as (); + return file (file_path); + } else if (result.count ("generate-human-key")) { const auto file_path = result ["generate-human-key"].as (); - cout << "Type passphrase (it will be visible in the console)" << endl; - string passphrase; - cin >> passphrase; + const auto passphrase = get_passphrase_from_user (); auto key_opt = HumanKeyFile::generate (file_path, passphrase); if (! key_opt) { @@ -201,9 +191,31 @@ int main (int argc, char ** argv) { } cout << "The pubkey is `" << base64_encode (key_opt->pubkey ()) << "`" << endl; } - else if (result.count ("file")) { - const auto file_path = result ["file"].as (); - return file (file_path); + else if (result.count ("generate-key-cert")) { + if (! result.count ("using-key")) { + cerr << "Usage: bmc --generate-key-cert output.cert --using-key human.key" << endl; + return 1; + } + + const auto key_path = result ["using-key"].as (); + const auto output_path = result ["generate-key-cert"].as (); + const auto passphrase = get_passphrase_from_user (); + + auto key_opt = HumanKeyFile::load (key_path, passphrase); + if (! key_opt) { + cerr << "Error, could not load human key." << endl; + return 1; + } + const auto key = std::move (*key_opt); + + cout << "Paste Base64-encoded public key" << endl; + string pubkey_b64; + cin >> pubkey_b64; + + auto pubkey_opt = base64_decode (pubkey_b64); + const auto pubkey = std::move (*pubkey_opt); + + } else if (result.count ("test")) { if (test () != 0) { diff --git a/bare_minimum_crypto/cpp/signing_key.cpp b/bare_minimum_crypto/cpp/signing_key.cpp index 1982545..dc01db7 100644 --- a/bare_minimum_crypto/cpp/signing_key.cpp +++ b/bare_minimum_crypto/cpp/signing_key.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "json.hpp" @@ -99,28 +100,42 @@ namespace BareMinimumCrypto { return true; } - // The whole process for a passphrased key is like this: - // Passphrase + random salt --> - // Seed --> - // Secret key + Public key - - // Passphrases should be mandatory for keys that can sign other keys. - - optional HumanKeyFile::generate (const string & file_path, const string & passphrase) - { - try_sodium_init (); + string get_passphrase_from_user () { + cout << "Type or paste passphrase (it will be visible in the console)" << endl; + string passphrase; + cin >> passphrase; - if (passphrase.size () < 8) { + return passphrase; + } + + optional try_load_msgpack_file (const string & file_path) { + ifstream f; + f.open (file_path, ifstream::binary); + if (! f.is_open ()) { return nullopt; } + f.seekg (0, ifstream::end); + const auto len = f.tellg (); + f.seekg (0, ifstream::beg); + + if (len > 1024 * 1024) { + return nullopt; + } + + Bytes bytes; + bytes.resize (len); + + f.read ((char *)bytes.data (), bytes.size ()); + + return json::from_msgpack (bytes); + } + + optional HumanKeyFile::unlock_key (const Bytes & salt, const string & passphrase) + { Bytes seed; seed.resize (crypto_sign_SEEDBYTES); - Bytes salt; - salt.resize (crypto_pwhash_SALTBYTES); - randombytes_buf (salt.data (), salt.size ()); - if (crypto_pwhash ( seed.data (), seed.size (), passphrase.data (), passphrase.size (), @@ -142,6 +157,34 @@ namespace BareMinimumCrypto { return nullopt; } + return key; + } + + // The whole process for a passphrased key is like this: + // Passphrase + random salt --> + // Seed --> + // Secret key + Public key + + // Passphrases should be mandatory for keys that can sign other keys. + + optional HumanKeyFile::generate (const string & file_path, const string & passphrase) + { + try_sodium_init (); + + if (passphrase.size () < 8) { + return nullopt; + } + + Bytes salt; + salt.resize (crypto_pwhash_SALTBYTES); + randombytes_buf (salt.data (), salt.size ()); + + auto key_opt = unlock_key (salt, passphrase); + if (! key_opt) { + return nullopt; + } + const auto key = std::move (*key_opt); + const auto machine_id = get_machine_id (); HumanKeyFile key_on_disk { @@ -159,6 +202,16 @@ namespace BareMinimumCrypto { return key; } + optional HumanKeyFile::load (const string & file_path, const string & passphrase) + { + const auto j = std::move (*try_load_msgpack_file (file_path)); + const auto human_key = std::move (*HumanKeyFile::try_from_msgpack (j)); + + const auto key = std::move (*unlock_key (human_key.salt, passphrase)); + + return key; + } + optional MachineKeyFile::generate (const string & file_path) { const SigningKey key; diff --git a/bare_minimum_crypto/cpp/signing_key.h b/bare_minimum_crypto/cpp/signing_key.h index 5ac1838..4fe934e 100644 --- a/bare_minimum_crypto/cpp/signing_key.h +++ b/bare_minimum_crypto/cpp/signing_key.h @@ -15,14 +15,14 @@ namespace BareMinimumCrypto { using nlohmann::json; string get_machine_id (); + string get_passphrase_from_user (); + optional try_load_msgpack_file (const string & file_path); struct SigningKey { Bytes sk; SigningKey (); - static optional load_human_key_file (const string & file_path, const string & passphrase); - Bytes pubkey () const; Bytes pub_to_msgpack () const; @@ -47,6 +47,10 @@ namespace BareMinimumCrypto { static optional generate (const string & file_path, const string & passphrase); + static optional load (const string & file_path, const string & passphrase); + + static optional unlock_key (const Bytes & salt, const string & passphrase); + Bytes to_msgpack () const; static optional try_from_msgpack (const json & msg); };