title: Need advice on checking signature/certificate of a signed pdf using java
body: <p>Several questions to the code below.  </p>
  
  <p>googled, read javadoc</p>
  
  
  
  <pre class="lang-java prettyprint-override"><code>import org.apache.pdfbox.io.IOUtils;
  import org.apache.pdfbox.pdmodel.PDDocument;
  import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
  import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
  import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
  import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
  import org.apache.pdfbox.pdmodel.interactive.form.PDField;
  import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
  import org.bouncycastle.cert.X509CertificateHolder;
  import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
  import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
  import org.bouncycastle.cms.*;
  import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
  import org.bouncycastle.jcajce.util.MessageDigestUtils;
  import org.bouncycastle.jce.provider.BouncyCastleProvider;
  import org.bouncycastle.util.Store;
  import org.bouncycastle.util.encoders.Hex;
  
  import javax.security.cert.CertificateEncodingException;
  import javax.xml.bind.DatatypeConverter;
  import java.io.*;
  import java.security.GeneralSecurityException;
  import java.security.KeyStore;
  import java.security.PublicKey;
  import java.security.Security;
  import java.security.cert.*;
  import java.text.SimpleDateFormat;
  import java.util.*;
  
  import static java.security.AlgorithmParameterGenerator.getInstance;
  
  public class PDFProcess {
      public static void main(String[] args) {
          System.out.println("Assume customer has signed the prefilled.pdf.  Read prefilled.pdf");
          PDDocument document = null;
  
          /*
           * processes file anacreditForm-signed trusted which has password protection.  both owner password 1234 or user password abce will work
           *
           */
          try {
              File signedFile = new File("anacreditForm-signed expired not locked.pdf");
              document = PDDocument.load(signedFile, "1234");
  
              System.out.println("Number of pages" + document.getNumberOfPages());
  
              PDDocumentCatalog pdCatalog = document.getDocumentCatalog();
              PDAcroForm pdAcroForm = pdCatalog.getAcroForm();
  
              for (PDField pdField : pdAcroForm.getFields()) {
                  System.out.println("Values found: " + pdField.getValueAsString());
              }
  
              System.out.println("Signed? " + pdAcroForm.isSignaturesExist());
              if (pdAcroForm.isSignaturesExist()) {
                  PDSignatureField signatureField = (PDSignatureField) pdAcroForm.getField("signatureField");
                  System.out.println("Name:         " + signatureField.getSignature().getName());
                  System.out.println("Contact Info: " + signatureField.getSignature().getContactInfo());
  
                  Security.addProvider(new BouncyCastleProvider());
                  List&lt;PDSignature&gt; signatureDictionaries = document.getSignatureDictionaries();
                  X509Certificate cert;
                  Collection&lt;X509Certificate&gt; result = new HashSet&lt;X509Certificate&gt;();
                  // Then we validate signatures one at the time.
                  for (PDSignature signatureDictionary : signatureDictionaries) {
                      // NOTE that this code currently supports only "adbe.pkcs7.detached", the most common signature /SubFilter anyway.
                      byte[] signatureContent = signatureDictionary.getContents(new FileInputStream(signedFile));
                      byte[] signedContent = signatureDictionary.getSignedContent(new FileInputStream(signedFile));
                      // Now we construct a PKCS #7 or CMS.
                      CMSProcessable cmsProcessableInputStream = new CMSProcessableByteArray(signedContent);
                      try {
                          CMSSignedData cmsSignedData = new CMSSignedData(cmsProcessableInputStream, signatureContent);
                          // get certificates
                          Store&lt;?&gt; certStore = cmsSignedData.getCertificates();
                          // get signers
                          SignerInformationStore signers = cmsSignedData.getSignerInfos();
                          // variable "it" iterates all signers
                          Iterator&lt;?&gt; it = signers.getSigners().iterator();
                          while (it.hasNext()) {
                              SignerInformation signer = (SignerInformation) it.next();
                              // get all certificates for a signer
                              Collection&lt;?&gt; certCollection = certStore.getMatches(signer.getSID());
                              // variable "certIt" iterates all certificates of a signer
                              Iterator&lt;?&gt; certIt = certCollection.iterator();
                              while (certIt.hasNext()) {
                                  // print details of each certificate
                                  X509CertificateHolder certificateHolder = (X509CertificateHolder) certIt.next();
                                  System.out.println("Subject:      " + certificateHolder.getSubject());
                                  System.out.println("Issuer:       " + certificateHolder.getIssuer());
                                  System.out.println("Valid from:   " + certificateHolder.getNotBefore());
                                  System.out.println("Valid to:     " + certificateHolder.getNotAfter());
                                  //System.out.println("Public key:   " + Hex.toHexString(certificateHolder.getSubjectPublicKeyInfo().getPublicKeyData().getOctets()));
  
                                  CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
                                  InputStream in = new ByteArrayInputStream(certificateHolder.getEncoded());
                                  X509Certificate cert2 = (X509Certificate) certFactory.generateCertificate(in);
                                  // the validity of the certificate isn't verified, just the fact that one of the certs matches the given signer
                                  SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder()
                                              .build(cert2);
                                  if (signer.verify(signerInformationVerifier)){
                                      System.out.println("PDF signature verification is correct");
                                  } else { System.out.println ("PDF signature verification failed");}
  
                                  StringBuilder encodedChain = new StringBuilder();
                                  encodedChain.append("-----BEGIN CERTIFICATE-----\n");
                                  encodedChain.append(new String(Base64.getEncoder().encode(cert2.getEncoded())));
                                  encodedChain.append("\n-----END CERTIFICATE-----\n");
                                  System.out.println(encodedChain.toString());
  
                                  //System.out.println("Public key:   " + DatatypeConverter.printHexBinary(certificateHolder.getSubjectPublicKeyInfo().getPublicKeyData().getBytes()));
                                  // SerialNumber isi BigInteger in java and hex value in Windows/Mac/Adobe
                                  System.out.println("SerialNumber: " + certificateHolder.getSerialNumber().toString(16));
  
                                  //result.add(new JcaX509CertificateConverter().getCertificate(certificateHolder));
  
                                  CertificateFactory certificateFactory2 = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());
                                  InputStream is = new ByteArrayInputStream(certificateHolder.getEncoded());
  
                                  KeyStore keyStore = PKISetup.createKeyStore();
  
                                  PKIXParameters parameters = new PKIXParameters(keyStore);
                                  parameters.setRevocationEnabled(false);
  
                                  ArrayList&lt;X509Certificate&gt; start = new ArrayList&lt;&gt;();
                                  start.add(cert2);
                                  CertificateFactory certFactory3 = CertificateFactory.getInstance("X.509");
                                  CertPath certPath = certFactory3.generateCertPath(start);
                                  //CertPath certPath = certificateFactory.generateCertPath(is, "PKCS7"); // Throws Certificate Exception when a cert path cannot be generated
                                  CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX", new BouncyCastleProvider());
  
                                  // verifies if certificate is signed by trust anchor available in keystore.  For example jsCAexpired.cer was removed as trust anchor - all certificates signed by jsCAexpired.cer will fail the check below
                                  PKIXCertPathValidatorResult validatorResult = (PKIXCertPathValidatorResult) certPathValidator.validate(certPath, parameters); // This will throw a CertPathValidatorException if validation fails
                                  System.out.println("Val result:  " + validatorResult );
                                  System.out.println("Subject was: " + cert2.getSubjectDN().getName());
                                  System.out.println("Issuer was:  " + cert2.getIssuerDN().getName());
                                  System.out.println("Trust Anchor CA Name:  " + validatorResult.getTrustAnchor().getCAName());
                                  System.out.println("Trust Anchor CA:       " + validatorResult.getTrustAnchor().getCA());
                                  System.out.println("Trust Anchor Issuer DN:" + validatorResult.getTrustAnchor().getTrustedCert().getIssuerDN());
                                  System.out.println("Trust Anchor SubjectDN:" + validatorResult.getTrustAnchor().getTrustedCert().getSubjectDN());
                                  System.out.println("Trust Cert Issuer UID:  " + validatorResult.getTrustAnchor().getTrustedCert().getIssuerUniqueID());
                                  System.out.println("Trust Cert Subject UID: " + validatorResult.getTrustAnchor().getTrustedCert().getSubjectUniqueID());
  
                                  System.out.println("Trust Cert SerialNumber: " + validatorResult.getTrustAnchor().getTrustedCert().getSerialNumber().toString(16));
                                  System.out.println("Trust Cert Valid From:   " + validatorResult.getTrustAnchor().getTrustedCert().getNotBefore());
                                  System.out.println("Trust Cert Valid After:  " + validatorResult.getTrustAnchor().getTrustedCert().getNotAfter());
                              }
                          }
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
              }   //this.testValidateSignatureValidationTest();
  
              document.close();
          } catch (InvalidPasswordException e) {
              e.printStackTrace();
          } catch (FileNotFoundException e) {
              e.printStackTrace();
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
          }
      }
  }
  </code></pre>
  
  <p>The code reads in a password protected pdf which contains form fields and a signature field.  the trusted (root) certificates are in a keystone.</p>
  
  <p>Question 1:  See code near:</p>
  
  <pre class="lang-java prettyprint-override"><code>// the validity of the certificate isn't verified, just the fact that one of the certs matches the given signer
  </code></pre>
  
  <p>Why would one check that?  What could go wrong here?</p>
  
  <p>Question 2:  See code near:</p>
  
  <pre class="lang-java prettyprint-override"><code>Collection&lt;?&gt; certCollection = certStore.getMatches(signer.getSID());   
  </code></pre>
  
  <p>This gets certificates out of the pdf that belong to the signer.  Isn't that duplicated in the code near:</p>
  
  <pre class="lang-java prettyprint-override"><code>SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder().build(cert2);                                                                       
  </code></pre>
  
  <p>Question 3:  if the pdf was modified after signature then the code still produces the message "PDF signature verification is correct"</p>
  
  <p>I would have thought the check fails!  What is the java code to detect that the pdf was modified after signing?</p>
  
  <p>Question 4:  See code:</p>
  
  <pre class="lang-java prettyprint-override"><code>PKIXCertPathValidatorResult validatorResult = (PKIXCertPathValidatorResult) certPathValidator.validate(certPath, parameters); 
  </code></pre>
  
  <p>This fails if the certificate path does not lead to a trusted certificate.  Isn't this a much better check than the check referenced to in question 1?</p>