如何在不使用DSS的情况下用Bouncy Castle生成PAdES-LT签名(CMSSignedData)
将PAdES-T升级为PAdES-LT的实现方法及撤销信息位置
我现有一段可生成PAdES-B格式CMSSignedData的代码,在将时间戳添加至未签名属性后,该签名已升级为PAdES-T格式。请问如何将其进一步转换为PAdES-LT格式?撤销信息应添加至何处?
原PAdES-B生成代码
private CMSSignedDataGenerator getCmsSignedDataGenerator(ElectronicSignatureModel signatureModel) { CMSSignedDataGenerator signGenerator = new CMSSignedDataGenerator(); signGenerator.addCertificates(new JcaCertStore(signatureModel.getChain())); ContentSigner nonSigner = new ContentSigner() { @Override public byte[] getSignature() { return signatureModel.getSignature(); } @Override public OutputStream getOutputStream() { return new ByteArrayOutputStream(); } @Override public AlgorithmIdentifier getAlgorithmIdentifier() { return new DefaultSignatureAlgorithmIdentifierFinder() .find(signatureModel.getSigningAlgorithm().getAlgoName()); } }; JcaSignerInfoGeneratorBuilder signerBuilder = new JcaSignerInfoGeneratorBuilder( new JcaDigestCalculatorProviderBuilder().build()); signerBuilder.setDirectSignature(true); //user certificate Certificate userCertHolder = Certificate.getInstance(ASN1Primitive.fromByteArray( signatureModel.getChain().get(0).getEncoded())); signGenerator.addSignerInfoGenerator(signerBuilder.build(nonSigner, new X509CertificateHolder(userCertHolder))); return signGenerator; } CMSSignedDataGenerator signGenerator = getCmsSignedDataGenerator(signatureModel, false); CMSSignedData cmsSignedData = signGenerator.generate(new CMSProcessableByteArray(signatureModel.getSignature()), false);
实现PAdES-LT的核心要求
PAdES-LT需包含长期可验证的撤销信息(CRL或OCSP响应),确保签名在证书过期后仍能被验证。撤销信息需被签名保护防止篡改,因此优先添加到SignerInfo的已签名属性中。
具体实现步骤
1. 撤销信息的添加位置
- 推荐方式:已签名属性中的
id-aa-ets-revocationValues:直接将CRL/OCSP响应嵌入已签名属性,该属性会被签名覆盖,确保不可篡改,完全符合PAdES-LT的长期验证要求。 - 补充方式:CMS的CertStore:将CRL添加到证书存储中,但该方式的撤销信息不被签名保护,仅作为辅助验证手段。
2. 修改代码实现
调整签名生成器,添加已签名属性和撤销信息
private CMSSignedDataGenerator getCmsSignedDataGenerator(ElectronicSignatureModel signatureModel, List<X509CRL> crls) throws Exception { CMSSignedDataGenerator signGenerator = new CMSSignedDataGenerator(); // 将证书和CRL加入CertStore(可选,作为补充) List<ASN1Encodable> certsAndCrls = new ArrayList<>(); for (Certificate cert : signatureModel.getChain()) { certsAndCrls.add(X509CertificateHolder.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()))); } for (X509CRL crl : crls) { certsAndCrls.add(CRL.getInstance(ASN1Primitive.fromByteArray(crl.getEncoded()))); } signGenerator.addCertificates(new JcaCertStore(certsAndCrls)); ContentSigner nonSigner = new ContentSigner() { @Override public byte[] getSignature() { return signatureModel.getSignature(); } @Override public OutputStream getOutputStream() { return new ByteArrayOutputStream(); } @Override public AlgorithmIdentifier getAlgorithmIdentifier() { return new DefaultSignatureAlgorithmIdentifierFinder() .find(signatureModel.getSigningAlgorithm().getAlgoName()); } }; JcaSignerInfoGeneratorBuilder signerBuilder = new JcaSignerInfoGeneratorBuilder( new JcaDigestCalculatorProviderBuilder().build()); // 构建已签名属性集合 ASN1EncodableVector signedAttrs = new ASN1EncodableVector(); // 添加消息摘要属性:必须计算待签名数据的摘要 String digestAlgoName = signatureModel.getSigningAlgorithm().getAlgoName().replace("SHA256withRSA", "SHA-256"); MessageDigest md = MessageDigest.getInstance(digestAlgoName); byte[] messageDigest = md.digest(signatureModel.getSignature()); signedAttrs.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest)))); // 添加签名时间属性 signedAttrs.add(new Attribute(CMSAttributes.signingTime, new DERSet(new DERUTCTime(new Date())))); // 添加撤销值属性:嵌入CRL数据 if (crls != null && !crls.isEmpty()) { ASN1EncodableVector crlVectors = new ASN1EncodableVector(); for (X509CRL crl : crls) { crlVectors.add(CRL.getInstance(ASN1Primitive.fromByteArray(crl.getEncoded()))); } // 若有OCSP响应,将第二个参数替换为OCSPResp的ASN1结构 RevocationValues revValues = new RevocationValues(crlVectors, null); signedAttrs.add(new Attribute(PKCSObjectIdentifiers.id_aa_ets_revocationValues, new DERSet(revValues))); } // 设置已签名属性生成器,移除directSignature模式(该模式会跳过已签名属性生成) signerBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeGenerator(new AttributeTable(signedAttrs))); // 用户证书处理 X509CertificateHolder userCertHolder = new X509CertificateHolder( ASN1Primitive.fromByteArray(signatureModel.getChain().get(0).getEncoded())); signGenerator.addSignerInfoGenerator(signerBuilder.build(nonSigner, userCertHolder)); return signGenerator; }
调用修改后的生成器
// 从CA获取当前签名证书链对应的CRL列表(需自行实现获取逻辑) List<X509CRL> crls = fetchCRLsForCertificateChain(signatureModel.getChain()); CMSSignedDataGenerator signGenerator = getCmsSignedDataGenerator(signatureModel, crls); CMSSignedData cmsSignedData = signGenerator.generate(new CMSProcessableByteArray(signatureModel.getSignature()), false);
3. 关键注意事项
- 必须移除原代码中的
signerBuilder.setDirectSignature(true);,该模式会跳过已签名属性的自动生成,而PAdES-LT依赖已签名属性携带撤销信息和验证必要字段。 - 已签名属性必须包含
messageDigest和signingTime,否则签名无法通过标准验证。 - 若使用OCSP响应替代CRL,需将OCSPResp转换为ASN1结构后传入
RevocationValues的第二个参数。
内容的提问来源于stack exchange,提问作者Rajath R Joshi
相关产品推荐
相关产品推荐

