如何使用iText在Web上下文中使用智能卡签署PDF? [英] How do I sign a PDF with a Smart Card in a web context using iText?

查看:197
本文介绍了如何使用iText在Web上下文中使用智能卡签署PDF?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

阅读以下参考资料:





哈希代码:

  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 =测试网络上下文签名;

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

字典< 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
);

//这需要吗?
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算法
ContentInfo content = new ContentInfo(new Oid(RSA),originalText);

SignedCms cms = new SignedCms(content,true);
cms.Decode(Convert.FromBase64String(signedValue));
// CheckSignature不会抛出异常
cms.CheckSignature(true);
var encodedSignature = cms.Encode();

/ *也尝试了这一点,但对结果没有影响
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时,获取臭名昭着的 文档已被更改或损坏,因为它已签名 无效的签名消息。



客户端代码(不是我创作的)可以在这里找到。 Source有一个变量命名错误,已更正。作为参考,CAPICOM文档说签名响应采用PKCS#7格式



编辑2015-03-12



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



作为参考,这是测试项目



编辑2015-03-25



要关闭此循环, 这里是VS 2013 ASP.NET MVC项目的链接 。可能不是最佳方式,但确实为问题提供完全可行的解决方案。由于CAPICOM奇怪且不灵活的签名实现,如上所述,知道可能的解决方案可能需要第二次传递,并且如果 PdfSignatureAppearance.GetRangeStream()(再次, Stream.Length )是一个奇数。我打算通过填充PDF内容来尝试漫长而艰难的方式,但幸运的是,一位同事发现填写 PdfSignatureAppearance.Reason 更容易。要求第二次通过使用iText [夏普]做某事并不是前所未有的 - 例如为文档页眉/页脚添加y的第x页

解决方案

使用 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签署什么



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



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

  var signedData = new ActiveXObject(CAPICOM.SignedData); 

//设置我们要签署的数据
signedData.Content = src;

SignedData.Content ,另一方面,记录为


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



msdn:SignedData对象


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

因此,看起来你必须传输整个远程流,这实际上并不实用......



但过去常常使用CAPICOM签署样本......



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



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




  • 它需要使用SHA1,在严重情况下无效,

  • 至少自ISO 32000-1(2008)以来不鼓励使用它,并且将在ISO 32000-2中正式弃用(正在开发中)。


Read through the following references:

Hashing code:

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);
}

Just a simple test - a dummy Pdf document is created on initial page request, hash is calculated, and put in a hidden input field Base64 encoded. (the hashedValue above)

Then use CAPICOM on client-side to POST the form and get user's signed response:

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);

So right now I'm not sure if I'm messing up hashing part, signature part, or both. In signature code snippet above and in client code (not shown) I'm calling what I think is signature verification code, but that may be wrong too, since this is a first for me. Get the infamous "Document has been altered or corrupted since it was signed" invalid signature message when opening the PDF.

Client-side code (not authored by me) can be found here. Source has a variable naming error, which was corrected. For reference, CAPICOM documentation says signed response is in PKCS#7 format.

EDIT 2015-03-12:

After some nice pointers from @mkl and more research, it seems CAPICOM is practicably unusable in this scenario. Although not documented clearly, (what else is new?) according to here and here, CAPICOM expects a utf16 string (Encoding.Unicode in .NET) as input to create a digital signature. From there it either pads or truncates (depending which source in previous sentence in correct) whatever data it receives if the length is an odd number. I.e. signature creation will ALWAYS FAIL if the Stream returned by PdfSignatureAppearance.GetRangeStream() has a length that is an odd number. Maybe I should create an I'm lucky option: sign if ranged stream length is even, and throw an InvalidOperationException if odd. (sad attempt at humor)

For reference, here's the test project.

EDIT 2015-03-25:

To close the loop on this, here's a link to a VS 2013 ASP.NET MVC project. May not the be best way, but it does provide a fully working solution to the problem. Because of CAPICOM's strange and inflexible signing implementation, as described above, knew a possible solution would potentially require a second pass and a way to inject an extra byte if the return value of PdfSignatureAppearance.GetRangeStream() (again, Stream.Length) is an odd number. I was going to try the long and hard way by padding the PDF content, but luckily a co-worker found it was much easier to pad PdfSignatureAppearance.Reason. Requiring a second pass to do something with iText[Sharp], is not unprecedented - e.g. adding page x of y for a document page header/footer.

解决方案

Use of PdfPkcs7

The server-side code contains this block after the calculation of the range stream digest and before forwarding data to the web page:

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);

In the case at hand this is not necessary. It is needed only if the external signing API you use merely returns a signed digest; in that case the PdfPKCS7 instance builds the CMS/PKCS#7 signature container. You, on the other hand, use an API for which you know

CAPICOM documentation says signed response is in PKCS#7 format.

Thus, you don't need and (more to the point) must not use the PdfPKCS7 instance.

What does sign.js sign

The content of the server-side hash variable already is the hash digest value of the data to sign. Thus, the frontend, i.e. the sign.js used there, must not hash it again to get the message digest attribute value to put into the signature.

But sign.js signing methods for IE eventually execute

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

// Set the data that we want to sign
signedData.Content = src;

SignedData.Content, on the other hand, is documented as

Content Read/write Data to be signed.

(msdn: "SignedData object")

So the hash from the backend is used as data to be signed and not as hash of the data to be signed, you indeed hash twice and so have the wrong hash value there.

Thus, it looks like you have to transmit the whole ranged stream which is not really practical...

"But there used to be signing samples using CAPICOM..."

Indeed some old iTextSharp (version 4.x) signing example used CAPICOM. But that code only worked because it created signatures of PDF signature type adbe.pkcs7.sha1 for which a SHA1 hash of the ranged stream indeed is the data embedded in and signed by the PKCS#7 signature.

This is no real option anymore because

  • it requires the use of SHA1 which in serious contexts is invalid, and
  • its use has been discouraged at least since ISO 32000-1 (2008) and will be officially deprecated in ISO 32000-2 (under development).

这篇关于如何使用iText在Web上下文中使用智能卡签署PDF?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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