Coverage Report

Created: 2024-01-26 01:52

/work/toxcore/group_moderation.c
Line
Count
Source (jump to first uncovered line)
1
/* SPDX-License-Identifier: GPL-3.0-or-later
2
 * Copyright © 2016-2020 The TokTok team.
3
 * Copyright © 2015 Tox project.
4
 */
5
6
/**
7
 * An implementation of massive text only group chats.
8
 */
9
10
#include "group_moderation.h"
11
12
#include <assert.h>
13
14
#include <stdlib.h>
15
#include <string.h>
16
#include <time.h>
17
18
#include "DHT.h"
19
#include "ccompat.h"
20
#include "crypto_core.h"
21
#include "logger.h"
22
#include "network.h"
23
#include "util.h"
24
25
static_assert(MOD_SANCTIONS_CREDS_SIZE <= MAX_PACKET_SIZE_NO_HEADERS,
26
              "MOD_SANCTIONS_CREDS_SIZE must be <= the maximum allowed payload size");
27
static_assert(MOD_MAX_NUM_SANCTIONS * MOD_SANCTION_PACKED_SIZE + MOD_SANCTIONS_CREDS_SIZE <= MAX_PACKET_SIZE_NO_HEADERS,
28
              "MOD_MAX_NUM_SANCTIONS must be able to fit inside the maximum allowed payload size");
29
static_assert(MOD_MAX_NUM_MODERATORS * MOD_LIST_ENTRY_SIZE <= MAX_PACKET_SIZE_NO_HEADERS,
30
              "MOD_MAX_NUM_MODERATORS must be able to fit insize the maximum allowed payload size");
31
static_assert(MOD_MAX_NUM_MODERATORS <= MOD_MAX_NUM_MODERATORS_LIMIT,
32
              "MOD_MAX_NUM_MODERATORS must be <= MOD_MAX_NUM_MODERATORS_LIMIT");
33
static_assert(MOD_MAX_NUM_SANCTIONS <= MOD_MAX_NUM_SANCTIONS_LIMIT,
34
              "MOD_MAX_NUM_SANCTIONS must be <= MOD_MAX_NUM_SANCTIONS_LIMIT");
35
36
uint16_t mod_list_packed_size(const Moderation *moderation)
37
14
{
38
14
    return moderation->num_mods * MOD_LIST_ENTRY_SIZE;
39
14
}
40
41
int mod_list_unpack(Moderation *moderation, const uint8_t *data, uint16_t length, uint16_t num_mods)
42
357
{
43
357
    if (length < num_mods * MOD_LIST_ENTRY_SIZE) {
44
3
        return -1;
45
3
    }
46
47
354
    mod_list_cleanup(moderation);
48
49
354
    if (num_mods == 0) {
50
144
        return 0;
51
144
    }
52
53
210
    uint8_t **tmp_list = (uint8_t **)calloc(num_mods, sizeof(uint8_t *));
54
55
210
    if (tmp_list == nullptr) {
56
0
        return -1;
57
0
    }
58
59
210
    uint16_t unpacked_len = 0;
60
61
497
    for (uint16_t i = 0; i < num_mods; ++i) {
62
287
        uint8_t *entry = (uint8_t *)malloc(MOD_LIST_ENTRY_SIZE);
63
64
287
        if (entry == nullptr) {
65
0
            free_uint8_t_pointer_array(moderation->mem, tmp_list, i);
66
0
            return -1;
67
0
        }
68
69
287
        memcpy(entry, &data[i * MOD_LIST_ENTRY_SIZE], MOD_LIST_ENTRY_SIZE);
70
287
        tmp_list[i] = entry;
71
72
287
        unpacked_len += MOD_LIST_ENTRY_SIZE;
73
287
    }
74
75
210
    moderation->mod_list = tmp_list;
76
210
    moderation->num_mods = num_mods;
77
78
210
    return unpacked_len;
79
210
}
80
81
void mod_list_pack(const Moderation *moderation, uint8_t *data)
82
428
{
83
884
    for (uint16_t i = 0; i < moderation->num_mods; ++i) {
84
456
        memcpy(&data[i * MOD_LIST_ENTRY_SIZE], moderation->mod_list[i], MOD_LIST_ENTRY_SIZE);
85
456
    }
86
428
}
87
88
void mod_list_get_data_hash(uint8_t *hash, const uint8_t *packed_mod_list, uint16_t length)
89
20
{
90
20
    crypto_sha256(hash, packed_mod_list, length);
91
20
}
92
93
bool mod_list_make_hash(const Moderation *moderation, uint8_t *hash)
94
13
{
95
13
    if (moderation->num_mods == 0) {
96
2
        memzero(hash, MOD_MODERATION_HASH_SIZE);
97
2
        return true;
98
2
    }
99
100
11
    const size_t data_buf_size = mod_list_packed_size(moderation);
101
102
11
    assert(data_buf_size > 0);
103
104
11
    uint8_t *data = (uint8_t *)malloc(data_buf_size);
105
106
11
    if (data == nullptr) {
107
0
        return false;
108
0
    }
109
110
11
    mod_list_pack(moderation, data);
111
112
11
    mod_list_get_data_hash(hash, data, data_buf_size);
113
114
11
    free(data);
115
116
11
    return true;
117
11
}
118
119
/**
120
 * Returns moderator list index for public_sig_key.
121
 * Returns -1 if key is not in the list.
122
 */
123
non_null()
124
static int mod_list_index_of_sig_pk(const Moderation *moderation, const uint8_t *public_sig_key)
125
28
{
126
33
    for (uint16_t i = 0; i < moderation->num_mods; ++i) {
127
33
        if (memcmp(moderation->mod_list[i], public_sig_key, SIG_PUBLIC_KEY_SIZE) == 0) {
128
28
            return i;
129
28
        }
130
33
    }
131
132
0
    return -1;
133
28
}
134
135
bool mod_list_verify_sig_pk(const Moderation *moderation, const uint8_t *sig_pk)
136
2.01k
{
137
2.01k
    if (memcmp(moderation->founder_public_sig_key, sig_pk, SIG_PUBLIC_KEY_SIZE) == 0) {
138
465
        return true;
139
465
    }
140
141
2.02k
    for (uint16_t i = 0; i < moderation->num_mods; ++i) {
142
747
        if (memcmp(moderation->mod_list[i], sig_pk, SIG_PUBLIC_KEY_SIZE) == 0) {
143
266
            return true;
144
266
        }
145
747
    }
146
147
1.28k
    return false;
148
1.54k
}
149
150
bool mod_list_remove_index(Moderation *moderation, uint16_t index)
151
31
{
152
31
    if (index >= moderation->num_mods) {
153
2
        return false;
154
2
    }
155
156
29
    if ((moderation->num_mods - 1) == 0) {
157
13
        mod_list_cleanup(moderation);
158
13
        return true;
159
13
    }
160
161
16
    --moderation->num_mods;
162
163
16
    if (index != moderation->num_mods) {
164
16
        memcpy(moderation->mod_list[index], moderation->mod_list[moderation->num_mods],
165
16
               MOD_LIST_ENTRY_SIZE);
166
16
    }
167
168
16
    free(moderation->mod_list[moderation->num_mods]);
169
16
    moderation->mod_list[moderation->num_mods] = nullptr;
170
171
16
    uint8_t **tmp_list = (uint8_t **)realloc(moderation->mod_list, moderation->num_mods * sizeof(uint8_t *));
172
173
16
    if (tmp_list == nullptr) {
174
0
        return false;
175
0
    }
176
177
16
    moderation->mod_list = tmp_list;
178
179
16
    return true;
180
16
}
181
182
bool mod_list_remove_entry(Moderation *moderation, const uint8_t *public_sig_key)
183
29
{
184
29
    if (moderation->num_mods == 0) {
185
1
        return false;
186
1
    }
187
188
28
    const int idx = mod_list_index_of_sig_pk(moderation, public_sig_key);
189
190
28
    if (idx == -1) {
191
0
        return false;
192
0
    }
193
194
28
    assert(idx <= UINT16_MAX);
195
196
28
    return mod_list_remove_index(moderation, (uint16_t)idx);
197
28
}
198
199
bool mod_list_add_entry(Moderation *moderation, const uint8_t *mod_data)
200
42
{
201
42
    if (moderation->num_mods >= MOD_MAX_NUM_MODERATORS) {
202
0
        return false;
203
0
    }
204
205
42
    uint8_t **tmp_list = (uint8_t **)realloc(moderation->mod_list, (moderation->num_mods + 1) * sizeof(uint8_t *));
206
207
42
    if (tmp_list == nullptr) {
208
0
        return false;
209
0
    }
210
211
42
    moderation->mod_list = tmp_list;
212
213
42
    uint8_t *entry = (uint8_t *)malloc(MOD_LIST_ENTRY_SIZE);
214
215
42
    if (entry == nullptr) {
216
0
        return false;
217
0
    }
218
219
42
    memcpy(entry, mod_data, MOD_LIST_ENTRY_SIZE);
220
221
42
    tmp_list[moderation->num_mods] = entry;
222
42
    ++moderation->num_mods;
223
224
42
    return true;
225
42
}
226
227
void mod_list_cleanup(Moderation *moderation)
228
2.99k
{
229
2.99k
    free_uint8_t_pointer_array(moderation->mem, moderation->mod_list, moderation->num_mods);
230
2.99k
    moderation->num_mods = 0;
231
2.99k
    moderation->mod_list = nullptr;
232
2.99k
}
233
234
uint16_t sanctions_creds_pack(const Mod_Sanction_Creds *creds, uint8_t *data)
235
174
{
236
174
    uint16_t packed_len = 0;
237
238
174
    net_pack_u32(data + packed_len, creds->version);
239
174
    packed_len += sizeof(uint32_t);
240
174
    memcpy(data + packed_len, creds->hash, MOD_SANCTION_HASH_SIZE);
241
174
    packed_len += MOD_SANCTION_HASH_SIZE;
242
174
    net_pack_u16(data + packed_len, creds->checksum);
243
174
    packed_len += sizeof(uint16_t);
244
174
    memcpy(data + packed_len, creds->sig_pk, SIG_PUBLIC_KEY_SIZE);
245
174
    packed_len += SIG_PUBLIC_KEY_SIZE;
246
174
    memcpy(data + packed_len, creds->sig, SIGNATURE_SIZE);
247
174
    packed_len += SIGNATURE_SIZE;
248
249
174
    return packed_len;
250
174
}
251
252
uint16_t sanctions_list_packed_size(uint16_t num_sanctions)
253
2
{
254
2
    return MOD_SANCTION_PACKED_SIZE * num_sanctions;
255
2
}
256
257
int sanctions_list_pack(uint8_t *data, uint16_t length, const Mod_Sanction *sanctions, uint16_t num_sanctions,
258
                        const Mod_Sanction_Creds *creds)
259
240
{
260
240
    assert(sanctions != nullptr || num_sanctions == 0);
261
240
    assert(sanctions != nullptr || creds != nullptr);
262
263
240
    uint16_t packed_len = 0;
264
265
331
    for (uint16_t i = 0; i < num_sanctions; ++i) {
266
93
        if (packed_len + sizeof(uint8_t) + SIG_PUBLIC_KEY_SIZE + TIME_STAMP_SIZE > length) {
267
1
            return -1;
268
1
        }
269
270
92
        memcpy(data + packed_len, &sanctions[i].type, sizeof(uint8_t));
271
92
        packed_len += sizeof(uint8_t);
272
92
        memcpy(data + packed_len, sanctions[i].setter_public_sig_key, SIG_PUBLIC_KEY_SIZE);
273
92
        packed_len += SIG_PUBLIC_KEY_SIZE;
274
92
        net_pack_u64(data + packed_len, sanctions[i].time_set);
275
92
        packed_len += TIME_STAMP_SIZE;
276
277
92
        const uint8_t sanctions_type = sanctions[i].type;
278
279
92
        if (sanctions_type == SA_OBSERVER) {
280
92
            if (packed_len + ENC_PUBLIC_KEY_SIZE > length) {
281
0
                return -1;
282
0
            }
283
284
92
            memcpy(data + packed_len, sanctions[i].target_public_enc_key, ENC_PUBLIC_KEY_SIZE);
285
92
            packed_len += ENC_PUBLIC_KEY_SIZE;
286
92
        } else {
287
0
            return -1;
288
0
        }
289
290
92
        if (packed_len + SIGNATURE_SIZE > length) {
291
1
            return -1;
292
1
        }
293
294
        /* Signature must be packed last */
295
91
        memcpy(data + packed_len, sanctions[i].signature, SIGNATURE_SIZE);
296
91
        packed_len += SIGNATURE_SIZE;
297
91
    }
298
299
238
    if (creds == nullptr) {
300
67
        return packed_len;
301
67
    }
302
303
171
    if (length < packed_len || length - packed_len < MOD_SANCTIONS_CREDS_SIZE) {
304
0
        return -1;
305
0
    }
306
307
171
    const uint16_t cred_len = sanctions_creds_pack(creds, data + packed_len);
308
309
171
    if (cred_len != MOD_SANCTIONS_CREDS_SIZE) {
310
0
        return -1;
311
0
    }
312
313
171
    return packed_len + cred_len;
314
171
}
315
316
uint16_t sanctions_creds_unpack(Mod_Sanction_Creds *creds, const uint8_t *data)
317
191
{
318
191
    uint16_t len_processed = 0;
319
320
191
    net_unpack_u32(data + len_processed, &creds->version);
321
191
    len_processed += sizeof(uint32_t);
322
191
    memcpy(creds->hash, data + len_processed, MOD_SANCTION_HASH_SIZE);
323
191
    len_processed += MOD_SANCTION_HASH_SIZE;
324
191
    net_unpack_u16(data + len_processed, &creds->checksum);
325
191
    len_processed += sizeof(uint16_t);
326
191
    memcpy(creds->sig_pk, data + len_processed, SIG_PUBLIC_KEY_SIZE);
327
191
    len_processed += SIG_PUBLIC_KEY_SIZE;
328
191
    memcpy(creds->sig, data + len_processed, SIGNATURE_SIZE);
329
191
    len_processed += SIGNATURE_SIZE;
330
331
191
    return len_processed;
332
191
}
333
334
int sanctions_list_unpack(Mod_Sanction *sanctions, Mod_Sanction_Creds *creds, uint16_t max_sanctions,
335
                          const uint8_t *data, uint16_t length, uint16_t *processed_data_len)
336
196
{
337
196
    uint16_t num = 0;
338
196
    uint16_t len_processed = 0;
339
340
279
    while (num < max_sanctions && num < MOD_MAX_NUM_SANCTIONS && len_processed < length) {
341
91
        if (len_processed + sizeof(uint8_t) + SIG_PUBLIC_KEY_SIZE + TIME_STAMP_SIZE > length) {
342
1
            return -1;
343
1
        }
344
345
90
        memcpy(&sanctions[num].type, data + len_processed, sizeof(uint8_t));
346
90
        len_processed += sizeof(uint8_t);
347
90
        memcpy(sanctions[num].setter_public_sig_key, data + len_processed, SIG_PUBLIC_KEY_SIZE);
348
90
        len_processed += SIG_PUBLIC_KEY_SIZE;
349
90
        net_unpack_u64(data + len_processed, &sanctions[num].time_set);
350
90
        len_processed += TIME_STAMP_SIZE;
351
352
90
        if (sanctions[num].type == SA_OBSERVER) {
353
86
            if (len_processed + ENC_PUBLIC_KEY_SIZE > length) {
354
2
                return -1;
355
2
            }
356
357
84
            memcpy(sanctions[num].target_public_enc_key, data + len_processed, ENC_PUBLIC_KEY_SIZE);
358
84
            len_processed += ENC_PUBLIC_KEY_SIZE;
359
84
        } else {
360
4
            return -1;
361
4
        }
362
363
84
        if (len_processed + SIGNATURE_SIZE > length) {
364
1
            return -1;
365
1
        }
366
367
83
        memcpy(sanctions[num].signature, data + len_processed, SIGNATURE_SIZE);
368
83
        len_processed += SIGNATURE_SIZE;
369
370
83
        ++num;
371
83
    }
372
373
188
    if (length <= len_processed || length - len_processed < MOD_SANCTIONS_CREDS_SIZE) {
374
7
        if (length != len_processed) {
375
1
            return -1;
376
1
        }
377
378
6
        if (processed_data_len != nullptr) {
379
6
            *processed_data_len = len_processed;
380
6
        }
381
382
6
        return num;
383
7
    }
384
385
181
    const uint16_t creds_len = sanctions_creds_unpack(creds, data + len_processed);
386
387
181
    if (creds_len != MOD_SANCTIONS_CREDS_SIZE) {
388
0
        return -1;
389
0
    }
390
391
181
    if (processed_data_len != nullptr) {
392
1
        *processed_data_len = len_processed + creds_len;
393
1
    }
394
395
181
    return num;
396
181
}
397
398
399
/** @brief Creates a new sanction list hash and puts it in hash.
400
 *
401
 * The hash is derived from the signature of all entries plus the version number.
402
 * hash must have room for at least MOD_SANCTION_HASH_SIZE bytes.
403
 *
404
 * If num_sanctions is 0 the hash is zeroed.
405
 *
406
 * Return true on success.
407
 */
408
non_null(4) nullable(1)
409
static bool sanctions_list_make_hash(const Mod_Sanction *sanctions, uint32_t new_version, uint16_t num_sanctions,
410
                                     uint8_t *hash)
411
323
{
412
323
    if (num_sanctions == 0 || sanctions == nullptr) {
413
274
        memzero(hash, MOD_SANCTION_HASH_SIZE);
414
274
        return true;
415
274
    }
416
417
49
    const size_t sig_data_size = num_sanctions * SIGNATURE_SIZE;
418
49
    const size_t data_buf_size = sig_data_size + sizeof(uint32_t);
419
420
    // check for integer overflower
421
49
    if (data_buf_size < num_sanctions) {
422
0
        return false;
423
0
    }
424
425
49
    uint8_t *data = (uint8_t *)malloc(data_buf_size);
426
427
49
    if (data == nullptr) {
428
0
        return false;
429
0
    }
430
431
120
    for (uint16_t i = 0; i < num_sanctions; ++i) {
432
71
        memcpy(&data[i * SIGNATURE_SIZE], sanctions[i].signature, SIGNATURE_SIZE);
433
71
    }
434
435
49
    memcpy(&data[sig_data_size], &new_version, sizeof(uint32_t));
436
49
    crypto_sha256(hash, data, data_buf_size);
437
438
49
    free(data);
439
440
49
    return true;
441
49
}
442
443
/** @brief Verifies that sanction contains valid info and was assigned by a current mod or group founder.
444
 *
445
 * Returns true on success.
446
 */
447
non_null()
448
static bool sanctions_list_validate_entry(const Moderation *moderation, const Mod_Sanction *sanction)
449
57
{
450
57
    if (!mod_list_verify_sig_pk(moderation, sanction->setter_public_sig_key)) {
451
0
        return false;
452
0
    }
453
454
57
    if (sanction->type >= SA_INVALID) {
455
0
        return false;
456
0
    }
457
458
57
    if (sanction->time_set == 0) {
459
4
        return false;
460
4
    }
461
462
53
    uint8_t packed_data[MOD_SANCTION_PACKED_SIZE];
463
53
    const int packed_len = sanctions_list_pack(packed_data, sizeof(packed_data), sanction, 1, nullptr);
464
465
53
    if (packed_len <= SIGNATURE_SIZE) {
466
0
        return false;
467
0
    }
468
469
53
    return crypto_signature_verify(sanction->signature, packed_data, packed_len - SIGNATURE_SIZE,
470
53
                                   sanction->setter_public_sig_key);
471
53
}
472
473
non_null()
474
static uint16_t sanctions_creds_get_checksum(const Mod_Sanction_Creds *creds)
475
317
{
476
317
    return data_checksum(creds->hash, sizeof(creds->hash));
477
317
}
478
479
non_null()
480
static void sanctions_creds_set_checksum(Mod_Sanction_Creds *creds)
481
127
{
482
127
    creds->checksum = sanctions_creds_get_checksum(creds);
483
127
}
484
485
bool sanctions_list_make_creds(Moderation *moderation)
486
127
{
487
127
    const Mod_Sanction_Creds old_creds = moderation->sanctions_creds;
488
489
127
    ++moderation->sanctions_creds.version;
490
491
127
    memcpy(moderation->sanctions_creds.sig_pk, moderation->self_public_sig_key, SIG_PUBLIC_KEY_SIZE);
492
493
127
    uint8_t hash[MOD_SANCTION_HASH_SIZE];
494
495
127
    if (!sanctions_list_make_hash(moderation->sanctions, moderation->sanctions_creds.version,
496
127
                                  moderation->num_sanctions, hash)) {
497
0
        moderation->sanctions_creds = old_creds;
498
0
        return false;
499
0
    }
500
501
127
    memcpy(moderation->sanctions_creds.hash, hash, MOD_SANCTION_HASH_SIZE);
502
503
127
    sanctions_creds_set_checksum(&moderation->sanctions_creds);
504
505
127
    if (!crypto_signature_create(moderation->sanctions_creds.sig, moderation->sanctions_creds.hash,
506
127
                                 MOD_SANCTION_HASH_SIZE, moderation->self_secret_sig_key)) {
507
0
        moderation->sanctions_creds = old_creds;
508
0
        return false;
509
0
    }
510
511
127
    return true;
512
127
}
513
514
/** @brief Validates sanction list credentials.
515
 *
516
 * Verifies that:
517
 * - the public signature key belongs to a mod or the founder
518
 * - the signature for the hash was made by the owner of the public signature key.
519
 * - the received hash matches our own hash of the new sanctions list
520
 * - the received checksum matches the received hash
521
 * - the new version is >= our current version
522
 *
523
 * Returns true on success.
524
 */
525
non_null(1, 3) nullable(2)
526
static bool sanctions_creds_validate(const Moderation *moderation, const Mod_Sanction *sanctions,
527
                                     const Mod_Sanction_Creds *creds, uint16_t num_sanctions)
528
196
{
529
196
    if (!mod_list_verify_sig_pk(moderation, creds->sig_pk)) {
530
0
        LOGGER_WARNING(moderation->log, "Invalid credentials signature pk");
531
0
        return false;
532
0
    }
533
534
196
    uint8_t hash[MOD_SANCTION_HASH_SIZE];
535
536
196
    if (!sanctions_list_make_hash(sanctions, creds->version, num_sanctions, hash)) {
537
0
        return false;
538
0
    }
539
540
196
    if (memcmp(hash, creds->hash, MOD_SANCTION_HASH_SIZE) != 0) {
541
6
        LOGGER_WARNING(moderation->log, "Invalid credentials hash");
542
6
        return false;
543
6
    }
544
545
190
    if (creds->checksum != sanctions_creds_get_checksum(creds)) {
546
0
        LOGGER_WARNING(moderation->log, "Invalid credentials checksum");
547
0
        return false;
548
0
    }
549
550
190
    if (moderation->shared_state_version > 0) {
551
178
        if ((creds->version < moderation->sanctions_creds.version)
552
178
                && !(creds->version == 0 && moderation->sanctions_creds.version == UINT32_MAX)) {
553
0
            LOGGER_WARNING(moderation->log, "Invalid version");
554
0
            return false;
555
0
        }
556
178
    }
557
558
190
    if (!crypto_signature_verify(creds->sig, hash, MOD_SANCTION_HASH_SIZE, creds->sig_pk)) {
559
2
        LOGGER_WARNING(moderation->log, "Invalid signature");
560
2
        return false;
561
2
    }
562
563
188
    return true;
564
190
}
565
566
bool sanctions_list_check_integrity(const Moderation *moderation, const Mod_Sanction_Creds *creds,
567
                                    const Mod_Sanction *sanctions, uint16_t num_sanctions)
568
176
{
569
202
    for (uint16_t i = 0; i < num_sanctions; ++i) {
570
30
        if (!sanctions_list_validate_entry(moderation, &sanctions[i])) {
571
4
            LOGGER_WARNING(moderation->log, "Invalid entry");
572
4
            return false;
573
4
        }
574
30
    }
575
576
172
    return sanctions_creds_validate(moderation, sanctions, creds, num_sanctions);
577
176
}
578
579
/** @brief Validates a sanctions list if credentials are supplied. If successful,
580
 *   or if no credentials are supplied, assigns new sanctions list and credentials
581
 *   to moderation object.
582
 *
583
 * @param moderation The moderation object being operated on.
584
 * @param new_sanctions The sanctions list to validate and assign to moderation object.
585
 * @param new_creds The new sanctions credentials to be assigned to moderation object.
586
 * @param num_sanctions The number of sanctions in the sanctions list.
587
 *
588
 * @retval false if sanctions credentials validation fails.
589
 */
590
non_null(1, 2) nullable(3)
591
static bool sanctions_apply_new(Moderation *moderation, Mod_Sanction *new_sanctions,
592
                                const Mod_Sanction_Creds *new_creds,
593
                                uint16_t num_sanctions)
594
34
{
595
34
    if (new_creds != nullptr) {
596
20
        if (!sanctions_creds_validate(moderation, new_sanctions, new_creds, num_sanctions)) {
597
6
            LOGGER_WARNING(moderation->log, "Failed to validate credentials");
598
6
            return false;
599
6
        }
600
601
14
        moderation->sanctions_creds = *new_creds;
602
14
    }
603
604
28
    sanctions_list_cleanup(moderation);
605
28
    moderation->sanctions = new_sanctions;
606
28
    moderation->num_sanctions = num_sanctions;
607
608
28
    return true;
609
34
}
610
611
/** @brief Returns a copy of the sanctions list. The caller is responsible for freeing the
612
 * memory returned by this function.
613
 */
614
non_null()
615
static Mod_Sanction *sanctions_list_copy(const Mod_Sanction *sanctions, uint16_t num_sanctions)
616
16
{
617
16
    Mod_Sanction *copy = (Mod_Sanction *)calloc(num_sanctions, sizeof(Mod_Sanction));
618
619
16
    if (copy == nullptr) {
620
0
        return nullptr;
621
0
    }
622
623
16
    memcpy(copy, sanctions, num_sanctions * sizeof(Mod_Sanction));
624
625
16
    return copy;
626
16
}
627
628
/** @brief Removes index-th sanction list entry.
629
 *
630
 * New credentials will be validated if creds is non-null.
631
 *
632
 * Returns true on success.
633
 */
634
non_null(1) nullable(3)
635
static bool sanctions_list_remove_index(Moderation *moderation, uint16_t index, const Mod_Sanction_Creds *creds)
636
14
{
637
14
    if (index >= moderation->num_sanctions) {
638
0
        return false;
639
0
    }
640
641
14
    const uint16_t new_num = moderation->num_sanctions - 1;
642
643
14
    if (new_num == 0) {
644
7
        if (creds != nullptr) {
645
4
            if (!sanctions_creds_validate(moderation, nullptr, creds, 0)) {
646
0
                return false;
647
0
            }
648
649
4
            moderation->sanctions_creds = *creds;
650
4
        }
651
652
7
        sanctions_list_cleanup(moderation);
653
654
7
        return true;
655
7
    }
656
657
    /* Operate on a copy of the list in case something goes wrong. */
658
7
    Mod_Sanction *sanctions_copy = sanctions_list_copy(moderation->sanctions, moderation->num_sanctions);
659
660
7
    if (sanctions_copy == nullptr) {
661
0
        return false;
662
0
    }
663
664
7
    if (index != new_num) {
665
7
        sanctions_copy[index] = sanctions_copy[new_num];
666
7
    }
667
668
7
    Mod_Sanction *new_list = (Mod_Sanction *)realloc(sanctions_copy, new_num * sizeof(Mod_Sanction));
669
670
7
    if (new_list == nullptr) {
671
0
        free(sanctions_copy);
672
0
        return false;
673
0
    }
674
675
7
    if (!sanctions_apply_new(moderation, new_list, creds, new_num)) {
676
0
        free(new_list);
677
0
        return false;
678
0
    }
679
680
7
    return true;
681
7
}
682
683
bool sanctions_list_remove_observer(Moderation *moderation, const uint8_t *public_key,
684
                                    const Mod_Sanction_Creds *creds)
685
14
{
686
14
    for (uint16_t i = 0; i < moderation->num_sanctions; ++i) {
687
14
        const Mod_Sanction *curr_sanction = &moderation->sanctions[i];
688
689
14
        if (curr_sanction->type != SA_OBSERVER) {
690
0
            continue;
691
0
        }
692
693
14
        if (memcmp(public_key, curr_sanction->target_public_enc_key, ENC_PUBLIC_KEY_SIZE) == 0) {
694
14
            if (!sanctions_list_remove_index(moderation, i, creds)) {
695
0
                return false;
696
0
            }
697
698
14
            if (creds == nullptr) {
699
6
                return sanctions_list_make_creds(moderation);
700
6
            }
701
702
8
            return true;
703
14
        }
704
14
    }
705
706
0
    return false;
707
14
}
708
709
bool sanctions_list_is_observer(const Moderation *moderation, const uint8_t *public_key)
710
2.28k
{
711
2.47k
    for (uint16_t i = 0; i < moderation->num_sanctions; ++i) {
712
256
        const Mod_Sanction *curr_sanction = &moderation->sanctions[i];
713
714
256
        if (curr_sanction->type != SA_OBSERVER) {
715
0
            continue;
716
0
        }
717
718
256
        if (memcmp(curr_sanction->target_public_enc_key, public_key, ENC_PUBLIC_KEY_SIZE) == 0) {
719
73
            return true;
720
73
        }
721
256
    }
722
723
2.21k
    return false;
724
2.28k
}
725
726
bool sanctions_list_entry_exists(const Moderation *moderation, const Mod_Sanction *sanction)
727
49
{
728
49
    if (sanction->type == SA_OBSERVER) {
729
49
        return sanctions_list_is_observer(moderation, sanction->target_public_enc_key);
730
49
    }
731
732
0
    return false;
733
49
}
734
735
bool sanctions_list_add_entry(Moderation *moderation, const Mod_Sanction *sanction, const Mod_Sanction_Creds *creds)
736
27
{
737
27
    if (moderation->num_sanctions >= MOD_MAX_NUM_SANCTIONS) {
738
0
        LOGGER_WARNING(moderation->log, "num_sanctions %d exceeds maximum", moderation->num_sanctions);
739
0
        return false;
740
0
    }
741
742
27
    if (!sanctions_list_validate_entry(moderation, sanction)) {
743
0
        LOGGER_ERROR(moderation->log, "Failed to validate sanction");
744
0
        return false;
745
0
    }
746
747
27
    if (sanctions_list_entry_exists(moderation, sanction)) {
748
0
        LOGGER_WARNING(moderation->log, "Attempted to add duplicate sanction");
749
0
        return false;
750
0
    }
751
752
    /* Operate on a copy of the list in case something goes wrong. */
753
27
    Mod_Sanction *sanctions_copy = nullptr;
754
755
27
    if (moderation->num_sanctions > 0) {
756
9
        sanctions_copy = sanctions_list_copy(moderation->sanctions, moderation->num_sanctions);
757
758
9
        if (sanctions_copy == nullptr) {
759
0
            return false;
760
0
        }
761
9
    }
762
763
27
    const uint16_t index = moderation->num_sanctions;
764
27
    Mod_Sanction *new_list = (Mod_Sanction *)realloc(sanctions_copy, (index + 1) * sizeof(Mod_Sanction));
765
766
27
    if (new_list == nullptr) {
767
0
        free(sanctions_copy);
768
0
        return false;
769
0
    }
770
771
27
    new_list[index] = *sanction;
772
773
27
    if (!sanctions_apply_new(moderation, new_list, creds, index + 1)) {
774
6
        free(new_list);
775
6
        return false;
776
6
    }
777
778
21
    return true;
779
27
}
780
781
/** @brief Signs packed sanction data.
782
 *
783
 * This function must be called by the owner of the entry's public_sig_key.
784
 *
785
 * Returns true on success.
786
 */
787
non_null()
788
static bool sanctions_list_sign_entry(const Moderation *moderation, Mod_Sanction *sanction)
789
13
{
790
13
    uint8_t packed_data[MOD_SANCTION_PACKED_SIZE];
791
13
    const int packed_len = sanctions_list_pack(packed_data, sizeof(packed_data), sanction, 1, nullptr);
792
793
13
    if (packed_len <= SIGNATURE_SIZE) {
794
0
        LOGGER_ERROR(moderation->log, "Failed to pack sanctions list: %d", packed_len);
795
0
        return false;
796
0
    }
797
798
13
    return crypto_signature_create(sanction->signature, packed_data, packed_len - SIGNATURE_SIZE,
799
13
                                   moderation->self_secret_sig_key);
800
13
}
801
802
bool sanctions_list_make_entry(Moderation *moderation, const uint8_t *public_key, Mod_Sanction *sanction,
803
                               uint8_t type)
804
11
{
805
11
    *sanction = (Mod_Sanction) {
806
11
        0
807
11
    };
808
809
11
    if (type == SA_OBSERVER) {
810
11
        memcpy(sanction->target_public_enc_key, public_key, ENC_PUBLIC_KEY_SIZE);
811
11
    } else {
812
0
        LOGGER_ERROR(moderation->log, "Tried to create sanction with invalid type: %u", type);
813
0
        return false;
814
0
    }
815
816
11
    memcpy(sanction->setter_public_sig_key, moderation->self_public_sig_key, SIG_PUBLIC_KEY_SIZE);
817
818
11
    sanction->time_set = (uint64_t)time(nullptr);
819
11
    sanction->type = type;
820
821
11
    if (!sanctions_list_sign_entry(moderation, sanction)) {
822
0
        LOGGER_ERROR(moderation->log, "Failed to sign sanction");
823
0
        return false;
824
0
    }
825
826
11
    if (!sanctions_list_add_entry(moderation, sanction, nullptr)) {
827
0
        return false;
828
0
    }
829
830
11
    if (!sanctions_list_make_creds(moderation)) {
831
0
        LOGGER_ERROR(moderation->log, "Failed to make credentials for new sanction");
832
0
        return false;
833
0
    }
834
835
11
    return true;
836
11
}
837
uint16_t sanctions_list_replace_sig(Moderation *moderation, const uint8_t *public_sig_key)
838
5
{
839
5
    uint16_t count = 0;
840
841
7
    for (uint16_t i = 0; i < moderation->num_sanctions; ++i) {
842
2
        if (memcmp(moderation->sanctions[i].setter_public_sig_key, public_sig_key, SIG_PUBLIC_KEY_SIZE) != 0) {
843
0
            continue;
844
0
        }
845
846
2
        memcpy(moderation->sanctions[i].setter_public_sig_key, moderation->self_public_sig_key, SIG_PUBLIC_KEY_SIZE);
847
848
2
        if (!sanctions_list_sign_entry(moderation, &moderation->sanctions[i])) {
849
0
            LOGGER_ERROR(moderation->log, "Failed to sign sanction");
850
0
            continue;
851
0
        }
852
853
2
        ++count;
854
2
    }
855
856
5
    if (count > 0) {
857
1
        if (!sanctions_list_make_creds(moderation)) {
858
0
            return 0;
859
0
        }
860
1
    }
861
862
5
    return count;
863
5
}
864
865
void sanctions_list_cleanup(Moderation *moderation)
866
2.75k
{
867
2.75k
    if (moderation->sanctions != nullptr) {
868
92
        free(moderation->sanctions);
869
92
    }
870
871
2.75k
    moderation->sanctions = nullptr;
872
2.75k
    moderation->num_sanctions = 0;
873
2.75k
}