New upstream version 2.0.13
Markus Koschany
5 years ago
0 | Release Notes -- Apache PDFBox -- Version 2.0.12 | |
0 | Release Notes -- Apache PDFBox -- Version 2.0.13 | |
1 | 1 | |
2 | 2 | Introduction |
3 | 3 | ------------ |
4 | 4 | |
5 | 5 | The Apache PDFBox library is an open source Java tool for working with PDF documents. |
6 | 6 | |
7 | This is an incremental bugfix release based on the earlier 2.0.11 release. It contains | |
7 | This is an incremental bugfix release based on the earlier 2.0.12 release. It contains | |
8 | 8 | a couple of fixes and small improvements. |
9 | 9 | |
10 | 10 | For more details on these changes and all the other fixes and improvements |
13 | 13 | |
14 | 14 | Bug |
15 | 15 | |
16 | [PDFBOX-4013] - Java 9/macOS: Debugger App does not start (NoSuchMethodException) | |
17 | [PDFBOX-4219] - Multithreading problem when rendering several documents with Standard 14 fonts | |
18 | [PDFBOX-4242] - Fontbox does not close file descriptor when loading fonts. | |
19 | [PDFBOX-4245] - wrong rendering of the transparency group at the specific position on a page | |
20 | [PDFBOX-4254] - PDDocument.close() might ignore throwing an Exception | |
21 | [PDFBOX-4266] - Java 6 error | |
22 | [PDFBOX-4267] - Incorrect rendering when /Matte entry | |
23 | [PDFBOX-4268] - Japanese text displayed as barcode | |
24 | [PDFBOX-4276] - Multiply blend mode not detected | |
25 | [PDFBOX-4278] - Type 3 font .notdef-named glyph missing in rendering | |
26 | [PDFBOX-4279] - ArrayIndexOutOfBoundsException in PDDeviceGray.toRGB | |
27 | [PDFBOX-4283] - Allowing Rectangles with additional elements | |
28 | [PDFBOX-4288] - needless adding while parsing dictionary objects | |
29 | [PDFBOX-4291] - JavaDoc on website is marked as being in German | |
30 | [PDFBOX-4292] - Validation fails if ModifyDate and ModDate are specified using different time zones | |
31 | [PDFBOX-4298] - NullPointerException when doing overlay | |
32 | [PDFBOX-4299] - ArrayIndexOutOfBoundsException in CmapSubtable.processSubtype2 | |
33 | [PDFBOX-4301] - ClassCastException in PDExtendedGraphicsState | |
34 | [PDFBOX-4302] - ToUnicode CMap is not written correctly when the entry count is just 100. | |
35 | [PDFBOX-4305] - Log message not in english | |
36 | [PDFBOX-4306] - Image clipping area rounding error | |
37 | [PDFBOX-4307] - ClassCastException in PDDocumentCatalog.getDocumentOutline if 'outlines' is not a dictionary | |
38 | [PDFBOX-4308] - PDDocument protect changes color palette | |
39 | [PDFBOX-4315] - PDFBox info message when not using it | |
40 | [PDFBOX-4316] - RemoveAllText does not delete all parameters with " operator | |
41 | [PDFBOX-4318] - PDFont.encode results change on identical input | |
42 | [PDFBOX-4319] - Parsing 100000 page pdf is slow | |
43 | [PDFBOX-4322] - Extract Text feature is not working for some part of PDF | |
16 | [PDFBOX-3646] - Annotations parsed from XFDF containing ampersand characters are not properly imported | |
17 | [PDFBOX-4163] - Java 11 compile error | |
18 | [PDFBOX-4326] - PDF with JPEG2000 image can't be rendered | |
19 | [PDFBOX-4327] - NullPointerException in PDFStreamEngine.processSoftMask() when running ExtractImages | |
20 | [PDFBOX-4330] - NumberFormatException in CFFParser.readRealNumber() | |
21 | [PDFBOX-4331] - Make jdk9 profile activation automatic | |
22 | [PDFBOX-4333] - ClassCastException when loading PDF | |
23 | [PDFBOX-4336] - "CMap is invalid" exception thrown | |
24 | [PDFBOX-4338] - ArrayIndexOutOfBoundsException in COSParser | |
25 | [PDFBOX-4339] - NullPointerException in COSParser | |
26 | [PDFBOX-4343] - Prevent calling addSignature twice | |
27 | [PDFBOX-4345] - FDFAnnotation.richContentsToString does not evaluate text nodes which have siblings in the XML | |
28 | [PDFBOX-4347] - ArrayIndexOutOfBoundsException in PDFXrefStreamParser | |
29 | [PDFBOX-4348] - ClassCastException in COSParser | |
30 | [PDFBOX-4349] - ClassCastException in COSParser | |
31 | [PDFBOX-4350] - IllegalArgumentException in PDFObjectStreamParser | |
32 | [PDFBOX-4351] - IndexOutOfBoundsException when reading from InputStreamSource | |
33 | [PDFBOX-4352] - NullPointerException in COSParser | |
34 | [PDFBOX-4353] - NullPointerException in PDFXrefStreamParser | |
35 | [PDFBOX-4354] - NumberFormatException in COSParser | |
36 | [PDFBOX-4355] - PDFTextStripperByArea dies on Chinese/Japanese files | |
37 | [PDFBOX-4357] - IllegalArgumentException "root cannot be null" | |
38 | [PDFBOX-4359] - Bad sizing of signature field inside rotated page | |
39 | [PDFBOX-4360] - ArrayIndexOutOfBoundsException in ASCIIHexFilter | |
40 | [PDFBOX-4361] - ArrayIndexOutOfBoundsException in COSParser | |
41 | [PDFBOX-4364] - example AddValidationInformation fails with scratchfile error | |
42 | [PDFBOX-4365] - PDFDebugger: JComboBox does not take generic parameters in Java 1.6 | |
43 | [PDFBOX-4366] - NullPointerException in PDButton.updateByValue() when appearance missing | |
44 | [PDFBOX-4367] - Error expected floating point number actual='18-5' | |
45 | [PDFBOX-4369] - unsupported ExtractText -force option still appears in online 2.0 docs | |
46 | [PDFBOX-4372] - Stack overflow around PDFStreamEngine.processStream | |
47 | [PDFBOX-4374] - Switch from log4j to slf4j | |
48 | [PDFBOX-4377] - Verify CRL in AddValidation example | |
49 | [PDFBOX-4381] - Revocation CRL check should be done at signing time in AddValidation example | |
50 | [PDFBOX-4383] - PDFMergerUtility seems to leave source file open | |
51 | [PDFBOX-4384] - PDF/A Document Validation out of memory | |
44 | 52 | |
45 | 53 | Improvement |
46 | 54 | |
47 | [PDFBOX-4184] - [PATCH]: Support simple lossless compression of 16 bit RGB images | |
48 | [PDFBOX-4253] - Optimize PDFunctionType3.eval() | |
49 | [PDFBOX-4256] - Return default value for CheckBox / RadioButton if /V entry is missing (widget /AS entries are present) | |
50 | [PDFBOX-4259] - Add polygon annotation to AddAnnotations.java example | |
51 | [PDFBOX-4260] - Reduce RAM requirement of COSOutputStream | |
52 | [PDFBOX-4263] - Check object key when signing | |
53 | [PDFBOX-4271] - Consistently using the same version of the download-maven-plugin | |
54 | [PDFBOX-4274] - Get rid of warning about prerequisites | |
55 | [PDFBOX-4285] - Expose the tiff compression type to the user. | |
56 | [PDFBOX-4289] - Double negative number | |
57 | [PDFBOX-4295] - Don't create intermediate streams when merging files | |
55 | [PDFBOX-4335] - Overlay should implement Closeable | |
56 | [PDFBOX-4363] - [Patch] Add a common interface PDShadingPaint for all shading paints | |
57 | [PDFBOX-4371] - Improve ExtractText utility so that it can extract rotated text automatically | |
58 | [PDFBOX-4375] - Change visibility of Overlay#loadPDF to protected | |
59 | ||
60 | Test | |
61 | ||
62 | [PDFBOX-4373] - Add additional unit tests | |
58 | 63 | |
59 | 64 | Task |
60 | 65 | |
61 | [PDFBOX-4281] - Replace Apache Wink dependency | |
66 | [PDFBOX-4358] - Prevent stack overflow in COSDictionary.toString() | |
67 | [PDFBOX-4362] - Create simple text extraction example | |
68 | ||
62 | 69 | |
63 | 70 | Release Contents |
64 | 71 | ---------------- |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.12</version> | |
25 | <version>2.0.13</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 | |
67 | 67 | <Embed-Transitive>true</Embed-Transitive> |
68 | 68 | <Embed-Dependency>*;scope=provided;inline=org/apache/**|org/bouncycastle/**|META-INF/services/**</Embed-Dependency> |
69 | 69 | <Bundle-DocURL>${project.url}</Bundle-DocURL> |
70 | <Import-Package>!junit.framework,!junit.textui,javax.*;resolution:=optional,org.apache.avalon.framework.logger;resolution:=optional,org.apache.log;resolution:=optional,org.apache.log4j;resolution:=optional,*</Import-Package> | |
70 | <Import-Package>!junit.framework,!junit.textui,javax.*;resolution:=optional,org.apache.avalon.framework.logger;resolution:=optional,org.apache.log;resolution:=optional,*</Import-Package> | |
71 | 71 | <Main-Class>org.apache.pdfbox.tools.PDFBox</Main-Class> |
72 | 72 | </instructions> |
73 | 73 | </configuration> |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.12</version> | |
25 | <version>2.0.13</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 |
905 | 905 | { |
906 | 906 | selectedNode = ((MapEntry)selectedNode).getKey(); |
907 | 907 | selectedNode = getUnderneathObject(selectedNode); |
908 | FlagBitsPane flagBitsPane = new FlagBitsPane((COSDictionary) parentNode, (COSName) selectedNode); | |
908 | FlagBitsPane flagBitsPane = new FlagBitsPane(document, | |
909 | (COSDictionary) parentNode, | |
910 | (COSName) selectedNode); | |
909 | 911 | replaceRightComponent(flagBitsPane.getPane()); |
910 | 912 | } |
911 | 913 | } |
19 | 19 | import javax.swing.JPanel; |
20 | 20 | import org.apache.pdfbox.cos.COSDictionary; |
21 | 21 | import org.apache.pdfbox.cos.COSName; |
22 | import org.apache.pdfbox.pdmodel.PDDocument; | |
22 | 23 | |
23 | 24 | /** |
24 | 25 | * @author Khyrul Bashar |
28 | 29 | public class FlagBitsPane |
29 | 30 | { |
30 | 31 | private FlagBitsPaneView view; |
32 | private final PDDocument document; | |
31 | 33 | |
32 | 34 | /** |
33 | 35 | * Constructor. |
34 | 36 | * @param dictionary COSDictionary instance. |
35 | 37 | * @param flagType COSName instance. |
36 | 38 | */ |
37 | public FlagBitsPane(final COSDictionary dictionary, COSName flagType) | |
39 | public FlagBitsPane(PDDocument document, final COSDictionary dictionary, COSName flagType) | |
38 | 40 | { |
41 | this.document = document; | |
39 | 42 | createPane(dictionary, flagType); |
40 | 43 | } |
41 | 44 | |
78 | 81 | } |
79 | 82 | if (COSName.SIG_FLAGS.equals(flagType)) |
80 | 83 | { |
81 | flag = new SigFlag(dictionary); | |
84 | flag = new SigFlag(document, dictionary); | |
82 | 85 | view = new FlagBitsPaneView( |
83 | 86 | flag.getFlagType(), flag.getFlagValue(), flag.getFlagBits(), flag.getColumnNames()); |
84 | 87 | } |
18 | 18 | |
19 | 19 | import org.apache.pdfbox.cos.COSDictionary; |
20 | 20 | import org.apache.pdfbox.cos.COSName; |
21 | import org.apache.pdfbox.pdmodel.PDDocument; | |
21 | 22 | import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; |
22 | 23 | |
23 | 24 | /** |
27 | 28 | */ |
28 | 29 | public class SigFlag extends Flag |
29 | 30 | { |
30 | private final COSDictionary acroformDictionary; | |
31 | private final PDDocument document; | |
32 | private final COSDictionary acroFormDictionary; | |
31 | 33 | |
32 | 34 | /** |
33 | 35 | * Constructor |
34 | 36 | * |
35 | 37 | * @param acroFormDictionary COSDictionary instance. |
36 | 38 | */ |
37 | SigFlag(COSDictionary acroFormDictionary) | |
39 | SigFlag(PDDocument document, COSDictionary acroFormDictionary) | |
38 | 40 | { |
39 | acroformDictionary = acroFormDictionary; | |
41 | this.document = document; | |
42 | this.acroFormDictionary = acroFormDictionary; | |
40 | 43 | } |
41 | 44 | |
42 | 45 | @Override |
48 | 51 | @Override |
49 | 52 | String getFlagValue() |
50 | 53 | { |
51 | return "Flag value: " + acroformDictionary.getInt(COSName.SIG_FLAGS); | |
54 | return "Flag value: " + acroFormDictionary.getInt(COSName.SIG_FLAGS); | |
52 | 55 | } |
53 | 56 | |
54 | 57 | @Override |
55 | 58 | Object[][] getFlagBits() |
56 | 59 | { |
57 | PDAcroForm acroForm = new PDAcroForm(null, acroformDictionary); | |
60 | PDAcroForm acroForm = new PDAcroForm(document, acroFormDictionary); | |
58 | 61 | return new Object[][]{ |
59 | 62 | new Object[]{1, "SignaturesExist", acroForm.isSignaturesExist()}, |
60 | 63 | new Object[]{2, "AppendOnly", acroForm.isAppendOnly()}, |
164 | 164 | |
165 | 165 | private JPanel createHeaderPanel(List<String> availableFilters, String i, ActionListener actionListener) |
166 | 166 | { |
167 | JComboBox<String> filters = new JComboBox<String>(new Vector<String>(availableFilters)); | |
167 | JComboBox filters = new JComboBox(new Vector<String>(availableFilters)); | |
168 | 168 | filters.setSelectedItem(i); |
169 | 169 | filters.addActionListener(actionListener); |
170 | 170 |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.12</version> | |
25 | <version>2.0.13</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 | |
67 | 67 | <Embed-Transitive>true</Embed-Transitive> |
68 | 68 | <Embed-Dependency>*;scope=provided;inline=org/apache/**|org/bouncycastle/**|META-INF/services/**</Embed-Dependency> |
69 | 69 | <Bundle-DocURL>${project.url}</Bundle-DocURL> |
70 | <Import-Package>!junit.framework,!junit.textui,javax.*;resolution:=optional,org.apache.avalon.framework.logger;resolution:=optional,org.apache.log;resolution:=optional,org.apache.log4j;resolution:=optional,*</Import-Package> | |
70 | <Import-Package>!junit.framework,!junit.textui,javax.*;resolution:=optional,org.apache.avalon.framework.logger;resolution:=optional,org.apache.log;resolution:=optional,*</Import-Package> | |
71 | 71 | <Main-Class>org.apache.pdfbox.debugger.PDFDebugger</Main-Class> |
72 | 72 | </instructions> |
73 | 73 | </configuration> |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.12</version> | |
25 | <version>2.0.13</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 | |
40 | 40 | <lucene.version>4.7.2</lucene.version> |
41 | 41 | <!-- don't update this, because later versions require JDK7 --> |
42 | 42 | </properties> |
43 | ||
44 | <profiles> | |
45 | <profile> | |
46 | <activation> | |
47 | <jdk>[11,)</jdk> | |
48 | </activation> | |
49 | <dependencies> | |
50 | <dependency> | |
51 | <groupId>javax.xml.bind</groupId> | |
52 | <artifactId>jaxb-api</artifactId> | |
53 | <scope>provided</scope> | |
54 | </dependency> | |
55 | <dependency> | |
56 | <groupId>javax.activation</groupId> | |
57 | <artifactId>activation</artifactId> | |
58 | <scope>provided</scope> | |
59 | </dependency> | |
60 | </dependencies> | |
61 | </profile> | |
62 | </profiles> | |
43 | 63 | |
44 | 64 | <dependencies> |
45 | 65 | <dependency> |
20 | 20 | |
21 | 21 | import org.apache.pdfbox.pdmodel.PDDocument; |
22 | 22 | import org.apache.pdfbox.pdmodel.PDPage; |
23 | import org.apache.pdfbox.pdmodel.PageMode; | |
23 | 24 | import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination; |
24 | 25 | import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageFitWidthDestination; |
25 | 26 | import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline; |
83 | 84 | } |
84 | 85 | pagesOutline.openNode(); |
85 | 86 | outline.openNode(); |
87 | ||
88 | // optional: show the outlines when opening the file | |
89 | document.getDocumentCatalog().setPageMode(PageMode.USE_OUTLINES); | |
86 | 90 | |
87 | 91 | document.save( args[1] ); |
88 | 92 | } |
+5
-10
26 | 26 | import java.security.cert.Certificate; |
27 | 27 | import java.security.cert.CertificateException; |
28 | 28 | import java.security.cert.X509Certificate; |
29 | import java.util.ArrayList; | |
30 | 29 | import java.util.Arrays; |
31 | 30 | import java.util.Enumeration; |
32 | import java.util.List; | |
33 | 31 | import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; |
34 | import org.bouncycastle.cert.X509CertificateHolder; | |
35 | 32 | import org.bouncycastle.cert.jcajce.JcaCertStore; |
36 | 33 | import org.bouncycastle.cms.CMSException; |
37 | 34 | import org.bouncycastle.cms.CMSSignedData; |
41 | 38 | import org.bouncycastle.operator.OperatorCreationException; |
42 | 39 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; |
43 | 40 | import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; |
44 | import org.bouncycastle.util.Store; | |
45 | 41 | |
46 | 42 | public abstract class CreateSignatureBase implements SignatureInterface |
47 | 43 | { |
86 | 82 | { |
87 | 83 | // avoid expired certificate |
88 | 84 | ((X509Certificate) cert).checkValidity(); |
85 | ||
86 | SigUtils.checkCertificateUsage((X509Certificate) cert); | |
89 | 87 | } |
90 | 88 | break; |
91 | 89 | } |
129 | 127 | // cannot be done private (interface) |
130 | 128 | try |
131 | 129 | { |
132 | List<Certificate> certList = new ArrayList<Certificate>(); | |
133 | certList.addAll(Arrays.asList(certificateChain)); | |
134 | Store certs = new JcaCertStore(certList); | |
135 | 130 | CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); |
136 | org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(certificateChain[0].getEncoded()); | |
131 | X509Certificate cert = (X509Certificate) certificateChain[0]; | |
137 | 132 | ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey); |
138 | gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, new X509CertificateHolder(cert))); | |
139 | gen.addCertificates(certs); | |
133 | gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, cert)); | |
134 | gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain))); | |
140 | 135 | CMSProcessableInputStream msg = new CMSProcessableInputStream(content); |
141 | 136 | CMSSignedData signedData = gen.generate(msg, false); |
142 | 137 | if (tsaUrl != null && tsaUrl.length() > 0) |
19 | 19 | import java.io.File; |
20 | 20 | import java.io.FileInputStream; |
21 | 21 | import java.io.IOException; |
22 | import java.security.InvalidKeyException; | |
22 | import java.security.GeneralSecurityException; | |
23 | 23 | import java.security.MessageDigest; |
24 | 24 | import java.security.NoSuchAlgorithmException; |
25 | import java.security.NoSuchProviderException; | |
26 | import java.security.PublicKey; | |
27 | import java.security.SignatureException; | |
25 | import java.security.Security; | |
28 | 26 | import java.security.cert.Certificate; |
29 | 27 | import java.security.cert.CertificateException; |
28 | import java.security.cert.CertificateExpiredException; | |
30 | 29 | import java.security.cert.CertificateFactory; |
30 | import java.security.cert.CertificateNotYetValidException; | |
31 | 31 | import java.security.cert.X509Certificate; |
32 | 32 | import java.text.SimpleDateFormat; |
33 | import java.util.Arrays; | |
33 | 34 | import java.util.Collection; |
35 | import java.util.Date; | |
36 | import java.util.HashSet; | |
37 | import java.util.Set; | |
34 | 38 | import org.apache.pdfbox.cos.COSArray; |
35 | 39 | import org.apache.pdfbox.cos.COSBase; |
36 | ||
37 | 40 | import org.apache.pdfbox.cos.COSDictionary; |
38 | 41 | import org.apache.pdfbox.cos.COSInputStream; |
39 | 42 | import org.apache.pdfbox.cos.COSName; |
40 | 43 | import org.apache.pdfbox.cos.COSObject; |
41 | 44 | import org.apache.pdfbox.cos.COSStream; |
42 | 45 | import org.apache.pdfbox.cos.COSString; |
46 | import org.apache.pdfbox.examples.signature.cert.CertificateVerificationException; | |
47 | import org.apache.pdfbox.examples.signature.cert.CertificateVerifier; | |
43 | 48 | import org.apache.pdfbox.io.IOUtils; |
44 | 49 | import org.apache.pdfbox.pdmodel.PDDocument; |
45 | 50 | import org.apache.pdfbox.pdmodel.PDDocumentCatalog; |
51 | import org.apache.pdfbox.pdmodel.encryption.SecurityProvider; | |
46 | 52 | import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; |
47 | 53 | import org.apache.pdfbox.util.Hex; |
54 | import org.bouncycastle.asn1.ASN1Object; | |
55 | import org.bouncycastle.asn1.cms.Attribute; | |
56 | import org.bouncycastle.asn1.cms.AttributeTable; | |
57 | import org.bouncycastle.asn1.cms.CMSAttributes; | |
58 | import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; | |
59 | import org.bouncycastle.asn1.x509.Time; | |
48 | 60 | import org.bouncycastle.cert.X509CertificateHolder; |
61 | import org.bouncycastle.cert.jcajce.JcaCertStore; | |
49 | 62 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; |
50 | 63 | import org.bouncycastle.cms.CMSException; |
51 | 64 | import org.bouncycastle.cms.CMSProcessable; |
52 | 65 | import org.bouncycastle.cms.CMSProcessableByteArray; |
53 | 66 | import org.bouncycastle.cms.CMSSignedData; |
54 | 67 | import org.bouncycastle.cms.SignerInformation; |
68 | import org.bouncycastle.cms.SignerInformationVerifier; | |
55 | 69 | import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; |
56 | 70 | import org.bouncycastle.operator.OperatorCreationException; |
57 | 71 | import org.bouncycastle.tsp.TSPException; |
58 | 72 | import org.bouncycastle.tsp.TimeStampToken; |
59 | 73 | import org.bouncycastle.util.Selector; |
60 | 74 | import org.bouncycastle.util.Store; |
61 | import org.bouncycastle.util.StoreException; | |
62 | 75 | |
63 | 76 | /** |
64 | * This will read a document from the filesystem, decrypt it and do something with the signature. | |
77 | * This will get the signature(s) from the document, do some verifications and | |
78 | * show the signature(s) and the certificates. This is a complex topic - the | |
79 | * code here is an example and not a production-ready solution. | |
65 | 80 | * |
66 | 81 | * @author Ben Litchfield |
67 | 82 | */ |
68 | 83 | public final class ShowSignature |
69 | 84 | { |
70 | 85 | private final SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); |
71 | ||
86 | ||
72 | 87 | private ShowSignature() |
73 | 88 | { |
74 | 89 | } |
79 | 94 | * @param args The command-line arguments. |
80 | 95 | * |
81 | 96 | * @throws IOException If there is an error reading the file. |
82 | * @throws CertificateException | |
83 | * @throws java.security.NoSuchAlgorithmException | |
84 | * @throws java.security.NoSuchProviderException | |
85 | 97 | * @throws org.bouncycastle.tsp.TSPException |
98 | * @throws java.security.GeneralSecurityException | |
99 | * @throws org.apache.pdfbox.examples.signature.cert.CertificateVerificationException | |
86 | 100 | */ |
87 | public static void main(String[] args) throws IOException, CertificateException, | |
88 | NoSuchAlgorithmException, | |
89 | NoSuchProviderException, | |
90 | TSPException | |
91 | { | |
101 | public static void main(String[] args) throws IOException, TSPException, GeneralSecurityException, | |
102 | CertificateVerificationException | |
103 | { | |
104 | // register BouncyCastle provider, needed for "exotic" algorithms | |
105 | Security.addProvider(SecurityProvider.getProvider()); | |
106 | ||
92 | 107 | ShowSignature show = new ShowSignature(); |
93 | 108 | show.showSignature( args ); |
94 | 109 | } |
95 | 110 | |
96 | private void showSignature(String[] args) throws IOException, CertificateException, | |
97 | NoSuchAlgorithmException, | |
98 | NoSuchProviderException, | |
99 | TSPException | |
111 | private void showSignature(String[] args) throws IOException, TSPException, GeneralSecurityException, | |
112 | CertificateVerificationException | |
100 | 113 | { |
101 | 114 | if( args.length != 2 ) |
102 | 115 | { |
168 | 181 | subFilter.equals("ETSI.CAdES.detached")) |
169 | 182 | { |
170 | 183 | verifyPKCS7(buf, contents, sig); |
171 | ||
172 | //TODO check certificate chain, revocation lists, timestamp... | |
173 | 184 | } |
174 | 185 | else if (subFilter.equals("adbe.pkcs7.sha1")) |
175 | 186 | { |
182 | 193 | |
183 | 194 | byte[] hash = MessageDigest.getInstance("SHA1").digest(buf); |
184 | 195 | verifyPKCS7(hash, contents, sig); |
185 | ||
186 | //TODO check certificate chain, revocation lists, timestamp... | |
187 | 196 | } |
188 | 197 | else if (subFilter.equals("adbe.x509.rsa_sha1")) |
189 | 198 | { |
190 | 199 | // example: PDFBOX-2693.pdf |
191 | 200 | COSString certString = (COSString) sigDict.getDictionaryObject(COSName.CERT); |
201 | //TODO this could also be an array. | |
192 | 202 | if (certString == null) |
193 | 203 | { |
194 | 204 | System.err.println("The /Cert certificate string is missing in the signature dictionary"); |
199 | 209 | ByteArrayInputStream certStream = new ByteArrayInputStream(certData); |
200 | 210 | Collection<? extends Certificate> certs = factory.generateCertificates(certStream); |
201 | 211 | System.out.println("certs=" + certs); |
202 | ||
203 | //TODO verify signature | |
212 | ||
213 | X509Certificate cert = (X509Certificate) certs.iterator().next(); | |
214 | ||
215 | // to verify signature, see code at | |
216 | // https://stackoverflow.com/questions/43383859/ | |
217 | ||
218 | try | |
219 | { | |
220 | if (sig.getSignDate() != null) | |
221 | { | |
222 | cert.checkValidity(sig.getSignDate().getTime()); | |
223 | System.out.println("Certificate valid at signing time"); | |
224 | } | |
225 | else | |
226 | { | |
227 | System.err.println("Certificate cannot be verified without signing time"); | |
228 | } | |
229 | } | |
230 | catch (CertificateExpiredException ex) | |
231 | { | |
232 | System.err.println("Certificate expired at signing time"); | |
233 | } | |
234 | catch (CertificateNotYetValidException ex) | |
235 | { | |
236 | System.err.println("Certificate not yet valid at signing time"); | |
237 | } | |
238 | if (CertificateVerifier.isSelfSigned(cert)) | |
239 | { | |
240 | System.err.println("Certificate is self-signed, LOL!"); | |
241 | } | |
242 | else | |
243 | { | |
244 | System.out.println("Certificate is not self-signed"); | |
245 | ||
246 | if (sig.getSignDate() != null) | |
247 | { | |
248 | verifyCertificateChain(new JcaCertStore(certs), | |
249 | cert, | |
250 | sig.getSignDate().getTime()); | |
251 | } | |
252 | } | |
204 | 253 | } |
205 | 254 | else if (subFilter.equals("ETSI.RFC3161")) |
206 | 255 | { |
207 | TimeStampToken timeStampToken = new TimeStampToken(new CMSSignedData(contents.getBytes())); | |
208 | System.out.println("Time stamp gen time: " + timeStampToken.getTimeStampInfo().getGenTime()); | |
209 | System.out.println("Time stamp tsa name: " + timeStampToken.getTimeStampInfo().getTsa().getName()); | |
210 | ||
211 | CertificateFactory factory = CertificateFactory.getInstance("X.509"); | |
212 | ByteArrayInputStream certStream = new ByteArrayInputStream(contents.getBytes()); | |
213 | Collection<? extends Certificate> certs = factory.generateCertificates(certStream); | |
214 | System.out.println("certs=" + certs); | |
215 | ||
216 | //TODO verify signature | |
256 | // e.g. PDFBOX-1848, file_timestamped.pdf | |
257 | verifyETSIdotRFC3161(buf, contents); | |
217 | 258 | } |
218 | 259 | else |
219 | 260 | { |
244 | 285 | } |
245 | 286 | System.out.println("Analyzed: " + args[1]); |
246 | 287 | } |
288 | } | |
289 | ||
290 | private void verifyETSIdotRFC3161(byte[] buf, COSString contents) | |
291 | throws CertificateException, CMSException, IOException, OperatorCreationException, | |
292 | TSPException, NoSuchAlgorithmException, CertificateVerificationException | |
293 | { | |
294 | TimeStampToken timeStampToken = new TimeStampToken(new CMSSignedData(contents.getBytes())); | |
295 | System.out.println("Time stamp gen time: " + timeStampToken.getTimeStampInfo().getGenTime()); | |
296 | System.out.println("Time stamp tsa name: " + timeStampToken.getTimeStampInfo().getTsa().getName()); | |
297 | ||
298 | CertificateFactory factory = CertificateFactory.getInstance("X.509"); | |
299 | ByteArrayInputStream certStream = new ByteArrayInputStream(contents.getBytes()); | |
300 | Collection<? extends Certificate> certs = factory.generateCertificates(certStream); | |
301 | System.out.println("certs=" + certs); | |
302 | ||
303 | String hashAlgorithm = timeStampToken.getTimeStampInfo().getMessageImprintAlgOID().getId(); | |
304 | // compare the hash of the signed content with the hash in | |
305 | // the timestamp | |
306 | if (Arrays.equals(MessageDigest.getInstance(hashAlgorithm).digest(buf), | |
307 | timeStampToken.getTimeStampInfo().getMessageImprintDigest())) | |
308 | { | |
309 | System.out.println("ETSI.RFC3161 timestamp signature verified"); | |
310 | } | |
311 | else | |
312 | { | |
313 | System.err.println("ETSI.RFC3161 timestamp signature verification failed"); | |
314 | } | |
315 | ||
316 | X509Certificate certFromTimeStamp = (X509Certificate) certs.iterator().next(); | |
317 | SigUtils.checkTimeStampCertificateUsage(certFromTimeStamp); | |
318 | validateTimestampToken(timeStampToken); | |
319 | verifyCertificateChain(timeStampToken.getCertificates(), | |
320 | certFromTimeStamp, | |
321 | timeStampToken.getTimeStampInfo().getGenTime()); | |
247 | 322 | } |
248 | 323 | |
249 | 324 | /** |
252 | 327 | * @param byteArray the byte sequence that has been signed |
253 | 328 | * @param contents the /Contents field as a COSString |
254 | 329 | * @param sig the PDF signature (the /V dictionary) |
255 | * @throws CertificateException | |
256 | 330 | * @throws CMSException |
257 | * @throws StoreException | |
258 | 331 | * @throws OperatorCreationException |
332 | * @throws IOException | |
333 | * @throws GeneralSecurityException | |
334 | * @throws TSPException | |
259 | 335 | */ |
260 | 336 | private void verifyPKCS7(byte[] byteArray, COSString contents, PDSignature sig) |
261 | throws CMSException, CertificateException, StoreException, OperatorCreationException, | |
262 | NoSuchAlgorithmException, NoSuchProviderException | |
337 | throws CMSException, OperatorCreationException, | |
338 | IOException, GeneralSecurityException, TSPException, CertificateVerificationException | |
263 | 339 | { |
264 | 340 | // inspiration: |
265 | 341 | // http://stackoverflow.com/a/26702631/535646 |
276 | 352 | X509CertificateHolder certificateHolder = matches.iterator().next(); |
277 | 353 | X509Certificate certFromSignedData = new JcaX509CertificateConverter().getCertificate(certificateHolder); |
278 | 354 | System.out.println("certFromSignedData: " + certFromSignedData); |
279 | certFromSignedData.checkValidity(sig.getSignDate().getTime()); | |
280 | ||
281 | if (isSelfSigned(certFromSignedData)) | |
355 | ||
356 | SigUtils.checkCertificateUsage(certFromSignedData); | |
357 | ||
358 | // Embedded timestamp | |
359 | TimeStampToken timeStampToken = extractTimeStampTokenFromSignerInformation(signerInformation); | |
360 | if (timeStampToken != null) | |
361 | { | |
362 | // tested with QV_RCA1_RCA3_CPCPS_V4_11.pdf | |
363 | // https://www.quovadisglobal.com/~/media/Files/Repository/QV_RCA1_RCA3_CPCPS_V4_11.ashx | |
364 | // timeStampToken.getCertificates() only contained the local certificate and not | |
365 | // the whole chain, so use the store of the main signature. | |
366 | // (If this assumption is incorrect, then the code must be changed to merge | |
367 | // both stores, or to pass a collection) | |
368 | validateTimestampToken(timeStampToken); | |
369 | X509CertificateHolder tstCertHolder = (X509CertificateHolder) timeStampToken.getCertificates().getMatches(null).iterator().next(); | |
370 | X509Certificate certFromTimeStamp = new JcaX509CertificateConverter().getCertificate(tstCertHolder); | |
371 | verifyCertificateChain(certificatesStore, | |
372 | certFromTimeStamp, | |
373 | timeStampToken.getTimeStampInfo().getGenTime()); | |
374 | SigUtils.checkTimeStampCertificateUsage(certFromTimeStamp); | |
375 | } | |
376 | ||
377 | try | |
378 | { | |
379 | if (sig.getSignDate() != null) | |
380 | { | |
381 | certFromSignedData.checkValidity(sig.getSignDate().getTime()); | |
382 | System.out.println("Certificate valid at signing time"); | |
383 | } | |
384 | else | |
385 | { | |
386 | System.err.println("Certificate cannot be verified without signing time"); | |
387 | } | |
388 | } | |
389 | catch (CertificateExpiredException ex) | |
390 | { | |
391 | System.err.println("Certificate expired at signing time"); | |
392 | } | |
393 | catch (CertificateNotYetValidException ex) | |
394 | { | |
395 | System.err.println("Certificate not yet valid at signing time"); | |
396 | } | |
397 | ||
398 | // usually not available | |
399 | if (signerInformation.getSignedAttributes() != null) | |
400 | { | |
401 | // From SignedMailValidator.getSignatureTime() | |
402 | Attribute signingTime = signerInformation.getSignedAttributes().get(CMSAttributes.signingTime); | |
403 | if (signingTime != null) | |
404 | { | |
405 | Time timeInstance = Time.getInstance(signingTime.getAttrValues().getObjectAt(0)); | |
406 | try | |
407 | { | |
408 | certFromSignedData.checkValidity(timeInstance.getDate()); | |
409 | System.out.println("Certificate valid at signing time: " + timeInstance.getDate()); | |
410 | } | |
411 | catch (CertificateExpiredException ex) | |
412 | { | |
413 | System.err.println("Certificate expired at signing time"); | |
414 | } | |
415 | catch (CertificateNotYetValidException ex) | |
416 | { | |
417 | System.err.println("Certificate not yet valid at signing time"); | |
418 | } | |
419 | } | |
420 | } | |
421 | ||
422 | if (signerInformation.verify(new JcaSimpleSignerInfoVerifierBuilder(). | |
423 | setProvider(SecurityProvider.getProvider()).build(certFromSignedData))) | |
424 | { | |
425 | System.out.println("Signature verified"); | |
426 | } | |
427 | else | |
428 | { | |
429 | System.out.println("Signature verification failed"); | |
430 | } | |
431 | ||
432 | if (CertificateVerifier.isSelfSigned(certFromSignedData)) | |
282 | 433 | { |
283 | 434 | System.err.println("Certificate is self-signed, LOL!"); |
284 | 435 | } |
285 | 436 | else |
286 | 437 | { |
287 | 438 | System.out.println("Certificate is not self-signed"); |
288 | // todo rest of chain | |
289 | } | |
290 | ||
291 | if (signerInformation.verify(new JcaSimpleSignerInfoVerifierBuilder().build(certFromSignedData))) | |
292 | { | |
293 | System.out.println("Signature verified"); | |
294 | } | |
295 | else | |
296 | { | |
297 | System.out.println("Signature verification failed"); | |
298 | } | |
439 | ||
440 | if (sig.getSignDate() != null) | |
441 | { | |
442 | verifyCertificateChain(certificatesStore, certFromSignedData, sig.getSignDate().getTime()); | |
443 | } | |
444 | else | |
445 | { | |
446 | System.err.println("Certificate cannot be verified without signing time"); | |
447 | } | |
448 | } | |
449 | } | |
450 | ||
451 | private TimeStampToken extractTimeStampTokenFromSignerInformation(SignerInformation signerInformation) | |
452 | throws CMSException, IOException, TSPException | |
453 | { | |
454 | if (signerInformation.getUnsignedAttributes() == null) | |
455 | { | |
456 | return null; | |
457 | } | |
458 | AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes(); | |
459 | // https://stackoverflow.com/questions/1647759/how-to-validate-if-a-signed-jar-contains-a-timestamp | |
460 | Attribute attribute = unsignedAttributes.get( | |
461 | PKCSObjectIdentifiers.id_aa_signatureTimeStampToken); | |
462 | ASN1Object obj = (ASN1Object) attribute.getAttrValues().getObjectAt(0); | |
463 | CMSSignedData signedTSTData = new CMSSignedData(obj.getEncoded()); | |
464 | return new TimeStampToken(signedTSTData); | |
465 | } | |
466 | ||
467 | private void verifyCertificateChain(Store<X509CertificateHolder> certificatesStore, | |
468 | X509Certificate certFromSignedData, Date signDate) | |
469 | throws CertificateVerificationException, CertificateException | |
470 | { | |
471 | // Verify certificate chain (new since 11/2018) | |
472 | // Please post bad PDF files that succeed and | |
473 | // good PDF files that fail in | |
474 | // https://issues.apache.org/jira/browse/PDFBOX-3017 | |
475 | Collection<X509CertificateHolder> certificateHolders = certificatesStore.getMatches(null); | |
476 | Set<X509Certificate> additionalCerts = new HashSet<X509Certificate>(); | |
477 | JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); | |
478 | for (X509CertificateHolder certHolder : certificateHolders) | |
479 | { | |
480 | X509Certificate certificate = certificateConverter.getCertificate(certHolder); | |
481 | if (!certificate.equals(certFromSignedData)) | |
482 | { | |
483 | additionalCerts.add(certificate); | |
484 | } | |
485 | } | |
486 | CertificateVerifier.verifyCertificate(certFromSignedData, additionalCerts, true, signDate); | |
487 | } | |
488 | ||
489 | private void validateTimestampToken(TimeStampToken timeStampToken) | |
490 | throws IOException, CertificateException, TSPException, OperatorCreationException | |
491 | { | |
492 | // https://stackoverflow.com/questions/42114742/ | |
493 | Collection<X509CertificateHolder> tstMatches = | |
494 | timeStampToken.getCertificates().getMatches(timeStampToken.getSID()); | |
495 | X509CertificateHolder holder = tstMatches.iterator().next(); | |
496 | X509Certificate tstCert = new JcaX509CertificateConverter().getCertificate(holder); | |
497 | SignerInformationVerifier siv = new JcaSimpleSignerInfoVerifierBuilder().setProvider(SecurityProvider.getProvider()).build(tstCert); | |
498 | timeStampToken.validate(siv); | |
499 | System.out.println("TimeStampToken validated"); | |
299 | 500 | } |
300 | 501 | |
301 | 502 | /** |
357 | 558 | } |
358 | 559 | } |
359 | 560 | |
360 | // https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.1/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/CertificateVerifier.java | |
361 | ||
362 | /** | |
363 | * Checks whether given X.509 certificate is self-signed. | |
364 | */ | |
365 | private boolean isSelfSigned(X509Certificate cert) | |
366 | throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException | |
367 | { | |
368 | try | |
369 | { | |
370 | // Try to verify certificate signature with its own public key | |
371 | PublicKey key = cert.getPublicKey(); | |
372 | cert.verify(key); | |
373 | return true; | |
374 | } | |
375 | catch (SignatureException sigEx) | |
376 | { | |
377 | return false; | |
378 | } | |
379 | catch (InvalidKeyException keyEx) | |
380 | { | |
381 | return false; | |
382 | } | |
383 | } | |
384 | ||
385 | 561 | /** |
386 | 562 | * This will print a usage message. |
387 | 563 | */ |
15 | 15 | |
16 | 16 | package org.apache.pdfbox.examples.signature; |
17 | 17 | |
18 | import java.io.IOException; | |
19 | import java.security.cert.CertificateParsingException; | |
20 | import java.security.cert.X509Certificate; | |
21 | import java.util.List; | |
22 | import java.util.SortedMap; | |
23 | import java.util.TreeMap; | |
24 | import org.apache.commons.logging.Log; | |
25 | import org.apache.commons.logging.LogFactory; | |
18 | 26 | import org.apache.pdfbox.cos.COSArray; |
19 | 27 | import org.apache.pdfbox.cos.COSBase; |
20 | 28 | import org.apache.pdfbox.cos.COSDictionary; |
21 | 29 | import org.apache.pdfbox.cos.COSName; |
22 | 30 | import org.apache.pdfbox.pdmodel.PDDocument; |
23 | 31 | import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; |
32 | import org.bouncycastle.asn1.x509.KeyPurposeId; | |
24 | 33 | |
25 | 34 | /** |
26 | 35 | * Utility class for the signature / timestamp examples. |
29 | 38 | */ |
30 | 39 | public class SigUtils |
31 | 40 | { |
41 | private static final Log LOG = LogFactory.getLog(SigUtils.class); | |
42 | ||
32 | 43 | private SigUtils() |
33 | 44 | { |
34 | 45 | } |
125 | 136 | catalogDict.setNeedToBeUpdated(true); |
126 | 137 | permsDict.setNeedToBeUpdated(true); |
127 | 138 | } |
139 | ||
140 | /** | |
141 | * Log if the certificate is not valid for signature usage. Doing this | |
142 | * anyway results in Adobe Reader failing to validate the PDF. | |
143 | * | |
144 | * @param x509Certificate | |
145 | * @throws java.security.cert.CertificateParsingException | |
146 | */ | |
147 | public static void checkCertificateUsage(X509Certificate x509Certificate) | |
148 | throws CertificateParsingException | |
149 | { | |
150 | // Check whether signer certificate is "valid for usage" | |
151 | // https://stackoverflow.com/a/52765021/535646 | |
152 | // https://www.adobe.com/devnet-docs/acrobatetk/tools/DigSig/changes.html#id1 | |
153 | boolean[] keyUsage = x509Certificate.getKeyUsage(); | |
154 | if (keyUsage != null && !keyUsage[0] && !keyUsage[1]) | |
155 | { | |
156 | // (unclear what "signTransaction" is) | |
157 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.3 | |
158 | LOG.error("Certificate key usage does not include " + | |
159 | "digitalSignature nor nonRepudiation"); | |
160 | } | |
161 | List<String> extendedKeyUsage = x509Certificate.getExtendedKeyUsage(); | |
162 | if (extendedKeyUsage != null && | |
163 | !extendedKeyUsage.contains(KeyPurposeId.id_kp_emailProtection.toString()) && | |
164 | !extendedKeyUsage.contains(KeyPurposeId.id_kp_codeSigning.toString()) && | |
165 | !extendedKeyUsage.contains(KeyPurposeId.anyExtendedKeyUsage.toString()) && | |
166 | !extendedKeyUsage.contains("1.2.840.113583.1.1.5") && | |
167 | // not mentioned in Adobe document, but tolerated in practice | |
168 | !extendedKeyUsage.contains("1.3.6.1.4.1.311.10.3.12")) | |
169 | { | |
170 | LOG.error("Certificate extended key usage does not include " + | |
171 | "emailProtection, nor codeSigning, nor anyExtendedKeyUsage, " + | |
172 | "nor 'Adobe Authentic Documents Trust'"); | |
173 | } | |
174 | } | |
175 | ||
176 | /** | |
177 | * Log if the certificate is not valid for timestamping. | |
178 | * | |
179 | * @param x509Certificate | |
180 | * @throws java.security.cert.CertificateParsingException | |
181 | */ | |
182 | public static void checkTimeStampCertificateUsage(X509Certificate x509Certificate) | |
183 | throws CertificateParsingException | |
184 | { | |
185 | List<String> extendedKeyUsage = x509Certificate.getExtendedKeyUsage(); | |
186 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.12 | |
187 | if (extendedKeyUsage != null && | |
188 | !extendedKeyUsage.contains(KeyPurposeId.id_kp_timeStamping.toString())) | |
189 | { | |
190 | LOG.error("Certificate extended key usage does not include timeStamping"); | |
191 | } | |
192 | } | |
193 | ||
194 | /** | |
195 | * Log if the certificate is not valid for responding. | |
196 | * | |
197 | * @param x509Certificate | |
198 | * @throws java.security.cert.CertificateParsingException | |
199 | */ | |
200 | public static void checkResponderCertificateUsage(X509Certificate x509Certificate) | |
201 | throws CertificateParsingException | |
202 | { | |
203 | List<String> extendedKeyUsage = x509Certificate.getExtendedKeyUsage(); | |
204 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.12 | |
205 | if (extendedKeyUsage != null && | |
206 | !extendedKeyUsage.contains(KeyPurposeId.id_kp_OCSPSigning.toString())) | |
207 | { | |
208 | LOG.error("Certificate extended key usage does not include OCSP responding"); | |
209 | } | |
210 | } | |
211 | ||
212 | /** | |
213 | * Gets the last relevant signature in the document, i.e. the one with the highest offset. | |
214 | * | |
215 | * @param document to get its last signature | |
216 | * @return last signature or null when none found | |
217 | * @throws IOException | |
218 | */ | |
219 | public static PDSignature getLastRelevantSignature(PDDocument document) throws IOException | |
220 | { | |
221 | SortedMap<Integer, PDSignature> sortedMap = new TreeMap<Integer, PDSignature>(); | |
222 | for (PDSignature signature : document.getSignatureDictionaries()) | |
223 | { | |
224 | int sigOffset = signature.getByteRange()[1]; | |
225 | sortedMap.put(sigOffset, signature); | |
226 | } | |
227 | if (sortedMap.size() > 0) | |
228 | { | |
229 | PDSignature lastSignature = sortedMap.get(sortedMap.lastKey()); | |
230 | COSBase type = lastSignature.getCOSObject().getItem(COSName.TYPE); | |
231 | if (type.equals(COSName.SIG) || type.equals(COSName.DOC_TIME_STAMP)) | |
232 | { | |
233 | return lastSignature; | |
234 | } | |
235 | } | |
236 | return null; | |
237 | } | |
128 | 238 | } |
0 | /** | |
1 | * Licensed to the Apache Software Foundation (ASF) under one | |
2 | * or more contributor license agreements. See the NOTICE file | |
3 | * distributed with this work for additional information | |
4 | * regarding copyright ownership. The ASF licenses this file | |
5 | * to you under the Apache License, Version 2.0 (the | |
6 | * "License"); you may not use this file except in compliance | |
7 | * with the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, | |
12 | * software distributed under the License is distributed on an | |
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
14 | * KIND, either express or implied. See the License for the | |
15 | * specific language governing permissions and limitations | |
16 | * under the License. | |
17 | */ | |
18 | ||
19 | package org.apache.pdfbox.examples.signature.cert; | |
20 | ||
21 | import java.io.ByteArrayInputStream; | |
22 | import java.io.IOException; | |
23 | import java.io.InputStream; | |
24 | import java.net.URL; | |
25 | import java.security.PublicKey; | |
26 | import java.security.cert.CRLException; | |
27 | import java.security.cert.CertificateException; | |
28 | import java.security.cert.CertificateFactory; | |
29 | import java.security.cert.X509CRL; | |
30 | import java.security.cert.X509CRLEntry; | |
31 | import java.security.cert.X509Certificate; | |
32 | import java.util.ArrayList; | |
33 | import java.util.Date; | |
34 | import java.util.Hashtable; | |
35 | import java.util.List; | |
36 | import java.util.Set; | |
37 | ||
38 | import javax.naming.Context; | |
39 | import javax.naming.NamingException; | |
40 | import javax.naming.directory.Attribute; | |
41 | import javax.naming.directory.Attributes; | |
42 | import javax.naming.directory.DirContext; | |
43 | import javax.naming.directory.InitialDirContext; | |
44 | import org.apache.commons.logging.Log; | |
45 | import org.apache.commons.logging.LogFactory; | |
46 | import org.apache.pdfbox.pdmodel.encryption.SecurityProvider; | |
47 | ||
48 | import org.bouncycastle.asn1.ASN1InputStream; | |
49 | import org.bouncycastle.asn1.ASN1Primitive; | |
50 | import org.bouncycastle.asn1.DERIA5String; | |
51 | import org.bouncycastle.asn1.DEROctetString; | |
52 | import org.bouncycastle.asn1.x509.CRLDistPoint; | |
53 | import org.bouncycastle.asn1.x509.DistributionPoint; | |
54 | import org.bouncycastle.asn1.x509.DistributionPointName; | |
55 | import org.bouncycastle.asn1.x509.Extension; | |
56 | import org.bouncycastle.asn1.x509.GeneralName; | |
57 | import org.bouncycastle.asn1.x509.GeneralNames; | |
58 | ||
59 | /** | |
60 | * Copied from Apache CXF 2.4.9, initial version: | |
61 | * https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.9/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/ | |
62 | * | |
63 | */ | |
64 | public final class CRLVerifier | |
65 | { | |
66 | private static final Log LOG = LogFactory.getLog(CRLVerifier.class); | |
67 | ||
68 | private CRLVerifier() | |
69 | { | |
70 | } | |
71 | ||
72 | /** | |
73 | * Extracts the CRL distribution points from the certificate (if available) | |
74 | * and checks the certificate revocation status against the CRLs coming from | |
75 | * the distribution points. Supports HTTP, HTTPS, FTP and LDAP based URLs. | |
76 | * | |
77 | * @param cert the certificate to be checked for revocation | |
78 | * @param signDate the date when the signing took place | |
79 | * @param additionalCerts set of trusted root CA certificates that will be | |
80 | * used as "trust anchors" and intermediate CA certificates that will be | |
81 | * used as part of the certification chain. | |
82 | * @throws CertificateVerificationException if the certificate could not be verified | |
83 | * @throws RevokedCertificateException if the certificate is revoked | |
84 | */ | |
85 | public static void verifyCertificateCRLs(X509Certificate cert, Date signDate, | |
86 | Set<X509Certificate> additionalCerts) | |
87 | throws CertificateVerificationException, RevokedCertificateException | |
88 | { | |
89 | try | |
90 | { | |
91 | Exception firstException = null; | |
92 | List<String> crlDistributionPointsURLs = getCrlDistributionPoints(cert); | |
93 | for (String crlDistributionPointsURL : crlDistributionPointsURLs) | |
94 | { | |
95 | LOG.info("Checking distribution point URL: " + crlDistributionPointsURL); | |
96 | X509CRL crl; | |
97 | try | |
98 | { | |
99 | crl = downloadCRL(crlDistributionPointsURL); | |
100 | } | |
101 | catch (Exception ex) | |
102 | { | |
103 | // e.g. LDAP behind corporate proxy | |
104 | LOG.warn("Caught " + ex.getClass().getSimpleName() + " downloading CRL, will try next distribution point if available"); | |
105 | if (firstException == null) | |
106 | { | |
107 | firstException = ex; | |
108 | } | |
109 | continue; | |
110 | } | |
111 | ||
112 | // Verify CRL, see wikipedia: | |
113 | // "To validate a specific CRL prior to relying on it, | |
114 | // the certificate of its corresponding CA is needed" | |
115 | PublicKey issuerKey = null; | |
116 | for (X509Certificate additionalCert : additionalCerts) | |
117 | { | |
118 | if (crl.getIssuerX500Principal().equals( | |
119 | additionalCert.getSubjectX500Principal())) | |
120 | { | |
121 | issuerKey = additionalCert.getPublicKey(); | |
122 | } | |
123 | } | |
124 | if (issuerKey == null) | |
125 | { | |
126 | throw new CertificateVerificationException( | |
127 | "Certificate for " + crl.getIssuerX500Principal() + | |
128 | "not found in certificate chain, so the CRL at " + | |
129 | crlDistributionPointsURL + " could not be verified"); | |
130 | } | |
131 | crl.verify(issuerKey, SecurityProvider.getProvider().getName()); | |
132 | ||
133 | checkRevocation(crl, cert, signDate, crlDistributionPointsURL); | |
134 | ||
135 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.13 | |
136 | // If the DistributionPointName contains multiple values, | |
137 | // each name describes a different mechanism to obtain the same | |
138 | // CRL. For example, the same CRL could be available for | |
139 | // retrieval through both LDAP and HTTP. | |
140 | // | |
141 | // => thus no need to check several protocols | |
142 | return; | |
143 | } | |
144 | if (firstException != null) | |
145 | { | |
146 | throw firstException; | |
147 | } | |
148 | } | |
149 | catch (CertificateVerificationException ex) | |
150 | { | |
151 | throw ex; | |
152 | } | |
153 | catch (RevokedCertificateException ex) | |
154 | { | |
155 | throw ex; | |
156 | } | |
157 | catch (Exception ex) | |
158 | { | |
159 | throw new CertificateVerificationException( | |
160 | "Cannot verify CRL for certificate: " | |
161 | + cert.getSubjectX500Principal(), ex); | |
162 | ||
163 | } | |
164 | } | |
165 | ||
166 | /** | |
167 | * Check whether the certificate was revoked at signing time. | |
168 | * | |
169 | * @param crl certificate revocation list | |
170 | * @param cert certificate to be checked | |
171 | * @param signDate date the certificate was used for signing | |
172 | * @param crlDistributionPointsURL URL for log message or exception text | |
173 | * @throws RevokedCertificateException if the certificate was revoked at signing time | |
174 | */ | |
175 | public static void checkRevocation( | |
176 | X509CRL crl, X509Certificate cert, Date signDate, String crlDistributionPointsURL) | |
177 | throws RevokedCertificateException | |
178 | { | |
179 | X509CRLEntry revokedCRLEntry = crl.getRevokedCertificate(cert); | |
180 | if (revokedCRLEntry != null && | |
181 | revokedCRLEntry.getRevocationDate().compareTo(signDate) <= 0) | |
182 | { | |
183 | throw new RevokedCertificateException( | |
184 | "The certificate was revoked by CRL " + | |
185 | crlDistributionPointsURL + " on " + revokedCRLEntry.getRevocationDate(), | |
186 | revokedCRLEntry.getRevocationDate()); | |
187 | } | |
188 | else if (revokedCRLEntry != null) | |
189 | { | |
190 | LOG.info("The certificate was revoked after signing by CRL " + | |
191 | crlDistributionPointsURL + " on " + revokedCRLEntry.getRevocationDate()); | |
192 | } | |
193 | else | |
194 | { | |
195 | LOG.info("The certificate was not revoked by CRL " + crlDistributionPointsURL); | |
196 | } | |
197 | } | |
198 | ||
199 | /** | |
200 | * Downloads CRL from given URL. Supports http, https, ftp and ldap based URLs. | |
201 | */ | |
202 | private static X509CRL downloadCRL(String crlURL) throws IOException, | |
203 | CertificateException, CRLException, | |
204 | CertificateVerificationException, NamingException | |
205 | { | |
206 | if (crlURL.startsWith("http://") || crlURL.startsWith("https://") | |
207 | || crlURL.startsWith("ftp://")) | |
208 | { | |
209 | return downloadCRLFromWeb(crlURL); | |
210 | } | |
211 | else if (crlURL.startsWith("ldap://")) | |
212 | { | |
213 | return downloadCRLFromLDAP(crlURL); | |
214 | } | |
215 | else | |
216 | { | |
217 | throw new CertificateVerificationException( | |
218 | "Can not download CRL from certificate " | |
219 | + "distribution point: " + crlURL); | |
220 | } | |
221 | } | |
222 | ||
223 | /** | |
224 | * Downloads a CRL from given LDAP url, e.g. | |
225 | * ldap://ldap.infonotary.com/dc=identity-ca,dc=infonotary,dc=com | |
226 | */ | |
227 | private static X509CRL downloadCRLFromLDAP(String ldapURL) throws CertificateException, | |
228 | NamingException, CRLException, | |
229 | CertificateVerificationException | |
230 | { | |
231 | Hashtable<String, String> env = new Hashtable<String, String>(); | |
232 | env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); | |
233 | env.put(Context.PROVIDER_URL, ldapURL); | |
234 | ||
235 | // https://docs.oracle.com/javase/jndi/tutorial/ldap/connect/create.html | |
236 | // don't wait forever behind corporate proxy | |
237 | env.put("com.sun.jndi.ldap.connect.timeout", "1000"); | |
238 | ||
239 | DirContext ctx = new InitialDirContext(env); | |
240 | Attributes avals = ctx.getAttributes(""); | |
241 | Attribute aval = avals.get("certificateRevocationList;binary"); | |
242 | byte[] val = (byte[]) aval.get(); | |
243 | if (val == null || val.length == 0) | |
244 | { | |
245 | throw new CertificateVerificationException("Can not download CRL from: " + ldapURL); | |
246 | } | |
247 | else | |
248 | { | |
249 | InputStream inStream = new ByteArrayInputStream(val); | |
250 | CertificateFactory cf = CertificateFactory.getInstance("X.509"); | |
251 | return (X509CRL) cf.generateCRL(inStream); | |
252 | } | |
253 | } | |
254 | ||
255 | /** | |
256 | * Downloads a CRL from given HTTP/HTTPS/FTP URL, e.g. | |
257 | * http://crl.infonotary.com/crl/identity-ca.crl | |
258 | */ | |
259 | public static X509CRL downloadCRLFromWeb(String crlURL) | |
260 | throws IOException, CertificateException, CRLException | |
261 | { | |
262 | InputStream crlStream = new URL(crlURL).openStream(); | |
263 | try | |
264 | { | |
265 | return (X509CRL) CertificateFactory.getInstance("X.509").generateCRL(crlStream); | |
266 | } | |
267 | finally | |
268 | { | |
269 | crlStream.close(); | |
270 | } | |
271 | } | |
272 | ||
273 | /** | |
274 | * Extracts all CRL distribution point URLs from the "CRL Distribution | |
275 | * Point" extension in a X.509 certificate. If CRL distribution point | |
276 | * extension is unavailable, returns an empty list. | |
277 | * @param cert | |
278 | * @return List of CRL distribution point URLs. | |
279 | * @throws java.io.IOException | |
280 | */ | |
281 | public static List<String> getCrlDistributionPoints(X509Certificate cert) | |
282 | throws IOException | |
283 | { | |
284 | byte[] crldpExt = cert.getExtensionValue(Extension.cRLDistributionPoints.getId()); | |
285 | if (crldpExt == null) | |
286 | { | |
287 | return new ArrayList<String>(); | |
288 | } | |
289 | ASN1InputStream oAsnInStream = new ASN1InputStream(new ByteArrayInputStream(crldpExt)); | |
290 | ASN1Primitive derObjCrlDP = oAsnInStream.readObject(); | |
291 | DEROctetString dosCrlDP = (DEROctetString) derObjCrlDP; | |
292 | byte[] crldpExtOctets = dosCrlDP.getOctets(); | |
293 | ASN1InputStream oAsnInStream2 = new ASN1InputStream(new ByteArrayInputStream(crldpExtOctets)); | |
294 | ASN1Primitive derObj2 = oAsnInStream2.readObject(); | |
295 | CRLDistPoint distPoint = CRLDistPoint.getInstance(derObj2); | |
296 | List<String> crlUrls = new ArrayList<String>(); | |
297 | for (DistributionPoint dp : distPoint.getDistributionPoints()) | |
298 | { | |
299 | DistributionPointName dpn = dp.getDistributionPoint(); | |
300 | // Look for URIs in fullName | |
301 | if (dpn != null && dpn.getType() == DistributionPointName.FULL_NAME) | |
302 | { | |
303 | // Look for an URI | |
304 | for (GeneralName genName : GeneralNames.getInstance(dpn.getName()).getNames()) | |
305 | { | |
306 | if (genName.getTagNo() == GeneralName.uniformResourceIdentifier) | |
307 | { | |
308 | String url = DERIA5String.getInstance(genName.getName()).getString(); | |
309 | crlUrls.add(url); | |
310 | } | |
311 | } | |
312 | } | |
313 | } | |
314 | return crlUrls; | |
315 | } | |
316 | } |
+40
-0
0 | /** | |
1 | * Licensed to the Apache Software Foundation (ASF) under one | |
2 | * or more contributor license agreements. See the NOTICE file | |
3 | * distributed with this work for additional information | |
4 | * regarding copyright ownership. The ASF licenses this file | |
5 | * to you under the Apache License, Version 2.0 (the | |
6 | * "License"); you may not use this file except in compliance | |
7 | * with the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, | |
12 | * software distributed under the License is distributed on an | |
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
14 | * KIND, either express or implied. See the License for the | |
15 | * specific language governing permissions and limitations | |
16 | * under the License. | |
17 | */ | |
18 | ||
19 | package org.apache.pdfbox.examples.signature.cert; | |
20 | ||
21 | /** | |
22 | * Copied from Apache CXF 2.4.9, initial version: | |
23 | * https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.9/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/ | |
24 | * | |
25 | */ | |
26 | public class CertificateVerificationException extends Exception | |
27 | { | |
28 | private static final long serialVersionUID = 1L; | |
29 | ||
30 | public CertificateVerificationException(String message, Throwable cause) | |
31 | { | |
32 | super(message, cause); | |
33 | } | |
34 | ||
35 | public CertificateVerificationException(String message) | |
36 | { | |
37 | super(message); | |
38 | } | |
39 | } |
+65
-0
0 | /** | |
1 | * Licensed to the Apache Software Foundation (ASF) under one | |
2 | * or more contributor license agreements. See the NOTICE file | |
3 | * distributed with this work for additional information | |
4 | * regarding copyright ownership. The ASF licenses this file | |
5 | * to you under the Apache License, Version 2.0 (the | |
6 | * "License"); you may not use this file except in compliance | |
7 | * with the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, | |
12 | * software distributed under the License is distributed on an | |
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
14 | * KIND, either express or implied. See the License for the | |
15 | * specific language governing permissions and limitations | |
16 | * under the License. | |
17 | */ | |
18 | ||
19 | package org.apache.pdfbox.examples.signature.cert; | |
20 | ||
21 | import java.security.cert.PKIXCertPathBuilderResult; | |
22 | ||
23 | /** | |
24 | * Copied from Apache CXF 2.4.9, initial version: | |
25 | * https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.9/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/ | |
26 | * | |
27 | */ | |
28 | public class CertificateVerificationResult | |
29 | { | |
30 | private boolean valid; | |
31 | private PKIXCertPathBuilderResult result; | |
32 | private Throwable exception; | |
33 | ||
34 | /** | |
35 | * Constructs a certificate verification result for valid certificate by | |
36 | * given certification path. | |
37 | */ | |
38 | public CertificateVerificationResult(PKIXCertPathBuilderResult result) | |
39 | { | |
40 | this.valid = true; | |
41 | this.result = result; | |
42 | } | |
43 | ||
44 | public CertificateVerificationResult(Throwable exception) | |
45 | { | |
46 | this.valid = false; | |
47 | this.exception = exception; | |
48 | } | |
49 | ||
50 | public boolean isValid() | |
51 | { | |
52 | return valid; | |
53 | } | |
54 | ||
55 | public PKIXCertPathBuilderResult getResult() | |
56 | { | |
57 | return result; | |
58 | } | |
59 | ||
60 | public Throwable getException() | |
61 | { | |
62 | return exception; | |
63 | } | |
64 | } |
+401
-0
0 | /** | |
1 | * Licensed to the Apache Software Foundation (ASF) under one | |
2 | * or more contributor license agreements. See the NOTICE file | |
3 | * distributed with this work for additional information | |
4 | * regarding copyright ownership. The ASF licenses this file | |
5 | * to you under the Apache License, Version 2.0 (the | |
6 | * "License"); you may not use this file except in compliance | |
7 | * with the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, | |
12 | * software distributed under the License is distributed on an | |
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
14 | * KIND, either express or implied. See the License for the | |
15 | * specific language governing permissions and limitations | |
16 | * under the License. | |
17 | */ | |
18 | ||
19 | package org.apache.pdfbox.examples.signature.cert; | |
20 | ||
21 | import java.io.IOException; | |
22 | import java.security.GeneralSecurityException; | |
23 | import java.security.InvalidKeyException; | |
24 | import java.security.PublicKey; | |
25 | import java.security.SignatureException; | |
26 | import java.security.cert.CertPathBuilder; | |
27 | import java.security.cert.CertPathBuilderException; | |
28 | import java.security.cert.CertStore; | |
29 | import java.security.cert.CertificateException; | |
30 | import java.security.cert.CollectionCertStoreParameters; | |
31 | import java.security.cert.PKIXBuilderParameters; | |
32 | import java.security.cert.PKIXCertPathBuilderResult; | |
33 | import java.security.cert.TrustAnchor; | |
34 | import java.security.cert.X509CertSelector; | |
35 | import java.security.cert.X509Certificate; | |
36 | import java.util.Calendar; | |
37 | import java.util.Date; | |
38 | import java.util.Enumeration; | |
39 | import java.util.HashSet; | |
40 | import java.util.Set; | |
41 | import org.apache.commons.logging.Log; | |
42 | import org.apache.commons.logging.LogFactory; | |
43 | import org.apache.pdfbox.pdmodel.encryption.SecurityProvider; | |
44 | import org.bouncycastle.asn1.ASN1ObjectIdentifier; | |
45 | import org.bouncycastle.asn1.ASN1Sequence; | |
46 | import org.bouncycastle.asn1.DEROctetString; | |
47 | import org.bouncycastle.asn1.DERTaggedObject; | |
48 | import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; | |
49 | import org.bouncycastle.asn1.x509.Extension; | |
50 | import org.bouncycastle.asn1.x509.GeneralName; | |
51 | import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; | |
52 | import org.bouncycastle.cert.X509CertificateHolder; | |
53 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; | |
54 | import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; | |
55 | import org.bouncycastle.cert.ocsp.BasicOCSPResp; | |
56 | import org.bouncycastle.cert.ocsp.OCSPException; | |
57 | import org.bouncycastle.cert.ocsp.OCSPResp; | |
58 | ||
59 | /** | |
60 | * Copied from Apache CXF 2.4.9, initial version: | |
61 | * https://svn.apache.org/repos/asf/cxf/tags/cxf-2.4.9/distribution/src/main/release/samples/sts_issue_operation/src/main/java/demo/sts/provider/cert/ | |
62 | * | |
63 | */ | |
64 | public final class CertificateVerifier | |
65 | { | |
66 | private static final Log LOG = LogFactory.getLog(CertificateVerifier.class); | |
67 | ||
68 | private CertificateVerifier() | |
69 | { | |
70 | ||
71 | } | |
72 | ||
73 | /** | |
74 | * Attempts to build a certification chain for given certificate and to | |
75 | * verify it. Relies on a set of root CA certificates and intermediate | |
76 | * certificates that will be used for building the certification chain. The | |
77 | * verification process assumes that all self-signed certificates in the set | |
78 | * are trusted root CA certificates and all other certificates in the set | |
79 | * are intermediate certificates. | |
80 | * | |
81 | * @param cert - certificate for validation | |
82 | * @param additionalCerts - set of trusted root CA certificates that will be | |
83 | * used as "trust anchors" and intermediate CA certificates that will be | |
84 | * used as part of the certification chain. All self-signed certificates are | |
85 | * considered to be trusted root CA certificates. All the rest are | |
86 | * considered to be intermediate CA certificates. | |
87 | * @param verifySelfSignedCert true if a self-signed certificate is accepted, false if not. | |
88 | * @param signDate the date when the signing took place | |
89 | * @return the certification chain (if verification is successful) | |
90 | * @throws CertificateVerificationException - if the certification is not | |
91 | * successful (e.g. certification path cannot be built or some certificate | |
92 | * in the chain is expired or CRL checks are failed) | |
93 | */ | |
94 | public static PKIXCertPathBuilderResult verifyCertificate( | |
95 | X509Certificate cert, Set<X509Certificate> additionalCerts, | |
96 | boolean verifySelfSignedCert, Date signDate) | |
97 | throws CertificateVerificationException | |
98 | { | |
99 | try | |
100 | { | |
101 | // Check for self-signed certificate | |
102 | if (!verifySelfSignedCert && isSelfSigned(cert)) | |
103 | { | |
104 | throw new CertificateVerificationException("The certificate is self-signed."); | |
105 | } | |
106 | ||
107 | // Prepare a set of trust anchors (set of root CA certificates) | |
108 | // and a set of intermediate certificates | |
109 | Set<X509Certificate> intermediateCerts = new HashSet<X509Certificate>(); | |
110 | Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>(); | |
111 | for (X509Certificate additionalCert : additionalCerts) | |
112 | { | |
113 | if (isSelfSigned(additionalCert)) | |
114 | { | |
115 | trustAnchors.add(new TrustAnchor(additionalCert, null)); | |
116 | } | |
117 | else | |
118 | { | |
119 | intermediateCerts.add(additionalCert); | |
120 | } | |
121 | } | |
122 | ||
123 | if (trustAnchors.isEmpty()) | |
124 | { | |
125 | throw new CertificateVerificationException("No root certificate in the chain"); | |
126 | } | |
127 | ||
128 | // Attempt to build the certification chain and verify it | |
129 | PKIXCertPathBuilderResult verifiedCertChain = verifyCertificate( | |
130 | cert, trustAnchors, intermediateCerts, signDate); | |
131 | ||
132 | LOG.info("Certification chain verified successfully"); | |
133 | ||
134 | checkRevocations(cert, additionalCerts, signDate); | |
135 | ||
136 | return verifiedCertChain; | |
137 | } | |
138 | catch (CertPathBuilderException certPathEx) | |
139 | { | |
140 | throw new CertificateVerificationException( | |
141 | "Error building certification path: " | |
142 | + cert.getSubjectX500Principal(), certPathEx); | |
143 | } | |
144 | catch (CertificateVerificationException cvex) | |
145 | { | |
146 | throw cvex; | |
147 | } | |
148 | catch (Exception ex) | |
149 | { | |
150 | throw new CertificateVerificationException( | |
151 | "Error verifying the certificate: " | |
152 | + cert.getSubjectX500Principal(), ex); | |
153 | } | |
154 | } | |
155 | ||
156 | private static void checkRevocations(X509Certificate cert, | |
157 | Set<X509Certificate> additionalCerts, | |
158 | Date signDate) | |
159 | throws IOException, CertificateVerificationException, OCSPException, | |
160 | RevokedCertificateException, GeneralSecurityException | |
161 | { | |
162 | if (isSelfSigned(cert)) | |
163 | { | |
164 | // root, we're done | |
165 | return; | |
166 | } | |
167 | X509Certificate issuerCert = null; | |
168 | for (X509Certificate additionalCert : additionalCerts) | |
169 | { | |
170 | if (cert.getIssuerX500Principal().equals(additionalCert.getSubjectX500Principal())) | |
171 | { | |
172 | issuerCert = additionalCert; | |
173 | break; | |
174 | } | |
175 | } | |
176 | // issuerCert is never null here. If it hadn't been found, then there wouldn't be a | |
177 | // verifiedCertChain earlier. | |
178 | ||
179 | // Try checking the certificate through OCSP (faster than CRL) | |
180 | String ocspURL = extractOCSPURL(cert); | |
181 | if (ocspURL != null) | |
182 | { | |
183 | OcspHelper ocspHelper = new OcspHelper(cert, signDate, issuerCert, additionalCerts, ocspURL); | |
184 | try | |
185 | { | |
186 | verifyOCSP(ocspHelper, additionalCerts); | |
187 | } | |
188 | catch (IOException ex) | |
189 | { | |
190 | // happens with 021496.pdf because OCSP responder no longer exists | |
191 | LOG.warn("IOException trying OCSP, will try CRL", ex); | |
192 | CRLVerifier.verifyCertificateCRLs(cert, signDate, additionalCerts); | |
193 | } | |
194 | } | |
195 | else | |
196 | { | |
197 | LOG.info("OCSP not available, will try CRL"); | |
198 | ||
199 | // Check whether the certificate is revoked by the CRL | |
200 | // given in its CRL distribution point extension | |
201 | CRLVerifier.verifyCertificateCRLs(cert, signDate, additionalCerts); | |
202 | } | |
203 | ||
204 | // now check the issuer | |
205 | checkRevocations(issuerCert, additionalCerts, signDate); | |
206 | } | |
207 | ||
208 | /** | |
209 | * Checks whether given X.509 certificate is self-signed. | |
210 | * @param cert The X.509 certificate to check. | |
211 | * @return true if the certificate is self-signed, false if not. | |
212 | * @throws java.security.GeneralSecurityException | |
213 | */ | |
214 | public static boolean isSelfSigned(X509Certificate cert) throws GeneralSecurityException | |
215 | { | |
216 | try | |
217 | { | |
218 | // Try to verify certificate signature with its own public key | |
219 | PublicKey key = cert.getPublicKey(); | |
220 | cert.verify(key, SecurityProvider.getProvider().getName()); | |
221 | return true; | |
222 | } | |
223 | catch (SignatureException ex) | |
224 | { | |
225 | // Invalid signature --> not self-signed | |
226 | LOG.debug("Couldn't get signature information - returning false", ex); | |
227 | return false; | |
228 | } | |
229 | catch (InvalidKeyException ex) | |
230 | { | |
231 | // Invalid signature --> not self-signed | |
232 | LOG.debug("Couldn't get signature information - returning false", ex); | |
233 | return false; | |
234 | } | |
235 | catch (IOException ex) | |
236 | { | |
237 | // Invalid signature --> not self-signed | |
238 | LOG.debug("Couldn't get signature information - returning false", ex); | |
239 | return false; | |
240 | } | |
241 | } | |
242 | ||
243 | /** | |
244 | * Attempts to build a certification chain for given certificate and to | |
245 | * verify it. Relies on a set of root CA certificates (trust anchors) and a | |
246 | * set of intermediate certificates (to be used as part of the chain). | |
247 | * | |
248 | * @param cert - certificate for validation | |
249 | * @param trustAnchors - set of trust anchors | |
250 | * @param intermediateCerts - set of intermediate certificates | |
251 | * @param signDate the date when the signing took place | |
252 | * @return the certification chain (if verification is successful) | |
253 | * @throws GeneralSecurityException - if the verification is not successful | |
254 | * (e.g. certification path cannot be built or some certificate in the chain | |
255 | * is expired) | |
256 | */ | |
257 | private static PKIXCertPathBuilderResult verifyCertificate( | |
258 | X509Certificate cert, Set<TrustAnchor> trustAnchors, | |
259 | Set<X509Certificate> intermediateCerts, Date signDate) | |
260 | throws GeneralSecurityException | |
261 | { | |
262 | // Create the selector that specifies the starting certificate | |
263 | X509CertSelector selector = new X509CertSelector(); | |
264 | selector.setCertificate(cert); | |
265 | ||
266 | // Configure the PKIX certificate builder algorithm parameters | |
267 | PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustAnchors, selector); | |
268 | ||
269 | // Disable CRL checks (this is done manually as additional step) | |
270 | pkixParams.setRevocationEnabled(false); | |
271 | ||
272 | // not doing this brings | |
273 | // "SunCertPathBuilderException: unable to find valid certification path to requested target" | |
274 | // (when using -Djava.security.debug=certpath: "critical policy qualifiers present in certificate") | |
275 | // for files like 021496.pdf that have the "Adobe CDS Certificate Policy" 1.2.840.113583.1.2.1 | |
276 | // CDS = "Certified Document Services" | |
277 | // https://www.adobe.com/misc/pdfs/Adobe_CDS_CP.pdf | |
278 | pkixParams.setPolicyQualifiersRejected(false); | |
279 | // However, maybe there is still work to do: | |
280 | // "If the policyQualifiersRejected flag is set to false, it is up to the application | |
281 | // to validate all policy qualifiers in this manner in order to be PKIX compliant." | |
282 | ||
283 | pkixParams.setDate(signDate); | |
284 | ||
285 | // Specify a list of intermediate certificates | |
286 | CertStore intermediateCertStore = CertStore.getInstance("Collection", | |
287 | new CollectionCertStoreParameters(intermediateCerts)); | |
288 | pkixParams.addCertStore(intermediateCertStore); | |
289 | ||
290 | // Build and verify the certification chain | |
291 | // If this doesn't work although it should, it can be debugged | |
292 | // by starting java with -Djava.security.debug=certpath | |
293 | // see also | |
294 | // https://docs.oracle.com/javase/8/docs/technotes/guides/security/troubleshooting-security.html | |
295 | CertPathBuilder builder = CertPathBuilder.getInstance("PKIX"); | |
296 | return (PKIXCertPathBuilderResult) builder.build(pkixParams); | |
297 | } | |
298 | ||
299 | /** | |
300 | * Extract the OCSP URL from an X.509 certificate if available. | |
301 | * | |
302 | * @param cert X.509 certificate | |
303 | * @return the URL of the OCSP validation service | |
304 | * @throws IOException | |
305 | */ | |
306 | private static String extractOCSPURL(X509Certificate cert) throws IOException | |
307 | { | |
308 | byte[] authorityExtensionValue = cert.getExtensionValue(Extension.authorityInfoAccess.getId()); | |
309 | if (authorityExtensionValue != null) | |
310 | { | |
311 | // copied from CertInformationHelper.getAuthorityInfoExtensionValue() | |
312 | // DRY refactor should be done some day | |
313 | ASN1Sequence asn1Seq = (ASN1Sequence) JcaX509ExtensionUtils.parseExtensionValue(authorityExtensionValue); | |
314 | Enumeration<?> objects = asn1Seq.getObjects(); | |
315 | while (objects.hasMoreElements()) | |
316 | { | |
317 | // AccessDescription | |
318 | ASN1Sequence obj = (ASN1Sequence) objects.nextElement(); | |
319 | ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) obj.getObjectAt(0); | |
320 | // accessLocation | |
321 | DERTaggedObject location = (DERTaggedObject) obj.getObjectAt(1); | |
322 | if (oid.equals(X509ObjectIdentifiers.id_ad_ocsp) | |
323 | && location.getTagNo() == GeneralName.uniformResourceIdentifier) | |
324 | { | |
325 | DEROctetString url = (DEROctetString) location.getObject(); | |
326 | String ocspURL = new String(url.getOctets()); | |
327 | LOG.info("OCSP URL: " + ocspURL); | |
328 | return ocspURL; | |
329 | } | |
330 | } | |
331 | } | |
332 | return null; | |
333 | } | |
334 | ||
335 | /** | |
336 | * Verify whether the certificate has been revoked at signing date, and verify whether the | |
337 | * certificate of the responder has been revoked now. | |
338 | * | |
339 | * @param ocspHelper the OCSP helper. | |
340 | * @param additionalCerts | |
341 | * @throws RevokedCertificateException | |
342 | * @throws IOException | |
343 | * @throws OCSPException | |
344 | * @throws CertificateVerificationException | |
345 | */ | |
346 | private static void verifyOCSP(OcspHelper ocspHelper, Set<X509Certificate> additionalCerts) | |
347 | throws RevokedCertificateException, IOException, OCSPException, CertificateVerificationException | |
348 | { | |
349 | Date now = Calendar.getInstance().getTime(); | |
350 | OCSPResp ocspResponse; | |
351 | ocspResponse = ocspHelper.getResponseOcsp(); | |
352 | if (ocspResponse.getStatus() != OCSPResp.SUCCESSFUL) | |
353 | { | |
354 | throw new CertificateVerificationException("OCSP check not successful, status: " | |
355 | + ocspResponse.getStatus()); | |
356 | } | |
357 | LOG.info("OCSP check successful"); | |
358 | ||
359 | BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject(); | |
360 | X509Certificate ocspResponderCertificate = ocspHelper.getOcspResponderCertificate(); | |
361 | if (ocspResponderCertificate.getExtensionValue(OCSPObjectIdentifiers.id_pkix_ocsp_nocheck.getId()) != null) | |
362 | { | |
363 | // https://tools.ietf.org/html/rfc6960#section-4.2.2.2.1 | |
364 | // A CA may specify that an OCSP client can trust a responder for the | |
365 | // lifetime of the responder's certificate. The CA does so by | |
366 | // including the extension id-pkix-ocsp-nocheck. | |
367 | LOG.info("Revocation check of OCSP responder certificate skipped (id-pkix-ocsp-nocheck is set)"); | |
368 | return; | |
369 | } | |
370 | ||
371 | LOG.info("Revocation check of OCSP responder certificate"); | |
372 | Set<X509Certificate> additionalCerts2 = new HashSet<X509Certificate>(additionalCerts); | |
373 | JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); | |
374 | for (X509CertificateHolder certHolder : basicResponse.getCerts()) | |
375 | { | |
376 | try | |
377 | { | |
378 | X509Certificate cert = certificateConverter.getCertificate(certHolder); | |
379 | if (!ocspResponderCertificate.equals(cert)) | |
380 | { | |
381 | additionalCerts2.add(cert); | |
382 | } | |
383 | } | |
384 | catch (CertificateException ex) | |
385 | { | |
386 | // unlikely to happen because the certificate existed as an object | |
387 | LOG.error(ex, ex); | |
388 | } | |
389 | } | |
390 | try | |
391 | { | |
392 | checkRevocations(ocspResponderCertificate, additionalCerts2, now); | |
393 | } | |
394 | catch (GeneralSecurityException ex) | |
395 | { | |
396 | throw new CertificateVerificationException(ex.getMessage(), ex); | |
397 | } | |
398 | LOG.info("Revocation check of OCSP responder certificate done"); | |
399 | } | |
400 | } |
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.examples.signature.cert; | |
17 | ||
18 | import java.io.ByteArrayOutputStream; | |
19 | import java.io.IOException; | |
20 | import java.io.InputStream; | |
21 | import java.io.OutputStream; | |
22 | import java.net.HttpURLConnection; | |
23 | import java.net.URL; | |
24 | import java.security.MessageDigest; | |
25 | import java.security.NoSuchAlgorithmException; | |
26 | import java.security.Security; | |
27 | import java.security.cert.CertificateEncodingException; | |
28 | import java.security.cert.CertificateException; | |
29 | import java.security.cert.CertificateParsingException; | |
30 | import java.security.cert.X509Certificate; | |
31 | import java.util.Calendar; | |
32 | import java.util.Date; | |
33 | import java.util.Random; | |
34 | import java.util.Set; | |
35 | ||
36 | import org.apache.commons.logging.Log; | |
37 | import org.apache.commons.logging.LogFactory; | |
38 | import org.apache.pdfbox.examples.signature.SigUtils; | |
39 | import org.apache.pdfbox.io.IOUtils; | |
40 | import org.apache.pdfbox.pdmodel.encryption.SecurityProvider; | |
41 | import org.bouncycastle.asn1.DEROctetString; | |
42 | import org.bouncycastle.asn1.DLSequence; | |
43 | import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; | |
44 | import org.bouncycastle.asn1.ocsp.OCSPResponseStatus; | |
45 | import org.bouncycastle.asn1.ocsp.ResponderID; | |
46 | import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; | |
47 | import org.bouncycastle.asn1.x500.X500Name; | |
48 | import org.bouncycastle.asn1.x509.AlgorithmIdentifier; | |
49 | import org.bouncycastle.asn1.x509.Extension; | |
50 | import org.bouncycastle.asn1.x509.Extensions; | |
51 | import org.bouncycastle.cert.X509CertificateHolder; | |
52 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; | |
53 | import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; | |
54 | import org.bouncycastle.cert.ocsp.BasicOCSPResp; | |
55 | import org.bouncycastle.cert.ocsp.CertificateID; | |
56 | import org.bouncycastle.cert.ocsp.CertificateStatus; | |
57 | import org.bouncycastle.cert.ocsp.OCSPException; | |
58 | import org.bouncycastle.cert.ocsp.OCSPReq; | |
59 | import org.bouncycastle.cert.ocsp.OCSPReqBuilder; | |
60 | import org.bouncycastle.cert.ocsp.OCSPResp; | |
61 | import org.bouncycastle.cert.ocsp.RevokedStatus; | |
62 | import org.bouncycastle.cert.ocsp.SingleResp; | |
63 | import org.bouncycastle.operator.ContentVerifierProvider; | |
64 | import org.bouncycastle.operator.DigestCalculator; | |
65 | import org.bouncycastle.operator.OperatorCreationException; | |
66 | import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; | |
67 | ||
68 | /** | |
69 | * Helper Class for OCSP-Operations with bouncy castle. | |
70 | * | |
71 | * @author Alexis Suter | |
72 | */ | |
73 | public class OcspHelper | |
74 | { | |
75 | private static final Log LOG = LogFactory.getLog(OcspHelper.class); | |
76 | ||
77 | private final X509Certificate issuerCertificate; | |
78 | private final Date signDate; | |
79 | private final X509Certificate certificateToCheck; | |
80 | private final Set<X509Certificate> additionalCerts; | |
81 | private final String ocspUrl; | |
82 | private DEROctetString encodedNonce; | |
83 | private X509Certificate ocspResponderCertificate; | |
84 | private final JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter(); | |
85 | ||
86 | /** | |
87 | * @param checkCertificate Certificate to be OCSP-checked | |
88 | * @param signDate the date when the signing took place | |
89 | * @param issuerCertificate Certificate of the issuer | |
90 | * @param additionalCerts Set of trusted root CA certificates that will be used as "trust | |
91 | * anchors" and intermediate CA certificates that will be used as part of the certification | |
92 | * chain. All self-signed certificates are considered to be trusted root CA certificates. All | |
93 | * the rest are considered to be intermediate CA certificates. | |
94 | * @param ocspUrl where to fetch for OCSP | |
95 | */ | |
96 | public OcspHelper(X509Certificate checkCertificate, Date signDate, X509Certificate issuerCertificate, | |
97 | Set<X509Certificate> additionalCerts, String ocspUrl) | |
98 | { | |
99 | this.certificateToCheck = checkCertificate; | |
100 | this.signDate = signDate; | |
101 | this.issuerCertificate = issuerCertificate; | |
102 | this.additionalCerts = additionalCerts; | |
103 | this.ocspUrl = ocspUrl; | |
104 | } | |
105 | ||
106 | /** | |
107 | * Performs and verifies the OCSP-Request | |
108 | * | |
109 | * @return the OCSPResp, when the request was successful, else a corresponding exception will be | |
110 | * thrown. Never returns null. | |
111 | * | |
112 | * @throws IOException | |
113 | * @throws OCSPException | |
114 | * @throws RevokedCertificateException | |
115 | */ | |
116 | public OCSPResp getResponseOcsp() throws IOException, OCSPException, RevokedCertificateException | |
117 | { | |
118 | OCSPResp ocspResponse = performRequest(); | |
119 | verifyOcspResponse(ocspResponse); | |
120 | return ocspResponse; | |
121 | } | |
122 | ||
123 | /** | |
124 | * Get responder certificate. This is available after {@link #getResponseOcsp()} has been called. | |
125 | * | |
126 | * @return The certificate of the responder. | |
127 | */ | |
128 | public X509Certificate getOcspResponderCertificate() | |
129 | { | |
130 | return ocspResponderCertificate; | |
131 | } | |
132 | ||
133 | /** | |
134 | * Verifies the status and the response itself (including nonce), but not the signature. | |
135 | * | |
136 | * @param ocspResponse to be verified | |
137 | * @throws OCSPException | |
138 | * @throws RevokedCertificateException | |
139 | * @throws IOException if the default security provider can't be instantiated | |
140 | */ | |
141 | private void verifyOcspResponse(OCSPResp ocspResponse) | |
142 | throws OCSPException, RevokedCertificateException, IOException | |
143 | { | |
144 | verifyRespStatus(ocspResponse); | |
145 | ||
146 | BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject(); | |
147 | if (basicResponse != null) | |
148 | { | |
149 | ResponderID responderID = basicResponse.getResponderId().toASN1Primitive(); | |
150 | // https://tools.ietf.org/html/rfc6960#section-4.2.2.3 | |
151 | // The basic response type contains: | |
152 | // (...) | |
153 | // either the name of the responder or a hash of the responder's | |
154 | // public key as the ResponderID | |
155 | // (...) | |
156 | // The responder MAY include certificates in the certs field of | |
157 | // BasicOCSPResponse that help the OCSP client verify the responder's | |
158 | // signature. | |
159 | X500Name name = responderID.getName(); | |
160 | if (name != null) | |
161 | { | |
162 | findResponderCertificateByName(basicResponse, name); | |
163 | } | |
164 | else | |
165 | { | |
166 | byte[] keyHash = responderID.getKeyHash(); | |
167 | //TODO | |
168 | // KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key | |
169 | // -- (i.e., the SHA-1 hash of the value of the | |
170 | // -- BIT STRING subjectPublicKey [excluding | |
171 | // -- the tag, length, and number of unused | |
172 | // -- bits] in the responder's certificate) | |
173 | throw new UnsupportedOperationException("search by key hash is not implemented yet"); | |
174 | ||
175 | // how BC calculates the HeyHash: | |
176 | // see CertificateID.createCertID() | |
177 | // digCalc is a SHA1DigestCalculator | |
178 | // SubjectPublicKeyInfo info = issuerCert.getSubjectPublicKeyInfo(); | |
179 | // dgOut = digCalc.getOutputStream(); | |
180 | // dgOut.write(info.getPublicKeyData().getBytes()); | |
181 | // dgOut.close(); | |
182 | // ASN1OctetString issuerKeyHash = new DEROctetString(digCalc.getDigest()); | |
183 | } | |
184 | ||
185 | if (ocspResponderCertificate == null) | |
186 | { | |
187 | throw new OCSPException("OCSP: certificate for responder " + name + " not found"); | |
188 | } | |
189 | ||
190 | try | |
191 | { | |
192 | SigUtils.checkResponderCertificateUsage(ocspResponderCertificate); | |
193 | } | |
194 | catch (CertificateParsingException ex) | |
195 | { | |
196 | // unlikely to happen because the certificate existed as an object | |
197 | LOG.error(ex, ex); | |
198 | } | |
199 | checkOcspSignature(ocspResponderCertificate, basicResponse); | |
200 | ||
201 | boolean nonceChecked = checkNonce(basicResponse); | |
202 | ||
203 | SingleResp[] responses = basicResponse.getResponses(); | |
204 | if (responses.length == 1) | |
205 | { | |
206 | SingleResp resp = responses[0]; | |
207 | Object status = resp.getCertStatus(); | |
208 | ||
209 | if (!nonceChecked) | |
210 | { | |
211 | // https://tools.ietf.org/html/rfc5019 | |
212 | // fall back to validating the OCSPResponse based on time | |
213 | checkOcspResponseFresh(resp); | |
214 | } | |
215 | ||
216 | if (status instanceof RevokedStatus) | |
217 | { | |
218 | RevokedStatus revokedStatus = (RevokedStatus) status; | |
219 | if (revokedStatus.getRevocationTime().compareTo(signDate) <= 0) | |
220 | { | |
221 | throw new RevokedCertificateException( | |
222 | "OCSP: Certificate is revoked since " + | |
223 | revokedStatus.getRevocationTime(), | |
224 | revokedStatus.getRevocationTime()); | |
225 | } | |
226 | LOG.info("The certificate was revoked after signing by OCSP " + ocspUrl + | |
227 | " on " + revokedStatus.getRevocationTime()); | |
228 | } | |
229 | else if (status != CertificateStatus.GOOD) | |
230 | { | |
231 | throw new OCSPException("OCSP: Status of Cert is unknown"); | |
232 | } | |
233 | } | |
234 | else | |
235 | { | |
236 | throw new OCSPException( | |
237 | "OCSP: Received " + responses.length + " responses instead of 1!"); | |
238 | } | |
239 | } | |
240 | } | |
241 | ||
242 | private void findResponderCertificateByName(BasicOCSPResp basicResponse, X500Name name) | |
243 | { | |
244 | X509CertificateHolder[] certHolders = basicResponse.getCerts(); | |
245 | for (X509CertificateHolder certHolder : certHolders) | |
246 | { | |
247 | if (name.equals(certHolder.getSubject())) | |
248 | { | |
249 | try | |
250 | { | |
251 | ocspResponderCertificate = certificateConverter.getCertificate(certHolder); | |
252 | } | |
253 | catch (CertificateException ex) | |
254 | { | |
255 | // unlikely to happen because the certificate existed as an object | |
256 | LOG.error(ex, ex); | |
257 | } | |
258 | break; | |
259 | } | |
260 | } | |
261 | if (ocspResponderCertificate == null) | |
262 | { | |
263 | // DO NOT use the certificate found in additionalCerts first. One file had a | |
264 | // responder certificate in the PDF itself with SHA1withRSA algorithm, but | |
265 | // the responder delivered a different (newer, more secure) certificate | |
266 | // with SHA256withRSA (tried with QV_RCA1_RCA3_CPCPS_V4_11.pdf) | |
267 | // https://www.quovadisglobal.com/~/media/Files/Repository/QV_RCA1_RCA3_CPCPS_V4_11.ashx | |
268 | for (X509Certificate cert : additionalCerts) | |
269 | { | |
270 | X500Name certSubjectName = new X500Name(cert.getSubjectX500Principal().getName()); | |
271 | if (certSubjectName.equals(name)) | |
272 | { | |
273 | ocspResponderCertificate = cert; | |
274 | break; | |
275 | } | |
276 | } | |
277 | } | |
278 | } | |
279 | ||
280 | private void checkOcspResponseFresh(SingleResp resp) throws OCSPException | |
281 | { | |
282 | // https://tools.ietf.org/html/rfc5019 | |
283 | // Clients MUST check for the existence of the nextUpdate field and MUST | |
284 | // ensure the current time, expressed in GMT time as described in | |
285 | // Section 2.2.4, falls between the thisUpdate and nextUpdate times. If | |
286 | // the nextUpdate field is absent, the client MUST reject the response. | |
287 | ||
288 | Date curDate = Calendar.getInstance().getTime(); | |
289 | ||
290 | Date thisUpdate = resp.getThisUpdate(); | |
291 | if (thisUpdate == null) | |
292 | { | |
293 | throw new OCSPException("OCSP: thisUpdate field is missing in response (RFC 5019 2.2.4.)"); | |
294 | } | |
295 | Date nextUpdate = resp.getNextUpdate(); | |
296 | if (nextUpdate == null) | |
297 | { | |
298 | throw new OCSPException("OCSP: nextUpdate field is missing in response (RFC 5019 2.2.4.)"); | |
299 | } | |
300 | if (curDate.compareTo(thisUpdate) < 0) | |
301 | { | |
302 | LOG.error(curDate + " < " + thisUpdate); | |
303 | throw new OCSPException("OCSP: current date < thisUpdate field (RFC 5019 2.2.4.)"); | |
304 | } | |
305 | if (curDate.compareTo(nextUpdate) > 0) | |
306 | { | |
307 | LOG.error(curDate + " > " + nextUpdate); | |
308 | throw new OCSPException("OCSP: current date > nextUpdate field (RFC 5019 2.2.4.)"); | |
309 | } | |
310 | LOG.info("OCSP response is fresh"); | |
311 | } | |
312 | ||
313 | /** | |
314 | * Checks whether the OCSP response is signed by the given certificate. | |
315 | * | |
316 | * @param certificate the certificate to check the signature | |
317 | * @param basicResponse OCSP response containing the signature | |
318 | * @throws OCSPException when the signature is invalid or could not be checked | |
319 | * @throws IOException if the default security provider can't be instantiated | |
320 | */ | |
321 | private void checkOcspSignature(X509Certificate certificate, BasicOCSPResp basicResponse) | |
322 | throws OCSPException, IOException | |
323 | { | |
324 | try | |
325 | { | |
326 | ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder() | |
327 | .setProvider(SecurityProvider.getProvider()).build(certificate); | |
328 | ||
329 | if (!basicResponse.isSignatureValid(verifier)) | |
330 | { | |
331 | throw new OCSPException("OCSP-Signature is not valid!"); | |
332 | } | |
333 | } | |
334 | catch (OperatorCreationException e) | |
335 | { | |
336 | throw new OCSPException("Error checking Ocsp-Signature", e); | |
337 | } | |
338 | } | |
339 | ||
340 | /** | |
341 | * Checks if the nonce in the response matches. | |
342 | * | |
343 | * @param basicResponse Response to be checked | |
344 | * @return true if the nonce is present and matches, false if nonce is missing. | |
345 | * @throws OCSPException if the nonce is different | |
346 | */ | |
347 | private boolean checkNonce(BasicOCSPResp basicResponse) throws OCSPException | |
348 | { | |
349 | Extension nonceExt = basicResponse.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce); | |
350 | if (nonceExt != null) | |
351 | { | |
352 | DEROctetString responseNonceString = (DEROctetString) nonceExt.getExtnValue(); | |
353 | if (!responseNonceString.equals(encodedNonce)) | |
354 | { | |
355 | throw new OCSPException("Different nonce found in response!"); | |
356 | } | |
357 | else | |
358 | { | |
359 | LOG.info("Nonce is good"); | |
360 | return true; | |
361 | } | |
362 | } | |
363 | // https://tools.ietf.org/html/rfc5019 | |
364 | // Clients that opt to include a nonce in the | |
365 | // request SHOULD NOT reject a corresponding OCSPResponse solely on the | |
366 | // basis of the nonexistent expected nonce, but MUST fall back to | |
367 | // validating the OCSPResponse based on time. | |
368 | return false; | |
369 | } | |
370 | ||
371 | /** | |
372 | * Performs the OCSP-Request, with given data. | |
373 | * | |
374 | * @return the OCSPResp, that has been fetched from the ocspUrl | |
375 | * @throws IOException | |
376 | * @throws OCSPException | |
377 | */ | |
378 | private OCSPResp performRequest() throws IOException, OCSPException | |
379 | { | |
380 | OCSPReq request = generateOCSPRequest(); | |
381 | URL url = new URL(ocspUrl); | |
382 | HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(); | |
383 | try | |
384 | { | |
385 | httpConnection.setRequestProperty("Content-Type", "application/ocsp-request"); | |
386 | httpConnection.setRequestProperty("Accept", "application/ocsp-response"); | |
387 | httpConnection.setDoOutput(true); | |
388 | OutputStream out = httpConnection.getOutputStream(); | |
389 | try | |
390 | { | |
391 | out.write(request.getEncoded()); | |
392 | } | |
393 | finally | |
394 | { | |
395 | IOUtils.closeQuietly(out); | |
396 | } | |
397 | ||
398 | if (httpConnection.getResponseCode() != 200) | |
399 | { | |
400 | throw new IOException("OCSP: Could not access url, ResponseCode: " | |
401 | + httpConnection.getResponseCode()); | |
402 | } | |
403 | // Get response | |
404 | InputStream in = (InputStream) httpConnection.getContent(); | |
405 | try | |
406 | { | |
407 | return new OCSPResp(in); | |
408 | } | |
409 | finally | |
410 | { | |
411 | IOUtils.closeQuietly(in); | |
412 | } | |
413 | } | |
414 | finally | |
415 | { | |
416 | httpConnection.disconnect(); | |
417 | } | |
418 | } | |
419 | ||
420 | /** | |
421 | * Helper method to verify response status. | |
422 | * | |
423 | * @param resp OCSP response | |
424 | * @throws OCSPException if the response status is not ok | |
425 | */ | |
426 | public void verifyRespStatus(OCSPResp resp) throws OCSPException | |
427 | { | |
428 | String statusInfo = ""; | |
429 | if (resp != null) | |
430 | { | |
431 | int status = resp.getStatus(); | |
432 | switch (status) | |
433 | { | |
434 | case OCSPResponseStatus.INTERNAL_ERROR: | |
435 | statusInfo = "INTERNAL_ERROR"; | |
436 | LOG.error("An internal error occurred in the OCSP Server!"); | |
437 | break; | |
438 | case OCSPResponseStatus.MALFORMED_REQUEST: | |
439 | // This happened when the "critical" flag was used for extensions | |
440 | // on a responder known by the committer of this comment. | |
441 | statusInfo = "MALFORMED_REQUEST"; | |
442 | LOG.error("Your request did not fit the RFC 2560 syntax!"); | |
443 | break; | |
444 | case OCSPResponseStatus.SIG_REQUIRED: | |
445 | statusInfo = "SIG_REQUIRED"; | |
446 | LOG.error("Your request was not signed!"); | |
447 | break; | |
448 | case OCSPResponseStatus.TRY_LATER: | |
449 | statusInfo = "TRY_LATER"; | |
450 | LOG.error("The server was too busy to answer you!"); | |
451 | break; | |
452 | case OCSPResponseStatus.UNAUTHORIZED: | |
453 | statusInfo = "UNAUTHORIZED"; | |
454 | LOG.error("The server could not authenticate you!"); | |
455 | break; | |
456 | case OCSPResponseStatus.SUCCESSFUL: | |
457 | break; | |
458 | default: | |
459 | statusInfo = "UNKNOWN"; | |
460 | LOG.error("Unknown OCSPResponse status code! " + status); | |
461 | } | |
462 | } | |
463 | if (resp == null || resp.getStatus() != OCSPResponseStatus.SUCCESSFUL) | |
464 | { | |
465 | throw new OCSPException("OCSP response unsuccessful, status: " + statusInfo); | |
466 | } | |
467 | } | |
468 | ||
469 | /** | |
470 | * Generates an OCSP request and generates the <code>CertificateID</code>. | |
471 | * | |
472 | * @return OCSP request, ready to fetch data | |
473 | * @throws OCSPException | |
474 | * @throws IOException | |
475 | */ | |
476 | private OCSPReq generateOCSPRequest() throws OCSPException, IOException | |
477 | { | |
478 | Security.addProvider(SecurityProvider.getProvider()); | |
479 | ||
480 | // Generate the ID for the certificate we are looking for | |
481 | CertificateID certId; | |
482 | try | |
483 | { | |
484 | certId = new CertificateID(new SHA1DigestCalculator(), | |
485 | new JcaX509CertificateHolder(issuerCertificate), | |
486 | certificateToCheck.getSerialNumber()); | |
487 | } | |
488 | catch (CertificateEncodingException e) | |
489 | { | |
490 | throw new IOException("Error creating CertificateID with the Certificate encoding", e); | |
491 | } | |
492 | ||
493 | // https://tools.ietf.org/html/rfc2560#section-4.1.2 | |
494 | // Support for any specific extension is OPTIONAL. The critical flag | |
495 | // SHOULD NOT be set for any of them. | |
496 | ||
497 | Extension responseExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_response, | |
498 | false, new DLSequence(OCSPObjectIdentifiers.id_pkix_ocsp_basic).getEncoded()); | |
499 | ||
500 | Random rand = new Random(); | |
501 | byte[] nonce = new byte[16]; | |
502 | rand.nextBytes(nonce); | |
503 | encodedNonce = new DEROctetString(new DEROctetString(nonce)); | |
504 | Extension nonceExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, | |
505 | encodedNonce); | |
506 | ||
507 | OCSPReqBuilder builder = new OCSPReqBuilder(); | |
508 | builder.setRequestExtensions( | |
509 | new Extensions(new Extension[] { responseExtension, nonceExtension })); | |
510 | builder.addRequest(certId); | |
511 | return builder.build(); | |
512 | } | |
513 | ||
514 | /** | |
515 | * Class to create SHA-1 Digest, used for creation of CertificateID. | |
516 | */ | |
517 | private static class SHA1DigestCalculator implements DigestCalculator | |
518 | { | |
519 | private final ByteArrayOutputStream bOut = new ByteArrayOutputStream(); | |
520 | ||
521 | @Override | |
522 | public AlgorithmIdentifier getAlgorithmIdentifier() | |
523 | { | |
524 | return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1); | |
525 | } | |
526 | ||
527 | @Override | |
528 | public OutputStream getOutputStream() | |
529 | { | |
530 | return bOut; | |
531 | } | |
532 | ||
533 | @Override | |
534 | public byte[] getDigest() | |
535 | { | |
536 | byte[] bytes = bOut.toByteArray(); | |
537 | bOut.reset(); | |
538 | ||
539 | try | |
540 | { | |
541 | MessageDigest md = MessageDigest.getInstance("SHA-1"); | |
542 | return md.digest(bytes); | |
543 | } | |
544 | catch (NoSuchAlgorithmException e) | |
545 | { | |
546 | LOG.error("SHA-1 Algorithm not found", e); | |
547 | return null; | |
548 | } | |
549 | } | |
550 | } | |
551 | } |
+48
-0
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.examples.signature.cert; | |
17 | ||
18 | import java.util.Date; | |
19 | ||
20 | /** | |
21 | * Exception to handle a revoked Certificate explicitly | |
22 | * | |
23 | * @author Alexis Suter | |
24 | */ | |
25 | public class RevokedCertificateException extends Exception | |
26 | { | |
27 | private static final long serialVersionUID = 3543946618794126654L; | |
28 | ||
29 | private final Date revocationTime; | |
30 | ||
31 | public RevokedCertificateException(String message) | |
32 | { | |
33 | super(message); | |
34 | this.revocationTime = null; | |
35 | } | |
36 | ||
37 | public RevokedCertificateException(String message, Date revocationTime) | |
38 | { | |
39 | super(message); | |
40 | this.revocationTime = revocationTime; | |
41 | } | |
42 | ||
43 | public Date getRevocationTime() | |
44 | { | |
45 | return revocationTime; | |
46 | } | |
47 | } |
+95
-52
23 | 23 | import java.io.OutputStream; |
24 | 24 | import java.math.BigInteger; |
25 | 25 | import java.security.GeneralSecurityException; |
26 | import java.security.cert.CRLException; | |
26 | import java.security.Security; | |
27 | 27 | import java.security.cert.CertificateEncodingException; |
28 | import java.security.cert.X509CRL; | |
28 | 29 | import java.security.cert.X509Certificate; |
30 | import java.util.Calendar; | |
29 | 31 | import java.util.HashSet; |
30 | 32 | import java.util.Set; |
31 | 33 | |
34 | 36 | import org.apache.pdfbox.cos.COSArray; |
35 | 37 | import org.apache.pdfbox.cos.COSBase; |
36 | 38 | import org.apache.pdfbox.cos.COSDictionary; |
37 | import org.apache.pdfbox.cos.COSInteger; | |
38 | 39 | import org.apache.pdfbox.cos.COSName; |
39 | 40 | import org.apache.pdfbox.cos.COSStream; |
40 | 41 | import org.apache.pdfbox.cos.COSUpdateInfo; |
42 | import org.apache.pdfbox.examples.signature.SigUtils; | |
43 | import org.apache.pdfbox.examples.signature.cert.CRLVerifier; | |
44 | import org.apache.pdfbox.examples.signature.cert.CertificateVerificationException; | |
45 | import org.apache.pdfbox.examples.signature.cert.OcspHelper; | |
46 | import org.apache.pdfbox.examples.signature.cert.RevokedCertificateException; | |
41 | 47 | import org.apache.pdfbox.examples.signature.validation.CertInformationCollector.CertSignatureInformation; |
48 | import org.apache.pdfbox.io.IOUtils; | |
42 | 49 | import org.apache.pdfbox.pdmodel.PDDocument; |
43 | 50 | import org.apache.pdfbox.pdmodel.PDDocumentCatalog; |
51 | import org.apache.pdfbox.pdmodel.encryption.SecurityProvider; | |
52 | import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; | |
44 | 53 | import org.bouncycastle.cert.ocsp.BasicOCSPResp; |
45 | 54 | import org.bouncycastle.cert.ocsp.OCSPException; |
46 | 55 | import org.bouncycastle.cert.ocsp.OCSPResp; |
66 | 75 | private COSArray certs; |
67 | 76 | private PDDocument document; |
68 | 77 | private final Set<BigInteger> foundRevocationInformation = new HashSet<BigInteger>(); |
78 | private Calendar signDate; | |
69 | 79 | |
70 | 80 | /** |
71 | 81 | * Signs the given PDF file. |
90 | 100 | } |
91 | 101 | |
92 | 102 | /** |
93 | * Fetches certificate information from the last signature of the document and appends a DSS with the validation | |
94 | * information to the document. | |
95 | * | |
103 | * Fetches certificate information from the last signature of the document and appends a DSS | |
104 | * with the validation information to the document. | |
105 | * | |
96 | 106 | * @param document containing the Signature |
97 | 107 | * @param filename in file to extract signature |
98 | 108 | * @param output where to write the changed document |
101 | 111 | private void doValidation(String filename, OutputStream output) throws IOException |
102 | 112 | { |
103 | 113 | certInformationHelper = new CertInformationCollector(); |
104 | CertSignatureInformation certInfo; | |
114 | CertSignatureInformation certInfo = null; | |
105 | 115 | try |
106 | 116 | { |
107 | certInfo = certInformationHelper.getLastCertInfo(document, filename); | |
117 | PDSignature signature = SigUtils.getLastRelevantSignature(document); | |
118 | if (signature != null) | |
119 | { | |
120 | certInfo = certInformationHelper.getLastCertInfo(signature, filename); | |
121 | signDate = signature.getSignDate(); | |
122 | } | |
108 | 123 | } |
109 | 124 | catch (CertificateProccessingException e) |
110 | 125 | { |
189 | 204 | |
190 | 205 | /** |
191 | 206 | * Fetches and adds revocation information based on the certInfo to the DSS. |
192 | * | |
193 | * @param certInfo Certificate information from CertInformationHelper containing certificate chains. | |
207 | * | |
208 | * @param certInfo Certificate information from CertInformationHelper containing certificate | |
209 | * chains. | |
194 | 210 | * @throws IOException |
195 | 211 | */ |
196 | 212 | private void addRevocationData(CertSignatureInformation certInfo) throws IOException |
197 | 213 | { |
198 | 214 | COSDictionary vri = new COSDictionary(); |
199 | vriBase.setItem(COSName.getPDFName(certInfo.getSignatureHash()), vri); | |
215 | vriBase.setItem(certInfo.getSignatureHash(), vri); | |
200 | 216 | |
201 | 217 | correspondingOCSPs = new COSArray(); |
202 | 218 | correspondingCRLs = new COSArray(); |
205 | 221 | |
206 | 222 | if (correspondingOCSPs.size() > 0) |
207 | 223 | { |
208 | vri.setItem(COSName.getPDFName("OCSP"), correspondingOCSPs); | |
224 | vri.setItem("OCSP", correspondingOCSPs); | |
209 | 225 | } |
210 | 226 | if (correspondingCRLs.size() > 0) |
211 | 227 | { |
212 | vri.setItem(COSName.getPDFName("CRL"), correspondingCRLs); | |
228 | vri.setItem("CRL", correspondingCRLs); | |
213 | 229 | } |
214 | 230 | |
215 | 231 | if (certInfo.getTsaCerts() != null) |
223 | 239 | |
224 | 240 | /** |
225 | 241 | * Tries to get Revocation Data (first OCSP, else CRL) from the given Certificate Chain. |
226 | * | |
227 | * @param certInfo from which to fetch revocation data. Will work recursively through its chains. | |
242 | * | |
243 | * @param certInfo from which to fetch revocation data. Will work recursively through its | |
244 | * chains. | |
228 | 245 | * @throws IOException when failed to fetch an revocation data. |
229 | 246 | */ |
230 | 247 | private void addRevocationDataRecursive(CertSignatureInformation certInfo) throws IOException |
248 | 265 | isRevocationInfoFound = true; |
249 | 266 | } |
250 | 267 | |
251 | if (!isRevocationInfoFound) | |
268 | if (certInfo.getOcspUrl() == null && certInfo.getCrlUrl() == null) | |
269 | { | |
270 | LOG.info("No revocation information for cert " + certInfo.getCertificate().getSubjectX500Principal()); | |
271 | } | |
272 | else if (!isRevocationInfoFound) | |
252 | 273 | { |
253 | 274 | throw new IOException("Could not fetch Revocation Info for Cert: " |
254 | + certInfo.getCertificate().getSubjectDN()); | |
275 | + certInfo.getCertificate().getSubjectX500Principal()); | |
255 | 276 | } |
256 | 277 | } |
257 | 278 | |
268 | 289 | |
269 | 290 | /** |
270 | 291 | * Tries to fetch and add OCSP Data to its containers. |
271 | * | |
292 | * | |
272 | 293 | * @param certInfo the certificate info, for it to check OCSP data. |
273 | 294 | * @return true when the OCSP data has successfully been fetched and added |
274 | 295 | * @throws IOException when Certificate is revoked. |
303 | 324 | |
304 | 325 | /** |
305 | 326 | * Tries to fetch and add CRL Data to its containers. |
306 | * | |
327 | * | |
307 | 328 | * @param certInfo the certificate info, for it to check CRL data. |
308 | * @throws IOException when failed to fetch, because no validation data could be fetched for data. | |
329 | * @throws IOException when failed to fetch, because no validation data could be fetched for | |
330 | * data. | |
309 | 331 | */ |
310 | 332 | private void fetchCrlData(CertSignatureInformation certInfo) throws IOException |
311 | 333 | { |
313 | 335 | { |
314 | 336 | addCrlRevocationInfo(certInfo); |
315 | 337 | } |
316 | catch (CRLException e) | |
338 | catch (GeneralSecurityException e) | |
317 | 339 | { |
318 | 340 | LOG.warn("Failed fetching CRL", e); |
319 | 341 | throw new IOException(e); |
324 | 346 | throw new IOException(e); |
325 | 347 | } |
326 | 348 | catch (IOException e) |
349 | { | |
350 | LOG.warn("Failed fetching CRL", e); | |
351 | throw new IOException(e); | |
352 | } | |
353 | catch (CertificateVerificationException e) | |
327 | 354 | { |
328 | 355 | LOG.warn("Failed fetching CRL", e); |
329 | 356 | throw new IOException(e); |
342 | 369 | private void addOcspData(CertSignatureInformation certInfo) throws IOException, OCSPException, |
343 | 370 | CertificateProccessingException, RevokedCertificateException |
344 | 371 | { |
345 | OcspHelper ocspHelper = new OcspHelper(certInfo.getCertificate(), | |
346 | certInfo.getIssuerCertificate(), certInfo.getOcspUrl()); | |
372 | OcspHelper ocspHelper = new OcspHelper( | |
373 | certInfo.getCertificate(), | |
374 | signDate.getTime(), | |
375 | certInfo.getIssuerCertificate(), | |
376 | new HashSet<X509Certificate>(certInformationHelper.getCertificatesMap().values()), | |
377 | certInfo.getOcspUrl()); | |
347 | 378 | |
348 | 379 | OCSPResp ocspResp = ocspHelper.getResponseOcsp(); |
349 | 380 | BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResp.getResponseObject(); |
350 | 381 | certInformationHelper.addAllCertsFromHolders(basicResponse.getCerts()); |
382 | ||
383 | //TODO check revocation of responder + include in separate VRI (usually not needed). | |
384 | // See comment by mkl in PDFBOX-3017 on 21.11.2018 | |
351 | 385 | |
352 | 386 | byte[] ocspData = ocspResp.getEncoded(); |
353 | 387 | |
364 | 398 | * Fetches and adds CRL data to storage for the given Certificate. |
365 | 399 | * |
366 | 400 | * @param certInfo the certificate info, for it to check CRL data. |
367 | * @throws CRLException | |
368 | 401 | * @throws IOException |
369 | 402 | * @throws RevokedCertificateException |
403 | * @throws GeneralSecurityException | |
404 | * @throws CertificateVerificationException | |
370 | 405 | */ |
371 | 406 | private void addCrlRevocationInfo(CertSignatureInformation certInfo) |
372 | throws CRLException, IOException, RevokedCertificateException | |
373 | { | |
374 | byte[] crlData = CrlHelper.performCrlRequestAndCheck(certInfo.getCrlUrl(), | |
375 | certInfo.getCertificate()); | |
376 | COSStream crlStream = writeDataToStream(crlData); | |
407 | throws IOException, RevokedCertificateException, GeneralSecurityException, | |
408 | CertificateVerificationException | |
409 | { | |
410 | X509CRL crl = CRLVerifier.downloadCRLFromWeb(certInfo.getCrlUrl()); | |
411 | crl.verify(certInfo.getIssuerCertificate().getPublicKey(), SecurityProvider.getProvider().getName()); | |
412 | CRLVerifier.checkRevocation(crl, certInfo.getCertificate(), signDate.getTime(), certInfo.getCrlUrl()); | |
413 | COSStream crlStream = writeDataToStream(crl.getEncoded()); | |
377 | 414 | crls.add(crlStream); |
378 | 415 | if (correspondingCRLs != null) |
379 | 416 | { |
383 | 420 | } |
384 | 421 | |
385 | 422 | /** |
386 | * Adds all certs to the certs-array. Make sure, all certificates are inside the certificateStore of | |
387 | * certInformationHelper | |
388 | * | |
423 | * Adds all certs to the certs-array. Make sure, all certificates are inside the | |
424 | * certificateStore of certInformationHelper | |
425 | * | |
389 | 426 | * @throws IOException |
390 | 427 | */ |
391 | 428 | private void addAllCertsToCertArray() throws IOException |
392 | 429 | { |
393 | 430 | try |
394 | 431 | { |
395 | for (X509Certificate cert : certInformationHelper.getCertificateStore().values()) | |
432 | for (X509Certificate cert : certInformationHelper.getCertificatesMap().values()) | |
396 | 433 | { |
397 | 434 | COSStream stream = writeDataToStream(cert.getEncoded()); |
398 | 435 | certs.add(stream); |
405 | 442 | } |
406 | 443 | |
407 | 444 | /** |
408 | * Creates a FlateDecoded <code>COSStream</code> element with the given data. | |
445 | * Creates a Flate encoded <code>COSStream</code> object with the given data. | |
409 | 446 | * |
410 | * @param data to write into the element | |
411 | * @return COSStream Element, that can be added to the document | |
447 | * @param data to write into the COSStream | |
448 | * @return COSStream a COSStream object that can be added to the document | |
412 | 449 | * @throws IOException |
413 | 450 | */ |
414 | 451 | private COSStream writeDataToStream(byte[] data) throws IOException |
415 | 452 | { |
416 | 453 | COSStream stream = document.getDocument().createCOSStream(); |
417 | COSArray filters = new COSArray(); | |
418 | filters.add(COSName.FLATE_DECODE); | |
419 | ||
420 | OutputStream os = stream.createOutputStream(filters); | |
421 | os.write(data); | |
422 | os.close(); | |
423 | ||
454 | OutputStream os = null; | |
455 | try | |
456 | { | |
457 | os = stream.createOutputStream(COSName.FLATE_DECODE); | |
458 | os.write(data); | |
459 | } | |
460 | finally | |
461 | { | |
462 | IOUtils.closeQuietly(os); | |
463 | } | |
424 | 464 | return stream; |
425 | 465 | } |
426 | 466 | |
427 | 467 | /** |
428 | * Adds Extensions to the document catalog. So that the use of DSS is identified. Described in PAdES Part 4, Chapter | |
429 | * 4.4. | |
430 | * | |
468 | * Adds Extensions to the document catalog. So that the use of DSS is identified. Described in | |
469 | * PAdES Part 4, Chapter 4.4. | |
470 | * | |
431 | 471 | * @param catalog to add Extensions into |
432 | 472 | */ |
433 | 473 | private void addExtensions(PDDocumentCatalog catalog) |
434 | 474 | { |
435 | 475 | COSDictionary dssExtensions = new COSDictionary(); |
436 | 476 | dssExtensions.setDirect(true); |
437 | catalog.getCOSObject().setItem(COSName.getPDFName("Extensions"), dssExtensions); | |
477 | catalog.getCOSObject().setItem("Extensions", dssExtensions); | |
438 | 478 | |
439 | 479 | COSDictionary adbeExtension = new COSDictionary(); |
440 | 480 | adbeExtension.setDirect(true); |
441 | dssExtensions.setItem(COSName.getPDFName("ADBE"), adbeExtension); | |
442 | ||
443 | adbeExtension.setItem(COSName.getPDFName("BaseVersion"), COSName.getPDFName("1.7")); | |
444 | adbeExtension.setItem(COSName.getPDFName("ExtensionLevel"), COSInteger.get(5)); | |
445 | ||
446 | catalog.getCOSObject().setItem(COSName.getPDFName("Version"), COSName.getPDFName("1.7")); | |
481 | dssExtensions.setItem("ADBE", adbeExtension); | |
482 | ||
483 | adbeExtension.setName("BaseVersion", "1.7"); | |
484 | adbeExtension.setInt("ExtensionLevel", 5); | |
485 | ||
486 | catalog.setVersion("1.7"); | |
447 | 487 | } |
448 | 488 | |
449 | 489 | public static void main(String[] args) throws IOException, GeneralSecurityException |
453 | 493 | usage(); |
454 | 494 | System.exit(1); |
455 | 495 | } |
496 | ||
497 | // register BouncyCastle provider, needed for "exotic" algorithms | |
498 | Security.addProvider(SecurityProvider.getProvider()); | |
456 | 499 | |
457 | 500 | // add ocspInformation |
458 | 501 | AddValidationInformation addOcspInformation = new AddValidationInformation(); |
+135
-121
21 | 21 | import java.io.InputStream; |
22 | 22 | import java.math.BigInteger; |
23 | 23 | import java.net.URL; |
24 | import java.security.GeneralSecurityException; | |
25 | import java.security.InvalidKeyException; | |
26 | import java.security.NoSuchAlgorithmException; | |
27 | import java.security.NoSuchProviderException; | |
28 | import java.security.SignatureException; | |
24 | 29 | import java.security.cert.CertificateException; |
25 | 30 | import java.security.cert.CertificateFactory; |
26 | 31 | import java.security.cert.X509Certificate; |
32 | import java.util.Arrays; | |
27 | 33 | import java.util.Collection; |
28 | 34 | import java.util.HashMap; |
29 | 35 | import java.util.Map; |
30 | import java.util.SortedMap; | |
31 | import java.util.TreeMap; | |
32 | 36 | |
33 | 37 | import org.apache.commons.logging.Log; |
34 | 38 | import org.apache.commons.logging.LogFactory; |
35 | import org.apache.pdfbox.cos.COSBase; | |
36 | import org.apache.pdfbox.cos.COSName; | |
39 | import org.apache.pdfbox.examples.signature.cert.CertificateVerifier; | |
37 | 40 | import org.apache.pdfbox.io.IOUtils; |
38 | import org.apache.pdfbox.pdmodel.PDDocument; | |
41 | import org.apache.pdfbox.pdmodel.encryption.SecurityProvider; | |
39 | 42 | import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; |
40 | 43 | import org.bouncycastle.asn1.DERSequence; |
41 | 44 | import org.bouncycastle.asn1.DERSet; |
42 | 45 | import org.bouncycastle.asn1.cms.Attribute; |
43 | 46 | import org.bouncycastle.asn1.cms.AttributeTable; |
44 | 47 | import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; |
48 | import org.bouncycastle.asn1.x509.Extension; | |
45 | 49 | import org.bouncycastle.cert.X509CertificateHolder; |
46 | 50 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; |
47 | 51 | import org.bouncycastle.cms.CMSException; |
64 | 68 | { |
65 | 69 | private static final Log LOG = LogFactory.getLog(CertInformationCollector.class); |
66 | 70 | |
67 | // As described in https://tools.ietf.org/html/rfc3280.html#section-4.2.2.1 | |
68 | private static final String ID_PE_AUTHORITYINFOACCESS = "1.3.6.1.5.5.7.1.1"; | |
69 | ||
70 | // As described in https://tools.ietf.org/html/rfc3280.html#section-4.2.1.14 | |
71 | // Disable false Sonar warning for "Hardcoded IP Address ..." | |
72 | @SuppressWarnings("squid:S1313") | |
73 | private static final String ID_CE_CRLDISTRIBUTIONPOINTS = "2.5.29.31"; | |
74 | ||
75 | 71 | private static final int MAX_CERTIFICATE_CHAIN_DEPTH = 5; |
76 | 72 | |
77 | private final Map<BigInteger, X509Certificate> certificateStore = new HashMap<BigInteger, X509Certificate>(); | |
73 | private final Map<BigInteger, X509Certificate> certificatesMap = new HashMap<BigInteger, X509Certificate>(); | |
78 | 74 | |
79 | 75 | private final JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter(); |
80 | 76 | |
81 | 77 | private CertSignatureInformation rootCertInfo; |
82 | 78 | |
83 | 79 | /** |
84 | * Gets the Certificate Information of the last Signature. | |
80 | * Gets the certificate information of a signature. | |
85 | 81 | * |
86 | * @param document to get the Signature from | |
82 | * @param signature the signature of the document. | |
87 | 83 | * @param fileName of the document. |
88 | 84 | * @return the CertSignatureInformation containing all certificate information |
89 | 85 | * @throws CertificateProccessingException when there is an error processing the certificates |
90 | 86 | * @throws IOException on a data processing error |
91 | 87 | */ |
92 | public CertSignatureInformation getLastCertInfo(PDDocument document, String fileName) | |
88 | public CertSignatureInformation getLastCertInfo(PDSignature signature, String fileName) | |
93 | 89 | throws CertificateProccessingException, IOException |
94 | 90 | { |
95 | PDSignature signature = getLastRelevantSignature(document); | |
96 | if (signature != null) | |
97 | { | |
98 | FileInputStream documentInput = null; | |
99 | try | |
100 | { | |
101 | documentInput = new FileInputStream(fileName); | |
102 | byte[] docBytes = IOUtils.toByteArray(documentInput); | |
103 | byte[] signatureContent = signature.getContents(docBytes); | |
104 | return getCertInfo(signatureContent); | |
105 | } | |
106 | finally | |
107 | { | |
108 | IOUtils.closeQuietly(document); | |
109 | } | |
110 | } | |
111 | return null; | |
112 | } | |
113 | ||
114 | /** | |
115 | * Gets the last relevant signature in the document | |
116 | * | |
117 | * @param document to get its last Signature | |
118 | * @return last signature or null when none found | |
119 | * @throws IOException | |
120 | */ | |
121 | private PDSignature getLastRelevantSignature(PDDocument document) throws IOException | |
122 | { | |
123 | SortedMap<Integer, PDSignature> sortedMap = new TreeMap<Integer, PDSignature>(); | |
124 | for (PDSignature signature : document.getSignatureDictionaries()) | |
125 | { | |
126 | int sigOffset = signature.getByteRange()[1]; | |
127 | sortedMap.put(sigOffset, signature); | |
128 | } | |
129 | if (sortedMap.size() > 0) | |
130 | { | |
131 | PDSignature lastSignature = sortedMap.get(sortedMap.lastKey()); | |
132 | COSBase type = lastSignature.getCOSObject().getItem(COSName.TYPE); | |
133 | if (type.equals(COSName.SIG) || type.equals(COSName.DOC_TIME_STAMP)) | |
134 | { | |
135 | return lastSignature; | |
136 | } | |
137 | } | |
138 | return null; | |
91 | FileInputStream documentInput = null; | |
92 | try | |
93 | { | |
94 | documentInput = new FileInputStream(fileName); | |
95 | byte[] signatureContent = signature.getContents(documentInput); | |
96 | return getCertInfo(signatureContent); | |
97 | } | |
98 | finally | |
99 | { | |
100 | IOUtils.closeQuietly(documentInput); | |
101 | } | |
139 | 102 | } |
140 | 103 | |
141 | 104 | /** |
156 | 119 | try |
157 | 120 | { |
158 | 121 | CMSSignedData signedData = new CMSSignedData(signatureContent); |
159 | @SuppressWarnings("unchecked") | |
160 | 122 | Store<X509CertificateHolder> certificatesStore = signedData.getCertificates(); |
161 | 123 | |
162 | 124 | SignerInformation signerInformation = processSignerStore(certificatesStore, signedData, |
226 | 188 | * not yet practicable. |
227 | 189 | * |
228 | 190 | * @param certificatesStore To get the certificate information from. Certificates will be saved |
229 | * in the certificateStore. | |
230 | * @param signedData to get SignerInformation off | |
191 | * in certificatesMap. | |
192 | * @param signedData data from which to get the SignerInformation | |
231 | 193 | * @param certInfo where to add certificate information |
232 | * @return Signer Information of the processed certificate Store for further usage. | |
194 | * @return Signer Information of the processed certificatesStore for further usage. | |
233 | 195 | * @throws IOException on data-processing error |
234 | 196 | * @throws CertificateProccessingException on a specific error with a certificate |
235 | 197 | */ |
268 | 230 | certInfo.certificate = certificate; |
269 | 231 | |
270 | 232 | // Certificate Authority Information Access |
271 | byte[] authorityExtensionValue = certificate.getExtensionValue(ID_PE_AUTHORITYINFOACCESS); | |
233 | // As described in https://tools.ietf.org/html/rfc3280.html#section-4.2.2.1 | |
234 | byte[] authorityExtensionValue = certificate.getExtensionValue(Extension.authorityInfoAccess.getId()); | |
272 | 235 | if (authorityExtensionValue != null) |
273 | 236 | { |
274 | 237 | CertInformationHelper.getAuthorityInfoExtensionValue(authorityExtensionValue, certInfo); |
279 | 242 | getAlternativeIssuerCertificate(certInfo, maxDepth); |
280 | 243 | } |
281 | 244 | |
282 | byte[] crlExtensionValue = certificate.getExtensionValue(ID_CE_CRLDISTRIBUTIONPOINTS); | |
245 | // As described in https://tools.ietf.org/html/rfc3280.html#section-4.2.1.14 | |
246 | byte[] crlExtensionValue = certificate.getExtensionValue(Extension.cRLDistributionPoints.getId()); | |
283 | 247 | if (crlExtensionValue != null) |
284 | 248 | { |
285 | 249 | certInfo.crlUrl = CertInformationHelper.getCrlUrlFromExtensionValue(crlExtensionValue); |
286 | 250 | } |
287 | 251 | |
288 | if (CertInformationHelper.isSelfSigned(certificate)) | |
289 | { | |
290 | certInfo.isSelfSigned = true; | |
252 | try | |
253 | { | |
254 | certInfo.isSelfSigned = CertificateVerifier.isSelfSigned(certificate); | |
255 | } | |
256 | catch (GeneralSecurityException ex) | |
257 | { | |
258 | throw new CertificateProccessingException(ex); | |
291 | 259 | } |
292 | 260 | if (maxDepth <= 0 || certInfo.isSelfSigned) |
293 | 261 | { |
294 | 262 | return; |
295 | 263 | } |
296 | 264 | |
297 | for (X509Certificate issuer : certificateStore.values()) | |
298 | { | |
299 | if (CertInformationHelper.verify(certificate, issuer.getPublicKey())) | |
300 | { | |
301 | LOG.info("Found the right Issuer Cert! for Cert: " + certificate.getSubjectDN() | |
302 | + "\n" + issuer.getSubjectDN()); | |
265 | for (X509Certificate issuer : certificatesMap.values()) | |
266 | { | |
267 | if (certificate.getIssuerX500Principal().equals(issuer.getSubjectX500Principal())) | |
268 | { | |
269 | try | |
270 | { | |
271 | certificate.verify(issuer.getPublicKey(), SecurityProvider.getProvider().getName()); | |
272 | } | |
273 | catch (CertificateException ex) | |
274 | { | |
275 | throw new CertificateProccessingException(ex); | |
276 | } | |
277 | catch (NoSuchAlgorithmException ex) | |
278 | { | |
279 | throw new CertificateProccessingException(ex); | |
280 | } | |
281 | catch (InvalidKeyException ex) | |
282 | { | |
283 | throw new CertificateProccessingException(ex); | |
284 | } | |
285 | catch (SignatureException ex) | |
286 | { | |
287 | throw new CertificateProccessingException(ex); | |
288 | } | |
289 | catch (NoSuchProviderException ex) | |
290 | { | |
291 | throw new CertificateProccessingException(ex); | |
292 | } | |
293 | LOG.info("Found the right Issuer Cert! for Cert: " + certificate.getSubjectX500Principal() | |
294 | + "\n" + issuer.getSubjectX500Principal()); | |
303 | 295 | certInfo.issuerCertificate = issuer; |
304 | 296 | certInfo.certChain = new CertSignatureInformation(); |
305 | traverseChain(issuer, certInfo.certChain, --maxDepth); | |
297 | traverseChain(issuer, certInfo.certChain, maxDepth - 1); | |
306 | 298 | break; |
307 | 299 | } |
308 | 300 | } |
309 | 301 | if (certInfo.issuerCertificate == null) |
310 | 302 | { |
311 | 303 | throw new IOException( |
312 | "No Issuer Certificate found for Cert: " + certificate.getSubjectDN()); | |
304 | "No Issuer Certificate found for Cert: " + certificate.getSubjectX500Principal()); | |
313 | 305 | } |
314 | 306 | } |
315 | 307 | |
326 | 318 | private void getAlternativeIssuerCertificate(CertSignatureInformation certInfo, int maxDepth) |
327 | 319 | throws CertificateProccessingException |
328 | 320 | { |
329 | System.out.println("Get Certificate from: " + certInfo.issuerUrl); | |
321 | LOG.info("Get alternative issuer certificate from: " + certInfo.issuerUrl); | |
330 | 322 | try |
331 | 323 | { |
332 | 324 | URL certUrl = new URL(certInfo.issuerUrl); |
334 | 326 | InputStream in = certUrl.openStream(); |
335 | 327 | |
336 | 328 | X509Certificate altIssuerCert = (X509Certificate) certFactory.generateCertificate(in); |
337 | addCertToCertStore(altIssuerCert); | |
329 | addCertToCertificatesMap(altIssuerCert); | |
338 | 330 | |
339 | 331 | certInfo.alternativeCertChain = new CertSignatureInformation(); |
340 | 332 | traverseChain(altIssuerCert, certInfo.alternativeCertChain, maxDepth - 1); |
342 | 334 | } |
343 | 335 | catch (IOException e) |
344 | 336 | { |
345 | LOG.error("Error getting additional Certificate from " + certInfo.issuerUrl, e); | |
337 | LOG.error("Error getting alternative issuer certificate from " + certInfo.issuerUrl, e); | |
346 | 338 | } |
347 | 339 | catch (CertificateException e) |
348 | 340 | { |
349 | LOG.error("Error getting additional Certificate from " + certInfo.issuerUrl, e); | |
350 | } | |
351 | } | |
352 | ||
353 | /** | |
354 | * Adds the given Certificate to the certificateStore, if not yet containing. | |
341 | LOG.error("Error getting alternative issuer certificate from " + certInfo.issuerUrl, e); | |
342 | } | |
343 | } | |
344 | ||
345 | /** | |
346 | * Adds the given Certificate to the certificatesMap, if not yet containing. | |
355 | 347 | * |
356 | * @param certificate to add to the certificateStore | |
357 | */ | |
358 | private void addCertToCertStore(X509Certificate certificate) | |
359 | { | |
360 | if (!certificateStore.containsKey(certificate.getSerialNumber())) | |
361 | { | |
362 | certificateStore.put(certificate.getSerialNumber(), certificate); | |
363 | } | |
364 | } | |
365 | ||
366 | /** | |
367 | * Gets the X509Certificate out of the X509CertificateHolder | |
368 | * | |
348 | * @param certificate to add to the certificatesMap | |
349 | */ | |
350 | private void addCertToCertificatesMap(X509Certificate certificate) | |
351 | { | |
352 | if (!certificatesMap.containsKey(certificate.getSerialNumber())) | |
353 | { | |
354 | certificatesMap.put(certificate.getSerialNumber(), certificate); | |
355 | } | |
356 | } | |
357 | ||
358 | /** | |
359 | * Gets the X509Certificate out of the X509CertificateHolder and add it to certificatesMap. | |
360 | * | |
369 | 361 | * @param certificateHolder to get the certificate from |
370 | 362 | * @return a X509Certificate or <code>null</code> when there was an Error with the Certificate |
371 | 363 | * @throws CertificateProccessingException on failed conversion from X509CertificateHolder to X509Certificate |
373 | 365 | private X509Certificate getCertFromHolder(X509CertificateHolder certificateHolder) |
374 | 366 | throws CertificateProccessingException |
375 | 367 | { |
376 | if (!certificateStore.containsKey(certificateHolder.getSerialNumber())) | |
368 | //TODO getCertFromHolder violates "do one thing" rule (adds to the map and returns a certificate) | |
369 | if (!certificatesMap.containsKey(certificateHolder.getSerialNumber())) | |
377 | 370 | { |
378 | 371 | try |
379 | 372 | { |
380 | 373 | X509Certificate certificate = certConverter.getCertificate(certificateHolder); |
381 | certificateStore.put(certificate.getSerialNumber(), certificate); | |
374 | certificatesMap.put(certificate.getSerialNumber(), certificate); | |
382 | 375 | return certificate; |
383 | 376 | } |
384 | 377 | catch (CertificateException e) |
389 | 382 | } |
390 | 383 | else |
391 | 384 | { |
392 | return certificateStore.get(certificateHolder.getSerialNumber()); | |
393 | } | |
394 | } | |
395 | ||
396 | /** | |
397 | * Adds multiple Certificates out of a Collection of X509CertificateHolder into the | |
398 | * Certificate-Store. | |
385 | return certificatesMap.get(certificateHolder.getSerialNumber()); | |
386 | } | |
387 | } | |
388 | ||
389 | /** | |
390 | * Adds multiple Certificates out of a Collection of X509CertificateHolder into certificatesMap. | |
399 | 391 | * |
400 | 392 | * @param certHolders Collection of X509CertificateHolder |
401 | 393 | */ |
416 | 408 | |
417 | 409 | /** |
418 | 410 | * Gets a list of X509Certificate out of an array of X509CertificateHolder. The certificates |
419 | * will be added to the certificateStore. | |
411 | * will be added to certificatesMap. | |
420 | 412 | * |
421 | 413 | * @param certHolders Array of X509CertificateHolder |
422 | 414 | * @throws CertificateProccessingException when one of the Certificates could not be parsed. |
424 | 416 | public void addAllCertsFromHolders(X509CertificateHolder[] certHolders) |
425 | 417 | throws CertificateProccessingException |
426 | 418 | { |
427 | for (X509CertificateHolder certHolder : certHolders) | |
428 | { | |
429 | getCertFromHolder(certHolder); | |
430 | } | |
431 | } | |
432 | ||
433 | /** | |
434 | * Get the certificate store of all processed certificates until now. | |
419 | addAllCerts(Arrays.asList(certHolders)); | |
420 | } | |
421 | ||
422 | /** | |
423 | * Traverse the OCSP certificate. | |
424 | * | |
425 | * @param certHolder | |
426 | * @return | |
427 | * @throws CertificateProccessingException | |
428 | */ | |
429 | CertSignatureInformation getOCSPCertInfo(X509CertificateHolder certHolder) throws CertificateProccessingException | |
430 | { | |
431 | try | |
432 | { | |
433 | CertSignatureInformation certSignatureInformation = new CertSignatureInformation(); | |
434 | traverseChain(certConverter.getCertificate(certHolder), certSignatureInformation, MAX_CERTIFICATE_CHAIN_DEPTH); | |
435 | return certSignatureInformation; | |
436 | } | |
437 | catch (CertificateException ex) | |
438 | { | |
439 | throw new CertificateProccessingException(ex); | |
440 | } | |
441 | catch (IOException ex) | |
442 | { | |
443 | throw new CertificateProccessingException(ex); | |
444 | } | |
445 | } | |
446 | ||
447 | /** | |
448 | * Get the map of all processed certificates until now. | |
435 | 449 | * |
436 | 450 | * @return a map of serial numbers to certificates. |
437 | 451 | */ |
438 | public Map<BigInteger, X509Certificate> getCertificateStore() | |
439 | { | |
440 | return certificateStore; | |
452 | public Map<BigInteger, X509Certificate> getCertificatesMap() | |
453 | { | |
454 | return certificatesMap; | |
441 | 455 | } |
442 | 456 | |
443 | 457 | /** |
+0
-73
16 | 16 | package org.apache.pdfbox.examples.signature.validation; |
17 | 17 | |
18 | 18 | import java.io.IOException; |
19 | import java.security.InvalidKeyException; | |
20 | 19 | import java.security.MessageDigest; |
21 | 20 | import java.security.NoSuchAlgorithmException; |
22 | import java.security.NoSuchProviderException; | |
23 | import java.security.PublicKey; | |
24 | import java.security.SignatureException; | |
25 | import java.security.cert.CertificateException; | |
26 | import java.security.cert.X509Certificate; | |
27 | 21 | import java.util.Enumeration; |
28 | 22 | |
29 | 23 | import org.apache.commons.logging.Log; |
65 | 59 | LOG.error("No SHA-1 Algorithm found", e); |
66 | 60 | } |
67 | 61 | return null; |
68 | } | |
69 | ||
70 | /** | |
71 | * Checks whether the given certificate is self-signed (root). | |
72 | * | |
73 | * @param cert to be checked | |
74 | * @return true when it is a self-signed certificate | |
75 | * @throws CertificateProccessingException containing the cause, on multiple exception with the given data | |
76 | */ | |
77 | public static boolean isSelfSigned(X509Certificate cert) throws CertificateProccessingException | |
78 | { | |
79 | return verify(cert, cert.getPublicKey()); | |
80 | } | |
81 | ||
82 | /** | |
83 | * Verifies whether the certificate is signed by the given public key. Can be done to check | |
84 | * signature chain. Idea and code are from Julius Musseau at: | |
85 | * https://stackoverflow.com/a/10822177/2497581 | |
86 | * | |
87 | * @param cert Child certificate to check | |
88 | * @param key Fathers public key to check | |
89 | * @return true when the certificate is signed by the public key | |
90 | * @throws CertificateProccessingException containing the cause, on multiple exception with the | |
91 | * given data | |
92 | */ | |
93 | public static boolean verify(X509Certificate cert, PublicKey key) | |
94 | throws CertificateProccessingException | |
95 | { | |
96 | try | |
97 | { | |
98 | String sigAlg = cert.getSigAlgName(); | |
99 | String keyAlg = key.getAlgorithm(); | |
100 | sigAlg = sigAlg != null ? sigAlg.trim().toUpperCase() : ""; | |
101 | keyAlg = keyAlg != null ? keyAlg.trim().toUpperCase() : ""; | |
102 | if (keyAlg.length() >= 2 && sigAlg.endsWith(keyAlg)) | |
103 | { | |
104 | try | |
105 | { | |
106 | cert.verify(key); | |
107 | return true; | |
108 | } | |
109 | catch (SignatureException se) | |
110 | { | |
111 | return false; | |
112 | } | |
113 | } | |
114 | else | |
115 | { | |
116 | return false; | |
117 | } | |
118 | } | |
119 | catch (InvalidKeyException e) | |
120 | { | |
121 | throw new CertificateProccessingException(e); | |
122 | } | |
123 | catch (CertificateException e) | |
124 | { | |
125 | throw new CertificateProccessingException(e); | |
126 | } | |
127 | catch (NoSuchAlgorithmException e) | |
128 | { | |
129 | throw new CertificateProccessingException(e); | |
130 | } | |
131 | catch (NoSuchProviderException e) | |
132 | { | |
133 | throw new CertificateProccessingException(e); | |
134 | } | |
135 | 62 | } |
136 | 63 | |
137 | 64 | /** |
+0
-77
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.examples.signature.validation; | |
17 | ||
18 | import java.io.IOException; | |
19 | import java.net.HttpURLConnection; | |
20 | import java.net.URL; | |
21 | import java.security.cert.CRLException; | |
22 | import java.security.cert.X509CRL; | |
23 | import java.security.cert.X509Certificate; | |
24 | ||
25 | import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory; | |
26 | ||
27 | /** | |
28 | * Helper class to get CRL (Certificate revocation list) from given crlUrl and check if Certificate | |
29 | * has been revoked. | |
30 | * | |
31 | * @author Alexis Suter | |
32 | */ | |
33 | public final class CrlHelper | |
34 | { | |
35 | private CrlHelper() | |
36 | { | |
37 | } | |
38 | ||
39 | /** | |
40 | * Performs the CRL-Request and checks if the given certificate has been revoked. | |
41 | * | |
42 | * @param crlUrl to get the CRL from | |
43 | * @param cert to be checked if it is inside the CRL | |
44 | * @return CRL-Response; might be very big depending on the issuer. | |
45 | * @throws CRLException if an Error occurred getting the CRL, or parsing it. | |
46 | * @throws RevokedCertificateException | |
47 | */ | |
48 | public static byte[] performCrlRequestAndCheck(String crlUrl, X509Certificate cert) | |
49 | throws CRLException, RevokedCertificateException | |
50 | { | |
51 | try | |
52 | { | |
53 | URL url = new URL(crlUrl); | |
54 | ||
55 | HttpURLConnection con = (HttpURLConnection) url.openConnection(); | |
56 | if (con.getResponseCode() != 200) | |
57 | { | |
58 | throw new IOException("Unsuccessful CRL request. Status: " + con.getResponseCode() | |
59 | + " Url: " + crlUrl); | |
60 | } | |
61 | ||
62 | CertificateFactory certFac = new CertificateFactory(); | |
63 | X509CRL crl = (X509CRL) certFac.engineGenerateCRL(con.getInputStream()); | |
64 | if (crl.isRevoked(cert)) | |
65 | { | |
66 | throw new RevokedCertificateException("The Certificate was found on the CRL and is revoked!"); | |
67 | } | |
68 | return crl.getEncoded(); | |
69 | } | |
70 | catch (IOException e) | |
71 | { | |
72 | throw new CRLException(e); | |
73 | } | |
74 | } | |
75 | ||
76 | } |
+0
-353
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.examples.signature.validation; | |
17 | ||
18 | import java.io.ByteArrayOutputStream; | |
19 | import java.io.IOException; | |
20 | import java.io.InputStream; | |
21 | import java.io.OutputStream; | |
22 | import java.net.HttpURLConnection; | |
23 | import java.net.URL; | |
24 | import java.security.MessageDigest; | |
25 | import java.security.NoSuchAlgorithmException; | |
26 | import java.security.Security; | |
27 | import java.security.cert.CertificateEncodingException; | |
28 | import java.security.cert.X509Certificate; | |
29 | import java.util.Random; | |
30 | ||
31 | import org.apache.commons.logging.Log; | |
32 | import org.apache.commons.logging.LogFactory; | |
33 | import org.apache.pdfbox.util.Hex; | |
34 | import org.bouncycastle.asn1.DEROctetString; | |
35 | import org.bouncycastle.asn1.DLSequence; | |
36 | import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; | |
37 | import org.bouncycastle.asn1.ocsp.OCSPResponseStatus; | |
38 | import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; | |
39 | import org.bouncycastle.asn1.x509.AlgorithmIdentifier; | |
40 | import org.bouncycastle.asn1.x509.Extension; | |
41 | import org.bouncycastle.asn1.x509.Extensions; | |
42 | import org.bouncycastle.cert.X509CertificateHolder; | |
43 | import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; | |
44 | import org.bouncycastle.cert.jcajce.JcaX509ContentVerifierProviderBuilder; | |
45 | import org.bouncycastle.cert.ocsp.BasicOCSPResp; | |
46 | import org.bouncycastle.cert.ocsp.CertificateID; | |
47 | import org.bouncycastle.cert.ocsp.CertificateStatus; | |
48 | import org.bouncycastle.cert.ocsp.OCSPException; | |
49 | import org.bouncycastle.cert.ocsp.OCSPReq; | |
50 | import org.bouncycastle.cert.ocsp.OCSPReqBuilder; | |
51 | import org.bouncycastle.cert.ocsp.OCSPResp; | |
52 | import org.bouncycastle.cert.ocsp.RevokedStatus; | |
53 | import org.bouncycastle.cert.ocsp.SingleResp; | |
54 | import org.bouncycastle.jce.provider.BouncyCastleProvider; | |
55 | import org.bouncycastle.operator.ContentVerifierProvider; | |
56 | import org.bouncycastle.operator.DigestCalculator; | |
57 | import org.bouncycastle.operator.OperatorCreationException; | |
58 | ||
59 | /** | |
60 | * Helper Class for OCSP-Operations with bouncy castle. | |
61 | * | |
62 | * @author Alexis Suter | |
63 | */ | |
64 | public class OcspHelper | |
65 | { | |
66 | private static final Log LOG = LogFactory.getLog(OcspHelper.class); | |
67 | ||
68 | private final X509Certificate issuerCertificate; | |
69 | private final X509Certificate certificateToCheck; | |
70 | private final String ocspUrl; | |
71 | private DEROctetString encodedNonce; | |
72 | ||
73 | /** | |
74 | * @param checkCertificate Certificate to be OCSP-Checked | |
75 | * @param issuerCertificate Certificate of the issuer | |
76 | * @param ocspUrl where to fetch for OCSP | |
77 | */ | |
78 | public OcspHelper(X509Certificate checkCertificate, X509Certificate issuerCertificate, | |
79 | String ocspUrl) | |
80 | { | |
81 | this.certificateToCheck = checkCertificate; | |
82 | this.issuerCertificate = issuerCertificate; | |
83 | this.ocspUrl = ocspUrl; | |
84 | } | |
85 | ||
86 | /** | |
87 | * Performs and verifies the OCSP-Request | |
88 | * | |
89 | * @return the OCSPResp, when the request was successful, else a corresponding exception will be | |
90 | * thrown. | |
91 | * @throws IOException | |
92 | * @throws OCSPException | |
93 | * @throws RevokedCertificateException | |
94 | */ | |
95 | public OCSPResp getResponseOcsp() throws IOException, OCSPException, RevokedCertificateException | |
96 | { | |
97 | OCSPResp ocspResponse = performRequest(); | |
98 | verifyOcspResponse(ocspResponse); | |
99 | return ocspResponse; | |
100 | } | |
101 | ||
102 | /** | |
103 | * Verifies the status and the response itself (including nonce), but not the signature. | |
104 | * | |
105 | * @param ocspResponse to be verified | |
106 | * @throws OCSPException | |
107 | * @throws RevokedCertificateException | |
108 | */ | |
109 | private void verifyOcspResponse(OCSPResp ocspResponse) | |
110 | throws OCSPException, RevokedCertificateException | |
111 | { | |
112 | verifyRespStatus(ocspResponse); | |
113 | ||
114 | BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject(); | |
115 | if (basicResponse != null) | |
116 | { | |
117 | checkOcspSignature(basicResponse.getCerts()[0], basicResponse); | |
118 | ||
119 | checkNonce(basicResponse); | |
120 | ||
121 | SingleResp[] responses = basicResponse.getResponses(); | |
122 | if (responses.length == 1) | |
123 | { | |
124 | SingleResp resp = responses[0]; | |
125 | Object status = resp.getCertStatus(); | |
126 | ||
127 | if (status instanceof RevokedStatus) | |
128 | { | |
129 | throw new RevokedCertificateException("OCSP: Certificate is revoked."); | |
130 | } | |
131 | else if (status != CertificateStatus.GOOD) | |
132 | { | |
133 | throw new OCSPException("OCSP: Status of Cert is unknown"); | |
134 | } | |
135 | } | |
136 | else | |
137 | { | |
138 | throw new OCSPException( | |
139 | "OCSP: Recieved " + responses.length + " responses instead of 1!"); | |
140 | } | |
141 | } | |
142 | } | |
143 | ||
144 | /** | |
145 | * Checks whether the OCSP response is signed by the given certificate. | |
146 | * | |
147 | * @param certificate the certificate to check the signature | |
148 | * @param basicResponse OCSP response containing the signature | |
149 | * @throws OCSPException when the signature is invalid or could not be checked | |
150 | */ | |
151 | private void checkOcspSignature(X509CertificateHolder certificate, BasicOCSPResp basicResponse) | |
152 | throws OCSPException | |
153 | { | |
154 | try | |
155 | { | |
156 | ContentVerifierProvider verifier = new JcaX509ContentVerifierProviderBuilder() | |
157 | .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(certificate); | |
158 | ||
159 | if (!basicResponse.isSignatureValid(verifier)) | |
160 | { | |
161 | throw new OCSPException("OCSP-Signature is not valid!"); | |
162 | } | |
163 | } | |
164 | catch (OperatorCreationException e) | |
165 | { | |
166 | throw new OCSPException("Error checking Ocsp-Signature", e); | |
167 | } | |
168 | } | |
169 | ||
170 | /** | |
171 | * Checks if the nonce in the response is correct | |
172 | * | |
173 | * @param basicResponse Response to be checked | |
174 | * @throws OCSPException if nonce is wrong or inexistent | |
175 | */ | |
176 | private void checkNonce(BasicOCSPResp basicResponse) throws OCSPException | |
177 | { | |
178 | Extension nonceExt = basicResponse.getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce); | |
179 | if (nonceExt != null) | |
180 | { | |
181 | DEROctetString responseNonceString = (DEROctetString) nonceExt.getExtnValue(); | |
182 | if (!responseNonceString.equals(encodedNonce)) | |
183 | { | |
184 | throw new OCSPException("Invalid Nonce found in response!"); | |
185 | } | |
186 | } | |
187 | else if (encodedNonce != null) | |
188 | { | |
189 | throw new OCSPException("Nonce not found in response!"); | |
190 | } | |
191 | } | |
192 | ||
193 | /** | |
194 | * Performs the OCSP-Request, with given data. | |
195 | * | |
196 | * @return the OCSPResp, that has been fetched from the ocspUrl | |
197 | * @throws IOException | |
198 | * @throws OCSPException | |
199 | */ | |
200 | private OCSPResp performRequest() throws IOException, OCSPException | |
201 | { | |
202 | OCSPReq request = generateOCSPRequest(); | |
203 | URL url = new URL(ocspUrl); | |
204 | HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection(); | |
205 | httpConnection.setRequestProperty("Content-Type", "application/ocsp-request"); | |
206 | httpConnection.setRequestProperty("Accept", "application/ocsp-response"); | |
207 | httpConnection.setDoOutput(true); | |
208 | OutputStream out = httpConnection.getOutputStream(); | |
209 | out.write(request.getEncoded()); | |
210 | out.close(); | |
211 | ||
212 | if (httpConnection.getResponseCode() != 200) | |
213 | { | |
214 | throw new IOException("OCSP: Could not access url, ResponseCode: " | |
215 | + httpConnection.getResponseCode()); | |
216 | } | |
217 | // Get Response | |
218 | InputStream in = (InputStream) httpConnection.getContent(); | |
219 | return new OCSPResp(in); | |
220 | } | |
221 | ||
222 | /** | |
223 | * Helper method to verify response status. | |
224 | * | |
225 | * @param resp OCSP response | |
226 | * @throws OCSPException if the response status is not ok | |
227 | */ | |
228 | public void verifyRespStatus(OCSPResp resp) throws OCSPException | |
229 | { | |
230 | String statusInfo = ""; | |
231 | if (resp != null) | |
232 | { | |
233 | int status = resp.getStatus(); | |
234 | switch (status) | |
235 | { | |
236 | case OCSPResponseStatus.INTERNAL_ERROR: | |
237 | statusInfo = "INTERNAL_ERROR"; | |
238 | System.err.println("An internal error occurred in the OCSP Server!"); | |
239 | break; | |
240 | case OCSPResponseStatus.MALFORMED_REQUEST: | |
241 | statusInfo = "MALFORMED_REQUEST"; | |
242 | System.err.println("Your request did not fit the RFC 2560 syntax!"); | |
243 | break; | |
244 | case OCSPResponseStatus.SIG_REQUIRED: | |
245 | statusInfo = "SIG_REQUIRED"; | |
246 | System.err.println("Your request was not signed!"); | |
247 | break; | |
248 | case OCSPResponseStatus.TRY_LATER: | |
249 | statusInfo = "TRY_LATER"; | |
250 | System.err.println("The server was too busy to answer you!"); | |
251 | break; | |
252 | case OCSPResponseStatus.UNAUTHORIZED: | |
253 | statusInfo = "UNAUTHORIZED"; | |
254 | System.err.println("The server could not authenticate you!"); | |
255 | break; | |
256 | case OCSPResponseStatus.SUCCESSFUL: | |
257 | break; | |
258 | default: | |
259 | statusInfo = "UNKNOWN"; | |
260 | System.err.println("Unknown OCSPResponse status code! " + status); | |
261 | } | |
262 | } | |
263 | if (resp == null || resp.getStatus() != OCSPResponseStatus.SUCCESSFUL) | |
264 | { | |
265 | throw new OCSPException(statusInfo + "OCSP response unsuccessful! "); | |
266 | } | |
267 | } | |
268 | ||
269 | /** | |
270 | * Generates an OCSP request and generates the <code>CertificateID</code>. | |
271 | * | |
272 | * @return OCSP request, ready to fetch data | |
273 | * @throws OCSPException | |
274 | * @throws IOException | |
275 | */ | |
276 | private OCSPReq generateOCSPRequest() throws OCSPException, IOException | |
277 | { | |
278 | Security.addProvider(new BouncyCastleProvider()); | |
279 | ||
280 | // Generate the ID for the certificate we are looking for | |
281 | CertificateID certId; | |
282 | try | |
283 | { | |
284 | certId = new CertificateID(new SHA1DigestCalculator(), | |
285 | new JcaX509CertificateHolder(issuerCertificate), | |
286 | certificateToCheck.getSerialNumber()); | |
287 | } | |
288 | catch (CertificateEncodingException e) | |
289 | { | |
290 | throw new IOException("Error creating CertificateID with the Certificate encoding", e); | |
291 | } | |
292 | ||
293 | OCSPReqBuilder builder = new OCSPReqBuilder(); | |
294 | ||
295 | Extension responseExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_response, | |
296 | true, new DLSequence(OCSPObjectIdentifiers.id_pkix_ocsp_basic).getEncoded()); | |
297 | ||
298 | Random rand = new Random(); | |
299 | byte[] nonce = new byte[16]; | |
300 | rand.nextBytes(nonce); | |
301 | encodedNonce = new DEROctetString(new DEROctetString(nonce)); | |
302 | Extension nonceExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true, | |
303 | encodedNonce); | |
304 | ||
305 | builder.setRequestExtensions( | |
306 | new Extensions(new Extension[] { responseExtension, nonceExtension })); | |
307 | ||
308 | builder.addRequest(certId); | |
309 | ||
310 | System.out.println("Nonce: " + Hex.getString(nonceExtension.getExtnValue().getEncoded())); | |
311 | ||
312 | return builder.build(); | |
313 | } | |
314 | ||
315 | /** | |
316 | * Class to create SHA-1 Digest, used for creation of CertificateID. | |
317 | */ | |
318 | private static class SHA1DigestCalculator implements DigestCalculator | |
319 | { | |
320 | private final ByteArrayOutputStream bOut = new ByteArrayOutputStream(); | |
321 | ||
322 | @Override | |
323 | public AlgorithmIdentifier getAlgorithmIdentifier() | |
324 | { | |
325 | return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1); | |
326 | } | |
327 | ||
328 | @Override | |
329 | public OutputStream getOutputStream() | |
330 | { | |
331 | return bOut; | |
332 | } | |
333 | ||
334 | @Override | |
335 | public byte[] getDigest() | |
336 | { | |
337 | byte[] bytes = bOut.toByteArray(); | |
338 | bOut.reset(); | |
339 | ||
340 | try | |
341 | { | |
342 | MessageDigest md = MessageDigest.getInstance("SHA-1"); | |
343 | return md.digest(bytes); | |
344 | } | |
345 | catch (NoSuchAlgorithmException e) | |
346 | { | |
347 | LOG.error("SHA-1 Algorithm not found", e); | |
348 | return null; | |
349 | } | |
350 | } | |
351 | } | |
352 | } |
+0
-32
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.examples.signature.validation; | |
17 | ||
18 | /** | |
19 | * Exception to handle a revoked Certificate explicitly | |
20 | * | |
21 | * @author Alexis Suter | |
22 | */ | |
23 | public class RevokedCertificateException extends Exception | |
24 | { | |
25 | private static final long serialVersionUID = 3543946618794126654L; | |
26 | ||
27 | public RevokedCertificateException(String message) | |
28 | { | |
29 | super(message); | |
30 | } | |
31 | } |
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.examples.util; | |
17 | ||
18 | import java.io.File; | |
19 | import java.io.IOException; | |
20 | import org.apache.pdfbox.pdmodel.PDDocument; | |
21 | import org.apache.pdfbox.pdmodel.encryption.AccessPermission; | |
22 | import org.apache.pdfbox.text.PDFTextStripper; | |
23 | ||
24 | /** | |
25 | * This is a simple text extraction example to get started. For more advance usage, see the | |
26 | * ExtractTextByArea and the DrawPrintTextLocations examples in this subproject, as well as the | |
27 | * ExtractText tool in the tools subproject. | |
28 | * | |
29 | * @author Tilman Hausherr | |
30 | */ | |
31 | public class ExtractTextSimple | |
32 | { | |
33 | private ExtractTextSimple() | |
34 | { | |
35 | // example class should not be instantiated | |
36 | } | |
37 | ||
38 | /** | |
39 | * This will print the documents text page by page. | |
40 | * | |
41 | * @param args The command line arguments. | |
42 | * | |
43 | * @throws IOException If there is an error parsing or extracting the document. | |
44 | */ | |
45 | public static void main(String[] args) throws IOException | |
46 | { | |
47 | if (args.length != 1) | |
48 | { | |
49 | usage(); | |
50 | } | |
51 | ||
52 | PDDocument document = PDDocument.load(new File(args[0])); | |
53 | AccessPermission ap = document.getCurrentAccessPermission(); | |
54 | if (!ap.canExtractContent()) | |
55 | { | |
56 | throw new IOException("You do not have permission to extract text"); | |
57 | } | |
58 | ||
59 | PDFTextStripper stripper = new PDFTextStripper(); | |
60 | ||
61 | // This example uses sorting, but in some cases it is more useful to switch it off, | |
62 | // e.g. in some files with columns where the PDF content stream respects the | |
63 | // column order. | |
64 | stripper.setSortByPosition(true); | |
65 | ||
66 | for (int p = 1; p <= document.getNumberOfPages(); ++p) | |
67 | { | |
68 | // Set the page interval to extract. If you don't, then all pages would be extracted. | |
69 | stripper.setStartPage(p); | |
70 | stripper.setEndPage(p); | |
71 | ||
72 | // let the magic happen | |
73 | String text = stripper.getText(document); | |
74 | ||
75 | // do some nice output with a header | |
76 | String pageStr = String.format("page %d:", p); | |
77 | System.out.println(pageStr); | |
78 | for (int i = 0; i < pageStr.length(); ++i) | |
79 | { | |
80 | System.out.print("-"); | |
81 | } | |
82 | System.out.println(); | |
83 | System.out.println(text.trim()); | |
84 | System.out.println(); | |
85 | ||
86 | // If the extracted text is empty or gibberish, please try extracting text | |
87 | // with Adobe Reader first before asking for help. Also read the FAQ | |
88 | // on the website: | |
89 | // https://pdfbox.apache.org/2.0/faq.html#text-extraction | |
90 | } | |
91 | document.close(); | |
92 | } | |
93 | ||
94 | /** | |
95 | * This will print the usage for this document. | |
96 | */ | |
97 | private static void usage() | |
98 | { | |
99 | System.err.println("Usage: java " + ExtractTextSimple.class.getName() + " <input-pdf>"); | |
100 | System.exit(-1); | |
101 | } | |
102 | } |
53 | 53 | */ |
54 | 54 | public PDFHighlighter() throws IOException |
55 | 55 | { |
56 | super(); | |
57 | 56 | super.setLineSeparator( "" ); |
58 | 57 | super.setWordSeparator( "" ); |
59 | 58 | super.setShouldSeparateByBeads( false ); |
35 | 35 | import java.util.List; |
36 | 36 | |
37 | 37 | import org.apache.pdfbox.cos.COSName; |
38 | import org.apache.pdfbox.cos.COSObject; | |
39 | 38 | import org.apache.pdfbox.cos.COSString; |
40 | 39 | import org.apache.pdfbox.examples.signature.CreateEmptySignatureForm; |
41 | 40 | import org.apache.pdfbox.examples.signature.CreateSignature; |
287 | 286 | { |
288 | 287 | PDDocument document = PDDocument.load(origFile); |
289 | 288 | // get string representation of pages COSObject |
290 | String origPageKey = ((COSObject) document.getDocumentCatalog().getCOSObject().getItem(COSName.PAGES)).toString(); | |
289 | String origPageKey = document.getDocumentCatalog().getCOSObject().getItem(COSName.PAGES).toString(); | |
291 | 290 | document.close(); |
292 | 291 | |
293 | 292 | document = PDDocument.load(signedFile); |
20 | 20 | <parent> |
21 | 21 | <groupId>org.apache.pdfbox</groupId> |
22 | 22 | <artifactId>pdfbox-parent</artifactId> |
23 | <version>2.0.12</version> | |
23 | <version>2.0.13</version> | |
24 | 24 | <relativePath>../parent/pom.xml</relativePath> |
25 | 25 | </parent> |
26 | 26 |
23 | 23 | import java.util.LinkedList; |
24 | 24 | import java.util.List; |
25 | 25 | import java.util.Map; |
26 | import org.apache.commons.logging.Log; | |
27 | import org.apache.commons.logging.LogFactory; | |
26 | 28 | |
27 | 29 | import org.apache.fontbox.util.Charsets; |
28 | 30 | |
32 | 34 | */ |
33 | 35 | public class CFFParser |
34 | 36 | { |
37 | /** | |
38 | * Log instance. | |
39 | */ | |
40 | private static final Log LOG = LogFactory.getLog(CFFParser.class); | |
41 | ||
35 | 42 | private static final String TAG_OTTO = "OTTO"; |
36 | 43 | private static final String TAG_TTCF = "ttcf"; |
37 | 44 | private static final String TAG_TTFONLY = "\u0000\u0001\u0000\u0000"; |
335 | 342 | StringBuilder sb = new StringBuilder(); |
336 | 343 | boolean done = false; |
337 | 344 | boolean exponentMissing = false; |
345 | boolean hasExponent = false; | |
338 | 346 | while (!done) |
339 | 347 | { |
340 | 348 | int b = input.readUnsignedByte(); |
360 | 368 | sb.append("."); |
361 | 369 | break; |
362 | 370 | case 0xb: |
371 | if (hasExponent) | |
372 | { | |
373 | LOG.warn("duplicate 'E' ignored after " + sb); | |
374 | break; | |
375 | } | |
363 | 376 | sb.append("E"); |
364 | 377 | exponentMissing = true; |
378 | hasExponent = true; | |
365 | 379 | break; |
366 | 380 | case 0xc: |
381 | if (hasExponent) | |
382 | { | |
383 | LOG.warn("duplicate 'E-' ignored after " + sb); | |
384 | break; | |
385 | } | |
367 | 386 | sb.append("E-"); |
368 | 387 | exponentMissing = true; |
388 | hasExponent = true; | |
369 | 389 | break; |
370 | 390 | case 0xd: |
371 | 391 | break; |
21 | 21 | import java.util.HashMap; |
22 | 22 | import java.util.List; |
23 | 23 | import java.util.Map; |
24 | import org.apache.commons.logging.Log; | |
25 | import org.apache.commons.logging.LogFactory; | |
24 | 26 | |
25 | 27 | /** |
26 | 28 | * This class represents a CMap file. |
29 | 31 | */ |
30 | 32 | public class CMap |
31 | 33 | { |
34 | private static final Log LOG = LogFactory.getLog(CMap.class); | |
35 | ||
32 | 36 | private int wmode = 0; |
33 | 37 | private String cmapName = null; |
34 | 38 | private String cmapVersion = null; |
119 | 123 | bytes[byteCount] = (byte)in.read(); |
120 | 124 | } |
121 | 125 | } |
122 | throw new IOException("CMap is invalid"); | |
126 | String seq = ""; | |
127 | for (int i = 0; i < maxCodeLength; ++i) | |
128 | { | |
129 | seq += String.format("0x%02X (%04o) ", bytes[i], bytes[i]); | |
130 | } | |
131 | LOG.warn("Invalid character code sequence " + seq + "in CMap " + cmapName); | |
132 | return 0; | |
123 | 133 | } |
124 | 134 | |
125 | 135 | /** |
32 | 32 | |
33 | 33 | <groupId>org.apache.pdfbox</groupId> |
34 | 34 | <artifactId>pdfbox-parent</artifactId> |
35 | <version>2.0.12</version> | |
35 | <version>2.0.13</version> | |
36 | 36 | <packaging>pom</packaging> |
37 | 37 | |
38 | 38 | <name>PDFBox parent</name> |
51 | 51 | <properties> |
52 | 52 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
53 | 53 | <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> |
54 | ||
55 | <bouncycastle.version>1.60</bouncycastle.version> | |
54 | 56 | </properties> |
55 | 57 | |
56 | 58 | <dependencyManagement> |
75 | 77 | <dependency> |
76 | 78 | <groupId>org.bouncycastle</groupId> |
77 | 79 | <artifactId>bcprov-jdk15on</artifactId> |
78 | <version>1.54</version> | |
80 | <version>${bouncycastle.version}</version> | |
79 | 81 | </dependency> |
80 | 82 | <dependency> |
81 | 83 | <groupId>org.bouncycastle</groupId> |
82 | 84 | <artifactId>bcmail-jdk15on</artifactId> |
83 | <version>1.54</version> | |
85 | <version>${bouncycastle.version}</version> | |
84 | 86 | </dependency> |
85 | 87 | <dependency> |
86 | 88 | <groupId>org.bouncycastle</groupId> |
87 | 89 | <artifactId>bcpkix-jdk15on</artifactId> |
88 | <version>1.54</version> | |
89 | </dependency> | |
90 | <dependency> | |
91 | <groupId>log4j</groupId> | |
92 | <artifactId>log4j</artifactId> | |
93 | <version>1.2.17</version> | |
90 | <version>${bouncycastle.version}</version> | |
94 | 91 | </dependency> |
95 | 92 | |
96 | 93 | <dependency> |
122 | 119 | <!-- call mvn with -Pjdk9 or call with -Daddmod="...." --> |
123 | 120 | <profile> |
124 | 121 | <id>jdk9</id> |
122 | <!-- | |
123 | allows automatic activation when jdk9 or jdk10 are used | |
124 | https://maven.apache.org/guides/introduction/introduction-to-profiles.html | |
125 | https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html | |
126 | --> | |
127 | <activation> | |
128 | <jdk>[9,10]</jdk> | |
129 | </activation> | |
125 | 130 | <properties> |
126 | 131 | <addmod>--add-modules java.activation --add-modules java.xml.bind</addmod> |
127 | 132 | </properties> |
133 | </profile> | |
134 | <profile> | |
135 | <!-- from jdk11 onwards activation and bind are no longer part of the jdk --> | |
136 | <!-- must be set to "test" or "provided" in subprojects --> | |
137 | <id>jdk11</id> | |
138 | <activation> | |
139 | <jdk>[11,)</jdk> | |
140 | </activation> | |
141 | <dependencyManagement> | |
142 | <dependencies> | |
143 | <dependency> | |
144 | <groupId>javax.xml.bind</groupId> | |
145 | <artifactId>jaxb-api</artifactId> | |
146 | <version>2.3.1</version> | |
147 | </dependency> | |
148 | <dependency> | |
149 | <groupId>javax.activation</groupId> | |
150 | <artifactId>activation</artifactId> | |
151 | <version>1.1.1</version> | |
152 | </dependency> | |
153 | </dependencies> | |
154 | </dependencyManagement> | |
128 | 155 | </profile> |
129 | 156 | <profile> |
130 | 157 | <id>pedantic</id> |
145 | 172 | <plugin> |
146 | 173 | <groupId>org.owasp</groupId> |
147 | 174 | <artifactId>dependency-check-maven</artifactId> |
148 | <version>3.3.2</version> | |
175 | <version>3.3.3</version> | |
149 | 176 | <configuration> |
150 | 177 | <failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability> |
151 | 178 | </configuration> |
285 | 312 | <!-- don't upgrade to 3.0.x as long as we have to ensure jdk6 compatibility --> |
286 | 313 | <version>2.5.4</version> |
287 | 314 | </plugin> |
315 | <plugin> | |
316 | <!-- default version 2.20.1 doesn't work with jdk11 --> | |
317 | <artifactId>maven-surefire-plugin</artifactId> | |
318 | <version>2.22.1</version> | |
319 | </plugin> | |
288 | 320 | </plugins> |
289 | 321 | </pluginManagement> |
290 | 322 | </build> |
455 | 487 | </developers> |
456 | 488 | |
457 | 489 | <scm> |
458 | <connection>scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/2.0.12/pdfbox-parent</connection> | |
459 | <developerConnection>scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/2.0.12/pdfbox-parent</developerConnection> | |
460 | <url>http://svn.apache.org/viewvc/maven/pom/tags/2.0.12/pdfbox-parent</url> | |
490 | <connection>scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/2.0.13/pdfbox-parent</connection> | |
491 | <developerConnection>scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/2.0.13/pdfbox-parent</developerConnection> | |
492 | <url>http://svn.apache.org/viewvc/maven/pom/tags/2.0.13/pdfbox-parent</url> | |
461 | 493 | </scm> |
462 | 494 | </project> |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.12</version> | |
25 | <version>2.0.13</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 | |
411 | 411 | <goal>wget</goal> |
412 | 412 | </goals> |
413 | 413 | <configuration> |
414 | <url>http://www.crh.noaa.gov/Image/gjt/images/ImageGallery/Uncompahgre_small.jpg</url> | |
415 | <!-- file is also 032163.jpg | |
416 | from http://downloads.digitalcorpora.org/corpora/files/govdocs1/zipfiles/032.zip --> | |
414 | <url>https://issues.apache.org/jira/secure/attachment/12949710/032163.jpg</url> | |
415 | <!-- 032163.jpg is from | |
416 | http://downloads.digitalcorpora.org/corpora/files/govdocs1/zipfiles/032.zip --> | |
417 | 417 | <outputDirectory>${project.build.directory}/imgs</outputDirectory> |
418 | 418 | <outputFileName>PDFBOX-4184-032163.jpg</outputFileName> |
419 | 419 | <sha512>35241c979d3808ca9d2641b5ec5e40637132b313f75070faca8b8f6d00ddce394070414236db3993f1092fe3bc16995750d528b6d803a7851423c14c308ccdde</sha512> |
451 | 451 | <sha512>566346239d51f10b2ccfc435620e8f3b0281e91286983cb86660060a8d48777998eab46dfda93d35024e7e4b50b7ab6654f9a1002524163d228a5e41a80a1221</sha512> |
452 | 452 | </configuration> |
453 | 453 | </execution> |
454 | <execution> | |
455 | <id>PDFBOX-4338</id> | |
456 | <phase>generate-test-resources</phase> | |
457 | <goals> | |
458 | <goal>wget</goal> | |
459 | </goals> | |
460 | <configuration> | |
461 | <url>https://issues.apache.org/jira/secure/attachment/12943502/ArrayIndexOutOfBoundsException%20COSParser</url> | |
462 | <outputDirectory>${project.build.directory}/pdfs</outputDirectory> | |
463 | <outputFileName>PDFBOX-4338.pdf</outputFileName> | |
464 | <sha512>130fa4b49345410b203613f3e67263f483f9a9797bef22322647655bb55cc55bcb1d1e0eb03c27f6f2855b3823675b27e8899d8eeb880d27a74fad5f60f23b47</sha512> | |
465 | </configuration> | |
466 | </execution> | |
467 | <execution> | |
468 | <id>PDFBOX-4339</id> | |
469 | <phase>generate-test-resources</phase> | |
470 | <goals> | |
471 | <goal>wget</goal> | |
472 | </goals> | |
473 | <configuration> | |
474 | <url>https://issues.apache.org/jira/secure/attachment/12943503/NullPointerException%20COSParser</url> | |
475 | <outputDirectory>${project.build.directory}/pdfs</outputDirectory> | |
476 | <outputFileName>PDFBOX-4339.pdf</outputFileName> | |
477 | <sha512>2e48aeae83ef6fc4c5f95aafdfe8c76dd8d2dcf3516701c70ffeb14f06ba246a17c21f2dadf8fa48bccef5b72daffdd30ed7c9aa7f5183ddf889968caa2ded6a</sha512> | |
478 | </configuration> | |
479 | </execution> | |
454 | 480 | </executions> |
455 | 481 | </plugin> |
456 | 482 | </plugins> |
163 | 163 | * @param keyList The list of keys to find a value. |
164 | 164 | * |
165 | 165 | * @return The object that matches the key. |
166 | * | |
167 | * @deprecated Will be removed in 3.0. A value may have to keys, the regular one and sometimes an additional | |
168 | * abbreviation. More than 2 values doesn't make sense. | |
166 | 169 | */ |
167 | 170 | public COSBase getDictionaryObject(String[] keyList) |
168 | 171 | { |
559 | 562 | } |
560 | 563 | |
561 | 564 | /** |
565 | * This is a convenience method that will get the dictionary object that is expected to be a COSObject. Null is | |
566 | * returned if the entry does not exist in the dictionary. | |
567 | * | |
568 | * @param key The key to the item in the dictionary. | |
569 | * @return The COSObject. | |
570 | */ | |
571 | public COSObject getCOSObject(COSName key) | |
572 | { | |
573 | COSBase object = getItem(key); | |
574 | if (object instanceof COSObject) | |
575 | { | |
576 | return (COSObject) object; | |
577 | } | |
578 | return null; | |
579 | } | |
580 | ||
581 | /** | |
582 | * This is a convenience method that will get the dictionary object that is expected to be a COSDictionary. Null is | |
583 | * returned if the entry does not exist in the dictionary. | |
584 | * | |
585 | * @param key The key to the item in the dictionary. | |
586 | * @return The COSDictionary. | |
587 | */ | |
588 | public COSDictionary getCOSDictionary(COSName key) | |
589 | { | |
590 | COSBase dictionary = getDictionaryObject(key); | |
591 | if (dictionary instanceof COSDictionary) | |
592 | { | |
593 | return (COSDictionary) dictionary; | |
594 | } | |
595 | return null; | |
596 | } | |
597 | ||
598 | /** | |
599 | * This is a convenience method that will get the dictionary object that is expected to be a COSArray. Null is | |
600 | * returned if the entry does not exist in the dictionary. | |
601 | * | |
602 | * @param key The key to the item in the dictionary. | |
603 | * @return The COSArray. | |
604 | */ | |
605 | public COSArray getCOSArray(COSName key) | |
606 | { | |
607 | COSBase array = getDictionaryObject(key); | |
608 | if (array instanceof COSArray) | |
609 | { | |
610 | return (COSArray) array; | |
611 | } | |
612 | return null; | |
613 | } | |
614 | ||
615 | /** | |
562 | 616 | * This is a convenience method that will get the dictionary object that is expected to be a name. Default is |
563 | 617 | * returned if the entry does not exist in the dictionary. |
564 | 618 | * |
1025 | 1079 | * @param keyList The key to the item in the dictionary. |
1026 | 1080 | * @param defaultValue The value to return if the dictionary item is null. |
1027 | 1081 | * @return The integer value. |
1082 | * | |
1083 | * @deprecated Will be removed in 3.0. A value may have to keys, the regular one and sometimes an additional | |
1084 | * abbreviation. More than 2 values doesn't make sense. | |
1028 | 1085 | */ |
1029 | 1086 | public int getInt(String[] keyList, int defaultValue) |
1030 | 1087 | { |
1128 | 1185 | * @param keyList The key to the item in the dictionary. |
1129 | 1186 | * @param defaultValue The value to return if the dictionary item is null. |
1130 | 1187 | * @return The long value. |
1188 | * | |
1189 | * @deprecated Will be removed in 3.0. A value may have to keys, the regular one and sometimes an additional | |
1190 | * abbreviation. More than 2 values doesn't make sense. | |
1131 | 1191 | */ |
1132 | 1192 | public long getLong(String[] keyList, long defaultValue) |
1133 | 1193 | { |
1508 | 1568 | } |
1509 | 1569 | return sb.toString(); |
1510 | 1570 | } |
1571 | if (base instanceof COSArray) | |
1572 | { | |
1573 | StringBuilder sb = new StringBuilder(); | |
1574 | sb.append("COSArray{"); | |
1575 | for (COSBase x : ((COSArray) base).toList()) | |
1576 | { | |
1577 | sb.append(getDictionaryString(x, objs)); | |
1578 | sb.append(";"); | |
1579 | } | |
1580 | sb.append("}"); | |
1581 | return sb.toString(); | |
1582 | } | |
1511 | 1583 | if (base instanceof COSObject) |
1512 | 1584 | { |
1513 | 1585 | COSObject obj = (COSObject) base; |
44 | 44 | /* 70 */ 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
45 | 45 | /* 80 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
46 | 46 | /* 90 */ -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, |
47 | /* 100 */ 13, 14, 15 | |
47 | /* 100 */ 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, | |
48 | /* 110 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
49 | /* 120 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
50 | /* 130 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
51 | /* 140 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
52 | /* 150 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
53 | /* 160 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
54 | /* 170 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
55 | /* 180 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
56 | /* 190 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
57 | /* 200 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
58 | /* 210 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
59 | /* 220 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
60 | /* 230 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
61 | /* 240 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
62 | /* 250 */ -1, -1, -1, -1, -1, -1 | |
48 | 63 | }; |
49 | 64 | |
50 | 65 | @Override |
15 | 15 | */ |
16 | 16 | package org.apache.pdfbox.filter; |
17 | 17 | |
18 | import java.awt.color.ColorSpace; | |
18 | 19 | import java.awt.image.BufferedImage; |
19 | 20 | import java.awt.image.DataBuffer; |
20 | 21 | import java.awt.image.DataBufferByte; |
21 | 22 | import java.awt.image.DataBufferUShort; |
23 | import java.awt.image.IndexColorModel; | |
24 | import java.awt.image.MultiPixelPackedSampleModel; | |
22 | 25 | import java.awt.image.Raster; |
23 | 26 | import java.io.IOException; |
24 | 27 | import java.io.InputStream; |
48 | 51 | */ |
49 | 52 | public final class JPXFilter extends Filter |
50 | 53 | { |
54 | /** | |
55 | * {@inheritDoc} | |
56 | */ | |
51 | 57 | @Override |
52 | 58 | public DecodeResult decode(InputStream encoded, OutputStream decoded, COSDictionary |
53 | 59 | parameters, int index, DecodeOptions options) throws IOException |
73 | 79 | } |
74 | 80 | return result; |
75 | 81 | |
82 | case DataBuffer.TYPE_INT: | |
83 | // not yet used (as of October 2018) but works as fallback | |
84 | // if we decide to convert to BufferedImage.TYPE_INT_RGB | |
85 | int[] ar = new int[raster.getNumBands()]; | |
86 | for (int y = 0; y < image.getHeight(); ++y) | |
87 | { | |
88 | for (int x = 0; x < image.getWidth(); ++x) | |
89 | { | |
90 | raster.getPixel(x, y, ar); | |
91 | for (int i = 0; i < ar.length; ++i) | |
92 | { | |
93 | decoded.write(ar[i]); | |
94 | } | |
95 | } | |
96 | } | |
97 | return result; | |
98 | ||
76 | 99 | default: |
77 | 100 | throw new IOException("Data type " + raster.getDataBuffer().getDataType() + " not implemented"); |
78 | 101 | } |
135 | 158 | // extract embedded color space |
136 | 159 | if (!parameters.containsKey(COSName.COLORSPACE)) |
137 | 160 | { |
138 | result.setColorSpace(new PDJPXColorSpace(image.getColorModel().getColorSpace())); | |
161 | if (image.getSampleModel() instanceof MultiPixelPackedSampleModel && | |
162 | image.getColorModel().getPixelSize() == 1 && | |
163 | image.getRaster().getNumBands() == 1 && | |
164 | image.getColorModel() instanceof IndexColorModel) | |
165 | { | |
166 | // PDFBOX-4326: | |
167 | // force CS_GRAY colorspace because colorspace in IndexColorModel | |
168 | // has 3 colors despite that there is only 1 color per pixel | |
169 | // in raster | |
170 | result.setColorSpace(new PDJPXColorSpace(ColorSpace.getInstance(ColorSpace.CS_GRAY))); | |
171 | } | |
172 | else | |
173 | { | |
174 | result.setColorSpace(new PDJPXColorSpace(image.getColorModel().getColorSpace())); | |
175 | } | |
139 | 176 | } |
140 | 177 | |
141 | 178 | return image; |
150 | 187 | } |
151 | 188 | } |
152 | 189 | |
190 | /** | |
191 | * {@inheritDoc} | |
192 | */ | |
153 | 193 | @Override |
154 | 194 | protected void encode(InputStream input, OutputStream encoded, COSDictionary parameters) |
155 | 195 | throws IOException |
44 | 44 | import org.apache.pdfbox.util.Matrix; |
45 | 45 | |
46 | 46 | /** |
47 | * This class allows to import pages as Form XObjects into a PDF file and use them to create | |
48 | * layers (optional content groups). | |
49 | * | |
47 | * This class allows to import pages as Form XObjects into a document and use them to create layers | |
48 | * (optional content groups). It should used only on loaded documents, not on generated documents | |
49 | * because these can contain unfinished parts, e.g. font subsetting information. | |
50 | 50 | */ |
51 | 51 | public class LayerUtility |
52 | 52 | { |
16 | 16 | package org.apache.pdfbox.multipdf; |
17 | 17 | |
18 | 18 | import java.awt.geom.AffineTransform; |
19 | import java.io.Closeable; | |
19 | 20 | import java.io.File; |
20 | 21 | import java.io.IOException; |
21 | 22 | import java.io.InputStream; |
23 | 24 | import java.math.BigDecimal; |
24 | 25 | import java.util.ArrayList; |
25 | 26 | import java.util.HashMap; |
27 | import java.util.HashSet; | |
26 | 28 | import java.util.List; |
27 | 29 | import java.util.Map; |
30 | import java.util.Set; | |
28 | 31 | import org.apache.pdfbox.cos.COSArray; |
29 | 32 | import org.apache.pdfbox.cos.COSBase; |
30 | 33 | import org.apache.pdfbox.cos.COSDictionary; |
44 | 47 | * Based on code contributed by Balazs Jerk. |
45 | 48 | * |
46 | 49 | */ |
47 | public class Overlay | |
50 | public class Overlay implements Closeable | |
48 | 51 | { |
49 | 52 | /** |
50 | 53 | * Possible location of the overlayed pages: foreground or background. |
60 | 63 | private LayoutPage oddPageOverlayPage; |
61 | 64 | private LayoutPage evenPageOverlayPage; |
62 | 65 | |
63 | private final Map<Integer, PDDocument> specificPageOverlay = new HashMap<Integer, PDDocument>(); | |
66 | private final Set<PDDocument> openDocuments = new HashSet<PDDocument>(); | |
64 | 67 | private Map<Integer, LayoutPage> specificPageOverlayPage = new HashMap<Integer, LayoutPage>(); |
65 | 68 | |
66 | 69 | private Position position = Position.BACKGROUND; |
115 | 118 | loadedDocuments.put(e.getValue(), doc); |
116 | 119 | layouts.put(doc, getLayoutPage(doc)); |
117 | 120 | } |
118 | specificPageOverlay.put(e.getKey(), doc); | |
121 | openDocuments.add(doc); | |
119 | 122 | specificPageOverlayPage.put(e.getKey(), layouts.get(doc)); |
120 | 123 | } |
121 | 124 | processPages(inputPDFDocument); |
123 | 126 | } |
124 | 127 | |
125 | 128 | /** |
126 | * Close all input pdfs which were used for the overlay. | |
127 | * | |
129 | * This will add overlays documents to a document. | |
130 | * | |
131 | * @param specificPageOverlayDocuments map of overlay documents for specific pages. The page | |
132 | * numbers are 1-based. | |
133 | * | |
134 | * @return The modified input PDF document, which has to be saved and closed by the caller. If | |
135 | * the input document was passed by {@link #setInputPDF(PDDocument) setInputPDF(PDDocument)} | |
136 | * then it is that object that is returned. | |
137 | * | |
128 | 138 | * @throws IOException if something went wrong |
129 | 139 | */ |
140 | public PDDocument overlayDocuments(Map<Integer, PDDocument> specificPageOverlayDocuments) throws IOException | |
141 | { | |
142 | loadPDFs(); | |
143 | for (Map.Entry<Integer, PDDocument> e : specificPageOverlayDocuments.entrySet()) | |
144 | { | |
145 | PDDocument doc = e.getValue(); | |
146 | if (doc != null) | |
147 | { | |
148 | specificPageOverlayPage.put(e.getKey(), getLayoutPage(doc)); | |
149 | } | |
150 | } | |
151 | processPages(inputPDFDocument); | |
152 | return inputPDFDocument; | |
153 | } | |
154 | ||
155 | /** | |
156 | * Close all input documents which were used for the overlay and opened by this class. | |
157 | * | |
158 | * @throws IOException if something went wrong | |
159 | */ | |
160 | @Override | |
130 | 161 | public void close() throws IOException |
131 | 162 | { |
132 | 163 | if (defaultOverlay != null) |
153 | 184 | { |
154 | 185 | evenPageOverlay.close(); |
155 | 186 | } |
156 | if (specificPageOverlay != null) | |
157 | { | |
158 | for (Map.Entry<Integer, PDDocument> e : specificPageOverlay.entrySet()) | |
159 | { | |
160 | e.getValue().close(); | |
161 | } | |
162 | specificPageOverlay.clear(); | |
163 | specificPageOverlayPage.clear(); | |
164 | } | |
187 | for (PDDocument doc : openDocuments) | |
188 | { | |
189 | doc.close(); | |
190 | } | |
191 | openDocuments.clear(); | |
192 | specificPageOverlayPage.clear(); | |
165 | 193 | } |
166 | 194 | |
167 | 195 | private void loadPDFs() throws IOException |
90 | 90 | private DocumentMergeMode documentMergeMode = DocumentMergeMode.PDFBOX_LEGACY_MODE; |
91 | 91 | |
92 | 92 | /** |
93 | * The mode to use when merging documents. | |
93 | * The mode to use when merging documents: | |
94 | 94 | * |
95 | * <p><ul> | |
95 | * <ul> | |
96 | 96 | * <li>{@link DocumentMergeMode#OPTIMIZE_RESOURCES_MODE} Optimizes resource handling such as |
97 | 97 | * closing documents early. <strong>Not all document elements are merged</strong> compared to |
98 | 98 | * the PDFBOX_LEGACY_MODE. Currently supported are: |
223 | 223 | */ |
224 | 224 | public void addSource(File source) throws FileNotFoundException |
225 | 225 | { |
226 | FileInputStream stream = new FileInputStream(source); | |
227 | sources.add(stream); | |
226 | sources.add(source); | |
228 | 227 | } |
229 | 228 | |
230 | 229 | /** |
620 | 620 | */ |
621 | 621 | protected COSArray parseCOSArray() throws IOException |
622 | 622 | { |
623 | long startPosition = seqSource.getPosition(); | |
623 | 624 | readExpectedChar('['); |
624 | 625 | COSArray po = new COSArray(); |
625 | 626 | COSBase pbo; |
631 | 632 | if( pbo instanceof COSObject ) |
632 | 633 | { |
633 | 634 | // We have to check if the expected values are there or not PDFBOX-385 |
634 | if (po.get(po.size()-1) instanceof COSInteger) | |
635 | if (po.size() > 0 && po.get(po.size() - 1) instanceof COSInteger) | |
635 | 636 | { |
636 | 637 | COSInteger genNumber = (COSInteger)po.remove( po.size() -1 ); |
637 | if (po.get(po.size()-1) instanceof COSInteger) | |
638 | if (po.size() > 0 && po.get(po.size() - 1) instanceof COSInteger) | |
638 | 639 | { |
639 | 640 | COSInteger number = (COSInteger)po.remove( po.size() -1 ); |
640 | 641 | COSObjectKey key = new COSObjectKey(number.longValue(), genNumber.intValue()); |
658 | 659 | else |
659 | 660 | { |
660 | 661 | //it could be a bad object in the array which is just skipped |
661 | LOG.warn("Corrupt object reference at offset " + seqSource.getPosition()); | |
662 | LOG.warn("Corrupt object reference at offset " + | |
663 | seqSource.getPosition() + ", start offset: " + startPosition); | |
662 | 664 | |
663 | 665 | // This could also be an "endobj" or "endstream" which means we can assume that |
664 | 666 | // the array has ended. |
322 | 322 | { |
323 | 323 | // xref table and trailer |
324 | 324 | // use existing parser to parse xref table |
325 | parseXrefTable(prev); | |
326 | if (!parseTrailer()) | |
327 | { | |
328 | throw new IOException("Expected trailer object at position: " | |
325 | if (!parseXrefTable(prev) || !parseTrailer()) | |
326 | { | |
327 | throw new IOException("Expected trailer object at offset " | |
329 | 328 | + source.getPosition()); |
330 | 329 | } |
331 | 330 | COSDictionary trailer = xrefTrailerResolver.getCurrentTrailer(); |
1736 | 1735 | skipSpaces(); |
1737 | 1736 | COSDictionary trailerDict = parseCOSDictionary(); |
1738 | 1737 | StringBuilder trailerKeys = new StringBuilder(); |
1739 | if (trailerDict.containsKey(COSName.ROOT)) | |
1740 | { | |
1741 | COSBase rootObj = trailerDict.getItem(COSName.ROOT); | |
1742 | if (rootObj instanceof COSObject) | |
1743 | { | |
1744 | long objNumber = ((COSObject) rootObj).getObjectNumber(); | |
1745 | int genNumber = ((COSObject) rootObj).getGenerationNumber(); | |
1746 | trailerKeys.append(objNumber).append(" "); | |
1747 | trailerKeys.append(genNumber).append(" "); | |
1748 | rootFound = true; | |
1749 | } | |
1750 | } | |
1751 | if (trailerDict.containsKey(COSName.INFO)) | |
1752 | { | |
1753 | COSBase infoObj = trailerDict.getItem(COSName.INFO); | |
1754 | if (infoObj instanceof COSObject) | |
1755 | { | |
1756 | long objNumber = ((COSObject) infoObj).getObjectNumber(); | |
1757 | int genNumber = ((COSObject) infoObj).getGenerationNumber(); | |
1758 | trailerKeys.append(objNumber).append(" "); | |
1759 | trailerKeys.append(genNumber).append(" "); | |
1760 | infoFound = true; | |
1761 | } | |
1738 | COSObject rootObj = trailerDict.getCOSObject(COSName.ROOT); | |
1739 | if (rootObj != null) | |
1740 | { | |
1741 | long objNumber = rootObj.getObjectNumber(); | |
1742 | int genNumber = rootObj.getGenerationNumber(); | |
1743 | trailerKeys.append(objNumber).append(" "); | |
1744 | trailerKeys.append(genNumber).append(" "); | |
1745 | rootFound = true; | |
1746 | } | |
1747 | COSObject infoObj = trailerDict.getCOSObject(COSName.INFO); | |
1748 | if (infoObj != null) | |
1749 | { | |
1750 | long objNumber = infoObj.getObjectNumber(); | |
1751 | int genNumber = infoObj.getGenerationNumber(); | |
1752 | trailerKeys.append(objNumber).append(" "); | |
1753 | trailerKeys.append(genNumber).append(" "); | |
1754 | infoFound = true; | |
1762 | 1755 | } |
1763 | 1756 | if (rootFound && infoFound) |
1764 | 1757 | { |
2031 | 2024 | } |
2032 | 2025 | int start = 0; |
2033 | 2026 | // skip spaces |
2034 | while (numbersBytes[start] == 32) | |
2027 | while (start < numbersBytes.length && numbersBytes[start] == 32) | |
2035 | 2028 | { |
2036 | 2029 | start++; |
2037 | 2030 | } |
2048 | 2041 | Map<COSObjectKey, Long> xrefOffset = xrefTrailerResolver.getXrefTable(); |
2049 | 2042 | for (int i = 0; i < nrOfObjects; i++) |
2050 | 2043 | { |
2051 | long objNumber = Long.parseLong(numbers[i * 2]); | |
2052 | COSObjectKey objKey = new COSObjectKey(objNumber, 0); | |
2053 | Long existingOffset = bfSearchCOSObjectKeyOffsets.get(objKey); | |
2054 | if (existingOffset == null || offset > existingOffset) | |
2055 | { | |
2056 | bfSearchCOSObjectKeyOffsets.put(objKey, -stmObjNumber); | |
2057 | xrefOffset.put(objKey, -stmObjNumber); | |
2044 | try | |
2045 | { | |
2046 | long objNumber = Long.parseLong(numbers[i * 2]); | |
2047 | COSObjectKey objKey = new COSObjectKey(objNumber, 0); | |
2048 | Long existingOffset = bfSearchCOSObjectKeyOffsets.get(objKey); | |
2049 | if (existingOffset == null || offset > existingOffset) | |
2050 | { | |
2051 | bfSearchCOSObjectKeyOffsets.put(objKey, -stmObjNumber); | |
2052 | xrefOffset.put(objKey, -stmObjNumber); | |
2053 | } | |
2054 | } | |
2055 | catch (NumberFormatException exception) | |
2056 | { | |
2057 | LOG.debug("Skipped corrupt object key in stream: " + stmObjNumber); | |
2058 | 2058 | } |
2059 | 2059 | } |
2060 | 2060 | } |
2333 | 2333 | List<? extends COSBase> kidsList = kidsArray.toList(); |
2334 | 2334 | for (COSBase kid : kidsList) |
2335 | 2335 | { |
2336 | COSObject kidObject = (COSObject) kid; | |
2337 | if (set.contains(kidObject)) | |
2336 | if (!(kid instanceof COSObject) || set.contains((COSObject) kid)) | |
2338 | 2337 | { |
2339 | 2338 | kidsArray.remove(kid); |
2340 | 2339 | continue; |
2341 | 2340 | } |
2341 | COSObject kidObject = (COSObject) kid; | |
2342 | 2342 | COSBase kidBaseobject = kidObject.getObject(); |
2343 | 2343 | // object wasn't dereferenced -> remove it |
2344 | if (kidBaseobject.equals(COSNull.NULL)) | |
2344 | if (kidBaseobject == null || kidBaseobject.equals(COSNull.NULL)) | |
2345 | 2345 | { |
2346 | 2346 | LOG.warn("Removed null object " + kid + " from pages dictionary"); |
2347 | 2347 | kidsArray.remove(kid); |
2496 | 2496 | if (source.getPosition() == trailerOffset) |
2497 | 2497 | { |
2498 | 2498 | // warn only the first time |
2499 | LOG.warn("Expected trailer object at position " + trailerOffset | |
2499 | LOG.warn("Expected trailer object at offset " + trailerOffset | |
2500 | 2500 | + ", keep trying"); |
2501 | 2501 | } |
2502 | 2502 | readLine(); |
2687 | 2687 | if (splitString.length != 2) |
2688 | 2688 | { |
2689 | 2689 | LOG.warn("Unexpected XRefTable Entry: " + currentLine); |
2690 | break; | |
2690 | return false; | |
2691 | 2691 | } |
2692 | 2692 | // first obj id |
2693 | long currObjID = Long.parseLong(splitString[0]); | |
2693 | long currObjID = 0; | |
2694 | try | |
2695 | { | |
2696 | currObjID = Long.parseLong(splitString[0]); | |
2697 | } | |
2698 | catch (NumberFormatException exception) | |
2699 | { | |
2700 | LOG.warn("XRefTable: invalid ID for the first object: " + currentLine); | |
2701 | return false; | |
2702 | } | |
2703 | ||
2694 | 2704 | // the number of objects in the xref table |
2695 | int count = Integer.parseInt(splitString[1]); | |
2705 | int count = 0; | |
2706 | try | |
2707 | { | |
2708 | count = Integer.parseInt(splitString[1]); | |
2709 | } | |
2710 | catch (NumberFormatException exception) | |
2711 | { | |
2712 | LOG.warn("XRefTable: invalid number of objects: " + currentLine); | |
2713 | return false; | |
2714 | } | |
2696 | 2715 | |
2697 | 2716 | skipSpaces(); |
2698 | 2717 | for(int i = 0; i < count; i++) |
2837 | 2856 | } |
2838 | 2857 | } |
2839 | 2858 | // parse catalog or root object |
2840 | COSObject root = (COSObject) trailer.getItem(COSName.ROOT); | |
2859 | COSObject root = trailer.getCOSObject(COSName.ROOT); | |
2841 | 2860 | if (root == null) |
2842 | 2861 | { |
2843 | 2862 | throw new IOException("Missing root object specification in trailer."); |
2915 | 2934 | private void parseDictionaryRecursive(COSObject dictionaryObject) throws IOException |
2916 | 2935 | { |
2917 | 2936 | parseObjectDynamically(dictionaryObject, true); |
2937 | if (!(dictionaryObject.getObject() instanceof COSDictionary)) | |
2938 | { | |
2939 | // we can't be lenient here, this is called by prepareDecryption() | |
2940 | // to get the encryption directory | |
2941 | throw new IOException("Dictionary object expected at offset " + source.getPosition()); | |
2942 | } | |
2918 | 2943 | COSDictionary dictionary = (COSDictionary) dictionaryObject.getObject(); |
2919 | 2944 | for (COSBase value : dictionary.getValues()) |
2920 | 2945 | { |
51 | 51 | public int read(byte[] b) throws IOException |
52 | 52 | { |
53 | 53 | int n = input.read(b); |
54 | position += n; | |
55 | return n; | |
54 | if (n > 0) | |
55 | { | |
56 | position += n; | |
57 | return n; | |
58 | } | |
59 | else | |
60 | { | |
61 | return -1; | |
62 | } | |
56 | 63 | } |
57 | 64 | |
58 | 65 | @Override |
59 | 66 | public int read(byte[] b, int offset, int length) throws IOException |
60 | 67 | { |
61 | 68 | int n = input.read(b, offset, length); |
62 | position += n; | |
63 | return n; | |
69 | if (n > 0) | |
70 | { | |
71 | position += n; | |
72 | return n; | |
73 | } | |
74 | else | |
75 | { | |
76 | return -1; | |
77 | } | |
64 | 78 | } |
65 | 79 | |
66 | 80 | @Override |
110 | 124 | while (len > 0) |
111 | 125 | { |
112 | 126 | int n = this.read(bytes, off, len); |
113 | off += n; | |
114 | len -= n; | |
115 | position += n; | |
127 | if (n > 0) | |
128 | { | |
129 | off += n; | |
130 | len -= n; | |
131 | position += n; | |
132 | } | |
133 | else | |
134 | { | |
135 | break; | |
136 | } | |
116 | 137 | } |
117 | 138 | return bytes; |
118 | 139 | } |
68 | 68 | { |
69 | 69 | //need to first parse the header. |
70 | 70 | int numberOfObjects = stream.getInt( "N" ); |
71 | if (numberOfObjects == -1) | |
72 | { | |
73 | throw new IOException("/N entry missing in object stream"); | |
74 | } | |
71 | 75 | List<Long> objectNumbers = new ArrayList<Long>( numberOfObjects ); |
72 | 76 | streamObjects = new ArrayList<COSObject>( numberOfObjects ); |
73 | 77 | for( int i=0; i<numberOfObjects; i++ ) |
24 | 24 | import org.apache.pdfbox.cos.COSDictionary; |
25 | 25 | import org.apache.pdfbox.cos.COSDocument; |
26 | 26 | import org.apache.pdfbox.cos.COSName; |
27 | import org.apache.pdfbox.cos.COSNull; | |
28 | import org.apache.pdfbox.cos.COSObject; | |
27 | 29 | import org.apache.pdfbox.io.IOUtils; |
28 | 30 | import org.apache.pdfbox.io.RandomAccessRead; |
29 | 31 | import org.apache.pdfbox.io.ScratchFile; |
190 | 192 | } |
191 | 193 | // check pages dictionaries |
192 | 194 | checkPages(root); |
195 | if (!(root.getDictionaryObject(COSName.PAGES) instanceof COSDictionary)) | |
196 | { | |
197 | throw new IOException("Page tree root must be a dictionary"); | |
198 | } | |
193 | 199 | document.setDecrypted(); |
194 | 200 | initialParseDone = true; |
195 | 201 | } |
71 | 71 | } |
72 | 72 | COSArray xrefFormat = (COSArray) w; |
73 | 73 | |
74 | COSArray indexArray = (COSArray)stream.getDictionaryObject(COSName.INDEX); | |
75 | /* | |
76 | * If Index doesn't exist, we will use the default values. | |
77 | */ | |
78 | if(indexArray == null) | |
79 | { | |
74 | COSBase base = stream.getDictionaryObject(COSName.INDEX); | |
75 | COSArray indexArray; | |
76 | if (base instanceof COSArray) | |
77 | { | |
78 | indexArray = (COSArray) base; | |
79 | } | |
80 | else | |
81 | { | |
82 | // If /Index doesn't exist, we will use the default values. | |
80 | 83 | indexArray = new COSArray(); |
81 | 84 | indexArray.add(COSInteger.ZERO); |
82 | indexArray.add(stream.getDictionaryObject(COSName.SIZE)); | |
85 | indexArray.add(COSInteger.get(stream.getInt(COSName.SIZE, 0))); | |
83 | 86 | } |
84 | 87 | |
85 | 88 | List<Long> objNums = new ArrayList<Long>(); |
88 | 91 | * Populates objNums with all object numbers available |
89 | 92 | */ |
90 | 93 | Iterator<COSBase> indexIter = indexArray.iterator(); |
91 | while(indexIter.hasNext()) | |
92 | { | |
93 | long objID = ((COSInteger)indexIter.next()).longValue(); | |
94 | int size = ((COSInteger)indexIter.next()).intValue(); | |
95 | for(int i = 0; i < size; i++) | |
94 | while (indexIter.hasNext()) | |
95 | { | |
96 | base = indexIter.next(); | |
97 | if (!(base instanceof COSInteger)) | |
98 | { | |
99 | throw new IOException("Xref stream must have integer in /Index array"); | |
100 | } | |
101 | long objID = ((COSInteger) base).longValue(); | |
102 | if (!indexIter.hasNext()) | |
103 | { | |
104 | break; | |
105 | } | |
106 | base = indexIter.next(); | |
107 | if (!(base instanceof COSInteger)) | |
108 | { | |
109 | throw new IOException("Xref stream must have integer in /Index array"); | |
110 | } | |
111 | int size = ((COSInteger) base).intValue(); | |
112 | for (int i = 0; i < size; i++) | |
96 | 113 | { |
97 | 114 | objNums.add(objID + i); |
98 | 115 | } |
101 | 118 | /* |
102 | 119 | * Calculating the size of the line in bytes |
103 | 120 | */ |
104 | int w0 = xrefFormat.getInt(0); | |
105 | int w1 = xrefFormat.getInt(1); | |
106 | int w2 = xrefFormat.getInt(2); | |
121 | int w0 = xrefFormat.getInt(0, 0); | |
122 | int w1 = xrefFormat.getInt(1, 0); | |
123 | int w2 = xrefFormat.getInt(2, 0); | |
107 | 124 | int lineSize = w0 + w1 + w2; |
108 | 125 | |
109 | 126 | while(!seqSource.isEOF() && objIter.hasNext()) |
157 | 157 | |
158 | 158 | // document-wide cached resources |
159 | 159 | private ResourceCache resourceCache = new DefaultResourceCache(); |
160 | ||
160 | ||
161 | // to make sure only one signature is added | |
162 | private boolean signatureAdded = false; | |
163 | ||
161 | 164 | /** |
162 | 165 | * Creates an empty PDF document. |
163 | 166 | * You need to add at least one page for the document to be valid. |
228 | 231 | * Add parameters of signature to be created externally using default signature options. See |
229 | 232 | * {@link #saveIncrementalForExternalSigning(OutputStream)} method description on external |
230 | 233 | * signature creation scenario details. |
234 | * <p> | |
235 | * Only one signature may be added in a document. To sign several times, | |
236 | * load document, add signature, save incremental and close again. | |
231 | 237 | * |
232 | 238 | * @param sigObject is the PDSignatureField model |
233 | 239 | * @throws IOException if there is an error creating required fields |
240 | * @throws IllegalStateException if one attempts to add several signature | |
241 | * fields. | |
234 | 242 | */ |
235 | 243 | public void addSignature(PDSignature sigObject) throws IOException |
236 | 244 | { |
241 | 249 | * Add parameters of signature to be created externally. See |
242 | 250 | * {@link #saveIncrementalForExternalSigning(OutputStream)} method description on external |
243 | 251 | * signature creation scenario details. |
252 | * <p> | |
253 | * Only one signature may be added in a document. To sign several times, | |
254 | * load document, add signature, save incremental and close again. | |
244 | 255 | * |
245 | 256 | * @param sigObject is the PDSignatureField model |
246 | 257 | * @param options signature options |
247 | 258 | * @throws IOException if there is an error creating required fields |
259 | * @throws IllegalStateException if one attempts to add several signature | |
260 | * fields. | |
248 | 261 | */ |
249 | 262 | public void addSignature(PDSignature sigObject, SignatureOptions options) throws IOException |
250 | 263 | { |
253 | 266 | |
254 | 267 | /** |
255 | 268 | * Add a signature to be created using the instance of given interface. |
269 | * <p> | |
270 | * Only one signature may be added in a document. To sign several times, | |
271 | * load document, add signature, save incremental and close again. | |
256 | 272 | * |
257 | 273 | * @param sigObject is the PDSignatureField model |
258 | * @param signatureInterface is an interface which provides signing capabilities | |
274 | * @param signatureInterface is an interface whose implementation provides | |
275 | * signing capabilities. Can be null if external signing if used. | |
259 | 276 | * @throws IOException if there is an error creating required fields |
277 | * @throws IllegalStateException if one attempts to add several signature | |
278 | * fields. | |
260 | 279 | */ |
261 | 280 | public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface) throws IOException |
262 | 281 | { |
267 | 286 | * This will add a signature to the document. If the 0-based page number in the options |
268 | 287 | * parameter is smaller than 0 or larger than max, the nearest valid page number will be used |
269 | 288 | * (i.e. 0 or max) and no exception will be thrown. |
289 | * <p> | |
290 | * Only one signature may be added in a document. To sign several times, | |
291 | * load document, add signature, save incremental and close again. | |
270 | 292 | * |
271 | 293 | * @param sigObject is the PDSignatureField model |
272 | * @param signatureInterface is an interface which provides signing capabilities | |
294 | * @param signatureInterface is an interface whose implementation provides | |
295 | * signing capabilities. Can be null if external signing if used. | |
273 | 296 | * @param options signature options |
274 | 297 | * @throws IOException if there is an error creating required fields |
298 | * @throws IllegalStateException if one attempts to add several signature | |
299 | * fields. | |
275 | 300 | */ |
276 | 301 | public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface, |
277 | 302 | SignatureOptions options) throws IOException |
278 | 303 | { |
304 | if (signatureAdded) | |
305 | { | |
306 | throw new IllegalStateException("Only one signature may be added in a document"); | |
307 | } | |
308 | signatureAdded = true; | |
309 | ||
279 | 310 | // Reserve content |
280 | 311 | // We need to reserve some space for the signature. Some signatures including |
281 | 312 | // big certificate chain and we need enough space to store it. |
583 | 614 | * This will add a list of signature fields to the document. |
584 | 615 | * |
585 | 616 | * @param sigFields are the PDSignatureFields that should be added to the document |
586 | * @param signatureInterface is a interface which provides signing capabilities | |
617 | * @param signatureInterface is an interface whose implementation provides | |
618 | * signing capabilities. Can be null if external signing if used. | |
587 | 619 | * @param options signature options |
588 | 620 | * @throws IOException if there is an error creating required fields |
589 | */ | |
621 | * @deprecated The method is misleading, because only one signature may be | |
622 | * added in a document. The method will be removed in the future. | |
623 | */ | |
624 | @Deprecated | |
590 | 625 | public void addSignatureField(List<PDSignatureField> sigFields, SignatureInterface signatureInterface, |
591 | 626 | SignatureOptions options) throws IOException |
592 | 627 | { |
659 | 694 | } |
660 | 695 | |
661 | 696 | /** |
662 | * This will import and copy the contents from another location. Currently the content stream is stored in a scratch | |
663 | * file. The scratch file is associated with the document. If you are adding a page to this document from another | |
664 | * document and want to copy the contents to this | |
665 | * document's scratch file then use this method otherwise just use the {@link #addPage addPage} | |
697 | * This will import and copy the contents from another location. Currently the content stream is | |
698 | * stored in a scratch file. The scratch file is associated with the document. If you are adding | |
699 | * a page to this document from another document and want to copy the contents to this | |
700 | * document's scratch file then use this method otherwise just use the {@link #addPage addPage()} | |
666 | 701 | * method. |
667 | 702 | * <p> |
668 | * Unlike {@link #addPage addPage}, this method creates a new PDPage object. If your page has | |
703 | * Unlike {@link #addPage addPage()}, this method creates a new PDPage object. If your page has | |
669 | 704 | * annotations, and if these link to pages not in the target document, then the target document |
670 | 705 | * might become huge. What you need to do is to delete page references of such annotations. See |
671 | 706 | * <a href="http://stackoverflow.com/a/35477351/535646">here</a> for how to do this. |
672 | 707 | * <p> |
673 | * Inherited (global) resources are ignored. If you need them, call | |
674 | * <code>importedPage.setRotation(page.getRotation());</code> | |
708 | * Inherited (global) resources are ignored because these can contain resources not needed for | |
709 | * this page which could bloat your document, see | |
710 | * <a href="https://issues.apache.org/jira/browse/PDFBOX-28">PDFBOX-28</a> and related issues. | |
711 | * If you need them, call <code>importedPage.setResources(page.getResources());</code> | |
712 | * <p> | |
713 | * This method should only be used to import a page from a loaded document, not from a generated | |
714 | * document because these can contain unfinished parts, e.g. font subsetting information. | |
675 | 715 | * |
676 | 716 | * @param page The page to import. |
677 | 717 | * @return The page that was imported. |
678 | * | |
718 | * | |
679 | 719 | * @throws IOException If there is an error copying the page. |
680 | 720 | */ |
681 | 721 | public PDPage importPage(PDPage page) throws IOException |
1303 | 1343 | * Save the PDF as an incremental update. This is only possible if the PDF was loaded from a |
1304 | 1344 | * file or a stream, not if the document was created in PDFBox itself. |
1305 | 1345 | * |
1306 | * @param output stream to write to. It will be closed when done. It should <i><b>not</b></i> | |
1307 | * point to the source file. | |
1346 | * @param output stream to write to. It will be closed when done. It | |
1347 | * <i><b>must never</b></i> point to the source file or that one will be | |
1348 | * harmed! | |
1308 | 1349 | * @throws IOException if the output could not be written |
1309 | 1350 | * @throws IllegalStateException if the document was not loaded from a file or a stream. |
1310 | 1351 | */ |
1360 | 1401 | * {@code PDDocument} instance and only AFTER {@link ExternalSigningSupport} instance is used. |
1361 | 1402 | * </p> |
1362 | 1403 | * |
1363 | * @param output stream to write the final PDF. It should <i><b>not</b></i> point to the source | |
1364 | * file. It will be closed when the document is closed. | |
1404 | * @param output stream to write the final PDF. It will be closed when the | |
1405 | * document is closed. It <i><b>must never</b></i> point to the source file | |
1406 | * or that one will be harmed! | |
1365 | 1407 | * @return instance to be used for external signing and setting CMS signature |
1366 | 1408 | * @throws IOException if the output could not be written |
1367 | 1409 | * @throws IllegalStateException if the document was not loaded from a file or a stream or |
464 | 464 | * |
465 | 465 | * @param text The Unicode text to show. |
466 | 466 | * @throws IOException If an io exception occurs. |
467 | * @throws IllegalArgumentException if a character isn't supported by the current font | |
467 | 468 | */ |
468 | 469 | public void showText(String text) throws IOException |
469 | 470 | { |
2501 | 2502 | @Override |
2502 | 2503 | public void close() throws IOException |
2503 | 2504 | { |
2505 | if (inTextMode) | |
2506 | { | |
2507 | LOG.warn("You did not call endText(), some viewers won't display your text"); | |
2508 | } | |
2504 | 2509 | if (output != null) |
2505 | 2510 | { |
2506 | 2511 | output.close(); |
71 | 71 | { |
72 | 72 | if (root == null) |
73 | 73 | { |
74 | throw new IllegalArgumentException("root cannot be null"); | |
74 | throw new IllegalArgumentException("page tree root cannot be null"); | |
75 | 75 | } |
76 | 76 | // repair bad PDFs which contain a Page dict instead of a page tree, see PDFBOX-3154 |
77 | 77 | if (COSName.PAGE.equals(root.getCOSName(COSName.TYPE))) |
36 | 36 | import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderEffectDictionary; |
37 | 37 | import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; |
38 | 38 | import org.apache.pdfbox.util.DateConverter; |
39 | import org.w3c.dom.CDATASection; | |
39 | 40 | import org.w3c.dom.Element; |
40 | 41 | import org.w3c.dom.NamedNodeMap; |
41 | 42 | import org.w3c.dom.Node; |
42 | 43 | import org.w3c.dom.NodeList; |
44 | import org.w3c.dom.Text; | |
43 | 45 | |
44 | 46 | /** |
45 | 47 | * This represents an FDF annotation that is part of the FDF document. |
958 | 960 | |
959 | 961 | private String richContentsToString(Node node, boolean root) |
960 | 962 | { |
961 | String retval = ""; | |
962 | XPath xpath = XPathFactory.newInstance().newXPath(); | |
963 | try | |
964 | { | |
965 | NodeList nodelist = (NodeList) xpath.evaluate("*", node, XPathConstants.NODESET); | |
966 | String subString = ""; | |
967 | if (nodelist.getLength() == 0) | |
968 | { | |
969 | subString = node.getFirstChild().getNodeValue(); | |
970 | } | |
971 | for (int i = 0; i < nodelist.getLength(); i++) | |
972 | { | |
973 | Node child = nodelist.item(i); | |
974 | if (child instanceof Element) | |
975 | { | |
976 | subString += richContentsToString(child, false); | |
977 | } | |
978 | } | |
979 | NamedNodeMap attributes = node.getAttributes(); | |
980 | StringBuilder builder = new StringBuilder(); | |
981 | for (int i = 0; i < attributes.getLength(); i++) | |
982 | { | |
983 | Node attribute = attributes.item(i); | |
984 | builder.append(String.format(" %s=\"%s\"", attribute.getNodeName(), | |
985 | attribute.getNodeValue())); | |
986 | } | |
987 | if (root) | |
988 | { | |
989 | return subString; | |
990 | } | |
991 | retval = String.format("<%s%s>%s</%s>", node.getNodeName(), builder.toString(), | |
992 | subString, node.getNodeName()); | |
993 | } | |
994 | catch (XPathExpressionException e) | |
995 | { | |
996 | LOG.debug("Error while evaluating XPath expression for richtext contents"); | |
997 | } | |
998 | return retval; | |
999 | } | |
1000 | } | |
963 | String subString = ""; | |
964 | ||
965 | NodeList nodelist = node.getChildNodes(); | |
966 | for (int i = 0; i < nodelist.getLength(); i++) | |
967 | { | |
968 | Node child = nodelist.item(i); | |
969 | if (child instanceof Element) | |
970 | { | |
971 | subString += richContentsToString(child, false); | |
972 | } | |
973 | else if (child instanceof CDATASection) | |
974 | { | |
975 | subString += "<![CDATA[" + ((CDATASection) child).getData() + "]]>"; | |
976 | } | |
977 | else if (child instanceof Text) | |
978 | { | |
979 | String cdata = ((Text) child).getData(); | |
980 | if (cdata!=null) | |
981 | { | |
982 | cdata = cdata.replace("&", "&").replace("<", "<"); | |
983 | } | |
984 | subString += cdata; | |
985 | } | |
986 | } | |
987 | if (root) | |
988 | { | |
989 | return subString; | |
990 | } | |
991 | ||
992 | NamedNodeMap attributes = node.getAttributes(); | |
993 | StringBuilder builder = new StringBuilder(); | |
994 | for (int i = 0; i < attributes.getLength(); i++) | |
995 | { | |
996 | Node attribute = attributes.item(i); | |
997 | String attributeNodeValue = attribute.getNodeValue(); | |
998 | if (attributeNodeValue!=null) | |
999 | { | |
1000 | attributeNodeValue = attributeNodeValue.replace("\"", """); | |
1001 | } | |
1002 | builder.append(String.format(" %s=\"%s\"", attribute.getNodeName(), | |
1003 | attributeNodeValue)); | |
1004 | } | |
1005 | return String.format("<%s%s>%s</%s>", node.getNodeName(), builder.toString(), | |
1006 | subString, node.getNodeName()); | |
1007 | } | |
1008 | }⏎ |
157 | 157 | public float getWidthFromFont(int code) throws IOException |
158 | 158 | { |
159 | 159 | PDType3CharProc charProc = getCharProc(code); |
160 | if (charProc == null) | |
160 | if (charProc == null || charProc.getContentStream() == null || | |
161 | charProc.getContentStream().getLength() == 0) | |
161 | 162 | { |
162 | 163 | return 0; |
163 | 164 | } |
27 | 27 | public interface PDVectorFont |
28 | 28 | { |
29 | 29 | /** |
30 | * Returns the glyph path for the given character code. | |
30 | * Returns the glyph path for the given character code in a PDF. | |
31 | 31 | * |
32 | 32 | * @param code character code in a PDF. Not to be confused with unicode. |
33 | 33 | * @throws java.io.IOException if the font could not be read |
34 | 34 | */ |
35 | 35 | GeneralPath getPath(int code) throws IOException; |
36 | ||
36 | ||
37 | 37 | /** |
38 | * Returns true if this font contains a glyph for the given character code. | |
38 | * Returns true if this font contains a glyph for the given character code in a PDF. | |
39 | 39 | * |
40 | 40 | * @param code character code in a PDF. Not to be confused with unicode. |
41 | 41 | */ |
152 | 152 | @Override |
153 | 153 | public PDResources getResources() |
154 | 154 | { |
155 | COSDictionary resources = (COSDictionary) getCOSObject().getDictionaryObject(COSName.RESOURCES); | |
155 | COSDictionary resources = getCOSObject().getCOSDictionary(COSName.RESOURCES); | |
156 | 156 | if (resources != null) |
157 | 157 | { |
158 | 158 | return new PDResources(resources, cache); |
159 | } | |
160 | if (getCOSObject().containsKey(COSName.RESOURCES)) | |
161 | { | |
162 | // PDFBOX-4372 if the resource key exists but has nothing, return empty resources, | |
163 | // to avoid a self-reference (xobject form Fm0 contains "/Fm0 Do") | |
164 | // See also the mention of PDFBOX-1359 in PDFStreamEngine | |
165 | return new PDResources(); | |
159 | 166 | } |
160 | 167 | return null; |
161 | 168 | } |
+2
-7
16 | 16 | package org.apache.pdfbox.pdmodel.graphics.shading; |
17 | 17 | |
18 | 18 | import java.awt.Color; |
19 | import java.awt.Paint; | |
20 | 19 | import java.awt.PaintContext; |
21 | 20 | import java.awt.Rectangle; |
22 | 21 | import java.awt.RenderingHints; |
32 | 31 | * AWT Paint for axial shading. |
33 | 32 | * |
34 | 33 | */ |
35 | public class AxialShadingPaint implements Paint | |
34 | public class AxialShadingPaint extends ShadingPaint<PDShadingType2> | |
36 | 35 | { |
37 | 36 | private static final Log LOG = LogFactory.getLog(AxialShadingPaint.class); |
38 | ||
39 | private final PDShadingType2 shading; | |
40 | private final Matrix matrix; | |
41 | 37 | |
42 | 38 | /** |
43 | 39 | * Constructor. |
47 | 43 | */ |
48 | 44 | AxialShadingPaint(PDShadingType2 shadingType2, Matrix matrix) |
49 | 45 | { |
50 | shading = shadingType2; | |
51 | this.matrix = matrix; | |
46 | super(shadingType2, matrix); | |
52 | 47 | } |
53 | 48 | |
54 | 49 | @Override |
+2
-7
16 | 16 | package org.apache.pdfbox.pdmodel.graphics.shading; |
17 | 17 | |
18 | 18 | import java.awt.Color; |
19 | import java.awt.Paint; | |
20 | 19 | import java.awt.PaintContext; |
21 | 20 | import java.awt.Rectangle; |
22 | 21 | import java.awt.RenderingHints; |
33 | 32 | * AWT Paint for radial shading. |
34 | 33 | * |
35 | 34 | */ |
36 | public class RadialShadingPaint implements Paint | |
35 | public class RadialShadingPaint extends ShadingPaint<PDShadingType3> | |
37 | 36 | { |
38 | 37 | private static final Log LOG = LogFactory.getLog(RadialShadingPaint.class); |
39 | ||
40 | private final PDShadingType3 shading; | |
41 | private final Matrix matrix; | |
42 | 38 | |
43 | 39 | /** |
44 | 40 | * Constructor. |
48 | 44 | */ |
49 | 45 | RadialShadingPaint(PDShadingType3 shading, Matrix matrix) |
50 | 46 | { |
51 | this.shading = shading; | |
52 | this.matrix = matrix; | |
47 | super(shading, matrix); | |
53 | 48 | } |
54 | 49 | |
55 | 50 | @Override |
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.pdmodel.graphics.shading; | |
17 | ||
18 | import java.awt.Paint; | |
19 | ||
20 | import org.apache.pdfbox.util.Matrix; | |
21 | ||
22 | /** | |
23 | * This is base class for all PDShading-Paints to allow other low level libraries access to the | |
24 | * shading source data. One user of this interface is the PdfBoxGraphics2D-adapter. | |
25 | * | |
26 | * @param <T> the actual PDShading class. | |
27 | */ | |
28 | public abstract class ShadingPaint<T extends PDShading> implements Paint | |
29 | { | |
30 | protected final T shading; | |
31 | protected final Matrix matrix; | |
32 | ||
33 | ShadingPaint(T shading, Matrix matrix) | |
34 | { | |
35 | this.shading = shading; | |
36 | this.matrix = matrix; | |
37 | } | |
38 | ||
39 | /** | |
40 | * @return the PDShading of this paint | |
41 | */ | |
42 | public T getShading() | |
43 | { | |
44 | return shading; | |
45 | } | |
46 | ||
47 | /** | |
48 | * @return the active Matrix of this paint | |
49 | */ | |
50 | public Matrix getMatrix() | |
51 | { | |
52 | return matrix; | |
53 | } | |
54 | } |
+2
-7
16 | 16 | package org.apache.pdfbox.pdmodel.graphics.shading; |
17 | 17 | |
18 | 18 | import java.awt.Color; |
19 | import java.awt.Paint; | |
20 | 19 | import java.awt.PaintContext; |
21 | 20 | import java.awt.Rectangle; |
22 | 21 | import java.awt.RenderingHints; |
31 | 30 | /** |
32 | 31 | * AWT PaintContext for function-based (Type 1) shading. |
33 | 32 | */ |
34 | class Type1ShadingPaint implements Paint | |
33 | class Type1ShadingPaint extends ShadingPaint<PDShadingType1> | |
35 | 34 | { |
36 | 35 | private static final Log LOG = LogFactory.getLog(Type1ShadingPaint.class); |
37 | ||
38 | private final PDShadingType1 shading; | |
39 | private final Matrix matrix; | |
40 | 36 | |
41 | 37 | /** |
42 | 38 | * Constructor. |
46 | 42 | */ |
47 | 43 | Type1ShadingPaint(PDShadingType1 shading, Matrix matrix) |
48 | 44 | { |
49 | this.shading = shading; | |
50 | this.matrix = matrix; | |
45 | super(shading, matrix); | |
51 | 46 | } |
52 | 47 | |
53 | 48 | @Override |
+2
-6
31 | 31 | /** |
32 | 32 | * AWT PaintContext for Gouraud Triangle Mesh (Type 4) shading. |
33 | 33 | */ |
34 | class Type4ShadingPaint implements Paint | |
34 | class Type4ShadingPaint extends ShadingPaint<PDShadingType4> | |
35 | 35 | { |
36 | 36 | private static final Log LOG = LogFactory.getLog(Type4ShadingPaint.class); |
37 | ||
38 | private final PDShadingType4 shading; | |
39 | private final Matrix matrix; | |
40 | 37 | |
41 | 38 | /** |
42 | 39 | * Constructor. |
46 | 43 | */ |
47 | 44 | Type4ShadingPaint(PDShadingType4 shading, Matrix matrix) |
48 | 45 | { |
49 | this.shading = shading; | |
50 | this.matrix = matrix; | |
46 | super(shading, matrix); | |
51 | 47 | } |
52 | 48 | |
53 | 49 | @Override |
+2
-7
16 | 16 | package org.apache.pdfbox.pdmodel.graphics.shading; |
17 | 17 | |
18 | 18 | import java.awt.Color; |
19 | import java.awt.Paint; | |
20 | 19 | import java.awt.PaintContext; |
21 | 20 | import java.awt.Rectangle; |
22 | 21 | import java.awt.RenderingHints; |
31 | 30 | /** |
32 | 31 | * AWT Paint for Gouraud Triangle Lattice (Type 5) shading. |
33 | 32 | */ |
34 | class Type5ShadingPaint implements Paint | |
33 | class Type5ShadingPaint extends ShadingPaint<PDShadingType5> | |
35 | 34 | { |
36 | 35 | private static final Log LOG = LogFactory.getLog(Type5ShadingPaint.class); |
37 | ||
38 | private final PDShadingType5 shading; | |
39 | private final Matrix matrix; | |
40 | 36 | |
41 | 37 | /** |
42 | 38 | * Constructor. |
46 | 42 | */ |
47 | 43 | Type5ShadingPaint(PDShadingType5 shading, Matrix matrix) |
48 | 44 | { |
49 | this.shading = shading; | |
50 | this.matrix = matrix; | |
45 | super(shading, matrix); | |
51 | 46 | } |
52 | 47 | |
53 | 48 | @Override |
+2
-7
15 | 15 | package org.apache.pdfbox.pdmodel.graphics.shading; |
16 | 16 | |
17 | 17 | import java.awt.Color; |
18 | import java.awt.Paint; | |
19 | 18 | import java.awt.PaintContext; |
20 | 19 | import java.awt.Rectangle; |
21 | 20 | import java.awt.RenderingHints; |
33 | 32 | * |
34 | 33 | * @author Shaola Ren |
35 | 34 | */ |
36 | class Type6ShadingPaint implements Paint | |
35 | class Type6ShadingPaint extends ShadingPaint<PDShadingType6> | |
37 | 36 | { |
38 | 37 | private static final Log LOG = LogFactory.getLog(Type6ShadingPaint.class); |
39 | ||
40 | private final PDShadingType6 shading; | |
41 | private final Matrix matrix; | |
42 | 38 | |
43 | 39 | /** |
44 | 40 | * Constructor. |
48 | 44 | */ |
49 | 45 | Type6ShadingPaint(PDShadingType6 shading, Matrix matrix) |
50 | 46 | { |
51 | this.shading = shading; | |
52 | this.matrix = matrix; | |
47 | super(shading, matrix); | |
53 | 48 | } |
54 | 49 | |
55 | 50 | @Override |
+2
-7
15 | 15 | package org.apache.pdfbox.pdmodel.graphics.shading; |
16 | 16 | |
17 | 17 | import java.awt.Color; |
18 | import java.awt.Paint; | |
19 | 18 | import java.awt.PaintContext; |
20 | 19 | import java.awt.Rectangle; |
21 | 20 | import java.awt.RenderingHints; |
33 | 32 | * |
34 | 33 | * @author Shaola Ren |
35 | 34 | */ |
36 | class Type7ShadingPaint implements Paint | |
35 | class Type7ShadingPaint extends ShadingPaint<PDShadingType7> | |
37 | 36 | { |
38 | 37 | private static final Log LOG = LogFactory.getLog(Type7ShadingPaint.class); |
39 | ||
40 | private final PDShadingType7 shading; | |
41 | private final Matrix matrix; | |
42 | 38 | |
43 | 39 | /** |
44 | 40 | * Constructor. |
48 | 44 | */ |
49 | 45 | Type7ShadingPaint(PDShadingType7 shading, Matrix matrix) |
50 | 46 | { |
51 | this.shading = shading; | |
52 | this.matrix = matrix; | |
47 | super(shading, matrix); | |
53 | 48 | } |
54 | 49 | |
55 | 50 | @Override |
+9
-7
225 | 225 | yAxis = pageHeight - xAxis - imageWidth; |
226 | 226 | xAxis = temp; |
227 | 227 | |
228 | affineTransform = new AffineTransform( | |
229 | 0, imageHeight / imageWidth, -imageWidth / imageHeight, 0, imageWidth, 0); | |
230 | ||
228 | 231 | temp = imageHeight; |
229 | 232 | imageHeight = imageWidth; |
230 | 233 | imageWidth = temp; |
231 | ||
232 | affineTransform = new AffineTransform(0, 0.5, -2, 0, 100, 0); | |
233 | 234 | break; |
234 | ||
235 | ||
235 | 236 | case 180: |
236 | 237 | float newX = pageWidth - xAxis - imageWidth; |
237 | 238 | float newY = pageHeight - yAxis - imageHeight; |
238 | 239 | xAxis = newX; |
239 | 240 | yAxis = newY; |
240 | ||
241 | affineTransform = new AffineTransform(-1, 0, 0, -1, 100, 50); | |
241 | ||
242 | affineTransform = new AffineTransform(-1, 0, 0, -1, imageWidth, imageHeight); | |
242 | 243 | break; |
243 | 244 | |
244 | 245 | case 270: |
246 | 247 | xAxis = pageWidth - yAxis - imageHeight; |
247 | 248 | yAxis = temp; |
248 | 249 | |
250 | affineTransform = new AffineTransform( | |
251 | 0, -imageHeight / imageWidth, imageWidth / imageHeight, 0, 0, imageHeight); | |
252 | ||
249 | 253 | temp = imageHeight; |
250 | 254 | imageHeight = imageWidth; |
251 | 255 | imageWidth = temp; |
252 | ||
253 | affineTransform = new AffineTransform(0, -0.5, 2, 0, 0, 50); | |
254 | 256 | break; |
255 | 257 | |
256 | 258 | case 0: |
381 | 381 | // update the appearance state (AS) |
382 | 382 | for (PDAnnotationWidget widget : getWidgets()) |
383 | 383 | { |
384 | if (widget.getAppearance() == null) | |
385 | { | |
386 | continue; | |
387 | } | |
384 | 388 | PDAppearanceEntry appearanceEntry = widget.getAppearance().getNormalAppearance(); |
385 | 389 | if (((COSDictionary) appearanceEntry.getCOSObject()).containsKey(value)) |
386 | 390 | { |
387 | widget.getCOSObject().setName(COSName.AS, value); | |
391 | widget.setAppearanceState(value); | |
388 | 392 | } |
389 | 393 | else |
390 | 394 | { |
391 | widget.getCOSObject().setItem(COSName.AS, COSName.Off); | |
395 | widget.setAppearanceState(COSName.Off.getName()); | |
392 | 396 | } |
393 | 397 | } |
394 | 398 | } |
45 | 45 | class TilingPaint implements Paint |
46 | 46 | { |
47 | 47 | private static final Log LOG = LogFactory.getLog(TilingPaint.class); |
48 | private final TexturePaint paint; | |
48 | private final Paint paint; | |
49 | 49 | private final Matrix patternMatrix; |
50 | 50 | private static final int MAXEDGE; |
51 | 51 | private static final String DEFAULTMAXEDGE = "3000"; |
14 | 14 | */ |
15 | 15 | package org.apache.pdfbox.rendering; |
16 | 16 | |
17 | import java.awt.Paint; | |
17 | 18 | import java.awt.geom.AffineTransform; |
18 | 19 | import java.io.IOException; |
19 | 20 | import java.lang.ref.WeakReference; |
33 | 34 | class TilingPaintFactory |
34 | 35 | { |
35 | 36 | private final PageDrawer drawer; |
36 | private final Map<TilingPaintParameter, WeakReference<TilingPaint>> weakCache | |
37 | = new WeakHashMap<TilingPaintParameter, WeakReference<TilingPaint>>(); | |
37 | private final Map<TilingPaintParameter, WeakReference<Paint>> weakCache | |
38 | = new WeakHashMap<TilingPaintParameter, WeakReference<Paint>>(); | |
38 | 39 | |
39 | 40 | TilingPaintFactory(PageDrawer drawer) |
40 | 41 | { |
41 | 42 | this.drawer = drawer; |
42 | 43 | } |
43 | 44 | |
44 | TilingPaint create(PDTilingPattern pattern, PDColorSpace colorSpace, | |
45 | Paint create(PDTilingPattern pattern, PDColorSpace colorSpace, | |
45 | 46 | PDColor color, AffineTransform xform) throws IOException |
46 | 47 | { |
47 | TilingPaint paint = null; | |
48 | Paint paint = null; | |
48 | 49 | TilingPaintParameter tilingPaintParameter |
49 | 50 | = new TilingPaintParameter(drawer.getInitialMatrix(), pattern.getCOSObject(), colorSpace, color, xform); |
50 | WeakReference<TilingPaint> weakRef = weakCache.get(tilingPaintParameter); | |
51 | WeakReference<Paint> weakRef = weakCache.get(tilingPaintParameter); | |
51 | 52 | if (weakRef != null) |
52 | 53 | { |
53 | 54 | // PDFBOX-4058: additional WeakReference makes gc work better |
56 | 57 | if (paint == null) |
57 | 58 | { |
58 | 59 | paint = new TilingPaint(drawer, pattern, colorSpace, color, xform); |
59 | weakCache.put(tilingPaintParameter, new WeakReference<TilingPaint>(paint)); | |
60 | weakCache.put(tilingPaintParameter, new WeakReference<Paint>(paint)); | |
60 | 61 | } |
61 | 62 | return paint; |
62 | 63 | } |
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.cos; | |
17 | ||
18 | import org.junit.Assert; | |
19 | import org.junit.Rule; | |
20 | import org.junit.Test; | |
21 | import org.junit.rules.ExpectedException; | |
22 | ||
23 | public class COSObjectKeyTest | |
24 | { | |
25 | @Rule | |
26 | public ExpectedException thrown = ExpectedException.none(); | |
27 | ||
28 | @Test | |
29 | public void compareToInputNotNullOutputZero() | |
30 | { | |
31 | // Arrange | |
32 | final COSObjectKey objectUnderTest = new COSObjectKey(0L, 0); | |
33 | final COSObjectKey other = new COSObjectKey(0L, 0); | |
34 | ||
35 | // Act | |
36 | final int retval = objectUnderTest.compareTo(other); | |
37 | ||
38 | // Assert result | |
39 | Assert.assertEquals(0, retval); | |
40 | } | |
41 | ||
42 | @Test | |
43 | public void compareToInputNotNullOutputPositive() | |
44 | { | |
45 | // Arrange | |
46 | final COSObjectKey objectUnderTest = new COSObjectKey(0L, 0); | |
47 | final COSObjectKey other = new COSObjectKey(-9223372036854775808L, 0); | |
48 | ||
49 | // Act | |
50 | final int retval = objectUnderTest.compareTo(other); | |
51 | ||
52 | // Assert result | |
53 | Assert.assertEquals(1, retval); | |
54 | } | |
55 | } |
16 | 16 | |
17 | 17 | import java.awt.image.BufferedImage; |
18 | 18 | import java.io.File; |
19 | import java.io.FileOutputStream; | |
19 | 20 | import java.io.IOException; |
21 | import java.io.OutputStream; | |
20 | 22 | import java.util.HashSet; |
21 | 23 | import java.util.Set; |
22 | 24 | |
233 | 235 | doc.close(); |
234 | 236 | } |
235 | 237 | |
238 | /** | |
239 | * PDFBOX-4383: Test that file can be deleted after merge. | |
240 | * | |
241 | * @throws IOException | |
242 | */ | |
243 | public void testFileDeletion() throws IOException | |
244 | { | |
245 | File outFile = new File(TARGETTESTDIR, "PDFBOX-4383-result.pdf"); | |
246 | ||
247 | File inFile1 = new File(TARGETTESTDIR, "PDFBOX-4383-src1.pdf"); | |
248 | File inFile2 = new File(TARGETTESTDIR, "PDFBOX-4383-src2.pdf"); | |
249 | ||
250 | createSimpleFile(inFile1); | |
251 | createSimpleFile(inFile2); | |
252 | ||
253 | OutputStream out = new FileOutputStream(outFile); | |
254 | PDFMergerUtility merger = new PDFMergerUtility(); | |
255 | merger.setDestinationStream(out); | |
256 | merger.addSource(inFile1); | |
257 | merger.addSource(inFile2); | |
258 | merger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly()); | |
259 | out.close(); | |
260 | ||
261 | assertTrue(inFile1.delete()); | |
262 | assertTrue(inFile2.delete()); | |
263 | assertTrue(outFile.delete()); | |
264 | } | |
265 | ||
266 | private void createSimpleFile(File file) throws IOException | |
267 | { | |
268 | PDDocument doc = new PDDocument(); | |
269 | doc.addPage(new PDPage()); | |
270 | doc.save(file); | |
271 | doc.close(); | |
272 | } | |
273 | ||
236 | 274 | private class ElementCounter |
237 | 275 | { |
238 | 276 | int cnt = 0; |
307 | 307 | PDDocument.load(new File(TARGETPDFDIR, "genko_oc_shiryo1.pdf")).close(); |
308 | 308 | } |
309 | 309 | |
310 | /** | |
311 | * Test parsing the file from PDFBOX-4338, which brought an | |
312 | * ArrayIndexOutOfBoundsException before the bug was fixed. | |
313 | * | |
314 | * @throws IOException | |
315 | */ | |
316 | @Test | |
317 | public void testPDFBox4338() throws IOException | |
318 | { | |
319 | PDDocument.load(new File(TARGETPDFDIR, "PDFBOX-4338.pdf")).close(); | |
320 | } | |
321 | ||
322 | /** | |
323 | * Test parsing the file from PDFBOX-4339, which brought a | |
324 | * NullPointerException before the bug was fixed. | |
325 | * | |
326 | * @throws IOException | |
327 | */ | |
328 | @Test | |
329 | public void testPDFBox4339() throws IOException | |
330 | { | |
331 | PDDocument.load(new File(TARGETPDFDIR, "PDFBOX-4339.pdf")).close(); | |
332 | } | |
333 | ||
310 | 334 | private void executeParserTest(RandomAccessRead source, MemoryUsageSetting memUsageSetting) throws IOException |
311 | 335 | { |
312 | 336 | ScratchFile scratchFile = new ScratchFile(memUsageSetting); |
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.pdfwriter; | |
17 | ||
18 | import org.junit.Assert; | |
19 | import org.junit.Rule; | |
20 | import org.junit.Test; | |
21 | import org.junit.rules.ExpectedException; | |
22 | ||
23 | public class COSWriterXRefEntryTest | |
24 | { | |
25 | @Rule | |
26 | public ExpectedException thrown = ExpectedException.none(); | |
27 | ||
28 | @Test | |
29 | public void compareToInputNullOutputNegative() | |
30 | { | |
31 | // Arrange | |
32 | final COSWriterXRefEntry objectUnderTest = new COSWriterXRefEntry(0L, null, null); | |
33 | final COSWriterXRefEntry obj = null; | |
34 | ||
35 | // Act | |
36 | final int retval = objectUnderTest.compareTo(obj); | |
37 | ||
38 | // Assert result | |
39 | Assert.assertEquals(-1, retval); | |
40 | } | |
41 | } |
18 | 18 | import java.util.HashSet; |
19 | 19 | import java.util.Set; |
20 | 20 | import static org.junit.Assert.assertEquals; |
21 | import org.junit.Rule; | |
21 | 22 | import org.junit.Test; |
23 | import org.junit.rules.ExpectedException; | |
22 | 24 | |
23 | /** | |
24 | * @author Tilman Hausherr | |
25 | */ | |
26 | 25 | public class PageLayoutTest |
27 | 26 | { |
28 | 27 | /** |
28 | * @author Tilman Hausherr | |
29 | * | |
29 | 30 | * Test for completeness (PDFBOX-3362). |
30 | 31 | */ |
31 | 32 | @Test |
42 | 43 | assertEquals(PageLayout.values().length, pageLayoutSet.size()); |
43 | 44 | assertEquals(PageLayout.values().length, stringSet.size()); |
44 | 45 | } |
46 | ||
47 | /** | |
48 | * @author John Bergqvist | |
49 | */ | |
50 | @Rule | |
51 | public ExpectedException thrown = ExpectedException.none(); | |
52 | ||
53 | @Test | |
54 | public void fromStringInputNotNullOutputIllegalArgumentException() | |
55 | { | |
56 | // Arrange | |
57 | final String value = "SinglePag"; | |
58 | ||
59 | // Act | |
60 | thrown.expect(IllegalArgumentException.class); | |
61 | PageLayout.fromString(value); | |
62 | ||
63 | // Method is not expected to return due to exception thrown | |
64 | } | |
45 | 65 | } |
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.pdmodel; | |
17 | ||
18 | import org.junit.Assert; | |
19 | import org.junit.Rule; | |
20 | import org.junit.Test; | |
21 | import org.junit.rules.ExpectedException; | |
22 | ||
23 | public class PageModeTest | |
24 | { | |
25 | @Rule | |
26 | public ExpectedException thrown = ExpectedException.none(); | |
27 | ||
28 | @Test | |
29 | public void fromStringInputNotNullOutputNotNull() | |
30 | { | |
31 | // Arrange | |
32 | final String value = "FullScreen"; | |
33 | ||
34 | // Act | |
35 | final PageMode retval = PageMode.fromString(value); | |
36 | ||
37 | // Assert result | |
38 | Assert.assertEquals(PageMode.FULL_SCREEN, retval); | |
39 | } | |
40 | ||
41 | @Test | |
42 | public void fromStringInputNotNullOutputNotNull2() | |
43 | { | |
44 | // Arrange | |
45 | final String value = "UseThumbs"; | |
46 | ||
47 | // Act | |
48 | final PageMode retval = PageMode.fromString(value); | |
49 | ||
50 | // Assert result | |
51 | Assert.assertEquals(PageMode.USE_THUMBS, retval); | |
52 | } | |
53 | ||
54 | @Test | |
55 | public void fromStringInputNotNullOutputNotNull3() | |
56 | { | |
57 | // Arrange | |
58 | final String value = "UseOC"; | |
59 | ||
60 | // Act | |
61 | final PageMode retval = PageMode.fromString(value); | |
62 | ||
63 | // Assert result | |
64 | Assert.assertEquals(PageMode.USE_OPTIONAL_CONTENT, retval); | |
65 | } | |
66 | ||
67 | @Test | |
68 | public void fromStringInputNotNullOutputNotNull4() | |
69 | { | |
70 | // Arrange | |
71 | final String value = "UseNone"; | |
72 | ||
73 | // Act | |
74 | final PageMode retval = PageMode.fromString(value); | |
75 | ||
76 | // Assert result | |
77 | Assert.assertEquals(PageMode.USE_NONE, retval); | |
78 | } | |
79 | ||
80 | @Test | |
81 | public void fromStringInputNotNullOutputNotNull5() | |
82 | { | |
83 | // Arrange | |
84 | final String value = "UseAttachments"; | |
85 | ||
86 | // Act | |
87 | final PageMode retval = PageMode.fromString(value); | |
88 | ||
89 | // Assert result | |
90 | Assert.assertEquals(PageMode.USE_ATTACHMENTS, retval); | |
91 | } | |
92 | ||
93 | @Test | |
94 | public void fromStringInputNotNullOutputNotNull6() | |
95 | { | |
96 | // Arrange | |
97 | final String value = "UseOutlines"; | |
98 | ||
99 | // Act | |
100 | final PageMode retval = PageMode.fromString(value); | |
101 | ||
102 | // Assert result | |
103 | Assert.assertEquals(PageMode.USE_OUTLINES, retval); | |
104 | } | |
105 | ||
106 | @Test | |
107 | public void fromStringInputNotNullOutputIllegalArgumentException() | |
108 | { | |
109 | // Arrange | |
110 | final String value = ""; | |
111 | ||
112 | // Act | |
113 | thrown.expect(IllegalArgumentException.class); | |
114 | PageMode.fromString(value); | |
115 | ||
116 | // Method is not expected to return due to exception thrown | |
117 | } | |
118 | ||
119 | @Test | |
120 | public void fromStringInputNotNullOutputIllegalArgumentException2() | |
121 | { | |
122 | // Arrange | |
123 | final String value = "Dulacb`ecj"; | |
124 | ||
125 | // Act | |
126 | thrown.expect(IllegalArgumentException.class); | |
127 | PageMode.fromString(value); | |
128 | ||
129 | // Method is not expected to return due to exception thrown | |
130 | } | |
131 | ||
132 | @Test | |
133 | public void stringValueOutputNotNull() | |
134 | { | |
135 | // Arrange | |
136 | final PageMode objectUnderTest = PageMode.USE_OPTIONAL_CONTENT; | |
137 | ||
138 | // Act | |
139 | final String retval = objectUnderTest.stringValue(); | |
140 | ||
141 | // Assert result | |
142 | Assert.assertEquals("UseOC", retval); | |
143 | } | |
144 | } |
22 | 22 | import java.net.URISyntaxException; |
23 | 23 | import java.util.List; |
24 | 24 | |
25 | import org.junit.Assert; | |
25 | 26 | import org.junit.Test; |
26 | 27 | |
27 | 28 | /** |
38 | 39 | File f = new File(FDFAnnotationTest.class.getResource("xfdf-test-document-annotations.xml").toURI()); |
39 | 40 | FDFDocument fdfDoc = FDFDocument.loadXFDF(f); |
40 | 41 | List<FDFAnnotation> fdfAnnots = fdfDoc.getCatalog().getFDF().getAnnotations(); |
41 | assertEquals(17, fdfAnnots.size()); | |
42 | assertEquals(18, fdfAnnots.size()); | |
43 | ||
44 | // test PDFBOX-4345 and PDFBOX-3646 | |
45 | boolean testedPDFBox4345andPDFBox3646 = false; | |
46 | for (FDFAnnotation ann : fdfAnnots) | |
47 | { | |
48 | if (ann instanceof FDFAnnotationFreeText) | |
49 | { | |
50 | FDFAnnotationFreeText annotationFreeText = (FDFAnnotationFreeText) ann; | |
51 | if ("P&1 P&2 P&3".equals(annotationFreeText.getContents())) | |
52 | { | |
53 | testedPDFBox4345andPDFBox3646 = true; | |
54 | Assert.assertEquals("<body style=\"font:12pt Helvetica; " | |
55 | + "color:#D66C00;\" xfa:APIVersion=\"Acrobat:7.0.8\" " | |
56 | + "xfa:spec=\"2.0.2\" xmlns=\"http://www.w3.org/1999/xhtml\" " | |
57 | + "xmlns:xfa=\"http://www.xfa.org/schema/xfa-data/1.0/\">\n" | |
58 | + " <p dir=\"ltr\">P&1 <span style=\"text-" | |
59 | + "decoration:word;font-family:Helvetica\">P&2</span> " | |
60 | + "P&3</p>\n" | |
61 | + " </body>", annotationFreeText.getRichContents().trim()); | |
62 | } | |
63 | } | |
64 | } | |
65 | Assert.assertTrue(testedPDFBox4345andPDFBox3646); | |
42 | 66 | fdfDoc.close(); |
43 | 67 | } |
44 | 68 | }⏎ |
15 | 15 | package org.apache.pdfbox.pdmodel.graphics.image; |
16 | 16 | |
17 | 17 | import java.awt.image.BufferedImage; |
18 | import java.io.ByteArrayOutputStream; | |
19 | 18 | import java.io.File; |
20 | 19 | import java.io.IOException; |
20 | import java.io.OutputStream; | |
21 | 21 | import java.util.HashSet; |
22 | 22 | import java.util.Set; |
23 | 23 | import javax.imageio.ImageIO; |
24 | import javax.imageio.ImageWriter; | |
25 | import javax.imageio.spi.ImageWriterSpi; | |
24 | 26 | import org.apache.pdfbox.cos.COSName; |
25 | 27 | import org.apache.pdfbox.cos.COSStream; |
26 | 28 | import org.apache.pdfbox.pdmodel.PDDocument; |
60 | 62 | assertEquals(ximage.getWidth(), ximage.getImage().getWidth()); |
61 | 63 | assertEquals(ximage.getHeight(), ximage.getImage().getHeight()); |
62 | 64 | |
63 | boolean writeOk = ImageIO.write(ximage.getImage(), | |
64 | format, new ByteArrayOutputStream()); | |
65 | boolean canEncode = true; | |
66 | boolean writeOk; | |
67 | // jdk11+ no longer encodes ARGB jpg | |
68 | // https://bugs.openjdk.java.net/browse/JDK-8211748 | |
69 | if ("jpg".equals(format) && | |
70 | ximage.getImage().getType() == BufferedImage.TYPE_INT_ARGB) | |
71 | { | |
72 | ImageWriter writer = ImageIO.getImageWritersBySuffix(format).next(); | |
73 | ImageWriterSpi originatingProvider = writer.getOriginatingProvider(); | |
74 | canEncode = originatingProvider.canEncodeImage(ximage.getImage()); | |
75 | } | |
76 | if (canEncode) | |
77 | { | |
78 | writeOk = ImageIO.write(ximage.getImage(), format, new NullOutputStream()); | |
79 | assertTrue(writeOk); | |
80 | } | |
81 | writeOk = ImageIO.write(ximage.getOpaqueImage(), format, new NullOutputStream()); | |
65 | 82 | assertTrue(writeOk); |
66 | writeOk = ImageIO.write(ximage.getOpaqueImage(), | |
67 | format, new ByteArrayOutputStream()); | |
68 | assertTrue(writeOk); | |
83 | } | |
84 | ||
85 | private static class NullOutputStream extends OutputStream | |
86 | { | |
87 | @Override | |
88 | public void write(int b) throws IOException | |
89 | { | |
90 | } | |
69 | 91 | } |
70 | 92 | |
71 | 93 | static int colorCount(BufferedImage bim) |
+106
-0
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.pdmodel.graphics.state; | |
17 | ||
18 | import org.junit.Assert; | |
19 | import org.junit.Rule; | |
20 | import org.junit.Test; | |
21 | import org.junit.rules.ExpectedException; | |
22 | ||
23 | public class RenderingIntentTest | |
24 | { | |
25 | @Rule | |
26 | public ExpectedException thrown = ExpectedException.none(); | |
27 | ||
28 | @Test | |
29 | public void fromStringInputNotNullOutputNotNull() | |
30 | { | |
31 | // Arrange | |
32 | final String value = "AbsoluteColorimetric"; | |
33 | ||
34 | // Act | |
35 | final RenderingIntent retval = RenderingIntent.fromString(value); | |
36 | ||
37 | // Assert result | |
38 | Assert.assertEquals(RenderingIntent.ABSOLUTE_COLORIMETRIC, retval); | |
39 | } | |
40 | ||
41 | @Test | |
42 | public void fromStringInputNotNullOutputNotNull2() | |
43 | { | |
44 | // Arrange | |
45 | final String value = "RelativeColorimetric"; | |
46 | ||
47 | // Act | |
48 | final RenderingIntent retval = RenderingIntent.fromString(value); | |
49 | ||
50 | // Assert result | |
51 | Assert.assertEquals(RenderingIntent.RELATIVE_COLORIMETRIC, retval); | |
52 | } | |
53 | ||
54 | @Test | |
55 | public void fromStringInputNotNullOutputNotNull3() | |
56 | { | |
57 | // Arrange | |
58 | final String value = "Perceptual"; | |
59 | ||
60 | // Act | |
61 | final RenderingIntent retval = RenderingIntent.fromString(value); | |
62 | ||
63 | // Assert result | |
64 | Assert.assertEquals(RenderingIntent.PERCEPTUAL, retval); | |
65 | } | |
66 | ||
67 | @Test | |
68 | public void fromStringInputNotNullOutputNotNull4() | |
69 | { | |
70 | // Arrange | |
71 | final String value = "Saturation"; | |
72 | ||
73 | // Act | |
74 | final RenderingIntent retval = RenderingIntent.fromString(value); | |
75 | ||
76 | // Assert result | |
77 | Assert.assertEquals(RenderingIntent.SATURATION, retval); | |
78 | } | |
79 | ||
80 | @Test | |
81 | public void fromStringInputNotNullOutputNotNull5() | |
82 | { | |
83 | // Arrange | |
84 | final String value = ""; | |
85 | ||
86 | // Act | |
87 | final RenderingIntent retval = RenderingIntent.fromString(value); | |
88 | ||
89 | // Assert result | |
90 | Assert.assertEquals(RenderingIntent.RELATIVE_COLORIMETRIC, retval); | |
91 | } | |
92 | ||
93 | @Test | |
94 | public void stringValueOutputNotNull() | |
95 | { | |
96 | // Arrange | |
97 | final RenderingIntent objectUnderTest = RenderingIntent.ABSOLUTE_COLORIMETRIC; | |
98 | ||
99 | // Act | |
100 | final String retval = objectUnderTest.stringValue(); | |
101 | ||
102 | // Assert result | |
103 | Assert.assertEquals("AbsoluteColorimetric", retval); | |
104 | } | |
105 | } |
24 | 24 | import junit.framework.TestSuite; |
25 | 25 | |
26 | 26 | import org.apache.pdfbox.cos.COSArray; |
27 | import org.apache.pdfbox.cos.COSDictionary; | |
27 | 28 | import org.apache.pdfbox.cos.COSName; |
28 | 29 | import org.apache.pdfbox.pdmodel.PDDocument; |
30 | import org.apache.pdfbox.pdmodel.PDPage; | |
31 | import org.apache.pdfbox.pdmodel.common.PDRectangle; | |
32 | import org.apache.pdfbox.pdmodel.graphics.color.PDColor; | |
33 | import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; | |
34 | import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget; | |
35 | import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceCharacteristicsDictionary; | |
36 | import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; | |
29 | 37 | |
30 | 38 | /** |
31 | * This will test the functionality of Radio Buttons in PDFBox. | |
39 | * This will test the functionality of checkboxes in PDFBox. | |
32 | 40 | */ |
33 | 41 | public class TestCheckBox extends TestCase |
34 | 42 | { |
65 | 73 | } |
66 | 74 | |
67 | 75 | /** |
68 | * This will test the radio button PDModel. | |
76 | * This will test the checkbox PDModel. | |
69 | 77 | * |
70 | 78 | * @throws IOException If there is an error creating the field. |
71 | 79 | */ |
105 | 113 | checkBox.setExportValues(null); |
106 | 114 | assertNull(checkBox.getCOSObject().getItem(COSName.OPT)); |
107 | 115 | // if there is no Opt entry an empty List shall be returned |
108 | assertEquals(checkBox.getExportValues(), new ArrayList<String>()); | |
116 | assertTrue(checkBox.getExportValues().isEmpty()); | |
109 | 117 | } |
110 | 118 | finally |
111 | 119 | { |
115 | 123 | } |
116 | 124 | } |
117 | 125 | } |
126 | ||
127 | /** | |
128 | * PDFBOX-4366: Create and test a checkbox with no /AP. The created file works with Adobe Reader! | |
129 | * | |
130 | * @throws IOException | |
131 | */ | |
132 | public void testCheckBoxNoAppearance() throws IOException | |
133 | { | |
134 | PDDocument doc = new PDDocument(); | |
135 | PDPage page = new PDPage(); | |
136 | doc.addPage(page); | |
137 | PDAcroForm acroForm = new PDAcroForm(doc); | |
138 | acroForm.setNeedAppearances(true); // need this or it won't appear on Adobe Reader | |
139 | doc.getDocumentCatalog().setAcroForm(acroForm); | |
140 | List<PDField> fields = new ArrayList<PDField>(); | |
141 | PDCheckBox checkBox = new PDCheckBox(acroForm); | |
142 | checkBox.setPartialName("checkbox"); | |
143 | PDAnnotationWidget widget = checkBox.getWidgets().get(0); | |
144 | widget.setRectangle(new PDRectangle(50, 600, 100, 100)); | |
145 | PDBorderStyleDictionary bs = new PDBorderStyleDictionary(); | |
146 | bs.setStyle(PDBorderStyleDictionary.STYLE_SOLID); | |
147 | bs.setWidth(1); | |
148 | COSDictionary acd = new COSDictionary(); | |
149 | PDAppearanceCharacteristicsDictionary ac = new PDAppearanceCharacteristicsDictionary(acd); | |
150 | ac.setBackground(new PDColor(new float[] { 1, 1, 0 }, PDDeviceRGB.INSTANCE)); | |
151 | ac.setBorderColour(new PDColor(new float[] { 1, 0, 0 }, PDDeviceRGB.INSTANCE)); | |
152 | ac.setNormalCaption("4"); // 4 is checkmark, 8 is cross | |
153 | widget.setAppearanceCharacteristics(ac); | |
154 | widget.setBorderStyle(bs); | |
155 | checkBox.setValue("Off"); | |
156 | fields.add(checkBox); | |
157 | page.getAnnotations().add(widget); | |
158 | acroForm.setFields(fields); | |
159 | assertEquals("Off", checkBox.getValue()); | |
160 | doc.close(); | |
161 | } | |
118 | 162 | }⏎ |
+82
-0
0 | %PDF-1.7 | |
1 | %цдья | |
2 | 1 0 obj | |
3 | << | |
4 | /Type /Catalog | |
5 | /Version /1.4 | |
6 | /Pages 2 0 R | |
7 | /ViewerPreferences 3 0 R | |
8 | >> | |
9 | endobj | |
10 | 2 0 obj | |
11 | << | |
12 | /Type /Pages | |
13 | /Kids [4 0 R] | |
14 | /Count 1 | |
15 | >> | |
16 | endobj | |
17 | 3 0 obj | |
18 | << | |
19 | /Direction /L2R | |
20 | >> | |
21 | endobj | |
22 | 4 0 obj | |
23 | << | |
24 | /Contents 5 0 R | |
25 | /MediaBox [0.0 0.0 595.276 595.276] | |
26 | /Parent 2 0 R | |
27 | /Resources 6 0 R | |
28 | /Type /Page | |
29 | >> | |
30 | endobj | |
31 | 5 0 obj | |
32 | << | |
33 | /Length 9 | |
34 | >> | |
35 | stream | |
36 | /Fm0 Do | |
37 | ||
38 | endstream | |
39 | endobj | |
40 | 6 0 obj | |
41 | << | |
42 | /XObject 7 0 R | |
43 | >> | |
44 | endobj | |
45 | 7 0 obj | |
46 | << | |
47 | /Fm0 8 0 R | |
48 | >> | |
49 | endobj | |
50 | 8 0 obj | |
51 | << | |
52 | /Length 7 | |
53 | /BBox [-16.4768 477.771 616.049 439.202] | |
54 | /Matrix [1 0 0 1 0 0] | |
55 | /Resources null | |
56 | /Subtype /Form | |
57 | >> | |
58 | stream | |
59 | /Fm0 Do | |
60 | endstream | |
61 | endobj | |
62 | xref | |
63 | 0 9 | |
64 | 0000000000 65535 f | |
65 | 0000000015 00000 n | |
66 | 0000000103 00000 n | |
67 | 0000000160 00000 n | |
68 | 0000000197 00000 n | |
69 | 0000000313 00000 n | |
70 | 0000000373 00000 n | |
71 | 0000000409 00000 n | |
72 | 0000000441 00000 n | |
73 | trailer | |
74 | << | |
75 | /Root 1 0 R | |
76 | /ID [<BC99C0C77FC82B57E84060F605632DC1> <BC99C0C77FC82B57E84060F605632DC1>] | |
77 | /Size 9 | |
78 | >> | |
79 | startxref | |
80 | 593 | |
81 | %%EOF |
pdfbox/src/test/resources/input/rendering/PDFBOX-4372-2DAYCLVOFG3FTVO4RMAJJL3VTPNYDFRO-p4_reduced.pdf-1.png
less
more
Binary diff not shown
+11
-0
64 | 64 | <link width="0" page="3" rect="345.110000,457.870000,499.680000,471.040000" opacity="1" rotation="0" actiontype="URI" target="https://www.dropbox.com/help/26" /> |
65 | 65 | <link width="0" page="3" rect="158.240000,205.870000,273.730000,219.040000" opacity="1" rotation="0" actiontype="URI" target="http://www.dropbox.com/anywhere" /> |
66 | 66 | <link width="0" page="3" rect="105.110000,187.870000,217.190000,201.040000" opacity="1" rotation="0" actiontype="URI" target="http://www.dropbox.com" /> |
67 | <!-- the following one only to test PDFBOX-4345 and PDFBOX-3646. Might appear over another --> | |
68 | <freetext page="0" date="D:20150415150448+00'00'" title="weblogic" rect="400.430000,487.270000,562.980000,571.310000" flags="print" name="88D147A8-CC51-4178-8102-8C63E2C90F09" creationdate="D:20150415150432+00'00'" opacity="1" rotation="0" width="2" style="solid" justification="left"> | |
69 | <contents-richtext> | |
70 | <body | |
71 | xmlns="http://www.w3.org/1999/xhtml" | |
72 | xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" style="font:12pt Helvetica; color:#D66C00;" xfa:APIVersion="Acrobat:7.0.8" xfa:spec="2.0.2"> | |
73 | <p dir="ltr">P&1 <span style="text-decoration:word;font-family:Helvetica">P&2</span> P&3</p> | |
74 | </body> | |
75 | </contents-richtext> | |
76 | <defaultappearance>/Helvetica 12 Tf 0.842 0.424 0.000 rg</defaultappearance> | |
77 | </freetext> | |
67 | 78 | </annots> |
68 | 79 | </xfdf>⏎ |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.12</version> | |
25 | <version>2.0.13</version> | |
26 | 26 | <relativePath>parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 | |
33 | 33 | |
34 | 34 | <scm> |
35 | 35 | <connection> |
36 | scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/2.0.12 | |
36 | scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/2.0.13 | |
37 | 37 | </connection> |
38 | 38 | <developerConnection> |
39 | scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/2.0.12 | |
39 | scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/2.0.13 | |
40 | 40 | </developerConnection> |
41 | <url>http://svn.apache.org/viewvc/pdfbox/tags/2.0.12</url> | |
41 | <url>http://svn.apache.org/viewvc/pdfbox/tags/2.0.13</url> | |
42 | 42 | </scm> |
43 | 43 | |
44 | 44 | <modules> |
25 | 25 | <parent> |
26 | 26 | <groupId>org.apache.pdfbox</groupId> |
27 | 27 | <artifactId>pdfbox-parent</artifactId> |
28 | <version>2.0.12</version> | |
28 | <version>2.0.13</version> | |
29 | 29 | <relativePath>../parent/pom.xml</relativePath> |
30 | 30 | </parent> |
31 | 31 | |
33 | 33 | <properties> |
34 | 34 | <skip-bavaria>true</skip-bavaria> |
35 | 35 | </properties> |
36 | ||
37 | <profiles> | |
38 | <profile> | |
39 | <activation> | |
40 | <jdk>[11,)</jdk> | |
41 | </activation> | |
42 | <dependencies> | |
43 | <dependency> | |
44 | <groupId>javax.xml.bind</groupId> | |
45 | <artifactId>jaxb-api</artifactId> | |
46 | <scope>provided</scope> | |
47 | </dependency> | |
48 | <dependency> | |
49 | <groupId>javax.activation</groupId> | |
50 | <artifactId>activation</artifactId> | |
51 | <scope>provided</scope> | |
52 | </dependency> | |
53 | </dependencies> | |
54 | </profile> | |
55 | </profiles> | |
36 | 56 | |
37 | 57 | <build> |
38 | 58 | <plugins> |
191 | 211 | <groupId>junit</groupId> |
192 | 212 | <artifactId>junit</artifactId> |
193 | 213 | </dependency> |
194 | <dependency> | |
195 | <groupId>log4j</groupId> | |
196 | <artifactId>log4j</artifactId> | |
197 | <scope>test</scope> | |
198 | </dependency> | |
199 | 214 | <!-- TODO find a suitable place to store the isator test pdfs <dependency> |
200 | 215 | <groupId>org.pdfa</groupId> <artifactId>isartor</artifactId> <version>1.0-20080813</version> |
201 | 216 | <scope>test</scope> </dependency> --> |
113 | 113 | */ |
114 | 114 | private ColorSpaceHelperFactory colorSpaceHelperFact; |
115 | 115 | |
116 | /** | |
117 | * Define the maximum number of errors. | |
118 | */ | |
119 | private int maxErrors = 10000; | |
120 | ||
116 | 121 | public static PreflightConfiguration createPdfA1BConfiguration() |
117 | 122 | { |
118 | 123 | PreflightConfiguration configuration = new PreflightConfiguration(); |
299 | 304 | this.colorSpaceHelperFact = colorSpaceHelperFact; |
300 | 305 | } |
301 | 306 | |
307 | /** | |
308 | * Get the maximum number of errors after which to abort when possible. | |
309 | * | |
310 | * @return | |
311 | */ | |
312 | public int getMaxErrors() | |
313 | { | |
314 | return maxErrors; | |
315 | } | |
316 | ||
317 | /** | |
318 | * Set the maximum number of errors after which to abort when possible. | |
319 | * | |
320 | * @param maxErrors | |
321 | */ | |
322 | public void setMaxErrors(int maxErrors) | |
323 | { | |
324 | this.maxErrors = maxErrors; | |
325 | } | |
302 | 326 | } |
50 | 50 | /** |
51 | 51 | * The datasource to load the document from. Needed by StreamValidationProcess. |
52 | 52 | */ |
53 | private DataSource source = null; | |
53 | private DataSource dataSource = null; | |
54 | 54 | |
55 | 55 | /** |
56 | 56 | * Contains all Xref/trailer objects and resolves them into single object using startxref reference. |
83 | 83 | /** |
84 | 84 | * Create the DocumentHandler using the DataSource which represent the PDF file to check. |
85 | 85 | * |
86 | * @param source | |
87 | */ | |
88 | public PreflightContext(DataSource source) | |
89 | { | |
90 | this.source = source; | |
91 | } | |
92 | ||
93 | public PreflightContext(DataSource source, PreflightConfiguration configuration) | |
94 | { | |
95 | this.source = source; | |
86 | * @param dataSource | |
87 | */ | |
88 | public PreflightContext(DataSource dataSource) | |
89 | { | |
90 | this.dataSource = dataSource; | |
91 | } | |
92 | ||
93 | public PreflightContext(DataSource dataSource, PreflightConfiguration configuration) | |
94 | { | |
95 | this.dataSource = dataSource; | |
96 | 96 | this.config = configuration; |
97 | 97 | } |
98 | 98 | |
147 | 147 | */ |
148 | 148 | public DataSource getSource() |
149 | 149 | { |
150 | return source; | |
150 | return dataSource; | |
151 | 151 | } |
152 | 152 | |
153 | 153 | public boolean isComplete() |
154 | 154 | { |
155 | return (document != null) && (source != null); | |
155 | return (document != null) && (dataSource != null); | |
156 | 156 | } |
157 | 157 | |
158 | 158 | /** |
156 | 156 | public void validate() throws ValidationException |
157 | 157 | { |
158 | 158 | // force early class loading to check if people forgot to use --add-modules javax.xml.bind |
159 | // on java 9 or later | |
159 | // on java 9 & 10, or to add jaxb-api on java 11 and later | |
160 | 160 | javax.xml.bind.DatatypeConverter.parseInt("0"); |
161 | 161 | context.setConfig(config); |
162 | 162 | Collection<String> processes = config.getProcessNames(); |
42 | 42 | { |
43 | 43 | |
44 | 44 | |
45 | public Element validate(DataSource source) throws IOException | |
45 | public Element validate(DataSource dataSource) throws IOException | |
46 | 46 | { |
47 | 47 | try |
48 | 48 | { |
49 | 49 | Document rdocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); |
50 | return validate(rdocument,source); | |
50 | return validate(rdocument,dataSource); | |
51 | 51 | } |
52 | 52 | catch (ParserConfigurationException e) |
53 | 53 | { |
56 | 56 | } |
57 | 57 | |
58 | 58 | |
59 | public Element validate(Document rdocument, DataSource source) throws IOException | |
59 | public Element validate(Document rdocument, DataSource dataSource) throws IOException | |
60 | 60 | { |
61 | 61 | String pdfType = null; |
62 | 62 | ValidationResult result; |
63 | 63 | long before = System.currentTimeMillis(); |
64 | 64 | try |
65 | 65 | { |
66 | PreflightParser parser = new PreflightParser(source); | |
66 | PreflightParser parser = new PreflightParser(dataSource); | |
67 | 67 | try |
68 | 68 | { |
69 | 69 | parser.parse(); |
81 | 81 | catch(Exception e) |
82 | 82 | { |
83 | 83 | long after = System.currentTimeMillis(); |
84 | return generateFailureResponse(rdocument, source.getName(), after-before, pdfType, e); | |
84 | return generateFailureResponse(rdocument, dataSource.getName(), after-before, pdfType, e); | |
85 | 85 | } |
86 | 86 | |
87 | 87 | long after = System.currentTimeMillis(); |
88 | 88 | if (result.isValid()) |
89 | 89 | { |
90 | Element preflight = generateResponseSkeleton(rdocument, source.getName(), after-before); | |
90 | Element preflight = generateResponseSkeleton(rdocument, dataSource.getName(), after-before); | |
91 | 91 | // valid ? |
92 | 92 | Element valid = rdocument.createElement("isValid"); |
93 | 93 | valid.setAttribute("type", pdfType); |
97 | 97 | } |
98 | 98 | else |
99 | 99 | { |
100 | Element preflight = generateResponseSkeleton(rdocument, source.getName(), after-before); | |
100 | Element preflight = generateResponseSkeleton(rdocument, dataSource.getName(), after-before); | |
101 | 101 | // valid ? |
102 | 102 | createResponseWithError(rdocument, pdfType, result, preflight); |
103 | 103 | return preflight; |
+14
-4
27 | 27 | |
28 | 28 | import org.apache.pdfbox.pdmodel.PDDocumentCatalog; |
29 | 29 | import org.apache.pdfbox.pdmodel.PDPage; |
30 | import org.apache.pdfbox.preflight.PreflightConstants; | |
30 | 31 | import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_PDF_PROCESSING_MISSING; |
31 | 32 | import org.apache.pdfbox.preflight.PreflightContext; |
32 | 33 | import org.apache.pdfbox.preflight.ValidationResult.ValidationError; |
49 | 50 | "/Pages dictionary entry is missing in document catalog")); |
50 | 51 | return; |
51 | 52 | } |
52 | int numPages = context.getDocument().getNumberOfPages(); | |
53 | for (int i = 0; i < numPages; i++) | |
53 | int p = 0; | |
54 | for (PDPage page : context.getDocument().getPages()) | |
54 | 55 | { |
55 | context.setCurrentPageNumber(i); | |
56 | validatePage(context, context.getDocument().getPage(i)); | |
56 | context.setCurrentPageNumber(p); | |
57 | validatePage(context, page); | |
58 | ||
59 | if (context.getDocument().getResult().getErrorsList().size() > context.getConfig().getMaxErrors()) | |
60 | { | |
61 | context.addValidationError(new ValidationError(PreflightConstants.ERROR_UNKOWN_ERROR, | |
62 | "Over " + context.getConfig().getMaxErrors() + | |
63 | " errors, page tree validation process aborted")); | |
64 | break; | |
65 | } | |
57 | 66 | context.setCurrentPageNumber(null); |
67 | ++p; | |
58 | 68 | } |
59 | 69 | } |
60 | 70 | else |
+1
-1
358 | 358 | * @throws Exception |
359 | 359 | */ |
360 | 360 | @Test |
361 | public void testAllInfoSynhcronized() throws Exception | |
361 | public void testAllInfoSynchronized() throws Exception | |
362 | 362 | { |
363 | 363 | initValues(); |
364 | 364 |
0 | <?xml version="1.0" encoding="UTF-8" ?> | |
1 | <!-- | |
2 | ! Licensed to the Apache Software Foundation (ASF) under one or more | |
3 | ! contributor license agreements. See the NOTICE file distributed with | |
4 | ! this work for additional information regarding copyright ownership. | |
5 | ! The ASF licenses this file to You under the Apache License, Version 2.0 | |
6 | ! (the "License"); you may not use this file except in compliance with | |
7 | ! the License. You may obtain a copy of the License at | |
8 | ! | |
9 | ! http://www.apache.org/licenses/LICENSE-2.0 | |
10 | ! | |
11 | ! Unless required by applicable law or agreed to in writing, software | |
12 | ! distributed under the License is distributed on an "AS IS" BASIS, | |
13 | ! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | ! See the License for the specific language governing permissions and | |
15 | ! limitations under the License. | |
16 | !--> | |
17 | <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> | |
18 | <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'> | |
19 | <appender name="console" class="org.apache.log4j.ConsoleAppender"> | |
20 | <layout class="org.apache.log4j.PatternLayout"> | |
21 | <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %C:%L - %m%n" /> | |
22 | </layout> | |
23 | </appender> | |
24 | <root> | |
25 | <level value="WARN" /> | |
26 | <appender-ref ref="console" /> | |
27 | </root> | |
28 | </log4j:configuration>⏎ |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.12</version> | |
25 | <version>2.0.13</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 | |
79 | 79 | <Embed-Transitive>true</Embed-Transitive> |
80 | 80 | <Embed-Dependency>*;scope=provided;inline=org/apache/**|org/bouncycastle/**|com/ibm/icu/**|META-INF/services/**</Embed-Dependency> |
81 | 81 | <Bundle-DocURL>${project.url}</Bundle-DocURL> |
82 | <Import-Package>!junit.framework,!junit.textui,javax.*;resolution:=optional,org.apache.avalon.framework.logger;resolution:=optional,org.apache.log;resolution:=optional,org.apache.log4j;resolution:=optional,*</Import-Package> | |
82 | <Import-Package>!junit.framework,!junit.textui,javax.*;resolution:=optional,org.apache.avalon.framework.logger;resolution:=optional,org.apache.log;resolution:=optional,*</Import-Package> | |
83 | 83 | <Main-Class>org.apache.pdfbox.preflight.Validator_A1b</Main-Class> |
84 | 84 | </instructions> |
85 | 85 | </configuration> |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.12</version> | |
25 | <version>2.0.13</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 |
177 | 177 | throw new IOException("You do not have permission to extract images"); |
178 | 178 | } |
179 | 179 | |
180 | for (int i = 0; i < document.getNumberOfPages(); i++) // todo: ITERATOR would be much better | |
181 | { | |
182 | PDPage page = document.getPage(i); | |
180 | for (PDPage page : document.getPages()) | |
181 | { | |
183 | 182 | ImageGraphicsEngine extractor = new ImageGraphicsEngine(page); |
184 | 183 | extractor.run(); |
185 | 184 | } |
213 | 212 | PDTransparencyGroup group = softMask.getGroup(); |
214 | 213 | if (group != null) |
215 | 214 | { |
215 | // PDFBOX-4327: without this line NPEs will occur | |
216 | res.getExtGState(name).copyIntoGraphicsState(getGraphicsState()); | |
217 | ||
216 | 218 | processSoftMask(group); |
217 | 219 | } |
218 | 220 | } |
22 | 22 | import java.io.OutputStreamWriter; |
23 | 23 | import java.io.Writer; |
24 | 24 | import java.util.Map; |
25 | import java.util.Set; | |
26 | import java.util.TreeSet; | |
27 | import org.apache.commons.logging.Log; | |
28 | import org.apache.commons.logging.LogFactory; | |
29 | import org.apache.pdfbox.cos.COSArray; | |
30 | import org.apache.pdfbox.cos.COSName; | |
25 | 31 | import org.apache.pdfbox.io.IOUtils; |
26 | 32 | import org.apache.pdfbox.pdmodel.PDDocument; |
27 | 33 | import org.apache.pdfbox.pdmodel.PDDocumentCatalog; |
28 | 34 | import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; |
29 | 35 | import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode; |
36 | import org.apache.pdfbox.pdmodel.PDPage; | |
37 | import org.apache.pdfbox.pdmodel.PDPageContentStream; | |
30 | 38 | import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification; |
31 | 39 | import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile; |
32 | 40 | import org.apache.pdfbox.pdmodel.encryption.AccessPermission; |
33 | 41 | import org.apache.pdfbox.text.PDFTextStripper; |
42 | import org.apache.pdfbox.text.TextPosition; | |
43 | import org.apache.pdfbox.util.Matrix; | |
34 | 44 | |
35 | 45 | /** |
36 | 46 | * This is the main program that simply parses the pdf document and transforms it |
37 | 47 | * into text. |
38 | 48 | * |
39 | 49 | * @author Ben Litchfield |
50 | * @author Tilman Hausherr | |
40 | 51 | */ |
41 | 52 | public final class ExtractText |
42 | 53 | { |
54 | private static final Log LOG = LogFactory.getLog(ExtractText.class); | |
55 | ||
43 | 56 | private static final String PASSWORD = "-password"; |
44 | 57 | private static final String ENCODING = "-encoding"; |
45 | 58 | private static final String CONSOLE = "-console"; |
49 | 62 | private static final String IGNORE_BEADS = "-ignoreBeads"; |
50 | 63 | private static final String DEBUG = "-debug"; |
51 | 64 | private static final String HTML = "-html"; |
52 | ||
65 | private static final String ALWAYSNEXT = "-alwaysNext"; | |
66 | private static final String ROTATION_MAGIC = "-rotationMagic"; | |
53 | 67 | private static final String STD_ENCODING = "UTF-8"; |
54 | 68 | |
55 | 69 | /* |
92 | 106 | boolean toHTML = false; |
93 | 107 | boolean sort = false; |
94 | 108 | boolean separateBeads = true; |
109 | boolean alwaysNext = false; | |
110 | boolean rotationMagic = false; | |
95 | 111 | String password = ""; |
96 | 112 | String encoding = STD_ENCODING; |
97 | 113 | String pdfFile = null; |
142 | 158 | { |
143 | 159 | separateBeads = false; |
144 | 160 | } |
161 | else if (args[i].equals(ALWAYSNEXT)) | |
162 | { | |
163 | alwaysNext = true; | |
164 | } | |
165 | else if (args[i].equals(ROTATION_MAGIC)) | |
166 | { | |
167 | rotationMagic = true; | |
168 | } | |
145 | 169 | else if( args[i].equals( DEBUG ) ) |
146 | 170 | { |
147 | 171 | debug = true; |
211 | 235 | } |
212 | 236 | output = new OutputStreamWriter( new FileOutputStream( outputFile ), encoding ); |
213 | 237 | } |
238 | startTime = startProcessing("Starting text extraction"); | |
239 | if (debug) | |
240 | { | |
241 | System.err.println("Writing to " + outputFile); | |
242 | } | |
214 | 243 | |
215 | 244 | PDFTextStripper stripper; |
216 | 245 | if(toHTML) |
217 | 246 | { |
247 | // HTML stripper can't work page by page because of startDocument() callback | |
218 | 248 | stripper = new PDFText2HTML(); |
249 | stripper.setSortByPosition(sort); | |
250 | stripper.setShouldSeparateByBeads(separateBeads); | |
251 | stripper.setStartPage(startPage); | |
252 | stripper.setEndPage(endPage); | |
253 | ||
254 | // Extract text for main document: | |
255 | stripper.writeText(document, output); | |
219 | 256 | } |
220 | 257 | else |
221 | 258 | { |
222 | stripper = new PDFTextStripper(); | |
223 | } | |
224 | stripper.setSortByPosition( sort ); | |
225 | stripper.setShouldSeparateByBeads( separateBeads ); | |
226 | stripper.setStartPage( startPage ); | |
227 | stripper.setEndPage( endPage ); | |
228 | ||
229 | startTime = startProcessing("Starting text extraction"); | |
230 | if (debug) | |
231 | { | |
232 | System.err.println("Writing to "+outputFile); | |
233 | } | |
234 | ||
235 | // Extract text for main document: | |
236 | stripper.writeText( document, output ); | |
237 | ||
259 | if (rotationMagic) | |
260 | { | |
261 | stripper = new FilteredTextStripper(); | |
262 | } | |
263 | else | |
264 | { | |
265 | stripper = new PDFTextStripper(); | |
266 | } | |
267 | stripper.setSortByPosition(sort); | |
268 | stripper.setShouldSeparateByBeads(separateBeads); | |
269 | ||
270 | // Extract text for main document: | |
271 | extractPages(startPage, Math.min(endPage, document.getNumberOfPages()), | |
272 | stripper, document, output, rotationMagic, alwaysNext); | |
273 | } | |
274 | ||
238 | 275 | // ... also for any embedded PDFs: |
239 | 276 | PDDocumentCatalog catalog = document.getDocumentCatalog(); |
240 | 277 | PDDocumentNameDictionary names = catalog.getNames(); |
265 | 302 | try |
266 | 303 | { |
267 | 304 | subDoc = PDDocument.load(fis); |
305 | if (toHTML) | |
306 | { | |
307 | // will not really work because of HTML header + footer | |
308 | stripper.writeText( subDoc, output ); | |
309 | } | |
310 | else | |
311 | { | |
312 | extractPages(1, subDoc.getNumberOfPages(), | |
313 | stripper, subDoc, output, rotationMagic, alwaysNext); | |
314 | } | |
268 | 315 | } |
269 | 316 | finally |
270 | 317 | { |
271 | 318 | fis.close(); |
272 | } | |
273 | try | |
274 | { | |
275 | stripper.writeText( subDoc, output ); | |
276 | } | |
277 | finally | |
278 | { | |
279 | 319 | IOUtils.closeQuietly(subDoc); |
280 | 320 | } |
281 | 321 | } |
293 | 333 | } |
294 | 334 | } |
295 | 335 | |
336 | private void extractPages(int startPage, int endPage, | |
337 | PDFTextStripper stripper, PDDocument document, Writer output, | |
338 | boolean rotationMagic, boolean alwaysNext) throws IOException | |
339 | { | |
340 | for (int p = startPage; p <= endPage; ++p) | |
341 | { | |
342 | stripper.setStartPage(p); | |
343 | stripper.setEndPage(p); | |
344 | try | |
345 | { | |
346 | if (rotationMagic) | |
347 | { | |
348 | PDPage page = document.getPage(p - 1); | |
349 | int rotation = page.getRotation(); | |
350 | page.setRotation(0); | |
351 | AngleCollector angleCollector = new AngleCollector(); | |
352 | angleCollector.setStartPage(p); | |
353 | angleCollector.setEndPage(p); | |
354 | angleCollector.writeText(document, new NullWriter()); | |
355 | // rotation magic | |
356 | for (int angle : angleCollector.getAngles()) | |
357 | { | |
358 | // prepend a transformation | |
359 | // (we could skip these parts for angle 0, but it doesn't matter much) | |
360 | PDPageContentStream cs = new PDPageContentStream(document, page, | |
361 | PDPageContentStream.AppendMode.PREPEND, false); | |
362 | cs.transform(Matrix.getRotateInstance(-Math.toRadians(angle), 0, 0)); | |
363 | cs.close(); | |
364 | ||
365 | stripper.writeText(document, output); | |
366 | ||
367 | // remove prepended transformation | |
368 | ((COSArray) page.getCOSObject().getItem(COSName.CONTENTS)).remove(0); | |
369 | } | |
370 | page.setRotation(rotation); | |
371 | } | |
372 | else | |
373 | { | |
374 | stripper.writeText(document, output); | |
375 | } | |
376 | } | |
377 | catch (IOException ex) | |
378 | { | |
379 | if (!alwaysNext) | |
380 | { | |
381 | throw ex; | |
382 | } | |
383 | LOG.error("Failed to process page " + p, ex); | |
384 | } | |
385 | } | |
386 | } | |
387 | ||
296 | 388 | private long startProcessing(String message) |
297 | 389 | { |
298 | 390 | if (debug) |
319 | 411 | { |
320 | 412 | String message = "Usage: java -jar pdfbox-app-x.y.z.jar ExtractText [options] <inputfile> [output-text-file]\n" |
321 | 413 | + "\nOptions:\n" |
322 | + " -password <password> : Password to decrypt document\n" | |
323 | + " -encoding <output encoding> : UTF-8 (default) or ISO-8859-1, UTF-16BE, UTF-16LE, etc.\n" | |
324 | + " -console : Send text to console instead of file\n" | |
325 | + " -html : Output in HTML format instead of raw text\n" | |
326 | + " -sort : Sort the text before writing\n" | |
327 | + " -ignoreBeads : Disables the separation by beads\n" | |
328 | + " -debug : Enables debug output about the time consumption of every stage\n" | |
329 | + " -startPage <number> : The first page to start extraction(1 based)\n" | |
330 | + " -endPage <number> : The last page to extract(inclusive)\n" | |
331 | + " <inputfile> : The PDF document to use\n" | |
332 | + " [output-text-file] : The file to write the text to"; | |
414 | + " -password <password> : Password to decrypt document\n" | |
415 | + " -encoding <output encoding> : UTF-8 (default) or ISO-8859-1, UTF-16BE,\n" | |
416 | + " UTF-16LE, etc.\n" | |
417 | + " -console : Send text to console instead of file\n" | |
418 | + " -html : Output in HTML format instead of raw text\n" | |
419 | + " -sort : Sort the text before writing\n" | |
420 | + " -ignoreBeads : Disables the separation by beads\n" | |
421 | + " -debug : Enables debug output about the time consumption\n" | |
422 | + " of every stage\n" | |
423 | + " -alwaysNext : Process next page (if applicable) despite\n" | |
424 | + " IOException (ignored when -html)\n" | |
425 | + " -rotationMagic : Analyze each page for rotated/skewed text,\n" | |
426 | + " rotate to 0° and extract separately\n" | |
427 | + " (slower, and ignored when -html)\n" | |
428 | + " -startPage <number> : The first page to start extraction (1 based)\n" | |
429 | + " -endPage <number> : The last page to extract (1 based, inclusive)\n" | |
430 | + " <inputfile> : The PDF document to use\n" | |
431 | + " [output-text-file] : The file to write the text to"; | |
333 | 432 | |
334 | 433 | System.err.println(message); |
335 | 434 | System.exit( 1 ); |
336 | 435 | } |
337 | 436 | } |
437 | ||
438 | /** | |
439 | * Collect all angles while doing text extraction. Angles are in degrees and rounded to the closest | |
440 | * integer (to avoid slight differences from floating point arithmethic resulting in similarly | |
441 | * angled glyphs being treated separately). This class must be constructed for each page so that the | |
442 | * angle set is initialized. | |
443 | */ | |
444 | class AngleCollector extends PDFTextStripper | |
445 | { | |
446 | private final Set<Integer> angles = new TreeSet<Integer>(); | |
447 | ||
448 | AngleCollector() throws IOException | |
449 | { | |
450 | } | |
451 | ||
452 | Set<Integer> getAngles() | |
453 | { | |
454 | return angles; | |
455 | } | |
456 | ||
457 | @Override | |
458 | protected void processTextPosition(TextPosition text) | |
459 | { | |
460 | Matrix m = text.getTextMatrix(); | |
461 | int angle = (int) Math.round(Math.toDegrees(Math.atan2(m.getShearY(), m.getScaleY()))); | |
462 | angle = (angle + 360) % 360; | |
463 | angles.add(angle); | |
464 | } | |
465 | } | |
466 | ||
467 | /** | |
468 | * TextStripper that only processes glyphs that have angle 0. | |
469 | */ | |
470 | class FilteredTextStripper extends PDFTextStripper | |
471 | { | |
472 | FilteredTextStripper() throws IOException | |
473 | { | |
474 | } | |
475 | ||
476 | @Override | |
477 | protected void processTextPosition(TextPosition text) | |
478 | { | |
479 | Matrix m = text.getTextMatrix(); | |
480 | int angle = (int) Math.round(Math.toDegrees(Math.atan2(m.getShearY(), m.getScaleY()))); | |
481 | if (angle == 0) | |
482 | { | |
483 | super.processTextPosition(text); | |
484 | } | |
485 | } | |
486 | } | |
487 | ||
488 | /** | |
489 | * Dummy output. | |
490 | */ | |
491 | class NullWriter extends Writer | |
492 | { | |
493 | @Override | |
494 | public void write(char[] cbuf, int off, int len) throws IOException | |
495 | { | |
496 | // do nothing | |
497 | } | |
498 | ||
499 | @Override | |
500 | public void flush() throws IOException | |
501 | { | |
502 | // do nothing | |
503 | } | |
504 | ||
505 | @Override | |
506 | public void close() throws IOException | |
507 | { | |
508 | // do nothing | |
509 | } | |
510 | } |
136 | 136 | usage(); |
137 | 137 | } |
138 | 138 | |
139 | try | |
139 | try | |
140 | 140 | { |
141 | 141 | PDDocument result = overlayer.overlay(specificPageOverlayFile); |
142 | 142 | result.save(outputFilename); |
143 | 143 | result.close(); |
144 | // close the input files AFTER saving the resulting file as some | |
145 | // streams are shared among the input and the output files | |
146 | overlayer.close(); | |
147 | 144 | } |
148 | 145 | catch (IOException e) |
149 | 146 | { |
150 | 147 | LOG.error("Overlay failed: " + e.getMessage(), e); |
151 | 148 | throw e; |
149 | } | |
150 | finally | |
151 | { | |
152 | // close the input files AFTER saving the resulting file as some | |
153 | // streams are shared among the input and the output files | |
154 | overlayer.close(); | |
152 | 155 | } |
153 | 156 | } |
154 | 157 |
26 | 26 | <parent> |
27 | 27 | <groupId>org.apache.pdfbox</groupId> |
28 | 28 | <artifactId>pdfbox-parent</artifactId> |
29 | <version>2.0.12</version> | |
29 | <version>2.0.13</version> | |
30 | 30 | <relativePath>../parent/pom.xml</relativePath> |
31 | 31 | </parent> |
32 | 32 | |
33 | <profiles> | |
34 | <profile> | |
35 | <activation> | |
36 | <jdk>[11,)</jdk> | |
37 | </activation> | |
38 | <dependencies> | |
39 | <dependency> | |
40 | <groupId>javax.xml.bind</groupId> | |
41 | <artifactId>jaxb-api</artifactId> | |
42 | <scope>provided</scope> | |
43 | </dependency> | |
44 | </dependencies> | |
45 | </profile> | |
46 | </profiles> | |
33 | 47 | |
34 | 48 | <dependencies> |
35 | 49 | <dependency> |
45 | 45 | @Test |
46 | 46 | public void testDateConversion() throws Exception |
47 | 47 | { |
48 | final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); | |
49 | Calendar jaxbCal; | |
48 | 50 | |
49 | final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); | |
50 | Calendar jaxbCal = null, | |
51 | convDate = null; | |
52 | 51 | // Test partial dates |
53 | convDate = DateConverter.toCalendar("2015-02-02"); | |
52 | Calendar convDate = DateConverter.toCalendar("2015-02-02"); | |
54 | 53 | assertEquals(2015, convDate.get(Calendar.YEAR)); |
55 | ||
54 | ||
56 | 55 | //Test missing seconds |
57 | 56 | assertEquals(DateConverter.toCalendar("2015-12-08T12:07:00-05:00"), |
58 | 57 | DateConverter.toCalendar("2015-12-08T12:07-05:00")); |