hsmd: take the passphrase raw, not the derived secret.

In preparation for BIP-39, we need to hand the passphrase (if any) to HSMD.

So we extend the hsmd wire protocol to allow that.
This commit is contained in:
Sangbida Chaudhuri
2025-10-24 13:57:43 +10:30
committed by Rusty Russell
parent 3da0f16f14
commit e3fe739f64
7 changed files with 47 additions and 24 deletions

View File

@@ -461,6 +461,7 @@ static struct io_plan *init_hsm(struct io_conn *conn,
struct bip32_key_version bip32_key_version;
u32 minversion, maxversion;
const u32 our_minversion = 4, our_maxversion = 6;
struct tlv_hsmd_init_tlvs *tlvs;
/* This must be lightningd. */
assert(is_lightningd(c));
@@ -475,7 +476,7 @@ static struct io_plan *init_hsm(struct io_conn *conn,
&dev_force_bip32_seed,
&dev_force_channel_secrets,
&dev_force_channel_secrets_shaseed,
&minversion, &maxversion))
&minversion, &maxversion, &tlvs))
return bad_req(conn, c, msg_in);
/*~ Usually we don't worry about API breakage between internal daemons,
@@ -487,6 +488,25 @@ static struct io_plan *init_hsm(struct io_conn *conn,
minversion, maxversion,
our_minversion, our_maxversion);
/*~ We used to have lightningd hand us the encryption key derived from
* the passphrase which was used to encrypt the `hsm_secret` file. Then
* Rusty gave me the thankless task of introducing BIP-39 mnemonics. I
* think this is some kind of obscure CLN hazing ritual? Anyway, the
* passphrase needs to be *appended* to the mnemonic, so the HSM needs
* the raw passphrase. To avoid a compatibility break, I put it inside
* the TLV, and left the old "hsm_encryption_key" field in place, even
* though we override it here for the old-style non-BIP39 hsm_secret. */
if (tlvs->hsm_passphrase) {
const char *hsm_passphrase = (const char *)tlvs->hsm_passphrase;
const char *err_msg;
hsm_encryption_key = tal(NULL, struct secret);
if (hsm_secret_encryption_key_with_exitcode(hsm_passphrase, hsm_encryption_key, &err_msg) != 0)
return bad_req_fmt(conn, c, msg_in,
"Bad passphrase: %s", err_msg);
}
tal_free(tlvs);
/*~ The memory is actually copied in towire(), so lock the `hsm_secret`
* encryption key (new) memory again here. */
if (hsm_encryption_key && sodium_mlock(hsm_encryption_key,

View File

@@ -29,6 +29,9 @@ msgdata,hsmd_init,dev_force_channel_secrets,?secrets,
msgdata,hsmd_init,dev_force_channel_secrets_shaseed,?sha256,
msgdata,hsmd_init,hsm_wire_min_version,u32,
msgdata,hsmd_init,hsm_wire_max_version,u32,
msgdata,hsmd_init,tlvs,hsmd_init_tlvs,
tlvtype,hsmd_init_tlvs,hsm_passphrase,1
tlvdata,hsmd_init_tlvs,hsm_passphrase,passphrase,wirestring,
#include <common/bip32.h>
# Sorry: I should have put version in v2 :(
1 # Clients should not give a bad request but not the HSM's decision to crash.
29 msgdata,hsmd_init,hsm_wire_max_version,u32,
30 #include <common/bip32.h> msgdata,hsmd_init,tlvs,hsmd_init_tlvs,
31 # Sorry: I should have put version in v2 :( tlvtype,hsmd_init_tlvs,hsm_passphrase,1
32 tlvdata,hsmd_init_tlvs,hsm_passphrase,passphrase,wirestring,
33 #include <common/bip32.h>
34 # Sorry: I should have put version in v2 :(
35 msgtype,hsmd_init_reply_v4,114
36 # This gets upgraded when the wire protocol changes in incompatible
37 # ways:

View File

@@ -98,7 +98,7 @@ struct ext_key *hsm_init(struct lightningd *ld)
/* If hsm_secret is encrypted and the --encrypted-hsm startup option is
* not passed, don't let hsmd use the first 32 bytes of the cypher as the
* actual secret. */
if (!ld->config.keypass) {
if (!ld->hsm_passphrase) {
if (is_hsm_secret_encrypted("hsm_secret") == 1)
errx(EXITCODE_HSM_ERROR_IS_ENCRYPT, "hsm_secret is encrypted, you need to pass the "
"--encrypted-hsm startup option.");
@@ -122,16 +122,24 @@ struct ext_key *hsm_init(struct lightningd *ld)
err(EXITCODE_HSM_GENERIC_ERROR, "Writing preinit msg to hsm");
}
/* Create TLV for passphrase if needed */
struct tlv_hsmd_init_tlvs *tlv = NULL;
if (ld->hsm_passphrase) {
tlv = tlv_hsmd_init_tlvs_new(tmpctx);
tlv->hsm_passphrase = ld->hsm_passphrase;
}
if (!wire_sync_write(ld->hsm_fd, towire_hsmd_init(tmpctx,
&chainparams->bip32_key_version,
chainparams,
ld->config.keypass,
NULL,
ld->dev_force_privkey,
ld->dev_force_bip32_seed,
ld->dev_force_channel_secrets,
ld->dev_force_channel_secrets_shaseed,
HSM_MIN_VERSION,
HSM_MAX_VERSION)))
HSM_MAX_VERSION,
tlv)))
err(EXITCODE_HSM_GENERIC_ERROR, "Writing init msg to hsm");
bip32_base = tal(ld, struct ext_key);
@@ -143,7 +151,7 @@ struct ext_key *hsm_init(struct lightningd *ld)
&unused)) {
/* nothing to do. */
} else {
if (ld->config.keypass)
if (ld->hsm_passphrase)
errx(EXITCODE_HSM_BAD_PASSWORD, "Wrong password for encrypted hsm_secret.");
errx(EXITCODE_HSM_GENERIC_ERROR, "HSM did not give init reply");
}

View File

@@ -309,10 +309,10 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
/*~ This is set when a JSON RPC command comes in to shut us down. */
ld->stop_conn = NULL;
/*~ This is used to signal that `hsm_secret` is encrypted, and will
* be set to `true` if the `--encrypted-hsm` option is passed at startup.
/*~ This is used to store the passphrase for hsm_secret if needed.
* It will be set if the `--hsm-passphrase` option is passed at startup.
*/
ld->encrypted_hsm = false;
ld->hsm_passphrase = NULL;
/* This is used to override subdaemons */
strmap_init(&ld->alt_subdaemons);
@@ -1305,11 +1305,6 @@ int main(int argc, char *argv[])
/*~ This is the ccan/io central poll override from above. */
io_poll_override(io_poll_lightningd);
/*~ If hsm_secret is encrypted, we don't need its encryption key
* anymore. Note that sodium_munlock() also zeroes the memory.*/
if (ld->config.keypass)
discard_key(take(ld->config.keypass));
/*~ Our default color and alias are derived from our node id, so we
* can only set those now (if not set by config options). */
setup_color_and_alias(ld);

View File

@@ -69,8 +69,6 @@ struct config {
/* Minimal amount of effective funding_satoshis for accepting channels */
u64 min_capacity_sat;
/* This is the key we use to encrypt `hsm_secret`. */
struct secret *keypass;
/* How long before we give up waiting for INIT msg */
u32 connection_timeout_secs;
@@ -387,7 +385,9 @@ struct lightningd {
char *old_bookkeeper_dir;
char *old_bookkeeper_db;
bool encrypted_hsm;
/* HSM passphrase for any format that needs it */
char *hsm_passphrase;
/* What (additional) messages the HSM accepts */
u32 *hsm_capabilities;

View File

@@ -597,13 +597,7 @@ static char *opt_set_hsm_password(struct lightningd *ld)
}
prompt(ld, "");
ld->config.keypass = tal(NULL, struct secret);
opt_exitcode = hsm_secret_encryption_key_with_exitcode(passwd, ld->config.keypass, &err_msg);
if (opt_exitcode > 0)
return cast_const(char *, err_msg);
ld->encrypted_hsm = true;
ld->hsm_passphrase = tal_strdup(ld, passwd);
free(passwd);
return NULL;

View File

@@ -641,6 +641,9 @@ void tell_connectd_peer_importance(struct peer *peer UNNEEDED,
/* Generated stub for tlv_hsmd_dev_preinit_tlvs_new */
struct tlv_hsmd_dev_preinit_tlvs *tlv_hsmd_dev_preinit_tlvs_new(const tal_t *ctx UNNEEDED)
{ fprintf(stderr, "tlv_hsmd_dev_preinit_tlvs_new called!\n"); abort(); }
/* Generated stub for tlv_hsmd_init_tlvs_new */
struct tlv_hsmd_init_tlvs *tlv_hsmd_init_tlvs_new(const tal_t *ctx UNNEEDED)
{ fprintf(stderr, "tlv_hsmd_init_tlvs_new called!\n"); abort(); }
/* Generated stub for topology_add_sync_waiter_ */
void topology_add_sync_waiter_(const tal_t *ctx UNNEEDED,
struct chain_topology *topo UNNEEDED,
@@ -712,7 +715,7 @@ u8 *towire_hsmd_forget_channel(const tal_t *ctx UNNEEDED, const struct node_id *
u8 *towire_hsmd_get_output_scriptpubkey(const tal_t *ctx UNNEEDED, u64 channel_id UNNEEDED, const struct node_id *peer_id UNNEEDED, const struct pubkey *commitment_point UNNEEDED)
{ fprintf(stderr, "towire_hsmd_get_output_scriptpubkey called!\n"); abort(); }
/* Generated stub for towire_hsmd_init */
u8 *towire_hsmd_init(const tal_t *ctx UNNEEDED, const struct bip32_key_version *bip32_key_version UNNEEDED, const struct chainparams *chainparams UNNEEDED, const struct secret *hsm_encryption_key UNNEEDED, const struct privkey *dev_force_privkey UNNEEDED, const struct secret *dev_force_bip32_seed UNNEEDED, const struct secrets *dev_force_channel_secrets UNNEEDED, const struct sha256 *dev_force_channel_secrets_shaseed UNNEEDED, u32 hsm_wire_min_version UNNEEDED, u32 hsm_wire_max_version UNNEEDED)
u8 *towire_hsmd_init(const tal_t *ctx UNNEEDED, const struct bip32_key_version *bip32_key_version UNNEEDED, const struct chainparams *chainparams UNNEEDED, const struct secret *hsm_encryption_key UNNEEDED, const struct privkey *dev_force_privkey UNNEEDED, const struct secret *dev_force_bip32_seed UNNEEDED, const struct secrets *dev_force_channel_secrets UNNEEDED, const struct sha256 *dev_force_channel_secrets_shaseed UNNEEDED, u32 hsm_wire_min_version UNNEEDED, u32 hsm_wire_max_version UNNEEDED, const struct tlv_hsmd_init_tlvs *tlvs UNNEEDED)
{ fprintf(stderr, "towire_hsmd_init called!\n"); abort(); }
/* Generated stub for towire_hsmd_new_channel */
u8 *towire_hsmd_new_channel(const tal_t *ctx UNNEEDED, const struct node_id *id UNNEEDED, u64 dbid UNNEEDED)