X-Git-Url: https://gitweb.ps.run/matrix_esp_thesis/blobdiff_plain/21c6e8484b0bd05c27e5a91f2884d431926adc61..da776f86b42946715c27edd64f7558b9d5080df1:/ext/olm/src/account.cpp?ds=sidebyside diff --git a/ext/olm/src/account.cpp b/ext/olm/src/account.cpp new file mode 100644 index 0000000..41b7188 --- /dev/null +++ b/ext/olm/src/account.cpp @@ -0,0 +1,580 @@ +/* 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/account.hh" +#include "olm/base64.hh" +#include "olm/pickle.h" +#include "olm/pickle.hh" +#include "olm/memory.hh" + +olm::Account::Account( +) : num_fallback_keys(0), + next_one_time_key_id(0), + last_error(OlmErrorCode::OLM_SUCCESS) { +} + + +olm::OneTimeKey const * olm::Account::lookup_key( + _olm_curve25519_public_key const & public_key +) { + for (olm::OneTimeKey const & key : one_time_keys) { + if (olm::array_equal(key.key.public_key.public_key, public_key.public_key)) { + return &key; + } + } + if (num_fallback_keys >= 1 + && olm::array_equal( + current_fallback_key.key.public_key.public_key, public_key.public_key + ) + ) { + return ¤t_fallback_key; + } + if (num_fallback_keys >= 2 + && olm::array_equal( + prev_fallback_key.key.public_key.public_key, public_key.public_key + ) + ) { + return &prev_fallback_key; + } + return 0; +} + +std::size_t olm::Account::remove_key( + _olm_curve25519_public_key const & public_key +) { + OneTimeKey * i; + for (i = one_time_keys.begin(); i != one_time_keys.end(); ++i) { + if (olm::array_equal(i->key.public_key.public_key, public_key.public_key)) { + std::uint32_t id = i->id; + one_time_keys.erase(i); + return id; + } + } + // check if the key is a fallback key, to avoid returning an error, but + // don't actually remove it + if (num_fallback_keys >= 1 + && olm::array_equal( + current_fallback_key.key.public_key.public_key, public_key.public_key + ) + ) { + return current_fallback_key.id; + } + if (num_fallback_keys >= 2 + && olm::array_equal( + prev_fallback_key.key.public_key.public_key, public_key.public_key + ) + ) { + return prev_fallback_key.id; + } + return std::size_t(-1); +} + +std::size_t olm::Account::new_account_random_length() const { + return ED25519_RANDOM_LENGTH + CURVE25519_RANDOM_LENGTH; +} + +std::size_t olm::Account::new_account( + uint8_t const * random, std::size_t random_length +) { + if (random_length < new_account_random_length()) { + last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; + return std::size_t(-1); + } + + _olm_crypto_ed25519_generate_key(random, &identity_keys.ed25519_key); + random += ED25519_RANDOM_LENGTH; + _olm_crypto_curve25519_generate_key(random, &identity_keys.curve25519_key); + + return 0; +} + +namespace { + +uint8_t KEY_JSON_ED25519[] = "\"ed25519\":"; +uint8_t KEY_JSON_CURVE25519[] = "\"curve25519\":"; + +template +static std::uint8_t * write_string( + std::uint8_t * pos, + T const & value +) { + std::memcpy(pos, value, sizeof(T) - 1); + return pos + (sizeof(T) - 1); +} + +} + + +std::size_t olm::Account::get_identity_json_length() const { + std::size_t length = 0; + length += 1; /* { */ + length += sizeof(KEY_JSON_CURVE25519) - 1; + length += 1; /* " */ + length += olm::encode_base64_length( + sizeof(identity_keys.curve25519_key.public_key) + ); + length += 2; /* ", */ + length += sizeof(KEY_JSON_ED25519) - 1; + length += 1; /* " */ + length += olm::encode_base64_length( + sizeof(identity_keys.ed25519_key.public_key) + ); + length += 2; /* "} */ + return length; +} + + +std::size_t olm::Account::get_identity_json( + std::uint8_t * identity_json, std::size_t identity_json_length +) { + std::uint8_t * pos = identity_json; + size_t expected_length = get_identity_json_length(); + + if (identity_json_length < expected_length) { + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + + *(pos++) = '{'; + pos = write_string(pos, KEY_JSON_CURVE25519); + *(pos++) = '\"'; + pos = olm::encode_base64( + identity_keys.curve25519_key.public_key.public_key, + sizeof(identity_keys.curve25519_key.public_key.public_key), + pos + ); + *(pos++) = '\"'; *(pos++) = ','; + pos = write_string(pos, KEY_JSON_ED25519); + *(pos++) = '\"'; + pos = olm::encode_base64( + identity_keys.ed25519_key.public_key.public_key, + sizeof(identity_keys.ed25519_key.public_key.public_key), + pos + ); + *(pos++) = '\"'; *(pos++) = '}'; + return pos - identity_json; +} + + +std::size_t olm::Account::signature_length( +) const { + return ED25519_SIGNATURE_LENGTH; +} + + +std::size_t olm::Account::sign( + std::uint8_t const * message, std::size_t message_length, + std::uint8_t * signature, std::size_t signature_length +) { + if (signature_length < this->signature_length()) { + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + _olm_crypto_ed25519_sign( + &identity_keys.ed25519_key, message, message_length, signature + ); + return this->signature_length(); +} + + +std::size_t olm::Account::get_one_time_keys_json_length( +) const { + std::size_t length = 0; + bool is_empty = true; + for (auto const & key : one_time_keys) { + if (key.published) { + continue; + } + is_empty = false; + length += 2; /* {" */ + length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id)); + length += 3; /* ":" */ + length += olm::encode_base64_length(sizeof(key.key.public_key)); + length += 1; /* " */ + } + if (is_empty) { + length += 1; /* { */ + } + length += 3; /* }{} */ + length += sizeof(KEY_JSON_CURVE25519) - 1; + return length; +} + + +std::size_t olm::Account::get_one_time_keys_json( + std::uint8_t * one_time_json, std::size_t one_time_json_length +) { + std::uint8_t * pos = one_time_json; + if (one_time_json_length < get_one_time_keys_json_length()) { + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + *(pos++) = '{'; + pos = write_string(pos, KEY_JSON_CURVE25519); + std::uint8_t sep = '{'; + for (auto const & key : one_time_keys) { + if (key.published) { + continue; + } + *(pos++) = sep; + *(pos++) = '\"'; + std::uint8_t key_id[_olm_pickle_uint32_length(key.id)]; + _olm_pickle_uint32(key_id, key.id); + pos = olm::encode_base64(key_id, sizeof(key_id), pos); + *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"'; + pos = olm::encode_base64( + key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos + ); + *(pos++) = '\"'; + sep = ','; + } + if (sep != ',') { + /* The list was empty */ + *(pos++) = sep; + } + *(pos++) = '}'; + *(pos++) = '}'; + return pos - one_time_json; +} + + +std::size_t olm::Account::mark_keys_as_published( +) { + std::size_t count = 0; + for (auto & key : one_time_keys) { + if (!key.published) { + key.published = true; + count++; + } + } + current_fallback_key.published = true; + return count; +} + + +std::size_t olm::Account::max_number_of_one_time_keys( +) const { + return olm::MAX_ONE_TIME_KEYS; +} + +std::size_t olm::Account::generate_one_time_keys_random_length( + std::size_t number_of_keys +) const { + return CURVE25519_RANDOM_LENGTH * number_of_keys; +} + +std::size_t olm::Account::generate_one_time_keys( + std::size_t number_of_keys, + std::uint8_t const * random, std::size_t random_length +) { + if (random_length < generate_one_time_keys_random_length(number_of_keys)) { + last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; + return std::size_t(-1); + } + for (unsigned i = 0; i < number_of_keys; ++i) { + OneTimeKey & key = *one_time_keys.insert(one_time_keys.begin()); + key.id = ++next_one_time_key_id; + key.published = false; + _olm_crypto_curve25519_generate_key(random, &key.key); + random += CURVE25519_RANDOM_LENGTH; + } + return number_of_keys; +} + +std::size_t olm::Account::generate_fallback_key_random_length() const { + return CURVE25519_RANDOM_LENGTH; +} + +std::size_t olm::Account::generate_fallback_key( + std::uint8_t const * random, std::size_t random_length +) { + if (random_length < generate_fallback_key_random_length()) { + last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; + return std::size_t(-1); + } + if (num_fallback_keys < 2) { + num_fallback_keys++; + } + prev_fallback_key = current_fallback_key; + current_fallback_key.id = ++next_one_time_key_id; + current_fallback_key.published = false; + _olm_crypto_curve25519_generate_key(random, ¤t_fallback_key.key); + return 1; +} + + +std::size_t olm::Account::get_fallback_key_json_length( +) const { + std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */ + if (num_fallback_keys >= 1) { + const OneTimeKey & key = current_fallback_key; + length += 1; /* " */ + length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id)); + length += 3; /* ":" */ + length += olm::encode_base64_length(sizeof(key.key.public_key)); + length += 1; /* " */ + } + return length; +} + +std::size_t olm::Account::get_fallback_key_json( + std::uint8_t * fallback_json, std::size_t fallback_json_length +) { + std::uint8_t * pos = fallback_json; + if (fallback_json_length < get_fallback_key_json_length()) { + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + *(pos++) = '{'; + pos = write_string(pos, KEY_JSON_CURVE25519); + *(pos++) = '{'; + OneTimeKey & key = current_fallback_key; + if (num_fallback_keys >= 1) { + *(pos++) = '\"'; + std::uint8_t key_id[_olm_pickle_uint32_length(key.id)]; + _olm_pickle_uint32(key_id, key.id); + pos = olm::encode_base64(key_id, sizeof(key_id), pos); + *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"'; + pos = olm::encode_base64( + key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos + ); + *(pos++) = '\"'; + } + *(pos++) = '}'; + *(pos++) = '}'; + return pos - fallback_json; +} + +std::size_t olm::Account::get_unpublished_fallback_key_json_length( +) const { + std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */ + const OneTimeKey & key = current_fallback_key; + if (num_fallback_keys >= 1 && !key.published) { + length += 1; /* " */ + length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id)); + length += 3; /* ":" */ + length += olm::encode_base64_length(sizeof(key.key.public_key)); + length += 1; /* " */ + } + return length; +} + +std::size_t olm::Account::get_unpublished_fallback_key_json( + std::uint8_t * fallback_json, std::size_t fallback_json_length +) { + std::uint8_t * pos = fallback_json; + if (fallback_json_length < get_unpublished_fallback_key_json_length()) { + last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL; + return std::size_t(-1); + } + *(pos++) = '{'; + pos = write_string(pos, KEY_JSON_CURVE25519); + *(pos++) = '{'; + OneTimeKey & key = current_fallback_key; + if (num_fallback_keys >= 1 && !key.published) { + *(pos++) = '\"'; + std::uint8_t key_id[_olm_pickle_uint32_length(key.id)]; + _olm_pickle_uint32(key_id, key.id); + pos = olm::encode_base64(key_id, sizeof(key_id), pos); + *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"'; + pos = olm::encode_base64( + key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos + ); + *(pos++) = '\"'; + } + *(pos++) = '}'; + *(pos++) = '}'; + return pos - fallback_json; +} + +void olm::Account::forget_old_fallback_key( +) { + if (num_fallback_keys >= 2) { + num_fallback_keys = 1; + olm::unset(&prev_fallback_key, sizeof(prev_fallback_key)); + } +} + +namespace olm { + +static std::size_t pickle_length( + olm::IdentityKeys const & value +) { + size_t length = 0; + length += _olm_pickle_ed25519_key_pair_length(&value.ed25519_key); + length += olm::pickle_length(value.curve25519_key); + return length; +} + + +static std::uint8_t * pickle( + std::uint8_t * pos, + olm::IdentityKeys const & value +) { + pos = _olm_pickle_ed25519_key_pair(pos, &value.ed25519_key); + pos = olm::pickle(pos, value.curve25519_key); + return pos; +} + + +static std::uint8_t const * unpickle( + std::uint8_t const * pos, std::uint8_t const * end, + olm::IdentityKeys & value +) { + pos = _olm_unpickle_ed25519_key_pair(pos, end, &value.ed25519_key); UNPICKLE_OK(pos); + pos = olm::unpickle(pos, end, value.curve25519_key); UNPICKLE_OK(pos); + return pos; +} + + +static std::size_t pickle_length( + olm::OneTimeKey const & value +) { + std::size_t length = 0; + length += olm::pickle_length(value.id); + length += olm::pickle_length(value.published); + length += olm::pickle_length(value.key); + return length; +} + + +static std::uint8_t * pickle( + std::uint8_t * pos, + olm::OneTimeKey const & value +) { + pos = olm::pickle(pos, value.id); + pos = olm::pickle(pos, value.published); + pos = olm::pickle(pos, value.key); + return pos; +} + + +static std::uint8_t const * unpickle( + std::uint8_t const * pos, std::uint8_t const * end, + olm::OneTimeKey & value +) { + pos = olm::unpickle(pos, end, value.id); UNPICKLE_OK(pos); + pos = olm::unpickle(pos, end, value.published); UNPICKLE_OK(pos); + pos = olm::unpickle(pos, end, value.key); UNPICKLE_OK(pos); + return pos; +} + +} // namespace olm + +namespace { +// pickle version 1 used only 32 bytes for the ed25519 private key. +// Any keys thus used should be considered compromised. +// pickle version 2 does not have fallback keys. +// pickle version 3 does not store whether the current fallback key is published. +static const std::uint32_t ACCOUNT_PICKLE_VERSION = 4; +} + + +std::size_t olm::pickle_length( + olm::Account const & value +) { + std::size_t length = 0; + length += olm::pickle_length(ACCOUNT_PICKLE_VERSION); + length += olm::pickle_length(value.identity_keys); + length += olm::pickle_length(value.one_time_keys); + length += olm::pickle_length(value.num_fallback_keys); + if (value.num_fallback_keys >= 1) { + length += olm::pickle_length(value.current_fallback_key); + if (value.num_fallback_keys >= 2) { + length += olm::pickle_length(value.prev_fallback_key); + } + } + length += olm::pickle_length(value.next_one_time_key_id); + return length; +} + + +std::uint8_t * olm::pickle( + std::uint8_t * pos, + olm::Account const & value +) { + pos = olm::pickle(pos, ACCOUNT_PICKLE_VERSION); + pos = olm::pickle(pos, value.identity_keys); + pos = olm::pickle(pos, value.one_time_keys); + pos = olm::pickle(pos, value.num_fallback_keys); + if (value.num_fallback_keys >= 1) { + pos = olm::pickle(pos, value.current_fallback_key); + if (value.num_fallback_keys >= 2) { + pos = olm::pickle(pos, value.prev_fallback_key); + } + } + pos = olm::pickle(pos, value.next_one_time_key_id); + return pos; +} + + +std::uint8_t const * olm::unpickle( + std::uint8_t const * pos, std::uint8_t const * end, + olm::Account & value +) { + uint32_t pickle_version; + + pos = olm::unpickle(pos, end, pickle_version); UNPICKLE_OK(pos); + + switch (pickle_version) { + case ACCOUNT_PICKLE_VERSION: + case 3: + case 2: + break; + case 1: + value.last_error = OlmErrorCode::OLM_BAD_LEGACY_ACCOUNT_PICKLE; + return nullptr; + default: + value.last_error = OlmErrorCode::OLM_UNKNOWN_PICKLE_VERSION; + return nullptr; + } + + pos = olm::unpickle(pos, end, value.identity_keys); UNPICKLE_OK(pos); + pos = olm::unpickle(pos, end, value.one_time_keys); UNPICKLE_OK(pos); + + if (pickle_version <= 2) { + // version 2 did not have fallback keys + value.num_fallback_keys = 0; + } else if (pickle_version == 3) { + // version 3 used the published flag to indicate how many fallback keys + // were present (we'll have to assume that the keys were published) + pos = olm::unpickle(pos, end, value.current_fallback_key); UNPICKLE_OK(pos); + pos = olm::unpickle(pos, end, value.prev_fallback_key); UNPICKLE_OK(pos); + if (value.current_fallback_key.published) { + if (value.prev_fallback_key.published) { + value.num_fallback_keys = 2; + } else { + value.num_fallback_keys = 1; + } + } else { + value.num_fallback_keys = 0; + } + } else { + pos = olm::unpickle(pos, end, value.num_fallback_keys); UNPICKLE_OK(pos); + if (value.num_fallback_keys >= 1) { + pos = olm::unpickle(pos, end, value.current_fallback_key); UNPICKLE_OK(pos); + if (value.num_fallback_keys >= 2) { + pos = olm::unpickle(pos, end, value.prev_fallback_key); UNPICKLE_OK(pos); + if (value.num_fallback_keys >= 3) { + value.last_error = OlmErrorCode::OLM_CORRUPTED_PICKLE; + return nullptr; + } + } + } + } + + pos = olm::unpickle(pos, end, value.next_one_time_key_id); UNPICKLE_OK(pos); + + return pos; +}