Skip to content

Commit

Permalink
Add base Signer and ExternalSigner implementations using PKI.js
Browse files Browse the repository at this point in the history
  • Loading branch information
dcbr authored and vbuch committed Feb 9, 2024
1 parent d07f655 commit 6ca87ca
Show file tree
Hide file tree
Showing 39 changed files with 1,303 additions and 49 deletions.
4 changes: 2 additions & 2 deletions packages/signer-p12/dist/P12Signer.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @prop {string} [passphrase]
* @prop {boolean} [asn1StrictParsing]
*/
export class P12Signer extends Signer {
export class P12Signer extends ISigner {
/**
* @param {Buffer | Uint8Array | string} p12Buffer
* @param {SignerOptions} additionalOptions
Expand All @@ -19,5 +19,5 @@ export type SignerOptions = {
passphrase?: string;
asn1StrictParsing?: boolean;
};
import { Signer } from '@signpdf/utils';
import { ISigner } from '@signpdf/utils';
//# sourceMappingURL=P12Signer.d.ts.map
2 changes: 1 addition & 1 deletion packages/signer-p12/dist/P12Signer.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/signer-p12/dist/P12Signer.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
* @prop {boolean} [asn1StrictParsing]
*/

class P12Signer extends _utils.Signer {
class P12Signer extends _utils.ISigner {
/**
* @param {Buffer | Uint8Array | string} p12Buffer
* @param {SignerOptions} additionalOptions
Expand Down
4 changes: 2 additions & 2 deletions packages/signer-p12/src/P12Signer.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import forge from 'node-forge';
import {convertBuffer, SignPdfError, Signer} from '@signpdf/utils';
import {convertBuffer, SignPdfError, ISigner} from '@signpdf/utils';

/**
* @typedef {object} SignerOptions
* @prop {string} [passphrase]
* @prop {boolean} [asn1StrictParsing]
*/

export class P12Signer extends Signer {
export class P12Signer extends ISigner {
/**
* @param {Buffer | Uint8Array | string} p12Buffer
* @param {SignerOptions} additionalOptions
Expand Down
3 changes: 3 additions & 0 deletions packages/signer/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../babel.config.json"
}
5 changes: 5 additions & 0 deletions packages/signer/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extends": [
"@signpdf/eslint-config"
]
}
17 changes: 17 additions & 0 deletions packages/signer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Signer base implementation with PKI.js

for [![@signpdf](https://raw.githubusercontent.com/vbuch/node-signpdf/master/resources/logo-horizontal.svg?sanitize=true)](https://github.com/vbuch/node-signpdf/)

[![npm version](https://badge.fury.io/js/@signpdf%2Fsigner.svg)](https://badge.fury.io/js/@signpdf%2Fsigner)
[![Donate to this project using Buy Me A Coffee](https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg)](https://buymeacoffee.com/vbuch)

Uses `PKI.js` to create a detached signature of a given `Buffer`.

## Usage

This is an abstract base implementation of the `Signer` and `ExternalSigner` classes. Use any of the available implementations (or subclass any of these classes yourself) to sign an actual PDF file. See for example the [P12Signer package](/packages/signer-p12) for signing with a PKCS#12 certificate bundle.

## Notes

* Make sure to have a look at the docs of the [@signpdf family of packages](https://github.com/vbuch/node-signpdf/).
* Feel free to copy and paste any part of this code. See its defined [Purpose](https://github.com/vbuch/node-signpdf#purpose).
23 changes: 23 additions & 0 deletions packages/signer/dist/ExternalSigner.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Abstract ExternalSigner class taking care of creating a suitable signature for a given pdf
* using an external signature provider.
* Subclasses should specify the required signature and hashing algorithms used by the external
* provider (either through the `signAlgorithm` and `hashAlgorithm` attributes, or by overriding
* the `getSignAlgorithm` and `getHashAlgorithm` methods), as well as provide the used signing
* certificate and final signature (by implementing the `getCertificate` and `getSignature`
* methods).
*/
export class ExternalSigner extends Signer {
/**
* Method to retrieve the signature of the given hash (of the given data) from the external
* service. The original data is included in case the external signature provider computes
* the hash automatically before signing.
* To be implemented by subclasses.
* @param {Uint8Array} hash
* @param {Uint8Array} data
* @returns {Promise<Uint8Array>}
*/
getSignature(hash: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
}
import { Signer } from './Signer';
//# sourceMappingURL=ExternalSigner.d.ts.map
1 change: 1 addition & 0 deletions packages/signer/dist/ExternalSigner.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions packages/signer/dist/ExternalSigner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ExternalSigner = void 0;
var pkijs = _interopRequireWildcard(require("pkijs"));
var _utils = require("@signpdf/utils");
var _Signer = require("./Signer");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/* eslint-disable no-unused-vars */

/**
* Abstract ExternalSigner class taking care of creating a suitable signature for a given pdf
* using an external signature provider.
* Subclasses should specify the required signature and hashing algorithms used by the external
* provider (either through the `signAlgorithm` and `hashAlgorithm` attributes, or by overriding
* the `getSignAlgorithm` and `getHashAlgorithm` methods), as well as provide the used signing
* certificate and final signature (by implementing the `getCertificate` and `getSignature`
* methods).
*/
class ExternalSigner extends _Signer.Signer {
/**
* Method to retrieve the signature of the given hash (of the given data) from the external
* service. The original data is included in case the external signature provider computes
* the hash automatically before signing.
* To be implemented by subclasses.
* @param {Uint8Array} hash
* @param {Uint8Array} data
* @returns {Promise<Uint8Array>}
*/
async getSignature(hash, data) {
throw new _utils.SignPdfError(`getSignature() is not implemented on ${this.constructor.name}`, _utils.SignPdfError.TYPE_INPUT);
}

/**
* Get a "crypto" extension and override the function used by SignedData.sign to support
* external signing.
* @returns {pkijs.ICryptoEngine}
*/
getCrypto() {
const crypto = super.getCrypto();
crypto.sign = async (_algo, _key, data) => {
// Calculate hash
const hash = await crypto.digest({
name: this.hashAlgorithm
}, data);
// And pass it to the external signature provider
const signature = await this.getSignature(Buffer.from(hash), Buffer.from(data));
return signature;
};
return crypto;
}

/**
* Obtain a dummy private key to pass the correct signing parameters to the sign function.
* @returns {CryptoKey}
*/
async obtainKey() {
// The algorithm parameters cannot be passed directly to the SignedData.sign function, so we
// need to generate a dummy private key with the required parameters and pass that to the
// sign function. The private key is not actually used for signing, as we override the
// crypto.sign function in the getCrypto method.
const algorithmParams = this.crypto.getAlgorithmParameters(this.signAlgorithm, 'generatekey').algorithm;
const keypair = await this.crypto.generateKey({
name: this.signAlgorithm,
...algorithmParams,
hash: {
name: this.hashAlgorithm
}
}, false, ['sign', 'verify']);
return keypair.privateKey;
}
}
exports.ExternalSigner = ExternalSigner;
79 changes: 79 additions & 0 deletions packages/signer/dist/Signer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Abstract Signer class taking care of creating a suitable signature for a given pdf.
* Subclasses should specify the required signature and hashing algorithms (either through
* the `signAlgorithm` and `hashAlgorithm` attributes, or by overriding the `getSignAlgorithm`
* and `getHashAlgorithm` methods), as well as provide the signing certificate and private key
* used for signing (by implementing the `getCertificate` and `getKey` methods).
*/
export class Signer extends ISigner {
/** Signature algorithm used for PDF signing
* @type {string}
*/
signAlgorithm: string;
/** Hash algorithm used for PDF signing
* @type {string}
*/
hashAlgorithm: string;
/**
* Method to retrieve the signature algorithm used for PDF signing.
* To be implemented by subclasses or set in the `signAlgorithm` attribute.
* @returns {Promise<string>}
*/
getSignAlgorithm(): Promise<string>;
/**
* Method to retrieve the hashing algorithm used for PDF signing.
* To be implemented by subclasses or set in the `hashAlgorithm` attribute.
* @returns {Promise<string>}
*/
getHashAlgorithm(): Promise<string>;
/**
* Method to retrieve the signing certificate. If multiple certificates are returned, the first
* one is used for the actual signing, while the others are added for verification purposes.
* To be implemented by subclasses.
* @returns {Promise<Uint8Array | Uint8Array[]>}
*/
getCertificate(): Promise<Uint8Array | Uint8Array[]>;
/**
* Method to retrieve the private key used for signing.
* The returned private key should be in its PKCS#8 binary representation.
* To be implemented by subclasses.
* @returns {Promise<Uint8Array>}
*/
getKey(): Promise<Uint8Array>;
/**
* Get a "crypto" extension.
* @returns {pkijs.ICryptoEngine}
*/
getCrypto(): pkijs.ICryptoEngine;
/**
* Obtain the certificates used for signing (first one) and verification (whole list).
* @returns {pkijs.Certificate[]}
*/
obtainCertificates(): pkijs.Certificate[];
/**
* Obtain the private key used for signing.
* @returns {CryptoKey}
*/
obtainKey(): CryptoKey;
/**
* Obtain the signed attributes, which are the actual content that is signed in detached mode.
* @returns {pkijs.Attribute[]}
*/
obtainSignedAttributes(signingTime: any, data: any, signCert: any): pkijs.Attribute[];
/**
* Obtain the unsigned attributes.
* @returns {pkijs.Attribute[]}
*/
obtainUnsignedAttributes(signature: any): pkijs.Attribute[];
crypto: pkijs.ICryptoEngine;
/**
* Verify whether the signature generated by the sign function is correct.
* @param {Buffer} cmsSignedBuffer
* @param {Buffer} pdfBuffer
* @returns {boolean}
*/
verify(cmsSignedBuffer: Buffer, pdfBuffer: Buffer): boolean;
}
import { ISigner } from '@signpdf/utils';
import * as pkijs from 'pkijs';
//# sourceMappingURL=Signer.d.ts.map
1 change: 1 addition & 0 deletions packages/signer/dist/Signer.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6ca87ca

Please sign in to comment.