Several questions to the code below.
googled, read javadoc
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<PDSignature> signatureDictionaries = document.getSignatureDictionaries();
X509Certificate cert;
Collection<X509Certificate> result = new HashSet<X509Certificate>();
// 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<?> certStore = cmsSignedData.getCertificates();
// get signers
SignerInformationStore signers = cmsSignedData.getSignerInfos();
// variable "it" iterates all signers
Iterator<?> it = signers.getSigners().iterator();
while (it.hasNext()) {
SignerInformation signer = (SignerInformation) it.next();
// get all certificates for a signer
Collection<?> certCollection = certStore.getMatches(signer.getSID());
// variable "certIt" iterates all certificates of a signer
Iterator<?> 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<X509Certificate> start = new ArrayList<>();
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 {
}
}
}
The code reads in a password protected pdf which contains form fields and a signature field. the trusted (root) certificates are in a keystone.
Question 1: See code near:
// the validity of the certificate isn't verified, just the fact that one of the certs matches the given signer
Why would one check that? What could go wrong here?
Question 2: See code near:
Collection<?> certCollection = certStore.getMatches(signer.getSID());
This gets certificates out of the pdf that belong to the signer. Isn't that duplicated in the code near:
SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder().build(cert2);
Question 3: if the pdf was modified after signature then the code still produces the message "PDF signature verification is correct"
I would have thought the check fails! What is the java code to detect that the pdf was modified after signing?
Question 4: See code:
PKIXCertPathValidatorResult validatorResult = (PKIXCertPathValidatorResult) certPathValidator.validate(certPath, parameters);
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?