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 ¤t_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 ¤t_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