@syncfusion/ej2-pdf
Version:
Feature-rich JavaScript PDF library with built-in support for loading and manipulating PDF document.
626 lines (625 loc) • 28.5 kB
JavaScript
import { PdfCertificationFlags, CryptographicStandard, DigestAlgorithm } from '../../../enumerator';
import { PdfDocument } from '../../../pdf-document';
import { _PdfDictionary, _PdfName } from '../../../pdf-primitives';
import { _bytesToHex, _decode, _isNullOrUndefined } from '../../../utils';
import { _PdfCertificate } from './../pdf-certificate';
import { _PdfSignatureDictionary } from './signature-dictionary';
import { _PdfX509CertificateParser } from '../x509/x509-certificate-parser';
import { _PdfSignaturePrivateKey } from './signature-privatekey';
import { _PdfCryptographicMessageSyntaxSigner } from './cryptographic-signer';
import { Save } from '@syncfusion/ej2-file-utils';
import { initializeTelemetryFeature } from '@syncfusion/ej2-base';
/**
* 'PdfSignature' class represents a digital signature used for signing a PDF document.
*
* ```typescript
* // Load the document
* let document: PdfDocument = new PdfDocument(data);
* // Gets the first page of the document
* let page: PdfPage = document.getPage(0);
* // Access the PDF form
* let form: PdfForm = document.form;
* // Create a new signature field
* let field: PdfSignatureField = new PdfSignatureField(page, 'Signature', {x: 10, y: 10, width: 100, height: 50});
* // Create a new signature using PFX data and private key
* const sign: PdfSignature = PdfSignature.create({ cryptographicStandard: CryptographicStandard.cms, digestAlgorithm: DigestAlgorithm.sha256 }, certData, password);
* // Sets the signature to the field
* field.setSignature(sign);
* // Add the field into PDF form
* form.add(field);
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*/
var PdfSignature = /** @class */ (function () {
/**
* Initializes a new instance of the `PdfSignature` class.
*
* @private
*/
function PdfSignature() {
/**
* Whether the signature should be visible in the document.
*
* @private
*/
this._visible = true;
/**
* External certificate chain provided for external signing scenarios.
*
* @private
*/
this._externalChain = [];
/**
* Whether the field is locked (signature lock dictionary present).
*
* @private
*/
this._isLocked = false;
/**
* Whether the signature has been applied.
*
* @private
*/
this._signed = false;
/**
* Whether certificates should be appended to existing certificate collection.
*
* @private
*/
this._appendCertificates = false;
/**
* Indicates whether a timestamp token is present on the signature.
*
* @private
*/
this._hasTimeStamp = false;
/**
* When true, the signature represents timestamp-only content.
*
* @private
*/
this._isTimestampOnly = false;
this._digestAlgorithm = DigestAlgorithm.sha256;
this._cryptographicStandard = CryptographicStandard.cms;
this._documentPermissions = PdfCertificationFlags.forbidChanges;
}
PdfSignature.create = function (arg1, arg2, arg3, arg4) {
var signature = new PdfSignature();
initializeTelemetryFeature('DigitalSignature', 'PDFLibrary');
if (arg1 instanceof Uint8Array || typeof arg1 === 'string') {
var data = arg1 instanceof Uint8Array ? arg1 : _decode(arg1);
if (!data || data.length === 0) {
throw new Error('Certificate data is required.');
}
var password = arg2;
if (password === null || typeof password === 'undefined' || password.length === 0) {
throw new Error('Password is required to open the certificate.');
}
var certificate = new _PdfCertificate(data, password);
signature._certificate = certificate;
signature._certificateInfo = {
issuerName: certificate._issuerName,
serialNumber: certificate._serialNumber,
subjectName: certificate._subjectName,
validFrom: certificate._validFrom,
validTo: certificate._validTo,
version: certificate._version
};
if (arg3) {
signature._applySignatureOptions(arg3);
}
if (arg4 && typeof arg4 === 'function') {
signature._timestampCallback = arg4;
}
return signature;
}
if (typeof arg1 === 'function') {
signature._externalSignatureCallback = arg1;
if (Array.isArray(arg2)) {
var publicCerts = arg2;
for (var _i = 0, publicCerts_1 = publicCerts; _i < publicCerts_1.length; _i++) {
var data = publicCerts_1[_i];
if (data && data.length > 0) {
signature._externalChain.push(new _PdfX509CertificateParser()._readCertificate(data));
}
}
if (arg3) {
signature._applySignatureOptions(arg3);
}
if (arg4 && typeof arg4 === 'function') {
signature._timestampCallback = arg4;
}
return signature;
}
if (arg2 && typeof arg2 === 'object') {
signature._applySignatureOptions(arg2);
if (arg3 && typeof arg3 === 'function') {
signature._timestampCallback = arg3;
}
return signature;
}
return signature;
}
if (arg1 && typeof arg1 === 'object' && !Array.isArray(arg1)) {
signature._applySignatureOptions(arg1);
if (arg2 && typeof arg2 === 'function') {
signature._timestampCallback = arg2;
}
signature._isTimestampOnly = true;
return signature;
}
if ((arg1 === null || typeof arg1 === 'undefined') && (arg2 === null || typeof arg2 === 'undefined')
&& arg3 && arg4 && typeof arg4 === 'function') {
signature._applySignatureOptions(arg3);
signature._timestampCallback = arg4;
signature._isTimestampOnly = true;
return signature;
}
throw new Error('Cannot create signature due to invalid arguments.');
};
/**
* Gets the date when the PDF was signed.
*
* ```typescript
* // Load the document
* let document: PdfDocument = new PdfDocument(data);
* // Gets the first page of the document
* let page: PdfPage = document.getPage(0);
* // Access the PDF form
* let form: PdfForm = document.form;
* // Create a new signature field
* let field: PdfSignatureField = new PdfSignatureField(page, 'Signature', {x: 10, y: 10, width: 100, height: 50});
* // Create a new signature using PFX data and private key
* const sign: PdfSignature = PdfSignature.create({ cryptographicStandard: CryptographicStandard.cms, digestAlgorithm: DigestAlgorithm.sha256 }, certData, password);
* // Sets the signature to the field
* field.setSignature(sign);
* // Gets the signed date
* sign.getSignedDate();
* // Add the field into PDF form
* form.add(field);
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*
* @returns {Date} - The signed date.
*/
PdfSignature.prototype.getSignedDate = function () {
return this._signedDate;
};
/**
* Gets the certificate information associated with the PDF signature.
*
* ```typescript
* // Load the document
* let document: PdfDocument = new PdfDocument(data);
* // Gets the first page of the document
* let page: PdfPage = document.getPage(0);
* // Access the PDF form
* let form: PdfForm = document.form;
* // Create a new signature field
* let field: PdfSignatureField = new PdfSignatureField(page, 'Signature', {x: 10, y: 10, width: 100, height: 50});
* // Create a new signature using PFX data and private key
* const sign: PdfSignature = PdfSignature.create({ cryptographicStandard: CryptographicStandard.cms, digestAlgorithm: DigestAlgorithm.sha256 }, certData, password);
* // Sets the signature to the field
* field.setSignature(sign);
* // Gets the certificate information of the signature
* const certificateInfo: PdfCertificateInformation = sign.getCertificateInformation();
* // Add the field into PDF form
* form.add(field);
* // Save the document
* document.save('output.pdf');
* // Destroy the document
* document.destroy();
* ```
*
* @returns {PdfCertificateInformation} - The certificate information.
*/
PdfSignature.prototype.getCertificateInformation = function () {
return this._certificateInfo;
};
/**
* Gets the options for configuring a digital signature in a PDF document.
*
* ```typescript
* // Load the document
* let document: PdfDocument = new PdfDocument(data);
* // Gets the first page of the document
* let page: PdfPage = document.getPage(0);
* // Access the PDF form
* let form: PdfForm = document.form;
* // Gets the signature field
* let field: PdfSignatureField = form.fieldAt(0) as PdfSignatureField;
* // Gets the PDF signature
* let signature: PdfSignature = field.getSignature();
* // Gets the signature options
* let options: PdfSignatureOptions = signature.getSignatureOptions();
* // Gets the cryptographic standard of the signature
* let cryptographicStandard: CryptographicStandard = options.cryptographicStandard;
* // Destroy the document
* document.destroy();
* ```
*
* @returns {PdfSignatureOptions} The options for configuring a digital signature in a PDF document.
*/
PdfSignature.prototype.getSignatureOptions = function () {
var options = {
cryptographicStandard: this._cryptographicStandard,
digestAlgorithm: this._digestAlgorithm,
contactInfo: this._contactInfo,
reason: this._reason,
locationInfo: this._locationInfo,
certify: this._certify,
documentPermissions: this._documentPermissions,
signedName: this._signedName,
isLocked: this._isLocked
};
return options;
};
PdfSignature.replaceEmptySignature = function (inputPdfData, signatureName, signedData, algorithm, publicCertificates, arg6, arg7) {
if (!(inputPdfData instanceof Uint8Array) || inputPdfData.length === 0 &&
!(signedData instanceof Uint8Array) || signedData.length === 0) {
throw new Error('Invalid Uint8Array: Data is either not a Uint8Array or is empty.');
}
if (typeof signatureName !== 'string' && signatureName !== '') {
throw new Error('Signature field name is required');
}
var _externalChain = [];
var options;
if (arg6 && typeof arg6 !== 'string') {
options = arg6;
}
else {
options = arg7;
}
if (publicCertificates && Array.isArray(publicCertificates)) {
for (var _i = 0, publicCertificates_1 = publicCertificates; _i < publicCertificates_1.length; _i++) {
var data = publicCertificates_1[_i];
var publicCertificatesData = data;
if (publicCertificatesData && publicCertificatesData.length > 0) {
_externalChain.push(new _PdfX509CertificateParser()._readCertificate(publicCertificatesData));
}
}
}
if (!Array.isArray(_externalChain) || _externalChain.length === 0) {
throw new Error('Invalid certificate chain: Expected a non-empty array of Certificate.');
}
var document;
var encodeSignature;
if (options) {
document = new PdfDocument(inputPdfData, options.password);
encodeSignature = typeof options.skipSignatureEncoding === 'undefined' ||
options.skipSignatureEncoding === null ||
options.skipSignatureEncoding === false ? true : false;
}
else {
document = new PdfDocument(inputPdfData);
encodeSignature = true;
}
try {
var form = document.form;
var field = void 0;
for (var i = 0; i < form.count; i++) {
if (form.fieldAt(i).name === signatureName) {
field = form.fieldAt(i);
break;
}
}
if (!field) {
throw new Error('Signature field name not found.');
}
var signatureDict = field._dictionary;
if (signatureDict && signatureDict.has('V')) {
signatureDict = signatureDict.get('V');
var byteRange = signatureDict.getArray('ByteRange');
if (byteRange.length >= 4) {
var buf1 = inputPdfData.subarray(0, byteRange[1]);
var buf2 = inputPdfData.subarray(byteRange[2]);
var combined = new Uint8Array(buf1.length + buf2.length);
combined.set(buf1, 0);
combined.set(buf2, buf1.length);
var signedContent = void 0;
if (encodeSignature) {
var hashAlgorithm = '';
var externalSignature = void 0;
var crlBytes = void 0;
var ocspByte = void 0;
var chain = void 0;
if (_externalChain && _externalChain.length > 0) {
hashAlgorithm = DigestAlgorithm[algorithm];
var pks = new _PdfSignaturePrivateKey(hashAlgorithm);
externalSignature = pks;
chain = _externalChain;
}
var pkcs7 = new _PdfCryptographicMessageSyntaxSigner(null, chain, hashAlgorithm, false);
var hash = pkcs7._getDigestAlgorithm()._digest(combined, hashAlgorithm);
pkcs7._setSignedData(signedData, null, externalSignature._getEncryptionAlgorithm());
var subFilter = {
'adbe.pkcs7.detached': CryptographicStandard.cms,
'ETSI.CAdES.detached': CryptographicStandard.cades
};
var cryptographicStandard = CryptographicStandard.cms;
if (signatureDict.has('SubFilter')) {
var filter = signatureDict.get('SubFilter');
var kind = filter.name ? subFilter[filter.name] : undefined;
if (kind === CryptographicStandard.cades) {
cryptographicStandard = CryptographicStandard.cades;
}
}
signedContent = pkcs7._sign(hash, null, ocspByte, crlBytes, cryptographicStandard, hashAlgorithm);
}
var spaceAvailable = (byteRange[2] - byteRange[1]) - 2;
if ((spaceAvailable & 1) !== 0) {
throw new Error('Allocated space was not enough');
}
spaceAvailable = Math.floor(spaceAvailable / 2);
if (spaceAvailable < signedContent.length) {
throw new Error('Signature content space is not enough for signed bytes');
}
var hexEncodedSignature = _bytesToHex(signedContent);
var signatureStartPos = byteRange[1];
inputPdfData[signatureStartPos] = '<'.charCodeAt(0) & 0xff;
for (var i = 0; i < hexEncodedSignature.length; i++) {
inputPdfData[signatureStartPos + 1 + i] = hexEncodedSignature.charCodeAt(i) & 0xff;
}
var signatureEndPos = signatureStartPos + 1 + hexEncodedSignature.length;
var paddingLength = byteRange[2] - signatureEndPos - 1;
if (paddingLength > 0) {
inputPdfData.fill('0'.charCodeAt(0) & 0xff, signatureEndPos, signatureEndPos + paddingLength);
}
inputPdfData[byteRange[2] - 1] = '>'.charCodeAt(0) & 0xff;
}
}
if (arg6 && typeof arg6 === 'string') {
Save.save(arg6, new Blob([inputPdfData], { type: 'application/pdf' }));
}
else {
return inputPdfData;
}
}
catch (error) {
throw new Error("Signing failed: " + error.message);
}
finally {
document.destroy();
}
};
/**
* Applies provided signature options to this signature instance.
*
* @private
* @param {PdfSignatureOptions} [options] Options for signature configuration.
* @returns {void} nothing.
*/
PdfSignature.prototype._applySignatureOptions = function (options) {
if (options) {
if (typeof options.cryptographicStandard !== 'undefined' && options.cryptographicStandard !== null) {
this._cryptographicStandard = options.cryptographicStandard;
}
if (typeof options.digestAlgorithm !== 'undefined' && options.digestAlgorithm !== null) {
this._digestAlgorithm = options.digestAlgorithm;
}
if (_isNullOrUndefined(options.contactInfo)) {
this._contactInfo = options.contactInfo;
}
if (_isNullOrUndefined(options.reason)) {
this._reason = options.reason;
}
if (_isNullOrUndefined(options.locationInfo)) {
this._locationInfo = options.locationInfo;
}
if (typeof options.documentPermissions !== 'undefined' && options.documentPermissions !== null) {
this._documentPermissions = options.documentPermissions;
}
if (_isNullOrUndefined(options.signedName)) {
this._signedName = options.signedName;
}
if (typeof options.certify === 'boolean') {
this._certify = options.certify;
}
if (typeof options.isLocked === 'boolean') {
this._isLocked = options.isLocked;
}
}
};
/**
* Initializes internal state from an existing signature dictionary and field.
*
* @private
* @param {_PdfDictionary} dictionary The signature dictionary object.
* @param {PdfSignatureField} field The signature field associated with the dictionary.
* @returns {void} nothing.
*/
PdfSignature.prototype._initializeInternals = function (dictionary, field) {
this._crossReference = field._crossReference;
this._signed = true;
this._signatureField = field;
var subFilter = {
'adbe.pkcs7.detached': CryptographicStandard.cms,
'ETSI.CAdES.detached': CryptographicStandard.cades
};
this._signatureDictionary = new _PdfSignatureDictionary(dictionary, this);
if (dictionary.has('SubFilter')) {
var filter = dictionary.get('SubFilter');
var kind = filter.name ? subFilter[filter.name] : undefined;
if (kind === CryptographicStandard.cades) {
this._cryptographicStandard = CryptographicStandard.cades;
}
}
if (dictionary.has('Contents')) {
this._digestAlgorithm = this._signatureDictionary._parseDigestAlgorithm();
if (this._signatureDictionary._certificate) {
var certificate = this._signatureDictionary._certificate;
if (certificate) {
this._certificate = certificate;
this._certificateInfo = {
issuerName: certificate._issuerName,
serialNumber: certificate._serialNumber,
subjectName: certificate._subjectName,
validFrom: certificate._validFrom,
validTo: certificate._validTo,
version: certificate._version
};
}
}
}
this._signedDate = this._signatureDictionary._parseSignedDate();
this._signedName = this._signatureDictionary._parseDirect('Name');
this._reason = this._signatureDictionary._parseDirect('Reason');
this._locationInfo = this._signatureDictionary._parseDirect('Location');
this._contactInfo = this._signatureDictionary._parseDirect('ContactInfo');
if (dictionary.has('ByteRange')) {
var arr = dictionary.get('ByteRange'); // eslint-disable-line
var actualRange_1 = this._toNumberArray(arr);
if (actualRange_1 && actualRange_1.length > 0) {
var hasPermission = false;
var catalog = this._crossReference._document._catalog._catalogDictionary;
if (catalog && catalog.has('Perms')) {
var permission = catalog.get('Perms');
if (permission && permission.has('DocMDP')) {
var docPermission = permission.get('DocMDP');
if (docPermission && docPermission.has('ByteRange')) {
var byteRange = docPermission.get('ByteRange'); // eslint-disable-line
var range = this._toNumberArray(byteRange);
if (range && actualRange_1 &&
range.length === actualRange_1.length &&
range.every(function (v, i) { return v === actualRange_1[i]; })) {
hasPermission = true;
}
}
}
}
if (hasPermission && dictionary.has('Reference')) {
var primitive = dictionary.get('Reference');
if (primitive && Array.isArray(primitive)) {
primitive = primitive[0];
}
if (primitive && primitive.has('TransformParams')) {
var transformParam = primitive.get('TransformParams');
if (transformParam && transformParam.has('P')) {
this._documentPermissions = transformParam.get('P');
}
}
}
}
}
if (field._dictionary && field._dictionary.has('Lock')) {
var lock = field._dictionary.get('Lock');
if (lock) {
this._isLocked = true;
}
}
else if (field._dictionary && field._dictionary.has('Kids') && this._crossReference) {
var reference = field._dictionary.get('Kids');
var dictionary_1 = this._crossReference._cacheMap.get(reference[0]);
if (dictionary_1 && dictionary_1.has('Lock')) {
var lock = dictionary_1.get('Lock');
if (lock) {
this._isLocked = true;
}
}
}
if (!this._certify && this._crossReference._document._isLoaded && this._crossReference._document._catalog) {
this._certify = this._checkCertificated(dictionary.objId);
}
};
/**
* Converts an array-like object into a number array if possible.
*
* @private
* @param {any} arr The array-like input to convert.
* @returns {number[]} The converted number array or `undefined` when conversion is not possible.
*/
PdfSignature.prototype._toNumberArray = function (arr) {
if (!arr) {
return undefined;
}
if (Array.isArray(arr)) {
var values = arr.map(function (v) { return (typeof v === 'number' ? v : Number(v)); });
return values.every(function (n) { return Number.isFinite(n); }) ? values : undefined;
}
return undefined;
};
/**
* Checks whether the provided object id corresponds to a certificated signature in the document.
*
* @private
* @param {any} objId Object identifier to check.
* @returns {boolean} True when the object id refers to a certificated signature.
*/
PdfSignature.prototype._checkCertificated = function (objId) {
var certificatedSignature = false;
if (this._crossReference && this._crossReference._document) {
var document_1 = this._crossReference._document;
if (document_1._catalog && document_1._catalog._catalogDictionary && document_1._catalog._catalogDictionary.has('Perms')) {
var perms = document_1._catalog._catalogDictionary.get('Perms');
if (perms && perms.has('DocMDP')) {
var documentPermissions = perms.get('DocMDP');
if (documentPermissions && documentPermissions.objId && objId && documentPermissions.objId === objId) {
certificatedSignature = true;
}
}
}
}
return certificatedSignature;
};
/**
* Ensures catalog permissions are updated when beginning a save operation for certified signatures.
*
* @private
* @returns {void} nothing.
*/
PdfSignature.prototype._catalogBeginSave = function () {
if (this._certify) {
var document_2 = this._signatureField._crossReference._document;
var permission = document_2._catalog._catalogDictionary.get('Perms');
if (typeof permission === 'undefined' || permission === null) {
permission = new _PdfDictionary(this._crossReference);
permission.update('DocMDP', this._reference);
permission._updated = true;
document_2._catalog._catalogDictionary.update('Perms', permission);
document_2._catalog._catalogDictionary._updated = true;
}
else if (!permission.has('DocMDP')) {
var ref = this._crossReference._getNextReference();
this._signatureField._crossReference._cacheMap.set(ref, this._signatureDictionary._dictionary);
permission.set('DocMDP', ref);
permission._updated = true;
}
}
};
/**
* Adds a lock dictionary to the signature field to lock form fields when signing.
*
* @private
* @returns {void} nothing.
*/
PdfSignature.prototype._lockSignature = function () {
var lockDictionary = new _PdfDictionary();
lockDictionary.update('Type', _PdfName.get('SigFieldLock'));
lockDictionary.update('Action', _PdfName.get('All'));
lockDictionary.update('P', PdfCertificationFlags.forbidChanges);
if (this._signatureField && this._signatureField._crossReference) {
var ref = this._signatureField._crossReference._getNextReference();
this._signatureField._crossReference._cacheMap.set(ref, lockDictionary);
this._signatureField._dictionary.update('Lock', ref);
}
};
/**
* Creates a new signature dictionary for the provided document and signature instance.
*
* @private
* @param {PdfDocument} document The PDF document the dictionary belongs to.
* @param {PdfSignature} signature The signature instance to back the dictionary.
* @returns {_PdfSignatureDictionary} The created signature dictionary.
*/
PdfSignature.prototype._createDictionary = function (document, signature) {
return new _PdfSignatureDictionary(document, signature);
};
return PdfSignature;
}());
export { PdfSignature };