gettalong / hexapdf

Versatile PDF creation and manipulation for Ruby
https://hexapdf.gettalong.org
Other
1.21k stars 69 forks source link

Signatures not recognised as valid when including and image #251

Closed jotolo closed 1 year ago

jotolo commented 1 year ago

Hi @gettalong,

I have been using HexaPDF a lot lately and something happen to me when I tried to use some of the functionalities it provides. First, I was trying to get a valid signature with my certificates using a HSM, we talked about this here: https://github.com/gettalong/hexapdf/issues/187#issuecomment-1611789263 and it worked well. Then, I tried adding an image to the pdf to simulate the signature of a user (something I had working before).

When this is done, suddenly the signature is not recognised by Adobe anymore, same certificates and keys, nothing changed. At first, I thought it has something to do with the external_signing option. I tried using the private key directly to sign and I got the same result, signature not being recognised by Adobe. After that, I have tried to not use the external_signing mechanism and pass the key directly into the sign method as explained in the documentation. This option also failed. At last, I tried the example provided in the documentation to add a visual using my private key and that did work without issues. I tried again the example from the docs, modifying it a bit to include an image (script provided) and again, it didn't work.

image = File.new( 'signature.png', 'rb')
doc = HexaPDF::Document.open(Rails.root.join('document.pdf'))
sig_field = doc.acro_form(create: true).create_signature_field('signature')
widget = sig_field.create_widget(doc.pages[0], Rect: [20, 500, 120, 600])
widget.create_appearance.canvas.xobject(image, at: [0, 0], width: 100, height: 100)
doc.sign("signed_with_image.pdf", signature: sig_field,
                                  key: key, 
                                  certificate: certificate,
                                  certificate_chain: [certificate_sub_ca, certificate_root])

The image is shown perfectly. The problem is that, for some reason, something changed within the document that affect the signature and the ability for Adobe Reader to recognise it as valid. If we skip the image part, the signature is recognised.

Any idea what could be creating this issue? Thanks in advance

version: hexapdf (0.32.2)

jotolo commented 1 year ago

This is what I mean when I mentioned the signature validity is not recognised by Adobe. The certificate we use is valid and the signature is recognised if we don't add the image to the document. Maybe something is being changed that is affecting the way Adobe verify the signature? I tried with different images to make sure this specific images was not the issue. I am trying to figure out any differences but unfortunately I am a newbie on this topic.

CleanShot 2023-06-30 at 10 59 22

gettalong commented 1 year ago

Hmm... I'm not sure why that happens. When viewing it in others viewers it shows correctly.

What happens if you right click on "Rev. 1: ..." and select "Validate signature"? And what does "Show signature properties" say?

gettalong commented 1 year ago

If you run the following code

require 'hexapdf'
require HexaPDF.data_dir + '/cert/demo_cert.rb'

image = 'signature.png'
doc = HexaPDF::Document.open('minimal.pdf') # from test/data/minimal.pdf in HexaPDF repo
sig_field = doc.acro_form(create: true).create_signature_field('signature')
widget = sig_field.create_widget(doc.pages[0], Rect: [20, 20, 120, 120])
widget.create_appearance.canvas.xobject(image, at: [0, 0], width: 100, height: 100)
doc.sign("signed_with_image.pdf", signature: sig_field,
                                  key: HexaPDF.demo_cert.key,
                                  certificate: HexaPDF.demo_cert.cert,
                                  certificate_chain: [HexaPDF.demo_cert.sub_ca,
                                                      HexaPDF.demo_cert.root_ca])

does it have the same effect in Adobe Reader? And does it still have the same effect if you manually trust the HexaPDF demo root certificate?

jotolo commented 1 year ago

@gettalong I ran the code and I can confirm it does have the same effect in Adobe Reader. I manually trusted the HexaPDF demo root certificate but the result was the same. I tried opening and closing the reader in case it needed to reload the certificates but it shows the same result unfortunately.

The strange thing is, I verified the process with 2 different readers. Once I trusted the HexaPDF certificate in the other reader the signature is shown as valid, same as with our certificate. This does not happen with Adobe Reader.

jotolo commented 1 year ago

What happens if you right click on "Rev. 1: ..." and select "Validate signature"? And what does "Show signature properties" say?

CleanShot 2023-06-30 at 12 23 57 CleanShot 2023-06-30 at 12 25 10

jotolo commented 1 year ago

This is the result when used with our certificate and without image. What I can see here is that Adobe is not able to recognize the Hash Algorithm nor the Signature Algorithm. If I repeat the process with our certificate and including the image the same happens, Adobe is not able to recognize Hash nor Signature Algorithms. Without those I think it won't be able to validate the signatures?

CleanShot 2023-06-30 at 12 28 55

gettalong commented 1 year ago

Thanks @jotolo! So Adobe Reader expects a PDF dictionary instance somewhere, I will just have to find out where :grin:

As for the problem with the hash and signature algorithms: Those aren't stored in a PDF data structure but in the signature. So my guess is that this information is not correctly shown because of the other error.

gettalong commented 1 year ago

@jotolo Oops, that was easier than thought: https://stackoverflow.com/questions/72935931/pdf-signature-expected-a-dict-object describes the problem and has the solution.

Modify the line where the widget's appearance gets created like this:

image = widget.create_appearance.canvas.xobject(image, at: [0, 0], width: 100, height: 100)
image[:Resources] = {}

So we add that unnecessary /Resources hash for the image object. I tried it out and it worked for me!

Could you verify that this work for you?

jotolo commented 1 year ago

@gettalong It does work! 😄 By adding the :Resources to the image Adobe recognise it. I even tried with multiple signers and with multiple signatures, all of them containing an image and all of them worked!

Do you think this assignment should happen internally in HexaPDF?

Thanks again for checking this so quickly and providing the right guidance. It is really appreciated! 🤝

gettalong commented 1 year ago

Thanks for testing this out!

Yes, I think this should be in HexaPDF. However, I really dislike adding such work-arounds...

jotolo commented 1 year ago

I don't know exactly how it works but maybe it could be the default value for this type of object? I noticed it didn't have it before. Maybe a simple set up during initialization works.

gettalong commented 1 year ago

I don't know exactly how it works but maybe it could be the default value for this type of object? I noticed it didn't have it before. Maybe a simple set up during initialization works.

Yeah, that could easily be done in lib/hexapdf/type/image.rb but then the size of all image objects increases, by not much but still. So I wanna first see if I can narrow down this work-around to just images in signatures.

gettalong commented 1 year ago

@jotolo I have added the work-around to the method for creating digital signatures. So with the next version of HexaPDF this will now work without any additional fixes on your side.

jotolo commented 1 year ago

Thanks @gettalong ! Amazing work!