簡體   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