使用PDFBOX为PDF添加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

