iText实现Certification签名LTV启用后PDF显示未修改状态的技术咨询
我之前也碰到过类似的情况,核心问题出在你先做Certification签名,再用PdfStamper追加LTV信息的流程上——这种方式本质是对PDF做了一次增量修改,而Certification签名默认或你设置的权限允许这类修改,所以Adobe Reader会提示“已进行认证方允许的更改”。要让PDF显示“签名后文档未被修改”,必须把LTV验证材料直接嵌入到Certification签名的容器里,而不是事后追加。
问题原因拆解
当你用PdfStamper单独添加LTV时,相当于在Certification签名之后给PDF加了新的验证信息对象,属于PDF的后续修改。哪怕你设置了Certification权限为允许填充/注释,这类LTV操作也会被判定为“允许的修改”,从而触发提示。只有当整个签名流程(包括LTV材料)一次性完成,没有后续增量更新时,才会显示“未被修改”。
解决方案:在Certification签名时嵌入LTV材料
你需要调整代码,把OCSP响应和CRL数据直接包含在Certification签名的PKCS7容器中,而不是事后用PdfStamper追加。具体步骤如下:
1. 修改Certification签名的创建流程
先设置文档的Certification权限(建议设为不允许任何修改,确保后续无变更),然后在签名过程中嵌入LTV验证材料:
using (PdfReader reader = new PdfReader(src)) using (FileStream os = new FileStream(dest, FileMode.CreateNew)) { // 创建签名Stamper,指定做Certification签名 PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0'); PdfSignatureAppearance appearance = stamper.SignatureAppearance; // 设置签名位置和名称 appearance.SetVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "CertificationSignature"); // 设置Certification级别:不允许任何修改(如果需要允许后续签名,可设为CERTIFIED_FORM_FILLING_AND_ANNOTATIONS,但要确保LTV嵌入签名内) appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED; // 准备你的签名密钥、证书链、OCSP和CRL数据 IExternalSignature signature = new PrivateKeySignature(privateKey, "SHA256"); X509Certificate[] chain = ...; // 你的证书链 byte[] ocspBytes = ...; // 获取GlobalSign的OCSP响应字节 byte[] crlBytes = ...; // 获取GlobalSign的CRL字节 // 自定义外部签名容器,传入LTV材料 IExternalSignatureContainer externalContainer = new EmbeddedLtvSignatureContainer(signature, chain, ocspBytes, crlBytes); MakeSignature.SignExternalContainer(appearance, externalContainer, 8192); }
2. 实现包含LTV的外部签名容器
自定义EmbeddedLtvSignatureContainer类,在生成PKCS7签名时直接加入OCSP和CRL数据:
public class EmbeddedLtvSignatureContainer : IExternalSignatureContainer { private readonly IExternalSignature _signature; private readonly X509Certificate[] _chain; private readonly byte[] _ocspBytes; private readonly byte[] _crlBytes; public EmbeddedLtvSignatureContainer(IExternalSignature signature, X509Certificate[] chain, byte[] ocspBytes, byte[] crlBytes) { _signature = signature; _chain = chain; _ocspBytes = ocspBytes; _crlBytes = crlBytes; } public byte[] Sign(Stream data) { // 计算数据摘要 byte[] digest = DigestAlgorithms.Digest(data, _signature.GetHashAlgorithm()); // 创建PKCS7对象并嵌入LTV材料 PdfPKCS7 pkcs7 = new PdfPKCS7(null, _chain, _signature.GetHashAlgorithm(), false); pkcs7.SetExternalDigest(_signature.Sign(digest), null, _signature.GetEncryptionAlgorithm()); // 添加OCSP响应和CRL到PKCS7中 if (_ocspBytes != null && _ocspBytes.Length > 0) { pkcs7.AddOcsp(_ocspBytes); } if (_crlBytes != null && _crlBytes.Length > 0) { pkcs7.AddCrl(_crlBytes); } // 生成包含LTV的PKCS7签名 return pkcs7.GetEncodedPKCS7(digest, null, _ocspBytes, new List<byte[]> { _crlBytes }, CryptoStandard.CMS); } public void ModifySigningDictionary(PdfDictionary signDic) { // 设置签名类型为Adobe.PPKLite,子过滤器为adbe.pkcs7.detached signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKLITE); signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED); } }
3. 移除后续的LTV追加代码
不再需要单独调用那段用PdfStamper添加LTV的代码,因为LTV材料已经嵌入到Certification签名里了。
关键注意点
- Certification权限设置:如果你需要允许后续添加其他签名,可将
CertificationLevel设为PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS,但必须保证LTV材料嵌入到初始的Certification签名中,不能事后追加。 - OCSP/CRL获取:确保从GlobalSign获取的OCSP响应和CRL数据是有效的、未过期的,否则LTV验证会失败。
这样操作后,PDF的Certification签名本身就包含了所有LTV验证所需的材料,没有后续的修改操作,Adobe Reader就会显示“Document has not been modified since this signature was applied”。
内容的提问来源于stack exchange,提问作者Jitendra Bhargava

