ITextSharp SetVisibleSignature 未按预期工作
ITextSharp SetVisibleSignature not working as expected
所以我一直在使用 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, '[=10=]', 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, '[=10=]', 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, '[=10=]', 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;
OP提供的代码引用并访问了多个未知对象类。因此,为了使其可运行,必须将其缩减为独立的。
幸好精简版还能重现分析问题,cf. post 剧本。从这里开始的任何陈述都是基于这个简化版本的行为。
OP 观察到的问题可以使用 iTextSharp 5.5.7(以及类似地使用 iText 5.5.7)重现,而且非常有趣的是它可以 not 使用版本重现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 中的对象(它是多个对象的虚拟聚合),因此调用 writer.markUsed
确实 not 将更改标记为增量更新。
所以,虽然实际的签名值仍然写入文件,但它不再连接到指定的签名字段。
已完成更改以修复方法行为。
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)
这是正确的,在单独的字段和小部件字典的情况下,必须对字段而不是小部件进行某些更改。只是实现在 append 模式.
中无法正常工作
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, '[=10=]', 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, '[=10=]', 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 which works on test-2-user2699460.pdf 中的 signTest_2_user2699460
单元测试完成的,中介上面 C# 代码的输出。
PPPS:同时回滚了导致回归的变化:
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)
因此,回归很可能会在 5.5.8 版本中得到解决
所以我一直在使用 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, '[=10=]', 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, '[=10=]', 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, '[=10=]', 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;
OP提供的代码引用并访问了多个未知对象类。因此,为了使其可运行,必须将其缩减为独立的。
幸好精简版还能重现分析问题,cf. post 剧本。从这里开始的任何陈述都是基于这个简化版本的行为。
OP 观察到的问题可以使用 iTextSharp 5.5.7(以及类似地使用 iText 5.5.7)重现,而且非常有趣的是它可以 not 使用版本重现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 中的对象(它是多个对象的虚拟聚合),因此调用 writer.markUsed
确实 not 将更改标记为增量更新。
所以,虽然实际的签名值仍然写入文件,但它不再连接到指定的签名字段。
已完成更改以修复方法行为。
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)
这是正确的,在单独的字段和小部件字典的情况下,必须对字段而不是小部件进行某些更改。只是实现在 append 模式.
中无法正常工作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, '[=10=]', 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, '[=10=]', 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 which works on test-2-user2699460.pdf 中的 signTest_2_user2699460
单元测试完成的,中介上面 C# 代码的输出。
PPPS:同时回滚了导致回归的变化:
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)
因此,回归很可能会在 5.5.8 版本中得到解决