[英]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中的錯誤。
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
的下一次出現,並嘗試完成標稱字節范圍值,以便第二個范圍以該標記結尾。
這種確定標稱有符號字節范圍值的方法錯誤的原因有多種:
根據PDF / A規范,
除了ISO 32000-1:2008,7.5.5中所述的單個可選的行尾標記外,沒有數據可以跟隨文件的最后一個標記。
因此, 緊接在下一個文件結尾標記%%EOF
之后的偏移量不一定已經是已簽名修訂的結尾 ,正確的偏移量可能是在下一個行結束標記之后的偏移量! 由於PDF行尾標記可以是單個CR或單個LF或CRLF組合,因此這意味着veraPDF選擇了三種可能的偏移量之一,並聲稱它是修訂版的標稱結尾,因此,有符號字節范圍的標稱結尾 。
可能(盡管幾乎從未見過)在一個修訂版中准備了簽名值(以文件結尾標記結尾),然后在增量更新中附加了一些數據以產生新的修訂版(以另一個結尾)文件結尾標記),然后用對文檔進行簽名的值(包括此新修訂版)填充簽名值。
由於veraPDF 在簽名字典之后使用下一個文件結尾標記 ,因此在這種情況下, veraPDF實際上選擇了錯誤的文件結尾標記 。
實際上,文件結束標記%%EOF
僅僅是在PDF /修訂版末尾具有特殊含義的注釋,並且在PDF字符串,PDF流數據和PDF交叉引用之外的PDF中幾乎所有位置都允許注釋表。 因此,字節序列%%EOF
可以作為常規注釋或字符串的非注釋內容出現,也可以在簽名值字典與簽名修訂的實際結尾之間流過任意次。
如果發生這種情況, veraPDF會選擇一個字節序列作為文件結束標記,而該標記從未被視為某事物的結束 。
此外,除非在循環中到達了實際的文件結尾(並且pdfSource.length()
/ source.getStreamLength()
), source.getStreamLength()
結果似乎是一一對應的,即- 1
return result - 1
與使用結果不符。
我對照了目前標記為veraPDF的1.5.0-SNAPSHOT版本:
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是什么? 我相信所有情況:
應該被允許。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.