提问人:Tanmay Sharma 提问时间:2/20/2023 最后编辑:Tanmay Sharma 更新时间:2/22/2023 访问量:285
PDFBox - 添加签名属性后文档损坏
pdfbox - document getting corrupted after adding signed attributes
问:
我正在尝试使用pdfbox对文档进行数字签名。我使用过滤器作为FILTER_ADOBE_PPKLITE,子过滤器作为SUBFILTER_ETSI_CADES_DETACHED。对于ETSI_CADES_Detached,需要添加签名属性。我正在从 CSC 获取签名哈希和证书> 但是在添加签名属性后,它会使文档损坏。共享屏幕截图以供参考
错误图像
似乎哈希越来越烦恼了。共享代码以供参考。
PDDocument document = PDDocument.load(inputStream);
outFile = File.createTempFile("signedFIle", ".pdf");
Certificate[] certificateChain = retrieveCertificates(requestId, providerId, credentialId, accessToken);//Retrieve certificates from CSC.
setCertificateChain(certificateChain);
// sign
FileOutputStream output = new FileOutputStream(outFile);
IOUtils.copy(inputStream, output);
// create signature dictionary
PDSignature signature = new PDSignature();
// signature.setType(COSName.SIG);
// PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(null);
int accessPermissions = SigUtils.getMDPPermission(document);
if (accessPermissions == 1)
{
throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
}
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
signature.setName("Test Name");
// signature.setLocation("Bucharest, RO");
// signature.setReason("PDFBox Signing");
signature.setSignDate(Calendar.getInstance());
Rectangle2D humanRect = new Rectangle2D.Float(location.getLeft(), location.getBottom(), location.getRight(), location.getTop());
PDRectangle rect = createSignatureRectangle(document, humanRect);
SignatureOptions signatureOptions = new SignatureOptions();
signatureOptions.setVisualSignature(createVisualSignatureTemplate(document, 0, rect, signature));
signatureOptions.setPage(0);
document.addSignature(signature, signatureOptions);
ExternalSigningSupport externalSigning =
document.saveIncrementalForExternalSigning(output);
InputStream content = externalSigning.getContent();
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
X509Certificate cert = (X509Certificate) certificateChain[0];
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
MessageDigest digest = MessageDigest.getInstance("SHA-256");
// Use a buffer to read the input stream in chunks
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = content.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
byte[] hashBytes = digest.digest();
ESSCertIDv2 certid = new ESSCertIDv2(
new AlgorithmIdentifier(new ASN1ObjectIdentifier("*****")),
MessageDigest.getInstance("SHA-256").digest(cert.getEncoded())
);
SigningCertificateV2 sigcert = new SigningCertificateV2(certid);
final DERSet attrValues = new DERSet(sigcert);
Attribute attr = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, attrValues);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
v.add(new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(hashBytes))));
AttributeTable atttributeTable = new AttributeTable(v);
//Create a standard attribute table from the passed in parameters - certhash
CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(atttributeTable);
final byte[] signedHash = signHash(requestId, providerId, accessToken, hashBytes); //Retrieve signed hash from CSC.
ContentSigner contentSigner = new ContentSigner() {
@Override
public byte[] getSignature() {
return signedHash;
}
@Override
public OutputStream getOutputStream() {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.writeBytes(hashBytes);
return byteArrayOutputStream;
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
}
};
org.bouncycastle.asn1.x509.Certificate cert2 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
// RevocationInfoResponse revocationInfoResponse = sealingService.getRevocationInfo(requestId, accessToken, revocationInfoRequest);
sigb.setSignedAttributeGenerator(attrGen);
// sigb.setDirectSignature( true );
gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));
CMSTypedData msg = new CMSProcessableInputStream( inputStream);
CMSSignedData signedData = gen.generate((CMSTypedData)msg, false);
byte[] cmsSignature = signedData.getEncoded();
inputStream.close();
externalSigning.setSignature(cmsSignature);
IOUtils.closeQuietly(signatureOptions);
return new FileInputStream(outFile);
如果我使用子过滤器作为SUBFILTER_ADBE_PKCS7_DETACHED并且不添加addtibutesTable,那么它就可以正常工作。但对于SUBFILTER_ETSI_CADES_DETACHED,需要添加属性。
答:
2赞
mkl
2/22/2023
#1
在你解释的评论中
我们从一些外部提供 (CSC) 获取证书和 signedHash。我们没有 privateKey。因此,在从 CSC 获取 signedHash 后,我们直接覆盖 ContentSignature 的 getSignature 方法并返回该 signedHash。ContentSigner 实际上无法对字节进行签名。
ContentSigner 实际上可以对字节进行签名。您只需从其中调用 CSC 签名即可。
稍微简化代码,可以按如下方式执行此操作:
try (
OutputStream output = new FileOutputStream(outFile);
PDDocument document = PDDocument.load(resource)
) {
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
signature.setName("Test Name");
signature.setSignDate(Calendar.getInstance());
SignatureOptions signatureOptions = new SignatureOptions();
signatureOptions.setPage(0);
document.addSignature(signature, signatureOptions);
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);
// retrieve signer certificate and its chain
Certificate[] certificateChain = retrieveCertificates(requestId, providerId, credentialId, accessToken);
X509Certificate cert = (X509Certificate) certificateChain[0];
// build signed attribute table generator and SignerInfo generator builder
ESSCertIDv2 certid = new ESSCertIDv2(
new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256),
MessageDigest.getInstance("SHA-256").digest(cert.getEncoded())
);
SigningCertificateV2 sigcert = new SigningCertificateV2(certid);
Attribute attr = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(sigcert));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
AttributeTable atttributeTable = new AttributeTable(v);
CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(atttributeTable);
org.bouncycastle.asn1.x509.Certificate cert2 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
sigb.setSignedAttributeGenerator(attrGen);
// create ContentSigner that signs by calling the CSC endpoint
ContentSigner contentSigner = new ContentSigner() {
private MessageDigest digest = MessageDigest.getInstance("SHA-256");
private OutputStream stream = OutputStreamFactory.createStream(digest);
@Override
public byte[] getSignature() {
try {
byte[] hash = digest.digest();
byte[] signedHash = signHash(requestId, providerId, accessToken, hash);
return signedHash;
} catch (Exception e) {
throw new RuntimeException("Exception while signing", e);
}
}
@Override
public OutputStream getOutputStream() {
return stream;
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
}
};
// create the SignedData generator and execute
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));
CMSTypedData msg = new CMSTypedDataInputStream(externalSigning.getContent());
CMSSignedData signedData = gen.generate(msg, false);
byte[] cmsSignature = signedData.getEncoded();
externalSigning.setSignature(cmsSignature);
}
(RemoteSigning 测试方法 testSignLikeSkdjksDfkslImproved
)
评论
0赞
Tanmay Sharma
11/1/2023
当我将algorithmIdentifier从1.2.840.113549.1.1.11更改为1.2.840.113549.1.1.1时,我收到错误“digest oid is null”。你能告诉我这有什么问题吗?
0赞
mkl
11/1/2023
@Tanmay '当我将 algorithmIdentifier 从 1.2.840.113549.1.1.11 更改为 1.2.840.113549.1.1.1 时,我收到错误“digest oid is null”.' - 该更改意味着说 RSA 而不是 SHA256withRSA,后者是没有显式摘要算法的签名算法。因此,显然,给出该错误的代码要求将摘要算法显式包含在签名算法中。
0赞
Tanmay Sharma
11/1/2023
用于签名的 OID 算法为 1.2.840.113549.1.1.1,用于计算哈希值的算法的 OID 应为 2.16.840.1.101.3.4.2.1。我应该在我的代码中进行哪些更新?目前,我在 acrobat 中遇到文档损坏错误。
0赞
Tanmay Sharma
11/1/2023
这是发送到外部 TSP 的请求,用于计算签名哈希 我在 acrobat 中收到此请求的文档相关错误{ "credentialID": "{{cscKeyID}}", "SAD": "{{sad}}", "hash": [ "ODEyYjk2OTA5YTdiOTgwZDYxMjMwOWJlMWVhNWMzNTcwMmZjNTJhOTg5MDg3YzcyODc3MTY3NzhhMDY3NGY2ZA==" ], "hashAlgo": "2.16.840.1.101.3.4.2.1", "signAlgo": "1.2.840.113549.1.1.1", "signAlgoParams": "SHA256withRSA" }
0赞
Tanmay Sharma
11/1/2023
上面的代码只有在发送到外部 TSP 以计算签名哈希的以下请求下才能正常工作{ "credentialID": "{{cscKeyID}}", "SAD": "{{sad}}", "hash": [ "ODEyYjk2OTA5YTdiOTgwZDYxMjMwOWJlMWVhNWMzNTcwMmZjNTJhOTg5MDg3YzcyODc3MTY3NzhhMDY3NGY2ZA==" ], "signAlgo": "1.2.840.113549.1.1.11" }
评论
attrGen
hashBytes
ContentSigner nonSigner
ContentSigner