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;
|
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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue