From f5cb791e39755f43f8007ece66ad566fd8ace6b9 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Mon, 24 Nov 2025 10:22:40 -0700 Subject: [PATCH 1/2] ML-KEM: Add check for Pubkey hash mismatch on decoding the dk --- wolfcrypt/src/error.c | 3 +++ wolfcrypt/src/wc_mlkem.c | 11 +++++++++++ wolfssl/wolfcrypt/error-crypt.h | 6 ++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/wolfcrypt/src/error.c b/wolfcrypt/src/error.c index 1d5c90d10..45092a2da 100644 --- a/wolfcrypt/src/error.c +++ b/wolfcrypt/src/error.c @@ -656,6 +656,9 @@ const char* wc_GetErrorString(int error) case INTERRUPTED_E: return "Process interrupted"; + case MLKEM_PUB_HASH_E: + return "ML-KEM priv key's stored hash doesn't match encoded pub key"; + case MAX_CODE_E: case WC_SPAN1_MIN_CODE_E: case MIN_CODE_E: diff --git a/wolfcrypt/src/wc_mlkem.c b/wolfcrypt/src/wc_mlkem.c index d9372f5f7..3bbf299b6 100644 --- a/wolfcrypt/src/wc_mlkem.c +++ b/wolfcrypt/src/wc_mlkem.c @@ -1681,6 +1681,9 @@ int wc_MlKemKey_DecodePrivateKey(MlKemKey* key, const unsigned char* in, } if (ret == 0) { + byte computedHash[WC_ML_KEM_SYM_SZ]; + XMEMSET(computedHash, 0, WC_ML_KEM_SYM_SZ); + /* Decode private key that is vector of polynomials. * Alg 18 Step 1: dk_PKE <- dk[0 : 384k] * Alg 15 Step 5: s_hat <- ByteDecode_12(dk_PKE) */ @@ -1689,16 +1692,24 @@ int wc_MlKemKey_DecodePrivateKey(MlKemKey* key, const unsigned char* in, /* Decode the public key that is after the private key. */ mlkemkey_decode_public(key->pub, key->pubSeed, p, k); + /* Compute the hash of the public key. */ + MLKEM_HASH_H(&key->hash, p, pubLen, computedHash); p += pubLen; /* Copy the hash of the encoded public key that is after public key. */ XMEMCPY(key->h, p, sizeof(key->h)); p += WC_ML_KEM_SYM_SZ; + /* Copy the z (randomizer) that is after hash. */ XMEMCPY(key->z, p, sizeof(key->z)); /* Set flags */ key->flags |= MLKEM_FLAG_H_SET | MLKEM_FLAG_BOTH_SET; + + /* Compare computed public key hash with stored hash */ + if (XMEMCMP(key->h, computedHash, WC_ML_KEM_SYM_SZ) != 0) + ret = MLKEM_PUB_HASH_E; + } return ret; diff --git a/wolfssl/wolfcrypt/error-crypt.h b/wolfssl/wolfcrypt/error-crypt.h index 4a5df0561..0e7d21cfb 100644 --- a/wolfssl/wolfcrypt/error-crypt.h +++ b/wolfssl/wolfcrypt/error-crypt.h @@ -307,9 +307,11 @@ enum wolfCrypt_ErrorCodes { WC_ACCEL_INHIBIT_E = -1002, /* Crypto acceleration is currently inhibited */ BAD_INDEX_E = -1003, /* Bad index */ INTERRUPTED_E = -1004, /* Process interrupted */ + MLKEM_PUB_HASH_E = -1005, /* Encoded public key in decapsulation key does + * not match stored hash*/ - WC_SPAN2_LAST_E = -1004, /* Update to indicate last used error code */ - WC_LAST_E = -1004, /* the last code used either here or in + WC_SPAN2_LAST_E = -1005, /* Update to indicate last used error code */ + WC_LAST_E = -1005, /* the last code used either here or in * error-ssl.h */ WC_SPAN2_MIN_CODE_E = -1999, /* Last usable code in span 2 */ From eace02115b4bbbf999f67641461de8eaf97978b5 Mon Sep 17 00:00:00 2001 From: Lealem Amedie Date: Mon, 24 Nov 2025 16:57:52 -0700 Subject: [PATCH 2/2] Address review feedback --- wolfcrypt/src/wc_mlkem.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/wolfcrypt/src/wc_mlkem.c b/wolfcrypt/src/wc_mlkem.c index 3bbf299b6..67f34013f 100644 --- a/wolfcrypt/src/wc_mlkem.c +++ b/wolfcrypt/src/wc_mlkem.c @@ -1681,9 +1681,6 @@ int wc_MlKemKey_DecodePrivateKey(MlKemKey* key, const unsigned char* in, } if (ret == 0) { - byte computedHash[WC_ML_KEM_SYM_SZ]; - XMEMSET(computedHash, 0, WC_ML_KEM_SYM_SZ); - /* Decode private key that is vector of polynomials. * Alg 18 Step 1: dk_PKE <- dk[0 : 384k] * Alg 15 Step 5: s_hat <- ByteDecode_12(dk_PKE) */ @@ -1693,23 +1690,23 @@ int wc_MlKemKey_DecodePrivateKey(MlKemKey* key, const unsigned char* in, /* Decode the public key that is after the private key. */ mlkemkey_decode_public(key->pub, key->pubSeed, p, k); /* Compute the hash of the public key. */ - MLKEM_HASH_H(&key->hash, p, pubLen, computedHash); + ret = MLKEM_HASH_H(&key->hash, p, pubLen, key->h); p += pubLen; + } + + if (ret == 0) { + /* Compare computed public key hash with stored hash */ + if (XMEMCMP(key->h, p, WC_ML_KEM_SYM_SZ) != 0) + ret = MLKEM_PUB_HASH_E; /* Copy the hash of the encoded public key that is after public key. */ XMEMCPY(key->h, p, sizeof(key->h)); p += WC_ML_KEM_SYM_SZ; - /* Copy the z (randomizer) that is after hash. */ XMEMCPY(key->z, p, sizeof(key->z)); /* Set flags */ key->flags |= MLKEM_FLAG_H_SET | MLKEM_FLAG_BOTH_SET; - - /* Compare computed public key hash with stored hash */ - if (XMEMCMP(key->h, computedHash, WC_ML_KEM_SYM_SZ) != 0) - ret = MLKEM_PUB_HASH_E; - } return ret;