mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
🐛 invalid pades signature fix node-sign pdf locally
This commit is contained in:
@ -149,15 +149,15 @@ class SignPdf {
|
||||
authenticatedAttributes: [{
|
||||
type: _nodeForge.default.pki.oids.contentType,
|
||||
value: _nodeForge.default.pki.oids.data
|
||||
}, {
|
||||
type: _nodeForge.default.pki.oids.messageDigest // value will be auto-populated at signing time
|
||||
|
||||
}, {
|
||||
type: _nodeForge.default.pki.oids.signingTime,
|
||||
// value can also be auto-populated at signing time
|
||||
// We may also support passing this as an option to sign().
|
||||
// Would be useful to match the creation time of the document for example.
|
||||
value: new Date()
|
||||
}, {
|
||||
type: _nodeForge.default.pki.oids.messageDigest // value will be auto-populated at signing time
|
||||
|
||||
}]
|
||||
}); // Sign in detached mode.
|
||||
|
||||
|
||||
@ -1,193 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
var _exportNames = {
|
||||
SignPdf: true,
|
||||
SignPdfError: true
|
||||
};
|
||||
exports.SignPdf = void 0;
|
||||
Object.defineProperty(exports, "SignPdfError", {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _SignPdfError.default;
|
||||
}
|
||||
});
|
||||
exports.default = void 0;
|
||||
|
||||
var _nodeForge = _interopRequireDefault(require("node-forge"));
|
||||
|
||||
var _SignPdfError = _interopRequireDefault(require("./SignPdfError"));
|
||||
|
||||
var _helpers = require("./helpers");
|
||||
|
||||
Object.keys(_helpers).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
||||
if (key in exports && exports[key] === _helpers[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _helpers[key];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var _const = require("./helpers/const");
|
||||
|
||||
Object.keys(_const).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
||||
if (key in exports && exports[key] === _const[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 = _const.DEFAULT_BYTE_RANGE_PLACEHOLDER;
|
||||
this.lastSignature = null;
|
||||
}
|
||||
|
||||
sign(pdfBuffer, p12Buffer, additionalOptions = {}) {
|
||||
const options = {
|
||||
asn1StrictParsing: false,
|
||||
passphrase: '',
|
||||
...additionalOptions
|
||||
};
|
||||
|
||||
if (!(pdfBuffer instanceof Buffer)) {
|
||||
throw new _SignPdfError.default('PDF expected as Buffer.', _SignPdfError.default.TYPE_INPUT);
|
||||
}
|
||||
|
||||
if (!(p12Buffer instanceof Buffer)) {
|
||||
throw new _SignPdfError.default('p12 certificate expected as Buffer.', _SignPdfError.default.TYPE_INPUT);
|
||||
}
|
||||
|
||||
let pdf = (0, _helpers.removeTrailingNewLine)(pdfBuffer); // Find the ByteRange placeholder.
|
||||
|
||||
const {
|
||||
byteRangePlaceholder
|
||||
} = (0, _helpers.findByteRange)(pdf);
|
||||
|
||||
if (!byteRangePlaceholder) {
|
||||
throw new _SignPdfError.default(`Could not find empty ByteRange placeholder: ${byteRangePlaceholder}`, _SignPdfError.default.TYPE_PARSE);
|
||||
}
|
||||
|
||||
const byteRangePos = pdf.indexOf(byteRangePlaceholder); // Calculate the actual ByteRange that needs to replace the placeholder.
|
||||
|
||||
const byteRangeEnd = byteRangePos + byteRangePlaceholder.length;
|
||||
const contentsTagPos = pdf.indexOf('/Contents ', byteRangeEnd);
|
||||
const placeholderPos = pdf.indexOf('<', contentsTagPos);
|
||||
const placeholderEnd = pdf.indexOf('>', placeholderPos);
|
||||
const placeholderLengthWithBrackets = placeholderEnd + 1 - placeholderPos;
|
||||
const placeholderLength = placeholderLengthWithBrackets - 2;
|
||||
const byteRange = [0, 0, 0, 0];
|
||||
byteRange[1] = placeholderPos;
|
||||
byteRange[2] = byteRange[1] + placeholderLengthWithBrackets;
|
||||
byteRange[3] = pdf.length - byteRange[2];
|
||||
let actualByteRange = `/ByteRange [${byteRange.join(' ')}]`;
|
||||
actualByteRange += ' '.repeat(byteRangePlaceholder.length - actualByteRange.length); // Replace the /ByteRange placeholder with the actual ByteRange
|
||||
|
||||
pdf = Buffer.concat([pdf.slice(0, byteRangePos), Buffer.from(actualByteRange), pdf.slice(byteRangeEnd)]); // Remove the placeholder signature
|
||||
|
||||
pdf = Buffer.concat([pdf.slice(0, byteRange[1]), pdf.slice(byteRange[2], byteRange[2] + byteRange[3])]); // Convert Buffer P12 to a forge implementation.
|
||||
|
||||
const forgeCert = _nodeForge.default.util.createBuffer(p12Buffer.toString('binary'));
|
||||
|
||||
const p12Asn1 = _nodeForge.default.asn1.fromDer(forgeCert);
|
||||
|
||||
const p12 = _nodeForge.default.pkcs12.pkcs12FromAsn1(p12Asn1, options.asn1StrictParsing, options.passphrase); // Extract safe bags by type.
|
||||
// We will need all the certificates and the private key.
|
||||
|
||||
|
||||
const certBags = p12.getBags({
|
||||
bagType: _nodeForge.default.pki.oids.certBag
|
||||
})[_nodeForge.default.pki.oids.certBag];
|
||||
|
||||
const keyBags = p12.getBags({
|
||||
bagType: _nodeForge.default.pki.oids.pkcs8ShroudedKeyBag
|
||||
})[_nodeForge.default.pki.oids.pkcs8ShroudedKeyBag];
|
||||
|
||||
const privateKey = keyBags[0].key; // Here comes the actual PKCS#7 signing.
|
||||
|
||||
const p7 = _nodeForge.default.pkcs7.createSignedData(); // Start off by setting the content.
|
||||
|
||||
|
||||
p7.content = _nodeForge.default.util.createBuffer(pdf.toString('binary')); // Then add all the certificates (-cacerts & -clcerts)
|
||||
// Keep track of the last found client certificate.
|
||||
// This will be the public key that will be bundled in the signature.
|
||||
|
||||
let certificate;
|
||||
Object.keys(certBags).forEach(i => {
|
||||
const {
|
||||
publicKey
|
||||
} = certBags[i].cert;
|
||||
p7.addCertificate(certBags[i].cert); // Try to find the certificate that matches the private key.
|
||||
|
||||
if (privateKey.n.compareTo(publicKey.n) === 0 && privateKey.e.compareTo(publicKey.e) === 0) {
|
||||
certificate = certBags[i].cert;
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof certificate === 'undefined') {
|
||||
throw new _SignPdfError.default('Failed to find a certificate that matches the private key.', _SignPdfError.default.TYPE_INPUT);
|
||||
} // Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
|
||||
|
||||
|
||||
p7.addSigner({
|
||||
key: privateKey,
|
||||
certificate,
|
||||
digestAlgorithm: _nodeForge.default.pki.oids.sha256,
|
||||
authenticatedAttributes: [{
|
||||
type: _nodeForge.default.pki.oids.contentType,
|
||||
value: _nodeForge.default.pki.oids.data
|
||||
}, {
|
||||
type: _nodeForge.default.pki.oids.signingTime,
|
||||
// value can also be auto-populated at signing time
|
||||
// We may also support passing this as an option to sign().
|
||||
// Would be useful to match the creation time of the document for example.
|
||||
value: new Date()
|
||||
}, {
|
||||
type: _nodeForge.default.pki.oids.messageDigest // value will be auto-populated at signing time
|
||||
|
||||
}]
|
||||
}); // Sign in detached mode.
|
||||
|
||||
p7.sign({
|
||||
detached: true
|
||||
}); // Check if the PDF has a good enough placeholder to fit the signature.
|
||||
|
||||
const raw = _nodeForge.default.asn1.toDer(p7.toAsn1()).getBytes(); // placeholderLength represents the length of the HEXified symbols but we're
|
||||
// checking the actual lengths.
|
||||
|
||||
|
||||
if (raw.length * 2 > placeholderLength) {
|
||||
throw new _SignPdfError.default(`Signature exceeds placeholder length: ${raw.length * 2} > ${placeholderLength}`, _SignPdfError.default.TYPE_INPUT);
|
||||
}
|
||||
|
||||
let signature = Buffer.from(raw, 'binary').toString('hex'); // Store the HEXified signature. At least useful in tests.
|
||||
|
||||
this.lastSignature = signature; // Pad the signature with zeroes so the it is the same length as the placeholder
|
||||
|
||||
signature += Buffer.from(String.fromCharCode(0).repeat(placeholderLength / 2 - raw.length)).toString('hex'); // Place it in the document.
|
||||
|
||||
pdf = Buffer.concat([pdf.slice(0, byteRange[1]), Buffer.from(`<${signature}>`), pdf.slice(byteRange[1])]); // Magic. Done.
|
||||
|
||||
return pdf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.SignPdf = SignPdf;
|
||||
|
||||
var _default = new SignPdf();
|
||||
|
||||
exports.default = _default;
|
||||
Reference in New Issue
Block a user