Skip to content

Commit e93ec5d

Browse files
committed
hsmd: use the new mnemonic-compatible hsm_secret routines.
Changelog-Changed: hsmd: New nodes will now be created with a BIP-39 12-word phrase as their root secret.
1 parent 13ae5fd commit e93ec5d

File tree

10 files changed

+336
-228
lines changed

10 files changed

+336
-228
lines changed

hsmd/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ HSMD_COMMON_OBJS := \
3535
common/daemon_conn.o \
3636
common/derive_basepoints.o \
3737
common/hash_u5.o \
38-
common/hsm_encryption.o \
38+
common/hsm_secret.o \
3939
common/htlc_wire.o \
4040
common/key_derive.o \
4141
common/lease_rates.o \

hsmd/hsmd.c

Lines changed: 123 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
#include <ccan/io/fdpass/fdpass.h>
1313
#include <ccan/noerr/noerr.h>
1414
#include <ccan/read_write_all/read_write_all.h>
15+
#include <ccan/tal/grab_file/grab_file.h>
1516
#include <ccan/tal/str/str.h>
1617
#include <common/daemon_conn.h>
17-
#include <common/hsm_encryption.h>
18+
#include <common/hsm_secret.h>
1819
#include <common/memleak.h>
1920
#include <common/status.h>
2021
#include <common/status_wiregen.h>
@@ -26,6 +27,7 @@
2627
#include <hsmd/permissions.h>
2728
#include <sys/socket.h>
2829
#include <sys/stat.h>
30+
#include <wally_bip39.h>
2931
#include <wire/wire_io.h>
3032

3133
/*~ Each subdaemon is started with stdin connected to lightningd (for status
@@ -35,7 +37,7 @@
3537
#define REQ_FD 3
3638

3739
/* Temporary storage for the secret until we pass it to `hsmd_init` */
38-
struct secret hsm_secret;
40+
struct hsm_secret hsm_secret;
3941

4042
/*~ We keep track of clients, but there's not much to keep. */
4143
struct client {
@@ -270,66 +272,113 @@ static struct io_plan *req_reply(struct io_conn *conn,
270272
return io_write_wire(conn, msg_out, client_read_next, c);
271273
}
272274

273-
/*~ This encrypts the content of the `struct secret hsm_secret` and
274-
* stores it in hsm_secret, this is called instead of create_hsm() if
275-
* `lightningd` is started with --encrypted-hsm.
276-
*/
277-
static void create_encrypted_hsm(int fd, const struct secret *encryption_key)
275+
static void create_hsm(int fd, const char *passphrase)
278276
{
279-
struct encrypted_hsm_secret cipher;
280-
281-
if (!encrypt_hsm_secret(encryption_key, &hsm_secret,
282-
&cipher))
277+
u8 *hsm_secret_data;
278+
size_t hsm_secret_len;
279+
int ret;
280+
/* Always create a mnemonic-based hsm_secret */
281+
u8 entropy[BIP39_ENTROPY_LEN_128];
282+
char *mnemonic = NULL;
283+
struct sha256 seed_hash;
284+
285+
status_debug("HSM: Starting create_hsm with passphrase=%s", passphrase ? "provided" : "none");
286+
287+
/* Initialize wally tal context for libwally operations */
288+
289+
status_debug("HSM: Initialized wally tal context");
290+
291+
/* Generate random entropy for new mnemonic */
292+
randombytes_buf(entropy, sizeof(entropy));
293+
status_debug("HSM: Generated random entropy");
294+
295+
296+
/* Generate mnemonic from entropy */
297+
tal_wally_start();
298+
ret = bip39_mnemonic_from_bytes(NULL, entropy, sizeof(entropy), &mnemonic);
299+
tal_wally_end(tmpctx);
300+
301+
if (ret != WALLY_OK) {
302+
unlink_noerr("hsm_secret");
283303
status_failed(STATUS_FAIL_INTERNAL_ERROR,
284-
"Encrypting hsm_secret");
285-
if (!write_all(fd, cipher.data, ENCRYPTED_HSM_SECRET_LEN)) {
304+
"Failed to generate mnemonic from entropy");
305+
}
306+
status_debug("HSM: Generated mnemonic from entropy");
307+
308+
if (!mnemonic) {
286309
unlink_noerr("hsm_secret");
310+
//TODO: Add passphrase error message, add new codes
287311
status_failed(STATUS_FAIL_INTERNAL_ERROR,
288-
"Writing encrypted hsm_secret: %s", strerror(errno));
312+
"Failed to get generated mnemonic");
289313
}
290-
}
291-
292-
static void create_hsm(int fd)
293-
{
294-
/*~ ccan/read_write_all has a more convenient return than write() where
295-
* we'd have to check the return value == the length we gave: write()
296-
* can return short on normal files if we run out of disk space. */
297-
if (!write_all(fd, &hsm_secret, sizeof(hsm_secret))) {
298-
/* ccan/noerr contains useful routines like this, which don't
299-
* clobber errno, so we can use it in our error report. */
314+
315+
/* Derive seed hash from mnemonic + passphrase (or zero if no passphrase) */
316+
if (!derive_seed_hash(mnemonic, passphrase, &seed_hash)) {
317+
unlink_noerr("hsm_secret");
318+
status_failed(STATUS_FAIL_INTERNAL_ERROR,
319+
"Failed to derive seed hash from mnemonic");
320+
}
321+
status_debug("HSM: Derived seed hash from mnemonic");
322+
323+
/* Create hsm_secret format: seed_hash (32 bytes) + mnemonic */
324+
hsm_secret_len = PASSPHRASE_HASH_LEN + strlen(mnemonic);
325+
hsm_secret_data = tal_arr(tmpctx, u8, hsm_secret_len);
326+
327+
/* Copy seed hash first */
328+
memcpy(hsm_secret_data, &seed_hash, PASSPHRASE_HASH_LEN);
329+
/* Copy mnemonic after seed hash */
330+
memcpy(hsm_secret_data + PASSPHRASE_HASH_LEN, mnemonic, strlen(mnemonic));
331+
status_debug("HSM: Created hsm_secret data structure");
332+
333+
/* Derive the actual secret from mnemonic + passphrase for our global hsm_secret */
334+
u8 bip32_seed[BIP39_SEED_LEN_512];
335+
size_t bip32_seed_len;
336+
337+
if (bip39_mnemonic_to_seed(mnemonic, passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len) != WALLY_OK) {
338+
unlink_noerr("hsm_secret");
339+
status_failed(STATUS_FAIL_INTERNAL_ERROR,
340+
"Failed to derive seed from mnemonic");
341+
}
342+
status_debug("HSM: Derived BIP32 seed from mnemonic");
343+
344+
/* Use first 32 bytes for hsm_secret */
345+
memcpy(&hsm_secret.secret, bip32_seed, sizeof(hsm_secret.secret));
346+
347+
/* Write the hsm_secret data to file */
348+
if (!write_all(fd, hsm_secret_data, hsm_secret_len)) {
300349
unlink_noerr("hsm_secret");
301350
status_failed(STATUS_FAIL_INTERNAL_ERROR,
302351
"writing: %s", strerror(errno));
303352
}
353+
status_debug("HSM: Successfully wrote hsm_secret to file");
304354
}
305355

306356
/*~ We store our root secret in a "hsm_secret" file (like all of Core Lightning,
307-
* we run in the user's .lightning directory). */
308-
static void maybe_create_new_hsm(const struct secret *encryption_key,
309-
bool random_hsm)
357+
* we run in the user's .lightning directory).
358+
*
359+
* NOTE: This function no longer creates encrypted 32-byte secrets. New hsm_secret
360+
* files will use mnemonic format with passphrases.
361+
*/
362+
static void maybe_create_new_hsm(const char *passphrase)
310363
{
311364
/*~ Note that this is opened for write-only, even though the permissions
312365
* are set to read-only. That's perfectly valid! */
313366
int fd = open("hsm_secret", O_CREAT|O_EXCL|O_WRONLY, 0400);
314367
if (fd < 0) {
315368
/* If this is not the first time we've run, it will exist. */
316-
if (errno == EEXIST)
369+
if (errno == EEXIST) {
370+
status_debug("HSM: hsm_secret file already exists, skipping creation");
317371
return;
372+
}
318373
status_failed(STATUS_FAIL_INTERNAL_ERROR,
319374
"creating: %s", strerror(errno));
320375
}
321376

322-
/*~ This is libsodium's cryptographic randomness routine: we assume
323-
* it's doing a good job. */
324-
if (random_hsm)
325-
randombytes_buf(&hsm_secret, sizeof(hsm_secret));
326-
327-
/*~ If an encryption_key was provided, store an encrypted seed. */
328-
if (encryption_key)
329-
create_encrypted_hsm(fd, encryption_key);
330-
/*~ Otherwise store the seed in clear.. */
331-
else
332-
create_hsm(fd);
377+
status_debug("HSM: Creating new hsm_secret file");
378+
379+
/*~ Store the seed in clear. New hsm_secret files will use mnemonic format
380+
* with passphrases, not encrypted 32-byte secrets. */
381+
create_hsm(fd, passphrase);
333382
/*~ fsync (mostly!) ensures that the file has reached the disk. */
334383
if (fsync(fd) != 0) {
335384
unlink_noerr("hsm_secret");
@@ -367,62 +416,35 @@ static void maybe_create_new_hsm(const struct secret *encryption_key,
367416
/*~ We always load the HSM file, even if we just created it above. This
368417
* both unifies the code paths, and provides a nice sanity check that the
369418
* file contents are as they will be for future invocations. */
370-
static void load_hsm(const struct secret *encryption_key)
419+
static void load_hsm(const char *passphrase)
371420
{
372-
struct stat st;
373-
int fd = open("hsm_secret", O_RDONLY);
374-
if (fd < 0)
375-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
376-
"opening: %s", strerror(errno));
377-
if (stat("hsm_secret", &st) != 0)
378-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
379-
"stating: %s", strerror(errno));
421+
u8 *hsm_secret_contents;
422+
struct hsm_secret *hsms;
423+
enum hsm_secret_error err;
380424

381-
/* If the seed is stored in clear. */
382-
if (st.st_size == 32) {
383-
if (!read_all(fd, &hsm_secret, sizeof(hsm_secret)))
384-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
385-
"reading: %s", strerror(errno));
386-
/* If an encryption key was passed with a not yet encrypted hsm_secret,
387-
* remove the old one and create an encrypted one. */
388-
if (encryption_key) {
389-
if (close(fd) != 0)
390-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
391-
"closing: %s", strerror(errno));
392-
if (remove("hsm_secret") != 0)
393-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
394-
"removing clear hsm_secret: %s", strerror(errno));
395-
maybe_create_new_hsm(encryption_key, false);
396-
fd = open("hsm_secret", O_RDONLY);
397-
if (fd < 0)
398-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
399-
"opening: %s", strerror(errno));
400-
}
425+
/* Read the hsm_secret file */
426+
hsm_secret_contents = grab_file(tmpctx, "hsm_secret");
427+
if (!hsm_secret_contents) {
428+
status_failed(STATUS_FAIL_INTERNAL_ERROR,
429+
"Could not read hsm_secret: %s", strerror(errno));
401430
}
402-
/* If an encryption key was passed and the `hsm_secret` is stored
403-
* encrypted, recover the seed from the cipher. */
404-
else if (st.st_size == ENCRYPTED_HSM_SECRET_LEN) {
405-
struct encrypted_hsm_secret encrypted_secret;
406431

407-
/* hsm_control must have checked it! */
408-
assert(encryption_key);
432+
/* Remove the NUL terminator that grab_file adds */
433+
tal_resize(&hsm_secret_contents, tal_bytelen(hsm_secret_contents) - 1);
409434

410-
if (!read_all(fd, encrypted_secret.data, ENCRYPTED_HSM_SECRET_LEN))
411-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
412-
"Reading encrypted hsm_secret: %s", strerror(errno));
413-
if (!decrypt_hsm_secret(encryption_key, &encrypted_secret,
414-
&hsm_secret)) {
415-
/* Exit but don't throw a backtrace when the user made a mistake in typing
416-
* its password. Instead exit and `lightningd` will be able to give
417-
* an error message. */
418-
exit(1);
419-
}
435+
/* Extract the secret using the new hsm_secret module */
436+
tal_wally_start();
437+
hsms = extract_hsm_secret(tmpctx, hsm_secret_contents,
438+
tal_bytelen(hsm_secret_contents),
439+
passphrase, &err);
440+
tal_wally_end(tmpctx);
441+
if (!hsms) {
442+
status_failed(STATUS_FAIL_INTERNAL_ERROR,
443+
"Failed to load hsm_secret: %s", hsm_secret_error_str(err));
420444
}
421-
else
422-
status_failed(STATUS_FAIL_INTERNAL_ERROR, "Invalid hsm_secret, "
423-
"no plaintext nor encrypted"
424-
" seed.");
425-
close(fd);
445+
446+
/* Copy the extracted secret to our global hsm_secret */
447+
memcpy(&hsm_secret, &hsms->secret, sizeof(hsm_secret));
426448
}
427449

428450
/*~ We have a pre-init call in developer mode, to set dev flags */
@@ -458,6 +480,7 @@ static struct io_plan *init_hsm(struct io_conn *conn,
458480
const u8 *msg_in)
459481
{
460482
struct secret *hsm_encryption_key;
483+
const char *hsm_passphrase;
461484
struct bip32_key_version bip32_key_version;
462485
u32 minversion, maxversion;
463486
const u32 our_minversion = 4, our_maxversion = 6;
@@ -494,27 +517,13 @@ static struct io_plan *init_hsm(struct io_conn *conn,
494517
* think this is some kind of obscure CLN hazing ritual? Anyway, the
495518
* passphrase needs to be *appended* to the mnemonic, so the HSM needs
496519
* the raw passphrase. To avoid a compatibility break, I put it inside
497-
* the TLV, and left the old "hsm_encryption_key" field in place, even
498-
* though we override it here for the old-style non-BIP39 hsm_secret. */
499-
if (tlvs->hsm_passphrase) {
500-
const char *hsm_passphrase = (const char *)tlvs->hsm_passphrase;
501-
const char *err_msg;
502-
503-
hsm_encryption_key = tal(NULL, struct secret);
504-
if (hsm_secret_encryption_key_with_exitcode(hsm_passphrase, hsm_encryption_key, &err_msg) != 0)
505-
return bad_req_fmt(conn, c, msg_in,
506-
"Bad passphrase: %s", err_msg);
507-
}
508-
tal_free(tlvs);
520+
* the TLV, and left the old "hsm_encryption_key" field in place (and lightningd
521+
* never sets that anymore), and we use the TLV instead. */
522+
if (tlvs->hsm_passphrase)
523+
hsm_passphrase = (const char *)tlvs->hsm_passphrase;
509524

510-
/*~ The memory is actually copied in towire(), so lock the `hsm_secret`
511-
* encryption key (new) memory again here. */
512-
if (hsm_encryption_key && sodium_mlock(hsm_encryption_key,
513-
sizeof(hsm_encryption_key)) != 0)
514-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
515-
"Could not lock memory for hsm_secret encryption key.");
516525
/*~ Don't swap this. */
517-
sodium_mlock(hsm_secret.data, sizeof(hsm_secret.data));
526+
sodium_mlock(hsm_secret.secret.data, sizeof(hsm_secret.secret.data));
518527

519528
if (!developer) {
520529
assert(!dev_force_privkey);
@@ -526,16 +535,15 @@ static struct io_plan *init_hsm(struct io_conn *conn,
526535
/* Once we have read the init message we know which params the master
527536
* will use */
528537
c->chainparams = chainparams;
529-
maybe_create_new_hsm(hsm_encryption_key, true);
530-
load_hsm(hsm_encryption_key);
531-
532-
/*~ We don't need the hsm_secret encryption key anymore. */
533-
if (hsm_encryption_key)
534-
discard_key(take(hsm_encryption_key));
538+
maybe_create_new_hsm(hsm_passphrase);
539+
load_hsm(hsm_passphrase);
535540

536541
/* Define the minimum common max version for the hsmd one */
537542
hsmd_mutual_version = maxversion < our_maxversion ? maxversion : our_maxversion;
538-
return req_reply(conn, c, hsmd_init(hsm_secret, hsmd_mutual_version,
543+
544+
/* This was tallocated off NULL, and memleak complains if we don't free it */
545+
tal_free(tlvs);
546+
return req_reply(conn, c, hsmd_init(hsm_secret.secret, hsmd_mutual_version,
539547
bip32_key_version));
540548
}
541549

lightningd/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ LIGHTNINGD_COMMON_OBJS := \
111111
common/hash_u5.o \
112112
common/hmac.o \
113113
common/hsm_capable.o \
114-
common/hsm_encryption.o \
114+
common/hsm_secret.o \
115115
common/htlc_state.o \
116116
common/htlc_trim.o \
117117
common/htlc_tx.o \

lightningd/hsm_control.c

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#include "config.h"
22
#include <ccan/err/err.h>
33
#include <ccan/fdpass/fdpass.h>
4+
#include <ccan/tal/str/str.h>
45
#include <common/bolt12_id.h>
56
#include <common/ecdh.h>
67
#include <common/errcode.h>
78
#include <common/hsm_capable.h>
8-
#include <common/hsm_encryption.h>
9+
#include <common/hsm_secret.h>
910
#include <common/hsm_version.h>
1011
#include <common/json_command.h>
1112
#include <common/json_param.h>
@@ -100,15 +101,6 @@ struct ext_key *hsm_init(struct lightningd *ld)
100101
if (!ld->hsm)
101102
err(EXITCODE_HSM_GENERIC_ERROR, "Could not subd hsm");
102103

103-
/* If hsm_secret is encrypted and the --encrypted-hsm startup option is
104-
* not passed, don't let hsmd use the first 32 bytes of the cypher as the
105-
* actual secret. */
106-
if (!ld->hsm_passphrase) {
107-
if (is_hsm_secret_encrypted("hsm_secret") == 1)
108-
errx(EXITCODE_HSM_ERROR_IS_ENCRYPT, "hsm_secret is encrypted, you need to pass the "
109-
"--encrypted-hsm startup option.");
110-
}
111-
112104
ld->hsm_fd = fds[0];
113105

114106
if (ld->developer) {
@@ -131,7 +123,7 @@ struct ext_key *hsm_init(struct lightningd *ld)
131123
struct tlv_hsmd_init_tlvs *tlv = NULL;
132124
if (ld->hsm_passphrase) {
133125
tlv = tlv_hsmd_init_tlvs_new(tmpctx);
134-
tlv->hsm_passphrase = ld->hsm_passphrase;
126+
tlv->hsm_passphrase = tal_strdup(tlv, ld->hsm_passphrase);
135127
}
136128

137129
if (!wire_sync_write(ld->hsm_fd, towire_hsmd_init(tmpctx,
@@ -157,7 +149,7 @@ struct ext_key *hsm_init(struct lightningd *ld)
157149
/* nothing to do. */
158150
} else {
159151
if (ld->hsm_passphrase)
160-
errx(EXITCODE_HSM_BAD_PASSWORD, "Wrong password for encrypted hsm_secret.");
152+
errx(EXITCODE_HSM_BAD_PASSWORD, "Wrong passphrase for hsm_secret.");
161153
errx(EXITCODE_HSM_GENERIC_ERROR, "HSM did not give init reply");
162154
}
163155

0 commit comments

Comments
 (0)