UNPKG

@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
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 };