com.swisscom.ais.itext.PDF.java Source code

Java tutorial

Introduction

Here is the source code for com.swisscom.ais.itext.PDF.java

Source

/**
 * Class to modify or get information from pdf document
 *
 * Created:
 * 19.12.13 KW51 08:04
 * </p>
 * Last Modification:
 * 22.01.2014 13:58
 * <p/>
 * Version:
 * 1.0.0
 * </p>
 * Copyright:
 * Copyright (C) 2013. All rights reserved.
 * </p>
 * License:
 * Licensed under the Apache License, Version 2.0 or later; see LICENSE.md
 * </p>
 * Author:
 * Swisscom (Schweiz) AG
 */

package com.swisscom.ais.itext;

import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.codec.Base64;
import com.itextpdf.text.pdf.security.*;

import javax.annotation.Nonnull;

import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.OCSPResp;

import java.io.*;
import java.security.MessageDigest;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;

public class PDF {

    /**
     * Save file path from input file
     */
    private String inputFilePath;

    /**
     * Save file path from output file
     */
    private String outputFilePath;

    /**
     * Save password from pdf
     */
    private String pdfPassword;

    /**
     * Save signing reason
     */
    private int certificationLevel = 0;

    /**
     * Save signing reason
     */
    private String signReason;

    /**
     * Save signing location
     */
    private String signLocation;

    /**
     * Save signing contact
     */
    private String signContact;

    /**
     * Save PdfReader
     */
    private PdfReader pdfReader = null;

    /**
     * Save signature appearance from pdf
     */
    private PdfSignatureAppearance pdfSignatureAppearance;

    /**
     * Save pdf signature
     */
    private PdfSignature pdfSignature;

    /**
     * Save pdfStamper
     */
    private PdfStamper pdfStamper;

    /**
     * Save byte array outputstream for writing pdf file
     */
    private ByteArrayOutputStream byteArrayOutputStream;

    /**
     * Set parameters
     *
     * @param inputFilePath  Path from input file
     * @param outputFilePath Path from output file
     * @param pdfPassword    Password form pdf
     * @param signReason     Reason from signing
     * @param signLocation   Location for frOn signing
     * @param signContact    Contact for signing
     */
    PDF(@Nonnull String inputFilePath, @Nonnull String outputFilePath, String pdfPassword, String signReason,
            String signLocation, String signContact, int certificationLevel) {
        this.inputFilePath = inputFilePath;
        this.outputFilePath = outputFilePath;
        this.pdfPassword = pdfPassword;
        this.signReason = signReason;
        this.signLocation = signLocation;
        this.signContact = signContact;
        this.certificationLevel = certificationLevel;
    }

    /**
     * Get file path of pdf to sign
     *
     * @return Path from pdf to sign
     */
    public String getInputFilePath() {
        return inputFilePath;
    }

    /**
     * Add signature information (reason for signing, location, contact, date) and create hash from pdf document
     *
     * @param signDate        Date of signing
     * @param estimatedSize   The estimated size for signatures
     * @param hashAlgorithm   The hash algorithm which will be used to sign the pdf
     * @param isTimestampOnly If it is a timestamp signature. This is necessary because the filter is an other one compared to a "standard" signature
     * @return Hash of pdf as bytes
     * @throws Exception 
     */
    public byte[] getPdfHash(@Nonnull Calendar signDate, int estimatedSize, @Nonnull String hashAlgorithm,
            boolean isTimestampOnly) throws Exception {

        pdfReader = new PdfReader(inputFilePath, pdfPassword != null ? pdfPassword.getBytes() : null);
        AcroFields acroFields = pdfReader.getAcroFields();
        boolean hasSignature = acroFields.getSignatureNames().size() > 0;
        byteArrayOutputStream = new ByteArrayOutputStream();
        pdfStamper = PdfStamper.createSignature(pdfReader, byteArrayOutputStream, '\0', null, hasSignature);
        pdfStamper.setXmpMetadata(pdfReader.getMetadata());

        pdfSignatureAppearance = pdfStamper.getSignatureAppearance();
        pdfSignature = new PdfSignature(PdfName.ADOBE_PPKLITE,
                isTimestampOnly ? PdfName.ETSI_RFC3161 : PdfName.ADBE_PKCS7_DETACHED);
        pdfSignature.setReason(signReason);
        pdfSignature.setLocation(signLocation);
        pdfSignature.setContact(signContact);
        pdfSignature.setDate(new PdfDate(signDate));
        pdfSignatureAppearance.setCryptoDictionary(pdfSignature);

        // certify the pdf, if requested
        if (certificationLevel > 0) {
            // check: at most one certification per pdf is allowed
            if (pdfReader.getCertificationLevel() != PdfSignatureAppearance.NOT_CERTIFIED)
                throw new Exception(
                        "Could not apply -certlevel option. At most one certification per pdf is allowed, but source pdf contained already a certification.");
            pdfSignatureAppearance.setCertificationLevel(certificationLevel);
        }

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

        pdfSignatureAppearance.preClose(exc);

        MessageDigest messageDigest = MessageDigest.getInstance(hashAlgorithm);
        InputStream rangeStream = pdfSignatureAppearance.getRangeStream();
        int i;
        while ((i = rangeStream.read()) != -1) {
            messageDigest.update((byte) i);
        }

        return messageDigest.digest();
    }

    /**
     * Add a signature to pdf document
     *
     * @param externalSignature  The extern generated signature
     * @param estimatedSize      Size of external signature
     * @throws Exception 
     */
    public void createSignedPdf(@Nonnull byte[] externalSignature, int estimatedSize) throws Exception {
        // Check if source pdf is not protected by a certification
        if (pdfReader.getCertificationLevel() == PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED)
            throw new Exception(
                    "Could not apply signature because source file contains a certification that does not allow any changes to the document");

        if (Soap._debugMode) {
            System.out.println("\nEstimated SignatureSize: " + estimatedSize);
            System.out.println("Actual    SignatureSize: " + externalSignature.length);
            System.out.println("Remaining Size         : " + (estimatedSize - externalSignature.length));
        }

        if (estimatedSize < externalSignature.length) {
            throw new IOException(
                    "\nNot enough space for signature (" + (estimatedSize - externalSignature.length) + " bytes)");
        }

        PdfLiteral pdfLiteral = (PdfLiteral) pdfSignature.get(PdfName.CONTENTS);
        byte[] outc = new byte[(pdfLiteral.getPosLength() - 2) / 2];

        Arrays.fill(outc, (byte) 0);

        System.arraycopy(externalSignature, 0, outc, 0, externalSignature.length);
        PdfDictionary dic2 = new PdfDictionary();
        dic2.put(PdfName.CONTENTS, new PdfString(outc).setHexWriting(true));
        pdfSignatureAppearance.close(dic2);

        // Save to file
        OutputStream outputStream = new FileOutputStream(outputFilePath);
        byteArrayOutputStream.writeTo(outputStream);

        if (Soap._debugMode) {
            System.out.println("\nOK writing signature to " + outputFilePath);
        }

        byteArrayOutputStream.close();
        outputStream.close();
    }

    /** 
     * Add external revocation information to DSS Dictionary, to enable Long Term Validation (LTV) in Adobe Reader
     * 
     * @param ocspArr List of OCSP Responses as base64 encoded String
     * @param crlArr  List of CRLs as base64 encoded String
     * @throws Exception 
     */
    public void addValidationInformation(ArrayList<String> ocspArr, ArrayList<String> crlArr) throws Exception {
        if (ocspArr == null && crlArr == null)
            return;

        PdfReader reader = new PdfReader(outputFilePath);

        // Check if source pdf is not protected by a certification
        if (reader.getCertificationLevel() == PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED)
            throw new Exception(
                    "Could not apply revocation information (LTV) to the DSS Dictionary. Document contains a certification that does not allow any changes.");

        Collection<byte[]> ocspColl = new ArrayList<byte[]>();
        Collection<byte[]> crlColl = new ArrayList<byte[]>();

        // Decode each OCSP Response (String of base64 encoded form) and add it to the Collection (byte[])
        if (ocspArr != null) {
            for (String ocspBase64 : ocspArr) {
                OCSPResp ocspResp = new OCSPResp(new ByteArrayInputStream(Base64.decode(ocspBase64)));
                BasicOCSPResp basicResp = (BasicOCSPResp) ocspResp.getResponseObject();

                if (Soap._debugMode) {
                    System.out.println("\nEmbedding OCSP Response...");
                    System.out.println("Status                : " + ((ocspResp.getStatus() == 0) ? "GOOD" : "BAD"));
                    System.out.println("Produced at           : " + basicResp.getProducedAt());
                    System.out.println("This Update           : " + basicResp.getResponses()[0].getThisUpdate());
                    System.out.println("Next Update           : " + basicResp.getResponses()[0].getNextUpdate());
                    System.out.println("X509 Cert Issuer      : " + basicResp.getCerts()[0].getIssuer());
                    System.out.println("X509 Cert Subject     : " + basicResp.getCerts()[0].getSubject());
                    System.out.println(
                            "Responder ID X500Name : " + basicResp.getResponderId().toASN1Object().getName());
                    System.out.println("Certificate ID        : "
                            + basicResp.getResponses()[0].getCertID().getSerialNumber().toString() + " ("
                            + basicResp.getResponses()[0].getCertID().getSerialNumber().toString(16).toUpperCase()
                            + ")");
                }

                ocspColl.add(basicResp.getEncoded()); // Add Basic OCSP Response to Collection (ASN.1 encoded representation of this object)
            }
        }

        // Decode each CRL (String of base64 encoded form) and add it to the Collection (byte[])
        if (crlArr != null) {
            for (String crlBase64 : crlArr) {
                X509CRL x509crl = (X509CRL) CertificateFactory.getInstance("X.509")
                        .generateCRL(new ByteArrayInputStream(Base64.decode(crlBase64)));

                if (Soap._debugMode) {
                    System.out.println("\nEmbedding CRL...");
                    System.out.println("IssuerDN                    : " + x509crl.getIssuerDN());
                    System.out.println("This Update                 : " + x509crl.getThisUpdate());
                    System.out.println("Next Update                 : " + x509crl.getNextUpdate());
                    System.out.println(
                            "No. of Revoked Certificates : " + ((x509crl.getRevokedCertificates() == null) ? "0"
                                    : x509crl.getRevokedCertificates().size()));
                }

                crlColl.add(x509crl.getEncoded()); // Add CRL to Collection (ASN.1 DER-encoded form of this CRL)
            }
        }

        byteArrayOutputStream = new ByteArrayOutputStream();
        PdfStamper stamper = new PdfStamper(reader, byteArrayOutputStream, '\0', true);
        LtvVerification validation = stamper.getLtvVerification();

        // Add the CRL/OCSP validation information to the DSS Dictionary
        boolean addVerification = false;
        for (String sigName : stamper.getAcroFields().getSignatureNames()) {
            addVerification = validation.addVerification(sigName, // Signature Name
                    ocspColl, // OCSP
                    crlColl, // CRL
                    null // certs
            );
        }

        validation.merge(); // Merges the validation with any validation already in the document or creates a new one.

        stamper.close();
        reader.close();

        // Save to (same) file
        OutputStream outputStream = new FileOutputStream(outputFilePath);
        byteArrayOutputStream.writeTo(outputStream);

        if (Soap._debugMode) {
            if (addVerification)
                System.out.println("\nOK merging LTV validation information to " + outputFilePath);
            else
                System.out.println("\nFAILED merging LTV validation information to " + outputFilePath);
        }

        byteArrayOutputStream.close();
        outputStream.close();
    }
}