1
votes

I am trying to add multiple signatures to my pdf using pdfbox, I am trying with two signatures and second signature always turns to be invalid.Please advice, Can a be PDF be signed digitally several times? Adobe livecycle forums says yes.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.List;

import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.exceptions.SignatureException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSSignedGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class SigningTest implements SignatureInterface {

    private static BouncyCastleProvider provider = new BouncyCastleProvider();

    private PrivateKey privKey;

    private Certificate[] cert;

    public SigningTest(KeyStore keystore, char[] pin) {
        try {
            Enumeration<String> aliases = keystore.aliases();
            String alias = null;
            if (aliases.hasMoreElements())
                alias = aliases.nextElement();
            else
                throw new RuntimeException("Could not find Key");
            privKey = (PrivateKey) keystore.getKey(alias, pin);
            cert = keystore.getCertificateChain(alias);
        } catch (KeyStoreException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public byte[] sign(InputStream content) throws SignatureException,
            IOException {
        CMSProcessableInputStream input = new CMSProcessableInputStream(content);
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        // CertificateChain
        List<Certificate> certList = Arrays.asList(cert);

        CertStore certStore = null;
        try {
            certStore = CertStore.getInstance("Collection",
                    new CollectionCertStoreParameters(certList), provider);
            gen.addSigner(privKey, (X509Certificate) certList.get(0),
                    CMSSignedGenerator.DIGEST_SHA256);
            gen.addCertificatesAndCRLs(certStore);
            CMSSignedData signedData = gen.generate(input, false, provider);
            return signedData.getEncoded();
        } catch (Exception e) {
            // should be handled
            e.printStackTrace();
        }
        throw new RuntimeException("Problem while preparing signature");
    }

    public static void main(String[] args) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException,
            FileNotFoundException, IOException, COSVisitorException,
            SignatureException, Exception {
        File document = new File("resources/OCD.pdf");
        PDDocument pdDocument = PDDocument.load(document);

        addSignature(pdDocument, "resources/j4l_test.p12", "test");
        addSignature(pdDocument, "resources/my.p12", "123456");

        File outputDocument = new File("resources/signed" + document.getName());
        FileInputStream fis = new FileInputStream(document);
        FileOutputStream fos = new FileOutputStream(outputDocument);
        byte[] buffer = new byte[8 * 1024];
        int c;
        while ((c = fis.read(buffer)) != -1)
        {
          fos.write(buffer, 0, c);
        }
        fis.close();
        fis = new FileInputStream(outputDocument);

        pdDocument.saveIncremental(fis, fos);
        //pdDocument.close();

    }




    static void addSignature(PDDocument  pdDocument,  String filePath,String pwd)  throws Exception{
        File ksFile = new File(filePath);
        KeyStore keystore = KeyStore.getInstance("PKCS12", provider);
        char[] pin = pwd.toCharArray();
        keystore.load(new FileInputStream(ksFile), pin);
        SigningTest signing = new SigningTest(keystore, pin.clone());
        //signing.signPDF(document);

        // create signature dictionary
        PDSignature signature = new PDSignature();
        signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
        // subfilter for basic and PAdES Part 2 signatures
        signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
        signature.setName("signer name");
        signature.setLocation("signer location");
        signature.setReason("reason for signature");

        // the signing date, needed for valid signature
        signature.setSignDate(Calendar.getInstance());
//      SignatureOptions signatureOptions= new SignatureOptions();
//      signatureOptions.setVisualSignature();
        // register signature dictionary and sign interface
        pdDocument.addSignature(signature, signing);

    }
}

class CMSProcessableInputStream implements CMSProcessable {

    InputStream in;

    public CMSProcessableInputStream(InputStream is) {
        in = is;
    }

    public Object getContent() {
        return null;
    }

    public void write(OutputStream out) throws IOException, CMSException {
        // read the content only one time
        byte[] buffer = new byte[8 * 1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
        in.close();
    }
}
1

1 Answers

3
votes

Can a be PDF be signed digitally several times

Yes, it can, but that has to happen in consecutive incremental updates, not in the same revision.

You do

PDDocument pdDocument = PDDocument.load(document);
addSignature(pdDocument, "resources/j4l_test.p12", "test");
addSignature(pdDocument, "resources/my.p12", "123456");
...
pdDocument.saveIncremental(fis, fos);

But you'd have to do

PDDocument pdDocument = PDDocument.load(document);
addSignature(pdDocument, "resources/j4l_test.p12", "test");
...
pdDocument.saveIncremental(fis, fos); // fos an intermediary file

...

pdDocument = PDDocument.load(intermediaryFile);
addSignature(pdDocument, "resources/my.p12", "123456");
...
pdDocument.saveIncremental(fis, fos); // fos the final file

Some backgrounds can be found in this answer and the links therein.