Digital Signatures

A digital signature is an electronic fingerprint uniquely identifying the signing person. A digital signature on a PDF document is both reliable proof of the document’s origin and protection against modification by third parties. Be sure to check out our blog post about electronic signatures in a PDF, which explains how digital signatures work and when they are needed.

ℹ️ Note: For more information on digital signatures, please look at the Digital Signatures in a PDF guide by Adobe. You should also take a look at the 12.8 Digital Signatures section of Adobe’s PDF 1.7 specification. For general information on how digital signatures work, please read the digital signature entry on Wikipedia.


Considerations

PSPDFKit allows signing both existing signature form elements and documents without a signature form element.

The supported signing method is:

  • CMS (adobe.pkcs7.detached)

Supported signing algorithms are:

  • RSA
  • ECDSA

Supported hashing algorithms include:

  • MD4
  • MD5
  • SHA-2 (SHA-224, SHA-256, SHA-384, SHA-512, SHA-512-256)

A hashing algorithm is an algorithm that converts a large amount of data (your PDF document) to a fixed-length string. If someone changes one bit of data in your PDF document, the hashing algorithm produces a different string, so if you store that hash inside the PDF document, you could potentially know when it has been changed by a third party. However, as easy as it sounds, this approach is insecure. If someone knows the hashing algorithm that was used to sign the PDF, they could replace the hash inside the PDF so that it validates as authentic. To avoid this, PSPDFKit works with encryption algorithms.

More specifically, PSPDFKit supports RSA, one of the most popular public key encryption algorithms. ECDSA, an algorithm based on elliptic curve cryptography, is also supported. Cryptographic systems based on ECDSA are becoming the de facto standard for messaging and systems security. For example, in the iOS Security Guide, Apple explains how it uses ECDSA extensively in its mobile operating system, along with detailing its benefits when compared to other algorithms. You can use OpenSSL to generate a self-signed RSA certificate using this OpenSSL command:

1
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem

ECDSA certificates can be created using the openssl ecparam command.

You can use a self-signed certificate for testing purposes, but you will need to make sure the certificate is trusted by all the devices the PDF is opened on (including PCs/Macs with Acrobat). A self-signed certificate will probably also generate warnings about its keyUsage extension (the self-signed certificate must permit certificate signing — keyCertSign, see RFC 5280).

⚠️ Important: In production, always use a certificate from a valid certificate authority. Make sure the certificate’s keyUsage has the digitalSignature permission set (see RFC 5280).

How to Create Digital Signatures

To create a digital signature, you need two things.

  • First, you need an X509 certificate that contains your public key and your signer information. PSPDFKit supports PEM-encoded and DER-encoded X509 certificates, as well as DER-encoded PKCS#7 certificates. You can check the encoding of a certificate file by using the OpenSSL command line tool as follows:
1
  openssl pkcs7 -noout -text -print_certs -in example.p7b

The above command will print an error message if “example.p7b” is not a PEM-encoded PKCS#7 certificate or certificate chain.

1
  openssl pkcs7 -inform der -noout -text -print_certs -in example.p7b

The above command will print an error message if “example.p7b” is not a DER-encoded PKCS#7 certificate or certificate chain.

  • Second, you need your private key.

The signing process produces the signature by encrypting the message digest from the PDF file with a private key. The certificate with its public key is added to the signature and saved in the PDF file. For your convenience, PSPDFKit provides a PSPDFPKCS12Signer that loads a certificate with the public and private key from a p12 archive. If you want to customize the signing process, you need to subclass PSPDFSigner. Keep in mind that certificates installed by a user via opening the .p12 container with built-in apps (via Install Profile) will go to the Apple access group and will only be available to Apple-provided apps such as Safari or Mail. See the Apple Technical Q&A for more details and a suggested workaround.

Here is an example of how to register a PSPDFPKCS12Signer:

Copy
1
2
3
4
5
6
7
8
9
// `p12Data` is a `p12` archive` NSData` object.
let p12 = PSPDFPKCS12(data: p12Data)

// Create a signer with a display name. The display name will show up in the list of identities when you tap on a signature form field.
let p12Signer = PSPDFPKCS12Signer(displayName: "John Appleseed", pkcs12: p12)

// Register your signer with the signature manager.
let signatureManager = PSPDFKitGlobal.sharedInstance.signatureManager
signatureManager.register(p12Signer)
Copy
1
2
3
4
5
6
7
8
9
// `p12Data` is a `p12` archive `NSData` object.
PSPDFPKCS12 *p12 = [[PSPDFPKCS12 alloc] initWithData:p12Data];

// Create a signer with a display name. The display name will show up in the list of identities when you tap on a signature form field.
PSPDFPKCS12Signer *p12Signer = [[PSPDFPKCS12Signer alloc] initWithDisplayName:@"John Appleseed" PKCS12:p12];

// Register your signer with the signature manager.
PSPDFSignatureManager *signatureManager = PSPDFKitGlobal.sharedInstance.signatureManager;
[signatureManager registerSigner:p12Signer];

How to Use the Digital Signatures UI

PSPDFKit comes with a ready-to-use user interface collection that allows you to provide digital signing capabilities inside your apps with minimal effort. When creating a signature in the user interface, you need to provide a certificate or draw an ink signature, or both. The document is only digitally signed if a certificate has been provided. Otherwise, only the ink signature is added to the document.

By default, the user is able to select a certificate if there are registeredSigners available. This behavior can be customized via PSPDFConfiguration.signatureCertificateSelectionMode.

Signing with a Form Element

If there are no stored signatures in the shared PSPDFSignatureStore, then tapping an empty signature form element shows a PSPDFSignatureViewController, which is where you can both sign a document with an ink signature and assign a certificate to your signature.

If there are already stored signatures, a PSPDFSignatureSelectorViewController is shown instead, allowing you to either choose an existing signature or create a new one.

Signing without a Form Element

If a document doesn’t have a signature form element, you can still digitally sign it. The way to initiate this flow is to tap on the signature toolbar button and then select or create a signature as shown above. If you added a certificate to the signature, you will be asked to select the signature placement on the page; otherwise, the signature is placed into the document and you can move and resize it to your liking. After making the selection, you will be asked for the password of the certificate. Finally, after entering the password, the document will be signed.

Only Allow Digital Signing

By default, users can create both an ink signature without a signer and a digital signature by selecting a signer in PSPDFSignatureViewController. There might be use cases where you don’t want users to create an ink signature at all and only allow digitally signing via a predefined signer, without users being able to change it. You can see how this can be configured in the OnlyAllowDigitalSigningExample.swift example in our Catalog example project. This can be done by subclassing PSPDFSignatureViewController and overriding the signer property to return the predefined signer that needs to be used. Additionally, you should set certificateSelectionMode to PSPDFSignatureCertificateSelectionModeNever — either on the signature view controller directly, or on PSPDFConfiguration.signatureCertificateSelectionMode — to disable the user choosing another signer.

To allow the user to select a different certificate but still prevent the creation of a normal ink signature, you can subclass PSPDFCertificatePickerViewController and only show the signers you want to allow the user to select by overriding the default PSPDFCertificatePickerViewController via overrideClass:withClass:.

How to Validate a Digital Signature

PSPDFKit also needs to be able to validate signatures. The validation process consists of two steps.

  • In the first step, we check if the signature certificate embedded during signing can be trusted. In order to do this, we need to obtain the trusted certificate from the authority that issued it, a root CA certificate, or an intermediate CA certificate. For your convenience, PSPDFKit already provides the Adobe Root CA. This CA is also typically loaded by third-party readers.

  • In the second step, we verify the signature. This process essentially decrypts the signature with a public key from the certificate embedded in the PDF file on signing and compares it with the message digest built from the PDF file, excluding the signature itself.

Here is an example of how to provide the trusted certificate:

Copy
1
2
3
4
5
6
7
8
9
10
11
do {
    // Load a certificate (with a public key) from a `p7` archive.
    let certificateData = try Data(contentsOf: p7URL)
    let certificates = try PSPDFX509.certificatesFromPKCS7Data(certificateData)

    for certificate in certificates {
        signatureManager.addTrustedCertificate(certificate)
    }
} catch {
    // Don't forget to check for errors here.
}
Copy
1
2
3
4
5
6
7
8
9
10
11
// Load a certificate (with a public key) from a `p7` archive.
NSData *certificateData = [NSData dataWithContentsOfURL:p7URL];

NSError *error = nil;
NSArray *certificates = [PSPDFX509 certificatesFromPKCS7Data:certData error:&err];

// Don't forget to check for errors here.

for (PSPDFX509 *certificate in certificates) {
	[signatureManager addTrustedCertificate:certificate];
}

The PSPDFSignatureValidator can then validate the signature in the PSPDFSignatureFormElement with -verifySignatureWithTrustedCertificates:error:.

Here’s an example on how to validate a signature and get its status:

Copy
1
2
3
4
5
6
7
8
// Validate the signed document.
let validator = PSPDFSignatureValidator.init(signatureFormElement: signatureFormElement)
do {
		let status = try validator.verifySignature(withTrustedCertificates: certificates)
		// Decide what to do with the status.
} catch {
		// Handle errors.
}
Copy
1
2
3
// Validate the signed document.
PSPDFSignatureValidator *validator = [[PSPDFSignatureValidator alloc] initWithSignatureFormElement:signatureFormElement];
PSPDFSignatureStatus *status = [validator verifySignatureWithTrustedCertificates:certificates error:error];

This can be very useful for the topic that comes next.

What Are the Different Signature Validation Statuses?

If you validate a digital signature with PSPDFKit, either programmatically or using our UI, there can be several possible statuses for the signature. Programmatically, the PSPDFSignatureStatus class contains a property, signatureIntegrityStatus, that can be queried to determine if the document was altered in some way after it was digitally signed. Additionally, the coversEntireDocument Boolean property is useful if the document has many digital signatures. If coversEntireDocument returns false, it means the digital signature only covers a particular revision of the document — that is, there may be subsequent revisions covered by digital signatures that were applied later on.

Let’s take a look at our validation UI when the document is signed with a self-signed certificate:

In this sample case, the first paragraph informs us of the general status of the digital signature. Next, the signer name and signing date is shown. This information always comes from the certificate itself, so you don’t have programmatic access to modify it. Following that paragraph, the integrity status of the document is displayed. In this case, the document has not been modified since it was signed, which is a good sign. Finally, the UI lets us know that the certificate used for signing was a self-signed certificate. This is not necessarily bad, but we find out about this situation with a severity level of “warning.”

Let’s see what happens if we try to validate a signature with an expired certificate:

In this case, we are informed that the certificate is not valid because it has expired. To prevent confusing our users, our default UI does not show the integrity status of a document if the certificate validation failed (that is, if the general signature status is “error”).

This is the validation UI that is shown when the certificate is not self-signed or hasn’t expired but is not yet trusted by PSPDFKit:

Biometric Signatures

Beginning with PSPDFKit 7 for iOS, we automatically store biometric properties encrypted in a digital signature. These biometric properties are stored via PSPDFSignatureBiometricProperties. This information includes things like whether or not the signature was created with a stylus, the size of the signee’s finger, and the timing and pressure information that was collected while writing the signature. Ultimately, this data can be used to create solutions that provide a higher grade of security than traditional digital signatures do. A digital signature can only contain biometric data if an ink signature was used to create it.

By default, all available properties on PSPDFSignatureBiometricProperties are populated and stored. These properties can be customized to restrict storing specific properties via PSPDFConfiguration.signatureBiometricPropertiesOptions. When signing a document, only the specified values are stored in the digital signature.

For example, to only store the touch radius and the input method biometric properties of the ink signature, you can use the following code:

Copy
1
2
3
let controller = PSPDFViewController(document: document, configuration: PSPDFConfiguration { builder in
    builder.signatureBiometricPropertiesOptions = [.touchRadius, .inputMethod]
})
Copy
1
2
3
PSPDFViewController *controller = [[PSPDFViewController alloc] initWithDocument:document configuration:[PSPDFConfiguration configurationWithBuilder:^(PSPDFConfigurationBuilder *builder) {
    builder.signatureBiometricPropertiesOptions = PSPDFSignatureBiometricPropertiesOptionTouchRadius | PSPDFSignatureBiometricPropertiesOptionInputMethod;
}]];

You can customize the properties that are stored by overriding -[PSPDFSignatureContainer initWithAnnotation:signer:biometricProperties:] and modifying the biometricProperties object to fit your needs.

The following biometric properties and data points are captured and stored in the digital signature when using the default document signing UI flow.

If you are creating digital signatures manually, you can provide a custom PSPDFSignatureBiometricProperties object by implementing the - (PSPDFSignatureAppearance)documentSigner:signatureBiometricProperties: method of the dataSource set on PSPDFSigner, which is then saved alongside the digital signature — encrypted with the signing certificate — in the document.

Collecting Biometric Data

You can access biometric properties from a PSPDFSignatureFormElement object after the signing process is complete by using -[PSPDFSignatureFormElement signatureBiometricProperties:], like so:

Copy
1
2
3
4
5
6
7
let signedSignatureFormElement: PSPDFSignatureFormElement = ...
var privateKey: PSPDFPrivateKey = ...
let pkcs12: PSPDFPKCS12 = ...
pkcs12.unlock(withPassword: password) { _, key, _ in
    privateKey = key
}
let biometricProperties = signedSignatureFormElement.signatureBiometricProperties(privateKey)
Copy
1
2
3
4
5
6
7
8
PSPDFSignatureFormElement *signedSignatureFormElement = ...
PSPDFPrivateKey *privateKey = ...
PSPDFPKCS12 *pkcs12 = ...
[pkcs12 unlockWithPassword:password done:^(PSPDFX509 *x509, PSPDFPrivateKey *pkey, NSError *err) {
    privateKey = pkey;
}];

PSPDFSignatureBiometricProperties *biometricProperties = [signedSignatureFormElement signatureBiometricProperties:privateKey];

Customizing Digital Signing

PSPDFKit offers various options for customizing the digital signing experience, primarily via PSPDFDigitalSignatureCoordinator subclassing hooks and PSPDFSignatureViewController.

Customizing the File Path for Digitally Signed Documents

A document that is digitally signed will be copied, signed, and saved to a new path to keep the (unsigned) original document intact. You can customize the path where the digitally signed document should be saved by subclassing PSPDFDigitalSignatureCoordinator and overriding pathForDigitallySignedDocumentFromOriginalDocument:suggestedFileName: to return a custom path. The suggested file name parameter is based on the title of the original document, if available, or the file name as a fallback. It can be used as the last path component in the path where the digitally signed document should be stored. The returned path will be sanitized, and we will append the .pdf path extension.

Customizing the Presentation for Digitally Signed Documents

You can also customize how the signed document should be presented. By default, this is done by pushing a new PSPDFViewController instance on the navigation controller with the digitally signed document shown. This can be customized by overriding presentSignedDocument:showingPageIndex: in your PSPDFDigitalSignatureCoordinator subclass. By overriding this method, you are responsible for presenting/pushing the document that should be part of your digital signing experience. The pageIndex parameter is the page index of the signature form field of the document, which you have the option of scrolling to.

Customizing Signature Appearances

During the signing process, you can customize how the final signature will be shown in the document.

PSPDFDigitalSignatureCoordinator provides configureSignatureAppearanceWithBuilder:document:signature:, which you can override to customize the signature appearance.

PSPDFSignatureAppearance has some Boolean properties to decide whether the signer’s name, date of signing, signature location, or signature reason should be shown after the document is signed.

The appearanceMode property enables further configuration of the visual appearance, with two supported modes: PSPDFSignatureAppearanceModeSignatureAndDescription and PSPDFSignatureAppearanceModeDescriptionOnly. If your signature field has some appearance you want to keep after it’s signed (for example, a border or watermark logo), set the reuseExistingAppearance property.

You can provide your own artwork (for example, a handwritten signature) that will be displayed on the left part of a signature field when its appearance mode is PSPDFSignatureAppearanceModeSignatureAndDescription. Set the signatureGraphic property to type PSPDFAnnotationAppearanceStream to accomplish this. You can create a PSPDFAnnotationAppearanceStream with either a JPEG image or a PDF document. The signature watermark that will be drawn in the center of the signature can also be configured. Set the signatureWatermark property to accomplish this. If you don’t configure this property, the PSPDFKit logo will be shown. If you don’t want to show a signature watermark at all, set the showWatermark property to false.

If you are signing a document programmatically, you can pass the PSPDFSignatureAppearance class by configuring the dataSource property of the PSPDFSigner instance, like this:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// A custom document signer data source that will provide the custom signature appearance.
class MyCustomDataSource: NSObject, PSPDFDocumentSignerDataSource {
    func documentSigner(_ signer: PSPDFSigner, signatureAppearance formFieldFqn: String) -> PSPDFSignatureAppearance {
         let image = ...
         let signatureAppearance = PSPDFSignatureAppearance { builder in
             builder.appearanceMode = .signatureAndDescription
             builder.reuseExistingAppearance = true
             builder.signatureGraphic = PSPDFAnnotationAppearanceStream(image: image!)
         }
         return signatureAppearance
    }
}

// Set the custom signer's data source.
let customDataSource = MyCustomDataSource()
signer.dataSource = customDataSource

// Sign the document.
var signedDocument: PSPDFDocument?
signer.sign(signatureFormElement, usingPassword: "test", writeTo: path) {(_ success: Bool, _ document: PSPDFDocument, _ err: Error?) -> Void in
    signedDocument = document
}
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// A custom document signer data source that will provide the custom signature appearance.
@interface MyCustomDataSource: NSObject<PSPDFDocumentSignerDataSource>

@end

@implementation MyCustomDataSource

- (PSPDFSignatureAppearance *)documentSigner:(PSPDFSigner *)signer signatureAppearance:(NSString *)formFieldFqn {
    PSPDFSignatureAppearance *signatureAppearance = [PSPDFSignatureAppearance configurationWithBuilder:^(PSPDFSignatureAppearanceBuilder *builder) {
       builder.appearanceMode = PSPDFSignatureAppearanceModeSignatureAndDescription;
       builder.reuseExistingAppearance = YES;
       builder.signatureGraphic = [PSPDFAnnotationAppearanceStream appearanceStreamWithImage:image];
    }];
    return signatureAppearance;
}

@end

// Set the custom signer's data source.
let customDataSource = [[MyCustomDataSource alloc] init];
signer.dataSource = customDataSource;

// Sign the document.
__block PSPDFDocument *signedDocument;
[signer signFormElement:signatureFormElement usingPassword:@"test" writeTo:path completion:^(BOOL success, PSPDFDocument *document, NSError *err) {
    signedDocument = document;
}];

When providing an ink signature, the default signature appearance looks like the following image.

Custom Signers

There are various use cases in which PSPDFKit’s default signing implementation can’t be used. Some example use cases are when:

  • You are required to use a specific crypto library .
  • You aren’t in direct possession of a signing key (for example, when using an HSM or a signing service).
  • You have a specific multi-step signing flow that is not supported by PSPDFKit’s default implementation (for example, with multiple passwords).

In order to customize the signing process, you can subclass PSPDFSigner. Then register the signer with the signature manager, as described above. Please have a look at the documentation in the PSPDFSigner header file for more information on what methods you need to override.

Signing a Document Using Your Own Encryption

Sometimes it’s not feasible to have access to a private key and load it on an iOS device to sign a document. Or, you may want more control over the signing process than what the PSPDFSigner class provides by default — for example, to sign information using a web service. For such situations, the delegate property of PSPDFSigner may be useful. Another alternative is to use the contained digital signatures workflow explained later in this guide.

First create a class that implements the PSPDFDocumentSignerDelegate protocol, and set it as the delegate property of PSPDFSigner or one of its subclasses. Then you must implement the following method:

  • - (void)documentSigner:signData:hashAlgorithm:completion: will be called during the signing process with two important pieces of information. The data parameter is the hashed document information that must be signed. PSPDFKit follows the security recommendations of the PDF standard and always hashes the entire PDF document, except for a space reserved for the signature itself. hashAlgorithm is the hash function that was used to convert the raw bytes in the PDF document into the NSData that was provided to this method. You must return the signed data using the private key associated with the certificate’s public key you passed to the sign methods in PSPDFSigner.

You can customize the hash and encryption algorithms used to sign the document by implementing a few optional methods of the PSPDFDocumentSignerDataSource protocol:

Contained Digital Signatures

PSPDFKit also supports a workflow where the creation of a digital signature is split in two phases:

  • First, you can “prepare” a document with a signature form field by stamping a custom signature appearance and reserving space in the PDF for the digital signature.
  • Second, you can “embed” a custom PKCS#7 signature container in a document that was already “prepared” in the first step. The end result is a digitally signed document.

This workflow, which we call contained digital signatures, is especially useful when the cryptographic material to sign a document (keys, certificates) is not available on the platform that executes PSPDFKit.

For the first part, preparing a document, you may call - (void)prepareFormElement:toBeSignedWithAppearance:contents:writingToDataSink:completion: on the PSPDFSigner class. This preparation step allows customization for the raw content that will fill the digital signature contents before a real digital signature is applied: The PSPDFSignatureContents protocol is an abstraction for that. If you want to fill the digital signature contents with binary zeroes, pass an instance of the PSPDFBlankSignatureContents class to - (void)prepareFormElement:toBeSignedWithAppearance:contents:writingToDataSink:completion:.

Note that the document generated by the - (void)prepareFormElement:toBeSignedWithAppearance:contents:writingToDataSink:completion: API has two important properties: First, it’s not a valid digitally signed document yet, so it may show errors if you try to validate it with PSPDFKit or any other third-party tool. Second, this document must not be modified in any way, in order to prevent corrupting the digital signature that will be embedded in the final document. The process of embedding the real digital signature is explained next.

For embedding a real digital signature in a document generated by - (void)prepareFormElement:toBeSignedWithAppearance:contents:writingToDataSink:completion:, you can call - (void)embedSignatureInFormElement:withContents:writingToDataSink:completion: on the PSPDFSigner class. You are responsible for generating a valid digital signature in the cryptographic PKCS#7 format for the prepared document. In order to do that, pass a custom implementation of PSPDFSignatureContents to the previously cited method. In your - (NSData *)signData: implementation, you will receive the part of the document that you need to hash, encrypt, and package into a digital signature. You can call - (NSData*)hashDocumentProviderRange:hashAlgorithm:error on a PSPDFDocumentProvider instance to help you calculate the hash value of some parts of a PDF document. You can generate the PKCS7 signature container with the help of our PSPDFPKCS7 class.

You will find examples of this API in the Catalog file ContainedDigitalSignaturesExample.swift.

How Does Adobe Acrobat Validate a Document Signed by PSPDFKit?

Once you have signed a PDF document using PSPDFKit, you’ll notice that after opening it in Adobe Acrobat, you might get the following validation status.

This warning is informing you that the reader application has verified that the document has not been modified since the last time it was signed, but that the certificate is not yet trusted. You can configure the certificate as a trusted anchor in Adobe Acrobat by following the instructions provided by Adobe.

Alternatively, if the PDF is opened in a Windows environment, you can configure Adobe Acrobat to automatically trust every certificate in the Windows Certificate Store by clicking Edit > Preferences > Security > Advanced Preferences, and then checking Enable searching the Windows Certificate Store for the following operations. Use this option with caution, as it might pose a security risk.

Adobe Acrobat 9 introduced a new program to make the validation process more user-friendly: the Adobe Approved Trust List (AATL). Businesses that provide certificates to their users can apply to this program by submitting application materials and their root certificates. Once approved, Adobe Acrobat will automatically download and trust every certificate that is part of this program.

Troubleshooting

I Have a Chain of Certificates That I Used for Signing but When I Validate the Digital Signature in PSPDFKit It Shows as “Not Trusted”

When you have a chain of certificates comprising a leaf certificate and some intermediate certificate authorities, we require that you explicitly trust every intermediate certificate authority in the chain. If any certificate in the path is not trusted, we can’t perform proper path validation, and as a result, we will show two possible error messages:

  • If there is not any expired certificate in the chain, we show a warning explaining that we couldn’t verify the chain of certificates.
  • If there is an expired certificate in the chain, we show an error because we consider an expired certificate to be a very important issue during digital signature validation.

In order to trust certificates, read the Signature Validation section of this guide to learn how to use the PSPDFSignatureManager API to do exactly that.