aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Selkirk <paul@psgd.org>2016-06-02 14:01:39 -0400
committerPaul Selkirk <paul@psgd.org>2016-06-02 14:01:39 -0400
commitf94203f344a7eabba834702dd171ae6c5c01c729 (patch)
tree5706fa9a3fa55ba408abee5b3c874cc170f0de11
parent63fd94a724893152592b5f318e7d98f2be0ede74 (diff)
Add RPC client daemon.
-rw-r--r--.gitignore1
-rw-r--r--GNUmakefile20
-rw-r--r--daemon.c343
-rw-r--r--rpc_client_daemon.c89
4 files changed, 448 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore
index 6919235..7131463 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ TAGS
autom4te.cache
config.log
config.status
+cryptech_rpcd
tests/test-aes-key-wrap
tests/test-bus
tests/test-ecdsa
diff --git a/GNUmakefile b/GNUmakefile
index e0a569a..e92dd4f 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -51,7 +51,7 @@ PKEY2_OBJ = aes_keywrap.o modexp.o
# IO_BUS = eim | i2c | fmc
# eim: EIM bus from Novena
# i2c: older I2C bus from Novena
-# fmc: FMC bus from dev-bridge board
+# fmc: FMC bus from dev-bridge and alpha boards
IO_BUS ?= eim
ifeq (${IO_BUS},eim)
@@ -95,26 +95,29 @@ endif
#
# RPC_SERVER = yes
#
-# RPC_TRANSPORT = loopback | serial
+# RPC_TRANSPORT = loopback | serial | daemon
# loopback: communicate over loopback socket on Novena
# serial: communicate over USB in serial pass-through mode
+# daemon: communicate over USB via a daemon, to arbitrate multiple clients
-RPC_TRANSPORT ?= serial
+RPC_TRANSPORT ?= daemon
RPC_CLIENT_OBJ = rpc_api.o rpc_client.o xdr.o
ifeq (${RPC_TRANSPORT},loopback)
RPC_CLIENT_OBJ += rpc_client_loopback.o
else ifeq (${RPC_TRANSPORT},serial)
RPC_CLIENT_OBJ += rpc_client_serial.o slip.o
+else ifeq (${RPC_TRANSPORT},daemon)
+ RPC_CLIENT_OBJ += rpc_client_daemon.o
endif
RPC_DISPATCH_OBJ = rpc_hash.o rpc_misc.o rpc_pkey.o
-RPC_SERVER_OBJ = rpc_server.o xdr.o ${RPC_DISPATCH_OBJ}
+RPC_SERVER_OBJ = rpc_api.o rpc_server.o xdr.o ${RPC_DISPATCH_OBJ}
ifeq (${RPC_TRANSPORT},loopback)
RPC_SERVER_OBJ += rpc_server_loopback.o
else ifeq (${RPC_TRANSPORT},serial)
- RPC_SERVER_OBJ += rpc_server_serial.o rpc_serial.o slip.o
+ RPC_SERVER_OBJ += rpc_server_serial.o slip.o
endif
# Not building any of the RPC stuff, access FPGA cores directly.
@@ -186,6 +189,13 @@ server:
loopback:
${MAKE} RPC_CLIENT=remote RPC_SERVER=yes RPC_TRANSPORT=loopback
+daemon: cryptech_rpcd
+# ${MAKE} RPC_CLIENT=mixed RPC_TRANSPORT=daemon
+ ${MAKE} RPC_CLIENT=remote RPC_TRANSPORT=daemon
+
+cryptech_rpcd: daemon.o slip.o rpc_serial.o xdr.o
+ ${CC} ${CFLAGS} -o $@ $^ ${LDFLAGS}
+
${OBJ}: ${INC}
${LIB}: ${OBJ}
diff --git a/daemon.c b/daemon.c
new file mode 100644
index 0000000..601fc0f
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,343 @@
+#define DEBUG
+/*
+ * daemon.c
+ * --------
+ * A daemon to arbitrate shared access to a serial connection to the HSM.
+ *
+ * Copyright (c) 2016, NORDUnet A/S All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the NORDUnet nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <poll.h>
+#include <getopt.h> /* required with -std=c99 */
+#include <termios.h> /* for default speed */
+
+#include "slip_internal.h"
+#include "xdr_internal.h"
+
+static char usage[] =
+ "usage: %s [-n socketname] [-d ttydevice] [-s ttyspeed]\n";
+
+/* select() is hopelessly broken, and epoll() is Linux-specific, so we'll use
+ * poll() until such a time as libevent or libev seems more appropriate.
+ * Unfortunately, poll() doesn't come with any macros or functions to manage
+ * the pollfd array, so we have to invent them.
+ */
+
+static struct pollfd *pollfds = NULL;
+static nfds_t nfds = 0;
+static nfds_t npollfds = 0;
+
+static void poll_add(int fd)
+{
+ /* add 4 entries at a time to avoid having to realloc too often */
+#define NNEW 4
+
+ /* expand the array if necessary */
+ if (nfds == npollfds) {
+ npollfds = nfds + NNEW;
+ pollfds = realloc(pollfds, npollfds * sizeof(struct pollfd));
+ if (pollfds == NULL) {
+ perror("realloc");
+ exit(EXIT_FAILURE);
+ }
+ /* zero the new entries for hygiene */
+ memset(&pollfds[nfds], 0, NNEW * sizeof(struct pollfd));
+ }
+
+ /* populate the new entry */
+ pollfds[nfds].fd = fd;
+ pollfds[nfds].events = POLLIN;
+ ++nfds;
+}
+
+static void poll_remove(int fd)
+{
+ nfds_t i;
+
+ /* search the pollfd array */
+ for (i = 0; i < nfds; ++i) {
+ if (pollfds[i].fd == fd) {
+ /* shift remainder of the array left by one */
+ memmove(&pollfds[i], &pollfds[i + 1], (nfds - i - 1) * sizeof(struct pollfd));
+ /* zero the last entry for hygiene */
+ memset(&pollfds[nfds - 1], 0, sizeof(struct pollfd));
+ --nfds;
+ return;
+ }
+ }
+ /* if it's not found, return without an error */
+}
+
+#ifndef MAX_PKT_SIZE /* move this to hal_internal.h */
+#define MAX_PKT_SIZE 4096
+#endif
+
+typedef struct {
+ size_t len;
+ uint8_t buf[MAX_PKT_SIZE];
+} rpc_buffer_t;
+static rpc_buffer_t ibuf, obuf;
+
+#ifndef DEVICE
+#define DEVICE "/dev/ttyUSB0"
+#endif
+#ifndef SPEED
+#define SPEED B921600
+#endif
+
+#ifndef SOCKET_NAME
+#define SOCKET_NAME "/tmp/cryptechd.socket"
+#endif
+char *socket_name = SOCKET_NAME;
+
+/* Set up an atexit handler to remove the filesystem entry for the unix domain
+ * socket. This will trigger on error exits, but not on the "normal" SIGKILL.
+ */
+void atexit_cleanup(void)
+{
+ unlink(socket_name);
+}
+
+#ifdef DEBUG
+static void hexdump(uint8_t *buf, uint32_t len)
+{
+ for (uint32_t i = 0; i < len; ++i)
+ printf("%02x%c", buf[i], ((i & 0x07) == 0x07) ? '\n' : ' ');
+ if ((len & 0x07) != 0)
+ printf("\n");
+}
+#endif
+
+int main(int argc, char *argv[])
+{
+ struct sockaddr_un name;
+ int ret;
+ int lsock;
+ int dsock;
+ int opt;
+ char *device = DEVICE;
+ speed_t speed = SPEED;
+
+ while ((opt = getopt(argc, argv, "hn:d:s:")) != -1) {
+ switch (opt) {
+ case 'h':
+ printf(usage, argv[0]);
+ exit(EXIT_SUCCESS);
+ case 'n':
+ socket_name = optarg;
+ break;
+ case 'd':
+ device = optarg;
+ break;
+ case 's':
+ switch (atoi(optarg)) {
+ case 115200:
+ speed = B115200;
+ break;
+ case 921600:
+ speed = B921600;
+ break;
+ default:
+ printf("invalid speed value %s\n", optarg);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ default:
+ printf(usage, argv[0]);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (atexit(atexit_cleanup) != 0) {
+ perror("atexit");
+ exit(EXIT_FAILURE);
+ }
+
+ if (hal_serial_init(device, speed) != HAL_OK)
+ exit(EXIT_FAILURE);
+
+ int serial_fd = hal_serial_get_fd();
+ poll_add(serial_fd);
+
+ /* Remove the filesystem entry for the unix domain socket. The usual way
+ * to stop a daemon is SIGKILL, which we can't catch, so the file remains,
+ * and will prevent us from binding the socket.
+ *
+ * XXX We should also scan the process table, to make sure the daemon
+ * isn't already running.
+ */
+ unlink(socket_name);
+
+ /* Create the listening socket.
+ */
+ lsock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if (lsock == -1) {
+ perror("socket");
+ exit(EXIT_FAILURE);
+ }
+ poll_add(lsock);
+
+ /* For portability, clear the whole address structure, since some
+ * implementations have additional (nonstandard) fields in the structure.
+ */
+ memset(&name, 0, sizeof(struct sockaddr_un));
+
+ /* Bind the listening socket.
+ */
+ name.sun_family = AF_UNIX;
+ strncpy(name.sun_path, socket_name, sizeof(name.sun_path) - 1);
+ ret = bind(lsock, (const struct sockaddr *) &name, sizeof(struct sockaddr_un));
+ if (ret == -1) {
+ perror("bind");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Prepare to accept connections.
+ */
+ ret = listen(lsock, 20);
+ if (ret == -1) {
+ perror("listen");
+ exit(EXIT_FAILURE);
+ }
+
+ /* The main loop.
+ */
+ for (;;) {
+
+ /* Blocking poll on all descriptors of interest.
+ */
+ ret = poll(pollfds, nfds, -1);
+ if (ret == -1) {
+ perror("poll");
+ exit(EXIT_FAILURE);
+ }
+
+ for (nfds_t i = 0; i < nfds; ++i) {
+ if (pollfds[i].revents != 0) {
+ /* XXX POLLERR|POLLHUP|POLLNVAL */
+
+ /* serial port */
+ if (pollfds[i].fd == serial_fd) {
+ int complete;
+ hal_slip_recv_char(ibuf.buf, &ibuf.len, sizeof(ibuf.buf), &complete);
+ if (complete) {
+#ifdef DEBUG
+ printf("serial port received response:\n");
+ hexdump(ibuf.buf, ibuf.len);
+#endif
+ /* We've got a complete rpc response packet. */
+ const uint8_t *bufptr = ibuf.buf;
+ const uint8_t * const limit = ibuf.buf + ibuf.len;
+ uint32_t sock;
+ /* First word of the response is the client ID. */
+ hal_xdr_decode_int(&bufptr, limit, &sock);
+ /* Pass response on to the client that requested it. */
+ send(sock, bufptr, ibuf.len - 4, 0);
+ /* Reinitialize the receive buffer. */
+ memset(&ibuf, 0, sizeof(ibuf));
+ }
+ }
+
+ /* listening socket */
+ else if (pollfds[i].fd == lsock) {
+ /* Accept incoming connection. */
+ dsock = accept(lsock, NULL, NULL);
+ if (ret == -1) {
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+ poll_add(dsock);
+#ifdef DEBUG
+ printf("listening socket accept data socket %d\n", dsock);
+#endif
+ }
+
+ /* client data socket */
+ else {
+ uint8_t *bufptr = obuf.buf;
+ const uint8_t * const limit = obuf.buf + MAX_PKT_SIZE;
+ /* First word of the request is the client ID. */
+ hal_xdr_encode_int(&bufptr, limit, pollfds[i].fd);
+ /* Get the client's rpc request packet. */
+ obuf.len = recv(pollfds[i].fd, bufptr, MAX_PKT_SIZE - 4, 0);
+#ifdef DEBUG
+ printf("data socket %d received request:\n", pollfds[i].fd);
+ hexdump(obuf.buf, obuf.len + 4);
+#endif
+
+ /* Fill in the client handle arg as needed. */
+ uint32_t opcode;
+ hal_xdr_decode_int((const uint8_t ** const)&bufptr, limit, &opcode);
+ switch (opcode) {
+ case RPC_FUNC_SET_PIN:
+ case RPC_FUNC_LOGIN:
+ case RPC_FUNC_LOGOUT:
+ case RPC_FUNC_IS_LOGGED_IN:
+ case RPC_FUNC_HASH_INITIALIZE:
+ case RPC_FUNC_PKEY_LOAD:
+ case RPC_FUNC_PKEY_FIND:
+ case RPC_FUNC_PKEY_GENERATE_RSA:
+ case RPC_FUNC_PKEY_GENERATE_EC:
+ /* first argument is client handle */
+ hal_xdr_encode_int(&bufptr, limit, pollfds[i].fd);
+ break;
+ default:
+ break;
+ }
+
+ if (obuf.len > 0) {
+#ifdef DEBUG
+ printf("passing to serial port\n");
+#endif
+ /* Pass it on to the serial port. */
+ hal_slip_send(obuf.buf, obuf.len + 4);
+ }
+ else {
+#ifdef DEBUG
+ printf("closing data socket\n");
+#endif
+ /* Client has closed the socket. */
+ close(pollfds[i].fd);
+ poll_remove(pollfds[i].fd);
+ }
+ /* Reinitialize the transmit buffer. */
+ memset(&obuf, 0, sizeof(obuf));
+ }
+ }
+ }
+ }
+
+ /*NOTREACHED*/
+ exit(EXIT_SUCCESS);
+}
diff --git a/rpc_client_daemon.c b/rpc_client_daemon.c
new file mode 100644
index 0000000..f328302
--- /dev/null
+++ b/rpc_client_daemon.c
@@ -0,0 +1,89 @@
+/*
+ * rpc_client_daemon.c
+ * -------------------
+ * Remote procedure call transport over a socket to a daemon.
+ *
+ * Copyright (c) 2016, NORDUnet A/S All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the NORDUnet nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <sys/un.h>
+
+#include "hal.h"
+#include "hal_internal.h"
+
+#ifndef SOCKET_NAME
+#define SOCKET_NAME "/tmp/cryptechd.socket"
+#endif
+
+static int sock = -1;
+
+hal_error_t hal_rpc_client_transport_init(void)
+{
+ struct sockaddr_un name;
+ int ret;
+
+ sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if (sock == -1)
+ return perror("socket"), HAL_ERROR_RPC_TRANSPORT;
+ memset(&name, 0, sizeof(struct sockaddr_un));
+ name.sun_family = AF_UNIX;
+ strncpy(name.sun_path, SOCKET_NAME, sizeof(name.sun_path) - 1);
+ ret = connect(sock, (const struct sockaddr *) &name, sizeof(struct sockaddr_un));
+ if (ret == -1)
+ return perror("connect"), HAL_ERROR_RPC_TRANSPORT;
+ return HAL_OK;
+}
+
+hal_error_t hal_rpc_client_transport_close(void)
+{
+ int ret = close(sock);
+ sock = -1;
+ if (ret != 0)
+ return perror("close"), HAL_ERROR_RPC_TRANSPORT;
+ return HAL_OK;
+}
+
+hal_error_t hal_rpc_send(const uint8_t * const buf, const size_t len)
+{
+ ssize_t ret = send(sock, (const void *)buf, len, 0);
+ return (ret == -1) ? HAL_ERROR_RPC_TRANSPORT : HAL_OK;
+}
+
+hal_error_t hal_rpc_recv(uint8_t * const buf, size_t * const len)
+{
+ ssize_t ret = recv(sock, (void *)buf, *len, 0);
+ if (ret == -1)
+ return HAL_ERROR_RPC_TRANSPORT;
+ *len = (size_t)ret;
+ return HAL_OK;
+}