PDF 签名使 Acrobat 中的现有签名无效 Reader
Pdf signature invalidates existing signature in Acrobat Reader
我正在使用 iText 7.1.15 和 SignDeferred 将签名应用于 pdf 文档。
SignDeferred 是必需的,因为签名是创建 PKCS11 硬件令牌(USB 密钥)。
当我签署“常规”pdf 时,例如通过 word 创建,我可以应用多个签名,并且所有签名在 adobe acrobat 中都显示为有效 reader.
如果 pdf 是通过将多个 pdf 文档与 adobe DC 合并创建的,则第一个签名有效,但在应用第二个签名后立即失效。
应用第一个签名后 Adobe reader 中的文档:
应用第二个签名后 Adobe reader 中的文档:
同一个文档的签名在foxit中显示为有效reader。
我在 Whosebug () 上发现了类似的问题,但它使用的是 iText 5,我不确定这是不是同一个问题。
问题:我该怎么做才能使 Acrobat 中的两个签名都有效 Reader?
第一个签名无效的未签名 Pdf 文档:
https://github.com/suntsu42/iTextDemoInvalidSecondSignature/blob/master/test.pdf
用于签名的代码
//Step #1 >> prepare pdf for signing (Allocate space for the signature and calculate hash)
using (MemoryStream input = new MemoryStream(pdfToSign))
{
using (var reader = new PdfReader(input))
{
StampingProperties sp = new StampingProperties();
sp.UseAppendMode();
using (MemoryStream baos = new MemoryStream())
{
var signer = new PdfSigner(reader, baos, sp);
//Has to be NOT_CERTIFIED since otherwiese a pdf cannot be signed multiple times
signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
if (visualRepresentation != null)
{
try
{
PdfSignatureAppearance appearance = signer.GetSignatureAppearance();
base.SetPdfSignatureAppearance(appearance, visualRepresentation);
}
catch (Exception ex)
{
throw new Exception("Unable to set provided signature image", ex);
}
}
//Make the SignatureAttributeName unique
SignatureAttributeName = $"SignatureAttributeName_{DateTime.Now:yyyyMMddTHHmmss}";
signer.SetFieldName(SignatureAttributeName);
DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.SignExternalContainer(external, EstimateSize);
hash = external.GetDocBytesHash();
tmpPdf = baos.ToArray();
}
}
//Step #2 >> Create the signature based on the document hash
// This is the part which accesses the HSM via PCKS11
byte[] signature = null;
if (LocalSigningCertificate == null)
{
signature = CreatePKCS7SignatureViaPKCS11(hash, pin);
}
else
{
signature = CreatePKCS7SignatureViaX509Certificate(hash);
}
//Step #3 >> Apply the signature to the document
ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signature);
using (MemoryStream preparedPdfStream = new MemoryStream(tmpPdf))
{
using (var pdfReader = new PdfReader(preparedPdfStream))
{
using (PdfDocument docToSign = new PdfDocument(pdfReader))
{
using (MemoryStream outStream = new MemoryStream())
{
PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
return outStream.ToArray();
}
}
}
}
}
示例项目
我创建了一个使用本地证书进行签名的工作示例项目。我也确实将 iText 更新到 7.2 版,但结果相同。
它还包含无法签名两次的文档 (test.pdf)
https://github.com/suntsu42/iTextDemoInvalidSecondSignature/tree/master
编辑
我已经将 MKL 提供的解决方案应用于 github 上的示例项目。
作为第二个说明,也可以使用 PdfSigner,但在这种情况下,必须删除原始文档的书签。
正如评论中已经提到的,示例文档“InvalidDocumentSignedTwice.pdf”的签名未在增量更新中应用,因此很明显,以前的签名会被破坏。但这不是 OP 示例项目的问题。因此,问题的处理着眼于示例项目的实际输出。
分析问题
在验证签名的 PDF 时,Adobe Acrobat 会执行两种类型的检查:
- 它会检查签名本身以及它所涵盖的 PDF 的修订版是否未受影响。
- (如果在签名涵盖的修订后对 PDF 进行了添加:)它检查增量更新中应用的更改是否仅包含允许的更改。
前一个检查非常稳定和标准,但第二个检查非常异想天开,容易出现不正确的负面验证结果。就像你的情况一样...
对于您的示例文档,可以简单地确定第一次检查必须肯定地验证第一个签名:只有一个(有效!)签名的文件构成文件的 byte-wise 起始部分,其中有两个签名,所以这里没有任何东西可以被破坏。
因此,第二种检查,变化无常的类型,在本案中肯定会出错。
要找出有什么变化,需要分析签名期间所做的更改。一个有用的事实是,使用 iText 5 执行相同的操作不会产生问题;因此,触发检查的更改必须是 iText 7 与此处的 iText 5 不同的地方。在此上下文中的主要区别在于 iText 7 具有比 iText 5 更全面的标记支持,因此,还将对新签名字段的引用添加到文档结构树中。
这本身还不会触发异想天开的检查,不过,它只会在这里触发,因为一个大纲元素将更改的父结构树元素引用为其 结构元素 (SE)。显然,Adobe Acrobat 将关联结构元素中的更改视为大纲的更改 link,因此,将其视为由第一个签名签署的文档修订行为的(不允许的)更改。
这是 iText 错误(向结构树添加条目)还是 Adobe Acrobat 错误(抱怨添加)?那么,在 标记的 PDF 中(并且您的 PDF 将相应的 Marked 条目设置为 true)内容 包括注释和表单字段 预计会被标记。因此,对于新增的签名字段及其外观,增加结构树条目不仅应该允许,而且实际上是推荐甚至要求的!所以这似乎是 Adobe Acrobat 的错误。
一个Work-Around
知道这似乎是一个 Adobe Acrobat 错误很好,但在一天结束时,现在可能需要一种方法来多次签署此类文档,而当前的 Adobe Acrobat 不会调用该错误。
可以让iText相信没有结构树并且不需要更新结构树。这可以通过使文档标签结构的初始化失败来完成。为此,我们覆盖 PdfDocument
方法 TryInitTagStructure
。由于 iText PdfSigner
在内部创建其文档对象,我们在 PdfSigner
方法 InitDocument
.
的重写中执行此操作
即而不是 PdfSigner
我们使用 class MySigner
定义如下:
public class MySigner : PdfSigner
{
public MySigner(PdfReader reader, Stream outputStream, StampingProperties properties) : base(reader, outputStream, properties)
{
}
override protected PdfDocument InitDocument(PdfReader reader, PdfWriter writer, StampingProperties properties)
{
return new MyDocument(reader, writer, properties);
}
}
public class MyDocument : PdfDocument
{
public MyDocument(PdfReader reader, PdfWriter writer, StampingProperties properties) : base(reader, writer, properties)
{
}
override protected void TryInitTagStructure(PdfDictionary str)
{
structTreeRoot = null;
structParentIndex = -1;
}
}
使用 MySigner
签署文档 iText 将不再添加标签,因此不会让 Adobe Acrobat 抱怨结构树中的新条目。
Java
中的相同 Work-Around
因为我觉得在 Java 中工作更舒服,所以我对此进行了分析并在 Java 中测试了 work-around。
这里可以写成更封闭的形式(也许C#也可以,我不知道),而不是像这样初始化签名者
PdfSigner pdfSigner = new PdfSigner(pdfReader, os, new StampingProperties().useAppendMode());
我们这样做:
PdfSigner pdfSigner = new PdfSigner(pdfReader, os, new StampingProperties().useAppendMode()) {
@Override
protected PdfDocument initDocument(PdfReader reader, PdfWriter writer, StampingProperties properties) {
return new PdfDocument(reader, writer, properties) {
@Override
protected void tryInitTagStructure(PdfDictionary str) {
structTreeRoot = null;
structParentIndex = -1;
}
};
}
};
(MultipleSignaturesAndTagging 测试 testSignTestManuelTwiceNoTag
)
TL;DR
iText 7 在签名期间将新签名字段的结构元素添加到文档结构树中。但是,如果此新节点的父节点被引用为大纲元素的关联结构元素,Adobe Acrobat 会错误地认为这是不允许的更改。作为 work-around 可以调整 iText 签名以不添加结构元素。
我在上次更新 Adobe Reader 后遇到了类似的签名问题。我在他们的社区写了一篇post,但他们仍然没有回复我。
我正在使用 iText v.5.5.5 生成 pdf。我以单一方法签署和认证 pdf 文档。此外,foxit reader 表明签名是有效的。我相信这是一个 Adobe 错误,很快就会修复 :) 关键是一个日志。
我正在使用 iText 7.1.15 和 SignDeferred 将签名应用于 pdf 文档。 SignDeferred 是必需的,因为签名是创建 PKCS11 硬件令牌(USB 密钥)。
当我签署“常规”pdf 时,例如通过 word 创建,我可以应用多个签名,并且所有签名在 adobe acrobat 中都显示为有效 reader.
如果 pdf 是通过将多个 pdf 文档与 adobe DC 合并创建的,则第一个签名有效,但在应用第二个签名后立即失效。
应用第一个签名后 Adobe reader 中的文档:
应用第二个签名后 Adobe reader 中的文档:
同一个文档的签名在foxit中显示为有效reader。
我在 Whosebug (
问题:我该怎么做才能使 Acrobat 中的两个签名都有效 Reader?
第一个签名无效的未签名 Pdf 文档: https://github.com/suntsu42/iTextDemoInvalidSecondSignature/blob/master/test.pdf
用于签名的代码
//Step #1 >> prepare pdf for signing (Allocate space for the signature and calculate hash)
using (MemoryStream input = new MemoryStream(pdfToSign))
{
using (var reader = new PdfReader(input))
{
StampingProperties sp = new StampingProperties();
sp.UseAppendMode();
using (MemoryStream baos = new MemoryStream())
{
var signer = new PdfSigner(reader, baos, sp);
//Has to be NOT_CERTIFIED since otherwiese a pdf cannot be signed multiple times
signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
if (visualRepresentation != null)
{
try
{
PdfSignatureAppearance appearance = signer.GetSignatureAppearance();
base.SetPdfSignatureAppearance(appearance, visualRepresentation);
}
catch (Exception ex)
{
throw new Exception("Unable to set provided signature image", ex);
}
}
//Make the SignatureAttributeName unique
SignatureAttributeName = $"SignatureAttributeName_{DateTime.Now:yyyyMMddTHHmmss}";
signer.SetFieldName(SignatureAttributeName);
DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.SignExternalContainer(external, EstimateSize);
hash = external.GetDocBytesHash();
tmpPdf = baos.ToArray();
}
}
//Step #2 >> Create the signature based on the document hash
// This is the part which accesses the HSM via PCKS11
byte[] signature = null;
if (LocalSigningCertificate == null)
{
signature = CreatePKCS7SignatureViaPKCS11(hash, pin);
}
else
{
signature = CreatePKCS7SignatureViaX509Certificate(hash);
}
//Step #3 >> Apply the signature to the document
ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signature);
using (MemoryStream preparedPdfStream = new MemoryStream(tmpPdf))
{
using (var pdfReader = new PdfReader(preparedPdfStream))
{
using (PdfDocument docToSign = new PdfDocument(pdfReader))
{
using (MemoryStream outStream = new MemoryStream())
{
PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
return outStream.ToArray();
}
}
}
}
}
示例项目
我创建了一个使用本地证书进行签名的工作示例项目。我也确实将 iText 更新到 7.2 版,但结果相同。 它还包含无法签名两次的文档 (test.pdf) https://github.com/suntsu42/iTextDemoInvalidSecondSignature/tree/master
编辑
我已经将 MKL 提供的解决方案应用于 github 上的示例项目。
作为第二个说明,也可以使用 PdfSigner,但在这种情况下,必须删除原始文档的书签。
正如评论中已经提到的,示例文档“InvalidDocumentSignedTwice.pdf”的签名未在增量更新中应用,因此很明显,以前的签名会被破坏。但这不是 OP 示例项目的问题。因此,问题的处理着眼于示例项目的实际输出。
分析问题
在验证签名的 PDF 时,Adobe Acrobat 会执行两种类型的检查:
- 它会检查签名本身以及它所涵盖的 PDF 的修订版是否未受影响。
- (如果在签名涵盖的修订后对 PDF 进行了添加:)它检查增量更新中应用的更改是否仅包含允许的更改。
前一个检查非常稳定和标准,但第二个检查非常异想天开,容易出现不正确的负面验证结果。就像你的情况一样...
对于您的示例文档,可以简单地确定第一次检查必须肯定地验证第一个签名:只有一个(有效!)签名的文件构成文件的 byte-wise 起始部分,其中有两个签名,所以这里没有任何东西可以被破坏。
因此,第二种检查,变化无常的类型,在本案中肯定会出错。
要找出有什么变化,需要分析签名期间所做的更改。一个有用的事实是,使用 iText 5 执行相同的操作不会产生问题;因此,触发检查的更改必须是 iText 7 与此处的 iText 5 不同的地方。在此上下文中的主要区别在于 iText 7 具有比 iText 5 更全面的标记支持,因此,还将对新签名字段的引用添加到文档结构树中。
这本身还不会触发异想天开的检查,不过,它只会在这里触发,因为一个大纲元素将更改的父结构树元素引用为其 结构元素 (SE)。显然,Adobe Acrobat 将关联结构元素中的更改视为大纲的更改 link,因此,将其视为由第一个签名签署的文档修订行为的(不允许的)更改。
这是 iText 错误(向结构树添加条目)还是 Adobe Acrobat 错误(抱怨添加)?那么,在 标记的 PDF 中(并且您的 PDF 将相应的 Marked 条目设置为 true)内容 包括注释和表单字段 预计会被标记。因此,对于新增的签名字段及其外观,增加结构树条目不仅应该允许,而且实际上是推荐甚至要求的!所以这似乎是 Adobe Acrobat 的错误。
一个Work-Around
知道这似乎是一个 Adobe Acrobat 错误很好,但在一天结束时,现在可能需要一种方法来多次签署此类文档,而当前的 Adobe Acrobat 不会调用该错误。
可以让iText相信没有结构树并且不需要更新结构树。这可以通过使文档标签结构的初始化失败来完成。为此,我们覆盖 PdfDocument
方法 TryInitTagStructure
。由于 iText PdfSigner
在内部创建其文档对象,我们在 PdfSigner
方法 InitDocument
.
即而不是 PdfSigner
我们使用 class MySigner
定义如下:
public class MySigner : PdfSigner
{
public MySigner(PdfReader reader, Stream outputStream, StampingProperties properties) : base(reader, outputStream, properties)
{
}
override protected PdfDocument InitDocument(PdfReader reader, PdfWriter writer, StampingProperties properties)
{
return new MyDocument(reader, writer, properties);
}
}
public class MyDocument : PdfDocument
{
public MyDocument(PdfReader reader, PdfWriter writer, StampingProperties properties) : base(reader, writer, properties)
{
}
override protected void TryInitTagStructure(PdfDictionary str)
{
structTreeRoot = null;
structParentIndex = -1;
}
}
使用 MySigner
签署文档 iText 将不再添加标签,因此不会让 Adobe Acrobat 抱怨结构树中的新条目。
Java
中的相同 Work-Around因为我觉得在 Java 中工作更舒服,所以我对此进行了分析并在 Java 中测试了 work-around。
这里可以写成更封闭的形式(也许C#也可以,我不知道),而不是像这样初始化签名者
PdfSigner pdfSigner = new PdfSigner(pdfReader, os, new StampingProperties().useAppendMode());
我们这样做:
PdfSigner pdfSigner = new PdfSigner(pdfReader, os, new StampingProperties().useAppendMode()) {
@Override
protected PdfDocument initDocument(PdfReader reader, PdfWriter writer, StampingProperties properties) {
return new PdfDocument(reader, writer, properties) {
@Override
protected void tryInitTagStructure(PdfDictionary str) {
structTreeRoot = null;
structParentIndex = -1;
}
};
}
};
(MultipleSignaturesAndTagging 测试 testSignTestManuelTwiceNoTag
)
TL;DR
iText 7 在签名期间将新签名字段的结构元素添加到文档结构树中。但是,如果此新节点的父节点被引用为大纲元素的关联结构元素,Adobe Acrobat 会错误地认为这是不允许的更改。作为 work-around 可以调整 iText 签名以不添加结构元素。
我在上次更新 Adobe Reader 后遇到了类似的签名问题。我在他们的社区写了一篇post,但他们仍然没有回复我。
我正在使用 iText v.5.5.5 生成 pdf。我以单一方法签署和认证 pdf 文档。此外,foxit reader 表明签名是有效的。我相信这是一个 Adobe 错误,很快就会修复 :) 关键是一个日志。