mirror of
https://github.com/irungentoo/toxcore.git
synced 2024-03-22 13:30:51 +08:00
Merge moderation portion of new groupchats codebase
This commit is contained in:
parent
09575dc05b
commit
015305a088
|
@ -260,19 +260,25 @@ set(toxcore_SOURCES ${toxcore_SOURCES}
|
||||||
toxcore/friend_requests.c
|
toxcore/friend_requests.c
|
||||||
toxcore/friend_requests.h)
|
toxcore/friend_requests.h)
|
||||||
|
|
||||||
# LAYER 6: Tox messenger
|
# LAYER 6: DHT based group chats
|
||||||
|
# ----------------------------------------
|
||||||
|
set(toxcore_SOURCES ${toxcore_SOURCES}
|
||||||
|
toxcore/group_moderation.c
|
||||||
|
toxcore/group_moderation.h)
|
||||||
|
|
||||||
|
# LAYER 7: Tox messenger
|
||||||
# ----------------------
|
# ----------------------
|
||||||
set(toxcore_SOURCES ${toxcore_SOURCES}
|
set(toxcore_SOURCES ${toxcore_SOURCES}
|
||||||
toxcore/Messenger.c
|
toxcore/Messenger.c
|
||||||
toxcore/Messenger.h)
|
toxcore/Messenger.h)
|
||||||
|
|
||||||
# LAYER 7: Group chats
|
# LAYER 8: Conferences
|
||||||
# --------------------
|
# --------------------
|
||||||
set(toxcore_SOURCES ${toxcore_SOURCES}
|
set(toxcore_SOURCES ${toxcore_SOURCES}
|
||||||
toxcore/group.c
|
toxcore/group.c
|
||||||
toxcore/group.h)
|
toxcore/group.h)
|
||||||
|
|
||||||
# LAYER 8: Public API
|
# LAYER 9: Public API
|
||||||
# -------------------
|
# -------------------
|
||||||
set(toxcore_SOURCES ${toxcore_SOURCES}
|
set(toxcore_SOURCES ${toxcore_SOURCES}
|
||||||
toxcore/tox_api.c
|
toxcore/tox_api.c
|
||||||
|
@ -281,7 +287,7 @@ set(toxcore_SOURCES ${toxcore_SOURCES}
|
||||||
toxcore/tox_private.h)
|
toxcore/tox_private.h)
|
||||||
set(toxcore_API_HEADERS ${toxcore_API_HEADERS} ${toxcore_SOURCE_DIR}/toxcore/tox.h^tox)
|
set(toxcore_API_HEADERS ${toxcore_API_HEADERS} ${toxcore_SOURCE_DIR}/toxcore/tox.h^tox)
|
||||||
|
|
||||||
# LAYER 9: New async events API
|
# LAYER 10: New async events API
|
||||||
# -------------------
|
# -------------------
|
||||||
set(toxcore_SOURCES ${toxcore_SOURCES}
|
set(toxcore_SOURCES ${toxcore_SOURCES}
|
||||||
third_party/cmp/cmp.c
|
third_party/cmp/cmp.c
|
||||||
|
@ -319,7 +325,7 @@ set(toxcore_SOURCES ${toxcore_SOURCES}
|
||||||
toxcore/tox_unpack.h)
|
toxcore/tox_unpack.h)
|
||||||
set(toxcore_API_HEADERS ${toxcore_API_HEADERS} ${toxcore_SOURCE_DIR}/toxcore/tox_events.h^tox)
|
set(toxcore_API_HEADERS ${toxcore_API_HEADERS} ${toxcore_SOURCE_DIR}/toxcore/tox_events.h^tox)
|
||||||
|
|
||||||
# LAYER 10: Dispatch recorded events to callbacks.
|
# LAYER 11: Dispatch recorded events to callbacks.
|
||||||
# -------------------
|
# -------------------
|
||||||
set(toxcore_SOURCES ${toxcore_SOURCES}
|
set(toxcore_SOURCES ${toxcore_SOURCES}
|
||||||
toxcore/tox_dispatch.c
|
toxcore/tox_dispatch.c
|
||||||
|
@ -438,6 +444,7 @@ unit_test(toxav rtp)
|
||||||
unit_test(toxcore DHT)
|
unit_test(toxcore DHT)
|
||||||
unit_test(toxcore bin_pack)
|
unit_test(toxcore bin_pack)
|
||||||
unit_test(toxcore crypto_core)
|
unit_test(toxcore crypto_core)
|
||||||
|
unit_test(toxcore group_moderation)
|
||||||
unit_test(toxcore mono_time)
|
unit_test(toxcore mono_time)
|
||||||
unit_test(toxcore ping_array)
|
unit_test(toxcore ping_array)
|
||||||
unit_test(toxcore tox)
|
unit_test(toxcore tox)
|
||||||
|
|
|
@ -11,6 +11,8 @@ CPPCHECK+=("--inconclusive")
|
||||||
CPPCHECK+=("--error-exitcode=1")
|
CPPCHECK+=("--error-exitcode=1")
|
||||||
# Used for VLA.
|
# Used for VLA.
|
||||||
CPPCHECK+=("--suppress=allocaCalled")
|
CPPCHECK+=("--suppress=allocaCalled")
|
||||||
|
# False positives on sanctions_copy in group_moderation.c
|
||||||
|
CPPCHECK+=("--suppress=doubleFree")
|
||||||
# False positives in switch statements.
|
# False positives in switch statements.
|
||||||
CPPCHECK+=("--suppress=knownConditionTrueFalse")
|
CPPCHECK+=("--suppress=knownConditionTrueFalse")
|
||||||
# Cppcheck does not need standard library headers to get proper results.
|
# Cppcheck does not need standard library headers to get proper results.
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
8216f2b48b15db02c373766c9e8f898a0cf8ebb26310635b146a118d0bcf1f71 /usr/local/bin/tox-bootstrapd
|
073710c9592554bbcb8bd094c9d64a987d52a2d1dedf965557b56fe848ddc14b /usr/local/bin/tox-bootstrapd
|
||||||
|
|
|
@ -45,6 +45,7 @@ RUN source .github/scripts/flags-coverage.sh \
|
||||||
-DSTRICT_ABI=ON \
|
-DSTRICT_ABI=ON \
|
||||||
-DAUTOTEST=ON \
|
-DAUTOTEST=ON \
|
||||||
-DPROXY_TEST=ON \
|
-DPROXY_TEST=ON \
|
||||||
|
-DUSE_IPV6=OFF \
|
||||||
&& cmake --build _build --parallel 8 --target install
|
&& cmake --build _build --parallel 8 --target install
|
||||||
|
|
||||||
WORKDIR /work/_build
|
WORKDIR /work/_build
|
||||||
|
|
|
@ -15,6 +15,7 @@ sh_test(
|
||||||
args = ["$(locations %s)" % f for f in CIMPLE_FILES] + [
|
args = ["$(locations %s)" % f for f in CIMPLE_FILES] + [
|
||||||
"-Wno-boolean-return",
|
"-Wno-boolean-return",
|
||||||
"-Wno-callback-names",
|
"-Wno-callback-names",
|
||||||
|
"-Wno-callgraph",
|
||||||
"-Wno-enum-names",
|
"-Wno-enum-names",
|
||||||
"-Wno-type-check",
|
"-Wno-type-check",
|
||||||
"+RTS",
|
"+RTS",
|
||||||
|
|
|
@ -76,6 +76,7 @@ cc_test(
|
||||||
flaky = True,
|
flaky = True,
|
||||||
deps = [
|
deps = [
|
||||||
":crypto_core",
|
":crypto_core",
|
||||||
|
":util",
|
||||||
"@com_google_googletest//:gtest",
|
"@com_google_googletest//:gtest",
|
||||||
"@com_google_googletest//:gtest_main",
|
"@com_google_googletest//:gtest_main",
|
||||||
],
|
],
|
||||||
|
@ -163,6 +164,7 @@ cc_library(
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
":attributes",
|
":attributes",
|
||||||
|
":ccompat",
|
||||||
":crypto_core",
|
":crypto_core",
|
||||||
"@pthread",
|
"@pthread",
|
||||||
],
|
],
|
||||||
|
@ -487,6 +489,46 @@ cc_library(
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "group_moderation",
|
||||||
|
srcs = ["group_moderation.c"],
|
||||||
|
hdrs = ["group_moderation.h"],
|
||||||
|
deps = [
|
||||||
|
":DHT",
|
||||||
|
":ccompat",
|
||||||
|
":crypto_core",
|
||||||
|
":logger",
|
||||||
|
":mono_time",
|
||||||
|
":network",
|
||||||
|
":util",
|
||||||
|
"@libsodium",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_test(
|
||||||
|
name = "group_moderation_test",
|
||||||
|
size = "small",
|
||||||
|
srcs = ["group_moderation_test.cc"],
|
||||||
|
deps = [
|
||||||
|
":crypto_core",
|
||||||
|
":group_moderation",
|
||||||
|
":logger",
|
||||||
|
":util",
|
||||||
|
"@com_google_googletest//:gtest",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_fuzz_test(
|
||||||
|
name = "group_moderation_fuzz_test",
|
||||||
|
srcs = ["group_moderation_fuzz_test.cc"],
|
||||||
|
corpus = ["//tools/toktok-fuzzer/corpus:group_moderation_fuzz_test"],
|
||||||
|
deps = [
|
||||||
|
":group_moderation",
|
||||||
|
"//c-toxcore/testing/fuzzing:fuzz_support",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
cc_library(
|
cc_library(
|
||||||
name = "Messenger",
|
name = "Messenger",
|
||||||
srcs = ["Messenger.c"],
|
srcs = ["Messenger.c"],
|
||||||
|
|
|
@ -1800,7 +1800,7 @@ static uint8_t do_ping_and_sendnode_requests(DHT *dht, uint64_t *lastgetnode, co
|
||||||
}
|
}
|
||||||
|
|
||||||
if (num_nodes > 0 && (mono_time_is_timeout(dht->mono_time, *lastgetnode, GET_NODE_INTERVAL)
|
if (num_nodes > 0 && (mono_time_is_timeout(dht->mono_time, *lastgetnode, GET_NODE_INTERVAL)
|
||||||
|| *bootstrap_times < MAX_BOOTSTRAP_TIMES)) {
|
|| *bootstrap_times < MAX_BOOTSTRAP_TIMES)) {
|
||||||
uint32_t rand_node = random_range_u32(num_nodes);
|
uint32_t rand_node = random_range_u32(num_nodes);
|
||||||
|
|
||||||
if ((num_nodes - 1) != rand_node) {
|
if ((num_nodes - 1) != rand_node) {
|
||||||
|
@ -1855,8 +1855,8 @@ static void do_Close(DHT *dht)
|
||||||
dht->num_to_bootstrap = 0;
|
dht->num_to_bootstrap = 0;
|
||||||
|
|
||||||
const uint8_t not_killed = do_ping_and_sendnode_requests(
|
const uint8_t not_killed = do_ping_and_sendnode_requests(
|
||||||
dht, &dht->close_lastgetnodes, dht->self_public_key, dht->close_clientlist, LCLIENT_LIST, &dht->close_bootstrap_times,
|
dht, &dht->close_lastgetnodes, dht->self_public_key, dht->close_clientlist, LCLIENT_LIST, &dht->close_bootstrap_times,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
if (not_killed != 0) {
|
if (not_killed != 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -2568,7 +2568,8 @@ static int handle_LANdiscovery(void *object, const IP_Port *source, const uint8_
|
||||||
|
|
||||||
/*----------------------------------------------------------------------------------*/
|
/*----------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
DHT *new_dht(const Logger *log, Mono_Time *mono_time, Networking_Core *net, bool hole_punching_enabled, bool lan_discovery_enabled)
|
DHT *new_dht(const Logger *log, Mono_Time *mono_time, Networking_Core *net, bool hole_punching_enabled,
|
||||||
|
bool lan_discovery_enabled)
|
||||||
{
|
{
|
||||||
if (net == nullptr) {
|
if (net == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -2761,7 +2762,8 @@ void dht_save(const DHT *dht, uint8_t *data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state_write_section_header(old_data, DHT_STATE_COOKIE_TYPE, pack_nodes(dht->log, data, sizeof(Node_format) * num, clients, num), DHT_STATE_TYPE_NODES);
|
state_write_section_header(old_data, DHT_STATE_COOKIE_TYPE, pack_nodes(dht->log, data, sizeof(Node_format) * num,
|
||||||
|
clients, num), DHT_STATE_TYPE_NODES);
|
||||||
|
|
||||||
free(clients);
|
free(clients);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Maximum size of a signature (may be smaller) */
|
||||||
|
#define SIGNATURE_SIZE CRYPTO_SIGNATURE_SIZE
|
||||||
/** Maximum number of clients stored per friend. */
|
/** Maximum number of clients stored per friend. */
|
||||||
#define MAX_FRIEND_CLIENTS 8
|
#define MAX_FRIEND_CLIENTS 8
|
||||||
|
|
||||||
|
@ -64,6 +66,7 @@ extern "C" {
|
||||||
#define CRYPTO_PACKET_DHTPK 156
|
#define CRYPTO_PACKET_DHTPK 156
|
||||||
#define CRYPTO_PACKET_NAT_PING 254 // NAT ping crypto packet ID.
|
#define CRYPTO_PACKET_NAT_PING 254 // NAT ping crypto packet ID.
|
||||||
|
|
||||||
|
/* Max size of a packed node for IPV4 and IPV6 respectively */
|
||||||
#define PACKED_NODE_SIZE_IP4 (1 + SIZE_IP4 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE)
|
#define PACKED_NODE_SIZE_IP4 (1 + SIZE_IP4 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE)
|
||||||
#define PACKED_NODE_SIZE_IP6 (1 + SIZE_IP6 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE)
|
#define PACKED_NODE_SIZE_IP6 (1 + SIZE_IP6 + sizeof(uint16_t) + CRYPTO_PUBLIC_KEY_SIZE)
|
||||||
|
|
||||||
|
@ -467,7 +470,8 @@ int dht_load(DHT *dht, const uint8_t *data, uint32_t length);
|
||||||
|
|
||||||
/** Initialize DHT. */
|
/** Initialize DHT. */
|
||||||
non_null()
|
non_null()
|
||||||
DHT *new_dht(const Logger *log, Mono_Time *mono_time, Networking_Core *net, bool hole_punching_enabled, bool lan_discovery_enabled);
|
DHT *new_dht(const Logger *log, Mono_Time *mono_time, Networking_Core *net, bool hole_punching_enabled,
|
||||||
|
bool lan_discovery_enabled);
|
||||||
|
|
||||||
non_null()
|
non_null()
|
||||||
void kill_dht(DHT *dht);
|
void kill_dht(DHT *dht);
|
||||||
|
|
|
@ -92,23 +92,6 @@ int getfriendcon_id(const Messenger *m, int32_t friendnumber)
|
||||||
return m->friendlist[friendnumber].friendcon_id;
|
return m->friendlist[friendnumber].friendcon_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a uint16_t that represents the checksum of address of length len.
|
|
||||||
*/
|
|
||||||
non_null()
|
|
||||||
static uint16_t address_checksum(const uint8_t *address, uint32_t len)
|
|
||||||
{
|
|
||||||
uint8_t checksum[2] = {0};
|
|
||||||
uint16_t check;
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < len; ++i) {
|
|
||||||
checksum[i % 2] ^= address[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(&check, checksum, sizeof(check));
|
|
||||||
return check;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format: `[real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)]`
|
* Format: `[real_pk (32 bytes)][nospam number (4 bytes)][checksum (2 bytes)]`
|
||||||
*
|
*
|
||||||
|
@ -119,7 +102,7 @@ void getaddress(const Messenger *m, uint8_t *address)
|
||||||
pk_copy(address, nc_get_self_public_key(m->net_crypto));
|
pk_copy(address, nc_get_self_public_key(m->net_crypto));
|
||||||
uint32_t nospam = get_nospam(m->fr);
|
uint32_t nospam = get_nospam(m->fr);
|
||||||
memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &nospam, sizeof(nospam));
|
memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &nospam, sizeof(nospam));
|
||||||
uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum));
|
uint16_t checksum = data_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum));
|
||||||
memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(nospam), &checksum, sizeof(checksum));
|
memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(nospam), &checksum, sizeof(checksum));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,7 +219,7 @@ int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, u
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t check;
|
uint16_t check;
|
||||||
const uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum));
|
const uint16_t checksum = data_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum));
|
||||||
memcpy(&check, address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), sizeof(check));
|
memcpy(&check, address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), sizeof(check));
|
||||||
|
|
||||||
if (check != checksum) {
|
if (check != checksum) {
|
||||||
|
@ -2887,7 +2870,7 @@ static State_Load_Status friends_list_load(Messenger *m, const uint8_t *data, ui
|
||||||
uint8_t address[FRIEND_ADDRESS_SIZE];
|
uint8_t address[FRIEND_ADDRESS_SIZE];
|
||||||
pk_copy(address, temp.real_pk);
|
pk_copy(address, temp.real_pk);
|
||||||
memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &temp.friendrequest_nospam, sizeof(uint32_t));
|
memcpy(address + CRYPTO_PUBLIC_KEY_SIZE, &temp.friendrequest_nospam, sizeof(uint32_t));
|
||||||
uint16_t checksum = address_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum));
|
uint16_t checksum = data_checksum(address, FRIEND_ADDRESS_SIZE - sizeof(checksum));
|
||||||
memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), &checksum, sizeof(checksum));
|
memcpy(address + CRYPTO_PUBLIC_KEY_SIZE + sizeof(uint32_t), &checksum, sizeof(checksum));
|
||||||
m_addfriend(m, address, temp.info, net_ntohs(temp.info_size));
|
m_addfriend(m, address, temp.info, net_ntohs(temp.info_size));
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,12 @@
|
||||||
#define crypto_box_MACBYTES (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES)
|
#define crypto_box_MACBYTES (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef VANILLA_NACL
|
||||||
|
// Need dht because of ENC_SECRET_KEY_SIZE and ENC_PUBLIC_KEY_SIZE
|
||||||
|
#define ENC_PUBLIC_KEY_SIZE CRYPTO_PUBLIC_KEY_SIZE
|
||||||
|
#define ENC_SECRET_KEY_SIZE CRYPTO_SECRET_KEY_SIZE
|
||||||
|
#endif
|
||||||
|
|
||||||
//!TOKSTYLE-
|
//!TOKSTYLE-
|
||||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||||
#include "../testing/fuzzing/fuzz_adapter.h"
|
#include "../testing/fuzzing/fuzz_adapter.h"
|
||||||
|
@ -58,6 +64,33 @@ static_assert(CRYPTO_SHA512_SIZE == crypto_hash_sha512_BYTES,
|
||||||
static_assert(CRYPTO_PUBLIC_KEY_SIZE == 32,
|
static_assert(CRYPTO_PUBLIC_KEY_SIZE == 32,
|
||||||
"CRYPTO_PUBLIC_KEY_SIZE is required to be 32 bytes for public_key_eq to work");
|
"CRYPTO_PUBLIC_KEY_SIZE is required to be 32 bytes for public_key_eq to work");
|
||||||
|
|
||||||
|
#ifndef VANILLA_NACL
|
||||||
|
static_assert(CRYPTO_SIGNATURE_SIZE == crypto_sign_BYTES,
|
||||||
|
"CRYPTO_SIGNATURE_SIZE should be equal to crypto_sign_BYTES");
|
||||||
|
static_assert(CRYPTO_SIGN_PUBLIC_KEY_SIZE == crypto_sign_PUBLICKEYBYTES,
|
||||||
|
"CRYPTO_SIGN_PUBLIC_KEY_SIZE should be equal to crypto_sign_PUBLICKEYBYTES");
|
||||||
|
static_assert(CRYPTO_SIGN_SECRET_KEY_SIZE == crypto_sign_SECRETKEYBYTES,
|
||||||
|
"CRYPTO_SIGN_SECRET_KEY_SIZE should be equal to crypto_sign_SECRETKEYBYTES");
|
||||||
|
#endif /* VANILLA_NACL */
|
||||||
|
|
||||||
|
bool create_extended_keypair(uint8_t *pk, uint8_t *sk)
|
||||||
|
{
|
||||||
|
#ifdef VANILLA_NACL
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
/* create signature key pair */
|
||||||
|
crypto_sign_keypair(pk + ENC_PUBLIC_KEY_SIZE, sk + ENC_SECRET_KEY_SIZE);
|
||||||
|
|
||||||
|
/* convert public signature key to public encryption key */
|
||||||
|
const int res1 = crypto_sign_ed25519_pk_to_curve25519(pk, pk + ENC_PUBLIC_KEY_SIZE);
|
||||||
|
|
||||||
|
/* convert secret signature key to secret encryption key */
|
||||||
|
const int res2 = crypto_sign_ed25519_sk_to_curve25519(sk, sk + ENC_SECRET_KEY_SIZE);
|
||||||
|
|
||||||
|
return res1 == 0 && res2 == 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
|
#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
|
||||||
static uint8_t *crypto_malloc(size_t bytes)
|
static uint8_t *crypto_malloc(size_t bytes)
|
||||||
{
|
{
|
||||||
|
@ -177,6 +210,26 @@ uint32_t random_range_u32(uint32_t upper_bound)
|
||||||
#endif // VANILLA_NACL
|
#endif // VANILLA_NACL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool crypto_signature_create(uint8_t *signature, const uint8_t *message, uint64_t message_length,
|
||||||
|
const uint8_t *secret_key)
|
||||||
|
{
|
||||||
|
#ifdef VANILLA_NACL
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return crypto_sign_detached(signature, nullptr, message, message_length, secret_key) == 0;
|
||||||
|
#endif // VANILLA_NACL
|
||||||
|
}
|
||||||
|
|
||||||
|
bool crypto_signature_verify(const uint8_t *signature, const uint8_t *message, uint64_t message_length,
|
||||||
|
const uint8_t *public_key)
|
||||||
|
{
|
||||||
|
#ifdef VANILLA_NACL
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return crypto_sign_verify_detached(signature, message, message_length, public_key) == 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
bool public_key_valid(const uint8_t *public_key)
|
bool public_key_valid(const uint8_t *public_key)
|
||||||
{
|
{
|
||||||
if (public_key[31] >= 128) { /* Last bit of key is always zero. */
|
if (public_key[31] >= 128) { /* Last bit of key is always zero. */
|
||||||
|
|
|
@ -19,6 +19,21 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of bytes in a signature.
|
||||||
|
*/
|
||||||
|
#define CRYPTO_SIGNATURE_SIZE 64
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of bytes in a Tox public key used for signatures.
|
||||||
|
*/
|
||||||
|
#define CRYPTO_SIGN_PUBLIC_KEY_SIZE 32
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of bytes in a Tox secret key used for signatures.
|
||||||
|
*/
|
||||||
|
#define CRYPTO_SIGN_SECRET_KEY_SIZE 64
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The number of bytes in a Tox public key used for encryption.
|
* @brief The number of bytes in a Tox public key used for encryption.
|
||||||
*/
|
*/
|
||||||
|
@ -60,6 +75,41 @@ extern "C" {
|
||||||
*/
|
*/
|
||||||
#define CRYPTO_SHA512_SIZE 64
|
#define CRYPTO_SHA512_SIZE 64
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of bytes in an encryption public key used by DHT group chats.
|
||||||
|
*/
|
||||||
|
#define ENC_PUBLIC_KEY_SIZE CRYPTO_PUBLIC_KEY_SIZE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of bytes in an encryption secret key used by DHT group chats.
|
||||||
|
*/
|
||||||
|
#define ENC_SECRET_KEY_SIZE CRYPTO_SECRET_KEY_SIZE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of bytes in a signature public key.
|
||||||
|
*/
|
||||||
|
#define SIG_PUBLIC_KEY_SIZE CRYPTO_SIGN_PUBLIC_KEY_SIZE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of bytes in a signature secret key.
|
||||||
|
*/
|
||||||
|
#define SIG_SECRET_KEY_SIZE CRYPTO_SIGN_SECRET_KEY_SIZE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of bytes in a DHT group chat public key identifier.
|
||||||
|
*/
|
||||||
|
#define CHAT_ID_SIZE SIG_PUBLIC_KEY_SIZE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of bytes in an extended public key used by DHT group chats.
|
||||||
|
*/
|
||||||
|
#define EXT_PUBLIC_KEY_SIZE (ENC_PUBLIC_KEY_SIZE + SIG_PUBLIC_KEY_SIZE)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of bytes in an extended secret key used by DHT group chats.
|
||||||
|
*/
|
||||||
|
#define EXT_SECRET_KEY_SIZE (ENC_SECRET_KEY_SIZE + SIG_SECRET_KEY_SIZE)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A `bzero`-like function which won't be optimised away by the compiler.
|
* @brief A `bzero`-like function which won't be optimised away by the compiler.
|
||||||
*
|
*
|
||||||
|
@ -129,6 +179,36 @@ uint64_t random_u64(void);
|
||||||
*/
|
*/
|
||||||
uint32_t random_range_u32(uint32_t upper_bound);
|
uint32_t random_range_u32(uint32_t upper_bound);
|
||||||
|
|
||||||
|
/** @brief Cryptographically signs a message using the supplied secret key and puts the resulting signature
|
||||||
|
* in the supplied buffer.
|
||||||
|
*
|
||||||
|
* @param signature The buffer for the resulting signature, which must have room for at
|
||||||
|
* least CRYPTO_SIGNATURE_SIZE bytes.
|
||||||
|
* @param message The message being signed.
|
||||||
|
* @param message_length The length in bytes of the message being signed.
|
||||||
|
* @param secret_key The secret key used to create the signature. The key should be
|
||||||
|
* produced by either `create_extended_keypair` or the libsodium function `crypto_sign_keypair`.
|
||||||
|
*
|
||||||
|
* @retval true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool crypto_signature_create(uint8_t *signature, const uint8_t *message, uint64_t message_length,
|
||||||
|
const uint8_t *secret_key);
|
||||||
|
|
||||||
|
/** @brief Verifies that the given signature was produced by a given message and public key.
|
||||||
|
*
|
||||||
|
* @param signature The signature we wish to verify.
|
||||||
|
* @param message The message we wish to verify.
|
||||||
|
* @param message_length The length of the message.
|
||||||
|
* @param public_key The public key counterpart of the secret key that was used to
|
||||||
|
* create the signature.
|
||||||
|
*
|
||||||
|
* @retval true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool crypto_signature_verify(const uint8_t *signature, const uint8_t *message, uint64_t message_length,
|
||||||
|
const uint8_t *public_key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Fill the given nonce with random bytes.
|
* @brief Fill the given nonce with random bytes.
|
||||||
*/
|
*/
|
||||||
|
@ -151,6 +231,17 @@ void random_bytes(uint8_t *bytes, size_t length);
|
||||||
non_null()
|
non_null()
|
||||||
bool public_key_valid(const uint8_t *public_key);
|
bool public_key_valid(const uint8_t *public_key);
|
||||||
|
|
||||||
|
/** @brief Creates an extended keypair: curve25519 and ed25519 for encryption and signing
|
||||||
|
* respectively. The Encryption keys are derived from the signature keys.
|
||||||
|
*
|
||||||
|
* @param pk The buffer where the public key will be stored. Must have room for EXT_PUBLIC_KEY_SIZE bytes.
|
||||||
|
* @param sk The buffer where the secret key will be stored. Must have room for EXT_SECRET_KEY_SIZE bytes.
|
||||||
|
*
|
||||||
|
* @retval true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool create_extended_keypair(uint8_t *pk, uint8_t *sk);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Generate a new random keypair.
|
* @brief Generate a new random keypair.
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,11 +6,17 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using ExtPublicKey = std::array<uint8_t, EXT_PUBLIC_KEY_SIZE>;
|
||||||
|
using ExtSecretKey = std::array<uint8_t, EXT_SECRET_KEY_SIZE>;
|
||||||
|
using Signature = std::array<uint8_t, CRYPTO_SIGNATURE_SIZE>;
|
||||||
|
using Nonce = std::array<uint8_t, CRYPTO_NONCE_SIZE>;
|
||||||
|
|
||||||
TEST(CryptoCore, IncrementNonce)
|
TEST(CryptoCore, IncrementNonce)
|
||||||
{
|
{
|
||||||
using Nonce = std::array<uint8_t, CRYPTO_NONCE_SIZE>;
|
|
||||||
Nonce nonce{};
|
Nonce nonce{};
|
||||||
increment_nonce(nonce.data());
|
increment_nonce(nonce.data());
|
||||||
EXPECT_EQ(
|
EXPECT_EQ(
|
||||||
|
@ -26,7 +32,6 @@ TEST(CryptoCore, IncrementNonce)
|
||||||
|
|
||||||
TEST(CryptoCore, IncrementNonceNumber)
|
TEST(CryptoCore, IncrementNonceNumber)
|
||||||
{
|
{
|
||||||
using Nonce = std::array<uint8_t, CRYPTO_NONCE_SIZE>;
|
|
||||||
Nonce nonce{};
|
Nonce nonce{};
|
||||||
|
|
||||||
increment_nonce_number(nonce.data(), 0x1F5);
|
increment_nonce_number(nonce.data(), 0x1F5);
|
||||||
|
@ -43,4 +48,24 @@ TEST(CryptoCore, IncrementNonceNumber)
|
||||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12, 0x34, 0x5A, 0x62}}));
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12, 0x34, 0x5A, 0x62}}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(CryptoCore, Signatures)
|
||||||
|
{
|
||||||
|
ExtPublicKey pk;
|
||||||
|
ExtSecretKey sk;
|
||||||
|
|
||||||
|
EXPECT_TRUE(create_extended_keypair(pk.data(), sk.data()));
|
||||||
|
|
||||||
|
std::vector<uint8_t> message;
|
||||||
|
|
||||||
|
// Try a few different sizes, including empty 0 length message.
|
||||||
|
for (uint8_t i = 0; i < 100; ++i) {
|
||||||
|
message.push_back(random_u08());
|
||||||
|
Signature signature;
|
||||||
|
EXPECT_TRUE(crypto_signature_create(
|
||||||
|
signature.data(), message.data(), message.size(), get_sig_sk(sk.data())));
|
||||||
|
EXPECT_TRUE(crypto_signature_verify(
|
||||||
|
signature.data(), message.data(), message.size(), get_sig_pk(pk.data())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
864
toxcore/group_moderation.c
Normal file
864
toxcore/group_moderation.c
Normal file
|
@ -0,0 +1,864 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
* Copyright © 2016-2020 The TokTok team.
|
||||||
|
* Copyright © 2015 Tox project.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of massive text only group chats.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "group_moderation.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "ccompat.h"
|
||||||
|
#include "crypto_core.h"
|
||||||
|
#include "mono_time.h"
|
||||||
|
#include "network.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
static_assert(MOD_SANCTIONS_CREDS_SIZE <= MAX_PACKET_SIZE_NO_HEADERS,
|
||||||
|
"MOD_SANCTIONS_CREDS_SIZE must be <= the maximum allowed payload size");
|
||||||
|
static_assert(MOD_MAX_NUM_SANCTIONS * MOD_SANCTION_PACKED_SIZE + MOD_SANCTIONS_CREDS_SIZE <= MAX_PACKET_SIZE_NO_HEADERS,
|
||||||
|
"MOD_MAX_NUM_SANCTIONS must be able to fit inside the maximum allowed payload size");
|
||||||
|
static_assert(MOD_MAX_NUM_MODERATORS * MOD_LIST_ENTRY_SIZE <= MAX_PACKET_SIZE_NO_HEADERS,
|
||||||
|
"MOD_MAX_NUM_MODERATORS must be able to fit insize the maximum allowed payload size");
|
||||||
|
|
||||||
|
uint16_t mod_list_packed_size(const Moderation *moderation)
|
||||||
|
{
|
||||||
|
return moderation->num_mods * MOD_LIST_ENTRY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mod_list_unpack(Moderation *moderation, const uint8_t *data, uint16_t length, uint16_t num_mods)
|
||||||
|
{
|
||||||
|
if (length < num_mods * MOD_LIST_ENTRY_SIZE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_list_cleanup(moderation);
|
||||||
|
|
||||||
|
if (num_mods == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t **tmp_list = (uint8_t **)calloc(num_mods, sizeof(uint8_t *));
|
||||||
|
|
||||||
|
if (tmp_list == nullptr) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t unpacked_len = 0;
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < num_mods; ++i) {
|
||||||
|
tmp_list[i] = (uint8_t *)malloc(sizeof(uint8_t) * MOD_LIST_ENTRY_SIZE);
|
||||||
|
|
||||||
|
if (tmp_list[i] == nullptr) {
|
||||||
|
free_uint8_t_pointer_array(tmp_list, i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(tmp_list[i], &data[i * MOD_LIST_ENTRY_SIZE], MOD_LIST_ENTRY_SIZE);
|
||||||
|
unpacked_len += MOD_LIST_ENTRY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
moderation->mod_list = tmp_list;
|
||||||
|
moderation->num_mods = num_mods;
|
||||||
|
|
||||||
|
return unpacked_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mod_list_pack(const Moderation *moderation, uint8_t *data)
|
||||||
|
{
|
||||||
|
for (uint16_t i = 0; i < moderation->num_mods; ++i) {
|
||||||
|
memcpy(&data[i * MOD_LIST_ENTRY_SIZE], moderation->mod_list[i], MOD_LIST_ENTRY_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mod_list_get_data_hash(uint8_t *hash, const uint8_t *packed_mod_list, uint16_t length)
|
||||||
|
{
|
||||||
|
crypto_sha256(hash, packed_mod_list, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mod_list_make_hash(const Moderation *moderation, uint8_t *hash)
|
||||||
|
{
|
||||||
|
if (moderation->num_mods == 0) {
|
||||||
|
memset(hash, 0, MOD_MODERATION_HASH_SIZE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t data_buf_size = mod_list_packed_size(moderation);
|
||||||
|
|
||||||
|
assert(data_buf_size > 0);
|
||||||
|
|
||||||
|
uint8_t *data = (uint8_t *)malloc(data_buf_size);
|
||||||
|
|
||||||
|
if (data == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod_list_pack(moderation, data);
|
||||||
|
|
||||||
|
mod_list_get_data_hash(hash, data, data_buf_size);
|
||||||
|
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns moderator list index for public_sig_key.
|
||||||
|
* Returns -1 if key is not in the list.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
static int mod_list_index_of_sig_pk(const Moderation *moderation, const uint8_t *public_sig_key)
|
||||||
|
{
|
||||||
|
for (uint16_t i = 0; i < moderation->num_mods; ++i) {
|
||||||
|
if (memcmp(moderation->mod_list[i], public_sig_key, SIG_PUBLIC_KEY_SIZE) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mod_list_verify_sig_pk(const Moderation *moderation, const uint8_t *sig_pk)
|
||||||
|
{
|
||||||
|
if (memcmp(moderation->founder_public_sig_key, sig_pk, SIG_PUBLIC_KEY_SIZE) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < moderation->num_mods; ++i) {
|
||||||
|
if (memcmp(moderation->mod_list[i], sig_pk, SIG_PUBLIC_KEY_SIZE) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mod_list_remove_index(Moderation *moderation, uint16_t index)
|
||||||
|
{
|
||||||
|
if (index >= moderation->num_mods) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((moderation->num_mods - 1) == 0) {
|
||||||
|
mod_list_cleanup(moderation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
--moderation->num_mods;
|
||||||
|
|
||||||
|
if (index != moderation->num_mods) {
|
||||||
|
memcpy(moderation->mod_list[index], moderation->mod_list[moderation->num_mods],
|
||||||
|
MOD_LIST_ENTRY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(moderation->mod_list[moderation->num_mods]);
|
||||||
|
moderation->mod_list[moderation->num_mods] = nullptr;
|
||||||
|
|
||||||
|
uint8_t **tmp_list = (uint8_t **)realloc(moderation->mod_list, moderation->num_mods * sizeof(uint8_t *));
|
||||||
|
|
||||||
|
if (tmp_list == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
moderation->mod_list = tmp_list;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mod_list_remove_entry(Moderation *moderation, const uint8_t *public_sig_key)
|
||||||
|
{
|
||||||
|
if (moderation->num_mods == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int idx = mod_list_index_of_sig_pk(moderation, public_sig_key);
|
||||||
|
|
||||||
|
if (idx == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(idx <= UINT16_MAX);
|
||||||
|
|
||||||
|
return mod_list_remove_index(moderation, (uint16_t)idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mod_list_add_entry(Moderation *moderation, const uint8_t *mod_data)
|
||||||
|
{
|
||||||
|
if (moderation->num_mods >= MOD_MAX_NUM_MODERATORS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t **tmp_list = (uint8_t **)realloc(moderation->mod_list, (moderation->num_mods + 1) * sizeof(uint8_t *));
|
||||||
|
|
||||||
|
if (tmp_list == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
moderation->mod_list = tmp_list;
|
||||||
|
|
||||||
|
tmp_list[moderation->num_mods] = (uint8_t *)malloc(sizeof(uint8_t) * MOD_LIST_ENTRY_SIZE);
|
||||||
|
|
||||||
|
if (tmp_list[moderation->num_mods] == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(tmp_list[moderation->num_mods], mod_data, MOD_LIST_ENTRY_SIZE);
|
||||||
|
++moderation->num_mods;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mod_list_cleanup(Moderation *moderation)
|
||||||
|
{
|
||||||
|
free_uint8_t_pointer_array(moderation->mod_list, moderation->num_mods);
|
||||||
|
moderation->num_mods = 0;
|
||||||
|
moderation->mod_list = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t sanctions_creds_pack(const Mod_Sanction_Creds *creds, uint8_t *data)
|
||||||
|
{
|
||||||
|
uint16_t packed_len = 0;
|
||||||
|
|
||||||
|
net_pack_u32(data + packed_len, creds->version);
|
||||||
|
packed_len += sizeof(uint32_t);
|
||||||
|
memcpy(data + packed_len, creds->hash, MOD_SANCTION_HASH_SIZE);
|
||||||
|
packed_len += MOD_SANCTION_HASH_SIZE;
|
||||||
|
net_pack_u16(data + packed_len, creds->checksum);
|
||||||
|
packed_len += sizeof(uint16_t);
|
||||||
|
memcpy(data + packed_len, creds->sig_pk, SIG_PUBLIC_KEY_SIZE);
|
||||||
|
packed_len += SIG_PUBLIC_KEY_SIZE;
|
||||||
|
memcpy(data + packed_len, creds->sig, SIGNATURE_SIZE);
|
||||||
|
packed_len += SIGNATURE_SIZE;
|
||||||
|
|
||||||
|
return packed_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t sanctions_list_packed_size(uint16_t num_sanctions)
|
||||||
|
{
|
||||||
|
return MOD_SANCTION_PACKED_SIZE * num_sanctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sanctions_list_pack(uint8_t *data, uint16_t length, const Mod_Sanction *sanctions, uint16_t num_sanctions,
|
||||||
|
const Mod_Sanction_Creds *creds)
|
||||||
|
{
|
||||||
|
assert(sanctions != nullptr || num_sanctions == 0);
|
||||||
|
assert(sanctions != nullptr || creds != nullptr);
|
||||||
|
|
||||||
|
uint16_t packed_len = 0;
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < num_sanctions; ++i) {
|
||||||
|
if (packed_len + sizeof(uint8_t) + SIG_PUBLIC_KEY_SIZE + TIME_STAMP_SIZE > length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data + packed_len, &sanctions[i].type, sizeof(uint8_t));
|
||||||
|
packed_len += sizeof(uint8_t);
|
||||||
|
memcpy(data + packed_len, sanctions[i].setter_public_sig_key, SIG_PUBLIC_KEY_SIZE);
|
||||||
|
packed_len += SIG_PUBLIC_KEY_SIZE;
|
||||||
|
net_pack_u64(data + packed_len, sanctions[i].time_set);
|
||||||
|
packed_len += TIME_STAMP_SIZE;
|
||||||
|
|
||||||
|
const uint8_t sanctions_type = sanctions[i].type;
|
||||||
|
|
||||||
|
if (sanctions_type == SA_OBSERVER) {
|
||||||
|
if (packed_len + ENC_PUBLIC_KEY_SIZE > length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data + packed_len, sanctions[i].target_public_enc_key, ENC_PUBLIC_KEY_SIZE);
|
||||||
|
packed_len += ENC_PUBLIC_KEY_SIZE;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packed_len + SIGNATURE_SIZE > length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signature must be packed last */
|
||||||
|
memcpy(data + packed_len, sanctions[i].signature, SIGNATURE_SIZE);
|
||||||
|
packed_len += SIGNATURE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creds == nullptr) {
|
||||||
|
return packed_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length < packed_len || length - packed_len < MOD_SANCTIONS_CREDS_SIZE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t cred_len = sanctions_creds_pack(creds, data + packed_len);
|
||||||
|
|
||||||
|
if (cred_len != MOD_SANCTIONS_CREDS_SIZE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)(packed_len + cred_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t sanctions_creds_unpack(Mod_Sanction_Creds *creds, const uint8_t *data)
|
||||||
|
{
|
||||||
|
uint16_t len_processed = 0;
|
||||||
|
|
||||||
|
net_unpack_u32(data + len_processed, &creds->version);
|
||||||
|
len_processed += sizeof(uint32_t);
|
||||||
|
memcpy(creds->hash, data + len_processed, MOD_SANCTION_HASH_SIZE);
|
||||||
|
len_processed += MOD_SANCTION_HASH_SIZE;
|
||||||
|
net_unpack_u16(data + len_processed, &creds->checksum);
|
||||||
|
len_processed += sizeof(uint16_t);
|
||||||
|
memcpy(creds->sig_pk, data + len_processed, SIG_PUBLIC_KEY_SIZE);
|
||||||
|
len_processed += SIG_PUBLIC_KEY_SIZE;
|
||||||
|
memcpy(creds->sig, data + len_processed, SIGNATURE_SIZE);
|
||||||
|
len_processed += SIGNATURE_SIZE;
|
||||||
|
|
||||||
|
return len_processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sanctions_list_unpack(Mod_Sanction *sanctions, Mod_Sanction_Creds *creds, uint16_t max_sanctions,
|
||||||
|
const uint8_t *data, uint16_t length, uint16_t *processed_data_len)
|
||||||
|
{
|
||||||
|
uint16_t num = 0;
|
||||||
|
uint16_t len_processed = 0;
|
||||||
|
|
||||||
|
while (num < max_sanctions && num < MOD_MAX_NUM_SANCTIONS && len_processed < length) {
|
||||||
|
if (len_processed + sizeof(uint8_t) + SIG_PUBLIC_KEY_SIZE + TIME_STAMP_SIZE > length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&sanctions[num].type, data + len_processed, sizeof(uint8_t));
|
||||||
|
len_processed += sizeof(uint8_t);
|
||||||
|
memcpy(sanctions[num].setter_public_sig_key, data + len_processed, SIG_PUBLIC_KEY_SIZE);
|
||||||
|
len_processed += SIG_PUBLIC_KEY_SIZE;
|
||||||
|
net_unpack_u64(data + len_processed, &sanctions[num].time_set);
|
||||||
|
len_processed += TIME_STAMP_SIZE;
|
||||||
|
|
||||||
|
if (sanctions[num].type == SA_OBSERVER) {
|
||||||
|
if (len_processed + ENC_PUBLIC_KEY_SIZE > length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(sanctions[num].target_public_enc_key, data + len_processed, ENC_PUBLIC_KEY_SIZE);
|
||||||
|
len_processed += ENC_PUBLIC_KEY_SIZE;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len_processed + SIGNATURE_SIZE > length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(sanctions[num].signature, data + len_processed, SIGNATURE_SIZE);
|
||||||
|
len_processed += SIGNATURE_SIZE;
|
||||||
|
|
||||||
|
++num;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length <= len_processed || length - len_processed < MOD_SANCTIONS_CREDS_SIZE) {
|
||||||
|
if (length != len_processed) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processed_data_len != nullptr) {
|
||||||
|
*processed_data_len = len_processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t creds_len = sanctions_creds_unpack(creds, data + len_processed);
|
||||||
|
|
||||||
|
if (creds_len != MOD_SANCTIONS_CREDS_SIZE) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processed_data_len != nullptr) {
|
||||||
|
*processed_data_len = len_processed + creds_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** @brief Creates a new sanction list hash and puts it in hash.
|
||||||
|
*
|
||||||
|
* The hash is derived from the signature of all entries plus the version number.
|
||||||
|
* hash must have room for at least MOD_SANCTION_HASH_SIZE bytes.
|
||||||
|
*
|
||||||
|
* If num_sanctions is 0 the hash is zeroed.
|
||||||
|
*
|
||||||
|
* Return true on success.
|
||||||
|
*/
|
||||||
|
non_null(4) nullable(1)
|
||||||
|
static bool sanctions_list_make_hash(const Mod_Sanction *sanctions, uint32_t new_version, uint16_t num_sanctions,
|
||||||
|
uint8_t *hash)
|
||||||
|
{
|
||||||
|
if (num_sanctions == 0 || sanctions == nullptr) {
|
||||||
|
memset(hash, 0, MOD_SANCTION_HASH_SIZE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t sig_data_size = num_sanctions * SIGNATURE_SIZE;
|
||||||
|
const size_t data_buf_size = sig_data_size + sizeof(uint32_t);
|
||||||
|
|
||||||
|
// check for integer overflower
|
||||||
|
if (data_buf_size < num_sanctions) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *data = (uint8_t *)malloc(data_buf_size);
|
||||||
|
|
||||||
|
if (data == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < num_sanctions; ++i) {
|
||||||
|
memcpy(&data[i * SIGNATURE_SIZE], sanctions[i].signature, SIGNATURE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&data[sig_data_size], &new_version, sizeof(uint32_t));
|
||||||
|
crypto_sha256(hash, data, data_buf_size);
|
||||||
|
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Verifies that sanction contains valid info and was assigned by a current mod or group founder.
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
static bool sanctions_list_validate_entry(const Moderation *moderation, const Mod_Sanction *sanction)
|
||||||
|
{
|
||||||
|
if (!mod_list_verify_sig_pk(moderation, sanction->setter_public_sig_key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sanction->type >= SA_INVALID) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sanction->time_set == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t packed_data[MOD_SANCTION_PACKED_SIZE];
|
||||||
|
const int packed_len = sanctions_list_pack(packed_data, sizeof(packed_data), sanction, 1, nullptr);
|
||||||
|
|
||||||
|
if (packed_len <= (int) SIGNATURE_SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto_signature_verify(sanction->signature, packed_data, packed_len - SIGNATURE_SIZE,
|
||||||
|
sanction->setter_public_sig_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
non_null()
|
||||||
|
static uint16_t sanctions_creds_get_checksum(const Mod_Sanction_Creds *creds)
|
||||||
|
{
|
||||||
|
return data_checksum(creds->hash, sizeof(creds->hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
non_null()
|
||||||
|
static void sanctions_creds_set_checksum(Mod_Sanction_Creds *creds)
|
||||||
|
{
|
||||||
|
creds->checksum = sanctions_creds_get_checksum(creds);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sanctions_list_make_creds(Moderation *moderation)
|
||||||
|
{
|
||||||
|
const Mod_Sanction_Creds old_creds = moderation->sanctions_creds;
|
||||||
|
|
||||||
|
++moderation->sanctions_creds.version;
|
||||||
|
|
||||||
|
memcpy(moderation->sanctions_creds.sig_pk, moderation->self_public_sig_key, SIG_PUBLIC_KEY_SIZE);
|
||||||
|
|
||||||
|
uint8_t hash[MOD_SANCTION_HASH_SIZE];
|
||||||
|
|
||||||
|
if (!sanctions_list_make_hash(moderation->sanctions, moderation->sanctions_creds.version,
|
||||||
|
moderation->num_sanctions, hash)) {
|
||||||
|
moderation->sanctions_creds = old_creds;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(moderation->sanctions_creds.hash, hash, MOD_SANCTION_HASH_SIZE);
|
||||||
|
|
||||||
|
sanctions_creds_set_checksum(&moderation->sanctions_creds);
|
||||||
|
|
||||||
|
if (!crypto_signature_create(moderation->sanctions_creds.sig, moderation->sanctions_creds.hash,
|
||||||
|
MOD_SANCTION_HASH_SIZE, moderation->self_secret_sig_key)) {
|
||||||
|
moderation->sanctions_creds = old_creds;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Validates sanction list credentials.
|
||||||
|
*
|
||||||
|
* Verifies that:
|
||||||
|
* - the public signature key belongs to a mod or the founder
|
||||||
|
* - the signature for the hash was made by the owner of the public signature key.
|
||||||
|
* - the received hash matches our own hash of the new sanctions list
|
||||||
|
* - the received checksum matches the received hash
|
||||||
|
* - the new version is >= our current version
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null(1, 3) nullable(2)
|
||||||
|
static bool sanctions_creds_validate(const Moderation *moderation, const Mod_Sanction *sanctions,
|
||||||
|
const Mod_Sanction_Creds *creds, uint16_t num_sanctions)
|
||||||
|
{
|
||||||
|
if (!mod_list_verify_sig_pk(moderation, creds->sig_pk)) {
|
||||||
|
LOGGER_WARNING(moderation->log, "Invalid credentials signature pk");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t hash[MOD_SANCTION_HASH_SIZE];
|
||||||
|
|
||||||
|
if (!sanctions_list_make_hash(sanctions, creds->version, num_sanctions, hash)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(hash, creds->hash, MOD_SANCTION_HASH_SIZE) != 0) {
|
||||||
|
LOGGER_WARNING(moderation->log, "Invalid credentials hash");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creds->checksum != sanctions_creds_get_checksum(creds)) {
|
||||||
|
LOGGER_WARNING(moderation->log, "Invalid credentials checksum");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moderation->shared_state_version > 0) {
|
||||||
|
if ((creds->version < moderation->sanctions_creds.version)
|
||||||
|
&& !(creds->version == 0 && moderation->sanctions_creds.version == UINT32_MAX)) {
|
||||||
|
LOGGER_WARNING(moderation->log, "Invalid version");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!crypto_signature_verify(creds->sig, hash, MOD_SANCTION_HASH_SIZE, creds->sig_pk)) {
|
||||||
|
LOGGER_WARNING(moderation->log, "Invalid signature");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sanctions_list_check_integrity(const Moderation *moderation, const Mod_Sanction_Creds *creds,
|
||||||
|
const Mod_Sanction *sanctions, uint16_t num_sanctions)
|
||||||
|
{
|
||||||
|
for (uint16_t i = 0; i < num_sanctions; ++i) {
|
||||||
|
if (!sanctions_list_validate_entry(moderation, &sanctions[i])) {
|
||||||
|
LOGGER_WARNING(moderation->log, "Invalid entry");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanctions_creds_validate(moderation, sanctions, creds, num_sanctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Validates a sanctions list if credentials are supplied. If successful,
|
||||||
|
* or if no credentials are supplid, assigns new sanctions list and credentials
|
||||||
|
* to moderation object.
|
||||||
|
*
|
||||||
|
* @param moderation The moderation object being operated on.
|
||||||
|
* @param new_sanctions The sanctions list to validate and assign to moderation object.
|
||||||
|
* @param new_creds The new sanctions credentials to be assigned to moderation object.
|
||||||
|
* @param num_sanctions The number of sanctions in the sanctions list.
|
||||||
|
*
|
||||||
|
* @retval false if sanctions credentials validation fails.
|
||||||
|
*/
|
||||||
|
non_null(1, 2) nullable(3)
|
||||||
|
static bool sanctions_apply_new(Moderation *moderation, Mod_Sanction *new_sanctions,
|
||||||
|
const Mod_Sanction_Creds *new_creds,
|
||||||
|
uint16_t num_sanctions)
|
||||||
|
{
|
||||||
|
if (new_creds != nullptr) {
|
||||||
|
if (!sanctions_creds_validate(moderation, new_sanctions, new_creds, num_sanctions)) {
|
||||||
|
LOGGER_WARNING(moderation->log, "Failed to validate credentials");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
moderation->sanctions_creds = *new_creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
sanctions_list_cleanup(moderation);
|
||||||
|
moderation->sanctions = new_sanctions;
|
||||||
|
moderation->num_sanctions = num_sanctions;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Returns a copy of the sanctions list. The caller is responsible for freeing the
|
||||||
|
* memory returned by this function.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
static Mod_Sanction *sanctions_list_copy(const Mod_Sanction *sanctions, uint16_t num_sanctions)
|
||||||
|
{
|
||||||
|
Mod_Sanction *copy = (Mod_Sanction *)calloc(num_sanctions, sizeof(Mod_Sanction));
|
||||||
|
|
||||||
|
if (copy == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(copy, sanctions, num_sanctions * sizeof(Mod_Sanction));
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Removes index-th sanction list entry.
|
||||||
|
*
|
||||||
|
* New credentials will be validated if creds is non-null.
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null(1) nullable(3)
|
||||||
|
static bool sanctions_list_remove_index(Moderation *moderation, uint16_t index, const Mod_Sanction_Creds *creds)
|
||||||
|
{
|
||||||
|
if (index >= moderation->num_sanctions) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t new_num = moderation->num_sanctions - 1;
|
||||||
|
|
||||||
|
if (new_num == 0) {
|
||||||
|
if (creds != nullptr) {
|
||||||
|
if (!sanctions_creds_validate(moderation, nullptr, creds, 0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
moderation->sanctions_creds = *creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
sanctions_list_cleanup(moderation);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Operate on a copy of the list in case something goes wrong. */
|
||||||
|
Mod_Sanction *sanctions_copy = sanctions_list_copy(moderation->sanctions, moderation->num_sanctions);
|
||||||
|
|
||||||
|
if (sanctions_copy == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index != new_num) {
|
||||||
|
sanctions_copy[index] = sanctions_copy[new_num];
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod_Sanction *new_list = (Mod_Sanction *)realloc(sanctions_copy, new_num * sizeof(Mod_Sanction));
|
||||||
|
|
||||||
|
if (new_list == nullptr) {
|
||||||
|
free(sanctions_copy);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sanctions_apply_new(moderation, new_list, creds, new_num)) {
|
||||||
|
free(new_list);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sanctions_list_remove_observer(Moderation *moderation, const uint8_t *public_key,
|
||||||
|
const Mod_Sanction_Creds *creds)
|
||||||
|
{
|
||||||
|
for (uint16_t i = 0; i < moderation->num_sanctions; ++i) {
|
||||||
|
const Mod_Sanction *curr_sanction = &moderation->sanctions[i];
|
||||||
|
|
||||||
|
if (curr_sanction->type != SA_OBSERVER) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(public_key, curr_sanction->target_public_enc_key, ENC_PUBLIC_KEY_SIZE) == 0) {
|
||||||
|
if (!sanctions_list_remove_index(moderation, i, creds)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creds == nullptr) {
|
||||||
|
return sanctions_list_make_creds(moderation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sanctions_list_is_observer(const Moderation *moderation, const uint8_t *public_key)
|
||||||
|
{
|
||||||
|
for (uint16_t i = 0; i < moderation->num_sanctions; ++i) {
|
||||||
|
const Mod_Sanction *curr_sanction = &moderation->sanctions[i];
|
||||||
|
|
||||||
|
if (curr_sanction->type != SA_OBSERVER) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(curr_sanction->target_public_enc_key, public_key, ENC_PUBLIC_KEY_SIZE) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sanctions_list_entry_exists(const Moderation *moderation, const Mod_Sanction *sanction)
|
||||||
|
{
|
||||||
|
if (sanction->type == SA_OBSERVER) {
|
||||||
|
return sanctions_list_is_observer(moderation, sanction->target_public_enc_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sanctions_list_add_entry(Moderation *moderation, const Mod_Sanction *sanction, const Mod_Sanction_Creds *creds)
|
||||||
|
{
|
||||||
|
if (moderation->num_sanctions >= MOD_MAX_NUM_SANCTIONS) {
|
||||||
|
LOGGER_WARNING(moderation->log, "num_sanctions %d exceeds maximum", moderation->num_sanctions);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sanctions_list_validate_entry(moderation, sanction)) {
|
||||||
|
LOGGER_ERROR(moderation->log, "Failed to validate sanction");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sanctions_list_entry_exists(moderation, sanction)) {
|
||||||
|
LOGGER_WARNING(moderation->log, "Attempted to add duplicate sanction");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Operate on a copy of the list in case something goes wrong. */
|
||||||
|
Mod_Sanction *sanctions_copy = nullptr;
|
||||||
|
|
||||||
|
if (moderation->num_sanctions > 0) {
|
||||||
|
sanctions_copy = sanctions_list_copy(moderation->sanctions, moderation->num_sanctions);
|
||||||
|
|
||||||
|
if (sanctions_copy == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t index = moderation->num_sanctions;
|
||||||
|
Mod_Sanction *new_list = (Mod_Sanction *)realloc(sanctions_copy, (index + 1) * sizeof(Mod_Sanction));
|
||||||
|
|
||||||
|
if (new_list == nullptr) {
|
||||||
|
free(sanctions_copy);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_list[index] = *sanction;
|
||||||
|
|
||||||
|
if (!sanctions_apply_new(moderation, new_list, creds, index + 1)) {
|
||||||
|
free(new_list);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Signs packed sanction data.
|
||||||
|
*
|
||||||
|
* This function must be called by the owner of the entry's public_sig_key.
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
static bool sanctions_list_sign_entry(const Moderation *moderation, Mod_Sanction *sanction)
|
||||||
|
{
|
||||||
|
uint8_t packed_data[MOD_SANCTION_PACKED_SIZE];
|
||||||
|
const int packed_len = sanctions_list_pack(packed_data, sizeof(packed_data), sanction, 1, nullptr);
|
||||||
|
|
||||||
|
if (packed_len <= (int) SIGNATURE_SIZE) {
|
||||||
|
LOGGER_ERROR(moderation->log, "Failed to pack sanctions list: %d", packed_len);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto_signature_create(sanction->signature, packed_data, packed_len - SIGNATURE_SIZE,
|
||||||
|
moderation->self_secret_sig_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sanctions_list_make_entry(Moderation *moderation, const uint8_t *public_key, Mod_Sanction *sanction,
|
||||||
|
uint8_t type)
|
||||||
|
{
|
||||||
|
*sanction = (Mod_Sanction) {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type == SA_OBSERVER) {
|
||||||
|
memcpy(sanction->target_public_enc_key, public_key, ENC_PUBLIC_KEY_SIZE);
|
||||||
|
} else {
|
||||||
|
LOGGER_ERROR(moderation->log, "Tried to create sanction with invalid type: %u", type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(sanction->setter_public_sig_key, moderation->self_public_sig_key, SIG_PUBLIC_KEY_SIZE);
|
||||||
|
|
||||||
|
sanction->time_set = (uint64_t)time(nullptr);
|
||||||
|
sanction->type = type;
|
||||||
|
|
||||||
|
if (!sanctions_list_sign_entry(moderation, sanction)) {
|
||||||
|
LOGGER_ERROR(moderation->log, "Failed to sign sanction");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sanctions_list_add_entry(moderation, sanction, nullptr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sanctions_list_make_creds(moderation)) {
|
||||||
|
LOGGER_ERROR(moderation->log, "Failed to make credentials for new sanction");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
uint16_t sanctions_list_replace_sig(Moderation *moderation, const uint8_t *public_sig_key)
|
||||||
|
{
|
||||||
|
uint16_t count = 0;
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < moderation->num_sanctions; ++i) {
|
||||||
|
if (memcmp(moderation->sanctions[i].setter_public_sig_key, public_sig_key, SIG_PUBLIC_KEY_SIZE) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(moderation->sanctions[i].setter_public_sig_key, moderation->self_public_sig_key, SIG_PUBLIC_KEY_SIZE);
|
||||||
|
|
||||||
|
if (!sanctions_list_sign_entry(moderation, &moderation->sanctions[i])) {
|
||||||
|
LOGGER_ERROR(moderation->log, "Failed to sign sanction");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
if (!sanctions_list_make_creds(moderation)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sanctions_list_cleanup(Moderation *moderation)
|
||||||
|
{
|
||||||
|
if (moderation->sanctions != nullptr) {
|
||||||
|
free(moderation->sanctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
moderation->sanctions = nullptr;
|
||||||
|
moderation->num_sanctions = 0;
|
||||||
|
}
|
288
toxcore/group_moderation.h
Normal file
288
toxcore/group_moderation.h
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
* Copyright © 2016-2020 The TokTok team.
|
||||||
|
* Copyright © 2015 Tox project.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of massive text only group chats.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef C_TOXCORE_TOXCORE_GROUP_MODERATION_H
|
||||||
|
#define C_TOXCORE_TOXCORE_GROUP_MODERATION_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "DHT.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MOD_MODERATION_HASH_SIZE CRYPTO_SHA256_SIZE
|
||||||
|
#define MOD_LIST_ENTRY_SIZE SIG_PUBLIC_KEY_SIZE
|
||||||
|
#define MOD_SANCTION_HASH_SIZE CRYPTO_SHA256_SIZE
|
||||||
|
|
||||||
|
#define TIME_STAMP_SIZE sizeof(uint64_t)
|
||||||
|
|
||||||
|
/* The packed size of a Mod_Sanction_Creds */
|
||||||
|
#define MOD_SANCTIONS_CREDS_SIZE (sizeof(uint32_t) + MOD_SANCTION_HASH_SIZE + sizeof(uint16_t) +\
|
||||||
|
SIG_PUBLIC_KEY_SIZE + SIGNATURE_SIZE)
|
||||||
|
|
||||||
|
/* The packed size of a Mod_Sanction */
|
||||||
|
#define MOD_SANCTION_PACKED_SIZE (SIG_PUBLIC_KEY_SIZE + TIME_STAMP_SIZE + 1 + ENC_PUBLIC_KEY_SIZE + SIGNATURE_SIZE)
|
||||||
|
|
||||||
|
/* The max size of a groupchat packet with 100 bytes reserved for header data */
|
||||||
|
#define MAX_PACKET_SIZE_NO_HEADERS 49900
|
||||||
|
|
||||||
|
/* These values must take into account the maximum allowed packet size and headers. */
|
||||||
|
#define MOD_MAX_NUM_MODERATORS (((MAX_PACKET_SIZE_NO_HEADERS) / (MOD_LIST_ENTRY_SIZE)))
|
||||||
|
#define MOD_MAX_NUM_SANCTIONS (((MAX_PACKET_SIZE_NO_HEADERS - (MOD_SANCTIONS_CREDS_SIZE)) / (MOD_SANCTION_PACKED_SIZE)))
|
||||||
|
|
||||||
|
typedef enum Mod_Sanction_Type {
|
||||||
|
SA_OBSERVER = 0x00,
|
||||||
|
SA_INVALID = 0x01,
|
||||||
|
} Mod_Sanction_Type;
|
||||||
|
|
||||||
|
typedef struct Mod_Sanction_Creds {
|
||||||
|
uint32_t version;
|
||||||
|
uint8_t hash[MOD_SANCTION_HASH_SIZE]; // hash of all sanctions list signatures + version
|
||||||
|
uint16_t checksum; // a sum of the hash
|
||||||
|
uint8_t sig_pk[SIG_PUBLIC_KEY_SIZE]; // Last mod to have modified the sanctions list
|
||||||
|
uint8_t sig[SIGNATURE_SIZE]; // signature of hash, signed by sig_pk
|
||||||
|
} Mod_Sanction_Creds;
|
||||||
|
|
||||||
|
/** Holds data pertaining to a peer who has been sanctioned. */
|
||||||
|
typedef struct Mod_Sanction {
|
||||||
|
uint8_t setter_public_sig_key[SIG_PUBLIC_KEY_SIZE];
|
||||||
|
|
||||||
|
// TODO(Jfreegman): This timestamp can potentially be used to track a user across
|
||||||
|
// different group chats if they're a moderator and set many sanctions across the
|
||||||
|
// different groups. This should be addressed in the future.
|
||||||
|
uint64_t time_set;
|
||||||
|
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t target_public_enc_key[ENC_PUBLIC_KEY_SIZE];
|
||||||
|
|
||||||
|
/* Signature of all above packed data signed by the owner of public_sig_key */
|
||||||
|
uint8_t signature[SIGNATURE_SIZE];
|
||||||
|
} Mod_Sanction;
|
||||||
|
|
||||||
|
typedef struct Moderation {
|
||||||
|
const Logger *log;
|
||||||
|
|
||||||
|
Mod_Sanction *sanctions;
|
||||||
|
uint16_t num_sanctions;
|
||||||
|
|
||||||
|
Mod_Sanction_Creds sanctions_creds;
|
||||||
|
|
||||||
|
uint8_t **mod_list; // array of public signature keys of all the mods
|
||||||
|
uint16_t num_mods;
|
||||||
|
|
||||||
|
// copies from parent/sibling chat/shared state objects
|
||||||
|
uint8_t founder_public_sig_key[SIG_PUBLIC_KEY_SIZE];
|
||||||
|
uint8_t self_public_sig_key[SIG_PUBLIC_KEY_SIZE];
|
||||||
|
uint8_t self_secret_sig_key[SIG_SECRET_KEY_SIZE];
|
||||||
|
uint32_t shared_state_version;
|
||||||
|
} Moderation;
|
||||||
|
|
||||||
|
/** @brief Returns the size in bytes of the packed moderation list. */
|
||||||
|
non_null()
|
||||||
|
uint16_t mod_list_packed_size(const Moderation *moderation);
|
||||||
|
|
||||||
|
/** @brief Unpacks data into the moderator list.
|
||||||
|
*
|
||||||
|
* @param data should contain num_mods entries of size MOD_LIST_ENTRY_SIZE.
|
||||||
|
*
|
||||||
|
* Returns length of unpacked data on success.
|
||||||
|
* Returns -1 on failure.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
int mod_list_unpack(Moderation *moderation, const uint8_t *data, uint16_t length, uint16_t num_mods);
|
||||||
|
|
||||||
|
/** @brief Packs moderator list into data.
|
||||||
|
* @param data must have room for the number of bytes returned by `mod_list_packed_size`.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
void mod_list_pack(const Moderation *moderation, uint8_t *data);
|
||||||
|
|
||||||
|
/** @brief Creates a new moderator list hash and puts it in `hash`.
|
||||||
|
*
|
||||||
|
* @param hash must have room for at least MOD_MODERATION_HASH_SIZE bytes.
|
||||||
|
*
|
||||||
|
* If num_mods is 0 the hash is zeroed.
|
||||||
|
*
|
||||||
|
* Returns true on sucess.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool mod_list_make_hash(const Moderation *moderation, uint8_t *hash);
|
||||||
|
|
||||||
|
/** @brief Puts a sha256 hash of `packed_mod_list` of `length` bytes in `hash`.
|
||||||
|
*
|
||||||
|
* @param hash must have room for at least MOD_MODERATION_HASH_SIZE bytes.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
void mod_list_get_data_hash(uint8_t *hash, const uint8_t *packed_mod_list, uint16_t length);
|
||||||
|
|
||||||
|
/** @brief Removes moderator at index-th position in the moderator list.
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool mod_list_remove_index(Moderation *moderation, uint16_t index);
|
||||||
|
|
||||||
|
/** @brief Removes public_sig_key from the moderator list.
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool mod_list_remove_entry(Moderation *moderation, const uint8_t *public_sig_key);
|
||||||
|
|
||||||
|
/** @brief Adds a mod to the moderator list.
|
||||||
|
*
|
||||||
|
* @param mod_data must be MOD_LIST_ENTRY_SIZE bytes.
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool mod_list_add_entry(Moderation *moderation, const uint8_t *mod_data);
|
||||||
|
|
||||||
|
/** @return true if the public signature key belongs to a moderator or the founder */
|
||||||
|
non_null()
|
||||||
|
bool mod_list_verify_sig_pk(const Moderation *moderation, const uint8_t *sig_pk);
|
||||||
|
|
||||||
|
/** @brief Frees all memory associated with the moderator list and sets num_mods to 0. */
|
||||||
|
nullable(1)
|
||||||
|
void mod_list_cleanup(Moderation *moderation);
|
||||||
|
|
||||||
|
/** @brief Returns the size in bytes of num_sanctions packed sanctions. */
|
||||||
|
uint16_t sanctions_list_packed_size(uint16_t num_sanctions);
|
||||||
|
|
||||||
|
/** @brief Packs sanctions into data. Additionally packs the sanctions credentials into creds.
|
||||||
|
*
|
||||||
|
* @param data The byte array being packed. Must have room for the number of bytes returned
|
||||||
|
* by `sanctions_list_packed_size`.
|
||||||
|
* @param length The size of the byte array.
|
||||||
|
* @param sanctions The sanctions list.
|
||||||
|
* @param num_sanctions The number of sanctions in the sanctions list. This value must be the same
|
||||||
|
* value used when calling `sanctions_list_packed_size`.
|
||||||
|
* @param creds The credentials object to fill.
|
||||||
|
*
|
||||||
|
* @retval The length of packed data on success.
|
||||||
|
* @retval -1 on failure.
|
||||||
|
*/
|
||||||
|
non_null(1) nullable(3, 5)
|
||||||
|
int sanctions_list_pack(uint8_t *data, uint16_t length, const Mod_Sanction *sanctions, uint16_t num_sanctions,
|
||||||
|
const Mod_Sanction_Creds *creds);
|
||||||
|
|
||||||
|
/** @brief Unpacks sanctions and new sanctions credentials.
|
||||||
|
*
|
||||||
|
* @param sanctions The sanctions array the sanctions data is unpacked into.
|
||||||
|
* @param creds The creds object the creds data is unpacked into.
|
||||||
|
* @param max_sanctions The maximum number of sanctions that the sanctions array can hold.
|
||||||
|
* @param data The packed data array.
|
||||||
|
* @param length The size of the packed data.
|
||||||
|
* @param processed_data_len If non-null, will contain the number of processed bytes on success.
|
||||||
|
*
|
||||||
|
* @retval The number of unpacked entries on success.
|
||||||
|
* @retval -1 on failure.
|
||||||
|
*/
|
||||||
|
non_null(1, 2, 4) nullable(6)
|
||||||
|
int sanctions_list_unpack(Mod_Sanction *sanctions, Mod_Sanction_Creds *creds, uint16_t max_sanctions,
|
||||||
|
const uint8_t *data, uint16_t length, uint16_t *processed_data_len);
|
||||||
|
|
||||||
|
/** @brief Packs sanction list credentials into data.
|
||||||
|
*
|
||||||
|
* @param data must have room for MOD_SANCTIONS_CREDS_SIZE bytes.
|
||||||
|
*
|
||||||
|
* Returns length of packed data.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
uint16_t sanctions_creds_pack(const Mod_Sanction_Creds *creds, uint8_t *data);
|
||||||
|
|
||||||
|
/** @brief Unpacks sanctions credentials into creds from data.
|
||||||
|
*
|
||||||
|
* @param data must have room for MOD_SANCTIONS_CREDS_SIZE bytes.
|
||||||
|
*
|
||||||
|
* Returns the length of the data processed.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
uint16_t sanctions_creds_unpack(Mod_Sanction_Creds *creds, const uint8_t *data);
|
||||||
|
|
||||||
|
/** @brief Updates sanction list credentials.
|
||||||
|
*
|
||||||
|
* Increment version, replace sig_pk with your own, update hash to reflect new
|
||||||
|
* sanction list, and sign new hash signature.
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool sanctions_list_make_creds(Moderation *moderation);
|
||||||
|
|
||||||
|
/** @brief Validates all sanctions list entries as well as the list itself.
|
||||||
|
*
|
||||||
|
* Returns true if all entries are valid.
|
||||||
|
* Returns false if one or more entries are invalid.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool sanctions_list_check_integrity(const Moderation *moderation, const Mod_Sanction_Creds *creds,
|
||||||
|
const Mod_Sanction *sanctions, uint16_t num_sanctions);
|
||||||
|
|
||||||
|
/** @brief Adds an entry to the sanctions list.
|
||||||
|
*
|
||||||
|
* The entry is first validated and the resulting new sanction list is
|
||||||
|
* compared against the new credentials.
|
||||||
|
*
|
||||||
|
* Entries must be unique.
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null(1, 2) nullable(3)
|
||||||
|
bool sanctions_list_add_entry(Moderation *moderation, const Mod_Sanction *sanction, const Mod_Sanction_Creds *creds);
|
||||||
|
|
||||||
|
/** @brief Creates a new sanction entry for `public_key` where type is one of Mod_Sanction_Type.
|
||||||
|
*
|
||||||
|
* New entry is signed and placed in the sanctions list.
|
||||||
|
*
|
||||||
|
* Returns true on success.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
bool sanctions_list_make_entry(Moderation *moderation, const uint8_t *public_key, Mod_Sanction *sanction,
|
||||||
|
uint8_t type);
|
||||||
|
|
||||||
|
/** @return true if public key is in the observer list. */
|
||||||
|
non_null()
|
||||||
|
bool sanctions_list_is_observer(const Moderation *moderation, const uint8_t *public_key);
|
||||||
|
|
||||||
|
/** @return true if sanction already exists in the sanctions list. */
|
||||||
|
non_null()
|
||||||
|
bool sanctions_list_entry_exists(const Moderation *moderation, const Mod_Sanction *sanction);
|
||||||
|
|
||||||
|
/** @brief Removes observer entry for public key from sanction list.
|
||||||
|
*
|
||||||
|
* If creds is NULL we make new credentials (this should only be done by a moderator or founder)
|
||||||
|
*
|
||||||
|
* Returns false on failure or if entry was not found.
|
||||||
|
*/
|
||||||
|
non_null(1, 2) nullable(3)
|
||||||
|
bool sanctions_list_remove_observer(Moderation *moderation, const uint8_t *public_key,
|
||||||
|
const Mod_Sanction_Creds *creds);
|
||||||
|
|
||||||
|
/** @brief Replaces all sanctions list signatures made by public_sig_key with the caller's.
|
||||||
|
*
|
||||||
|
* This is called whenever the founder demotes a moderator.
|
||||||
|
*
|
||||||
|
* Returns the number of entries re-signed.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
uint16_t sanctions_list_replace_sig(Moderation *moderation, const uint8_t *public_sig_key);
|
||||||
|
|
||||||
|
non_null()
|
||||||
|
void sanctions_list_cleanup(Moderation *moderation);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // C_TOXCORE_TOXCORE_GROUP_MODERATION_H
|
38
toxcore/group_moderation_fuzz_test.cc
Normal file
38
toxcore/group_moderation_fuzz_test.cc
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#include "group_moderation.h"
|
||||||
|
|
||||||
|
#include "../testing/fuzzing/fuzz_support.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void TestModListUnpack(Fuzz_Data input)
|
||||||
|
{
|
||||||
|
CONSUME1_OR_RETURN(const uint16_t num_mods, input);
|
||||||
|
Moderation mods{};
|
||||||
|
mod_list_unpack(&mods, input.data, input.size, num_mods);
|
||||||
|
mod_list_cleanup(&mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSanctionsListUnpack(Fuzz_Data input)
|
||||||
|
{
|
||||||
|
Mod_Sanction sanctions[10];
|
||||||
|
Mod_Sanction_Creds creds;
|
||||||
|
uint16_t processed_data_len;
|
||||||
|
sanctions_list_unpack(sanctions, &creds, 10, input.data, input.size, &processed_data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSanctionCredsUnpack(Fuzz_Data input)
|
||||||
|
{
|
||||||
|
CONSUME_OR_RETURN(const uint8_t *data, input, MOD_SANCTIONS_CREDS_SIZE);
|
||||||
|
Mod_Sanction_Creds creds;
|
||||||
|
sanctions_creds_unpack(&creds, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||||
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
fuzz_select_target(
|
||||||
|
data, size, TestModListUnpack, TestSanctionsListUnpack, TestSanctionCredsUnpack);
|
||||||
|
return 0;
|
||||||
|
}
|
248
toxcore/group_moderation_test.cc
Normal file
248
toxcore/group_moderation_test.cc
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
#include "group_moderation.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "crypto_core.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ExtPublicKey = std::array<uint8_t, EXT_PUBLIC_KEY_SIZE>;
|
||||||
|
using ExtSecretKey = std::array<uint8_t, EXT_SECRET_KEY_SIZE>;
|
||||||
|
using ModerationHash = std::array<uint8_t, MOD_MODERATION_HASH_SIZE>;
|
||||||
|
|
||||||
|
TEST(ModList, PackedSizeOfEmptyModListIsZero)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
EXPECT_EQ(mod_list_packed_size(&mods), 0);
|
||||||
|
|
||||||
|
uint8_t byte = 1;
|
||||||
|
mod_list_pack(&mods, &byte);
|
||||||
|
EXPECT_EQ(byte, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, UnpackingZeroSizeArrayIsNoop)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
const uint8_t byte = 1;
|
||||||
|
EXPECT_EQ(mod_list_unpack(&mods, &byte, 0, 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, AddRemoveMultipleMods)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
uint8_t sig_pk1[32] = {1};
|
||||||
|
uint8_t sig_pk2[32] = {2};
|
||||||
|
EXPECT_TRUE(mod_list_add_entry(&mods, sig_pk1));
|
||||||
|
EXPECT_TRUE(mod_list_add_entry(&mods, sig_pk2));
|
||||||
|
EXPECT_TRUE(mod_list_remove_entry(&mods, sig_pk1));
|
||||||
|
EXPECT_TRUE(mod_list_remove_entry(&mods, sig_pk2));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, PackingAndUnpackingList)
|
||||||
|
{
|
||||||
|
using ModListEntry = std::array<uint8_t, MOD_LIST_ENTRY_SIZE>;
|
||||||
|
Moderation mods{};
|
||||||
|
EXPECT_TRUE(mod_list_add_entry(&mods, ModListEntry{}.data()));
|
||||||
|
|
||||||
|
std::vector<uint8_t> packed(mod_list_packed_size(&mods));
|
||||||
|
mod_list_pack(&mods, packed.data());
|
||||||
|
|
||||||
|
EXPECT_TRUE(mod_list_remove_entry(&mods, ModListEntry{}.data()));
|
||||||
|
|
||||||
|
Moderation mods2{};
|
||||||
|
EXPECT_EQ(mod_list_unpack(&mods2, packed.data(), packed.size(), 1), packed.size());
|
||||||
|
EXPECT_TRUE(mod_list_remove_entry(&mods2, ModListEntry{}.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, UnpackingTooManyModsFails)
|
||||||
|
{
|
||||||
|
using ModListEntry = std::array<uint8_t, MOD_LIST_ENTRY_SIZE>;
|
||||||
|
Moderation mods{};
|
||||||
|
EXPECT_TRUE(mod_list_add_entry(&mods, ModListEntry{}.data()));
|
||||||
|
|
||||||
|
std::vector<uint8_t> packed(mod_list_packed_size(&mods));
|
||||||
|
mod_list_pack(&mods, packed.data());
|
||||||
|
|
||||||
|
Moderation mods2{};
|
||||||
|
EXPECT_EQ(mod_list_unpack(&mods2, packed.data(), packed.size(), 2), -1);
|
||||||
|
EXPECT_TRUE(mod_list_remove_entry(&mods, ModListEntry{}.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, UnpackingFromEmptyBufferFails)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> packed(1);
|
||||||
|
|
||||||
|
Moderation mods{};
|
||||||
|
EXPECT_EQ(mod_list_unpack(&mods, packed.end().base(), 0, 1), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, HashOfEmptyModListZeroesOutBuffer)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
|
||||||
|
// Fill with random data, check that it's zeroed.
|
||||||
|
ModerationHash hash;
|
||||||
|
random_bytes(hash.data(), hash.size());
|
||||||
|
EXPECT_TRUE(mod_list_make_hash(&mods, hash.data()));
|
||||||
|
EXPECT_EQ(hash, ModerationHash{});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, RemoveIndexFromEmptyModListFails)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
EXPECT_FALSE(mod_list_remove_index(&mods, 0));
|
||||||
|
EXPECT_FALSE(mod_list_remove_index(&mods, UINT16_MAX));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, RemoveEntryFromEmptyModListFails)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
uint8_t sig_pk[32] = {0};
|
||||||
|
EXPECT_FALSE(mod_list_remove_entry(&mods, sig_pk));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, ModListRemoveIndex)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
uint8_t sig_pk[32] = {1};
|
||||||
|
EXPECT_TRUE(mod_list_add_entry(&mods, sig_pk));
|
||||||
|
EXPECT_TRUE(mod_list_remove_index(&mods, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, CleanupOnEmptyModsIsNoop)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
mod_list_cleanup(&mods);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, EmptyModListCannotVerifyAnySigPk)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
uint8_t sig_pk[32] = {1};
|
||||||
|
EXPECT_FALSE(mod_list_verify_sig_pk(&mods, sig_pk));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, ModListAddVerifyRemoveSigPK)
|
||||||
|
{
|
||||||
|
Moderation mods{};
|
||||||
|
uint8_t sig_pk[32] = {1};
|
||||||
|
EXPECT_TRUE(mod_list_add_entry(&mods, sig_pk));
|
||||||
|
EXPECT_TRUE(mod_list_verify_sig_pk(&mods, sig_pk));
|
||||||
|
EXPECT_TRUE(mod_list_remove_entry(&mods, sig_pk));
|
||||||
|
EXPECT_FALSE(mod_list_verify_sig_pk(&mods, sig_pk));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ModList, ModListHashCheck)
|
||||||
|
{
|
||||||
|
Moderation mods1{};
|
||||||
|
uint8_t sig_pk1[32] = {1};
|
||||||
|
std::array<uint8_t, MOD_MODERATION_HASH_SIZE> hash1;
|
||||||
|
|
||||||
|
EXPECT_TRUE(mod_list_add_entry(&mods1, sig_pk1));
|
||||||
|
EXPECT_TRUE(mod_list_make_hash(&mods1, hash1.data()));
|
||||||
|
EXPECT_TRUE(mod_list_remove_entry(&mods1, sig_pk1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SanctionsList, PackingIntoUndersizedBufferFails)
|
||||||
|
{
|
||||||
|
Mod_Sanction sanctions[1] = {};
|
||||||
|
std::array<uint8_t, 1> packed;
|
||||||
|
EXPECT_EQ(sanctions_list_pack(packed.data(), packed.size(), sanctions, 1, nullptr), -1);
|
||||||
|
|
||||||
|
uint16_t length = sanctions_list_packed_size(1) - 1;
|
||||||
|
std::vector<uint8_t> packed2(length);
|
||||||
|
EXPECT_EQ(sanctions_list_pack(packed2.data(), packed2.size(), sanctions, 1, nullptr), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SanctionsList, PackUnpackSanctionsCreds)
|
||||||
|
{
|
||||||
|
Moderation mod{};
|
||||||
|
std::array<uint8_t, MOD_SANCTIONS_CREDS_SIZE> packed;
|
||||||
|
EXPECT_EQ(sanctions_creds_pack(&mod.sanctions_creds, packed.data()), MOD_SANCTIONS_CREDS_SIZE);
|
||||||
|
EXPECT_EQ(
|
||||||
|
sanctions_creds_unpack(&mod.sanctions_creds, packed.data()), MOD_SANCTIONS_CREDS_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SanctionsListMod : ::testing::Test {
|
||||||
|
protected:
|
||||||
|
ExtPublicKey pk;
|
||||||
|
ExtSecretKey sk;
|
||||||
|
Logger *log = logger_new();
|
||||||
|
Moderation mod{};
|
||||||
|
|
||||||
|
Mod_Sanction sanctions[2] = {};
|
||||||
|
const uint8_t sanctioned_pk1[32] = {1};
|
||||||
|
const uint8_t sanctioned_pk2[32] = {2};
|
||||||
|
|
||||||
|
void SetUp() override
|
||||||
|
{
|
||||||
|
ASSERT_TRUE(create_extended_keypair(pk.data(), sk.data()));
|
||||||
|
|
||||||
|
mod.log = log;
|
||||||
|
|
||||||
|
memcpy(mod.self_public_sig_key, get_sig_pk(pk.data()), SIG_PUBLIC_KEY_SIZE);
|
||||||
|
memcpy(mod.self_secret_sig_key, get_sig_sk(sk.data()), SIG_SECRET_KEY_SIZE);
|
||||||
|
|
||||||
|
ASSERT_TRUE(mod_list_add_entry(&mod, get_sig_pk(pk.data())));
|
||||||
|
|
||||||
|
EXPECT_FALSE(sanctions_list_check_integrity(&mod, &mod.sanctions_creds, &sanctions[0], 0));
|
||||||
|
EXPECT_FALSE(sanctions_list_check_integrity(&mod, &mod.sanctions_creds, &sanctions[0], 1));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
sanctions_list_check_integrity(&mod, &mod.sanctions_creds, &sanctions[0], UINT16_MAX));
|
||||||
|
|
||||||
|
EXPECT_TRUE(sanctions_list_make_entry(&mod, sanctioned_pk1, &sanctions[0], SA_OBSERVER));
|
||||||
|
EXPECT_TRUE(sanctions_list_check_integrity(
|
||||||
|
&mod, &mod.sanctions_creds, sanctions, mod.num_sanctions));
|
||||||
|
EXPECT_TRUE(sanctions_list_make_entry(&mod, sanctioned_pk2, &sanctions[1], SA_OBSERVER));
|
||||||
|
EXPECT_TRUE(sanctions_list_check_integrity(
|
||||||
|
&mod, &mod.sanctions_creds, sanctions, mod.num_sanctions));
|
||||||
|
}
|
||||||
|
|
||||||
|
~SanctionsListMod() override
|
||||||
|
{
|
||||||
|
EXPECT_TRUE(sanctions_list_remove_observer(&mod, sanctioned_pk1, nullptr));
|
||||||
|
EXPECT_TRUE(sanctions_list_remove_observer(&mod, sanctioned_pk2, nullptr));
|
||||||
|
EXPECT_FALSE(sanctions_list_entry_exists(&mod, &sanctions[0]));
|
||||||
|
EXPECT_FALSE(sanctions_list_entry_exists(&mod, &sanctions[1]));
|
||||||
|
EXPECT_TRUE(mod_list_remove_entry(&mod, get_sig_pk(pk.data())));
|
||||||
|
|
||||||
|
logger_kill(log);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(JFreegman): Split this up into smaller subtests
|
||||||
|
TEST_F(SanctionsListMod, PackUnpackSanction)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> packed(sanctions_list_packed_size(2));
|
||||||
|
|
||||||
|
EXPECT_EQ(
|
||||||
|
sanctions_list_pack(packed.data(), packed.size(), sanctions, 2, nullptr), packed.size());
|
||||||
|
|
||||||
|
Mod_Sanction unpacked_sanctions[2] = {};
|
||||||
|
uint16_t processed_data_len = 0;
|
||||||
|
|
||||||
|
EXPECT_EQ(sanctions_list_unpack(unpacked_sanctions, &mod.sanctions_creds, 2, packed.data(),
|
||||||
|
packed.size(), &processed_data_len),
|
||||||
|
2);
|
||||||
|
|
||||||
|
EXPECT_EQ(processed_data_len, packed.size());
|
||||||
|
EXPECT_TRUE(sanctions_list_check_integrity(
|
||||||
|
&mod, &mod.sanctions_creds, unpacked_sanctions, mod.num_sanctions));
|
||||||
|
EXPECT_TRUE(sanctions_list_entry_exists(&mod, &unpacked_sanctions[0]));
|
||||||
|
EXPECT_TRUE(sanctions_list_entry_exists(&mod, &unpacked_sanctions[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SanctionsListMod, ReplaceSanctionSignatures)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(sanctions_list_replace_sig(&mod, mod.self_public_sig_key), mod.num_sanctions);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
sanctions_list_check_integrity(&mod, &mod.sanctions_creds, sanctions, mod.num_sanctions));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
|
@ -17,10 +17,39 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include "crypto_core.h" /* for CRYPTO_PUBLIC_KEY_SIZE */
|
#include "ccompat.h"
|
||||||
|
#include "crypto_core.h" // for CRYPTO_PUBLIC_KEY_SIZE
|
||||||
|
|
||||||
|
bool is_power_of_2(uint64_t x)
|
||||||
|
{
|
||||||
|
return x != 0 && (x & (~x + 1)) == x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *get_enc_key(const uint8_t *key)
|
||||||
|
{
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *get_sig_pk(const uint8_t *key)
|
||||||
|
{
|
||||||
|
return key + ENC_PUBLIC_KEY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_sig_pk(uint8_t *key, const uint8_t *sig_pk)
|
||||||
|
{
|
||||||
|
memcpy(key + ENC_PUBLIC_KEY_SIZE, sig_pk, SIG_PUBLIC_KEY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *get_sig_sk(const uint8_t *key)
|
||||||
|
{
|
||||||
|
return key + ENC_SECRET_KEY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *get_chat_id(const uint8_t *key)
|
||||||
|
{
|
||||||
|
return key + ENC_PUBLIC_KEY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
/** Equality function for public keys. */
|
|
||||||
bool pk_equal(const uint8_t *dest, const uint8_t *src)
|
bool pk_equal(const uint8_t *dest, const uint8_t *src)
|
||||||
{
|
{
|
||||||
return public_key_eq(dest, src);
|
return public_key_eq(dest, src);
|
||||||
|
@ -31,6 +60,34 @@ void pk_copy(uint8_t *dest, const uint8_t *src)
|
||||||
memcpy(dest, src, CRYPTO_PUBLIC_KEY_SIZE);
|
memcpy(dest, src, CRYPTO_PUBLIC_KEY_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void free_uint8_t_pointer_array(uint8_t **ary, size_t n_items)
|
||||||
|
{
|
||||||
|
if (ary == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n_items; ++i) {
|
||||||
|
if (ary[i] != nullptr) {
|
||||||
|
free(ary[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(ary);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t data_checksum(const uint8_t *data, uint32_t length)
|
||||||
|
{
|
||||||
|
uint8_t checksum[2] = {0};
|
||||||
|
uint16_t check;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < length; ++i) {
|
||||||
|
checksum[i % 2] ^= data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&check, checksum, sizeof(check));
|
||||||
|
return check;
|
||||||
|
}
|
||||||
|
|
||||||
int create_recursive_mutex(pthread_mutex_t *mutex)
|
int create_recursive_mutex(pthread_mutex_t *mutex)
|
||||||
{
|
{
|
||||||
pthread_mutexattr_t attr;
|
pthread_mutexattr_t attr;
|
||||||
|
@ -111,3 +168,19 @@ uint64_t min_u64(uint64_t a, uint64_t b)
|
||||||
{
|
{
|
||||||
return a < b ? a : b;
|
return a < b ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t jenkins_one_at_a_time_hash(const uint8_t *key, size_t len)
|
||||||
|
{
|
||||||
|
uint32_t hash = 0;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < len; ++i) {
|
||||||
|
hash += key[i];
|
||||||
|
hash += hash << 10;
|
||||||
|
hash ^= hash >> 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
hash += hash << 3;
|
||||||
|
hash ^= hash >> 11;
|
||||||
|
hash += hash << 15;
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
|
@ -16,18 +16,33 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "attributes.h"
|
#include "attributes.h"
|
||||||
|
#include "crypto_core.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Equality function for public keys. */
|
bool is_power_of_2(uint64_t x);
|
||||||
|
|
||||||
|
/** Functions for groupchat extended keys */
|
||||||
|
non_null() const uint8_t *get_enc_key(const uint8_t *key);
|
||||||
|
non_null() const uint8_t *get_sig_pk(const uint8_t *key);
|
||||||
|
non_null() void set_sig_pk(uint8_t *key, const uint8_t *sig_pk);
|
||||||
|
non_null() const uint8_t *get_sig_sk(const uint8_t *key);
|
||||||
|
non_null() const uint8_t *get_chat_id(const uint8_t *key);
|
||||||
|
|
||||||
|
|
||||||
|
/** @brief Equality function for public keys. */
|
||||||
non_null() bool pk_equal(const uint8_t *dest, const uint8_t *src);
|
non_null() bool pk_equal(const uint8_t *dest, const uint8_t *src);
|
||||||
/**
|
/**
|
||||||
* @brief Copy a public key from `src` to `dest`.
|
* @brief Copy a public key from `src` to `dest`.
|
||||||
*/
|
*/
|
||||||
non_null() void pk_copy(uint8_t *dest, const uint8_t *src);
|
non_null() void pk_copy(uint8_t *dest, const uint8_t *src);
|
||||||
|
|
||||||
|
/** @brief Frees all pointers in a uint8_t pointer array, as well as the array itself. */
|
||||||
|
nullable(1)
|
||||||
|
void free_uint8_t_pointer_array(uint8_t **ary, size_t n_items);
|
||||||
|
|
||||||
/** Returns -1 if failed or 0 if success */
|
/** Returns -1 if failed or 0 if success */
|
||||||
non_null() int create_recursive_mutex(pthread_mutex_t *mutex);
|
non_null() int create_recursive_mutex(pthread_mutex_t *mutex);
|
||||||
|
|
||||||
|
@ -61,6 +76,20 @@ uint16_t min_u16(uint16_t a, uint16_t b);
|
||||||
uint32_t min_u32(uint32_t a, uint32_t b);
|
uint32_t min_u32(uint32_t a, uint32_t b);
|
||||||
uint64_t min_u64(uint64_t a, uint64_t b);
|
uint64_t min_u64(uint64_t a, uint64_t b);
|
||||||
|
|
||||||
|
/** @brief Returns a 32-bit hash of key of size len */
|
||||||
|
non_null()
|
||||||
|
uint32_t jenkins_one_at_a_time_hash(const uint8_t *key, size_t len);
|
||||||
|
|
||||||
|
/** @brief Computes a checksum of a byte array.
|
||||||
|
*
|
||||||
|
* @param data The byte array used to compute the checksum.
|
||||||
|
* @param length The length in bytes of the passed data.
|
||||||
|
*
|
||||||
|
* @retval The resulting checksum.
|
||||||
|
*/
|
||||||
|
non_null()
|
||||||
|
uint16_t data_checksum(const uint8_t *data, uint32_t length);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue
Block a user