Signing PDF with PDFBox and BouncyCastle

I try to sign a PDF using the PDFBox and it signs, but when I open the document in adobe reader, I get the following message: β€œThe document has been modified or damaged since it was signed”, someone can please help me find the problem.

The vault storage was created using "keytool -genkeypair -storepass 123456 -storetype pkcs12 -alias test -validity 365 -v -keyalg RSA -keystore keystore.p12"

Using pdfbox-1.8.9 and bcpkix-jdk15on-1.52

Here is my code:

import org.apache.pdfbox.exceptions.COSVisitorException; import org.apache.pdfbox.exceptions.SignatureException; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.util.Store; import java.io.*; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.PrivateKey; import java.security.cert.Certificate; import java.util.Calendar; import java.util.Collections; import java.util.Enumeration; public class CreateSignature implements SignatureInterface { private static PrivateKey privateKey; private static Certificate certificate; boolean signPdf(File pdfFile, File signedPdfFile) { try ( FileInputStream fis1 = new FileInputStream(pdfFile); FileInputStream fis = new FileInputStream(pdfFile); FileOutputStream fos = new FileOutputStream(signedPdfFile); PDDocument doc = PDDocument.load(pdfFile)) { int readCount; byte[] buffer = new byte[8 * 1024]; while ((readCount = fis1.read(buffer)) != -1) { fos.write(buffer, 0, readCount); } PDSignature signature = new PDSignature(); signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); signature.setName("NAME"); signature.setLocation("LOCATION"); signature.setReason("REASON"); signature.setSignDate(Calendar.getInstance()); doc.addSignature(signature, this); doc.saveIncremental(fis, fos); return true; } catch (Exception e) { e.printStackTrace(); return false; } } @Override public byte[] sign(InputStream is) throws SignatureException, IOException { try { BouncyCastleProvider BC = new BouncyCastleProvider(); Store certStore = new JcaCertStore(Collections.singletonList(certificate)); CMSTypedDataInputStream input = new CMSTypedDataInputStream(is); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); ContentSigner sha512Signer = new JcaContentSignerBuilder("SHA256WithRSA").setProvider(BC).build(privateKey); gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder( new JcaDigestCalculatorProviderBuilder().setProvider(BC).build()).build(sha512Signer, new X509CertificateHolder(certificate.getEncoded()) )); gen.addCertificates(certStore); CMSSignedData signedData = gen.generate(input, false); return signedData.getEncoded(); } catch (Exception e) { e.printStackTrace(); return null; } } public static void main(String[] args) throws IOException, GeneralSecurityException, SignatureException, COSVisitorException { char[] password = "123456".toCharArray(); KeyStore keystore = KeyStore.getInstance("PKCS12"); keystore.load(new FileInputStream("/home/user/Desktop/keystore.p12"), password); Enumeration<String> aliases = keystore.aliases(); String alias; if (aliases.hasMoreElements()) { alias = aliases.nextElement(); } else { throw new KeyStoreException("Keystore is empty"); } privateKey = (PrivateKey) keystore.getKey(alias, password); Certificate[] certificateChain = keystore.getCertificateChain(alias); certificate = certificateChain[0]; File inFile = new File("/home/user/Desktop/sign.pdf"); File outFile = new File("/home/user/Desktop/sign_signed.pdf"); new CreateSignature().signPdf(inFile, outFile); } } class CMSTypedDataInputStream implements CMSTypedData { InputStream in; public CMSTypedDataInputStream(InputStream is) { in = is; } @Override public ASN1ObjectIdentifier getContentType() { return PKCSObjectIdentifiers.data; } @Override public Object getContent() { return in; } @Override public void write(OutputStream out) throws IOException, CMSException { byte[] buffer = new byte[8 * 1024]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } in.close(); } } 
+5
source share
1 answer

Commit "Document has been modified or damaged"

The error is that you are calling PDDocument.saveIncremental with an InputStream , just covering the original PDF:

 FileInputStream fis1 = new FileInputStream(pdfFile); FileInputStream fis = new FileInputStream(pdfFile); FileOutputStream fos = new FileOutputStream(signedPdfFile); ... doc.saveIncremental(fis, fos); 

But the method expects the InputStream to cover the source file, as well as the changes made to prepare for signing.

Thus, fis should also point to signedPdfFile , and since this file may not exist before, the order of creation of fis and fos must be disabled>

 FileInputStream fis1 = new FileInputStream(pdfFile); FileOutputStream fos = new FileOutputStream(signedPdfFile); FileInputStream fis = new FileInputStream(signedPdfFile); ... doc.saveIncremental(fis, fos); 

Unfortunately, JavaDocs do not indicate this.

Another problem

There is another problem with the generated signature. If you look at the dump of the ASN.1 sample, you will see something starting like this:

  <30 80> 0 NDEF: SEQUENCE { <06 09> 2 9: OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2) : (PKCS #7) <A0 80> 13 NDEF: [0] { <30 80> 15 NDEF: SEQUENCE { <02 01> 17 1: INTEGER 1 <31 0F> 20 15: SET { 

NDEF length NDEF show that an indefinite length method is used to encode these outer layers of the signature container. The use of this method is permitted in basic coding rules (BER), but not in more stringent rules for outstanding encodings (DER). Although BER for external layers is allowed for generic PKCS # 7 / CMS signatures, the PDF specification explicitly requires:

When using PKCS # 7 signatures, the Content value must be a PKCS # 7 binary data object containing DER containing the signature.

(Section 12.8.3.3.1 "PKCS Signatures No. 7 Used in ISO 32000" / "General" in ISO 32000-1 )

Thus, strictly speaking, your signature is even structurally invalid . Typically, this is not detected by the PDF signature verification services, since most of them use the standard PKCS # 7 / CMS libraries or signature container verification methods.

If you want to make sure that your signatures are indeed genuine PDF signatures, you can achieve this by replacing

 return signedData.getEncoded(); 

something like

 ByteArrayOutputStream baos = new ByteArrayOutputStream(); DEROutputStream dos = new DEROutputStream(baos); dos.writeObject(signedData.toASN1Structure()); return baos.toByteArray(); 

Now the entire signature object is DER encoded.

(You can find test creation of signatures with both your original and fixed code with or without improved encoding: SignLikeLoneWolf.java )

+6
source

All Articles