java - iTextPdf : why misleading number of revisions when verifiying signature?

标签 java itext alfresco bouncycastle

我使用 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, '\0', 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返回一个ByteArrayOutputStream,其中已经包含要开始的原始文档的副本,或者您的方法getWriter 返回一个 ContentWriter,它附加到现有内容,而不是替换现有内容。

结果是,最终结果文档是(A)原始文档和(B)原始文档加签名的串联。

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

详细

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

效果是这样的

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

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

对于 iText 类来说,这种行为是闻所未闻的。因此,这似乎是由您的代码引起的。

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

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

因此,我建议不要将 outputStream 设置为 getNodeRefOutputStream 返回的 ByteArrayOutputStream 来设置 outputStream到一个空的new ByteArrayOutputStream();如果这没有帮助,我建议寻找 getWriterContentWriter.putContent 的替代品。

关于java - iTextPdf : why misleading number of revisions when verifiying signature?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40418205/

相关文章:

javascript - Alfresco:根据文档的位置添加标签

java - 如何使用变量和标记实现马尔可夫算法?

java - 非美国邮政编码的最小和最大字符长度是多少

java - LDAP。没有身份验证的 Java 应用程序

java - Maven 插件 - 编辑目标中的文件(war)

itext - 文档是抽象的;不能实例化

java - Alfresco webscript - 401 授权

java - 尝试使用 itext :Exception in thread "main" java. lang.NoClassDefFoundError 签署 pdf 时出错:org/bouncycaSTLe/cert/X509CertificateHolder

c# - 在 winform 中显示 PDF

java - Alfresco 社区版中的扫描功能?