Skip to content

Commit 232a7a8

Browse files
committed
First Kex Packet Follows Test
Add a regression for checking the `first_kex_packet_follows` flag versus the guesses for KEX algorithm and public key algorithm.
1 parent 98db525 commit 232a7a8

3 files changed

Lines changed: 165 additions & 0 deletions

File tree

src/internal.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,19 @@ int wolfSSH_TestIsMessageAllowed(WOLFSSH* ssh, byte msg, byte state)
854854
{
855855
return IsMessageAllowed(ssh, msg, state);
856856
}
857+
858+
static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx);
859+
static int DoKexDhInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx);
860+
861+
int wolfSSH_TestDoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
862+
{
863+
return DoKexInit(ssh, buf, len, idx);
864+
}
865+
866+
int wolfSSH_TestDoKexDhInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
867+
{
868+
return DoKexDhInit(ssh, buf, len, idx);
869+
}
857870
#endif
858871

859872

tests/regress.c

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,6 +1619,149 @@ static void TestKeyboardResponseNullCtx(WOLFSSH* ssh)
16191619
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
16201620

16211621

1622+
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) \
1623+
&& !defined(WOLFSSH_NO_RSA) \
1624+
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \
1625+
&& !defined(WOLFSSH_NO_RSA_SHA2_256)
1626+
1627+
#define FPF_KEX_GOOD "ecdh-sha2-nistp256"
1628+
#define FPF_KEX_BAD "curve25519-sha256"
1629+
#define FPF_KEY_GOOD "ssh-rsa"
1630+
#define FPF_KEY_BAD "rsa-sha2-256"
1631+
1632+
static word32 BuildKexInitPayload(const char* kexList, const char* keyList,
1633+
byte firstPacketFollows, byte* out, word32 outSz)
1634+
{
1635+
word32 idx = 0;
1636+
1637+
/* cookie */
1638+
AssertTrue(idx + COOKIE_SZ <= outSz);
1639+
WMEMSET(out + idx, 0, COOKIE_SZ);
1640+
idx += COOKIE_SZ;
1641+
1642+
idx = AppendString(out, outSz, idx, kexList);
1643+
idx = AppendString(out, outSz, idx, keyList);
1644+
idx = AppendString(out, outSz, idx, "aes128-ctr");
1645+
idx = AppendString(out, outSz, idx, "aes128-ctr");
1646+
idx = AppendString(out, outSz, idx, "hmac-sha2-256");
1647+
idx = AppendString(out, outSz, idx, "hmac-sha2-256");
1648+
idx = AppendString(out, outSz, idx, "none");
1649+
idx = AppendString(out, outSz, idx, "none");
1650+
idx = AppendString(out, outSz, idx, "");
1651+
idx = AppendString(out, outSz, idx, "");
1652+
1653+
idx = AppendByte(out, outSz, idx, firstPacketFollows);
1654+
idx = AppendUint32(out, outSz, idx, 0); /* reserved */
1655+
1656+
return idx;
1657+
}
1658+
1659+
typedef struct {
1660+
const char* description;
1661+
const char* kexList;
1662+
const char* keyList;
1663+
byte firstPacketFollows;
1664+
byte expectIgnore;
1665+
} FirstPacketFollowsCase;
1666+
1667+
static const FirstPacketFollowsCase firstPacketFollowsCases[] = {
1668+
{ "follows=0, guesses irrelevant: flag stays off",
1669+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 0, 0 },
1670+
{ "follows=1, both guesses match: do not skip",
1671+
FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 0 },
1672+
{ "follows=1, KEX guess wrong: skip",
1673+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 1 },
1674+
{ "follows=1, host-key guess wrong: skip", /* regression case */
1675+
FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1 },
1676+
{ "follows=1, both guesses wrong: skip",
1677+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1 },
1678+
};
1679+
1680+
static void RunFirstPacketFollowsCase(const FirstPacketFollowsCase* tc)
1681+
{
1682+
WOLFSSH_CTX* ctx;
1683+
WOLFSSH* ssh;
1684+
byte payload[512];
1685+
word32 payloadSz;
1686+
word32 idx = 0;
1687+
1688+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
1689+
AssertNotNull(ctx);
1690+
1691+
ssh = wolfSSH_new(ctx);
1692+
AssertNotNull(ssh);
1693+
1694+
AssertIntEQ(wolfSSH_SetAlgoListKex(ssh, FPF_KEX_GOOD), WS_SUCCESS);
1695+
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, FPF_KEY_GOOD), WS_SUCCESS);
1696+
1697+
payloadSz = BuildKexInitPayload(tc->kexList, tc->keyList,
1698+
tc->firstPacketFollows, payload, sizeof(payload));
1699+
1700+
/* DoKexInit's tail hashes and sends a response; on a stripped-down
1701+
* WOLFSSH without a loaded host key or a primed peer proto id, that
1702+
* tail errors. We only care about the parse path up through
1703+
* first_packet_follows, where ignoreNextKexMsg is set. */
1704+
(void)wolfSSH_TestDoKexInit(ssh, payload, payloadSz, &idx);
1705+
1706+
AssertNotNull(ssh->handshake);
1707+
if (ssh->handshake->ignoreNextKexMsg != tc->expectIgnore) {
1708+
Fail(("ignoreNextKexMsg == %u (%s)",
1709+
tc->expectIgnore, tc->description),
1710+
("%u", ssh->handshake->ignoreNextKexMsg));
1711+
}
1712+
1713+
wolfSSH_free(ssh);
1714+
wolfSSH_CTX_free(ctx);
1715+
}
1716+
1717+
/* With ignoreNextKexMsg set, DoKexDhInit must consume the packet, clear the
1718+
* flag, and not advance clientState to CLIENT_KEXDH_INIT_DONE. */
1719+
static void TestFirstPacketFollowsDhInitSkipped(void)
1720+
{
1721+
WOLFSSH_CTX* ctx;
1722+
WOLFSSH* ssh;
1723+
byte dhPayload[8];
1724+
word32 idx = 0;
1725+
int ret;
1726+
1727+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
1728+
AssertNotNull(ctx);
1729+
1730+
ssh = wolfSSH_new(ctx);
1731+
AssertNotNull(ssh);
1732+
1733+
ssh->handshake = AllocHandshake(ssh);
1734+
ssh->handshake->ignoreNextKexMsg = 1;
1735+
ssh->clientState = CLIENT_KEXINIT_DONE;
1736+
1737+
/* Garbage payload — must never be parsed when skipped. */
1738+
WMEMSET(dhPayload, 0xAB, sizeof(dhPayload));
1739+
1740+
ret = wolfSSH_TestDoKexDhInit(ssh, dhPayload, sizeof(dhPayload), &idx);
1741+
AssertIntEQ(ret, WS_SUCCESS);
1742+
AssertIntEQ(idx, sizeof(dhPayload));
1743+
AssertIntEQ(ssh->handshake->ignoreNextKexMsg, 0);
1744+
AssertIntEQ(ssh->clientState, CLIENT_KEXINIT_DONE);
1745+
1746+
wolfSSH_free(ssh);
1747+
wolfSSH_CTX_free(ctx);
1748+
}
1749+
1750+
static void TestFirstPacketFollows(void)
1751+
{
1752+
size_t i;
1753+
size_t n = sizeof(firstPacketFollowsCases)
1754+
/ sizeof(firstPacketFollowsCases[0]);
1755+
1756+
for (i = 0; i < n; i++) {
1757+
RunFirstPacketFollowsCase(&firstPacketFollowsCases[i]);
1758+
}
1759+
TestFirstPacketFollowsDhInitSkipped();
1760+
}
1761+
1762+
#endif /* first_packet_follows coverage guard */
1763+
1764+
16221765
int main(int argc, char** argv)
16231766
{
16241767
WOLFSSH_CTX* ctx;
@@ -1653,6 +1796,11 @@ int main(int argc, char** argv)
16531796
TestAgentChannelNullAgentSendsOpenFail();
16541797
#endif
16551798
TestKexInitRejectedWhenKeying(ssh);
1799+
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) && !defined(WOLFSSH_NO_RSA) \
1800+
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \
1801+
&& !defined(WOLFSSH_NO_RSA_SHA2_256)
1802+
TestFirstPacketFollows();
1803+
#endif
16561804
TestDisconnectSetsDisconnectError();
16571805
TestClientBuffersIdempotent();
16581806
TestPasswordEofNoCrash();

wolfssh/internal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,10 @@ enum WS_MessageIdLimits {
13221322
#ifdef WOLFSSH_TEST_INTERNAL
13231323
WOLFSSH_API int wolfSSH_TestIsMessageAllowed(WOLFSSH* ssh, byte msg,
13241324
byte state);
1325+
WOLFSSH_API int wolfSSH_TestDoKexInit(WOLFSSH* ssh, byte* buf,
1326+
word32 len, word32* idx);
1327+
WOLFSSH_API int wolfSSH_TestDoKexDhInit(WOLFSSH* ssh, byte* buf,
1328+
word32 len, word32* idx);
13251329
#endif
13261330

13271331
/* dynamic memory types */

0 commit comments

Comments
 (0)