【问题标题】:I want to sign a pdf document with ITextSharp and return ltv pdf enabled file我想用 ITextSharp 签署一个 pdf 文档并返回启用 ltv pdf 的文件
【发布时间】:2019-02-19 05:36:03
【问题描述】:

该方法接收作为应签名的字节数组的 pdf 文档、要签名的证书和 TSA 客户端,如果出现错误,它将以字节数组的形式返回已签名的文档或 null。现在它返回签名的 pdf 文档,但它没有启用 LTV。 签名文档必须启用 LTV。如何使返回的文档启用 LTV?如有任何建议,我将不胜感激。

public byte[] Sign(byte[] document, X509Certificate2 certificate, ITSAClient tsaClient)
    {
    byte[] signedDocument = null;

    IExternalSignature signature = new X509Certificate2Signature(certificate, "SHA-1");
    Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();
    Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { cp.ReadCertificate(certificate.RawData) };

    PdfReader reader = new PdfReader(document);
    MemoryStream ms = new MemoryStream();
    PdfStamper st = PdfStamper.CreateSignature(reader, ms, '\0');

    PdfSignatureAppearance sap = st.SignatureAppearance;
    sap.CertificationLevel = PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED;
    sap.SignatureCreator = "NAME";
    sap.Reason = "REASON";
    sap.Contact = "CONTACT";
    sap.Location = "LOCATION";
    sap.SignDate = DateTime.Now;

    RectangleF rectangle = new RectangleF(400.98139f, 54.88828f, 530, 84.88828f);
    sap.Layer2Font = iTextSharp.text.FontFactory.GetFont(BaseFont.TIMES_ROMAN, BaseFont.CP1257, 7f);
    sap.Layer2Font.Color = iTextSharp.text.BaseColor.RED;
    sap.Layer2Text = string.Format("Signed for testing: {0}", DateTime.Now.ToString("dd.MM.yyyy."));
    sap.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
    sap.SetVisibleSignature(new iTextSharp.text.Rectangle(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height), 1, null);

    MakeSignature.SignDetached(sap, signature, chain, null, null, tsaClient, 0, CryptoStandard.CMS);

    st.Close();

    ms.Flush();
    signedDocument = ms.ToArray();
    ms.Close();

    reader.Close();

    return signedDocument;
}

【问题讨论】:

  • This answer 专注于使用 iText 5 for Java 启用 LTV,this answer 专注于使用 iText 7 for Java 启用 LTV。您只需移植适用于您的用例的变体。
  • 很抱歉给您带来不便。我正在使用 itextShap 库。不是文本。而且我对java知之甚少。
  • 请问谁能指导我如何将This java code 转换为 C# 并使用 itextSharp 在 pdf 上启用 ltv?
  • “我正在使用 itextShap 库,而不是 itext。” - iTextSharp 是 iText 从 Java 到 C# 到 5.5.x 版本的端口名称.这样,你就快到了。 “请谁能指导我如何将这个 java 代码转换为 C#” - 可能我下周会有一些空闲时间。
  • 好的@mkl 感谢您的澄清。

标签: c# pdf itext


【解决方案1】:

通常,您不能期望签名创建步骤返回启用 LTV 的签名。

Leonard Rosenthol(Adobe 的主要 PDF 专家)评论 on the iText mailing list in early 2013 说,虽然可以让签名容器本身已经包含启用 LTV 的签名所需的所有信息,但这种情况非常少见,而且并非总是可行。

(也有例外,例如,Swisscom 签名服务生成签名容器,其中包含启用 LTV 的集成 PDF 签名所需的所有额外信息。)

因此,通常您必须在第二步中添加所有缺失的信息。

另一方面,这样的第二步意味着这样的第二次通过可能会干扰使用CertificationLevel = CERTIFIED_NO_CHANGES_ALLOWED 进行签名 - 当前的 PDF 规范要求即使对于这样的认证级别,也允许增量更新,如果它们只包含签名验证信息但我还没有看到 Adob​​e Reader 在这种情况下没有抱怨。因此,您可能不得不放宽 LTV 启用的认证级别。

可以在this answer (iText 5)this answer (iText 7) 中找到执行此第二步的 iText 5 / Java 和 iText 7 / Java 帮助程序类。

我已将 iText 5 的 Java 帮助程序类移植到 C#:

using iTextSharp.text;
using iTextSharp.text.error_messages;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.security;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Ocsp;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Ocsp;
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;

[...]

class AdobeLtvEnabling
{
    /**
     * Use this constructor with a {@link PdfStamper} in append mode. Otherwise
     * the existing signatures will be damaged.
     */
    public AdobeLtvEnabling(PdfStamper pdfStamper)
    {
        this.pdfStamper = pdfStamper;
    }

    /**
     * Call this method to have LTV information added to the {@link PdfStamper}
     * given in the constructor.
     */
    public void enable(IOcspClient ocspClient, ICrlClient crlClient)
    {
        AcroFields fields = pdfStamper.AcroFields;
        bool encrypted = pdfStamper.Reader.IsEncrypted();

        List<String> names = fields.GetSignatureNames();
        foreach (String name in names)
        {
            PdfPKCS7 pdfPKCS7 = fields.VerifySignature(name);
            PdfDictionary signatureDictionary = fields.GetSignatureDictionary(name);
            X509Certificate certificate = pdfPKCS7.SigningCertificate;
            addLtvForChain(certificate, ocspClient, crlClient, getSignatureHashKey(signatureDictionary, encrypted));
        }

        outputDss();
    }

    //
    // the actual LTV enabling methods
    //
    void addLtvForChain(X509Certificate certificate, IOcspClient ocspClient, ICrlClient crlClient, PdfName key)
    {
        if (seenCertificates.Contains(certificate))
            return;
        seenCertificates.Add(certificate);
        ValidationData validationData = new ValidationData();

        while (certificate != null)
        {
            Console.WriteLine(certificate.SubjectDN);
            X509Certificate issuer = getIssuerCertificate(certificate);
            validationData.certs.Add(certificate.GetEncoded());
            byte[] ocspResponse = ocspClient.GetEncoded(certificate, issuer, null);
            if (ocspResponse != null)
            {
                Console.WriteLine("  with OCSP response");
                validationData.ocsps.Add(ocspResponse);
                X509Certificate ocspSigner = getOcspSignerCertificate(ocspResponse);
                if (ocspSigner != null)
                {
                    Console.WriteLine("  signed by {0}\n", ocspSigner.SubjectDN);
                }
                addLtvForChain(ocspSigner, ocspClient, crlClient, getOcspHashKey(ocspResponse));
            }
            else
            {
                ICollection<byte[]> crl = crlClient.GetEncoded(certificate, null);
                if (crl != null && crl.Count > 0)
                {
                    Console.WriteLine("  with {0} CRLs\n", crl.Count);
                    foreach (byte[] crlBytes in crl)
                    {
                        validationData.crls.Add(crlBytes);
                        addLtvForChain(null, ocspClient, crlClient, getCrlHashKey(crlBytes));
                    }
                }
            }
            certificate = issuer;
        }

        validated[key] = validationData;
    }

    void outputDss()
    {
        PdfWriter writer = pdfStamper.Writer;
        PdfReader reader = pdfStamper.Reader;

        PdfDictionary dss = new PdfDictionary();
        PdfDictionary vrim = new PdfDictionary();
        PdfArray ocsps = new PdfArray();
        PdfArray crls = new PdfArray();
        PdfArray certs = new PdfArray();

        writer.AddDeveloperExtension(PdfDeveloperExtension.ESIC_1_7_EXTENSIONLEVEL5);
        writer.AddDeveloperExtension(new PdfDeveloperExtension(PdfName.ADBE, new PdfName("1.7"), 8));

        PdfDictionary catalog = reader.Catalog;
        pdfStamper.MarkUsed(catalog);
        foreach (PdfName vkey in validated.Keys)
        {
            PdfArray ocsp = new PdfArray();
            PdfArray crl = new PdfArray();
            PdfArray cert = new PdfArray();
            PdfDictionary vri = new PdfDictionary();
            foreach (byte[] b in validated[vkey].crls)
            {
                PdfStream ps = new PdfStream(b);
                ps.FlateCompress();
                PdfIndirectReference iref = writer.AddToBody(ps, false).IndirectReference;
                crl.Add(iref);
                crls.Add(iref);
            }
            foreach (byte[] b in validated[vkey].ocsps)
            {
                PdfStream ps = new PdfStream(buildOCSPResponse(b));
                ps.FlateCompress();
                PdfIndirectReference iref = writer.AddToBody(ps, false).IndirectReference;
                ocsp.Add(iref);
                ocsps.Add(iref);
            }
            foreach (byte[] b in validated[vkey].certs)
            {
                PdfStream ps = new PdfStream(b);
                ps.FlateCompress();
                PdfIndirectReference iref = writer.AddToBody(ps, false).IndirectReference;
                cert.Add(iref);
                certs.Add(iref);
            }
            if (ocsp.Length > 0)
                vri.Put(PdfName.OCSP, writer.AddToBody(ocsp, false).IndirectReference);
            if (crl.Length > 0)
                vri.Put(PdfName.CRL, writer.AddToBody(crl, false).IndirectReference);
            if (cert.Length > 0)
                vri.Put(PdfName.CERT, writer.AddToBody(cert, false).IndirectReference);
            vri.Put(PdfName.TU, new PdfDate());
            vrim.Put(vkey, writer.AddToBody(vri, false).IndirectReference);
        }
        dss.Put(PdfName.VRI, writer.AddToBody(vrim, false).IndirectReference);
        if (ocsps.Length > 0)
            dss.Put(PdfName.OCSPS, writer.AddToBody(ocsps, false).IndirectReference);
        if (crls.Length > 0)
            dss.Put(PdfName.CRLS, writer.AddToBody(crls, false).IndirectReference);
        if (certs.Length > 0)
            dss.Put(PdfName.CERTS, writer.AddToBody(certs, false).IndirectReference);
        catalog.Put(PdfName.DSS, writer.AddToBody(dss, false).IndirectReference);
    }

    //
    // VRI signature hash key calculation
    //
    static PdfName getCrlHashKey(byte[] crlBytes)
    {
        X509Crl crl = new X509Crl(CertificateList.GetInstance(crlBytes)); 
        byte[] signatureBytes = crl.GetSignature();
        DerOctetString octetString = new DerOctetString(signatureBytes);
        byte[] octetBytes = octetString.GetEncoded();
        byte[] octetHash = hashBytesSha1(octetBytes);
        PdfName octetName = new PdfName(Utilities.ConvertToHex(octetHash));
        return octetName;
    }

    static PdfName getOcspHashKey(byte[] basicResponseBytes)
    {
        BasicOcspResponse basicResponse = BasicOcspResponse.GetInstance(Asn1Sequence.GetInstance(basicResponseBytes));
        byte[] signatureBytes = basicResponse.Signature.GetBytes();
        DerOctetString octetString = new DerOctetString(signatureBytes);
        byte[] octetBytes = octetString.GetEncoded();
        byte[] octetHash = hashBytesSha1(octetBytes);
        PdfName octetName = new PdfName(Utilities.ConvertToHex(octetHash));
        return octetName;
    }

    static PdfName getSignatureHashKey(PdfDictionary dic, bool encrypted)
    {
        PdfString contents = dic.GetAsString(PdfName.CONTENTS);
        byte[] bc = contents.GetOriginalBytes();
        if (PdfName.ETSI_RFC3161.Equals(PdfReader.GetPdfObject(dic.Get(PdfName.SUBFILTER))))
        {
            using (Asn1InputStream din = new Asn1InputStream(bc))
            {
                Asn1Object pkcs = din.ReadObject();
                bc = pkcs.GetEncoded();
            }
        }
        byte[] bt = hashBytesSha1(bc);
        return new PdfName(Utilities.ConvertToHex(bt));
    }

    static byte[] hashBytesSha1(byte[] b)
    {
        SHA1 sha = new SHA1CryptoServiceProvider();
        return sha.ComputeHash(b);
    }

    //
    // OCSP response helpers
    //
    static X509Certificate getOcspSignerCertificate(byte[] basicResponseBytes)
    {
        BasicOcspResponse borRaw = BasicOcspResponse.GetInstance(Asn1Sequence.GetInstance(basicResponseBytes));
        BasicOcspResp bor = new BasicOcspResp(borRaw);

        foreach (X509Certificate x509Certificate in bor.GetCerts())
        {
            if (bor.Verify(x509Certificate.GetPublicKey()))
                return x509Certificate;
        }

        return null;
    }

    static byte[] buildOCSPResponse(byte[] BasicOCSPResponse)
    {
        DerOctetString doctet = new DerOctetString(BasicOCSPResponse);
        Asn1EncodableVector v2 = new Asn1EncodableVector();
        v2.Add(OcspObjectIdentifiers.PkixOcspBasic);
        v2.Add(doctet);
        DerEnumerated den = new DerEnumerated(0);
        Asn1EncodableVector v3 = new Asn1EncodableVector();
        v3.Add(den);
        v3.Add(new DerTaggedObject(true, 0, new DerSequence(v2)));            
        DerSequence seq = new DerSequence(v3);
        return seq.GetEncoded();
    }

    //
    // X509 certificate related helpers
    //
    static X509Certificate getIssuerCertificate(X509Certificate certificate)
    {
        String url = getCACURL(certificate);
        if (url != null && url.Length > 0)
        {
            HttpWebRequest con = (HttpWebRequest)WebRequest.Create(url);
            HttpWebResponse response = (HttpWebResponse)con.GetResponse();
            if (response.StatusCode != HttpStatusCode.OK)
                throw new IOException(MessageLocalization.GetComposedMessage("invalid.http.response.1", (int)response.StatusCode));
            //Get Response
            Stream inp = response.GetResponseStream();
            byte[] buf = new byte[1024];
            MemoryStream bout = new MemoryStream();
            while (true)
            {
                int n = inp.Read(buf, 0, buf.Length);
                if (n <= 0)
                    break;
                bout.Write(buf, 0, n);
            }
            inp.Close();

            var cert2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(bout.ToArray());

            return new X509Certificate(X509CertificateStructure.GetInstance(cert2.GetRawCertData()));
        }

        try
        {
            certificate.Verify(certificate.GetPublicKey());
            return null;
        }
        catch (Exception e)
        {
        }

        foreach (X509Certificate candidate in extraCertificates)
        {
            try
            {
                certificate.Verify(candidate.GetPublicKey());
                return candidate;
            }
            catch (Exception e)
            {
            }
        }

        return null;
    }

    static String getCACURL(X509Certificate certificate)
    {
        try
        {
            Asn1Object obj = getExtensionValue(certificate, X509Extensions.AuthorityInfoAccess.Id);
            if (obj == null)
            {
                return null;
            }

            Asn1Sequence AccessDescriptions = (Asn1Sequence)obj;
            for (int i = 0; i < AccessDescriptions.Count; i++)
            {
                Asn1Sequence AccessDescription = (Asn1Sequence)AccessDescriptions[i];
                if (AccessDescription.Count != 2)
                {
                    continue;
                }
                else
                {
                    if ((AccessDescription[0] is DerObjectIdentifier) && ((DerObjectIdentifier)AccessDescription[0]).Id.Equals("1.3.6.1.5.5.7.48.2"))
                    {
                        String AccessLocation = getStringFromGeneralName((Asn1Object)AccessDescription[1]);
                        return AccessLocation == null ? "" : AccessLocation;
                    }
                }
            }
        }
        catch
        {
        }
        return null;
    }

    static Asn1Object getExtensionValue(X509Certificate certificate, String oid)
    {
        byte[] bytes = certificate.GetExtensionValue(new DerObjectIdentifier(oid)).GetDerEncoded();
        if (bytes == null) {
            return null;
        }
        Asn1InputStream aIn = new Asn1InputStream(new MemoryStream(bytes));
        Asn1OctetString octs = (Asn1OctetString)aIn.ReadObject();
        aIn = new Asn1InputStream(new MemoryStream(octs.GetOctets()));
        return aIn.ReadObject();
    }

    private static String getStringFromGeneralName(Asn1Object names)
    {
        Asn1TaggedObject taggedObject = (Asn1TaggedObject) names;
        return Encoding.GetEncoding(1252).GetString(Asn1OctetString.GetInstance(taggedObject, false).GetOctets());
    }

    //
    // inner class
    //
    class ValidationData
    {
        public IList<byte[]> crls = new List<byte[]>();
        public IList<byte[]> ocsps = new List<byte[]>();
        public IList<byte[]> certs = new List<byte[]>();
    }

    //
    // member variables
    //
    PdfStamper pdfStamper;
    ISet<X509Certificate> seenCertificates = new HashSet<X509Certificate>();
    IDictionary<PdfName, ValidationData> validated = new Dictionary<PdfName, ValidationData>();

    public static List<X509Certificate> extraCertificates = new List<X509Certificate>();
}

你通常像这样使用那个类

PdfReader reader = new PdfReader(signedDocument);
FileStream os = new FileStream(ENABLED_PDF, FileMode.Create);
PdfStamper pdfStamper = new PdfStamper(reader, os, (char)0, true);

AdobeLtvEnabling adobeLtvEnabling = new AdobeLtvEnabling(pdfStamper);
IOcspClient ocsp = new OcspClientBouncyCastle();
ICrlClient crl = new CrlClientOnline();
adobeLtvEnabling.enable(ocsp, crl);

pdfStamper.Close();

原来的Java类有这样的限制

假定可以使用 AIA 条目构建完整的证书链。

这意味着有问题的每个(非根)证书都包含一个用于下载其颁发者证书的 URL。

情况并非总是如此。为了解决这个限制,我添加了一个public static List extraCertificates,如果某些证书中没有颁发者证书 URL,则可以将其他证书作为颁发者证书候选者进行测试。在调用 enable 之前,您必须将这些额外的证书添加为 BouncyCastle X509Certificate 对象。

【讨论】:

  • 我尝试使用相同的代码但遇到问题,在这里发布问题:stackoverflow.com/questions/56458787/…,请您检查一下,非常感谢。
  • @sonsha 该问题的原因是,对于签名中的一个证书,相关的 CRL 以 base64 编码形式提供下载,而上面的代码假定它是二进制文件,而不是base64 编码的文本形式。您可以找到上述代码的扩展,它还处理 base64 编码的 CRL in this answer
  • enable 中使用PdfPKCS7 将支持的签名限制为iText 也支持的签名。在 this answer 中,enable 改为使用 BouncyCastle 类,它支持更广泛的签名算法,实际上比 Adob​​e Reader 支持的范围更广。
猜你喜欢
  • 2016-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-10
  • 1970-01-01
  • 2015-03-12
  • 2014-02-08
  • 1970-01-01
相关资源
最近更新 更多