diff --git a/bare_minimum_crypto/cpp/.gitignore b/bare_minimum_crypto/cpp/.gitignore index 61d165a..35aba71 100644 --- a/bare_minimum_crypto/cpp/.gitignore +++ b/bare_minimum_crypto/cpp/.gitignore @@ -1,3 +1,4 @@ /bmc +*.cert *.key *.o diff --git a/bare_minimum_crypto/cpp/bmc_main.cpp b/bare_minimum_crypto/cpp/bmc_main.cpp index 8aec328..dd4c5ee 100644 --- a/bare_minimum_crypto/cpp/bmc_main.cpp +++ b/bare_minimum_crypto/cpp/bmc_main.cpp @@ -28,12 +28,24 @@ int file (const string & file_path) { const auto j = std::move (*try_load_msgpack_file (file_path)); + if (! j.contains ("app")) { + cout << "Can't find app magic string. This file was probably not made by BMC." << endl; + return 1; + } + + const string app = j ["app"]; + + if (app != "4B27CL32") { + cout << "Can't find app magic string. This file was probably not made by BMC." << endl; + return 1; + } + const string schema = j ["schema"]; cout << "Schema: " << schema << endl; if (schema == "3T6XF5DZ") { - cout << "File is a secret key for humans (It's passphrase-protected)" << endl; + cout << "File is a secret key for humans. (It's passphrase-protected)" << endl; // Read msgpack fields const auto key = std::move (*HumanKeyFile::try_from_msgpack (j)); @@ -60,7 +72,7 @@ int file (const string & file_path) { } } else if (schema == "2PVHIKMA") { - cout << "File is a secret key for machines (No passphrase)" << endl; + cout << "File is a secret key for machines. (No passphrase)" << endl; // Read msgpack fields const auto key = std::move (*MachineKeyFile::try_from_msgpack (j)); @@ -86,8 +98,47 @@ int file (const string & file_path) { cout << "* The key doesn't have the right permissions. Try `chmod 400` on it." << endl; } } + else if (schema == "MSYZGDBI") { + cout << "File is a signed certificate, possibly for a pubkey." << endl; + + cout << "Claims to be signed by pubkey `" << base64_encode (j ["signer_pubkey"].get_binary ()) << "`" << endl; + + const auto cert_bytes = j ["cert"].get_binary (); + const auto cert = json::from_msgpack (cert_bytes); + + const TimeRange tr { + cert ["not_before"], + cert ["not_after"], + }; + + const auto now = Instant::now (); + + if (! tr.contains (now)) { + cout << "Cert is NOT valid." << endl; + + if (now.x <= tr.not_before) { + cout << "Cert is from the future. Is your clock behind? Is the signer's clock ahead?" << endl; + } + else if (now.x >= tr.not_after) { + cout << "Cert expired " << now.x - tr.not_after << " seconds ago. Don't trust it." << endl; + } + return 1; + } + + cout << "Cert is valid for another " << tr.not_after - now.x << " seconds." << endl; + + const auto payload = cert ["payload"].get_binary (); + + if (payload.size () == crypto_sign_PUBLICKEYBYTES) { + cout << "Payload might be the pubkey `" << base64_encode (payload) << "`" << endl; + } + else { + cout << "Payload doesn't have the right size to be a pubkey." << endl; + } + } else { cout << "Unknown schema. Maybe this file is from a newer version of BMC?" << endl; + return 1; } return 0; @@ -118,7 +169,7 @@ int test () { cerr << "Sender key " << base64_encode (sender_key.pubkey ()) << endl; // The root signs the sender key - const ExpiringSignature sender_cert = std::move (*root_key.sign_key (sender_key, now)); + const auto sender_cert = std::move (*root_key.sign_key (sender_key.pubkey (), now)); const auto sender = std::move (*Sender::create (sender_key, sender_cert)); @@ -221,8 +272,17 @@ int main (int argc, char ** argv) { return 1; } const auto sig = std::move (*sig_opt); + const auto sig_bytes = sig.to_msgpack (); + ofstream f; + f.open (output_path, ofstream::binary); + if (! f.is_open ()) { + cerr << "Error, could not open output file" << endl; + return 1; + } + + f.write ((const char *)sig_bytes.data (), sig_bytes.size ()); } else if (result.count ("test")) { if (test () != 0) { diff --git a/bare_minimum_crypto/cpp/expiring_signature.cpp b/bare_minimum_crypto/cpp/expiring_signature.cpp index 8853a0b..171f759 100644 --- a/bare_minimum_crypto/cpp/expiring_signature.cpp +++ b/bare_minimum_crypto/cpp/expiring_signature.cpp @@ -13,6 +13,18 @@ namespace BareMinimumCrypto { return ! (*this == o); } + Bytes ExpiringSignature::to_msgpack () const { + const json j { + {"app", "4B27CL32"}, + {"schema", "MSYZGDBI"}, + {"sig", json::binary (sig)}, + {"cert", json::binary (cert)}, + {"signer_pubkey", json::binary (signer_pubkey)}, + }; + + return json::to_msgpack (j); + } + Bytes KeyCertFile::to_msgpack () const { const json cert_j { {"pubkey", json::binary (pubkey)}, @@ -22,12 +34,10 @@ namespace BareMinimumCrypto { const auto cert = json::to_msgpack (cert_j); - const json j { - {"sig", json::binary (sig)}, - {"cert", json::binary (cert)}, - }; - - return json::to_msgpack (j); + return ExpiringSignature { + cert, + sig, + }.to_msgpack (); } optional KeyCertFile::try_from_msgpack (const json & msg) diff --git a/bare_minimum_crypto/cpp/expiring_signature.h b/bare_minimum_crypto/cpp/expiring_signature.h index 16a7ce1..2b7fe25 100644 --- a/bare_minimum_crypto/cpp/expiring_signature.h +++ b/bare_minimum_crypto/cpp/expiring_signature.h @@ -18,9 +18,12 @@ namespace BareMinimumCrypto { // Payload is contained in here Bytes cert; Bytes sig; + Bytes signer_pubkey; bool operator == (const ExpiringSignature & o) const; bool operator != (const ExpiringSignature & o) const; + + Bytes to_msgpack () const; }; struct KeyCertFile { diff --git a/bare_minimum_crypto/cpp/signing_key.cpp b/bare_minimum_crypto/cpp/signing_key.cpp index dc01db7..1e9dbb3 100644 --- a/bare_minimum_crypto/cpp/signing_key.cpp +++ b/bare_minimum_crypto/cpp/signing_key.cpp @@ -26,6 +26,8 @@ namespace BareMinimumCrypto { Bytes HumanKeyFile::to_msgpack () const { const auto j = json { + // All BMC msgpack artifacts should have this string + {"app", "4B27CL32"}, // Breaking changes should generate a new Base32 schema. {"schema", "3T6XF5DZ"}, {"salt", json::binary (salt)}, @@ -38,6 +40,9 @@ namespace BareMinimumCrypto { optional HumanKeyFile::try_from_msgpack (const json & j) { + if (j ["app"] != "4B27CL32") { + return nullopt; + } if (j ["schema"] != "3T6XF5DZ") { return nullopt; } @@ -52,6 +57,8 @@ namespace BareMinimumCrypto { Bytes MachineKeyFile::to_msgpack () const { const auto j = json { + // All BMC msgpack artifacts should have this string + {"app", "4B27CL32"}, // Breaking changes should generate a new Base32 schema. {"schema", "2PVHIKMA"}, {"secretkey", json::binary (secretkey)}, @@ -63,6 +70,9 @@ namespace BareMinimumCrypto { optional MachineKeyFile::try_from_msgpack (const json & j) { + if (j ["app"] != "4B27CL32") { + return nullopt; + } if (j ["schema"] != "2PVHIKMA") { return nullopt; } @@ -282,12 +292,13 @@ namespace BareMinimumCrypto { return ExpiringSignature { cert, sig, + pubkey (), }; } - optional SigningKey::sign_key (const SigningKey & k, Instant now) const + optional SigningKey::sign_key (const Bytes & pubkey, Instant now) const { - return sign (k.pub_to_msgpack (), TimeRange::from_start_and_dur (now, about_3_months)); + return sign (pubkey, TimeRange::from_start_and_dur (now, about_3_months)); } optional SigningKey::sign_data (const Bytes & v, Instant now) const diff --git a/bare_minimum_crypto/cpp/signing_key.h b/bare_minimum_crypto/cpp/signing_key.h index 4fe934e..0334cb6 100644 --- a/bare_minimum_crypto/cpp/signing_key.h +++ b/bare_minimum_crypto/cpp/signing_key.h @@ -31,7 +31,7 @@ namespace BareMinimumCrypto { TimeRange tr ) const; - optional sign_key (const SigningKey & k, Instant now) const; + optional sign_key (const Bytes & pubkey, Instant now) const; optional sign_data (const Bytes & v, Instant now) const; }; diff --git a/bare_minimum_crypto/schemas.md b/bare_minimum_crypto/schemas.md index 5eaa6d6..c59c452 100644 --- a/bare_minimum_crypto/schemas.md +++ b/bare_minimum_crypto/schemas.md @@ -1,5 +1,7 @@ - 2PVHIKMA - 3T6XF5DZ +- 4B27CL32 +- MSYZGDBI 2PVHIKMA is a secret key for machines. It has no passphrase, so computer programs can use it automatically. @@ -7,3 +9,11 @@ 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. + +4B27CL32 can be used by older versions of BMC to differentiate between +generic Msgpack files, and files created by newer versions of BMC. This is +in addition to the schema, since older versions won't recognize newer +schemas. + +MSYZGDBI is a signed certificate. The payload is in the file and may be a +public key.