所以我一直在使用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;
答案 0 :(得分:4)
OP提供的代码引用并访问未知类的多个对象。因此,为了使其可以运行,它必须被削减为独立的。
幸运的是,仍然可以使用简化版本来重现和分析问题,参见后脚本。此处的任何声明都基于此缩减版本的行为。
OP观察到的问题可以使用iTextSharp 5.5.7(类似地使用iText 5.5.7)重现,而且非常有趣的是它可以不使用版本5.5.6再现图书馆。随着我对Java的更多关注,我研究了iText的变化。他们以非常忠实的方式被移植到iTextSharp。
事实上,这个问题是回归,在iText(夏普)5.5中附加模式中已经存在的空签名字段已经破了 0.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
而不是将更改标记为增量更新。
因此,虽然实际签名值仍然写入文件,但它不再连接到指定的签名字段。
已完成更改以修复方法行为。
在此之前
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 :使用ComplexSignatureFields.java中的signTest_2_user2699460
单元测试完成了Java / iText测试,该测试适用于test-2-user2699460.pdf,C#的中间输出上面的代码。
PPPS :同时导致回归的更改已被回滚:
返回使用.getWidget方法而不是.getMerged,因为在签名字段字典和字典小部件注释没有合并的情况下,如果可以遇到则很少见。此外,使用合并字典而不是小部件需要更多的努力,因为.getMerged方法实际上并不返回通过合并签名字段dict和小部件注释字典获得的字典,而且还返回AcroForm字典。
(DEV-1579)
因此,回归最有可能在版本5.5.8中得到解决