如何使用itextsharp.net将相同的数字签名放置在PDF中的多个位置

问题描述 投票:0回答:1

我已经使用iTextSharp Dll实现了数字签名,使用单个签名对PDF文件进行签名,创建空签名字段,并使用签名散列更新签名字段。现在,我想在pdf的每一页中放置相同的数字签名。这是我的客户要求。

我正在使用以下代码:

public class MyExternalSignatureContainer : IExternalSignatureContainer
{
    private readonly byte[] signedBytes;

    public MyExternalSignatureContainer(byte[] signedBytes)
    {
        this.signedBytes = signedBytes;
    }

    public byte[] Sign(Stream data)
    {
        return signedBytes;
    }

    public void ModifySigningDictionary(PdfDictionary signDic)
    {
    }
}

程序中使用的代码如下

PdfReader reader = new PdfReader(unsignedPdf);
FileStream os = File.OpenWrite(tempPdf);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;

appearance.Reason = "Reason1";
appearance.Contact = "";
appearance.Location = "Location1";
appearance.Acro6Layers = false;
appearance.Image = null;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 748, 144, 780), 1, null);
for (int i = 1; i < 8; i++)
{
    var signatureField = PdfFormField.CreateSignature(stamper.Writer);
    var signatureRect = new Rectangle(200, 200, 100, 100);
    signatureField.Put(PdfName.T, new PdfString("ClientSignature_"+i.ToString()));
    PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
    signatureField.Put(PdfName.V, PRef);
    signatureField.Put(PdfName.F, new PdfNumber("132"));
    signatureField.SetWidget(signatureRect, null);
    signatureField.Put(PdfName.SUBTYPE, PdfName.WIDGET);

    PdfDictionary xobject1 = new PdfDictionary();
    PdfDictionary xobject2 = new PdfDictionary();
    xobject1.Put(PdfName.N, appearance.GetAppearance().IndirectReference);
    xobject2.Put(PdfName.AP, xobject1);
    signatureField.Put(PdfName.AP, xobject1);
    signatureField.SetPage();
    PdfDictionary xobject3 = new PdfDictionary();
    PdfDictionary xobject4 = new PdfDictionary();
    xobject4.Put(PdfName.FRM, appearance.GetAppearance().IndirectReference);
    xobject3.Put(PdfName.XOBJECT, xobject4);
    signatureField.Put(PdfName.DR, xobject3);

    stamper.AddAnnotation(signatureField, i);
}

IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
stamper.Close();

byte[] SignedHash =  DoEsign(SHA256Managed.Create().ComputeHash(appearance.GetRangeStream());
os.close();
reader.close();

reader = new PdfReader(tempPdf))
os = File.OpenWrite(signedPdf)

IExternalSignatureContainer external1 = new MyExternalSignatureContainer(SignedHash);
MakeSignature.SignDeferred(reader, signatureFieldName, os, external1);
os.close();
reader.close();

请建议我完成任务

itext
1个回答
0
投票

要为所有签名字段提供包装新创建的签名容器的相同单个值,它们必须全部引用与值相同的间接对象。不幸的是,iText只有在应用程序代码有机会添加其附加字段后才会为签名值创建间接对象,而这些字段又需要对该签名值对象的引用。因此,应用程序代码必须预测间接对象将具有的对象编号。

对象号的预期或预测是非常微妙的,它取决于完全相同的用例,并且由于iTextSharp库中的微小变化也可能变得不正确

为了使这更容易,应用程序代码应该尽可能晚地添加这些签名字段及其签名值引用,因此在iText创建值间接对象之前,尽可能少地创建其他新的间接对象。

事实证明,ModifySigningDictionaryIExternalSignatureContainer方法是一个很好的位置。

只要在那里添加一个代码,就会弹出另一个问题:无法在外部的PdfIndirectReference实例中设置预期的对象编号。解决这个问题的一种方法是使用PdfLiteral来模仿这样的引用。 (好吧,也许人们也可以使用反射。)

此外,事实证明,在构建模仿PdfLiteralPdfIndirectReference之前,最好创建所有人的附加签名字段使用的外观流,因为这简化了iText将用于实际值对象的对象编号的计算。

考虑到这一点,这里是一个概念验证。这个概念证明使用IExternalSignature实例进行实际签名。这不是一个必要的前提条件,人们也可以使用IExternalSignatureContainer而只需要进行一些更改,甚至是问题中的ExternalBlankSignatureContainer,以便稍后使用MakeSignature.SignDeferred完成签名。

所以给定密码参数cp(私钥材料,例如用于pk.KeyOrg.BouncyCastle.Pkcs.AsymmetricKeyEntry pk)和证书链chain,可以使用

PdfReader reader = new PdfReader(SRC);
FileStream os = new FileStream(DEST, FileMode.Create, FileAccess.Write);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;

appearance.Reason = "Reason1";
appearance.Contact = "";
appearance.Location = "Location1";
appearance.Acro6Layers = false;
appearance.Image = null;
appearance.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.DESCRIPTION;
appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(10, 10, 100, 100), reader.NumberOfPages, null);

IExternalSignature externalSignature = new PrivateKeySignature(cp, "SHA-256");
AllPagesSignatureContainer allPagesContainer = new AllPagesSignatureContainer(appearance, externalSignature, chain);
MakeSignature.SignExternalContainer(appearance, allPagesContainer, 8192);

使用此外部签名容器类

public class AllPagesSignatureContainer : IExternalSignatureContainer
{
    public AllPagesSignatureContainer(PdfSignatureAppearance appearance, IExternalSignature externalSignature, ICollection<X509Certificate> chain)
    {
        this.appearance = appearance;
        this.chain = chain;
        this.externalSignature = externalSignature;
    }

    public void ModifySigningDictionary(PdfDictionary signDic)
    {
        signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
        signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);

        PdfStamper stamper = appearance.Stamper;
        PdfReader reader = stamper.Reader;
        PdfDictionary xobject1 = new PdfDictionary();
        PdfDictionary xobject2 = new PdfDictionary();
        xobject1.Put(PdfName.N, appearance.GetAppearance().IndirectReference);
        xobject2.Put(PdfName.AP, xobject1);

        PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
        PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + 1 + 2*(reader.NumberOfPages - 1)) + " 0 R");

        for (int i = 1; i < reader.NumberOfPages; i++)
        {
            var signatureField = PdfFormField.CreateSignature(stamper.Writer);

            signatureField.Put(PdfName.T, new PdfString("ClientSignature_" + i.ToString()));
            signatureField.Put(PdfName.V, PRefLiteral);
            signatureField.Put(PdfName.F, new PdfNumber("132"));
            signatureField.SetWidget(appearance.Rect, null);
            signatureField.Put(PdfName.SUBTYPE, PdfName.WIDGET);

            signatureField.Put(PdfName.AP, xobject1);
            signatureField.SetPage();
            Console.WriteLine(signatureField);

            stamper.AddAnnotation(signatureField, i);
        }
    }

    public byte[] Sign(Stream data)
    {
        String hashAlgorithm = externalSignature.GetHashAlgorithm();
        PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
        IDigest messageDigest = DigestUtilities.GetDigest(hashAlgorithm);
        byte[] hash = DigestAlgorithms.Digest(data, hashAlgorithm);
        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
        byte[] extSignature = externalSignature.Sign(sh);
        sgn.SetExternalDigest(extSignature, null, externalSignature.GetEncryptionAlgorithm());
        return sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
    }

    PdfSignatureAppearance appearance;
    ICollection<X509Certificate> chain;
    IExternalSignature externalSignature;
}

行中签名值的预测间接对象编号

PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + 1 + 2*(reader.NumberOfPages - 1)) + " 0 R");

严格依赖于用例是“每页只有一个签名字段”。对于不同的用例,预测的估计会有所不同。

我再次强调这一点,因为例如当尝试“在单页上放置多个签名”时,this question的OP没有考虑到这一点。

对上述对象数预测的另一个严格要求是如上所述创建PdfStamper,即不是附加模式。如果签名作为增量更新应用,即在附加模式中,则上面的行必须替换为

stamper.Writer.AddToBody(new PdfNull(), stamper.Writer.PdfIndirectReference, true);

PdfIndirectReference PRef = stamper.Writer.PdfIndirectReference;
PdfLiteral PRefLiteral = new PdfLiteral((PRef.Number + reader.NumberOfPages) + " 0 R");

这在this question的背景下产生了不同;第一行,将间接空对象添加到PDF,是必要的,以确保在具有对象流的PDF的情况下,对象流对象编号已经确定,并且不会在下一个对象之间滑动,从而导致 - 我们的预测错误。


注意:虽然此过程创建的内容不违反PDF规范的字母(仅禁止从多个页面引用相同字段对象的情况,无论是通过相同还是通过不同的小部件),但它显然违反了意图,它的精神。因此,作为规范的勘误表文件的一部分,该程序也可能被禁止。

© www.soinside.com 2019 - 2024. All rights reserved.