Skip to content

vbuch/node-signpdf

Repository files navigation

@signpdf

Known Vulnerabilities Coverage as reported by Coveralls GitHub last commit

Formerly known as node-signpdf @signpdf is a family of packages trying to make signing of PDFs simple in Node.js.

Purpose

The main purpose of this package is to demonstrate the way signing can be achieved in a piece of readable code as it can take a lot of hours to figure out.

Usage

When this repo was started we really wanted people to understand the signing flow. If that's your case, you should read the [Signing PDF in simple steps] section. If you are here with "Give me the code", you should maybe go to our packages/examples.

Depending on your usecase you may need different combinations of packages.

I am getting PDFs that already have placeholders

This is the most simple case of them all. $ npm i -S @signpdf/signpdf @signpdf/signer-p12 node-forge. Then have a look at the with-placeholder.js example. It should be as simple as:

import signpdf from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
...
const signer = new P12Signer(fs.readFileSync(PATH_TO_P12_CERTIFICATE));
const signedPdf = await signpdf.sign(fs.readFileSync(PATH_TO_PDF_FILE), signer);

I am generating a PDF with PDFKit

This is how the library was started as we needed to sign a document that we were generating on the fly. You will need $ npm i -S @signpdf/signpdf @signpdf/placeholder-pdfkit010 @signpdf/signer-p12 node-forge and a look at the pdfkit010.js example.

I have a .pdf file and I want to sign it

This seems to be the most common usecase - people work with PDF documents coming from different sources and they need to digitally sign them. Both placeholder helpers placeholder-plain and placeholder-pdf-lib can help here.

Plain

Start with $ npm i -S @signpdf/signpdf @signpdf/placeholder-plain @signpdf/signer-p12 node-forge. Head over to either the JS example or the TS one. An advantage of working with the plain version would be that in theory it should be quicker and use less memory (not benchmarked). A great disadvantage: it is very fragile relying on strings being poisitioned in a certain way.

PDF-LIB

$ npm i -S @signpdf/signpdf @signpdf/placeholder-pdf-lib pdf-lib @signpdf/signer-p12 node-forge gets you started. Then comes the the PDF-LIB example. PDF-LIB provides tremendous PDF API, it is very well documented and well supported.

Packages

@signpdf is split into multiple packages. In the case where you are already working with the PDF-generating library PDFKit, this is the command you will start with once you want to start signing these documents: $ npm i -S @signpdf/signpdf @signpdf/placeholder-pdfkit010 @signpdf/signer-p12 node-forge. So what are all these packages and why do you need them?

npm version

This is the main package, the integrating one, the one that wraps everything up. It uses a Signer implementation that provides cryptographic signing to sign a well-prepared PDF document. A PDF document is well-prepared if it has a signature placeholder. If your PDF does not have that, you may want to add one using one of our placeholder helpers.

Signers

Signers are small libraries that @signpdf/signpdf will call with a PDF and they will know how to provide an e-signature in return. Their output is then fed as the signature in the resulting document.

npm version

With the help of its peerDependency node-forge the P12 signer provides the actual cryptographic signing of a Buffer using a P12 certificate bundle. This is done in detached mode as required for PDF.

Placeholder helpers

A placeholder is the e-signature equivallent of the label "Sign here:......." in your paper document. They are a required part of the process of Signing PDFs. Different projects acquire their PDFs differently so we try to support some helpers that know how to add e-signature placeholders.

npm version

Works on top of PDFKit 0.11.0+ and given a PDFDocument that is in the works (not yet ended), adds an e-signature placeholder. When the placeholder is in place @signpdf/signpdf can complete the process.

npm version

Works on top of PDFKit 0.10.0 and given a PDFDocument that is in the works (not yet ended), adds an e-signature placeholder. When the placeholder is in place @signpdf/signpdf can complete the process.

npm version

Uses the process and knowledge from placeholder-pdfkit010 on how to add e-signature placeholder but implements it with plain string operations (.indexOf(), .replace(), .match(), etc.). Because of the lack of semantics it is rather fragile. Additionally it doesn't support streams and only works on PDF version <= 1.3. Regardless of those disadvantages this helper seems to be the most popular among the users of @signpdf. When the placeholder is in place @signpdf/signpdf can complete the process.

npm version

Works with PDF-LIB and given a loaded PDFDocument, adds an e-signature placeholder. When the placeholder is in place @signpdf/signpdf can complete the process.

Notes

Signing PDF in simple steps

Generate a PDF or read a ready one

We have examples of PDFKit generation of documents and we also have some where a ready .pdf file is read. Browse through our examples.

Append a signature placeholder

What's needed is a Sig element and a Widget that is also linked in a Form. The form needs to be referenced in the Root descriptor of the PDF as well. A (hopefully) readable sample is available in the helpers. Note the Contents descriptor of the Sig where zeros are placed that will later be replaced with the actual signature.

We provides placeholder helpers that do that.

Signature length

Note: Signing in detached mode makes the signature length independent of the PDF's content length, but it may still vary between different signing certificates. So every time you sign using the same P12 you will get the same length of the output signature, no matter the length of the signed content. It is safe to find out the actual signature length your certificate produces and use it to properly configure the placeholder length.

PAdES compliant signatures

To produce PAdES compliant signatures, the ETSI Signature Dictionary SubFilter value must be ETSI.CAdES.detached instead of the standard Adobe value. This can be declared using the subFilter option argument passed to placeholder helpers.

import { pdfkitAddPlaceholder } from '@signpdf/placeholder-pdfkit010';
import { SUBFILTER_ETSI_CADES_DETACHED } from '@signpdf/utils';

const pdfToSign = pdfkitAddPlaceholder({
  ...,
  subFilter: SUBFILTER_ETSI_CADES_DETACHED,
});

Generate and apply signature

That's where the @signpdf/signpdf kicks in. Given a PDF and a signer implementation a signature is generated and replaced in the placeholder.

import signpdf from '@signpdf/signpdf';

...
const signedPdf = await signpdf.sign(pdfBuffer, signer);

Credits

node-forge

node-forge is used for working with signatures. It is an awesome package written in pure JavaScript and supports signing in detached mode. Many thanks to all the guys who wrote and maintain it.

PDFKit

PDFKit is extensively used for generating PDFs with a signature placeholder and additionally its flows are used in placeholder-plain. Thanks to the guys of PDFKit as they've made PDF generation incredibly easy.

pdfsign.js

The signing flow of @signpdf/signpdf and @signpdf/signer-p12 is a rework of what was already in pdfsign.js so thanks go to @tbocek.