mirror of
https://github.com/documenso/documenso.git
synced 2025-11-14 00:32:43 +10:00
🐛 invalid pades signature fix node-sign pdf locally
This commit is contained in:
@ -149,15 +149,15 @@ class SignPdf {
|
|||||||
authenticatedAttributes: [{
|
authenticatedAttributes: [{
|
||||||
type: _nodeForge.default.pki.oids.contentType,
|
type: _nodeForge.default.pki.oids.contentType,
|
||||||
value: _nodeForge.default.pki.oids.data
|
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,
|
type: _nodeForge.default.pki.oids.signingTime,
|
||||||
// value can also be auto-populated at signing time
|
// value can also be auto-populated at signing time
|
||||||
// We may also support passing this as an option to sign().
|
// We may also support passing this as an option to sign().
|
||||||
// Would be useful to match the creation time of the document for example.
|
// Would be useful to match the creation time of the document for example.
|
||||||
value: new Date()
|
value: new Date()
|
||||||
|
}, {
|
||||||
|
type: _nodeForge.default.pki.oids.messageDigest // value will be auto-populated at signing time
|
||||||
|
|
||||||
}]
|
}]
|
||||||
}); // Sign in detached mode.
|
}); // 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