import "reflect-metadata"; // @peculiar/x509 2.x needs a Reflect polyfill, imported firstimport { writeFileSync } from "node:fs";import { webcrypto } from "node:crypto";import * as x509 from "@peculiar/x509"; x509.cryptoProvider.set(webcrypto); // Node.js 20+: the global crypto works as well const algorithm = { name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" };const keys = await webcrypto.subtle.generateKey(algorithm, true, ["sign", "verify"]); const csr = await x509.Pkcs10CertificateRequestGenerator.create({ name: "C=US, O=Example Corp, CN=api.example.com", keys, signingAlgorithm: algorithm, extensions: [ new x509.SubjectAlternativeNameExtension([ { type: "dns", value: "api.example.com" }, { type: "dns", value: "www.example.com" }, ]), ],}); const pkcs8 = Buffer.from(await webcrypto.subtle.exportKey("pkcs8", keys.privateKey));writeFileSync("key.pem", x509.PemConverter.encode(pkcs8, "PRIVATE KEY"));writeFileSync("csr.pem", csr.toString("pem"));console.log("signature valid:", await csr.verify());How it works
x509.cryptoProvider.set(webcrypto)tells the library which WebCrypto implementation to use. On current Node.js the globalcryptoworks without it (the browser recipe of this task relies on that); setting it matters when another provider should sign.generateKey({ name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" }, true, …)makes an exportable key pair; the same algorithm object is thesigningAlgorithm.nametakes an RFC 4514-style string; components are encoded in the order written.csr.toString("pem")returns the PEM, andPemConverter.encode(pkcs8, "PRIVATE KEY")wraps the exported PKCS#8 key.
Gotchas
- @peculiar/x509 2.x needs a Reflect polyfill: without
import "reflect-metadata"before the library, it fails withtsyringe requires a reflect polyfill. Please add 'import "reflect-metadata"' to the top of your entry point. - The key pair must be extractable (
true) to save the private key; a non-extractable key can sign the CSR but can never be written tokey.pem. - Without the
SubjectAlternativeNameExtension, the CSR has only a CN, and certificates issued from it fail host-name checks in browsers and in Node.js's owntlsmodule. - node:crypto alone cannot build the request: it can create keys and sign bytes, but has no PKCS#10 encoder — hence the library.