@@ -1692,6 +1692,214 @@ static int test_RsaVerify_BadDigest(void)
16921692}
16931693
16941694#endif /* !WOLFSSH_NO_RSA */
1695+
1696+ #if !defined(WOLFSSH_NO_ED25519 ) && defined(HAVE_ED25519 ) && \
1697+ defined(HAVE_ED25519_SIGN ) && defined(HAVE_ED25519_VERIFY ) && \
1698+ defined(WOLFSSL_ED25519_STREAMING_VERIFY )
1699+
1700+ /* Locally-generated Ed25519 keypair for the DoUserAuthRequestEd25519 test.
1701+ * 32-byte private seed followed by the 32-byte raw public key. Created with
1702+ * ssh-keygen and decoded from the OpenSSH private key format so the test is
1703+ * deterministic and does not depend on an RNG. */
1704+ static const byte unitTestEd25519Priv [32 ] = {
1705+ 0x05 , 0xf5 , 0x9c , 0x02 , 0x55 , 0x93 , 0x32 , 0x93 ,
1706+ 0xb9 , 0xc2 , 0x2e , 0xa7 , 0x20 , 0x05 , 0x33 , 0x0c ,
1707+ 0x40 , 0xcd , 0xfa , 0xff , 0x73 , 0xe4 , 0x4a , 0xe1 ,
1708+ 0x50 , 0x2a , 0x4b , 0x37 , 0x20 , 0x66 , 0xc5 , 0x56
1709+ };
1710+ static const byte unitTestEd25519Pub [32 ] = {
1711+ 0x33 , 0x76 , 0xaf , 0x20 , 0x97 , 0xce , 0x38 , 0xdf ,
1712+ 0x5a , 0x76 , 0x62 , 0xfc , 0xb2 , 0x87 , 0x6e , 0x9d ,
1713+ 0xd3 , 0x9e , 0x85 , 0x87 , 0xf3 , 0x0e , 0x72 , 0x6c ,
1714+ 0x1e , 0xc0 , 0x01 , 0xe2 , 0x81 , 0x96 , 0xb8 , 0x49
1715+ };
1716+
1717+ /* Write a 32-bit big-endian length prefix at out. */
1718+ static void Ed25519Test_PutLen (byte * out , word32 v )
1719+ {
1720+ out [0 ] = (byte )(v >> 24 );
1721+ out [1 ] = (byte )(v >> 16 );
1722+ out [2 ] = (byte )(v >> 8 );
1723+ out [3 ] = (byte )(v );
1724+ }
1725+
1726+ /* DoUserAuthRequestEd25519 unit test
1727+ *
1728+ * Drives DoUserAuthRequestEd25519 directly with a fully-formed Ed25519
1729+ * USERAUTH_REQUEST and asserts (1) a correct signature returns WS_SUCCESS
1730+ * and (2) a tampered signature returns either WS_ED25519_E or
1731+ * WS_CRYPTO_FAILED. This is the test that makes the
1732+ * `status ? WS_SUCCESS : WS_ED25519_E` style mapping load-bearing -- without
1733+ * the negative case any mutation that hard-codes WS_SUCCESS or inverts the
1734+ * status would silently survive.
1735+ *
1736+ * The Ed25519 verifier in wolfSSL has two failure paths reachable from this
1737+ * function: wc_ed25519_verify_msg_final returns non-zero (WS_CRYPTO_FAILED)
1738+ * or returns zero with status=0 (WS_ED25519_E). Different wolfSSL versions
1739+ * have routed bad signatures through either branch; we accept either failure
1740+ * code so the test stays portable while still killing the most dangerous
1741+ * mutation -- turning either error into WS_SUCCESS.
1742+ */
1743+ static int test_DoUserAuthRequestEd25519 (void )
1744+ {
1745+ static const char keyTypeName [] = "ssh-ed25519" ;
1746+ static const char username [] = "wolfssh" ;
1747+ static const char serviceName [] = "ssh-connection" ;
1748+ static const char authName [] = "publickey" ;
1749+ const word32 keyTypeNameSz = (word32 )(sizeof (keyTypeName ) - 1 );
1750+ const word32 usernameSz = (word32 )(sizeof (username ) - 1 );
1751+ const word32 serviceNameSz = (word32 )(sizeof (serviceName ) - 1 );
1752+ const word32 authNameSz = (word32 )(sizeof (authName ) - 1 );
1753+
1754+ WOLFSSH_CTX * ctx = NULL ;
1755+ WOLFSSH * ssh = NULL ;
1756+ ed25519_key signingKey ;
1757+ int signingKeyInit = 0 ;
1758+ WS_UserAuthData authData ;
1759+ byte pubKeyBlob [64 ];
1760+ byte sigBlob [128 ];
1761+ byte badSigBlob [128 ];
1762+ byte dataToSign [256 ];
1763+ byte checkData [512 ];
1764+ byte sig [ED25519_SIG_SIZE ];
1765+ word32 pubKeyBlobSz = 0 ;
1766+ word32 sigBlobSz = 0 ;
1767+ word32 dataToSignSz = 0 ;
1768+ word32 checkDataSz = 0 ;
1769+ word32 sigSz = sizeof (sig );
1770+ word32 off ;
1771+ int result = 0 ;
1772+ int ret ;
1773+
1774+ ctx = wolfSSH_CTX_new (WOLFSSH_ENDPOINT_SERVER , NULL );
1775+ if (ctx == NULL ) return -600 ;
1776+ ssh = wolfSSH_new (ctx );
1777+ if (ssh == NULL ) { result = -601 ; goto done ; }
1778+
1779+ /* Stub a session id so the verify hash has something to absorb. */
1780+ ssh -> sessionIdSz = 16 ;
1781+ WMEMSET (ssh -> sessionId , 0xA5 , ssh -> sessionIdSz );
1782+
1783+ /* Load the embedded Ed25519 keypair for signing. */
1784+ if (wc_ed25519_init (& signingKey ) != 0 ) { result = -602 ; goto done ; }
1785+ signingKeyInit = 1 ;
1786+ if (wc_ed25519_import_private_key (unitTestEd25519Priv ,
1787+ (word32 )sizeof (unitTestEd25519Priv ),
1788+ unitTestEd25519Pub , (word32 )sizeof (unitTestEd25519Pub ),
1789+ & signingKey ) != 0 ) {
1790+ result = -603 ; goto done ;
1791+ }
1792+
1793+ /* Build the SSH public key blob: string "ssh-ed25519" || string pubkey */
1794+ off = 0 ;
1795+ Ed25519Test_PutLen (pubKeyBlob + off , keyTypeNameSz ); off += UINT32_SZ ;
1796+ WMEMCPY (pubKeyBlob + off , keyTypeName , keyTypeNameSz );
1797+ off += keyTypeNameSz ;
1798+ Ed25519Test_PutLen (pubKeyBlob + off ,
1799+ (word32 )sizeof (unitTestEd25519Pub ));
1800+ off += UINT32_SZ ;
1801+ WMEMCPY (pubKeyBlob + off , unitTestEd25519Pub ,
1802+ sizeof (unitTestEd25519Pub ));
1803+ off += (word32 )sizeof (unitTestEd25519Pub );
1804+ pubKeyBlobSz = off ;
1805+
1806+ /* Build the dataToSign region the same way the wire packet would lay it
1807+ * out: username || service || authmethod || hasSig=1 || pkAlgo || pkBlob.
1808+ * DoUserAuthRequestEd25519 hashes exactly this region. */
1809+ off = 0 ;
1810+ Ed25519Test_PutLen (dataToSign + off , usernameSz ); off += UINT32_SZ ;
1811+ WMEMCPY (dataToSign + off , username , usernameSz ); off += usernameSz ;
1812+ Ed25519Test_PutLen (dataToSign + off , serviceNameSz ); off += UINT32_SZ ;
1813+ WMEMCPY (dataToSign + off , serviceName , serviceNameSz );
1814+ off += serviceNameSz ;
1815+ Ed25519Test_PutLen (dataToSign + off , authNameSz ); off += UINT32_SZ ;
1816+ WMEMCPY (dataToSign + off , authName , authNameSz ); off += authNameSz ;
1817+ dataToSign [off ++ ] = 1 ; /* hasSignature */
1818+ Ed25519Test_PutLen (dataToSign + off , keyTypeNameSz ); off += UINT32_SZ ;
1819+ WMEMCPY (dataToSign + off , keyTypeName , keyTypeNameSz );
1820+ off += keyTypeNameSz ;
1821+ Ed25519Test_PutLen (dataToSign + off , pubKeyBlobSz ); off += UINT32_SZ ;
1822+ WMEMCPY (dataToSign + off , pubKeyBlob , pubKeyBlobSz ); off += pubKeyBlobSz ;
1823+ dataToSignSz = off ;
1824+
1825+ /* Build the bytes that get signed: sessionIdSz || sessionId ||
1826+ * MSGID_USERAUTH_REQUEST || dataToSign. Mirrors BuildUserAuthRequestEd25519
1827+ * on the client side. */
1828+ off = 0 ;
1829+ Ed25519Test_PutLen (checkData + off , ssh -> sessionIdSz ); off += UINT32_SZ ;
1830+ WMEMCPY (checkData + off , ssh -> sessionId , ssh -> sessionIdSz );
1831+ off += ssh -> sessionIdSz ;
1832+ checkData [off ++ ] = MSGID_USERAUTH_REQUEST ;
1833+ WMEMCPY (checkData + off , dataToSign , dataToSignSz );
1834+ off += dataToSignSz ;
1835+ checkDataSz = off ;
1836+
1837+ if (wc_ed25519_sign_msg (checkData , checkDataSz , sig , & sigSz ,
1838+ & signingKey ) != 0 ) {
1839+ result = -604 ; goto done ;
1840+ }
1841+
1842+ /* Build the SSH signature blob: string "ssh-ed25519" || string sig */
1843+ off = 0 ;
1844+ Ed25519Test_PutLen (sigBlob + off , keyTypeNameSz ); off += UINT32_SZ ;
1845+ WMEMCPY (sigBlob + off , keyTypeName , keyTypeNameSz );
1846+ off += keyTypeNameSz ;
1847+ Ed25519Test_PutLen (sigBlob + off , sigSz ); off += UINT32_SZ ;
1848+ WMEMCPY (sigBlob + off , sig , sigSz ); off += sigSz ;
1849+ sigBlobSz = off ;
1850+
1851+ /* Populate authData the way DoUserAuthRequest/DoUserAuthRequestPublicKey
1852+ * would before dispatching to DoUserAuthRequestEd25519. */
1853+ WMEMSET (& authData , 0 , sizeof (authData ));
1854+ authData .type = WOLFSSH_USERAUTH_PUBLICKEY ;
1855+ authData .username = (const byte * )username ;
1856+ authData .usernameSz = usernameSz ;
1857+ authData .serviceName = (const byte * )serviceName ;
1858+ authData .serviceNameSz = serviceNameSz ;
1859+ authData .authName = (const byte * )authName ;
1860+ authData .authNameSz = authNameSz ;
1861+ authData .sf .publicKey .dataToSign = dataToSign ;
1862+ authData .sf .publicKey .publicKeyType = (const byte * )keyTypeName ;
1863+ authData .sf .publicKey .publicKeyTypeSz = keyTypeNameSz ;
1864+ authData .sf .publicKey .publicKey = pubKeyBlob ;
1865+ authData .sf .publicKey .publicKeySz = pubKeyBlobSz ;
1866+ authData .sf .publicKey .hasSignature = 1 ;
1867+ authData .sf .publicKey .signature = sigBlob ;
1868+ authData .sf .publicKey .signatureSz = sigBlobSz ;
1869+
1870+ /* Positive case: untouched signature must verify. */
1871+ ret = wolfSSH_TestDoUserAuthRequestEd25519 (ssh , & authData );
1872+ if (ret != WS_SUCCESS ) {
1873+ printf ("DoUserAuthRequestEd25519 positive: ret=%d (expected %d)\n" ,
1874+ ret , WS_SUCCESS );
1875+ result = -605 ; goto done ;
1876+ }
1877+
1878+ /* Negative case: flip a byte inside the raw signature (skip past the
1879+ * 4 + keyTypeNameSz + 4 header so we land in the actual signature
1880+ * material). Must NOT return WS_SUCCESS. */
1881+ WMEMCPY (badSigBlob , sigBlob , sigBlobSz );
1882+ badSigBlob [UINT32_SZ + keyTypeNameSz + UINT32_SZ + 10 ] ^= 0xFF ;
1883+ authData .sf .publicKey .signature = badSigBlob ;
1884+
1885+ ret = wolfSSH_TestDoUserAuthRequestEd25519 (ssh , & authData );
1886+ if (ret != WS_ED25519_E && ret != WS_CRYPTO_FAILED ) {
1887+ printf ("DoUserAuthRequestEd25519 tampered: ret=%d\n" , ret );
1888+ result = -606 ; goto done ;
1889+ }
1890+
1891+ done :
1892+ if (signingKeyInit )
1893+ wc_ed25519_free (& signingKey );
1894+ if (ssh != NULL )
1895+ wolfSSH_free (ssh );
1896+ if (ctx != NULL )
1897+ wolfSSH_CTX_free (ctx );
1898+ return result ;
1899+ }
1900+
1901+ #endif /* Ed25519 verify test guards */
1902+
16951903#endif /* WOLFSSH_TEST_INTERNAL */
16961904
16971905/* Error Code And Message Test */
@@ -1801,6 +2009,14 @@ int wolfSSH_UnitTest(int argc, char** argv)
18012009 printf ("RsaVerify_BadDigest: %s\n" ,
18022010 (unitResult == 0 ? "SUCCESS" : "FAILED" ));
18032011 testResult = testResult || unitResult ;
2012+ #endif
2013+ #if !defined(WOLFSSH_NO_ED25519 ) && defined(HAVE_ED25519 ) && \
2014+ defined(HAVE_ED25519_SIGN ) && defined(HAVE_ED25519_VERIFY ) && \
2015+ defined(WOLFSSL_ED25519_STREAMING_VERIFY )
2016+ unitResult = test_DoUserAuthRequestEd25519 ();
2017+ printf ("DoUserAuthRequestEd25519: %s\n" ,
2018+ (unitResult == 0 ? "SUCCESS" : "FAILED" ));
2019+ testResult = testResult || unitResult ;
18042020#endif
18052021 unitResult = test_ChannelPutData ();
18062022 printf ("ChannelPutData: %s\n" , (unitResult == 0 ? "SUCCESS" : "FAILED" ));
0 commit comments