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