X-Git-Url: https://gitweb.ps.run/matrix_esp_thesis/blobdiff_plain/21c6e8484b0bd05c27e5a91f2884d431926adc61..da776f86b42946715c27edd64f7558b9d5080df1:/ext/olm/src/session.cpp diff --git a/ext/olm/src/session.cpp b/ext/olm/src/session.cpp new file mode 100644 index 0000000..732e0c0 --- /dev/null +++ b/ext/olm/src/session.cpp @@ -0,0 +1,531 @@ +/* Copyright 2015, 2016 OpenMarket Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "olm/session.hh" +#include "olm/cipher.h" +#include "olm/crypto.h" +#include "olm/account.hh" +#include "olm/memory.hh" +#include "olm/message.hh" +#include "olm/pickle.hh" + +#include +#include + +namespace { + +static const std::uint8_t PROTOCOL_VERSION = 0x3; + +static const std::uint8_t ROOT_KDF_INFO[] = "OLM_ROOT"; +static const std::uint8_t RATCHET_KDF_INFO[] = "OLM_RATCHET"; +static const std::uint8_t CIPHER_KDF_INFO[] = "OLM_KEYS"; + +static const olm::KdfInfo OLM_KDF_INFO = { + ROOT_KDF_INFO, sizeof(ROOT_KDF_INFO) - 1, + RATCHET_KDF_INFO, sizeof(RATCHET_KDF_INFO) - 1 +}; + +static const struct _olm_cipher_aes_sha_256 OLM_CIPHER = + OLM_CIPHER_INIT_AES_SHA_256(CIPHER_KDF_INFO); + +} // namespace + +olm::Session::Session( +) : ratchet(OLM_KDF_INFO, OLM_CIPHER_BASE(&OLM_CIPHER)), + last_error(OlmErrorCode::OLM_SUCCESS), + received_message(false) { + +} + + +std::size_t olm::Session::new_outbound_session_random_length() const { + return CURVE25519_RANDOM_LENGTH * 2; +} + + +std::size_t olm::Session::new_outbound_session( + olm::Account const & local_account, + _olm_curve25519_public_key const & identity_key, + _olm_curve25519_public_key const & one_time_key, + std::uint8_t const * random, std::size_t random_length +) { + if (random_length < new_outbound_session_random_length()) { + last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; + return std::size_t(-1); + } + + _olm_curve25519_key_pair base_key; + _olm_crypto_curve25519_generate_key(random, &base_key); + + _olm_curve25519_key_pair ratchet_key; + _olm_crypto_curve25519_generate_key(random + CURVE25519_RANDOM_LENGTH, &ratchet_key); + + _olm_curve25519_key_pair const & alice_identity_key_pair = ( + local_account.identity_keys.curve25519_key + ); + + received_message = false; + alice_identity_key = alice_identity_key_pair.public_key; + alice_base_key = base_key.public_key; + bob_one_time_key = one_time_key; + + // Calculate the shared secret S via triple DH + std::uint8_t secret[3 * CURVE25519_SHARED_SECRET_LENGTH]; + std::uint8_t * pos = secret; + + _olm_crypto_curve25519_shared_secret(&alice_identity_key_pair, &one_time_key, pos); + pos += CURVE25519_SHARED_SECRET_LENGTH; + _olm_crypto_curve25519_shared_secret(&base_key, &identity_key, pos); + pos += CURVE25519_SHARED_SECRET_LENGTH; + _olm_crypto_curve25519_shared_secret(&base_key, &one_time_key, pos); + + ratchet.initialise_as_alice(secret, sizeof(secret), ratchet_key); + + olm::unset(base_key); + olm::unset(ratchet_key); + olm::unset(secret); + + return std::size_t(0); +} + +namespace { + +static bool check_message_fields( + olm::PreKeyMessageReader & reader, bool have_their_identity_key +) { + bool ok = true; + ok = ok && (have_their_identity_key || reader.identity_key); + if (reader.identity_key) { + ok = ok && reader.identity_key_length == CURVE25519_KEY_LENGTH; + } + ok = ok && reader.message; + ok = ok && reader.base_key; + ok = ok && reader.base_key_length == CURVE25519_KEY_LENGTH; + ok = ok && reader.one_time_key; + ok = ok && reader.one_time_key_length == CURVE25519_KEY_LENGTH; + return ok; +} + +} // namespace + + +std::size_t olm::Session::new_inbound_session( + olm::Account & local_account, + _olm_curve25519_public_key const * their_identity_key, + std::uint8_t const * one_time_key_message, std::size_t message_length +) { + olm::PreKeyMessageReader reader; + decode_one_time_key_message(reader, one_time_key_message, message_length); + + if (!check_message_fields(reader, their_identity_key)) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; + return std::size_t(-1); + } + + if (reader.identity_key && their_identity_key) { + bool same = 0 == std::memcmp( + their_identity_key->public_key, reader.identity_key, CURVE25519_KEY_LENGTH + ); + if (!same) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_KEY_ID; + return std::size_t(-1); + } + } + + olm::load_array(alice_identity_key.public_key, reader.identity_key); + olm::load_array(alice_base_key.public_key, reader.base_key); + olm::load_array(bob_one_time_key.public_key, reader.one_time_key); + + olm::MessageReader message_reader; + decode_message( + message_reader, reader.message, reader.message_length, + ratchet.ratchet_cipher->ops->mac_length(ratchet.ratchet_cipher) + ); + + if (!message_reader.ratchet_key + || message_reader.ratchet_key_length != CURVE25519_KEY_LENGTH) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; + return std::size_t(-1); + } + + _olm_curve25519_public_key ratchet_key; + olm::load_array(ratchet_key.public_key, message_reader.ratchet_key); + + olm::OneTimeKey const * our_one_time_key = local_account.lookup_key( + bob_one_time_key + ); + + if (!our_one_time_key) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_KEY_ID; + return std::size_t(-1); + } + + _olm_curve25519_key_pair const & bob_identity_key = ( + local_account.identity_keys.curve25519_key + ); + _olm_curve25519_key_pair const & bob_one_time_key = our_one_time_key->key; + + // Calculate the shared secret S via triple DH + std::uint8_t secret[CURVE25519_SHARED_SECRET_LENGTH * 3]; + std::uint8_t * pos = secret; + _olm_crypto_curve25519_shared_secret(&bob_one_time_key, &alice_identity_key, pos); + pos += CURVE25519_SHARED_SECRET_LENGTH; + _olm_crypto_curve25519_shared_secret(&bob_identity_key, &alice_base_key, pos); + pos += CURVE25519_SHARED_SECRET_LENGTH; + _olm_crypto_curve25519_shared_secret(&bob_one_time_key, &alice_base_key, pos); + + ratchet.initialise_as_bob(secret, sizeof(secret), ratchet_key); + + olm::unset(secret); + + return std::size_t(0); +} + + +std::size_t olm::Session::session_id_length() const { + return SHA256_OUTPUT_LENGTH; +} + + +std::size_t olm::Session::session_id( + std::uint8_t * id, std::size_t id_length +) { + if (id_length < session_id_length()) { + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + std::uint8_t tmp[CURVE25519_KEY_LENGTH * 3]; + std::uint8_t * pos = tmp; + pos = olm::store_array(pos, alice_identity_key.public_key); + pos = olm::store_array(pos, alice_base_key.public_key); + pos = olm::store_array(pos, bob_one_time_key.public_key); + _olm_crypto_sha256(tmp, sizeof(tmp), id); + return session_id_length(); +} + + +bool olm::Session::matches_inbound_session( + _olm_curve25519_public_key const * their_identity_key, + std::uint8_t const * one_time_key_message, std::size_t message_length +) const { + olm::PreKeyMessageReader reader; + decode_one_time_key_message(reader, one_time_key_message, message_length); + + if (!check_message_fields(reader, their_identity_key)) { + return false; + } + + bool same = true; + if (reader.identity_key) { + same = same && 0 == std::memcmp( + reader.identity_key, alice_identity_key.public_key, CURVE25519_KEY_LENGTH + ); + } + if (their_identity_key) { + same = same && 0 == std::memcmp( + their_identity_key->public_key, alice_identity_key.public_key, + CURVE25519_KEY_LENGTH + ); + } + same = same && 0 == std::memcmp( + reader.base_key, alice_base_key.public_key, CURVE25519_KEY_LENGTH + ); + same = same && 0 == std::memcmp( + reader.one_time_key, bob_one_time_key.public_key, CURVE25519_KEY_LENGTH + ); + return same; +} + + +olm::MessageType olm::Session::encrypt_message_type() const { + if (received_message) { + return olm::MessageType::MESSAGE; + } else { + return olm::MessageType::PRE_KEY; + } +} + + +std::size_t olm::Session::encrypt_message_length( + std::size_t plaintext_length +) const { + std::size_t message_length = ratchet.encrypt_output_length( + plaintext_length + ); + + if (received_message) { + return message_length; + } + + return encode_one_time_key_message_length( + CURVE25519_KEY_LENGTH, + CURVE25519_KEY_LENGTH, + CURVE25519_KEY_LENGTH, + message_length + ); +} + + +std::size_t olm::Session::encrypt_random_length() const { + return ratchet.encrypt_random_length(); +} + + +std::size_t olm::Session::encrypt( + std::uint8_t const * plaintext, std::size_t plaintext_length, + std::uint8_t const * random, std::size_t random_length, + std::uint8_t * message, std::size_t message_length +) { + if (message_length < encrypt_message_length(plaintext_length)) { + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + std::uint8_t * message_body; + std::size_t message_body_length = ratchet.encrypt_output_length( + plaintext_length + ); + + if (received_message) { + message_body = message; + } else { + olm::PreKeyMessageWriter writer; + encode_one_time_key_message( + writer, + PROTOCOL_VERSION, + CURVE25519_KEY_LENGTH, + CURVE25519_KEY_LENGTH, + CURVE25519_KEY_LENGTH, + message_body_length, + message + ); + olm::store_array(writer.one_time_key, bob_one_time_key.public_key); + olm::store_array(writer.identity_key, alice_identity_key.public_key); + olm::store_array(writer.base_key, alice_base_key.public_key); + message_body = writer.message; + } + + std::size_t result = ratchet.encrypt( + plaintext, plaintext_length, + random, random_length, + message_body, message_body_length + ); + + if (result == std::size_t(-1)) { + last_error = ratchet.last_error; + ratchet.last_error = OlmErrorCode::OLM_SUCCESS; + return result; + } + + return result; +} + + +std::size_t olm::Session::decrypt_max_plaintext_length( + MessageType message_type, + std::uint8_t const * message, std::size_t message_length +) { + std::uint8_t const * message_body; + std::size_t message_body_length; + if (message_type == olm::MessageType::MESSAGE) { + message_body = message; + message_body_length = message_length; + } else { + olm::PreKeyMessageReader reader; + decode_one_time_key_message(reader, message, message_length); + if (!reader.message) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; + return std::size_t(-1); + } + message_body = reader.message; + message_body_length = reader.message_length; + } + + std::size_t result = ratchet.decrypt_max_plaintext_length( + message_body, message_body_length + ); + + if (result == std::size_t(-1)) { + last_error = ratchet.last_error; + ratchet.last_error = OlmErrorCode::OLM_SUCCESS; + } + return result; +} + + +std::size_t olm::Session::decrypt( + olm::MessageType message_type, + std::uint8_t const * message, std::size_t message_length, + std::uint8_t * plaintext, std::size_t max_plaintext_length +) { + std::uint8_t const * message_body; + std::size_t message_body_length; + if (message_type == olm::MessageType::MESSAGE) { + message_body = message; + message_body_length = message_length; + } else { + olm::PreKeyMessageReader reader; + decode_one_time_key_message(reader, message, message_length); + if (!reader.message) { + last_error = OlmErrorCode::OLM_BAD_MESSAGE_FORMAT; + return std::size_t(-1); + } + message_body = reader.message; + message_body_length = reader.message_length; + } + + std::size_t result = ratchet.decrypt( + message_body, message_body_length, plaintext, max_plaintext_length + ); + + if (result == std::size_t(-1)) { + last_error = ratchet.last_error; + ratchet.last_error = OlmErrorCode::OLM_SUCCESS; + return result; + } + + received_message = true; + return result; +} + +// make the description end with "..." instead of stopping abruptly with no +// warning +void elide_description(char *end) { + end[-3] = '.'; + end[-2] = '.'; + end[-1] = '.'; + end[0] = '\0'; +} + +void olm::Session::describe(char *describe_buffer, size_t buflen) { + // how much of the buffer is remaining (this is an int rather than a size_t + // because it will get compared to the return value from snprintf) + int remaining = buflen; + // do nothing if we have a zero-length buffer, or if buflen > INT_MAX, + // resulting in an overflow + if (remaining <= 0) return; + + describe_buffer[0] = '\0'; + // we need at least 23 characters to get any sort of meaningful + // information, so bail if we don't have that. (But more importantly, we + // need it to be at least 4 so that elide_description doesn't go out of + // bounds.) + if (remaining < 23) return; + + int size; + + // check that snprintf didn't return an error or reach the end of the buffer +#define CHECK_SIZE_AND_ADVANCE \ + if (size > remaining) { \ + return elide_description(describe_buffer + remaining - 1); \ + } else if (size > 0) { \ + describe_buffer += size; \ + remaining -= size; \ + } else { \ + return; \ + } + + size = snprintf( + describe_buffer, remaining, + "sender chain index: %ld ", ratchet.sender_chain[0].chain_key.index + ); + CHECK_SIZE_AND_ADVANCE; + + size = snprintf(describe_buffer, remaining, "receiver chain indices:"); + CHECK_SIZE_AND_ADVANCE; + + for (size_t i = 0; i < ratchet.receiver_chains.size(); ++i) { + size = snprintf( + describe_buffer, remaining, + " %ld", ratchet.receiver_chains[i].chain_key.index + ); + CHECK_SIZE_AND_ADVANCE; + } + + size = snprintf(describe_buffer, remaining, " skipped message keys:"); + CHECK_SIZE_AND_ADVANCE; + + for (size_t i = 0; i < ratchet.skipped_message_keys.size(); ++i) { + size = snprintf( + describe_buffer, remaining, + " %ld", ratchet.skipped_message_keys[i].message_key.index + ); + CHECK_SIZE_AND_ADVANCE; + } +#undef CHECK_SIZE_AND_ADVANCE +} + +namespace { +// the master branch writes pickle version 1; the logging_enabled branch writes +// 0x80000001. +static const std::uint32_t SESSION_PICKLE_VERSION = 1; +} + +std::size_t olm::pickle_length( + Session const & value +) { + std::size_t length = 0; + length += olm::pickle_length(SESSION_PICKLE_VERSION); + length += olm::pickle_length(value.received_message); + length += olm::pickle_length(value.alice_identity_key); + length += olm::pickle_length(value.alice_base_key); + length += olm::pickle_length(value.bob_one_time_key); + length += olm::pickle_length(value.ratchet); + return length; +} + + +std::uint8_t * olm::pickle( + std::uint8_t * pos, + Session const & value +) { + pos = olm::pickle(pos, SESSION_PICKLE_VERSION); + pos = olm::pickle(pos, value.received_message); + pos = olm::pickle(pos, value.alice_identity_key); + pos = olm::pickle(pos, value.alice_base_key); + pos = olm::pickle(pos, value.bob_one_time_key); + pos = olm::pickle(pos, value.ratchet); + return pos; +} + + +std::uint8_t const * olm::unpickle( + std::uint8_t const * pos, std::uint8_t const * end, + Session & value +) { + uint32_t pickle_version; + pos = olm::unpickle(pos, end, pickle_version); UNPICKLE_OK(pos); + + bool includes_chain_index; + switch (pickle_version) { + case 1: + includes_chain_index = false; + break; + + case 0x80000001UL: + includes_chain_index = true; + break; + + default: + value.last_error = OlmErrorCode::OLM_UNKNOWN_PICKLE_VERSION; + return nullptr; + } + + pos = olm::unpickle(pos, end, value.received_message); UNPICKLE_OK(pos); + pos = olm::unpickle(pos, end, value.alice_identity_key); UNPICKLE_OK(pos); + pos = olm::unpickle(pos, end, value.alice_base_key); UNPICKLE_OK(pos); + pos = olm::unpickle(pos, end, value.bob_one_time_key); UNPICKLE_OK(pos); + pos = olm::unpickle(pos, end, value.ratchet, includes_chain_index); UNPICKLE_OK(pos); + + return pos; +}