Skip to content

Commit 36c2f7a

Browse files
author
Alexander Kozlov
committed
Fix for consiquent signatures verification by preventing copying of existing RandomAccessSource
DEVSIX-6181
1 parent 3213363 commit 36c2f7a

File tree

9 files changed

+242
-15
lines changed

9 files changed

+242
-15
lines changed

io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,5 @@ public final class IoExceptionMessage {
6666
public static final String IMAGE_MAGICK_OUTPUT_IS_NULL = "ImageMagick process output is null.";
6767
public static final String IMAGE_MAGICK_PROCESS_EXECUTION_FAILED =
6868
"ImageMagick process execution finished with errors: ";
69+
public static final String ALREADY_CLOSED = "Already closed";
6970
}

io/src/main/java/com/itextpdf/io/source/ArrayRandomAccessSource.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ This file is part of the iText (R) project.
4444
package com.itextpdf.io.source;
4545

4646

47+
import com.itextpdf.io.exceptions.IoExceptionMessage;
48+
4749
/**
4850
* A RandomAccessSource that is based on an underlying byte array
4951
*/
@@ -53,30 +55,41 @@ class ArrayRandomAccessSource implements IRandomAccessSource {
5355
private byte[] array;
5456

5557
public ArrayRandomAccessSource(byte[] array) {
56-
if(array == null) throw new IllegalArgumentException("Passed byte array can not be null.");
58+
if(array == null) {
59+
throw new IllegalArgumentException("Passed byte array can not be null.");
60+
}
5761
this.array = array;
5862
}
5963

6064
public int get(long offset) {
61-
if (offset >= array.length) return -1;
65+
if (array == null) {
66+
throw new IllegalStateException(IoExceptionMessage.ALREADY_CLOSED);
67+
}
68+
if (offset >= array.length) {
69+
return -1;
70+
}
6271
return 0xff & array[(int)offset];
6372
}
6473

6574
public int get(long offset, byte[] bytes, int off, int len) {
66-
if (array == null) throw new IllegalStateException("Already closed");
67-
68-
if (offset >= array.length)
75+
if (array == null) {
76+
throw new IllegalStateException(IoExceptionMessage.ALREADY_CLOSED);
77+
}
78+
if (offset >= array.length) {
6979
return -1;
70-
71-
if (offset + len > array.length)
80+
}
81+
if (offset + len > array.length) {
7282
len = (int)(array.length - offset);
73-
83+
}
7484
System.arraycopy(array, (int)offset, bytes, off, len);
7585

7686
return len;
7787
}
7888

7989
public long length() {
90+
if (array == null) {
91+
throw new IllegalStateException(IoExceptionMessage.ALREADY_CLOSED);
92+
}
8093
return array.length;
8194
}
8295

io/src/main/java/com/itextpdf/io/source/RASInputStream.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ public RASInputStream(IRandomAccessSource source){
6969
this.source = source;
7070
}
7171

72+
/**
73+
* Gets the source
74+
*
75+
* @return an instance of {@link IRandomAccessSource}
76+
*/
77+
public IRandomAccessSource getSource() {
78+
return source;
79+
}
80+
7281
/**
7382
* {@inheritDoc}
7483
*/

io/src/main/java/com/itextpdf/io/source/RandomAccessSourceFactory.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,37 @@ public IRandomAccessSource createSource(URL url) throws java.io.IOException{
140140
}
141141

142142
/**
143-
* Creates a {@link IRandomAccessSource} based on an {@link InputStream}. The full content of the InputStream is read into memory and used
143+
* Creates or extracts a {@link IRandomAccessSource} based on an {@link InputStream}.
144+
*
145+
* <p>
146+
* If the InputStream is an instance of {@link RASInputStream} then extracts the source from it.
147+
* Otherwise The full content of the InputStream is read into memory and used
144148
* as the source for the {@link IRandomAccessSource}
149+
*
145150
* @param inputStream the stream to read from
151+
*
152+
* @return the newly created or extracted {@link IRandomAccessSource}
153+
*
154+
* @throws java.io.IOException in case of any I/O error.
155+
*/
156+
public IRandomAccessSource extractOrCreateSource(InputStream inputStream) throws java.io.IOException {
157+
if (inputStream instanceof RASInputStream) {
158+
return ((RASInputStream) inputStream).getSource();
159+
}
160+
return createSource(StreamUtil.inputStreamToArray(inputStream));
161+
}
162+
163+
/**
164+
* Creates a {@link IRandomAccessSource} based on an {@link InputStream}.
165+
*
166+
* <p>
167+
* The full content of the InputStream is read into memory and used
168+
* as the source for the {@link IRandomAccessSource}
169+
*
170+
* @param inputStream the stream to read from
171+
*
146172
* @return the newly created {@link IRandomAccessSource}
173+
*
147174
* @throws java.io.IOException in case of any I/O error.
148175
*/
149176
public IRandomAccessSource createSource(InputStream inputStream) throws java.io.IOException{
@@ -169,7 +196,6 @@ public IRandomAccessSource createBestSource(String filename) throws java.io.IOEx
169196
|| filename.startsWith("https://")
170197
|| filename.startsWith("jar:")
171198
|| filename.startsWith("wsjar:")
172-
|| filename.startsWith("wsjar:")
173199
|| filename.startsWith("vfszip:")) {
174200
return createSource(new URL(filename));
175201
} else {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2022 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is free software; you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License version 3
8+
as published by the Free Software Foundation with the addition of the
9+
following permission added to Section 15 as permitted in Section 7(a):
10+
FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
11+
ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
12+
OF THIRD PARTY RIGHTS
13+
14+
This program is distributed in the hope that it will be useful, but
15+
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16+
or FITNESS FOR A PARTICULAR PURPOSE.
17+
See the GNU Affero General Public License for more details.
18+
You should have received a copy of the GNU Affero General Public License
19+
along with this program; if not, see http://www.gnu.org/licenses or write to
20+
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21+
Boston, MA, 02110-1301 USA, or download the license from the following URL:
22+
http://itextpdf.com/terms-of-use/
23+
24+
The interactive user interfaces in modified source and object code versions
25+
of this program must display Appropriate Legal Notices, as required under
26+
Section 5 of the GNU Affero General Public License.
27+
28+
In accordance with Section 7(b) of the GNU Affero General Public License,
29+
a covered work must retain the producer line in every PDF that is created
30+
or manipulated using iText.
31+
32+
You can be released from the requirements of the license by purchasing
33+
a commercial license. Buying such a license is mandatory as soon as you
34+
develop commercial activities involving the iText software without
35+
disclosing the source code of your own applications.
36+
These activities include: offering paid services to customers as an ASP,
37+
serving PDFs on the fly in a web application, shipping iText with a closed
38+
source product.
39+
40+
For more information, please contact iText Software Corp. at this
41+
42+
*/
43+
package com.itextpdf.io.source;
44+
45+
import com.itextpdf.io.exceptions.IoExceptionMessage;
46+
import com.itextpdf.test.ExtendedITextTest;
47+
import com.itextpdf.test.annotations.type.UnitTest;
48+
49+
import java.io.FileInputStream;
50+
import java.io.IOException;
51+
import java.io.InputStream;
52+
import org.junit.Assert;
53+
import org.junit.Test;
54+
import org.junit.experimental.categories.Category;
55+
56+
@Category(UnitTest.class)
57+
public class RandomAccessSourceFactoryTest extends ExtendedITextTest {
58+
59+
private final static String SOURCE_FILE = "./src/test/resources/com/itextpdf/io/source/RAF.txt";
60+
61+
@Test
62+
public void readRASInputStreamClosedTest() throws IOException {
63+
String fileName = SOURCE_FILE;
64+
try (InputStream pdfStream = new FileInputStream(fileName)) {
65+
66+
IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory()
67+
.extractOrCreateSource(pdfStream);
68+
RASInputStream rasInputStream = new RASInputStream(randomAccessSource);
69+
IRandomAccessSource extractedRandomAccessSource = new RandomAccessSourceFactory()
70+
.extractOrCreateSource(rasInputStream);
71+
72+
extractedRandomAccessSource.close();
73+
74+
Exception e = Assert.assertThrows(IllegalStateException.class, () -> rasInputStream.read());
75+
Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage());
76+
77+
e = Assert.assertThrows(IllegalStateException.class,
78+
() -> randomAccessSource.get(0));
79+
Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage());
80+
e = Assert.assertThrows(IllegalStateException.class,
81+
() -> randomAccessSource.get(0, new byte[10], 0, 10));
82+
Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage());
83+
e = Assert.assertThrows(IllegalStateException.class,
84+
() -> randomAccessSource.length());
85+
Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage());
86+
}
87+
}
88+
89+
@Test
90+
public void readRASInputStreamTest() throws IOException {
91+
String fileName = SOURCE_FILE;
92+
try (InputStream pdfStream = new FileInputStream(fileName)) {
93+
IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory()
94+
.extractOrCreateSource(pdfStream);
95+
RASInputStream rasInputStream = new RASInputStream(randomAccessSource);
96+
IRandomAccessSource extractedRandomAccessSource = new RandomAccessSourceFactory()
97+
.extractOrCreateSource(rasInputStream);
98+
99+
Assert.assertEquals(72, rasInputStream.read());
100+
Assert.assertEquals(72, extractedRandomAccessSource.get(0));
101+
Assert.assertEquals(extractedRandomAccessSource, rasInputStream.getSource());
102+
}
103+
}
104+
}

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ This file is part of the iText (R) project.
4848
import com.itextpdf.io.source.ByteUtils;
4949
import com.itextpdf.io.source.IRandomAccessSource;
5050
import com.itextpdf.io.source.PdfTokenizer;
51+
import com.itextpdf.io.source.RASInputStream;
5152
import com.itextpdf.io.source.RandomAccessFileOrArray;
5253
import com.itextpdf.io.source.RandomAccessSourceFactory;
5354
import com.itextpdf.io.source.WindowRandomAccessSource;
@@ -138,13 +139,15 @@ public PdfReader(IRandomAccessSource byteSource, ReaderProperties properties) th
138139
/**
139140
* Reads and parses a PDF document.
140141
*
141-
* @param is the {@code InputStream} containing the document. The stream is read to the
142-
* end but is not closed.
142+
* @param is the {@code InputStream} containing the document. If the inputStream is an instance of
143+
* {@link RASInputStream} then the {@link IRandomAccessSource} would be extracted. Otherwise the stream
144+
* is read to the end but is not closed.
143145
* @param properties properties of the created reader
146+
*
144147
* @throws IOException on error
145148
*/
146149
public PdfReader(InputStream is, ReaderProperties properties) throws IOException {
147-
this(new RandomAccessSourceFactory().createSource(is), properties, true);
150+
this(new RandomAccessSourceFactory().extractOrCreateSource(is), properties, true);
148151
}
149152

150153
/**
@@ -161,8 +164,10 @@ public PdfReader(java.io.File file) throws FileNotFoundException, IOException {
161164
/**
162165
* Reads and parses a PDF document.
163166
*
164-
* @param is the {@code InputStream} containing the document. the {@code InputStream} containing the document. The stream is read to the
165-
* end but is not closed.
167+
* @param is the {@code InputStream} containing the document. If the inputStream is an instance of
168+
* {@link RASInputStream} then the {@link IRandomAccessSource} would be extracted. Otherwise the stream
169+
* is read to the end but is not closed.
170+
*
166171
* @throws IOException on error
167172
*/
168173
public PdfReader(InputStream is) throws IOException {

kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,13 @@ This file is part of the iText (R) project.
4444

4545
import com.itextpdf.commons.utils.FileUtil;
4646
import com.itextpdf.commons.utils.MessageFormatUtil;
47+
import com.itextpdf.io.exceptions.IoExceptionMessage;
4748
import com.itextpdf.io.font.PdfEncodings;
4849
import com.itextpdf.io.logs.IoLogMessageConstant;
4950
import com.itextpdf.io.source.ByteArrayOutputStream;
5051
import com.itextpdf.io.source.ByteUtils;
5152
import com.itextpdf.io.source.IRandomAccessSource;
53+
import com.itextpdf.io.source.RASInputStream;
5254
import com.itextpdf.io.source.RandomAccessSourceFactory;
5355
import com.itextpdf.kernel.exceptions.InvalidXRefPrevException;
5456
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
@@ -2223,6 +2225,53 @@ public void closingArrayBracketMissingConservativeTest() throws IOException {
22232225
exception.getCause().getMessage());
22242226
}
22252227

2228+
@Test
2229+
public void readRASInputStreamClosedTest() throws IOException {
2230+
String fileName = SOURCE_FOLDER + "hello.pdf";
2231+
try (InputStream pdfStream = new FileInputStream(fileName)) {
2232+
2233+
IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory()
2234+
.extractOrCreateSource(pdfStream);
2235+
RASInputStream rasInputStream = new RASInputStream(randomAccessSource);
2236+
2237+
randomAccessSource.close();
2238+
2239+
Exception e = Assert.assertThrows(IllegalStateException.class,
2240+
() -> new PdfReader(rasInputStream));
2241+
Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage());
2242+
}
2243+
}
2244+
2245+
@Test
2246+
public void readRASInputStreamTest() throws IOException {
2247+
String fileName = SOURCE_FOLDER + "hello.pdf";
2248+
try (InputStream pdfStream = new FileInputStream(fileName)) {
2249+
IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory()
2250+
.extractOrCreateSource(pdfStream);
2251+
RASInputStream rasInputStream = new RASInputStream(randomAccessSource);
2252+
2253+
try (PdfReader reader = new PdfReader(rasInputStream)) {
2254+
randomAccessSource.close();
2255+
Exception e = Assert.assertThrows(IllegalStateException.class, () -> new PdfDocument(reader));
2256+
Assert.assertEquals(IoExceptionMessage.ALREADY_CLOSED, e.getMessage());
2257+
}
2258+
}
2259+
}
2260+
2261+
@Test
2262+
public void readRASInputStreamValidTest() throws IOException {
2263+
String fileName = SOURCE_FOLDER + "hello.pdf";
2264+
try (InputStream pdfStream = new FileInputStream(fileName)) {
2265+
IRandomAccessSource randomAccessSource = new RandomAccessSourceFactory()
2266+
.extractOrCreateSource(pdfStream);
2267+
RASInputStream rasInputStream = new RASInputStream(randomAccessSource);
2268+
2269+
try (PdfReader reader = new PdfReader(rasInputStream)) {
2270+
AssertUtil.doesNotThrow(() -> new PdfDocument(reader));
2271+
}
2272+
}
2273+
}
2274+
22262275
private static File copyFileForTest(String fileName, String copiedFileName) throws IOException {
22272276
File copiedFile = new File(copiedFileName);
22282277
Files.copy(Paths.get(fileName), Paths.get(copiedFileName));

sign/src/test/java/com/itextpdf/signatures/LtvVerifierIntegrationTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ This file is part of the iText (R) project.
4545
public class LtvVerifierIntegrationTest extends ExtendedITextTest {
4646
private static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/signatures/LtvVerifierIntegrationTest/";
4747

48+
4849
@BeforeClass
4950
public static void before() {
5051
Security.addProvider(new BouncyCastleProvider());
@@ -262,4 +263,23 @@ public void notTrustedRootCertificateInLatestRevisionTest()
262263
Assert.assertEquals("Root certificate passed without checking", verificationOK.message);
263264
}
264265
}
266+
267+
@Test
268+
public void switchBetweenSeveralRevisionsTest() throws IOException, GeneralSecurityException {
269+
String testInput = SOURCE_FOLDER + "severalConsequentSignatures.pdf";
270+
271+
try(PdfReader pdfReader = new PdfReader(testInput); PdfDocument pdfDoc = new PdfDocument(pdfReader)) {
272+
273+
LtvVerifier ltvVerifier = new LtvVerifier(pdfDoc);
274+
275+
Assert.assertEquals("timestampSig2", ltvVerifier.signatureName);
276+
ltvVerifier.switchToPreviousRevision();
277+
Assert.assertEquals("Signature2", ltvVerifier.signatureName);
278+
ltvVerifier.switchToPreviousRevision();
279+
Assert.assertEquals("timestampSig1", ltvVerifier.signatureName);
280+
ltvVerifier.switchToPreviousRevision();
281+
Assert.assertEquals("Signature1", ltvVerifier.signatureName);
282+
ltvVerifier.switchToPreviousRevision();
283+
}
284+
}
265285
}

0 commit comments

Comments
 (0)