PDF数字签名验证在政府文档上失败

时间:2018-09-12 09:29:04

标签: pdf itext digital-signature

我们正在尝试验证荷兰政府机构(UWV Verzekeringsbericht)的数字签名,包括文件的真实性。
Adobe Acrobat Reader能够正确验证此文件。

通过一个小的概念验证应用程序,我们就能验证各种经过数字签名的PDF的真实性:

import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.Security;
import java.util.ArrayList;

public class Verifier {

    public static void main(String[] args) throws IOException, GeneralSecurityException {

        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);

        new Verifier().run(args[0]);
    }

    private void run(String path) throws IOException, GeneralSecurityException {

        final PdfReader reader = new PdfReader(path);

        final AcroFields fields = reader.getAcroFields();

        final ArrayList<String> signatureNames = fields.getSignatureNames();

        for(String signatureName: signatureNames) {
            System.out.println("Verify signature " + signatureName);
            verifySignature(fields, signatureName);
        }
    }

    private PdfPKCS7 verifySignature(final AcroFields fields, final String name) throws GeneralSecurityException {
        System.out.println("Signature covers whole document: " + fields.signatureCoversWholeDocument(name));
        System.out.println("Document revision: " + fields.getRevision(name) + " of " + fields.getTotalRevisions());
        PdfPKCS7 pkcs7 = fields.verifySignature(name);
        System.out.println("Integrity check OK? " + pkcs7.verify());
        return pkcs7;
    }
}

使用这些(Maven)依赖项:

<dependencies>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.5.13</version>
    </dependency>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-debug-jdk15on</artifactId>
        <version>1.60</version>
    </dependency>
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk15on</artifactId>
        <version>1.60</version>
    </dependency>
</dependencies>

您可能会猜到,无法通过此权限验证PDF。

运行该应用程序的结果是:

Exception in thread "main" java.lang.IllegalArgumentException: can't decode PKCS7SignedData object
    at com.itextpdf.text.pdf.security.PdfPKCS7.<init>(PdfPKCS7.java:214)

这是在PdfPKCS7类内引起的,该类正在根据签名的内容实例化ASN1输入流(第203行):

SN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(contentsKey));

这又导致 IOException:DER长度超过4个字节:31 因此签名似乎无效。

AcroFields的verifySignature方法调用尝试创建一个PdfPKCS7实例。此方法的摘要:

if(!reader.isEncrypted()){
    pk = new PdfPKCS7(contents.getOriginalBytes(), sub, provider);
}else{
    pk = new PdfPKCS7(contents.getBytes(),sub,provider);
}

由于某种原因,iTextPDF得出结论认为PDF已加密,并使用getBytes变体进行签名验证。 但是,据我所知,PDF未加密,因此应使用getOriginalBytes

当我强制使用此原始内容时,在调试时,验证成功!

因此,这似乎是iTextPDF中的错误,可能是由于pdf中异常的因素组合造成的。

PDF证书中的一些详细信息:

Version: 3
Signature algorithm: SHA256 RSA
Key usage: Digital Signature, Encrypt Keys
Public Key: RSA (2048 bits)

很遗憾,我无法共享相关的PDF,因为它包含个人信息。 作为荷兰公民,您可以从UWV下载自己的版本,请参见these instructions

任何帮助或建议都会受到赞赏。

1 个答案:

答案 0 :(得分:2)

此问题的背景似乎是ISO 32000-1 PDF规范中缺少的信息;同时,iText 5.5支持ISO 32000-1逐字解释。

与此同时,在ISO 32000-2中对此进行了澄清。


缺少的信息

在PDF成为ISO标准 之前,当PDF文档不清楚甚至有其他主张时,PDF处理器实施者会遵循Adobe Acrobat的领导。

当Adobe Acrobat对PDF进行加密和签名时,包含签名容器的二进制字符串不会被加密。因此,在这种情况下,其他PDF工具也没有不加密签名容器

PDF于2008年成为ISO标准 。根据ISO 32000-1,

  

加密适用于文档PDF文件中的所有字符串和流,但以下情况除外:

     
      
  • 预告片中ID条目的值
  •   
  • 加密字典中的任何字符串
  •   
  • 诸如内容流和压缩对象流之类的流内部的任何字符串,它们本身都是经过加密的
  •   

(ISO 32000-1,第7.6节-加密)

因此,在经过加密和签名的PDF中,包含嵌入式签名容器的二进制字符串也将被加密

2017年,发布了ISO 32000第2部分 。在上面的枚举中添加了一个新条目

  
      
  • 代表签名字典中Contents键值的任何十六进制字符串
  •   

(ISO 32000-2,第7.6节-加密)

因此,在经过加密和签名的PDF中,包含嵌入式签名容器的二进制字符串不会被加密

在iText中检索签名容器的代码

在iText中用于检索签名容器的最早代码中,假定从未对包含签名容器的二进制字符串进行加密:

pk = new PdfPKCS7(contents.getOriginalBytes(), provider);

(提交日期为2004年11月5日的ffc70db,注释为“ paulo 139版”)

方法getOriginalBytes检索PDF字符串中的字节,就像它们在PDF中一样,从未解密。

此后,代码被移动了两次或三次而没有更改。

添加了PAdES支持后,此处仅添加了子过滤器,仍然使用了原始字节:

pk = new PdfPKCS7(contents.getOriginalBytes(), sub, provider);

(commit 691281c,日期为2012年8月31日,评论为“验证CAdES签名”)

但是在2017年初,它已更改为您找到的代码:

if(!reader.isEncrypted()){
    pk = new PdfPKCS7(contents.getOriginalBytes(), sub, provider);
}else{
    pk = new PdfPKCS7(contents.getBytes(),sub,provider);
}

(提交日期为2017年2月9日的0b852d7,注释为“在验证签名SUP-1783时处理加密的内容流”)

显然,支持问题SUP-1783促使人们转向了对ISO 32000-1的逐字解释。

在iText 7中,我们拥有

pk = new PdfPKCS7(PdfEncodings.convertToBytes(contents.getValue(), null), sub, provider);

(提交ae73650,日期为2015年10月11日,评论为“添加了支持LTV,Ocsp,CRL和TSA的类。”)

但是这里的contents之前已标记为未加密

contents.markAsUnencryptedObject();

(提交日期为2018年4月24日的6dfb206,注释为“在传递只读文档时避免SignatureUtil中出现异常”)

,在iText 7中,这使contents.getValue()返回原始字节。因此,iText 7支持PDF 2.0澄清。

该怎么办?

我认为,考虑到ISO 32000-1的逐字解释,应该接受加密的或未加密的签名容器,但是根据ISO 32000-2的措辞,一个容器只能生成未加密的签名容器。