Coverage Report

Created: 2026-10-24 08:16

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