如何在启用嵌入时间戳和 LTV 的情况下签署 PDF?

luc*_*sdc 5 java pdf timestamp itext digital-signature

我正在尝试签署启用了时间戳和 LTV 的 pdf,以便它在 Adob​​e Reader 中显示如下:

正确签名

在英语中,这意味着“签名包含嵌入的时间戳”和“签名启用了 LTV”。这是我正在使用的代码:

PrivateKey pk = // get pk from an encrypting certificate created using encrypting file system
Certificate[] chain = ks.getCertificateChain(alias);

PdfReader reader = new PdfReader(src);
FileOutputStream fout = new FileOutputStream(dest);
PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0');

PdfSignatureAppearance sap = stp.getSignatureAppearance();

ExternalSignature signature = new PrivateKeySignature(pk, "SHA-512", "SunMSCAPI");
TSAClient tsc = null;
String url = // TSA URL
tsc = new TSAClientBouncyCastle(url, null, null, 4096, "SHA-512");

List<CrlClient> crlList = new ArrayList<>();
crlList.add(new CrlClientOnline(chain));

ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(sap, digest, signature, chain, crlList, null, tsc, 0, CryptoStandard.CMS);
Run Code Online (Sandbox Code Playgroud)

基于这个答案,我需要一种方法来获取 TSA 证书的 CRL CrlList,但是..我怎样才能获得 TSA 证书?我是否需要timestamp-query向 TSA 提出请求并读取响应,然后将其添加到CrlListMakeSignature.signDetached请注意,当它调用 时,这已经在内部完成了sgn.getEncodedPKCS7。请注意,我使用的是免费的 TSA 服务器。

这就是上面代码在 Adob​​e Reader 中显示的内容。签名详情: 在此输入图像描述

时间戳详细信息: 在此输入图像描述

TSA 证书详细信息: 在此输入图像描述


更新

由于它是免费的 TSA 服务器,因此我只需在 Adob​​e 受信任的证书中添加 TSA 服务器证书,现在它就可以工作了。但是,我使用智能卡签署文档进行了另一项测试,这就是我得到的结果(我已将根证书添加到 Adob​​e 中的受信任证书中):

签名详情:

在此输入图像描述

签署证书详细信息:

在此输入图像描述

基于此链接,启用 LTV 意味着验证文件所需的所有信息(减去根证书)都包含在 PDF 中。因此,如果 PDF 已正确签名并包含所有必要的证书以及每个证书的有效 CRL 或 OSCP 响应,并且如果它包含 CRL 和 OCSP 上的签名,而不仅仅是签名证书,则该 PDF 已启用 LTV。看起来我已经满足了所有这些要求,还是我遗漏了一些东西?如果是这样,我如何知道获取支持 LTV 的 pdf 缺少什么?

luc*_*sdc 1

首先,根据 @mkl 评论,我将 TSA 服务器证书添加到 Adob​​e 受信任的证书中,以便该消息

签名包含嵌入的时间戳,但无法验证

成为

签名包含嵌入的时间戳

并解决

签名未启用 LTV,并将在 (...) 后过期

我可以注意到使用

List<CrlClient> crlList = new ArrayList<>();
crlList.add(new CrlClientOnline(chain));
Run Code Online (Sandbox Code Playgroud)

有一些 CRL(某些证书有多个分发点)未添加 - 导致 pdf LTV 未启用。为了解决这个问题,我这样做了:

// long term validation (LTV)
List<CrlClient> crlList = new ArrayList<>();

for(Certificate cert : chain) {
    X509Certificate c = (X509Certificate)cert;
    List<String> crls = this.getCrlDistributionPoints(c);
    if(crls != null && !crls.isEmpty()) {
        crlList.add(new CrlClientOnline(crls.toArray(new String[crls.size()])));
    }
}

private List<String> getCrlDistributionPoints(final X509Certificate cert) throws Exception {
    final byte[] crldpExt = cert.getExtensionValue(X509Extension.cRLDistributionPoints.getId());
    if (crldpExt == null) {
        final List<String> emptyList = new ArrayList<String>();
        return emptyList;
    }
    ASN1InputStream oAsnInStream = null;
    ASN1InputStream oAsnInStream2 = null;
    List<String> crlUrls = new ArrayList<String>();

    try { 
        oAsnInStream = new ASN1InputStream(new ByteArrayInputStream(crldpExt));
        final ASN1Object derObjCrlDP = oAsnInStream.readObject();
        final DEROctetString dosCrlDP = (DEROctetString) derObjCrlDP;
        final byte[] crldpExtOctets = dosCrlDP.getOctets();
        oAsnInStream2 = new ASN1InputStream(new ByteArrayInputStream(crldpExtOctets));
        final ASN1Object derObj2 = oAsnInStream2.readObject();
        final CRLDistPoint distPoint = CRLDistPoint.getInstance(derObj2);
        for (final DistributionPoint dp : distPoint.getDistributionPoints()) {
            final DistributionPointName dpn = dp.getDistributionPoint();
            // Look for URIs in fullName
            if (dpn != null) {
                if (dpn.getType() == DistributionPointName.FULL_NAME) {
                    final GeneralName[] genNames = GeneralNames.getInstance(dpn.getName()).getNames();
                    // Look for an URI
                    for (int j = 0; j < genNames.length; j++) {
                        if (genNames[j].getTagNo() == GeneralName.uniformResourceIdentifier) {
                            final String url = DERIA5String.getInstance(genNames[j].getName()).getString();
                            crlUrls.add(url);
                        }
                    }
                }
            }
        }
    } catch(IOException e) {
        throw new Exception(e.getMessage(), e);
    } finally {
        IOUtils.closeQuietly(oAsnInStream);
        IOUtils.closeQuietly(oAsnInStream2);
    }

    return crlUrls;
}
Run Code Online (Sandbox Code Playgroud)