From aacb72197864b7b9538bdc1e5abf8b43f5c9009b Mon Sep 17 00:00:00 2001 From: darosior Date: Tue, 22 Oct 2019 12:41:08 +0200 Subject: [PATCH] hsmtool: add a tool to dump commitment points and secrets This takes a dbid, a "depth" (how many points to dump), the hsm_secret path, and a potential password to dump informations about all commitments until the depth. Co-Authored-By: Sjors Provoost --- tools/Makefile | 2 +- tools/hsmtool.c | 147 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 129 insertions(+), 20 deletions(-) diff --git a/tools/Makefile b/tools/Makefile index 31256ef4ccf5..bd6571f837a8 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -14,7 +14,7 @@ tools/headerversions: FORCE tools/headerversions.o $(CCAN_OBJS) tools/check-bolt: tools/check-bolt.o $(CCAN_OBJS) $(TOOLS_COMMON_OBJS) -tools/hsmtool: tools/hsmtool.o $(CCAN_OBJS) $(TOOLS_COMMON_OBJS) bitcoin/privkey.o +tools/hsmtool: tools/hsmtool.o $(CCAN_OBJS) $(TOOLS_COMMON_OBJS) $(BITCOIN_OBJS) common/amount.o common/bigsize.o common/derive_basepoints.o common/node_id.o common/type_to_string.o wire/fromwire.o wire/towire.o clean: tools-clean diff --git a/tools/hsmtool.c b/tools/hsmtool.c index 4be37a821d5f..7c26f2f40465 100644 --- a/tools/hsmtool.c +++ b/tools/hsmtool.c @@ -1,12 +1,17 @@ #include +#include #include #include #include #include #include +#include +#include +#include #include #include #include +#include #include #include #include @@ -14,6 +19,8 @@ #define ERROR_HSM_FILE errno #define ERROR_USAGE 2 #define ERROR_LIBSODIUM 3 +#define ERROR_LIBWALLY 4 +#define ERROR_KEYDERIV 5 static void show_usage(void) { @@ -21,6 +28,8 @@ static void show_usage(void) printf("methods:\n"); printf(" - decrypt \n"); printf(" - encrypt \n"); + printf(" - dumpcommitments " + " [hsm_secret password]\n"); exit(0); } @@ -47,25 +56,33 @@ static bool ensure_hsm_secret_exists(int fd, const char *path) return true; } -static int decrypt_hsm(const char *hsm_secret_path, const char *passwd) +static void get_hsm_secret(struct secret *hsm_secret, + const char *hsm_secret_path) { int fd; - struct stat st; - struct secret key, hsm_secret; + + fd = open(hsm_secret_path, O_RDONLY); + if (fd < 0) + err(ERROR_HSM_FILE, "Could not open hsm_secret"); + if (!read_all(fd, hsm_secret, sizeof(*hsm_secret))) + err(ERROR_HSM_FILE, "Could not read hsm_secret"); + close(fd); +} + +/* Derive the encryption key from the password provided, and try to decrypt + * the cipher. */ +static void get_encrypted_hsm_secret(struct secret *hsm_secret, + const char *hsm_secret_path, + const char *passwd) +{ + int fd; + struct secret key; u8 salt[16] = "c-lightning\0\0\0\0\0"; crypto_secretstream_xchacha20poly1305_state crypto_state; u8 header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; /* The cipher size is static with xchacha20poly1305. */ u8 cipher[sizeof(struct secret) + crypto_secretstream_xchacha20poly1305_ABYTES]; - if (sodium_init() == -1) - err(ERROR_LIBSODIUM, - "Could not initialize libsodium. Not enough entropy ?"); - - if (stat(hsm_secret_path, &st) != 0) - err(ERROR_HSM_FILE, "Could not stat hsm_secret"); - if (st.st_size <= 32) - err(ERROR_USAGE, "hsm_secret is not encrypted"); fd = open(hsm_secret_path, O_RDONLY); if (fd < 0) err(ERROR_HSM_FILE, "Could not open hsm_secret"); @@ -75,8 +92,6 @@ static int decrypt_hsm(const char *hsm_secret_path, const char *passwd) if (!read_all(fd, cipher, sizeof(cipher))) err(ERROR_HSM_FILE, "Could not read cipher body"); - /* Derive the encryption key from the password provided, and try to decrypt - * the cipher. */ if (crypto_pwhash(key.data, sizeof(key.data), passwd, strlen(passwd), salt, crypto_pwhash_argon2id_OPSLIMIT_MODERATE, crypto_pwhash_argon2id_MEMLIMIT_MODERATE, @@ -85,11 +100,65 @@ static int decrypt_hsm(const char *hsm_secret_path, const char *passwd) if (crypto_secretstream_xchacha20poly1305_init_pull(&crypto_state, header, key.data) != 0) err(ERROR_LIBSODIUM, "Could not initialize the crypto state"); - if (crypto_secretstream_xchacha20poly1305_pull(&crypto_state, hsm_secret.data, + if (crypto_secretstream_xchacha20poly1305_pull(&crypto_state, hsm_secret->data, NULL, 0, cipher, sizeof(cipher), NULL, 0) != 0) err(ERROR_LIBSODIUM, "Could not retrieve the seed. Wrong password ?"); + close(fd); +} + +/* Taken from hsmd. */ +static void get_channel_seed(struct secret *channel_seed, struct node_id *peer_id, + u64 dbid, struct secret *hsm_secret) +{ + struct secret channel_base; + u8 input[sizeof(peer_id->k) + sizeof(dbid)]; + /*~ Again, "per-peer" should be "per-channel", but Hysterical Raisins */ + const char *info = "per-peer seed"; + + /*~ We use the DER encoding of the pubkey, because it's platform + * independent. Since the dbid is unique, however, it's completely + * unnecessary, but again, existing users can't be broken. */ + /* FIXME: lnd has a nicer BIP32 method for deriving secrets which we + * should migrate to. */ + hkdf_sha256(&channel_base, sizeof(struct secret), NULL, 0, + hsm_secret, sizeof(*hsm_secret), + /*~ Initially, we didn't support multiple channels per + * peer at all: a channel had to be completely forgotten + * before another could exist. That was slightly relaxed, + * but the phrase "peer seed" is wired into the seed + * generation here, so we need to keep it that way for + * existing clients, rather than using "channel seed". */ + "peer seed", strlen("peer seed")); + memcpy(input, peer_id->k, sizeof(peer_id->k)); + BUILD_ASSERT(sizeof(peer_id->k) == PUBKEY_CMPR_LEN); + /*~ For all that talk about platform-independence, note that this + * field is endian-dependent! But let's face it, little-endian won. + * In related news, we don't support EBCDIC or middle-endian. */ + memcpy(input + PUBKEY_CMPR_LEN, &dbid, sizeof(dbid)); + + hkdf_sha256(channel_seed, sizeof(*channel_seed), + input, sizeof(input), + &channel_base, sizeof(channel_base), + info, strlen(info)); +} + +static int decrypt_hsm(const char *hsm_secret_path, const char *passwd) +{ + int fd; + struct stat st; + struct secret hsm_secret; + + if (sodium_init() == -1) + err(ERROR_LIBSODIUM, + "Could not initialize libsodium. Not enough entropy ?"); + + if (stat(hsm_secret_path, &st) != 0) + err(ERROR_HSM_FILE, "Could not stat hsm_secret"); + if (st.st_size <= 32) + err(ERROR_HSM_FILE, "hsm_secret is not encrypted"); + get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd); /* Create a backup file, "just in case". */ rename(hsm_secret_path, "hsm_secret.backup"); @@ -137,11 +206,7 @@ static int encrypt_hsm(const char *hsm_secret_path, const char *passwd) err(ERROR_HSM_FILE, "Could not stat hsm_secret"); if (st.st_size > 32) err(ERROR_USAGE, "hsm_secret is already encrypted"); - fd = open(hsm_secret_path, O_RDONLY); - if (fd < 0) - err(ERROR_HSM_FILE, "Could not open hsm_secret"); - if (!read_all(fd, &hsm_secret, sizeof(hsm_secret))) - err(ERROR_HSM_FILE, "Could not read hsm_secret"); + get_hsm_secret(&hsm_secret, hsm_secret_path); /* Derive the encryption key from the password provided, and try to encrypt * the seed. */ @@ -187,6 +252,39 @@ static int encrypt_hsm(const char *hsm_secret_path, const char *passwd) return 0; } +static int dump_commitments_infos(struct node_id *node_id, u64 channel_id, + u64 depth, char *hsm_secret_path, char *passwd) +{ + struct sha256 shaseed; + struct secret hsm_secret, channel_seed, per_commitment_secret; + struct pubkey per_commitment_point; + + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY + | SECP256K1_CONTEXT_SIGN); + + if (passwd) + get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd); + else + get_hsm_secret(&hsm_secret, hsm_secret_path); + get_channel_seed(&channel_seed, node_id, channel_id, &hsm_secret); + + derive_shaseed(&channel_seed, &shaseed); + printf("shaseed: %s\n", type_to_string(tmpctx, struct sha256, &shaseed)); + for (u64 i = 0; i < depth; i++) { + if (!per_commit_secret(&shaseed, &per_commitment_secret, i)) + err(ERROR_KEYDERIV, "Could not derive secret #%"PRIu64, i); + printf("commit secret #%"PRIu64": %s\n", + i, tal_hexstr(tmpctx, per_commitment_secret.data, + sizeof(per_commitment_secret.data))); + if (!per_commit_point(&shaseed, &per_commitment_point, i)) + err(ERROR_KEYDERIV, "Could not derive point #%"PRIu64, i); + printf("commit point #%"PRIu64": %s\n", + i, type_to_string(tmpctx, struct pubkey, &per_commitment_point)); + } + + return 0; +} + int main(int argc, char *argv[]) { const char *method; @@ -210,5 +308,16 @@ int main(int argc, char *argv[]) return encrypt_hsm(argv[2], argv[3]); } + if (streq(method, "dumpcommitments")) { + /* node_id channel_id depth hsm_secret ?password? */ + if (!(argv[2] && argv[3] && argv[4] && argv[5])) + show_usage(); + struct node_id node_id; + if (!node_id_from_hexstr(argv[2], strlen(argv[2]), &node_id)) + err(ERROR_USAGE, "Bad node id"); + return dump_commitments_infos(&node_id, atol(argv[3]), atol(argv[4]), + argv[5], argv[6]); + } + show_usage(); }