Skip to content

Commit 3df6bcf

Browse files
ejohnstownpadelsbach
authored andcommitted
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 55d48e6 commit 3df6bcf

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

@@ -6299,6 +6323,15 @@ static int DoKexDhGexRequest(WOLFSSH* ssh,
62996323
ret = WS_BAD_ARGUMENT;
63006324

63016325
if (ret == WS_SUCCESS) {
6326+
if (ssh->handshake->ignoreNextKexMsg) {
6327+
/* skip this message. */
6328+
WLOG(WS_LOG_DEBUG, "Skipping client's KEXDH_GEX_REQUEST message "
6329+
"due to first_packet_follows guess mismatch.");
6330+
ssh->handshake->ignoreNextKexMsg = 0;
6331+
*idx += len;
6332+
return WS_SUCCESS;
6333+
}
6334+
63026335
begin = *idx;
63036336
ret = GetUint32(&ssh->handshake->dhGexMinSz, buf, len, &begin);
63046337
}

tests/regress.c

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,6 +1925,167 @@ static void TestKeyboardResponseNullCtx(WOLFSSH* ssh)
19251925
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
19261926

19271927

1928+
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) \
1929+
&& !defined(WOLFSSH_NO_RSA) \
1930+
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \
1931+
&& !defined(WOLFSSH_NO_RSA_SHA2_256)
1932+
1933+
#define FPF_KEX_GOOD "ecdh-sha2-nistp256"
1934+
#define FPF_KEX_BAD "curve25519-sha256"
1935+
#define FPF_KEY_GOOD "ssh-rsa"
1936+
#define FPF_KEY_BAD "rsa-sha2-256"
1937+
1938+
/* Build a KEXINIT payload using the server ssh's own canned cipher/MAC lists
1939+
* so negotiation succeeds whichever AES/HMAC modes are compiled in. */
1940+
static word32 BuildKexInitPayload(WOLFSSH* ssh, const char* kexList,
1941+
const char* keyList, byte firstPacketFollows,
1942+
byte* out, word32 outSz)
1943+
{
1944+
word32 idx = 0;
1945+
1946+
/* cookie */
1947+
AssertTrue(idx + COOKIE_SZ <= outSz);
1948+
WMEMSET(out + idx, 0, COOKIE_SZ);
1949+
idx += COOKIE_SZ;
1950+
1951+
idx = AppendString(out, outSz, idx, kexList);
1952+
idx = AppendString(out, outSz, idx, keyList);
1953+
idx = AppendString(out, outSz, idx, ssh->algoListCipher);
1954+
idx = AppendString(out, outSz, idx, ssh->algoListCipher);
1955+
idx = AppendString(out, outSz, idx, ssh->algoListMac);
1956+
idx = AppendString(out, outSz, idx, ssh->algoListMac);
1957+
idx = AppendString(out, outSz, idx, "none");
1958+
idx = AppendString(out, outSz, idx, "none");
1959+
idx = AppendString(out, outSz, idx, "");
1960+
idx = AppendString(out, outSz, idx, "");
1961+
1962+
idx = AppendByte(out, outSz, idx, firstPacketFollows);
1963+
idx = AppendUint32(out, outSz, idx, 0); /* reserved */
1964+
1965+
return idx;
1966+
}
1967+
1968+
typedef struct {
1969+
const char* description;
1970+
const char* kexList;
1971+
const char* keyList;
1972+
byte firstPacketFollows;
1973+
byte expectIgnore;
1974+
} FirstPacketFollowsCase;
1975+
1976+
static const FirstPacketFollowsCase firstPacketFollowsCases[] = {
1977+
{ "follows=0, guesses irrelevant: flag stays off",
1978+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 0, 0 },
1979+
{ "follows=1, both guesses match: do not skip",
1980+
FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 0 },
1981+
{ "follows=1, KEX guess wrong: skip",
1982+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_GOOD, 1, 1 },
1983+
{ "follows=1, host-key guess wrong: skip", /* regression case */
1984+
FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1 },
1985+
{ "follows=1, both guesses wrong: skip",
1986+
FPF_KEX_BAD "," FPF_KEX_GOOD, FPF_KEY_BAD "," FPF_KEY_GOOD, 1, 1 },
1987+
};
1988+
1989+
static void RunFirstPacketFollowsCase(const FirstPacketFollowsCase* tc)
1990+
{
1991+
WOLFSSH_CTX* ctx;
1992+
WOLFSSH* ssh;
1993+
byte payload[512];
1994+
word32 payloadSz;
1995+
word32 idx = 0;
1996+
1997+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
1998+
AssertNotNull(ctx);
1999+
2000+
ssh = wolfSSH_new(ctx);
2001+
AssertNotNull(ssh);
2002+
2003+
AssertIntEQ(wolfSSH_SetAlgoListKex(ssh, FPF_KEX_GOOD), WS_SUCCESS);
2004+
AssertIntEQ(wolfSSH_SetAlgoListKey(ssh, FPF_KEY_GOOD), WS_SUCCESS);
2005+
2006+
payloadSz = BuildKexInitPayload(ssh, tc->kexList, tc->keyList,
2007+
tc->firstPacketFollows, payload, sizeof(payload));
2008+
2009+
/* DoKexInit's tail hashes and sends a response; on a stripped-down
2010+
* WOLFSSH without a loaded host key or a primed peer proto id, that
2011+
* tail errors. We only care about the parse path up through
2012+
* first_packet_follows, where ignoreNextKexMsg is set. */
2013+
(void)wolfSSH_TestDoKexInit(ssh, payload, payloadSz, &idx);
2014+
2015+
AssertNotNull(ssh->handshake);
2016+
if (ssh->handshake->ignoreNextKexMsg != tc->expectIgnore) {
2017+
Fail(("ignoreNextKexMsg == %u (%s)",
2018+
tc->expectIgnore, tc->description),
2019+
("%u", ssh->handshake->ignoreNextKexMsg));
2020+
}
2021+
2022+
wolfSSH_free(ssh);
2023+
wolfSSH_CTX_free(ctx);
2024+
}
2025+
2026+
typedef int (*FirstPacketFollowsSkipFn)(WOLFSSH* ssh, byte* buf, word32 len,
2027+
word32* idx);
2028+
2029+
/* With ignoreNextKexMsg set, the target Do* handler must consume the packet,
2030+
* clear the flag, and not advance clientState past CLIENT_KEXINIT_DONE. */
2031+
static void RunFirstPacketFollowsSkipCase(FirstPacketFollowsSkipFn fn,
2032+
const char* label)
2033+
{
2034+
WOLFSSH_CTX* ctx;
2035+
WOLFSSH* ssh;
2036+
byte payload[8];
2037+
word32 idx = 0;
2038+
int ret;
2039+
2040+
ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL);
2041+
AssertNotNull(ctx);
2042+
2043+
ssh = wolfSSH_new(ctx);
2044+
AssertNotNull(ssh);
2045+
AssertNotNull(ssh->handshake);
2046+
2047+
ssh->handshake->ignoreNextKexMsg = 1;
2048+
ssh->clientState = CLIENT_KEXINIT_DONE;
2049+
2050+
/* Garbage payload — must never be parsed when skipped. */
2051+
WMEMSET(payload, 0xAB, sizeof(payload));
2052+
2053+
ret = fn(ssh, payload, sizeof(payload), &idx);
2054+
if (ret != WS_SUCCESS) {
2055+
Fail(("%s returns WS_SUCCESS when skipping", label), ("%d", ret));
2056+
}
2057+
AssertIntEQ(idx, sizeof(payload));
2058+
AssertIntEQ(ssh->handshake->ignoreNextKexMsg, 0);
2059+
AssertIntEQ(ssh->clientState, CLIENT_KEXINIT_DONE);
2060+
2061+
wolfSSH_free(ssh);
2062+
wolfSSH_CTX_free(ctx);
2063+
}
2064+
2065+
static void TestFirstPacketFollowsSkipped(void)
2066+
{
2067+
RunFirstPacketFollowsSkipCase(wolfSSH_TestDoKexDhInit, "DoKexDhInit");
2068+
#ifndef WOLFSSH_NO_DH_GEX_SHA256
2069+
RunFirstPacketFollowsSkipCase(wolfSSH_TestDoKexDhGexRequest,
2070+
"DoKexDhGexRequest");
2071+
#endif
2072+
}
2073+
2074+
static void TestFirstPacketFollows(void)
2075+
{
2076+
size_t i;
2077+
size_t n = sizeof(firstPacketFollowsCases)
2078+
/ sizeof(firstPacketFollowsCases[0]);
2079+
2080+
for (i = 0; i < n; i++) {
2081+
RunFirstPacketFollowsCase(&firstPacketFollowsCases[i]);
2082+
}
2083+
TestFirstPacketFollowsSkipped();
2084+
}
2085+
2086+
#endif /* first_packet_follows coverage guard */
2087+
2088+
19282089
int main(int argc, char** argv)
19292090
{
19302091
WOLFSSH_CTX* ctx;
@@ -1965,6 +2126,11 @@ int main(int argc, char** argv)
19652126
TestAgentChannelNullAgentSendsOpenFail();
19662127
#endif
19672128
TestKexInitRejectedWhenKeying(ssh);
2129+
#if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256) && !defined(WOLFSSH_NO_RSA) \
2130+
&& !defined(WOLFSSH_NO_CURVE25519_SHA256) \
2131+
&& !defined(WOLFSSH_NO_RSA_SHA2_256)
2132+
TestFirstPacketFollows();
2133+
#endif
19682134
TestDisconnectSetsDisconnectError();
19692135
TestClientBuffersIdempotent();
19702136
TestPasswordEofNoCrash();

wolfssh/internal.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,13 @@ enum WS_MessageIdLimits {
13331333
word32 len, word32* idx);
13341334
WOLFSSH_API int wolfSSH_TestDoChannelRequest(WOLFSSH* ssh, byte* buf,
13351335
word32 len, word32* idx);
1336+
WOLFSSH_API int wolfSSH_TestDoKexInit(WOLFSSH* ssh, byte* buf,
1337+
word32 len, word32* idx);
1338+
WOLFSSH_API int wolfSSH_TestDoKexDhInit(WOLFSSH* ssh, byte* buf,
1339+
word32 len, word32* idx);
13361340
#ifndef WOLFSSH_NO_DH_GEX_SHA256
1341+
WOLFSSH_API int wolfSSH_TestDoKexDhGexRequest(WOLFSSH* ssh, byte* buf,
1342+
word32 len, word32* idx);
13371343
WOLFSSH_API int wolfSSH_TestValidateKexDhGexGroup(const byte* primeGroup,
13381344
word32 primeGroupSz, const byte* generator, word32 generatorSz,
13391345
word32 minBits, word32 maxBits, WC_RNG* rng);

0 commit comments

Comments
 (0)