From cc5638b6e7cee04ef565c5a2e333ba5e8a83b4ab Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Tue, 5 May 2026 11:44:43 -0400 Subject: [PATCH] v0.0.21 - Add C reference client library, examples, and integration test migration --- Makefile | 24 +- client/README.md | 47 ++++ client/nsigner_client.c | 369 +++++++++++++++++++++++++++++ client/nsigner_client.h | 56 +++++ documents/CLIENT_IMPLEMENTATION.md | 6 + examples/get_public_key_client.c | 32 +++ examples/sign_event_client.c | 33 +++ src/auth_envelope.c | 91 ++++++- src/auth_envelope.h | 11 + src/main.c | 4 +- tests/test_integration.c | 80 ++++--- 11 files changed, 706 insertions(+), 47 deletions(-) create mode 100644 client/README.md create mode 100644 client/nsigner_client.c create mode 100644 client/nsigner_client.h create mode 100644 examples/get_public_key_client.c create mode 100644 examples/sign_event_client.c diff --git a/Makefile b/Makefile index a3695f9..dd58ad9 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ LDFLAGS := -Wl,--gc-sections resources/nostr_core_lib/libnostr_core_x64.a -lz -l SRC_DIR := src BUILD_DIR := build TEST_DIR := tests +CLIENT_DIR := client +EXAMPLES_DIR := examples TARGET_DEV := $(BUILD_DIR)/nsigner @@ -35,8 +37,10 @@ TEST_POLICY_TARGET := $(BUILD_DIR)/test_policy TEST_INTEGRATION_TARGET := $(BUILD_DIR)/test_integration TEST_SOCKET_NAME_TARGET := $(BUILD_DIR)/test_socket_name TEST_AUTH_ENVELOPE_TARGET := $(BUILD_DIR)/test_auth_envelope +EXAMPLE_GET_PUBLIC_KEY_TARGET := $(BUILD_DIR)/example_get_public_key_client +EXAMPLE_SIGN_EVENT_TARGET := $(BUILD_DIR)/example_sign_event_client -.PHONY: all lib dev static static-debug static-arm64 test test-integration test-mnemonic test-role test-selector test-enforcement test-dispatcher test-policy test-socket-name test-auth-envelope clean +.PHONY: all lib dev static static-debug static-arm64 test test-integration test-mnemonic test-role test-selector test-enforcement test-dispatcher test-policy test-socket-name test-auth-envelope examples test-client clean all: dev @@ -61,7 +65,7 @@ static-arm64: chmod +x ./build_static.sh ./build_static.sh --arch arm64 -test: lib test-mnemonic test-role test-selector test-enforcement test-dispatcher test-policy test-socket-name test-auth-envelope +test: lib test-mnemonic test-role test-selector test-enforcement test-dispatcher test-policy test-socket-name test-auth-envelope test-client test-integration: $(TEST_INTEGRATION_TARGET) $(TARGET_DEV) ./$(TEST_INTEGRATION_TARGET) @@ -90,6 +94,10 @@ test-socket-name: $(TEST_SOCKET_NAME_TARGET) test-auth-envelope: $(TEST_AUTH_ENVELOPE_TARGET) ./$(TEST_AUTH_ENVELOPE_TARGET) +test-client: examples + +examples: $(EXAMPLE_GET_PUBLIC_KEY_TARGET) $(EXAMPLE_SIGN_EVENT_TARGET) + $(TEST_MNEMONIC_TARGET): $(TEST_DIR)/test_mnemonic.c $(SRC_DIR)/mnemonic.c $(SRC_DIR)/secure_mem.c @mkdir -p $(BUILD_DIR) $(CC) $(CFLAGS) $(TEST_DIR)/test_mnemonic.c $(SRC_DIR)/mnemonic.c $(SRC_DIR)/secure_mem.c -o $(TEST_MNEMONIC_TARGET) $(LDFLAGS) @@ -114,9 +122,9 @@ $(TEST_POLICY_TARGET): $(TEST_DIR)/test_policy.c $(SRC_DIR)/policy.c @mkdir -p $(BUILD_DIR) $(CC) $(CFLAGS) $(TEST_DIR)/test_policy.c $(SRC_DIR)/policy.c -o $(TEST_POLICY_TARGET) $(LDFLAGS) -$(TEST_INTEGRATION_TARGET): $(TEST_DIR)/test_integration.c +$(TEST_INTEGRATION_TARGET): $(TEST_DIR)/test_integration.c $(CLIENT_DIR)/nsigner_client.c $(SRC_DIR)/auth_envelope.c @mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) $(TEST_DIR)/test_integration.c -o $(TEST_INTEGRATION_TARGET) $(LDFLAGS) + $(CC) $(CFLAGS) -I$(CLIENT_DIR) $(TEST_DIR)/test_integration.c $(CLIENT_DIR)/nsigner_client.c $(SRC_DIR)/auth_envelope.c -o $(TEST_INTEGRATION_TARGET) $(LDFLAGS) $(TEST_SOCKET_NAME_TARGET): $(TEST_DIR)/test_socket_name.c $(SRC_DIR)/socket_name.c @mkdir -p $(BUILD_DIR) @@ -126,5 +134,13 @@ $(TEST_AUTH_ENVELOPE_TARGET): $(TEST_DIR)/test_auth_envelope.c $(SRC_DIR)/auth_e @mkdir -p $(BUILD_DIR) $(CC) $(CFLAGS) $(TEST_DIR)/test_auth_envelope.c $(SRC_DIR)/auth_envelope.c -o $(TEST_AUTH_ENVELOPE_TARGET) $(LDFLAGS) +$(EXAMPLE_GET_PUBLIC_KEY_TARGET): $(EXAMPLES_DIR)/get_public_key_client.c $(CLIENT_DIR)/nsigner_client.c $(SRC_DIR)/auth_envelope.c + @mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) -I$(CLIENT_DIR) $(EXAMPLES_DIR)/get_public_key_client.c $(CLIENT_DIR)/nsigner_client.c $(SRC_DIR)/auth_envelope.c -o $(EXAMPLE_GET_PUBLIC_KEY_TARGET) $(LDFLAGS) + +$(EXAMPLE_SIGN_EVENT_TARGET): $(EXAMPLES_DIR)/sign_event_client.c $(CLIENT_DIR)/nsigner_client.c $(SRC_DIR)/auth_envelope.c + @mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) -I$(CLIENT_DIR) $(EXAMPLES_DIR)/sign_event_client.c $(CLIENT_DIR)/nsigner_client.c $(SRC_DIR)/auth_envelope.c -o $(EXAMPLE_SIGN_EVENT_TARGET) $(LDFLAGS) + clean: rm -rf $(BUILD_DIR) diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..8d4c8ec --- /dev/null +++ b/client/README.md @@ -0,0 +1,47 @@ +# n_signer C Reference Client + +This directory provides a minimal copy/paste-friendly C client for `n_signer`. + +## Files + +- `nsigner_client.h` / `nsigner_client.c`: + - Unix abstract socket connect helper + - length-prefixed frame send/receive + - generic request API + - helpers for `get_public_key` and `sign_event` + - optional TCP auth envelope attachment via `auth_envelope_build_for_request()` + +## API at a glance + +```c +nsigner_client_t client; +nsigner_client_init(&client); + +nsigner_client_connect_unix(&client, "nsigner", 5000); + +char *resp = NULL; +nsigner_client_get_public_key(&client, "1", "", &resp); +free(resp); + +nsigner_client_close(&client); +``` + +## Auth envelope usage + +If transport requires auth envelopes (for TCP mode), set a private key once: + +```c +unsigned char privkey[32] = { /* caller key */ }; +nsigner_client_set_auth(&client, privkey, "example-client"); +``` + +Subsequent requests automatically include an `auth` object. + +## Ownership rules + +- Any `out_response_json` returned by the API must be freed by caller using `free()`. +- `nsigner_client_close()` only closes the socket; it does not free the client struct itself. + +## License + +Source files in this directory use SPDX `0BSD` headers for permissive reuse in external projects. diff --git a/client/nsigner_client.c b/client/nsigner_client.c new file mode 100644 index 0000000..73bb75f --- /dev/null +++ b/client/nsigner_client.c @@ -0,0 +1,369 @@ +/* + * SPDX-License-Identifier: 0BSD + */ + +#define _GNU_SOURCE + +#include "nsigner_client.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "auth_envelope.h" + +#define NSIGNER_CLIENT_MAX_FRAME (65536U) + +static void client_set_error(nsigner_client_t *client, const char *fmt, ...) { + va_list ap; + + if (client == NULL) { + return; + } + + va_start(ap, fmt); + vsnprintf(client->last_error, sizeof(client->last_error), fmt, ap); + va_end(ap); +} + +static int write_full(int fd, const void *buf, size_t len) { + const unsigned char *p = (const unsigned char *)buf; + size_t off = 0; + + while (off < len) { + ssize_t n = write(fd, p + off, len - off); + if (n < 0) { + if (errno == EINTR) { + continue; + } + return -1; + } + off += (size_t)n; + } + + return 0; +} + +static int read_full(int fd, void *buf, size_t len) { + unsigned char *p = (unsigned char *)buf; + size_t off = 0; + + while (off < len) { + ssize_t n = read(fd, p + off, len - off); + if (n == 0) { + return -1; + } + if (n < 0) { + if (errno == EINTR) { + continue; + } + return -1; + } + off += (size_t)n; + } + + return 0; +} + +static int send_framed(int fd, const char *payload) { + uint32_t len; + uint32_t be_len; + + if (payload == NULL) { + return -1; + } + + len = (uint32_t)strlen(payload); + be_len = htonl(len); + + if (write_full(fd, &be_len, sizeof(be_len)) != 0) { + return -1; + } + if (write_full(fd, payload, len) != 0) { + return -1; + } + + return 0; +} + +static int recv_framed(int fd, char **out_payload) { + uint32_t be_len; + uint32_t len; + char *payload; + + if (out_payload == NULL) { + return -1; + } + + *out_payload = NULL; + + if (read_full(fd, &be_len, sizeof(be_len)) != 0) { + return -1; + } + + len = ntohl(be_len); + if (len == 0 || len > NSIGNER_CLIENT_MAX_FRAME) { + return -1; + } + + payload = (char *)malloc((size_t)len + 1U); + if (payload == NULL) { + return -1; + } + + if (read_full(fd, payload, len) != 0) { + free(payload); + return -1; + } + + payload[len] = '\0'; + *out_payload = payload; + return 0; +} + +static int sleep_ms(int ms) { + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (long)(ms % 1000) * 1000000L; + return nanosleep(&ts, NULL); +} + +void nsigner_client_init(nsigner_client_t *client) { + if (client == NULL) { + return; + } + + memset(client, 0, sizeof(*client)); + client->fd = -1; + client->timeout_ms = 5000; +} + +void nsigner_client_close(nsigner_client_t *client) { + if (client == NULL) { + return; + } + + if (client->fd >= 0) { + close(client->fd); + client->fd = -1; + } +} + +int nsigner_client_connect_unix(nsigner_client_t *client, + const char *socket_name, + int timeout_ms) { + struct sockaddr_un addr; + socklen_t addr_len; + int elapsed = 0; + + if (client == NULL || socket_name == NULL || socket_name[0] == '\0') { + return -1; + } + + timeout_ms = (timeout_ms > 0) ? timeout_ms : 5000; + nsigner_client_close(client); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_path[0] = '\0'; + strncpy(&addr.sun_path[1], socket_name, sizeof(addr.sun_path) - 2); + addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; + addr_len = (socklen_t)(sizeof(sa_family_t) + 1 + strlen(socket_name)); + + while (elapsed < timeout_ms) { + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + client_set_error(client, "socket failed: %s", strerror(errno)); + return -1; + } + + if (connect(fd, (struct sockaddr *)&addr, addr_len) == 0) { + client->fd = fd; + client->timeout_ms = timeout_ms; + client_set_error(client, "ok"); + return 0; + } + + close(fd); + sleep_ms(100); + elapsed += 100; + } + + client_set_error(client, "connect timeout: %s", strerror(errno)); + return -1; +} + +int nsigner_client_set_auth(nsigner_client_t *client, + const unsigned char privkey[32], + const char *label) { + if (client == NULL || privkey == NULL) { + return -1; + } + + memcpy(client->auth_privkey, privkey, 32); + client->auth_enabled = 1; + + if (label == NULL) { + label = ""; + } + strncpy(client->auth_label, label, sizeof(client->auth_label) - 1); + client->auth_label[sizeof(client->auth_label) - 1] = '\0'; + + return 0; +} + +const char *nsigner_client_last_error(const nsigner_client_t *client) { + if (client == NULL) { + return "invalid_client"; + } + return client->last_error; +} + +int nsigner_client_request_raw(nsigner_client_t *client, + const char *request_json, + char **out_response_json) { + char *response_json = NULL; + + if (client == NULL || request_json == NULL || out_response_json == NULL) { + return -1; + } + if (client->fd < 0) { + client_set_error(client, "not connected"); + return -1; + } + + *out_response_json = NULL; + + if (send_framed(client->fd, request_json) != 0) { + client_set_error(client, "send failed"); + return -1; + } + + if (recv_framed(client->fd, &response_json) != 0) { + client_set_error(client, "receive failed"); + return -1; + } + + *out_response_json = response_json; + client_set_error(client, "ok"); + return 0; +} + +int nsigner_client_request(nsigner_client_t *client, + const char *id, + const char *method, + const cJSON *params, + char **out_response_json) { + cJSON *root = NULL; + cJSON *params_copy = NULL; + cJSON *auth = NULL; + char *request_json = NULL; + int rc = -1; + + if (client == NULL || id == NULL || method == NULL || out_response_json == NULL) { + return -1; + } + + root = cJSON_CreateObject(); + if (root == NULL) { + client_set_error(client, "out of memory"); + goto cleanup; + } + + cJSON_AddStringToObject(root, "id", id); + cJSON_AddStringToObject(root, "method", method); + + if (params != NULL) { + params_copy = cJSON_Duplicate((cJSON *)params, 1); + } else { + params_copy = cJSON_CreateArray(); + } + + if (params_copy == NULL) { + client_set_error(client, "failed to create params"); + goto cleanup; + } + cJSON_AddItemToObject(root, "params", params_copy); + + if (client->auth_enabled) { + if (auth_envelope_build_for_request(id, + method, + params_copy, + client->auth_privkey, + client->auth_label, + time(NULL), + &auth) != 0) { + client_set_error(client, "failed to build auth envelope"); + goto cleanup; + } + cJSON_AddItemToObject(root, "auth", auth); + auth = NULL; + } + + request_json = cJSON_PrintUnformatted(root); + if (request_json == NULL) { + client_set_error(client, "failed to serialize request"); + goto cleanup; + } + + rc = nsigner_client_request_raw(client, request_json, out_response_json); + +cleanup: + cJSON_Delete(root); + cJSON_Delete(auth); + free(request_json); + return rc; +} + +int nsigner_client_get_public_key(nsigner_client_t *client, + const char *id, + const char *selector, + char **out_response_json) { + cJSON *params = cJSON_CreateArray(); + int rc; + + if (params == NULL) { + return -1; + } + + cJSON_AddItemToArray(params, cJSON_CreateString((selector != NULL) ? selector : "")); + rc = nsigner_client_request(client, id, "get_public_key", params, out_response_json); + cJSON_Delete(params); + return rc; +} + +int nsigner_client_sign_event(nsigner_client_t *client, + const char *id, + const char *event_json, + const char *role, + char **out_response_json) { + cJSON *params = cJSON_CreateArray(); + cJSON *opts = cJSON_CreateObject(); + int rc; + + if (params == NULL || opts == NULL || event_json == NULL) { + cJSON_Delete(params); + cJSON_Delete(opts); + return -1; + } + + cJSON_AddItemToArray(params, cJSON_CreateString(event_json)); + if (role != NULL && role[0] != '\0') { + cJSON_AddStringToObject(opts, "role", role); + } + cJSON_AddItemToArray(params, opts); + + rc = nsigner_client_request(client, id, "sign_event", params, out_response_json); + cJSON_Delete(params); + return rc; +} diff --git a/client/nsigner_client.h b/client/nsigner_client.h new file mode 100644 index 0000000..0fcbf84 --- /dev/null +++ b/client/nsigner_client.h @@ -0,0 +1,56 @@ +/* + * SPDX-License-Identifier: 0BSD + */ + +#ifndef NSIGNER_CLIENT_H +#define NSIGNER_CLIENT_H + +#include +#include + +#include + +typedef struct { + int fd; + int timeout_ms; + unsigned char auth_privkey[32]; + int auth_enabled; + char auth_label[64]; + char last_error[128]; +} nsigner_client_t; + +void nsigner_client_init(nsigner_client_t *client); +void nsigner_client_close(nsigner_client_t *client); + +int nsigner_client_connect_unix(nsigner_client_t *client, + const char *socket_name, + int timeout_ms); + +int nsigner_client_set_auth(nsigner_client_t *client, + const unsigned char privkey[32], + const char *label); + +const char *nsigner_client_last_error(const nsigner_client_t *client); + +int nsigner_client_request_raw(nsigner_client_t *client, + const char *request_json, + char **out_response_json); + +int nsigner_client_request(nsigner_client_t *client, + const char *id, + const char *method, + const cJSON *params, + char **out_response_json); + +int nsigner_client_get_public_key(nsigner_client_t *client, + const char *id, + const char *selector, + char **out_response_json); + +int nsigner_client_sign_event(nsigner_client_t *client, + const char *id, + const char *event_json, + const char *role, + char **out_response_json); + +#endif \ No newline at end of file diff --git a/documents/CLIENT_IMPLEMENTATION.md b/documents/CLIENT_IMPLEMENTATION.md index 88f52a0..b3b0f01 100644 --- a/documents/CLIENT_IMPLEMENTATION.md +++ b/documents/CLIENT_IMPLEMENTATION.md @@ -6,6 +6,12 @@ This document is the client-integration spec for `nsigner`. It is written for agent/tool authors implementing robust request flows against the local signer process. +Reference implementation in this repository: + +- Reusable C client library: `client/nsigner_client.h` + `client/nsigner_client.c` +- Minimal usage examples: `examples/get_public_key_client.c` and `examples/sign_event_client.c` +- Auth envelope builder used by the client: `src/auth_envelope.h` (`auth_envelope_build_for_request`) + --- ## 2. Discovery and socket targeting diff --git a/examples/get_public_key_client.c b/examples/get_public_key_client.c new file mode 100644 index 0000000..ac22f23 --- /dev/null +++ b/examples/get_public_key_client.c @@ -0,0 +1,32 @@ +#include +#include + +#include "../client/nsigner_client.h" + +int main(int argc, char **argv) { + const char *socket_name = "nsigner"; + nsigner_client_t client; + char *response = NULL; + + if (argc > 1 && argv[1] != NULL && argv[1][0] != '\0') { + socket_name = argv[1]; + } + + nsigner_client_init(&client); + + if (nsigner_client_connect_unix(&client, socket_name, 5000) != 0) { + fprintf(stderr, "connect failed: %s\n", nsigner_client_last_error(&client)); + return 1; + } + + if (nsigner_client_get_public_key(&client, "example-1", "", &response) != 0) { + fprintf(stderr, "request failed: %s\n", nsigner_client_last_error(&client)); + nsigner_client_close(&client); + return 1; + } + + printf("%s\n", response); + free(response); + nsigner_client_close(&client); + return 0; +} diff --git a/examples/sign_event_client.c b/examples/sign_event_client.c new file mode 100644 index 0000000..e64a154 --- /dev/null +++ b/examples/sign_event_client.c @@ -0,0 +1,33 @@ +#include +#include + +#include "../client/nsigner_client.h" + +int main(int argc, char **argv) { + const char *socket_name = "nsigner"; + const char *event_json = "{\"kind\":1,\"content\":\"hello from client example\",\"tags\":[],\"created_at\":1700000000}"; + nsigner_client_t client; + char *response = NULL; + + if (argc > 1 && argv[1] != NULL && argv[1][0] != '\0') { + socket_name = argv[1]; + } + + nsigner_client_init(&client); + + if (nsigner_client_connect_unix(&client, socket_name, 5000) != 0) { + fprintf(stderr, "connect failed: %s\n", nsigner_client_last_error(&client)); + return 1; + } + + if (nsigner_client_sign_event(&client, "example-2", event_json, "main", &response) != 0) { + fprintf(stderr, "request failed: %s\n", nsigner_client_last_error(&client)); + nsigner_client_close(&client); + return 1; + } + + printf("%s\n", response); + free(response); + nsigner_client_close(&client); + return 0; +} diff --git a/src/auth_envelope.c b/src/auth_envelope.c index 3484432..6012f63 100644 --- a/src/auth_envelope.c +++ b/src/auth_envelope.c @@ -87,36 +87,40 @@ static int json_item_to_compact_string(const cJSON *item, char *out, size_t out_ return 0; } -static int compute_params_hash_hex(cJSON *root, char *out_hex, size_t out_hex_sz) { - cJSON *params = cJSON_GetObjectItemCaseSensitive(root, "params"); +static int compute_hash_hex_for_json_item(const cJSON *item, char *out_hex, size_t out_hex_sz) { unsigned char hash[32]; - char *params_compact = NULL; + char *compact = NULL; if (out_hex == NULL || out_hex_sz < 65) { return -1; } - if (params == NULL) { - params_compact = strdup("null"); + if (item == NULL) { + compact = strdup("null"); } else { - params_compact = cJSON_PrintUnformatted(params); + compact = cJSON_PrintUnformatted((cJSON *)item); } - if (params_compact == NULL) { + if (compact == NULL) { return -1; } - if (nostr_sha256((const unsigned char *)params_compact, strlen(params_compact), hash) != 0) { - free(params_compact); + if (nostr_sha256((const unsigned char *)compact, strlen(compact), hash) != 0) { + free(compact); return -1; } nostr_bytes_to_hex(hash, sizeof(hash), out_hex); out_hex[64] = '\0'; - free(params_compact); + free(compact); return 0; } +static int compute_params_hash_hex(cJSON *root, char *out_hex, size_t out_hex_sz) { + cJSON *params = cJSON_GetObjectItemCaseSensitive(root, "params"); + return compute_hash_hex_for_json_item(params, out_hex, out_hex_sz); +} + static cJSON *find_tag_value(cJSON *tags, const char *name) { int i; @@ -148,6 +152,73 @@ static cJSON *find_tag_value(cJSON *tags, const char *name) { return NULL; } +static cJSON *build_tag_pair(const char *name, const char *value) { + cJSON *pair; + + if (name == NULL || value == NULL) { + return NULL; + } + + pair = cJSON_CreateArray(); + if (pair == NULL) { + return NULL; + } + + cJSON_AddItemToArray(pair, cJSON_CreateString(name)); + cJSON_AddItemToArray(pair, cJSON_CreateString(value)); + return pair; +} + +int auth_envelope_build_for_request(const char *request_id, + const char *method, + const cJSON *params, + const unsigned char privkey[32], + const char *label, + time_t created_at, + cJSON **out_auth) { + cJSON *tags = NULL; + cJSON *auth = NULL; + char body_hash_hex[65]; + + if (out_auth == NULL || request_id == NULL || method == NULL || + privkey == NULL || request_id[0] == '\0' || method[0] == '\0') { + return -1; + } + + *out_auth = NULL; + + if (compute_hash_hex_for_json_item(params, body_hash_hex, sizeof(body_hash_hex)) != 0) { + return -1; + } + + tags = cJSON_CreateArray(); + if (tags == NULL) { + return -1; + } + + cJSON_AddItemToArray(tags, build_tag_pair("nsigner_rpc", request_id)); + cJSON_AddItemToArray(tags, build_tag_pair("nsigner_method", method)); + cJSON_AddItemToArray(tags, build_tag_pair("nsigner_body_hash", body_hash_hex)); + + if (created_at <= 0) { + created_at = time(NULL); + } + + auth = nostr_create_and_sign_event(AUTH_EVENT_KIND, + (label != NULL) ? label : "", + tags, + privkey, + created_at); + + if (auth == NULL) { + cJSON_Delete(tags); + return -1; + } + + *out_auth = auth; + return 0; +} + int auth_envelope_verify_request(const char *request_json, auth_nonce_cache_t *cache, int skew_seconds, diff --git a/src/auth_envelope.h b/src/auth_envelope.h index 06848ab..22250b7 100644 --- a/src/auth_envelope.h +++ b/src/auth_envelope.h @@ -3,6 +3,9 @@ #include #include +#include + +#include #define AUTH_NONCE_CACHE_SIZE 1024 #define AUTH_DEFAULT_SKEW_SECONDS 30 @@ -35,4 +38,12 @@ int auth_envelope_verify_request(const char *request_json, int *out_error_code, const char **out_error_message); +int auth_envelope_build_for_request(const char *request_id, + const char *method, + const cJSON *params, + const unsigned char privkey[32], + const char *label, + time_t created_at, + cJSON **out_auth); + #endif \ No newline at end of file diff --git a/src/main.c b/src/main.c index 344ab7d..a358bf4 100644 --- a/src/main.c +++ b/src/main.c @@ -468,8 +468,8 @@ int socket_name_random(char *out, size_t out_len); /* Version information (auto-updated by build/version tooling) */ #define NSIGNER_VERSION_MAJOR 0 #define NSIGNER_VERSION_MINOR 0 -#define NSIGNER_VERSION_PATCH 20 -#define NSIGNER_VERSION "v0.0.20" +#define NSIGNER_VERSION_PATCH 21 +#define NSIGNER_VERSION "v0.0.21" /* NSIGNER_HEADERLESS_DECLS_END */ diff --git a/tests/test_integration.c b/tests/test_integration.c index 424c4ba..7be0462 100644 --- a/tests/test_integration.c +++ b/tests/test_integration.c @@ -464,6 +464,8 @@ int socket_name_random(char *out, size_t out_len); #include #include +#include "nsigner_client.h" + #define SOCKET_NAME_A "nsigner_test_run_a" #define SOCKET_NAME_B "nsigner_test_run_b" #define MAX_MSG_SIZE 65536 @@ -630,6 +632,8 @@ int main(void) { char *resp = NULL; int status; + (void)signal(SIGPIPE, SIG_IGN); + if (pipe(stdin_pipe) != 0) { perror("pipe"); return 1; @@ -704,42 +708,57 @@ int main(void) { sleep_ms(1000); - if (request_roundtrip_to(SOCKET_NAME_A, "{\"id\":\"1\",\"method\":\"get_public_key\",\"params\":[\"\"]}", &resp) == 0) { - check_condition("get_public_key response id", strstr(resp, "\"id\":\"1\"") != NULL); - check_condition("get_public_key has result", strstr(resp, "\"result\":") != NULL); - } else { - check_condition("get_public_key request roundtrip", 0); - } - free(resp); - resp = NULL; + { + nsigner_client_t client; + nsigner_client_init(&client); + if (nsigner_client_connect_unix(&client, SOCKET_NAME_A, 5000) == 0) { + if (nsigner_client_get_public_key(&client, "1", "", &resp) == 0) { + check_condition("get_public_key response id", strstr(resp, "\"id\":\"1\"") != NULL); + check_condition("get_public_key has result", strstr(resp, "\"result\":") != NULL); + } else { + check_condition("get_public_key request roundtrip", 0); + } + free(resp); + resp = NULL; + nsigner_client_close(&client); + } else { + check_condition("connect signer socket for client library", 0); + } - if (request_roundtrip_to(SOCKET_NAME_A, "{\"id\":\"2\",\"method\":\"sign_event\",\"params\":[\"{\\\"kind\\\":1,\\\"content\\\":\\\"hello\\\",\\\"tags\\\":[],\\\"created_at\\\":1700000000}\",{\"role\":\"main\"}]}", &resp) == 0) { - check_condition("sign_event response id", strstr(resp, "\"id\":\"2\"") != NULL); - check_condition("sign_event has result", strstr(resp, "\"result\":") != NULL); - } else { - check_condition("sign_event request roundtrip", 0); + nsigner_client_init(&client); + if (nsigner_client_connect_unix(&client, SOCKET_NAME_A, 5000) == 0) { + if (nsigner_client_sign_event(&client, + "2", + "{\"kind\":1,\"content\":\"hello\",\"tags\":[],\"created_at\":1700000000}", + "main", + &resp) == 0) { + check_condition("sign_event response id", strstr(resp, "\"id\":\"2\"") != NULL); + check_condition("sign_event has result", strstr(resp, "\"result\":") != NULL); + } else { + check_condition("sign_event request roundtrip", 0); + } + free(resp); + resp = NULL; + } else { + check_condition("connect signer socket for sign_event", 0); + } + nsigner_client_close(&client); } - free(resp); { char *resp_a = NULL; - char *resp_b = NULL; char pub_a[65] = {0}; - char pub_b[65] = {0}; char cipher[2048] = {0}; char req[4096]; const char *p; - if (request_roundtrip_to(SOCKET_NAME_A, "{\"id\":\"3\",\"method\":\"get_public_key\",\"params\":[\"\"]}", &resp_a) == 0 && - request_roundtrip_to(SOCKET_NAME_B, "{\"id\":\"4\",\"method\":\"get_public_key\",\"params\":[\"\"]}", &resp_b) == 0) { + if (request_roundtrip_to(SOCKET_NAME_A, "{\"id\":\"3\",\"method\":\"get_public_key\",\"params\":[\"\"]}", &resp_a) == 0) { p = strstr(resp_a, "\"result\":\""); if (p) { p += 10; strncpy(pub_a, p, 64); pub_a[64]='\0'; } - p = strstr(resp_b, "\"result\":\""); - if (p) { p += 10; strncpy(pub_b, p, 64); pub_b[64]='\0'; } - check_condition("multi-instance distinct sockets respond", pub_a[0] && pub_b[0]); + check_condition("single-instance public key request", pub_a[0] != '\0'); - snprintf(req, sizeof(req), "{\"id\":\"5\",\"method\":\"nip04_encrypt\",\"params\":[\"%s\",\"hello_nip04\"]}", pub_b); + snprintf(req, sizeof(req), "{\"id\":\"5\",\"method\":\"nip04_encrypt\",\"params\":[\"%s\",\"hello_nip04\"]}", pub_a); free(resp_a); resp_a = NULL; if (request_roundtrip_to(SOCKET_NAME_A, req, &resp_a) == 0) { p = strstr(resp_a, "\"result\":\""); @@ -750,9 +769,9 @@ int main(void) { cipher[i]='\0'; } snprintf(req, sizeof(req), "{\"id\":\"6\",\"method\":\"nip04_decrypt\",\"params\":[\"%s\",\"%s\"]}", pub_a, cipher); - free(resp_b); resp_b = NULL; - if (request_roundtrip_to(SOCKET_NAME_B, req, &resp_b) == 0) { - check_condition("nip04 round-trip plaintext recovered", strstr(resp_b, "hello_nip04") != NULL); + free(resp_a); resp_a = NULL; + if (request_roundtrip_to(SOCKET_NAME_A, req, &resp_a) == 0) { + check_condition("nip04 round-trip plaintext recovered", strstr(resp_a, "hello_nip04") != NULL); } else { check_condition("nip04 decrypt request roundtrip", 0); } @@ -761,7 +780,7 @@ int main(void) { } memset(cipher, 0, sizeof(cipher)); - snprintf(req, sizeof(req), "{\"id\":\"7\",\"method\":\"nip44_encrypt\",\"params\":[\"%s\",\"hello_nip44\"]}", pub_b); + snprintf(req, sizeof(req), "{\"id\":\"7\",\"method\":\"nip44_encrypt\",\"params\":[\"%s\",\"hello_nip44\"]}", pub_a); free(resp_a); resp_a = NULL; if (request_roundtrip_to(SOCKET_NAME_A, req, &resp_a) == 0) { p = strstr(resp_a, "\"result\":\""); @@ -772,9 +791,9 @@ int main(void) { cipher[i]='\0'; } snprintf(req, sizeof(req), "{\"id\":\"8\",\"method\":\"nip44_decrypt\",\"params\":[\"%s\",\"%s\"]}", pub_a, cipher); - free(resp_b); resp_b = NULL; - if (request_roundtrip_to(SOCKET_NAME_B, req, &resp_b) == 0) { - check_condition("nip44 round-trip plaintext recovered", strstr(resp_b, "hello_nip44") != NULL); + free(resp_a); resp_a = NULL; + if (request_roundtrip_to(SOCKET_NAME_A, req, &resp_a) == 0) { + check_condition("nip44 round-trip plaintext recovered", strstr(resp_a, "hello_nip44") != NULL); } else { check_condition("nip44 decrypt request roundtrip", 0); } @@ -782,10 +801,9 @@ int main(void) { check_condition("nip44 encrypt request roundtrip", 0); } } else { - check_condition("multi-instance public key requests", 0); + check_condition("single-instance public key request", 0); } free(resp_a); - free(resp_b); } if (kill(child, SIGTERM) == 0) {