Skip to content

Commit

Permalink
Merge branch 'release/1.4.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
vbuch committed Nov 13, 2021
2 parents 5f784b9 + c38ae5b commit cf40fd6
Show file tree
Hide file tree
Showing 36 changed files with 2,719 additions and 2,708 deletions.
5 changes: 3 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"node": true,
"jest": true
},
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"plugins": [
"jest"
],
Expand All @@ -21,7 +21,8 @@
"no-console": ["warn", { "allow": ["warn", "error", "info"] }],
"space-before-function-paren": ["error", {"anonymous": "never", "named": "never", "asyncArrow": "always"}],
"radix": ["error", "as-needed"],
"class-methods-use-this": "off"
"class-methods-use-this": "off",
"jest/no-conditional-expect": "off"
},
"settings": {
"import/resolver": {
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/not-travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: not-travis
on: ["push", "pull_request"]
jobs:
do-de-stuff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Use Node.js 10.x
uses: actions/setup-node@v2
with:
node-version: 10.x

- run: yarn

- run: yarn lint

- run: yarn run jest --coverage

- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
19 changes: 0 additions & 19 deletions .travis.yml

This file was deleted.

7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG

## [1.4.0]

* Added support for overriding signature SubFilter value allowing the creation of PAdES compliant signatures;
* Added linting to CI;
* Bump dependencies;
* Removed Travis integration in favor of GitHub Actions;

## [1.3.3]

* plainAddPlaceholder: Fixed loss of PDF metadata when adding placeholder;
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
* [ashirman](https://github.com/ashirman)
* [brunoserrano](https://github.com/brunoserrano)
* [waaronking](https://github.com/waaronking)
* [dhensby](https://github.com/dhensby)
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ This package provides two [helpers](https://github.com/vbuch/node-signpdf/blob/m

**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 `pdfkitAddPlaceholder` and `plainAddPlaceholder`.

```js
import { SUBFILTER_ETSI_CADES_DETACHED, pdfkitAddPlaceholder } from 'node-signpdf';

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

### Generate and apply signature

That's where the Signer 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).
Expand Down
12 changes: 10 additions & 2 deletions dist/helpers/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DEFAULT_BYTE_RANGE_PLACEHOLDER = exports.DEFAULT_SIGNATURE_LENGTH = void 0;
exports.SUBFILTER_ETSI_CADES_DETACHED = exports.SUBFILTER_ADOBE_X509_SHA1 = exports.SUBFILTER_ADOBE_PKCS7_SHA1 = exports.SUBFILTER_ADOBE_PKCS7_DETACHED = exports.DEFAULT_BYTE_RANGE_PLACEHOLDER = exports.DEFAULT_SIGNATURE_LENGTH = void 0;
const DEFAULT_SIGNATURE_LENGTH = 8192;
exports.DEFAULT_SIGNATURE_LENGTH = DEFAULT_SIGNATURE_LENGTH;
const DEFAULT_BYTE_RANGE_PLACEHOLDER = '**********';
exports.DEFAULT_BYTE_RANGE_PLACEHOLDER = DEFAULT_BYTE_RANGE_PLACEHOLDER;
exports.DEFAULT_BYTE_RANGE_PLACEHOLDER = DEFAULT_BYTE_RANGE_PLACEHOLDER;
const SUBFILTER_ADOBE_PKCS7_DETACHED = 'adbe.pkcs7.detached';
exports.SUBFILTER_ADOBE_PKCS7_DETACHED = SUBFILTER_ADOBE_PKCS7_DETACHED;
const SUBFILTER_ADOBE_PKCS7_SHA1 = 'adbe.pkcs7.sha1';
exports.SUBFILTER_ADOBE_PKCS7_SHA1 = SUBFILTER_ADOBE_PKCS7_SHA1;
const SUBFILTER_ADOBE_X509_SHA1 = 'adbe.x509.rsa.sha1';
exports.SUBFILTER_ADOBE_X509_SHA1 = SUBFILTER_ADOBE_X509_SHA1;
const SUBFILTER_ETSI_CADES_DETACHED = 'ETSI.CAdES.detached';
exports.SUBFILTER_ETSI_CADES_DETACHED = SUBFILTER_ETSI_CADES_DETACHED;
6 changes: 3 additions & 3 deletions dist/helpers/extractSignature.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ var _SignPdfError = _interopRequireDefault(require("../SignPdfError"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const getSubstringIndex = (str, substring, n) => {
var times = 0,
index = null;
let times = 0;
let index = null;

while (times < n && index !== -1) {
index = str.indexOf(substring, index + 1);
times++;
times += 1;
}

return index;
Expand Down
4 changes: 2 additions & 2 deletions dist/helpers/findByteRange.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ const findByteRange = pdf => {
throw new _SignPdfError.default('PDF expected as Buffer.', _SignPdfError.default.TYPE_INPUT);
}

const byteRangeStrings = pdf.toString().match(/\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*\]{1}/g);
const byteRangeStrings = pdf.toString().match(/\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*]{1}/g);

if (!byteRangeStrings) {
throw new _SignPdfError.default('No ByteRangeStrings found within PDF buffer', _SignPdfError.default.TYPE_PARSE);
}

const byteRangePlaceholder = byteRangeStrings.find(s => s.includes(`/${_const.DEFAULT_BYTE_RANGE_PLACEHOLDER}`));
const byteRanges = byteRangeStrings.map(brs => brs.match(/[^\[\s]*(?:\d|\/\*{10})/g));
const byteRanges = byteRangeStrings.map(brs => brs.match(/[^[\s]*(?:\d|\/\*{10})/g));
return {
byteRangePlaceholder,
byteRangeStrings,
Expand Down
52 changes: 17 additions & 35 deletions dist/helpers/pdfkit/pdfobject.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ By Devon Govett
*/
const pad = (str, length) => (Array(length + 1).join('0') + str).slice(-length);

const escapableRe = /[\n\r\t\b\f\(\)\\]/g;
const escapableRe = /[\n\r\t\b\f()\\]/g;
const escapable = {
'\n': '\\n',
'\r': '\\r',
Expand All @@ -34,21 +34,7 @@ const escapable = {
')': '\\)'
}; // Convert little endian UTF-16 to big endian

const swapBytes = buff => {
const l = buff.length;

if (l & 0x01) {
throw new Error('Buffer length must be even');
} else {
for (let i = 0, end = l - 1; i < end; i += 2) {
const a = buff[i];
buff[i] = buff[i + 1];
buff[i + 1] = a;
}
}

return buff;
};
const swapBytes = buff => buff.swap16();

class PDFObject {
static convert(object, encryptFn = null) {
Expand Down Expand Up @@ -117,27 +103,23 @@ class PDFObject {

if ({}.toString.call(object) === '[object Object]') {
const out = ['<<'];
let streamData;

for (const key in object) {
if (object.hasOwnProperty(key)) {
let val = object[key];
let checkedValue = '';

if (val.toString().indexOf('<<') !== -1) {
checkedValue = val;
} else {
checkedValue = PDFObject.convert(val, encryptFn);
}

if (key === 'stream') {
streamData = `${key}\n${val}\nendstream`;
} else {
out.push(`/${key} ${checkedValue}`);
}
let streamData; // @todo this can probably be refactored into a reduce

Object.entries(object).forEach(([key, val]) => {
let checkedValue = '';

if (val.toString().indexOf('<<') !== -1) {
checkedValue = val;
} else {
checkedValue = PDFObject.convert(val, encryptFn);
}
}

if (key === 'stream') {
streamData = `${key}\n${val}\nendstream`;
} else {
out.push(`/${key} ${checkedValue}`);
}
});
out.push('>>');

if (streamData) {
Expand Down
17 changes: 10 additions & 7 deletions dist/helpers/pdfkitAddPlaceholder.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ const pdfkitAddPlaceholder = ({
name = 'Name from p12',
location = 'Location from p12',
signatureLength = _const.DEFAULT_SIGNATURE_LENGTH,
byteRangePlaceholder = _const.DEFAULT_BYTE_RANGE_PLACEHOLDER
byteRangePlaceholder = _const.DEFAULT_BYTE_RANGE_PLACEHOLDER,
subFilter = _const.SUBFILTER_ADOBE_PKCS7_DETACHED
}) => {
/* eslint-disable no-underscore-dangle,no-param-reassign */
// Generate the signature placeholder
const signature = pdf.ref({
Type: 'Sig',
Filter: 'Adobe.PPKLite',
SubFilter: 'adbe.pkcs7.detached',
SubFilter: subFilter,
ByteRange: [0, byteRangePlaceholder, byteRangePlaceholder, byteRangePlaceholder],
Contents: Buffer.from(String.fromCharCode(0).repeat(signatureLength)),
Reason: new String(reason),
Expand All @@ -60,15 +61,17 @@ const pdfkitAddPlaceholder = ({

const charsUntilIdEnd = 10;
const acroFormIdEnd = acroFormPosition - charsUntilIdEnd; // Let's find AcroForm ID by trying to find the "\n" before the ID
// 12 is a enough space to find the "\n" (generally it's 2 or 3, but I'm giving a big space though)
// 12 is a enough space to find the "\n"
// (generally it's 2 or 3, but I'm giving a big space though)

const maxAcroFormIdLength = 12;
let foundAcroFormId = '';
let index = charsUntilIdEnd + 1;

for (let index = charsUntilIdEnd + 1; index < charsUntilIdEnd + maxAcroFormIdLength; index++) {
let acroFormIdString = pdfBuffer.slice(acroFormPosition - index, acroFormIdEnd).toString();
for (index; index < charsUntilIdEnd + maxAcroFormIdLength; index += 1) {
const acroFormIdString = pdfBuffer.slice(acroFormPosition - index, acroFormIdEnd).toString();

if (acroFormIdString[0] == '\n') {
if (acroFormIdString[0] === '\n') {
break;
}

Expand All @@ -80,7 +83,7 @@ const pdfkitAddPlaceholder = ({
const acroForm = pdfSlice.slice(0, pdfSlice.indexOf('endobj')).toString();
acroFormId = parseInt(foundAcroFormId);
const acroFormFields = acroForm.slice(acroForm.indexOf('/Fields [') + 9, acroForm.indexOf(']'));
fieldIds = acroFormFields.split(' ').filter((element, index) => index % 3 === 0).map(fieldId => new _pdfkitReferenceMock.default(fieldId));
fieldIds = acroFormFields.split(' ').filter((element, i) => i % 3 === 0).map(fieldId => new _pdfkitReferenceMock.default(fieldId));
}

const signatureName = 'Signature'; // Generate signature annotation widget
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
const createBufferPageWithAnnotation = (pdf, info, pagesRef, widget) => {
const pagesDictionary = (0, _findObject.default)(pdf, info.xref, pagesRef).toString(); // Extend page dictionary with newly created annotations

let annotsStart, annotsEnd, annots;
let annotsStart;
let annotsEnd;
let annots;
annotsStart = pagesDictionary.indexOf('/Annots');

if (annotsStart > -1) {
Expand All @@ -29,7 +31,7 @@ const createBufferPageWithAnnotation = (pdf, info, pagesRef, widget) => {

const pagesDictionaryIndex = (0, _getIndexFromRef.default)(info.xref, pagesRef);
const widgetValue = widget.toString();
annots = annots + ' ' + widgetValue + ']'; // add the trailing ] back
annots = `${annots} ${widgetValue}]`; // add the trailing ] back

const preAnnots = pagesDictionary.substr(0, annotsStart);
let postAnnots = '';
Expand Down
2 changes: 1 addition & 1 deletion dist/helpers/plainAddPlaceholder/getPagesDictionaryRef.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
* @param {Object} info As extracted from readRef()
*/
function getPagesDictionaryRef(info) {
const pagesRefRegex = new RegExp('\\/Pages\\s+(\\d+\\s+\\d+\\s+R)', 'g');
const pagesRefRegex = /\/Pages\s+(\d+\s+\d+\s+R)/g;
const match = pagesRefRegex.exec(info.root);

if (match === null) {
Expand Down
8 changes: 5 additions & 3 deletions dist/helpers/plainAddPlaceholder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var _createBufferTrailer = _interopRequireDefault(require("./createBufferTrailer
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const isContainBufferRootWithAcroform = pdf => {
const bufferRootWithAcroformRefRegex = new RegExp('\\/AcroForm\\s+(\\d+\\s\\d+\\sR)', 'g');
const bufferRootWithAcroformRefRegex = /\/AcroForm\s+(\d+\s\d+\sR)/g;
const match = bufferRootWithAcroformRefRegex.exec(pdf.toString());
return match != null && match[1] != null && match[1] !== '';
};
Expand All @@ -51,7 +51,8 @@ const plainAddPlaceholder = ({
contactInfo = '[email protected]',
name = 'Name from p12',
location = 'Location from p12',
signatureLength = _const.DEFAULT_SIGNATURE_LENGTH
signatureLength = _const.DEFAULT_SIGNATURE_LENGTH,
subFilter = _const.SUBFILTER_ADOBE_PKCS7_DETACHED
}) => {
let pdf = (0, _removeTrailingNewLine.default)(pdfBuffer);
const info = (0, _readPdf.default)(pdf);
Expand Down Expand Up @@ -88,7 +89,8 @@ const plainAddPlaceholder = ({
contactInfo,
name,
location,
signatureLength
signatureLength,
subFilter
});

if (!isContainBufferRootWithAcroform(pdf)) {
Expand Down
2 changes: 1 addition & 1 deletion dist/helpers/plainAddPlaceholder/readRefTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const getXref = (pdf, position) => {
let reducer;

if (isContainingPrev) {
const pagesRefRegex = new RegExp('Prev (\\d+)', 'g');
const pagesRefRegex = /Prev (\d+)/g;
const match = pagesRefRegex.exec(infos);
const [, prevPosition] = match;
prev = prevPosition;
Expand Down
21 changes: 15 additions & 6 deletions dist/signpdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
var _exportNames = {
DEFAULT_BYTE_RANGE_PLACEHOLDER: true,
SignPdf: true,
SignPdfError: true
};
Expand All @@ -14,7 +13,7 @@ Object.defineProperty(exports, "SignPdfError", {
return _SignPdfError.default;
}
});
exports.default = exports.SignPdf = exports.DEFAULT_BYTE_RANGE_PLACEHOLDER = void 0;
exports.default = exports.SignPdf = void 0;

var _nodeForge = _interopRequireDefault(require("node-forge"));

Expand All @@ -33,14 +32,24 @@ Object.keys(_helpers).forEach(function (key) {
});
});

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _const = require("./helpers/const");

const DEFAULT_BYTE_RANGE_PLACEHOLDER = '**********';
exports.DEFAULT_BYTE_RANGE_PLACEHOLDER = DEFAULT_BYTE_RANGE_PLACEHOLDER;
Object.keys(_const).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _const[key];
}
});
});

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

class SignPdf {
constructor() {
this.byteRangePlaceholder = DEFAULT_BYTE_RANGE_PLACEHOLDER;
this.byteRangePlaceholder = _const.DEFAULT_BYTE_RANGE_PLACEHOLDER;
this.lastSignature = null;
}

Expand Down
Loading

0 comments on commit cf40fd6

Please sign in to comment.