title: Insert a SignedHash into PDF for external signing process - WorkingSample
body: <p>Following the eBook section 4.3.3  "<a href="https://jira.nuxeo.com/secure/attachment/49931/digitalsignatures20130304.pdf" rel="nofollow noreferrer">Digital Signature for PDF document</a>"
  I'm trying to create a working example where : </p>
  
  <ul>
  <li>the client has a PDF to sign and only a public Cert </li>
  <li>the External HW (with private cert) takes an HASH and returns a SIGNED HASH </li>
  </ul>
  
  <p>I tried to do it, but the signature inside the PDF shows me that the file was modified after the signing process. </p>
  
  <p>The following code takes the original PDF and a public certificate and create a temporary pdf with empty sign and return an HASH </p>
  
  <p>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.</p>
  
  <p>Full Working Code updated: </p>
  
  <pre><code>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&lt;PdfName, Integer&gt; exc = new HashMap&lt;PdfName, Integer&gt;();
              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 &gt;= 2 &amp;&amp; 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&lt;byte[]&gt; 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);
          }
  
      }
  
  
  }
  </code></pre>
  
  <p>Here some <a href="https://i.stack.imgur.com/5t9Uu.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/5t9Uu.png" alt="screenshot"></a></p>
  
  <p>Some hint: 
  in this incomplete <a href="https://stackoverflow.com/questions/28717704/sign-pdf-using-an-external-service-and-itext/55899277#55899277">post</a> the author says: </p>
  
  <blockquote>
    <p>"After much debugging, we finally found the problem.</p>
    
    <p>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).</p>
    
    <p>After a refactoring work of the code, the original code worked
    correctly.</p>
    
    <p>Very thanks to all people that help me, especially mkl."</p>
  </blockquote>
  
  <p>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. </p>
  
  <p>Some hints ? </p>
  
  <p>Thanks </p>
  
  <h2>Update 1</h2>
  
  <p>(the previus code was updated) </p>
  
  <p>the external services doesn't accept in input the whole Sign structure but only the 32byte hash </p>
  
  <p><a href="https://i.stack.imgur.com/j9kxw.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/j9kxw.png" alt="screenshot2"></a></p>
  
  <p>now the sh var is never used ! </p>
  
  <p>i take the hash byte[], send to it but again Adobe Reader say that the file was modified. </p>
  
  <p>Maybe can i try with the "invisible signature" method. Or the "visible stamper" didn't make difference in the signature validation process ? </p>
  
  <p>Or Maybe i need to recreate a ANS.1 structure somehow with the signed byte and then sign the doc ? </p>
  
  <p>Maybe the time must be the same between the tsa and the sign ? </p>
  
  <p><a href="https://i.stack.imgur.com/PzEks.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/PzEks.png" alt="Screenshot3"></a></p>
  
  <p>Any kind of help would be appreciates. </p>
  
  <p>Thanks </p>
  
  <h2>Update 2 - WORKING SOLUTION !!!</h2>
  
  <p>Really really really thanks to mkl for the answer ! </p>
  
  <p>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 <code>signedAttributesHash</code> variable
  <a href="https://i.stack.imgur.com/cACeX.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/cACeX.jpg" alt="signed pdf image"></a></p>