From 5634be05074168e33b77246bbc9b60bd683759d8 Mon Sep 17 00:00:00 2001
From: Mark Haines <mark.haines@matrix.org>
Date: Thu, 9 Jul 2015 16:09:16 +0100
Subject: Add methods for generating new one time keys and for tracking which
 one time keys have been published

---
 include/olm/account.hh | 88 ++++++++++++++++++++++++++++++++------------------
 src/account.cpp        | 73 +++++++++++++++++++++++++++++++++++------
 2 files changed, 121 insertions(+), 40 deletions(-)

diff --git a/include/olm/account.hh b/include/olm/account.hh
index cf886d1..f54758d 100644
--- a/include/olm/account.hh
+++ b/include/olm/account.hh
@@ -31,6 +31,7 @@ struct IdentityKeys {
 
 struct OneTimeKey {
     std::uint32_t id;
+    bool published;
     Curve25519KeyPair key;
 };
 
@@ -39,15 +40,17 @@ static std::size_t const MAX_ONE_TIME_KEYS = 100;
 
 
 struct Account {
+    Account();
     IdentityKeys identity_keys;
     List<OneTimeKey, MAX_ONE_TIME_KEYS> one_time_keys;
+    std::uint32_t next_one_time_key_id;
     ErrorCode last_error;
 
     /** Number of random bytes needed to create a new account */
     std::size_t new_account_random_length();
 
-    /** Create a new account. Returns NOT_ENOUGH_RANDOM if the number of random
-     * bytes is too small. */
+    /** Create a new account. 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 new_account(
         uint8_t const * random, std::size_t random_length
     );
@@ -61,35 +64,30 @@ struct Account {
     );
 
     /** Output the identity keys for this account as JSON in the following
-     * format.
+     * format:
      *
-     *  14 {"algorithms":
-     *  30 ["m.olm.curve25519-aes-sha256"
-     *  15 ],"device_id":"
-     *   ? <device identifier>
-     *  22 ","keys":{"curve25519:
-     *   4 <base64 characters>
-     *   3 ":"
-     *  43 <base64 characters>
-     *  11 ","ed25519:
-     *   4 <base64 characters>
-     *   3 ":"
-     *  43 <base64 characters>
-     *  14 "},"user_id":"
-     *   ? <user identifier>
-     *  19 ","valid_after_ts":
-     *   ? <digits>
-     *  18 ,"valid_until_ts":
-     *   ? <digits>
-     *  16 ,"signatures":{"
-     *   ? <user identifier>
-     *   1 /
-     *   ? <device identifier>
-     *  12 ":{"ed25519:
-     *   4 <base64 characters>
-     *   3 ":"
-     *  86 <base64 characters>
-     *   4 "}}}
+     *    {"algorithms":
+     *    ["m.olm.curve25519-aes-sha256"
+     *    ]
+     *    ,"device_id":"<device identifier>"
+     *    ,"keys":
+     *    {"curve25519:<key id>":"<base64 characters>"
+     *    ,"ed25519:<key id>":"<base64 characters>"
+     *    }
+     *    ,"user_id":"<user identifier>"
+     *    ,"valid_after_ts":<digits>
+     *    ,"valid_until_ts":<digits>
+     *    ,"signatures":
+     *    {"<user identifier>/<device identifier>":
+     *    {"ed25519:<key id>":"<base64 characters>"
+     *    }
+     *    }
+     *    }
+     *
+     * The user_id and device_id must not contain 0x00-0x1F, '\"' or '\\'.
+     * The JSON up to but not including the "signatures" key will be signed
+     * using the account's ed25519 key. That signature is then included under
+     * the "signatures" key.
      *
      * 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. */
@@ -104,7 +102,13 @@ struct Account {
     /** Number of bytes needed to output the one time keys for this account */
     std::size_t get_one_time_keys_json_length();
 
-    /*
+    /** Output the one time keys that haven't been published yet as JSON:
+     *
+     *  {"curve25519:<key id>":"<base64 characters>"
+     *  ,"curve25519:<key_id>":"<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.
      */
@@ -112,6 +116,28 @@ struct Account {
         std::uint8_t * one_time_json, std::size_t one_time_json_length
     );
 
+    /** Mark the current list of one_time_keys as being published. They
+     * will no longer be returned by get_one_time_keys_json_length(). */
+    std::size_t mark_keys_as_published();
+
+    /** The largest number of one time keys this account can store. */
+    std::size_t max_number_of_one_time_keys();
+
+    /** Returns the number of random bytes needed to generate a given number
+     * of new one time keys. */
+    std::size_t generate_one_time_keys_random_length(
+        std::size_t number_of_keys
+    );
+
+    /** Generates a number of new one time keys. If the total number of keys
+     * stored by this account exceeds max_number_of_one_time_keys() then the
+     * old keys are discarded. 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_one_time_keys(
+        std::size_t number_of_keys,
+        std::uint8_t const * random, std::size_t random_length
+    );
+
     /** Lookup a one time key with the given public key */
     OneTimeKey const * lookup_key(
         Curve25519PublicKey const & public_key
diff --git a/src/account.cpp b/src/account.cpp
index 5bbd6a6..ede327b 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -16,6 +16,11 @@
 #include "olm/base64.hh"
 #include "olm/pickle.hh"
 
+olm::Account::Account(
+) : next_one_time_key_id(0),
+    last_error(olm::ErrorCode::SUCCESS) {
+}
+
 
 olm::OneTimeKey const * olm::Account::lookup_key(
     olm::Curve25519PublicKey const & public_key
@@ -54,19 +59,12 @@ std::size_t olm::Account::new_account(
         return std::size_t(-1);
     }
 
-    unsigned id = 0;
-
     olm::ed25519_generate_key(random, identity_keys.ed25519_key);
     random += 32;
     olm::curve25519_generate_key(random, identity_keys.curve25519_key);
     random += 32;
 
-    for (unsigned i = 0; i < 10; ++i) {
-        OneTimeKey & key = *one_time_keys.insert(one_time_keys.end());
-        key.id = ++id;
-        olm::curve25519_generate_key(random, key.key);
-        random += 32;
-    }
+    generate_one_time_keys(10, random, random_length - 64);
 
     return 0;
 }
@@ -236,6 +234,9 @@ std::size_t olm::Account::get_one_time_keys_json_length(
 ) {
     std::size_t length = 0;
     for (auto const & key : one_time_keys) {
+        if (key.published) {
+            continue;
+        }
         length += 2; /* {" */
         length += sizeof(ONE_TIME_KEY_JSON_ALG) - 1;
         length += 1; /* : */
@@ -262,6 +263,9 @@ std::size_t olm::Account::get_one_time_keys_json(
     }
     std::uint8_t sep = '{';
     for (auto const & key : one_time_keys) {
+        if (key.published) {
+            continue;
+        }
         *(pos++) = sep;
         *(pos++) = '\"';
         pos = write_string(pos, ONE_TIME_KEY_JSON_ALG);
@@ -284,6 +288,48 @@ std::size_t olm::Account::get_one_time_keys_json(
 }
 
 
+std::size_t olm::Account::mark_keys_as_published(
+) {
+    std::size_t count = 0;
+    for (auto & key : one_time_keys) {
+        if (!key.published) {
+            key.published = true;
+            count++;
+        }
+    }
+    return count;
+}
+
+
+std::size_t olm::Account::max_number_of_one_time_keys(
+) {
+    return olm::MAX_ONE_TIME_KEYS;
+}
+
+std::size_t olm::Account::generate_one_time_keys_random_length(
+    std::size_t number_of_keys
+) {
+    return 32 * number_of_keys;
+}
+
+std::size_t olm::Account::generate_one_time_keys(
+    std::size_t number_of_keys,
+    std::uint8_t const * random, std::size_t random_length
+) {
+    if (random_length < generate_one_time_keys_random_length(number_of_keys)) {
+        last_error = olm::ErrorCode::NOT_ENOUGH_RANDOM;
+        return std::size_t(-1);
+    }
+    for (unsigned i = 0; i < number_of_keys; ++i) {
+        OneTimeKey & key = *one_time_keys.insert(one_time_keys.begin());
+        key.id = ++next_one_time_key_id;
+        key.published = false;
+        olm::curve25519_generate_key(random, key.key);
+        random += 32;
+    }
+    return number_of_keys;
+}
+
 namespace olm {
 
 static std::size_t pickle_length(
@@ -319,7 +365,11 @@ static std::uint8_t const * unpickle(
 static std::size_t pickle_length(
     olm::OneTimeKey const & value
 ) {
-    return olm::pickle_length(value.id) + olm::pickle_length(value.key);
+    std::size_t length = 0;
+    length += olm::pickle_length(value.id);
+    length += olm::pickle_length(value.published);
+    length += olm::pickle_length(value.key);
+    return length;
 }
 
 
@@ -328,6 +378,7 @@ static std::uint8_t * pickle(
     olm::OneTimeKey const & value
 ) {
     pos = olm::pickle(pos, value.id);
+    pos = olm::pickle(pos, value.published);
     pos = olm::pickle(pos, value.key);
     return pos;
 }
@@ -338,6 +389,7 @@ static std::uint8_t const * unpickle(
     olm::OneTimeKey & value
 ) {
     pos = olm::unpickle(pos, end, value.id);
+    pos = olm::unpickle(pos, end, value.published);
     pos = olm::unpickle(pos, end, value.key);
     return pos;
 }
@@ -351,6 +403,7 @@ std::size_t olm::pickle_length(
     std::size_t length = 0;
     length += olm::pickle_length(value.identity_keys);
     length += olm::pickle_length(value.one_time_keys);
+    length += olm::pickle_length(value.next_one_time_key_id);
     return length;
 }
 
@@ -361,6 +414,7 @@ std::uint8_t * olm::pickle(
 ) {
     pos = olm::pickle(pos, value.identity_keys);
     pos = olm::pickle(pos, value.one_time_keys);
+    pos = olm::pickle(pos, value.next_one_time_key_id);
     return pos;
 }
 
@@ -371,5 +425,6 @@ std::uint8_t const * olm::unpickle(
 ) {
     pos = olm::unpickle(pos, end, value.identity_keys);
     pos = olm::unpickle(pos, end, value.one_time_keys);
+    pos = olm::unpickle(pos, end, value.next_one_time_key_id);
     return pos;
 }
-- 
cgit v1.2.3-70-g09d2