Commit 3a382aec authored by Mark Haines's avatar Mark Haines
Browse files

Encode the account keys as a signed JSON object

parent 2a873fd4
...@@ -24,7 +24,12 @@ ...@@ -24,7 +24,12 @@
namespace olm { namespace olm {
struct LocalKey { struct IdentityKeys {
Ed25519KeyPair ed25519_key;
Curve25519KeyPair curve25519_key;
};
struct OneTimeKey {
std::uint32_t id; std::uint32_t id;
Curve25519KeyPair key; Curve25519KeyPair key;
}; };
...@@ -34,8 +39,8 @@ static std::size_t const MAX_ONE_TIME_KEYS = 100; ...@@ -34,8 +39,8 @@ static std::size_t const MAX_ONE_TIME_KEYS = 100;
struct Account { struct Account {
LocalKey identity_key; IdentityKeys identity_keys;
List<LocalKey, MAX_ONE_TIME_KEYS> one_time_keys; List<OneTimeKey, MAX_ONE_TIME_KEYS> one_time_keys;
ErrorCode last_error; ErrorCode last_error;
/** Number of random bytes needed to create a new account */ /** Number of random bytes needed to create a new account */
...@@ -47,7 +52,54 @@ struct Account { ...@@ -47,7 +52,54 @@ struct Account {
uint8_t const * random, std::size_t random_length uint8_t const * random, std::size_t random_length
); );
LocalKey const * lookup_key( /** Number of bytes needed to output the identity keys for this account */
std::size_t get_identity_json_length(
std::size_t user_id_length,
std::size_t device_id_length,
std::uint64_t valid_after_ts,
std::uint64_t valid_until_ts
);
/** Output the identity keys for this account as JSON in the following
* 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 "\"}}}"
*/
std::size_t get_identity_json(
std::uint8_t const * user_id, std::size_t user_id_length,
std::uint8_t const * device_id, std::size_t device_id_length,
std::uint64_t valid_after_ts,
std::uint64_t valid_until_ts,
std::uint8_t * identity_keys, std::size_t identity_keys_length
);
OneTimeKey const * lookup_key(
std::uint32_t id std::uint32_t id
); );
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#define OLM_HH_ #define OLM_HH_
#include <stddef.h> #include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
...@@ -131,18 +132,23 @@ size_t olm_create_account( ...@@ -131,18 +132,23 @@ size_t olm_create_account(
/** The size of the output buffer needed to hold the identity keys */ /** The size of the output buffer needed to hold the identity keys */
size_t olm_account_identity_keys_length( size_t olm_account_identity_keys_length(
OlmAccount * account OlmAccount * account,
size_t user_id_length,
size_t device_id_length,
uint64_t valid_after_ts,
uint64_t valid_until_ts
); );
/** Writes the public parts of the identity keys for the account into the /** Writes the public parts of the identity keys for the account into the
* identity_keys output buffer. The output is formatted as though it was * identity_keys output buffer. Returns olm_error() on failure. If the
* created with sprintf(output, "[[%10d,\"%43s\"]\n]", key_id, key_base64). * identity_keys buffer was too small then olm_account_last_error() will be
* The output can either be parsed as fixed width using the above format or by
* a JSON parser. Returns olm_error() on failure. If the identity_keys
* buffer was too small then olm_account_last_error() will be
* "OUTPUT_BUFFER_TOO_SMALL". */ * "OUTPUT_BUFFER_TOO_SMALL". */
size_t olm_account_identity_keys( size_t olm_account_identity_keys(
OlmAccount * account, OlmAccount * account,
void const * user_id, size_t user_id_length,
void const * device_id, size_t device_id_length,
uint64_t valid_after_ts,
uint64_t valid_until_ts,
void * identity_keys, size_t identity_key_length void * identity_keys, size_t identity_key_length
); );
......
...@@ -170,6 +170,39 @@ std::uint8_t const * unpickle( ...@@ -170,6 +170,39 @@ std::uint8_t const * unpickle(
); );
std::size_t pickle_length(
const Ed25519PublicKey & value
);
std::uint8_t * pickle(
std::uint8_t * pos,
const Ed25519PublicKey & value
);
std::uint8_t const * unpickle(
std::uint8_t const * pos, std::uint8_t const * end,
Ed25519PublicKey & value
);
std::size_t pickle_length(
const Ed25519KeyPair & value
);
std::uint8_t * pickle(
std::uint8_t * pos,
const Ed25519KeyPair & value
);
std::uint8_t const * unpickle(
std::uint8_t const * pos, std::uint8_t const * end,
Ed25519KeyPair & value
);
} // namespace olm } // namespace olm
......
...@@ -45,8 +45,15 @@ account_function( ...@@ -45,8 +45,15 @@ account_function(
) )
account_function(lib.olm_create_account_random_length) account_function(lib.olm_create_account_random_length)
account_function(lib.olm_create_account, c_void_p, c_size_t) account_function(lib.olm_create_account, c_void_p, c_size_t)
account_function(lib.olm_account_identity_keys_length) account_function(
account_function(lib.olm_account_identity_keys, c_void_p, c_size_t) lib.olm_account_identity_keys_length,
c_size_t, c_size_t, c_uint64, c_uint64
)
account_function(
lib.olm_account_identity_keys,
c_void_p, c_size_t, c_void_p, c_size_t, c_uint64, c_uint64,
c_void_p, c_size_t
)
account_function(lib.olm_account_one_time_keys_length) account_function(lib.olm_account_one_time_keys_length)
account_function(lib.olm_account_one_time_keys, c_void_p, c_size_t) account_function(lib.olm_account_one_time_keys, c_void_p, c_size_t)
...@@ -81,10 +88,20 @@ class Account(object): ...@@ -81,10 +88,20 @@ class Account(object):
self.ptr, key_buffer, len(key), pickle_buffer, len(pickle) self.ptr, key_buffer, len(key), pickle_buffer, len(pickle)
) )
def identity_keys(self): def identity_keys(self, user_id, device_id, valid_after, valid_until):
out_length = lib.olm_account_identity_keys_length(self.ptr) out_length = lib.olm_account_identity_keys_length(
self.ptr, len(user_id), len(device_id), valid_after, valid_until
)
user_id_buffer = create_string_buffer(user_id)
device_id_buffer = create_string_buffer(device_id)
out_buffer = create_string_buffer(out_length) out_buffer = create_string_buffer(out_length)
lib.olm_account_identity_keys(self.ptr, out_buffer, out_length) lib.olm_account_identity_keys(
self.ptr,
user_id_buffer, len(user_id),
device_id_buffer, len(device_id),
valid_after, valid_until,
out_buffer, out_length
)
return json.loads(out_buffer.raw) return json.loads(out_buffer.raw)
def one_time_keys(self): def one_time_keys(self):
...@@ -280,33 +297,31 @@ if __name__ == '__main__': ...@@ -280,33 +297,31 @@ if __name__ == '__main__':
create_account.set_defaults(func=do_create_account) create_account.set_defaults(func=do_create_account)
keys = commands.add_parser("keys", help="List public keys for an account") keys = commands.add_parser("keys", help="List public keys for an account")
keys.add_argument("account_file", help="Local account_file") keys.add_argument("--user-id", default="A User ID")
keys.add_argument("--device-id", default="A Device ID")
keys.add_argument("--valid-after", default=0, type=int)
keys.add_argument("--valid-until", default=0, type=int)
keys.add_argument("account_file", help="Local account file")
def do_keys(args): def do_keys(args):
account = Account() account = Account()
with open(args.account_file, "rb") as f: with open(args.account_file, "rb") as f:
account.unpickle(args.key, f.read()) account.unpickle(args.key, f.read())
(r_id, id_key), (signed_id, signed_key) = account.identity_keys()
ot_keys = account.one_time_keys()
result1 = { result1 = {
"identityKey": str(id_key), "device_keys": account.identity_keys(
"signedKey": { args.user_id, args.device_id,
"keyId": signed_id, args.valid_after, args.valid_until,
"publicKey": str(signed_key), )
},
"lastResortKey": {
"keyId": ot_keys[0][0],
"publicKey": str(ot_keys[0][1]),
},
} }
ot_keys = account.one_time_keys()
result2 = { result2 = {
"oneTimeKeys": [{ "one_time_keys": [{
"keyId": k[0], "keyId": k[0],
"publicKey": str(k[1]), "publicKey": str(k[1]),
} for k in ot_keys[1:]] } for k in ot_keys[1:]]
} }
try: try:
yaml.dump(result1, sys.stdout, default_flow_style=False) yaml.safe_dump(result1, sys.stdout, default_flow_style=False)
yaml.dump(result2, sys.stdout, default_flow_style=False) yaml.dump(result2, sys.stdout, default_flow_style=False)
except: except:
pass pass
......
...@@ -13,13 +13,14 @@ ...@@ -13,13 +13,14 @@
* limitations under the License. * limitations under the License.
*/ */
#include "olm/account.hh" #include "olm/account.hh"
#include "olm/base64.hh"
#include "olm/pickle.hh" #include "olm/pickle.hh"
olm::LocalKey const * olm::Account::lookup_key( olm::OneTimeKey const * olm::Account::lookup_key(
std::uint32_t id std::uint32_t id
) { ) {
for (olm::LocalKey const & key : one_time_keys) { for (olm::OneTimeKey const & key : one_time_keys) {
if (key.id == id) return &key; if (key.id == id) return &key;
} }
return 0; return 0;
...@@ -28,7 +29,7 @@ olm::LocalKey const * olm::Account::lookup_key( ...@@ -28,7 +29,7 @@ olm::LocalKey const * olm::Account::lookup_key(
std::size_t olm::Account::remove_key( std::size_t olm::Account::remove_key(
std::uint32_t id std::uint32_t id
) { ) {
LocalKey * i; OneTimeKey * i;
for (i = one_time_keys.begin(); i != one_time_keys.end(); ++i) { for (i = one_time_keys.begin(); i != one_time_keys.end(); ++i) {
if (i->id == id) { if (i->id == id) {
one_time_keys.erase(i); one_time_keys.erase(i);
...@@ -52,14 +53,13 @@ std::size_t olm::Account::new_account( ...@@ -52,14 +53,13 @@ std::size_t olm::Account::new_account(
unsigned id = 0; unsigned id = 0;
identity_key.id = ++id; olm::ed25519_generate_key(random, identity_keys.ed25519_key);
olm::curve25519_generate_key(random, identity_key.key);
random += 32; random += 32;
olm::curve25519_generate_key(random, identity_keys.curve25519_key);
random += 32; random += 32;
for (unsigned i = 0; i < 10; ++i) { for (unsigned i = 0; i < 10; ++i) {
LocalKey & key = *one_time_keys.insert(one_time_keys.end()); OneTimeKey & key = *one_time_keys.insert(one_time_keys.end());
key.id = ++id; key.id = ++id;
olm::curve25519_generate_key(random, key.key); olm::curve25519_generate_key(random, key.key);
random += 32; random += 32;
...@@ -68,12 +68,200 @@ std::size_t olm::Account::new_account( ...@@ -68,12 +68,200 @@ std::size_t olm::Account::new_account(
return 0; return 0;
} }
namespace {
static const uint8_t IDENTITY_JSON_PART_0[] =
"{\"algorithms\":"
"[\"m.olm.curve25519-aes-sha256\""
"],\"device_id\":\"";
static const uint8_t IDENTITY_JSON_PART_1[] = "\",\"keys\":{\"curve25519:";
static const uint8_t IDENTITY_JSON_PART_2[] = "\":\"";
static const uint8_t IDENTITY_JSON_PART_3[] = "\",\"ed25519:";
static const uint8_t IDENTITY_JSON_PART_4[] = "\":\"";
static const uint8_t IDENTITY_JSON_PART_5[] = "\"},\"user_id\":\"";
static const uint8_t IDENTITY_JSON_PART_6[] = "\",\"valid_after_ts\":";
static const uint8_t IDENTITY_JSON_PART_7[] = ",\"valid_until_ts\":";
static const uint8_t IDENTITY_JSON_PART_8[] = ",\"signatures\":{\"";
static const uint8_t IDENTITY_JSON_PART_9[] = "/";
static const uint8_t IDENTITY_JSON_PART_A[] = "\":{\"ed25519:";
static const uint8_t IDENTITY_JSON_PART_B[] = "\":\"";
static const uint8_t IDENTITY_JSON_PART_C[] = "\"}}}";
std::size_t count_digits(
std::uint64_t value
) {
std::size_t digits = 0;
do {
digits++;
value /= 10;
} while (value);
return digits;
}
template<typename T>
std::uint8_t * write_string(
std::uint8_t * pos,
T const & value
) {
std::memcpy(pos, value, sizeof(T) - 1);
return pos + (sizeof(T) - 1);
}
std::uint8_t * write_string(
std::uint8_t * pos,
std::uint8_t const * value, std::size_t value_length
) {
std::memcpy(pos, value, value_length);
return pos + value_length;
}
std::uint8_t * write_digits(
std::uint8_t * pos,
std::uint64_t value
) {
size_t digits = count_digits(value);
pos += digits;
do {
*(--pos) = '0' + (value % 10);
value /= 10;
} while (value);
return pos + digits;
}
}
std::size_t olm::Account::get_identity_json_length(
std::size_t user_id_length,
std::size_t device_id_length,
std::uint64_t valid_after_ts,
std::uint64_t valid_until_ts
) {
std::size_t length = 0;
length += sizeof(IDENTITY_JSON_PART_0) - 1;
length += device_id_length;
length += sizeof(IDENTITY_JSON_PART_1) - 1;
length += 4;
length += sizeof(IDENTITY_JSON_PART_2) - 1;
length += 43;
length += sizeof(IDENTITY_JSON_PART_3) - 1;
length += 4;
length += sizeof(IDENTITY_JSON_PART_4) - 1;
length += 43;
length += sizeof(IDENTITY_JSON_PART_5) - 1;
length += user_id_length;
length += sizeof(IDENTITY_JSON_PART_6) - 1;
length += count_digits(valid_after_ts);
length += sizeof(IDENTITY_JSON_PART_7) - 1;
length += count_digits(valid_until_ts);
length += sizeof(IDENTITY_JSON_PART_8) - 1;
length += user_id_length;
length += sizeof(IDENTITY_JSON_PART_9) - 1;
length += device_id_length;
length += sizeof(IDENTITY_JSON_PART_A) - 1;
length += 4;
length += sizeof(IDENTITY_JSON_PART_B) - 1;
length += 86;
length += sizeof(IDENTITY_JSON_PART_C) - 1;
return length;
}
std::size_t olm::Account::get_identity_json(
std::uint8_t const * user_id, std::size_t user_id_length,
std::uint8_t const * device_id, std::size_t device_id_length,
std::uint64_t valid_until_ts,
std::uint64_t valid_after_ts,
std::uint8_t * identity_json, std::size_t identity_json_length
) {
std::uint8_t * pos = identity_json;
std::uint8_t signature[64];
size_t expected_length = get_identity_json_length(
user_id_length, device_id_length, valid_after_ts, valid_until_ts
);
if (identity_json_length < expected_length) {
last_error = olm::ErrorCode::OUTPUT_BUFFER_TOO_SMALL;
return std::size_t(-1);
}
pos = write_string(pos, IDENTITY_JSON_PART_0);
pos = write_string(pos, device_id, device_id_length);
pos = write_string(pos, IDENTITY_JSON_PART_1);
encode_base64(identity_keys.curve25519_key.public_key, 3, pos);
pos += 4;
pos = write_string(pos, IDENTITY_JSON_PART_2);
encode_base64(identity_keys.curve25519_key.public_key, 32, pos);
pos += 43;
pos = write_string(pos, IDENTITY_JSON_PART_3);
encode_base64(identity_keys.ed25519_key.public_key, 3, pos);
pos += 4;
pos = write_string(pos, IDENTITY_JSON_PART_4);
encode_base64(identity_keys.ed25519_key.public_key, 32, pos);
pos += 43;
pos = write_string(pos, IDENTITY_JSON_PART_5);
pos = write_string(pos, user_id, user_id_length);
pos = write_string(pos, IDENTITY_JSON_PART_6);
pos = write_digits(pos, valid_after_ts);
pos = write_string(pos, IDENTITY_JSON_PART_7);
pos = write_digits(pos, valid_until_ts);
*pos = '}';
// Sign the JSON up to written up to this point.
ed25519_sign(
identity_keys.ed25519_key,
identity_json, 1 + pos - identity_json,
signature
);
// Append the signature to the end of the JSON.
pos = write_string(pos, IDENTITY_JSON_PART_8);
pos = write_string(pos, user_id, user_id_length);
pos = write_string(pos, IDENTITY_JSON_PART_9);
pos = write_string(pos, device_id, device_id_length);
pos = write_string(pos, IDENTITY_JSON_PART_A);
encode_base64(identity_keys.ed25519_key.public_key, 3, pos);
pos += 4;
pos = write_string(pos, IDENTITY_JSON_PART_B);
encode_base64(signature, 64, pos);
pos += 86;
pos = write_string(pos, IDENTITY_JSON_PART_C);
return pos - identity_json;
}
namespace olm { namespace olm {
static std::size_t pickle_length(
olm::IdentityKeys const & value
) {
size_t length = 0;
length += olm::pickle_length(value.ed25519_key);
length += olm::pickle_length(value.curve25519_key);
return length;
}
static std::uint8_t * pickle(
std::uint8_t * pos,
olm::IdentityKeys const & value
) {
pos = olm::pickle(pos, value.ed25519_key);
pos = olm::pickle(pos, value.curve25519_key);
return pos;
}
static std::uint8_t const * unpickle(
std::uint8_t const * pos, std::uint8_t const * end,
olm::IdentityKeys & value
) {
pos = olm::unpickle(pos, end, value.ed25519_key);
pos = olm::unpickle(pos, end, value.curve25519_key);
return pos;
}
static std::size_t pickle_length( static std::size_t pickle_length(
olm::LocalKey const & value olm::OneTimeKey const & value
) { ) {
return olm::pickle_length(value.id) + olm::pickle_length(value.key); return olm::pickle_length(value.id) + olm::pickle_length(value.key);
} }
...@@ -81,7 +269,7 @@ static std::size_t pickle_length( ...@@ -81,7 +269,7 @@ static std::size_t pickle_length(
static std::uint8_t * pickle( static std::uint8_t * pickle(
std::uint8_t * pos, std::uint8_t * pos,
olm::LocalKey const & value olm::OneTimeKey const & value
) { ) {
pos = olm::pickle(pos, value.id); pos = olm::pickle(pos, value.id);
pos = olm::pickle(pos, value.key); pos = olm::pickle(pos, value.key);
...@@ -91,7 +279,7 @@ static std::uint8_t * pickle( ...@@ -91,7 +279,7 @@ static std::uint8_t * pickle(
static std::uint8_t const * unpickle( static std::uint8_t const * unpickle(
std::uint8_t const * pos, std::uint8_t const * end, std::uint8_t const * pos, std::uint8_t const * end,
olm::LocalKey & value olm::OneTimeKey & value
) { ) {
pos = olm::unpickle(pos, end, value.id); pos = olm::unpickle(pos, end, value.id);
pos = olm::unpickle(pos, end, value.key); pos = olm::unpickle(pos, end, value.key);
...@@ -105,7 +293,7 @@ std::size_t olm::pickle_length( ...@@ -105,7 +293,7 @@ std::size_t olm::pickle_length(
olm::Account const & value olm::Account const & value
) { ) {
std::size_t length = 0; std::size_t length = 0;
length += olm::pickle_length(value.identity_key); length += olm::pickle_length(value.identity_keys);
length += olm::pickle_length(value.one_time_keys); length += olm::pickle_length(value.one_time_keys);
return length; return length;
} }
...@@ -115,7 +303,7 @@ std::uint8_t * olm::pickle( ...@@ -115,7 +303,7 @@ std::uint8_t * olm::pickle(
std::uint8_t * pos, std::uint8_t * pos,
olm::Account const & value olm::Account const & value
) { ) {
pos = olm::pickle(pos, value.identity_key); pos = olm::pickle(pos, value.identity_keys);