Files
utun2/tests/test_dm_identity.c
T

151 lines
9.8 KiB
C

#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <sqlite3.h>
#include <openssl/rand.h>
#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;
}