Skip to content

Commit 3b867e7

Browse files
yosuke-wolfsslejohnstown
authored andcommitted
Add unit tests and regress tests
1 parent fd29780 commit 3b867e7

1 file changed

Lines changed: 144 additions & 0 deletions

File tree

tests/regress.c

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,28 @@ static void TestKexDhReplyRejectsWhenCallbackRejects(void)
993993

994994
#endif /* KEXDH_REPLY_REGRESS_KEX_ALGO */
995995

996+
static word32 ParseChannelOpenFailRecipient(const byte* pkt, word32 sz)
997+
{
998+
word32 chan;
999+
/* SSH binary-packet layout: 4 (len) + 1 (pad_len) + 1 (msg_id) = 6;
1000+
* + 4 for the recipient_channel field itself gives the 10-byte minimum. */
1001+
AssertTrue(sz >= 10);
1002+
AssertIntEQ(pkt[5], MSGID_CHANNEL_OPEN_FAIL);
1003+
WMEMCPY(&chan, pkt + 6, sizeof(chan));
1004+
return ntohl(chan);
1005+
}
1006+
1007+
static word32 ParseChannelOpenFailReason(const byte* pkt, word32 sz)
1008+
{
1009+
word32 reason;
1010+
/* SSH binary-packet layout: 4 (len) + 1 (pad_len) + 1 (msg_id) + 4 (chan) = 10;
1011+
* + 4 for the reason field itself gives the 14-byte minimum. */
1012+
AssertTrue(sz >= 14);
1013+
AssertIntEQ(pkt[5], MSGID_CHANNEL_OPEN_FAIL);
1014+
WMEMCPY(&reason, pkt + 10, sizeof(reason));
1015+
return ntohl(reason);
1016+
}
1017+
9961018
static void AssertChannelOpenFailResponse(const ChannelOpenHarness* harness,
9971019
int ret)
9981020
{
@@ -1347,6 +1369,54 @@ static void TestChannelOpenCallbackRejectSendsOpenFail(void)
13471369
FreeChannelOpenHarness(&harness);
13481370
}
13491371

1372+
static void TestSecondSessionChannelRejected(void)
1373+
{
1374+
ChannelOpenHarness harness;
1375+
byte in1[128];
1376+
byte in2[128];
1377+
word32 in1Sz;
1378+
word32 in2Sz;
1379+
int ret;
1380+
1381+
in1Sz = BuildChannelOpenPacket("session", 7, 0x4000, 0x8000,
1382+
NULL, 0, in1, sizeof(in1));
1383+
in2Sz = BuildChannelOpenPacket("session", 8, 0x4000, 0x8000,
1384+
NULL, 0, in2, sizeof(in2));
1385+
1386+
InitChannelOpenHarness(&harness, in1, in1Sz);
1387+
1388+
/* First channel open must succeed */
1389+
ret = DoReceive(harness.ssh);
1390+
AssertIntEQ(ret, WS_SUCCESS);
1391+
AssertIntEQ(harness.io.inOff, harness.io.inSz);
1392+
AssertTrue(harness.io.outSz > 0);
1393+
AssertIntEQ(ParseMsgId(harness.io.out, harness.io.outSz),
1394+
MSGID_CHANNEL_OPEN_CONF);
1395+
AssertIntEQ(harness.ssh->channelListSz, 1);
1396+
1397+
/* Repoint input and rewind outSz so the second response writes from offset 0.
1398+
* io.out and outCap need no change - both still refer to harness.out[256]. */
1399+
harness.io.in = in2;
1400+
harness.io.inSz = in2Sz;
1401+
harness.io.inOff = 0;
1402+
harness.io.outSz = 0;
1403+
1404+
/* Second session channel open must be rejected */
1405+
ret = DoReceive(harness.ssh);
1406+
AssertIntEQ(ret, WS_SUCCESS);
1407+
AssertIntEQ(harness.io.inOff, harness.io.inSz);
1408+
AssertTrue(harness.io.outSz > 0);
1409+
AssertIntEQ(ParseMsgId(harness.io.out, harness.io.outSz),
1410+
MSGID_CHANNEL_OPEN_FAIL);
1411+
AssertIntEQ(ParseChannelOpenFailRecipient(harness.io.out, harness.io.outSz),
1412+
8); /* RFC 4254 5.1: server must echo the peer's channel ID */
1413+
AssertIntEQ(ParseChannelOpenFailReason(harness.io.out, harness.io.outSz),
1414+
OPEN_ADMINISTRATIVELY_PROHIBITED);
1415+
AssertIntEQ(harness.ssh->channelListSz, 1); /* original channel intact */
1416+
1417+
FreeChannelOpenHarness(&harness);
1418+
}
1419+
13501420
#ifdef WOLFSSH_FWD
13511421
static void TestDirectTcpipRejectSendsOpenFail(void)
13521422
{
@@ -3179,10 +3249,82 @@ static void TestDoNewKeys(void)
31793249
wolfSSH_free(ssh);
31803250
wolfSSH_CTX_free(ctx);
31813251
#endif /* !WOLFSSH_NO_AES_GCM */
3252+
3253+
/* Sub-test E: SELF_IS_KEYING guard - DoNewKeys must return
3254+
* WS_INVALID_STATE_E when the local side has not yet sent its own
3255+
* NEWKEYS (WOLFSSH_SELF_IS_KEYING still set). */
3256+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
3257+
AssertNotNull(ctx);
3258+
ssh = wolfSSH_new(ctx);
3259+
AssertNotNull(ssh);
3260+
AssertIntEQ(wolfSSH_SetAlgoListKex(ssh, FPF_KEX_GOOD), WS_SUCCESS);
3261+
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, FPF_KEY_GOOD), WS_SUCCESS);
3262+
AssertIntEQ(wolfSSH_SetAlgoListCipher(ssh,
3263+
"aes128-cbc,aes256-ctr"), WS_SUCCESS);
3264+
AssertIntEQ(wolfSSH_SetAlgoListMac(ssh,
3265+
"hmac-sha1,hmac-sha2-256"), WS_SUCCESS);
3266+
idx = 0;
3267+
payloadSz = BuildKexInitPayloadFull(
3268+
FPF_KEX_GOOD, FPF_KEY_GOOD,
3269+
"aes128-cbc", /* C2S enc */
3270+
"aes256-ctr", /* S2C enc */
3271+
"hmac-sha1", /* C2S MAC */
3272+
"hmac-sha2-256", /* S2C MAC */
3273+
0, payload, (word32)sizeof(payload));
3274+
(void)wolfSSH_TestDoKexInit(ssh, payload, payloadSz, &idx);
3275+
AssertNotNull(ssh->handshake);
3276+
3277+
/* peer has sent NEWKEYS but local NEWKEYS not yet sent.
3278+
* Key-material setup (k, h, sessionId, GenerateKeys) is intentionally
3279+
* absent - SELF_IS_KEYING must fire before key derivation reads those fields. */
3280+
ssh->isKeying = WOLFSSH_SELF_IS_KEYING | WOLFSSH_PEER_IS_KEYING;
3281+
AssertIntEQ(wolfSSH_TestDoNewKeys(ssh, NULL, 0, NULL), WS_INVALID_STATE_E);
3282+
3283+
/* DoNewKeys bailed before cleanup - handshake must still be allocated. */
3284+
AssertNotNull(ssh->handshake);
3285+
3286+
wolfSSH_free(ssh);
3287+
wolfSSH_CTX_free(ctx);
31823288
}
31833289

31843290
#endif /* AES_CBC + AES_CTR + HMAC guards */
31853291

3292+
/* DoKexInit's PEER_IS_KEYING guard must return WS_INVALID_STATE_E when a
3293+
* second SSH_MSG_KEXINIT arrives while a key exchange is already in progress,
3294+
* preventing HandshakeInfo corruption if the outer IsMessageAllowed filter
3295+
* were ever bypassed. */
3296+
static void TestDoKexInitRejectsWhenPeerIsKeying(void)
3297+
{
3298+
WOLFSSH_CTX* ctx;
3299+
WOLFSSH* ssh;
3300+
byte payload[512];
3301+
word32 payloadSz;
3302+
word32 idx = 0;
3303+
3304+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
3305+
AssertNotNull(ctx);
3306+
3307+
ssh = wolfSSH_new(ctx);
3308+
AssertNotNull(ssh);
3309+
3310+
AssertIntEQ(wolfSSH_SetAlgoListKex(ssh, FPF_KEX_GOOD), WS_SUCCESS);
3311+
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, FPF_KEY_GOOD), WS_SUCCESS);
3312+
3313+
payloadSz = BuildKexInitPayload(ssh, FPF_KEX_GOOD, FPF_KEY_GOOD, 0,
3314+
payload, (word32)sizeof(payload));
3315+
3316+
ssh->isKeying |= WOLFSSH_PEER_IS_KEYING;
3317+
3318+
AssertIntEQ(wolfSSH_TestDoKexInit(ssh, payload, payloadSz, &idx),
3319+
WS_INVALID_STATE_E);
3320+
/* wolfSSH_new pre-allocates handshake; DoKexInit must not free it on
3321+
* early return, so the ongoing key-exchange state is preserved. */
3322+
AssertNotNull(ssh->handshake);
3323+
3324+
wolfSSH_free(ssh);
3325+
wolfSSH_CTX_free(ctx);
3326+
}
3327+
31863328
#endif /* first_packet_follows coverage guard */
31873329

31883330

@@ -3213,6 +3355,7 @@ int main(int argc, char** argv)
32133355
TestChannelBlockedBeforeAuth(ssh);
32143356
TestChannelAllowedAfterAuth(ssh);
32153357
TestChannelOpenCallbackRejectSendsOpenFail();
3358+
TestSecondSessionChannelRejected();
32163359
#ifdef WOLFSSH_FWD
32173360
TestDirectTcpipRejectSendsOpenFail();
32183361
TestDirectTcpipNoFwdCbSendsOpenFail();
@@ -3232,6 +3375,7 @@ int main(int argc, char** argv)
32323375
&& !defined(WOLFSSH_NO_RSA_SHA2_256)
32333376
TestFirstPacketFollows();
32343377
TestKexInitReservedNonZeroRejected();
3378+
TestDoKexInitRejectsWhenPeerIsKeying();
32353379
#endif
32363380
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) && !defined(WOLFSSH_NO_RSA) \
32373381
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \

0 commit comments

Comments
 (0)