#include "signing_key.h" #include #include #include "json.hpp" #include "sodium_helpers.h" namespace BareMinimumCrypto { using nlohmann::json; namespace fs = std::filesystem; string get_machine_id () { ifstream f; f.open ("/etc/machine-id", ifstream::binary); string machine_id; if (! f.is_open ()) { return machine_id; } f >> machine_id; return machine_id; } vector HumanKeyFile::to_msgpack () const { const auto j = json { // Breaking changes should generate a new Base32 schema. {"schema", "3T6XF5DZ"}, {"salt", json::binary (salt)}, {"time_created", time_created.x}, {"pubkey", json::binary (pubkey)}, {"machine_id", machine_id}, }; return json::to_msgpack (j); } optional 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 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::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 MachineKeyFile::pubkey () const { vector 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 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 --> // Secret key + Public key // Passphrases should be mandatory for keys that can sign other keys. optional SigningKey::generate_human_key_file (const string & file_path, const string & passphrase) { try_sodium_init (); if (passphrase.size () < 8) { return nullopt; } vector seed; seed.resize (crypto_sign_SEEDBYTES); vector 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; } // This generates a redundant key but that's fine. 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 machine_id = get_machine_id (); HumanKeyFile key_on_disk { salt, Instant::now (), key.pk, machine_id, }; const auto msg = key_on_disk.to_msgpack (); if (! save_key_file (file_path, msg)) { return nullopt; } return key; } optional 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; } SigningKey::SigningKey () { try_sodium_init (); pk.resize (crypto_sign_PUBLICKEYBYTES); sk.resize (crypto_sign_SECRETKEYBYTES); crypto_sign_keypair (pk.data (), sk.data ()); } vector SigningKey::pubkey () const { return pk; } vector SigningKey::pub_to_msgpack () const { const json j = { {"key", json::binary (pk)}, }; return json::to_msgpack (j); } optional SigningKey::sign ( const vector & payload, TimeRange tr ) const { try_sodium_init (); if (tr.duration () > about_1_year) { return nullopt; } const json j { {"not_before", tr.not_before}, {"not_after", tr.not_after}, {"payload", json::binary (payload)}, }; const auto cert = json::to_msgpack (j); vector sig; sig.resize (crypto_sign_BYTES); crypto_sign_detached (sig.data (), nullptr, cert.data (), cert.size (), sk.data ()); return ExpiringSignature { cert, sig, }; } optional SigningKey::sign_key (const SigningKey & k, Instant now) const { return sign (k.pub_to_msgpack (), TimeRange::from_start_and_dur (now, about_3_months)); } optional SigningKey::sign_data (const vector & v, Instant now) const { return sign (v, TimeRange::from_start_and_dur (now, about_1_week)); } }