Available Now: Explore our latest release with enhanced accessibility and powerful IDP features
By Roger Dunham | 2024 Jun 12
11 min
Tags
digital signature
webviewer
data privacy
Summary: Ever wished you could sign PDFs directly within your application without relying on external services? This article covers how to create self-signed certificates for digital signatures using WebViewer. We'll guide you through obtaining a certificate to digitally signing PDFs within your application.
We have written before about why digital signatures are a valuable tool for verifying the authenticity and integrity of documents and how they work.
In this article, we will look at how to implement the ability to add a digital signature using a self-signed certificate. The source code used for this project can be found at GitHub.
This blog post is for people who are just starting to work with digital signatures. It is intended to help you quickly develop a first proof-of-concept WebViewer-based app. Having done that, you will have a better idea of the technology and be able to use that knowledge to develop the final production version of code.
I asked ChatGPT, and it gave this answer:
“A digital certificate is an electronic document used to prove the ownership of a public key. It is a central component of Public Key Infrastructure (PKI), used to authenticate identities over networks such as the Internet.”
The essential part is that there is a public key to encrypt data, information about who the certificate belongs to, and information about who the issuer is that says the owner is trustworthy.
The last part – the issuer – is relevant here, since the issuer can be an organization that is widely trusted (in the case of a CA signed certificate), or it could just be the owner (a self-signed certificate). Let’s look at those two types of certificates.
Since we are at the proof-of-concept stage, a self-signed certificate will be adequate. Next, we will go through the steps of how to create a self-signed certificate, and then move onto how to use that certificate to digitally sign a PDF. After that, we’ll verify that the signature is valid and that the documented is untampered.
If you already have a certificate (self-signed or CA-signed), you can skip ahead to: Using the certificate to digitally sign a PDF.
OpenSSL is one tool that you can use to create a self-signed certificate. It is available for Windows, macOS, and Linux. Other tools like Java KeyTool exist, so feel free to use that if you prefer, although some of the details will differ.
Your private key is the single most important component of the digital signature system. It helps to enable encryption and prevents others from impersonating you.
It is essential that you keep the private key secure – anyone that gets the key can claim to be you.
Creating a private key is straightforward.
openssl genpkey -algorithm RSA -out private-key.pem
This command generates a private key and saves it into the file private-key.pem.
There are a lot of options available for this function, but the ones used here are adequate for now.
Figure 1 – A typical private key. Note that sharing this, even as a screenshot, means that it is now compromised.
Next, you need to create a Certificate Signing Request (CSR). The CSR contains the information about the entity (you) who needs the certificate. It also contains the public key that will be included in your certificate and is signed with the corresponding private key.
openssl req -new -key private-key.pem -out csr.pem
The command req takes the private key that we previously created (although it could generate one itself), then prompts the user to provide various pieces of information. For example, country, state, organization, and common name (CN).
Figure 2 – A typical example of the additional information needed to create a CSR
When the process completes, a new file will have been created. In this case, it’s called csr.pem.
Figure 3 – A typical certificate signing request file
This file doesn’t need to be kept private, but there really is no reason to make it public.
Finally, we can generate the self-signed certificate. This uses the CSR and our private key as inputs and creates a file (in this case called certificate.pem) that we can share.
All certificates have a validity – in this sample, it is valid for 365 days.
openssl x509 -req -days 365 -in csr.pem -signkey private-key.pem -out certificate.pem
As with the other functions, there are many options like specifying the signing algorithm. But for now, these options are OK.
The generated certificate includes the information you entered when you created the CSR.
Figure 4 – Typical output when creating a certificate
Once again, the file is easy to read in a text viewer (even if it doesn’t make much sense).
Figure 5 – A typical self-signed certificate
Apryse WebViewer doesn’t use *.pem files directly, though it does support various certificate file types.
An easy way to get started is with the PKCS 12 archive file format (*.pfx), so we will use that as an example.
OpenSSL allows us to generate a *.pfx file from the certificate that we have already created using the pkcs12 command.
openssl pkcs12 -export -out certificate.pfx -inkey private-key.pem -in certificate.pem
Figure 6 – Typical output when creating the pfx file
After running this command, you will be prompted to set a password for the *.pfx file.
That password will be needed when importing or using the certificate in other applications or systems. As an example, we will use the password weak-password1.
For any real-world example, though, you would want to use a much stronger password. That password is one of the things a malicious user needs to sign documents in your name, so you want to make it hard to guess and keep it secure.
Make sure to keep this password secure, as it protects the private key and the certificate contained within the *.pfx file.
By now, we should have the file certificate.pfx which is not directly readable by humans but does make sense to the WebViewer code.
Figure 7 – A typical pfx file
OK, that is us finished with OpenSSL. If you acquired a commercial key from a CA, and skipped over this section, welcome back!
We have three things that we will be using:
We will be using the *.pfx file and its password to sign PDFs. Before we do that, though, we need to enable the fullAPI in WebViewer.
fullAPI: true,
The fullAPI means we have access to functionality that is not needed by many users, but it is needed when creating or verifying digital signatures.
Before we can sign the document, we need to add a signature field to the PDF. There are several steps to this, as we will see.
The relevant one from a digital certificate point of view is to create a StdSignatureHandler using the *.pfx file and the password.
const sigHandlerId = await doc.addStdSignatureHandlerFromURL('[pfx file], [password]);
Note that there is a potential security risk here – the code that will run in the browser needs to have access to both the pfx file and its password. Unfortunately, making these available to WebViewer means that they are potentially available to anyone using the webpage. Malicious users could, with effort, get hold of the certificate and password, and then use those to fraudulently sign documents in your name.
The risk of that happening may be acceptable. If you are only going to use the certificate “in house,” that may be adequate for keeping malicious users away from the password.
If, however, you are going to deploy your WebViewer-based website publicly, you might want to consider moving the digital signing code to a server, so that the certificate and password are never sent to the browser.
The actual mechanism for signing PDFs within the browser uses the following code:
async function addSignature() {
await PDFNet.initialize();
const doc = await documentViewer.getDocument().getPDFDoc();
// Run PDFNet methods with memory management
await PDFNet.runWithCleanup(async () => {
// runWithCleanup will auto unlock when complete
doc.lock();
// Add an StdSignatureHandler instance to PDFDoc, making sure to keep track of it using the ID returned.
const sigHandlerId = await doc.addStdSignatureHandlerFromURL('/files/certificate.pfx', 'weak-password1');
const approvalSigField = await doc.createDigitalSignatureField("newfield");
const approvalSignatureWidget = await PDFNet.SignatureWidget.createWithDigitalSignatureField(doc, await PDFNet.Rect.init(500, 20, 600, 100), approvalSigField);
// // (OPTIONAL) Add an appearance to the signature field.
const img = await PDFNet.Image.createFromURL(doc, 'https://cdn.iconscout.com/icon/free/png-256/free-signed-2653334-2202906.png');
await approvalSignatureWidget.createSignatureAppearance(img);
//We will need to get the first page so that we can add an approval signature field to it
const page1 = await doc.getPage(1);
page1.annotPushBack(approvalSignatureWidget);
// Prepare the signature and signature handler for signing.
await approvalSigField.signOnNextSaveWithCustomHandler(sigHandlerId);
// The actual approval signing will be done during the save operation.
const buf = await doc.saveMemoryBuffer(0);
const blob = new Blob([buf], { type: 'application/pdf' });
//Save via any mechanism that you like - saveBlob creates a link, then clicks the link
saveBlob(blob, 'signed_doc.pdf');
instance.UI.loadDocument(blob, { filename: 'signed_doc.pdf' });
}, licKey);
}
In fact, this code does more than just sign the PDF – it also adds an image using approvalSignatureWidget.createSignatureAppearance(img) that gives a visual indication that the document has been signed, and automatically downloads the file.
You may decide that this isn’t quite what you want, so feel free to use whatever parts of the code suit your purpose.
When the code is run, you will then see the tick mark to indicate that the PDF has been signed.
Figure 8 – The visual representation that the document has been signed
While the tick mark is useful, it could just be an image added by anyone. How can we verify that the document has really been signed? Once again, WebViewer has a solution: just click on the panel button then on the Signatures tab.
Figure 9 – The initial result of trying to verify that the document is signed
When you first do this, it might inform you of a problem with the signature. There is no need to panic just yet.
If we are using a certificate that we signed ourselves, there is no automatic way for WebViewer to know that it can be trusted. Had we used a certificate from a Certificate Authority, the “trust chain” would be present and the signature verified.
However, anyone could have created a certificate using OpenSSL, and that includes people trying to send falsified invoices or something similarly fraudulent.
In this case, we know that we created the certificate, and we trust ourselves. As such, we can add our certificate (via the file certificate.pem that we created earlier) to the list of ones that WebViewer considers trusted.
VerificationOptions.addTrustedCertificates(['/files/certificate.pem'])
Now, when we restart WebViewer, the signature will be considered valid. We can, if we wish, drill down into the properties to find out more about that signature.
Figure 10 – The document is now considered to be validly signed
You can even see that the signer has specified that some changes can be made to the document without invalidating the document integrity.
Once you have WebViewer running and are able to digitally sign a PDF, you should probably take a moment to reflect and ask yourself:
Whatever you choose to do, it is better to make a well-informed choice, rather than guessing.
Digital signatures and certificates are unavoidably complex, and they need to be. They do important work and keep your documents secure.
We have only touched on a few parts of the digital signature process – there are other options with different algorithms, or using buffers instead of files, so check out the documentation for the SDK to get started quickly.
Don’t forget, you can also reach out to us on Discord if you have any issues.
Tags
digital signature
webviewer
data privacy
Roger Dunham
Share this post
PRODUCTS
Enterprise
Small Business
Popular Content