Skip to content

Commit 4143a11

Browse files
committed
add PQ key integrity tests
1 parent 8b030c0 commit 4143a11

3 files changed

Lines changed: 69 additions & 1 deletion

File tree

tests/api/test_mlkem.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4018,3 +4018,63 @@ int test_wc_mlkem_decap_fo_reject(void)
40184018
return EXPECT_RESULT();
40194019
} /* END test_wc_mlkem_decap_fo_reject */
40204020

4021+
/*
4022+
* Exercise the public-key-hash integrity check in wc_MlKemKey_DecodePrivateKey.
4023+
* Encode a valid private key, flip a byte inside the embedded H(pk) field,
4024+
* then re-decode and assert MLKEM_PUB_HASH_E is returned.
4025+
*
4026+
* Private key layout per FIPS 203: dk_PKE || ek || H(ek) || z
4027+
* The 32-byte hash H(ek) sits at offset (privLen - 2 * WC_ML_KEM_SYM_SZ).
4028+
*/
4029+
int test_wc_mlkem_decode_privkey_bad_pubhash(void)
4030+
{
4031+
EXPECT_DECLS;
4032+
#if defined(WOLFSSL_HAVE_MLKEM) && defined(WOLFSSL_WC_MLKEM) && \
4033+
!defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_MLKEM_NO_MAKE_KEY)
4034+
MlKemKey* key = NULL;
4035+
WC_RNG rng;
4036+
byte priv[WC_ML_KEM_MAX_PRIVATE_KEY_SIZE];
4037+
word32 privLen = 0;
4038+
#ifndef WOLFSSL_NO_ML_KEM_768
4039+
const int mlkemType = WC_ML_KEM_768;
4040+
#elif !defined(WOLFSSL_NO_ML_KEM_512)
4041+
const int mlkemType = WC_ML_KEM_512;
4042+
#else
4043+
const int mlkemType = WC_ML_KEM_1024;
4044+
#endif
4045+
4046+
XMEMSET(&rng, 0, sizeof(rng));
4047+
XMEMSET(priv, 0, sizeof(priv));
4048+
4049+
key = (MlKemKey*)XMALLOC(sizeof(*key), NULL, DYNAMIC_TYPE_TMP_BUFFER);
4050+
ExpectNotNull(key);
4051+
ExpectIntEQ(wc_InitRng(&rng), 0);
4052+
4053+
ExpectIntEQ(wc_MlKemKey_Init(key, mlkemType, NULL, INVALID_DEVID), 0);
4054+
ExpectIntEQ(wc_MlKemKey_MakeKey(key, &rng), 0);
4055+
ExpectIntEQ(wc_MlKemKey_PrivateKeySize(key, &privLen), 0);
4056+
ExpectTrue(privLen > (word32)(2 * WC_ML_KEM_SYM_SZ));
4057+
ExpectIntEQ(wc_MlKemKey_EncodePrivateKey(key, priv, privLen), 0);
4058+
4059+
/* Baseline: untampered blob decodes cleanly. */
4060+
wc_MlKemKey_Free(key);
4061+
ExpectIntEQ(wc_MlKemKey_Init(key, mlkemType, NULL, INVALID_DEVID), 0);
4062+
ExpectIntEQ(wc_MlKemKey_DecodePrivateKey(key, priv, privLen), 0);
4063+
wc_MlKemKey_Free(key);
4064+
4065+
/* Flip one byte inside H(ek) - the 32 bytes preceding z. */
4066+
if (privLen > (word32)(2 * WC_ML_KEM_SYM_SZ)) {
4067+
priv[privLen - 2 * WC_ML_KEM_SYM_SZ] ^= 0x01;
4068+
}
4069+
4070+
ExpectIntEQ(wc_MlKemKey_Init(key, mlkemType, NULL, INVALID_DEVID), 0);
4071+
ExpectIntEQ(wc_MlKemKey_DecodePrivateKey(key, priv, privLen),
4072+
WC_NO_ERR_TRACE(MLKEM_PUB_HASH_E));
4073+
wc_MlKemKey_Free(key);
4074+
4075+
DoExpectIntEQ(wc_FreeRng(&rng), 0);
4076+
XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER);
4077+
#endif
4078+
return EXPECT_RESULT();
4079+
} /* END test_wc_mlkem_decode_privkey_bad_pubhash */
4080+

tests/api/test_mlkem.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ int test_wc_mlkem_encapsulate_kats(void);
2929
int test_wc_mlkem_decapsulate_kats(void);
3030
int test_wc_mlkem_decapsulate_pubonly_fails(void);
3131
int test_wc_mlkem_decap_fo_reject(void);
32+
int test_wc_mlkem_decode_privkey_bad_pubhash(void);
3233

3334
#define TEST_MLKEM_DECLS \
3435
TEST_DECL_GROUP("mlkem", test_wc_mlkem_make_key_kats), \
3536
TEST_DECL_GROUP("mlkem", test_wc_mlkem_encapsulate_kats), \
3637
TEST_DECL_GROUP("mlkem", test_wc_mlkem_decapsulate_kats), \
3738
TEST_DECL_GROUP("mlkem", test_wc_mlkem_decapsulate_pubonly_fails), \
38-
TEST_DECL_GROUP("mlkem", test_wc_mlkem_decap_fo_reject)
39+
TEST_DECL_GROUP("mlkem", test_wc_mlkem_decap_fo_reject), \
40+
TEST_DECL_GROUP("mlkem", test_wc_mlkem_decode_privkey_bad_pubhash)
3941

4042
#endif /* WOLFCRYPT_TEST_MLKEM_H */

tests/api/test_slhdsa.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,12 @@ int test_wc_slhdsa_check_key(void)
11641164
ExpectIntEQ(wc_SlhDsaKey_ImportPublic(&key, pubKey, pubKeyLen), 0);
11651165
ExpectIntEQ(wc_SlhDsaKey_ImportPrivate(&key, privKey, privKeyLen), 0);
11661166
ExpectIntEQ(wc_SlhDsaKey_CheckKey(&key), 0);
1167+
1168+
/* Tamper the private seed. The re-derived root will not match the
1169+
* stored pk_root, so CheckKey must return WC_KEY_MISMATCH_E. */
1170+
key.sk[0] ^= 0x01;
1171+
ExpectIntEQ(wc_SlhDsaKey_CheckKey(&key),
1172+
WC_NO_ERR_TRACE(WC_KEY_MISMATCH_E));
11671173
wc_SlhDsaKey_Free(&key);
11681174

11691175
wc_FreeRng(&rng);

0 commit comments

Comments
 (0)