Ad
Threats

Trust without verification: wolfSSL accepts forged certificate chains (CVE-2026-5501)

Artem Safonov
By Artem Safonov , Threat Analyst
Trust without verification: wolfSSL accepts forged certificate chains (CVE-2026-5501)
Cover © Anonhaven

wolfSSL released version 5.9.1 to patch a certificate validation bypass in its OpenSSL compatibility layer. CVE-2026-5501 carries a CVSS 4.0 base score of 8.6 (High) and a CWE-295 classification. Anyone holding a free Let's Encrypt certificate for a domain they control can forge a TLS chain for any other domain.

The forged chain returns WOLFSSL_SUCCESS from wolfSSL_X509_verify_cert, the function that backs OpenSSL-compatible certificate validation in wolfSSL. The leaf certificate's signature is never actually checked. Subject name, public key, and signature bytes can all be attacker-controlled.

The native wolfSSL TLS handshake path is not affected. The bypass is reachable only through the OpenSSL compatibility API. Production impact is concentrated in nginx, haproxy, and other server software built against wolfSSL instead of OpenSSL. The bug is one of six security fixes that landed in PR #10102 from a single coordinated audit. The wolfSSL 5.9.1 release notes credit that audit to "Calif.io in collaboration with Claude and Anthropic Research". The credit names the AI assistant directly in a public security advisory.

About wolfSSL and the compatibility layer

wolfSSL is a compact open-source TLS and crypto library written in C. It powers TLS in embedded Linux systems, IoT firmware, automotive controllers, and industrial network appliances. Vendors choose wolfSSL when the OpenSSL footprint is too large or the licensing terms are wrong. Some also build server software against wolfSSL to obtain FIPS 140 validation without bringing in the full OpenSSL FIPS module.

Two API surfaces sit on top of the same crypto core. One is the native wolfSSL API. The other is an OpenSSL compatibility layer that lets applications written against OpenSSL link against wolfSSL with little or no code change.

nginx and haproxy can both be built against wolfSSL through this compatibility layer. The configuration is uncommon compared to OpenSSL builds. It appears in commercial appliances, container images shipped by third-party vendors, and embedded servers where binary size matters.

The compatibility layer is the security boundary that matters for CVE-2026-5501. Code paths that look identical to OpenSSL on the surface can carry different semantics underneath. The wolfSSL bug lives in exactly that gap.

The broken verification path

The bug sits in wolfSSL_X509_verify_cert, the compatibility-layer mirror of OpenSSL's X509_verify_cert. This is the function nginx calls when checking a client certificate during mutual TLS. Any application that asks wolfSSL to validate a certificate chain through the compatibility API ends up here.

The intended logic walks the supplied chain one certificate at a time. For each non-leaf certificate, the function must confirm that the issuer is itself a valid CA carrying BasicConstraints CA:TRUE. For the leaf at the end of the chain, it must call X509StoreVerifyCert to check the leaf's signature against the issuer's public key.

Pre-patch wolfSSL did neither correctly when the chain contained a non-CA intermediate. The fix commit inside PR #10102 describes the broken control flow with unusual clarity.

When an untrusted issuer has CA:FALSE and no verify_cb is registered, the !isCa branch now fails closed (ret=WOLFSSL_FAILURE, goto exit) instead of falling through and skipping X509StoreVerifyCert for the leaf.

— wolfSSL commit e5ab7fa

Two distinct failures combine into the bypass.

The first failure is the fall-through. When chain processing reached an issuer with CA:FALSE and no verify_cb callback was registered, the !isCa branch silently skipped the leaf signature check. The function then exited as if validation had completed.

The second failure is in SetupStoreCtxError_ex, the helper that records errors into the X509_STORE_CTX. The helper could be called multiple times during chain processing, and it would happily overwrite a previously recorded error with a later success. Even a caller that explicitly checked ctx->error after verification would see X509_V_OK.

The combined effect. A chain containing a CA:FALSE intermediate followed by a leaf with arbitrary signature bytes returns WOLFSSL_SUCCESS and reports X509_V_OK.

Forging the chain

An attacker exploiting CVE-2026-5501 needs exactly two things. The first is one leaf certificate from any CA in the victim's trust store. A free domain-validated certificate from Let's Encrypt for any domain the attacker controls is enough. The second is the ability to present a TLS certificate to the victim. This means running a server the victim connects to, or performing on-path interception.

The forged chain looks like this.

Leaf cert (forged):
  Subject:    CN=victim.example          ← any name the attacker wants
  Public key: <attacker's keypair>        ← any key
  Signature:  <arbitrary bytes>           ← never actually verified
 
Intermediate cert (the trick):
  Subject:    CN=attacker.example
  CA:FALSE                                ← this triggers the bypass
  Issuer:     A real, trusted CA (e.g. Let's Encrypt)
  Signature:  Genuine signature from the CA
              (this is the legitimately issued cert)
 
Root: Trusted CA already in the victim's store

Each certificate references the next as its issuer, so the chain is structurally well-formed. The intermediate is a real certificate properly signed by a trusted root. The only thing wrong is that the intermediate carries CA:FALSE, because it is in fact a leaf certificate masquerading as an intermediate. The leaf above it was never signed by the intermediate at all. Its signature bytes are arbitrary.

wolfSSL's compatibility layer validates this chain by walking from leaf to root. The broken !isCa branch fires on the intermediate, the leaf signature check is skipped, and the function returns success. nginx, haproxy, or any other consumer of the compatibility API receives a "valid" certificate for victim.example.

What that gives an attacker depends on what the application does next. In mutual TLS authentication, the attacker authenticates as any subject the application trusts. If the application uses Subject Common Name or Subject Alternative Name as an identifier, the attacker can impersonate any user or service. In client-side TLS verification, the attacker can intercept connections to any domain.

The advisory is explicit that the native wolfSSL TLS handshake path (ProcessPeerCerts) does not call wolfSSL_X509_verify_cert and is not vulnerable. wolfSSL's own server-side TLS code uses an internal validation path that lacks this bug. The bypass affects only code that goes through the OpenSSL compatibility function. This includes nginx mTLS, haproxy mTLS, and any C application built on the assumption that wolfSSL's X509_verify_cert behaves like OpenSSL's.

The cryptographic primitives in wolfSSL are correct. The bug is in the application logic that wires the primitives together. Control flow that should have failed closed instead fell through, and an error-recording helper that should have preserved failures instead overwrote them with success.

— Artem Safonov, Threat Analyst at AnonHaven

The fix in PR #10102

The patch is commit e5ab7fa inside PR #10102. It contains two distinct fixes for the two distinct failures.

The first fix makes the !isCa branch fail closed. When chain processing finds a non-CA issuer with no verify_cb callback, the function now sets ret = WOLFSSL_FAILURE and jumps to the exit label. The leaf signature check is no longer skipped because the function exits with a clear failure state before reaching it.

The second fix hardens SetupStoreCtxError_ex against error overwriting. The helper now refuses to replace a previously recorded non-zero error with a success value. Even if a later code path tries to "succeed" after an earlier failure, the original failure is preserved in ctx->error.

Tests were added in the same commit covering both the no-callback rejection case and the error-preservation case.

Operators who cannot upgrade to 5.9.1 immediately have one workaround. The pre-patch code path only fired the bug when no verify_cb was registered. Applications can register a custom verify_cb on their X509_STORE_CTX that explicitly checks the BasicConstraints CA:TRUE bit on every non-leaf certificate. This is a stopgap, not a substitute for the upgrade.

One bug from a coordinated security audit

CVE-2026-5501 is one of six security fixes that landed in PR #10102. A seventh fix from the same audit was split into PR #10133 because its first implementation regressed ECC throughput by roughly half. The pull request is titled "Various fixes" and the commits read like a single coordinated review of wolfSSL's cryptographic surface. Several of the other bugs are more severe than the X.509 bypass.

The CMAC streaming code contained a 32-bit counter wraparound in wc_CmacUpdate. After 2^28 block flushes (the 4 GiB mark), two messages sharing a common suffix produce identical CMAC tags. The result is a zero-work prefix-substitution forgery against any CMAC-protected message large enough to cross that boundary.

The ECCSI signature verifier wc_VerifyEccsiHash (CVE-2026-5466) was missing range checks on the signature components. The function failed to validate that r and s lie in [1, q-1]. With s=0, scalar multiplication returns the point at infinity. With r=0, the final mp_cmp(0,0)==MP_EQ check accepts the forged signature unconditionally against any message and any identity. The result is universal forgery against ECCSI.

The ChaCha20-Poly1305 implementation in the EVP layer never compared the authentication tag at all. EVP_DecryptFinal_ex called wc_ChaCha20Poly1305_Final, which computed the Poly1305 tag and wrote it into ctx->authTag, overwriting the expected tag stored there by EVP_CTRL_AEAD_SET_TAG. No comparison was performed. Any forged tag was accepted, which collapses AEAD integrity entirely on this code path.

The PKCS7 decoder in wc_PKCS7_DecodeAuthEnvelopedData (CVE-2026-5500) accepted attacker-controlled GCM tag lengths down to a single byte, weakening AEAD integrity from 128 bits to 8. A man-in-the-middle could truncate the mac field from 16 bytes to 1 byte. The chance of forging a valid tag drops from one in 2^128 to one in 256.

The TLS Encrypted Client Hello processing (CVE-2026-5503) contained a heap buffer overflow in TLSX_EchChangeSNI. The attacker-controlled publicName polluted the shared WOLFSSL_CTX extensions when no inner SNI was configured. The inner ClientHello was sized before the pollution but written after. TLSX_SNI_Write then copied 255 bytes past the allocation boundary. The split-off fix in PR #10133 addresses an invalid-curve attack against ECC where on-curve validation was missing.

Three forgery primitives, a heap overflow in TLS extension processing, an integrity downgrade in PKCS7, and an authentication bypass in X.509. All in production crypto code, all from the same review. Fuzzers do not find this kind of cluster. Someone read the source for weeks.

— Artem Safonov, Threat Analyst at AnonHaven

An old class of bug, again

Improper handling of the BasicConstraints CA:FALSE bit is one of the oldest classes of TLS PKI bugs. Multiple browser TLS stacks have shipped, over the years, code that failed to enforce CA:TRUE on intermediate certificates. The end result was always the same. Anyone with any leaf certificate could chain it as an intermediate and forge certificates for arbitrary subject names.

Apple's "goto fail" (CVE-2014-1266) was a different class of bug. Signature verification was skipped due to a duplicated goto fail statement that bypassed the check. The mechanism differed but the practical security effect was similar. A leaf signature was never actually verified.

This class of bug is still appearing in 2026. TrueConf shipped a recent update mechanism with no signature check at all, pushing C2 implants to Southeast Asian government agencies through a single compromised vendor server. ShareFile let one Boolean parameter break authentication on 30,000 servers because the admin panel kept rendering after the redirect. wolfSSL shipped this X.509 bypass in widely deployed code that mimics OpenSSL's API.

The cryptography is fine. The control flow around the cryptography is where the bugs live.

Who is exposed and what to do

The vast majority of nginx and haproxy deployments use OpenSSL and are not affected by CVE-2026-5501. Only installations where the binary was specifically built against wolfSSL through the compatibility layer are vulnerable. The build choice is invisible from outside the server, so the only reliable check is to inspect the binary directly.

For nginx, run nginx -V and look at the configure flags. A wolfSSL build references --with-wolfssl= in the configuration line. A build referencing only --with-openssl= or no TLS library at all is most likely linked against system OpenSSL and is not affected.

Operators of vendor-supplied appliances and third-party container images cannot rely on their own build configuration because the choice was made by someone else. Vendors who ship nginx, haproxy, or custom TLS servers based on wolfSSL need to publish whether their builds include CVE-2026-5501. They should ship updated firmware or images linked against wolfSSL 5.9.1.

Embedded device operators face the hardest case. wolfSSL is widely deployed in IoT firmware, automotive systems, and industrial network equipment. In these environments the TLS library is baked into the device and updates depend on the vendor. Operators of such devices should treat any mTLS client authentication that terminates on wolfSSL-based equipment as compromised until firmware updates ship.

The fix is in wolfSSL 5.9.1. Upgrade.

An AI assistant in the credits

The wolfSSL 5.9.1 release notes credit the audit to "Calif.io in collaboration with Claude and Anthropic Research". Claude is the AI assistant developed by Anthropic. Calif.io is a security firm whose researchers are listed in the commit messages from the same audit. Anthropic Research is the research arm of Anthropic, where Nicholas Carlini works on adversarial machine learning and security.

The credit names the AI assistant directly in the public security advisory of a major TLS library. This is unusual but not unprecedented. Google's Big Sleep project, built on Gemini, has credited its AI agent for finding a real vulnerability in SQLite since late 2024. DARPA's AIxCC competition in 2024 ran an entire program around AI-driven vulnerability discovery and patching, with public attribution to specific AI systems. The wolfSSL credit fits a pattern where AI-assisted security research has begun to receive formal acknowledgment in vendor advisories.

What the credit does not say is exactly how Claude was used. Public AI-assisted bug-hunting work usually involves a human researcher driving the investigation. The AI tool helps to read code, generate hypotheses about edge cases, or write proof-of-concept inputs. The cluster of bugs in PR #10102 has the shape of a careful human audit. Six distinct bug classes across CMAC, ECCSI, ChaCha20-Poly1305, PKCS7, X.509, and TLS extension processing each get a specific structural fix. That does not look like fuzzer output. It looks like a code reviewer working through wolfSSL's cryptographic surface with an attacker's mindset, possibly with Claude as an analytical companion.

The significance for the wider industry is small but specific. AI tools are now finding real bugs in production crypto code. Vendors acknowledge this in their advisories. The bugs being found include the kind of subtle control-flow errors that defeat automated fuzzers. CVE-2026-5501 is a clean example. Finding it requires reading wolfSSL_X509_verify_cert closely. The reviewer must notice that the !isCa branch falls through instead of failing closed, then trace the consequence to the missing leaf signature check. That is reading-and-reasoning work, not blind input generation.

Attribution

wolfSSL 5.9.1 credits the PR #10102 audit cluster to Calif.io with Claude and Anthropic Research. This applies to CVE-2026-5500, CVE-2026-5503, and the other audit fixes from the same pull request. CVE-2026-5501 is part of that cluster and carries the same attribution.

At the commit-message level inside PR #10102, individual researcher names are recorded. The X.509 fix (commit e5ab7fa) is attributed to Nicholas Carlini of Anthropic and Thai Duong of Calif.io. The CMAC, PKCS7-GCM, and ECH commits carry the same two names. The ECCSI and ChaCha20-Poly1305 commits credit Carlini together with Bronson Yen of Calif.io.

The patch for CVE-2026-5501 was implemented by wolfSSL contributors Frauschi and JacobBarthelmeh. PR #10102 was approved by JacobBarthelmeh, dgarske, and douzzer, and merged into wolfSSL master by douzzer on April 6, 2026. The fix is included in wolfSSL release 5.9.1.

Have a story? Become a contributor.

We work with independent researchers and cybersecurity professionals. Send us a tip or submit your article for editorial review.

Questions on the topic

What is CVE-2026-5501 in wolfSSL?
CVE-2026-5501 is a CVSS 4.0 score 8.6 certificate validation bypass in wolfSSL's OpenSSL compatibility layer. It lets attackers forge TLS chains for any domain using a free DV certificate. Patched in wolfSSL 5.9.1.
Does the attacker need to compromise a CA?
No. The attacker needs one legitimately issued leaf certificate from any CA in the victim's trust store. A free domain-validated certificate from Let's Encrypt for any domain the attacker controls is sufficient. No CA compromise, hash collision, or cryptographic break is required.
How can I tell if my nginx is built against wolfSSL?
Run nginx -V and inspect the configure flags. A wolfSSL build references --with-wolfssl=. A build referencing --with-openssl= or no TLS library at all is most likely linked against system OpenSSL and is not affected by CVE-2026-5501.