1/* Copyright 2015, 2016 OpenMarket Ltd
2 *
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
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
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.
14 */
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"
20
21olm::Account::Account(
22) : num_fallback_keys(0),
23 next_one_time_key_id(0),
24 last_error(OlmErrorCode::OLM_SUCCESS) {
25}
26
27
28olm::OneTimeKey const * olm::Account::lookup_key(
29 _olm_curve25519_public_key const & public_key
30) {
31 for (olm::OneTimeKey const & key : one_time_keys) {
32 if (olm::array_equal(array_a: key.key.public_key.public_key, array_b: public_key.public_key)) {
33 return &key;
34 }
35 }
36 if (num_fallback_keys >= 1
37 && olm::array_equal(
38 array_a: current_fallback_key.key.public_key.public_key, array_b: public_key.public_key
39 )
40 ) {
41 return &current_fallback_key;
42 }
43 if (num_fallback_keys >= 2
44 && olm::array_equal(
45 array_a: prev_fallback_key.key.public_key.public_key, array_b: public_key.public_key
46 )
47 ) {
48 return &prev_fallback_key;
49 }
50 return 0;
51}
52
53std::size_t olm::Account::remove_key(
54 _olm_curve25519_public_key const & public_key
55) {
56 OneTimeKey * i;
57 for (i = one_time_keys.begin(); i != one_time_keys.end(); ++i) {
58 if (olm::array_equal(array_a: i->key.public_key.public_key, array_b: public_key.public_key)) {
59 std::uint32_t id = i->id;
60 one_time_keys.erase(pos: i);
61 return id;
62 }
63 }
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
67 && olm::array_equal(
68 array_a: current_fallback_key.key.public_key.public_key, array_b: public_key.public_key
69 )
70 ) {
71 return current_fallback_key.id;
72 }
73 if (num_fallback_keys >= 2
74 && olm::array_equal(
75 array_a: prev_fallback_key.key.public_key.public_key, array_b: public_key.public_key
76 )
77 ) {
78 return prev_fallback_key.id;
79 }
80 return std::size_t(-1);
81}
82
83std::size_t olm::Account::new_account_random_length() const {
84 return ED25519_RANDOM_LENGTH + CURVE25519_RANDOM_LENGTH;
85}
86
87std::size_t olm::Account::new_account(
88 uint8_t const * random, std::size_t random_length
89) {
90 if (random_length < new_account_random_length()) {
91 last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
92 return std::size_t(-1);
93 }
94
95 _olm_crypto_ed25519_generate_key(random_bytes: random, output: &identity_keys.ed25519_key);
96 random += ED25519_RANDOM_LENGTH;
97 _olm_crypto_curve25519_generate_key(random_32_bytes: random, output: &identity_keys.curve25519_key);
98
99 return 0;
100}
101
102namespace {
103
104uint8_t KEY_JSON_ED25519[] = "\"ed25519\":";
105uint8_t KEY_JSON_CURVE25519[] = "\"curve25519\":";
106
107template<typename T>
108static std::uint8_t * write_string(
109 std::uint8_t * pos,
110 T const & value
111) {
112 std::memcpy(dest: pos, src: value, n: sizeof(T) - 1);
113 return pos + (sizeof(T) - 1);
114}
115
116}
117
118
119std::size_t olm::Account::get_identity_json_length() const {
120 std::size_t length = 0;
121 length += 1; /* { */
122 length += sizeof(KEY_JSON_CURVE25519) - 1;
123 length += 1; /* " */
124 length += olm::encode_base64_length(
125 input_length: sizeof(identity_keys.curve25519_key.public_key)
126 );
127 length += 2; /* ", */
128 length += sizeof(KEY_JSON_ED25519) - 1;
129 length += 1; /* " */
130 length += olm::encode_base64_length(
131 input_length: sizeof(identity_keys.ed25519_key.public_key)
132 );
133 length += 2; /* "} */
134 return length;
135}
136
137
138std::size_t olm::Account::get_identity_json(
139 std::uint8_t * identity_json, std::size_t identity_json_length
140) {
141 std::uint8_t * pos = identity_json;
142 size_t expected_length = get_identity_json_length();
143
144 if (identity_json_length < expected_length) {
145 last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
146 return std::size_t(-1);
147 }
148
149 *(pos++) = '{';
150 pos = write_string(pos, value: KEY_JSON_CURVE25519);
151 *(pos++) = '\"';
152 pos = olm::encode_base64(
153 input: identity_keys.curve25519_key.public_key.public_key,
154 input_length: sizeof(identity_keys.curve25519_key.public_key.public_key),
155 output: pos
156 );
157 *(pos++) = '\"'; *(pos++) = ',';
158 pos = write_string(pos, value: KEY_JSON_ED25519);
159 *(pos++) = '\"';
160 pos = olm::encode_base64(
161 input: identity_keys.ed25519_key.public_key.public_key,
162 input_length: sizeof(identity_keys.ed25519_key.public_key.public_key),
163 output: pos
164 );
165 *(pos++) = '\"'; *(pos++) = '}';
166 return pos - identity_json;
167}
168
169
170std::size_t olm::Account::signature_length(
171) const {
172 return ED25519_SIGNATURE_LENGTH;
173}
174
175
176std::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
179) {
180 if (signature_length < this->signature_length()) {
181 last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
182 return std::size_t(-1);
183 }
184 _olm_crypto_ed25519_sign(
185 our_key: &identity_keys.ed25519_key, message, message_length, output: signature
186 );
187 return this->signature_length();
188}
189
190
191std::size_t olm::Account::get_one_time_keys_json_length(
192) const {
193 std::size_t length = 0;
194 bool is_empty = true;
195 for (auto const & key : one_time_keys) {
196 if (key.published) {
197 continue;
198 }
199 is_empty = false;
200 length += 2; /* {" */
201 length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
202 length += 3; /* ":" */
203 length += olm::encode_base64_length(input_length: sizeof(key.key.public_key));
204 length += 1; /* " */
205 }
206 if (is_empty) {
207 length += 1; /* { */
208 }
209 length += 3; /* }{} */
210 length += sizeof(KEY_JSON_CURVE25519) - 1;
211 return length;
212}
213
214
215std::size_t olm::Account::get_one_time_keys_json(
216 std::uint8_t * one_time_json, std::size_t one_time_json_length
217) {
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);
222 }
223 *(pos++) = '{';
224 pos = write_string(pos, value: KEY_JSON_CURVE25519);
225 std::uint8_t sep = '{';
226 for (auto const & key : one_time_keys) {
227 if (key.published) {
228 continue;
229 }
230 *(pos++) = sep;
231 *(pos++) = '\"';
232 std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
233 _olm_pickle_uint32(pos: key_id, value: key.id);
234 pos = olm::encode_base64(input: key_id, input_length: sizeof(key_id), output: pos);
235 *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"';
236 pos = olm::encode_base64(
237 input: key.key.public_key.public_key, input_length: sizeof(key.key.public_key.public_key), output: pos
238 );
239 *(pos++) = '\"';
240 sep = ',';
241 }
242 if (sep != ',') {
243 /* The list was empty */
244 *(pos++) = sep;
245 }
246 *(pos++) = '}';
247 *(pos++) = '}';
248 return pos - one_time_json;
249}
250
251
252std::size_t olm::Account::mark_keys_as_published(
253) {
254 std::size_t count = 0;
255 for (auto & key : one_time_keys) {
256 if (!key.published) {
257 key.published = true;
258 count++;
259 }
260 }
261 current_fallback_key.published = true;
262 return count;
263}
264
265
266std::size_t olm::Account::max_number_of_one_time_keys(
267) const {
268 return olm::MAX_ONE_TIME_KEYS;
269}
270
271std::size_t olm::Account::generate_one_time_keys_random_length(
272 std::size_t number_of_keys
273) const {
274 return CURVE25519_RANDOM_LENGTH * number_of_keys;
275}
276
277std::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
280) {
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);
284 }
285 for (unsigned i = 0; i < number_of_keys; ++i) {
286 OneTimeKey & key = *one_time_keys.insert(pos: one_time_keys.begin());
287 key.id = ++next_one_time_key_id;
288 key.published = false;
289 _olm_crypto_curve25519_generate_key(random_32_bytes: random, output: &key.key);
290 random += CURVE25519_RANDOM_LENGTH;
291 }
292 return number_of_keys;
293}
294
295std::size_t olm::Account::generate_fallback_key_random_length() const {
296 return CURVE25519_RANDOM_LENGTH;
297}
298
299std::size_t olm::Account::generate_fallback_key(
300 std::uint8_t const * random, std::size_t random_length
301) {
302 if (random_length < generate_fallback_key_random_length()) {
303 last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
304 return std::size_t(-1);
305 }
306 if (num_fallback_keys < 2) {
307 num_fallback_keys++;
308 }
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_32_bytes: random, output: &current_fallback_key.key);
313 return 1;
314}
315
316
317std::size_t olm::Account::get_fallback_key_json_length(
318) const {
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;
322 length += 1; /* " */
323 length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
324 length += 3; /* ":" */
325 length += olm::encode_base64_length(input_length: sizeof(key.key.public_key));
326 length += 1; /* " */
327 }
328 return length;
329}
330
331std::size_t olm::Account::get_fallback_key_json(
332 std::uint8_t * fallback_json, std::size_t fallback_json_length
333) {
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);
338 }
339 *(pos++) = '{';
340 pos = write_string(pos, value: KEY_JSON_CURVE25519);
341 *(pos++) = '{';
342 OneTimeKey & key = current_fallback_key;
343 if (num_fallback_keys >= 1) {
344 *(pos++) = '\"';
345 std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
346 _olm_pickle_uint32(pos: key_id, value: key.id);
347 pos = olm::encode_base64(input: key_id, input_length: sizeof(key_id), output: pos);
348 *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"';
349 pos = olm::encode_base64(
350 input: key.key.public_key.public_key, input_length: sizeof(key.key.public_key.public_key), output: pos
351 );
352 *(pos++) = '\"';
353 }
354 *(pos++) = '}';
355 *(pos++) = '}';
356 return pos - fallback_json;
357}
358
359std::size_t olm::Account::get_unpublished_fallback_key_json_length(
360) const {
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) {
364 length += 1; /* " */
365 length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
366 length += 3; /* ":" */
367 length += olm::encode_base64_length(input_length: sizeof(key.key.public_key));
368 length += 1; /* " */
369 }
370 return length;
371}
372
373std::size_t olm::Account::get_unpublished_fallback_key_json(
374 std::uint8_t * fallback_json, std::size_t fallback_json_length
375) {
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);
380 }
381 *(pos++) = '{';
382 pos = write_string(pos, value: KEY_JSON_CURVE25519);
383 *(pos++) = '{';
384 OneTimeKey & key = current_fallback_key;
385 if (num_fallback_keys >= 1 && !key.published) {
386 *(pos++) = '\"';
387 std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
388 _olm_pickle_uint32(pos: key_id, value: key.id);
389 pos = olm::encode_base64(input: key_id, input_length: sizeof(key_id), output: pos);
390 *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"';
391 pos = olm::encode_base64(
392 input: key.key.public_key.public_key, input_length: sizeof(key.key.public_key.public_key), output: pos
393 );
394 *(pos++) = '\"';
395 }
396 *(pos++) = '}';
397 *(pos++) = '}';
398 return pos - fallback_json;
399}
400
401void olm::Account::forget_old_fallback_key(
402) {
403 if (num_fallback_keys >= 2) {
404 num_fallback_keys = 1;
405 olm::unset(buffer: &prev_fallback_key, buffer_length: sizeof(prev_fallback_key));
406 }
407}
408
409namespace olm {
410
411static std::size_t pickle_length(
412 olm::IdentityKeys const & value
413) {
414 size_t length = 0;
415 length += _olm_pickle_ed25519_key_pair_length(value: &value.ed25519_key);
416 length += olm::pickle_length(value: value.curve25519_key);
417 return length;
418}
419
420
421static std::uint8_t * pickle(
422 std::uint8_t * pos,
423 olm::IdentityKeys const & value
424) {
425 pos = _olm_pickle_ed25519_key_pair(pos, value: &value.ed25519_key);
426 pos = olm::pickle(pos, value: value.curve25519_key);
427 return pos;
428}
429
430
431static std::uint8_t const * unpickle(
432 std::uint8_t const * pos, std::uint8_t const * end,
433 olm::IdentityKeys & value
434) {
435 pos = _olm_unpickle_ed25519_key_pair(pos, end, value: &value.ed25519_key); UNPICKLE_OK(pos);
436 pos = olm::unpickle(pos, end, value&: value.curve25519_key); UNPICKLE_OK(pos);
437 return pos;
438}
439
440
441static std::size_t pickle_length(
442 olm::OneTimeKey const & value
443) {
444 std::size_t length = 0;
445 length += olm::pickle_length(value: value.id);
446 length += olm::pickle_length(value: value.published);
447 length += olm::pickle_length(value: value.key);
448 return length;
449}
450
451
452static std::uint8_t * pickle(
453 std::uint8_t * pos,
454 olm::OneTimeKey const & value
455) {
456 pos = olm::pickle(pos, value: value.id);
457 pos = olm::pickle(pos, value: value.published);
458 pos = olm::pickle(pos, value: value.key);
459 return pos;
460}
461
462
463static std::uint8_t const * unpickle(
464 std::uint8_t const * pos, std::uint8_t const * end,
465 olm::OneTimeKey & value
466) {
467 pos = olm::unpickle(pos, end, value&: value.id); UNPICKLE_OK(pos);
468 pos = olm::unpickle(pos, end, value&: value.published); UNPICKLE_OK(pos);
469 pos = olm::unpickle(pos, end, value&: value.key); UNPICKLE_OK(pos);
470 return pos;
471}
472
473} // namespace olm
474
475namespace {
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.
480static const std::uint32_t ACCOUNT_PICKLE_VERSION = 4;
481}
482
483
484std::size_t olm::pickle_length(
485 olm::Account const & value
486) {
487 std::size_t length = 0;
488 length += olm::pickle_length(value: ACCOUNT_PICKLE_VERSION);
489 length += olm::pickle_length(value: value.identity_keys);
490 length += olm::pickle_length(list: value.one_time_keys);
491 length += olm::pickle_length(value: value.num_fallback_keys);
492 if (value.num_fallback_keys >= 1) {
493 length += olm::pickle_length(value: value.current_fallback_key);
494 if (value.num_fallback_keys >= 2) {
495 length += olm::pickle_length(value: value.prev_fallback_key);
496 }
497 }
498 length += olm::pickle_length(value: value.next_one_time_key_id);
499 return length;
500}
501
502
503std::uint8_t * olm::pickle(
504 std::uint8_t * pos,
505 olm::Account const & value
506) {
507 pos = olm::pickle(pos, value: ACCOUNT_PICKLE_VERSION);
508 pos = olm::pickle(pos, value: value.identity_keys);
509 pos = olm::pickle(pos, list: value.one_time_keys);
510 pos = olm::pickle(pos, value: value.num_fallback_keys);
511 if (value.num_fallback_keys >= 1) {
512 pos = olm::pickle(pos, value: value.current_fallback_key);
513 if (value.num_fallback_keys >= 2) {
514 pos = olm::pickle(pos, value: value.prev_fallback_key);
515 }
516 }
517 pos = olm::pickle(pos, value: value.next_one_time_key_id);
518 return pos;
519}
520
521
522std::uint8_t const * olm::unpickle(
523 std::uint8_t const * pos, std::uint8_t const * end,
524 olm::Account & value
525) {
526 uint32_t pickle_version;
527
528 pos = olm::unpickle(pos, end, value&: pickle_version); UNPICKLE_OK(pos);
529
530 switch (pickle_version) {
531 case ACCOUNT_PICKLE_VERSION:
532 case 3:
533 case 2:
534 break;
535 case 1:
536 value.last_error = OlmErrorCode::OLM_BAD_LEGACY_ACCOUNT_PICKLE;
537 return nullptr;
538 default:
539 value.last_error = OlmErrorCode::OLM_UNKNOWN_PICKLE_VERSION;
540 return nullptr;
541 }
542
543 pos = olm::unpickle(pos, end, value&: value.identity_keys); UNPICKLE_OK(pos);
544 pos = olm::unpickle(pos, end, list&: value.one_time_keys); UNPICKLE_OK(pos);
545
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&: value.current_fallback_key); UNPICKLE_OK(pos);
553 pos = olm::unpickle(pos, end, value&: 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;
557 } else {
558 value.num_fallback_keys = 1;
559 }
560 } else {
561 value.num_fallback_keys = 0;
562 }
563 } else {
564 pos = olm::unpickle(pos, end, value&: value.num_fallback_keys); UNPICKLE_OK(pos);
565 if (value.num_fallback_keys >= 1) {
566 pos = olm::unpickle(pos, end, value&: value.current_fallback_key); UNPICKLE_OK(pos);
567 if (value.num_fallback_keys >= 2) {
568 pos = olm::unpickle(pos, end, value&: value.prev_fallback_key); UNPICKLE_OK(pos);
569 if (value.num_fallback_keys >= 3) {
570 value.last_error = OlmErrorCode::OLM_CORRUPTED_PICKLE;
571 return nullptr;
572 }
573 }
574 }
575 }
576
577 pos = olm::unpickle(pos, end, value&: value.next_one_time_key_id); UNPICKLE_OK(pos);
578
579 return pos;
580}
581