Aller au contenu
ecdsa.com
Node.js

node-forge

node-forge de Digital Bazaar, une boîte à outils TLS, PKI et PKCS en pur JavaScript : RSA, PKCS#12, X.509 et Ed25519, mais aucun ECDSA, si bien qu’elle ne peut lire ni une clé ni un certificat à courbe elliptique.
Licence
BSD-3-Clause OR GPL-2.0
Version vérifiée
1.4.0 ·
Code source
github.com/digitalbazaar/forge
Documentation
github.com/digitalbazaar/forge#readme

Compatibilité en un coup d’œil

EdDSA

EdDSA
Ed25519

node-forge est née comme une implémentation TLS en JavaScript et sert surtout aujourd’hui pour ses briques PKI : génération et analyse de certificats X.509 et de CSR, lecture et écriture de fichiers PKCS#12, chiffrement et signatures RSA, et un analyseur ASN.1. Elle fonctionne de la même façon dans les navigateurs et dans Node.js, et ne contient aucun code natif.

Elle ne prend en charge aucune courbe elliptique hormis Ed25519. forge.pki n’expose que rsa et ed25519, et les essais en ont confirmé la conséquence : certificateFromPem() sur un certificat ECDSA lève Cannot read public key. OID is not RSA., et publicKeyFromPem() sur une clé publique EC lève Cannot read public key. Unknown OID., alors même que le lecteur ASN.1 de forge analyse sans problème les mêmes octets. Un code qui inspecte des certificats trouvés dans la nature croise sans cesse des clés ECDSA et a besoin d’un autre analyseur pour elles ; le X509Certificate de node:crypto ou le décodeur de certificats gèrent les deux types de clés.

Pour RSA, l’interopérabilité est sans accroc dès que les paramètres concordent. Les signatures PKCS#1 v1.5 et PSS produites par forge ont été vérifiées avec node:crypto ; PSS exige que l’algorithme de condensat, celui de MGF1 et la longueur du sel concordent des deux côtés, et forge vous oblige à nommer les trois, comme le fait l’exemple. Les clés se chargent depuis du PEM SPKI, PKCS#1 ou de certificat, les clés privées depuis du PKCS#8, du PKCS#8 chiffré, du PKCS#1 et du PKCS#12 ; ouvrez les fichiers PKCS#12 dans l’inspecteur PFX pour voir ce qu’ils contiennent.

Pour la même graine, les signatures Ed25519 de forge étaient identiques octet pour octet à celles de node:crypto. Sa clé privée est la forme étendue de 64 octets (la graine suivie de la clé publique) : les bibliothèques qui attendent la graine de 32 octets n’ont besoin que de la première moitié.

La vérification des signatures a un historique d’analyse trop permissive, et un cas reste ouvert. L’avis GHSA-86w9-cpqp-85rv (CVE-2026-85393), publié en septembre 2026 sans version corrigée, signale que la vérification RSA PKCS#1 v1.5 des versions jusqu’à la 1.4.0 ignore les éléments supplémentaires à l’intérieur de l’identifiant d’algorithme du DigestInfo, ce qui rend les signatures falsifiables avec des clés à petit exposant public, comme 3. Les essais ont reproduit cette permissivité : un bloc au remplissage correct contenant un élément supplémentaire à cet endroit a été vérifié par forge et rejeté par node:crypto. Tant qu’aucun correctif n’est publié, vérifiez les signatures PKCS#1 v1.5 avec node:crypto ou WebCrypto, et évitez les clés RSA d’exposant 3. La 1.4.0 a elle-même corrigé la série précédente (un champ supplémentaire après le condensat et des signatures Ed25519 non canoniques ont tous deux été rejetés), et les versions antérieures à la 1.3.0 présentaient déjà des bugs de falsification de signature (CVE-2022-24771, CVE-2022-24772).

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/node-forge/run.sh a exécuté node-forge 1.4.0 sur Node.js 24.22.0 dans l’image de conteneur node:24.22.0-bookworm-slim le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Courbes ECDSA

    none

    Aucune implémentation d’ECDSA : forge.pki contient rsa et ed25519, et rien pour les courbes NIST ou secp256k1.

    Relevé par la vérification
    fact ecdsa-curves = none
    evidence ecdsa-curves: forge.pki members: ed25519, rsa
  • Certificats à clé EC

    rejected

    certificateFromPem() sur un certificat P-256 lève Cannot read public key. OID is not RSA. ; une clé publique EC provoque Cannot read public key. Unknown OID.

    Relevé par la vérification
    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 via forge.pki.ed25519.

    Relevé par la vérification
    fact eddsa = Ed25519
  • Ed25519 conforme à la RFC 8032

    yes

    Pour la même graine de 32 octets, forge a produit exactement la même signature que node:crypto, comme l’exige le schéma déterministe de la RFC 8032.

    Relevé par la vérification
    fact ed25519-matches-rfc8032 = yes
    evidence ed25519-matches-rfc8032: forge and node:crypto signatures for the same seed are identical: true
  • Structure de la clé privée Ed25519

    expanded-64

    Les clés privées Ed25519 font 64 octets : la graine suivie de la clé publique.

    Relevé par la vérification
    fact ed25519-private-key = expanded-64
  • Signature Ed25519 avec S non canonique

    rejected

    Une signature Ed25519 dont S avait été remplacé par S + L (la forme modifiée par malléabilité qu’interdit la RFC 8032) a été rejetée, comme par node:crypto ; la 1.4.0 a ajouté ce contrôle (CVE-2026-33895).

    Relevé par la vérification
    fact ed25519-non-canonical-s = rejected
    evidence ed25519-non-canonical-s: forge: false; node:crypto: false
  • Algorithmes post-quantiques

    none

    Aucun algorithme post-quantique.

    Relevé par la vérification
    fact pqc = none
  • Schémas de signature RSA

    PKCS1-v1_5PSS

    Les signatures PKCS#1 v1.5 et PSS produites avec SHA-256 ont été vérifiées dans node:crypto ; PSS exige une longueur de sel et un algorithme de condensat MGF1 explicites.

    Relevé par la vérification
    fact rsa-signature-schemes = PKCS1-v1_5, PSS
  • Élément supplémentaire dans un DigestInfo PKCS #1

    accepted-in-algorithm-identifier

    Un bloc PKCS#1 v1.5 comportant un élément supplémentaire dans l’AlgorithmIdentifier du DigestInfo a passé la vérification dans forge, mais pas dans node:crypto ; un élément supplémentaire après le condensat a été rejeté. C’est l’objet de l’avis ouvert GHSA-86w9-cpqp-85rv.

    Relevé par la vérification
    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
  • Formats de clé publique RSA

    spki-pempkcs1x509

    Les clés publiques RSA se sont chargées depuis du SPKI PEM, du PKCS#1 RSA PUBLIC KEY en PEM et des certificats.

    Relevé par la vérification
    fact rsa-public-key-formats = spki-pem, pkcs1, x509
  • Formats de clé privée RSA

    pkcs8-pemencrypted-pkcs8pkcs1pkcs12

    Les clés privées RSA se sont chargées depuis du PKCS#8 PEM, du PKCS#8 chiffré (decryptRsaPrivateKey), du PKCS#1 RSA PRIVATE KEY en PEM et du PKCS#12.

    Relevé par la vérification
    fact rsa-private-key-formats = pkcs8-pem, encrypted-pkcs8, pkcs1, pkcs12

Utilisation

Signer avec RSA-PSS dans forge et vérifier dans node:crypto avec les mêmes paramètres, puis montrer ce qui se passe avec une clé EC.
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);
}

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur node:24.22.0-bookworm-slim, et l’exécution échoue si la sortie ne correspond pas.

Mise en place
npm init -y >/dev/null && npm install --no-audit --no-fund node-forge@1.4.0
Commande
node usage.mjs
La sortie doit correspondre à cette expression régulière
PSS verified by node:crypto: true\nEC key: Cannot read public key\. Unknown OID\.

Bibliothèques