[英]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.