From 1d13857fa6837e4a434c35eba33a327c3457f888 Mon Sep 17 00:00:00 2001 From: microshine Date: Thu, 18 Jan 2024 11:50:16 +0100 Subject: [PATCH 1/5] fix: incorrect wrongStructure calculation --- packages/core/src/structure/DocumentUpdate.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/structure/DocumentUpdate.ts b/packages/core/src/structure/DocumentUpdate.ts index 65b69a1..a22cf15 100644 --- a/packages/core/src/structure/DocumentUpdate.ts +++ b/packages/core/src/structure/DocumentUpdate.ts @@ -114,12 +114,17 @@ export class PDFDocumentUpdate { // Check file structure if (!this.document.wrongStructure) { + // check if update sections are in the right order if (this.previous && this.previous.startXref > this.startXref) { + // if not, then the file structure is wrong this.document.wrongStructure = true; } else { + // check if xref table/stream is before any of objects in the update section for (const obj of this.getObjects()) { - if (obj.offset > this.startXref) { + if (obj.offset > this.xref.view.byteOffset) { + // if not, then the file structure is wrong this.document.wrongStructure = true; + break; } } } From d53cfcc28171918a65cc63f0e16941c7fc7a35b1 Mon Sep 17 00:00:00 2001 From: microshine Date: Thu, 18 Jan 2024 11:52:55 +0100 Subject: [PATCH 2/5] fix: EOF handling in PDFDocumentUpdate --- packages/core/src/structure/DocumentUpdate.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core/src/structure/DocumentUpdate.ts b/packages/core/src/structure/DocumentUpdate.ts index a22cf15..3c55371 100644 --- a/packages/core/src/structure/DocumentUpdate.ts +++ b/packages/core/src/structure/DocumentUpdate.ts @@ -109,8 +109,16 @@ export class PDFDocumentUpdate { const eofReader = new ViewReader(reader.view.buffer); eofReader.position = reader.position + reader.view.byteOffset; eofReader.read("%%EOF"); + // read eol + let eofOffset = eofReader.position + 5; + if (eofReader.view[eofOffset] === 0x0D) { + eofOffset++; + } + if (eofReader.view[eofOffset] === 0x0A) { + eofOffset++; + } - this.view = eofReader.view.subarray(0, eofReader.position + 5); + this.view = eofReader.view.subarray(0, eofOffset); // Check file structure if (!this.document.wrongStructure) { From b53ce8dfe83617a115063874f2cf4c2c559c3de6 Mon Sep 17 00:00:00 2001 From: microshine Date: Fri, 19 Jan 2024 18:41:24 +0100 Subject: [PATCH 3/5] feat: formatting issue in SignatureBoxGroup class --- packages/core/src/structure/DocumentUpdate.ts | 11 ++- packages/doc/src/forms/SignatureBox.Group.ts | 83 +++++++++++++------ 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/packages/core/src/structure/DocumentUpdate.ts b/packages/core/src/structure/DocumentUpdate.ts index 3c55371..9124ac7 100644 --- a/packages/core/src/structure/DocumentUpdate.ts +++ b/packages/core/src/structure/DocumentUpdate.ts @@ -6,6 +6,13 @@ import { ViewWriter } from "../ViewWriter"; export class PDFDocumentUpdate { + /** + * @internal + * %%EOF end-of-line characters. + * This parameter is more suitable for test cases and should not be used in production. + */ + public static EOF_EOL = "\n"; + public static readonly DEFAULT_VIEW = new Uint8Array(); public view = PDFDocumentUpdate.DEFAULT_VIEW; @@ -198,8 +205,8 @@ export class PDFDocumentUpdate { new objects.PDFNumeric(offset).writePDF(writer); writer.writeLine(); - writer.writeString("%%EOF\n"); - this.view = writer.toUint8Array().subarray(startOffset, writer.length - 2); // exclude \n + writer.writeString(`%%EOF${PDFDocumentUpdate.EOF_EOL}`); + this.view = writer.toUint8Array(); } public previous: PDFDocumentUpdate | null = null; diff --git a/packages/doc/src/forms/SignatureBox.Group.ts b/packages/doc/src/forms/SignatureBox.Group.ts index 6a775ad..8875fa5 100644 --- a/packages/doc/src/forms/SignatureBox.Group.ts +++ b/packages/doc/src/forms/SignatureBox.Group.ts @@ -8,6 +8,7 @@ import { FormComponentGroup } from "./FormComponent.Group"; import { type SignatureBox } from "./SignatureBox"; import * as types from "./SignatureBox.Types"; +const ERR_INCORRECT_BYTE_RANGE = "The range of bytes points to an incorrect data"; export class SignatureBoxGroup extends FormComponentGroup { @@ -74,7 +75,7 @@ export class SignatureBoxGroup extends FormComponentGroup lastBlockLength) { - const documentView = signatureValue.documentUpdate.document.view; - const view = documentView.slice(begin2 + lastBlockLength, begin2 + byteRangeLastBlockLength); - const reader = new core.ViewReader(view); - const spaces = reader.read((octet) => !core.CharSet.whiteSpaceChars.includes(octet)); - check4 = spaces.length === view.length; + + if (!(check1 && check2 && check3)) { + const index = !check1 ? 0 : !check2 ? 1 : 2; + throw new Error(`${ERR_INCORRECT_BYTE_RANGE}. ByteRange[${index}] points to an incorrect data.`); } - if (!(check1 && check2 && check3 && check4)) { - throw new Error("The range of bytes points to an incorrect data."); + + const lastOffset = byteRange3 + byteRange4; + + const updateViewReader = new core.ViewReader(updateView); + updateViewReader.end(); + updateViewReader.backward = true; + const updateEofIndex = updateViewReader.findIndex("%%EOF"); + + const documentViewReader = new core.ViewReader(doc.view); + documentViewReader.position = lastOffset - 1; + documentViewReader.backward = true; + let eofIndex = documentViewReader.findIndex("%%EOF"); + + // Check that Update section ends with %%EOF marker with EOL characters + if (eofIndex === -1 || updateEofIndex !== eofIndex) { + throw new Error(`${ERR_INCORRECT_BYTE_RANGE}. The %%EOF marker is not found.`); + } + eofIndex += 1; // index points to F, but we need to point to the next character + if (eofIndex !== lastOffset) { + if (lastOffset - eofIndex > 3) { // Acrobat allows up to 3 bytes after %%EOF marker + throw new Error(`${ERR_INCORRECT_BYTE_RANGE}. Too many bytes after %%EOF marker.`); + } + const eolText = Convert.ToBinary(doc.view.subarray(eofIndex, lastOffset)); + if (/^(?:\r|\n)*$/.test(eolText) === false) { + throw new Error(`${ERR_INCORRECT_BYTE_RANGE}. EOL contains invalid characters.`); + } + } + + // Check if the Update section is the last section in the document + // the byte range points includes all bytes to the end of the file + let lastUpdate = doc.update; + if (lastUpdate.view.length === 0 && lastUpdate.previous) { + lastUpdate = lastUpdate.previous; + } + if (lastUpdate === signatureValue.documentUpdate && lastOffset !== doc.view.length) { + throw new Error(`${ERR_INCORRECT_BYTE_RANGE}. Document contains extra bytes after signed data.`); } } catch (e) { const state: types.FormattingState = { @@ -697,13 +737,6 @@ export class SignatureBoxGroup extends FormComponentGroup Date: Fri, 19 Jan 2024 18:42:04 +0100 Subject: [PATCH 4/5] test: add tests for 'formatting' state --- packages/doc/src/Page.spec.ts | 142 ++++---- .../doc/src/forms/SignatureBox.Group.spec.ts | 342 ++++++++++++++++++ 2 files changed, 419 insertions(+), 65 deletions(-) create mode 100644 packages/doc/src/forms/SignatureBox.Group.spec.ts diff --git a/packages/doc/src/Page.spec.ts b/packages/doc/src/Page.spec.ts index 9b121e9..62af949 100644 --- a/packages/doc/src/Page.spec.ts +++ b/packages/doc/src/Page.spec.ts @@ -592,7 +592,7 @@ context("Page", () => { }); const page2 = doc.pages.create(); - const img3 = page2.addSignatureBox({ + page2.addSignatureBox({ left: "5mm", top: "5mm", width: "3cm", @@ -600,7 +600,7 @@ context("Page", () => { groupName: "stepan", }); - const img4 = page2.addSignatureBox({ + page2.addSignatureBox({ left: "40mm", top: "5mm", width: "3cm", @@ -634,72 +634,84 @@ context("Page", () => { page.addSignatureBox({ groupName: "box1", }); + page.addSignatureBox({ + groupName: "box2", + }); await doc.save(); - const box = doc.getComponentByName("box1"); - assert.ok(box instanceof SignatureBoxGroup); - - await box.sign({ - dictionaryUpdate: async (dict) => { - dict.subFilter = "ETSI.CAdES.detached"; - dict.Reason.get().text = "Описание причины"; - dict.Location.get().text = "56.632N 47.928E"; - }, - containerCreate: async (data) => { - - //#region Create certificate - const alg: RsaHashedKeyGenParams = { - name: "RSASSA-PKCS1-v1_5", - hash: "SHA-256", - publicExponent: new Uint8Array([1, 0, 1]), - modulusLength: 2048, - }; - const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]); - const cert = await x509.X509CertificateGenerator.createSelfSigned({ - serialNumber: "0102030405", - notBefore: new Date("2021-06-29"), - notAfter: new Date("2022-06-29"), - name: "CN=Test", - keys, - signingAlgorithm: alg, - extensions: [ - new x509.KeyUsagesExtension( - x509.KeyUsageFlags.digitalSignature | - x509.KeyUsageFlags.nonRepudiation | - x509.KeyUsageFlags.keyCertSign - ), - new x509.BasicConstraintsExtension(false), - await x509.AuthorityKeyIdentifierExtension.create(keys.publicKey!, false, crypto), - await x509.SubjectKeyIdentifierExtension.create(keys.publicKey!, false, crypto), - new x509.ExtendedKeyUsageExtension([ - "1.3.6.1.4.1.311.10.3.12", // documentSigning - "1.2.840.113583.1.1.5", // pdfAuthenticDocumentsTrust - ]), - ] - }, crypto); - //#endregion - - //#region Create CMS - const messageDigest = await crypto.subtle.digest(alg.hash, data); - const signedData = new cms.CMSSignedData(); - const signer = signedData.createSigner(cert, { - digestAlgorithm: alg.hash, - signedAttributes: [ - new cms.ContentTypeAttribute(cms.CMSContentType.data), - new cms.SigningTimeAttribute(new Date()), - new cms.MessageDigestAttribute(messageDigest), - ] - }); - - signedData.certificates.push(cert); - - await signedData.sign(keys.privateKey!, signer); - //#endregion - - return signedData.toBER(); - } - }); + const box1 = doc.getComponentByName("box1"); + assert.ok(box1 instanceof SignatureBoxGroup); + const box2 = doc.getComponentByName("box2"); + assert.ok(box2 instanceof SignatureBoxGroup); + + const boxes = [ + box1, + // box2, + ]; + for (const box of boxes) { + const cn = `CN=${box.name}`; + await box.sign({ + dictionaryUpdate: async (dict) => { + dict.subFilter = "adbe.pkcs7.detached"; + dict.Reason.get().text = "Описание причины"; + dict.Location.get().text = "56.632N 47.928E"; + }, + containerCreate: async (data) => { + + //#region Create certificate + const alg: RsaHashedKeyGenParams = { + name: "RSASSA-PKCS1-v1_5", + hash: "SHA-256", + publicExponent: new Uint8Array([1, 0, 1]), + modulusLength: 2048, + }; + const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]); + const cert = await x509.X509CertificateGenerator.createSelfSigned({ + serialNumber: "0102030405", + notBefore: new Date("2021-06-29"), + notAfter: new Date("2022-06-29"), + name: cn, + keys, + signingAlgorithm: alg, + extensions: [ + new x509.KeyUsagesExtension( + x509.KeyUsageFlags.digitalSignature | + x509.KeyUsageFlags.nonRepudiation | + x509.KeyUsageFlags.keyCertSign + ), + new x509.BasicConstraintsExtension(false), + await x509.AuthorityKeyIdentifierExtension.create(keys.publicKey!, false, crypto), + await x509.SubjectKeyIdentifierExtension.create(keys.publicKey!, false, crypto), + new x509.ExtendedKeyUsageExtension([ + "1.3.6.1.4.1.311.10.3.12", // documentSigning + "1.2.840.113583.1.1.5", // pdfAuthenticDocumentsTrust + ]), + ] + }, crypto); + //#endregion + + //#region Create CMS + const messageDigest = await crypto.subtle.digest(alg.hash, data); + const signedData = new cms.CMSSignedData(); + const signer = signedData.createSigner(cert, { + digestAlgorithm: alg.hash, + signedAttributes: [ + new cms.ContentTypeAttribute(cms.CMSContentType.data), + new cms.SigningTimeAttribute(new Date()), + new cms.MessageDigestAttribute(messageDigest), + ] + }); + + signedData.certificates.push(cert); + + await signedData.sign(keys.privateKey!, signer); + //#endregion + + return signedData.toBER(); + } + }); + } const pdf = await doc.save(); writeFile(pdf); diff --git a/packages/doc/src/forms/SignatureBox.Group.spec.ts b/packages/doc/src/forms/SignatureBox.Group.spec.ts new file mode 100644 index 0000000..904abe6 --- /dev/null +++ b/packages/doc/src/forms/SignatureBox.Group.spec.ts @@ -0,0 +1,342 @@ +import * as assert from "node:assert"; +import * as path from "node:path"; +import * as fs from "node:fs"; +import { Crypto } from "@peculiar/webcrypto"; +import * as x509 from "@peculiar/x509"; +import * as core from "@peculiarventures/pdf-core"; +import { CMSContentType, CMSSignedData, ContentTypeAttribute, MessageDigestAttribute, PDFDocument, SignatureBoxGroup, SigningTimeAttribute } from "@peculiarventures/pdf-doc"; +import { BufferSourceConverter, Convert } from "pvtsutils"; +import * as pkijs from "pkijs"; + +const crypto = new Crypto() as globalThis.Crypto; +pkijs.setEngine("PDF crypto", crypto, new core.PDFCryptoEngine({ crypto: crypto, subtle: crypto.subtle })); + +const WRITE_FILES = false; + +export function writeFile(data: BufferSource, name = "tmp"): void { + if (!WRITE_FILES) { + return; + } + const filePath = path.resolve(__dirname, `../../../../${name}.pdf`); + fs.writeFileSync(filePath, Buffer.from(BufferSourceConverter.toArrayBuffer(data)), { flag: "w+" }); + console.log(`File saved to ${filePath}`); +} + +async function createEmptyDocument() { + const doc = await PDFDocument.create({ + useXrefTable: true, + disableCompressedStreams: true, + }); + assert.strictEqual(doc.pages.length, 0); + const page = doc.pages.create(); + + return { doc, page }; +} + +async function createSelfSignedCertificate() { + const alg: RsaHashedKeyGenParams = { + name: "RSASSA-PKCS1-v1_5", + hash: "SHA-256", + publicExponent: new Uint8Array([1, 0, 1]), + modulusLength: 2048, + }; + const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]); + const cert = await x509.X509CertificateGenerator.createSelfSigned({ + serialNumber: "0102030405", + notBefore: new Date("2021-06-29"), + notAfter: new Date("2022-06-29"), + name: "CN=Test", + keys, + signingAlgorithm: alg, + extensions: [ + new x509.KeyUsagesExtension( + x509.KeyUsageFlags.digitalSignature | + x509.KeyUsageFlags.nonRepudiation | + x509.KeyUsageFlags.keyCertSign + ), + new x509.BasicConstraintsExtension(false), + await x509.AuthorityKeyIdentifierExtension.create(keys.publicKey!, false, crypto), + await x509.SubjectKeyIdentifierExtension.create(keys.publicKey!, false, crypto), + new x509.ExtendedKeyUsageExtension([ + "1.3.6.1.4.1.311.10.3.12", // documentSigning + "1.2.840.113583.1.1.5", // pdfAuthenticDocumentsTrust + ]), + ] + }, crypto); + + return { keys, cert }; +} + +function signHandler(hash: string, keys: CryptoKeyPair, cert: x509.X509Certificate) { + return async (data: Uint8Array) => { + const messageDigest = await crypto.subtle.digest(hash, data); + const signedData = new CMSSignedData(); + const signer = signedData.createSigner(cert, { + digestAlgorithm: hash, + signedAttributes: [ + new ContentTypeAttribute(CMSContentType.data), + new SigningTimeAttribute(new Date()), + new MessageDigestAttribute(messageDigest), + ] + }); + + signedData.certificates.push(cert); + + await signedData.sign(keys.privateKey!, signer); + + return signedData.toBER(); + }; +} + +function checkSignatureValue(signatureValue: core.SignatureDictionary, doc: PDFDocument, eol: string, isLastSignature: boolean) { + const byteRange = signatureValue.ByteRange.get(); + assert.strictEqual(byteRange.get(0, core.PDFNumeric).value, 0); + assert.strictEqual(byteRange.get(1, core.PDFNumeric).value, signatureValue.Contents.view.byteOffset); + assert.strictEqual(byteRange.get(2, core.PDFNumeric).value, signatureValue.Contents.view.byteOffset + signatureValue.Contents.view.byteLength); + const lastByte = byteRange.get(3, core.PDFNumeric).value + byteRange.get(2, core.PDFNumeric).value; + + if (isLastSignature) { + assert.strictEqual(lastByte, doc.target.view.byteLength); + } else { + const endView = doc.target.view.subarray(lastByte - eol.length - 5, lastByte); + assert.strictEqual(Convert.ToBinary(endView), `%%EOF${eol}`); + } +} + +context("SignatureBoxGroup", () => { + context("States", () => { + context("formatting", () => { + context("%%EOF with different EOL", () => { + after(() => { + core.PDFDocumentUpdate.EOF_EOL = "\n"; // restore default value + }); + + [ + "", // empty + "\n", // LF + "\r\n", // CRLF + "\n\n", // LFLF (Acrobat Reader supports this EOL) + "\n\n\n", // LFLFLF (Acrobat Reader supports this EOL) + ].forEach((eol) => { + it(`should create valid PDF with EOL = ${JSON.stringify(eol)}`, async () => { + core.PDFDocumentUpdate.EOF_EOL = eol; + const emptyDoc = await createEmptyDocument(); + let doc = emptyDoc.doc; + const page = emptyDoc.page; + const fileName = JSON.stringify(eol) + .replace(/"/g, "") // remove quotes + .replace(/\\/g, "") // remove backslash + || "empty"; + + // Add signature box + page.addSignatureBox({ + groupName: "box1", + }); + page.addSignatureBox({ + groupName: "box2", + }); + + // Save document + await doc.save(); + + const { keys, cert } = await createSelfSignedCertificate(); + const box1 = doc.getComponentByName("box1"); + assert.ok(box1 instanceof SignatureBoxGroup); + await box1.sign({ + dictionaryUpdate: async (dict) => { + dict.subFilter = "adbe.pkcs7.detached"; + dict.Reason.get().text = "Описание причины"; + dict.Location.get().text = "56.632N 47.928E"; + }, + containerCreate: signHandler("SHA-256", keys, cert), + }); + let raw = await doc.save(); + writeFile(raw, `SignatureBox.Group.spec.${fileName}.1.pdf`); + // reopen document, in this case UpdateSection will have different EOL after parsing + // Parser reads %%EOF[\d][\n] + doc = await PDFDocument.load(raw); + + let verify = await doc.verify(); + let formattingSate = verify.items[0].states[0]; + assert.strictEqual(formattingSate.type, "valid"); + + const box2 = doc.getComponentByName("box2"); + assert.ok(box2 instanceof SignatureBoxGroup); + await box2.sign({ + dictionaryUpdate: async (dict) => { + dict.subFilter = "adbe.pkcs7.detached"; + dict.Reason.get().text = "Описание причины 2"; + dict.Location.get().text = "56.632N 47.928E"; + }, + containerCreate: signHandler("SHA-256", keys, cert), + }); + raw = await doc.save(); + writeFile(raw, `SignatureBox.Group.spec.${fileName}.2.pdf`); + + verify = await doc.verify(); + formattingSate = verify.items[0].states[0]; + assert.strictEqual(formattingSate.type, "valid"); + formattingSate = verify.items[1].states[0]; + assert.strictEqual(formattingSate.type, "valid"); + + // check byteRange positions + const signatureValue1 = box1.getSignatureValue(); + checkSignatureValue(signatureValue1, doc, eol, false); + + const signatureValue2 = box2.getSignatureValue(); + checkSignatureValue(signatureValue2, doc, eol, true); + }); + }); + + it("should return invalid state if there are 'Too many bytes after %%EOF marker'", async () => { + core.PDFDocumentUpdate.EOF_EOL = "\n\n\n\n"; + const emptyDoc = await createEmptyDocument(); + const doc = emptyDoc.doc; + const page = emptyDoc.page; + + // Add signature box + page.addSignatureBox({ + groupName: "box1", + }); + + // Save document + await doc.save(); + + const { keys, cert } = await createSelfSignedCertificate(); + const box1 = doc.getComponentByName("box1"); + assert.ok(box1 instanceof SignatureBoxGroup); + await box1.sign({ + dictionaryUpdate: async (dict) => { + dict.subFilter = "adbe.pkcs7.detached"; + dict.Reason.get().text = "Описание причины"; + dict.Location.get().text = "56.632N 47.928E"; + }, + containerCreate: signHandler("SHA-256", keys, cert), + }); + const raw = await doc.save(); + writeFile(raw, "SignatureBox.Group.spec.invalid.pdf"); + + const verify = await doc.verify(); + const formattingSate = verify.items[0].states[0]; + assert.strictEqual(formattingSate.type, "invalid"); + assert.strictEqual(formattingSate.data.error.message, "The range of bytes points to an incorrect data. Too many bytes after %%EOF marker."); + }); + + it("should return invalid state if there are 'EOL contains invalid characters'", async () => { + core.PDFDocumentUpdate.EOF_EOL = "\n \n"; + const emptyDoc = await createEmptyDocument(); + const doc = emptyDoc.doc; + const page = emptyDoc.page; + + // Add signature box + page.addSignatureBox({ + groupName: "box1", + }); + + // Save document + await doc.save(); + + const { keys, cert } = await createSelfSignedCertificate(); + const box1 = doc.getComponentByName("box1"); + assert.ok(box1 instanceof SignatureBoxGroup); + await box1.sign({ + dictionaryUpdate: async (dict) => { + dict.subFilter = "adbe.pkcs7.detached"; + dict.Reason.get().text = "Описание причины"; + dict.Location.get().text = "56.632N 47.928E"; + }, + containerCreate: signHandler("SHA-256", keys, cert), + }); + const raw = await doc.save(); + writeFile(raw, "SignatureBox.Group.spec.invalid.pdf"); + + const verify = await doc.verify(); + const formattingSate = verify.items[0].states[0]; + assert.strictEqual(formattingSate.type, "invalid"); + assert.strictEqual(formattingSate.data.error.message, "The range of bytes points to an incorrect data. EOL contains invalid characters."); + }); + + it("should return invalid state if there are 'Document contains extra bytes after signed data'", async () => { + core.PDFDocumentUpdate.EOF_EOL = "\n"; + const emptyDoc = await createEmptyDocument(); + let doc = emptyDoc.doc; + const page = emptyDoc.page; + + // Add signature box + page.addSignatureBox({ + groupName: "box1", + }); + + // Save document + await doc.save(); + + const { keys, cert } = await createSelfSignedCertificate(); + const box1 = doc.getComponentByName("box1"); + assert.ok(box1 instanceof SignatureBoxGroup); + await box1.sign({ + dictionaryUpdate: async (dict) => { + dict.subFilter = "adbe.pkcs7.detached"; + dict.Reason.get().text = "Описание причины"; + dict.Location.get().text = "56.632N 47.928E"; + }, + containerCreate: signHandler("SHA-256", keys, cert), + }); + let raw = await doc.save(); + raw = BufferSourceConverter.concat(raw, Buffer.from("\n")); + writeFile(raw, "SignatureBox.Group.spec.invalid.pdf"); + + doc = await PDFDocument.load(raw); + const verify = await doc.verify(); + const formattingSate = verify.items[0].states[0]; + assert.strictEqual(formattingSate.type, "invalid"); + assert.strictEqual(formattingSate.data.error.message, "The range of bytes points to an incorrect data. Document contains extra bytes after signed data."); + }); + }); + context("ByteRange", () => { + [0, 1, 2].forEach((index) => { + it(`should return invalid state if ByteRange[${index}] points to incorrect data`, async () => { + const { doc, page } = await createEmptyDocument(); + page.addSignatureBox({ + groupName: "box1", + }); + await doc.save(); + + const { keys, cert } = await createSelfSignedCertificate(); + const box1 = doc.getComponentByName("box1", SignatureBoxGroup); + await box1.sign({ + dictionaryUpdate: async (dict) => { + dict.subFilter = "adbe.pkcs7.detached"; + dict.Reason.get().text = "Описание причины"; + dict.Location.get().text = "56.632N 47.928E"; + }, + containerCreate: (data) => { + const byteRange = box1.getSignatureValue().ByteRange.get(); + const num = byteRange.get(index, core.PDFNumeric); + let numValue = num.value; + if (index > 1) { + numValue -= 1; + } else { + numValue += 1; + } + const str = numValue.toString(); + for (let i = 0; i < num.view.length; i++) { + num.view[i] = str.charCodeAt(i) || 0; + } + + return signHandler("SHA-256", keys, cert)(data); + }, + }); + const raw = await doc.save(); + writeFile(raw, "SignatureBox.Group.spec.invalid.pdf"); + + const doc2 = await PDFDocument.load(raw); + const verify = await doc2.verify(); + const formattingSate = verify.items[0].states[0]; + assert.strictEqual(formattingSate.type, "invalid"); + assert.strictEqual(formattingSate.data.error.message, `The range of bytes points to an incorrect data. ByteRange[${index}] points to an incorrect data.`); + }); + }); + }); + }); + }); +}); From 2f94ad606a8ded90c654aefe5659b1003350698b Mon Sep 17 00:00:00 2001 From: microshine Date: Fri, 19 Jan 2024 18:42:53 +0100 Subject: [PATCH 5/5] style: fix lint error --- packages/core/src/structure/DocumentUpdate.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/src/structure/DocumentUpdate.ts b/packages/core/src/structure/DocumentUpdate.ts index 9124ac7..e7caa77 100644 --- a/packages/core/src/structure/DocumentUpdate.ts +++ b/packages/core/src/structure/DocumentUpdate.ts @@ -169,8 +169,6 @@ export class PDFDocumentUpdate { await this.encrypt(); } - const startOffset = writer.length; - // check if the last char in the document is not a new line // then add a new line before the update section if (this.document.view.length && this.document.view[this.document.view.length - 1] !== 0x0A) {