title: External signing PDF with iText
body: <p>First of all, although I've been following StackOverflow for quite some time, it is the first time I posted something, so if I do something wrong or not according to the rules, please feel free to point me in the right direction.</p>
  <p>I'm developing a PDF digital signature application, using iText5, which depends on an external service to provide a signed hash after I prepare the PDF for signing.</p>
  <p>As described in <a href="https://itextpdf.com/en/resources/books/digital-signatures-pdf" rel="nofollow noreferrer">iText documentation</a>, in the first phase I prepared the PDF (in the final implementation, all PDFs may be multi signed, so I use append mode), like so:</p>
  <pre class="lang-cs prettyprint-override"><code>public static byte[] GetBytesToSign(string unsignedPdf, string tempPdf, string signatureFieldName, List&lt;Org.BouncyCastle.X509.X509Certificate&gt; certificateChain) {
          // we create a reader and a stamper
          using (PdfReader reader = new PdfReader(unsignedPdf)) {
              using (FileStream baos = File.OpenWrite(tempPdf)) {
  
                  List&lt;Org.BouncyCastle.X509.X509Certificate&gt; chain = certificateChain;
                  PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
                  sap                   = pdfStamper.SignatureAppearance;
                  sap.Certificate       = certificateChain[0];
                  sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, signatureFieldName);
                  //sap.SetVisibleSignature(signatureFieldName);
                  sap.SignDate          = DateTime.Now;
                  PdfSignature dic      = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);  
                  dic.Date              = new PdfDate(sap.SignDate);
                  dic.Name              = CertificateInfo.GetSubjectFields(chain[0]).GetField(&quot;CN&quot;);
                  sap.CryptoDictionary  = dic;
                  sap.Certificate       = certificateChain[0];
                  sap.Acro6Layers       = true;
                  sap.Reason            = &quot;test&quot;;
                  sap.Location          = &quot;test&quot;;
  
                  IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
                  MakeSignature.SignExternalContainer(sap, external, 8192);
                  signatureContainer = new PdfPKCS7(null, chain, &quot;SHA256&quot;, false);
                  byte[] hash = DigestAlgorithms.Digest(sap.GetRangeStream(), &quot;SHA256&quot;);
                  //byte[] signatureHash = signatureContainer.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
  
                  return hash;
              }
          }
      }
  </code></pre>
  <p>After this step, I send the hash to the external service, which returns a signed hash.</p>
  <p>Inspecting the hash that I send to the service, it seems to be correct, since it covers all the PDF except for the new signature contents.</p>
  <p>I then conclude the signature process using the method below:</p>
  <pre class="lang-cs prettyprint-override"><code>private byte[] Sign(PdfPKCS7 signatureContainer, List&lt;X509Certificate2&gt; chain2, List&lt;Org.BouncyCastle.X509.X509Certificate&gt; chain, byte[] hash, byte[] signedBytes, string tmpPdf, string signedPdf, string signatureFieldName) {
          System.Security.Cryptography.RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = chain2[0].PublicKey.Key as System.Security.Cryptography.RSACryptoServiceProvider;
          bool verify = publicCertifiedRSACryptoServiceProvider.VerifyHash(hash, &quot;SHA256&quot;, signedBytes); //verify if the computed hash is same as signed hash using the cert public key
          Console.WriteLine(&quot;PKey signed computed hash is equal to signed hash: &quot; + verify);
  
          AsnEncodedData asnEncodedData = new AsnEncodedData(signedBytes);
          Console.WriteLine(asnEncodedData.Format(true));
          
          //ITEXT5
          try {
              //Console.WriteLine(&quot;Signed bytes: &quot; + Encoding.UTF8.GetString(signedBytes));
  
              using (PdfReader reader = new PdfReader(tmpPdf)) {
                  using (FileStream outputStream = File.OpenWrite(signedPdf)) {
                  IExternalSignatureContainer external = new Objects.MyExternalSignatureContainer(signedBytes, chain, signatureContainer);
                  MakeSignature.SignDeferred(reader, signatureFieldName, outputStream, external);
                  }
              }
              return new byte[] { };
          }
          catch(Exception ex) {
              File.Delete(tmpPdf);
              Console.WriteLine(&quot;Error signing file: &quot; + ex.Message);
              return new byte[] { };
          }
      }
  </code></pre>
  <p>In the beggining of the Sign method, I verify if the hash sent to the external service, signed with the same certificate, is equal to the external service response, which is true.</p>
  <p>MyExternalSignatureContainer code:</p>
  <pre class="lang-cs prettyprint-override"><code>public class MyExternalSignatureContainer : IExternalSignatureContainer {
          private readonly byte[] signedBytes;
          public List&lt;Org.BouncyCastle.X509.X509Certificate&gt; Chain;
          private PdfPKCS7 sigField;
  
          public MyExternalSignatureContainer(byte[] signedBytes) {
              this.signedBytes = signedBytes;
          }
  
          public MyExternalSignatureContainer(byte[] signedBytes, List&lt;Org.BouncyCastle.X509.X509Certificate&gt; chain, PdfPKCS7 pdfPKCS7) {
              this.signedBytes = signedBytes;
              this.Chain = chain;
              this.sigField = pdfPKCS7;
          }
  
          public byte[] Sign(Stream data) {
              try {
                  sigField.SetExternalDigest(signedBytes, null, &quot;RSA&quot;);
                  return sigField.GetEncodedPKCS7(signedBytes, null, null, null, CryptoStandard.CMS);
              }
              catch (IOException ioe) {
                  throw ioe;
              }
          }
  
          public void ModifySigningDictionary(PdfDictionary signDic) {
          }
      }
  </code></pre>
  <p>The problem is when I open the PDF in Acrobat, it states that the document has been modified or corrupted since the signature was applied.</p>
  <p>(If I open the same PDF in PDF-XChange, it says the PDF wasn't modified).</p>
  <p>What I've tried so far without luck:</p>
  <p>Not being completely sure if the external service uses SHA256, I've already tried changing the digest to SHA1 of the pre-signing, resulting in a &quot;Formatting error&quot; in Acrobat Reader.</p>
  <p>Like stated in another post in StackOverlow regarding the same issue (I'm unable to find the post to link it), a potential problem would be using different streams for the temporary file. I already tried to use the same stream without luck as well.</p>
  <p>Samples of the PDF's:</p>
  <p><a href="https://drive.google.com/file/d/1zfR2_K6cwseCxMsEF5nOww2vmdeiDnkw/view?usp=sharing" rel="nofollow noreferrer">Original file</a></p>
  <p><a href="https://drive.google.com/file/d/1QfD-xsyZdA4e15INTDK6AhdGW6BeVEQW/view?usp=sharing" rel="nofollow noreferrer">Temp File</a></p>
  <p><a href="https://drive.google.com/file/d/1PD0avPqV-Qj6tNWQ_ifGRmGFBeLDadSj/view?usp=sharing" rel="nofollow noreferrer">Signed File</a></p>
  <p><strong>Base64 hash sent to the service:</strong></p>
  <p><code>XYfaS/SisA/tk5hcl035RpBjOczrH9E5rgiAMpqgkjI=</code></p>
  <p><strong>Base64 signed hash sent in response:</strong></p>
  <p><code>CnV3WL7skhMCtZG1r1Qi2oyE9WPO3KP4Ieu/Xm4lec+DAbYbhQxCvjMISsG3sTwYY7Lqi4luD60uceViDH848rS9OkTn8szzAnnX2fSYIwqDpG3qjJAb6NOXEv41hy+XYhSBJWS4ji2mM2ReruwPafxB1aM25L5Jyd0V7WecuNFUevUrvd85Y2KBkyBw9zCA8NDAQPPY0UT4GkXZi3Z35+Sf/s2o8zxCOlBDaIJyMvJ9De79nw4jC5L9NesHpFxx3mX1g1N33GHjUNdETgFMhnd8RDUlGLW6bsAyv78gvwE6aXF6COObap/VtlLvMOME68MzLr6izKte6uA35Zwj9Q== </code></p>
  <hr />
  <p><strong>Update after <a href="https://stackoverflow.com/a/54575350/6248239">mkl's answer</a>:</strong></p>
  <p>According to the answer, I changed the code sign the document in only one phase and ended up with the following methods:</p>
  <pre class="lang-cs prettyprint-override"><code>using (PdfReader reader = new PdfReader(fileLocation)) {
      using (FileStream baos = File.OpenWrite(tmpFile)) {
  
          List&lt;Org.BouncyCastle.X509.X509Certificate&gt; chain = Chain;
          PdfStamper pdfStamper = PdfStamper.CreateSignature(reader, baos, '\0', null, true);
          PdfSignatureAppearance sap = pdfStamper.SignatureAppearance;
          sap.Certificate = Chain[0];
          sap.SetVisibleSignature(new iTextSharp.text.Rectangle(36, 720, 160, 780), 1, signatureFieldName);
          //sap.SetVisibleSignature(signatureFieldName);
          sap.SignDate = DateTime.Now;
          PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
          dic.Date = new PdfDate(sap.SignDate);
          dic.Name = CertificateInfo.GetSubjectFields(chain[0]).GetField(&quot;CN&quot;);
          sap.CryptoDictionary = dic;
          sap.Certificate = Chain[0];
          sap.Acro6Layers = true;
          //sap.CertificationLevel = PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS;
          sap.Reason = &quot;test&quot;;
          sap.Location = &quot;test&quot;;
  
          IExternalSignature signature = new Objects.RemoteSignature(client, signatureRequest);
          MakeSignature.SignDetached(sap, signature, Chain, null, null, null, 8192, CryptoStandard.CMS);
  
      }
  }
  </code></pre>
  <p><strong>And the IExternalSignature implementation:</strong></p>
  <pre class="lang-cs prettyprint-override"><code>public virtual byte[] Sign(byte[] message) {
      IDigest messageDigest = DigestUtilities.GetDigest(GetHashAlgorithm());
      byte[] messageHash = DigestAlgorithms.Digest(messageDigest, message);
      //
      // Request signature for hash value messageHash
      // and return signature bytes
      //
      signatureRequest.Hash = messageHash;
      SignatureService.SignatureResponse signatureResponse = client.Signature(signatureRequest);
  
      if (signatureResponse.Status.Code == &quot;00&quot;) {
           return signatureResponse.DocumentSignature;
      }
      else {
          throw new Exception(&quot;Error signing file: &quot; + signatureResponse.Status.Message);
      }
  }
  </code></pre>
  <p>The <em>signatureResponse.DocumentSignature</em> represents the signed bytes returned by the service.</p>
  <p><strong>In the result PDF now I'm a getting a BER decoding error.</strong></p>
  <blockquote>
  <p>Analyzing your example PDF you appear to declare the wrong certificate as signer certificate</p>
  </blockquote>
  <p>Although I know the current certificate isn't valid, it is provided by the service and in the previous implementation of the service, where I would send the entire PDF for signing, the signed PDF was also signed with this certificate.</p>
  <p><strong>A question:</strong> Knowing that in a two-phase signing I was able to sign the PDF with this certificate (apart from the altered or corrupted document after signing error), shouldn't this method also work with the same certificate?</p>
  <p>Currently, what is happening is this:</p>
  <p><a href="https://i.stack.imgur.com/o8QZq.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/o8QZq.png" alt="Signature in Acrobat Reader" /></a></p>
  <p>Inspecting the signature:</p>
  <p><a href="https://i.stack.imgur.com/0VkEO.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/0VkEO.png" alt="Signature properties" /></a></p>
  <p>Again, if I open the same PDF in PDF-XChange, the signature is valid and the document was not modified. The requirement is the PDF to be valid in Acrobat, but I'm puzzled by this difference between readers.</p>
  <p><a href="https://drive.google.com/file/d/1hjt6uPbUhov5tspu1WFSTninTc26AB8o/view?usp=sharing" rel="nofollow noreferrer">Result PDF</a></p>
  <hr />
  <h2>Update 2</h2>
  <blockquote>
  <p>I.e. you only have to prefix your hash with the byte sequence 30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20.</p>
  </blockquote>
  <p>After adding this SHA256 prefix to the message digest, the resulting PDF is now correctly signed.</p>
  <blockquote>
  <p><strong>Will Adobe Reader accept the fixed signature?</strong></p>
  <blockquote>
  <p>I doubt it. The key usage of the signer certificate only contains the value for signing other certificates.</p>
  </blockquote>
  </blockquote>
  <p>The current certificate is only used for testing. In the production environment I believe the certificate provided by the external service will be valid.</p>
  <p><strong>I have two more questions regarding this issue:</strong></p>
  <blockquote>
  <p>For your code this means that you have to pack the hash into a DigestInfo structure before sending it to the service.</p>
  </blockquote>
  <p>Q: How did you inspect the signature container to conclude that it wasn't correct?</p>
  <p>Q: In my initial code, I had a two-phased signing. Would the same principal applied in a single-sign method still be valid, i.e., applying the SHA256 prefix do the pre-signed bytes and after setting the digest with the resulting signed bytes?</p>