Coverage Report

Created: 2025-10-08 19:34

/work/toxencryptsave/toxencryptsave.c
Line
Count
Source (jump to first uncovered line)
1
/* SPDX-License-Identifier: GPL-3.0-or-later
2
 * Copyright © 2016-2025 The TokTok team.
3
 * Copyright © 2013 Tox project.
4
 */
5
6
/**
7
 * Batch encryption functions.
8
 */
9
#include "toxencryptsave.h"
10
11
#include <sodium.h>
12
13
#include <stdlib.h>
14
#include <string.h>
15
16
#include "../toxcore/ccompat.h"
17
#include "../toxcore/crypto_core.h"
18
#include "../toxcore/mem.h"
19
#include "../toxcore/os_memory.h"
20
#include "../toxcore/os_random.h"
21
#include "defines.h"
22
23
static_assert(TOX_PASS_SALT_LENGTH == crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
24
              "TOX_PASS_SALT_LENGTH is assumed to be equal to crypto_pwhash_scryptsalsa208sha256_SALTBYTES");
25
static_assert(TOX_PASS_KEY_LENGTH == CRYPTO_SHARED_KEY_SIZE,
26
              "TOX_PASS_KEY_LENGTH is assumed to be equal to CRYPTO_SHARED_KEY_SIZE");
27
static_assert(TOX_PASS_ENCRYPTION_EXTRA_LENGTH == (crypto_box_MACBYTES + crypto_box_NONCEBYTES +
28
              crypto_pwhash_scryptsalsa208sha256_SALTBYTES + TOX_ENC_SAVE_MAGIC_LENGTH),
29
              "TOX_PASS_ENCRYPTION_EXTRA_LENGTH is assumed to be equal to (crypto_box_MACBYTES + crypto_box_NONCEBYTES + crypto_pwhash_scryptsalsa208sha256_SALTBYTES + TOX_ENC_SAVE_MAGIC_LENGTH)");
30
31
#define SET_ERROR_PARAMETER(param, x) \
32
179
    do {                              \
33
179
        if (param != nullptr) {       \
34
138
            *param = x;               \
35
138
        }                             \
36
179
    } while (0)
37
38
uint32_t tox_pass_salt_length(void)
39
0
{
40
0
    return TOX_PASS_SALT_LENGTH;
41
0
}
42
uint32_t tox_pass_key_length(void)
43
0
{
44
0
    return TOX_PASS_KEY_LENGTH;
45
0
}
46
uint32_t tox_pass_encryption_extra_length(void)
47
0
{
48
0
    return TOX_PASS_ENCRYPTION_EXTRA_LENGTH;
49
0
}
50
51
struct Tox_Pass_Key {
52
    uint8_t salt[TOX_PASS_SALT_LENGTH];
53
    uint8_t key[TOX_PASS_KEY_LENGTH];
54
};
55
56
void tox_pass_key_free(Tox_Pass_Key *key)
57
86
{
58
86
    free(key);
59
86
}
60
61
/* Clients should consider alerting their users that, unlike plain data, if even one bit
62
 * becomes corrupted, the data will be entirely unrecoverable.
63
 * Ditto if they forget their password, there is no way to recover the data.
64
 */
65
66
/**
67
 * Retrieves the salt used to encrypt the given data.
68
 *
69
 * The retrieved salt can then be passed to tox_pass_key_derive_with_salt to
70
 * produce the same key as was previously used. Any data encrypted with this
71
 * module can be used as input.
72
 *
73
 * The cipher text must be at least TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes in
74
 * length.
75
 * The salt must be TOX_PASS_SALT_LENGTH bytes in length.
76
 * If the passed byte arrays are smaller than required, the behaviour is
77
 * undefined.
78
 *
79
 * If the cipher text pointer or the salt is NULL, this function returns false.
80
 *
81
 * Success does not say anything about the validity of the data, only that
82
 * data of the appropriate size was copied.
83
 *
84
 * @return true on success.
85
 */
86
bool tox_get_salt(
87
    const uint8_t ciphertext[TOX_PASS_ENCRYPTION_EXTRA_LENGTH],
88
    uint8_t salt[TOX_PASS_SALT_LENGTH], Tox_Err_Get_Salt *error)
89
1
{
90
1
    if (ciphertext == nullptr || salt == nullptr) {
91
0
        SET_ERROR_PARAMETER(error, TOX_ERR_GET_SALT_NULL);
92
0
        return false;
93
0
    }
94
95
1
    if (memcmp(ciphertext, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) {
96
0
        SET_ERROR_PARAMETER(error, TOX_ERR_GET_SALT_BAD_FORMAT);
97
0
        return false;
98
0
    }
99
100
1
    ciphertext += TOX_ENC_SAVE_MAGIC_LENGTH;
101
1
    memcpy(salt, ciphertext, crypto_pwhash_scryptsalsa208sha256_SALTBYTES);
102
1
    SET_ERROR_PARAMETER(error, TOX_ERR_GET_SALT_OK);
103
1
    return true;
104
1
}
105
106
/**
107
 * Generates a secret symmetric key from the given passphrase.
108
 *
109
 * Be sure to not compromise the key! Only keep it in memory, do not write
110
 * it to disk.
111
 *
112
 * Note that this function is not deterministic; to derive the same key from
113
 * a password, you also must know the random salt that was used. A
114
 * deterministic version of this function is `tox_pass_key_derive_with_salt`.
115
 *
116
 * @param passphrase The user-provided password. Can be empty.
117
 * @param passphrase_len The length of the password.
118
 *
119
 * @return new symmetric key on success, NULL on failure.
120
 */
121
Tox_Pass_Key *tox_pass_key_derive(
122
    const uint8_t passphrase[], size_t passphrase_len,
123
    Tox_Err_Key_Derivation *error)
124
46
{
125
46
    const Random *rng = os_random();
126
127
46
    if (rng == nullptr) {
128
0
        SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_FAILED);
129
0
        return nullptr;
130
0
    }
131
132
46
    uint8_t salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES];
133
46
    random_bytes(rng, salt, sizeof(salt));
134
46
    return tox_pass_key_derive_with_salt(passphrase, passphrase_len, salt, error);
135
46
}
136
137
/**
138
 * Same as above, except use the given salt for deterministic key derivation.
139
 *
140
 * @param passphrase The user-provided password. Can be empty.
141
 * @param passphrase_len The length of the password.
142
 * @param salt An array of at least TOX_PASS_SALT_LENGTH bytes.
143
 *
144
 * @return new symmetric key on success, NULL on failure.
145
 */
146
Tox_Pass_Key *tox_pass_key_derive_with_salt(
147
    const uint8_t passphrase[], size_t passphrase_len,
148
    const uint8_t salt[TOX_PASS_SALT_LENGTH], Tox_Err_Key_Derivation *error)
149
88
{
150
88
    if (salt == nullptr || (passphrase == nullptr && passphrase_len != 0)) {
151
0
        SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_NULL);
152
0
        return nullptr;
153
0
    }
154
155
88
    uint8_t passkey[crypto_hash_sha256_BYTES];
156
88
    crypto_hash_sha256(passkey, passphrase, passphrase_len);
157
158
88
    uint8_t key[CRYPTO_SHARED_KEY_SIZE];
159
160
    // Derive a key from the password
161
    // http://doc.libsodium.org/key_derivation/README.html
162
    // note that, according to the documentation, a generic pwhash interface will be created
163
    // once the pwhash competition (https://password-hashing.net/) is over */
164
88
    if (crypto_pwhash_scryptsalsa208sha256(
165
88
                key, sizeof(key), (char *)passkey, sizeof(passkey), salt,
166
88
                crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE * 2, /* slightly stronger */
167
88
                crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE) != 0) {
168
        /* out of memory most likely */
169
0
        SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_FAILED);
170
0
        return nullptr;
171
0
    }
172
173
88
    crypto_memzero(passkey, crypto_hash_sha256_BYTES); /* wipe plaintext pw */
174
175
88
    Tox_Pass_Key *out_key = (Tox_Pass_Key *)calloc(1, sizeof(Tox_Pass_Key));
176
177
88
    if (out_key == nullptr) {
178
2
        SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_FAILED);
179
2
        return nullptr;
180
2
    }
181
182
86
    memcpy(out_key->salt, salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES);
183
86
    memcpy(out_key->key, key, CRYPTO_SHARED_KEY_SIZE);
184
86
    SET_ERROR_PARAMETER(error, TOX_ERR_KEY_DERIVATION_OK);
185
86
    return out_key;
186
88
}
187
188
/**
189
 * Encrypt a plain text with a key produced by tox_pass_key_derive or
190
 * tox_pass_key_derive_with_salt.
191
 *
192
 * The output array must be at least
193
 * `plaintext_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH` bytes long.
194
 *
195
 * @param plaintext A byte array of length `plaintext_len`.
196
 * @param plaintext_len The length of the plain text array. Bigger than 0.
197
 * @param ciphertext The cipher text array to write the encrypted data to.
198
 *
199
 * @return true on success.
200
 */
201
bool tox_pass_key_encrypt(const Tox_Pass_Key *key, const uint8_t plaintext[], size_t plaintext_len,
202
                          uint8_t ciphertext[], Tox_Err_Encryption *error)
203
45
{
204
45
    const Memory *mem = os_memory();
205
45
    const Random *rng = os_random();
206
207
45
    if (mem == nullptr || rng == nullptr) {
208
0
        SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_FAILED);
209
0
        return false;
210
0
    }
211
212
45
    if (plaintext_len == 0 || plaintext == nullptr || key == nullptr || ciphertext == nullptr) {
213
0
        SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_NULL);
214
0
        return false;
215
0
    }
216
217
    // the output data consists of, in order:
218
    // salt, nonce, mac, enc_data
219
    // where the mac is automatically prepended by the encrypt()
220
    // the salt+nonce is called the prefix
221
    // I'm not sure what else I'm supposed to do with the salt and nonce, since we
222
    // need them to decrypt the data
223
224
    /* first add the magic number */
225
45
    memcpy(ciphertext, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH);
226
45
    ciphertext += TOX_ENC_SAVE_MAGIC_LENGTH;
227
228
    /* then add the rest prefix */
229
45
    memcpy(ciphertext, key->salt, crypto_pwhash_scryptsalsa208sha256_SALTBYTES);
230
45
    ciphertext += crypto_pwhash_scryptsalsa208sha256_SALTBYTES;
231
232
45
    uint8_t nonce[crypto_box_NONCEBYTES];
233
45
    random_nonce(rng, nonce);
234
45
    memcpy(ciphertext, nonce, crypto_box_NONCEBYTES);
235
45
    ciphertext += crypto_box_NONCEBYTES;
236
237
    /* now encrypt */
238
45
    const int32_t encrypted_len = encrypt_data_symmetric(mem, key->key, nonce, plaintext, plaintext_len, ciphertext);
239
45
    if (encrypted_len < 0 || (size_t)encrypted_len != plaintext_len + crypto_box_MACBYTES) {
240
1
        SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_FAILED);
241
1
        return false;
242
1
    }
243
244
44
    SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_OK);
245
44
    return true;
246
45
}
247
248
/**
249
 * Encrypts the given data with the given passphrase.
250
 *
251
 * The output array must be at least
252
 * `plaintext_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH` bytes long. This delegates
253
 * to tox_pass_key_derive and tox_pass_key_encrypt.
254
 *
255
 * @param plaintext A byte array of length `plaintext_len`.
256
 * @param plaintext_len The length of the plain text array. Bigger than 0.
257
 * @param passphrase The user-provided password. Can be empty.
258
 * @param passphrase_len The length of the password.
259
 * @param ciphertext The cipher text array to write the encrypted data to.
260
 *
261
 * @return true on success.
262
 */
263
bool tox_pass_encrypt(const uint8_t plaintext[], size_t plaintext_len, const uint8_t passphrase[], size_t passphrase_len,
264
                      uint8_t ciphertext[/*! plaintext_len + TOX_PASS_ENCRYPTION_EXTRA_LENGTH */], Tox_Err_Encryption *error)
265
44
{
266
44
    Tox_Err_Key_Derivation err;
267
44
    Tox_Pass_Key *key = tox_pass_key_derive(passphrase, passphrase_len, &err);
268
269
44
    if (key == nullptr) {
270
1
        if (err == TOX_ERR_KEY_DERIVATION_NULL) {
271
0
            SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_NULL);
272
1
        } else if (err == TOX_ERR_KEY_DERIVATION_FAILED) {
273
1
            SET_ERROR_PARAMETER(error, TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED);
274
1
        }
275
276
1
        return false;
277
1
    }
278
279
43
    const bool result = tox_pass_key_encrypt(key, plaintext, plaintext_len, ciphertext, error);
280
43
    tox_pass_key_free(key);
281
43
    return result;
282
44
}
283
284
/**
285
 * This is the inverse of tox_pass_key_encrypt, also using only keys produced by
286
 * tox_pass_key_derive or tox_pass_key_derive_with_salt.
287
 *
288
 * @param ciphertext A byte array of length `ciphertext_len`.
289
 * @param ciphertext_len The length of the cipher text array. At least
290
 *   TOX_PASS_ENCRYPTION_EXTRA_LENGTH.
291
 * @param plaintext The plain text array to write the decrypted data to.
292
 *
293
 * @return true on success.
294
 */
295
bool tox_pass_key_decrypt(const Tox_Pass_Key *key, const uint8_t ciphertext[], size_t ciphertext_len,
296
                          uint8_t plaintext[], Tox_Err_Decryption *error)
297
42
{
298
42
    const Memory *mem = os_memory();
299
300
42
    if (mem == nullptr) {
301
0
        SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_FAILED);
302
0
        return false;
303
0
    }
304
305
42
    if (ciphertext_len <= TOX_PASS_ENCRYPTION_EXTRA_LENGTH) {
306
0
        SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_INVALID_LENGTH);
307
0
        return false;
308
0
    }
309
310
42
    if (ciphertext == nullptr || key == nullptr || plaintext == nullptr) {
311
0
        SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_NULL);
312
0
        return false;
313
0
    }
314
315
42
    if (memcmp(ciphertext, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) {
316
0
        SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_BAD_FORMAT);
317
0
        return false;
318
0
    }
319
320
42
    ciphertext += TOX_ENC_SAVE_MAGIC_LENGTH;
321
42
    ciphertext += crypto_pwhash_scryptsalsa208sha256_SALTBYTES; // salt only affects key derivation
322
323
42
    const size_t decrypt_length = ciphertext_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH;
324
325
42
    uint8_t nonce[crypto_box_NONCEBYTES];
326
42
    memcpy(nonce, ciphertext, crypto_box_NONCEBYTES);
327
42
    ciphertext += crypto_box_NONCEBYTES;
328
329
    /* decrypt the ciphertext */
330
42
    const int32_t decrypted_len = decrypt_data_symmetric(mem, key->key, nonce, ciphertext, decrypt_length + crypto_box_MACBYTES, plaintext);
331
42
    if (decrypted_len < 0 || (size_t)decrypted_len != decrypt_length) {
332
1
        SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_FAILED);
333
1
        return false;
334
1
    }
335
336
41
    SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_OK);
337
41
    return true;
338
42
}
339
340
/**
341
 * Decrypts the given data with the given passphrase.
342
 *
343
 * The output array must be at least
344
 * `ciphertext_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH` bytes long. This
345
 * delegates to tox_pass_key_decrypt.
346
 *
347
 * @param ciphertext A byte array of length `ciphertext_len`.
348
 * @param ciphertext_len The length of the cipher text array. At least
349
 *   TOX_PASS_ENCRYPTION_EXTRA_LENGTH.
350
 * @param passphrase The user-provided password. Can be empty.
351
 * @param passphrase_len The length of the password.
352
 * @param plaintext The plain text array to write the decrypted data to.
353
 *
354
 * @return true on success.
355
 */
356
bool tox_pass_decrypt(const uint8_t ciphertext[], size_t ciphertext_len, const uint8_t passphrase[],
357
                      size_t passphrase_len, uint8_t plaintext[/*! ciphertext_len - TOX_PASS_ENCRYPTION_EXTRA_LENGTH */], Tox_Err_Decryption *error)
358
42
{
359
42
    if (ciphertext_len <= TOX_PASS_ENCRYPTION_EXTRA_LENGTH) {
360
0
        SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_INVALID_LENGTH);
361
0
        return false;
362
0
    }
363
364
42
    if (ciphertext == nullptr || passphrase == nullptr || plaintext == nullptr) {
365
1
        SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_NULL);
366
1
        return false;
367
1
    }
368
369
41
    if (memcmp(ciphertext, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) != 0) {
370
0
        SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_BAD_FORMAT);
371
0
        return false;
372
0
    }
373
374
41
    uint8_t salt[crypto_pwhash_scryptsalsa208sha256_SALTBYTES];
375
41
    memcpy(salt, ciphertext + TOX_ENC_SAVE_MAGIC_LENGTH, crypto_pwhash_scryptsalsa208sha256_SALTBYTES);
376
377
    /* derive the key */
378
41
    Tox_Pass_Key *key = tox_pass_key_derive_with_salt(passphrase, passphrase_len, salt, nullptr);
379
380
41
    if (key == nullptr) {
381
        /* out of memory most likely */
382
1
        SET_ERROR_PARAMETER(error, TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED);
383
1
        return false;
384
1
    }
385
386
40
    const bool result = tox_pass_key_decrypt(key, ciphertext, ciphertext_len, plaintext, error);
387
40
    tox_pass_key_free(key);
388
40
    return result;
389
41
}
390
391
/**
392
 * Determines whether or not the given data is encrypted by this module.
393
 *
394
 * It does this check by verifying that the magic number is the one put in
395
 * place by the encryption functions.
396
 *
397
 * The data must be at least TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes in length.
398
 * If the passed byte array is smaller than required, the behaviour is
399
 * undefined.
400
 *
401
 * If the data pointer is NULL, the behaviour is undefined
402
 *
403
 * @return true if the data is encrypted by this module.
404
 */
405
bool tox_is_data_encrypted(const uint8_t data[TOX_PASS_ENCRYPTION_EXTRA_LENGTH])
406
2
{
407
2
    return memcmp(data, TOX_ENC_SAVE_MAGIC_NUMBER, TOX_ENC_SAVE_MAGIC_LENGTH) == 0;
408
2
}
409
410
const char *tox_err_key_derivation_to_string(Tox_Err_Key_Derivation error)
411
0
{
412
0
    switch (error) {
413
0
        case TOX_ERR_KEY_DERIVATION_OK:
414
0
            return "TOX_ERR_KEY_DERIVATION_OK";
415
0
        case TOX_ERR_KEY_DERIVATION_NULL:
416
0
            return "TOX_ERR_KEY_DERIVATION_NULL";
417
0
        case TOX_ERR_KEY_DERIVATION_FAILED:
418
0
            return "TOX_ERR_KEY_DERIVATION_FAILED";
419
0
    }
420
0
    return "<invalid Tox_Err_Key_Derivation>";
421
0
}
422
423
const char *tox_err_encryption_to_string(Tox_Err_Encryption error)
424
0
{
425
0
    switch (error) {
426
0
        case TOX_ERR_ENCRYPTION_OK:
427
0
            return "TOX_ERR_ENCRYPTION_OK";
428
0
        case TOX_ERR_ENCRYPTION_NULL:
429
0
            return "TOX_ERR_ENCRYPTION_NULL";
430
0
        case TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED:
431
0
            return "TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED";
432
0
        case TOX_ERR_ENCRYPTION_FAILED:
433
0
            return "TOX_ERR_ENCRYPTION_FAILED";
434
0
    }
435
0
    return "<invalid Tox_Err_Encryption>";
436
0
}
437
438
const char *tox_err_decryption_to_string(Tox_Err_Decryption error)
439
2
{
440
2
    switch (error) {
441
0
        case TOX_ERR_DECRYPTION_OK:
442
0
            return "TOX_ERR_DECRYPTION_OK";
443
0
        case TOX_ERR_DECRYPTION_NULL:
444
0
            return "TOX_ERR_DECRYPTION_NULL";
445
0
        case TOX_ERR_DECRYPTION_INVALID_LENGTH:
446
0
            return "TOX_ERR_DECRYPTION_INVALID_LENGTH";
447
0
        case TOX_ERR_DECRYPTION_BAD_FORMAT:
448
0
            return "TOX_ERR_DECRYPTION_BAD_FORMAT";
449
1
        case TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED:
450
1
            return "TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED";
451
1
        case TOX_ERR_DECRYPTION_FAILED:
452
1
            return "TOX_ERR_DECRYPTION_FAILED";
453
2
    }
454
0
    return "<invalid Tox_Err_Decryption>";
455
2
}
456
457
const char *tox_err_get_salt_to_string(Tox_Err_Get_Salt error)
458
0
{
459
0
    switch (error) {
460
0
        case TOX_ERR_GET_SALT_OK:
461
0
            return "TOX_ERR_GET_SALT_OK";
462
0
        case TOX_ERR_GET_SALT_NULL:
463
0
            return "TOX_ERR_GET_SALT_NULL";
464
0
        case TOX_ERR_GET_SALT_BAD_FORMAT:
465
0
            return "TOX_ERR_GET_SALT_BAD_FORMAT";
466
0
    }
467
0
    return "<invalid Tox_Err_Get_Salt>";
468
0
}