/* * 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; }