Files
n_signer/client/nsigner_client.c

370 lines
9.1 KiB
C

/*
* SPDX-License-Identifier: 0BSD
*/
#define _GNU_SOURCE
#include "nsigner_client.h"
#include <arpa/inet.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
#include <cJSON.h>
#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;
}