ProvenPin Install

node-forge self-signed certificate: working versions (tested 2026-10-17)

Generate an RSA 2048 keypair, build a self-signed X.509 certificate with subject, issuer, basicConstraints, keyUsage and subjectAltName extensions, round-trip it through PEM, verify its signature and sign/verify data with SHA-256 offline.

passed6 passed · 0 failed2026-10-17 19:19 UTC
Status
passed
Last passed
2026-10-17 19:19 UTC
Tests
6 / 6
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
node-forge@1.4.0@types/node@26.6.5@types/node-forge@1.3.14tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 39061946720

The code

Exactly the files that passed. Your agent can pull them with get_recipe("node-forge-self-signed-cert") (install), or see them on GitHub.

package.json
{
  "name": "node-forge-self-signed-cert",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "node-forge": "1.4.0"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "@types/node-forge": "1.3.14",
    "tsx": "4.23.15"
  }
}
src/cert.ts
import forge from "node-forge";

export type Certificate = ReturnType<typeof forge.pki.createCertificate>;

export interface GeneratedCertificate {
  privateKeyPem: string;
  publicKeyPem: string;
  certPem: string;
  certificate: Certificate;
}

export function generateSelfSignedCertificate(
  commonName: string,
  altNames: string[],
  days = 365,
): GeneratedCertificate {
  const keys = forge.pki.rsa.generateKeyPair(2048);

  const cert = forge.pki.createCertificate();
  cert.publicKey = keys.publicKey;
  cert.serialNumber = "01";

  cert.validity.notBefore = new Date();
  cert.validity.notAfter = new Date();
  cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + days);

  const attributes = [
    { name: "commonName", value: commonName },
    { name: "organizationName", value: "ProvenPin" },
    { shortName: "C", value: "US" },
  ];

  cert.setSubject(attributes);
  cert.setIssuer(attributes);

  cert.setExtensions([
    { name: "basicConstraints", cA: false },
    { name: "keyUsage", digitalSignature: true, keyEncipherment: true },
    {
      name: "subjectAltName",
      altNames: altNames.map((value) => ({ type: 2, value })),
    },
  ]);

  cert.sign(keys.privateKey, forge.md.sha256.create());

  return {
    privateKeyPem: forge.pki.privateKeyToPem(keys.privateKey),
    publicKeyPem: forge.pki.publicKeyToPem(keys.publicKey),
    certPem: forge.pki.certificateToPem(cert),
    certificate: cert,
  };
}

export function parseCertificate(pem: string): Certificate {
  return forge.pki.certificateFromPem(pem);
}

export function verifyCertificate(certificate: Certificate): boolean {
  return certificate.verify(certificate);
}

export function signData(privateKeyPem: string, data: string): string {
  const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
  const digest = forge.md.sha256.create();
  digest.update(data, "utf8");
  return privateKey.sign(digest);
}

export function verifyData(
  publicKeyPem: string,
  data: string,
  signature: string,
): boolean {
  const publicKey = forge.pki.publicKeyFromPem(publicKeyPem);
  const digest = forge.md.sha256.create();
  digest.update(data, "utf8");
  return publicKey.verify(digest.digest().bytes(), signature);
}
test/cert.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import {
  generateSelfSignedCertificate,
  parseCertificate,
  signData,
  verifyCertificate,
  verifyData,
} from "../src/cert.ts";

const fixture = generateSelfSignedCertificate("example.test", [
  "example.test",
  "www.example.test",
]);

test("generates a self-signed certificate with subject and issuer", () => {
  const { certificate } = fixture;
  assert.equal(certificate.subject.getField("CN")?.value, "example.test");
  assert.equal(certificate.issuer.getField("CN")?.value, "example.test");
  assert.equal(certificate.issuer.getField("O")?.value, "ProvenPin");
});

test("sets the requested validity window", () => {
  const { certificate } = fixture;
  const { notBefore, notAfter } = certificate.validity;
  assert.ok(notAfter.getTime() > notBefore.getTime());
  const days = (notAfter.getTime() - notBefore.getTime()) / 86_400_000;
  assert.ok(Math.abs(days - 365) < 1);
});

test("includes basicConstraints, keyUsage and subjectAltName extensions", () => {
  const { certificate } = fixture;
  const constraints = certificate.getExtension("basicConstraints");
  assert.equal(constraints.cA, false);
  const usage = certificate.getExtension("keyUsage");
  assert.equal(usage.digitalSignature, true);
  assert.equal(usage.keyEncipherment, true);
  const san = certificate.getExtension("subjectAltName");
  assert.deepEqual(
    san.altNames.map((entry: { value: string }) => entry.value),
    ["example.test", "www.example.test"],
  );
});

test("round-trips through PEM and verifies the certificate signature", () => {
  const parsed = parseCertificate(fixture.certPem);
  assert.equal(parsed.subject.getField("CN")?.value, "example.test");
  assert.equal(parsed.issuer.getField("CN")?.value, "example.test");
  assert.equal(verifyCertificate(parsed), true);
});

test("signs and verifies data with SHA-256", () => {
  const message = "provenpin";
  const signature = signData(fixture.privateKeyPem, message);
  assert.equal(typeof signature, "string");
  assert.equal(verifyData(fixture.publicKeyPem, message, signature), true);
});

test("rejects tampered data", () => {
  const signature = signData(fixture.privateKeyPem, "original");
  assert.equal(verifyData(fixture.publicKeyPem, "tampered", signature), false);
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.