Skip to content

Commit

Permalink
Merge 5b617fc into 333a8c5
Browse files Browse the repository at this point in the history
  • Loading branch information
vbuch committed Jun 28, 2019
2 parents 333a8c5 + 5b617fc commit 0bc92d0
Show file tree
Hide file tree
Showing 61 changed files with 6,474 additions and 295 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ node_js:
- "10"
- "node"
sudo: false
install: npm install
install: yarn install
script:
- npm test
- yarn test
notifications:
email:
on_success: change
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# CHANGELOG

## [next]

* Remove signature verification as it is an incomplete implementation
* Split helpers as they became a huge piece of code
* Renamed addSignaturePlaceholder to pdfkitAddPlaceholder
* Implemented plainAddPlaceholder that works without pdfkit but with string/Buffer operations instead
* Started this CHNAGELOG

## [0.3.0]

* Added signature verification
* Extracted helpers out to make them reusable
46 changes: 16 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,11 @@ Simple signing of PDFs in node.
* [node-signpdf](#node-signpdf)
* [Purpose](#purpose)
* [Usage](#usage)
* [Signing](#signing)
* [Verifying](#verifying)
* [Notes](#notes)
* [Signing PDF in simple steps](#signing-pdf-in-simple-steps)
* [Generate a PDF](#generate-a-pdf)
* [Append a signature placeholder](#append-a-signature-placeholder)
* [Generate and apply signature](#generate-and-apply-signature)
* [Verifying PDF signature](#verifying-pdf-signature)
* [Dependencies](#dependencies)
* [Credits](#credits)
* [Contributing](#contributing)
Expand All @@ -28,36 +25,23 @@ The purpose of this package is not as much to be used as a dependendency, althou

## Usage

Simply said this could be used in two steps. `install` and `sign`.

Install with `npm i -S node-signpdf node-forge`.

### Signing
In practice we expect that most people will just read through the code we've written in the testing part of this package and figure it out themselves. If that's your case, you should read the [[Signing PDF in simple steps]](#signing-pdf-in-simple-steps) section.

Call `.sign()`
### With pdfkit-created document

```javascript
import signer from 'node-signpdf';
You have already created a PDF using foliojs/pdfkit and you want to sign that. Here's how this becomes possible:

const signedPdf = signer.sign(
fs.readFileSync(PATH_TO_PDF_FILE)
fs.readFileSync(PATH_TO_P12_CERTIFICATE),
);
```
Before saving your file, you need to a add a signature placehoolder to it. We have a helper for that.

In practice we expect that most people will just read through the code we've written in the testing part of this package and figure it out themselves. If that's your case, you should read the [[Signing PDF in simple steps]](#signing-pdf-in-simple-steps) section.
Once you have the placeholder, just [sign the document].

### Verifying
### With any PDF document

To verify a signed pdf call `.verify()`.
Yes. This is new since version 1.0. We have a helper that can add a signature placeholder in at least the most basic PDFs without depending on pdfkit.

```javascript
import signer from 'node-signpdf';
...

const signedPdfBuffer = signer.sign(pdfBuffer, p12Buffer);
const {verified} = signer.verify(signedPdfBuffer);
```
Once you have the placeholder, just [sign the document].

## Notes

Expand All @@ -77,18 +61,19 @@ See the [unit-testing code](https://github.com/vbuch/node-signpdf/blob/master/sr

### 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](https://github.com/vbuch/node-signpdf/blob/master/src/helpers.js#L12) 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.
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](https://github.com/vbuch/node-signpdf/blob/master/src/helpers/pdfkitAddPlaceholder.js) 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.

This package provides two [helpers](https://github.com/vbuch/node-signpdf/blob/master/src/helpers/index.js) for adding the signature placeholder:

* pdfkitAddPlaceholder
* plainAddPlaceholder

**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.

### Generate and apply signature

That's where `node-signpdf` kicks in. Given a PDF and a P12 certificate a signature is generated in detached mode and is replaced in the placeholder. This is best demonstrated in [the tests](https://github.com/vbuch/node-signpdf/blob/master/src/signpdf.test.js#L100).

## Verifying PDF signature

The signed PDF file has the public certificate embeded in it, so all we need to verify a PDF file is the file itself.

## Dependencies

[node-forge](https://github.com/digitalbazaar/forge) is used for working with signatures.
Expand All @@ -100,6 +85,7 @@ The signed PDF file has the public certificate embeded in it, so all we need to
* The whole signing flow is a rework of what's already [in pdfsign.js](https://github.com/Communication-Systems-Group/pdfsign.js/blob/master/src/js/main.js#L594) so thanks go to [@tbocek](https://github.com/tbocek)
* [node-forge](https://github.com/digitalbazaar/forge) is an awesome package written in pure JavaScript and [supports signing in detached mode](https://github.com/digitalbazaar/forge/pull/605). Many thanks to all the guys who wrote and maintain it.
* Thanks to the guys of [PDFKit](https://github.com/foliojs/pdfkit) as well. They've made PDF generation incredibly easy.
* This incredible [Stack Overflow answer](https://stackoverflow.com/questions/15969733/verify-pkcs7-pem-signature-unpack-data-in-node-js/16148331#16148331) for describing the whole process of verifying PKCS7 signatures.

## [Contributing](CONTRIBUTING.md)

## [Changelog](CHANGELOG.md)
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ module.exports = {
],
coveragePathIgnorePatterns: [
'/node_modules/',
'/src/helpers/pdfkit/',
],
};
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@
"node-forge": "^0.7.6"
},
"devDependencies": {
"assertion-error": "^1.1.0",
"@babel/cli": "^7.0.0",
"@babel/core": "^7.4.0",
"@babel/node": "^7.0.0",
"@babel/plugin-syntax-object-rest-spread": "^7.0.0",
"@babel/preset-env": "^7.4.2",
"assertion-error": "^1.1.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^24.5.0",
"babel-plugin-module-resolver": "^3.1.1",
Expand All @@ -83,6 +83,6 @@
"eslint-plugin-jest": "^22.4.1",
"jest": "^24.5.0",
"node-forge": "^0.7.6",
"pdfkit": "^0.9.0"
"pdfkit": "^0.10.0"
}
}
23 changes: 23 additions & 0 deletions resources/SOURCES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# SOURCES

Just documenting where those PDFs came from:

## contributing.pdf

Exported/Printed from Chrome viewing an altered version of CONTRIBUTING.md on GitHub.

## formexample.pdf

[foersom.com](http://foersom.com/net/HowTo/data/OoPdfFormExample.pdf)

## incrementally_signed.pdf

w3dummy.pdf signed by tests with plainAddPlaceholder().

## signed.pdf

Generated with PDFKit and signed by tests.

## w3dummy.pdf

[w3.org](https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf)
File renamed without changes.
File renamed without changes.
Binary file added resources/contributing.pdf
Binary file not shown.
Binary file added resources/formexample.pdf
Binary file not shown.
Binary file added resources/incrementally_signed.pdf
Binary file not shown.
Binary file added resources/signed.pdf
Binary file not shown.
Binary file added resources/w3dummy.pdf
Binary file not shown.
File renamed without changes.
13 changes: 13 additions & 0 deletions src/__snapshots__/signpdf.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Test signing errors on wrong certificate passphrase 1`] = `"PKCS#12 MAC could not be verified. Invalid password?"`;

exports[`Test signing errors when no matching certificate is found in bags 1`] = `"Failed to find a certificate that matches the private key."`;

exports[`Test signing expects P12 certificate to be Buffer 1`] = `"p12 certificate expected as Buffer."`;

exports[`Test signing expects PDF to be Buffer 1`] = `"PDF expected as Buffer."`;

exports[`Test signing expects PDF to contain a ByteRange placeholder 1`] = `"Could not find ByteRange placeholder: /ByteRange [0 /********** /********** /**********]"`;

exports[`Test signing expects a reasonably sized placeholder 1`] = `"Signature exceeds placeholder length: 3224 > 4"`;
101 changes: 0 additions & 101 deletions src/helpers.js

This file was deleted.

13 changes: 9 additions & 4 deletions src/helpers.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import PDFDocument from 'pdfkit';
import signer from './signpdf';
import {addSignaturePlaceholder, extractSignature} from './helpers';
import {pdfkitAddPlaceholder, extractSignature} from './helpers';
import SignPdfError from './SignPdfError';

/**
Expand Down Expand Up @@ -39,7 +39,7 @@ const createPdf = (params = {
});

// Externally (to PDFKit) add the signature placeholder.
const refs = addSignaturePlaceholder({
const refs = pdfkitAddPlaceholder({
pdf,
reason: 'I am the author',
...params.placeholder,
Expand All @@ -52,10 +52,15 @@ const createPdf = (params = {
// See pdf.on('end'... on how it is then converted to Buffer.
pdf.end();
});

describe('Helpers', () => {
it('extract signature from signed pdf', async () => {
const pdfBuffer = await createPdf();
const p12Buffer = fs.readFileSync(`${__dirname}/../certificate.p12`);
const pdfBuffer = await createPdf({
placeholder: {
signatureLength: 1612,
},
});
const p12Buffer = fs.readFileSync(`${__dirname}/../resources/certificate.p12`);

const signedPdfBuffer = signer.sign(pdfBuffer, p12Buffer);
const originalSignature = signer.lastSignature;
Expand Down
22 changes: 22 additions & 0 deletions src/helpers/PDFKitReferenceMock.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import PDFKitReferenceMock from './pdfkitReferenceMock';

describe('pdfkitReferenceMock', () => {
it('stores index', () => {
const index = 54321;
const instance = new PDFKitReferenceMock(index);
expect(instance.index).toBe(index);
});
it('accepts and stores additional data', () => {
const index = 123;
const data = 'DATA';
const instance = new PDFKitReferenceMock(index, {a: data, b: data});
expect(instance.index).toBe(index);
expect(instance.a).toBe(data);
expect(instance.b).toBe(data);
});
it('can be converted to string', () => {
const index = 123;
const instance = new PDFKitReferenceMock(index);
expect(instance.toString()).toMatchSnapshot();
});
});
3 changes: 3 additions & 0 deletions src/helpers/__snapshots__/PDFKitReferenceMock.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`pdfkitReferenceMock can be converted to string 1`] = `"123 0 R"`;
Binary file not shown.
10 changes: 10 additions & 0 deletions src/helpers/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Helpers index Exports expected helpers 1`] = `
Array [
"extractSignature",
"pdfkitAddPlaceholder",
"plainAddPlaceholder",
"removeTrailingNewLine",
]
`;
9 changes: 9 additions & 0 deletions src/helpers/__snapshots__/pdfkitAddPlaceholder.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`pdfkitAddPlaceholder adds placeholder to PDFKit document 1`] = `
Array [
"signature",
"form",
"widget",
]
`;
3 changes: 3 additions & 0 deletions src/helpers/__snapshots__/pdfkitReferenceMock.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`pdfkitReferenceMock can be converted to string 1`] = `"123 0 R"`;
2 changes: 2 additions & 0 deletions src/helpers/const.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DEFAULT_SIGNATURE_LENGTH = 8192;
export const DEFAULT_BYTE_RANGE_PLACEHOLDER = '**********';
Loading

0 comments on commit 0bc92d0

Please sign in to comment.