diff --git a/src/chat/chat_feedback.c b/src/chat/chat_feedback.c index f056fdb8..6c55ee02 100644 --- a/src/chat/chat_feedback.c +++ b/src/chat/chat_feedback.c @@ -102,7 +102,7 @@ static int fb_key(uint64_t channel, const struct chat_feedback_record* r, merkle return 0; } -/* Членство и привязка Ed25519 к X25519 подтверждаются существующими подписями мембера. */ +/* Полный мембер, доверенный ключ узла, join_sig и подпись дерева приглашений обязательны. */ static int fb_author_key(struct UTUN_INSTANCE* inst, const char* channel, uint64_t author, uint8_t key[32]) { char sql[384]; snprintf(sql, sizeof(sql), "SELECT x25519_pubkey,ed25519_pubkey,signed_by,signature,join_sig,join_ts" @@ -111,24 +111,38 @@ static int fb_author_key(struct UTUN_INSTANCE* inst, const char* channel, uint64 if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) return fb_db_error(inst, channel, "author lookup"); sqlite3_bind_int64(st, 1, (sqlite3_int64)author); int rc = sqlite3_step(st), ok = 0; - if (rc == SQLITE_ROW && sqlite3_column_bytes(st, 0) == 32 && sqlite3_column_bytes(st, 1) == 32 - && sqlite3_column_bytes(st, 3) == 64 && sqlite3_column_bytes(st, 4) == 64 && sqlite3_column_int64(st, 5) > 0) { - struct ms_member_rec member = { .node_id = author, .x25519 = sqlite3_column_blob(st, 0), - .ed25519 = sqlite3_column_blob(st, 1), .signed_by = (uint64_t)sqlite3_column_int64(st, 2), - .signature = sqlite3_column_blob(st, 3), .join_sig = sqlite3_column_blob(st, 4), - .join_ts = (uint64_t)sqlite3_column_int64(st, 5) }; - uint8_t x[32], ed[32], body[112]; - int n = topo_node_sqlite_channel_get(db, channel, NULL, 0, NULL, x, ed, NULL); - if (n == 0) n = member_sync_build_join_msg(x, ed, author, member.x25519, member.join_ts, body, sizeof(body)); - ok = n == 112 && sc_derive_node_id_from_pubkey(member.x25519) == author - && member_sync_verify_signature(db, channel, &member) == 1 - && sc_ed25519_verify(member.ed25519, body, n, member.join_sig) == SC_OK; - if (ok) memcpy(key, member.ed25519, 32); - } + int x_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 0) : 0, ed_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 1) : 0; + int tree_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 3) : 0, join_bytes = rc == SQLITE_ROW ? sqlite3_column_bytes(st, 4) : 0; + const char* reason = "member absent"; + if (rc != SQLITE_ROW) goto done; + reason = "invalid member fields"; + if (x_bytes != 32 || ed_bytes != 32 || tree_bytes != 64 + || join_bytes != 64 || sqlite3_column_int64(st, 5) <= 0) goto done; + struct ms_member_rec member = { .node_id = author, .x25519 = sqlite3_column_blob(st, 0), + .ed25519 = sqlite3_column_blob(st, 1), .signed_by = (uint64_t)sqlite3_column_int64(st, 2), + .signature = sqlite3_column_blob(st, 3), .join_sig = sqlite3_column_blob(st, 4), + .join_ts = (uint64_t)sqlite3_column_int64(st, 5) }; + reason = "node_id/X25519 mismatch"; + if (sc_derive_node_id_from_pubkey(member.x25519) != author) goto done; + uint8_t trusted[32], x[32], ed[32], body[112]; + reason = "trusted Ed25519 key unavailable"; + if (topo_node_sqlite_get_ed25519_pubkey(db, author, trusted) != 0) goto done; + reason = "trusted Ed25519 key mismatch"; + if (memcmp(trusted, member.ed25519, 32)) goto done; + reason = "channel keys unavailable"; + if (topo_node_sqlite_channel_get(db, channel, NULL, 0, NULL, x, ed, NULL) != 0) goto done; + reason = "invalid join signature"; + int bytes = member_sync_build_join_msg(x, ed, author, member.x25519, member.join_ts, body, sizeof(body)); + if (bytes != 112 || sc_ed25519_verify(member.ed25519, body, bytes, member.join_sig) != SC_OK) goto done; + reason = "invalid or unresolved invitation signature"; + if (member_sync_verify_signature(db, channel, &member) != 1) goto done; + memcpy(key, member.ed25519, 32); ok = 1; +done: sqlite3_finalize(st); if (ok) return 0; - DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "feedback: author not verified ch=%s node=%016llx lookup=%d", - channel, (unsigned long long)author, rc); return -1; + DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, + "feedback: author not verified ch=%s node=%016llx reason=%s rc=%d x_bytes=%d ed_bytes=%d tree_bytes=%d join_bytes=%d", + channel, (unsigned long long)author, reason, rc, x_bytes, ed_bytes, tree_bytes, join_bytes); return -1; } /* Создаёт одну таблицу подписанных состояний и индекс обхода листьев. */ diff --git a/src/chat/member_sync.c b/src/chat/member_sync.c index eccc82f2..1fe726c5 100644 --- a/src/chat/member_sync.c +++ b/src/chat/member_sync.c @@ -541,6 +541,7 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, /* ── Блок A (мембер): verify update_sig (или join_sig) + сравнить ver ── */ int is_local = (from_peer == inst->node_id); int identity_ok = 1; + int has_signature = m->signature && !_sig_is_zero64(m->signature); if (!is_local) { /* жёсткая привязка идентичности: node_id = derive(x25519), ed25519 — доверенный из nodes */ if (sc_derive_node_id_from_pubkey(m->x25519) != m->node_id) { @@ -555,7 +556,7 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, } } /* подпись дерева приглашений: -1 = родитель ещё не пришёл (defer до verify_local), 0 = явная подделка */ - if (identity_ok && m->signature && !_sig_is_zero64(m->signature)) { + if (identity_ok && has_signature) { int sv = member_sync_verify_signature(db, ch_id, m); if (sv == 0) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record invalid tree-signature (forgery) nid=0x%016llx signed_by=0x%016llx ns=%s", @@ -658,12 +659,16 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, return MT_ERR_DATA; } + /* Подпись приглашения не зависит от версии профиля: подтверждаем уже сохранённого мембера. */ + int signature_changed = has_local && has_signature && _sig_is_zero64(local_signature) + && member_sync_verify_signature(db, ch_id, m) == 1; + /* ── запись изменённых блоков ── */ if (block_a_changed) { const uint8_t* jsig = m->join_sig ? m->join_sig : (has_local ? local_join_sig : NULL); - /* дерево приглашений: если в обновлении подписи нет — сохраняем локальную (set один раз при join/create) */ - uint64_t w_signed_by = m->signature ? m->signed_by : local_signed_by; - const uint8_t* w_signature = m->signature ? m->signature : (has_local ? local_signature : NULL); + /* Нулевой блок на проводе означает отсутствие подписи и не стирает сохранённое подтверждение. */ + uint64_t w_signed_by = has_signature ? m->signed_by : local_signed_by; + const uint8_t* w_signature = has_signature ? m->signature : (has_local ? local_signature : NULL); if (topo_node_sqlite_member_block_put(db, ch_id, m->node_id, jsig, m->join_ts, m->update_sig, m->update_ts, m->x25519, m->ed25519, m->userinfo ? m->userinfo : "", NULL, w_signed_by, w_signature) != 0) { @@ -681,6 +686,23 @@ static int member_apply_record(struct UTUN_INSTANCE* inst, const char* ch_id, (unsigned long long)m->node_id); return MT_ERR_IO; } } + if (signature_changed && !block_a_changed) { + char sql[256]; sqlite3_stmt* st = NULL; + snprintf(sql, sizeof(sql), "UPDATE \"%s\" SET signed_by=?,signature=? WHERE node_id=?", peers_tbl); + int rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL); + if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 1, (sqlite3_int64)m->signed_by); + if (rc == SQLITE_OK) rc = sqlite3_bind_blob(st, 2, m->signature, 64, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_bind_int64(st, 3, (sqlite3_int64)m->node_id); + if (rc == SQLITE_OK) rc = sqlite3_step(st); + sqlite3_finalize(st); + if (rc != SQLITE_DONE) { + DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: invitation update failed ns=%s node=%016llx rc=%d: %s", + MS_ID, ch_id, (unsigned long long)m->node_id, rc, sqlite3_errmsg(db)); return MT_ERR_IO; + } + changed = 1; + } + if (signature_changed) DEBUG_INFO(DEBUG_CATEGORY_MEMBER_SYNC, "%s: invitation confirmed ns=%s node=%016llx signer=%016llx", + MS_ID, ch_id, (unsigned long long)m->node_id, (unsigned long long)m->signed_by); if (block_b_changed) { if (topo_node_sqlite_member_owner_put(db, ch_id, m->node_id, m->adm_tags, m->adm_tags_sig, adm_storage) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: apply_record owner_put FAILED ch=%s nid=0x%016llx", MS_ID, ch_id, (unsigned long long)m->node_id);