External Signature with PDFBox 2.0.12

Viewed 926

Signing modified issue.

Iam passing the doc hash and signing hash using a DSC token(External Signing).

iam getting error like:"The document has been altered or corrupted since it was signed" Getting the Hash of Document:-

public String genrateDigitalCertificateSign() {
        try {
            src = new FileInputStream(inputFilePath);
            OutputStream dest = new FileOutputStream(new File(RESULT_FOLDER, "Test.pdf"));
            pdDocument = PDDocument.load(src);

            PDSignature pds = null;
            String hashdocument = null;
            File imgFile = new File(inputImgPath);
            PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
            if (acroForm == null) {
                pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
            }
            acroForm.setSignaturesExist(true);
            acroForm.setAppendOnly(true);
            acroForm.getCOSObject().setDirect(true);

            pds = new PDSignature();
            pds.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            pds.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
            pds.setSignDate(Calendar.getInstance());

            PDPage pdPage = pdDocument.getPage(0);
            PDImageXObject pdImage = PDImageXObject.createFromFileByContent(imgFile, pdDocument);

            //visible signature rectangle
            rectangle = new PDRectangle(200.00, 200.00,150.00,50.00);
            
            List<PDField> acroFormFields = acroForm.getFields();
            PDSignatureField signatureField = new PDSignatureField(acroForm);
            acroForm.setSignaturesExist(true);
            acroForm.setAppendOnly(true);
            acroForm.getCOSObject().setDirect(true);
            signatureField.setValue(pds);
            acroFormFields.add(signatureField);
            pdDocument.addSignature(pds);
            
            //creating visible stamp
            createVisualSignatureTemplate(pdDocument, signatureField, pdPage, rectangle, pdImage, signDisplayInfo);

            externalSigning = pdDocument.saveIncrementalForExternalSigning(dest);
            InputStream dataToSign = externalSigning.getContent();
            hashdocument = DigestUtils.sha256Hex(dataToSign); // hash is generated
            return hashdocument;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

External signing Code:-

public byte[] sign(byte[] hash)
            throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        PrivateKey privKey = pk;
        try {
            List<Certificate> certList = new ArrayList<>();
            certList.addAll(Arrays.asList(chain));
            Store certs = new JcaCertStore(certList);
            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate
                    .getInstance(chain[0].getEncoded());
            ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privKey);
            gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())
                            .build(sha1Signer, new X509CertificateHolder(cert)));
            gen.addCertificates(certs);
            CMSProcessableInputStream msg = new CMSProcessableInputStream(new ByteArrayInputStream(hash));
            CMSSignedData signedData = gen.generate(msg, false);
            return signedData.getEncoded();
        } catch (GeneralSecurityException e) {
            throw new IOException(e);
        } catch (CMSException e) {
            throw new IOException(e);
        } catch (OperatorCreationException e) {
            throw new IOException(e);
        }
    }

Signature appending code:-

public void signedPDF(byte[] hash)
            throws InvalidKeyException, NoSuchAlgorithmException, SignatureException, IOException {
        byte[] signedHash = sign(hash);
        externalSigning.setSignature(signedHash);
        IOUtils.closeQuietly(src);
        pdDocument.close();
    } 

After this signature is appending,but while opening the signed PDF getting the error like as shown in the below image.

PDF file Link: https://drive.google.com/file/d/1qRT2CVgET8Ds1fu0b5psii3j8ytPKaLH/view?usp=sharing

[EDITED]

public byte[] genrateDigitalCertificateSign() {
        try {
            src = new FileInputStream(inputFilePath);
            OutputStream dest = new FileOutputStream(new File(RESULT_FOLDER, "Test.pdf"));
            pdDocument = PDDocument.load(src);

            PDSignature pds = null;
            String hashdocument = null;
            File imgFile = new File(inputImgPath);
            PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
            if (acroForm == null) {
                pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
            }
            acroForm.setSignaturesExist(true);
            acroForm.setAppendOnly(true);
            acroForm.getCOSObject().setDirect(true);

            pds = new PDSignature();
            pds.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            pds.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
            pds.setSignDate(Calendar.getInstance());

            PDPage pdPage = pdDocument.getPage(0);
            PDImageXObject pdImage = PDImageXObject.createFromFileByContent(imgFile, pdDocument);

            //visible signature rectangle
            rectangle = new PDRectangle(200.00, 200.00,150.00,50.00);
            
            List<PDField> acroFormFields = acroForm.getFields();
            PDSignatureField signatureField = new PDSignatureField(acroForm);
            acroForm.setSignaturesExist(true);
            acroForm.setAppendOnly(true);
            acroForm.getCOSObject().setDirect(true);
            signatureField.setValue(pds);
            acroFormFields.add(signatureField);
            pdDocument.addSignature(pds);
            
            //creating visible stamp
            createVisualSignatureTemplate(pdDocument, signatureField, pdPage, rectangle, pdImage, signDisplayInfo);

            externalSigning = pdDocument.saveIncrementalForExternalSigning(dest);
            InputStream dataToSign = externalSigning.getContent();
            hashdocument = DigestUtils.sha256(dataToSign); // hash is generated
            return hashdocument;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

Recently signed PDF file link:- https://drive.google.com/file/d/1mZ8Wqppx3EylI1aLYB9Fl8NzibBXZxzR/view?usp=sharing

[Edited 2] this is the flow i tried after your suggestions.
In this what is the 1st issue your pointing out? Iam unable to figure out, please help.

externalSigning = pdDocument.saveIncrementalForExternalSigning(dest);
            InputStream dataToSign = externalSigning.getContent();
            hashdocument = DigestUtils.sha256(dataToSign); // hash is generated 
            byte[] signedhash = sign(hashdocument);
            externalSigning.setSignature(signedhash);
            pdDocument.close();
1 Answers

Ok, there are two issues in your code:

Double Hashing

Your code creates a signature that contains the hash of the hash of the document data where simply the hash of the document data is expected.

Your code mostly is borrowed from the PDFBox examples. In case of external signing the content to sign is retrieved and forwarded to the sign method:

byte[] cmsSignature = sign(externalSigning.getContent());

(from CreateSignature which extends CreateSignatureBase)

In your code, though, you first hash the content to sign and forward that hash to the sign method:

public String genrateDigitalCertificateSign() {
    ...
    InputStream dataToSign = externalSigning.getContent();
    hashdocument = DigestUtils.sha256Hex(dataToSign); // hash is generated
    return hashdocument;
    ...
}

byte[] signedHash = sign(hash);

As fas as creation of the CMS signature container is concerned, your sign method uses the same code as the PDFBox example.

Thus, your code hashes the document data once too often. To fix this, you have to

  • either drop the hashing step and forward the document data to sign
  • or change the sign method to not hash its input again.

I assume your intention is to calculate and forward the hash to a separate signing service, so your choice would be the latter.

Hex Encoding the Hash

Your hash generating method genrateDigitalCertificateSign returns the hash value not as the actual byte[] but hex encodes it and returns that hex string:

hashdocument = DigestUtils.sha256Hex(dataToSign); // hash is generated
return hashdocument;

Your other methods, though, expect to get and operate on the actual byte[].

To fix this, you have to

  • either not hex encode to start with, i.e. use DigestUtils.sha256 instead of DigestUtils.sha256Hex and return a byte[] instead of a String from genrateDigitalCertificateSign,
  • or hex decode the hash value before feeding it to the sign method in your signedPDF method.

I assume your intention is to transport the hash in string form, so your choice would be the latter.

Related