0
votes

I want to sign a pdf with multiple signatures but i can only sign the pdf with one. Im using the Itext Library.

public static void sign(InputStream src,OutputStream dest, InputStream p12Stream, char[] password, String reason, String location, String imagePath) throws Exception {
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(p12Stream, password);
        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
        Certificate[] chain = ks.getCertificateChain(alias);

        PdfReader reader = new PdfReader(src);
        PdfStamper stamper = PdfStamper.createSignature(reader, dest, '\0', null, true);
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setReason(reason);
        appearance.setLocation(location);
        appearance.setVisibleSignature(new Rectangle(300, 600, 630, 500), 1, "sig");

        Image image = Image.getInstance(imagePath);
        appearance.setSignatureGraphic(image);
        appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

        appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);

        ExternalDigest digest = new BouncyCastleDigest();
        ExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, null);
        MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
    }

    public static void main(String[] args) throws Exception {
        sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
        sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");
    }

I already tried using the append mode as true and removing the image signing but it only shows as one signature. image showing pdf signed

1
First of all - your first sign call uses the same file for input and output. This cannot work. Furthermore, you want to add two signatures with certification levels. But only the first signaturesmkl
i know, its signed.pdf on output, but i still cant make more than one signature in the filePaulo Gonçalves
After your change you now take the unsigned pdf as input for both signing attempts. For the second signing attempt you need to take the output if the first attempt as input and write to yet another file.mkl

1 Answers

0
votes

There are a number of errors in your approach

Your main method

The Original main Code

In your original code your main method contained these two calls:

sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"nonsigned.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");

In particular in your first line you created a FileInputStream and a FileOutputStream for the same file name. Doing the latter truncates the file, so when the code in your sign method attempts to read the file from the former stream, it doesn't find anything and throws an exception.

Thus, you must not use the same file as both input and output of your signing procedure.

The Edited main Code

You then edited your main method to contain these two calls:

sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signed.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");

Now both calls read the unsigned PDF, sign it, and write the result to the same output file. Thus, the first call creates a PDF with a single signature and the second call also creates a PDF with a single signature and its output overwrites the output of the first call.

Thus, you must use the output of the first signing call as input of the second signing call.

For example like this:

sign(new FileInputStream(basePath+"nonsigned.pdf"), new FileOutputStream(basePath+"signedOnce.pdf"), new FileInputStream(basePath+"mycert3.p12"), "mycert3".toCharArray(), "something", "something", basePath + "signing1.png");
sign(new FileInputStream(basePath+"signedOnce.pdf"), new FileOutputStream(basePath+"signedTwice.pdf"), new FileInputStream(basePath+"mycert4.p12"), "mycert4".toCharArray(), "something", "something", basePath + "signing2.png");

Your sign Method

Having made sure that no signing call uses the same file both as input and output and that the second signing call uses the output of the first call as input, we now run into the issues of your sign method used multiple times. (For one-time use it's ok.)

Signature Field Names

In your sign method you have hard-coded the signature field name:

appearance.setVisibleSignature(new Rectangle(300, 600, 630, 500), 1, "sig");

I.e. each call tries to sign the same signature field. According to the JavaDocs of the setVisibleSignature method you use, though:

/**
 * Sets the signature to be visible. It creates a new visible signature field.
 * @param pageRect the position and dimension of the field in the page
 * @param page the page to place the field. The fist page is 1
 * @param fieldName the field name or <CODE>null</CODE> to generate automatically a new field name
 */
public void setVisibleSignature(Rectangle pageRect, int page, String fieldName)

So this method tries to create a new visible signature field. As each field has a distinct name, using the same name twice is an error.

Thus, you must make sure that you use different signature field names. A simple option for that is described in the JavaDoc description of the fieldName parameter: If you use null, iText automatically creates a new signature name. So, simply replace "sig" by null in your code line above.

Certification Signatures

In your sign method you set the certification level like this:

appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);

This causes two issues:

  1. CERTIFIED_NO_CHANGES_ALLOWED means what it says: No changes allowed. Signing a second time is a change. So using this certification level forbids signing again. For details on allowed and disallowed changes to a signed PDF read this answer.

    Thus, you must use a certification level which does not forbid creating the second signature.

  2. A document may only contain a single certification signature and any number of approval signatures (regular signatures without a certification level).

    Thus, you must make sure you set the signature appearance certification level only for your first signing call for the document in question.