ANT-2026-P23DVQM2 · wolfssl/wolfssl
crypto-failure high
CVE-2026-5500 GHSA-m77r-vqw2-hffx
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-P23DVQM2: wolfSSL PKCS#7 AuthEnvelopedData decoder accepts truncated AES-GCM authentication tags
wc_PKCS7_DecodeAuthEnvelopedData() in wolfcrypt/src/pkcs7.c took the AES-GCM authentication tag length directly from the mac OCTET STRING of the incoming CMS AuthEnvelopedData message, applied only an upper-bound check, and never compared it with the message's aes-ICVlen parameter; the underlying wc_AesGcmDecrypt() in turn accepted tags as short as one byte even though the encrypt path enforces WOLFSSL_MIN_AUTH_TAG_SZ. An attacker able to modify a message in transit could alter the encrypted content and truncate the mac field from 16 bytes to 1 byte, reducing the integrity check from a 2^-128 to a 2^-8 per-attempt forgery probability. The issue is tracked as CVE-2026-5500 / GHSA-m77r-vqw2-hffx and was fixed in commit a88dd07c7 (wolfSSL PR #10102), included in wolfSSL 5.9.1.
Target
Project: wolfSSL
Location: wolfcrypt/src/pkcs7.c:wc_PKCS7_DecodeAuthEnvelopedData (with wolfcrypt/src/aes.c:wc_AesGcmDecrypt)
Discovery: static analysis — not yet dynamically reproduced
Technical Details
Root cause. In wc_PKCS7_DecodeAuthEnvelopedData() (wolfcrypt/src/pkcs7.c), the length of the mac OCTET STRING parsed from the attacker-supplied DER is stored in authTagSz and checked only against the 16-byte upper bound of the local tag buffer before being passed through wc_PKCS7_DecryptContent() to wc_AesGcmDecrypt(). The RFC 5084 aes-ICVlen value from the content-encryption AlgorithmIdentifier is parsed into macSz but never cross-checked against authTagSz, and neither length is covered by the GCM additional authenticated data (only the encoded authenticated attributes are). Separately, in wolfcrypt/src/aes.c, wc_AesGcmDecrypt() rejected only authTagSz == 0 or authTagSz > WC_AES_BLOCK_SIZE, whereas the encrypt side enforces WOLFSSL_MIN_AUTH_TAG_SZ (12 by default). The combination made a one-byte tag comparison reachable through the CMS AuthEnvelopedData decoder.
Reach and impact. The path requires a build with PKCS#7 and AES-GCM support and an application that calls wc_PKCS7_DecodeAuthEnvelopedData() on untrusted input using AES-128/192/256-GCM content encryption. A man-in-the-middle who captures a legitimate AuthEnvelopedData message can modify the ciphertext, rewrite the mac field to a single byte, and enumerate that byte in at most 256 submissions; the default software AES-GCM path then compares only the one byte and the recipient accepts the tampered content as authentic. The impact is loss of integrity and authenticity of the decrypted content; the issue does not provide key or plaintext recovery. The GitHub advisory rates it High (CVSS 3.1 5.9, AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N; CVSS 4.0 8.7; CWE-20).
Fix. Commit a88dd07c70e9415c3daa64d478f9a45bd65f9e5d ("pkcs7,aes: reject truncated GCM auth tags", merged via wolfSSL PR #10102 and shipped in wolfSSL 5.9.1) validates that the parsed macSz is in 1..16, requires authTagSz to equal macSz, rejects GCM tags shorter than WOLFSSL_MIN_AUTH_TAG_SZ in the PKCS#7 decoder, enforces the same minimum in wc_AesGcmDecrypt(), and adds a regression test that truncates the final MAC OCTET STRING from 16 bytes to 1 and expects decoding to fail:
authTagSz = (word32)length;
if (ret == 0 && authTagSz != (word32)macSz) {
WOLFSSL_MSG("AuthEnvelopedData authTag size mismatch");
ret = ASN_PARSE_E;
}
if (ret == 0 &&
(encOID == AES128GCMb || encOID == AES192GCMb ||
encOID == AES256GCMb) &&
authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ) {
WOLFSSL_MSG("AuthEnvelopedData GCM authTag too small");
ret = ASN_PARSE_E;
}
Identifiers: CVE-2026-5500, GHSA-m77r-vqw2-hffx.
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-P23DVQM2.
Reference: ANT-2026-P23DVQM2
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/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c
index a3b75dbb34b..41b01031c41 100644
--- a/wolfcrypt/src/aes.c
+++ b/wolfcrypt/src/aes.c
@@ -10217,8 +10217,9 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz,
/* If the sz is non-zero, both in and out must be set. If sz is 0,
* in and out are don't cares, as this is is the GMAC case. */
if (aes == NULL || iv == NULL || (sz != 0 && (in == NULL || out == NULL)) ||
- authTag == NULL || authTagSz > WC_AES_BLOCK_SIZE || authTagSz == 0 ||
- ivSz == 0 || ((authInSz > 0) && (authIn == NULL)))
+ authTag == NULL || authTagSz > WC_AES_BLOCK_SIZE ||
+ authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ || ivSz == 0 ||
+ ((authInSz > 0) && (authIn == NULL)))
{
return BAD_FUNC_ARG;
}
@@ -10781,8 +10782,8 @@ int wc_AesGcmDecrypt(Aes* aes, byte* out, const byte* in, word32 sz,
/* If the sz is non-zero, both in and out must be set. If sz is 0,
* in and out are don't cares, as this is is the GMAC case. */
if (aes == NULL || iv == NULL || (sz != 0 && (in == NULL || out == NULL)) ||
- authTag == NULL || authTagSz > WC_AES_BLOCK_SIZE || authTagSz == 0 ||
- ivSz == 0) {
+ authTag == NULL || authTagSz > WC_AES_BLOCK_SIZE ||
+ authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ || ivSz == 0) {
return BAD_FUNC_ARG;
}
@@ -12473,7 +12474,7 @@ int wc_AesGcmEncryptFinal(Aes* aes, byte* authTag, word32 authTagSz)
/* Check validity of parameters. */
if ((aes == NULL) || (authTag == NULL) || (authTagSz > WC_AES_BLOCK_SIZE) ||
- (authTagSz == 0)) {
+ (authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ)) {
ret = BAD_FUNC_ARG;
}
diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c
index 9efec15ec7f..174c04d632c 100644
--- a/wolfcrypt/src/pkcs7.c
+++ b/wolfcrypt/src/pkcs7.c
@@ -140,6 +140,7 @@ struct PKCS7State {
word32 nonceSz; /* size of nonce stored */
word32 aadSz; /* size of additional AEAD data */
word32 tagSz; /* size of tag for AEAD */
+ word32 icvSz; /* expected ICV/MAC size from AlgoID parameter */
word32 contentSz;
word32 currContIdx; /* index of current content */
word32 currContSz; /* size of current content */
@@ -14235,6 +14236,10 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in,
if (ret == 0 && GetMyVersion(pkiMsg, &idx, &macSz, pkiMsgSz) < 0) {
ret = ASN_PARSE_E;
}
+ if (ret == 0 && (macSz <= 0 || macSz > WC_AES_BLOCK_SIZE)) {
+ WOLFSSL_MSG("AuthEnvelopedData invalid MAC length");
+ ret = ASN_PARSE_E;
+ }
if (ret == 0) {
explicitOctet = 0;
@@ -14280,7 +14285,8 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in,
break;
}
- /* store nonce for later */
+ /* store nonce and macSz for later */
+ pkcs7->stream->icvSz = (word32)macSz;
if (nonceSz > 0) {
pkcs7->stream->nonceSz = (word32)nonceSz;
pkcs7->stream->nonce = (byte*)XMALLOC((word32)nonceSz,
@@ -14471,6 +14477,7 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in,
encodedAttribSz = pkcs7->stream->aadSz;
encodedAttribs = pkcs7->stream->aad;
}
+ macSz = (int)pkcs7->stream->icvSz;
#endif
@@ -14487,6 +14494,17 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in,
ret = ASN_PARSE_E;
}
authTagSz = (word32)length;
+ if (ret == 0 && authTagSz != (word32)macSz) {
+ WOLFSSL_MSG("AuthEnvelopedData authTag size mismatch");
+ ret = ASN_PARSE_E;
+ }
+ if (ret == 0 &&
+ (encOID == AES128GCMb || encOID == AES192GCMb ||
+ encOID == AES256GCMb) &&
+ authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ) {
+ WOLFSSL_MSG("AuthEnvelopedData GCM authTag too small");
+ ret = ASN_PARSE_E;
+ }
#ifndef NO_PKCS7_STREAM
/* there might not be enough data for the auth tag too */
diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c
index 755aa94358e..8077342566b 100644
--- a/wolfcrypt/test/test.c
+++ b/wolfcrypt/test/test.c
@@ -57721,6 +57721,9 @@ static wc_test_ret_t pkcs7authenveloped_run_vectors(byte* rsaCert, word32 rsaCer
wc_test_ret_t ret = 0;
int testSz = 0, i;
int envelopedSz, decodedSz;
+#ifdef HAVE_AESGCM
+ int tagTruncationChecked = 0;
+#endif
byte *enveloped = NULL;
byte *decoded = NULL;
@@ -58232,6 +58235,45 @@ static wc_test_ret_t pkcs7authenveloped_run_vectors(byte* rsaCert, word32 rsaCer
ERROR_OUT(WC_TEST_RET_ENC_NC, out);
}
+#ifdef HAVE_AESGCM
+ if (tagTruncationChecked == 0 &&
+ (testVectors[i].encryptOID == AES128GCMb ||
+ testVectors[i].encryptOID == AES192GCMb ||
+ testVectors[i].encryptOID == AES256GCMb) &&
+ testVectors[i].authAttribsSz == 0 &&
+ testVectors[i].unauthAttribsSz == 0 &&
+ envelopedSz > (WC_AES_BLOCK_SIZE + 2)) {
+ int macIdx = envelopedSz - (WC_AES_BLOCK_SIZE + 2);
+ byte* tampered = NULL;
+
+ /* For plain DER output without unauthenticated attributes, the
+ * MAC OCTET STRING is the final field. */
+ if (enveloped[macIdx] == ASN_OCTET_STRING &&
+ enveloped[macIdx + 1] == WC_AES_BLOCK_SIZE) {
+ tampered = (byte*)XMALLOC((word32)envelopedSz, HEAP_HINT,
+ DYNAMIC_TYPE_TMP_BUFFER);
+ if (tampered == NULL) {
+ wc_PKCS7_Free(pkcs7);
+ ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out);
+ }
+ XMEMCPY(tampered, enveloped, (word32)envelopedSz);
+ tampered[macIdx + 1] = 1;
+
+ decodedSz = wc_PKCS7_DecodeAuthEnvelopedData(pkcs7, tampered,
+ (word32)envelopedSz, decoded, PKCS7_BUF_SIZE);
+
+ XFREE(tampered, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
+ tampered = NULL;
+
+ if (decodedSz > 0) {
+ wc_PKCS7_Free(pkcs7);
+ ERROR_OUT(WC_TEST_RET_ENC_NC, out);
+ }
+ tagTruncationChecked = 1;
+ }
+ }
+#endif
+
#ifdef PKCS7_OUTPUT_TEST_BUNDLES
/* output pkcs7 envelopedData for external testing */
pkcs7File = XFOPEN(testVectors[i].outFileName, "wb");
diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h
index a0c79c5e846..4929b61cb43 100644
--- a/wolfssl/wolfcrypt/settings.h
+++ b/wolfssl/wolfcrypt/settings.h
@@ -3429,6 +3429,8 @@ extern void uITRON4_free(void *p) ;
/* Default AES minimum auth tag sz, allow user to override */
#ifndef WOLFSSL_MIN_AUTH_TAG_SZ
#define WOLFSSL_MIN_AUTH_TAG_SZ 12
+#elif WOLFSSL_MIN_AUTH_TAG_SZ < 1
+ #error WOLFSSL_MIN_AUTH_TAG_SZ must be at least 1
#endif
https://github.com/wolfSSL/wolfssl/commit/a88dd07c7
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:
2c8aa01adff247bee658ef6ef3ae642a97192eb85e456ea259eb203b183b4eed7b40c8a991be73dc4ab9dd6829d840aae1799e93cdad5de9615a26a38614f46a
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-P23DVQM2",
"bug_class": "crypto-failure",
"claude_severity": "high",
"commit_sha": null,
"created_at": "2026-03-29T20:42:35+00:00",
"description": null,
"discovered_at": null,
"location": null,
"poc_sha256": null,
"preimage_version": 1,
"project": "wolfSSL",
"reproduction": null,
"technical_details": null,
"title": "cms authenvelopeddata aead forgery via gcm tag truncati",
"vendor_severity": "high"
}