add support for fallback keys

This commit is contained in:
Hubert Chathi 2020-08-14 17:29:25 -04:00
parent a0284c2ba3
commit 171044f3fc
6 changed files with 216 additions and 2 deletions

View file

@ -43,6 +43,8 @@ struct Account {
Account();
IdentityKeys identity_keys;
List<OneTimeKey, MAX_ONE_TIME_KEYS> one_time_keys;
OneTimeKey current_fallback_key;
OneTimeKey prev_fallback_key;
std::uint32_t next_one_time_key_id;
OlmErrorCode last_error;
@ -126,6 +128,36 @@ struct Account {
std::uint8_t const * random, std::size_t random_length
);
/** The number of random bytes needed to generate a fallback key. */
std::size_t generate_fallback_key_random_length(
);
/** Generates a new fallback key. Returns std::size_t(-1) on error. If the
* number of random bytes is too small then last_error will be
* NOT_ENOUGH_RANDOM */
std::size_t generate_fallback_key(
std::uint8_t const * random, std::size_t random_length
);
/** Number of bytes needed to output the one time keys for this account */
std::size_t get_fallback_key_json_length();
/** Output the fallback key as JSON:
*
* {"curve25519":
* ["<6 byte key id>":"<43 base64 characters>"
* ,"<6 byte key id>":"<43 base64 characters>"
* ...
* ]
* }
*
* 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::uint8_t * fallback_json, std::size_t fallback_json_length
);
/** Lookup a one time key with the given public key */
OneTimeKey const * lookup_key(
_olm_curve25519_public_key const & public_key

View file

@ -254,6 +254,31 @@ size_t olm_account_generate_one_time_keys(
void * random, size_t random_length
);
/** The number of random bytes needed to generate a fallback key. */
size_t olm_account_generate_fallback_key_random_length(
OlmAccount * account
);
/** Generates a new fallback key. Only one previous fallback key is
* stored. Returns olm_error() on error. If the number of random bytes is too
* small then olm_account_last_error() will be "NOT_ENOUGH_RANDOM". */
size_t olm_account_generate_fallback_key(
OlmAccount * account,
void * random, size_t random_length
);
/** The number of bytes needed to hold the fallback key as returned by
* olm_account_fallback_key. */
size_t olm_account_fallback_key_length(
OlmAccount * account
);
size_t olm_account_fallback_key(
OlmAccount * account,
void * fallback_key, size_t fallback_key_size
);
/** The number of random bytes needed to create an outbound session */
size_t olm_create_outbound_session_random_length(
OlmSession * session

View file

@ -27,6 +27,8 @@ declare class Account {
max_number_of_one_time_keys(): number;
generate_one_time_keys(number_of_keys: number);
remove_one_time_keys(session: Session);
generate_fallback_key();
fallback_key(): string;
pickle(key: string): string;
unpickle(key: string, pickle: string);
}

View file

@ -141,11 +141,32 @@ Account.prototype['generate_one_time_keys'] = restore_stack(function(
});
Account.prototype['remove_one_time_keys'] = restore_stack(function(session) {
account_method(Module['_olm_remove_one_time_keys'])(
account_method(Module['_olm_remove_one_time_keys'])(
this.ptr, session.ptr
);
});
Account.prototype['generate_fallback_key'] = restore_stack(function() {
var random_length = account_method(
Module['_olm_account_generate_fallback_key_random_length']
)(this.ptr);
var random = random_stack(random_length);
account_method(Module['_olm_account_generate_fallback_key'])(
this.ptr, random, random_length
);
});
Account.prototype['fallback_key'] = restore_stack(function() {
var keys_length = account_method(
Module['_olm_account_fallback_key_length']
)(this.ptr);
var keys = stack(keys_length + NULL_BYTE_PADDING_LENGTH);
account_method(Module['_olm_account_fallback_key'])(
this.ptr, keys, keys_length
);
return UTF8ToString(keys, keys_length);
});
Account.prototype['pickle'] = restore_stack(function(key) {
var key_array = array_from_string(key);
var pickle_length = account_method(

View file

@ -21,6 +21,11 @@
olm::Account::Account(
) : 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;
}
@ -32,6 +37,14 @@ olm::OneTimeKey const * olm::Account::lookup_key(
return &key;
}
}
if (current_fallback_key.published
&& olm::array_equal(current_fallback_key.key.public_key.public_key, public_key.public_key)) {
return &current_fallback_key;
}
if (prev_fallback_key.published
&& olm::array_equal(prev_fallback_key.key.public_key.public_key, public_key.public_key)) {
return &prev_fallback_key;
}
return 0;
}
@ -46,6 +59,16 @@ std::size_t olm::Account::remove_key(
return id;
}
}
// check if the key is a fallback key, to avoid returning an error, but
// don't actually remove it
if (current_fallback_key.published
&& 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
&& 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);
}
@ -260,6 +283,67 @@ std::size_t olm::Account::generate_one_time_keys(
return number_of_keys;
}
std::size_t olm::Account::generate_fallback_key_random_length() {
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);
}
prev_fallback_key = current_fallback_key;
current_fallback_key.id = ++next_one_time_key_id;
current_fallback_key.published = true;
_olm_crypto_curve25519_generate_key(random, &current_fallback_key.key);
return 1;
}
std::size_t olm::Account::get_fallback_key_json_length(
) {
std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519); /* {"curve25519":{}} */
OneTimeKey & key = current_fallback_key;
if (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_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 (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;
}
namespace olm {
static std::size_t pickle_length(
@ -329,7 +413,8 @@ static std::uint8_t const * unpickle(
namespace {
// pickle version 1 used only 32 bytes for the ed25519 private key.
// Any keys thus used should be considered compromised.
static const std::uint32_t ACCOUNT_PICKLE_VERSION = 2;
// pickle version 2 does not have fallback keys.
static const std::uint32_t ACCOUNT_PICKLE_VERSION = 3;
}
@ -340,6 +425,8 @@ 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.next_one_time_key_id);
return length;
}
@ -352,6 +439,8 @@ 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.next_one_time_key_id);
return pos;
}
@ -365,6 +454,7 @@ std::uint8_t const * olm::unpickle(
pos = olm::unpickle(pos, end, pickle_version);
switch (pickle_version) {
case ACCOUNT_PICKLE_VERSION:
case 2:
break;
case 1:
value.last_error = OlmErrorCode::OLM_BAD_LEGACY_ACCOUNT_PICKLE;
@ -375,6 +465,14 @@ std::uint8_t const * olm::unpickle(
}
pos = olm::unpickle(pos, end, value.identity_keys);
pos = olm::unpickle(pos, end, value.one_time_keys);
if (pickle_version == 2) {
// version 2 did not have fallback keys
value.current_fallback_key.published = false;
value.prev_fallback_key.published = false;
} else {
pos = olm::unpickle(pos, end, value.current_fallback_key);
pos = olm::unpickle(pos, end, value.prev_fallback_key);
}
pos = olm::unpickle(pos, end, value.next_one_time_key_id);
return pos;
}

View file

@ -417,6 +417,42 @@ size_t olm_account_generate_one_time_keys(
}
size_t olm_account_generate_fallback_key_random_length(
OlmAccount * account
) {
return from_c(account)->generate_fallback_key_random_length();
}
size_t olm_account_generate_fallback_key(
OlmAccount * account,
void * random, size_t random_length
) {
size_t result = from_c(account)->generate_fallback_key(
from_c(random), random_length
);
olm::unset(random, random_length);
return result;
}
size_t olm_account_fallback_key_length(
OlmAccount * account
) {
return from_c(account)->get_fallback_key_json_length();
}
size_t olm_account_fallback_key(
OlmAccount * account,
void * fallback_key_json, size_t fallback_key_json_length
) {
return from_c(account)->get_fallback_key_json(
from_c(fallback_key_json), fallback_key_json_length
);
}
size_t olm_create_outbound_session_random_length(
OlmSession * session
) {