|
|
|
@ -502,6 +502,137 @@ int member_sync_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, |
|
|
|
return (changed ? MS_APPLY_CHANGED : 0) | (stale ? MS_APPLY_STALE : 0); |
|
|
|
return (changed ? MS_APPLY_CHANGED : 0) | (stale ? MS_APPLY_STALE : 0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ── Верификация локальной записи при чтении из БД ──
|
|
|
|
|
|
|
|
Единственный сценарий удаления мембера: подпись присутствует и невалидна, |
|
|
|
|
|
|
|
либо node_id != derive(x25519). Отсутствие подписи = «не верифицировано», не удаляем. */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int member_sync_verify_local_record(struct sqlite3* db, const char* ch_id, const struct ms_member_rec* m) { |
|
|
|
|
|
|
|
if (!db || !ch_id || !m || !m->x25519 || !m->ed25519) return 1; /* нет данных — не битая */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* жёсткая привязка идентичности */ |
|
|
|
|
|
|
|
if (sc_derive_node_id_from_pubkey(m->x25519) != m->node_id) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: verify_local — node_id/x25519 mismatch nid=0x%016llx ns=%s", |
|
|
|
|
|
|
|
MS_ID, (unsigned long long)m->node_id, ch_id); |
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* update_sig присутствует — верифицируем */ |
|
|
|
|
|
|
|
if (m->update_sig && !_sig_is_zero64(m->update_sig)) { |
|
|
|
|
|
|
|
uint8_t vmsg[8192]; |
|
|
|
|
|
|
|
int vlen = member_sync_build_update_msg(m->join_sig, m->update_ts, |
|
|
|
|
|
|
|
m->userinfo ? m->userinfo : "", vmsg, (int)sizeof(vmsg)); |
|
|
|
|
|
|
|
if (vlen < 0 || !m->join_sig) return 0; |
|
|
|
|
|
|
|
EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, m->ed25519, 32); |
|
|
|
|
|
|
|
if (!pkey) return 0; |
|
|
|
|
|
|
|
EVP_MD_CTX* ver = EVP_MD_CTX_new(); |
|
|
|
|
|
|
|
int ok = ver && (EVP_DigestVerifyInit(ver, NULL, NULL, NULL, pkey) == 1) |
|
|
|
|
|
|
|
&& (EVP_DigestVerify(ver, m->update_sig, 64, vmsg, (size_t)vlen) == 1); |
|
|
|
|
|
|
|
if (ver) EVP_MD_CTX_free(ver); |
|
|
|
|
|
|
|
EVP_PKEY_free(pkey); |
|
|
|
|
|
|
|
if (!ok) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: verify_local — invalid update_sig nid=0x%016llx ns=%s", |
|
|
|
|
|
|
|
MS_ID, (unsigned long long)m->node_id, ch_id); |
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else if (m->join_sig && !_sig_is_zero64(m->join_sig)) { |
|
|
|
|
|
|
|
/* join-only — верифицируем join_sig напрямую */ |
|
|
|
|
|
|
|
if (!_verify_join_sig(db, ch_id, m)) return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* adm_tags присутствует — верифицируем adm_tags_sig */ |
|
|
|
|
|
|
|
if (m->adm_tags && m->adm_tags[0] && m->adm_tags_sig && !_sig_is_zero64(m->adm_tags_sig)) { |
|
|
|
|
|
|
|
uint8_t ch_ed_pub[32] = {0}; |
|
|
|
|
|
|
|
if (topo_node_sqlite_channel_get(db, ch_id, NULL, 0, NULL, NULL, ch_ed_pub, NULL) != 0) |
|
|
|
|
|
|
|
return 1; /* нет канального ключа — не можем проверить, не удаляем */ |
|
|
|
|
|
|
|
uint8_t amsg[264]; size_t aoff = 0; |
|
|
|
|
|
|
|
size_t atl2 = strlen(m->adm_tags); |
|
|
|
|
|
|
|
if (atl2 > 191) atl2 = 191; |
|
|
|
|
|
|
|
memcpy(amsg + aoff, m->adm_tags, atl2); aoff += atl2; |
|
|
|
|
|
|
|
memcpy(amsg + aoff, &m->node_id, 8); aoff += 8; |
|
|
|
|
|
|
|
EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, ch_ed_pub, 32); |
|
|
|
|
|
|
|
if (!pkey) return 1; |
|
|
|
|
|
|
|
EVP_MD_CTX* vctx = EVP_MD_CTX_new(); |
|
|
|
|
|
|
|
int ok = vctx && (EVP_DigestVerifyInit(vctx, NULL, NULL, NULL, pkey) == 1) |
|
|
|
|
|
|
|
&& (EVP_DigestVerify(vctx, m->adm_tags_sig, 64, amsg, aoff) == 1); |
|
|
|
|
|
|
|
if (vctx) EVP_MD_CTX_free(vctx); |
|
|
|
|
|
|
|
EVP_PKEY_free(pkey); |
|
|
|
|
|
|
|
if (!ok) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: verify_local — invalid adm_tags_sig nid=0x%016llx ns=%s tags=%s", |
|
|
|
|
|
|
|
MS_ID, (unsigned long long)m->node_id, ch_id, m->adm_tags); |
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return 1; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Прогоняет все записи канала: битая → удалить + пересчитать хеши. */ |
|
|
|
|
|
|
|
int member_sync_verify_and_purge(struct UTUN_INSTANCE* inst, const char* ch_id) { |
|
|
|
|
|
|
|
if (!inst || !ch_id) return 0; |
|
|
|
|
|
|
|
sqlite3* db = _db(inst); if (!db) return 0; |
|
|
|
|
|
|
|
char peers_tbl[128]; _peers_table(ch_id, peers_tbl, sizeof(peers_tbl)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sqlite3_stmt* st = NULL; |
|
|
|
|
|
|
|
char sql[512]; snprintf(sql, sizeof(sql), |
|
|
|
|
|
|
|
"SELECT node_id, x25519_pubkey, ed25519_pubkey," |
|
|
|
|
|
|
|
" join_sig, join_ts, update_sig, update_ts, userinfo, adm_tags, adm_tags_sig, source" |
|
|
|
|
|
|
|
" FROM \"%s\" ORDER BY node_id ASC", peers_tbl); |
|
|
|
|
|
|
|
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int purged = 0; |
|
|
|
|
|
|
|
while (sqlite3_step(st) == SQLITE_ROW) { |
|
|
|
|
|
|
|
uint64_t nid = (uint64_t)sqlite3_column_int64(st, 0); |
|
|
|
|
|
|
|
const uint8_t* x25 = (const uint8_t*)sqlite3_column_blob(st, 1); |
|
|
|
|
|
|
|
const uint8_t* ed = (const uint8_t*)sqlite3_column_blob(st, 2); |
|
|
|
|
|
|
|
const uint8_t* jsig = (const uint8_t*)sqlite3_column_blob(st, 3); |
|
|
|
|
|
|
|
uint64_t jts = (uint64_t)sqlite3_column_int64(st, 4); |
|
|
|
|
|
|
|
const uint8_t* usig = (const uint8_t*)sqlite3_column_blob(st, 5); |
|
|
|
|
|
|
|
uint64_t uts = (uint64_t)sqlite3_column_int64(st, 6); |
|
|
|
|
|
|
|
const char* nm = (const char*)sqlite3_column_text(st, 7); |
|
|
|
|
|
|
|
const char* atags = (const char*)sqlite3_column_text(st, 8); |
|
|
|
|
|
|
|
const uint8_t* atsig = (const uint8_t*)sqlite3_column_blob(st, 9); |
|
|
|
|
|
|
|
int src = sqlite3_column_int(st, 10); |
|
|
|
|
|
|
|
if (!x25 || !ed) continue; |
|
|
|
|
|
|
|
if (src != 0) continue; /* плейсхолдер topo_group — не верифицируем */ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct ms_member_rec m; |
|
|
|
|
|
|
|
memset(&m, 0, sizeof(m)); |
|
|
|
|
|
|
|
m.node_id = nid; |
|
|
|
|
|
|
|
m.x25519 = x25; m.ed25519 = ed; |
|
|
|
|
|
|
|
m.join_sig = jsig; m.join_ts = jts; |
|
|
|
|
|
|
|
m.update_sig = usig; m.update_ts = uts; |
|
|
|
|
|
|
|
m.userinfo = nm; |
|
|
|
|
|
|
|
m.adm_tags = atags; m.adm_tags_sig = atsig; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!member_sync_verify_local_record(db, ch_id, &m)) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: verify_and_purge — PURGE broken record nid=0x%016llx ns=%s", |
|
|
|
|
|
|
|
MS_ID, (unsigned long long)nid, ch_id); |
|
|
|
|
|
|
|
topo_node_sqlite_member_del(db, ch_id, nid); |
|
|
|
|
|
|
|
merkle_sync_recompute_path(inst, ch_id, nid); |
|
|
|
|
|
|
|
purged++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sqlite3_finalize(st); |
|
|
|
|
|
|
|
if (purged > 0) |
|
|
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "%s: verify_and_purge — purged %d broken records ns=%s", |
|
|
|
|
|
|
|
MS_ID, purged, ch_id); |
|
|
|
|
|
|
|
return purged; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Прогнать очистку битых записей по всем каналам. */ |
|
|
|
|
|
|
|
void member_sync_verify_and_purge_all(struct UTUN_INSTANCE* inst) { |
|
|
|
|
|
|
|
sqlite3* db = _db(inst); if (!db) return; |
|
|
|
|
|
|
|
sqlite3_stmt* st = NULL; |
|
|
|
|
|
|
|
if (sqlite3_prepare_v2(db, |
|
|
|
|
|
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'peers_%'", |
|
|
|
|
|
|
|
-1, &st, NULL) != SQLITE_OK) return; |
|
|
|
|
|
|
|
while (sqlite3_step(st) == SQLITE_ROW) { |
|
|
|
|
|
|
|
const char* tbl = (const char*)sqlite3_column_text(st, 0); |
|
|
|
|
|
|
|
if (!tbl || strncmp(tbl, "peers_", 6) != 0) continue; |
|
|
|
|
|
|
|
member_sync_verify_and_purge(inst, tbl + 6); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
sqlite3_finalize(st); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, |
|
|
|
static int _member_apply_items(void* ctx, const char* ns, uint64_t from_peer, |
|
|
|
const uint8_t* data, size_t len) { |
|
|
|
const uint8_t* data, size_t len) { |
|
|
|
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)ctx; |
|
|
|
struct UTUN_INSTANCE* inst = (struct UTUN_INSTANCE*)ctx; |
|
|
|
@ -629,6 +760,8 @@ int member_sync_init(struct UTUN_INSTANCE* inst) { |
|
|
|
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: init — inst is NULL", MS_ID); return -1; } |
|
|
|
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: init — inst is NULL", MS_ID); return -1; } |
|
|
|
int rc = merkle_sync_init(inst, 0x31, &g_member_ops, inst); |
|
|
|
int rc = merkle_sync_init(inst, 0x31, &g_member_ops, inst); |
|
|
|
if (rc != 0) return rc; |
|
|
|
if (rc != 0) return rc; |
|
|
|
|
|
|
|
/* очистка битых записей во всех каналах перед построением дерева */ |
|
|
|
|
|
|
|
member_sync_verify_and_purge_all(inst); |
|
|
|
_rebuild_all_trees(inst); |
|
|
|
_rebuild_all_trees(inst); |
|
|
|
/* подписка на BGP-события существующих chat-групп */ |
|
|
|
/* подписка на BGP-события существующих chat-групп */ |
|
|
|
if (inst->topo_groups && inst->topo_groups->group_list) { |
|
|
|
if (inst->topo_groups && inst->topo_groups->group_list) { |
|
|
|
@ -660,6 +793,7 @@ void member_sync_destroy(struct UTUN_INSTANCE* inst) { |
|
|
|
|
|
|
|
|
|
|
|
int member_sync_start(struct UTUN_INSTANCE* inst, uint64_t peer, |
|
|
|
int member_sync_start(struct UTUN_INSTANCE* inst, uint64_t peer, |
|
|
|
const char* ch_id, merkle_sync_done_cb done_cb, void* arg) { |
|
|
|
const char* ch_id, merkle_sync_done_cb done_cb, void* arg) { |
|
|
|
|
|
|
|
member_sync_verify_and_purge(inst, ch_id); |
|
|
|
return merkle_sync_start(inst, peer, ch_id, done_cb, arg); |
|
|
|
return merkle_sync_start(inst, peer, ch_id, done_cb, arg); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|