diff --git a/include/session/config/convo_info_volatile.h b/include/session/config/convo_info_volatile.h index cd3f6f43..6ddba3eb 100644 --- a/include/session/config/convo_info_volatile.h +++ b/include/session/config/convo_info_volatile.h @@ -280,7 +280,6 @@ LIBSESSION_EXPORT bool convo_info_volatile_get_or_construct_group( config_object* conf, convo_info_volatile_group* convo, const char* id) __attribute__((warn_unused_result)); - /// API: convo_info_volatile/convo_info_volatile_get_legacy_group /// /// Fills `convo` with the conversation info given a legacy group ID (specified as a null-terminated @@ -478,9 +477,7 @@ LIBSESSION_EXPORT bool convo_info_volatile_erase_community( /// /// Outputs: /// - `bool` - Returns true if group was found and removed -LIBSESSION_EXPORT bool convo_info_volatile_erase_group( - config_object* conf, const char* group_id); - +LIBSESSION_EXPORT bool convo_info_volatile_erase_group(config_object* conf, const char* group_id); /// API: convo_info_volatile/convo_info_volatile_erase_legacy_group /// diff --git a/include/session/config/user_groups.h b/include/session/config/user_groups.h index c58f5584..65529fe0 100644 --- a/include/session/config/user_groups.h +++ b/include/session/config/user_groups.h @@ -121,9 +121,7 @@ LIBSESSION_EXPORT int user_groups_init( /// Outputs: /// Returns `true` and populates `group` if the group was found; returns false otherwise. LIBSESSION_EXPORT bool user_groups_get_group( - config_object* conf, - ugroups_group_info* group, - const char* group_id); + config_object* conf, ugroups_group_info* group, const char* group_id); /// API: user_groups/user_groups_get_or_construct_group /// @@ -138,9 +136,7 @@ LIBSESSION_EXPORT bool user_groups_get_group( /// Outputs: /// Returns `true` on success, `false` upon error (such as when given an invalid group id). LIBSESSION_EXPORT bool user_groups_get_or_construct_group( - config_object* conf, - ugroups_group_info* group, - const char* group_id); + config_object* conf, ugroups_group_info* group, const char* group_id); /// API: user_groups/user_groups_get_community /// @@ -318,8 +314,7 @@ LIBSESSION_EXPORT void user_groups_set_community( /// Inputs: /// - `conf` -- [in] Pointer to config_object object /// - `group` -- [in] Pointer to a group info object -LIBSESSION_EXPORT void user_groups_set_group( - config_object* conf, const ugroups_group_info* group); +LIBSESSION_EXPORT void user_groups_set_group(config_object* conf, const ugroups_group_info* group); /// API: user_groups/user_groups_set_legacy_group /// @@ -407,8 +402,7 @@ LIBSESSION_EXPORT bool user_groups_erase_community( /// /// Outputs: /// - `bool` -- Returns True if conversation was found and removed -LIBSESSION_EXPORT bool user_groups_erase_group( - config_object* conf, const char* group_id); +LIBSESSION_EXPORT bool user_groups_erase_group(config_object* conf, const char* group_id); /// API: user_groups/user_groups_erase_legacy_group /// @@ -778,9 +772,7 @@ LIBSESSION_EXPORT user_groups_iterator* user_groups_iterator_new_legacy_groups( /// /// Outputs: /// - `user_groups_iterator*` -- The Iterator -LIBSESSION_EXPORT user_groups_iterator* user_groups_iterator_new_groups( - const config_object* conf); - +LIBSESSION_EXPORT user_groups_iterator* user_groups_iterator_new_groups(const config_object* conf); /// API: user_groups/user_groups_iterator_free /// @@ -862,10 +854,9 @@ LIBSESSION_EXPORT bool user_groups_it_is_community( /// - `group` -- [out] sets details of the group into here (if true is returned) /// /// Outputs: -/// - `bool` -- Returns `true` and sets `group` if the group is a non-legacy group (aka closed group). -LIBSESSION_EXPORT bool user_groups_it_is_group( - user_groups_iterator* it, ugroups_group_info* group); - +/// - `bool` -- Returns `true` and sets `group` if the group is a non-legacy group (aka closed +/// group). +LIBSESSION_EXPORT bool user_groups_it_is_group(user_groups_iterator* it, ugroups_group_info* group); /// API: user_groups/user_groups_it_is_legacy_group /// diff --git a/src/config/convo_info_volatile.cpp b/src/config/convo_info_volatile.cpp index d4eac2c1..3ce3dae1 100644 --- a/src/config/convo_info_volatile.cpp +++ b/src/config/convo_info_volatile.cpp @@ -69,7 +69,6 @@ namespace convo { c.unread = unread; } - legacy_group::legacy_group(std::string&& cgid) : id{std::move(cgid)} { check_session_id(id); } @@ -174,8 +173,7 @@ convo::community ConvoInfoVolatile::get_or_construct_community( return result; } -std::optional ConvoInfoVolatile::get_group( - std::string_view pubkey_hex) const { +std::optional ConvoInfoVolatile::get_group(std::string_view pubkey_hex) const { std::string pubkey = session_id_to_bytes(pubkey_hex, "03"); auto* info_dict = data["g"][pubkey].dict(); @@ -187,15 +185,13 @@ std::optional ConvoInfoVolatile::get_group( return result; } -convo::group ConvoInfoVolatile::get_or_construct_group( - std::string_view pubkey_hex) const { +convo::group ConvoInfoVolatile::get_or_construct_group(std::string_view pubkey_hex) const { if (auto maybe = get_group(pubkey_hex)) return *std::move(maybe); return convo::group{std::string{pubkey_hex}}; } - std::optional ConvoInfoVolatile::get_legacy_group( std::string_view pubkey_hex) const { std::string pubkey = session_id_to_bytes(pubkey_hex); diff --git a/src/config/user_groups.cpp b/src/config/user_groups.cpp index e5c827b1..c84a4893 100644 --- a/src/config/user_groups.cpp +++ b/src/config/user_groups.cpp @@ -614,7 +614,7 @@ LIBSESSION_C_API bool user_groups_get_or_construct_community( } } LIBSESSION_C_API bool user_groups_get_group( - config_object* conf, ugroups_group_info* group, const char* group_id){ + config_object* conf, ugroups_group_info* group, const char* group_id) { try { conf->last_error = nullptr; if (auto g = unbox(conf)->get_group(group_id)) { @@ -627,14 +627,10 @@ LIBSESSION_C_API bool user_groups_get_group( return false; } LIBSESSION_C_API bool user_groups_get_or_construct_group( - config_object* conf, - ugroups_group_info* group, - const char* group_id) { + config_object* conf, ugroups_group_info* group, const char* group_id) { try { conf->last_error = nullptr; - unbox(conf) - ->get_or_construct_group(group_id) - .into(*group); + unbox(conf)->get_or_construct_group(group_id).into(*group); return true; } catch (const std::exception& e) { set_error(conf, e.what()); @@ -685,8 +681,7 @@ LIBSESSION_C_API void user_groups_set_community( config_object* conf, const ugroups_community_info* comm) { unbox(conf)->set(community_info{*comm}); } -LIBSESSION_C_API void user_groups_set_group( - config_object* conf, const ugroups_group_info* group) { +LIBSESSION_C_API void user_groups_set_group(config_object* conf, const ugroups_group_info* group) { unbox(conf)->set(group_info{*group}); } LIBSESSION_C_API void user_groups_set_legacy_group( @@ -706,8 +701,7 @@ LIBSESSION_C_API bool user_groups_erase_community( return false; } } -LIBSESSION_C_API bool user_groups_erase_group( - config_object* conf, const char* group_id) { +LIBSESSION_C_API bool user_groups_erase_group(config_object* conf, const char* group_id) { try { return unbox(conf)->erase_group(group_id); } catch (...) { @@ -827,8 +821,7 @@ LIBSESSION_C_API user_groups_iterator* user_groups_iterator_new_communities( const config_object* conf) { return new user_groups_iterator{{unbox(conf)->begin_communities()}}; } -LIBSESSION_C_API user_groups_iterator* user_groups_iterator_new_groups( - const config_object* conf) { +LIBSESSION_C_API user_groups_iterator* user_groups_iterator_new_groups(const config_object* conf) { return new user_groups_iterator{{unbox(conf)->begin_groups()}}; } LIBSESSION_C_API user_groups_iterator* user_groups_iterator_new_legacy_groups( @@ -865,8 +858,7 @@ LIBSESSION_C_API bool user_groups_it_is_community( return user_groups_it_is_impl(it, c); } -LIBSESSION_C_API bool user_groups_it_is_group( - user_groups_iterator* it, ugroups_group_info* g) { +LIBSESSION_C_API bool user_groups_it_is_group(user_groups_iterator* it, ugroups_group_info* g) { return user_groups_it_is_impl(it, g); } diff --git a/tests/test_config_convo_info_volatile.cpp b/tests/test_config_convo_info_volatile.cpp index a7d6095b..9e4ddf22 100644 --- a/tests/test_config_convo_info_volatile.cpp +++ b/tests/test_config_convo_info_volatile.cpp @@ -93,7 +93,6 @@ TEST_CASE("Conversations", "[config][conversations]") { g.unread = true; convos.set(g); - auto [seqno, to_push, obs] = convos.push(); CHECK(seqno == 1); diff --git a/tests/test_group_keys.cpp b/tests/test_group_keys.cpp index be4fd860..d5a867c9 100644 --- a/tests/test_group_keys.cpp +++ b/tests/test_group_keys.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -383,34 +384,20 @@ TEST_CASE("Group Keys - C++ API", "[config][groups][keys][cpp]") { TEST_CASE("Group Keys - C API", "[config][groups][keys][c]") { struct pseudo_client { - const bool is_admin; - - const ustring seed; - std::string session_id; - - std::array public_key; std::array secret_key; + const ustring_view public_key{secret_key.data() + 32, 32}; + std::string session_id{session_id_from_ed(public_key)}; config_group_keys* keys; config_object* info; config_object* members; - pseudo_client(ustring s, bool a, unsigned char* gpk, std::optional gsk) : - seed{s}, is_admin{a} { - crypto_sign_ed25519_seed_keypair( - public_key.data(), - secret_key.data(), - reinterpret_cast(seed.data())); - - REQUIRE(oxenc::to_hex(seed.begin(), seed.end()) == - oxenc::to_hex(secret_key.begin(), secret_key.begin() + 32)); - - std::array sid; - int rc = crypto_sign_ed25519_pk_to_curve25519(&sid[1], public_key.data()); - REQUIRE(rc == 0); - session_id += "\x05"; - oxenc::to_hex(sid.begin(), sid.end(), std::back_inserter(session_id)); - + pseudo_client( + ustring seed, + bool is_admin, + unsigned char* gpk, + std::optional gsk) : + secret_key{sk_from_seed(seed)} { int rv = groups_members_init(&members, gpk, is_admin ? *gsk : NULL, NULL, 0, NULL); REQUIRE(rv == 0); @@ -459,33 +446,149 @@ TEST_CASE("Group Keys - C API", "[config][groups][keys][c]") { REQUIRE(oxenc::to_hex(group_seed.begin(), group_seed.end()) == oxenc::to_hex(group_sk.begin(), group_sk.begin() + 32)); - std::vector admins; - std::vector members; + hacky_list admins; + hacky_list members; // Initialize admin and member objects admins.emplace_back(admin1_seed, true, group_pk.data(), group_sk.data()); - // admins.emplace_back(admin2_seed, true, group_pk.data(), group_sk.data()); - - // for (int i = 0; i < 4; ++i) - // members.emplace_back(member_seeds[i], false, group_pk.data(), std::nullopt); - - // REQUIRE(admins[0].session_id == - // "05f1e8b64bbf761edf8f7b47e3a1f369985644cce0a62adb8e21604474bdd49627"); - // REQUIRE(admins[1].session_id == - // "05c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e"); - // REQUIRE(members[0].session_id == - // "05ece06dd8e02fb2f7d9497f956a1996e199953c651f4016a2f79a3b3e38d55628"); - // REQUIRE(members[1].session_id == - // "053ac269b71512776b0bd4a1234aaf93e67b4e9068a2c252f3b93a20acb590ae3c"); - // REQUIRE(members[2].session_id == - // "05a2b03abdda4df8316f9d7aed5d2d1e483e9af269d0b39191b08321b8495bc118"); - // REQUIRE(members[3].session_id == - // "050a41669a06c098f22633aee2eba03764ef6813bd4f770a3a2b9033b868ca470d"); - - // for (const auto& a : admins) - // REQUIRE(contacts_size(a.members) == 0); - // for (const auto& m : members) - // REQUIRE(contacts_size(m.members) == 0); + admins.emplace_back(admin2_seed, true, group_pk.data(), group_sk.data()); + + for (int i = 0; i < 4; ++i) + members.emplace_back(member_seeds[i], false, group_pk.data(), std::nullopt); + + REQUIRE(admins[0].session_id == + "05f1e8b64bbf761edf8f7b47e3a1f369985644cce0a62adb8e21604474bdd49627"); + REQUIRE(admins[1].session_id == + "05c5ba413c336f2fe1fb9a2c525f8a86a412a1db128a7841b4e0e217fa9eb7fd5e"); + REQUIRE(members[0].session_id == + "05ece06dd8e02fb2f7d9497f956a1996e199953c651f4016a2f79a3b3e38d55628"); + REQUIRE(members[1].session_id == + "053ac269b71512776b0bd4a1234aaf93e67b4e9068a2c252f3b93a20acb590ae3c"); + REQUIRE(members[2].session_id == + "05a2b03abdda4df8316f9d7aed5d2d1e483e9af269d0b39191b08321b8495bc118"); + REQUIRE(members[3].session_id == + "050a41669a06c098f22633aee2eba03764ef6813bd4f770a3a2b9033b868ca470d"); + + for (const auto& a : admins) + REQUIRE(groups_members_size(a.members) == 0); + for (const auto& m : members) + REQUIRE(groups_members_size(m.members) == 0); + + // add admin account, re-key, distribute + auto& admin1 = admins[0]; + config_group_member new_admin1; + + REQUIRE(groups_members_get_or_construct( + admin1.members, &new_admin1, admin1.session_id.c_str())); + + new_admin1.admin = true; + groups_members_set(admin1.members, &new_admin1); + + CHECK(config_needs_push(admin1.members)); + + const unsigned char* new_keys_config_1; + size_t key_len1; + REQUIRE(groups_keys_pending_config(admin1.keys, &new_keys_config_1, &key_len1)); + + config_push_data* new_info_config1 = config_push(admin1.info); + CHECK(new_info_config1->seqno == 1); + + config_push_data* new_mem_config1 = config_push(admin1.members); + CHECK(new_mem_config1->seqno == 1); + + const char* merge_hash1[1]; + const unsigned char* merge_data1[2]; + size_t merge_size1[2]; + + merge_hash1[0] = "fakehash1"; + + merge_data1[0] = new_info_config1->config; + merge_size1[0] = new_info_config1->config_len; + + merge_data1[1] = new_mem_config1->config; + merge_size1[1] = new_mem_config1->config_len; + + /* Even though we have only added one admin, admin2 will still be able to see group info + like group size and merge all configs. This is because they have loaded the key config + message, which they can decrypt with the group secret key. + */ + for (auto& a : admins) { + REQUIRE(groups_keys_load_message( + a.keys, new_keys_config_1, key_len1, get_timestamp(), a.info, a.members)); + REQUIRE(config_merge(a.info, merge_hash1, &merge_data1[0], &merge_size1[0], 1)); + config_confirm_pushed(a.info, new_info_config1->seqno, "fakehash1"); + + REQUIRE(config_merge(a.members, merge_hash1, &merge_data1[1], &merge_size1[1], 1)); + config_confirm_pushed(a.members, new_mem_config1->seqno, "fakehash1"); + + REQUIRE(groups_members_size(a.members) == 1); + } + + /* All attempts to merge non-admin members will throw, as none of the non admin members + will be able to decrypt the new info/member configs using the updated keys + */ + for (auto& m : members) { + // this will return true if the message was parsed successfully, NOT if the keys were + // decrypted + REQUIRE(groups_keys_load_message( + m.keys, new_keys_config_1, key_len1, get_timestamp(), m.info, m.members)); + REQUIRE_THROWS(config_merge(m.info, merge_hash1, &merge_data1[0], &merge_size1[0], 1)); + REQUIRE_THROWS(config_merge(m.members, merge_hash1, &merge_data1[1], &merge_size1[1], 1)); + + REQUIRE(groups_members_size(m.members) == 0); + } + + free(new_info_config1); + free(new_mem_config1); + + for (int i = 0; i < members.size(); ++i) { + config_group_member new_mem; + + REQUIRE(groups_members_get_or_construct( + members[i].members, &new_mem, members[i].session_id.c_str())); + new_mem.admin = false; + groups_members_set(admin1.members, &new_mem); + } + + CHECK(config_needs_push(admin1.members)); + + const unsigned char* new_keys_config_2; + size_t key_len2; + REQUIRE(groups_keys_rekey( + admin1.keys, admin1.info, admin1.members, &new_keys_config_2, &key_len2)); + + config_push_data* new_info_config2 = config_push(admin1.info); + CHECK(new_info_config2->seqno == 2); + + config_push_data* new_mem_config2 = config_push(admin1.members); + CHECK(new_mem_config2->seqno == 2); + + const char* merge_hash2[1]; + const unsigned char* merge_data2[2]; + size_t merge_size2[2]; + + merge_hash2[0] = "fakehash2"; + + merge_data2[0] = new_info_config2->config; + merge_size2[0] = new_info_config2->config_len; + + merge_data2[1] = new_mem_config2->config; + merge_size2[1] = new_mem_config2->config_len; + + for (auto& a : admins) { + REQUIRE(groups_keys_load_message( + a.keys, new_keys_config_2, key_len2, get_timestamp(), a.info, a.members)); + REQUIRE(config_merge(a.info, merge_hash2, &merge_data2[0], &merge_size2[0], 1)); + config_confirm_pushed(a.info, new_info_config2->seqno, "fakehash2"); + + REQUIRE(config_merge(a.members, merge_hash2, &merge_data2[1], &merge_size2[1], 1)); + config_confirm_pushed(a.members, new_mem_config2->seqno, "fakehash2"); + + REQUIRE(groups_members_size(a.members) == 5); + } + + free(new_info_config2); + free(new_mem_config2); } TEST_CASE("Group Keys - swarm authentication", "[config][groups][keys][swarm]") {