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 simplermain
parent
278d591954
commit
cc1c7c9229
|
@ -50,28 +50,53 @@ int file (const string & file_path) {
|
|||
cout << "Schema: " << schema << endl;
|
||||
|
||||
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
|
||||
const Instant time_created (j ["time_created"]);
|
||||
const auto pubkey = j ["pubkey"].get_binary ();
|
||||
const string key_machine_id = j ["machine_id"];
|
||||
const auto key = std::move (*HumanKeyFile::try_from_msgpack (j));
|
||||
|
||||
// Read data from other places
|
||||
const auto now = Instant::now ();
|
||||
|
||||
// Print normal stuff
|
||||
cout << "Generated at Unix time " << time_created.x
|
||||
<< " (" << now.x - time_created.x << " seconds ago)"
|
||||
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 (pubkey) << 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 < time_created.x) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
if (fs::status (file_path).permissions () != fs::perms::owner_read) {
|
||||
|
@ -136,7 +161,8 @@ int test () {
|
|||
int main (int argc, char ** argv) {
|
||||
cxxopts::Options options ("BareMinimumCrypto", "Simple crypto things you might need.");
|
||||
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> ())
|
||||
("test", "Run self-test")
|
||||
("help", "Print usage")
|
||||
|
@ -147,13 +173,23 @@ int main (int argc, char ** argv) {
|
|||
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> ();
|
||||
else if (result.count ("generate-human-key")) {
|
||||
const auto file_path = result ["generate-human-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);
|
||||
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) {
|
||||
cerr << "Error. Key was not generated" << endl;
|
||||
return 1;
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace BareMinimumCrypto {
|
|||
return machine_id;
|
||||
}
|
||||
|
||||
vector <uint8_t> SigningKeyFile::to_msgpack () const {
|
||||
vector <uint8_t> HumanKeyFile::to_msgpack () const {
|
||||
const auto j = json {
|
||||
// Breaking changes should generate a new Base32 schema.
|
||||
{"schema", "3T6XF5DZ"},
|
||||
|
@ -35,6 +35,70 @@ namespace BareMinimumCrypto {
|
|||
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:
|
||||
// Passphrase + random salt -->
|
||||
// Seed -->
|
||||
|
@ -42,7 +106,10 @@ namespace BareMinimumCrypto {
|
|||
|
||||
// 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) {
|
||||
return nullopt;
|
||||
}
|
||||
|
@ -64,6 +131,7 @@ namespace BareMinimumCrypto {
|
|||
return nullopt;
|
||||
}
|
||||
|
||||
// This generates a redundant key but that's fine.
|
||||
SigningKey key;
|
||||
key.pk.resize (crypto_sign_PUBLICKEYBYTES);
|
||||
key.sk.resize (crypto_sign_SECRETKEYBYTES);
|
||||
|
@ -74,7 +142,7 @@ namespace BareMinimumCrypto {
|
|||
|
||||
const auto machine_id = get_machine_id ();
|
||||
|
||||
SigningKeyFile key_on_disk {
|
||||
HumanKeyFile key_on_disk {
|
||||
salt,
|
||||
Instant::now (),
|
||||
key.pk,
|
||||
|
@ -82,18 +150,29 @@ namespace BareMinimumCrypto {
|
|||
};
|
||||
const auto msg = key_on_disk.to_msgpack ();
|
||||
|
||||
ofstream f;
|
||||
f.open (file_path, ofstream::binary);
|
||||
if (! f.is_open ()) {
|
||||
if (! save_key_file (file_path, msg)) {
|
||||
return nullopt;
|
||||
}
|
||||
fs::permissions (file_path,
|
||||
fs::perms::owner_read,
|
||||
fs::perm_options::replace
|
||||
);
|
||||
|
||||
f.write ((const char *)msg.data (), msg.size ());
|
||||
f.close ();
|
||||
return key;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -5,22 +5,36 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
#include "expiring_signature.h"
|
||||
#include "time_helpers.h"
|
||||
|
||||
namespace BareMinimumCrypto {
|
||||
using namespace std;
|
||||
using nlohmann::json;
|
||||
|
||||
string get_machine_id ();
|
||||
|
||||
struct SigningKeyFile {
|
||||
struct HumanKeyFile {
|
||||
vector <uint8_t> salt;
|
||||
Instant time_created;
|
||||
vector <uint8_t> pubkey;
|
||||
string machine_id;
|
||||
|
||||
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 {
|
||||
|
@ -33,9 +47,11 @@ namespace BareMinimumCrypto {
|
|||
// 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> 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> pub_to_msgpack () const;
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
- 2PVHIKMA
|
||||
- 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.
|
||||
|
|
Loading…
Reference in New Issue