【发布时间】: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 只会向我显示黄色警告,指出签名有问题:
由于文档与测试链一起使用,看起来签名工作正常(尽管我可能完全错了)。
所以,为了修复容器的错误使用,我重写了IExternalSignatureContainer 的Sign 方法的代码。这是我为准备将要发送到服务器的文档哈希的代码:
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