@@ -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+
19282089int 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 ();
0 commit comments