Skip to main content
Version: 1.3

Validate signatures in a signed PDF document

The Pdftools SDK lets you validate digital signatures that have been added to approve or certify a PDF document. The validation process can reference online and offline sources such as:

  • Certificates, OCSP and CRL data embedded in the PDF file
  • Certificates, OCSP and CRL data downloaded from online sources
  • Certificates stored on the local file system
  • Certificates stored in the system's default trust store
Quick start

Download the full sample now in C# and Java.

Interested in C or other language samples? Let us know on the contact page and we'll add it to our samples backlog.

This example shows how to validate a digital signature using certificate information embedded in the PDF file, and a Custom Trust List built from certificate files stored on the local file system.

Steps to validate signatures in a document:

  1. Create a validator object.
  2. Configure the validation parameters.
  3. (Optional) Add certificates for offline validation.
  4. Open and validate the document.
  5. Check and output the results

Before you begin

Creating a Validator object

The Validator object validates the signatures in the input PDF document against trusted sources. It is used to process the signatures as defined in the validation parameters and calculate the overall validation result.

// Create a Validator object
var validator = new Validator();

Configuring the validation parameters

The validator object lets you configure the validation process to handle your business logic.

For example, you can validate all signatures in a document and use several trusted sources for signature validation. To do this, you can use the Default profile and the SignatureSelector.ALL.

// Use the default validation profile as a base for further settings
var profile = new Profiles.Default();

// Validate ALL signatures in the document (not only the latest)
var signatureSelector = SignatureSelector.All;
tip

You can adjust the validation settings to your specific business case using the profile's ValidationOptions, SigningCertTrustConstraints, and TimeStampTrustConstraints.

Adding certificates for offline validation

Certificate files (e.g. PFX or CER) from the local file system can be added as trusted sources using a CustomTrustList.

// create a CustomTrustList to hold the certificates
var ctl = new CustomTrustList();

// Iterate through files in the certificate directory and add certificates
// to the custom trust list
if (Directory.Exists(certDir))
{
var directoryListing = Directory.EnumerateFiles(certDir);
foreach (string fileName in directoryListing)
{
try
{
using var certStr = File.OpenRead(fileName);

if (fileName.EndsWith(".cer") || fileName.EndsWith(".pem"))
{
ctl.AddCertificates(certStr);
}
else if (fileName.EndsWith(".p12") || fileName.EndsWith(".pfx"))
{
// If a password is required, use addArchive(certStr, password).
ctl.AddArchive(certStr);
}
}
catch (Exception e)
{
Console.WriteLine("Could not add certificate '" + fileName + "' to custom trust list: " + e.Message);
}
}
}
else
{
// Handle the case where dir is not a directory
Console.WriteLine("Directory " + certDir + " is missing. No certificates were added to the custom trust list.");
}
Console.WriteLine();

// Assign the custom trust list to the validation profile
profile.CustomTrustList = ctl;

// Allow validation from embedded file sources and the custom trust list
var vo = profile.ValidationOptions;
vo.TimeSource = TimeSource.ProofOfExistence | TimeSource.ExpiredTimeStamp | TimeSource.SignatureTime;
vo.CertificateSources = DataSource.EmbedInSignature | DataSource.EmbedInDocument | DataSource.CustomTrustList;

// Disable revocation checks.
profile.SigningCertTrustConstraints.RevocationCheckPolicy = RevocationCheckPolicy.NoCheck;
profile.TimeStampTrustConstraints.RevocationCheckPolicy = RevocationCheckPolicy.NoCheck;

Opening and validating the document

After instantiating the validator object and configuring the validation profile, you are ready to validate the digital signatures in a document.

The input document is created as a stream (in this example, as file streams). The validator object is used to validate the digital signatures.

using var inStr = File.OpenRead(inputFile);
// Open input document
// If a password is required, use Open(inStr, password)
using var document = Document.Open(inStr);

// Run the validate method passing the document, profile, and selector
var results = validator.Validate(document, profile, signatureSelector);

Checking and outputting the results

Checking validation results

When validation is completed, the validator object returns a ValidationResults object that contains all the information obtained during the validation process. This list contains a ValidationResult object for each signature with all the required information needed to build custom business logic. In this example, all available information is printed to the console.

// Print results
foreach (var result in results)
{
var field = result.SignatureField;
Console.WriteLine(field.FieldName + " of " + field.Name);
try
{
Console.WriteLine(" - Revision : " + (field.Revision.IsLatest ? "latest" : "intermediate"));
}
catch (Exception ex)
{
Console.WriteLine("Unable to validate document Revision: " + ex.Message);
}

PrintContent(result.SignatureContent);
Console.WriteLine();
}
note

To implement PrintContent, see print and to string functions.

Checking constraint events

In addition to the validation results, the validator object raises constraint events for each signature Constraint checked. A handler function can listen to these events to track the progress of the validation process. This way, you can link any errors to the signature causing the issue. In this example, a simple console printer is used to output the signature information.

validator.Constraint += (s, e) =>
{
Console.WriteLine(" - " + e.Signature.Name + (e.DataPart.Length > 0 ? (": " + e.DataPart) : "") + ": " +
ConstraintToString(e.Indication, e.SubIndication, e.Message));
};
note

To implement ConstraintToString, see print and to string functions.

Full example

// Use the default validation profile as a base for further settings
var profile = new Default();

// For offline operation, build a custom trust list from the file system
// and disable external revocation checks
if (certDir != null && certDir.Length != 0)
{
Console.WriteLine("Using 'offline' validation mode with custom trust list.");
Console.WriteLine();

// create a CustomTrustList to hold the certificates
var ctl = new CustomTrustList();

// Iterate through files in the certificate directory and add certificates
// to the custom trust list
if (Directory.Exists(certDir))
{
var directoryListing = Directory.EnumerateFiles(certDir);
foreach (string fileName in directoryListing)
{
try
{
using var certStr = File.OpenRead(fileName);

if (fileName.EndsWith(".cer") || fileName.EndsWith(".pem"))
{
ctl.AddCertificates(certStr);
}
else if (fileName.EndsWith(".p12") || fileName.EndsWith(".pfx"))
{
// If a password is required, use addArchive(certStr, password).
ctl.AddArchive(certStr);
}
}
catch (Exception e)
{
Console.WriteLine("Could not add certificate '" + fileName + "' to custom trust list: " + e.Message);
}
}
}
else
{
// Handle the case where dir is not a directory
Console.WriteLine("Directory " + certDir + " is missing. No certificates were added to the custom trust list.");
}
Console.WriteLine();

// Assign the custom trust list to the validation profile
profile.CustomTrustList = ctl;

// Allow validation from embedded file sources and the custom trust list
var vo = profile.ValidationOptions;
vo.TimeSource = TimeSource.ProofOfExistence | TimeSource.ExpiredTimeStamp | TimeSource.SignatureTime;
vo.CertificateSources = DataSource.EmbedInSignature | DataSource.EmbedInDocument | DataSource.CustomTrustList;

// Disable revocation checks.
profile.SigningCertTrustConstraints.RevocationCheckPolicy = RevocationCheckPolicy.NoCheck;
profile.TimeStampTrustConstraints.RevocationCheckPolicy = RevocationCheckPolicy.NoCheck;
}

// Validate ALL signatures in the document (not only the latest)
var signatureSelector = SignatureSelector.All;

// Create the validator object and event listeners
var validator = new Validator();
validator.Constraint += (s, e) =>
{
Console.WriteLine(" - " + e.Signature.Name + (e.DataPart.Length > 0 ? (": " + e.DataPart) : "") + ": " +
ConstraintToString(e.Indication, e.SubIndication, e.Message));
};

try
{
using var inStr = File.OpenRead(inputFile);
// Open input document
// If a password is required, use Open(inStr, password)
using var document = Document.Open(inStr);

// Run the validate method passing the document, profile and selector
Console.WriteLine("Validation Constraints");
var results = validator.Validate(document, profile, signatureSelector);

Console.WriteLine();
Console.WriteLine("Signatures validated: " + results.Count);
Console.WriteLine();

// Print results
foreach (var result in results)
{
var field = result.SignatureField;
Console.WriteLine(field.FieldName + " of " + field.Name);
try
{
Console.WriteLine(" - Revision : " + (field.Revision.IsLatest ? "latest" : "intermediate"));
}
catch (Exception ex)
{
Console.WriteLine("Unable to validate document Revision: " + ex.Message);
}

PrintContent(result.SignatureContent);
Console.WriteLine();
}

return 0;
}
catch (Exception ex)
{
Console.WriteLine("Unable to validate file: " + ex.Message);
return 5;
}

The following print and to string functions let you list all the properties of the signatures and the signature results.

For example, you can use PrintContent to print certification details such as subject, issuer, and validity. Using ConstraintsToString, you can convert a constraint to a string representation.

// Helper functions to print signature validation details

private static void PrintContent(SignatureContent content)
{
if(content != null)
{
Console.WriteLine(" - Validity : " + ConstraintToString(content.Validity));
switch (content)
{
case UnsupportedSignatureContent:
break;
case CmsSignatureContent signature:
{
Console.WriteLine(" - Validation: " + signature.ValidationTime + " from " + signature.ValidationTimeSource);
Console.WriteLine(" - Hash : " + signature.HashAlgorithm);
Console.WriteLine(" - Signing Cert");
PrintContent(signature.SigningCertificate);
Console.WriteLine(" - Chain");
foreach (var cert in signature.CertificateChain)
{
Console.WriteLine(" - Issuer Cert " + (signature.CertificateChain.IndexOf(cert) + 1));
PrintContent(cert);
}
Console.WriteLine(" - Chain : " + (signature.CertificateChain.IsComplete ? "complete" : "incomplete") + " chain");
Console.WriteLine(" Time-Stamp");
PrintContent(signature.TimeStamp);
break;
}
case TimeStampContent timeStamp:
{
Console.WriteLine(" - Validation: " + timeStamp.ValidationTime + " from " + timeStamp.ValidationTimeSource);
Console.WriteLine(" - Hash : " + timeStamp.HashAlgorithm);
Console.WriteLine(" - Time : " + timeStamp.Date);
Console.WriteLine(" - Signing Cert");
PrintContent(timeStamp.SigningCertificate);
Console.WriteLine(" - Chain");
foreach (var cert in timeStamp.CertificateChain)
{
Console.WriteLine(" - Issuer Cert " + (timeStamp.CertificateChain.IndexOf(cert) + 1));
PrintContent(cert);
}
Console.WriteLine(" - Chain : " + (timeStamp.CertificateChain.IsComplete ? "complete" : "incomplete") + " chain");
break;
}
default:
Console.WriteLine("Unsupported signature content type " + content.GetType().Name);
break;
}
}
else
{
Console.WriteLine(" - null");
}
}

private static void PrintContent(Certificate cert)
{
if(cert != null)
{
Console.WriteLine(" - Subject : " + cert.SubjectName);
Console.WriteLine(" - Issuer : " + cert.IssuerName);
Console.WriteLine(" - Validity : " + cert.NotBefore + " - " + cert.NotAfter);
try
{
Console.WriteLine(" - Fingerprint: " + FormatSha1Digest(new BigInteger(SHA1.Create().ComputeHash(cert.RawData)).ToByteArray(), "-"));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(" - Source : " + cert.Source);
Console.WriteLine(" - Validity : " + ConstraintToString(cert.Validity));
}
else
{
Console.WriteLine(" - null");
}
}

private static String ConstraintToString(ConstraintResult constraint)
{
return ConstraintToString(constraint.Indication, constraint.SubIndication, constraint.Message);
}

private static String ConstraintToString(Indication indication, SubIndication subIndication, String message)
{
return (indication == Indication.Valid ? "" : (indication == Indication.Indeterminate ? "?" : "!")) + "" +
subIndication + " " +
message;
}

// Helper function to generate a delimited SHA-1 digest string
private static String FormatSha1Digest(byte[] bytes, String delimiter)
{
var result = new StringBuilder();
foreach (byte aByte in bytes)
{
int number = (int)aByte & 0xff;
String hex = number.ToString("X2");
result.Append(hex.ToUpper() + delimiter);
}
return result.ToString().Substring(0, result.Length - delimiter.Length);
}