aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHubert Chathi <hubertc@matrix.org>2020-09-17 21:42:25 +0000
committerHubert Chathi <hubertc@matrix.org>2020-09-17 21:42:25 +0000
commit3cd6b15853923fff512138ebb5e8ba390cfb38e2 (patch)
tree6830d4d13f7c1106f8e4e772e16f9204d523fcd5
parent89050dc0b68f0d5f1bf9f9f386f7c2f4ac043f4f (diff)
parentc47c6ca3992c74489700cda44443ec29c3cb3b13 (diff)
Merge branch 'uhoreg/fallback' into 'master'
add support for fallback keys See merge request matrix-org/olm!13
-rw-r--r--include/olm/account.hh31
-rw-r--r--include/olm/olm.h25
-rw-r--r--javascript/index.d.ts2
-rw-r--r--javascript/olm_post.js23
-rw-r--r--src/account.cpp112
-rw-r--r--src/olm.cpp36
6 files changed, 227 insertions, 2 deletions
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<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,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 &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 +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, &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 +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
) {