iTextSharp的SetVisibleSignature未正常工作 [英] ITextSharp SetVisibleSignature not working as expected

查看:781
本文介绍了iTextSharp的SetVisibleSignature未正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以,我一直在用Java实现iText的,但现在我几乎写我们的签名过程到C#的港口,我已经遇到了障碍。因此,当IM签约使用SetVisibleSignature我的文档(RECT,页面名称)过载,它标志预期(只要签名域不存在)的文件,但是当我使用过载SetVisibleSignature(名称)签署的现有场,它实际上并不签署该文件。我做一些愚蠢的事也许并失去了一些东西?



感谢您的任何帮助。



更新代码

 使用iTextSharp.text;使用iTextSharp.text.pdf 
;
使用iTextSharp.text.pdf.security;
使用Org.BouncyCastle.Security;
使用系统;
使用System.Collections.Generic;
:使用System.IO;
使用System.Linq的;
使用System.Security.Cryptography;使用System.Security.Cryptography.X509Certificates
;
使用BouncyCastle的= Org.BouncyCastle;

公共类DocumentSigner:IDocumentSigner
{
私人常量字符串_datetimeFormat =DD / MM / YYYY HH:MM:SS;
私人只读IDateTimeProvider _dateTimeProvider;
私人只读ISettingManager _settingManager;

公共DocumentSigner(IDateTimeProvider dateTimeProvider,ISettingManager settingManager)
{
Guard.ArgumentNotNull(dateTimeProviderdateTimeProvider);
Guard.ArgumentNotNull(settingManagersettingManager);

_dateTimeProvider = dateTimeProvider;
_settingManager = settingManager;
}

公共字节[]符号(证书,SigningInformation信息,列表与LT; SigningBlock> signingBlocks,列表与LT; MemberItemSignature> signatureImages,字节[]文件,布尔证明)
{
=文件AddMetaData(资料,文件);
=文件AddSignatureFields(信息,signingBlocks,文件);
返回SignDocument(证书,资料,signingBlocks,signatureImages,文件,证明);
}

私人字节[] AddMetaData(SigningInformation信息,字节[]文件)
{
如果(information.CustomProperties = NULL&放大器;!&安培;信息。使用CustomProperties.Any())
{
(MemoryStream的的OutputStream =新的MemoryStream()使用)
{
(PdfReader读卡器=新PdfReader(文件))
{字符串,字符串>使用(PdfStamper压模=新PdfStamper(读者的OutputStream,\0',真))
{
&字典LT
; currentProperties = reader.Info;
的foreach(在information.CustomProperties.Keys字符串键)
{
如果(currentProperties.ContainsKey(密钥))
{
currentProperties [关键] = information.CustomProperties [键];
}
,否则
{
currentProperties.Add(键,information.CustomProperties [关键]);
}
}

stamper.MoreInfo = currentProperties;
}
}

返回outputStream.ToArray();
}
}

返回文件;
}

私人字节[] AddSignatureFields(SigningInformation信息,列表与LT; SigningBlock> signingBlocks,字节[]文件)
{
的for(int i = 0;我< signingBlocks.Count;我++)使用
{
(MemoryStream的的OutputStream =新的MemoryStream())
{
使用(PdfReader读卡器=新PdfReader(文件))
{
使用(PdfStamper压模=新PdfStamper(读者的OutputStream,\0',真))
{
CreateSignatureField(读卡器,冲压,signingBlocks [I]);
}
}

=文件outputStream.ToArray();
}
}

返回文件;
}

私人PdfSignatureAppearance CreatePdfAppearance(PdfStamper模子,SigningInformation信息,布尔证明)
{
PdfSignatureAppearance外观= stamper.SignatureAppearance;
appearance.Location = information.Location;
appearance.Reason = information.Purpose;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
CreatePdfAppearanceCertifyDocument(外观,证明);

返回美观;
}

私人无效CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance外观,布尔证明)
{
如果(认证)
{
appearance.CertificationLevel = PdfSignatureAppearance .CERTIFIED_FORM_FILLING;
}
,否则
{
appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
}
}

私人PdfStamper CreatePdfStamper(PdfReader读卡器,MemoryStream的OutputStream中,字节[]文件)
{
返回PdfStamper.CreateSignature(读卡器,为OutputStream \0',NULL,TRUE);
}

私人无效CreateSignatureField(PdfReader读卡器,PdfStamper模子,SigningBlock signingBlock)
{
如果(signingBlock == NULL)
{
返回;
}

如果
{
PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer)(DoesSignatureFieldExist(读卡器,signingBlock.Name)!);
signatureField.SetWidget(新的Rectangle(signingBlock.X,signingBlock.Y,signingBlock.X + signingBlock.Width,signingBlock.Y + signingBlock.Height),NULL);
signatureField.Flags = PdfAnnotation.FLAGS_PR​​INT;
signatureField.FieldName = signingBlock.Name;
signatureField.Page = signingBlock.Page;
stamper.AddAnnotation(signatureField,signingBlock.Page);
}
}

私人布尔DoesSignatureFieldExist(PdfReader阅读器,串signatureFieldName)
{
如果(String.IsNullOrWhiteSpace(signatureFieldName))
{
返回false;
}

返回reader.AcroFields.DoesSignatureFieldExist(signatureFieldName);
}

私人字节[] GetSignatureImage(列表< MemberItemSignature> signatureImages,串signingBlockName)
{
MemberItemSignature签名= signingBlockName.Contains(初始)? signatureImages.Where(图像= GT; image.Use == SignatureUses.Initial).FirstOrDefault():signatureImages.Where(图片= GT; image.Use == SignatureUses.Signature).FirstOrDefault();
如果(签名!= NULL)
{
返回signature.Image;
}
,否则
{
返回NULL;
}
}

私人字节[] SignDocument(证书,SigningInformation信息,列表与LT; SigningBlock> signingBlocks,列表与LT; MemberItemSignature> signatureImages,字节[]文件,布尔证明)
{
的for(int i = 0; I< signingBlocks.Count;我++)
{使用
(MemoryStream的的OutputStream =新的MemoryStream())
{$使用b $ b(PdfReader读卡器=新PdfReader(文件))使用(PdfStamper压模= CreatePdfStamper(读卡器,OutputStream中,文件))
{
signingBlock signingBlock = signingBlocks
{
[一世];
PdfSignatureAppearance外观= CreatePdfAppearance(冲压,信息,认证和放大器;&安培;我== 0);

SignDocumentSigningBlock(证书,资料,signingBlock,外观,冲压,GetSignatureImage(signatureImages,signingBlock.Name));
}
}

=文件outputStream.ToArray();
}
}

返回文件;
}

私人无效SignDocumentSigningBlock(证书,SigningInformation信息,SigningBlock块,PdfSignatureAppearance外观,PdfStamper模子,字节[] signatureImage)
{
X509Certificate2 x509证书=新X509Certificate2(certificate.Bytes,certificate.Password,X509KeyStorageFlags.Exportable);

appearance.SetVisibleSignature(block.Name);
SignDocumentSigningBlockWithImage(signatureImage,外观);
SignDocumentSigningBlockWithText(外观,X509证书);使用

(RSA的RSACryptoServiceProvider =(的RSACryptoServiceProvider)x509Certificate.PrivateKey)
{
IExternalSignature externalSignature =新PrivateKeySignature(DotNetUtilities.GetRsaKeyPair(RSA).Private,_settingManager [DocumentSigningEncryptionHashAlgorithm] );
MakeSignature.SignDetached(外观,externalSignature,新BouncyCastle的:: X509.X509Certificate [] {DotNetUtilities.FromX509Certificate(X509证书)},NULL,NULL,新TSAClientBouncyCastle(_settingManager [DocumentSigningTimestampingServiceAddress]),Int32.Parse(_settingManager [DocumentSigningEstimatedTimestampSize]),CryptoStandard.CMS);
}
}

私人无效SignDocumentSigningBlockWithImage(字节[] signatureImage,PdfSignatureAppearance外观)
{
如果(signatureImage = NULL&放大器;!&安培; signatureImage 。长度大于0)
{
图像signatureImageInstance = Image.GetInstance(signatureImage);

appearance.Image = signatureImageInstance;
appearance.SignatureGraphic = signatureImageInstance;
}
}

私人无效SignDocumentSigningBlockWithText(PdfSignatureAppearance外观,X509Certificate2 x509证书)
{
如果(x509证书== NULL)
$ { b $ b返回;
}

appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(x509证书);
appearance.Layer2Font =新字体(Font.FontFamily.COURIER,7.0f,Font.NORMAL,BaseColor.LIGHT_GRAY);
appearance.Acro6Layers = TRUE;
}

私人字符串SignDocumentSigningBlockWithTextBuildText(X509Certificate2 x509证书)
{
&字典LT;字符串,字符串>域= SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerName.Name);

串组织= fields.Keys.Contains(O)?域[O]的String.Empty;
串COMMONNAME = fields.Keys.Contains(CN)?域[CN]的String.Empty;
串signDate = _dateTimeProvider.Now.ToString(_datetimeFormat);
串EXPIRATIONDATE = x509Certificate.NotAfter.ToString(_datetimeFormat);

返回+组织+的数字签名,\\\
Signee:+ COMMONNAME +\\\
Sign日期:+ signDate +\\\
+到期日期:+ EXPIRATIONDATE;
}

私人字典<字符串,字符串> SignDocumentSigningBlockWithTextBuildTextIssuerFields(字符串发行人)
{
&字典LT;字符串,字符串>域=新词典<字符串,字符串>();

串[] issuerFields = issuer.Split(,);
的foreach(在issuerFields串场)
{
的String [] = fieldSplit field.Split('=');
串键= fieldSplit [0] .Trim();
字符串值= fieldSplit [1] .Trim();

如果
{
fields.Add(键,值)(fields.Keys.Contains(密钥)!);
}
,否则
{
领域[关键] =价值;
}
}

返回领域;
}
}



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


解决方案

由OP参考提供的代码和访问类别未知多个对象。为了使它可运行的,因此,它必须被削减到自成体系。



切割下来的版本幸好仍可能用于重现和分析问题,比照。在邮政Scriptum。从这里的任何声明是基于此的缩减版本的行为。

使用iTextSharp的5.5.7

由OP观察到的问题可能再现(和类似利用iText 5.5.7),也很有趣它可能的的使用任一库5.5.6版本进行复制。正如我更成Java,我看着在iText的变化。他们已经被移植到了iTextSharp的一个非常忠实的方式。



事实上,这个问题是一个回归,签署预先存在的空白签名域中的追加模式的坏中的iText(夏普)5.5.7。



5.5.6之间的5.5.7和改变已经取得了 PdfSignatureAppearance.preClose 。如果签名的现有签名字段,所使用的代码来操纵所讨论的签名字段( af.getFieldItem(名称).getWidget(0))的第一部件,现在它适用于相关的合并字典( af.getFieldItem(名).getMerged(0))。



不幸的是,而前者是一个对象实际上存在于原始PDF,因此,主叫 writer.markUsed 为它标志着其改变的内容写入到所述增量更新部,后者则不对应于原始PDF(它是多个对象的虚拟集合)的对象,因此调用 writer.markUsed 为它的的标志的变化再被写为增量更新



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






变更已经完成,修复方法的行为。



< BLOCKQUOTE>

在此拟闭工作正确,因为它检索领域的字典作为小部件的注释。这是不正确的情况下,当现场和widget类型的字典不合并。在情况下,他们被合并,一切都如预期。后者是用于数字签名域的最可能的情况,但是它是根据规范不负责



(DEV-1448)




这是正确的,在不同的领域和widget字典的情况下,一定的变化都到外地,而不是小工具来进行。仅仅根据需要在执行不工作的追加模式






PS :这是OP代码的简化版本:

 公共类DocumentSigner 
{
私人常量字符串_datetimeFormat =DD / MM / YYYY HH:MM:SS;

公共字节[]符号(ICollection的< x509证书>链,ICipherParameters PK,串signingBlock,字​​节[]文件,布尔作证,细绳纹= NULL)
{
文档= AddMetaData(文件);
如果(模式!= NULL)
File.WriteAllBytes(的String.Format(模式,1),文件);
=文件AddSignatureFields(signingBlock,文件);
如果(模式!= NULL)
File.WriteAllBytes(的String.Format(模式,2),文件);
返回SignDocument(链,PK,signingBlock,文件,证明);
}

私人字节[] AddMetaData(字节[]文件)
{
返回文件;
}

私人字节[] AddSignatureFields(字符串signingBlock,字​​节[]文件)
{使用
(MemoryStream的的OutputStream =新的MemoryStream())
{使用(PdfReader读卡器=新PdfReader(文件))
使用(PdfStamper压模=新PdfStamper(读者的OutputStream,\0',真))

{
{
CreateSignatureField(读卡器,压模,signingBlock);
}
}

=文件outputStream.ToArray();
}

返回文件;
}

私人PdfSignatureAppearance CreatePdfAppearance(PdfStamper模子,布尔证明)
{
PdfSignatureAppearance外观= stamper.SignatureAppearance;
appearance.Location =information.Location;
appearance.Reason =information.Purpose;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
CreatePdfAppearanceCertifyDocument(外观,证明);

返回美观;
}

私人无效CreatePdfAppearanceCertifyDocument(PdfSignatureAppearance外观,布尔证明)
{
如果(认证)
{
appearance.CertificationLevel = PdfSignatureAppearance .CERTIFIED_FORM_FILLING;
}
,否则
{
appearance.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
}
}

私人PdfStamper CreatePdfStamper(PdfReader读卡器,MemoryStream的OutputStream中,字节[]文件)
{
返回PdfStamper.CreateSignature(读卡器,为OutputStream \0',NULL,TRUE);
}

私人无效CreateSignatureField(PdfReader读卡器,PdfStamper模子,串signingBlock)
{
如果(signingBlock == NULL)
{
返回;
}

如果
{
PdfFormField signatureField = PdfFormField.CreateSignature(stamper.Writer)(DoesSignatureFieldExist(读卡器,signingBlock)!);
signatureField.SetWidget(新的Rectangle(100,100,200,200),NULL);
signatureField.Flags = PdfAnnotation.FLAGS_PR​​INT;
signatureField.FieldName = signingBlock;
signatureField.Page = 1;
stamper.AddAnnotation(signatureField,1);
}
}

私人布尔DoesSignatureFieldExist(PdfReader阅读器,串signatureFieldName)
{
如果(String.IsNullOrWhiteSpace(signatureFieldName))
{
返回false;
}

返回reader.AcroFields.DoesSignatureFieldExist(signatureFieldName);
}

私人字节[] GetSignatureImage(字符串signingBlockName)
{
返回NULL;
}

私人字节[] SignDocument(ICollection的< x509证书>链,ICipherParameters PK,串signingBlock,字​​节[]文件,布尔证明)
{$ B $使用B( MemoryStream的的OutputStream =新的MemoryStream())使用
{
(PdfReader读卡器=新PdfReader(文件)使用)
{
(PdfStamper压模= CreatePdfStamper(读卡器,OutputStream中,文件) )
{
PdfSignatureAppearance外观= CreatePdfAppearance(冲压,证明);

SignDocumentSigningBlock(链,PK,signingBlock,外观,冲压,GetSignatureImage(signingBlock));
}
}

=文件outputStream.ToArray();
}

返回文件;
}

私人无效SignDocumentSigningBlock(ICollection的< x509证书>链,ICipherParameters PK,串块,PdfSignatureAppearance外观,PdfStamper模子,字节[] signatureImage)
{
外观.SetVisibleSignature(块);
SignDocumentSigningBlockWithImage(signatureImage,外观);
SignDocumentSigningBlockWithText(外观,chain.First());

IExternalSignature externalSignature =新PrivateKeySignature(PK,SHA-256);
MakeSignature.SignDetached(外观,externalSignature,链条,NULL,NULL,新TSAClientBouncyCastle(http://services.globaltrustfinder.com/adss/tsa),104000,CryptoStandard.CMS);
}

私人无效SignDocumentSigningBlockWithImage(字节[] signatureImage,PdfSignatureAppearance外观)
{
如果(signatureImage = NULL&放大器;!&安培; signatureImage.Length大于0 )
{
图像signatureImageInstance = Image.GetInstance(signatureImage);

appearance.Image = signatureImageInstance;
appearance.SignatureGraphic = signatureImageInstance;
}
}

私人无效SignDocumentSigningBlockWithText(PdfSignatureAppearance外观,X509证书X509证书)
{
如果(x509证书== NULL)
$ { b $ b返回;
}

appearance.Layer2Text = SignDocumentSigningBlockWithTextBuildText(x509证书);
appearance.Layer2Font =新字体(Font.FontFamily.COURIER,7.0f,Font.NORMAL,BaseColor.LIGHT_GRAY);
appearance.Acro6Layers = TRUE;
}

私人字符串SignDocumentSigningBlockWithTextBuildText(X509证书X509证书)
{
&字典LT;字符串,字符串>域= SignDocumentSigningBlockWithTextBuildTextIssuerFields(x509Certificate.IssuerDN.ToString());

串组织= fields.Keys.Contains(O)?域[O]的String.Empty;
串COMMONNAME = fields.Keys.Contains(CN)?域[CN]的String.Empty;
串signDate = System.DateTime.Now.ToString(_datetimeFormat);
串EXPIRATIONDATE = x509Certificate.NotAfter.ToString();

返回+组织+的数字签名,\\\
Signee:+ COMMONNAME +\\\
Sign日期:+ signDate +\\\
+到期日期:+ EXPIRATIONDATE;
}

私人字典<字符串,字符串> SignDocumentSigningBlockWithTextBuildTextIssuerFields(字符串发行人)
{
&字典LT;字符串,字符串>域=新词典<字符串,字符串>();

串[] issuerFields = issuer.Split(,);
的foreach(在issuerFields串场)
{
的String [] = fieldSplit field.Split('=');
串键= fieldSplit [0] .Trim();
字符串值= fieldSplit [1] .Trim();

如果
{
fields.Add(键,值)(fields.Keys.Contains(密钥)!);
}
,否则
{
领域[关键] =价值;
}
}

返回领域;
}
}



PPS :在Java / iText的测试已经使用 signTest_2_user2699460 单元测试的 ComplexSignatureFields.java 其上的测试-2-user2699460.pdf ,一个上面的C#代码的中介输出



PPPS :同时造成了回归更改已回滚:




返回使用.getWidget方法代替.getMerged以来的情况下,当签名域词典和辞典其插件注解不合并,是相当罕见的,如果能在所有遇到过。此外,该合并使用字典而不是部件都需要更多的努力,因为.getMerged方法返回实际上不是通过合并签名域字典和Widget注释字典中获得的字典,也AcroForm字典。



(DEV-1579)




因此,回归极有可能将在5.5版本解决。 8


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;

解决方案

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

这篇关于iTextSharp的SetVisibleSignature未正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆