@@ -3950,3 +3950,67 @@ int test_wc_mlkem_decapsulate_pubonly_fails(void)
39503950 return EXPECT_RESULT ();
39513951} /* END test_wc_mlkem_decapsulate_pubonly_fails */
39523952
3953+ /* Verify that the FO re-encryption check catches ciphertext tampering
3954+ * at various byte offsets and falls back to implicit rejection. */
3955+ int test_wc_mlkem_decap_fo_reject (void )
3956+ {
3957+ EXPECT_DECLS ;
3958+ #if !defined(HAVE_FIPS ) || FIPS_VERSION3_GE (7 ,0 ,0 )
3959+ #if defined(WOLFSSL_HAVE_MLKEM ) && defined(WOLFSSL_WC_MLKEM ) && \
3960+ !defined(WOLFSSL_NO_ML_KEM ) && !defined(WOLFSSL_MLKEM_NO_DECAPSULATE ) && \
3961+ !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE ) && \
3962+ !defined(WOLFSSL_MLKEM_NO_MAKE_KEY )
3963+ MlKemKey * key = NULL ;
3964+ WC_RNG rng ;
3965+ byte ct [WC_ML_KEM_MAX_CIPHER_TEXT_SIZE ];
3966+ byte ctTampered [WC_ML_KEM_MAX_CIPHER_TEXT_SIZE ];
3967+ byte ss [WC_ML_KEM_SS_SZ ];
3968+ byte ssDec [WC_ML_KEM_SS_SZ ];
3969+ byte ssTampered [WC_ML_KEM_SS_SZ ];
3970+ word32 ctLen = 0 ;
3971+
3972+ key = (MlKemKey * )XMALLOC (sizeof (* key ), NULL , DYNAMIC_TYPE_TMP_BUFFER );
3973+ ExpectNotNull (key );
3974+
3975+ XMEMSET (& rng , 0 , sizeof (rng ));
3976+ ExpectIntEQ (wc_InitRng (& rng ), 0 );
3977+
3978+ #ifndef WOLFSSL_NO_ML_KEM_768
3979+ ExpectIntEQ (wc_MlKemKey_Init (key , WC_ML_KEM_768 , NULL , INVALID_DEVID ), 0 );
3980+ #elif !defined(WOLFSSL_NO_ML_KEM_512 )
3981+ ExpectIntEQ (wc_MlKemKey_Init (key , WC_ML_KEM_512 , NULL , INVALID_DEVID ), 0 );
3982+ #else
3983+ ExpectIntEQ (wc_MlKemKey_Init (key , WC_ML_KEM_1024 , NULL , INVALID_DEVID ), 0 );
3984+ #endif
3985+
3986+ ExpectIntEQ (wc_MlKemKey_CipherTextSize (key , & ctLen ), 0 );
3987+ ExpectIntEQ (wc_MlKemKey_MakeKey (key , & rng ), 0 );
3988+ ExpectIntEQ (wc_MlKemKey_Encapsulate (key , ct , ss , & rng ), 0 );
3989+
3990+ /* Untampered ciphertext recovers the original ss. */
3991+ XMEMSET (ssDec , 0 , sizeof (ssDec ));
3992+ ExpectIntEQ (wc_MlKemKey_Decapsulate (key , ssDec , ct , ctLen ), 0 );
3993+ ExpectIntEQ (XMEMCMP (ssDec , ss , WC_ML_KEM_SS_SZ ), 0 );
3994+
3995+ /* Tamper at byte 32: implicit rejection must fire. */
3996+ XMEMCPY (ctTampered , ct , ctLen );
3997+ ctTampered [32 ] ^= 0x01 ;
3998+ XMEMSET (ssTampered , 0 , sizeof (ssTampered ));
3999+ ExpectIntEQ (wc_MlKemKey_Decapsulate (key , ssTampered , ctTampered , ctLen ), 0 );
4000+ ExpectIntNE (XMEMCMP (ssTampered , ss , WC_ML_KEM_SS_SZ ), 0 );
4001+
4002+ /* Tamper at byte 0: also must be rejected. */
4003+ XMEMCPY (ctTampered , ct , ctLen );
4004+ ctTampered [0 ] ^= 0x01 ;
4005+ XMEMSET (ssTampered , 0 , sizeof (ssTampered ));
4006+ ExpectIntEQ (wc_MlKemKey_Decapsulate (key , ssTampered , ctTampered , ctLen ), 0 );
4007+ ExpectIntNE (XMEMCMP (ssTampered , ss , WC_ML_KEM_SS_SZ ), 0 );
4008+
4009+ DoExpectIntEQ (wc_FreeRng (& rng ), 0 );
4010+ wc_MlKemKey_Free (key );
4011+ XFREE (key , NULL , DYNAMIC_TYPE_TMP_BUFFER );
4012+ #endif
4013+ #endif
4014+ return EXPECT_RESULT ();
4015+ } /* END test_wc_mlkem_decap_fo_reject */
4016+
0 commit comments