Vulnerability details

3.7/10
CVSS 3.1
GHSA-4487-j8r4-4g2f
in_triage Published 2026-10-30 Updated 2026-10-30
Vulnerability Data from Dependency-Track
Description
### Summary When a CA store created by `pki.createCaStore()` holds two or more certificates with the same subject name, `pki.verifyCertificateChain()` permanently removes those certificates from the store while trying each one as the issuer. A peer can send any certificate that names that subject as its issuer to make all later verifications against those CA certificates fail. Fixed in node-forge 1.4.1. ### Details `pki.verifyCertificateChain()` (`lib/x509.js`) asks the CA store for the issuer of each certificate with `caStore.getIssuer()`. When the store holds several certificates with the same subject, `caStore.getIssuer()` returns the store's own internal array of those certificates (`caStore.certs[hash]`, built by `addCertificate()`). `verifyCertificateChain()` used that array directly as its list of candidate parents and called `parents.shift()` on it while trying each candidate. Every candidate it tried, including the one that finally verified the certificate, was removed from the store. Once the array is empty, `getIssuer()` returns an empty list, no candidate is tried, and verification fails with "Certificate signature is invalid." `hasCertificate()` and `listAllCertificates()` no longer see the removed certificates. The store stays in this state for as long as the object is used. The attacker's certificate only needs an issuer name equal to the shared subject name, which is public. Its signature and key can be arbitrary: every candidate is tried and shifted out because none verifies, and errors from `parent.verify()` are caught by the loop. Verification of the attacker's chain fails as expected. Verifying valid chains has a similar effect: the CA certificate that verified the chain and any tried before it are removed. No chain is accepted that should be rejected. Removing candidates can only turn a success into a failure. RFC 5280, Section 6.1.1(d) treats trust anchor information as an input to path validation. Validation should not consume it. The fix copies the array returned by `getIssuer()` before iterating over it, so the CA store is no longer modified. `getIssuer()` itself is unchanged. ### Patches Fixed in node-forge 1.4.1. `pki.verifyCertificateChain()` now works on a copy of the candidate issuer list instead of shifting entries out of the CA store. ### Workarounds - Pass a fresh CA store to each `pki.verifyCertificateChain()` call (for example `pki.createCaStore(trustedCerts)`), or pass `caStore` to `forge.tls.createConnection()` as an array of certificates instead of a store object, so that forge creates a new store per connection. - Or wrap the store so that `getIssuer()` returns a copy: var getIssuer = caStore.getIssuer; caStore.getIssuer = function(cert) { var r = getIssuer(cert); return Array.isArray(r) ? r.slice() : r; }; ### Proof of concept Two trusted self-signed CA certificates, `rootA` and `rootB`, share the subject `CN=Root`. `bogus` is any certificate whose issuer is `CN=Root`, signed with an unrelated key. `leafA` is a valid certificate issued by `rootA`. var caStore = forge.pki.createCaStore([rootA, rootB]); caStore.listAllCertificates().length; // 2 try { forge.pki.verifyCertificateChain(caStore, [bogus]); } catch(e) {} // rejected as expected ("Certificate signature is invalid.") caStore.listAllCertificates().length; // 0: both CA certificates are gone forge.pki.verifyCertificateChain(caStore, [leafA]); // throws "Certificate signature is invalid." (valid chain now rejected) Without the attacker certificate, verifying `leafA` twice against a fresh store gives the same result: the first call succeeds and removes `rootA`, the second fails. With the fix, the store keeps both certificates and every valid chain verifies. The regression tests are in `tests/unit/x509.js` ("CA store" section). ### Impact Applications that keep a long-lived CA store holding several trusted certificates with the same subject name, and that verify certificate chains received from other parties with `pki.verifyCertificateChain()`. This includes `forge.tls` clients that pass the same `caStore` object to several connections. Duplicate subject names occur with re-keyed or renewed CAs that kept their name, or a CA next to a cross-signed version of itself, mostly in private PKIs. A remote, unauthenticated peer (for example a malicious TLS server or a party submitting a signed document) can send a certificate naming the shared subject as its issuer. This removes every certificate with that subject from the store, so later verifications of valid chains issued by those CA certificates fail until the store is recreated. This is a persistent denial of service for certificate verification against those CA certificates. Verification against other trusted CAs keeps working, no invalid chain is accepted, and stores without duplicate subject names are not affected.
Analysis Read only

CVE is a trademark and the CVE logo is a registered trademark of The MITRE Corporation. CVE data may be incomplete, delayed, or inaccurate. This website and all information provided are offered on an “as is” basis without warranties of any kind. This site is not affiliated with, endorsed by, or sponsored by the CVE Program or The MITRE Corporation.

This site uses data from the National Vulnerability Database (NVD), obtained through Dependency-Track, but is not endorsed or certified by the NVD or the National Institute of Standards and Technology (NIST). NVD data may be incomplete, delayed, or inaccurate.

CVSS (Common Vulnerability Scoring System) is owned and managed by FIRST.Org, Inc. and used by permission. The links on CVSS vectors open the FIRST CVSS calculators (CVSS 3.0, 3.1 and 4.0), or the NVD calculator for CVSS 2.0. This site is not affiliated with, endorsed by, or sponsored by FIRST.

GitHub is a trademark of GitHub, Inc. GHSA identifiers and advisory data come from the GitHub Advisory Database, licensed under CC-BY 4.0. GHSA data may be incomplete, delayed, or inaccurate. This site is not affiliated with, endorsed by, or sponsored by GitHub, Inc.

Source of the EUVD data: European Vulnerability Database (EUVD), © European Union Agency for Cybersecurity (ENISA), reused with acknowledgement of the source as provided by the ENISA legal notice and IPR policy. Changes: CVEManager does not display the EUVD records as such; it uses their aliases to link the CVE and GHSA identifiers designating the same vulnerability. EUVD data may be incomplete, delayed, or inaccurate. This site is not affiliated with, endorsed by, or sponsored by ENISA or the European Union.