在签名中添加LTV会使使用C#的pdf无效 [英] Adding LTV in signature makes pdf invalid using C#

查看:118
本文介绍了在签名中添加LTV会使使用C#的pdf无效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

将LTV添加到数字签名后,它表明文档已更改.

After Adding LTV to digital signature it shows document has changed.

从此查询中获取引用后: LTV之后证书签名,PDF显示文档已更改"

After taking ref from this que : After LTV Certification Signature, PDF shows "Document has been Changed"

我对我的代码进行了更改,它适用于所有文档,但适用于该文档: https://www.sendspace.com/file/3ulwn7 -显示无效签名.

I made changes in my code, It works fine with all document but for this document : https://www.sendspace.com/file/3ulwn7 - It shows Invalid signature.

我们还使用来自全局符号的文档签名服务.

we are also using document signing service from global sign for same.

以下添加LTV的代码:

Below code for adding 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参考的问题.但是,如果我在操作pdf时遇到任何问题,我将按以下方式共享我的代码.帮助将不胜感激.

I think the way i operate pdf in code is causing issue in reading/writing pdf . Somehow i could not get any validator for pdf which can identify issue which @mkl told about cross pdf ref. Yet i am sharing my code as below if there is any problem in how i operate pdf. Help would be appreciated.

当我添加新的pdf时,此pdf的旧签名无效.如果我添加LTV,那么即使是单个签名也无效.

This pdf's old signature becomes invalid when I add new one. and if i add LTV then it is invalid for even single signature.

未签名的pdf URL: https://www.sendspace.com/file/n0ckem

unsigned pdf URL: https://www.sendspace.com/file/n0ckem

没有LTV URL的签名pdf: https://www.sendspace.com/file/t1gwp9

Signed pdf without LTV URL : https://www.sendspace.com/file/t1gwp9

带有LTV单个符号的pdf签名: https://www.sendspace.com/file/ba8leq

Signed pdf with LTV single sign: https://www.sendspace.com/file/ba8leq

使用LTV的两个符号签署pdf: https://www.sendspace.com/file/6b53z1

Signed pdf with LTV two sign: https://www.sendspace.com/file/6b53z1

以下用于创建空容器和添加签名的代码:

Below code for creating empty container and adding signature :

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签名验证对此类错误敏感(请参阅此答案对连接的PDF签名和此答案),在某些情况下会将此类文件的签名显示为无效.

There is an error in the cross reference table of the original PDF. Adobe signature validation is known to be sensitive to such errors (see this answer and this answer), under certain circumstances it shows signatures of such files as invalid.

您应该要求文档的来源提供没有该错误的版本

You should ask the source of that document to provide a version without that error

该文档的第一个未签名版本的交叉引用表如下:

The cross reference table of the first, unsigned revision of the document looks like this:

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个对象.

As you see it consists of two subsections, the first one for 55 objects starting at 0, the second one of 18 objects starting at 54.

这是无效的,原因有两个:

This is invalid for two reasons:

  • 首先(正如上面提到的两个答案中已经解释的那样),初始PDF修订版的交叉引用表必须仅包含一个部分!

  • First of all (as already has been explained in the two answers referenced above) the cross reference table of the initial PDF revision must consist of a single section only!

对于从未进行过增量更新的文件,交叉引用节应仅包含一个子节,其对象编号从0开始.

For a file that has never been incrementally updated, the cross-reference section shall contain only one subsection, whose object numbering begins at 0.

(ISO 32000-1和ISO 32000-2,在两种情况下均属于第7.5.4节交叉引用表")

此外,该交叉引用表具有同一对象的两个条目,第一个子节的最后一个条目和第二个子节的第一个条目都与对象54相关.这也是禁止的:

Furthermore, that cross reference table has two entries for the same object, both the last entry of the first subsection and the first entry of the second subsection relate to object 54. This also is forbidden:

给定的对象编号不得在一个部分的一个以上的子部分中具有条目.

A given object number shall not have an entry in more than one subsection within a single section.

(ibidem)

根据相应代码的详细信息,在使用某些PDF处理器(例如Windows XP)处理PDF时,这可能会或可能不会导致任意问题. Adobe Acrobat Reader.

Depending on details of the respective code this may or may not result in arbitrary problems when processing the PDF with some PDF processor, e.g. Adobe Acrobat Reader.

在编辑中,您共享了许多文件.特别是您分享了

In your edit you shared a number of files. In particular you shared

  • VeriFinger_SDK_Brochure_2017-12-27.pdf

  • VeriFinger_SDK_Brochure_2017-12-27.pdf

未签名的pdf URL: https://www.sendspace.com/file/n0ckem

  • VeriFinger_SDK_Brochure_signed_witoutltv.Pdf

  • VeriFinger_SDK_Brochure_signed_witoutltv.Pdf

    没有LTV URL的签名pdf: https://www.sendspace.com/file/t1gwp9

  • FinaltrustedSignedpdf.pdf

  • FinaltrustedSignedpdf.pdf

    带有LTV单个符号的pdf签名: https://www.sendspace.com/file/ba8leq

  • FinaltrustedSignedpdf.pdf(名称相同,但与以前不同)

  • FinaltrustedSignedpdf.pdf (same name but differing from the previous)

    使用LTV的两个符号签署pdf: https://www.sendspace.com/file/6b53z1

  • 根据您的代码,您将所有更改应用于追加模式.因此,后三个文件每个都必须由第一个文件 VeriFinger_SDK_Brochure_2017-12-27.pdf 加上一些附加数据组成. 但是事实并非如此,后三个文件实际上都比第一个短 .因此,我必须假定第一个文件先以某种方式处理,然后再进行处理.签名.

    According to your code you apply all changes in append mode. Thus, the three latter files each have to consist of the first file VeriFinger_SDK_Brochure_2017-12-27.pdf plus some appended data. But that is not the case, the three latter files actually all are shorter than the first one. Thus, I have to assume that that first file is first processed somehow and then signed.

    现在查看原始文件"的交叉引用表 VeriFinger_SDK_Brochure_2017-12-27.pdf (只需在文本查看器中将其打开并滚动到末尾),我们看到它位于一个件,只有一个小节.它包含许多标记为 free 的条目,但没有空格.

    Now looking at the cross reference table of the "original file" VeriFinger_SDK_Brochure_2017-12-27.pdf (simply open it in a text viewer and scroll to its end) we see it is in one piece, just a single subsection. It contains a number of entries marked as free but no gaps.

    但是,通过查看后三个文件的第一个修订版的交叉引用表,我们看到它们每个都分为多个小节.显然,从表中删除了标记为 free 的条目的运行,从而形成了一个包含许多小节的表.可能这是作为优化尝试而设计的,但是结果是损坏的PDF.

    Looking at the cross reference tables of the first revisions of the latter three files, though, we see that each of them is split into multiple subsections. Apparently runs of entries marked free have been cut out of the table resulting in a table with many subsections. Probably this has been designed as an optimization attempt but the result is a damaged PDF.

    因此,无论您在签名前将其应用于文件的任何PDF处理器,该处理器都会损坏PDF.

    Thus, whatever PDF processor it is which you apply to your files before signing, exactly that processor damages the PDF.

    将原始文件的文档信息与其他三个文件中的初始修订版本进行比较,在签名之前处理该文件的PDF处理器似乎是 Aspose.PDF for .NET 19.1 ,因为生产者值更改为该值.

    Comparing the document information of your original file and the initial revisions in the other three files, a PDF processor handling the file before it is signed appears to be Aspose.PDF for .NET 19.1 because the Producer value is changed to that.

    确实,这似乎是一个已知的Aspose问题,例如,参见

    And indeed, this appears to be a known Aspose issue, see for example the PDF/A-1 conversion creates invalid XRef table thread on the Aspose free support forum, started in August 2016.

    它已归档为PDFNET-41272,并于2017年2月在Aspose.Pdf for .NET 17.2.0中标记为已修复,但正如同月在该论坛线程上所报道的那样,它根本没有得到修复.

    It has been filed as PDFNET-41272 and marked as fixed in Aspose.Pdf for .NET 17.2.0 in February 2017, but as reported on that very forum thread in the same month, it was not really fixed at all.

    显然,Aspose尚未修复此错误,并且仍在解决该问题.

    Apparently Aspose has not yet fixed this bug and still is working on it.

    这篇关于在签名中添加LTV会使使用C#的pdf无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆