lightningd: support hsm error code

Suggested-by: Rusty Russell
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Changelog-Changed: Support hsm specific error error code in lightning-cli
This commit is contained in:
Vincenzo Palazzo 2021-12-14 22:57:21 +01:00 committed by Rusty Russell
parent 9a85b02c6f
commit 43ff949ea7
7 changed files with 109 additions and 59 deletions

View File

@ -9,4 +9,10 @@ typedef s32 errcode_t;
#define PRIerrcode PRId32
// HSM errors code
#define HSM_GENERIC_ERROR 20
#define HSM_ERROR_IS_ENCRYPT 21
#define HSM_BAD_PASSWORD 22
#define HSM_PASSWORD_INPUT_ERR 23
#endif /* LIGHTNING_COMMON_ERRCODE_H */

View File

@ -1,21 +1,28 @@
#include "config.h"
#include <common/errcode.h>
#include <common/hsm_encryption.h>
#include <termios.h>
#include <unistd.h>
char *hsm_secret_encryption_key(const char *pass, struct secret *key)
int hsm_secret_encryption_key_with_exitcode(const char *pass, struct secret *key,
char **err_msg)
{
u8 salt[16] = "c-lightning\0\0\0\0\0";
/* Don't swap the encryption key ! */
if (sodium_mlock(key->data, sizeof(key->data)) != 0)
return "Could not lock hsm_secret encryption key memory.";
if (sodium_mlock(key->data, sizeof(key->data)) != 0) {
*err_msg = "Could not lock hsm_secret encryption key memory.";
return HSM_GENERIC_ERROR;
}
/* Check bounds. */
if (strlen(pass) < crypto_pwhash_argon2id_PASSWD_MIN)
return "Password too short to be able to derive a key from it.";
if (strlen(pass) > crypto_pwhash_argon2id_PASSWD_MAX)
return "Password too long to be able to derive a key from it.";
if (strlen(pass) < crypto_pwhash_argon2id_PASSWD_MIN) {
*err_msg = "Password too short to be able to derive a key from it.";
return HSM_BAD_PASSWORD;
} else if (strlen(pass) > crypto_pwhash_argon2id_PASSWD_MAX) {
*err_msg = "Password too long to be able to derive a key from it.";
return HSM_BAD_PASSWORD;
}
/* Now derive the key. */
if (crypto_pwhash(key->data, sizeof(key->data), pass, strlen(pass), salt,
@ -23,10 +30,12 @@ char *hsm_secret_encryption_key(const char *pass, struct secret *key)
* and SENSITIVE needs 1024. */
crypto_pwhash_argon2id_OPSLIMIT_MODERATE,
crypto_pwhash_argon2id_MEMLIMIT_MODERATE,
crypto_pwhash_ALG_ARGON2ID13) != 0)
return "Could not derive a key from the password.";
crypto_pwhash_ALG_ARGON2ID13) != 0) {
*err_msg = "Could not derive a key from the password.";
return HSM_BAD_PASSWORD;
}
return NULL;
return 0;
}
bool encrypt_hsm_secret(const struct secret *encryption_key,
@ -90,7 +99,7 @@ static bool getline_stdin_pass(char **passwd, size_t *passwd_size)
return true;
}
char *read_stdin_pass(char **reason)
char *read_stdin_pass_with_exit_code(char **reason, int *exit_code)
{
struct termios current_term, temp_term;
char *passwd = NULL;
@ -100,17 +109,20 @@ char *read_stdin_pass(char **reason)
/* Set a temporary term, same as current but with ECHO disabled. */
if (tcgetattr(fileno(stdin), &current_term) != 0) {
*reason = "Could not get current terminal options.";
*exit_code = HSM_PASSWORD_INPUT_ERR;
return NULL;
}
temp_term = current_term;
temp_term.c_lflag &= ~ECHO;
if (tcsetattr(fileno(stdin), TCSANOW, &temp_term) != 0) {
*reason = "Could not disable pass echoing.";
*exit_code = HSM_PASSWORD_INPUT_ERR;
return NULL;
}
if (!getline_stdin_pass(&passwd, &passwd_size)) {
*reason = "Could not read pass from stdin.";
*exit_code = HSM_PASSWORD_INPUT_ERR;
return NULL;
}
@ -118,12 +130,13 @@ char *read_stdin_pass(char **reason)
if (tcsetattr(fileno(stdin), TCSANOW, &current_term) != 0) {
*reason = "Could not restore terminal options.";
free(passwd);
*exit_code = HSM_PASSWORD_INPUT_ERR;
return NULL;
}
} else if (!getline_stdin_pass(&passwd, &passwd_size)) {
*reason = "Could not read pass from stdin.";
*exit_code = HSM_PASSWORD_INPUT_ERR;
return NULL;
}
return passwd;
}

View File

@ -21,10 +21,12 @@ struct encrypted_hsm_secret {
/** Derive the hsm_secret encryption key from a passphrase.
* @pass: the passphrase string.
* @encryption_key: the output key derived from the passphrase.
* @err_msg: if not NULL the error message contains the reason of the failure.
*
* On success, NULL is returned. On error, a human-readable error is.
* On success, 0 is returned, on error a value > 0 is returned and it can be used as exit code.
*/
char *hsm_secret_encryption_key(const char *pass, struct secret *encryption_key);
int hsm_secret_encryption_key_with_exitcode(const char *pass, struct secret *key,
char **err_msg);
/** Encrypt the hsm_secret using a previously derived encryption key.
* @encryption_key: the key derived from the passphrase.
@ -54,10 +56,11 @@ bool decrypt_hsm_secret(const struct secret *encryption_key,
void discard_key(struct secret *key TAKES);
/** Read hsm_secret encryption pass from stdin, disabling echoing.
* @reason: if NULL is returned, will point to the human-readable error.
* @reason: if NULL is returned, will point to the human-readable error,
* and the correct exit code is returned by the exit_code parameter.
*
* Caller must free the string as it does tal-reallocate getline's output.
*/
char *read_stdin_pass(char **reason);
char *read_stdin_pass_with_exit_code(char **reason, int *exit_code);
#endif /* LIGHTNING_COMMON_HSM_ENCRYPTION_H */

View File

@ -2,6 +2,7 @@
#include <ccan/err/err.h>
#include <ccan/fdpass/fdpass.h>
#include <common/ecdh.h>
#include <common/errcode.h>
#include <common/hsm_encryption.h>
#include <common/json_helpers.h>
#include <common/param.h>
@ -82,14 +83,14 @@ struct ext_key *hsm_init(struct lightningd *ld)
/* We actually send requests synchronously: only status is async. */
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) != 0)
err(1, "Could not create hsm socketpair");
err(HSM_GENERIC_ERROR, "Could not create hsm socketpair");
ld->hsm = new_global_subd(ld, "lightning_hsmd",
hsmd_wire_name,
hsm_msg,
take(&fds[1]), NULL);
if (!ld->hsm)
err(1, "Could not subd hsm");
err(HSM_GENERIC_ERROR, "Could not subd hsm");
/* 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
@ -98,7 +99,7 @@ struct ext_key *hsm_init(struct lightningd *ld)
struct stat st;
if (stat("hsm_secret", &st) == 0 &&
st.st_size == ENCRYPTED_HSM_SECRET_LEN)
errx(1, "hsm_secret is encrypted, you need to pass the "
errx(HSM_ERROR_IS_ENCRYPT, "hsm_secret is encrypted, you need to pass the "
"--encrypted-hsm startup option.");
}
@ -111,7 +112,7 @@ struct ext_key *hsm_init(struct lightningd *ld)
IFDEV(ld->dev_force_bip32_seed, NULL),
IFDEV(ld->dev_force_channel_secrets, NULL),
IFDEV(ld->dev_force_channel_secrets_shaseed, NULL))))
err(1, "Writing init msg to hsm");
err(HSM_GENERIC_ERROR, "Writing init msg to hsm");
bip32_base = tal(ld, struct ext_key);
msg = wire_sync_read(tmpctx, ld->hsm_fd);
@ -120,8 +121,8 @@ struct ext_key *hsm_init(struct lightningd *ld)
&ld->bolt12_base,
&ld->onion_reply_secret)) {
if (ld->config.keypass)
errx(1, "Wrong password for encrypted hsm_secret.");
errx(1, "HSM did not give init reply");
errx(HSM_BAD_PASSWORD, "Wrong password for encrypted hsm_secret.");
errx(HSM_GENERIC_ERROR, "HSM did not give init reply");
}
return bip32_base;

View File

@ -26,6 +26,20 @@
#include <sys/stat.h>
#include <sys/wait.h>
/* Unless overridden, we exit with status 1 when option parsing fails */
static int opt_exitcode = 1;
static void opt_log_stderr_exitcode(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
exit(opt_exitcode);
}
/* Declare opt_add_addr here, because we we call opt_add_addr
* and opt_announce_addr vice versa
*/
@ -461,7 +475,7 @@ static char *opt_important_plugin(const char *arg, struct lightningd *ld)
*/
static char *opt_set_hsm_password(struct lightningd *ld)
{
char *passwd, *passwd_confirmation, *err;
char *passwd, *passwd_confirmation, *err_msg;
printf("The hsm_secret is encrypted with a password. In order to "
"decrypt it and start the node you must provide the password.\n");
@ -469,20 +483,23 @@ static char *opt_set_hsm_password(struct lightningd *ld)
/* If we don't flush we might end up being buffered and we might seem
* to hang while we wait for the password. */
fflush(stdout);
passwd = read_stdin_pass(&err);
passwd = read_stdin_pass_with_exit_code(&err_msg, &opt_exitcode);
if (!passwd)
return err;
return err_msg;
printf("Confirm hsm_secret password:\n");
fflush(stdout);
passwd_confirmation = read_stdin_pass(&err);
passwd_confirmation = read_stdin_pass_with_exit_code(&err_msg, &opt_exitcode);
if (!passwd_confirmation)
return err;
return err_msg;
printf("\n");
ld->config.keypass = tal(NULL, struct secret);
err = hsm_secret_encryption_key(passwd, ld->config.keypass);
if (err)
return err;
opt_exitcode = hsm_secret_encryption_key_with_exitcode(passwd, ld->config.keypass, &err_msg);
if (opt_exitcode > 0)
return err_msg;
ld->encrypted_hsm = true;
free(passwd);
free(passwd_confirmation);
@ -1087,8 +1104,8 @@ static void register_opts(struct lightningd *ld)
opt_hidden);
opt_register_noarg("--encrypted-hsm", opt_set_hsm_password, ld,
"Set the password to encrypt hsm_secret with. If no password is passed through command line, "
"you will be prompted to enter it.");
"Set the password to encrypt hsm_secret with. If no password is passed through command line, "
"you will be prompted to enter it.");
opt_register_arg("--rpc-file-mode", &opt_set_mode, &opt_show_mode,
&ld->rpc_filemode,
@ -1315,10 +1332,9 @@ void handle_opts(struct lightningd *ld, int argc, char *argv[])
parse_config_files(ld->config_filename, ld->config_basedir, false);
/* Now parse cmdline, which overrides config. */
opt_parse(&argc, argv, opt_log_stderr_exit);
opt_parse(&argc, argv, opt_log_stderr_exitcode);
if (argc != 1)
errx(1, "no arguments accepted");
/* We keep a separate variable rather than overriding always_use_proxy,
* so listconfigs shows the correct thing. */
if (tal_count(ld->proposed_wireaddr) != 0

View File

@ -17,6 +17,11 @@ import unittest
WAIT_TIMEOUT = 60 # Wait timeout for processes
# Errors codes
HSM_GENERIC_ERROR = 20
HSM_ERROR_IS_ENCRYPT = 21
HSM_BAD_PASSWORD = 22
@unittest.skipIf(TEST_NETWORK != 'regtest', "Test relies on a number of example addresses valid only in regtest")
def test_withdraw(node_factory, bitcoind):
@ -1018,7 +1023,7 @@ def test_hsm_secret_encryption(node_factory):
# Test we cannot start the same wallet without specifying --encrypted-hsm
l1.daemon.opts.pop("encrypted-hsm")
with pytest.raises(subprocess.CalledProcessError, match=r'returned non-zero exit status 1'):
with pytest.raises(subprocess.CalledProcessError, match=r'returned non-zero exit status {}'.format(HSM_ERROR_IS_ENCRYPT)):
subprocess.check_call(l1.daemon.cmd_line)
# Test we cannot restore the same wallet with another password
@ -1029,7 +1034,7 @@ def test_hsm_secret_encryption(node_factory):
write_all(master_fd, password[2:].encode("utf-8"))
l1.daemon.wait_for_log(r'Confirm hsm_secret password')
write_all(master_fd, password[2:].encode("utf-8"))
assert(l1.daemon.proc.wait(WAIT_TIMEOUT) == 1)
assert(l1.daemon.proc.wait(WAIT_TIMEOUT) == HSM_BAD_PASSWORD)
assert(l1.daemon.is_in_log("Wrong password for encrypted hsm_secret."))
# Test we can restore the same wallet with the same password
@ -1097,6 +1102,7 @@ def test_hsmtool_secret_decryption(node_factory):
hsmtool.wait_for_log(r"Enter hsm_secret password:")
write_all(master_fd, password.encode("utf-8"))
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
# Then test we can now start it without password
l1.daemon.opts.pop("encrypted-hsm")
l1.daemon.start(stdin=slave_fd, wait_for_initialized=True)
@ -1115,7 +1121,7 @@ def test_hsmtool_secret_decryption(node_factory):
assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0
# Now we need to pass the encrypted-hsm startup option
l1.stop()
with pytest.raises(subprocess.CalledProcessError, match=r'returned non-zero exit status 1'):
with pytest.raises(subprocess.CalledProcessError, match=r'returned non-zero exit status {}'.format(HSM_ERROR_IS_ENCRYPT)):
subprocess.check_call(l1.daemon.cmd_line)
l1.daemon.opts.update({"encrypted-hsm": None})

View File

@ -90,6 +90,7 @@ static void get_encrypted_hsm_secret(struct secret *hsm_secret,
struct secret key;
struct encrypted_hsm_secret encrypted_secret;
char *err;
int exit_code = 0;
fd = open(hsm_secret_path, O_RDONLY);
if (fd < 0)
@ -98,9 +99,9 @@ static void get_encrypted_hsm_secret(struct secret *hsm_secret,
if (!read_all(fd, encrypted_secret.data, ENCRYPTED_HSM_SECRET_LEN))
errx(ERROR_HSM_FILE, "Could not read encrypted hsm_secret");
err = hsm_secret_encryption_key(passwd, &key);
if (err)
errx(ERROR_LIBSODIUM, "%s", err);
exit_code = hsm_secret_encryption_key_with_exitcode(passwd, &key, &err);
if (exit_code > 0)
errx(exit_code, "%s", err);
if (!decrypt_hsm_secret(&key, &encrypted_secret, hsm_secret))
errx(ERROR_LIBSODIUM, "Could not retrieve the seed. Wrong password ?");
@ -164,15 +165,15 @@ static int decrypt_hsm(const char *hsm_secret_path)
struct secret hsm_secret;
char *passwd, *err;
const char *dir, *backup;
int exit_code = 0;
/* This checks the file existence, too. */
if (!hsm_secret_is_encrypted(hsm_secret_path))
errx(ERROR_USAGE, "hsm_secret is not encrypted");
printf("Enter hsm_secret password:\n");
fflush(stdout);
passwd = read_stdin_pass(&err);
passwd = read_stdin_pass_with_exit_code(&err, &exit_code);
if (!passwd)
errx(ERROR_TERM, "%s", err);
errx(exit_code, "%s", err);
if (sodium_init() == -1)
errx(ERROR_LIBSODIUM,
@ -221,6 +222,7 @@ static int encrypt_hsm(const char *hsm_secret_path)
struct encrypted_hsm_secret encrypted_hsm_secret;
char *passwd, *passwd_confirmation, *err;
const char *dir, *backup;
int exit_code = 0;
/* This checks the file existence, too. */
if (hsm_secret_is_encrypted(hsm_secret_path))
@ -228,14 +230,14 @@ static int encrypt_hsm(const char *hsm_secret_path)
printf("Enter hsm_secret password:\n");
fflush(stdout);
passwd = read_stdin_pass(&err);
passwd = read_stdin_pass_with_exit_code(&err, &exit_code);
if (!passwd)
errx(ERROR_TERM, "%s", err);
errx(exit_code, "%s", err);
printf("Confirm hsm_secret password:\n");
fflush(stdout);
passwd_confirmation = read_stdin_pass(&err);
passwd_confirmation = read_stdin_pass_with_exit_code(&err, &exit_code);
if (!passwd_confirmation)
errx(ERROR_TERM, "%s", err);
errx(exit_code, "%s", err);
if (!streq(passwd, passwd_confirmation))
errx(ERROR_USAGE, "Passwords confirmation mismatch.");
get_hsm_secret(&hsm_secret, hsm_secret_path);
@ -249,9 +251,9 @@ static int encrypt_hsm(const char *hsm_secret_path)
/* Derive the encryption key from the password provided, and try to encrypt
* the seed. */
err = hsm_secret_encryption_key(passwd, &key);
if (err)
errx(ERROR_LIBSODIUM, "%s", err);
exit_code = hsm_secret_encryption_key_with_exitcode(passwd, &key, &err);
if (exit_code > 0)
errx(exit_code, "%s", err);
if (!encrypt_hsm_secret(&key, &hsm_secret, &encrypted_hsm_secret))
errx(ERROR_LIBSODIUM, "Could not encrypt the hsm_secret seed.");
@ -295,6 +297,7 @@ static int dump_commitments_infos(struct node_id *node_id, u64 channel_id,
struct secret hsm_secret, channel_seed, per_commitment_secret;
struct pubkey per_commitment_point;
char *passwd, *err;
int exit_code = 0;
secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY
| SECP256K1_CONTEXT_SIGN);
@ -303,9 +306,9 @@ static int dump_commitments_infos(struct node_id *node_id, u64 channel_id,
if (hsm_secret_is_encrypted(hsm_secret_path)) {
printf("Enter hsm_secret password:\n");
fflush(stdout);
passwd = read_stdin_pass(&err);
passwd = read_stdin_pass_with_exit_code(&err, &exit_code);
if (!passwd)
errx(ERROR_TERM, "%s", err);
errx(exit_code, "%s", err);
get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd);
free(passwd);
} else
@ -357,7 +360,7 @@ static int guess_to_remote(const char *address, struct node_id *node_id,
u8 goal_pubkeyhash[20];
/* See common/bech32.h for buffer size. */
char hrp[strlen(address) - 6];
int witver;
int witver, exit_code = 0;
size_t witlen;
/* Get the hrp to accept addresses from any network. */
@ -373,9 +376,9 @@ static int guess_to_remote(const char *address, struct node_id *node_id,
if (hsm_secret_is_encrypted(hsm_secret_path)) {
printf("Enter hsm_secret password:\n");
fflush(stdout);
passwd = read_stdin_pass(&err);
passwd = read_stdin_pass_with_exit_code(&err, &exit_code);
if (!passwd)
errx(ERROR_TERM, "%s", err);
errx(exit_code, "%s", err);
get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd);
free(passwd);
} else
@ -478,6 +481,7 @@ static int generate_hsm(const char *hsm_secret_path)
{
char mnemonic[BIP39_WORDLIST_LEN];
char *passphrase, *err;
int exit_code = 0;
read_mnemonic(mnemonic);
printf("Warning: remember that different passphrases yield different "
@ -485,9 +489,9 @@ static int generate_hsm(const char *hsm_secret_path)
printf("If left empty, no password is used (echo is disabled).\n");
printf("Enter your passphrase: \n");
fflush(stdout);
passphrase = read_stdin_pass(&err);
passphrase = read_stdin_pass_with_exit_code(&err, &exit_code);
if (!passphrase)
errx(ERROR_TERM, "%s", err);
errx(exit_code, "%s", err);
if (strlen(passphrase) == 0) {
free(passphrase);
passphrase = NULL;
@ -534,14 +538,15 @@ static int dumponchaindescriptors(const char *hsm_secret_path, const char *old_p
struct ext_key master_extkey;
char *enc_xpub, *descriptor;
struct descriptor_checksum checksum;
int exit_code = 0;
/* This checks the file existence, too. */
if (hsm_secret_is_encrypted(hsm_secret_path)) {
printf("Enter hsm_secret password:\n");
fflush(stdout);
passwd = read_stdin_pass(&err);
passwd = read_stdin_pass_with_exit_code(&err, &exit_code);
if (!passwd)
errx(ERROR_TERM, "%s", err);
errx(exit_code, "%s", err);
get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd);
free(passwd);
} else