Java tutorial
/* * Copyright (C) 2016 Hobrasoft s.r.o. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package cz.hobrasoft.pdfmu.operation; import com.itextpdf.text.pdf.AcroFields; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.security.CertificateInfo; import com.itextpdf.text.pdf.security.CertificateInfo.X500Name; import com.itextpdf.text.pdf.security.PdfPKCS7; import cz.hobrasoft.pdfmu.MapSorter; import cz.hobrasoft.pdfmu.PreferenceListComparator; import cz.hobrasoft.pdfmu.jackson.CertificateResult; import cz.hobrasoft.pdfmu.jackson.Inspect; import cz.hobrasoft.pdfmu.jackson.Signature; import cz.hobrasoft.pdfmu.jackson.SignatureDisplay; import cz.hobrasoft.pdfmu.jackson.SignatureMetadata; import cz.hobrasoft.pdfmu.operation.args.InPdfArgs; import cz.hobrasoft.pdfmu.operation.metadata.MetadataParameters; import cz.hobrasoft.pdfmu.operation.version.PdfVersion; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedMap; import javax.security.auth.x500.X500Principal; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.apache.commons.lang3.StringUtils; /** * * @author Filip Bartek */ public class OperationInspect extends OperationCommon { private final InPdfArgs in = new InPdfArgs(); @Override public Subparser configureSubparser(Subparser subparser) { String help = "Display PDF version, properties and signatures of a PDF document"; // Configure the subparser subparser.help(help).description(help).defaultHelp(true); in.addArguments(subparser); return subparser; } @Override public void execute(Namespace namespace) throws OperationException { in.setFromNamespace(namespace); in.open(); PdfReader pdfReader = in.getPdfReader(); Inspect result; try { result = execute(pdfReader); } finally { in.close(); } writeResult(result); } public Inspect execute(File file) throws OperationException, IOException { assert file != null; Inspect result; try (InputStream is = new FileInputStream(file)) { PdfReader pdfReader = new PdfReader(is); try { result = execute(pdfReader); } finally { pdfReader.close(); } } return result; } private Inspect execute(PdfReader pdfReader) throws OperationException { Inspect result = new Inspect(); // Fetch the PDF version of the input PDF document PdfVersion inVersion = new PdfVersion(pdfReader.getPdfVersion()); to.println(String.format("PDF version: %s", inVersion)); result.version = inVersion.toString(); result.properties = get(pdfReader); result.signatures = display(pdfReader); return result; } private SortedMap<String, String> get(PdfReader pdfReader) { Map<String, String> properties = pdfReader.getInfo(); MetadataParameters mp = new MetadataParameters(); mp.setFromInfo(properties); SortedMap<String, String> propertiesSorted = mp.getSorted(); { to.indentMore("Properties:"); for (Map.Entry<String, String> property : propertiesSorted.entrySet()) { String key = property.getKey(); String value = property.getValue(); to.println(String.format("%s: %s", key, value)); } to.indentLess(); } return propertiesSorted; } public SignatureDisplay display(PdfReader pdfReader) { // digitalsignatures20130304.pdf : Code sample 5.1 AcroFields fields = pdfReader.getAcroFields(); return display(fields); } private SignatureDisplay display(AcroFields fields) { SignatureDisplay result = new SignatureDisplay(); // digitalsignatures20130304.pdf : Code sample 5.1 ArrayList<String> names = fields.getSignatureNames(); // Print number of signatures to.println(String.format("Number of signatures: %d", names.size())); to.println(String.format("Number of document revisions: %d", fields.getTotalRevisions())); result.nRevisions = fields.getTotalRevisions(); List<Signature> signatures = new ArrayList<>(); for (String name : names) { to.println(String.format("Signature field name: %s", name)); to.indentMore(); Signature signature; try { signature = display(fields, name); // May throw OperationException } finally { to.indentLess(); } signature.id = name; signatures.add(signature); } result.signatures = signatures; return result; } private Signature display(AcroFields fields, String name) { // digitalsignatures20130304.pdf : Code sample 5.2 to.println(String.format("Signature covers the whole document: %s", (fields.signatureCoversWholeDocument(name) ? "Yes" : "No"))); to.println( String.format("Document revision: %d of %d", fields.getRevision(name), fields.getTotalRevisions())); PdfPKCS7 pkcs7 = fields.verifySignature(name); Signature signature = display(pkcs7); signature.coversWholeDocument = fields.signatureCoversWholeDocument(name); signature.revision = fields.getRevision(name); return signature; } private Signature display(PdfPKCS7 pkcs7) { Signature signature = new Signature(); // digitalsignatures20130304.pdf : Code sample 5.3 to.println("Signature metadata:"); { SignatureMetadata metadata = new SignatureMetadata(); to.indentMore(); // Only name may be null. // The values are set in {@link PdfPKCS7#verifySignature}. { // name String name = pkcs7.getSignName(); // May be null metadata.name = name; if (name == null) { to.println("Name is not set."); } else { to.println(String.format("Name: %s", name)); } } // TODO?: Print "N/A" if the value is an empty string // TODO?: Determine whether the value is set in the signature to.println(String.format("Reason: %s", pkcs7.getReason())); metadata.reason = pkcs7.getReason(); to.println(String.format("Location: %s", pkcs7.getLocation())); metadata.location = pkcs7.getLocation(); { // Date Date date = pkcs7.getSignDate().getTime(); to.println(String.format("Date and time: %s", date)); metadata.date = date.toString(); } to.indentLess(); signature.metadata = metadata; } { // Certificate chain to.indentMore("Certificate chain:"); Certificate[] certificates = pkcs7.getSignCertificateChain(); to.println(String.format("Number of certificates: %d", certificates.length)); int i = 0; List<CertificateResult> certificatesResult = new ArrayList<>(); for (Certificate certificate : certificates) { to.indentMore(String.format("Certificate %d%s:", i, (i == 0 ? " (the signing certificate)" : ""))); CertificateResult certRes; String type = certificate.getType(); to.println(String.format("Type: %s", type)); // http://docs.oracle.com/javase/1.5.0/docs/guide/security/CryptoSpec.html#AppA if ("X.509".equals(type)) { X509Certificate certificateX509 = (X509Certificate) certificate; certRes = showCertInfo(certificateX509); } else { certRes = new CertificateResult(); } certRes.type = type; to.indentLess(); certificatesResult.add(certRes); ++i; } signature.certificates = certificatesResult; to.indentLess(); } return signature; } private CertificateResult showCertInfo(X509Certificate cert) { CertificateResult certRes = new CertificateResult(); { // Self-signed? X500Principal principalSubject = cert.getSubjectX500Principal(); X500Principal principalIssuer = cert.getIssuerX500Principal(); boolean selfSigned = principalSubject.equals(principalIssuer); to.println(String.format("Self-signed: %s", (selfSigned ? "Yes" : "No"))); certRes.selfSigned = selfSigned; } // Note: More attributes may be available by more direct processing of `cert` // than by using `CertificateInfo.get*Fields`. { // Subject to.indentMore("Subject:"); certRes.subject = showX500Name(CertificateInfo.getSubjectFields(cert)); to.indentLess(); } { // Issuer to.indentMore("Issuer:"); certRes.issuer = showX500Name(CertificateInfo.getIssuerFields(cert)); to.indentLess(); } return certRes; } // The desired order of DN attributes by their type private static final MapSorter<String> dnTypeSorter = new PreferenceListComparator( new String[] { "CN", "E", "OU", "O", "STREET", "L", "ST", "C" }); /** * The returned map is ordered by keys by {@link dnTypeSorter}. */ private SortedMap<String, List<String>> showX500Name(X500Name name) { Map<String, ArrayList<String>> fields = name.getFields(); // Convert to Map<String, List<String>> Map<String, List<String>> fieldsLists = new LinkedHashMap<>(); fieldsLists.putAll(fields); // Sort by dnTypeSorter SortedMap<String, List<String>> fieldsSorted = dnTypeSorter.sort(fieldsLists); // Print for (Entry<String, List<String>> field : fieldsSorted.entrySet()) { String type = field.getKey(); type = niceX500AttributeType(type); List<String> values = field.getValue(); String valuesString = StringUtils.join(values, ", "); to.println(String.format("%s: %s", type, valuesString)); } return fieldsSorted; } private static final Map<String, String> attributeTypeAliases = new HashMap<>(); static { // Alias sources: // http://www.ietf.org/rfc/rfc2253.txt : Section 2.3 // http://api.itextpdf.com/itext/com/itextpdf/text/pdf/security/CertificateInfo.X500Name.html attributeTypeAliases.put("CN", "Common name"); attributeTypeAliases.put("L", "Locality"); attributeTypeAliases.put("ST", "State or province"); attributeTypeAliases.put("O", "Organization"); attributeTypeAliases.put("OU", "Organizational unit"); attributeTypeAliases.put("C", "Country code"); attributeTypeAliases.put("STREET", "Street address"); attributeTypeAliases.put("DC", "Domain component"); attributeTypeAliases.put("UID", "User ID"); attributeTypeAliases.put("E", "Email address"); attributeTypeAliases.put("SN", "Serial number"); attributeTypeAliases.put("T", "Title"); } private static String niceX500AttributeType(String type) { String nice = attributeTypeAliases.get(type); if (nice != null) { type = nice; } else { return String.format("<%s>", type); } return type; } private static OperationInspect instance = null; public static OperationInspect getInstance() { if (instance == null) { instance = new OperationInspect(); } return instance; } private OperationInspect() { // Singleton } }