Insert a SignedHash into PDF for external signing process - WorkingSample

Fabrizio Barone :

Following the eBook section 4.3.3 "Digital Signature for PDF document" I'm trying to create a working example where :

  • the client has a PDF to sign and only a public Cert
  • the External HW (with private cert) takes an HASH and returns a SIGNED HASH

I tried to do it, but the signature inside the PDF shows me that the file was modified after the signing process.

The following code takes the original PDF and a public certificate and create a temporary pdf with empty sign and return an HASH

This hash is sent externally to another remote application (where there is a corrispondent Private cert) and returns the signed hash, I read the signed hash and added it into the temporary pdf.

Full Working Code updated:

package com.Marloo;


import org.apache.commons.codec.Charsets;
import org.bouncycastle.util.encoders.Base64;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;

import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;

public class Test {

    public static final String CERT = "src/main/resources/certificate.pem";
    public static final String SRC = "src/main/resources/tmp.pdf";
    public static final String DEST = "src/main/resources/signed.pdf";

    public static void main(String args[]) throws IOException {
        getHash(SRC, CERT);
    }



    public static void getHash(String doc, String cert) throws IOException {

        try {

            File initialFile = new File(cert);
            InputStream is = new FileInputStream(initialFile);

            // We get the self-signed certificate from the client
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            Certificate[] chain = new Certificate[1];
            chain[0] = factory.generateCertificate(is);

            // we create a reader and a stamper
            PdfReader reader = new PdfReader(doc);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0');

            // we create the signature appearance
            PdfSignatureAppearance sap = stamper.getSignatureAppearance();
            sap.setReason("TEST REASON");
            sap.setLocation("TEST LOCATION");
            //sap.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig"); //visible
            sap.setVisibleSignature(new Rectangle(36, 748, 36, 748), 1, "sig"); //invisible
            sap.setCertificate(chain[0]);

            // we create the signature infrastructure
            PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
            dic.setReason(sap.getReason());
            dic.setLocation(sap.getLocation());
            dic.setContact(sap.getContact());
            dic.setDate(new PdfDate(sap.getSignDate()));
            sap.setCryptoDictionary(dic);
            HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
            exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
            sap.preClose(exc);
            ExternalDigest externalDigest = new ExternalDigest() {
                public MessageDigest getMessageDigest(String hashAlgorithm)
                        throws GeneralSecurityException {
                    return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
                }
            };
            PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
            InputStream data = sap.getRangeStream();
            byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));


            // we get OCSP and CRL for the cert
            OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
            OcspClient ocspClient = new OcspClientBouncyCastle(ocspVerifier);
            byte[] ocsp = null;
            if (chain.length >= 2 && ocspClient != null) {
                ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
            }

        byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);
        InputStream sh_is = new ByteArrayInputStream(sh);
        byte[] signedAttributesHash = DigestAlgorithms.digest(sh_is, externalDigest.getMessageDigest("SHA256"));


        System.out.println("----------------------------------------------");
        System.out.println("Hash to be sign:");
        System.out.println( new String(Base64.encode(signedAttributesHash), Charsets.UTF_8));
            System.out.println("----------------------------------------------");
            System.out.println("Insert b64 signed hash [ENTER]");
            System.out.println("----------------------------------------------");

            Scanner in = new Scanner(System.in);
            String signedHashB64 = in.nextLine();
            System.out.println( signedHashB64);

            ByteArrayOutputStream os = baos;

            byte[] signedHash = org.apache.commons.codec.binary.Base64.decodeBase64(signedHashB64.getBytes());

            // we complete the PDF signing process
            sgn.setExternalDigest(signedHash, null, "RSA");
            Collection<byte[]> crlBytes = null;
            TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle("http://timestamp.gdca.com.cn/tsa", null, null);

            byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
            byte[] paddedSig = new byte[8192];
            System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
            PdfDictionary dic2 = new PdfDictionary();
            dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));

            try {
                sap.close(dic2);
            } catch (DocumentException e) {
                throw new IOException(e);
            }

            FileOutputStream fos = new FileOutputStream(new File(DEST));
            os.writeTo(fos);

            System.out.println("pdfsig " + System.getProperty("user.dir") + "/" + DEST);
            System.out.println("------------------End Of Life --------------------------");

            System.exit(0);


        } catch (GeneralSecurityException e) {
            throw new IOException(e);
        } catch (DocumentException e) {
            throw new IOException(e);
        }

    }


}

Here some screenshot

Some hint: in this incomplete post the author says:

"After much debugging, we finally found the problem.

For some mysterious reason, the method that generates the hash of the document, was executed twice, invalidating the first hash (which we use to send to the service).

After a refactoring work of the code, the original code worked correctly.

Very thanks to all people that help me, especially mkl."

but no futher information was provided, also the Time write on the stamper and the time from TSA is different on purpose. I think this wouldn't be the problem.

Some hints ?

Thanks

Update 1

(the previus code was updated)

the external services doesn't accept in input the whole Sign structure but only the 32byte hash

screenshot2

now the sh var is never used !

i take the hash byte[], send to it but again Adobe Reader say that the file was modified.

Maybe can i try with the "invisible signature" method. Or the "visible stamper" didn't make difference in the signature validation process ?

Or Maybe i need to recreate a ANS.1 structure somehow with the signed byte and then sign the doc ?

Maybe the time must be the same between the tsa and the sign ?

Screenshot3

Any kind of help would be appreciates.

Thanks

Update 2 - WORKING SOLUTION !!!

Really really really thanks to mkl for the answer !

the working fix was that we need to generate the hash of the signed/authenticated attributes inside the PKCS#7 package !!! See the original code at the signedAttributesHash variable signed pdf image

mkl :

Your current code

Your current code signs the entirely wrong hash.

You sign hash which is calculated as

InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));

I.e. you directly sign the hash of the signed ranges of the document. This is wrong because you are building a PKCS#7 signature container with signed attributes, i.e. the hash of the signed ranges of the document must be the value of one of those signed attributes and you have to sign the hash of the signed attributes!

Your former code

Your former code signed the wrong bytes, too, but was nearer to the correct bytes.

You used to sign last32 which was calculated as

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, null, MakeSignature.CryptoStandard.CMS);
byte[] last32 = Arrays.copyOfRange(sh, sh.length - 32, sh.length);

I.e. you correctly generated the signed attribute bytes (aka authenticated attribute bytes) with hash included as attribute value, but then you simply took the last 32 bytes thereof. This is wrong, you must sign the hash of the signed attribute bytes, not their final 32 bytes.

What should work

You should sign signedAttributesHash, the hash of the signed attribute bytes, i.e.

byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocsp, null, MakeSignature.CryptoStandard.CMS);
byte[] signedAttributesHash = DigestAlgorithms.digest(new ByteArrayInputStream(sh), externalDigest.getMessageDigest("SHA256"));

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=157650&siteId=1