@@ -1740,6 +1740,17 @@ static void RunKexReplyHandshake(KexReplyHarness* harness,
17401740 result->steps = REGRESS_MAX_HANDSHAKE_STEPS;
17411741}
17421742
1743+ /* The shared secret K does not outlive the key exchange. */
1744+ static void AssertKexSecretWiped(const WOLFSSH* ssh)
1745+ {
1746+ word32 i;
1747+
1748+ AssertIntEQ(ssh->kSz, 0);
1749+ for (i = 0; i < (word32)sizeof(ssh->k); i++) {
1750+ AssertIntEQ(ssh->k[i], 0);
1751+ }
1752+ }
1753+
17431754static void AssertHandshakeSucceeds(const char* keyAlgo, const char* keyPath)
17441755{
17451756 KexReplyHarness harness;
@@ -1751,6 +1762,8 @@ static void AssertHandshakeSucceeds(const char* keyAlgo, const char* keyPath)
17511762
17521763 AssertTrue(result.clientSuccess);
17531764 AssertTrue(result.serverSuccess);
1765+ AssertKexSecretWiped(harness.client);
1766+ AssertKexSecretWiped(harness.server);
17541767 AssertIntEQ(harness.mutator.mutatedPackets, 0);
17551768 AssertIntEQ(harness.client->connectState, CONNECT_SERVER_CHANNEL_REQUEST_DONE);
17561769 AssertIntEQ(harness.server->acceptState, ACCEPT_CLIENT_SESSION_ESTABLISHED);
@@ -2159,10 +2172,55 @@ static void AssertHandshakeRejectsCorruptedSig(const char* keyAlgo,
21592172 AssertTrue(result.clientErr != WS_WANT_READ &&
21602173 result.clientErr != WS_WANT_WRITE);
21612174 AssertIntEQ(result.clientErr, expectedErr);
2175+ /* K was agreed before the verify failed. */
2176+ AssertKexSecretWiped(harness.client);
21622177
21632178 FreeKexReplyHarness(&harness);
21642179}
21652180
2181+ /* Every key agreement, the ML-KEM hybrids included, wipes K on both sides. */
2182+ static void TestKexSecretWipedPerKex(void)
2183+ {
2184+ static const char* kexAlgos[] = {
2185+ #ifndef WOLFSSH_NO_DH_GROUP14_SHA256
2186+ "diffie-hellman-group14-sha256",
2187+ #endif
2188+ #ifndef WOLFSSH_NO_ECDH_SHA2_NISTP256
2189+ "ecdh-sha2-nistp256",
2190+ #endif
2191+ #ifndef WOLFSSH_NO_CURVE25519_SHA256
2192+ "curve25519-sha256",
2193+ #endif
2194+ #ifndef WOLFSSH_NO_NISTP256_MLKEM768_SHA256
2195+ "mlkem768nistp256-sha256",
2196+ #endif
2197+ #ifndef WOLFSSH_NO_NISTP384_MLKEM1024_SHA384
2198+ "mlkem1024nistp384-sha384",
2199+ #endif
2200+ #ifndef WOLFSSH_NO_CURVE25519_MLKEM768_SHA256
2201+ "mlkem768x25519-sha256",
2202+ #endif
2203+ NULL
2204+ };
2205+ KexReplyHarness harness;
2206+ KexReplyRunResult result;
2207+ word32 i;
2208+
2209+ for (i = 0; kexAlgos[i] != NULL; i++) {
2210+ InitKexReplyHarnessKex(&harness, kexAlgos[i],
2211+ REGRESS_DEFAULT_KEY_ALGO, REGRESS_DEFAULT_KEY_PATH, 0,
2212+ REGRESS_MUTATE_SIG_NAME, NULL, 0);
2213+ RunKexReplyHandshake(&harness, &result);
2214+
2215+ AssertTrue(result.clientSuccess);
2216+ AssertTrue(result.serverSuccess);
2217+ AssertKexSecretWiped(harness.client);
2218+ AssertKexSecretWiped(harness.server);
2219+
2220+ FreeKexReplyHarness(&harness);
2221+ }
2222+ }
2223+
21662224#ifndef WOLFSSH_NO_RSA_SHA2_256
21672225static void TestKexDhReplyRejectsRsaSha2_256CorruptSig(void)
21682226{
@@ -16713,6 +16771,7 @@ static void TestGenerateKeysSplit(void)
1671316771 word32 payloadSz;
1671416772 word32 idx;
1671516773 byte zeros[AES_256_KEY_SIZE];
16774+ Keys zeroKeys;
1671616775
1671716776 WMEMSET(zeros, 0, sizeof(zeros));
1671816777
@@ -16780,6 +16839,13 @@ static void TestGenerateKeysSplit(void)
1678016839 AssertTrue(WMEMCMP(ssh->handshake->peerKeys.encKey,
1678116840 ssh->handshake->keys.encKey, AES_128_KEY_SIZE) != 0);
1678216841
16842+ /* A failed derivation leaves no key material behind. */
16843+ AssertTrue(wolfSSH_TestGenerateKeys(ssh, WC_HASH_TYPE_NONE) != WS_SUCCESS);
16844+ WMEMSET(&zeroKeys, 0, sizeof(zeroKeys));
16845+ AssertTrue(WMEMCMP(&ssh->handshake->keys, &zeroKeys, sizeof(Keys)) == 0);
16846+ AssertTrue(WMEMCMP(&ssh->handshake->peerKeys, &zeroKeys,
16847+ sizeof(Keys)) == 0);
16848+
1678316849 wolfSSH_free(ssh);
1678416850
1678516851#ifndef WOLFSSH_NO_AES_GCM
@@ -18660,6 +18726,7 @@ int main(int argc, char** argv)
1866018726 TestKexDhReplyRejectsEd25519CorruptSig();
1866118727 #endif
1866218728 TestKexDhReplyRejectsSigNameOverrun();
18729+ TestKexSecretWipedPerKex();
1866318730 #ifdef REGRESS_TRUNC_KEX_ALGO
1866418731 TestKexDhReplyTruncatedFSendsDisconnect();
1866518732 TestKexDhInitTruncatedESendsDisconnect();
0 commit comments