| 1 | /* Copyright 2015, 2016 OpenMarket Ltd |
| 2 | * |
| 3 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | * you may not use this file except in compliance with the License. |
| 5 | * You may obtain a copy of the License at |
| 6 | * |
| 7 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | * |
| 9 | * Unless required by applicable law or agreed to in writing, software |
| 10 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | * See the License for the specific language governing permissions and |
| 13 | * limitations under the License. |
| 14 | */ |
| 15 | #include "olm/ratchet.hh" |
| 16 | #include "olm/message.hh" |
| 17 | #include "olm/memory.hh" |
| 18 | #include "olm/cipher.h" |
| 19 | #include "olm/pickle.hh" |
| 20 | |
| 21 | #include <cstring> |
| 22 | |
| 23 | namespace { |
| 24 | |
| 25 | static const std::uint8_t PROTOCOL_VERSION = 3; |
| 26 | static const std::uint8_t MESSAGE_KEY_SEED[1] = {0x01}; |
| 27 | static const std::uint8_t CHAIN_KEY_SEED[1] = {0x02}; |
| 28 | static const std::size_t MAX_MESSAGE_GAP = 2000; |
| 29 | |
| 30 | |
| 31 | /** |
| 32 | * Advance the root key, creating a new message chain. |
| 33 | * |
| 34 | * @param root_key previous root key R(n-1) |
| 35 | * @param our_key our new ratchet key T(n) |
| 36 | * @param their_key their most recent ratchet key T(n-1) |
| 37 | * @param info table of constants for the ratchet function |
| 38 | * @param new_root_key[out] returns the new root key R(n) |
| 39 | * @param new_chain_key[out] returns the first chain key in the new chain |
| 40 | * C(n,0) |
| 41 | */ |
| 42 | static void create_chain_key( |
| 43 | olm::SharedKey const & root_key, |
| 44 | _olm_curve25519_key_pair const & our_key, |
| 45 | _olm_curve25519_public_key const & their_key, |
| 46 | olm::KdfInfo const & info, |
| 47 | olm::SharedKey & new_root_key, |
| 48 | olm::ChainKey & new_chain_key |
| 49 | ) { |
| 50 | olm::SharedKey secret; |
| 51 | _olm_crypto_curve25519_shared_secret(our_key: &our_key, their_key: &their_key, output: secret); |
| 52 | std::uint8_t derived_secrets[2 * olm::OLM_SHARED_KEY_LENGTH]; |
| 53 | _olm_crypto_hkdf_sha256( |
| 54 | input: secret, input_length: sizeof(secret), |
| 55 | info: root_key, info_length: sizeof(root_key), |
| 56 | salt: info.ratchet_info, salt_length: info.ratchet_info_length, |
| 57 | output: derived_secrets, output_length: sizeof(derived_secrets) |
| 58 | ); |
| 59 | std::uint8_t const * pos = derived_secrets; |
| 60 | pos = olm::load_array(destination&: new_root_key, source: pos); |
| 61 | pos = olm::load_array(destination&: new_chain_key.key, source: pos); |
| 62 | new_chain_key.index = 0; |
| 63 | olm::unset(value&: derived_secrets); |
| 64 | olm::unset(value&: secret); |
| 65 | } |
| 66 | |
| 67 | |
| 68 | static void advance_chain_key( |
| 69 | olm::ChainKey const & chain_key, |
| 70 | olm::ChainKey & new_chain_key |
| 71 | ) { |
| 72 | _olm_crypto_hmac_sha256( |
| 73 | key: chain_key.key, key_length: sizeof(chain_key.key), |
| 74 | input: CHAIN_KEY_SEED, input_length: sizeof(CHAIN_KEY_SEED), |
| 75 | output: new_chain_key.key |
| 76 | ); |
| 77 | new_chain_key.index = chain_key.index + 1; |
| 78 | } |
| 79 | |
| 80 | |
| 81 | static void create_message_keys( |
| 82 | olm::ChainKey const & chain_key, |
| 83 | olm::KdfInfo const & info, |
| 84 | olm::MessageKey & message_key) { |
| 85 | _olm_crypto_hmac_sha256( |
| 86 | key: chain_key.key, key_length: sizeof(chain_key.key), |
| 87 | input: MESSAGE_KEY_SEED, input_length: sizeof(MESSAGE_KEY_SEED), |
| 88 | output: message_key.key |
| 89 | ); |
| 90 | message_key.index = chain_key.index; |
| 91 | } |
| 92 | |
| 93 | |
| 94 | static std::size_t verify_mac_and_decrypt( |
| 95 | _olm_cipher const *cipher, |
| 96 | olm::MessageKey const & message_key, |
| 97 | olm::MessageReader const & reader, |
| 98 | std::uint8_t * plaintext, std::size_t max_plaintext_length |
| 99 | ) { |
| 100 | return cipher->ops->decrypt( |
| 101 | cipher, |
| 102 | message_key.key, sizeof(message_key.key), |
| 103 | reader.input, reader.input_length, |
| 104 | reader.ciphertext, reader.ciphertext_length, |
| 105 | plaintext, max_plaintext_length |
| 106 | ); |
| 107 | } |
| 108 | |
| 109 | |
| 110 | static std::size_t verify_mac_and_decrypt_for_existing_chain( |
| 111 | olm::Ratchet const & session, |
| 112 | olm::ChainKey const & chain, |
| 113 | olm::MessageReader const & reader, |
| 114 | std::uint8_t * plaintext, std::size_t max_plaintext_length |
| 115 | ) { |
| 116 | if (reader.counter < chain.index) { |
| 117 | return std::size_t(-1); |
| 118 | } |
| 119 | |
| 120 | /* Limit the number of hashes we're prepared to compute */ |
| 121 | if (reader.counter - chain.index > MAX_MESSAGE_GAP) { |
| 122 | return std::size_t(-1); |
| 123 | } |
| 124 | |
| 125 | olm::ChainKey new_chain = chain; |
| 126 | |
| 127 | while (new_chain.index < reader.counter) { |
| 128 | advance_chain_key(chain_key: new_chain, new_chain_key&: new_chain); |
| 129 | } |
| 130 | |
| 131 | olm::MessageKey message_key; |
| 132 | create_message_keys(chain_key: new_chain, info: session.kdf_info, message_key); |
| 133 | |
| 134 | std::size_t result = verify_mac_and_decrypt( |
| 135 | cipher: session.ratchet_cipher, message_key, reader, |
| 136 | plaintext, max_plaintext_length |
| 137 | ); |
| 138 | |
| 139 | olm::unset(value&: new_chain); |
| 140 | return result; |
| 141 | } |
| 142 | |
| 143 | |
| 144 | static std::size_t verify_mac_and_decrypt_for_new_chain( |
| 145 | olm::Ratchet const & session, |
| 146 | olm::MessageReader const & reader, |
| 147 | std::uint8_t * plaintext, std::size_t max_plaintext_length |
| 148 | ) { |
| 149 | olm::SharedKey new_root_key; |
| 150 | olm::ReceiverChain new_chain; |
| 151 | |
| 152 | /* They shouldn't move to a new chain until we've sent them a message |
| 153 | * acknowledging the last one */ |
| 154 | if (session.sender_chain.empty()) { |
| 155 | return std::size_t(-1); |
| 156 | } |
| 157 | |
| 158 | /* Limit the number of hashes we're prepared to compute */ |
| 159 | if (reader.counter > MAX_MESSAGE_GAP) { |
| 160 | return std::size_t(-1); |
| 161 | } |
| 162 | olm::load_array(destination&: new_chain.ratchet_key.public_key, source: reader.ratchet_key); |
| 163 | |
| 164 | create_chain_key( |
| 165 | root_key: session.root_key, our_key: session.sender_chain[0].ratchet_key, |
| 166 | their_key: new_chain.ratchet_key, info: session.kdf_info, |
| 167 | new_root_key, new_chain_key&: new_chain.chain_key |
| 168 | ); |
| 169 | std::size_t result = verify_mac_and_decrypt_for_existing_chain( |
| 170 | session, chain: new_chain.chain_key, reader, |
| 171 | plaintext, max_plaintext_length |
| 172 | ); |
| 173 | olm::unset(value&: new_root_key); |
| 174 | olm::unset(value&: new_chain); |
| 175 | return result; |
| 176 | } |
| 177 | |
| 178 | } // namespace |
| 179 | |
| 180 | |
| 181 | olm::Ratchet::Ratchet( |
| 182 | olm::KdfInfo const & kdf_info, |
| 183 | _olm_cipher const * ratchet_cipher |
| 184 | ) : kdf_info(kdf_info), |
| 185 | ratchet_cipher(ratchet_cipher), |
| 186 | last_error(OlmErrorCode::OLM_SUCCESS) { |
| 187 | } |
| 188 | |
| 189 | |
| 190 | void olm::Ratchet::initialise_as_bob( |
| 191 | std::uint8_t const * shared_secret, std::size_t shared_secret_length, |
| 192 | _olm_curve25519_public_key const & their_ratchet_key |
| 193 | ) { |
| 194 | std::uint8_t derived_secrets[2 * olm::OLM_SHARED_KEY_LENGTH]; |
| 195 | _olm_crypto_hkdf_sha256( |
| 196 | input: shared_secret, input_length: shared_secret_length, |
| 197 | info: nullptr, info_length: 0, |
| 198 | salt: kdf_info.root_info, salt_length: kdf_info.root_info_length, |
| 199 | output: derived_secrets, output_length: sizeof(derived_secrets) |
| 200 | ); |
| 201 | receiver_chains.insert(); |
| 202 | receiver_chains[0].chain_key.index = 0; |
| 203 | std::uint8_t const * pos = derived_secrets; |
| 204 | pos = olm::load_array(destination&: root_key, source: pos); |
| 205 | pos = olm::load_array(destination&: receiver_chains[0].chain_key.key, source: pos); |
| 206 | receiver_chains[0].ratchet_key = their_ratchet_key; |
| 207 | olm::unset(value&: derived_secrets); |
| 208 | } |
| 209 | |
| 210 | |
| 211 | void olm::Ratchet::initialise_as_alice( |
| 212 | std::uint8_t const * shared_secret, std::size_t shared_secret_length, |
| 213 | _olm_curve25519_key_pair const & our_ratchet_key |
| 214 | ) { |
| 215 | std::uint8_t derived_secrets[2 * olm::OLM_SHARED_KEY_LENGTH]; |
| 216 | _olm_crypto_hkdf_sha256( |
| 217 | input: shared_secret, input_length: shared_secret_length, |
| 218 | info: nullptr, info_length: 0, |
| 219 | salt: kdf_info.root_info, salt_length: kdf_info.root_info_length, |
| 220 | output: derived_secrets, output_length: sizeof(derived_secrets) |
| 221 | ); |
| 222 | sender_chain.insert(); |
| 223 | sender_chain[0].chain_key.index = 0; |
| 224 | std::uint8_t const * pos = derived_secrets; |
| 225 | pos = olm::load_array(destination&: root_key, source: pos); |
| 226 | pos = olm::load_array(destination&: sender_chain[0].chain_key.key, source: pos); |
| 227 | sender_chain[0].ratchet_key = our_ratchet_key; |
| 228 | olm::unset(value&: derived_secrets); |
| 229 | } |
| 230 | |
| 231 | namespace olm { |
| 232 | |
| 233 | |
| 234 | static std::size_t pickle_length( |
| 235 | const olm::SharedKey & value |
| 236 | ) { |
| 237 | return olm::OLM_SHARED_KEY_LENGTH; |
| 238 | } |
| 239 | |
| 240 | |
| 241 | static std::uint8_t * pickle( |
| 242 | std::uint8_t * pos, |
| 243 | const olm::SharedKey & value |
| 244 | ) { |
| 245 | return olm::pickle_bytes(pos, bytes: value, bytes_length: olm::OLM_SHARED_KEY_LENGTH); |
| 246 | } |
| 247 | |
| 248 | |
| 249 | static std::uint8_t const * unpickle( |
| 250 | std::uint8_t const * pos, std::uint8_t const * end, |
| 251 | olm::SharedKey & value |
| 252 | ) { |
| 253 | return olm::unpickle_bytes(pos, end, bytes: value, bytes_length: olm::OLM_SHARED_KEY_LENGTH); |
| 254 | } |
| 255 | |
| 256 | |
| 257 | static std::size_t pickle_length( |
| 258 | const olm::SenderChain & value |
| 259 | ) { |
| 260 | std::size_t length = 0; |
| 261 | length += olm::pickle_length(value: value.ratchet_key); |
| 262 | length += olm::pickle_length(value: value.chain_key.key); |
| 263 | length += olm::pickle_length(value: value.chain_key.index); |
| 264 | return length; |
| 265 | } |
| 266 | |
| 267 | |
| 268 | static std::uint8_t * pickle( |
| 269 | std::uint8_t * pos, |
| 270 | const olm::SenderChain & value |
| 271 | ) { |
| 272 | pos = olm::pickle(pos, value: value.ratchet_key); |
| 273 | pos = olm::pickle(pos, value: value.chain_key.key); |
| 274 | pos = olm::pickle(pos, value: value.chain_key.index); |
| 275 | return pos; |
| 276 | } |
| 277 | |
| 278 | |
| 279 | static std::uint8_t const * unpickle( |
| 280 | std::uint8_t const * pos, std::uint8_t const * end, |
| 281 | olm::SenderChain & value |
| 282 | ) { |
| 283 | pos = olm::unpickle(pos, end, value&: value.ratchet_key); UNPICKLE_OK(pos); |
| 284 | pos = olm::unpickle(pos, end, value&: value.chain_key.key); UNPICKLE_OK(pos); |
| 285 | pos = olm::unpickle(pos, end, value&: value.chain_key.index); UNPICKLE_OK(pos); |
| 286 | return pos; |
| 287 | } |
| 288 | |
| 289 | static std::size_t pickle_length( |
| 290 | const olm::ReceiverChain & value |
| 291 | ) { |
| 292 | std::size_t length = 0; |
| 293 | length += olm::pickle_length(value: value.ratchet_key); |
| 294 | length += olm::pickle_length(value: value.chain_key.key); |
| 295 | length += olm::pickle_length(value: value.chain_key.index); |
| 296 | return length; |
| 297 | } |
| 298 | |
| 299 | |
| 300 | static std::uint8_t * pickle( |
| 301 | std::uint8_t * pos, |
| 302 | const olm::ReceiverChain & value |
| 303 | ) { |
| 304 | pos = olm::pickle(pos, value: value.ratchet_key); |
| 305 | pos = olm::pickle(pos, value: value.chain_key.key); |
| 306 | pos = olm::pickle(pos, value: value.chain_key.index); |
| 307 | return pos; |
| 308 | } |
| 309 | |
| 310 | |
| 311 | static std::uint8_t const * unpickle( |
| 312 | std::uint8_t const * pos, std::uint8_t const * end, |
| 313 | olm::ReceiverChain & value |
| 314 | ) { |
| 315 | pos = olm::unpickle(pos, end, value&: value.ratchet_key); UNPICKLE_OK(pos); |
| 316 | pos = olm::unpickle(pos, end, value&: value.chain_key.key); UNPICKLE_OK(pos); |
| 317 | pos = olm::unpickle(pos, end, value&: value.chain_key.index); UNPICKLE_OK(pos); |
| 318 | return pos; |
| 319 | } |
| 320 | |
| 321 | |
| 322 | static std::size_t pickle_length( |
| 323 | const olm::SkippedMessageKey & value |
| 324 | ) { |
| 325 | std::size_t length = 0; |
| 326 | length += olm::pickle_length(value: value.ratchet_key); |
| 327 | length += olm::pickle_length(value: value.message_key.key); |
| 328 | length += olm::pickle_length(value: value.message_key.index); |
| 329 | return length; |
| 330 | } |
| 331 | |
| 332 | |
| 333 | static std::uint8_t * pickle( |
| 334 | std::uint8_t * pos, |
| 335 | const olm::SkippedMessageKey & value |
| 336 | ) { |
| 337 | pos = olm::pickle(pos, value: value.ratchet_key); |
| 338 | pos = olm::pickle(pos, value: value.message_key.key); |
| 339 | pos = olm::pickle(pos, value: value.message_key.index); |
| 340 | return pos; |
| 341 | } |
| 342 | |
| 343 | |
| 344 | static std::uint8_t const * unpickle( |
| 345 | std::uint8_t const * pos, std::uint8_t const * end, |
| 346 | olm::SkippedMessageKey & value |
| 347 | ) { |
| 348 | pos = olm::unpickle(pos, end, value&: value.ratchet_key); UNPICKLE_OK(pos); |
| 349 | pos = olm::unpickle(pos, end, value&: value.message_key.key); UNPICKLE_OK(pos); |
| 350 | pos = olm::unpickle(pos, end, value&: value.message_key.index); UNPICKLE_OK(pos); |
| 351 | return pos; |
| 352 | } |
| 353 | |
| 354 | |
| 355 | } // namespace olm |
| 356 | |
| 357 | |
| 358 | std::size_t olm::pickle_length( |
| 359 | olm::Ratchet const & value |
| 360 | ) { |
| 361 | std::size_t length = 0; |
| 362 | length += olm::OLM_SHARED_KEY_LENGTH; |
| 363 | length += olm::pickle_length(list: value.sender_chain); |
| 364 | length += olm::pickle_length(list: value.receiver_chains); |
| 365 | length += olm::pickle_length(list: value.skipped_message_keys); |
| 366 | return length; |
| 367 | } |
| 368 | |
| 369 | std::uint8_t * olm::pickle( |
| 370 | std::uint8_t * pos, |
| 371 | olm::Ratchet const & value |
| 372 | ) { |
| 373 | pos = pickle(pos, value: value.root_key); |
| 374 | pos = pickle(pos, list: value.sender_chain); |
| 375 | pos = pickle(pos, list: value.receiver_chains); |
| 376 | pos = pickle(pos, list: value.skipped_message_keys); |
| 377 | return pos; |
| 378 | } |
| 379 | |
| 380 | |
| 381 | std::uint8_t const * olm::unpickle( |
| 382 | std::uint8_t const * pos, std::uint8_t const * end, |
| 383 | olm::Ratchet & value, |
| 384 | bool includes_chain_index |
| 385 | ) { |
| 386 | pos = unpickle(pos, end, value&: value.root_key); UNPICKLE_OK(pos); |
| 387 | pos = unpickle(pos, end, list&: value.sender_chain); UNPICKLE_OK(pos); |
| 388 | pos = unpickle(pos, end, list&: value.receiver_chains); UNPICKLE_OK(pos); |
| 389 | pos = unpickle(pos, end, list&: value.skipped_message_keys); UNPICKLE_OK(pos); |
| 390 | |
| 391 | // pickle v 0x80000001 includes a chain index; pickle v1 does not. |
| 392 | if (includes_chain_index) { |
| 393 | std::uint32_t dummy; |
| 394 | pos = unpickle(pos, end, value&: dummy); UNPICKLE_OK(pos); |
| 395 | } |
| 396 | return pos; |
| 397 | } |
| 398 | |
| 399 | |
| 400 | std::size_t olm::Ratchet::encrypt_output_length( |
| 401 | std::size_t plaintext_length |
| 402 | ) const { |
| 403 | std::size_t counter = 0; |
| 404 | if (!sender_chain.empty()) { |
| 405 | counter = sender_chain[0].chain_key.index; |
| 406 | } |
| 407 | std::size_t padded = ratchet_cipher->ops->encrypt_ciphertext_length( |
| 408 | ratchet_cipher, |
| 409 | plaintext_length |
| 410 | ); |
| 411 | return olm::encode_message_length( |
| 412 | counter, CURVE25519_KEY_LENGTH, ciphertext_length: padded, mac_length: ratchet_cipher->ops->mac_length(ratchet_cipher) |
| 413 | ); |
| 414 | } |
| 415 | |
| 416 | |
| 417 | std::size_t olm::Ratchet::encrypt_random_length() const { |
| 418 | return sender_chain.empty() ? CURVE25519_RANDOM_LENGTH : 0; |
| 419 | } |
| 420 | |
| 421 | |
| 422 | std::size_t olm::Ratchet::encrypt( |
| 423 | std::uint8_t const * plaintext, std::size_t plaintext_length, |
| 424 | std::uint8_t const * random, std::size_t random_length, |
| 425 | std::uint8_t * output, std::size_t max_output_length |
| 426 | ) { |
| 427 | std::size_t output_length = encrypt_output_length(plaintext_length); |
| 428 | |
| 429 | if (random_length < encrypt_random_length()) { |
| 430 | last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; |
| 431 | return std::size_t(-1); |
| 432 | } |
| 433 | if (max_output_length < output_length) { |
| 434 | last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; |
| 435 | return std::size_t(-1); |
| 436 | } |
| 437 | |
| 438 | if (sender_chain.empty()) { |
| 439 | sender_chain.insert(); |
| 440 | _olm_crypto_curve25519_generate_key(random_32_bytes: random, output: &sender_chain[0].ratchet_key); |
| 441 | create_chain_key( |
| 442 | root_key, |
| 443 | our_key: sender_chain[0].ratchet_key, |
| 444 | their_key: receiver_chains[0].ratchet_key, |
| 445 | info: kdf_info, |
| 446 | new_root_key&: root_key, new_chain_key&: sender_chain[0].chain_key |
| 447 | ); |
| 448 | } |
| 449 | |
| 450 | MessageKey keys; |
| 451 | create_message_keys(chain_key: sender_chain[0].chain_key, info: kdf_info, message_key&: keys); |
| 452 | advance_chain_key(chain_key: sender_chain[0].chain_key, new_chain_key&: sender_chain[0].chain_key); |
| 453 | |
| 454 | std::size_t ciphertext_length = ratchet_cipher->ops->encrypt_ciphertext_length( |
| 455 | ratchet_cipher, |
| 456 | plaintext_length |
| 457 | ); |
| 458 | std::uint32_t counter = keys.index; |
| 459 | _olm_curve25519_public_key const & ratchet_key = |
| 460 | sender_chain[0].ratchet_key.public_key; |
| 461 | |
| 462 | olm::MessageWriter writer; |
| 463 | |
| 464 | olm::encode_message( |
| 465 | writer, version: PROTOCOL_VERSION, counter, CURVE25519_KEY_LENGTH, |
| 466 | ciphertext_length, |
| 467 | output |
| 468 | ); |
| 469 | |
| 470 | olm::store_array(destination: writer.ratchet_key, source: ratchet_key.public_key); |
| 471 | |
| 472 | ratchet_cipher->ops->encrypt( |
| 473 | ratchet_cipher, |
| 474 | keys.key, sizeof(keys.key), |
| 475 | plaintext, plaintext_length, |
| 476 | writer.ciphertext, ciphertext_length, |
| 477 | output, output_length |
| 478 | ); |
| 479 | |
| 480 | olm::unset(value&: keys); |
| 481 | return output_length; |
| 482 | } |
| 483 | |
| 484 | |
| 485 | std::size_t olm::Ratchet::decrypt_max_plaintext_length( |
| 486 | std::uint8_t const * input, std::size_t input_length |
| 487 | ) { |
| 488 | olm::MessageReader reader; |
| 489 | olm::decode_message( |
| 490 | reader, input, input_length, |
| 491 | mac_length: ratchet_cipher->ops->mac_length(ratchet_cipher) |
| 492 | ); |
| 493 | |
| 494 | if (!reader.ciphertext) { |
| 495 | last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; |
| 496 | return std::size_t(-1); |
| 497 | } |
| 498 | |
| 499 | return ratchet_cipher->ops->decrypt_max_plaintext_length( |
| 500 | ratchet_cipher, reader.ciphertext_length); |
| 501 | } |
| 502 | |
| 503 | |
| 504 | std::size_t olm::Ratchet::decrypt( |
| 505 | std::uint8_t const * input, std::size_t input_length, |
| 506 | std::uint8_t * plaintext, std::size_t max_plaintext_length |
| 507 | ) { |
| 508 | olm::MessageReader reader; |
| 509 | olm::decode_message( |
| 510 | reader, input, input_length, |
| 511 | mac_length: ratchet_cipher->ops->mac_length(ratchet_cipher) |
| 512 | ); |
| 513 | |
| 514 | if (reader.version != PROTOCOL_VERSION) { |
| 515 | last_error = OlmErrorCode::OLM_BAD_MESSAGE_VERSION; |
| 516 | return std::size_t(-1); |
| 517 | } |
| 518 | |
| 519 | if (!reader.has_counter || !reader.ratchet_key || !reader.ciphertext) { |
| 520 | last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; |
| 521 | return std::size_t(-1); |
| 522 | } |
| 523 | |
| 524 | std::size_t max_length = ratchet_cipher->ops->decrypt_max_plaintext_length( |
| 525 | ratchet_cipher, |
| 526 | reader.ciphertext_length |
| 527 | ); |
| 528 | |
| 529 | if (max_plaintext_length < max_length) { |
| 530 | last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; |
| 531 | return std::size_t(-1); |
| 532 | } |
| 533 | |
| 534 | if (reader.ratchet_key_length != CURVE25519_KEY_LENGTH) { |
| 535 | last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; |
| 536 | return std::size_t(-1); |
| 537 | } |
| 538 | |
| 539 | ReceiverChain * chain = nullptr; |
| 540 | |
| 541 | for (olm::ReceiverChain & receiver_chain : receiver_chains) { |
| 542 | if (0 == std::memcmp( |
| 543 | s1: receiver_chain.ratchet_key.public_key, s2: reader.ratchet_key, |
| 544 | CURVE25519_KEY_LENGTH |
| 545 | )) { |
| 546 | chain = &receiver_chain; |
| 547 | break; |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | std::size_t result = std::size_t(-1); |
| 552 | |
| 553 | if (!chain) { |
| 554 | result = verify_mac_and_decrypt_for_new_chain( |
| 555 | session: *this, reader, plaintext, max_plaintext_length |
| 556 | ); |
| 557 | } else if (chain->chain_key.index > reader.counter) { |
| 558 | /* Chain already advanced beyond the key for this message |
| 559 | * Check if the message keys are in the skipped key list. */ |
| 560 | for (olm::SkippedMessageKey & skipped : skipped_message_keys) { |
| 561 | if (reader.counter == skipped.message_key.index |
| 562 | && 0 == std::memcmp( |
| 563 | s1: skipped.ratchet_key.public_key, s2: reader.ratchet_key, |
| 564 | CURVE25519_KEY_LENGTH |
| 565 | ) |
| 566 | ) { |
| 567 | /* Found the key for this message. Check the MAC. */ |
| 568 | |
| 569 | result = verify_mac_and_decrypt( |
| 570 | cipher: ratchet_cipher, message_key: skipped.message_key, reader, |
| 571 | plaintext, max_plaintext_length |
| 572 | ); |
| 573 | |
| 574 | if (result != std::size_t(-1)) { |
| 575 | /* Remove the key from the skipped keys now that we've |
| 576 | * decoded the message it corresponds to. */ |
| 577 | olm::unset(value&: skipped); |
| 578 | skipped_message_keys.erase(pos: &skipped); |
| 579 | return result; |
| 580 | } |
| 581 | } |
| 582 | } |
| 583 | } else { |
| 584 | result = verify_mac_and_decrypt_for_existing_chain( |
| 585 | session: *this, chain: chain->chain_key, |
| 586 | reader, plaintext, max_plaintext_length |
| 587 | ); |
| 588 | } |
| 589 | |
| 590 | if (result == std::size_t(-1)) { |
| 591 | last_error = OlmErrorCode::OLM_BAD_MESSAGE_MAC; |
| 592 | return std::size_t(-1); |
| 593 | } |
| 594 | |
| 595 | if (!chain) { |
| 596 | /* They have started using a new ephemeral ratchet key. |
| 597 | * We need to derive a new set of chain keys. |
| 598 | * We can discard our previous ephemeral ratchet key. |
| 599 | * We will generate a new key when we send the next message. */ |
| 600 | |
| 601 | chain = receiver_chains.insert(); |
| 602 | olm::load_array(destination&: chain->ratchet_key.public_key, source: reader.ratchet_key); |
| 603 | |
| 604 | // TODO: we've already done this once, in |
| 605 | // verify_mac_and_decrypt_for_new_chain(). we could reuse the result. |
| 606 | create_chain_key( |
| 607 | root_key, our_key: sender_chain[0].ratchet_key, their_key: chain->ratchet_key, |
| 608 | info: kdf_info, new_root_key&: root_key, new_chain_key&: chain->chain_key |
| 609 | ); |
| 610 | |
| 611 | olm::unset(value&: sender_chain[0]); |
| 612 | sender_chain.erase(pos: sender_chain.begin()); |
| 613 | } |
| 614 | |
| 615 | while (chain->chain_key.index < reader.counter) { |
| 616 | olm::SkippedMessageKey & key = *skipped_message_keys.insert(); |
| 617 | create_message_keys(chain_key: chain->chain_key, info: kdf_info, message_key&: key.message_key); |
| 618 | key.ratchet_key = chain->ratchet_key; |
| 619 | advance_chain_key(chain_key: chain->chain_key, new_chain_key&: chain->chain_key); |
| 620 | } |
| 621 | |
| 622 | advance_chain_key(chain_key: chain->chain_key, new_chain_key&: chain->chain_key); |
| 623 | |
| 624 | return result; |
| 625 | } |
| 626 | |