title: iText7: How to verify signature in detached PKCS7?
body: <p>I am writing a service that receives PKCS7 data (extracted from signed PDF documents) and needs to verify it. </p>
  
  <p>I am using iText7 PdfPKCS7 for that, but the signature verification always fails. I can read all other information from the PKCS7 (certificates, timestamps etc., I have verified that also with OpenSSL). Only the signature appears as invalid.</p>
  
  <p>Here's the test case:</p>
  
  <pre><code>public static void main(String[] args) throws IOException, GeneralSecurityException,
      NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
  
      Logger logger = Logger.getLogger(PKCS7Test.class.getName());
  
      BouncyCastleProvider provider = new BouncyCastleProvider();
      Security.addProvider(provider);
  
      String path ="/tmp/signed.pdf";
  
      PdfDocument pdf = new PdfDocument(new PdfReader(path));
      SignatureUtil signatureUtil = new SignatureUtil(pdf);
      List&lt;String&gt; names = signatureUtil.getSignatureNames();
      String outerRevisionName = names.get(names.size()-1);
  
      PdfPKCS7 pkcs7In = signatureUtil.verifySignature(outerRevisionName);
  
      boolean isValidSignature = pkcs7In.verify();
      logger.log(Level.INFO, "pkcs7In signature is " + ((isValidSignature)?"":"not ") + "valid");
  
      // get hash of original document       
      Field digestAttrField = PdfPKCS7.class.getDeclaredField("digestAttr");
      digestAttrField.setAccessible(true);
      byte[] originalDigest = (byte[]) digestAttrField.get(pkcs7In);
  
      // get pkcs7 structure of original signature                
      PdfDictionary dict = signatureUtil.getSignatureDictionary(outerRevisionName);        
      PdfString contents = dict.getAsString(PdfName.Contents);
      byte [] originalBytes = contents.getValueBytes();
      String originalPkcs7 = Base64.getEncoder().encodeToString(originalBytes);
  
      // now reverse process and import PKCS7 data back into object
      byte[] pkcs7Bytes = Base64.getDecoder().decode(originalPkcs7);
      PdfPKCS7 pkcs7Out = new PdfPKCS7(pkcs7Bytes, PdfName.Adbe_pkcs7_detached, provider.getName());
  
      isValidSignature = pkcs7Out.verify();
      logger.log(Level.INFO, "pkcs7Out signature is " + ((isValidSignature)?"":"not ") + "valid");
  
      // get hash of original document from imported signature     
      digestAttrField = PdfPKCS7.class.getDeclaredField("digestAttr");
      digestAttrField.setAccessible(true);
      byte [] importedDigest = (byte[]) digestAttrField.get(pkcs7Out);
  
      logger.log(Level.INFO, "Hash values are " + ((Arrays.areEqual(originalDigest, importedDigest))?"":"not ") + "equal");
  </code></pre>
  
  <p>The output is invariably:</p>
  
  <pre><code>pkcs7In signature is valid
  pkcs7Out signature is not valid
  Hash values are equal
  </code></pre>
  
  <p>I suppose I'm doing something wrong with the import, but just can't find out what...</p>
  
  <p>Btw. <code>/tmp/signed.pdf</code> validates OK (signature and content) in other PDF tools (Adobe DC, PdfOnline etc.)</p>
  
  <p>Edit:
  I tried to verify the signature with BouncyCastle, which fails too ...</p>
  
  <pre><code>    CMSSignedData signature = new CMSSignedData(pkcs7Bytes);        
  
      Store                   certStore = signature.getCertificates();
      SignerInformationStore  signers = signature.getSignerInfos();
      Collection              c = signers.getSigners();
      Iterator                it = c.iterator();
  
      while (it.hasNext())
      {
          SignerInformation   signer = (SignerInformation)it.next();
          Collection          certCollection = certStore.getMatches(signer.getSID());
  
          Iterator              certIt = certCollection.iterator();
          X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
  
         try {
              signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert));
          }
          catch (Exception ex) {
              logger.log(Level.INFO, "Failed to verify with: " + cert.getSubject().toString() + ": " + ex.getLocalizedMessage());
              byte [] contentDigest = signer.getContentDigest();
              bi = new BigInteger(1, contentDigest);
              String hash = String.format("%0" + (contentDigest.length &lt;&lt; 1) + "x", bi);
              logger.log(Level.INFO, "Bouncycastle Hash from pkcs7Out is {0}", hash);
          }
      }
  </code></pre>
  
  <p>The result is <code>Failed to verify with: ... message-digest attribute value does not match calculated value</code>, and the hash value obviously differs from the real one...</p>
  
  <p>Edit 2:
  Workaround by adding separate <code>verify(byte[] msgDigestBytes)</code> method to PdfPKCS7:</p>
  
  <pre><code>public boolean verify(final byte[] msgDigestBytes) throws GeneralSecurityException {
      if (verified)
          return verifyResult;
      if (isTsp) {
          TimeStampTokenInfo info = timeStampToken.getTimeStampInfo();
          MessageImprint imprint = info.toASN1Structure().getMessageImprint();
          byte[] md = msgDigestBytes; // was: messageDigest.digest();
          byte[] imphashed = imprint.getHashedMessage();
          verifyResult = Arrays.equals(md, imphashed);
      } else {
          if (sigAttr != null || sigAttrDer != null) {
              // was: final byte[] msgDigestBytes = messageDigest.digest();
              boolean verifyRSAdata = true;
              // Stefan Santesson fixed a bug, keeping the code backward compatible
              boolean encContDigestCompare = false;
              if (rsaData != null) {
                  verifyRSAdata = Arrays.equals(msgDigestBytes, rsaData);
                  encContDigest.update(rsaData);
                  encContDigestCompare = Arrays.equals(encContDigest.digest(), digestAttr);
              }
              boolean absentEncContDigestCompare = Arrays.equals(msgDigestBytes, digestAttr);
              boolean concludingDigestCompare = absentEncContDigestCompare || encContDigestCompare;
              boolean sigVerify = verifySigAttributes(sigAttr) || verifySigAttributes(sigAttrDer);
              verifyResult = concludingDigestCompare &amp;&amp; sigVerify &amp;&amp; verifyRSAdata;
          } else {
              if (rsaData != null)
                  sig.update(msgDigestBytes); // was: sig.update(messageDigest.digest());
              verifyResult = sig.verify(digest);
          }
      }
      verified = true;
      return verifyResult;
  }
  </code></pre>
  
  <p>For this to work, I need the originally signed hash of the document from a trusted source, which in my case I have. Does this still verify that the signature made over the hash is correct?</p>