12

7 Tips for Creating Easy-to-Validate PDF Signature - DZone Open Source

 2 years ago
source link: https://dzone.com/articles/7-tips-for-creating-pdf-signatures
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

7 Tips for Creating Easy-to-Validate PDF Signatures

Validating digital signatures in PDF documents can be complicated, but following these 7 tips will make the process easier to navigate.

Join the DZone community and get the full member experience.

Join For Free

Validating digital signatures in PDF documents is a tricky business, and contrary to popular belief, there’s no one true validation algorithm. I’m not going to try to tell you how to validate PDF signatures—that’d be hard to do in a blog post anyhow. Instead, I’ll turn things around, and try to give you some advice on how to produce PDF signatures that are easy to digest (pun intended) for validators.

Most of the content of this post is geared towards producing signatures for public workflows, i.e., cases where you don’t know who is going to need to validate your signature, so aiming for maximal interoperability is important. Most of the advice is still relevant for other, closed workflows as well, but it depends on the circumstances.

I’ll first provide some background on why signature validation is harder than it seems, and then we’ll move on to some concrete best practices, supplemented with iText code samples to show them in action.

Aspects of Signature Validation

Remember that asymmetric cryptography works with pairs of keys: a private key and a public key. Both are mathematically related. In a signing scheme that makes use of asymmetric cryptography, the private key is used to produce signatures, and the public key is used to verify them. As the name implies, the idea is that the private key is only ever accessible by the signer, while the public key is distributed to validating parties.

Validating a PDF signature involves many checks. Roughly, they break down into the following steps:

  1. checking whether the digest of the document matches the value in the signature container;
  2. cryptographic validation of the actual signature using the signer’s public key;
  3. matching the public key to its owner;
  4. vetting unauthenticated changes to the PDF file (if any).

Of these, only the last aspect is specific to PDF; the others apply to essentially any kind of digital signature out there.

The first two items can be summarized as “basic integrity checking”, and can actually become one and the same in some simple cases. These steps are essentially black-and-white: either the signature is cryptographically sound or it isn’t, and there’s no real room for interpretation. The only caveats are that both signer and validator have to take care to abide by applicable standards, and both need to understand all the cryptographic algorithms in play.

Items three and four are more interesting, and generally much less clear-cut. Let’s review those in a little more detail before moving on.

Trust Validation Explained

The process of matching a public key to its owner is often called trust validation. Trust validation is a cornerstone of asymmetric cryptography: knowing that a signature was produced by a particular private key is pointless if you don’t know who controls that key! After all, there’s nothing that mathematically prevents me from generating a key pair myself, distributing the public key, and claiming that it is actually the President’s personal key. If people were to take that at face value, it would allow me to forge signatures in the President’s name. Clearly, that shouldn’t be possible.

There are many ways to address this problem, but the mechanism used in PDF signing is based on X.509 certificates. Roughly speaking, a certificate contains a public key, together with an assertion by a so-called certificate authority (CA), essentially amounting to “I verified that the private key corresponding to this public key belongs to so-and-so”. The certificate itself is signed using the CA’s private key. If the validator trusts the issuing CA’s judgment, that’s the end of it.

Of course, in practice, things aren’t as simple. Here are some of the problems that the validator might have to deal with.

  • Who authorizes the certificate authorities? Taking certificates on faith just moves the problem. In practice, the validator needs to build a validation path consisting of multiple certificates, all the way to a trust root.
  • Certificates can expire or even be revoked (e.g., if the private key is compromised). While validating, the validator has to be able to check the status of the signer’s certificate at the time of signing.
  • How does the validator even know when the signature was produced?

All of these are questions with a variety of possible answers that depend on many variables, including the validator’s environment, internet access, trust configuration, … As a signer, you typically don’t really know what information the validator needs, but there are certain things that you can do to make their job a little easier. We’ll discuss some of those below.

Post-Signing Modifications

PDF files can actually be modified after signing without (necessarily) breaking the basic integrity of the signature, through PDF’s incremental update mechanism. An incremental update is made by appending data to the end of the file. Since PDF signatures explicitly encode the region in the file that is covered by the signature, such updates don’t affect the cryptographic integrity of the signature.

However, incremental updates can override any object in the file, so while they don’t present a problem for cryptographic validation, they can affect document content in all sorts of ways: modifying text, replacing pages, messing with form fields—anything goes. In other words, the onus is on the validator to check whether any incremental updates (if present) are legitimate or not.

So, why don’t validators just reject all incremental updates, then? While that’s certainly an option, it excludes some legitimate, common-use cases.

  • Multiple signatures: due to the way PDF signing works, the only way to have a PDF file with more than one signature is by putting them in separate incremental updates.
  • PAdES-related bookkeeping data: signatures following the PAdES-B-LT and PAdES-B-LTA profiles typically append validation-related data after signing, as an incremental update to the PDF.

To make matters worse, there are currently no standards governing this kind of validation, other than some vague guidelines in the PDF standard. As a signer, you can’t know exactly what the validator is going to check. This makes catering to the lowest common denominator a delicate balancing act.

Helpful Tips and Tricks

Now that you know what sort of problems validators have to deal with, let’s discuss how you can make their jobs a little easier.

For a More Reliable Trust Validation

Given that this post is primarily about public signing workflows, I’ll assume that the signer’s certificate you plan to use is one issued by a widely trusted commercial or government-backed CA (e.g., one on the AATL or EUTL).

If you’re writing code for private workflows, you should judge for yourself what’s still relevant to your use case.

In any case, if you have peculiar trust validation requirements, consider reading up on explicit policy-based electronic signatures—that’s out of scope for this post.

Tip 1: Embed All Relevant Certificates

When creating a signature, you typically want to embed the full chain of trust into the PDF file, from your signer’s certificate up to the root. If there are multiple valid chains of trusts, feel free to include those other certificates as well. The size increase is usually not significant, and it helps to account for differences in trust store configurations (not everyone validates against the AATL, for example).

Strictly speaking, including the root certificate itself is optional, and it shouldn’t matter for validation purposes. I like to include it anyway, if only for documentation purposes.

Fortunately, chain-of-trust embedding is almost automatic in iText. Here’s an example.

import com.itextpdf.kernel.pdf.PdfReader;

import com.itextpdf.kernel.pdf.StampingProperties;

import com.itextpdf.signatures.BouncyCastleDigest;

import com.itextpdf.signatures.PdfSigner;

import com.itextpdf.signatures.PrivateKeySignature;

 

import org.bouncycastle.jce.provider.BouncyCastleProvider;

 

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.security.GeneralSecurityException;

import java.security.KeyStore;

import java.security.PrivateKey;

import java.security.Security;

import java.security.cert.Certificate;

 

class EmbedChainExample {

    public static final String INPUT_DOCUMENT = "/path/to/input.pdf";

    public static final String OUTPUT_DOCUMENT = "/path/to/output.pdf";

 

    public static final String PKCS12_FILE = "/path/to/signer.pfx";

    public static final String PKCS12_PASSWORD = "secret";

 

 

    public static void embedChain() throws GeneralSecurityException, IOException {

        // Boilerplate to load key material from a PKCS#12 file

        // My PKCS#12 file contains the private key, signer's certificate 

        // and relevant CA certificates

        KeyStore ks = KeyStore.getInstance("PKCS12", "BC");

        try(InputStream fin = new FileInputStream(PKCS12_FILE)) {

            ks.load(fin, PKCS12_PASSWORD.toCharArray());

        }

        // There's only one entry in the keystore

        String alias = ks.aliases().nextElement();

        PrivateKey pk = (PrivateKey) ks.getKey(alias, PKCS12_PASSWORD.toCharArray());

 

        // The KeyStore implementation will grab all the relevant certificates,

        // (Note: the iText API expects the signer's certificate to be first)

        Certificate[] chain = ks.getCertificateChain(alias);

 

        // We just have the keys in memory, so PrivateKeySignature it is

        PrivateKeySignature inMemSigner = new PrivateKeySignature(pk, "sha256", "BC");

 

        try(PdfReader reader = new PdfReader(INPUT_DOCUMENT);

            OutputStream os = new FileOutputStream(OUTPUT_DOCUMENT)) {

            // Instantiate a PdfSigner on top of the opened document.

            PdfSigner pdfSigner = new PdfSigner(

                    reader, os, new StampingProperties().useAppendMode());

 

            // Set the signature field name (will be created if necessary)

            // (note: we're not setting any visual appearance here)

             pdfSigner.setFieldName("Signature1");

 

            // Tell the PdfSigner to include the chain

            pdfSigner.signDetached(

                    new BouncyCastleDigest(), inMemSigner,

                    chain, null, null, null, 0,

                     PdfSigner.CryptoStandard.CADES

            );

        }

    }

 

 

    public static void main(String[] args) throws Exception {

        BouncyCastleProvider provider = new BouncyCastleProvider();

        Security.addProvider(provider);

        embedChain();

    }

}


Tip 2: Add a Timestamp to Your Signature

For a validator to be able to reliably establish whether or not the signer’s certificate was valid at the time of signing, having a trusted record of when the signature was produced is essential. You can provide such a record by having a trusted time-stamping authority (TSA) issue a time stamp token for your signature.

Such a time-stamping token is signed by the TSA. If the validator trusts the TSA’s clock (this is also based on certificates), it serves as proof that the signature existed at the time indicated in the time-stamp token.

There are many TSAs that provide their services to the public free of charge.

Once you’ve picked a TSA that works for you, using it to timestamp a signature in iText requires only a few extra lines compared to the previous example.

import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.StampingProperties;
import com.itextpdf.signatures.BouncyCastleDigest;
import com.itextpdf.signatures.ITSAClient;
import com.itextpdf.signatures.PdfSigner;
import com.itextpdf.signatures.PrivateKeySignature;
import com.itextpdf.signatures.TSAClientBouncyCastle;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
class SignWithTimestampExample {
    public static final String INPUT_DOCUMENT = "/path/to/input.pdf";
    public static final String OUTPUT_DOCUMENT = "/path/to/output.pdf";
    public static final String PKCS12_FILE = "/path/to/signer.pfx";
    public static final String PKCS12_PASSWORD = "secret";


    // Note
     // The vast majority of these services operate over a plaintex
     // HTTP connection, i.e. not HTTPS.
     public static final String TSA_URL = "http://url.for.your/tsa/endpoint";

    public static void signWithTimestamp() throws GeneralSecurityException, IOException {

        // Same as before
        KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
        try(InputStream fin = new FileInputStream(PKCS12_FILE)) {
            ks.load(fin, PKCS12_PASSWORD.toCharArray())
        }
        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, PKCS12_PASSWORD.toCharArray());
        Certificate[] chain = ks.getCertificateChain(alias);
        PrivateKeySignature inMemSigner = new PrivateKeySignature(pk, "sha256", "BC");


        // Instantiate a time stamping client
        ITSAClient tsaClient = new TSAClientBouncyCastle(TSA_URL);

        try(PdfReader reader = new PdfReader(INPUT_DOCUMENT);
            OutputStream os = new FileOutputStream(OUTPUT_DOCUMENT)) {
            PdfSigner pdfSigner = new PdfSigner(
                    reader, os, new StampingProperties().useAppendMode());
             pdfSigner.setFieldName("Signature1");
            // Tell the PdfSigner to include the chain, and pass along 
            // a reference to the TSA client
            pdfSigner.signDetached(
                    new BouncyCastleDigest(), inMemSigner,
                    chain, null, null, tsaClient, 0,
                    PdfSigner.CryptoStandard.CADES
            );
        }
    }

    public static void main(String[] args) throws Exception {
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);
        signWithTimestamp();
    }
}

Tip 3: Embed Revocation Information for all Certificates

In principle, all certificates that are not self-signed are subject to revocation checking. However, obtaining accurate revocation information for a certificate at some time in the past is not typically feasible, especially for certificates that have long since expired. As such, it’s usually a good idea to embed that revocation information together with the signature. There are two revocation checking mechanisms in common use:

  • Certificate Revocation Lists (CRLs). As the name implies, a CRL is a list of revoked certificates, periodically published by the CA. CRLs typically have a validity period of several days to several weeks.
  • The Online Certificate Status Protocol (OCSP). This is a protocol that allows you to request live status information for a specific certificate from an authorized service (the OCSP responder). The validity window of an OCSP response is typically much shorter than that of a CRL.

URLs for relevant CRL or OCSP services are usually embedded into certificates issued by a CA. While CRLs are quite a bit older than OCSP, both are still widely used, and they have different security tradeoffs. As a signer, you should know that CRLs can get quite large for some CAs. Hence, a good rule of thumb is to try to grab an OCSP response, and fall back to CRLs if OCSP is not available.

In PDF, there are two ways to embed revocation information.

  • Legacy Adobe style: embed the revocation information into the signature container, as a signed attribute;
  • PAdES-LT style: insert revocation information into the document security store (DSS), typically after signing.

These also each have their own advantages and drawbacks. When in doubt, going with PAdES is probably your best bet: PAdES signatures are standard fare in EU-based signing workflows, and the PAdES standards have been adopted in other jurisdictions as well.

The code sample for the next tip demonstrates the PAdES way of doing things.

Tip 4: When Using PAdES, Go for PAdES-B-LTA

Just like certificates, revocation information also has an expiry date. Therefore, timestamps are important not just to provide a record of the time of signing, but also to establish the integrity of revocation data. The signature time-stamp token that we discussed in Tip 2 isn’t sufficient in this case: it doesn’t cover changes made to the document security store (DSS) after signing.

Upgrading from PAdES-B-LT to PAdES-B-LTA fixes that, and it’s not particularly difficult either: it suffices to add in a document timestamp after updating the DSS.

Here’s an example that demonstrates both how to add revocation information to the document security store (to create a PAdES-B-LT signature), and how to add a document timestamp to turn the PAdES-B-LT signature into a PAdES-B-LTA one.

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.StampingProperties;
import com.itextpdf.signatures.BouncyCastleDigest;
import com.itextpdf.signatures.CrlClientOnline;
import com.itextpdf.signatures.ICrlClient;
import com.itextpdf.signatures.IOcspClient;
import com.itextpdf.signatures.ITSAClient;
import com.itextpdf.signatures.LtvVerification;
import com.itextpdf.signatures.OcspClientBouncyCastle;
import com.itextpdf.signatures.PdfSigner;
import com.itextpdf.signatures.PrivateKeySignature;
import com.itextpdf.signatures.TSAClientBouncyCastle;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;

class SignPAdESLTA {
    public static final String INPUT_DOCUMENT = "/path/to/input.pdf";
    public static final String OUTPUT_DOCUMENT = "/path/to/output.pdf";

    public static final String PKCS12_FILE = "/path/to/signer.pfx";
    public static final String PKCS12_PASSWORD = "secret";

    public static final String TSA_URL = "http://url.for.your/tsa/endpoint";

    public static void signPAdESLTA() throws GeneralSecurityException, IOException {
        // Same as before
        KeyStore ks = KeyStore.getInstance("PKCS12", "BC");
        try(InputStream fin = new FileInputStream(PKCS12_FILE)) {
            ks.load(fin, PKCS12_PASSWORD.toCharArray());
        }
        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, PKCS12_PASSWORD.toCharArray());
        Certificate[] chain = ks.getCertificateChain(alias);
        PrivateKeySignature inMemSigner = new PrivateKeySignature(pk, "sha256", "BC");

        // Time stamping client will be used for both the signature timestamp
        // and the PAdES-LTA document timestamp
        ITSAClient tsaClient = new TSAClientBouncyCastle(TSA_URL);

        // We're going to need two revisions this time, so we'll
        // write some intermediate output to an in-memory buffer

        ByteArrayOutputStream signedOutput = new ByteArrayOutputStream();

        // As before, we first produce a signed file with a signature time stamp

        try(signedOutput; PdfReader reader = new PdfReader(INPUT_DOCUMENT)) {
            PdfSigner pdfSigner = new PdfSigner(
                    reader, signedOutput, new StampingProperties().useAppendMode());
             pdfSigner.setFieldName("Signature1")
            pdfSigner.signDetached(
                    new BouncyCastleDigest(), inMemSigner,
                    chain, null, null, tsaClient, 0,
                    // Note: for PAdES signatures, using
                    // the CADES value is mandatory
                     PdfSigner.CryptoStandard.CADE
            );
        }

        // Next, append revocation information to the DSS (document security store)
        // We again keep the output in-memory, since we need to add the timestamp later
        //   (note: there's some needless byte array copying here, that can be avoided with
        //    better buffer discipline)
        ByteArrayOutputStream padesLTOutput = new ByteArrayOutputStream();
        try(InputStream is = new ByteArrayInputStream(signedOutput.toByteArray());
            PdfReader reader = new PdfReader(is);
            PdfWriter writer = new PdfWriter(padesLTOutput)) {
 

            // Note: here, append mode is critical, since we on't want to
            // destroy the signature we just create
            PdfDocument doc = new PdfDocument(reader, writer,
                    new StampingProperties().useAppendMode());

            // Add verification information to the DSS
            LtvVerification ltvVerification = new LtvVerification(doc);

            // Instantiate clients to fetch CRLs and OCSP responses
            ICrlClient crlClient = new CrlClientOnline();
            IOcspClient ocspClient = new OcspClientBouncyCastle(null);

            // Add the validation information for our previous
            // signature.
            ltvVerification.addVerification(
                    "Signature1", ocspClient, crlClient,
                    // We'll put revocation info for the full chain 
                    // of trust into the DSS
                     LtvVerification.CertificateOption.WHOLE_CHAIN,
                    // This option includes OCSP by default, and embeds 
                    // a CRL if OCSP is unavailable
                     LtvVerification.Level.OCSP_OPTIONAL_CRL,
                    // We'll also register all relevant certificates in the DSS
                    LtvVerification.CertificateInclusion.YES
            );

            // Append update with verification info to the file
            ltvVerification.merge();
            doc.close();
        }
        // We now have a PAdES-LT document. Let's turn it into a PAdES-LTA
        // document by appending a document timestamp.
        try(InputStream is = new ByteArrayInputStream(padesLTOutput.toByteArray());
            PdfReader reader = new PdfReader(is);
            OutputStream os = new FileOutputStream(OUTPUT_DOCUMENT)) {
            // Document timestamps are also produced by PdfSigner instances
            // (they're actually a special kind of signature in PDF internals)

            PdfSigner pdfSigner = new PdfSigner(
                    reader, os, new StampingProperties().useAppendMode());

            // Use the same TSA client to add in our timestamp
            pdfSigner.timestamp(tsaClient, "Timestamp1");
        }
    }
    public static void main(String[] args) throws Exception {
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);
        signPAdESLTA();
    }
}

What Happens After Signing?

Some document workflows require changes after the (first) signature was produced. Such documents are by no means rare: digital agreements with multiple parties require multiple signatures. In PDF-land, that means that the second signature is technically a modification applied to the file signed by the first signer.

For this kind of validation, there are much fewer hard and fast rules. As a consequence, it’s not always easy to figure out what is and isn’t allowed as a signer. Nonetheless, there are a number of general principles to live by; paying heed to those should reduce the possibility of things failing down the road.

Tip 5: Principle of Least Modification

In general, whenever you modify a signed document through incremental updates, you should strive to make as few modifications as possible. The fewer changes you apply, the lower the risk.

Tip 6: Set Up All Signature Fields First

When working with visible signatures (i.e., signatures that are visually rendered on the page), it’s good practice to set up all signature fields before creating the first signature. It’s easy to accidentally change something that rouses suspicion with the validator when adding fields, especially when complex operations like tagging are involved. Making sure the field setup is taken care of beforehand drastically reduces the potential for such mishaps.

Tip 7: Consider Using Certification Signatures

If you’re authoring a document as opposed to signing an existing one, you should consider whether certification signatures fit your use case. Essentially, a certification signature allows you to tell the validator what changes you expect to occur after your signature, which reduces ambiguity.

For some of the documents you create, you might want to allow for some kind of interactivity: a contract with multiple parties, a personalized form, etc. You could sign those documents with a certification signature that informs the validator that you want to allow form filling and signing, but nothing else. In other cases (e.g., an invoice to a customer), you don’t expect the document to change at all. In that case, you can add a certification signature that disallows all changes, including form filling. Explicit is better than implicit.

Having said that, it’s important to be aware of the limitations of what this certification mechanism can provide. We’ve recently commented on the do's-and-don'ts of certification signatures on our blog.

Let’s conclude with an example demonstrating how to create a minimal certified form.

import com.itextpdf.forms.PdfAcroForm;

import com.itextpdf.forms.fields.PdfFormField;

import com.itextpdf.kernel.geom.Rectangle;

import com.itextpdf.kernel.pdf.PdfDocument;

import com.itextpdf.kernel.pdf.PdfReader;

import com.itextpdf.kernel.pdf.PdfWriter;

import com.itextpdf.kernel.pdf.StampingProperties;

import com.itextpdf.signatures.BouncyCastleDigest;

import com.itextpdf.signatures.ITSAClient;

import com.itextpdf.signatures.PdfSigner;

import com.itextpdf.signatures.PrivateKeySignature;

 

import com.itextpdf.signatures.TSAClientBouncyCastle;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

 

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.security.GeneralSecurityException;

import java.security.KeyStore;

import java.security.PrivateKey;

import java.security.Security;

import java.security.cert.Certificate;

 

class CertifiedFormExample {

    public static final String OUTPUT_DOCUMENT = "/path/to/output.pdf";

 

    public static final String PKCS12_FILE = "/path/to/signer.pfx";

    public static final String PKCS12_PASSWORD = "secret";

 

    public static final String TSA_URL = "http://url.for.your/tsa/endpoint";

 

    public static void createCertifiedForm()

            throws GeneralSecurityException, IOException {

 

        // Set up a simple form with a visible signature field

        // to be signed later, and a text field

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try(baos; PdfWriter writer = new PdfWriter(baos);

            PdfDocument pdfDoc = new PdfDocument(writer)) {

 

            pdfDoc.addNewPage();

 

            PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);

 

            // Add a text field

            // Using absolute positioning for simplicity here.

            Rectangle textFieldRect = new Rectangle(36, 750, 300, 18);

            PdfFormField textField = PdfFormField.createText(pdfDoc, textFieldRect);

             textField.setFieldName("DemoTextField");

            form.addField(textField);

 

            // Add a visible signature field, for someone else to sign later

            // We do this here, rather than letting the other signer

            // create their own field (see Tip 6)

            Rectangle sigFieldRect = new Rectangle(36, 588, 300, 118);

            PdfFormField sigField = PdfFormField.createSignature(pdfDoc, sigFieldRect);

             sigField.setFieldName("UserSignature");

            form.addField(sigField);

        }

 

        ByteArrayInputStream unsignedForm = new ByteArrayInputStream(baos.toByteArray());

 

        // Prepare for signing in the same way as before

        KeyStore ks = KeyStore.getInstance("PKCS12", "BC");

        try(InputStream fin = new FileInputStream(PKCS12_FILE)) {

            ks.load(fin, PKCS12_PASSWORD.toCharArray());

        }

        String alias = ks.aliases().nextElement();

        PrivateKey pk = (PrivateKey) ks.getKey(alias, PKCS12_PASSWORD.toCharArray());

        Certificate[] chain = ks.getCertificateChain(alias);

        PrivateKeySignature inMemSigner = new PrivateKeySignature(pk, "sha256", "BC");

        ITSAClient tsaClient = new TSAClientBouncyCastle(TSA_URL);

 

        try(PdfReader reader = new PdfReader(unsignedForm);

            OutputStream os = new FileOutputStream(OUTPUT_DOCUMENT)) {

            PdfSigner pdfSigner = new PdfSigner(reader, os, new StampingProperties());

 

            // Add a new signature field to hold the certification signature

            // and set the DocMDP level to 2 (form filling allowed)

             pdfSigner.setFieldName("CertifyingSignature");

             pdfSigner.setCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING);

 

            // Proceed to create the signature as usual

            pdfSigner.signDetached(

                    new BouncyCastleDigest(), inMemSigner,

                    chain, null, null, tsaClient, 0,

                     PdfSigner.CryptoStandard.CADES

            );

        }

    }

 

    public static void main(String[] args) throws Exception {

        BouncyCastleProvider provider = new BouncyCastleProvider();

        Security.addProvider(provider);

        createCertifiedForm();

    }

Nonetheless, this kind of cryptographic Jedi mind trick is capable of fooling many real systems out there.

The AATL and EUTL are trust lists of certificate authorities maintained by Adobe. Of these two, the EUTL (based on the EU Trusted Lists) specifically caters to those of us with eIDAS-related requirements.

The observant reader will note that this only provides an upper bound on the time the signature was created. That’s good enough in most cases, but if you also need to record that the signature was created after a certain time, have a look at the content-time-stamp attribute in CAdES.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK