Multiple signatures with Itext - issue with existi

2019-09-02 08:49发布

问题:

So im currently working on a project to sign PDF documents and I ran into a problem. So i can sign documents correctly without any problems if I specify the block to sign i.e.

signatureAppearance.setVisibleSignature(rectangle, page, signingBlockName);

I can add multiple signatures without a problem and all of the signatures remain valid. Now I have changed the code to first add empty signature blocks and then sign these blocks using the signature block name that i've added i.e.

signatureAppearance.setVisibleSignature(signingBlockName);

The reason for this is that we are going to generate PDF documents with signature fields already in place(there can be multiple) but the problem here is that since i've started using this method of signing the signature blocks, the second signature invalidates the first one even though the signing is done in append mode, and only the last signature has the PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED certification level.

Am i missing something or is there a bug in the older version of IText? Im currently using IText version 4.2.0.

Thanks in advance.

------Code-------------

 /**
 * The method is used to sign a pdf document using a certificate, image of the signature as well as specified signature blocks
 *
 * @param document the document to be signed
 * @param certificate the certificate to be used to do the signing
 * @param signature the image of the signature used to sign the document
 * @param signatureBlocks the blocks in the pdf document to sign
 * @return the signed document bytes
 * @throws Exception
 */
public byte[] signPDFDocument(PDFDocument document, PKSCertificate certificate, SignatureImage signature, List<SigningBlock> signatureBlocks, boolean certifyDocument) throws Exception
{
    document.addSignatureBlocks(signatureBlocks);
    PDFDocument signedDocument = signPDFDocumentSignatureBlocks(document, certificate, signature, signatureBlocks, certifyDocument);
    return signedDocument.getDocumentBytes();
}

/**
 * The method is used to sign a pdf document using a certificate, image of the signature as well as specified signature blocks
 *
 * @param document the document to be signed
 * @param certificate the certificate to be used to do the signing
 * @param signatureBlocks the blocks in the pdf document to sign
 * @return the signed document bytes
 * @throws Exception
 */
public byte[] signPDFDocument(PDFDocument document, PKSCertificate certificate, List<SigningBlock> signatureBlocks, boolean certifyDocument) throws Exception
{
    document.addSignatureBlocks(signatureBlocks);
    PDFDocument signedDocument = signPDFDocumentSignatureBlocks(document, certificate, null, signatureBlocks, certifyDocument);
    return signedDocument.getDocumentBytes();
}

/**
 * The method is used to get the names of all signature fields
 *
 * @param document the document to check for the signature fields
 * @return the list of signature field names used and unused
 * @throws Exception
 */
public List<String> getAvailableSignatureBlocks(PDFDocument document) throws Exception
{
    PdfReader reader = new PdfReader(document.getDocumentBytes());
    ArrayList arrayList = reader.getAcroFields().getBlankSignatureNames();
    return Arrays.asList((String[]) arrayList.toArray(new String[arrayList.size()]));
}

/**
 * This method is used to loop over the signature blocks and sign them
 *
 * @param document the document to be signed
 * @param certificate the certificate to apply to the signature
 * @param signature the image of the client signature
 * @param signatureBlocks the signature blocks to create and sign
 * @param certifyDocument flag to indicate if the document should be signed
 * @throws Exception
 */
private PDFDocument signPDFDocumentSignatureBlocks(PDFDocument document, PKSCertificate certificate, SignatureImage signature, List<SigningBlock> signatureBlocks, boolean certifyDocument) throws Exception
{
    for (int i = 0; i < signatureBlocks.size();i++)
    {
        PDFDocument signedDocument = new PDFDocument(new ByteArrayOutputStream());
        PdfReader reader = new PdfReader(document.getDocumentBytes());
        PdfStamper stamper = createPDFStamper(signedDocument, reader);
        document = signPDFDocumentSignatureBlock(signedDocument, certificate, signature, signatureBlocks.get(i), stamper, certifyDocument && i == (signatureBlocks.size() - 1));
    }

    return document;
}

/**
 * The method is used to sign the pdf document, it also marks the signing process if it is the final signature process
 *
 * @param signedDocument the signed document to be generated
 * @param certificate the certificate to be used to do the signing
 * @param signature the image of the signature used to sign the document
 * @param signingBlock the current block to sign in the document
 * @param stamper the current document stamper reference
 * @param certifyDocument indicate if this signing should certify the document
 * @return the signed document object
 * @throws Exception
 */
private PDFDocument signPDFDocumentSignatureBlock(PDFDocument signedDocument, PKSCertificate certificate, SignatureImage signature, SigningBlock signingBlock, PdfStamper stamper, boolean certifyDocument) throws Exception
{
    PdfSignatureAppearance appearance = signWithSignatureImage(stamper, signature, signingBlock.getName(), certifyDocument);
    signWithCertificate(certificate, appearance);
    signWithTimeStampingAuthority(appearance, certificate);

    signedDocument.updateBytesFromByteStream();
    return signedDocument;
}

/**
 * The method is used to get the instance of the PDF stamper to stamp the document
 *
 * @param signedDocument the document that is currently being signed
 * @param reader the reader that is reading the document to sign.
 * @return the stamper instance
 * @throws Exception
 */
private PdfStamper createPDFStamper(PDFDocument signedDocument, PdfReader reader) throws Exception
{
    return PdfStamper.createSignature(reader, signedDocument.getByteStream(), '\0', null, true);
}

/**
 * The method is used to add the signature image to the signing block
 *
 * @param stamper the current pdf stamping reference
 * @param signature the image to apply to the stamper
 * @param signingBlockName the block to sign
 * @param certifyDocument indicate if this signing should certify the document
 * @throws Exception
 */
private PdfSignatureAppearance signWithSignatureImage(PdfStamper stamper, SignatureImage signature, String signingBlockName, boolean certifyDocument) throws Exception
{
    PdfSignatureAppearance signatureAppearance = stamper.getSignatureAppearance();
    signatureAppearance.setVisibleSignature(signingBlockName);

    setImageForSignature(signatureAppearance, signature);
    certifyDocumentSignature(signatureAppearance, certifyDocument);

    return signatureAppearance;
}

/**
 * The method is used to add an image to the signature block
 *
 * @param signatureAppearance the reference to the current document appearance
 * @param signature the image to apply to the signature
 * @throws Exception
 */
private void setImageForSignature(PdfSignatureAppearance signatureAppearance, SignatureImage signature) throws Exception
{
    if(signature != null)
    {
        signatureAppearance.setImage(Image.getInstance(signature.getSignatureImage(), null));
    }
}

/**
 * The method is used to mark the signature as the certification signature
 *
 * @param signatureAppearance the reference to the current document appearance
 * @param certifyDocument indicates if the document should be certified
 */
private void certifyDocumentSignature(PdfSignatureAppearance signatureAppearance, boolean certifyDocument)
{
    if(certifyDocument)
    {
        signatureAppearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
    }
}

/**
 * The method is used to add the text containing information about the certificate to the signing appearance
 *
 * @param certificate the certificate to be used to do the signing
 * @param signatureAppearance the appearance of the signature on the document
 * @throws Exception
 */
private void signWithCertificate(PKSCertificate certificate, PdfSignatureAppearance signatureAppearance) throws Exception
{
    signatureAppearance.setLayer2Text(buildTextSignature(certificate));
    signatureAppearance.setLayer2Font(new Font(Font.COURIER, 9));
    signatureAppearance.setAcro6Layers(true);
    signatureAppearance.setRender(PdfSignatureAppearance.SignatureRenderDescription);
}

/**
 * The method is used to encrypt the document using the certificate as well as a timestamping authority
 *
 * @param appearance the appearance of the signature on the document
 * @param certificate the certificate to be used to do the signing
 * @throws Exception
 */
private void signWithTimeStampingAuthority(PdfSignatureAppearance appearance, PKSCertificate certificate) throws Exception
{
    _timestampingService.signWithTimestampingAuthority(appearance, certificate);
}

/**
 * The method builds the text that is used in the text representation of the signature
 *
 * @param certificate the certificate to be used to do the signing
 * @return the text representation of certificate information
 * @throws Exception
 */
private String buildTextSignature(PKSCertificate certificate) throws Exception
{
    String organization = certificate.getCertificateFieldByName("O");
    String commonName = certificate.getCertificateFieldByName("CN");
    String signDate = new SimpleDateFormat(_datetimeFormat).format(Calendar.getInstance().getTime());
    String expirationDate = new SimpleDateFormat(_datetimeFormat).format(((X509Certificate)certificate.getCertificateChain()[0]).getNotAfter());

    return  "Digitally signed by " + organization + "\nSignee: " + commonName + "\nSign date: " + signDate + "\n" + "Expiration date: " + expirationDate;
}

回答1:

the second signature invalidates the first one even though the signing is done in append mode, and only the last signature has the PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED certification level.

As already conjectured in an initial comment, this actually turned out to be the problem: Only the first signature of a document may be a certification signature. Later signature may only (PDF 2.0) restrict the access permissions using a signature field lock entry, cf. the specification:

A PDF document may contain [...]

At most one certification signature (PDF 1.5). [...] The signature dictionary shall contain a signature reference dictionary (see Table 253) that has a DocMDP transform method. [...]

A document can contain only one signature field that contains a DocMDP transform method; it shall be the first signed field in the document.

(sections 12.8.1 and 12.8.2.2.1 of ISO 32000-1)

In the upcoming version 2.0 of the PDF standard (ISO-32000-2) it will be possible to restrict access permissions using a signature field lock entry:

P number (Optional; PDF 2.0) The access permissions granted for this document. Valid values follow:

1 no changes to the document are permitted; any change to the document invalidates the signature.

2 permitted changes are filling in forms, instantiating page templates, and signing; other changes invalidate the signature.

3 permitted changes are the same as for 2, as well as annotation creation, deletion, and modification; other changes invalidate the signature.

There is no default value; absence of this key shall result in no effect on signature validation rules.

If MDP permission is already in effect from an earlier incremental save section or the original part of the document, the number shall specify permissions less than or equal to the permissions already in effect based on signatures earlier in the document. That is, permissions can be denied but not added. If the number specifies greater permissions than an MDP value already in effect, the new number is ignored.

If the document does not have an author signature, the initial permissions in effect are those based on the number 3.

The new permission applies to any incremental changes to the document following the signature of which this key is part.

(Table "Entries in a signature field lock dictionary" of a PDF 2.0 draft)

This already is supported by Adobe Reader/Acrobat and also by iText (since some 5.3'ish version IIRC) and seems to allow the effect the OP is after, i.e. disallowing any changes after the final signature field.



标签: java pdf itext