unidoc / unipdf

Golang PDF library for creating and processing PDF files (pure go)
https://unidoc.io
Other
2.56k stars 251 forks source link

[BUG] Digital signature is invalid with version 3.26 #463

Closed ZaiatsDmytro closed 3 years ago

ZaiatsDmytro commented 3 years ago

Description

We use UniPdf version 3.9.0 to sign the documents on production. When we try to update the UniPdf version up to 3.26 all functionality related to digital signature become broken

Expected Behavior

Digital signature should be valid after update

Actual Behavior

Steps to reproduce the behavior:

  1. Use version UniPdf 3.9 with provided example
  2. Digital signature is valid (Example: grid-3-test-signed_3_9.pdf)
  3. Switch to version 3.26 and run provided example again
  4. Digital signature is invalid (Example: grid-3-test-signed_3_26.pdf)

Attachments

example_invalid_digital_signature.zip

github-actions[bot] commented 3 years ago

Welcome! Thanks for posting your first issue. The way things work here is that while customer issues are prioritized, other issues go into our backlog where they are assessed and fitted into the roadmap when suitable. If you need to get this done, consider buying a license which also enables you to use it in your commercial products. More information can be found on https://unidoc.io/

gunnsth commented 3 years ago

Initially looking at this. Seems like unipdf-examples/signatures/pdf_sign_validate.go example was working on grid-3-test-signed_3_9.pdf until v3.11.1

The change that causes the validation to fail was that

func getHashFromSignatureAlgorithm(sa x509.SignatureAlgorithm) (crypto.Hash, bool) {
    return crypto.SHA1, false
}

was updated to

func getHashFromSignatureAlgorithm(sa x509.SignatureAlgorithm) (crypto.Hash, bool) {
    var hash crypto.Hash
    switch sa {
    case x509.SHA1WithRSA:
        hash = crypto.SHA1
    case x509.SHA256WithRSA:
        hash = crypto.SHA256
    case x509.SHA384WithRSA:
        hash = crypto.SHA384
    case x509.SHA512WithRSA:
        hash = crypto.SHA512
    default:
        return crypto.SHA1, false
    }

    return hash, true
}

See https://github.com/unidoc/unipdf/issues/401 where this change was suggested and actually should be an improvement.

The signature algorithm is specified in the certificate

h, _ := getHashFromSignatureAlgorithm(certificate.SignatureAlgorithm)

This is used both in signing and validation. So my guess would be that this is related to that. If we print out the SignatureAlgorithm used here, we get SHA256-RSA

So the hash should have been a x509.SHA256WithRSA but was x509.SHA1WithRSA which Adobe seems happy with even though the certificate says otherwise.

Looking at the PDF specifications, we see: image

It is possible that since the PDF file version is 1.4, that Adobe thinks only SHA1 should be used here. One way to test that would be to simply change the PDF file version to 1.6. Unfortunately Adobe does not provide much information about the problem.

It would be best if you can open a support ticket in our service desk to continue with the case as we might need some further information and files, including the certificates and such.

gunnsth commented 3 years ago

@ZaiatsDmytro Can you try using

handler, err := sighandler.NewAdobeX509RSASHA1(cert, signFunc)
if err != nil {
    log.Fatalf("Fail: %v\n", err)
}

instead of using the custom signature function. I believe the problem with it is the hardcoded crypto.SHA1 in the custom handler (as per above comments also). Using NewAdobeX509RSASHA1 should handle this automatically, for the certificate with specified SHA256, it will select that automatically.

gunnsth commented 3 years ago

After further thought, it is probably not correct to be getting the signature algorithm from the certificate. Instead, the user can decide this based on the table above, or use SHA1 by default. For validation we will determine the signature from the PKCS#1 package.

gunnsth commented 3 years ago

Fixed in UniPDF v3.28.0, where for signing, uses SHA1 by default, or can be specified by user as an option. For validation, detects the algorithm basd on the PKCS#1 package.