繁体   English   中英

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

[英]iText digital signature corrupts PDF/A 2b

使用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文档未损坏,也可能会有所帮助。

任何帮助将非常感激。

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

(基于greenfield解析器的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会选择一个字节序列作为文件结束标记,而该标记从未被视为某事物的结束

此外,除非在循环中到达了实际的文件结尾(并且pdfSource.length() / source.getStreamLength() ), source.getStreamLength()结果似乎是一一对应的,即- 1 return result - 1与使用结果不符。

veraPDF版本

我对照了目前标记为veraPDF的1.5.0-SNAPSHOT版本:

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

OP的样本文件

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

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

特别是,iText签名的PDF / A-2文档似乎可以通过veraPDF验证。

我确实同意有关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序列

应该被允许。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM