From 171044f3fca084bd1c5c1f8f4bfe146434b07f0b Mon Sep 17 00:00:00 2001
From: Hubert Chathi <hubert@uhoreg.ca>
Date: Fri, 14 Aug 2020 17:29:25 -0400
Subject: add support for fallback keys

---
 include/olm/account.hh |  32 ++++++++++++++++
 include/olm/olm.h      |  25 +++++++++++++
 javascript/index.d.ts  |   2 +
 javascript/olm_post.js |  23 +++++++++++-
 src/account.cpp        | 100 ++++++++++++++++++++++++++++++++++++++++++++++++-
 src/olm.cpp            |  36 ++++++++++++++++++
 6 files changed, 216 insertions(+), 2 deletions(-)

diff --git a/include/olm/account.hh b/include/olm/account.hh
index 7e58ca3..82bba27 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,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
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 a0107ac..9d101a8 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 e204f30..aed4b69 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..a747677 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,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;
 }
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
 ) {
-- 
cgit v1.2.3-70-g09d2


From c47c6ca3992c74489700cda44443ec29c3cb3b13 Mon Sep 17 00:00:00 2001
From: Hubert Chathi <hubert@uhoreg.ca>
Date: Wed, 16 Sep 2020 16:14:23 -0400
Subject: fix style

---
 include/olm/account.hh |  3 +--
 src/account.cpp        | 20 ++++++++++++++++----
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/include/olm/account.hh b/include/olm/account.hh
index 82bba27..826b26d 100644
--- a/include/olm/account.hh
+++ b/include/olm/account.hh
@@ -129,8 +129,7 @@ struct Account {
     );
 
     /** The number of random bytes needed to generate a fallback key. */
-    std::size_t generate_fallback_key_random_length(
-    );
+    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
diff --git a/src/account.cpp b/src/account.cpp
index a747677..e84a540 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -38,11 +38,17 @@ olm::OneTimeKey const * olm::Account::lookup_key(
         }
     }
     if (current_fallback_key.published
-        && olm::array_equal(current_fallback_key.key.public_key.public_key, public_key.public_key)) {
+            && 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)) {
+            && olm::array_equal(
+                prev_fallback_key.key.public_key.public_key, public_key.public_key
+            )
+    ) {
         return &prev_fallback_key;
     }
     return 0;
@@ -62,11 +68,17 @@ 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
-        && olm::array_equal(current_fallback_key.key.public_key.public_key, public_key.public_key)) {
+            && 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)) {
+            && 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);
-- 
cgit v1.2.3-70-g09d2