diff --git a/tests/api/test_aes.c b/tests/api/test_aes.c index 1b2f21ee35f..eed90019623 100644 --- a/tests/api/test_aes.c +++ b/tests/api/test_aes.c @@ -34,6 +34,12 @@ #include #include +#if defined(HAVE_SELFTEST) || (defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION <= 2)) + #define GCM_NONCE_MAX_SZ 16 + #define CCM_NONCE_MAX_SZ 13 +#endif + /******************************************************************************* * AES ******************************************************************************/ @@ -990,6 +996,137 @@ int test_wc_AesCbcEncryptDecrypt(void) return EXPECT_RESULT(); } /* END test_wc_AesCbcEncryptDecrypt */ +/******************************************************************************* + * AES-CBC unaligned buffers + ******************************************************************************/ + +/* + * Verify that wc_AesCbcEncrypt / wc_AesCbcDecrypt produce correct results + * when the input and output buffers are byte-offset (unaligned). Tests + * offsets 1, 2, and 3 to cover all misalignment residues mod 4. + */ +int test_wc_AesCbcEncryptDecrypt_UnalignedBuffers(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128) + Aes aes; + /* NIST SP 800-38A F.2.1 key and IV (AES-128 CBC) */ + static const byte key[AES_128_KEY_SIZE] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + static const byte iv[AES_IV_SIZE] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f + }; + /* Two AES blocks of plaintext */ + static const byte plain[32] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51 + }; + byte ref_ct[sizeof(plain)]; + byte in_buf[sizeof(plain) + 3]; + byte out_buf[sizeof(plain) + 3]; + int off; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + + /* Reference ciphertext with naturally-aligned buffers */ + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), iv, AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCbcEncrypt(&aes, ref_ct, plain, sizeof(plain)), 0); + + /* Encrypt with byte offsets 1, 2, 3 on both in and out */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, plain, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), iv, AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCbcEncrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain)), 0); + ExpectBufEQ(out_buf + off, ref_ct, sizeof(plain)); + } + +#ifdef HAVE_AES_DECRYPT + /* Decrypt with byte offsets 1, 2, 3 on both in and out */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, ref_ct, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), iv, AES_DECRYPTION), 0); + ExpectIntEQ(wc_AesCbcDecrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain)), 0); + ExpectBufEQ(out_buf + off, plain, sizeof(plain)); + } +#endif + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesCbcEncryptDecrypt_UnalignedBuffers */ + +/* + * Cross-cipher test: CBC mode is equivalent to block-by-block ECB encryption + * with XOR chaining. C[i] = ECB_Encrypt(K, P[i] XOR C[i-1]), C[-1] = IV. + * + * This test verifies that relationship directly: encrypt with CBC, then + * independently compute the same ciphertext using ECB + XOR, and compare. + */ +int test_wc_AesCbc_CrossCipher(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(HAVE_AES_ECB) && \ + defined(WOLFSSL_AES_128) + Aes aes; + /* NIST SP 800-38A F.2.1 (first two plaintext blocks) */ + static const byte key[AES_128_KEY_SIZE] = { + 0x2b,0x7e,0x15,0x16, 0x28,0xae,0xd2,0xa6, + 0xab,0xf7,0x15,0x88, 0x09,0xcf,0x4f,0x3c + }; + static const byte iv[WC_AES_BLOCK_SIZE] = { + 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f + }; + static const byte plain[2 * WC_AES_BLOCK_SIZE] = { + 0x6b,0xc1,0xbe,0xe2, 0x2e,0x40,0x9f,0x96, + 0xe9,0x3d,0x7e,0x11, 0x73,0x93,0x17,0x2a, + 0xae,0x2d,0x8a,0x57, 0x1e,0x03,0xac,0x9c, + 0x9e,0xb7,0x6f,0xac, 0x45,0xaf,0x8e,0x51 + }; + byte cbc_ct[sizeof(plain)]; + byte ecb_ct[sizeof(plain)]; + byte xored[WC_AES_BLOCK_SIZE]; + int i; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + + /* CBC ciphertext via the API */ + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), iv, AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCbcEncrypt(&aes, cbc_ct, plain, sizeof(plain)), 0); + + /* Manually compute CBC via ECB + XOR chaining */ + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), NULL, AES_ENCRYPTION), 0); + + /* Block 0: xor plaintext with IV, then ECB-encrypt */ + for (i = 0; i < WC_AES_BLOCK_SIZE; i++) + xored[i] = plain[i] ^ iv[i]; + ExpectIntEQ(wc_AesEcbEncrypt(&aes, ecb_ct, xored, WC_AES_BLOCK_SIZE), 0); + + /* Block 1: xor plaintext with C[0], then ECB-encrypt */ + for (i = 0; i < WC_AES_BLOCK_SIZE; i++) + xored[i] = plain[WC_AES_BLOCK_SIZE + i] ^ ecb_ct[i]; + ExpectIntEQ(wc_AesEcbEncrypt(&aes, ecb_ct + WC_AES_BLOCK_SIZE, xored, + WC_AES_BLOCK_SIZE), 0); + + /* CBC ciphertext must equal the manually-chained ECB ciphertext */ + ExpectBufEQ(cbc_ct, ecb_ct, sizeof(plain)); + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesCbc_CrossCipher */ + /******************************************************************************* * AES-CFB ******************************************************************************/ @@ -1321,7 +1458,76 @@ int test_wc_AesCfbEncryptDecrypt(void) wc_AesFree(&aes); #endif return EXPECT_RESULT(); -} +} /* END test_wc_AesCfbEncryptDecrypt */ + +/* + * Cross-cipher test: CFB128 encrypts by first running ECB on the previous + * ciphertext block (or IV for the first block), then XOR-ing the result with + * the plaintext. + * C[i] = ECB_Encrypt(K, C[i-1]) XOR P[i], C[-1] = IV. + * + * This test verifies that relationship: encrypt with CFB, then independently + * compute the same ciphertext using ECB + feedback, and compare. + */ +int test_wc_AesCfb_CrossCipher(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_CFB) && defined(HAVE_AES_ECB) && \ + defined(WOLFSSL_AES_128) + Aes aes; + /* NIST SP 800-38A F.3.13 (first two plaintext blocks, CFB128) */ + static const byte key[AES_128_KEY_SIZE] = { + 0x2b,0x7e,0x15,0x16, 0x28,0xae,0xd2,0xa6, + 0xab,0xf7,0x15,0x88, 0x09,0xcf,0x4f,0x3c + }; + static const byte iv[WC_AES_BLOCK_SIZE] = { + 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f + }; + static const byte plain[2 * WC_AES_BLOCK_SIZE] = { + 0x6b,0xc1,0xbe,0xe2, 0x2e,0x40,0x9f,0x96, + 0xe9,0x3d,0x7e,0x11, 0x73,0x93,0x17,0x2a, + 0xae,0x2d,0x8a,0x57, 0x1e,0x03,0xac,0x9c, + 0x9e,0xb7,0x6f,0xac, 0x45,0xaf,0x8e,0x51 + }; + byte cfb_ct[sizeof(plain)]; + byte ecb_ct[sizeof(plain)]; + byte ks[WC_AES_BLOCK_SIZE]; + int i; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + + /* CFB ciphertext via the API */ + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), NULL, AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesSetIV(&aes, iv), 0); + ExpectIntEQ(wc_AesCfbEncrypt(&aes, cfb_ct, plain, sizeof(plain)), 0); + + /* Manually compute CFB via ECB + ciphertext feedback */ + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), NULL, AES_ENCRYPTION), 0); + + /* Block 0: encrypt IV to get keystream, then XOR with plaintext */ + ExpectIntEQ(wc_AesEcbEncrypt(&aes, ks, iv, WC_AES_BLOCK_SIZE), 0); + if (EXPECT_SUCCESS()) { + for (i = 0; i < WC_AES_BLOCK_SIZE; i++) + ecb_ct[i] = plain[i] ^ ks[i]; + } + + /* Block 1: encrypt C[0] to get keystream, then XOR with plaintext */ + ExpectIntEQ(wc_AesEcbEncrypt(&aes, ks, ecb_ct, WC_AES_BLOCK_SIZE), 0); + if (EXPECT_SUCCESS()) { + for (i = 0; i < WC_AES_BLOCK_SIZE; i++) + ecb_ct[WC_AES_BLOCK_SIZE + i] = plain[WC_AES_BLOCK_SIZE + i] ^ + ks[i]; + } + + /* CFB ciphertext must equal the manually computed ECB+feedback ciphertext */ + ExpectBufEQ(cfb_ct, ecb_ct, sizeof(plain)); + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesCfb_CrossCipher */ /******************************************************************************* * AES-OFB @@ -1639,7 +1845,77 @@ int test_wc_AesOfbEncryptDecrypt(void) wc_AesFree(&aes); #endif return EXPECT_RESULT(); -} +} /* END test_wc_AesOfbEncryptDecrypt */ + +/* + * Cross-cipher test: OFB mode generates a keystream by repeatedly ECB- + * encrypting the previous output block, starting from the IV. + * O[0] = ECB_Encrypt(K, IV); C[0] = P[0] XOR O[0] + * O[1] = ECB_Encrypt(K, O[0]); C[1] = P[1] XOR O[1] + * + * Unlike CFB, the feedback is taken from the keystream output, not the + * ciphertext, making OFB a synchronous stream cipher. + */ +int test_wc_AesOfb_CrossCipher(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_OFB) && defined(HAVE_AES_ECB) && \ + defined(WOLFSSL_AES_128) + Aes aes; + /* NIST SP 800-38A F.4.1 (first two plaintext blocks, OFB) */ + static const byte key[AES_128_KEY_SIZE] = { + 0x2b,0x7e,0x15,0x16, 0x28,0xae,0xd2,0xa6, + 0xab,0xf7,0x15,0x88, 0x09,0xcf,0x4f,0x3c + }; + static const byte iv[WC_AES_BLOCK_SIZE] = { + 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f + }; + static const byte plain[2 * WC_AES_BLOCK_SIZE] = { + 0x6b,0xc1,0xbe,0xe2, 0x2e,0x40,0x9f,0x96, + 0xe9,0x3d,0x7e,0x11, 0x73,0x93,0x17,0x2a, + 0xae,0x2d,0x8a,0x57, 0x1e,0x03,0xac,0x9c, + 0x9e,0xb7,0x6f,0xac, 0x45,0xaf,0x8e,0x51 + }; + byte ofb_ct[sizeof(plain)]; + byte ecb_ct[sizeof(plain)]; + byte o0[WC_AES_BLOCK_SIZE]; /* output-feedback block 0 */ + byte o1[WC_AES_BLOCK_SIZE]; /* output-feedback block 1 */ + int i; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + + /* OFB ciphertext via the API */ + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), NULL, AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesSetIV(&aes, iv), 0); + ExpectIntEQ(wc_AesOfbEncrypt(&aes, ofb_ct, plain, sizeof(plain)), 0); + + /* Manually compute OFB via ECB + output feedback */ + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), NULL, AES_ENCRYPTION), 0); + + /* O[0] = ECB_E(K, IV); C[0] = P[0] XOR O[0] */ + ExpectIntEQ(wc_AesEcbEncrypt(&aes, o0, iv, WC_AES_BLOCK_SIZE), 0); + if (EXPECT_SUCCESS()) { + for (i = 0; i < WC_AES_BLOCK_SIZE; i++) + ecb_ct[i] = plain[i] ^ o0[i]; + } + + /* O[1] = ECB_E(K, O[0]); C[1] = P[1] XOR O[1] */ + ExpectIntEQ(wc_AesEcbEncrypt(&aes, o1, o0, WC_AES_BLOCK_SIZE), 0); + if (EXPECT_SUCCESS()) { + for (i = 0; i < WC_AES_BLOCK_SIZE; i++) + ecb_ct[WC_AES_BLOCK_SIZE + i] = plain[WC_AES_BLOCK_SIZE + i] ^ + o1[i]; + } + + /* OFB ciphertext must equal the manually computed ECB+output-feedback */ + ExpectBufEQ(ofb_ct, ecb_ct, sizeof(plain)); + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesOfb_CrossCipher */ /******************************************************************************* * AES-CTS @@ -1865,6 +2141,121 @@ int test_wc_AesCtsEncryptDecrypt(void) return EXPECT_RESULT(); } +/******************************************************************************* + * AES-CTS overlapping (in-place) buffers + ******************************************************************************/ + +/* + * Verify that wc_AesCtsEncrypt / wc_AesCtsDecrypt correctly handle an + * in-place call (out == in). RFC 3962 Appendix B test vector 5 (48 bytes, + * three full AES blocks) is used because the CTS one-shot API buffers input + * internally before writing output, so it is safe for in-place use. + */ +int test_wc_AesCtsEncryptDecrypt_InPlace(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_CTS) && \ + defined(HAVE_AES_DECRYPT) && defined(WOLFSSL_AES_128) + static const byte key[AES_128_KEY_SIZE] = { + 0x63, 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x20, + 0x74, 0x65, 0x72, 0x69, 0x79, 0x61, 0x6b, 0x69 + }; + /* RFC 3962 plaintext vector 5 (48 bytes): + * "I would like the General Gau's Chicken, please, " */ + static const byte plain[48] = { + 0x49, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, + 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x47, 0x61, 0x75, 0x27, 0x73, 0x20, 0x43, + 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x2c, 0x20, + 0x70, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2c, 0x20 + }; + byte iv[AES_IV_SIZE]; + byte ref_ct[sizeof(plain)]; + byte buf[sizeof(plain)]; + + /* Reference ciphertext with separate in/out buffers */ + XMEMSET(iv, 0, sizeof(iv)); + ExpectIntEQ(wc_AesCtsEncrypt(key, sizeof(key), ref_ct, plain, + sizeof(plain), iv), 0); + + /* Encrypt in-place (out == in) - must produce the same ciphertext */ + XMEMSET(iv, 0, sizeof(iv)); + XMEMCPY(buf, plain, sizeof(buf)); + ExpectIntEQ(wc_AesCtsEncrypt(key, sizeof(key), buf, buf, + sizeof(buf), iv), 0); + ExpectBufEQ(buf, ref_ct, sizeof(buf)); + + /* Decrypt in-place - must recover original plaintext */ + XMEMSET(iv, 0, sizeof(iv)); + ExpectIntEQ(wc_AesCtsDecrypt(key, sizeof(key), buf, buf, + sizeof(buf), iv), 0); + ExpectBufEQ(buf, plain, sizeof(buf)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesCtsEncryptDecrypt_InPlace */ + +/******************************************************************************* + * AES-CTS unaligned buffers + ******************************************************************************/ + +/* + * Verify that wc_AesCtsEncrypt / wc_AesCtsDecrypt produce correct results + * when the input and output buffers are byte-offset (unaligned). Tests + * offsets 1, 2, and 3 to cover all misalignment residues mod 4. + */ +int test_wc_AesCtsEncryptDecrypt_UnalignedBuffers(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_CTS) && \ + defined(HAVE_AES_DECRYPT) && defined(WOLFSSL_AES_128) + /* RFC 3962 Appendix B test vector 5 - same as InPlace test */ + static const byte key[AES_128_KEY_SIZE] = { + 0x63, 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x20, + 0x74, 0x65, 0x72, 0x69, 0x79, 0x61, 0x6b, 0x69 + }; + static const byte plain[48] = { + 0x49, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, + 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, + 0x20, 0x47, 0x61, 0x75, 0x27, 0x73, 0x20, 0x43, + 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x2c, 0x20, + 0x70, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2c, 0x20 + }; + byte iv[AES_IV_SIZE]; + byte ref_ct[sizeof(plain)]; + byte in_buf[sizeof(plain) + 3]; + byte out_buf[sizeof(plain) + 3]; + int off; + + /* Reference ciphertext with naturally-aligned buffers */ + XMEMSET(iv, 0, sizeof(iv)); + ExpectIntEQ(wc_AesCtsEncrypt(key, sizeof(key), ref_ct, plain, + sizeof(plain), iv), 0); + + /* Encrypt with byte offsets 1, 2, 3 on both in and out */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMSET(iv, 0, sizeof(iv)); + XMEMCPY(in_buf + off, plain, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesCtsEncrypt(key, sizeof(key), out_buf + off, + in_buf + off, sizeof(plain), iv), 0); + ExpectBufEQ(out_buf + off, ref_ct, sizeof(plain)); + } + + /* Decrypt with byte offsets 1, 2, 3 on both in and out */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMSET(iv, 0, sizeof(iv)); + XMEMCPY(in_buf + off, ref_ct, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesCtsDecrypt(key, sizeof(key), out_buf + off, + in_buf + off, sizeof(plain), iv), 0); + ExpectBufEQ(out_buf + off, plain, sizeof(plain)); + } +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesCtsEncryptDecrypt_UnalignedBuffers */ + /******************************************************************************* * AES-CTR ******************************************************************************/ @@ -2167,6 +2558,113 @@ static int test_wc_AesCtrEncrypt_SameBuffer(Aes* aes, byte* key, #endif #endif +/******************************************************************************* + * AES-CTR counter overflow + ******************************************************************************/ + +/* + * Verify that AES-CTR counter carry-propagation works across byte boundaries + * when the counter wraps around. We encrypt three blocks starting from a + * near-overflow IV (last four bytes = 0xFF,0xFF,0xFF,0xFE) in a single call, + * then re-encrypt each block individually with the expected IV value for that + * block position, and confirm the outputs match. + * + * block 0 IV: ...0xFF,0xFF,0xFF,0xFE + * block 1 IV: ...0xFF,0xFF,0xFF,0xFF + * block 2 IV: ...0x01,0x00,0x00,0x00,0x00 (carry propagated through four FFs) + */ +int test_wc_AesCtrCounterOverflow(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_COUNTER) && \ + defined(WOLFSSL_AES_128) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) && \ + !defined(HAVE_SELFTEST) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI) + Aes enc; + /* IV with last four bytes = 0xFF,0xFF,0xFF,0xFE (one before two-step + * overflow: 0xFE->0xFF is a normal increment; 0xFF->0x00 carries through + * all four bytes into byte[11]). */ + static const byte iv_start[WC_AES_BLOCK_SIZE] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0xFF,0xFF,0xFF,0xFE + }; + /* Expected IV for block 1: last byte incremented 0xFE->0xFF */ + static const byte iv_b1[WC_AES_BLOCK_SIZE] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0xFF,0xFF,0xFF,0xFF + }; + /* Expected IV for block 2: carry propagates all four 0xFF bytes -> + * byte[11] increments 0x00->0x01, bytes[12..15] all become 0x00. */ + static const byte iv_b2[WC_AES_BLOCK_SIZE] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01, 0x00,0x00,0x00,0x00 + }; + static const byte key[16] = { + 0x2b,0x7e,0x15,0x16, 0x28,0xae,0xd2,0xa6, + 0xab,0xf7,0x15,0x88, 0x09,0xcf,0x4f,0x3c + }; + /* Three blocks of all-zero plaintext - simplifies comparison. */ + static const byte plain[3 * WC_AES_BLOCK_SIZE] = { 0 }; + + byte cipher_combined[3 * WC_AES_BLOCK_SIZE]; + byte cipher_b0[WC_AES_BLOCK_SIZE]; + byte cipher_b1[WC_AES_BLOCK_SIZE]; + byte cipher_b2[WC_AES_BLOCK_SIZE]; + byte decrypted[3 * WC_AES_BLOCK_SIZE]; + + XMEMSET(&enc, 0, sizeof(enc)); + ExpectIntEQ(wc_AesInit(&enc, NULL, INVALID_DEVID), 0); + + /* Encrypt three blocks in one call, spanning the carry-propagation + * boundary. */ + ExpectIntEQ(wc_AesCtrSetKey(&enc, key, sizeof(key), iv_start, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&enc, cipher_combined, plain, + sizeof(plain)), 0); + + /* Block 0: starts at iv_start. */ + ExpectIntEQ(wc_AesCtrSetKey(&enc, key, sizeof(key), iv_start, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&enc, cipher_b0, plain, + WC_AES_BLOCK_SIZE), 0); + + /* Block 1: counter incremented once (0xFFFFFFFE -> 0xFFFFFFFF). */ + ExpectIntEQ(wc_AesCtrSetKey(&enc, key, sizeof(key), iv_b1, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&enc, cipher_b1, plain + WC_AES_BLOCK_SIZE, + WC_AES_BLOCK_SIZE), 0); + + /* Block 2: counter wrapped (0xFFFFFFFF -> 0x00000000 with carry into + * the next byte group). */ + ExpectIntEQ(wc_AesCtrSetKey(&enc, key, sizeof(key), iv_b2, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&enc, cipher_b2, + plain + 2 * WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE), 0); + + /* Combined output must match per-block results. */ + ExpectBufEQ(cipher_combined, cipher_b0, WC_AES_BLOCK_SIZE); + ExpectBufEQ(cipher_combined + WC_AES_BLOCK_SIZE, cipher_b1, + WC_AES_BLOCK_SIZE); + ExpectBufEQ(cipher_combined + 2 * WC_AES_BLOCK_SIZE, cipher_b2, + WC_AES_BLOCK_SIZE); + + /* Blocks 1 and 2 must differ - different counter values produce different + * key-stream blocks. */ + ExpectIntNE(XMEMCMP(cipher_b1, cipher_b2, WC_AES_BLOCK_SIZE), 0); + + /* Decrypt round-trip. */ + ExpectIntEQ(wc_AesCtrSetKey(&enc, key, sizeof(key), iv_start, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&enc, decrypted, cipher_combined, + sizeof(cipher_combined)), 0); + ExpectBufEQ(decrypted, plain, sizeof(plain)); + + wc_AesFree(&enc); +#endif + return EXPECT_RESULT(); +} + /* * Testing wc_AesCtrEncrypt * Decrypt is an encrypt. @@ -2373,6 +2871,153 @@ int test_wc_AesCtrEncryptDecrypt(void) return EXPECT_RESULT(); } /* END test_wc_AesCtrEncryptDecrypt */ +/******************************************************************************* + * AES-CTR unaligned buffers + ******************************************************************************/ + +/* + * Verify that wc_AesCtrEncrypt produces correct results when the input and + * output buffers are byte-offset (unaligned). Tests offsets 1, 2, and 3. + * A 35-byte plaintext is used to exercise both the full-block path and the + * partial-block leftover (35 = 2*16 + 3). + */ +int test_wc_AesCtrEncryptDecrypt_UnalignedBuffers(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_COUNTER) && \ + defined(WOLFSSL_AES_128) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) && \ + !defined(HAVE_SELFTEST) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI) + Aes aes; + static const byte key[AES_128_KEY_SIZE] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + static const byte iv[AES_IV_SIZE] = { + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + /* 35 bytes: two full blocks + 3-byte tail */ + static const byte plain[35] = { + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c + }; + byte ref_ct[sizeof(plain)]; + byte in_buf[sizeof(plain) + 3]; + byte out_buf[sizeof(plain) + 3]; + int off; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + + /* Reference ciphertext with naturally-aligned buffers */ + ExpectIntEQ(wc_AesCtrSetKey(&aes, key, sizeof(key), iv, AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, ref_ct, plain, sizeof(plain)), 0); + + /* Encrypt with byte offsets 1, 2, 3 on both in and out */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, plain, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesCtrSetKey(&aes, key, sizeof(key), iv, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain)), 0); + ExpectBufEQ(out_buf + off, ref_ct, sizeof(plain)); + } + + /* Decrypt (CTR is symmetric: encrypt again to recover plaintext) */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, ref_ct, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesCtrSetKey(&aes, key, sizeof(key), iv, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain)), 0); + ExpectBufEQ(out_buf + off, plain, sizeof(plain)); + } + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesCtrEncryptDecrypt_UnalignedBuffers */ + +/* + * Cross-cipher test: CTR mode generates a keystream by ECB-encrypting the + * counter block. The counter starts at the IV value and increments as a + * 128-bit big-endian integer after each block. + * KS[i] = ECB_Encrypt(K, counter[i]); C[i] = P[i] XOR KS[i] + * + * This test verifies that relationship: encrypt with CTR, then independently + * compute the same ciphertext using ECB + counter increment, and compare. + */ +int test_wc_AesCtr_CrossCipher(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_COUNTER) && defined(HAVE_AES_ECB) && \ + defined(WOLFSSL_AES_128) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) && \ + !defined(HAVE_SELFTEST) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI) + Aes aes; + /* NIST SP 800-38A F.5.1 (first two plaintext blocks, CTR) */ + static const byte key[AES_128_KEY_SIZE] = { + 0x2b,0x7e,0x15,0x16, 0x28,0xae,0xd2,0xa6, + 0xab,0xf7,0x15,0x88, 0x09,0xcf,0x4f,0x3c + }; + static const byte iv[WC_AES_BLOCK_SIZE] = { + 0xf0,0xf1,0xf2,0xf3, 0xf4,0xf5,0xf6,0xf7, + 0xf8,0xf9,0xfa,0xfb, 0xfc,0xfd,0xfe,0xff + }; + static const byte plain[2 * WC_AES_BLOCK_SIZE] = { + 0x6b,0xc1,0xbe,0xe2, 0x2e,0x40,0x9f,0x96, + 0xe9,0x3d,0x7e,0x11, 0x73,0x93,0x17,0x2a, + 0xae,0x2d,0x8a,0x57, 0x1e,0x03,0xac,0x9c, + 0x9e,0xb7,0x6f,0xac, 0x45,0xaf,0x8e,0x51 + }; + byte ctr_ct[sizeof(plain)]; + byte ecb_ct[sizeof(plain)]; + byte counter[WC_AES_BLOCK_SIZE]; + byte ks[WC_AES_BLOCK_SIZE]; + int i, j; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + + /* CTR ciphertext via the API */ + ExpectIntEQ(wc_AesCtrSetKey(&aes, key, sizeof(key), iv, AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, ctr_ct, plain, sizeof(plain)), 0); + + /* Manually compute CTR via ECB + big-endian counter increment */ + ExpectIntEQ(wc_AesSetKey(&aes, key, sizeof(key), NULL, AES_ENCRYPTION), 0); + XMEMCPY(counter, iv, WC_AES_BLOCK_SIZE); + + for (i = 0; i < 2; i++) { + /* KS[i] = ECB_E(K, counter[i]) */ + ExpectIntEQ(wc_AesEcbEncrypt(&aes, ks, counter, WC_AES_BLOCK_SIZE), 0); + if (EXPECT_SUCCESS()) { + /* C[i] = P[i] XOR KS[i] */ + for (j = 0; j < WC_AES_BLOCK_SIZE; j++) + ecb_ct[i * WC_AES_BLOCK_SIZE + j] = + plain[i * WC_AES_BLOCK_SIZE + j] ^ ks[j]; + /* Increment 128-bit counter big-endian (carry from last byte + * upward) */ + for (j = WC_AES_BLOCK_SIZE - 1; j >= 0 && (++counter[j]) == 0; j--) + ; + } + } + + /* CTR ciphertext must equal the manually computed ECB+counter ciphertext */ + ExpectBufEQ(ctr_ct, ecb_ct, sizeof(plain)); + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesCtr_CrossCipher */ + /******************************************************************************* * AES-GCM ******************************************************************************/ @@ -2770,67 +3415,551 @@ int test_wc_AesGcmEncryptDecrypt(void) } /* END test_wc_AesGcmEncryptDecrypt */ +/******************************************************************************* + * AES-GCM overlapping (in-place) buffers + ******************************************************************************/ + /* - * test function for mixed (one-shot encryption + stream decryption) AES GCM - * using a long IV (older FIPS does NOT support long IVs). Relates to zd15423 + * Verify that wc_AesGcmEncrypt / wc_AesGcmDecrypt work correctly when the + * plaintext/ciphertext pointer is the same buffer (in == out). AES-GCM uses + * CTR mode for encryption (XOR keystream), so in-place operation is safe. + * The auth tag is always a separate buffer, so it is not affected. + * + * McGrew & Viega Test Case 4 (AES-128) is used for the key and IV; a 24-byte + * slice of the test-case plaintext provides a non-block-aligned length. */ -int test_wc_AesGcmMixedEncDecLongIV(void) +int test_wc_AesGcmEncryptDecrypt_InPlace(void) { EXPECT_DECLS; -#if (!defined(HAVE_FIPS) || \ - (defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION >= 2))) && \ - !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_256) && \ - defined(WOLFSSL_AESGCM_STREAM) - const byte key[] = { - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 +#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_128) && \ + !defined(WOLFSSL_AFALG) && !defined(WOLFSSL_DEVCRYPTO_AES) + Aes aes; + static const byte key[AES_128_KEY_SIZE] = { + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 }; - const byte in[] = { - 0x4e,0x6f,0x77,0x20,0x69,0x73,0x20,0x74, - 0x68,0x65,0x20,0x74,0x69,0x6d,0x65,0x20, - 0x66,0x6f,0x72,0x20,0x61,0x6c,0x6c,0x20 + static const byte iv[GCM_NONCE_MID_SZ] = { + 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, + 0xde, 0xca, 0xf8, 0x88 }; - const byte aad[] = { + static const byte aad[20] = { 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xab, 0xad, 0xda, 0xd2 }; - Aes aesEnc; - Aes aesDec; - byte iv[] = "1234567890abcdefghij"; - byte out[sizeof(in)]; - byte plain[sizeof(in)]; - byte tag[WC_AES_BLOCK_SIZE]; + static const byte plain[24] = { + 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, + 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, + 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda + }; + byte ref_ct[sizeof(plain)], ref_tag[WC_AES_BLOCK_SIZE]; + byte buf[sizeof(plain)], tag[WC_AES_BLOCK_SIZE]; - XMEMSET(&aesEnc, 0, sizeof(Aes)); - XMEMSET(&aesDec, 0, sizeof(Aes)); - XMEMSET(out, 0, sizeof(out)); - XMEMSET(plain, 0, sizeof(plain)); - XMEMSET(tag, 0, sizeof(tag)); + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&aes, key, sizeof(key)), 0); - /* Perform one-shot encryption using long IV */ - ExpectIntEQ(wc_AesInit(&aesEnc, NULL, INVALID_DEVID), 0); - ExpectIntEQ(wc_AesGcmSetKey(&aesEnc, key, sizeof(key)), 0); - ExpectIntEQ(wc_AesGcmEncrypt(&aesEnc, out, in, sizeof(in), iv, sizeof(iv), - tag, sizeof(tag), aad, sizeof(aad)), 0); + /* Reference ciphertext with separate in/out buffers */ + XMEMSET(ref_ct, 0, sizeof(ref_ct)); + XMEMSET(ref_tag, 0, sizeof(ref_tag)); + ExpectIntEQ(wc_AesGcmEncrypt(&aes, ref_ct, plain, sizeof(plain), + iv, sizeof(iv), ref_tag, sizeof(ref_tag), aad, sizeof(aad)), 0); - /* Perform streaming decryption using long IV */ - ExpectIntEQ(wc_AesInit(&aesDec, NULL, INVALID_DEVID), 0); - ExpectIntEQ(wc_AesGcmInit(&aesDec, key, sizeof(key), iv, sizeof(iv)), 0); - ExpectIntEQ(wc_AesGcmDecryptUpdate(&aesDec, plain, out, sizeof(out), aad, - sizeof(aad)), 0); - ExpectIntEQ(wc_AesGcmDecryptFinal(&aesDec, tag, sizeof(tag)), 0); - ExpectIntEQ(XMEMCMP(plain, in, sizeof(in)), 0); + /* Encrypt in-place (out == in) - must produce the same ciphertext/tag */ + XMEMSET(tag, 0, sizeof(tag)); + XMEMCPY(buf, plain, sizeof(buf)); + ExpectIntEQ(wc_AesGcmEncrypt(&aes, buf, buf, sizeof(buf), + iv, sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)), 0); + ExpectBufEQ(buf, ref_ct, sizeof(buf)); + ExpectBufEQ(tag, ref_tag, sizeof(tag)); - /* Free resources */ - wc_AesFree(&aesEnc); - wc_AesFree(&aesDec); +#ifdef HAVE_AES_DECRYPT + /* Decrypt in-place - must recover original plaintext */ + ExpectIntEQ(wc_AesGcmDecrypt(&aes, buf, buf, sizeof(buf), + iv, sizeof(iv), tag, sizeof(tag), aad, sizeof(aad)), 0); + ExpectBufEQ(buf, plain, sizeof(buf)); +#endif + + wc_AesFree(&aes); #endif return EXPECT_RESULT(); +} /* END test_wc_AesGcmEncryptDecrypt_InPlace */ -} /* END wc_AesGcmMixedEncDecLongIV */ +/******************************************************************************* + * AES-GCM unaligned buffers + ******************************************************************************/ + +/* + * Verify that wc_AesGcmEncrypt / wc_AesGcmDecrypt produce correct results + * when plaintext, ciphertext, and AAD buffers are byte-offset (unaligned). + * Tests offsets 1, 2, and 3. Exercises the GHASH path as well as the CTR + * encryption, both of which may use SIMD intrinsics sensitive to alignment. + */ +int test_wc_AesGcmEncryptDecrypt_UnalignedBuffers(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_128) && \ + !defined(WOLFSSL_AFALG) && !defined(WOLFSSL_DEVCRYPTO_AES) + Aes aes; + /* Same key / IV / AAD as InPlace test (McGrew TC4, AES-128) */ + static const byte key[AES_128_KEY_SIZE] = { + 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, + 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 + }; + static const byte iv[GCM_NONCE_MID_SZ] = { + 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, + 0xde, 0xca, 0xf8, 0x88 + }; + static const byte aad[20] = { + 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xab, 0xad, 0xda, 0xd2 + }; + static const byte plain[24] = { + 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, + 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, + 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda + }; + byte ref_ct[sizeof(plain)], ref_tag[WC_AES_BLOCK_SIZE]; + byte in_buf[sizeof(plain) + 3], out_buf[sizeof(plain) + 3]; + byte aad_buf[sizeof(aad) + 3]; + byte tag[WC_AES_BLOCK_SIZE]; + int off; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&aes, key, sizeof(key)), 0); + + /* Reference ciphertext/tag with naturally-aligned buffers */ + XMEMSET(ref_ct, 0, sizeof(ref_ct)); + XMEMSET(ref_tag, 0, sizeof(ref_tag)); + ExpectIntEQ(wc_AesGcmEncrypt(&aes, ref_ct, plain, sizeof(plain), + iv, sizeof(iv), ref_tag, sizeof(ref_tag), aad, sizeof(aad)), 0); + + /* Encrypt with byte offsets 1, 2, 3 on plaintext, ciphertext, and AAD */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, plain, sizeof(plain)); + XMEMCPY(aad_buf + off, aad, sizeof(aad)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + XMEMSET(tag, 0, sizeof(tag)); + ExpectIntEQ(wc_AesGcmEncrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain), iv, sizeof(iv), tag, sizeof(tag), + aad_buf + off, sizeof(aad)), 0); + ExpectBufEQ(out_buf + off, ref_ct, sizeof(plain)); + ExpectBufEQ(tag, ref_tag, sizeof(tag)); + } + +#ifdef HAVE_AES_DECRYPT + /* Decrypt with byte offsets 1, 2, 3 */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, ref_ct, sizeof(plain)); + XMEMCPY(aad_buf + off, aad, sizeof(aad)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesGcmDecrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain), iv, sizeof(iv), ref_tag, sizeof(ref_tag), + aad_buf + off, sizeof(aad)), 0); + ExpectBufEQ(out_buf + off, plain, sizeof(plain)); + } +#endif + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesGcmEncryptDecrypt_UnalignedBuffers */ + +/* + * Cross-cipher test: AES-GCM encrypts plaintext using AES-CTR starting at the + * counter block J0+1. For a 12-byte nonce, J0 = nonce || 0x00000001, so the + * first counter block used for data is nonce || 0x00000002. + * + * This test verifies that the ciphertext portion of a GCM encrypt equals the + * output of AES-CTR with the initial counter set to nonce || 0x00000002. + */ +int test_wc_AesGcm_CrossCipher(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_COUNTER) && \ + defined(WOLFSSL_AES_128) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_DEVCRYPTO_AES) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION_GE(7,0)) && \ + !defined(HAVE_SELFTEST) && !defined(WOLFSSL_KCAPI) + Aes aes; + /* McGrew/Viega GCM test case 4 (128-bit key, 12-byte nonce) */ + static const byte key[AES_128_KEY_SIZE] = { + 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, + 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08 + }; + static const byte nonce[GCM_NONCE_MID_SZ] = { + 0xca,0xfe,0xba,0xbe, 0xfa,0xce,0xdb,0xad, + 0xde,0xca,0xf8,0x88 + }; + static const byte aad[20] = { + 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, + 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, + 0xab,0xad,0xda,0xd2 + }; + static const byte plain[24] = { + 0xd9,0x31,0x32,0x25, 0xf8,0x84,0x06,0xe5, + 0xa5,0x59,0x09,0xc5, 0xaf,0xf5,0x26,0x9a, + 0x86,0xa7,0xa9,0x53, 0x15,0x34,0xf7,0xda + }; + /* CTR initial counter = nonce || 0x00000002 (GCM's J0+1) */ + byte ctr_iv[WC_AES_BLOCK_SIZE]; + byte gcm_ct[sizeof(plain)], gcm_tag[WC_AES_BLOCK_SIZE]; + byte ctr_ct[sizeof(plain)]; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + + /* GCM ciphertext */ + ExpectIntEQ(wc_AesGcmSetKey(&aes, key, sizeof(key)), 0); + ExpectIntEQ(wc_AesGcmEncrypt(&aes, gcm_ct, plain, sizeof(plain), + nonce, sizeof(nonce), gcm_tag, sizeof(gcm_tag), aad, sizeof(aad)), 0); + + /* CTR ciphertext starting at J0+1: nonce || 0x00000002 */ + XMEMCPY(ctr_iv, nonce, sizeof(nonce)); + ctr_iv[12] = 0x00; ctr_iv[13] = 0x00; ctr_iv[14] = 0x00; ctr_iv[15] = 0x02; + ExpectIntEQ(wc_AesCtrSetKey(&aes, key, sizeof(key), ctr_iv, + AES_ENCRYPTION), 0); + ExpectIntEQ(wc_AesCtrEncrypt(&aes, ctr_ct, plain, sizeof(plain)), 0); + + /* GCM ciphertext portion must equal the CTR ciphertext */ + ExpectBufEQ(gcm_ct, ctr_ct, sizeof(plain)); + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesGcm_CrossCipher */ + +/* + * test function for mixed (one-shot encryption + stream decryption) AES GCM + * using a long IV (older FIPS does NOT support long IVs). Relates to zd15423 + */ +int test_wc_AesGcmMixedEncDecLongIV(void) +{ + EXPECT_DECLS; +#if (!defined(HAVE_FIPS) || \ + (defined(HAVE_FIPS_VERSION) && (HAVE_FIPS_VERSION >= 2))) && \ + !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_256) && \ + defined(WOLFSSL_AESGCM_STREAM) + const byte key[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 + }; + const byte in[] = { + 0x4e,0x6f,0x77,0x20,0x69,0x73,0x20,0x74, + 0x68,0x65,0x20,0x74,0x69,0x6d,0x65,0x20, + 0x66,0x6f,0x72,0x20,0x61,0x6c,0x6c,0x20 + }; + const byte aad[] = { + 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, + 0xab, 0xad, 0xda, 0xd2 + }; + Aes aesEnc; + Aes aesDec; + byte iv[] = "1234567890abcdefghij"; + byte out[sizeof(in)]; + byte plain[sizeof(in)]; + byte tag[WC_AES_BLOCK_SIZE]; + + XMEMSET(&aesEnc, 0, sizeof(Aes)); + XMEMSET(&aesDec, 0, sizeof(Aes)); + XMEMSET(out, 0, sizeof(out)); + XMEMSET(plain, 0, sizeof(plain)); + XMEMSET(tag, 0, sizeof(tag)); + + /* Perform one-shot encryption using long IV */ + ExpectIntEQ(wc_AesInit(&aesEnc, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&aesEnc, key, sizeof(key)), 0); + ExpectIntEQ(wc_AesGcmEncrypt(&aesEnc, out, in, sizeof(in), iv, sizeof(iv), + tag, sizeof(tag), aad, sizeof(aad)), 0); + + /* Perform streaming decryption using long IV */ + ExpectIntEQ(wc_AesInit(&aesDec, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmInit(&aesDec, key, sizeof(key), iv, sizeof(iv)), 0); + ExpectIntEQ(wc_AesGcmDecryptUpdate(&aesDec, plain, out, sizeof(out), aad, + sizeof(aad)), 0); + ExpectIntEQ(wc_AesGcmDecryptFinal(&aesDec, tag, sizeof(tag)), 0); + ExpectIntEQ(XMEMCMP(plain, in, sizeof(in)), 0); + + /* Free resources */ + wc_AesFree(&aesEnc); + wc_AesFree(&aesDec); +#endif + return EXPECT_RESULT(); + +} /* END wc_AesGcmMixedEncDecLongIV */ + +/******************************************************************************* + * AES-GCM non-standard nonce lengths + ******************************************************************************/ + +/* + * Non-standard (non-96-bit) nonce tests for AES-GCM. + * + * NIST SP 800-38D requires a different counter-derivation path when + * len(IV) != 96 bits (12 bytes): J0 = GHASH_H(IV || pad || len64(IV)). + * Most hardware accelerators only support the 12-byte fast path, so these + * tests are skipped on FIPS builds and hardware-only backends. + * + * Three sections: + * 1. 1-byte IV - FIPS CAVS example vector (AES-128). + * 2. 60-byte IV - McGrew & Viega Test Case 12 (AES-192). + * 3. Variable IV length loop (1..GCM_NONCE_MAX_SZ, AES-128): roundtrip and + * uniqueness - each distinct IV length must produce distinct ciphertext. + * 4. Zero-length IV must be rejected with an error. + */ +int test_wc_AesGcmNonStdNonce(void) +{ + EXPECT_DECLS; +/* Hardware accelerators and FIPS mode only support the 12-byte IV fast path + * and cannot exercise the GHASH-based counter derivation. */ +#if !defined(NO_AES) && defined(HAVE_AESGCM) && \ + !defined(HAVE_FIPS) && \ + !defined(WOLFSSL_AFALG) && !defined(WOLFSSL_KCAPI) + + /* ------------------------------------------------------------------ + * Section 1: 1-byte IV, AES-128 + * Key, IV, plaintext, AAD, ciphertext, and tag are taken directly from + * the FIPS CAVS non-96-bit-IV example vectors, also present in + * wolfcrypt/test/test.c (variable k3/iv3/p3/a3/c3/t3). + * ------------------------------------------------------------------ */ +#ifdef WOLFSSL_AES_128 + { + static const byte key_1b[AES_128_KEY_SIZE] = { + 0xbb,0x01,0xd7,0x03, 0x81,0x1c,0x10,0x1a, + 0x35,0xe0,0xff,0xd2, 0x91,0xba,0xf2,0x4b + }; + static const byte iv_1b[1] = { 0xca }; + static const byte pt_1b[AES_128_KEY_SIZE] = { + 0x57,0xce,0x45,0x1f, 0xa5,0xe2,0x35,0xa5, + 0x8e,0x1a,0xa2,0x3b, 0x77,0xcb,0xaf,0xe2 + }; + static const byte aad_1b[AES_128_KEY_SIZE] = { + 0x40,0xfc,0xdc,0xd7, 0x4a,0xd7,0x8b,0xf1, + 0x3e,0x7c,0x60,0x55, 0x50,0x51,0xdd,0x54 + }; + static const byte expCt_1b[AES_128_KEY_SIZE] = { + 0x6b,0x5f,0xb3,0x9d, 0xc1,0xc5,0x7a,0x4f, + 0xf3,0x51,0x4d,0xc2, 0xd5,0xf0,0xd0,0x07 + }; + static const byte expTag_1b[WC_AES_BLOCK_SIZE] = { + 0x06,0x90,0xed,0x01, 0x34,0xdd,0xc6,0x95, + 0x31,0x2e,0x2a,0xf9, 0x57,0x7a,0x1e,0xa6 + }; + Aes enc; +#ifdef HAVE_AES_DECRYPT + Aes dec; +#endif + byte ct[AES_128_KEY_SIZE]; + byte tag[WC_AES_BLOCK_SIZE]; +#ifdef HAVE_AES_DECRYPT + byte pt[AES_128_KEY_SIZE]; +#endif + + XMEMSET(&enc, 0, sizeof(enc)); + ExpectIntEQ(wc_AesInit(&enc, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&enc, key_1b, sizeof(key_1b)), 0); + ExpectIntEQ(wc_AesGcmEncrypt(&enc, ct, pt_1b, sizeof(pt_1b), + iv_1b, sizeof(iv_1b), tag, sizeof(tag), + aad_1b, sizeof(aad_1b)), 0); + ExpectBufEQ(ct, expCt_1b, sizeof(expCt_1b)); + ExpectBufEQ(tag, expTag_1b, sizeof(expTag_1b)); + +#ifdef HAVE_AES_DECRYPT + XMEMSET(&dec, 0, sizeof(dec)); + ExpectIntEQ(wc_AesInit(&dec, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&dec, key_1b, sizeof(key_1b)), 0); + ExpectIntEQ(wc_AesGcmDecrypt(&dec, pt, ct, sizeof(ct), + iv_1b, sizeof(iv_1b), tag, sizeof(tag), + aad_1b, sizeof(aad_1b)), 0); + ExpectBufEQ(pt, pt_1b, sizeof(pt_1b)); + wc_AesFree(&dec); +#endif + wc_AesFree(&enc); + } +#endif /* WOLFSSL_AES_128 */ + + /* ------------------------------------------------------------------ + * Section 2: 60-byte IV, AES-192 + * McGrew & Viega Test Case 12 - uses the shared 60-byte plaintext and + * 20-byte AAD from Test Case 16, but with a 60-byte (non-96-bit) IV. + * Reference: wolfcrypt/test/test.c vectors k2/iv2/p/a/c2/t2. + * ------------------------------------------------------------------ */ +#ifdef WOLFSSL_AES_192 + { + static const byte key_60b[AES_192_KEY_SIZE] = { + 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, + 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08, + 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c + }; + static const byte iv_60b[60] = { + 0x93,0x13,0x22,0x5d, 0xf8,0x84,0x06,0xe5, + 0x55,0x90,0x9c,0x5a, 0xff,0x52,0x69,0xaa, + 0x6a,0x7a,0x95,0x38, 0x53,0x4f,0x7d,0xa1, + 0xe4,0xc3,0x03,0xd2, 0xa3,0x18,0xa7,0x28, + 0xc3,0xc0,0xc9,0x51, 0x56,0x80,0x95,0x39, + 0xfc,0xf0,0xe2,0x42, 0x9a,0x6b,0x52,0x54, + 0x16,0xae,0xdb,0xf5, 0xa0,0xde,0x6a,0x57, + 0xa6,0x37,0xb3,0x9b + }; + static const byte pt_60b[60] = { + 0xd9,0x31,0x32,0x25, 0xf8,0x84,0x06,0xe5, + 0xa5,0x59,0x09,0xc5, 0xaf,0xf5,0x26,0x9a, + 0x86,0xa7,0xa9,0x53, 0x15,0x34,0xf7,0xda, + 0x2e,0x4c,0x30,0x3d, 0x8a,0x31,0x8a,0x72, + 0x1c,0x3c,0x0c,0x95, 0x95,0x68,0x09,0x53, + 0x2f,0xcf,0x0e,0x24, 0x49,0xa6,0xb5,0x25, + 0xb1,0x6a,0xed,0xf5, 0xaa,0x0d,0xe6,0x57, + 0xba,0x63,0x7b,0x39 + }; + static const byte aad_60b[20] = { + 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, + 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, + 0xab,0xad,0xda,0xd2 + }; + static const byte expCt_60b[60] = { + 0xd2,0x7e,0x88,0x68, 0x1c,0xe3,0x24,0x3c, + 0x48,0x30,0x16,0x5a, 0x8f,0xdc,0xf9,0xff, + 0x1d,0xe9,0xa1,0xd8, 0xe6,0xb4,0x47,0xef, + 0x6e,0xf7,0xb7,0x98, 0x28,0x66,0x6e,0x45, + 0x81,0xe7,0x90,0x12, 0xaf,0x34,0xdd,0xd9, + 0xe2,0xf0,0x37,0x58, 0x9b,0x29,0x2d,0xb3, + 0xe6,0x7c,0x03,0x67, 0x45,0xfa,0x22,0xe7, + 0xe9,0xb7,0x37,0x3b + }; + static const byte expTag_60b[WC_AES_BLOCK_SIZE] = { + 0xdc,0xf5,0x66,0xff, 0x29,0x1c,0x25,0xbb, + 0xb8,0x56,0x8f,0xc3, 0xd3,0x76,0xa6,0xd9 + }; + Aes enc; +#ifdef HAVE_AES_DECRYPT + Aes dec; +#endif + byte ct[60]; + byte tag[WC_AES_BLOCK_SIZE]; +#ifdef HAVE_AES_DECRYPT + byte pt[60]; +#endif + + XMEMSET(&enc, 0, sizeof(enc)); + ExpectIntEQ(wc_AesInit(&enc, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&enc, key_60b, sizeof(key_60b)), 0); + ExpectIntEQ(wc_AesGcmEncrypt(&enc, ct, pt_60b, sizeof(pt_60b), + iv_60b, sizeof(iv_60b), tag, sizeof(tag), + aad_60b, sizeof(aad_60b)), 0); + ExpectBufEQ(ct, expCt_60b, sizeof(expCt_60b)); + ExpectBufEQ(tag, expTag_60b, sizeof(expTag_60b)); + +#ifdef HAVE_AES_DECRYPT + XMEMSET(&dec, 0, sizeof(dec)); + ExpectIntEQ(wc_AesInit(&dec, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&dec, key_60b, sizeof(key_60b)), 0); + ExpectIntEQ(wc_AesGcmDecrypt(&dec, pt, ct, sizeof(ct), + iv_60b, sizeof(iv_60b), tag, sizeof(tag), + aad_60b, sizeof(aad_60b)), 0); + ExpectBufEQ(pt, pt_60b, sizeof(pt_60b)); + wc_AesFree(&dec); +#endif + wc_AesFree(&enc); + } +#endif /* WOLFSSL_AES_192 */ + + /* ------------------------------------------------------------------ + * Section 3: Variable IV length loop, AES-128 + * Iterates IV lengths 1..GCM_NONCE_MAX_SZ. For each length: + * - Encrypt succeeds and produces a full-length ciphertext. + * - Decrypt recovers the original plaintext (auth-tag verification). + * - Adjacent IV lengths produce different ciphertext (uniqueness). + * ------------------------------------------------------------------ */ +#ifdef WOLFSSL_AES_128 + { + static const byte key_var[AES_128_KEY_SIZE] = { + 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, + 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08 + }; + /* IV material: reuse the key bytes, take the first ivLen bytes. */ + static const byte ivMat[GCM_NONCE_MAX_SZ] = { + 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, + 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08 + }; + static const byte plain_var[AES_128_KEY_SIZE] = { + 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f + }; + Aes enc; + byte ct[AES_128_KEY_SIZE]; + byte ctPrev[AES_128_KEY_SIZE]; /* ciphertext from previous ivLen */ + byte tag[WC_AES_BLOCK_SIZE]; +#ifdef HAVE_AES_DECRYPT + byte ptOut[AES_128_KEY_SIZE]; +#endif + word32 ivLen; + int hasPrev = 0; + + XMEMSET(&enc, 0, sizeof(enc)); + ExpectIntEQ(wc_AesInit(&enc, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&enc, key_var, sizeof(key_var)), 0); + + for (ivLen = 1; + ivLen <= GCM_NONCE_MAX_SZ && EXPECT_SUCCESS(); + ivLen++) { + XMEMSET(ct, 0, sizeof(ct)); + XMEMSET(tag, 0, sizeof(tag)); + + ExpectIntEQ(wc_AesGcmEncrypt(&enc, ct, plain_var, + sizeof(plain_var), ivMat, ivLen, tag, sizeof(tag), + NULL, 0), 0); + + /* Adjacent IV lengths must produce distinct ciphertext. */ + if (hasPrev) { + ExpectIntNE(XMEMCMP(ct, ctPrev, sizeof(ct)), 0); + } + XMEMCPY(ctPrev, ct, sizeof(ct)); + hasPrev = 1; + +#ifdef HAVE_AES_DECRYPT + XMEMSET(ptOut, 0, sizeof(ptOut)); + ExpectIntEQ(wc_AesGcmDecrypt(&enc, ptOut, ct, sizeof(ct), + ivMat, ivLen, tag, sizeof(tag), NULL, 0), 0); + ExpectBufEQ(ptOut, plain_var, sizeof(plain_var)); +#endif + } + wc_AesFree(&enc); + } +#endif /* WOLFSSL_AES_128 */ + + /* ------------------------------------------------------------------ + * Section 4: Zero-length IV must be rejected. + * ------------------------------------------------------------------ */ +#ifdef WOLFSSL_AES_128 + { + static const byte key_z[AES_128_KEY_SIZE] = { 0 }; + static const byte pt_z[1] = { 0 }; + Aes enc; + byte ct[1]; + byte tag[WC_AES_BLOCK_SIZE]; + + XMEMSET(&enc, 0, sizeof(enc)); + ExpectIntEQ(wc_AesInit(&enc, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesGcmSetKey(&enc, key_z, sizeof(key_z)), 0); +#ifdef HAVE_SELFTEST + ExpectIntEQ(wc_AesGcmEncrypt(&enc, ct, pt_z, sizeof(pt_z), + NULL, 0, tag, sizeof(tag), NULL, 0), 0); +#else + ExpectIntNE(wc_AesGcmEncrypt(&enc, ct, pt_z, sizeof(pt_z), + NULL, 0, tag, sizeof(tag), NULL, 0), 0); +#endif + wc_AesFree(&enc); + } +#endif + +#endif /* !NO_AES && HAVE_AESGCM && !HAVE_FIPS && !HW */ + return EXPECT_RESULT(); +} /* END test_wc_AesGcmNonStdNonce */ /* * Testing streaming AES-GCM API. @@ -3071,6 +4200,198 @@ int test_wc_AesGcmStream(void) return EXPECT_RESULT(); } /* END test_wc_AesGcmStream */ +/******************************************************************************* + * AES-GCM streaming mid-stream state corruption + ******************************************************************************/ + +/* + * Verify that the AES-GCM streaming API enforces its state flags even when + * they are cleared after a streaming session has already been started. + * + * The state is represented by three bitfields in struct Aes: + * gcmKeySet - set by wc_AesGcmInit/SetKey + * nonceSet - set by wc_AesGcmInit (when an IV is provided) + * ctrSet - set once the keystream counter has been initialised + * + * Clearing these fields mid-stream simulates either a software bug or a + * deliberate tampering attempt, and the API must detect and reject it. + */ +int test_wc_AesGcmStream_MidStreamState(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_128) && \ + defined(WOLFSSL_AESGCM_STREAM) + static const byte key[AES_128_KEY_SIZE] = { 0 }; + static const byte iv[GCM_NONCE_MID_SZ] = { 1 }; + static const byte aad[4] = { 0xfe, 0xed, 0xfa, 0xce }; + static const byte in[4] = { 0x00, 0x01, 0x02, 0x03 }; + Aes aes[1]; + byte out[4]; + byte tag[WC_AES_BLOCK_SIZE]; + + XMEMSET(aes, 0, sizeof(Aes)); + ExpectIntEQ(wc_AesInit(aes, NULL, INVALID_DEVID), 0); + + /* ------------------------------------------------------------------ + * Test 1: clear gcmKeySet after streaming has started -> MISSING_KEY + * ------------------------------------------------------------------ */ + ExpectIntEQ(wc_AesGcmInit(aes, key, sizeof(key), iv, sizeof(iv)), 0); + ExpectIntEQ(wc_AesGcmEncryptUpdate(aes, out, in, sizeof(in), + aad, sizeof(aad)), 0); + /* Corrupt the key-set flag mid-stream. */ + aes->gcmKeySet = 0; + ExpectIntEQ(wc_AesGcmEncryptFinal(aes, tag, sizeof(tag)), + WC_NO_ERR_TRACE(MISSING_KEY)); + + /* ------------------------------------------------------------------ + * Test 2: clear nonceSet after streaming has started -> MISSING_IV + * ------------------------------------------------------------------ */ + ExpectIntEQ(wc_AesGcmInit(aes, key, sizeof(key), iv, sizeof(iv)), 0); + ExpectIntEQ(wc_AesGcmEncryptUpdate(aes, out, in, sizeof(in), + aad, sizeof(aad)), 0); + /* Corrupt the nonce-set flag mid-stream. */ + aes->nonceSet = 0; + ExpectIntEQ(wc_AesGcmEncryptFinal(aes, tag, sizeof(tag)), + WC_NO_ERR_TRACE(MISSING_IV)); + +#ifdef HAVE_AES_DECRYPT + /* ------------------------------------------------------------------ + * Test 3: clear gcmKeySet during a decrypt session -> MISSING_KEY + * ------------------------------------------------------------------ */ + ExpectIntEQ(wc_AesGcmDecryptInit(aes, key, sizeof(key), iv, sizeof(iv)), 0); + ExpectIntEQ(wc_AesGcmDecryptUpdate(aes, out, in, sizeof(in), + aad, sizeof(aad)), 0); + aes->gcmKeySet = 0; + ExpectIntEQ(wc_AesGcmDecryptFinal(aes, tag, sizeof(tag)), + WC_NO_ERR_TRACE(MISSING_KEY)); +#endif + + wc_AesFree(aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesGcmStream_MidStreamState */ + +/******************************************************************************* + * AES-GCM streaming re-initialization after Final + ******************************************************************************/ + +/* + * Verify that an AES-GCM streaming context can be re-initialized and reused + * after wc_AesGcmEncryptFinal / wc_AesGcmDecryptFinal. + * + * wc_AesGcmInit resets the GHASH accumulator and running-length counters + * (aSz, cSz, over) and re-initialises the keystream counter, so calling it + * again after Final must produce a clean new session. + * + * 1. Re-init with the same key and IV produces identical ciphertext and tag. + * 2. Re-init with a different IV produces different ciphertext and tag. + * 3. Re-init after an abandoned session (Init but no Final) also works. + * 4. Decrypt re-init: re-initialise the decrypt context and recover plaintext. + */ +int test_wc_AesGcmStream_ReinitAfterFinal(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(HAVE_AESGCM) && defined(WOLFSSL_AES_128) && \ + defined(WOLFSSL_AESGCM_STREAM) + static const byte key[AES_128_KEY_SIZE] = { + 0xfe,0xff,0xe9,0x92, 0x86,0x65,0x73,0x1c, + 0x6d,0x6a,0x8f,0x94, 0x67,0x30,0x83,0x08 + }; + static const byte iv1[GCM_NONCE_MID_SZ] = { + 0xca,0xfe,0xba,0xbe, 0xfa,0xce,0xdb,0xad, + 0xde,0xca,0xf8,0x88 + }; + /* Different IV - last byte changed. */ + static const byte iv2[GCM_NONCE_MID_SZ] = { + 0xca,0xfe,0xba,0xbe, 0xfa,0xce,0xdb,0xad, + 0xde,0xca,0xf8,0x89 + }; + static const byte aad[20] = { + 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, + 0xfe,0xed,0xfa,0xce, 0xde,0xad,0xbe,0xef, + 0xab,0xad,0xda,0xd2 + }; + static const byte plain[16] = { + 0xd9,0x31,0x32,0x25, 0xf8,0x84,0x06,0xe5, + 0xa5,0x59,0x09,0xc5, 0xaf,0xf5,0x26,0x9a + }; + Aes enc[1]; +#ifdef HAVE_AES_DECRYPT + Aes dec[1]; +#endif + byte ct1[sizeof(plain)], ct2[sizeof(plain)], ct3[sizeof(plain)]; + byte tag1[WC_AES_BLOCK_SIZE], tag2[WC_AES_BLOCK_SIZE], + tag3[WC_AES_BLOCK_SIZE]; +#ifdef HAVE_AES_DECRYPT + byte pt[sizeof(plain)]; +#endif + + XMEMSET(enc, 0, sizeof(Aes)); + ExpectIntEQ(wc_AesInit(enc, NULL, INVALID_DEVID), 0); + + /* ---- Session 1: baseline ---- */ + ExpectIntEQ(wc_AesGcmInit(enc, key, sizeof(key), iv1, sizeof(iv1)), 0); + ExpectIntEQ(wc_AesGcmEncryptUpdate(enc, ct1, plain, sizeof(plain), + aad, sizeof(aad)), 0); + ExpectIntEQ(wc_AesGcmEncryptFinal(enc, tag1, sizeof(tag1)), 0); + + /* ---- Session 2: re-init with same key and IV -> must match ---- */ + ExpectIntEQ(wc_AesGcmInit(enc, key, sizeof(key), iv1, sizeof(iv1)), 0); + ExpectIntEQ(wc_AesGcmEncryptUpdate(enc, ct2, plain, sizeof(plain), + aad, sizeof(aad)), 0); + ExpectIntEQ(wc_AesGcmEncryptFinal(enc, tag2, sizeof(tag2)), 0); + ExpectBufEQ(ct2, ct1, sizeof(ct1)); + ExpectBufEQ(tag2, tag1, sizeof(tag1)); + + /* ---- Session 3: re-init with different IV -> must differ ---- */ + ExpectIntEQ(wc_AesGcmInit(enc, key, sizeof(key), iv2, sizeof(iv2)), 0); + ExpectIntEQ(wc_AesGcmEncryptUpdate(enc, ct3, plain, sizeof(plain), + aad, sizeof(aad)), 0); + ExpectIntEQ(wc_AesGcmEncryptFinal(enc, tag3, sizeof(tag3)), 0); + ExpectIntNE(XMEMCMP(ct3, ct1, sizeof(ct1)), 0); + ExpectIntNE(XMEMCMP(tag3, tag1, sizeof(tag1)), 0); + + /* ---- Session 4: re-init after abandoned session ---- + * Start a session (Init + Update) but never call Final, then re-init. */ + ExpectIntEQ(wc_AesGcmInit(enc, key, sizeof(key), iv2, sizeof(iv2)), 0); + /* partial update - abandon without Final */ + ExpectIntEQ(wc_AesGcmEncryptUpdate(enc, ct3, plain, sizeof(plain), + aad, sizeof(aad)), 0); + /* Re-init with iv1 - must produce session-1 output. */ + ExpectIntEQ(wc_AesGcmInit(enc, key, sizeof(key), iv1, sizeof(iv1)), 0); + ExpectIntEQ(wc_AesGcmEncryptUpdate(enc, ct2, plain, sizeof(plain), + aad, sizeof(aad)), 0); + ExpectIntEQ(wc_AesGcmEncryptFinal(enc, tag2, sizeof(tag2)), 0); + ExpectBufEQ(ct2, ct1, sizeof(ct1)); + ExpectBufEQ(tag2, tag1, sizeof(tag1)); + + wc_AesFree(enc); + +#ifdef HAVE_AES_DECRYPT + /* ---- Decrypt: re-init recovers plaintext on each session ---- */ + XMEMSET(dec, 0, sizeof(Aes)); + ExpectIntEQ(wc_AesInit(dec, NULL, INVALID_DEVID), 0); + + /* Session A: decrypt ct1 with iv1 -> plaintext. */ + ExpectIntEQ(wc_AesGcmDecryptInit(dec, key, sizeof(key), iv1, sizeof(iv1)), 0); + ExpectIntEQ(wc_AesGcmDecryptUpdate(dec, pt, ct1, sizeof(ct1), + aad, sizeof(aad)), 0); + ExpectIntEQ(wc_AesGcmDecryptFinal(dec, tag1, sizeof(tag1)), 0); + ExpectBufEQ(pt, plain, sizeof(plain)); + + /* Session B: re-init and decrypt again -> same plaintext. */ + ExpectIntEQ(wc_AesGcmDecryptInit(dec, key, sizeof(key), iv1, sizeof(iv1)), 0); + ExpectIntEQ(wc_AesGcmDecryptUpdate(dec, pt, ct1, sizeof(ct1), + aad, sizeof(aad)), 0); + ExpectIntEQ(wc_AesGcmDecryptFinal(dec, tag1, sizeof(tag1)), 0); + ExpectBufEQ(pt, plain, sizeof(plain)); + + wc_AesFree(dec); +#endif +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesGcmStream_ReinitAfterFinal */ + /******************************************************************************* * GMAC ******************************************************************************/ @@ -3444,27 +4765,257 @@ int test_wc_AesCcmEncryptDecrypt(void) } /* END test_wc_AesCcmEncryptDecrypt */ /******************************************************************************* - * AES-XTS + * AES-CCM overlapping (in-place) buffers ******************************************************************************/ /* - * test function for wc_AesXtsSetKey() + * Verify that wc_AesCcmEncrypt / wc_AesCcmDecrypt work correctly when the + * plaintext/ciphertext pointer is the same buffer (in == out). AES-CCM uses + * CTR mode for encryption (XOR keystream), so in-place operation is safe. + * + * Vectors are the IEEE 802.15.4 / RFC 3610 test case used in + * test_wc_AesCcmEncryptDecrypt. */ -int test_wc_AesXtsSetKey(void) +int test_wc_AesCcmEncryptDecrypt_InPlace(void) { EXPECT_DECLS; -#if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) - XtsAes aes; -#ifdef WOLFSSL_AES_128 - byte key16[] = { - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, +#if defined(HAVE_AESCCM) && defined(WOLFSSL_AES_128) && defined(HAVE_AES_DECRYPT) + Aes aes; + static const byte key[AES_128_KEY_SIZE] = { + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf }; -#endif -#if defined(WOLFSSL_AES_192) && !defined(HAVE_FIPS) - byte key24[] = { + static const byte nonce[13] = { + 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0xa0, + 0xa1, 0xa2, 0xa3, 0xa4, 0xa5 + }; + static const byte aad[8] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + }; + static const byte plain[23] = { + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e + }; + byte ref_ct[sizeof(plain)], ref_tag[8]; + byte buf[sizeof(plain)], tag[8]; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesCcmSetKey(&aes, key, sizeof(key)), 0); + + /* Reference ciphertext with separate in/out buffers */ + ExpectIntEQ(wc_AesCcmEncrypt(&aes, ref_ct, plain, sizeof(plain), + nonce, sizeof(nonce), ref_tag, sizeof(ref_tag), + aad, sizeof(aad)), 0); + + /* Encrypt in-place (out == in) - must produce the same ciphertext/tag */ + XMEMCPY(buf, plain, sizeof(buf)); + ExpectIntEQ(wc_AesCcmEncrypt(&aes, buf, buf, sizeof(buf), + nonce, sizeof(nonce), tag, sizeof(tag), + aad, sizeof(aad)), 0); + ExpectBufEQ(buf, ref_ct, sizeof(buf)); + ExpectBufEQ(tag, ref_tag, sizeof(tag)); + + /* Decrypt in-place - must recover original plaintext */ + ExpectIntEQ(wc_AesCcmDecrypt(&aes, buf, buf, sizeof(buf), + nonce, sizeof(nonce), tag, sizeof(tag), + aad, sizeof(aad)), 0); + ExpectBufEQ(buf, plain, sizeof(buf)); + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesCcmEncryptDecrypt_InPlace */ + +/******************************************************************************* + * AES-CCM unaligned buffers + ******************************************************************************/ + +/* + * Verify that wc_AesCcmEncrypt / wc_AesCcmDecrypt produce correct results + * when plaintext, ciphertext, and AAD buffers are byte-offset (unaligned). + * Tests offsets 1, 2, and 3. Same vectors as the InPlace test. + */ +int test_wc_AesCcmEncryptDecrypt_UnalignedBuffers(void) +{ + EXPECT_DECLS; +#if defined(HAVE_AESCCM) && defined(WOLFSSL_AES_128) && defined(HAVE_AES_DECRYPT) + Aes aes; + static const byte key[AES_128_KEY_SIZE] = { + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf + }; + static const byte nonce[13] = { + 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0xa0, + 0xa1, 0xa2, 0xa3, 0xa4, 0xa5 + }; + static const byte aad[8] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + }; + static const byte plain[23] = { + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e + }; + byte ref_ct[sizeof(plain)], ref_tag[8]; + byte in_buf[sizeof(plain) + 3], out_buf[sizeof(plain) + 3]; + byte aad_buf[sizeof(aad) + 3]; + byte tag[8]; + int off; + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesCcmSetKey(&aes, key, sizeof(key)), 0); + + /* Reference ciphertext/tag with naturally-aligned buffers */ + ExpectIntEQ(wc_AesCcmEncrypt(&aes, ref_ct, plain, sizeof(plain), + nonce, sizeof(nonce), ref_tag, sizeof(ref_tag), + aad, sizeof(aad)), 0); + + /* Encrypt with byte offsets 1, 2, 3 on plaintext, ciphertext, and AAD */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, plain, sizeof(plain)); + XMEMCPY(aad_buf + off, aad, sizeof(aad)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesCcmEncrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain), nonce, sizeof(nonce), tag, sizeof(tag), + aad_buf + off, sizeof(aad)), 0); + ExpectBufEQ(out_buf + off, ref_ct, sizeof(plain)); + ExpectBufEQ(tag, ref_tag, sizeof(tag)); + } + + /* Decrypt with byte offsets 1, 2, 3 */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, ref_ct, sizeof(plain)); + XMEMCPY(aad_buf + off, aad, sizeof(aad)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesCcmDecrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain), nonce, sizeof(nonce), ref_tag, sizeof(ref_tag), + aad_buf + off, sizeof(aad)), 0); + ExpectBufEQ(out_buf + off, plain, sizeof(plain)); + } + + wc_AesFree(&aes); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesCcmEncryptDecrypt_UnalignedBuffers */ + +/* + * AES-CCM AEAD edge cases: + * - invalid auth tag rejection + * - empty AAD (NULL / 0-length) + * - empty plaintext with non-empty AAD + */ +int test_wc_AesCcmAeadEdgeCases(void) +{ + EXPECT_DECLS; +#if defined(HAVE_AESCCM) && defined(WOLFSSL_AES_128) + static const byte key[] = { + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf + }; + static const byte nonce[] = { + 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x00, 0xa0, + 0xa1, 0xa2, 0xa3, 0xa4, 0xa5 + }; + static const byte plainT[] = { + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e + }; + static const byte authIn[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 + }; + Aes aes; + byte cipherOut[sizeof(plainT)]; + byte authTag[8]; +#ifdef HAVE_AES_DECRYPT + byte plainOut[sizeof(plainT)]; +#endif + + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesInit(&aes, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesCcmSetKey(&aes, key, sizeof(key)), 0); + + /* --- Empty AAD (NULL/0): encrypt with no additional data --- */ + XMEMSET(cipherOut, 0, sizeof(cipherOut)); + XMEMSET(authTag, 0, sizeof(authTag)); + ExpectIntEQ(wc_AesCcmEncrypt(&aes, cipherOut, plainT, sizeof(plainT), + nonce, sizeof(nonce), authTag, sizeof(authTag), NULL, 0), 0); +#ifdef HAVE_AES_DECRYPT + XMEMSET(plainOut, 0, sizeof(plainOut)); + ExpectIntEQ(wc_AesCcmDecrypt(&aes, plainOut, cipherOut, sizeof(cipherOut), + nonce, sizeof(nonce), authTag, sizeof(authTag), NULL, 0), 0); + ExpectBufEQ(plainOut, plainT, sizeof(plainT)); +#endif /* HAVE_AES_DECRYPT */ + + /* --- Empty plaintext with non-empty AAD --- */ + XMEMSET(authTag, 0, sizeof(authTag)); +#if defined(HAVE_SELFTEST) || (defined(HAVE_FIPS_VERSION) && \ + (HAVE_FIPS_VERSION <= 2)) + ExpectIntEQ(wc_AesCcmEncrypt(&aes, NULL, NULL, 0, + nonce, sizeof(nonce), authTag, sizeof(authTag), + authIn, sizeof(authIn)), BAD_FUNC_ARG); +#else + ExpectIntEQ(wc_AesCcmEncrypt(&aes, NULL, NULL, 0, + nonce, sizeof(nonce), authTag, sizeof(authTag), + authIn, sizeof(authIn)), 0); +#ifdef HAVE_AES_DECRYPT + /* Correct tag must pass */ + ExpectIntEQ(wc_AesCcmDecrypt(&aes, NULL, NULL, 0, + nonce, sizeof(nonce), authTag, sizeof(authTag), + authIn, sizeof(authIn)), 0); + /* Tampered tag must fail */ + authTag[0] ^= 0xff; + ExpectIntEQ(wc_AesCcmDecrypt(&aes, NULL, NULL, 0, + nonce, sizeof(nonce), authTag, sizeof(authTag), + authIn, sizeof(authIn)), + WC_NO_ERR_TRACE(AES_CCM_AUTH_E)); +#endif /* HAVE_AES_DECRYPT */ +#endif + + /* --- Invalid tag rejection: encrypt then tamper auth tag --- */ + XMEMSET(cipherOut, 0, sizeof(cipherOut)); + XMEMSET(authTag, 0, sizeof(authTag)); + ExpectIntEQ(wc_AesCcmEncrypt(&aes, cipherOut, plainT, sizeof(plainT), + nonce, sizeof(nonce), authTag, sizeof(authTag), + authIn, sizeof(authIn)), 0); +#ifdef HAVE_AES_DECRYPT + authTag[0] ^= 0xff; + ExpectIntEQ(wc_AesCcmDecrypt(&aes, plainOut, cipherOut, sizeof(cipherOut), + nonce, sizeof(nonce), authTag, sizeof(authTag), + authIn, sizeof(authIn)), + WC_NO_ERR_TRACE(AES_CCM_AUTH_E)); +#endif /* HAVE_AES_DECRYPT */ + + wc_AesFree(&aes); +#endif /* HAVE_AESCCM && WOLFSSL_AES_128 */ + return EXPECT_RESULT(); +} /* END test_wc_AesCcmAeadEdgeCases */ + +/******************************************************************************* + * AES-XTS + ******************************************************************************/ + +/* + * test function for wc_AesXtsSetKey() + */ +int test_wc_AesXtsSetKey(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) + XtsAes aes; +#ifdef WOLFSSL_AES_128 + byte key16[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + }; +#endif +#if defined(WOLFSSL_AES_192) && !defined(HAVE_FIPS) + byte key24[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, @@ -3538,34 +5089,343 @@ int test_wc_AesXtsSetKey(void) AES_ENCRYPTION, NULL, INVALID_DEVID), 0); wc_AesXtsFree(&aes); #endif - - /* Pass in bad args. */ - ExpectIntEQ(wc_AesXtsSetKey(NULL, NULL, keyLen, AES_ENCRYPTION, NULL, - INVALID_DEVID), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ(wc_AesXtsSetKey(NULL, key, keyLen, AES_ENCRYPTION, NULL, - INVALID_DEVID), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ(wc_AesXtsSetKey(&aes, NULL, keyLen, AES_ENCRYPTION, NULL, - INVALID_DEVID), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ(wc_AesXtsSetKey(&aes, badKey16, sizeof(badKey16)/sizeof(byte), - AES_ENCRYPTION, NULL, INVALID_DEVID), WC_NO_ERR_TRACE(WC_KEY_SIZE_E)); - ExpectIntEQ(wc_AesXtsSetKey(&aes, badKey24, sizeof(badKey24)/sizeof(byte), - AES_ENCRYPTION, NULL, INVALID_DEVID), WC_NO_ERR_TRACE(WC_KEY_SIZE_E)); - ExpectIntEQ(wc_AesXtsSetKey(&aes, badKey32, sizeof(badKey32)/sizeof(byte), - AES_ENCRYPTION, NULL, INVALID_DEVID), WC_NO_ERR_TRACE(WC_KEY_SIZE_E)); - ExpectIntEQ(wc_AesXtsSetKey(&aes, key, keyLen, -2, NULL, INVALID_DEVID), - WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* Pass in bad args. */ + ExpectIntEQ(wc_AesXtsSetKey(NULL, NULL, keyLen, AES_ENCRYPTION, NULL, + INVALID_DEVID), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsSetKey(NULL, key, keyLen, AES_ENCRYPTION, NULL, + INVALID_DEVID), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, NULL, keyLen, AES_ENCRYPTION, NULL, + INVALID_DEVID), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, badKey16, sizeof(badKey16)/sizeof(byte), + AES_ENCRYPTION, NULL, INVALID_DEVID), WC_NO_ERR_TRACE(WC_KEY_SIZE_E)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, badKey24, sizeof(badKey24)/sizeof(byte), + AES_ENCRYPTION, NULL, INVALID_DEVID), WC_NO_ERR_TRACE(WC_KEY_SIZE_E)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, badKey32, sizeof(badKey32)/sizeof(byte), + AES_ENCRYPTION, NULL, INVALID_DEVID), WC_NO_ERR_TRACE(WC_KEY_SIZE_E)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key, keyLen, -2, NULL, INVALID_DEVID), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesXtsSetKey */ + +int test_wc_AesXtsEncryptDecrypt_Sizes(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) && \ + defined(WOLFSSL_AES_256) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI) + #define XTS_LEN (WC_AES_BLOCK_SIZE * 16) + byte key32[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 + }; + byte tweak[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + }; + XtsAes aes; + word32 tweakLen = (word32)sizeof(tweak)/sizeof(byte); + int sz; + WC_DECLARE_VAR(plain, byte, XTS_LEN, NULL); + WC_DECLARE_VAR(cipher, byte, XTS_LEN, NULL); +#ifdef HAVE_AES_DECRYPT + WC_DECLARE_VAR(decrypted, byte, XTS_LEN, NULL); +#endif + + WC_ALLOC_VAR(plain, byte, XTS_LEN, NULL); + WC_ALLOC_VAR(cipher, byte, XTS_LEN, NULL); +#ifdef HAVE_AES_DECRYPT + WC_ALLOC_VAR(decrypted, byte, XTS_LEN, NULL); +#endif + +#ifdef WC_DECLARE_VAR_IS_HEAP_ALLOC + ExpectNotNull(plain); + ExpectNotNull(cipher); +#ifdef HAVE_AES_DECRYPT + ExpectNotNull(decrypted); +#endif +#endif + + XMEMSET(&aes, 0, sizeof(Aes)); + XMEMSET(plain, 0xa5, XTS_LEN); + + for (sz = WC_AES_BLOCK_SIZE; sz <= XTS_LEN; sz *= 2) { + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + XMEMSET(cipher, 0, XTS_LEN); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, cipher, plain, sz, tweak, tweakLen), + 0); + wc_AesXtsFree(&aes); + +#ifdef HAVE_AES_DECRYPT + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + XMEMSET(decrypted, 0xff, XTS_LEN); + ExpectIntEQ(wc_AesXtsDecrypt(&aes, decrypted, cipher, sz, tweak, + tweakLen), 0); + ExpectBufEQ(decrypted, plain, sz); + wc_AesXtsFree(&aes); +#endif + } + + WC_FREE_VAR(plain, NULL); + WC_FREE_VAR(cipher, NULL); +#ifdef HAVE_AES_DECRYPT + WC_FREE_VAR(decrypted, NULL); +#endif +#endif + return EXPECT_RESULT(); +} + +/* + * test function for wc_AesXtsEncrypt and wc_AesXtsDecrypt + */ +int test_wc_AesXtsEncryptDecrypt(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) && \ + defined(WOLFSSL_AES_256) + XtsAes aes; + byte key32[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 + }; + byte vector[] = { /* Now is the time for all w/o trailing 0 */ + 0x4e,0x6f,0x77,0x20,0x69,0x73,0x20,0x74, + 0x68,0x65,0x20,0x74,0x69,0x6d,0x65,0x20, + 0x66,0x6f,0x72,0x20,0x61,0x6c,0x6c,0x20 + }; + byte tweak[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + }; + word32 tweakLen = (word32)sizeof(tweak)/sizeof(byte); + byte enc[sizeof(vector)]; + byte resultT[WC_AES_BLOCK_SIZE]; + byte dec[sizeof(vector)]; + + /* Init stack variables. */ + XMEMSET(&aes, 0, sizeof(Aes)); + XMEMSET(enc, 0, sizeof(vector)); + XMEMSET(dec, 0, sizeof(vector)); + XMEMSET(resultT, 0, WC_AES_BLOCK_SIZE); + + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, enc, vector, sizeof(vector), tweak, + tweakLen), 0); + wc_AesXtsFree(&aes); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsDecrypt(&aes, dec, enc, sizeof(vector), tweak, + tweakLen), 0); + ExpectIntEQ(XMEMCMP(vector, dec, sizeof(vector)), 0); + wc_AesXtsFree(&aes); + + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + /* Test bad args for wc_AesXtsEncrypt and wc_AesXtsDecrypt */ + ExpectIntEQ(wc_AesXtsEncrypt(NULL, enc, vector, sizeof(vector), tweak, + tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, NULL, vector, sizeof(vector), tweak, + tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, enc, NULL, sizeof(vector), tweak, + tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_AesXtsFree(&aes); + /* END wc_AesXtsEncrypt */ + +#ifdef HAVE_AES_DECRYPT + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsDecrypt(NULL, dec, enc, sizeof(enc)/sizeof(byte), + tweak, tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecrypt(&aes, NULL, enc, sizeof(enc)/sizeof(byte), + tweak, tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecrypt(&aes, dec, NULL, sizeof(enc)/sizeof(byte), + tweak, tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_AesXtsFree(&aes); +#endif /* HAVE_AES_DECRYPT */ +#endif + + return EXPECT_RESULT(); +} /* END test_wc_AesXtsEncryptDecrypt */ + +/******************************************************************************* + * AES-XTS overlapping (in-place) buffers + ******************************************************************************/ + +/* + * Verify that wc_AesXtsEncrypt / wc_AesXtsDecrypt work correctly when the + * plaintext/ciphertext pointer is the same buffer (in == out). The software + * path explicitly handles this case by reading each input block into a local + * copy before XOR-and-encrypt, so in-place operation is safe. + */ +int test_wc_AesXtsEncryptDecrypt_InPlace(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) && \ + defined(WOLFSSL_AES_256) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI) + XtsAes aes; + static const byte key64[64] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 + }; + static const byte tweak[WC_AES_BLOCK_SIZE] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 + }; + /* 24 bytes: one full block + 8-byte partial block (CTS-style steal) */ + static const byte plain[24] = { + 0x4e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6c, 0x6c, 0x20 + }; + byte ref_ct[sizeof(plain)]; + byte buf[sizeof(plain)]; + + XMEMSET(&aes, 0, sizeof(aes)); + + /* Reference ciphertext with separate in/out buffers */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, ref_ct, plain, sizeof(plain), + tweak, sizeof(tweak)), 0); + wc_AesXtsFree(&aes); + + /* Encrypt in-place (out == in) - must produce the same ciphertext */ + XMEMCPY(buf, plain, sizeof(buf)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, buf, buf, sizeof(buf), + tweak, sizeof(tweak)), 0); + wc_AesXtsFree(&aes); + ExpectBufEQ(buf, ref_ct, sizeof(buf)); + +#ifdef HAVE_AES_DECRYPT + /* Decrypt in-place - must recover original plaintext */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsDecrypt(&aes, buf, buf, sizeof(buf), + tweak, sizeof(tweak)), 0); + wc_AesXtsFree(&aes); + ExpectBufEQ(buf, plain, sizeof(buf)); +#endif +#endif + return EXPECT_RESULT(); +} /* END test_wc_AesXtsEncryptDecrypt_InPlace */ + +/******************************************************************************* + * AES-XTS unaligned buffers + ******************************************************************************/ + +/* + * Verify that wc_AesXtsEncrypt / wc_AesXtsDecrypt produce correct results + * when plaintext and ciphertext buffers are byte-offset (unaligned). Tests + * offsets 1, 2, and 3. Same key/tweak/plain as InPlace test. + */ +int test_wc_AesXtsEncryptDecrypt_UnalignedBuffers(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) && \ + defined(WOLFSSL_AES_256) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI) + XtsAes aes; + static const byte key64[64] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 + }; + static const byte tweak[WC_AES_BLOCK_SIZE] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 + }; + static const byte plain[24] = { + 0x4e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6c, 0x6c, 0x20 + }; + byte ref_ct[sizeof(plain)]; + byte in_buf[sizeof(plain) + 3], out_buf[sizeof(plain) + 3]; + int off; + + XMEMSET(&aes, 0, sizeof(aes)); + + /* Reference ciphertext with naturally-aligned buffers */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, ref_ct, plain, sizeof(plain), + tweak, sizeof(tweak)), 0); + wc_AesXtsFree(&aes); + + /* Encrypt with byte offsets 1, 2, 3 on both in and out */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, plain, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain), tweak, sizeof(tweak)), 0); + wc_AesXtsFree(&aes); + ExpectBufEQ(out_buf + off, ref_ct, sizeof(plain)); + } + +#ifdef HAVE_AES_DECRYPT + /* Decrypt with byte offsets 1, 2, 3 */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, ref_ct, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsDecrypt(&aes, out_buf + off, in_buf + off, + sizeof(plain), tweak, sizeof(tweak)), 0); + wc_AesXtsFree(&aes); + ExpectBufEQ(out_buf + off, plain, sizeof(plain)); + } +#endif #endif return EXPECT_RESULT(); -} /* END test_wc_AesXtsSetKey */ +} /* END test_wc_AesXtsEncryptDecrypt_UnalignedBuffers */ -int test_wc_AesXtsEncryptDecrypt_Sizes(void) +/******************************************************************************* + * AES-XTS streaming (Init/Update/Final) + ******************************************************************************/ + +/* + * test function for AES-XTS streaming encrypt/decrypt + */ +int test_wc_AesXtsStream(void) { EXPECT_DECLS; #if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) && \ - defined(WOLFSSL_AES_256) && !defined(WOLFSSL_AFALG) && \ - !defined(WOLFSSL_KCAPI) - #define XTS_LEN (WC_AES_BLOCK_SIZE * 16) - byte key32[] = { + defined(WOLFSSL_AES_256) && defined(WOLFSSL_AESXTS_STREAM) && \ + !defined(WOLFSSL_AFALG) && !defined(WOLFSSL_KCAPI) + /* Same key as test_wc_AesXtsEncryptDecrypt */ + static const byte key32[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, @@ -3575,74 +5435,391 @@ int test_wc_AesXtsEncryptDecrypt_Sizes(void) 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 }; - byte tweak[] = { + static const byte tweak[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 + }; + /* Non-block-aligned plaintext from test_wc_AesXtsEncryptDecrypt (24 bytes) */ + static const byte vector[] = { + 0x4e, 0x6f, 0x77, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6c, 0x6c, 0x20 }; + const word32 tweakLen = (word32)sizeof(tweak); XtsAes aes; - word32 tweakLen = (word32)sizeof(tweak)/sizeof(byte); - int sz; - WC_DECLARE_VAR(plain, byte, XTS_LEN, NULL); - WC_DECLARE_VAR(cipher, byte, XTS_LEN, NULL); + XtsAesStreamData stream; + byte plain3[WC_AES_BLOCK_SIZE * 3]; /* block-aligned plaintext */ + byte expEnc[sizeof(vector)]; /* expected ciphertext (non-aligned) */ + byte expEnc3[WC_AES_BLOCK_SIZE * 3]; /* expected ciphertext (3 blocks) */ + byte enc[WC_AES_BLOCK_SIZE * 3]; + byte dec[WC_AES_BLOCK_SIZE * 3]; + + XMEMSET(&aes, 0, sizeof(aes)); + XMEMSET(plain3, 0xa5, sizeof(plain3)); + + /* Get expected ciphertext for non-aligned vector via single-shot */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, expEnc, vector, sizeof(vector), tweak, + tweakLen), 0); + wc_AesXtsFree(&aes); + + /* Get expected ciphertext for 3-block plain via single-shot */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, expEnc3, plain3, sizeof(plain3), tweak, + tweakLen), 0); + wc_AesXtsFree(&aes); + + /* --- Stream encrypt: Init + Final(non-aligned, 24 bytes) --- */ + XMEMSET(enc, 0, sizeof(enc)); + XMEMSET(&stream, 0, sizeof(stream)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, enc, vector, sizeof(vector), + &stream), 0); + ExpectBufEQ(enc, expEnc, sizeof(expEnc)); + wc_AesXtsFree(&aes); + + /* --- Stream encrypt: Init + Update(2 blocks) + Final(1 block) --- */ + XMEMSET(enc, 0, sizeof(enc)); + XMEMSET(&stream, 0, sizeof(stream)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, enc, plain3, + WC_AES_BLOCK_SIZE * 2, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, + enc + WC_AES_BLOCK_SIZE * 2, + plain3 + WC_AES_BLOCK_SIZE * 2, + WC_AES_BLOCK_SIZE, &stream), 0); + ExpectBufEQ(enc, expEnc3, sizeof(expEnc3)); + wc_AesXtsFree(&aes); + + /* --- Stream encrypt: Init + Update(1 block) x3 via individual calls + + * Final(0 bytes) --- */ + XMEMSET(enc, 0, sizeof(enc)); + XMEMSET(&stream, 0, sizeof(stream)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, enc, + plain3, WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, + enc + WC_AES_BLOCK_SIZE, + plain3 + WC_AES_BLOCK_SIZE, WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, + enc + WC_AES_BLOCK_SIZE * 2, + plain3 + WC_AES_BLOCK_SIZE * 2, WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, NULL, NULL, 0, &stream), 0); + ExpectBufEQ(enc, expEnc3, sizeof(expEnc3)); + wc_AesXtsFree(&aes); + #ifdef HAVE_AES_DECRYPT - WC_DECLARE_VAR(decrypted, byte, XTS_LEN, NULL); -#endif + /* --- Stream decrypt: Init + Final(non-aligned, 24 bytes) --- */ + XMEMSET(dec, 0, sizeof(dec)); + XMEMSET(&stream, 0, sizeof(stream)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, tweakLen, &stream), 0); + ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, dec, expEnc, sizeof(expEnc), + &stream), 0); + ExpectBufEQ(dec, vector, sizeof(vector)); + wc_AesXtsFree(&aes); - WC_ALLOC_VAR(plain, byte, XTS_LEN, NULL); - WC_ALLOC_VAR(cipher, byte, XTS_LEN, NULL); + /* --- Stream decrypt: Init + Update(2 blocks) + Final(1 block) --- */ + XMEMSET(dec, 0, sizeof(dec)); + XMEMSET(&stream, 0, sizeof(stream)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, tweakLen, &stream), 0); + ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, dec, expEnc3, + WC_AES_BLOCK_SIZE * 2, &stream), 0); + ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, + dec + WC_AES_BLOCK_SIZE * 2, + expEnc3 + WC_AES_BLOCK_SIZE * 2, + WC_AES_BLOCK_SIZE, &stream), 0); + ExpectBufEQ(dec, plain3, sizeof(plain3)); + wc_AesXtsFree(&aes); +#endif /* HAVE_AES_DECRYPT */ + + /* --- Bad args --- */ + XMEMSET(&stream, 0, sizeof(stream)); + /* NULL aes */ + ExpectIntEQ(wc_AesXtsEncryptInit(NULL, tweak, tweakLen, &stream), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* NULL tweak */ + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, NULL, tweakLen, &stream), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* NULL stream */ + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, tweakLen, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* sz not a multiple of block size */ + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, enc, plain3, 1, &stream), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* NULL stream to Update */ + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, enc, plain3, + WC_AES_BLOCK_SIZE, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* NULL stream to Final */ + ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, enc, vector, sizeof(vector), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); #ifdef HAVE_AES_DECRYPT - WC_ALLOC_VAR(decrypted, byte, XTS_LEN, NULL); + ExpectIntEQ(wc_AesXtsDecryptInit(NULL, tweak, tweakLen, &stream), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecryptInit(&aes, NULL, tweakLen, &stream), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, tweakLen, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, dec, expEnc3, + WC_AES_BLOCK_SIZE, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, dec, expEnc3, sizeof(plain3), NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif /* HAVE_AES_DECRYPT */ #endif + return EXPECT_RESULT(); +} /* END test_wc_AesXtsStream */ + +/******************************************************************************* + * AES-XTS streaming mid-stream state corruption + ******************************************************************************/ + +/* + * Verify that calling wc_AesXtsEncryptUpdate / wc_AesXtsDecryptUpdate after + * wc_AesXtsEncryptFinal / wc_AesXtsDecryptFinal is rejected. + * + * AES-XTS tracks state through stream->bytes_crypted_with_this_tweak. After + * a Final call that processed a non-block-aligned chunk, this field is left + * with a value whose low bits are non-zero. A subsequent Update call checks + * this condition and returns BAD_FUNC_ARG to prevent reuse of a completed + * streaming session. + */ +int test_wc_AesXtsStream_MidStreamState(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) && \ + defined(WOLFSSL_AES_256) && defined(WOLFSSL_AESXTS_STREAM) && \ + !defined(WOLFSSL_AFALG) && !defined(WOLFSSL_KCAPI) + static const byte key64[64] = { + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66 + }; + static const byte tweak[WC_AES_BLOCK_SIZE] = { + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66 + }; + /* 24-byte (non-block-aligned) vector - ensures Final leaves + * bytes_crypted_with_this_tweak with a value whose low 4 bits are + * non-zero, triggering the guard on the next Update call. */ + static const byte plain24[24] = { + 0x4e,0x6f,0x77,0x20, 0x69,0x73,0x20,0x74, + 0x68,0x65,0x20,0x74, 0x69,0x6d,0x65,0x20, + 0x66,0x6f,0x72,0x20, 0x61,0x6c,0x6c,0x20 + }; + /* One full block for the subsequent (illegal) Update call. */ + static const byte block[WC_AES_BLOCK_SIZE] = { 0 }; + XtsAes aes; + XtsAesStreamData stream; + byte enc[24]; + byte dummy[WC_AES_BLOCK_SIZE]; + + XMEMSET(&aes, 0, sizeof(aes)); + + /* ------------------------------------------------------------------ + * Encrypt: Init -> Final (non-aligned 24 B) -> Update must fail + * ------------------------------------------------------------------ */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak, sizeof(tweak), &stream), 0); + /* Final processes all 24 bytes; bytes_crypted_with_this_tweak becomes 24 + * (not a multiple of WC_AES_BLOCK_SIZE=16). */ + ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, enc, plain24, sizeof(plain24), + &stream), 0); + /* The subsequent Update must be rejected because the stream is "done". */ + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, dummy, block, sizeof(block), + &stream), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_AesXtsFree(&aes); -#ifdef WC_DECLARE_VAR_IS_HEAP_ALLOC - ExpectNotNull(plain); - ExpectNotNull(cipher); #ifdef HAVE_AES_DECRYPT - ExpectNotNull(decrypted); + /* ------------------------------------------------------------------ + * Decrypt: Init -> Final (non-aligned 24 B) -> Update must fail + * ------------------------------------------------------------------ */ + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak, sizeof(tweak), &stream), 0); + ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, enc, enc, sizeof(enc), + &stream), 0); + ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, dummy, block, sizeof(block), + &stream), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wc_AesXtsFree(&aes); #endif #endif + return EXPECT_RESULT(); +} /* END test_wc_AesXtsStream_MidStreamState */ - XMEMSET(&aes, 0, sizeof(Aes)); - XMEMSET(plain, 0xa5, XTS_LEN); - - for (sz = WC_AES_BLOCK_SIZE; sz <= XTS_LEN; sz *= 2) { - ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), - AES_ENCRYPTION, NULL, INVALID_DEVID), 0); - XMEMSET(cipher, 0, XTS_LEN); - ExpectIntEQ(wc_AesXtsEncrypt(&aes, cipher, plain, sz, tweak, tweakLen), - 0); - wc_AesXtsFree(&aes); +/******************************************************************************* + * AES-XTS streaming re-initialization after Final + ******************************************************************************/ +/* + * Verify that an AES-XTS streaming context can be re-initialized and reused + * after wc_AesXtsEncryptFinal / wc_AesXtsDecryptFinal. + * + * wc_AesXtsEncryptInit unconditionally resets stream->bytes_crypted_with_this_tweak + * to 0 and reloads the tweak, so it is safe to call it again after Final. + * + * 1. Re-init with the same key and tweak produces identical ciphertext. + * 2. Re-init with a different tweak produces different ciphertext. + * 3. Re-init after an abandoned session (Init + Update but no Final) works. + * 4. Decrypt re-init: recover plaintext across two separate sessions. + */ +int test_wc_AesXtsStream_ReinitAfterFinal(void) +{ + EXPECT_DECLS; +#if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) && \ + defined(WOLFSSL_AES_256) && defined(WOLFSSL_AESXTS_STREAM) && \ + !defined(WOLFSSL_AFALG) && !defined(WOLFSSL_KCAPI) + static const byte key64[64] = { + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66 + }; + /* Two distinct tweaks (sector numbers). */ + static const byte tweak1[WC_AES_BLOCK_SIZE] = { + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x66 + }; + static const byte tweak2[WC_AES_BLOCK_SIZE] = { + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x38,0x39,0x61,0x62, 0x63,0x64,0x65,0x67 /* last byte differs */ + }; + /* Two-block-aligned plaintext + a partial tail (40 bytes total). */ + static const byte plain[40] = { + 0x4e,0x6f,0x77,0x20, 0x69,0x73,0x20,0x74, + 0x68,0x65,0x20,0x74, 0x69,0x6d,0x65,0x20, + 0x66,0x6f,0x72,0x20, 0x61,0x6c,0x6c,0x20, + 0x67,0x6f,0x6f,0x64, 0x20,0x6d,0x65,0x6e, + 0x20,0x74,0x6f,0x20, 0x63,0x6f,0x6d,0x65 + }; + XtsAes aes; + XtsAesStreamData stream; + byte ct1[sizeof(plain)], ct2[sizeof(plain)], ct3[sizeof(plain)]; #ifdef HAVE_AES_DECRYPT - ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), - AES_DECRYPTION, NULL, INVALID_DEVID), 0); - XMEMSET(decrypted, 0xff, XTS_LEN); - ExpectIntEQ(wc_AesXtsDecrypt(&aes, decrypted, cipher, sz, tweak, - tweakLen), 0); - ExpectBufEQ(decrypted, plain, sz); - wc_AesXtsFree(&aes); + byte pt[sizeof(plain)]; #endif - } - WC_FREE_VAR(plain, NULL); - WC_FREE_VAR(cipher, NULL); + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + + /* ---- Session 1: baseline ---- + * One full block via Update, the remaining 24 bytes via Final. + * Note: AesXtsEncryptFinal forwards to the Update path, so the Final + * size must be >= WC_AES_BLOCK_SIZE when sz > 0. */ + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct1, plain, + WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, ct1 + WC_AES_BLOCK_SIZE, + plain + WC_AES_BLOCK_SIZE, + sizeof(plain) - WC_AES_BLOCK_SIZE, &stream), 0); + + /* ---- Session 2: re-init with same tweak -> must match ---- */ + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct2, plain, + WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, ct2 + WC_AES_BLOCK_SIZE, + plain + WC_AES_BLOCK_SIZE, + sizeof(plain) - WC_AES_BLOCK_SIZE, &stream), 0); + ExpectBufEQ(ct2, ct1, sizeof(ct1)); + + /* ---- Session 3: re-init with different tweak -> must differ ---- */ + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak2, sizeof(tweak2), &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct3, plain, + WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, ct3 + WC_AES_BLOCK_SIZE, + plain + WC_AES_BLOCK_SIZE, + sizeof(plain) - WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntNE(XMEMCMP(ct3, ct1, sizeof(ct1)), 0); + + /* ---- Session 4: re-init after abandoned (no Final) session ---- */ + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak2, sizeof(tweak2), &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct3, plain, + WC_AES_BLOCK_SIZE, &stream), 0); + /* Abandon - re-init with tweak1, must give session-1 output. */ + ExpectIntEQ(wc_AesXtsEncryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptUpdate(&aes, ct2, plain, + WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntEQ(wc_AesXtsEncryptFinal(&aes, ct2 + WC_AES_BLOCK_SIZE, + plain + WC_AES_BLOCK_SIZE, + sizeof(plain) - WC_AES_BLOCK_SIZE, &stream), 0); + ExpectBufEQ(ct2, ct1, sizeof(ct1)); + + wc_AesXtsFree(&aes); + #ifdef HAVE_AES_DECRYPT - WC_FREE_VAR(decrypted, NULL); + /* ---- Decrypt: re-init recovers plaintext on each session ---- */ + XMEMSET(&aes, 0, sizeof(aes)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key64, sizeof(key64), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + + /* Session A: decrypt ct1 with tweak1 -> plaintext. */ + ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0); + ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, pt, ct1, + WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, pt + WC_AES_BLOCK_SIZE, + ct1 + WC_AES_BLOCK_SIZE, + sizeof(ct1) - WC_AES_BLOCK_SIZE, &stream), 0); + ExpectBufEQ(pt, plain, sizeof(plain)); + + /* Session B: re-init and decrypt again -> same plaintext. */ + ExpectIntEQ(wc_AesXtsDecryptInit(&aes, tweak1, sizeof(tweak1), &stream), 0); + ExpectIntEQ(wc_AesXtsDecryptUpdate(&aes, pt, ct1, + WC_AES_BLOCK_SIZE, &stream), 0); + ExpectIntEQ(wc_AesXtsDecryptFinal(&aes, pt + WC_AES_BLOCK_SIZE, + ct1 + WC_AES_BLOCK_SIZE, + sizeof(ct1) - WC_AES_BLOCK_SIZE, &stream), 0); + ExpectBufEQ(pt, plain, sizeof(plain)); + + wc_AesXtsFree(&aes); #endif #endif return EXPECT_RESULT(); -} +} /* END test_wc_AesXtsStream_ReinitAfterFinal */ + +/******************************************************************************* + * AES-XTS sector APIs + ******************************************************************************/ /* - * test function for wc_AesXtsEncrypt and wc_AesXtsDecrypt + * test function for wc_AesXtsEncryptSector, wc_AesXtsDecryptSector, + * wc_AesXtsEncryptConsecutiveSectors, and wc_AesXtsDecryptConsecutiveSectors */ -int test_wc_AesXtsEncryptDecrypt(void) +int test_wc_AesXtsEncryptDecryptSector(void) { EXPECT_DECLS; #if !defined(NO_AES) && defined(WOLFSSL_AES_XTS) && \ - defined(WOLFSSL_AES_256) - XtsAes aes; - byte key32[] = { + defined(WOLFSSL_AES_256) && !defined(WOLFSSL_AFALG) && \ + !defined(WOLFSSL_KCAPI) + /* Sector size used for consecutive-sector tests (2 AES blocks) */ + #define SECTOR_SZ (WC_AES_BLOCK_SIZE * 2) + #define NUM_SECTORS 3 + #define TOTAL_SZ (SECTOR_SZ * NUM_SECTORS) + + static const byte key32[] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, @@ -3652,65 +5829,188 @@ int test_wc_AesXtsEncryptDecrypt(void) 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66 }; - byte vector[] = { /* Now is the time for all w/o trailing 0 */ - 0x4e,0x6f,0x77,0x20,0x69,0x73,0x20,0x74, - 0x68,0x65,0x20,0x74,0x69,0x6d,0x65,0x20, - 0x66,0x6f,0x72,0x20,0x61,0x6c,0x6c,0x20 - }; - byte tweak[] = { - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, - }; - word32 tweakLen = (word32)sizeof(tweak)/sizeof(byte); - byte enc[sizeof(vector)]; - byte resultT[WC_AES_BLOCK_SIZE]; - byte dec[sizeof(vector)]; + XtsAes aes; + byte plain[TOTAL_SZ]; + byte enc[TOTAL_SZ]; + byte dec[TOTAL_SZ]; + byte encRef[TOTAL_SZ]; /* sector-by-sector reference */ + byte zeroTweak[WC_AES_BLOCK_SIZE]; + byte encZeroTweak[SECTOR_SZ]; + byte encSector0[SECTOR_SZ]; + byte encSector1[SECTOR_SZ]; + int i; - /* Init stack variables. */ - XMEMSET(&aes, 0, sizeof(Aes)); - XMEMSET(enc, 0, sizeof(vector)); - XMEMSET(dec, 0, sizeof(vector)); - XMEMSET(resultT, 0, WC_AES_BLOCK_SIZE); + XMEMSET(&aes, 0, sizeof(aes)); + XMEMSET(zeroTweak, 0, sizeof(zeroTweak)); - ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + /* Fill plaintext with a recognisable pattern */ + for (i = 0; i < (int)sizeof(plain); i++) + plain[i] = (byte)i; + + /* + * 1. wc_AesXtsEncryptSector / wc_AesXtsDecryptSector + */ + + /* Encrypt sector 0 and verify it matches wc_AesXtsEncrypt with zero tweak */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), AES_ENCRYPTION, NULL, INVALID_DEVID), 0); - ExpectIntEQ(wc_AesXtsEncrypt(&aes, enc, vector, sizeof(vector), tweak, - tweakLen), 0); + ExpectIntEQ(wc_AesXtsEncryptSector(&aes, encSector0, plain, + SECTOR_SZ, 0), 0); + ExpectIntEQ(wc_AesXtsEncrypt(&aes, encZeroTweak, plain, SECTOR_SZ, + zeroTweak, WC_AES_BLOCK_SIZE), 0); + ExpectBufEQ(encSector0, encZeroTweak, SECTOR_SZ); wc_AesXtsFree(&aes); - ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + + /* Encrypt sector 1 and verify it differs from sector 0 */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncryptSector(&aes, encSector1, plain, + SECTOR_SZ, 1), 0); + ExpectIntNE(XMEMCMP(encSector0, encSector1, SECTOR_SZ), 0); + wc_AesXtsFree(&aes); + +#ifdef HAVE_AES_DECRYPT + /* Decrypt sector 0 and verify roundtrip */ + XMEMSET(dec, 0, sizeof(dec)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), AES_DECRYPTION, NULL, INVALID_DEVID), 0); - ExpectIntEQ(wc_AesXtsDecrypt(&aes, dec, enc, sizeof(vector), tweak, - tweakLen), 0); - ExpectIntEQ(XMEMCMP(vector, dec, sizeof(vector)), 0); + ExpectIntEQ(wc_AesXtsDecryptSector(&aes, dec, encSector0, + SECTOR_SZ, 0), 0); + ExpectBufEQ(dec, plain, SECTOR_SZ); wc_AesXtsFree(&aes); - ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + /* Decrypt sector 1 and verify roundtrip */ + XMEMSET(dec, 0, sizeof(dec)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsDecryptSector(&aes, dec, encSector1, + SECTOR_SZ, 1), 0); + ExpectBufEQ(dec, plain, SECTOR_SZ); + wc_AesXtsFree(&aes); +#endif /* HAVE_AES_DECRYPT */ + + /* + * 2. wc_AesXtsEncryptConsecutiveSectors + */ + + /* Build reference ciphertext by encrypting each sector individually */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), AES_ENCRYPTION, NULL, INVALID_DEVID), 0); - /* Test bad args for wc_AesXtsEncrypt and wc_AesXtsDecrypt */ - ExpectIntEQ(wc_AesXtsEncrypt(NULL, enc, vector, sizeof(vector), tweak, - tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ(wc_AesXtsEncrypt(&aes, NULL, vector, sizeof(vector), tweak, - tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ(wc_AesXtsEncrypt(&aes, enc, NULL, sizeof(vector), tweak, - tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + for (i = 0; i < NUM_SECTORS; i++) { + ExpectIntEQ(wc_AesXtsEncryptSector(&aes, + encRef + i * SECTOR_SZ, + plain + i * SECTOR_SZ, + SECTOR_SZ, (word64)(5 + i)), 0); + } + wc_AesXtsFree(&aes); + + /* Encrypt all sectors in one call and compare against reference */ + XMEMSET(enc, 0, sizeof(enc)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncryptConsecutiveSectors(&aes, enc, plain, + TOTAL_SZ, 5, SECTOR_SZ), 0); + ExpectBufEQ(enc, encRef, TOTAL_SZ); wc_AesXtsFree(&aes); - /* END wc_AesXtsEncrypt */ #ifdef HAVE_AES_DECRYPT - ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32)/sizeof(byte), + /* Decrypt all sectors at once and verify roundtrip */ + XMEMSET(dec, 0, sizeof(dec)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), AES_DECRYPTION, NULL, INVALID_DEVID), 0); - ExpectIntEQ(wc_AesXtsDecrypt(NULL, dec, enc, sizeof(enc)/sizeof(byte), - tweak, tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ(wc_AesXtsDecrypt(&aes, NULL, enc, sizeof(enc)/sizeof(byte), - tweak, tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectIntEQ(wc_AesXtsDecrypt(&aes, dec, NULL, sizeof(enc)/sizeof(byte), - tweak, tweakLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecryptConsecutiveSectors(&aes, dec, enc, + TOTAL_SZ, 5, SECTOR_SZ), 0); + ExpectBufEQ(dec, plain, TOTAL_SZ); wc_AesXtsFree(&aes); #endif /* HAVE_AES_DECRYPT */ -#endif + /* + * 3. ConsecutiveSectors with a remainder (total not a multiple of sectorSz) + * TOTAL_SZ + WC_AES_BLOCK_SIZE bytes: NUM_SECTORS full sectors plus one + * partial sector of exactly WC_AES_BLOCK_SIZE bytes. + */ + { + #define REMAINDER_SZ (TOTAL_SZ + WC_AES_BLOCK_SIZE) + byte plainR[REMAINDER_SZ]; + byte encR[REMAINDER_SZ]; + byte decR[REMAINDER_SZ]; + byte encRref[REMAINDER_SZ]; + + for (i = 0; i < (int)sizeof(plainR); i++) + plainR[i] = (byte)(i ^ 0xA5); + + /* Build reference: NUM_SECTORS full + 1 partial */ + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + for (i = 0; i < NUM_SECTORS; i++) { + ExpectIntEQ(wc_AesXtsEncryptSector(&aes, + encRref + i * SECTOR_SZ, + plainR + i * SECTOR_SZ, + SECTOR_SZ, (word64)(10 + i)), 0); + } + /* Partial final sector */ + ExpectIntEQ(wc_AesXtsEncryptSector(&aes, + encRref + TOTAL_SZ, + plainR + TOTAL_SZ, + WC_AES_BLOCK_SIZE, (word64)(10 + NUM_SECTORS)), 0); + wc_AesXtsFree(&aes); + + /* ConsecutiveSectors with same data */ + XMEMSET(encR, 0, sizeof(encR)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_ENCRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsEncryptConsecutiveSectors(&aes, encR, plainR, + REMAINDER_SZ, 10, SECTOR_SZ), 0); + ExpectBufEQ(encR, encRref, REMAINDER_SZ); + wc_AesXtsFree(&aes); + +#ifdef HAVE_AES_DECRYPT + XMEMSET(decR, 0, sizeof(decR)); + ExpectIntEQ(wc_AesXtsSetKey(&aes, key32, sizeof(key32), + AES_DECRYPTION, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_AesXtsDecryptConsecutiveSectors(&aes, decR, encR, + REMAINDER_SZ, 10, SECTOR_SZ), 0); + ExpectBufEQ(decR, plainR, REMAINDER_SZ); + wc_AesXtsFree(&aes); +#endif /* HAVE_AES_DECRYPT */ + + #undef REMAINDER_SZ + } + + /* + * 4. Bad args for ConsecutiveSectors + */ + ExpectIntEQ(wc_AesXtsEncryptConsecutiveSectors(NULL, enc, plain, + TOTAL_SZ, 0, SECTOR_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsEncryptConsecutiveSectors(&aes, NULL, plain, + TOTAL_SZ, 0, SECTOR_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsEncryptConsecutiveSectors(&aes, enc, NULL, + TOTAL_SZ, 0, SECTOR_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* sectorSz == 0 */ + ExpectIntEQ(wc_AesXtsEncryptConsecutiveSectors(&aes, enc, plain, + TOTAL_SZ, 0, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* sz < WC_AES_BLOCK_SIZE */ + ExpectIntEQ(wc_AesXtsEncryptConsecutiveSectors(&aes, enc, plain, + WC_AES_BLOCK_SIZE - 1, 0, SECTOR_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#ifdef HAVE_AES_DECRYPT + ExpectIntEQ(wc_AesXtsDecryptConsecutiveSectors(NULL, dec, enc, + TOTAL_SZ, 0, SECTOR_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecryptConsecutiveSectors(&aes, NULL, enc, + TOTAL_SZ, 0, SECTOR_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecryptConsecutiveSectors(&aes, dec, NULL, + TOTAL_SZ, 0, SECTOR_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecryptConsecutiveSectors(&aes, dec, enc, + TOTAL_SZ, 0, 0), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_AesXtsDecryptConsecutiveSectors(&aes, dec, enc, + WC_AES_BLOCK_SIZE - 1, 0, SECTOR_SZ), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif /* HAVE_AES_DECRYPT */ + + #undef SECTOR_SZ + #undef NUM_SECTORS + #undef TOTAL_SZ +#endif return EXPECT_RESULT(); -} /* END test_wc_AesXtsEncryptDecrypt */ +} /* END test_wc_AesXtsEncryptDecryptSector */ #if defined(WOLFSSL_AES_EAX) && defined(WOLFSSL_AES_256) && \ (!defined(HAVE_FIPS) || FIPS_VERSION_GE(5, 3)) && !defined(HAVE_SELFTEST) @@ -6540,12 +8840,7 @@ int test_wc_AesCcm_MonteCarlo(void) Aes aes; WC_RNG rng; byte key[AES_256_KEY_SIZE]; -#if !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS_VERSION) || \ - (HAVE_FIPS_VERSION > 2)) byte nonce[CCM_NONCE_MAX_SZ]; -#else - byte nonce[13]; -#endif byte tag[WC_AES_BLOCK_SIZE]; word32 plainLen = 0, keyLen; int i; diff --git a/tests/api/test_aes.h b/tests/api/test_aes.h index e70cbc195dc..51f0ba3efcd 100644 --- a/tests/api/test_aes.h +++ b/tests/api/test_aes.h @@ -29,21 +29,45 @@ int test_wc_AesSetIV(void); int test_wc_AesEncryptDecryptDirect(void); int test_wc_AesEcbEncryptDecrypt(void); int test_wc_AesCbcEncryptDecrypt(void); +int test_wc_AesCbcEncryptDecrypt_UnalignedBuffers(void); +int test_wc_AesCbc_CrossCipher(void); int test_wc_AesCfbEncryptDecrypt(void); +int test_wc_AesCfb_CrossCipher(void); int test_wc_AesOfbEncryptDecrypt(void); +int test_wc_AesOfb_CrossCipher(void); int test_wc_AesCtsEncryptDecrypt(void); +int test_wc_AesCtsEncryptDecrypt_InPlace(void); +int test_wc_AesCtsEncryptDecrypt_UnalignedBuffers(void); int test_wc_AesCtrSetKey(void); int test_wc_AesCtrEncryptDecrypt(void); +int test_wc_AesCtrEncryptDecrypt_UnalignedBuffers(void); +int test_wc_AesCtr_CrossCipher(void); +int test_wc_AesCtrCounterOverflow(void); int test_wc_AesGcmSetKey(void); int test_wc_AesGcmEncryptDecrypt_Sizes(void); int test_wc_AesGcmEncryptDecrypt(void); +int test_wc_AesGcmEncryptDecrypt_InPlace(void); +int test_wc_AesGcmEncryptDecrypt_UnalignedBuffers(void); +int test_wc_AesGcm_CrossCipher(void); int test_wc_AesGcmMixedEncDecLongIV(void); +int test_wc_AesGcmNonStdNonce(void); int test_wc_AesGcmStream(void); +int test_wc_AesGcmStream_MidStreamState(void); +int test_wc_AesGcmStream_ReinitAfterFinal(void); int test_wc_AesCcmSetKey(void); int test_wc_AesCcmEncryptDecrypt(void); +int test_wc_AesCcmEncryptDecrypt_InPlace(void); +int test_wc_AesCcmEncryptDecrypt_UnalignedBuffers(void); +int test_wc_AesCcmAeadEdgeCases(void); int test_wc_AesXtsSetKey(void); int test_wc_AesXtsEncryptDecrypt_Sizes(void); int test_wc_AesXtsEncryptDecrypt(void); +int test_wc_AesXtsEncryptDecrypt_InPlace(void); +int test_wc_AesXtsEncryptDecrypt_UnalignedBuffers(void); +int test_wc_AesXtsEncryptDecryptSector(void); +int test_wc_AesXtsStream(void); +int test_wc_AesXtsStream_MidStreamState(void); +int test_wc_AesXtsStream_ReinitAfterFinal(void); #if defined(WOLFSSL_AES_EAX) && defined(WOLFSSL_AES_256) && \ (!defined(HAVE_FIPS) || FIPS_VERSION_GE(5, 3)) && !defined(HAVE_SELFTEST) int test_wc_AesEaxVectors(void); @@ -83,22 +107,46 @@ int test_wc_CryptoCb_AesGcm_EncryptDecrypt(void); TEST_DECL_GROUP("aes", test_wc_AesSetIV), \ TEST_DECL_GROUP("aes", test_wc_AesEncryptDecryptDirect), \ TEST_DECL_GROUP("aes", test_wc_AesEcbEncryptDecrypt), \ - TEST_DECL_GROUP("aes", test_wc_AesCbcEncryptDecrypt), \ - TEST_DECL_GROUP("aes", test_wc_AesCfbEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesCbcEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesCbcEncryptDecrypt_UnalignedBuffers), \ + TEST_DECL_GROUP("aes", test_wc_AesCbc_CrossCipher), \ + TEST_DECL_GROUP("aes", test_wc_AesCfbEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesCfb_CrossCipher), \ TEST_DECL_GROUP("aes", test_wc_AesOfbEncryptDecrypt), \ - TEST_DECL_GROUP("aes", test_wc_AesCtsEncryptDecrypt), \ - TEST_DECL_GROUP("aes", test_wc_AesCtrSetKey), \ - TEST_DECL_GROUP("aes", test_wc_AesCtrEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesOfb_CrossCipher), \ + TEST_DECL_GROUP("aes", test_wc_AesCtsEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesCtsEncryptDecrypt_InPlace), \ + TEST_DECL_GROUP("aes", test_wc_AesCtsEncryptDecrypt_UnalignedBuffers), \ + TEST_DECL_GROUP("aes", test_wc_AesCtrSetKey), \ + TEST_DECL_GROUP("aes", test_wc_AesCtrEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesCtrEncryptDecrypt_UnalignedBuffers), \ + TEST_DECL_GROUP("aes", test_wc_AesCtr_CrossCipher), \ + TEST_DECL_GROUP("aes", test_wc_AesCtrCounterOverflow), \ TEST_DECL_GROUP("aes", test_wc_AesGcmSetKey), \ TEST_DECL_GROUP("aes", test_wc_AesGcmEncryptDecrypt_Sizes), \ - TEST_DECL_GROUP("aes", test_wc_AesGcmEncryptDecrypt), \ - TEST_DECL_GROUP("aes", test_wc_AesGcmMixedEncDecLongIV), \ + TEST_DECL_GROUP("aes", test_wc_AesGcmEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesGcmEncryptDecrypt_InPlace), \ + TEST_DECL_GROUP("aes", test_wc_AesGcmEncryptDecrypt_UnalignedBuffers), \ + TEST_DECL_GROUP("aes", test_wc_AesGcm_CrossCipher), \ + TEST_DECL_GROUP("aes", test_wc_AesGcmMixedEncDecLongIV), \ + TEST_DECL_GROUP("aes", test_wc_AesGcmNonStdNonce), \ TEST_DECL_GROUP("aes", test_wc_AesGcmStream), \ + TEST_DECL_GROUP("aes", test_wc_AesGcmStream_MidStreamState), \ + TEST_DECL_GROUP("aes", test_wc_AesGcmStream_ReinitAfterFinal), \ TEST_DECL_GROUP("aes", test_wc_AesCcmSetKey), \ - TEST_DECL_GROUP("aes", test_wc_AesCcmEncryptDecrypt), \ - TEST_DECL_GROUP("aes", test_wc_AesXtsSetKey), \ - TEST_DECL_GROUP("aes", test_wc_AesXtsEncryptDecrypt_Sizes), \ - TEST_DECL_GROUP("aes", test_wc_AesXtsEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesCcmEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesCcmEncryptDecrypt_InPlace), \ + TEST_DECL_GROUP("aes", test_wc_AesCcmEncryptDecrypt_UnalignedBuffers), \ + TEST_DECL_GROUP("aes", test_wc_AesCcmAeadEdgeCases), \ + TEST_DECL_GROUP("aes", test_wc_AesXtsSetKey), \ + TEST_DECL_GROUP("aes", test_wc_AesXtsEncryptDecrypt_Sizes), \ + TEST_DECL_GROUP("aes", test_wc_AesXtsEncryptDecrypt), \ + TEST_DECL_GROUP("aes", test_wc_AesXtsEncryptDecrypt_InPlace), \ + TEST_DECL_GROUP("aes", test_wc_AesXtsEncryptDecrypt_UnalignedBuffers), \ + TEST_DECL_GROUP("aes", test_wc_AesXtsEncryptDecryptSector), \ + TEST_DECL_GROUP("aes", test_wc_AesXtsStream), \ + TEST_DECL_GROUP("aes", test_wc_AesXtsStream_MidStreamState), \ + TEST_DECL_GROUP("aes", test_wc_AesXtsStream_ReinitAfterFinal), \ TEST_DECL_GROUP("aes", test_wc_AesCbc_MonteCarlo), \ TEST_DECL_GROUP("aes", test_wc_AesCtr_MonteCarlo), \ TEST_DECL_GROUP("aes", test_wc_AesGcm_MonteCarlo), \ diff --git a/tests/api/test_ascon.c b/tests/api/test_ascon.c index 18db754ea6c..09c6fc32998 100644 --- a/tests/api/test_ascon.c +++ b/tests/api/test_ascon.c @@ -184,3 +184,163 @@ int test_ascon_aead128(void) #endif return EXPECT_RESULT(); } + +/* + * Ascon-AEAD128 AEAD edge cases: + * - invalid auth tag rejection (DecryptFinal with wrong tag -> ASCON_AUTH_E) + * - empty plaintext with empty AAD (KAT[0]) + * - empty plaintext with non-empty AAD (KAT[1]) + * + * KAT vectors are from the Ascon reference implementation: + * https://github.com/ascon/ascon-c + */ +int test_ascon_aead128_edge_cases(void) +{ + EXPECT_DECLS; +#ifdef HAVE_ASCON + /* Shared key and nonce for all sub-tests (same as KAT[0..N]) */ + static const byte key[ASCON_AEAD128_KEY_SZ] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F + }; + static const byte nonce[ASCON_AEAD128_NONCE_SZ] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F + }; + /* KAT[0]: PT="", AD="" -> CT = tag only */ + static const byte expTag0[ASCON_AEAD128_TAG_SZ] = { + 0x44, 0x27, 0xD6, 0x4B, 0x8E, 0x1E, 0x14, 0x51, + 0xFC, 0x44, 0x59, 0x60, 0xF0, 0x83, 0x9B, 0xB0 + }; + /* KAT[1]: PT="", AD="00" -> CT = tag only */ + static const byte ad1[1] = { 0x00 }; + static const byte expTag1[ASCON_AEAD128_TAG_SZ] = { + 0x10, 0x3A, 0xB7, 0x9D, 0x91, 0x3A, 0x03, 0x21, + 0x28, 0x77, 0x15, 0xA9, 0x79, 0xBB, 0x85, 0x85 + }; + wc_AsconAEAD128* asconAEAD = NULL; + byte tagBuf[ASCON_AEAD128_TAG_SZ]; + byte badTag[ASCON_AEAD128_TAG_SZ]; + byte dummy[1]; /* non-NULL placeholder for 0-length pt/ct args */ + + ExpectNotNull(asconAEAD = wc_AsconAEAD128_New()); + + /* ------------------------------------------------------------------ */ + /* 1. Empty plaintext + empty AAD (KAT[0]) */ + /* ------------------------------------------------------------------ */ + + /* Encrypt and verify tag against KAT */ + ExpectIntEQ(wc_AsconAEAD128_Init(asconAEAD), 0); + ExpectIntEQ(wc_AsconAEAD128_SetKey(asconAEAD, key), 0); + ExpectIntEQ(wc_AsconAEAD128_SetNonce(asconAEAD, nonce), 0); + ExpectIntEQ(wc_AsconAEAD128_SetAD(asconAEAD, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_EncryptUpdate(asconAEAD, dummy, dummy, 0), 0); + XMEMSET(tagBuf, 0, sizeof(tagBuf)); + ExpectIntEQ(wc_AsconAEAD128_EncryptFinal(asconAEAD, tagBuf), 0); + ExpectBufEQ(tagBuf, expTag0, ASCON_AEAD128_TAG_SZ); + wc_AsconAEAD128_Clear(asconAEAD); + + /* Decrypt with correct tag -> success */ + ExpectIntEQ(wc_AsconAEAD128_Init(asconAEAD), 0); + ExpectIntEQ(wc_AsconAEAD128_SetKey(asconAEAD, key), 0); + ExpectIntEQ(wc_AsconAEAD128_SetNonce(asconAEAD, nonce), 0); + ExpectIntEQ(wc_AsconAEAD128_SetAD(asconAEAD, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptUpdate(asconAEAD, dummy, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptFinal(asconAEAD, expTag0), 0); + wc_AsconAEAD128_Clear(asconAEAD); + + /* Decrypt with wrong tag -> ASCON_AUTH_E */ + XMEMCPY(badTag, expTag0, ASCON_AEAD128_TAG_SZ); + badTag[0] ^= 0xff; + ExpectIntEQ(wc_AsconAEAD128_Init(asconAEAD), 0); + ExpectIntEQ(wc_AsconAEAD128_SetKey(asconAEAD, key), 0); + ExpectIntEQ(wc_AsconAEAD128_SetNonce(asconAEAD, nonce), 0); + ExpectIntEQ(wc_AsconAEAD128_SetAD(asconAEAD, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptUpdate(asconAEAD, dummy, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptFinal(asconAEAD, badTag), + WC_NO_ERR_TRACE(ASCON_AUTH_E)); + wc_AsconAEAD128_Clear(asconAEAD); + + /* ------------------------------------------------------------------ */ + /* 2. Empty plaintext + non-empty AAD (KAT[1], AD = {0x00}) */ + /* ------------------------------------------------------------------ */ + + /* Encrypt and verify tag against KAT */ + ExpectIntEQ(wc_AsconAEAD128_Init(asconAEAD), 0); + ExpectIntEQ(wc_AsconAEAD128_SetKey(asconAEAD, key), 0); + ExpectIntEQ(wc_AsconAEAD128_SetNonce(asconAEAD, nonce), 0); + ExpectIntEQ(wc_AsconAEAD128_SetAD(asconAEAD, ad1, sizeof(ad1)), 0); + ExpectIntEQ(wc_AsconAEAD128_EncryptUpdate(asconAEAD, dummy, dummy, 0), 0); + XMEMSET(tagBuf, 0, sizeof(tagBuf)); + ExpectIntEQ(wc_AsconAEAD128_EncryptFinal(asconAEAD, tagBuf), 0); + ExpectBufEQ(tagBuf, expTag1, ASCON_AEAD128_TAG_SZ); + wc_AsconAEAD128_Clear(asconAEAD); + + /* Decrypt with correct tag -> success */ + ExpectIntEQ(wc_AsconAEAD128_Init(asconAEAD), 0); + ExpectIntEQ(wc_AsconAEAD128_SetKey(asconAEAD, key), 0); + ExpectIntEQ(wc_AsconAEAD128_SetNonce(asconAEAD, nonce), 0); + ExpectIntEQ(wc_AsconAEAD128_SetAD(asconAEAD, ad1, sizeof(ad1)), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptUpdate(asconAEAD, dummy, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptFinal(asconAEAD, expTag1), 0); + wc_AsconAEAD128_Clear(asconAEAD); + + /* Decrypt with wrong tag -> ASCON_AUTH_E */ + XMEMCPY(badTag, expTag1, ASCON_AEAD128_TAG_SZ); + badTag[0] ^= 0xff; + ExpectIntEQ(wc_AsconAEAD128_Init(asconAEAD), 0); + ExpectIntEQ(wc_AsconAEAD128_SetKey(asconAEAD, key), 0); + ExpectIntEQ(wc_AsconAEAD128_SetNonce(asconAEAD, nonce), 0); + ExpectIntEQ(wc_AsconAEAD128_SetAD(asconAEAD, ad1, sizeof(ad1)), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptUpdate(asconAEAD, dummy, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptFinal(asconAEAD, badTag), + WC_NO_ERR_TRACE(ASCON_AUTH_E)); + wc_AsconAEAD128_Clear(asconAEAD); + + /* ------------------------------------------------------------------ */ + /* 3. Non-empty plaintext: invalid tag rejection */ + /* ------------------------------------------------------------------ */ + { + static const byte pt[] = { 0x00 }; + byte ct[sizeof(pt)]; + byte encTag[ASCON_AEAD128_TAG_SZ]; + + /* Encrypt one byte */ + XMEMSET(ct, 0, sizeof(ct)); + XMEMSET(encTag, 0, sizeof(encTag)); + ExpectIntEQ(wc_AsconAEAD128_Init(asconAEAD), 0); + ExpectIntEQ(wc_AsconAEAD128_SetKey(asconAEAD, key), 0); + ExpectIntEQ(wc_AsconAEAD128_SetNonce(asconAEAD, nonce), 0); + ExpectIntEQ(wc_AsconAEAD128_SetAD(asconAEAD, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_EncryptUpdate(asconAEAD, ct, pt, + sizeof(pt)), 0); + ExpectIntEQ(wc_AsconAEAD128_EncryptFinal(asconAEAD, encTag), 0); + wc_AsconAEAD128_Clear(asconAEAD); + + /* Decrypt with correct tag -> success */ + ExpectIntEQ(wc_AsconAEAD128_Init(asconAEAD), 0); + ExpectIntEQ(wc_AsconAEAD128_SetKey(asconAEAD, key), 0); + ExpectIntEQ(wc_AsconAEAD128_SetNonce(asconAEAD, nonce), 0); + ExpectIntEQ(wc_AsconAEAD128_SetAD(asconAEAD, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptUpdate(asconAEAD, dummy, ct, + sizeof(ct)), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptFinal(asconAEAD, encTag), 0); + wc_AsconAEAD128_Clear(asconAEAD); + + /* Decrypt with tampered tag -> ASCON_AUTH_E */ + encTag[ASCON_AEAD128_TAG_SZ - 1] ^= 0xff; + ExpectIntEQ(wc_AsconAEAD128_Init(asconAEAD), 0); + ExpectIntEQ(wc_AsconAEAD128_SetKey(asconAEAD, key), 0); + ExpectIntEQ(wc_AsconAEAD128_SetNonce(asconAEAD, nonce), 0); + ExpectIntEQ(wc_AsconAEAD128_SetAD(asconAEAD, dummy, 0), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptUpdate(asconAEAD, dummy, ct, + sizeof(ct)), 0); + ExpectIntEQ(wc_AsconAEAD128_DecryptFinal(asconAEAD, encTag), + WC_NO_ERR_TRACE(ASCON_AUTH_E)); + wc_AsconAEAD128_Clear(asconAEAD); + } + + wc_AsconAEAD128_Free(asconAEAD); +#endif /* HAVE_ASCON */ + return EXPECT_RESULT(); +} /* END test_ascon_aead128_edge_cases */ diff --git a/tests/api/test_ascon.h b/tests/api/test_ascon.h index 7d1da6624fe..4e183b8f3fd 100644 --- a/tests/api/test_ascon.h +++ b/tests/api/test_ascon.h @@ -26,9 +26,11 @@ int test_ascon_hash256(void); int test_ascon_aead128(void); +int test_ascon_aead128_edge_cases(void); -#define TEST_ASCON_DECLS \ - TEST_DECL_GROUP("ascon", test_ascon_hash256), \ - TEST_DECL_GROUP("ascon", test_ascon_aead128) +#define TEST_ASCON_DECLS \ + TEST_DECL_GROUP("ascon", test_ascon_hash256), \ + TEST_DECL_GROUP("ascon", test_ascon_aead128), \ + TEST_DECL_GROUP("ascon", test_ascon_aead128_edge_cases) #endif /* TESTS_API_TEST_ASCON_H */ diff --git a/tests/api/test_chacha.c b/tests/api/test_chacha.c index 974386a4853..e3d531fdb5e 100644 --- a/tests/api/test_chacha.c +++ b/tests/api/test_chacha.c @@ -433,3 +433,187 @@ int test_wc_Chacha_MonteCarlo(void) #endif return EXPECT_RESULT(); } + +/******************************************************************************* + * ChaCha20 counter overflow + ******************************************************************************/ + +/* + * Verify that the ChaCha20 block counter (X[12], a 32-bit value) wraps + * correctly from 0xFFFFFFFF back to 0x00000000. + * + * We set the counter to 0xFFFFFFFF and encrypt 65 bytes in a single call, + * which requires two 64-byte key-stream blocks. We then re-encrypt the same + * data as two separate calls - 64 bytes at counter=0xFFFFFFFF and 1 byte at + * counter=0x00000000 - and confirm the outputs match. + */ +int test_wc_Chacha_CounterOverflow(void) +{ + EXPECT_DECLS; +#ifdef HAVE_CHACHA + ChaCha enc; + static const byte key[CHACHA_MAX_KEY_SZ] = { + 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b, 0x1c,0x1d,0x1e,0x1f + }; + static const byte nonce[CHACHA_IV_BYTES] = { + 0x00,0x00,0x00,0x09, 0x00,0x00,0x00,0x4a, 0x00,0x00,0x00,0x00 + }; + /* 65 bytes of zeroed plaintext - easy to verify, no KAT needed. */ + static const byte plain[65] = { 0 }; + /* combined: single call spanning the 0xFFFFFFFF->0x00000000 boundary */ + byte cipher_combined[65]; + /* per-block: block at counter=0xFFFFFFFF (64 bytes) */ + byte cipher_b0[64]; + /* per-block: first byte of block at counter=0x00000000 (1 byte) */ + byte cipher_b1[1]; + + XMEMSET(&enc, 0, sizeof(enc)); + + /* Encrypt 65 bytes in one shot, starting at counter 0xFFFFFFFF. */ + ExpectIntEQ(wc_Chacha_SetKey(&enc, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&enc, nonce, 0xFFFFFFFFUL), 0); + ExpectIntEQ(wc_Chacha_Process(&enc, cipher_combined, plain, 65), 0); + + /* First 64 bytes: key-stream block at counter 0xFFFFFFFF. */ + ExpectIntEQ(wc_Chacha_SetKey(&enc, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&enc, nonce, 0xFFFFFFFFUL), 0); + ExpectIntEQ(wc_Chacha_Process(&enc, cipher_b0, plain, 64), 0); + + /* Byte 65: first byte of key-stream block at counter 0x00000000. */ + ExpectIntEQ(wc_Chacha_SetKey(&enc, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&enc, nonce, 0x00000000UL), 0); + ExpectIntEQ(wc_Chacha_Process(&enc, cipher_b1, plain + 64, 1), 0); + + /* Combined output must match per-block results. */ + ExpectBufEQ(cipher_combined, cipher_b0, 64); + ExpectBufEQ(cipher_combined + 64, cipher_b1, 1); +#endif + return EXPECT_RESULT(); +} + +/* + * Verify that wc_Chacha_Process works correctly when the output buffer is the + * same as the input buffer (in-place operation). ChaCha20 XORs the keystream + * onto the input byte-by-byte, so in == out is always safe. + */ +int test_wc_Chacha_InPlace(void) +{ + EXPECT_DECLS; +#ifdef HAVE_CHACHA + ChaCha enc; + static const byte key[CHACHA_MAX_KEY_SZ] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x01 + }; + static const byte nonce[CHACHA_IV_BYTES] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x02 + }; + /* 67 bytes: spans one full block (64) plus a partial block tail */ + static const byte plain[67] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00 + }; + byte ref_ct[sizeof(plain)]; + byte buf[sizeof(plain)]; + + XMEMSET(&enc, 0, sizeof(enc)); + + /* Reference ciphertext with separate in/out buffers */ + ExpectIntEQ(wc_Chacha_SetKey(&enc, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&enc, nonce, 1), 0); + ExpectIntEQ(wc_Chacha_Process(&enc, ref_ct, plain, sizeof(plain)), 0); + + /* Encrypt in-place (out == in) - must produce the same ciphertext */ + XMEMCPY(buf, plain, sizeof(buf)); + ExpectIntEQ(wc_Chacha_SetKey(&enc, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&enc, nonce, 1), 0); + ExpectIntEQ(wc_Chacha_Process(&enc, buf, buf, sizeof(buf)), 0); + ExpectBufEQ(buf, ref_ct, sizeof(buf)); + + /* Decrypt in-place (ChaCha20 is symmetric - apply keystream again) */ + ExpectIntEQ(wc_Chacha_SetKey(&enc, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&enc, nonce, 1), 0); + ExpectIntEQ(wc_Chacha_Process(&enc, buf, buf, sizeof(buf)), 0); + ExpectBufEQ(buf, plain, sizeof(buf)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_Chacha_InPlace */ + +/* + * Verify that wc_Chacha_Process produces correct results when both the input + * and output buffers are byte-offset (unaligned). Tests offsets 1, 2, and 3. + * A 67-byte plaintext is used (same as InPlace) to exercise both full-block + * and partial-block tail paths. + */ +int test_wc_Chacha_UnalignedBuffers(void) +{ + EXPECT_DECLS; +#ifdef HAVE_CHACHA + ChaCha enc; + static const byte key[CHACHA_MAX_KEY_SZ] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x01 + }; + static const byte nonce[CHACHA_IV_BYTES] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x02 + }; + static const byte plain[67] = { + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, + 0x00,0x00,0x00 + }; + byte ref_ct[sizeof(plain)]; + byte in_buf[sizeof(plain) + 3], out_buf[sizeof(plain) + 3]; + int off; + + XMEMSET(&enc, 0, sizeof(enc)); + + /* Reference ciphertext with naturally-aligned buffers */ + ExpectIntEQ(wc_Chacha_SetKey(&enc, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&enc, nonce, 1), 0); + ExpectIntEQ(wc_Chacha_Process(&enc, ref_ct, plain, sizeof(plain)), 0); + + /* Encrypt with byte offsets 1, 2, 3 on both in and out */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, plain, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_Chacha_SetKey(&enc, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&enc, nonce, 1), 0); + ExpectIntEQ(wc_Chacha_Process(&enc, out_buf + off, in_buf + off, + sizeof(plain)), 0); + ExpectBufEQ(out_buf + off, ref_ct, sizeof(plain)); + } + + /* Decrypt (ChaCha20 is symmetric) */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, ref_ct, sizeof(plain)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_Chacha_SetKey(&enc, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&enc, nonce, 1), 0); + ExpectIntEQ(wc_Chacha_Process(&enc, out_buf + off, in_buf + off, + sizeof(plain)), 0); + ExpectBufEQ(out_buf + off, plain, sizeof(plain)); + } +#endif + return EXPECT_RESULT(); +} /* END test_wc_Chacha_UnalignedBuffers */ diff --git a/tests/api/test_chacha.h b/tests/api/test_chacha.h index 4e418f55f5d..a212592d1e7 100644 --- a/tests/api/test_chacha.h +++ b/tests/api/test_chacha.h @@ -28,11 +28,17 @@ int test_wc_Chacha_SetKey(void); int test_wc_Chacha_Process(void); int test_wc_Chacha_Process_Chunking(void); int test_wc_Chacha_MonteCarlo(void); +int test_wc_Chacha_CounterOverflow(void); +int test_wc_Chacha_InPlace(void); +int test_wc_Chacha_UnalignedBuffers(void); -#define TEST_CHACHA_DECLS \ - TEST_DECL_GROUP("chacha", test_wc_Chacha_SetKey), \ - TEST_DECL_GROUP("chacha", test_wc_Chacha_Process), \ - TEST_DECL_GROUP("chacha", test_wc_Chacha_Process_Chunking), \ - TEST_DECL_GROUP("chacha", test_wc_Chacha_MonteCarlo) +#define TEST_CHACHA_DECLS \ + TEST_DECL_GROUP("chacha", test_wc_Chacha_SetKey), \ + TEST_DECL_GROUP("chacha", test_wc_Chacha_Process), \ + TEST_DECL_GROUP("chacha", test_wc_Chacha_Process_Chunking), \ + TEST_DECL_GROUP("chacha", test_wc_Chacha_MonteCarlo), \ + TEST_DECL_GROUP("chacha", test_wc_Chacha_CounterOverflow), \ + TEST_DECL_GROUP("chacha", test_wc_Chacha_InPlace), \ + TEST_DECL_GROUP("chacha", test_wc_Chacha_UnalignedBuffers) #endif /* WOLFCRYPT_TEST_CHACHA_H */ diff --git a/tests/api/test_chacha20_poly1305.c b/tests/api/test_chacha20_poly1305.c index e3f57adbe11..9a8ad4032b0 100644 --- a/tests/api/test_chacha20_poly1305.c +++ b/tests/api/test_chacha20_poly1305.c @@ -340,3 +340,609 @@ int test_wc_ChaCha20Poly1305_MonteCarlo(void) #endif return EXPECT_RESULT(); } + +/* + * Testing wc_ChaCha20Poly1305_Init(), wc_ChaCha20Poly1305_UpdateAad(), + * wc_ChaCha20Poly1305_UpdateData(), and wc_ChaCha20Poly1305_Final() + * streaming API using the RFC 8439 Section 2.8.2 test vector. + */ +int test_wc_ChaCha20Poly1305_Stream(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + /* RFC 8439 Section 2.8.2 test vector */ + static const byte key[] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f + }; + static const byte iv[] = { + 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47 + }; + static const byte aad[] = { + 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7 + }; + static const byte plaintext[] = { + 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, + 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, + 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, + 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, + 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, + 0x74, 0x2e + }; + static const byte expCipher[] = { + 0xd3, 0x1a, 0x8d, 0x34, 0x64, 0x8e, 0x60, 0xdb, + 0x7b, 0x86, 0xaf, 0xbc, 0x53, 0xef, 0x7e, 0xc2, + 0xa4, 0xad, 0xed, 0x51, 0x29, 0x6e, 0x08, 0xfe, + 0xa9, 0xe2, 0xb5, 0xa7, 0x36, 0xee, 0x62, 0xd6, + 0x3d, 0xbe, 0xa4, 0x5e, 0x8c, 0xa9, 0x67, 0x12, + 0x82, 0xfa, 0xfb, 0x69, 0xda, 0x92, 0x72, 0x8b, + 0x1a, 0x71, 0xde, 0x0a, 0x9e, 0x06, 0x0b, 0x29, + 0x05, 0xd6, 0xa5, 0xb6, 0x7e, 0xcd, 0x3b, 0x36, + 0x92, 0xdd, 0xbd, 0x7f, 0x2d, 0x77, 0x8b, 0x8c, + 0x98, 0x03, 0xae, 0xe3, 0x28, 0x09, 0x1b, 0x58, + 0xfa, 0xb3, 0x24, 0xe4, 0xfa, 0xd6, 0x75, 0x94, + 0x55, 0x85, 0x80, 0x8b, 0x48, 0x31, 0xd7, 0xbc, + 0x3f, 0xf4, 0xde, 0xf0, 0x8e, 0x4b, 0x7a, 0x9d, + 0xe5, 0x76, 0xd2, 0x65, 0x86, 0xce, 0xc6, 0x4b, + 0x61, 0x16 + }; + static const byte expAuthTag[] = { + 0x1a, 0xe1, 0x0b, 0x59, 0x4f, 0x09, 0xe2, 0x6a, + 0x7e, 0x90, 0x2e, 0xcb, 0xd0, 0x60, 0x06, 0x91 + }; + ChaChaPoly_Aead aead; + byte outCipher[sizeof(plaintext)]; + byte outPlain[sizeof(plaintext)]; + byte outTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + + /* --- Streaming encrypt: AAD in two chunks, plaintext in three chunks --- */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, 6), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad + 6, + (word32)(sizeof(aad) - 6)), 0); + XMEMSET(outCipher, 0, sizeof(outCipher)); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plaintext, + outCipher, 38), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plaintext + 38, + outCipher + 38, 38), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plaintext + 76, + outCipher + 76, (word32)(sizeof(plaintext) - 76)), 0); + XMEMSET(outTag, 0, sizeof(outTag)); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, outTag), 0); + ExpectBufEQ(outCipher, expCipher, sizeof(expCipher)); + ExpectBufEQ(outTag, expAuthTag, sizeof(expAuthTag)); + + /* --- Streaming decrypt: single AAD chunk, ciphertext in three chunks --- */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv, + CHACHA20_POLY1305_AEAD_DECRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, + (word32)sizeof(aad)), 0); + XMEMSET(outPlain, 0, sizeof(outPlain)); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, expCipher, + outPlain, 38), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, expCipher + 38, + outPlain + 38, 38), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, expCipher + 76, + outPlain + 76, (word32)(sizeof(expCipher) - 76)), 0); + XMEMSET(outTag, 0, sizeof(outTag)); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, outTag), 0); + ExpectBufEQ(outPlain, plaintext, sizeof(plaintext)); + ExpectIntEQ(wc_ChaCha20Poly1305_CheckTag(outTag, expAuthTag), 0); + + /* --- Bad args --- */ + /* wc_ChaCha20Poly1305_Init: NULL aead */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(NULL, key, iv, + CHACHA20_POLY1305_AEAD_ENCRYPT), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* wc_ChaCha20Poly1305_Init: NULL key */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, NULL, iv, + CHACHA20_POLY1305_AEAD_ENCRYPT), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* wc_ChaCha20Poly1305_Init: NULL iv */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, NULL, + CHACHA20_POLY1305_AEAD_ENCRYPT), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* wc_ChaCha20Poly1305_UpdateAad: NULL aead */ + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(NULL, aad, (word32)sizeof(aad)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* wc_ChaCha20Poly1305_UpdateData: NULL aead */ + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(NULL, plaintext, outCipher, + (word32)sizeof(plaintext)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* wc_ChaCha20Poly1305_Final: NULL aead */ + ExpectIntEQ(wc_ChaCha20Poly1305_Final(NULL, outTag), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* wc_ChaCha20Poly1305_Final: wrong state (INIT, not AAD/DATA) */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, outTag), + WC_NO_ERR_TRACE(BAD_STATE_E)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ChaCha20Poly1305_Stream */ + +/* + * ChaCha20-Poly1305 AEAD edge cases: + * - invalid auth tag rejection (one-shot API) + * - empty plaintext with non-empty AAD (streaming API) + */ +int test_wc_ChaCha20Poly1305_AeadEdgeCases(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + /* RFC 8439 Section 2.8.2 key/iv/aad */ + static const byte key[] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f + }; + static const byte iv[] = { + 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47 + }; + static const byte aad[] = { + 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7 + }; + static const byte plaintext[] = { + 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, + 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63, + 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, + 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, + 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, + 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, + 0x74, 0x2e + }; + ChaChaPoly_Aead aead; + byte cipherOut[sizeof(plaintext)]; + byte plainOut[sizeof(plaintext)]; + byte authTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + byte authTagDecrypt[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + + /* --- Invalid tag rejection (one-shot API) --- + * Encrypt with correct key/iv/aad/pt, then flip a tag byte and + * verify that Decrypt returns MAC_CMP_FAILED_E. */ + XMEMSET(cipherOut, 0, sizeof(cipherOut)); + XMEMSET(authTag, 0, sizeof(authTag)); + ExpectIntEQ(wc_ChaCha20Poly1305_Encrypt(key, iv, aad, sizeof(aad), + plaintext, sizeof(plaintext), cipherOut, authTag), 0); + authTag[0] ^= 0xff; + XMEMSET(plainOut, 0, sizeof(plainOut)); + ExpectIntEQ(wc_ChaCha20Poly1305_Decrypt(key, iv, aad, sizeof(aad), + cipherOut, sizeof(cipherOut), authTag, plainOut), + WC_NO_ERR_TRACE(MAC_CMP_FAILED_E)); + + /* --- Empty plaintext with non-empty AAD (streaming API) --- + * Init + UpdateAad + Final, no UpdateData call. + * Correct computed tag must verify; tampered tag must fail. */ + XMEMSET(authTag, 0, sizeof(authTag)); + XMEMSET(authTagDecrypt, 0, sizeof(authTagDecrypt)); + + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, authTag), 0); + + /* Decrypt with same AAD and no data; verify tag matches */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv, + CHACHA20_POLY1305_AEAD_DECRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, authTagDecrypt), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_CheckTag(authTagDecrypt, authTag), 0); + + /* Tamper and verify CheckTag rejects it */ + authTagDecrypt[0] ^= 0xff; + ExpectIntEQ(wc_ChaCha20Poly1305_CheckTag(authTagDecrypt, authTag), + WC_NO_ERR_TRACE(MAC_CMP_FAILED_E)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ChaCha20Poly1305_AeadEdgeCases */ + +/******************************************************************************* + * ChaCha20-Poly1305 mid-stream state corruption + ******************************************************************************/ + +/* + * Verify that the ChaCha20-Poly1305 streaming state machine rejects operations + * called in the wrong order, and handles post-Final reuse gracefully. + * + * State transitions: INIT(0) -> READY(1) -> AAD(2) -> DATA(3) + * UpdateAad: READY or AAD only + * UpdateData: READY, AAD, or DATA + * Final: AAD or DATA only + * After Final, ForceZero resets the struct to all-zeros (state == INIT). + */ +int test_wc_ChaCha20Poly1305_MidStreamState(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + static const byte key[CHACHA20_POLY1305_AEAD_KEYSIZE] = { + 0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97, + 0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f + }; + static const byte iv[CHACHA20_POLY1305_AEAD_IV_SIZE] = { + 0x07,0x00,0x00,0x00, 0x40,0x41,0x42,0x43, 0x44,0x45,0x46,0x47 + }; + static const byte aad[8] = { 0x50,0x51,0x52,0x53, 0xc0,0xc1,0xc2,0xc3 }; + static const byte plain[8] = { 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07 }; + ChaChaPoly_Aead aead; + byte ct[8]; + byte tag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + + /* ------------------------------------------------------------------ + * Test 1: UpdateAad after UpdateData (DATA state) -> BAD_STATE_E + * Once UpdateData has been called the state advances to DATA and any + * further UpdateAad calls must be rejected. + * ------------------------------------------------------------------ */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plain, ct, + sizeof(plain)), 0); + /* State is now DATA - UpdateAad must fail. */ + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + /* Clean up the aead object so the next test starts fresh. */ + XMEMSET(&aead, 0, sizeof(aead)); + + /* ------------------------------------------------------------------ + * Test 2: UpdateData in INIT state (no Init called) -> BAD_STATE_E + * state == INIT(0): UpdateData requires READY(1), AAD(2), or DATA(3). + * ------------------------------------------------------------------ */ + /* aead was zeroed above so state == INIT. */ + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plain, ct, sizeof(plain)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* ------------------------------------------------------------------ + * Test 3: Reuse after Final - state reset to INIT by ForceZero + * wc_ChaCha20Poly1305_Final calls ForceZero on the whole struct, which + * sets state back to INIT(0). Any subsequent streaming call must fail. + * ------------------------------------------------------------------ */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), 0); + /* First Final succeeds (state == AAD). */ + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, tag), 0); + /* State is now INIT (all zeros after ForceZero). */ + /* Second Final must fail. */ + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, tag), + WC_NO_ERR_TRACE(BAD_STATE_E)); + /* UpdateAad after Final must also fail. */ + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + /* UpdateData after Final must also fail. */ + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plain, ct, sizeof(plain)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* ------------------------------------------------------------------ + * Test 4: Direct state field corruption to an invalid value + * Forcing state to a value outside the defined enum range makes all + * state-checking calls return BAD_STATE_E. + * ------------------------------------------------------------------ */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + /* Corrupt state: 99 is not a valid CHACHA20_POLY1305_STATE_* value. */ + aead.state = 99; + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plain, ct, sizeof(plain)), + WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, tag), + WC_NO_ERR_TRACE(BAD_STATE_E)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ChaCha20Poly1305_MidStreamState */ + +/******************************************************************************* + * ChaCha20-Poly1305 re-initialization after Final + ******************************************************************************/ + +/* + * Verify that a ChaCha20-Poly1305 AEAD context can be re-initialized and + * reused after wc_ChaCha20Poly1305_Final has been called. + * + * wc_ChaCha20Poly1305_Final calls ForceZero on the whole ChaChaPoly_Aead + * struct, so a fresh wc_ChaCha20Poly1305_Init is needed before the next + * session. These tests confirm: + * + * 1. Re-init with the same key and IV produces identical ciphertext and tag. + * 2. Re-init with a different IV produces different ciphertext and tag. + * 3. Re-init after an *abandoned* session (Init but no Final) also works. + */ +int test_wc_ChaCha20Poly1305_ReinitAfterFinal(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + static const byte key[CHACHA20_POLY1305_AEAD_KEYSIZE] = { + 0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97, + 0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f + }; + static const byte iv1[CHACHA20_POLY1305_AEAD_IV_SIZE] = { + 0x07,0x00,0x00,0x00, 0x40,0x41,0x42,0x43, 0x44,0x45,0x46,0x47 + }; + /* Distinct IV - same length, one byte different. */ + static const byte iv2[CHACHA20_POLY1305_AEAD_IV_SIZE] = { + 0x07,0x00,0x00,0x00, 0x40,0x41,0x42,0x43, 0x44,0x45,0x46,0x48 + }; + static const byte aad[] = { + 0x50,0x51,0x52,0x53, 0xc0,0xc1,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7 + }; + static const byte plain[] = { + 0x4c,0x61,0x64,0x69, 0x65,0x73,0x20,0x61, + 0x6e,0x64,0x20,0x47, 0x65,0x6e,0x74,0x6c + }; + ChaChaPoly_Aead aead; + byte ct1[sizeof(plain)]; + byte ct2[sizeof(plain)]; + byte ct3[sizeof(plain)]; + byte tag1[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + byte tag2[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + byte tag3[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + + /* ---- Session 1: establish baseline ciphertext and tag ---- */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv1, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plain, ct1, + sizeof(plain)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, tag1), 0); + + /* ---- Session 2: re-init with the same key and IV ---- */ + /* aead was ForceZero'd by Final; Init must succeed. */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv1, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plain, ct2, + sizeof(plain)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, tag2), 0); + /* Same key + IV must produce identical output. */ + ExpectBufEQ(ct2, ct1, sizeof(ct1)); + ExpectBufEQ(tag2, tag1, sizeof(tag1)); + + /* ---- Session 3: re-init with a different IV ---- */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv2, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plain, ct3, + sizeof(plain)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, tag3), 0); + /* Different IV must produce different ciphertext and tag. */ + ExpectIntNE(XMEMCMP(ct3, ct1, sizeof(ct1)), 0); + ExpectIntNE(XMEMCMP(tag3, tag1, sizeof(tag1)), 0); + + /* ---- Session 4: re-init after an abandoned session ---- + * Start a session (Init + UpdateAad) but never call Final. + * Then re-init and complete normally - must match session 1. */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv2, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), 0); + /* Abandon this session - manually reset before re-init. */ + XMEMSET(&aead, 0, sizeof(aead)); + /* Now re-init with iv1 and verify we get session-1 output again. */ + ExpectIntEQ(wc_ChaCha20Poly1305_Init(&aead, key, iv1, + CHACHA20_POLY1305_AEAD_ENCRYPT), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateAad(&aead, aad, sizeof(aad)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_UpdateData(&aead, plain, ct2, + sizeof(plain)), 0); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, tag2), 0); + ExpectBufEQ(ct2, ct1, sizeof(ct1)); + ExpectBufEQ(tag2, tag1, sizeof(tag1)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ChaCha20Poly1305_ReinitAfterFinal */ + +/* + * Verify that wc_ChaCha20Poly1305_Encrypt and wc_ChaCha20Poly1305_Decrypt work + * correctly when the plaintext/ciphertext pointer is the same buffer (in-place + * operation). The cipher uses a ChaCha20 keystream XOR, so in == out is safe. + * The Poly1305 tag is always a separate output buffer. + * + * RFC 8439 2.8.2 key, IV, and AAD are used with a 64-byte counter-pattern + * plaintext (self-consistency: reference ciphertext computed at test time). + */ +int test_wc_ChaCha20Poly1305_InPlace(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + static const byte key[CHACHA20_POLY1305_AEAD_KEYSIZE] = { + 0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97, + 0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f + }; + static const byte iv[CHACHA20_POLY1305_AEAD_IV_SIZE] = { + 0x07,0x00,0x00,0x00, 0x40,0x41,0x42,0x43, 0x44,0x45,0x46,0x47 + }; + static const byte aad[12] = { + 0x50,0x51,0x52,0x53, 0xc0,0xc1,0xc2,0xc3, 0xc4,0xc5,0xc6,0xc7 + }; + /* 67-byte counter pattern: spans one full ChaCha20 block (64 B) plus + * a 3-byte partial tail, exercising both full-block and leftover paths. */ + static const byte plain[67] = { + 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b, 0x1c,0x1d,0x1e,0x1f, + 0x20,0x21,0x22,0x23, 0x24,0x25,0x26,0x27, + 0x28,0x29,0x2a,0x2b, 0x2c,0x2d,0x2e,0x2f, + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x38,0x39,0x3a,0x3b, 0x3c,0x3d,0x3e,0x3f, + 0x40,0x41,0x42 + }; + byte ref_ct[sizeof(plain)], ref_tag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + byte buf[sizeof(plain)], tag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + + /* Reference ciphertext with separate in/out buffers */ + ExpectIntEQ(wc_ChaCha20Poly1305_Encrypt(key, iv, + aad, sizeof(aad), plain, sizeof(plain), ref_ct, ref_tag), 0); + + /* Encrypt in-place (outCiphertext == inPlaintext) */ + XMEMCPY(buf, plain, sizeof(buf)); + XMEMSET(tag, 0, sizeof(tag)); + ExpectIntEQ(wc_ChaCha20Poly1305_Encrypt(key, iv, + aad, sizeof(aad), buf, sizeof(buf), buf, tag), 0); + ExpectBufEQ(buf, ref_ct, sizeof(buf)); + ExpectBufEQ(tag, ref_tag, sizeof(tag)); + + /* Decrypt in-place (outPlaintext == inCiphertext) */ + ExpectIntEQ(wc_ChaCha20Poly1305_Decrypt(key, iv, + aad, sizeof(aad), buf, sizeof(buf), tag, buf), 0); + ExpectBufEQ(buf, plain, sizeof(buf)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ChaCha20Poly1305_InPlace */ + +/* + * Verify that wc_ChaCha20Poly1305_Encrypt and wc_ChaCha20Poly1305_Decrypt + * produce correct results when plaintext, ciphertext, and AAD buffers are + * byte-offset (unaligned). Tests offsets 1, 2, and 3. + */ +int test_wc_ChaCha20Poly1305_UnalignedBuffers(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + /* Same key / IV / AAD as InPlace test */ + static const byte key[CHACHA20_POLY1305_AEAD_KEYSIZE] = { + 0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97, + 0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f + }; + static const byte iv[CHACHA20_POLY1305_AEAD_IV_SIZE] = { + 0x07,0x00,0x00,0x00, 0x40,0x41,0x42,0x43, 0x44,0x45,0x46,0x47 + }; + static const byte aad[12] = { + 0x50,0x51,0x52,0x53, 0xc0,0xc1,0xc2,0xc3, 0xc4,0xc5,0xc6,0xc7 + }; + /* 67-byte counter pattern - same as InPlace test */ + static const byte plain[67] = { + 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b, 0x1c,0x1d,0x1e,0x1f, + 0x20,0x21,0x22,0x23, 0x24,0x25,0x26,0x27, + 0x28,0x29,0x2a,0x2b, 0x2c,0x2d,0x2e,0x2f, + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x38,0x39,0x3a,0x3b, 0x3c,0x3d,0x3e,0x3f, + 0x40,0x41,0x42 + }; + byte ref_ct[sizeof(plain)], ref_tag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + byte in_buf[sizeof(plain) + 3], out_buf[sizeof(plain) + 3]; + byte aad_buf[sizeof(aad) + 3]; + byte tag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + int off; + + /* Reference ciphertext/tag with naturally-aligned buffers */ + ExpectIntEQ(wc_ChaCha20Poly1305_Encrypt(key, iv, + aad, sizeof(aad), plain, sizeof(plain), ref_ct, ref_tag), 0); + + /* Encrypt with byte offsets 1, 2, 3 on plaintext, ciphertext, and AAD */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, plain, sizeof(plain)); + XMEMCPY(aad_buf + off, aad, sizeof(aad)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + XMEMSET(tag, 0, sizeof(tag)); + ExpectIntEQ(wc_ChaCha20Poly1305_Encrypt(key, iv, + aad_buf + off, sizeof(aad), in_buf + off, sizeof(plain), + out_buf + off, tag), 0); + ExpectBufEQ(out_buf + off, ref_ct, sizeof(plain)); + ExpectBufEQ(tag, ref_tag, sizeof(tag)); + } + + /* Decrypt with byte offsets 1, 2, 3 */ + for (off = 1; off <= 3 && EXPECT_SUCCESS(); off++) { + XMEMCPY(in_buf + off, ref_ct, sizeof(plain)); + XMEMCPY(aad_buf + off, aad, sizeof(aad)); + XMEMSET(out_buf, 0, sizeof(out_buf)); + ExpectIntEQ(wc_ChaCha20Poly1305_Decrypt(key, iv, + aad_buf + off, sizeof(aad), in_buf + off, sizeof(plain), + ref_tag, out_buf + off), 0); + ExpectBufEQ(out_buf + off, plain, sizeof(plain)); + } +#endif + return EXPECT_RESULT(); +} /* END test_wc_ChaCha20Poly1305_UnalignedBuffers */ + +/* + * Cross-cipher test: ChaCha20-Poly1305 encrypts plaintext using ChaCha20 with + * the block counter starting at 1. Counter 0 is reserved for generating the + * 32-byte Poly1305 one-time key; plaintext encryption begins at counter 1. + * + * This test verifies that the ciphertext produced by + * wc_ChaCha20Poly1305_Encrypt equals the output of wc_Chacha_Process when + * the counter is initialised to 1 via wc_Chacha_SetIV(ctx, iv, 1). + */ +int test_wc_ChaCha20Poly1305_CrossCipher(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + ChaCha ctx; + /* Same key / IV / plain as the InPlace and UnalignedBuffers tests */ + static const byte key[CHACHA20_POLY1305_AEAD_KEYSIZE] = { + 0x80,0x81,0x82,0x83, 0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x8b, 0x8c,0x8d,0x8e,0x8f, + 0x90,0x91,0x92,0x93, 0x94,0x95,0x96,0x97, + 0x98,0x99,0x9a,0x9b, 0x9c,0x9d,0x9e,0x9f + }; + static const byte iv[CHACHA20_POLY1305_AEAD_IV_SIZE] = { + 0x07,0x00,0x00,0x00, 0x40,0x41,0x42,0x43, 0x44,0x45,0x46,0x47 + }; + static const byte aad[12] = { + 0x50,0x51,0x52,0x53, 0xc0,0xc1,0xc2,0xc3, 0xc4,0xc5,0xc6,0xc7 + }; + static const byte plain[67] = { + 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07, + 0x08,0x09,0x0a,0x0b, 0x0c,0x0d,0x0e,0x0f, + 0x10,0x11,0x12,0x13, 0x14,0x15,0x16,0x17, + 0x18,0x19,0x1a,0x1b, 0x1c,0x1d,0x1e,0x1f, + 0x20,0x21,0x22,0x23, 0x24,0x25,0x26,0x27, + 0x28,0x29,0x2a,0x2b, 0x2c,0x2d,0x2e,0x2f, + 0x30,0x31,0x32,0x33, 0x34,0x35,0x36,0x37, + 0x38,0x39,0x3a,0x3b, 0x3c,0x3d,0x3e,0x3f, + 0x40,0x41,0x42 + }; + byte aead_ct[sizeof(plain)], aead_tag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE]; + byte chacha_ct[sizeof(plain)]; + + /* ChaCha20-Poly1305 ciphertext */ + ExpectIntEQ(wc_ChaCha20Poly1305_Encrypt(key, iv, + aad, sizeof(aad), plain, sizeof(plain), aead_ct, aead_tag), 0); + + /* ChaCha20 ciphertext with counter=1 (counter 0 is the Poly1305 key block) */ + ExpectIntEQ(wc_Chacha_SetKey(&ctx, key, sizeof(key)), 0); + ExpectIntEQ(wc_Chacha_SetIV(&ctx, iv, 1), 0); + ExpectIntEQ(wc_Chacha_Process(&ctx, chacha_ct, plain, sizeof(plain)), 0); + + /* ChaCha20-Poly1305 ciphertext must equal ChaCha20(counter=1) ciphertext */ + ExpectBufEQ(aead_ct, chacha_ct, sizeof(plain)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ChaCha20Poly1305_CrossCipher */ diff --git a/tests/api/test_chacha20_poly1305.h b/tests/api/test_chacha20_poly1305.h index 88b924fadca..398d1d939c0 100644 --- a/tests/api/test_chacha20_poly1305.h +++ b/tests/api/test_chacha20_poly1305.h @@ -27,10 +27,24 @@ int test_wc_ChaCha20Poly1305_aead(void); int test_wc_XChaCha20Poly1305_aead(void); int test_wc_ChaCha20Poly1305_MonteCarlo(void); +int test_wc_ChaCha20Poly1305_Stream(void); +int test_wc_ChaCha20Poly1305_AeadEdgeCases(void); +int test_wc_ChaCha20Poly1305_MidStreamState(void); +int test_wc_ChaCha20Poly1305_ReinitAfterFinal(void); +int test_wc_ChaCha20Poly1305_InPlace(void); +int test_wc_ChaCha20Poly1305_UnalignedBuffers(void); +int test_wc_ChaCha20Poly1305_CrossCipher(void); -#define TEST_CHACHA20_POLY1305_DECLS \ - TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_aead), \ - TEST_DECL_GROUP("xchacha20-poly1305", test_wc_XChaCha20Poly1305_aead), \ - TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_MonteCarlo) +#define TEST_CHACHA20_POLY1305_DECLS \ + TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_aead), \ + TEST_DECL_GROUP("xchacha20-poly1305", test_wc_XChaCha20Poly1305_aead), \ + TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_MonteCarlo), \ + TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_Stream), \ + TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_AeadEdgeCases), \ + TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_MidStreamState), \ + TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_ReinitAfterFinal), \ + TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_InPlace), \ + TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_UnalignedBuffers), \ + TEST_DECL_GROUP("chacha20-poly1305", test_wc_ChaCha20Poly1305_CrossCipher) #endif /* WOLFCRYPT_TEST_CHACHA20_POLY1305_H */