@@ -1619,6 +1619,149 @@ static void TestKeyboardResponseNullCtx(WOLFSSH* ssh)
16191619#endif /* WOLFSSH_KEYBOARD_INTERACTIVE */
16201620
16211621
1622+ #if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256 ) \
1623+ && !defined(WOLFSSH_NO_RSA ) \
1624+ && !defined(WOLFSSH_NO_CURVE25519_SHA256 ) \
1625+ && !defined(WOLFSSH_NO_RSA_SHA2_256 )
1626+
1627+ #define FPF_KEX_GOOD "ecdh-sha2-nistp256"
1628+ #define FPF_KEX_BAD "curve25519-sha256"
1629+ #define FPF_KEY_GOOD "ssh-rsa"
1630+ #define FPF_KEY_BAD "rsa-sha2-256"
1631+
1632+ static word32 BuildKexInitPayload (const char * kexList , const char * keyList ,
1633+ byte firstPacketFollows , byte * out , word32 outSz )
1634+ {
1635+ word32 idx = 0 ;
1636+
1637+ /* cookie */
1638+ AssertTrue (idx + COOKIE_SZ <= outSz );
1639+ WMEMSET (out + idx , 0 , COOKIE_SZ );
1640+ idx += COOKIE_SZ ;
1641+
1642+ idx = AppendString (out , outSz , idx , kexList );
1643+ idx = AppendString (out , outSz , idx , keyList );
1644+ idx = AppendString (out , outSz , idx , "aes128-ctr" );
1645+ idx = AppendString (out , outSz , idx , "aes128-ctr" );
1646+ idx = AppendString (out , outSz , idx , "hmac-sha2-256" );
1647+ idx = AppendString (out , outSz , idx , "hmac-sha2-256" );
1648+ idx = AppendString (out , outSz , idx , "none" );
1649+ idx = AppendString (out , outSz , idx , "none" );
1650+ idx = AppendString (out , outSz , idx , "" );
1651+ idx = AppendString (out , outSz , idx , "" );
1652+
1653+ idx = AppendByte (out , outSz , idx , firstPacketFollows );
1654+ idx = AppendUint32 (out , outSz , idx , 0 ); /* reserved */
1655+
1656+ return idx ;
1657+ }
1658+
1659+ typedef struct {
1660+ const char * description ;
1661+ const char * kexList ;
1662+ const char * keyList ;
1663+ byte firstPacketFollows ;
1664+ byte expectIgnore ;
1665+ } FirstPacketFollowsCase ;
1666+
1667+ static const FirstPacketFollowsCase firstPacketFollowsCases [] = {
1668+ { "follows=0, guesses irrelevant: flag stays off" ,
1669+ FPF_KEX_BAD "," FPF_KEX_GOOD , FPF_KEY_BAD "," FPF_KEY_GOOD , 0 , 0 },
1670+ { "follows=1, both guesses match: do not skip" ,
1671+ FPF_KEX_GOOD , FPF_KEY_GOOD , 1 , 0 },
1672+ { "follows=1, KEX guess wrong: skip" ,
1673+ FPF_KEX_BAD "," FPF_KEX_GOOD , FPF_KEY_GOOD , 1 , 1 },
1674+ { "follows=1, host-key guess wrong: skip" , /* regression case */
1675+ FPF_KEX_GOOD , FPF_KEY_BAD "," FPF_KEY_GOOD , 1 , 1 },
1676+ { "follows=1, both guesses wrong: skip" ,
1677+ FPF_KEX_BAD "," FPF_KEX_GOOD , FPF_KEY_BAD "," FPF_KEY_GOOD , 1 , 1 },
1678+ };
1679+
1680+ static void RunFirstPacketFollowsCase (const FirstPacketFollowsCase * tc )
1681+ {
1682+ WOLFSSH_CTX * ctx ;
1683+ WOLFSSH * ssh ;
1684+ byte payload [512 ];
1685+ word32 payloadSz ;
1686+ word32 idx = 0 ;
1687+
1688+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
1689+ AssertNotNull (ctx );
1690+
1691+ ssh = wolfSSH_new (ctx );
1692+ AssertNotNull (ssh );
1693+
1694+ AssertIntEQ (wolfSSH_SetAlgoListKex (ssh , FPF_KEX_GOOD ), WS_SUCCESS );
1695+ AssertIntEQ (wolfSSH_SetAlgoListKey (ssh , FPF_KEY_GOOD ), WS_SUCCESS );
1696+
1697+ payloadSz = BuildKexInitPayload (tc -> kexList , tc -> keyList ,
1698+ tc -> firstPacketFollows , payload , sizeof (payload ));
1699+
1700+ /* DoKexInit's tail hashes and sends a response; on a stripped-down
1701+ * WOLFSSH without a loaded host key or a primed peer proto id, that
1702+ * tail errors. We only care about the parse path up through
1703+ * first_packet_follows, where ignoreNextKexMsg is set. */
1704+ (void )wolfSSH_TestDoKexInit (ssh , payload , payloadSz , & idx );
1705+
1706+ AssertNotNull (ssh -> handshake );
1707+ if (ssh -> handshake -> ignoreNextKexMsg != tc -> expectIgnore ) {
1708+ Fail (("ignoreNextKexMsg == %u (%s)" ,
1709+ tc -> expectIgnore , tc -> description ),
1710+ ("%u" , ssh -> handshake -> ignoreNextKexMsg ));
1711+ }
1712+
1713+ wolfSSH_free (ssh );
1714+ wolfSSH_CTX_free (ctx );
1715+ }
1716+
1717+ /* With ignoreNextKexMsg set, DoKexDhInit must consume the packet, clear the
1718+ * flag, and not advance clientState to CLIENT_KEXDH_INIT_DONE. */
1719+ static void TestFirstPacketFollowsDhInitSkipped (void )
1720+ {
1721+ WOLFSSH_CTX * ctx ;
1722+ WOLFSSH * ssh ;
1723+ byte dhPayload [8 ];
1724+ word32 idx = 0 ;
1725+ int ret ;
1726+
1727+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
1728+ AssertNotNull (ctx );
1729+
1730+ ssh = wolfSSH_new (ctx );
1731+ AssertNotNull (ssh );
1732+
1733+ ssh -> handshake = AllocHandshake (ssh );
1734+ ssh -> handshake -> ignoreNextKexMsg = 1 ;
1735+ ssh -> clientState = CLIENT_KEXINIT_DONE ;
1736+
1737+ /* Garbage payload — must never be parsed when skipped. */
1738+ WMEMSET (dhPayload , 0xAB , sizeof (dhPayload ));
1739+
1740+ ret = wolfSSH_TestDoKexDhInit (ssh , dhPayload , sizeof (dhPayload ), & idx );
1741+ AssertIntEQ (ret , WS_SUCCESS );
1742+ AssertIntEQ (idx , sizeof (dhPayload ));
1743+ AssertIntEQ (ssh -> handshake -> ignoreNextKexMsg , 0 );
1744+ AssertIntEQ (ssh -> clientState , CLIENT_KEXINIT_DONE );
1745+
1746+ wolfSSH_free (ssh );
1747+ wolfSSH_CTX_free (ctx );
1748+ }
1749+
1750+ static void TestFirstPacketFollows (void )
1751+ {
1752+ size_t i ;
1753+ size_t n = sizeof (firstPacketFollowsCases )
1754+ / sizeof (firstPacketFollowsCases [0 ]);
1755+
1756+ for (i = 0 ; i < n ; i ++ ) {
1757+ RunFirstPacketFollowsCase (& firstPacketFollowsCases [i ]);
1758+ }
1759+ TestFirstPacketFollowsDhInitSkipped ();
1760+ }
1761+
1762+ #endif /* first_packet_follows coverage guard */
1763+
1764+
16221765int main (int argc , char * * argv )
16231766{
16241767 WOLFSSH_CTX * ctx ;
@@ -1653,6 +1796,11 @@ int main(int argc, char** argv)
16531796 TestAgentChannelNullAgentSendsOpenFail ();
16541797#endif
16551798 TestKexInitRejectedWhenKeying (ssh );
1799+ #if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256 ) && !defined(WOLFSSH_NO_RSA ) \
1800+ && !defined(WOLFSSH_NO_CURVE25519_SHA256 ) \
1801+ && !defined(WOLFSSH_NO_RSA_SHA2_256 )
1802+ TestFirstPacketFollows ();
1803+ #endif
16561804 TestDisconnectSetsDisconnectError ();
16571805 TestClientBuffersIdempotent ();
16581806 TestPasswordEofNoCrash ();
0 commit comments