如何使用iText在Web上下文中使用智能卡签署PDF?

时间:2015-03-09 18:28:41

标签: itext activex digital-signature smartcard capicom

阅读以下参考资料:

哈希代码:

BouncyCastle.X509Certificate[] chain = Utils.GetSignerCertChain();
reader = Utils.GetReader();
MemoryStream stream = new MemoryStream();
using (var stamper = PdfStamper.CreateSignature(reader, stream, '\0'))
{
    PdfSignatureAppearance sap = stamper.SignatureAppearance;
    sap.SetVisibleSignature(
        new Rectangle(36, 740, 144, 770),
        reader.NumberOfPages,
        "SignatureField"
    );
    sap.Certificate = chain[0];
    sap.SignDate = DateTime.Now;
    sap.Reason = "testing web context signatures";

    PdfSignature pdfSignature = new PdfSignature(
        PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED
    );
    pdfSignature.Date = new PdfDate(sap.SignDate);
    pdfSignature.Reason = sap.Reason;
    sap.CryptoDictionary = pdfSignature;

    Dictionary<PdfName, int> exclusionSizes = new Dictionary<PdfName, int>();
    exclusionSizes.Add(PdfName.CONTENTS, SIG_BUFFER * 2 + 2);
    sap.PreClose(exclusionSizes);

    Stream sapStream = sap.GetRangeStream();
    byte[] hash = DigestAlgorithms.Digest(
        sapStream,
        DigestAlgorithms.SHA256
    );

// is this needed?
    PdfPKCS7 sgn = new PdfPKCS7(
        null, chain, DigestAlgorithms.SHA256, true
    );
    byte[] preSigned = sgn.getAuthenticatedAttributeBytes(
        hash, sap.SignDate, null, null, CryptoStandard.CMS
    );

    var hashedValue = Convert.ToBase64String(preSigned);
}

只是一个简单的测试 - 在初始页面请求上创建虚拟Pdf文档,计算哈希值,并将其放入隐藏的输入字段Base64编码。 (上面hashedValue

然后在客户端使用CAPICOM来发布表单并获取用户的签名响应:

PdfSignatureAppearance sap = (PdfSignatureAppearance)TempData[TEMPDATA_SAP];
PdfPKCS7 sgn = (PdfPKCS7)TempData[TEMPDATA_PKCS7];
stream = (MemoryStream)TempData[TEMPDATA_STREAM];
byte[] hash = (byte[])TempData[TEMPDATA_HASH];

byte[] originalText = (Encoding.Unicode.GetBytes(hashValue));
// Oid algorithm verified on client side
ContentInfo content = new ContentInfo(new Oid("RSA"), originalText);

SignedCms cms = new SignedCms(content, true);
cms.Decode(Convert.FromBase64String(signedValue));
// CheckSignature does not throw exception
cms.CheckSignature(true);
var encodedSignature = cms.Encode();

/* tried this too, but no effect on result
sgn.SetExternalDigest(
    Convert.FromBase64String(signedValue),
    null,
    "RSA"
);
byte[] encodedSignature = sgn.GetEncodedPKCS7(
    hash, sap.SignDate, null, null, null, CryptoStandard.CMS
);
*/
byte[] paddedSignature = new byte[SIG_BUFFER];
Array.Copy(encodedSignature, 0, paddedSignature, 0, encodedSignature.Length);
var pdfDictionary = new PdfDictionary();
pdfDictionary.Put(
    PdfName.CONTENTS,
    new PdfString(paddedSignature).SetHexWriting(true)
);
sap.Close(pdfDictionary);

所以现在我不确定我是否搞乱了散列部分,签名部分或两者兼而有之。在上面的签名代码片段和客户端代码(未显示)中,我正在调用我认为的签名验证码,但这也可能是错误的,因为这对我来说是第一次。在打开PDF时,获取臭名昭着的“ 文档已被更改或损坏,因为它已签名 ”无效的签名消息。

客户端代码(不是我创作的)can be found here。 Source有一个变量命名错误,已更正。作为参考,CAPICOM文档说明已签署的回复is in PKCS#7 format

编辑2015-03-12

经过@mkl和更多研究的一些不错的指示,在这种情况下,似乎CAPICOM 实际上无法使用。虽然根据herehere没有明确记录,(还有什么是新的?),CAPICOM期望将utf16字符串(.NET中的Encoding.Unicode)作为输入来创建数字签名。从那里它填充或截断(取决于前一句中的哪个源正确)如果长度是奇数,它接收的任何数据。即如果PdfSignatureAppearance.GetRangeStream()返回的Stream的长度为奇数,则签名创建将始终失败。也许我应该创建一个我很幸运选项:如果远程流长度是偶数则签名,如果奇数则抛出InvalidOperationException。 (悲伤的幽默尝试)

供参考,这是测试项目

编辑2015-03-25

关闭此循环,here's a link to a VS 2013 ASP.NET MVC project。可能不是最佳方式,但确实为问题提供完全可行的解决方案。由于CAPICOM奇怪且不灵活的签名实现,如上所述,知道可能的解决方案可能需要第二次传递,并且如果PdfSignatureAppearance.GetRangeStream()的返回值(再次,Stream.Length)注入额外字节的方法是一个奇数。我打算通过填充PDF内容来尝试漫长而艰难的方式,但幸运的是,同事发现填充PdfSignatureAppearance.Reason要容易得多。需要第二次通过才能使用iText [夏普],这并不是史无前例的 - 例如adding page x of y for a document page header/footer

1 个答案:

答案 0 :(得分:1)

使用 PdfPkcs7

在计算范围流摘要之后和将数据转发到网页之前,服务器端代码包含此块:

PdfPKCS7 sgn = new PdfPKCS7(
    null, chain, DigestAlgorithms.SHA256, true
);
byte[] preSigned = sgn.getAuthenticatedAttributeBytes(
    hash, sap.SignDate, null, null, CryptoStandard.CMS
);

var hashedValue = Convert.ToBase64String(preSigned);

在手头的情况下,这不是必要的。仅当您使用的外部签名API仅返回已签名的摘要时才需要它;在这种情况下,PdfPKCS7实例构建CMS / PKCS#7签名容器。另一方面,您使用您知道的API

  

CAPICOM文档说签署的响应采用PKCS#7格式。

因此,您不需要(更重要的是)不得使用 PdfPKCS7实例。

sign.js签署什么

服务器端hash变量的内容已经是要签名的数据的哈希摘要值。因此,前端,即那里使用的sign.js,不得再次散列它以获取消息摘要属性值以放入签名。

但是IE的sign.js签名方法最终会执行

var signedData = new ActiveXObject("CAPICOM.SignedData");

// Set the data that we want to sign
signedData.Content = src;
另一方面,

SignedData.Content被记录为

  

内容 读/写要签名的数据。

     

msdn: "SignedData object"

因此来自后端的哈希用作要签名的数据而不是要签名的数据的哈希,你确实哈希两次所以有错误那里的哈希值。

因此,看起来你必须传输整个远程流,这不是真正实用的......

&#34;但过去曾使用CAPICOM签署样本...&#34;

事实上,一些旧的iTextSharp(版本4.x)签名示例使用了CAPICOM。但该代码只有用,因为它创建了PDF签名类型 adbe.pkcs7.sha1 的签名,其中远程流的SHA1哈希确实是嵌入并由PKCS#7签名的数据签名

这不再是一个真正的选择,因为

  • 它需要使用SHA1,在严重情况下无效,
  • 至少自ISO 32000-1(2008)以来一直不鼓励使用它,并且将在ISO 32000-2(正在开发中)中正式弃用。