Validating Digital Signature
PdfReader.ValidateSignatures walks the document’s signature fields, recomputes the
digest over each signature’s byte ranges, and returns one result per signature. You
supply the handlers it may use; a handler with nil key and certificate is a
validation-only handler, since the certificate needed comes out of the signature
itself.
That is an integrity check and nothing more. It proves the bytes match the signature under the certificate embedded in the file, which whoever made the file chose. It does not prove who signed. For that, see Validating Signer Trust and Revocation.
Validating
handlerX509RSASHA1, err := sighandler.NewAdobeX509RSASHA1(nil, nil)
if err != nil {
return err
}
handlerPKCS7Detached, err := sighandler.NewAdobePKCS7Detached(nil, nil)
if err != nil {
return err
}
handlerPAdES, err := sighandler.NewEtsiPAdESLevelB(nil, nil, nil)
if err != nil {
return err
}
handlerDocTimeStamp, err := sighandler.NewDocTimeStamp("", 0)
if err != nil {
return err
}
res, err := reader.ValidateSignatures([]model.SignatureHandler{
handlerX509RSASHA1,
handlerPKCS7Detached,
handlerPAdES,
handlerDocTimeStamp,
})
if err != nil {
return err
}
if len(res) == 0 {
return errors.New("no signature fields found")
}
var failed []string
for i, item := range res {
fmt.Printf("--- Signature %d\n%s\n", i+1, item.String())
if !item.IsSigned || !item.IsVerified {
failed = append(failed, strconv.Itoa(i+1))
}
}
if len(failed) > 0 {
return fmt.Errorf("validation failed for signature(s) %s", strings.Join(failed, ", "))
}The four handlers cover the subfilters UniPDF signs with: adbe.x509.rsa_sha1,
adbe.pkcs7.detached, ETSI.CAdES.detached for PAdES at any level, and
ETSI.RFC3161 for document timestamps.
Handlers are matched by /Filter and /SubFilter and the list is scanned in order,
first match wins. Each of these four claims a different subfilter, so their order
does not matter. It does matter when a handler wraps another: put the more specific one
first, or the general one will claim the signature. A DocMDP-certified signature, for
instance, is also a valid adbe.pkcs7.detached signature, so a DocMDPHandler placed
after a plain PKCS#7 handler never gets a chance to check the document’s modifications.
Reading the result
| Field | Meaning |
|---|---|
IsSigned | The field has a signature dictionary and the handler validated it without an error. False when the handler’s Validate fails, for example on tampered bytes or a malformed PKCS#7 package. |
IsVerified | The digest matched under the embedded certificate. Not proof of identity. |
IsTrusted | Always false here. Only ValidateSignaturesWithOpts with trust roots sets it. |
TrustErrors | Says no trust roots configured for every result of ValidateSignatures. |
Errors | Why validation failed, as strings. |
Fields | The signature field the result belongs to. |
DiffResults | Modification analysis, present only for DocMDP validation. |
IsCrlFound, IsOcspFound | Revocation data found in the signature. Only the PAdES handler sets these here. |
Certificates | The certificate chain embedded in the signature, signer first. |
IsCoveringEntireFile, IsCoveringEntireRevision, RevisionsAfterSignature, ContentsGapOnly | How much of the file the signed byte range covers. |
IsVerified is the field this check fills in. String() prints
Trusted: Untrusted certificate with no trust roots configured under it for every
signature, and outside PAdES it prints that no CRL or OCSP data was found. Those lines
describe what this call did not check, not a problem with the signature.
To decide whether the signer is trusted, pass your trust roots to
ValidateSignaturesWithOpts instead, with the same handlers:
res, err := reader.ValidateSignaturesWithOpts(handlers, &model.SignatureValidationOpts{
Roots: roots,
})Validating Signer Trust and Revocation covers the roots, the validation time and revocation checks.
Limitations
A document without an AcroForm, or with an AcroForm holding no fields, gets nil, nil
back: no error and no results. That is why the example checks the length separately
from the error.
Validation failures do not surface as the returned error. They land in
result.Errors on the individual result, with IsVerified false. The returned error
is reserved for problems reading the file. A signature the handler rejects, such as
one over tampered bytes, comes back with IsSigned and IsVerified both false and
the handler’s error in Errors. If no handler in your list is applicable, the result
comes back with IsSigned true, IsVerified false, and handler not set in
Errors: the signature is there, but none of your handlers supports its type.
A signature over an early revision of an incrementally updated document verifies
cleanly for the bytes it covers, while later revisions changed the rest.
IsCoveringEntireRevision and RevisionsAfterSignature tell you that happened, but
not whether the later changes were acceptable. Judging that is the DocMDP handler’s
job, so validating a certified document means wrapping the base handler in
sighandler.NewDocMDPHandler.
A signature whose subfilter none of the handlers claims gets handler not set.
The PAdES and document timestamp handlers are in the list for that reason; what
they check is covered in Validating PAdES Signatures
and Validate a Timestamp Signature.
Unlike signing, validation does not need a seekable source. It re-reads the signed
byte ranges through the parser, so a reader built with model.NewPdfReaderFromParser
can validate signatures even though it cannot be passed to NewPdfAppender.
Run the example
The example registers the four handlers above and prints the String() form of every
result, which covers the name, date, reason, location and the flags above. A file with
no signature fields ends with Fail: no signature fields found. If any signature is not
signed or does not verify, the example prints every result first, then exits with
status 1 and names the failures, for example
Fail: validation failed for signature(s) 2.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/signatures/validation
go run pdf_sign_validate.go <INPUT_PDF_PATH>If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.