iTextPdf:为什么在验证签名时误导修订数量?

iTextPdf: why misleading number of revisions when verifiying signature?

我正在使用 iTextPdf 对 PDF 进行签名和完整性检查,由 Alfresco 提供支持

这是签名代码:

public void signItem(NodeRef itemToSign, String signer) {

       try{
        // retrieving user's public and private key
        Certificate chain[] = getCertificate(signer);
        PrivateKey pk = getPrivateKey(signer);

        String digestAlgorithm = DigestAlgorithms.SHA512;
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);

        // Getting content of item to sign
        InputStream originalInputStream = getNodeRefInputStream(itemToSign);
        PdfReader pdfReader = new PdfReader(originalInputStream);

        // get an outputStream on the item to sign nodeRef and give to the
        // pdfStamper
        ByteArrayOutputStream outputStream = getNodeRefOutputStream(itemToSign);
        // logger.info("Before" + outputStream);

        PdfStamper pdfStamper = PdfStamper.createSignature(pdfReader, outputStream, '[=10=]', new File("temp"), true);

        // Creating the appearance
        PdfSignatureAppearance appearance = pdfStamper.getSignatureAppearance();
        appearance.setReason("freeze");
        appearance.setLocation("koosserydesk");
        appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "signature space");

        // the sign document is subject to future approval signatures
        appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_FORM_FILLING);

        // Creating the signature
        ExternalDigest digest = new BouncyCastleDigest();
        ExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, provider.getName());
        // signing...
        MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, CryptoStandard.CMS);


        // get the signed input stream
        InputStream signedInputStream = new ByteArrayInputStream(outputStream.toByteArray());

        // replace the itemToSign content with the signed content
        ContentWriter writer = getWriter(itemToSign);
        writer.putContent(signedInputStream);
} catch (Exception e) {

        // do something

    }

}

这是完整性检查的代码

public void checkDocIntegrity(NodeRef itemToSign) throws KoosseryDeskServerException {
    /** check the integrity of the document **/

    ArrayList<String> signatureNames;
    PdfPKCS7 pkcs7;
    boolean result = false;
    try {
        InputStream is = getNodeRefInputStream(itemToSign);
        PdfReader reader = new PdfReader(is);
        AcroFields fields = reader.getAcroFields();

        signatureNames = fields.getSignatureNames();
        String name = signatureNames.get(0);
        System.out.println("Siganture names = " + signatureNames);
        System.out.println("Document revision: " + fields.getRevision(name) + " of " + fields.getTotalRevisions());
        pkcs7 = fields.verifySignature(name);

        result = pkcs7.verify();
        System.out.println("Is the document integrity check OK? : "+result);
    } catch (Exception e) {

        // do something

    }

}

当我运行对使用上述 signItem 函数签名的文档进行完整性检查时,我总是得到这个输出:

Siganture names = [signature space] 
Document revision: 1 of 2
Is the document integrity check OK? : false

我猜完整性检查总是错误的,因为在签名后添加了第二次修订,但是: 我不知道为什么我会得到两个文件修订版,但我没有添加任何注释或其他批准签名。

请告诉我我做错了什么? 谢谢!

简而言之

看起来你的方法 getNodeRefOutputStream return 是一个 ByteArrayOutputStream 已经包含 原始文档的副本,或者您的方法 getWriter return 是一个 ContentWriter 附加 到现有内容而不是 替换 它。

结果是最终结果文档是(A)原始文档和(B)原始文档加上签名的拼接。

要解决此问题,请更改或替换对 return 对象的错误方法调用,该对象有效地 替换了 压模输出的原始内容。

详细

分析您的 PDF 后很快就会发现它有些破损,而不是预期的 两次 修订(首先是原始 PDF,然后是为签署它而创建的附加内容)它实际上由 部分组成(首先是原始 PDF,然后是原始 PDF,然后是为使用交叉引用对其进行签名而创建的附加内容,就好像原始部分在前面但一次)。

效果是

  • 聚合的交叉引用是错误的,它们指向原始文档的第二个副本中的不适当位置,而不是实际添加的对象,并且 startxref 指针指向第二个副本中的某处原始PDF的副本;因此,Adobe Reader 在后台“修复”了关闭文档时引发“是否要保存更改”对话框的 PDF;
  • 签名字节范围的差距在原始文档的第二个副本中的某处,特别是不包含编码的签名字节;因此,Adobe Reader - 期待那个间隙中的签名 - 表示 此签名中包含的格式或信息有错误
  • 带符号的字节范围有点进入原始文件的第二个副本,导致 iText 报告两次修订,首先是带符号字节范围末尾的数据,然后是超出的数据;和
  • 签名的哈希值被破坏,因为签名的字节范围不包含原始文档加上除了实际签名字节之外的签名添加,而是原始文档加上原始文档的第二个副本的一些奇怪部分文档;这会导致 iText 验证失败。

(您可能需要阅读信息安全堆栈交换上的 this answer 以了解详细信息。)

这种行为在 iText classes 中闻所未闻。因此,它似乎是由您的代码引起的。

查看您发布的原始文档的重复代码很可能是由于您的代码造成的

  • 标记到 ByteArrayOutputStream return 由 getNodeRefOutputStream 编辑(如果该流是用原始文档的副本初始化的)或
  • 将结果 PDF 写入由 getWriter 编辑的 ContentWriter return(如果 class 的 putContent 方法实际上附加到现有内容).

因此,我建议不要将 outputStream 设置为由 getNodeRefOutputStream 编辑的 ByteArrayOutputStream return,而是将 outputStream 设置为空 new ByteArrayOutputStream();如果这没有帮助,我建议寻找 getWriterContentWriter.putContent.

的替代方案