I'm trying to build an application where the following happens:
- A client requests a PDF-hash from the server.
- The server generates the hash of a PDF-file and sends this to the client.
- The client signs this hash with his private key and sends the signed hash along with the public part of his own certificate.
- The server generates a new, signed PDF file.
The problem I'm having with this: It seems impossible for the server to generate a to-be-signed hash without having the client's certificate available beforehand. I'd really prefer to create a solution where the server does not need to know the client's certificate in order to create the document digest.
All the examples I have found so far use the PdfPKCS7.getAuthenticatedAttributeBytes function to get the to-be-signed hash, but this requires a client certificate to be known. I have looked at the "Digital Signatures for PDF documents" white paper by Bruno Lowagie, but I failed to see exactly what information is digested.
Here's a code snippet of my current attempt:
public byte[] simplePresign(String src, String digestAlgorithm) throws IOException, DocumentException, GeneralSecurityException {
this.digestAlgorithm = digestAlgorithm;
tsaClient = new CustomTSAClient();
PdfReader reader = new PdfReader(src);
os = new ByteArrayOutputStream();
PdfAStamper stamper = PdfAStamper.createSignature(reader, os, '\0', PdfAConformanceLevel.PDF_A_1B);
appearance = stamper.getSignatureAppearance();
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
appearance.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, getEstimatedSize(null, tsaClient) * 2 + 2);
appearance.preClose(exc);
InputStream data = appearance.getRangeStream();
MessageDigest mDigest = DigestAlgorithms.getMessageDigest(digestAlgorithm, null);
return DigestAlgorithms.digest(data, mDigest);
}
Unfortunately this hash does not seem to be correct, signing this hash and generating a signed document based on the signed hash leads to an invalid signature.
I would be most grateful if someone can help me improve this code snippet, or otherwise give me some insight in the data that I need to digest for a signature.
It seems that you have overlooked the DeferredSigning example.
In this example, we first create a PDF with an empty signature:
Granted, the public certificate
chain[0]
is passed to theappearance
in this example, it is used to create the visual appearance and to create thePdfPKCS7
object.Once you have a PDF with an empty signature, you can create a
PdfSignatureAppearance
on the server and get the hash that can be sent to the client for signing. This can be done using thegetRangeStream()
method to get the ranges of PDF bytes that need to be hashed. This method returns anInputStream
, that can be used like this:Now you can send this
sh
to the client for signing. You will receive anotherbyte[]
which is the actual signature that needs to be added to the PDF, let's say thebyte[]
is calledsig
.Your external signature container can be kept very simple: it just needs to return the signature bytes:
You can now use the
createSignature()
method on the server: