Skip to content

Commit 292d4d3

Browse files
committed
Simplify P12Signer implementation
1 parent 2df2428 commit 292d4d3

7 files changed

Lines changed: 123 additions & 168 deletions

File tree

packages/signer-p12/dist/P12Signer.d.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ export class P12Signer extends Signer {
1414
asn1StrictParsing: boolean;
1515
};
1616
cert: any;
17+
certBags: any;
18+
keyBags: any;
1719
/**
18-
* @param {Buffer} pdfBuffer
19-
* @param {Date | undefined} signingTime
20-
* @returns {Buffer}
20+
* Retrieve all certificates from the safe bag and determine the signing certificate.
21+
* @returns {Promise<Uint8Array[]>}
2122
*/
22-
sign(pdfBuffer: Buffer, signingTime?: Date | undefined): Buffer;
23+
getCertificate(): Promise<Uint8Array[]>;
2324
}
2425
export type SignerOptions = {
2526
passphrase?: string;
2627
asn1StrictParsing?: boolean;
2728
};
28-
import { Signer } from '@signpdf/utils';
29+
import { Signer } from '@signpdf/signer';
2930
//# sourceMappingURL=P12Signer.d.ts.map

packages/signer-p12/dist/P12Signer.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/signer-p12/dist/P12Signer.js

Lines changed: 40 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ Object.defineProperty(exports, "__esModule", {
66
exports.P12Signer = void 0;
77
var _nodeForge = _interopRequireDefault(require("node-forge"));
88
var _utils = require("@signpdf/utils");
9+
var _signer = require("@signpdf/signer");
910
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
1011
/**
1112
* @typedef {object} SignerOptions
1213
* @prop {string} [passphrase]
1314
* @prop {boolean} [asn1StrictParsing]
1415
*/
1516

16-
class P12Signer extends _utils.Signer {
17+
class P12Signer extends _signer.Signer {
18+
hashAlgorithm = 'SHA-256';
19+
signAlgorithm = 'RSASSA-PKCS1-v1_5';
20+
1721
/**
1822
* @param {Buffer | Uint8Array | string} p12Buffer
1923
* @param {SignerOptions} additionalOptions
@@ -26,82 +30,64 @@ class P12Signer extends _utils.Signer {
2630
passphrase: '',
2731
...additionalOptions
2832
};
29-
this.cert = _nodeForge.default.util.createBuffer(buffer.toString('binary'));
30-
}
31-
32-
/**
33-
* @param {Buffer} pdfBuffer
34-
* @param {Date | undefined} signingTime
35-
* @returns {Buffer}
36-
*/
37-
async sign(pdfBuffer, signingTime = undefined) {
38-
if (!(pdfBuffer instanceof Buffer)) {
39-
throw new _utils.SignPdfError('PDF expected as Buffer.', _utils.SignPdfError.TYPE_INPUT);
40-
}
4133

4234
// Convert Buffer P12 to a forge implementation.
35+
this.cert = _nodeForge.default.util.createBuffer(buffer.toString('binary'));
4336
const p12Asn1 = _nodeForge.default.asn1.fromDer(this.cert);
4437
const p12 = _nodeForge.default.pkcs12.pkcs12FromAsn1(p12Asn1, this.options.asn1StrictParsing, this.options.passphrase);
4538

4639
// Extract safe bags by type.
4740
// We will need all the certificates and the private key.
48-
const certBags = p12.getBags({
41+
this.certBags = p12.getBags({
4942
bagType: _nodeForge.default.pki.oids.certBag
5043
})[_nodeForge.default.pki.oids.certBag];
51-
const keyBags = p12.getBags({
44+
this.keyBags = p12.getBags({
5245
bagType: _nodeForge.default.pki.oids.pkcs8ShroudedKeyBag
5346
})[_nodeForge.default.pki.oids.pkcs8ShroudedKeyBag];
54-
const privateKey = keyBags[0].key;
55-
// Here comes the actual PKCS#7 signing.
56-
const p7 = _nodeForge.default.pkcs7.createSignedData();
57-
// Start off by setting the content.
58-
p7.content = _nodeForge.default.util.createBuffer(pdfBuffer.toString('binary'));
47+
}
5948

60-
// Then add all the certificates (-cacerts & -clcerts)
61-
// Keep track of the last found client certificate.
49+
/**
50+
* Retrieve all certificates from the safe bag and determine the signing certificate.
51+
* @returns {Promise<Uint8Array[]>}
52+
*/
53+
async getCertificate() {
54+
const privateKey = this.keyBags[0].key;
55+
// Retrieve all the certificates (-cacerts & -clcerts)
56+
// Keep track of the last found client certificate matching the private key.
6257
// This will be the public key that will be bundled in the signature.
63-
let certificate;
64-
Object.keys(certBags).forEach(i => {
58+
let signCertFound = false;
59+
const certificates = [];
60+
Object.values(this.certBags).forEach(bag => {
6561
const {
6662
publicKey
67-
} = certBags[i].cert;
68-
p7.addCertificate(certBags[i].cert);
63+
} = bag.cert;
6964

7065
// Try to find the certificate that matches the private key.
7166
if (privateKey.n.compareTo(publicKey.n) === 0 && privateKey.e.compareTo(publicKey.e) === 0) {
72-
certificate = certBags[i].cert;
67+
// Insert signing certificate in front
68+
certificates.unshift(bag.cert);
69+
signCertFound = true;
70+
} else {
71+
// Insert all other certificates at the end
72+
certificates.push(bag.cert);
7373
}
7474
});
75-
if (typeof certificate === 'undefined') {
75+
if (!signCertFound) {
7676
throw new _utils.SignPdfError('Failed to find a certificate that matches the private key.', _utils.SignPdfError.TYPE_INPUT);
7777
}
78+
// Convert certificates to their binary representation
79+
return certificates.map(cert => Buffer.from(_nodeForge.default.asn1.toDer(_nodeForge.default.pki.certificateToAsn1(cert)).getBytes(), 'binary'));
80+
}
7881

79-
// Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
80-
// Note that the authenticatedAttributes order is relevant for correct
81-
// EU signature validation:
82-
// https://ec.europa.eu/digital-building-blocks/DSS/webapp-demo/validation
83-
p7.addSigner({
84-
key: privateKey,
85-
certificate,
86-
digestAlgorithm: _nodeForge.default.pki.oids.sha256,
87-
authenticatedAttributes: [{
88-
type: _nodeForge.default.pki.oids.contentType,
89-
value: _nodeForge.default.pki.oids.data
90-
}, {
91-
type: _nodeForge.default.pki.oids.signingTime,
92-
// value can also be auto-populated at signing time
93-
value: signingTime !== null && signingTime !== void 0 ? signingTime : new Date()
94-
}, {
95-
type: _nodeForge.default.pki.oids.messageDigest
96-
// value will be auto-populated at signing time
97-
}]
98-
});
99-
100-
// Sign in detached mode.
101-
p7.sign({
102-
detached: true
103-
});
104-
return Buffer.from(_nodeForge.default.asn1.toDer(p7.toAsn1()).getBytes(), 'binary');
82+
/**
83+
* Retrieve the private key from the safe bag.
84+
* @returns {Promise<Uint8Array>}
85+
*/
86+
async getKey() {
87+
const privateKey = this.keyBags[0].key;
88+
// Convert private key to its pkcs8 binary representation
89+
const pkcs8 = _nodeForge.default.pki.wrapRsaPrivateKey(_nodeForge.default.pki.privateKeyToAsn1(privateKey));
90+
return Buffer.from(_nodeForge.default.asn1.toDer(pkcs8).getBytes(), 'binary');
10591
}
10692
}
10793
exports.P12Signer = P12Signer;

packages/signer-p12/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"lint": "eslint -c .eslintrc --ignore-path ../../.eslintignore ./"
4040
},
4141
"dependencies": {
42+
"@signpdf/signer": "^3.2.0",
4243
"@signpdf/utils": "^3.2.0"
4344
},
4445
"peerDependencies": {
Lines changed: 41 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import forge from 'node-forge';
2-
import {convertBuffer, SignPdfError, Signer} from '@signpdf/utils';
2+
import {convertBuffer, SignPdfError} from '@signpdf/utils';
3+
import {Signer} from '@signpdf/signer';
34

45
/**
56
* @typedef {object} SignerOptions
@@ -8,6 +9,10 @@ import {convertBuffer, SignPdfError, Signer} from '@signpdf/utils';
89
*/
910

1011
export class P12Signer extends Signer {
12+
hashAlgorithm = 'SHA-256';
13+
14+
signAlgorithm = 'RSASSA-PKCS1-v1_5';
15+
1116
/**
1217
* @param {Buffer | Uint8Array | string} p12Buffer
1318
* @param {SignerOptions} additionalOptions
@@ -22,23 +27,9 @@ export class P12Signer extends Signer {
2227
passphrase: '',
2328
...additionalOptions,
2429
};
25-
this.cert = forge.util.createBuffer(buffer.toString('binary'));
26-
}
27-
28-
/**
29-
* @param {Buffer} pdfBuffer
30-
* @param {Date | undefined} signingTime
31-
* @returns {Buffer}
32-
*/
33-
async sign(pdfBuffer, signingTime = undefined) {
34-
if (!(pdfBuffer instanceof Buffer)) {
35-
throw new SignPdfError(
36-
'PDF expected as Buffer.',
37-
SignPdfError.TYPE_INPUT,
38-
);
39-
}
4030

4131
// Convert Buffer P12 to a forge implementation.
32+
this.cert = forge.util.createBuffer(buffer.toString('binary'));
4233
const p12Asn1 = forge.asn1.fromDer(this.cert);
4334
const p12 = forge.pkcs12.pkcs12FromAsn1(
4435
p12Asn1,
@@ -48,69 +39,59 @@ export class P12Signer extends Signer {
4839

4940
// Extract safe bags by type.
5041
// We will need all the certificates and the private key.
51-
const certBags = p12.getBags({
42+
this.certBags = p12.getBags({
5243
bagType: forge.pki.oids.certBag,
5344
})[forge.pki.oids.certBag];
54-
const keyBags = p12.getBags({
45+
this.keyBags = p12.getBags({
5546
bagType: forge.pki.oids.pkcs8ShroudedKeyBag,
5647
})[forge.pki.oids.pkcs8ShroudedKeyBag];
48+
}
5749

58-
const privateKey = keyBags[0].key;
59-
// Here comes the actual PKCS#7 signing.
60-
const p7 = forge.pkcs7.createSignedData();
61-
// Start off by setting the content.
62-
p7.content = forge.util.createBuffer(pdfBuffer.toString('binary'));
63-
64-
// Then add all the certificates (-cacerts & -clcerts)
65-
// Keep track of the last found client certificate.
50+
/**
51+
* Retrieve all certificates from the safe bag and determine the signing certificate.
52+
* @returns {Promise<Uint8Array[]>}
53+
*/
54+
async getCertificate() {
55+
const privateKey = this.keyBags[0].key;
56+
// Retrieve all the certificates (-cacerts & -clcerts)
57+
// Keep track of the last found client certificate matching the private key.
6658
// This will be the public key that will be bundled in the signature.
67-
let certificate;
68-
Object.keys(certBags).forEach((i) => {
69-
const {publicKey} = certBags[i].cert;
70-
71-
p7.addCertificate(certBags[i].cert);
59+
let signCertFound = false;
60+
const certificates = [];
61+
Object.values(this.certBags).forEach((bag) => {
62+
const {publicKey} = bag.cert;
7263

7364
// Try to find the certificate that matches the private key.
7465
if (privateKey.n.compareTo(publicKey.n) === 0
7566
&& privateKey.e.compareTo(publicKey.e) === 0
7667
) {
77-
certificate = certBags[i].cert;
68+
// Insert signing certificate in front
69+
certificates.unshift(bag.cert);
70+
signCertFound = true;
71+
} else {
72+
// Insert all other certificates at the end
73+
certificates.push(bag.cert);
7874
}
7975
});
8076

81-
if (typeof certificate === 'undefined') {
77+
if (!signCertFound) {
8278
throw new SignPdfError(
8379
'Failed to find a certificate that matches the private key.',
8480
SignPdfError.TYPE_INPUT,
8581
);
8682
}
83+
// Convert certificates to their binary representation
84+
return certificates.map((cert) => Buffer.from(forge.asn1.toDer(forge.pki.certificateToAsn1(cert)).getBytes(), 'binary'));
85+
}
8786

88-
// Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
89-
// Note that the authenticatedAttributes order is relevant for correct
90-
// EU signature validation:
91-
// https://ec.europa.eu/digital-building-blocks/DSS/webapp-demo/validation
92-
p7.addSigner({
93-
key: privateKey,
94-
certificate,
95-
digestAlgorithm: forge.pki.oids.sha256,
96-
authenticatedAttributes: [
97-
{
98-
type: forge.pki.oids.contentType,
99-
value: forge.pki.oids.data,
100-
}, {
101-
type: forge.pki.oids.signingTime,
102-
// value can also be auto-populated at signing time
103-
value: signingTime ?? new Date(),
104-
}, {
105-
type: forge.pki.oids.messageDigest,
106-
// value will be auto-populated at signing time
107-
},
108-
],
109-
});
110-
111-
// Sign in detached mode.
112-
p7.sign({detached: true});
113-
114-
return Buffer.from(forge.asn1.toDer(p7.toAsn1()).getBytes(), 'binary');
87+
/**
88+
* Retrieve the private key from the safe bag.
89+
* @returns {Promise<Uint8Array>}
90+
*/
91+
async getKey() {
92+
const privateKey = this.keyBags[0].key;
93+
// Convert private key to its pkcs8 binary representation
94+
const pkcs8 = forge.pki.wrapRsaPrivateKey(forge.pki.privateKeyToAsn1(privateKey));
95+
return Buffer.from(forge.asn1.toDer(pkcs8).getBytes(), 'binary');
11596
}
11697
}

0 commit comments

Comments
 (0)