iTextPdf:为什么在验证签名时会产生误导性的修改?

时间:2016-11-04 08:31:51

标签: 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

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

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

1 个答案:

答案 0 :(得分:3)

简而言之

看起来您的方法getNodeRefOutputStream会返回ByteArrayOutputStream 已包含原始文档的副本,或者您的方法getWriter返回{em>将追加添加到现有内容而不是替换。{/ 1>

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

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

详细

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

效果是

  • 汇总的交叉引用是错误的,它们指向原始文档的第二个副本中的位置不足而不是实际添加的对象,并且 startxref 指针指向原始PDF的第二个副本中的某处也是;因此,引擎盖下的Adobe Reader&#34; repair&#34;引发&#34;你要保存更改的PDF&#34;关闭文档时的对话框;
  • 有符号字节范围中的间隙位于原始文档的第二个副本内,特别是不包含编码的签名字节;因此,Adobe Reader - 期望该间隙中的签名 - 表示此签名中包含的格式或信息中的错误;
  • 签名的字节范围稍微达到原始的第二个副本,导致iText报告两个修订,首先是数据直到签名字节范围的末尾,然后是超出的数据;和
  • 签名的哈希值被破坏,因为有符号的字节范围不包含原始文档加上签名添加,但实际签名字节除外,而是原始文档加上原始文档的第二个副本的一些奇怪部分;这会导致iText验证失败。

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

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

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

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

因此,我建议不要将putContent设置为outputStream返回的ByteArrayOutputStream,以将getNodeRefOutputStream设置为空outputStream;如果这没有帮助,我建议寻找new ByteArrayOutputStream()getWriter的替代方案。