@@ -1886,6 +1886,149 @@ 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+ static word32 BuildKexInitPayload (const char * kexList , const char * keyList ,
1900+ byte firstPacketFollows , byte * out , word32 outSz )
1901+ {
1902+ word32 idx = 0 ;
1903+
1904+ /* cookie */
1905+ AssertTrue (idx + COOKIE_SZ <= outSz );
1906+ WMEMSET (out + idx , 0 , COOKIE_SZ );
1907+ idx += COOKIE_SZ ;
1908+
1909+ idx = AppendString (out , outSz , idx , kexList );
1910+ idx = AppendString (out , outSz , idx , keyList );
1911+ idx = AppendString (out , outSz , idx , "aes128-ctr" );
1912+ idx = AppendString (out , outSz , idx , "aes128-ctr" );
1913+ idx = AppendString (out , outSz , idx , "hmac-sha2-256" );
1914+ idx = AppendString (out , outSz , idx , "hmac-sha2-256" );
1915+ idx = AppendString (out , outSz , idx , "none" );
1916+ idx = AppendString (out , outSz , idx , "none" );
1917+ idx = AppendString (out , outSz , idx , "" );
1918+ idx = AppendString (out , outSz , idx , "" );
1919+
1920+ idx = AppendByte (out , outSz , idx , firstPacketFollows );
1921+ idx = AppendUint32 (out , outSz , idx , 0 ); /* reserved */
1922+
1923+ return idx ;
1924+ }
1925+
1926+ typedef struct {
1927+ const char * description ;
1928+ const char * kexList ;
1929+ const char * keyList ;
1930+ byte firstPacketFollows ;
1931+ byte expectIgnore ;
1932+ } FirstPacketFollowsCase ;
1933+
1934+ static const FirstPacketFollowsCase firstPacketFollowsCases [] = {
1935+ { "follows=0, guesses irrelevant: flag stays off" ,
1936+ FPF_KEX_BAD "," FPF_KEX_GOOD , FPF_KEY_BAD "," FPF_KEY_GOOD , 0 , 0 },
1937+ { "follows=1, both guesses match: do not skip" ,
1938+ FPF_KEX_GOOD , FPF_KEY_GOOD , 1 , 0 },
1939+ { "follows=1, KEX guess wrong: skip" ,
1940+ FPF_KEX_BAD "," FPF_KEX_GOOD , FPF_KEY_GOOD , 1 , 1 },
1941+ { "follows=1, host-key guess wrong: skip" , /* regression case */
1942+ FPF_KEX_GOOD , FPF_KEY_BAD "," FPF_KEY_GOOD , 1 , 1 },
1943+ { "follows=1, both guesses wrong: skip" ,
1944+ FPF_KEX_BAD "," FPF_KEX_GOOD , FPF_KEY_BAD "," FPF_KEY_GOOD , 1 , 1 },
1945+ };
1946+
1947+ static void RunFirstPacketFollowsCase (const FirstPacketFollowsCase * tc )
1948+ {
1949+ WOLFSSH_CTX * ctx ;
1950+ WOLFSSH * ssh ;
1951+ byte payload [512 ];
1952+ word32 payloadSz ;
1953+ word32 idx = 0 ;
1954+
1955+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
1956+ AssertNotNull (ctx );
1957+
1958+ ssh = wolfSSH_new (ctx );
1959+ AssertNotNull (ssh );
1960+
1961+ AssertIntEQ (wolfSSH_SetAlgoListKex (ssh , FPF_KEX_GOOD ), WS_SUCCESS );
1962+ AssertIntEQ (wolfSSH_SetAlgoListKey (ssh , FPF_KEY_GOOD ), WS_SUCCESS );
1963+
1964+ payloadSz = BuildKexInitPayload (tc -> kexList , tc -> keyList ,
1965+ tc -> firstPacketFollows , payload , sizeof (payload ));
1966+
1967+ /* DoKexInit's tail hashes and sends a response; on a stripped-down
1968+ * WOLFSSH without a loaded host key or a primed peer proto id, that
1969+ * tail errors. We only care about the parse path up through
1970+ * first_packet_follows, where ignoreNextKexMsg is set. */
1971+ (void )wolfSSH_TestDoKexInit (ssh , payload , payloadSz , & idx );
1972+
1973+ AssertNotNull (ssh -> handshake );
1974+ if (ssh -> handshake -> ignoreNextKexMsg != tc -> expectIgnore ) {
1975+ Fail (("ignoreNextKexMsg == %u (%s)" ,
1976+ tc -> expectIgnore , tc -> description ),
1977+ ("%u" , ssh -> handshake -> ignoreNextKexMsg ));
1978+ }
1979+
1980+ wolfSSH_free (ssh );
1981+ wolfSSH_CTX_free (ctx );
1982+ }
1983+
1984+ /* With ignoreNextKexMsg set, DoKexDhInit must consume the packet, clear the
1985+ * flag, and not advance clientState to CLIENT_KEXDH_INIT_DONE. */
1986+ static void TestFirstPacketFollowsDhInitSkipped (void )
1987+ {
1988+ WOLFSSH_CTX * ctx ;
1989+ WOLFSSH * ssh ;
1990+ byte dhPayload [8 ];
1991+ word32 idx = 0 ;
1992+ int ret ;
1993+
1994+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
1995+ AssertNotNull (ctx );
1996+
1997+ ssh = wolfSSH_new (ctx );
1998+ AssertNotNull (ssh );
1999+ AssertNotNull (ssh -> handshake );
2000+
2001+ ssh -> handshake -> ignoreNextKexMsg = 1 ;
2002+ ssh -> clientState = CLIENT_KEXINIT_DONE ;
2003+
2004+ /* Garbage payload — must never be parsed when skipped. */
2005+ WMEMSET (dhPayload , 0xAB , sizeof (dhPayload ));
2006+
2007+ ret = wolfSSH_TestDoKexDhInit (ssh , dhPayload , sizeof (dhPayload ), & idx );
2008+ AssertIntEQ (ret , WS_SUCCESS );
2009+ AssertIntEQ (idx , sizeof (dhPayload ));
2010+ AssertIntEQ (ssh -> handshake -> ignoreNextKexMsg , 0 );
2011+ AssertIntEQ (ssh -> clientState , CLIENT_KEXINIT_DONE );
2012+
2013+ wolfSSH_free (ssh );
2014+ wolfSSH_CTX_free (ctx );
2015+ }
2016+
2017+ static void TestFirstPacketFollows (void )
2018+ {
2019+ size_t i ;
2020+ size_t n = sizeof (firstPacketFollowsCases )
2021+ / sizeof (firstPacketFollowsCases [0 ]);
2022+
2023+ for (i = 0 ; i < n ; i ++ ) {
2024+ RunFirstPacketFollowsCase (& firstPacketFollowsCases [i ]);
2025+ }
2026+ TestFirstPacketFollowsDhInitSkipped ();
2027+ }
2028+
2029+ #endif /* first_packet_follows coverage guard */
2030+
2031+
18892032int main (int argc , char * * argv )
18902033{
18912034 WOLFSSH_CTX * ctx ;
@@ -1926,6 +2069,11 @@ int main(int argc, char** argv)
19262069 TestAgentChannelNullAgentSendsOpenFail ();
19272070#endif
19282071 TestKexInitRejectedWhenKeying (ssh );
2072+ #if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256 ) && !defined(WOLFSSH_NO_RSA ) \
2073+ && !defined(WOLFSSH_NO_CURVE25519_SHA256 ) \
2074+ && !defined(WOLFSSH_NO_RSA_SHA2_256 )
2075+ TestFirstPacketFollows ();
2076+ #endif
19292077 TestDisconnectSetsDisconnectError ();
19302078 TestClientBuffersIdempotent ();
19312079 TestPasswordEofNoCrash ();
0 commit comments