Skip to content
Validating Digital Signature

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

FieldMeaning
IsSignedThe 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.
IsVerifiedThe digest matched under the embedded certificate. Not proof of identity.
IsTrustedAlways false here. Only ValidateSignaturesWithOpts with trust roots sets it.
TrustErrorsSays no trust roots configured for every result of ValidateSignatures.
ErrorsWhy validation failed, as strings.
FieldsThe signature field the result belongs to.
DiffResultsModification analysis, present only for DocMDP validation.
IsCrlFound, IsOcspFoundRevocation data found in the signature. Only the PAdES handler sets these here.
CertificatesThe certificate chain embedded in the signature, signer first.
IsCoveringEntireFile, IsCoveringEntireRevision, RevisionsAfterSignature, ContentsGapOnlyHow 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.

View the full source
Last updated on