hsmtool: implement checkhsm.

This gives a nice way to ensure your secret is the correct one.

Also, we don't need to suppress VALGRIND for this test, now the output
races are fixed.

Changelog-Added: `hsmtool`: new command `checkhsm` to check BIP39 passphrase against hsm_secret.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2022-07-20 12:00:25 +09:30 committed by Christian Decker
parent c10e385612
commit 8c38302ab8
3 changed files with 104 additions and 2 deletions

View File

@ -51,6 +51,9 @@ Specify *password* if the `hsm_secret` is encrypted.
**generatehsm** *hsm\_secret\_path*
Generates a new hsm_secret using BIP39.
**checkhsm** *hsm\_secret\_path*
Checks that hsm_secret matchs a BIP39 pass phrase.
**dumponchaindescriptors** *hsm_secret* \[*password*\] \[*network*\]
Dump output descriptors for our onchain wallet.
The descriptors can be used by external services to be able to generate

View File

@ -1218,7 +1218,6 @@ def test_hsmtool_dump_descriptors(node_factory, bitcoind):
assert len(bitcoind.rpc.listunspent(1, 1, [addr])) == 1
@unittest.skipIf(VALGRIND, "It does not play well with prompt and key derivation.")
def test_hsmtool_generatehsm(node_factory):
l1 = node_factory.get_node(start=False)
hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK,
@ -1242,9 +1241,48 @@ def test_hsmtool_generatehsm(node_factory):
"cake have wedding\n".encode("utf-8"))
hsmtool.wait_for_log(r"Enter your passphrase:")
write_all(master_fd, "This is actually not a passphrase\n".encode("utf-8"))
hsmtool.proc.wait(WAIT_TIMEOUT)
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
hsmtool.is_in_log(r"New hsm_secret file created")
# Check should pass.
hsmtool = HsmTool(node_factory.directory, "checkhsm", hsm_path)
master_fd, slave_fd = os.openpty()
hsmtool.start(stdin=slave_fd)
hsmtool.wait_for_log(r"Enter your passphrase:")
write_all(master_fd, "This is actually not a passphrase\n".encode("utf-8"))
hsmtool.wait_for_log(r"Select your language:")
write_all(master_fd, "0\n".encode("utf-8"))
hsmtool.wait_for_log(r"Introduce your BIP39 word list")
write_all(master_fd, "ritual idle hat sunny universe pluck key alpha wing "
"cake have wedding\n".encode("utf-8"))
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
hsmtool.is_in_log(r"OK")
# Wrong mnemonic will fail.
master_fd, slave_fd = os.openpty()
hsmtool.start(stdin=slave_fd)
hsmtool.wait_for_log(r"Enter your passphrase:")
write_all(master_fd, "This is actually not a passphrase\n".encode("utf-8"))
hsmtool.wait_for_log(r"Select your language:")
write_all(master_fd, "0\n".encode("utf-8"))
hsmtool.wait_for_log(r"Introduce your BIP39 word list")
write_all(master_fd, "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\n".encode("utf-8"))
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 5
hsmtool.is_in_log(r"resulting hsm_secret did not match")
# Wrong passphrase will fail.
master_fd, slave_fd = os.openpty()
hsmtool.start(stdin=slave_fd)
hsmtool.wait_for_log(r"Enter your passphrase:")
write_all(master_fd, "This is actually not a passphrase \n".encode("utf-8"))
hsmtool.wait_for_log(r"Select your language:")
write_all(master_fd, "0\n".encode("utf-8"))
hsmtool.wait_for_log(r"Introduce your BIP39 word list")
write_all(master_fd, "ritual idle hat sunny universe pluck key alpha wing "
"cake have wedding\n".encode("utf-8"))
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 5
hsmtool.is_in_log(r"resulting hsm_secret did not match")
# We can start the node with this hsm_secret
l1.start()
assert l1.info['id'] == '02244b73339edd004bc6dfbb953a87984c88e9e7c02ca14ef6ec593ca6be622ba7'

View File

@ -39,6 +39,7 @@ static void show_usage(const char *progname)
printf(" - guesstoremote <P2WPKH address> <node id> <tries> "
"<path/to/hsm_secret>\n");
printf(" - generatehsm <path/to/new/hsm_secret>\n");
printf(" - checkhsm <path/to/new/hsm_secret>\n");
printf(" - dumponchaindescriptors <path/to/hsm_secret> [network]\n");
exit(0);
}
@ -595,6 +596,60 @@ static int dumponchaindescriptors(const char *hsm_secret_path, const char *old_p
return 0;
}
static int check_hsm(const char *hsm_secret_path)
{
char mnemonic[BIP39_WORDLIST_LEN];
struct secret hsm_secret;
u8 bip32_seed[BIP39_SEED_LEN_512];
size_t bip32_seed_len;
int exit_code;
char *passphrase, *err;
/* This checks the file existence, too. */
if (hsm_secret_is_encrypted(hsm_secret_path)) {
char *passwd;
printf("Enter hsm_secret password:\n");
fflush(stdout);
passwd = read_stdin_pass_with_exit_code(&err, &exit_code);
if (!passwd)
errx(exit_code, "%s", err);
if (sodium_init() == -1)
errx(ERROR_LIBSODIUM,
"Could not initialize libsodium. Not enough entropy ?");
get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd);
/* Once the encryption key derived, we don't need it anymore. */
free(passwd);
} else
get_hsm_secret(&hsm_secret, hsm_secret_path);
printf("Warning: remember that different passphrases yield different "
"bitcoin wallets.\n");
printf("If left empty, no password is used (echo is disabled).\n");
printf("Enter your passphrase: \n");
fflush(stdout);
passphrase = read_stdin_pass_with_exit_code(&err, &exit_code);
if (!passphrase)
errx(exit_code, "%s", err);
if (strlen(passphrase) == 0) {
free(passphrase);
passphrase = NULL;
}
read_mnemonic(mnemonic);
if (bip39_mnemonic_to_seed(mnemonic, passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len) != WALLY_OK)
errx(ERROR_LIBWALLY, "Unable to derive BIP32 seed from BIP39 mnemonic");
/* We only use first 32 bytes */
if (memcmp(bip32_seed, hsm_secret.data, sizeof(hsm_secret.data)) != 0)
errx(ERROR_KEYDERIV, "resulting hsm_secret did not match");
printf("OK\n");
return 0;
}
int main(int argc, char *argv[])
{
const char *method;
@ -682,5 +737,11 @@ int main(int argc, char *argv[])
return dumponchaindescriptors(argv[2], NULL, is_testnet);
}
if (streq(method, "checkhsm")) {
if (argc < 3)
show_usage(argv[0]);
return check_hsm(argv[2]);
}
show_usage(argv[0]);
}