Skip to content
ecdsa.com
Node.js

node-forge

Digital Bazaar's node-forge, a pure-JavaScript TLS, PKI and PKCS toolkit: RSA, PKCS#12, X.509 and Ed25519 — but no ECDSA at all, so it cannot read an elliptic-curve key or certificate.
License
BSD-3-Clause OR GPL-2.0
Checked version
1.4.0 ·
Source
github.com/digitalbazaar/forge
Documentation
github.com/digitalbazaar/forge#readme

Compatibility at a glance

EdDSA

EdDSA
Ed25519

node-forge grew up as a JavaScript TLS implementation and is mostly used today for its PKI pieces: generating and parsing X.509 certificates and CSRs, reading and writing PKCS#12 bundles, RSA encryption and signatures, and an ASN.1 parser. It runs the same in browsers and Node.js and has no native code.

It has no elliptic-curve support beyond Ed25519. forge.pki exposes only rsa and ed25519, and the check confirmed the consequence: certificateFromPem() on an ECDSA certificate throws Cannot read public key. OID is not RSA., and publicKeyFromPem() on an EC public key throws Cannot read public key. Unknown OID. — even though the same bytes parse fine with forge's own ASN.1 reader. Code that inspects certificates from the wild meets ECDSA keys all the time and needs another parser for them; node:crypto's X509Certificate or the certificate decoder handle both key types.

For RSA it interoperates cleanly once the parameters match. PKCS#1 v1.5 and PSS signatures made by forge verified with node:crypto; PSS needs the digest, the MGF1 digest and the salt length to agree on both sides, and forge makes you name all three, as the sample does. Keys load from SPKI, PKCS#1 and certificate PEM, private keys from PKCS#8, encrypted PKCS#8, PKCS#1 and PKCS#12 — open bundles in the PFX inspector to see what they contain.

Ed25519 signatures from forge were byte-identical to node:crypto's for the same seed. Its private key is the 64-byte expanded form — the seed followed by the public key — so libraries that expect the 32-byte seed need only the first half.

Signature verification has a history of parser leniency, and one case is still open. Advisory GHSA-86w9-cpqp-85rv (CVE-2026-85393), published in September 2026 with no fixed release, reports that RSA PKCS#1 v1.5 verification in versions up to 1.4.0 ignores extra elements inside the DigestInfo's algorithm identifier, which makes signatures forgeable under keys with a small public exponent such as 3. The check reproduced the leniency: a correctly padded block with an extra element there verified in forge and was rejected by node:crypto. Until a fix ships, verify PKCS#1 v1.5 signatures with node:crypto or WebCrypto, and avoid RSA keys with exponent 3. 1.4.0 itself fixed the previous round — an extra field after the digest and non-canonical Ed25519 signatures were both rejected — and versions before 1.3.0 had earlier signature-forgery bugs (CVE-2022-24771, CVE-2022-24772).

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/node-forge/run.sh ran node-forge 1.4.0 on Node.js 24.22.0 in the container image node:24.22.0-bookworm-slim on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • ECDSA curves

    none

    No ECDSA implementation: forge.pki has rsa and ed25519 and nothing for NIST or secp256k1 curves.

    Recorded by the check
    fact ecdsa-curves = none
    evidence ecdsa-curves: forge.pki members: ed25519, rsa
  • Certificates with EC keys

    rejected

    certificateFromPem() on a P-256 certificate throws Cannot read public key. OID is not RSA.; an EC public key throws Cannot read public key. Unknown OID.

    Recorded by the check
    fact ec-certificate = rejected
    evidence ec-certificate: Cannot read public key. OID is not RSA.
    evidence ec-certificate: the same certificate parses at the ASN.1 level: true
    evidence ec-certificate: publicKeyFromPem on an EC SPKI: Cannot read public key. Unknown OID.
  • EdDSA

    Ed25519

    Ed25519 through forge.pki.ed25519.

    Recorded by the check
    fact eddsa = Ed25519
  • Ed25519 matches RFC 8032

    yes

    For the same 32-byte seed, forge produced exactly the signature node:crypto produced, as RFC 8032's deterministic scheme requires.

    Recorded by the check
    fact ed25519-matches-rfc8032 = yes
    evidence ed25519-matches-rfc8032: forge and node:crypto signatures for the same seed are identical: true
  • Ed25519 private key layout

    expanded-64

    Ed25519 private keys are 64 bytes: the seed followed by the public key.

    Recorded by the check
    fact ed25519-private-key = expanded-64
  • Ed25519 signature with non-canonical S

    rejected

    An Ed25519 signature whose S was replaced by S + L — the malleated form RFC 8032 forbids — was rejected, as it was by node:crypto; 1.4.0 added this check (CVE-2026-33895).

    Recorded by the check
    fact ed25519-non-canonical-s = rejected
    evidence ed25519-non-canonical-s: forge: false; node:crypto: false
  • Post-quantum algorithms

    none

    No post-quantum algorithms.

    Recorded by the check
    fact pqc = none
  • RSA signature schemes

    PKCS1-v1_5PSS

    PKCS#1 v1.5 and PSS signatures made with SHA-256 verified in node:crypto; PSS requires an explicit salt length and MGF1 digest.

    Recorded by the check
    fact rsa-signature-schemes = PKCS1-v1_5, PSS
  • Extra element in a PKCS #1 DigestInfo

    accepted-in-algorithm-identifier

    A PKCS#1 v1.5 block with an extra element inside the DigestInfo's AlgorithmIdentifier verified in forge but not in node:crypto; an extra element after the digest was rejected. This is the open advisory GHSA-86w9-cpqp-85rv.

    Recorded by the check
    fact pkcs1-digestinfo-extra-element = accepted-in-algorithm-identifier
    evidence pkcs1-digestinfo-extra-element: well-formed block: forge true, node:crypto true
    evidence pkcs1-digestinfo-extra-element: extra element inside AlgorithmIdentifier: forge true, node:crypto false
    evidence pkcs1-digestinfo-extra-element: extra element after the digest: forge throws ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 DigestInfo value., node:crypto false
  • RSA public key formats

    spki-pempkcs1x509

    RSA public keys loaded from SPKI PEM, PKCS#1 RSA PUBLIC KEY PEM and certificates.

    Recorded by the check
    fact rsa-public-key-formats = spki-pem, pkcs1, x509
  • RSA private key formats

    pkcs8-pemencrypted-pkcs8pkcs1pkcs12

    RSA private keys loaded from PKCS#8 PEM, encrypted PKCS#8 (decryptRsaPrivateKey), PKCS#1 RSA PRIVATE KEY PEM and PKCS#12.

    Recorded by the check
    fact rsa-private-key-formats = pkcs8-pem, encrypted-pkcs8, pkcs1, pkcs12

Usage

Sign with RSA-PSS in forge and verify in node:crypto with the same parameters, then show what happens with an EC key.
import { constants, generateKeyPairSync, verify } from "node:crypto";
import forge from "node-forge";

const message = "order #1042 approved";
const { privateKey, publicKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
const key = forge.pki.privateKeyFromPem(privateKey.export({ type: "pkcs8", format: "pem" }));

// RSA-PSS: SHA-256 for the digest and for MGF1, 32-byte salt. The verifier
// must use the same three parameters.
const sha256 = () => forge.md.sha256.create();
const pss = forge.pss.create({ md: sha256(), mgf: forge.mgf.mgf1.create(sha256()), saltLength: 32 });
const signature = Buffer.from(key.sign(sha256().update(message, "utf8"), pss), "binary");
const options = { key: publicKey, padding: constants.RSA_PKCS1_PSS_PADDING, saltLength: 32 };
console.log("PSS verified by node:crypto:", verify("sha256", Buffer.from(message), options, signature));

// forge has no elliptic-curve support: EC keys and certificates throw.
const ec = generateKeyPairSync("ec", { namedCurve: "P-256" }).publicKey.export({ type: "spki", format: "pem" });
try {
  forge.pki.publicKeyFromPem(ec);
} catch (error) {
  console.log("EC key:", error.message);
}

The snippet harness runs every sample verbatim in the container image node:24.22.0-bookworm-slim, and the run fails unless the output matches.

Setup
npm init -y >/dev/null && npm install --no-audit --no-fund node-forge@1.4.0
Command
node usage.mjs
Output must match (regular expression)
PSS verified by node:crypto: true\nEC key: Cannot read public key\. Unknown OID\.

Libraries