使用Itext的多个签名 - 现有签名块的问题

时间:2015-06-09 06:27:12

标签: java pdf itext

所以我目前正在开展一个签署PDF文档的项目,但我遇到了一个问题。因此,如果我指定要签名的块,即

,我可以正确签署文档而不会出现任何问题

signatureAppearance.setVisibleSignature(rectangle,page,signingBlockName);

我可以毫无问题地添加多个签名,并且所有签名都保持有效。现在我已经将代码更改为首先添加空签名块,然后使用我添加的签名块名称对这些块进行签名,即

signatureAppearance.setVisibleSignature(signingBlockName);

这样做的原因是我们将生成带有签名字段的PDF文档(可能有多个),但问题在于,因为我已经开始使用这种签名块的签名方法,所以即使签名是在追加模式下完成的,签名也会使第一个签名无效,只有最后一个签名具有PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED认证级别。

我错过了什么或旧版本的IText中是否有错误? 我目前正在使用IText版本4.2.0。

提前致谢。

------代码-------------

 /**
 * 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 个答案:

答案 0 :(得分:1)

  

第二个签名使第一个签名无效,即使签名是在追加模式下完成的,只有最后一个签名具有PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED认证级别。

正如在初步评论中已经推测的那样,这实际上是问题所在:只有文档的第一个签名可能是证书签名。以后签名可能(PDF 2.0)使用签名字段锁定条目限制访问权限, cf.规范:

  

PDF文档可能包含[...]

     

最多一个认证签名(PDF 1.5)。 [...]签名字典应包含具有DocMDP转换方法的签名参考字典(参见表253)。 [...]

     

文档只能包含一个包含DocMDP转换方法的签名字段;它应该是文件中第一个签名的字段。

     

ISO 32000-1的第12.8.1和12.8.2.2.1节)

在即将发布的PDF标准版本2.0(ISO-32000-2)中,可以使用签名字段锁定条目来限制访问权限:

  

P 号码(可选; PDF 2.0)为此文档授予的访问权限。有效值如下:

     

1不允许对文件进行任何更改;对文档的任何更改都会使签名无效。

     

2个允许的更改填写表单,实例化页面模板和签名;其他更改使签名无效。

     

3个允许的更改与2相同,以及注释创建,删除和修改;其他更改使签名无效。

     

没有默认值;如果没有此密钥,则不会对签名验证规则产生任何影响。

     

如果MDP权限已从先前的增量保存部分或文档的原始部分生效,则该数字应指定小于或等于基于文档中较早的签名已生效的权限的权限。也就是说,可以拒绝权限但不添加权限。如果该数字指定的权限大于已生效的MDP值,则忽略新数字。

     

如果文档没有作者签名,则有效的初始权限是基于数字3的权限。

     

新权限适用于此密钥所属签名后文档的任何增量更改。

     

(表格“PDF 2.0草案的签名字段锁定词典中的条目”)

这已经得到Adobe Reader / Acrobat以及iText的支持(因为某些版本为5.3'ish版本IIRC)并且似乎允许OP效应,即在最终签名字段后不允许任何更改。