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