diff --git a/hsmd/hsmd.c b/hsmd/hsmd.c index c3f04b10f..26b3fb75b 100644 --- a/hsmd/hsmd.c +++ b/hsmd/hsmd.c @@ -1803,47 +1803,6 @@ static struct io_plan *handle_sign_node_announcement(struct io_conn *conn, return req_reply(conn, c, take(reply)); } -/*~ lightningd asks us to sign a message. I tweeted the spec - * in https://twitter.com/rusty_twit/status/1182102005914800128: - * - * @roasbeef & @bitconner point out that #lnd algo is: - * zbase32(SigRec(SHA256(SHA256("Lightning Signed Message:" + msg)))). - * zbase32 from https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt - * and SigRec has first byte 31 + recovery id, followed by 64 byte sig. #specinatweet - */ -static struct io_plan *handle_sign_message(struct io_conn *conn, - struct client *c, - const u8 *msg_in) -{ - u8 *msg; - struct sha256_ctx sctx = SHA256_INIT; - struct sha256_double shad; - secp256k1_ecdsa_recoverable_signature rsig; - struct privkey node_pkey; - - if (!fromwire_hsmd_sign_message(tmpctx, msg_in, &msg)) - return bad_req(conn, c, msg_in); - - /* Prefixing by a known string means we'll never be convinced - * to sign some gossip message, etc. */ - sha256_update(&sctx, "Lightning Signed Message:", - strlen("Lightning Signed Message:")); - sha256_update(&sctx, msg, tal_count(msg)); - sha256_double_done(&sctx, &shad); - - node_key(&node_pkey, NULL); - /*~ By no small coincidence, this libsecp routine uses the exact - * recovery signature format mandated by BOLT 11. */ - if (!secp256k1_ecdsa_sign_recoverable(secp256k1_ctx, &rsig, - shad.sha.u.u8, - node_pkey.secret.data, - NULL, NULL)) { - return bad_req_fmt(conn, c, msg_in, "Failed to sign message"); - } - - return req_reply(conn, c, - take(towire_hsmd_sign_message_reply(NULL, &rsig))); -} /*~ lightningd asks us to sign a bolt12 (e.g. offer). */ static struct io_plan *handle_sign_bolt12(struct io_conn *conn, @@ -2050,7 +2009,10 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c) return handle_sign_mutual_close_tx(conn, c, c->msg_in); case WIRE_HSMD_SIGN_MESSAGE: - return handle_sign_message(conn, c, c->msg_in); + /* Hand off to libhsmd for processing */ + return req_reply(conn, c, + take(hsmd_handle_client_message( + tmpctx, c->hsmd_client, c->msg_in))); case WIRE_HSMD_SIGN_BOLT12: return handle_sign_bolt12(conn, c, c->msg_in); diff --git a/hsmd/libhsmd.c b/hsmd/libhsmd.c index 3d92d4382..4f3deee31 100644 --- a/hsmd/libhsmd.c +++ b/hsmd/libhsmd.c @@ -1,3 +1,4 @@ +#include #include #include @@ -125,13 +126,12 @@ bool check_client_capabilities(struct hsmd_client *client, enum hsmd_wire t) */ /* This function is used to format an error message before passing it * to the library user specified hsmd_status_bad_request */ -/* Temporarily pre-declare until we use the function, then we can make - * it static again. */ -u8 *hsmd_status_bad_request_fmt(struct hsmd_client *client, const u8 *msg, - const char *fmt, ...) PRINTF_FMT(3, 4); +static u8 *hsmd_status_bad_request_fmt(struct hsmd_client *client, + const u8 *msg, const char *fmt, ...) + PRINTF_FMT(3, 4); -u8 *hsmd_status_bad_request_fmt(struct hsmd_client *client, const u8 *msg, - const char *fmt, ...) +static u8 *hsmd_status_bad_request_fmt(struct hsmd_client *client, + const u8 *msg, const char *fmt, ...) { va_list ap; char *str; @@ -142,8 +142,162 @@ u8 *hsmd_status_bad_request_fmt(struct hsmd_client *client, const u8 *msg, return hsmd_status_bad_request(client, msg, str); } +/* Convenience wrapper for when we simply can't parse. */ +static u8 *hsmd_status_malformed_request(struct hsmd_client *c, const u8 *msg_in) +{ + return hsmd_status_bad_request(c, msg_in, "could not parse request"); +} + +/*~ This returns the secret and/or public key for this node. */ +static void node_key(struct privkey *node_privkey, struct pubkey *node_id) +{ + u32 salt = 0; + struct privkey unused_s; + struct pubkey unused_k; + + /* If caller specifies NULL, they don't want the results. */ + if (node_privkey == NULL) + node_privkey = &unused_s; + if (node_id == NULL) + node_id = &unused_k; + + /*~ So, there is apparently a 1 in 2^127 chance that a random value is + * not a valid private key, so this never actually loops. */ + do { + /*~ ccan/crypto/hkdf_sha256 implements RFC5869 "Hardened Key + * Derivation Functions". That means that if a derived key + * leaks somehow, the other keys are not compromised. */ + hkdf_sha256(node_privkey, sizeof(*node_privkey), + &salt, sizeof(salt), + &secretstuff.hsm_secret, + sizeof(secretstuff.hsm_secret), + "nodeid", 6); + salt++; + } while (!secp256k1_ec_pubkey_create(secp256k1_ctx, &node_id->pubkey, + node_privkey->secret.data)); + +#if DEVELOPER + /* In DEVELOPER mode, we can override with --dev-force-privkey */ + if (dev_force_privkey) { + *node_privkey = *dev_force_privkey; + if (!secp256k1_ec_pubkey_create(secp256k1_ctx, &node_id->pubkey, + node_privkey->secret.data)) + hsmd_status_failed(STATUS_FAIL_INTERNAL_ERROR, + "Failed to derive pubkey for dev_force_privkey"); + } +#endif +} + +/*~ lightningd asks us to sign a message. I tweeted the spec + * in https://twitter.com/rusty_twit/status/1182102005914800128: + * + * @roasbeef & @bitconner point out that #lnd algo is: + * zbase32(SigRec(SHA256(SHA256("Lightning Signed Message:" + msg)))). + * zbase32 from https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt + * and SigRec has first byte 31 + recovery id, followed by 64 byte sig. #specinatweet + */ +static u8 *handle_sign_message(struct hsmd_client *c, const u8 *msg_in) +{ + u8 *msg; + struct sha256_ctx sctx = SHA256_INIT; + struct sha256_double shad; + secp256k1_ecdsa_recoverable_signature rsig; + struct privkey node_pkey; + + if (!fromwire_hsmd_sign_message(tmpctx, msg_in, &msg)) + return hsmd_status_malformed_request(c, msg_in); + + /* Prefixing by a known string means we'll never be convinced + * to sign some gossip message, etc. */ + sha256_update(&sctx, "Lightning Signed Message:", + strlen("Lightning Signed Message:")); + sha256_update(&sctx, msg, tal_count(msg)); + sha256_double_done(&sctx, &shad); + + node_key(&node_pkey, NULL); + /*~ By no small coincidence, this libsecp routine uses the exact + * recovery signature format mandated by BOLT 11. */ + if (!secp256k1_ecdsa_sign_recoverable(secp256k1_ctx, &rsig, + shad.sha.u.u8, + node_pkey.secret.data, + NULL, NULL)) { + return hsmd_status_bad_request(c, msg_in, "Failed to sign message"); + } + + return towire_hsmd_sign_message_reply(NULL, &rsig); +} + u8 *hsmd_handle_client_message(const tal_t *ctx, struct hsmd_client *client, const u8 *msg) { - return NULL; + enum hsmd_wire t = fromwire_peektype(msg); + + hsmd_status_debug("Client: Received message %d from client", t); + + /* Before we do anything else, is this client allowed to do + * what he asks for? */ + if (!check_client_capabilities(client, t)) + return hsmd_status_bad_request_fmt( + client, msg, "does not have capability to run %d", t); + + /* If we aren't initialized yet we better get an init message + * first. Otherwise we don't load the secret and every + * signature we produce is just going to be junk. */ + if (!initialized && t != WIRE_HSMD_INIT) + hsmd_status_failed(STATUS_FAIL_MASTER_IO, + "hsmd was not initialized correctly, expected " + "message type %d, got %d", + WIRE_HSMD_INIT, t); + + /* Now actually go and do what the client asked for */ + switch (t) { + case WIRE_HSMD_INIT: + case WIRE_HSMD_CLIENT_HSMFD: + case WIRE_HSMD_GET_CHANNEL_BASEPOINTS: + case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY: + case WIRE_HSMD_ECDH_REQ: + case WIRE_HSMD_CANNOUNCEMENT_SIG_REQ: + case WIRE_HSMD_CUPDATE_SIG_REQ: + case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REQ: + case WIRE_HSMD_SIGN_INVOICE: + case WIRE_HSMD_SIGN_WITHDRAWAL: + case WIRE_HSMD_SIGN_COMMITMENT_TX: + case WIRE_HSMD_SIGN_DELAYED_PAYMENT_TO_US: + case WIRE_HSMD_SIGN_REMOTE_HTLC_TO_US: + case WIRE_HSMD_SIGN_PENALTY_TO_US: + case WIRE_HSMD_SIGN_LOCAL_HTLC_TX: + case WIRE_HSMD_GET_PER_COMMITMENT_POINT: + case WIRE_HSMD_CHECK_FUTURE_SECRET: + case WIRE_HSMD_SIGN_REMOTE_COMMITMENT_TX: + case WIRE_HSMD_SIGN_REMOTE_HTLC_TX: + case WIRE_HSMD_SIGN_MUTUAL_CLOSE_TX: + case WIRE_HSMD_SIGN_BOLT12: + /* Not implemented yet. Should not have been passed here yet. */ + return hsmd_status_bad_request_fmt(client, msg, "Not implemented yet."); + + case WIRE_HSMD_SIGN_MESSAGE: + return handle_sign_message(client, msg); + + case WIRE_HSMD_DEV_MEMLEAK: + case WIRE_HSMD_ECDH_RESP: + case WIRE_HSMD_CANNOUNCEMENT_SIG_REPLY: + case WIRE_HSMD_CUPDATE_SIG_REPLY: + case WIRE_HSMD_CLIENT_HSMFD_REPLY: + case WIRE_HSMD_NODE_ANNOUNCEMENT_SIG_REPLY: + case WIRE_HSMD_SIGN_WITHDRAWAL_REPLY: + case WIRE_HSMD_SIGN_INVOICE_REPLY: + case WIRE_HSMD_INIT_REPLY: + case WIRE_HSMSTATUS_CLIENT_BAD_REQUEST: + case WIRE_HSMD_SIGN_COMMITMENT_TX_REPLY: + case WIRE_HSMD_SIGN_TX_REPLY: + case WIRE_HSMD_GET_PER_COMMITMENT_POINT_REPLY: + case WIRE_HSMD_CHECK_FUTURE_SECRET_REPLY: + case WIRE_HSMD_GET_CHANNEL_BASEPOINTS_REPLY: + case WIRE_HSMD_DEV_MEMLEAK_REPLY: + case WIRE_HSMD_SIGN_MESSAGE_REPLY: + case WIRE_HSMD_GET_OUTPUT_SCRIPTPUBKEY_REPLY: + case WIRE_HSMD_SIGN_BOLT12_REPLY: + break; + } + return hsmd_status_bad_request(client, msg, "Unknown request"); }