feat: mutual Realm peer handshake over TCP (0.9.11)

Exchange enrolled certificates and Ed25519 challenge signatures so
same-Realm instances can authenticate each other after discovery.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WHC
2026-10-28 22:49:33 +08:00
co-authored by Cursor
parent 5ee82f126d
commit 08ca613a0e
6 changed files with 460 additions and 2 deletions
+334
View File
@@ -0,0 +1,334 @@
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE
#endif
#include "cst_peer.h"
#include "cJSON.h"
#include "cst_net.h"
#include "cst_util.h"
#include "monocypher.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
static void hex_encode(const uint8_t *in, size_t n, char *out) {
static const char *H = "0123456789abcdef";
for (size_t i = 0; i < n; i++) {
out[i * 2] = H[in[i] >> 4];
out[i * 2 + 1] = H[in[i] & 15];
}
out[n * 2] = 0;
}
static int hex_nibble(int c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int hex_decode(const char *s, uint8_t *out, size_t n) {
if (!s || strlen(s) != n * 2)
return -1;
for (size_t i = 0; i < n; i++) {
int a = hex_nibble(s[i * 2]), b = hex_nibble(s[i * 2 + 1]);
if (a < 0 || b < 0)
return -1;
out[i] = (uint8_t)((a << 4) | b);
}
return 0;
}
static int fill_random(uint8_t *buf, size_t n) {
FILE *f = fopen("/dev/urandom", "rb");
if (!f)
return -1;
size_t got = fread(buf, 1, n, f);
fclose(f);
return got == n ? 0 : -1;
}
static int send_line(os_socket_t s, const char *line, int timeout_ms) {
size_t n = strlen(line);
cst_buf_t b = {0};
if (cst_buf_append(&b, line, n) || cst_buf_puts(&b, "\n")) {
cst_buf_free(&b);
return -1;
}
int rc = os_net_send_all(s, b.buf, b.len, timeout_ms);
cst_buf_free(&b);
return rc;
}
static char *recv_line(os_socket_t s, int timeout_ms) {
cst_buf_t b = {0};
char ch;
for (;;) {
long r = os_net_recv(s, &ch, 1, timeout_ms);
if (r <= 0) {
cst_buf_free(&b);
return NULL;
}
if (ch == '\n')
break;
if (ch != '\r')
cst_buf_append(&b, &ch, 1);
if (b.len > 65536) {
cst_buf_free(&b);
return NULL;
}
}
if (!b.buf)
cst_buf_puts(&b, "");
return cst_buf_steal(&b);
}
static int send_hello(os_socket_t s, const cst_realm_cert_t *self, int timeout_ms) {
char *cert = cst_realm_cert_to_json(self);
if (!cert)
return -1;
cJSON *o = cJSON_CreateObject();
cJSON_AddNumberToObject(o, "v", 1);
cJSON_AddStringToObject(o, "op", "hello");
cJSON *c = cJSON_Parse(cert);
free(cert);
if (!c) {
cJSON_Delete(o);
return -1;
}
cJSON_AddItemToObject(o, "cert", c);
char *line = cJSON_PrintUnformatted(o);
cJSON_Delete(o);
if (!line)
return -1;
int rc = send_line(s, line, timeout_ms);
free(line);
return rc;
}
static int read_hello(os_socket_t s, const uint8_t realm_pk[32], int timeout_ms,
cst_realm_cert_t *remote) {
char *line = recv_line(s, timeout_ms);
if (!line)
return -1;
cJSON *o = cJSON_Parse(line);
free(line);
if (!o)
return -1;
const cJSON *op = cJSON_GetObjectItemCaseSensitive(o, "op");
const cJSON *cert = cJSON_GetObjectItemCaseSensitive(o, "cert");
int ok = 0;
if (cJSON_IsString(op) && !strcmp(op->valuestring, "hello") && cert) {
char *js = cJSON_PrintUnformatted(cert);
if (js && cst_realm_cert_from_json(js, remote) == 0 &&
cst_realm_verify_cert(realm_pk, remote, (int64_t)time(NULL)) == 0)
ok = 1;
free(js);
}
cJSON_Delete(o);
return ok ? 0 : -1;
}
static int send_chal(os_socket_t s, const uint8_t nonce[32], int timeout_ms) {
char hx[65];
hex_encode(nonce, 32, hx);
cJSON *o = cJSON_CreateObject();
cJSON_AddNumberToObject(o, "v", 1);
cJSON_AddStringToObject(o, "op", "chal");
cJSON_AddStringToObject(o, "nonce", hx);
char *line = cJSON_PrintUnformatted(o);
cJSON_Delete(o);
if (!line)
return -1;
int rc = send_line(s, line, timeout_ms);
free(line);
return rc;
}
static int read_chal(os_socket_t s, int timeout_ms, uint8_t nonce[32]) {
char *line = recv_line(s, timeout_ms);
if (!line)
return -1;
cJSON *o = cJSON_Parse(line);
free(line);
if (!o)
return -1;
const cJSON *op = cJSON_GetObjectItemCaseSensitive(o, "op");
const cJSON *n = cJSON_GetObjectItemCaseSensitive(o, "nonce");
int ok = cJSON_IsString(op) && !strcmp(op->valuestring, "chal") &&
cJSON_IsString(n) && hex_decode(n->valuestring, nonce, 32) == 0;
cJSON_Delete(o);
return ok ? 0 : -1;
}
static int send_auth(os_socket_t s, const uint8_t sk[64], const uint8_t nonce[32],
int timeout_ms) {
uint8_t sig[64];
if (cst_realm_challenge_sign(sk, nonce, 32, sig) != 0)
return -1;
char hx[129];
hex_encode(sig, 64, hx);
cJSON *o = cJSON_CreateObject();
cJSON_AddNumberToObject(o, "v", 1);
cJSON_AddStringToObject(o, "op", "auth");
cJSON_AddStringToObject(o, "sig", hx);
char *line = cJSON_PrintUnformatted(o);
cJSON_Delete(o);
if (!line)
return -1;
int rc = send_line(s, line, timeout_ms);
free(line);
return rc;
}
static int read_auth(os_socket_t s, const cst_realm_cert_t *remote,
const uint8_t nonce[32], int timeout_ms) {
char *line = recv_line(s, timeout_ms);
if (!line)
return -1;
cJSON *o = cJSON_Parse(line);
free(line);
if (!o)
return -1;
const cJSON *op = cJSON_GetObjectItemCaseSensitive(o, "op");
const cJSON *sig = cJSON_GetObjectItemCaseSensitive(o, "sig");
uint8_t raw[64];
int ok = cJSON_IsString(op) && !strcmp(op->valuestring, "auth") &&
cJSON_IsString(sig) && hex_decode(sig->valuestring, raw, 64) == 0 &&
cst_realm_challenge_check(remote, nonce, 32, raw) == 0;
cJSON_Delete(o);
return ok ? 0 : -1;
}
static int send_ok(os_socket_t s, int timeout_ms) {
return send_line(s, "{\"v\":1,\"op\":\"ok\"}", timeout_ms);
}
static int read_ok(os_socket_t s, int timeout_ms) {
char *line = recv_line(s, timeout_ms);
if (!line)
return -1;
cJSON *o = cJSON_Parse(line);
free(line);
if (!o)
return -1;
const cJSON *op = cJSON_GetObjectItemCaseSensitive(o, "op");
int ok = cJSON_IsString(op) && !strcmp(op->valuestring, "ok");
cJSON_Delete(o);
return ok ? 0 : -1;
}
static int handshake_as_client(os_socket_t s, const cst_realm_cert_t *self,
const uint8_t self_sk[64],
const uint8_t realm_pk[32], int timeout_ms,
cst_realm_cert_t *remote) {
if (send_hello(s, self, timeout_ms) != 0)
return -1;
uint8_t nonce[32];
if (read_chal(s, timeout_ms, nonce) != 0)
return -1;
if (send_auth(s, self_sk, nonce, timeout_ms) != 0)
return -1;
if (read_hello(s, realm_pk, timeout_ms, remote) != 0)
return -1;
if (fill_random(nonce, 32) != 0)
return -1;
if (send_chal(s, nonce, timeout_ms) != 0)
return -1;
if (read_auth(s, remote, nonce, timeout_ms) != 0)
return -1;
if (send_ok(s, timeout_ms) != 0 || read_ok(s, timeout_ms) != 0)
return -1;
return 0;
}
static int handshake_as_server(os_socket_t s, const cst_realm_cert_t *self,
const uint8_t self_sk[64],
const uint8_t realm_pk[32], int timeout_ms,
cst_realm_cert_t *remote) {
if (read_hello(s, realm_pk, timeout_ms, remote) != 0)
return -1;
uint8_t nonce[32];
if (fill_random(nonce, 32) != 0)
return -1;
if (send_chal(s, nonce, timeout_ms) != 0)
return -1;
if (read_auth(s, remote, nonce, timeout_ms) != 0)
return -1;
if (send_hello(s, self, timeout_ms) != 0)
return -1;
if (read_chal(s, timeout_ms, nonce) != 0)
return -1;
if (send_auth(s, self_sk, nonce, timeout_ms) != 0)
return -1;
if (read_ok(s, timeout_ms) != 0 || send_ok(s, timeout_ms) != 0)
return -1;
return 0;
}
int cst_peer_dial(const char *host, int port, const cst_realm_cert_t *self,
const uint8_t self_sk[64], const uint8_t realm_pk[32],
int timeout_ms, cst_peer_session_t *out, char *err,
size_t err_sz) {
if (err && err_sz)
err[0] = 0;
if (!host || !self || !self_sk || !realm_pk || !out)
return -1;
os_net_init();
os_socket_t s = os_net_connect(host, port, timeout_ms);
if (s == OS_SOCKET_BAD) {
if (err && err_sz)
snprintf(err, err_sz, "connect failed");
return -1;
}
cst_realm_cert_t remote;
if (handshake_as_client(s, self, self_sk, realm_pk, timeout_ms, &remote) !=
0) {
if (err && err_sz)
snprintf(err, err_sz, "peer handshake failed");
os_net_close(s);
return -1;
}
memset(out, 0, sizeof(*out));
out->remote = remote;
snprintf(out->host, sizeof(out->host), "%s", host);
out->port = port;
os_net_close(s);
return 0;
}
int cst_peer_accept(intptr_t listen_sock, const cst_realm_cert_t *self,
const uint8_t self_sk[64], const uint8_t realm_pk[32],
int timeout_ms, cst_peer_session_t *out, char *err,
size_t err_sz) {
if (err && err_sz)
err[0] = 0;
if (!self || !self_sk || !realm_pk || !out)
return -1;
int timed = 0;
os_socket_t s = os_net_accept((os_socket_t)listen_sock, timeout_ms, &timed);
if (s == OS_SOCKET_BAD) {
if (err && err_sz)
snprintf(err, err_sz, timed ? "accept timeout" : "accept failed");
return -1;
}
cst_realm_cert_t remote;
if (handshake_as_server(s, self, self_sk, realm_pk, timeout_ms, &remote) !=
0) {
if (err && err_sz)
snprintf(err, err_sz, "peer handshake failed");
os_net_close(s);
return -1;
}
memset(out, 0, sizeof(*out));
out->remote = remote;
os_net_peer(s, out->host, sizeof(out->host));
out->port = 0;
os_net_close(s);
return 0;
}