iText数字签名会破坏PDF / A 2b

时间:2017-04-20 14:29:56

标签: 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文档未被破坏。

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:3)

  

使用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

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

此错误检测错误的原因是veraPDF中的错误。

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

veryPDF版本(基于绿域解析器的版本和基于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规范,

      

    除了所描述的单个可选的行尾标记之外,没有数据可以跟随最后的文件结束标记   在ISO 32000-1:2008中,7.5.5。

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

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

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

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

    如果出现这种情况, veraPDF会选择一个字节序列作为文件结束标记,这从来就不会被视为某种结尾

  4. 此外,除非在循环中到达实际的文件结尾(并返回pdfSource.length() / source.getStreamLength()),否则结果似乎是一个一个{{1 } - 1中的}与结果的使用不符。

    veraPDF版本

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

    • veraPDF-pdfbox-validation 1.5.4
    • veraPDF-validation 1.5.2
    • veraPDF-parser 1.5.1

    OP的样本文件

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

答案 1 :(得分:2)

如上所述,我们刚刚发布了veraPDF 1.4的修补程序,它解决了本讨论中的问题。新版本可供下载:http://downloads.verapdf.org/rel/1.4/verapdf-1.4.5-installer.zip

特别是,iText签名的PDF / A-2文件似乎很好地通过了veraPDF验证。

答案 2 :(得分:0)

我同意分析veraPDF目前如何检查ByteRange。实际上,它假定文件正好在签名字段后紧跟在%EOF标记处终止。

原因很简单。该文档可以由多个人按顺序签名,并且仍然可以是有效的PDF / A-2B文档。生成第二个签名时,它将逐步更新包含第一个签名的文件。

因此,如果我们在字面上解释PDF / A-2B要求中的术语文件

  

在计算文件的摘要时,应在整个文件上计算,包括签名词典,但不包括PDF签名本身。然后,此范围由签名字典的ByteRange条目指示。

我们永远无法创建具有多个签名的有效PDF / A文件。这显然不是PDF / A-2标准的意图。

PDF文件通常被理解为前导%PDF与尾随%EOF之间的字节范围,例如,允许PDF文件作为较大字节流的一部分(例如,邮件附件)。这就是veraPDF实现的基础。

我确实同意这种方法没有考虑%EOF之后的可选行尾序列。我为veraPDF创建了相应的问题:https://github.com/veraPDF/veraPDF-validation/issues/166

它留下了一个有趣的问题:如果文档有更多签名,第一个签名的有效ByteRange是什么?我相信,所有情况:

  • ByteRange将文件覆盖到下一个%EOF标记
  • ByteRange将文件覆盖到下一个%EOF标记+单个CR字符
  • ByteRange将文件覆盖到下一个%EOF标记+单个LF字符
  • ByteRange覆盖文件,直到下一个%EOF标记+一个双字节CR + LF序列
应该允许