node-signpdf
Version:
DEPRECATED. Have a look at @signpdf/signpdf instead.
196 lines (148 loc) • 7.38 kB
JavaScript
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.
// Note that the authenticatedAttributes order is relevant for correct
// EU signature validation:
// https://ec.europa.eu/digital-building-blocks/DSS/webapp-demo/validation
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;
;