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(); Account();
IdentityKeys identity_keys; IdentityKeys identity_keys;
List<OneTimeKey, MAX_ONE_TIME_KEYS> one_time_keys; List<OneTimeKey, MAX_ONE_TIME_KEYS> one_time_keys;
std::uint8_t num_fallback_keys;
OneTimeKey current_fallback_key; OneTimeKey current_fallback_key;
OneTimeKey prev_fallback_key; OneTimeKey prev_fallback_key;
std::uint32_t next_one_time_key_id; 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 */ /** Number of bytes needed to output the one time keys for this account */
std::size_t get_fallback_key_json_length() const; 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: /** Output the fallback key as JSON:
* *
* {"curve25519": * {"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. * 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. * 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 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 OlmAccount const * account
); );
/** Deprecated: use olm_account_unpublished_fallback_key instead */
OLM_EXPORT size_t olm_account_fallback_key( OLM_EXPORT size_t olm_account_fallback_key(
OlmAccount * account, OlmAccount * account,
void * fallback_key, size_t fallback_key_size 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 */ /** The number of random bytes needed to create an outbound session */
OLM_EXPORT size_t olm_create_outbound_session_random_length( 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( inline std::size_t pickle_length(
const bool & value const bool & value
) { ) {

View file

@ -19,13 +19,9 @@
#include "olm/memory.hh" #include "olm/memory.hh"
olm::Account::Account( olm::Account::Account(
) : next_one_time_key_id(0), ) : num_fallback_keys(0),
next_one_time_key_id(0),
last_error(OlmErrorCode::OLM_SUCCESS) { 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; return &key;
} }
} }
if (current_fallback_key.published if (num_fallback_keys >= 1
&& olm::array_equal( && olm::array_equal(
current_fallback_key.key.public_key.public_key, public_key.public_key current_fallback_key.key.public_key.public_key, public_key.public_key
) )
) { ) {
return &current_fallback_key; return &current_fallback_key;
} }
if (prev_fallback_key.published if (num_fallback_keys >= 2
&& olm::array_equal( && olm::array_equal(
prev_fallback_key.key.public_key.public_key, public_key.public_key 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 // check if the key is a fallback key, to avoid returning an error, but
// don't actually remove it // don't actually remove it
if (current_fallback_key.published if (num_fallback_keys >= 1
&& olm::array_equal( && olm::array_equal(
current_fallback_key.key.public_key.public_key, public_key.public_key current_fallback_key.key.public_key.public_key, public_key.public_key
) )
) { ) {
return current_fallback_key.id; return current_fallback_key.id;
} }
if (prev_fallback_key.published if (num_fallback_keys >= 2
&& olm::array_equal( && olm::array_equal(
prev_fallback_key.key.public_key.public_key, public_key.public_key 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++; count++;
} }
} }
current_fallback_key.published = true;
return count; return count;
} }
@ -306,9 +303,12 @@ std::size_t olm::Account::generate_fallback_key(
last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM; last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
return std::size_t(-1); return std::size_t(-1);
} }
if (num_fallback_keys < 2) {
num_fallback_keys++;
}
prev_fallback_key = current_fallback_key; prev_fallback_key = current_fallback_key;
current_fallback_key.id = ++next_one_time_key_id; 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); _olm_crypto_curve25519_generate_key(random, &current_fallback_key.key);
return 1; return 1;
} }
@ -317,8 +317,8 @@ std::size_t olm::Account::generate_fallback_key(
std::size_t olm::Account::get_fallback_key_json_length( std::size_t olm::Account::get_fallback_key_json_length(
) const { ) const {
std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */ std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */
const OneTimeKey & key = current_fallback_key; if (num_fallback_keys >= 1) {
if (key.published) { const OneTimeKey & key = current_fallback_key;
length += 1; /* " */ length += 1; /* " */
length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id)); length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
length += 3; /* ":" */ length += 3; /* ":" */
@ -340,7 +340,35 @@ std::size_t olm::Account::get_fallback_key_json(
pos = write_string(pos, KEY_JSON_CURVE25519); pos = write_string(pos, KEY_JSON_CURVE25519);
*(pos++) = '{'; *(pos++) = '{';
OneTimeKey & key = current_fallback_key; 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++) = '\"'; *(pos++) = '\"';
std::uint8_t key_id[_olm_pickle_uint32_length(key.id)]; std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
_olm_pickle_uint32(key_id, 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. // pickle version 1 used only 32 bytes for the ed25519 private key.
// Any keys thus used should be considered compromised. // Any keys thus used should be considered compromised.
// pickle version 2 does not have fallback keys. // 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(ACCOUNT_PICKLE_VERSION);
length += olm::pickle_length(value.identity_keys); length += olm::pickle_length(value.identity_keys);
length += olm::pickle_length(value.one_time_keys); length += olm::pickle_length(value.one_time_keys);
length += olm::pickle_length(value.current_fallback_key); length += olm::pickle_length(value.num_fallback_keys);
length += olm::pickle_length(value.prev_fallback_key); 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); length += olm::pickle_length(value.next_one_time_key_id);
return length; return length;
} }
@ -451,8 +485,13 @@ std::uint8_t * olm::pickle(
pos = olm::pickle(pos, ACCOUNT_PICKLE_VERSION); pos = olm::pickle(pos, ACCOUNT_PICKLE_VERSION);
pos = olm::pickle(pos, value.identity_keys); pos = olm::pickle(pos, value.identity_keys);
pos = olm::pickle(pos, value.one_time_keys); pos = olm::pickle(pos, value.one_time_keys);
pos = olm::pickle(pos, value.current_fallback_key); pos = olm::pickle(pos, value.num_fallback_keys);
pos = olm::pickle(pos, value.prev_fallback_key); 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); pos = olm::pickle(pos, value.next_one_time_key_id);
return pos; return pos;
} }
@ -468,6 +507,7 @@ std::uint8_t const * olm::unpickle(
switch (pickle_version) { switch (pickle_version) {
case ACCOUNT_PICKLE_VERSION: case ACCOUNT_PICKLE_VERSION:
case 3:
case 2: case 2:
break; break;
case 1: 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.identity_keys); UNPICKLE_OK(pos);
pos = olm::unpickle(pos, end, value.one_time_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 // version 2 did not have fallback keys
value.current_fallback_key.published = false; value.num_fallback_keys = 0;
value.prev_fallback_key.published = false; } else if (pickle_version == 3) {
} else { // 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.current_fallback_key); UNPICKLE_OK(pos);
pos = olm::unpickle(pos, end, value.prev_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); 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( size_t olm_create_outbound_session_random_length(
OlmSession const * session OlmSession const * session
) { ) {

View file

@ -35,6 +35,23 @@ std::uint8_t const * olm::unpickle(
return pos; 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 * olm::pickle(
std::uint8_t * pos, std::uint8_t * pos,
bool value bool value
@ -224,6 +241,17 @@ uint8_t const * _olm_unpickle_uint32(
return olm::unpickle(pos, end, *value); 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) { uint8_t * _olm_pickle_bool(uint8_t * pos, int value) {
return olm::pickle(pos, (bool)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);
}
} }