#include #include #include #include #include #include #include #include "cpp-base64/base64.h" #include "json.hpp" #include "string_helpers.h" #include "time_helpers.h" using namespace std; using nlohmann::json; using namespace BareMinimumCrypto; struct ExpiringSignature { string cert_s; vector sig; // C++ nonsense bool operator == (const ExpiringSignature & o) const { return cert_s == o.cert_s && sig == o.sig ; } bool operator != (const ExpiringSignature & o) const { return ! (*this == o); } }; void try_sodium_init () { if (sodium_init () < 0) { throw std::runtime_error ("Can't initialize libsodium"); } } bool is_pubkey_length (const vector & v) { return v.size () == crypto_sign_PUBLICKEYBYTES; } optional > try_verify_signed_data ( const ExpiringSignature & sig, const vector & pubkey, Instant now ) { try_sodium_init (); if (! is_pubkey_length (pubkey)) { return nullopt; } if (crypto_sign_verify_detached ( sig.sig.data (), (const uint8_t *)sig.cert_s.data (), sig.cert_s.size (), pubkey.data () ) != 0) { return nullopt; } const json j = json::parse (sig.cert_s); const TimeRange tr { j ["not_before"], j ["not_after"] }; if (! tr.contains (now)) { return nullopt; } const string payload_b64 = j ["payload_b64"]; const auto payload = std::move (*BareMinimumCrypto::base64_decode (payload_b64)); return payload; } optional > verify_signed_data ( const ExpiringSignature & sig, const vector & pubkey, Instant now ) { try { return try_verify_signed_data (sig, pubkey, now); } catch (json::exception &) { return nullopt; } } optional > verify_cert_and_data ( const ExpiringSignature & signed_cert, const ExpiringSignature & signed_data, const vector & root_pubkey, Instant now ) { auto subkey_opt = verify_signed_data (signed_cert, root_pubkey, now); if (! subkey_opt) { return nullopt; } const auto subkey = std::move (*subkey_opt); return verify_signed_data (signed_data, subkey, now); } optional > verify_cert_and_data ( const ExpiringSignature & signed_cert, const ExpiringSignature & signed_data, const vector & root_pubkey ) { return verify_cert_and_data (signed_cert, signed_data, root_pubkey, Instant::now ()); } optional > verify_signed_data ( const ExpiringSignature & sig, const vector & pubkey ) { return verify_signed_data (sig, pubkey, Instant::now ()); } 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 = 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; }