1 /* Copyright 2015, 2016 OpenMarket Ltd
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
15 #include "olm/account.hh"
16 #include "olm/base64.hh"
17 #include "olm/pickle.h"
18 #include "olm/pickle.hh"
19 #include "olm/memory.hh"
21 olm::Account::Account(
22 ) : num_fallback_keys(0),
23 next_one_time_key_id(0),
24 last_error(OlmErrorCode::OLM_SUCCESS) {
28 olm::OneTimeKey const * olm::Account::lookup_key(
29 _olm_curve25519_public_key const & public_key
31 for (olm::OneTimeKey const & key : one_time_keys) {
32 if (olm::array_equal(key.key.public_key.public_key, public_key.public_key)) {
36 if (num_fallback_keys >= 1
38 current_fallback_key.key.public_key.public_key, public_key.public_key
41 return ¤t_fallback_key;
43 if (num_fallback_keys >= 2
45 prev_fallback_key.key.public_key.public_key, public_key.public_key
48 return &prev_fallback_key;
53 std::size_t olm::Account::remove_key(
54 _olm_curve25519_public_key const & public_key
57 for (i = one_time_keys.begin(); i != one_time_keys.end(); ++i) {
58 if (olm::array_equal(i->key.public_key.public_key, public_key.public_key)) {
59 std::uint32_t id = i->id;
60 one_time_keys.erase(i);
64 // check if the key is a fallback key, to avoid returning an error, but
65 // don't actually remove it
66 if (num_fallback_keys >= 1
68 current_fallback_key.key.public_key.public_key, public_key.public_key
71 return current_fallback_key.id;
73 if (num_fallback_keys >= 2
75 prev_fallback_key.key.public_key.public_key, public_key.public_key
78 return prev_fallback_key.id;
80 return std::size_t(-1);
83 std::size_t olm::Account::new_account_random_length() const {
84 return ED25519_RANDOM_LENGTH + CURVE25519_RANDOM_LENGTH;
87 std::size_t olm::Account::new_account(
88 uint8_t const * random, std::size_t random_length
90 if (random_length < new_account_random_length()) {
91 last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
92 return std::size_t(-1);
95 _olm_crypto_ed25519_generate_key(random, &identity_keys.ed25519_key);
96 random += ED25519_RANDOM_LENGTH;
97 _olm_crypto_curve25519_generate_key(random, &identity_keys.curve25519_key);
104 uint8_t KEY_JSON_ED25519[] = "\"ed25519\":";
105 uint8_t KEY_JSON_CURVE25519[] = "\"curve25519\":";
108 static std::uint8_t * write_string(
112 std::memcpy(pos, value, sizeof(T) - 1);
113 return pos + (sizeof(T) - 1);
119 std::size_t olm::Account::get_identity_json_length() const {
120 std::size_t length = 0;
122 length += sizeof(KEY_JSON_CURVE25519) - 1;
124 length += olm::encode_base64_length(
125 sizeof(identity_keys.curve25519_key.public_key)
127 length += 2; /* ", */
128 length += sizeof(KEY_JSON_ED25519) - 1;
130 length += olm::encode_base64_length(
131 sizeof(identity_keys.ed25519_key.public_key)
133 length += 2; /* "} */
138 std::size_t olm::Account::get_identity_json(
139 std::uint8_t * identity_json, std::size_t identity_json_length
141 std::uint8_t * pos = identity_json;
142 size_t expected_length = get_identity_json_length();
144 if (identity_json_length < expected_length) {
145 last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
146 return std::size_t(-1);
150 pos = write_string(pos, KEY_JSON_CURVE25519);
152 pos = olm::encode_base64(
153 identity_keys.curve25519_key.public_key.public_key,
154 sizeof(identity_keys.curve25519_key.public_key.public_key),
157 *(pos++) = '\"'; *(pos++) = ',';
158 pos = write_string(pos, KEY_JSON_ED25519);
160 pos = olm::encode_base64(
161 identity_keys.ed25519_key.public_key.public_key,
162 sizeof(identity_keys.ed25519_key.public_key.public_key),
165 *(pos++) = '\"'; *(pos++) = '}';
166 return pos - identity_json;
170 std::size_t olm::Account::signature_length(
172 return ED25519_SIGNATURE_LENGTH;
176 std::size_t olm::Account::sign(
177 std::uint8_t const * message, std::size_t message_length,
178 std::uint8_t * signature, std::size_t signature_length
180 if (signature_length < this->signature_length()) {
181 last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
182 return std::size_t(-1);
184 _olm_crypto_ed25519_sign(
185 &identity_keys.ed25519_key, message, message_length, signature
187 return this->signature_length();
191 std::size_t olm::Account::get_one_time_keys_json_length(
193 std::size_t length = 0;
194 bool is_empty = true;
195 for (auto const & key : one_time_keys) {
200 length += 2; /* {" */
201 length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
202 length += 3; /* ":" */
203 length += olm::encode_base64_length(sizeof(key.key.public_key));
209 length += 3; /* }{} */
210 length += sizeof(KEY_JSON_CURVE25519) - 1;
215 std::size_t olm::Account::get_one_time_keys_json(
216 std::uint8_t * one_time_json, std::size_t one_time_json_length
218 std::uint8_t * pos = one_time_json;
219 if (one_time_json_length < get_one_time_keys_json_length()) {
220 last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
221 return std::size_t(-1);
224 pos = write_string(pos, KEY_JSON_CURVE25519);
225 std::uint8_t sep = '{';
226 for (auto const & key : one_time_keys) {
232 std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
233 _olm_pickle_uint32(key_id, key.id);
234 pos = olm::encode_base64(key_id, sizeof(key_id), pos);
235 *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"';
236 pos = olm::encode_base64(
237 key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos
243 /* The list was empty */
248 return pos - one_time_json;
252 std::size_t olm::Account::mark_keys_as_published(
254 std::size_t count = 0;
255 for (auto & key : one_time_keys) {
256 if (!key.published) {
257 key.published = true;
261 current_fallback_key.published = true;
266 std::size_t olm::Account::max_number_of_one_time_keys(
268 return olm::MAX_ONE_TIME_KEYS;
271 std::size_t olm::Account::generate_one_time_keys_random_length(
272 std::size_t number_of_keys
274 return CURVE25519_RANDOM_LENGTH * number_of_keys;
277 std::size_t olm::Account::generate_one_time_keys(
278 std::size_t number_of_keys,
279 std::uint8_t const * random, std::size_t random_length
281 if (random_length < generate_one_time_keys_random_length(number_of_keys)) {
282 last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
283 return std::size_t(-1);
285 for (unsigned i = 0; i < number_of_keys; ++i) {
286 OneTimeKey & key = *one_time_keys.insert(one_time_keys.begin());
287 key.id = ++next_one_time_key_id;
288 key.published = false;
289 _olm_crypto_curve25519_generate_key(random, &key.key);
290 random += CURVE25519_RANDOM_LENGTH;
292 return number_of_keys;
295 std::size_t olm::Account::generate_fallback_key_random_length() const {
296 return CURVE25519_RANDOM_LENGTH;
299 std::size_t olm::Account::generate_fallback_key(
300 std::uint8_t const * random, std::size_t random_length
302 if (random_length < generate_fallback_key_random_length()) {
303 last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
304 return std::size_t(-1);
306 if (num_fallback_keys < 2) {
309 prev_fallback_key = current_fallback_key;
310 current_fallback_key.id = ++next_one_time_key_id;
311 current_fallback_key.published = false;
312 _olm_crypto_curve25519_generate_key(random, ¤t_fallback_key.key);
317 std::size_t olm::Account::get_fallback_key_json_length(
319 std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */
320 if (num_fallback_keys >= 1) {
321 const OneTimeKey & key = current_fallback_key;
323 length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
324 length += 3; /* ":" */
325 length += olm::encode_base64_length(sizeof(key.key.public_key));
331 std::size_t olm::Account::get_fallback_key_json(
332 std::uint8_t * fallback_json, std::size_t fallback_json_length
334 std::uint8_t * pos = fallback_json;
335 if (fallback_json_length < get_fallback_key_json_length()) {
336 last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
337 return std::size_t(-1);
340 pos = write_string(pos, KEY_JSON_CURVE25519);
342 OneTimeKey & key = current_fallback_key;
343 if (num_fallback_keys >= 1) {
345 std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
346 _olm_pickle_uint32(key_id, key.id);
347 pos = olm::encode_base64(key_id, sizeof(key_id), pos);
348 *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"';
349 pos = olm::encode_base64(
350 key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos
356 return pos - fallback_json;
359 std::size_t olm::Account::get_unpublished_fallback_key_json_length(
361 std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */
362 const OneTimeKey & key = current_fallback_key;
363 if (num_fallback_keys >= 1 && !key.published) {
365 length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
366 length += 3; /* ":" */
367 length += olm::encode_base64_length(sizeof(key.key.public_key));
373 std::size_t olm::Account::get_unpublished_fallback_key_json(
374 std::uint8_t * fallback_json, std::size_t fallback_json_length
376 std::uint8_t * pos = fallback_json;
377 if (fallback_json_length < get_unpublished_fallback_key_json_length()) {
378 last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
379 return std::size_t(-1);
382 pos = write_string(pos, KEY_JSON_CURVE25519);
384 OneTimeKey & key = current_fallback_key;
385 if (num_fallback_keys >= 1 && !key.published) {
387 std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
388 _olm_pickle_uint32(key_id, key.id);
389 pos = olm::encode_base64(key_id, sizeof(key_id), pos);
390 *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"';
391 pos = olm::encode_base64(
392 key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos
398 return pos - fallback_json;
401 void olm::Account::forget_old_fallback_key(
403 if (num_fallback_keys >= 2) {
404 num_fallback_keys = 1;
405 olm::unset(&prev_fallback_key, sizeof(prev_fallback_key));
411 static std::size_t pickle_length(
412 olm::IdentityKeys const & value
415 length += _olm_pickle_ed25519_key_pair_length(&value.ed25519_key);
416 length += olm::pickle_length(value.curve25519_key);
421 static std::uint8_t * pickle(
423 olm::IdentityKeys const & value
425 pos = _olm_pickle_ed25519_key_pair(pos, &value.ed25519_key);
426 pos = olm::pickle(pos, value.curve25519_key);
431 static std::uint8_t const * unpickle(
432 std::uint8_t const * pos, std::uint8_t const * end,
433 olm::IdentityKeys & value
435 pos = _olm_unpickle_ed25519_key_pair(pos, end, &value.ed25519_key); UNPICKLE_OK(pos);
436 pos = olm::unpickle(pos, end, value.curve25519_key); UNPICKLE_OK(pos);
441 static std::size_t pickle_length(
442 olm::OneTimeKey const & value
444 std::size_t length = 0;
445 length += olm::pickle_length(value.id);
446 length += olm::pickle_length(value.published);
447 length += olm::pickle_length(value.key);
452 static std::uint8_t * pickle(
454 olm::OneTimeKey const & value
456 pos = olm::pickle(pos, value.id);
457 pos = olm::pickle(pos, value.published);
458 pos = olm::pickle(pos, value.key);
463 static std::uint8_t const * unpickle(
464 std::uint8_t const * pos, std::uint8_t const * end,
465 olm::OneTimeKey & value
467 pos = olm::unpickle(pos, end, value.id); UNPICKLE_OK(pos);
468 pos = olm::unpickle(pos, end, value.published); UNPICKLE_OK(pos);
469 pos = olm::unpickle(pos, end, value.key); UNPICKLE_OK(pos);
476 // pickle version 1 used only 32 bytes for the ed25519 private key.
477 // Any keys thus used should be considered compromised.
478 // pickle version 2 does not have fallback keys.
479 // pickle version 3 does not store whether the current fallback key is published.
480 static const std::uint32_t ACCOUNT_PICKLE_VERSION = 4;
484 std::size_t olm::pickle_length(
485 olm::Account const & value
487 std::size_t length = 0;
488 length += olm::pickle_length(ACCOUNT_PICKLE_VERSION);
489 length += olm::pickle_length(value.identity_keys);
490 length += olm::pickle_length(value.one_time_keys);
491 length += olm::pickle_length(value.num_fallback_keys);
492 if (value.num_fallback_keys >= 1) {
493 length += olm::pickle_length(value.current_fallback_key);
494 if (value.num_fallback_keys >= 2) {
495 length += olm::pickle_length(value.prev_fallback_key);
498 length += olm::pickle_length(value.next_one_time_key_id);
503 std::uint8_t * olm::pickle(
505 olm::Account const & value
507 pos = olm::pickle(pos, ACCOUNT_PICKLE_VERSION);
508 pos = olm::pickle(pos, value.identity_keys);
509 pos = olm::pickle(pos, value.one_time_keys);
510 pos = olm::pickle(pos, value.num_fallback_keys);
511 if (value.num_fallback_keys >= 1) {
512 pos = olm::pickle(pos, value.current_fallback_key);
513 if (value.num_fallback_keys >= 2) {
514 pos = olm::pickle(pos, value.prev_fallback_key);
517 pos = olm::pickle(pos, value.next_one_time_key_id);
522 std::uint8_t const * olm::unpickle(
523 std::uint8_t const * pos, std::uint8_t const * end,
526 uint32_t pickle_version;
528 pos = olm::unpickle(pos, end, pickle_version); UNPICKLE_OK(pos);
530 switch (pickle_version) {
531 case ACCOUNT_PICKLE_VERSION:
536 value.last_error = OlmErrorCode::OLM_BAD_LEGACY_ACCOUNT_PICKLE;
539 value.last_error = OlmErrorCode::OLM_UNKNOWN_PICKLE_VERSION;
543 pos = olm::unpickle(pos, end, value.identity_keys); UNPICKLE_OK(pos);
544 pos = olm::unpickle(pos, end, value.one_time_keys); UNPICKLE_OK(pos);
546 if (pickle_version <= 2) {
547 // version 2 did not have fallback keys
548 value.num_fallback_keys = 0;
549 } else if (pickle_version == 3) {
550 // version 3 used the published flag to indicate how many fallback keys
551 // were present (we'll have to assume that the keys were published)
552 pos = olm::unpickle(pos, end, value.current_fallback_key); UNPICKLE_OK(pos);
553 pos = olm::unpickle(pos, end, value.prev_fallback_key); UNPICKLE_OK(pos);
554 if (value.current_fallback_key.published) {
555 if (value.prev_fallback_key.published) {
556 value.num_fallback_keys = 2;
558 value.num_fallback_keys = 1;
561 value.num_fallback_keys = 0;
564 pos = olm::unpickle(pos, end, value.num_fallback_keys); UNPICKLE_OK(pos);
565 if (value.num_fallback_keys >= 1) {
566 pos = olm::unpickle(pos, end, value.current_fallback_key); UNPICKLE_OK(pos);
567 if (value.num_fallback_keys >= 2) {
568 pos = olm::unpickle(pos, end, value.prev_fallback_key); UNPICKLE_OK(pos);
569 if (value.num_fallback_keys >= 3) {
570 value.last_error = OlmErrorCode::OLM_CORRUPTED_PICKLE;
577 pos = olm::unpickle(pos, end, value.next_one_time_key_id); UNPICKLE_OK(pos);