4

I'm trying to build an application where the following happens:

  1. A client requests a PDF-hash from the server.
  2. The server generates the hash of a PDF-file and sends this to the client.
  3. The client signs this hash with his private key and sends the signed hash along with the public part of his own certificate.
  4. 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.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
Niels
  • 125
  • 2
  • 4
  • **A** Does your client create a naked PKCS#1 signature or a complete PKCS#7 signatrue container? **B** I don't see you injecting the client's signature. Have you made sure it uses the exact same signature appearance object? **C** Can you share a sample PDF signed by your code? – mkl Mar 23 '15 at 13:26
  • Note that the [tag:signature] is not correct as that's about *method* signatures. – Maarten Bodewes Apr 08 '15 at 21:46

2 Answers2

5

It seems that you have overlooked the DeferredSigning example.

In this example, we first create a PDF with an empty signature:

public void emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
    appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
    appearance.setCertificate(chain[0]);
    ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    MakeSignature.signExternalContainer(appearance, external, 8192);
}

Granted, the public certificate chain[0] is passed to the appearance in this example, it is used to create the visual appearance and to create the PdfPKCS7 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 the getRangeStream() method to get the ranges of PDF bytes that need to be hashed. This method returns an InputStream, that can be used like this:

BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
byte[] hash = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
Calendar cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);

Now you can send this sh to the client for signing. You will receive another byte[] which is the actual signature that needs to be added to the PDF, let's say the byte[] is called sig.

Your external signature container can be kept very simple: it just needs to return the signature bytes:

class MyExternalSignatureContainer implements ExternalSignatureContainer {
    protected byte[] sig;
    public MyExternalSignatureContainer(byte[] sig) {
        this.sig = sig;
    }
    public byte[] sign(InputStream is) throws Exception {
        return sig;
    }
    public void modifySigningDictionary(PdfDictionary signDic) {
    }
}

You can now use the createSignature() method on the server:

public void createSignature(String src, String dest, String fieldname, PrivateKey pk, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
    
    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    ExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
    MakeSignature.signDeferred(reader, fieldname, os, external);
}
Stamos
  • 3,938
  • 1
  • 22
  • 48
Bruno Lowagie
  • 75,994
  • 9
  • 109
  • 165
  • Thank you for the reply. I feel like I may have forgotten to mention something: I do not want the client to use iText, the only responsibility the client should have is signing a PDF hash with it's private key. The example you have given me is very interesting, but it requires the client to create a PdfPKCS7 object. Is there any way to accomplish what I'm trying to do without having the client rely on iText? – Niels Mar 23 '15 at 13:41
  • Er... I think you aren't reading the example correctly. I know at least two customers (Zetes and Trust1Team) who use this example on their servers and they don't require the `PdfPKCS7` object to be created on the client. It should be obvious that they created their own `MyExternalSignatureContainer` to achieve this. – Bruno Lowagie Mar 23 '15 at 13:57
  • I have updated the example so that you understand what you're overlooking. It seems that you are creating a SaaS solution for signing. Make sure that you either open source your complete code base as soon as you bring this into production, or that you get a commercial license (you have at least two competitors in Belgium who are paying customers). – Bruno Lowagie Mar 23 '15 at 14:15
  • Thank you for clearing this up and thank you for the great amount of help! I'm very close to getting this to work the way I want, there's just one last thing that I'm not getting: In order to create 'sh' you need the 'PdfPkcs7' object on the server, but as far as I know you cannot make this object without having a valid certificate chain. So unless there's a different way to create the object I'll still need the client certificate on the server for creating the signable hash. – Niels Mar 24 '15 at 09:55
  • I think the other companies transfer the public certificate to the server first. I was in a hurry when I answered the question initially. Now I remember that the signer certificate is an authenticated attribute for CAdES signatures and the `PdfPKCS7` constructor will throw an exception if you don't pass at least one certificate in the `chain`. – Bruno Lowagie Mar 24 '15 at 10:20
  • Ok, so just to be clear (I'll close the question after this): In the end there is no way to generate the unsigned hash without having the client's certificate available? – Niels Mar 24 '15 at 11:49
  • I think it's theoretically possible for CMS signatures, but I should check PAdES-2 to be certain. Maybe @mkl knows off the cuff. However, with the way iText is currently implemented, the client's certificate is necessary. – Bruno Lowagie Mar 24 '15 at 11:55
  • Hhmmm, for some reason that "@ m k l" did not show up in my inbox. Anyways, I hope my answer to the OP's [follow-up question](http://stackoverflow.com/q/29251895/1729265) clarified things a bit. CAdES / PAdES are groups of examples for profiles requiring the signer certificate to be known before constructing the signed attributes of the signature container to embed. – mkl Mar 25 '15 at 10:49
  • From where exactly do I get the InputStream for hash calculation? Can I use the PdfSignatureAppearance object from the emptySignature appereance? – AlexandruC Aug 18 '16 at 13:18
  • @AlexandruC. That's a strange question. You don't need to trigger the `sign(InputStream is)` method yourself. It is triggered by iText when you pass the `MyExternalSignatureContainer` as a parameter to the `MakeSignature.signDeferred()` method. Maybe you should look at the full example to understand what happens: https://sourceforge.net/p/itext/code/HEAD/tree/tutorial/signatures/src/main/java/signatures/chapter4/C4_09_DeferredSigning.java – Bruno Lowagie Aug 18 '16 at 13:45
  • I need the hash so I can externally sign it, the hash is obtained by DigestAlgorithms.digest(is ..), how do I get _is_ (InputStream)? this is my question. – AlexandruC Aug 18 '16 at 13:50
  • You say: "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." . Do I need to create another PdfSignatureAppearance on the blank signed PDF document to get InputStream via getRangeStream? – AlexandruC Aug 18 '16 at 13:51
  • I don't know how else to explain it. To get the hash, you need to implement the `ExternalSignatureContainer` and get the `is` through the `sign()` method that is triggered by iText. Use that `InputStream` to make the digest. If you can't process the `InputStream` immediately, return zeroes for the signature. Create another `ExternalSignatureContainer` to put the signed bytes in the document. – Bruno Lowagie Aug 18 '16 at 14:16
  • Thank you for your time, your example is correct, my problem was in the way I was signing the hash. – AlexandruC Aug 18 '16 at 16:39
0

In itext7 there is class

com.itextpdf.signatures.PdfPKCS7

In this class there are 2 members of interest

Collection<Certificate> signCerts;

private X509Certificate signCert;

these are private, If we make them public or a setter method for these variables then it becomes easy.

I did it as below

  1. Created PdfPKCS7 object with dummy certificate and retrieved getAuthenticatedAttributeBytes() to get tbs bytes. Which i sent for raw siging.

  2. After I received raw signed bytes i set these 2 variables to actual final certificates that i received from signer app or service and then it worked

for now i created new PdfPKCS7 class exact copy and made these 2 variables public.

Vijay
  • 2,021
  • 4
  • 24
  • 33
  • Your solution requests two signatures from the signing app for each PDF signature. In contexts where you have to pay or enter a PIN for each signature this may be a no-go. – mkl Jun 19 '23 at 09:36
  • @mkl it need to enter pin only once. not twice. – Vijay Jun 30 '23 at 11:24
  • Ah, OK. But that would not work for PAdES, merely for more simple signatures without signer identity security. – mkl Jun 30 '23 at 11:52
  • @mkl only signer certificate and raw signed data available. – Vijay Jun 30 '23 at 12:56