mirror of https://github.com/wolfSSL/wolfssl.git
2423 lines
92 KiB
C
2423 lines
92 KiB
C
/* test_lms_xmss.c
|
|
*
|
|
* Copyright (C) 2006-2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfSSL.
|
|
*
|
|
* wolfSSL is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* wolfSSL is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
#include <tests/unit.h>
|
|
|
|
#ifdef NO_INLINE
|
|
#include <wolfssl/wolfcrypt/misc.h>
|
|
#else
|
|
#define WOLFSSL_MISC_INCLUDED
|
|
#include <wolfcrypt/src/misc.c>
|
|
#endif
|
|
|
|
#include <wolfssl/ssl.h>
|
|
#include <wolfssl/wolfcrypt/asn.h>
|
|
#ifdef HAVE_ECC
|
|
#include <wolfssl/wolfcrypt/ecc.h>
|
|
#endif
|
|
#ifdef WOLF_CRYPTO_CB
|
|
#include <wolfssl/wolfcrypt/cryptocb.h>
|
|
#endif
|
|
#include <tests/api/api.h>
|
|
#include <tests/utils.h>
|
|
#include <tests/api/test_lms_xmss.h>
|
|
|
|
/* getpid() gives the stateful LMS/XMSS test key files a per-process name so
|
|
* parallel unit.test runs in one directory do not clobber each other. */
|
|
#if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID)
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* LMS tests */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)
|
|
|
|
#include <wolfssl/wolfcrypt/wc_lms.h>
|
|
|
|
/* Per-process temp file: parallel unit.test runs (e.g. CI shards sharing a
|
|
* working directory) must not clobber each other's stateful LMS private key. */
|
|
static const char* lms_test_priv_key_file(void)
|
|
{
|
|
static char lmsPath[64];
|
|
if (lmsPath[0] == '\0') {
|
|
#if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID)
|
|
(void)XSNPRINTF(lmsPath, sizeof(lmsPath),
|
|
"./wolfssl_test_lms_%d.key", (int)getpid());
|
|
#else
|
|
(void)XSNPRINTF(lmsPath, sizeof(lmsPath), "./wolfssl_test_lms.key");
|
|
#endif
|
|
}
|
|
return lmsPath;
|
|
}
|
|
#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;
|
|
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)
|
|
ret = -1;
|
|
fclose(f);
|
|
return ret;
|
|
}
|
|
|
|
static int test_lms_read_key(byte* priv, word32 privSz, void* context)
|
|
{
|
|
FILE* f = fopen((const char*)context, "rb");
|
|
if (f == NULL)
|
|
return -1;
|
|
if (fread(priv, 1, privSz, f) == 0) {
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
fclose(f);
|
|
return WC_LMS_RC_READ_TO_MEMORY;
|
|
}
|
|
|
|
/* Helper: set the L1-H10-W8 params the tests share, H5 on small builds */
|
|
static int test_lms_set_params(LmsKey* key)
|
|
{
|
|
#if !defined(WOLFSSL_LMS_MAX_HEIGHT) || (WOLFSSL_LMS_MAX_HEIGHT >= 10)
|
|
return wc_LmsKey_SetParameters(key, 1, 10, 8);
|
|
#else
|
|
return wc_LmsKey_SetParameters(key, 1, 5, 8);
|
|
#endif
|
|
}
|
|
|
|
/* Helper: init an LMS key on devId with callbacks and L1-H10-W8 params */
|
|
static int test_lms_init_key_ex(LmsKey* key, int devId)
|
|
{
|
|
int ret;
|
|
|
|
ret = wc_LmsKey_Init(key, NULL, devId);
|
|
if (ret != 0) return ret;
|
|
|
|
ret = test_lms_set_params(key);
|
|
if (ret != 0) return ret;
|
|
|
|
ret = wc_LmsKey_SetWriteCb(key, test_lms_write_key);
|
|
if (ret != 0) return ret;
|
|
|
|
ret = wc_LmsKey_SetReadCb(key, test_lms_read_key);
|
|
if (ret != 0) return ret;
|
|
|
|
ret = wc_LmsKey_SetContext(key, (void*)LMS_TEST_PRIV_KEY_FILE);
|
|
if (ret != 0) return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Helper: init an LMS key with callbacks and L1-H10-W8 params */
|
|
static int test_lms_init_key(LmsKey* key, WC_RNG* rng)
|
|
{
|
|
(void)rng;
|
|
return test_lms_init_key_ex(key, INVALID_DEVID);
|
|
}
|
|
|
|
#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */
|
|
|
|
/*
|
|
* Test basic LMS sign/verify with multiple signings.
|
|
* Uses L1-H10-W8 (1024 total signatures, 32-entry leaf cache).
|
|
*/
|
|
int test_wc_LmsKey_sign_verify(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;
|
|
int i;
|
|
int numSigs = 5;
|
|
|
|
/* 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);
|
|
|
|
for (i = 0; i < numSigs; i++) {
|
|
sigSz = sizeof(sig);
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
|
ExpectIntEQ(wc_LmsKey_Verify(&key, sig, sigSz, msg, sizeof(msg)), 0);
|
|
}
|
|
|
|
wc_LmsKey_Free(&key);
|
|
wc_FreeRng(&rng);
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
#endif
|
|
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.
|
|
*
|
|
* Reproduces a heap-buffer-overflow bug in wc_lms_treehash_init() where the
|
|
* leaf cache write uses (i * hash_len) instead of ((i - leaf->idx) * hash_len).
|
|
* When q > max_cb (default 32), wc_LmsKey_Reload calls wc_hss_init_auth_path
|
|
* which calls wc_lms_treehash_init with q > 0, causing writes past the end of
|
|
* the leaf cache buffer.
|
|
*
|
|
* Reproduction steps:
|
|
* 1. Generate L1-H10-W8 key (cacheBits=5, max_cb=32)
|
|
* 2. Sign 33 times to advance q past the cache window
|
|
* 3. Free the key and reload from persisted state
|
|
* 4. Sign and verify after reload
|
|
*
|
|
* Without the fix: heap-buffer-overflow at wc_lms_impl.c:1965
|
|
* With the fix: all operations succeed, signatures verify
|
|
*/
|
|
int test_wc_LmsKey_reload_cache(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) && \
|
|
(!defined(WOLFSSL_LMS_MAX_HEIGHT) || (WOLFSSL_LMS_MAX_HEIGHT >= 10))
|
|
LmsKey key;
|
|
LmsKey vkey;
|
|
WC_RNG rng;
|
|
byte msg[] = "test message for LMS signing";
|
|
byte sig[2048];
|
|
word32 sigSz;
|
|
byte pub[64];
|
|
word32 pubSz = sizeof(pub);
|
|
int i;
|
|
/* Sign 33 times to advance q past the 32-entry cache window. */
|
|
int preSigs = 33;
|
|
|
|
/* Zero so cleanup is safe if an early alloc failure skips init. */
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(&vkey, 0, sizeof(vkey));
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
|
|
ExpectIntEQ(wc_InitRng(&rng), 0);
|
|
|
|
/* Phase 1: Generate key and sign past cache window */
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
|
|
|
|
for (i = 0; i < preSigs; i++) {
|
|
sigSz = sizeof(sig);
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
|
}
|
|
|
|
/* Save public key for verification after reload */
|
|
ExpectIntEQ(wc_LmsKey_ExportPubRaw(&key, pub, &pubSz), 0);
|
|
|
|
wc_LmsKey_Free(&key);
|
|
|
|
/* Phase 2: Reload key. Triggers wc_lms_treehash_init with q=33 */
|
|
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
|
|
ExpectIntEQ(wc_LmsKey_Reload(&key), 0);
|
|
|
|
/* Phase 3: Sign after reload and verify with separate verify-only key */
|
|
sigSz = sizeof(sig);
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
|
|
|
ExpectIntEQ(wc_LmsKey_Init(&vkey, NULL, INVALID_DEVID), 0);
|
|
#if !defined(WOLFSSL_LMS_MAX_HEIGHT) || (WOLFSSL_LMS_MAX_HEIGHT >= 10)
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&vkey, 1, 10, 8), 0);
|
|
#else
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&vkey, 1, 5, 8), 0);
|
|
#endif
|
|
ExpectIntEQ(wc_LmsKey_ImportPubRaw(&vkey, pub, pubSz), 0);
|
|
ExpectIntEQ(wc_LmsKey_Verify(&vkey, sig, sigSz, msg, sizeof(msg)), 0);
|
|
|
|
wc_LmsKey_Free(&vkey);
|
|
wc_LmsKey_Free(&key);
|
|
wc_FreeRng(&rng);
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* Crypto callback devId reload tests */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
/* XMSS-SHA2_10_256 is only in the algorithm table when SHA-256 and a height
|
|
* of 10 are both compiled in. */
|
|
#if defined(WC_XMSS_SHA256) && \
|
|
(WOLFSSL_WC_XMSS_MIN_HASH_SIZE <= 256) && \
|
|
(WOLFSSL_WC_XMSS_MAX_HASH_SIZE >= 256) && \
|
|
(WOLFSSL_XMSS_MIN_HEIGHT <= 10) && (WOLFSSL_XMSS_MAX_HEIGHT >= 10)
|
|
#define TEST_XMSS_H10_AVAILABLE
|
|
#endif
|
|
|
|
#ifdef WOLF_CRYPTO_CB
|
|
/* devId of the accelerators registered by the reload tests below. */
|
|
#define TEST_LMS_XMSS_CRYPTOCB_DEVID 0x4C4D5853 /* "LMXS" */
|
|
#endif
|
|
|
|
/* Must be the exact union of the two test guards below, or the callback has
|
|
* no caller and -Wunused-function fails the build. */
|
|
#if defined(WOLF_CRYPTO_CB) && \
|
|
((defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) && \
|
|
!defined(NO_FILESYSTEM)) || \
|
|
(defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) && \
|
|
!defined(NO_FILESYSTEM) && defined(TEST_XMSS_H10_AVAILABLE)))
|
|
|
|
/* An accelerator with no stateful hash-based signature support: declines
|
|
* everything, so the software implementation is used. */
|
|
static int test_lms_xmss_cryptocb(int devIdArg, wc_CryptoInfo* info, void* ctx)
|
|
{
|
|
(void)devIdArg;
|
|
(void)info;
|
|
(void)ctx;
|
|
return CRYPTOCB_UNAVAILABLE;
|
|
}
|
|
#endif
|
|
|
|
/* Must be the exact union of the two verify test guards below. */
|
|
#if defined(WOLF_CRYPTO_CB) && \
|
|
((defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)) || \
|
|
(defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) && \
|
|
defined(TEST_XMSS_H10_AVAILABLE)))
|
|
|
|
/* Number of stateful signature verifications the accelerator below answered. */
|
|
static int test_lms_xmss_verify_calls;
|
|
|
|
/* An accelerator that owns the key: it answers stateful signature
|
|
* verification and declines everything else. */
|
|
static int test_lms_xmss_verify_cryptocb(int devIdArg, wc_CryptoInfo* info,
|
|
void* ctx)
|
|
{
|
|
(void)devIdArg;
|
|
(void)ctx;
|
|
|
|
if ((info != NULL) && (info->algo_type == WC_ALGO_TYPE_PK) &&
|
|
(info->pk.type == WC_PK_TYPE_PQC_STATEFUL_SIG_VERIFY)) {
|
|
test_lms_xmss_verify_calls++;
|
|
*info->pk.pqc_stateful_sig_verify.res = 1;
|
|
return 0;
|
|
}
|
|
return CRYPTOCB_UNAVAILABLE;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Test reloading an LMS key on a devId that keeps its state in software, the
|
|
* case of a devId set only to route other algorithms to an accelerator. The
|
|
* second half covers the device-backed arm, where no read callback is set.
|
|
*
|
|
* Without the fix: Reload does no work, priv_data is left NULL and the
|
|
* following sign dereferences it.
|
|
* With the fix: the key is reloaded, and sign/verify succeed.
|
|
*/
|
|
int test_wc_LmsKey_reload_devid(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) && \
|
|
defined(WOLF_CRYPTO_CB) && !defined(NO_FILESYSTEM)
|
|
LmsKey key;
|
|
LmsKey vkey;
|
|
LmsKey hsmKey;
|
|
WC_RNG rng;
|
|
byte msg[] = "test message for LMS signing";
|
|
byte sig[2048];
|
|
word32 sigSz;
|
|
byte pub[64];
|
|
word32 pubSz = sizeof(pub);
|
|
|
|
/* Zero so cleanup is safe if an early alloc failure skips init. */
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(&vkey, 0, sizeof(vkey));
|
|
XMEMSET(&hsmKey, 0, sizeof(hsmKey));
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_LMS_XMSS_CRYPTOCB_DEVID,
|
|
test_lms_xmss_cryptocb, NULL), 0);
|
|
ExpectIntEQ(wc_InitRng(&rng), 0);
|
|
|
|
/* Generate on the devId. MakeKey falls back to software when the callback
|
|
* declines. */
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(test_lms_init_key_ex(&key, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0);
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
|
|
ExpectIntEQ(wc_LmsKey_ExportPubRaw(&key, pub, &pubSz), 0);
|
|
wc_LmsKey_Free(&key);
|
|
|
|
/* Reload the same key on the same devId. */
|
|
ExpectIntEQ(test_lms_init_key_ex(&key, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0);
|
|
ExpectIntEQ(wc_LmsKey_Reload(&key), 0);
|
|
/* The reload must have expanded the private key, not been skipped. */
|
|
ExpectNotNull(key.priv_data);
|
|
|
|
/* Sign with the reloaded key and verify with a software-only key. */
|
|
sigSz = sizeof(sig);
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
|
|
|
ExpectIntEQ(wc_LmsKey_Init(&vkey, NULL, INVALID_DEVID), 0);
|
|
#if !defined(WOLFSSL_LMS_MAX_HEIGHT) || (WOLFSSL_LMS_MAX_HEIGHT >= 10)
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&vkey, 1, 10, 8), 0);
|
|
#else
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&vkey, 1, 5, 8), 0);
|
|
#endif
|
|
ExpectIntEQ(wc_LmsKey_ImportPubRaw(&vkey, pub, pubSz), 0);
|
|
ExpectIntEQ(wc_LmsKey_Verify(&vkey, sig, sigSz, msg, sizeof(msg)), 0);
|
|
|
|
/* Device-backed arm: no read callback, so the reload must still be a
|
|
* no-op. */
|
|
ExpectIntEQ(wc_LmsKey_Init(&hsmKey, NULL, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0);
|
|
ExpectIntEQ(test_lms_set_params(&hsmKey), 0);
|
|
ExpectIntEQ(wc_LmsKey_Reload(&hsmKey), 0);
|
|
ExpectNull(hsmKey.priv_data);
|
|
|
|
wc_LmsKey_Free(&hsmKey);
|
|
wc_LmsKey_Free(&vkey);
|
|
wc_LmsKey_Free(&key);
|
|
wc_FreeRng(&rng);
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
wc_CryptoCb_UnRegisterDevice(TEST_LMS_XMSS_CRYPTOCB_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Test that a reloaded LMS key refuses to hand out a public key.
|
|
*
|
|
* Neither Reload arm populates key->pub: the software path passes NULL as
|
|
* wc_hss_reload_key()'s pub_root, and the HSM path does no work at all. The
|
|
* key still reaches WC_LMS_STATE_OK so that it can sign.
|
|
*
|
|
* Without the fix: export returns 0 and an all-zero public key.
|
|
* With the fix: export and verify return BAD_STATE_E, signing still works.
|
|
*/
|
|
int test_wc_LmsKey_reload_no_pub(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) && \
|
|
!defined(NO_FILESYSTEM)
|
|
LmsKey key;
|
|
LmsKey dst;
|
|
WC_RNG rng;
|
|
byte msg[] = "test message for LMS signing";
|
|
byte sig[2048];
|
|
word32 sigSz;
|
|
byte pub[64];
|
|
word32 pubSz;
|
|
|
|
/* Zero so cleanup is safe if an early alloc failure skips init. */
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(&dst, 0, sizeof(dst));
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
|
|
ExpectIntEQ(wc_InitRng(&rng), 0);
|
|
|
|
/* A generated key holds its public key and exports it. */
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
|
|
pubSz = sizeof(pub);
|
|
ExpectIntEQ(wc_LmsKey_ExportPubRaw(&key, pub, &pubSz), 0);
|
|
wc_LmsKey_Free(&key);
|
|
|
|
/* The same key reloaded from storage signs but holds no public key. */
|
|
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
|
|
ExpectIntEQ(wc_LmsKey_Reload(&key), 0);
|
|
sigSz = sizeof(sig);
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
|
|
|
pubSz = sizeof(pub);
|
|
ExpectIntEQ(wc_LmsKey_ExportPubRaw(&key, pub, &pubSz),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
ExpectIntEQ(wc_LmsKey_ExportPub_ex(&dst, &key, NULL, INVALID_DEVID),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
ExpectIntEQ(wc_LmsKey_Verify(&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 that a device-backed LMS key reloaded from its device can still be
|
|
* verified through the crypto callback.
|
|
*
|
|
* The HSM reload arm leaves the local public key unset by design, so the
|
|
* public-key requirement must not be enforced before the device dispatch.
|
|
*
|
|
* Without the fix: Verify returns BAD_STATE_E and the callback never runs.
|
|
* With the fix: the callback answers and Verify succeeds.
|
|
*/
|
|
int test_wc_LmsKey_reload_devid_verify(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY) && \
|
|
defined(WOLF_CRYPTO_CB)
|
|
LmsKey key;
|
|
byte msg[] = "test message for LMS signing";
|
|
byte* sig = NULL;
|
|
word32 sigSz = 0;
|
|
|
|
/* Zero so cleanup is safe if an early alloc failure skips init. */
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
test_lms_xmss_verify_calls = 0;
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_LMS_XMSS_CRYPTOCB_DEVID,
|
|
test_lms_xmss_verify_cryptocb, NULL), 0);
|
|
|
|
/* No read callback, so the reload leaves the state with the device. */
|
|
ExpectIntEQ(wc_LmsKey_Init(&key, NULL, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0);
|
|
ExpectIntEQ(test_lms_set_params(&key), 0);
|
|
ExpectIntEQ(wc_LmsKey_Reload(&key), 0);
|
|
ExpectNull(key.priv_data);
|
|
|
|
/* Only the signature length is checked before the device is asked, so the
|
|
* buffer contents do not matter. */
|
|
ExpectIntEQ(wc_LmsKey_GetSigLen(&key, &sigSz), 0);
|
|
ExpectIntGT(sigSz, 0);
|
|
ExpectNotNull(sig = (byte*)XMALLOC(sigSz, NULL, DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (sig != NULL) {
|
|
XMEMSET(sig, 0, sigSz);
|
|
ExpectIntEQ(wc_LmsKey_Verify(&key, sig, sigSz, msg, sizeof(msg)), 0);
|
|
ExpectIntEQ(test_lms_xmss_verify_calls, 1);
|
|
}
|
|
|
|
XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
wc_LmsKey_Free(&key);
|
|
wc_CryptoCb_UnRegisterDevice(TEST_LMS_XMSS_CRYPTOCB_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) && \
|
|
!defined(NO_FILESYSTEM) && defined(TEST_XMSS_H10_AVAILABLE)
|
|
/* Per-process temp file so parallel unit.test runs sharing a working
|
|
* directory do not clobber each other's stateful XMSS private key. */
|
|
static const char* xmss_devid_priv_key_file(void)
|
|
{
|
|
static char xmssPath[64];
|
|
if (xmssPath[0] == '\0') {
|
|
#if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID)
|
|
(void)XSNPRINTF(xmssPath, sizeof(xmssPath),
|
|
"./wolfssl_test_xmss_devid_%d.key", (int)getpid());
|
|
#else
|
|
(void)XSNPRINTF(xmssPath, sizeof(xmssPath),
|
|
"./wolfssl_test_xmss_devid.key");
|
|
#endif
|
|
}
|
|
return xmssPath;
|
|
}
|
|
#define XMSS_DEVID_TEST_PRIV_KEY_FILE xmss_devid_priv_key_file()
|
|
|
|
static enum wc_XmssRc xmss_devid_write_key(const byte* priv, word32 privSz,
|
|
void* context)
|
|
{
|
|
XFILE f = XFOPEN((const char*)context, "wb");
|
|
enum wc_XmssRc ret = WC_XMSS_RC_SAVED_TO_NV_MEMORY;
|
|
if (f == XBADFILE)
|
|
return WC_XMSS_RC_WRITE_FAIL;
|
|
if (XFWRITE(priv, 1, privSz, f) != privSz)
|
|
ret = WC_XMSS_RC_WRITE_FAIL;
|
|
XFCLOSE(f);
|
|
return ret;
|
|
}
|
|
|
|
static enum wc_XmssRc xmss_devid_read_key(byte* priv, word32 privSz,
|
|
void* context)
|
|
{
|
|
XFILE f = XFOPEN((const char*)context, "rb");
|
|
enum wc_XmssRc ret = WC_XMSS_RC_READ_TO_MEMORY;
|
|
if (f == XBADFILE)
|
|
return WC_XMSS_RC_READ_FAIL;
|
|
if (XFREAD(priv, 1, privSz, f) != privSz)
|
|
ret = WC_XMSS_RC_READ_FAIL;
|
|
XFCLOSE(f);
|
|
return ret;
|
|
}
|
|
|
|
/* Init an XMSS key on devId with the reload test's persistence callbacks. */
|
|
static int test_xmss_init_key_ex(XmssKey* key, int devId)
|
|
{
|
|
int ret = wc_XmssKey_Init(key, NULL, devId);
|
|
if (ret == 0)
|
|
ret = wc_XmssKey_SetParamStr(key, "XMSS-SHA2_10_256");
|
|
if (ret == 0)
|
|
ret = wc_XmssKey_SetWriteCb(key, xmss_devid_write_key);
|
|
if (ret == 0)
|
|
ret = wc_XmssKey_SetReadCb(key, xmss_devid_read_key);
|
|
if (ret == 0)
|
|
ret = wc_XmssKey_SetContext(key, (void*)XMSS_DEVID_TEST_PRIV_KEY_FILE);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Same scenario as test_wc_LmsKey_reload_devid, for XMSS.
|
|
*
|
|
* Without the fix: Reload does no work, so key->sk is never allocated and the
|
|
* following sign either fails or writes through NULL.
|
|
* With the fix: the key is reloaded, and sign/verify succeed.
|
|
*/
|
|
int test_wc_XmssKey_reload_devid(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) && \
|
|
defined(WOLF_CRYPTO_CB) && !defined(NO_FILESYSTEM) && \
|
|
defined(TEST_XMSS_H10_AVAILABLE)
|
|
XmssKey key;
|
|
XmssKey vkey;
|
|
XmssKey hsmKey;
|
|
WC_RNG rng;
|
|
byte msg[] = "test message for XMSS signing";
|
|
byte sig[4096];
|
|
word32 sigSz;
|
|
byte pub[128];
|
|
word32 pubSz = sizeof(pub);
|
|
|
|
/* Zero so cleanup is safe if an early alloc failure skips init. */
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(&vkey, 0, sizeof(vkey));
|
|
XMEMSET(&hsmKey, 0, sizeof(hsmKey));
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_LMS_XMSS_CRYPTOCB_DEVID,
|
|
test_lms_xmss_cryptocb, NULL), 0);
|
|
ExpectIntEQ(wc_InitRng(&rng), 0);
|
|
|
|
(void)remove(XMSS_DEVID_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(test_xmss_init_key_ex(&key, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_MakeKey(&key, &rng), 0);
|
|
ExpectIntEQ(wc_XmssKey_ExportPubRaw(&key, pub, &pubSz), 0);
|
|
wc_XmssKey_Free(&key);
|
|
|
|
/* Reload the same key on the same devId. */
|
|
ExpectIntEQ(test_xmss_init_key_ex(&key, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_Reload(&key), 0);
|
|
/* The reload must have allocated the secret key, not been skipped. */
|
|
ExpectNotNull(key.sk);
|
|
|
|
/* Sign with the reloaded key and verify with a software-only key. */
|
|
sigSz = sizeof(sig);
|
|
ExpectIntEQ(wc_XmssKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
|
|
|
ExpectIntEQ(wc_XmssKey_Init(&vkey, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&vkey, "XMSS-SHA2_10_256"), 0);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw(&vkey, pub, pubSz), 0);
|
|
ExpectIntEQ(wc_XmssKey_Verify(&vkey, sig, sigSz, msg, sizeof(msg)), 0);
|
|
|
|
/* The device-backed arm: no read callback means the device holds the
|
|
* state, so the reload must still be a no-op. */
|
|
ExpectIntEQ(wc_XmssKey_Init(&hsmKey, NULL, TEST_LMS_XMSS_CRYPTOCB_DEVID),
|
|
0);
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&hsmKey, "XMSS-SHA2_10_256"), 0);
|
|
ExpectIntEQ(wc_XmssKey_Reload(&hsmKey), 0);
|
|
ExpectNull(hsmKey.sk);
|
|
|
|
wc_XmssKey_Free(&hsmKey);
|
|
wc_XmssKey_Free(&vkey);
|
|
wc_XmssKey_Free(&key);
|
|
wc_FreeRng(&rng);
|
|
(void)remove(XMSS_DEVID_TEST_PRIV_KEY_FILE);
|
|
wc_CryptoCb_UnRegisterDevice(TEST_LMS_XMSS_CRYPTOCB_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Same scenario as test_wc_LmsKey_reload_no_pub, for XMSS. The software
|
|
* reload reads the secret key only to sanity-check it and ForceZeros it
|
|
* immediately, so key->pk is never populated.
|
|
*/
|
|
int test_wc_XmssKey_reload_no_pub(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) && \
|
|
!defined(NO_FILESYSTEM) && defined(TEST_XMSS_H10_AVAILABLE)
|
|
XmssKey key;
|
|
XmssKey dst;
|
|
WC_RNG rng;
|
|
byte msg[] = "test message for XMSS signing";
|
|
byte sig[4096];
|
|
word32 sigSz;
|
|
byte pub[128];
|
|
word32 pubSz;
|
|
|
|
/* Zero so cleanup is safe if an early alloc failure skips init. */
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
XMEMSET(&dst, 0, sizeof(dst));
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
|
|
ExpectIntEQ(wc_InitRng(&rng), 0);
|
|
|
|
/* A generated key holds its public key and exports it. */
|
|
(void)remove(XMSS_DEVID_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(test_xmss_init_key_ex(&key, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_MakeKey(&key, &rng), 0);
|
|
pubSz = sizeof(pub);
|
|
ExpectIntEQ(wc_XmssKey_ExportPubRaw(&key, pub, &pubSz), 0);
|
|
wc_XmssKey_Free(&key);
|
|
|
|
/* The same key reloaded from storage signs but holds no public key. */
|
|
ExpectIntEQ(test_xmss_init_key_ex(&key, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_Reload(&key), 0);
|
|
sigSz = sizeof(sig);
|
|
ExpectIntEQ(wc_XmssKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
|
|
|
pubSz = sizeof(pub);
|
|
ExpectIntEQ(wc_XmssKey_ExportPubRaw(&key, pub, &pubSz),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
ExpectIntEQ(wc_XmssKey_ExportPub_ex(&dst, &key, NULL, INVALID_DEVID),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
ExpectIntEQ(wc_XmssKey_Verify(&key, sig, sigSz, msg, sizeof(msg)),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
|
|
wc_XmssKey_Free(&key);
|
|
wc_FreeRng(&rng);
|
|
(void)remove(XMSS_DEVID_TEST_PRIV_KEY_FILE);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*
|
|
* Same scenario as test_wc_LmsKey_reload_devid_verify, for XMSS.
|
|
*/
|
|
int test_wc_XmssKey_reload_devid_verify(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY) && \
|
|
defined(WOLF_CRYPTO_CB) && defined(TEST_XMSS_H10_AVAILABLE)
|
|
XmssKey key;
|
|
byte msg[] = "test message for XMSS signing";
|
|
byte* sig = NULL;
|
|
word32 sigSz = 0;
|
|
|
|
/* Zero so cleanup is safe if an early alloc failure skips init. */
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
test_lms_xmss_verify_calls = 0;
|
|
|
|
ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_LMS_XMSS_CRYPTOCB_DEVID,
|
|
test_lms_xmss_verify_cryptocb, NULL), 0);
|
|
|
|
/* No read callback, so the reload leaves the state with the device. */
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, TEST_LMS_XMSS_CRYPTOCB_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&key, "XMSS-SHA2_10_256"), 0);
|
|
ExpectIntEQ(wc_XmssKey_Reload(&key), 0);
|
|
ExpectNull(key.sk);
|
|
|
|
/* Only the signature length is checked before the device is asked, so the
|
|
* buffer contents do not matter. */
|
|
ExpectIntEQ(wc_XmssKey_GetSigLen(&key, &sigSz), 0);
|
|
ExpectIntGT(sigSz, 0);
|
|
ExpectNotNull(sig = (byte*)XMALLOC(sigSz, NULL, DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (sig != NULL) {
|
|
XMEMSET(sig, 0, sigSz);
|
|
ExpectIntEQ(wc_XmssKey_Verify(&key, sig, sigSz, msg, sizeof(msg)), 0);
|
|
ExpectIntEQ(test_lms_xmss_verify_calls, 1);
|
|
}
|
|
|
|
XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
wc_XmssKey_Free(&key);
|
|
wc_CryptoCb_UnRegisterDevice(TEST_LMS_XMSS_CRYPTOCB_DEVID);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* RFC 9802 (HSS/LMS and XMSS/XMSS^MT in X.509) tests */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
/* For every committed self-signed test certificate confirm:
|
|
* - wc_ParseCert succeeds on the RFC 9802 AlgorithmIdentifier encoding
|
|
* (OID-only SEQUENCE, no NULL parameters)
|
|
* - keyOID and signatureOID are set to the expected values
|
|
* - loading as a trust anchor and verifying the same bytes through
|
|
* wolfSSL_CertManagerVerifyBuffer exercises the ConfirmSignature
|
|
* path and succeeds on a valid cert
|
|
* - flipping a byte in the signature AND flipping a byte in the
|
|
* TBSCertificate both cause verification to fail.
|
|
*
|
|
* Test vectors are in certs/lms/ and certs/xmss/, generated with Bouncy
|
|
* Castle 1.81. BC's default XMSS / XMSS^MT X.509 encoding uses pre-
|
|
* standard ISARA OIDs and wraps the raw RFC 8391 pub key in an OCTET
|
|
* STRING, so the fixtures were produced with a small generator that
|
|
* overrides the AlgorithmIdentifier and SPKI to match RFC 9802. */
|
|
/* Only the LMS interop-anchor verification still loads a committed fixture
|
|
* (bc_lms_native_bc_root.der); everything else is generated in-process. Gate
|
|
* these file helpers on exactly that call site to avoid an unused-function
|
|
* warning in XMSS-only or truncated-hash builds. */
|
|
#if defined(WOLFSSL_HAVE_LMS) && !defined(NO_FILESYSTEM) && \
|
|
!defined(NO_CERTS) && !defined(WOLFSSL_NO_LMS_SHA256_256)
|
|
/* Sanity bound on a test fixture cert. 1 MiB is well above any realistic
|
|
* RFC 9802 cert and catches a wild XFTELL. Typed as
|
|
* long to match XFTELL's return so the size comparison below isn't
|
|
* a mixed long-vs-int compare. */
|
|
#define RFC9802_TEST_MAX_CERT_SIZE ((long)(1L << 20))
|
|
|
|
/* Load a whole file into a freshly-allocated buffer. Caller frees. */
|
|
static int rfc9802_load_file(const char* path, byte** out, int* outLen)
|
|
{
|
|
EXPECT_DECLS;
|
|
XFILE f = XBADFILE;
|
|
long sz = 0;
|
|
size_t got = 0;
|
|
byte* buf = NULL;
|
|
|
|
*out = NULL;
|
|
*outLen = 0;
|
|
ExpectTrue((f = XFOPEN(path, "rb")) != XBADFILE);
|
|
if (f == XBADFILE)
|
|
return TEST_FAIL;
|
|
if (XFSEEK(f, 0, XSEEK_END) == 0)
|
|
sz = XFTELL(f);
|
|
(void)XFSEEK(f, 0, XSEEK_SET);
|
|
ExpectIntGT(sz, 0);
|
|
ExpectIntLT(sz, RFC9802_TEST_MAX_CERT_SIZE);
|
|
/* Hard-fail before XMALLOC if XFSEEK / XFTELL produced an unusable
|
|
* size: ExpectInt* records the failure but doesn't short-circuit,
|
|
* so without this guard a -1 from XFTELL would cast to a multi-GiB
|
|
* (size_t) allocation, and a 0 would request a zero-byte malloc. */
|
|
if (sz <= 0 || sz >= RFC9802_TEST_MAX_CERT_SIZE) {
|
|
XFCLOSE(f);
|
|
return TEST_FAIL;
|
|
}
|
|
ExpectNotNull(buf = (byte*)XMALLOC((size_t)sz, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (buf != NULL) {
|
|
got = XFREAD(buf, 1, (size_t)sz, f);
|
|
ExpectIntEQ(got, (size_t)sz);
|
|
/* On a short read the caller would otherwise proceed with a
|
|
* partially-initialized buffer and produce cascading parse
|
|
* failures driven by the uninitialized tail. Free here so the
|
|
* caller's `if (buf == NULL) return TEST_FAIL;` short-circuits
|
|
* cleanly with a single recorded failure. */
|
|
if (got != (size_t)sz) {
|
|
XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
buf = NULL;
|
|
sz = 0;
|
|
}
|
|
}
|
|
XFCLOSE(f);
|
|
*out = buf;
|
|
*outLen = (int)sz;
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
static WC_MAYBE_UNUSED int rfc9802_verify_one_cert(const char* path,
|
|
word32 expectedKeyOID, word32 expectedSigOID)
|
|
{
|
|
EXPECT_DECLS;
|
|
byte* buf = NULL;
|
|
byte* tampered = NULL;
|
|
int bytes = 0;
|
|
DecodedCert cert;
|
|
WOLFSSL_CERT_MANAGER* cm = NULL;
|
|
word32 certBegin = 0;
|
|
word32 sigIndex = 0;
|
|
|
|
ExpectIntEQ(rfc9802_load_file(path, &buf, &bytes), TEST_SUCCESS);
|
|
if (buf == NULL)
|
|
return TEST_FAIL;
|
|
|
|
/* Parse + check OIDs, capture certBegin and sigIndex for later tamper. */
|
|
wc_InitDecodedCert(&cert, buf, (word32)bytes, NULL);
|
|
ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0);
|
|
ExpectIntEQ((int)cert.keyOID, (int)expectedKeyOID);
|
|
ExpectIntEQ((int)cert.signatureOID, (int)expectedSigOID);
|
|
certBegin = cert.certBegin;
|
|
sigIndex = cert.sigIndex;
|
|
wc_FreeDecodedCert(&cert);
|
|
|
|
/* Full verify against a self-installed trust anchor. */
|
|
ExpectNotNull(cm = wolfSSL_CertManagerNew());
|
|
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, buf, (long)bytes,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, buf, (long)bytes,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
if (cm != NULL) {
|
|
wolfSSL_CertManagerFree(cm);
|
|
cm = NULL;
|
|
}
|
|
|
|
ExpectNotNull(tampered = (byte*)XMALLOC((size_t)bytes, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
|
|
/* Negative 1: flip a byte inside the signatureValue BIT STRING.
|
|
* Everything after sigIndex is the signatureAlgorithm + the BIT
|
|
* STRING payload, so flipping the last byte is always inside the
|
|
* signature content. */
|
|
if (tampered != NULL) {
|
|
XMEMCPY(tampered, buf, (size_t)bytes);
|
|
tampered[bytes - 1] ^= 0x01;
|
|
ExpectNotNull(cm = wolfSSL_CertManagerNew());
|
|
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, buf, (long)bytes,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, tampered,
|
|
(long)bytes, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
if (cm != NULL) {
|
|
wolfSSL_CertManagerFree(cm);
|
|
cm = NULL;
|
|
}
|
|
}
|
|
|
|
/* Negative 2: flip a byte at the midpoint of the TBSCertificate. The
|
|
* TBS is the first element of the outer Certificate SEQUENCE and
|
|
* its bytes lie between (certBegin + outerSeqHeader) and sigIndex.
|
|
* Picking the midpoint ensures we're inside TBS regardless of the
|
|
* fixture's DN / extensions layout. */
|
|
if (tampered != NULL && sigIndex > certBegin + 8U) {
|
|
word32 midTbs = certBegin + 8 + ((sigIndex - (certBegin + 8)) / 2);
|
|
XMEMCPY(tampered, buf, (size_t)bytes);
|
|
tampered[midTbs] ^= 0x01;
|
|
ExpectNotNull(cm = wolfSSL_CertManagerNew());
|
|
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, buf, (long)bytes,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, tampered,
|
|
(long)bytes, WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
if (cm != NULL) {
|
|
wolfSSL_CertManagerFree(cm);
|
|
cm = NULL;
|
|
}
|
|
}
|
|
|
|
/* The fixtures MUST carry a KeyUsage extension with at least one of
|
|
* digitalSignature / nonRepudiation / keyCertSign / cRLSign set per
|
|
* RFC 9802 sec 3. Re-parse and assert that wolfSSL recorded a non-
|
|
* empty set of KeyUsage bits from one of those values. */
|
|
wc_InitDecodedCert(&cert, buf, (word32)bytes, NULL);
|
|
ExpectIntEQ(wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL), 0);
|
|
ExpectIntEQ(cert.extKeyUsageSet, 1);
|
|
ExpectIntNE(cert.extKeyUsage & (KEYUSE_DIGITAL_SIG | KEYUSE_CONTENT_COMMIT |
|
|
KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN), 0);
|
|
wc_FreeDecodedCert(&cert);
|
|
|
|
XFREE(tampered, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
return EXPECT_RESULT();
|
|
}
|
|
#endif
|
|
|
|
/* Direct wolfCrypt-level negative tests for the parameter-derivation
|
|
* helpers used by the RFC 9802 parse path. These exercise failure modes
|
|
* (unknown algorithm bytes, truncated inputs, mismatches) that a real
|
|
* cert body wouldn't easily reach. */
|
|
#if defined(WOLFSSL_HAVE_LMS)
|
|
static int rfc9802_lms_import_negative(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
LmsKey key;
|
|
/* 60-byte buffer matches HSS_PUBLIC_KEY_LEN(32), just like a valid
|
|
* SHA-256/M32/H5 key; the algorithm-type bytes are junk so param
|
|
* derivation must fail cleanly. */
|
|
byte junk[60];
|
|
|
|
XMEMSET(junk, 0, sizeof(junk));
|
|
/* levels=1, lmsType=0xFFFFFFFF, lmOtsType=0xFFFFFFFF. */
|
|
junk[3] = 1;
|
|
XMEMSET(junk + 4, 0xFF, 4);
|
|
XMEMSET(junk + 8, 0xFF, 4);
|
|
|
|
/* Unknown algorithm types must be rejected. */
|
|
ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_LmsKey_ImportPubRaw(&key, junk, sizeof(junk)),
|
|
WC_NO_ERR_TRACE(NOT_COMPILED_IN));
|
|
wc_LmsKey_Free(&key);
|
|
|
|
/* Too-short buffer: only L + lmsType, no lmOtsType. */
|
|
ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_LmsKey_ImportPubRaw(&key, junk, 8),
|
|
WC_NO_ERR_TRACE(BUFFER_E));
|
|
wc_LmsKey_Free(&key);
|
|
|
|
#if !defined(WOLFSSL_NO_LMS_SHA256_256)
|
|
/* The two cases below pin specific SHA-256/M32 parameter codes
|
|
* (L1_H5_W8, L1_H5_W4, L1_H10_W2). Skip them in builds where the
|
|
* SHA-256/M32 family is disabled -- the family-agnostic checks
|
|
* above (junk algorithm types, too-short buffer, GetSigLen on
|
|
* unconfigured key) still cover the universal invariants. */
|
|
|
|
/* Pre-set params that disagree with the raw key's algorithm bytes:
|
|
* configure H=5/W=8 but feed buffer that claims H=10 / W=2. */
|
|
XMEMSET(junk, 0, sizeof(junk));
|
|
junk[3] = 1; /* levels=1 */
|
|
junk[7] = 6; /* lmsType = LMS_SHA256_M32_H10 = 6 */
|
|
junk[11] = 2; /* lmOtsType = LMOTS_SHA256_N32_W2 = 2 */
|
|
ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&key, 1, 5, 8), 0);
|
|
ExpectIntEQ(wc_LmsKey_ImportPubRaw(&key, junk, sizeof(junk)),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
wc_LmsKey_Free(&key);
|
|
#endif /* !WOLFSSL_NO_LMS_SHA256_256 */
|
|
|
|
/* GetSigLen on a key with no params set must not NULL-deref the
|
|
* params pointer; it must return BAD_FUNC_ARG instead. */
|
|
{
|
|
word32 sigLen = 0;
|
|
ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_LmsKey_GetSigLen(&key, &sigLen),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
wc_LmsKey_Free(&key);
|
|
}
|
|
|
|
#if !defined(WOLFSSL_NO_LMS_SHA256_256)
|
|
/* Partial-write invariant: a length mismatch after a successful
|
|
* auto-derive must leave key->params NULL. Build a buffer whose
|
|
* leading u32str(L) || lmsType || lmOtsType identifies a known
|
|
* parameter set, but truncate to one byte less than the real pub
|
|
* key length so the post-derive length check fails. */
|
|
{
|
|
byte truncated[59]; /* HSS_PUBLIC_KEY_LEN(32) is 60 */
|
|
XMEMSET(truncated, 0, sizeof(truncated));
|
|
truncated[3] = 1; /* L = 1 */
|
|
truncated[7] = 5; /* lmsType = LMS_SHA256_M32_H5 */
|
|
truncated[11] = 4; /* lmOtsType = LMOTS_SHA256_N32_W4 */
|
|
ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectNull(key.params);
|
|
ExpectIntEQ(wc_LmsKey_ImportPubRaw(&key, truncated,
|
|
sizeof(truncated)), WC_NO_ERR_TRACE(BUFFER_E));
|
|
ExpectNull(key.params);
|
|
wc_LmsKey_Free(&key);
|
|
}
|
|
#endif /* !WOLFSSL_NO_LMS_SHA256_256 */
|
|
|
|
return EXPECT_RESULT();
|
|
}
|
|
#endif
|
|
|
|
#if defined(WOLFSSL_HAVE_XMSS)
|
|
static int rfc9802_xmss_import_negative(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
XmssKey key;
|
|
byte junk[8];
|
|
|
|
XMEMSET(junk, 0, sizeof(junk));
|
|
|
|
/* Too-short buffer. */
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, junk, 2, 0),
|
|
WC_NO_ERR_TRACE(BUFFER_E));
|
|
wc_XmssKey_Free(&key);
|
|
|
|
/* Unknown OID (all-zero) for both XMSS and XMSS^MT. */
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, junk, sizeof(junk), 0),
|
|
WC_NO_ERR_TRACE(NOT_COMPILED_IN));
|
|
wc_XmssKey_Free(&key);
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, junk, sizeof(junk), 1),
|
|
WC_NO_ERR_TRACE(NOT_COMPILED_IN));
|
|
wc_XmssKey_Free(&key);
|
|
|
|
/* NULL key / input. */
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(NULL, junk, sizeof(junk), 0),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, NULL, 8, 0),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
wc_XmssKey_Free(&key);
|
|
|
|
/* GetSigLen on a key with no params set must not NULL-deref the
|
|
* params pointer; it must return BAD_FUNC_ARG instead. */
|
|
{
|
|
word32 sigLen = 0;
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_GetSigLen(&key, &sigLen),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
wc_XmssKey_Free(&key);
|
|
}
|
|
|
|
#if !defined(WOLFSSL_XMSS_MIN_HEIGHT) || (WOLFSSL_XMSS_MIN_HEIGHT <= 10)
|
|
/* Once params have been configured (state != INITED), the OID
|
|
* prefix in the raw key MUST match key->oid and is_xmssmt MUST
|
|
* match key->is_xmssmt. Set XMSS-SHA2_10_256 and feed a valid-
|
|
* sized buffer whose 4-byte OID prefix is bogus -> BAD_FUNC_ARG. */
|
|
{
|
|
byte mismatch[XMSS_SHA256_PUBLEN];
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&key, "XMSS-SHA2_10_256"), 0);
|
|
XMEMSET(mismatch, 0, sizeof(mismatch));
|
|
mismatch[3] = 0x77; /* nonsense OID */
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, mismatch,
|
|
sizeof(mismatch), 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
/* Same buffer with the correct OID, but is_xmssmt hint
|
|
* contradicts the configured family -> BAD_FUNC_ARG. */
|
|
mismatch[3] = 0x01; /* WC_XMSS_OID_SHA2_10_256 */
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, mismatch,
|
|
sizeof(mismatch), 1), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
wc_XmssKey_Free(&key);
|
|
}
|
|
|
|
/* Partial-write invariant: a length mismatch after a successful
|
|
* auto-derive must leave the key in its INITED state, with
|
|
* key->params NULL. */
|
|
{
|
|
byte truncated[XMSS_SHA256_PUBLEN - 1];
|
|
XMEMSET(truncated, 0, sizeof(truncated));
|
|
truncated[3] = 0x01;
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectNull(key.params);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, truncated,
|
|
sizeof(truncated), 0), WC_NO_ERR_TRACE(BUFFER_E));
|
|
ExpectNull(key.params);
|
|
wc_XmssKey_Free(&key);
|
|
}
|
|
|
|
/* is_xmssmt disambiguation: XMSS oid=1 and XMSS^MT oid=1 share
|
|
* the wire-numeric value but resolve to different parameter sets.
|
|
* Importing the same 68-byte buffer with hint=0 vs hint=1 must
|
|
* land in different tables and produce distinct is_xmssmt. */
|
|
{
|
|
byte buf[XMSS_SHA256_PUBLEN];
|
|
XMEMSET(buf, 0, sizeof(buf));
|
|
buf[3] = 0x01;
|
|
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, buf, sizeof(buf), 0), 0);
|
|
ExpectIntEQ((int)key.is_xmssmt, 0);
|
|
wc_XmssKey_Free(&key);
|
|
|
|
#if WOLFSSL_XMSS_MAX_HEIGHT >= 20
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, buf, sizeof(buf), 1), 0);
|
|
ExpectIntEQ((int)key.is_xmssmt, 1);
|
|
wc_XmssKey_Free(&key);
|
|
#endif
|
|
}
|
|
|
|
/* Lenient state: re-importing the same pub key into a VERIFYONLY
|
|
* key (params set, no private material) succeeds. The second
|
|
* call exercises the lenient-state branch. */
|
|
{
|
|
byte buf[XMSS_SHA256_PUBLEN];
|
|
XMEMSET(buf, 0, sizeof(buf));
|
|
buf[3] = 0x01;
|
|
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, buf, sizeof(buf), 0), 0);
|
|
ExpectIntEQ((int)key.state, (int)WC_XMSS_STATE_VERIFYONLY);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, buf, sizeof(buf), 0), 0);
|
|
ExpectIntEQ((int)key.state, (int)WC_XMSS_STATE_VERIFYONLY);
|
|
wc_XmssKey_Free(&key);
|
|
}
|
|
|
|
/* Strict signature-length check: wc_XmssKey_Verify rejects any
|
|
* sigLen != key->params->sig_len. This guards every consumer
|
|
* (RFC 9802 X.509, PKCS#7, CMS, ...) against a longer wrapper that
|
|
* happens to start with a valid signature. Construct a key in
|
|
* VERIFYONLY state, then verify with sig_len + 1 and sig_len - 1
|
|
* byte buffers; both must fail with BUFFER_E before any crypto
|
|
* runs. The buffer contents are irrelevant since the length check
|
|
* fires first. */
|
|
{
|
|
byte pub[XMSS_SHA256_PUBLEN];
|
|
byte* sigBuf = NULL;
|
|
word32 sigLen = 0;
|
|
const byte msg[1] = { 0 };
|
|
|
|
XMEMSET(pub, 0, sizeof(pub));
|
|
pub[3] = 0x01;
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, pub, sizeof(pub), 0), 0);
|
|
ExpectIntEQ((int)key.state, (int)WC_XMSS_STATE_VERIFYONLY);
|
|
ExpectIntEQ(wc_XmssKey_GetSigLen(&key, &sigLen), 0);
|
|
ExpectIntGT(sigLen, 0);
|
|
ExpectNotNull(sigBuf = (byte*)XMALLOC((size_t)sigLen + 1, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (sigBuf != NULL) {
|
|
XMEMSET(sigBuf, 0, (size_t)sigLen + 1);
|
|
ExpectIntEQ(wc_XmssKey_Verify(&key, sigBuf, sigLen + 1,
|
|
msg, (int)sizeof(msg)), WC_NO_ERR_TRACE(BUFFER_E));
|
|
ExpectIntEQ(wc_XmssKey_Verify(&key, sigBuf, sigLen - 1,
|
|
msg, (int)sizeof(msg)), WC_NO_ERR_TRACE(BUFFER_E));
|
|
XFREE(sigBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
}
|
|
wc_XmssKey_Free(&key);
|
|
}
|
|
|
|
/* BAD_STATE_E branch: WC_XMSS_STATE_OK must be rejected. Reaching
|
|
* OK normally requires a successful private-key Reload / sign,
|
|
* which is unavailable in WOLFSSL_XMSS_VERIFY_ONLY builds. Force
|
|
* the state directly to exercise the rejection without coupling
|
|
* this helper to the signing test fixture; sk stays NULL so Free
|
|
* is still safe. */
|
|
{
|
|
byte pub[XMSS_SHA256_PUBLEN];
|
|
|
|
XMEMSET(pub, 0, sizeof(pub));
|
|
pub[3] = 0x01;
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&key, "XMSS-SHA2_10_256"), 0);
|
|
key.state = WC_XMSS_STATE_OK;
|
|
ExpectIntEQ(wc_XmssKey_ImportPubRaw_ex(&key, pub, sizeof(pub), 0),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
wc_XmssKey_Free(&key);
|
|
}
|
|
#endif
|
|
|
|
return EXPECT_RESULT();
|
|
}
|
|
#endif
|
|
|
|
/* Collect the byte offset of the final sub-identifier of every
|
|
* 1.3.6.1.5.5.7.6.<lastByte> OID in a DER cert (XMSS ends 0x22, XMSS^MT ends
|
|
* 0x23). RFC 9802 reuses the same OID for the SubjectPublicKeyInfo algorithm,
|
|
* the TBS signatureAlgorithm and the outer signatureAlgorithm, so a conformant
|
|
* XMSS/XMSS^MT cert contains exactly three, in TBS-signature / SPKI-key /
|
|
* outer-signature order. Returns the number of occurrences found. */
|
|
#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_XMSS) && \
|
|
!defined(WOLFSSL_XMSS_VERIFY_ONLY) && defined(WOLFSSL_CERT_GEN) && \
|
|
!defined(NO_FILESYSTEM) && !defined(NO_CERTS)
|
|
static int rfc9802_collect_hbs_oid_offsets(const byte* der, word32 derSz,
|
|
byte lastByte, word32* offsets, int maxOff)
|
|
{
|
|
/* OID body for 1.3.6.1.5.5.7.6: 2B 06 01 05 05 07 06, then <lastByte>. */
|
|
static const byte pfx[] = { 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x06 };
|
|
int n = 0;
|
|
word32 i;
|
|
|
|
for (i = 0; (word32)(i + sizeof(pfx)) < derSz; i++) {
|
|
if (XMEMCMP(der + i, pfx, sizeof(pfx)) == 0 &&
|
|
der[i + sizeof(pfx)] == lastByte) {
|
|
if (n < maxOff)
|
|
offsets[n] = i + (word32)sizeof(pfx);
|
|
n++;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
#endif
|
|
|
|
int test_rfc9802_lms_x509_verify(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_LMS)
|
|
#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \
|
|
!defined(WOLFSSL_NO_LMS_SHA256_256)
|
|
/* Cross-implementation interop gate. bc_lms_native_bc_root.der is
|
|
* generated through Bouncy Castle's stock JcaContentSignerBuilder("LMS")
|
|
* + JcaX509v3CertificateBuilder with no overrides; BC's native LMS X.509
|
|
* path is RFC 9802-compliant for HSS/LMS, so wolfSSL must accept it
|
|
* end-to-end. This is the one fixture from an independent implementation
|
|
* that we keep; wolfSSL's own generation is exercised by
|
|
* test_rfc9802_lms_x509_gen instead of committed wolfSSL fixtures. */
|
|
ExpectIntEQ(rfc9802_verify_one_cert("./certs/lms/bc_lms_native_bc_root.der",
|
|
HSS_LMSk, CTC_HSS_LMS), TEST_SUCCESS);
|
|
#endif /* !NO_FILESYSTEM && !NO_CERTS && !WOLFSSL_NO_LMS_SHA256_256 */
|
|
/* Pure wolfCrypt-level negative tests don't need filesystem or cert
|
|
* support, so they run for any LMS-enabled build. */
|
|
ExpectIntEQ(rfc9802_lms_import_negative(), TEST_SUCCESS);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
int test_rfc9802_xmss_x509_verify(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_XMSS)
|
|
/* No independent (RFC 9802-aligned) third-party XMSS X.509 implementation
|
|
* exists to interop against - OpenSSL has no XMSS cert signing and Bouncy
|
|
* Castle's XMSS encoding is not yet aligned with the final RFC - so there
|
|
* is no committed interop fixture here. wolfSSL's own XMSS/XMSS^MT cert
|
|
* generation, chain signing and the X.509-level signatureAlgorithm/SPKI
|
|
* mismatch rejection are exercised in test_rfc9802_xmss_x509_gen.
|
|
*
|
|
* Pure wolfCrypt-level negative tests run for any XMSS-enabled build. */
|
|
ExpectIntEQ(rfc9802_xmss_import_negative(), TEST_SUCCESS);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/* RFC 9802 certificate/CSR GENERATION tests.
|
|
*
|
|
* These exercise the cert-gen path (wc_MakeCert_ex / wc_SignCert_ex and
|
|
* wc_MakeCertReq_ex) with a freshly generated LMS or XMSS key, then feed
|
|
* the result back through the existing verification path to prove the
|
|
* generated SubjectPublicKeyInfo, signatureAlgorithm and signature are
|
|
* RFC 9802-compliant and self-consistent. */
|
|
/* RFC 9802 cert/CSR generation is only wired into the ASN.1 template
|
|
* implementation (the original/non-template path has no LMS/XMSS support),
|
|
* so all of these tests require WOLFSSL_ASN_TEMPLATE. */
|
|
#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_CERT_GEN) && \
|
|
!defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \
|
|
((defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)) || \
|
|
(defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY)))
|
|
/* Populate a minimal self-consistent subject/issuer name. */
|
|
static void rfc9802_gen_set_names(Cert* cert)
|
|
{
|
|
XSTRNCPY(cert->subject.country, "US", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.state, "OR", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.locality, "Portland", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.org, "wolfSSL", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.unit, "Testing", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.commonName, "RFC9802 Gen Root CA", CTC_NAME_SIZE);
|
|
}
|
|
|
|
/* Verify a self-signed DER cert by loading it as its own CA. */
|
|
static int rfc9802_gen_verify_selfsigned(const byte* der, int derSz)
|
|
{
|
|
EXPECT_DECLS;
|
|
WOLFSSL_CERT_MANAGER* cm = NULL;
|
|
|
|
ExpectNotNull(cm = wolfSSL_CertManagerNew());
|
|
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, der, (long)derSz,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
if (cm != NULL)
|
|
wolfSSL_CertManagerFree(cm);
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
#ifdef WOLFSSL_CERT_REQ
|
|
/* Parse a generated CSR and confirm its proof-of-possession signature. */
|
|
static int rfc9802_gen_verify_csr(const byte* der, int derSz)
|
|
{
|
|
EXPECT_DECLS;
|
|
DecodedCert dc;
|
|
|
|
wc_InitDecodedCert(&dc, der, (word32)derSz, NULL);
|
|
ExpectIntEQ(wc_ParseCert(&dc, CERTREQ_TYPE, VERIFY, NULL), 0);
|
|
wc_FreeDecodedCert(&dc);
|
|
return EXPECT_RESULT();
|
|
}
|
|
#endif /* WOLFSSL_CERT_REQ */
|
|
|
|
/* Generate a self-signed root CA (and, when CSRs are enabled, a PKCS#10
|
|
* request) for an already-made key, then feed each back through the
|
|
* verification path. keyType is the wc_MakeCert_ex/wc_SignCert_ex selector
|
|
* (LMS_TYPE / XMSS_TYPE / XMSSMT_TYPE) and sigType the matching CTC_ OID.
|
|
* key is void* to mirror the public wc_MakeCert_ex API; callers must pass a
|
|
* key object whose type matches keyType. */
|
|
static int rfc9802_gen_roundtrip(void* key, int keyType, int sigType,
|
|
WC_RNG* rng, word32 derCap)
|
|
{
|
|
EXPECT_DECLS;
|
|
byte* der = NULL;
|
|
int derSz = 0;
|
|
|
|
ExpectNotNull(der = (byte*)XMALLOC(derCap, NULL, DYNAMIC_TYPE_TMP_BUFFER));
|
|
|
|
/* Self-signed root CA: generate -> sign -> verify round trip. */
|
|
if (EXPECT_SUCCESS() && der != NULL) {
|
|
Cert cert;
|
|
ExpectIntEQ(wc_InitCert(&cert), 0);
|
|
rfc9802_gen_set_names(&cert);
|
|
cert.sigType = sigType;
|
|
cert.isCA = 1;
|
|
cert.selfSigned = 1;
|
|
cert.daysValid = 365;
|
|
ExpectIntGT(wc_MakeCert_ex(&cert, der, derCap, keyType, key, rng), 0);
|
|
ExpectIntGT(derSz = wc_SignCert_ex(cert.bodySz, cert.sigType, der,
|
|
derCap, keyType, key, rng), 0);
|
|
ExpectIntEQ(rfc9802_gen_verify_selfsigned(der, derSz), TEST_SUCCESS);
|
|
}
|
|
|
|
#ifdef WOLFSSL_CERT_REQ
|
|
/* PKCS#10 CSR: generate -> self-sign proof-of-possession -> parse. */
|
|
if (EXPECT_SUCCESS() && der != NULL) {
|
|
Cert cert;
|
|
ExpectIntEQ(wc_InitCert(&cert), 0);
|
|
rfc9802_gen_set_names(&cert);
|
|
cert.sigType = sigType;
|
|
ExpectIntGT(wc_MakeCertReq_ex(&cert, der, derCap, keyType, key), 0);
|
|
ExpectIntGT(derSz = wc_SignCert_ex(cert.bodySz, cert.sigType, der,
|
|
derCap, keyType, key, rng), 0);
|
|
ExpectIntEQ(rfc9802_gen_verify_csr(der, derSz), TEST_SUCCESS);
|
|
}
|
|
#endif /* WOLFSSL_CERT_REQ */
|
|
|
|
XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/* wc_ecc_make_key is available with HAVE_ECC; HAVE_ECC_KEY_EXPORT is needed
|
|
* for the leaf SPKI and !WC_NO_RNG for key generation. */
|
|
#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && !defined(WC_NO_RNG)
|
|
/* Subject name for the generated leaf (distinct from the CA subject). */
|
|
static void rfc9802_gen_set_leaf_names(Cert* cert)
|
|
{
|
|
XSTRNCPY(cert->subject.country, "US", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.state, "OR", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.locality, "Portland", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.org, "wolfSSL", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.unit, "Testing", CTC_NAME_SIZE);
|
|
XSTRNCPY(cert->subject.commonName, "RFC9802 Gen Leaf", CTC_NAME_SIZE);
|
|
}
|
|
|
|
/* Generate a self-signed LMS/XMSS CA, then an ECC leaf issued and signed by
|
|
* that CA, and confirm the leaf chains to the CA (and fails without it). This
|
|
* is the real RFC 9802 use case - a hash-based CA signing another cert - that
|
|
* self-signed roots and CSRs don't cover. caKey is the already-made CA key;
|
|
* caKeyType/caSigType select its algorithm. */
|
|
static int rfc9802_gen_chain(void* caKey, int caKeyType, int caSigType,
|
|
WC_RNG* rng, word32 derCap)
|
|
{
|
|
EXPECT_DECLS;
|
|
ecc_key leafKey;
|
|
int leafKeyInit = 0;
|
|
byte* caDer = NULL;
|
|
byte* leafDer = NULL;
|
|
int caSz = 0;
|
|
int leafSz = 0;
|
|
WOLFSSL_CERT_MANAGER* cm = NULL;
|
|
|
|
ExpectNotNull(caDer = (byte*)XMALLOC(derCap, NULL, DYNAMIC_TYPE_TMP_BUFFER));
|
|
ExpectNotNull(leafDer = (byte*)XMALLOC(derCap, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
if (wc_ecc_init(&leafKey) == 0) /* only flag for free if init succeeded */
|
|
leafKeyInit = 1;
|
|
ExpectIntEQ(leafKeyInit, 1);
|
|
ExpectIntEQ(wc_ecc_make_key(rng, 32, &leafKey), 0);
|
|
|
|
/* Self-signed CA root. */
|
|
if (EXPECT_SUCCESS() && caDer != NULL) {
|
|
Cert ca;
|
|
ExpectIntEQ(wc_InitCert(&ca), 0);
|
|
rfc9802_gen_set_names(&ca);
|
|
ca.sigType = caSigType;
|
|
ca.isCA = 1;
|
|
ca.selfSigned = 1;
|
|
ca.daysValid = 365;
|
|
ExpectIntGT(wc_MakeCert_ex(&ca, caDer, derCap, caKeyType, caKey, rng),
|
|
0);
|
|
ExpectIntGT(caSz = wc_SignCert_ex(ca.bodySz, caSigType, caDer, derCap,
|
|
caKeyType, caKey, rng), 0);
|
|
}
|
|
|
|
/* ECC leaf, issued by the CA's subject and signed with the CA key. */
|
|
if (EXPECT_SUCCESS() && leafDer != NULL && caSz > 0) {
|
|
Cert leaf;
|
|
ExpectIntEQ(wc_InitCert(&leaf), 0);
|
|
rfc9802_gen_set_leaf_names(&leaf);
|
|
leaf.sigType = caSigType;
|
|
leaf.daysValid = 365;
|
|
ExpectIntEQ(wc_SetIssuerBuffer(&leaf, caDer, caSz), 0);
|
|
ExpectIntGT(wc_MakeCert_ex(&leaf, leafDer, derCap, ECC_TYPE, &leafKey,
|
|
rng), 0);
|
|
ExpectIntGT(leafSz = wc_SignCert_ex(leaf.bodySz, caSigType, leafDer,
|
|
derCap, caKeyType, caKey, rng), 0);
|
|
}
|
|
|
|
/* Leaf verifies only when the CA is the trust anchor. */
|
|
if (EXPECT_SUCCESS() && leafSz > 0) {
|
|
ExpectNotNull(cm = wolfSSL_CertManagerNew());
|
|
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, caDer, (long)caSz,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
ExpectIntEQ(wolfSSL_CertManagerVerifyBuffer(cm, leafDer, (long)leafSz,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
if (cm != NULL) {
|
|
wolfSSL_CertManagerFree(cm);
|
|
cm = NULL;
|
|
}
|
|
ExpectNotNull(cm = wolfSSL_CertManagerNew());
|
|
ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, leafDer, (long)leafSz,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
if (cm != NULL) {
|
|
wolfSSL_CertManagerFree(cm);
|
|
cm = NULL;
|
|
}
|
|
}
|
|
|
|
/* Negative: corrupt the leaf's signature (last byte of the DER, in the
|
|
* signatureValue) and confirm verification fails even with the CA loaded.
|
|
* This proves the CA's hash-based signature is cryptographically checked,
|
|
* not accepted on issuer-name chaining alone. */
|
|
if (EXPECT_SUCCESS() && leafSz > 0) {
|
|
byte saved = leafDer[leafSz - 1];
|
|
leafDer[leafSz - 1] ^= 0xFF;
|
|
ExpectNotNull(cm = wolfSSL_CertManagerNew());
|
|
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, caDer, (long)caSz,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, leafDer, (long)leafSz,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
if (cm != NULL) {
|
|
wolfSSL_CertManagerFree(cm);
|
|
cm = NULL;
|
|
}
|
|
leafDer[leafSz - 1] = saved;
|
|
}
|
|
|
|
if (leafKeyInit)
|
|
wc_ecc_free(&leafKey);
|
|
XFREE(leafDer, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
XFREE(caDer, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
return EXPECT_RESULT();
|
|
}
|
|
#endif /* HAVE_ECC && HAVE_ECC_KEY_EXPORT */
|
|
#endif /* gen test support */
|
|
|
|
#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_LMS) && \
|
|
!defined(WOLFSSL_LMS_VERIFY_ONLY) && \
|
|
defined(WOLFSSL_CERT_GEN) && !defined(NO_FILESYSTEM) && \
|
|
!defined(NO_CERTS) && !defined(WOLFSSL_NO_LMS_SHA256_256)
|
|
/* Init an LMS key with the shared persistence callbacks and given params. */
|
|
static int rfc9802_gen_lms_init(LmsKey* key, int levels, int height, int win)
|
|
{
|
|
int ret = wc_LmsKey_Init(key, NULL, INVALID_DEVID);
|
|
if (ret == 0)
|
|
ret = wc_LmsKey_SetParameters(key, levels, height, win);
|
|
if (ret == 0)
|
|
ret = wc_LmsKey_SetWriteCb(key, test_lms_write_key);
|
|
if (ret == 0)
|
|
ret = wc_LmsKey_SetReadCb(key, test_lms_read_key);
|
|
if (ret == 0)
|
|
ret = wc_LmsKey_SetContext(key, (void*)LMS_TEST_PRIV_KEY_FILE);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
int test_rfc9802_lms_x509_gen(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_LMS) && \
|
|
!defined(WOLFSSL_LMS_VERIFY_ONLY) && \
|
|
defined(WOLFSSL_CERT_GEN) && !defined(NO_FILESYSTEM) && \
|
|
!defined(NO_CERTS) && !defined(WOLFSSL_NO_LMS_SHA256_256)
|
|
LmsKey key;
|
|
WC_RNG rng;
|
|
|
|
/* 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);
|
|
|
|
/* Single-level LMS (L1-H5-W8). */
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(rfc9802_gen_lms_init(&key, 1, 5, 8), 0);
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
|
|
ExpectIntEQ(rfc9802_gen_roundtrip(&key, LMS_TYPE, CTC_HSS_LMS, &rng, 8192),
|
|
TEST_SUCCESS);
|
|
|
|
/* Negative: signing an LMS key with a non-LMS signature OID must be
|
|
* rejected rather than emit a cert whose signatureAlgorithm contradicts
|
|
* its public key. The check fires before any signature is produced, so
|
|
* the key's one-time signatures are not consumed. */
|
|
if (EXPECT_SUCCESS()) {
|
|
Cert cert;
|
|
byte* tmp = NULL;
|
|
ExpectNotNull(tmp = (byte*)XMALLOC(8192, NULL, DYNAMIC_TYPE_TMP_BUFFER));
|
|
ExpectIntEQ(wc_InitCert(&cert), 0);
|
|
rfc9802_gen_set_names(&cert);
|
|
cert.sigType = CTC_HSS_LMS;
|
|
cert.isCA = 1;
|
|
cert.selfSigned = 1;
|
|
cert.daysValid = 365;
|
|
if (tmp != NULL) {
|
|
ExpectIntGT(wc_MakeCert_ex(&cert, tmp, 8192, LMS_TYPE, &key,
|
|
&rng), 0);
|
|
ExpectIntEQ(wc_SignCert_ex(cert.bodySz, CTC_XMSS, tmp, 8192,
|
|
LMS_TYPE, &key, &rng), WC_NO_ERR_TRACE(ALGO_ID_E));
|
|
}
|
|
XFREE(tmp, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
}
|
|
|
|
#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && !defined(WC_NO_RNG)
|
|
/* Real CA use case: the LMS CA signs an ECC leaf; the leaf must chain to
|
|
* the CA. Reuses the L1 key (plenty of one-time signatures remain). */
|
|
ExpectIntEQ(rfc9802_gen_chain(&key, LMS_TYPE, CTC_HSS_LMS, &rng, 8192),
|
|
TEST_SUCCESS);
|
|
#endif
|
|
|
|
wc_LmsKey_Free(&key);
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
|
|
#if !defined(WOLFSSL_LMS_MAX_LEVELS) || (WOLFSSL_LMS_MAX_LEVELS >= 2)
|
|
/* Multi-level HSS (L2-H5-W8): the signature embeds a lower-level LMS
|
|
* public key + signature, exercising the larger, multi-level encoding. */
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(rfc9802_gen_lms_init(&key, 2, 5, 8), 0);
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
|
|
ExpectIntEQ(rfc9802_gen_roundtrip(&key, LMS_TYPE, CTC_HSS_LMS, &rng, 8192),
|
|
TEST_SUCCESS);
|
|
wc_LmsKey_Free(&key);
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
#endif
|
|
|
|
#if !defined(WOLFSSL_LMS_MAX_LEVELS) || (WOLFSSL_LMS_MAX_LEVELS >= 3)
|
|
/* Three-level HSS with Winternitz 4 (L3-H5-W4): exercises the deepest
|
|
* multi-level encoding and a different Winternitz parameter than the
|
|
* W8 cases above. */
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(rfc9802_gen_lms_init(&key, 3, 5, 4), 0);
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
|
|
ExpectIntEQ(rfc9802_gen_roundtrip(&key, LMS_TYPE, CTC_HSS_LMS, &rng, 8192),
|
|
TEST_SUCCESS);
|
|
wc_LmsKey_Free(&key);
|
|
(void)remove(LMS_TEST_PRIV_KEY_FILE);
|
|
#endif
|
|
|
|
wc_FreeRng(&rng);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_XMSS) && \
|
|
!defined(WOLFSSL_XMSS_VERIFY_ONLY) && \
|
|
defined(WOLFSSL_CERT_GEN) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS)
|
|
/* Per-process temp file: parallel unit.test runs (e.g. CI shards sharing a
|
|
* working directory) must not clobber each other's stateful XMSS private key. */
|
|
static const char* xmss_gen_priv_key_file(void)
|
|
{
|
|
static char xmssPath[64];
|
|
if (xmssPath[0] == '\0') {
|
|
#if defined(HAVE_GETPID) && !defined(WOLFSSL_NO_GETPID)
|
|
(void)XSNPRINTF(xmssPath, sizeof(xmssPath),
|
|
"./wolfssl_test_xmss_gen_%d.key", (int)getpid());
|
|
#else
|
|
(void)XSNPRINTF(xmssPath, sizeof(xmssPath),
|
|
"./wolfssl_test_xmss_gen.key");
|
|
#endif
|
|
}
|
|
return xmssPath;
|
|
}
|
|
#define XMSS_GEN_TEST_PRIV_KEY_FILE xmss_gen_priv_key_file()
|
|
static enum wc_XmssRc xmss_gen_write_key(const byte* priv, word32 privSz,
|
|
void* context)
|
|
{
|
|
XFILE f = XFOPEN((const char*)context, "wb");
|
|
enum wc_XmssRc ret = WC_XMSS_RC_SAVED_TO_NV_MEMORY;
|
|
if (f == XBADFILE)
|
|
return WC_XMSS_RC_WRITE_FAIL;
|
|
if (XFWRITE(priv, 1, privSz, f) != privSz)
|
|
ret = WC_XMSS_RC_WRITE_FAIL;
|
|
XFCLOSE(f);
|
|
return ret;
|
|
}
|
|
static enum wc_XmssRc xmss_gen_read_key(byte* priv, word32 privSz,
|
|
void* context)
|
|
{
|
|
XFILE f = XFOPEN((const char*)context, "rb");
|
|
enum wc_XmssRc ret = WC_XMSS_RC_READ_TO_MEMORY;
|
|
if (f == XBADFILE)
|
|
return WC_XMSS_RC_READ_FAIL;
|
|
if (XFREAD(priv, 1, privSz, f) != privSz)
|
|
ret = WC_XMSS_RC_READ_FAIL;
|
|
XFCLOSE(f);
|
|
return ret;
|
|
}
|
|
|
|
/* Init an XMSS/XMSS^MT key with the shared persistence callbacks. */
|
|
static int rfc9802_gen_xmss_init(XmssKey* key, const char* paramStr)
|
|
{
|
|
int ret = wc_XmssKey_Init(key, NULL, INVALID_DEVID);
|
|
if (ret == 0)
|
|
ret = wc_XmssKey_SetParamStr(key, paramStr);
|
|
if (ret == 0)
|
|
ret = wc_XmssKey_SetWriteCb(key, xmss_gen_write_key);
|
|
if (ret == 0)
|
|
ret = wc_XmssKey_SetReadCb(key, xmss_gen_read_key);
|
|
if (ret == 0)
|
|
ret = wc_XmssKey_SetContext(key, (void*)XMSS_GEN_TEST_PRIV_KEY_FILE);
|
|
return ret;
|
|
}
|
|
|
|
/* X.509-level negative tests on a wolfSSL-generated XMSS/XMSS^MT cert, run
|
|
* against the already-made key (no extra keygen). oidLast is the cert's true
|
|
* final OID byte (XMSS 0x22, XMSS^MT 0x23) and oidSwap the other family's:
|
|
*
|
|
* (a) flip only the outer signatureAlgorithm OID -> it no longer equals the
|
|
* TBS signatureAlgorithm, which the generic X.509 algId-consistency check
|
|
* rejects (ASN_SIG_OID_E at parse);
|
|
* (b) flip both signatureAlgorithm copies (TBS + outer) but leave the SPKI
|
|
* key OID -> outer == TBS (that check passes), yet the signature
|
|
* algorithm now disagrees with the public-key algorithm, which RFC 9802
|
|
* requires verification to reject (SigOidMatchesKeyOid, before the - now
|
|
* also invalid - signature is even checked).
|
|
*
|
|
* Either way verification must fail. */
|
|
static int rfc9802_gen_xmss_oid_tamper(void* key, int keyType, int sigType,
|
|
WC_RNG* rng, byte oidLast, byte oidSwap)
|
|
{
|
|
EXPECT_DECLS;
|
|
byte* der = NULL;
|
|
int derSz = 0;
|
|
word32 off[8];
|
|
int n = 0;
|
|
WOLFSSL_CERT_MANAGER* cm = NULL;
|
|
|
|
ExpectNotNull(der = (byte*)XMALLOC(16384, NULL, DYNAMIC_TYPE_TMP_BUFFER));
|
|
|
|
if (EXPECT_SUCCESS() && der != NULL) {
|
|
Cert cert;
|
|
ExpectIntEQ(wc_InitCert(&cert), 0);
|
|
rfc9802_gen_set_names(&cert);
|
|
cert.sigType = sigType;
|
|
cert.isCA = 1;
|
|
cert.selfSigned = 1;
|
|
cert.daysValid = 365;
|
|
ExpectIntGT(wc_MakeCert_ex(&cert, der, 16384, keyType, key, rng), 0);
|
|
ExpectIntGT(derSz = wc_SignCert_ex(cert.bodySz, sigType, der, 16384,
|
|
keyType, key, rng), 0);
|
|
}
|
|
|
|
if (EXPECT_SUCCESS() && derSz > 0) {
|
|
n = rfc9802_collect_hbs_oid_offsets(der, (word32)derSz, oidLast, off, 8);
|
|
/* TBS-signature, SPKI-key, outer-signature - in that order. */
|
|
ExpectIntEQ(n, 3);
|
|
}
|
|
|
|
/* (a) Outer signatureAlgorithm != TBS signatureAlgorithm. */
|
|
if (EXPECT_SUCCESS() && n == 3) {
|
|
der[off[2]] = oidSwap;
|
|
ExpectNotNull(cm = wolfSSL_CertManagerNew());
|
|
(void)wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz,
|
|
WOLFSSL_FILETYPE_ASN1);
|
|
ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, der, (long)derSz,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
if (cm != NULL) {
|
|
wolfSSL_CertManagerFree(cm);
|
|
cm = NULL;
|
|
}
|
|
der[off[2]] = oidLast; /* restore */
|
|
}
|
|
|
|
/* (b) signatureAlgorithm (both copies) disagrees with the SPKI key OID. */
|
|
if (EXPECT_SUCCESS() && n == 3) {
|
|
der[off[0]] = oidSwap;
|
|
der[off[2]] = oidSwap;
|
|
ExpectNotNull(cm = wolfSSL_CertManagerNew());
|
|
(void)wolfSSL_CertManagerLoadCABuffer(cm, der, (long)derSz,
|
|
WOLFSSL_FILETYPE_ASN1);
|
|
ExpectIntNE(wolfSSL_CertManagerVerifyBuffer(cm, der, (long)derSz,
|
|
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
|
|
if (cm != NULL) {
|
|
wolfSSL_CertManagerFree(cm);
|
|
cm = NULL;
|
|
}
|
|
}
|
|
|
|
XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
return EXPECT_RESULT();
|
|
}
|
|
#endif /* XMSS gen support */
|
|
|
|
int test_rfc9802_xmss_x509_gen(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_HAVE_XMSS) && \
|
|
!defined(WOLFSSL_XMSS_VERIFY_ONLY) && \
|
|
defined(WOLFSSL_CERT_GEN) && !defined(NO_FILESYSTEM) && !defined(NO_CERTS)
|
|
XmssKey key;
|
|
WC_RNG rng;
|
|
|
|
/* 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);
|
|
|
|
/* Single-tree XMSS. */
|
|
(void)remove(XMSS_GEN_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(rfc9802_gen_xmss_init(&key, "XMSS-SHA2_10_256"), 0);
|
|
ExpectIntEQ(wc_XmssKey_MakeKey(&key, &rng), 0);
|
|
ExpectIntEQ((int)key.is_xmssmt, 0);
|
|
ExpectIntEQ(rfc9802_gen_roundtrip(&key, XMSS_TYPE, CTC_XMSS, &rng, 16384),
|
|
TEST_SUCCESS);
|
|
|
|
/* Negative: the XMSSMT_TYPE selector must not be accepted for a
|
|
* single-tree XMSS key, and signing a single-tree key as XMSS^MT must be
|
|
* rejected. Both checks fire before signing, so no signature is used. */
|
|
if (EXPECT_SUCCESS()) {
|
|
Cert cert;
|
|
byte* tmp = NULL;
|
|
ExpectNotNull(tmp = (byte*)XMALLOC(16384, NULL,
|
|
DYNAMIC_TYPE_TMP_BUFFER));
|
|
ExpectIntEQ(wc_InitCert(&cert), 0);
|
|
rfc9802_gen_set_names(&cert);
|
|
cert.sigType = CTC_XMSS;
|
|
cert.isCA = 1;
|
|
cert.selfSigned = 1;
|
|
cert.daysValid = 365;
|
|
/* Wrong selector for the key's tree variant. */
|
|
if (tmp != NULL) {
|
|
ExpectIntEQ(wc_MakeCert_ex(&cert, tmp, 16384, XMSSMT_TYPE, &key,
|
|
&rng), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
/* Correct selector, but signed with the XMSS^MT OID. */
|
|
ExpectIntGT(wc_MakeCert_ex(&cert, tmp, 16384, XMSS_TYPE, &key,
|
|
&rng), 0);
|
|
ExpectIntEQ(wc_SignCert_ex(cert.bodySz, CTC_XMSSMT, tmp, 16384,
|
|
XMSS_TYPE, &key, &rng), WC_NO_ERR_TRACE(ALGO_ID_E));
|
|
}
|
|
XFREE(tmp, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
}
|
|
|
|
#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && !defined(WC_NO_RNG)
|
|
/* Real CA use case: the XMSS CA signs an ECC leaf; the leaf must chain. */
|
|
ExpectIntEQ(rfc9802_gen_chain(&key, XMSS_TYPE, CTC_XMSS, &rng, 16384),
|
|
TEST_SUCCESS);
|
|
#endif
|
|
/* X.509-level signatureAlgorithm/SPKI OID consistency, reusing this key. */
|
|
ExpectIntEQ(rfc9802_gen_xmss_oid_tamper(&key, XMSS_TYPE, CTC_XMSS, &rng,
|
|
/* XMSS */ 0x22, /* swap */ 0x23), TEST_SUCCESS);
|
|
|
|
wc_XmssKey_Free(&key);
|
|
(void)remove(XMSS_GEN_TEST_PRIV_KEY_FILE);
|
|
|
|
/* Multi-tree XMSS^MT: exercises the XMSSMT_TYPE selector, the
|
|
* XMSSMTk public-key OID branch and the CTC_XMSSMT signature OID. */
|
|
(void)remove(XMSS_GEN_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(rfc9802_gen_xmss_init(&key, "XMSSMT-SHA2_20/2_256"), 0);
|
|
ExpectIntEQ(wc_XmssKey_MakeKey(&key, &rng), 0);
|
|
ExpectIntEQ((int)key.is_xmssmt, 1);
|
|
ExpectIntEQ(rfc9802_gen_roundtrip(&key, XMSSMT_TYPE, CTC_XMSSMT, &rng,
|
|
16384), TEST_SUCCESS);
|
|
#if defined(HAVE_ECC) && defined(HAVE_ECC_KEY_EXPORT) && !defined(WC_NO_RNG)
|
|
ExpectIntEQ(rfc9802_gen_chain(&key, XMSSMT_TYPE, CTC_XMSSMT, &rng, 16384),
|
|
TEST_SUCCESS);
|
|
#endif
|
|
ExpectIntEQ(rfc9802_gen_xmss_oid_tamper(&key, XMSSMT_TYPE, CTC_XMSSMT, &rng,
|
|
/* XMSS^MT */ 0x23, /* swap */ 0x22), TEST_SUCCESS);
|
|
wc_XmssKey_Free(&key);
|
|
(void)remove(XMSS_GEN_TEST_PRIV_KEY_FILE);
|
|
|
|
/* A second XMSS^MT parameter set (different embedded param-set OID and a
|
|
* larger signature) to keep the encoder/auto-derive decoder exercised
|
|
* across sizes now that the committed multi-size fixtures are gone. */
|
|
(void)remove(XMSS_GEN_TEST_PRIV_KEY_FILE);
|
|
ExpectIntEQ(rfc9802_gen_xmss_init(&key, "XMSSMT-SHA2_20/4_256"), 0);
|
|
ExpectIntEQ(wc_XmssKey_MakeKey(&key, &rng), 0);
|
|
ExpectIntEQ((int)key.is_xmssmt, 1);
|
|
ExpectIntEQ(rfc9802_gen_roundtrip(&key, XMSSMT_TYPE, CTC_XMSSMT, &rng,
|
|
16384), TEST_SUCCESS);
|
|
wc_XmssKey_Free(&key);
|
|
(void)remove(XMSS_GEN_TEST_PRIV_KEY_FILE);
|
|
|
|
wc_FreeRng(&rng);
|
|
#endif
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* MC/DC decision + feature coverage: LMS */
|
|
/* */
|
|
/* Per-argument negative (decision) tests and positive multi-sign/verify */
|
|
/* (feature) tests that drive the public wc_LmsKey_* API and, through it, the */
|
|
/* wc_lms.c / wc_lms_impl.c decisions. Both are additive to the group above. */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)
|
|
/* In-memory private-key persistence callbacks (filesystem-independent) so the
|
|
* feature roundtrip works in any LMS-signing build. Guarded identically to the
|
|
* only caller (test_wc_LmsFeatureCoverage) to avoid an unused-function warning
|
|
* under -Werror in verify-only builds. */
|
|
static byte lms_mc_priv[8192];
|
|
static word32 lms_mc_privSz = 0;
|
|
|
|
static int lms_mc_write_key(const byte* priv, word32 privSz, void* context)
|
|
{
|
|
(void)context;
|
|
if (privSz > (word32)sizeof(lms_mc_priv))
|
|
return -1;
|
|
XMEMCPY(lms_mc_priv, priv, privSz);
|
|
lms_mc_privSz = privSz;
|
|
return WC_LMS_RC_SAVED_TO_NV_MEMORY;
|
|
}
|
|
|
|
static int lms_mc_read_key(byte* priv, word32 privSz, void* context)
|
|
{
|
|
(void)context;
|
|
if (privSz != lms_mc_privSz)
|
|
return -1;
|
|
XMEMCPY(priv, lms_mc_priv, privSz);
|
|
return WC_LMS_RC_READ_TO_MEMORY;
|
|
}
|
|
|
|
/* One hash-family roundtrip: parameter-select via SetParameters_ex(hash),
|
|
* keygen, a short multi-sign/verify loop (drives the treehash / auth-path
|
|
* loop true-sides), the GetSigLen / SigsLeft / short-buffer decisions, then
|
|
* free. Returns TEST_SUCCESS / TEST_FAIL. */
|
|
static int lms_mc_family_roundtrip(WC_RNG* rng, int hash)
|
|
{
|
|
EXPECT_DECLS;
|
|
LmsKey key;
|
|
byte msg[] = "lms feature-coverage message";
|
|
byte sig[8192];
|
|
word32 sigSz;
|
|
word32 sigLen = 0;
|
|
int i;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
lms_mc_privSz = 0;
|
|
|
|
ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
/* L1/H5/W8 exists in every compiled-in hash family and keygen is fast. */
|
|
ExpectIntEQ(wc_LmsKey_SetParameters_ex(&key, 1, 5, 8, hash), 0);
|
|
ExpectIntEQ(wc_LmsKey_SetWriteCb(&key, lms_mc_write_key), 0);
|
|
ExpectIntEQ(wc_LmsKey_SetReadCb(&key, lms_mc_read_key), 0);
|
|
ExpectIntEQ(wc_LmsKey_SetContext(&key, (void*)lms_mc_priv), 0);
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(&key, rng), 0);
|
|
|
|
ExpectIntEQ(wc_LmsKey_GetSigLen(&key, &sigLen), 0);
|
|
ExpectIntGT((int)sigLen, 0);
|
|
ExpectIntLE((int)sigLen, (int)sizeof(sig));
|
|
ExpectIntGT(wc_LmsKey_SigsLeft(&key), 0);
|
|
|
|
/* Short-buffer decision: *sigSz < sig_len -> BUFFER_E. */
|
|
sigSz = 1;
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)),
|
|
WC_NO_ERR_TRACE(BUFFER_E));
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
sigSz = sizeof(sig);
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
|
ExpectIntEQ(wc_LmsKey_Verify(&key, sig, sigSz, msg, sizeof(msg)), 0);
|
|
/* Negative verify: flip a signature byte -> must not verify. */
|
|
sig[sigSz - 1] ^= 0x01;
|
|
ExpectIntNE(wc_LmsKey_Verify(&key, sig, sigSz, msg, sizeof(msg)), 0);
|
|
sig[sigSz - 1] ^= 0x01;
|
|
}
|
|
|
|
wc_LmsKey_Free(&key);
|
|
return EXPECT_RESULT();
|
|
}
|
|
#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */
|
|
|
|
int test_wc_LmsDecisionCoverage(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_LMS)
|
|
LmsKey key;
|
|
word32 sigLen = 0;
|
|
int levels = 0;
|
|
int height = 0;
|
|
int width = 0;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
|
|
/* wc_LmsKey_Init: NULL key operand. */
|
|
ExpectIntEQ(wc_LmsKey_Init(NULL, NULL, INVALID_DEVID),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
|
|
/* wc_LmsKey_SetParameters: unknown (levels,height,winternitz) triple ->
|
|
* table search exhausts and returns BAD_FUNC_ARG. Independence: vary one
|
|
* coordinate at a time away from the known-good L1/H5/W8 set (the others
|
|
* held at valid values), plus the NULL-key operand. */
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&key, 99, 5, 8),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&key, 1, 99, 8),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&key, 1, 5, 99),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(NULL, 1, 5, 8),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
/* wc_LmsKey_SetParameters_ex: BAD_STATE_E once params are already set
|
|
* (state != INITED). Valid set first, then a second call must fail on the
|
|
* state operand, not the table search. */
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&key, 1, 5, 8), 0);
|
|
ExpectIntEQ(wc_LmsKey_SetParameters_ex(&key, 1, 5, 8, LMS_SHA256),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
|
|
/* wc_LmsKey_GetParameters: each NULL out operand, then success. */
|
|
ExpectIntEQ(wc_LmsKey_GetParameters(&key, NULL, &height, &width),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_GetParameters(&key, &levels, NULL, &width),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_GetParameters(&key, &levels, &height, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_GetParameters(&key, &levels, &height, &width), 0);
|
|
|
|
/* wc_LmsKey_GetSigLen: NULL len operand (params set), then a params==NULL
|
|
* key -> BAD_FUNC_ARG on the params operand. */
|
|
ExpectIntEQ(wc_LmsKey_GetSigLen(&key, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_GetSigLen(&key, &sigLen), 0);
|
|
ExpectIntGT((int)sigLen, 0);
|
|
{
|
|
LmsKey bare;
|
|
XMEMSET(&bare, 0, sizeof(bare));
|
|
ExpectIntEQ(wc_LmsKey_Init(&bare, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_LmsKey_GetSigLen(&bare, &sigLen),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
wc_LmsKey_Free(&bare);
|
|
}
|
|
|
|
/* wc_LmsKey_ExportPubRaw: params are set but no key has been made, so
|
|
* key->pub is still zeroed. Exporting it must fail on the state operand
|
|
* rather than hand back a buffer of zeros. */
|
|
{
|
|
byte pub[64];
|
|
word32 pubSz = (word32)sizeof(pub);
|
|
ExpectIntEQ(wc_LmsKey_ExportPubRaw(&key, pub, &pubSz),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
}
|
|
|
|
/* wc_LmsKey_Verify: each NULL operand and msgSz < 0. The arg checks fire
|
|
* before any state/crypto, so the params-set (non-verifiable) key is fine.
|
|
* Independence: exactly one operand invalid per call. */
|
|
{
|
|
byte vsig[4] = {0};
|
|
byte vmsg[4] = {0};
|
|
ExpectIntEQ(wc_LmsKey_Verify(NULL, vsig, sizeof(vsig), vmsg,
|
|
sizeof(vmsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_Verify(&key, NULL, sizeof(vsig), vmsg,
|
|
sizeof(vmsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_Verify(&key, vsig, sizeof(vsig), NULL,
|
|
sizeof(vmsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_Verify(&key, vsig, sizeof(vsig), vmsg, -1),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
}
|
|
|
|
#ifndef WOLFSSL_LMS_VERIFY_ONLY
|
|
/* wc_LmsKey_SetWriteCb: NULL key vs NULL callback (independence pair). */
|
|
ExpectIntEQ(wc_LmsKey_SetWriteCb(NULL, lms_mc_write_key),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_SetWriteCb(&key, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
/* wc_LmsKey_MakeKey: NULL key vs NULL rng (independence pair). Both fail on
|
|
* the arg check before any allocation. */
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(NULL, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(&key, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
/* wc_LmsKey_Sign: each NULL operand + msgSz < 0, all before crypto. */
|
|
{
|
|
byte ssig[4] = {0};
|
|
word32 ssigSz = sizeof(ssig);
|
|
byte smsg[4] = {0};
|
|
ExpectIntEQ(wc_LmsKey_Sign(NULL, ssig, &ssigSz, smsg, sizeof(smsg)),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, NULL, &ssigSz, smsg, sizeof(smsg)),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, ssig, NULL, smsg, sizeof(smsg)),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, ssig, &ssigSz, NULL, sizeof(smsg)),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, ssig, &ssigSz, smsg, -1),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
}
|
|
#endif /* !WOLFSSL_LMS_VERIFY_ONLY */
|
|
|
|
wc_LmsKey_Free(&key);
|
|
#endif /* WOLFSSL_HAVE_LMS */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
int test_wc_LmsFeatureCoverage(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_LMS) && !defined(WOLFSSL_LMS_VERIFY_ONLY)
|
|
WC_RNG rng;
|
|
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
ExpectIntEQ(wc_InitRng(&rng), 0);
|
|
|
|
/* Default SHA-256/256 family is compiled into every LMS build. */
|
|
ExpectIntEQ(lms_mc_family_roundtrip(&rng, LMS_SHA256), TEST_SUCCESS);
|
|
|
|
#ifdef WOLFSSL_LMS_SHA256_192
|
|
/* Truncated 192-bit SHA-256 family: exercises the wc_lms_*sha256_192*
|
|
* hash helpers and the LMS_SHA256_192 map entries. */
|
|
ExpectIntEQ(lms_mc_family_roundtrip(&rng, LMS_SHA256_192), TEST_SUCCESS);
|
|
#endif
|
|
#ifdef WOLFSSL_LMS_SHAKE256
|
|
/* SHAKE256 family: exercises the wc_lms_shake256_* hash helpers. */
|
|
ExpectIntEQ(lms_mc_family_roundtrip(&rng, LMS_SHAKE256), TEST_SUCCESS);
|
|
#endif
|
|
|
|
/* Multi-level HSS (L2): drives the wc_hss_* subtree init / auth-path /
|
|
* presign helpers and the level>1 branches that a single-level key skips.
|
|
* Guarded on the compiled-in level bound. */
|
|
#if !defined(WOLFSSL_LMS_MAX_LEVELS) || (WOLFSSL_LMS_MAX_LEVELS >= 2)
|
|
{
|
|
LmsKey key;
|
|
byte msg[] = "lms hss L2 message";
|
|
byte sig[8192];
|
|
word32 sigSz;
|
|
int i;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
lms_mc_privSz = 0;
|
|
ExpectIntEQ(wc_LmsKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_LmsKey_SetParameters(&key, 2, 5, 8), 0);
|
|
ExpectIntEQ(wc_LmsKey_SetWriteCb(&key, lms_mc_write_key), 0);
|
|
ExpectIntEQ(wc_LmsKey_SetReadCb(&key, lms_mc_read_key), 0);
|
|
ExpectIntEQ(wc_LmsKey_SetContext(&key, (void*)lms_mc_priv), 0);
|
|
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
|
|
for (i = 0; i < 2; i++) {
|
|
sigSz = sizeof(sig);
|
|
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
|
|
ExpectIntEQ(wc_LmsKey_Verify(&key, sig, sigSz, msg, sizeof(msg)), 0);
|
|
}
|
|
wc_LmsKey_Free(&key);
|
|
}
|
|
#endif
|
|
|
|
wc_FreeRng(&rng);
|
|
#endif /* WOLFSSL_HAVE_LMS && !WOLFSSL_LMS_VERIFY_ONLY */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
/* MC/DC decision + feature coverage: XMSS */
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY)
|
|
/* In-memory XMSS private-key persistence (filesystem-independent). Sized for
|
|
* the tall XMSS^MT parameter set used below; skipped at run time if a key's
|
|
* private length ever exceeds it. Guarded identically to its only caller. */
|
|
static byte xmss_mc_priv[262144];
|
|
static word32 xmss_mc_privSz = 0;
|
|
|
|
static enum wc_XmssRc xmss_mc_write_key(const byte* priv, word32 privSz,
|
|
void* context)
|
|
{
|
|
(void)context;
|
|
if (privSz > (word32)sizeof(xmss_mc_priv))
|
|
return WC_XMSS_RC_WRITE_FAIL;
|
|
XMEMCPY(xmss_mc_priv, priv, privSz);
|
|
xmss_mc_privSz = privSz;
|
|
return WC_XMSS_RC_SAVED_TO_NV_MEMORY;
|
|
}
|
|
|
|
static enum wc_XmssRc xmss_mc_read_key(byte* priv, word32 privSz, void* context)
|
|
{
|
|
(void)context;
|
|
if (privSz != xmss_mc_privSz)
|
|
return WC_XMSS_RC_READ_FAIL;
|
|
XMEMCPY(priv, xmss_mc_priv, privSz);
|
|
return WC_XMSS_RC_READ_TO_MEMORY;
|
|
}
|
|
|
|
/* One parameter-set roundtrip: init, param-string select, keygen, a short
|
|
* multi-sign/verify loop, GetSigLen / GetPubLen / SigsLeft and the
|
|
* short-buffer decisions, then free. */
|
|
static int xmss_mc_param_roundtrip(WC_RNG* rng, const char* paramStr)
|
|
{
|
|
EXPECT_DECLS;
|
|
XmssKey key;
|
|
byte msg[] = "xmss feature-coverage message";
|
|
byte* sig = NULL;
|
|
word32 sigSz;
|
|
word32 sigLen = 0;
|
|
word32 pubLen = 0;
|
|
word32 privLen = 0;
|
|
int i;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
xmss_mc_privSz = 0;
|
|
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&key, paramStr), 0);
|
|
ExpectIntEQ(wc_XmssKey_SetWriteCb(&key, xmss_mc_write_key), 0);
|
|
ExpectIntEQ(wc_XmssKey_SetReadCb(&key, xmss_mc_read_key), 0);
|
|
ExpectIntEQ(wc_XmssKey_SetContext(&key, (void*)xmss_mc_priv), 0);
|
|
|
|
ExpectIntEQ(wc_XmssKey_GetPrivLen(&key, &privLen), 0);
|
|
/* Skip cleanly (no failure) if this param set's secret key is larger than
|
|
* the in-memory scratch buffer. */
|
|
if (EXPECT_SUCCESS() && privLen > (word32)sizeof(xmss_mc_priv)) {
|
|
wc_XmssKey_Free(&key);
|
|
return TEST_SUCCESS;
|
|
}
|
|
|
|
ExpectIntEQ(wc_XmssKey_MakeKey(&key, rng), 0);
|
|
ExpectIntEQ(wc_XmssKey_GetPubLen(&key, &pubLen), 0);
|
|
ExpectIntGT((int)pubLen, 0);
|
|
ExpectIntEQ(wc_XmssKey_GetSigLen(&key, &sigLen), 0);
|
|
ExpectIntGT((int)sigLen, 0);
|
|
ExpectIntGT(wc_XmssKey_SigsLeft(&key), 0);
|
|
|
|
ExpectNotNull(sig = (byte*)XMALLOC(sigLen, NULL, DYNAMIC_TYPE_TMP_BUFFER));
|
|
|
|
/* Short-buffer decision: *sigLen < sig_len -> BUFFER_E. */
|
|
if (sig != NULL) {
|
|
sigSz = 1;
|
|
ExpectIntEQ(wc_XmssKey_Sign(&key, sig, &sigSz, msg, (int)sizeof(msg)),
|
|
WC_NO_ERR_TRACE(BUFFER_E));
|
|
}
|
|
|
|
for (i = 0; sig != NULL && i < 2; i++) {
|
|
sigSz = sigLen;
|
|
ExpectIntEQ(wc_XmssKey_Sign(&key, sig, &sigSz, msg, (int)sizeof(msg)),
|
|
0);
|
|
ExpectIntEQ(wc_XmssKey_Verify(&key, sig, sigSz, msg, (int)sizeof(msg)),
|
|
0);
|
|
/* Negative verify: flip a signature byte. */
|
|
sig[sigSz - 1] ^= 0x01;
|
|
ExpectIntNE(wc_XmssKey_Verify(&key, sig, sigSz, msg, (int)sizeof(msg)),
|
|
0);
|
|
sig[sigSz - 1] ^= 0x01;
|
|
}
|
|
|
|
XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER);
|
|
wc_XmssKey_Free(&key);
|
|
return EXPECT_RESULT();
|
|
}
|
|
#endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */
|
|
|
|
int test_wc_XmssDecisionCoverage(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_XMSS)
|
|
XmssKey key;
|
|
word32 len = 0;
|
|
|
|
XMEMSET(&key, 0, sizeof(key));
|
|
|
|
/* wc_XmssKey_Init: NULL key operand. */
|
|
ExpectIntEQ(wc_XmssKey_Init(NULL, NULL, INVALID_DEVID),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_Init(&key, NULL, INVALID_DEVID), 0);
|
|
|
|
/* wc_XmssKey_SetParamStr: NULL key vs NULL str (independence pair), then
|
|
* an unknown parameter string -> BAD_FUNC_ARG from the lookup. */
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(NULL, "XMSS-SHA2_10_256"),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&key, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&key, "XMSS-NOT-A-REAL-PARAM"),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
/* wc_XmssKey_SetParamStr: BAD_STATE_E once params are set (state !=
|
|
* INITED). Valid set first, then a second call fails on the state operand.
|
|
* Guard on the compiled-in minimum height (H10 must be available). */
|
|
#if !defined(WOLFSSL_XMSS_MIN_HEIGHT) || (WOLFSSL_XMSS_MIN_HEIGHT <= 10)
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&key, "XMSS-SHA2_10_256"), 0);
|
|
ExpectIntEQ(wc_XmssKey_SetParamStr(&key, "XMSS-SHA2_10_256"),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
|
|
/* wc_XmssKey_GetPubLen / GetSigLen: NULL len operand (params set). */
|
|
ExpectIntEQ(wc_XmssKey_GetPubLen(&key, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_GetPubLen(&key, &len), 0);
|
|
ExpectIntGT((int)len, 0);
|
|
ExpectIntEQ(wc_XmssKey_GetSigLen(&key, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_GetSigLen(&key, &len), 0);
|
|
ExpectIntGT((int)len, 0);
|
|
|
|
/* wc_XmssKey_ExportPub_ex / ExportPubRaw: params are set but no key has
|
|
* been made, so key->pk is still zeroed. Both exports must fail on the
|
|
* state operand rather than hand back a zeroed public key. */
|
|
{
|
|
XmssKey dst;
|
|
byte pub[2 * WC_XMSS_MAX_N + XMSS_OID_LEN];
|
|
word32 pubSz = (word32)sizeof(pub);
|
|
|
|
XMEMSET(&dst, 0, sizeof(dst));
|
|
ExpectIntEQ(wc_XmssKey_ExportPub_ex(&dst, &key, NULL, INVALID_DEVID),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
ExpectIntEQ(wc_XmssKey_ExportPubRaw(&key, pub, &pubSz),
|
|
WC_NO_ERR_TRACE(BAD_STATE_E));
|
|
}
|
|
#endif
|
|
|
|
/* wc_XmssKey_Verify: each NULL operand and mLen < 0 (arg check before any
|
|
* state/crypto). Independence: exactly one operand invalid per call. */
|
|
{
|
|
byte vsig[4] = {0};
|
|
byte vmsg[4] = {0};
|
|
ExpectIntEQ(wc_XmssKey_Verify(NULL, vsig, sizeof(vsig), vmsg,
|
|
(int)sizeof(vmsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_Verify(&key, NULL, sizeof(vsig), vmsg,
|
|
(int)sizeof(vmsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_Verify(&key, vsig, sizeof(vsig), NULL,
|
|
(int)sizeof(vmsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_Verify(&key, vsig, sizeof(vsig), vmsg, -1),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
}
|
|
|
|
#ifndef WOLFSSL_XMSS_VERIFY_ONLY
|
|
/* wc_XmssKey_SetWriteCb: NULL key vs NULL callback (independence pair). */
|
|
ExpectIntEQ(wc_XmssKey_SetWriteCb(NULL, xmss_mc_write_key),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_SetWriteCb(&key, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
/* wc_XmssKey_MakeKey: NULL key vs NULL rng (independence pair). */
|
|
ExpectIntEQ(wc_XmssKey_MakeKey(NULL, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_MakeKey(&key, NULL),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
|
|
/* wc_XmssKey_Sign: each NULL operand + mLen < 0, all before crypto. */
|
|
{
|
|
byte ssig[4] = {0};
|
|
word32 ssigSz = sizeof(ssig);
|
|
byte smsg[4] = {0};
|
|
ExpectIntEQ(wc_XmssKey_Sign(NULL, ssig, &ssigSz, smsg,
|
|
(int)sizeof(smsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_Sign(&key, NULL, &ssigSz, smsg,
|
|
(int)sizeof(smsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_Sign(&key, ssig, NULL, smsg,
|
|
(int)sizeof(smsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_Sign(&key, ssig, &ssigSz, NULL,
|
|
(int)sizeof(smsg)), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
ExpectIntEQ(wc_XmssKey_Sign(&key, ssig, &ssigSz, smsg, -1),
|
|
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
|
|
}
|
|
#endif /* !WOLFSSL_XMSS_VERIFY_ONLY */
|
|
|
|
wc_XmssKey_Free(&key);
|
|
#endif /* WOLFSSL_HAVE_XMSS */
|
|
return EXPECT_RESULT();
|
|
}
|
|
|
|
int test_wc_XmssFeatureCoverage(void)
|
|
{
|
|
EXPECT_DECLS;
|
|
#if defined(WOLFSSL_HAVE_XMSS) && !defined(WOLFSSL_XMSS_VERIFY_ONLY)
|
|
WC_RNG rng;
|
|
|
|
XMEMSET(&rng, 0, sizeof(rng));
|
|
ExpectIntEQ(wc_InitRng(&rng), 0);
|
|
|
|
/* Single-tree XMSS, one per compiled-in hash family. H10 keeps keygen
|
|
* fast; each family drives its own wc_xmss_impl.c hash-address helpers. */
|
|
#if !defined(WOLFSSL_XMSS_MIN_HEIGHT) || (WOLFSSL_XMSS_MIN_HEIGHT <= 10)
|
|
#ifdef WC_XMSS_SHA256
|
|
ExpectIntEQ(xmss_mc_param_roundtrip(&rng, "XMSS-SHA2_10_256"),
|
|
TEST_SUCCESS);
|
|
#endif
|
|
#ifdef WC_XMSS_SHA512
|
|
ExpectIntEQ(xmss_mc_param_roundtrip(&rng, "XMSS-SHA2_10_512"),
|
|
TEST_SUCCESS);
|
|
#endif
|
|
#ifdef WC_XMSS_SHAKE128
|
|
ExpectIntEQ(xmss_mc_param_roundtrip(&rng, "XMSS-SHAKE_10_256"),
|
|
TEST_SUCCESS);
|
|
#endif
|
|
#ifdef WC_XMSS_SHAKE256
|
|
ExpectIntEQ(xmss_mc_param_roundtrip(&rng, "XMSS-SHAKE256_10_256"),
|
|
TEST_SUCCESS);
|
|
#endif
|
|
#endif /* min height <= 10 */
|
|
|
|
/* Multi-tree XMSS^MT with total height 20 (2 layers): drives the
|
|
* XMSS^MT-specific subtree / BDS-state helpers a single tree skips. */
|
|
#if defined(WC_XMSS_SHA256) && (WOLFSSL_XMSS_MAX_HEIGHT >= 20) && \
|
|
(!defined(WOLFSSL_XMSS_MIN_HEIGHT) || (WOLFSSL_XMSS_MIN_HEIGHT <= 20))
|
|
ExpectIntEQ(xmss_mc_param_roundtrip(&rng, "XMSSMT-SHA2_20/2_256"),
|
|
TEST_SUCCESS);
|
|
#endif
|
|
|
|
/* Tall XMSS^MT (total height 40): the actual key height > 32 drives the
|
|
* 64-bit tree-index runtime path inside the mixed (MAX>32 && MIN<=32)
|
|
* index arm. 8 layers keep each subtree small (H5) so keygen stays fast.
|
|
* Only reachable when the compiled-in window admits height 40. Skipped
|
|
* under WOLFSSL_WC_XMSS_SMALL, whose recompute-signing makes a height-40
|
|
* tree slow; the 64-bit index path is unioned from the fast variant. */
|
|
#if defined(WC_XMSS_SHA256) && !defined(WOLFSSL_WC_XMSS_SMALL) && \
|
|
(WOLFSSL_XMSS_MAX_HEIGHT >= 40) && \
|
|
(!defined(WOLFSSL_XMSS_MIN_HEIGHT) || (WOLFSSL_XMSS_MIN_HEIGHT <= 40))
|
|
ExpectIntEQ(xmss_mc_param_roundtrip(&rng, "XMSSMT-SHA2_40/8_256"),
|
|
TEST_SUCCESS);
|
|
#endif
|
|
|
|
wc_FreeRng(&rng);
|
|
#endif /* WOLFSSL_HAVE_XMSS && !WOLFSSL_XMSS_VERIFY_ONLY */
|
|
return EXPECT_RESULT();
|
|
}
|