Skip to content

Commit 47ae4da

Browse files
committed
Keep negotiated key sizes when derivation fails
GenerateKeys() now wipes only the iv, encKey and macKey bytes when a derivation fails, so the negotiated sizes stay put and a second call cannot report success with no keys. - tests/regress.c checks a failed server KEXDH_REPLY send wipes K - AssertHandshakeSucceeds() takes a KEX algorithm for the per-KEX check - KEX wipe failures name the algorithm and the endpoint Issue: F-8845, F-11680, F-11681, F-8843, F-11682, F-14001
1 parent 387e7f3 commit 47ae4da

2 files changed

Lines changed: 76 additions & 27 deletions

File tree

‎src/internal.c‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3724,10 +3724,15 @@ 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. */
3727+
/* Do not keep a partial key set from a failed derivation. Keep the
3728+
* negotiated sizes so a retry cannot succeed with no keys. */
37283729
if (ret != WS_SUCCESS && cK != NULL) {
3729-
WS_FORCEZERO(cK, sizeof(Keys));
3730-
WS_FORCEZERO(sK, sizeof(Keys));
3730+
WS_FORCEZERO(cK->iv, sizeof(cK->iv));
3731+
WS_FORCEZERO(cK->encKey, sizeof(cK->encKey));
3732+
WS_FORCEZERO(cK->macKey, sizeof(cK->macKey));
3733+
WS_FORCEZERO(sK->iv, sizeof(sK->iv));
3734+
WS_FORCEZERO(sK->encKey, sizeof(sK->encKey));
3735+
WS_FORCEZERO(sK->macKey, sizeof(sK->macKey));
37313736
}
37323737

37333738
return ret;

‎tests/regress.c‎

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,7 @@ typedef struct DuplexEndpoint {
797797
/* What this endpoint put in its own plaintext KEXINIT */
798798
byte sentStrictKexMarker;
799799
byte sentExtInfoMarker;
800+
byte failSendMsgId; /* nonzero: fail the send of a packet with this id */
800801
} DuplexEndpoint;
801802

802803
typedef struct {
@@ -1505,6 +1506,12 @@ static int DuplexSend(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
15051506
return WS_CBIO_ERR_GENERAL;
15061507
}
15071508

1509+
if (endpoint->failSendMsgId != 0 &&
1510+
outputSz > UINT32_SZ + PAD_LENGTH_SZ &&
1511+
output[UINT32_SZ + PAD_LENGTH_SZ] == endpoint->failSendMsgId) {
1512+
return WS_CBIO_ERR_GENERAL;
1513+
}
1514+
15081515
if (endpoint->mutator != NULL &&
15091516
endpoint->mutator->enabled &&
15101517
IsTerrapinMode(endpoint->mutator->mode) &&
@@ -1741,29 +1748,34 @@ static void RunKexReplyHandshake(KexReplyHarness* harness,
17411748
}
17421749

17431750
/* The shared secret K does not outlive the key exchange. */
1744-
static void AssertKexSecretWiped(const WOLFSSH* ssh)
1751+
static int KexSecretWiped(const WOLFSSH* ssh)
17451752
{
17461753
word32 i;
17471754

1748-
AssertIntEQ(ssh->kSz, 0);
1755+
if (ssh->kSz != 0)
1756+
return 0;
17491757
for (i = 0; i < (word32)sizeof(ssh->k); i++) {
1750-
AssertIntEQ(ssh->k[i], 0);
1758+
if (ssh->k[i] != 0)
1759+
return 0;
17511760
}
1761+
return 1;
17521762
}
17531763

1754-
static void AssertHandshakeSucceeds(const char* keyAlgo, const char* keyPath)
1764+
/* A NULL kexAlgo uses the harness default. */
1765+
static void AssertHandshakeSucceedsKex(const char* kexAlgo,
1766+
const char* keyAlgo, const char* keyPath)
17551767
{
17561768
KexReplyHarness harness;
17571769
KexReplyRunResult result;
17581770

1759-
InitKexReplyHarnessEx(&harness, keyAlgo, keyPath, 0,
1771+
InitKexReplyHarnessKex(&harness, kexAlgo, keyAlgo, keyPath, 0,
17601772
REGRESS_MUTATE_SIG_NAME, NULL, 0);
17611773
RunKexReplyHandshake(&harness, &result);
17621774

17631775
AssertTrue(result.clientSuccess);
17641776
AssertTrue(result.serverSuccess);
1765-
AssertKexSecretWiped(harness.client);
1766-
AssertKexSecretWiped(harness.server);
1777+
AssertTrue(KexSecretWiped(harness.client));
1778+
AssertTrue(KexSecretWiped(harness.server));
17671779
AssertIntEQ(harness.mutator.mutatedPackets, 0);
17681780
AssertIntEQ(harness.client->connectState, CONNECT_SERVER_CHANNEL_REQUEST_DONE);
17691781
AssertIntEQ(harness.server->acceptState, ACCEPT_CLIENT_SESSION_ESTABLISHED);
@@ -1775,6 +1787,11 @@ static void AssertHandshakeSucceeds(const char* keyAlgo, const char* keyPath)
17751787
FreeKexReplyHarness(&harness);
17761788
}
17771789

1790+
static void AssertHandshakeSucceeds(const char* keyAlgo, const char* keyPath)
1791+
{
1792+
AssertHandshakeSucceedsKex(NULL, keyAlgo, keyPath);
1793+
}
1794+
17781795
static void AssertHandshakeRejectsMutatedReply(const char* keyAlgo,
17791796
const char* keyPath, const char* replaceName, int expectedErr)
17801797
{
@@ -2173,7 +2190,7 @@ static void AssertHandshakeRejectsCorruptedSig(const char* keyAlgo,
21732190
result.clientErr != WS_WANT_WRITE);
21742191
AssertIntEQ(result.clientErr, expectedErr);
21752192
/* K was agreed before the verify failed. */
2176-
AssertKexSecretWiped(harness.client);
2193+
AssertTrue(KexSecretWiped(harness.client));
21772194

21782195
FreeKexReplyHarness(&harness);
21792196
}
@@ -2202,23 +2219,35 @@ static void TestKexSecretWipedPerKex(void)
22022219
#endif
22032220
NULL
22042221
};
2205-
KexReplyHarness harness;
2206-
KexReplyRunResult result;
22072222
word32 i;
22082223

22092224
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);
2225+
printf(" KEX secret wipe with %s.\n", kexAlgos[i]);
2226+
AssertHandshakeSucceedsKex(kexAlgos[i],
2227+
REGRESS_DEFAULT_KEY_ALGO, REGRESS_DEFAULT_KEY_PATH);
2228+
}
2229+
}
22142230

2215-
AssertTrue(result.clientSuccess);
2216-
AssertTrue(result.serverSuccess);
2217-
AssertKexSecretWiped(harness.client);
2218-
AssertKexSecretWiped(harness.server);
2231+
/* The server wipes K when sending its KEXDH_REPLY fails after key
2232+
* agreement. */
2233+
static void TestKexSecretWipedOnServerSendFail(void)
2234+
{
2235+
KexReplyHarness harness;
2236+
KexReplyRunResult result;
22192237

2220-
FreeKexReplyHarness(&harness);
2221-
}
2238+
InitKexReplyHarness(&harness, REGRESS_DEFAULT_KEY_ALGO,
2239+
REGRESS_DEFAULT_KEY_PATH, 0, NULL);
2240+
harness.serverIo.failSendMsgId = MSGID_KEXDH_REPLY;
2241+
RunKexReplyHandshake(&harness, &result);
2242+
2243+
AssertFalse(result.serverSuccess);
2244+
AssertTrue(result.serverErr != WS_WANT_READ &&
2245+
result.serverErr != WS_WANT_WRITE);
2246+
/* H is hashed over K, so K was agreed. */
2247+
AssertTrue(harness.server->sessionIdSz > 0);
2248+
AssertTrue(KexSecretWiped(harness.server));
2249+
2250+
FreeKexReplyHarness(&harness);
22222251
}
22232252

22242253
#ifndef WOLFSSH_NO_RSA_SHA2_256
@@ -16839,12 +16868,26 @@ static void TestGenerateKeysSplit(void)
1683916868
AssertTrue(WMEMCMP(ssh->handshake->peerKeys.encKey,
1684016869
ssh->handshake->keys.encKey, AES_128_KEY_SIZE) != 0);
1684116870

16842-
/* A failed derivation leaves no key material behind. */
16871+
/* A failed derivation leaves no key material behind but keeps the
16872+
* negotiated sizes. */
1684316873
AssertTrue(wolfSSH_TestGenerateKeys(ssh, WC_HASH_TYPE_NONE) != WS_SUCCESS);
1684416874
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);
16875+
AssertTrue(WMEMCMP(ssh->handshake->keys.iv, zeroKeys.iv,
16876+
sizeof(zeroKeys.iv)) == 0);
16877+
AssertTrue(WMEMCMP(ssh->handshake->keys.encKey, zeroKeys.encKey,
16878+
sizeof(zeroKeys.encKey)) == 0);
16879+
AssertTrue(WMEMCMP(ssh->handshake->keys.macKey, zeroKeys.macKey,
16880+
sizeof(zeroKeys.macKey)) == 0);
16881+
AssertTrue(WMEMCMP(ssh->handshake->peerKeys.iv, zeroKeys.iv,
16882+
sizeof(zeroKeys.iv)) == 0);
16883+
AssertTrue(WMEMCMP(ssh->handshake->peerKeys.encKey, zeroKeys.encKey,
16884+
sizeof(zeroKeys.encKey)) == 0);
16885+
AssertTrue(WMEMCMP(ssh->handshake->peerKeys.macKey, zeroKeys.macKey,
16886+
sizeof(zeroKeys.macKey)) == 0);
16887+
AssertIntEQ(ssh->handshake->peerKeys.encKeySz, AES_128_KEY_SIZE);
16888+
AssertIntEQ(ssh->handshake->peerKeys.macKeySz, WC_SHA_DIGEST_SIZE);
16889+
AssertIntEQ(ssh->handshake->keys.encKeySz, AES_256_KEY_SIZE);
16890+
AssertIntEQ(ssh->handshake->keys.macKeySz, WC_SHA256_DIGEST_SIZE);
1684816891

1684916892
wolfSSH_free(ssh);
1685016893

@@ -18727,6 +18770,7 @@ int main(int argc, char** argv)
1872718770
#endif
1872818771
TestKexDhReplyRejectsSigNameOverrun();
1872918772
TestKexSecretWipedPerKex();
18773+
TestKexSecretWipedOnServerSendFail();
1873018774
#ifdef REGRESS_TRUNC_KEX_ALGO
1873118775
TestKexDhReplyTruncatedFSendsDisconnect();
1873218776
TestKexDhInitTruncatedESendsDisconnect();

0 commit comments

Comments
 (0)