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 
Account()21 olm::Account::Account(
22 ) : next_one_time_key_id(0),
23     last_error(OlmErrorCode::OLM_SUCCESS) {
24     // since we don't need to keep track of whether the fallback keys are
25     // published, use the published flag as in indication for whether the keys
26     // were generated
27     current_fallback_key.published = false;
28     prev_fallback_key.published = false;
29 }
30 
31 
lookup_key(_olm_curve25519_public_key const & public_key)32 olm::OneTimeKey const * olm::Account::lookup_key(
33     _olm_curve25519_public_key const & public_key
34 ) {
35     for (olm::OneTimeKey const & key : one_time_keys) {
36         if (olm::array_equal(key.key.public_key.public_key, public_key.public_key)) {
37             return &key;
38         }
39     }
40     if (current_fallback_key.published
41             && olm::array_equal(
42                 current_fallback_key.key.public_key.public_key, public_key.public_key
43             )
44     ) {
45         return &current_fallback_key;
46     }
47     if (prev_fallback_key.published
48             && olm::array_equal(
49                 prev_fallback_key.key.public_key.public_key, public_key.public_key
50             )
51     ) {
52         return &prev_fallback_key;
53     }
54     return 0;
55 }
56 
remove_key(_olm_curve25519_public_key const & public_key)57 std::size_t olm::Account::remove_key(
58     _olm_curve25519_public_key const & public_key
59 ) {
60     OneTimeKey * i;
61     for (i = one_time_keys.begin(); i != one_time_keys.end(); ++i) {
62         if (olm::array_equal(i->key.public_key.public_key, public_key.public_key)) {
63             std::uint32_t id = i->id;
64             one_time_keys.erase(i);
65             return id;
66         }
67     }
68     // check if the key is a fallback key, to avoid returning an error, but
69     // don't actually remove it
70     if (current_fallback_key.published
71             && olm::array_equal(
72                 current_fallback_key.key.public_key.public_key, public_key.public_key
73             )
74     ) {
75         return current_fallback_key.id;
76     }
77     if (prev_fallback_key.published
78             && olm::array_equal(
79                 prev_fallback_key.key.public_key.public_key, public_key.public_key
80             )
81     ) {
82         return prev_fallback_key.id;
83     }
84     return std::size_t(-1);
85 }
86 
new_account_random_length() const87 std::size_t olm::Account::new_account_random_length() const {
88     return ED25519_RANDOM_LENGTH + CURVE25519_RANDOM_LENGTH;
89 }
90 
new_account(uint8_t const * random,std::size_t random_length)91 std::size_t olm::Account::new_account(
92     uint8_t const * random, std::size_t random_length
93 ) {
94     if (random_length < new_account_random_length()) {
95         last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
96         return std::size_t(-1);
97     }
98 
99     _olm_crypto_ed25519_generate_key(random, &identity_keys.ed25519_key);
100     random += ED25519_RANDOM_LENGTH;
101     _olm_crypto_curve25519_generate_key(random, &identity_keys.curve25519_key);
102 
103     return 0;
104 }
105 
106 namespace {
107 
108 uint8_t KEY_JSON_ED25519[] = "\"ed25519\":";
109 uint8_t KEY_JSON_CURVE25519[] = "\"curve25519\":";
110 
111 template<typename T>
write_string(std::uint8_t * pos,T const & value)112 static std::uint8_t * write_string(
113     std::uint8_t * pos,
114     T const & value
115 ) {
116     std::memcpy(pos, value, sizeof(T) - 1);
117     return pos + (sizeof(T) - 1);
118 }
119 
120 }
121 
122 
get_identity_json_length() const123 std::size_t olm::Account::get_identity_json_length() const {
124     std::size_t length = 0;
125     length += 1; /* { */
126     length += sizeof(KEY_JSON_CURVE25519) - 1;
127     length += 1; /* " */
128     length += olm::encode_base64_length(
129         sizeof(identity_keys.curve25519_key.public_key)
130     );
131     length += 2; /* ", */
132     length += sizeof(KEY_JSON_ED25519) - 1;
133     length += 1; /* " */
134     length += olm::encode_base64_length(
135         sizeof(identity_keys.ed25519_key.public_key)
136     );
137     length += 2; /* "} */
138     return length;
139 }
140 
141 
get_identity_json(std::uint8_t * identity_json,std::size_t identity_json_length)142 std::size_t olm::Account::get_identity_json(
143     std::uint8_t * identity_json, std::size_t identity_json_length
144 ) {
145     std::uint8_t * pos = identity_json;
146     size_t expected_length = get_identity_json_length();
147 
148     if (identity_json_length < expected_length) {
149         last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
150         return std::size_t(-1);
151     }
152 
153     *(pos++) = '{';
154     pos = write_string(pos, KEY_JSON_CURVE25519);
155     *(pos++) = '\"';
156     pos = olm::encode_base64(
157         identity_keys.curve25519_key.public_key.public_key,
158         sizeof(identity_keys.curve25519_key.public_key.public_key),
159         pos
160     );
161     *(pos++) = '\"'; *(pos++) = ',';
162     pos = write_string(pos, KEY_JSON_ED25519);
163     *(pos++) = '\"';
164     pos = olm::encode_base64(
165         identity_keys.ed25519_key.public_key.public_key,
166         sizeof(identity_keys.ed25519_key.public_key.public_key),
167         pos
168     );
169     *(pos++) = '\"'; *(pos++) = '}';
170     return pos - identity_json;
171 }
172 
173 
signature_length() const174 std::size_t olm::Account::signature_length(
175 ) const {
176     return ED25519_SIGNATURE_LENGTH;
177 }
178 
179 
sign(std::uint8_t const * message,std::size_t message_length,std::uint8_t * signature,std::size_t signature_length)180 std::size_t olm::Account::sign(
181     std::uint8_t const * message, std::size_t message_length,
182     std::uint8_t * signature, std::size_t signature_length
183 ) {
184     if (signature_length < this->signature_length()) {
185         last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
186         return std::size_t(-1);
187     }
188     _olm_crypto_ed25519_sign(
189         &identity_keys.ed25519_key, message, message_length, signature
190     );
191     return this->signature_length();
192 }
193 
194 
get_one_time_keys_json_length() const195 std::size_t olm::Account::get_one_time_keys_json_length(
196 ) const {
197     std::size_t length = 0;
198     bool is_empty = true;
199     for (auto const & key : one_time_keys) {
200         if (key.published) {
201             continue;
202         }
203         is_empty = false;
204         length += 2; /* {" */
205         length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
206         length += 3; /* ":" */
207         length += olm::encode_base64_length(sizeof(key.key.public_key));
208         length += 1; /* " */
209     }
210     if (is_empty) {
211         length += 1; /* { */
212     }
213     length += 3; /* }{} */
214     length += sizeof(KEY_JSON_CURVE25519) - 1;
215     return length;
216 }
217 
218 
get_one_time_keys_json(std::uint8_t * one_time_json,std::size_t one_time_json_length)219 std::size_t olm::Account::get_one_time_keys_json(
220     std::uint8_t * one_time_json, std::size_t one_time_json_length
221 ) {
222     std::uint8_t * pos = one_time_json;
223     if (one_time_json_length < get_one_time_keys_json_length()) {
224         last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
225         return std::size_t(-1);
226     }
227     *(pos++) = '{';
228     pos = write_string(pos, KEY_JSON_CURVE25519);
229     std::uint8_t sep = '{';
230     for (auto const & key : one_time_keys) {
231         if (key.published) {
232             continue;
233         }
234         *(pos++) = sep;
235         *(pos++) = '\"';
236         std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
237         _olm_pickle_uint32(key_id, key.id);
238         pos = olm::encode_base64(key_id, sizeof(key_id), pos);
239         *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"';
240         pos = olm::encode_base64(
241             key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos
242         );
243         *(pos++) = '\"';
244         sep = ',';
245     }
246     if (sep != ',') {
247         /* The list was empty */
248         *(pos++) = sep;
249     }
250     *(pos++) = '}';
251     *(pos++) = '}';
252     return pos - one_time_json;
253 }
254 
255 
mark_keys_as_published()256 std::size_t olm::Account::mark_keys_as_published(
257 ) {
258     std::size_t count = 0;
259     for (auto & key : one_time_keys) {
260         if (!key.published) {
261             key.published = true;
262             count++;
263         }
264     }
265     return count;
266 }
267 
268 
max_number_of_one_time_keys() const269 std::size_t olm::Account::max_number_of_one_time_keys(
270 ) const {
271     return olm::MAX_ONE_TIME_KEYS;
272 }
273 
generate_one_time_keys_random_length(std::size_t number_of_keys) const274 std::size_t olm::Account::generate_one_time_keys_random_length(
275     std::size_t number_of_keys
276 ) const {
277     return CURVE25519_RANDOM_LENGTH * number_of_keys;
278 }
279 
generate_one_time_keys(std::size_t number_of_keys,std::uint8_t const * random,std::size_t random_length)280 std::size_t olm::Account::generate_one_time_keys(
281     std::size_t number_of_keys,
282     std::uint8_t const * random, std::size_t random_length
283 ) {
284     if (random_length < generate_one_time_keys_random_length(number_of_keys)) {
285         last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
286         return std::size_t(-1);
287     }
288     for (unsigned i = 0; i < number_of_keys; ++i) {
289         OneTimeKey & key = *one_time_keys.insert(one_time_keys.begin());
290         key.id = ++next_one_time_key_id;
291         key.published = false;
292         _olm_crypto_curve25519_generate_key(random, &key.key);
293         random += CURVE25519_RANDOM_LENGTH;
294     }
295     return number_of_keys;
296 }
297 
generate_fallback_key_random_length() const298 std::size_t olm::Account::generate_fallback_key_random_length() const {
299     return CURVE25519_RANDOM_LENGTH;
300 }
301 
generate_fallback_key(std::uint8_t const * random,std::size_t random_length)302 std::size_t olm::Account::generate_fallback_key(
303     std::uint8_t const * random, std::size_t random_length
304 ) {
305     if (random_length < generate_fallback_key_random_length()) {
306         last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
307         return std::size_t(-1);
308     }
309     prev_fallback_key = current_fallback_key;
310     current_fallback_key.id = ++next_one_time_key_id;
311     current_fallback_key.published = true;
312     _olm_crypto_curve25519_generate_key(random, &current_fallback_key.key);
313     return 1;
314 }
315 
316 
get_fallback_key_json_length() const317 std::size_t olm::Account::get_fallback_key_json_length(
318 ) const {
319     std::size_t length = 4 + sizeof(KEY_JSON_CURVE25519) - 1; /* {"curve25519":{}} */
320     const OneTimeKey & key = current_fallback_key;
321     if (key.published) {
322         length += 1; /* " */
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));
326         length += 1; /* " */
327     }
328     return length;
329 }
330 
get_fallback_key_json(std::uint8_t * fallback_json,std::size_t fallback_json_length)331 std::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, KEY_JSON_CURVE25519);
341     *(pos++) = '{';
342     OneTimeKey & key = current_fallback_key;
343     if (key.published) {
344         *(pos++) = '\"';
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
351         );
352         *(pos++) = '\"';
353     }
354     *(pos++) = '}';
355     *(pos++) = '}';
356     return pos - fallback_json;
357 }
358 
359 namespace olm {
360 
pickle_length(olm::IdentityKeys const & value)361 static std::size_t pickle_length(
362     olm::IdentityKeys const & value
363 ) {
364     size_t length = 0;
365     length += _olm_pickle_ed25519_key_pair_length(&value.ed25519_key);
366     length += olm::pickle_length(value.curve25519_key);
367     return length;
368 }
369 
370 
pickle(std::uint8_t * pos,olm::IdentityKeys const & value)371 static std::uint8_t * pickle(
372     std::uint8_t * pos,
373     olm::IdentityKeys const & value
374 ) {
375     pos = _olm_pickle_ed25519_key_pair(pos, &value.ed25519_key);
376     pos = olm::pickle(pos, value.curve25519_key);
377     return pos;
378 }
379 
380 
unpickle(std::uint8_t const * pos,std::uint8_t const * end,olm::IdentityKeys & value)381 static std::uint8_t const * unpickle(
382     std::uint8_t const * pos, std::uint8_t const * end,
383     olm::IdentityKeys & value
384 ) {
385     pos = _olm_unpickle_ed25519_key_pair(pos, end, &value.ed25519_key); UNPICKLE_OK(pos);
386     pos = olm::unpickle(pos, end, value.curve25519_key); UNPICKLE_OK(pos);
387     return pos;
388 }
389 
390 
pickle_length(olm::OneTimeKey const & value)391 static std::size_t pickle_length(
392     olm::OneTimeKey const & value
393 ) {
394     std::size_t length = 0;
395     length += olm::pickle_length(value.id);
396     length += olm::pickle_length(value.published);
397     length += olm::pickle_length(value.key);
398     return length;
399 }
400 
401 
pickle(std::uint8_t * pos,olm::OneTimeKey const & value)402 static std::uint8_t * pickle(
403     std::uint8_t * pos,
404     olm::OneTimeKey const & value
405 ) {
406     pos = olm::pickle(pos, value.id);
407     pos = olm::pickle(pos, value.published);
408     pos = olm::pickle(pos, value.key);
409     return pos;
410 }
411 
412 
unpickle(std::uint8_t const * pos,std::uint8_t const * end,olm::OneTimeKey & value)413 static std::uint8_t const * unpickle(
414     std::uint8_t const * pos, std::uint8_t const * end,
415     olm::OneTimeKey & value
416 ) {
417     pos = olm::unpickle(pos, end, value.id); UNPICKLE_OK(pos);
418     pos = olm::unpickle(pos, end, value.published); UNPICKLE_OK(pos);
419     pos = olm::unpickle(pos, end, value.key); UNPICKLE_OK(pos);
420     return pos;
421 }
422 
423 } // namespace olm
424 
425 namespace {
426 // pickle version 1 used only 32 bytes for the ed25519 private key.
427 // Any keys thus used should be considered compromised.
428 // pickle version 2 does not have fallback keys.
429 static const std::uint32_t ACCOUNT_PICKLE_VERSION = 3;
430 }
431 
432 
pickle_length(olm::Account const & value)433 std::size_t olm::pickle_length(
434     olm::Account const & value
435 ) {
436     std::size_t length = 0;
437     length += olm::pickle_length(ACCOUNT_PICKLE_VERSION);
438     length += olm::pickle_length(value.identity_keys);
439     length += olm::pickle_length(value.one_time_keys);
440     length += olm::pickle_length(value.current_fallback_key);
441     length += olm::pickle_length(value.prev_fallback_key);
442     length += olm::pickle_length(value.next_one_time_key_id);
443     return length;
444 }
445 
446 
pickle(std::uint8_t * pos,olm::Account const & value)447 std::uint8_t * olm::pickle(
448     std::uint8_t * pos,
449     olm::Account const & value
450 ) {
451     pos = olm::pickle(pos, ACCOUNT_PICKLE_VERSION);
452     pos = olm::pickle(pos, value.identity_keys);
453     pos = olm::pickle(pos, value.one_time_keys);
454     pos = olm::pickle(pos, value.current_fallback_key);
455     pos = olm::pickle(pos, value.prev_fallback_key);
456     pos = olm::pickle(pos, value.next_one_time_key_id);
457     return pos;
458 }
459 
460 
unpickle(std::uint8_t const * pos,std::uint8_t const * end,olm::Account & value)461 std::uint8_t const * olm::unpickle(
462     std::uint8_t const * pos, std::uint8_t const * end,
463     olm::Account & value
464 ) {
465     uint32_t pickle_version;
466 
467     pos = olm::unpickle(pos, end, pickle_version); UNPICKLE_OK(pos);
468 
469     switch (pickle_version) {
470         case ACCOUNT_PICKLE_VERSION:
471         case 2:
472             break;
473         case 1:
474             value.last_error = OlmErrorCode::OLM_BAD_LEGACY_ACCOUNT_PICKLE;
475             return nullptr;
476         default:
477             value.last_error = OlmErrorCode::OLM_UNKNOWN_PICKLE_VERSION;
478             return nullptr;
479     }
480 
481     pos = olm::unpickle(pos, end, value.identity_keys); UNPICKLE_OK(pos);
482     pos = olm::unpickle(pos, end, value.one_time_keys); UNPICKLE_OK(pos);
483 
484     if (pickle_version == 2) {
485         // version 2 did not have fallback keys
486         value.current_fallback_key.published = false;
487         value.prev_fallback_key.published = false;
488     } else {
489         pos = olm::unpickle(pos, end, value.current_fallback_key); UNPICKLE_OK(pos);
490         pos = olm::unpickle(pos, end, value.prev_fallback_key); UNPICKLE_OK(pos);
491     }
492 
493     pos = olm::unpickle(pos, end, value.next_one_time_key_id); UNPICKLE_OK(pos);
494 
495     return pos;
496 }
497