diff --git a/lightningd/.gitignore b/lightningd/.gitignore index c9af60b4c..4168578a6 100644 --- a/lightningd/.gitignore +++ b/lightningd/.gitignore @@ -1 +1,2 @@ lightningd +lightningd_hsm diff --git a/lightningd/Makefile b/lightningd/Makefile index acb017553..8783a409c 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -4,7 +4,7 @@ lightningd-wrongdir: $(MAKE) -C .. lightningd-all -lightningd-all: lightningd/lightningd +lightningd-all: lightningd/lightningd lightningd/lightningd_hsm default: lightningd-all @@ -40,6 +40,8 @@ LIGHTNINGD_HEADERS := lightningd/lightningd.h \ $(LIGHTNINGD_OBJS) $(LIGHTNINGD_LIB_OBJS): $(LIGHTNINGD_HEADERS) $(LIGHTNINGD_JSMN_HEADERS) $(BITCOIN_HEADERS) $(CORE_HEADERS) $(GEN_HEADERS) $(CCAN_HEADERS) $(DAEMON_HEADERS) $(LIBBASE58_HEADERS) +include lightningd/hsm/Makefile + check-source: $(LIGHTNINGD_SRC:%=check-src-include-order/%) check-source: $(LIGHTNINGD_LIB_SRC:%=check-src-include-order/%) check-source: $(LIGHTNINGD_CLI_SRC:%=check-src-include-order/%) diff --git a/lightningd/hsm/Makefile b/lightningd/hsm/Makefile new file mode 100644 index 000000000..d76236a7c --- /dev/null +++ b/lightningd/hsm/Makefile @@ -0,0 +1,66 @@ +#! /usr/bin/make + +# Designed to be run one level up +lightningd/hsm-wrongdir: + $(MAKE) -C .. lightningd/hsm-all + +default: lightningd/hsm-all + +# Clients use this: +LIGHTNINGD_HSM_CLIENT_HEADERS := lightningd/hsm/client.h +LIGHTNINGD_HSM_CLIENT_SRC := lightningd/hsm/client.c lightningd/hsm/gen_hsm_client_wire.c +LIGHTNINGD_HSM_CLIENT_OBJS := $(LIGHTNINGD_HSM_CLIENT_SRC:.c=.o) + +# Control daemon uses this: +LIGHTNINGD_HSM_CONTROL_HEADERS := lightningd/hsm/gen_hsm_control_wire.h +LIGHTNINGD_HSM_CONTROL_SRC := lightningd/hsm/gen_hsm_control_wire.c +LIGHTNINGD_HSM_CONTROL_OBJS := $(LIGHTNINGD_HSM_CONTROL_SRC:.c=.o) + +# lightningd/hsm needs these: +LIGHTNINGD_HSM_HEADERS := lightningd/hsm/gen_hsm_client_wire.h \ + lightningd/hsm/gen_hsm_control_wire.h \ + lightningd/hsm/gen_hsm_status_wire.h +LIGHTNINGD_HSM_SRC := lightningd/hsm/hsm.c \ + $(LIGHTNINGD_HSM_HEADERS:.h=.c) +LIGHTNINGD_HSM_OBJS := $(LIGHTNINGD_HSM_SRC:.c=.o) + +# For checking +LIGHTNINGD_HSM_ALLSRC_NOGEN := $(filter-out lightningd/hsm/gen_%, $(LIGHTNINGD_HSM_CLIENT_SRC) $(LIGHTNINGD_HSM_SRC)) +LIGHTNINGD_HSM_ALLHEADERS_NOGEN := $(filter-out lightningd/hsm/gen_%, $(LIGHTNINGD_HSM_CLIENT_HEADERS) $(LIGHTNINGD_HSM_HEADERS)) + +$(LIGHTNINGD_HSM_OBJS) $(LIGHTNINGD_HSM_CLIENT_OBJS): $(CCAN_HEADERS) $(CORE_HEADERS) $(BITCOIN_HEADERS) $(GEN_HEADERS) $(WIRE_HEADERS) $(LIGHTNINGD_HSM_HEADERS) $(LIGHTNINGD_HSM_GEN_HEADERS) $(LIBBASE58_HEADERS) + +lightningd/hsm-all: lightningd/lightningd_hsm $(LIGHTNINGD_HSM_CLIENT_OBJS) + +lightningd/lightningd_hsm: $(LIGHTNINGD_HSM_OBJS) $(CORE_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(CCAN_OBJS) $(LIBBASE58_OBJS) libsecp256k1.a + $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) + +lightningd/hsm/gen_hsm_client_wire.h: $(WIRE_GEN) lightningd/hsm/hsm_client_wire_csv + $(WIRE_GEN) --header $@ hsm_client_wire_type < lightningd/hsm/hsm_client_wire_csv > $@ + +lightningd/hsm/gen_hsm_client_wire.c: $(WIRE_GEN) lightningd/hsm/hsm_client_wire_csv + $(WIRE_GEN) ${@:.c=.h} hsm_client_wire_type< lightningd/hsm/hsm_client_wire_csv > $@ + +lightningd/hsm/gen_hsm_control_wire.h: $(WIRE_GEN) lightningd/hsm/hsm_control_wire_csv + $(WIRE_GEN) --header $@ hsm_control_wire_type < lightningd/hsm/hsm_control_wire_csv > $@ + +lightningd/hsm/gen_hsm_control_wire.c: $(WIRE_GEN) lightningd/hsm/hsm_control_wire_csv + $(WIRE_GEN) ${@:.c=.h} hsm_control_wire_type < lightningd/hsm/hsm_control_wire_csv > $@ + +lightningd/hsm/gen_hsm_status_wire.h: $(WIRE_GEN) lightningd/hsm/hsm_status_wire_csv + $(WIRE_GEN) --header $@ hsm_status_wire_type < lightningd/hsm/hsm_status_wire_csv > $@ + +lightningd/hsm/gen_hsm_status_wire.c: $(WIRE_GEN) lightningd/hsm/hsm_status_wire_csv + $(WIRE_GEN) ${@:.c=.h} hsm_status_wire_type < lightningd/hsm/hsm_status_wire_csv > $@ + +check-source: $(LIGHTNINGD_HSM_ALLSRC_NOGEN:%=check-src-include-order/%) $(LIGHTNINGD_HSM_ALLHEADERS_NOGEN:%=check-hdr-include-order/%) +check-source-bolt: $(LIGHTNINGD_HSM_SRC:%=bolt-check/%) $(LIGHTNINGD_HSM_HEADERS:%=bolt-check/%) + +check-whitespace: $(LIGHTNINGD_HSM_ALLSRC_NOGEN:%=check-whitespace/%) $(LIGHTNINGD_HSM_ALLHEADERS_NOGEN:%=check-whitespace/%) + +clean: lightningd/hsm-clean + +lightningd/hsm-clean: + $(RM) $(LIGHTNINGD_HSM_OBJS) gen_* + +-include lightningd/hsm/test/Makefile diff --git a/lightningd/hsm/client.c b/lightningd/hsm/client.c new file mode 100644 index 000000000..a3d4c5588 --- /dev/null +++ b/lightningd/hsm/client.c @@ -0,0 +1,31 @@ +#include +#include +#include + +static int hsm_fd = -1; + +void hsm_setup(int fd) +{ + hsm_fd = fd; +} + +bool hsm_do_ecdh(struct sha256 *ss, const struct pubkey *point) +{ + u8 *req = towire_hsm_ecdh_req(NULL, point), *resp; + size_t len; + + if (!wire_sync_write(hsm_fd, req)) + goto fail; + resp = wire_sync_read(req, hsm_fd); + if (!resp) + goto fail; + len = tal_count(resp); + if (!fromwire_hsm_ecdh_resp(resp, &len, ss)) + goto fail; + tal_free(req); + return true; + +fail: + tal_free(req); + return false; +} diff --git a/lightningd/hsm/client.h b/lightningd/hsm/client.h new file mode 100644 index 000000000..d1be785f1 --- /dev/null +++ b/lightningd/hsm/client.h @@ -0,0 +1,17 @@ +/* API to ask the HSM for things. */ +#ifndef LIGHTNING_LIGHTNINGD_HSM_H +#define LIGHTNING_LIGHTNINGD_HSM_H +#include "config.h" +#include +#include +#include + +struct pubkey; +struct sha256; + +/* Setup communication to the HSM */ +void hsm_setup(int fd); + +/* Do ECDH using this node id secret. */ +bool hsm_do_ecdh(struct sha256 *ss, const struct pubkey *point); +#endif /* LIGHTNING_LIGHTNINGD_HSM_H */ diff --git a/lightningd/hsm/hsm.c b/lightningd/hsm/hsm.c new file mode 100644 index 000000000..0c3e17dd8 --- /dev/null +++ b/lightningd/hsm/hsm.c @@ -0,0 +1,303 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Nobody will ever find it here! */ +static struct privkey hsm_secret; + +struct conn_info { + struct io_plan *(*received_req)(struct io_conn *, struct conn_info *); + u8 *in; + u8 *out; + int out_fd; +}; + +struct client { + struct conn_info ci; + u64 id; + u8 *(*handle)(struct client *c, const tal_t *data); +}; + +static void node_key(struct privkey *node_secret, struct pubkey *node_id) +{ + u32 salt = 0; + struct privkey unused_s; + struct pubkey unused_k; + + if (node_secret == NULL) + node_secret = &unused_s; + else if (node_id == NULL) + node_id = &unused_k; + + do { + hkdf_sha256(node_secret, sizeof(*node_secret), + &salt, sizeof(salt), + &hsm_secret, sizeof(hsm_secret), + "nodeid", 6); + salt++; + } while (!secp256k1_ec_pubkey_create(secp256k1_ctx, &node_id->pubkey, + node_secret->secret)); +} + +static void conn_info_init(struct conn_info *ci, + struct io_plan *(*received_req)(struct io_conn *conn, + struct conn_info *ci)) +{ + ci->received_req = received_req; + ci->in = ci->out = NULL; + ci->out_fd = -1; +} + +static struct io_plan *sent_resp(struct io_conn *conn, struct conn_info *ci); + +/* Client operations */ +static struct io_plan *client_received_req(struct io_conn *conn, + struct conn_info *ci) +{ + struct client *c = container_of(ci, struct client, ci); + + status_trace("Client %"PRIu64": type %s len %zu", + c->id, + hsm_client_wire_type_name(fromwire_peektype(ci->in)), + tal_count(ci->in)); + + ci->out = c->handle(c, ci->in); + if (!ci->out) { + status_send(towire_hsmstatus_client_bad_request(c, c->id, + ci->in)); + return io_close(conn); + } + ci->in = tal_free(ci->in); + return io_write_wire(conn, ci->out, sent_resp, ci); +} + +static struct client *new_client(const tal_t *ctx, + u64 id, + u8 *(*handle)(struct client *c, + const tal_t *data)) +{ + struct client *c = tal(ctx, struct client); + c->id = id; + c->handle = handle; + conn_info_init(&c->ci, client_received_req); + + return c; +} + +static u8 *handle_ecdh(struct client *c, const void *data) +{ + struct privkey privkey; + struct pubkey point; + struct sha256 ss; + + if (!fromwire_hsm_ecdh_req(data, NULL, &point)) + return NULL; + + node_key(&privkey, NULL); + if (secp256k1_ecdh(secp256k1_ctx, ss.u.u8, &point.pubkey, + privkey.secret) != 1) + return NULL; + + return towire_hsm_ecdh_resp(c, &ss); +} + +/* Control messages */ +static u8 *init_response(struct conn_info *control) +{ + struct pubkey node_id; + node_key(NULL, &node_id); + return towire_hsmctl_init_response(control, &node_id); +} + +static u8 *create_new_hsm(struct conn_info *control) +{ + int fd = open("hsm_secret", O_CREAT|O_EXCL|O_WRONLY, 0400); + if (fd < 0) + status_failed(WIRE_HSMSTATUS_INIT_FAILED, + "creating: %s", strerror(errno)); + + randombytes_buf(&hsm_secret, sizeof(hsm_secret)); + if (!write_all(fd, &hsm_secret, sizeof(hsm_secret))) { + unlink_noerr("hsm_secret"); + status_failed(WIRE_HSMSTATUS_INIT_FAILED, + "writing: %s", strerror(errno)); + } + if (fsync(fd) != 0) { + unlink_noerr("hsm_secret"); + status_failed(WIRE_HSMSTATUS_INIT_FAILED, + "fsync: %s", strerror(errno)); + } + if (close(fd) != 0) { + unlink_noerr("hsm_secret"); + status_failed(WIRE_HSMSTATUS_INIT_FAILED, + "closing: %s", strerror(errno)); + } + fd = open(".", O_RDONLY); + if (fsync(fd) != 0) { + unlink_noerr("hsm_secret"); + status_failed(WIRE_HSMSTATUS_INIT_FAILED, + "fsyncdir: %s", strerror(errno)); + } + close(fd); + + return init_response(control); +} + +static u8 *load_hsm(struct conn_info *control) +{ + int fd = open("hsm_secret", O_RDONLY); + if (fd < 0) + status_failed(WIRE_HSMSTATUS_INIT_FAILED, + "opening: %s", strerror(errno)); + if (!read_all(fd, &hsm_secret, sizeof(hsm_secret))) + status_failed(WIRE_HSMSTATUS_INIT_FAILED, + "reading: %s", strerror(errno)); + close(fd); + + return init_response(control); +} + +static struct io_plan *recv_req(struct io_conn *conn, struct conn_info *ci) +{ + return io_read_wire(conn, ci, &ci->in, ci->received_req, ci); +} + +static struct io_plan *sent_out_fd(struct io_conn *conn, struct conn_info *ci) +{ + ci->out_fd = -1; + return recv_req(conn, ci); +} + +static struct io_plan *sent_resp(struct io_conn *conn, struct conn_info *ci) +{ + ci->out = tal_free(ci->out); + if (ci->out_fd != -1) + return io_send_fd(conn, ci->out_fd, sent_out_fd, ci); + return recv_req(conn, ci); +} + +static struct io_plan *ecdh_client(struct io_conn *conn, struct client *c) +{ + tal_steal(conn, c); + return recv_req(conn, &c->ci); +} + +static u8 *pass_hsmfd_ecdh(struct io_conn *conn, + struct conn_info *control, + const tal_t *data, + int *fd_to_pass) +{ + int fds[2]; + u64 id; + struct client *c; + + if (!fromwire_hsmctl_hsmfd_ecdh(data, NULL, &id)) + status_failed(WIRE_HSMSTATUS_BAD_REQUEST, "bad HSMFD_ECDH"); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) + status_failed(WIRE_HSMSTATUS_FD_FAILED, + "creating fds: %s", strerror(errno)); + + c = new_client(control, id, handle_ecdh); + io_new_conn(control, fds[0], ecdh_client, c); + + *fd_to_pass = fds[1]; + return towire_hsmctl_hsmfd_ecdh_response(control); +} + +static struct io_plan *control_received_req(struct io_conn *conn, + struct conn_info *control) +{ + enum hsm_control_wire_type t = fromwire_peektype(control->in); + + status_trace("Control: type %s len %zu", + hsm_control_wire_type_name(t), tal_count(control->in)); + + switch (t) { + case WIRE_HSMCTL_INIT_NEW: + control->out = create_new_hsm(control); + goto send_out; + case WIRE_HSMCTL_INIT_LOAD: + control->out = load_hsm(control); + goto send_out; + case WIRE_HSMCTL_HSMFD_ECDH: + control->out = pass_hsmfd_ecdh(conn, control, control->in, + &control->out_fd); + goto send_out; + case WIRE_HSMCTL_SHUTDOWN: + io_break(control); + return io_never(conn, control); + + case WIRE_HSMCTL_INIT_RESPONSE: + case WIRE_HSMCTL_HSMFD_ECDH_RESPONSE: + break; + } + + /* Control shouldn't give bad requests. */ + status_failed(WIRE_HSMSTATUS_BAD_REQUEST, "%i", t); + +send_out: + if (control->out) + return io_write_wire(conn, control->out, sent_resp, control); + else + return sent_resp(conn, control); +} + +static struct io_plan *control_init(struct io_conn *conn, + struct conn_info *control) +{ + return recv_req(conn, control); +} + +#ifndef TESTING +int main(int argc, char *argv[]) +{ + struct conn_info *control; + + if (argc == 2 && streq(argv[1], "--version")) { + printf("%s\n", version()); + exit(0); + } + + breakpoint(); + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY + | SECP256K1_CONTEXT_SIGN); + + control = tal(NULL, struct conn_info); + conn_info_init(control, control_received_req); + + /* Stdout == status, stdin == requests */ + status_setup(STDOUT_FILENO); + + io_new_conn(control, STDIN_FILENO, control_init, control); + io_loop(NULL, NULL); + + tal_free(control); + return 0; +} +#endif diff --git a/lightningd/hsm/hsm_client_wire_csv b/lightningd/hsm/hsm_client_wire_csv new file mode 100644 index 000000000..693e2991d --- /dev/null +++ b/lightningd/hsm/hsm_client_wire_csv @@ -0,0 +1,5 @@ +# Give me ECDH(node-id-secret,point) +hsm_ecdh_req,1 +hsm_ecdh_req,0,point,33 +hsm_ecdh_resp,100 +hsm_ecdh_resp,0,ss,32 diff --git a/lightningd/hsm/hsm_control_wire_csv b/lightningd/hsm/hsm_control_wire_csv new file mode 100644 index 000000000..7224af3cc --- /dev/null +++ b/lightningd/hsm/hsm_control_wire_csv @@ -0,0 +1,16 @@ +# These both respond with init_response +hsmctl_init_new,1 +hsmctl_init_load,2 + +hsmctl_init_response,100 +hsmctl_init_response,0,node_id,33 + +# ECDH returns an fd. +hsmctl_hsmfd_ecdh,3 +hsmctl_hsmfd_ecdh,0,unique_id,8 + +# No message, just an fd. +hsmctl_hsmfd_ecdh_response,103 + +# Shutdown just results in an exit. +hsmctl_shutdown,4 diff --git a/lightningd/hsm/hsm_status_wire_csv b/lightningd/hsm/hsm_status_wire_csv new file mode 100644 index 000000000..807b5bb75 --- /dev/null +++ b/lightningd/hsm/hsm_status_wire_csv @@ -0,0 +1,11 @@ +# These are fatal. +hsmstatus_init_failed,0x8000 +hsmstatus_writemsg_failed,0x8001 +hsmstatus_bad_request,0x8002 +hsmstatus_fd_failed,0x8003 + +# Clients should not give a bad request but not the HSM's decision to crash. +hsmstatus_client_bad_request,1 +hsmstatus_client_bad_request,0,unique-id,8 +hsmstatus_client_bad_request,8,len,2 +hsmstatus_client_bad_request,10,msg,len,u8 diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 3cf7844ae..e8c7015a7 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -94,7 +94,8 @@ static struct lightningd *new_lightningd(const tal_t *ctx) } static const char *daemons[] = { - "lightningd" + "lightningd", + "lightningd_hsm" }; /* Check we can run them, and check their versions */