Digitally signing a PDF with the GlobalSign Digital Signing Service
GlobalSign are one of the larger players in the world of Digital Identity, and a new innovation from them is their Digital Signing Service, which enables the signature applied to a PDF to be generated via a web service.
This offers an alternative to the local management of a Hardware Security Module (HSM), and you can think of it as an HSM at the end of a web-service; although it provides a bit more flexibility than a traditional HSM when it comes to identity management. You can read a bit more about it in their own blog article and service datasheet.
Support for this service was added to our API in release 2.22, and this article, along with the API docs will hopefully provide enough information to get started: although you will need an account for the service to do so, which you'd need to get from GlobalSign.
Let's start with the code:
String apikey = "apikey"; // supplied by GlobalSign String apisecret = "apisecret"; // supplied by GlobalSign String keystore = "keystore.p12"; // path to KeyStore containing client certificate char password = "password".toCharArray(); // password for KeyStore PDF pdf = new PDF(new PDFReader(new File("input.pdf"))); GlobalSignDSSManager gs = new GlobalSignDSSManager(); KeyStore keystore = KeyStore.getInstance("PKCS12"); keystore.load(new FileInputStream(keystore), password); gs.setLogin(keystore, password, apikey, apisecret); FormSignature sig = new FormSignature(); pdf.getForm().getElements().put("Sig1", sig); sig.sign(null, null, null, gs.createSignatureHandlerFactory(new X500Principal("OU=Test Unit"))); pdf.render(new FileOutputStream("output.pdf")));
This code will load PDF, apply a new invisible signature to it using GlobalSign's service, and save it out to another file. In order to run it there are a few things that need to be set up.
First, you need an account with GlobalSign for the service. They were kind enough to grant us a test account, which involved creating a key pair and sending them the public half.
openssl genrsa -des3 -out privatekey.key 2048 –sha256 openssl rsa -in privatekey.key -pubout -out pubkey.pem
We sent them our "pubkey.pem", and received back an X.509 certificate and encrypted login credentials. The next step was to import the certificate and private key into a PKCS#12 keystore that can be accessed from Java.
openssl pkcs12 -export -name myalias -in pubkey.crt -inkey privatekey.key -out keystore.p12
The certificate is used as an HTTPS client certificate by the web service, and the private key is required to decrypt the login credentials. You can decrypt them with the following command (the "apikey" and "apisecret" within are referenced in the code above):
openssl rsautl -decrypt -inkey privatekey.key -in credentials.txt.enc -out credentials.txt
However this step is not necessary; using the key in the KeyStore, the API can decrypt the encypted credentials file on demand when supplied with the contents of the encrypted file to the appropriate API method. Choose whichever approach you find more convenient.
With the keystore, the password to access it and the login credentials (decrypted or otherwise) that is most of what's required to run the above code. The final step is to specify an identity to sign the PDF with, and the details of that depend on what type of account you have.
Creating an Identity
The signing identity is created by sending certain fields to the network service, which responds with a signed X.509 certificate combining those fields with the fixed fields stored by GlobalSign. Which fields are specified and which are fixed depends on the account with GlobalSign: for our test corporate account, we needed only to supply an Organizational Unit which you can see in the code above. Other types of account may have different requirements.
Our API can extract them from an X.500 identity as shown above, but also from an X.509 Certificate or a JSON structure, the details of which are defined by the API documentation supplied by GlobalSign. The X.509 or JSON approach allows the specification of ETSI EN 319 412-5 qcStatements, which may be required to ensure the certificate fulfils specific legal requirements for qualified certificates according to a defined legal framework.
Supplying an incorrect set of fields will result in an HTTP 422 Error from the signing service, which will be reported as an GeneralSecurityException during signing. The text of the Exception should help identify the issue, but more useful is the Validation Policy, which can be queried as part of the web service. This is a JSON structure which looks like it might be something the API could query to identify the fields to supply: in practice, the documentation on how to do this automatically is currently a bit thin. As it will probably only have to be done once, it's easy enough to read and interpret with the aid of GlobalSign's documentation.
This web service is an interesting option. The resulting signatures tick many boxes: they use SHA-256, are timestamped, have Long-Term Validation information included by default, and the integration with our API fairly painless. For mid-sized organisations that want the benefits of digital signatures without the complexity that comes with local management of cryptographic data, this might be something to consider.