➕ update: add keygen for root keys
parent
49a30866df
commit
7b11633015
|
@ -1,2 +1,3 @@
|
||||||
/bmc_test
|
/bmc
|
||||||
|
*.key
|
||||||
*.o
|
*.o
|
||||||
|
|
|
@ -5,10 +5,10 @@ CXX_FLAGS=$(FLAGS) -c
|
||||||
L_FLAGS=$(FLAGS)
|
L_FLAGS=$(FLAGS)
|
||||||
LIBS=-lsodium
|
LIBS=-lsodium
|
||||||
|
|
||||||
bmc_test: bmc_test.o base64.o expiring_signature.o receiver.o sender.o signing_key.o sodium_helpers.o string_helpers.o time_helpers.o
|
bmc: bmc_main.o base64.o expiring_signature.o receiver.o sender.o signing_key.o sodium_helpers.o string_helpers.o time_helpers.o
|
||||||
$(CXX) -o $@ $^ $(L_FLAGS) $(LIBS)
|
$(CXX) -o $@ $^ $(L_FLAGS) $(LIBS)
|
||||||
|
|
||||||
bmc_test.o: bmc_test.cpp expiring_signature.h receiver.h sender.h signing_key.h sodium_helpers.h string_helpers.h time_helpers.h
|
bmc_main.o: bmc_main.cpp expiring_signature.h receiver.h sender.h signing_key.h sodium_helpers.h string_helpers.h time_helpers.h
|
||||||
$(CXX) -o $@ $(CXX_FLAGS) $<
|
$(CXX) -o $@ $(CXX_FLAGS) $<
|
||||||
|
|
||||||
base64.o: cpp-base64/base64.cpp cpp-base64/base64.h
|
base64.o: cpp-base64/base64.cpp cpp-base64/base64.h
|
||||||
|
@ -36,5 +36,5 @@ time_helpers.o: time_helpers.cpp time_helpers.h
|
||||||
$(CXX) -o $@ $(CXX_FLAGS) $<
|
$(CXX) -o $@ $(CXX_FLAGS) $<
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f bmc_test bmc_test.o base64.o expiring_signature.o receiver.o sender.o signing_key.o sodium_helpers.o string_helpers.o time_helpers.o
|
rm -f bmc bmc_main.o base64.o expiring_signature.o receiver.o sender.o signing_key.o sodium_helpers.o string_helpers.o time_helpers.o
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cxxopts.hpp"
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
|
|
||||||
#include "expiring_signature.h"
|
#include "expiring_signature.h"
|
||||||
|
@ -19,46 +20,14 @@ using namespace std;
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
using namespace BareMinimumCrypto;
|
using namespace BareMinimumCrypto;
|
||||||
|
|
||||||
string get_passphrase_from_user () {
|
int test () {
|
||||||
// In prod this would NOT be hard-coded.
|
if (test_base64 () != 0) {
|
||||||
return "Correct Horse Battery Staple";
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int happy_path () {
|
|
||||||
// We generate a root key and keep it somewhere safe
|
// We generate a root key and keep it somewhere safe
|
||||||
// (offline, hopefully)
|
// (offline, hopefully)
|
||||||
|
|
||||||
// Passphrases are mandatory for root keys, and BMC also generates
|
|
||||||
// a salt to maximize entropy.
|
|
||||||
const auto passphrase = get_passphrase_from_user ();
|
|
||||||
vector <uint8_t> seed;
|
|
||||||
seed.resize (crypto_sign_SEEDBYTES);
|
|
||||||
|
|
||||||
vector <uint8_t> salt;
|
|
||||||
salt.resize (crypto_pwhash_SALTBYTES);
|
|
||||||
randombytes_buf (salt.data (), salt.size ());
|
|
||||||
|
|
||||||
if (crypto_pwhash (
|
|
||||||
seed.data (), seed.size (),
|
|
||||||
passphrase.data (), passphrase.size (),
|
|
||||||
salt.data (),
|
|
||||||
crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
|
|
||||||
crypto_pwhash_ALG_DEFAULT
|
|
||||||
) != 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
vector <uint8_t> pk;
|
|
||||||
pk.resize (crypto_sign_PUBLICKEYBYTES);
|
|
||||||
vector <uint8_t> sk;
|
|
||||||
sk.resize (crypto_sign_SECRETKEYBYTES);
|
|
||||||
|
|
||||||
if (crypto_sign_seed_keypair (pk.data (), sk.data (), seed.data ()) != 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cerr << "Passphrased root pub key " << base64_encode (pk) << endl;
|
|
||||||
|
|
||||||
SigningKey root_key;
|
SigningKey root_key;
|
||||||
cerr << "Root pub key " << base64_encode (root_key.pubkey ()) << endl;
|
cerr << "Root pub key " << base64_encode (root_key.pubkey ()) << endl;
|
||||||
|
|
||||||
|
@ -99,15 +68,39 @@ int happy_path () {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main () {
|
int main (int argc, char ** argv) {
|
||||||
if (test_base64 () != 0) {
|
cxxopts::Options options ("BareMinimumCrypto", "Simple crypto things you might need.");
|
||||||
return 1;
|
options.add_options ()
|
||||||
}
|
("generate-ca-key", "Generate a passphrase-protected certificate authority key", cxxopts::value <string> ())
|
||||||
|
("check-ca-key", "Read information from a CA key without decrypting it")
|
||||||
|
("test", "Run self-test")
|
||||||
|
("help", "Print usage")
|
||||||
|
;
|
||||||
|
|
||||||
if (happy_path () != 0) {
|
auto result = options.parse (argc, argv);
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
cerr << "All good." << endl;
|
if (result.count ("help")) {
|
||||||
|
cout << options.help () << endl;
|
||||||
|
}
|
||||||
|
else if (result.count ("generate-ca-key")) {
|
||||||
|
const auto file_path = result ["generate-ca-key"].as <string> ();
|
||||||
|
cout << "Type passphrase (it will be visible in the console)" << endl;
|
||||||
|
string passphrase;
|
||||||
|
cin >> passphrase;
|
||||||
|
|
||||||
|
auto key_opt = SigningKey::generate_to_file (file_path, passphrase);
|
||||||
|
if (! key_opt) {
|
||||||
|
cerr << "Error. Key was not generated" << endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
cout << "The pubkey is `" << base64_encode (key_opt->pubkey ()) << "`" << endl;
|
||||||
|
}
|
||||||
|
else if (result.count ("test")) {
|
||||||
|
if (test () != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
cerr << "All good." << endl;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,7 @@
|
||||||
#include "signing_key.h"
|
#include "signing_key.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
|
|
||||||
#include "sodium_helpers.h"
|
#include "sodium_helpers.h"
|
||||||
|
@ -7,6 +9,62 @@
|
||||||
namespace BareMinimumCrypto {
|
namespace BareMinimumCrypto {
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
// 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 <SigningKey> SigningKey::generate_to_file (const string & file_path, const string & passphrase) {
|
||||||
|
if (passphrase.size () < 8) {
|
||||||
|
return nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector <uint8_t> seed;
|
||||||
|
seed.resize (crypto_sign_SEEDBYTES);
|
||||||
|
|
||||||
|
vector <uint8_t> salt;
|
||||||
|
salt.resize (crypto_pwhash_SALTBYTES);
|
||||||
|
randombytes_buf (salt.data (), salt.size ());
|
||||||
|
|
||||||
|
if (crypto_pwhash (
|
||||||
|
seed.data (), seed.size (),
|
||||||
|
passphrase.data (), passphrase.size (),
|
||||||
|
salt.data (),
|
||||||
|
crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
|
||||||
|
crypto_pwhash_ALG_DEFAULT
|
||||||
|
) != 0) {
|
||||||
|
return nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
SigningKey key;
|
||||||
|
key.pk.resize (crypto_sign_PUBLICKEYBYTES);
|
||||||
|
key.sk.resize (crypto_sign_SECRETKEYBYTES);
|
||||||
|
|
||||||
|
if (crypto_sign_seed_keypair (key.pk.data (), key.sk.data (), seed.data ()) != 0) {
|
||||||
|
return nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto j = json {
|
||||||
|
{"salt", json::binary (salt)},
|
||||||
|
{"time_created", Instant::now ().x},
|
||||||
|
{"pubkey", json::binary (key.pk)},
|
||||||
|
};
|
||||||
|
const auto msg = json::to_msgpack (j);
|
||||||
|
|
||||||
|
ofstream f;
|
||||||
|
f.open (file_path, ofstream::binary);
|
||||||
|
if (! f.is_open ()) {
|
||||||
|
return nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.write ((const char *)msg.data (), msg.size ());
|
||||||
|
f.close ();
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
SigningKey::SigningKey () {
|
SigningKey::SigningKey () {
|
||||||
try_sodium_init ();
|
try_sodium_init ();
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "expiring_signature.h"
|
#include "expiring_signature.h"
|
||||||
|
@ -10,14 +11,19 @@
|
||||||
namespace BareMinimumCrypto {
|
namespace BareMinimumCrypto {
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
class SigningKey {
|
struct SigningKey {
|
||||||
vector <uint8_t> pk;
|
vector <uint8_t> pk;
|
||||||
vector <uint8_t> sk;
|
vector <uint8_t> sk;
|
||||||
|
|
||||||
public:
|
|
||||||
SigningKey ();
|
SigningKey ();
|
||||||
|
|
||||||
//static optional <SigningKey> generate_to_file
|
// This doesn't fsync, so it's possible to lose the key due to a power outage
|
||||||
|
// or filesystem nonsense right after this function returns.
|
||||||
|
// It also doesn't do the rename trick. The caller may do that.
|
||||||
|
|
||||||
|
static optional <SigningKey> generate_to_file (const string & file_path, const string & passphrase);
|
||||||
|
|
||||||
|
static optional <SigningKey> load_from_file (const string & file_path, const string & passphrase);
|
||||||
|
|
||||||
vector <uint8_t> pubkey () const;
|
vector <uint8_t> pubkey () const;
|
||||||
vector <uint8_t> pub_to_msgpack () const;
|
vector <uint8_t> pub_to_msgpack () const;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
- Use libsodium's secure memory when handling keys / seeds / passphrases
|
Loading…
Reference in New Issue