/* * Based on PDFSign v1.0.0 * https://github.com/Communication-Systems-Group/pdfsign.js * * Copyright 2015, Thomas Bocek, University of Zurich * * Licensed under the MIT license: * http://www.opensource.org/licenses/MIT */ import { ObjectIdentifier, UTCTime, OctetString } from 'asn1js'; import { ContentInfo, SignedData, Attribute, SignerInfo, IssuerAndSerialNumber, SignedAndUnsignedAttributes, EncapsulatedContentInfo, getCrypto } from 'pkijs'; import { parseCertificate, parsePrivateKey, } from './signingUtilities'; //keep this import to include the patched version of pdfjs library import {PDFJS} from "../lib/pdfjs.parser.js"; function createXrefTable(xrefEntries) { xrefEntries = sortOnKeys(xrefEntries); var retVal ='xref\n'; var last = -2; for(var i in xrefEntries) { i = parseInt(i); if(typeof xrefEntries[i].offset === 'undefined') { continue; } retVal += calcFlow(i, last, xrefEntries); var offset = xrefEntries[i].offset; retVal += pad10(offset)+' '+pad5(xrefEntries[i].gen)+' '+(xrefEntries[i].free?'f':'n')+' \n'; last = i; } return retVal; } function calcFlow(i, last, xrefEntries) { if(last + 1 === i) {return '';} var count = 1; while(typeof xrefEntries[(i+count)] !== 'undefined' && typeof xrefEntries[(i+count)].offset !== 'undefined') {count ++;} return i + ' '+count+'\n'; } function createTrailer(topDict, startxref, sha256Hex, size, prev) { var retVal ='trailer <<\n'; retVal +=' /Size '+(size)+'\n'; var refRoot = topDict.getRaw('Root'); if(typeof refRoot !== 'undefined') { retVal +=' /Root '+refRoot.num+' '+refRoot.gen+' R\n'; } var refInfo = topDict.getRaw('Info'); if(typeof refInfo !== 'undefined') { retVal +=' /Info '+refInfo.num+' '+refInfo.gen+' R\n'; } retVal +=' /ID [<'+sha256Hex.substring(0,32)+'><'+sha256Hex.substring(32,64)+'>]\n'; if(typeof prev !== 'undefined' ) { retVal +=' /Prev '+prev+'\n'; } retVal +='>>\n'; retVal +='startxref\n'; retVal +=startxref + '\n'; retVal +='%%EOF\n'; return retVal; } function createXrefTableAppend(xrefEntries) { xrefEntries = sortOnKeys(xrefEntries); var retVal ='xref\n'; var last = -2; for(var i in xrefEntries) { i = parseInt(i); if(typeof xrefEntries[i].offset === 'undefined') { continue; } retVal += calcFlow(i, last, xrefEntries); var offset = xrefEntries[i].offset; retVal += pad10(offset)+' '+pad5(xrefEntries[i].gen)+' '+(xrefEntries[i].free?'f':'n')+' \n'; last = i; } return retVal; } //http://stackoverflow.com/questions/10946880/sort-a-dictionary-or-whatever-key-value-data-structure-in-js-on-word-number-ke function sortOnKeys(dict) { var sorted = []; for(var key in dict) { sorted[sorted.length] = key; } sorted.sort(); var tempDict = {}; for(var i = 0; i < sorted.length; i++) { tempDict[sorted[i]] = dict[sorted[i]]; } return tempDict; } function removeFromArray(array, from, to) { var cutlen = to - from; var buf = new Uint8Array(array.length - cutlen); for (var i = 0; i < from; i++) { buf[i] = array[i]; } for (var i = to, len = array.length; i < len; i++) { buf[i-cutlen] = array[i]; } return buf; } function findXrefBlocks(xrefBlocks) { var num = xrefBlocks.length / 2; var retVal = []; for (var i=0;i<num;i++) { retVal.push({start: xrefBlocks[i], end: xrefBlocks[i+num]}); } return retVal; } function convertUint8ArrayToBinaryString(u8Array) { var i, len = u8Array.length, b_str = ""; for (i=0; i<len; i++) { b_str += String.fromCharCode(u8Array[i]); } return b_str; } function arrayObjectIndexOf(array, start, end, orig) { for(var i = 0, len = array.length; i < len; i++) { if ((array[i].start === start) && (array[i].end === end) && (array[i].orig === orig)) { return i; } } return -1; } function pad10(num) { var s = "000000000" + num; return s.substr(s.length-10); } function pad5(num) { var s = "0000" + num; return s.substr(s.length-5); } function pad2(num) { var s = "0" + num; return s.substr(s.length-2); } function findRootEntry(xref) { var rootNr = xref.root.objId.substring(0, xref.root.objId.length - 1); return xref.entries[rootNr]; } function findSuccessorEntry(xrefEntries, current) { //find it first var currentOffset = current.offset; var currentMin = Number.MAX_SAFE_INTEGER; var currentMinIndex = -1; for(var i in xrefEntries) { if(xrefEntries[i].offset > currentOffset) { if(xrefEntries[i].offset < currentMin) { currentMin = xrefEntries[i].offset; currentMinIndex = i; } } } if(currentMinIndex === -1) { return current; } return xrefEntries[currentMinIndex]; } function updateArray(array, pos, str) { var upd = stringToUint8Array(str); for (var i = 0, len=upd.length; i < len; i++) { array[i+pos] = upd[i]; } return array; } function copyToEnd(array, from, to) { var buf = new Uint8Array(array.length + (to - from)); for (var i = 0, len=array.length; i < len; i++) { buf[i] = array[i]; } for (var i = 0, len=(to - from); i < len; i++) { buf[array.length + i] = array[from + i]; } return buf; } function insertIntoArray(array, pos, str) { var ins = stringToUint8Array(str); var buf = new Uint8Array(array.length + ins.length); for (var i = 0; i < pos; i++) { buf[i] = array[i]; } for (var i = 0; i < ins.length; i++) { buf[pos+i] = ins[i]; } for (var i = pos; i < array.length; i++) { buf[ins.length+i] = array[i]; } return buf; } function stringToUint8Array(str) { var buf = new Uint8Array(str.length); for (var i=0, strLen=str.length; i<strLen; i++) { buf[i] = str.charCodeAt(i); } return buf; } function uint8ArrayToString(buf, from, to) { if(typeof from !== 'undefined' && typeof to !== 'undefined') { var s = ''; for (var i=from; i<to; i++) { s = s + String.fromCharCode(buf[i]); } return s; } return String.fromCharCode.apply(null, buf); } function findFreeXrefNr(xrefEntries, used) { used = typeof used !== 'undefined' ? used : []; var inc = used.length; for (var i=1;i<xrefEntries.length;i++) { var index = used.indexOf(i); var entry = xrefEntries[""+i]; if(index === -1 && (typeof entry === 'undefined' || entry.free)) { return i; } if(index !== -1) { inc--; } } return xrefEntries.length + inc; } function find(uint8, needle, start, limit) { start = typeof start !== 'undefined' ? start : 0; limit = typeof limit !== 'undefined' ? limit : Number.MAX_SAFE_INTEGER; var search = stringToUint8Array(needle); var match = 0; for(var i=start;i<uint8.length && i<limit;i++) { if(uint8[i] === search[match]) { match++; } else { match = 0; if(uint8[i] === search[match]) { match++; } } if(match === search.length) { return (i + 1) - match; } } return -1; } function findBackwards(uint8, needle, start, limit) { start = typeof start !== 'undefined' ? start : uint8.length; limit = typeof limit !== 'undefined' ? limit : Number.MAX_SAFE_INTEGER; var search = stringToUint8Array(needle); var match = search.length - 1; for(var i=start;i>=0 && i<limit;i--) { if(uint8[i] === search[match]) { match--; } else { match = search.length - 1; if(uint8[i] === search[match]) { match--; } } if(match === 0) { return i - 1; } } return -1; } function strHex(s) { var a = ""; for( var i=0; i<s.length; i++ ) { a = a + pad2(s.charCodeAt(i).toString(16)); } return a; } async function sha256(array) { const cryptoLib = getCrypto(); const digestTmpBuf = await cryptoLib.digest({ name: "SHA-256" }, array); const digestTmpArray = new Uint8Array(digestTmpBuf); const digestTmpStr = uint8ArrayToString(digestTmpArray); const sha256Hex = strHex(digestTmpStr); return sha256Hex; } function isSigInRoot(pdf) { if (typeof pdf.acroForm === 'undefined') { return false; } return pdf.acroForm.get('SigFlags') === 3; } function updateXrefOffset(xref, offset, offsetDelta) { for(var i in xref.entries) { if(xref.entries[i].offset >= offset) { xref.entries[i].offset += offsetDelta; } } for(var i in xref.xrefBlocks) { if(xref.xrefBlocks[i] >= offset) { xref.xrefBlocks[i] += offsetDelta; } } } function updateXrefBlocks(xrefBlocks, offset, offsetDelta) { for(var i in xrefBlocks) { if(xrefBlocks[i].start >= offset) { xrefBlocks[i].start += offsetDelta; } if(xrefBlocks[i].end >= offset) { xrefBlocks[i].end += offsetDelta; } } } function updateOffset(pos, offset, offsetDelta) { if(pos >= offset) { return pos + offsetDelta; } return pos; } function round256(x) { return (Math.ceil(x/256)*256) - 1; } /** * (D:YYYYMMDDHHmmSSOHH'mm) * e.g. (D:20151210164400+01'00') * where: * YYYY shall be the year * MM shall be the month (01–12) * DD shall be the day (01–31) * HH shall be the hour (00–23) * mm shall be the minute (00–59) * SS shall be the second (00–59) * O shall be the relationship of local time to Universal Time (UT), and shall be denoted by one of the characters PLUS SIGN (U+002B) (+), HYPHEN-MINUS (U+002D) (-), or LATIN CAPITAL LETTER Z (U+005A) (Z) (see below) * HH followed by APOSTROPHE (U+0027) (') shall be the absolute value of the offset from UT in hours (00–23) * mm shall be the absolute value of the offset from UT in minutes (00–59) */ function now(date) { //date = typeof date !== 'undefined' ? date : new Date(); var yyyy = date.getFullYear().toString(); var MM = pad2(date.getMonth() + 1); var dd = pad2(date.getDate()); var hh = pad2(date.getHours()); var mm = pad2(date.getMinutes()); var ss = pad2(date.getSeconds()); return yyyy + MM + dd+ hh + mm + ss + createOffset(date); } function createOffset(date) { var sign = (date.getTimezoneOffset() > 0) ? "-" : "+"; var offset = Math.abs(date.getTimezoneOffset()); var hours = pad2(Math.floor(offset / 60)); var minutes = pad2(offset % 60); return sign + hours + "'" + minutes; } async function newSig(pdf, root, rootSuccessor, date, signingCert, certificateChain, privateKey) { try { // {annotEntry} is the ref to the annot widget. If we enlarge the array, make sure all the offsets // after the modification will be updated -> xref table and startxref var annotEntry = findFreeXrefNr(pdf.xref.entries); // we'll store all the modifications we make, as we need to adjust the offset in the PDF var offsetForm = find(pdf.stream.bytes, '<<', root.offset, rootSuccessor.offset) + 2; //first we need to find the root element and add the following: // // /AcroForm<</Fields[{annotEntry} 0 R] /SigFlags 3>> // var appendAcroForm = '/AcroForm<</Fields[' + annotEntry + ' 0 R] /SigFlags 3>>'; //before we insert the acroform, we find the right place for annotentry //we need to add Annots [x y R] to the /Type /Page section. We can do that by searching /Contents[ var pages = pdf.catalog.catDict.get('Pages'); //get first page, we have hidden sig, so don't bother var ref = pages.get('Kids')[0]; var xref = pdf.xref.fetch(ref); var offsetContentEnd = xref.get('#Contents_offset'); //we now search backwards, this is safe as we don't expect user content here var offsetContent = findBackwards(pdf.stream.bytes, '/Contents', offsetContentEnd); var appendAnnots = '/Annots[' + annotEntry + ' 0 R]\n '; //now insert string into stream var array = insertIntoArray(pdf.stream.bytes, offsetForm, appendAcroForm); //recalculate the offsets in the xref table, only update those that are affected updateXrefOffset(pdf.xref, offsetForm, appendAcroForm.length); offsetContent = updateOffset(offsetContent, offsetForm, appendAcroForm.length); var array = insertIntoArray(array, offsetContent, appendAnnots); updateXrefOffset(pdf.xref, offsetContent, appendAnnots.length); offsetContent = -1; //not needed anymore, don't update when offset changes //Then add to the next free object (annotEntry) //add right before the xref table or stream //if its a table, place element before the xref table // // sigEntry is the ref to the signature content. Next we need the signature object var sigEntry = findFreeXrefNr(pdf.xref.entries, [annotEntry]); // // {annotEntry} 0 obj // <</F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]/FT/Sig/DR<<>>/T(signature)/V Y 0 R>> // endobj // var append = annotEntry + ' 0 obj\n<</F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]/FT/Sig/DR<<>>/T(signature' + annotEntry + ')/V ' + sigEntry + ' 0 R>>\nendobj\n\n'; // we want the offset just before the last xref table or entry var blocks = findXrefBlocks(pdf.xref.xrefBlocks); var offsetAnnot = blocks[0].start; array = insertIntoArray(array, offsetAnnot, append); //no updateXrefOffset, as the next entry will be following // // {sigEntry} 0 obj // <</Contents <0481801e6d931d561563fb254e27c846e08325570847ed63d6f9e35 ... b2c8788a5> // /Type/Sig/SubFilter/adbe.pkcs7.detached/Location(Ghent)/M(D:20120928104114+02'00') // /ByteRange [A B C D]/Filter/Adobe.PPKLite/Reason(Test)/ContactInfo()>> // endobj // //the next entry goes below the above var offsetSig = offsetAnnot + append.length; // Both {annotEntry} and {sigEntry} objects need to be added to the last xref table. The byte range needs // to be adjusted. Since the signature will always be in a gap, use first an empty sig // to check the size, add ~25% size, then calculate the signature and place in the empty // space. var start = sigEntry + ' 0 obj\n<</Contents <'; var dummy = await sign_pki(signingCert, certificateChain, privateKey, stringToUint8Array('A'), date); //TODO: Adobe thinks its important to have the right size, no idea why this is the case var crypto = new Array(round256(dummy.length * 2)).join('0'); var middle = '>\n/Type/Sig/SubFilter/adbe.pkcs7.detached/Location()/M(D:' + now(date) + '\')\n/ByteRange '; var byteRange = '[0000000000 0000000000 0000000000 0000000000]'; var end = '/Filter/Adobe.PPKLite/Reason()/ContactInfo()>>\nendobj\n\n'; //all together var append2 = start + crypto + middle + byteRange + end; var offsetByteRange = start.length + crypto.length + middle.length; array = insertIntoArray(array, offsetSig, append2); updateXrefOffset(pdf.xref, offsetAnnot, append2.length + append.length); //find the xref tables, remove them and also the EOF, as we'll write a new table var xrefBlocks = findXrefBlocks(pdf.xref.xrefBlocks); for (var i in xrefBlocks) { var oldSize = array.length; array = removeFromArray(array, xrefBlocks[i].start, xrefBlocks[i].end); var length = array.length - oldSize; updateXrefOffset(pdf.xref, xrefBlocks[i].start, length); //check for %%EOF and remove it as well var offsetEOF = find(array, '%%EOF', xrefBlocks[i].start, xrefBlocks[i].start + 20); if (offsetEOF > 0) { var lengthEOF = '%%EOF'.length; array = removeFromArray(array, offsetEOF, offsetEOF + lengthEOF); updateXrefOffset(pdf.xref, offsetEOF, -lengthEOF); updateXrefBlocks(xrefBlocks, offsetEOF, -lengthEOF); offsetAnnot = updateOffset(offsetAnnot, offsetEOF, -lengthEOF); offsetSig = updateOffset(offsetSig, offsetEOF, -lengthEOF); } updateXrefBlocks(xrefBlocks, xrefBlocks[i].start, length); offsetAnnot = updateOffset(offsetAnnot, xrefBlocks[i].start, length); offsetSig = updateOffset(offsetSig, xrefBlocks[i].start, length); } var sha256Hex = await sha256(array); //add the new entries to the xref pdf.xref.entries[annotEntry] = {offset: offsetAnnot, gen: 0, free: false}; pdf.xref.entries[sigEntry] = {offset: offsetSig, gen: 0, free: false}; var xrefTable = createXrefTable(pdf.xref.entries); //also empty entries count as in the PDF spec, page 720 (example) xrefTable += createTrailer(pdf.xref.topDict, array.length, sha256Hex, pdf.xref.entries.length); array = insertIntoArray(array, array.length, xrefTable); //since we consolidate, no prev! [adjust /Prev -> rawparsing + offset] var from1 = 0; var to1 = offsetSig + start.length; var from2 = to1 + crypto.length; var to2 = (array.length - from2) - 1; var byteRange = '[' + pad10(from1) + ' ' + pad10(to1 - 1) + ' ' + pad10(from2 + 1) + ' ' + pad10(to2) + ']'; array = updateArray(array, (offsetSig + offsetByteRange), byteRange); var data = removeFromArray(array, to1 - 1, from2 + 1); var crypto2 = await sign_pki(signingCert, certificateChain, privateKey, data.buffer, date); array = updateArray(array, to1, crypto2); return array; } catch (err) { throw new Error('Error creating new signature in PDF: ' + err); } } async function appendSig(pdf, root, rootSuccessor, date, signingCert, certificateChain, privateKey) { try { //copy root and the entry with contents to the end var startRoot = pdf.stream.bytes.length + 1; var array = copyToEnd(pdf.stream.bytes, root.offset - 1, rootSuccessor.offset); //since we signed the first one, we know how the pdf has to look like: var offsetAcroForm = find(array, '/AcroForm<</Fields', startRoot); var endOffsetAcroForm = find(array, ']', offsetAcroForm); var annotEntry = findFreeXrefNr(pdf.xref.entries); var sigEntry = findFreeXrefNr(pdf.xref.entries, [annotEntry]); var appendAnnot = ' ' + annotEntry + ' 0 R'; array = insertIntoArray(array, endOffsetAcroForm, appendAnnot); //we need to add Annots [x y R] to the /Type /Page section. We can do that by searching /Annots var pages = pdf.catalog.catDict.get('Pages'); //get first page, we have hidden sig, so don't bother var contentRef = pages.get('Kids')[0]; var xref = pdf.xref.fetch(contentRef); var offsetAnnotEnd = xref.get('#Annots_offset'); //we now search ], this is safe as we signed it previously var endOffsetAnnot = find(array, ']', offsetAnnotEnd); var xrefEntry = pdf.xref.getEntry(contentRef.num); var xrefEntrySuccosser = findSuccessorEntry(pdf.xref.entries, xrefEntry); var offsetAnnotRelative = endOffsetAnnot - xrefEntrySuccosser.offset; var startContent = array.length; array = copyToEnd(array, xrefEntry.offset, xrefEntrySuccosser.offset); array = insertIntoArray(array, array.length + offsetAnnotRelative, appendAnnot); var startAnnot = array.length; var append = annotEntry + ' 0 obj\n<</F 132/Type/Annot/Subtype/Widget/Rect[0 0 0 0]/FT/Sig/DR<<>>/T(signature' + annotEntry + ')/V ' + sigEntry + ' 0 R>>\nendobj\n\n'; array = insertIntoArray(array, startAnnot, append); var startSig = array.length; var start = sigEntry + ' 0 obj\n<</Contents <'; var dummy = await sign_pki(signingCert, certificateChain, privateKey, stringToUint8Array('A'), date); //TODO: Adobe thinks its important to have the right size, no idea why this is the case var crypto = new Array(round256(dummy.length * 2)).join('0'); var middle = '>\n/Type/Sig/SubFilter/adbe.pkcs7.detached/Location()/M(D:' + now(date) + '\')\n/ByteRange '; var byteRange = '[0000000000 0000000000 0000000000 0000000000]'; var end = '/Filter/Adobe.PPKLite/Reason()/ContactInfo()>>\nendobj\n\n'; //all together var append2 = start + crypto + middle + byteRange + end; array = insertIntoArray(array, startSig, append2); var sha256Hex = await sha256(array); var prev = pdf.xref.xrefBlocks[0]; var startxref = array.length; var xrefEntries = []; xrefEntries[0] = {offset: 0, gen: 65535, free: true}; xrefEntries[pdf.xref.topDict.getRaw('Root').num] = {offset: startRoot, gen: 0, free: false}; xrefEntries[contentRef.num] = {offset: startContent, gen: 0, free: false}; xrefEntries[annotEntry] = {offset: startAnnot, gen: 0, free: false}; xrefEntries[sigEntry] = {offset: startSig, gen: 0, free: false}; var xrefTable = createXrefTableAppend(xrefEntries); xrefTable += createTrailer(pdf.xref.topDict, startxref, sha256Hex, xrefEntries.length, prev); array = insertIntoArray(array, array.length, xrefTable); var from1 = 0; var to1 = startSig + start.length; var from2 = to1 + crypto.length; var to2 = (array.length - from2) - 1; var byteRange = '[' + pad10(from1) + ' ' + pad10(to1 - 1) + ' ' + pad10(from2 + 1) + ' ' + pad10(to2) + ']'; array = updateArray(array, from2 + middle.length, byteRange); //now sign from1-to1 / from2-to2 and update byterange var data = removeFromArray(array, to1 - 1, from2 + 1); var crypto2 = await sign_pki(signingCert, certificateChain, privateKey, data.buffer, date); array = updateArray(array, to1, crypto2); return array; } catch (err) { throw new Error('Error appending signature in PDF: ' + err); } } function loadPdf(pdfArray) { try { var pdf = new pdfjsCoreDocument.PDFDocument(false, pdfArray, ''); pdf.parseStartXRef(); pdf.parse(); return pdf; } catch(err) { throw new Error('Error parsing PDF: ' + err); } } //data must be Uint8Array async function sign_pki(signingCert, certificateChain, privateKey, data, date) { const crypto = getCrypto(); //date = typeof date !== 'undefined' ? date : new Date(); const hashAlg = "SHA-256"; const digest = await crypto.digest({ name: hashAlg }, data); const signedAttr = []; signedAttr.push(new Attribute({ type: "1.2.840.113549.1.9.3", values: [ new ObjectIdentifier({ value: "1.2.840.113549.1.7.1" }) ] })); // contentType signedAttr.push(new Attribute({ type: "1.2.840.113549.1.9.4", values: [ new OctetString({ valueHex: digest }) ] })); // messageDigest signedAttr.push(new Attribute({ type: "1.2.840.113549.1.9.5", values: [ new UTCTime({ valueDate: date }) ] })); // signingTime const cmsSignedSimpl = new SignedData({ version: 1, encapContentInfo: new EncapsulatedContentInfo({ eContentType: "1.2.840.113549.1.7.1" // "data" content type }), signerInfos: [ new SignerInfo({ version: 1, sid: new IssuerAndSerialNumber({ issuer: signingCert.issuer, serialNumber: signingCert.serialNumber }), signedAttrs: new SignedAndUnsignedAttributes({ type: 0, attributes: signedAttr }) }) ], certificates: certificateChain }); const signatureBuffer = await cmsSignedSimpl.sign(privateKey, 0, hashAlg, data.buffer); const cmsSignedSchema = cmsSignedSimpl.toSchema(true); const cmsContentSimp = new ContentInfo({ contentType: "1.2.840.113549.1.7.2", content: cmsSignedSchema }); const _cmsSignedSchema = cmsContentSimp.toSchema(); //region Make length of some elements in "indefinite form" _cmsSignedSchema.lenBlock.isIndefiniteForm = true; const block1 = _cmsSignedSchema.valueBlock.value[1]; block1.lenBlock.isIndefiniteForm = true; const block2 = block1.valueBlock.value[0]; block2.lenBlock.isIndefiniteForm = true; //endregion let cmsSignedBuffer = _cmsSignedSchema.toBER(false); const cmsSignedArray = new Uint8Array(cmsSignedBuffer); const cmsSignedString = uint8ArrayToString(cmsSignedArray); const hex = strHex(cmsSignedString); return hex; } async function signPdfObjects (pdfRaw, signingCert, certificateChain, privateKey, date) { date = ((typeof date !== 'undefined') && (date !== null)) ? date : new Date(); if (pdfRaw instanceof Buffer) { pdfRaw = new Uint8Array(pdfRaw); } else if(pdfRaw instanceof ArrayBuffer) { pdfRaw = new Uint8Array(pdfRaw); } var pdf = loadPdf(pdfRaw); var root = findRootEntry(pdf.xref); var rootSuccessor = findSuccessorEntry(pdf.xref.entries, root); if(!isSigInRoot(pdf)) { return await newSig(pdf, root, rootSuccessor, date, signingCert, certificateChain, privateKey); } else { return await appendSig(pdf, root, rootSuccessor, date, signingCert, certificateChain, privateKey); } } export function signPdf(pdfRaw, signingCert, certificateChain, privateKey) { const signingCertObj = parseCertificate(signingCert); const certificateChainObj = []; certificateChainObj[0] = parseCertificate(signingCert); for (let i = 0; i < certificateChain.length; i++) { certificateChainObj[i + 1] = parseCertificate(certificateChain[i]) } return parsePrivateKey(privateKey).then(privateKeyDecoded => { return signPdfObjects(pdfRaw, signingCertObj, certificateChainObj, privateKeyDecoded); }); }