3 Commits

8 changed files with 1070 additions and 13 deletions

View File

@@ -152,14 +152,10 @@ chmod +x "$BUILD_DIR/$OUTPUT_NAME"
print_status "Verifying binary with ldd/file"
LDD_OK=false
if LDD_OUTPUT=$(ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1); then
echo "$LDD_OUTPUT" >&2
if echo "$LDD_OUTPUT" | grep -q "not a dynamic executable\|statically linked"; then
LDD_OK=true
fi
else
print_warning "ldd check failed (this can happen for foreign-arch binaries)"
echo "$LDD_OUTPUT" >&2
LDD_OUTPUT=$(ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1 || true)
echo "$LDD_OUTPUT" >&2
if echo "$LDD_OUTPUT" | grep -q "not a dynamic executable\|statically linked"; then
LDD_OK=true
fi
FILE_OUTPUT=$(file "$BUILD_DIR/$OUTPUT_NAME" 2>&1 || true)

View File

@@ -56,4 +56,69 @@ int nsigner_nip44_encrypt(const char *socket_name, const char *role,
int nsigner_nip44_decrypt(const char *socket_name, const char *role,
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
/*
* 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 char *role, char *out_hex_65);
int nsigner_sign_event_url(const char *url, const char *role,
const char *unsigned_event_json, char **signed_event_json_out);
int nsigner_nip04_encrypt_url(const char *url, const char *role,
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int nsigner_nip04_decrypt_url(const char *url, const char *role,
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
int nsigner_nip44_encrypt_url(const char *url, const char *role,
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
int nsigner_nip44_decrypt_url(const char *url, const char *role,
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").
* service_name: qrexec service name (e.g. "qubes.NsignerRpc").
*/
int nsigner_qrexec_get_public_key(const char *target_qube,
const char *service_name,
const char *role,
char *out_hex_65);
int nsigner_qrexec_sign_event(const char *target_qube,
const char *service_name,
const char *role,
const char *unsigned_event_json,
char **signed_event_json_out);
int nsigner_qrexec_nip04_encrypt(const char *target_qube,
const char *service_name,
const char *role,
const char *peer_pub_hex,
const char *plaintext,
char **cipher_out);
int nsigner_qrexec_nip04_decrypt(const char *target_qube,
const char *service_name,
const char *role,
const char *peer_pub_hex,
const char *ciphertext,
char **plain_out);
int nsigner_qrexec_nip44_encrypt(const char *target_qube,
const char *service_name,
const char *role,
const char *peer_pub_hex,
const char *plaintext,
char **cipher_out);
int nsigner_qrexec_nip44_decrypt(const char *target_qube,
const char *service_name,
const char *role,
const char *peer_pub_hex,
const char *ciphertext,
char **plain_out);
#endif /* NSIGNER_CLIENT_H */

View File

@@ -7,12 +7,17 @@
typedef enum {
NT_SIGNER_LOCAL = 0,
NT_SIGNER_NSIGNER = 1
NT_SIGNER_NSIGNER = 1,
NT_SIGNER_NSIGNER_QREXEC = 2,
NT_SIGNER_NSIGNER_URL = 3
} nt_signer_kind_t;
typedef struct {
nt_signer_kind_t kind;
char socket_name[128];
char qrexec_target[128];
char qrexec_service[128];
char endpoint_url[256];
char role[64];
} nt_signer_t;
@@ -21,6 +26,8 @@ extern nt_signer_t g_signer;
void signer_init(void);
int signer_init_local(void);
int signer_init_nsigner(const char *socket_name);
int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name);
int signer_init_nsigner_url(const char *endpoint_url);
int signer_create_and_sign(int kind,
const char *content,

View File

@@ -10,8 +10,8 @@
*/
#define NT_VERSION_MAJOR 0
#define NT_VERSION_MINOR 0
#define NT_VERSION_PATCH 6
#define NT_VERSION "v0.0.6"
#define NT_VERSION_PATCH 9
#define NT_VERSION "v0.0.9"
#include <stdio.h>
#include <stdlib.h>

View File

@@ -1,6 +1,7 @@
#include "tui.h"
#include "state.h"
#include "net.h"
#include "db.h"
#include "publish.h"
#include "signer.h"
@@ -160,7 +161,14 @@ void menu_login(void) {
unsigned char pub[32];
tui_clear_screen();
tui_print("LOGIN");
tui_print("LOGIN\n");
tui_print("^_E^:nter test/dev seed fallback");
tui_print("^_P^:rivate key (nsec or 64-hex)");
tui_print("^_M^:nemonic (12 words)");
tui_print("^_S^:igner local (same qube)");
tui_print("^_U^:RL signer (FIPS/web address)");
tui_print("^_N^:ew account");
tui_print("^_Q^:uit");
tui_get_line(">", input, (int)sizeof(input));
@@ -169,6 +177,20 @@ void menu_login(void) {
exit(0);
}
if (input[0] == 'e' || input[0] == 'E') {
input[0] = '\0';
} else if (input[0] == 'p' || input[0] == 'P') {
tui_get_line("Private key (nsec or 64-hex) >", input, (int)sizeof(input));
if (input[0] == '\0') {
continue;
}
} else if (input[0] == 'm' || input[0] == 'M') {
tui_get_line("Seed phrase (12 words) >", input, (int)sizeof(input));
if (input[0] == '\0') {
continue;
}
}
if (strcmp(input, "s") == 0 || strcmp(input, "S") == 0) {
char **names = NULL;
int name_count = 0;
@@ -247,6 +269,126 @@ void menu_login(void) {
return;
}
if (strcmp(input, "u") == 0 || strcmp(input, "U") == 0) {
char pubkey_hex[65] = {0};
char endpoint_url[256];
const char *saved_endpoint = db_get_local("nsigner_url_endpoint");
snprintf(endpoint_url, sizeof(endpoint_url), "%s", (saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : "");
tui_get_line("Signer URL (http://<npub>.fips:8080) >", endpoint_url, (int)sizeof(endpoint_url));
if (endpoint_url[0] == '\0') {
if (saved_endpoint && saved_endpoint[0] != '\0') {
snprintf(endpoint_url, sizeof(endpoint_url), "%s", saved_endpoint);
} else {
tui_print("No URL provided.");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
}
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, "main", pubkey_hex) != 0) {
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) != 0) {
tui_print("Failed to initialize URL signer mode.");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
(void)db_set_local("nsigner_url_endpoint", endpoint_url);
snprintf(g_state.npub_hex, sizeof(g_state.npub_hex), "%s", pubkey_hex);
{
unsigned char pub_bytes[32];
nostr_hex_to_bytes(pubkey_hex, pub_bytes, 32);
nostr_key_to_bech32(pub_bytes, "npub", g_state.npub_bech32);
}
g_state.nsec_hex[0] = '\0';
g_state.nsec_bech32[0] = '\0';
g_state.seed_phrase[0] = '\0';
g_state.logged_in = 1;
tui_print("Signed in via n_signer URL (%s)", endpoint_url);
tui_print("npub: %s", g_state.npub_bech32);
(void)state_load_user_info();
if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) {
tui_print("No profile found on relays. Setting up new user...");
menu_add_new_user();
(void)state_load_user_info();
}
return;
}
if (strcmp(input, "S") == 0) {
char pubkey_hex[65] = {0};
char target_qube[128];
char service_name[128];
const char *saved_target = db_get_local("nsigner_qrexec_target");
snprintf(target_qube, sizeof(target_qube), "%s", (saved_target && saved_target[0] != '\0') ? saved_target : "nsigner-vault");
snprintf(service_name, sizeof(service_name), "%s", "qubes.NsignerRpc");
tui_get_line("Target signer qube (nsigner-vault) >", target_qube, (int)sizeof(target_qube));
if (target_qube[0] == '\0') {
snprintf(target_qube, sizeof(target_qube), "%s", (saved_target && saved_target[0] != '\0') ? saved_target : "nsigner-vault");
}
tui_get_line("Qrexec service (qubes.NsignerRpc) >", service_name, (int)sizeof(service_name));
if (service_name[0] == '\0') {
snprintf(service_name, sizeof(service_name), "%s", "qubes.NsignerRpc");
}
tui_print("Calling qrexec service %s in %s...", service_name, target_qube);
tui_print("Waiting for n_signer approval...");
if (nsigner_qrexec_get_public_key(target_qube, service_name, "main", pubkey_hex) != 0) {
tui_print("Failed to get public key via qrexec (denied or error).");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
if (signer_init_nsigner_qrexec(target_qube, service_name) != 0) {
tui_print("Failed to initialize qrexec signer mode.");
tui_get_line(">", input, (int)sizeof(input));
continue;
}
(void)db_set_local("nsigner_qrexec_target", target_qube);
snprintf(g_state.npub_hex, sizeof(g_state.npub_hex), "%s", pubkey_hex);
{
unsigned char pub_bytes[32];
nostr_hex_to_bytes(pubkey_hex, pub_bytes, 32);
nostr_key_to_bech32(pub_bytes, "npub", g_state.npub_bech32);
}
g_state.nsec_hex[0] = '\0';
g_state.nsec_bech32[0] = '\0';
g_state.seed_phrase[0] = '\0';
g_state.logged_in = 1;
tui_print("Signed in via qrexec n_signer (%s:%s)", target_qube, service_name);
tui_print("npub: %s", g_state.npub_bech32);
(void)state_load_user_info();
if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) {
tui_print("No profile found on relays. Setting up new user...");
menu_add_new_user();
(void)state_load_user_info();
}
return;
}
if (strcmp(input, "n") == 0) {
char mnemonic[256];
char idxbuf[32];
@@ -382,7 +524,7 @@ void menu_login(void) {
}
}
tui_print("Invalid login input. Enter 12 words, nsec, 64-hex, [s], [n], or [q].");
tui_print("Invalid login input. Choose a menu command or enter valid key/mnemonic data.");
tui_get_line(">", input, (int)sizeof(input));
}
}

View File

@@ -29,6 +29,13 @@ void menu_profile(void) {
if (g_signer.kind == NT_SIGNER_NSIGNER) {
tui_print("Signer: n_signer (@%s)", g_signer.socket_name);
} else if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
tui_print("Signer: n_signer qrexec");
tui_print("Signer target: %s", g_signer.qrexec_target);
tui_print("Signer service: %s", g_signer.qrexec_service);
} else if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
tui_print("Signer: n_signer URL");
tui_print("Signer endpoint: %s", g_signer.endpoint_url);
} else {
tui_print("nsecHex: %s", g_state.nsec_hex);
tui_print("nsec: %s", g_state.nsec_bech32);

View File

@@ -9,7 +9,9 @@
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <unistd.h>
#include <netdb.h>
static int write_all(int fd, const void *buf, size_t len) {
const unsigned char *p = (const unsigned char *)buf;
@@ -223,6 +225,152 @@ int nsigner_connect(const char *name) {
return fd;
}
static int nsigner_parse_url(const char *url, char *host_out, size_t host_out_sz, int *port_out) {
const char *p;
const char *end;
const char *authority_end;
const char *port_sep = NULL;
int port = 8080;
size_t host_len;
if (!url || !host_out || host_out_sz == 0 || !port_out) {
return NT_NSIGNER_E_IO;
}
p = url;
if (strncmp(p, "http://", 7) == 0) {
p += 7;
} else if (strncmp(p, "https://", 8) == 0) {
p += 8;
}
end = p;
while (*end && *end != '/' && *end != '?' && *end != '#') {
end++;
}
authority_end = end;
if (p == authority_end) {
return NT_NSIGNER_E_PARSE;
}
if (*p == '[') {
const char *rb = p + 1;
while (rb < authority_end && *rb != ']') {
rb++;
}
if (rb >= authority_end || *rb != ']') {
return NT_NSIGNER_E_PARSE;
}
host_len = (size_t)(rb - (p + 1));
if (host_len == 0 || host_len >= host_out_sz) {
return NT_NSIGNER_E_PARSE;
}
memcpy(host_out, p + 1, host_len);
host_out[host_len] = '\0';
if ((rb + 1) < authority_end) {
if (*(rb + 1) != ':') {
return NT_NSIGNER_E_PARSE;
}
port_sep = rb + 1;
}
} else {
const char *c = authority_end;
while (c > p) {
c--;
if (*c == ':') {
port_sep = c;
break;
}
}
if (port_sep) {
host_len = (size_t)(port_sep - p);
} else {
host_len = (size_t)(authority_end - p);
}
if (host_len == 0 || host_len >= host_out_sz) {
return NT_NSIGNER_E_PARSE;
}
memcpy(host_out, p, host_len);
host_out[host_len] = '\0';
}
if (port_sep && (port_sep + 1) < authority_end) {
char port_buf[16];
size_t port_len = (size_t)(authority_end - (port_sep + 1));
char *endptr = NULL;
long parsed;
if (port_len == 0 || port_len >= sizeof(port_buf)) {
return NT_NSIGNER_E_PARSE;
}
memcpy(port_buf, port_sep + 1, port_len);
port_buf[port_len] = '\0';
parsed = strtol(port_buf, &endptr, 10);
if (!endptr || *endptr != '\0' || parsed <= 0 || parsed > 65535) {
return NT_NSIGNER_E_PARSE;
}
port = (int)parsed;
} else if (port_sep) {
return NT_NSIGNER_E_PARSE;
}
*port_out = port;
return NT_NSIGNER_OK;
}
static int nsigner_connect_url(const char *url) {
struct addrinfo hints;
struct addrinfo *res = NULL;
struct addrinfo *ai;
char host[256];
int port;
char port_str[16];
int fd = -1;
if (nsigner_parse_url(url, host, sizeof(host), &port) != NT_NSIGNER_OK) {
return -1;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
snprintf(port_str, sizeof(port_str), "%d", port);
if (getaddrinfo(host, port_str, &hints, &res) != 0) {
return -1;
}
for (ai = res; ai; ai = ai->ai_next) {
struct timeval tv;
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (fd < 0) {
continue;
}
tv.tv_sec = 5;
tv.tv_usec = 0;
(void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) {
break;
}
close(fd);
fd = -1;
}
freeaddrinfo(res);
return fd;
}
int nsigner_rpc(int fd, const cJSON *request, cJSON **response_out, int read_timeout_ms) {
char *req_json = NULL;
uint32_t req_len;
@@ -377,6 +525,274 @@ 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 char *role,
const char *method,
const char **args,
int arg_count,
char **result_out) {
int fd = -1;
cJSON *request = NULL;
cJSON *params = NULL;
cJSON *response = NULL;
cJSON *result_item;
int rc;
if (!url || !method || !result_out || arg_count < 0) {
return NT_NSIGNER_E_IO;
}
(void)role;
*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;
}
cJSON_AddStringToObject(request, "id", "1");
cJSON_AddStringToObject(request, "method", method);
for (int i = 0; i < arg_count; i++) {
if (!args || !args[i]) {
cJSON_Delete(request);
cJSON_Delete(params);
close(fd);
return NT_NSIGNER_E_IO;
}
cJSON_AddItemToArray(params, cJSON_CreateString(args[i]));
}
cJSON_AddItemToObject(request, "params", params);
rc = nsigner_rpc(fd, request, &response, 60000);
cJSON_Delete(request);
close(fd);
if (rc != NT_NSIGNER_OK) {
return rc;
}
result_item = cJSON_GetObjectItemCaseSensitive(response, "result");
if (!cJSON_IsString(result_item) || !result_item->valuestring) {
cJSON_Delete(response);
return NT_NSIGNER_E_PARSE;
}
*result_out = strdup(result_item->valuestring);
cJSON_Delete(response);
return *result_out ? NT_NSIGNER_OK : NT_NSIGNER_E_IO;
}
static int nsigner_qrexec_rpc(const char *target_qube,
const char *service_name,
const cJSON *request,
cJSON **response_out) {
int in_pipe[2] = {-1, -1};
int out_pipe[2] = {-1, -1};
pid_t pid;
char *req_json = NULL;
uint32_t req_len;
uint32_t req_be;
uint32_t resp_be;
uint32_t resp_len;
char *resp_buf = NULL;
cJSON *response = NULL;
int err_code;
int status = 0;
if (!target_qube || !*target_qube || !service_name || !*service_name || !request || !response_out) {
return NT_NSIGNER_E_IO;
}
*response_out = NULL;
if (pipe(in_pipe) != 0 || pipe(out_pipe) != 0) {
if (in_pipe[0] >= 0) close(in_pipe[0]);
if (in_pipe[1] >= 0) close(in_pipe[1]);
if (out_pipe[0] >= 0) close(out_pipe[0]);
if (out_pipe[1] >= 0) close(out_pipe[1]);
return NT_NSIGNER_E_IO;
}
pid = fork();
if (pid < 0) {
close(in_pipe[0]);
close(in_pipe[1]);
close(out_pipe[0]);
close(out_pipe[1]);
return NT_NSIGNER_E_IO;
}
if (pid == 0) {
dup2(in_pipe[0], STDIN_FILENO);
dup2(out_pipe[1], STDOUT_FILENO);
close(in_pipe[0]);
close(in_pipe[1]);
close(out_pipe[0]);
close(out_pipe[1]);
execlp("qrexec-client-vm", "qrexec-client-vm", target_qube, service_name, (char *)NULL);
_exit(127);
}
close(in_pipe[0]);
close(out_pipe[1]);
req_json = cJSON_PrintUnformatted((cJSON *)request);
if (!req_json) {
close(in_pipe[1]);
close(out_pipe[0]);
waitpid(pid, NULL, 0);
return NT_NSIGNER_E_IO;
}
req_len = (uint32_t)strlen(req_json);
req_be = htonl(req_len);
if (write_all(in_pipe[1], &req_be, sizeof(req_be)) != 0 ||
write_all(in_pipe[1], req_json, req_len) != 0) {
free(req_json);
close(in_pipe[1]);
close(out_pipe[0]);
waitpid(pid, NULL, 0);
return NT_NSIGNER_E_IO;
}
free(req_json);
close(in_pipe[1]);
if (read_all(out_pipe[0], &resp_be, sizeof(resp_be)) != 0) {
close(out_pipe[0]);
waitpid(pid, NULL, 0);
return NT_NSIGNER_E_IO;
}
resp_len = ntohl(resp_be);
if (resp_len == 0 || resp_len > (16U * 1024U * 1024U)) {
close(out_pipe[0]);
waitpid(pid, NULL, 0);
return NT_NSIGNER_E_PARSE;
}
resp_buf = (char *)malloc((size_t)resp_len + 1U);
if (!resp_buf) {
close(out_pipe[0]);
waitpid(pid, NULL, 0);
return NT_NSIGNER_E_IO;
}
if (read_all(out_pipe[0], resp_buf, resp_len) != 0) {
free(resp_buf);
close(out_pipe[0]);
waitpid(pid, NULL, 0);
return NT_NSIGNER_E_IO;
}
resp_buf[resp_len] = '\0';
close(out_pipe[0]);
if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
free(resp_buf);
return NT_NSIGNER_E_IO;
}
response = cJSON_Parse(resp_buf);
free(resp_buf);
if (!response || !cJSON_IsObject(response)) {
cJSON_Delete(response);
return NT_NSIGNER_E_PARSE;
}
err_code = nsigner_error_code_from_response(response);
if (err_code != NT_NSIGNER_OK) {
cJSON_Delete(response);
return err_code;
}
if (!cJSON_GetObjectItemCaseSensitive(response, "result")) {
cJSON_Delete(response);
return NT_NSIGNER_E_ERROR;
}
*response_out = response;
return NT_NSIGNER_OK;
}
static int nsigner_qrexec_call_string_method(const char *target_qube,
const char *service_name,
const char *role,
const char *method,
const char **args,
int arg_count,
char **result_out) {
cJSON *request = NULL;
cJSON *params = NULL;
cJSON *role_obj = NULL;
cJSON *response = NULL;
cJSON *result_item;
int rc;
if (!target_qube || !service_name || !method || !result_out || arg_count < 0) {
return NT_NSIGNER_E_IO;
}
*result_out = NULL;
request = cJSON_CreateObject();
params = cJSON_CreateArray();
if (!request || !params) {
cJSON_Delete(request);
cJSON_Delete(params);
return NT_NSIGNER_E_IO;
}
cJSON_AddStringToObject(request, "id", "1");
cJSON_AddStringToObject(request, "method", method);
for (int i = 0; i < arg_count; i++) {
if (!args || !args[i]) {
cJSON_Delete(request);
cJSON_Delete(params);
return NT_NSIGNER_E_IO;
}
cJSON_AddItemToArray(params, cJSON_CreateString(args[i]));
}
role_obj = cJSON_CreateObject();
if (!role_obj) {
cJSON_Delete(request);
cJSON_Delete(params);
return NT_NSIGNER_E_IO;
}
cJSON_AddStringToObject(role_obj, "role", (role && *role) ? role : "main");
cJSON_AddItemToArray(params, role_obj);
cJSON_AddItemToObject(request, "params", params);
rc = nsigner_qrexec_rpc(target_qube, service_name, request, &response);
cJSON_Delete(request);
if (rc != NT_NSIGNER_OK) {
return rc;
}
result_item = cJSON_GetObjectItemCaseSensitive(response, "result");
if (!cJSON_IsString(result_item) || !result_item->valuestring) {
cJSON_Delete(response);
return NT_NSIGNER_E_PARSE;
}
*result_out = strdup(result_item->valuestring);
cJSON_Delete(response);
return *result_out ? NT_NSIGNER_OK : NT_NSIGNER_E_IO;
}
int nsigner_get_public_key(const char *socket_name, const char *role, char *out_hex_65) {
char *result = NULL;
int rc;
@@ -465,3 +881,209 @@ int nsigner_nip44_decrypt(const char *socket_name, const char *role,
args[1] = ciphertext;
return nsigner_call_string_method(socket_name, role, "nip44_decrypt", args, 2, plain_out);
}
int nsigner_get_public_key_url(const char *url, const char *role, char *out_hex_65) {
char *result = NULL;
int rc;
size_t len;
if (!out_hex_65) {
return NT_NSIGNER_E_IO;
}
rc = nsigner_call_string_method_url(url, role, "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_sign_event_url(const char *url, const char *role,
const char *unsigned_event_json, char **signed_event_json_out) {
const char *args[1];
if (!unsigned_event_json || !signed_event_json_out) {
return NT_NSIGNER_E_IO;
}
args[0] = unsigned_event_json;
return nsigner_call_string_method_url(url, role, "sign_event", args, 1, signed_event_json_out);
}
int nsigner_nip04_encrypt_url(const char *url, const char *role,
const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
const char *args[2];
if (!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(url, role, "nip04_encrypt", args, 2, cipher_out);
}
int nsigner_nip04_decrypt_url(const char *url, const char *role,
const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
const char *args[2];
if (!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(url, role, "nip04_decrypt", args, 2, plain_out);
}
int nsigner_nip44_encrypt_url(const char *url, const char *role,
const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
const char *args[2];
if (!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(url, role, "nip44_encrypt", args, 2, cipher_out);
}
int nsigner_nip44_decrypt_url(const char *url, const char *role,
const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
const char *args[2];
if (!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(url, role, "nip44_decrypt", args, 2, plain_out);
}
int nsigner_qrexec_get_public_key(const char *target_qube,
const char *service_name,
const char *role,
char *out_hex_65) {
char *result = NULL;
int rc;
size_t len;
if (!out_hex_65) {
return NT_NSIGNER_E_IO;
}
rc = nsigner_qrexec_call_string_method(target_qube, service_name, role,
"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_qrexec_sign_event(const char *target_qube,
const char *service_name,
const char *role,
const char *unsigned_event_json,
char **signed_event_json_out) {
const char *args[1];
if (!unsigned_event_json || !signed_event_json_out) {
return NT_NSIGNER_E_IO;
}
args[0] = unsigned_event_json;
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
"sign_event", args, 1, signed_event_json_out);
}
int nsigner_qrexec_nip04_encrypt(const char *target_qube,
const char *service_name,
const char *role,
const char *peer_pub_hex,
const char *plaintext,
char **cipher_out) {
const char *args[2];
if (!peer_pub_hex || !plaintext || !cipher_out) {
return NT_NSIGNER_E_IO;
}
args[0] = peer_pub_hex;
args[1] = plaintext;
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
"nip04_encrypt", args, 2, cipher_out);
}
int nsigner_qrexec_nip04_decrypt(const char *target_qube,
const char *service_name,
const char *role,
const char *peer_pub_hex,
const char *ciphertext,
char **plain_out) {
const char *args[2];
if (!peer_pub_hex || !ciphertext || !plain_out) {
return NT_NSIGNER_E_IO;
}
args[0] = peer_pub_hex;
args[1] = ciphertext;
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
"nip04_decrypt", args, 2, plain_out);
}
int nsigner_qrexec_nip44_encrypt(const char *target_qube,
const char *service_name,
const char *role,
const char *peer_pub_hex,
const char *plaintext,
char **cipher_out) {
const char *args[2];
if (!peer_pub_hex || !plaintext || !cipher_out) {
return NT_NSIGNER_E_IO;
}
args[0] = peer_pub_hex;
args[1] = plaintext;
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
"nip44_encrypt", args, 2, cipher_out);
}
int nsigner_qrexec_nip44_decrypt(const char *target_qube,
const char *service_name,
const char *role,
const char *peer_pub_hex,
const char *ciphertext,
char **plain_out) {
const char *args[2];
if (!peer_pub_hex || !ciphertext || !plain_out) {
return NT_NSIGNER_E_IO;
}
args[0] = peer_pub_hex;
args[1] = ciphertext;
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
"nip44_decrypt", args, 2, plain_out);
}

View File

@@ -15,6 +15,7 @@
nt_signer_t g_signer = {
.kind = NT_SIGNER_LOCAL,
.socket_name = {0},
.endpoint_url = {0},
.role = "main",
};
@@ -31,6 +32,9 @@ static int signer_local_private_key(unsigned char out_priv[32]) {
void signer_init(void) {
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';
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
}
@@ -45,6 +49,36 @@ int signer_init_nsigner(const char *socket_name) {
}
g_signer.kind = NT_SIGNER_NSIGNER;
snprintf(g_signer.socket_name, sizeof(g_signer.socket_name), "%s", socket_name);
g_signer.qrexec_target[0] = '\0';
g_signer.qrexec_service[0] = '\0';
g_signer.endpoint_url[0] = '\0';
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
return 0;
}
int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name) {
if (!target_qube || target_qube[0] == '\0' || !service_name || service_name[0] == '\0') {
return -1;
}
g_signer.kind = NT_SIGNER_NSIGNER_QREXEC;
g_signer.socket_name[0] = '\0';
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';
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
return 0;
}
int signer_init_nsigner_url(const char *endpoint_url) {
if (!endpoint_url || endpoint_url[0] == '\0') {
return -1;
}
g_signer.kind = NT_SIGNER_NSIGNER_URL;
g_signer.socket_name[0] = '\0';
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);
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
return 0;
}
@@ -123,6 +157,107 @@ int signer_create_and_sign(int kind,
return rc;
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
cJSON *unsigned_event = NULL;
cJSON *tags_copy = NULL;
char *unsigned_json = NULL;
int rc = -1;
unsigned_event = cJSON_CreateObject();
if (!unsigned_event) {
return -1;
}
if (!cJSON_AddNumberToObject(unsigned_event, "kind", kind) ||
!cJSON_AddStringToObject(unsigned_event, "content", content ? content : "")) {
cJSON_Delete(unsigned_event);
return -1;
}
tags_copy = tags ? cJSON_Duplicate(tags, 1) : cJSON_CreateArray();
if (!tags_copy) {
cJSON_Delete(unsigned_event);
return -1;
}
if (!cJSON_AddItemToObject(unsigned_event, "tags", tags_copy)) {
cJSON_Delete(tags_copy);
cJSON_Delete(unsigned_event);
return -1;
}
if (!cJSON_AddNumberToObject(unsigned_event, "created_at", (double)timestamp) ||
!cJSON_AddStringToObject(unsigned_event, "pubkey", g_state.npub_hex)) {
cJSON_Delete(unsigned_event);
return -1;
}
unsigned_json = cJSON_PrintUnformatted(unsigned_event);
if (!unsigned_json) {
cJSON_Delete(unsigned_event);
return -1;
}
rc = nsigner_qrexec_sign_event(g_signer.qrexec_target,
g_signer.qrexec_service,
g_signer.role,
unsigned_json,
signed_event_json_out);
free(unsigned_json);
cJSON_Delete(unsigned_event);
return rc;
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
cJSON *unsigned_event = NULL;
cJSON *tags_copy = NULL;
char *unsigned_json = NULL;
int rc = -1;
unsigned_event = cJSON_CreateObject();
if (!unsigned_event) {
return -1;
}
if (!cJSON_AddNumberToObject(unsigned_event, "kind", kind) ||
!cJSON_AddStringToObject(unsigned_event, "content", content ? content : "")) {
cJSON_Delete(unsigned_event);
return -1;
}
tags_copy = tags ? cJSON_Duplicate(tags, 1) : cJSON_CreateArray();
if (!tags_copy) {
cJSON_Delete(unsigned_event);
return -1;
}
if (!cJSON_AddItemToObject(unsigned_event, "tags", tags_copy)) {
cJSON_Delete(tags_copy);
cJSON_Delete(unsigned_event);
return -1;
}
if (!cJSON_AddNumberToObject(unsigned_event, "created_at", (double)timestamp) ||
!cJSON_AddStringToObject(unsigned_event, "pubkey", g_state.npub_hex)) {
cJSON_Delete(unsigned_event);
return -1;
}
unsigned_json = cJSON_PrintUnformatted(unsigned_event);
if (!unsigned_json) {
cJSON_Delete(unsigned_event);
return -1;
}
rc = nsigner_sign_event_url(g_signer.endpoint_url,
g_signer.role,
unsigned_json,
signed_event_json_out);
free(unsigned_json);
cJSON_Delete(unsigned_event);
return rc;
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -177,6 +312,21 @@ int signer_sign_event_json(const char *unsigned_event_json, char **signed_event_
signed_event_json_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_sign_event(g_signer.qrexec_target,
g_signer.qrexec_service,
g_signer.role,
unsigned_event_json,
signed_event_json_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_sign_event_url(g_signer.endpoint_url,
g_signer.role,
unsigned_event_json,
signed_event_json_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -241,6 +391,23 @@ int signer_nip04_encrypt(const char *peer_pub_hex, const char *plaintext, char *
cipher_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_nip04_encrypt(g_signer.qrexec_target,
g_signer.qrexec_service,
g_signer.role,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_nip04_encrypt_url(g_signer.endpoint_url,
g_signer.role,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -283,6 +450,23 @@ int signer_nip04_decrypt(const char *peer_pub_hex, const char *ciphertext, char
plain_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_nip04_decrypt(g_signer.qrexec_target,
g_signer.qrexec_service,
g_signer.role,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_nip04_decrypt_url(g_signer.endpoint_url,
g_signer.role,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -325,6 +509,23 @@ int signer_nip44_encrypt(const char *peer_pub_hex, const char *plaintext, char *
cipher_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_nip44_encrypt(g_signer.qrexec_target,
g_signer.qrexec_service,
g_signer.role,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_nip44_encrypt_url(g_signer.endpoint_url,
g_signer.role,
peer_pub_hex,
plaintext,
cipher_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}
@@ -367,6 +568,23 @@ int signer_nip44_decrypt(const char *peer_pub_hex, const char *ciphertext, char
plain_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
return nsigner_qrexec_nip44_decrypt(g_signer.qrexec_target,
g_signer.qrexec_service,
g_signer.role,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
return nsigner_nip44_decrypt_url(g_signer.endpoint_url,
g_signer.role,
peer_pub_hex,
ciphertext,
plain_out);
}
if (g_signer.kind != NT_SIGNER_LOCAL) {
return -1;
}