diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java index ad40a1321e..03e7bc8b72 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java @@ -607,6 +607,33 @@ protected PdfObject readObject(boolean readAsDirect) throws IOException { return readObject(readAsDirect, false); } + protected PdfObject readReference(boolean readAsDirect) { + int num = tokens.getObjNr(); + PdfXrefTable table = pdfDocument.getXref(); + PdfIndirectReference reference = table.get(num); + if (reference != null) { + if (reference.isFree()) { + Logger logger = LoggerFactory.getLogger(PdfReader.class); + logger.warn(MessageFormatUtil.format(LogMessageConstant.INVALID_INDIRECT_REFERENCE, tokens.getObjNr(), tokens.getGenNr())); + return createPdfNullInstance(readAsDirect); + } + if (reference.getGenNumber() != tokens.getGenNr()) { + if (fixedXref) { + Logger logger = LoggerFactory.getLogger(PdfReader.class); + logger.warn(MessageFormatUtil.format(LogMessageConstant.INVALID_INDIRECT_REFERENCE, tokens.getObjNr(), tokens.getGenNr())); + return createPdfNullInstance(readAsDirect); + } else { + throw new PdfException(PdfException.InvalidIndirectReference1, + MessageFormatUtil.format("{0} {1} R", reference.getObjNumber(), reference.getGenNumber())); + } + } + } else { + reference = table.add((PdfIndirectReference) new PdfIndirectReference(pdfDocument, + num, tokens.getGenNr(), 0).setState(PdfObject.READING)); + } + return reference; + } + protected PdfObject readObject(boolean readAsDirect, boolean objStm) throws IOException { tokens.nextValidToken(); PdfTokenizer.TokenType type = tokens.getTokenType(); @@ -650,30 +677,7 @@ protected PdfObject readObject(boolean readAsDirect, boolean objStm) throws IOEx case Name: return readPdfName(readAsDirect); case Ref: - int num = tokens.getObjNr(); - PdfXrefTable table = pdfDocument.getXref(); - PdfIndirectReference reference = table.get(num); - if (reference != null) { - if (reference.isFree()) { - Logger logger = LoggerFactory.getLogger(PdfReader.class); - logger.warn(MessageFormatUtil.format(LogMessageConstant.INVALID_INDIRECT_REFERENCE, tokens.getObjNr(), tokens.getGenNr())); - return createPdfNullInstance(readAsDirect); - } - if (reference.getGenNumber() != tokens.getGenNr()) { - if (fixedXref) { - Logger logger = LoggerFactory.getLogger(PdfReader.class); - logger.warn(MessageFormatUtil.format(LogMessageConstant.INVALID_INDIRECT_REFERENCE, tokens.getObjNr(), tokens.getGenNr())); - return createPdfNullInstance(readAsDirect); - } else { - throw new PdfException(PdfException.InvalidIndirectReference1, - MessageFormatUtil.format("{0} {1} R", reference.getObjNumber(), reference.getGenNumber())); - } - } - } else { - reference = table.add((PdfIndirectReference) new PdfIndirectReference(pdfDocument, - num, tokens.getGenNr(), 0).setState(PdfObject.READING)); - } - return reference; + return readReference(readAsDirect); case EndOfFile: throw new PdfException(PdfException.UnexpectedEndOfFile); default: diff --git a/sign/src/main/java/com/itextpdf/signatures/SignatureUtil.java b/sign/src/main/java/com/itextpdf/signatures/SignatureUtil.java index 03136a3572..2ed17879e4 100644 --- a/sign/src/main/java/com/itextpdf/signatures/SignatureUtil.java +++ b/sign/src/main/java/com/itextpdf/signatures/SignatureUtil.java @@ -46,6 +46,8 @@ This file is part of the iText (R) project. import com.itextpdf.forms.PdfAcroForm; import com.itextpdf.forms.fields.PdfFormField; import com.itextpdf.io.font.PdfEncodings; +import com.itextpdf.io.source.IRandomAccessSource; +import com.itextpdf.io.source.PdfTokenizer; import com.itextpdf.io.source.RASInputStream; import com.itextpdf.io.source.RandomAccessFileOrArray; import com.itextpdf.io.source.RandomAccessSourceFactory; @@ -56,6 +58,9 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.PdfDictionary; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfName; +import com.itextpdf.kernel.pdf.PdfNull; +import com.itextpdf.kernel.pdf.PdfObject; +import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfString; import java.io.IOException; @@ -80,6 +85,24 @@ public class SignatureUtil { private List orderedSignatureNames; private int totalRevisions; + /** + * Converts a {@link com.itextpdf.kernel.pdf.PdfArray} to an array of longs + * + * @param pdfArray PdfArray to be converted + * @return long[] containing the PdfArray values + * @deprecated Will be removed in 7.2. Use {@link PdfArray#toLongArray()} instead + */ + @Deprecated + public static long[] asLongArray(PdfArray pdfArray) { + long[] rslt = new long[pdfArray.size()]; + + for (int k = 0; k < rslt.length; ++k) { + rslt[k] = pdfArray.getAsNumber(k).longValue(); + } + + return rslt; + } + /** * Creates a SignatureUtil instance. Sets the acroForm field to the acroForm in the PdfDocument. * iText will create a new AcroForm if the PdfDocument doesn't contain one. @@ -107,7 +130,7 @@ public PdfPKCS7 verifySignature(String name) { * Verifies a signature. Further verification can be done on the returned * {@link PdfPKCS7} object. * - * @param name the signature field name + * @param name the signature field name * @param provider the provider or null for the default provider * @return PdfPKCS7 object to continue the verification */ @@ -124,8 +147,7 @@ public PdfPKCS7 verifySignature(String name, String provider) { if (cert == null) cert = signature.getPdfObject().getAsArray(PdfName.Cert).getAsString(0); pk = new PdfPKCS7(PdfEncodings.convertToBytes(contents.getValue(), null), cert.getValueBytes(), provider); - } - else + } else pk = new PdfPKCS7(PdfEncodings.convertToBytes(contents.getValue(), null), sub, provider); updateByteRange(pk, signature); PdfString date = signature.getDate(); @@ -140,8 +162,7 @@ public PdfPKCS7 verifySignature(String name, String provider) { if (location != null) pk.setLocation(location); return pk; - } - catch (Exception e) { + } catch (Exception e) { throw new PdfException(e); } } @@ -179,8 +200,7 @@ private void updateByteRange(PdfPKCS7 pkcs7, PdfSignature signature) { while ((rd = rg.read(buf, 0, buf.length)) > 0) { pkcs7.update(buf, 0, rd); } - } - catch (Exception e) { + } catch (Exception e) { throw new PdfException(e); } finally { try { @@ -234,13 +254,12 @@ public int getTotalRevisions() { return totalRevisions; } - public int getRevision(String field) { getSignatureNames(); field = getTranslatedFieldName(field); if (!sigNames.containsKey(field)) return 0; - return sigNames.get(field)[1]; + return sigNames.get(field)[1]; } public String getTranslatedFieldName(String name) { @@ -269,7 +288,7 @@ public InputStream extractRevision(String field) throws IOException { } /** - * Checks if the signature covers the entire document or just part of it. + * Checks if the signature covers the entire document (except for signature's Contents) or just a part of it. * * @param name the signature field name * @return true if the signature covers the entire document, false if it doesn't @@ -279,7 +298,8 @@ public boolean signatureCoversWholeDocument(String name) { if (!sigNames.containsKey(name)) return false; try { - return sigNames.get(name)[0] == document.getReader().getFileLength(); + ContentsChecker signatureReader = new ContentsChecker(document.getReader().getSafeFile().createSourceView()); + return signatureReader.checkWhetherSignatureCoversWholeDocument(acroForm.getField(name)); } catch (IOException e) { throw new PdfException(e); } @@ -295,24 +315,6 @@ public boolean doesSignatureFieldExist(String name) { return getBlankSignatureNames().contains(name) || getSignatureNames().contains(name); } - /** - * Converts a {@link com.itextpdf.kernel.pdf.PdfArray} to an array of longs - * - * @param pdfArray PdfArray to be converted - * @return long[] containing the PdfArray values - * @deprecated Will be removed in 7.2. Use {@link PdfArray#toLongArray()} instead - */ - @Deprecated - public static long[] asLongArray(PdfArray pdfArray) { - long[] rslt = new long[pdfArray.size()]; - - for (int k = 0; k < rslt.length; ++k) { - rslt[k] = pdfArray.getAsNumber(k).longValue(); - } - - return rslt; - } - private void populateSignatureNames() { if (acroForm == null) { return; @@ -345,7 +347,7 @@ private void populateSignatureNames() { Collections.sort(sorter, new SorterComparator()); if (sorter.size() > 0) { try { - if (((int[])sorter.get(sorter.size() - 1)[1])[0] == document.getReader().getFileLength()) + if (((int[]) sorter.get(sorter.size() - 1)[1])[0] == document.getReader().getFileLength()) totalRevisions = sorter.size(); else totalRevisions = sorter.size() + 1; @@ -354,8 +356,8 @@ private void populateSignatureNames() { } for (int k = 0; k < sorter.size(); ++k) { Object[] objs = sorter.get(k); - String name = (String)objs[0]; - int[] p = (int[])objs[1]; + String name = (String) objs[0]; + int[] p = (int[]) objs[1]; p[1] = k + 1; sigNames.put(name, p); orderedSignatureNames.add(name); @@ -366,9 +368,118 @@ private void populateSignatureNames() { private static class SorterComparator implements Comparator { @Override public int compare(Object[] o1, Object[] o2) { - int n1 = ((int[])o1[1])[0]; - int n2 = ((int[])o2[1])[0]; + int n1 = ((int[]) o1[1])[0]; + int n2 = ((int[]) o2[1])[0]; return n1 - n2; } } + + private static class ContentsChecker extends PdfReader { + + private long contentsStart; + private long contentsEnd; + + private int currentLevel = 0; + private int contentsLevel = 1; + private boolean searchInV = true; + + private boolean rangeIsCorrect = false; + + + public ContentsChecker(IRandomAccessSource byteSource) throws IOException { + super(byteSource, null); + } + + public boolean checkWhetherSignatureCoversWholeDocument(PdfFormField signatureField) { + rangeIsCorrect = false; + PdfDictionary signature = (PdfDictionary) signatureField.getValue(); + int[] byteRange = ((PdfArray) signature.get(PdfName.ByteRange)).toIntArray(); + try { + if (4 != byteRange.length || 0 != byteRange[0] || tokens.getSafeFile().length() != byteRange[2] + byteRange[3]) { + return false; + } + } catch (IOException e) { + // That's not expected because if the signature is invalid, it should have already failed + return false; + } + + contentsStart = byteRange[1]; + contentsEnd = byteRange[2]; + + long signatureOffset; + if (null != signature.getIndirectReference()) { + signatureOffset = signature.getIndirectReference().getOffset(); + searchInV = true; + } else { + signatureOffset = signatureField.getPdfObject().getIndirectReference().getOffset(); + searchInV = false; + contentsLevel++; + } + + try { + tokens.seek(signatureOffset); + tokens.nextValidToken(); + readObject(false, false); + } catch (IOException e) { + // That's not expected because if the signature is invalid, it should have already failed + return false; + } + + return rangeIsCorrect; + } + + @Override + // The method copies the logic of PdfReader's method. + // Only Contents related checks have been introduced. + protected PdfDictionary readDictionary(boolean objStm) throws IOException { + currentLevel++; + PdfDictionary dic = new PdfDictionary(); + while (!rangeIsCorrect) { + tokens.nextValidToken(); + if (tokens.getTokenType() == PdfTokenizer.TokenType.EndDic) { + currentLevel--; + break; + } + if (tokens.getTokenType() != PdfTokenizer.TokenType.Name) { + tokens.throwError(PdfException.DictionaryKey1IsNotAName, tokens.getStringValue()); + } + PdfName name = readPdfName(true); + PdfObject obj; + if (PdfName.Contents.equals(name) && searchInV && contentsLevel == currentLevel) { + long startPosition = tokens.getPosition(); + int ch; + int whiteSpacesCount = -1; + do { + ch = tokens.read(); + whiteSpacesCount++; + } while (ch != -1 && PdfTokenizer.isWhitespace(ch)); + tokens.seek(startPosition); + obj = readObject(true, objStm); + long endPosition = tokens.getPosition(); + if (endPosition == contentsEnd && startPosition + whiteSpacesCount == contentsStart) { + rangeIsCorrect = true; + } + } else if (PdfName.V.equals(name) && !searchInV && 1 == currentLevel) { + searchInV = true; + obj = readObject(true, objStm); + searchInV = false; + } else { + obj = readObject(true, objStm); + } + if (obj == null) { + if (tokens.getTokenType() == PdfTokenizer.TokenType.EndDic) + tokens.throwError(PdfException.UnexpectedGtGt); + if (tokens.getTokenType() == PdfTokenizer.TokenType.EndArray) + tokens.throwError(PdfException.UnexpectedCloseBracket); + } + dic.put(name, obj); + } + return dic; + } + + @Override + protected PdfObject readReference(boolean readAsDirect) { + return new PdfNull(); + } + } } diff --git a/sign/src/test/java/com/itextpdf/signatures/SignatureUtilTest.java b/sign/src/test/java/com/itextpdf/signatures/SignatureUtilTest.java index ef5be2248c..6b06fc1f8b 100644 --- a/sign/src/test/java/com/itextpdf/signatures/SignatureUtilTest.java +++ b/sign/src/test/java/com/itextpdf/signatures/SignatureUtilTest.java @@ -79,4 +79,93 @@ public void getSignaturesTest02() throws IOException { Assert.assertEquals(0, signatureNames.size()); } + @Test + public void firstBytesNotCoveredTest01() throws IOException { + String inPdf = sourceFolder + "firstBytesNotCoveredTest01.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertFalse(signatureUtil.signatureCoversWholeDocument("Signature1")); + } + + @Test + public void lastBytesNotCoveredTest01() throws IOException { + String inPdf = sourceFolder + "lastBytesNotCoveredTest01.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertFalse(signatureUtil.signatureCoversWholeDocument("Signature1")); + } + + @Test + public void lastBytesNotCoveredTest02() throws IOException { + String inPdf = sourceFolder + "lastBytesNotCoveredTest02.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertFalse(signatureUtil.signatureCoversWholeDocument("Signature1")); + } + + @Test + public void bytesAreNotCoveredTest01() throws IOException { + String inPdf = sourceFolder + "bytesAreNotCoveredTest01.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertFalse(signatureUtil.signatureCoversWholeDocument("Signature1")); + } + + @Test + public void bytesAreCoveredTest01() throws IOException { + String inPdf = sourceFolder + "bytesAreCoveredTest01.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertTrue(signatureUtil.signatureCoversWholeDocument("Signature1")); + } + + @Test + public void bytesAreCoveredTest02() throws IOException { + String inPdf = sourceFolder + "bytesAreCoveredTest02.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertTrue(signatureUtil.signatureCoversWholeDocument("sig")); + } + + @Test + public void twoContentsTest01() throws IOException { + String inPdf = sourceFolder + "twoContentsTest01.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertTrue(signatureUtil.signatureCoversWholeDocument("Signature1")); + } + + @Test + public void spacesBeforeContentsTest01() throws IOException { + String inPdf = sourceFolder + "spacesBeforeContentsTest01.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertFalse(signatureUtil.signatureCoversWholeDocument("Signature1")); + } + + @Test + public void spacesBeforeContentsTest02() throws IOException { + String inPdf = sourceFolder + "spacesBeforeContentsTest02.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertTrue(signatureUtil.signatureCoversWholeDocument("Signature1")); + } + + @Test + public void notIndirectSigDictionaryTest() throws IOException { + String inPdf = sourceFolder + "notIndirectSigDictionaryTest.pdf"; + PdfDocument pdfDocument = new PdfDocument(new PdfReader(inPdf)); + SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); + + Assert.assertTrue(signatureUtil.signatureCoversWholeDocument("Signature1")); + } } diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/bytesAreCoveredTest01.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/bytesAreCoveredTest01.pdf new file mode 100644 index 0000000000..3269b79d37 Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/bytesAreCoveredTest01.pdf differ diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/bytesAreCoveredTest02.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/bytesAreCoveredTest02.pdf new file mode 100644 index 0000000000..9fb391ba0d Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/bytesAreCoveredTest02.pdf differ diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/bytesAreNotCoveredTest01.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/bytesAreNotCoveredTest01.pdf new file mode 100644 index 0000000000..6b9e2b90f6 Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/bytesAreNotCoveredTest01.pdf differ diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/firstBytesNotCoveredTest01.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/firstBytesNotCoveredTest01.pdf new file mode 100644 index 0000000000..d551f717b8 Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/firstBytesNotCoveredTest01.pdf differ diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/lastBytesNotCoveredTest01.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/lastBytesNotCoveredTest01.pdf new file mode 100644 index 0000000000..3c9a8dde21 Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/lastBytesNotCoveredTest01.pdf differ diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/lastBytesNotCoveredTest02.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/lastBytesNotCoveredTest02.pdf new file mode 100644 index 0000000000..353ce79865 Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/lastBytesNotCoveredTest02.pdf differ diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/notIndirectSigDictionaryTest.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/notIndirectSigDictionaryTest.pdf new file mode 100644 index 0000000000..4fce05752b Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/notIndirectSigDictionaryTest.pdf differ diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/spacesBeforeContentsTest01.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/spacesBeforeContentsTest01.pdf new file mode 100644 index 0000000000..237565607d Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/spacesBeforeContentsTest01.pdf differ diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/spacesBeforeContentsTest02.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/spacesBeforeContentsTest02.pdf new file mode 100644 index 0000000000..3a1034c838 Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/spacesBeforeContentsTest02.pdf differ diff --git a/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/twoContentsTest01.pdf b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/twoContentsTest01.pdf new file mode 100644 index 0000000000..802465bad3 Binary files /dev/null and b/sign/src/test/resources/com/itextpdf/signatures/SignatureUtilTest/twoContentsTest01.pdf differ