Skip to content

Commit 387e7f3

Browse files
committed
Wipe the KEX shared secret once keys are derived
DoKexDhReply() and SendKexDhReply() zero all of ssh->k and reset kSz on every exit, so K does not outlive the key exchange. GenerateKeys() zeroes both handshake key sets when a derivation fails. - the full-buffer wipe clears CreateMpint and ML-KEM hybrid leftovers - tests/regress.c checks K after each KEX, the hybrids included - tests/regress.c checks a failed derivation leaves no keys Issue: F-8845, F-11680, F-11681, F-8843, F-11682, F-14001
1 parent 8221e4e commit 387e7f3

2 files changed

Lines changed: 83 additions & 0 deletions

File tree

‎src/internal.c‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3724,6 +3724,12 @@ static int GenerateKeys(WOLFSSH* ssh, byte hashId, byte doKeyPad)
37243724
}
37253725
#endif /* SHOW_SECRETS */
37263726

3727+
/* Do not keep a partial key set from a failed derivation. */
3728+
if (ret != WS_SUCCESS && cK != NULL) {
3729+
WS_FORCEZERO(cK, sizeof(Keys));
3730+
WS_FORCEZERO(sK, sizeof(Keys));
3731+
}
3732+
37273733
return ret;
37283734
}
37293735

@@ -9015,6 +9021,10 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
90159021
ret = SendNewKeys(ssh);
90169022
}
90179023

9024+
/* K is only needed to derive the keys. Wipe all of it, success or not. */
9025+
WS_FORCEZERO(ssh->k, sizeof(ssh->k));
9026+
ssh->kSz = 0;
9027+
90189028
if (sigKeyBlock_ptr)
90199029
WFREE(sigKeyBlock_ptr, ssh->ctx->heap, DYNTYPE_PRIVKEY);
90209030
/* RFC 4253 11.1: WS_PUBKEY_REJECTED_E here is only the host key check,
@@ -18882,6 +18892,12 @@ int SendKexDhReply(WOLFSSH* ssh)
1888218892
if (ret != WS_WANT_WRITE && ret != WS_SUCCESS)
1888318893
PurgePacket(ssh);
1888418894

18895+
/* K is only needed to derive the keys. Wipe all of it, success or not. */
18896+
if (ssh != NULL) {
18897+
WS_FORCEZERO(ssh->k, sizeof(ssh->k));
18898+
ssh->kSz = 0;
18899+
}
18900+
1888518901
WLOG(WS_LOG_DEBUG, "Leaving SendKexDhReply(), ret = %d", ret);
1888618902
if (sigKeyBlock_ptr) {
1888718903
WS_FORCEZERO(sigKeyBlock_ptr, sizeof(struct wolfSSH_sigKeyBlockFull));

‎tests/regress.c‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
17431754
static 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
21672225
static 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

Comments
 (0)