/* * chat_admin.c — передача канального приватного ключа (прав админа) другому узлу * * Владелец канала передаёт оба ключа (ed25519_privkey + x25519_privkey) выбранному * мемберу через etcp_router с ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT. * Получатель верифицирует ключи (вывод pubkey == сохранённые pubkey канала) и импортирует. */ #include "chat_admin.h" #include "chat_core_priv.h" #include "chat_event.h" #include "../utun_instance.h" #include "../routing_layer/topo_node_sqlite.h" #include "../routing_layer/etcp_router.h" #include "../routing_layer/route_crypto.h" #include "../transport_layer/etcp_api.h" #include "../transport_layer/etcp.h" #include "../transport_layer/secure_channel.h" #include "../../lib/mem.h" #include "../../lib/ll_queue.h" #include "../../lib/debug_config.h" #include #include #include #include #define CA_ID "chat_admin" /* ─── ed25519 pubkey из ed25519 privkey (канальные ключи — независимая пара) ─── */ static int ed25519_pub_from_priv(const uint8_t priv[32], uint8_t pub[32]) { EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, priv, 32); if (!pkey) return -1; size_t plen = 32; int ok = EVP_PKEY_get_raw_public_key(pkey, pub, &plen) == 1 && plen == 32; EVP_PKEY_free(pkey); return ok ? 0 : -1; } /* ─── приём переданного ключа ─── * payload: [group_id:8 LE][ed25519_priv:32][x25519_priv:32] */ static void admin_key_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) { if (!entry) return; if (!conn || !entry->dgram || entry->len < ROUTER_SVC_PAYLOAD_OFF + 8 + 32 + 32) { queue_dgram_free(entry); queue_entry_free(entry); return; } struct UTUN_INSTANCE* inst = conn->instance; if (!inst) { queue_dgram_free(entry); queue_entry_free(entry); return; } struct chat_core_ctx* cc = CC(inst); if (!cc || !cc->initialized || !cc->db) { queue_dgram_free(entry); queue_entry_free(entry); return; } const uint8_t* d = entry->dgram; uint64_t from_node; memcpy(&from_node, d + ROUTER_SVC_SRC_OFF, 8); const uint8_t* payload = d + ROUTER_SVC_PAYLOAD_OFF; uint64_t group_id; memcpy(&group_id, payload, 8); const uint8_t* ed_priv = payload + 8; const uint8_t* x_priv = payload + 8 + 32; char ch_id[64]; snprintf(ch_id, sizeof(ch_id), "%llu", (unsigned long long)group_id); /* проверить, что ключи соответствуют публичным ключам канала (защита от подлога) */ uint8_t ch_x_pub[32], ch_ed_pub[32]; if (topo_node_sqlite_channel_get(cc->db, ch_id, NULL, 0, NULL, ch_x_pub, ch_ed_pub, NULL) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv — no channel ch=%s from 0x%016llx, drop", CA_ID, ch_id, (unsigned long long)from_node); queue_dgram_free(entry); queue_entry_free(entry); return; } uint8_t ed_pub_check[32], x_pub_check[32]; if (ed25519_pub_from_priv(ed_priv, ed_pub_check) != 0 || memcmp(ed_pub_check, ch_ed_pub, 32) != 0 || sc_compute_public_key_from_private(x_priv, x_pub_check) != SC_OK || memcmp(x_pub_check, ch_x_pub, 32) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv — key mismatch for ch=%s from 0x%016llx, drop", CA_ID, ch_id, (unsigned long long)from_node); queue_dgram_free(entry); queue_entry_free(entry); return; } if (topo_node_sqlite_channel_set_privs(cc->db, ch_id, x_priv, ed_priv) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: recv — set_privs FAILED ch=%s from 0x%016llx", CA_ID, ch_id, (unsigned long long)from_node); queue_dgram_free(entry); queue_entry_free(entry); return; } DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: received admin key ch=%s from 0x%016llx — imported, now co-owner", CA_ID, ch_id, (unsigned long long)from_node); uint8_t evt[72]; uint8_t cl = (uint8_t)strlen(ch_id); evt[0] = cl; memcpy(evt + 1, ch_id, cl); memcpy(evt + 1 + cl, &from_node, 8); chat_event_post(inst, CHAT_EVT_ADMIN_KEY_RECEIVED, evt, 1 + cl + 8); queue_dgram_free(entry); queue_entry_free(entry); } /* ─── жизненный цикл ─── */ int chat_admin_init(struct UTUN_INSTANCE* inst) { if (!inst) return -1; if (etcp_router_bind(inst, ETCP_RT_ID_ADMIN_KEY, admin_key_recv_cb) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: etcp_router_bind failed", CA_ID); return -1; } DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: initialized", CA_ID); return 0; } void chat_admin_destroy(struct UTUN_INSTANCE* inst) { if (!inst) return; etcp_router_unbind(inst, ETCP_RT_ID_ADMIN_KEY); DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: destroyed", CA_ID); } /* ─── передача прав (вызывается из uasync-потока) ─── */ void chat_core_transfer_admin(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id) { struct chat_core_ctx* cc = CC(inst); if (!cc || !cc->initialized || !inst || !cc->db) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: transfer — not initialized", CA_ID); return; } if (!ch_id || !ch_id[0] || target_node_id == 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: transfer — invalid args ch=%s target=0x%016llx", CA_ID, ch_id ? ch_id : "(null)", (unsigned long long)target_node_id); return; } uint8_t x_priv[32], ed_priv[32]; if (topo_node_sqlite_channel_get_privs(cc->db, ch_id, x_priv, ed_priv) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: transfer — no channel privkey ch=%s (not owner?)", CA_ID, ch_id); return; } uint64_t group_id = strtoull(ch_id, NULL, 10); if (group_id == 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: transfer — invalid ch_id=%s", CA_ID, ch_id); return; } uint8_t payload[8 + 32 + 32]; memcpy(payload, &group_id, 8); memcpy(payload + 8, ed_priv, 32); memcpy(payload + 8 + 32, x_priv, 32); struct ll_entry* entry = queue_entry_new(0); if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: transfer — queue_entry_new failed", CA_ID); return; } entry->dgram = u_malloc(1 + sizeof(payload)); if (!entry->dgram) { queue_entry_free(entry); return; } entry->dgram[0] = ETCP_RT_ID_ADMIN_KEY; memcpy(entry->dgram + 1, payload, sizeof(payload)); entry->len = 1 + (uint16_t)sizeof(payload); int rc = etcp_route_send(inst, group_id, target_node_id, entry, 1, ROUTE_CRYPTO_SIGN | ROUTE_CRYPTO_ENCRYPT); if (rc != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: transfer — etcp_route_send FAILED rc=%d ch=%s → 0x%016llx", CA_ID, rc, ch_id, (unsigned long long)target_node_id); queue_dgram_free(entry); queue_entry_free(entry); return; } DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: admin key sent ch=%s → 0x%016llx (encrypted+signed)", CA_ID, ch_id, (unsigned long long)target_node_id); } void chat_core_transfer_admin_trampoline(void* arg) { struct chat_transfer_admin_req* req = (struct chat_transfer_admin_req*)arg; if (!req) return; chat_core_transfer_admin(req->inst, req->ch_id, req->target_node_id); u_free(req); }