ANT-2026-RSSMAMA7 · wolfssl/wolfssl
crypto-failure high
Severity Claude high · Security research firm high · Maintainer high
Discovered by Claude Mythos Preview
Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Calif.
ANT-2026-RSSMAMA7: wolfSSL EVP_CipherFinal does not verify the Poly1305 tag on ChaCha20-Poly1305 decrypt
In wolfSSL's OpenSSL-compatibility EVP layer, wolfSSL_EVP_CipherFinal() (wolfcrypt/src/evp.c) finalised ChaCha20-Poly1305 decryption by computing the Poly1305 tag into ctx->authTag, overwriting the expected tag the caller had supplied through EVP_CTRL_AEAD_SET_TAG, and then returned success without comparing the two. Any application that decrypts with EVP_chacha20_poly1305() and relies on EVP_DecryptFinal_ex()/EVP_CipherFinal() to reject forgeries, including wolfSSL's own QUIC helper wolfSSL_quic_aead_decrypt() when TLS_CHACHA20_POLY1305_SHA256 is negotiated, therefore accepted tampered or forged ciphertext as authentic. The issue is tracked as CVE-2026-5479 (GHSA-3xr8-r75g-g9c6) and was fixed by commit 1faddd640, which is included in wolfSSL 5.9.1.
Target
Project: wolfSSL
Location: wolfcrypt/src/evp.c:wolfSSL_EVP_CipherFinal
Discovery: static analysis — not yet dynamically reproduced
Technical Details
Root cause. In wolfSSL_EVP_CipherFinal() (wolfcrypt/src/evp.c), the WC_CHACHA20_POLY1305_TYPE case had no ctx->enc branch: for both encryption and decryption it called wc_ChaCha20Poly1305_Final(&ctx->cipher.chachaPoly, ctx->authTag) and returned WOLFSSL_SUCCESS whenever that call succeeded. wc_ChaCha20Poly1305_Final() only generates a tag, so on the decrypt path it overwrote the expected tag that EVP_CIPHER_CTX_ctrl(..., EVP_CTRL_AEAD_SET_TAG, ...) had copied into ctx->authTag. wc_ChaCha20Poly1305_CheckTag() was not called anywhere in evp.c, and wolfSSL_EVP_DecryptFinal_ex() is a plain passthrough to wolfSSL_EVP_CipherFinal(), so no tag comparison took place at any point. The adjacent AES-GCM branch of the same function does verify the tag.
Reach and preconditions. The code is compiled when the EVP compatibility layer is enabled (OPENSSL_EXTRA) together with HAVE_CHACHA/HAVE_POLY1305. It is reached by any caller that follows the standard OpenSSL AEAD pattern: decrypt with EVP_chacha20_poly1305(), set the received tag with EVP_CTRL_AEAD_SET_TAG, and treat a successful EVP_DecryptFinal_ex()/EVP_CipherFinal() as proof of authenticity. wolfSSL's QUIC integration uses exactly this pattern: wolfSSL_quic_get_aead() maps TLS_CHACHA20_POLY1305_SHA256 to EVP_chacha20_poly1305(), and wolfSSL_quic_aead_decrypt() (src/quic.c) sets the tag via EVP_CTRL_AEAD_SET_TAG and relies solely on the return value of wolfSSL_EVP_CipherFinal(). wolfSSL's native TLS record layer calls the wolfCrypt ChaCha20-Poly1305 primitives directly and does not go through this EVP path.
Impact. Loss of AEAD integrity and authenticity for ChaCha20-Poly1305 via the EVP interface: an attacker who can modify or inject ciphertext has forged or tampered messages accepted, and the resulting plaintext is returned to the application as if it had been authenticated. The published advisory (CVE-2026-5479 / GHSA-3xr8-r75g-g9c6, CWE-354) rates the issue High, CVSS 3.1 8.1 (AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N).
Fix. Commit 1faddd640edc8195c1fb7cb3904876e434ecab87 ("evp: verify Poly1305 tag on ChaCha20-Poly1305 decrypt"), merged via wolfSSL/wolfssl#10102 on 2026-04-06 and included in the wolfSSL 5.9.1 release (the 5.9.0 release does not contain it), saves the expected tag before calling wc_ChaCha20Poly1305_Final() and, on the decrypt path, compares it against the computed tag with wc_ChaCha20Poly1305_CheckTag(), returning WOLFSSL_FAILURE on mismatch. A regression test was added asserting that EVP_DecryptFinal_ex() rejects a forged tag.
case WC_CHACHA20_POLY1305_TYPE:
{
byte computedTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
if (!ctx->enc) {
/* Save the expected tag before _Final() overwrites ctx->authTag */
XMEMCPY(computedTag, ctx->authTag, sizeof(computedTag));
}
if (wc_ChaCha20Poly1305_Final(&ctx->cipher.chachaPoly, ctx->authTag) != 0) {
WOLFSSL_MSG("wc_ChaCha20Poly1305_Final failed");
return WOLFSSL_FAILURE;
}
if (!ctx->enc) {
int tagErr = wc_ChaCha20Poly1305_CheckTag(computedTag, ctx->authTag);
ForceZero(computedTag, sizeof(computedTag));
if (tagErr != 0) {
WOLFSSL_MSG("ChaCha20-Poly1305 tag mismatch");
return WOLFSSL_FAILURE;
}
}
*outl = 0;
return WOLFSSL_SUCCESS;
}
Reproduction
This finding was identified by static analysis and has not yet been dynamically reproduced. The Technical Details section above describes the code path; a trigger input is not included.
[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]
Acknowledgement
This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-RSSMAMA7.
Reference: ANT-2026-RSSMAMA7
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure
Triage and disclosure were performed by Calif.
- Verdict
- true positive
- Severity
- high
The change that resolved this finding.
diff --git a/tests/api/test_evp_cipher.c b/tests/api/test_evp_cipher.c
index b4e37df7a28..1e88da9979c 100644
--- a/tests/api/test_evp_cipher.c
+++ b/tests/api/test_evp_cipher.c
@@ -1915,6 +1915,7 @@ int test_wolfssl_EVP_chacha20_poly1305(void)
byte cipherText[sizeof(plainText)];
byte decryptedText[sizeof(plainText)];
byte tag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
+ byte badTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
EVP_CIPHER_CTX* ctx = NULL;
int outSz;
@@ -1979,6 +1980,28 @@ int test_wolfssl_EVP_chacha20_poly1305(void)
EVP_CIPHER_CTX_free(ctx);
ctx = NULL;
+ /* Negative test: forged (all-zero) tag must be rejected. */
+ XMEMSET(badTag, 0, sizeof(badTag));
+ ExpectNotNull((ctx = EVP_CIPHER_CTX_new()));
+ ExpectIntEQ(EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL,
+ NULL, NULL), WOLFSSL_SUCCESS);
+ ExpectIntEQ(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN,
+ CHACHA20_POLY1305_AEAD_IV_SIZE, NULL), WOLFSSL_SUCCESS);
+ ExpectIntEQ(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG,
+ CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE, badTag),
+ WOLFSSL_SUCCESS);
+ ExpectIntEQ(EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv),
+ WOLFSSL_SUCCESS);
+ ExpectIntEQ(EVP_DecryptUpdate(ctx, NULL, &outSz, aad, sizeof(aad)),
+ WOLFSSL_SUCCESS);
+ ExpectIntEQ(EVP_DecryptUpdate(ctx, decryptedText, &outSz, cipherText,
+ sizeof(cipherText)), WOLFSSL_SUCCESS);
+ /* EVP_DecryptFinal_ex MUST return failure on tag mismatch */
+ ExpectIntNE(EVP_DecryptFinal_ex(ctx, decryptedText, &outSz),
+ WOLFSSL_SUCCESS);
+ EVP_CIPHER_CTX_free(ctx);
+ ctx = NULL;
+
/* Test partial Inits. CipherInit() allow setting of key and iv
* in separate calls. */
ExpectNotNull((ctx = EVP_CIPHER_CTX_new()));
diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c
index fc4f68eb9fc..121d926555f 100644
--- a/wolfcrypt/src/evp.c
+++ b/wolfcrypt/src/evp.c
@@ -1499,16 +1499,33 @@ int wolfSSL_EVP_CipherFinal(WOLFSSL_EVP_CIPHER_CTX *ctx, unsigned char *out,
* HAVE_FIPS_VERSION >= 2 */
#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
case WC_CHACHA20_POLY1305_TYPE:
+ {
+ byte computedTag[CHACHA20_POLY1305_AEAD_AUTHTAG_SIZE];
+ if (!ctx->enc) {
+ /* Save the expected tag before _Final() overwrites
+ * ctx->authTag */
+ XMEMCPY(computedTag, ctx->authTag, sizeof(computedTag));
+ }
if (wc_ChaCha20Poly1305_Final(&ctx->cipher.chachaPoly,
ctx->authTag) != 0) {
WOLFSSL_MSG("wc_ChaCha20Poly1305_Final failed");
return WOLFSSL_FAILURE;
}
- else {
- *outl = 0;
- return WOLFSSL_SUCCESS;
+ if (!ctx->enc) {
+ /* ctx->authTag now holds computed tag; computedTag holds
+ * expected */
+ int tagErr = wc_ChaCha20Poly1305_CheckTag(computedTag,
+ ctx->authTag);
+ ForceZero(computedTag, sizeof(computedTag));
+ if (tagErr != 0) {
+ WOLFSSL_MSG("ChaCha20-Poly1305 tag mismatch");
+ return WOLFSSL_FAILURE;
+ }
}
- break;
+ *outl = 0;
+ return WOLFSSL_SUCCESS;
+ }
+ break;
#endif
#ifdef WOLFSSL_SM4_GCM
case WC_SM4_GCM_TYPE:https://github.com/wolfSSL/wolfssl/commit/1faddd640
Dates from discovery through public reveal.
- 2026-03-29 Reported to tracker
- 2026-04-05 Sent to maintainer
- 2026-05-07 Patch released
- 2026-05-07 Maintainer acknowledged
- 2026-05-20 Publicly revealed
SHA-3-512 hash:
3d2166ecae422707fc5deddb8399d45cfe7100af81f11c4db5e69c322ab9e357919f53f646abd59e37131b10cd08784ab37185431b9cfd803d3bc0f26297cddf
Committed 2026-04-05 16:37 PT
Revealed 2026-05-20 00:40 PT
Verify (download preimage.json)
Show preimage JSON
{
"ant_id": "ANT-2026-RSSMAMA7",
"bug_class": "crypto-failure",
"claude_severity": "high",
"commit_sha": null,
"created_at": "2026-03-29T20:42:34+00:00",
"description": null,
"discovered_at": null,
"location": null,
"poc_sha256": null,
"preimage_version": 1,
"project": "wolfSSL",
"reproduction": null,
"technical_details": null,
"title": "wolfssl evp chacha20 poly1305 poly1305 tag never verifi",
"vendor_severity": "high"
}