megolm.cpp (5749B)
1 #include "megolm.hpp" 2 #include "pickle.hpp" 3 4 #include <botan/auto_rng.h> 5 #include <botan/hash.h> 6 #include <botan/mac.h> 7 8 9 /* Convenience macro for checking the return value of internal unpickling 10 * functions and returning early on failure. */ 11 #ifndef UNPICKLE_OK 12 #define UNPICKLE_OK(x) \ 13 do \ 14 { \ 15 if (!(x)) \ 16 return nullptr; \ 17 } \ 18 while (0) 19 #endif 20 21 /* the seeds used in the HMAC-SHA-256 functions for each part of the ratchet. 22 */ 23 #define HASH_KEY_SEED_LENGTH 1 24 static uint8_t HASH_KEY_SEEDS[MEGOLM_RATCHET_PARTS][HASH_KEY_SEED_LENGTH] = {{0x00}, {0x01}, {0x02}, {0x03}}; 25 26 27 namespace spank_olm 28 { 29 constexpr size_t UINT32_LENGTH = 4; 30 31 std::uint8_t *_olm_pickle_uint32(std::uint8_t *pos, uint32_t const value) { return pickle(pos, value); } 32 33 34 std::uint8_t const *_olm_unpickle_uint32(std::uint8_t const *pos, std::uint8_t const *end, std::uint32_t *value) 35 { 36 return unpickle(pos, end, *value); 37 } 38 39 40 std::uint8_t *_olm_pickle_bytes(std::uint8_t *pos, std::uint8_t const *bytes, size_t bytes_length) 41 { 42 return pickle_bytes(pos, bytes, bytes_length); 43 } 44 45 std::uint8_t const *_olm_unpickle_bytes(std::uint8_t const *pos, std::uint8_t const *end, std::uint8_t *bytes, 46 size_t bytes_length) 47 { 48 return unpickle_bytes(pos, end, bytes, bytes_length); 49 } 50 51 52 static void 53 rehash_part(std::array<std::array<std::uint8_t, MEGOLM_RATCHET_PART_LENGTH>, MEGOLM_RATCHET_PARTS> &data, 54 const int rehash_from_part, const int rehash_to_part) 55 { 56 const auto hmac = Botan::MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); 57 58 hmac->set_key(HASH_KEY_SEEDS[rehash_to_part], HASH_KEY_SEED_LENGTH); 59 hmac->update(data[rehash_from_part].data(), MEGOLM_RATCHET_PART_LENGTH); 60 hmac->final(data[rehash_to_part].data()); 61 } 62 63 64 void Megolm::init(Botan::RandomNumberGenerator &rng, const unsigned int counter) 65 { 66 this->counter = counter; 67 for (auto &part : data) 68 { 69 rng.randomize(part.data(), part.size()); 70 } 71 } 72 73 size_t Megolm::pickle_length() const { return data.size() * data[0].size() + UINT32_LENGTH; } 74 75 std::uint8_t *Megolm::pickle(std::uint8_t *pos) const 76 { 77 78 pos = _olm_pickle_bytes(pos, get_data(), MEGOLM_RATCHET_LENGTH); 79 pos = _olm_pickle_uint32(pos, counter); 80 return pos; 81 } 82 83 std::uint8_t const *Megolm::unpickle(std::uint8_t const *pos, const std::uint8_t *end) 84 { 85 pos = _olm_unpickle_bytes(pos, end, const_cast<std::uint8_t *>(get_data()), MEGOLM_RATCHET_LENGTH); 86 UNPICKLE_OK(pos); 87 88 pos = _olm_unpickle_uint32(pos, end, &counter); 89 UNPICKLE_OK(pos); 90 91 return pos; 92 } 93 94 void Megolm::advance() 95 { 96 counter++; 97 uint32_t mask = 0x00FFFFFF; 98 int h = 0; 99 100 /* figure out how much we need to rekey */ 101 while (h < MEGOLM_RATCHET_PARTS && (counter & mask)) 102 { 103 h++; 104 mask >>= 8; 105 } 106 107 /* now update R(h)...R(3) based on R(h) */ 108 for (int i = MEGOLM_RATCHET_PARTS - 1; i >= h; i--) 109 { 110 rehash_part(data, h, i); 111 } 112 } 113 114 void Megolm::advance(const unsigned int advance_to) 115 { 116 /* starting with R0, see if we need to update each part of the hash */ 117 for (int j = 0; j < MEGOLM_RATCHET_PARTS; j++) 118 { 119 const int shift = (MEGOLM_RATCHET_PARTS - j - 1) * 8; 120 const uint32_t mask = (~static_cast<uint32_t>(0)) << shift; 121 122 123 /* how many times do we need to rehash this part? 124 * 125 * '& 0xff' ensures we handle integer wraparound correctly 126 */ 127 unsigned int steps = ((advance_to >> shift) - (counter >> shift)) & 0xff; 128 129 if (steps == 0) 130 { 131 /* deal with the edge case where megolm->counter is slightly larger 132 * than advance_to. This should only happen for R(0), and implies 133 * that advance_to has wrapped around and we need to advance R(0) 134 * 256 times. 135 */ 136 if (advance_to < counter) 137 { 138 steps = 0x100; 139 } 140 else 141 { 142 continue; 143 } 144 } 145 146 /* for all but the last step, we can just bump R(j) without regard 147 * to R(j+1)...R(3). 148 */ 149 while (steps-- > 1) 150 { 151 rehash_part(data, j, j); 152 } 153 154 /* on the last step we also need to bump R(j+1)...R(3). 155 * 156 * (Theoretically, we could skip bumping R(j+2) if we're going to bump 157 * R(j+1) again, but the code to figure that out is a bit baroque and 158 * doesn't save us much). 159 */ 160 for (int k = 3; k >= j; k--) 161 { 162 rehash_part(data, j, k); 163 } 164 counter = advance_to & mask; 165 } 166 } 167 168 } // namespace spank_olm