Skip to content

Commit 1edee3c

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 96fdbe5 commit 1edee3c

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
@@ -3700,6 +3700,12 @@ static int GenerateKeys(WOLFSSH* ssh, byte hashId, byte doKeyPad)
37003700
}
37013701
#endif /* SHOW_SECRETS */
37023702

3703+
/* Do not keep a partial key set from a failed derivation. */
3704+
if (ret != WS_SUCCESS && cK != NULL) {
3705+
WS_FORCEZERO(cK, sizeof(Keys));
3706+
WS_FORCEZERO(sK, sizeof(Keys));
3707+
}
3708+
37033709
return ret;
37043710
}
37053711

@@ -8914,6 +8920,10 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
89148920
ret = SendNewKeys(ssh);
89158921
}
89168922

8923+
/* K is only needed to derive the keys. Wipe all of it, success or not. */
8924+
WS_FORCEZERO(ssh->k, sizeof(ssh->k));
8925+
ssh->kSz = 0;
8926+
89178927
if (sigKeyBlock_ptr)
89188928
WFREE(sigKeyBlock_ptr, ssh->ctx->heap, DYNTYPE_PRIVKEY);
89198929
/* RFC 4253 11.1: WS_PUBKEY_REJECTED_E here is only the host key check,
@@ -18675,6 +18685,12 @@ int SendKexDhReply(WOLFSSH* ssh)
1867518685
if (ret != WS_WANT_WRITE && ret != WS_SUCCESS)
1867618686
PurgePacket(ssh);
1867718687

18688+
/* K is only needed to derive the keys. Wipe all of it, success or not. */
18689+
if (ssh != NULL) {
18690+
WS_FORCEZERO(ssh->k, sizeof(ssh->k));
18691+
ssh->kSz = 0;
18692+
}
18693+
1867818694
WLOG(WS_LOG_DEBUG, "Leaving SendKexDhReply(), ret = %d", ret);
1867918695
if (sigKeyBlock_ptr) {
1868018696
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
@@ -1500,6 +1500,17 @@ static void RunKexReplyHandshake(KexReplyHarness* harness,
15001500
result->steps = REGRESS_MAX_HANDSHAKE_STEPS;
15011501
}
15021502

1503+
/* The shared secret K does not outlive the key exchange. */
1504+
static void AssertKexSecretWiped(const WOLFSSH* ssh)
1505+
{
1506+
word32 i;
1507+
1508+
AssertIntEQ(ssh->kSz, 0);
1509+
for (i = 0; i < (word32)sizeof(ssh->k); i++) {
1510+
AssertIntEQ(ssh->k[i], 0);
1511+
}
1512+
}
1513+
15031514
static void AssertHandshakeSucceeds(const char* keyAlgo, const char* keyPath)
15041515
{
15051516
KexReplyHarness harness;
@@ -1511,6 +1522,8 @@ static void AssertHandshakeSucceeds(const char* keyAlgo, const char* keyPath)
15111522

15121523
AssertTrue(result.clientSuccess);
15131524
AssertTrue(result.serverSuccess);
1525+
AssertKexSecretWiped(harness.client);
1526+
AssertKexSecretWiped(harness.server);
15141527
AssertIntEQ(harness.mutator.mutatedPackets, 0);
15151528
AssertIntEQ(harness.client->connectState, CONNECT_SERVER_CHANNEL_REQUEST_DONE);
15161529
AssertIntEQ(harness.server->acceptState, ACCEPT_CLIENT_SESSION_ESTABLISHED);
@@ -1919,10 +1932,55 @@ static void AssertHandshakeRejectsCorruptedSig(const char* keyAlgo,
19191932
AssertTrue(result.clientErr != WS_WANT_READ &&
19201933
result.clientErr != WS_WANT_WRITE);
19211934
AssertIntEQ(result.clientErr, expectedErr);
1935+
/* K was agreed before the verify failed. */
1936+
AssertKexSecretWiped(harness.client);
19221937

19231938
FreeKexReplyHarness(&harness);
19241939
}
19251940

1941+
/* Every key agreement, the ML-KEM hybrids included, wipes K on both sides. */
1942+
static void TestKexSecretWipedPerKex(void)
1943+
{
1944+
static const char* kexAlgos[] = {
1945+
#ifndef WOLFSSH_NO_DH_GROUP14_SHA256
1946+
"diffie-hellman-group14-sha256",
1947+
#endif
1948+
#ifndef WOLFSSH_NO_ECDH_SHA2_NISTP256
1949+
"ecdh-sha2-nistp256",
1950+
#endif
1951+
#ifndef WOLFSSH_NO_CURVE25519_SHA256
1952+
"curve25519-sha256",
1953+
#endif
1954+
#ifndef WOLFSSH_NO_NISTP256_MLKEM768_SHA256
1955+
"mlkem768nistp256-sha256",
1956+
#endif
1957+
#ifndef WOLFSSH_NO_NISTP384_MLKEM1024_SHA384
1958+
"mlkem1024nistp384-sha384",
1959+
#endif
1960+
#ifndef WOLFSSH_NO_CURVE25519_MLKEM768_SHA256
1961+
"mlkem768x25519-sha256",
1962+
#endif
1963+
NULL
1964+
};
1965+
KexReplyHarness harness;
1966+
KexReplyRunResult result;
1967+
word32 i;
1968+
1969+
for (i = 0; kexAlgos[i] != NULL; i++) {
1970+
InitKexReplyHarnessKex(&harness, kexAlgos[i],
1971+
REGRESS_DEFAULT_KEY_ALGO, REGRESS_DEFAULT_KEY_PATH, 0,
1972+
REGRESS_MUTATE_SIG_NAME, NULL, 0);
1973+
RunKexReplyHandshake(&harness, &result);
1974+
1975+
AssertTrue(result.clientSuccess);
1976+
AssertTrue(result.serverSuccess);
1977+
AssertKexSecretWiped(harness.client);
1978+
AssertKexSecretWiped(harness.server);
1979+
1980+
FreeKexReplyHarness(&harness);
1981+
}
1982+
}
1983+
19261984
#ifndef WOLFSSH_NO_RSA_SHA2_256
19271985
static void TestKexDhReplyRejectsRsaSha2_256CorruptSig(void)
19281986
{
@@ -15674,6 +15732,7 @@ static void TestGenerateKeysSplit(void)
1567415732
word32 payloadSz;
1567515733
word32 idx;
1567615734
byte zeros[AES_256_KEY_SIZE];
15735+
Keys zeroKeys;
1567715736

1567815737
WMEMSET(zeros, 0, sizeof(zeros));
1567915738

@@ -15741,6 +15800,13 @@ static void TestGenerateKeysSplit(void)
1574115800
AssertTrue(WMEMCMP(ssh->handshake->peerKeys.encKey,
1574215801
ssh->handshake->keys.encKey, AES_128_KEY_SIZE) != 0);
1574315802

15803+
/* A failed derivation leaves no key material behind. */
15804+
AssertTrue(wolfSSH_TestGenerateKeys(ssh, WC_HASH_TYPE_NONE) != WS_SUCCESS);
15805+
WMEMSET(&zeroKeys, 0, sizeof(zeroKeys));
15806+
AssertTrue(WMEMCMP(&ssh->handshake->keys, &zeroKeys, sizeof(Keys)) == 0);
15807+
AssertTrue(WMEMCMP(&ssh->handshake->peerKeys, &zeroKeys,
15808+
sizeof(Keys)) == 0);
15809+
1574415810
wolfSSH_free(ssh);
1574515811

1574615812
#ifndef WOLFSSH_NO_AES_GCM
@@ -17406,6 +17472,7 @@ int main(int argc, char** argv)
1740617472
TestKexDhReplyRejectsEd25519CorruptSig();
1740717473
#endif
1740817474
TestKexDhReplyRejectsSigNameOverrun();
17475+
TestKexSecretWipedPerKex();
1740917476
#ifdef REGRESS_TRUNC_KEX_ALGO
1741017477
TestKexDhReplyTruncatedFSendsDisconnect();
1741117478
TestKexDhInitTruncatedESendsDisconnect();

0 commit comments

Comments
 (0)