spank-olm

WIP Do not look
git clone git://archive.git.mtrnord.blog/MTRNord/spank-olm.git
Log | Files | Refs | README | LICENSE

account.cpp (9041B)


      1 #include "account.hpp"
      2 #include "errors.hpp"
      3 #include "pickle.hpp"
      4 
      5 #include <botan/pubkey.h>
      6 #include <botan/rng.h>
      7 
      8 namespace spank_olm
      9 {
     10     void Account::new_account(Botan::RandomNumberGenerator &rng)
     11     {
     12         identity_keys = IdentityKeys{Botan::Ed25519_PrivateKey(rng), Botan::X25519_PrivateKey(rng)};
     13 
     14         // Make sure we check the key pairs.
     15         if (!identity_keys->ed25519_key.check_key(rng, false) || !identity_keys->curve25519_key.check_key(rng, false) ||
     16             !identity_keys->ed25519_key.public_key()->check_key(rng, false) ||
     17             !identity_keys->curve25519_key.public_key()->check_key(rng, false))
     18         {
     19             throw SpankOlmErrorKeyGeneration();
     20         }
     21     }
     22 
     23 
     24     [[nodiscard]] std::string Account::get_identity_json() const
     25     {
     26         auto curve25519_key = identity_keys->curve25519_key.public_key()->raw_public_key_bits();
     27         auto ed25519_key = identity_keys->ed25519_key.public_key()->raw_public_key_bits();
     28 
     29         const auto curve25519_base64 = Botan::base64_encode(curve25519_key);
     30         const auto ed25519_base64 = Botan::base64_encode(ed25519_key);
     31 
     32         return R"({"curve25519": ")" + curve25519_base64 + R"(", "ed25519": ")" + ed25519_base64 + "\"}";
     33     }
     34 
     35     std::vector<uint8_t> Account::sign(Botan::RandomNumberGenerator &rng, const std::string_view message) const
     36     {
     37         // According to https://botan.randombit.net/handbook/api_ref/pubkey.html#ed25519-ed448-variants
     38         const std::string padding_scheme = "Ed25519ph";
     39 
     40         // Use the Ed25519 key to sign the message using the Botan library.
     41 
     42 
     43         Botan::PK_Signer signer(identity_keys->ed25519_key, rng, padding_scheme);
     44         signer.update(message);
     45         auto signature = signer.signature(rng);
     46 
     47         return signature;
     48     }
     49 
     50     std::size_t Account::mark_keys_as_published()
     51     {
     52         auto count = 0;
     53         for (const auto &key : one_time_keys)
     54         {
     55             if (!key->published)
     56             {
     57                 key->published = true;
     58                 ++count;
     59             }
     60         }
     61 
     62         current_fallback_key->published = true;
     63         return count;
     64     }
     65 
     66     void Account::generate_one_time_keys(Botan::RandomNumberGenerator &rng, const std::size_t number_of_keys)
     67     {
     68         for (std::size_t i = 0; i < number_of_keys; ++i)
     69         {
     70             one_time_keys.insert({++next_one_time_key_id, false, Botan::X25519_PrivateKey(rng)});
     71         }
     72     }
     73 
     74     void Account::generate_fallback_key(Botan::RandomNumberGenerator &rng)
     75     {
     76         prev_fallback_key = current_fallback_key;
     77         current_fallback_key = OneTimeKey{++next_one_time_key_id, false, Botan::X25519_PrivateKey(rng)};
     78     }
     79 
     80     void Account::forget_old_fallback_key()
     81     {
     82         if (current_fallback_key && prev_fallback_key)
     83         {
     84             // TODO: Verify if this is correct.
     85             prev_fallback_key.reset();
     86         }
     87     }
     88 
     89     std::optional<OneTimeKey const *> Account::lookup_key(Botan::Public_Key const &key) const
     90     {
     91         for (const auto &one_time_key : one_time_keys)
     92         {
     93             if (one_time_key->key.public_key()->raw_public_key_bits() == key.raw_public_key_bits())
     94             {
     95                 return one_time_key;
     96             }
     97         }
     98         if (current_fallback_key &&
     99             current_fallback_key->key.public_key()->raw_public_key_bits() == key.raw_public_key_bits())
    100         {
    101             return &current_fallback_key.value();
    102         }
    103         if (prev_fallback_key &&
    104             prev_fallback_key->key.public_key()->raw_public_key_bits() == key.raw_public_key_bits())
    105         {
    106             return &current_fallback_key.value();
    107         }
    108         return std::nullopt;
    109     }
    110 
    111     void Account::remove_key(Botan::Public_Key const &key)
    112     {
    113         // Use iterator to find and remove the key.
    114         for (const auto &one_time_key : one_time_keys)
    115         {
    116             if (one_time_key->key.public_key()->raw_public_key_bits() == key.raw_public_key_bits())
    117             {
    118                 one_time_keys.erase(one_time_key);
    119                 return;
    120             }
    121         }
    122     }
    123 
    124 
    125     namespace
    126     {
    127         /**
    128          * \brief The current version of the account pickle format.
    129          *
    130          * \details
    131          * - Version 1 used only 32 bytes for the ed25519 private key. Any keys thus used should be considered
    132          * compromised.
    133          * - Version 2 does not have fallback keys.
    134          * - Version 3 does not store whether the current fallback key is published.
    135          */
    136         constexpr std::uint32_t ACCOUNT_PICKLE_VERSION = 4;
    137     } // namespace
    138 
    139 
    140     /**
    141      * Serializes the Account object into a byte array.
    142      *
    143      * @return A vector of uint8_t containing the serialized data.
    144      */
    145     std::vector<uint8_t> Account::pickle() const
    146     {
    147         std::vector<uint8_t> buffer(1024); // Initial buffer size, can be adjusted
    148         auto pos = buffer.data();
    149 
    150         pos = spank_olm::pickle(pos, ACCOUNT_PICKLE_VERSION);
    151 
    152         pos = spank_olm::pickle(pos, identity_keys);
    153 
    154         pos = spank_olm::pickle(pos, one_time_keys);
    155 
    156         // Calculate the number of fallback keys
    157         std::uint8_t fallback_key_count = 0;
    158         if (current_fallback_key && current_fallback_key->published)
    159             fallback_key_count++;
    160         if (prev_fallback_key && prev_fallback_key->published)
    161             fallback_key_count++;
    162 
    163         // Serialize the fallback key count
    164         pos = spank_olm::pickle(pos, fallback_key_count);
    165 
    166         if (current_fallback_key)
    167         {
    168             pos = spank_olm::pickle(pos, current_fallback_key);
    169             if (prev_fallback_key)
    170             {
    171                 pos = spank_olm::pickle(pos, prev_fallback_key);
    172             }
    173         }
    174 
    175         pos = spank_olm::pickle(pos, next_one_time_key_id);
    176 
    177         buffer.resize(pos - buffer.data()); // Adjust buffer size to actual data size
    178         return buffer;
    179     }
    180 
    181     /**
    182      * Deserializes an Account object from a byte array.
    183      *
    184      * @param data A vector of uint8_t containing the serialized data.
    185      * @return The deserialized Account object.
    186      * @throws SpankOlmErrorVersionNotFound if the pickle version is not found.
    187      * @throws SpankOlmErrorBadLegacyAccountPickle if the pickle version is 1.
    188      * @throws SpankOlmErrorUnknownPickleVersion if the pickle version is unknown.
    189      * @throws SpankOlmErrorCorruptedAccountPickle if the pickle data is corrupted.
    190      */
    191     Account Account::unpickle(std::vector<uint8_t> const &data)
    192     {
    193         Account value;
    194         auto pos = data.data();
    195         const auto end = data.data() + data.size();
    196         uint32_t pickle_version;
    197 
    198         pos = spank_olm::unpickle(pos, end, pickle_version);
    199         if (!pos)
    200         {
    201             throw SpankOlmErrorVersionNotFound();
    202         }
    203 
    204         switch (pickle_version)
    205         {
    206         case ACCOUNT_PICKLE_VERSION:
    207         case 3:
    208         case 2:
    209             break;
    210         case 1:
    211             throw SpankOlmErrorBadLegacyAccountPickle();
    212         default:
    213             throw SpankOlmErrorUnknownPickleVersion();
    214         }
    215 
    216         pos = spank_olm::unpickle(pos, end, value.identity_keys);
    217         if (!pos)
    218         {
    219             throw SpankOlmErrorCorruptedAccountPickle();
    220         }
    221         pos = spank_olm::unpickle(pos, end, value.one_time_keys);
    222         if (!pos)
    223         {
    224             throw SpankOlmErrorCorruptedAccountPickle();
    225         }
    226 
    227         if (pickle_version == 3)
    228         {
    229             pos = spank_olm::unpickle(pos, end, value.current_fallback_key);
    230             if (!pos)
    231             {
    232                 throw SpankOlmErrorCorruptedAccountPickle();
    233             }
    234             pos = spank_olm::unpickle(pos, end, value.prev_fallback_key);
    235             if (!pos)
    236             {
    237                 throw SpankOlmErrorCorruptedAccountPickle();
    238             }
    239         }
    240         else
    241         {
    242             std::uint8_t num_fallback_keys;
    243             pos = spank_olm::unpickle(pos, end, num_fallback_keys);
    244             if (!pos)
    245             {
    246                 throw SpankOlmErrorCorruptedAccountPickle();
    247             }
    248             if (num_fallback_keys >= 1)
    249             {
    250                 pos = spank_olm::unpickle(pos, end, value.current_fallback_key);
    251                 if (!pos)
    252                 {
    253                     throw SpankOlmErrorCorruptedAccountPickle();
    254                 }
    255                 if (num_fallback_keys >= 2)
    256                 {
    257                     pos = spank_olm::unpickle(pos, end, value.prev_fallback_key);
    258                     if (!pos)
    259                     {
    260                         throw SpankOlmErrorCorruptedAccountPickle();
    261                     }
    262                     if (num_fallback_keys >= 3)
    263                     {
    264                         throw SpankOlmErrorCorruptedAccountPickle();
    265                     }
    266                 }
    267             }
    268         }
    269 
    270         pos = spank_olm::unpickle(pos, end, value.next_one_time_key_id);
    271         if (!pos)
    272         {
    273             throw SpankOlmErrorCorruptedAccountPickle();
    274         }
    275 
    276         return value;
    277     }
    278 } // namespace spank_olm