npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

js-pdf-signer

v1.0.0

Published

PDF 自签名证书 + PKCS#7 分离数字签名库(浏览器/Node 通用,依赖 WebCrypto,基于 pdf-lib)

Readme

js-pdf-signer

Add certificate-based PKCS#7 detached digital signatures (adbe.pkcs7.detached) to PDF files in the browser or Node.js. It generates a self-signed X.509 certificate on the fly, so no external certificate files are needed — the resulting signature is recognized by Adobe Acrobat, UPDF and other viewers as a real digital signature.

Built on top of pdf-lib and designed to work with it.

Features

  • 🔐 Real digital signature — produces a standard PKCS#7/CMS SignedData that can be verified by openssl, Adobe Acrobat, UPDF, etc.
  • 📜 Built-in self-signed certificate — generates an RSA-2048 key pair + X.509 self-signed certificate in-browser using WebCrypto, zero setup.
  • 🌐 Browser & Node compatible — relies only on the global WebCrypto (window.crypto / globalThis.crypto), no native Node dependencies.
  • 🖋 ByteRange detached signature — computes the /ByteRange per the PDF spec and signs only the digest, leaving the rest of the document untouched.
  • 📦 Build to an IIFE bundlenpm run build produces pdfsigner.bundle.js; include it with a <script> tag to get the global PDFSigner.
  • 🧩 Works seamlessly with pdf-libaddSignaturePlaceholder operates directly on a pdf-lib signature field.

Install

npm install js-pdf-signer

The publish registry is pinned to the official source: publishConfig.registry = https://registry.npmjs.org/

Quick Start

Browser (built bundle)

The pre-built bundle is published as dist/pdfsigner.bundle.js. You can either load it from the installed package, or rebuild it yourself:

npm run build   # generates dist/pdfsigner.bundle.js (and copies it to ../pdfsigner.bundle.js for the companion app in this repo)
<script src="pdf-lib.min.js"></script>
<script src="node_modules/js-pdf-signer/dist/pdfsigner.bundle.js"></script>
<script>
  // 1) Load/create a PDF with pdf-lib and get the signature field
  const pdfDoc = await PDFLib.PDFDocument.load(bytes);
  const field = pdfDoc.getForm().getField('Signature');

  // 2) Add a signature placeholder (field /V → standalone signature dict)
  window.PDFSigner.addSignaturePlaceholder({
    pdfLib: PDFLib,
    pdfDocLib: pdfDoc,
    field,
  });

  // 3) IMPORTANT: save without object streams, otherwise the placeholder
  //    gets compressed and can no longer be located
  const withPlaceholder = await pdfDoc.save({ useObjectStreams: false });

  // 4) Sign (a self-signed certificate is generated and cached automatically)
  const signed = await window.PDFSigner.signPdfBytes(withPlaceholder);
</script>

Node.js (direct API)

const PDFLib = require('pdf-lib');
const { addSignaturePlaceholder, signPdfBytes } = require('js-pdf-signer');

const pdfDoc = await PDFLib.PDFDocument.load(bytes);
const field = pdfDoc.getForm().getField('Signature');
addSignaturePlaceholder({ pdfLib: PDFLib, pdfDocLib: pdfDoc, field });

const withPlaceholder = await pdfDoc.save({ useObjectStreams: false });
const signed = await signPdfBytes(withPlaceholder); // Buffer / Uint8Array

API

addSignaturePlaceholder({ pdfLib, pdfDocLib, field, placeholderHexLen? })

Adds a signature placeholder to a pdf-lib signature field.

  • pdfLib — the pdf-lib namespace (pass your own instance to avoid bundling it twice)
  • pdfDocLib — a PDFDocument instance
  • field — the signature field wrapper (field.acroField.dict must exist)
  • placeholderHexLen — length of the /Contents placeholder hex string; defaults to PLACEHOLDER_HEX_LEN (8192)

Per what Acrobat requires, the signature dictionary is created as a standalone object referenced by the field's /V (containing /Type /Sig, /ByteRange, /Contents, /Filter, /SubFilter), rather than being merged directly into the field dict.

signPdfBytes(pdfBytes, signer?)

Performs the ByteRange detached signature on PDF bytes that already contain a placeholder.

  • pdfBytesUint8Array / Buffer (must be the bytes saved with useObjectStreams: false after addSignaturePlaceholder)
  • signer — optional { cert, privateKey }; defaults to the session-cached self-signed signer
  • Returns — the signed Uint8Array

Internally: locate the /ByteRange and /Contents placeholders → write the real ByteRange numbers → remove the placeholder → compute the SHA-256 digest → build the CMS SignedData (with contentType / signingTime / messageDigest signed attributes) → RSA-PKCS#1 v1.5 signature → write it back into /Contents.

getOrCreateSigner()

Returns the session-cached self-signed signer { cert, privateKey } (generates RSA-2048 + X.509 on first call, reuses afterwards).

makeSelfSignedCert()

Generates a brand-new self-signed certificate + key pair on every call.

buildCmsSignature(cert, privateKey, digestBytes, signingTime)

Low-level helper that builds the CMS ContentInfo DER bytes (usually not needed directly).

Utilities

bytesToLatin1 / latin1ToBytes / bytesToHex — PDF bytes ↔ latin1 string conversion (used by the ByteRange handling).

Constants

PLACEHOLDER_HEX_LEN — default /Contents placeholder hex length (8192, enough for an RSA-2048 signature + certificate).

How It Works

PDF digital signatures (ISO 32000-1 §12.8):

  1. The signature field's V points to a signature dictionary containing:
    • /ByteRange [0 <len1> <start2> <len2>] — two byte ranges
    • /Contents <...> — the signature data (DER-encoded PKCS#7/CMS)
  2. Compute a SHA-256 digest over everything except /Contents (i.e. the two ByteRange spans).
  3. The digest, together with contentType and signingTime, form the CMS signedAttrs, which are signed with the private key (PKCS#1 v1.5).
  4. Write the DER-encoded CMS (including the signer certificate) back into the /Contents placeholder.

This library uses pkijs to generate the X.509 certificate and asn1js to build the CMS by hand (avoiding a SignedData serialization bug in some pkijs versions). All signing operations go through WebCrypto.

Development

npm install
npm run build   # generates dist/pdfsigner.bundle.js (IIFE, exposes global PDFSigner)
npm test        # end-to-end: create → sign → openssl cms -verify

Directory layout:

src/
  index.js        # entry, exports the full API + pkijs/asn1js
  utils.js        # bytes ↔ latin1 ↔ hex
  cert.js         # self-signed X.509 + RSA-2048
  cms.js          # PKCS#7/CMS SignedData construction & signing
  placeholder.js  # add the /V signature placeholder to a pdf-lib field
  sign-pdf.js     # ByteRange detached signing + signer cache
build.mjs         # esbuild build script (outputs dist/pdfsigner.bundle.js)
dist/             # built IIFE bundle (published to npm)
test/test.js      # end-to-end test

Limitations

  • Self-signed certificate: Acrobat / UPDF will warn that the signer identity cannot be verified — normal for self-signed certs. The signature itself is cryptographically valid and can be checked with openssl cms -verify. To show "verified", use a certificate from a trusted CA.
  • Single signature: the current implementation targets a single signature field; multiple sequential signatures (each covering the previous signature's /Contents) are not yet implemented.
  • Zero padding in /Contents: the placeholder is fixed-length and shorter signatures are padded with 00 bytes (same approach as signpdf), which mainstream validators tolerate.

License

MIT