Blog

Mar
03
2015

Qualified electronic signatures in PDFs using PHP and a USB token/smartcard

Posted by  symos  in  Web development
Signed PDF icon

Note: We don't usually post technical articles on our blog, this is an exception due to the difficulty we had in finding information relevant to this topic online, so we felt like sharing our findings.

Adding a digital signature to a PDF file is relatively easy using a library such as TCPDF, as long as your personal certificate is stored somewhere on your server/computer. However, in order for a digital signature to be considered “Qualified” in the European Union (which -technical details aside- means it holds the same value as a handwritten signature) the certificate needs to be stored in a Secure Signature Creation Device, such as a smartcard or a USB token.

TCPDF does not support this kind of integration but, luckily, this can be fixed by making a few small tweaks, as long as your smartcard/token supports the PKCS#11 standard (most do). At the end of this post, we also give two alternative solutions without the use of TCPDF.

Using TCPDF

The following assumes you already have a working TCPDF installation which you use to create your PDFs.

Step 1

Download and install all the necessary software/packages (the following are based on an Ubuntu installation. Other distributions might be slightly different):

  • Your token’s drivers/middleware
    This is important because the middleware will also install a PKCS#11 library, which we will need in order to “talk” with the device. Different devices will name their libraries differently. We use a Safenet eToken 5100 token, which installs the library in /usr/lib/libeTPkcs11.so
  • The OpenSC package
    It doesn’t matter if OpenSC doesn’t support your token. We won’t use it as a driver/middleware, we will only use pkcs11-tool to read some information off our token
  • OpenSSL (although it should most likely already be installed)
  • Package “libengine-pkcs11-openssl”

Step 2

Now we have to setup our “engine” in OpenSSL, which essentially is our USB token. In the OpenSSL configuration file, include the following lines. If you don’t know where your OpenSSL configuration file lives, read this answer.

#This line needs to be added in the "default" area, i.e. before any section
openssl_conf = openssl_def

[openssl_def]
engines = engine_section

[engine_section]
pkcs11 = pkcs11_section

[pkcs11_section]
engine_id = pkcs11
#If your distribution is not Ubuntu, the pkcs11 engine path coule be different:
dynamic_path = /usr/lib/engines/engine_pkcs11.so
#This is the path to your token's PKCS#11 library:
MODULE_PATH = /usr/lib/libeTPkcs11.so
#This is your token's PIN:
PIN = 123456

Step 3

In tcpdf.php (found in TCPDF’s root directory) search for the line where a signature is added using the openssl_pkcs7_sign function. It should be in the "Output" function, looking like this:

if (empty($this->signature_data['extracerts'])) {
      openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
} else {
      openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED, $this->signature_data['extracerts']);
}

Comment this out and add:

exec('openssl smime -md sha256 -binary -outform SMIME -sign -certfile /home/user/full_chain.pem -signer /home/user/personal.pem -inkey slot_x-id_xxxxxxxxxxxxxxx -keyform engine -in ' . $tempdoc . ' -out ' . $tempsign . ' -engine pkcs11 -passin pass:123456');

where:

certfile: The path to the chain certificates (in PEM format). All certificates should be in a single file, one after the other. If your certificate authority has given you the certificates in another format (e.g. DER), see this link on how to convert them using OpenSSL.
signer: The path to your personal certificate (its public key). You can usually export this from your token using the manufacturer's middleware.
inkey: The slot and id of your certificate. In order to get these, from command line run "pkcs11-tool --module /usr/lib/libeTPkcs11.so --list-objects" where you have to replace the --module parameter value with the correct path for your middleware library.
passin: The PIN of your token (after pass:)

It would be wise to first test this command in the command line before adding it to your PHP script, to see if it works or if it produces any error messages.

Step 4

Use TCPDF's example_052 to create a signed PDF. If all went well, the document should include your signature, which you can validate using Adobe Reader (if Reader nags about your signature, it might be because it doesn't trust your issuing authority and/or intermediate certificates, not because there is a problem with the signature).

You can then use the functions found in example_052 in your script to add signatures to the files you already produce.

Using MyPDFSigner

If you want to sign PDFs that have been created externally (i.e. not from TCPDF) and/or you want to add a secure timestamp (which TCPDF does not support yet) you can use a command line tool called MyPDFSigner, which also comes with a PHP module.

You can download MyPDFSigner here.

Please note MyPDFSigner is not open source software, but it’s free to use as long as you don’t mind having "Evaluating MyPDFSigner" as the "Reason" field of your signature. If you do, you will have to buy a license.

MyPDFSigner also has the ability to make the documents you create LTV (Long Term Validation) enabled. So, if LTV is a requirement for you, MyPDFSigner is the way to go!

Using other tools

We have also had success in signing PDFs via command line using JSignPdf which is an open source tool. However, JSignPdf does not support adding the certificate chain to your signature (assuming it’s not self-signed), so by default it is being treated as invalid by Adobe Reader. Otherwise JSignPdf seems like a pretty robust and mature tool (although not in active development any more).

comments powered by Disqus