So I have been working with the Java implementation of IText, but now I'm pretty much writing a port of our signing process to C#, and I've hit a snag. So when im signing my document using the SetVisibleSignature(rect, page, name) overload, it signs the document as expected(as long as the signature field does not exist), but when i use the overload SetVisibleSignature(name) to sign an existing field, it does not actually sign the document. Am I doing something stupid perhaps and missing something?
Thank you for any help.
Updated Code
using iTextSharp.text; using iTextSharp.text.pdf; using iTextSharp.text.pdf.security; using Org.BouncyCastle.Security; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using BouncyCastle = Org.BouncyCastle; public class DocumentSigner : IDocumentSigner { private const string _datetimeFormat = "dd/MM/yyyy hh:mm:ss"; private readonly IDateTimeProvider _dateTimeProvider; private readonly ISettingManager _settingManager; public DocumentSigner(IDateTimeProvider dateTimeProvider, ISettingManager settingManager) { Guard.ArgumentNotNull(dateTimeProvider, "dateTimeProvider"); Guard.ArgumentNotNull(settingManager, "settingManager"); _dateTimeProvider = dateTimeProvider; _settingManager = settingManager; } public byte[] Sign(Certificate certificate, SigningInformation information, List<SigningBlock> signingBlocks, List<MemberItemSignature> signatureImages, byte[] document, bool certify) { document = AddMetaData(information, document); document = AddSignatureFields(information, signingBlocks, document); return SignDocument(certificate, information, signingBlocks, signatureImages, document, certify); } private byte[] AddMetaData(SigningInformation information, byte[] document) { if (information.CustomProperties != null && information.CustomProperties.Any()) { using (MemoryStream outputStream = new MemoryStream()) { using (PdfReader reader = new PdfReader(document)) { using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true)) { Dictionary<string, string> currentProperties = reader.Info; foreach (string key in information.CustomProperties.Keys) { if (currentProperties.ContainsKey(key)) { currentProperties[key] = information.CustomProperties[key]; } else { currentProperties.Add(key, information.CustomProperties[key]); } } stamper.MoreInfo = currentProperties; } } return outputStream.ToArray(); } } return document; } private byte[] AddSignatureFields(SigningInformation information, List<SigningBlock> signingBlocks, byte[] document) { for (int i = 0; i < signingBlocks.Count; i++) { using (MemoryStream outputStream = new MemoryStream()) { using (PdfReader reader = new PdfReader(document)) { using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true)) { CreateSignatureField(reader, stamper, signingBlocks[i]); } } document = outputStream.ToArray(); } } return document; } private PdfSignatureAppearance CreatePdfAppearance(PdfStamper stamper, SigningInformation information, bool certify) { PdfSignatureAppearance appearance = stamper.SignatureAppearance; appearance.Location = information.Location; appearance.Reason = information.Purpose; appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION; CreatePdfAppearanceCertifyDocument(appearance, certify); return appearance; } private void CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance appearance, bool certify) { if (certify) { appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING; } else { appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED; } } private PdfStamper CreatePdfStamper(PdfReader reader, MemoryStream outputStream, byte[] document) { return PdfStamper.CreateSignature(reader, outputStream, '\0', null, true); } private void CreateSignatureField(PdfReader reader, PdfStamper stamper, SigningBlock signingBlock) { if (signingBlock == null) { return; } if (!DoesSignatureFieldExist(reader, signingBlock.Name)) { PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer); signatureField.SetWidget(new Rectangle(signingBlock.X, signingBlock.Y, signingBlock.X + signingBlock.Width, signingBlock.Y + signingBlock.Height), null); signatureField.Flags = PdfAnnotation.FLAGS_PRINT; signatureField.FieldName = signingBlock.Name; signatureField.Page = signingBlock.Page; stamper.AddAnnotation(signatureField, signingBlock.Page); } } private bool DoesSignatureFieldExist(PdfReader reader, string signatureFieldName) { if (String.IsNullOrWhiteSpace(signatureFieldName)) { return false; } return reader.AcroFields.DoesSignatureFieldExist(signatureFieldName); } private byte[] GetSignatureImage(List<MemberItemSignature> signatureImages, string signingBlockName) { MemberItemSignature signature = signingBlockName.Contains("Initial") ? signatureImages.Where(image => image.Use == SignatureUses.Initial).FirstOrDefault() : signatureImages.Where(image => image.Use == SignatureUses.Signature).FirstOrDefault(); if (signature != null) { return signature.Image; } else { return null; } } private byte[] SignDocument(Certificate certificate, SigningInformation information, List<SigningBlock> signingBlocks, List<MemberItemSignature> signatureImages, byte[] document, bool certify) { for (int i = 0; i < signingBlocks.Count; i++) { using (MemoryStream outputStream = new MemoryStream()) { using (PdfReader reader = new PdfReader(document)) { using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, document)) { SigningBlock signingBlock = signingBlocks[i]; PdfSignatureAppearance appearance = CreatePdfAppearance(stamper, information, certify && i == 0); SignDocumentSigningBlock(certificate, information, signingBlock, appearance, stamper, GetSignatureImage(signatureImages, signingBlock.Name)); } } document = outputStream.ToArray(); } } return document; } private void SignDocumentSigningBlock(Certificate certificate, SigningInformation information, SigningBlock block, PdfSignatureAppearance appearance, PdfStamper stamper, byte[] signatureImage) { X509Certificate2 x509Certificate = new X509Certificate2(certificate.Bytes, certificate.Password, X509KeyStorageFlags.Exportable); appearance.SetVisibleSignature(block.Name); SignDocumentSigningBlockWithImage(signatureImage, appearance); SignDocumentSigningBlockWithText(appearance, x509Certificate); using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)x509Certificate.PrivateKey) { IExternalSignature externalSignature = new PrivateKeySignature(DotNetUtilities.GetRsaKeyPair(rsa).Private, _settingManager["DocumentSigningEncryptionHashAlgorithm"]); MakeSignature.SignDetached(appearance, externalSignature, new BouncyCastle::X509.X509Certificate[] { DotNetUtilities.FromX509Certificate(x509Certificate) }, null, null, new TSAClientBouncyCastle(_settingManager["DocumentSigningTimestampingServiceAddress"]), Int32.Parse(_settingManager["DocumentSigningEstimatedTimestampSize"]), CryptoStandard.CMS); } } private void SignDocumentSigningBlockWithImage(byte[] signatureImage, PdfSignatureAppearance appearance) { if (signatureImage != null && signatureImage.Length > 0) { Image signatureImageInstance = Image.GetInstance(signatureImage); appearance.Image = signatureImageInstance; appearance.SignatureGraphic = signatureImageInstance; } } private void SignDocumentSigningBlockWithText(PdfSignatureAppearance appearance, X509Certificate2 x509Certificate) { if (x509Certificate == null) { return; } appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(x509Certificate); appearance.Layer2Font = new Font(Font.FontFamily.COURIER, 7.0f, Font.NORMAL, BaseColor.LIGHT_GRAY); appearance.Acro6Layers = true; } private string SignDocumentSigningBlockWithTextBuildText(X509Certificate2 x509Certificate) { Dictionary<string, string> fields = SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerName.Name); string organization = fields.Keys.Contains("O") ? fields["O"] : String.Empty; string commonName = fields.Keys.Contains("CN") ? fields["CN"] : String.Empty; string signDate = _dateTimeProvider.Now.ToString(_datetimeFormat); string expirationDate = x509Certificate.NotAfter.ToString(_datetimeFormat); return "Digitally signed by " + organization + "\nSignee: " + commonName + "\nSign date: " + signDate + "\n" + "Expiration date: " + expirationDate; } private Dictionary<string, string> SignDocumentSigningBlockWithTextBuildTextIssuerFields(string issuer) { Dictionary<string, string> fields = new Dictionary<string, string>(); string[] issuerFields = issuer.Split(','); foreach (string field in issuerFields) { string[] fieldSplit = field.Split('='); string key = fieldSplit[0].Trim(); string value = fieldSplit[1].Trim(); if (!fields.Keys.Contains(key)) { fields.Add(key, value); } else { fields[key] = value; } } return fields; } }
Values
_settingManager["DocumentSigningEncryptionHashAlgorithm"] = "SHA-256"; _settingManager["DocumentSigningTimestampingServiceAddress"] = "http://services.globaltrustfinder.com/adss/tsa"; _settingManager["DocumentSigningEstimatedTimestampSize"] = 104000;