Add explicit server and channel link kinds to shared codec
This commit is contained in:
1 parent
b773656dad
commit
bd50140d26
3 files changed
+65
-28
No files matched your search
+35
-17
@@ -6,7 +6,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define INVITE_RAW_MAX 1157u
|
||||
#define INVITE_RAW_MAX 1158u
|
||||
#define INVITE_PREFIX "utun://"
|
||||
|
||||
static int reject(char* error, size_t size, const char* reason) {
|
||||
@@ -21,8 +21,15 @@ static int nonzero(const uint8_t* data, size_t len) {
|
||||
}
|
||||
|
||||
int invite_link_validate(const struct InviteData* d) {
|
||||
if (!d || !d->channelId || d->channelId > INT64_MAX || !d->join_key || !nonzero(d->pubkey, 32) || !sc_derive_node_id_from_pubkey(d->pubkey))
|
||||
return reject(NULL, 0, "missing channel, join key or node identity");
|
||||
if (!d || !nonzero(d->pubkey, 32) || !sc_derive_node_id_from_pubkey(d->pubkey))
|
||||
return reject(NULL, 0, "missing node identity");
|
||||
if (d->kind == INVITE_KIND_CHANNEL) {
|
||||
if (!d->channelId || d->channelId > INT64_MAX || !d->join_key || nonzero(d->add_key, sizeof(d->add_key)))
|
||||
return reject(NULL, 0, "invalid channel invitation credentials");
|
||||
} else if (d->kind == INVITE_KIND_SERVER) {
|
||||
if (d->channelId || d->join_key || d->password_len || d->password[0] || !nonzero(d->add_key, sizeof(d->add_key)))
|
||||
return reject(NULL, 0, "invalid server addition credentials");
|
||||
} else return reject(NULL, 0, "unknown link kind");
|
||||
if (!d->addrCount || d->addrCount > INVITE_ADDR_MAX) return reject(NULL, 0, "invalid address count");
|
||||
if (d->reality.has > 1) return reject(NULL, 0, "invalid REALITY marker");
|
||||
if (d->reality.has && (!memchr(d->reality.server_name, 0, sizeof(d->reality.server_name)) ||
|
||||
@@ -65,14 +72,21 @@ int invite_link_decode(const char* link, size_t len, struct InviteData* out, cha
|
||||
#define NEED(n) do { if ((size_t)(n) > count - pos) return reject(error, error_size, "truncated invitation"); } while (0)
|
||||
NEED(2);
|
||||
if (raw[pos++] != INVITE_LINK_VERSION) return reject(error, error_size, "unsupported invitation version");
|
||||
value.password_len = raw[pos++];
|
||||
if (value.password_len >= INVITE_PASS_MAX) return reject(error, error_size, "password too long");
|
||||
NEED(value.password_len);
|
||||
if (memchr(raw + pos, 0, value.password_len)) return reject(error, error_size, "password contains NUL");
|
||||
memcpy(value.password, raw + pos, value.password_len); pos += value.password_len;
|
||||
NEED(17);
|
||||
for (unsigned i = 0; i < 8; i++) value.channelId = (value.channelId << 8) | raw[pos++];
|
||||
for (unsigned i = 0; i < 8; i++) value.join_key = (value.join_key << 8) | raw[pos++];
|
||||
value.kind = raw[pos++];
|
||||
if (value.kind == INVITE_KIND_CHANNEL) {
|
||||
NEED(1); value.password_len = raw[pos++];
|
||||
if (value.password_len >= INVITE_PASS_MAX) return reject(error, error_size, "password too long");
|
||||
NEED(value.password_len);
|
||||
if (memchr(raw + pos, 0, value.password_len)) return reject(error, error_size, "password contains NUL");
|
||||
memcpy(value.password, raw + pos, value.password_len); pos += value.password_len;
|
||||
NEED(16);
|
||||
for (unsigned i = 0; i < 8; i++) value.channelId = (value.channelId << 8) | raw[pos++];
|
||||
for (unsigned i = 0; i < 8; i++) value.join_key = (value.join_key << 8) | raw[pos++];
|
||||
} else if (value.kind == INVITE_KIND_SERVER) {
|
||||
NEED(INVITE_ADD_KEY_SIZE);
|
||||
memcpy(value.add_key, raw + pos, sizeof(value.add_key)); pos += sizeof(value.add_key);
|
||||
} else return reject(error, error_size, "unknown link kind");
|
||||
NEED(1);
|
||||
value.reality.has = raw[pos++];
|
||||
if (value.reality.has > 1) return reject(error, error_size, "invalid REALITY marker");
|
||||
if (value.reality.has) {
|
||||
@@ -110,8 +124,8 @@ int invite_link_decode(const char* link, size_t len, struct InviteData* out, cha
|
||||
value.nodeId = sc_derive_node_id_from_pubkey(value.pubkey);
|
||||
*out = value;
|
||||
if (error && error_size) error[0] = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "invite decoded channel=%llu node=%016llx addresses=%u",
|
||||
(unsigned long long)value.channelId, (unsigned long long)value.nodeId, value.addrCount);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "link decoded kind=%u channel=%llu node=%016llx addresses=%u",
|
||||
value.kind, (unsigned long long)value.channelId, (unsigned long long)value.nodeId, value.addrCount);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -143,11 +157,15 @@ int invite_link_encode(const struct InviteData* data, const char* password, char
|
||||
if (invite_link_validate(data)) return -1;
|
||||
size_t pass_len = password ? strnlen(password, INVITE_PASS_MAX) : 0;
|
||||
if (pass_len >= INVITE_PASS_MAX) return reject(NULL, 0, "password too long");
|
||||
if (data->kind == INVITE_KIND_SERVER && pass_len) return reject(NULL, 0, "server link cannot contain channel password");
|
||||
uint8_t raw[INVITE_RAW_MAX]; size_t pos = 0;
|
||||
raw[pos++] = INVITE_LINK_VERSION; raw[pos++] = (uint8_t)pass_len;
|
||||
if (pass_len) { memcpy(raw + pos, password, pass_len); pos += pass_len; }
|
||||
for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)(data->channelId >> (8 * i));
|
||||
for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)(data->join_key >> (8 * i));
|
||||
raw[pos++] = INVITE_LINK_VERSION; raw[pos++] = data->kind;
|
||||
if (data->kind == INVITE_KIND_CHANNEL) {
|
||||
raw[pos++] = (uint8_t)pass_len;
|
||||
if (pass_len) { memcpy(raw + pos, password, pass_len); pos += pass_len; }
|
||||
for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)(data->channelId >> (8 * i));
|
||||
for (int i = 7; i >= 0; i--) raw[pos++] = (uint8_t)(data->join_key >> (8 * i));
|
||||
} else { memcpy(raw + pos, data->add_key, sizeof(data->add_key)); pos += sizeof(data->add_key); }
|
||||
uint8_t addresses[INVITE_RAW_MAX];
|
||||
int address_len = invite_serialize_addrs(data, addresses, sizeof(addresses));
|
||||
if (address_len < 0) return -1;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/*
|
||||
* invite_link.h — invite-ссылки utun:// для каналов
|
||||
*
|
||||
* encode/decode используют только версию 0x03. Проверка общая для всех клиентов.
|
||||
* Формат 0x03: utun:// + обычный base64 с padding(version | pass_len/password | channel_id | join_key |
|
||||
* reality_has/params | блоки pubkey/addrs). ID/port кодируются big-endian; nodeId выводится из pubkey.
|
||||
* Версия 0x04: utun:// + base64(version | kind | реквизиты | reality | блоки pubkey/addrs).
|
||||
* CHANNEL: pass_len/password | channel_id | join_key; SERVER: add_key[32], без полей канала.
|
||||
* Проверка общая для всех клиентов. ID/port — big-endian; nodeId выводится из pubkey.
|
||||
* Сам кодек не подключает узел, не регистрирует join_key и не проверяет подпись/членство.
|
||||
* decode: 0/-1; encode: длина base64 без utun:// и NUL/-1; serialize_addrs: число байтов/-1.
|
||||
* Буферы предоставляет вызывающий; encode завершает строку NUL.
|
||||
@@ -14,7 +14,10 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define INVITE_LINK_VERSION 0x03
|
||||
#define INVITE_LINK_VERSION 0x04
|
||||
#define INVITE_KIND_CHANNEL 0
|
||||
#define INVITE_KIND_SERVER 1
|
||||
#define INVITE_ADD_KEY_SIZE 32
|
||||
#define INVITE_LINK_MAX 1552 /* максимальная ссылка с NUL: 32 IPv6, REALITY и пароль */
|
||||
#define INVITE_PUBKEY_SIZE 32
|
||||
#define INVITE_ADDR_MAX 32
|
||||
@@ -47,6 +50,8 @@ struct InviteReality {
|
||||
};
|
||||
|
||||
struct InviteData {
|
||||
uint8_t kind; /* INVITE_KIND_*; SERVER не разрешает обычный JOIN */
|
||||
uint8_t add_key[INVITE_ADD_KEY_SIZE]; /* постоянный секрет добавления сервера; у CHANNEL нули */
|
||||
uint64_t channelId; /* числовой ID канала */
|
||||
uint64_t join_key; /* 64-битный ключ входа (генерируется инвайтером, хранится на connection-узле 10 мин) */
|
||||
uint8_t pubkey[INVITE_PUBKEY_SIZE]; /* X25519 узла подключения */
|
||||
|
||||
@@ -80,14 +80,14 @@ int main(void) {
|
||||
raw_len -= link[strlen(link) - 1] == '='; raw_len -= link[strlen(link) - 2] == '=';
|
||||
uint8_t saved[1200]; memcpy(saved, raw, (size_t)raw_len);
|
||||
raw[0] = 2; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
raw[18] = 2; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
raw[62] = 64; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
raw[63] = 0; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
raw[126] |= 0x80; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
size_t second = 126 + 33 + 4 * 20; raw[second + 1] ^= 1;
|
||||
raw[19] = 2; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
raw[63] = 64; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
raw[64] = 0; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
raw[127] |= 0x80; rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
size_t second = 127 + 33 + 4 * 20; raw[second + 1] ^= 1;
|
||||
rejected_raw(raw, (size_t)raw_len); memcpy(raw, saved, (size_t)raw_len);
|
||||
rejected_raw(raw, 19); /* обязательный узел отсутствует */
|
||||
memcpy(raw, saved, (size_t)raw_len); raw[1] = 128; rejected_raw(raw, (size_t)raw_len);
|
||||
rejected_raw(raw, 20); /* обязательный узел отсутствует */
|
||||
memcpy(raw, saved, (size_t)raw_len); raw[2] = 128; rejected_raw(raw, (size_t)raw_len);
|
||||
struct InviteData bad = input; bad.addrCount = 0; CHECK(invite_link_validate(&bad) < 0);
|
||||
bad = input; bad.channelId = UINT64_MAX; CHECK(invite_link_validate(&bad) < 0);
|
||||
bad = input; bad.addrCount = 33; CHECK(invite_link_validate(&bad) < 0);
|
||||
@@ -96,6 +96,20 @@ int main(void) {
|
||||
bad = input; bad.addrs[0].port = 0; CHECK(invite_link_validate(&bad) < 0);
|
||||
bad = input; bad.addrs[0].proto = 3; CHECK(invite_link_validate(&bad) < 0);
|
||||
bad = input; bad.reality.has = 0; CHECK(invite_link_validate(&bad) < 0);
|
||||
struct InviteData server = input, decoded_server;
|
||||
server.kind = INVITE_KIND_SERVER; server.channelId = 0; server.join_key = 0;
|
||||
memset(server.add_key, 0x57, sizeof(server.add_key));
|
||||
CHECK(invite_link_encode(&server, NULL, link, sizeof(link)) > 0);
|
||||
CHECK(!invite_link_decode(link, strlen(link), &decoded_server, NULL, 0));
|
||||
CHECK(decoded_server.kind == INVITE_KIND_SERVER && !decoded_server.channelId && !decoded_server.join_key);
|
||||
CHECK(!memcmp(server.add_key, decoded_server.add_key, sizeof(server.add_key)) && decoded_server.addrCount == server.addrCount);
|
||||
for (size_t cut = 0; cut < 34; cut++) {
|
||||
uint8_t server_raw[34] = {INVITE_LINK_VERSION, INVITE_KIND_SERVER};
|
||||
rejected_raw(server_raw, cut);
|
||||
}
|
||||
server.channelId = 1; CHECK(invite_link_validate(&server) < 0); server.channelId = 0;
|
||||
memset(server.add_key, 0, sizeof(server.add_key)); CHECK(invite_link_validate(&server) < 0);
|
||||
bad = input; bad.kind = 2; CHECK(invite_link_validate(&bad) < 0);
|
||||
rejected("utun://", 7); rejected(NULL, 1);
|
||||
printf("invite codec: %u contract cases PASS\n", cases); return 0;
|
||||
}
|
||||
Reference in new issue
Block a user