ITextSharp SetVisibleSignature无法按预期工作

所以我一直在使用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 signingBlocks, List 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 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 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 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 signingBlocks, List signatureImages, byte[] document, bool certify) { for (int i = 0; i  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 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 SignDocumentSigningBlockWithTextBuildTextIssuerFields(string issuer) { Dictionary fields = new Dictionary(); 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; 

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进行了PdfSignatureAppearance.preClose 。 如果签署现有签名字段,用于操纵有问题的签名字段的第一个窗口小部件的代码( af.getFieldItem(name).getWidget(0) ),现在它适用于关联的合并字典( af.getFieldItem(name).getMerged(0) )。

不幸的是,虽然前者是实际存在于原始PDF中的对象,因此,调用writer.markUsed为其标记其更改的内容以写入增量更新部分,后者不对应于原始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 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 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 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 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 SignDocumentSigningBlockWithTextBuildTextIssuerFields(string issuer) { Dictionary fields = new Dictionary(); 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测试是使用signTest_2_user2699460unit testing完成的,该测试适用于test-2-user2699460.pdf ,它是上述C#代码的中间输出。

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

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

(DEV-1579)

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