feat: password Realm, single-chair login, shared parliament context (0.9.12)
Derive Realm keys from a user password (Argon2id, no private key on disk). Login claims the sole chair with handover; peers share parliament context; --new-project clears it. CLI peer/discover helpers and Docker alpine recipe included; headless empty-answer retries raised for unattended runs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
#ifndef _DEFAULT_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
#include "cst_parliament_cli.h"
|
||||
#include "cst_discover.h"
|
||||
#include "cst_net.h"
|
||||
#include "cst_parliament.h"
|
||||
#include "cst_peer.h"
|
||||
#include "cst_realm.h"
|
||||
#include "os_compat.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static void sleep_ms(int ms) {
|
||||
#ifdef _WIN32
|
||||
Sleep((DWORD)ms);
|
||||
#else
|
||||
struct timespec ts = {ms / 1000, (ms % 1000) * 1000000L};
|
||||
nanosleep(&ts, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
int cst_parliament_cli_realm_new(void) {
|
||||
char dir[OS_PATH_MAX], fp[33];
|
||||
if (cst_realm_dir(dir, sizeof(dir)) != 0) {
|
||||
fprintf(stderr, "cannot resolve realm directory\n");
|
||||
return 1;
|
||||
}
|
||||
cst_realm_keys_t k;
|
||||
if (cst_realm_generate(&k) != 0 || cst_realm_save(dir, &k) != 0) {
|
||||
fprintf(stderr, "cannot create realm keys in %s\n", dir);
|
||||
return 1;
|
||||
}
|
||||
cst_realm_fingerprint(k.pk, fp);
|
||||
printf("realm_created dir=%s fingerprint=%s\n", dir, fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_parliament_cli_realm_password_init(const char *password) {
|
||||
if (!password || !password[0]) {
|
||||
fprintf(stderr, "password required (or CST_REALM_PASSWORD)\n");
|
||||
return 1;
|
||||
}
|
||||
char dir[OS_PATH_MAX], fp[33];
|
||||
if (cst_realm_dir(dir, sizeof(dir)) != 0) {
|
||||
fprintf(stderr, "cannot resolve realm directory\n");
|
||||
return 1;
|
||||
}
|
||||
if (cst_realm_init_password(dir, password) != 0) {
|
||||
fprintf(stderr, "password realm init failed\n");
|
||||
return 1;
|
||||
}
|
||||
cst_realm_keys_t k;
|
||||
if (cst_realm_unlock_password(dir, password, &k) != 0)
|
||||
return 1;
|
||||
cst_realm_fingerprint(k.pk, fp);
|
||||
printf("realm_password_mode dir=%s fingerprint=%s\n", dir, fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_parliament_cli_login(const char *password, const char *instance_id,
|
||||
const char *device_label) {
|
||||
char err[256];
|
||||
char *hand = cst_parliament_login(password, instance_id, device_label, err,
|
||||
sizeof(err));
|
||||
if (!hand) {
|
||||
fprintf(stderr, "%s\n", err[0] ? err : "login failed");
|
||||
return 1;
|
||||
}
|
||||
fputs(hand, stdout);
|
||||
free(hand);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_parliament_cli_new_project(const char *project_id) {
|
||||
if (cst_parliament_new_project(project_id) != 0) {
|
||||
fprintf(stderr, "new project failed\n");
|
||||
return 1;
|
||||
}
|
||||
printf("parliament_new_project id=%s\n",
|
||||
project_id && project_id[0] ? project_id : "default");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_parliament_cli_realm_enroll(const char *instance_id, const char *label) {
|
||||
if (!instance_id || !instance_id[0]) {
|
||||
fprintf(stderr, "instance id required\n");
|
||||
return 1;
|
||||
}
|
||||
char dir[OS_PATH_MAX];
|
||||
cst_realm_keys_t k;
|
||||
if (cst_realm_dir(dir, sizeof(dir)) != 0 || cst_realm_load(dir, &k) != 0) {
|
||||
fprintf(stderr, "load realm keys failed (run --realm-new first)\n");
|
||||
return 1;
|
||||
}
|
||||
cst_realm_cert_t cert;
|
||||
uint8_t seed[32], sk[64];
|
||||
if (cst_realm_enroll(&k, instance_id, label, 0, &cert, seed, sk) != 0 ||
|
||||
cst_realm_instance_save(instance_id, &cert, seed) != 0) {
|
||||
fprintf(stderr, "enroll/save failed\n");
|
||||
return 1;
|
||||
}
|
||||
char fp[33];
|
||||
cst_realm_fingerprint(k.pk, fp);
|
||||
printf("enrolled id=%s label=%s realm=%s\n", instance_id,
|
||||
label ? label : "", fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_parliament_cli_peer_listen(int port, const char *instance_id) {
|
||||
if (!instance_id || !instance_id[0]) {
|
||||
fprintf(stderr, "instance id required\n");
|
||||
return 1;
|
||||
}
|
||||
char dir[OS_PATH_MAX], err[256];
|
||||
cst_realm_keys_t realm;
|
||||
cst_realm_cert_t cert;
|
||||
uint8_t sk[64];
|
||||
if (cst_realm_dir(dir, sizeof(dir)) != 0 || cst_realm_load(dir, &realm) != 0 ||
|
||||
cst_realm_instance_load(instance_id, &cert, sk) != 0) {
|
||||
fprintf(stderr, "cannot load realm/instance %s\n", instance_id);
|
||||
return 1;
|
||||
}
|
||||
if (cst_realm_revoked(dir, instance_id)) {
|
||||
fprintf(stderr, "instance revoked\n");
|
||||
return 1;
|
||||
}
|
||||
os_net_init();
|
||||
os_socket_t ls = os_net_listen("0.0.0.0", port > 0 ? port : 0, err, sizeof(err));
|
||||
if (ls == OS_SOCKET_BAD) {
|
||||
fprintf(stderr, "listen failed: %s\n", err);
|
||||
return 1;
|
||||
}
|
||||
int bound = os_net_port(ls);
|
||||
printf("peer_listen port=%d id=%s\n", bound, instance_id);
|
||||
fflush(stdout);
|
||||
cst_peer_session_t sess;
|
||||
if (cst_peer_accept((intptr_t)ls, &cert, sk, realm.pk, 60000, &sess, err,
|
||||
sizeof(err)) != 0) {
|
||||
fprintf(stderr, "accept/handshake failed: %s\n", err);
|
||||
os_net_close(ls);
|
||||
return 1;
|
||||
}
|
||||
printf("peer_ok remote=%s host=%s\n", sess.remote.instance_id, sess.host);
|
||||
os_net_close(ls);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_parliament_cli_peer_dial(const char *host, int port,
|
||||
const char *instance_id) {
|
||||
if (!host || port <= 0 || !instance_id) {
|
||||
fprintf(stderr, "host, port and instance id required\n");
|
||||
return 1;
|
||||
}
|
||||
char dir[OS_PATH_MAX], err[256];
|
||||
cst_realm_keys_t realm;
|
||||
cst_realm_cert_t cert;
|
||||
uint8_t sk[64];
|
||||
if (cst_realm_dir(dir, sizeof(dir)) != 0 || cst_realm_load(dir, &realm) != 0 ||
|
||||
cst_realm_instance_load(instance_id, &cert, sk) != 0) {
|
||||
fprintf(stderr, "cannot load realm/instance %s\n", instance_id);
|
||||
return 1;
|
||||
}
|
||||
cst_peer_session_t sess;
|
||||
if (cst_peer_dial(host, port, &cert, sk, realm.pk, 30000, &sess, err,
|
||||
sizeof(err)) != 0) {
|
||||
fprintf(stderr, "dial/handshake failed: %s\n", err);
|
||||
return 1;
|
||||
}
|
||||
printf("peer_ok remote=%s\n", sess.remote.instance_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cst_parliament_cli_discover(const char *instance_id, int peer_port,
|
||||
int wait_ms) {
|
||||
if (!instance_id || peer_port <= 0) {
|
||||
fprintf(stderr, "instance id and peer port required\n");
|
||||
return 1;
|
||||
}
|
||||
char dir[OS_PATH_MAX], fp[33];
|
||||
cst_realm_keys_t realm;
|
||||
if (cst_realm_dir(dir, sizeof(dir)) != 0 || cst_realm_load(dir, &realm) != 0 ||
|
||||
cst_realm_fingerprint(realm.pk, fp) != 0) {
|
||||
fprintf(stderr, "cannot load realm\n");
|
||||
return 1;
|
||||
}
|
||||
cst_discover_t *d = cst_discover_start(fp, instance_id, instance_id, peer_port);
|
||||
if (!d) {
|
||||
fprintf(stderr, "discover start failed\n");
|
||||
return 1;
|
||||
}
|
||||
int left = wait_ms > 0 ? wait_ms : 3000;
|
||||
while (left > 0) {
|
||||
cst_peer_info_t peers[16];
|
||||
int n = cst_discover_peers(d, peers, 16);
|
||||
for (int i = 0; i < n; i++) {
|
||||
printf("peer iid=%s host=%s port=%d name=%s\n", peers[i].instance_id,
|
||||
peers[i].host, peers[i].port, peers[i].name);
|
||||
}
|
||||
if (n > 0)
|
||||
break;
|
||||
sleep_ms(200);
|
||||
left -= 200;
|
||||
}
|
||||
cst_discover_stop(d);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user