ITextSharp SetVisibleSignature not working as expected

天涯浪子 提交于 2019-12-02 00:20:11

问题


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;

回答1:


The code provided by the OP references and accesses multiple objects of unknown classes. To make it runnable, therefore, it had to be cut down to be self-contained.

The cut-down version fortunately could still be used to reproduce and analyze the issue, cf. the post scriptum. Any statement from here on is based on the behavior of this cut-down version.

The issue observed by the OP could be reproduced using iTextSharp 5.5.7 (and analogously using iText 5.5.7), and also very interestingly it could not be reproduced using version 5.5.6 of either library. As I'm more into Java, I looked into the changes in iText. They had been ported to iTextSharp in a very faithful fashion.

Indeed, this issue is a regression, signing pre-existing empty signature fields in append mode is broken in iText(Sharp) 5.5.7.

Between 5.5.6 and 5.5.7 a change has been made to PdfSignatureAppearance.preClose. If signing an existing signature field, the code used to manipulate the first widget of the signature field in question (af.getFieldItem(name).getWidget(0)), now it works on the associated merged dictionary (af.getFieldItem(name).getMerged(0)).

Unfortunately, while the former was an object actually existing in the original PDF and, therefore, calling writer.markUsed for it marked its changed contents for writing to the incremental update section, the latter does not correspond to an object in the original PDF (it is a virtual aggregation of multiple objects), so calling writer.markUsed for it does not mark the changes to be written as incremental update anymore.

So, while the actual signature value still is written to the file, it is not connected to the designated signature field anymore.


The change has been done to fix the method behavior.

Before this preClosed worked incorrectly because it retrieved field dictionary as widget annotation. It's incorrect in case when field and widget dicts are not merged. In case they were merged, everything worked as expected. The latter is the most possible case for digital signature fields, but it isn't obligated according to spec.

(DEV-1448)

This is correct, in case of separate field and widget dictionaries certain changes have to be made to the field, not the widget. Merely the implementation does not work as desired in append mode.


PS: This is the cut-down version of the OP's code:

public class DocumentSigner
{
    private const string _datetimeFormat = "dd/MM/yyyy hh:mm:ss";

    public byte[] Sign(ICollection<X509Certificate> chain, ICipherParameters pk, string signingBlock, byte[] document, bool certify, String pattern = null)
    {
        document = AddMetaData(document);
        if (pattern != null)
            File.WriteAllBytes(String.Format(pattern, "1"), document);
        document = AddSignatureFields(signingBlock, document);
        if (pattern != null)
            File.WriteAllBytes(String.Format(pattern, "2"), document);
        return SignDocument(chain, pk, signingBlock, document, certify);
    }

    private byte[] AddMetaData(byte[] document)
    {
        return document;
    }

    private byte[] AddSignatureFields(string signingBlock, byte[] document)
    {
            using (MemoryStream outputStream = new MemoryStream())
            {
                using (PdfReader reader = new PdfReader(document))
                {
                    using (PdfStamper stamper = new PdfStamper(reader, outputStream, '\0', true))
                    {
                        CreateSignatureField(reader, stamper, signingBlock);
                    }
                }

                document = outputStream.ToArray();
            }

        return document;
    }

    private PdfSignatureAppearance CreatePdfAppearance(PdfStamper stamper, 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, string signingBlock)
    {
        if (signingBlock == null)
        {
            return;
        }

        if (!DoesSignatureFieldExist(reader, signingBlock))
        {
            PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer);
            signatureField.SetWidget(new Rectangle(100, 100, 200, 200), null);
            signatureField.Flags = PdfAnnotation.FLAGS_PRINT;
            signatureField.FieldName = signingBlock;
            signatureField.Page = 1;
            stamper.AddAnnotation(signatureField, 1);
        }
    }

    private bool DoesSignatureFieldExist(PdfReader reader, string signatureFieldName)
    {
        if (String.IsNullOrWhiteSpace(signatureFieldName))
        {
            return false;
        }

        return reader.AcroFields.DoesSignatureFieldExist(signatureFieldName);
    }

    private byte[] GetSignatureImage(string signingBlockName)
    {
        return null;
    }

    private byte[] SignDocument(ICollection<X509Certificate> chain, ICipherParameters pk, string signingBlock, byte[] document, bool certify)
    {
            using (MemoryStream outputStream = new MemoryStream())
            {
                using (PdfReader reader = new PdfReader(document))
                {
                    using (PdfStamper stamper = CreatePdfStamper(reader, outputStream, document))
                    {
                        PdfSignatureAppearance appearance = CreatePdfAppearance(stamper, certify);

                        SignDocumentSigningBlock(chain, pk, signingBlock, appearance, stamper, GetSignatureImage(signingBlock));
                    }
                }

                document = outputStream.ToArray();
            }

        return document;
    }

    private void SignDocumentSigningBlock(ICollection<X509Certificate> chain, ICipherParameters pk, string block, PdfSignatureAppearance appearance, PdfStamper stamper, byte[] signatureImage)
    {
        appearance.SetVisibleSignature(block);
        SignDocumentSigningBlockWithImage(signatureImage, appearance);
        SignDocumentSigningBlockWithText(appearance, chain.First());

        IExternalSignature externalSignature = new PrivateKeySignature(pk, "SHA-256");
        MakeSignature.SignDetached(appearance, externalSignature, chain, null, null, new TSAClientBouncyCastle("http://services.globaltrustfinder.com/adss/tsa"), 104000, 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, X509Certificate 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(X509Certificate x509Certificate)
    {
        Dictionary<string, string> fields = SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerDN.ToString());

        string organization = fields.Keys.Contains("O") ? fields["O"] : String.Empty;
        string commonName = fields.Keys.Contains("CN") ? fields["CN"] : String.Empty;
        string signDate = System.DateTime.Now.ToString(_datetimeFormat);
        string expirationDate = x509Certificate.NotAfter.ToString();

        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;
    }
}

PPS: The Java/iText tests have been done using the signTest_2_user2699460 unit test in ComplexSignatureFields.java which works on test-2-user2699460.pdf, an intermediary output of the C# code above.

PPPS: Meanwhile the changes resulting in the regression have been rolled back:

Returned the use of .getWidget method instead of .getMerged since the case, when signature field dictionary and dictionary its widget annotation are not merged, is rather uncommon if can be encountered at all. Moreover the use of merged dictionary instead of widget requires more efforts since .getMerged method returns not actually the dictionary obtained by merging signature field dict and widget annotation dict, but also AcroForm dict.

(DEV-1579)

Thus, the regression most likely will be resolved in version 5.5.8



来源:https://stackoverflow.com/questions/32818522/itextsharp-setvisiblesignature-not-working-as-expected

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!