Die externe und verspätete Signatur von PDFBox 3.0.x verursacht „Signatur ist ungültig“Java

Java-Forum
Anonymous
 Die externe und verspätete Signatur von PDFBox 3.0.x verursacht „Signatur ist ungültig“

Post by Anonymous »

Ich werde das externe und späte Signieren mit PDFBox 3.0.x implementieren, aber das ausgegebene signierte PDF verursacht „Signatur ist ungültig“. Das Folgende ist mein Code:

Code: Select all

public class CreateSignature2 {

final DataSigner signer;
private Certificate cert;
private Certificate[] certificateChain;

public CreateSignature2(DataSigner signer) {
this.signer = signer;

try {
this.cert = signer.getSignerCert();
this.certificateChain = signer.getSignerCertChain().toArray(new Certificate[0]);
} catch (ApiException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

public void signDocument(File inFile, Certificate cert, Certificate[] certChain) throws
Exception,
IOException,
CertificateEncodingException,
NoSuchAlgorithmException,
OperatorCreationException,
CMSException {
this.cert = cert;
setCertificateChain(certChain);

String name = inFile.getName();
String substring = name.substring(0, name.lastIndexOf('.'));
File outFile = new File(inFile.getParent(), substring + "_signed_pdfbox.pdf");

// Use late signing approach
LateSigningSession session = prepareSigning(inFile, outFile);
byte[] dataToSign = session.getDataToSign();

// Sign the data externally
List dataList = Arrays.asList(dataToSign);
List  signatures = signer.sign(dataList);
byte[] signature = signatures.get(0);

// Complete the signing
completeSigning(session, signature);
}

private void setCertificateChain(final Certificate[] certificateChain) {
this.certificateChain = certificateChain;
}

/**
* Prepares the document for signing and returns the data that needs to be signed
*/
public LateSigningSession prepareSigning(File inFile, File outFile) throws
IOException,
NoSuchAlgorithmException,
CertificateEncodingException,
OperatorCreationException,
CMSException {
FileOutputStream output = new FileOutputStream(outFile);
PDDocument document = Loader.loadPDF(inFile);

try {
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_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);

// Build CMS structure
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 a ContentSigner that captures the data
HashCapturingContentSigner contentSigner = new HashCapturingContentSigner();

CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));

// Process the document content to populate the ContentSigner's OutputStream
CMSTypedData msg = new CMSProcessableInputStream(externalSigning.getContent());

// This will write the data to be signed to the ContentSigner's OutputStream
// We catch the expected exception when getSignature() is called prematurely
try {
CMSSignedData signedData = gen.generate(msg, false);
} catch (RuntimeException e) {
if (e.getMessage() != null &&  e.getMessage().contains("Signature not set")) {
// Expected - we're capturing the data for late signing
} else {
throw e;
}
}

// Get the captured data from the ContentSigner
byte[] dataToSign = contentSigner.getCapturedData();

return new LateSigningSession(document, output, externalSigning, contentSigner,
gen, msg, dataToSign);

} catch (Exception e) {
// Clean up resources if anything fails
document.close();
output.close();
throw e;
}
}

/**
* Completes the signing process with the externally generated signature
*/
public void completeSigning(LateSigningSession session, byte[] externalSignature) throws
IOException,
CMSException {
try {
// Set the external signature on the ContentSigner
session.getContentSigner().setSignature(externalSignature);

// Now generate the final CMS signature
CMSSignedData signedData = session.getGenerator().generate(session.getMessage(), false);
byte[] cmsSignature = signedData.getEncoded();

// Set the signature on the document
session.getExternalSigningSupport().setSignature(cmsSignature);
} finally {
// Always close resources
session.close();
}
}

/**
* ContentSigner that captures data for late signing
*/
private static class HashCapturingContentSigner implements ContentSigner {
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private byte[] signature;
private boolean signatureSet = false;

@Override
public byte[] getSignature() {
if (!signatureSet) {
throw new RuntimeException("Signature not set.  Use setSignature() for late signing.");
}
return signature;
}

public void setSignature(byte[] signature) {
this.signature = signature;
this.signatureSet = true;
}

@Override
public OutputStream getOutputStream() {
return outputStream;
}

@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
}

public byte[] getCapturedData() {
return outputStream.toByteArray();
}
}

/**
* Session class to hold signing state between preparation and completion
*/
public static class LateSigningSession implements AutoCloseable {
private final PDDocument document;
private final FileOutputStream output;
private final ExternalSigningSupport externalSigningSupport;
private final HashCapturingContentSigner contentSigner;
private final CMSSignedDataGenerator generator;
private final CMSTypedData message;
private final byte[] dataToSign;

public LateSigningSession(PDDocument document, FileOutputStream output,
ExternalSigningSupport externalSigningSupport,
HashCapturingContentSigner contentSigner,
CMSSignedDataGenerator generator,
CMSTypedData message,
byte[] dataToSign) {
this.document = document;
this.output = output;
this.externalSigningSupport = externalSigningSupport;
this.contentSigner = contentSigner;
this.generator = generator;
this.message = message;
this.dataToSign = dataToSign;
}

public byte[] getDataToSign() {
return dataToSign;
}

public ExternalSigningSupport getExternalSigningSupport() {
return externalSigningSupport;
}

public HashCapturingContentSigner getContentSigner() {
return contentSigner;
}

public CMSSignedDataGenerator getGenerator() {
return generator;
}

public CMSTypedData getMessage() {
return message;
}

@Override
public void close() throws IOException {
if (document != null) {
document.close();
}
if (output != null) {
output.close();
}
}
}

/**
* Alternative approach that recreates the CMS structure for completion
* This is more robust if the first approach has issues
*/
public LateSigningSession prepareSigningAlternative(File inFile, File outFile) throws
IOException,
NoSuchAlgorithmException,
CertificateEncodingException {
FileOutputStream output = new FileOutputStream(outFile);
PDDocument document = Loader.loadPDF(inFile);

try {
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_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);

// Read the document content that will be processed
InputStream contentStream = externalSigning.getContent();
ByteArrayOutputStream contentBaos = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = contentStream.read(buffer)) != -1) {
contentBaos.write(buffer, 0, bytesRead);
}
byte[] documentContent = contentBaos.toByteArray();

return new LateSigningSession(document, output,  externalSigning, null,
null, null, documentContent);

} catch (Exception e) {
document.close();
output.close();
throw e;
}
}

/**
* Alternative completion that recreates the CMS structure
*/
public void completeSigningAlternative(LateSigningSession session, byte[] externalSignature) throws
IOException,
NoSuchAlgorithmException,
CertificateEncodingException,
OperatorCreationException,
CMSException {
try {
// Recreate the CMS structure with the external signature
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())
);

// Create ContentSigner with the pre-computed signature
ContentSigner contentSigner = new PrecomputedContentSigner(externalSignature);

JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().build()
);
sigb.setSignedAttributeGenerator(attrGen);

CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));

// Use the captured document content
CMSTypedData msg = new CMSProcessableByteArray(session.getDataToSign());
CMSSignedData signedData = gen.generate(msg, false);

byte[] cmsSignature = signedData.getEncoded();
session.getExternalSigningSupport().setSignature(cmsSignature);
} finally {
session.close();
}
}

/**
* ContentSigner that uses a pre-computed signature
*/
private static class PrecomputedContentSigner implements ContentSigner {
private final byte[] signature;
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

public PrecomputedContentSigner(byte[] signature) {
this.signature = signature;
}

@Override
public byte[] getSignature() {
return signature;
}

@Override
public OutputStream getOutputStream() {
return outputStream;
}

@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
}
}
}
Und das Folgende ist, die obige Klasse aufzurufen:

Code: Select all

    File inputFile = new File(inPdfPath1);
File outputFile = new File(inPdfPath2);

CreateSignature2 sig2 = new CreateSignature2(externalSignService);

CreateSignature2.LateSigningSession session = sig2.prepareSigning(inputFile, outputFile);
byte[] dataToSign = session.getDataToSign();

System.out.println(">>>>SIZE: " + dataToSign.length);

// Send to external signing service
List dataList = Arrays.asList(dataToSign);
List signatures = externalSignService.sign(dataList);
byte[] signature = signatures.get(0);

// Complete the signing stage
sig2.completeSigning(session, signature);
Hängt von der oben genannten CreateSignature2-Klasse ab. Bitte helfen Sie mir und geben Sie mir einen Rat, wie ich das externe und späte Signieren mit PDFBox implementieren kann. Danke.

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post