From f94203f344a7eabba834702dd171ae6c5c01c729 Mon Sep 17 00:00:00 2001 From: Paul Selkirk Date: Thu, 2 Jun 2016 14:01:39 -0400 Subject: Add RPC client daemon. --- .gitignore | 1 + GNUmakefile | 20 ++- daemon.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++++++++ rpc_client_daemon.c | 89 ++++++++++++++ 4 files changed, 448 insertions(+), 5 deletions(-) create mode 100644 daemon.c create mode 100644 rpc_client_daemon.c 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 +#include +#include +#include +#include +#include +#include /* required with -std=c99 */ +#include /* 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 +#include +#include +#include +#include + +#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; +} -- cgit v1.2.3