Digitally Signing a signed document using iText 5

Viewed 1046

Hi I am able to digitally sign a PDF document using iText 5. I have a requirement of signing the PDF again, whereas while validating the PDF it shows that the initial signature is invalid. You can view the file here which was signed again.

See below the code used for sigining,

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;

import com.itextpdf.text.DocumentException;
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.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.PrivateKeySignature;

public class Test {

    

    public static void main(String[] args) throws DocumentException, IOException, GeneralSecurityException {

        PdfReader reader = null;
        PrivateKey pk = null; 
        String alias = "PRASANTH KARUNAKARAN NAIR"; 

        KeyStore ks = null; 
        try { 
            ks = KeyStore.getInstance("Windows-MY", "SunMSCAPI"); 
        } 
        catch (KeyStoreException|java.security.NoSuchProviderException e4){ 
            e4.printStackTrace(); 
        }  
        try { 
            ks.load(null, null); 
        } 
        catch (NoSuchAlgorithmException|java.security.cert.CertificateException|IOException e4){ 
            e4.printStackTrace(); 
        } 

        try { 
            pk = (PrivateKey)ks.getKey(alias, "abcd".toCharArray()); 
        } 

        catch (UnrecoverableKeyException|KeyStoreException|NoSuchAlgorithmException e3){ 
            e3.printStackTrace(); 
        }  
        Certificate[] chain = null; 
        try { 
            chain = ks.getCertificateChain(alias); 
        } 
        catch (KeyStoreException e3){ 
            e3.printStackTrace(); 
        }  

        
        try {
            reader = new PdfReader("D:///signedSample.pdf"); 
        } 
        catch (IOException e5){ 
            e5.printStackTrace(); 
        }  
        String signedFileNameWithPath = "D:///signedsignedSample.pdf"; 
        FileOutputStream os = null; 
        try { 
            os = new FileOutputStream(signedFileNameWithPath); 
        } 
        catch (FileNotFoundException e5){ 
            e5.printStackTrace();
        }
        PdfStamper stamper = null; 
        
        
        try {
            stamper = PdfStamper.createSignature(reader, os,'\0');
        } 
        catch (DocumentException|IOException e5) {
            e5.printStackTrace(); 
        } 
        
        
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        Integer pageNumber = 2; 
        Rectangle rect=new Rectangle(50,100,220,140);
        appearance.setAcro6Layers(false);
        appearance.setLayer4Text(PdfSignatureAppearance.questionMark);
        appearance.setVisibleSignature(rect,pageNumber, "sig2");

        PrivateKeySignature privateKeySignature=null;
        try {
            privateKeySignature= new PrivateKeySignature(pk, "SHA-256", ks.getProvider().getName()); 
        }
        catch (NullPointerException e) {
        }
        if(privateKeySignature!=null) {
            BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest(); 
            try { 
                MakeSignature.signDetached(appearance, (ExternalDigest)bouncyCastleDigest, (ExternalSignature)privateKeySignature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
            } 
            catch (IOException e1){
                e1.printStackTrace(); 
            }
            catch (DocumentException e1){
                e1.printStackTrace(); 
            } 
            catch (SignatureException e1) {
                e1.printStackTrace(); 
            } 
            catch (GeneralSecurityException e1){ 
                e1.printStackTrace(); 
            }  
        }
        
    }

}

Please let me know what went wrong.

1 Answers

If you want to add another signature to an already signed PDF, you obviously must take care and not change any signed byte. In other words, additions and changes must be appended to the existing file as an incremental update.

A PDF with three signatures, for example, schematically must look similar to this:

(For backgrounds read this answer and documents referenced from it.)

By default, though, the iText PdfStamper does not use incremental updates but instead creates a completely new file using the individual objects from the original file in a possibly completely changed order and with objects dropped not needed in the new versions anymore. This of course renders the first signature invalid.

To create a PdfStamper that works using an incremental update, you have to use a different PdfStamper.createSignature overload. Please replace

stamper = PdfStamper.createSignature(reader, os,'\0');

by

stamper = PdfStamper.createSignature(reader, os, '\0', null, true);

The extra parameters are documented like this in the method JavaDocs:

/* @param reader the original document
 * @param os the output stream or <CODE>null</CODE> to keep the document in the temporary file
 * @param pdfVersion the new pdf version or '\0' to keep the same version as the original
 * document
 * @param tempFile location of the temporary file. If it's a directory a temporary file will be created there.
 *     If it's a file it will be used directly. The file will be deleted on exit unless <CODE>os</CODE> is null.
 *     In that case the document can be retrieved directly from the temporary file. If it's <CODE>null</CODE>
 *     no temporary file will be created and memory will be used
 * @param append if <CODE>true</CODE> the signature and all the other content will be added as a
 * new revision thus not invalidating existing signatures
Related