使用Itext的多个签名 - 现有签名块的问题 [英] Multiple signatures with Itext - issue with existing signature blocks

查看:998
本文介绍了使用Itext的多个签名 - 现有签名块的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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);

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);

signatureAppearance.setVisibleSignature(signingBlockName);

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

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.

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

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

提前致谢。

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

------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;
}


推荐答案


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

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

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:


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

A PDF document may contain [...]

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

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. [...]

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

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

ISO 32000-1

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

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 (可选; PDF 2.0)为此文档授予的访问权限。有效值如下:

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

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

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

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

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

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

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.

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

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.

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

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.

(表格PDF 2.0草稿中的签名字段锁定字典中的条目)

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

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.

这篇关于使用Itext的多个签名 - 现有签名块的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆