java - iText 数字签名损坏 PDF/A 2b

标签 java itext digital-signature pdfa

当使用 itext v5.5.11 对文档进行数字签名时,PDF/A-2b 文档会损坏 - 这意味着它们不再有效作为 PDF/A 文档。违反了以下规则: https://github.com/veraPDF/veraPDF-validation-profiles/wiki/PDFA-Parts-2-and-3-rules#rule-643-1

在上面的链接中指定了摘要无效,因此我还为您提供了一个代码段,该代码段在使用 iText 签署 pdf 文档时处理计算摘要:

        // Make the digest
        InputStream data;
        try {

            data = signatureAppearance.getRangeStream();
        } catch (IOException e) {
            String message = "MessageDigest error for signature input, type: IOException";
            signLogger.logError(message, e);
            throw new CustomException(message, e);
        }
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("SHA1");

        } catch (NoSuchAlgorithmException ex) {
            String message = "MessageDigest error for signature input, type: NoSuchAlgorithmException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] buf = new byte[8192];
        int n;
        try {
            while ((n = data.read(buf)) > 0) {
                messageDigest.update(buf, 0, n);
            }
        } catch (IOException ex) {
            String message = "MessageDigest update error for signature input, type: IOException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] hash = messageDigest.digest();
        // If we add a time stamp:
        // Create the signature
        PdfPKCS7 sgn;
        try {

            sgn = new PdfPKCS7(key, chain, configuration.getSignCertificate().getSignatureHashAlgorithm().value() , null, new BouncyCastleDigest(), false);
        } catch (InvalidKeyException ex) {
            String message = "Certificate PDF sign error for signature input, type: InvalidKeyException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        } catch (NoSuchProviderException ex) {
            String message = "Certificate PDF sign error for signature input, type: NoSuchProviderException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        } catch (NoSuchAlgorithmException ex) {
            String message = "Certificate PDF sign error for signature input, type: NoSuchAlgorithmException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }catch (Exception ex) {
            String message = "Certificate PDF sign error for signature input, type: Exception";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null,null, MakeSignature.CryptoStandard.CMS);
        try {
            sgn.update(sh, 0, sh.length);
        } catch (java.security.SignatureException ex) {
            String message = "Certificate PDF sign error for signature input, type: SignatureException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }
        byte[] encodedSig = sgn.getEncodedPKCS7(hash);
        if (contentEstimated + 2 < encodedSig.length) {
            String message = "The estimated size for the signature is smaller than the required one. Terminating request..";
            signLogger.log("ERROR", message);
            throw new CustomException(message);
        }
        byte[] paddedSig = new byte[contentEstimated];
        System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
        // Replace the contents
        PdfDictionary dic2 = new PdfDictionary();
        dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
        try {
            signatureAppearance.close(dic2);
        } catch (IOException ex) {
            String message = "PdfSignatureAppearance close error for signature input, type: IOException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        } catch (DocumentException ex) {
            String message = "PdfSignatureAppearance close error for signature input, type: DocumentException";
            signLogger.logError(message, ex);
            throw new CustomException(message, ex);
        }

对于 PDF/A 验证,我使用 VeraPDF 库。

提及 VeraPDF 库报告损坏的 PDF/A 库时,Adobe Reader 验证工具报告 PDF/A 文档未损坏可能也有帮助。

任何帮助将不胜感激。

最佳答案

When digitally signing document with itext v5.5.11 PDF/A-2b documents get corrupted - meaning they are no longer valid as PDF/A documents. Following rule is violated: https://github.com/veraPDF/veraPDF-validation-profiles/wiki/PDFA-Parts-2-and-3-rules#rule-643-1

虽然这确实是 veraPDF 所声称的,但这是错误的; iText 创建的签名覆盖整个修订版减去为签名容器保留的空间。

这种不正确的违规检测的原因是 veraPDF 中的错误。

veraPDF 如何确定带符号的字节范围是否有效

veryPDF 版本(基于 greenfield 解析器的版本和基于 PDFBox 的版本)都试图确定标称字节范围值并将其与实际值进行比较。这就是它确定标称值的方式:

public long[] getByteRangeBySignatureOffset(long signatureOffset) throws IOException {
    pdfSource.seek(signatureOffset);
    skipID();
    byteRange[0] = 0;
    parseDictionary();
    byteRange[3] = getOffsetOfNextEOF(byteRange[2]) - byteRange[2];
    return byteRange;
}

private long getOffsetOfNextEOF(long currentOffset) throws IOException {
    byte[] buffer = new byte[EOF_STRING.length];
    pdfSource.seek(currentOffset + document.getHeaderOffset());
    readWholeBuffer(pdfSource, buffer);
    pdfSource.rewind(buffer.length - 1);
    while (!Arrays.equals(buffer, EOF_STRING)) {    //TODO: does it need to be optimized?
        readWholeBuffer(pdfSource, buffer);
        if (pdfSource.isEOF()) {
            pdfSource.seek(currentOffset + document.getHeaderOffset());
            return pdfSource.length();
        }
        pdfSource.rewind(buffer.length - 1);
    }
    long result = pdfSource.getPosition() + buffer.length - 1;  // offset of byte after 'F'
    pdfSource.seek(currentOffset + document.getHeaderOffset());
    return result - 1;
}

(基于 PDFBox 的 SignatureParser 类)

public long[] getByteRangeBySignatureOffset(long signatureOffset) throws IOException {
    source.seek(signatureOffset);
    skipID();
    byteRange[0] = 0;
    parseDictionary();
    byteRange[3] = getOffsetOfNextEOF(byteRange[2]) - byteRange[2];
    return byteRange;
}

private long getOffsetOfNextEOF(long currentOffset) throws IOException {
    byte[] buffer = new byte[EOF_STRING.length];
    source.seek(currentOffset + document.getHeader().getHeaderOffset());
    source.read(buffer);
    source.unread(buffer.length - 1);
    while (!Arrays.equals(buffer, EOF_STRING)) {    //TODO: does it need to be optimized?
        source.read(buffer);
        if (source.isEOF()) {
            source.seek(currentOffset + document.getHeader().getHeaderOffset());
            return source.getStreamLength();
        }
        source.unread(buffer.length - 1);
    }
    long result = source.getOffset() - 1 + buffer.length;   // byte right after 'F'
    source.seek(currentOffset + document.getHeader().getHeaderOffset());
    return result - 1;
}

(基于SignatureParser 的绿地解析器)

本质上,这两种实现在这里都做同样的事情,从签名开始,它们寻找文件结束标记 %%EOF 的下一次出现,并尝试完成标称字节范围值,以便第二个范围以该标记结束。

为什么这是错误的

这种确定标称带符号字节范围值的方法错误的原因有多种:

  1. 根据PDF/A规范,

    No data can follow the last end-of-file marker except a single optional end-of-line marker as described in ISO 32000-1:2008, 7.5.5.

    因此,紧接在下一个文件结束标记 %%EOF 之后的偏移量不一定已经是签名修订的结尾,正确的偏移量可能是下一个行尾标记之后的那个!由于 PDF 行尾标记可以是单个 CR 或单个 LF 或 CRLF 组合,这意味着 veraPDF 选择三个可能的偏移量之一,并声称它是修订的名义结束因此,有符号字节范围的标称结束

  2. 有可能(即使几乎从未见过)在一次修订中准备签名值(以文件结束标记结尾),然后在增量更新中附加一些数据,从而产生新修订(以另一个文件结束标记结束),然后签名值填充为签署文档的值,包括这个新修订。

    由于 veraPDF 在签名字典之后使用下一个文件结束标记,在这种情况下 veraPDF 实际上选择了错误的文件结束标记。。 p>

  3. 文件结束标记 %%EOF 在句法上实际上只是一个在 PDF/修订版末尾具有特殊含义的注释,并且几乎在任何地方都允许注释PDF 外部 PDF 字符串、PDF 流数据和 PDF 交叉引用表。因此,字节序列 %%EOF 可以作为常规注释或字符串的非注释内容或流在签名值字典和已签名修订的实际结尾之间出现任意次数.

    如果出现这种情况,veraPDF 会选择一个字节序列作为文件结束标记,该标记永远不会作为某事的结束

此外,除非在循环中到达实际的文件结尾(并且返回 pdfSource.length()/source.getStreamLength()),否则结果出现差一,return result - 1中的- 1与result的使用不对应。

veraPDF 版本

我检查了标记为 veraPDF 的当前 1.5.0-SNAPSHOT 版本:

  • veraPDF-pdfbox-validation 1.5.4
  • veraPDF-验证 1.5.2
  • veraPDF 解析器 1.5.1

OP 的示例文档

OP 提供的示例文档在文件结束标记后有一个 LF。由于这个和上面提到的差一问题,veraPDF 确定了一个标称有符号字节范围结束,这是两个字节短。

关于java - iText 数字签名损坏 PDF/A 2b,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43522415/

相关文章:

java - 使用签名公钥 RSA java 验证数据

java - 配置单元服务器2-异常hive.service.ServiceException:设置阶段目录时出错

java文件移动高性能

c# - iText 或 iTextSharp 基本文本编辑

c# - 使用 PdfWriter 和 iTextSharp 以及 SetFontAndSize 函数的斜体文本

java - 在 Android 上对共享库进行数字签名

java - PKCS#11 实例化问题

java - 将语言名称转换为 ISO 639 语言代码

java - Jdbc SQL参数问题

java - 当 Document 对象打开时,获取 "java.lang.RuntimeException: The document is not open"