diff --git a/include/nsigner_auth.h b/include/nsigner_auth.h new file mode 100644 index 0000000..27d4e80 --- /dev/null +++ b/include/nsigner_auth.h @@ -0,0 +1,26 @@ +#ifndef NSIGNER_AUTH_H +#define NSIGNER_AUTH_H + +#include "../resources/nostr_core_lib/cjson/cJSON.h" + +#include + +typedef struct { + int enabled; + unsigned char privkey[32]; + char label[64]; +} nt_nsigner_auth_ctx_t; + +/* + * Build a TCP auth envelope event (kind 27235) for an n_signer JSON-RPC request. + * Caller owns *out_auth and must cJSON_Delete() it. + * Returns 0 on success. + */ +int nt_nsigner_auth_build(const char *request_id, + const char *method, + const cJSON *params, + const nt_nsigner_auth_ctx_t *auth_ctx, + time_t created_at, + cJSON **out_auth); + +#endif /* NSIGNER_AUTH_H */ diff --git a/include/nsigner_client.h b/include/nsigner_client.h index 36a7f3f..917d556 100644 --- a/include/nsigner_client.h +++ b/include/nsigner_client.h @@ -2,6 +2,7 @@ #define NSIGNER_CLIENT_H #include "../resources/nostr_core_lib/cjson/cJSON.h" +#include "nsigner_auth.h" /* Selector for key derivation/role in n_signer requests. */ typedef struct { @@ -25,6 +26,7 @@ static inline nt_nsigner_selector_t nsigner_selector_default(void) { #define NT_NSIGNER_E_UNAUTH -3 #define NT_NSIGNER_E_PARSE -4 #define NT_NSIGNER_E_ERROR -5 +#define NT_NSIGNER_E_AUTH -6 /* * Enumerate running nsigner instances by reading /proc/net/unix. @@ -75,23 +77,81 @@ int nsigner_nip44_decrypt(const char *socket_name, const nt_nsigner_selector_t * * URL helpers for FIPS/TCP transport. * URL format: http://host:port (path/query ignored if present). */ -int nsigner_get_public_key_url(const char *url, const nt_nsigner_selector_t *selector, char *out_hex_65); +int nsigner_get_public_key_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, char *out_hex_65); int nsigner_sign_event_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *unsigned_event_json, char **signed_event_json_out); int nsigner_nip04_encrypt_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *peer_pub_hex, const char *plaintext, char **cipher_out); int nsigner_nip04_decrypt_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *peer_pub_hex, const char *ciphertext, char **plain_out); int nsigner_nip44_encrypt_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *peer_pub_hex, const char *plaintext, char **cipher_out); int nsigner_nip44_decrypt_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *peer_pub_hex, const char *ciphertext, char **plain_out); +/* + * Persistent URL session helpers. + * Open once and reuse the fd for all RPC calls to keep a stable TCP source port. + */ +int nsigner_session_open_url(const char *url); +void nsigner_session_close(int fd); + +int nsigner_session_get_public_key(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + char *out_hex_65); + +int nsigner_session_sign_event(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *unsigned_event_json, + char **signed_event_json_out); + +int nsigner_session_nip04_encrypt(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *peer_pub_hex, + const char *plaintext, + char **cipher_out); + +int nsigner_session_nip04_decrypt(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *peer_pub_hex, + const char *ciphertext, + char **plain_out); + +int nsigner_session_nip44_encrypt(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *peer_pub_hex, + const char *plaintext, + char **cipher_out); + +int nsigner_session_nip44_decrypt(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *peer_pub_hex, + const char *ciphertext, + char **plain_out); + /* * qrexec helpers for cross-qube n_signer transport. * target_qube: destination signer qube (e.g. "nsigner-vault"). diff --git a/include/signer.h b/include/signer.h index 8a5edc0..ae48df1 100644 --- a/include/signer.h +++ b/include/signer.h @@ -5,6 +5,7 @@ #include "nsigner_client.h" #include +#include typedef enum { NT_SIGNER_LOCAL = 0, @@ -19,17 +20,27 @@ typedef struct { char qrexec_target[128]; char qrexec_service[128]; char endpoint_url[256]; + int url_session_fd; nt_nsigner_selector_t selector; + nt_nsigner_auth_ctx_t url_auth; } nt_signer_t; extern nt_signer_t g_signer; void signer_init(void); +void signer_shutdown(void); int signer_init_local(void); void signer_set_selector(int has_index, int index, const char *role); int signer_init_nsigner(const char *socket_name, const nt_nsigner_selector_t *selector); int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name, const nt_nsigner_selector_t *selector); int signer_init_nsigner_url(const char *endpoint_url, const nt_nsigner_selector_t *selector); +int signer_init_nsigner_url_with_session(const char *endpoint_url, + const nt_nsigner_selector_t *selector, + int session_fd); +int signer_init_nsigner_url_with_session_auth(const char *endpoint_url, + const nt_nsigner_selector_t *selector, + int session_fd, + const nt_nsigner_auth_ctx_t *auth_or_null); int signer_create_and_sign(int kind, const char *content, diff --git a/src/db.c b/src/db.c index 617b1f8..b4b9625 100644 --- a/src/db.c +++ b/src/db.c @@ -179,7 +179,10 @@ int db_open(void) { return -1; } - snprintf(db_path, sizeof(db_path), "%s/nostr.db", dir_path); + if (snprintf(db_path, sizeof(db_path), "%s/nostr.db", dir_path) >= (int)sizeof(db_path)) { + fprintf(stderr, "db_open: database path too long\n"); + return -1; + } rc = sqlite3_open(db_path, &g_db); if (rc != SQLITE_OK) { fprintf(stderr, "db_open: sqlite open failed: %s\n", sqlite3_errmsg(g_db)); diff --git a/src/main.c b/src/main.c index e98f883..ef87bbb 100644 --- a/src/main.c +++ b/src/main.c @@ -3,6 +3,7 @@ #include "tui.h" #include "../resources/nostr_core_lib/nostr_core/nostr_common.h" +#include "signer.h" /* * Canonical version is the latest git tag. @@ -10,8 +11,8 @@ */ #define NT_VERSION_MAJOR 0 #define NT_VERSION_MINOR 0 -#define NT_VERSION_PATCH 11 -#define NT_VERSION "v0.0.11" +#define NT_VERSION_PATCH 12 +#define NT_VERSION "v0.0.12" #include #include @@ -208,6 +209,7 @@ int main(int argc, char *argv[]) { menu_main(); nostr_cleanup(); + signer_shutdown(); state_cleanup(); tui_cleanup(); db_close(); diff --git a/src/menu_ai.c b/src/menu_ai.c index 836a91a..4c84a0e 100644 --- a/src/menu_ai.c +++ b/src/menu_ai.c @@ -500,7 +500,7 @@ static void ai_chat(const ai_prefs_t *prefs) { char auth[4096]; char *resp_json; char *answer; - char hold[16]; + char hold[512]; if (!prefs) { return; @@ -513,7 +513,7 @@ static void ai_chat(const ai_prefs_t *prefs) { if (!prefs->api_key || prefs->api_key[0] == '\0') { tui_print("AI API key is empty. Configure settings first."); - tui_get_line(">", hold, (int)sizeof(hold)); + tui_get_line("Press Enter to continue >", hold, (int)sizeof(hold)); return; } @@ -548,7 +548,7 @@ static void ai_chat(const ai_prefs_t *prefs) { if (!resp_json) { tui_print("AI request failed."); - tui_get_line(">", hold, (int)sizeof(hold)); + tui_get_line("Press Enter to continue >", hold, (int)sizeof(hold)); return; } @@ -563,7 +563,7 @@ static void ai_chat(const ai_prefs_t *prefs) { tui_print("Failed to parse AI response."); } free(answer); - tui_get_line(">", hold, (int)sizeof(hold)); + tui_get_line("Press Enter to continue >", hold, (int)sizeof(hold)); } static void ai_choose_model(ai_prefs_t *prefs) { @@ -585,7 +585,7 @@ static void ai_settings(ai_prefs_t *prefs) { char endpoint[512]; char key[1024]; char model[256]; - char hold[16]; + char hold[512]; if (!prefs) { return; @@ -617,12 +617,12 @@ static void ai_settings(ai_prefs_t *prefs) { tui_print("Prefs saved."); } - tui_get_line(">", hold, (int)sizeof(hold)); + tui_get_line("Press Enter to continue >", hold, (int)sizeof(hold)); } void menu_ai(void) { ai_prefs_t prefs; - char input[16]; + char input[512]; ai_prefs_init(&prefs); (void)ai_load_prefs(&prefs); diff --git a/src/menu_follows.c b/src/menu_follows.c index 1147f56..fda2295 100644 --- a/src/menu_follows.c +++ b/src/menu_follows.c @@ -127,7 +127,8 @@ void menu_follows(void) { tui_get_line(">", input, (int)sizeof(input)); continue; } - snprintf(follow_hex, sizeof(follow_hex), "%s", who); + strncpy(follow_hex, who, sizeof(follow_hex) - 1U); + follow_hex[sizeof(follow_hex) - 1U] = '\0'; } else { tui_print("Enter npub or 64-char hex pubkey."); tui_get_line(">", input, (int)sizeof(input)); diff --git a/src/menu_live.c b/src/menu_live.c index 16b6901..86f2f33 100644 --- a/src/menu_live.c +++ b/src/menu_live.c @@ -60,7 +60,7 @@ static void live_preview_text(const char *text, int max_len, char *buf, size_t b buf[0] = '\0'; if (!text || text[0] == '\0' || max_len <= 0) { - snprintf(buf, buf_size, ""); + buf[0] = '\0'; return; } diff --git a/src/menu_login.c b/src/menu_login.c index 729a2f4..c8ed74d 100644 --- a/src/menu_login.c +++ b/src/menu_login.c @@ -17,6 +17,7 @@ #include #include #include +#include #define NT_DEV_SEED_PHRASE "cube dirt movie learn depth axis ball view aunt electric finish release" @@ -174,6 +175,7 @@ void menu_login(void) { if (strcmp(input, "q") == 0 || strcmp(input, "x") == 0) { tui_print("\nHasta luego.\n"); + signer_shutdown(); exit(0); } @@ -290,6 +292,8 @@ void menu_login(void) { nt_nsigner_selector_t selector = nsigner_selector_default(); char idxbuf[32]; int nostr_index = 0; + int session_fd = -1; + nt_nsigner_auth_ctx_t auth = {0}; snprintf(endpoint_url, sizeof(endpoint_url), "%s", (saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : ""); tui_get_line("Signer URL (http://.fips:8080) >", endpoint_url, (int)sizeof(endpoint_url)); @@ -317,13 +321,30 @@ void menu_login(void) { tui_print("Connecting to n_signer URL: %s", endpoint_url); tui_print("Waiting for n_signer approval..."); - if (nsigner_get_public_key_url(endpoint_url, &selector, pubkey_hex) != 0) { + auth.enabled = 1; + if (getrandom(auth.privkey, sizeof(auth.privkey), 0) != (ssize_t)sizeof(auth.privkey)) { + tui_print("Failed to generate URL auth key."); + tui_get_line(">", input, (int)sizeof(input)); + continue; + } + snprintf(auth.label, sizeof(auth.label), "%s", "nostr_terminal"); + + session_fd = nsigner_session_open_url(endpoint_url); + if (session_fd < 0) { + tui_print("Failed to open persistent connection to n_signer URL."); + tui_get_line(">", input, (int)sizeof(input)); + continue; + } + + if (nsigner_session_get_public_key(&session_fd, endpoint_url, &selector, &auth, pubkey_hex) != 0) { + nsigner_session_close(session_fd); tui_print("Failed to get public key from n_signer URL (denied or error)."); tui_get_line(">", input, (int)sizeof(input)); continue; } - if (signer_init_nsigner_url(endpoint_url, &selector) != 0) { + if (signer_init_nsigner_url_with_session_auth(endpoint_url, &selector, session_fd, &auth) != 0) { + nsigner_session_close(session_fd); tui_print("Failed to initialize URL signer mode."); tui_get_line(">", input, (int)sizeof(input)); continue; diff --git a/src/net.c b/src/net.c index 7073368..0e2b97e 100644 --- a/src/net.c +++ b/src/net.c @@ -530,7 +530,7 @@ void nt_publish_results_free(nt_publish_result_t *results, int result_count) { } /* Query one relay and append deduplicated EVENT payloads until EOSE/timeout. */ -static int nt_query_one_relay(const char *relay_url, +static __attribute__((unused)) int nt_query_one_relay(const char *relay_url, cJSON *filter, int timeout_ms, int verbose, diff --git a/src/nsigner_auth.c b/src/nsigner_auth.c new file mode 100644 index 0000000..c8eb5e5 --- /dev/null +++ b/src/nsigner_auth.c @@ -0,0 +1,109 @@ +#include "nsigner_auth.h" + +#include "../resources/nostr_core_lib/nostr_core/nip001.h" +#include "../resources/nostr_core_lib/nostr_core/utils.h" + +#include +#include +#include + +#define NT_NSIGNER_AUTH_EVENT_KIND 27235 + +static cJSON *nt_nsigner_auth_build_tag_pair(const char *name, const char *value) { + cJSON *pair; + + if (!name || !value) { + return NULL; + } + + pair = cJSON_CreateArray(); + if (!pair) { + return NULL; + } + + cJSON_AddItemToArray(pair, cJSON_CreateString(name)); + cJSON_AddItemToArray(pair, cJSON_CreateString(value)); + return pair; +} + +static int nt_nsigner_auth_compute_params_hash_hex(const cJSON *params, char out_hex_65[65]) { + char *params_json = NULL; + unsigned char hash[32]; + int rc = -1; + + if (!out_hex_65) { + return -1; + } + + if (params) { + params_json = cJSON_PrintUnformatted((cJSON *)params); + } else { + params_json = strdup("null"); + } + + if (!params_json) { + return -1; + } + + if (nostr_sha256((const unsigned char *)params_json, strlen(params_json), hash) != 0) { + goto cleanup; + } + + nostr_bytes_to_hex(hash, sizeof(hash), out_hex_65); + out_hex_65[64] = '\0'; + rc = 0; + +cleanup: + free(params_json); + return rc; +} + +int nt_nsigner_auth_build(const char *request_id, + const char *method, + const cJSON *params, + const nt_nsigner_auth_ctx_t *auth_ctx, + time_t created_at, + cJSON **out_auth) { + char body_hash_hex[65]; + cJSON *tags = NULL; + cJSON *auth_event = NULL; + + if (!request_id || request_id[0] == '\0' || + !method || method[0] == '\0' || + !auth_ctx || !auth_ctx->enabled || + !out_auth) { + return -1; + } + + *out_auth = NULL; + + if (nt_nsigner_auth_compute_params_hash_hex(params, body_hash_hex) != 0) { + return -1; + } + + tags = cJSON_CreateArray(); + if (!tags) { + return -1; + } + + cJSON_AddItemToArray(tags, nt_nsigner_auth_build_tag_pair("nsigner_rpc", request_id)); + cJSON_AddItemToArray(tags, nt_nsigner_auth_build_tag_pair("nsigner_method", method)); + cJSON_AddItemToArray(tags, nt_nsigner_auth_build_tag_pair("nsigner_body_hash", body_hash_hex)); + + if (created_at <= 0) { + created_at = time(NULL); + } + + auth_event = nostr_create_and_sign_event(NT_NSIGNER_AUTH_EVENT_KIND, + auth_ctx->label, + tags, + auth_ctx->privkey, + created_at); + if (!auth_event) { + cJSON_Delete(tags); + return -1; + } + + *out_auth = auth_event; + return 0; +} diff --git a/src/nsigner_client.c b/src/nsigner_client.c index 0ea8ffb..73a0a85 100644 --- a/src/nsigner_client.c +++ b/src/nsigner_client.c @@ -12,6 +12,7 @@ #include #include #include +#include static int write_all(int fd, const void *buf, size_t len) { const unsigned char *p = (const unsigned char *)buf; @@ -71,6 +72,7 @@ static int set_recv_timeout_ms(int fd, int timeout_ms) { static int nsigner_error_code_from_response(const cJSON *response) { const cJSON *error_item; const char *name = NULL; + int code = 0; error_item = cJSON_GetObjectItemCaseSensitive((cJSON *)response, "error"); if (!error_item || cJSON_IsNull(error_item)) { @@ -81,18 +83,28 @@ static int nsigner_error_code_from_response(const cJSON *response) { name = error_item->valuestring; } else if (cJSON_IsObject(error_item)) { const cJSON *message_item = cJSON_GetObjectItemCaseSensitive((cJSON *)error_item, "message"); + const cJSON *code_item = cJSON_GetObjectItemCaseSensitive((cJSON *)error_item, "code"); if (cJSON_IsString(message_item) && message_item->valuestring) { name = message_item->valuestring; } + if (cJSON_IsNumber(code_item)) { + code = code_item->valueint; + } } - if (name && strcmp(name, "approval_denied") == 0) { + if ((name && strcmp(name, "approval_denied") == 0) || + (name && strcmp(name, "policy_denied") == 0)) { return NT_NSIGNER_E_DENIED; } if (name && strcmp(name, "unauthorized") == 0) { return NT_NSIGNER_E_UNAUTH; } + if ((name && strncmp(name, "auth_envelope_", 14) == 0) || + (code >= 2010 && code <= 2017)) { + return NT_NSIGNER_E_AUTH; + } + return NT_NSIGNER_E_ERROR; } @@ -349,6 +361,7 @@ static int nsigner_connect_url(const char *url) { for (ai = res; ai; ai = ai->ai_next) { struct timeval tv; + int one = 1; fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (fd < 0) { @@ -358,6 +371,10 @@ static int nsigner_connect_url(const char *url) { tv.tv_sec = 5; tv.tv_usec = 0; (void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + (void)setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)); +#ifdef TCP_NODELAY + (void)setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); +#endif if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) { break; @@ -557,36 +574,69 @@ static int nsigner_call_string_method(const char *socket_name, return *result_out ? NT_NSIGNER_OK : NT_NSIGNER_E_IO; } -static int nsigner_call_string_method_url(const char *url, - const nt_nsigner_selector_t *selector, - const char *method, - const char **args, - int arg_count, - char **result_out) { - int fd = -1; +static int nsigner_rpc_with_retry(int *fd_inout, + const char *url, + const cJSON *request, + cJSON **response_out, + int read_timeout_ms) { + int attempts = 0; + + if (!fd_inout || !url || !request || !response_out) { + return NT_NSIGNER_E_IO; + } + *response_out = NULL; + + while (attempts < 2) { + int rc; + + if (*fd_inout < 0) { + *fd_inout = nsigner_connect_url(url); + if (*fd_inout < 0) { + attempts++; + continue; + } + } + + rc = nsigner_rpc(*fd_inout, request, response_out, read_timeout_ms); + if (rc == NT_NSIGNER_E_IO) { + close(*fd_inout); + *fd_inout = -1; + attempts++; + continue; + } + + return rc; + } + + return NT_NSIGNER_E_IO; +} + +static int nsigner_call_string_method_url_session(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *method, + const char **args, + int arg_count, + char **result_out) { cJSON *request = NULL; cJSON *params = NULL; + cJSON *selector_obj = NULL; cJSON *response = NULL; cJSON *result_item; + cJSON *auth_json = NULL; int rc; - if (!url || !method || !result_out || arg_count < 0) { + if (!fd_inout || !url || !method || !result_out || arg_count < 0) { return NT_NSIGNER_E_IO; } - cJSON *selector_obj = NULL; *result_out = NULL; - fd = nsigner_connect_url(url); - if (fd < 0) { - return NT_NSIGNER_E_IO; - } - request = cJSON_CreateObject(); params = cJSON_CreateArray(); if (!request || !params) { cJSON_Delete(request); cJSON_Delete(params); - close(fd); return NT_NSIGNER_E_IO; } @@ -597,7 +647,6 @@ static int nsigner_call_string_method_url(const char *url, if (!args || !args[i]) { cJSON_Delete(request); cJSON_Delete(params); - close(fd); return NT_NSIGNER_E_IO; } cJSON_AddItemToArray(params, cJSON_CreateString(args[i])); @@ -607,16 +656,25 @@ static int nsigner_call_string_method_url(const char *url, if (!selector_obj) { cJSON_Delete(request); cJSON_Delete(params); - close(fd); return NT_NSIGNER_E_IO; } cJSON_AddItemToArray(params, selector_obj); cJSON_AddItemToObject(request, "params", params); - rc = nsigner_rpc(fd, request, &response, 60000); + if (auth && auth->enabled) { + if (nt_nsigner_auth_build("1", method, params, auth, 0, &auth_json) != 0 || + !auth_json || + !cJSON_AddItemToObject(request, "auth", auth_json)) { + cJSON_Delete(auth_json); + cJSON_Delete(request); + return NT_NSIGNER_E_IO; + } + auth_json = NULL; + } + + rc = nsigner_rpc_with_retry(fd_inout, url, request, &response, 60000); cJSON_Delete(request); - close(fd); if (rc != NT_NSIGNER_OK) { return rc; } @@ -633,6 +691,23 @@ static int nsigner_call_string_method_url(const char *url, return *result_out ? NT_NSIGNER_OK : NT_NSIGNER_E_IO; } +static int nsigner_call_string_method_url(const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *method, + const char **args, + int arg_count, + char **result_out) { + int fd = -1; + int rc; + + rc = nsigner_call_string_method_url_session(&fd, url, selector, auth, method, args, arg_count, result_out); + if (fd >= 0) { + close(fd); + } + return rc; +} + static int nsigner_qrexec_rpc(const char *target_qube, const char *service_name, const cJSON *request, @@ -922,7 +997,135 @@ int nsigner_nip44_decrypt(const char *socket_name, const nt_nsigner_selector_t * return nsigner_call_string_method(socket_name, selector, "nip44_decrypt", args, 2, plain_out); } -int nsigner_get_public_key_url(const char *url, const nt_nsigner_selector_t *selector, char *out_hex_65) { +int nsigner_session_open_url(const char *url) { + return nsigner_connect_url(url); +} + +void nsigner_session_close(int fd) { + if (fd >= 0) { + close(fd); + } +} + +int nsigner_session_get_public_key(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + char *out_hex_65) { + char *result = NULL; + int rc; + size_t len; + + if (!fd_inout || !out_hex_65) { + return NT_NSIGNER_E_IO; + } + + rc = nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "get_public_key", NULL, 0, &result); + if (rc != NT_NSIGNER_OK) { + return rc; + } + + len = strlen(result); + if (len != 64) { + free(result); + return NT_NSIGNER_E_PARSE; + } + + memcpy(out_hex_65, result, len + 1); + free(result); + return NT_NSIGNER_OK; +} + +int nsigner_session_sign_event(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *unsigned_event_json, + char **signed_event_json_out) { + const char *args[1]; + + if (!fd_inout || !url || !unsigned_event_json || !signed_event_json_out) { + return NT_NSIGNER_E_IO; + } + + args[0] = unsigned_event_json; + return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "sign_event", args, 1, signed_event_json_out); +} + +int nsigner_session_nip04_encrypt(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *peer_pub_hex, + const char *plaintext, + char **cipher_out) { + const char *args[2]; + + if (!fd_inout || !url || !peer_pub_hex || !plaintext || !cipher_out) { + return NT_NSIGNER_E_IO; + } + + args[0] = peer_pub_hex; + args[1] = plaintext; + return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "nip04_encrypt", args, 2, cipher_out); +} + +int nsigner_session_nip04_decrypt(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *peer_pub_hex, + const char *ciphertext, + char **plain_out) { + const char *args[2]; + + if (!fd_inout || !url || !peer_pub_hex || !ciphertext || !plain_out) { + return NT_NSIGNER_E_IO; + } + + args[0] = peer_pub_hex; + args[1] = ciphertext; + return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "nip04_decrypt", args, 2, plain_out); +} + +int nsigner_session_nip44_encrypt(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *peer_pub_hex, + const char *plaintext, + char **cipher_out) { + const char *args[2]; + + if (!fd_inout || !url || !peer_pub_hex || !plaintext || !cipher_out) { + return NT_NSIGNER_E_IO; + } + + args[0] = peer_pub_hex; + args[1] = plaintext; + return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "nip44_encrypt", args, 2, cipher_out); +} + +int nsigner_session_nip44_decrypt(int *fd_inout, + const char *url, + const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, + const char *peer_pub_hex, + const char *ciphertext, + char **plain_out) { + const char *args[2]; + + if (!fd_inout || !url || !peer_pub_hex || !ciphertext || !plain_out) { + return NT_NSIGNER_E_IO; + } + + args[0] = peer_pub_hex; + args[1] = ciphertext; + return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "nip44_decrypt", args, 2, plain_out); +} + +int nsigner_get_public_key_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, char *out_hex_65) { char *result = NULL; int rc; size_t len; @@ -931,7 +1134,7 @@ int nsigner_get_public_key_url(const char *url, const nt_nsigner_selector_t *sel return NT_NSIGNER_E_IO; } - rc = nsigner_call_string_method_url(url, selector, "get_public_key", NULL, 0, &result); + rc = nsigner_call_string_method_url(url, selector, auth, "get_public_key", NULL, 0, &result); if (rc != NT_NSIGNER_OK) { return rc; } @@ -948,6 +1151,7 @@ int nsigner_get_public_key_url(const char *url, const nt_nsigner_selector_t *sel } int nsigner_sign_event_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *unsigned_event_json, char **signed_event_json_out) { const char *args[1]; @@ -956,10 +1160,11 @@ int nsigner_sign_event_url(const char *url, const nt_nsigner_selector_t *selecto } args[0] = unsigned_event_json; - return nsigner_call_string_method_url(url, selector, "sign_event", args, 1, signed_event_json_out); + return nsigner_call_string_method_url(url, selector, auth, "sign_event", args, 1, signed_event_json_out); } int nsigner_nip04_encrypt_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *peer_pub_hex, const char *plaintext, char **cipher_out) { const char *args[2]; @@ -969,10 +1174,11 @@ int nsigner_nip04_encrypt_url(const char *url, const nt_nsigner_selector_t *sele args[0] = peer_pub_hex; args[1] = plaintext; - return nsigner_call_string_method_url(url, selector, "nip04_encrypt", args, 2, cipher_out); + return nsigner_call_string_method_url(url, selector, auth, "nip04_encrypt", args, 2, cipher_out); } int nsigner_nip04_decrypt_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *peer_pub_hex, const char *ciphertext, char **plain_out) { const char *args[2]; @@ -982,10 +1188,11 @@ int nsigner_nip04_decrypt_url(const char *url, const nt_nsigner_selector_t *sele args[0] = peer_pub_hex; args[1] = ciphertext; - return nsigner_call_string_method_url(url, selector, "nip04_decrypt", args, 2, plain_out); + return nsigner_call_string_method_url(url, selector, auth, "nip04_decrypt", args, 2, plain_out); } int nsigner_nip44_encrypt_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *peer_pub_hex, const char *plaintext, char **cipher_out) { const char *args[2]; @@ -995,10 +1202,11 @@ int nsigner_nip44_encrypt_url(const char *url, const nt_nsigner_selector_t *sele args[0] = peer_pub_hex; args[1] = plaintext; - return nsigner_call_string_method_url(url, selector, "nip44_encrypt", args, 2, cipher_out); + return nsigner_call_string_method_url(url, selector, auth, "nip44_encrypt", args, 2, cipher_out); } int nsigner_nip44_decrypt_url(const char *url, const nt_nsigner_selector_t *selector, + const nt_nsigner_auth_ctx_t *auth, const char *peer_pub_hex, const char *ciphertext, char **plain_out) { const char *args[2]; @@ -1008,7 +1216,7 @@ int nsigner_nip44_decrypt_url(const char *url, const nt_nsigner_selector_t *sele args[0] = peer_pub_hex; args[1] = ciphertext; - return nsigner_call_string_method_url(url, selector, "nip44_decrypt", args, 2, plain_out); + return nsigner_call_string_method_url(url, selector, auth, "nip44_decrypt", args, 2, plain_out); } int nsigner_qrexec_get_public_key(const char *target_qube, diff --git a/src/signer.c b/src/signer.c index b14a054..046bfda 100644 --- a/src/signer.c +++ b/src/signer.c @@ -16,11 +16,17 @@ nt_signer_t g_signer = { .kind = NT_SIGNER_LOCAL, .socket_name = {0}, .endpoint_url = {0}, + .url_session_fd = -1, .selector = { .has_nostr_index = 1, .nostr_index = 0, .role = {0} }, + .url_auth = { + .enabled = 0, + .privkey = {0}, + .label = {0} + } }; static int signer_local_private_key(unsigned char out_priv[32]) { @@ -45,12 +51,26 @@ void signer_set_selector(int has_index, int index, const char *role) { void signer_init(void) { nt_nsigner_selector_t sel = nsigner_selector_default(); + + if (g_signer.url_session_fd >= 0) { + nsigner_session_close(g_signer.url_session_fd); + g_signer.url_session_fd = -1; + } + g_signer.kind = NT_SIGNER_LOCAL; g_signer.socket_name[0] = '\0'; g_signer.qrexec_target[0] = '\0'; g_signer.qrexec_service[0] = '\0'; g_signer.endpoint_url[0] = '\0'; signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role); + g_signer.url_auth.enabled = 0; + memset(g_signer.url_auth.privkey, 0, sizeof(g_signer.url_auth.privkey)); + g_signer.url_auth.label[0] = '\0'; +} + +void signer_shutdown(void) { + memset(g_signer.url_auth.privkey, 0, sizeof(g_signer.url_auth.privkey)); + signer_init(); } int signer_init_local(void) { @@ -69,6 +89,10 @@ int signer_init_nsigner(const char *socket_name, const nt_nsigner_selector_t *se g_signer.qrexec_target[0] = '\0'; g_signer.qrexec_service[0] = '\0'; g_signer.endpoint_url[0] = '\0'; + if (g_signer.url_session_fd >= 0) { + nsigner_session_close(g_signer.url_session_fd); + g_signer.url_session_fd = -1; + } signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role); return 0; } @@ -84,14 +108,24 @@ int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name snprintf(g_signer.qrexec_target, sizeof(g_signer.qrexec_target), "%s", target_qube); snprintf(g_signer.qrexec_service, sizeof(g_signer.qrexec_service), "%s", service_name); g_signer.endpoint_url[0] = '\0'; + if (g_signer.url_session_fd >= 0) { + nsigner_session_close(g_signer.url_session_fd); + g_signer.url_session_fd = -1; + } signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role); return 0; } -int signer_init_nsigner_url(const char *endpoint_url, const nt_nsigner_selector_t *selector) { +int signer_init_nsigner_url_with_session_auth(const char *endpoint_url, + const nt_nsigner_selector_t *selector, + int session_fd, + const nt_nsigner_auth_ctx_t *auth_or_null) { nt_nsigner_selector_t sel = selector ? *selector : nsigner_selector_default(); if (!endpoint_url || endpoint_url[0] == '\0') { + if (session_fd >= 0) { + nsigner_session_close(session_fd); + } return -1; } @@ -100,10 +134,41 @@ int signer_init_nsigner_url(const char *endpoint_url, const nt_nsigner_selector_ g_signer.qrexec_target[0] = '\0'; g_signer.qrexec_service[0] = '\0'; snprintf(g_signer.endpoint_url, sizeof(g_signer.endpoint_url), "%s", endpoint_url); + + if (g_signer.url_session_fd >= 0 && g_signer.url_session_fd != session_fd) { + nsigner_session_close(g_signer.url_session_fd); + } + g_signer.url_session_fd = session_fd; + + if (g_signer.url_session_fd < 0) { + g_signer.url_session_fd = nsigner_session_open_url(g_signer.endpoint_url); + } + signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role); + + g_signer.url_auth.enabled = 0; + memset(g_signer.url_auth.privkey, 0, sizeof(g_signer.url_auth.privkey)); + g_signer.url_auth.label[0] = '\0'; + + if (auth_or_null && auth_or_null->enabled) { + g_signer.url_auth.enabled = 1; + memcpy(g_signer.url_auth.privkey, auth_or_null->privkey, sizeof(g_signer.url_auth.privkey)); + snprintf(g_signer.url_auth.label, sizeof(g_signer.url_auth.label), "%s", auth_or_null->label); + } + return 0; } +int signer_init_nsigner_url_with_session(const char *endpoint_url, + const nt_nsigner_selector_t *selector, + int session_fd) { + return signer_init_nsigner_url_with_session_auth(endpoint_url, selector, session_fd, NULL); +} + +int signer_init_nsigner_url(const char *endpoint_url, const nt_nsigner_selector_t *selector) { + return signer_init_nsigner_url_with_session_auth(endpoint_url, selector, -1, NULL); +} + int signer_get_local_private_key(unsigned char out_priv[32]) { if (!out_priv) { return -1; @@ -269,10 +334,12 @@ int signer_create_and_sign(int kind, return -1; } - rc = nsigner_sign_event_url(g_signer.endpoint_url, - &g_signer.selector, - unsigned_json, - signed_event_json_out); + rc = nsigner_session_sign_event(&g_signer.url_session_fd, + g_signer.endpoint_url, + &g_signer.selector, + g_signer.url_auth.enabled ? &g_signer.url_auth : NULL, + unsigned_json, + signed_event_json_out); free(unsigned_json); cJSON_Delete(unsigned_event); @@ -342,10 +409,12 @@ int signer_sign_event_json(const char *unsigned_event_json, char **signed_event_ } if (g_signer.kind == NT_SIGNER_NSIGNER_URL) { - return nsigner_sign_event_url(g_signer.endpoint_url, - &g_signer.selector, - unsigned_event_json, - signed_event_json_out); + return nsigner_session_sign_event(&g_signer.url_session_fd, + g_signer.endpoint_url, + &g_signer.selector, + g_signer.url_auth.enabled ? &g_signer.url_auth : NULL, + unsigned_event_json, + signed_event_json_out); } if (g_signer.kind != NT_SIGNER_LOCAL) { @@ -422,11 +491,13 @@ int signer_nip04_encrypt(const char *peer_pub_hex, const char *plaintext, char * } if (g_signer.kind == NT_SIGNER_NSIGNER_URL) { - return nsigner_nip04_encrypt_url(g_signer.endpoint_url, - &g_signer.selector, - peer_pub_hex, - plaintext, - cipher_out); + return nsigner_session_nip04_encrypt(&g_signer.url_session_fd, + g_signer.endpoint_url, + &g_signer.selector, + g_signer.url_auth.enabled ? &g_signer.url_auth : NULL, + peer_pub_hex, + plaintext, + cipher_out); } if (g_signer.kind != NT_SIGNER_LOCAL) { @@ -481,11 +552,13 @@ int signer_nip04_decrypt(const char *peer_pub_hex, const char *ciphertext, char } if (g_signer.kind == NT_SIGNER_NSIGNER_URL) { - return nsigner_nip04_decrypt_url(g_signer.endpoint_url, - &g_signer.selector, - peer_pub_hex, - ciphertext, - plain_out); + return nsigner_session_nip04_decrypt(&g_signer.url_session_fd, + g_signer.endpoint_url, + &g_signer.selector, + g_signer.url_auth.enabled ? &g_signer.url_auth : NULL, + peer_pub_hex, + ciphertext, + plain_out); } if (g_signer.kind != NT_SIGNER_LOCAL) { @@ -540,11 +613,13 @@ int signer_nip44_encrypt(const char *peer_pub_hex, const char *plaintext, char * } if (g_signer.kind == NT_SIGNER_NSIGNER_URL) { - return nsigner_nip44_encrypt_url(g_signer.endpoint_url, - &g_signer.selector, - peer_pub_hex, - plaintext, - cipher_out); + return nsigner_session_nip44_encrypt(&g_signer.url_session_fd, + g_signer.endpoint_url, + &g_signer.selector, + g_signer.url_auth.enabled ? &g_signer.url_auth : NULL, + peer_pub_hex, + plaintext, + cipher_out); } if (g_signer.kind != NT_SIGNER_LOCAL) { @@ -599,11 +674,13 @@ int signer_nip44_decrypt(const char *peer_pub_hex, const char *ciphertext, char } if (g_signer.kind == NT_SIGNER_NSIGNER_URL) { - return nsigner_nip44_decrypt_url(g_signer.endpoint_url, - &g_signer.selector, - peer_pub_hex, - ciphertext, - plain_out); + return nsigner_session_nip44_decrypt(&g_signer.url_session_fd, + g_signer.endpoint_url, + &g_signer.selector, + g_signer.url_auth.enabled ? &g_signer.url_auth : NULL, + peer_pub_hex, + ciphertext, + plain_out); } if (g_signer.kind != NT_SIGNER_LOCAL) {