215 lines
7.1 KiB
C
215 lines
7.1 KiB
C
/*
|
|
* key_store.c — Nostr identity (in-memory only) for sovereign_browser
|
|
*
|
|
* Private keys are NEVER persisted to disk. They live only in RAM for
|
|
* the duration of the session and are gone when the browser quits.
|
|
*
|
|
* This module provides:
|
|
* - key_store_create_signer() — create a nostr_signer_t from an
|
|
* in-memory identity struct
|
|
* - key_store_delete_legacy_file() — defensively delete any
|
|
* identity.json left by a previous version that persisted keys
|
|
* - key_store_save_profile_identity() — save public identity info
|
|
* (method, pubkey_hex) to the per-profile identity.json
|
|
* - key_store_load_profile_identity() — load public identity info
|
|
* from the per-profile identity.json
|
|
*/
|
|
|
|
#include "key_store.h"
|
|
#include "profile.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include "../nostr_core_lib/cjson/cJSON.h"
|
|
|
|
#include "nostr_core/nostr_core.h"
|
|
#include "nostr_core/nip006.h"
|
|
#include "nostr_core/nip019.h"
|
|
|
|
/* ── Signer creation ──────────────────────────────────────────────── */
|
|
|
|
nostr_signer_t *key_store_create_signer(const key_store_identity_t *identity) {
|
|
if (identity == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
switch (identity->method) {
|
|
case KEY_STORE_METHOD_LOCAL:
|
|
case KEY_STORE_METHOD_SEED: {
|
|
unsigned char privkey[32];
|
|
if (nostr_hex_to_bytes(identity->privkey_hex, privkey, 32) != 0) {
|
|
return NULL;
|
|
}
|
|
return nostr_signer_local(privkey);
|
|
}
|
|
|
|
case KEY_STORE_METHOD_READONLY:
|
|
/* No signer for read-only mode. */
|
|
return NULL;
|
|
|
|
case KEY_STORE_METHOD_NIP46:
|
|
/* NIP-46 requires a live WebSocket session — handled in login_dialog
|
|
* and the bridge, not here. For now, return NULL; the session
|
|
* will be established separately. */
|
|
return NULL;
|
|
|
|
case KEY_STORE_METHOD_NSIGNER: {
|
|
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
|
nostr_signer_t *signer = NULL;
|
|
if (strcmp(identity->nsigner_transport, "serial") == 0) {
|
|
signer = nostr_signer_nsigner_serial(identity->nsigner_device, NULL, 15000);
|
|
} else if (strcmp(identity->nsigner_transport, "unix") == 0) {
|
|
signer = nostr_signer_nsigner_unix(identity->nsigner_device, NULL, 15000);
|
|
} else if (strcmp(identity->nsigner_transport, "tcp") == 0) {
|
|
/* nsigner_device is "host:port" */
|
|
char host[256];
|
|
int port = 0;
|
|
const char *sep = strrchr(identity->nsigner_device, ':');
|
|
if (sep && sep != identity->nsigner_device) {
|
|
size_t hlen = (size_t)(sep - identity->nsigner_device);
|
|
if (hlen < sizeof(host)) {
|
|
memcpy(host, identity->nsigner_device, hlen);
|
|
host[hlen] = '\0';
|
|
port = atoi(sep + 1);
|
|
signer = nostr_signer_nsigner_tcp(host, port, NULL, 15000);
|
|
}
|
|
}
|
|
} else if (strcmp(identity->nsigner_transport, "qrexec") == 0) {
|
|
signer = nostr_signer_nsigner_qrexec(identity->nsigner_device, "qubes.NsignerRpc", NULL, 30000);
|
|
}
|
|
if (signer && identity->nsigner_index >= 0) {
|
|
nostr_signer_nsigner_set_nostr_index(signer, identity->nsigner_index);
|
|
}
|
|
return signer;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* ── Legacy file cleanup ──────────────────────────────────────────── */
|
|
|
|
int key_store_delete_legacy_file(void) {
|
|
const char *home = getenv("HOME");
|
|
if (home == NULL || home[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
char path[512];
|
|
int n = snprintf(path, sizeof(path), "%s/.sovereign_browser/identity.json",
|
|
home);
|
|
if (n < 0 || (size_t)n >= sizeof(path)) {
|
|
return -1;
|
|
}
|
|
|
|
/* Delete the file if it exists. ENOENT is not an error. */
|
|
if (unlink(path) != 0 && errno != ENOENT) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ── Per-profile identity persistence ──────────────────────────────── *
|
|
* The per-profile identity.json stores public identity information
|
|
* (method, pubkey_hex) so the browser can display profile info without
|
|
* requiring the user to log in first. Private keys are NEVER stored.
|
|
*/
|
|
|
|
int key_store_save_profile_identity(const char *pubkey_hex,
|
|
key_store_method_t method) {
|
|
if (pubkey_hex == NULL || pubkey_hex[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
char path[512];
|
|
profile_get_identity_path(pubkey_hex, path, sizeof(path));
|
|
if (path[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
cJSON *root = cJSON_CreateObject();
|
|
cJSON_AddStringToObject(root, "pubkey_hex", pubkey_hex);
|
|
cJSON_AddNumberToObject(root, "method", (double)method);
|
|
|
|
/* Method name for human readability. */
|
|
const char *method_name = "none";
|
|
switch (method) {
|
|
case KEY_STORE_METHOD_NONE: method_name = "none"; break;
|
|
case KEY_STORE_METHOD_LOCAL: method_name = "local"; break;
|
|
case KEY_STORE_METHOD_SEED: method_name = "seed"; break;
|
|
case KEY_STORE_METHOD_READONLY: method_name = "readonly"; break;
|
|
case KEY_STORE_METHOD_NIP46: method_name = "nip46"; break;
|
|
case KEY_STORE_METHOD_NSIGNER: method_name = "nsigner"; break;
|
|
}
|
|
cJSON_AddStringToObject(root, "method_name", method_name);
|
|
|
|
char *json = cJSON_PrintUnformatted(root);
|
|
cJSON_Delete(root);
|
|
if (json == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
FILE *f = fopen(path, "w");
|
|
if (f == NULL) {
|
|
free(json);
|
|
return -1;
|
|
}
|
|
fputs(json, f);
|
|
fputc('\n', f);
|
|
fclose(f);
|
|
free(json);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int key_store_load_profile_identity(const char *pubkey_hex,
|
|
key_store_method_t *method_out) {
|
|
if (pubkey_hex == NULL || pubkey_hex[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
char path[512];
|
|
profile_get_identity_path(pubkey_hex, path, sizeof(path));
|
|
if (path[0] == '\0') {
|
|
return -1;
|
|
}
|
|
|
|
FILE *f = fopen(path, "r");
|
|
if (f == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
char buf[1024];
|
|
size_t n = fread(buf, 1, sizeof(buf) - 1, f);
|
|
fclose(f);
|
|
buf[n] = '\0';
|
|
|
|
cJSON *root = cJSON_Parse(buf);
|
|
if (root == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
int rc = -1;
|
|
cJSON *pk = cJSON_GetObjectItemCaseSensitive(root, "pubkey_hex");
|
|
if (cJSON_IsString(pk) && strcmp(pk->valuestring, pubkey_hex) == 0) {
|
|
if (method_out) {
|
|
cJSON *m = cJSON_GetObjectItemCaseSensitive(root, "method");
|
|
if (cJSON_IsNumber(m)) {
|
|
*method_out = (key_store_method_t)m->valuedouble;
|
|
}
|
|
}
|
|
rc = 0;
|
|
}
|
|
|
|
cJSON_Delete(root);
|
|
return rc;
|
|
}
|