Skip to content

Commit 5de3e77

Browse files
committed
Fix up check_hsm implementation
1 parent f1f398f commit 5de3e77

File tree

7 files changed

+225
-163
lines changed

7 files changed

+225
-163
lines changed

common/hsm_secret.c

Lines changed: 56 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "config.h"
2+
#include <assert.h>
23
#include <ccan/crypto/sha256/sha256.h>
34
#include <ccan/mem/mem.h>
45
#include <ccan/tal/str/str.h>
@@ -22,11 +23,28 @@
2223
/* Total length of an encrypted hsm_secret */
2324
#define ENCRYPTED_HSM_SECRET_LEN (HS_HEADER_LEN + HS_CIPHERTEXT_LEN)
2425

25-
static void destroy_secret(struct secret *secret)
26+
void destroy_secret(struct secret *secret)
2627
{
2728
sodium_munlock(secret->data, sizeof(secret->data));
2829
}
2930

31+
/* Helper function to validate a mnemonic string */
32+
static bool validate_mnemonic(const char *mnemonic, enum hsm_secret_error *err)
33+
{
34+
struct words *words;
35+
36+
if (bip39_get_wordlist("en", &words) != WALLY_OK) {
37+
abort();
38+
}
39+
40+
if (bip39_mnemonic_validate(words, mnemonic) != WALLY_OK) {
41+
*err = HSM_SECRET_ERR_INVALID_MNEMONIC;
42+
return false;
43+
}
44+
45+
return true;
46+
}
47+
3048
struct secret *get_encryption_key(const tal_t *ctx, const char *passphrase)
3149
{
3250
struct secret *secret = tal(ctx, struct secret);
@@ -113,27 +131,6 @@ bool derive_seed_hash(const char *mnemonic, const char *passphrase, struct sha25
113131
return true;
114132
}
115133

116-
/* Validate the passphrase for a mnemonic secret */
117-
bool validate_mnemonic_passphrase(const u8 *hsm_secret, size_t len, const char *passphrase)
118-
{
119-
enum hsm_secret_type type = detect_hsm_secret_type(hsm_secret, len);
120-
121-
if (type != HSM_SECRET_MNEMONIC_WITH_PASS)
122-
return true; /* No validation needed */
123-
124-
/* First 32 bytes are the stored seed hash */
125-
const struct sha256 *stored_hash = (const struct sha256 *)hsm_secret;
126-
struct sha256 computed_hash;
127-
128-
/* Extract mnemonic portion (skip first 32 bytes which are seed hash) */
129-
const char *mnemonic_start = (const char *)(hsm_secret + sizeof(struct sha256));
130-
131-
if (!derive_seed_hash(mnemonic_start, passphrase, &computed_hash))
132-
return false;
133-
134-
return sha256_eq(stored_hash, &computed_hash);
135-
}
136-
137134
static bool decrypt_hsm_secret(const struct secret *encryption_key,
138135
const u8 *cipher,
139136
struct secret *output)
@@ -171,8 +168,6 @@ const char *hsm_secret_error_str(enum hsm_secret_error err)
171168
return "Invalid mnemonic";
172169
case HSM_SECRET_ERR_ENCRYPTION_FAILED:
173170
return "Encryption failed";
174-
case HSM_SECRET_ERR_WORDLIST_FAILED:
175-
return "Could not load wordlist";
176171
case HSM_SECRET_ERR_SEED_DERIVATION_FAILED:
177172
return "Could not derive seed from mnemonic";
178173
case HSM_SECRET_ERR_INVALID_FORMAT:
@@ -227,7 +222,7 @@ static struct hsm_secret *extract_encrypted_secret(const tal_t *ctx,
227222
decrypt_success = decrypt_hsm_secret(encryption_key, hsm_secret, &hsms->secret);
228223

229224
/* Clear encryption key immediately after use */
230-
discard_key(encryption_key);
225+
destroy_secret(encryption_key);
231226

232227
if (!decrypt_success) {
233228
/* Clear any partial decryption data */
@@ -247,15 +242,14 @@ static struct hsm_secret *extract_mnemonic_secret(const tal_t *ctx,
247242
const u8 *hsm_secret,
248243
size_t len,
249244
const char *passphrase,
245+
enum hsm_secret_type type,
250246
enum hsm_secret_error *err)
251247
{
252248
struct hsm_secret *hsms = tal(ctx, struct hsm_secret);
253-
struct words *words;
254249
const u8 *mnemonic_start;
255250
size_t mnemonic_len;
256-
enum hsm_secret_type type;
257251

258-
type = detect_hsm_secret_type(hsm_secret, len);
252+
assert(type == HSM_SECRET_MNEMONIC_NO_PASS || type == HSM_SECRET_MNEMONIC_WITH_PASS);
259253
hsms->type = type;
260254

261255
/* Extract mnemonic portion (skip first 32 bytes which are passphrase hash) */
@@ -269,7 +263,14 @@ static struct hsm_secret *extract_mnemonic_secret(const tal_t *ctx,
269263
return tal_free(hsms);
270264
}
271265

272-
if (!validate_mnemonic_passphrase(hsm_secret, len, passphrase)) {
266+
/* Validate passphrase by comparing stored hash with computed hash */
267+
struct sha256 stored_hash, computed_hash;
268+
memcpy(&stored_hash, hsm_secret, sizeof(stored_hash));
269+
if (!derive_seed_hash((const char *)mnemonic_start, passphrase, &computed_hash)) {
270+
*err = HSM_SECRET_ERR_SEED_DERIVATION_FAILED;
271+
return tal_free(hsms);
272+
}
273+
if (!sha256_eq(&stored_hash, &computed_hash)) {
273274
*err = HSM_SECRET_ERR_WRONG_PASSPHRASE;
274275
return tal_free(hsms);
275276
}
@@ -283,23 +284,16 @@ static struct hsm_secret *extract_mnemonic_secret(const tal_t *ctx,
283284
/* Copy and validate mnemonic */
284285
hsms->mnemonic = tal_strndup(hsms, (const char *)mnemonic_start, mnemonic_len);
285286

286-
/* Load wordlist and validate mnemonic */
287-
if (bip39_get_wordlist("en", &words) != WALLY_OK) {
288-
*err = HSM_SECRET_ERR_WORDLIST_FAILED;
289-
return tal_free(hsms);
290-
}
291-
292-
if (bip39_mnemonic_validate(words, hsms->mnemonic) != WALLY_OK) {
293-
*err = HSM_SECRET_ERR_INVALID_MNEMONIC;
287+
/* Validate mnemonic */
288+
if (!validate_mnemonic(hsms->mnemonic, err)) {
294289
return tal_free(hsms);
295290
}
296291

297292
/* Derive the seed from the mnemonic */
298293
u8 bip32_seed[BIP39_SEED_LEN_512];
299294
size_t bip32_seed_len;
300-
const char *seed_passphrase = (type == HSM_SECRET_MNEMONIC_WITH_PASS) ? passphrase : NULL;
301295

302-
if (bip39_mnemonic_to_seed(hsms->mnemonic, seed_passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len) != WALLY_OK) {
296+
if (bip39_mnemonic_to_seed(hsms->mnemonic, passphrase, bip32_seed, sizeof(bip32_seed), &bip32_seed_len) != WALLY_OK) {
303297
*err = HSM_SECRET_ERR_SEED_DERIVATION_FAILED;
304298
return tal_free(hsms);
305299
}
@@ -326,13 +320,13 @@ struct hsm_secret *extract_hsm_secret(const tal_t *ctx,
326320
case HSM_SECRET_ENCRYPTED:
327321
return extract_encrypted_secret(ctx, hsm_secret, len, passphrase, err);
328322
case HSM_SECRET_MNEMONIC_NO_PASS:
329-
return extract_mnemonic_secret(ctx, hsm_secret, len, NULL, err);
330323
case HSM_SECRET_MNEMONIC_WITH_PASS:
331-
return extract_mnemonic_secret(ctx, hsm_secret, len, passphrase, err);
324+
return extract_mnemonic_secret(ctx, hsm_secret, len, passphrase, type, err);
332325
case HSM_SECRET_INVALID:
333326
*err = HSM_SECRET_ERR_INVALID_FORMAT;
334327
return NULL;
335328
}
329+
abort();
336330
}
337331

338332
bool encrypt_legacy_hsm_secret(const struct secret *encryption_key,
@@ -355,28 +349,8 @@ bool encrypt_legacy_hsm_secret(const struct secret *encryption_key,
355349
return true;
356350
}
357351

358-
/* Returns -1 on error (and sets errno), 0 if not encrypted, 1 if it is */
359-
int is_legacy_hsm_secret_encrypted(const char *path)
360-
{
361-
struct stat st;
362-
363-
if (stat(path, &st) != 0)
364-
return -1;
365-
366-
return st.st_size == ENCRYPTED_HSM_SECRET_LEN;
367-
}
368-
369-
void discard_key(struct secret *key TAKES)
370-
{
371-
/* sodium_munlock() also zeroes the memory. */
372-
sodium_munlock(key->data, sizeof(key->data));
373-
if (taken(key))
374-
tal_free(key);
375-
}
376-
377352
static void destroy_passphrase(char *passphrase)
378353
{
379-
sodium_memzero(passphrase, tal_bytelen(passphrase));
380354
sodium_munlock(passphrase, tal_bytelen(passphrase));
381355
}
382356

@@ -404,8 +378,8 @@ static void restore_echo(const struct termios *saved_term)
404378
tcsetattr(fileno(stdin), TCSANOW, saved_term);
405379
}
406380

407-
/* Read line from stdin (uses malloc internally) */
408-
static char *read_line(void)
381+
/* Read line from stdin (uses tal allocation) */
382+
static char *read_line(const tal_t *ctx)
409383
{
410384
char *line = NULL;
411385
size_t size = 0;
@@ -420,7 +394,10 @@ static char *read_line(void)
420394
if (len > 0 && line[len - 1] == '\n')
421395
line[len - 1] = '\0';
422396

423-
return line;
397+
/* Convert to tal string */
398+
char *result = tal_strndup(ctx, line, len);
399+
free(line);
400+
return result;
424401
}
425402

426403
const char *read_stdin_pass(const tal_t *ctx, enum hsm_secret_error *err)
@@ -434,75 +411,53 @@ const char *read_stdin_pass(const tal_t *ctx, enum hsm_secret_error *err)
434411
return NULL;
435412
}
436413

437-
char *input = read_line();
414+
char *input = read_line(ctx);
438415
if (!input) {
439416
if (echo_disabled)
440417
restore_echo(&saved_term);
441418
*err = HSM_SECRET_ERR_INVALID_FORMAT;
442419
return NULL;
443420
}
444421

445-
size_t len = strlen(input);
446-
char *passphrase = tal_arr(ctx, char, len + 1);
447-
strcpy(passphrase, input);
448-
free(input);
449-
450422
/* Memory locking is mandatory: failure means we're on an insecure system */
451-
if (sodium_mlock(passphrase, len + 1) != 0)
423+
if (sodium_mlock(input, tal_bytelen(input)) != 0)
452424
abort();
453425

454-
tal_add_destructor(passphrase, destroy_passphrase);
426+
tal_add_destructor(input, destroy_passphrase);
455427

456428
if (echo_disabled)
457429
restore_echo(&saved_term);
458430

459-
return passphrase;
431+
return input;
460432
}
461433

462-
/* Add this function to hsm_secret.c */
463434
const char *read_stdin_mnemonic(const tal_t *ctx, enum hsm_secret_error *err)
464435
{
465436
*err = HSM_SECRET_OK;
466437

467438
printf("Introduce your BIP39 word list separated by space (at least 12 words):\n");
468439
fflush(stdout);
469440

470-
char *line = NULL;
471-
size_t size = 0;
472-
if (getline(&line, &size, stdin) < 0) {
473-
free(line);
441+
char *line = read_line(ctx);
442+
if (!line) {
474443
*err = HSM_SECRET_ERR_INVALID_FORMAT;
475444
return NULL;
476445
}
477446

478-
/* Strip newline */
479-
size_t len = strlen(line);
480-
if (len > 0 && line[len - 1] == '\n')
481-
line[len - 1] = '\0';
482-
483447
/* Validate mnemonic */
484-
struct words *words;
485-
if (bip39_get_wordlist("en", &words) != WALLY_OK) {
486-
free(line);
487-
*err = HSM_SECRET_ERR_WORDLIST_FAILED;
488-
return NULL;
489-
}
490-
491-
if (bip39_mnemonic_validate(words, line) != WALLY_OK) {
492-
free(line);
493-
*err = HSM_SECRET_ERR_INVALID_MNEMONIC;
448+
if (!validate_mnemonic(line, err)) {
494449
return NULL;
495450
}
496451

497-
/* Convert to tal string */
498-
char *mnemonic = tal_strdup(ctx, line);
499-
free(line);
500-
501-
return mnemonic;
452+
return line;
502453
}
503454

455+
int is_legacy_hsm_secret_encrypted(const char *path)
456+
{
457+
struct stat st;
504458

459+
if (stat(path, &st) != 0)
460+
return -1;
505461

506-
507-
508-
462+
return st.st_size == ENCRYPTED_HSM_SECRET_LEN;
463+
}

common/hsm_secret.h

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#ifndef LIGHTNING_COMMON_HSM_SECRET_H
22
#define LIGHTNING_COMMON_HSM_SECRET_H
33
#include "config.h"
4-
#include <sys/types.h>
4+
#include <bitcoin/privkey.h>
55
#include <ccan/crypto/sha256/sha256.h>
66
#include <ccan/tal/tal.h>
7-
#include <bitcoin/privkey.h>
87
#include <sodium.h>
8+
#include <sys/types.h>
99

1010
/* Length constants for encrypted HSM secret files */
1111
#define HS_HEADER_LEN crypto_secretstream_xchacha20poly1305_HEADERBYTES
@@ -30,7 +30,6 @@ enum hsm_secret_error {
3030
HSM_SECRET_ERR_WRONG_PASSPHRASE,
3131
HSM_SECRET_ERR_INVALID_MNEMONIC,
3232
HSM_SECRET_ERR_ENCRYPTION_FAILED,
33-
HSM_SECRET_ERR_WORDLIST_FAILED,
3433
HSM_SECRET_ERR_SEED_DERIVATION_FAILED,
3534
HSM_SECRET_ERR_INVALID_FORMAT,
3635
HSM_SECRET_ERR_TERMINAL,
@@ -130,16 +129,6 @@ const char *hsm_secret_error_str(enum hsm_secret_error err);
130129
*/
131130
enum hsm_secret_type detect_hsm_secret_type(const u8 *hsm_secret, size_t len);
132131

133-
/**
134-
* Validate passphrase for mnemonic-based secrets.
135-
* @hsm_secret - raw file contents
136-
* @len - length of file
137-
* @passphrase - passphrase to validate
138-
*
139-
* Returns true if passphrase is valid or not needed.
140-
*/
141-
bool validate_mnemonic_passphrase(const u8 *hsm_secret, size_t len, const char *passphrase);
142-
143132
/**
144133
* Reads a BIP39 mnemonic from stdin with validation.
145134
* Returns a newly allocated string on success, NULL on error.
@@ -160,4 +149,18 @@ const char *read_stdin_mnemonic(const tal_t *ctx, enum hsm_secret_error *err);
160149
*/
161150
bool derive_seed_hash(const char *mnemonic, const char *passphrase, struct sha256 *seed_hash);
162151

152+
/**
153+
* Check if hsm_secret file is encrypted (legacy format only).
154+
* @path - path to the hsm_secret file
155+
*
156+
* Returns 1 if encrypted, 0 if not encrypted, -1 on error.
157+
*/
158+
int is_legacy_hsm_secret_encrypted(const char *path);
159+
160+
/**
161+
* Zero and unlock a secret's memory.
162+
* @secret - the secret to destroy
163+
*/
164+
void destroy_secret(struct secret *secret);
165+
163166
#endif /* LIGHTNING_COMMON_HSM_SECRET_H */

0 commit comments

Comments
 (0)