@@ -1246,10 +1246,10 @@ static int test_OpenSshFormatNonCompositeRejected(void)
12461246 !defined(WOLFSSH_NO_AES_GCM))
12471247
12481248/* Minimal SSH binary packet: uint32 length, padding_length, msgId, padding.
1249- * Same layout as tests/regress.c BuildPacket (8-byte aligned body). */
1250- static word32 BuildMacTestPacketPrefix(byte msgId, byte* out, word32 outSz)
1249+ * The caller picks padLen to get the alignment it wants. */
1250+ static word32 BuildMacTestPacketPrefix(byte msgId, byte padLen,
1251+ byte* out, word32 outSz)
12511252{
1252- byte padLen = 6;
12531253 word32 packetLen = (word32)(1 + 1 + padLen);
12541254 word32 need = UINT32_SZ + packetLen;
12551255
@@ -1284,7 +1284,7 @@ static int test_DoReceive_VerifyMacFailure(void)
12841284 Hmac hmac;
12851285 word32 prefixLen;
12861286 word32 totalLen;
1287- byte pkt[UINT32_SZ + 8 + MAX_HMAC_SZ];
1287+ byte pkt[UINT32_SZ + 12 + MAX_HMAC_SZ];
12881288 int i;
12891289 struct {
12901290 byte macId;
@@ -1320,7 +1320,8 @@ static int test_DoReceive_VerifyMacFailure(void)
13201320 WMEMSET(macKey, 0xA5, sizeof(macKey));
13211321
13221322 for (i = 0; i < (int)(sizeof(cases) / sizeof(cases[0])); i++) {
1323- prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, pkt, sizeof(pkt));
1323+ prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, 10,
1324+ pkt, sizeof(pkt));
13241325 if (prefixLen == 0) {
13251326 result = -202;
13261327 goto done;
@@ -1412,8 +1413,8 @@ static int test_DoReceive_AeadTagFailure(void)
14121413 int aesInited = 0;
14131414 byte key[AES_256_KEY_SIZE];
14141415 byte iv[GCM_NONCE_MID_SZ];
1415- byte pkt[UINT32_SZ + 8 ];
1416- byte record[UINT32_SZ + 8 + AES_BLOCK_SIZE];
1416+ byte pkt[UINT32_SZ + 16 ];
1417+ byte record[UINT32_SZ + 16 + AES_BLOCK_SIZE];
14171418 word32 prefixLen, payloadSz, totalLen;
14181419
14191420 ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
@@ -1428,7 +1429,8 @@ static int test_DoReceive_AeadTagFailure(void)
14281429 WMEMSET(key, 0x5A, sizeof(key));
14291430 WMEMSET(iv, 0x31, sizeof(iv));
14301431
1431- prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, pkt, sizeof(pkt));
1432+ prefixLen = BuildMacTestPacketPrefix(MSGID_IGNORE, 14,
1433+ pkt, sizeof(pkt));
14321434 if (prefixLen == 0) {
14331435 result = -222;
14341436 goto done;
@@ -1466,7 +1468,7 @@ static int test_DoReceive_AeadTagFailure(void)
14661468 ssh->decryptCipher.cipherType = ID_AES256_GCM;
14671469 ssh->peerEncryptId = ID_AES256_GCM;
14681470 ssh->peerAeadMode = 1;
1469- ssh->peerBlockSz = UINT32_SZ ;
1471+ ssh->peerBlockSz = AES_BLOCK_SIZE ;
14701472 ssh->peerMacSz = AES_BLOCK_SIZE;
14711473 WMEMCPY(ssh->peerKeys.iv, iv, sizeof(iv));
14721474 ssh->peerKeys.ivSz = sizeof(iv);
@@ -1685,11 +1687,11 @@ static int test_DoReceive_RejectsShortPadding(void)
16851687 int ret;
16861688 int result = 0;
16871689 /* A well-formed MSGID_IGNORE packet carrying an empty string, but with
1688- * padding_length = 1 (below MIN_PAD_LENGTH). Aside from the short padding
1689- * the packet parses cleanly, so the padding check is the only thing that
1690- * can reject it. Layout: uint32 packet_length=7 , padding_length=1,
1691- * msgId, uint32 string_len=0, 1 pad byte => 11 bytes total. */
1692- byte pkt[11 ];
1690+ * padding_length = 1 (below MIN_PAD_LENGTH). The 16-byte total keeps the
1691+ * packet block aligned so the padding check is the only thing that can
1692+ * reject it. Layout: uint32 packet_length=12 , padding_length=1, msgId ,
1693+ * uint32 string_len=0, 6 pad bytes => 16 bytes total. */
1694+ byte pkt[16 ];
16931695 word32 totalLen = (word32)sizeof(pkt);
16941696
16951697 ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
@@ -1701,11 +1703,11 @@ static int test_DoReceive_RejectsShortPadding(void)
17011703 return -761;
17021704 }
17031705
1704- pkt[0] = 0; pkt[1] = 0; pkt[2] = 0; pkt[3] = 7; /* packet_length */
1706+ WMEMSET(pkt, 0, sizeof(pkt));
1707+ pkt[3] = 12; /* packet_length */
17051708 pkt[4] = 1; /* padding_length, below MIN_PAD_LENGTH (4) */
17061709 pkt[5] = MSGID_IGNORE;
1707- pkt[6] = 0; pkt[7] = 0; pkt[8] = 0; pkt[9] = 0; /* string_len = 0 */
1708- pkt[10] = 0; /* padding */
1710+ /* string_len = 0, then padding, both zero */
17091711
17101712 ssh->peerEncryptId = ID_NONE;
17111713 ssh->peerAeadMode = 0;
@@ -1743,6 +1745,182 @@ static int test_DoReceive_RejectsShortPadding(void)
17431745 return result;
17441746}
17451747
1748+
1749+ /* Verify DoReceive rejects a cleartext binary packet whose length field is
1750+ * not block aligned. The packet is valid in every other respect, so the
1751+ * RFC 4253 section 6 alignment check is the only thing that can reject it. */
1752+ static int test_DoReceive_RejectsMisalignedPacket(void)
1753+ {
1754+ WOLFSSH_CTX* ctx = NULL;
1755+ WOLFSSH* ssh = NULL;
1756+ int ret;
1757+ int result = 0;
1758+ /* Layout: uint32 packet_length=10, padding_length=4, msgId,
1759+ * uint32 string_len=0, 4 pad bytes => 14 bytes total. 14 is not a
1760+ * multiple of the 8-byte minimum block size. */
1761+ byte pkt[14];
1762+ word32 totalLen = (word32)sizeof(pkt);
1763+
1764+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
1765+ if (ctx == NULL)
1766+ return -770;
1767+ ssh = wolfSSH_new(ctx);
1768+ if (ssh == NULL) {
1769+ wolfSSH_CTX_free(ctx);
1770+ return -771;
1771+ }
1772+
1773+ WMEMSET(pkt, 0, sizeof(pkt));
1774+ pkt[3] = 10; /* packet_length */
1775+ pkt[4] = MIN_PAD_LENGTH;
1776+ pkt[5] = MSGID_IGNORE;
1777+ /* string_len = 0, then padding, both zero */
1778+
1779+ ssh->peerEncryptId = ID_NONE;
1780+ ssh->peerAeadMode = 0;
1781+ ssh->peerBlockSz = MIN_BLOCK_SZ;
1782+ ssh->peerMacId = ID_NONE;
1783+ ssh->peerMacSz = 0;
1784+ ssh->peerSeq = 0;
1785+ ssh->curSz = 0;
1786+ ssh->processReplyState = PROCESS_INIT;
1787+ ssh->error = 0;
1788+
1789+ ShrinkBuffer(&ssh->inputBuffer, 1);
1790+ ret = GrowBuffer(&ssh->inputBuffer, totalLen);
1791+ if (ret != WS_SUCCESS) {
1792+ result = -772;
1793+ goto done3;
1794+ }
1795+ WMEMCPY(ssh->inputBuffer.buffer, pkt, totalLen);
1796+ ssh->inputBuffer.length = totalLen;
1797+ ssh->inputBuffer.idx = 0;
1798+
1799+ ret = wolfSSH_TestDoReceive(ssh);
1800+ if (ret != WS_FATAL_ERROR) {
1801+ result = -773;
1802+ goto done3;
1803+ }
1804+ if (ssh->error != WS_BUFFER_E) {
1805+ result = -774;
1806+ goto done3;
1807+ }
1808+
1809+ done3:
1810+ wolfSSH_free(ssh);
1811+ wolfSSH_CTX_free(ctx);
1812+ return result;
1813+ }
1814+ #endif /* WOLFSSH_TEST_INTERNAL */
1815+
1816+
1817+ #if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_AES_GCM)
1818+ /* Verify DoReceive rejects an AES-GCM record whose length field is not a
1819+ * multiple of the cipher block size. RFC 5647 aligns the body alone, so a
1820+ * 12-byte body is misaligned even though the tag over it is valid. */
1821+ static int test_DoReceive_RejectsMisalignedAead(void)
1822+ {
1823+ WOLFSSH_CTX* ctx = NULL;
1824+ WOLFSSH* ssh = NULL;
1825+ Aes encAes;
1826+ int ret;
1827+ int result = 0;
1828+ int aesInited = 0;
1829+ byte key[AES_256_KEY_SIZE];
1830+ byte iv[GCM_NONCE_MID_SZ];
1831+ /* Layout: uint32 packet_length=12, padding_length=6, msgId,
1832+ * uint32 string_len=0, 6 pad bytes => a 12-byte body, which is not a
1833+ * multiple of AES_BLOCK_SIZE. Valid in every other respect. */
1834+ byte pkt[UINT32_SZ + 12];
1835+ byte record[UINT32_SZ + 12 + AES_BLOCK_SIZE];
1836+ word32 prefixLen, payloadSz, totalLen;
1837+
1838+ ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
1839+ if (ctx == NULL)
1840+ return -780;
1841+ ssh = wolfSSH_new(ctx);
1842+ if (ssh == NULL) {
1843+ wolfSSH_CTX_free(ctx);
1844+ return -781;
1845+ }
1846+
1847+ WMEMSET(key, 0x5A, sizeof(key));
1848+ WMEMSET(iv, 0x31, sizeof(iv));
1849+
1850+ WMEMSET(pkt, 0, sizeof(pkt));
1851+ pkt[3] = 12; /* packet_length */
1852+ pkt[4] = 6; /* padding_length */
1853+ pkt[5] = MSGID_IGNORE;
1854+ /* string_len = 0, then padding, both zero */
1855+ prefixLen = (word32)sizeof(pkt);
1856+ payloadSz = prefixLen - UINT32_SZ;
1857+
1858+ ret = wc_AesInit(&encAes, NULL, INVALID_DEVID);
1859+ if (ret != 0) {
1860+ result = -783;
1861+ goto done4;
1862+ }
1863+ aesInited = 1;
1864+ if (wc_AesGcmSetKey(&encAes, key, sizeof(key)) != 0) {
1865+ result = -784;
1866+ goto done4;
1867+ }
1868+ WMEMCPY(record, pkt, UINT32_SZ);
1869+ if (wc_AesGcmEncrypt(&encAes, record + UINT32_SZ, pkt + UINT32_SZ,
1870+ payloadSz, iv, sizeof(iv), record + UINT32_SZ + payloadSz,
1871+ AES_BLOCK_SIZE, record, UINT32_SZ) != 0) {
1872+ result = -785;
1873+ goto done4;
1874+ }
1875+ totalLen = UINT32_SZ + payloadSz + AES_BLOCK_SIZE;
1876+
1877+ if (wc_AesInit(&ssh->decryptCipher.aes, ssh->ctx->heap, INVALID_DEVID) != 0
1878+ || wc_AesGcmSetKey(&ssh->decryptCipher.aes, key, sizeof(key)) != 0) {
1879+ result = -786;
1880+ goto done4;
1881+ }
1882+ ssh->decryptCipher.isInit = 1;
1883+ ssh->decryptCipher.cipherType = ID_AES256_GCM;
1884+ ssh->peerEncryptId = ID_AES256_GCM;
1885+ ssh->peerAeadMode = 1;
1886+ ssh->peerBlockSz = AES_BLOCK_SIZE;
1887+ ssh->peerMacSz = AES_BLOCK_SIZE;
1888+ WMEMCPY(ssh->peerKeys.iv, iv, sizeof(iv));
1889+ ssh->peerKeys.ivSz = sizeof(iv);
1890+ ssh->curSz = 0;
1891+ ssh->processReplyState = PROCESS_INIT;
1892+ ssh->error = 0;
1893+
1894+ ShrinkBuffer(&ssh->inputBuffer, 1);
1895+ if (GrowBuffer(&ssh->inputBuffer, totalLen) != WS_SUCCESS) {
1896+ result = -787;
1897+ goto done4;
1898+ }
1899+ WMEMCPY(ssh->inputBuffer.buffer, record, totalLen);
1900+ ssh->inputBuffer.length = totalLen;
1901+ ssh->inputBuffer.idx = 0;
1902+
1903+ ret = wolfSSH_TestDoReceive(ssh);
1904+ if (ret != WS_FATAL_ERROR) {
1905+ result = -788;
1906+ goto done4;
1907+ }
1908+ if (ssh->error != WS_BUFFER_E) {
1909+ result = -789;
1910+ goto done4;
1911+ }
1912+
1913+ done4:
1914+ if (aesInited)
1915+ wc_AesFree(&encAes);
1916+ wolfSSH_free(ssh);
1917+ wolfSSH_CTX_free(ctx);
1918+ return result;
1919+ }
1920+ #endif /* WOLFSSH_TEST_INTERNAL && !WOLFSSH_NO_AES_GCM */
1921+
1922+
1923+ #ifdef WOLFSSH_TEST_INTERNAL
17461924/* Send sink, so a test can run a real send path (build the packet, drain it)
17471925 * without a live transport. */
17481926static int UnitIoSendSink(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
@@ -16145,6 +16323,20 @@ int wolfSSH_UnitTest(int argc, char** argv)
1614516323 testResult = testResult || unitResult;
1614616324#endif
1614716325
16326+ #ifdef WOLFSSH_TEST_INTERNAL
16327+ unitResult = test_DoReceive_RejectsMisalignedPacket();
16328+ printf("DoReceiveRejectsMisalignedPacket: %s\n",
16329+ (unitResult == 0 ? "SUCCESS" : "FAILED"));
16330+ testResult = testResult || unitResult;
16331+ #endif
16332+
16333+ #if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_AES_GCM)
16334+ unitResult = test_DoReceive_RejectsMisalignedAead();
16335+ printf("DoReceiveRejectsMisalignedAead: %s\n",
16336+ (unitResult == 0 ? "SUCCESS" : "FAILED"));
16337+ testResult = testResult || unitResult;
16338+ #endif
16339+
1614816340#if defined(WOLFSSH_TEST_INTERNAL) && !defined(WOLFSSH_NO_DH_GEX_SHA256)
1614916341 unitResult = test_DhGexGroupValidate();
1615016342 printf("DhGexGroupValidate: %s\n",
0 commit comments