Skip to content

Commit 47c9e57

Browse files
Add independent ciper and MAC algorithms negotiation for each direction, And add regress test
1 parent fdf621c commit 47c9e57

3 files changed

Lines changed: 171 additions & 29 deletions

File tree

src/internal.c

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,9 @@ static HandshakeInfo* HandshakeInfoNew(void* heap)
578578
newHs->encryptId = ID_NONE;
579579
newHs->macId = ID_NONE;
580580
newHs->blockSz = MIN_BLOCK_SZ;
581+
newHs->peerEncryptId = ID_NONE;
582+
newHs->peerMacId = ID_NONE;
583+
newHs->peerBlockSz = MIN_BLOCK_SZ;
581584
newHs->eSz = (word32)sizeof(newHs->e);
582585
newHs->xSz = (word32)sizeof(newHs->x);
583586
#ifndef WOLFSSH_NO_DH_GEX_SHA256
@@ -4422,6 +4425,22 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
44224425
ret = WS_MATCH_ENC_ALGO_E;
44234426
}
44244427
}
4428+
if (ret == WS_SUCCESS) {
4429+
ssh->handshake->peerEncryptId = algoId;
4430+
ssh->handshake->peerAeadMode = AeadModeForId(algoId);
4431+
ssh->handshake->peerBlockSz = BlockSzForId(algoId);
4432+
ssh->handshake->peerKeys.encKeySz = KeySzForId(algoId);
4433+
if (!ssh->handshake->peerAeadMode) {
4434+
ssh->handshake->peerKeys.ivSz = ssh->handshake->peerBlockSz;
4435+
}
4436+
else {
4437+
/* Reaching here requires peerAeadMode==1, which requires an AEAD
4438+
* cipher ID, which requires WOLFSSH_NO_AES_GCM to be unset, which
4439+
* means WOLFSSH_NO_AEAD is also unset (see internal.h). */
4440+
ssh->handshake->peerKeys.ivSz = AEAD_NONCE_SZ;
4441+
ssh->handshake->peerMacSz = ssh->handshake->peerBlockSz;
4442+
}
4443+
}
44254444

44264445
/* Enc Algorithms - Server to Client */
44274446
if (ret == WS_SUCCESS) {
@@ -4430,7 +4449,13 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
44304449
ret = GetNameList(list, &listSz, buf, len, &begin);
44314450
}
44324451
if (ret == WS_SUCCESS) {
4433-
algoId = MatchIdLists(side, list, listSz, &algoId, 1);
4452+
cannedAlgoNamesSz = AlgoListSz(ssh->algoListCipher);
4453+
cannedListSz = (word32)sizeof(cannedList);
4454+
ret = GetNameListRaw(cannedList, &cannedListSz,
4455+
(const byte*)ssh->algoListCipher, cannedAlgoNamesSz);
4456+
}
4457+
if (ret == WS_SUCCESS) {
4458+
algoId = MatchIdLists(side, list, listSz, cannedList, cannedListSz);
44344459
if (algoId == ID_UNKNOWN) {
44354460
WLOG(WS_LOG_DEBUG, "Unable to negotiate Encryption Algo S2C");
44364461
ret = WS_MATCH_ENC_ALGO_E;
@@ -4440,21 +4465,14 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
44404465
ssh->handshake->encryptId = algoId;
44414466
ssh->handshake->aeadMode = AeadModeForId(algoId);
44424467
ssh->handshake->blockSz = BlockSzForId(algoId);
4443-
ssh->handshake->keys.encKeySz =
4444-
ssh->handshake->peerKeys.encKeySz =
4445-
KeySzForId(algoId);
4468+
ssh->handshake->keys.encKeySz = KeySzForId(algoId);
44464469
if (!ssh->handshake->aeadMode) {
4447-
ssh->handshake->keys.ivSz =
4448-
ssh->handshake->peerKeys.ivSz =
4449-
ssh->handshake->blockSz;
4470+
ssh->handshake->keys.ivSz = ssh->handshake->blockSz;
44504471
}
44514472
else {
4452-
#ifndef WOLFSSH_NO_AEAD
4453-
ssh->handshake->keys.ivSz =
4454-
ssh->handshake->peerKeys.ivSz =
4455-
AEAD_NONCE_SZ;
4473+
/* Same invariant: aeadMode==1 implies !WOLFSSH_NO_AEAD. */
4474+
ssh->handshake->keys.ivSz = AEAD_NONCE_SZ;
44564475
ssh->handshake->macSz = ssh->handshake->blockSz;
4457-
#endif
44584476
}
44594477
}
44604478

@@ -4464,7 +4482,7 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
44644482
listSz = (word32)sizeof(list);
44654483
ret = GetNameList(list, &listSz, buf, len, &begin);
44664484
}
4467-
if (ret == WS_SUCCESS && !ssh->handshake->aeadMode) {
4485+
if (ret == WS_SUCCESS && !ssh->handshake->peerAeadMode) {
44684486
cannedAlgoNamesSz = AlgoListSz(ssh->algoListMac);
44694487
cannedListSz = (word32)sizeof(cannedList);
44704488
ret = GetNameListRaw(cannedList, &cannedListSz,
@@ -4476,6 +4494,11 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
44764494
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo C2S");
44774495
ret = WS_MATCH_MAC_ALGO_E;
44784496
}
4497+
else {
4498+
ssh->handshake->peerMacId = algoId;
4499+
ssh->handshake->peerMacSz = MacSzForId(algoId);
4500+
ssh->handshake->peerKeys.macKeySz = KeySzForId(algoId);
4501+
}
44794502
}
44804503
}
44814504

@@ -4486,17 +4509,21 @@ static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
44864509
ret = GetNameList(list, &listSz, buf, len, &begin);
44874510
}
44884511
if (ret == WS_SUCCESS && !ssh->handshake->aeadMode) {
4489-
algoId = MatchIdLists(side, list, listSz, &algoId, 1);
4490-
if (algoId == ID_UNKNOWN) {
4491-
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo S2C");
4492-
ret = WS_MATCH_MAC_ALGO_E;
4493-
}
4494-
else {
4495-
ssh->handshake->macId = algoId;
4496-
ssh->handshake->macSz = MacSzForId(algoId);
4497-
ssh->handshake->keys.macKeySz =
4498-
ssh->handshake->peerKeys.macKeySz =
4499-
KeySzForId(algoId);
4512+
cannedAlgoNamesSz = AlgoListSz(ssh->algoListMac);
4513+
cannedListSz = (word32)sizeof(cannedList);
4514+
ret = GetNameListRaw(cannedList, &cannedListSz,
4515+
(const byte*)ssh->algoListMac, cannedAlgoNamesSz);
4516+
if (ret == WS_SUCCESS) {
4517+
algoId = MatchIdLists(side, list, listSz, cannedList, cannedListSz);
4518+
if (algoId == ID_UNKNOWN) {
4519+
WLOG(WS_LOG_DEBUG, "Unable to negotiate MAC Algo S2C");
4520+
ret = WS_MATCH_MAC_ALGO_E;
4521+
}
4522+
else {
4523+
ssh->handshake->macId = algoId;
4524+
ssh->handshake->macSz = MacSzForId(algoId);
4525+
ssh->handshake->keys.macKeySz = KeySzForId(algoId);
4526+
}
45004527
}
45014528
}
45024529

@@ -6238,11 +6265,11 @@ static int DoNewKeys(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
62386265
}
62396266

62406267
if (ret == WS_SUCCESS) {
6241-
ssh->peerEncryptId = ssh->handshake->encryptId;
6242-
ssh->peerMacId = ssh->handshake->macId;
6243-
ssh->peerBlockSz = ssh->handshake->blockSz;
6244-
ssh->peerMacSz = ssh->handshake->macSz;
6245-
ssh->peerAeadMode = ssh->handshake->aeadMode;
6268+
ssh->peerEncryptId = ssh->handshake->peerEncryptId;
6269+
ssh->peerMacId = ssh->handshake->peerMacId;
6270+
ssh->peerBlockSz = ssh->handshake->peerBlockSz;
6271+
ssh->peerMacSz = ssh->handshake->peerMacSz;
6272+
ssh->peerAeadMode = ssh->handshake->peerAeadMode;
62466273
WMEMCPY(&ssh->peerKeys, &ssh->handshake->peerKeys, sizeof(Keys));
62476274

62486275
switch (ssh->peerEncryptId) {

tests/regress.c

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,36 @@ static word32 BuildKexInitPayload(WOLFSSH* ssh, const char* kexList,
19891989
return idx;
19901990
}
19911991

1992+
#if !defined(WOLFSSH_NO_AES_CBC) && !defined(WOLFSSH_NO_AES_CTR) \
1993+
&& !defined(WOLFSSH_NO_HMAC_SHA1) && !defined(WOLFSSH_NO_HMAC_SHA2_256)
1994+
/* Like BuildKexInitPayload but with explicit per-direction cipher/MAC lists. */
1995+
static word32 BuildKexInitPayloadFull(const char* kexList,
1996+
const char* keyList, const char* encC2S, const char* encS2C,
1997+
const char* macC2S, const char* macS2C,
1998+
byte firstPacketFollows, byte* out, word32 outSz)
1999+
{
2000+
word32 idx = 0;
2001+
2002+
AssertTrue(idx + COOKIE_SZ <= outSz);
2003+
WMEMSET(out + idx, 0, COOKIE_SZ);
2004+
idx += COOKIE_SZ;
2005+
idx = AppendString(out, outSz, idx, kexList);
2006+
idx = AppendString(out, outSz, idx, keyList);
2007+
idx = AppendString(out, outSz, idx, encC2S);
2008+
idx = AppendString(out, outSz, idx, encS2C);
2009+
idx = AppendString(out, outSz, idx, macC2S);
2010+
idx = AppendString(out, outSz, idx, macS2C);
2011+
idx = AppendString(out, outSz, idx, "none");
2012+
idx = AppendString(out, outSz, idx, "none");
2013+
idx = AppendString(out, outSz, idx, "");
2014+
idx = AppendString(out, outSz, idx, "");
2015+
idx = AppendByte(out, outSz, idx, firstPacketFollows);
2016+
idx = AppendUint32(out, outSz, idx, 0); /* reserved */
2017+
2018+
return idx;
2019+
}
2020+
#endif /* AES_CBC + AES_CTR + HMAC guards (BuildKexInitPayloadFull) */
2021+
19922022
typedef struct {
19932023
const char* description;
19942024
const char* kexList;
@@ -2107,6 +2137,79 @@ static void TestFirstPacketFollows(void)
21072137
TestFirstPacketFollowsSkipped();
21082138
}
21092139

2140+
#if !defined(WOLFSSH_NO_AES_CBC) && !defined(WOLFSSH_NO_AES_CTR) \
2141+
&& !defined(WOLFSSH_NO_HMAC_SHA1) && !defined(WOLFSSH_NO_HMAC_SHA2_256)
2142+
static void TestIndependentAlgoNegotiation(void)
2143+
{
2144+
WOLFSSH_CTX* ctx;
2145+
WOLFSSH* ssh;
2146+
byte payload[512];
2147+
word32 payloadSz;
2148+
word32 idx;
2149+
2150+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
2151+
AssertNotNull(ctx);
2152+
2153+
/* Sub-test A: different non-AEAD cipher and MAC per direction */
2154+
ssh = wolfSSH_new(ctx);
2155+
AssertNotNull(ssh);
2156+
AssertIntEQ(wolfSSH_SetAlgoListKex(ssh, FPF_KEX_GOOD), WS_SUCCESS);
2157+
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, FPF_KEY_GOOD), WS_SUCCESS);
2158+
AssertIntEQ(wolfSSH_SetAlgoListCipher(ssh,
2159+
"aes128-cbc,aes256-ctr"), WS_SUCCESS);
2160+
AssertIntEQ(wolfSSH_SetAlgoListMac(ssh,
2161+
"hmac-sha1,hmac-sha2-256"), WS_SUCCESS);
2162+
idx = 0;
2163+
payloadSz = BuildKexInitPayloadFull(
2164+
FPF_KEX_GOOD, FPF_KEY_GOOD,
2165+
"aes128-cbc", /* C2S enc */
2166+
"aes256-ctr", /* S2C enc */
2167+
"hmac-sha1", /* C2S MAC */
2168+
"hmac-sha2-256", /* S2C MAC */
2169+
0, payload, (word32)sizeof(payload));
2170+
(void)wolfSSH_TestDoKexInit(ssh, payload, payloadSz, &idx);
2171+
AssertNotNull(ssh->handshake);
2172+
AssertIntEQ(ssh->handshake->peerEncryptId, ID_AES128_CBC);
2173+
AssertIntEQ(ssh->handshake->encryptId, ID_AES256_CTR);
2174+
AssertIntEQ(ssh->handshake->peerMacId, ID_HMAC_SHA1);
2175+
AssertIntEQ(ssh->handshake->macId, ID_HMAC_SHA2_256);
2176+
AssertIntEQ(ssh->handshake->peerAeadMode, 0);
2177+
AssertIntEQ(ssh->handshake->aeadMode, 0);
2178+
wolfSSH_free(ssh);
2179+
2180+
#ifndef WOLFSSH_NO_AES_GCM
2181+
/* Sub-test B: AEAD S2C, non-AEAD C2S — MAC only negotiated for C2S */
2182+
ssh = wolfSSH_new(ctx);
2183+
AssertNotNull(ssh);
2184+
AssertIntEQ(wolfSSH_SetAlgoListKex(ssh, FPF_KEX_GOOD), WS_SUCCESS);
2185+
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, FPF_KEY_GOOD), WS_SUCCESS);
2186+
AssertIntEQ(wolfSSH_SetAlgoListCipher(ssh,
2187+
"aes128-cbc,aes256-gcm@openssh.com"), WS_SUCCESS);
2188+
AssertIntEQ(wolfSSH_SetAlgoListMac(ssh,
2189+
"hmac-sha1,hmac-sha2-256"), WS_SUCCESS);
2190+
idx = 0;
2191+
payloadSz = BuildKexInitPayloadFull(
2192+
FPF_KEX_GOOD, FPF_KEY_GOOD,
2193+
"aes128-cbc", /* C2S enc: non-AEAD */
2194+
"aes256-gcm@openssh.com", /* S2C enc: AEAD */
2195+
"hmac-sha1", /* C2S MAC: negotiated */
2196+
"hmac-sha2-256", /* S2C MAC: skipped (aeadMode) */
2197+
0, payload, (word32)sizeof(payload));
2198+
(void)wolfSSH_TestDoKexInit(ssh, payload, payloadSz, &idx);
2199+
AssertNotNull(ssh->handshake);
2200+
AssertIntEQ(ssh->handshake->peerEncryptId, ID_AES128_CBC);
2201+
AssertIntEQ(ssh->handshake->encryptId, ID_AES256_GCM);
2202+
AssertIntEQ(ssh->handshake->peerAeadMode, 0);
2203+
AssertIntEQ(ssh->handshake->aeadMode, 1);
2204+
AssertIntEQ(ssh->handshake->peerMacId, ID_HMAC_SHA1);
2205+
AssertIntEQ(ssh->handshake->macId, ID_NONE);
2206+
wolfSSH_free(ssh);
2207+
#endif /* !WOLFSSH_NO_AES_GCM */
2208+
2209+
wolfSSH_CTX_free(ctx);
2210+
}
2211+
#endif /* AES_CBC + AES_CTR + HMAC guards */
2212+
21102213
#endif /* first_packet_follows coverage guard */
21112214

21122215

@@ -2155,6 +2258,13 @@ int main(int argc, char** argv)
21552258
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \
21562259
&& !defined(WOLFSSH_NO_RSA_SHA2_256)
21572260
TestFirstPacketFollows();
2261+
#endif
2262+
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) && !defined(WOLFSSH_NO_RSA) \
2263+
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \
2264+
&& !defined(WOLFSSH_NO_RSA_SHA2_256) \
2265+
&& !defined(WOLFSSH_NO_AES_CBC) && !defined(WOLFSSH_NO_AES_CTR) \
2266+
&& !defined(WOLFSSH_NO_HMAC_SHA1) && !defined(WOLFSSH_NO_HMAC_SHA2_256)
2267+
TestIndependentAlgoNegotiation();
21582268
#endif
21592269
TestDisconnectSetsDisconnectError();
21602270
TestClientBuffersIdempotent();

wolfssh/internal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,9 +636,14 @@ typedef struct HandshakeInfo {
636636
byte encryptId;
637637
byte macId;
638638
byte aeadMode;
639+
byte peerEncryptId;
640+
byte peerMacId;
641+
byte peerAeadMode;
639642

640643
byte blockSz;
641644
byte macSz;
645+
byte peerBlockSz;
646+
byte peerMacSz;
642647

643648
Keys keys;
644649
Keys peerKeys;

0 commit comments

Comments
 (0)