使用外部服务和iText签署PDF

时间:2015-02-25 11:21:25

标签: itext digital-signature digest

我有这种情况。

我有一个生成PDF的应用程序,需要签名。

我们没有签署文件的证书,因为他们在HSM中,我们使用证书的唯一方法就是使用网络服务。

此Web服务提供两个选项,发送PDF文档,然后返回已签名的pdf,或发送将要签名的哈希。

第一个选项不可行,因为PDF没有时间戳签名(这是一个非常重要的必要条件),因此选择了第二个选项。

这是我们的代码,首先,我们获得签名外观,并计算哈希:

PdfReader reader = new PdfReader(Base64.decode(pdfB64));
reader.setAppendable(true);
baos = new ByteArrayOutputStream();

PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0', null, true);
appearance = stamper.getSignatureAppearance();
appearance.setCrypto(null, chain, null, PdfSignatureAppearance.SELF_SIGNED);
appearance.setVisibleSignature("Representant");
cal = Calendar.getInstance();
PdfDictionary dic = new PdfDictionary();
dic.put(PdfName.TYPE, PdfName.SIG);
dic.put(PdfName.FILTER, PdfName.ADOBE_PPKLITE);
dic.put(PdfName.SUBFILTER, new PdfName("adbe.pkcs7.detached"));
dic.put(PdfName.M, new PdfDate(cal));
appearance.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, Integer.valueOf(reservedSpace.intValue() * 2 + 2));
appearance.setCertificationLevel(1);
appearance.preClose(exc);

AbstractChecksum checksum = JacksumAPI.getChecksumInstance("sha1");
checksum.reset();
checksum.update(Utils.streamToByteArray(appearance.getRangeStream()));
hash = checksum.getByteArray();

在这一点上,我们有文档的哈希码。然后我们将哈希发送到webservice,我们得到签名的哈希码。

最后,我们将签名的哈希值放到PDF:

byte[] paddedSig = new byte[reservedSpace.intValue()];
System.arraycopy(signedHash, 0, paddedSig, 0, signedHash.length);

PdfDictionary dic = new PdfDictionary();
dic.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
appearance.close(dic);

byte[] pdf = baos.toByteArray();

在这一点上,我们获得了PDF签名,但签名无效。 Adobe表示&#34;文档自签署以来已被更改或损坏&#34;。

我认为我们在这个过程中犯了错误,而且我们并不确切知道可能是什么。

我们非常感谢您提供帮助或其他方式。

感谢。


EDITED

正如mkl所建议的那样,我已经按照本书的{4.3}部分Digital Signatures for PDF documents,现在我的代码如下:

第一部分,当我们计算哈希时:

PdfReader reader = new PdfReader(Base64.decode(pdfB64));
reader.setAppendable(true);
baos = new ByteArrayOutputStream();

PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0');
appearance = stamper.getSignatureAppearance();

appearance.setReason("Test");
appearance.setLocation("A casa de la caputeta");
appearance.setVisibleSignature("TMAQ-TSR[0].Pagina1[0].DadesSignatura[0].Representant[0]");
appearance.setCertificate(chain[0]);

PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(appearance.getReason());
dic.setLocation(appearance.getLocation());
dic.setContact(appearance.getContact());
dic.setDate(new PdfDate(appearance.getSignDate()));
appearance.setCryptoDictionary(dic);

HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(reservedSpace.intValue() * 2 + 2));
appearance.preClose(exc);

ExternalDigest externalDigest = new ExternalDigest()
{
    public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException
    {
        return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
    }
};

sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
InputStream data = appearance.getRangeStream();
hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
cal = Calendar.getInstance();

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);

hashPdf = new String(Base64.encode(sh));

在第二部分中,我们得到了签名的哈希,我们将其放入PDF:

sgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA");
byte[] encodedSign = sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
byte[] paddedSig = new byte[reservedSpace.intValue()];
System.arraycopy(encodedSign, 0, paddedSig, 0, encodedSign.length);

PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

appearance.close(dic2);

byte[] pdf = baos.toByteArray();

现在,Adobe引发了内部加密库错误。错误代码:0x2726,当我们尝试验证签名时。

2 个答案:

答案 0 :(得分:7)

如果Web服务返回了一个仅仅签名的哈希

  

在这一点上,我们有文档的哈希码。然后我们将哈希发送到webservice,我们得到签名的哈希码。

     

最后,我们将签名的哈希值放到PDF:

如果网络服务仅返回签名哈希,则您的PDF签名不正确:您将签名 SubFilter 设置为 adbe.pkcs7.detached 。这意味着签名目录必须包含一个完整的PKCS#7签名容器,而不仅仅是签名的哈希。

您可能希望下载Digital Signatures for PDF documents Bruno Lowagie(iText软件)的白皮书,了解如何使用iText创建和验证数字PDF签名。它特别包含一个用于签名的部分&#34; 4.3客户端/服务器体系结构&#34;这应该包含你的用例。

但是,Web服务返回一个完整的CMS签名容器

根据上述说明,OP开始使用上述白皮书第4.3.3节中的代码,该白皮书旨在使用外部生成的签名哈希进行签名。由于这也导致了Adobe Reader不满意的签名文档,他提供了使用这个新代码创建的示例文档。

对示例的分析表明,嵌入在文档中的CMS签名容器包含另一个CMS签名容器,其中应该有签名属性的签名字节(签名哈希):

2417   13:           SEQUENCE {
2419    9:             OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
2430    0:             NULL
         :             }
2432 5387:           OCTET STRING, encapsulates {
2436 NDEF:             SEQUENCE {
2438    9:               OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2)
2449 NDEF:               [0] {
2451 NDEF:                 SEQUENCE {

(签名算法之后的OCTET STRING应该包含签名字节,而不是嵌入另一个SignedData结构。)

这表明Web服务确实已经返回了一个成熟的CMS容器。

对于这种情况,原始代码看起来很不错。问题可能是由于使用了错误的散列算法(使用SHA1散列的原始代码)这样​​的细节。

可能的问题:BER编码

由iText从OP提供的第一个样本生成的CMS容器中嵌入的Web服务的CMS签名容器提示可能存在的问题:查看嵌入式外部结构大小的ASN.1转储CMS容器通常为NDEF

这表明这些外部结构是使用较不严格的BER(基本编码规则)创建的,而不是更严格的DER(可分辨编码规则),因为在DER中禁止启动结构而没有说明其大小的BER选项。 / p>

从PDF规范引用的CMS规范(RFC 3852)允许对容器的外部结构进行任何BER编码,另一方面需要PDF规范:

  

Contents的值应为DER编码的PKCS#7二进制数据   包含签名的对象。 PKCS#7对象应符合RFC3852加密消息语法。

因此,严格地说,嵌入在PDF中的签名容器需要全部进行DER编码。

据我所知,只要签名容器DER编码某些关键元素,就没有PDF签名验证器拒绝这样的签名。关于未来的工具,这种签名可能是失败的原因。

答案 1 :(得分:0)

经过多次调试,我们终于找到了问题。

出于某种神秘的原因,生成文档哈希的方法执行了两次,使第一个哈希(我们用来发送给服务)无效。

在对代码进行重构工作之后,原始代码才能正常工作。

非常感谢所有帮助我的人,特别是mkl。