Skip to content

Commit 5798eb2

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 88ca5ba commit 5798eb2

3 files changed

Lines changed: 168 additions & 0 deletions

File tree

src/internal.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,19 @@ int wolfSSH_TestIsMessageAllowed(WOLFSSH* ssh, byte msg, byte state)
858858
{
859859
return IsMessageAllowed(ssh, msg, state);
860860
}
861+
862+
static int DoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx);
863+
static int DoKexDhInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx);
864+
865+
int wolfSSH_TestDoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
866+
{
867+
return DoKexInit(ssh, buf, len, idx);
868+
}
869+
870+
int wolfSSH_TestDoKexDhInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
871+
{
872+
return DoKexDhInit(ssh, buf, len, idx);
873+
}
861874
#endif
862875

863876

tests/regress.c

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,6 +1886,152 @@ static void TestKeyboardResponseNullCtx(WOLFSSH* ssh)
18861886
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
18871887

18881888

1889+
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) \
1890+
&& !defined(WOLFSSH_NO_RSA) \
1891+
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \
1892+
&& !defined(WOLFSSH_NO_RSA_SHA2_256)
1893+
1894+
#define FPF_KEX_GOOD "ecdh-sha2-nistp256"
1895+
#define FPF_KEX_BAD "curve25519-sha256"
1896+
#define FPF_KEY_GOOD "ssh-rsa"
1897+
#define FPF_KEY_BAD "rsa-sha2-256"
1898+
1899+
/* Build a KEXINIT payload using the server ssh's own canned cipher/MAC lists
1900+
* so negotiation succeeds whichever AES/HMAC modes are compiled in. */
1901+
static word32 BuildKexInitPayload(WOLFSSH* ssh, const char* kexList,
1902+
const char* keyList, byte firstPacketFollows,
1903+
byte* out, word32 outSz)
1904+
{
1905+
word32 idx = 0;
1906+
1907+
/* cookie */
1908+
AssertTrue(idx + COOKIE_SZ <= outSz);
1909+
WMEMSET(out + idx, 0, COOKIE_SZ);
1910+
idx += COOKIE_SZ;
1911+
1912+
idx = AppendString(out, outSz, idx, kexList);
1913+
idx = AppendString(out, outSz, idx, keyList);
1914+
idx = AppendString(out, outSz, idx, ssh->algoListCipher);
1915+
idx = AppendString(out, outSz, idx, ssh->algoListCipher);
1916+
idx = AppendString(out, outSz, idx, ssh->algoListMac);
1917+
idx = AppendString(out, outSz, idx, ssh->algoListMac);
1918+
idx = AppendString(out, outSz, idx, "none");
1919+
idx = AppendString(out, outSz, idx, "none");
1920+
idx = AppendString(out, outSz, idx, "");
1921+
idx = AppendString(out, outSz, idx, "");
1922+
1923+
idx = AppendByte(out, outSz, idx, firstPacketFollows);
1924+
idx = AppendUint32(out, outSz, idx, 0); /* reserved */
1925+
1926+
return idx;
1927+
}
1928+
1929+
typedef struct {
1930+
const char* description;
1931+
const char* kexList;
1932+
const char* keyList;
1933+
byte firstPacketFollows;
1934+
byte expectIgnore;
1935+
} FirstPacketFollowsCase;
1936+
1937+
static const FirstPacketFollowsCase firstPacketFollowsCases[] = {
1938+
{ "follows=0, guesses irrelevant: flag stays off",
1939+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 0, 0 },
1940+
{ "follows=1, both guesses match: do not skip",
1941+
FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 0 },
1942+
{ "follows=1, KEX guess wrong: skip",
1943+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 1 },
1944+
{ "follows=1, host-key guess wrong: skip", /* regression case */
1945+
FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1 },
1946+
{ "follows=1, both guesses wrong: skip",
1947+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1 },
1948+
};
1949+
1950+
static void RunFirstPacketFollowsCase(const FirstPacketFollowsCase* tc)
1951+
{
1952+
WOLFSSH_CTX* ctx;
1953+
WOLFSSH* ssh;
1954+
byte payload[512];
1955+
word32 payloadSz;
1956+
word32 idx = 0;
1957+
1958+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
1959+
AssertNotNull(ctx);
1960+
1961+
ssh = wolfSSH_new(ctx);
1962+
AssertNotNull(ssh);
1963+
1964+
AssertIntEQ(wolfSSH_SetAlgoListKex(ssh, FPF_KEX_GOOD), WS_SUCCESS);
1965+
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, FPF_KEY_GOOD), WS_SUCCESS);
1966+
1967+
payloadSz = BuildKexInitPayload(ssh, tc->kexList, tc->keyList,
1968+
tc->firstPacketFollows, payload, sizeof(payload));
1969+
1970+
/* DoKexInit's tail hashes and sends a response; on a stripped-down
1971+
* WOLFSSH without a loaded host key or a primed peer proto id, that
1972+
* tail errors. We only care about the parse path up through
1973+
* first_packet_follows, where ignoreNextKexMsg is set. */
1974+
(void)wolfSSH_TestDoKexInit(ssh, payload, payloadSz, &idx);
1975+
1976+
AssertNotNull(ssh->handshake);
1977+
if (ssh->handshake->ignoreNextKexMsg != tc->expectIgnore) {
1978+
Fail(("ignoreNextKexMsg == %u (%s)",
1979+
tc->expectIgnore, tc->description),
1980+
("%u", ssh->handshake->ignoreNextKexMsg));
1981+
}
1982+
1983+
wolfSSH_free(ssh);
1984+
wolfSSH_CTX_free(ctx);
1985+
}
1986+
1987+
/* With ignoreNextKexMsg set, DoKexDhInit must consume the packet, clear the
1988+
* flag, and not advance clientState to CLIENT_KEXDH_INIT_DONE. */
1989+
static void TestFirstPacketFollowsDhInitSkipped(void)
1990+
{
1991+
WOLFSSH_CTX* ctx;
1992+
WOLFSSH* ssh;
1993+
byte dhPayload[8];
1994+
word32 idx = 0;
1995+
int ret;
1996+
1997+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
1998+
AssertNotNull(ctx);
1999+
2000+
ssh = wolfSSH_new(ctx);
2001+
AssertNotNull(ssh);
2002+
AssertNotNull(ssh->handshake);
2003+
2004+
ssh->handshake->ignoreNextKexMsg = 1;
2005+
ssh->clientState = CLIENT_KEXINIT_DONE;
2006+
2007+
/* Garbage payload — must never be parsed when skipped. */
2008+
WMEMSET(dhPayload, 0xAB, sizeof(dhPayload));
2009+
2010+
ret = wolfSSH_TestDoKexDhInit(ssh, dhPayload, sizeof(dhPayload), &idx);
2011+
AssertIntEQ(ret, WS_SUCCESS);
2012+
AssertIntEQ(idx, sizeof(dhPayload));
2013+
AssertIntEQ(ssh->handshake->ignoreNextKexMsg, 0);
2014+
AssertIntEQ(ssh->clientState, CLIENT_KEXINIT_DONE);
2015+
2016+
wolfSSH_free(ssh);
2017+
wolfSSH_CTX_free(ctx);
2018+
}
2019+
2020+
static void TestFirstPacketFollows(void)
2021+
{
2022+
size_t i;
2023+
size_t n = sizeof(firstPacketFollowsCases)
2024+
/ sizeof(firstPacketFollowsCases[0]);
2025+
2026+
for (i = 0; i < n; i++) {
2027+
RunFirstPacketFollowsCase(&firstPacketFollowsCases[i]);
2028+
}
2029+
TestFirstPacketFollowsDhInitSkipped();
2030+
}
2031+
2032+
#endif /* first_packet_follows coverage guard */
2033+
2034+
18892035
int main(int argc, char** argv)
18902036
{
18912037
WOLFSSH_CTX* ctx;
@@ -1926,6 +2072,11 @@ int main(int argc, char** argv)
19262072
TestAgentChannelNullAgentSendsOpenFail();
19272073
#endif
19282074
TestKexInitRejectedWhenKeying(ssh);
2075+
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) && !defined(WOLFSSH_NO_RSA) \
2076+
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \
2077+
&& !defined(WOLFSSH_NO_RSA_SHA2_256)
2078+
TestFirstPacketFollows();
2079+
#endif
19292080
TestDisconnectSetsDisconnectError();
19302081
TestClientBuffersIdempotent();
19312082
TestPasswordEofNoCrash();

wolfssh/internal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,10 @@ enum WS_MessageIdLimits {
13261326
WOLFSSH_API int wolfSSH_TestIsMessageAllowed(WOLFSSH* ssh, byte msg,
13271327
byte state);
13281328
WOLFSSH_API int wolfSSH_TestDoReceive(WOLFSSH* ssh);
1329+
WOLFSSH_API int wolfSSH_TestDoKexInit(WOLFSSH* ssh, byte* buf,
1330+
word32 len, word32* idx);
1331+
WOLFSSH_API int wolfSSH_TestDoKexDhInit(WOLFSSH* ssh, byte* buf,
1332+
word32 len, word32* idx);
13291333
#ifndef WOLFSSH_NO_DH_GEX_SHA256
13301334
WOLFSSH_API int wolfSSH_TestValidateKexDhGexGroup(const byte* primeGroup,
13311335
word32 primeGroupSz, const byte* generator, word32 generatorSz,

0 commit comments

Comments
 (0)