mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-08-17 06:21:35 +02:00
Invalidate LMS key when private key write fails
wc_LmsKey_Sign computes the signature with the one-time key at the current leaf and only afterwards advances the leaf index and writes it to non-volatile storage. When that write failed the signature was erased but the key state was left as OK, so the same in-memory key could sign again while storage still recorded the already consumed leaf. A restart followed by wc_LmsKey_Reload would then resume from that leaf and sign a second message with the same LM-OTS private key. Set the key state to bad before signing and only restore it to OK once the advanced private key has been committed to storage, matching what wc_xmsskey_signupdate already does for XMSS. Exhausted keys keep their existing NOSIGS state. Fixes F-7411.
This commit is contained in:
@@ -71,10 +71,19 @@ static const char* lms_test_priv_key_file(void)
|
||||
}
|
||||
#define LMS_TEST_PRIV_KEY_FILE lms_test_priv_key_file()
|
||||
|
||||
/* Set to 1 to make test_lms_write_key() simulate a non-volatile storage write
|
||||
* that never completes. */
|
||||
static int lms_write_key_fail = 0;
|
||||
|
||||
static int test_lms_write_key(const byte* priv, word32 privSz, void* context)
|
||||
{
|
||||
FILE* f = fopen((const char*)context, "wb");
|
||||
FILE* f;
|
||||
int ret = WC_LMS_RC_SAVED_TO_NV_MEMORY;
|
||||
|
||||
if (lms_write_key_fail)
|
||||
return -1;
|
||||
|
||||
f = fopen((const char*)context, "wb");
|
||||
if (f == NULL)
|
||||
return -1;
|
||||
if (fwrite(priv, 1, privSz, f) != privSz)
|
||||
@@ -177,6 +186,80 @@ int test_wc_LmsKey_sign_verify(void)
|
||||
return EXPECT_RESULT();
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that a failed private key write permanently invalidates the key.
|
||||
*
|
||||
* RFC 8554 section 5.4.1 requires the advanced leaf index to be stored before
|
||||
* the signature is released. The signature is computed with the one-time key
|
||||
* at the current leaf and only then is the index advanced and written out, so
|
||||
* a failed write leaves storage pointing at a leaf that has already been used.
|
||||
* The key must refuse to sign again rather than hand out a second signature
|
||||
* from the same one-time key.
|
||||
*/
|
||||
int test_wc_LmsKey_write_fail(void)
|
||||
{
|
||||
EXPECT_DECLS;
|
||||
#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)
|
||||
LmsKey key;
|
||||
WC_RNG rng;
|
||||
byte msg[] = "test message for LMS signing";
|
||||
byte sig[2048];
|
||||
word32 sigSz;
|
||||
word32 goodSigSz = 0;
|
||||
word32 i;
|
||||
int nonZero;
|
||||
|
||||
/* Zero so cleanup is safe if an early alloc failure skips init. */
|
||||
XMEMSET(&key, 0, sizeof(key));
|
||||
XMEMSET(&rng, 0, sizeof(rng));
|
||||
|
||||
ExpectIntEQ(wc_InitRng(&rng), 0);
|
||||
|
||||
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
||||
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
|
||||
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
|
||||
|
||||
sigSz = sizeof(sig);
|
||||
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
||||
goodSigSz = sigSz;
|
||||
|
||||
/* A signature was really produced, so the check below is not vacuous. */
|
||||
nonZero = 0;
|
||||
for (i = 0; i < goodSigSz; i++) {
|
||||
if (sig[i] != 0)
|
||||
nonZero++;
|
||||
}
|
||||
ExpectIntGT(nonZero, 0);
|
||||
|
||||
/* Fail the write of the advanced private key. */
|
||||
lms_write_key_fail = 1;
|
||||
sigSz = sizeof(sig);
|
||||
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)),
|
||||
WC_NO_ERR_TRACE(IO_FAILED_E));
|
||||
lms_write_key_fail = 0;
|
||||
|
||||
/* The signature has to be erased as well, not just reported as failed.
|
||||
* The leaf index was never stored, so releasing it would allow the same
|
||||
* one-time key to sign twice. */
|
||||
nonZero = 0;
|
||||
for (i = 0; i < goodSigSz; i++) {
|
||||
if (sig[i] != 0)
|
||||
nonZero++;
|
||||
}
|
||||
ExpectIntEQ(nonZero, 0);
|
||||
|
||||
/* Storage works again but the key must stay unusable. */
|
||||
sigSz = sizeof(sig);
|
||||
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)),
|
||||
WC_NO_ERR_TRACE(BAD_STATE_E));
|
||||
|
||||
wc_LmsKey_Free(&key);
|
||||
wc_FreeRng(&rng);
|
||||
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
||||
#endif
|
||||
return EXPECT_RESULT();
|
||||
}
|
||||
|
||||
/*
|
||||
* Test LMS key reload after advancing past the leaf cache window.
|
||||
*
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <tests/api/api_decl.h>
|
||||
|
||||
int test_wc_LmsKey_sign_verify(void);
|
||||
int test_wc_LmsKey_write_fail(void);
|
||||
int test_wc_LmsKey_reload_cache(void);
|
||||
int test_wc_LmsKey_reload_devid(void);
|
||||
int test_wc_XmssKey_reload_devid(void);
|
||||
@@ -40,6 +41,7 @@ int test_wc_XmssFeatureCoverage(void);
|
||||
/* LMS, and RFC 9802 (HSS/LMS and XMSS/XMSS^MT in X.509). */
|
||||
#define TEST_LMS_XMSS_DECLS \
|
||||
TEST_DECL_GROUP("lms", test_wc_LmsKey_sign_verify), \
|
||||
TEST_DECL_GROUP("lms", test_wc_LmsKey_write_fail), \
|
||||
TEST_DECL_GROUP("lms", test_wc_LmsKey_reload_cache), \
|
||||
TEST_DECL_GROUP("lms", test_wc_LmsKey_reload_devid), \
|
||||
TEST_DECL_GROUP("xmss", test_wc_XmssKey_reload_devid), \
|
||||
|
||||
@@ -1420,6 +1420,10 @@ int wc_LmsKey_GetPrivLen(const LmsKey* key, word32* len)
|
||||
}
|
||||
|
||||
/* Sign a message.
|
||||
*
|
||||
* The one-time key at the current leaf is consumed before the advanced private
|
||||
* key is written to storage. If either step fails then the key state is left
|
||||
* bad and no further signatures can be created with this key.
|
||||
*
|
||||
* @param [in, out] key LMS key to sign with.
|
||||
* @param [out] sig Signature data. Buffer must be big enough to hold
|
||||
@@ -1434,6 +1438,7 @@ int wc_LmsKey_GetPrivLen(const LmsKey* key, word32* len)
|
||||
* @return BAD_FUNC_ARG when a read/write private key context is not set.
|
||||
* @return BUFFER_E when sigSz is too small.
|
||||
* @return BAD_STATE_E when wrong state for operation.
|
||||
* @return KEY_EXHAUSTED_E when no signatures are left in the key.
|
||||
* @return IO_FAILED_E when reading or writing private key failed.
|
||||
*/
|
||||
int wc_LmsKey_Sign(LmsKey* key, byte* sig, word32* sigSz, const byte* msg,
|
||||
@@ -1475,6 +1480,16 @@ int wc_LmsKey_Sign(LmsKey* key, byte* sig, word32* sigSz, const byte* msg,
|
||||
ret = wc_CryptoCb_PqcStatefulSigSign(msg, (word32)msgSz, sig, sigSz,
|
||||
WC_PQC_STATEFUL_SIG_TYPE_LMS, key);
|
||||
if (ret != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) {
|
||||
if (ret == WC_NO_ERR_TRACE(KEY_EXHAUSTED_E)) {
|
||||
/* Signature space exhausted. */
|
||||
WOLFSSL_MSG("error: no LMS signatures remaining");
|
||||
key->state = WC_LMS_STATE_NOSIGS;
|
||||
}
|
||||
else if (ret != 0) {
|
||||
/* The device may have consumed the one-time key without
|
||||
* committing the advanced state, so presume the key is bad. */
|
||||
key->state = WC_LMS_STATE_BAD;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ret = 0; /* fall through to software path */
|
||||
@@ -1498,6 +1513,9 @@ int wc_LmsKey_Sign(LmsKey* key, byte* sig, word32* sigSz, const byte* msg,
|
||||
/* Initialize working state for use. */
|
||||
ret = wc_lmskey_state_init(state, key->params);
|
||||
if (ret == 0) {
|
||||
/* Set the key state to bad by default. State is presumed bad
|
||||
* unless a correct sign and write operation happen together. */
|
||||
key->state = WC_LMS_STATE_BAD;
|
||||
/* Sign message. */
|
||||
ret = wc_hss_sign(state, key->priv_raw, &key->priv,
|
||||
key->priv_data, msg, (word32)msgSz, sig);
|
||||
@@ -1507,6 +1525,11 @@ int wc_LmsKey_Sign(LmsKey* key, byte* sig, word32* sigSz, const byte* msg,
|
||||
WC_FREE_VAR_EX(state, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
||||
}
|
||||
}
|
||||
if (ret == WC_NO_ERR_TRACE(KEY_EXHAUSTED_E)) {
|
||||
/* Signature space exhausted. */
|
||||
WOLFSSL_MSG("error: no LMS signatures remaining");
|
||||
key->state = WC_LMS_STATE_NOSIGS;
|
||||
}
|
||||
if (ret == 0) {
|
||||
*sigSz = (word32)key->params->sig_len;
|
||||
}
|
||||
@@ -1532,10 +1555,16 @@ int wc_LmsKey_Sign(LmsKey* key, byte* sig, word32* sigSz, const byte* msg,
|
||||
if (rv != WC_LMS_RC_SAVED_TO_NV_MEMORY) {
|
||||
/* Write to NV storage failed. Erase the signature from
|
||||
* memory to prevent OTS key reuse if state is rolled back. */
|
||||
WOLFSSL_MSG("error: LmsKey write_private_key failed");
|
||||
ForceZero(sig, key->params->sig_len);
|
||||
ret = IO_FAILED_E;
|
||||
}
|
||||
}
|
||||
if (ret == 0) {
|
||||
/* The advanced private key was committed to storage. The key is safe
|
||||
* to sign with again. */
|
||||
key->state = WC_LMS_STATE_OK;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user