org.sinekartads.core.pdf.PDFTools.java Source code

Java tutorial

Introduction

Here is the source code for org.sinekartads.core.pdf.PDFTools.java

Source

/*
 * Copyright (C) 2010 - 2012 Jenia Software.
 *
 * This file is part of Sinekarta
 *
 * Sinekarta is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Sinekarta 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 General Public License for more details.
 *
 * Part of this code come from 
 * FirmaPdf version 0.0.x Copyright (C) 2006 Antonino Iacono (ant_iacono@tin.it)
 * and Roberto Resoli
 * See method description for more details
 * 
 * Part of this code come from 
 * com.itextpdf.text.pdf.security.MakeSignature
 * Paulo Soares
 * 
 * See method description for more details
 * 
 */
package org.sinekartads.core.pdf;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.DERGeneralString;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.sinekartads.model.domain.DigestInfo;
import org.sinekartads.model.domain.PDFSignatureInfo;
import org.sinekartads.model.domain.SecurityLevel;
import org.sinekartads.model.domain.SignDisposition;
import org.sinekartads.model.domain.SignatureType;
import org.sinekartads.model.domain.Transitions.ChainSignature;
import org.sinekartads.model.domain.Transitions.DigestSignature;
import org.sinekartads.model.domain.Transitions.FinalizedSignature;
import org.sinekartads.model.domain.Transitions.SignedSignature;
import org.sinekartads.model.domain.TsRequestInfo;

import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfDate;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignature;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import com.itextpdf.text.pdf.security.TSAClient;
import com.itextpdf.text.pdf.security.TSAClientBouncyCastle;

public class PDFTools {

    private static Logger tracer = Logger.getLogger(PDFTools.class);

    public static final String PDF = ".pdf";

    public static FinalizedSignature<SignatureType.SignCategory, SignDisposition.PDF, SecurityLevel.VerifyResult, PDFSignatureInfo> sign(
            SignedSignature<SignatureType.SignCategory, SignDisposition.PDF, SecurityLevel.VerifyResult, PDFSignatureInfo> signedSignature,
            //                                   X509Certificate certificate, 
            InputStream is, OutputStream os) throws SignatureException {
        ////      signAndMark(doc, certificate, is, os, null, null, null, null, null);
        //      signAndMark(signatureInfo, certificate, is, os, null, null, null);
        //   }
        //
        //   public static void signAndMark(PDFSignatureInfo doc,
        //         X509Certificate certificate, InputStream is, OutputStream os,
        //         String tsaUrl, String tsaUser, String tsaPassword) {
        ////      signAndMark(doc, certificate, is, os, tsaUrl, tsaUser, tsaPassword, null, null);
        ////   }
        ////   
        ////   public static void signAndMark(DigitalSignatureDocument doc,
        ////         X509Certificate certificate, InputStream is, OutputStream os,
        ////         String tsaUrl, String tsaUser, String tsaPassword, Collection<CrlClient> crlList, OcspClient ocspClient) {
        try {
            PDFSignatureInfo signature = (PDFSignatureInfo) signedSignature;
            TSAClient tsaClient = null;

            TsRequestInfo tsRequest = signature.getTsRequest();
            if (tsRequest != null && StringUtils.isNotBlank(tsRequest.getTsUrl())) {
                tsaClient = new TSAClientBouncyCastle(tsRequest.getTsUrl(), tsRequest.getTsUsername(),
                        tsRequest.getTsPassword());
            }
            //         if (tsaUrl!=null) {
            //            tsaClient = new TSAClientBouncyCastle(tsaUrl, tsaUser, tsaPassword);
            //         }

            int estimatedSize = 0;
            CryptoStandard sigtype = CryptoStandard.CMS;

            // creo il reader del pdf
            PdfReader reader = new PdfReader(is);

            // creo lo stamper (se il pdf e' gia' firmato, controfirma,
            // altrimenti firma
            PdfStamper stamper = null;
            if (isPdfSigned(reader)) {
                if (tracer.isDebugEnabled())
                    tracer.debug("document already signed, i will apply another sign");
                stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
            } else {
                if (tracer.isDebugEnabled())
                    tracer.debug("document never signed before, this is first");
                stamper = PdfStamper.createSignature(reader, os, '\0');
            }

            // questo e' il certificato su cui lavorare
            Certificate[] chain = signature.getRawX509Certificates();
            //         Certificate[] chain = new Certificate[1];
            //         chain[0] = certificate;

            // creo la signature apparence
            PdfSignatureAppearance sap = stamper.getSignatureAppearance();
            ExternalDigest externalDigest = new BouncyCastleDigest();

            // inizio codice copiato da MakeSignature

            //         Collection<byte[]> crlBytes = null;
            //           int i = 0;
            //           while (crlBytes == null && i < chain.length)
            //              crlBytes = MakeSignature.processCrl(chain[i++], crlList);
            if (estimatedSize == 0) {
                estimatedSize = 8192;
                //               if (crlBytes != null) {
                //                   for (byte[] element : crlBytes) {
                //                       estimatedSize += element.length + 10;
                //                   }
                //               }
                //               if (ocspClient != null)
                estimatedSize += 4192;
                //               if (tsaClient != null)
                estimatedSize += 4192;
            }
            sap.setCertificate(chain[0]);
            sap.setReason(signature.getReason());
            sap.setLocation(signature.getLocation());

            Calendar cal = Calendar.getInstance();
            cal.setTime(signature.getSigningTime());
            sap.setSignDate(cal);
            sap.getStamper().setUnicodeModDate(signature.getUnicodeModDate());
            sap.getStamper().setFileId(signature.getFileId());

            PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
            dic.setReason(sap.getReason());
            dic.setLocation(sap.getLocation());
            dic.setContact(sap.getContact());
            dic.setDate(new PdfDate(sap.getSignDate())); // time-stamp will over-rule this
            sap.setCryptoDictionary(dic);

            HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
            exc.put(PdfName.CONTENTS, new Integer(estimatedSize * 2 + 2));
            sap.preClose(exc);

            String hashAlgorithm = signature.getDigestAlgorithm().getName();
            PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, BouncyCastleProvider.PROVIDER_NAME,
                    externalDigest, false);
            InputStream data = sap.getRangeStream();
            byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest(hashAlgorithm));
            //           byte[] ocsp = null;
            //           if (chain.length >= 2 && ocspClient != null) {
            //               ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
            //           }
            sgn.setExternalDigest(signature.getDigitalSignature(), null, "RSA");

            //           byte[] encodedSig = sgn.getEncodedPKCS7(hash, _getSignDate(doc.getSignDate()), tsaClient, ocsp, crlBytes, sigtype);
            byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, tsaClient, null, null, sigtype);

            if (estimatedSize + 2 < encodedSig.length)
                throw new IOException("Not enough space");

            ASN1EncodableVector extraDataVectorEncoding = new ASN1EncodableVector();
            // 
            extraDataVectorEncoding.add(new DERObjectIdentifier("1.2.840.114283")); // encoding attribute 
            extraDataVectorEncoding.add(new DERGeneralString("115.105.110.101.107.97.114.116.97"));

            // applico la firma al PDF
            byte[] extraDataVectorEncodingBytes = new DERSequence(new DERSequence(extraDataVectorEncoding))
                    .getEncoded();

            byte[] paddedSig = new byte[estimatedSize];
            System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
            System.arraycopy(extraDataVectorEncodingBytes, 0, paddedSig, encodedSig.length,
                    extraDataVectorEncodingBytes.length); // encoding attribute

            PdfDictionary dic2 = new PdfDictionary();
            dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
            sap.close(dic2);

            // this should be already done, but ...
            // closing streams
            try {
                is.close();
            } catch (IOException e) {
                tracer.error("error on input stream", e);
            }
            try {
                os.flush();
            } catch (IOException e) {
                tracer.error("error on output stream", e);
            }
            try {
                os.close();
            } catch (IOException e) {
                tracer.error("error on output stream", e);
            }
            return signature.finalizeSignature();
            //      } catch (MarkFailedException e) {
            //         throw e;
        } catch (Exception e) {
            tracer.error("Unable to sign PDF.", e);
            throw new SignatureException("Unable to sign PDF.", e);
        }
    }

    public static DigestSignature<SignatureType.SignCategory, SignDisposition.PDF, SecurityLevel.VerifyResult, PDFSignatureInfo> calculateFingerPrint(
            ChainSignature<SignatureType.SignCategory, SignDisposition.PDF, SecurityLevel.VerifyResult, PDFSignatureInfo> chainSignature,
            //                                                           X509Certificate certificate, 
            InputStream is) throws SignatureException {
        //      calculateFingerPrint(doc, certificate, is, null, null, null, null, null);
        //   }
        //   
        //   public static void calculateFingerPrint(DigitalSignatureDocument doc,
        //         X509Certificate certificate, InputStream is, Collection<CrlClient> crlList, OcspClient ocspClient, String tsaUrl, String tsaUser, String tsaPassword) {
        try {

            //         TSAClient tsaClient=null;
            //         
            //         if (tsaUrl!=null) {
            //            tsaClient = new SinekartaTSAClient(tsaUrl, tsaUser, tsaPassword);
            //         }
            //
            int estimatedSize = 0;
            CryptoStandard sigtype = CryptoStandard.CMS; // FIXME qui c'era CMS
            PDFSignatureInfo signature = (PDFSignatureInfo) chainSignature;

            // creo il reader del pdf
            PdfReader reader = new PdfReader(is);

            // creo lo stamper (se il pdf e' gia' firmato, controfirma,
            // altrimenti firma
            PdfStamper stamper = null;
            if (isPdfSigned(reader)) {
                if (tracer.isDebugEnabled())
                    tracer.debug("calculating finger print for document already signed");
                stamper = PdfStamper.createSignature(reader, null, '\0', null, true);
            } else {
                if (tracer.isDebugEnabled())
                    tracer.debug("calculating finger print for document never signed before");
                stamper = PdfStamper.createSignature(reader, null, '\0');
            }

            // questo e' il certificato su cui lavorare
            Certificate[] chain = signature.getRawX509Certificates();
            //         Certificate[] chain = new Certificate[1];
            //         chain[0] = certificate;

            // creo la signature apparence
            PdfSignatureAppearance sap = stamper.getSignatureAppearance();
            ExternalDigest externalDigest = new BouncyCastleDigest();

            // inizio codice copiato da MakeSignature

            //         Collection<byte[]> crlBytes = null;
            //           int i = 0;
            //           while (crlBytes == null && i < chain.length)
            //              crlBytes = MakeSignature.processCrl(chain[i++], crlList);
            if (estimatedSize == 0) {
                estimatedSize = 8192;
                //               if (crlBytes != null) {
                //                   for (byte[] element : crlBytes) {
                //                       estimatedSize += element.length + 10;
                //                   }
                //               }
                //               if (ocspClient != null)
                estimatedSize += 4192;
                //               if (tsaClient != null)
                estimatedSize += 4192;
            }
            Calendar now = Calendar.getInstance();
            PdfDate date = new PdfDate(now);

            sap.setSignDate(now);
            signature.setSigningTime(now.getTime());
            signature.setUnicodeModDate(date.toUnicodeString());

            sap.setCertificate(chain[0]);
            sap.setReason(signature.getReason());
            sap.setLocation(signature.getLocation());

            PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
            dic.setReason(sap.getReason());
            dic.setLocation(sap.getLocation());
            dic.setContact(sap.getContact());
            dic.setDate(date); // time-stamp will over-rule this
            sap.setCryptoDictionary(dic);

            HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
            exc.put(PdfName.CONTENTS, new Integer(estimatedSize * 2 + 2));
            sap.preClose(exc);

            String hashAlgorithm = signature.getDigestAlgorithm().getName();
            PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, BouncyCastleProvider.PROVIDER_NAME,
                    externalDigest, false);
            //           String hashAlgorithm = Constants.SHA256;
            //           PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, Constants.BC, externalDigest, false);
            InputStream data = sap.getRangeStream();
            byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest(hashAlgorithm));
            //           byte[] ocsp = null;
            //           if (chain.length >= 2 && ocspClient != null) {
            //               ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
            //           }
            //           byte[] authenticatedAttributeBytes = sgn.getAuthenticatedAttributeBytes(hash, now, ocsp, crlBytes, sigtype);
            byte[] authenticatedAttributeBytes = sgn.getAuthenticatedAttributeBytes(hash, now, null, null, sigtype);

            // calcolo dell'impronta
            MessageDigest digester = MessageDigest.getInstance(signature.getDigestAlgorithm().getName());
            byte[] fingerPrint = digester.digest(authenticatedAttributeBytes);

            //           byte[] fingerPrint = Util.digest256(authenticatedAttributeBytes);

            signature.setAuthenticatedAttributeBytes(authenticatedAttributeBytes);
            signature.setFileId(sap.getStamper().getFileId());
            //           signature.setFileIDByteContent(TextUtil.byteToHex(sap.getStamper().getFileID().getBytes()));
            signature.setUnicodeModDate(sap.getStamper().getUnicodeModDate());
            //         signature.setModDateUnicodeString(sap.getStamper().getModDate().toUnicodeString());
            signature.setSigningTime(now.getTime());
            //         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSSZ");
            //         signature.setSignDate(sdf.format(now.getTime()));

            // this should be already done, but ...
            // closing streams
            try {
                is.close();
            } catch (IOException e) {
                tracer.error("error on input stream", e);
            }

            return signature.toDigestSignature(DigestInfo.getInstance(signature.getDigestAlgorithm(), fingerPrint));
        } catch (Exception e) {
            tracer.error("Unable to calculate finger print of PDF.", e);
            //         throw new PDFException("Unable calculate finger print of PDF.", e);
            throw new SignatureException("Unable calculate finger print of PDF.", e);
        }
    }

    /**
     * metodo di utilita' che verifica se il pdf in input e' gia' firmato
     * 
     * @param reader
     * @return
     * @throws SignatureException 
     */
    public static boolean isPdfSigned(InputStream is) throws SignatureException {
        if (tracer.isDebugEnabled())
            tracer.debug("chacking if PDF/A is signed");
        try {
            PdfReader reader = new PdfReader(is);
            boolean ret = false;
            if (PDFTools.isPdfSigned(reader)) {
                ret = true;
            }
            reader.close();
            return ret;
        } catch (Exception e) {
            tracer.error("Unable to read PDF. Unable to check if the pdf is signed.", e);
            throw new SignatureException("Unable to read PDF. Unable to check if the pdf is signed.", e);
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * metodo di utilita' che verifica se il pdf in input e' gia' firmato
     * 
     * @param reader
     * @return
     * @throws SignatureException 
     */
    public static boolean isPdfSigned(PdfReader reader) throws SignatureException {
        if (tracer.isDebugEnabled())
            tracer.debug("chacking if PDF/A is signed");
        try {
            AcroFields af = reader.getAcroFields();

            // Search of the whole signature
            ArrayList<String> names = af.getSignatureNames();

            // For every signature :
            if (names.size() > 0) {
                if (tracer.isDebugEnabled())
                    tracer.debug("yes, it is");
                return true;
            } else {
                if (tracer.isDebugEnabled())
                    tracer.debug("no, it isn't");
                return false;
            }
        } catch (Exception e) {
            tracer.error("Unable to read PDF. Unable to check if the pdf is signed.", e);
            throw new SignatureException("Unable to read PDF. Unable to check if the pdf is signed.", e);
        }
    }

    //   /**
    //    * metodo di utilita' che verifica se il pdf in input e' un PDF/A
    //    * 
    //    * @param reader
    //    * @return
    //    */
    //   public static boolean isPdfa(InputStream is) {
    //      if (tracer.isDebugEnabled()) tracer.debug("checking if PDF is PDF/A");
    //      PdfReader reader = null;
    //      ByteArrayInputStream bais = null;
    //      XMLStreamReader sr = null;
    //      try {
    //         reader = new PdfReader(is);
    //         byte[] metadata = reader.getMetadata();
    //         if (metadata == null || metadata.length == 0)
    //            return false;
    //         bais = new ByteArrayInputStream(metadata);
    //         sr = XMLInputFactory.newInstance().createXMLStreamReader(bais);
    //         boolean isConformanceTag = false;
    //         int eventCode;
    //         while (sr.hasNext()) {
    //            eventCode = sr.next();
    //            String val = null;
    //            switch (eventCode) {
    //            case 1:
    //               val = sr.getLocalName();
    //               if (val.equals("conformance") && sr.getNamespaceURI().equals("http://www.aiim.org/pdfa/ns/id/"))
    //                  isConformanceTag = true;
    //               break;
    //            case 4:
    //               val = sr.getText();
    //               if (isConformanceTag) {
    //                  if (val.equals("A") || val.equals("B")) {
    //                     if (tracer.isDebugEnabled()) tracer.debug("yes, it is");
    //                     return true;
    //                  } else {
    //                     if (tracer.isDebugEnabled()) tracer.debug("no, it isn't");
    //                     return false;
    //                  }
    //               }
    //               break;
    //            }
    //         }
    //      } catch (Exception e) {
    //         tracer.error("Unable to read PDF. Unable to check if the pdf is a pdf/a.",e);
    //         throw new PDFException("Unable to read PDF. Unable to check if the pdf is a pdf/a.",e);
    //      } finally {
    //         try {
    //            if (reader != null)
    //               reader.close();
    //         } catch (Exception e) {
    //            tracer.error("error on pdf reader", e);
    //         }
    //         try {
    //            if (sr != null)
    //               sr.close();
    //         } catch (Exception e) {
    //            tracer.error("error on stax reader", e);
    //         }
    //         try {
    //            if (bais != null)
    //               bais.close();
    //         } catch (Exception e) {
    //            tracer.error("error on input stream", e);
    //         }
    //         try {
    //            if (is != null)
    //               is.close();
    //         } catch (Exception e) {
    //            tracer.error("error on input stream", e);
    //         }
    //      }
    //      if (tracer.isDebugEnabled())
    //         tracer.debug("no, it isn't");
    //      return false;
    //   }
    //
    //   /**
    //    * metodo di utilita' che verifica se il pdf in input e' un PDF/A
    //    * 
    //    * @param reader
    //    * @return
    //    */
    //   public static String getPrintableTimestampToken(InputStream is) {
    //      if (tracer.isDebugEnabled()) tracer.debug("getting timestamp token");
    //      String ret = null;
    //      PdfReader reader = null;
    //      try {
    //         reader = new PdfReader(is);
    //         AcroFields af = reader.getAcroFields();
    //         for (String name : af.getSignatureNames()) {
    //            PdfPKCS7 pk = af.verifySignature(name);
    //            TimeStampToken tst = pk.getTimeStampToken();
    //            if (tst != null) {
    //               ret = Base64.encodeBytes(tst.getEncoded());
    //            }
    //         }
    //         if (tracer.isDebugEnabled()) tracer.debug("timestamp token returned : " + ret);
    //         return ret;
    //      } catch (Exception e) {
    //         tracer.error("Unable to read PDF. Unable to get timestamp token.", e);
    //         throw new PDFException("Unable to read PDF. Unable to get timestamp token.", e);
    //      } finally {
    //         try {
    //            if (reader != null)
    //               reader.close();
    //         } catch (Exception e) {
    //            tracer.error("error on pdf reader", e);
    //         }
    //         try {
    //            if (is != null)
    //               is.close();
    //         } catch (Exception e) {
    //            tracer.error("error on input stream", e);
    //         }
    //      }
    //   }
    //
    //   public static String calculatePdfName(String fileName) {
    //      int dotIdx = fileName.lastIndexOf('.');
    //      if (dotIdx >= 0) {
    //         return fileName.substring(0, dotIdx)
    //               + Configuration.getInstance().getPdfaSuffix()
    //               + PDFTools.PDF;
    //      } else {
    //         return fileName + Configuration.getInstance().getPdfaSuffix()
    //               + PDFTools.PDF;
    //      }
    //   }
    //
    //   public static Calendar _getSignDate(String signDate) {
    //      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSSZ");
    //      Date d = null;
    //      try {
    //         d = sdf.parse(signDate);
    //      } catch (ParseException e) {
    //         // not possible
    //      }
    //      Calendar c = Calendar.getInstance();
    //      c.setTime(d);
    //      return c;
    //   }

}