fix(chat): verify incoming JOIN profiles before local insertion
This commit is contained in:
1 parent
054ade0dcd
commit
ce3287ac94
5 files changed
+89
-37
No files matched your search
@@ -17,7 +17,7 @@
|
||||
#endif
|
||||
|
||||
void chat_event_set_handler(struct UTUN_INSTANCE* inst, chat_event_handler_fn handler) {
|
||||
if (!inst) return;
|
||||
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "chat_event: handler requires instance"); return; }
|
||||
#ifdef __ANDROID__
|
||||
__android_log_print(ANDROID_LOG_INFO, "utun-evt", "chat_event_set_handler: handler=%p old=%p",
|
||||
(void*)handler, (void*)inst->chat_event_handler);
|
||||
@@ -26,7 +26,11 @@ void chat_event_set_handler(struct UTUN_INSTANCE* inst, chat_event_handler_fn ha
|
||||
}
|
||||
|
||||
void chat_event_post(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len) {
|
||||
if (inst && inst->chat_event_handler) { inst->chat_event_handler(inst, type, data, len); return; }
|
||||
if (!inst || type <= 0 || len < 0 || (len && !data)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "chat_event: invalid event instance=%p type=%d bytes=%d data=%p",
|
||||
(void*)inst, type, len, (const void*)data); return;
|
||||
}
|
||||
if (inst->chat_event_handler) { inst->chat_event_handler(inst, type, data, len); return; }
|
||||
|
||||
static const char* names[] = {
|
||||
[1]="MSG_RECEIVED", [2]="CONNECT_RESULT", [3]="NEW_PEER",
|
||||
@@ -64,7 +68,8 @@ void chat_event_post(struct UTUN_INSTANCE* inst, int type, const uint8_t* data,
|
||||
const char* n = (type >= 1 && type < (int)(sizeof(names) / sizeof(names[0]))) ? names[type] : NULL;
|
||||
if (!n) n = "?";
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "chat_event: %s(%d) data=%d bytes", n, type, len);
|
||||
if (data && len > 0 && type != CHAT_EVT_LOCAL_INVITE) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CHAT, " ", data, len);
|
||||
if (data && len > 0 && type != CHAT_EVT_LOCAL_INVITE && type != CHAT_EVT_INVITE_LINK_READY)
|
||||
log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CHAT, " ", data, len);
|
||||
}
|
||||
|
||||
/* Идентификатор связывает результат с конкретной отправкой, независимо от текущего канала GUI. */
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
* post вызывает его сразу, в том же uasync-потоке; очереди событий здесь нет.
|
||||
* data заимствован только до возврата обработчика. Для передачи в другой поток нужна копия.
|
||||
*
|
||||
* Без обработчика — данные логируются через DEBUG_DEBUG + log_dump; LOCAL_INVITE с ключом не дампится.
|
||||
* Без обработчика — DEBUG_DEBUG + log_dump; LOCAL_INVITE/INVITE_LINK_READY с секретами не дампятся.
|
||||
* Обработчик может удалить канал; stop/destroy сервиса выполняются вне callbacks.
|
||||
* Все вызовы — из uasync-потока.
|
||||
*/
|
||||
|
||||
@@ -61,7 +62,7 @@ extern "C" {
|
||||
#define CHAT_EVT_CALL_ENDED 37 /* [call_id:8][reason:1] */
|
||||
#define CHAT_EVT_CALL_DECLINED 38 /* [call_id:8][reason:1] */
|
||||
#define CHAT_EVT_CALL_STATS 39 /* [call_id:8][rtt:2][buffer:2][tempo:2][dropped:2][underruns:2][min:2][max:2][reserve:2] */
|
||||
#define CHAT_EVT_CALL_ERROR 40 /* [call_id:8][err:1][text:var] */
|
||||
#define CHAT_EVT_CALL_ERROR 40 /* [call_id:8][err:1] */
|
||||
#define CHAT_EVT_DM_MESSAGES 41 /* [conv_id_len:1][conv_id:var][generation:8][JSON:utf8] — история указанного поколения */
|
||||
#define CHAT_EVT_CALL_PATH 42 /* [call_id:8][text:var] — BGP-маршрут до пира звонка */
|
||||
#define CHAT_EVT_RADIO_TALK 43 /* [group_id:8][src_node_id:8][stream_id:2][on:1] — кто-то начал/закончил говорить */
|
||||
@@ -101,7 +102,8 @@ void chat_event_set_handler(struct UTUN_INSTANCE* inst, chat_event_handler_fn ha
|
||||
|
||||
// ── Отправка событий из ядра ──
|
||||
|
||||
// Синхронно передать type и len байт data; память остаётся у отправителя. Формат зависит от CHAT_EVT_*.
|
||||
// Синхронно передать type>0 и len>=0 байт data; inst обязателен,data может быть NULL только при len=0.
|
||||
// Неверные аргументы логируются без доставки;формат CHAT_EVT_* проверяет получатель,память остаётся отправителю.
|
||||
void chat_event_post(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len);
|
||||
|
||||
// Сообщить этап подготовки: state=0 начало,1 готово,2 ошибка; target — ID без префикса, is_dm добавляет "dm:".
|
||||
|
||||
+40
-25
@@ -8,6 +8,7 @@
|
||||
#include "chat_join.h"
|
||||
#include "member_sync.h"
|
||||
#include "chat_event.h"
|
||||
#include "chat_sync.h"
|
||||
|
||||
#include "../utun_instance.h"
|
||||
#include "../routing_layer/topo_node_sqlite.h"
|
||||
@@ -59,11 +60,11 @@ static void _keys_purge_expired(struct UTUN_INSTANCE* inst) {
|
||||
}
|
||||
|
||||
static int _keys_put(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key, uint64_t inviter_id) {
|
||||
_keys_purge_expired(inst);
|
||||
if (!channel_id || !join_key || !inviter_id) {
|
||||
if (!inst || inst->join_stopping || !channel_id || channel_id > INT64_MAX || !join_key || !inviter_id) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid key registration", CJ_ID);
|
||||
return -1;
|
||||
}
|
||||
_keys_purge_expired(inst);
|
||||
for (struct join_key_entry* e = inst->join_keys; e; e = e->next) {
|
||||
if (e->channel_id != channel_id || e->join_key != join_key) continue;
|
||||
if (e->inviter_id != inviter_id) {
|
||||
@@ -103,13 +104,17 @@ static void _keys_free_all(struct UTUN_INSTANCE* inst) {
|
||||
|
||||
int chat_join_serialize_member(const struct join_member_data* m,
|
||||
uint8_t* out, size_t out_sz, size_t* out_len) {
|
||||
if (!m || !out || !out_len || !m->x25519 || !m->ed25519) return -1;
|
||||
if (!m || !out || !out_len || !m->x25519 || !m->ed25519) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat_join: invalid member serialization arguments"); return -1;
|
||||
}
|
||||
size_t ul = strnlen(m->userinfo, sizeof(m->userinfo));
|
||||
if (ul > MS_USERINFO_MAX) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_MEMBER_SYNC, "chat_join: profile exceeds limit bytes=%zu", ul); return -1;
|
||||
}
|
||||
size_t need = 8 + 32 + 32 + 64 + 8 + 64 + 8 + 2 + ul;
|
||||
if (need > out_sz) return -1;
|
||||
if (need > out_sz) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat_join: member buffer too small need=%zu capacity=%zu", need, out_sz); return -1;
|
||||
}
|
||||
size_t off = 0;
|
||||
memcpy(out + off, &m->node_id, 8); off += 8;
|
||||
memcpy(out + off, m->x25519, 32); off += 32;
|
||||
@@ -125,8 +130,11 @@ int chat_join_serialize_member(const struct join_member_data* m,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int chat_join_parse_member(const uint8_t* data, size_t len, struct join_member_data* m) {
|
||||
if (!data || !m || len < 8 + 32 + 32 + 64 + 8 + 64 + 8 + 2) return -1;
|
||||
int chat_join_parse_member(const uint8_t* data, size_t len, struct join_member_data* output) {
|
||||
if (!data || !output || len < 8 + 32 + 32 + 64 + 8 + 64 + 8 + 2) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "chat_join: invalid/short member block bytes=%zu", len); return -1;
|
||||
}
|
||||
struct join_member_data parsed, *m = &parsed;
|
||||
memset(m, 0, sizeof(*m));
|
||||
size_t off = 0;
|
||||
memcpy(&m->node_id, data + off, 8); off += 8;
|
||||
@@ -142,6 +150,7 @@ int chat_join_parse_member(const uint8_t* data, size_t len, struct join_member_d
|
||||
}
|
||||
memcpy(m->userinfo, data + off, ul);
|
||||
m->userinfo[ul] = '\0';
|
||||
*output = parsed;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -150,6 +159,9 @@ int chat_join_parse_member(const uint8_t* data, size_t len, struct join_member_d
|
||||
void chat_join_process_request(struct UTUN_INSTANCE* inst, uint64_t group_id,
|
||||
uint64_t from_node, uint64_t join_key,
|
||||
const uint8_t* member_data, size_t member_len) {
|
||||
if (!inst || inst->join_stopping || !group_id || group_id > INT64_MAX || !from_node) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid/stopped process request", CJ_ID); return;
|
||||
}
|
||||
struct join_member_data m;
|
||||
if (chat_join_parse_member(member_data, member_len, &m) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request parse failed ch=%llu from=%016llx", CJ_ID, (unsigned long long)group_id, (unsigned long long)from_node);
|
||||
@@ -188,6 +200,18 @@ void chat_join_process_request(struct UTUN_INSTANCE* inst, uint64_t group_id,
|
||||
return;
|
||||
}
|
||||
|
||||
/* Локальная запись доверенная: до неё проверяем также профиль и уже известный ключ идентичности. */
|
||||
struct ms_member_rec record = {.node_id = m.node_id, .x25519 = m.x25519, .ed25519 = m.ed25519,
|
||||
.join_sig = m.join_sig, .join_ts = m.join_ts, .update_sig = m.update_sig, .update_ts = m.update_ts,
|
||||
.userinfo = m.userinfo};
|
||||
uint8_t trusted_ed[32];
|
||||
int lookup = topo_node_sqlite_get_ed25519_pubkey(db, m.node_id, trusted_ed);
|
||||
if (lookup == TOPO_SQLITE_LOOKUP_ERROR || (lookup == 0 && memcmp(trusted_ed, m.ed25519, sizeof(trusted_ed))) ||
|
||||
member_sync_verify_local_record(db, ch_id, &record) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: member validation failed ch=%s node=%016llx lookup=%d", CJ_ID,
|
||||
ch_id, (unsigned long long)m.node_id, lookup); return;
|
||||
}
|
||||
|
||||
/* 3. добавить мембера + подписать его pubkey (дерево приглашений) */
|
||||
uint8_t tree_sig[64];
|
||||
if (member_sync_sign_pubkey(inst->my_ed25519_privkey, m.x25519, tree_sig) != 0) {
|
||||
@@ -204,22 +228,8 @@ void chat_join_process_request(struct UTUN_INSTANCE* inst, uint64_t group_id,
|
||||
return;
|
||||
}
|
||||
if (rc & MS_APPLY_CHANGED) {
|
||||
/* джойнер авторизован — добавляем в CHAT-группу сразу, чтобы BGP подключился */
|
||||
uint64_t gid = strtoull(ch_id, NULL, 10);
|
||||
struct TOPO_GROUP* g = topo_groups_find(inst->topo_groups, gid);
|
||||
if (g && g->group_type == TOPO_GROUP_TYPE_CHAT) {
|
||||
struct ETCP_CONN* jc = instance_find_conn(inst, m.node_id);
|
||||
if (jc) {
|
||||
topo_group_new_conn(g, jc);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: joiner 0x%016llx added to group ch=%s for BGP",
|
||||
CJ_ID, (unsigned long long)m.node_id, ch_id);
|
||||
}
|
||||
}
|
||||
/* уведомить GUI инвайтера об успешном присоединении джойнера */
|
||||
uint8_t cevt[20]; memcpy(cevt, &m.node_id, 8);
|
||||
int r = 0; memcpy(cevt + 8, &r, 4);
|
||||
memcpy(cevt + 12, &group_id, 8);
|
||||
chat_event_post(inst, CHAT_EVT_CONNECT_RESULT, cevt, 20);
|
||||
/* member_sync уже запускает сессию после commit; GUI получает общий формат результата. */
|
||||
chat_sync_join_result(inst, 0, group_id, m.node_id, CHAT_JOIN_OK);
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: process_request accepted joiner=0x%016llx ch=%s via=0x%016llx key=%016llx — signed by me",
|
||||
@@ -354,7 +364,7 @@ static void join_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
/* ── Жизненный цикл ── */
|
||||
|
||||
int chat_join_init(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return -1;
|
||||
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: init without instance", CJ_ID); return -1; }
|
||||
inst->join_stopping = 0;
|
||||
if (etcp_router_bind(inst, ETCP_RT_ID_JOIN, join_recv_cb) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: etcp_router_bind failed", CJ_ID);
|
||||
@@ -382,7 +392,7 @@ int chat_join_store_local_key(struct UTUN_INSTANCE* inst, uint64_t channel_id, u
|
||||
|
||||
struct chat_join_registration* chat_join_register_key(struct UTUN_INSTANCE* inst, uint64_t channel_id,
|
||||
uint64_t target_node_id, uint64_t join_key, chat_join_registered_fn callback, void* arg) {
|
||||
if (!inst || !inst->ua || inst->join_stopping || !channel_id || !join_key || !callback) {
|
||||
if (!inst || !inst->ua || inst->join_stopping || !channel_id || channel_id > INT64_MAX || !join_key || !callback) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid registration arguments", CJ_ID);
|
||||
return NULL;
|
||||
}
|
||||
@@ -415,6 +425,9 @@ struct chat_join_registration* chat_join_register_key(struct UTUN_INSTANCE* inst
|
||||
}
|
||||
|
||||
uint64_t chat_join_lookup_inviter(struct UTUN_INSTANCE* inst, uint64_t channel_id, uint64_t join_key) {
|
||||
if (!inst || !channel_id || channel_id > INT64_MAX || !join_key) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid key lookup", CJ_ID); return 0;
|
||||
}
|
||||
struct join_key_entry* e = _keys_find(inst, channel_id, join_key);
|
||||
return e ? e->inviter_id : 0;
|
||||
}
|
||||
@@ -422,7 +435,9 @@ uint64_t chat_join_lookup_inviter(struct UTUN_INSTANCE* inst, uint64_t channel_i
|
||||
int chat_join_forward_request(struct UTUN_INSTANCE* inst, uint64_t channel_id,
|
||||
uint64_t inviter_id, uint64_t join_key,
|
||||
const struct join_member_data* m) {
|
||||
if (!inst || !m || inviter_id == 0) return -1;
|
||||
if (!inst || inst->join_stopping || !m || !channel_id || channel_id > INT64_MAX || !join_key || !inviter_id) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "%s: invalid forward arguments", CJ_ID); return -1;
|
||||
}
|
||||
|
||||
uint8_t mbuf[MS_USERINFO_MAX + 256]; size_t mlen = 0;
|
||||
if (chat_join_serialize_member(m, mbuf, sizeof(mbuf), &mlen) != 0) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* │ │◀─── JOIN_REQUEST ────────────┤ (key + полный набор мембера J)
|
||||
* │ │ валиден ключ → только pending, БЕЗ BGP new_conn
|
||||
* │◀── REQUEST_FWD ───────────┤ │
|
||||
* │ verif join_sig, node_id=derive(x25519) │
|
||||
* │ verif JOIN,профиля,известного Ed25519,node_id=derive(x25519) │
|
||||
* │ member_sync_put(J, signed_by=A, signature=sign(A, J_x25519||"group_join"))
|
||||
* │ ── merkle broadcast ──▶ (рекорд J дошёл по merkle) │
|
||||
* │ │── JOIN_READY(ch) ────────────▶│ добавление подтверждено у C
|
||||
@@ -115,11 +115,14 @@ struct join_member_data {
|
||||
char userinfo[MS_USERINFO_MAX + 1]; /* JSON, null-terminated */
|
||||
};
|
||||
|
||||
/* Сериализовать member-блок (без join_key). Возвращает 0 и *out_len, -1 если не влез. */
|
||||
/* Сериализовать member-блок (без join_key). 0 и *out_len,-1 при неверных аргументах/размере.
|
||||
* На отказе out/out_len сохраняются; подписи NULL кодируются 64 нулями,ключи обязательны.
|
||||
* Числа native,кроме userinfo_len:u16BE;userinfo<=1024 байт без NUL на проводе. */
|
||||
int chat_join_serialize_member(const struct join_member_data* m,
|
||||
uint8_t* out, size_t out_sz, size_t* out_len);
|
||||
|
||||
/* Разобрать member-блок. Указатели ссылаются на data (не копируют). 0=ок, -1=ошибка. */
|
||||
/* Разобрать весь member-блок:проверить длину/отсутствие скрытого NUL. 0/-1,на отказе m сохраняется.
|
||||
* Ключи/подписи заимствованы у data,userinfo копируется;криптография здесь не проверяется. */
|
||||
int chat_join_parse_member(const uint8_t* data, size_t len, struct join_member_data* m);
|
||||
|
||||
/* Инициализация: etcp_router_bind(ETCP_RT_ID_JOIN). */
|
||||
@@ -147,7 +150,9 @@ int chat_join_forward_request(struct UTUN_INSTANCE* inst, uint64_t channel_id,
|
||||
const struct join_member_data* m);
|
||||
|
||||
/* Инвайтер (локально, когда connection==инвайтер, или из recv REQUEST_FWD):
|
||||
* верификация join_sig + добавление мембера + подпись дерева. */
|
||||
* Проверка ключа,идентичности,JOIN,подписи профиля при наличии и известного Ed25519;
|
||||
* затем доверенная локальная запись с подписью приглашения. Join-only допускается.
|
||||
* Подключение группы инициирует member_sync после commit;при изменении CONNECT_RESULT имеет request_id=0. */
|
||||
void chat_join_process_request(struct UTUN_INSTANCE* inst, uint64_t group_id,
|
||||
uint64_t from_node, uint64_t join_key,
|
||||
const uint8_t* member_data, size_t member_len);
|
||||
|
||||
Reference in new issue
Block a user