From 7eaf7e88496202f90ae2d803d1d73c11fa8e6acb Mon Sep 17 00:00:00 2001 From: Uladzimir Asipchuk Date: Fri, 1 Oct 2021 18:23:59 +0300 Subject: [PATCH] Do not allow to override signer's document with only a writer wrapper. Cover PdfSigner with some tests. DEVSIX-5838 --- .../com/itextpdf/signatures/PdfSigner.java | 3 + .../SignExceptionMessageConstant.java | 1 + .../signatures/PdfSignerUnitTest.java | 210 ++++++++++++++++-- .../pdfa/sRGB Color Space Profile.icm | Bin 0 -> 3144 bytes 4 files changed, 201 insertions(+), 13 deletions(-) create mode 100644 sign/src/test/resources/com/itextpdf/signatures/pdfa/sRGB Color Space Profile.icm diff --git a/sign/src/main/java/com/itextpdf/signatures/PdfSigner.java b/sign/src/main/java/com/itextpdf/signatures/PdfSigner.java index 8e5330ad26..4e7c78639b 100644 --- a/sign/src/main/java/com/itextpdf/signatures/PdfSigner.java +++ b/sign/src/main/java/com/itextpdf/signatures/PdfSigner.java @@ -454,6 +454,9 @@ public PdfDocument getDocument() { * @param document The PdfDocument */ protected void setDocument(PdfDocument document) { + if (null == document.getReader()) { + throw new IllegalArgumentException(SignExceptionMessageConstant.DOCUMENT_MUST_HAVE_READER); + } this.document = document; } diff --git a/sign/src/main/java/com/itextpdf/signatures/exceptions/SignExceptionMessageConstant.java b/sign/src/main/java/com/itextpdf/signatures/exceptions/SignExceptionMessageConstant.java index 736cc6e61f..98824a527e 100644 --- a/sign/src/main/java/com/itextpdf/signatures/exceptions/SignExceptionMessageConstant.java +++ b/sign/src/main/java/com/itextpdf/signatures/exceptions/SignExceptionMessageConstant.java @@ -39,6 +39,7 @@ public final class SignExceptionMessageConstant { public static final String DICTIONARY_THIS_KEY_IS_NOT_A_NAME = "Dictionary key {0} is not a name."; public static final String DOCUMENT_ALREADY_PRE_CLOSED = "Document has been already pre closed."; public static final String DOCUMENT_MUST_BE_PRE_CLOSED = "Document must be preClosed."; + public static final String DOCUMENT_MUST_HAVE_READER = "Document must have reader."; public static final String FAILED_TO_GET_TSA_RESPONSE = "Failed to get TSA response from {0}."; public static final String FIELD_ALREADY_SIGNED = "Field has been already signed."; public static final String FIELD_NAMES_CANNOT_CONTAIN_A_DOT = "Field names cannot contain a dot."; diff --git a/sign/src/test/java/com/itextpdf/signatures/PdfSignerUnitTest.java b/sign/src/test/java/com/itextpdf/signatures/PdfSignerUnitTest.java index db0e63e256..c10f618208 100644 --- a/sign/src/test/java/com/itextpdf/signatures/PdfSignerUnitTest.java +++ b/sign/src/test/java/com/itextpdf/signatures/PdfSignerUnitTest.java @@ -22,6 +22,7 @@ This file is part of the iText (R) project. */ package com.itextpdf.signatures; +import com.itextpdf.commons.utils.DateTimeUtil; import com.itextpdf.forms.PdfAcroForm; import com.itextpdf.forms.PdfSigFieldLock; import com.itextpdf.forms.fields.PdfFormField; @@ -29,32 +30,53 @@ This file is part of the iText (R) project. import com.itextpdf.io.source.ByteArrayOutputStream; import com.itextpdf.kernel.geom.Rectangle; import com.itextpdf.kernel.pdf.EncryptionConstants; +import com.itextpdf.kernel.pdf.PdfAConformanceLevel; import com.itextpdf.kernel.pdf.PdfDictionary; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfName; +import com.itextpdf.kernel.pdf.PdfOutputIntent; import com.itextpdf.kernel.pdf.PdfReader; +import com.itextpdf.kernel.pdf.PdfString; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.ReaderProperties; import com.itextpdf.kernel.pdf.StampingProperties; import com.itextpdf.kernel.pdf.WriterProperties; import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation; +import com.itextpdf.pdfa.PdfADocument; +import com.itextpdf.signatures.PdfSigner.ISignatureEvent; +import com.itextpdf.signatures.exceptions.SignExceptionMessageConstant; import com.itextpdf.test.ExtendedITextTest; import com.itextpdf.test.annotations.type.UnitTest; -import org.junit.Assert; -import org.junit.Test; -import org.junit.experimental.categories.Category; import java.io.ByteArrayInputStream; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; +import java.util.Calendar; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; @Category(UnitTest.class) public class PdfSignerUnitTest extends ExtendedITextTest { + private static final byte[] OWNER = "owner".getBytes(); + private static final byte[] USER = "user".getBytes(); + + private static final String PDFA_RESOURCES = "./src/test/resources/com/itextpdf/signatures/pdfa/"; + private static final String DESTINATION_FOLDER = "./target/test/com/itextpdf/signatures/PdfSignerUnitTest/"; + + @BeforeClass + public static void before() { + createOrClearDestinationFolder(DESTINATION_FOLDER); + } + @Test public void createNewSignatureFormFieldInvisibleAnnotationTest() throws IOException { PdfSigner signer = new PdfSigner( - new PdfReader(new ByteArrayInputStream(createDocumentWithoutWidgetAnnotation()), - new ReaderProperties().setPassword("owner".getBytes())), new ByteArrayOutputStream(), new StampingProperties()); + new PdfReader(new ByteArrayInputStream(createEncryptedDocumentWithoutWidgetAnnotation()), + new ReaderProperties().setPassword(OWNER)), new ByteArrayOutputStream(), new StampingProperties()); signer.cryptoDictionary = new PdfSignature(); signer.appearance.setPageRect(new Rectangle(100, 100, 0, 0)); @@ -70,8 +92,8 @@ public void createNewSignatureFormFieldInvisibleAnnotationTest() throws IOExcept @Test public void createNewSignatureFormFieldNotInvisibleAnnotationTest() throws IOException { PdfSigner signer = new PdfSigner( - new PdfReader(new ByteArrayInputStream(createDocumentWithoutWidgetAnnotation()), - new ReaderProperties().setPassword("owner".getBytes())), new ByteArrayOutputStream(), new StampingProperties()); + new PdfReader(new ByteArrayInputStream(createEncryptedDocumentWithoutWidgetAnnotation()), + new ReaderProperties().setPassword(OWNER)), new ByteArrayOutputStream(), new StampingProperties()); signer.cryptoDictionary = new PdfSignature(); signer.appearance.setPageRect(new Rectangle(100, 100, 10, 10)); PdfSigFieldLock fieldLock = new PdfSigFieldLock(); @@ -90,13 +112,13 @@ public void createNewSignatureFormFieldNotInvisibleAnnotationTest() throws IOExc public void populateExistingSignatureFormFieldInvisibleAnnotationTest() throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); PdfDocument document = new PdfDocument(new PdfWriter(outputStream, - new WriterProperties().setStandardEncryption("user".getBytes(), "owner".getBytes(), 0, EncryptionConstants.STANDARD_ENCRYPTION_128))); + new WriterProperties().setStandardEncryption(USER, OWNER, 0, EncryptionConstants.STANDARD_ENCRYPTION_128))); document.addNewPage(); PdfWidgetAnnotation widgetAnnotation = new PdfWidgetAnnotation(new Rectangle(100, 100, 0, 0)); document.getPage(1).addAnnotation(widgetAnnotation); document.close(); PdfSigner signer = new PdfSigner( - new PdfReader(new ByteArrayInputStream(outputStream.toByteArray()), new ReaderProperties().setPassword("owner".getBytes())), + new PdfReader(new ByteArrayInputStream(outputStream.toByteArray()), new ReaderProperties().setPassword(OWNER)), new ByteArrayOutputStream(), new StampingProperties()); signer.cryptoDictionary = new PdfSignature(); signer.appearance.setPageRect(new Rectangle(100, 100, 0, 0)); @@ -118,13 +140,13 @@ public void populateExistingSignatureFormFieldInvisibleAnnotationTest() throws I public void populateExistingSignatureFormFieldNotInvisibleAnnotationTest() throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); PdfDocument document = new PdfDocument(new PdfWriter(outputStream, - new WriterProperties().setStandardEncryption("user".getBytes(), "owner".getBytes(), 0, EncryptionConstants.STANDARD_ENCRYPTION_128))); + new WriterProperties().setStandardEncryption(USER, OWNER, 0, EncryptionConstants.STANDARD_ENCRYPTION_128))); document.addNewPage(); PdfWidgetAnnotation widgetAnnotation = new PdfWidgetAnnotation(new Rectangle(100, 100, 0, 0)); document.getPage(1).addAnnotation(widgetAnnotation); document.close(); PdfSigner signer = new PdfSigner( - new PdfReader(new ByteArrayInputStream(outputStream.toByteArray()), new ReaderProperties().setPassword("owner".getBytes())), + new PdfReader(new ByteArrayInputStream(outputStream.toByteArray()), new ReaderProperties().setPassword(OWNER)), new ByteArrayOutputStream(), new StampingProperties()); signer.cryptoDictionary = new PdfSignature(); PdfSigFieldLock fieldLock = new PdfSigFieldLock(); @@ -144,12 +166,166 @@ public void populateExistingSignatureFormFieldNotInvisibleAnnotationTest() throw Assert.assertTrue(formFieldDictionary.containsKey(PdfName.AP)); } - private static byte[] createDocumentWithoutWidgetAnnotation() { + @Test + public void tempFileProvidedTest() throws IOException { + String tempFileName = "tempFile"; + PdfSigner signer = new PdfSigner( + new PdfReader(new ByteArrayInputStream(createSimpleDocument())), + new ByteArrayOutputStream(), DESTINATION_FOLDER + tempFileName, new StampingProperties()); + Assert.assertNotNull(signer.tempFile); + Assert.assertEquals(tempFileName, signer.tempFile.getName()); + Assert.assertNull(signer.temporaryOS); + } + + @Test + // TODO DEVSIX-5910 The wrapped document should be recognized as Pdf/A + public void initPdfaDocumentTest() throws IOException { + PdfSigner signer = new PdfSigner( + new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())), + new ByteArrayOutputStream(), new StampingProperties()); + Assert.assertFalse(signer.getDocument() instanceof PdfADocument); + } + + @Test + public void signingDateSetGetTest() throws IOException { + PdfSigner signer = new PdfSigner( + new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())), + new ByteArrayOutputStream(), new StampingProperties()); + Calendar testDate = DateTimeUtil.getCurrentTimeCalendar(); + signer.setSignDate(testDate); + + Assert.assertEquals(testDate, signer.getSignDate()); + } + + @Test + public void certificationLevelSetGetTest() throws IOException { + PdfSigner signer = new PdfSigner( + new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())), + new ByteArrayOutputStream(), new StampingProperties()); + Assert.assertEquals(PdfSigner.NOT_CERTIFIED, signer.getCertificationLevel()); + + int testLevel = PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED; + signer.setCertificationLevel(testLevel); + Assert.assertEquals(testLevel, signer.getCertificationLevel()); + } + + @Test + public void signatureDictionarySetGetTest() throws IOException { + PdfSigner signer = new PdfSigner( + new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())), + new ByteArrayOutputStream(), new StampingProperties()); + Assert.assertNull(signer.getSignatureDictionary()); + + PdfSignature testSignature = new PdfSignature(); + signer.cryptoDictionary = testSignature; + Assert.assertEquals(testSignature, signer.getSignatureDictionary()); + } + + @Test + public void signatureEventSetGetTest() throws IOException { + PdfSigner signer = new PdfSigner( + new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())), + new ByteArrayOutputStream(), new StampingProperties()); + Assert.assertNull(signer.getSignatureEvent()); + + ISignatureEvent testEvent = new DummySignatureEvent(); + signer.setSignatureEvent(testEvent); + Assert.assertEquals(testEvent, signer.getSignatureEvent()); + } + + @Test + public void signatureFieldNameMustNotContainDotTest() throws IOException { + PdfSigner signer = new PdfSigner( + new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())), + new ByteArrayOutputStream(), new StampingProperties()); + + Exception exception = + Assert.assertThrows(IllegalArgumentException.class, () -> signer.setFieldName("name.with.dots")); + Assert.assertEquals(SignExceptionMessageConstant.FIELD_NAMES_CANNOT_CONTAIN_A_DOT, exception.getMessage()); + } + + @Test + public void documentWithoutReaderCannotBeSetToSignerTest() throws IOException { + PdfReader reader = new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())); + PdfSigner signer = new PdfSigner(reader, new ByteArrayOutputStream(), new StampingProperties()); + + PdfDocument documentWithoutReader = new PdfDocument(new PdfWriter(new ByteArrayOutputStream())); + Exception e = + Assert.assertThrows(IllegalArgumentException.class, () -> signer.setDocument(documentWithoutReader)); + Assert.assertEquals(SignExceptionMessageConstant.DOCUMENT_MUST_HAVE_READER, e.getMessage()); + } + + @Test + public void documentSetGetTest() throws IOException { + PdfReader reader = new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())); + PdfSigner signer = new PdfSigner(reader, new ByteArrayOutputStream(), new StampingProperties()); + + PdfDocument document = signer.getDocument(); + Assert.assertEquals(reader, document.getReader()); + + PdfDocument documentWithoutReader = new PdfDocument( + new PdfReader(new ByteArrayInputStream(createSimpleDocument())), + new PdfWriter(new ByteArrayOutputStream())); + signer.setDocument(documentWithoutReader); + Assert.assertEquals(documentWithoutReader, signer.getDocument()); + } + + @Test + public void outputStreamSetGetTest() throws IOException { + PdfReader reader = new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PdfSigner signer = new PdfSigner(reader, outputStream, new StampingProperties()); + + Assert.assertEquals(outputStream, signer.originalOS); + + ByteArrayOutputStream anotherStream = new ByteArrayOutputStream(); + signer.setOriginalOutputStream(anotherStream); + Assert.assertEquals(anotherStream, signer.originalOS); + } + + @Test + public void fieldLockSetGetTest() throws IOException { + PdfReader reader = new PdfReader(new ByteArrayInputStream(createSimplePdfaDocument())); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PdfSigner signer = new PdfSigner(reader, outputStream, new StampingProperties()); + + Assert.assertNull(signer.getFieldLockDict()); + + PdfSigFieldLock fieldLock = new PdfSigFieldLock(); + signer.setFieldLockDict(fieldLock); + Assert.assertEquals(fieldLock, signer.getFieldLockDict()); + } + + private static byte[] createEncryptedDocumentWithoutWidgetAnnotation() { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); PdfDocument document = new PdfDocument(new PdfWriter(outputStream, - new WriterProperties().setStandardEncryption("user".getBytes(), "owner".getBytes(), 0, EncryptionConstants.STANDARD_ENCRYPTION_128))); + new WriterProperties().setStandardEncryption(USER, OWNER, 0, EncryptionConstants.STANDARD_ENCRYPTION_128))); + document.addNewPage(); + document.close(); + return outputStream.toByteArray(); + } + + private static byte[] createSimpleDocument() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PdfDocument document = new PdfDocument(new PdfWriter(outputStream)); + document.addNewPage(); + document.close(); + return outputStream.toByteArray(); + } + + private static byte[] createSimplePdfaDocument() throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PdfWriter writer = new PdfWriter(outputStream); + InputStream is = new FileInputStream(PDFA_RESOURCES + "sRGB Color Space Profile.icm"); + PdfOutputIntent outputIntent = new PdfOutputIntent("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", is); + PdfDocument document = new PdfADocument(writer, PdfAConformanceLevel.PDF_A_1A, outputIntent); + + document.setTagged(); + document.getCatalog().setLang(new PdfString("en-US")); + document.addNewPage(); document.close(); + return outputStream.toByteArray(); } @@ -158,4 +334,12 @@ public ExtendedPdfSignatureFormField(PdfWidgetAnnotation widgetAnnotation, PdfDo super(widgetAnnotation, document); } } + + class DummySignatureEvent implements ISignatureEvent { + + @Override + public void getSignatureDictionary(PdfSignature sig) { + // Do nothing + } + } } diff --git a/sign/src/test/resources/com/itextpdf/signatures/pdfa/sRGB Color Space Profile.icm b/sign/src/test/resources/com/itextpdf/signatures/pdfa/sRGB Color Space Profile.icm new file mode 100644 index 0000000000000000000000000000000000000000..7f9d18d097d1bcccb32e6d5743ac4af593170b6f GIT binary patch literal 3144 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 L{PK7vZD!`*_k{EN literal 0 HcmV?d00001