track if fallback keys were published

This commit is contained in:
Hubert Chathi 2021-11-17 14:30:04 -05:00
parent 5039c0cc3a
commit b989db0117
7 changed files with 190 additions and 24 deletions

View file

@ -43,6 +43,7 @@ struct Account {
Account();
IdentityKeys identity_keys;
List<OneTimeKey, MAX_ONE_TIME_KEYS> 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
);

View file

@ -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(

View file

@ -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
) {

View file

@ -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 &current_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, &current_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);

View file

@ -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
) {

View file

@ -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);
}

View file

@ -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<std::uint8_t> 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<std::uint8_t> 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);
}
}