#include #include #include #include #include #include #include // #include "cpp-base64/base64.h" #include "json.hpp" #include "expiring_signature.h" #include "receiver.h" #include "sodium_helpers.h" #include "string_helpers.h" #include "time_helpers.h" using namespace std; using nlohmann::json; using namespace BareMinimumCrypto; class SigningKey { vector pk; vector sk; public: SigningKey () { try_sodium_init (); pk.resize (crypto_sign_PUBLICKEYBYTES); sk.resize (crypto_sign_SECRETKEYBYTES); crypto_sign_keypair (pk.data (), sk.data ()); } vector pubkey () const { return pk; } string pub_to_base64 () const { return base64_encode (pk); } optional sign_base64 ( const string & payload_b64, 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_b64", payload_b64}, }; const auto cert_s = j.dump (); vector sig; sig.resize (crypto_sign_BYTES); crypto_sign_detached (sig.data (), nullptr, (const uint8_t *)cert_s.data (), cert_s.size (), sk.data ()); return ExpiringSignature { cert_s, sig, }; } optional sign_key (const SigningKey & k, Instant now) const { return sign_base64 (k.pub_to_base64 (), TimeRange::from_start_and_dur (now, about_3_months)); } optional sign_data (const vector & v, Instant now) const { return sign_base64 (base64_encode (v), TimeRange::from_start_and_dur (now, about_1_week)); } }; int happy_path () { // We generate a root key and keep it somewhere safe // (offline, hopefully) SigningKey root_key; cerr << "Root pub key " << root_key.pub_to_base64 () << endl; if (test_time () != 0) { return 1; } // The server generates a signing key SigningKey signing_key; cerr << "Signing key " << signing_key.pub_to_base64 () << endl; const auto now = Instant::now (); // That signing key signs some important data const auto important_data = copy_to_bytes ("Nikolai, Anna, Ivan, Mikhail, Ivan, Nikolai, Anna. 7 4 1 4 3 5 7 4"); const ExpiringSignature signed_data = std::move (*signing_key.sign_data (important_data, now)); // The server signs our temporary signing key const ExpiringSignature cert = std::move (*root_key.sign_key (signing_key, now)); { // Check that a different time results in a different cert const auto cert_2 = std::move (*root_key.sign_key (signing_key, now)); const auto cert_3 = std::move (*root_key.sign_key (signing_key, Instant {now.x + 1})); if (cert != cert_2) { cerr << "Certs should have been identical" << endl; return 1; } if (cert == cert_3) { cerr << "Certs should have been different" << endl; return 1; } if (cert == cert_3) { cerr << "Signatures should have been different" << endl; return 1; } } { cerr << "Cert:" << endl; cerr << cert.cert_s << endl; cerr << base64_encode (cert.sig) << endl; } // The receiver verifies the data by the root public key, // even though the receiver has never seen the sub-key. const auto root_pubkey = root_key.pubkey (); auto verified_opt = Receiver::verify_cert_and_data (cert, signed_data, root_pubkey); if (! verified_opt) { cerr << "Receiver couldn't verify cert and data" << endl; return 1; } const auto verified = std::move (*verified_opt); if (verified != important_data) { cerr << "Verified payload did not match expected payload" << endl; return 1; } return 0; } int main () { if (test_base64 () != 0) { return 1; } if (happy_path () != 0) { return 1; } cerr << "All good." << endl; return 0; }