Coverage Report

Created: 2025-10-08 19:34

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