@@ -1989,6 +1989,33 @@ static word32 BuildKexInitPayload(WOLFSSH* ssh, const char* kexList,
19891989 return idx ;
19901990}
19911991
1992+ /* Like BuildKexInitPayload but with explicit per-direction cipher/MAC lists. */
1993+ static word32 BuildKexInitPayloadFull (const char * kexList ,
1994+ const char * keyList , const char * encC2S , const char * encS2C ,
1995+ const char * macC2S , const char * macS2C ,
1996+ byte firstPacketFollows , byte * out , word32 outSz )
1997+ {
1998+ word32 idx = 0 ;
1999+
2000+ AssertTrue (idx + COOKIE_SZ <= outSz );
2001+ WMEMSET (out + idx , 0 , COOKIE_SZ );
2002+ idx += COOKIE_SZ ;
2003+ idx = AppendString (out , outSz , idx , kexList );
2004+ idx = AppendString (out , outSz , idx , keyList );
2005+ idx = AppendString (out , outSz , idx , encC2S );
2006+ idx = AppendString (out , outSz , idx , encS2C );
2007+ idx = AppendString (out , outSz , idx , macC2S );
2008+ idx = AppendString (out , outSz , idx , macS2C );
2009+ idx = AppendString (out , outSz , idx , "none" );
2010+ idx = AppendString (out , outSz , idx , "none" );
2011+ idx = AppendString (out , outSz , idx , "" );
2012+ idx = AppendString (out , outSz , idx , "" );
2013+ idx = AppendByte (out , outSz , idx , firstPacketFollows );
2014+ idx = AppendUint32 (out , outSz , idx , 0 ); /* reserved */
2015+
2016+ return idx ;
2017+ }
2018+
19922019typedef struct {
19932020 const char * description ;
19942021 const char * kexList ;
@@ -2107,6 +2134,79 @@ static void TestFirstPacketFollows(void)
21072134 TestFirstPacketFollowsSkipped ();
21082135}
21092136
2137+ #if !defined(WOLFSSH_NO_AES_CBC ) && !defined(WOLFSSH_NO_AES_CTR ) \
2138+ && !defined(WOLFSSH_NO_HMAC_SHA1 ) && !defined(WOLFSSH_NO_HMAC_SHA2_256 )
2139+ static void TestIndependentAlgoNegotiation (void )
2140+ {
2141+ WOLFSSH_CTX * ctx ;
2142+ WOLFSSH * ssh ;
2143+ byte payload [512 ];
2144+ word32 payloadSz ;
2145+ word32 idx ;
2146+
2147+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
2148+ AssertNotNull (ctx );
2149+
2150+ /* Sub-test A: different non-AEAD cipher and MAC per direction */
2151+ ssh = wolfSSH_new (ctx );
2152+ AssertNotNull (ssh );
2153+ AssertIntEQ (wolfSSH_SetAlgoListKex (ssh , FPF_KEX_GOOD ), WS_SUCCESS );
2154+ AssertIntEQ (wolfSSH_SetAlgoListKey (ssh , FPF_KEY_GOOD ), WS_SUCCESS );
2155+ AssertIntEQ (wolfSSH_SetAlgoListCipher (ssh ,
2156+ "aes128-cbc,aes256-ctr" ), WS_SUCCESS );
2157+ AssertIntEQ (wolfSSH_SetAlgoListMac (ssh ,
2158+ "hmac-sha1,hmac-sha2-256" ), WS_SUCCESS );
2159+ idx = 0 ;
2160+ payloadSz = BuildKexInitPayloadFull (
2161+ FPF_KEX_GOOD , FPF_KEY_GOOD ,
2162+ "aes128-cbc" , /* C2S enc */
2163+ "aes256-ctr" , /* S2C enc */
2164+ "hmac-sha1" , /* C2S MAC */
2165+ "hmac-sha2-256" , /* S2C MAC */
2166+ 0 , payload , (word32 )sizeof (payload ));
2167+ (void )wolfSSH_TestDoKexInit (ssh , payload , payloadSz , & idx );
2168+ AssertNotNull (ssh -> handshake );
2169+ AssertIntEQ (ssh -> handshake -> peerEncryptId , ID_AES128_CBC );
2170+ AssertIntEQ (ssh -> handshake -> encryptId , ID_AES256_CTR );
2171+ AssertIntEQ (ssh -> handshake -> peerMacId , ID_HMAC_SHA1 );
2172+ AssertIntEQ (ssh -> handshake -> macId , ID_HMAC_SHA2_256 );
2173+ AssertIntEQ (ssh -> handshake -> peerAeadMode , 0 );
2174+ AssertIntEQ (ssh -> handshake -> aeadMode , 0 );
2175+ wolfSSH_free (ssh );
2176+
2177+ #ifndef WOLFSSH_NO_AES_GCM
2178+ /* Sub-test B: AEAD S2C, non-AEAD C2S — MAC only negotiated for C2S */
2179+ ssh = wolfSSH_new (ctx );
2180+ AssertNotNull (ssh );
2181+ AssertIntEQ (wolfSSH_SetAlgoListKex (ssh , FPF_KEX_GOOD ), WS_SUCCESS );
2182+ AssertIntEQ (wolfSSH_SetAlgoListKey (ssh , FPF_KEY_GOOD ), WS_SUCCESS );
2183+ AssertIntEQ (wolfSSH_SetAlgoListCipher (ssh ,
2184+ "aes128-cbc,aes256-gcm@openssh.com" ), WS_SUCCESS );
2185+ AssertIntEQ (wolfSSH_SetAlgoListMac (ssh ,
2186+ "hmac-sha1,hmac-sha2-256" ), WS_SUCCESS );
2187+ idx = 0 ;
2188+ payloadSz = BuildKexInitPayloadFull (
2189+ FPF_KEX_GOOD , FPF_KEY_GOOD ,
2190+ "aes128-cbc" , /* C2S enc: non-AEAD */
2191+ "aes256-gcm@openssh.com" , /* S2C enc: AEAD */
2192+ "hmac-sha1" , /* C2S MAC: negotiated */
2193+ "hmac-sha2-256" , /* S2C MAC: skipped (aeadMode) */
2194+ 0 , payload , (word32 )sizeof (payload ));
2195+ (void )wolfSSH_TestDoKexInit (ssh , payload , payloadSz , & idx );
2196+ AssertNotNull (ssh -> handshake );
2197+ AssertIntEQ (ssh -> handshake -> peerEncryptId , ID_AES128_CBC );
2198+ AssertIntEQ (ssh -> handshake -> encryptId , ID_AES256_GCM );
2199+ AssertIntEQ (ssh -> handshake -> peerAeadMode , 0 );
2200+ AssertIntEQ (ssh -> handshake -> aeadMode , 1 );
2201+ AssertIntEQ (ssh -> handshake -> peerMacId , ID_HMAC_SHA1 );
2202+ AssertIntEQ (ssh -> handshake -> macId , ID_NONE );
2203+ wolfSSH_free (ssh );
2204+ #endif /* !WOLFSSH_NO_AES_GCM */
2205+
2206+ wolfSSH_CTX_free (ctx );
2207+ }
2208+ #endif /* AES_CBC + AES_CTR + HMAC guards */
2209+
21102210#endif /* first_packet_follows coverage guard */
21112211
21122212
@@ -2155,6 +2255,13 @@ int main(int argc, char** argv)
21552255 && !defined(WOLFSSH_NO_CURVE25519_SHA256 ) \
21562256 && !defined(WOLFSSH_NO_RSA_SHA2_256 )
21572257 TestFirstPacketFollows ();
2258+ #endif
2259+ #if !defined(WOLFSSH_NO_ECDH_SHA2_NISTP256 ) && !defined(WOLFSSH_NO_RSA ) \
2260+ && !defined(WOLFSSH_NO_CURVE25519_SHA256 ) \
2261+ && !defined(WOLFSSH_NO_RSA_SHA2_256 ) \
2262+ && !defined(WOLFSSH_NO_AES_CBC ) && !defined(WOLFSSH_NO_AES_CTR ) \
2263+ && !defined(WOLFSSH_NO_HMAC_SHA1 ) && !defined(WOLFSSH_NO_HMAC_SHA2_256 )
2264+ TestIndependentAlgoNegotiation ();
21582265#endif
21592266 TestDisconnectSetsDisconnectError ();
21602267 TestClientBuffersIdempotent ();
0 commit comments