Perfect PDF Digital Signatures, EU style

Signing PDFs with an EU identity card (and how to get one)

History

Back in 2011 we wrote an article on how to achieve a "perfect" Digital Signature in Acrobat. This involved signing with a key issued by a company on the Adobe CDS list, where CDS was "Certificate Document Services".

Eight years on and the technology hasn't changed much. But the legislative framework has, certainly in Europe. The EU eSignature program has standardised the use of digital signatures across most of Europe as part of its eiDAS Regulation. The technical standards for these Advanced Electronic Signatures describe how to digitally sign XML, binary files and PDF, with the PDF variation known as PAdES.

PAdES and PDF

PAdES is part of "PDF 2.0" (officially ISO32000-2:2017), although the original ETSI Technical Specification, TS 102778 was published in 2010. Technically, they're not that different to the regular PDF signatures first added to PDF 20 years ago in PDF 1.3. We've supported PAdES in our API since 2016, but as the process of signing with a PAdES signature is really no different to a normal signature, we haven't written much on it: the one line you need to add is factory.setPAdES(true).

What is different is that the list of trusted root certificates supplied by the EU is included in Acrobat by default. This makes a big difference when you're verifying a signatures authenticity. Eight years ago, when we wrote the original version of this article, we had to track down a hardware token, purchase a digital identity (from a brand that has long since disappeared), install custom drivers for the USB token they posted us before signing the PDF. It was complicated.

Now, in theory, all you need to create a "perfect" digital signature is an EU identity card.

EU Identity Cards

The EU is in the news a bit at the moment, especially in London, where BFO is based. But today I shall skip lightly over our national trainwreck and talk, instead, about identity cards.

Most EU countries have some form of ID card. They are largely standardized smart-cards, and many of them actually run Java. Which sounds promising, but the javax.smartcard interface is very low level and the flexibility of smart-cards has led to considerable complexity. We have tried and failed to understand most of it. All we specifically want is the ability to sign data with one, and for that we need PKCS#11.

PKCS#11 is a software API for accessing cryptographic hardware. Java supports it quite well, and our API can use it for signing documents: we documented the process in the last article we wrote on this topic, when we used a USB PKCS#11 hardware token, and software from OpenSC.

So when started planning this project a few months ago, and went straight to the OpenSC project to see what hardware they supported. We tried a few USB tokens and a few cards, with mixed success. Part of the issue was that the technical requirements for an Adobe AATL was that the storage had to be on a FIPS140-2 or better device, which we struggled to find in card format. Then we noticed that OpenSC also supported some National Identity cards, including Estonia.

Estonian Identity Cards and e-Residency

Since 2014, Estonia have been running their e-Residency program, which theoretically allows anyone to become an "e-resident" of Estonia. So I applied. It's quite an easy process (and recently quite popular in Britain, for some reason). Initially OpenSC's software was unable to use it, but that's been fixed just this week.

So, without further waffle, here are the steps we took, and that you can take too - anywhere in the world - to get yourself a digital identity card that will allow you to sign a PDF with a PAdES signature that is approved by Acrobat, and automatically accepted across the EU for signing documents, and generally doing business.

Step 1: Establish your identity

Apply for e-Residency in Estonia. If you already have an EU identity card listed on the OpenSC supported hardware page, you can skip this step.

If you prefer to do this without a national identity card, we can recommend Trustfactory's "Personal Pass" which was, at the time of writing, the cheapest way to get an Adobe AATL approved certificate at $43. You'll need your own hardware in this case. I would like to be able to recommend a particular FIPS-140 USB token that just worked but so far, I can't. We'll keep trying, and will update this article when we have a fully working solution

The steps to generate a keypair and certificate signing request are broadly the same for all Certifiying Authorities, and are probably unchanged from our previous article.

Step 2: Install the software

Download and install OpenSC from their website. For the Estonian ID cards, you'll need at least version 0.20. A few months from now, it's likely the opensc package supplied with your version of Linux will includes these changes too.

Step 3: Set up your card

When your identity card arrives, you may need to do some setup. For my e-Residency card, I had to run the DigiDoc 4 software and initialise two PIN numbers. The PIN you'll use with Java will depend on the slot.

You'll probably want to put your identity card into a USB smart card reader, and see if it's recognised by OpenSC. Here's what we got from mine.

bash$ /usr/local/bin/pkcs11-tool -L
Available slots:
Slot 0 (0x0): ACS ACR 38U-CCID
  token label        : BREMFORD,MICHAEL PAUL,... (PIN1)
  token manufacturer : IDEMIA
  token model        : PKCS#15 emulated
  token flags        : login required, token initialized, PIN initialized, readonly
  hardware version   : 0.0
  firmware version   : 0.0
  serial num         : UA00XXXXX
  pin min/max        : 4/12
Slot 1 (0x1): ACS ACR 38U-CCID
  token label        : BREMFORD,MICHAEL PAUL,... (PIN2)
  token manufacturer : IDEMIA
  token model        : PKCS#15 emulated
  token flags        : login required, token initialized, PIN initialized, readonly
  hardware version   : 0.0
  firmware version   : 0.0
  serial num         : UA00XXXXX
  pin min/max        : 5/12

Step 4: Sign!

That's it. All we need to do now is plug it into Java. Here's some code I used to create a simple "Hello World" (in Estonian, of course) and sign it.

Edit Apr 2021: we've changed our example to select "slot 1" on the card, as this seems to be the certificate intended for signing on most - if not all - eiDAS cards.

import org.faceless.pdf2.*;
import java.security.*;
import java.util.*;
import java.io.*;
import java.net.URL;

public class TestEE {
    private static final String password = "00000000";
    private static final String config = "name=OpenSC\nlibrary=/Library/OpenSC/lib/opensc-pkcs11.so\nslot=1";

    public static void main(String[] args) throws Exception {
        PDF pdf = new PDF();
        PDFPage page = pdf.newPage("A4");
        PDFStyle style = new PDFStyle();
        style.setFont(new StandardFont(StandardFont.HELVETICA), 24);
        page.setStyle(style);
        page.drawText("Tere, Maailm", 50, 800);

        Provider provider;
        // Set up the provider (if you're using Java 6, 7 or 8)...
        provider = new sun.security.pkcs11.SunPKCS11(new ByteArrayInputStream(config.getBytes("ISO-8859-1")));

        // ... or to set up the provider (if you're using Java 9 or later)
        provider = Security.getProvider("SunPKCS11");
        provider.configure("--" + config); // undocumented way to load configuration from a String

        AcrobatSignatureHandlerFactory factory = new AcrobatSignatureHandlerFactory();
        factory.setPAdES(true);
        factory.setValidateCertificatesOnSigning(true); // Make it LTV
        factory.setTimeStampServer(new URL("http://timestamp.digicert.com"));
        FormSignature sig = new FormSignature();
        KeyStore keystore = KeyStore.getInstance("PKCS11", provider);
        keystore.load(null, password.toCharArray());
        String alias = keystore.aliases().nextElement(); // Use the first alias
        sig.sign(keystore, alias, password.toCharArray(), factory);
        pdf.getForm().getElements().put("Sig1", sig);
        OutputStream fo = new FileOutputStream("HelloWorld.pdf");
        pdf.render(fo);
        fo.close();
    }
}

There are a few things to note here. First, the method of creating a "PKCS11" provider changed in Java 9 - you no longer need to call into the sun.* package. The config string is just the name of the provider (anything you like) and the path to the OpenSC "pkcs11" library, which is as shown for macOS, and probably /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so for Linux.

And here's the result.

You can also do this with our PDF Viewer. You'll need to configure it to use a PKCS11 when you run it, which is quite easy to do from the command line. You'll recognise the config string from the example above (and don't forget to add "type=pkcs11"). We recommend at least version 2.23.5, which included some fixes for smartcard use - this article was supposed to accompany that release, but was delayed while we struggled with the other parts of the equation.

java -Dorg.faceless.pdf2.viewer2.KeyStoreManager.params="type=pkcs11;name=OpenSC;library=/Library/OpenSC/lib/opensc-pkcs11.so;slot=1" org.faceless.pdf2.viewer2.PDFViewer

There you have it. Anyone can become at least an electronic resident of the EU.