#include #include #include #include #include #include "utun_instance.h" #include "routing_layer/topo_node.h" #include "routing_layer/topo_node_sqlite.h" #include "routing_layer/devgroup.h" #include "dm/dm_identity.h" #include "dm/dm_crypto.h" /* Полноценные ключи/удостоверения без сети: офлайн-узлы входят в snapshot. */ static void device(struct TOPO_NODE* node, struct SC_MYKEYS* keys, uint8_t ed_private[32]) { memset(node, 0, sizeof(*node)); assert(sc_generate_keypair(keys) == SC_OK && RAND_bytes(ed_private, 32) == 1); assert(!devgroup_public_keys(ed_private, keys->private_key, node->ed25519_public_key, node->public_key)); node->node_id = sc_derive_node_id_from_pubkey(node->public_key); } static void group(struct TOPO_NODE* node, const uint8_t ed[32], const uint8_t x[32], const char* name, uint64_t version) { assert(!devgroup_public_keys(ed, x, node->devgroup.ed, node->devgroup.x)); assert(!devgroup_sign(&node->devgroup, ed, name, version, version, node->node_id, node->public_key, node->ed25519_public_key)); } /* Устойчивое собственное состояние, которое читает штатный devgroup_load_local. */ static void local_group(struct UTUN_INSTANCE* inst, const struct devgroup_record* record, const uint8_t ed[32], const uint8_t x[32]) { uint8_t wire[DEVGROUP_WIRE_MAX]; int n = devgroup_encode(record, wire, sizeof(wire)); assert(n > 0); sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(inst->topo_sqlite_db, "INSERT INTO devgroup_local(node_id,ed_priv,x_priv,record) VALUES(?,?,?,?)", -1, &st, NULL) == SQLITE_OK); assert(sqlite3_bind_int64(st, 1, (sqlite3_int64)inst->node_id) == SQLITE_OK); assert(sqlite3_bind_blob(st, 2, ed, 32, SQLITE_STATIC) == SQLITE_OK); assert(sqlite3_bind_blob(st, 3, x, 32, SQLITE_STATIC) == SQLITE_OK); assert(sqlite3_bind_blob(st, 4, wire, n, SQLITE_STATIC) == SQLITE_OK); assert(sqlite3_step(st) == SQLITE_DONE && sqlite3_finalize(st) == SQLITE_OK); } static int deny_nodes(void* arg, int action, const char* table, const char* column, const char* db, const char* trigger) { (void)arg; (void)column; (void)db; (void)trigger; return action == SQLITE_READ && table && !strcmp(table, "nodes") ? SQLITE_DENY : SQLITE_OK; } static int has_target(const struct dm_target* targets, size_t count, uint64_t node, unsigned side) { for (size_t i = 0; i < count; i++) if (targets[i].node == node && targets[i].side == side) return 1; return 0; } int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG); struct UTUN_INSTANCE inst = {0}; assert(sqlite3_open(":memory:", &inst.topo_sqlite_db) == SQLITE_OK && !topo_node_sqlite_init(inst.topo_sqlite_db)); struct TOPO_NODE nodes[5]; struct SC_MYKEYS keys[5]; uint8_t ed_private[5][32]; uint8_t group_ed[2][32], group_x[2][32]; assert(RAND_bytes((uint8_t*)group_ed, sizeof(group_ed)) == 1 && RAND_bytes((uint8_t*)group_x, sizeof(group_x)) == 1); for (unsigned i = 0; i < 5; i++) { device(nodes + i, keys + i, ed_private[i]); if (i < 4) group(nodes + i, group_ed[i / 2], group_x[i / 2], i < 2 ? "Own devices" : "Peer devices", 1); assert(!devgroup_project(inst.topo_sqlite_db, nodes + i)); } inst.node_id = nodes[0].node_id; inst.my_keys = keys[0]; memcpy(inst.my_ed25519_pubkey, nodes[0].ed25519_public_key, 32); memcpy(inst.my_ed25519_privkey, ed_private[0], 32); local_group(&inst, &nodes[0].devgroup, group_ed[0], group_x[0]); struct dm_party direct[2], parties[2], reversed[2], standalone; assert(!dm_party_resolve(&inst, nodes[0].node_id, DM_MODE_DEVICE, direct)); assert(!dm_party_resolve(&inst, nodes[2].node_id, DM_MODE_DEVICE, direct + 1)); assert(dm_identity_conv_id(DM_MODE_DEVICE, direct) == dm_derive_conv_id(nodes[0].node_id, nodes[2].node_id)); struct dm_target targets[DM_TARGET_MAX] = {{0}}; size_t count = 99; assert(!dm_identity_targets(&inst, direct, targets, DM_TARGET_MAX, &count) && count == 2); assert(has_target(targets, count, nodes[0].node_id, 0) && has_target(targets, count, nodes[2].node_id, 1)); assert(!dm_party_resolve(&inst, nodes[1].node_id, DM_MODE_DEVICE, direct + 1)); assert(dm_identity_conv_id(DM_MODE_DEVICE, direct) == dm_derive_conv_id(nodes[0].node_id, nodes[1].node_id)); assert(!dm_identity_targets(&inst, direct, targets, DM_TARGET_MAX, &count) && count == 2); assert(!dm_party_resolve(&inst, nodes[0].node_id, DM_MODE_GROUPS, parties)); assert(!dm_party_resolve(&inst, nodes[2].node_id, DM_MODE_GROUPS, parties + 1)); assert(parties[0].kind == DM_PARTY_GROUP && parties[1].kind == DM_PARTY_GROUP); reversed[0] = parties[1]; reversed[1] = parties[0]; uint64_t conv = dm_identity_conv_id(DM_MODE_GROUPS, parties); assert(conv && conv == dm_identity_conv_id(DM_MODE_GROUPS, reversed)); assert(!dm_identity_conv_id(DM_MODE_DEVICE, parties)); assert(!dm_identity_targets(&inst, parties, targets, DM_TARGET_MAX, &count) && count == 4); for (unsigned i = 0; i < 4; i++) assert(has_target(targets, count, nodes[i].node_id, i / 2)); for (size_t i = 1; i < count; i++) assert(targets[i - 1].node < targets[i].node); assert(dm_party_member(&inst, parties, nodes[4].node_id) == 0); uint8_t secret[32]; assert(!dm_party_private(&inst, parties, secret) && !memcmp(secret, group_x[0], 32)); memset(secret, 0x5a, sizeof(secret)); uint8_t before_secret[32]; memcpy(before_secret, secret, 32); assert(dm_party_private(&inst, parties + 1, secret) < 0 && !memcmp(secret, before_secret, 32)); struct dm_target snapshot[DM_TARGET_MAX]; memcpy(snapshot, targets, sizeof(targets)); size_t old_count = count; assert(dm_identity_targets(&inst, parties, targets, 3, &count) < 0); assert(count == old_count && !memcmp(targets, snapshot, sizeof(targets))); assert(!dm_party_resolve(&inst, nodes[4].node_id, DM_MODE_GROUPS, &standalone) && standalone.kind == DM_PARTY_DEVICE); reversed[0] = parties[0]; reversed[1] = standalone; assert(!dm_identity_targets(&inst, reversed, targets, DM_TARGET_MAX, &count) && count == 3); reversed[0] = standalone; reversed[1] = parties[1]; assert(!dm_identity_targets(&inst, reversed, targets, DM_TARGET_MAX, &count) && count == 3); reversed[0] = parties[0]; reversed[1] = parties[0]; assert(!dm_identity_conv_id(DM_MODE_GROUPS, reversed)); assert(dm_identity_targets(&inst, reversed, targets, DM_TARGET_MAX, &count) < 0); reversed[1].x[0] ^= 1; assert(!dm_identity_conv_id(DM_MODE_GROUPS, reversed)); assert(dm_identity_targets(&inst, reversed, targets, DM_TARGET_MAX, &count) < 0); group(nodes + 2, group_ed[1], group_x[1], "Renamed peer devices", 2); assert(!devgroup_project(inst.topo_sqlite_db, nodes + 2)); assert(!dm_party_resolve(&inst, nodes[3].node_id, DM_MODE_GROUPS, reversed + 1)); assert(dm_party_equal(reversed + 1, parties + 1) && conv == dm_identity_conv_id(DM_MODE_GROUPS, parties)); group(nodes + 4, group_ed[1], group_x[1], "Renamed peer devices", 2); assert(!devgroup_project(inst.topo_sqlite_db, nodes + 4)); assert(!dm_identity_targets(&inst, parties, targets, DM_TARGET_MAX, &count) && count == 5); assert(old_count == 4 && !has_target(snapshot, old_count, nodes[4].node_id, 1)); struct dm_party decoded = parties[0]; uint8_t wire[DM_PARTY_WIRE_SIZE]; assert(!dm_party_encode(direct, wire) && !dm_party_decode(wire, &decoded) && dm_party_equal(direct, &decoded)); wire[1] ^= 1; assert(dm_party_decode(wire, &decoded) < 0 && dm_party_equal(direct, &decoded)); assert(!dm_party_encode(parties, wire) && !dm_party_decode(wire, &decoded) && dm_party_equal(parties, &decoded)); wire[0] = 42; assert(dm_party_decode(wire, &decoded) < 0 && dm_party_equal(parties, &decoded)); /* Поддельная принадлежность не превращается в fallback на устройство. */ struct TOPO_NODE bad = nodes[1]; bad.devgroup.member_sig[0] ^= 1; assert(!devgroup_project(inst.topo_sqlite_db, &bad)); decoded = parties[1]; assert(dm_party_resolve(&inst, bad.node_id, DM_MODE_GROUPS, &decoded) < 0 && dm_party_equal(&decoded, parties + 1)); assert(dm_identity_targets(&inst, parties, targets, DM_TARGET_MAX, &count) < 0); assert(!devgroup_project(inst.topo_sqlite_db, nodes + 1)); /* Одинаковый Ed ID с разными X ключами не объединяет две криптографические группы. */ bad = nodes[2]; uint8_t other_x[32]; assert(RAND_bytes(other_x, 32) == 1); group(&bad, group_ed[1], other_x, "Conflicting key", 3); assert(!devgroup_project(inst.topo_sqlite_db, &bad)); assert(dm_party_resolve(&inst, nodes[3].node_id, DM_MODE_GROUPS, &decoded) < 0); assert(!devgroup_project(inst.topo_sqlite_db, nodes + 2)); assert(sqlite3_set_authorizer(inst.topo_sqlite_db, deny_nodes, NULL) == SQLITE_OK); decoded = parties[0]; assert(dm_party_resolve(&inst, nodes[2].node_id, DM_MODE_GROUPS, &decoded) < 0 && dm_party_equal(&decoded, parties)); assert(sqlite3_set_authorizer(inst.topo_sqlite_db, NULL, NULL) == SQLITE_OK); /* Выход удаляет секрет: закреплённая сторона не должна незаметно стать DEVICE. */ assert(sqlite3_exec(inst.topo_sqlite_db, "DELETE FROM devgroup_local", NULL, NULL, NULL) == SQLITE_OK); assert(dm_party_private(&inst, parties, secret) < 0); assert(!dm_party_resolve(&inst, inst.node_id, DM_MODE_GROUPS, &decoded) && decoded.kind == DM_PARTY_DEVICE); assert(!dm_party_equal(&decoded, parties) && dm_identity_conv_id(DM_MODE_GROUPS, parties) == conv); assert(sqlite3_close(inst.topo_sqlite_db) == SQLITE_OK); puts("PASS DM identity: device/group modes, own devices, fallback, offline snapshots, rename, signatures, SQL errors and leave"); return 0; }