Skip to content

Commit f6030aa

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 45467c1 commit f6030aa

File tree

12 files changed

+364
-218
lines changed

12 files changed

+364
-218
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: 153 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,23 @@
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>
2122
#include <common/subdaemon.h>
2223
#include <errno.h>
2324
#include <fcntl.h>
25+
#include <stdarg.h>
2426
/*~ _wiregen files are autogenerated by tools/generate-wire.py */
2527
#include <hsmd/libhsmd.h>
2628
#include <hsmd/permissions.h>
2729
#include <sys/socket.h>
2830
#include <sys/stat.h>
31+
#include <wally_bip39.h>
2932
#include <wire/wire_io.h>
3033

3134
/*~ Each subdaemon is started with stdin connected to lightningd (for status
@@ -35,7 +38,7 @@
3538
#define REQ_FD 3
3639

3740
/* Temporary storage for the secret until we pass it to `hsmd_init` */
38-
struct secret hsm_secret;
41+
struct hsm_secret hsm_secret;
3942

4043
/*~ We keep track of clients, but there's not much to keep. */
4144
struct client {
@@ -270,66 +273,138 @@ static struct io_plan *req_reply(struct io_conn *conn,
270273
return io_write_wire(conn, msg_out, client_read_next, c);
271274
}
272275

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)
276+
/* Send an init reply failure message to lightningd and then call status_failed */
277+
static void hsmd_send_init_reply_failure(enum hsm_secret_error error_code, enum status_failreason reason, const char *error_msg, ...)
278278
{
279-
struct encrypted_hsm_secret cipher;
280-
281-
if (!encrypt_hsm_secret(encryption_key, &hsm_secret,
282-
&cipher))
283-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
284-
"Encrypting hsm_secret");
285-
if (!write_all(fd, cipher.data, ENCRYPTED_HSM_SECRET_LEN)) {
286-
unlink_noerr("hsm_secret");
287-
status_failed(STATUS_FAIL_INTERNAL_ERROR,
288-
"Writing encrypted hsm_secret: %s", strerror(errno));
279+
u8 *msg;
280+
va_list ap;
281+
char *formatted_msg;
282+
283+
va_start(ap, error_msg);
284+
formatted_msg = tal_vfmt(tmpctx, error_msg, ap);
285+
va_end(ap);
286+
287+
/* Send the init reply failure first */
288+
msg = towire_hsmd_init_reply_failure(NULL, error_code, formatted_msg);
289+
if (msg) {
290+
/* Send directly to lightningd via REQ_FD */
291+
write_all(REQ_FD, msg, tal_bytelen(msg));
292+
tal_free(msg);
289293
}
294+
295+
/* Then call status_failed with the error message */
296+
status_failed(reason, "%s", formatted_msg);
290297
}
291298

292-
static void create_hsm(int fd)
299+
static void create_hsm(int fd, const char *passphrase)
293300
{
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. */
301+
u8 *hsm_secret_data;
302+
size_t hsm_secret_len;
303+
int ret;
304+
/* Always create a mnemonic-based hsm_secret */
305+
u8 entropy[BIP39_ENTROPY_LEN_128];
306+
char *mnemonic = NULL;
307+
struct sha256 seed_hash;
308+
309+
status_debug("HSM: Starting create_hsm with passphrase=%s", passphrase ? "provided" : "none");
310+
311+
/* Initialize wally tal context for libwally operations */
312+
313+
status_debug("HSM: Initialized wally tal context");
314+
315+
/* Generate random entropy for new mnemonic */
316+
randombytes_buf(entropy, sizeof(entropy));
317+
status_debug("HSM: Generated random entropy");
318+
319+
320+
/* Generate mnemonic from entropy */
321+
tal_wally_start();
322+
ret = bip39_mnemonic_from_bytes(NULL, entropy, sizeof(entropy), &mnemonic);
323+
tal_wally_end(tmpctx);
324+
325+
if (ret != WALLY_OK) {
326+
unlink_noerr("hsm_secret");
327+
hsmd_send_init_reply_failure(HSM_SECRET_ERR_SEED_DERIVATION_FAILED, STATUS_FAIL_INTERNAL_ERROR,
328+
"Failed to generate mnemonic from entropy");
329+
}
330+
status_debug("HSM: Generated mnemonic from entropy");
331+
332+
if (!mnemonic) {
333+
unlink_noerr("hsm_secret");
334+
hsmd_send_init_reply_failure(HSM_SECRET_ERR_SEED_DERIVATION_FAILED, STATUS_FAIL_INTERNAL_ERROR,
335+
"Failed to get generated mnemonic");
336+
}
337+
338+
/* Derive seed hash from mnemonic + passphrase (or zero if no passphrase) */
339+
if (!derive_seed_hash(mnemonic, passphrase, &seed_hash)) {
340+
unlink_noerr("hsm_secret");
341+
hsmd_send_init_reply_failure(HSM_SECRET_ERR_SEED_DERIVATION_FAILED, STATUS_FAIL_INTERNAL_ERROR,
342+
"Failed to derive seed hash from mnemonic");
343+
}
344+
status_debug("HSM: Derived seed hash from mnemonic");
345+
346+
/* Create hsm_secret format: seed_hash (32 bytes) + mnemonic */
347+
hsm_secret_len = PASSPHRASE_HASH_LEN + strlen(mnemonic);
348+
hsm_secret_data = tal_arr(tmpctx, u8, hsm_secret_len);
349+
350+
/* Copy seed hash first */
351+
memcpy(hsm_secret_data, &seed_hash, PASSPHRASE_HASH_LEN);
352+
/* Copy mnemonic after seed hash */
353+
memcpy(hsm_secret_data + PASSPHRASE_HASH_LEN, mnemonic, strlen(mnemonic));
354+
status_debug("HSM: Created hsm_secret data structure");
355+
356+
/* Derive the actual secret from mnemonic + passphrase for our global hsm_secret */
357+
u8 bip32_seed[BIP39_SEED_LEN_512];
358+
size_t bip32_seed_len;
359+
360+
tal_wally_start();
361+
ret = bip39_mnemonic_to_seed(mnemonic, passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len);
362+
tal_wally_end(tmpctx);
363+
if (ret != WALLY_OK) {
364+
unlink_noerr("hsm_secret");
365+
hsmd_send_init_reply_failure(HSM_SECRET_ERR_SEED_DERIVATION_FAILED, STATUS_FAIL_INTERNAL_ERROR,
366+
"Failed to derive seed from mnemonic");
367+
}
368+
status_debug("HSM: Derived BIP32 seed from mnemonic");
369+
370+
/* Use first 32 bytes for hsm_secret */
371+
memcpy(&hsm_secret.secret, bip32_seed, sizeof(hsm_secret.secret));
372+
373+
/* Write the hsm_secret data to file */
374+
if (!write_all(fd, hsm_secret_data, hsm_secret_len)) {
300375
unlink_noerr("hsm_secret");
301376
status_failed(STATUS_FAIL_INTERNAL_ERROR,
302377
"writing: %s", strerror(errno));
303378
}
379+
status_debug("HSM: Successfully wrote hsm_secret to file");
304380
}
305381

306382
/*~ 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)
383+
* we run in the user's .lightning directory).
384+
*
385+
* NOTE: This function no longer creates encrypted 32-byte secrets. New hsm_secret
386+
* files will use mnemonic format with passphrases.
387+
*/
388+
static void maybe_create_new_hsm(const char *passphrase)
310389
{
311390
/*~ Note that this is opened for write-only, even though the permissions
312391
* are set to read-only. That's perfectly valid! */
313392
int fd = open("hsm_secret", O_CREAT|O_EXCL|O_WRONLY, 0400);
314393
if (fd < 0) {
315394
/* If this is not the first time we've run, it will exist. */
316-
if (errno == EEXIST)
395+
if (errno == EEXIST) {
396+
status_debug("HSM: hsm_secret file already exists, skipping creation");
317397
return;
398+
}
318399
status_failed(STATUS_FAIL_INTERNAL_ERROR,
319400
"creating: %s", strerror(errno));
320401
}
321402

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);
403+
status_debug("HSM: Creating new hsm_secret file");
404+
405+
/*~ Store the seed in clear. New hsm_secret files will use mnemonic format
406+
* with passphrases, not encrypted 32-byte secrets. */
407+
create_hsm(fd, passphrase);
333408
/*~ fsync (mostly!) ensures that the file has reached the disk. */
334409
if (fsync(fd) != 0) {
335410
unlink_noerr("hsm_secret");
@@ -367,62 +442,36 @@ static void maybe_create_new_hsm(const struct secret *encryption_key,
367442
/*~ We always load the HSM file, even if we just created it above. This
368443
* both unifies the code paths, and provides a nice sanity check that the
369444
* file contents are as they will be for future invocations. */
370-
static void load_hsm(const struct secret *encryption_key)
445+
static void load_hsm(const char *passphrase)
371446
{
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));
380-
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-
}
447+
u8 *hsm_secret_contents;
448+
struct hsm_secret *hsms;
449+
enum hsm_secret_error err;
450+
451+
/* Read the hsm_secret file */
452+
hsm_secret_contents = grab_file(tmpctx, "hsm_secret");
453+
if (!hsm_secret_contents) {
454+
hsmd_send_init_reply_failure(HSM_SECRET_ERR_INVALID_FORMAT, STATUS_FAIL_INTERNAL_ERROR,
455+
"Could not read hsm_secret: %s", strerror(errno));
401456
}
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;
406457

407-
/* hsm_control must have checked it! */
408-
assert(encryption_key);
409-
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-
}
458+
/* Remove the NUL terminator that grab_file adds */
459+
tal_resize(&hsm_secret_contents, tal_bytelen(hsm_secret_contents) - 1);
460+
461+
/* Extract the secret using the new hsm_secret module */
462+
tal_wally_start();
463+
hsms = extract_hsm_secret(tmpctx, hsm_secret_contents,
464+
tal_bytelen(hsm_secret_contents),
465+
passphrase, &err);
466+
tal_wally_end(tmpctx);
467+
if (!hsms) {
468+
status_debug("HSM: Failed to load hsm_secret: %s", hsm_secret_error_str(err));
469+
hsmd_send_init_reply_failure(err, STATUS_FAIL_INTERNAL_ERROR,
470+
"Failed to load hsm_secret: %s", hsm_secret_error_str(err));
420471
}
421-
else
422-
status_failed(STATUS_FAIL_INTERNAL_ERROR, "Invalid hsm_secret, "
423-
"no plaintext nor encrypted"
424-
" seed.");
425-
close(fd);
472+
473+
/* Copy the extracted secret to our global hsm_secret */
474+
memcpy(&hsm_secret, &hsms->secret, sizeof(hsm_secret));
426475
}
427476

428477
/*~ We have a pre-init call in developer mode, to set dev flags */
@@ -458,6 +507,7 @@ static struct io_plan *init_hsm(struct io_conn *conn,
458507
const u8 *msg_in)
459508
{
460509
struct secret *hsm_encryption_key;
510+
const char *hsm_passphrase;
461511
struct bip32_key_version bip32_key_version;
462512
u32 minversion, maxversion;
463513
const u32 our_minversion = 4, our_maxversion = 6;
@@ -494,27 +544,13 @@ static struct io_plan *init_hsm(struct io_conn *conn,
494544
* think this is some kind of obscure CLN hazing ritual? Anyway, the
495545
* passphrase needs to be *appended* to the mnemonic, so the HSM needs
496546
* 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);
547+
* the TLV, and left the old "hsm_encryption_key" field in place (and lightningd
548+
* never sets that anymore), and we use the TLV instead. */
549+
if (tlvs->hsm_passphrase)
550+
hsm_passphrase = (const char *)tlvs->hsm_passphrase;
509551

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.");
516552
/*~ Don't swap this. */
517-
sodium_mlock(hsm_secret.data, sizeof(hsm_secret.data));
553+
sodium_mlock(hsm_secret.secret.data, sizeof(hsm_secret.secret.data));
518554

519555
if (!developer) {
520556
assert(!dev_force_privkey);
@@ -526,16 +562,15 @@ static struct io_plan *init_hsm(struct io_conn *conn,
526562
/* Once we have read the init message we know which params the master
527563
* will use */
528564
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));
565+
maybe_create_new_hsm(hsm_passphrase);
566+
load_hsm(hsm_passphrase);
535567

536568
/* Define the minimum common max version for the hsmd one */
537569
hsmd_mutual_version = maxversion < our_maxversion ? maxversion : our_maxversion;
538-
return req_reply(conn, c, hsmd_init(hsm_secret, hsmd_mutual_version,
570+
571+
/* This was tallocated off NULL, and memleak complains if we don't free it */
572+
tal_free(tlvs);
573+
return req_reply(conn, c, hsmd_init(hsm_secret.secret, hsmd_mutual_version,
539574
bip32_key_version));
540575
}
541576

@@ -756,6 +791,7 @@ static struct io_plan *handle_client(struct io_conn *conn, struct client *c)
756791
case WIRE_HSMD_SIGN_WITHDRAWAL_REPLY:
757792
case WIRE_HSMD_SIGN_INVOICE_REPLY:
758793
case WIRE_HSMD_INIT_REPLY_V4:
794+
case WIRE_HSMD_INIT_REPLY_FAILURE:
759795
case WIRE_HSMD_DERIVE_SECRET_REPLY:
760796
case WIRE_HSMSTATUS_CLIENT_BAD_REQUEST:
761797
case WIRE_HSMD_SIGN_COMMITMENT_TX_REPLY:

hsmd/hsmd_wire.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ msgdata,hsmd_init_reply_v4,node_id,node_id,
4747
msgdata,hsmd_init_reply_v4,bip32,ext_key,
4848
msgdata,hsmd_init_reply_v4,bolt12,pubkey,
4949

50+
# HSM initialization failure response
51+
msgtype,hsmd_init_reply_failure,115
52+
msgdata,hsmd_init_reply_failure,error_code,u32,
53+
msgdata,hsmd_init_reply_failure,error_message,wirestring,
54+
5055
# Declare a new channel.
5156
msgtype,hsmd_new_channel,30
5257
msgdata,hsmd_new_channel,id,node_id,

0 commit comments

Comments
 (0)