You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

请求确认PDFBox实现LTV启用的文档/签名时间戳签名思路及代码示例

理解确认与补充

核心逻辑正确性

你的梳理整体准确,补充几个关键细节:

  • 文档时间戳(DTS)作为独立签名,需将证书、CRL/OCSP关联到DSS字典的VRI条目,而非仅存入DSS根节点,确保验证时能精准匹配时间戳的信任链;
  • 后续签名添加前序吊销信息时需去重,避免DSS中重复存储相同的证书、CRL或OCSP;
  • 签名时间戳(无符号属性)的吊销信息,既要关联到当前签名的VRI条目,也要合并到DSS的全局吊销集合中。

PDFBox实现LTV启用的代码示例

以下是获取时间戳服务的证书、CRL、OCSP并嵌入DSS字典的核心代码:

1. 工具类:获取时间戳服务的吊销信息

import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import java.io.InputStream;
import java.net.URL;
import java.security.cert.CRL;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

public class LTVUtils {
    // 获取TSA证书链(假设TSA提供证书获取接口)
    public static List<X509Certificate> getTSACertChain(String tsaUrl) throws Exception {
        URL url = new URL(tsaUrl + "/cert");
        try (InputStream in = url.openStream()) {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            List<X509Certificate> certChain = new ArrayList<>();
            certChain.add((X509Certificate) cf.generateCertificate(in));
            // 补充中间证书(若TSA接口返回完整链可省略)
            return certChain;
        }
    }

    // 从证书提取CRL分发点并获取CRL
    public static CRL getCRL(X509Certificate cert) throws Exception {
        String crlUrl = cert.getExtensionValue("2.5.29.31").toString();
        if (crlUrl == null) return null;
        URL url = new URL(crlUrl);
        try (InputStream in = url.openStream()) {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            return cf.generateCRL(in);
        }
    }

    // 从证书提取OCSP分发点并获取OCSP响应
    public static BasicOCSPResp getOCSPResp(X509Certificate cert, X509Certificate issuerCert) throws Exception {
        String ocspUrl = cert.getExtensionValue("1.3.6.1.5.5.7.48.1").toString();
        if (ocspUrl == null) return null;
        // 实际需实现完整OCSP请求签名逻辑,此处为简化示例
        org.bouncycastle.cert.ocsp.OCSPResp ocspResp = new org.bouncycastle.cert.ocsp.OCSPRespBuilder()
                .build(org.bouncycastle.cert.ocsp.OCSPRespBuilder.SUCCESSFUL, null);
        return (BasicOCSPResp) ocspResp.getResponseObject();
    }
}

2. 文档时间戳(DTS)添加与DSS更新

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;

import java.io.File;
import java.io.FileOutputStream;
import java.security.cert.X509Certificate;
import java.util.List;

public class DocTimeStampLTV {
    public static void addDocTimeStampWithLTV(String inputPath, String outputPath, String tsaUrl) throws Exception {
        try (PDDocument document = PDDocument.load(new File(inputPath))) {
            // 创建文档时间戳签名
            PDSignature signature = new PDSignature();
            signature.setType(PDSignature.TYPE_TIMESTAMP);
            signature.setSubFilter(PDSignature.SUBFILTER_ETSI_RFC3161);
            signature.setName("Document Timestamp");
            signature.setLocation("LTV Enabled");

            // 获取TSA证书链及吊销信息
            List<X509Certificate> tsaCertChain = LTVUtils.getTSACertChain(tsaUrl);
            X509Certificate tsaCert = tsaCertChain.get(0);
            CRL crl = LTVUtils.getCRL(tsaCert);
            BasicOCSPResp ocspResp = LTVUtils.getOCSPResp(tsaCert, tsaCertChain.get(1));

            // 初始化签名选项
            SignatureOptions options = new SignatureOptions();
            options.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);

            // 更新DSS字典
            var dss = document.getDocumentCatalog().getAcroForm().getDSS();
            dss.addCertificate(tsaCert);
            if (crl != null) dss.addCRL(crl);
            if (ocspResp != null) dss.addOCSP(ocspResp);

            // 关联VRI到时间戳签名
            dss.addValidationReference(signature, tsaCertChain, crl, ocspResp);

            // 获取TSA时间戳令牌(需自行实现TSAHelper类处理RFC3161请求)
            TSAHelper tsaHelper = new TSAHelper(tsaUrl);
            byte[] timestampBytes = tsaHelper.getTimeStampToken(signature.getByteRange());
            signature.setContents(timestampBytes);

            document.addSignature(signature, options);
            document.save(new FileOutputStream(outputPath));
        }
    }
}

3. 签名时间戳(无符号属性)的DSS更新

public static void addSignatureTimestampWithLTV(PDDocument document, PDSignature existingSignature, String tsaUrl) throws Exception {
    // 获取TSA吊销信息
    List<X509Certificate> tsaCertChain = LTVUtils.getTSACertChain(tsaUrl);
    X509Certificate tsaCert = tsaCertChain.get(0);
    CRL crl = LTVUtils.getCRL(tsaCert);
    BasicOCSPResp ocspResp = LTVUtils.getOCSPResp(tsaCert, tsaCertChain.get(1));

    // 更新DSS字典
    var dss = document.getDocumentCatalog().getAcroForm().getDSS();
    dss.addCertificate(tsaCert);
    if (crl != null) dss.addCRL(crl);
    if (ocspResp != null) dss.addOCSP(ocspResp);

    // 关联VRI到现有签名
    dss.addValidationReference(existingSignature, tsaCertChain, crl, ocspResp);

    // 生成时间戳并添加为无符号属性
    TSAHelper tsaHelper = new TSAHelper(tsaUrl);
    byte[] timestampToken = tsaHelper.getTimeStampToken(existingSignature.getSignedContents());
    existingSignature.getUnsignedAttributes().add(new org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignatureAttribute(
            org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignatureAttribute.TYPE_TIMESTAMP, timestampToken));
}

注意事项

  • 需自行实现TSAHelper类,完成符合RFC3161标准的时间戳请求与响应解析;
  • 吊销信息获取需处理异常场景(如TSA未提供CRL/OCSP接口);
  • 多签名场景下,需遍历所有前序签名,将其吊销信息合并到DSS中。

内容的提问来源于stack exchange,提问作者Qazazazaz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 08:54:58