From de3ec51a7781f70f32b972e80331c7dfd359f4d4 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Tue, 29 Sep 2026 15:55:08 -0700 Subject: [PATCH] regress: cover the hybrid KEX failure disconnect DoKexDhReply() maps any KeyAgree_client() failure to WS_CRYPTO_FAILED and sends KEY_EXCHANGE_FAILED. Pin that for the hybrid ML-KEM KEXes, whose client agreement returns raw wolfCrypt codes. - truncate f in a hybrid KEXDH_REPLY and check the client's error and disconnect reason --- tests/regress.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/regress.c b/tests/regress.c index d12a9a42..a99cfd54 100644 --- a/tests/regress.c +++ b/tests/regress.c @@ -729,6 +729,15 @@ static word32 LoadFileBuffer(const char* path, byte* buf, word32 bufSz) #define REGRESS_TRUNC_KEX_ALGO "ecdh-sha2-nistp256" #endif +/* Hybrid KEX algorithm for the truncated hybrid f test */ +#if !defined(WOLFSSH_NO_CURVE25519_MLKEM768_SHA256) + #define REGRESS_HYBRID_KEX_ALGO "mlkem768x25519-sha256" +#elif !defined(WOLFSSH_NO_NISTP256_MLKEM768_SHA256) + #define REGRESS_HYBRID_KEX_ALGO "mlkem768nistp256-sha256" +#elif !defined(WOLFSSH_NO_NISTP384_MLKEM1024_SHA384) + #define REGRESS_HYBRID_KEX_ALGO "mlkem1024nistp384-sha384" +#endif + /* KEX algorithm for the GEX group test */ #ifndef WOLFSSH_NO_DH_GEX_SHA256 #define REGRESS_GEX_KEX_ALGO "diffie-hellman-group-exchange-sha256" @@ -2381,6 +2390,33 @@ static void TestKexDhInitEmptyESendsDisconnect(void) } #endif /* REGRESS_TRUNC_KEX_ALGO */ +#ifdef REGRESS_HYBRID_KEX_ALGO +/* A hybrid KEX failure on the client also ends with KEY_EXCHANGE_FAILED. The + * short f leaves the classical peer key one byte short. */ +static void TestKexHybridReplyTruncatedFSendsDisconnect(void) +{ + KexReplyHarness harness; + KexReplyRunResult result; + + InitKexReplyHarnessKex(&harness, REGRESS_HYBRID_KEX_ALGO, + REGRESS_DEFAULT_KEY_ALGO, REGRESS_DEFAULT_KEY_PATH, 1, + REGRESS_MUTATE_F_TRUNC, NULL, 0); + RunKexReplyHandshake(&harness, &result); + + AssertIntEQ(harness.mutator.parseError, 0); + AssertIntEQ(harness.mutator.mutatedPackets, 1); + AssertFalse(result.clientSuccess); + AssertFalse(harness.client->connectState >= CONNECT_KEYED); + AssertTrue(result.clientRet == WS_FATAL_ERROR); + AssertIntEQ(result.clientErr, WS_CRYPTO_FAILED); + AssertTrue(harness.clientIo.sawDisconnect); + AssertIntEQ(harness.clientIo.disconnectReason, + WOLFSSH_DISCONNECT_KEY_EXCHANGE_FAILED); + + FreeKexReplyHarness(&harness); +} +#endif /* REGRESS_HYBRID_KEX_ALGO */ + #ifdef REGRESS_GEX_KEX_ALGO /* A GEX group below the floor this client enforces (RFC 8270) ends the key * exchange. Covers the WS_DH_SIZE_E arm of the client's guard, which no @@ -19605,6 +19641,9 @@ int main(int argc, char** argv) TestKexDhInitTruncatedESendsDisconnect(); TestKexDhInitEmptyESendsDisconnect(); #endif + #ifdef REGRESS_HYBRID_KEX_ALGO + TestKexHybridReplyTruncatedFSendsDisconnect(); + #endif #ifdef REGRESS_GEX_KEX_ALGO TestKexDhGexGroupShrunkPrimeSendsDisconnect(); TestKexDhGexGroupBadGeneratorSendsDisconnect();