370 lines
9.1 KiB
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;
|
|
}
|