ICEpdf: Working with Digital Signatures

ICEpdf 6.1.0 introduced support for validating a document’s digital signatures. Validation of a signature is, however, not always as simple as it might seem on the surface, since the very idea of what “validation” means can vary by use case.

A digital signature’s first job is to ensure that a document’s state can be captured at the time of signing. A digital signature’s second job is to ensure the identity of the signer is valid and can be trusted. In the simplest validation case a user may only wish to see if the document has been altered since it was signed (by one or more parties). In a more advanced verification process a user may also need to check the validity of the signer’s certificate against a trusted authority to ensure the signer is who they say they are, and that the certificate used to create the signature has not been revoked.

A digital signature is very much like a hand signature that is written on a good old piece of paper. The signature can be recognised by the signer, but if someone else signs the document then how does the original signer know the document wasn’t modified afterwards? In the paper days a signer would generally make a photocopy of the signed document to ensure a “snap-shot” of the document at the time of signing was taken. Digital signatures by design keep a copy of the signed document and provide a mechanism to check if a document has been modified since the signature was applied. The first step in validating a signature is to get at the signature objects that are associated with a document. The signatures, if present, will be returned with a call to:

InteractiveForm interactiveForm = document.getCatalog().getInteractiveForm();
ArrayList signatureFields = interactiveForm.getSignatureFields();

If the document is signed there will be at least one SignatureWidgetAnnotation object returned. In general, the first step in validation is to verify that the signatures account for every byte in the document. If the signatures cover the complete document length, then we can make the assumption the signatures are intact and signature validation should continue. The following API call is all that is needed to verify that the signatures cover the length of the document.

boolean coversLength = interactiveForm.isSignaturesCoverDocumentLength();

A program validating the signature can make a choice at this point, if the signatures don’t cover the length of the document then further processing might not be needed since modifications have been made to the document post-signing, and the document signature may be considered invalid at this point (depending upon the requirements of the application). However, if the signature covers the entire length of the document, then validation of the signatures should continue.

Each SignatureWidgetAnnotation associated with the document contains an instance of a SignatureValidator which is responsible for doing the cryptographic validation work needed to verify the signed data and the identify of the signer.

When the method validate() is called all the heavy lifting is handled by the validator to check if the signature is sound and if the certificate is to be trusted. Once validate() is called a number of state variables will have been set to allow the end user to quickly check the validity of the signature and certificate.

Signer validation can be summarized as follows based on the properties of the validator:

System.out.println("Validity Summary:");
if (!signatureValidator.isSignedDataModified() &&!signatureValidator.isDocumentDataModified()) {
   System.out.println(" Document has not been modified since it was signed");
} else if (!signatureValidator.isSignedDataModified() &&
       signatureValidator.isDocumentDataModified() &&
       signatureValidator.isSignaturesCoverDocumentLength()) {
   System.out.println(" This version of the document is unaltered but subsequent changes have been made");
} else if (!signatureValidator.isSignaturesCoverDocumentLength()) {
   System.out.println(" Document has been altered or corrupted since it was signed");
}
if (!signatureValidator.isCertificateDateValid()) {
   System.out.println(" Signers certificate has expired");
}
if (signatureValidator.isEmbeddedTimeStamp()) {
   System.out.println(" Signature included an embedded timestamp but it could not be validated");
} else {
   System.out.println(" Signing time is from the clock on this signer's computer");
}
if (signatureValidator.isSelfSigned()) {
   System.out.println(" Document is self signed");
}

Now that we know how to interpret the results of a signer SignatureValidator we need to revisit our paper analogy. In the old days if we wanted to verify someone’s signature we would go to a notary or some trusted individual that would validate that we are who we said we are and would endorse our signature. In the digital signature world any signer can generate their own signing certificate which is referred to as self-signed. Self-signing may be fine for some organization’s digital signature needs, but in theory could be faked or manipulated. The solution is to introduce a digital notary that issues a trusted signer certificate. Calls can be made into the SignatureValidator to verify a signer certificate but this can only be made against trusted certificates that have been imported into the JVM’s keystore file. Signer certificate validation can be implemented as follows:

System.out.println("Signer Info:");
if (signatureValidator.isCertificateChainTrusted()) {
   System.out.println(" Path validation checks were successful");
} else {
   System.out.println(" Path validation checks were unsuccessful");
}
if (!signatureValidator.isCertificateChainTrusted() || signatureValidator.isRevocation()) {
   System.out.println(" Revocation checking was not performed");
} else {
   System.out.println(" Signer's certificate is valid and has not been revoked");
}

The SignatureValidator in ICEpdf uses a keystore file found using the following location pattern:

${java.home}/caCertLocationPath

Where…

caCertLocationPath = /lib/security/cacerts

…is assigned by default. Note that the value of caCertLocationPath can also be set with the system property “-Dorg.icepdf.core.signatures.caCertPath”

Importing a trusted certificate is relatively straightforward using the Java keytool command, for example:

>keytool -import -keystore .\cacerts -trustcacerts -alias "GlobalSign Root CA - G1" -file .\Root-R1.crt
>keytool -import -keystore .\cacerts -trustcacerts -alias "GlobalSign Root CA - G2" -file .\Root-R2.crt
>keytool -import -keystore .\cacerts -trustcacerts -alias "GlobalSign Root CA - G3" -file .\Root-R3.crt

Once a keystore has been populated with the needed trusted certificates the SignatureValidator can be used to test if a certificate chain is valid, hasn’t been revoked by the issuer, and is thus trusted!

The ICEpdf Viewer RI application contains a utility pane panel that shows a document’s digital signatures and their validation status. There is also a simplified example located in ./icepdf/examples/signatures/SignatureVerification.java.

As you can see from the above examples, the SignatureValidator is relatively easy to use to validate a signature against a file, but validating a signer certificate may take bit more effort as setting up a trusted keystore is a necessity.

Happy Validating!

ICEpdf Development Team

Leave a Reply

Your email address will not be published. Required fields are marked *

nineteen + 8 =