From dcbd1175856a1ec694f7a212f2c960aeb7987596 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Fri, 17 Apr 2026 14:32:34 -0700 Subject: [PATCH 1/3] fix: ChaCha20-Poly1305 Final() allow empty plaintext MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wc_ChaCha20Poly1305_Final() rejected CHACHA20_POLY1305_STATE_READY, blocking use when no data or AAD was ever provided. RFC 8439 ยง2.8 permits empty plaintext and produces a well-defined authentication tag. Found via Wycheproof test vectors. --- wolfcrypt/src/chacha20_poly1305.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wolfcrypt/src/chacha20_poly1305.c b/wolfcrypt/src/chacha20_poly1305.c index 75b9b094d13..a53cec65e8d 100644 --- a/wolfcrypt/src/chacha20_poly1305.c +++ b/wolfcrypt/src/chacha20_poly1305.c @@ -275,7 +275,8 @@ int wc_ChaCha20Poly1305_Final(ChaChaPoly_Aead* aead, if (aead == NULL || outAuthTag == NULL) { return BAD_FUNC_ARG; } - if (aead->state != CHACHA20_POLY1305_STATE_AAD && + if (aead->state != CHACHA20_POLY1305_STATE_READY && + aead->state != CHACHA20_POLY1305_STATE_AAD && aead->state != CHACHA20_POLY1305_STATE_DATA) { return BAD_STATE_E; } From 933b7c2eb8aa54efbe344f72a430933516ba0d56 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Thu, 30 Apr 2026 13:53:32 -0700 Subject: [PATCH 2/3] test: update ChaCha20-Poly1305 bad-state test for empty-plaintext fix Replace the READY-state BAD_STATE_E assertion (which the fix renders obsolete) with a positive Wycheproof tc2 test: empty plaintext + empty AAD through the streaming API must produce the correct authentication tag per RFC 8439 Section 2.8. --- wolfcrypt/test/test.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index cd3275650bd..0b32f05636b 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -11790,6 +11790,22 @@ static wc_test_ret_t chacha20_poly1305_oneshot_test(void) static wc_test_ret_t chacha20_poly1305_stream_param_test(void) { + /* Wycheproof tc2: empty plaintext + empty AAD (valid per RFC 8439 Section 2.8) */ + WOLFSSL_SMALL_STACK_STATIC const byte key_tc2[] = { + 0x80, 0xba, 0x31, 0x92, 0xc8, 0x03, 0xce, 0x96, + 0x5e, 0xa3, 0x71, 0xd5, 0xff, 0x07, 0x3c, 0xf0, + 0xf4, 0x3b, 0x6a, 0x2a, 0xb5, 0x76, 0xb2, 0x08, + 0x42, 0x6e, 0x11, 0x40, 0x9c, 0x09, 0xb9, 0xb0 + }; + WOLFSSL_SMALL_STACK_STATIC const byte iv_tc2[] = { + 0x4d, 0xa5, 0xbf, 0x8d, 0xfd, 0x58, 0x52, 0xc1, + 0xea, 0x12, 0x37, 0x9d + }; + WOLFSSL_SMALL_STACK_STATIC const byte authTag_tc2[] = { + 0x76, 0xac, 0xb3, 0x42, 0xcf, 0x31, 0x66, 0xa5, + 0xb6, 0x3c, 0x0c, 0x0e, 0xa1, 0x38, 0x3c, 0x8d + }; + const byte* key1 = chacha20_poly1305_test1_key; const byte* iv1 = chacha20_poly1305_test1_iv; const byte* aad1 = chacha20_poly1305_test1_aad; @@ -11863,10 +11879,16 @@ static wc_test_ret_t chacha20_poly1305_stream_param_test(void) err = wc_ChaCha20Poly1305_Final(&aead, generatedAuthTag); if (err != WC_NO_ERR_TRACE(BAD_STATE_E)) return WC_TEST_RET_ENC_EC(err); - aead.state = CHACHA20_POLY1305_STATE_READY; + /* Wycheproof tc2: empty plaintext + empty AAD must succeed (RFC 8439 Section 2.8) */ + err = wc_ChaCha20Poly1305_Init(&aead, key_tc2, iv_tc2, + CHACHA20_POLY1305_AEAD_ENCRYPT); + if (err != 0) + return WC_TEST_RET_ENC_EC(err); err = wc_ChaCha20Poly1305_Final(&aead, generatedAuthTag); - if (err != WC_NO_ERR_TRACE(BAD_STATE_E)) + if (err != 0) return WC_TEST_RET_ENC_EC(err); + if (XMEMCMP(generatedAuthTag, authTag_tc2, sizeof(authTag_tc2)) != 0) + return WC_TEST_RET_ENC_NC; return 0; } From 89460fae2dec1d38865f61c015c90f0821911843 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Thu, 4 Jun 2026 15:45:08 -0700 Subject: [PATCH 3/3] test: update API test for Init->Final READY state acceptance Update test_wc_ChaCha20Poly1305_Stream to expect success (not BAD_STATE_E) when calling Final() immediately after Init(), since empty plaintext + empty AAD is valid per RFC 8439 Section 2.8. --- tests/api/test_chacha20_poly1305.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/api/test_chacha20_poly1305.c b/tests/api/test_chacha20_poly1305.c index 6f90c71712c..921cf20b491 100644 --- a/tests/api/test_chacha20_poly1305.c +++ b/tests/api/test_chacha20_poly1305.c @@ -526,11 +526,11 @@ int test_wc_ChaCha20Poly1305_Stream(void) /* 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) */ + /* wc_ChaCha20Poly1305_Final: READY state (Init, no UpdateAad/UpdateData) + * is valid -- RFC 8439 Section 2.8 permits empty plaintext + empty AAD. */ 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)); + ExpectIntEQ(wc_ChaCha20Poly1305_Final(&aead, outTag), 0); #endif return EXPECT_RESULT(); } /* END test_wc_ChaCha20Poly1305_Stream */