Codebase list libpdfbox2-java / 0d2a4e1
New upstream version 2.0.13 Markus Koschany 5 years ago
93 changed file(s) with 3822 addition(s) and 1223 deletion(s). Raw diff Collapse all Expand all
0 Release Notes -- Apache PDFBox -- Version 2.0.12
0 Release Notes -- Apache PDFBox -- Version 2.0.13
11
22 Introduction
33 ------------
44
55 The Apache PDFBox library is an open source Java tool for working with PDF documents.
66
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
88 a couple of fixes and small improvements.
99
1010 For more details on these changes and all the other fixes and improvements
1313
1414 Bug
1515
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
4452
4553 Improvement
4654
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
5863
5964 Task
6065
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
6269
6370 Release Contents
6471 ----------------
2222 <parent>
2323 <groupId>org.apache.pdfbox</groupId>
2424 <artifactId>pdfbox-parent</artifactId>
25 <version>2.0.12</version>
25 <version>2.0.13</version>
2626 <relativePath>../parent/pom.xml</relativePath>
2727 </parent>
2828
6767 <Embed-Transitive>true</Embed-Transitive>
6868 <Embed-Dependency>*;scope=provided;inline=org/apache/**|org/bouncycastle/**|META-INF/services/**</Embed-Dependency>
6969 <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>
7171 <Main-Class>org.apache.pdfbox.tools.PDFBox</Main-Class>
7272 </instructions>
7373 </configuration>
2222 <parent>
2323 <groupId>org.apache.pdfbox</groupId>
2424 <artifactId>pdfbox-parent</artifactId>
25 <version>2.0.12</version>
25 <version>2.0.13</version>
2626 <relativePath>../parent/pom.xml</relativePath>
2727 </parent>
2828
905905 {
906906 selectedNode = ((MapEntry)selectedNode).getKey();
907907 selectedNode = getUnderneathObject(selectedNode);
908 FlagBitsPane flagBitsPane = new FlagBitsPane((COSDictionary) parentNode, (COSName) selectedNode);
908 FlagBitsPane flagBitsPane = new FlagBitsPane(document,
909 (COSDictionary) parentNode,
910 (COSName) selectedNode);
909911 replaceRightComponent(flagBitsPane.getPane());
910912 }
911913 }
1919 import javax.swing.JPanel;
2020 import org.apache.pdfbox.cos.COSDictionary;
2121 import org.apache.pdfbox.cos.COSName;
22 import org.apache.pdfbox.pdmodel.PDDocument;
2223
2324 /**
2425 * @author Khyrul Bashar
2829 public class FlagBitsPane
2930 {
3031 private FlagBitsPaneView view;
32 private final PDDocument document;
3133
3234 /**
3335 * Constructor.
3436 * @param dictionary COSDictionary instance.
3537 * @param flagType COSName instance.
3638 */
37 public FlagBitsPane(final COSDictionary dictionary, COSName flagType)
39 public FlagBitsPane(PDDocument document, final COSDictionary dictionary, COSName flagType)
3840 {
41 this.document = document;
3942 createPane(dictionary, flagType);
4043 }
4144
7881 }
7982 if (COSName.SIG_FLAGS.equals(flagType))
8083 {
81 flag = new SigFlag(dictionary);
84 flag = new SigFlag(document, dictionary);
8285 view = new FlagBitsPaneView(
8386 flag.getFlagType(), flag.getFlagValue(), flag.getFlagBits(), flag.getColumnNames());
8487 }
1818
1919 import org.apache.pdfbox.cos.COSDictionary;
2020 import org.apache.pdfbox.cos.COSName;
21 import org.apache.pdfbox.pdmodel.PDDocument;
2122 import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
2223
2324 /**
2728 */
2829 public class SigFlag extends Flag
2930 {
30 private final COSDictionary acroformDictionary;
31 private final PDDocument document;
32 private final COSDictionary acroFormDictionary;
3133
3234 /**
3335 * Constructor
3436 *
3537 * @param acroFormDictionary COSDictionary instance.
3638 */
37 SigFlag(COSDictionary acroFormDictionary)
39 SigFlag(PDDocument document, COSDictionary acroFormDictionary)
3840 {
39 acroformDictionary = acroFormDictionary;
41 this.document = document;
42 this.acroFormDictionary = acroFormDictionary;
4043 }
4144
4245 @Override
4851 @Override
4952 String getFlagValue()
5053 {
51 return "Flag value: " + acroformDictionary.getInt(COSName.SIG_FLAGS);
54 return "Flag value: " + acroFormDictionary.getInt(COSName.SIG_FLAGS);
5255 }
5356
5457 @Override
5558 Object[][] getFlagBits()
5659 {
57 PDAcroForm acroForm = new PDAcroForm(null, acroformDictionary);
60 PDAcroForm acroForm = new PDAcroForm(document, acroFormDictionary);
5861 return new Object[][]{
5962 new Object[]{1, "SignaturesExist", acroForm.isSignaturesExist()},
6063 new Object[]{2, "AppendOnly", acroForm.isAppendOnly()},
164164
165165 private JPanel createHeaderPanel(List<String> availableFilters, String i, ActionListener actionListener)
166166 {
167 JComboBox<String> filters = new JComboBox<String>(new Vector<String>(availableFilters));
167 JComboBox filters = new JComboBox(new Vector<String>(availableFilters));
168168 filters.setSelectedItem(i);
169169 filters.addActionListener(actionListener);
170170
2222 <parent>
2323 <groupId>org.apache.pdfbox</groupId>
2424 <artifactId>pdfbox-parent</artifactId>
25 <version>2.0.12</version>
25 <version>2.0.13</version>
2626 <relativePath>../parent/pom.xml</relativePath>
2727 </parent>
2828
6767 <Embed-Transitive>true</Embed-Transitive>
6868 <Embed-Dependency>*;scope=provided;inline=org/apache/**|org/bouncycastle/**|META-INF/services/**</Embed-Dependency>
6969 <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>
7171 <Main-Class>org.apache.pdfbox.debugger.PDFDebugger</Main-Class>
7272 </instructions>
7373 </configuration>
2222 <parent>
2323 <groupId>org.apache.pdfbox</groupId>
2424 <artifactId>pdfbox-parent</artifactId>
25 <version>2.0.12</version>
25 <version>2.0.13</version>
2626 <relativePath>../parent/pom.xml</relativePath>
2727 </parent>
2828
4040 <lucene.version>4.7.2</lucene.version>
4141 <!-- don't update this, because later versions require JDK7 -->
4242 </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>
4363
4464 <dependencies>
4565 <dependency>
2020
2121 import org.apache.pdfbox.pdmodel.PDDocument;
2222 import org.apache.pdfbox.pdmodel.PDPage;
23 import org.apache.pdfbox.pdmodel.PageMode;
2324 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
2425 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageFitWidthDestination;
2526 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
8384 }
8485 pagesOutline.openNode();
8586 outline.openNode();
87
88 // optional: show the outlines when opening the file
89 document.getDocumentCatalog().setPageMode(PageMode.USE_OUTLINES);
8690
8791 document.save( args[1] );
8892 }
2626 import java.security.cert.Certificate;
2727 import java.security.cert.CertificateException;
2828 import java.security.cert.X509Certificate;
29 import java.util.ArrayList;
3029 import java.util.Arrays;
3130 import java.util.Enumeration;
32 import java.util.List;
3331 import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
34 import org.bouncycastle.cert.X509CertificateHolder;
3532 import org.bouncycastle.cert.jcajce.JcaCertStore;
3633 import org.bouncycastle.cms.CMSException;
3734 import org.bouncycastle.cms.CMSSignedData;
4138 import org.bouncycastle.operator.OperatorCreationException;
4239 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
4340 import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
44 import org.bouncycastle.util.Store;
4541
4642 public abstract class CreateSignatureBase implements SignatureInterface
4743 {
8682 {
8783 // avoid expired certificate
8884 ((X509Certificate) cert).checkValidity();
85
86 SigUtils.checkCertificateUsage((X509Certificate) cert);
8987 }
9088 break;
9189 }
129127 // cannot be done private (interface)
130128 try
131129 {
132 List<Certificate> certList = new ArrayList<Certificate>();
133 certList.addAll(Arrays.asList(certificateChain));
134 Store certs = new JcaCertStore(certList);
135130 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];
137132 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)));
140135 CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
141136 CMSSignedData signedData = gen.generate(msg, false);
142137 if (tsaUrl != null && tsaUrl.length() > 0)
1919 import java.io.File;
2020 import java.io.FileInputStream;
2121 import java.io.IOException;
22 import java.security.InvalidKeyException;
22 import java.security.GeneralSecurityException;
2323 import java.security.MessageDigest;
2424 import java.security.NoSuchAlgorithmException;
25 import java.security.NoSuchProviderException;
26 import java.security.PublicKey;
27 import java.security.SignatureException;
25 import java.security.Security;
2826 import java.security.cert.Certificate;
2927 import java.security.cert.CertificateException;
28 import java.security.cert.CertificateExpiredException;
3029 import java.security.cert.CertificateFactory;
30 import java.security.cert.CertificateNotYetValidException;
3131 import java.security.cert.X509Certificate;
3232 import java.text.SimpleDateFormat;
33 import java.util.Arrays;
3334 import java.util.Collection;
35 import java.util.Date;
36 import java.util.HashSet;
37 import java.util.Set;
3438 import org.apache.pdfbox.cos.COSArray;
3539 import org.apache.pdfbox.cos.COSBase;
36
3740 import org.apache.pdfbox.cos.COSDictionary;
3841 import org.apache.pdfbox.cos.COSInputStream;
3942 import org.apache.pdfbox.cos.COSName;
4043 import org.apache.pdfbox.cos.COSObject;
4144 import org.apache.pdfbox.cos.COSStream;
4245 import org.apache.pdfbox.cos.COSString;
46 import org.apache.pdfbox.examples.signature.cert.CertificateVerificationException;
47 import org.apache.pdfbox.examples.signature.cert.CertificateVerifier;
4348 import org.apache.pdfbox.io.IOUtils;
4449 import org.apache.pdfbox.pdmodel.PDDocument;
4550 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
51 import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
4652 import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
4753 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;
4860 import org.bouncycastle.cert.X509CertificateHolder;
61 import org.bouncycastle.cert.jcajce.JcaCertStore;
4962 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
5063 import org.bouncycastle.cms.CMSException;
5164 import org.bouncycastle.cms.CMSProcessable;
5265 import org.bouncycastle.cms.CMSProcessableByteArray;
5366 import org.bouncycastle.cms.CMSSignedData;
5467 import org.bouncycastle.cms.SignerInformation;
68 import org.bouncycastle.cms.SignerInformationVerifier;
5569 import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
5670 import org.bouncycastle.operator.OperatorCreationException;
5771 import org.bouncycastle.tsp.TSPException;
5872 import org.bouncycastle.tsp.TimeStampToken;
5973 import org.bouncycastle.util.Selector;
6074 import org.bouncycastle.util.Store;
61 import org.bouncycastle.util.StoreException;
6275
6376 /**
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.
6580 *
6681 * @author Ben Litchfield
6782 */
6883 public final class ShowSignature
6984 {
7085 private final SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
71
86
7287 private ShowSignature()
7388 {
7489 }
7994 * @param args The command-line arguments.
8095 *
8196 * @throws IOException If there is an error reading the file.
82 * @throws CertificateException
83 * @throws java.security.NoSuchAlgorithmException
84 * @throws java.security.NoSuchProviderException
8597 * @throws org.bouncycastle.tsp.TSPException
98 * @throws java.security.GeneralSecurityException
99 * @throws org.apache.pdfbox.examples.signature.cert.CertificateVerificationException
86100 */
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
92107 ShowSignature show = new ShowSignature();
93108 show.showSignature( args );
94109 }
95110
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
100113 {
101114 if( args.length != 2 )
102115 {
168181 subFilter.equals("ETSI.CAdES.detached"))
169182 {
170183 verifyPKCS7(buf, contents, sig);
171
172 //TODO check certificate chain, revocation lists, timestamp...
173184 }
174185 else if (subFilter.equals("adbe.pkcs7.sha1"))
175186 {
182193
183194 byte[] hash = MessageDigest.getInstance("SHA1").digest(buf);
184195 verifyPKCS7(hash, contents, sig);
185
186 //TODO check certificate chain, revocation lists, timestamp...
187196 }
188197 else if (subFilter.equals("adbe.x509.rsa_sha1"))
189198 {
190199 // example: PDFBOX-2693.pdf
191200 COSString certString = (COSString) sigDict.getDictionaryObject(COSName.CERT);
201 //TODO this could also be an array.
192202 if (certString == null)
193203 {
194204 System.err.println("The /Cert certificate string is missing in the signature dictionary");
199209 ByteArrayInputStream certStream = new ByteArrayInputStream(certData);
200210 Collection<? extends Certificate> certs = factory.generateCertificates(certStream);
201211 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 }
204253 }
205254 else if (subFilter.equals("ETSI.RFC3161"))
206255 {
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);
217258 }
218259 else
219260 {
244285 }
245286 System.out.println("Analyzed: " + args[1]);
246287 }
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());
247322 }
248323
249324 /**
252327 * @param byteArray the byte sequence that has been signed
253328 * @param contents the /Contents field as a COSString
254329 * @param sig the PDF signature (the /V dictionary)
255 * @throws CertificateException
256330 * @throws CMSException
257 * @throws StoreException
258331 * @throws OperatorCreationException
332 * @throws IOException
333 * @throws GeneralSecurityException
334 * @throws TSPException
259335 */
260336 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
263339 {
264340 // inspiration:
265341 // http://stackoverflow.com/a/26702631/535646
276352 X509CertificateHolder certificateHolder = matches.iterator().next();
277353 X509Certificate certFromSignedData = new JcaX509CertificateConverter().getCertificate(certificateHolder);
278354 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))
282433 {
283434 System.err.println("Certificate is self-signed, LOL!");
284435 }
285436 else
286437 {
287438 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");
299500 }
300501
301502 /**
357558 }
358559 }
359560
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
385561 /**
386562 * This will print a usage message.
387563 */
1515
1616 package org.apache.pdfbox.examples.signature;
1717
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;
1826 import org.apache.pdfbox.cos.COSArray;
1927 import org.apache.pdfbox.cos.COSBase;
2028 import org.apache.pdfbox.cos.COSDictionary;
2129 import org.apache.pdfbox.cos.COSName;
2230 import org.apache.pdfbox.pdmodel.PDDocument;
2331 import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
32 import org.bouncycastle.asn1.x509.KeyPurposeId;
2433
2534 /**
2635 * Utility class for the signature / timestamp examples.
2938 */
3039 public class SigUtils
3140 {
41 private static final Log LOG = LogFactory.getLog(SigUtils.class);
42
3243 private SigUtils()
3344 {
3445 }
125136 catalogDict.setNeedToBeUpdated(true);
126137 permsDict.setNeedToBeUpdated(true);
127138 }
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 }
128238 }
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 }
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 }
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 }
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 }
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 }
2323 import java.io.OutputStream;
2424 import java.math.BigInteger;
2525 import java.security.GeneralSecurityException;
26 import java.security.cert.CRLException;
26 import java.security.Security;
2727 import java.security.cert.CertificateEncodingException;
28 import java.security.cert.X509CRL;
2829 import java.security.cert.X509Certificate;
30 import java.util.Calendar;
2931 import java.util.HashSet;
3032 import java.util.Set;
3133
3436 import org.apache.pdfbox.cos.COSArray;
3537 import org.apache.pdfbox.cos.COSBase;
3638 import org.apache.pdfbox.cos.COSDictionary;
37 import org.apache.pdfbox.cos.COSInteger;
3839 import org.apache.pdfbox.cos.COSName;
3940 import org.apache.pdfbox.cos.COSStream;
4041 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;
4147 import org.apache.pdfbox.examples.signature.validation.CertInformationCollector.CertSignatureInformation;
48 import org.apache.pdfbox.io.IOUtils;
4249 import org.apache.pdfbox.pdmodel.PDDocument;
4350 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
51 import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
52 import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
4453 import org.bouncycastle.cert.ocsp.BasicOCSPResp;
4554 import org.bouncycastle.cert.ocsp.OCSPException;
4655 import org.bouncycastle.cert.ocsp.OCSPResp;
6675 private COSArray certs;
6776 private PDDocument document;
6877 private final Set<BigInteger> foundRevocationInformation = new HashSet<BigInteger>();
78 private Calendar signDate;
6979
7080 /**
7181 * Signs the given PDF file.
90100 }
91101
92102 /**
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 *
96106 * @param document containing the Signature
97107 * @param filename in file to extract signature
98108 * @param output where to write the changed document
101111 private void doValidation(String filename, OutputStream output) throws IOException
102112 {
103113 certInformationHelper = new CertInformationCollector();
104 CertSignatureInformation certInfo;
114 CertSignatureInformation certInfo = null;
105115 try
106116 {
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 }
108123 }
109124 catch (CertificateProccessingException e)
110125 {
189204
190205 /**
191206 * 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.
194210 * @throws IOException
195211 */
196212 private void addRevocationData(CertSignatureInformation certInfo) throws IOException
197213 {
198214 COSDictionary vri = new COSDictionary();
199 vriBase.setItem(COSName.getPDFName(certInfo.getSignatureHash()), vri);
215 vriBase.setItem(certInfo.getSignatureHash(), vri);
200216
201217 correspondingOCSPs = new COSArray();
202218 correspondingCRLs = new COSArray();
205221
206222 if (correspondingOCSPs.size() > 0)
207223 {
208 vri.setItem(COSName.getPDFName("OCSP"), correspondingOCSPs);
224 vri.setItem("OCSP", correspondingOCSPs);
209225 }
210226 if (correspondingCRLs.size() > 0)
211227 {
212 vri.setItem(COSName.getPDFName("CRL"), correspondingCRLs);
228 vri.setItem("CRL", correspondingCRLs);
213229 }
214230
215231 if (certInfo.getTsaCerts() != null)
223239
224240 /**
225241 * 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.
228245 * @throws IOException when failed to fetch an revocation data.
229246 */
230247 private void addRevocationDataRecursive(CertSignatureInformation certInfo) throws IOException
248265 isRevocationInfoFound = true;
249266 }
250267
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)
252273 {
253274 throw new IOException("Could not fetch Revocation Info for Cert: "
254 + certInfo.getCertificate().getSubjectDN());
275 + certInfo.getCertificate().getSubjectX500Principal());
255276 }
256277 }
257278
268289
269290 /**
270291 * Tries to fetch and add OCSP Data to its containers.
271 *
292 *
272293 * @param certInfo the certificate info, for it to check OCSP data.
273294 * @return true when the OCSP data has successfully been fetched and added
274295 * @throws IOException when Certificate is revoked.
303324
304325 /**
305326 * Tries to fetch and add CRL Data to its containers.
306 *
327 *
307328 * @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.
309331 */
310332 private void fetchCrlData(CertSignatureInformation certInfo) throws IOException
311333 {
313335 {
314336 addCrlRevocationInfo(certInfo);
315337 }
316 catch (CRLException e)
338 catch (GeneralSecurityException e)
317339 {
318340 LOG.warn("Failed fetching CRL", e);
319341 throw new IOException(e);
324346 throw new IOException(e);
325347 }
326348 catch (IOException e)
349 {
350 LOG.warn("Failed fetching CRL", e);
351 throw new IOException(e);
352 }
353 catch (CertificateVerificationException e)
327354 {
328355 LOG.warn("Failed fetching CRL", e);
329356 throw new IOException(e);
342369 private void addOcspData(CertSignatureInformation certInfo) throws IOException, OCSPException,
343370 CertificateProccessingException, RevokedCertificateException
344371 {
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());
347378
348379 OCSPResp ocspResp = ocspHelper.getResponseOcsp();
349380 BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResp.getResponseObject();
350381 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
351385
352386 byte[] ocspData = ocspResp.getEncoded();
353387
364398 * Fetches and adds CRL data to storage for the given Certificate.
365399 *
366400 * @param certInfo the certificate info, for it to check CRL data.
367 * @throws CRLException
368401 * @throws IOException
369402 * @throws RevokedCertificateException
403 * @throws GeneralSecurityException
404 * @throws CertificateVerificationException
370405 */
371406 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());
377414 crls.add(crlStream);
378415 if (correspondingCRLs != null)
379416 {
383420 }
384421
385422 /**
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 *
389426 * @throws IOException
390427 */
391428 private void addAllCertsToCertArray() throws IOException
392429 {
393430 try
394431 {
395 for (X509Certificate cert : certInformationHelper.getCertificateStore().values())
432 for (X509Certificate cert : certInformationHelper.getCertificatesMap().values())
396433 {
397434 COSStream stream = writeDataToStream(cert.getEncoded());
398435 certs.add(stream);
405442 }
406443
407444 /**
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.
409446 *
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
412449 * @throws IOException
413450 */
414451 private COSStream writeDataToStream(byte[] data) throws IOException
415452 {
416453 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 }
424464 return stream;
425465 }
426466
427467 /**
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 *
431471 * @param catalog to add Extensions into
432472 */
433473 private void addExtensions(PDDocumentCatalog catalog)
434474 {
435475 COSDictionary dssExtensions = new COSDictionary();
436476 dssExtensions.setDirect(true);
437 catalog.getCOSObject().setItem(COSName.getPDFName("Extensions"), dssExtensions);
477 catalog.getCOSObject().setItem("Extensions", dssExtensions);
438478
439479 COSDictionary adbeExtension = new COSDictionary();
440480 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");
447487 }
448488
449489 public static void main(String[] args) throws IOException, GeneralSecurityException
453493 usage();
454494 System.exit(1);
455495 }
496
497 // register BouncyCastle provider, needed for "exotic" algorithms
498 Security.addProvider(SecurityProvider.getProvider());
456499
457500 // add ocspInformation
458501 AddValidationInformation addOcspInformation = new AddValidationInformation();
2121 import java.io.InputStream;
2222 import java.math.BigInteger;
2323 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;
2429 import java.security.cert.CertificateException;
2530 import java.security.cert.CertificateFactory;
2631 import java.security.cert.X509Certificate;
32 import java.util.Arrays;
2733 import java.util.Collection;
2834 import java.util.HashMap;
2935 import java.util.Map;
30 import java.util.SortedMap;
31 import java.util.TreeMap;
3236
3337 import org.apache.commons.logging.Log;
3438 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;
3740 import org.apache.pdfbox.io.IOUtils;
38 import org.apache.pdfbox.pdmodel.PDDocument;
41 import org.apache.pdfbox.pdmodel.encryption.SecurityProvider;
3942 import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
4043 import org.bouncycastle.asn1.DERSequence;
4144 import org.bouncycastle.asn1.DERSet;
4245 import org.bouncycastle.asn1.cms.Attribute;
4346 import org.bouncycastle.asn1.cms.AttributeTable;
4447 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
48 import org.bouncycastle.asn1.x509.Extension;
4549 import org.bouncycastle.cert.X509CertificateHolder;
4650 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
4751 import org.bouncycastle.cms.CMSException;
6468 {
6569 private static final Log LOG = LogFactory.getLog(CertInformationCollector.class);
6670
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
7571 private static final int MAX_CERTIFICATE_CHAIN_DEPTH = 5;
7672
77 private final Map<BigInteger, X509Certificate> certificateStore = new HashMap<BigInteger, X509Certificate>();
73 private final Map<BigInteger, X509Certificate> certificatesMap = new HashMap<BigInteger, X509Certificate>();
7874
7975 private final JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter();
8076
8177 private CertSignatureInformation rootCertInfo;
8278
8379 /**
84 * Gets the Certificate Information of the last Signature.
80 * Gets the certificate information of a signature.
8581 *
86 * @param document to get the Signature from
82 * @param signature the signature of the document.
8783 * @param fileName of the document.
8884 * @return the CertSignatureInformation containing all certificate information
8985 * @throws CertificateProccessingException when there is an error processing the certificates
9086 * @throws IOException on a data processing error
9187 */
92 public CertSignatureInformation getLastCertInfo(PDDocument document, String fileName)
88 public CertSignatureInformation getLastCertInfo(PDSignature signature, String fileName)
9389 throws CertificateProccessingException, IOException
9490 {
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 }
139102 }
140103
141104 /**
156119 try
157120 {
158121 CMSSignedData signedData = new CMSSignedData(signatureContent);
159 @SuppressWarnings("unchecked")
160122 Store<X509CertificateHolder> certificatesStore = signedData.getCertificates();
161123
162124 SignerInformation signerInformation = processSignerStore(certificatesStore, signedData,
226188 * not yet practicable.
227189 *
228190 * @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
231193 * @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.
233195 * @throws IOException on data-processing error
234196 * @throws CertificateProccessingException on a specific error with a certificate
235197 */
268230 certInfo.certificate = certificate;
269231
270232 // 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());
272235 if (authorityExtensionValue != null)
273236 {
274237 CertInformationHelper.getAuthorityInfoExtensionValue(authorityExtensionValue, certInfo);
279242 getAlternativeIssuerCertificate(certInfo, maxDepth);
280243 }
281244
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());
283247 if (crlExtensionValue != null)
284248 {
285249 certInfo.crlUrl = CertInformationHelper.getCrlUrlFromExtensionValue(crlExtensionValue);
286250 }
287251
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);
291259 }
292260 if (maxDepth <= 0 || certInfo.isSelfSigned)
293261 {
294262 return;
295263 }
296264
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());
303295 certInfo.issuerCertificate = issuer;
304296 certInfo.certChain = new CertSignatureInformation();
305 traverseChain(issuer, certInfo.certChain, --maxDepth);
297 traverseChain(issuer, certInfo.certChain, maxDepth - 1);
306298 break;
307299 }
308300 }
309301 if (certInfo.issuerCertificate == null)
310302 {
311303 throw new IOException(
312 "No Issuer Certificate found for Cert: " + certificate.getSubjectDN());
304 "No Issuer Certificate found for Cert: " + certificate.getSubjectX500Principal());
313305 }
314306 }
315307
326318 private void getAlternativeIssuerCertificate(CertSignatureInformation certInfo, int maxDepth)
327319 throws CertificateProccessingException
328320 {
329 System.out.println("Get Certificate from: " + certInfo.issuerUrl);
321 LOG.info("Get alternative issuer certificate from: " + certInfo.issuerUrl);
330322 try
331323 {
332324 URL certUrl = new URL(certInfo.issuerUrl);
334326 InputStream in = certUrl.openStream();
335327
336328 X509Certificate altIssuerCert = (X509Certificate) certFactory.generateCertificate(in);
337 addCertToCertStore(altIssuerCert);
329 addCertToCertificatesMap(altIssuerCert);
338330
339331 certInfo.alternativeCertChain = new CertSignatureInformation();
340332 traverseChain(altIssuerCert, certInfo.alternativeCertChain, maxDepth - 1);
342334 }
343335 catch (IOException e)
344336 {
345 LOG.error("Error getting additional Certificate from " + certInfo.issuerUrl, e);
337 LOG.error("Error getting alternative issuer certificate from " + certInfo.issuerUrl, e);
346338 }
347339 catch (CertificateException e)
348340 {
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.
355347 *
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 *
369361 * @param certificateHolder to get the certificate from
370362 * @return a X509Certificate or <code>null</code> when there was an Error with the Certificate
371363 * @throws CertificateProccessingException on failed conversion from X509CertificateHolder to X509Certificate
373365 private X509Certificate getCertFromHolder(X509CertificateHolder certificateHolder)
374366 throws CertificateProccessingException
375367 {
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()))
377370 {
378371 try
379372 {
380373 X509Certificate certificate = certConverter.getCertificate(certificateHolder);
381 certificateStore.put(certificate.getSerialNumber(), certificate);
374 certificatesMap.put(certificate.getSerialNumber(), certificate);
382375 return certificate;
383376 }
384377 catch (CertificateException e)
389382 }
390383 else
391384 {
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.
399391 *
400392 * @param certHolders Collection of X509CertificateHolder
401393 */
416408
417409 /**
418410 * 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.
420412 *
421413 * @param certHolders Array of X509CertificateHolder
422414 * @throws CertificateProccessingException when one of the Certificates could not be parsed.
424416 public void addAllCertsFromHolders(X509CertificateHolder[] certHolders)
425417 throws CertificateProccessingException
426418 {
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.
435449 *
436450 * @return a map of serial numbers to certificates.
437451 */
438 public Map<BigInteger, X509Certificate> getCertificateStore()
439 {
440 return certificateStore;
452 public Map<BigInteger, X509Certificate> getCertificatesMap()
453 {
454 return certificatesMap;
441455 }
442456
443457 /**
1616 package org.apache.pdfbox.examples.signature.validation;
1717
1818 import java.io.IOException;
19 import java.security.InvalidKeyException;
2019 import java.security.MessageDigest;
2120 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;
2721 import java.util.Enumeration;
2822
2923 import org.apache.commons.logging.Log;
6559 LOG.error("No SHA-1 Algorithm found", e);
6660 }
6761 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 }
13562 }
13663
13764 /**
+0
-77
examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CrlHelper.java less more
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
examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java less more
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
examples/src/main/java/org/apache/pdfbox/examples/signature/validation/RevokedCertificateException.java less more
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 }
5353 */
5454 public PDFHighlighter() throws IOException
5555 {
56 super();
5756 super.setLineSeparator( "" );
5857 super.setWordSeparator( "" );
5958 super.setShouldSeparateByBeads( false );
3535 import java.util.List;
3636
3737 import org.apache.pdfbox.cos.COSName;
38 import org.apache.pdfbox.cos.COSObject;
3938 import org.apache.pdfbox.cos.COSString;
4039 import org.apache.pdfbox.examples.signature.CreateEmptySignatureForm;
4140 import org.apache.pdfbox.examples.signature.CreateSignature;
287286 {
288287 PDDocument document = PDDocument.load(origFile);
289288 // 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();
291290 document.close();
292291
293292 document = PDDocument.load(signedFile);
2020 <parent>
2121 <groupId>org.apache.pdfbox</groupId>
2222 <artifactId>pdfbox-parent</artifactId>
23 <version>2.0.12</version>
23 <version>2.0.13</version>
2424 <relativePath>../parent/pom.xml</relativePath>
2525 </parent>
2626
2323 import java.util.LinkedList;
2424 import java.util.List;
2525 import java.util.Map;
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
2628
2729 import org.apache.fontbox.util.Charsets;
2830
3234 */
3335 public class CFFParser
3436 {
37 /**
38 * Log instance.
39 */
40 private static final Log LOG = LogFactory.getLog(CFFParser.class);
41
3542 private static final String TAG_OTTO = "OTTO";
3643 private static final String TAG_TTCF = "ttcf";
3744 private static final String TAG_TTFONLY = "\u0000\u0001\u0000\u0000";
335342 StringBuilder sb = new StringBuilder();
336343 boolean done = false;
337344 boolean exponentMissing = false;
345 boolean hasExponent = false;
338346 while (!done)
339347 {
340348 int b = input.readUnsignedByte();
360368 sb.append(".");
361369 break;
362370 case 0xb:
371 if (hasExponent)
372 {
373 LOG.warn("duplicate 'E' ignored after " + sb);
374 break;
375 }
363376 sb.append("E");
364377 exponentMissing = true;
378 hasExponent = true;
365379 break;
366380 case 0xc:
381 if (hasExponent)
382 {
383 LOG.warn("duplicate 'E-' ignored after " + sb);
384 break;
385 }
367386 sb.append("E-");
368387 exponentMissing = true;
388 hasExponent = true;
369389 break;
370390 case 0xd:
371391 break;
2121 import java.util.HashMap;
2222 import java.util.List;
2323 import java.util.Map;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
2426
2527 /**
2628 * This class represents a CMap file.
2931 */
3032 public class CMap
3133 {
34 private static final Log LOG = LogFactory.getLog(CMap.class);
35
3236 private int wmode = 0;
3337 private String cmapName = null;
3438 private String cmapVersion = null;
119123 bytes[byteCount] = (byte)in.read();
120124 }
121125 }
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;
123133 }
124134
125135 /**
3232
3333 <groupId>org.apache.pdfbox</groupId>
3434 <artifactId>pdfbox-parent</artifactId>
35 <version>2.0.12</version>
35 <version>2.0.13</version>
3636 <packaging>pom</packaging>
3737
3838 <name>PDFBox parent</name>
5151 <properties>
5252 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
5353 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
54
55 <bouncycastle.version>1.60</bouncycastle.version>
5456 </properties>
5557
5658 <dependencyManagement>
7577 <dependency>
7678 <groupId>org.bouncycastle</groupId>
7779 <artifactId>bcprov-jdk15on</artifactId>
78 <version>1.54</version>
80 <version>${bouncycastle.version}</version>
7981 </dependency>
8082 <dependency>
8183 <groupId>org.bouncycastle</groupId>
8284 <artifactId>bcmail-jdk15on</artifactId>
83 <version>1.54</version>
85 <version>${bouncycastle.version}</version>
8486 </dependency>
8587 <dependency>
8688 <groupId>org.bouncycastle</groupId>
8789 <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>
9491 </dependency>
9592
9693 <dependency>
122119 <!-- call mvn with -Pjdk9 or call with -Daddmod="...." -->
123120 <profile>
124121 <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>
125130 <properties>
126131 <addmod>--add-modules java.activation --add-modules java.xml.bind</addmod>
127132 </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>
128155 </profile>
129156 <profile>
130157 <id>pedantic</id>
145172 <plugin>
146173 <groupId>org.owasp</groupId>
147174 <artifactId>dependency-check-maven</artifactId>
148 <version>3.3.2</version>
175 <version>3.3.3</version>
149176 <configuration>
150177 <failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability>
151178 </configuration>
285312 <!-- don't upgrade to 3.0.x as long as we have to ensure jdk6 compatibility -->
286313 <version>2.5.4</version>
287314 </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>
288320 </plugins>
289321 </pluginManagement>
290322 </build>
455487 </developers>
456488
457489 <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>
461493 </scm>
462494 </project>
2222 <parent>
2323 <groupId>org.apache.pdfbox</groupId>
2424 <artifactId>pdfbox-parent</artifactId>
25 <version>2.0.12</version>
25 <version>2.0.13</version>
2626 <relativePath>../parent/pom.xml</relativePath>
2727 </parent>
2828
411411 <goal>wget</goal>
412412 </goals>
413413 <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 -->
417417 <outputDirectory>${project.build.directory}/imgs</outputDirectory>
418418 <outputFileName>PDFBOX-4184-032163.jpg</outputFileName>
419419 <sha512>35241c979d3808ca9d2641b5ec5e40637132b313f75070faca8b8f6d00ddce394070414236db3993f1092fe3bc16995750d528b6d803a7851423c14c308ccdde</sha512>
451451 <sha512>566346239d51f10b2ccfc435620e8f3b0281e91286983cb86660060a8d48777998eab46dfda93d35024e7e4b50b7ab6654f9a1002524163d228a5e41a80a1221</sha512>
452452 </configuration>
453453 </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>
454480 </executions>
455481 </plugin>
456482 </plugins>
163163 * @param keyList The list of keys to find a value.
164164 *
165165 * @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.
166169 */
167170 public COSBase getDictionaryObject(String[] keyList)
168171 {
559562 }
560563
561564 /**
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 /**
562616 * This is a convenience method that will get the dictionary object that is expected to be a name. Default is
563617 * returned if the entry does not exist in the dictionary.
564618 *
10251079 * @param keyList The key to the item in the dictionary.
10261080 * @param defaultValue The value to return if the dictionary item is null.
10271081 * @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.
10281085 */
10291086 public int getInt(String[] keyList, int defaultValue)
10301087 {
11281185 * @param keyList The key to the item in the dictionary.
11291186 * @param defaultValue The value to return if the dictionary item is null.
11301187 * @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.
11311191 */
11321192 public long getLong(String[] keyList, long defaultValue)
11331193 {
15081568 }
15091569 return sb.toString();
15101570 }
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 }
15111583 if (base instanceof COSObject)
15121584 {
15131585 COSObject obj = (COSObject) base;
4444 /* 70 */ 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4545 /* 80 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
4646 /* 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
4863 };
4964
5065 @Override
1515 */
1616 package org.apache.pdfbox.filter;
1717
18 import java.awt.color.ColorSpace;
1819 import java.awt.image.BufferedImage;
1920 import java.awt.image.DataBuffer;
2021 import java.awt.image.DataBufferByte;
2122 import java.awt.image.DataBufferUShort;
23 import java.awt.image.IndexColorModel;
24 import java.awt.image.MultiPixelPackedSampleModel;
2225 import java.awt.image.Raster;
2326 import java.io.IOException;
2427 import java.io.InputStream;
4851 */
4952 public final class JPXFilter extends Filter
5053 {
54 /**
55 * {@inheritDoc}
56 */
5157 @Override
5258 public DecodeResult decode(InputStream encoded, OutputStream decoded, COSDictionary
5359 parameters, int index, DecodeOptions options) throws IOException
7379 }
7480 return result;
7581
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
7699 default:
77100 throw new IOException("Data type " + raster.getDataBuffer().getDataType() + " not implemented");
78101 }
135158 // extract embedded color space
136159 if (!parameters.containsKey(COSName.COLORSPACE))
137160 {
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 }
139176 }
140177
141178 return image;
150187 }
151188 }
152189
190 /**
191 * {@inheritDoc}
192 */
153193 @Override
154194 protected void encode(InputStream input, OutputStream encoded, COSDictionary parameters)
155195 throws IOException
4444 import org.apache.pdfbox.util.Matrix;
4545
4646 /**
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.
5050 */
5151 public class LayerUtility
5252 {
1616 package org.apache.pdfbox.multipdf;
1717
1818 import java.awt.geom.AffineTransform;
19 import java.io.Closeable;
1920 import java.io.File;
2021 import java.io.IOException;
2122 import java.io.InputStream;
2324 import java.math.BigDecimal;
2425 import java.util.ArrayList;
2526 import java.util.HashMap;
27 import java.util.HashSet;
2628 import java.util.List;
2729 import java.util.Map;
30 import java.util.Set;
2831 import org.apache.pdfbox.cos.COSArray;
2932 import org.apache.pdfbox.cos.COSBase;
3033 import org.apache.pdfbox.cos.COSDictionary;
4447 * Based on code contributed by Balazs Jerk.
4548 *
4649 */
47 public class Overlay
50 public class Overlay implements Closeable
4851 {
4952 /**
5053 * Possible location of the overlayed pages: foreground or background.
6063 private LayoutPage oddPageOverlayPage;
6164 private LayoutPage evenPageOverlayPage;
6265
63 private final Map<Integer, PDDocument> specificPageOverlay = new HashMap<Integer, PDDocument>();
66 private final Set<PDDocument> openDocuments = new HashSet<PDDocument>();
6467 private Map<Integer, LayoutPage> specificPageOverlayPage = new HashMap<Integer, LayoutPage>();
6568
6669 private Position position = Position.BACKGROUND;
115118 loadedDocuments.put(e.getValue(), doc);
116119 layouts.put(doc, getLayoutPage(doc));
117120 }
118 specificPageOverlay.put(e.getKey(), doc);
121 openDocuments.add(doc);
119122 specificPageOverlayPage.put(e.getKey(), layouts.get(doc));
120123 }
121124 processPages(inputPDFDocument);
123126 }
124127
125128 /**
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 *
128138 * @throws IOException if something went wrong
129139 */
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
130161 public void close() throws IOException
131162 {
132163 if (defaultOverlay != null)
153184 {
154185 evenPageOverlay.close();
155186 }
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();
165193 }
166194
167195 private void loadPDFs() throws IOException
9090 private DocumentMergeMode documentMergeMode = DocumentMergeMode.PDFBOX_LEGACY_MODE;
9191
9292 /**
93 * The mode to use when merging documents.
93 * The mode to use when merging documents:
9494 *
95 * <p><ul>
95 * <ul>
9696 * <li>{@link DocumentMergeMode#OPTIMIZE_RESOURCES_MODE} Optimizes resource handling such as
9797 * closing documents early. <strong>Not all document elements are merged</strong> compared to
9898 * the PDFBOX_LEGACY_MODE. Currently supported are:
223223 */
224224 public void addSource(File source) throws FileNotFoundException
225225 {
226 FileInputStream stream = new FileInputStream(source);
227 sources.add(stream);
226 sources.add(source);
228227 }
229228
230229 /**
620620 */
621621 protected COSArray parseCOSArray() throws IOException
622622 {
623 long startPosition = seqSource.getPosition();
623624 readExpectedChar('[');
624625 COSArray po = new COSArray();
625626 COSBase pbo;
631632 if( pbo instanceof COSObject )
632633 {
633634 // 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)
635636 {
636637 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)
638639 {
639640 COSInteger number = (COSInteger)po.remove( po.size() -1 );
640641 COSObjectKey key = new COSObjectKey(number.longValue(), genNumber.intValue());
658659 else
659660 {
660661 //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);
662664
663665 // This could also be an "endobj" or "endstream" which means we can assume that
664666 // the array has ended.
322322 {
323323 // xref table and trailer
324324 // 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 "
329328 + source.getPosition());
330329 }
331330 COSDictionary trailer = xrefTrailerResolver.getCurrentTrailer();
17361735 skipSpaces();
17371736 COSDictionary trailerDict = parseCOSDictionary();
17381737 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;
17621755 }
17631756 if (rootFound && infoFound)
17641757 {
20312024 }
20322025 int start = 0;
20332026 // skip spaces
2034 while (numbersBytes[start] == 32)
2027 while (start < numbersBytes.length && numbersBytes[start] == 32)
20352028 {
20362029 start++;
20372030 }
20482041 Map<COSObjectKey, Long> xrefOffset = xrefTrailerResolver.getXrefTable();
20492042 for (int i = 0; i < nrOfObjects; i++)
20502043 {
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);
20582058 }
20592059 }
20602060 }
23332333 List<? extends COSBase> kidsList = kidsArray.toList();
23342334 for (COSBase kid : kidsList)
23352335 {
2336 COSObject kidObject = (COSObject) kid;
2337 if (set.contains(kidObject))
2336 if (!(kid instanceof COSObject) || set.contains((COSObject) kid))
23382337 {
23392338 kidsArray.remove(kid);
23402339 continue;
23412340 }
2341 COSObject kidObject = (COSObject) kid;
23422342 COSBase kidBaseobject = kidObject.getObject();
23432343 // object wasn't dereferenced -> remove it
2344 if (kidBaseobject.equals(COSNull.NULL))
2344 if (kidBaseobject == null || kidBaseobject.equals(COSNull.NULL))
23452345 {
23462346 LOG.warn("Removed null object " + kid + " from pages dictionary");
23472347 kidsArray.remove(kid);
24962496 if (source.getPosition() == trailerOffset)
24972497 {
24982498 // warn only the first time
2499 LOG.warn("Expected trailer object at position " + trailerOffset
2499 LOG.warn("Expected trailer object at offset " + trailerOffset
25002500 + ", keep trying");
25012501 }
25022502 readLine();
26872687 if (splitString.length != 2)
26882688 {
26892689 LOG.warn("Unexpected XRefTable Entry: " + currentLine);
2690 break;
2690 return false;
26912691 }
26922692 // 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
26942704 // 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 }
26962715
26972716 skipSpaces();
26982717 for(int i = 0; i < count; i++)
28372856 }
28382857 }
28392858 // parse catalog or root object
2840 COSObject root = (COSObject) trailer.getItem(COSName.ROOT);
2859 COSObject root = trailer.getCOSObject(COSName.ROOT);
28412860 if (root == null)
28422861 {
28432862 throw new IOException("Missing root object specification in trailer.");
29152934 private void parseDictionaryRecursive(COSObject dictionaryObject) throws IOException
29162935 {
29172936 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 }
29182943 COSDictionary dictionary = (COSDictionary) dictionaryObject.getObject();
29192944 for (COSBase value : dictionary.getValues())
29202945 {
5151 public int read(byte[] b) throws IOException
5252 {
5353 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 }
5663 }
5764
5865 @Override
5966 public int read(byte[] b, int offset, int length) throws IOException
6067 {
6168 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 }
6478 }
6579
6680 @Override
110124 while (len > 0)
111125 {
112126 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 }
116137 }
117138 return bytes;
118139 }
6868 {
6969 //need to first parse the header.
7070 int numberOfObjects = stream.getInt( "N" );
71 if (numberOfObjects == -1)
72 {
73 throw new IOException("/N entry missing in object stream");
74 }
7175 List<Long> objectNumbers = new ArrayList<Long>( numberOfObjects );
7276 streamObjects = new ArrayList<COSObject>( numberOfObjects );
7377 for( int i=0; i<numberOfObjects; i++ )
2424 import org.apache.pdfbox.cos.COSDictionary;
2525 import org.apache.pdfbox.cos.COSDocument;
2626 import org.apache.pdfbox.cos.COSName;
27 import org.apache.pdfbox.cos.COSNull;
28 import org.apache.pdfbox.cos.COSObject;
2729 import org.apache.pdfbox.io.IOUtils;
2830 import org.apache.pdfbox.io.RandomAccessRead;
2931 import org.apache.pdfbox.io.ScratchFile;
190192 }
191193 // check pages dictionaries
192194 checkPages(root);
195 if (!(root.getDictionaryObject(COSName.PAGES) instanceof COSDictionary))
196 {
197 throw new IOException("Page tree root must be a dictionary");
198 }
193199 document.setDecrypted();
194200 initialParseDone = true;
195201 }
7171 }
7272 COSArray xrefFormat = (COSArray) w;
7373
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.
8083 indexArray = new COSArray();
8184 indexArray.add(COSInteger.ZERO);
82 indexArray.add(stream.getDictionaryObject(COSName.SIZE));
85 indexArray.add(COSInteger.get(stream.getInt(COSName.SIZE, 0)));
8386 }
8487
8588 List<Long> objNums = new ArrayList<Long>();
8891 * Populates objNums with all object numbers available
8992 */
9093 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++)
96113 {
97114 objNums.add(objID + i);
98115 }
101118 /*
102119 * Calculating the size of the line in bytes
103120 */
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);
107124 int lineSize = w0 + w1 + w2;
108125
109126 while(!seqSource.isEOF() && objIter.hasNext())
157157
158158 // document-wide cached resources
159159 private ResourceCache resourceCache = new DefaultResourceCache();
160
160
161 // to make sure only one signature is added
162 private boolean signatureAdded = false;
163
161164 /**
162165 * Creates an empty PDF document.
163166 * You need to add at least one page for the document to be valid.
228231 * Add parameters of signature to be created externally using default signature options. See
229232 * {@link #saveIncrementalForExternalSigning(OutputStream)} method description on external
230233 * 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.
231237 *
232238 * @param sigObject is the PDSignatureField model
233239 * @throws IOException if there is an error creating required fields
240 * @throws IllegalStateException if one attempts to add several signature
241 * fields.
234242 */
235243 public void addSignature(PDSignature sigObject) throws IOException
236244 {
241249 * Add parameters of signature to be created externally. See
242250 * {@link #saveIncrementalForExternalSigning(OutputStream)} method description on external
243251 * 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.
244255 *
245256 * @param sigObject is the PDSignatureField model
246257 * @param options signature options
247258 * @throws IOException if there is an error creating required fields
259 * @throws IllegalStateException if one attempts to add several signature
260 * fields.
248261 */
249262 public void addSignature(PDSignature sigObject, SignatureOptions options) throws IOException
250263 {
253266
254267 /**
255268 * 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.
256272 *
257273 * @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.
259276 * @throws IOException if there is an error creating required fields
277 * @throws IllegalStateException if one attempts to add several signature
278 * fields.
260279 */
261280 public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface) throws IOException
262281 {
267286 * This will add a signature to the document. If the 0-based page number in the options
268287 * parameter is smaller than 0 or larger than max, the nearest valid page number will be used
269288 * (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.
270292 *
271293 * @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.
273296 * @param options signature options
274297 * @throws IOException if there is an error creating required fields
298 * @throws IllegalStateException if one attempts to add several signature
299 * fields.
275300 */
276301 public void addSignature(PDSignature sigObject, SignatureInterface signatureInterface,
277302 SignatureOptions options) throws IOException
278303 {
304 if (signatureAdded)
305 {
306 throw new IllegalStateException("Only one signature may be added in a document");
307 }
308 signatureAdded = true;
309
279310 // Reserve content
280311 // We need to reserve some space for the signature. Some signatures including
281312 // big certificate chain and we need enough space to store it.
583614 * This will add a list of signature fields to the document.
584615 *
585616 * @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.
587619 * @param options signature options
588620 * @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
590625 public void addSignatureField(List<PDSignatureField> sigFields, SignatureInterface signatureInterface,
591626 SignatureOptions options) throws IOException
592627 {
659694 }
660695
661696 /**
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()}
666701 * method.
667702 * <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
669704 * annotations, and if these link to pages not in the target document, then the target document
670705 * might become huge. What you need to do is to delete page references of such annotations. See
671706 * <a href="http://stackoverflow.com/a/35477351/535646">here</a> for how to do this.
672707 * <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.
675715 *
676716 * @param page The page to import.
677717 * @return The page that was imported.
678 *
718 *
679719 * @throws IOException If there is an error copying the page.
680720 */
681721 public PDPage importPage(PDPage page) throws IOException
13031343 * Save the PDF as an incremental update. This is only possible if the PDF was loaded from a
13041344 * file or a stream, not if the document was created in PDFBox itself.
13051345 *
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!
13081349 * @throws IOException if the output could not be written
13091350 * @throws IllegalStateException if the document was not loaded from a file or a stream.
13101351 */
13601401 * {@code PDDocument} instance and only AFTER {@link ExternalSigningSupport} instance is used.
13611402 * </p>
13621403 *
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!
13651407 * @return instance to be used for external signing and setting CMS signature
13661408 * @throws IOException if the output could not be written
13671409 * @throws IllegalStateException if the document was not loaded from a file or a stream or
464464 *
465465 * @param text The Unicode text to show.
466466 * @throws IOException If an io exception occurs.
467 * @throws IllegalArgumentException if a character isn't supported by the current font
467468 */
468469 public void showText(String text) throws IOException
469470 {
25012502 @Override
25022503 public void close() throws IOException
25032504 {
2505 if (inTextMode)
2506 {
2507 LOG.warn("You did not call endText(), some viewers won't display your text");
2508 }
25042509 if (output != null)
25052510 {
25062511 output.close();
7171 {
7272 if (root == null)
7373 {
74 throw new IllegalArgumentException("root cannot be null");
74 throw new IllegalArgumentException("page tree root cannot be null");
7575 }
7676 // repair bad PDFs which contain a Page dict instead of a page tree, see PDFBOX-3154
7777 if (COSName.PAGE.equals(root.getCOSName(COSName.TYPE)))
3636 import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderEffectDictionary;
3737 import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
3838 import org.apache.pdfbox.util.DateConverter;
39 import org.w3c.dom.CDATASection;
3940 import org.w3c.dom.Element;
4041 import org.w3c.dom.NamedNodeMap;
4142 import org.w3c.dom.Node;
4243 import org.w3c.dom.NodeList;
44 import org.w3c.dom.Text;
4345
4446 /**
4547 * This represents an FDF annotation that is part of the FDF document.
958960
959961 private String richContentsToString(Node node, boolean root)
960962 {
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("&", "&amp;").replace("<", "&lt;");
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("\"", "&quot;");
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 }
157157 public float getWidthFromFont(int code) throws IOException
158158 {
159159 PDType3CharProc charProc = getCharProc(code);
160 if (charProc == null)
160 if (charProc == null || charProc.getContentStream() == null ||
161 charProc.getContentStream().getLength() == 0)
161162 {
162163 return 0;
163164 }
2727 public interface PDVectorFont
2828 {
2929 /**
30 * Returns the glyph path for the given character code.
30 * Returns the glyph path for the given character code in a PDF.
3131 *
3232 * @param code character code in a PDF. Not to be confused with unicode.
3333 * @throws java.io.IOException if the font could not be read
3434 */
3535 GeneralPath getPath(int code) throws IOException;
36
36
3737 /**
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.
3939 *
4040 * @param code character code in a PDF. Not to be confused with unicode.
4141 */
152152 @Override
153153 public PDResources getResources()
154154 {
155 COSDictionary resources = (COSDictionary) getCOSObject().getDictionaryObject(COSName.RESOURCES);
155 COSDictionary resources = getCOSObject().getCOSDictionary(COSName.RESOURCES);
156156 if (resources != null)
157157 {
158158 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();
159166 }
160167 return null;
161168 }
1616 package org.apache.pdfbox.pdmodel.graphics.shading;
1717
1818 import java.awt.Color;
19 import java.awt.Paint;
2019 import java.awt.PaintContext;
2120 import java.awt.Rectangle;
2221 import java.awt.RenderingHints;
3231 * AWT Paint for axial shading.
3332 *
3433 */
35 public class AxialShadingPaint implements Paint
34 public class AxialShadingPaint extends ShadingPaint<PDShadingType2>
3635 {
3736 private static final Log LOG = LogFactory.getLog(AxialShadingPaint.class);
38
39 private final PDShadingType2 shading;
40 private final Matrix matrix;
4137
4238 /**
4339 * Constructor.
4743 */
4844 AxialShadingPaint(PDShadingType2 shadingType2, Matrix matrix)
4945 {
50 shading = shadingType2;
51 this.matrix = matrix;
46 super(shadingType2, matrix);
5247 }
5348
5449 @Override
1616 package org.apache.pdfbox.pdmodel.graphics.shading;
1717
1818 import java.awt.Color;
19 import java.awt.Paint;
2019 import java.awt.PaintContext;
2120 import java.awt.Rectangle;
2221 import java.awt.RenderingHints;
3332 * AWT Paint for radial shading.
3433 *
3534 */
36 public class RadialShadingPaint implements Paint
35 public class RadialShadingPaint extends ShadingPaint<PDShadingType3>
3736 {
3837 private static final Log LOG = LogFactory.getLog(RadialShadingPaint.class);
39
40 private final PDShadingType3 shading;
41 private final Matrix matrix;
4238
4339 /**
4440 * Constructor.
4844 */
4945 RadialShadingPaint(PDShadingType3 shading, Matrix matrix)
5046 {
51 this.shading = shading;
52 this.matrix = matrix;
47 super(shading, matrix);
5348 }
5449
5550 @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 }
1616 package org.apache.pdfbox.pdmodel.graphics.shading;
1717
1818 import java.awt.Color;
19 import java.awt.Paint;
2019 import java.awt.PaintContext;
2120 import java.awt.Rectangle;
2221 import java.awt.RenderingHints;
3130 /**
3231 * AWT PaintContext for function-based (Type 1) shading.
3332 */
34 class Type1ShadingPaint implements Paint
33 class Type1ShadingPaint extends ShadingPaint<PDShadingType1>
3534 {
3635 private static final Log LOG = LogFactory.getLog(Type1ShadingPaint.class);
37
38 private final PDShadingType1 shading;
39 private final Matrix matrix;
4036
4137 /**
4238 * Constructor.
4642 */
4743 Type1ShadingPaint(PDShadingType1 shading, Matrix matrix)
4844 {
49 this.shading = shading;
50 this.matrix = matrix;
45 super(shading, matrix);
5146 }
5247
5348 @Override
3131 /**
3232 * AWT PaintContext for Gouraud Triangle Mesh (Type 4) shading.
3333 */
34 class Type4ShadingPaint implements Paint
34 class Type4ShadingPaint extends ShadingPaint<PDShadingType4>
3535 {
3636 private static final Log LOG = LogFactory.getLog(Type4ShadingPaint.class);
37
38 private final PDShadingType4 shading;
39 private final Matrix matrix;
4037
4138 /**
4239 * Constructor.
4643 */
4744 Type4ShadingPaint(PDShadingType4 shading, Matrix matrix)
4845 {
49 this.shading = shading;
50 this.matrix = matrix;
46 super(shading, matrix);
5147 }
5248
5349 @Override
1616 package org.apache.pdfbox.pdmodel.graphics.shading;
1717
1818 import java.awt.Color;
19 import java.awt.Paint;
2019 import java.awt.PaintContext;
2120 import java.awt.Rectangle;
2221 import java.awt.RenderingHints;
3130 /**
3231 * AWT Paint for Gouraud Triangle Lattice (Type 5) shading.
3332 */
34 class Type5ShadingPaint implements Paint
33 class Type5ShadingPaint extends ShadingPaint<PDShadingType5>
3534 {
3635 private static final Log LOG = LogFactory.getLog(Type5ShadingPaint.class);
37
38 private final PDShadingType5 shading;
39 private final Matrix matrix;
4036
4137 /**
4238 * Constructor.
4642 */
4743 Type5ShadingPaint(PDShadingType5 shading, Matrix matrix)
4844 {
49 this.shading = shading;
50 this.matrix = matrix;
45 super(shading, matrix);
5146 }
5247
5348 @Override
1515 package org.apache.pdfbox.pdmodel.graphics.shading;
1616
1717 import java.awt.Color;
18 import java.awt.Paint;
1918 import java.awt.PaintContext;
2019 import java.awt.Rectangle;
2120 import java.awt.RenderingHints;
3332 *
3433 * @author Shaola Ren
3534 */
36 class Type6ShadingPaint implements Paint
35 class Type6ShadingPaint extends ShadingPaint<PDShadingType6>
3736 {
3837 private static final Log LOG = LogFactory.getLog(Type6ShadingPaint.class);
39
40 private final PDShadingType6 shading;
41 private final Matrix matrix;
4238
4339 /**
4440 * Constructor.
4844 */
4945 Type6ShadingPaint(PDShadingType6 shading, Matrix matrix)
5046 {
51 this.shading = shading;
52 this.matrix = matrix;
47 super(shading, matrix);
5348 }
5449
5550 @Override
1515 package org.apache.pdfbox.pdmodel.graphics.shading;
1616
1717 import java.awt.Color;
18 import java.awt.Paint;
1918 import java.awt.PaintContext;
2019 import java.awt.Rectangle;
2120 import java.awt.RenderingHints;
3332 *
3433 * @author Shaola Ren
3534 */
36 class Type7ShadingPaint implements Paint
35 class Type7ShadingPaint extends ShadingPaint<PDShadingType7>
3736 {
3837 private static final Log LOG = LogFactory.getLog(Type7ShadingPaint.class);
39
40 private final PDShadingType7 shading;
41 private final Matrix matrix;
4238
4339 /**
4440 * Constructor.
4844 */
4945 Type7ShadingPaint(PDShadingType7 shading, Matrix matrix)
5046 {
51 this.shading = shading;
52 this.matrix = matrix;
47 super(shading, matrix);
5348 }
5449
5550 @Override
225225 yAxis = pageHeight - xAxis - imageWidth;
226226 xAxis = temp;
227227
228 affineTransform = new AffineTransform(
229 0, imageHeight / imageWidth, -imageWidth / imageHeight, 0, imageWidth, 0);
230
228231 temp = imageHeight;
229232 imageHeight = imageWidth;
230233 imageWidth = temp;
231
232 affineTransform = new AffineTransform(0, 0.5, -2, 0, 100, 0);
233234 break;
234
235
235236 case 180:
236237 float newX = pageWidth - xAxis - imageWidth;
237238 float newY = pageHeight - yAxis - imageHeight;
238239 xAxis = newX;
239240 yAxis = newY;
240
241 affineTransform = new AffineTransform(-1, 0, 0, -1, 100, 50);
241
242 affineTransform = new AffineTransform(-1, 0, 0, -1, imageWidth, imageHeight);
242243 break;
243244
244245 case 270:
246247 xAxis = pageWidth - yAxis - imageHeight;
247248 yAxis = temp;
248249
250 affineTransform = new AffineTransform(
251 0, -imageHeight / imageWidth, imageWidth / imageHeight, 0, 0, imageHeight);
252
249253 temp = imageHeight;
250254 imageHeight = imageWidth;
251255 imageWidth = temp;
252
253 affineTransform = new AffineTransform(0, -0.5, 2, 0, 0, 50);
254256 break;
255257
256258 case 0:
381381 // update the appearance state (AS)
382382 for (PDAnnotationWidget widget : getWidgets())
383383 {
384 if (widget.getAppearance() == null)
385 {
386 continue;
387 }
384388 PDAppearanceEntry appearanceEntry = widget.getAppearance().getNormalAppearance();
385389 if (((COSDictionary) appearanceEntry.getCOSObject()).containsKey(value))
386390 {
387 widget.getCOSObject().setName(COSName.AS, value);
391 widget.setAppearanceState(value);
388392 }
389393 else
390394 {
391 widget.getCOSObject().setItem(COSName.AS, COSName.Off);
395 widget.setAppearanceState(COSName.Off.getName());
392396 }
393397 }
394398 }
4545 class TilingPaint implements Paint
4646 {
4747 private static final Log LOG = LogFactory.getLog(TilingPaint.class);
48 private final TexturePaint paint;
48 private final Paint paint;
4949 private final Matrix patternMatrix;
5050 private static final int MAXEDGE;
5151 private static final String DEFAULTMAXEDGE = "3000";
1414 */
1515 package org.apache.pdfbox.rendering;
1616
17 import java.awt.Paint;
1718 import java.awt.geom.AffineTransform;
1819 import java.io.IOException;
1920 import java.lang.ref.WeakReference;
3334 class TilingPaintFactory
3435 {
3536 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>>();
3839
3940 TilingPaintFactory(PageDrawer drawer)
4041 {
4142 this.drawer = drawer;
4243 }
4344
44 TilingPaint create(PDTilingPattern pattern, PDColorSpace colorSpace,
45 Paint create(PDTilingPattern pattern, PDColorSpace colorSpace,
4546 PDColor color, AffineTransform xform) throws IOException
4647 {
47 TilingPaint paint = null;
48 Paint paint = null;
4849 TilingPaintParameter tilingPaintParameter
4950 = new TilingPaintParameter(drawer.getInitialMatrix(), pattern.getCOSObject(), colorSpace, color, xform);
50 WeakReference<TilingPaint> weakRef = weakCache.get(tilingPaintParameter);
51 WeakReference<Paint> weakRef = weakCache.get(tilingPaintParameter);
5152 if (weakRef != null)
5253 {
5354 // PDFBOX-4058: additional WeakReference makes gc work better
5657 if (paint == null)
5758 {
5859 paint = new TilingPaint(drawer, pattern, colorSpace, color, xform);
59 weakCache.put(tilingPaintParameter, new WeakReference<TilingPaint>(paint));
60 weakCache.put(tilingPaintParameter, new WeakReference<Paint>(paint));
6061 }
6162 return paint;
6263 }
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 }
1616
1717 import java.awt.image.BufferedImage;
1818 import java.io.File;
19 import java.io.FileOutputStream;
1920 import java.io.IOException;
21 import java.io.OutputStream;
2022 import java.util.HashSet;
2123 import java.util.Set;
2224
233235 doc.close();
234236 }
235237
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
236274 private class ElementCounter
237275 {
238276 int cnt = 0;
307307 PDDocument.load(new File(TARGETPDFDIR, "genko_oc_shiryo1.pdf")).close();
308308 }
309309
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
310334 private void executeParserTest(RandomAccessRead source, MemoryUsageSetting memUsageSetting) throws IOException
311335 {
312336 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 }
1818 import java.util.HashSet;
1919 import java.util.Set;
2020 import static org.junit.Assert.assertEquals;
21 import org.junit.Rule;
2122 import org.junit.Test;
23 import org.junit.rules.ExpectedException;
2224
23 /**
24 * @author Tilman Hausherr
25 */
2625 public class PageLayoutTest
2726 {
2827 /**
28 * @author Tilman Hausherr
29 *
2930 * Test for completeness (PDFBOX-3362).
3031 */
3132 @Test
4243 assertEquals(PageLayout.values().length, pageLayoutSet.size());
4344 assertEquals(PageLayout.values().length, stringSet.size());
4445 }
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 }
4565 }
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 }
2222 import java.net.URISyntaxException;
2323 import java.util.List;
2424
25 import org.junit.Assert;
2526 import org.junit.Test;
2627
2728 /**
3839 File f = new File(FDFAnnotationTest.class.getResource("xfdf-test-document-annotations.xml").toURI());
3940 FDFDocument fdfDoc = FDFDocument.loadXFDF(f);
4041 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&amp;1 <span style=\"text-"
59 + "decoration:word;font-family:Helvetica\">P&amp;2</span> "
60 + "P&amp;3</p>\n"
61 + " </body>", annotationFreeText.getRichContents().trim());
62 }
63 }
64 }
65 Assert.assertTrue(testedPDFBox4345andPDFBox3646);
4266 fdfDoc.close();
4367 }
4468 }
1515 package org.apache.pdfbox.pdmodel.graphics.image;
1616
1717 import java.awt.image.BufferedImage;
18 import java.io.ByteArrayOutputStream;
1918 import java.io.File;
2019 import java.io.IOException;
20 import java.io.OutputStream;
2121 import java.util.HashSet;
2222 import java.util.Set;
2323 import javax.imageio.ImageIO;
24 import javax.imageio.ImageWriter;
25 import javax.imageio.spi.ImageWriterSpi;
2426 import org.apache.pdfbox.cos.COSName;
2527 import org.apache.pdfbox.cos.COSStream;
2628 import org.apache.pdfbox.pdmodel.PDDocument;
6062 assertEquals(ximage.getWidth(), ximage.getImage().getWidth());
6163 assertEquals(ximage.getHeight(), ximage.getImage().getHeight());
6264
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());
6582 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 }
6991 }
7092
7193 static int colorCount(BufferedImage bim)
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 }
2424 import junit.framework.TestSuite;
2525
2626 import org.apache.pdfbox.cos.COSArray;
27 import org.apache.pdfbox.cos.COSDictionary;
2728 import org.apache.pdfbox.cos.COSName;
2829 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;
2937
3038 /**
31 * This will test the functionality of Radio Buttons in PDFBox.
39 * This will test the functionality of checkboxes in PDFBox.
3240 */
3341 public class TestCheckBox extends TestCase
3442 {
6573 }
6674
6775 /**
68 * This will test the radio button PDModel.
76 * This will test the checkbox PDModel.
6977 *
7078 * @throws IOException If there is an error creating the field.
7179 */
105113 checkBox.setExportValues(null);
106114 assertNull(checkBox.getCOSObject().getItem(COSName.OPT));
107115 // if there is no Opt entry an empty List shall be returned
108 assertEquals(checkBox.getExportValues(), new ArrayList<String>());
116 assertTrue(checkBox.getExportValues().isEmpty());
109117 }
110118 finally
111119 {
115123 }
116124 }
117125 }
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 }
118162 }
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
6464 <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" />
6565 <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" />
6666 <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&amp;1 <span style="text-decoration:word;font-family:Helvetica">P&amp;2</span> P&amp;3</p>
74 </body>
75 </contents-richtext>
76 <defaultappearance>/Helvetica 12 Tf 0.842 0.424 0.000 rg</defaultappearance>
77 </freetext>
6778 </annots>
6879 </xfdf>
2222 <parent>
2323 <groupId>org.apache.pdfbox</groupId>
2424 <artifactId>pdfbox-parent</artifactId>
25 <version>2.0.12</version>
25 <version>2.0.13</version>
2626 <relativePath>parent/pom.xml</relativePath>
2727 </parent>
2828
3333
3434 <scm>
3535 <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
3737 </connection>
3838 <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
4040 </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>
4242 </scm>
4343
4444 <modules>
2525 <parent>
2626 <groupId>org.apache.pdfbox</groupId>
2727 <artifactId>pdfbox-parent</artifactId>
28 <version>2.0.12</version>
28 <version>2.0.13</version>
2929 <relativePath>../parent/pom.xml</relativePath>
3030 </parent>
3131
3333 <properties>
3434 <skip-bavaria>true</skip-bavaria>
3535 </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>
3656
3757 <build>
3858 <plugins>
191211 <groupId>junit</groupId>
192212 <artifactId>junit</artifactId>
193213 </dependency>
194 <dependency>
195 <groupId>log4j</groupId>
196 <artifactId>log4j</artifactId>
197 <scope>test</scope>
198 </dependency>
199214 <!-- TODO find a suitable place to store the isator test pdfs <dependency>
200215 <groupId>org.pdfa</groupId> <artifactId>isartor</artifactId> <version>1.0-20080813</version>
201216 <scope>test</scope> </dependency> -->
113113 */
114114 private ColorSpaceHelperFactory colorSpaceHelperFact;
115115
116 /**
117 * Define the maximum number of errors.
118 */
119 private int maxErrors = 10000;
120
116121 public static PreflightConfiguration createPdfA1BConfiguration()
117122 {
118123 PreflightConfiguration configuration = new PreflightConfiguration();
299304 this.colorSpaceHelperFact = colorSpaceHelperFact;
300305 }
301306
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 }
302326 }
5050 /**
5151 * The datasource to load the document from. Needed by StreamValidationProcess.
5252 */
53 private DataSource source = null;
53 private DataSource dataSource = null;
5454
5555 /**
5656 * Contains all Xref/trailer objects and resolves them into single object using startxref reference.
8383 /**
8484 * Create the DocumentHandler using the DataSource which represent the PDF file to check.
8585 *
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;
9696 this.config = configuration;
9797 }
9898
147147 */
148148 public DataSource getSource()
149149 {
150 return source;
150 return dataSource;
151151 }
152152
153153 public boolean isComplete()
154154 {
155 return (document != null) && (source != null);
155 return (document != null) && (dataSource != null);
156156 }
157157
158158 /**
156156 public void validate() throws ValidationException
157157 {
158158 // 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
160160 javax.xml.bind.DatatypeConverter.parseInt("0");
161161 context.setConfig(config);
162162 Collection<String> processes = config.getProcessNames();
4242 {
4343
4444
45 public Element validate(DataSource source) throws IOException
45 public Element validate(DataSource dataSource) throws IOException
4646 {
4747 try
4848 {
4949 Document rdocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
50 return validate(rdocument,source);
50 return validate(rdocument,dataSource);
5151 }
5252 catch (ParserConfigurationException e)
5353 {
5656 }
5757
5858
59 public Element validate(Document rdocument, DataSource source) throws IOException
59 public Element validate(Document rdocument, DataSource dataSource) throws IOException
6060 {
6161 String pdfType = null;
6262 ValidationResult result;
6363 long before = System.currentTimeMillis();
6464 try
6565 {
66 PreflightParser parser = new PreflightParser(source);
66 PreflightParser parser = new PreflightParser(dataSource);
6767 try
6868 {
6969 parser.parse();
8181 catch(Exception e)
8282 {
8383 long after = System.currentTimeMillis();
84 return generateFailureResponse(rdocument, source.getName(), after-before, pdfType, e);
84 return generateFailureResponse(rdocument, dataSource.getName(), after-before, pdfType, e);
8585 }
8686
8787 long after = System.currentTimeMillis();
8888 if (result.isValid())
8989 {
90 Element preflight = generateResponseSkeleton(rdocument, source.getName(), after-before);
90 Element preflight = generateResponseSkeleton(rdocument, dataSource.getName(), after-before);
9191 // valid ?
9292 Element valid = rdocument.createElement("isValid");
9393 valid.setAttribute("type", pdfType);
9797 }
9898 else
9999 {
100 Element preflight = generateResponseSkeleton(rdocument, source.getName(), after-before);
100 Element preflight = generateResponseSkeleton(rdocument, dataSource.getName(), after-before);
101101 // valid ?
102102 createResponseWithError(rdocument, pdfType, result, preflight);
103103 return preflight;
2727
2828 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
2929 import org.apache.pdfbox.pdmodel.PDPage;
30 import org.apache.pdfbox.preflight.PreflightConstants;
3031 import static org.apache.pdfbox.preflight.PreflightConstants.ERROR_PDF_PROCESSING_MISSING;
3132 import org.apache.pdfbox.preflight.PreflightContext;
3233 import org.apache.pdfbox.preflight.ValidationResult.ValidationError;
4950 "/Pages dictionary entry is missing in document catalog"));
5051 return;
5152 }
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())
5455 {
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 }
5766 context.setCurrentPageNumber(null);
67 ++p;
5868 }
5969 }
6070 else
358358 * @throws Exception
359359 */
360360 @Test
361 public void testAllInfoSynhcronized() throws Exception
361 public void testAllInfoSynchronized() throws Exception
362362 {
363363 initValues();
364364
+0
-29
preflight/src/test/resources/log4j.xml less more
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>
2222 <parent>
2323 <groupId>org.apache.pdfbox</groupId>
2424 <artifactId>pdfbox-parent</artifactId>
25 <version>2.0.12</version>
25 <version>2.0.13</version>
2626 <relativePath>../parent/pom.xml</relativePath>
2727 </parent>
2828
7979 <Embed-Transitive>true</Embed-Transitive>
8080 <Embed-Dependency>*;scope=provided;inline=org/apache/**|org/bouncycastle/**|com/ibm/icu/**|META-INF/services/**</Embed-Dependency>
8181 <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>
8383 <Main-Class>org.apache.pdfbox.preflight.Validator_A1b</Main-Class>
8484 </instructions>
8585 </configuration>
2222 <parent>
2323 <groupId>org.apache.pdfbox</groupId>
2424 <artifactId>pdfbox-parent</artifactId>
25 <version>2.0.12</version>
25 <version>2.0.13</version>
2626 <relativePath>../parent/pom.xml</relativePath>
2727 </parent>
2828
177177 throw new IOException("You do not have permission to extract images");
178178 }
179179
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 {
183182 ImageGraphicsEngine extractor = new ImageGraphicsEngine(page);
184183 extractor.run();
185184 }
213212 PDTransparencyGroup group = softMask.getGroup();
214213 if (group != null)
215214 {
215 // PDFBOX-4327: without this line NPEs will occur
216 res.getExtGState(name).copyIntoGraphicsState(getGraphicsState());
217
216218 processSoftMask(group);
217219 }
218220 }
2222 import java.io.OutputStreamWriter;
2323 import java.io.Writer;
2424 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;
2531 import org.apache.pdfbox.io.IOUtils;
2632 import org.apache.pdfbox.pdmodel.PDDocument;
2733 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
2834 import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary;
2935 import org.apache.pdfbox.pdmodel.PDEmbeddedFilesNameTreeNode;
36 import org.apache.pdfbox.pdmodel.PDPage;
37 import org.apache.pdfbox.pdmodel.PDPageContentStream;
3038 import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
3139 import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
3240 import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
3341 import org.apache.pdfbox.text.PDFTextStripper;
42 import org.apache.pdfbox.text.TextPosition;
43 import org.apache.pdfbox.util.Matrix;
3444
3545 /**
3646 * This is the main program that simply parses the pdf document and transforms it
3747 * into text.
3848 *
3949 * @author Ben Litchfield
50 * @author Tilman Hausherr
4051 */
4152 public final class ExtractText
4253 {
54 private static final Log LOG = LogFactory.getLog(ExtractText.class);
55
4356 private static final String PASSWORD = "-password";
4457 private static final String ENCODING = "-encoding";
4558 private static final String CONSOLE = "-console";
4962 private static final String IGNORE_BEADS = "-ignoreBeads";
5063 private static final String DEBUG = "-debug";
5164 private static final String HTML = "-html";
52
65 private static final String ALWAYSNEXT = "-alwaysNext";
66 private static final String ROTATION_MAGIC = "-rotationMagic";
5367 private static final String STD_ENCODING = "UTF-8";
5468
5569 /*
92106 boolean toHTML = false;
93107 boolean sort = false;
94108 boolean separateBeads = true;
109 boolean alwaysNext = false;
110 boolean rotationMagic = false;
95111 String password = "";
96112 String encoding = STD_ENCODING;
97113 String pdfFile = null;
142158 {
143159 separateBeads = false;
144160 }
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 }
145169 else if( args[i].equals( DEBUG ) )
146170 {
147171 debug = true;
211235 }
212236 output = new OutputStreamWriter( new FileOutputStream( outputFile ), encoding );
213237 }
238 startTime = startProcessing("Starting text extraction");
239 if (debug)
240 {
241 System.err.println("Writing to " + outputFile);
242 }
214243
215244 PDFTextStripper stripper;
216245 if(toHTML)
217246 {
247 // HTML stripper can't work page by page because of startDocument() callback
218248 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);
219256 }
220257 else
221258 {
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
238275 // ... also for any embedded PDFs:
239276 PDDocumentCatalog catalog = document.getDocumentCatalog();
240277 PDDocumentNameDictionary names = catalog.getNames();
265302 try
266303 {
267304 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 }
268315 }
269316 finally
270317 {
271318 fis.close();
272 }
273 try
274 {
275 stripper.writeText( subDoc, output );
276 }
277 finally
278 {
279319 IOUtils.closeQuietly(subDoc);
280320 }
281321 }
293333 }
294334 }
295335
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
296388 private long startProcessing(String message)
297389 {
298390 if (debug)
319411 {
320412 String message = "Usage: java -jar pdfbox-app-x.y.z.jar ExtractText [options] <inputfile> [output-text-file]\n"
321413 + "\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";
333432
334433 System.err.println(message);
335434 System.exit( 1 );
336435 }
337436 }
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 }
136136 usage();
137137 }
138138
139 try
139 try
140140 {
141141 PDDocument result = overlayer.overlay(specificPageOverlayFile);
142142 result.save(outputFilename);
143143 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();
147144 }
148145 catch (IOException e)
149146 {
150147 LOG.error("Overlay failed: " + e.getMessage(), e);
151148 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();
152155 }
153156 }
154157
2626 <parent>
2727 <groupId>org.apache.pdfbox</groupId>
2828 <artifactId>pdfbox-parent</artifactId>
29 <version>2.0.12</version>
29 <version>2.0.13</version>
3030 <relativePath>../parent/pom.xml</relativePath>
3131 </parent>
3232
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>
3347
3448 <dependencies>
3549 <dependency>
4545 @Test
4646 public void testDateConversion() throws Exception
4747 {
48 final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
49 Calendar jaxbCal;
4850
49 final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
50 Calendar jaxbCal = null,
51 convDate = null;
5251 // Test partial dates
53 convDate = DateConverter.toCalendar("2015-02-02");
52 Calendar convDate = DateConverter.toCalendar("2015-02-02");
5453 assertEquals(2015, convDate.get(Calendar.YEAR));
55
54
5655 //Test missing seconds
5756 assertEquals(DateConverter.toCalendar("2015-12-08T12:07:00-05:00"),
5857 DateConverter.toCalendar("2015-12-08T12:07-05:00"));