Search code examples
javapdfboxdigital-signaturebouncycastle

I have a pre-computed hash of a pdf document, how to generate digital signature using the hash and stamping it on the document using pdfbox?


I'm pretty new to the concept of digital signatures, signing etc. Currently in a pickle, my problem description is as follows:

I have 2 different servers. Server A : The pdf document is uploaded, a hash for this document is created using SHA-256 Algorithm with the MessageDigest interface. The hash is passed on to server B for generating the digital signature or signed hash. This signature from server B is then stamped on to the pdf document using pdfBox.

more details => The uploaded document is passed to pdfbox code (on server A) to attach a PDSignature to the document, the SignatureInterface is overwritten and the contents obtained as parameter from the sign() method (overriden method) is hashed using SHA-256, passed to server B to generate digital signature and then returned as byte[] in the sign() method. Pdfbox then goes ahead and stamps this signature to the document.

Server B : generates the digital signature using SHA256WithRSA algorithm with BouncyCastleFipsProvider.

I was expecting the signature to be certified as valid in Adobe. But, On opening the resulting document from Adobe, I get the following error "The document is altered or modified"

Is my approach wrong? is there a different approach that could solve my problem ? The required flow is => server A will generate a hash, server B will then generate the signature and server A will stamp this on the pdf document.

An update Just to add, I tried by simply passing the content bytes from pdfBox (without hashing) to server B and stamped the resulting signature on the pdf document, the signature is valid. But however the requirement strictly wants only the document hash to be passed from server A to B and not the contents.


Solution

  • According to your linked sources, your current hash signing on server B looks like this:

    /* Start : This is code snippet of the digital signature logic residing on server B */
    Security.addProvider(new BouncyCastleFipsProvider());
    X509Certificate cert = getCertificate();
    Certificate[] certificateChain = new Certificate[1];
    certificateChain[0] = cert;
    Store certs = new JcaCertStore(Arrays.asList(certificateChain));
    CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
    ContentSigner sha256Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(getPrivateKey());
    gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
        new JcaDigestCalculatorProviderBuilder().build()).build(sha256Signer, cert));
    gen.addCertificates(certs);
    CMSTypedData cmsData = new CMSProcessableByteArray(hash);
    CMSSignedData cms = gen.generate(cmsData, false);
    byte[] signedHash = cms.getEncoded(); 
    /* End : This is code snippet of the digital signature logic residing on server B */
    

    But this considers your hash to be the data to sign, not a pre-calculated hash thereof, and calculates the digest of it internally. Thus, the resulting signature container does not sign your PDF but your hash bytes.

    To make this code not calculate the digest itself but instead use your hash as pre-calculated digest, you have to replace the DigestCalculatorProvider returned by new JcaDigestCalculatorProviderBuilder().build() with a custom implementation that provides DigestCalculator objects that constantly return hash.

    For the normal BouncyCastle library such an implementation might look like this:

    public class PrecomputedDigestCalculatorProvider implements DigestCalculatorProvider {
        private final byte[] digest;
    
        public PrecomputedDigestCalculatorProvider(byte[] digest) {
            this.digest = digest;
        }
    
        @Override
        public DigestCalculator get(final AlgorithmIdentifier digestAlgorithmIdentifier) throws OperatorCreationException {
    
            return new DigestCalculator() {
                @Override
                public OutputStream getOutputStream() {
                    return new ByteArrayOutputStream();
                }
    
                @Override
                public byte[] getDigest() {
                    return digest;
                }
    
                @Override
                public AlgorithmIdentifier getAlgorithmIdentifier() {
                    return digestAlgorithmIdentifier;
                }
            };
        }
    }
    

    You say you're using the FIPS variant of BouncyCastle. Thus, you may have to adapt the code somewhat.