Skip to content

Commit 8cf743f

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 ca84928 commit 8cf743f

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
@@ -1924,6 +1924,167 @@ static void TestKeyboardResponseNullCtx(WOLFSSH* ssh)
19241924
#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
19251925

19261926

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