#include "dm_identity.h" #include "dm_crypto.h" #include "../utun_instance.h" #include "../routing_layer/devgroup.h" #include "../transport_layer/secure_channel.h" #include "../../lib/debug_config.h" #include #include #include #include #include static int identity_error(struct UTUN_INSTANCE* inst, const char* stage, uint64_t node) { DEBUG_ERROR(DEBUG_CATEGORY_DM, "DM identity: %s node=%llu sql=%s", stage, (unsigned long long)node, inst && inst->topo_sqlite_db ? sqlite3_errmsg(inst->topo_sqlite_db) : "unavailable"); return -1; } /* Проверка формы не зависит от доступности устройства и локального каталога. */ static int party_valid(const struct dm_party* party) { static const uint8_t zero[32] = {0}; if (!party || !memcmp(party->ed, zero, 32) || !memcmp(party->x, zero, 32)) return 0; if (party->kind == DM_PARTY_GROUP) return party->node == 0; return party->kind == DM_PARTY_DEVICE && party->node && party->node == sc_derive_node_id_from_pubkey(party->x); } int dm_party_encode(const struct dm_party* party, uint8_t out[DM_PARTY_WIRE_SIZE]) { if (!out || !party_valid(party)) return identity_error(NULL, "invalid party encode", party ? party->node : 0); out[0] = party->kind; for (unsigned i = 0; i < 8; i++) out[1 + i] = (uint8_t)(party->node >> (8 * i)); memcpy(out + 9, party->ed, 32); memcpy(out + 41, party->x, 32); return 0; } int dm_party_decode(const uint8_t data[DM_PARTY_WIRE_SIZE], struct dm_party* out) { if (!data || !out) return identity_error(NULL, "invalid party decode arguments", 0); struct dm_party party = {.kind = data[0]}; for (unsigned i = 0; i < 8; i++) party.node |= (uint64_t)data[1 + i] << (8 * i); memcpy(party.ed, data + 9, 32); memcpy(party.x, data + 41, 32); if (!party_valid(&party)) return identity_error(NULL, "invalid party encoding", party.node); *out = party; return 0; } int dm_party_equal(const struct dm_party* a, const struct dm_party* b) { return a && b && a->kind == b->kind && a->node == b->node && !memcmp(a->ed, b->ed, 32) && !memcmp(a->x, b->x, 32); } /* Собственное устройство берём из ядра, удалённое — из проверенной проекции nodes. */ int dm_party_resolve(struct UTUN_INSTANCE* inst, uint64_t node, enum dm_mode mode, struct dm_party* out) { if (!inst || !inst->topo_sqlite_db || !node || !out || (mode != DM_MODE_DEVICE && mode != DM_MODE_GROUPS)) return identity_error(inst, "invalid resolve arguments", node); struct dm_party party = {.kind = DM_PARTY_DEVICE, .node = node}; if (node == inst->node_id) { memcpy(party.ed, inst->my_ed25519_pubkey, 32); memcpy(party.x, inst->my_keys.public_key, 32); if (mode == DM_MODE_GROUPS) { struct devgroup_record record; uint8_t ed[32] = {0}, x[32] = {0}; int found = devgroup_load_local(inst, &record, ed, x); OPENSSL_cleanse(ed, sizeof(ed)); OPENSSL_cleanse(x, sizeof(x)); if (found < 0) return identity_error(inst, "local subgroup verification failed", node); if (found) { party.kind = DM_PARTY_GROUP; party.node = 0; memcpy(party.ed, record.ed, 32); memcpy(party.x, record.x, 32); } } } else { sqlite3_stmt* st = NULL; int rc = sqlite3_prepare_v2(inst->topo_sqlite_db, "SELECT x25519_pubkey,ed25519_pubkey FROM nodes WHERE node_id=?", -1, &st, NULL); if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)node); if (rc == SQLITE_OK) rc = sqlite3_step(st); if (rc == SQLITE_ROW && sqlite3_column_bytes(st, 0) == 32 && sqlite3_column_bytes(st, 1) == 32) { memcpy(party.x, sqlite3_column_blob(st, 0), 32); memcpy(party.ed, sqlite3_column_blob(st, 1), 32); } else { sqlite3_finalize(st); return identity_error(inst, "missing or damaged device keys", node); } if (sqlite3_finalize(st) != SQLITE_OK) return identity_error(inst, "device query finalization failed", node); if (!party_valid(&party)) return identity_error(inst, "device ID/key mismatch", node); if (mode == DM_MODE_GROUPS) { uint8_t id[32]; char name[64]; int found = devgroup_member(inst->topo_sqlite_db, node, id, name); if (found < 0) return identity_error(inst, "subgroup membership verification failed", node); if (found) { struct devgroup_record record; struct devgroup_profile profile; if (devgroup_latest(inst->topo_sqlite_db, id, &record, &profile) != 1) return identity_error(inst, "subgroup profile unavailable", node); party.kind = DM_PARTY_GROUP; party.node = 0; memcpy(party.ed, record.ed, 32); memcpy(party.x, record.x, 32); } } } if (!party_valid(&party)) return identity_error(inst, "invalid resolved party", node); *out = party; DEBUG_DEBUG(DEBUG_CATEGORY_DM, "DM identity: resolved node=%llu mode=%d kind=%u", (unsigned long long)node, mode, party.kind); return 0; } int dm_party_member(struct UTUN_INSTANCE* inst, const struct dm_party* party, uint64_t node) { if (!party_valid(party) || !node) return identity_error(inst, "invalid membership arguments", node); if (party->kind == DM_PARTY_DEVICE && party->node != node) return 0; struct dm_party current; if (dm_party_resolve(inst, node, party->kind == DM_PARTY_GROUP ? DM_MODE_GROUPS : DM_MODE_DEVICE, ¤t)) return -1; return dm_party_equal(party, ¤t); } int dm_party_private(struct UTUN_INSTANCE* inst, const struct dm_party* party, uint8_t out[32]) { if (!inst || !out || !party_valid(party)) return identity_error(inst, "invalid secret request", 0); if (party->kind == DM_PARTY_DEVICE) { if (party->node != inst->node_id || memcmp(party->x, inst->my_keys.public_key, 32) || memcmp(party->ed, inst->my_ed25519_pubkey, 32)) return identity_error(inst, "device secret unavailable", party->node); memcpy(out, inst->my_keys.private_key, 32); return 0; } struct devgroup_record record; uint8_t ed[32] = {0}, x[32] = {0}; int rc = devgroup_load_local(inst, &record, ed, x); int found = rc == 1 && !memcmp(party->ed, record.ed, 32) && !memcmp(party->x, record.x, 32); if (found) memcpy(out, x, 32); OPENSSL_cleanse(ed, sizeof(ed)); OPENSSL_cleanse(x, sizeof(x)); if (!found) return identity_error(inst, "pinned subgroup secret unavailable", inst->node_id); return 0; } uint64_t dm_identity_conv_id(enum dm_mode mode, const struct dm_party parties[2]) { if (!parties || (mode != DM_MODE_DEVICE && mode != DM_MODE_GROUPS) || !party_valid(parties) || !party_valid(parties + 1) || dm_party_equal(parties, parties + 1)) { identity_error(NULL, "invalid conversation parties", 0); return 0; } if (mode == DM_MODE_DEVICE) { if (parties[0].kind != DM_PARTY_DEVICE || parties[1].kind != DM_PARTY_DEVICE || parties[0].node == parties[1].node) { identity_error(NULL, "device mode requires distinct devices", 0); return 0; } return dm_derive_conv_id(parties[0].node, parties[1].node); } const char domain[] = "utun-dm-groups-v1"; uint8_t wire[2][DM_PARTY_WIRE_SIZE], data[sizeof(domain) + 2 * DM_PARTY_WIRE_SIZE], hash[32]; if (dm_party_encode(parties, wire[0]) || dm_party_encode(parties + 1, wire[1])) return 0; unsigned first = memcmp(wire[0], wire[1], DM_PARTY_WIRE_SIZE) > 0; memcpy(data, domain, sizeof(domain)); memcpy(data + sizeof(domain), wire[first], DM_PARTY_WIRE_SIZE); memcpy(data + sizeof(domain) + DM_PARTY_WIRE_SIZE, wire[1 - first], DM_PARTY_WIRE_SIZE); SHA256(data, sizeof(data), hash); uint64_t id = 0; for (unsigned i = 0; i < 8; i++) id |= (uint64_t)hash[i] << (8 * i); id &= INT64_MAX; return id ? id : 1; } /* Добавление не молча обрезает snapshot и не допускает принадлежность обеим сторонам. */ static int targets_add(struct dm_target targets[DM_TARGET_MAX], size_t* count, size_t capacity, uint64_t node, unsigned side) { for (size_t i = 0; i < *count; i++) { if (targets[i].node != node) continue; if (targets[i].side == side) return 0; return identity_error(NULL, "device belongs to both parties", node); } if (*count >= capacity || *count >= DM_TARGET_MAX) return identity_error(NULL, "recipient snapshot limit exceeded", node); targets[*count].node = node; targets[*count].side = (uint8_t)side; ++*count; return 0; } static int target_compare(const void* a, const void* b) { uint64_t x = ((const struct dm_target*)a)->node, y = ((const struct dm_target*)b)->node; return x > y ? 1 : x < y ? -1 : 0; } int dm_identity_targets(struct UTUN_INSTANCE* inst, const struct dm_party parties[2], struct dm_target* out, size_t capacity, size_t* count) { if (!inst || !inst->topo_sqlite_db || !parties || !out || !count || !capacity || !party_valid(parties) || !party_valid(parties + 1) || dm_party_equal(parties, parties + 1)) return identity_error(inst, "invalid recipient snapshot arguments", 0); struct dm_target targets[DM_TARGET_MAX]; size_t n = 0; for (unsigned side = 0; side < 2; side++) { const struct dm_party* party = parties + side; if (party->kind == DM_PARTY_DEVICE) { if (dm_party_member(inst, party, party->node) != 1 || targets_add(targets, &n, capacity, party->node, side)) return -1; continue; } size_t before = n; int local = dm_party_member(inst, party, inst->node_id); if (local < 0 || (local && targets_add(targets, &n, capacity, inst->node_id, side))) return -1; sqlite3_stmt* st = NULL; int rc = sqlite3_prepare_v2(inst->topo_sqlite_db, "SELECT node_id FROM nodes WHERE devgroup_ed=?", -1, &st, NULL); if (rc == SQLITE_OK) rc = sqlite3_bind_blob(st, 1, party->ed, 32, SQLITE_STATIC); if (rc == SQLITE_OK) while ((rc = sqlite3_step(st)) == SQLITE_ROW) { uint64_t node = (uint64_t)sqlite3_column_int64(st, 0); if (node == inst->node_id) continue; int member = dm_party_member(inst, party, node); if (member != 1 || targets_add(targets, &n, capacity, node, side)) { sqlite3_finalize(st); return identity_error(inst, "inconsistent recipient membership", node); } } int final = sqlite3_finalize(st); if (rc != SQLITE_DONE || final != SQLITE_OK) return identity_error(inst, "recipient query failed", 0); if (n == before) return identity_error(inst, "pinned party has no known devices", 0); } qsort(targets, n, sizeof(targets[0]), target_compare); memcpy(out, targets, n * sizeof(targets[0])); *count = n; DEBUG_DEBUG(DEBUG_CATEGORY_DM, "DM identity: recipient snapshot complete count=%zu", n); return 0; }