【问题标题】:itext7: deferred signing not producing a valid signed pdfitext7:延迟签名不产生有效的签名pdf
【发布时间】:2021-09-14 14:45:23
【问题描述】:

我正在尝试使用 itext7 通过从外部实体获取签名来签署 pdf。我一定遗漏了一些东西,因为延迟的 pdf 签名无效。让我们从延迟签名的代码开始:

public byte[] GetDocHashFromPreparedDocToSign(string pathToOriginalPdf, string pathToPreparedToBeSignedPdf, List<X509Certificate> certificates) {
    var pdfSigner = new PdfSigner(new PdfReader(pathToOriginalPdf),
                                  new FileStream(pathToPreparedToBeSignedPdf, FileMode.Create),
                                  new StampingProperties());
    pdfSigner.SetFieldName(_signatureFieldname);


    var appearance = pdfSigner.GetSignatureAppearance();
    appearance.SetPageRect(new Rectangle(144, 144, 200, 100))
              .SetPageNumber(1)
              .SetCertificate(certificates[0]);

    var container = new ExternalBlankSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
    pdfSigner.SignExternalContainer(container, 8192);
    



   byte[] sha256SigPrefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09,
                                 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
                                 0x05, 0x00, 0x04, 0x20 };
    // service needs to receive sha256 prepended
    using var stream = File.OpenRead(pathToPreparedToBeSignedPdf);
    var data = DigestAlgorithms.Digest(stream, DigestAlgorithms.SHA256);        
    var totalHash = new byte[sha256SigPrefix.Length + data.Length];
    sha256SigPrefix.CopyTo(totalHash, 0);
    data.CopyTo(totalHash, sha256SigPrefix.Length);
    return totalHash;
}

该方法接收到原始 pdf 的路径、将包含签名占位符的临时 pdf 的路径以及从原始服务中检索到的 X509Certificate 列表。在为签名保留空间后,该方法会计算文件的哈希值并在其前面加上 sha256 前缀(对文档进行签名的服务需要)。

此信息被发送到将返回签名的服务。检索到签名时,调用以下方法将签名占位符填充为真实签名:

public void SignPreparedToBeSignedDoc(string pathToPreparedToBeSignedPdf, string pathToSignedFile, byte[] signature) {
    var document = new PdfDocument(new PdfReader(pathToPreparedToBeSignedPdf));
    using var writer = new FileStream(pathToSignedFile, FileMode.Create);

    var container = new ExternalInjectingSignatureContainer(signature);
    PdfSigner.SignDeferred(document, _signatureFieldname, writer, container);
}

编辑:根据@mkl 评论,我修复了签名部分: 这是 ExternalInjectingSignatureContainer:

internal class ExternalInjectingSignatureContainer : IExternalSignatureContainer {
    private readonly byte[] _signature;

    public ExternalInjectingSignatureContainer(byte[] signature) {
        _signature = signature;
    }

    public byte[] Sign(Stream data){
        var sgn = new PdfPKCS7(null, _certificates.ToArray(), "SHA256", false);
        sgn.SetExternalDigest(_signature, null, "RSA");
        return sgn.GetEncodedPKCS7();

    }

    public void ModifySigningDictionary(PdfDictionary signDic) {
    }
}

即使代码运行没有错误,在 adobe 中打开 pdf 也会显示以下错误:

编辑:修复签名代码后,现在错误有所不同:它会显示签名信息,但会说文件已更改或损坏。

此时,临时 pdf 似乎正在正确生成,但我可能遗漏了一些东西...关于如何调试此问题的任何线索?

谢谢

编辑:为了响应 cmets 对@mkl 提出的解决方案,我尝试更新代码。今天我还有几分钟的时间来玩这个,我已经尝试遵循所提供的指导方针,但我显然仍然错过了一些东西。

在展示新代码之前,我想指出之前的更新版本(使用 2 个IExternalSignatureContainer 实例)似乎工作正常。即,在 adobe 上打开已签名的 pdf 只会向我显示黄色警告,指出签名有问题:

由于文档与测试链一起使用,看起来签名工作正常(尽管我可能完全错了)。

所以,为了修复容器的错误使用,我重写了IExternalSignatureContainerSign 方法的代码。这是我为准备将要发送到服务器的文档哈希的代码:

public override byte[] Sign(Stream data) {
// create PCKS7 for getting attributes
var sgn = new PdfPKCS7(null, _certificates.ToArray(), DigestAlgorithms.SHA256, false);
// get document hash
DocumentDigest = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
// get attributes
var docBytesHash = sgn.GetAuthenticatedAttributeBytes(DocumentDigest, 
                                PdfSigner.CryptoStandard.CMS, 
                                null, 
                                null);
 //prepend sha256 prefix
 var totalHash = new byte[_sha256SigPrefix.Length + docBytesHash.Length];
 _sha256SigPrefix.CopyTo(totalHash, 0);
 docBytesHash.CopyTo(totalHash, _sha256SigPrefix.Length);
 DataToSend = totalHash;
 return new byte[0];
}

由于我必须使用传递给GetAuthenticatedAttributes 的相同参数调用GetEncodedPKCS7 方法,因此我还要保存通过Digest 方法获得的文档哈希。 DataToSend 将被发送到服务器,以便它可以返回该哈希的签名。

这是另一个 IExternalSignatureContainer 的代码,它将被调用以进行延迟签名 (PdfSigner.SignDeferred):

public byte[] Sign(Stream data) {
    // create CMS      
    var sgn = new PdfPKCS7(null, _certificates.ToArray(), DigestAlgorithms.SHA256, false);
    // set the signature bytes
    sgn.SetExternalDigest(_signature, null, "RSA");
    // call GetEncoded with the same parameters as the original GetAuthenticatedAtt...
    //_documentHash == DocumentDigest previous sample
    var encodedSig = sgn.GetEncodedPKCS7(_documentHash,
                                         PdfSigner.CryptoStandard.CMS,
                                         null,
                                         null,
                                         null);

    return encodedSig;
}

不幸的是,我一定遗漏了一些东西(或很多东西):

我完全没有理解你的意思吗?

编辑:再一次,在@mkl 的带领下,我能够让它发挥作用。就像他说的,你需要散列 GetAuthenticatedAttributeBytes 值:

public override byte[] Sign(Stream data) {
// create PCKS7 for getting attributes
var sgn = new PdfPKCS7(null, _certificates.ToArray(), DigestAlgorithms.SHA256, false);
// get document hash
DocumentDigest = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
// get attributes
var docBytes = sgn.GetAuthenticatedAttributeBytes(DocumentDigest, 
                                PdfSigner.CryptoStandard.CMS, 
                                null, 
                                null);
// hash dochBytes 
using var hashMemoryStream = new MemoryStream(docBytes, false);
var docBytesHash = DigestAlgorithms.Digest(hashMemoryStream, 
                                            DigestAlgorithms.SHA256);
 //prepend sha256 prefix
 var totalHash = new byte[_sha256SigPrefix.Length + docBytesHash.Length];
 _sha256SigPrefix.CopyTo(totalHash, 0);
 docBytesHash.CopyTo(totalHash, _sha256SigPrefix.Length);
 DataToSend = totalHash;
 return new byte[0];
}

再次感谢。

【问题讨论】:

  • 您对完整的准备好的 PDF 进行哈希处理。这是错误的,您必须对准备好的 PDF 进行哈希处理,但最终注入的签名容器的占位符除外。此外,您最终注入的byte[] _signature 似乎不是一个合适的 CMS 签名容器;要么是破损的容器,要么根本不是容器。
  • 是的,_signature 不正确...我没有关注散列部分...pathToPreparedToBeSignedPdf 是具有签名占位符的 pdf 的路径。这是错的吗?谢谢
  • 我会在答案中解释。
  • 好的。我将更新现有帖子的 _signature 部分:)
  • 就是这样:将哈希添加到从 GetAuthenticatedAttributeBytes 返回的哈希中!现在,让我们看看如何给签名加上时间戳:) PS:我会更新代码并让你的答案保持正确。如果您访问马德拉岛,请给我打个电话,我会请您喝啤酒 :) 再次感谢!!!

标签: certificate itext .net-5


【解决方案1】:

有两个明显的问题,你散列了错误的数据,你注入了错误的签名类型:

散列错误数据

您可以这样计算要签名的哈希:

using var stream = File.OpenRead(pathToPreparedToBeSignedPdf);
var data = DigestAlgorithms.Digest(stream, DigestAlgorithms.SHA256);        

这是不正确的。

签名的 PDF 本质上具有这种结构(请阅读 here 了解更多详细信息):

(顺便说一下,草图不是 100% 正确的,因为签名值周围的尖括号分隔符“”也不能被散列。)

您在pathToPreparedToBeSignedPdf 准备的 PDF 具有相同的结构,只是“签名值”还不是实际的 签名 值,而是 8192 个十六进制编码零字节的占位符(8192 因为那是您在pdfSigner.SignExternalContainer 中提供的号码)。

正如您在草图中看到的那样,签名值(或在您的情况下为占位符)不得进行散列以进行签名。

除了占位符之外,检索准备好的 PDF 的最简单方法是在用于准备 PDF 的 IExternalSignatureContainer 实现中,因为它的 Sign 方法作为参数获取包含该内容的流。因此,不要使用ExternalBlankSignatureContainer,而是使用this answer 中的ExternalEmptySignatureContainer

public class ExternalEmptySignatureContainer : IExternalSignatureContainer
{
    public void ModifySigningDictionary(PdfDictionary signDic)
    {
        signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
        signDic.Put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
    }

    public byte[] Sign(Stream data)
    {
        // Store the data to sign and return an empty array
        Data = DigestAlgorithms.Digest(data, DigestAlgorithms.SHA256);
        return new byte[0];
    }

    public byte[] Data;
}

并在准备好后从其Data 成员中检索byte[]

var container = new ExternalEmptySignatureContainer();
pdfSigner.SignExternalContainer(container, 8192);
byte[] hash = container.Data;

在您的情况下,您可能必须预先添加 sha256SigPrefix 才能获得完整编码的 DigestInfo 对象。

注入错误类型的签名

此外,考虑到您的屏幕截图

您显然注入了错误类型的签名。您设置了一个子过滤器PdfName.Adbe_pkcs7_detached,这意味着要嵌入的签名是一个 CMS 签名容器,其中一个 SignerInfo 对 PDF 的签名字节进行签名。但是,错误消息表明您嵌入的签名容器已损坏或不是开始使用的 CMS 签名容器。

修改后:注入内容不正确的签名容器

要解决上一个问题,“注入错误类型的签名”,您更改了 ExternalInjectingSignatureContainer 为您的签名字节构建一个 CMS 签名容器,如下所示:

var sgn = new PdfPKCS7(null, _certificates.ToArray(), "SHA256", false);
sgn.SetExternalDigest(_signature, null, "RSA");
return sgn.GetEncodedPKCS7();

不幸的是,这会错误地使用 PdfPKCS7 类,从而导致 CMS 签名容器的数据不正确。

(更正:生成的 CMS 容器本身并没有不正确,但它在结构上非常简单。如今,许多配置文件 - 例如欧洲 PAdES - 需要额外的, 签名属性,并且要满足这些配置文件,您必须按照以下文本中的说明进行修复。但是,如果您只需要 Adob​​e Reader 显示有效,那么上面的代码就足够了。)

要解决此问题,您可以自己构建一个 CMS 签名容器,使用 iText PdfPKCS7 类或其他方式(如 BouncyCastle 或内置 .Net 类),或者您可以让 iText 为您完成。

最简单的方法是后一种。您实际上根本不需要延迟签名,您只需实现 IExternalSignature 以便它调用您的远程服务:

public class RemoteSignature : IExternalSignature
{
    public virtual byte[] Sign(byte[] message) {
        IDigest messageDigest = DigestUtilities.GetDigest(GetHashAlgorithm());
        byte[] messageHash = DigestAlgorithms.Digest(messageDigest, message);
        byte[] digestInfo = [... prefix messageHash with sha256SigPrefix ...];
        //
        // Request signature for DigestInfo digestInfo 
        // and return signature bytes
        //
        return CALL_YOUR_SERVICE_FOR_SIGNATURE_OF_HASH(digestInfo);
    } 

    public virtual String GetHashAlgorithm() {
        return "SHA-256";
    } 

    public virtual String GetEncryptionAlgorithm() {
        return "RSA";
    } 
}

现在你可以简单地签了

var pdfSigner = new PdfSigner(new PdfReader(pathToOriginalPdf),
                              new FileStream(pathToSignedFile, FileMode.Create),
                              new StampingProperties());
pdfSigner.SetFieldName(_signatureFieldname);

var appearance = pdfSigner.GetSignatureAppearance();
appearance.SetPageRect(new Rectangle(144, 144, 200, 100))
          .SetPageNumber(1)
          .SetCertificate(certificates[0]);

var signature = new RemoteSignature();
pdfSigner.SignDetached(signature, certificates, null, null, null, 0, PdfSigner.CryptoStandard.CMS);

【讨论】:

  • 感谢您的出色解释。不幸的是,我必须通过 CMS 签名容器路径,因为返回散列的调用受 SMS 代码保护。顺便说一句,如果我没记错的话,您在 PdfPKC7 中看到的问题与指定的子过滤器有关,对吧?如果我理解正确,我已经为单个证书设置了它,但随后我创建了一个具有完整证书链的证书...你能告诉我在通过证书链时要为子过滤器使用哪个值吗?跨度>
  • “您在 PdfPKC7 中看到的问题与指定的子过滤器有关,对吗?” - 不,要正确使用 PdfPKCS7 pkcs7,您必须调用pkcs7.GetAuthenticatedAttributeBytes 使用文档哈希(不带 sha256SigPrefix)获取签名属性,对其进行哈希和签名,使用pkcs7.SetExternalDigest 设置生成的签名字节,并最终使用与原始@987654352 相同的参数调用pkcs7.GetEncodedPKCS7 @调用。
  • 它不起作用...您知道是否有任何示例可以显示如何正确使用此方法策略?
  • 我会试着调查一下。不过可能要等到下周。
  • @LuisAbreu 好的,我再次查看了您的评论。您说您将不得不通过 CMS 签名容器路径,因为返回哈希的调用受 SMS 代码保护。如果您认为这也可能需要,那么该 SMS 代码只是一个障碍long 以保持线程运行并等待签名。你计划签约的开始和结束的时间间隔是多少?我问是因为您必须在开始时初始化 PdfPKCS7 对象并在最后再次使用它。您的设计是否允许持有与签名任务关联的此类对象?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-26
  • 1970-01-01
  • 1970-01-01
  • 2021-05-11
  • 1970-01-01
  • 1970-01-01
  • 2015-07-24
相关资源
最近更新 更多