-
Notifications
You must be signed in to change notification settings - Fork 945
Add support for mnemonics in hsm_secret #8400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
We want to validate if our secret is a mnemonic. In hsm_secret_is_encrypted we're checking if the secret is encrypted, we now have to add an additional check here to see if the secret is a mnemonic.
This adds support for reading a 12-word BIP39 mnemonic from a plaintext hsm_secret file. If the file contains a valid mnemonic, hsmtool will derive the 32-byte seed. There's a few TODOs left through out the code. I think the most important one being that I haven't handled the passphrase case so far. In a BIP39 secret, 512 bits of 64 bytes, the first 32 bytes are for the hsm_seed and the next 32 bytes are for the optional passphrase. We're currently hardcoding the passphrase to an empty string but we will probably want to handle this? I've also refactoed is_mnemonic_secret to use read_and_validate_mnemonic_file so it's a little tidier.
Updates generate_hsm() to write the 12-word BIP39 mnemonic to the hsm_secret file, instead of a 32-byte raw seed.
This is a newer API to replace hsm_encryption.c and hsm_encryption.c, this tidies up the API to be used and also cleans things up to support our new formts. Our hsm_secret formats now include: - Legacy 32-byte plain format - Legacy 73-byte encrypted format - New mnemonic format without passphrase (32 zero bytes + mnemonic) - New mnemonic format with passphrase (32-byte hash + mnemonic) This commit includes support to detect the format based on the file size and content structure. The hsm will store mnemonics in the hsm_secret file as: `passphraseHash`mnemonic`
I'm just committing this now cause it feels like I'm tired and going to write stupid code soon
gets rid of get_hsm_seed which would prompt the user for a password. We were prompting the user for a password 2-3 times., now only load_hsm_secret prompts the user for a password. The exception is check_hsm which requires you to reenter your password. Also, added a skeleton of some tests I'd like to add but none of them work just yet.
common/hsm_secret.c
Outdated
#include <ccan/tal/str/str.h> | ||
#include <ccan/tal/tal.h> | ||
#include <common/errcode.h> | ||
#include <common/utils.h> | ||
#include <common/hsm_secret.h> | ||
#include <errno.h> | ||
#include <termios.h> | ||
#include <unistd.h> | ||
#include <ccan/crypto/sha256/sha256.h> | ||
#include <ccan/mem/mem.h> | ||
#include <sodium.h> | ||
#include <wally_bip39.h> | ||
#include <sys/stat.h> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These need to be in alphabetical order!
common/hsm_secret.c
Outdated
case HSM_SECRET_INVALID: | ||
return false; | ||
} | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
abort()
common/hsm_secret.c
Outdated
return HSM_SECRET_ENCRYPTED; | ||
|
||
/* Check if it starts with our type bytes (mnemonic formats) */ | ||
if (len > 32) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (len < 32)
return HSM_SECRET_INVALID;
common/hsm_secret.c
Outdated
static void hash_passphrase(const char *passphrase, u8 hash[PASSPHRASE_HASH_LEN]) | ||
{ | ||
struct sha256 sha; | ||
sha256(&sha, passphrase, strlen(passphrase)); | ||
memcpy(hash, sha.u.u8, PASSPHRASE_HASH_LEN); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a sha256 type for a reason, let's use it, not convert to u8[]
common/hsm_secret.c
Outdated
/* First 32 bytes are the stored passphrase hash */ | ||
const u8 *stored_hash = hsm_secret; | ||
u8 computed_hash[32]; | ||
|
||
hash_passphrase(passphrase, computed_hash); | ||
return memcmp(stored_hash, computed_hash, 32) == 0; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct sha256 actual, expected;
/* First 32 bytes are the stored passphrase hash */
memcpy(&expected, hsm_secret, sizeof(struct sha256));
sha256(&actual, passphrase, strlen(passphrase));
return sha256_eq(&expected, &actual);
if (strlen(passphrase) == 0) { | ||
passphrase = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mildly prefer if (streq(passphrase, "")) ?
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"); | ||
|
||
/* Write to file using your new mnemonic format */ | ||
int fd = open(hsm_secret_path, O_CREAT|O_EXCL|O_WRONLY, 0400); | ||
if (fd < 0) { | ||
errx(ERROR_USAGE, "Unable to create hsm_secret file"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err() not errx()
tools/hsmtool.c
Outdated
sha256(&sha, passphrase, strlen(passphrase)); | ||
|
||
if (!write_all(fd, sha.u.u8, PASSPHRASE_HASH_LEN)) | ||
errx(ERROR_USAGE, "Error writing passphrase hash to hsm_secret file"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err...
tools/hsmtool.c
Outdated
if (passphrase) { | ||
/* Write passphrase hash (32 bytes) + mnemonic for protected format */ | ||
struct sha256 sha; | ||
sha256(&sha, passphrase, strlen(passphrase)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be hash of the final seed. This is a bit trickier: we need hsm_secret to expose that "calculate seed from mnemonic and passphrase" function (or opencode it with libwally?)
u8 bip32_seed[BIP39_SEED_LEN_512]; | ||
size_t bip32_seed_len; | ||
|
||
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need this for below.,
Important
25.09 FREEZE July 28TH: Non-bugfix PRs not ready by this date will wait for 25.12.
RC1 is scheduled on August 11th
The final release is scheduled for September 1st.
Checklist
Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked: