update: human and machine files working well.

It turns out the bate secret key for machine files already has the pubkey
cached in it by libsodium, so I dropped that.
I'm also going to drop it from the internals, it seems simpler
main
_ 2021-01-19 19:01:27 -06:00
parent 278d591954
commit cc1c7c9229
4 changed files with 168 additions and 31 deletions

View File

@ -50,28 +50,53 @@ int file (const string & file_path) {
cout << "Schema: " << schema << endl; cout << "Schema: " << schema << endl;
if (schema == "3T6XF5DZ") { if (schema == "3T6XF5DZ") {
cout << "File is a passphrase-protected secret key" << endl; cout << "File is a secret key for humans (It's passphrase-protected)" << endl;
// Read msgpack fields // Read msgpack fields
const Instant time_created (j ["time_created"]); const auto key = std::move (*HumanKeyFile::try_from_msgpack (j));
const auto pubkey = j ["pubkey"].get_binary ();
const string key_machine_id = j ["machine_id"];
// Read data from other places // Read data from other places
const auto now = Instant::now (); const auto now = Instant::now ();
// Print normal stuff // Print normal stuff
cout << "Generated at Unix time " << time_created.x cout << "Generated at Unix time " << key.time_created.x
<< " (" << now.x - time_created.x << " seconds ago)" << " (" << now.x - key.time_created.x << " seconds ago)"
<< endl; << endl;
cout << "Generated on machine ID " << key_machine_id << endl; cout << "Generated on machine ID " << key.machine_id << endl;
cout << "Claims to have Base64 pubkey " << base64_encode (pubkey) << endl; cout << "Claims to have Base64 pubkey " << base64_encode (key.pubkey) << endl;
// Print warnings // Print warnings
if (now.x < time_created.x) { if (now.x < key.time_created.x) {
cout << "* The key was generated in the past. Someone's clock is wrong." << endl; cout << "* The key was generated in the past. Someone's clock is wrong." << endl;
} }
if (get_machine_id () != key_machine_id) { if (get_machine_id () != key.machine_id) {
cout << "* The key was generated on another machine. You should report this." << endl;
}
if (fs::status (file_path).permissions () != fs::perms::owner_read) {
cout << "* The key doesn't have the right permissions. Try `chmod 400` on it." << endl;
}
}
else if (schema == "2PVHIKMA") {
cout << "File is a secret key for machines (No passphrase)" << endl;
// Read msgpack fields
const auto key = std::move (*MachineKeyFile::try_from_msgpack (j));
// Read data from other places
const auto now = Instant::now ();
// Print normal stuff
cout << "Generated at Unix time " << key.time_created.x
<< " (" << now.x - key.time_created.x << " seconds ago)"
<< endl;
cout << "Generated on machine ID " << key.machine_id << endl;
cout << "Claims to have Base64 pubkey " << base64_encode (key.pubkey ()) << endl;
// Print warnings
if (now.x < key.time_created.x) {
cout << "* The key was generated in the past. Someone's clock is wrong." << endl;
}
if (get_machine_id () != key.machine_id) {
cout << "* The key was generated on another machine. You should report this." << endl; cout << "* The key was generated on another machine. You should report this." << endl;
} }
if (fs::status (file_path).permissions () != fs::perms::owner_read) { if (fs::status (file_path).permissions () != fs::perms::owner_read) {
@ -136,7 +161,8 @@ int test () {
int main (int argc, char ** argv) { int main (int argc, char ** argv) {
cxxopts::Options options ("BareMinimumCrypto", "Simple crypto things you might need."); cxxopts::Options options ("BareMinimumCrypto", "Simple crypto things you might need.");
options.add_options () options.add_options ()
("generate-ca-key", "Generate a passphrase-protected certificate authority key", cxxopts::value <string> ()) ("generate-human-key", "Generate a passphrase-protected key for human use", cxxopts::value <string> ())
("generate-machine-key", "Generate a key for machine use, with no passphrase", cxxopts::value <string> ())
("file", "Print info about any file generated by BMC", cxxopts::value <string> ()) ("file", "Print info about any file generated by BMC", cxxopts::value <string> ())
("test", "Run self-test") ("test", "Run self-test")
("help", "Print usage") ("help", "Print usage")
@ -147,13 +173,23 @@ int main (int argc, char ** argv) {
if (result.count ("help")) { if (result.count ("help")) {
cout << options.help () << endl; cout << options.help () << endl;
} }
else if (result.count ("generate-ca-key")) { else if (result.count ("generate-human-key")) {
const auto file_path = result ["generate-ca-key"].as <string> (); const auto file_path = result ["generate-human-key"].as <string> ();
cout << "Type passphrase (it will be visible in the console)" << endl; cout << "Type passphrase (it will be visible in the console)" << endl;
string passphrase; string passphrase;
cin >> passphrase; cin >> passphrase;
auto key_opt = SigningKey::generate_to_file (file_path, passphrase); auto key_opt = SigningKey::generate_human_key_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 ("generate-machine-key")) {
const auto file_path = result ["generate-machine-key"].as <string> ();
auto key_opt = SigningKey::generate_machine_key_file (file_path);
if (! key_opt) { if (! key_opt) {
cerr << "Error. Key was not generated" << endl; cerr << "Error. Key was not generated" << endl;
return 1; return 1;

View File

@ -23,7 +23,7 @@ namespace BareMinimumCrypto {
return machine_id; return machine_id;
} }
vector <uint8_t> SigningKeyFile::to_msgpack () const { vector <uint8_t> HumanKeyFile::to_msgpack () const {
const auto j = json { const auto j = json {
// Breaking changes should generate a new Base32 schema. // Breaking changes should generate a new Base32 schema.
{"schema", "3T6XF5DZ"}, {"schema", "3T6XF5DZ"},
@ -35,6 +35,70 @@ namespace BareMinimumCrypto {
return json::to_msgpack (j); return json::to_msgpack (j);
} }
optional <HumanKeyFile> HumanKeyFile::try_from_msgpack (const json & j)
{
if (j ["schema"] != "3T6XF5DZ") {
return nullopt;
}
return HumanKeyFile {
j ["salt"].get_binary (),
Instant (j ["time_created"]),
j ["pubkey"].get_binary (),
j ["machine_id"],
};
}
vector <uint8_t> MachineKeyFile::to_msgpack () const {
const auto j = json {
// Breaking changes should generate a new Base32 schema.
{"schema", "2PVHIKMA"},
{"secretkey", json::binary (secretkey)},
{"time_created", time_created.x},
{"machine_id", machine_id},
};
return json::to_msgpack (j);
}
optional <MachineKeyFile> MachineKeyFile::try_from_msgpack (const json & j)
{
if (j ["schema"] != "2PVHIKMA") {
return nullopt;
}
return MachineKeyFile {
j ["secretkey"].get_binary (),
Instant (j ["time_created"]),
j ["machine_id"],
};
}
vector <uint8_t> MachineKeyFile::pubkey () const {
vector <uint8_t> pk;
pk.resize (crypto_sign_PUBLICKEYBYTES);
crypto_sign_ed25519_sk_to_pk (pk.data (), secretkey.data ());
return pk;
}
bool save_key_file (const string & file_path, const vector <uint8_t> msg)
{
ofstream f;
f.open (file_path, ofstream::binary);
if (! f.is_open ()) {
return false;
}
// Best-effort. It probably fails on Windows.
fs::permissions (file_path,
fs::perms::owner_read,
fs::perm_options::replace
);
f.write ((const char *)msg.data (), msg.size ());
f.close ();
return true;
}
// The whole process for a passphrased key is like this: // The whole process for a passphrased key is like this:
// Passphrase + random salt --> // Passphrase + random salt -->
// Seed --> // Seed -->
@ -42,7 +106,10 @@ namespace BareMinimumCrypto {
// Passphrases should be mandatory for keys that can sign other keys. // Passphrases should be mandatory for keys that can sign other keys.
optional <SigningKey> SigningKey::generate_to_file (const string & file_path, const string & passphrase) { optional <SigningKey> SigningKey::generate_human_key_file (const string & file_path, const string & passphrase)
{
try_sodium_init ();
if (passphrase.size () < 8) { if (passphrase.size () < 8) {
return nullopt; return nullopt;
} }
@ -64,6 +131,7 @@ namespace BareMinimumCrypto {
return nullopt; return nullopt;
} }
// This generates a redundant key but that's fine.
SigningKey key; SigningKey key;
key.pk.resize (crypto_sign_PUBLICKEYBYTES); key.pk.resize (crypto_sign_PUBLICKEYBYTES);
key.sk.resize (crypto_sign_SECRETKEYBYTES); key.sk.resize (crypto_sign_SECRETKEYBYTES);
@ -74,7 +142,7 @@ namespace BareMinimumCrypto {
const auto machine_id = get_machine_id (); const auto machine_id = get_machine_id ();
SigningKeyFile key_on_disk { HumanKeyFile key_on_disk {
salt, salt,
Instant::now (), Instant::now (),
key.pk, key.pk,
@ -82,18 +150,29 @@ namespace BareMinimumCrypto {
}; };
const auto msg = key_on_disk.to_msgpack (); const auto msg = key_on_disk.to_msgpack ();
ofstream f; if (! save_key_file (file_path, msg)) {
f.open (file_path, ofstream::binary);
if (! f.is_open ()) {
return nullopt; return nullopt;
} }
fs::permissions (file_path,
fs::perms::owner_read,
fs::perm_options::replace
);
f.write ((const char *)msg.data (), msg.size ()); return key;
f.close (); }
optional <SigningKey> SigningKey::generate_machine_key_file (const string & file_path)
{
const SigningKey key;
const auto machine_id = get_machine_id ();
MachineKeyFile key_on_disk {
key.sk,
Instant::now (),
machine_id,
};
const auto msg = key_on_disk.to_msgpack ();
if (! save_key_file (file_path, msg)) {
return nullopt;
}
return key; return key;
} }

View File

@ -5,22 +5,36 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "json.hpp"
#include "expiring_signature.h" #include "expiring_signature.h"
#include "time_helpers.h" #include "time_helpers.h"
namespace BareMinimumCrypto { namespace BareMinimumCrypto {
using namespace std; using namespace std;
using nlohmann::json;
string get_machine_id (); string get_machine_id ();
struct SigningKeyFile { struct HumanKeyFile {
vector <uint8_t> salt; vector <uint8_t> salt;
Instant time_created; Instant time_created;
vector <uint8_t> pubkey; vector <uint8_t> pubkey;
string machine_id; string machine_id;
vector <uint8_t> to_msgpack () const; vector <uint8_t> to_msgpack () const;
static optional <SigningKeyFile> try_from_msgpack (const vector <uint8_t> & msg); static optional <HumanKeyFile> try_from_msgpack (const json & msg);
};
struct MachineKeyFile {
vector <uint8_t> secretkey;
Instant time_created;
string machine_id;
vector <uint8_t> to_msgpack () const;
static optional <MachineKeyFile> try_from_msgpack (const json & msg);
vector <uint8_t> pubkey () const;
}; };
struct SigningKey { struct SigningKey {
@ -33,9 +47,11 @@ namespace BareMinimumCrypto {
// or filesystem nonsense right after this function returns. // or filesystem nonsense right after this function returns.
// It also doesn't do the rename trick. The caller may do that. // 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> generate_human_key_file (const string & file_path, const string & passphrase);
static optional <SigningKey> load_from_file (const string & file_path, const string & passphrase); static optional <SigningKey> generate_machine_key_file (const string & file_path);
static optional <SigningKey> load_human_key_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;

View File

@ -1,3 +1,9 @@
- 2PVHIKMA
- 3T6XF5DZ - 3T6XF5DZ
3T6XF5DZ is a secret key protected by a passphrase. 2PVHIKMA is a secret key for machines. It has no passphrase, so
computer programs can use it automatically.
3T6XF5DZ is a secret key for humans. It is protected by a passphrase.
The passphrase should be saved in a password manager or written on paper,
and not kept in the same disk as the key.