Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions docs/Signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,37 @@ file is in this format.
file is in this format.

* `--lms` Use LMS/HSS for signing the firmware. Assume that the given KEY.DER
file is in this format.
file is in this format. Stateful scheme, see "Stateful signatures" below.

* `--xmss` Use XMSS/XMSS^MT for signing the firmware. Assume that the given KEY.DER
file is in this format.
file is in this format. Stateful scheme, see "Stateful signatures" below.

#### Stateful signatures (LMS/HSS, XMSS/XMSS^MT)

LMS/HSS and XMSS/XMSS^MT are stateful hash-based signature schemes (NIST SP
800-208). "Stateful" means every signature permanently consumes one-time
components of the private key: if a consumed component is ever reused, the
security of the whole key is broken. Unlike ML-DSA or Ed25519, the private
key changes with every signature.

Practical consequences when using the key tools:

- The LMS/XMSS private key file (`KEY.DER`) is the signing authority itself,
not just a copy of a secret: `sign` rewrites it in place after every
signature, advancing the key state. The file must be kept safe, and its
storage must be atomic and durable, so a crash mid-write never leaves a
state that can be reloaded with a reused index.
- The key tools therefore treat the LMS/XMSS file-based flow as a
**demo/development pattern only**, and print a warning whenever `keygen`
generates an LMS/XMSS key or `sign` signs with one. Do not use it in
production. For production signing prefer a stateless algorithm (e.g.
ML-DSA), or keep the stateful key in protected storage (HSM, secure
element) behind the persistence callbacks.
- `sign` needs the explicit `--lms` / `--xmss` option: the key type is not
auto-detected, and `SIGN_AUTO` is not supported for these schemes.

See [PQ.md](PQ.md) for the algorithm tradeoffs and the configuration options
(`LMS_LEVELS`, `LMS_HEIGHT`, `LMS_WINTERNITZ`, `XMSS_PARAMS`).

* `--no-sign` Disable secure boot signature verification. No signature
verification is performed in the bootloader, and the KEY.DER argument should
Expand Down
19 changes: 19 additions & 0 deletions tools/keytools/keygen.c
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,21 @@ static void keygen_ed448(const char *privkey, uint32_t id_mask)

#include "../lms/lms_common.h"

/* LMS/XMSS are stateful schemes: every signature consumes a one-time key
* component, and reusing a component breaks the security of the whole key.
* keygen stores the private key in a plain local file, which is a
* demo/development pattern only. */
static void stateful_keygen_warning(void)
{
fprintf(stderr,
"warning: LMS/XMSS are stateful signature schemes: each signature\n"
"permanently consumes part of the private key, and reusing a consumed\n"
"component breaks the security of the whole key. Production signing\n"
"requires atomic, durable, protected storage of the key state (e.g. an\n"
"HSM or secure element). keygen stores the private key in a plain\n"
"local file: demo/development use only, not for production.\n");
}

static void keygen_lms(const char *priv_fname, uint32_t id_mask)
{
FILE * fpriv;
Expand Down Expand Up @@ -1007,6 +1022,8 @@ static void keygen_lms(const char *priv_fname, uint32_t id_mask)

keystore_add(AUTH_KEY_LMS, lms_pub, KEYSTORE_PUBKEY_SIZE_LMS, priv_fname, id_mask);

stateful_keygen_warning();

cleanup:
if (key_init) {
wc_LmsKey_Free(&key);
Expand Down Expand Up @@ -1126,6 +1143,8 @@ static void keygen_xmss(const char *priv_fname, uint32_t id_mask)

keystore_add(AUTH_KEY_XMSS, xmss_pub, KEYSTORE_PUBKEY_SIZE_XMSS, priv_fname, id_mask);

stateful_keygen_warning();

cleanup:
if (key_init) {
wc_XmssKey_Free(&key);
Expand Down
27 changes: 27 additions & 0 deletions tools/keytools/sign.c
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,27 @@ static void header_append_tag_u64(uint8_t *header, uint32_t *idx, uint16_t tag,
#include "../lms/lms_common.h"
#include "../xmss/xmss_common.h"

/* LMS/XMSS are stateful schemes: every signature consumes a one-time key
* component, and reusing a component breaks the security of the whole key.
* sign rewrites the private key file in place after each signature, so the
* file-based pattern is a demo/development one only. Printed once per run,
* since a single run may sign several headers (delta, compressed). */
static void stateful_sign_warning(void)
{
static int warned = 0;

if (warned)
return;
warned = 1;
fprintf(stderr,
"warning: LMS/XMSS are stateful signature schemes: each signature\n"
"permanently consumes part of the private key, and reusing a consumed\n"
"component breaks the security of the whole key. Production signing\n"
"requires atomic, durable, protected storage of the key state (e.g. an\n"
"HSM or secure element). sign updates the private key file in place:\n"
"demo/development use only, not for production.\n");
}

/* Globals */
static const char wolfboot_delta_file[] = "/tmp/wolfboot-delta.bin";

Expand Down Expand Up @@ -1295,6 +1316,9 @@ static int sign_digest(int sign, int hash_algo,
if (ret != 0) {
fprintf(stderr, "error signing with LMS: %d\n", ret);
}
else {
stateful_sign_warning();
}
}
else
if (sign == SIGN_XMSS) {
Expand Down Expand Up @@ -1326,6 +1350,9 @@ static int sign_digest(int sign, int hash_algo,
if (ret != 0) {
fprintf(stderr, "error signing with XMSS: %d\n", ret);
}
else {
stateful_sign_warning();
}
}
else
if (sign == SIGN_ML_DSA) {
Expand Down
Loading