pdfbox-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From til...@apache.org
Subject svn commit: r1818668 - /pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/
Date Tue, 19 Dec 2017 13:41:50 GMT
Author: tilman
Date: Tue Dec 19 13:41:50 2017
New Revision: 1818668

URL: http://svn.apache.org/viewvc?rev=1818668&view=rev
Log:
PDFBOX-3984: Add validation data of signer to document, by Alexis Suter

Added:
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/AddValidationInformation.java   (with props)
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationCollector.java   (with props)
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationHelper.java   (with props)
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertificateProccessingException.java   (with props)
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CrlHelper.java   (with props)
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java   (with props)
    pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/RevokedCertificateException.java   (with props)

Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/AddValidationInformation.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/AddValidationInformation.java?rev=1818668&view=auto
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/AddValidationInformation.java (added)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/AddValidationInformation.java Tue Dec 19 13:41:50 2017
@@ -0,0 +1,449 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.examples.signature.validation;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSDictionary;
+import org.apache.pdfbox.cos.COSInteger;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSStream;
+import org.apache.pdfbox.cos.COSUpdateInfo;
+import org.apache.pdfbox.examples.signature.validation.CertInformationCollector.CertSignatureInformation;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+
+/**
+ * An example for adding Validation Information to a signed PDF, inspired by ETSI TS 102 778-4
+ * V1.1.2 (2009-12), Part 4: PAdES Long Term - PAdES-LTV Profile. This procedure appends the
+ * Validation Information of the last signature (more precise its signer(s)) to a copy of the
+ * document. The signature and the signed data will not be touched and stay valid.
+ *
+ * @author Alexis Suter
+ */
+public class AddValidationInformation
+{
+    private static final Log LOG = LogFactory.getLog(AddValidationInformation.class);
+
+    private CertInformationCollector certInformationHelper;
+    private COSArray correspondingOCSPs;
+    private COSArray correspondingCRLs;
+    private COSDictionary vriBase;
+    private COSArray ocsps;
+    private COSArray crls;
+    private COSArray certs;
+    private PDDocument document;
+    private final Set<BigInteger> foundRevocationInformation = new HashSet<>();
+
+    /**
+     * Signs the given PDF file.
+     * 
+     * @param inFile input PDF file
+     * @param outFile output PDF file
+     * @throws IOException if the input file could not be read
+     */
+    public void validateSignature(File inFile, File outFile) throws IOException
+    {
+        if (inFile == null || !inFile.exists())
+        {
+            throw new FileNotFoundException("Document for signing does not exist");
+        }
+
+        try (PDDocument doc = PDDocument.load(inFile);
+                FileOutputStream fos = new FileOutputStream(outFile))
+        {
+            document = doc;
+            doValidation(inFile.getAbsolutePath(), fos);
+        }
+    }
+
+    /**
+     * Fetches certificate information from the last signature of the document and appends a DSS with the validation
+     * information to the document.
+     * 
+     * @param document containing the Signature
+     * @param filename in file to extract signature
+     * @param output where to write the changed document
+     * @throws IOException
+     */
+    private void doValidation(String filename, OutputStream output) throws IOException
+    {
+        certInformationHelper = new CertInformationCollector();
+        CertSignatureInformation certInfo;
+        try
+        {
+            certInfo = certInformationHelper.getLastCertInfo(document, filename);
+        }
+        catch (CertificateProccessingException e)
+        {
+            throw new IOException("An Error occurred processing the Signature", e);
+        }
+        if (certInfo == null)
+        {
+            throw new IOException(
+                    "No Certificate information or signature found in the given document");
+        }
+
+        PDDocumentCatalog docCatalog = document.getDocumentCatalog();
+        COSDictionary catalog = docCatalog.getCOSObject();
+        catalog.setNeedToBeUpdated(true);
+
+        COSDictionary dss = getOrCreateDictionaryEntry(COSDictionary.class, catalog, "DSS");
+
+        addExtensions(docCatalog);
+
+        vriBase = getOrCreateDictionaryEntry(COSDictionary.class, dss, "VRI");
+
+        ocsps = getOrCreateDictionaryEntry(COSArray.class, dss, "OCSPs");
+
+        crls = getOrCreateDictionaryEntry(COSArray.class, dss, "CRLs");
+
+        certs = getOrCreateDictionaryEntry(COSArray.class, dss, "Certs");
+
+        addRevocationData(certInfo);
+
+        addAllCertsToCertArray();
+
+        // write incremental
+        document.saveIncremental(output);
+    }
+
+    /**
+     * Gets or creates a dictionary entry. If existing checks for the type and sets need to be
+     * updated.
+     *
+     * @param clazz the class of the dictionary entry, must implement COSUpdateInfo
+     * @param parent where to find the element
+     * @param name of the element
+     * @return a Element of given class, new or existing
+     * @throws IOException when the type of the element is wrong
+     */
+    private static <T extends COSBase & COSUpdateInfo> T getOrCreateDictionaryEntry(Class<T> clazz,
+            COSDictionary parent, String name) throws IOException
+    {
+        T result;
+        COSBase element = parent.getDictionaryObject(name);
+        if (element != null && clazz.isInstance(element))
+        {
+            result = clazz.cast(element);
+            result.setNeedToBeUpdated(true);
+        }
+        else if (element != null)
+        {
+            throw new IOException("Element " + name + " from dictionary is not of type "
+                    + clazz.getCanonicalName());
+        }
+        else
+        {
+            try
+            {
+                result = clazz.newInstance();
+            }
+            catch (InstantiationException | IllegalAccessException e)
+            {
+                return null;
+            }
+            result.setDirect(false);
+            parent.setItem(COSName.getPDFName(name), result);
+        }
+        return result;
+    }
+
+    /**
+     * Fetches and adds revocation information based on the certInfo to the DSS.
+     * 
+     * @param certInfo Certificate information from CertInformationHelper containing certificate chains.
+     * @throws IOException
+     */
+    private void addRevocationData(CertSignatureInformation certInfo) throws IOException
+    {
+        COSDictionary vri = new COSDictionary();
+        vriBase.setItem(COSName.getPDFName(certInfo.getSignatureHash()), vri);
+
+        correspondingOCSPs = new COSArray();
+        correspondingCRLs = new COSArray();
+
+        addRevocationDataRecursive(certInfo);
+
+        if (correspondingOCSPs.size() > 0)
+        {
+            vri.setItem(COSName.getPDFName("OCSP"), correspondingOCSPs);
+        }
+        if (correspondingCRLs.size() > 0)
+        {
+            vri.setItem(COSName.getPDFName("CRL"), correspondingCRLs);
+        }
+
+        if (certInfo.getTsaCerts() != null)
+        {
+            // Don't add RevocationInfo from tsa to VRI's
+            correspondingOCSPs = null;
+            correspondingCRLs = null;
+            addRevocationDataRecursive(certInfo.getTsaCerts());
+        }
+    }
+
+    /**
+     * Tries to get Revocation Data (first OCSP, else CRL) from the given Certificate Chain.
+     * 
+     * @param certInfo from which to fetch revocation data. Will work recursively through its chains.
+     * @throws IOException when failed to fetch an revocation data.
+     */
+    private void addRevocationDataRecursive(CertSignatureInformation certInfo) throws IOException
+    {
+        if (certInfo.isSelfSigned())
+        {
+            return;
+        }
+        // To avoid getting same revocation information twice.
+        boolean isRevocationInfoFound = foundRevocationInformation
+                .contains(certInfo.getCertificate().getSerialNumber());
+        if (!isRevocationInfoFound)
+        {
+            if (certInfo.getOcspUrl() != null && certInfo.getIssuerCertificate() != null)
+            {
+                isRevocationInfoFound = fetchOcspData(certInfo);
+            }
+            if (!isRevocationInfoFound && certInfo.getCrlUrl() != null)
+            {
+                fetchCrlData(certInfo);
+                isRevocationInfoFound = true;
+            }
+
+            if (!isRevocationInfoFound)
+            {
+                throw new IOException("Could not fetch Revocation Info for Cert: "
+                        + certInfo.getCertificate().getSubjectDN());
+            }
+        }
+
+        if (certInfo.getAlternativeCertChain() != null)
+        {
+            addRevocationDataRecursive(certInfo.getAlternativeCertChain());
+        }
+
+        if (certInfo.getCertChain() != null && certInfo.getCertChain().getCertificate() != null)
+        {
+            addRevocationDataRecursive(certInfo.getCertChain());
+        }
+    }
+
+    /**
+     * Tries to fetch and add OCSP Data to its containers.
+     * 
+     * @param certInfo the certificate info, for it to check OCSP data.
+     * @return true when the OCSP data has successfully been fetched and added
+     * @throws IOException when Certificate is revoked.
+     */
+    private boolean fetchOcspData(CertSignatureInformation certInfo) throws IOException
+    {
+        try
+        {
+            addOcspData(certInfo);
+            return true;
+        }
+        catch (OCSPException | CertificateProccessingException | IOException e)
+        {
+            LOG.warn("Failed fetching Ocsp", e);
+            return false;
+        }
+        catch (RevokedCertificateException e)
+        {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Tries to fetch and add CRL Data to its containers.
+     * 
+     * @param certInfo the certificate info, for it to check CRL data.
+     * @throws IOException when failed to fetch, because no validation data could be fetched for data.
+     */
+    private void fetchCrlData(CertSignatureInformation certInfo) throws IOException
+    {
+        try
+        {
+            addCrlRevocationInfo(certInfo);
+        }
+        catch (CRLException | IOException | RevokedCertificateException e)
+        {
+            LOG.warn("Failed fetching CRL", e);
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Fetches and adds OCSP data to storage for the given Certificate.
+     * 
+     * @param certInfo the certificate info, for it to check OCSP data.
+     * @throws IOException
+     * @throws OCSPException
+     * @throws CertificateProccessingException
+     * @throws RevokedCertificateException
+     */
+    private void addOcspData(CertSignatureInformation certInfo) throws IOException, OCSPException,
+            CertificateProccessingException, RevokedCertificateException
+    {
+        OcspHelper ocspHelper = new OcspHelper(certInfo.getCertificate(),
+                certInfo.getIssuerCertificate(), certInfo.getOcspUrl());
+
+        OCSPResp ocspResp = ocspHelper.getResponseOcsp();
+        BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResp.getResponseObject();
+        certInformationHelper.addAllCertsFromHolders(basicResponse.getCerts());
+
+        byte[] ocspData = ocspResp.getEncoded();
+
+        COSStream ocspStream = writeDataToStream(ocspData);
+        ocsps.add(ocspStream);
+        if (correspondingOCSPs != null)
+        {
+            correspondingOCSPs.add(ocspStream);
+        }
+        foundRevocationInformation.add(certInfo.getCertificate().getSerialNumber());
+    }
+
+    /**
+     * Fetches and adds CRL data to storage for the given Certificate.
+     * 
+     * @param certInfo the certificate info, for it to check CRL data.
+     * @throws CRLException
+     * @throws IOException
+     * @throws RevokedCertificateException
+     */
+    private void addCrlRevocationInfo(CertSignatureInformation certInfo)
+            throws CRLException, IOException, RevokedCertificateException
+    {
+        byte[] crlData = CrlHelper.performCrlRequestAndCheck(certInfo.getCrlUrl(),
+                certInfo.getCertificate());
+        COSStream crlStream = writeDataToStream(crlData);
+        crls.add(crlStream);
+        if (correspondingCRLs != null)
+        {
+            correspondingCRLs.add(crlStream);
+        }
+        foundRevocationInformation.add(certInfo.getCertificate().getSerialNumber());
+    }
+
+    /**
+     * Adds all certs to the certs-array. Make sure, all certificates are inside the certificateStore of
+     * certInformationHelper
+     * 
+     * @throws IOException
+     */
+    private void addAllCertsToCertArray() throws IOException
+    {
+        try
+        {
+            for (X509Certificate cert : certInformationHelper.getCertificateStore().values())
+            {
+                COSStream stream = writeDataToStream(cert.getEncoded());
+                certs.add(stream);
+            }
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IOException(e);
+        }
+    }
+
+    /**
+     * Creates a FlateDecoded <code>COSStream</code> element with the given data.
+     * 
+     * @param data to write into the element
+     * @return COSStream Element, that can be added to the document
+     * @throws IOException
+     */
+    private COSStream writeDataToStream(byte[] data) throws IOException
+    {
+        COSStream stream = document.getDocument().createCOSStream();
+        COSArray filters = new COSArray();
+        filters.add(COSName.FLATE_DECODE);
+
+        try (OutputStream os = stream.createOutputStream(filters))
+        {
+            os.write(data);
+        }
+        return stream;
+    }
+
+    /**
+     * Adds Extensions to the document catalog. So that the use of DSS is identified. Described in PAdES Part 4, Chapter
+     * 4.4.
+     * 
+     * @param catalog to add Extensions into
+     */
+    private void addExtensions(PDDocumentCatalog catalog)
+    {
+        COSDictionary dssExtensions = new COSDictionary();
+        dssExtensions.setDirect(true);
+        catalog.getCOSObject().setItem(COSName.getPDFName("Extensions"), dssExtensions);
+
+        COSDictionary adbeExtension = new COSDictionary();
+        adbeExtension.setDirect(true);
+        dssExtensions.setItem(COSName.getPDFName("ADBE"), adbeExtension);
+
+        adbeExtension.setItem(COSName.getPDFName("BaseVersion"), COSName.getPDFName("1.7"));
+        adbeExtension.setItem(COSName.getPDFName("ExtensionLevel"), COSInteger.get(5));
+
+        catalog.getCOSObject().setItem(COSName.getPDFName("Version"), COSName.getPDFName("1.7"));
+    }
+
+    public static void main(String[] args) throws IOException, GeneralSecurityException
+    {
+        args = new String[]{"C:\\Users\\Tilman Hausherr\\Documents\\Java\\PDFBoxPageImageExtraction\\1_only_signed.pdf"};
+        if (args.length != 1)
+        {
+            usage();
+            System.exit(1);
+        }
+
+        // add ocspInformation
+        AddValidationInformation addOcspInformation = new AddValidationInformation();
+
+        File inFile = new File(args[0]);
+        String name = inFile.getName();
+        String substring = name.substring(0, name.lastIndexOf('.'));
+
+        File outFile = new File(inFile.getParent(), substring + "_ocsp.pdf");
+        addOcspInformation.validateSignature(inFile, outFile);
+    }
+
+    private static void usage()
+    {
+        System.err.println("usage: java " + AddValidationInformation.class.getName() + " "
+                + "<pdf_to_add_ocsp>\n");
+    }
+}

Propchange: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/AddValidationInformation.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationCollector.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationCollector.java?rev=1818668&view=auto
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationCollector.java (added)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationCollector.java Tue Dec 19 13:41:50 2017
@@ -0,0 +1,497 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.pdfbox.examples.signature.validation;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.net.URL;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.cms.Attribute;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.tsp.TSPException;
+import org.bouncycastle.tsp.TimeStampToken;
+import org.bouncycastle.util.Store;
+
+/**
+ * This Class helps to extract Data/Information from a signature. The information is held in
+ * CertSignatureInformation. Some information is needed for validation processing of the
+ * participating certificates.
+ *
+ * @author Alexis Suter
+ *
+ */
+public class CertInformationCollector
+{
+    private static final Log LOG = LogFactory.getLog(CertInformationCollector.class);
+
+    // As described in https://tools.ietf.org/html/rfc3280.html#section-4.2.2.1
+    private static final String ID_PE_AUTHORITYINFOACCESS = "1.3.6.1.5.5.7.1.1";
+
+    // As described in https://tools.ietf.org/html/rfc3280.html#section-4.2.1.14
+    private static final String ID_CE_CRLDISTRIBUTIONPOINTS = "2.5.29.31";
+
+    private static final int MAX_CERTIFICATE_CHAIN_DEPTH = 5;
+
+    private final Map<BigInteger, X509Certificate> certificateStore = new HashMap<>();
+
+    private final JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter();
+
+    private CertSignatureInformation rootCertInfo;
+
+    /**
+     * Gets the Certificate Information of the last Signature.
+     * 
+     * @param document to get the Signature from
+     * @param fileName of the document.
+     * @return the CertSignatureInformation containing all certificate information
+     * @throws CertificateProccessingException when there is an error processing the certificates
+     * @throws IOException on a data processing error
+     */
+    public CertSignatureInformation getLastCertInfo(PDDocument document, String fileName)
+            throws CertificateProccessingException, IOException
+    {
+        PDSignature signature = getLastRelevantSignature(document);
+        if (signature != null)
+        {
+            try (FileInputStream documentInput = new FileInputStream(fileName))
+            {
+                byte[] docBytes = IOUtils.toByteArray(documentInput);
+                byte[] signatureContent = signature.getContents(docBytes);
+                return getCertInfo(signatureContent);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the last relevant signature in the document
+     * 
+     * @param document to get its last Signature
+     * @return last signature or null when none found
+     * @throws IOException
+     */
+    private PDSignature getLastRelevantSignature(PDDocument document) throws IOException
+    {
+        SortedMap<Integer, PDSignature> sortedMap = new TreeMap<>();
+        for (PDSignature signature : document.getSignatureDictionaries())
+        {
+            int sigOffset = signature.getByteRange()[1];
+            sortedMap.put(sigOffset, signature);
+        }
+        if (sortedMap.size() > 0)
+        {
+            PDSignature lastSignature = sortedMap.get(sortedMap.lastKey());
+            COSBase type = lastSignature.getCOSObject().getItem(COSName.TYPE);
+            if (type.equals(COSName.SIG) || type.equals(COSName.DOC_TIME_STAMP))
+            {
+                return lastSignature;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Processes one signature and its including certificates.
+     *
+     * @param signatureContent the byte[]-Content of the signature
+     * @return the CertSignatureInformation for this signature
+     * @throws IOException
+     * @throws CertificateProccessingException
+     */
+    private CertSignatureInformation getCertInfo(byte[] signatureContent)
+            throws CertificateProccessingException, IOException
+    {
+        rootCertInfo = new CertSignatureInformation();
+
+        rootCertInfo.signatureHash = CertInformationHelper.getSha1Hash(signatureContent);
+
+        try
+        {
+            CMSSignedData signedData = new CMSSignedData(signatureContent);
+            Store<X509CertificateHolder> certificatesStore = signedData.getCertificates();
+
+            SignerInformation signerInformation = processSignerStore(certificatesStore, signedData,
+                    rootCertInfo);
+
+            addTimestampCerts(signerInformation);
+        }
+        catch (CMSException e)
+        {
+            LOG.error("Error occurred getting Certificate Information from Signature", e);
+            throw new CertificateProccessingException(e);
+        }
+        return rootCertInfo;
+    }
+
+    /**
+     * Processes an embedded signed timestamp, that has been placed into a signature. The
+     * certificates and its chain(s) will be processed the same way as the signature itself.
+     *
+     * @param signerInformation of the signature, to get unsigned attributes from it.
+     * @throws IOException
+     * @throws CertificateProccessingException
+     */
+    private void addTimestampCerts(SignerInformation signerInformation)
+            throws IOException, CertificateProccessingException
+    {
+        AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
+        if (unsignedAttributes == null)
+        {
+            return;
+        }
+        Attribute tsAttribute = signerInformation.getUnsignedAttributes()
+                .get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
+        if (tsAttribute.getAttrValues() instanceof DERSet)
+        {
+            DERSet tsSet = (DERSet) tsAttribute.getAttrValues();
+            tsSet.getEncoded("DER");
+            DERSequence tsSeq = (DERSequence) tsSet.getObjectAt(0);
+
+            try
+            {
+                TimeStampToken tsToken = new TimeStampToken(
+                        new CMSSignedData(tsSeq.getEncoded("DER")));
+
+                rootCertInfo.tsaCerts = new CertSignatureInformation();
+
+                @SuppressWarnings("unchecked")
+                Store<X509CertificateHolder> certificatesStore = tsToken.getCertificates();
+
+                processSignerStore(certificatesStore, tsToken.toCMSSignedData(),
+                        rootCertInfo.tsaCerts);
+            }
+            catch (TSPException | CMSException e)
+            {
+                throw new IOException("Error parsing timestamp token", e);
+            }
+        }
+    }
+
+    /**
+     * Processes a signer store and goes through the signers certificate-chain. Adds the found data
+     * to the certInfo. Handles only the first signer, although multiple would be possible, but is
+     * not yet practicable.
+     *
+     * @param certificatesStore To get the certificate information from. Certificates will be saved
+     * in the certificateStore.
+     * @param signedData to get SignerInformation off
+     * @param certInfo where to add certificate information
+     * @return Signer Information of the processed certificate Store for further usage.
+     * @throws IOException on data-processing error
+     * @throws CertificateProccessingException on a specific error with a certificate
+     */
+    private SignerInformation processSignerStore(Store<X509CertificateHolder> certificatesStore,
+            CMSSignedData signedData, CertSignatureInformation certInfo)
+            throws IOException, CertificateProccessingException
+    {
+        Collection<SignerInformation> signers = signedData.getSignerInfos().getSigners();
+        SignerInformation signerInformation = signers.iterator().next();
+
+        Collection<X509CertificateHolder> matches = certificatesStore
+                .getMatches(signerInformation.getSID());
+
+        X509Certificate certificate = getCertFromHolder(matches.iterator().next());
+
+        Collection<X509CertificateHolder> allCerts = certificatesStore.getMatches(null);
+        addAllCerts(allCerts);
+        traverseChain(certificate, certInfo, MAX_CERTIFICATE_CHAIN_DEPTH);
+        return signerInformation;
+    }
+
+    /**
+     * Traverse through the Cert-Chain of the given Certificate and add it to the CertInfo
+     * recursively.
+     *
+     * @param certificate Actual Certificate to be processed
+     * @param certInfo where to add the Certificate (and chain) information
+     * @param maxDepth Max depth from this point to go through CertChain (could be infinite)
+     * @throws IOException on data-processing error
+     * @throws CertificateProccessingException on a specific error with a certificate
+     */
+    private void traverseChain(X509Certificate certificate, CertSignatureInformation certInfo,
+            int maxDepth) throws IOException, CertificateProccessingException
+    {
+        certInfo.certificate = certificate;
+
+        // Certificate Authority Information Access
+        byte[] authorityExtensionValue = certificate.getExtensionValue(ID_PE_AUTHORITYINFOACCESS);
+        if (authorityExtensionValue != null)
+        {
+            CertInformationHelper.getAuthorityInfoExtensionValue(authorityExtensionValue, certInfo);
+        }
+
+        if (certInfo.issuerUrl != null)
+        {
+            getAlternativeIssuerCertificate(certInfo, maxDepth);
+        }
+
+        byte[] crlExtensionValue = certificate.getExtensionValue(ID_CE_CRLDISTRIBUTIONPOINTS);
+        if (crlExtensionValue != null)
+        {
+            certInfo.crlUrl = CertInformationHelper.getCrlUrlFromExtensionValue(crlExtensionValue);
+        }
+
+        if (CertInformationHelper.isSelfSigned(certificate))
+        {
+            certInfo.isSelfSigned = true;
+        }
+        if (maxDepth <= 0 || certInfo.isSelfSigned)
+        {
+            return;
+        }
+
+        for (X509Certificate issuer : certificateStore.values())
+        {
+            if (CertInformationHelper.verify(certificate, issuer.getPublicKey()))
+            {
+                LOG.info("Found the right Issuer Cert! for Cert: " + certificate.getSubjectDN()
+                        + "\n" + issuer.getSubjectDN());
+                certInfo.issuerCertificate = issuer;
+                certInfo.certChain = new CertSignatureInformation();
+                traverseChain(issuer, certInfo.certChain, --maxDepth);
+                break;
+            }
+        }
+        if (certInfo.issuerCertificate == null)
+        {
+            throw new IOException(
+                    "No Issuer Certificate found for Cert: " + certificate.getSubjectDN());
+        }
+    }
+
+    /**
+     * Get alternative certificate chain, from the Authority Information (a url). If the chain is
+     * not included in the signature, this is the main chain. Otherwise there might be a second
+     * chain. Exceptions which happen on this chain will be logged and ignored, because the cert
+     * might not be available at the time or other reasons.
+     *
+     * @param certInfo base Certificate Information, on which to put the alternative Certificate
+     * @param maxDepth Maximum depth to dig through the chain from here on.
+     * @throws CertificateProccessingException on a specific error with a certificate
+     */
+    private void getAlternativeIssuerCertificate(CertSignatureInformation certInfo, int maxDepth)
+            throws CertificateProccessingException
+    {
+        System.out.println("Get Certificate from: " + certInfo.issuerUrl);
+        try
+        {
+            URL certUrl = new URL(certInfo.issuerUrl);
+            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+            try (InputStream in = certUrl.openStream())
+            {
+                X509Certificate altIssuerCert = (X509Certificate) certFactory
+                        .generateCertificate(in);
+                addCertToCertStore(altIssuerCert);
+
+                certInfo.alternativeCertChain = new CertSignatureInformation();
+                traverseChain(altIssuerCert, certInfo.alternativeCertChain, maxDepth - 1);
+            }
+        }
+        catch (IOException | CertificateException e)
+        {
+            LOG.error("Error getting additional Certificate from " + certInfo.issuerUrl, e);
+        }
+    }
+
+    /**
+     * Adds the given Certificate to the certificateStore, if not yet containing.
+     * 
+     * @param certificate to add to the certificateStore
+     */
+    private void addCertToCertStore(X509Certificate certificate)
+    {
+        if (!certificateStore.containsKey(certificate.getSerialNumber()))
+        {
+            certificateStore.put(certificate.getSerialNumber(), certificate);
+        }
+    }
+
+    /**
+     * Gets the X509Certificate out of the X509CertificateHolder
+     * 
+     * @param certificateHolder to get the certificate from
+     * @return a X509Certificate or <code>null</code> when there was an Error with the Certificate
+     * @throws CertificateProccessingException on failed conversion from X509CertificateHolder to X509Certificate
+     */
+    private X509Certificate getCertFromHolder(X509CertificateHolder certificateHolder)
+            throws CertificateProccessingException
+    {
+        if (!certificateStore.containsKey(certificateHolder.getSerialNumber()))
+        {
+            try
+            {
+                X509Certificate certificate = certConverter.getCertificate(certificateHolder);
+                certificateStore.put(certificate.getSerialNumber(), certificate);
+                return certificate;
+            }
+            catch (CertificateException e)
+            {
+                LOG.error("Certificate Exception getting Certificate from certHolder.", e);
+                throw new CertificateProccessingException(e);
+            }
+        }
+        else
+        {
+            return certificateStore.get(certificateHolder.getSerialNumber());
+        }
+    }
+
+    /**
+     * Adds multiple Certificates out of a Collection of X509CertificateHolder into the
+     * Certificate-Store.
+     *
+     * @param certHolders Collection of X509CertificateHolder
+     */
+    private void addAllCerts(Collection<X509CertificateHolder> certHolders)
+    {
+        for (X509CertificateHolder certificateHolder : certHolders)
+        {
+            try
+            {
+                getCertFromHolder(certificateHolder);
+            }
+            catch (CertificateProccessingException e)
+            {
+                LOG.warn("Certificate Exception getting Certificate from certHolder.", e);
+            }
+        }
+    }
+
+    /**
+     * Gets a list of X509Certificate out of an array of X509CertificateHolder. The certificates
+     * will be added to the certificateStore.
+     *
+     * @param certHolders Array of X509CertificateHolder
+     * @throws CertificateProccessingException when one of the Certificates could not be parsed.
+     */
+    public void addAllCertsFromHolders(X509CertificateHolder[] certHolders)
+            throws CertificateProccessingException
+    {
+        for (X509CertificateHolder certHolder : certHolders)
+        {
+            getCertFromHolder(certHolder);
+        }
+    }
+
+    /**
+     * Get the certificate store of all processed certificates until now.
+     * 
+     * @return a map of serial numbers to certificates.
+     */
+    public Map<BigInteger, X509Certificate> getCertificateStore()
+    {
+        return certificateStore;
+    }
+
+    /**
+     * Data class to hold Signature, Certificate (and its chain(s)) and revocation Information
+     */
+    public class CertSignatureInformation
+    {
+        private X509Certificate certificate;
+        private String signatureHash;
+        private boolean isSelfSigned = false;
+        private String ocspUrl;
+        private String crlUrl;
+        private String issuerUrl;
+        private X509Certificate issuerCertificate;
+        private CertSignatureInformation certChain;
+        private CertSignatureInformation tsaCerts;
+        private CertSignatureInformation alternativeCertChain;
+
+        public String getOcspUrl()
+        {
+            return ocspUrl;
+        }
+
+        public void setOcspUrl(String ocspUrl)
+        {
+            this.ocspUrl = ocspUrl;
+        }
+
+        public void setIssuerUrl(String issuerUrl)
+        {
+            this.issuerUrl = issuerUrl;
+        }
+
+        public String getCrlUrl()
+        {
+            return crlUrl;
+        }
+
+        public X509Certificate getCertificate()
+        {
+            return certificate;
+        }
+
+        public boolean isSelfSigned()
+        {
+            return isSelfSigned;
+        }
+
+        public X509Certificate getIssuerCertificate()
+        {
+            return issuerCertificate;
+        }
+
+        public String getSignatureHash()
+        {
+            return signatureHash;
+        }
+
+        public CertSignatureInformation getCertChain()
+        {
+            return certChain;
+        }
+
+        public CertSignatureInformation getTsaCerts()
+        {
+            return tsaCerts;
+        }
+
+        public CertSignatureInformation getAlternativeCertChain()
+        {
+            return alternativeCertChain;
+        }
+    }
+}

Propchange: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationCollector.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationHelper.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationHelper.java?rev=1818668&view=auto
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationHelper.java (added)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationHelper.java Tue Dec 19 13:41:50 2017
@@ -0,0 +1,178 @@
+package org.apache.pdfbox.examples.signature.validation;
+
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Enumeration;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.examples.signature.validation.CertInformationCollector.CertSignatureInformation;
+import org.apache.pdfbox.util.Hex;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.DLSequence;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.x509.extension.X509ExtensionUtil;
+
+public class CertInformationHelper
+{
+    private static final Log LOG = LogFactory.getLog(CertInformationHelper.class);
+
+    private CertInformationHelper()
+    {
+    }
+
+    /**
+     * Gets the SHA-1-Hash has of given byte[]-content.
+     * 
+     * @param content to be hashed
+     * @return SHA-1 hash String
+     */
+    protected static String getSha1Hash(byte[] content)
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            return Hex.getString(md.digest(content));
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            LOG.error("No SHA-1 Algorithm found", e);
+        }
+        return null;
+    }
+
+    /**
+     * Checks whether the given certificate is self-signed (root).
+     * 
+     * @param cert to be checked
+     * @return true when it is a self-signed certificate
+     * @throws CertificateProccessingException containing the cause, on multiple exception with the given data
+     */
+    public static boolean isSelfSigned(X509Certificate cert) throws CertificateProccessingException
+    {
+        return verify(cert, cert.getPublicKey());
+    }
+
+    /**
+     * Verifies whether the certificate is signed by the given public key. Can be done to check
+     * signature Chain. Idea & code is from user Julius Musseau at:
+     * https://stackoverflow.com/a/10822177/2497581
+     *
+     * @param cert Child certificate to check
+     * @param key Fathers public key to check
+     * @return true when the certificate is signed by the public key
+     * @throws CertificateProccessingException containing the cause, on multiple exception with the
+     * given data
+     */
+    public static boolean verify(X509Certificate cert, PublicKey key)
+            throws CertificateProccessingException
+    {
+        try
+        {
+            String sigAlg = cert.getSigAlgName();
+            String keyAlg = key.getAlgorithm();
+            sigAlg = sigAlg != null ? sigAlg.trim().toUpperCase() : "";
+            keyAlg = keyAlg != null ? keyAlg.trim().toUpperCase() : "";
+            if (keyAlg.length() >= 2 && sigAlg.endsWith(keyAlg))
+            {
+                try
+                {
+                    cert.verify(key);
+                    return true;
+                }
+                catch (SignatureException se)
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                return false;
+            }
+        }
+        catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException
+                | NoSuchProviderException e)
+        {
+            throw new CertificateProccessingException(e);
+        }
+    }
+
+    /**
+     * Extracts authority information access extension values from the given data. The Data
+     * structure has to be implemented as described in RFC 2459, 4.2.2.1.
+     *
+     * @param extensionValue byte[] of the extension value.
+     * @param certInfo where to put the found values
+     * @throws IOException when there is a problem with the extensionValue
+     */
+    protected static void getAuthorityInfoExtensionValue(byte[] extensionValue,
+            CertSignatureInformation certInfo) throws IOException
+    {
+        ASN1Sequence asn1Seq = (ASN1Sequence) X509ExtensionUtil.fromExtensionValue(extensionValue);
+        Enumeration<?> objects = asn1Seq.getObjects();
+        while (objects.hasMoreElements())
+        {
+            // AccessDescription
+            ASN1Sequence obj = (ASN1Sequence) objects.nextElement();
+            ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) obj.getObjectAt(0);
+            // accessLocation
+            DERTaggedObject location = (DERTaggedObject) obj.getObjectAt(1);
+
+            if (oid.equals(X509ObjectIdentifiers.id_ad_ocsp)
+                    && location.getTagNo() == GeneralName.uniformResourceIdentifier)
+            {
+                DEROctetString url = (DEROctetString) location.getObject();
+                certInfo.setOcspUrl(new String(url.getOctets()));
+            }
+            else if (oid.equals(X509ObjectIdentifiers.id_ad_caIssuers))
+            {
+                DEROctetString uri = (DEROctetString) location.getObject();
+                certInfo.setIssuerUrl(new String(uri.getOctets()));
+            }
+        }
+    }
+
+    /**
+     * Gets the first CRL Url from given extension value. Structure has to be build as in 4.2.1.14
+     * CRL Distribution Points of RFC 2459.
+     *
+     * @param extensionValue to get the extension value from
+     * @return first CRL- URL or null
+     * @throws IOException when there is a problem with the extensionValue
+     */
+    protected static String getCrlUrlFromExtensionValue(byte[] extensionValue) throws IOException
+    {
+        ASN1Sequence asn1Seq = (ASN1Sequence) X509ExtensionUtil.fromExtensionValue(extensionValue);
+        Enumeration<?> objects = asn1Seq.getObjects();
+
+        while (objects.hasMoreElements())
+        {
+            DLSequence obj = (DLSequence) objects.nextElement();
+
+            DERTaggedObject derTagged = (DERTaggedObject) obj.getObjectAt(0);
+            derTagged = (DERTaggedObject) derTagged.getObject();
+            derTagged = (DERTaggedObject) derTagged.getObject();
+            DEROctetString uri = (DEROctetString) derTagged.getObject();
+            String url = new String(uri.getOctets());
+            // TODO Check for: DistributionPoint ::= SEQUENCE (see RFC 2459), multiples can be possible.
+
+            // return first http(s)-Url for crl
+            if (url.startsWith("http"))
+            {
+                return url;
+            }
+        }
+        return null;
+    }
+}

Propchange: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertInformationHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertificateProccessingException.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertificateProccessingException.java?rev=1818668&view=auto
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertificateProccessingException.java (added)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertificateProccessingException.java Tue Dec 19 13:41:50 2017
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.examples.signature.validation;
+
+/**
+ * Class to wrap around Certificate Processing exceptions
+ * 
+ * @author Alexis Suter
+ */
+public class CertificateProccessingException extends Exception
+{
+    private static final long serialVersionUID = 814859842830313903L;
+
+    public CertificateProccessingException(Throwable cause)
+    {
+        super(cause);
+    }
+}

Propchange: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CertificateProccessingException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CrlHelper.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CrlHelper.java?rev=1818668&view=auto
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CrlHelper.java (added)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CrlHelper.java Tue Dec 19 13:41:50 2017
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.examples.signature.validation;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.cert.CRLException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
+
+/**
+ * Helper class to get CRL (Certificate revocation list) from given crlUrl and check if Certificate
+ * has been revoked.
+ *
+ * @author Alexis Suter
+ */
+public final class CrlHelper
+{
+    private CrlHelper()
+    {
+    }
+
+    /**
+     * Performs the CRL-Request and checks if the given certificate has been revoked.
+     * 
+     * @param crlUrl to get the CRL from
+     * @param cert to be checked if it is inside the CRL
+     * @return CRL-Response; might be very big depending on the issuer. 
+     * @throws CRLException if an Error occurred getting the CRL, or parsing it.
+     * @throws RevokedCertificateException
+     */
+    public static byte[] performCrlRequestAndCheck(String crlUrl, X509Certificate cert)
+            throws CRLException, RevokedCertificateException
+    {
+        try
+        {
+            URL url = new URL(crlUrl);
+
+            HttpURLConnection con = (HttpURLConnection) url.openConnection();
+            if (con.getResponseCode() != 200)
+            {
+                throw new IOException("Unsuccessful CRL request. Status: " + con.getResponseCode()
+                        + " Url: " + crlUrl);
+            }
+
+            CertificateFactory certFac = new CertificateFactory();
+            X509CRL crl = (X509CRL) certFac.engineGenerateCRL(con.getInputStream());
+            if (crl.isRevoked(cert))
+            {   
+                throw new RevokedCertificateException("The Certificate was found on the CRL and is revoked!");
+            }
+            return crl.getEncoded();
+        }
+        catch (IOException e)
+        {
+            throw new CRLException(e);
+        }
+    }
+
+}

Propchange: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/CrlHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java?rev=1818668&view=auto
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java (added)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java Tue Dec 19 13:41:50 2017
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.examples.signature.validation;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Security;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Random;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.pdfbox.util.Hex;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DLSequence;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.CertificateID;
+import org.bouncycastle.cert.ocsp.CertificateStatus;
+import org.bouncycastle.cert.ocsp.OCSPException;
+import org.bouncycastle.cert.ocsp.OCSPReq;
+import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+import org.bouncycastle.cert.ocsp.RevokedStatus;
+import org.bouncycastle.cert.ocsp.SingleResp;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.operator.DigestCalculator;
+
+/**
+ * Helper Class for OCSP-Operations with bouncy castle.
+ * 
+ * @author Alexis Suter
+ */
+public class OcspHelper
+{
+    private static final Log LOG = LogFactory.getLog(OcspHelper.class);
+    
+    private final X509Certificate issuerCertificate;
+    private final X509Certificate certificateToCheck;
+    private final String ocspUrl;
+    private DEROctetString encodedNonce;
+
+    /**
+     * @param checkCertificate Certificate to be OCSP-Checked
+     * @param issuerCertificate Certificate of the issuer 
+     * @param ocspUrl where to fetch for OCSP
+     */
+    public OcspHelper(X509Certificate checkCertificate, X509Certificate issuerCertificate,
+            String ocspUrl)
+    {
+        this.certificateToCheck = checkCertificate;
+        this.issuerCertificate = issuerCertificate;
+        this.ocspUrl = ocspUrl;
+    }
+
+    /**
+     * Performs and verifies the OCSP-Request
+     *
+     * @return the OCSPResp, when the request was successful, else a corresponding exception will be
+     * thrown.
+     * @throws IOException
+     * @throws OCSPException
+     * @throws RevokedCertificateException
+     */
+    public OCSPResp getResponseOcsp() throws IOException, OCSPException, RevokedCertificateException
+    {
+        OCSPResp ocspResponse = performRequest();
+        verifyOcspResponse(ocspResponse);
+        return ocspResponse;
+    }
+
+    /**
+     * Verifies the status and the response itself (including nonce), but not the signature.
+     * 
+     * @param ocspResponse to be verified
+     * @throws OCSPException
+     * @throws RevokedCertificateException
+     */
+    private void verifyOcspResponse(OCSPResp ocspResponse)
+            throws OCSPException, RevokedCertificateException
+    {
+        verifyRespStatus(ocspResponse);
+
+        BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject();
+        if (basicResponse != null)
+        {
+            Extension nonceExt = basicResponse
+                    .getExtension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce);
+            if (nonceExt != null)
+            {
+                DEROctetString responseNonceString = (DEROctetString) nonceExt.getExtnValue();
+                if (!responseNonceString.equals(encodedNonce))
+                {
+                    throw new OCSPException("Invalid Nonce found in response!");
+                }
+            }
+            else if (encodedNonce != null)
+            {
+                throw new OCSPException("Nonce not found in response!");
+            }
+
+            // TODO Optional check if signature is correct. basicResponse.isSignatureValid(...)
+
+            SingleResp[] responses = basicResponse.getResponses();
+            if (responses.length == 1)
+            {
+                SingleResp resp = responses[0];
+                Object status = resp.getCertStatus();
+
+                if (status instanceof RevokedStatus)
+                {
+                    throw new RevokedCertificateException("OCSP: Certificate is revoked.");
+                }
+                else if(status != CertificateStatus.GOOD)
+                {
+                    throw new OCSPException("OCSP: Status of Cert is unknown");
+                }
+            }
+        }
+    }
+
+    /**
+     * Performs the OCSP-Request, with given data.
+     * 
+     * @return the OCSPResp, that has been fetched from the ocspUrl
+     * @throws IOException
+     * @throws OCSPException
+     */
+    private OCSPResp performRequest() throws IOException, OCSPException
+    {
+        OCSPReq request = generateOCSPRequest();
+        URL url = new URL(ocspUrl);
+        HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
+        httpConnection.setRequestProperty("Content-Type", "application/ocsp-request");
+        httpConnection.setRequestProperty("Accept", "application/ocsp-response");
+        httpConnection.setDoOutput(true);
+        try (OutputStream out = httpConnection.getOutputStream())
+        {
+            out.write(request.getEncoded());
+        }
+
+        if (httpConnection.getResponseCode() != 200)
+        {
+            throw new IOException("OCSP: Could not access url, ResponseCode: "
+                    + httpConnection.getResponseCode());
+        }
+        // Get Response
+        InputStream in = (InputStream) httpConnection.getContent();
+        return new OCSPResp(in);
+    }
+
+    /**
+     * Helper method to verify response status.
+     * 
+     * @param resp OCSP response
+     * @throws OCSPException if the response status is not ok
+     */
+    public void verifyRespStatus(OCSPResp resp) throws OCSPException
+    {
+        String statusInfo = "";
+        if (resp != null)
+        {
+            int status = resp.getStatus();
+            switch (status)
+            {
+            case OCSPResponseStatus.INTERNAL_ERROR:
+                statusInfo = "INTERNAL_ERROR";
+                System.err.println("An internal error occurred in the OCSP Server!");
+                break;
+            case OCSPResponseStatus.MALFORMED_REQUEST:
+                statusInfo = "MALFORMED_REQUEST";
+                System.err.println("Your request did not fit the RFC 2560 syntax!");
+                break;
+            case OCSPResponseStatus.SIG_REQUIRED:
+                statusInfo = "SIG_REQUIRED";
+                System.err.println("Your request was not signed!");
+                break;
+            case OCSPResponseStatus.TRY_LATER:
+                statusInfo = "TRY_LATER";
+                System.err.println("The server was too busy to answer you!");
+                break;
+            case OCSPResponseStatus.UNAUTHORIZED:
+                statusInfo = "UNAUTHORIZED";
+                System.err.println("The server could not authenticate you!");
+                break;
+            case OCSPResponseStatus.SUCCESSFUL:
+                break;
+            default:
+                statusInfo = "UNKNOWN";
+                System.err.println("Unknown OCSPResponse status code! " + status);
+            }
+        }
+        if (resp == null || resp.getStatus() != OCSPResponseStatus.SUCCESSFUL)
+        {
+            throw new OCSPException(statusInfo + "OCSP response unsuccessful! ");
+        }
+    }
+
+    /**
+     * Generates an OCSP request and generates the <code>CertificateID</code>.
+     *
+     * @return OCSP request, ready to fetch data
+     * @throws OCSPException
+     * @throws IOException
+     */
+    private OCSPReq generateOCSPRequest() throws OCSPException, IOException
+    {
+        Security.addProvider(new BouncyCastleProvider());
+
+        // Generate the ID for the certificate we are looking for
+        CertificateID certId;
+        try
+        {
+            certId = new CertificateID(new SHA1DigestCalculator(),
+                    new JcaX509CertificateHolder(issuerCertificate),
+                    certificateToCheck.getSerialNumber());
+        }
+        catch (CertificateEncodingException e)
+        {
+            throw new IOException("Error creating CertificateID with the Certificate encoding", e);
+        }
+
+        OCSPReqBuilder builder = new OCSPReqBuilder();
+
+        Extension responseExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_response,
+                true, new DLSequence(OCSPObjectIdentifiers.id_pkix_ocsp_basic).getEncoded());
+
+        Random rand = new Random();
+        byte[] nonce = new byte[16];
+        rand.nextBytes(nonce);
+        encodedNonce = new DEROctetString(new DEROctetString(nonce));
+        Extension nonceExtension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true,
+                encodedNonce);
+
+        builder.setRequestExtensions(
+                new Extensions(new Extension[] { responseExtension, nonceExtension }));
+
+        builder.addRequest(certId);
+
+        System.out.println("Nonce: " + Hex.getString(nonceExtension.getExtnValue().getEncoded()));
+
+        return builder.build();
+    }
+
+    /**
+     * Class to create SHA-1 Digest, used for creation of CertificateID.
+     */
+    private static class SHA1DigestCalculator implements DigestCalculator
+    {
+        private final ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+        @Override
+        public AlgorithmIdentifier getAlgorithmIdentifier()
+        {
+            return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1);
+        }
+
+        @Override
+        public OutputStream getOutputStream()
+        {
+            return bOut;
+        }
+        
+        @Override
+        public byte[] getDigest()
+        {
+            byte[] bytes = bOut.toByteArray();
+            bOut.reset();
+            
+            try
+            {
+                MessageDigest md = MessageDigest.getInstance("SHA-1");
+                return md.digest(bytes);
+            }
+            catch (NoSuchAlgorithmException e)
+            {
+                LOG.error("SHA-1 Algorithm not found", e);
+                return null;
+            }
+        }
+    }
+}

Propchange: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/OcspHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/RevokedCertificateException.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/RevokedCertificateException.java?rev=1818668&view=auto
==============================================================================
--- pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/RevokedCertificateException.java (added)
+++ pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/RevokedCertificateException.java Tue Dec 19 13:41:50 2017
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pdfbox.examples.signature.validation;
+
+/**
+ * Exception to handle a revoked Certificate explicitly
+ * 
+ * @author Alexis Suter
+ */
+public class RevokedCertificateException extends Exception
+{
+    private static final long serialVersionUID = 3543946618794126654L;
+
+    public RevokedCertificateException(String message)
+    {
+        super(message);
+    }
+}

Propchange: pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/validation/RevokedCertificateException.java
------------------------------------------------------------------------------
    svn:eol-style = native



Mime
View raw message