【问题标题】:ITextSharp SetVisibleSignature not working as expectedITextSharp SetVisibleSignature 未按预期工作
【发布时间】:2015-09-28 08:19:59
【问题描述】:

所以我一直在使用 IText 的 Java 实现,但现在我几乎正在将我们的签名过程的一个端口写入 C#,但我遇到了一个障碍。因此,当我使用 SetVisibleSignature(rect, page, name) 重载签署我的文档时,它会按预期签署文档(只要签名字段不存在),但是当我使用重载 SetVisibleSignature(name) 签署现有的字段,它实际上并不签署文件。我是不是在做一些愚蠢的事情而错过了什么?

感谢您的帮助。

更新代码

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

价值观

    _settingManager["DocumentSigningEncryptionHashAlgorithm"] = "SHA-256";
_settingManager["DocumentSigningTimestampingServiceAddress"] = "http://services.globaltrustfinder.com/adss/tsa";
_settingManager["DocumentSigningEstimatedTimestampSize"] = 104000;

【问题讨论】:

  • 它实际上并没有签署文件 - 会发生什么?
  • +1 只是为了说“没有按预期工作”,而不是像很多其他人那样说“不工作”。
  • 因此,在创建修订版时,文档的大小会增加,但签名字段在文档和签名窗格上仍然未签名。但是,当我不使用 SetVisibleSignature 进行签名时,它会按预期创建不可见的签名,并且现有块未签名。这些名称与我要签名的签名块匹配。如果我采用完全未签名的文档并使用 SetVisibleSignature(rect, page, name) 覆盖它,它会按照我的预期对文档进行签名。
  • 我正在使用 IText 5.5.7,在我忘记提及之前:)
  • @user2699460 我刚刚尝试编译您的代码,但有许多未知引用。此外,还不清楚安全类来自哪些命名空间,尤其是它们是 Org.BouncyCastle 还是 System.Security 类。因此,请在此处提供sscce,重点关注可编译。

标签: c# pdf itextsharp signing


【解决方案1】:

OP 提供的代码引用和访问多个未知类的对象。因此,为了使其可运行,必须将其缩减为独立的。

幸运的是,精简版仍然可以用来重现和分析问题,参见。后文。从这里开始的任何声明都是基于这个缩减版本的行为。

OP 观察到的问题可以使用 iTextSharp 5.5.7(类似地使用 iText 5.5.7)重现,而且非常有趣的是,它不能使用任何一个版本的 5.5.6 重现图书馆。随着我更喜欢 Java,我研究了 iText 的变化。它们以非常忠实的方式移植到 iTextSharp。

确实,这个问题是一个回归,在 iText(Sharp) 5.5 中,追加模式中签署预先存在的空签名字段被破坏 .7.

在 5.5.6 和 5.5.7 之间已对 PdfSignatureAppearance.preClose 进行了更改。如果对现有签名字段进行签名,用于操作相关签名字段的第一个小部件 (af.getFieldItem(name).getWidget(0)) 的代码现在可以在关联的合并字典 (af.getFieldItem(name).getMerged(0)) 上运行。

不幸的是,虽然前者是原始 PDF 中实际存在的对象,因此调用 writer.markUsed 将其更改的内容标记为写入增量更新部分,后者不对应于原始 PDF 中的对象PDF(它是多个对象的虚拟聚合),因此调用writer.markUsed 确实不再将要写入的更改标记为增量更新。

因此,虽然实际签名值仍然写入文件,但它不再连接到指定的签名字段。


已完成更改以修复方法行为。

在此之前preClosed 工作不正确,因为它检索字段字典作为小部件注释。如果字段和小部件字典未合并,这是不正确的。如果它们被合并,一切都会按预期进行。后者是数字签名字段最可能的情况,但根据规范不是必须的。

(DEV-1448)

这是正确的,在单独的字段和小部件字典的情况下,必须对字段进行某些更改,而不是小部件。只是实现在追加模式下无法正常工作。


PS:这是 OP 代码的精简版:

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:Java/iText 测试是使用ComplexSignatureFields.java 中的signTest_2_user2699460 单元测试完成的,该测试适用于test-2-user2699460.pdf,上面的C# 代码的中间输出。

PPPS:同时导致回归的变化已经回滚:

返回使用 .getWidget 方法而不是 .getMerged 的​​情况,因为当签名字段字典和字典其小部件注释未合并时,如果可以遇到的话,这是相当罕见的。此外,使用合并字典而不是widget需要更多的努力,因为.getMerged方法实际上返回的不是通过合并签名字段dict和widget注释dict得到的字典,而是AcroForm dict。

(DEV-1579)

因此,回归很可能会在版本 5.5.8 中得到解决

【讨论】:

  • 感谢您对问题的深入了解 :) 我完全迷失了。
  • @user2699460 您是否验证过您的原始代码也可以在 5.5.6 上运行?
  • 抱歉响应缓慢,周五出现了生产问题:(但我已降级到 itextsharp 5.5.6,现在它可以按预期工作了;)感谢您的所有建议和帮助 :)
  • 确认它在 5.5.8 中没有解决。必须靠运气才能找到失败的原因
猜你喜欢
  • 2021-06-04
  • 2022-01-24
  • 2015-05-11
  • 2020-05-15
  • 2014-10-31
  • 2018-02-12
  • 2014-01-20
  • 2015-01-13
  • 2013-08-01
相关资源
最近更新 更多