1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package cz.hobrasoft.pdfmu.operation;
18
19 import com.itextpdf.text.pdf.AcroFields;
20 import com.itextpdf.text.pdf.PdfReader;
21 import com.itextpdf.text.pdf.security.CertificateInfo;
22 import com.itextpdf.text.pdf.security.CertificateInfo.X500Name;
23 import com.itextpdf.text.pdf.security.PdfPKCS7;
24 import cz.hobrasoft.pdfmu.MapSorter;
25 import cz.hobrasoft.pdfmu.PreferenceListComparator;
26 import cz.hobrasoft.pdfmu.jackson.CertificateResult;
27 import cz.hobrasoft.pdfmu.jackson.Inspect;
28 import cz.hobrasoft.pdfmu.jackson.Signature;
29 import cz.hobrasoft.pdfmu.jackson.SignatureDisplay;
30 import cz.hobrasoft.pdfmu.jackson.SignatureMetadata;
31 import cz.hobrasoft.pdfmu.operation.args.InPdfArgs;
32 import cz.hobrasoft.pdfmu.operation.metadata.MetadataParameters;
33 import cz.hobrasoft.pdfmu.operation.version.PdfVersion;
34 import java.io.File;
35 import java.io.FileInputStream;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.security.cert.Certificate;
39 import java.security.cert.X509Certificate;
40 import java.util.ArrayList;
41 import java.util.Date;
42 import java.util.HashMap;
43 import java.util.LinkedHashMap;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Map.Entry;
47 import java.util.SortedMap;
48 import javax.security.auth.x500.X500Principal;
49 import net.sourceforge.argparse4j.inf.Namespace;
50 import net.sourceforge.argparse4j.inf.Subparser;
51 import org.apache.commons.lang3.StringUtils;
52
53
54
55
56
57 public class OperationInspect extends OperationCommon {
58
59 private final InPdfArgs in = new InPdfArgs();
60
61 @Override
62 public Subparser configureSubparser(Subparser subparser) {
63 String help = "Display PDF version, properties and signatures of a PDF document";
64
65
66 subparser.help(help)
67 .description(help)
68 .defaultHelp(true);
69
70 in.addArguments(subparser);
71
72 return subparser;
73 }
74
75 @Override
76 public void execute(Namespace namespace) throws OperationException {
77 in.setFromNamespace(namespace);
78 in.open();
79 PdfReader pdfReader = in.getPdfReader();
80 Inspect result;
81 try {
82 result = execute(pdfReader);
83 } finally {
84 in.close();
85 }
86 writeResult(result);
87 }
88
89 public Inspect execute(File file) throws OperationException, IOException {
90 assert file != null;
91 Inspect result;
92 try (InputStream is = new FileInputStream(file)) {
93 PdfReader pdfReader = new PdfReader(is);
94 try {
95 result = execute(pdfReader);
96 } finally {
97 pdfReader.close();
98 }
99 }
100 return result;
101 }
102
103 private Inspect execute(PdfReader pdfReader) throws OperationException {
104 Inspect result = new Inspect();
105
106
107 PdfVersion inVersion = new PdfVersion(pdfReader.getPdfVersion());
108 to.println(String.format("PDF version: %s", inVersion));
109 result.version = inVersion.toString();
110
111 result.properties = get(pdfReader);
112
113 result.signatures = display(pdfReader);
114
115 return result;
116 }
117
118 private SortedMap<String, String> get(PdfReader pdfReader) {
119 Map<String, String> properties = pdfReader.getInfo();
120
121 MetadataParameters mp = new MetadataParameters();
122 mp.setFromInfo(properties);
123
124 SortedMap<String, String> propertiesSorted = mp.getSorted();
125
126 {
127 to.indentMore("Properties:");
128 for (Map.Entry<String, String> property : propertiesSorted.entrySet()) {
129 String key = property.getKey();
130 String value = property.getValue();
131 to.println(String.format("%s: %s", key, value));
132 }
133 to.indentLess();
134 }
135
136 return propertiesSorted;
137 }
138
139 public SignatureDisplay display(PdfReader pdfReader) {
140
141 AcroFields fields = pdfReader.getAcroFields();
142 return display(fields);
143 }
144
145 private SignatureDisplay display(AcroFields fields) {
146 SignatureDisplay result = new SignatureDisplay();
147
148
149 ArrayList<String> names = fields.getSignatureNames();
150
151
152 to.println(String.format("Number of signatures: %d", names.size()));
153 to.println(String.format("Number of document revisions: %d", fields.getTotalRevisions()));
154 result.nRevisions = fields.getTotalRevisions();
155
156 List<Signature> signatures = new ArrayList<>();
157
158 for (String name : names) {
159 to.println(String.format("Signature field name: %s", name));
160
161 to.indentMore();
162 Signature signature;
163 try {
164 signature = display(fields, name);
165 } finally {
166 to.indentLess();
167 }
168 signature.id = name;
169 signatures.add(signature);
170 }
171
172 result.signatures = signatures;
173
174 return result;
175 }
176
177 private Signature display(AcroFields fields, String name) {
178
179 to.println(String.format("Signature covers the whole document: %s", (fields.signatureCoversWholeDocument(name) ? "Yes" : "No")));
180 to.println(String.format("Document revision: %d of %d", fields.getRevision(name), fields.getTotalRevisions()));
181
182 PdfPKCS7 pkcs7 = fields.verifySignature(name);
183 Signature signature = display(pkcs7);
184 signature.coversWholeDocument = fields.signatureCoversWholeDocument(name);
185 signature.revision = fields.getRevision(name);
186 return signature;
187 }
188
189 private Signature display(PdfPKCS7 pkcs7) {
190 Signature signature = new Signature();
191
192
193 to.println("Signature metadata:");
194 {
195 SignatureMetadata metadata = new SignatureMetadata();
196
197 to.indentMore();
198
199
200
201 {
202 String name = pkcs7.getSignName();
203 metadata.name = name;
204 if (name == null) {
205 to.println("Name is not set.");
206 } else {
207 to.println(String.format("Name: %s", name));
208 }
209 }
210
211
212
213 to.println(String.format("Reason: %s", pkcs7.getReason()));
214 metadata.reason = pkcs7.getReason();
215 to.println(String.format("Location: %s", pkcs7.getLocation()));
216 metadata.location = pkcs7.getLocation();
217
218 {
219 Date date = pkcs7.getSignDate().getTime();
220 to.println(String.format("Date and time: %s", date));
221 metadata.date = date.toString();
222 }
223
224 to.indentLess();
225
226 signature.metadata = metadata;
227 }
228 {
229 to.indentMore("Certificate chain:");
230 Certificate[] certificates = pkcs7.getSignCertificateChain();
231 to.println(String.format("Number of certificates: %d", certificates.length));
232 int i = 0;
233 List<CertificateResult> certificatesResult = new ArrayList<>();
234 for (Certificate certificate : certificates) {
235 to.indentMore(String.format("Certificate %d%s:", i, (i == 0 ? " (the signing certificate)" : "")));
236 CertificateResult certRes;
237 String type = certificate.getType();
238 to.println(String.format("Type: %s", type));
239
240 if ("X.509".equals(type)) {
241 X509Certificate certificateX509 = (X509Certificate) certificate;
242 certRes = showCertInfo(certificateX509);
243 } else {
244 certRes = new CertificateResult();
245 }
246 certRes.type = type;
247 to.indentLess();
248 certificatesResult.add(certRes);
249 ++i;
250 }
251 signature.certificates = certificatesResult;
252 to.indentLess();
253 }
254
255 return signature;
256 }
257
258 private CertificateResult showCertInfo(X509Certificate cert) {
259 CertificateResult certRes = new CertificateResult();
260
261 {
262 X500Principal principalSubject = cert.getSubjectX500Principal();
263 X500Principal principalIssuer = cert.getIssuerX500Principal();
264 boolean selfSigned = principalSubject.equals(principalIssuer);
265 to.println(String.format("Self-signed: %s", (selfSigned ? "Yes" : "No")));
266 certRes.selfSigned = selfSigned;
267 }
268
269
270
271 {
272 to.indentMore("Subject:");
273 certRes.subject = showX500Name(CertificateInfo.getSubjectFields(cert));
274 to.indentLess();
275 }
276 {
277 to.indentMore("Issuer:");
278 certRes.issuer = showX500Name(CertificateInfo.getIssuerFields(cert));
279 to.indentLess();
280 }
281
282 return certRes;
283 }
284
285
286 private static final MapSorter<String> dnTypeSorter = new PreferenceListComparator(new String[]{
287 "CN", "E", "OU", "O", "STREET", "L", "ST", "C"});
288
289
290
291
292 private SortedMap<String, List<String>> showX500Name(X500Name name) {
293 Map<String, ArrayList<String>> fields = name.getFields();
294
295
296 Map<String, List<String>> fieldsLists = new LinkedHashMap<>();
297 fieldsLists.putAll(fields);
298
299
300 SortedMap<String, List<String>> fieldsSorted = dnTypeSorter.sort(fieldsLists);
301
302
303 for (Entry<String, List<String>> field : fieldsSorted.entrySet()) {
304 String type = field.getKey();
305 type = niceX500AttributeType(type);
306 List<String> values = field.getValue();
307 String valuesString = StringUtils.join(values, ", ");
308 to.println(String.format("%s: %s", type, valuesString));
309 }
310
311 return fieldsSorted;
312 }
313
314 private static final Map<String, String> attributeTypeAliases = new HashMap<>();
315
316 static {
317
318
319
320 attributeTypeAliases.put("CN", "Common name");
321 attributeTypeAliases.put("L", "Locality");
322 attributeTypeAliases.put("ST", "State or province");
323 attributeTypeAliases.put("O", "Organization");
324 attributeTypeAliases.put("OU", "Organizational unit");
325 attributeTypeAliases.put("C", "Country code");
326 attributeTypeAliases.put("STREET", "Street address");
327 attributeTypeAliases.put("DC", "Domain component");
328 attributeTypeAliases.put("UID", "User ID");
329 attributeTypeAliases.put("E", "Email address");
330 attributeTypeAliases.put("SN", "Serial number");
331 attributeTypeAliases.put("T", "Title");
332 }
333
334 private static String niceX500AttributeType(String type) {
335 String nice = attributeTypeAliases.get(type);
336 if (nice != null) {
337 type = nice;
338 } else {
339 return String.format("<%s>", type);
340 }
341
342 return type;
343 }
344
345 private static OperationInspect instance = null;
346
347 public static OperationInspect getInstance() {
348 if (instance == null) {
349 instance = new OperationInspect();
350 }
351 return instance;
352 }
353
354 private OperationInspect() {
355
356 }
357
358 }