Skip to content

Commit c4716a1

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 69ae28b commit c4716a1

3 files changed

Lines changed: 205 additions & 0 deletions

File tree

src/internal.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,30 @@ 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+
#ifndef WOLFSSH_NO_DH_GEX_SHA256
865+
static int DoKexDhGexRequest(WOLFSSH* ssh, byte* buf, word32 len, word32* idx);
866+
#endif
867+
868+
int wolfSSH_TestDoKexInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
869+
{
870+
return DoKexInit(ssh, buf, len, idx);
871+
}
872+
873+
int wolfSSH_TestDoKexDhInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx)
874+
{
875+
return DoKexDhInit(ssh, buf, len, idx);
876+
}
877+
878+
#ifndef WOLFSSH_NO_DH_GEX_SHA256
879+
int wolfSSH_TestDoKexDhGexRequest(WOLFSSH* ssh, byte* buf, word32 len,
880+
word32* idx)
881+
{
882+
return DoKexDhGexRequest(ssh, buf, len, idx);
883+
}
884+
#endif
861885
#endif
862886

863887

@@ -6282,6 +6306,15 @@ static int DoKexDhGexRequest(WOLFSSH* ssh,
62826306
ret = WS_BAD_ARGUMENT;
62836307

62846308
if (ret == WS_SUCCESS) {
6309+
if (ssh->handshake->ignoreNextKexMsg) {
6310+
/* skip this message. */
6311+
WLOG(WS_LOG_DEBUG, "Skipping client's KEXDH_GEX_REQUEST message "
6312+
"due to first_packet_follows guess mismatch.");
6313+
ssh->handshake->ignoreNextKexMsg = 0;
6314+
*idx += len;
6315+
return WS_SUCCESS;
6316+
}
6317+
62856318
begin = *idx;
62866319
ret = GetUint32(&ssh->handshake->dhGexMinSz, buf, len, &begin);
62876320
}

tests/regress.c

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,6 +1886,167 @@ 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+
typedef int (*FirstPacketFollowsSkipFn)(WOLFSSH* ssh, byte* buf, word32 len,
1988+
word32* idx);
1989+
1990+
/* With ignoreNextKexMsg set, the target Do* handler must consume the packet,
1991+
* clear the flag, and not advance clientState past CLIENT_KEXINIT_DONE. */
1992+
static void RunFirstPacketFollowsSkipCase(FirstPacketFollowsSkipFn fn,
1993+
const char* label)
1994+
{
1995+
WOLFSSH_CTX* ctx;
1996+
WOLFSSH* ssh;
1997+
byte payload[8];
1998+
word32 idx = 0;
1999+
int ret;
2000+
2001+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
2002+
AssertNotNull(ctx);
2003+
2004+
ssh = wolfSSH_new(ctx);
2005+
AssertNotNull(ssh);
2006+
AssertNotNull(ssh->handshake);
2007+
2008+
ssh->handshake->ignoreNextKexMsg = 1;
2009+
ssh->clientState = CLIENT_KEXINIT_DONE;
2010+
2011+
/* Garbage payload — must never be parsed when skipped. */
2012+
WMEMSET(payload, 0xAB, sizeof(payload));
2013+
2014+
ret = fn(ssh, payload, sizeof(payload), &idx);
2015+
if (ret != WS_SUCCESS) {
2016+
Fail(("%s returns WS_SUCCESS when skipping", label), ("%d", ret));
2017+
}
2018+
AssertIntEQ(idx, sizeof(payload));
2019+
AssertIntEQ(ssh->handshake->ignoreNextKexMsg, 0);
2020+
AssertIntEQ(ssh->clientState, CLIENT_KEXINIT_DONE);
2021+
2022+
wolfSSH_free(ssh);
2023+
wolfSSH_CTX_free(ctx);
2024+
}
2025+
2026+
static void TestFirstPacketFollowsSkipped(void)
2027+
{
2028+
RunFirstPacketFollowsSkipCase(wolfSSH_TestDoKexDhInit, "DoKexDhInit");
2029+
#ifndef WOLFSSH_NO_DH_GEX_SHA256
2030+
RunFirstPacketFollowsSkipCase(wolfSSH_TestDoKexDhGexRequest,
2031+
"DoKexDhGexRequest");
2032+
#endif
2033+
}
2034+
2035+
static void TestFirstPacketFollows(void)
2036+
{
2037+
size_t i;
2038+
size_t n = sizeof(firstPacketFollowsCases)
2039+
/ sizeof(firstPacketFollowsCases[0]);
2040+
2041+
for (i = 0; i < n; i++) {
2042+
RunFirstPacketFollowsCase(&firstPacketFollowsCases[i]);
2043+
}
2044+
TestFirstPacketFollowsSkipped();
2045+
}
2046+
2047+
#endif /* first_packet_follows coverage guard */
2048+
2049+
18892050
int main(int argc, char** argv)
18902051
{
18912052
WOLFSSH_CTX* ctx;
@@ -1926,6 +2087,11 @@ int main(int argc, char** argv)
19262087
TestAgentChannelNullAgentSendsOpenFail();
19272088
#endif
19282089
TestKexInitRejectedWhenKeying(ssh);
2090+
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) && !defined(WOLFSSH_NO_RSA) \
2091+
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \
2092+
&& !defined(WOLFSSH_NO_RSA_SHA2_256)
2093+
TestFirstPacketFollows();
2094+
#endif
19292095
TestDisconnectSetsDisconnectError();
19302096
TestClientBuffersIdempotent();
19312097
TestPasswordEofNoCrash();

wolfssh/internal.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,7 +1328,13 @@ enum WS_MessageIdLimits {
13281328
WOLFSSH_API int wolfSSH_TestDoReceive(WOLFSSH* ssh);
13291329
WOLFSSH_API int wolfSSH_TestDoUserAuthBanner(WOLFSSH* ssh, byte* buf,
13301330
word32 len, word32* idx);
1331+
WOLFSSH_API int wolfSSH_TestDoKexInit(WOLFSSH* ssh, byte* buf,
1332+
word32 len, word32* idx);
1333+
WOLFSSH_API int wolfSSH_TestDoKexDhInit(WOLFSSH* ssh, byte* buf,
1334+
word32 len, word32* idx);
13311335
#ifndef WOLFSSH_NO_DH_GEX_SHA256
1336+
WOLFSSH_API int wolfSSH_TestDoKexDhGexRequest(WOLFSSH* ssh, byte* buf,
1337+
word32 len, word32* idx);
13321338
WOLFSSH_API int wolfSSH_TestValidateKexDhGexGroup(const byte* primeGroup,
13331339
word32 primeGroupSz, const byte* generator, word32 generatorSz,
13341340
word32 minBits, word32 maxBits, WC_RNG* rng);

0 commit comments

Comments
 (0)