From b989db011738897e20d143417b08435b9b6955a3 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Wed, 17 Nov 2021 14:30:04 -0500 Subject: [PATCH] track if fallback keys were published --- include/olm/account.hh | 10 +++- include/olm/olm.h | 8 +++ include/olm/pickle.hh | 17 +++++++ src/account.cpp | 108 ++++++++++++++++++++++++++++++++--------- src/olm.cpp | 10 ++++ src/pickle.cpp | 28 +++++++++++ tests/test_olm.cpp | 33 +++++++++++++ 7 files changed, 190 insertions(+), 24 deletions(-) diff --git a/include/olm/account.hh b/include/olm/account.hh index 99754ea..7002f51 100644 --- a/include/olm/account.hh +++ b/include/olm/account.hh @@ -43,6 +43,7 @@ struct Account { Account(); IdentityKeys identity_keys; List one_time_keys; + std::uint8_t num_fallback_keys; OneTimeKey current_fallback_key; OneTimeKey prev_fallback_key; std::uint32_t next_one_time_key_id; @@ -141,6 +142,11 @@ struct Account { /** Number of bytes needed to output the one time keys for this account */ std::size_t get_fallback_key_json_length() const; + /** Deprecated: use get_unpublished_fallback_key_json instead */ + std::size_t get_fallback_key_json( + std::uint8_t * fallback_json, std::size_t fallback_json_length + ); + /** Output the fallback key as JSON: * * {"curve25519": @@ -150,10 +156,12 @@ struct Account { * ] * } * + * if there is a fallback key and it has not been published yet. + * * Returns the size of the JSON written or std::size_t(-1) on error. * If the buffer is too small last_error will be OUTPUT_BUFFER_TOO_SMALL. */ - std::size_t get_fallback_key_json( + std::size_t get_unpublished_fallback_key_json( std::uint8_t * fallback_json, std::size_t fallback_json_length ); diff --git a/include/olm/olm.h b/include/olm/olm.h index 67df9a1..0e98e77 100644 --- a/include/olm/olm.h +++ b/include/olm/olm.h @@ -291,11 +291,19 @@ OLM_EXPORT size_t olm_account_fallback_key_length( OlmAccount const * account ); +/** Deprecated: use olm_account_unpublished_fallback_key instead */ OLM_EXPORT size_t olm_account_fallback_key( OlmAccount * account, void * fallback_key, size_t fallback_key_size ); +/** Returns the fallback key (if present, and if unpublished) into the + * fallback_key buffer */ +OLM_EXPORT size_t olm_account_unpublished_fallback_key( + OlmAccount * account, + void * fallback_key, size_t fallback_key_size +); + /** The number of random bytes needed to create an outbound session */ OLM_EXPORT size_t olm_create_outbound_session_random_length( diff --git a/include/olm/pickle.hh b/include/olm/pickle.hh index 44bb29e..f8d41b6 100644 --- a/include/olm/pickle.hh +++ b/include/olm/pickle.hh @@ -46,6 +46,23 @@ std::uint8_t const * unpickle( ); +inline std::size_t pickle_length( + const std::uint8_t & value +) { + return 1; +} + +std::uint8_t * pickle( + std::uint8_t * pos, + std::uint8_t value +); + +std::uint8_t const * unpickle( + std::uint8_t const * pos, std::uint8_t const * end, + std::uint8_t & value +); + + inline std::size_t pickle_length( const bool & value ) { diff --git a/src/account.cpp b/src/account.cpp index 2c660b9..cebd89f 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -19,13 +19,9 @@ #include "olm/memory.hh" olm::Account::Account( -) : next_one_time_key_id(0), +) : num_fallback_keys(0), + next_one_time_key_id(0), last_error(OlmErrorCode::OLM_SUCCESS) { - // since we don't need to keep track of whether the fallback keys are - // published, use the published flag as in indication for whether the keys - // were generated - current_fallback_key.published = false; - prev_fallback_key.published = false; } @@ -37,14 +33,14 @@ olm::OneTimeKey const * olm::Account::lookup_key( return &key; } } - if (current_fallback_key.published + 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 (prev_fallback_key.published + if (num_fallback_keys >= 2 && olm::array_equal( prev_fallback_key.key.public_key.public_key, public_key.public_key ) @@ -67,14 +63,14 @@ std::size_t olm::Account::remove_key( } // check if the key is a fallback key, to avoid returning an error, but // don't actually remove it - if (current_fallback_key.published + 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 (prev_fallback_key.published + if (num_fallback_keys >= 2 && olm::array_equal( prev_fallback_key.key.public_key.public_key, public_key.public_key ) @@ -262,6 +258,7 @@ std::size_t olm::Account::mark_keys_as_published( count++; } } + current_fallback_key.published = true; return count; } @@ -306,9 +303,12 @@ std::size_t olm::Account::generate_fallback_key( 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 = true; + current_fallback_key.published = false; _olm_crypto_curve25519_generate_key(random, ¤t_fallback_key.key); return 1; } @@ -317,8 +317,8 @@ std::size_t olm::Account::generate_fallback_key( std::size_t olm::Account::get_fallback_key_json_length( ) const { std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */ - const OneTimeKey & key = current_fallback_key; - if (key.published) { + 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; /* ":" */ @@ -340,7 +340,35 @@ std::size_t olm::Account::get_fallback_key_json( pos = write_string(pos, KEY_JSON_CURVE25519); *(pos++) = '{'; OneTimeKey & key = current_fallback_key; - if (key.published) { + 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( + 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 && !key.published) { *(pos++) = '\"'; std::uint8_t key_id[_olm_pickle_uint32_length(key.id)]; _olm_pickle_uint32(key_id, key.id); @@ -426,7 +454,8 @@ 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. -static const std::uint32_t ACCOUNT_PICKLE_VERSION = 3; +// pickle version 3 does not store whether the current fallback key is published. +static const std::uint32_t ACCOUNT_PICKLE_VERSION = 4; } @@ -437,8 +466,13 @@ std::size_t olm::pickle_length( 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.current_fallback_key); - length += olm::pickle_length(value.prev_fallback_key); + 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; } @@ -451,8 +485,13 @@ std::uint8_t * olm::pickle( 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.current_fallback_key); - pos = olm::pickle(pos, value.prev_fallback_key); + 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; } @@ -468,6 +507,7 @@ std::uint8_t const * olm::unpickle( switch (pickle_version) { case ACCOUNT_PICKLE_VERSION: + case 3: case 2: break; case 1: @@ -481,13 +521,35 @@ std::uint8_t const * olm::unpickle( 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) { + if (pickle_version <= 2) { // version 2 did not have fallback keys - value.current_fallback_key.published = false; - value.prev_fallback_key.published = false; - } else { + 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); diff --git a/src/olm.cpp b/src/olm.cpp index 80d5dba..2f3e777 100644 --- a/src/olm.cpp +++ b/src/olm.cpp @@ -495,6 +495,16 @@ size_t olm_account_fallback_key( } +size_t olm_account_unpublished_fallback_key( + OlmAccount * account, + void * fallback_key_json, size_t fallback_key_json_length +) { + return from_c(account)->get_unpublished_fallback_key_json( + from_c(fallback_key_json), fallback_key_json_length + ); +} + + size_t olm_create_outbound_session_random_length( OlmSession const * session ) { diff --git a/src/pickle.cpp b/src/pickle.cpp index f0e8f2f..3bffb36 100644 --- a/src/pickle.cpp +++ b/src/pickle.cpp @@ -35,6 +35,23 @@ std::uint8_t const * olm::unpickle( return pos; } +std::uint8_t * olm::pickle( + std::uint8_t * pos, + std::uint8_t value +) { + *(pos++) = value; + return pos; +} + +std::uint8_t const * olm::unpickle( + std::uint8_t const * pos, std::uint8_t const * end, + std::uint8_t & value +) { + if (!pos || pos == end) return nullptr; + value = *(pos++); + return pos; +} + std::uint8_t * olm::pickle( std::uint8_t * pos, bool value @@ -224,6 +241,17 @@ uint8_t const * _olm_unpickle_uint32( return olm::unpickle(pos, end, *value); } +uint8_t * _olm_pickle_uint8(uint8_t * pos, uint8_t value) { + return olm::pickle(pos, value); +} + +uint8_t const * _olm_unpickle_uint8( + uint8_t const * pos, uint8_t const * end, + uint8_t *value +) { + return olm::unpickle(pos, end, *value); +} + uint8_t * _olm_pickle_bool(uint8_t * pos, int value) { return olm::pickle(pos, (bool)value); } diff --git a/tests/test_olm.cpp b/tests/test_olm.cpp index d95be79..30328ed 100644 --- a/tests/test_olm.cpp +++ b/tests/test_olm.cpp @@ -432,4 +432,37 @@ for (unsigned i = 0; i < 8; ++i) { } } +{ + TestCase test_case("Old account (v3) unpickle test"); + + std::uint8_t pickle[] = + "0mSqVn3duHffbhaTbFgW+4JPlcRoqT7z0x4mQ72N+g+eSAk5sgcWSoDzKpMazgcB" + "46ItEpChthVHTGRA6PD3dly0dUs4ji7VtWTa+1tUv1UbxP92uYf1Ae3fomX0yAoH" + "OjSrz1+RmuXr+At8jsmsf260sKvhB6LnI3qYsrw6AAtpgk5d5xZd66sLxvvYUuai" + "+SmmcmT0bHosLTuDiiB9amBvPKkUKtKZmaEAl5ULrgnJygp1/FnwzVfSrw6PBSX6" + "ZaUEZHZGX1iI6/WjbHqlTQeOQjtaSsPaL5XXpteS9dFsuaANAj+8ks7Ut2Hwg/JP" + "Ih/ERYBwiMh9Mt3zSAG0NkvgUkcdipKxoSNZ6t+TkqZrN6jG6VCbx+4YpJO24iJb" + "ShZy8n79aePIgIsxX94ycsTq1ic38sCRSkWGVbCSRkPloHW7ZssLHA"; + + std::uint8_t expected_fallback[] = + "{\"curve25519\":{\"AAAAAQ\":\"dr98y6VOWt6lJaQgFVZeWY2ky76mga9MEMbdItJTdng\"}}"; + std::uint8_t expected_unpublished_fallback[] = + "{\"curve25519\":{}}"; + + std::vector account_buffer(::olm_account_size()); + ::OlmAccount *account = ::olm_account(account_buffer.data()); + assert_not_equals( + std::size_t(-1), + ::olm_unpickle_account( + account, "", 0, pickle, sizeof(pickle)-1 + ) + ); + + std::vector fallback(::olm_account_fallback_key_length(account)); + size_t len = ::olm_account_fallback_key(account, fallback.data(), fallback.size()); + assert_equals(expected_fallback, fallback.data(), len); + len = ::olm_account_unpublished_fallback_key(account, fallback.data(), fallback.size()); + assert_equals(expected_unpublished_fallback, fallback.data(), len); +} + }