diff --git a/src/chat/chat_channel.c b/src/chat/chat_channel.c index d3ed0f21..a1cbe916 100644 --- a/src/chat/chat_channel.c +++ b/src/chat/chat_channel.c @@ -122,13 +122,18 @@ void chat_core_create_channel(struct UTUN_INSTANCE* inst, struct chat_channel_cr uint64_t myid = inst->node_id; uint64_t join_ts = (uint64_t)ntp_time_get_seconds(inst); uint8_t join_sig[64]; - channel_sign_join(req->x25519_pubkey, req->ed25519_pubkey, - myid, inst->my_keys.public_key, - join_ts, inst->my_ed25519_privkey, join_sig); + if (channel_sign_join(req->x25519_pubkey, req->ed25519_pubkey, myid, inst->my_keys.public_key, + join_ts, inst->my_ed25519_privkey, join_sig) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: root join signing failed ch=%s", CC_ID, req->channel_id); return; + } /* корень дерева приглашений: подписан ключом группы (канальный ed25519) */ - uint8_t root_sig[64]; memset(root_sig, 0, 64); - member_sync_sign_pubkey(req->ed25519_privkey, inst->my_keys.public_key, root_sig); + uint8_t root_sig[64]; + struct ms_member_rec root = { .node_id = myid, .x25519 = inst->my_keys.public_key, .signature = root_sig }; + if (member_sync_sign_pubkey(req->ed25519_privkey, inst->my_keys.public_key, root_sig) < 0 + || member_sync_verify_signature(cc->db, req->channel_id, &root) != 1) { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: root invitation signing failed ch=%s", CC_ID, req->channel_id); return; + } int mrc = member_sync_put(inst, req->channel_id, myid, inst->my_keys.public_key, inst->my_ed25519_pubkey, join_sig, join_ts, NULL, 0, "{}", NULL, NULL, 0, 0, root_sig); diff --git a/tests/test_member_adapter.c b/tests/test_member_adapter.c index 129b41d8..8e966f3f 100644 --- a/tests/test_member_adapter.c +++ b/tests/test_member_adapter.c @@ -25,6 +25,62 @@ static void public_key(const uint8_t priv[32], uint8_t pub[32]) { size_t len = 32; assert(EVP_PKEY_get_raw_public_key(pk, pub, &len) == 1); EVP_PKEY_free(pk); } +/* Подтверждение корня доставляется независимо от версии профиля и переживает нулевой блок на проводе. */ +static void test_invitation_certificate(struct UASYNC* ua) { + struct UTUN_INSTANCE source, target; setup(&source, ua); setup(&target, ua); + uint8_t x[32] = {9}, priv[32] = {7}, pub[32], owner_priv[32] = {8}, owner_pub[32], zero[64] = {0}; + public_key(priv, pub); public_key(owner_priv, owner_pub); + struct UTUN_INSTANCE* nodes[] = { &source, &target }; + for (unsigned i = 0; i < 2; i++) { + assert(topo_node_sqlite_channel_delete(nodes[i]->topo_sqlite_db, "1001") == 0); + assert(topo_node_sqlite_channel_put(nodes[i]->topo_sqlite_db, "1001", "root", 1, x, NULL, owner_pub, NULL, zero) == 0); + } + uint64_t nid = sc_derive_node_id_from_pubkey(x); uint8_t join[64], tree[64], update[64], body[256]; + int len = member_sync_build_join_msg(x, owner_pub, nid, x, 1, body, sizeof(body)); assert(len == 112); + assert(sc_ed25519_sign(priv, body, len, join) == SC_OK && member_sync_sign_pubkey(owner_priv, x, tree) == 0); + struct ms_member_rec rec = { .node_id = nid, .x25519 = x, .ed25519 = pub, .join_sig = join, .join_ts = 1, + .update_sig = update, .update_ts = 2, .userinfo = "{\"name\":\"profile\"}", .signature = tree }; + len = member_sync_build_update_msg(join, rec.update_ts, rec.userinfo, body, sizeof(body)); assert(len > 0); + assert(sc_ed25519_sign(priv, body, len, update) == SC_OK); + assert(member_sync_apply_record(&source, "1001", source.node_id, &rec) & MS_APPLY_CHANGED); + rec.signature = zero; + assert(member_sync_apply_record(&target, "1001", target.node_id, &rec) & MS_APPLY_CHANGED); + assert(member_sync_verify_signature(target.topo_sqlite_db, "1001", &rec) == 0); + rec.signature = tree; + assert(member_sync_apply_record(&target, "1001", source.node_id + 1, &rec) & MS_APPLY_CHANGED); + uint8_t expected[32], actual[32], stored[64]; uint64_t parent; + root(&source, expected); root(&target, actual); assert(!memcmp(expected, actual, 32)); + assert(topo_node_sqlite_member_get_sign(target.topo_sqlite_db, "1001", nid, &parent, stored) == 0); + assert(parent == 0 && !memcmp(tree, stored, 64)); + assert(member_sync_apply_record(&target, "1001", source.node_id + 1, &rec) == 0); + tree[0] ^= 1; + assert(member_sync_apply_record(&target, "1001", source.node_id + 1, &rec) < 0); + root(&target, actual); assert(!memcmp(expected, actual, 32)); tree[0] ^= 1; + + /* Более свежий подписанный профиль с нулевой подписью приглашения сохраняет сертификат. */ + rec.update_ts = 3; rec.userinfo = "{\"name\":\"new profile\"}"; + len = member_sync_build_update_msg(join, rec.update_ts, rec.userinfo, body, sizeof(body)); assert(len > 0); + assert(sc_ed25519_sign(priv, body, len, update) == SC_OK); + assert(member_sync_apply_record(&source, "1001", source.node_id, &rec) & MS_APPLY_CHANGED); + rec.signature = zero; + assert(member_sync_apply_record(&target, "1001", source.node_id + 1, &rec) & MS_APPLY_CHANGED); + assert(topo_node_sqlite_member_get_sign(target.topo_sqlite_db, "1001", nid, &parent, stored) == 0); + assert(parent == 0 && !memcmp(tree, stored, 64)); + root(&source, expected); root(&target, actual); assert(!memcmp(expected, actual, 32)); + + /* Старый профиль может донести подтверждение, сохранив более свежие userinfo/update_ts. */ + assert(sqlite3_exec(target.topo_sqlite_db, "UPDATE peers_1001 SET signature=zeroblob(64)", NULL, NULL, NULL) == SQLITE_OK); + assert(merkle_sync_recompute_path(&target, MT_DATA_MEMBERS, "1001", nid) == 1); + rec.update_ts = 2; rec.userinfo = "{\"name\":\"old profile\"}"; rec.signature = tree; + len = member_sync_build_update_msg(join, rec.update_ts, rec.userinfo, body, sizeof(body)); assert(len > 0); + assert(sc_ed25519_sign(priv, body, len, update) == SC_OK); + int result = member_sync_apply_record(&target, "1001", source.node_id + 1, &rec); + assert((result & MS_APPLY_CHANGED) && (result & MS_APPLY_STALE)); + root(&target, actual); assert(!memcmp(expected, actual, 32)); + puts("PASS: root certificate validates with channel key; equal/stale profile versions do not block confirmation; empty wire signature preserves it"); + for (unsigned i = 0; i < 2; i++) { member_sync_destroy(nodes[i]); assert(sqlite3_close(nodes[i]->topo_sqlite_db) == SQLITE_OK); } +} + static void test_owner_sizes(struct UASYNC* ua) { struct UTUN_INSTANCE source, target; setup(&source, ua); setup(&target, ua); uint8_t x[32] = {9}, priv[32] = {7}, pub[32], owner_priv[32] = {8}, owner_pub[32], zero[64] = {0}; @@ -138,6 +194,7 @@ int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); size_t baseline = u_get_allocated_count(); struct UASYNC* ua = uasync_create(); assert(ua); + test_invitation_certificate(ua); test_owner_sizes(ua); struct UTUN_INSTANCE source, target; setup(&source, ua); setup(&target, ua);