Skip to content

Commit

Permalink
hsmtool: add a tool to dump commitment points and secrets
Browse files Browse the repository at this point in the history
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 <sjors@sprovoost.nl>
  • Loading branch information
darosior and Sjors committed Nov 11, 2019
1 parent c668230 commit aacb721
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 20 deletions.
2 changes: 1 addition & 1 deletion tools/Makefile
Expand Up @@ -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

Expand Down
147 changes: 128 additions & 19 deletions tools/hsmtool.c
@@ -1,26 +1,35 @@
#include <bitcoin/privkey.h>
#include <ccan/crypto/hkdf_sha256/hkdf_sha256.h>
#include <ccan/err/err.h>
#include <ccan/noerr/noerr.h>
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/tal/path/path.h>
#include <ccan/str/str.h>
#include <common/derive_basepoints.h>
#include <common/node_id.h>
#include <common/type_to_string.h>
#include <common/utils.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sodium.h>
#include <sys/stat.h>
#include <unistd.h>

#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)
{
printf("./hsmtool <method> [arguments]\n");
printf("methods:\n");
printf(" - decrypt <path/to/hsm_secret> <password>\n");
printf(" - encrypt <path/to/hsm_secret> <password>\n");
printf(" - dumpcommitments <node id> <channel dbid> <depth> "
"<path/to/hsm_secret> [hsm_secret password]\n");
exit(0);
}

Expand All @@ -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");
Expand All @@ -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,
Expand All @@ -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");
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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;
Expand All @@ -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();
}

0 comments on commit aacb721

Please sign in to comment.