Files
utun2/src/chat/invite_link.c
T

175 lines
9.5 KiB
C

/* Общий строгий кодек приглашений. До полной проверки выходы не изменяются. */
#include "invite_link.h"
#include "../transport_layer/secure_channel.h"
#include "../../lib/debug_config.h"
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>
#define INVITE_RAW_MAX 1157u
#define INVITE_PREFIX "utun://"
static int reject(char* error, size_t size, const char* reason) {
if (error && size) snprintf(error, size, "%s", reason);
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "invite rejected: %s", reason);
return -1;
}
static int nonzero(const uint8_t* data, size_t len) {
for (size_t i = 0; i < len; i++) if (data[i]) return 1;
return 0;
}
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->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)) ||
!d->reality.server_name[0] || !nonzero(d->reality.server_pubkey, 32)))
return reject(NULL, 0, "invalid REALITY parameters");
for (unsigned i = 0; i < d->addrCount; i++) {
const struct InviteAddr* a = &d->addrs[i];
if ((a->family != 4 && a->family != 6) || !a->port ||
(a->proto != INVITE_PROTO_UDP && a->proto != INVITE_PROTO_TCP && a->proto != (INVITE_PROTO_TCP | INVITE_PROTO_REALITY)))
return reject(NULL, 0, "invalid address family, port or protocol");
if ((a->proto & INVITE_PROTO_REALITY) && !d->reality.has) return reject(NULL, 0, "missing REALITY parameters");
}
return 0;
}
int invite_link_decode(const char* link, size_t len, struct InviteData* out, char* error, size_t error_size) {
if (!link || !out || len < 7 || len >= INVITE_LINK_MAX || memcmp(link, INVITE_PREFIX, 7))
return reject(error, error_size, "invalid invitation length or prefix");
size_t encoded = len - 7;
if (!encoded || encoded % 4 || encoded > 4 * ((INVITE_RAW_MAX + 2) / 3))
return reject(error, error_size, "invalid base64 length");
const char* b64 = link + 7;
size_t padding = (b64[encoded - 1] == '=') + (encoded > 1 && b64[encoded - 2] == '=');
for (size_t i = 0; i < encoded - padding; i++) {
unsigned char c = (unsigned char)b64[i];
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '+' || c == '/'))
return reject(error, error_size, "invalid base64 alphabet");
}
uint8_t raw[INVITE_RAW_MAX + 3];
int decoded = EVP_DecodeBlock(raw, (const unsigned char*)b64, (int)encoded);
if (decoded < 0 || (size_t)decoded < padding || (size_t)decoded - padding > INVITE_RAW_MAX)
return reject(error, error_size, "invalid base64 data");
size_t count = (size_t)decoded - padding;
unsigned char canonical[4 * ((INVITE_RAW_MAX + 2) / 3) + 1];
int canonical_len = EVP_EncodeBlock(canonical, raw, (int)count);
if ((size_t)canonical_len != encoded || memcmp(canonical, b64, encoded))
return reject(error, error_size, "noncanonical base64");
struct InviteData value = {0};
size_t pos = 0;
#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.reality.has = raw[pos++];
if (value.reality.has > 1) return reject(error, error_size, "invalid REALITY marker");
if (value.reality.has) {
NEED(44);
memcpy(value.reality.short_id, raw + pos, 8); pos += 8;
memcpy(value.reality.server_pubkey, raw + pos, 32); pos += 32;
memcpy(value.reality.version, raw + pos, 3); pos += 3;
size_t size = raw[pos++];
if (!size || size >= INVITE_SNAME_MAX) return reject(error, error_size, "invalid REALITY server name length");
NEED(size);
if (memchr(raw + pos, 0, size)) return reject(error, error_size, "REALITY server name contains NUL");
memcpy(value.reality.server_name, raw + pos, size); pos += size;
}
while (pos < count) {
NEED(33);
uint8_t header = raw[pos++];
unsigned addresses = (header & 3) + 1;
if ((header >> (2 + addresses)) || value.addrCount + addresses > INVITE_ADDR_MAX)
return reject(error, error_size, "invalid address block");
if (!value.addrCount) memcpy(value.pubkey, raw + pos, 32);
else if (memcmp(value.pubkey, raw + pos, 32)) return reject(error, error_size, "conflicting node keys");
pos += 32;
for (unsigned i = 0; i < addresses; i++) {
struct InviteAddr* a = &value.addrs[value.addrCount++];
a->family = (header & (1u << (2 + i))) ? 6 : 4;
size_t ip_len = a->family == 6 ? 16 : 4;
NEED(4 + ip_len);
a->socketId = raw[pos++]; a->proto = raw[pos++];
memcpy(a->address, raw + pos, ip_len); pos += ip_len;
a->port = (uint16_t)((raw[pos] << 8) | raw[pos + 1]); pos += 2;
}
}
#undef NEED
if (invite_link_validate(&value)) return reject(error, error_size, "invalid invitation fields");
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);
return 0;
}
int invite_serialize_addrs(const struct InviteData* data, uint8_t* out, size_t capacity) {
if (!out) return reject(NULL, 0, "missing output buffer");
if (invite_link_validate(data)) return -1;
uint8_t buf[INVITE_RAW_MAX]; size_t pos = 0;
buf[pos++] = data->reality.has;
if (data->reality.has) {
memcpy(buf + pos, data->reality.short_id, 8); pos += 8;
memcpy(buf + pos, data->reality.server_pubkey, 32); pos += 32;
memcpy(buf + pos, data->reality.version, 3); pos += 3;
size_t size = strlen(data->reality.server_name);
buf[pos++] = (uint8_t)size; memcpy(buf + pos, data->reality.server_name, size); pos += size;
}
for (unsigned i = 0; i < data->addrCount; i++) {
const struct InviteAddr* a = &data->addrs[i];
size_t size = a->family == 6 ? 16 : 4;
buf[pos++] = (uint8_t)a->family; buf[pos++] = a->socketId; buf[pos++] = a->proto;
memcpy(buf + pos, a->address, size); pos += size;
buf[pos++] = (uint8_t)(a->port >> 8); buf[pos++] = (uint8_t)a->port;
}
if (pos > capacity) return reject(NULL, 0, "address output buffer too small");
memcpy(out, buf, pos); return (int)pos;
}
int invite_link_encode(const struct InviteData* data, const char* password, char* out, size_t capacity) {
if (!out) return reject(NULL, 0, "missing output buffer");
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");
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));
uint8_t addresses[INVITE_RAW_MAX];
int address_len = invite_serialize_addrs(data, addresses, sizeof(addresses));
if (address_len < 0) return -1;
size_t prefix_len = 1;
if (data->reality.has) prefix_len += 44 + strlen(data->reality.server_name);
memcpy(raw + pos, addresses, prefix_len); pos += prefix_len;
for (unsigned i = 0; i < data->addrCount;) {
unsigned count = data->addrCount - i; if (count > 4) count = 4;
uint8_t header = (uint8_t)(count - 1);
for (unsigned j = 0; j < count; j++) if (data->addrs[i + j].family == 6) header |= (uint8_t)(1u << (2 + j));
raw[pos++] = header; memcpy(raw + pos, data->pubkey, 32); pos += 32;
for (unsigned j = 0; j < count; j++) {
const struct InviteAddr* a = &data->addrs[i++];
size_t size = a->family == 6 ? 16 : 4;
raw[pos++] = a->socketId; raw[pos++] = a->proto;
memcpy(raw + pos, a->address, size); pos += size;
raw[pos++] = (uint8_t)(a->port >> 8); raw[pos++] = (uint8_t)a->port;
}
}
size_t size = 4 * ((pos + 2) / 3);
if (capacity < 7 + size + 1) return reject(NULL, 0, "invitation output buffer too small");
memcpy(out, INVITE_PREFIX, 7); EVP_EncodeBlock((unsigned char*)out + 7, raw, (int)pos);
return (int)size;
}