diff --git a/include/olm/account.hh b/include/olm/account.hh index 7e58ca3..826b26d 100644 --- a/include/olm/account.hh +++ b/include/olm/account.hh @@ -43,6 +43,8 @@ struct Account { Account(); IdentityKeys identity_keys; List 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,35 @@ 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 diff --git a/include/olm/olm.h b/include/olm/olm.h index 6a2b3fb..11d99b4 100644 --- a/include/olm/olm.h +++ b/include/olm/olm.h @@ -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 diff --git a/javascript/index.d.ts b/javascript/index.d.ts index d11ce23..e558fec 100644 --- a/javascript/index.d.ts +++ b/javascript/index.d.ts @@ -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); } diff --git a/javascript/olm_post.js b/javascript/olm_post.js index 439041a..82dd803 100644 --- a/javascript/olm_post.js +++ b/javascript/olm_post.js @@ -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( diff --git a/src/account.cpp b/src/account.cpp index 05e8134..e84a540 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -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,20 @@ 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 ¤t_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 +65,22 @@ 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 +295,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, ¤t_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 +425,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 +437,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 +451,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 +466,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 +477,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; } diff --git a/src/olm.cpp b/src/olm.cpp index 0333b10..50742cc 100644 --- a/src/olm.cpp +++ b/src/olm.cpp @@ -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 ) {