From e3242eec96e39198a3a2693c3ded44b74a57ad50 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Wed, 11 Nov 2026 16:41:25 +0100 Subject: [PATCH] keytools: document stateful LMS/XMSS signing as demo/development only LMS/HSS and XMSS/XMSS^MT are stateful hash-based signature schemes: each signature consumes one-time key material, and reusing it breaks the whole key. The key tools store the private key in a plain local file and sign rewrites it in place, a pattern that does not meet production storage requirements (atomic, durable, protected). - keygen: print a warning after generating an LMS/XMSS key - sign: print a warning (once per run) after a successful LMS/XMSS signature; other algorithms are unaffected - docs/Signing.md: new "Stateful signatures" section explaining the statefulness, the file-based demo/development pattern, and the production guidance (stateless algorithm, or protected storage) Verification: - make -C tools/keytools: builds clean - keygen --lms / --xmss: warning printed, rc=0; keygen --ed25519: none - sign --lms / --xmss: warning printed once, rc=0; sign --ed25519: none - sign --lms --delta (two signatures per run): warning printed once - make -C tools/unit-tests run: exit 0 --- docs/Signing.md | 31 +++++++++++++++++++++++++++++-- tools/keytools/keygen.c | 19 +++++++++++++++++++ tools/keytools/sign.c | 27 +++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/docs/Signing.md b/docs/Signing.md index 7f76288c2c..2a86f9ddd2 100644 --- a/docs/Signing.md +++ b/docs/Signing.md @@ -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 diff --git a/tools/keytools/keygen.c b/tools/keytools/keygen.c index e48a9d0783..fe96bf4612 100644 --- a/tools/keytools/keygen.c +++ b/tools/keytools/keygen.c @@ -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; @@ -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); @@ -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); diff --git a/tools/keytools/sign.c b/tools/keytools/sign.c index 08b64971e0..d83a4eff89 100644 --- a/tools/keytools/sign.c +++ b/tools/keytools/sign.c @@ -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"; @@ -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) { @@ -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) {