account.cpp 14.5 KB
Newer Older
1
/* Copyright 2015, 2016 OpenMarket Ltd
Mark Haines's avatar
Mark Haines committed
2
3
4
5
6
7
8
9
10
11
12
13
14
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * 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"
20

21
22
olm::Account::Account(
) : next_one_time_key_id(0),
23
    last_error(OlmErrorCode::OLM_SUCCESS) {
Hubert Chathi's avatar
Hubert Chathi committed
24
25
26
27
28
    // since we don't need to keep track of whether the fallback keys are
    // published, use the published flag as in indication for whether the keys
    // were generated
    current_fallback_key.published = false;
    prev_fallback_key.published = false;
29
30
}

31

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
38
            return &key;
        }
39
    }
Hubert Chathi's avatar
Hubert Chathi committed
40
    if (current_fallback_key.published
Hubert Chathi's avatar
Hubert Chathi committed
41
42
43
44
            && olm::array_equal(
                current_fallback_key.key.public_key.public_key, public_key.public_key
            )
    ) {
Hubert Chathi's avatar
Hubert Chathi committed
45
46
47
        return &current_fallback_key;
    }
    if (prev_fallback_key.published
Hubert Chathi's avatar
Hubert Chathi committed
48
49
50
51
            && olm::array_equal(
                prev_fallback_key.key.public_key.public_key, public_key.public_key
            )
    ) {
Hubert Chathi's avatar
Hubert Chathi committed
52
53
        return &prev_fallback_key;
    }
54
55
56
    return 0;
}

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
65
66
67
            one_time_keys.erase(i);
            return id;
        }
    }
Hubert Chathi's avatar
Hubert Chathi committed
68
69
70
    // check if the key is a fallback key, to avoid returning an error, but
    // don't actually remove it
    if (current_fallback_key.published
Hubert Chathi's avatar
Hubert Chathi committed
71
72
73
74
            && olm::array_equal(
                current_fallback_key.key.public_key.public_key, public_key.public_key
            )
    ) {
Hubert Chathi's avatar
Hubert Chathi committed
75
76
77
        return current_fallback_key.id;
    }
    if (prev_fallback_key.published
Hubert Chathi's avatar
Hubert Chathi committed
78
79
80
81
            && olm::array_equal(
                prev_fallback_key.key.public_key.public_key, public_key.public_key
            )
    ) {
Hubert Chathi's avatar
Hubert Chathi committed
82
83
        return prev_fallback_key.id;
    }
84
85
    return std::size_t(-1);
}
Mark Haines's avatar
Mark Haines committed
86

87
std::size_t olm::Account::new_account_random_length() const {
88
    return ED25519_RANDOM_LENGTH + CURVE25519_RANDOM_LENGTH;
Mark Haines's avatar
Mark Haines committed
89
90
}

91
std::size_t olm::Account::new_account(
Mark Haines's avatar
Mark Haines committed
92
93
94
    uint8_t const * random, std::size_t random_length
) {
    if (random_length < new_account_random_length()) {
95
        last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
96
        return std::size_t(-1);
Mark Haines's avatar
Mark Haines committed
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);
Mark Haines's avatar
Mark Haines committed
102
103
104
105

    return 0;
}

106
107
namespace {

108
109
uint8_t KEY_JSON_ED25519[] = "\"ed25519\":";
uint8_t KEY_JSON_CURVE25519[] = "\"curve25519\":";
110
111

template<typename T>
112
static std::uint8_t * write_string(
113
114
115
116
117
118
119
120
121
122
    std::uint8_t * pos,
    T const & value
) {
    std::memcpy(pos, value, sizeof(T) - 1);
    return pos + (sizeof(T) - 1);
}

}


123
std::size_t olm::Account::get_identity_json_length() const {
124
    std::size_t length = 0;
125
126
127
    length += 1; /* { */
    length += sizeof(KEY_JSON_CURVE25519) - 1;
    length += 1; /* " */
128
129
130
    length += olm::encode_base64_length(
        sizeof(identity_keys.curve25519_key.public_key)
    );
131
132
133
    length += 2; /* ", */
    length += sizeof(KEY_JSON_ED25519) - 1;
    length += 1; /* " */
134
135
136
    length += olm::encode_base64_length(
        sizeof(identity_keys.ed25519_key.public_key)
    );
137
    length += 2; /* "} */
138
139
140
141
142
143
144
145
    return length;
}


std::size_t olm::Account::get_identity_json(
    std::uint8_t * identity_json, std::size_t identity_json_length
) {
    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
151
152
        return std::size_t(-1);
    }

153
154
155
156
    *(pos++) = '{';
    pos = write_string(pos, KEY_JSON_CURVE25519);
    *(pos++) = '\"';
    pos = olm::encode_base64(
157
158
        identity_keys.curve25519_key.public_key.public_key,
        sizeof(identity_keys.curve25519_key.public_key.public_key),
159
        pos
160
    );
161
162
163
164
    *(pos++) = '\"'; *(pos++) = ',';
    pos = write_string(pos, KEY_JSON_ED25519);
    *(pos++) = '\"';
    pos = olm::encode_base64(
165
166
        identity_keys.ed25519_key.public_key.public_key,
        sizeof(identity_keys.ed25519_key.public_key.public_key),
167
168
169
        pos
    );
    *(pos++) = '\"'; *(pos++) = '}';
170
171
    return pos - identity_json;
}
Mark Haines's avatar
Mark Haines committed
172

173
174

std::size_t olm::Account::signature_length(
175
) const {
176
    return ED25519_SIGNATURE_LENGTH;
177
178
}

179
180
181
182
183
184

std::size_t olm::Account::sign(
    std::uint8_t const * message, std::size_t message_length,
    std::uint8_t * signature, std::size_t signature_length
) {
    if (signature_length < this->signature_length()) {
185
        last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
186
187
        return std::size_t(-1);
    }
188
189
    _olm_crypto_ed25519_sign(
        &identity_keys.ed25519_key, message, message_length, signature
190
191
192
193
194
    );
    return this->signature_length();
}


195
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
201
202
        if (key.published) {
            continue;
        }
203
        is_empty = false;
204
        length += 2; /* {" */
Alexey Rusakov's avatar
Alexey Rusakov committed
205
        length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
206
207
208
209
        length += 3; /* ":" */
        length += olm::encode_base64_length(sizeof(key.key.public_key));
        length += 1; /* " */
    }
210
211
    if (is_empty) {
        length += 1; /* { */
212
    }
213
214
215
    length += 3; /* }{} */
    length += sizeof(KEY_JSON_CURVE25519) - 1;
    return length;
216
217
218
219
220
221
222
223
}


std::size_t olm::Account::get_one_time_keys_json(
    std::uint8_t * one_time_json, std::size_t one_time_json_length
) {
    std::uint8_t * pos = one_time_json;
    if (one_time_json_length < get_one_time_keys_json_length()) {
224
        last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
225
226
        return std::size_t(-1);
    }
227
228
    *(pos++) = '{';
    pos = write_string(pos, KEY_JSON_CURVE25519);
229
230
    std::uint8_t sep = '{';
    for (auto const & key : one_time_keys) {
231
232
233
        if (key.published) {
            continue;
        }
234
235
        *(pos++) = sep;
        *(pos++) = '\"';
Alexey Rusakov's avatar
Alexey Rusakov committed
236
237
        std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
        _olm_pickle_uint32(key_id, key.id);
238
239
240
        pos = olm::encode_base64(key_id, sizeof(key_id), pos);
        *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"';
        pos = olm::encode_base64(
241
            key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos
242
243
244
245
246
        );
        *(pos++) = '\"';
        sep = ',';
    }
    if (sep != ',') {
247
        /* The list was empty */
248
249
250
        *(pos++) = sep;
    }
    *(pos++) = '}';
251
    *(pos++) = '}';
252
253
254
255
    return pos - one_time_json;
}


256
257
258
259
260
261
262
263
264
265
266
267
268
269
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(
270
) const {
271
272
273
274
275
    return olm::MAX_ONE_TIME_KEYS;
}

std::size_t olm::Account::generate_one_time_keys_random_length(
    std::size_t number_of_keys
276
) const {
277
    return CURVE25519_RANDOM_LENGTH * number_of_keys;
278
279
280
281
282
283
284
}

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)) {
285
        last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
286
287
288
289
290
291
        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;
292
        _olm_crypto_curve25519_generate_key(random, &key.key);
293
        random += CURVE25519_RANDOM_LENGTH;
294
295
296
297
    }
    return number_of_keys;
}

298
std::size_t olm::Account::generate_fallback_key_random_length() const {
Hubert Chathi's avatar
Hubert Chathi committed
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
    return CURVE25519_RANDOM_LENGTH;
}

std::size_t olm::Account::generate_fallback_key(
    std::uint8_t const * random, std::size_t random_length
) {
    if (random_length < generate_fallback_key_random_length()) {
        last_error = OlmErrorCode::OLM_NOT_ENOUGH_RANDOM;
        return std::size_t(-1);
    }
    prev_fallback_key = current_fallback_key;
    current_fallback_key.id = ++next_one_time_key_id;
    current_fallback_key.published = true;
    _olm_crypto_curve25519_generate_key(random, &current_fallback_key.key);
    return 1;
}


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;
Hubert Chathi's avatar
Hubert Chathi committed
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
    if (key.published) {
        length += 1; /* " */
        length += olm::encode_base64_length(_olm_pickle_uint32_length(key.id));
        length += 3; /* ":" */
        length += olm::encode_base64_length(sizeof(key.key.public_key));
        length += 1; /* " */
    }
    return length;
}

std::size_t olm::Account::get_fallback_key_json(
    std::uint8_t * fallback_json, std::size_t fallback_json_length
) {
    std::uint8_t * pos = fallback_json;
    if (fallback_json_length < get_fallback_key_json_length()) {
        last_error = OlmErrorCode::OLM_OUTPUT_BUFFER_TOO_SMALL;
        return std::size_t(-1);
    }
    *(pos++) = '{';
    pos = write_string(pos, KEY_JSON_CURVE25519);
    *(pos++) = '{';
    OneTimeKey & key = current_fallback_key;
    if (key.published) {
        *(pos++) = '\"';
        std::uint8_t key_id[_olm_pickle_uint32_length(key.id)];
        _olm_pickle_uint32(key_id, key.id);
        pos = olm::encode_base64(key_id, sizeof(key_id), pos);
        *(pos++) = '\"'; *(pos++) = ':'; *(pos++) = '\"';
        pos = olm::encode_base64(
            key.key.public_key.public_key, sizeof(key.key.public_key.public_key), pos
        );
        *(pos++) = '\"';
    }
    *(pos++) = '}';
    *(pos++) = '}';
    return pos - fallback_json;
}

359
namespace olm {
360

361
362
363
364
static std::size_t pickle_length(
    olm::IdentityKeys const & value
) {
    size_t length = 0;
365
    length += _olm_pickle_ed25519_key_pair_length(&value.ed25519_key);
366
367
368
369
370
371
372
373
374
    length += olm::pickle_length(value.curve25519_key);
    return length;
}


static std::uint8_t * pickle(
    std::uint8_t * pos,
    olm::IdentityKeys const & value
) {
375
    pos = _olm_pickle_ed25519_key_pair(pos, &value.ed25519_key);
376
377
378
379
380
381
382
383
384
    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
) {
Denis Kasak's avatar
Denis Kasak committed
385
386
    pos = _olm_unpickle_ed25519_key_pair(pos, end, &value.ed25519_key); UNPICKLE_OK(pos);
    pos = olm::unpickle(pos, end, value.curve25519_key); UNPICKLE_OK(pos);
387
388
389
    return pos;
}

390
391

static std::size_t pickle_length(
392
    olm::OneTimeKey const & value
393
) {
394
395
396
397
398
    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;
399
400
401
402
403
}


static std::uint8_t * pickle(
    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
410
411
412
413
414
    return pos;
}


static std::uint8_t const * unpickle(
    std::uint8_t const * pos, std::uint8_t const * end,
415
    olm::OneTimeKey & value
416
) {
Denis Kasak's avatar
Denis Kasak committed
417
418
419
    pos = olm::unpickle(pos, end, value.id); UNPICKLE_OK(pos);
    pos = olm::unpickle(pos, end, value.published); UNPICKLE_OK(pos);
    pos = olm::unpickle(pos, end, value.key); UNPICKLE_OK(pos);
420
421
422
    return pos;
}

423
} // namespace olm
424

425
namespace {
426
427
// pickle version 1 used only 32 bytes for the ed25519 private key.
// Any keys thus used should be considered compromised.
Hubert Chathi's avatar
Hubert Chathi committed
428
429
// pickle version 2 does not have fallback keys.
static const std::uint32_t ACCOUNT_PICKLE_VERSION = 3;
430
431
}

432

433
434
std::size_t olm::pickle_length(
    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);
Hubert Chathi's avatar
Hubert Chathi committed
440
441
    length += olm::pickle_length(value.current_fallback_key);
    length += olm::pickle_length(value.prev_fallback_key);
442
    length += olm::pickle_length(value.next_one_time_key_id);
443
444
445
446
    return length;
}


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);
Hubert Chathi's avatar
Hubert Chathi committed
454
455
    pos = olm::pickle(pos, value.current_fallback_key);
    pos = olm::pickle(pos, value.prev_fallback_key);
456
    pos = olm::pickle(pos, value.next_one_time_key_id);
457
458
459
460
    return pos;
}


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;
Denis Kasak's avatar
Denis Kasak committed
466
467
468

    pos = olm::unpickle(pos, end, pickle_version); UNPICKLE_OK(pos);

469
470
    switch (pickle_version) {
        case ACCOUNT_PICKLE_VERSION:
Hubert Chathi's avatar
Hubert Chathi committed
471
        case 2:
472
473
474
            break;
        case 1:
            value.last_error = OlmErrorCode::OLM_BAD_LEGACY_ACCOUNT_PICKLE;
Denis Kasak's avatar
Denis Kasak committed
475
            return nullptr;
476
477
        default:
            value.last_error = OlmErrorCode::OLM_UNKNOWN_PICKLE_VERSION;
Denis Kasak's avatar
Denis Kasak committed
478
            return nullptr;
479
    }
Denis Kasak's avatar
Denis Kasak committed
480
481
482
483

    pos = olm::unpickle(pos, end, value.identity_keys); UNPICKLE_OK(pos);
    pos = olm::unpickle(pos, end, value.one_time_keys); UNPICKLE_OK(pos);

Hubert Chathi's avatar
Hubert Chathi committed
484
485
486
487
488
    if (pickle_version == 2) {
        // version 2 did not have fallback keys
        value.current_fallback_key.published = false;
        value.prev_fallback_key.published = false;
    } else {
Denis Kasak's avatar
Denis Kasak committed
489
490
        pos = olm::unpickle(pos, end, value.current_fallback_key); UNPICKLE_OK(pos);
        pos = olm::unpickle(pos, end, value.prev_fallback_key); UNPICKLE_OK(pos);
Hubert Chathi's avatar
Hubert Chathi committed
491
    }
Denis Kasak's avatar
Denis Kasak committed
492
493
494

    pos = olm::unpickle(pos, end, value.next_one_time_key_id); UNPICKLE_OK(pos);

495
496
    return pos;
}