net.sf.jsignpdf.verify.VerifierLogic.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jsignpdf.verify.VerifierLogic.java

Source

/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (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.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is 'JSignPdf, a free application for PDF signing'.
 * 
 * The Initial Developer of the Original Code is Josef Cacek.
 * Portions created by Josef Cacek are Copyright (C) Josef Cacek. All Rights Reserved.
 * 
 * Contributor(s): Josef Cacek.
 * 
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU Lesser General Public License, version 2.1 (the  "LGPL License"), in which case the
 * provisions of LGPL License are applicable instead of those
 * above. If you wish to allow use of your version of this file only
 * under the terms of the LGPL License and not to allow others to use
 * your version of this file under the MPL, indicate your decision by
 * deleting the provisions above and replace them with the notice and
 * other provisions required by the LGPL License. If you do not delete
 * the provisions above, a recipient may use your version of this file
 * under either the MPL or the LGPL License.
 */
package net.sf.jsignpdf.verify;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyStore;
import java.security.cert.CertPath;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.security.auth.x500.X500Principal;

import net.sf.jsignpdf.Constants;
import net.sf.jsignpdf.utils.KeyStoreUtils;
import net.sf.jsignpdf.utils.PdfUtils;

import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.ocsp.CertificateID;
import org.bouncycastle.ocsp.SingleResp;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TimeStampToken;

import com.lowagie.text.pdf.AcroFields;
import com.lowagie.text.pdf.OcspClientBouncyCastle;
import com.lowagie.text.pdf.PdfPKCS7;
import com.lowagie.text.pdf.PdfPKCS7.X509Name;
import com.lowagie.text.pdf.PdfReader;

/**
 * Class VerifierLogic contains all logic for PDF signatures verification. It
 * uses only system default keystore by default, but you can add additional
 * certificates from external files using {@link #addX509CertFile(String)}
 * method.
 * 
 * @author Josef Cacek
 * @author Aleksandar Stojsavljevic
 * @version $Revision: 1.21 $
 * @created $Date: 2012/02/17 11:50:36 $
 */
public class VerifierLogic {

    private KeyStore kall;
    private boolean failFast;

    /**
     * Constructor. It initializes default keystore.
     */
    public VerifierLogic(final String aType, final String aKeyStore, final String aPasswd) {
        reinitKeystore(aType, aKeyStore, aPasswd);
    }

    /**
     * Adds X.509 certificates from given file. If any Exception occures, it's
     * not throwed but returned as a result of this method.
     * 
     * @param aPath
     *            full path to the file with certificate(s)
     * @return Exception if any throwed during adding.
     */
    @SuppressWarnings("unchecked")
    public Exception addX509CertFile(final String aPath) {
        try {
            final CertificateFactory tmpCertFac = CertificateFactory.getInstance(Constants.CERT_TYPE_X509); // X.509
            // ?
            final Collection<X509Certificate> tmpCertCol = (Collection<X509Certificate>) tmpCertFac
                    .generateCertificates(new FileInputStream(aPath));
            for (X509Certificate tmpCert : tmpCertCol) {
                kall.setCertificateEntry(tmpCert.getSerialNumber().toString(Character.MAX_RADIX), tmpCert);
            }
        } catch (Exception e) {
            return e;
        }
        return null;
    }

    /**
     * Initializes keystore (load certificates from default keystore). All
     * previously added certificates from external files are forgotten.
     */
    public void reinitKeystore(String aKsType, final String aKeyStore, final String aPasswd) {
        try {
            kall = KeyStoreUtils.createKeyStore();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        final KeyStore ksToImport = KeyStoreUtils.loadKeyStore(aKsType, aKeyStore, aPasswd);
        if (ksToImport != null) {
            KeyStoreUtils.copyCertificates(ksToImport, kall);
        }
    }

    /**
     * Verifies signature(s) in PDF document.
     * 
     * @param aFileName
     *            path to a verified PDF file
     * @param aPassword
     *            PDF password - used if PDF is encrypted
     * @return
     */
    public VerificationResult verify(final String aFileName, byte[] aPassword) {

        try {
            return verify(PdfUtils.getPdfReader(aFileName, aPassword));
        } catch (Exception e) {
            final VerificationResult tmpResult = new VerificationResult();
            tmpResult.setException(e);
            return tmpResult;
        }
    }

    /**
     * Verifies signature(s) in PDF document.
     * 
     * @param content
     *            content of PDF
     * @param aPassword
     *            password
     * @return
     */
    public VerificationResult verify(final byte[] content, byte[] aPassword) {

        try {
            return verify(PdfUtils.getPdfReader(content, aPassword));
        } catch (Exception e) {
            final VerificationResult tmpResult = new VerificationResult();
            tmpResult.setException(e);
            return tmpResult;
        }
    }

    /**
     * Verifies signature(s) in PDF document.
     * 
     * @param tmpReader
     *            PdfReader for given PDF
     * @return
     */
    @SuppressWarnings("unchecked")
    private VerificationResult verify(final PdfReader tmpReader) {
        final VerificationResult tmpResult = new VerificationResult();
        try {
            final AcroFields tmpAcroFields = tmpReader.getAcroFields();
            final List<String> tmpNames = tmpAcroFields.getSignatureNames();
            tmpResult.setTotalRevisions(tmpAcroFields.getTotalRevisions());

            final int lastSignatureIdx = tmpNames.size() - 1;
            if (lastSignatureIdx < 0) {
                // there is no signature
                tmpResult.setWithoutSignature();
            }
            for (int i = lastSignatureIdx; i >= 0; i--) {
                final String name = tmpNames.get(i);
                final SignatureVerification tmpVerif = new SignatureVerification(name);
                tmpVerif.setLastSignature(i == lastSignatureIdx);
                tmpVerif.setWholeDocument(tmpAcroFields.signatureCoversWholeDocument(name));
                tmpVerif.setRevision(tmpAcroFields.getRevision(name));
                final PdfPKCS7 pk = tmpAcroFields.verifySignature(name);
                final TimeStampToken tst = pk.getTimeStampToken();
                tmpVerif.setTsTokenPresent(tst != null);
                tmpVerif.setTsTokenValidationResult(validateTimeStampToken(tst));
                tmpVerif.setDate(pk.getTimeStampDate() != null ? pk.getTimeStampDate() : pk.getSignDate());
                tmpVerif.setLocation(pk.getLocation());
                tmpVerif.setReason(pk.getReason());
                tmpVerif.setSignName(pk.getSignName());
                final Certificate pkc[] = pk.getCertificates();
                final X509Name tmpX509Name = PdfPKCS7.getSubjectFields(pk.getSigningCertificate());
                tmpVerif.setSubject(tmpX509Name.toString());
                tmpVerif.setModified(!pk.verify());
                tmpVerif.setOcspPresent(pk.getOcsp() != null);
                tmpVerif.setOcspValid(pk.isRevocationValid());
                tmpVerif.setCrlPresent(pk.getCRLs() != null && pk.getCRLs().size() > 0);
                tmpVerif.setFails(PdfPKCS7.verifyCertificates(pkc, kall, pk.getCRLs(), tmpVerif.getDate()));
                tmpVerif.setSigningCertificate(pk.getSigningCertificate());

                // generate CertPath
                List<Certificate> certList = Arrays.asList(pkc);
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                CertPath cp = cf.generateCertPath(certList);
                tmpVerif.setCertPath(cp);

                // to save time - check OCSP in certificate only if document's OCSP is not present and valid
                if (!tmpVerif.isOcspValid()) {
                    // try to get OCSP url from signing certificate 
                    String url = PdfPKCS7.getOCSPURL((X509Certificate) pk.getSigningCertificate());
                    tmpVerif.setOcspInCertPresent(url != null);

                    if (url != null) {
                        // OCSP url is found in signing certificate - verify certificate with that url
                        tmpVerif.setOcspInCertValid(validateCertificateOCSP(pk.getSignCertificateChain(), url));
                    }
                }

                String certificateAlias = kall.getCertificateAlias(pk.getSigningCertificate());
                if (certificateAlias != null) {
                    // this means that signing certificate is directly trusted

                    String verifyCertificate = PdfPKCS7.verifyCertificate(pk.getSigningCertificate(), pk.getCRLs(),
                            tmpVerif.getDate());
                    if (verifyCertificate == null) {
                        // this means that signing certificate is valid
                        tmpVerif.setSignCertTrustedAndValid(true);
                    }
                }

                final InputStream revision = tmpAcroFields.extractRevision(name);
                try {
                    final PdfReader revisionReader = new PdfReader(revision);
                    tmpVerif.setCertLevelCode(revisionReader.getCertificationLevel());
                } finally {
                    if (revision != null) {
                        revision.close();
                    }
                }
                tmpResult.addVerification(tmpVerif);
                if (failFast && tmpVerif.containsError()) {
                    return tmpResult;
                }
            }
        } catch (Exception e) {
            tmpResult.setException(e);
        }
        return tmpResult;
    }

    /**
     * @return the failFast
     */
    public boolean isFailFast() {
        return failFast;
    }

    /**
     * @param failFast
     *            the failFast to set
     */
    public void setFailFast(boolean failFast) {
        this.failFast = failFast;
    }

    /**
     * Returns InputStream which contains extracted revision (PDF) which was
     * signed with signature of given name.
     * 
     * @param aFileName
     * @param aPassword
     * @param aSignatureName
     * @return
     * @throws IOException
     */
    public InputStream extractRevision(String aFileName, byte[] aPassword, String aSignatureName)
            throws IOException {
        final PdfReader tmpReader = PdfUtils.getPdfReader(aFileName, aPassword);
        final AcroFields tmpAcroFields = tmpReader.getAcroFields();
        return tmpAcroFields.extractRevision(aSignatureName);
    }

    /**
     * Returns keystore used in verifier.
     * 
     * @return used keystore
     */
    public KeyStore getKeyStore() {
        return kall;
    }

    public Exception validateTimeStampToken(TimeStampToken token) {
        if (token == null) {
            return null;
        }
        try {
            SignerId signer = token.getSID();

            X509Certificate certificate = null;
            X500Principal sign_cert_issuer = signer.getIssuer();
            BigInteger sign_cert_serial = signer.getSerialNumber();

            CertStore store = token.getCertificatesAndCRLs("Collection", "BC");

            // Iterate CertStore to find a signing certificate
            Collection<? extends Certificate> certs = store.getCertificates(null);
            Iterator<? extends Certificate> iter = certs.iterator();

            while (iter.hasNext()) {
                X509Certificate cert = (X509Certificate) iter.next();
                if (cert.getIssuerX500Principal().equals(sign_cert_issuer)
                        && cert.getSerialNumber().equals(sign_cert_serial)) {
                    certificate = cert;
                    break;
                }
            }

            if (certificate == null) {
                throw new TSPException("Missing signing certificate for TSA.");
            }

            // check TS token's certificate against keystore
            if (certs.size() == 1) {
                boolean verifyTimestampCertificates = PdfPKCS7.verifyTimestampCertificates(token, kall, null);
                if (!verifyTimestampCertificates) {
                    throw new Exception("Timestamp certificate can't be verified.");
                }
            } else {
                int certSize = certs.size();
                Certificate[] array = certs.toArray(new Certificate[certSize]);
                Certificate[] certArray = new Certificate[certSize];
                // reverse order
                for (int i = 0; i < certSize; i++) {
                    certArray[i] = array[certSize - 1 - i];
                }
                // token.validate(SignerInformationVerifier) will check if certificate has been valid at the time the timestamp was created
                Object[] verifyCertificates = PdfPKCS7.verifyCertificates(certArray, kall, null, null);
                if (verifyCertificates != null) {
                    throw new Exception("Timestamp certificate can't be verified.");
                }
            }

            SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder().build(certificate);
            token.validate(verifier);
        } catch (Exception e) {
            return e;
        }
        return null;
    }

    /**
     * Validates certificate (chain) using OCSP.
     * 
     * @param pkc
     *            certificate chain, 1st certificate will be validated
     * @param url
     *            OCSP url for validation
     * @return
     */
    private static boolean validateCertificateOCSP(Certificate pkc[], String url) {
        if (pkc.length < 2) {
            return false;
        }

        try {
            X509Certificate sigcer = (X509Certificate) pkc[0];
            X509Certificate isscer = (X509Certificate) pkc[1];
            OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(sigcer, isscer, url);
            // TODO implement proxy support
            //         ocspClient.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8888)));

            byte[] encoded = ocspClient.getEncoded();

            ASN1InputStream inp = new ASN1InputStream(encoded);
            BasicOCSPResponse resp = BasicOCSPResponse.getInstance(inp.readObject());
            org.bouncycastle.ocsp.BasicOCSPResp basicResp = new org.bouncycastle.ocsp.BasicOCSPResp(resp);

            SingleResp sr = basicResp.getResponses()[0];
            CertificateID cid = sr.getCertID();
            CertificateID tis = new CertificateID(CertificateID.HASH_SHA1, isscer, sigcer.getSerialNumber());
            return tis.equals(cid);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Gets validation codes for all signatures in document.
     * 
     * @param verResult
     * @return
     */
    public static Map<String, Integer> getValidationCodes(VerificationResult verResult) {
        final Map<String, Integer> validationCodes = new HashMap<String, Integer>();

        for (SignatureVerification verification : verResult.getVerifications()) {
            validationCodes.put(verification.getName(), verification.getValidationCode());
        }

        return validationCodes;
    }

}