Browse Source

Preserve invitation certificates and accept confirmed roots without profile changes

master
evgeny 5 days ago
parent
commit
a9a65d53bf
  1. 48
      src/chat/chat_feedback.c
  2. 30
      src/chat/member_sync.c

48
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;
}
/* Создаёт одну таблицу подписанных состояний и индекс обхода листьев. */

30
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);

Loading…
Cancel
Save