diff --git a/CMakeLists.txt b/CMakeLists.txt index e48c75e5..e7d377a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,19 +260,25 @@ set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/friend_requests.c 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} toxcore/Messenger.c toxcore/Messenger.h) -# LAYER 7: Group chats +# LAYER 8: Conferences # -------------------- set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/group.c toxcore/group.h) -# LAYER 8: Public API +# LAYER 9: Public API # ------------------- set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/tox_api.c @@ -281,7 +287,7 @@ set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/tox_private.h) 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} third_party/cmp/cmp.c @@ -319,7 +325,7 @@ set(toxcore_SOURCES ${toxcore_SOURCES} toxcore/tox_unpack.h) 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} toxcore/tox_dispatch.c @@ -438,6 +444,7 @@ unit_test(toxav rtp) unit_test(toxcore DHT) unit_test(toxcore bin_pack) unit_test(toxcore crypto_core) +unit_test(toxcore group_moderation) unit_test(toxcore mono_time) unit_test(toxcore ping_array) unit_test(toxcore tox) diff --git a/other/analysis/run-cppcheck b/other/analysis/run-cppcheck index ac007190..ce60e567 100755 --- a/other/analysis/run-cppcheck +++ b/other/analysis/run-cppcheck @@ -11,6 +11,8 @@ CPPCHECK+=("--inconclusive") CPPCHECK+=("--error-exitcode=1") # Used for VLA. CPPCHECK+=("--suppress=allocaCalled") +# False positives on sanctions_copy in group_moderation.c +CPPCHECK+=("--suppress=doubleFree") # False positives in switch statements. CPPCHECK+=("--suppress=knownConditionTrueFalse") # Cppcheck does not need standard library headers to get proper results. diff --git a/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 b/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 index 9b50d12a..a5e6ced4 100644 --- a/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 +++ b/other/bootstrap_daemon/docker/tox-bootstrapd.sha256 @@ -1 +1 @@ -8216f2b48b15db02c373766c9e8f898a0cf8ebb26310635b146a118d0bcf1f71 /usr/local/bin/tox-bootstrapd +073710c9592554bbcb8bd094c9d64a987d52a2d1dedf965557b56fe848ddc14b /usr/local/bin/tox-bootstrapd diff --git a/other/docker/coverage/Dockerfile b/other/docker/coverage/Dockerfile index c5ed745f..7e103c55 100644 --- a/other/docker/coverage/Dockerfile +++ b/other/docker/coverage/Dockerfile @@ -45,6 +45,7 @@ RUN source .github/scripts/flags-coverage.sh \ -DSTRICT_ABI=ON \ -DAUTOTEST=ON \ -DPROXY_TEST=ON \ + -DUSE_IPV6=OFF \ && cmake --build _build --parallel 8 --target install WORKDIR /work/_build diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel index b286cc4e..de91456a 100644 --- a/testing/BUILD.bazel +++ b/testing/BUILD.bazel @@ -15,6 +15,7 @@ sh_test( args = ["$(locations %s)" % f for f in CIMPLE_FILES] + [ "-Wno-boolean-return", "-Wno-callback-names", + "-Wno-callgraph", "-Wno-enum-names", "-Wno-type-check", "+RTS", diff --git a/toxcore/BUILD.bazel b/toxcore/BUILD.bazel index 952d02cf..1aafae20 100644 --- a/toxcore/BUILD.bazel +++ b/toxcore/BUILD.bazel @@ -76,6 +76,7 @@ cc_test( flaky = True, deps = [ ":crypto_core", + ":util", "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], @@ -163,6 +164,7 @@ cc_library( ], deps = [ ":attributes", + ":ccompat", ":crypto_core", "@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( name = "Messenger", srcs = ["Messenger.c"], diff --git a/toxcore/DHT.c b/toxcore/DHT.c index 08a70d55..5567d386 100644 --- a/toxcore/DHT.c +++ b/toxcore/DHT.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) - || *bootstrap_times < MAX_BOOTSTRAP_TIMES)) { + || *bootstrap_times < MAX_BOOTSTRAP_TIMES)) { uint32_t rand_node = random_range_u32(num_nodes); if ((num_nodes - 1) != rand_node) { @@ -1855,8 +1855,8 @@ static void do_Close(DHT *dht) dht->num_to_bootstrap = 0; 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, - false); + dht, &dht->close_lastgetnodes, dht->self_public_key, dht->close_clientlist, LCLIENT_LIST, &dht->close_bootstrap_times, + false); if (not_killed != 0) { 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) { 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); } diff --git a/toxcore/DHT.h b/toxcore/DHT.h index 0b82eead..9c732f48 100644 --- a/toxcore/DHT.h +++ b/toxcore/DHT.h @@ -22,6 +22,8 @@ extern "C" { #endif +/* Maximum size of a signature (may be smaller) */ +#define SIGNATURE_SIZE CRYPTO_SIGNATURE_SIZE /** Maximum number of clients stored per friend. */ #define MAX_FRIEND_CLIENTS 8 @@ -64,6 +66,7 @@ extern "C" { #define CRYPTO_PACKET_DHTPK 156 #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_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. */ 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() void kill_dht(DHT *dht); diff --git a/toxcore/Messenger.c b/toxcore/Messenger.c index 08807971..3a67bbcb 100644 --- a/toxcore/Messenger.c +++ b/toxcore/Messenger.c @@ -92,23 +92,6 @@ int getfriendcon_id(const Messenger *m, int32_t friendnumber) 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)]` * @@ -119,7 +102,7 @@ void getaddress(const Messenger *m, uint8_t *address) pk_copy(address, nc_get_self_public_key(m->net_crypto)); uint32_t nospam = get_nospam(m->fr); 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)); } @@ -236,7 +219,7 @@ int32_t m_addfriend(Messenger *m, const uint8_t *address, const uint8_t *data, u } 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)); 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]; pk_copy(address, temp.real_pk); 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)); m_addfriend(m, address, temp.info, net_ntohs(temp.info_size)); } diff --git a/toxcore/crypto_core.c b/toxcore/crypto_core.c index ebaa9fa0..f90ac5b8 100644 --- a/toxcore/crypto_core.c +++ b/toxcore/crypto_core.c @@ -33,6 +33,12 @@ #define crypto_box_MACBYTES (crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES) #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- #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION #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, "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) static uint8_t *crypto_malloc(size_t bytes) { @@ -177,6 +210,26 @@ uint32_t random_range_u32(uint32_t upper_bound) #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) { if (public_key[31] >= 128) { /* Last bit of key is always zero. */ diff --git a/toxcore/crypto_core.h b/toxcore/crypto_core.h index ef1e125d..1651af85 100644 --- a/toxcore/crypto_core.h +++ b/toxcore/crypto_core.h @@ -19,6 +19,21 @@ extern "C" { #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. */ @@ -60,6 +75,41 @@ extern "C" { */ #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. * @@ -129,6 +179,36 @@ uint64_t random_u64(void); */ 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. */ @@ -151,6 +231,17 @@ void random_bytes(uint8_t *bytes, size_t length); non_null() 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. * diff --git a/toxcore/crypto_core_test.cc b/toxcore/crypto_core_test.cc index 0c6a0fdb..78c17963 100644 --- a/toxcore/crypto_core_test.cc +++ b/toxcore/crypto_core_test.cc @@ -6,11 +6,17 @@ #include #include +#include "util.h" + namespace { +using ExtPublicKey = std::array; +using ExtSecretKey = std::array; +using Signature = std::array; +using Nonce = std::array; + TEST(CryptoCore, IncrementNonce) { - using Nonce = std::array; Nonce nonce{}; increment_nonce(nonce.data()); EXPECT_EQ( @@ -26,7 +32,6 @@ TEST(CryptoCore, IncrementNonce) TEST(CryptoCore, IncrementNonceNumber) { - using Nonce = std::array; Nonce nonce{}; 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}})); } +TEST(CryptoCore, Signatures) +{ + ExtPublicKey pk; + ExtSecretKey sk; + + EXPECT_TRUE(create_extended_keypair(pk.data(), sk.data())); + + std::vector 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 diff --git a/toxcore/group_moderation.c b/toxcore/group_moderation.c new file mode 100644 index 00000000..dea38a64 --- /dev/null +++ b/toxcore/group_moderation.c @@ -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 + +#include +#include +#include + +#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; +} diff --git a/toxcore/group_moderation.h b/toxcore/group_moderation.h new file mode 100644 index 00000000..36b44a42 --- /dev/null +++ b/toxcore/group_moderation.h @@ -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 +#include + +#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 diff --git a/toxcore/group_moderation_fuzz_test.cc b/toxcore/group_moderation_fuzz_test.cc new file mode 100644 index 00000000..0f3b1e6a --- /dev/null +++ b/toxcore/group_moderation_fuzz_test.cc @@ -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; +} diff --git a/toxcore/group_moderation_test.cc b/toxcore/group_moderation_test.cc new file mode 100644 index 00000000..0f9c88c6 --- /dev/null +++ b/toxcore/group_moderation_test.cc @@ -0,0 +1,248 @@ +#include "group_moderation.h" + +#include + +#include +#include +#include + +#include "crypto_core.h" +#include "logger.h" +#include "util.h" + +namespace { + +using ExtPublicKey = std::array; +using ExtSecretKey = std::array; +using ModerationHash = std::array; + +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; + Moderation mods{}; + EXPECT_TRUE(mod_list_add_entry(&mods, ModListEntry{}.data())); + + std::vector 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; + Moderation mods{}; + EXPECT_TRUE(mod_list_add_entry(&mods, ModListEntry{}.data())); + + std::vector 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 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 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 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 packed2(length); + EXPECT_EQ(sanctions_list_pack(packed2.data(), packed2.size(), sanctions, 1, nullptr), -1); +} + +TEST(SanctionsList, PackUnpackSanctionsCreds) +{ + Moderation mod{}; + std::array 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 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 diff --git a/toxcore/util.c b/toxcore/util.c index 622a14ad..8c75b2c5 100644 --- a/toxcore/util.c +++ b/toxcore/util.c @@ -17,10 +17,39 @@ #include #include -#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) { 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); } +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) { pthread_mutexattr_t attr; @@ -111,3 +168,19 @@ uint64_t min_u64(uint64_t a, uint64_t 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; +} diff --git a/toxcore/util.h b/toxcore/util.h index ad83518a..b4630847 100644 --- a/toxcore/util.h +++ b/toxcore/util.h @@ -16,18 +16,33 @@ #include #include "attributes.h" +#include "crypto_core.h" #ifdef __cplusplus extern "C" { #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); /** * @brief Copy a public key from `src` to `dest`. */ 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 */ 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); 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 } // extern "C" #endif