iTextSharp and Windows SSL Certificates

Well, it took a bit of work, but I figured out how to add certificates and intermediate certificates into a PDF document while signing it using iTextSharp. The examples they had were close, but not quite there. They had hard coded the buffer size to 4000. Seemed a bit small, it got real tight when I had 2 intermediate certificates, so I made a change to that to use a more dynamic approach. I did a 4000 + RawData.Length for each certificate. Definitely opened up some more room to fit everything in there. After doing that in their little SignMsg method, I changed the IncludeOptions to be ExcludeRoot. This way, it would import the entire chain, skipping the root certificate (it should already be on the client's computer) You can always include it, but it will still not be trusted. So that's all up to you. If doing a windows SSL cert, you will need to check the correct options in Adobe Reader or Acrobat. Its in the Preferences->Security->Advanced->Windows Integration->Check all 3.

Here's my final proof of concept code:

using System; 
using System.Configuration; 
using System.Collections.Generic; 
using System.IO; 
using System.Security.Cryptography; 
using System.Security.Cryptography.Pkcs; 
using System.Security.Cryptography.X509Certificates; 
using iTextSharp.text; 
using iTextSharp.text.pdf; 
using System.Linq; 
 
namespace PDFStuff 
{ 
    class PDFPlayground 
    { 
        public static X509Store GetStore() 
        { 
            X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine); 
            store.Open(OpenFlags.MaxAllowed); 
            return store; 
        } 

        public static X509Certificate2 GetCertificateByThumbprint(string thumbprint, bool requirePrivate) 
        { 
            if (string.IsNullOrEmpty(thumbprint)) 
            { 
                return null; 
            } 

            X509Store store = GetStore(); 
            X509Certificate2 result = null; 
            try
            { 
                foreach (X509Certificate2 c in store.Certificates) 
                { 
                    if (c.Thumbprint == thumbprint && 
                        (c.HasPrivateKey || !requirePrivate)) 
                    {
                        result = c; 
                    }
                } 
            } 
            catch (Exception exception)
            { 
            } 
            finally
            { 
                store.Close(); 
            } 

            return result; 
        } 

        public static byte[] SignDocument(byte[] pdfDocument) 
        { 
            //get the certificate 
            X509Certificate2 cert = GetCertificateByThumbprint(ConfigurationManager.AppSettings["PDFCertificateThumbprint"], true); 

            if (cert == null) 
            { 
                //no cert, dont do anything 
                return pdfDocument; 
            } 

            using (MemoryStream stream = new MemoryStream()) 
            { 
                //set initial variables 
                X509Chain chain = X509Chain.Create(); 
                PdfReader reader = new PdfReader(pdfDocument); 
                PdfStamper stamper = PdfStamper.CreateSignature(reader, stream, '\0', null, false); 
                PdfSignatureAppearance sap = stamper.SignatureAppearance; 
                PdfSignature signatureDictionary = new PdfSignature(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1); 
                Dictionary<PdfName, int> reservedSpace = new Dictionary<PdfName, int>(); 
                HashAlgorithm sha = new SHA1CryptoServiceProvider(); 
                int read = 0; 
                byte[] buff = new byte[8192]; 
                int csize = 0; 
                byte[] outc = null; 
                Stream sapStream = null; 
                byte[] pk = null; 
                PdfDictionary certificateDictionary = new PdfDictionary(); 

                //build my certificate chain 
                chain.Build(cert); 

                //guess the size of the certificates and signature. 
                //we don't know for sure how big it really is going to be until after we hash it, so, we give it 4k for the signature 
                //and add the size of all the raw data of the certificates. 
                csize = 4096 + //4K for the signature 
                (from c in chain.ChainElements.OfType<X509ChainElement>() 
                select c.Certificate.RawData.Length).Sum(); //add up the size of each of the certificates and add it up 

                //certificate buffer 
                outc = new byte[csize]; 

                //protect certain features of the document 
                stamper.SetEncryption(null, 
                                      Guid.NewGuid().ToByteArray(), //random password 
                                      PdfWriter.ALLOW_PRINTING | 
                                      PdfWriter.ALLOW_COPY | 
                                      PdfWriter.ALLOW_SCREENREADERS, 
                                      PdfWriter.ENCRYPTION_AES_256); 


                //////sign the document 
                sap.SetVisibleSignature(new Rectangle(1, 1, 1, 1), 1, "Signature field"); //we use a rectangle of 1,1,1,1 to hide it. 
                sap.SignDate = DateTime.Parse(cert.GetEffectiveDateString()); 
                sap.Reason = "reason, i am"; //reason for being signed 
                sap.Location = "location, i am not"; //location it was signed 

                //set to use the wincer_signed, its a windows cert, not sure if this is needed. 
                sap.SetCrypto(null, null, null, PdfSignatureAppearance.WINCER_SIGNED); 

                //set some stuff in the signature dictionary. we dont need to set the appearance stuff because the signature is hidden. 
                signatureDictionary.Reason = sap.Reason; 
                signatureDictionary.Location = sap.Location; 
                signatureDictionary.Date = new PdfDate(sap.SignDate); 
                signatureDictionary.Name = cert.Subject; //certificate name 

                //set the appearance only stuff. 
                sap.Acro6Layers = true; //set to true to make adobe 6.x and higher happier with the layers. 
                sap.Render = PdfSignatureAppearance.SignatureRender.NameAndDescription; //what to show in the signature display. since its hidden, we dont care 
                // but if we do allow an image, then we have to put in an image. 

                //set the crypto dictionary 
                sap.CryptoDictionary = signatureDictionary; 

                //reserve some space for certs and signatures 
                reservedSpace[PdfName.CONTENTS] = csize * 2 + 2; //*2 because binary data is stored as hex strings (i think). +2 for end of field 
                sap.PreClose(reservedSpace); //actually reserve it 

                //build the signature 
                sapStream = sap.RangeStream; 
                while ((read = sapStream.Read(buff, 0, 8192)) > 0) 
                { 
                    sha.TransformBlock(buff, 0, read, buff, 0); 
                } 
                sha.TransformFinalBlock(buff, 0, 0); 
                pk = SignMsg(sha.Hash, chain, false); 

                //put the certs and signature into the reserved buffer 
                Array.Copy(pk, 0, outc, 0, pk.Length); 

                //put the reserved buffer into the reserved space 
                certificateDictionary.Put(PdfName.CONTENTS, new PdfString(outc).SetHexWriting(true)); 

                //write the signature 
                sap.Close(certificateDictionary); 

                //close the stamper and save it 
                stamper.Close(); 

                //close the reader 
                reader.Close(); 

                //return the saved pdf 
                return stream.GetBuffer(); 
            } 
        } 

        public static byte[] SignMsg(Byte[] msg, X509Chain chain, bool detached) 
        { 
            // Place message in a ContentInfo object. 
            // This is required to build a SignedCms object. 
            ContentInfo contentInfo = new ContentInfo(msg); 

            // Instantiate SignedCms object with the ContentInfo above. 
            // Has default SubjectIdentifierType IssuerAndSerialNumber. 
            SignedCms signedCms = new SignedCms(contentInfo, detached); 

            // Formulate a CmsSigner object for the signer. 
            CmsSigner cmsSigner = new CmsSigner(chain.ChainElements[0].Certificate); //first cert in the chain is the signer cert 

            // Do the whole certificate chain. This way intermediate certificates get sent across as well. 
            cmsSigner.IncludeOption = X509IncludeOption.ExcludeRoot; 

            // Sign the CMS/PKCS #7 message. The second argument is 
            // needed to ask for the pin. 
            signedCms.ComputeSignature(cmsSigner, false); 

            // Encode the CMS/PKCS #7 message. 
            return signedCms.Encode(); 
        } 
    } 
}