Skip to content

Commit 48243de

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 c776d97 commit 48243de

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
}
@@ -2205,23 +2222,35 @@ static void TestKexSecretWipedPerKex(void)
22052222
#endif
22062223
NULL
22072224
};
2208-
KexReplyHarness harness;
2209-
KexReplyRunResult result;
22102225
word32 i;
22112226

22122227
for (i = 0; kexAlgos[i] != NULL; i++) {
2213-
InitKexReplyHarnessKex(&harness, kexAlgos[i],
2214-
REGRESS_DEFAULT_KEY_ALGO, REGRESS_DEFAULT_KEY_PATH, 0,
2215-
REGRESS_MUTATE_SIG_NAME, NULL, 0);
2216-
RunKexReplyHandshake(&harness, &result);
2228+
printf(" KEX secret wipe with %s.\n", kexAlgos[i]);
2229+
AssertHandshakeSucceedsKex(kexAlgos[i],
2230+
REGRESS_DEFAULT_KEY_ALGO, REGRESS_DEFAULT_KEY_PATH);
2231+
}
2232+
}
22172233

2218-
AssertTrue(result.clientSuccess);
2219-
AssertTrue(result.serverSuccess);
2220-
AssertKexSecretWiped(harness.client);
2221-
AssertKexSecretWiped(harness.server);
2234+
/* The server wipes K when sending its KEXDH_REPLY fails after key
2235+
* agreement. */
2236+
static void TestKexSecretWipedOnServerSendFail(void)
2237+
{
2238+
KexReplyHarness harness;
2239+
KexReplyRunResult result;
22222240

2223-
FreeKexReplyHarness(&harness);
2224-
}
2241+
InitKexReplyHarness(&harness, REGRESS_DEFAULT_KEY_ALGO,
2242+
REGRESS_DEFAULT_KEY_PATH, 0, NULL);
2243+
harness.serverIo.failSendMsgId = MSGID_KEXDH_REPLY;
2244+
RunKexReplyHandshake(&harness, &result);
2245+
2246+
AssertFalse(result.serverSuccess);
2247+
AssertTrue(result.serverErr != WS_WANT_READ &&
2248+
result.serverErr != WS_WANT_WRITE);
2249+
/* H is hashed over K, so K was agreed. */
2250+
AssertTrue(harness.server->sessionIdSz > 0);
2251+
AssertTrue(KexSecretWiped(harness.server));
2252+
2253+
FreeKexReplyHarness(&harness);
22252254
}
22262255

22272256
#ifndef WOLFSSH_NO_RSA_SHA2_256
@@ -16842,12 +16871,26 @@ static void TestGenerateKeysSplit(void)
1684216871
AssertTrue(WMEMCMP(ssh->handshake->peerKeys.encKey,
1684316872
ssh->handshake->keys.encKey, AES_128_KEY_SIZE) != 0);
1684416873

16845-
/* A failed derivation leaves no key material behind. */
16874+
/* A failed derivation leaves no key material behind but keeps the
16875+
* negotiated sizes. */
1684616876
AssertTrue(wolfSSH_TestGenerateKeys(ssh, WC_HASH_TYPE_NONE) != WS_SUCCESS);
1684716877
WMEMSET(&zeroKeys, 0, sizeof(zeroKeys));
16848-
AssertTrue(WMEMCMP(&ssh->handshake->keys, &zeroKeys, sizeof(Keys)) == 0);
16849-
AssertTrue(WMEMCMP(&ssh->handshake->peerKeys, &zeroKeys,
16850-
sizeof(Keys)) == 0);
16878+
AssertTrue(WMEMCMP(ssh->handshake->keys.iv, zeroKeys.iv,
16879+
sizeof(zeroKeys.iv)) == 0);
16880+
AssertTrue(WMEMCMP(ssh->handshake->keys.encKey, zeroKeys.encKey,
16881+
sizeof(zeroKeys.encKey)) == 0);
16882+
AssertTrue(WMEMCMP(ssh->handshake->keys.macKey, zeroKeys.macKey,
16883+
sizeof(zeroKeys.macKey)) == 0);
16884+
AssertTrue(WMEMCMP(ssh->handshake->peerKeys.iv, zeroKeys.iv,
16885+
sizeof(zeroKeys.iv)) == 0);
16886+
AssertTrue(WMEMCMP(ssh->handshake->peerKeys.encKey, zeroKeys.encKey,
16887+
sizeof(zeroKeys.encKey)) == 0);
16888+
AssertTrue(WMEMCMP(ssh->handshake->peerKeys.macKey, zeroKeys.macKey,
16889+
sizeof(zeroKeys.macKey)) == 0);
16890+
AssertIntEQ(ssh->handshake->peerKeys.encKeySz, AES_128_KEY_SIZE);
16891+
AssertIntEQ(ssh->handshake->peerKeys.macKeySz, WC_SHA_DIGEST_SIZE);
16892+
AssertIntEQ(ssh->handshake->keys.encKeySz, AES_256_KEY_SIZE);
16893+
AssertIntEQ(ssh->handshake->keys.macKeySz, WC_SHA256_DIGEST_SIZE);
1685116894

1685216895
wolfSSH_free(ssh);
1685316896

@@ -18730,6 +18773,7 @@ int main(int argc, char** argv)
1873018773
#endif
1873118774
TestKexDhReplyRejectsSigNameOverrun();
1873218775
TestKexSecretWipedPerKex();
18776+
TestKexSecretWipedOnServerSendFail();
1873318777
#ifdef REGRESS_TRUNC_KEX_ALGO
1873418778
TestKexDhReplyTruncatedFSendsDisconnect();
1873518779
TestKexDhInitTruncatedESendsDisconnect();

0 commit comments

Comments
 (0)