Digital Signatures

What Are Digital Signatures?

A digital signature is an electronic fingerprint uniquely identifying the signing person. 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, you can read this.

Considerations

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

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).

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 = PSPDFKit.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 = PSPDFKit.sharedInstance.signatureManager;
[signatureManager registerSigner:p12Signer];

User Interface

When creating a signature in the user interface, you need to provide a certificate or draw an ink, 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:.

Biometric Signatures

Beginning with PSPDFKit 7.0 for iOS, we automatically store biometric properties encrypted in a digital signature. These biometric properties are stored via PSPDFSignatureBiometricProperties and include data points about the creation of an ink signature. If no ink signature was used to digitally sign a document, the biometric properties will be empty.

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 point 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.

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];

Signature Validation

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];
}

You can also take a look at the digital signing process examples in the PSPDFCatalog projects.

Customizing Digital Signing

PSPDFKit offers various options to customize 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.

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 this:

Creating a Custom Signer

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 those situations, the delegate property of PSPDFSigner may be useful.

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:

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.

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 that. 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 confusion to 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”). If you need that information, you can get it from the signatureIntegrityStatus property of the PSPDFSignatureStatus class.

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:

What are the Different Signature Validation Status?

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

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

In this sample case, The first paragraph informs about the general status of the digital signature. Next, the signer name and signing date is shown. This information comes always from the certificate itself, you don't have programmatic access to modify that. 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 informs that the certificate that was used for signing was a self-signed certificate. This is not necessarily bad, but we inform about this situation with a severity level of "warning".

Let's see what happens if we try to validate a signature whose certificate has expired:

In this case, we inform that the certificate is not valid because it has expired. To prevent confusion to users, our default UI does not show the integrity status of a document if the certificate validation failed (that is, the general signature status is "error"). If you need to show that information, you can get it from the signatureIntegrityStatus property of the PSPDFSignatureStatus class.

This is the validation UI that is shown when the certificate is not self-signed or expired, but it's not yet trusted by PSPDFKit:

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 can 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 that.