Newer
Older
/*
* 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 {
ContentInfo,
SignedData,
Attribute,
SignerInfo,
IssuerAndSerialNumber,
SignedAndUnsignedAttributes,
EncapsulatedContentInfo,
getCrypto
} 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);
let retVal = 'xref\n';
let last = -2;
for (let i in xrefEntries) {
i = parseInt(i);
if (typeof xrefEntries[i].offset === 'undefined') { continue; }
retVal += calcFlow(i, last, xrefEntries);
const 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 ''; }
let 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) {
let retVal = 'trailer <<\n';
retVal += ' /Size ' + size + '\n';
const refRoot = topDict.getRaw('Root');
if (typeof refRoot !== 'undefined') {
retVal += ' /Root ' + refRoot.num + ' ' + refRoot.gen + ' R\n';
}
const 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);
let retVal = 'xref\n';
let last = -2;
for (let i in xrefEntries) {
i = parseInt(i);
if (typeof xrefEntries[i].offset === 'undefined') { continue; }
retVal += calcFlow(i, last, xrefEntries);
const 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) {
const sorted = [];
for (const key in dict) {
sorted[sorted.length] = key;
}
sorted.sort();
const tempDict = {};
for (let i = 0; i < sorted.length; i++) {
tempDict[sorted[i]] = dict[sorted[i]];
}
}
function removeFromArray(array, from, to) {
const cutlen = to - from;
const buf = new Uint8Array(array.length - cutlen);
for (let i = 0; i < from; i++) {
buf[i] = array[i];
}
for (let i = to, len = array.length; i < len; i++) {
buf[i - cutlen] = array[i];
}
return buf;
function findXrefBlocks(xrefBlocks) {
const num = xrefBlocks.length / 2;
const retVal = [];
for (let i = 0; i < num; i++) {
retVal.push({start: xrefBlocks[i],
end: xrefBlocks[i + num]});
}
return retVal;
}
function convertUint8ArrayToBinaryString(u8Array) {
let i, len = u8Array.length, bStr = "";
for (i = 0; i < len; i++) {
bStr += String.fromCharCode(u8Array[i]);
}
return bStr;
}
function arrayObjectIndexOf(array, start, end, orig) {
for (let i = 0, len = array.length; i < len; i++) {
if ((array[i].start === start) && (array[i].end === end) && (array[i].orig === orig)) {
return i;
const s = "000000000" + num;
return s.substr(s.length - 10);
const s = "0000" + num;
return s.substr(s.length - 5);
const s = "0" + num;
return s.substr(s.length - 2);
}
function findRootEntry(xref) {
const rootNr = xref.root.objId.substring(0, xref.root.objId.length - 1);
return xref.entries[rootNr];
function findSuccessorEntry(xrefEntries, current) {
//find it first
const currentOffset = current.offset;
let currentMin = Number.MAX_SAFE_INTEGER;
let currentMinIndex = -1;
for (const 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) {
const upd = stringToUint8Array(str);
for (let i = 0, len = upd.length; i < len; i++) {
array[i + pos] = upd[i];
}
return array;
}
function copyToEnd(array, from, to) {
const buf = new Uint8Array(array.length + (to - from));
for (let i = 0, len = array.length; i < len; i++) {
buf[i] = array[i];
}
for (let i = 0, len = to - from; i < len; i++) {
buf[array.length + i] = array[from + i];
}
return buf;
}
function insertIntoArray(array, pos, str) {
const ins = stringToUint8Array(str);
const buf = new Uint8Array(array.length + ins.length);
for (let i = 0; i < pos; i++) {
buf[i] = array[i];
}
for (let i = 0; i < ins.length; i++) {
buf[pos + i] = ins[i];
}
for (let i = pos; i < array.length; i++) {
buf[ins.length + i] = array[i];
}
return buf;
}
function stringToUint8Array(str) {
const buf = new Uint8Array(str.length);
for (let 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') {
let s = '';
for (let 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 : [];
let inc = used.length;
for (let i = 1; i < xrefEntries.length; i++) {
const index = used.indexOf(i);
const entry = xrefEntries["" + i];
if (index === -1 && (typeof entry === 'undefined' || entry.free)) {
return i;
}
if (index !== -1) {
inc--;
}
function find(uint8, needle, start, limit) {
start = typeof start !== 'undefined' ? start : 0;
limit = typeof limit !== 'undefined' ? limit : Number.MAX_SAFE_INTEGER;
const search = stringToUint8Array(needle);
let match = 0;
for (let 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;
function findBackwards(uint8, needle, start, limit) {
start = typeof start !== 'undefined' ? start : uint8.length;
limit = typeof limit !== 'undefined' ? limit : Number.MAX_SAFE_INTEGER;
const search = stringToUint8Array(needle);
let match = search.length - 1;
for (let i = start; i >= 0 && i < limit; i--) {
if (uint8[i] === search[match]) {
match--;
} else {
match = search.length - 1;
if (uint8[i] === search[match]) {
match--;
}
function strHex(s) {
let a = "";
for (let i = 0; i < s.length; i++) {
a = a + pad2(s.charCodeAt(i).toString(16));
}
return a;
}
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 (const i in xref.entries) {
if (xref.entries[i].offset >= offset) {
xref.entries[i].offset += offsetDelta;
}
for (const i in xref.xrefBlocks) {
if (xref.xrefBlocks[i] >= offset) {
xref.xrefBlocks[i] += offsetDelta;
}
function updateXrefBlocks(xrefBlocks, offset, offsetDelta) {
for (const 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;
}
/**
* (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();
const yyyy = date.getFullYear().toString();
const MM = pad2(date.getMonth() + 1);
const dd = pad2(date.getDate());
const hh = pad2(date.getHours());
const mm = pad2(date.getMinutes());
const ss = pad2(date.getSeconds());
return yyyy + MM + dd + hh + mm + ss + createOffset(date);
}
function createOffset(date) {
const sign = date.getTimezoneOffset() > 0 ? "-" : "+";
const offset = Math.abs(date.getTimezoneOffset());
const hours = pad2(Math.floor(offset / 60));
const minutes = pad2(offset % 60);
return sign + hours + "'" + minutes;
}
async function newSig(pdf, root, rootSuccessor, date, signingCert, certificateChain, privateKey) {
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
// {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
const annotEntry = findFreeXrefNr(pdf.xref.entries);
// we'll store all the modifications we make, as we need to adjust the offset in the PDF
const 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>>
//
const 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[
const pages = pdf.catalog.catDict.get('Pages');
//get first page, we have hidden sig, so don't bother
const ref = pages.get('Kids')[0];
const xref = pdf.xref.fetch(ref);
const offsetContentEnd = xref.get('#Contents_offset');
//we now search backwards, this is safe as we don't expect user content here
let offsetContent = findBackwards(pdf.stream.bytes, '/Contents', offsetContentEnd);
const appendAnnots = '/Annots[' + annotEntry + ' 0 R]\n ';
//now insert string into stream
let 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);
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
const 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
//
const 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
const blocks = findXrefBlocks(pdf.xref.xrefBlocks);
let 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
let 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.
const start = sigEntry + ' 0 obj\n<</Contents <';
const dummy = await signPki(signingCert, certificateChain, privateKey, stringToUint8Array('A'), date);
//TODO: Adobe thinks its important to have the right size, no idea why this is the case
const crypto = new Array(round256(dummy.length * 2)).join('0');
const middle = '>\n/Type/Sig/SubFilter/adbe.pkcs7.detached/Location()/M(D:' + now(date) + '\')\n/ByteRange ';
let byteRange = '[0000000000 0000000000 0000000000 0000000000]';
const end = '/Filter/Adobe.PPKLite/Reason()/ContactInfo()>>\nendobj\n\n';
//all together
const append2 = start + crypto + middle + byteRange + end;
const 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
const xrefBlocks = findXrefBlocks(pdf.xref.xrefBlocks);
for (const i in xrefBlocks) {
const oldSize = array.length;
array = removeFromArray(array, xrefBlocks[i].start, xrefBlocks[i].end);
const length = array.length - oldSize;
updateXrefOffset(pdf.xref, xrefBlocks[i].start, length);
//check for %%EOF and remove it as well
const offsetEOF = find(array, '%%EOF', xrefBlocks[i].start, xrefBlocks[i].start + 20);
if (offsetEOF > 0) {
const 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);
}
const 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};
let 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]
const from1 = 0;
const to1 = offsetSig + start.length;
const from2 = to1 + crypto.length;
const to2 = (array.length - from2) - 1;
byteRange = '[' + pad10(from1) + ' ' + pad10(to1 - 1) + ' ' + pad10(from2 + 1) + ' ' + pad10(to2) + ']';
array = updateArray(array, offsetSig + offsetByteRange, byteRange);
const data = removeFromArray(array, to1 - 1, from2 + 1);
const crypto2 = await signPki(signingCert, certificateChain, privateKey, data.buffer, date);
array = updateArray(array, to1, crypto2);
return array;
async function appendSig(pdf, root, rootSuccessor, date, signingCert, certificateChain, privateKey) {
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
//copy root and the entry with contents to the end
const startRoot = pdf.stream.bytes.length + 1;
let 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:
const offsetAcroForm = find(array, '/AcroForm<</Fields', startRoot);
const endOffsetAcroForm = find(array, ']', offsetAcroForm);
const annotEntry = findFreeXrefNr(pdf.xref.entries);
const sigEntry = findFreeXrefNr(pdf.xref.entries, [annotEntry]);
const 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
const pages = pdf.catalog.catDict.get('Pages');
//get first page, we have hidden sig, so don't bother
const contentRef = pages.get('Kids')[0];
const xref = pdf.xref.fetch(contentRef);
const offsetAnnotEnd = xref.get('#Annots_offset');
//we now search ], this is safe as we signed it previously
const endOffsetAnnot = find(array, ']', offsetAnnotEnd);
const xrefEntry = pdf.xref.getEntry(contentRef.num);
const xrefEntrySuccosser = findSuccessorEntry(pdf.xref.entries, xrefEntry);
const offsetAnnotRelative = endOffsetAnnot - xrefEntrySuccosser.offset;
const startContent = array.length;
array = copyToEnd(array, xrefEntry.offset, xrefEntrySuccosser.offset);
array = insertIntoArray(array, array.length + offsetAnnotRelative, appendAnnot);
const startAnnot = array.length;
const 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);
const startSig = array.length;
const start = sigEntry + ' 0 obj\n<</Contents <';
const dummy = await signPki(signingCert, certificateChain, privateKey, stringToUint8Array('A'), date);
//TODO: Adobe thinks its important to have the right size, no idea why this is the case
const crypto = new Array(round256(dummy.length * 2)).join('0');
const middle = '>\n/Type/Sig/SubFilter/adbe.pkcs7.detached/Location()/M(D:' + now(date) + '\')\n/ByteRange ';
let byteRange = '[0000000000 0000000000 0000000000 0000000000]';
const end = '/Filter/Adobe.PPKLite/Reason()/ContactInfo()>>\nendobj\n\n';
//all together
const append2 = start + crypto + middle + byteRange + end;
array = insertIntoArray(array, startSig, append2);
const sha256Hex = await sha256(array);
const prev = pdf.xref.xrefBlocks[0];
const startxref = array.length;
const 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};
let xrefTable = createXrefTableAppend(xrefEntries);
xrefTable += createTrailer(pdf.xref.topDict, startxref, sha256Hex, xrefEntries.length, prev);
array = insertIntoArray(array, array.length, xrefTable);
const from1 = 0;
const to1 = startSig + start.length;
const from2 = to1 + crypto.length;
const to2 = (array.length - from2) - 1;
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
const data = removeFromArray(array, to1 - 1, from2 + 1);
const crypto2 = await signPki(signingCert, certificateChain, privateKey, data.buffer, date);
array = updateArray(array, to1, crypto2);
return array;
}
function loadPdf(pdfArray) {
const pdf = new pdfjsCoreDocument.PDFDocument(false, pdfArray, '');
pdf.parseStartXRef();
pdf.parse();
return pdf;
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
async function signPki(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({
sid: new IssuerAndSerialNumber({
issuer: signingCert.issuer,
serialNumber: signingCert.serialNumber
signedAttrs: new SignedAndUnsignedAttributes({
type: 0,
attributes: signedAttr
})
})
],
certificates: certificateChain
});
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
});
//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;
const 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 : new Date();
if (pdfRaw instanceof ArrayBuffer) {
pdfRaw = new Uint8Array(pdfRaw);
}
const pdf = loadPdf(pdfRaw);
const root = findRootEntry(pdf.xref);
const 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);
});
}