
Security News
White House Authorizes Private Companies to Conduct Offensive Cyber Operations
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.
@e-sig/core
Advanced tools
Self-contained PKCS#7 PDF signing — no SaaS, no metering, no per-doc fees. Render→sign→verify engine + pluggable persistence interfaces + end-to-end signDocument() orchestrator.
@e-sig/core — Portable In-Platform E-SignatureSelf-contained PKCS#7 PDF signing — no SaaS, no metering, no per-doc fees. Battle-tested in production at opendelphi.org.
This directory is the portable core of the Opendelphi e-signature pipeline. Drop it (plus a tiny adapter you write) into any TypeScript / Node.js project to add real cryptographic signing of PDFs.
Given an HTML document and a person who wants to sign it, this library:
puppeteer-core; scripting disabled by default).padesStrict: true for strict PAdES B-B (also drops the PAdES-forbidden signing-time attribute).verifyPdfSignature() checks this cryptographically (recomputes the digest over the signed ByteRange and RSA-verifies the signature).Trust vs. validity. The signature is cryptographically valid, but the cert is self-issued — stock Adobe Reader shows "validity unknown" until the cert is trusted (org trust-store import, or plug in an AATL/CA signer). This verifies the signature math and integrity, not third-party trust. See the compliance notes below.
That's the whole thing. It's ~600 lines of TypeScript with zero runtime dependencies on Supabase, Next.js, or any SaaS.
By design — these are wrapper concerns:
CertStore adapter interface.AuditLogStore adapter interface.The library gives you crypto + rendering. You bring persistence + UI + auth.
| File | Purpose | Project-agnostic? |
|---|---|---|
pem-signer.ts | Custom @signpdf Signer driven by raw PEM key+cert (bypasses node-forge's broken P12 round-trip — see Background below) | ✅ |
cert-issuer.ts | Generate self-signed RSA-2048 X.509; AES-256-GCM-wrap private keys for at-rest storage | ✅ |
render-pdf.ts | HTML → PDF via puppeteer-core; auto-detects Lambda vs local Chrome | ✅ |
sign-pdf.ts | Combine placeholder injection + PKCS#7 sign (ETSI.CAdES) | ✅ |
verify-pdf.ts | Structural verifier (parses /ByteRange + PKCS#7 blob, returns diagnostics) | ✅ |
signature-block.ts | HTML helper to render N signature blocks for multi-party flows | ✅ |
types.ts | Shared TS types (Signer, SigningCertPem, …) | ✅ |
index.ts | Public re-export barrel | ✅ |
npm install \
@signpdf/signpdf \
@signpdf/utils \
@signpdf/placeholder-plain \
node-forge \
puppeteer-core \
@sparticuz/chromium # only on Lambda; skip for local-only
Plus @types/node-forge if you're using TypeScript.
If you're on Next.js, you MUST externalize the binary-adjacent packages in next.config.ts:
const nextConfig = {
serverExternalPackages: [
"@sparticuz/chromium",
"puppeteer-core",
"node-forge",
"@signpdf/signpdf",
"@signpdf/utils",
"@signpdf/placeholder-plain",
],
// The chromium binary tarball is a static asset; tell file-tracing to
// include it in your e-sig route's bundle.
outputFileTracingIncludes: {
"/api/your-esig-route": [
"./node_modules/@sparticuz/chromium/bin/**",
],
},
};
import {
generateSelfSignedCert,
renderHtmlToPdf,
signPdf,
verifyPdfStructure,
} from "@e-sig/core";
// 1. Issue a one-off cert (in real life, persist + reuse).
const cert = generateSelfSignedCert({ subjectName: "Acme Corp" });
// 2. Render HTML → unsigned PDF.
const unsigned = await renderHtmlToPdf({
html: `<h1>Service Agreement</h1><p>Signed by Jane Doe at ${new Date().toISOString()}.</p>`,
});
// 3. Sign it.
const { signedPdf } = await signPdf({
pdf: unsigned,
keyPem: cert.keyPem,
certPem: cert.certPem,
reason: "Service Agreement acceptance",
location: "https://acme.example",
contactInfo: "jane@example.com",
name: "Jane Doe",
});
// 4. Verify the result cryptographically (also exported as verifyPdfSignature).
// ok === true only when structure + document digest + RSA signature all pass;
// a single flipped byte under the signature makes ok=false / digestValid=false.
const verify = verifyPdfStructure(signedPdf);
console.log(verify.ok, verify.digestValid, verify.signatureValid, verify.signerCommonName);
// → true, true, true, "E-sig (Acme Corp)"
// 5. Persist + serve. Up to you.
require("fs").writeFileSync("./signed.pdf", signedPdf);
That's it. Open signed.pdf in Preview — signature panel shows valid (self-signed).
Pass a tsa transport to signPdf to embed an RFC 3161 TimeStampToken,
upgrading the signature from CAdES-B to CAdES-T. The token is added as the
id-aa-timeStampToken unsigned attribute (OID 1.2.840.113549.1.9.16.2.14)
computed over the SignerInfo signatureValue (RFC 3161 §2.4.1).
The package performs no network egress — you inject the POST so the package stays dependency-free. The TSA only ever receives a SHA-256 hash, never the document or any PHI:
import type { TsaTransport } from "@e-sig/core";
const tsa: TsaTransport = {
required: false, // false = degrade to CAdES-B on TSA failure; true = throw
fetch: async (reqDerBytes) => {
const res = await fetch("http://timestamp.digicert.com", {
method: "POST",
headers: { "Content-Type": "application/timestamp-query" },
body: reqDerBytes,
});
return new Uint8Array(await res.arrayBuffer());
},
};
const { signedPdf, timestamped, tsaError } = await signPdf({
pdf, keyPem, certPem,
reason: "DUA acceptance", location: "opendelphi.org",
contactInfo: "legal@acme.org", name: "Acme Research Institute",
tsa,
});
const v = verifyPdfStructure(signedPdf);
// v.timestamped, v.timestampTime (ISO), v.tsaCommonName
// v.ok is false if the §2.4.2 binding check fails (imprint != sha256(sigValue))
Notes:
tsa is supplied and signatureLength is omitted, the
/Contents placeholder budget defaults to 30720 (vs 8192 without a TSA)
to fit the TimeStampToken plus the TSA certificate chain. An overflow is
rejected, never silently truncated.required: false (default), a TSA error produces a
valid CAdES-B signature and sets tsaError; with required: true the error
is rethrown.messageImprint must equal sha256(SignerInfo.signature), else ok:false.See CONSUMING.md for the full consumer guide.
For real usage you need to:
The library provides adapter interfaces (CertStore, AuditLogStore — see ../adapters/types.ts). Implement them against your DB.
A reference Supabase implementation lives at ../adapters/supabase.ts (~150 lines). It works against any schema with these two tables — copy the migration from supabase/migrations/00106_esig_self_contained.sql for the canonical shape, or write your own.
interface CertStore {
findActive(tenantId: string): Promise<StoredCert | null>;
insert(input: { tenantId; generated; keyPemEncrypted; rotatedFromId? }): Promise<StoredCert>;
deactivate(id: string): Promise<void>;
findExpiring(withinDays: number): Promise<StoredCert[]>;
}
interface AuditLogStore {
insert(entry: AuditLogEntry): Promise<AuditLogRow>;
}
Then use the convenience helper ensureActiveCert from ../adapters/supabase.ts as a template:
const result = await ensureActiveCert({
store: new YourCertStore(...),
tenantId: "acme-corp",
subjectName: "Acme Corp",
passphrase: process.env.ESIG_CERT_PASSPHRASE!,
});
// result.certPem + result.keyPem ready to feed into signPdf()
The Opendelphi production wire-up uses this library for HIPAA-bound Data Use Agreements and is mapped against:
See .planning/phases/19-esig-primitives-spike/19-04-ESIGN-GAPS.md for the full mapping.
Not legal advice. Talk to your lawyer about whether this satisfies the regulatory framework for your specific use case.
Per-document metering (~$0.20/sig) made the unit economics painful at scale. And every signed PDF flowed through a third-party processor — making HIPAA + GDPR compliance harder than it had to be.
This library is what you reach for when "no SaaS, no metering, no fees" is a hard requirement.
pdf-lib?pdf-lib is the most popular Node PDF library, but it hasn't shipped a release since 2021. Documenso uses @libpdf/core instead — same conclusion here. (Neither is actually used by this core — we drive puppeteer for rendering and @signpdf + node-forge for signing, both actively maintained.)
We tried. node-forge.pkcs12.toPkcs12Asn1 produces P12 bundles whose MAC neither node-forge nor openssl can verify. Looks like a long-standing BMPString-password-derivation bug. We bypass it entirely — the PemSigner takes raw PEM and drives forge.pkcs7 directly.
node-forge.pkcs12.toPkcs12Asn1 — see above.forge.pki.certificateFromPem mis-counts bytes for non-ASCII (em-dash in OU breaks PEM round-trip with "Too few bytes to parse DER").@signpdf/signpdf v3 ESM default import is opaque — use the named export: import { SignPdf } from "@signpdf/signpdf" and new SignPdf().sign(...).@sparticuz/chromium is Lambda-only — locally, use system Chrome via the executablePath override.forge.pem.decode extracts blocks correctly, but re-encoding the cert block from a multi-block buffer doesn't survive certificateFromPem round-trip. Store them as separate files / DB columns.End-to-end on Vercel Lambda (cold start), tested against opendelphi.org in production:
Subsequent signs reuse the cached cert → ~1–1.5 s warm.
Same as the parent project. The core/ directory is intentionally self-contained so it can be vendored under your own license.
.planning/phases/19-esig-primitives-spike/19-02-PATTERNS-OBSERVED.md for the lessons borrowed).FAQs
Self-contained PKCS#7 PDF signing — no SaaS, no metering, no per-doc fees. Render→sign→verify engine + pluggable persistence interfaces + end-to-end signDocument() orchestrator.
The npm package @e-sig/core receives a total of 10 weekly downloads. As such, @e-sig/core popularity was classified as not popular.
We found that @e-sig/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
A new federal program will let vetted U.S. cybersecurity firms help investigate and disrupt foreign cybercrime groups under government direction.

Research
/Security News
The campaign amassed more than 75,000 installs by targeting Russian-speaking users seeking access to blocked services.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.