1

I am dynamically creating a PDF document, add a signature field and afterwards trying to sign it. The signing works properly, but i am getting an exception:

"certfied by %, invalid signature and signature contains invalid data"

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.Calendar;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PrivateKeySignature;

//Actual class 
public class SignPdf {

    // Signs the pdf
    public void signPdf(String src, String dest, boolean certified, boolean graphic) throws GeneralSecurityException, IOException, DocumentException{
        String path = "src/certs.pfx";
        String keystore_password = "pwd";
        String key_password = "pwd";
        KeyStore ks = KeyStore.getInstance("PKCS12", new org.bouncycastle.jce.provider.BouncyCastleProvider());
        InputStream is = this.getClass().getResourceAsStream("/certs.pfx");
        ks.load(is, keystore_password.toCharArray());

        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, key_password.toCharArray());
        Certificate[] chain = ks.getCertificateChain(alias);
        byte[] pdfByteArray = null;
        // creating dynamic document using itext.
        Document document = new Document();
        OutputStream baosPDF = new ByteArrayOutputStream();
        PdfWriter.getInstance(document, baosPDF);
        document.open();
        document.add(new Paragraph("Hello World!"));
        document.close();
        byte[] bytearrayb = ((ByteArrayOutputStream) baosPDF).toByteArray();
        PdfReader reader = new PdfReader(bytearrayb);
        PdfStamper stamper = PdfStamper.createSignature(reader, baosPDF, '\0', null, true);

        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setReason("Security");
        appearance.setLocation("Footer");
        appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, "DVA");
        if (certified) appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
        if (graphic) {
            appearance.setSignatureGraphic(Image.getInstance(RESOURCE));
            appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
        }
        appearance.setSignDate(Calendar.getInstance());
        appearance.setSignatureCreator("test");
        ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", "BC");
        ExternalDigest digest = new BouncyCastleDigest();
        MakeSignature.signDetached(appearance, digest, es, chain, null, null, null, 0, CryptoStandard.CMS);
        baosPDF.flush();
        baosPDF.close();

        BufferedOutputStream fs = new BufferedOutputStream(new FileOutputStream(new File("myFile121.pdf")));
        fs.write(((ByteArrayOutputStream) baosPDF).toByteArray());
        fs.flush();
        fs.close();
    }

    public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());
        SignPdf signatures = new SignPdf();
        try {
            signatures.signPdf(ORIGINAL, "", true, false);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Lonzak
  • 9,334
  • 5
  • 57
  • 88
Ajay kuar
  • 13
  • 1
  • 4
  • Its signing properly, but i am getting an exception "certfied by %, invalid signature and signature contains invalid data" – Ajay kuar Jan 26 '15 at 11:36
  • You are mixing a newer version of iText (`import com.itextpdf`) with an obsolete version of iText (`import com.lowagie`). Please use the newer version of iText only. Due to your inconsistent use of the library, many unpredictable errors can occur. That makes your question unanswerable. – Bruno Lowagie Jan 26 '15 at 11:43
  • Thanks very much. I used the correct import this time (com.itextpdf.Document,Paragraph,PDFwrite).And i removed the older version of jar. It now compiles successfully, but when i exceuted it i am getting the same exception. – Ajay kuar Jan 26 '15 at 12:43
  • Please clean up your question. Make sure the code is displayed well (use the correct indentation) and move the actual question from your comment to the body of the question. It is very difficult to read your question. One thing that jumps to the eye is the fact that you reuse `baosPDF` without reinitializing the `ByteArrayOutputStream`. That looks like a very bad idea. – Bruno Lowagie Jan 26 '15 at 13:01

1 Answers1

1

You re-use the ByteArrayOutputStream without clearing it:

OutputStream baosPDF = new ByteArrayOutputStream();
PdfWriter.getInstance(document, baosPDF);
document.open();
document.add(new Paragraph("Hello World!"));
document.close();
byte[] bytearrayb = ((ByteArrayOutputStream) baosPDF).toByteArray();
PdfReader reader = new PdfReader(bytearrayb);
PdfStamper stamper = PdfStamper.createSignature(reader, baosPDF, '\0', null, true);

According to the ByteArrayOutputStream source:

/**
 * Resets the <code>count</code> field of this byte array output 
 * stream to zero, so that all currently accumulated output in the 
 * output stream is discarded. The output stream can be used again, 
 * reusing the already allocated buffer space. 
 *
 * @see     java.io.ByteArrayInputStream#count
 */
public synchronized void reset()

Thus, reset before re-use:

byte[] bytearrayb = ((ByteArrayOutputStream) baosPDF).toByteArray();
baosPDF.reset();
PdfReader reader = new PdfReader(bytearrayb);
PdfStamper stamper = PdfStamper.createSignature(reader, baosPDF, '\0', null, true);
mkl
  • 90,588
  • 15
  • 125
  • 265