Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
636 views
in Technique[技术] by (71.8m points)

c# - I want to sign a pdf document with ITextSharp and return ltv pdf enabled file

The method receives pdf document as byte array that should be signed, certificate to sign with and TSA client and it returns signed document as byte array or null if there was error. Now it returns signed pdf document but it isnt LTV enabled. Signed document must be LTV enabled. How do I make the document being returned LTV enabled? I'll be very grateful for any suggestions.

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, '');

    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;
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

In general you cannot expect the signature creation step to return a LTV-enabled signature.

Leonard Rosenthol (Adobe's prime PDF Guru) remarked on the iText mailing list in early 2013 that while it is possible to have the signature container itself already contain all the information required for a LTV enabled signature, this is very uncommon and not always possible.

(There are exceptions, e.g. there was a Swisscom signing service generating signature containers with all the extra information required for a LTV enabled integrated PDF signature.)

Thus, in general you'll have to add all the missing information in a second step.

Such a second step, on the other hand, means that such a second pass may interfere with signing with CertificationLevel = CERTIFIED_NO_CHANGES_ALLOWED - the current PDF specification requires that even for such a certification level incremental updates are allowed if they contain only signature validation information but I have not yet seen Adobe Reader not complain about in such a case. So you might have to loosen your certification level for LTV enabling.

For iText 5 / Java and iText 7 / Java helper classes that execute this second step can be found in this answer (iText 5) and this answer (iText 7).

I have ported the Java helper class for iText 5 to 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}
", 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
", 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 =

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...