Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 123 additions & 53 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,13 +571,9 @@ static HandshakeInfo* HandshakeInfoNew(void* heap)
heap, DYNTYPE_HS);
if (newHs != NULL) {
Comment thread
yosuke-wolfssl marked this conversation as resolved.
WMEMSET(newHs, 0, sizeof(HandshakeInfo));
newHs->expectMsgId = MSGID_NONE;
newHs->kexId = ID_NONE;
newHs->kexHashId = WC_HASH_TYPE_NONE;
newHs->pubKeyId = ID_NONE;
newHs->encryptId = ID_NONE;
newHs->macId = ID_NONE;
newHs->blockSz = MIN_BLOCK_SZ;
newHs->peerBlockSz = MIN_BLOCK_SZ;
newHs->eSz = (word32)sizeof(newHs->e);
newHs->xSz = (word32)sizeof(newHs->x);
#ifndef WOLFSSH_NO_DH_GEX_SHA256
Expand Down Expand Up @@ -2659,19 +2655,17 @@ static int GenerateKeys(WOLFSSH* ssh, byte hashId, byte doKeyPad)
sK->encKey, sK->encKeySz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz, doKeyPad);
if (ret == WS_SUCCESS) {
if (!ssh->handshake->aeadMode) {
ret = GenerateKey(hashId, 'E',
cK->macKey, cK->macKeySz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz, doKeyPad);
if (ret == WS_SUCCESS) {
ret = GenerateKey(hashId, 'F',
sK->macKey, sK->macKeySz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz, doKeyPad);
}
}
if (ret == WS_SUCCESS && cK->macKeySz > 0) {
ret = GenerateKey(hashId, 'E',
cK->macKey, cK->macKeySz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz, doKeyPad);
}
if (ret == WS_SUCCESS && sK->macKeySz > 0) {
ret = GenerateKey(hashId, 'F',
sK->macKey, sK->macKeySz,
ssh->k, ssh->kSz, ssh->h, ssh->hSz,
ssh->sessionId, ssh->sessionIdSz, doKeyPad);
}

#ifdef SHOW_SECRETS
Expand Down Expand Up @@ -4250,6 +4244,18 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
word32 cannedAlgoNamesSz;
word32 skipSz = 0;
word32 begin;
/* handshake->keys/encryptId/... always represent the LOCAL endpoint's
* outgoing direction; peer* counterparts represent the peer's outgoing
* (= our incoming) direction. Server: local=S2C, peer=C2S.
* Client: local=C2S, peer=S2C. These aliases let the four enc/MAC
* parse sections store results without inline side checks, and keep the
* fields consistent with what GenerateKeys/SendNewKeys/DoNewKeys expect. */
byte *c2sEncryptId = NULL, *c2sAeadMode = NULL, *c2sBlockSz = NULL,
*c2sMacId = NULL, *c2sMacSz = NULL;
Keys *c2sKeys = NULL;
byte *s2cEncryptId = NULL, *s2cAeadMode = NULL, *s2cBlockSz = NULL,
*s2cMacId = NULL, *s2cMacSz = NULL;
Keys *s2cKeys = NULL;

WLOG(WS_LOG_DEBUG, "Entering DoKexInit()");

Expand Down Expand Up @@ -4292,6 +4298,35 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
begin = *idx;
side = ssh->ctx->side;

if (side == WOLFSSH_ENDPOINT_SERVER) {
c2sEncryptId = &ssh->handshake->peerEncryptId;
c2sAeadMode = &ssh->handshake->peerAeadMode;
c2sBlockSz = &ssh->handshake->peerBlockSz;
c2sMacId = &ssh->handshake->peerMacId;
c2sMacSz = &ssh->handshake->peerMacSz;
c2sKeys = &ssh->handshake->peerKeys;
s2cEncryptId = &ssh->handshake->encryptId;
s2cAeadMode = &ssh->handshake->aeadMode;
s2cBlockSz = &ssh->handshake->blockSz;
s2cMacId = &ssh->handshake->macId;
s2cMacSz = &ssh->handshake->macSz;
s2cKeys = &ssh->handshake->keys;
}
else {
Comment thread
yosuke-wolfssl marked this conversation as resolved.
c2sEncryptId = &ssh->handshake->encryptId;
c2sAeadMode = &ssh->handshake->aeadMode;
c2sBlockSz = &ssh->handshake->blockSz;
c2sMacId = &ssh->handshake->macId;
c2sMacSz = &ssh->handshake->macSz;
c2sKeys = &ssh->handshake->keys;
s2cEncryptId = &ssh->handshake->peerEncryptId;
s2cAeadMode = &ssh->handshake->peerAeadMode;
s2cBlockSz = &ssh->handshake->peerBlockSz;
s2cMacId = &ssh->handshake->peerMacId;
s2cMacSz = &ssh->handshake->peerMacSz;
s2cKeys = &ssh->handshake->peerKeys;
}

/* Check that the cookie exists inside the message */
if (begin + COOKIE_SZ > len) {
/* error, out of bounds */
Expand Down Expand Up @@ -4391,6 +4426,24 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
ret = WS_MATCH_ENC_ALGO_E;
}
}
if (ret == WS_SUCCESS) {
Comment thread
yosuke-wolfssl marked this conversation as resolved.
Comment thread
yosuke-wolfssl marked this conversation as resolved.
*c2sEncryptId = algoId;
*c2sAeadMode = AeadModeForId(algoId);
*c2sBlockSz = BlockSzForId(algoId);
c2sKeys->encKeySz = KeySzForId(algoId);
if (!*c2sAeadMode) {
c2sKeys->ivSz = *c2sBlockSz;
}
else {
/* Reaching here requires an AEAD cipher ID, which requires
* WOLFSSH_NO_AES_GCM to be unset, hence WOLFSSH_NO_AEAD unset
* (see internal.h). */
c2sKeys->ivSz = AEAD_NONCE_SZ;
*c2sMacSz = *c2sBlockSz;
*c2sMacId = ID_NONE;
c2sKeys->macKeySz = 0;
}
Comment thread
yosuke-wolfssl marked this conversation as resolved.
}
Comment thread
yosuke-wolfssl marked this conversation as resolved.

/* Enc Algorithms - Server to Client */
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [Medium] Asymmetric S2C-only encryption-algo mismatch error path is untested · path

Pre-PR, the S2C encryption algorithm was matched against the C2S-chosen algorithm only (MatchIdLists(side, list, listSz, &algoId, 1)). Post-PR, S2C is matched independently against ssh->algoListCipher via cannedList. This introduces a NEW failure mode: C2S successfully matches but S2C does not (or vice versa) — yielding WS_MATCH_ENC_ALGO_E from the S2C block. The new tests in TestIndependentAlgoNegotiation/...Client only cover the success paths for asymmetric cipher selection; no test exercises asymmetric mismatch where one direction agrees and the other fails. Grep confirms WS_MATCH_ENC_ALGO_E has zero references in tests/.

Fix: Add a regress.c test that builds a kex_init payload where the C2S cipher list contains an algorithm in the local algoListCipher but the S2C cipher list does not (e.g., local accepts aes128-cbc,aes256-ctr; payload offers C2S=aes128-cbc, S2C=3des-cbc). Verify wolfSSH_TestDoKexInit returns WS_MATCH_ENC_ALGO_E. Mirror the test for the inverse case (C2S fails, S2C succeeds).

if (ret == WS_SUCCESS) {
Expand All @@ -4399,31 +4452,32 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
ret = GetNameList(list, &listSz, buf, len, &begin);
}
if (ret == WS_SUCCESS) {
algoId = MatchIdLists(side, list, listSz, &algoId, 1);
cannedAlgoNamesSz = AlgoListSz(ssh->algoListCipher);
cannedListSz = (word32)sizeof(cannedList);
ret = GetNameListRaw(cannedList, &cannedListSz,
(const byte*)ssh->algoListCipher, cannedAlgoNamesSz);
}
if (ret == WS_SUCCESS) {
algoId = MatchIdLists(side, list, listSz, cannedList, cannedListSz);
if (algoId == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate Encryption Algo S2C");
ret = WS_MATCH_ENC_ALGO_E;
}
}
if (ret == WS_SUCCESS) {
ssh->handshake->encryptId = algoId;
ssh->handshake->aeadMode = AeadModeForId(algoId);
ssh->handshake->blockSz = BlockSzForId(algoId);
ssh->handshake->keys.encKeySz =
ssh->handshake->peerKeys.encKeySz =
KeySzForId(algoId);
if (!ssh->handshake->aeadMode) {
ssh->handshake->keys.ivSz =
ssh->handshake->peerKeys.ivSz =
ssh->handshake->blockSz;
*s2cEncryptId = algoId;
*s2cAeadMode = AeadModeForId(algoId);
*s2cBlockSz = BlockSzForId(algoId);
s2cKeys->encKeySz = KeySzForId(algoId);
if (!*s2cAeadMode) {
s2cKeys->ivSz = *s2cBlockSz;
}
else {
#ifndef WOLFSSH_NO_AEAD
ssh->handshake->keys.ivSz =
ssh->handshake->peerKeys.ivSz =
AEAD_NONCE_SZ;
ssh->handshake->macSz = ssh->handshake->blockSz;
#endif
/* Same invariant as C2S: AEAD cipher ID implies !WOLFSSH_NO_AEAD. */
s2cKeys->ivSz = AEAD_NONCE_SZ;
*s2cMacSz = *s2cBlockSz;
*s2cMacId = ID_NONE;
s2cKeys->macKeySz = 0;
}
}

Expand All @@ -4433,7 +4487,7 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
listSz = (word32)sizeof(list);
ret = GetNameList(list, &listSz, buf, len, &begin);
}
if (ret == WS_SUCCESS && !ssh->handshake->aeadMode) {
if (ret == WS_SUCCESS && !*c2sAeadMode) {
cannedAlgoNamesSz = AlgoListSz(ssh->algoListMac);
cannedListSz = (word32)sizeof(cannedList);
ret = GetNameListRaw(cannedList, &cannedListSz,
Expand All @@ -4445,6 +4499,11 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo C2S");
ret = WS_MATCH_MAC_ALGO_E;
}
else {
*c2sMacId = algoId;
*c2sMacSz = MacSzForId(algoId);
c2sKeys->macKeySz = KeySzForId(algoId);
}
}
}

Expand All @@ -4454,18 +4513,22 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
listSz = (word32)sizeof(list);
ret = GetNameList(list, &listSz, buf, len, &begin);
}
if (ret == WS_SUCCESS && !ssh->handshake->aeadMode) {
algoId = MatchIdLists(side, list, listSz, &algoId, 1);
if (algoId == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo S2C");
ret = WS_MATCH_MAC_ALGO_E;
}
else {
ssh->handshake->macId = algoId;
ssh->handshake->macSz = MacSzForId(algoId);
ssh->handshake->keys.macKeySz =
ssh->handshake->peerKeys.macKeySz =
KeySzForId(algoId);
if (ret == WS_SUCCESS && !*s2cAeadMode) {
cannedAlgoNamesSz = AlgoListSz(ssh->algoListMac);
cannedListSz = (word32)sizeof(cannedList);
ret = GetNameListRaw(cannedList, &cannedListSz,
(const byte*)ssh->algoListMac, cannedAlgoNamesSz);
if (ret == WS_SUCCESS) {
algoId = MatchIdLists(side, list, listSz, cannedList, cannedListSz);
if (algoId == ID_UNKNOWN) {
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo S2C");
ret = WS_MATCH_MAC_ALGO_E;
}
else {
*s2cMacId = algoId;
*s2cMacSz = MacSzForId(algoId);
s2cKeys->macKeySz = KeySzForId(algoId);
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [Medium] Asymmetric S2C-only MAC-algo mismatch error path is untested · path

The S2C MAC negotiation was rewritten from MatchIdLists(side, list, listSz, &algoId, 1) (which forced S2C to equal C2S) to an independent match against ssh->algoListMac. The new failure path WS_MATCH_MAC_ALGO_E can now trigger when C2S MAC matches but S2C MAC does not (and vice versa for the C2S branch at lines 4484-4508). Neither branch's WS_MATCH_MAC_ALGO_E return is exercised by any test. Grep across the repo confirms no test references WS_MATCH_MAC_ALGO_E.

Fix: Add a regress.c test that supplies asymmetric MAC lists where one direction shares no entries with ssh->algoListMac (e.g., local accepts hmac-sha1,hmac-sha2-256; payload offers C2S=hmac-sha1, S2C=hmac-md5). Assert wolfSSH_TestDoKexInit returns WS_MATCH_MAC_ALGO_E. Cover the inverse C2S-mismatch case as well.


Expand Down Expand Up @@ -6207,11 +6270,11 @@ static int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
}

if (ret == WS_SUCCESS) {
ssh->peerEncryptId = ssh->handshake->encryptId;
ssh->peerMacId = ssh->handshake->macId;
ssh->peerBlockSz = ssh->handshake->blockSz;
ssh->peerMacSz = ssh->handshake->macSz;
ssh->peerAeadMode = ssh->handshake->aeadMode;
ssh->peerEncryptId = ssh->handshake->peerEncryptId;
ssh->peerMacId = ssh->handshake->peerMacId;
ssh->peerBlockSz = ssh->handshake->peerBlockSz;
ssh->peerMacSz = ssh->handshake->peerMacSz;
ssh->peerAeadMode = ssh->handshake->peerAeadMode;
WMEMCPY(&ssh->peerKeys, &ssh->handshake->peerKeys, sizeof(Keys));

switch (ssh->peerEncryptId) {
Expand Down Expand Up @@ -17896,6 +17959,13 @@ int wolfSSH_TestDoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
return DoKexInit(ssh, buf, len, idx);
}

int wolfSSH_TestGenerateKeys(WOLFSSH* ssh)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [Low] wolfSSH_TestGenerateKeys NULL-argument validation is untested · test

The newly added test helper validates ssh == NULL || ssh->handshake == NULL and returns WS_BAD_ARGUMENT, but no test in tests/regress.c (or anywhere else) calls it with NULL inputs to verify the guard. All four new test sites (TestGenerateKeysSplit, TestGenerateKeysSplitClient sub-tests A and B) invoke it only after AssertNotNull(ssh->handshake). While the function is test-only infrastructure, a sibling invariant check is cheap to add.

Fix: Add a one-line assertion in regress.c (e.g., inside or adjacent to TestGenerateKeysSplit): AssertIntEQ(wolfSSH_TestGenerateKeys(NULL), WS_BAD_ARGUMENT); and a second call with a freshly-created WOLFSSH* whose handshake has been freed/nulled to verify the second branch.

{
if (ssh == NULL || ssh->handshake == NULL)
return WS_BAD_ARGUMENT;
return GenerateKeys(ssh, (enum wc_HashType)ssh->handshake->kexHashId, 1);
}

int wolfSSH_TestDoKexDhInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
{
return DoKexDhInit(ssh, buf, len, idx);
Expand Down
Loading
Loading