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

使用PDFBOX为PDF添加CA证书后Adobe显示无效且无详情问题

PDFBOX添加CA数字证书后Adobe提示证书无效且详情无法显示的原因分析

我在Java环境中使用PDFBOX工具为PDF文档添加CA(Certificate Authority)提供的数字证书及签名,整个操作过程未出现任何报错,但使用Adobe Acrobat打开该PDF时,却提示证书无效,且签名面板中无法查看证书详情。已通过代码确认PDF中存在签名字段,期望签名详情弹窗正常显示、证书状态有效,并显示“文档由XXX认证”。

相关代码

private void attachNotariusVerifiedCertificate(String signRequestId, FileOutputStream output, PDDocument unsignedDocument) throws Exception
{
    loadExternalCert(eSignCertProvider.getCertificateChain());
    ExternalSigningSupport externalSigningSupport = unsignedDocument.saveIncrementalForExternalSigning(output);
    byte[] cmsSignature = signExternally(externalSigningSupport.getContent(), signRequestId);
    externalSigningSupport.setSignature(cmsSignature);
}

private byte[] signExternally(InputStream content, String signRequestId) throws IOException
{
    List<Certificate> externalCertificates;
    JcaCertStore jcaCertStore;
    CMSSignedDataGenerator generator;
    MessageDigest messageDigest;
    byte[] documentHash;
    byte[] signedDocumentHash;
    ContentSigner contentSigner;
    JcaSignerInfoGeneratorBuilder jcaSignerInfoGeneratorBuilder;
    try {
        externalCertificates = new ArrayList<>(Arrays.asList(externalCertificateChain));
        jcaCertStore = new JcaCertStore(externalCertificates);
        generator = new CMSSignedDataGenerator();
        generator.addCertificates(jcaCertStore);
        messageDigest = MessageDigest.getInstance("SHA-256");
        documentHash = messageDigest.digest(content.readAllBytes());
        signedDocumentHash = eSignCertProvider.signDocumentHash(documentHash, signRequestId);
        contentSigner = new ContentSigner()
        {
            @Override
            public byte[] getSignature() {
                return signedDocumentHash;
            }

            @Override
            public OutputStream getOutputStream() {
                return new ByteArrayOutputStream();
            }

            @Override
            public AlgorithmIdentifier getAlgorithmIdentifier() {
                return new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSA");
            }
        };
        jcaSignerInfoGeneratorBuilder = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
        jcaSignerInfoGeneratorBuilder.setDirectSignature(true);
        org.bouncycastle.asn1.x509.Certificate certificateX509 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(externalCertificateChain[0].getEncoded()));
        generator.addSignerInfoGenerator(jcaSignerInfoGeneratorBuilder.build(contentSigner, new X509CertificateHolder(org.bouncycastle.asn1.x509.Certificate.getInstance(X509CertificateStructure.getInstance(certificateX509)))));
        CMSSignedData signedData = generator.generate(new CMSAbsentContent(), false);
        return signedData.getEncoded();
    } catch (Exception e) {
        loggingService.error(LOGGER_NAME, "Exception in signExternally()", e);
        throw new IOException(e);
    }
}

public byte[] signDocumentHash(byte[] hash, String signRequestId) throws Exception
{
    byte[] signedDocumentHash;
    ASN1EncodableVector digestInfoVector;
    DERSequence derSequence;
    String base64DigestInfo;
    String requestBodyJson;
    String signedPayload;
    String response;
    String signatureBase64;
    try {
        digestInfoVector = new ASN1EncodableVector();
        digestInfoVector.add(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE));
        digestInfoVector.add(new DEROctetString(hash));
        derSequence = new DERSequence(digestInfoVector);
        base64DigestInfo = Base64.getEncoder().encodeToString(derSequence.getEncoded());
        requestBodyJson = signRequestPayload(base64DigestInfo, signRequestId);
        signedPayload = signedPayload(requestBodyJson, serverConfig.geteSignConfig().getSignatureKey());
        response = sign(signedPayload).getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        Map map = objectMapper.readValue(response, Map.class);
        signatureBase64 = map.get("sig").toString();
        signedDocumentHash = Base64.getDecoder().decode(String.valueOf(signatureBase64.toCharArray()));
        return signedDocumentHash;
    } catch (Exception ex) {
        loggingService.error(LOGGER_NAME, "Exception in sign document hash reason:: " + ex.getMessage(), ex);
        throw ex;
    }
}

问题原因分析

  • 直接签名模式不符合PDF规范:代码中调用jcaSignerInfoGeneratorBuilder.setDirectSignature(true)启用了直接签名模式,该模式下SignerInfo不会包含签名证书的标识符引用。Adobe Acrobat无法从CMS结构中定位到对应的签名证书,因此无法显示证书详情,同时会判定证书无效。PDF签名要求SignerInfo必须关联签名证书,应禁用直接签名模式。

  • CMS内容构造错误:使用CMSAbsentContent生成CMS签名数据,这意味着CMS结构中没有包含与被签名文档哈希的绑定信息。Adobe无法验证签名与文档内容的关联性,进而认为签名无效,导致证书状态异常。正确的做法是使用包含文档哈希的CMS内容构造器,而非CMSAbsentContent。

  • 证书链关联不明确:虽然将证书链添加到了JcaCertStore,但在生成SignerInfo时,没有确保签名证书在证书链中的优先级,也可能未正确设置证书的Issuer和Serial Number引用。Adobe需要明确的证书引用才能在签名面板中加载并显示证书详情。

  • DigestInfo与CA签名逻辑不匹配:在signDocumentHash方法中构造了标准的DigestInfo结构并发送给CA签名,但需要确认CA是否确实是对该DigestInfo进行签名。如果CA实际签名的是原始哈希值而非封装后的DigestInfo,会导致本地验证签名时失败,Adobe判定证书无效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 10:35:07