Blog Post

How to Insert a Digital Signature in a PDF Using JavaScript

Illustration: How to Insert a Digital Signature in a PDF Using JavaScript

In this post, you’ll learn how to insert a digital signature in a PDF using JavaScript. You’ll be leveraging PSPDFKit’s JavaScript PDF signature library, along with our JavaScript digital signature PDF guide, to achieve this.

You’ll also learn about digital certificates, their importance, and how to create your own self-signed digital certificate and then use it to sign a PDF via PSPDFKit.

After you’ve done everything outlined in this article, you’ll be able to verify the digital signature in a tool like Adobe Acrobat Reader.

What Are Digital Certificates and Why Are They Important for Digital Signatures?

Traditionally, we use signatures to verify identity. For example, consider bank checks, which require physical signatures to confirm ownership. If a signature is forged, a check can’t be deposited.

Digital signatures are an enhanced version of a regular signature. You can digitally sign a document and confirm that it belongs to you, and anyone who then opens the document will be able to see that you signed it. It can also alert the viewer if someone has tried to tamper with the document after it was signed.

To achieve this, digital signatures rely on public-key cryptography. To digitally sign a document, a user needs to generate a public and private key pair (unless they already have one). This can easily be done using a tool like OpenSSL. One key (the private key) is used to sign the document, and the other key (the public key) is used to verify that the signature is valid and the data hasn’t been tampered with.

After a document has been digitally signed, you’ll see something like what’s shown below in Adobe Acrobat Reader (or any other tool that supports digital signatures).

JavaScript Digital Signature — Valid Signature

If you plan on using digital signatures, you’ll have to work with a digital security company like DigiCert and buy one. These kinds of companies confirm your identity and make sure you’re exactly who you say you are. They then issue you a certificate, and any document signed with that certificate will be certified and validated on all other devices.

If you use a self-signed certificate, Adobe will show a warning that the certificate is self-signed and the owner may not be who they say they are. In this article, the focus will be on self-signed certificates; however, the process for inserting a digital signature is exactly the same for paid certificates.

Generating a Self-Signed Certificate

There are multiple ways you can generate a self-signed certificate. You can use Adobe Acrobat Reader, or you can use the free tool openssl. OpenSSL comes with macOS and Linux by default, so for the purposes of this post, you’ll use that.

Create a new folder named pspdfkit-demo and then run the following command in the terminal within that folder:

$ openssl req -x509 -sha256 -nodes -newkey rsa:2048 -extensions v3_req -keyout private-key.pem -out cert.pem

In the command, you’re requesting an x509 certificate from OpenSSL. You tell it to use SHA256 as the hash function, and you use the -nodes flag to ensure your private key file isn’t encrypted. You can get rid of this flag for production keys. You then tell OpenSSL to use RSA with 2,048 bits as the key size and ask it to use the v3_req extension. Finally, you name your private key private-key.pem and the certificate cert.pem. The certificate file contains your public key within it.

While running the command, you might encounter this error:

Error Loading extension section v3_req

If this happens, add the following content at the end of the /etc/ssl/openssl.cnf file and rerun the command:

[ v3_req ]
basicConstraints = CA:TRUE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

If you can’t find the /etc/ssl/openssl.cnf file, you can locate the OpenSSL directory by running this command:

$ openssl version -a | grep OPENSSLDIR

openssl.cnf will be located at the printed path. If it doesn’t work, please refer to the Stack Overflow question about finding the correct openssl.cnf file.

Now you’ll have two new files in the pspdfkit-demo folder:

  • private-key.pem

  • cert.pem

Getting the PSPDFKit Source

Before continuing, you need to download the PSPDFKit distribution source. You can easily do that by downloading the PSPDFKit standalone vanilla JavaScript package.

Extract the files from the downloaded archive and you’ll end up with something like this:

├── Documentation.html
├── Examples.html
├── LICENSE.md
├── README.md
├── dist
│   ├── index.d.ts
│   ├── modern
│   │   ├── pspdfkit-lib
│   │   │   ├── Caveat-Bold.woff
│   │   │   ├── ...
│   │   │   └── windows-ba2e2d3f7c5061a9.css
│   │   └── pspdfkit.js
│   ├── pspdfkit-lib
│   │   ├── Caveat-Bold.woff
│   │   ├── Caveat-Bold.woff2
│   │   ├── ...
│   │   └── windows-ba2e2d3f7c5061a9.css
│   └── pspdfkit.js
└── package.json

Move this entire directory to the pspdfkit-demo folder. Now that you have the distribution source code, you’ll create a sample PDF you can use for test purposes.

You can open any word processor like Word or Google Docs and export a simple PDF file, such as one with your name on it.

Sample PDF with name

Save this file with the name sample.pdf in the pspdfkit-demo folder. Now the pspdfkit-demo folder will have the following files/folders in its root:

cert.pem                            private-key.pem                          pspdfkit
sample.pdf

Inside the pspdfkit folder, you’ll find the PSPDFKit JavaScript package. Everything else is the same as before.

Opening the PDF Using PSPDFKit

Next, you’ll create an index.html file and save it in the pspdfkit-demo folder. This file will contain all of the code for using PSPDFKit to open and manipulate a PDF. Add the following code in this file:

<!DOCTYPE html>
<html lang="en-US">
	<head>
		<meta charset="UTF-8" />
		<title>Demo</title>
		<style>
			.pdfdiv {
				width: 100vw;
				height: 100vh;
			}
		</style>
	</head>
	<body>
		<div class="pdfdiv"></div>
		<script src="pspdfkit/dist/pspdfkit.js"></script>
		<script>
			var instance = PSPDFKit.load({
				document: 'sample.pdf',
				container: '.pdfdiv',
			})
				.then((instance) => {
					console.log('Successfully mounted PSPDFKit', instance);
				})
				.catch((error) => {
					console.error(error.message);
				});
		</script>
	</body>
</html>

Most of this code comes from the official documentation. You create a div with the pdfdiv class, which is where PSPDFKit will load the PDF file. You then give this div a width and a height. This is required because PSPDFKit uses the height and width of the div to figure out how much space it should take. By default, the container has no width and height, and that prevents PSPDFKit from successfully initializing.

Then, initialize PSPDFKit using the .load method. Pass in the path to a PDF file and the target div where you want PSPDFKit to load it. The licenseKey line that’s present in the official documentation has been removed, as you don’t need to have a valid license when trying PSPDFKit.

You can’t open this HTML file directly in your browser, so you need to use a server. This way, your file paths will behave correctly and you won’t have any file loading issues. The easiest way to do this is to install the free Live Server extension for VSCode and use that to open your index.html file in the browser. Once the extension is installed, you can right-click on the file in the sidebar and choose Open with Live Server.

JavaScript Digital Signature PDF — Live Server

One additional benefit of using this extension is that it provides live reload functionality: The browser will refresh automatically whenever you make any changes to the file.

If you aren’t using VSCode and would prefer to use something else, you can also use serve.

If everything works, you should be able to see something like what’s shown below in your browser.

PSPDFKit initial PDF load

Now, it’s time to figure out how to digitally sign the document.

Adding a JavaScript Digital Signature to a PDF

The image below shows what the signature creation workflow looks like, according to Adobe documentation.

Adobe digital signature workflow

There are a few steps listed here that will be covered one by one. However, one thing to note right now is that you need to encrypt some data, and PSPDFKit doesn’t natively support this. For encryption and working with certificate files, you’ll instead rely on an external open source library called Forge. Forge will do all the cryptography-related heavy lifting for you and let you focus on the overarching signing workflow.

To use Forge in your project, add this script tag in the body section of index.html. You can put it below the pspdfkit.js script tag:

<script src="https://cdn.jsdelivr.net/npm/node-forge@1.0.0/dist/forge.min.js"></script>

Now, back to the workflow. You need to create a digest of the document, and the recommended algorithm for that is SHA256. You then need to encrypt the digest using RSA, and you can potentially add some authenticated attributes, like signing time. All of this can easily be done using Forge.

First, take a look at the code:

function generatePKCS7({ fileContents }) {
	const certificatePromise = fetch('cert.pem').then((response) =>
		response.text(),
	);
	const privateKeyPromise = fetch('private-key.pem').then((response) =>
		response.text(),
	);
	return new Promise((resolve, reject) => {
		Promise.all([certificatePromise, privateKeyPromise])
			.then(([certificatePem, privateKeyPem]) => {
				const certificate = forge.pki.certificateFromPem(
					certificatePem,
				);
				const privateKey = forge.pki.privateKeyFromPem(
					privateKeyPem,
				);

				const p7 = forge.pkcs7.createSignedData();
				p7.content = new forge.util.ByteBuffer(fileContents);
				p7.addCertificate(certificate);
				p7.addSigner({
					key: privateKey,
					certificate: certificate,
					digestAlgorithm: forge.pki.oids.sha256,
					authenticatedAttributes: [
						{
							type: forge.pki.oids.contentType,
							value: forge.pki.oids.data,
						},
						{
							type: forge.pki.oids.messageDigest,
						},
						{
							type: forge.pki.oids.signingTime,
							value: new Date(),
						},
					],
				});

				p7.sign({ detached: true });
				const result = stringToArrayBuffer(
					forge.asn1.toDer(p7.toAsn1()).getBytes(),
				);
				resolve(result);
			})
			.catch(reject);
	});
}

function stringToArrayBuffer(binaryString) {
	const buffer = new ArrayBuffer(binaryString.length);
	let bufferView = new Uint8Array(buffer);
	for (let i = 0, len = binaryString.length; i < len; i++) {
		bufferView[i] = binaryString.charCodeAt(i);
	}
	return buffer;
}

In the code above, you start by downloading your certificate and private key using fetch. These files are located next to index.html in the pspdfkit-demo folder. Then, you load these files using Forge and create some new PKCS#7 signed data. You add the private key and certificate information to the PKCS#7 object, and you instruct Forge to use SHA256 as the digest algorithm and use contentType, messageDigest, and signingTime as the authenticated attributes. The message digest is auto-populated at the time of signing. Finally, you sign the data using the sign method and use detached: true as instructed by Adobe.

detached: true ensures only the signature and certificate (and not the signed data) are included in the output object.

In the end, you also serialize the PKCS#7 object to Distinguished Encoding Rules (DER) and convert that into an array of bytes, as this is what PSPDFKit expects as the return value from your digital signature function.

What’s now left is to actually use this code to add a JavaScript digital signature to your PDF document. You can do this by modifying your original PDF loading function like this:

var instance = PSPDFKit.load({
	document: 'sample.pdf',
	container: '.pdfdiv',
})
	.then((instance) => {
		console.log('Successfully mounted PSPDFKit', instance);
		instance
			.signDocument(null, generatePKCS7)
			.then(() => {
				console.log('document signed.');
			})
			.catch((error) => {
				console.error('The document could not be signed.', error);
			});
	})
	.catch((error) => {
		console.error(error.message);
	});

Save the index.html file and refresh the browser. You’ll see something like what’s shown below.

JavaScript digital signature sample PDF

This is what the final index.html looks like:

<!DOCTYPE html>
<html lang="en-US">
	<head>
		<meta charset="UTF-8" />
		<title>Demo</title>
		<style>
			.pdfdiv {
				width: 100vw;
				height: 100vh;
			}
		</style>
	</head>

	<body>
		<div class="pdfdiv"></div>
		<script src="pspdfkit/dist/pspdfkit.js"></script>
		<script src="https://cdn.jsdelivr.net/npm/node-forge@1.0.0/dist/forge.min.js"></script>
		<script>
			var instance = PSPDFKit.load({
				document: 'sample.pdf',
				container: '.pdfdiv',
			})
				.then((instance) => {
					console.log('Successfully mounted PSPDFKit', instance);
					instance
						.signDocument(null, generatePKCS7)
						.then(() => {
							console.log('document signed.');
						})
						.catch((error) => {
							console.error(
								'The document could not be signed.',
								error,
							);
						});
				})
				.catch((error) => {
					console.error(error.message);
				});

			function generatePKCS7({ fileContents }) {
				const certificatePromise = fetch(
					'cert.pem',
				).then((response) => response.text());
				const privateKeyPromise = fetch(
					'private-key.pem',
				).then((response) => response.text());
				return new Promise((resolve, reject) => {
					Promise.all([certificatePromise, privateKeyPromise])
						.then(([certificatePem, privateKeyPem]) => {
							const certificate = forge.pki.certificateFromPem(
								certificatePem,
							);
							const privateKey = forge.pki.privateKeyFromPem(
								privateKeyPem,
							);

							const p7 = forge.pkcs7.createSignedData();
							p7.content = new forge.util.ByteBuffer(
								fileContents,
							);
							p7.addCertificate(certificate);
							p7.addSigner({
								key: privateKey,
								certificate: certificate,
								digestAlgorithm: forge.pki.oids.sha256,
								authenticatedAttributes: [
									{
										type: forge.pki.oids.contentType,
										value: forge.pki.oids.data,
									},
									{
										type: forge.pki.oids.messageDigest,
									},
									{
										type: forge.pki.oids.signingTime,
										value: new Date(),
									},
								],
							});

							p7.sign({ detached: true });
							const result = stringToArrayBuffer(
								forge.asn1.toDer(p7.toAsn1()).getBytes(),
							);
							resolve(result);
						})
						.catch(reject);
				});
			}

			// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
			function stringToArrayBuffer(binaryString) {
				const buffer = new ArrayBuffer(binaryString.length);
				let bufferView = new Uint8Array(buffer);
				for (let i = 0, len = binaryString.length; i < len; i++) {
					bufferView[i] = binaryString.charCodeAt(i);
				}
				return buffer;
			}
		</script>
	</body>
</html>

Conclusion

Congrats! You just signed a PDF using your very own digital signature. You can open this PDF in Adobe Acrobat Reader and confirm the signature details.

Sample signed PDF in Adobe Acrobat

The signature may be temporarily invalid because of the PSPDFKit license watermark. This doesn’t happen when using the licensed version.

If you have any questions, please feel free to reach out to our support team. To get started with PSPDFKit’s digital signature SDK, start your free trial.

If you want to learn more about OpenSSL and the related topics discussed in this article, feel free to browse through some of the links below:

Related Products
Share Post
Free 60-Day Trial Try PSPDFKit in your app today.
Free Trial

Related Articles

Explore more
TUTORIALS  |  Web • JavaScript • How To • PDF Viewer

How to Build a JavaScript Image Viewer with Viewer.js

TUTORIALS  |  Web • Remix • React • JavaScript • How To • PDF Viewer

How to Build a PDF Viewer with Remix and PSPDFKit

WEB  |  Web • Vue.js • JavaScript • How to • PDF Generation

Generate a PDF from HTML with Vue.js