[英]Adding LTV in signature makes pdf invalid using C#
将 LTV 添加到数字签名后,它显示文档已更改。
从这个 que 中获取参考后: LTV 认证签名后,PDF 显示“文档已更改”
我对代码进行了更改,它适用于所有文档,但对于此文档: https : //www.sendspace.com/file/3ulwn7 - 它显示无效签名。
我们也在使用来自 global sign 的文档签名服务。
下面是添加 LTV 的代码:
public void AddLtv(string src, string dest, IOcspClient ocsp, ICrlClient crl, ITSAClient tsa)
{
using (PdfReader r = new PdfReader(src))
{
using (FileStream fos =new FileStream(dest,FileMode.CreateNew))
{
PdfStamper stp = new PdfStamper(r, fos, '\0', true);
LtvVerification v = stp.LtvVerification;
AcroFields fields = stp.AcroFields;
List<String> names = fields.GetSignatureNames();
String sigName = names[names.Count - 1];
PdfPKCS7 pkcs7 = fields.VerifySignature(sigName);
if (pkcs7.IsTsp)
{
v.AddVerification(sigName, ocsp, crl,
LtvVerification.CertificateOption.SIGNING_CERTIFICATE,
LtvVerification.Level.OCSP_CRL,
LtvVerification.CertificateInclusion.NO);
}
else
{
foreach (var name in names)
{
v.AddVerification(name, ocsp, crl,
LtvVerification.CertificateOption.WHOLE_CHAIN,
LtvVerification.Level.OCSP_CRL,
LtvVerification.CertificateInclusion.NO);
}
}
stp.Close();
}
}
}
编辑:我认为我在代码中操作 pdf 的方式导致读取/写入 pdf 出现问题。 不知何故,我无法获得任何 pdf 验证器,它可以识别 @mkl 讲述的关于交叉 pdf ref 的问题。 但是,如果我的 pdf 操作方式有任何问题,我将分享我的代码如下。 帮助将不胜感激。
当我添加新签名时,此 pdf 的旧签名无效。 如果我添加 LTV,那么即使是单个签名也是无效的。
未签名的 pdf 网址: https : //www.sendspace.com/file/n0ckem
没有 LTV URL 的签名 pdf: https : //www.sendspace.com/file/t1gwp9
带有 LTV 单一签名的 pdf 签名: https : //www.sendspace.com/file/ba8leq
带有 LTV 两个符号的签名 pdf: https : //www.sendspace.com/file/6b53z1
下面用于创建空容器和添加签名的代码:
private async Task<string> SignPdf(string ocspResponse, string cert, string unsignedPdf, DocumentShapeModel annotations, string caCertraw, int pageHeight, UserProfileModel user)
{
var trustedSignedpdf = Path.Combine(_env.WebRootPath, "TempPath", annotations.userId, annotations.Id.ToString(), "trustedSignedpdf.pdf");
if (!Directory.Exists(Path.GetDirectoryName(trustedSignedpdf)))
{
Directory.CreateDirectory(trustedSignedpdf);
}
var tempPdf = Path.Combine(_env.WebRootPath, "TempPath", annotations.userId, annotations.Id.ToString());
if (!Directory.Exists(tempPdf))
{
Directory.CreateDirectory(tempPdf);
}
tempPdf = Path.Combine(tempPdf, "tempSignedpdfglobal.pdf");
string finalsignedPdf = trustedSignedpdf;
var ocsp = new OcspClientBouncyCastle();
byte[] oc2 = Convert.FromBase64String(oc1);
OcspResp ocspResp = new OcspResp(oc2);
BasicOcspResp basicResp = (BasicOcspResp)ocspResp.GetResponseObject();
byte[] oc = basicResp.GetEncoded();
bool check = false;
string hexencodedDigest = null;
PdfPKCS7 sgn = null;
byte[] hash = null;
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[2];
var cer = new Org.BouncyCastle.X509.X509CertificateParser()
.ReadCertificate((new X509Certificate2(cert)).GetRawCertData());
chain[0] = cer;
var caCert = new Org.BouncyCastle.X509.X509CertificateParser()
.ReadCertificate((new X509Certificate2(caCertraw)).GetRawCertData());
chain[1] = caCert;
while (!check)
{
PdfReader.unethicalreading = true;
//create empty signature
using (PdfReader reader = new PdfReader(unsignedPdf))
{
using (FileStream os = File.OpenWrite(tempPdf))
{
PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, os, '\0', null, true);
PdfSignatureAppearance signatureAppearance = pdfStamper.SignatureAppearance;
// Sets Signature Appearance
signatureAppearance.Certificate = chain[0];
signatureAppearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
signatureAppearance.Reason = "E Signed by " + user.FirstName + " " + user.LastName + " (" + user.Email + ").";
signatureAppearance.Acro6Layers = false;
signatureAppearance.Layer4Text = PdfSignatureAppearance.questionMark;
float shapeH = annotations.IsResponsive == true ? annotations.h : ((annotations.h * 72 / 150) / (float)Convert.ToDouble(annotations.ratio));
float shapeX = annotations.IsResponsive == true ? annotations.x : ((annotations.x * 72 / 150) / (float)Convert.ToDouble(annotations.ratio));
float shapeY = annotations.IsResponsive == true ? annotations.y : ((annotations.y * 72 / 150) / (float)Convert.ToDouble(annotations.ratio));
float shapeW = annotations.IsResponsive == true ? annotations.w : ((annotations.w * 72 / 150) / (float)Convert.ToDouble(annotations.ratio));
double yaxis = (float)Convert.ToDouble(pageHeight) - (shapeH + shapeY);
// Sets Layer2 text and acro6layers
signatureAppearance.Layer2Text = " "; //Left blank so that it do not overwrite Esignature.
signatureAppearance.SetVisibleSignature(new iTextSharp.text.Rectangle((int)(shapeX), (int)yaxis, (int)(shapeX) + (int)shapeW, (int)yaxis + (int)shapeH), annotations.p, annotations.Id.ToString());
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(signatureAppearance, external, 8192);
Stream data = signatureAppearance.GetRangeStream();
string hashAlgorithm = "SHA256";
sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
hash = DigestAlgorithms.Digest(data, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, oc, null, CryptoStandard.CADES);
//create sha256 message digest
using (SHA256.Create())
{
sh = SHA256.Create().ComputeHash(sh);
}
//create hex encoded sha256 message digest
hexencodedDigest = new BigInteger(1, sh).ToString(16);
hexencodedDigest = hexencodedDigest.ToUpper();
if (hexencodedDigest.Length == 64)
{
check = true;
}
}
}
}
var identityGetResult = await IdentityGet(_appConfiguration.TrustedSignSettings.Url + "/identity", AccessToken, IdentityJson, hexencodedDigest);
//decode hex
byte[] dsg = FromHex(identityGetResult);
//include signature on PDF
sgn.SetExternalDigest(dsg, null, "RSA");
//create TimeStamp Client
ITSAClient tsc = new DssClient(AccessToken, _env, _appConfiguration.TrustedSignSettings.Url);
//byte[] ocspResponse = ocsp.GetEncoded(chain[0],chain[chain.Length -1], CertificateUtil.GetCRLURL(chain[0]));
//Collection<byte[]> crlBytes = CertificateUtil.fetchCrlBytes(x509certificate, chain);
byte[] encodedpkcs7 = sgn.GetEncodedPKCS7(hash, tsc, oc, null, CryptoStandard.CADES);
//adds PKCS7 format Signature on empty signature container
CreateSignature(tempPdf, finalsignedPdf, annotations.Id.ToString(), encodedpkcs7);
var finaltrustedSignedpdf = Path.Combine(_env.WebRootPath, "TempPath", annotations.userId, annotations.Id.ToString());
if (!Directory.Exists(finaltrustedSignedpdf))
{
Directory.CreateDirectory(finaltrustedSignedpdf);
}
finaltrustedSignedpdf = Path.Combine(finaltrustedSignedpdf, "FinaltrustedSignedpdf.pdf");
//adds LTV to signed document
AddLtv(finalsignedPdf, finaltrustedSignedpdf, ocsp, new CrlClientOnline(), tsc);
return finaltrustedSignedpdf;
}
用于创建签名
public void CreateSignature(string src, string dest, string fieldname, byte[] sig)
{
using (PdfReader reader = new PdfReader(src))
{
using (FileStream os = File.OpenWrite(dest))
{
IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
MakeSignature.SignDeferred(reader, fieldname, os, external);
}
}
}
原始 PDF 的交叉引用表中存在错误。 众所周知,Adobe 签名验证对此类错误很敏感(请参阅此答案和此答案),在某些情况下,它会将此类文件的签名显示为无效。
您应该要求该文档的来源提供没有该错误的版本
文档的第一个未签名修订的交叉引用表如下所示:
xref
0 55
0000000000 65535 f
0000000018 00000 n
0000000164 00000 n
0000000216 00000 n
0000000554 00000 n
0000003363 00000 n
0000003529 00000 n
0000003764 00000 n
0000003815 00000 n
0000003866 00000 n
0000004038 00000 n
0000004279 00000 n
0000004439 00000 n
0000004662 00000 n
0000004792 00000 n
0000004818 00000 n
0000004991 00000 n
0000005061 00000 n
0000005297 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000005466 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000006188 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000006236 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
0000000000 00000 n
54 18
0000006284 00000 n
0000006350 00000 n
0000006648 00000 n
0000171077 00000 n
0000171435 00000 n
0000171726 00000 n
0000171973 00000 n
0000323100 00000 n
0000323123 00000 n
0000324290 00000 n
0000324333 00000 n
0000324715 00000 n
0000326153 00000 n
0000328056 00000 n
0000328093 00000 n
0000328132 00000 n
0000328214 00000 n
0000328377 00000 n
如您所见,它由两个小节组成,第一个小节用于从 0 开始的 55 个对象,第二个小节用于从 54 开始的 18 个对象。
这是无效的,原因有两个:
首先(正如在上面引用的两个答案中已经解释过的)初始 PDF 修订版的交叉引用表必须仅包含一个部分!
对于从未增量更新的文件,交叉引用部分应仅包含一个子部分,其对象编号从 0 开始。
(ISO 32000-1 和 ISO 32000-2,在这两种情况下,第 7.5.4 节“交叉引用表”)
此外,该交叉引用表具有针对同一对象的两个条目,第一个小节的最后一个条目和第二个小节的第一个条目都与对象 54 相关。 这也是被禁止的:
一个给定的对象编号在一个节中不应有多个子节中的条目。
(同上)
根据相应代码的详细信息,在使用某些 PDF 处理器(例如 Adobe Acrobat Reader)处理 PDF 时,这可能会也可能不会导致任意问题。
在您的编辑中,您共享了许多文件。 特别是你分享了
VeriFinger_SDK_Brochure_2017-12-27.pdf
未签名的 pdf 网址: https : //www.sendspace.com/file/n0ckem
VeriFinger_SDK_Brochure_signed_witoutltv.Pdf
没有 LTV URL 的签名 pdf: https : //www.sendspace.com/file/t1gwp9
FinaltrustedSignedpdf.pdf
带有 LTV 单一签名的 pdf 签名: https : //www.sendspace.com/file/ba8leq
FinaltrustedSignedpdf.pdf(同名但与之前不同)
带有 LTV 两个符号的签名 pdf: https : //www.sendspace.com/file/6b53z1
根据您的代码,您以追加模式应用所有更改。 因此,后三个文件都必须由第一个文件VeriFinger_SDK_Brochure_2017-12-27.pdf加上一些附加数据组成。 但事实并非如此,后三个文件实际上都比第一个短。 因此,我必须假设第一个文件首先以某种方式处理,然后签名。
现在查看“原始文件” VeriFinger_SDK_Brochure_2017-12-27.pdf的交叉引用表(只需在文本查看器中打开它并滚动到其末尾),我们看到它是一个整体,只有一个小节。 它包含许多标记为空闲但没有间隙的条目。
但是,查看后三个文件的第一个修订版的交叉引用表,我们看到它们中的每一个都分为多个小节。 显然,从表中删除了标记为空闲的条目,从而生成了包含许多子部分的表。 可能这是为了优化尝试而设计的,但结果是损坏的 PDF。
因此,无论您在签名前对文件应用何种 PDF 处理器,该处理器都会损坏 PDF。
比较原始文件的文档信息和其他三个文件中的初始修订版本,在签名之前处理该文件的 PDF 处理器似乎是.NET 19.1 的 Aspose.PDF,因为Producer值已更改为该文件。
事实上,这似乎是一个已知的 Aspose 问题,例如,参见 2016 年 8 月开始的 Aspose 免费支持论坛上的PDF/A-1 转换创建无效的外部参照表线程。
它已被归档为 PDFNET-41272,并于 2017 年 2 月在 Aspose.Pdf for .NET 17.2.0 中标记为已修复,但正如同月在该论坛帖子中所报道的那样,它根本没有真正修复。
显然 Aspose 还没有修复这个错误,并且仍在处理它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.