Я собираюсь реализовать внешнее и позднее подписание с помощью PDFBox 3.0.x, но выходной подписанный PDF-файл вызывает сообщение «Подпись недействительна». Пожалуйста, помогите мне решить проблему. Большое спасибо. Ниже приведен мой код:
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);
Зависит от приведенного выше класса CreateSignature2. Пожалуйста, помогите мне и посоветуйте, как реализовать внешний и поздний знак с помощью PDFBox? Спасибо.
Я собираюсь реализовать внешнее и позднее подписание с помощью PDFBox 3.0.x, но выходной подписанный PDF-файл вызывает сообщение «Подпись недействительна». Пожалуйста, помогите мне решить проблему. Большое спасибо. Ниже приведен мой код: [code]public class CreateSignature2 {
final DataSigner signer; private Certificate cert; private Certificate[] certificateChain;
public CreateSignature2(DataSigner signer) { this.signer = signer;
/** * 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);
// 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; }
@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 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);
// 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);
/** * 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);
// 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);
/** * 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")); } } [/code]
И следующий вызов должен вызвать указанный выше класс: [code] File inputFile = new File(inPdfPath1); File outputFile = new File(inPdfPath2);
CreateSignature2 sig2 = new CreateSignature2(externalSignService);
// 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); [/code] Зависит от приведенного выше класса CreateSignature2. Пожалуйста, помогите мне и посоветуйте, как реализовать внешний и поздний знак с помощью PDFBox? Спасибо.